i18n

package module
v1.0.1 Latest Latest
Warning

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

Go to latest
Published: Jun 30, 2026 License: MIT Imports: 3 Imported by: 0

README

i18n

Translate text in your Go applications with a single import — no per-package translation boilerplate. Works with standard Go, TinyGo, and WebAssembly, with zero external dependencies.

Install

go get github.com/0verkilll/i18n

Sponsor

If this project is useful to you, please consider supporting its development:

Sponsor @0verkilll on GitHub

License

MIT

Documentation

Overview

Package i18n provides a security-hardened internationalization library for Go with locale fallback chains, embedded filesystem support, build-tag locale selection for WASM/TinyGo, and zero external dependencies.

Quick Start

translator, err := i18n.New(
    i18n.WithFileSystemLoader("locales"),
    i18n.WithDefaultLocale("en-US"),
)
if err != nil {
    log.Fatal(err)
}

message := translator.Translate("greeting")
welcome := translator.TranslateWithArgs("welcome", "Alice")
translator.SetLocale("es-ES")

Key Types

  • Translator: Main translation engine with fallback chain, plural, gender, and ICU MessageFormat support. Thread-safe for concurrent use.
  • TranslatorProvider: Interface for translation services; implemented by Translator and PackageTranslator.
  • MapCache: LRU-aware translation cache implementing Cacher.
  • Namespace: Immutable key prefix helper for scoping translations to a package name. Methods: T, TF, TD, Has, Key.
  • PackageTranslator: Per-package translator with hardcoded defaults and thread-safe translator swapping. Replaces ~100-200 lines of boilerplate.

Interfaces

Build-Tag Locale Selection

For WASM and TinyGo builds, compile only the locales you need:

go build -tags locale_en_us
go build -tags "locale_en_us,locale_es_es"
go build -tags locale_all

Use RegistryLoader to serve translations from the build-tag registry:

translator, err := i18n.New(
    i18n.WithRegistryLoader(),
    i18n.WithDefaultLocale("en-US"),
)

When no locale tags are specified, the registry is empty and RegistryLoader returns an error for any locale. The locale data files in internal/localedata/ are example translations for testing; production applications should supply their own translation files.

Plural and Gender

translator.TranslatePlural("items", 1)                      // "1 item"
translator.TranslatePlural("items", 5)                      // "5 items"
translator.TranslateGender("greeting", i18n.Feminine)       // "greeting.feminine"
translator.TranslateWithMessage("msg", map[string]interface{}{"count": 3})

Security

All input is validated and all output is sanitized automatically:

  • Path traversal prevention in locale names and file paths
  • Control character and ANSI escape sequence filtering
  • BiDi override attack prevention (U+202A-U+202E)
  • Format string validation (blocks %n, validates argument counts)
  • Input size limits: MaxLocaleLength (10), MaxKeyLength (256), MaxKeyDepth (10), MaxOutputLength (10240)
  • JSON size limit (10 MB) and nesting depth limit (50 levels)

Error Types

Custom error types supporting errors.Is and errors.As:

Examples

See example_test.go for complete working examples covering basic translation, format strings, locale switching, fallback chains, embedded filesystem, build-tag locale selection, Namespace, PackageTranslator, plural and gender translation, and parser registry extensibility.

Example

Example demonstrates basic translation usage with filesystem loader

package main

import (
	"fmt"
	"log"
	"os"
	"path/filepath"

	"github.com/0verkilll/i18n"
)

func main() {
	// Create temporary directory for this example
	tmpDir := setupExampleLocales()
	defer func() { _ = os.RemoveAll(tmpDir) }() //nolint:errcheck // Cleanup in test, error is non-critical

	// Create translator with filesystem loader
	translator, err := i18n.New(
		i18n.WithFileSystemLoader(tmpDir),
		i18n.WithDefaultLocale("en-US"),
	)
	if err != nil {
		_ = os.RemoveAll(tmpDir) //nolint:errcheck // Best-effort cleanup before Fatal
		log.Fatal(err)           //nolint:gocritic // Cleanup already done before Fatal
	}

	// Simple translation
	greeting := translator.Translate("greeting")
	fmt.Println(greeting)

	// Nested key translation
	title := translator.Translate("user.profile.title")
	fmt.Println(title)

}

// setupExampleLocales creates example locale files for basic translation examples.
func setupExampleLocales() string {
	tmpDir := os.TempDir() + "/i18n-example"
	_ = os.MkdirAll(tmpDir, 0o755)

	enUS := []byte(`{
		"greeting": "Hello",
		"welcome": "Welcome, %s!",
		"farewell": "Goodbye",
		"user": {
			"profile": {
				"title": "User Profile"
			}
		},
		"items_count": "You have %d items",
		"multi": "%s has %d items"
	}`)
	_ = os.WriteFile(filepath.Join(tmpDir, "en-US.json"), enUS, 0o644)

	esES := []byte(`{
		"greeting": "Hola",
		"farewell": "Adios"
	}`)
	_ = os.WriteFile(filepath.Join(tmpDir, "es-ES.json"), esES, 0o644)

	return tmpDir
}
Output:
Hello
User Profile
Example (CustomComponents)

Example_customComponents demonstrates using custom loader and parser

package main

import (
	"fmt"
	"log"
	"os"
	"path/filepath"

	"github.com/0verkilll/i18n"
)

func main() {
	tmpDir := setupExampleLocales()
	defer func() { _ = os.RemoveAll(tmpDir) }() //nolint:errcheck // Cleanup in test, error is non-critical

	// Create custom components
	loader := i18n.NewFileSystemLoader(tmpDir)
	parser := i18n.NewJSONParser()
	resolver := i18n.NewDefaultKeyResolver()

	translator, err := i18n.New(
		i18n.WithLoader(loader),
		i18n.WithParser(parser),
		i18n.WithResolver(resolver),
		i18n.WithDefaultLocale("en-US"),
	)
	if err != nil {
		_ = os.RemoveAll(tmpDir) //nolint:errcheck // Best-effort cleanup before Fatal
		log.Fatal(err)           //nolint:gocritic // Cleanup already done before Fatal
	}

	result := translator.Translate("greeting")
	fmt.Println(result)

}

// setupExampleLocales creates example locale files for basic translation examples.
func setupExampleLocales() string {
	tmpDir := os.TempDir() + "/i18n-example"
	_ = os.MkdirAll(tmpDir, 0o755)

	enUS := []byte(`{
		"greeting": "Hello",
		"welcome": "Welcome, %s!",
		"farewell": "Goodbye",
		"user": {
			"profile": {
				"title": "User Profile"
			}
		},
		"items_count": "You have %d items",
		"multi": "%s has %d items"
	}`)
	_ = os.WriteFile(filepath.Join(tmpDir, "en-US.json"), enUS, 0o644)

	esES := []byte(`{
		"greeting": "Hola",
		"farewell": "Adios"
	}`)
	_ = os.WriteFile(filepath.Join(tmpDir, "es-ES.json"), esES, 0o644)

	return tmpDir
}
Output:
Hello
Example (EmbeddedFS)

Example_embeddedFS demonstrates using embedded filesystem for translations

package main

import (
	"embed"
	"fmt"
	"log"

	"github.com/0verkilll/i18n"
)

//go:embed testdata/locales/*.json
var exampleEmbeddedLocales embed.FS

func main() {
	// Create loader with embedded filesystem
	loader := i18n.NewEmbedFSLoader(exampleEmbeddedLocales, "testdata/locales")

	translator, err := i18n.New(
		i18n.WithLoader(loader),
		i18n.WithDefaultLocale("en-US"),
	)
	if err != nil {
		log.Fatal(err) //nolint:gocritic // Cleanup already done before Fatal
	}

	// Use translations from embedded files
	greeting := translator.Translate("greeting")
	fmt.Println(greeting)

}
Output:
Hello
Example (FallbackChain)

Example_fallbackChain demonstrates locale fallback behavior

package main

import (
	"fmt"
	"log"
	"os"
	"path/filepath"

	"github.com/0verkilll/i18n"
)

func main() {
	tmpDir := setupExampleLocalesWithFallback()
	defer func() { _ = os.RemoveAll(tmpDir) }() //nolint:errcheck // Cleanup in test, error is non-critical

	translator, err := i18n.New(
		i18n.WithFileSystemLoader(tmpDir),
		i18n.WithDefaultLocale("en-US"),
	)
	if err != nil {
		_ = os.RemoveAll(tmpDir) //nolint:errcheck // Best-effort cleanup before Fatal
		log.Fatal(err)           //nolint:gocritic // Cleanup already done before Fatal
	}

	// Switch to Mexican Spanish (has partial translations)
	translator.SetLocale("es-MX")

	// This exists in es-MX
	fmt.Println("greeting:", translator.Translate("greeting"))

	// This doesn't exist in es-MX, falls back to es-ES
	fmt.Println("farewell:", translator.Translate("farewell"))

	// This doesn't exist in es-MX or es-ES, falls back to en-US
	fmt.Println("welcome:", translator.Translate("welcome"))

}

// setupExampleLocalesWithFallback creates example locale files with partial
// translations to demonstrate fallback chain behavior.
func setupExampleLocalesWithFallback() string {
	tmpDir := os.TempDir() + "/i18n-example-fallback"
	_ = os.MkdirAll(tmpDir, 0o755)

	enUS := []byte(`{
		"greeting": "Hello",
		"welcome": "Welcome",
		"farewell": "Goodbye"
	}`)
	_ = os.WriteFile(filepath.Join(tmpDir, "en-US.json"), enUS, 0o644)

	esES := []byte(`{
		"greeting": "Hola",
		"farewell": "Adios"
	}`)
	_ = os.WriteFile(filepath.Join(tmpDir, "es-ES.json"), esES, 0o644)

	esMX := []byte(`{
		"greeting": "Hola (Mexico)"
	}`)
	_ = os.WriteFile(filepath.Join(tmpDir, "es-MX.json"), esMX, 0o644)

	return tmpDir
}
Output:
greeting: Hola (Mexico)
farewell: Adios
welcome: Welcome
Example (HasKey)

Example_hasKey demonstrates checking for key existence

package main

import (
	"fmt"
	"log"
	"os"
	"path/filepath"

	"github.com/0verkilll/i18n"
)

func main() {
	tmpDir := setupExampleLocales()
	defer func() { _ = os.RemoveAll(tmpDir) }() //nolint:errcheck // Cleanup in test, error is non-critical

	translator, err := i18n.New(
		i18n.WithFileSystemLoader(tmpDir),
		i18n.WithDefaultLocale("en-US"),
	)
	if err != nil {
		_ = os.RemoveAll(tmpDir) //nolint:errcheck // Best-effort cleanup before Fatal
		log.Fatal(err)           //nolint:gocritic // Cleanup already done before Fatal
	}

	// Check simple key
	if translator.HasKey("greeting") {
		fmt.Println("greeting exists")
	}

	// Check nested key
	if translator.HasKey("user.profile.title") {
		fmt.Println("user.profile.title exists")
	}

	// Check non-existent key
	if !translator.HasKey("nonexistent") {
		fmt.Println("nonexistent does not exist")
	}

}

// setupExampleLocales creates example locale files for basic translation examples.
func setupExampleLocales() string {
	tmpDir := os.TempDir() + "/i18n-example"
	_ = os.MkdirAll(tmpDir, 0o755)

	enUS := []byte(`{
		"greeting": "Hello",
		"welcome": "Welcome, %s!",
		"farewell": "Goodbye",
		"user": {
			"profile": {
				"title": "User Profile"
			}
		},
		"items_count": "You have %d items",
		"multi": "%s has %d items"
	}`)
	_ = os.WriteFile(filepath.Join(tmpDir, "en-US.json"), enUS, 0o644)

	esES := []byte(`{
		"greeting": "Hola",
		"farewell": "Adios"
	}`)
	_ = os.WriteFile(filepath.Join(tmpDir, "es-ES.json"), esES, 0o644)

	return tmpDir
}
Output:
greeting exists
user.profile.title exists
nonexistent does not exist
Example (LocaleNormalization)

Example_localeNormalization demonstrates automatic locale normalization

package main

import (
	"fmt"
	"log"
	"os"
	"path/filepath"

	"github.com/0verkilll/i18n"
)

func main() {
	tmpDir := setupExampleLocales()
	defer func() { _ = os.RemoveAll(tmpDir) }() //nolint:errcheck // Cleanup in test, error is non-critical

	translator, err := i18n.New(
		i18n.WithFileSystemLoader(tmpDir),
		i18n.WithDefaultLocale("en-US"),
	)
	if err != nil {
		_ = os.RemoveAll(tmpDir) //nolint:errcheck // Best-effort cleanup before Fatal
		log.Fatal(err)           //nolint:gocritic // Cleanup already done before Fatal
	}

	// Underscore is normalized to hyphen
	translator.SetLocale("en_US")
	fmt.Println(translator.GetLocale())

	// Encoding suffix is removed
	translator.SetLocale("es_ES.UTF-8")
	fmt.Println(translator.GetLocale())

	// POSIX locale normalized to en-US
	translator.SetLocale("POSIX")
	fmt.Println(translator.GetLocale())

}

// setupExampleLocales creates example locale files for basic translation examples.
func setupExampleLocales() string {
	tmpDir := os.TempDir() + "/i18n-example"
	_ = os.MkdirAll(tmpDir, 0o755)

	enUS := []byte(`{
		"greeting": "Hello",
		"welcome": "Welcome, %s!",
		"farewell": "Goodbye",
		"user": {
			"profile": {
				"title": "User Profile"
			}
		},
		"items_count": "You have %d items",
		"multi": "%s has %d items"
	}`)
	_ = os.WriteFile(filepath.Join(tmpDir, "en-US.json"), enUS, 0o644)

	esES := []byte(`{
		"greeting": "Hola",
		"farewell": "Adios"
	}`)
	_ = os.WriteFile(filepath.Join(tmpDir, "es-ES.json"), esES, 0o644)

	return tmpDir
}
Output:
en-US
es-ES
en-US
Example (LocaleSwitching)

Example_localeSwitching demonstrates changing locales at runtime

package main

import (
	"fmt"
	"log"
	"os"
	"path/filepath"

	"github.com/0verkilll/i18n"
)

func main() {
	tmpDir := setupExampleLocales()
	defer func() { _ = os.RemoveAll(tmpDir) }() //nolint:errcheck // Cleanup in test, error is non-critical

	translator, err := i18n.New(
		i18n.WithFileSystemLoader(tmpDir),
		i18n.WithDefaultLocale("en-US"),
	)
	if err != nil {
		_ = os.RemoveAll(tmpDir) //nolint:errcheck // Best-effort cleanup before Fatal
		log.Fatal(err)           //nolint:gocritic // Cleanup already done before Fatal
	}

	// English greeting
	fmt.Println(translator.GetLocale()+":", translator.Translate("greeting"))

	// Switch to Spanish
	translator.SetLocale("es-ES")
	fmt.Println(translator.GetLocale()+":", translator.Translate("greeting"))

	// Switch back to English
	translator.SetLocale("en-US")
	fmt.Println(translator.GetLocale()+":", translator.Translate("greeting"))

}

// setupExampleLocales creates example locale files for basic translation examples.
func setupExampleLocales() string {
	tmpDir := os.TempDir() + "/i18n-example"
	_ = os.MkdirAll(tmpDir, 0o755)

	enUS := []byte(`{
		"greeting": "Hello",
		"welcome": "Welcome, %s!",
		"farewell": "Goodbye",
		"user": {
			"profile": {
				"title": "User Profile"
			}
		},
		"items_count": "You have %d items",
		"multi": "%s has %d items"
	}`)
	_ = os.WriteFile(filepath.Join(tmpDir, "en-US.json"), enUS, 0o644)

	esES := []byte(`{
		"greeting": "Hola",
		"farewell": "Adios"
	}`)
	_ = os.WriteFile(filepath.Join(tmpDir, "es-ES.json"), esES, 0o644)

	return tmpDir
}
Output:
en-US: Hello
es-ES: Hola
en-US: Hello
Example (MissingKey)

Example_missingKey demonstrates behavior when a translation key is not found

package main

import (
	"fmt"
	"log"
	"os"
	"path/filepath"

	"github.com/0verkilll/i18n"
)

func main() {
	tmpDir := setupExampleLocales()
	defer func() { _ = os.RemoveAll(tmpDir) }() //nolint:errcheck // Cleanup in test, error is non-critical

	translator, err := i18n.New(
		i18n.WithFileSystemLoader(tmpDir),
		i18n.WithDefaultLocale("en-US"),
	)
	if err != nil {
		_ = os.RemoveAll(tmpDir) //nolint:errcheck // Best-effort cleanup before Fatal
		log.Fatal(err)           //nolint:gocritic // Cleanup already done before Fatal
	}

	// Missing key returns the key itself
	result := translator.Translate("nonexistent.key")
	fmt.Println(result)

	// Check if key exists
	if !translator.HasKey("nonexistent.key") {
		fmt.Println("Key does not exist")
	}

}

// setupExampleLocales creates example locale files for basic translation examples.
func setupExampleLocales() string {
	tmpDir := os.TempDir() + "/i18n-example"
	_ = os.MkdirAll(tmpDir, 0o755)

	enUS := []byte(`{
		"greeting": "Hello",
		"welcome": "Welcome, %s!",
		"farewell": "Goodbye",
		"user": {
			"profile": {
				"title": "User Profile"
			}
		},
		"items_count": "You have %d items",
		"multi": "%s has %d items"
	}`)
	_ = os.WriteFile(filepath.Join(tmpDir, "en-US.json"), enUS, 0o644)

	esES := []byte(`{
		"greeting": "Hola",
		"farewell": "Adios"
	}`)
	_ = os.WriteFile(filepath.Join(tmpDir, "es-ES.json"), esES, 0o644)

	return tmpDir
}
Output:
nonexistent.key
Key does not exist
Example (RegisterParser)

Example_registerParser demonstrates registering a custom parser for a new format.

External modules (e.g., i18n-toml, i18n-yaml) follow this pattern:

  1. Implement TranslationParser
  2. Call RegisterParser in init()
  3. Application code activates via blank import
package main

import (
	"fmt"
	"log"

	"github.com/0verkilll/i18n"
)

func main() {
	// Define a mock TOML parser implementing TranslationParser
	parser := &exampleTOMLParser{}

	// Register it for the .toml extension
	err := i18n.RegisterParser(".toml", parser)
	if err != nil {
		log.Fatal(err)
	}

	// Verify .toml is now registered
	formats := i18n.RegisteredFormats()
	for _, f := range formats {
		if f == ".toml" {
			fmt.Println(".toml is registered")
		}
	}

	// Retrieve it back from the registry
	got, err := i18n.GetParser(".toml")
	if err != nil {
		log.Fatal(err)
	}
	if got != nil {
		fmt.Println("parser retrieved successfully")
	}

}

// exampleTOMLParser is a mock parser demonstrating the external module pattern.
// A real TOML parser would use github.com/BurntSushi/toml or similar.
// External parsers should enforce equivalent size and nesting depth limits
// (see MaxJSONSize and MaxJSONDepth) to maintain security parity.
type exampleTOMLParser struct{}

func (p *exampleTOMLParser) Parse(_ []byte) (map[string]interface{}, error) {
	return map[string]interface{}{"example": "toml-value"}, nil
}
Output:
.toml is registered
parser retrieved successfully
Example (WithArguments)

Example_withArguments demonstrates translation with format string arguments

package main

import (
	"fmt"
	"log"
	"os"
	"path/filepath"

	"github.com/0verkilll/i18n"
)

func main() {
	tmpDir := setupExampleLocales()
	defer func() { _ = os.RemoveAll(tmpDir) }() //nolint:errcheck // Cleanup in test, error is non-critical

	translator, err := i18n.New(
		i18n.WithFileSystemLoader(tmpDir),
		i18n.WithDefaultLocale("en-US"),
	)
	if err != nil {
		_ = os.RemoveAll(tmpDir) //nolint:errcheck // Best-effort cleanup before Fatal
		log.Fatal(err)           //nolint:gocritic // Cleanup already done before Fatal
	}

	// Translation with string argument
	welcome := translator.TranslateWithArgs("welcome", "Alice")
	fmt.Println(welcome)

	// Translation with integer argument
	count := translator.TranslateWithArgs("items_count", 5)
	fmt.Println(count)

	// Translation with multiple arguments
	message := translator.TranslateWithArgs("multi", "Bob", 3)
	fmt.Println(message)

}

// setupExampleLocales creates example locale files for basic translation examples.
func setupExampleLocales() string {
	tmpDir := os.TempDir() + "/i18n-example"
	_ = os.MkdirAll(tmpDir, 0o755)

	enUS := []byte(`{
		"greeting": "Hello",
		"welcome": "Welcome, %s!",
		"farewell": "Goodbye",
		"user": {
			"profile": {
				"title": "User Profile"
			}
		},
		"items_count": "You have %d items",
		"multi": "%s has %d items"
	}`)
	_ = os.WriteFile(filepath.Join(tmpDir, "en-US.json"), enUS, 0o644)

	esES := []byte(`{
		"greeting": "Hola",
		"farewell": "Adios"
	}`)
	_ = os.WriteFile(filepath.Join(tmpDir, "es-ES.json"), esES, 0o644)

	return tmpDir
}
Output:
Welcome, Alice!
You have 5 items
Bob has 3 items
Example (WithRegisteredParser)

Example_withRegisteredParser demonstrates using WithRegisteredParser to configure a Translator with a registry-resolved parser.

package main

import (
	"fmt"
	"log"
	"os"
	"path/filepath"

	"github.com/0verkilll/i18n"
)

func main() {
	tmpDir := setupExampleLocales()
	defer func() { _ = os.RemoveAll(tmpDir) }() //nolint:errcheck // Cleanup in test, error is non-critical

	// Use WithRegisteredParser to pull the built-in JSON parser from the registry
	translator, err := i18n.New(
		i18n.WithFileSystemLoader(tmpDir),
		i18n.WithRegisteredParser(".json"),
		i18n.WithDefaultLocale("en-US"),
	)
	if err != nil {
		_ = os.RemoveAll(tmpDir) //nolint:errcheck // Best-effort cleanup before Fatal
		log.Fatal(err)           //nolint:gocritic // Cleanup already done before Fatal
	}

	fmt.Println(translator.Translate("greeting"))

}

// setupExampleLocales creates example locale files for basic translation examples.
func setupExampleLocales() string {
	tmpDir := os.TempDir() + "/i18n-example"
	_ = os.MkdirAll(tmpDir, 0o755)

	enUS := []byte(`{
		"greeting": "Hello",
		"welcome": "Welcome, %s!",
		"farewell": "Goodbye",
		"user": {
			"profile": {
				"title": "User Profile"
			}
		},
		"items_count": "You have %d items",
		"multi": "%s has %d items"
	}`)
	_ = os.WriteFile(filepath.Join(tmpDir, "en-US.json"), enUS, 0o644)

	esES := []byte(`{
		"greeting": "Hola",
		"farewell": "Adios"
	}`)
	_ = os.WriteFile(filepath.Join(tmpDir, "es-ES.json"), esES, 0o644)

	return tmpDir
}
Output:
Hello

Index

Examples

Constants

View Source
const (
	Zero  = core.Zero
	One   = core.One
	Two   = core.Two
	Few   = core.Few
	Many  = core.Many
	Other = core.Other
)

Plural category constants.

View Source
const (
	Masculine   = core.Masculine
	Feminine    = core.Feminine
	Neuter      = core.Neuter
	GenderOther = core.GenderOther
)

Gender category constants.

View Source
const (
	LevelDebug = core.LevelDebug
	LevelInfo  = core.LevelInfo
	LevelWarn  = core.LevelWarn
	LevelError = core.LevelError
	LevelFatal = core.LevelFatal
)

Log level constants.

View Source
const (
	MaxLocaleLength      = core.MaxLocaleLength
	MaxKeyLength         = core.MaxKeyLength
	MaxKeyDepth          = core.MaxKeyDepth
	MaxOutputLength      = core.MaxOutputLength
	MaxJSONSize          = core.MaxJSONSize
	MaxJSONDepth         = core.MaxJSONDepth
	MaxPrefixLength      = core.MaxPrefixLength
	MaxFormatPrecision   = core.MaxFormatPrecision
	MaxRegisteredLocales = engine.MaxRegisteredLocales
)

Validation and security limit constants.

Variables

View Source
var (
	NewErrInvalidLocale = core.NewErrInvalidLocale
	NewErrInvalidKey    = core.NewErrInvalidKey
	NewErrKeyNotFound   = core.NewErrKeyNotFound
	NewErrInvalidFormat = core.NewErrInvalidFormat
	NewErrPathTraversal = core.NewErrPathTraversal
	NewErrUnknownFormat = core.NewErrUnknownFormat
)

Error constructors.

View Source
var (
	// ErrRegistryFull is returned when the in-process locale registry already
	// holds MaxRegisteredLocales distinct entries and a new locale is being added.
	ErrRegistryFull = engine.ErrRegistryFull
)

Sentinel errors re-exported from the engine package.

Functions

func DetectLocale

func DetectLocale() string

DetectLocale detects the system locale using default detection.

func DiscoverLocales

func DiscoverLocales(fs embed.FS, basePath string) []string

DiscoverLocales reads an embed.FS directory and returns locale codes from JSON filenames. Replaces per-package GetSupportedLocales reimplementations.

func EncodeBinary

func EncodeBinary(translations map[string]string) ([]byte, error)

EncodeBinary converts a flat map of translations to the compact binary format. Keys must be in dot notation (use FlattenKeys to convert nested maps). This function is intended for build-time tooling to convert JSON locale files.

func FlattenKeys

func FlattenKeys(nested map[string]interface{}) map[string]string

FlattenKeys converts a nested map to a flat dot-notation map. For example, {"error": {"required": "..."}} becomes {"error.required": "..."}. Use this together with EncodeBinary to convert JSON translation data.

func GetSupportedLocales

func GetSupportedLocales(loader TranslationLoader, locales ...string) []string

GetSupportedLocales probes a loader for supported locale codes.

func NormalizeLocale

func NormalizeLocale(locale string) string

NormalizeLocale converts a locale string to BCP 47 format.

func RegisterParser

func RegisterParser(ext string, p TranslationParser) error

RegisterParser registers a parser for a file extension in the default registry.

func RegisteredFormats

func RegisteredFormats() []string

RegisteredFormats returns all registered file extensions.

func RegisteredLocales

func RegisteredLocales() []string

RegisteredLocales returns all locale codes in the registry.

func SanitizeOutput

func SanitizeOutput(s string) string

SanitizeOutput removes potentially dangerous characters from translation output.

func SetLogger

func SetLogger(l Logger)

SetLogger sets the global logger.

func ValidateFormatString

func ValidateFormatString(format string, argCount int) error

ValidateFormatString validates a format string for safety.

func ValidateKey

func ValidateKey(key string) error

ValidateKey validates a translation key.

func ValidateLocale

func ValidateLocale(locale string) error

ValidateLocale validates a locale code against BCP 47 format.

Types

type AcceptLanguageDetector

type AcceptLanguageDetector = engine.AcceptLanguageDetector

AcceptLanguageDetector parses Accept-Language headers.

func NewAcceptLanguageDetector

func NewAcceptLanguageDetector(header string) *AcceptLanguageDetector

NewAcceptLanguageDetector creates a detector from an Accept-Language header.

type BinaryParser

type BinaryParser = engine.BinaryParser

BinaryParser parses compact binary translation files.

func NewBinaryParser

func NewBinaryParser() *BinaryParser

NewBinaryParser creates a new compact binary translation file parser. The binary format reduces both data size (~40% smaller than JSON) and parser code size. Use EncodeBinary and FlattenKeys to produce binary translation data at build time.

To register for automatic use with file-based loaders:

i18n.RegisterParser(".bin", i18n.NewBinaryParser())

type BrowserDetector

type BrowserDetector = engine.BrowserDetector

BrowserDetector reads the browser's preferred language via navigator.language in js/wasm builds. On non-WASM platforms, Detect returns "".

func NewBrowserDetector

func NewBrowserDetector() *BrowserDetector

NewBrowserDetector creates a detector that reads navigator.language in js/wasm builds. On non-WASM platforms, Detect returns "". Use in a ChainDetector so a fallback detector provides the locale outside browser environments.

type Cacher

type Cacher = core.Cacher

Cacher abstracts resolved-translation caching for the Translator.

type ChainDetector

type ChainDetector = engine.ChainDetector

ChainDetector chains multiple locale detectors.

func NewChainDetector

func NewChainDetector(detectors ...LocaleDetector) *ChainDetector

NewChainDetector creates a detector that chains multiple detectors.

type ContextTranslator

type ContextTranslator = engine.ContextTranslator

ContextTranslator wraps a Translator and passes a context.Context to the logger for trace correlation and observability integration.

type DefaultFallbackChainer

type DefaultFallbackChainer = engine.DefaultFallbackChainer

DefaultFallbackChainer generates locale fallback chains.

func NewDefaultFallbackChainer

func NewDefaultFallbackChainer() *DefaultFallbackChainer

NewDefaultFallbackChainer creates a new locale fallback chainer.

type DefaultKeyResolver

type DefaultKeyResolver = engine.DefaultKeyResolver

DefaultKeyResolver resolves translation keys using dot notation.

func NewDefaultKeyResolver

func NewDefaultKeyResolver() *DefaultKeyResolver

NewDefaultKeyResolver creates a new dot-notation key resolver.

type DefaultLocaleDetector

type DefaultLocaleDetector = engine.DefaultLocaleDetector

DefaultLocaleDetector detects system locale from environment variables.

func NewDefaultLocaleDetector

func NewDefaultLocaleDetector(env EnvProvider) *DefaultLocaleDetector

NewDefaultLocaleDetector creates a locale detector using environment variables.

type DefaultPluralResolver

type DefaultPluralResolver = engine.DefaultPluralResolver

DefaultPluralResolver resolves CLDR plural categories.

func NewDefaultPluralResolver

func NewDefaultPluralResolver() *DefaultPluralResolver

NewDefaultPluralResolver creates a CLDR plural resolver for 36 languages.

type Detector

type Detector = core.Detector

Detector retrieves the system locale from environment variables.

type EmbedFSLoader

type EmbedFSLoader = engine.EmbedFSLoader

EmbedFSLoader loads translations from an embedded filesystem.

func NewEmbedFSLoader

func NewEmbedFSLoader(fs embed.FS, basePath string, opts ...LoaderOption) *EmbedFSLoader

NewEmbedFSLoader creates a loader for embedded filesystem translations.

type EnvProvider

type EnvProvider = core.EnvProvider

EnvProvider abstracts environment variable access for locale detection.

type ErrInvalidFormat

type ErrInvalidFormat = core.ErrInvalidFormat

ErrInvalidFormat indicates a data format error.

type ErrInvalidKey

type ErrInvalidKey = core.ErrInvalidKey

ErrInvalidKey indicates a translation key failed validation.

type ErrInvalidLocale

type ErrInvalidLocale = core.ErrInvalidLocale

ErrInvalidLocale indicates a locale code failed validation.

type ErrKeyNotFound

type ErrKeyNotFound = core.ErrKeyNotFound

ErrKeyNotFound indicates a translation key was not found.

type ErrPathTraversal

type ErrPathTraversal = core.ErrPathTraversal

ErrPathTraversal indicates a path traversal attack was detected.

type ErrUnknownFormat

type ErrUnknownFormat = core.ErrUnknownFormat

ErrUnknownFormat indicates an unregistered file format.

type FallbackChainer

type FallbackChainer = core.FallbackChainer

FallbackChainer generates fallback locale chains for graceful degradation.

type FileSystemLoader

type FileSystemLoader = engine.FileSystemLoader

FileSystemLoader loads translations from the local filesystem.

func NewFileSystemLoader

func NewFileSystemLoader(baseDir string, opts ...LoaderOption) *FileSystemLoader

NewFileSystemLoader creates a loader for filesystem-based translations.

type FormattedTranslator

type FormattedTranslator = core.FormattedTranslator

FormattedTranslator provides formatted translation lookup with arguments.

type GenderCategory

type GenderCategory = core.GenderCategory

GenderCategory represents grammatical gender categories.

type GenderTranslator

type GenderTranslator = core.GenderTranslator

GenderTranslator provides gender-aware translation lookup.

type JSONParser

type JSONParser = engine.JSONParser

JSONParser parses JSON translation files.

func NewJSONParser

func NewJSONParser() *JSONParser

NewJSONParser creates a new JSON translation file parser.

type KeyChecker

type KeyChecker = core.KeyChecker

KeyChecker checks whether a translation key exists.

type KeyResolver

type KeyResolver = core.KeyResolver

KeyResolver handles nested key resolution using dot notation.

type LeveledLogger

type LeveledLogger = core.LeveledLogger

LeveledLogger extends Logger with level-checking capabilities.

type LoaderOption

type LoaderOption = engine.LoaderOption

LoaderOption configures a loader via the functional options pattern.

func WithExtension

func WithExtension(ext string) LoaderOption

WithExtension sets the file extension used by a loader.

type LocaleDetector

type LocaleDetector = core.LocaleDetector

LocaleDetector detects and normalizes system locale information.

type LocaleGetter

type LocaleGetter = core.LocaleGetter

LocaleGetter retrieves the active locale.

type LocaleSet

type LocaleSet[T any] = engine.LocaleSet[T]

LocaleSet holds named locale structs for lookup by locale code.

func NewLocaleSet

func NewLocaleSet[T any](fallbackCode string, fallback *T) *LocaleSet[T]

NewLocaleSet creates a LocaleSet with a fallback locale for struct-based translations.

type LocaleSetter

type LocaleSetter = core.LocaleSetter

LocaleSetter changes the active locale for translation lookups.

type LogLevel

type LogLevel = core.LogLevel

LogLevel represents logging severity levels.

type Logger

type Logger = core.Logger

Logger provides structured logging.

func GetLogger

func GetLogger() Logger

GetLogger returns the global logger.

type MapCache

type MapCache = engine.MapCache

MapCache is a thread-safe, in-memory translation cache with optional LRU eviction.

func NewMapCache

func NewMapCache() *MapCache

NewMapCache creates a MapCache with no size limit.

func NewMapCacheWithLimit

func NewMapCacheWithLimit(maxEntries int) *MapCache

NewMapCacheWithLimit creates a MapCache with LRU eviction.

type Namespace

type Namespace = engine.Namespace

Namespace automatically prefixes translation keys with a package name.

Example

ExampleNamespace demonstrates Namespace key prefixing for package-scoped translations.

package main

import (
	"fmt"
	"log"
	"os"
	"path/filepath"

	"github.com/0verkilll/i18n"
)

func main() {
	tmpDir := setupExampleNamespaceLocales()
	defer func() { _ = os.RemoveAll(tmpDir) }() //nolint:errcheck // Cleanup in test, error is non-critical

	translator, err := i18n.New(
		i18n.WithFileSystemLoader(tmpDir),
		i18n.WithDefaultLocale("en-US"),
	)
	if err != nil {
		_ = os.RemoveAll(tmpDir) //nolint:errcheck // Best-effort cleanup before Fatal
		log.Fatal(err)           //nolint:gocritic // Cleanup already done before Fatal
	}

	// Create a namespace for "mypackage"
	ns, err := i18n.NewNamespace("mypackage", translator)
	if err != nil {
		_ = os.RemoveAll(tmpDir) //nolint:errcheck // Best-effort cleanup before Fatal
		log.Fatal(err)           //nolint:gocritic // Cleanup already done before Fatal
	}

	// T translates with the namespace prefix
	fmt.Println(ns.T("greeting"))

	// TF translates with format arguments
	fmt.Println(ns.TF("welcome", "Alice"))

	// TD returns default if key not found
	fmt.Println(ns.TD("missing", "Default Value"))

	// Has checks key existence
	fmt.Println(ns.Has("greeting"))

	// Key returns the full namespaced key
	fmt.Println(ns.Key("greeting"))

}

// setupExampleNamespaceLocales creates locale files with namespace-prefixed keys
// for Namespace and PackageTranslator examples.
func setupExampleNamespaceLocales() string {
	tmpDir := os.TempDir() + "/i18n-example-ns"
	_ = os.MkdirAll(tmpDir, 0o755)

	enUS := []byte(`{
		"mypackage": {
			"greeting": "Hello from mypackage",
			"welcome": "Welcome, %s!"
		}
	}`)
	_ = os.WriteFile(filepath.Join(tmpDir, "en-US.json"), enUS, 0o644)

	return tmpDir
}
Output:
Hello from mypackage
Welcome, Alice!
Default Value
true
mypackage.greeting

func NewNamespace

func NewNamespace(prefix string, t TranslatorProvider) (*Namespace, error)

NewNamespace creates a Namespace that prefixes all keys with the given prefix.

type NopLogger

type NopLogger = engine.NopLogger

NopLogger is a no-op logger implementation.

type Normalizer

type Normalizer = core.Normalizer

Normalizer converts locale strings to BCP 47 format.

type Option

type Option = engine.Option

Option is a functional option for configuring the Translator.

func WithCache

func WithCache(cache Cacher) Option

WithCache sets a Cacher for resolved translations.

func WithDefaultLocale

func WithDefaultLocale(locale string) Option

WithDefaultLocale sets the default locale.

func WithFallbackChainer

func WithFallbackChainer(chainer FallbackChainer) Option

WithFallbackChainer sets a custom FallbackChainer.

func WithFileSystemLoader

func WithFileSystemLoader(baseDir string) Option

WithFileSystemLoader creates a FileSystemLoader for the given base directory.

func WithLoader

func WithLoader(loader TranslationLoader) Option

WithLoader sets a custom TranslationLoader.

func WithLocaleDetector

func WithLocaleDetector(detector LocaleDetector) Option

WithLocaleDetector sets a custom LocaleDetector.

func WithLogger

func WithLogger(l Logger) Option

WithLogger sets a custom Logger.

func WithParser

func WithParser(parser TranslationParser) Option

WithParser sets a custom TranslationParser.

func WithPluralResolver

func WithPluralResolver(resolver PluralResolver) Option

WithPluralResolver sets a custom PluralResolver.

func WithRegisteredParser

func WithRegisteredParser(ext string) Option

WithRegisteredParser selects a parser from the registry by file extension.

func WithRegistryLoader

func WithRegistryLoader() Option

WithRegistryLoader creates a RegistryLoader for build-tag locale data.

func WithResolver

func WithResolver(resolver KeyResolver) Option

WithResolver sets a custom KeyResolver.

type PackageOption

type PackageOption = engine.PackageOption

PackageOption is a functional option for configuring a PackageTranslator.

func WithDefaults

func WithDefaults(defaults map[string]string) PackageOption

WithDefaults sets hardcoded fallback strings on a PackageTranslator.

func WithTranslator

func WithTranslator(t TranslatorProvider) PackageOption

WithTranslator sets the initial translator on a PackageTranslator.

type PackageTranslator

type PackageTranslator = engine.PackageTranslator

PackageTranslator provides per-package translation with namespace scoping.

Example

ExamplePackageTranslator demonstrates per-package translation with defaults.

package main

import (
	"fmt"
	"log"
	"os"
	"path/filepath"

	"github.com/0verkilll/i18n"
)

func main() {
	tmpDir := setupExampleNamespaceLocales()
	defer func() { _ = os.RemoveAll(tmpDir) }() //nolint:errcheck // Cleanup in test, error is non-critical

	// Create a PackageTranslator with hardcoded defaults
	pt := i18n.NewPackageTranslator("mypackage", i18n.WithDefaults(map[string]string{
		"greeting": "Hi (default)",
		"welcome":  "Welcome, %s! (default)",
	}))

	// Before wiring up a translator, defaults are used
	fmt.Println(pt.T("greeting"))

	// Create and set the translator
	translator, err := i18n.New(
		i18n.WithFileSystemLoader(tmpDir),
		i18n.WithDefaultLocale("en-US"),
	)
	if err != nil {
		_ = os.RemoveAll(tmpDir) //nolint:errcheck // Best-effort cleanup before Fatal
		log.Fatal(err)           //nolint:gocritic // Cleanup already done before Fatal
	}
	pt.SetTranslator(translator)

	// Now translations come from locale files
	fmt.Println(pt.T("greeting"))
	fmt.Println(pt.TF("welcome", "Bob"))

}

// setupExampleNamespaceLocales creates locale files with namespace-prefixed keys
// for Namespace and PackageTranslator examples.
func setupExampleNamespaceLocales() string {
	tmpDir := os.TempDir() + "/i18n-example-ns"
	_ = os.MkdirAll(tmpDir, 0o755)

	enUS := []byte(`{
		"mypackage": {
			"greeting": "Hello from mypackage",
			"welcome": "Welcome, %s!"
		}
	}`)
	_ = os.WriteFile(filepath.Join(tmpDir, "en-US.json"), enUS, 0o644)

	return tmpDir
}
Output:
Hi (default)
Hello from mypackage
Welcome, Bob!

func NewPackageTranslator

func NewPackageTranslator(namespace string, opts ...PackageOption) *PackageTranslator

NewPackageTranslator creates a PackageTranslator with namespace scoping.

func NewPackageTranslatorWithFS

func NewPackageTranslatorWithFS(namespace string, fs embed.FS, basePath string, opts ...PackageOption) *PackageTranslator

NewPackageTranslatorWithFS creates a fully-configured PackageTranslator backed by an embedded filesystem. This replaces ~300 lines of per-package i18n.go boilerplate with a single constructor call.

Example — replaces the entire per-package i18n.go:

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

var I18n = i18n.NewPackageTranslatorWithFS("filesystem", localeFS, "locales",
    i18n.WithDefaults(map[string]string{
        "error.empty_path": "path cannot be empty",
    }),
)

type PluralCategory

type PluralCategory = core.PluralCategory

PluralCategory represents CLDR plural categories.

type PluralResolver

type PluralResolver = core.PluralResolver

PluralResolver resolves plural categories for a given locale and count.

type PluralTranslator

type PluralTranslator = core.PluralTranslator

PluralTranslator provides count-dependent translation lookup.

type Registry

type Registry = engine.Registry

Registry stores registered translation parsers.

func NewRegistry

func NewRegistry() *Registry

NewRegistry creates a new parser registry.

type RegistryLoader

type RegistryLoader = engine.RegistryLoader

RegistryLoader loads translations from the locale data registry.

Example

ExampleRegistryLoader demonstrates using RegistryLoader with build-tag-selected locales.

package main

import (
	"embed"
	"fmt"
	"log"

	"github.com/0verkilll/i18n"
)

//go:embed testdata/locales/*.json
var exampleEmbeddedLocales embed.FS

func main() {
	// RegistryLoader reads from the global locale registry, which is populated
	// by build-tag-selected locale files at init time. For this example, we
	// use the embedded testdata loader as a stand-in, since the registry
	// requires -tags locale_all to be populated.

	loader := i18n.NewEmbedFSLoader(exampleEmbeddedLocales, "testdata/locales")

	translator, err := i18n.New(
		i18n.WithLoader(loader),
		i18n.WithDefaultLocale("en-US"),
	)
	if err != nil {
		log.Fatal(err) //nolint:gocritic // Fatal terminates the program
	}

	// Translate using the loaded locale data
	fmt.Println(translator.Translate("greeting"))
	fmt.Println(translator.Translate("farewell"))

	// RegisteredLocales shows which locales are in the build-tag registry
	// (may be empty without -tags locale_all)
	fmt.Println("registry available:", i18n.NewRegistryLoader() != nil)

}
Output:
Hello
Goodbye
registry available: true

func NewRegistryLoader

func NewRegistryLoader() *RegistryLoader

NewRegistryLoader creates a loader for build-tag locale data.

type StaticDetector

type StaticDetector = engine.StaticDetector

StaticDetector returns a fixed locale.

func NewStaticDetector

func NewStaticDetector(locale string) *StaticDetector

NewStaticDetector creates a detector that returns a fixed locale.

type StructTranslator

type StructTranslator[T any] = engine.StructTranslator[T]

StructTranslator provides zero-cost translation lookups using Go struct field access (0.25 ns) instead of map lookups (25 ns). Locale switching is an atomic pointer swap — safe from any goroutine with zero locking overhead.

Define a struct with string fields for each translation key, create one instance per locale with build tags, and use StructTranslator to switch:

type Messages struct {
    Greeting string
    Farewell string
}

//go:build locale_en_us || locale_all
var enUS = Messages{Greeting: "Hello", Farewell: "Goodbye"}

var Msg = i18n.NewStructTranslator(&enUS)

// Read (0.25 ns per lookup):
fmt.Println(Msg.Get().Greeting)

// Switch locale atomically:
Msg.Set(&esES)

func NewStructTranslator

func NewStructTranslator[T any](initial *T) *StructTranslator[T]

NewStructTranslator creates a StructTranslator with the given initial locale.

type TranslationLoader

type TranslationLoader = core.TranslationLoader

TranslationLoader abstracts loading translation data from various sources.

type TranslationLookup

type TranslationLookup = core.TranslationLookup

TranslationLookup provides single-key translation lookup.

type TranslationParser

type TranslationParser = core.TranslationParser

TranslationParser abstracts parsing translation data formats.

func GetParser

func GetParser(ext string) (TranslationParser, error)

GetParser retrieves a parser for a file extension from the default registry.

type Translator

type Translator = engine.Translator

Translator is the main type for translation operations.

func New

func New(opts ...Option) (*Translator, error)

New creates a new Translator with the given options.

func NewWithFS

func NewWithFS(baseDir, defaultLocale string, opts ...Option) (*Translator, error)

NewWithFS creates a Translator that loads translations from the filesystem. This is a convenience wrapper around New with WithFileSystemLoader and WithDefaultLocale pre-applied. Additional options can be provided to customize other components.

func NewWithRegistry

func NewWithRegistry(defaultLocale string, opts ...Option) (*Translator, error)

NewWithRegistry creates a Translator that loads translations from build-tag locale data registered at init time. This is a convenience wrapper around New with WithRegistryLoader and WithDefaultLocale pre-applied. Additional options can be provided to customize other components.

type TranslatorProvider

type TranslatorProvider = core.TranslatorProvider

TranslatorProvider defines the complete interface for translation services.

Directories

Path Synopsis
cmd
i18ngen command
Command i18ngen extracts translation keys from Go source files, generates locale file templates, diffs extracted keys against existing locale files, and scaffolds per-package i18n.go integration files.
Command i18ngen extracts translation keys from Go source files, generates locale file templates, diffs extracted keys against existing locale files, and scaffolds per-package i18n.go integration files.
i18nlint command
Command i18nlint statically analyzes Go source files and JSON translation files to report missing keys, unused keys, format string argument mismatches, and namespace violations.
Command i18nlint statically analyzes Go source files and JSON translation files to report missing keys, unused keys, format string argument mismatches, and namespace violations.
i18nlint/internal/analyzer
Package analyzer provides static analysis for i18n translation key usage.
Package analyzer provides static analysis for i18n translation key usage.
internal
localedata
Package localedata holds build-tag-selected example locale data.
Package localedata holds build-tag-selected example locale data.
Package middleware provides net/http middleware for locale detection and translator injection.
Package middleware provides net/http middleware for locale detection and translator injection.

Jump to

Keyboard shortcuts

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