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

от автора


Уже давно считается хорошим тоном отдавать контент сайта на языке, предпочитаемым пользователем. Некоторые сервера определяют язык по месту нахождения пользователя с помощью модулей геолокации, остальные берут настройки браузера. Языковые предпочтения пользователя часто сохраняются в 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 для чего-то отличного от выбора языка. Но это вопрос архитектуры сайта и легко решается при проектировании.

ссылка на оригинал статьи http://habrahabr.ru/company/ruvpn/blog/183060/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *