Skip to main content

Go 国際化完全ガイド

メッセージファイルから goroutine セーフなロケール解決まで、go-i18n を使用して 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

メッセージファイルの作成

locales ディレクトリに言語ごとの JSON ファイルを 1 つ作成します。各メッセージには ID と 1 つ以上の複数形が含まれます。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」のように内容が分かるメッセージ ID を使用してください。go-i18n ではネストしたキーではなくフラットな ID を使用します。Go の慣例に合わせ、ID は PascalCase にします。
3

Bundle の読み込み

Bundle は go-i18n の中央レジストリです。起動時に 1 つ作成し、ファイル形式を登録して、すべてのメッセージファイルを読み込みます。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)LocalizeConfig の MessageID が JSON ファイル内のキーと大文字・小文字まで完全に一致することを確認してください。
4

Localizer の使用

ユーザーの優先言語を指定し、リクエストごとに Localizer を作成します。Localizer は Bundle からメッセージを解決し、Go の text/template エンジンでテンプレートをレンダリングし、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)です。メッセージファイルに必要な形式をすべて定義すると、PluralCount に基づいて go-i18n が正しい形式を選択します。

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

ロケール検出

Web アプリケーションでは、クエリパラメーター、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」だけの場合、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 を使用すると、リリース前に欠落キーや壊れたプレースホルダーを検出できます。実際の翻訳が届く前に、i18n-pseudo の疑似翻訳で UI をテストしてください。

よくある問題

PluralCount と TemplateData の不一致

PluralCount は複数形を選択しますが、値をテンプレートへ注入しません。レンダリング済みメッセージに件数を表示するには、TemplateData にも渡す必要があります。TemplateData がない場合、'{'{.Count}'}' は「<no value>」としてレンダリングされます。

RegisterUnmarshalFunc の登録漏れ

ファイル形式に対応する bundle.RegisterUnmarshalFunc() を呼び出さない場合、LoadMessageFile は通知なしでメッセージを返しません。ファイルを読み込む前に、必ず json.Unmarshal(または toml/yaml)を登録してください。

キー単位のフォールバックが機能しない

go-i18n の NewLocalizer は複数の言語を受け取りますが、ロケールが単一のキーに一致すると、欠落キーはフォールバックせず空文字列を返します。go-locale-chain を使用し、キー単位の正しい連鎖でこの問題を修正してください。

テンプレート構文:{'{.Var}'} であり {'{Var}'} ではない

go-i18n は Go の text/template 構文を使用します。変数にはドットプレフィックスが必要です。{'{.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 に関するよくある質問