場景:對于一個已經(jīng)做好靜態(tài) I18n 的 Rails 項目,需要對動態(tài)數(shù)據(jù)內(nèi)容也適配國際化儿子。
首先澎粟,動態(tài)內(nèi)容的數(shù)據(jù)肯定是存在數(shù)據(jù)庫中的奇昙,并且字段名也采用統(tǒng)一的 fieldname_#{I18n.locale}
期丰,方便統(tǒng)一管理群叶。
問題的核心是如何在盡量不修改現(xiàn)有代碼的情況下動態(tài)讀取模型某些字段的當前 locale 的值。
比如:原來的 user.name #=> Marry
咐汞,現(xiàn)在需要根據(jù)當前 locale (比如 cn)變成 user.name # => 翠花
。
首先接口肯定不能改儒鹿,view 層中有很多很多的 user.name
的調(diào)用化撕,就算用批量修改的方式改為類似 user.name_#{I18n.locale}
的寫法,
也很不優(yōu)雅约炎,何況不只是 name
屬性需要國際化植阴,以后每增加一個字段或模型的國際化都將成為很大的負擔。
說到如何在原有類的基礎(chǔ)上增加功能圾浅,那自然會想到使用裝飾器模式了掠手。
關(guān)于裝飾器模式的實現(xiàn),一種方式是使用 delegate :
require 'delegate'
class User < ApplicationRecord
# attribute name
end
class InternationalUserDecorator < SimpleDelegator
def name
__getobj__.send("name_#{I18n.locale}")
end
end
user = InternationalUserDecorator.new(User.find(12345))
I18n.locale = :en
user.name # => 'Marry'
I18n.locale = :cn
user.name # => '翠花'
但是這顯然是不行的狸捕,因為這需要修改每一個 User 實例的生成喷鸽,使用 Decorator 去顯式地包裝它。
類似的灸拍,還有一種通過繼承 module 的方式做祝,同樣需要顯示的修改每一個模型的實例,這樣的改動對原代碼改變很大鸡岗,也不能使用混槐。
module EnglishUser
def name
"Marry"
end
end
module ChineseUser
def name
"翠花"
end
end
user = User.find(123)
user.extend(EnglishUser) #=> name "Marry"
user.extend(ChineseUser) #=> name "翠花"
可見,需要在獲取模型實例時進行修改的思路是行不通轩性,如果項目一開始就使用倉儲模式的話声登,修改起來會容易很多,不過這超出了本文的范圍揣苏。
想要盡可能小的修改原代碼悯嗓,那只能使用元編程了,我們需要一個 Module卸察,來動態(tài)生成 locale 對應(yīng)的 field 供模型調(diào)用绅作。
當 locale
是 cn
的時候, user.name
=> user.name_cn
當 locale
是 en
的時候蛾派, user.name
=> user.name_en
當然俄认,不是模型所有的 field 都需要做國際化个少,必須可以指定需要國際化的字段
client 端的代碼應(yīng)該是這樣的:
class User < ApplicationRecord
# attributes :name, :position, :age
include I18nDecorator.new(:name, :position)
end
這里的難點在于,需要傳參數(shù)給這個 Module眯杏,可 include 的時候是不能傳參的夜焦。
在這里,我們把 I18nDecorator
定義為 Module 的一個子類岂贩,這樣就可以通過 new
的時候的 initialize
方法中茫经,
對父類進行元編程,動態(tài)定義 Module 的方法萎津,這樣就能在模型中進行調(diào)用了卸伞。
class I18nDecorator < Module
def initialize(*attrs)
super() do
attrs.each do |attr|
define_method attr do
send("#{attr}_#{I18n.locale}")
end
end
end
end
end
現(xiàn)在,當任何 User
類的實例調(diào)用 name
或者 position
的方法時锉屈,就會被 I18nDecorator
動態(tài)轉(zhuǎn)發(fā)給 name_cn
或者 position_en
的屬性上了荤傲。
就這樣,一共十幾行代碼颈渊,我們完成了一個 Rails 項目簡單的動態(tài) I18n 的功能遂黍,以后需要增加模型或?qū)傩缘臅r候,都只需要 include 這一行代碼就可以了俊嗽。