這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
什麼是 WeTalk?
WeTalk,作為一款當下比較流行的輕論壇類型程式,是目前為止使用 beego 開發的最重量級開源產品。該產品是由 beego Team Dev成員 slene 建立並維護的,因此可以說 WeTalk 涵蓋了當下 beego 所有的進階用法,是一個學習使用 beego 的最佳執行個體。
什麼是即時本地化?
本地化,簡單地講就是程式提供一個多語言的使用者介面。那麼什麼是即時本地化?顧名思義,就是可以隨時切換使用者介面所使用的語言,像許多國際網站就提供多語言的選項並即時轉換,而不是像一些案頭軟體,你必須為多個語言分別下載不同的版本。
第三方包
在切入正題之前,先給大家介紹一下本文所涉及到的本地化包:beego/i18n。該包同樣是由 beego Team Dev建立並維護的,代碼簡潔但功能完整,支援包括 代碼級 和 模板級 這兩種本地化方式。另外,由於底層採用 goconfig 來完成對 INI 格式設定檔的操作,因此包本身的代碼非常簡潔。
本地化檔案
beego/i18n 採用 INI 設定檔來作為本地化檔案的格式,每種語言一般都有一個相對應的檔案。例如,WeTalk 就為了支援英文與簡體中文而擁有 兩個本地化檔案。作為一個命名習慣,建議以 locale_.ini
來作為本地化檔案的檔案名稱。這樣做的一個明顯的好處就是,可以使用一個迴圈來完成對所有本地化檔案的載入:
setting/conf.go
func settingLocales() { // load locales with locale_LANG.ini files langs := "en-US|zh-CN" for _, lang := range strings.Split(langs, "|") { lang = strings.TrimSpace(lang) files := []string{"conf/" + "locale_" + lang + ".ini"} if fh, err := os.Open(files[0]); err == nil { fh.Close() } else { files = nil } if err := i18n.SetMessage(lang, "conf/global/"+"locale_"+lang+".ini", files...); err != nil { beego.Error("Fail to set message file: " + err.Error()) os.Exit(2) } } Langs = i18n.ListLangs()}
代碼級本地化
要想在控制器中使用本地化,就需要先初始化每個請求的語言。為此,WeTalk 為每個控制器都添加了一個嵌入結構 i18n.Locale
:
base/base.go
// baseRouter implemented global settings for all other routers.type BaseRouter struct { beego.Controller i18n.Locale User models.User IsLogin bool}
如此一來,就可以在接受到使用者請求的第一時間擷取使用者所偏好的語言並進行設定,以便在邏輯中使用本地化的功能。
下面的代碼示範了如何分別根據 URL 參數、Cookies、瀏覽器偏好語言和預設語言對每個請求進行語言選項的設定:
base/base.go
// setLang sets site language version.func (this *BaseRouter) setLang() bool { isNeedRedir := false hasCookie := false // get all lang names from i18n langs := setting.Langs // 1. Check URL arguments. lang := this.GetString("lang") // 2. Get language information from cookies. if len(lang) == 0 { lang = this.Ctx.GetCookie("lang") hasCookie = true } else { isNeedRedir = true } // Check again in case someone modify by purpose. if !i18n.IsExist(lang) { lang = "" isNeedRedir = false hasCookie = false } // 3. check if isLogin then use user setting if len(lang) == 0 && this.IsLogin { lang = i18n.GetLangByIndex(this.User.Lang) } // 4. Get language information from 'Accept-Language'. if len(lang) == 0 { al := this.Ctx.Input.Header("Accept-Language") if len(al) > 4 { al = al[:5] // Only compare first 5 letters. if i18n.IsExist(al) { lang = al } } } // 4. DefaucurLang language is English. if len(lang) == 0 { lang = "en-US" isNeedRedir = false } // Save language information in cookies. if !hasCookie { this.setLangCookie(lang) } // Set language properties. this.Data["Lang"] = lang this.Data["Langs"] = langs this.Lang = lang return isNeedRedir}
其中,isNeedRedir
變數用於表示使用者是否是通過 URL 指定來決定語言選項的,為了保持 URL 整潔,WeTalk 在遇到這種情況時自動將語言選項設定到 Cookies 中然後重新導向。
代碼 this.Data["Lang"] = curLang.Lang
是將使用者語言選項設定到名為 Lang
的模板變數中,使得能夠在模板中處理語言問題。
以下兩行:
this.Data["CurLang"] = curLang.Namethis.Data["RestLangs"] = restLangs
主要用於實現使用者自由切換語言的按鈕顯示。
控制器本地化處理
由於我們已經在控制器中嵌入了一個 i18n.Locale
結構,因此在需要進行本地化處理的時候,只要調用 i18n.Locale
的 Tr 方法即可:
func (l Locale) Tr(format string, args ...interface{}) string
該方法的第一個參數接受一個格式化字串,緊接著便是格式化時需要用到的參數,也就是說,你可以在本地化檔案使用像下面這樣的寫法:
seconds_ago = %d seconds ago
下面的代碼示範了如何在控制器方法中進行本地化處理:
base/base.go
if valid, ok := this.Data[errName].(*validation.Validation); ok { valid.SetError(fieldName, this.Tr(errMsg))}
模板級本地化
相對於代碼級,模板級本地化顯得更為常用,所以 beego/i18n 還專門提供了對模板中進行本地化處理的支援,即 Tr 函數:
func Tr(lang, format string, args ...interface{}) string
與之前的方法不同,該函數還要求傳入一個參數 lang
來指明目標語言。
當然,要想在模板中使用自訂函數,就必須先進行註冊:
modules/utils/template.go
beego.AddFuncMap("i18n", i18nHTML)
這裡的 i18nHTML
其實是對 i18n.Tr
的一個封裝:
modules/utils/template.go
// get HTML i18n stringfunc i18nHTML(lang, format string, args ...interface{}) template.HTML { return template.HTML(i18n.Tr(lang, format, args...))}
這樣做主要是為了翻譯結果不被 Go 語言的模板引擎作轉義處理。當然,一般情況下你直接將 i18n.Tr
註冊為模板函數也沒有問題。
還記得之前在控制器語言選項設定方法裡的那個 Lang
變數嗎?是時候派上用場了:
views/article/show.html
{{i18n .Lang "post.post_author"}}
在模板中處理本地化,就是這麼簡單。
不同頁面的同個關鍵詞
你應該已經注意到,上個小節中的 "post.post_author"
寫法有點奇怪,為什麼中間會有一個 .
呢?這其實是 goconfig 提供的一個資料分割函數,也就是說,針對不同的分區,你可以為想用的關鍵字分別採用不同的翻譯欄位。
下面的寫法就是採用了 INI 設定檔的分區特性:
conf/global/locale_en-US.ini
[topic]favorite_remove = Remove Favoritefavorite_add = Add Favoritefavorite_already = Favorited[post]post_new = New Postpost_edit = Edit Postset_best = Set Bestremove_best = Remove Best
歧義處理
由於 .
是作為分區的標誌,所以當您的鍵名出現該符號的時候,會出現歧義導致語言處理失敗。這時,您只需要在整個鍵名前加上一個額外的 . 即可避免歧義。
假設你的鍵名為 about.
,為了避免歧義,我們需要使用:
{{i18n .Lang ".about."}}
來擷取正確的本地化結果。
當前存在的不足
- 也許你已經發現,每次在模板中調用
i18n
函數,都需要傳入 .Lang
參數。其實,這是因為 beego 無法動態指定模板函數的實現,所以對應的翻譯函數都必須在 beego.Run()
之前設定。而對於那些允許在控制器中指定模板函數的架構(xweb)來說,則可以直接通過設定 this.Tr
作為模板函數來省略 .Lang
欄位。
其它說明
- 如果未找到相應鍵的對應值,則會輸出鍵的原字串。例如:當鍵為
hi
並未在本地化檔案中找到以該字串命名的鍵,則會將 hi
直接作為翻譯結果返回給調用者。
- i18n 包提供一個 命令列工具 來協助簡化開發步驟。
總結
對於一個網站而言,能夠支援即時的語言切換是一個對使用者非常友好的行為。此外,不像命令列或其它類型的程式,網站的多語言支援很大程度上需要 i18n 包能夠提供對模板級本地化的支援,而 beego/i18n 包就很出色地完成了這項任務。
其它案例