Skip to main content

Go 국제화 완벽 가이드

메시지 파일부터 고루틴 안전 로케일 해석까지 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 파일을 하나씩 만드세요. 각 메시지에는 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'처럼 설명적인 메시지 ID를 사용하세요. go-i18n은 중첩 키가 아닌 플랫 ID를 사용해요. Go 규칙에 맞춰 ID를 PascalCase로 유지하세요.
3

Bundle 로드

Bundle은 go-i18n의 중앙 레지스트리예요. 시작할 때 하나를 만들고 파일 형식을 등록한 뒤 모든 메시지 파일을 로드하세요. Bundle은 고루틴 안전하므로 한 번만 만들어 애플리케이션 전체에서 공유하세요.

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) 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)가 있어요. 메시지 파일에 필요한 형식을 모두 정의하면 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 경로 세그먼트 등 여러 출처에서 사용자의 선호 언어를 감지하세요. 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 자주 묻는 질문