Skip to main content

Повний посібник з інтернаціоналізації Go

Від файлів повідомлень до визначення локалі, безпечного для конкурентного використання в goroutine: налаштуйте i18n у своєму застосунку на Go за допомогою go-i18n, а потім автоматизуйте переклад за допомогою ШІ.

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."
  }
}
Використовуйте зрозумілі ID повідомлень на зразок «ItemCount» або «WelcomeBack» замість шляхів, розділених крапками. go-i18n використовує плоскі ID, а не вкладені ключі. Записуйте 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) розширення файла відповідає зареєстрованій функції десеріалізації; 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 and TemplateData: map[string]interface{'}'{"Count": n}.
6

Визначити локаль

У вебзастосунках визначайте бажану мову користувача з кількох джерел: параметрів запиту, cookie, заголовка Accept-Language або сегментів шляху URL. Для узгодження мови відповідно до BCP 47 використовуйте golang.org/x/text/language.Matcher.

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 перекладіть файли локалей за допомогою ШІ. Створюйте переклади всіма цільовими мовами з англомовного вихідного файла безпосередньо у своїй IDE або у своєму CI/CD pipeline.

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.

Поширені помилки

Неузгоджені PluralCount і TemplateData

PluralCount вибирає форму множини, але не підставляє значення в шаблон. Щоб воно з’явилося у відтвореному повідомленні, Ви також маєте передати лічильник у TemplateData. Без TemplateData вираз '{'{.Count}'}' відтворюється як '<no value>'.

Не зареєстровано RegisterUnmarshalFunc

Якщо Ви забули викликати bundle.RegisterUnmarshalFunc() для формату файла, LoadMessageFile без повідомлення про помилку не поверне жодного повідомлення. Завжди реєструйте 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