Skip to main content

คู่มือการทำให้ Go รองรับหลายภาษาอย่างครบถ้วน

ตั้งแต่ไฟล์ข้อความจนถึงการค้นหาภาษาที่ปลอดภัยสำหรับ goroutine ตั้งค่า i18n ในแอป Go ด้วย 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

สร้างไฟล์ข้อความ

สร้างไฟล์ JSON หนึ่งไฟล์ต่อภาษาในไดเรกทอรี locales แต่ละข้อความมี 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."
  }
}
ใช้ ID ข้อความที่สื่อความหมายอย่าง 'ItemCount' หรือ 'WelcomeBack' แทนพาธที่คั่นด้วยจุด go-i18n ใช้ ID แบบแบน ไม่ใช่คีย์ซ้อน และควรใช้ PascalCase ให้ตรงกับข้อกำหนด Go
3

โหลด Bundle

Bundle เป็นรีจิสทรีส่วนกลางของ go-i18n สร้างครั้งเดียวตอนเริ่มต้น ลงทะเบียนรูปแบบไฟล์ และโหลดไฟล์ข้อความทั้งหมด 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) นามสกุลไฟล์ตรงกับฟังก์ชัน unmarshal ที่ลงทะเบียน 3) MessageID ใน LocalizeConfig ตรงกับคีย์ในไฟล์ JSON ทุกตัวอักษร
4

ใช้ Localizer

สร้าง Localizer สำหรับแต่ละคำขอด้วยภาษาที่ผู้ใช้ต้องการ Localizer ค้นหาข้อความจาก Bundle จัดการการเรนเดอร์เทมเพลตด้วยเอนจิน text/template ของ Go และเลือกรูปพหูพจน์ที่ถูกต้องตาม 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

การตรวจหาภาษา

ในแอปเว็บ ให้ตรวจภาษาที่ผู้ใช้ต้องการจากหลายแหล่ง ได้แก่ พารามิเตอร์คำขอ คุกกี้ ส่วนหัว 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' matcher จะคืน 'pt' อย่างถูกต้อง หากไม่มี matcher คุณต้องเขียนตรรกะค่าสำรองเองสำหรับทุกภูมิภาค
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 จับคีย์ที่หายไปและตัวยึดตำแหน่งเสียหายก่อนส่งขึ้นใช้งาน แล้วทดสอบ UI ด้วยคำแปลจำลองผ่าน i18n-pseudo ก่อนคำแปลจริงจะมาถึง

ข้อผิดพลาดที่พบบ่อย

PluralCount และ TemplateData ไม่ตรงกัน

PluralCount เลือกรูปพหูพจน์แต่ไม่ฉีดค่าลงในเทมเพลต คุณต้องส่งจำนวนใน TemplateData ด้วยเพื่อให้แสดงในข้อความที่เรนเดอร์ หากไม่มี TemplateData '{'{.Count}'}' จะเรนเดอร์เป็น '<no value>'

ไม่มี RegisterUnmarshalFunc

LoadMessageFile คืนค่าโดยไม่มีข้อความอย่างเงียบๆ หากลืมเรียก bundle.RegisterUnmarshalFunc() สำหรับรูปแบบไฟล์ ให้ลงทะเบียน json.Unmarshal หรือ toml/yaml ก่อนโหลดไฟล์เสมอ

ค่าสำรองรายคีย์ไม่ทำงาน

NewLocalizer ของ go-i18n รับหลายภาษา แต่เมื่อภาษาตรงกับคีย์เดียว คีย์ที่หายจะคืนข้อความว่างแทนการถอยไปใช้ภาษาถัดไป ใช้ go-locale-chain แก้ปัญหาด้วยลำดับรายคีย์ที่ถูกต้อง

ไวยากรณ์เทมเพลต : {'{.Var}'} ไม่ใช่ {'{Var}'}

go-i18n ใช้ไวยากรณ์ text/template ของ Go ตัวแปรต้องมีคำนำหน้าจุด : {'{.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