Ruby I18n 框架为您提供了国际化/本地化 Rails 应用程序所需的所有工具。您还可以使用各种可用的 gem 添加额外的功能或特性。有关更多信息,请参阅rails-i18n gem。
1 Ruby on Rails 中的 I18n 工作原理
国际化是一个复杂的问题。自然语言在很多方面(例如复数规则)都有所不同,因此很难提供解决所有问题的工具。出于这个原因,Rails I18n API 侧重于
- 开箱即用地提供对英语和类似语言的支持
- 简化其他语言的自定义和扩展操作
作为此解决方案的一部分,Rails 框架中的每个静态字符串 - 例如 Active Record 验证消息、时间和日期格式 - 都已进行国际化。本地化 Rails 应用程序意味着为这些字符串在所需语言中定义翻译后的值。
要本地化、存储和更新应用程序中的内容(例如翻译博客文章),请参阅翻译模型内容部分。
1.1 库的整体架构
因此,Ruby I18n gem 分为两部分
- I18n 框架的公共 API - 一个 Ruby 模块,其公共方法定义了库的工作方式
- 默认后端(故意命名为Simple后端),它实现了这些方法
作为用户,您应该始终只访问 I18n 模块上的公共方法,但了解后端的功能很有用。
可以将提供的 Simple 后端替换为更强大的后端,后者将翻译数据存储在关系数据库、GetText 字典或类似内容中。请参阅下面的使用不同的后端部分。
1.2 公共 I18n API
I18n API 最重要的方法是
translate # Lookup text translations
localize # Localize Date and Time objects to local formats
它们有别名 #t 和 #l,因此您可以像这样使用它们
I18n.t "store.title"
I18n.l Time.now
还有一些用于以下属性的属性读写器和写方法
load_path # Announce your custom translation files
locale # Get and set the current locale
default_locale # Get and set the default locale
available_locales # Permitted locales available for the application
enforce_available_locales # Enforce locale permission (true or false)
exception_handler # Use a different exception_handler
backend # Use a different backend
那么,让我们在接下来的章节中从头开始国际化一个简单的 Rails 应用程序!
2 为国际化设置 Rails 应用程序
要为 Rails 应用程序启用 I18n 支持,需要执行几个步骤。
2.1 配置 I18n 模块
遵循约定优于配置的理念,Rails I18n 提供了合理的默认翻译字符串。如果需要不同的翻译字符串,可以覆盖它们。
Rails 会自动将 config/locales
目录中的所有 .rb
和 .yml
文件添加到翻译加载路径。
此目录中的默认 en.yml
区域设置包含一对示例翻译字符串
en:
hello: "Hello world"
这意味着,在 :en
区域设置中,键 hello 将映射到字符串 Hello world。Rails 中的每个字符串都以这种方式进行了国际化,例如,请参阅activemodel/lib/active_model/locale/en.yml
文件中的 Active Model 验证消息,或activesupport/lib/active_support/locale/en.yml
文件中的时间和日期格式。您可以在默认(Simple)后端中使用 YAML 或标准 Ruby 哈希存储翻译。
I18n 库将使用英语作为默认区域设置,也就是说,如果未设置其他区域设置,则将使用 :en
来查找翻译。
i18n 库对区域设置键采用**务实的方法**(经过一些讨论),仅包含区域设置(“语言”)部分,如:en
,:pl
,而不包含区域部分,如:"en-US"
或:"en-GB"
,这些通常用于区分“语言”和“区域设置”或“方言”。许多国际应用程序只使用区域设置的“语言”元素,如:cs
,:th
或:es
(分别对应捷克语、泰语和西班牙语)。但是,不同语言组内也存在一些可能很重要的区域差异。例如,在:"en-US"
区域设置中,货币符号为$,而在:"en-GB"
区域设置中,货币符号为£。没有什么能阻止你以这种方式将区域设置和其他设置区分开来:你只需要在:"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
# Where the I18n library should search for translation files
I18n.load_path += Dir[Rails.root.join("lib", "locale", "*.{rb,yml}")]
# Permitted locales available for the application
I18n.available_locales = [:en, :pt]
# Set default locale to something other than :en
I18n.default_locale = :pt
请注意,直接追加到I18n.load_path
而不是追加到应用程序配置的 I18n 不会覆盖来自外部 gem 的翻译。
2.2 管理跨请求的区域设置
一个本地化的应用程序可能需要提供对多种区域设置的支持。为了实现这一点,区域设置应该在每个请求开始时设置,以便在该请求的生命周期中,所有字符串都使用所需的区域设置进行翻译。
除非使用I18n.locale=
或I18n.with_locale
,否则所有翻译都使用默认区域设置。
如果I18n.locale
没有在每个控制器中始终如一地设置,它可能会泄漏到同一个线程/进程提供的后续请求中。例如,在一个 POST 请求中执行I18n.locale = :es
将对所有随后未设置区域设置的控制器的请求产生影响,但仅在该特定线程/进程中。因此,你可以使用I18n.with_locale
而不是I18n.locale =
,因为它没有这种泄漏问题。
可以在ApplicationController
中的around_action
中设置区域设置
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
)。使用这种方法,https://127.0.0.1:3000?locale=pt
将呈现葡萄牙语本地化,而https://127.0.0.1:3000?locale=de
将加载德语本地化。
可以使用多种不同的方法设置区域设置。
2.2.1 从域名设置区域设置
你可以选择从应用程序运行的域名设置区域设置。例如,我们希望www.example.com
加载英语(或默认)区域设置,www.example.es
加载西班牙语区域设置。因此,顶级域名用于设置区域设置。这有几个优点
- 区域设置是 URL 中明显的一部分。
- 人们直观地理解内容将以哪种语言显示。
- 在 Rails 中实现起来非常简单。
- 搜索引擎似乎喜欢不同语言的内容位于不同的互联域中。
你可以在你的ApplicationController
中这样实现
around_action :switch_locale
def switch_locale(&action)
locale = extract_locale_from_tld || I18n.default_locale
I18n.with_locale(locale, &action)
end
# Get locale from top-level domain or return +nil+ if such locale is not available
# You have to put something like:
# 127.0.0.1 application.com
# 127.0.0.1 application.it
# 127.0.0.1 application.pl
# in your /etc/hosts file to try this out locally
def extract_locale_from_tld
parsed_locale = request.host.split(".").last
I18n.available_locales.map(&:to_s).include?(parsed_locale) ? parsed_locale : nil
end
我们也可以以非常类似的方式从子域名设置区域设置
# Get locale code from request subdomain (like http://it.application.local:3000)
# You have to put something like:
# 127.0.0.1 it.application.local
# in your /etc/hosts file to try this out locally
#
# Additionally, you need to add the following configuration to your config/environments/development.rb:
# config.hosts << 'it.application.local:3000'
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 参数(或请求路径)中包含区域设置代码。
2.2.2 从 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 在其ApplicationController#default_url_options
中包含了“集中关于 URL 的动态决策”的机制,这在这种情况中非常有用:它使我们能够为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
等)都将自动在查询字符串中包含区域设置,如下所示:https://127.0.0.1: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"
(对于默认区域设置)。类似https://127.0.0.1:3001/nl/books
的 URL 应该加载荷兰语区域设置,然后对books_path
的后续调用应该返回"/nl/books"
(因为区域设置已更改)。
由于default_url_options
的返回值是在每个请求的基础上缓存的,因此在循环中设置相应I18n.locale
的每个迭代的区域设置选择器中,无法通过调用帮助器生成 URL。相反,保持I18n.locale
不变,并将显式的:locale
选项传递给帮助器,或者编辑request.original_fullpath
。
如果你不想在路由中强制使用区域设置,可以使用可选路径范围(用括号表示),如下所示
# config/routes.rb
scope "(:locale)", locale: /en|nl/ do
resources :books
end
使用这种方法,当访问你的资源(如https://127.0.0.1:3001/books
)时,没有区域设置,你将不会得到Routing Error
。这对于在未指定区域设置时想要使用默认区域设置的情况非常有用。
当然,你需要特别注意你的应用程序的根 URL(通常是“主页”或“仪表盘”)。类似https://127.0.0.1:3001/nl
的 URL 不会自动工作,因为你routes.rb
中的root to: "dashboard#index"
声明没有考虑到区域设置。(而且这样做是正确的:只有一个“根”URL。)
你可能需要映射类似以下内容的 URL
# config/routes.rb
get "/:locale" => "dashboard#index"
请特别注意你的路由顺序,这样该路由声明不会“吞噬”其他路由。(你可能希望将其直接添加到root :to
声明之前。)
看看各种简化路由操作的 gem:routing_filter,route_translator。
2.2.3 从用户偏好设置区域设置
具有经过身份验证的用户的应用程序可以允许用户通过应用程序的界面设置区域设置偏好。使用这种方法,用户的选定区域设置偏好会持久化到数据库中,并用于在经过身份验证的用户对该用户的请求时设置区域设置。
around_action :switch_locale
def switch_locale(&action)
locale = current_user.try(:locale) || I18n.default_locale
I18n.with_locale(locale, &action)
end
2.2.4 选择隐含的区域设置
当请求没有设置显式区域设置时(例如,通过上述方法之一),应用程序应该尝试推断所需的区域设置。
2.2.4.1 从语言标头推断区域设置
Accept-Language
HTTP 标头指示请求响应的优先语言。浏览器根据用户的语言偏好设置设置此标头值,使其成为推断区域设置时的首选。
使用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 的http_accept_language库或 Ryan Tomayko 的locale Rack 中间件提供了解决此问题的解决方案。
2.2.4.2 从 IP 地理位置推断区域设置
发出请求的客户端的 IP 地址可用于推断客户端的区域,从而推断其区域设置。可以使用诸如GeoLite2 Country之类的服务或诸如geocoder之类的 gem 来实现这种方法。
一般来说,这种方法远不如使用语言标头可靠,不建议大多数 Web 应用程序使用。
2.2.5 从会话或 Cookie 中存储区域设置
你可能想将所选的区域设置存储在会话或Cookie中。但是,不要这样做。区域设置应该是透明的,并且是 URL 的一部分。这样,你就不会打破人们对网络本身的基本假设:如果你向朋友发送一个 URL,他们应该看到与你相同的页面和内容。一个更正式的说法是,你是RESTful的。阅读更多关于 RESTful 方法的信息,请查看Stefan Tilkov 的文章。有时,此规则有一些例外,这些例外将在下面讨论。
3 国际化和本地化
好的!现在,你已经为你的 Ruby on Rails 应用程序初始化了 I18n 支持,并告诉它使用哪个区域设置以及如何在请求之间保留它。
接下来,我们需要通过抽象所有特定于区域设置的元素来国际化我们的应用程序。最后,我们需要通过为这些抽象提供必要的翻译来本地化它。
以下面的示例为例
# 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>
3.1 抽象本地化代码
在我们的代码中,有两个用英语编写的字符串将在我们的响应中呈现(“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">
中。
3.2 为国际化字符串提供翻译
将缺少的翻译添加到翻译字典文件中
# 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 设置为海盗区域设置(https://127.0.0.1:3000?locale=pirate
),响应将渲染海盗字符串
添加新的区域设置文件后,您需要重启服务器。
您可以使用 YAML (.yml
) 或纯 Ruby (.rb
) 文件在 SimpleStore 中存储翻译。 YAML 是 Rails 开发人员的首选。但是,它有一个很大的缺点。YAML 对空格和特殊字符非常敏感,因此应用程序可能无法正确加载您的字典。Ruby 文件会在第一次请求时使您的应用程序崩溃,因此您可以轻松地找到问题所在。(如果您遇到 YAML 字典的任何“奇怪问题”,请尝试将字典的相关部分放到 Ruby 文件中。)
如果您的翻译存储在 YAML 文件中,则某些键必须转义。它们是
- true, on, yes
- false, off, no
示例
# 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
3.3 将变量传递给翻译
成功国际化应用程序的一个关键考虑因素是,在抽象本地化代码时,避免对语法规则做出错误的假设。在一种区域设置中看起来很基本的语法规则可能在另一种区域设置中不成立。
以下示例显示了不当的抽象,其中对翻译的不同部分的排序做出了假设。请注意,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 gem 附带了一个名为变量插值的特性,它允许您在翻译定义中使用变量,并将这些变量的值传递给翻译方法。
以下示例显示了适当的抽象
<!-- 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
关键字是保留的,不能用作变量名。如果使用,将引发I18n::ReservedInterpolationKey
异常。如果翻译需要插值变量,但该变量未传递给#translate
,则会引发I18n::MissingInterpolationArgument
异常。
3.4 添加日期/时间格式
好的!现在让我们在视图中添加一个时间戳,这样我们也可以演示日期/时间本地化功能。要本地化时间格式,您将 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 后端按预期工作(至少对于“海盗”区域设置)。当然,很有可能有人已经完成了所有工作,即翻译了您区域设置的 Rails 默认设置。请参阅GitHub 上的 rails-i18n 存储库,了解各种区域设置文件的存档。当您将此类文件放在config/locales/
目录中时,它们将自动准备使用。
3.5 其他区域设置的词形变化规则
Rails 允许您为除英语以外的区域设置定义词形变化规则(例如单数化和复数化规则)。在config/initializers/inflections.rb
中,您可以为多个区域设置定义这些规则。初始化程序包含一个用于为英语指定其他规则的默认示例;根据需要,按照其他区域设置的格式进行操作。
3.6 本地化视图
假设您的应用程序中有一个BooksController。您的index 操作在app/views/books/index.html.erb
模板中渲染内容。当您将此模板的本地化变体:index.es.html.erb
放在同一目录中时,Rails 将在区域设置设置为:es
时渲染此模板中的内容。当区域设置设置为默认区域设置时,将使用通用的index.html.erb
视图。(未来的 Rails 版本可能会将这种自动本地化带到public
中的资产等。)
您可以利用此功能,例如,在处理大量静态内容时,将静态内容放入 YAML 或 Ruby 字典中会很麻烦。但请记住,您以后想要对模板进行的任何更改都必须传播到所有模板中。
3.7 区域设置文件组织
当您使用与 i18n 库一起提供的默认 SimpleStore 时,字典存储在磁盘上的纯文本文件中。将应用程序所有部分的翻译放在每个区域设置的一个文件中可能难以管理。您可以将这些文件存储在一个对您有意义的层次结构中。
例如,您的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
这样,您可以将模型和模型属性名称与视图中的文本分开,并将所有这些与“默认值”(例如日期和时间格式)分开。I18n 库的其他存储可能提供不同的此类分离方法。
Rails 中的默认区域设置加载机制不会加载嵌套字典中的区域设置文件,就像我们这里一样。因此,为了使它能够工作,我们必须明确地告诉 Rails 进一步查找
# config/application.rb
config.i18n.load_path += Dir[Rails.root.join("config", "locales", "**", "*.{rb,yml}")]
4 I18n API 功能概述
您现在应该对使用 i18n 库有一个很好的了解,并知道如何国际化一个基本的 Rails 应用程序。在接下来的章节中,我们将更深入地介绍它的功能。
这些章节将展示使用I18n.translate
方法以及translate
视图帮助器方法 的示例(注意视图帮助器方法提供的其他功能)。
涵盖的功能包括:
- 查找翻译
- 将数据插值到翻译中
- 翻译复数化
- 使用安全的 HTML 翻译(仅视图帮助器方法)
- 本地化日期、数字、货币等
4.1 查找翻译
4.1.1 基本查找、范围和嵌套键
通过键查找翻译,键可以是符号或字符串,因此以下调用等效
I18n.t :message
I18n.t "message"
translate
方法还接受一个:scope
选项,它可以包含一个或多个其他键,这些键将用于指定翻译键的“命名空间”或范围
I18n.t :record_invalid, scope: [:activerecord, :errors, :messages]
这将在 Active Record 错误消息中查找:record_invalid
消息。
此外,键和范围都可以指定为点分隔键,如
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]
4.1.2 默认值
当给出: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'
4.1.3 批量和命名空间查找
要一次查找多个翻译,可以传递一个键数组
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"}
4.1.4 “延迟”查找
Rails 实现了一种在视图内部查找区域设置的便捷方法。当您有以下字典时
es:
books:
index:
title: "Título"
您可以在app/views/books/index.html.erb
模板内部查找books.index.title
值,如下所示(注意点)
<%= t '.title' %>
仅translate
视图帮助器方法可使用通过部分进行的自动翻译范围。
“延迟”查找也可以在控制器中使用
en:
books:
create:
success: Book created!
这对设置闪存消息很有用,例如
class BooksController < ApplicationController
def create
# ...
redirect_to books_url, notice: t(".success")
end
end
4.2 复数化
在许多语言中 - 包括英语 - 给定字符串只有两种形式,单数和复数,例如“1 message”和“2 messages”。其他语言(阿拉伯语、日语、俄语 等等)有不同的语法,这些语法具有更多或更少的复数形式。因此,I18n API 提供了一个灵活的复数化功能。
:count
插值变量在以下方面发挥着特殊作用:它既被插值到翻译中,又用于根据复数化后端中定义的复数化规则从翻译中选择复数化。默认情况下,仅应用英语复数化规则。
I18n.backend.store_translations :en, inbox: {
zero: "no messages", # optional
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
的翻译被认为是单数,而:other
被用作复数。如果计数为零,并且存在:zero
条目,则它将用于代替:other
。
如果对键的查找没有返回适合复数化的哈希,则会引发I18n::InvalidPluralizationData
异常。
4.2.1 区域设置特定规则
I18n gem 提供了一个 Pluralization 后端,可用于启用区域设置特定的规则。将其包含到 Simple 后端中,然后将本地化的复数化算法添加到翻译存储中,作为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'
或者,可以使用单独的 gem rails-i18n 来提供更完整的区域设置特定复数化规则集。
4.3 设置和传递区域设置
区域设置可以是伪全局设置为I18n.locale
(它使用与Time.zone
相同的方式使用Thread.current
),也可以作为选项传递给#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
4.4 使用安全的 HTML 翻译
带有 '_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
)帮助器方法中可用。这在视图和控制器中都有效。
4.5 Active Record 模型的翻译
您可以使用Model.model_name.human
和Model.human_attribute_name(attribute)
方法透明地查找模型和属性名称的翻译。
例如,当您添加以下翻译时
en:
activerecord:
models:
user: Customer
attributes:
user:
login: "Handle"
# will translate User attribute "login" as "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
。
4.5.1 错误消息范围
Active Record 验证错误消息也可以轻松翻译。Active Record 提供了几个命名空间,您可以在其中放置消息翻译,以便为特定模型、属性和/或验证提供不同的消息和翻译。它还透明地考虑了单表继承。
这为您提供了灵活调整消息以满足应用程序需求的强大方法。
考虑一个名为 User 的模型,它对 name 属性具有如下验证
class User < ApplicationRecord
validates :name, presence: true
end
在这种情况下,错误消息的键是 :blank
。因此,在我们的示例中,它将按以下顺序尝试以下键并返回第一个结果
activerecord.errors.models.user.attributes.name.blank
activerecord.errors.models.user.blank
activerecord.errors.messages.blank
errors.attributes.name.blank
errors.messages.blank
为了更抽象地解释,它将按以下列表的顺序返回第一个匹配的键。
activerecord.errors.models.[model_name].attributes.[attribute_name].[key]
activerecord.errors.models.[model_name].[key]
activerecord.errors.messages.[key]
errors.attributes.[attribute_name].[key]
errors.messages.[key]
当您的模型另外使用继承时,将从继承链中查找消息。
例如,您可能有一个从 User 继承的 Admin 模型
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
这样,您可以在模型继承链的不同点以及属性、模型或默认作用域中提供各种错误消息的特殊翻译。
4.5.2 错误消息插值
翻译后的模型名称、翻译后的属性名称和值始终可用于插值,分别作为 model
、attribute
和 value
。
例如,您可以使用属性名称,而不是默认的错误消息 "cannot be blank"
:"Please fill in your %{attribute}"
。
- 如果存在,
count
可用于复数形式。
验证 | 带选项 | 消息 | 插值 |
---|---|---|---|
确认 | - | :confirmation | 属性 |
接受 | - | :accepted | - |
存在 | - | :blank | - |
不存在 | - | :present | - |
长度 | :within, :in | :too_short | 计数 |
长度 | :within, :in | :too_long | 计数 |
长度 | :is | :wrong_length | 计数 |
长度 | :minimum | :too_short | 计数 |
长度 | :maximum | :too_long | 计数 |
唯一性 | - | :taken | - |
格式 | - | :invalid | - |
包含 | - | :inclusion | - |
排除 | - | :exclusion | - |
关联 | - | :invalid | - |
非可选关联 | - | :required | - |
数值 | - | :not_a_number | - |
数值 | :greater_than | :greater_than | 计数 |
数值 | :greater_than_or_equal_to | :greater_than_or_equal_to | 计数 |
数值 | :equal_to | :equal_to | 计数 |
数值 | :less_than | :less_than | 计数 |
数值 | :less_than_or_equal_to | :less_than_or_equal_to | 计数 |
数值 | :other_than | :other_than | 计数 |
数值 | :only_integer | :not_an_integer | - |
数值 | :in | :in | 计数 |
数值 | :odd | :odd | - |
数值 | :even | :even | - |
比较 | :greater_than | :greater_than | 计数 |
比较 | :greater_than_or_equal_to | :greater_than_or_equal_to | 计数 |
比较 | :equal_to | :equal_to | 计数 |
比较 | :less_than | :less_than | 计数 |
比较 | :less_than_or_equal_to | :less_than_or_equal_to | 计数 |
比较 | :other_than | :other_than | 计数 |
4.6 Action Mailer 电子邮件主题的翻译
如果您没有将主题传递给 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!"
4.7 提供 i18n 支持的其他内置方法概述
Rails 在一些辅助方法中使用固定字符串和其他本地化,例如格式字符串和其他格式信息。以下是一个简要概述。
4.7.1 Action View 辅助方法
distance_of_time_in_words
会翻译和复数化其结果,并插值秒、分钟、小时等数量。请参阅 datetime.distance_in_words 翻译。datetime_select
和select_month
使用翻译后的月份名称来填充生成的 select 标签。请参阅 date.month_names 获取翻译。datetime_select
还从 date.order 中查找 order 选项(除非您显式传递选项)。所有日期选择辅助方法都会使用 datetime.prompts 范围中的翻译来翻译提示(如果适用)。number_to_currency
、number_with_precision
、number_to_percentage
、number_with_delimiter
和number_to_human_size
辅助方法使用位于 number 范围中的数字格式设置。
4.7.2 Active Model 方法
model_name.human
和human_attribute_name
使用模型名称和属性名称的翻译(如果存在于 activerecord.models 范围内)。它们还支持继承的类名的翻译(例如,用于 STI),如上文“错误消息范围”中所述。ActiveModel::Errors#generate_message
(由 Active Model 验证使用,但也可以手动使用)使用model_name.human
和human_attribute_name
(见上文)。它还会翻译错误消息并支持继承的类名的翻译,如上文“错误消息范围”中所述。ActiveModel::Error#full_message
和ActiveModel::Errors#full_messages
使用从errors.format
中查找的格式(默认:"%{attribute} %{message}"
)将属性名称添加到错误消息前面。要自定义默认格式,请在应用程序的语言环境文件中覆盖它。要自定义每个模型或每个属性的格式,请参阅config.active_model.i18n_customize_full_message
.
4.7.3 Active Support 方法
Array#to_sentence
使用 support.array 范围中给出的格式设置。
5 如何存储自定义翻译
与 Active Support 一起提供的 Simple 后端允许您以纯 Ruby 和 YAML 格式存储翻译。2
例如,提供翻译的 Ruby 哈希可能如下所示
{
pt: {
foo: {
bar: "baz"
}
}
}
等效的 YAML 文件将如下所示
pt:
foo:
bar: baz
如您所见,在这两种情况下,顶层键都是语言环境。:foo
是一个命名空间键,而 :bar
是翻译“baz”的键。
以下来自 Active Support en.yml
翻译 YAML 文件的“真实”示例
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 lambda 存储为语言环境数据的组成部分,例如,用于特殊日期格式。
6 自定义 i18n 设置
6.1 使用不同的后端
由于多种原因,与 Active Support 一起提供的 Simple 后端只做了针对 Ruby on Rails 的“最简单的事情”3 ... 这意味着它只能保证对英语以及与英语非常相似的语言有效。此外,Simple 后端只能读取翻译,而无法动态地将它们存储到任何格式。
但这并不意味着您必须忍受这些限制。Ruby I18n gem 使您可以很容易地用更适合您需求的其他内容替换 Simple 后端实现,方法是将后端实例传递给 I18n.backend=
设置器。
例如,您可以用 Chain 后端替换 Simple 后端,以便将多个后端链接在一起。这在您希望将标准翻译与 Simple 后端一起使用,但将自定义应用程序翻译存储在数据库或其他后端中时很有用。
使用 Chain 后端,您可以使用 Active Record 后端,然后回退到(默认)Simple 后端
I18n.backend = I18n::Backend::Chain.new(I18n::Backend::ActiveRecord.new, I18n.backend)
6.2 使用不同的异常处理程序
I18n API 定义了以下异常,这些异常将在后端遇到相应的意外情况时引发
异常 | 原因 |
---|---|
I18n::MissingTranslationData |
未找到请求键的翻译 |
I18n::InvalidLocale |
设置为 I18n.locale 的语言环境无效(例如 nil ) |
I18n::InvalidPluralizationData |
传递了 count 选项,但翻译数据不适合复数化 |
I18n::MissingInterpolationArgument |
翻译需要一个未传递的插值参数 |
I18n::ReservedInterpolationKey |
翻译包含一个保留的插值变量名称(即以下之一:scope 、default ) |
I18n::UnknownFileType |
后端不知道如何处理添加到 I18n.load_path 的文件类型 |
6.2.1 自定义 I18n::MissingTranslationData
的处理方式
如果 config.i18n.raise_on_missing_translations
为 true
,则 I18n::MissingTranslationData
错误将从视图和控制器中引发。如果值为 :strict
,则模型也会引发错误。最好在您的测试环境中打开此选项,以便您可以捕获请求缺失翻译的位置。
如果 config.i18n.raise_on_missing_translations
为 false
(所有环境中的默认值),则将打印异常的错误消息。这包含缺失的键/范围,以便您可以修复您的代码。
如果您希望进一步自定义此行为,您应该设置 config.i18n.raise_on_missing_translations = false
,然后实现一个 I18n.exception_handler
。自定义异常处理程序可以是 proc 或一个带有 call
方法的类
# config/initializers/i18n.rb
module I18n
class RaiseExceptForSpecificKeyExceptionHandler
def call(exception, locale, key, options)
if key == "special.key"
"translation missing!" # return this, don't raise it
elsif exception.is_a?(MissingTranslation)
raise exception.to_exception
else
raise exception
end
end
end
end
I18n.exception_handler = I18n::RaiseExceptForSpecificKeyExceptionHandler.new
这将像默认处理程序一样引发所有异常,除了 I18n.t("special.key")
的情况。
7 翻译模型内容
本指南中描述的 I18n API 主要用于翻译界面字符串。如果您希望翻译模型内容(例如,博客文章),则需要使用不同的解决方案来帮助您。
一些 gem 可以帮助您完成此操作
8 结论
现在,您应该对 Ruby on Rails 中的 i18n 支持的工作原理有一个很好的概述,并准备好开始翻译您的项目。
9 为 Rails i18n 贡献代码
Ruby on Rails 中的 i18n 支持是在 2.2 版本中引入的,并且仍在不断发展。该项目遵循了 Ruby on Rails 的良好开发传统,即先在 gem 和真实应用程序中发展解决方案,然后再将最广泛使用的功能中的佼佼者选入核心。
因此,我们鼓励大家在 gem 或其他库中尝试新的想法和功能,并将它们提供给社区。(别忘了在我们的 邮件列表 上宣布您的工作!)
如果您发现自己的语言(语言环境)在我们的 Ruby on Rails 示例翻译数据 仓库中缺失,请 fork 该仓库,添加您的数据并发送一个 pull request。
10 资源
- GitHub: rails-i18n - rails-i18n 项目的代码仓库和问题跟踪器。最重要的是,您可以找到大量的 Rails 示例翻译,这些翻译在大多数情况下应该适用于您的应用程序。
- GitHub: i18n - i18n gem 的代码仓库和问题跟踪器。
11 作者
- Sven Fuchs(最初的作者)
- Karel Minařík
12 脚注
1 或者,引用 维基百科:"国际化是指设计软件应用程序,使其能够适应不同的语言和地区,而无需进行工程更改。本地化是指通过添加特定于区域或语言的组件和翻译文本,使软件适应特定地区或语言的过程。"
2 其他后端可能允许或要求使用其他格式,例如,GetText 后端可能允许读取 GetText 文件。
3 其中一个原因是,我们不想为不需要任何 I18n 功能的应用程序暗示任何不必要的负载,因此我们需要尽可能地简化 I18n 库以支持英语。另一个原因是,几乎不可能为所有现有语言的所有与 I18n 相关问题实现一个万能的解决方案。因此,一种允许我们轻松交换整个实现的解决方案是合适的。这也有助于更轻松地试验自定义功能和扩展。