SEO//13 dk okuma

Go (Golang) i18n: Uluslararasılaştırma Kalıpları ve Kütüphaneleri

Eray Gündoğmuş
Paylaş

Go (Golang) i18n: Uluslararasılaştırma Kalıpları ve Kütüphaneleri

Go, küresel kullanıcılara hizmet veren backend servisleri, CLI'lar ve API'lar için giderek daha popüler hale gelen bir dildir. Go'nun standart kütüphanesi, bazı diğer ekosistemler kadar kapsamlı bir i18n çerçevesi içermese de golang.org/x/text, go-i18n ve dikkatli mimari bir arada kullanıldığında sağlam çok dilli Go uygulamaları geliştirmek mümkündür.

Bu kılavuz, x/text ile yerel ayara duyarlı biçimlendirmeden go-i18n ile çeviri yönetimine kadar eksiksiz Go i18n araç setini ele almakta; Go servisleri ve CLI'larında i18n yapılandırma kalıplarını da kapsamaktadır.

Go'nun i18n Ekosistemine Genel Bakış

Go'nun i18n araçları birkaç paket arasında bölünmüştür:

PaketAmaç
golang.org/x/text/languageBCP 47 dil etiketi ayrıştırma ve eşleştirme
golang.org/x/text/messagei18n destekli Printf tarzı biçimlendirilmiş mesajlar
golang.org/x/text/numberSayı biçimlendirme (çoğul, para birimi)
golang.org/x/text/collateYerel ayara duyarlı dize sıralama
golang.org/x/text/unicode/normUnicode normalleştirme
github.com/nicksnyder/go-i18n/v2Çeviri dosyası yönetimi, çoğullama
github.com/BurntSushi/tomlTOML ayrıştırma (yaygın çeviri formatı)

golang.org/x/text ile Dil Etiketi Ayrıştırma

golang.org/x/text/language paketi, HTTP Accept-Language başlıklarında, HTML lang niteliklerinde ve yerel ayar dizelerinde kullanılan yerel ayar tanımlayıcılarının standardı olan BCP 47 dil etiketlerini uygular.

Dil Etiketlerini Ayrıştırma ve Eşleştirme

package main

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

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

    // Language matching: find the best match from supported languages
    supported := []language.Tag{
        language.English,
        language.French,
        language.SimplifiedChinese,
        language.TraditionalChinese,
    }
    
    matcher := language.NewMatcher(supported)
    
    // User requests zh-TW (Traditional Chinese, Taiwan)
    t, _, _ := matcher.Match(language.MustParse("zh-TW"))
    fmt.Println(t) // zh-Hant → matches Traditional Chinese
    
    // User requests es-MX (Mexican Spanish)
    t, _, _ = matcher.Match(language.MustParse("es-MX"))
    fmt.Println(t) // en → falls back to English (Spanish not supported)
}

Accept-Language Başlıklarını Ayrıştırma

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 {
    // Parse Accept-Language header
    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: Çeviri Yönetimi

go-i18n, çeviri yönetimi için en yaygın kullanılan Go kütüphanesidir. Şunları destekler:

  • JSON, TOML ve YAML çeviri dosyaları
  • Çoğullama için ICU mesaj formatı
  • Şablon enterpolasyonu
  • Birden fazla çeviri dosyası yükleme

Kurulum ve Yapılandırma

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

Çeviri Dosyası Yapısı

# locales/en.toml
[welcome]
description = "Welcome message for new users"
one = "Welcome, {{.Name}}! You have {{.Count}} new notification."
other = "Welcome, {{.Name}}! You have {{.Count}} new notifications."

[item_count]
description = "Number of items in a list"
zero = "No items"
one = "{{.Count}} item"
other = "{{.Count}} items"
# 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"

go-i18n'i Başlatma

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)

    // Load all locale files
    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("loading locale %s: %w", entry.Name(), err)
        }
    }

    return nil
}

// NewLocalizer creates a localizer for the given language tags
func NewLocalizer(langs ...string) *i18n.Localizer {
    return i18n.NewLocalizer(bundle, langs...)
}

Mesajları Çevirme

package handlers

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

type WelcomeData struct {
    Name  string
    Count int
}

func WelcomeHandler(w http.ResponseWriter, r *http.Request) {
    // Get localizer for request language
    accept := r.Header.Get("Accept-Language")
    localizer := i18n.NewLocalizer(accept, "en")

    // Simple message with plural handling
    itemCount := 3
    msg, err := localizer.Localize(&i18n.LocalizeConfig{
        MessageID: "item_count",
        PluralCount: itemCount,
        TemplateData: map[string]interface{}{
            "Count": itemCount,
        },
    })
    if err != nil {
        msg = "Unknown number of items" // fallback
    }

    // Message with name and plural
    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)
}

Sayı ve Para Birimi Biçimlendirme

golang.org/x/text/message paketi yerel ayara duyarlı sayı biçimlendirme sağlar:

package main

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

func main() {
    // Create locale-specific printers
    enPrinter := message.NewPrinter(language.English)
    dePrinter := message.NewPrinter(language.German)
    jaPrinter := message.NewPrinter(language.Japanese)

    amount := 1234567.89

    // Locale-aware number formatting
    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

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

Tarih ve Saat Biçimlendirme

Go'nun time paketi temel tarih biçimlendirmeyi destekler; ancak yerel ayara duyarlı tarih biçimlendirme için x/text veya üçüncü taraf bir kütüphane gereklidir:

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

func formatDateLocale(t time.Time, locale language.Tag) string {
    // Go's built-in time formatting uses fixed layout strings
    // For locale-specific formatting, use strftime-equivalent libraries
    // or generate patterns from CLDR data
    
    switch locale {
    case language.English:
        return t.Format("January 2, 2006")
    case language.German:
        return t.Format("2. January 2006") // German date format
    case language.Japanese:
        return t.Format("2006年01月02日")
    default:
        return t.Format(time.RFC3339)
    }
}

Prodüksiyon kalitesinde yerel ayara duyarlı tarih biçimlendirmesi için gün/ay adları amacıyla github.com/goodsign/monday gibi bir kütüphane kullanın.

Bir Go Web Servisinde i18n Yapılandırma

Middleware Tabanlı Yerel Ayar Tespiti

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) {
        // Check URL query parameter first (e.g., ?lang=fr)
        lang := r.URL.Query().Get("lang")
        
        // Then check cookie
        if lang == "" {
            if cookie, err := r.Cookie("lang"); err == nil {
                lang = cookie.Value
            }
        }
        
        // Fall back to Accept-Language header
        if lang == "" {
            lang = r.Header.Get("Accept-Language")
        }

        // Create localizer and attach to context
        localizer := i18n.NewLocalizer(lang, "en")
        ctx := context.WithValue(r.Context(), localizerKey, localizer)
        
        next.ServeHTTP(w, r.WithContext(ctx))
    })
}

// GetLocalizer retrieves the localizer from the context
func GetLocalizer(ctx context.Context) *i18n.Localizer {
    l, ok := ctx.Value(localizerKey).(*i18n.Localizer)
    if !ok {
        return i18n.NewLocalizer("en")
    }
    return l
}

Çevrilebilir İçerik Olarak Hata Mesajları

package errors

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

// AppError is an error with a translatable message
type AppError struct {
    MessageID   string
    TemplateData interface{}
    Err         error
}

func (e *AppError) Error() string {
    return e.MessageID // Internal identifier
}

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 // Fallback to key
    }
    return msg
}

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

Go CLI Uygulamalarında i18n

CLI uygulamalarının i18n gereksinimleri farklıdır: sistem yerel ayarını tespit etmeli, çıktıyı uygun şekilde biçimlendirmeli ve terminal kodlamasını ele almalıdır.

package main

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

func detectSystemLocale() string {
    // Check LANG environment variable (Unix/Linux/macOS)
    if lang := os.Getenv("LANG"); lang != "" {
        // LANG is typically "en_US.UTF-8" format
        // Extract language tag
        parts := strings.Split(lang, ".")
        if len(parts) > 0 {
            // Convert "en_US" to "en-US" (BCP 47)
            return strings.ReplaceAll(parts[0], "_", "-")
        }
    }
    
    // Check 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" // Default to English
}

func main() {
    locale := detectSystemLocale()
    localizer := i18n.NewLocalizer(locale, "en")
    
    // Use localizer for all output
    fmt.Println(localizer.MustLocalize(&i18n.LocalizeConfig{
        MessageID: "cli.welcome",
    }))
}

Çıkarım ve Yönetim Araçları

go-i18n, çevrilebilir dizeleri ayıklamak için goi18n komut satırı aracı sunar:

# Install the CLI tool
go install github.com/nicksnyder/go-i18n/v2/goi18n@latest

# Extract strings from Go source (creates active.en.toml)
goi18n extract -format toml -outdir locales ./...

# Merge new strings into existing translations
# (creates translate.fr.toml with only new/changed strings)
goi18n merge -format toml -outdir locales locales/active.en.toml locales/active.fr.toml

# After translation, merge translate.fr.toml back into active.fr.toml
goi18n merge -format toml -outdir locales locales/active.fr.toml locales/translate.fr.toml

Bu iş akışı, sürekli çeviri güncellemeleri için CI/CD yerelleştirme otomasyonu ile uyumlu biçimde entegre olur.

Karmaşık Diller için Çoğullama

Go-i18n, desteklenen tüm diller için CLDR kuralları aracılığıyla çoğullamayı otomatik olarak yönetir:

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

Çoğullama kurallarının daha kapsamlı ele alınması için bkz. diller arası çoğullama kuralları.

Go'da i18n Testi

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 элементов"}, // Tricky: 11 uses "many", not "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("localize error: %v", err)
            }
            if got != tt.expected {
                t.Errorf("got %q, want %q", got, tt.expected)
            }
        })
    }
}

Kapsamlı test stratejileri için bkz. i18n test araçları, stratejileri ve otomasyonu.


Uygulamanızı better-i18n ile küreselleştirin

better-i18n; yapay zeka destekli çeviriler, git-native iş akışları ve küresel CDN teslimatını geliştirici odaklı tek bir platformda bir araya getirir. Elektronik tablolarla uğraşmayı bırakın ve her dilde ürün çıkarmaya başlayın.

Ücretsiz başlayın → · Özellikleri keşfedin · Belgeleri okuyun

Comments

Loading comments...