如何根据客户端浏览器的语言在 nginx 中重写位置?

Posted

技术标签:

【中文标题】如何根据客户端浏览器的语言在 nginx 中重写位置?【英文标题】:How to rewrite location in nginx depending on the client-browser's language? 【发布时间】:2011-04-09 03:03:59 【问题描述】:

例如: 我的浏览器接受语言是“uk,ru,en”。 当我请求位置 mysite.org 时,nginx 必须转发到 mysite.org/uk

【问题讨论】:

【参考方案1】:

当您无法将 AcceptLanguageModule 模块添加到系统中时,您可以通过此设置管理 $language_suffix。

rewrite (.*) $1/$http_accept_language

更有弹性的方法是使用地图:

map $http_accept_language $lang 
        default en;
        ~es es;
        ~fr fr;


...

rewrite (.*) $1/$lang;

【讨论】:

其实~和表达式之间不应该有空格。 这需要 AcceptLanguageModule 吗? 不适合我,它总是把我带到英文页面,即使我将浏览器配置为仅限法语 这不需要 AcceptLanguageModule 此映射不支持首选项。要改进此映射,您可以将正则表达式从 ~es es; 更改为 ~*^es es;,这将仅将语言映射到出现在 Accept-Language 标头上的第一种语言(假设浏览器首先提供首选语言,我认为非常频繁)【参考方案2】:

使用 AcceptLanguageModule 的缺点是您不能再依赖自动系统更新。每次 nginx 更新(甚至是安全更新),你都必须自己编译 Nginx。 第二个缺点是模块假定接受语言已经按质量值排序。 我更喜欢 Lua,因为它可以在基于 debian 的发行版中轻松安装:

apt-get install nginx-extras

我的同事 Fillipo 在 Lua 中制作了很棒的 nginx-http-accept-lang 脚本。它正确处理质量值并相应地重定向用户。 我已经为该脚本制作了small modification。它接受支持的语言作为输入参数,并根据 Accept-Language 标头返回最合格的语言。使用返回值,您可以做任何您想做的事情。可用于重写、设置 lang cookie ...

我仅将语言确定用于根路径(位置 = /)。并且用户 lang cookie 优先于浏览器。 我的 nginx 配置文件如下所示:

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;
   

【讨论】:

这很好,但是为什么不对原始脚本进行 PR 呢? 受此解决方案的启发,我提出了自己的支持获取参数和 cookie 的解决方案。你可以在这里试试:github.com/mallocator/nginx-lua-lang【参考方案3】:

我认为使用 nginx map $http_accept_language 不是一个好主意,因为 它不尊重质量价值(Accept-Language 标头中的q)。 假设您有:

map $http_accept_language $lang 
    default en;
    ~en en;
    ~da da;

客户端会发送Accept-Language: da, en-gb;q=0.8, en;q=0.7

使用 nginx 映射将始终将 $lang 映射到 en,因为它只是在标头字符串中查找。 但正确的映射将是$lang = da(因为在这种情况下,Danisch 的质量值q=1 大于英语q=0.7) 更多信息请参见 RFC:http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html

【讨论】:

我想这是对@brian-coca 答案的评论?【参考方案4】:

好的,我遇到了同样的问题,并且“滥用”Lua 以根据浏览器语言进行重定向。

# 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")
    ';

注意:NGINx 需要将 liblua 编译到它上面。对于 Debian/Ubuntu:

apt-get install nginx-extras

【讨论】:

是否也可以检查 url 是否以 mysite.org 结尾? 如果 http_accept_language 为空,这将引发内部服务器错误,如果有人想使用它,请务必在 lua 脚本的第一行在ngx.var.http_accept_language 上添加对nil 的检查。 【参考方案5】:

我知道这是一个非常古老的线程,但我在尝试解决相同问题时发现了它。只是想分享我最终得到的解决方案。与上面发布的不同,Accept-Language 中好像提到了几种语言,它会在我们可以服务的语言中选择第一个提到的。

    #
    # 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;

此解决方案的局限性在于它假定 Accept-Language 标头中的语言按其偏好顺序列出。通常这是正确的,但不是官方要求的。例如,如果标题是“Accept-Language: da, en-US;q=0.1, pt-BR;q=1”,则变量 $lang 将设置为“en”,因为它甚至位于“pt”之前虽然 pt 有更大的权重。

在没有外部脚本的情况下,在 nginx 中似乎不可能在考虑所有权重的情况下选择正确的语言。在所有实际情况下,该解决方案对我来说都足够好,并且不需要任何外部模块。

【讨论】:

> 这个解决方案在所有实际情况下对我来说都足够好,并且不需要任何外部模块。虽然是真的,但谁愿意与这种正则表达式地狱搏斗? @whaefelinger 好吧,我知道谁想要,我知道。 :-) 这里提到的所有好的解决方案都需要为 nginx 安装一些额外的东西。我觉得安装除了 nginx 之外的任何东西对于这样一个小任务来说都是多余的。所以我花了半个小时,想出了一个解决问题的正则表达式。我想为什么不分享它,也许它可以帮助那些不认为正则表达式是地狱的人? > [..] 谁不认为正则表达式是地狱? :-) 这不是我写的。我只是想象一个可怜的家伙必须支持大量的语言,然后出了点问题(除了你的解决方案假定降序)。 顺便说一句,添加 LUA 很容易并且得到很好的支持。 LUA 占用空间小,速度非常快。因此,LUA 似乎是一个不错的候选者。但是,标准 LUA 中的正则表达式支持很差。此外还需要第 3 方库。然后在 NGINX 中的集成可能会变得讨厌。但是,我知道 LUA 已被 javascript 正式取代。 @whaefelinger,是的,我同意用我的方法支持大量语言可能不是一个好主意。但是,对于我的 5 种语言,我将坚持使用正则表达式,而不是仅仅为此任务安装 LUA。【参考方案6】:

简单的解决方案,没有 MapModule 和 AcceptLanguageModule :

   if ( $http_accept_language ~ ^(..) ) 
         set $lang $1;
   
   set $args hl=$lang&$args;

请注意,“set $args hl=$lang&$args”在“hl”查询参数中设置所需的语言代码(例如“en”、“fr”、“es”等)。 当然,如果查询参数不适合,您可以在其他重写规则中使用 $lang。 示例:

location ~/my/dir/path/ 
          rewrite ^/my/dir/path/ /my/dir/path/$1/ break;
          proxy_pass http://upstream_server;
   

【讨论】:

【参考方案7】:

上面的 Lua 示例很好,但如果浏览器不发送任何 Accept-Language 标头,则会失败并返回错误 500。

在上面加上这个:

if ngx.var.http_accept_language == nil then
ngx.redirect("/en/")
end

【讨论】:

【参考方案8】:

您可以使用nginx_accept_language_module。 Nginx 必须重新编译,但它的工作量比集成 Lua 少。

Link to github

【讨论】:

【参考方案9】:

除了上面不尊重语言偏好的@Marks 答案。这是将 Accept-Language Header 值解析为语言和偏好值的 LUA 代码块

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

申请时,我得到:

$ 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]

最终我使用的不是像下面这样的 LUA 脚本来重定向:

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))

这给出了:

$ lua test.lua 'en-US;q=.7 , de;q=0.9 , fr ; q = 0.1 , dk ; q  =  1 '
winner: dk

【讨论】:

【参考方案10】:

所以这是原始问题的编译示例(基于我的案例验证可与 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;

【讨论】:

我们应该在哪里写地图部分?在 http 部分内? 我已经引用了特定 nginx.conf 的顶层部分。

以上是关于如何根据客户端浏览器的语言在 nginx 中重写位置?的主要内容,如果未能解决你的问题,请参考以下文章

如何根据语言重写自定义帖子类型 slug?

关于nginx你可能不知道的秘密----nginx地址重写以及错误页面配置

如何正确设置nginx中remote

9. Nginx Rewrite 功能

如何让nginx支持ThinkPHP框架

能不能通过 nginx 判断 url 参数,返回不同页面