├── LICENSE ├── README.textile ├── config └── src └── ngx_http_accept_language_module.c /LICENSE: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2008 Guillaume Maury (http://gom-jabbar.org) 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions 6 | * are met: 7 | * 1. Redistributions of source code must retain the above copyright 8 | * notice, this list of conditions and the following disclaimer. 9 | * 2. Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * 13 | * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND 14 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16 | * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE 17 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 18 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 19 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 20 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 21 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 22 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 23 | * SUCH DAMAGE. 24 | * 25 | */ 26 | -------------------------------------------------------------------------------- /README.textile: -------------------------------------------------------------------------------- 1 | h1. Nginx Accept Language module 2 | 3 | This module parses the Accept-Language header and gives the most suitable locale for the user from a list of supported locales from your website. 4 | 5 | h3. Syntax 6 | 7 | set_from_accept_language $lang en ja pl; 8 | - `$lang` is the variable in which to store the locale 9 | - `en ja pl` are the locales supported by your website 10 | 11 | If none of the locales from accept_language is available on your website, it sets the variable to the first locale of your website's supported locales (in this case en) 12 | 13 | h4. Caveat 14 | 15 | It currently assumes that the accept-language is sorted by quality values (from my tests it's the case for safari, firefox, opera and ie) and discards q (see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html). In the situation where I'm using the module, this assumption works... but buyer beware :-) 16 | 17 | h3. Example configuration 18 | 19 | If you have different subdomains for each languages 20 | 21 | server { 22 | listen 80; 23 | server_name your_domain.com; 24 | set_from_accept_language $lang en ja zh; 25 | rewrite ^/(.*) http://$lang.your_domain.com redirect; 26 | } 27 | 28 | 29 | Or you could do something like this, redirecting people coming to '/' to /en (or /pt) 30 | 31 | 32 | location / { 33 | set_from_accept_language $lang pt en; 34 | if ( $request_uri ~ ^/$ ) { 35 | rewrite ^/$ /$lang redirect; 36 | break; 37 | } 38 | } 39 | 40 | 41 | 42 | h3. Why did I create it? 43 | 44 | I'm using page caching with merb on a multi-lingual website and I needed a way to serve the correct language page from the cache 45 | I'll soon put an example on http://gom-jabbar.org 46 | 47 | h3. Bugs 48 | 49 | Send Bugs to Guillaume Maury (dev@gom-jabbar.org) 50 | 51 | h3. Acknowledgement 52 | 53 | Thanks to Evan Miller for his guide on writing nginx modules (http://emiller.info/nginx-modules-guide.html) -------------------------------------------------------------------------------- /config: -------------------------------------------------------------------------------- 1 | ngx_feature= 2 | ngx_feature_name= 3 | ngx_feature_run=no 4 | ngx_feature_incs= 5 | ngx_feature_path= 6 | ngx_feature_libs= 7 | ngx_feature_test= 8 | have=NGX_HTTP_HEADERS . auto/have 9 | . auto/feature 10 | 11 | if [ $ngx_found = yes ]; then 12 | ngx_addon_name=ngx_http_accept_language_module 13 | HTTP_MODULES="$HTTP_MODULES ngx_http_accept_language_module" 14 | NGX_ADDON_SRCS="$NGX_ADDON_SRCS ${ngx_addon_dir}/src/ngx_http_accept_language_module.c" 15 | else 16 | cat << END 17 | $0: error: unable to configure the ngx_http_accept_language_module. 18 | END 19 | exit 1 20 | fi 21 | -------------------------------------------------------------------------------- /src/ngx_http_accept_language_module.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | static char *ngx_http_accept_language(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); 6 | static ngx_int_t ngx_http_accept_language_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); 7 | 8 | static ngx_command_t ngx_http_accept_language_commands[] = { 9 | 10 | { ngx_string("set_from_accept_language"), 11 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE, 12 | ngx_http_accept_language, 13 | NGX_HTTP_MAIN_CONF_OFFSET, 14 | 0, 15 | NULL }, 16 | ngx_null_command 17 | }; 18 | 19 | // No need for any configuration callback 20 | static ngx_http_module_t ngx_http_accept_language_module_ctx = { 21 | NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL 22 | }; 23 | 24 | ngx_module_t ngx_http_accept_language_module = { 25 | NGX_MODULE_V1, 26 | &ngx_http_accept_language_module_ctx, /* module context */ 27 | ngx_http_accept_language_commands, /* module directives */ 28 | NGX_HTTP_MODULE, /* module type */ 29 | NULL, /* init master */ 30 | NULL, /* init module */ 31 | NULL, /* init process */ 32 | NULL, /* init thread */ 33 | NULL, /* exit thread */ 34 | NULL, /* exit process */ 35 | NULL, /* exit master */ 36 | NGX_MODULE_V1_PADDING 37 | }; 38 | 39 | static char * ngx_http_accept_language(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) 40 | { 41 | ngx_uint_t i; 42 | ngx_str_t *value, *elt, name; 43 | ngx_http_variable_t *var; 44 | ngx_array_t *langs_array; 45 | 46 | value = cf->args->elts; 47 | name = value[1]; 48 | 49 | if (name.data[0] != '$') { 50 | ngx_conf_log_error(NGX_LOG_WARN, cf, 0, "\"%V\" variable name should start with '$'", &name); 51 | } else { 52 | name.len--; 53 | name.data++; 54 | } 55 | 56 | var = ngx_http_add_variable(cf, &name, NGX_HTTP_VAR_CHANGEABLE); 57 | if (var == NULL) { 58 | return NGX_CONF_ERROR; 59 | } 60 | if (var->get_handler != NULL) { 61 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "variable already defined: \"%V\"", &name); 62 | return NGX_CONF_ERROR; 63 | } 64 | 65 | var->get_handler = ngx_http_accept_language_variable; 66 | langs_array = ngx_array_create(cf->pool, cf->args->nelts - 1, sizeof(ngx_str_t)); 67 | if (langs_array == NULL) { 68 | return NGX_CONF_ERROR; 69 | } 70 | var->data = (uintptr_t) langs_array; 71 | 72 | for (i = 2; i < cf->args->nelts; i++) { 73 | elt = ngx_array_push(langs_array); 74 | if (elt == NULL) { 75 | return NGX_CONF_ERROR; 76 | } 77 | 78 | *elt = value[i]; 79 | } 80 | 81 | return NGX_CONF_OK; 82 | } 83 | 84 | static ngx_int_t ngx_http_accept_language_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) 85 | { 86 | ngx_uint_t i = 0; 87 | ngx_uint_t found = 0; 88 | u_char *start, *pos, *end; 89 | ngx_array_t *langs_array = (ngx_array_t *) data; 90 | ngx_str_t *langs = (ngx_str_t *) langs_array->elts; 91 | 92 | if ( NULL != r->headers_in.accept_language ) { 93 | start = r->headers_in.accept_language->value.data; 94 | end = start + r->headers_in.accept_language->value.len; 95 | 96 | while (start < end) { 97 | // eating spaces 98 | while (start < end && *start == ' ') {start++; } 99 | 100 | pos = start; 101 | 102 | while (pos < end && *pos != ',' && *pos != ';') { pos++; } 103 | 104 | for (i = 0; i < langs_array->nelts; i++) 105 | { 106 | if ((ngx_uint_t)(pos - start) >= langs[i].len && ngx_strncasecmp(start, langs[i].data, langs[i].len) == 0) { 107 | found = 1; 108 | break; 109 | } 110 | } 111 | if (found) 112 | break; 113 | 114 | i = 0; // If not found default to the first language from the list 115 | 116 | // We discard the quality value 117 | if (*pos == ';') { 118 | while (pos < end && *pos != ',') {pos++; } 119 | } 120 | if (*pos == ',') { 121 | pos++; 122 | } 123 | 124 | start = pos; 125 | } 126 | } 127 | 128 | v->data = langs[i].data; 129 | v->len = langs[i].len; 130 | 131 | /* Set all required params */ 132 | v->valid = 1; 133 | v->no_cacheable = 0; 134 | v->not_found = 0; 135 | return NGX_OK; 136 | } 137 | --------------------------------------------------------------------------------