├── 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 |
--------------------------------------------------------------------------------