edge
更多信息请访问 rubyonrails.org: 更多 Ruby on Rails

Rails国际化(I18n)API

Ruby I18n(简称为国际化)gem与Ruby on Rails一起提供了一个易于使用和可扩展的框架,用于将您的应用程序翻译为除英语以外的单一自定义语言,或者为您的应用程序提供多语言支持。

“国际化”过程通常意味着将所有字符串和其他与区域设置相关的内容(例如日期或货币格式)从应用程序中抽象出来。 “本地化”过程意味着为这些内容提供翻译和本地化格式。1

因此,在国际化您的Rails应用程序的过程中,您需要:

在本地化应用程序的过程中,您可能想要执行以下三个操作:

本指南将引导您了解I18n API,并包含有关如何从头开始国际化Rails应用程序的教程。

阅读本指南后,您将了解:

注意: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 # 查找文本翻译
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应用程序!

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 文件中的时间和日期格式。您可以使用 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

# 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 中,将 不会 覆盖来自外部 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)。使用此方法,http://localhost:3000?locale=pt 将呈现葡萄牙语本地化,而 http://localhost: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

# 从顶级域名获取区域设置,如果该区域设置不可用,则返回+nil+
# 您需要在/etc/hosts文件中添加以下内容:
#   127.0.0.1 application.com
#   127.0.0.1 application.it
#   127.0.0.1 application.pl
# 以在本地尝试此功能
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)
# 您需要在/etc/hosts文件中添加以下内容:
#   127.0.0.1 gr.application.local
# 以在本地尝试此功能
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=jawww.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_pathroot_url,资源路由如books_pathbooks_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"(对于默认区域设置)。然后,像http://localhost: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

通过这种方法,当访问资源(例如http://localhost:3001/books)时没有指定区域设置时,将不会出现“路由错误”。当您想要在未指定区域设置时使用默认区域设置时,这非常有用。

当然,您需要特别注意应用程序的根URL(通常是“主页”或“仪表板”)。像http://localhost:3001/nl这样的URL不会自动工作,因为您的routes.rb中的root to: "dashboard#index"声明不考虑区域设置。(这样做是正确的:只有一个“根”URL。)

您可能需要映射这些URL:

# config/routes.rb
get '/:locale' => 'dashboard#index'

请特别注意路由的顺序,以免此路由声明“吞噬”其他路由。(您可能希望将其直接添加在root :to声明之前。)

注意:请查看各种简化路由工作的宝石:routing_filterroute_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之类的服务或gem来实现此方法。

总的来说,这种方法比使用语言标头要不可靠得多,不建议在大多数Web应用程序中使用。

2.2.5 将区域设置存储在会话或Cookie中

警告:您可能会尝试将选择的区域设置存储在会话Cookie中。然而,不要这样做。区域设置应该是透明的,并且是URL的一部分。这样,您不会破坏人们对Web本身的基本假设:如果您向朋友发送一个URL,他们应该看到与您相同的页面和内容。这个词的花哨之处在于,您正在遵循RESTful。在Stefan Tilkov的文章中阅读有关RESTful方法的更多信息。有时候,对于这个规则有例外情况,下面将讨论这些例外情况。

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>

rails i18n demo untranslated

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 i18n演示翻译缺失

注意:Rails在视图中添加了一个ttranslate)辅助方法,以便您不需要一直拼写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区域设置,响应呈现英文字符串:

rails i18n演示翻译为英文

如果通过URL将区域设置为海盗区域设置(http://localhost:3000?locale=pirate),响应将呈现海盗字符串:

rails i18n演示翻译为海盗语

注意:添加新的区域设置文件时,需要重新启动服务器。

您可以使用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' # => 翻译缺失
I18n.t 'failure.off'   # => 翻译缺失
I18n.t 'failure.true'  # => 翻译缺失

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} €"

所有的语法和标点决策都在定义本身中进行,因此抽象可以给出正确的翻译。

注意:defaultscope关键字是保留的,不能用作变量名。如果使用,将引发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"

这样就会得到:

rails i18n演示本地化时间为海盗语

提示:现在您可能需要添加一些更多的日期/时间格式,以使I18n后端按预期工作(至少对于“pirate”区域设置来说是如此)。当然,有很大的机会,有人已经通过翻译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时,当区域设置为:es时,Rails将呈现此模板中的内容。当区域设置为默认区域时,将使用通用的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 批量和命名空间查找

要一次查找多个翻译,可以传递一个键的数组: ```ruby I18n.t [:odd, :even], scope: 'errors.messages'

=> ["必须是奇数", "必须是偶数"]


此外,一个键可以翻译为一个(可能是嵌套的)分组翻译的哈希。例如,可以使用以下方式将 _所有_ Active Record 错误消息作为哈希接收:

```ruby
I18n.t 'errors.messages'
# => {:inclusion=>"不在列表中", :exclusion=> ... }

如果您想对一个批量翻译的哈希执行插值,您需要将 deep_interpolation: true 作为参数传递。当您有以下字典时:

en:
  welcome:
    title: "欢迎!"
    content: "欢迎来到 %{app_name}"

那么如果没有设置,则嵌套插值将被忽略:

I18n.t 'welcome', app_name: '书店'
# => {:title=>"欢迎!", :content=>"欢迎来到 %{app_name}"}

I18n.t 'welcome', deep_interpolation: true, app_name: '书店'
# => {:title=>"欢迎!", :content=>"欢迎来到书店"}

4.1.4 "懒惰"查找

Rails 实现了一种方便的方式来在 视图 中查找区域设置。当您有以下字典时:

es:
  books:
    index:
      title: "标题"

您可以在 app/views/books/index.html.erb 模板中 内部 查找 books.index.title 值,如下所示(注意点号):

<%= t '.title' %>

注意:只有从 translate 视图辅助方法才能自动翻译作用域。

"懒惰"查找也可以在控制器中使用:

en:
  books:
    create:
      success: 图书创建成功!

这对于设置闪存消息非常有用,例如:

class BooksController < ApplicationController
  def create
    # ...
    redirect_to books_url, notice: t('.success')
  end
end

4.2 复数形式

在许多语言中,包括英语,对于给定的字符串只有两种形式,单数和复数,例如 "1 条消息" 和 "2 条消息"。其他语言(阿拉伯语日语俄语 等)有不同的语法,有额外或更少的复数形式。因此,I18n API 提供了灵活的复数形式功能。

:count 插值变量在翻译中既被插值为翻译,又用于根据复数化后端中定义的复数化规则选择翻译。默认情况下,只应用英语的复数化规则。

I18n.backend.store_translations :en, inbox: {
  zero: '没有消息', # 可选
  one: '一条消息',
  other: '%{count} 条消息'
}
I18n.translate :inbox, count: 2
# => '2 条消息'

I18n.translate :inbox, count: 1
# => '一条消息'

I18n.translate :inbox, count: 0
# => '没有消息'

: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 提供了一个复数化后端,可以用于启用特定于区域设置的规则。将其包含到 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: '一个或没有', other: '超过一个' }

I18n.t :apples, count: 0, locale: :pt
# => '一个或没有'

或者,可以使用单独的 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>欢迎!</b>
  hello_html: <b>你好!</b>
  title:
    html: <b>标题!</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>"

你可以安全地传递用户设置的用户名:

<%# 这是安全的,如果需要的话,它会被转义。 %>
<%= t('welcome_html', username: @current_user.username) %>

而安全字符串则会直接插值。

注意:自动转换为HTML安全的翻译文本仅适用于translate(或t)辅助方法。这适用于视图和控制器。

i18n演示HTML安全

4.5 Active Record模型的翻译

您可以使用Model.model_name.humanModel.human_attribute_name(attribute)方法来透明地查找模型和属性名称的翻译。

例如,当您添加以下翻译时:

en:
  activerecord:
    models:
      user: Customer
    attributes:
      user:
        login: "Handle"
      # 将User属性"login"翻译为"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模型:

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

当您的模型还使用继承时,消息将在继承链中查找。

例如,您可能有一个从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 错误消息插值

翻译的模型名称、翻译的属性名称和值始终可以作为插值modelattributevalue使用。

因此,例如,您可以使用属性名称而不是默认错误消息"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 -
comparison :greater_than :greater_than count
comparison :greater_than_or_equal_to :greater_than_or_equal_to count
comparison :equal_to :equal_to count
comparison :less_than :less_than count
comparison :less_than_or_equal_to :less_than_or_equal_to count
comparison :other_than :other_than count

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: "欢迎来到 Rails 指南!"

要将参数发送到插值中,请在邮件发送器上使用 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},欢迎来到 Rails 指南!"

4.7 其他内置提供 I18n 支持的方法概述

Rails 在一些辅助方法中使用固定字符串和其他本地化,例如格式字符串和其他格式信息。以下是简要概述。

4.7.1 Action View 辅助方法

  • distance_of_time_in_words 翻译和复数化其结果,并插入秒、分钟、小时等的数量。查看 datetime.distance_in_words 的翻译。

  • datetime_selectselect_month 使用翻译后的月份名称填充生成的选择标签。查看 date.month_names 的翻译。datetime_select 还会查找 date.order 中的 order 选项(除非你显式传递了该选项)。所有日期选择辅助方法都会使用 datetime.prompts 范围中的翻译来翻译提示文本(如果适用)。

  • number_to_currencynumber_with_precisionnumber_to_percentagenumber_with_delimiternumber_to_human_size 辅助方法使用位于 number 范围中的数字格式设置。

4.7.2 Active Model 方法

  • model_name.humanhuman_attribute_name 如果在 activerecord.models 范围中可用,将使用模型名称和属性名称的翻译。它们还支持继承类名的翻译(例如,用于 STI),如上面的“错误消息范围”中所述。

  • ActiveModel::Errors#generate_message(用于 Active Model 验证,但也可以手动使用)使用 model_name.humanhuman_attribute_name(见上文)。它还会翻译错误消息,并支持继承类名的翻译,如上面的“错误消息范围”中所述。

  • ActiveModel::Error#full_messageActiveModel::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 后端只对英语和与英语非常相似的语言进行了“可能的最简单的事情”。3 这意味着它只能保证适用于英语,而其他语言可能会有问题。此外,简单后端只能读取翻译,无法动态存储到任何格式。

但这并不意味着你受限于这些限制。Ruby I18n gem 使得很容易通过将后端实例传递给 I18n.backend= setter 来将 Simple 后端实现替换为更适合你需求的其他后端。

例如,你可以用 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 传递了计数选项,但翻译数据不适合复数形式
I18n::MissingInterpolationArgument 翻译期望传递的插值参数未传递
I18n::ReservedInterpolationKey 翻译包含保留的插值变量名(即:scopedefault之一)
I18n::UnknownFileType 后端不知道如何处理添加到I18n.load_path的文件类型

6.2.1 自定义如何处理I18n::MissingTranslationData

如果config.i18n.raise_on_missing_translationstrue,将引发I18n::MissingTranslationData错误。在测试环境中打开此选项是个好主意,这样您可以捕获请求缺失翻译的地方。

如果config.i18n.raise_on_missing_translationsfalse(所有环境的默认值),将打印异常的错误消息。该消息包含缺失的键/作用域,以便您可以修复代码。

如果您想进一步自定义此行为,应将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!" # 返回此值,而不是引发异常
      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可以帮助您:

  • Mobility:提供对多种格式的翻译支持,包括翻译表、JSON列(PostgreSQL)等。
  • Traco:在模型表本身中存储可翻译的列

8 结论

到目前为止,您应该对Ruby on Rails中的I18n支持有了一个很好的概述,并准备开始翻译您的项目。

9 为Rails I18n做贡献

Ruby on Rails中的I18n支持在2.2版本中引入,并且仍在不断发展。该项目遵循Ruby on Rails开发传统,首先在gems和实际应用程序中演化解决方案,然后从中挑选最适合的、最广泛使用的功能,包含到核心中。

因此,我们鼓励每个人在gems或其他库中尝试新的想法和功能,并使它们可用于社区。(不要忘记在我们的邮件列表上宣布您的工作!)

如果您发现我们的Ruby on Rails示例翻译数据存储库中缺少您自己的区域设置(语言),请fork该存储库,添加您的数据,并发送拉取请求

10 资源

  • Google group: rails-i18n - 该项目的邮件列表。
  • GitHub: rails-i18n - rails-i18n项目的代码存储库和问题跟踪器。最重要的是,您可以在其中找到许多适用于Rails的示例翻译,在大多数情况下,这些翻译应该适用于您的应用程序。
  • GitHub: i18n - i18n gem的代码存储库和问题跟踪器。

11 作者

12 脚注

反馈

欢迎您帮助改进本指南的质量。

如果您发现任何拼写错误或事实错误,请贡献您的意见。 要开始,请阅读我们的 文档贡献 部分。

您还可能会发现不完整的内容或过时的内容。 请为主要内容添加任何缺失的文档。请先检查 Edge 指南,以验证问题是否已经修复或尚未修复。 请参阅 Ruby on Rails 指南准则 以了解样式和规范。

如果您发现需要修复但无法自行修复的问题,请 提交问题

最后但同样重要的是,欢迎您在 官方 Ruby on Rails 论坛 上讨论有关 Ruby on Rails 文档的任何问题。