公司專案基本上都會碰到 i18n,就算沒有要做多國語系,放 i18n 的好處還有之一就是改文案快。
i18n 這個看起來完全不難的東西,實際上碰到很多需求時會發現,啊好麻煩啊,一下要插 html 一下字太多,但是在這些場景其實可以有更優雅的解法。
HTML 處理
有些狀況是希望在翻譯內的字距加上 html, 例如粗線或連結等等
zh-TW:
privacy_tips: 請參考隱私權政策
所以會把 html 放在 yaml 中
zh-TW:
privacy_tips: 請參考<a href="xxx.com">隱私權政策</a>
然後在用 raw
去做處理。
<%= raw(t('privacy_tips')) %>
但其實可以有更優雅的作法,直接在尾綴加上 _html
就可以幫你處理了
zh-TW:
privacy_tips_html: 請參考<a href="xxx.com">隱私權政策</a>
好處:
- 在 view 裡面一看就知道這個是帶有 html 的 i18n
- 不需要到處 raw 來 raw 去
- 簡單,一致性高
長語句處理
翻譯最麻煩的情況就是一大堆文字,例如政策、說明、法律說明等等
假如有三句話分別要放三個 <p>
tag,一般可能會這樣做
zh-TW:
description_1: 第一行說明
description_2: 第二行說明
description_3: 第三段說明
然後在 view 裡面放上
<p> <%= t("description_1") %> </p>
<p> <%= t("description_2") %> </p>
<p> <%= t("description_3") %> </p>
但其實可以運用 YAML block literals 中的 |
zh-TW:
description: |
第一行說明
第二行說明
第三段說明
|
的功能是在每一行自動插上換行符 \n
所以我們可以用 rails console 打印出來
會發現
I18n.t("description")
# "第一行說明\n第二行說明\n第三段說明\n"
搭配 simple_format
更方便
<%= simple_format t("description") %>
會直接變成
<p>第一行文字</p>
<p>第二行文字</p>
<p>第三行文字</p>
或是用 each_line
處理
<ul>
<% t("description").each_line do |description| %>
<li><%= description %></li>
<% end %>
</ul>
也可以直接變成
<ul>
<li>第一行文字</li>
<li>第二行文字</li>
<li>第三行文字</li>
</ul>
Partial 使用語系檔案名稱
如果樣版裡面比較多靜態內容或是不同語系裡面有不同的說明,不想要一個一個翻,也可以這樣做。
app/views/pages/faq.zh-TW.html.erb
app/views/pages/faq.en.html.erb
這樣一來,在 locale
為 en
時會自動選擇 faq.en.html.erb
樣版,而 zh-TW
時會使用 faq.zh-TW.html.erb
這個樣版
簡寫
在檔案路徑為 app/views/products/index.html.erb 時
在前綴使用 .
做開頭
<%= t('.edit_label') %>
會自動找尋 products.index.edit_label
的 i18n key
key 是對照路徑(包含 namespace),所以說這樣做要注意只要 partail 路徑變更時,語系檔沒更改就會找不到翻譯。
Scope
<% i18n_scope = "products.index" %>
<%= t('edit_label', scope: i18n_scope) %>
這樣做的話可以解決上面提到的路徑變動疑慮,搬動 partial 時不會太難,變數跟著改一下就行了。
不過 scope 除了這個場景以外,我不太建議的原因有兩個
- 非到不可不在 view 裡面定義變數,請都由 controller 給,如果要計算或處理都丟 helper
- scope 寫法除了傳 String 還可以傳 Symbol, 可預期會有各種寫法出現
以下三種寫法是一樣的
# 第一種
<%= t('edit_label', scope: "product.index") %>
# 第二種
<%= t('edit_label', scope: [:product, :index]) %>
# 第三種
<%= t('product.index.edit_label') %>
建議都請寫第三種
原因:
- 全域搜尋好找
- 階層容易懂
語系檔放在 Database
如果不想寫在 yml, 也可以放在 Database 裡
在 config/initializers/locale.rb 下加入
require 'i18n/backend/active_record'
I18n.backend = I18n::Backend::ActiveRecord.new
好處大概就是可以有一個後台隨時換字,不用動 code 這樣 XD,需求一來直接空中換機翼的概念
實際作法可以參考 i18n-active_record repo
區分多檔案
不建議把所有翻譯檔全部塞在同一個檔案裡。
只用 en.yml
、zh-TW.yml
並不容易維護
更建議的作法是拆開
config/locales/admin.en.yml
config/locales/admin.zh-TW.yml
config/locales/controllers/products.en.yml
config/locales/controllers/products.zh-TW.yml
至於怎麼拆就開團隊的分類喜好了,只要能讓工程師好維護,翻譯好翻就是最適合團隊的拆法囉。
然而檔案名稱無論是什麼,只要 yml 裡面的第一層結構有對應 locale 名稱就會被載入 i18n 內。
Rails 預設只會抓 config/locales
這個階層下的檔案,再深入的資料夾是不抓的,所以要讓語系檔都載入請在 config/application.yml 加入這行
config.i18n.load_path += Dir[Rails.root.join("config", "locales", "**", "*.{rb,yml}")]
基本設定
自定義預設語系
修改 config/application.rb 的預設語系
config.i18n.default_locale = "zh-TW"
搭配 Model 使用
這樣一來噴 error 時就會直接抓 i18n,很方便顯示。
zh-TW:
activerecord:
attributes:
product:
title: "品項名稱"
description: "描述"
內嵌變數
如果要在詞彙內嵌變數的話,可以使用 %{variable_name}
的語法
zh-TW:
welcome: "你好 %{name}"
在 view 中使用
<%= t("welcome", name: "Nic") %>
# 你好 Nic