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 # 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 將翻譯儲存在預設 (簡單) 後端中。
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
,則 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 中所做的一樣。在這種情況下,我們希望有像 www.example.com/books?locale=ja
或 www.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_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
的回傳值是針對每個請求進行快取的,因此無法透過在迴圈中呼叫輔助方法來產生語系選取器中的 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
宣告之前直接新增它。)
請查看各種簡化路由工作的 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 從 Session 或 Cookies 儲存語系
您可能會想將選定的語系儲存在 session 或 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>
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
放在同一個目錄中時,當語言環境設定為 :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 批量和命名空間查找
要一次查找多個翻譯,可以傳遞一個鍵的陣列
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 提供了一個複數化後端,可以用來啟用語言環境特定的規則。將其包含到 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
,而 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 為您提供了幾個命名空間,您可以在其中放置訊息翻譯,以便為某些模型、屬性和/或驗證提供不同的訊息和翻譯。它還會透明地考慮單表繼承。
這為您提供了非常強大的方法,可以根據應用程式的需求靈活地調整訊息。
考慮一個具有 name 屬性驗證的 User 模型,如下所示
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 | 屬性 |
接受 | - | :已接受 | - |
存在 | - | :空白 | - |
不存在 | - | :存在 | - |
長度 | :在範圍內, :在之中 | :太短 | 計數 |
長度 | :在範圍內, :在之中 | :太長 | 計數 |
長度 | :是 | :錯誤的長度 | 計數 |
長度 | :最小值 | :太短 | 計數 |
長度 | :最大值 | :太長 | 計數 |
唯一性 | - | :已被使用 | - |
格式 | - | :無效 | - |
包含 | - | :包含 | - |
排除 | - | :排除 | - |
關聯 | - | :無效 | - |
非可選關聯 | - | :必要 | - |
數值性 | - | :非數字 | - |
數值性 | :大於 | :大於 | 計數 |
數值性 | :大於或等於 | :大於或等於 | 計數 |
數值性 | :等於 | :等於 | 計數 |
數值性 | :小於 | :小於 | 計數 |
數值性 | :小於或等於 | :小於或等於 | 計數 |
數值性 | :不等於 | :不等於 | 計數 |
數值性 | :僅限整數 | :非整數 | - |
數值性 | :在之中 | :在之中 | 計數 |
數值性 | :奇數 | :奇數 | - |
數值性 | :偶數 | :偶數 | - |
比較 | :大於 | :大於 | 計數 |
比較 | :大於或等於 | :大於或等於 | 計數 |
比較 | :等於 | :等於 | 計數 |
比較 | :小於 | :小於 | 計數 |
比較 | :小於或等於 | :小於或等於 | 計數 |
比較 | :不等於 | :不等於 | 計數 |
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!"
要將參數傳遞給插值,請在 mailer 上使用 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 查詢順序選項(除非您明確傳遞該選項)。所有日期選擇輔助方法都會使用 datetime.prompts 範圍內的翻譯來翻譯提示(如果適用)。number_to_currency
、number_with_precision
、number_to_percentage
、number_with_delimiter
和number_to_human_size
輔助方法使用位於 number 範圍中的數字格式設定。
4.7.2 Active Model 方法
如果 activerecord.models 範圍中可用,則
model_name.human
和human_attribute_name
會使用模型名稱和屬性名稱的翻譯。它們也支援繼承的類別名稱(例如用於 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 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 gem 可以透過將後端實例傳遞給 I18n.backend=
setter,非常輕鬆地將 Simple 後端實作替換為更符合您需求的後端實作。
例如,您可以將 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 |
已傳遞 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 相關問題實作一個適合所有情況的解決方案。因此,允許我們輕鬆交換整個實作的解決方案是合適的。這也使得嘗試自訂功能和擴充功能變得更加容易。