...

среда, 12 июня 2013 г.

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

This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at fivefilters.org/content-only/faq.php#publishers. Five Filters recommends: 'You Say What You Like, Because They Like What You Say' - http://www.medialens.org/index.php/alerts/alert-archive/alerts-2013/731-you-say-what-you-like-because-they-like-what-you-say.html


Комментариев нет:

Отправить комментарий