Skip to main content

Go 國際化完整指南

從訊息檔案到可確保 goroutine 安全的語系解析:使用 go-i18n 在 Go 應用程式中設定 i18n,再透過 AI 自動翻譯。

1

安裝 go-i18n

go-i18n 是最受歡迎的 Go 國際化庫。它使用 CLDR 複數規則和 Go 範本進行變數插值,並支援 JSON、TOML 和 YAML 訊息檔案。您還需要 golang.org/x/text 來匹配語言標籤。

go-i18n v2 需要 Go 1.16 或更高版本。golang.org/x/text 套件提供 BCP 47 語言標籤解析和匹配功能,go-i18n 在內部使用它來選擇複數規則。
Terminal
go get -u github.com/nicksnyder/go-i18n/v2/i18n
go get -u golang.org/x/text/language
2

建立訊息檔案

在 locales 目錄中為每種語言建立一個 JSON 檔案。每條訊息包含一個 ID 和一種或多種複數形式。Go-i18n 使用 Go 範本語法(帶點號前綴的雙花括號)進行變數插值。

locales/en.json
{
  "HelloWorld": { "other": "Hello, World!" },
  "Greeting": { "other": "Hello, {{.Name}}!" },
  "ItemCount": {
    "one": "{{.Count}} item",
    "other": "{{.Count}} items"
  },
  "WelcomeBack": {
    "other": "Welcome back, {{.Name}}. You have {{.Count}} messages."
  }
}
使用 'ItemCount' 或 'WelcomeBack' 等描述性訊息 ID,而不是以點分隔的路徑。go-i18n 使用扁平 ID,不使用嵌套鍵。ID 應採用 PascalCase,以符合 Go 約定。
3

載入 Bundle

Bundle 是 go-i18n 的中央註冊表。啟動時建立一個 Bundle,註冊檔案格式並載入所有訊息檔案。Bundle 可確保 goroutine 安全,只需建立一次並在整個應用程式中共享。

main.go
package main

import (
    "encoding/json"
    "fmt"
    "github.com/nicksnyder/go-i18n/v2/i18n"
    "golang.org/x/text/language"
)

func main() {
    // 1. Create a bundle with a default language
    bundle := i18n.NewBundle(language.English)
    // 2. Register the unmarshal function for your file format
    bundle.RegisterUnmarshalFunc("json", json.Unmarshal)
    // 3. Load message files
    bundle.MustLoadMessageFile("locales/en.json")
    bundle.MustLoadMessageFile("locales/ja.json")
    bundle.MustLoadMessageFile("locales/de.json")
    // 4. Create a localizer and localize a message
    localizer := i18n.NewLocalizer(bundle, "ja")
    msg := localizer.MustLocalize(&i18n.LocalizeConfig{
        MessageID: "HelloWorld",
    })
    fmt.Println(msg) // "こんにちは、世界!"
}
如果看到 'message not found',請檢查三項內容:1)訊息檔案是否已透過 LoadMessageFile 或 MustLoadMessageFile 載入;2)檔案副檔名是否與已註冊的反序列化函式匹配;3)LocalizeConfig 中的 MessageID 是否與 JSON 檔案中的鍵完全匹配(區分大小寫)。
4

使用 Localizer

使用使用者的首選語言為每個請求建立一個 Localizer。Localizer 從 Bundle 解析訊息,透過 Go 的 text/template 引擎處理範本呈現,並根據 PluralCount 選擇正確的複數形式。

Using the Localizer
// Simple message
msg := localizer.MustLocalize(&i18n.LocalizeConfig{
    MessageID: "HelloWorld",
})

// Message with template data
msg := localizer.MustLocalize(&i18n.LocalizeConfig{
    MessageID: "Greeting",
    TemplateData: map[string]interface{}{
        "Name": "Alice",
    },
})
// "Hello, Alice!" (en) or "こんにちは、Aliceさん!" (ja)

// Plural + template data
msg := localizer.MustLocalize(&i18n.LocalizeConfig{
    MessageID: "ItemCount",
    PluralCount: 5,
    TemplateData: map[string]interface{}{
        "Count": 5,
    },
})
// "5 items" (en) or "5個のアイテム" (ja)

// Combined: plurals + multiple variables
msg := localizer.MustLocalize(&i18n.LocalizeConfig{
    MessageID: "WelcomeBack",
    TemplateData: map[string]interface{}{
        "Name":  "Alice",
        "Count": 3,
    },
})
HTTP handler with locale detection
func handler(w http.ResponseWriter, r *http.Request) {
    // Accept-Language: ja,en;q=0.9,de;q=0.8
    accept := r.Header.Get("Accept-Language")

    // NewLocalizer accepts multiple languages — first match wins
    localizer := i18n.NewLocalizer(bundle, accept)

    msg := localizer.MustLocalize(&i18n.LocalizeConfig{
        MessageID: "HelloWorld",
    })

    w.Header().Set("Content-Type", "text/plain; charset=utf-8")
    w.Write([]byte(msg))
}
NewLocalizer 接收多個語言字串,並按順序嘗試。直接傳入 Accept-Language 標頭:i18n.NewLocalizer(bundle, r.Header.Get("Accept-Language"))。go-i18n 會自動解析標頭,並與可用翻譯進行匹配。
5

處理複數規則

go-i18n 為所有語言實現 CLDR 複數規則。英語有 2 種形式(one、other),阿拉伯語有 6 種(zero、one、two、few、many、other),日語有 1 種(other)。請在訊息檔案中定義所有必需形式,go-i18n 會根據 PluralCount 選擇正確形式。

Plural forms by language
// English: 2 forms (one, other)
{
  "ItemCount": {
    "one": "{{.Count}} item",
    "other": "{{.Count}} items"
  }
}

// Arabic: 6 forms (zero, one, two, few, many, other)
{
  "ItemCount": {
    "zero": "لا عناصر",
    "one": "عنصر واحد",
    "two": "عنصران",
    "few": "{{.Count}} عناصر",
    "many": "{{.Count}} عنصرًا",
    "other": "{{.Count}} عنصر"
  }
}

// Japanese: 1 form (other)
{
  "ItemCount": {
    "other": "{{.Count}}個のアイテム"
  }
}
Using plural rules
// go-i18n selects the correct plural form based on PluralCount
localizer := i18n.NewLocalizer(bundle, "ar")

msg := localizer.MustLocalize(&i18n.LocalizeConfig{
    MessageID:   "ItemCount",
    PluralCount: 3,
    TemplateData: map[string]interface{}{
        "Count": 3,
    },
})
// Arabic "few" form: "3 عناصر"

msg = localizer.MustLocalize(&i18n.LocalizeConfig{
    MessageID:   "ItemCount",
    PluralCount: 1,
    TemplateData: map[string]interface{}{
        "Count": 1,
    },
})
// Arabic "one" form: "عنصر واحد"
PluralCount 與 TemplateData 相互獨立。PluralCount 選擇複數形式,TemplateData 為範本呈現提供值。如果訊息文字中需要顯示數量,請同時傳入兩者:PluralCount: n 和 TemplateData: map[string]interface{'}'{"Count": n}。
6

語系偵測

在 Web 應用程式中,從多個來源偵測使用者的首選語言:查詢參數、Cookie、Accept-Language 標頭或 URL 路徑段。使用 golang.org/x/text/language.Matcher 進行符合 BCP 47 的語言協商。

Locale detection middleware
// detectLocale resolves the user's preferred language.
// Priority: query param > cookie > Accept-Language header > default
func detectLocale(r *http.Request, matcher language.Matcher) string {
    // 1. Explicit query parameter: ?lang=ja
    if lang := r.URL.Query().Get("lang"); lang != "" {
        tag, _, _ := matcher.Match(language.Make(lang))
        return tag.String()
    }
    // 2. Cookie from previous selection
    if cookie, err := r.Cookie("lang"); err == nil {
        tag, _, _ := matcher.Match(language.Make(cookie.Value))
        return tag.String()
    }
    // 3. Accept-Language header
    accept := r.Header.Get("Accept-Language")
    if accept != "" {
        tags, _, _ := language.ParseAcceptLanguage(accept)
        if len(tags) > 0 {
            tag, _, _ := matcher.Match(tags...)
            return tag.String()
        }
    }
    return "en" // 4. Default
}

// Usage:
matcher := language.NewMatcher([]language.Tag{
    language.English, language.Japanese,
    language.German, language.Spanish,
})

http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
    locale := detectLocale(r, matcher)
    localizer := i18n.NewLocalizer(bundle, locale)
    // ... use localizer
})
language.NewMatcher 傳回與支援語言最匹配的結果,而非原始使用者偏好。如果使用者請求 'pt-BR' 而您只支援 'pt',匹配器會正確傳回 'pt'。若沒有匹配器,您需要為每個地區變體手動實現回退邏輯。
7

使用 go-locale-chain 修復按鍵回退

go-i18n 存在一個已知限制:語系一旦匹配(已載入任何翻譯),缺失鍵就不會繼續回退到鏈中的下一個語系。如果 pt-BR 檔案只完成了部分翻譯,pt-BR 使用者會得到空字串,而不會回退到 pt-PT 或 pt。go-locale-chain 透過 75 條內建語系鏈進行按鍵回退解析,解決此問題。

Terminal
go get github.com/i18n-agent/go-locale-chain
go-locale-chain with go-i18n
package main

import (
    "encoding/json"
    "fmt"

    "github.com/nicksnyder/go-i18n/v2/i18n"
    localechain "github.com/i18n-agent/go-locale-chain"
    "golang.org/x/text/language"
)

func main() {
    // 1. Configure fallback chains (call once at startup)
    localechain.Configure()

    // 2. Set up go-i18n bundle as usual
    bundle := i18n.NewBundle(language.English)
    bundle.RegisterUnmarshalFunc("json", json.Unmarshal)
    bundle.MustLoadMessageFile("locales/en.json")
    bundle.MustLoadMessageFile("locales/pt.json")
    bundle.MustLoadMessageFile("locales/pt-PT.json")
    bundle.MustLoadMessageFile("locales/pt-BR.json")

    // 3. Resolve with per-key fallback
    result, _ := localechain.ResolveWithLoader("pt-BR", func(locale string) (map[string]string, error) {
        localizer := i18n.NewLocalizer(bundle, locale)
        messages := make(map[string]string)
        for _, id := range []string{"hello", "goodbye", "thanks"} {
            msg, err := localizer.Localize(&i18n.LocalizeConfig{MessageID: id})
            if err == nil {
                messages[id] = msg
            }
        }
        return messages, nil
    })

    fmt.Println(result["hello"])   // "Olá (BR)"   — from pt-BR
    fmt.Println(result["goodbye"]) // "Adeus (PT)" — fallback to pt-PT
    fmt.Println(result["thanks"])  // "Obrigado"   — fallback to pt
}
go-locale-chain 是一個零外部依賴的開源 Go 套件。它與 go-i18n 互為補充:使用 go-i18n 載入訊息、處理複數和呈現範本,使用 go-locale-chain 正確解析回退鏈。
Standalone usage (no go-i18n)
// Standalone: zero external dependencies, works with any format
localechain.Configure()

result, _ := localechain.ResolveWithLoader("es-MX", func(locale string) (map[string]string, error) {
    data, err := os.ReadFile(fmt.Sprintf("locales/%s.json", locale))
    if err != nil {
        return nil, err // Locale file doesn't exist — skip
    }
    var msgs map[string]string
    json.Unmarshal(data, &msgs)
    return msgs, nil
})
// es-MX -> es-419 -> es: each key resolved from most specific locale
使用 ConfigureWithOverrides() 自訂特定鏈,同時保留預設值。例如,可將 pt-BR 簡化為僅回退到 pt,或為預設設定中沒有的 sv-FI -> sv 等語系新增回退鏈。
8

自動翻譯

完成 i18n 設定後,使用 AI 翻譯語系檔案。直接從 IDE 或 CI/CD 管線,根據英語來源檔案產生所有目標語言的翻譯。

Terminal
# In your IDE, ask your AI assistant:
> Translate locales/en.json to German, Japanese, and Spanish

✓ locales/de.json created (1.2s)
✓ locales/ja.json created (1.5s)
✓ locales/es.json created (1.1s)

# Or use the CLI in CI/CD:
npx i18n-agent translate locales/en.json --lang de,ja,es
採用漸進式翻譯——向來源檔案新增新訊息 ID 時,只翻譯新增鍵,不要重新產生所有檔案。這樣可保留經人工審核的翻譯。

自動保證翻譯品質

使用 i18n-validate 在發佈前發現缺失鍵和損壞的預留位置。真實譯文完成前,可使用 i18n-pseudo 產生偽譯文來測試 UI。

常見問題

PluralCount 與 TemplateData 不匹配

PluralCount 選擇複數形式,但不會將值注入範本。您還必須在 TemplateData 中傳入數量,才能在呈現的訊息中顯示它。如果沒有 TemplateData,'{'{.Count}'}' 會呈現為 '<no value>'。

缺少 RegisterUnmarshalFunc

如果忘記針對檔案格式呼叫 bundle.RegisterUnmarshalFunc(),LoadMessageFile 會靜默傳回空訊息。載入檔案前,務必註冊 json.Unmarshal(或 toml/yaml)。

按鍵回退不起作用

go-i18n 的 NewLocalizer 接收多種語言,但一旦某個語系匹配一個鍵,缺失鍵就會傳回空字串而不會回退。使用 go-locale-chain,透過正確的按鍵逐級回退解決此問題。

範本語法:{'{.Var}'},而不是 {'{Var}'}

go-i18n 使用 Go 的 text/template 語法。變數必須帶點號前綴:{'{.Name}'},而不是 {'{Name}'}。點號指向 TemplateData 對應。缺少點號會導致範本執行錯誤。

推薦的檔案結構

Project Structure
my-go-app/
├── locales/
│   ├── en.json           # Source language (English)
│   ├── de.json           # German translations
│   ├── ja.json           # Japanese translations
│   ├── es.json           # Spanish translations
│   ├── pt.json           # Portuguese (base)
│   ├── pt-PT.json        # Portuguese (Portugal)
│   └── pt-BR.json        # Portuguese (Brazil)
├── i18n/
│   ├── bundle.go         # Bundle initialization
│   ├── detect.go         # Locale detection logic
│   └── middleware.go      # HTTP middleware for locale
├── main.go
├── go.mod
└── go.sum

立即試用 i18n Agent

將翻譯檔案拖放到此處

JSON, YAML, PO, XML, CSV, Markdown, Properties

或點擊選擇檔案

目標語言

無需註冊即時估價

使用 go-locale-chain 實現語系回退

當 pt-BR 等地區語系缺少翻譯鍵時,go-i18n 會直接跳到預設語言,而不會先檢查父級語系 pt。

Terminal
go get github.com/i18n-agent/go-locale-chain
Configuration
import localechain "github.com/i18n-agent/go-locale-chain"

chain := localechain.New(localechain.Config{
    Fallbacks: map[string][]string{
        "pt-BR":      {"pt", "en"},
        "zh-Hant-HK": {"zh-Hant", "zh", "en"},
    },
})

查看語言回退指南,瞭解受支援框架的完整列表和 75 條內建回退鏈。 Learn more →

Go i18n 常見問題