v7.1.3.2
更多資訊請至 rubyonrails.org: 更多 Ruby on Rails

Rails 國際化 (I18n) API

Ruby I18n(國際化的簡寫)寶石與 Ruby on Rails(從 Rails 2.2 開始)一起提供,它提供了一個易於使用且可擴充的架構,用於將您的應用程式翻譯成單一自訂語言(非英語)或在您的應用程式中提供多語言支援

「國際化」的過程通常表示將所有字串和其他特定語言環境的位元(例如日期或貨幣格式)從您的應用程式中抽象出來。「在地化」的過程表示為這些位元提供翻譯和在地化格式。1

因此,在國際化 Rails 應用程式的過程中,您必須

在地化應用程式的過程中,您可能會想要執行以下三件事

本指南將引導您了解 I18n API,並包含有關如何從一開始就將 Rails 應用程式國際化的教學課程。

閱讀本指南後,您將了解

Ruby I18n 框架提供您國際化/在地化 Rails 應用程式所需的所有必要手段。您也可以使用各種可用的寶石來新增其他功能或特點。請參閱 rails-i18n 寶石 以取得更多資訊。

1 Ruby on Rails 中的 I18n 如何運作

國際化是一個複雜的問題。自然語言在許多方面有所不同(例如複數化規則),因此很難提供工具來一次解決所有問題。基於這個原因,Rails I18n API 專注於

  • 提供對英語和類似語言的支援
  • 讓自訂和擴充所有其他語言變得容易

作為此解決方案的一部分,Rails 框架中的每個靜態字串 - 例如 Active Record 驗證訊息、時間和日期格式 - 已國際化。Rails 應用程式的在地化表示定義這些字串在所需語言中的翻譯值。

若要在地化、儲存和更新應用程式中的內容(例如翻譯部落格文章),請參閱 翻譯模型內容 區段。

1.1 函式庫的整體架構

因此,Ruby I18n 寶石分成兩部分

  • 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 檔案中的時間和日期格式。你可以使用 YAML 或標準 Ruby Hash 在預設 (Simple) 後端中儲存翻譯。

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,不會覆寫來自外部寶石的翻譯。

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 gr.application.local
# in your /etc/hosts file to try this out locally
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 中所做的那樣。我們希望在這種情況下有像 www.example.com/books?locale=jawww.example.com/ja/books 這樣的 URL。

這種方法與從網域名稱設定語言環境有幾乎相同的優點:也就是它符合 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 等)現在將自動在查詢字串中包含語言環境,如下所示: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 的傳回值會快取在每個要求中,因此無法在迴圈中呼叫輔助方法來產生語言環境選取器中的 URL,並在每次反覆運算中設定對應的 I18n.locale。相反地,不要變更 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 宣告之前。)

看看各種簡化路由工作的寶石: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 可以用來實作此方法。

一般來說,此方法的可靠性遠低於使用語言標頭,因此不建議用於大多數的網路應用程式。

2.2.5 從 Session 或 Cookie 儲存語言環境

您可能會想將所選的語言環境儲存在 sessioncookie 中。不過,請不要這麼做。語言環境應為透明的,且是 URL 的一部分。這樣一來,您就不會破壞人們對網路本身的基本假設:如果您將 URL 傳送給朋友,他們應該會看到與您相同的頁面和內容。一個比較文雅的說法是,您正在 實作 REST。在 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 設定為海盜地區設定(https://127.0.0.1:3000?locale=pirate),回應會呈現海盜字串

rails i18n demo translated to pirate

在你新增新的地區設定檔時,你需要重新啟動伺服器。

你可以使用 YAML(.yml)或純 Ruby(.rb)檔來儲存你在 SimpleStore 中的翻譯。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

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 後端依預期運作(至少對於「海盜」地區設定)。當然,很有可能已經有人透過翻譯 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. 由於兩者都沒有產生結果,因此會傳回字串「不在這裡」

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 則訊息」和「2 則訊息」。其他語言(阿拉伯語日語俄語 和許多其他語言)有不同的文法,具有額外的或更少的 複數形式。因此,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 寶石提供複數化後端,可以用來啟用區域設定特定規則。將它包含到 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'

或者,可以使用獨立的 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) 輔助方法取得。這在檢視和控制器中都有效。

i18n demo HTML safe

4.5 Active Record 模型的翻譯

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

例如,當您加入以下翻譯時

en:
  activerecord:
    models:
      user: Customer
    attributes:
      user:
        login: "Handle"
      # will translate User attribute "login" as "Handle"

那麼 User.model_name.human 會傳回「客戶」,而 User.human_attribute_name("login") 會傳回「處理」。

您也可以為模型名稱設定複數形式,如下所示

en:
  activerecord:
    models:
      user:
        one: Customer
        other: Customers

那麼 User.model_name.human(count: 2) 會傳回「客戶」。使用 count: 1 或不使用參數會傳回「客戶」。

如果您需要存取給定模型內的巢狀屬性,您應該在翻譯檔案的模型層級將這些屬性巢狀在 model/attribute

en:
  activerecord:
    attributes:
      user/role:
        admin: "Admin"
        contributor: "Contributor"

那麼 User.human_attribute_name("role.admin") 會傳回「管理員」。

如果您使用包含 ActiveModel 但未繼承自 ActiveRecord::Base 的類別,請在上述金鑰路徑中將 activerecord 替換為 activemodel

4.5.1 錯誤訊息範圍

Active Record 驗證錯誤訊息也可以輕鬆翻譯。Active Record 提供幾個命名空間,您可以在其中放置訊息翻譯,以提供特定模型、屬性及/或驗證的不同訊息和翻譯。它也會透明地考量單一表格繼承。

這讓您有強大的方式來彈性調整訊息以符合應用程式的需求。

考慮一個 User 模型,其對 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

當您的模型另外使用繼承時,訊息會在繼承鏈中查詢。

例如,您可能有一個繼承自 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 屬性
接受 - :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_selectselect_month 使用已翻譯的月份名稱來填入產生的選取標籤。請參閱 date.month_names 翻譯。datetime_select 也會從 date.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 Hash 可能如下所示

{
  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 寶石讓您可以輕鬆地將 Simple 後端實作替換為更符合您需求的其他實作,方法是將後端實例傳遞給 I18n.backend= 設定器。

例如,您可以將 Simple 後端替換為 Chain 後端,以將多個後端串連在一起。當您想使用具有 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 翻譯包含保留的內插變數名稱(例如:範圍預設
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。自訂例外狀況處理常式可以是程序或具有 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 主要用於翻譯介面字串。如果您想要翻譯模型內容(例如部落格文章),您需要不同的解決方案來協助您進行這項工作。

有幾個寶石可以協助您進行這項工作

  • Mobility:提供支援,讓您能以許多格式儲存翻譯,包括翻譯表格、JSON 欄位(PostgreSQL)等。
  • Traco:可翻譯的欄位儲存在模型表格本身

8 結論

在這個時候,您應該已經對 Ruby on Rails 中的 I18n 支援運作方式有很好的概觀,並準備好開始翻譯您的專案。

9 對 Rails I18n 進行貢獻

Ruby on Rails 中的 I18n 支援是在 2.2 版本中引入的,並且仍在持續發展中。此專案遵循 Ruby on Rails 良好的開發傳統,先在寶石和實際應用程式中發展解決方案,然後才精選出最廣泛使用的最佳功能納入核心。

因此,我們鼓勵大家在寶石或其他函式庫中嘗試新的構想和功能,並讓社群使用。(別忘了在我們的 郵寄清單 上宣布您的作品!)

如果您發現我們的 範例翻譯資料 儲存庫中缺少您的語言環境(語言),請 分岔 儲存庫,新增您的資料,並發送 拉取要求

10 資源

11 作者

12 註腳

1 或引用 維基百科「國際化是設計軟體應用程式的過程,讓它可以在不進行工程變更的情況下,適應各種語言和地區。在地化是透過新增特定於語言環境的元件和翻譯文字,來調整軟體以適應特定地區或語言的過程。」

2 其他後端可能允許或需要使用其他格式,例如 GetText 後端可能允許讀取 GetText 檔案。

3 這些原因之一是我們不想對不需要任何 I18n 功能的應用程式造成任何不必要的負擔,所以我們需要讓 I18n 程式庫對英文來說盡可能簡單。另一個原因是,對於所有現有語言的 I18n 相關問題,幾乎不可能實作一個適用於所有問題的解決方案。因此,一個允許我們輕鬆交換整個實作的解決方案無論如何都是適當的。這也讓嘗試自訂功能和延伸變得容易許多。

回饋

我們鼓勵您協助提升本指南的品質。

如果您發現任何錯字或事實錯誤,請貢獻您的心力。要開始,您可以閱讀我們的文件貢獻章節。

您也可能會發現不完整或過時的內容。請務必為 main 新增任何遺漏的文件。務必先查看Edge Guides,以驗證這些問題是否已在主分支中修正。查看Ruby on Rails 指南指南以了解風格和慣例。

如果您出於任何原因發現需要修正的地方,但無法自行修補,請開啟一個問題

最後但並非最不重要的一點是,我們非常歡迎在官方 Ruby on Rails 論壇上針對 Ruby on Rails 文件進行任何類型的討論。