Статьи

Установка и управление локалями в Rails i18n

Одна из предыдущих статей была посвящена I18n в Rails . Мы говорили о хранении и получении переводов, локализации приложения и других полезных вещей. Однако мы не обсуждали различные способы управления локализацией между запросами.

По умолчанию Rails собирается использовать языковой стандарт, заданный в I18n.default_locale(который является :enили любым другим значением, определенным вами в конфигурации), или значение, I18n.localeесли оно было явно определено. Конечно, если приложение поддерживает несколько языков, его пользователям нужен способ изменить локаль, и их выбор следует сохранить. Поэтому в этой статье мы рассмотрим следующие решения:

  • Введите имя локали в качестве параметра GET (example.com?locale=en)
  • Предоставьте это как часть доменного имени (en.example.com)
  • Установите локаль на основе агента пользователя, отправленного браузером
  • Установите локаль в зависимости от местоположения пользователя

Исходный код демонстрационного приложения доступен на GitHub .

Кстати, если вы только начинаете изучать Rails, вот большой список полезных ресурсов.

Подготовка приложения

В этой демонстрации я собираюсь использовать Rails 5.0.0.1, но описанные концепции применимы и к более старым версиям. Для начала создайте новое приложение без набора тестов по умолчанию:

    $ rails new Localizer -T

Чтобы обеспечить поддержку дополнительных языков, включите гем rails-i18n в свой Gemfile :

Gemfile

    [...]
    gem 'rails-i18n'
    [...]

Установить его

    $ bundle install

В этой демонстрации я собираюсь оказать поддержку английскому и польскому языкам, но вы можете выбрать что-нибудь еще. Давайте явно определим поддерживаемые локали:

конфиг / application.rb

    config.i18n.available_locales = [:en, :pl]

Также быстро настройте две маленькие страницы, управляемые PagesController:

pages_controller.rb

    class PagesController < ApplicationController
    end

просмотров / страниц / index.html.erb

    <h1><%= t('.title') %></h1>

    <%= link_to t('pages.about.title'), about_path %>

* Вид / страницы / about.html.erb

    <h1><%= t('.title') %></h1>

    <%= link_to t('pages.index.title'), root_path %>

Не забывайте, что tметод является псевдонимом для I18n.translateи ищет перевод на
основе предоставленного ключа. Вот наши переводы:

конфиг / локали / en.yml

    en:
      pages:
        index:
          title: 'Welcome!'
        about:
          title: 'About us'

конфиг / локали / pl.yml

    pl:
      pages:
        index:
          title: 'Powitanie!'
        about:
          title: 'O nas'

Пока мы называем эти ключи, основываясь на именах контроллера и представления, внутри файла .html.erb мы можем просто сказать, t('.title')опуская части pages.indexили pages.about.

Установите маршруты:

конфиг / routes.rb

  get '/about', to: 'pages#about', as: :about
  root 'pages#index'

Наконец, предоставьте ссылки для изменения локали сайта (пока URL-адреса будут пустыми):

общий / _change_locale.html.erb

    <ul>
      <li><%= link_to 'English', '#' %></li>.
      <li><%= link_to 'Polska', '#' %></li>
    </ul>

Визуализируйте этот фрагмент внутри макета:

макеты / application.html.erb

    <%= render 'shared/change_locale' %>

Ницца! Подготовка завершена, и мы можем перейти к основной части.

Настройка локали на основе доменного имени

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

Чтобы локально протестировать это решение, вам нужно немного настроить рабочую станцию, отредактировав файл hosts . Этот файл находится в каталоге etc (для Windows это будет % WINDIR% \ system32 \ drivers \ etc ). Отредактируйте его, добавив

    127.0.0.1   localizer.com
    127.0.0.1   localizer.pl

Теперь посетите localizer.com:3000и localizer.pl:3000должны перейти к нашему приложению Rails.

Очень распространенное место для установки локали — before_actionэто ApplicationController:

application_controller.rb

    [...]
    before_action :set_locale

    private
    def set_locale
      I18n.locale = extract_locale || I18n.default_locale
    end
    [...]

Чтобы получить запрошенное имя хоста, используйте request.host:

application_controller.rb

    [...]
    def extract_locale
    parsed_locale = request.host.split('.').last
    I18n.available_locales.map(&:to_s).include?(parsed_locale) ? parsed_locale : nil
    end
    [...]

В этом методе мы удаляем последнюю часть имени домена ( comи plт. Д.) И проверяем, поддерживается ли запрошенная локаль. Если да — верни, скажи иначе nil.

Теперь настройте ссылки для изменения локали:

общий / _change_locale.html.erb

    [...]
  <li><%= link_to 'English', "http://localizer.com:3000" %></li>
  <li><%= link_to 'Polska', "http://localizer.pl:3000" %></li> 
    [...]

Чтобы сделать его немного более удобным для пользователя, давайте добавим текущий путь к URL:

общий / _change_locale.html.erb

    [...]
  <li><%= link_to 'English', "http://localizer.com:3000#{request.env['PATH_INFO']}" %></li>
  <li><%= link_to 'Polska', "http://localizer.pl:3000#{request.env['PATH_INFO']}" %></li>
    [...]

Теперь вы можете проверить результат!

Использование субдомена

Конечно, вместо покупки нескольких доменов первого уровня, вы можете зарегистрировать субдомены в своей доменной зоне, например en.localizer.comи pl.localizer.com.

extract_localeМетод должен быть изменен следующим образом:

application_controller.rb

    [...]
    def extract_locale
    parsed_locale = request.subdomains.first
    I18n.available_locales.map(&:to_s).include?(parsed_locale) ? parsed_locale : nil
    end
    [...]

Конечно, ссылки будут выглядеть немного иначе:

общий / _change_locale.html.erb

  <li><%= link_to 'English', "http://en.localizer.com:3000#{request.env['PATH_INFO']}" %></li>
  <li><%= link_to 'Polska', "http://pl.localizer.com:3000#{request.env['PATH_INFO']}" %></li>

Установка локали на основе параметров HTTP GET

Например, другим очень распространенным подходом является использование параметров HTTP GET localhost:3000?locale=en. Это потребует от нас изменить extract_localeметод еще раз:

application_controller.rb

    [...]
  def extract_locale
    parsed_locale = params[:locale]
    I18n.available_locales.map(&:to_s).include?(parsed_locale) ? parsed_locale : nil
  end
    [...]

Проблема, однако, заключается в необходимости сохранения выбранной локали между запросами. Конечно, вы можете говорить link_to root_url(locale: I18n.locale)каждый раз, но это не лучшая идея. Вместо этого
вы можете положиться на default_url_optionsметод, который устанавливает параметры по умолчанию для url_forметода и другие методы, которые полагаются на него:

application_controller.rb

    [...]
    def default_url_options
      { locale: I18n.locale }
    end
    [...]

Это позволит вашим помощникам по маршруту автоматически включать ?localeчасть. Однако мне не очень
нравится этот подход, в основном из-за этого раздражающего параметра GET. Поэтому давайте обсудим еще одно решение.

Использование областей маршрутов

Как вы, наверное, помните, маршруты могут быть ограничены, и эту функцию можно использовать для простого сохранения названия локали:

конфиг / routes.rb

    [...]
  scope "(:locale)", locale: /en|pl/ do
    get '/about', to: 'pages#about', as: :about
    root 'pages#index'
  end
    [...]

Оборачивая :localeкруглыми скобками, мы делаем этот параметр GET необязательным. locale: /en|pl/устанавливает регулярное выражение, проверяющее, что этот параметр может содержать только enили pl, следовательно, любая из этих ссылок верна:

  • http://localhost:3000/about
  • http://localhost:3000/en/about
  • http://localhost:3000/pl/about

Измените ссылки для переключения локали:

общий / _change_locale.html.erb

  <li><%= link_to 'English', root_path(locale: :en) %></li>
  <li><%= link_to 'Polska', root_path(locale: :pl) %></li>

На мой взгляд, это решение намного более аккуратно, чем передача локали через параметр ?localeGET.

Определение локали на основе настроек пользователя

Если языковой стандарт не был установлен явно, вы вернетесь к значению по умолчанию, установленному в, I18n.default_localeно мы можем изменить это поведение. Чтобы получить неявную локаль, вы можете использовать HTTP-заголовки или информацию о местонахождении посетителя, поэтому давайте посмотрим на эти два подхода в действии.

Использование заголовков HTTP

Существует специальный заголовок HTTP, Accept-Languageкоторый браузеры устанавливают на основе языковых предпочтений на устройстве пользователя. Его содержимое обычно выглядит так en-US,en;q=0.5, но нас интересуют только первые два символа, поэтому extract_localeметод можно настроить следующим образом:

application_controller.rb

    [...]
    def extract_locale
        parsed_locale = params[:locale] || request.env['HTTP_ACCEPT_LANGUAGE'].scan(/^[a-z]{2}/)[0]
      I18n.available_locales.map(&:to_s).include?(parsed_locale) ? parsed_locale : nil
    end
    [...]

Существует замечательная жемчужина http_accept_language, которая действует как промежуточное программное обеспечение Rack и помогает вам более надежно решить эту проблему.

Использование местоположения пользователя

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

Чтобы получить местоположение пользователя, давайте используем гем под названием геокодер, который можно использовать для множества различных задач и даже предоставляет хуки для ActiveRecord и Mongoid. Однако в этой демонстрации все будет намного проще. Прежде всего, добавьте этот новый драгоценный камень

Gemfile

    [...]
    gem 'geocoder'
    [...]

и беги

    $ bundle install

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

application_controller.rb

    [...]
    def extract_locale
      parsed_locale = if params[:locale]
                        params[:locale]
                      else
                        request.location.country_code ? request.location.country_code.downcase : nil
                      end
        I18n.available_locales.map(&:to_s).include?(parsed_locale) ? parsed_locale : nil
    end
    [...]

Единственная проблема здесь в том, что вы не сможете проверить это локально, так как request.location.country_codeвсегда будет возвращать «RD». Тем не менее, вы можете развернуть свое приложение на Heroku (это займет буквально пару минут) и протестировать все, используя открытые прокси-серверы.

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

PhraseApp и управление переводами

Конечно, введение механизма переключения и сохранения локали очень важно для любого многоязычного приложения, но это не имеет большого смысла, если у вас нет переводов. И PhraseApp здесь, чтобы сделать процесс управления переводами намного проще!

Вы можете попробовать PhraseApp бесплатно в течение 14 дней прямо сейчас. Он поддерживает огромный список различных языков и фреймворков от Rails до JavaScript и позволяет легко импортировать и экспортировать данные переводов. Что здорово, вы можете быстро понять, какие ключи перевода отсутствуют, потому что легко потерять след при работе со многими языками в больших приложениях. Поэтому я действительно призываю вас попробовать!

Заключение

В этом приложении мы рассмотрели различные способы переключения и сохранения данных локали между запросами. Мы видели, как локаль может передаваться как часть имени домена и как часть URL. Кроме того, мы говорили о выводе локали на основе заголовка HTTP и местоположения пользователя.

Надеюсь, эта статья была полезной и интересной для вас. Я благодарю вас за то, что вы остались со мной и счастливого кодирования!