SEO//13 min de lecture

Go (Golang) i18n : Modèles et Bibliothèques d'Internationalisation

Eray Gündoğmuş
Partager

Go (Golang) i18n : Modèles et Bibliothèques d'Internationalisation

Go est un langage de plus en plus populaire pour les services backend, les CLIs et les APIs qui servent des utilisateurs mondiaux. Bien que la bibliothèque standard de Go n'inclue pas un framework i18n aussi complet que certains autres écosystèmes, la combinaison de golang.org/x/text, go-i18n et une architecture soigneuse permet de créer des applications Go multilingues robustes.

Ce guide couvre le kit d'outils i18n complet pour Go—du formatage adapté aux paramètres régionaux avec x/text à la gestion des traductions avec go-i18n, ainsi que des modèles pour structurer l'i18n dans les services Go et les CLIs.

Vue d'Ensemble de l'Écosystème i18n de Go

Les outils i18n de Go sont répartis entre plusieurs paquets :

PaquetObjectif
golang.org/x/text/languageAnalyse et correspondance des balises de langues BCP 47
golang.org/x/text/messageMessages formatés de style Printf avec i18n
golang.org/x/text/numberFormatage des nombres (pluriels, monnaie)
golang.org/x/text/collateTri de chaînes adapté aux paramètres régionaux
golang.org/x/text/unicode/normNormalisation Unicode
github.com/nicksnyder/go-i18n/v2Gestion des fichiers de traduction, pluralisation
github.com/BurntSushi/tomlAnalyse TOML (format de traduction courant)

Analyse des Balises de Langues avec golang.org/x/text

Le paquet golang.org/x/text/language implémente les balises de langues BCP 47—le standard pour les identifiants de paramètres régionaux utilisés dans les en-têtes HTTP Accept-Language, les attributs HTML lang et les chaînes de paramètres régionaux.

Analyse et Correspondance des Balises de Langues

package main

import (
    "fmt"
    "golang.org/x/text/language"
)

func main() {
    // Analyser une balise de langue
    tag, err := language.Parse("zh-Hant-TW")
    if err != nil {
        panic(err)
    }
    fmt.Println(tag) // zh-Hant-TW

    // Correspondance de langues : trouver la meilleure correspondance parmi les langues supportées
    supported := []language.Tag{
        language.English,
        language.French,
        language.SimplifiedChinese,
        language.TraditionalChinese,
    }
    
    matcher := language.NewMatcher(supported)
    
    // L'utilisateur demande zh-TW (Chinois Traditionnel, Taïwan)
    t, _, _ := matcher.Match(language.MustParse("zh-TW"))
    fmt.Println(t) // zh-Hant → correspond au Chinois Traditionnel
    
    // L'utilisateur demande es-MX (Espagnol Mexicain)
    t, _, _ = matcher.Match(language.MustParse("es-MX"))
    fmt.Println(t) // en → repli sur l'anglais (Espagnol non supporté)
}

Analyse des En-têtes Accept-Language

import (
    "net/http"
    "golang.org/x/text/language"
)

var supported = []language.Tag{
    language.English,
    language.French,
    language.German,
    language.Japanese,
}

var matcher = language.NewMatcher(supported)

func getLocale(r *http.Request) language.Tag {
    // Analyser l'en-tête Accept-Language
    accept := r.Header.Get("Accept-Language")
    tags, _, err := language.ParseAcceptLanguage(accept)
    if err != nil || len(tags) == 0 {
        return language.English
    }
    
    tag, _, _ := matcher.Match(tags...)
    return tag
}

go-i18n : Gestion des Traductions

go-i18n est la bibliothèque Go la plus utilisée pour la gestion des traductions. Elle supporte :

  • Les fichiers de traduction JSON, TOML et YAML
  • Le format de messages ICU pour la pluralisation
  • L'interpolation de modèles
  • Le chargement de plusieurs fichiers de traduction

Installation et Configuration

go get github.com/nicksnyder/go-i18n/v2@latest
go get github.com/BurntSushi/toml

Structure des Fichiers de Traduction

# locales/en.toml
[welcome]
description = "Message de bienvenue pour les nouveaux utilisateurs"
one = "Bienvenue, {{.Name}} ! Vous avez {{.Count}} nouvelle notification."
other = "Bienvenue, {{.Name}} ! Vous avez {{.Count}} nouvelles notifications."

[item_count]
description = "Nombre d'éléments dans une liste"
zero = "Aucun élément"
one = "{{.Count}} élément"
other = "{{.Count}} éléments"
# locales/fr.toml
[welcome]
one = "Bienvenue, {{.Name}} ! Vous avez {{.Count}} nouvelle notification."
other = "Bienvenue, {{.Name}} ! Vous avez {{.Count}} nouvelles notifications."

[item_count]
zero = "Aucun élément"
one = "{{.Count}} élément"
other = "{{.Count}} éléments"

Initialisation de go-i18n

package i18n

import (
    "embed"
    "encoding/json"

    "github.com/BurntSushi/toml"
    "github.com/nicksnyder/go-i18n/v2/i18n"
    "golang.org/x/text/language"
)

//go:embed locales/*.toml
var localeFS embed.FS

var bundle *i18n.Bundle

func Init() error {
    bundle = i18n.NewBundle(language.English)
    bundle.RegisterUnmarshalFunc("toml", toml.Unmarshal)

    // Charger tous les fichiers de paramètres régionaux
    entries, err := localeFS.ReadDir("locales")
    if err != nil {
        return err
    }

    for _, entry := range entries {
        if _, err := bundle.LoadMessageFileFS(localeFS, "locales/"+entry.Name()); err != nil {
            return fmt.Errorf("chargement des paramètres régionaux %s: %w", entry.Name(), err)
        }
    }

    return nil
}

// NewLocalizer crée un localiseur pour les balises de langues données
func NewLocalizer(langs ...string) *i18n.Localizer {
    return i18n.NewLocalizer(bundle, langs...)
}

Traduction des Messages

package handlers

import (
    "net/http"
    "myapp/i18n"
)

type WelcomeData struct {
    Name  string
    Count int
}

func WelcomeHandler(w http.ResponseWriter, r *http.Request) {
    // Obtenir le localiseur pour la langue de la requête
    accept := r.Header.Get("Accept-Language")
    localizer := i18n.NewLocalizer(accept, "en")

    // Message simple avec gestion des pluriels
    itemCount := 3
    msg, err := localizer.Localize(&i18n.LocalizeConfig{
        MessageID: "item_count",
        PluralCount: itemCount,
        TemplateData: map[string]interface{}{
            "Count": itemCount,
        },
    })
    if err != nil {
        msg = "Nombre d'éléments inconnu" // repli
    }

    // Message avec nom et pluriel
    welcome, err := localizer.Localize(&i18n.LocalizeConfig{
        MessageID:   "welcome",
        PluralCount: itemCount,
        TemplateData: WelcomeData{
            Name:  "Alice",
            Count: itemCount,
        },
    })

    fmt.Fprintf(w, "%s\n%s\n", welcome, msg)
}

Formatage des Nombres et des Monnaies

Le paquet golang.org/x/text/message fournit un formatage des nombres adapté aux paramètres régionaux :

package main

import (
    "fmt"
    "golang.org/x/text/language"
    "golang.org/x/text/message"
    "golang.org/x/text/number"
)

func main() {
    // Créer des imprimantes spécifiques aux paramètres régionaux
    enPrinter := message.NewPrinter(language.English)
    dePrinter := message.NewPrinter(language.German)
    jaPrinter := message.NewPrinter(language.Japanese)

    amount := 1234567.89

    // Formatage des nombres adapté aux paramètres régionaux
    enPrinter.Printf("%v\n", number.Decimal(amount))
    // 1,234,567.89
    
    dePrinter.Printf("%v\n", number.Decimal(amount))
    // 1.234.567,89
    
    jaPrinter.Printf("%v\n", number.Decimal(amount))
    // 1,234,567.89

    // Formatage des pourcentages
    enPrinter.Printf("%.2%\n", number.Percent(0.8527))
    // 85.27%
    
    dePrinter.Printf("%.2%\n", number.Percent(0.8527))
    // 85,27 %
}

Formatage des Dates et Heures

Le paquet time de Go gère le formatage de base des dates, mais pour un formatage adapté aux paramètres régionaux, vous avez besoin de x/text ou d'une bibliothèque tierce :

import (
    "time"
    "golang.org/x/text/language"
    "golang.org/x/text/language/display"
)

func formatDateLocale(t time.Time, locale language.Tag) string {
    // Le formatage de l'heure intégré à Go utilise des chaînes de mise en page fixes
    // Pour un formatage spécifique aux paramètres régionaux, utiliser des bibliothèques équivalentes à strftime
    // ou générer des modèles à partir des données CLDR
    
    switch locale {
    case language.English:
        return t.Format("January 2, 2006")
    case language.German:
        return t.Format("2. January 2006") // Format de date allemand
    case language.Japanese:
        return t.Format("2006年01月02日")
    default:
        return t.Format(time.RFC3339)
    }
}

Pour un formatage de dates de qualité production adapté aux paramètres régionaux, utilisez une bibliothèque comme github.com/goodsign/monday pour les noms de jours et de mois.

Structuration de l'i18n dans un Service Web Go

Détection des Paramètres Régionaux Basée sur le Middleware

package middleware

import (
    "context"
    "net/http"
    "golang.org/x/text/language"
    "myapp/i18n"
)

type contextKey string
const localizerKey contextKey = "localizer"

var supported = []language.Tag{
    language.English,
    language.French,
    language.German,
    language.Japanese,
}
var matcher = language.NewMatcher(supported)

func LocaleMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // Vérifier d'abord le paramètre de requête URL (ex. ?lang=fr)
        lang := r.URL.Query().Get("lang")
        
        // Puis vérifier le cookie
        if lang == "" {
            if cookie, err := r.Cookie("lang"); err == nil {
                lang = cookie.Value
            }
        }
        
        // Repli sur l'en-tête Accept-Language
        if lang == "" {
            lang = r.Header.Get("Accept-Language")
        }

        // Créer le localiseur et l'attacher au contexte
        localizer := i18n.NewLocalizer(lang, "en")
        ctx := context.WithValue(r.Context(), localizerKey, localizer)
        
        next.ServeHTTP(w, r.WithContext(ctx))
    })
}

// GetLocalizer récupère le localiseur depuis le contexte
func GetLocalizer(ctx context.Context) *i18n.Localizer {
    l, ok := ctx.Value(localizerKey).(*i18n.Localizer)
    if !ok {
        return i18n.NewLocalizer("en")
    }
    return l
}

Messages d'Erreur en tant que Contenu Traduisible

package errors

import (
    "github.com/nicksnyder/go-i18n/v2/i18n"
)

// AppError est une erreur avec un message traduisible
type AppError struct {
    MessageID   string
    TemplateData interface{}
    Err         error
}

func (e *AppError) Error() string {
    return e.MessageID // Identifiant interne
}

func (e *AppError) Localize(localizer *i18n.Localizer) string {
    msg, err := localizer.Localize(&i18n.LocalizeConfig{
        MessageID:    e.MessageID,
        TemplateData: e.TemplateData,
    })
    if err != nil {
        return e.MessageID // Repli sur la clé
    }
    return msg
}

// Utilisation
var ErrNotFound = &AppError{MessageID: "error.not_found"}
var ErrUnauthorized = &AppError{MessageID: "error.unauthorized"}

i18n pour les Applications CLI Go

Les applications CLI ont des exigences i18n distinctes : elles doivent détecter les paramètres régionaux du système, formater la sortie de manière appropriée et gérer l'encodage du terminal.

package main

import (
    "os"
    "golang.org/x/text/language"
    "myapp/i18n"
)

func detectSystemLocale() string {
    // Vérifier la variable d'environnement LANG (Unix/Linux/macOS)
    if lang := os.Getenv("LANG"); lang != "" {
        // LANG est généralement au format "en_US.UTF-8"
        // Extraire la balise de langue
        parts := strings.Split(lang, ".")
        if len(parts) > 0 {
            // Convertir "en_US" en "en-US" (BCP 47)
            return strings.ReplaceAll(parts[0], "_", "-")
        }
    }
    
    // Vérifier LC_ALL, LC_MESSAGES
    for _, env := range []string{"LC_ALL", "LC_MESSAGES"} {
        if lang := os.Getenv(env); lang != "" {
            return strings.ReplaceAll(strings.Split(lang, ".")[0], "_", "-")
        }
    }
    
    return "en" // Défaut à l'anglais
}

func main() {
    locale := detectSystemLocale()
    localizer := i18n.NewLocalizer(locale, "en")
    
    // Utiliser le localiseur pour toutes les sorties
    fmt.Println(localizer.MustLocalize(&i18n.LocalizeConfig{
        MessageID: "cli.welcome",
    }))
}

Outils d'Extraction et de Gestion

go-i18n fournit un outil en ligne de commande goi18n pour extraire les chaînes traduisibles :

# Installer l'outil CLI
go install github.com/nicksnyder/go-i18n/v2/goi18n@latest

# Extraire les chaînes du code source Go (crée active.en.toml)
goi18n extract -format toml -outdir locales ./...

# Fusionner les nouvelles chaînes dans les traductions existantes
# (crée translate.fr.toml avec uniquement les chaînes nouvelles/modifiées)
goi18n merge -format toml -outdir locales locales/active.en.toml locales/active.fr.toml

# Après traduction, fusionner translate.fr.toml dans active.fr.toml
goi18n merge -format toml -outdir locales locales/active.fr.toml locales/translate.fr.toml

Ce workflow s'intègre bien avec l'automatisation de localisation CI/CD pour des mises à jour de traduction continues.

Pluralisation pour les Langues Complexes

Go-i18n gère automatiquement la pluralisation via les règles CLDR pour toutes les langues supportées :

# locales/ru.toml (Russe - 4 formes de pluriel)
[item_count]
zero = "Нет элементов"
one = "{{.Count}} элемент"     # 1, 21, 31...
few = "{{.Count}} элемента"    # 2-4, 22-24...
many = "{{.Count}} элементов"  # 5-20, 25-30...
other = "{{.Count}} элемента"  # fractions, autres
# locales/ar.toml (Arabe - 6 formes de pluriel)
[item_count]
zero = "لا عناصر"
one = "{{.Count}} عنصر"
two = "{{.Count}} عنصران"
few = "{{.Count}} عناصر"
many = "{{.Count}} عنصرًا"
other = "{{.Count}} عنصر"

Pour une couverture plus approfondie des règles de pluralisation, voir les règles de pluralisation dans les différentes langues.

Tests i18n en Go

package i18n_test

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

func TestPluralForms(t *testing.T) {
    bundle := setupTestBundle(t)
    
    tests := []struct {
        lang     string
        count    int
        expected string
    }{
        {"en", 0, "No items"},
        {"en", 1, "1 item"},
        {"en", 2, "2 items"},
        {"ru", 1, "1 элемент"},
        {"ru", 2, "2 элемента"},
        {"ru", 5, "5 элементов"},
        {"ru", 11, "11 элементов"}, // Délicat : 11 utilise "many", pas "one"
    }
    
    for _, tt := range tests {
        t.Run(fmt.Sprintf("%s_%d", tt.lang, tt.count), func(t *testing.T) {
            localizer := i18n.NewLocalizer(bundle, tt.lang)
            got, err := localizer.Localize(&i18n.LocalizeConfig{
                MessageID:   "item_count",
                PluralCount: tt.count,
                TemplateData: map[string]interface{}{"Count": tt.count},
            })
            if err != nil {
                t.Fatalf("erreur de localisation : %v", err)
            }
            if got != tt.expected {
                t.Errorf("obtenu %q, attendu %q", got, tt.expected)
            }
        })
    }
}

Pour des stratégies de tests complètes, voir les outils, stratégies et automatisation des tests i18n.


Internationalisez votre application avec better-i18n

better-i18n combine des traductions alimentées par l'IA, des workflows natifs git et une livraison CDN mondiale dans une plateforme centrée sur les développeurs. Arrêtez de gérer des feuilles de calcul et commencez à publier dans toutes les langues.

Commencer gratuitement → · Explorer les fonctionnalités · Lire la documentation

Comments

Loading comments...