Локаль по умолчанию в Rails

Illustration of a person sitting on the floor against a wall, engrossed in reading a tablet. They are wearing casual clothing with orange sneakers and a scarf. Illustration of a person sitting on the floor against a wall, engrossed in reading a tablet. They are wearing casual clothing with orange sneakers and a scarf.

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

Из параметров URL

Напишем такой код:

# app/controllers/application_controller.rb

def default_url_options
  { locale: I18n.locale }
end

Теперь любой хелпер, зависимый от url_for (к примеру, root_path, root_url или quests_path), будет добавлять локаль в строку запроса: http://localhost:3000/quests?locale=de.

Чтобы получить более элегантный URL, такой как http://localhost:3000/de/quests, необходимо добавить следующее:

# config/routes.rb

scope "/:locale" do
  resources :quests
end

Если же мы хотим, чтобы работали адресы без явного указания (автоматически использовалась локаль по умолчанию), такие как http://localhost:3000/quests, то нужно изменить предыдущий код на:

# config/routes.rb

scope "(:locale)", locale: /en|de/ do
  resources :quests
end

Геолокация

Как-то в 2014 году я использовал плохой способ. Работал он так: при помощи геолокации определялась страна пользователя, а затем используя страну и гем i18n_data устанавливалась непосредственно локаль:

# app/controllers/application_controller.rb

class ApplicationController < ActionController::Base
  before_action :set_locale

  private

  def set_locale
    I18n.locale = I18nData.country_code(request.location.try(:country)).try(:downcase) if request.location.present?
  end
end

request.location становится доступным благодаря гему geocoder.

Чем этот метод плох:

  • на определение местоположения пользователя уходит время, по умолчанию geocoder для этого использует Google Maps API;
  • так как используется сторонний API, то вы обязательно уткнётесь в ограничение на количество запросов к этому API;
  • если пользователь со своим ноутбуком поедет отдыхать в Германию, то он получит немецкий интерфейс.

HTTP-заголовок Accept-Language

Любой приличный браузер с каждым запросом отправляет заголовок Accept-Language, в котором хранится список предпочитаемых пользователем языков. По умолчанию это язык операционной системы. Зная это, мы можем всегда точно определить нужный пользователю язык. Если его браузер использует немецкий, то скорее всего и веб-приложение он захочет на немецком.

Для парсинга Accept-Language можно использовать либо промежуточное приложение locale, либо небольшой гем http_accept_language, остановимся на последнем. По сути, всё что он делает это разбирает headers запроса, отыскивает там Accept-Language, а потом из этой строки находит используемые пользователем языки. После, используя полученный массив языков, можно легко определить доступную в Rails приложении локаль.

Полный код определения и хранения локали пользователя:

# app/controllers/application_controller.rb

class ApplicationController < ActionController::Base
  before_action :set_locale

  private

  def set_locale
    locale = if current_user
               current_user.settings.locale
             elsif params[:locale]
               session[:locale] = params[:locale]
             elsif session[:locale]
               session[:locale]
             else
               http_accept_language.compatible_language_from(I18n.available_locales)
             end
    if locale && I18n.available_locales.include?(locale.to_sym)
      session[:locale] = I18n.locale = locale.to_sym
    end
  end

end