How to rewrite 开发者_StackOverflowlocation in nginx depending on the client-browser's language?
For example: My browser accept-language is 'uk,ru,en'. When I request location mysite.org nginx must forward to mysite.org/uk
You can manage $language_suffix by this setting when you cannot add AcceptLanguageModule module into your system.
rewrite (.*) $1/$http_accept_language
A more resilient approach would use a map:
map $http_accept_language $lang {
default en;
~es es;
~fr fr;
}
...
rewrite (.*) $1/$lang;
The downside of using AcceptLanguageModule is you cannot rely on automatic system updates anymore. And with every nginx update (even security one), you have to compile Nginx yourself. The second downside is that module assumes that the accept-language is sorted by quality values already. I rather prefer Lua because it can be installed easily in debian based distros:
apt-get install nginx-extras
My colleague Fillipo made great nginx-http-accept-lang script in Lua. It correctly handles quality values and does redirect user accordingly. I've made small modification to that script. It accepts supported languages as input parameter and returns the most qualified language according to Accept-Language header. With returned value you can do whatever you want. It can be used for rewrites, setting lang cookie ...
I'm only using language determination for root path only (location = /). And user lang cookie has preference over browser. My nginx conf looks like this:
map $cookie_lang $pref_lang {
default "";
~en en;
~sk sk;
}
server {
listen 80 default_server;
root /usr/share/nginx/html;
index index.html index.htm;
# Make site accessible from http://localhost/
server_name localhost;
location = / {
# $lang_sup holds comma separated languages supported by site
set $lang_sup "en,sk";
set_by_lua_file $lang /etc/nginx/lang.lua $lang_sup;
if ($pref_lang) {
set $lang $pref_lang;
}
add_header Set-Cookie lang=$lang;
rewrite (.*) $scheme://$server_name/$lang$1;
}
location / {
# First attempt to serve request as file, then
# as directory, then fall back to displaying a 404.
try_files $uri $uri/ =404;
}
}
I think it's not good idea to use nginx map $http_accept_language
because
it does not honor quality value (q
in Accept-Language
header).
Let's imagine you have:
map $http_accept_language $lang {
default en;
~en en;
~da da;
}
And client will send Accept-Language: da, en-gb;q=0.8, en;q=0.7
Using nginx map will always map $lang
to en
because it simply find in header string.
But correct mapping will be $lang = da
(because Danisch has quality value q=1
which is bigger then English q=0.7
in this case)
More on this in RFC: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
Okay, I've had the same problem and "misuse" Lua to make a redirect possible based on the browser language.
# Use Lua for HTTP redirect so the site works
# without the proxy backend.
location = / {
rewrite_by_lua '
for lang in (ngx.var.http_accept_language .. ","):gmatch("([^,]*),") do
if string.sub(lang, 0, 2) == "en" then
ngx.redirect("/en/index.html")
end
if string.sub(lang, 0, 2) == "nl" then
ngx.redirect("/nl/index.html")
end
if string.sub(lang, 0, 2) == "de" then
ngx.redirect("/de/index.html")
end
end
ngx.redirect("/en/index.html")
';
}
Note: NGINx needs to have liblua compiled to it. For Debian/Ubuntu:
apt-get install nginx-extras
I know this is a very old thread, but I found it when trying to solve the same problem. Just wanted to share the solution I finally came with. It is different to the ones published above as if there are several languages mentioned in the Accept-Language, it will pick the first mentioned among the ones we can serve.
#
# Determine what language to redirect to
# this sets the value of $lang variable to the language depending on the contents of Accept-Language request header
# the regexp pattern automatically matches a known language that is not preceded by another known language
# If no known language is found, it uses some heuristics (like RU for (uk|be|ky|kk|ba|tt|uz|sr|mk|bg) languages)
#
map $http_accept_language $lang {
default en;
"~*^((|,)\s*(?!(ru|es|fr|pt|en))\w+(-\w+)?(;q=[\d\.]+)?)*(|,)\s*en\b" en;
"~*^((|,)\s*(?!(ru|es|fr|pt|en))\w+(-\w+)?(;q=[\d\.]+)?)*(|,)\s*es\b" es;
"~*^((|,)\s*(?!(ru|es|fr|pt|en))\w+(-\w+)?(;q=[\d\.]+)?)*(|,)\s*ru\b" ru;
"~*^((|,)\s*(?!(ru|es|fr|pt|en))\w+(-\w+)?(;q=[\d\.]+)?)*(|,)\s*fr\b" fr;
"~*^((|,)\s*(?!(ru|es|fr|pt|en))\w+(-\w+)?(;q=[\d\.]+)?)*(|,)\s*pt\b" pt;
"~*(^|,)\s*(uk|be|ky|kk|ba|tt|uz|sr|mk|bg)\b" ru;
"~*(^|,)\s*(ca|gl)\b" es;
}
...
rewrite (.*) $1/$lang;
The limitation of this solution is that it assumes the languages in the Accept-Language header are listed in the order of their preference. Usually this is true, but it is not officially required. For example, if the header is "Accept-Language: da, en-US;q=0.1, pt-BR;q=1", the variable $lang will be set to "en" because it comes before "pt" even though pt has larger weight.
Choosing the right language taking into account all the weights does not seem to be possible in nginx without external scripts. This solution was good enough for me in all practical cases and it did not require any external modules.
Simple solution, without MapModule and AcceptLanguageModule :
if ( $http_accept_language ~ ^(..) ) {
set $lang $1;
}
set $args hl=$lang&$args;
Note that the "set $args hl=$lang&$args" sets the desired language code (eg. "en", "fr", "es", etc) in the "hl" query parameter. Of course you can use $lang in other rewriting rules if the query parameter does not fit. Example:
location ~/my/dir/path/ {
rewrite ^/my/dir/path/ /my/dir/path/$1/ break;
proxy_pass http://upstream_server;
}
Lua example above is fine, but will fail with error 500 if browser does not send any Accept-Language headers.
Add this on top of it:
if ngx.var.http_accept_language == nil then
ngx.redirect("/en/")
end
You can use nginx_accept_language_module. Nginx has to be recompiled but its less work than integrating Lua.
Link to github
In addition to @Marks answer above which does not honor language preferences. Here's a LUA chunk of code parsing Accept-Language Header
value into language and preference value
-- need two LUA regex cause LUA's default regex is pretty broken
-- In my opinion a killer argument against using / supporting LUA
rx = "%s*([a-zA-Z-]+)%s*;%s*q%s*=%s*(%d*.?%d+)"
rx2 = "%s*([a-zA-Z-]+)%s*"
-- (arg .. ",") => concatenation operation
for chunk in (arg .. ","):gmatch("([^,]*),") do
lang, q = string.match(chunk, rx)
if (not lang) then
lang = string.match(chunk, rx2)
q = 1.0
end
print(string.format("lang=[%s] q=[%s]",lang, tonumber(q * 1.0)))
end
When applying, I'm getting:
$ lua demo.lua 'en-US , de , fr ; q = 0.1 , dk;q=1 '
lang=[en-US] q=[1.0]
lang=[de] q=[1.0]
lang=[fr] q=[0.1]
lang=[dk] q=[1.0]
$ lua demo.lua ' de'
lang=[de] q=[1.0]
$ lua demo.lua ' de;'
lang=[de] q=[1.0]
$ lua demo.lua ' de;q'
lang=[de] q=[1.0]
$ lua demo.lua ' de;q='
lang=[de] q=[1.0]
$ lua demo.lua ' de;q=0'
lang=[de] q=[0.0]
$ lua demo.lua ' de;q=0.1'
lang=[de] q=[0.1]
Eventually I'm using than a LUA script like below to redirect:
rx = "%s*([a-zA-Z-]+)%s*;%s*q%s*=%s*(%d*.?%d+)"
rx2 = "%s*([a-zA-Z-]+)%s*"
sup = {de = 0, en = 0, dk = 0} -- supported languages
win = {lang = "en", q = 0} -- default values / winner
for chunk in (arg[1] .. ","):gmatch("([^,]*),") do
lang, q = string.match(chunk, rx)
if (not lang) then
lang = string.match(chunk, rx2)
q = 1.0
end
lang = string.lower(lang)
-- handle only supported languages
if (sup[lang]) then
q = tonumber(q)
-- update winner table if a better match is found
if (win.q < q) then
win.q = q
win.lang = lang
end
end
end
-- which language pref?
print(string.format("winner: %s",win.lang))
This gives:
$ lua test.lua 'en-US;q=.7 , de;q=0.9 , fr ; q = 0.1 , dk ; q = 1 '
winner: dk
So here's the compiled example for the original question (based on my case verified to work with nginx-1.1.x):
map $http_accept_language $lang {
default en;
~ru ru;
~uk uk;
}
server {
server_name mysite.org;
# ...
rewrite (.*) http://mysite.org/$lang$1;
}
精彩评论