Hướng dẫn toàn diện về quốc tế hóa Go
Từ tệp thông báo đến phân giải ngôn ngữ an toàn với goroutine: thiết lập i18n trong ứng dụng Go bằng go-i18n rồi tự động hóa bản dịch bằng AI.
Cài đặt go-i18n
go-i18n là thư viện quốc tế hóa phổ biến nhất cho Go. Thư viện dùng quy tắc số nhiều CLDR và mẫu Go để nội suy biến, đồng thời hỗ trợ tệp thông báo JSON, TOML và YAML. Bạn cũng cần golang.org/x/text để so khớp thẻ ngôn ngữ.
go get -u github.com/nicksnyder/go-i18n/v2/i18n
go get -u golang.org/x/text/languageTạo tệp thông báo
Tạo một tệp JSON cho mỗi ngôn ngữ trong thư mục locales. Mỗi thông báo có một ID và một hoặc nhiều dạng số nhiều. Go-i18n dùng cú pháp mẫu Go (hai dấu ngoặc nhọn với dấu chấm ở đầu) để nội suy biến.
{
"HelloWorld": { "other": "Hello, World!" },
"Greeting": { "other": "Hello, {{.Name}}!" },
"ItemCount": {
"one": "{{.Count}} item",
"other": "{{.Count}} items"
},
"WelcomeBack": {
"other": "Welcome back, {{.Name}}. You have {{.Count}} messages."
}
}Tải Bundle
Bundle là sổ đăng ký trung tâm của go-i18n. Hãy tạo một Bundle khi khởi động, đăng ký định dạng tệp và tải mọi tệp thông báo. Bundle an toàn với goroutine — chỉ cần tạo một lần rồi dùng chung trong toàn ứng dụng.
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) // "こんにちは、世界!"
}Dùng Localizer
Tạo một Localizer cho mỗi yêu cầu theo ngôn ngữ ưu tiên của người dùng. Localizer phân giải thông báo từ Bundle, kết xuất mẫu bằng công cụ text/template của Go và chọn dạng số nhiều chính xác dựa trên PluralCount.
// 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,
},
})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))
}Xử lý quy tắc số nhiều
go-i18n triển khai quy tắc số nhiều CLDR cho mọi ngôn ngữ. Tiếng Anh có 2 dạng (one, other). Tiếng Ả Rập có 6 dạng (zero, one, two, few, many, other). Tiếng Nhật có 1 dạng (other). Hãy định nghĩa mọi dạng cần thiết trong tệp thông báo — go-i18n chọn dạng chính xác dựa trên PluralCount.
// 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}}個のアイテム"
}
}// 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: "عنصر واحد"Phát hiện ngôn ngữ
Trong ứng dụng web, hãy phát hiện ngôn ngữ ưu tiên của người dùng từ nhiều nguồn: tham số truy vấn, cookie, tiêu đề Accept-Language hoặc phân đoạn đường dẫn URL. Dùng golang.org/x/text/language.Matcher để thương lượng ngôn ngữ theo chuẩn BCP 47.
// 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
})Khắc phục phương án dự phòng theo từng khóa bằng go-locale-chain
go-i18n có một hạn chế đã biết: sau khi một ngôn ngữ khớp (đã tải bất kỳ bản dịch nào), khóa bị thiếu sẽ không chuyển sang ngôn ngữ tiếp theo trong chuỗi. Người dùng pt-BR với tệp pt-BR mới dịch một phần sẽ nhận chuỗi trống thay vì dùng pt-PT hoặc pt làm phương án dự phòng. go-locale-chain khắc phục vấn đề này bằng cách phân giải dự phòng theo từng khóa trên 75 chuỗi ngôn ngữ tích hợp sẵn.
go get github.com/i18n-agent/go-locale-chainpackage 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
}// 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 localeTự động hóa bản dịch
Sau khi hoàn tất thiết lập i18n, hãy dùng AI để dịch các tệp ngôn ngữ. Tạo bản dịch cho mọi ngôn ngữ đích từ tệp nguồn tiếng Anh ngay trong IDE hoặc quy trình CI/CD của bạn.
# 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,esTự động hóa chất lượng bản dịch
Lỗi thường gặp
PluralCount và TemplateData không khớp
Thiếu RegisterUnmarshalFunc
Phương án dự phòng theo từng khóa không hoạt động
Cú pháp mẫu: {'{.Var}'} chứ không phải {'{Var}'}
Cấu trúc tệp đề xuất
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.sumDùng thử i18n Agent ngay
Thả tệp bản dịch của bạn vào đây
JSON, YAML, PO, XML, CSV, Markdown, Properties
hoặc nhấp để duyệt
Ngôn ngữ đích
Phương án dự phòng ngôn ngữ với go-locale-chain
Khi thiếu khóa dịch trong một ngôn ngữ khu vực như pt-BR, go-i18n chuyển thẳng sang ngôn ngữ mặc định thay vì kiểm tra ngôn ngữ mẹ pt trước.
go get github.com/i18n-agent/go-locale-chainimport 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"},
},
})Xem Hướng dẫn về phương án dự phòng ngôn ngữ để biết danh sách đầy đủ các framework được hỗ trợ và 75 chuỗi tích hợp sẵn. Learn more →