edge
更多資訊請參考 rubyonrails.org: 更多 Ruby on Rails

Rails國際化(I18n)API

Ruby I18n(縮寫為國際化)gem是隨Ruby on Rails一起提供的(從Rails 2.2開始),它提供了一個易於使用和可擴展的框架,用於將您的應用程序翻譯成單一自定義語言(非英語)或為您的應用程序提供多語言支持。

“國際化”的過程通常意味著將所有字符串和其他與語言相關的部分(例如日期或貨幣格式)從應用程序中抽象出來。“本地化”的過程是為這些部分提供翻譯和本地化格式。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模塊,定義了庫的工作方式
  • 默認後端(故意命名為簡單後端),實現了這些方法

作為用戶,您應該始終只訪問I18n模塊上的公共方法,但了解後端的功能也是有用的。

注意:可以將提供的簡單後端與更強大的後端交換,該後端將翻譯數據存儲在關聯數據庫、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 Hash 來存儲預設(簡單)後端中的翻譯。

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)時,如果沒有指定語言環境,您將不會遇到Routing Error。這在您想要在未指定語言環境時使用默認語言環境時非常有用。

當然,您需要特別注意應用程序的根URL(通常是“主頁”或“儀表板”)。像http://localhost:3001/nl這樣的URL不會自動工作,因為您的routes.rb中的root to: "dashboard#index"聲明不考慮語言環境。(這樣做是正確的:只有一個“根”URL。)

您可能需要映射這些URL:

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

請特別注意路由的順序,以便此路由聲明不會“吃掉”其他路由。(您可能需要將其直接添加到root :to聲明之前。)

注意:請查看各種簡化路由工作的gem: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等服務或像geocoder這樣的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 demo translation missing

注意: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 demo translated to English

如果地區設定通過 URL 設為海盜地區(http://localhost:3000?locale=pirate),回應渲染海盜字串:

rails i18n demo translated to pirate

注意:當你添加新的地區文件時,需要重新啟動伺服器。

你可以使用 YAML(.yml)或純 Ruby(.rb)文件來存儲你的翻譯。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} €"

所有的語法和標點決策都在定義本身中進行,所以抽象可以給出正確的翻譯。

注意: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 demo localized time to pirate

提示:現在你可能需要添加一些更多的日期/時間格式,以使 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 message" 和 "2 messages"。其他語言(阿拉伯語日語俄語 等)有不同的語法,具有額外或更少的複數形式。因此,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>歡迎 %{username}!</b>"

你可以安全地傳遞由用戶設置的用戶名:

<%# 這是安全的,如果需要,它會被轉義。 %>
<%= t('welcome_html', username: @current_user.username) %>

而安全字符串則是直接插值。

注意:自動轉換為HTML安全的翻譯文本僅在translate(或t)輔助方法中可用。這在視圖和控制器中都有效。

i18n demo HTML safe

4.5 Active Record 模型的翻譯

你可以使用Model.model_name.humanModel.human_attribute_name(attribute)方法來透明地查找模型和屬性名的翻譯。

例如,當你添加以下翻譯時:

en:
  activerecord:
    models:
      user: 顧客
    attributes:
      user:
        login: "帳號"
      # 將 User 的屬性 "login" 翻譯為 "帳號"

那麼User.model_name.human將返回"顧客",User.human_attribute_name("login")將返回"帳號"。

你還可以為模型名設置複數形式,添加如下內容:

en:
  activerecord:
    models:
      user:
        one: 顧客
        other: 顧客們

然後User.model_name.human(count: 2)將返回"顧客們"。使用count: 1或不帶參數將返回"顧客"。

如果你需要訪問給定模型中的嵌套屬性,你應該在翻譯文件的模型級別下將其嵌套在model/attribute下:

en:
  activerecord:
    attributes:
      user/role:
        admin: "管理員"
        contributor: "貢獻者"

然後User.human_attribute_name("role.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",像這樣使用:"請填寫你的 %{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
zh-TW:
  user_mailer:
    welcome:
      subject: "歡迎來到 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
zh-TW:
  user_mailer:
    welcome:
      subject: "%{user},歡迎來到 Rails Guides!"

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 backend 允許您將翻譯存儲在純 Ruby 和 YAML 格式中。2

例如,提供翻譯的 Ruby Hash 可以如下所示:

{
  pt: {
    foo: {
      bar: "baz"
    }
  }
}

等效的 YAML 文件如下所示:

pt:
  foo:
    bar: baz

如您所見,無論是 Ruby 還是 YAML,頂層鍵都是語言代碼。: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 lambdas 作為區域數據的一部分存儲,例如用於特殊日期格式。

6 自定義您的 I18n 設置

6.1 使用不同的後端

出於幾個原因,Active Support 附帶的 Simple backend 只對英語和非常接近英語的語言進行了「可能的最簡單的事情」的處理3。此外,簡單的後端只能讀取翻譯,無法動態將其存儲到任何格式中。

這並不意味著您必須受到這些限制的束縛。Ruby I18n gem 使得非常容易通過將後端實例傳遞給 I18n.backend= setter 來將 Simple backend 實現替換為更適合您需求的其他後端。

例如,您可以將 Simple backend 替換為 Chain backend,以將多個後端鏈接在一起。這在您希望使用 Simple backend 的標準翻譯,但將自定義應用程序翻譯存儲在數據庫或其他後端時非常有用。 使用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主要用於翻譯界面字符串。如果您想要翻譯模型內容(例如博客文章),您將需要一個不同的解決方案來幫助您。

有幾個寶石可以幫助您:

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

8 結論

到目前為止,您應該對Ruby on Rails中的I18n支持有了很好的概述,並準備開始翻譯您的項目。

9 貢獻給Rails I18n

Ruby on Rails中的I18n支持在2.2版本中引入,並且仍在不斷發展。該項目遵循Ruby on Rails開發的良好傳統,首先在寶石和實際應用程序中發展解決方案,然後只選擇最廣泛使用的功能的最佳解決方案,並將其納入核心。

因此,我們鼓勵每個人在寶石或其他庫中嘗試新的想法和功能,並將它們提供給社區。(不要忘記在我們的郵件列表上宣布您的工作!)

如果您發現我們的Ruby on Rails示例翻譯數據存儲庫中缺少您自己的語言環境,請fork該存儲庫,添加您的數據,然後發送拉取請求

10 資源

  • Google group: rails-i18n - 這個項目的郵件列表。
  • GitHub: rails-i18n - rails-i18n項目的代碼存儲庫和問題跟踪器。最重要的是,您可以在這裡找到許多Rails的示例翻譯,這些翻譯在大多數情況下應該適用於您的應用程序。
  • GitHub: i18n - i18n寶石的代碼存儲庫和問題跟踪器。

11 作者

12 註腳

回饋

歡迎協助提升本指南的品質。

如果您發現任何錯別字或事實錯誤,請貢獻您的力量。 開始之前,您可以閱讀我們的 文件貢獻 部分。

您也可能會發現不完整的內容或過時的資訊。 請為主要的文件補充任何遺漏的內容。請先檢查 Edge 指南,以確認問題是否已經修復或尚未在主分支上修復。 請參考 Ruby on Rails 指南指引 以了解風格和慣例。

如果您發現需要修復但無法自行修補的問題,請 開啟一個問題

最後但同樣重要的是,關於 Ruby on Rails 文件的任何討論都非常歡迎在 官方 Ruby on Rails 論壇 上進行。