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 ¶
- TranslationLookup: Simple key-to-string translation.
- FormattedTranslator: Translation with format arguments.
- KeyChecker: Checks whether a key exists.
- LocaleSetter / LocaleGetter: Locale mutation and query.
- PluralTranslator: CLDR plural-aware translation (zero/one/two/few/many/other).
- GenderTranslator: Gender-aware translation (masculine/feminine/other).
- TranslationLoader: Loads translation data (FileSystemLoader, EmbedFSLoader, RegistryLoader).
- TranslationParser: Parses translation data (JSONParser; extensible via Registry).
- KeyResolver: Resolves nested dot-notation keys.
- LocaleDetector: Detects locale (DefaultLocaleDetector, AcceptLanguageDetector, BrowserDetector, StaticDetector, ChainDetector).
- FallbackChainer: Generates locale fallback chains.
- PluralResolver: Determines CLDR plural categories for 30+ languages.
- Cacher: Caches resolved translations.
- Logger / LeveledLogger: Structured logging with levels and context.
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:
- ErrInvalidLocale: Invalid locale format or path traversal attempt
- ErrInvalidKey: Invalid translation key format
- ErrKeyNotFound: Translation key does not exist
- ErrInvalidFormat: Invalid file format or parsing error
- ErrPathTraversal: Path traversal attack detected
- ErrUnknownFormat: No parser registered for a file extension
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:
- Implement TranslationParser
- Call RegisterParser in init()
- 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 ¶
- Constants
- Variables
- func DetectLocale() string
- func DiscoverLocales(fs embed.FS, basePath string) []string
- func EncodeBinary(translations map[string]string) ([]byte, error)
- func FlattenKeys(nested map[string]interface{}) map[string]string
- func GetSupportedLocales(loader TranslationLoader, locales ...string) []string
- func NormalizeLocale(locale string) string
- func RegisterParser(ext string, p TranslationParser) error
- func RegisteredFormats() []string
- func RegisteredLocales() []string
- func SanitizeOutput(s string) string
- func SetLogger(l Logger)
- func ValidateFormatString(format string, argCount int) error
- func ValidateKey(key string) error
- func ValidateLocale(locale string) error
- type AcceptLanguageDetector
- type BinaryParser
- type BrowserDetector
- type Cacher
- type ChainDetector
- type ContextTranslator
- type DefaultFallbackChainer
- type DefaultKeyResolver
- type DefaultLocaleDetector
- type DefaultPluralResolver
- type Detector
- type EmbedFSLoader
- type EnvProvider
- type ErrInvalidFormat
- type ErrInvalidKey
- type ErrInvalidLocale
- type ErrKeyNotFound
- type ErrPathTraversal
- type ErrUnknownFormat
- type FallbackChainer
- type FileSystemLoader
- type FormattedTranslator
- type GenderCategory
- type GenderTranslator
- type JSONParser
- type KeyChecker
- type KeyResolver
- type LeveledLogger
- type LoaderOption
- type LocaleDetector
- type LocaleGetter
- type LocaleSet
- type LocaleSetter
- type LogLevel
- type Logger
- type MapCache
- type Namespace
- type NopLogger
- type Normalizer
- type Option
- func WithCache(cache Cacher) Option
- func WithDefaultLocale(locale string) Option
- func WithFallbackChainer(chainer FallbackChainer) Option
- func WithFileSystemLoader(baseDir string) Option
- func WithLoader(loader TranslationLoader) Option
- func WithLocaleDetector(detector LocaleDetector) Option
- func WithLogger(l Logger) Option
- func WithParser(parser TranslationParser) Option
- func WithPluralResolver(resolver PluralResolver) Option
- func WithRegisteredParser(ext string) Option
- func WithRegistryLoader() Option
- func WithResolver(resolver KeyResolver) Option
- type PackageOption
- type PackageTranslator
- type PluralCategory
- type PluralResolver
- type PluralTranslator
- type Registry
- type RegistryLoader
- type StaticDetector
- type StructTranslator
- type TranslationLoader
- type TranslationLookup
- type TranslationParser
- type Translator
- type TranslatorProvider
Examples ¶
Constants ¶
const ( Zero = core.Zero One = core.One Two = core.Two Few = core.Few Many = core.Many Other = core.Other )
Plural category constants.
const ( Masculine = core.Masculine Feminine = core.Feminine Neuter = core.Neuter GenderOther = core.GenderOther )
Gender category constants.
const ( LevelDebug = core.LevelDebug LevelInfo = core.LevelInfo LevelWarn = core.LevelWarn LevelError = core.LevelError LevelFatal = core.LevelFatal )
Log level constants.
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 ¶
var ( NewErrInvalidLocale = core.NewErrInvalidLocale NewErrInvalidKey = core.NewErrInvalidKey NewErrKeyNotFound = core.NewErrKeyNotFound NewErrInvalidFormat = core.NewErrInvalidFormat NewErrPathTraversal = core.NewErrPathTraversal NewErrUnknownFormat = core.NewErrUnknownFormat )
Error constructors.
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 ¶
DiscoverLocales reads an embed.FS directory and returns locale codes from JSON filenames. Replaces per-package GetSupportedLocales reimplementations.
func EncodeBinary ¶
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 ¶
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 ¶
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 ¶
SanitizeOutput removes potentially dangerous characters from translation output.
func ValidateFormatString ¶
ValidateFormatString validates a format string for safety.
func ValidateLocale ¶
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 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 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 LocaleSet ¶
LocaleSet holds named locale structs for lookup by locale code.
func NewLocaleSet ¶
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 MapCache ¶
MapCache is a thread-safe, in-memory translation cache with optional LRU eviction.
func NewMapCacheWithLimit ¶
NewMapCacheWithLimit creates a MapCache with LRU eviction.
type 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 Normalizer ¶
type Normalizer = core.Normalizer
Normalizer converts locale strings to BCP 47 format.
type Option ¶
Option is a functional option for configuring the Translator.
func WithDefaultLocale ¶
WithDefaultLocale sets the default locale.
func WithFallbackChainer ¶
func WithFallbackChainer(chainer FallbackChainer) Option
WithFallbackChainer sets a custom FallbackChainer.
func WithFileSystemLoader ¶
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 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 ¶
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 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. |