В Ruby гем I18n (краткое наименование для internationalization), поставляемый с Ruby on Rails (начиная с Rails 2.2), представляет простой и расширяемый фреймворк для перевода вашего приложения на отдельный другой язык, иной чем английский, или для предоставления поддержки многоязычности в вашем приложении.
Процесс "интернационализация" обычно означает извлечение всех строк и других специфичных для локали частей (таких как форматы даты и валюты) за рамки вашего приложения. Процесс "локализация" означает предоставление переводов и локализованных форматов для этих частей.
Таким образом, в процессе интернационализации своего приложения на Rails вы должны:
В процессе локализации своего приложения вы, скорее всего, захотите сделать три вещи:
Это руководство проведет вас через I18n API, оно содержит консультации как интернационализировать приложения на Rails с самого начала.
После прочтения этого руководства вы узнаете:
Фреймворк Ruby I18n предоставляет все необходимые средства для интернационализации/локализации приложения на Rails. Можно также использовать другие различные гемы, добавляющие дополнительные функциональность или особенности. Для получения более подробной информации смотрите гем rails-i18n.
Интернационализация - это сложная проблема. Естественные языки отличаются во многих отношениях (например, в правилах образования множественного числа), поэтому трудно представить инструменты, решающие сразу все проблемы. По этой причине Rails I18n API сфокусировано на:
Как часть этого решения, каждая статичная строка в фреймворке Rails - например, валидационные сообщения Active Record, форматы времени и даты - стали интернационализированными. Локализация приложения на Rails означает определение переведенных значений этих строк на желаемые языки.
Для локализации хранилища и обновления content в приложении (например, перевода сообщений в блоге), смотрите раздел Перевод контента модели.
Таким образом, Ruby гем I18n разделен на две части:
Как у пользователя, у вас всегда будет доступ только к публичным методам модуля I18n, но полезно знать о возможностях бэкенда.
Возможно поменять встроенный простой бэкенд на более мощный, который будет хранить данные перевода в реляционной базе данных, словаре GetText или в чем-то похожем. Смотрите раздел Использование различных бэкендов.
Наиболее важными методами I18n API являются:
translate # Ищет перевод текстов
localize # Локализует объекты даты и времени в форматы локали
Имеются псевдонимы #t и #l, их можно использовать следующим образом:
I18n.t 'store.title'
I18n.l Time.now
Также имеются методы чтения и записи для следующих атрибутов:
load_path # Анонсировать ваши пользовательские файлы с переводом
locale # Получить и установить текущую локаль
default_locale # Получить и установить локаль по умолчанию
available_locales # Разрешенные локали, доступные приложению
enforce_available_locales # Принуждение к разрешенным локалям (true или false)
exception_handler # Использовать иной exception_handler
backend # Использовать иной бэкенд
Итак, давайте интернационализируем простое приложение на Rails с самого начала, в следующих главах!
Несколько шагов отделяют вас от получения и запуска поддержки I18n в вашем приложении.
Следуя философии соглашений над конфигурацией, Rails предоставляет приемлемые строки переводов по умолчанию. При необходимости иных строк переводов, они могут быть переопределены.
Rails автоматически добавляет все файлы .rb
и .yml
из директории config/locales
к пути загрузки переводов.
Локаль по умолчанию en.yml
в этой директории содержит образец строки перевода:
en:
hello: "Hello world"
Это означает, что в локали :en
, ключ hello связан со строкой "Hello world". Каждая строка в Rails интернационализируется подобным образом, смотрите, к примеру, валидационные сообщения Active Model в файле activemodel/lib/active_model/locale/en.yml
или форматы времени и даты в файле activesupport/lib/active_support/locale/en.yml
. Для хранения переводов в бэкенде по умолчанию (простом) можете использовать YAML или стандартные хэши Ruby.
Библиотека I18n будет использовать английский как локаль по умолчанию, т.е., если другая локаль не установлена, при поиске переводов будет использоваться :en
.
В библиотеке i18n принят прагматичный подход к ключам локали (после некоторых обсуждений), включающий только часть локаль ("язык"), наподобие :en
, :pl
, но не часть регион, подобно :"en-US"
или :"en-GB"
, что традиционно используется для разделения "языков" и "региональных настроек", или "диалектов". Многие международные приложения используют только элемент "язык" локали, такой как :cs
, :th
или :es
(для Чехии, Таиланда и Испании). Однако, также имеются региональные различия внутри языковой группы, которые могут быть важными. Например, в локали :"en-US"
как символ валюты будет $, а в :"en-GB"
будет £. Ничто не остановит вас от разделения региональных и других настроек следующим образом: предоставляете полную локаль "English - United Kingdom" в словаре :"en-GB"
.
Путь загрузки переводов (I18n.load_path
) - это массив путей к файлам, которые будут загружены автоматически. Настройка этого пути позволяет настроить структуру директорий переводов и схему именования файлов.
Бэкенд лениво загружает эти переводы, когда ищет перевод в первый раз. Этот бэкенд может быть переключен на что-то иное даже после того, как переводы были объявлены.
Можно изменить локаль по умолчанию, так же как и настроить пути загрузки переводов, в config/application.rb
следующим образом:
config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}')]
config.i18n.default_locale = :de
Путь загрузки должен быть указан до того, как будет произведен поиск любых переводов. Чтобы изменить локаль по умолчанию в инициализаторе вместо config/application.rb
:
# config/initializers/locale.rb
# где библиотека I18n должна искать наши переводы
I18n.load_path += Dir[Rails.root.join('lib', 'locale', '*.{rb,yml}')]
# Разрешенные локали, доступные приложению
I18n.available_locales = [:en, :pt]
# устанавливаем локаль по умолчанию на что-либо другое, чем :en
I18n.default_locale = :pt
Отметьте, что добавление напрямую в I18n.load_path
, вместо конфигурации I18n приложения, не перезапишет переводы из внешних гемов.
Локализованному приложению, вероятно, понадобится поддерживать несколько локалей. Для этого локаль должна быть установлена в начале каждого запроса, чтобы все строки были переведены, используя нужную локаль.
Локаль по умолчанию используется для всех переводов за исключением случаев, когда установлены I18n.locale=
или I18n.with_locale
.
I18n.locale
может вытечь в последующие запросы, обслуживаемые тем же тредом/процессом, если она не устанавливается последовательно в каждом контроллере. Например, выполнение I18n.locale = :es
в одном из запросов POST будет влиять на все последующие запросы в контроллерах, не устанавливающих локаль, но только в этом конкретном треде/процессе. Поэтому вместо I18n.locale =
можно использовать I18n.with_locale
, не имеющий этой проблемы утечки.
Локаль может быть установлена в around_action
в ApplicationController
:
around_action :switch_locale
def switch_locale(&action)
locale = params[:locale] || I18n.default_locale
I18n.with_locale(locale, &action)
end
Этот пример показывает использование параметра запроса URL для установки локали (т.е. http://example.com/books?locale=pt
). Таким образом, http://localhost:3000?locale=pt
загрузит португальскую локализацию, в то время как http://localhost:3000?locale=de
загрузит немецкую локализацию.
Локаль может быть установлена, используя один из множества других способов.
Одним из вариантов, которым можно установить локаль, является доменное имя, на котором запущено ваше приложение. Например, мы хотим, чтобы www.example.com
загружал английскую локаль (по умолчанию), а www.example.es
загружал испанскую локаль. Таким образом, доменное имя верхнего уровня используется для установки локали. В этом есть несколько преимуществ:
Это осуществляется так в ApplicationController
:
around_action :switch_locale
def switch_locale(&action)
locale = extract_locale_from_tld || I18n.default_locale
I18n.with_locale(locale, &action)
end
# Получаем локаль из домена верхнего уровня или возвращаем +nil+, если такая локаль недоступна
# Вам следует поместить что-то наподобие этого:
# 127.0.0.1 application.com
# 127.0.0.1 application.it
# 127.0.0.1 application.pl
# в ваш файл /etc/hosts, чтобы попробовать это локально
def extract_locale_from_tld
parsed_locale = request.host.split('.').last
I18n.available_locales.map(&:to_s).include?(parsed_locale) ? parsed_locale : nil
end
Также можно назначить локаль из поддомена похожим образом:
# Получаем код локали из поддомена запроса (подобно http://it.application.local:3000)
# Следует поместить что-то вроде:
# 127.0.0.1 gr.application.local
# в ваш файл /etc/hosts, чтобы попробовать это локально
def extract_locale_from_subdomain
parsed_locale = request.subdomains.first
I18n.available_locales.map(&:to_s).include?(parsed_locale) ? parsed_locale : nil
end
Если ваше приложение включает меню переключения локали, вам следует иметь что-то вроде этого в нем:
link_to("Deutsch", "#{APP_CONFIG[:deutsch_website_url]}#{request.env['PATH_INFO']}")
предполагая, что вы установили APP_CONFIG[:deutsch_website_url]
в некоторое значение, наподобие http://www.application.de
.
У этого решения есть вышеупомянутые преимущества, однако возможно, что вам нельзя или вы не хотите предоставлять разные локализации ("языковые версии") на разных доменах. Наиболее очевидным решением является включить код локали в параметры URL (или пути запроса).
Наиболее обычным способом назначения (и передачи) локали будет включение ее в параметры URL, как мы делали в I18n.with_locale(params[:locale], &action)
в around_action в первом примере. В этом случае нам нужны URL, такие как www.example.com/books?locale=ja
или www.example.com/ja/books
.
В этом подходе есть почти тот же набор преимуществ, как и в назначении локали из имени домена, а именно то, что это RESTful и соответствует остальной части Всемирной паутины. Хотя внедрение этого потребует немного больше работы.
Получение локали из params
и соответствующее назначение ее не сложно: включаете ее в каждый URL, и таким образом передаете ее через запросы. Конечно, включение явной опции в каждый URL (т.е. link_to(books_url(locale: I18n.locale))
) было бы утомительно и, вероятно, невозможно.
Rails содержит инфраструктуру для "централизации динамических решений об URL" в его ApplicationController#default_url_options
, что полезно в этом сценарии: он позволяет нам назначить "defaults" для url_for
и методов хелпера, основанных на нем (с помощью применения/переопределения метода default_url_options
).
Затем мы можем включить что-то наподобие этого в наш ApplicationController
:
# app/controllers/application_controller.rb
def default_url_options
{ locale: I18n.locale }
end
Каждый метод хелпера, зависимый от url_for
(т.е. хелперы для именованных маршрутов, такие как root_path
или root_url
, ресурсные маршруты, такие как books_path
или books_url
и т.д.) теперь будут автоматически включать локаль в строку запроса, как тут: http://localhost:3001/?locale=ja
.
Это может быть достаточным. Хотя и влияет на читаемость URL, когда локаль "висит" в конце каждого URL вашего приложения. Более того, с точки зрения архитектуры, локаль иерархически выше остальных частей домена приложения, и URL должен отражать это.
Вы, возможно, захотите, чтобы URL выглядел так: http://www.example.com/en/books
(который загружает английскую локаль) и http://www.example.com/nl/books
(который загружает голландскую локаль). Это достижимо с помощью такой же стратегии, как и с default_url_options
выше: нужно настроить свои маршруты с помощью scope
:
# config/routes.rb
scope "/:locale" do
resources :books
end
Теперь, когда вы вызовете метод books_path
, то получите "/en/books"
(для локали по умолчанию). URL подобный http://localhost:3001/nl/books
загрузит голландскую локаль, и затем, последующий вызов books_path
возвратит "/nl/books"
(поскольку локаль изменилась).
Поскольку возвращаемое значение default_url_options
кэшируется для каждого запроса, URL адреса в переключателе локали не могут быть сгенерированы при вызове хелперов в цикле, которые устанавливают соответствующие I18n.locale
в каждой итерации.
Вместо этого, не трогайте I18n.locale
и передайте явно опцию :locale
в хелпер или измените request.original_fullpath
.
Если не хотите принудительно использовать локаль в своих маршрутах, можете использовать опциональную область пути (заключенную в скобки), как здесь:
# config/routes.rb
scope "(:locale)", locale: /en|nl/ do
resources :books
end
С таким подходом вы не получите Routing Error
при доступе к своим ресурсам как http://localhost:3001/books
без локали. Это полезно, когда хочется использовать локаль по умолчанию, если она не определена.
Конечно, нужно специально позаботиться о корневом URL (это обычно "домашняя страница" или "лицевая панель") вашего приложения. URL, такой как http://localhost:3001/nl
не заработает автоматически, так как объявление root to: "dashboard#index"
в вашем routes.rb
не принимает локаль во внимание. (И правильно делает: может быть только один "корневой" URL.)
Вам, вероятно, потребуется связать URL так:
# config/routes.rb
get '/:locale' => 'dashboard#index'
Особенно побеспокойтесь относительно порядка ваших маршрутов, чтобы одно объявление маршрутов не "съело" другое. (Вы, возможно, захотите добавить его непосредственно перед объявлением root :to
.)
Обратите внимание на различные гемы, которые упрощают работу с роутами: routing_filter, route_translator.
В приложении с аутентифицированными пользователями можно позволять пользователям устанавливать предпочитаемую локаль через интерфейс приложения. В таком случае, выбранная пользователем установка локали является персистентной в базе данных и используется для установки локали в запросах этого пользователя.
around_action :switch_locale
def switch_locale(&action)
locale = current_user.try(:locale) || I18n.default_locale
I18n.with_locale(locale, &action)
end
Когда локаль не была установлена явно для запроса (например, с помощью одного из представленных выше методов), приложение должно попытаться определить требуемую локаль.
HTTP-заголовок Accept-Language
указывает предпочтительный язык для отклика запроса. Браузеры устанавливают это значение заголовка на основании языковых настроек пользователя, что делает его хорошим выбором при определении локали.
Обычной реализацией использования заголовка Accept-Language
будет следующее:
def switch_locale(&action)
logger.debug "* Accept-Language: #{request.env['HTTP_ACCEPT_LANGUAGE']}"
locale = extract_locale_from_accept_language_header
logger.debug "* Locale set to '#{locale}'"
I18n.with_locale(locale, &action)
end
private
def extract_locale_from_accept_language_header
request.env['HTTP_ACCEPT_LANGUAGE'].scan(/^[a-z]{2}/).first
end
На практике, чтобы сделать это нужен более надежный код. Библиотека Iain Hecker's http_accept_language или промежуточное приложение Rack от Ryan Tomayko's locale предоставляют решения этой проблемы.
IP-адрес клиента, выполняющего запрос, может использоваться для определения региона и его локали. Сервисы, такие как GeoLite2 Country, или гемы, такие как geocoder могут быть использованы для реализации этого подхода.
В целом, этот подход является менее надежным, чем при использовании языка заголовка и не рекомендуется для большинства веб-приложений.
Вы можете поддаться искушению хранить выбранную локаль в сессии или куки. Однако, не делайте этого. Локаль должна быть понятной и быть частью URL. В таком случае, вы не сломаете базовые представления людей о вебе: если вы отправляете URL друзьям, то они должны увидеть ту же самую страницу и то же содержимое. Причудливое слово для этого будет то, что вы будете спокойны - RESTful. Читайте более подробно о RESTful подходе в статье Stefan Tilkov. Иногда бывают исключения из этого правила, они описаны ниже.
Хорошо! Вы уже инициализировали поддержку I18n в своем приложении на Ruby on Rails, и сообщили ему, какую локаль использовать, и как ее сохранять между запросами.
Дальше нам нужно интернационализировать наше приложение, абстрагируя каждую специфичную к локали часть. Напоследок, нам нужно локализовать приложение, предоставляя необходимые переводы для этих абстракций.
У нас есть следующий пример:
# config/routes.rb
Rails.application.routes.draw do
root to: "home#index"
end
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
around_action :switch_locale
def switch_locale(&action)
locale = params[:locale] || I18n.default_locale
I18n.with_locale(locale, &action)
end
end
# app/controllers/home_controller.rb
class HomeController < ApplicationController
def index
flash[:notice] = "Hello Flash"
end
end
<!-- app/views/home/index.html.erb -->
<h1>Hello World</h1>
<p><%= flash[:notice] %></p>
В нашем коде есть две строки на английском, которые будут рендериться пользователям в нашем отклике ("Hello Flash" и "Hello World"). Для интернационализации этого кода, эти строки нужно заменить вызовами хелпера Rails #t
с соответствующими ключами для каждой строки:
# app/controllers/home_controller.rb
class HomeController < ApplicationController
def index
flash[:notice] = t(:hello_flash)
end
end
<!-- app/views/home/index.html.erb -->
<h1><%= t :hello_world %></h1>
<p><%= flash[:notice] %></p>
Теперь при рендеринге вью будет показано сообщение об ошибке, сообщающее, что отсутствуют переводы для ключей :hello_world
и :hello_flash
.
Rails добавляет метод хелпера t
(translate
) во вью, так что вам не нужно набирать I18n.t
каждый раз. Дополнительно этот хелпер ловит отсутствующие переводы и оборачивает результирующее сообщение об ошибке в <span class="translation_missing">
.
Добавим отсутствующие переводы в файлы словарей:
# config/locales/en.yml
en:
hello_world: Hello world!
hello_flash: Hello flash!
# config/locales/pirate.yml
pirate:
hello_world: Ahoy World
hello_flash: Ahoy Flash
Так как default_locale
не изменялась, переводы будут использовать :en
локаль, и в отклике будут рендериться английские строки.
Если локаль будет установлена через URL на пиратскую локаль (http://localhost:3000?locale=pirate
), то в отклике будут рендериться пиратские строки:
Нужно перезагрузить сервер после того, как вы добавили новые файлы локали.
Для хранения переводов в SimpleStore можно использовать файлы YAML (.yml
) или чистого Ruby (.rb
). YAML является наиболее предпочитаемым вариантом среди разработчиков Rails. Однако у него есть один большой недостаток. YAML очень чувствителен к пробелам и спецсимволам, поэтому приложение может неправильно загрузить ваш словарь. Файлы Ruby уронят ваше приложение при первом же обращении, поэтому вам будет просто найти, что в них неправильно. (Если возникают "странности" со словарями YAML, попробуйте поместить соответствующие части словаря в файл Ruby.)
Если переводы хранятся в файлах YAML, определенные ключи должны быть экранированы. Вот они:
Примеры:
# config/locales/en.yml
en:
success:
'true': 'True!'
'on': 'On!'
'false': 'False!'
failure:
true: 'True!'
off: 'Off!'
false: 'False!'
I18n.t 'success.true' # => 'True!'
I18n.t 'success.on' # => 'On!'
I18n.t 'success.false' # => 'False!'
I18n.t 'failure.false' # => Translation Missing
I18n.t 'failure.off' # => Translation Missing
I18n.t 'failure.true' # => Translation Missing
Один из ключевых факторов успешной интернационализации приложения - избегать неправильные предположения о грамматических правилах при абстракции локализованного кода. Грамматические правила, кажущиеся принципиальными в одной локали, могут быть неверными в другой.
Неправильная абстракция показана в следующем примере, где делается предположение о порядке в разных частях перевода. Обратите внимание, что Rails предоставляет хелпер number_to_currency
для обработки следующего случая.
<!-- app/views/products/show.html.erb -->
<%= "#{t('currency')}#{@product.price}" %>
# config/locales/en.yml
en:
currency: "$"
# config/locales/es.yml
es:
currency: "€"
Если цена продукта 10, тогда соответствующий перевод для испанского - "10 €", вместо "€10", но абстракция не может дать этого.
Для создания правильной абстракции, в геме i18n есть возможность, называемая интерполяцией переменных, которая позволяет вам использовать переменные в переводе определений и передавать значения этих переменных в метод перевода.
Правильная абстракция показана в следующем примере:
<!-- app/views/products/show.html.erb -->
<%= t('product_price', price: @product.price) %>
# config/locales/en.yml
en:
product_price: "$%{price}"
# config/locales/es.yml
es:
product_price: "%{price} €"
Все грамматические и пунктуационные решения принимаются в самом определении, таким образом абстракция может дать верный перевод.
Опции default
и scope
зарезервированы и не могут быть использованы как переменные. Если перевод использует :default
или :scope
как интерполяционную переменную, будет вызвано исключение I18n::ReservedInterpolationKey
.
Если перевод ожидает интерполяционную переменную, но она не была передана в #translate
, вызовется исключение I18n::MissingInterpolationArgument
.
Хорошо! Теперь давайте добавим временную метку во вью, чтобы продемонстрировать особенности локализации даты/времени. Чтобы локализовать формат даты, нужно передать объект Time в I18n.l
, или (лучше) использовать хелпер Rails #l
. Формат можно выбрать передав опцию :format
- по умолчанию используется формат :default
.
<!-- app/views/home/index.html.erb -->
<h1><%= t :hello_world %></h1>
<p><%= flash[:notice] %></p>
<p><%= l Time.now, format: :short %></p>
И в нашем файле переводов на пиратский давайте добавим формат времени (в Rails уже есть формат по умолчанию для английского):
# config/locales/pirate.yml
pirate:
time:
formats:
short: "arrrround %H'ish"
Что даст вам:
Сейчас вам, возможно, захочется добавить больше форматов для того, чтобы бэкенд I18n работал как нужно (как минимум для локали "pirate"). Конечно, есть большая вероятность, что кто-то еще выполнил всю работу по переводу значений по умолчанию Rails для вашей локали. Смотрите в репозитории rails-i18n на Github архив с различными файлами локали. Когда вы поместите такой файл(ы) в директорию config/locales/
, они автоматически станут готовыми для использования.
Rails позволяет определить правила словообразования (такие как единственное и множественное число) для локалей, отличных от английской. В config/initializers/inflections.rb
можно определить эти правила для нескольких локалей. Инициализатор содержит пример по умолчанию для определения дополнительных правил для английского, следуйте этому формату для других локалей.
Скажем, у вас в приложении есть BooksController. Экшн index рендерит содержимое в шаблоне app/views/books/index.html.erb
. Когда вы помещаете локализованный вариант этого шаблона: index.es.html.erb
в ту же директорию, Rails будет рендерить содержимое в этот шаблон, когда локаль будет установлена как :es
. Когда будет установлена локаль по умолчанию, будет использована обычная вью index.html.erb
. (Будущие версии Rails, возможно, перенесут эту возможность автоматической локализации ассетов в public
, и т.д.)
Можете использовать эту особенность, например, при работе с большим количеством статичного содержимого, который было бы неудобно вложить в словари YAML или Ruby. Хотя имейте в виду, что любое изменение, которое вы в дальнейшем сделаете в шаблоне, должно быть распространено на все локали.
При использовании дефолтного SimpleStore вместе с библиотекой i18n, словари хранятся в текстовых файлах на диске. Помещение переводов ко всем частям приложения в один файл на локаль будет трудным для управления. Можно хранить эти файлы в иерархии, которая будет для вас понятной.
К примеру, ваша директория config/locales
может выглядеть так:
|-defaults
|---es.yml
|---en.yml
|-models
|---book
|-----es.yml
|-----en.yml
|-views
|---defaults
|-----es.yml
|-----en.yml
|---books
|-----es.yml
|-----en.yml
|---users
|-----es.yml
|-----en.yml
|---navigation
|-----es.yml
|-----en.yml
Таким образом можно разделить модель и имена атрибутов модели от текста внутри вью, и все это от "defaults" (т.е. форматов даты и времени). Другие хранилища для библиотеки i18n могут предоставить другие средства подобного разделения.
Механизм загрузки локали по умолчанию в Rails не загружает файлы локали во вложенных словарях, как тут. Поэтому, чтобы это заработало, нужно явно указать Rails смотреть глубже:
# config/application.rb
config.i18n.load_path += Dir[Rails.root.join('config', 'locales', '**', '*.{rb,yml}')]
Теперь у вас есть хорошее понимание об использовании библиотеки i18n и знание, как интернационализировать простое приложения на Rails. В следующих частях мы раскроем особенности более детально.
Эти главы покажут примеры использования как метода I18n.translate
, так и метода хелпера вью translate
(отметив дополнительные функции, предоставленными методом хелпера вью).
Раскроем особенности такие, как:
Переводы ищутся по ключам, которые могут быть как символами, так и строками, поэтому следующие вызовы эквивалентны:
I18n.t :message
I18n.t 'message'
Метод translate
также принимает опцию :scope
, которая содержит один или более дополнительных ключей, которые будут использованы для определения "пространства" или области имен для ключа перевода:
I18n.t :record_invalid, scope: [:activerecord, :errors, :messages]
Тут будет искаться сообщение :record_invalid
в сообщениях об ошибке Active Record.
Кроме того, и ключ, и область имен могут быть определены как ключи с точкой в качестве разделителя, как в:
I18n.translate "activerecord.errors.messages.record_invalid"
Таким образом, следующие вызовы эквивалентны:
I18n.t 'activerecord.errors.messages.record_invalid'
I18n.t 'errors.messages.record_invalid', scope: :activerecord
I18n.t :record_invalid, scope: 'activerecord.errors.messages'
I18n.t :record_invalid, scope: [:activerecord, :errors, :messages]
Когда задана опция :default
, будет возвращено ее значение в случае, если отсутствует перевод:
I18n.t :missing, default: 'Not here'
# => 'Not here'
Если значение :default
является символом, оно будет использовано как ключ и будет переведено. Может быть представлено несколько значений по умолчанию. Будет возвращено первое, которое даст результат.
Т.е., следующее попытается перевести ключ :missing
, затем ключ :also_missing
. Если они оба не дадут результат, будет возвращена строка "Not here":
I18n.t :missing, default: [:also_missing, 'Not here']
# => 'Not here'
Чтобы найти несколько переводов за раз, может быть передан массив ключей:
I18n.t [:odd, :even], scope: 'errors.messages'
# => ["must be odd", "must be even"]
Также ключ может перевести хэш (потенциально вложенный) сгруппированных переводов. Т.е. следующее получит все сообщения об ошибке Active Record как хэш:
I18n.t 'errors.messages'
# => {:inclusion=>"is not included in the list", :exclusion=> ... }
Если хотите выполнить интерполяцию на вложенном хэше переводов, необходимо передать параметром deep_interpolation: true
. Когда у вас есть следующий словарь:
en:
welcome:
title: "Welcome!"
content: "Welcome to the %{app_name}"
тогда вложенные интерполяции будут проигнорированы без этой настройки:
I18n.t 'welcome', app_name: 'book store'
# => {:title=>"Welcome!", :content=>"Welcome to the %{app_name}"}
I18n.t 'welcome', deep_interpolation: true, app_name: 'book store'
# => {:title=>"Welcome!", :content=>"Welcome to the book store"}
Rails реализует удобный способ поиска локали внутри вью. Когда имеется следующий словарь:
es:
books:
index:
title: "Título"
можно найти значение books.index.title
в шаблоне app/views/books/index.html.erb
таким образом (обратите внимание на точку):
<%= t '.title' %>
Автоматическое ограничение перевода доступно только из вспомогательного метода вью translate
.
"Ленивый" поиск также может быть использован в контроллерах:
en:
books:
create:
success: Book created!
Это может быть полезным для установки сообщений флеш:
class BooksController < ApplicationController
def create
# ...
redirect_to books_url, notice: t('.success')
end
end
Во многих языках — включая английский — есть только две формы, единственного числа и множественного числа, для заданной строки, т.е. "1 message" и "2 messages". В других языках: (русском, арабском, японском и многих других) имеются различные правила грамматики, имеющие дополнительные или отсутствующие формы множественного числа. Таким образом, API I18n предоставляет гибкую возможность для форм множественного числа.
У переменной интерполяции :count
есть специальная роль в том, что она интерполируется для перевода, и используется для подбора множественного числа для перевода в соответствии с правилами множественного числа, определенными в бэкенде множественного числа. По умолчанию применяются правила множественного числа только для английского языка.
I18n.backend.store_translations :en, inbox: {
zero: 'no messages', # опционально
one: 'one message',
other: '%{count} messages'
}
I18n.translate :inbox, count: 2
# => '2 messages'
I18n.translate :inbox, count: 1
# => 'one message'
I18n.translate :inbox, count: 0
# => 'no messages'
Алгоритм для образования множественного числа в :en
прост:
lookup_key = :zero if count == 0 && entry.has_key?(:zero)
lookup_key ||= count == 1 ? :one : :other
entry[lookup_key]
Перевод помеченный как :one
, рассматривается как единственное число, все другое как множественное. Если количество нулевое, и существует запись :zero
, тогда будет использоваться она вместо :other
.
Если поиск по ключу не возвратит хэш, подходящий для образования множественного числа, вызовется исключение I18n::InvalidPluralizationData
.
Гем I18n предоставляет бэкенд множественного числа, который может использоваться для включения правил локализации. Добавьте это в простой бэкенд, затем добавьте алгоритмы для локализации множественного числа в хранилище переводов, как i18n.plural.rule
.
I18n::Backend::Simple.include(I18n::Backend::Pluralization)
I18n.backend.store_translations :pt, i18n: { plural: { rule: lambda { |n| [0, 1].include?(n) ? :one : :other } } }
I18n.backend.store_translations :pt, apples: { one: 'one or none', other: 'more than one' }
I18n.t :apples, count: 0, locale: :pt
# => 'one or none'
В качестве альтернативы, отдельный гем rails-i18n может быть использован для обеспечения более полного набора локализованных правил множественного числа.
Локаль может быть либо установленной псевдо-глобально в I18n.locale
(использующей Thread.current
наподобие, к примеру, Time.zone
), либо быть переданной опцией в #translate
и #localize
.
Если локаль не была передана, используется I18n.locale
:
I18n.locale = :de
I18n.t :foo
I18n.l Time.now
Явно переданная локаль:
I18n.t :foo, locale: :de
I18n.l Time.now, locale: :de
Умолчанием для I18n.locale
является I18n.default_locale
, для которой по умолчанию установлено :en
. Локаль по умолчанию может быть установлена так:
I18n.default_locale = :de
Ключи с суффиксом _html
и ключами с именем html
помечаются как HTML-безопасные. При их использовании во вью, HTML не будет экранирован.
# config/locales/en.yml
en:
welcome: <b>welcome!</b>
hello_html: <b>hello!</b>
title:
html: <b>title!</b>
<!-- app/views/home/index.html.erb -->
<div><%= t('welcome') %></div>
<div><%= raw t('welcome') %></div>
<div><%= t('hello_html') %></div>
<div><%= t('title.html') %></div>
Интерполяция экранируется по мере необходимости. Например, учитывая:
en:
welcome_html: "<b>Welcome %{username}!</b>"
вы можете спокойно передать имя пользователя, установленное пользователем:
<%# This is safe, it is going to be escaped if needed. %>
<%= t('welcome_html', username: @current_user.username) %>
С другой стороны, безопасные строки интерполируются дословно.
Автоматическое преобразование в HTML-безопасный текст перевода доступен только для метода хелпера translate
(или t
). Это работает во вью и в контроллерах.
Можете использовать методы Model.model_name.human
и Model.human_attribute_name(attribute)
для прозрачного поиска переводов для ваших моделей и имен атрибутов.
Например, когда добавляем следующие переводы:
en:
activerecord:
models:
user: Customer
attributes:
user:
login: "Handle"
# переводит атрибут "login" у User как "Handle"
Тогда User.model_name.human
возвратит "Customer", а User.human_attribute_name("login")
возвратит "Handle".
Для имен модели также можно установить множественное число, добавив следующее:
en:
activerecord:
models:
user:
one: Customer
other: Customers
Тогда User.model_name.human(count: 2)
возвратит "Customers". С count: 1
или без параметров возвратит "Customer".
В случае необходимости получить доступ к вложенным атрибутам модели, следует показать эту вложенность в виде model/attribute
на уровне модели в файле переводов:
en:
activerecord:
attributes:
user/role:
admin: "Admin"
contributor: "Contributor"
Тогда User.human_attribute_name("role.admin")
возвратит "Admin".
Если используется класс, включающий ActiveModel
, но не наследованный от ActiveRecord::Base
, замените activerecord
на activemodel
в вышеприведенных путях ключей.
Сообщение об ошибке валидации Active Record также может быть легко переведено. Active Record предоставляет ряд пространств имен, куда можно поместить ваши переводы для передачи различных сообщений и переводы для определенных моделей, атрибутов и/или валидаций. Также учитывается одиночное наследование таблицы (single table inheritance).
Это дает довольно мощное средство для гибкой настройки ваших сообщений в соответствии с потребностями приложения.
Рассмотрим модель User с валидацией validates_presence_of
для атрибута name, подобную следующей:
class User < ApplicationRecord
validates :name, presence: true
end
Ключом для сообщения об ошибке в этом случае будет :blank
. Active Record будет искать этот ключ в пространствах имен:
activerecord.errors.models.[model_name].attributes.[attribute_name]
activerecord.errors.models.[model_name]
activerecord.errors.messages
errors.attributes.[attribute_name]
errors.messages
Таким образом, в нашем примере он будет перебирать следующие ключи в указанном порядке и возвратит первый полученный результат:
activerecord.errors.models.user.attributes.name.blank
activerecord.errors.models.user.blank
activerecord.errors.messages.blank
errors.attributes.name.blank
errors.messages.blank
Когда ваши модели дополнительно используют наследование, тогда сообщения ищутся в цепочке наследования.
Например, у вас может быть модель Admin, унаследованная от User:
class Admin < User
validates :name, presence: true
end
Тогда Active Record будет искать сообщения в этом порядке:
activerecord.errors.models.admin.attributes.name.blank
activerecord.errors.models.admin.blank
activerecord.errors.models.user.attributes.name.blank
activerecord.errors.models.user.blank
activerecord.errors.messages.blank
errors.attributes.name.blank
errors.messages.blank
Таким образом можно предоставить специальные переводы для различных сообщений об ошибке в различных местах цепочки наследования моделей и в атрибутах, моделях и пространствах имен по умолчанию.
Переведенное имя модели, переведенное имя атрибута и значение всегда доступны для интерполяции как model
, attribute
и value
соответственно.
Так, к примеру, вместо сообщения об ошибке по умолчанию "cannot be blank"
можете использовать имя атрибута как тут: "Please fill in your %{attribute}"
.
count
может быть использован для множественного числа, если оно существует:
валидация | с опцией | сообщение | интерполяция |
---|---|---|---|
confirmation | - | :confirmation | attribute |
acceptance | - | :accepted | - |
presence | - | :blank | - |
absence | - | :present | - |
length | :within, :in | :too_short | count |
length | :within, :in | :too_long | count |
length | :is | :wrong_length | count |
length | :minimum | :too_short | count |
length | :maximum | :too_long | count |
uniqueness | - | :taken | - |
format | - | :invalid | - |
inclusion | - | :inclusion | - |
exclusion | - | :exclusion | - |
associated | - | :invalid | - |
non-optional association | - | :required | - |
numericality | - | :not_a_number | - |
numericality | :greater_than | :greater_than | count |
numericality | :greater_than_or_equal_to | :greater_than_or_equal_to | count |
numericality | :equal_to | :equal_to | count |
numericality | :less_than | :less_than | count |
numericality | :less_than_or_equal_to | :less_than_or_equal_to | count |
numericality | :other_than | :other_than | count |
numericality | :only_integer | :not_an_integer | - |
numericality | :in | :in | count |
numericality | :odd | :odd | - |
numericality | :even | :even | - |
Если не передать subject в метод mail
, Action Mailer попытается найти ее в ваших переводах. Выполняемый поиск будет использовать паттерн <mailer_scope>.<action_name>.subject
для создания ключа.
# user_mailer.rb
class UserMailer < ActionMailer::Base
def welcome(user)
#...
end
end
en:
user_mailer:
welcome:
subject: "Welcome to Rails Guides!"
Чтобы отослать параметры в интерполяцию, используйте в рассыльщике метод default_i18n_subject
.
# user_mailer.rb
class UserMailer < ActionMailer::Base
def welcome(user)
mail(to: user.email, subject: default_i18n_subject(user: user.name))
end
end
en:
user_mailer:
welcome:
subject: "%{user}, welcome to Rails Guides!"
Rails использует фиксированные строки и другие локализации, такие как формат строки и другая информация о формате, в ряде хелперов. Вот краткий обзор.
distance_of_time_in_words
переводит и образует множественное число своего результата и интерполирует число секунд, минут, часов и т.д. Смотрите переводы datetime.distance_in_words.
datetime_select
и select_month
используют переведенные имена месяцев для заполнения результирующего тега select. Смотрите переводы в date.month_names. datetime_select
также ищет опцию order из date.order (если вы передали эту опцию явно). Все хелперы выбора даты переводят prompt, используя переводы в пространстве имен datetime.prompts, если применимы.
number_to_currency
, number_with_precision
, number_to_percentage
, number_with_delimiter
и number_to_human_size
используют настройки формата чисел в пространстве имен number.
human_name
и human_attribute_name
используют переводы для имен модели и имен атрибутов, если они доступны в пространстве имен activerecord.models. Они также предоставляют переводы для имен унаследованного класса (т.е. для использования вместе с STI), как уже объяснялось выше в "Области сообщения об ошибке".
ActiveModel::Errors#generate_message
(который используется валидациями Active Model, но также может быть использован вручную) использует human_name
и human_attribute_name
(смотрите выше). Он также переводит сообщение об ошибке и поддерживает переводы для имен унаследованного класса, как уже объяснялось выше в "Пространства имен сообщений об ошибке".
ActiveModel::Error#full_message
и ActiveModel::Errors#full_messages
добавляют имя атрибута к сообщению об ошибке, используя формат, ищущийся в errors.format
(по умолчанию: "%{attribute} %{message}"
). Чтобы настроить формат по умолчанию, переопределите его в файлах локали приложения. Чтобы настроить формат для модели или атрибута, смотрите config.active_model.i18n_customize_full_message
.
Array#to_sentence
использует настройки формата, которые заданы в пространстве имен support.array.
Простой бэкенд, поставляющийся вместе с Active Support, позволяет хранить переводы как в формате чистого Ruby, так и в YAML.
Например, представляющий перевод хэш Ruby выглядит так:
{
pt: {
foo: {
bar: "baz"
}
}
}
Эквивалентный файл YAML выглядит так:
pt:
foo:
bar: baz
Как видите, в обоих случаях ключ верхнего уровня является локалью. :foo
- это ключ пространства имен, а :bar
- это ключ для перевода "baz".
Вот "реальный" пример из YAML файла перевода Active Support en.yml
:
en:
date:
formats:
default: "%Y-%m-%d"
short: "%b %d"
long: "%B %d, %Y"
Таким образом, все из нижеследующих эквивалентов возвратит краткий (:short
) формат даты "%b %d"
:
I18n.t 'date.formats.short'
I18n.t 'formats.short', scope: :date
I18n.t :short, scope: 'date.formats'
I18n.t :short, scope: [:date, :formats]
Как правило, мы рекомендуем использовать YAML как формат хранения переводов. Хотя имеются случаи, когда хочется хранить лямбда-функции Ruby как часть данных локали, например, для специальных форматов дат.
По некоторым причинам простой бэкенд, поставляющийся с Active Support, осуществляет только "простейшие вещи, в которых возможна работа" Ruby on Rails (или, цитируя Википедию, Интернационализация это процесс разработки программного обеспечения таким образом, что оно может быть адаптировано к различным языкам и регионам без существенных инженерных изменений. Локализация это процесс адаптации программы для отдельного региона или языка с помощью добавления специфичных для локали компонентов и перевод текстов), что означает то, что гарантируется работа для английского и, как побочный эффект, для схожих с английским языков. Также простой бэкенд способен только читать переводы, а не динамически хранить их в каком-либо формате.
Впрочем, это не означает, что вы связаны этими ограничениями. Гем Ruby I18n позволяет с легкостью заменить простой бэкенд на что-то иное, более предпочтительное для ваших нужд, передавая экземпляр бэкенда в сеттер I18n.backend=
:
Например, можно заменить простой бэкенд на бэкенд Chain для связывания нескольких бэкендов вместе. Это полезно, когда используются стандартные переводы с помощью простого бэкенда, а хранятся собственные переводы приложения в базе данных или других бэкендах.
С помощью бэкенда Chain можно использовать бэкенд Active Record и вернуться к простому бэкенду (по умолчанию):
I18n.backend = I18n::Backend::Chain.new(I18n::Backend::ActiveRecord.new, I18n.backend)
API I18n определяет следующие исключения, вызываемые бэкендами, когда происходят соответствующие неожидаемые условия:
MissingTranslationData # не обнаружен перевод для запрашиваемого ключа
InvalidLocale # локаль, установленная I18n.locale, невалидна (например, nil)
InvalidPluralizationData # была передана опция count, но данные для перевода не могут быть возведены во множественное число
MissingInterpolationArgument # перевод ожидает интерполяционный аргумент, который не был передан
ReservedInterpolationKey # перевод содержит зарезервированное имя интерполяционной переменной (т.е. scope, default)
UnknownFileType # бэкенд не знает, как обработать тип файла, добавленного в I18n.load_path
API I18n поймает все эти исключения, когда они были вызваны в бэкенде, и передаст их в метод default_exception_handler. Этот метод вызовет заново все исключения, кроме исключений MissingTranslationData
. Когда было вызвано исключение MissingTranslationData
, он возвратит строку сообщения об ошибке исключения, содержащую отсутствующие ключ/пространство имен.
Причиной для этого является то, что при разработке вам обычно хочется, чтобы вью рендерились несмотря на отсутствующие переводы.
Впрочем, в иных ситуациях, возможно, захочется изменить это поведение. Например, обработка исключений по умолчанию не позволяет просто ловить отсутствующие переводы во время автоматических тестов. Для этой цели может быть определен иной обработчик исключений. Определенный обработчик исключений должен быть методом в модуле I18n или классом с методом call
:
module I18n
class JustRaiseExceptionHandler < ExceptionHandler
def call(exception, locale, key, options)
if exception.is_a?(MissingTranslation)
raise exception.to_exception
else
super
end
end
end
end
I18n.exception_handler = I18n::JustRaiseExceptionHandler.new
Это вызовет только исключение MissingTranslationData
, передав все другие значения в обработчик исключений по умолчанию.
Однако, если вы используете I18n::Backend::Pluralization
, этот обработчик также вызывает исключение I18n::MissingTranslationData: translation missing: en.i18n.plural.rule
, которое обычно должно быть проигнорировано для отката к правилу плюрализации по умолчанию в английской локали. Чтобы этого избежать, можно добавить дополнительную проверку ключа перевода:
if exception.is_a?(MissingTranslation) && key.to_s != 'i18n.plural.rule'
raise exception.to_exception
else
super
end
Другим примером, когда поведение по умолчанию является менее желательным, является Rails TranslationHelper, который предоставляет метод #t
(то же самое, что #translate
). Когда в этом контексте происходит исключение MissingTranslationData
хелпер оборачивает сообщение в span с классом CSS translation_missing
.
Чтобы это осуществить, хелпер заставляет I18n#translate
вызвать исключения, независимо от того, какой обработчик исключений установлен, определяя опцию :raise
:
I18n.t :foo, raise: true # всегда перевызывает исключения из бэкенда
API I18n, описанный в этом руководстве, в первую очередь предназначен для перевода строк интерфейса. Если необходимо перевести контент модели (например, сообщений в блоге), может понадобится другое решение, помогающее в этом.
Несколько гемов, которые могут помочь:
С этого момента у вас должно быть хорошее понимание, как работает поддержка I18n в Ruby on Rails, и вы должны быть готовы начать переводить свой проект.
Поддержка I18n в Ruby on Rails была представлена в релизе 2.2 и до сих пор развивается. Проект следует хорошим традициям разработки Ruby on Rails в виде первоначального развития в виде отдельных гемов и реальных приложений, и только затем извлечения наилучших широко используемых особенностей для включения в ядро.
Поэтому каждый поощряется экспериментировать с новыми идеями и особенностями в гемах или других библиотеках и делать их доступными сообществу. (Не забудьте анонсировать свою работу в рассылке!)
Если вы обнаружите, что ваша локаль (язык) отсутствует в данных примеров переводов репозитория Ruby on Rails, сделайте fork репозитория, добавьте ваши данные и пошлите pull request.