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

Създайте файлове със съобщения

Създайте по един 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."
  }
}
Използвайте описателни идентификатори на съобщения като 'ItemCount' или 'WelcomeBack' вместо пътища, разделени с точки. go-i18n използва плоски идентификатори, а не вложени ключове. Записвайте идентификаторите с 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 и 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', функцията правилно връща '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.

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
Превеждайте поетапно — когато добавите нови идентификатори на съобщения към изходния файл, преведете само новите ключове, вместо да създавате наново всички файлове. Така ще запазите преводите, които вече са прегледани от човек.

Автоматизирайте контрола на качеството на превода

Откривайте липсващи ключове и повредени заместители с i18n-validate, преди да достигнат до потребителите. Тествайте потребителския интерфейс с псевдопреводи чрез 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