i18n

package module
v0.5.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 5, 2026 License: MIT Imports: 14 Imported by: 7

README

go-i18n

A Go internationalization library with ICU MessageFormat support, deterministic fallbacks, and optional net/http locale detection

For development guidelines, see AGENTS.md. For internal contracts and design rules, start with SPECS/00-overview.md.

Features

  • ICU MessageFormat: Use plural, select, and ordinal formatting through github.com/kaptinlin/messageformat-go/v1.
  • Flexible loading: Load translations from maps, files, glob patterns, or embedded filesystems.
  • Deterministic fallbacks: Root fallback chains in the configured default locale.
  • Lookup details: Use Lookup to get the rendered text, resolved locale, and result source.
  • Text and token keys: Use token keys like hello_world or literal text keys with GetX context disambiguation.
  • HTTP integration: Detect locales from query, cookie, header, or Accept-Language, then inject a request-scoped localizer.
  • Custom unmarshalers: Keep JSON as the default, or plug in YAML, TOML, or INI parsing.

Installation

Requires the Go version declared in go.mod.

go get github.com/kaptinlin/go-i18n@latest

Quick Start

package main

import (
	"fmt"
	"log"

	"github.com/kaptinlin/go-i18n"
)

func main() {
	bundle := i18n.NewBundle(
		i18n.WithDefaultLocale("en"),
		i18n.WithLocales("en", "zh-Hans"),
	)

	err := bundle.LoadMessages(map[string]map[string]string{
		"en": {"hello": "Hello, {name}!"},
		"zh-Hans": {"hello": "你好,{name}!"},
	})
	if err != nil {
		log.Fatal(err)
	}

	localizer := bundle.NewLocalizer("zh-CN")
	fmt.Println(localizer.Get("hello", i18n.Vars{"name": "World"}))
}

Core API

Bundle and localizer
  • NewBundle constructs the shared translation bundle.
  • WithDefaultLocale, WithLocales, WithFallback, WithUnmarshaler, WithMessageFormatOptions, WithCustomFormatters, and WithStrictMode configure bundle behavior.
  • NewLocalizer picks the first matching locale from its arguments, then falls back to the default locale.
  • SupportedLocales and IsLanguageSupported expose the configured locale matcher state.
Load translations

Use the loader that matches your source of truth:

  • LoadMessages for in-memory maps
  • LoadFiles for explicit files
  • LoadGlob for file pattern expansion
  • LoadFS for go:embed and other fs.FS implementations

Translation file names are normalized to locales, so names like zh_Hans.json and zh-Hans.user.json still resolve to zh-Hans.

Render translations

Use Get for the normal translation path, GetX for context-disambiguated text keys, and Format for dynamic messages that are not stored in translation files.

localizer := bundle.NewLocalizer("zh-Hans")

fmt.Println(localizer.Get("hello", i18n.Vars{"name": "Lin"}))
fmt.Println(localizer.GetX("Post", "verb"))
Inspect fallback behavior

Use Lookup when you need to know where a translation came from.

result := localizer.Lookup("hello", i18n.Vars{"name": "Lin"})
fmt.Println(result.Text)
fmt.Println(result.Locale)
fmt.Println(result.Source)

TranslationSource reports one of direct, fallback, or missing.

Use Has and Keys for direct locale contents only. They do not include inherited fallback keys.

Detect request locales

Use NewDetector to resolve the best locale from HTTP request inputs.

detector := i18n.NewDetector(
	bundle,
	i18n.WithDetectorPriority(
		i18n.DetectorSourceQuery,
		i18n.DetectorSourceCookie,
		i18n.DetectorSourceHeader,
		i18n.DetectorSourceAccept,
	),
)

locale := detector.DetectLocale(r)
localizer := bundle.NewLocalizer(locale)

Use WithDetectorQueryParam, WithDetectorCookieName, and WithDetectorHeaderName to customize source names. Use MatchAvailableLocale when you only need Accept-Language matching.

Inject a localizer into net/http

Use the optional middleware package to attach a request-scoped localizer to the request context.

package main

import (
	"fmt"
	"net/http"

	"github.com/kaptinlin/go-i18n"
	"github.com/kaptinlin/go-i18n/middleware"
)

func main() {
	bundle := i18n.NewBundle(i18n.WithDefaultLocale("en"), i18n.WithLocales("en", "zh-Hans"))
	_ = bundle.LoadMessages(map[string]map[string]string{
		"en": {"hello": "Hello"},
	})

	handler := middleware.HTTPMiddleware(bundle)(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		localizer, ok := i18n.LocalizerFromContext(r.Context())
		if !ok {
			http.Error(w, "missing localizer", http.StatusInternalServerError)
			return
		}
		fmt.Fprint(w, localizer.Get("hello"))
	}))

	http.ListenAndServe(":8080", handler)
}
Use custom unmarshalers

JSON is the default. Override it when your translation files use another format.

import "github.com/goccy/go-yaml"

bundle := i18n.NewBundle(
	i18n.WithDefaultLocale("en"),
	i18n.WithLocales("en", "zh-Hans"),
	i18n.WithUnmarshaler(yaml.Unmarshal),
)

See examples/yml, examples/toml, and examples/ini for complete loaders.

Examples

Run the package examples directly:

go run ./examples/basic
go run ./examples/files
go run ./examples/embed
go run ./examples/glob
go run ./examples/icu
go run ./examples/source_locale
go run ./examples/text

Additional examples cover nested files and custom unmarshalers under examples/.

API Reference

See pkg.go.dev/github.com/kaptinlin/go-i18n for the exported API.

Development

Run the project commands from the repository root:

task test
task test-coverage
task bench
task fmt
task vet
task lint

task verify

Contributing

  • Keep examples runnable and user-facing.
  • Run task fmt, task vet, task lint, task test before shipping changes.
  • Keep README.md, CLAUDE.md, and SPECS/00-overview.md aligned when public behavior changes.

License

This project is licensed under the MIT License - see the LICENSE file for details.

Documentation

Overview

Package i18n provides locale-aware message lookup with ICU MessageFormat support.

Index

Examples

Constants

This section is empty.

Variables

View Source
var ErrMessageFormatCompilation = errors.New("messageformat compilation failed")

ErrMessageFormatCompilation indicates that MessageFormat template compilation failed. The translation text is returned as-is without formatting capabilities.

Functions

func ContextWithLocalizer added in v0.3.1

func ContextWithLocalizer(ctx context.Context, l *Localizer) context.Context

ContextWithLocalizer returns a copy of ctx that carries l.

Types

type Detector added in v0.3.1

type Detector struct {
	// contains filtered or unexported fields
}

Detector resolves the best locale for an HTTP request.

func NewDetector added in v0.3.1

func NewDetector(bundle *I18n, opts ...DetectorOption) *Detector

NewDetector creates a request locale detector for the given bundle.

func (*Detector) DetectLocale added in v0.3.1

func (d *Detector) DetectLocale(r *http.Request) string

DetectLocale returns the best matching locale for r.

type DetectorOption added in v0.3.1

type DetectorOption func(*Detector)

DetectorOption configures a Detector.

func WithDetectorCookieName added in v0.3.1

func WithDetectorCookieName(name string) DetectorOption

WithDetectorCookieName sets the cookie name for locale detection.

func WithDetectorHeaderName added in v0.3.1

func WithDetectorHeaderName(name string) DetectorOption

WithDetectorHeaderName sets the header name for locale detection.

func WithDetectorPriority added in v0.3.1

func WithDetectorPriority(priority ...DetectorSource) DetectorOption

WithDetectorPriority sets the detector source priority.

func WithDetectorQueryParam added in v0.3.1

func WithDetectorQueryParam(name string) DetectorOption

WithDetectorQueryParam sets the query parameter name for locale detection.

type DetectorSource added in v0.4.0

type DetectorSource string

DetectorSource identifies a request input that can supply a locale.

const (
	// DetectorSourceQuery reads the locale from the request query string.
	DetectorSourceQuery DetectorSource = "query"
	// DetectorSourceCookie reads the locale from a request cookie.
	DetectorSourceCookie DetectorSource = "cookie"
	// DetectorSourceHeader reads the locale from a request header.
	DetectorSourceHeader DetectorSource = "header"
	// DetectorSourceAccept reads the locale from the Accept-Language header.
	DetectorSourceAccept DetectorSource = "accept-language"
)

type I18n

type I18n struct {
	// contains filtered or unexported fields
}

I18n is the main internationalization bundle that manages translations, locales, and fallback chains.

func NewBundle

func NewBundle(options ...Option) *I18n

NewBundle creates a new internationalization bundle with the given options. If no default locale is set, the first locale from WithLocales is used; if no locales are configured, English is used as the default.

func (*I18n) Has added in v0.4.0

func (i *I18n) Has(locale, key string) bool

Has reports whether key is defined directly for locale in the loaded bundle state. Fallback-populated keys are not included.

func (*I18n) IsLanguageSupported

func (i *I18n) IsLanguageSupported(lang language.Tag) bool

IsLanguageSupported reports whether lang can be matched to a configured locale. Languages not in SupportedLocales may still match through the language matcher.

func (*I18n) Keys added in v0.3.1

func (i *I18n) Keys(locale string) []string

Keys returns the sorted keys defined directly for locale in the loaded bundle state. Fallback-populated keys are not included.

func (*I18n) LoadFS

func (i *I18n) LoadFS(fsys fs.FS, patterns ...string) error

LoadFS loads translations from an fs.FS, useful for go:embed.

func (*I18n) LoadFiles

func (i *I18n) LoadFiles(files ...string) error

LoadFiles loads translations from the given file paths.

func (*I18n) LoadGlob

func (i *I18n) LoadGlob(patterns ...string) error

LoadGlob loads translations from files matching the given glob patterns.

func (*I18n) LoadMessages

func (i *I18n) LoadMessages(msgs map[string]map[string]string) error

LoadMessages loads translations from a locale-keyed map. Locales not matching any configured locale are silently skipped.

func (*I18n) MatchAvailableLocale

func (i *I18n) MatchAvailableLocale(accepts ...string) string

MatchAvailableLocale returns the best matching locale for the given Accept-Language header strings. Returns the default locale if no match is found.

func (*I18n) NewLocalizer

func (i *I18n) NewLocalizer(locales ...string) *Localizer

NewLocalizer creates a Localizer for the first matching locale from locales. If none match, the default locale is used.

func (*I18n) SupportedLocales added in v0.4.0

func (i *I18n) SupportedLocales() []language.Tag

SupportedLocales returns the configured locale tags for this bundle.

type Localizer

type Localizer struct {
	// contains filtered or unexported fields
}

Localizer provides translation methods for a specific locale. Create one via I18n.NewLocalizer.

func LocalizerFromContext added in v0.3.1

func LocalizerFromContext(ctx context.Context) (*Localizer, bool)

LocalizerFromContext returns the localizer stored in ctx.

func (*Localizer) Format added in v0.1.6

func (l *Localizer) Format(message string, data ...Vars) (string, error)

Format compiles and formats a MessageFormat message directly. This bypasses translation lookup and recompiles the message on each call, so it is intended for dynamic, non-hot-path messages that are not stored in translation files. Prefer Localizer.Get for normal translated content.

func (*Localizer) Get

func (l *Localizer) Get(name string, data ...Vars) string

Get returns the translation for name with optional MessageFormat variables. Returns name as fallback if no translation is found.

func (*Localizer) GetX

func (l *Localizer) GetX(name, context string, data ...Vars) string

GetX returns the translation for name disambiguated by context. The context is appended as " <context>" to form the lookup key. For example, GetX("Post", "verb") looks up "Post <verb>".

func (*Localizer) Locale

func (l *Localizer) Locale() string

Locale returns the resolved locale name for this localizer.

func (*Localizer) Lookup added in v0.3.0

func (l *Localizer) Lookup(name string, data ...Vars) TranslationResult

Lookup returns the translation for name with full lookup details. Use Localizer.Get for the common case where only the text is needed.

type Option added in v0.2.4

type Option func(*I18n)

Option configures an I18n bundle. See WithDefaultLocale, WithLocales, WithFallback, and WithUnmarshaler for available options.

func WithCustomFormatters added in v0.1.6

func WithCustomFormatters(formatters map[string]any) Option

WithCustomFormatters adds custom formatters for MessageFormat.

func WithDefaultLocale

func WithDefaultLocale(locale string) Option

WithDefaultLocale sets the default locale. This locale is used when no translation is found in the requested locale or its fallback chain.

func WithFallback

func WithFallback(f map[string][]string) Option

WithFallback configures locale fallback chains. Each key is a locale, and its value is an ordered list of fallback locales to try when a translation is missing. The default locale is used as the final fallback.

func WithLocales

func WithLocales(locales ...string) Option

WithLocales sets the supported locales for the bundle. Invalid locale strings are silently ignored.

func WithMessageFormatOptions added in v0.1.6

func WithMessageFormatOptions(opts *mf.MessageFormatOptions) Option

WithMessageFormatOptions sets MessageFormat options for the bundle.

func WithStrictMode added in v0.1.6

func WithStrictMode(strict bool) Option

WithStrictMode enables strict parsing mode for MessageFormat.

func WithUnmarshaler

func WithUnmarshaler(u Unmarshaler) Option

WithUnmarshaler sets a custom unmarshaler for translation files. The default is JSON. Common alternatives include YAML, TOML, and INI.

type TranslationResult added in v0.3.0

type TranslationResult struct {
	// Text is the translated message, or the key itself if not found.
	Text string

	// Locale is the BCP 47 locale tag that produced Text.
	Locale string

	// Source reports whether the result came from the requested locale,
	// the fallback chain, or runtime key fallback.
	Source TranslationSource
}

TranslationResult holds detailed translation lookup information.

type TranslationSource added in v0.4.0

type TranslationSource string

TranslationSource describes how a lookup result was produced.

const (
	// TranslationSourceDirect indicates that the requested locale supplied the translation.
	TranslationSourceDirect TranslationSource = "direct"
	// TranslationSourceFallback indicates that a fallback locale supplied the translation.
	TranslationSourceFallback TranslationSource = "fallback"
	// TranslationSourceMissing indicates that no loaded translation was found and the key was returned.
	TranslationSourceMissing TranslationSource = "missing"
)

type Unmarshaler

type Unmarshaler func(data []byte, v any) error

Unmarshaler unmarshals translation files. Common implementations include json.Unmarshal, yaml.Unmarshal, and toml.Unmarshal.

type Vars

type Vars map[string]any

Vars supplies named values for MessageFormat interpolation.

Example
v := Vars{"name": "Alice", "count": 3}
fmt.Println(v["name"])
fmt.Println(v["count"])
Output:
Alice
3

Directories

Path Synopsis
examples
basic command
embed command
files command
glob command
icu command
ini command
nested_files command
source_locale command
text command
toml command
yml command
Package middleware provides optional HTTP integration for go-i18n.
Package middleware provides optional HTTP integration for go-i18n.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL