Pull to refresh
0

Настраиваем NGINX для мультиязычных сайтов

Reading time4 min
Views26K

Уже давно считается хорошим тоном отдавать контент сайта на языке, предпочитаемом пользователем. Некоторые сервера определяют язык по месту нахождения пользователя с помощью модулей геолокации, остальные берут настройки браузера. Языковые предпочтения пользователя часто сохраняются в cookie, и затем используются при повторном визите.

Какой метод определения языка пользователя подходит лучше – вопрос достаточно спорный. Мой личный ранг значимости языковой информации (в порядке убывания): cookie, настройки браузера, регион.

Для поисковых систем, социальных сетей и прочих агрегаторов информации важно знать, на каком языке должна быть проиндексирована или загружена страница, например в качестве миниатюры в хронику фейсбука. Это значит, что ссылка должна однозначно указывать на язык.

Распространенные варианты кодирования языковой информации о ресурсе следующие:
  • каждая языковая версия на отдельном субдомене, например en.example.com, ru.example.com
  • язык ресурса указывается в префиксе URI, например example.com/en/, example.com/ru
  • язык ресурса указывается в GET параметре, например example.com?lang=en, example.com?lang=ru

Первый вариант наиболее радикальный, каждая языковая версия сайта рассматривается как отдельный ресурс. Могут возникнуть сложности с SSL сертификатом, необходимо заранее предусмотреть все возможные варианты в SAN DNS Host Name, или заказать сертификат с маской, например *.example.com.

Второй вариант наиболее практичный, выбор языка входит в URI, значит, не будет проблем с индексацией и копированием ссылки.

Третий вариант выглядит менее привычно, требует дополнительной логики при добавлении остальных GET параметров и может смутить пользователя при копировании ссылки. Не самый лучший вариант для публичных ссылок.
image
Я расскажу о реализации второго варианта на базе сервера NGINX. При минимальных изменениях можно применить описанные настройки и для первого варианта.

Настройка состоит из нескольких этапов.

Сначала проверяется языковая настройка браузера. Если у пользователя установлены cookie, то это значение переписывает настройку браузера. Итоговое значение передается в переменную $lang.

На первом этапе надо настроить back-end на получение GET параметра с информацией о языке. То есть реализуем третий вариант из списка, но внутри нашей системы. Для примера будем рассматривать GET параметр locale=<код локали>, используем двухбуквенный код ISO 639-1.

Надо удостовериться, что при переходе на ссылку типа http://<адрес_back-end_сервера>?locale=ru мы получает ответ на русском языке.
После этого можно настраивать NGINX на фронтенде.

Второй этап – получение языковых настроек от пользователя. Подразумевается, что при посещении сервер устанавливает cookie в браузере клиента с предпочтительным языком. Cookie называется $lang.

В конфигурации сайта пишем

map $http_accept_language $browser_lang {
        default en;
        ~ru ru;
}
map $cookie_lang $lang {
    default $browser_lang;
    ~en en;
    ~ru ru;
}

Сначала надо выделить запрос типа /NN/* в отдельный локейшен. Применяем регулярные выражения с выделением переменных.

location ~ '^/(?<lang_code>[\D-]{2})/(?<rest_uri>.*)'

Сохраняем двухсимвольный код в переменную $lang_code, все остальное в переменную $rest_uri

Так же можно перенаправлять близкие языки на один существующий, например для украинской или белорусской локали лучше показать сайт на русском, чем на английском.

if ($lang_code ~* (uk|be)) {
                                return 301 http://$host/ru/$rest_uri$is_args$args;
                                }

Если код неизвестен, то используется английский вариант сайта.

if ($lang_code !~* (en|ru)) {
                                return 301 http://$host/en/$rest_uri$is_args$args;
                                }

Для if-конструкций порядок расположения имеет значение. Поэтому сначала надо ставить блок на проверку соответствия, и только в конце – на проверку несоответствия.

Далее надо очистить пользовательскую ссылку от возможного использования параметра locale в GET запросе. Неизвестно, как поведет себя back-end, если на него послать дублирующие аргументы, типа ?locale=en&locale=ru. Поэтому если пользователь пришел со ссылкой example.com/en/?locale=ru, то locale=ru на back-end лучше не посылать.

if ($args ~ (.*)locale=[^&]*(.*)) {
          set $args $1$2;
}

Убираем повторяющиеся амперсанды

 if ($args ~ (.*)&&+(.*)) {
         set $args $1&$2;
}

Убираем амперсанд в начале

if ($args ~ ^&(.*)) {
          set $args $1;
}

Убираем амперсанд в конце

if ($args ~ (.*)&$) {
          set $args $1;
}

Все, осталось только передать необходимые параметры на back-end. У меня в примере все идет на группу серверов, прописанных как back-end в разделе конфигурации upstream.

proxy_pass      http://back-end/$rest_uri?locale=$lang_code&$args;

Итоговая конфигурация выглядит примерно так
## get locale
map $http_accept_language $browser_lang {
        default en;
        ~ru ru;
}
map $cookie_lang $lang {
    default $browser_lang;
    ~en en;
    ~ru ru;
}

upstream back-end {
        ip_hash;
        server 172.21.71.15:8080; # vm-deb-osl-scala-1
        server 172.21.71.16:8080; # vm-deb-osl-scala-2
        server 172.21.71.17:8080; # vm-deb-osl-scala-3
        server 172.21.71.18:8080; # vm-deb-osl-scala-4
        keepalive 32;
}
server {
        listen  109.233.59.100:80;
        server_name  ruvpn.net;

location / {
# Redirect to locale
        return 301 http://$host/$lang$uri$is_args$args;
     }

# Handle URL with locale
location ~ '^/(?<lang_code>[\w-]{2})/(?<rest_uri>.*)' {
# Redirect to Russian for some CIS countries
                 if ($lang_code ~* (uk|be|kk)) {
                                return 301 http://$host/ru/$rest_uri$is_args$args;
                }
# Redirect to English for unknown languages
                if ($lang_code !~* (en|ru)) {
                                return 301 http://$host/en/$rest_uri$is_args$args;
                  }
	
                        if ($args ~ (.*)locale=[^&]*(.*)) {
                                set $args $1$2;
                                }
# Cleanup any repeated & introduced
                        if ($args ~ (.*)&&+(.*)) {
                                set $args $1&$2;
                                }
# Cleanup leading &
                        if ($args ~ ^&(.*)) {
                                set $args $1;
                                }
# Cleanup ending &
                        if ($args ~ (.*)&$) {
                                set $args $1;
                                }

   proxy_pass      http://back-end/$rest_uri?locale=$lang_code&$args;
   include         /etc/nginx/proxy.conf;
}

Можно проверить, как это работает на реальном сайте. Как вы уже заметили из примера конфигурации, по такой схеме настроен ресурс http://ruvpn.net. Все запросы типа ruvpn.net/ru/product/details/4 будут отображать страницу на русском, в то время как запрос ruvpn.net/sv/product/details/4 будет переадресован на ruvpn.net/en/product/details/4, так как шведского варианта сайта не существует. При переходе на корневую ссылку ruvpn.net, произойдет автоматическая переадресация на ruvpn.net/ru или ruvpn.net/en, в зависимости от ваших языковых настроек.
Единственным недостатком описанного метода является то, что нельзя использовать ссылки с двумя символами в начале URI для чего-то отличного от выбора языка. Но это вопрос архитектуры сайта и легко решается при проектировании.
Tags:
Hubs:
Total votes 55: ↑50 and ↓5+45
Comments22

Articles

Information

Website
ruvpn.net
Registered
Founded
Employees
2–10 employees
Location
Норвегия