star 最多的 Go 語言本地化庫|GitHub 2.8K

白泽talk發表於2024-06-12

🌟 如果你是一位 Go 使用者,可以在我開源的學習倉庫中,找到針對各種往期歸檔文章,及學習資料。

📺 B站:白澤talk,公眾號【白澤talk】,回覆"電子書",即可獲得包含《100個Go經典錯誤場景》在內的純淨 Golang 電子書大全。

一、什麼是本地化

今天講講 i18n,無論是 ToB 還是 ToC 的業務,常常存在多語言的需求,由於使用者有時來自不同國家,因此需要對頁面展示內容,包括響應結果做多語言的適配。

hello world! -> 你好世界! err: "user not find" -> err: "使用者不存在"

⚠️ 如果發生了翻譯錯誤,可能會讓人十分困擾,參考鳴潮最近的一個類似的事故:

鳴潮日文客戶端將本次up角色忌炎的專武效果翻譯錯誤,將R技能翻譯成了E技能。

image-20240611215931184

二、前後端的不同實現

在前端實現國際化

  1. 準備多語言資原始檔:首先,需要準備多語言的資原始檔,包括不同語言版本的字串。
  2. 整合國際化外掛:使用前端框架提供的國際化外掛,如 React-intl、Vue-i18n 等,或者手動實現國際化邏輯。
  3. 根據使用者選擇的語言載入資源:在應用載入時,根據使用者選擇的語言載入對應的資原始檔,將介面展示的文字內容替換為對應語言的字串。

在後端實現國際化

  1. 準備多語言內容:將不同語言版本的文字或內容儲存在後端,可以是資料庫中、檔案中或其他形式。
  2. 處理國際化邏輯:在後端程式碼中編寫邏輯,根據使用者的語言選擇載入相應的內容。這可以透過模板引擎、多語言資原始檔或者介面返回不同語言的資料來實現。
  3. 提供介面或服務:如果後端需要提供國際化服務,可以設計介面或服務來根據使用者需求返回對應語言的內容。

三、Go 實現訊息本地化

🔥 最熱門的專案:https://github.com/nicksnyder/go-i18n

image-20240611220901762

本地化訊息的定義與翻譯流程

🌟 這部分具體參看 go-i18n 的 readme 更加!

  1. 命令列工具下載(用於從 Go 程式碼中,提取需要本地化的 Message)
go install -v github.com/nicksnyder/go-i18n/v2/goi18n@latest
  1. 建立兩個檔案存放翻譯結果
active.en.toml
active.zh.toml
  1. 在 Go 程式碼中,顯示宣告一個 Message 結構
localizer.Localize(&i18n.LocalizeConfig{
    DefaultMessage: &i18n.Message{
        ID: "PersonCats",
        One: "{{.Name}} has {{.Count}} cat.",
        Other: "{{.Name}} has {{.Count}} cats.",
    },
    TemplateData: map[string]interface{}{
        "Name": "Nick",
        "Count": 2,
    },
    PluralCount: 2,
}) // Nick has 2 cats.
  1. 執行 CMD 命令,將 Message 資訊提取到 active.en.toml 檔案中
goi18n extract

# active.en.toml
[PersonCats]
description = "The number of cats a person has"
one = "{{.Name}} has {{.Count}} cat."
other = "{{.Name}} has {{.Count}} cats."
  1. 執行 CMD 命令,將待翻譯成中文的 Message 提取到 translate.zh.toml 檔案中(這個檔案是工具建立的)
goi18n merge active.*.toml

# translate.zh.toml
[HelloPerson]
hash = "sha1-5b49bfdad81fedaeefb224b0ffc2acc58b09cff5"
other = "Hello {{.Name}}"
  1. 將 translate.zh.toml 翻譯成中文
# translate.zh.toml
[HelloPerson]
hash = "sha1-5b49bfdad81fedaeefb224b0ffc2acc58b09cff5"
other = "你好 {{.Name}}"
  1. 執行 CMD 命令將 translate.zh.toml 內的翻譯好的內容,自動增量合併進入 active.zh.toml 檔案中
goi18n merge active.*.toml translate.*.toml

四、基於 go-i18n 進一步封裝實現一個 HTTP 服務

🌟 見 demo:https://github.com/BaiZe1998/go-learning/tree/main/kit/i18n

效果:從 HTTP 頭部中獲取 lang,得到“zh”,響應中文的錯誤訊息。

image-20240611223711746

一個簡單的 HTTP 服務

建立一個具備本地化能力的 error,從請求頭提取語言,然後選擇對應語言的error資訊響應。

func helloHandler(w http.ResponseWriter, r *http.Request) {
	err := NewUserNotFoundErr(123)
	//err, _ := someFunc()
	fmt.Fprintf(w, FormatErr(err))
}

func main() {
	http.HandleFunc("/", helloHandler)

	fmt.Println("Starting server on port 8080...")
	if err := http.ListenAndServe(":8080", nil); err != nil {
		log.Fatal(err)
	}
}

服務啟動前初始化

預設語言選擇中文,選擇將 active.zh.toml 在服務啟動前載入進入記憶體。

var (
	bundle = i18n.NewBundle(language.English)
)

func init() {
	bundle.RegisterUnmarshalFunc("toml", toml.Unmarshal)
	bundle.LoadMessageFile("active.zh.toml")
}

高效封裝

以下的封裝確保每次新增一個本地化的 error,只需要組合 BaseError,即可繼承通用的本地化能力。

// 本地化方法宣告
type LocalizedError interface {
	error
	LocalizedID() string
	TemplateData() map[string]interface{}
}

// 基礎錯誤類,實現對應本地化方法
type BaseError struct {
	ID             string
	DefaultMessage string
	TempData       map[string]interface{}
}

func (b BaseError) Error() string {
	return b.DefaultMessage
}

func (b BaseError) LocalizedID() string {
	return b.ID
}

func (b BaseError) TemplateData() map[string]interface{} {
	return b.TempData
}

// 新增一個自定義錯誤
type UserNotFoundErr struct {
	BaseError
}

func NewUserNotFoundErr(userID int) LocalizedError {
	msg := i18n.Message{
		ID:    "user_not_found",
		Other: "User not found {{.UserID}}",
	}
	e := UserNotFoundErr{}
	e.ID = msg.ID
	e.DefaultMessage = msg.Other
	e.TempData = map[string]interface{}{
		"UserID": userID,
	}
	return e
}

提取本地化的 err 訊息

由於所有具備本地化能力的 err 都實現了 LocalizedError 介面,因此可以定義如下方法統一在 Handler 層提取本地化之後的錯誤內容。

// 這裡就不從 HTTP 請求頭獲取了,假設提取到了 zh
func GetLang(_ context.Context) string {
	return "zh"
}

func FormatErr(err error) string {
	lang := GetLang(context.Background())
	loc := i18n.NewLocalizer(bundle, lang)
	var i18nErr LocalizedError
	if errors.As(err, &i18nErr) {
		msg, _ := loc.Localize(&i18n.LocalizeConfig{
			DefaultMessage: &i18n.Message{
				ID:    i18nErr.LocalizedID(),
				Other: i18nErr.Error(),
			},
			//MessageID: i18nErr.LocalizedID(),
			TemplateData: i18nErr.TemplateData(),
		})
		return msg
	}
	return err.Error()
}

五、學習資料

參考文獻:

  • Xuanwo's Blog

  • 掘金:Go語言國際化 i18n

  • Go Web 程式設計

  • https://www.ituring.com.cn/article/508191?published=true

開源倉庫:

  • https://github.com/nicksnyder/go-i18n
  • https://github.com/gin-contrib/i18n
  • https://github.com/hertz-contrib/i18n
  • https://github.com/gofiber/contrib/tree/main/fiberi18n
  • https://github.com/beego/i18n

相關文章