engine

package
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: 15 Imported by: 0

Documentation

Index

Constants

View Source
const MaxRegisteredLocales = 256

MaxRegisteredLocales caps the total number of locales that may be held in the global registry simultaneously. It exists to prevent unbounded growth of the in-memory locale table, which could otherwise be exploited by a component that calls registerLocale in a loop (or by a misconfigured dynamic registration path).

The limit is intentionally generous — CLDR currently covers roughly 200 locales — but finite.

Variables

View Source
var ErrRegistryFull = errors.New("locale registry is full (MaxRegisteredLocales reached)")

ErrRegistryFull is returned by registerLocale when the registry already holds MaxRegisteredLocales distinct entries and the caller is attempting to add a NEW locale (replacing an existing entry is always permitted).

Functions

func DetectLocale

func DetectLocale() string

DetectLocale creates a temporary DefaultLocaleDetector and returns the detected system locale. This eliminates per-package DetectLocale duplication.

func DiscoverLocales

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

DiscoverLocales reads an embed.FS directory and returns the locale codes found based on JSON filenames. This eliminates the per-package GetSupportedLocales reimplementation (~30 packages do this identically).

Example:

//go:embed locales/*.json
var localeFS embed.FS
locales := i18n.DiscoverLocales(localeFS, "locales")
// returns ["en-US", "es-ES", "fr-FR"]

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 and no longer than 255 bytes. Values must be no longer than 65535 bytes. The entry count must not exceed 65535.

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": "..."}. Non-string leaf values are converted using fmt.Sprint.

func GetLogger

func GetLogger() core.Logger

GetLogger returns the package-level logger. By default, this returns NopLogger which discards all log messages.

func GetParser

func GetParser(ext string) (core.TranslationParser, error)

GetParser returns the core.TranslationParser registered for the given extension in the default package-level registry. See Registry.GetParser for details.

func GetSupportedLocales

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

GetSupportedLocales probes a core.TranslationLoader with the given candidate locale codes and returns those for which Load succeeds without error. Returns an empty slice (never nil) if no candidates are supported.

func NormalizeLocale

func NormalizeLocale(locale string) string

NormalizeLocale converts a locale string to BCP 47 format. It handles encoding suffix removal (e.g., .UTF-8), underscore-to-hyphen conversion, case normalization, POSIX/C locale mapping, and language-only codes mapped to their primary regional variant.

All LocaleDetector implementations delegate their Normalize method to this shared function to avoid duplicating normalization logic.

func RegisterParser

func RegisterParser(ext string, p core.TranslationParser) error

RegisterParser registers a core.TranslationParser for the given file extension in the default package-level registry. See Registry.RegisterParser for details.

func RegisteredFormats

func RegisteredFormats() []string

RegisteredFormats returns a sorted slice of all registered file extensions from the default package-level registry. See Registry.RegisteredFormats for details.

func RegisteredLocales

func RegisteredLocales() []string

RegisteredLocales returns a sorted slice of all locale codes currently in the global registry. The returned slice is safe to modify; it does not share memory with the registry internals.

func SetLogger

func SetLogger(l core.Logger)

SetLogger sets the package-level logger. If l is nil, the logger is reset to NopLogger (silent operation). This logger will be used by all Translator instances that don't have a custom logger configured via WithLogger option.

The Logger interface is compatible with github.com/0verkilll/logger.Logger, so you can pass any implementation from that package directly.

Types

type AcceptLanguageDetector

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

AcceptLanguageDetector is a core.LocaleDetector that parses an HTTP Accept-Language header per RFC 7231 Section 5.3.5 and returns the highest-priority language tag.

func NewAcceptLanguageDetector

func NewAcceptLanguageDetector(header string) *AcceptLanguageDetector

NewAcceptLanguageDetector creates a new AcceptLanguageDetector that parses the given raw Accept-Language header value. The header is parsed lazily when Detect is called.

Example usage in an HTTP handler:

func handler(w http.ResponseWriter, r *http.Request) {
    detector := engine.NewAcceptLanguageDetector(r.Header.Get("Accept-Language"))
    translator, _ := engine.New(
        engine.WithFileSystemLoader("locales"),
        engine.WithLocaleDetector(detector),
    )
    // translator will use the browser's preferred language
}

func (*AcceptLanguageDetector) Detect

func (d *AcceptLanguageDetector) Detect() string

Detect parses the Accept-Language header and returns the highest-priority language tag after normalization. Returns "" if the header is empty, exceeds the length limit, contains only wildcards, or is unparseable.

func (*AcceptLanguageDetector) Normalize

func (d *AcceptLanguageDetector) Normalize(locale string) string

Normalize converts a locale string to BCP 47 format using the shared normalization logic.

type BinaryParser

type BinaryParser struct{}

BinaryParser parses translations from the compact binary format. The binary format eliminates JSON syntax overhead (quotes, braces, colons) saving approximately 40% on translation data size. The parser implementation is minimal compared to the full JSON parser, reducing compiled binary size.

Binary format specification (version 1):

Header:  [0x69][0x31][version:1][entry_count:2 big-endian]
Entries: [key_len:1][key:N][val_len:2 big-endian][value:N]...

Keys are stored in pre-flattened dot notation (e.g., "error.validation.required"). Values are always UTF-8 strings. The parser reconstructs the nested map structure expected by the KeyResolver.

func NewBinaryParser

func NewBinaryParser() *BinaryParser

NewBinaryParser creates a new BinaryParser for the compact binary translation format.

func (*BinaryParser) Parse

func (p *BinaryParser) Parse(data []byte) (map[string]interface{}, error)

Parse decodes binary translation data into a nested map compatible with the KeyResolver interface. It validates magic bytes, version, and enforces core.MaxKeyCount. Keys in dot notation are unflattened into nested maps.

type BrowserDetector

type BrowserDetector struct{}

BrowserDetector is a core.LocaleDetector that reads the user's preferred language from the browser via navigator.language / navigator.languages.

On non-WASM builds Detect always returns "". The real implementation is in locale_browser_js.go and is compiled only when targeting js/wasm.

func NewBrowserDetector

func NewBrowserDetector() *BrowserDetector

NewBrowserDetector creates a new BrowserDetector. On non-WASM builds, Detect always returns "". Use this in a ChainDetector so that a fallback detector provides the locale when not running in a browser.

Example usage:

chain := engine.NewChainDetector(
    engine.NewBrowserDetector(),
    engine.NewDefaultLocaleDetector(nil),
)

func (*BrowserDetector) Detect

func (d *BrowserDetector) Detect() string

Detect returns "" on non-WASM builds.

func (*BrowserDetector) Normalize

func (d *BrowserDetector) Normalize(locale string) string

Normalize converts a locale string to BCP 47 format using the shared normalization logic.

type ChainDetector

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

ChainDetector is a core.LocaleDetector that composes multiple detectors in priority order. It iterates through its detectors and returns the first non-empty result. If all detectors return "", it falls back to "en-US".

func NewChainDetector

func NewChainDetector(detectors ...core.LocaleDetector) *ChainDetector

NewChainDetector creates a new ChainDetector that iterates the given detectors in order, returning the first non-empty locale. If no detectors are provided or all return "", Detect falls back to "en-US".

Example usage composing multiple detection strategies:

chain := engine.NewChainDetector(
    engine.NewStaticDetector(overrideFromURL),       // highest priority
    engine.NewAcceptLanguageDetector(acceptHeader),   // browser preference
    engine.NewDefaultLocaleDetector(nil),             // env var fallback
)
translator, _ := engine.New(
    engine.WithFileSystemLoader("locales"),
    engine.WithLocaleDetector(chain),
)

func (*ChainDetector) Detect

func (c *ChainDetector) Detect() string

Detect iterates detectors in order and returns the first non-empty result. If all detectors return "" or the chain is empty, returns "en-US".

func (*ChainDetector) Normalize

func (c *ChainDetector) Normalize(locale string) string

Normalize converts a locale string to BCP 47 format. If the chain has at least one detector, it delegates to the first detector's Normalize method. Otherwise it calls NormalizeLocale directly.

type ContextTranslator

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

ContextTranslator wraps a Translator and passes a context.Context to the logger for trace correlation and observability integration. Since translations are resolved from in-memory data (no I/O), context cancellation does not apply. The context is used solely for structured logging enrichment.

A ContextTranslator may optionally carry a locale override. When set, all translation operations route through that locale instead of reading the underlying Translator's shared locale state. This enables per-request locale scoping (e.g., in HTTP middleware) without mutating the shared Translator, which would race between concurrent requests.

ContextTranslator implements core.TranslatorProvider so it is substitutable anywhere a TranslatorProvider is accepted.

func (*ContextTranslator) GetLocale

func (ct *ContextTranslator) GetLocale() string

GetLocale returns the ContextTranslator's effective locale: the per-instance override if set (via WithLocaleContext), otherwise the underlying Translator's current locale.

func (*ContextTranslator) HasKey

func (ct *ContextTranslator) HasKey(key string) bool

HasKey checks whether a key exists for the ContextTranslator's effective locale (or its fallback chain).

func (*ContextTranslator) SetLocale

func (ct *ContextTranslator) SetLocale(locale string)

SetLocale delegates to the underlying Translator's SetLocale method. The context is passed to the logger for trace correlation.

NOTE: SetLocale mutates shared Translator state; it is NOT scoped to this ContextTranslator's locale override. For per-request locale scoping, use Translator.WithLocaleContext instead.

func (*ContextTranslator) Translate

func (ct *ContextTranslator) Translate(key string) string

Translate delegates to the underlying Translator, scoped to the ContextTranslator's effective locale. The context is passed to the logger for trace correlation.

func (*ContextTranslator) TranslateGender

func (ct *ContextTranslator) TranslateGender(key string, gender core.GenderCategory) string

TranslateGender delegates to the underlying Translator, scoped to the ContextTranslator's effective locale. The context is passed to the logger for trace correlation.

func (*ContextTranslator) TranslatePlural

func (ct *ContextTranslator) TranslatePlural(key string, count interface{}) string

TranslatePlural delegates to the underlying Translator, scoped to the ContextTranslator's effective locale. The context is passed to the logger for trace correlation.

func (*ContextTranslator) TranslateWithArgs

func (ct *ContextTranslator) TranslateWithArgs(key string, args ...interface{}) string

TranslateWithArgs delegates to the underlying Translator, scoped to the ContextTranslator's effective locale. The context is passed to the logger for trace correlation.

type DefaultFallbackChainer

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

DefaultFallbackChainer generates locale fallback chains using a built-in mapping of language codes to their primary regional variant. It always includes en-US as the final fallback when the input is not already en-US. Computed chains are cached internally so repeated calls for the same locale return a pre-built slice without allocation.

func NewDefaultFallbackChainer

func NewDefaultFallbackChainer() *DefaultFallbackChainer

NewDefaultFallbackChainer creates a new DefaultFallbackChainer with built-in language-to-region mappings for 28 languages.

func (*DefaultFallbackChainer) GetChain

func (c *DefaultFallbackChainer) GetChain(locale string) []string

GetChain returns the fallback chain for a given locale. The returned slice must not be modified by the caller. Examples:

  • "es-MX" returns ["es-MX", "es-ES", "en-US"]
  • "pt-BR" returns ["pt-BR", "pt-PT", "en-US"]
  • "en-GB" returns ["en-GB", "en-US"]
  • "en-US" returns ["en-US"]

type DefaultKeyResolver

type DefaultKeyResolver struct{}

DefaultKeyResolver resolves translation keys using dot notation to navigate nested maps. It enforces MaxKeyLength and MaxKeyDepth validation via ValidateKey before performing the lookup.

func NewDefaultKeyResolver

func NewDefaultKeyResolver() *DefaultKeyResolver

NewDefaultKeyResolver creates a new DefaultKeyResolver that resolves keys using dot-separated notation (e.g., "user.profile.title").

func (*DefaultKeyResolver) Resolve

func (r *DefaultKeyResolver) Resolve(translations map[string]interface{}, key string) (string, error)

Resolve retrieves a translation string for the given key from the translations map. Supports dot notation for nested keys (e.g., "error.validation.required"). Returns the translation string, or an error if the key is not found or invalid.

type DefaultLocaleDetector

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

DefaultLocaleDetector is the default implementation of core.LocaleDetector. It reads locale information from environment variables using the provided core.EnvProvider, falling back to "en-US" when no locale is configured.

func NewDefaultLocaleDetector

func NewDefaultLocaleDetector(env core.EnvProvider) *DefaultLocaleDetector

NewDefaultLocaleDetector creates a new DefaultLocaleDetector. If env is nil, the platform default core.EnvProvider is used (OSEnvProvider on standard Go, WASMEnvProvider on js/wasm).

func (*DefaultLocaleDetector) Detect

func (d *DefaultLocaleDetector) Detect() string

Detect retrieves the system locale from environment variables. Priority order: LC_ALL > LANG > LC_MESSAGES > default (en-US).

func (*DefaultLocaleDetector) Normalize

func (d *DefaultLocaleDetector) Normalize(locale string) string

Normalize converts locale strings to BCP 47 format. Handles: en_US.UTF-8 -> en-US, POSIX -> en-US, en -> en-US.

type DefaultPluralResolver

type DefaultPluralResolver struct{}

DefaultPluralResolver resolves plural categories using built-in CLDR rules.

func NewDefaultPluralResolver

func NewDefaultPluralResolver() *DefaultPluralResolver

NewDefaultPluralResolver creates a new DefaultPluralResolver.

func (*DefaultPluralResolver) Resolve

func (r *DefaultPluralResolver) Resolve(locale string, count interface{}) core.PluralCategory

Resolve determines the plural category for the given locale and count. It extracts the language subtag from the locale and looks up the CLDR rule. Returns Other for unknown locales or unrecognized count types.

type EmbedFSLoader

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

EmbedFSLoader loads translation files from an embedded filesystem.

func NewEmbedFSLoader

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

NewEmbedFSLoader creates a new EmbedFSLoader. The default file extension is ".json"; use WithExtension to override it.

func (*EmbedFSLoader) Load

func (l *EmbedFSLoader) Load(locale string) ([]byte, error)

Load reads a translation file for the given locale from the embedded filesystem.

type FileSystemLoader

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

FileSystemLoader loads translation files from the filesystem.

func NewFileSystemLoader

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

NewFileSystemLoader creates a new FileSystemLoader. The default file extension is ".json"; use WithExtension to override it.

func (*FileSystemLoader) Load

func (l *FileSystemLoader) Load(locale string) ([]byte, error)

Load reads a translation file for the given locale from the filesystem.

type JSONParser

type JSONParser struct{}

JSONParser parses JSON translation files into key-value maps. It enforces size limits (core.MaxJSONSize), nesting depth limits (core.MaxJSONDepth), and key count limits (core.MaxKeyCount) to prevent denial-of-service attacks from malicious input.

func NewJSONParser

func NewJSONParser() *JSONParser

NewJSONParser creates a new JSONParser with built-in security limits for input size, nesting depth, and key count.

func (*JSONParser) Parse

func (p *JSONParser) Parse(data []byte) (map[string]interface{}, error)

Parse decodes data from JSON into a map of translation keys to values. The data must be a JSON object (not array, string, or null). Parse enforces core.MaxJSONSize, core.MaxJSONDepth, and core.MaxKeyCount, returning an ErrInvalidFormat on violations.

type LoaderOption

type LoaderOption func(*loaderConfig)

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 to construct filenames. The extension must include the leading dot (e.g., ".toml", ".yaml").

type LocaleSet

type LocaleSet[T any] struct {
	// contains filtered or unexported fields
}

LocaleSet holds named locale structs for StructTranslator registration. Use RegisterLocales to set up build-tag-selected locale switching.

func NewLocaleSet

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

NewLocaleSet creates a LocaleSet with a fallback locale.

func (*LocaleSet[T]) Add

func (ls *LocaleSet[T]) Add(code string, data *T)

Add registers a locale struct for a given locale code.

func (*LocaleSet[T]) Codes

func (ls *LocaleSet[T]) Codes() []string

Codes returns all registered locale codes.

func (*LocaleSet[T]) Get

func (ls *LocaleSet[T]) Get(code string) *T

Get returns the locale struct for the given code, or the fallback if not found.

func (*LocaleSet[T]) SetLocale

func (ls *LocaleSet[T]) SetLocale(st *StructTranslator[T], code string) bool

SetLocale switches a StructTranslator to the named locale from this set. Returns true if the locale was found, false if the fallback was used.

type MapCache

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

MapCache is a thread-safe, in-memory translation cache backed by a map. It supports an optional LRU eviction policy when a maximum entry count is configured. When no limit is set (maxEntries == 0), the cache grows without bound. All methods are safe for concurrent use by multiple goroutines.

func NewMapCache

func NewMapCache() *MapCache

NewMapCache creates a new MapCache with no size limit (unlimited mode). Entries are never evicted; call Invalidate to clear the cache.

func NewMapCacheWithLimit

func NewMapCacheWithLimit(maxEntries int) *MapCache

NewMapCacheWithLimit creates a new MapCache with LRU eviction enabled. When the entry count exceeds maxEntries, the least-recently-used entry is evicted. If maxEntries is zero or negative, the cache behaves as unlimited.

func (*MapCache) Get

func (c *MapCache) Get(key string) (string, bool)

Get retrieves a cached translation by its cache key. Returns the cached value and true on a hit, or an empty string and false on a miss. When LRU eviction is enabled, a hit promotes the entry to the head of the access list (requires a write lock).

func (*MapCache) Invalidate

func (c *MapCache) Invalidate()

Invalidate discards all cached entries, resetting the cache to an empty state.

func (*MapCache) Set

func (c *MapCache) Set(key, value string)

Set stores a resolved translation under the given cache key. If the key already exists, its value is updated and the entry is promoted to the head of the LRU list. When at capacity, the least-recently-used entry (tail) is evicted.

type Namespace

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

Namespace automatically prefixes translation keys with a package name, eliminating manual key prefixing across ecosystem packages.

Namespace is immutable after construction. It holds no mutable state and requires no mutex of its own. All translation operations delegate to the underlying core.TranslatorProvider, which is already thread-safe. Multiple goroutines may safely share a single *Namespace instance.

func NewNamespace

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

NewNamespace creates a Namespace that prefixes all translation keys with the given prefix. The prefix must be non-empty, at most 64 characters, and contain only alphanumeric characters, underscores, or hyphens.

A nil core.TranslatorProvider is accepted without error; all methods will degrade gracefully by returning the namespaced key or a default value.

func (*Namespace) Has

func (ns *Namespace) Has(key string) bool

Has checks whether the namespaced key exists in the translator.

When the translator is nil, it returns false. Nil receiver safe: returns false.

func (*Namespace) Key

func (ns *Namespace) Key(key string) string

Key returns the full namespaced key (prefix + "." + key) without performing any translation lookup. Useful for logging, error wrapping, or passing keys to other systems.

Nil receiver safe: returns the key argument unchanged.

func (*Namespace) T

func (ns *Namespace) T(key string) string

T translates the namespaced key by joining the prefix and key with a dot separator and delegating to the underlying core.TranslatorProvider.

When the translator is nil, it returns the full namespaced key as a fallback. Nil receiver safe: returns the key argument unchanged.

func (*Namespace) TD

func (ns *Namespace) TD(key, defaultValue string) string

TD translates the namespaced key, returning defaultValue when the key is not found or the translator is nil. This is the primary method for ecosystem packages that provide hardcoded English defaults.

Nil receiver safe: returns defaultValue.

func (*Namespace) TF

func (ns *Namespace) TF(key string, args ...interface{}) string

TF translates the namespaced key with format arguments by joining the prefix and key with a dot separator and delegating to TranslateWithArgs.

When the translator is nil, it returns the full namespaced key without formatting. Nil receiver safe: returns the key argument unchanged.

type NopLogger

type NopLogger struct{}

NopLogger is a silent logger that discards all messages. This is the default logger when no logger is configured. NopLogger is stateless and safe for concurrent use.

func (NopLogger) Debug

func (NopLogger) Debug(string, ...any)

Debug discards the message.

func (NopLogger) Enabled

func (NopLogger) Enabled(core.LogLevel) bool

Enabled always returns false since NopLogger never logs.

func (NopLogger) Error

func (NopLogger) Error(string, ...any)

Error discards the message.

func (NopLogger) Fatal

func (NopLogger) Fatal(string, ...any)

Fatal discards the message.

func (NopLogger) Info

func (NopLogger) Info(string, ...any)

Info discards the message.

func (NopLogger) Warn

func (NopLogger) Warn(string, ...any)

Warn discards the message.

func (NopLogger) WithContext

func (n NopLogger) WithContext(context.Context) core.Logger

WithContext returns the same NopLogger since context is not used.

func (NopLogger) WithFields

func (n NopLogger) WithFields(...any) core.Logger

WithFields returns the same NopLogger since fields are not used.

func (NopLogger) WithLevel

func (n NopLogger) WithLevel(core.LogLevel) core.Logger

WithLevel returns the same NopLogger since level filtering is not used.

type OSEnvProvider

type OSEnvProvider struct{}

OSEnvProvider wraps os.Getenv for standard Go environments.

func (*OSEnvProvider) Getenv

func (p *OSEnvProvider) Getenv(key string) string

Getenv returns the value of the environment variable named by the key.

type Option

type Option func(*Translator) error

Option is a functional option for configuring the Translator.

func WithCache

func WithCache(cache core.Cacher) Option

WithCache sets a Cacher for resolved-translation caching. When enabled, Translate, TranslatePlural, and TranslateGender cache their results to avoid repeated fallback chain traversal and key resolution. Caching is opt-in; when not set, the Translator has zero caching overhead.

func WithDefaultLocale

func WithDefaultLocale(locale string) Option

WithDefaultLocale sets the default locale for the translator.

func WithFallbackChainer

func WithFallbackChainer(chainer core.FallbackChainer) Option

WithFallbackChainer sets a custom FallbackChainer.

func WithFileSystemLoader

func WithFileSystemLoader(baseDir string) Option

WithFileSystemLoader creates a FileSystemLoader with the given base directory. This is a convenience function that combines loader creation with configuration.

func WithLoader

func WithLoader(loader core.TranslationLoader) Option

WithLoader sets a custom TranslationLoader.

func WithLocaleDetector

func WithLocaleDetector(detector core.LocaleDetector) Option

WithLocaleDetector sets a custom LocaleDetector.

func WithLogger

func WithLogger(l core.Logger) Option

WithLogger sets a custom Logger for the Translator instance. If l is nil, NopLogger is used (silent operation). This logger takes precedence over the package-level logger set via SetLogger.

func WithParser

func WithParser(parser core.TranslationParser) Option

WithParser sets a custom TranslationParser.

func WithPluralResolver

func WithPluralResolver(resolver core.PluralResolver) Option

WithPluralResolver sets a custom PluralResolver.

func WithRegisteredParser

func WithRegisteredParser(ext string) Option

WithRegisteredParser resolves a TranslationParser from the default package-level registry by file extension and sets it on the Translator. The extension must already be registered (e.g., ".json" is registered by default; external modules register additional formats in their init() functions).

Returns an error wrapping ErrUnknownFormat if no parser is registered for ext.

func WithRegistryLoader

func WithRegistryLoader() Option

WithRegistryLoader creates a RegistryLoader and configures it as the translation loader for a Translator. Because built-in locale data is stored in the compact binary format, this option also sets the BinaryParser as the translation parser. A subsequent WithParser call in the options chain can override the parser if needed.

Usage:

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

func WithResolver

func WithResolver(resolver core.KeyResolver) Option

WithResolver sets a custom KeyResolver.

type PackageOption

type PackageOption func(*PackageTranslator)

PackageOption is a functional option for configuring a PackageTranslator. Unlike the core Option type, PackageOption does not return an error because option setters are trivial value assignments.

func WithDefaults

func WithDefaults(defaults map[string]string) PackageOption

WithDefaults sets the hardcoded English fallback strings on the PackageTranslator. Keys in the map are short keys (e.g., "error.empty_path"), not namespace-prefixed. A nil map is acceptable and is treated as empty.

func WithTranslator

func WithTranslator(t core.TranslatorProvider) PackageOption

WithTranslator sets the initial translator on the PackageTranslator. This has the same effect as calling SetTranslator after construction. A nil value is acceptable and puts the PackageTranslator in defaults-only mode.

type PackageTranslator

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

PackageTranslator provides per-package translation with namespace scoping, thread-safe translator swapping, and hardcoded default fallbacks. Package authors use this type to eliminate ~100-200 lines of duplicated i18n boilerplate per package.

All methods are nil-safe for the translator field: when no translator has been set, methods fall back to the defaults map, then to the raw key.

PackageTranslator implements core.TranslatorProvider so it can be passed to other packages expecting that interface.

The translator pointer is swapped atomically via atomic.Pointer for lock-free reads. Reads observe either the most recent Store or the zero value (which is treated as "no translator set").

func NewPackageTranslator

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

NewPackageTranslator creates a PackageTranslator with the given namespace and optional configuration. The namespace is validated with core.ValidateKey and becomes a dot-notation prefix for all translation keys.

Returns nil if the namespace is invalid.

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 is the primary integration point for ecosystem packages — it replaces ~300 lines of per-package i18n.go boilerplate with a single constructor call.

It creates an i18n.Translator with EmbedFSLoader, detects the system locale, and wires everything into a PackageTranslator with namespace scoping.

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",
        "error.not_found":  "file not found: %s",
    }),
)

The returned PackageTranslator:

  • Loads translations from the embedded filesystem
  • Auto-detects system locale (LC_ALL > LANG > en-US)
  • Prefixes all keys with the namespace (e.g., "filesystem.error.empty_path")
  • Falls back to defaults when translator is nil or key is missing
  • Is thread-safe for concurrent use
  • Implements TranslatorProvider for passing to other packages

func (*PackageTranslator) GetLocale

func (pt *PackageTranslator) GetLocale() string

GetLocale implements core.TranslatorProvider. Delegates to the underlying translator's GetLocale. Returns "en-US" if the translator is nil.

func (*PackageTranslator) GetTranslator

func (pt *PackageTranslator) GetTranslator() core.TranslatorProvider

GetTranslator returns the current underlying translator, or nil if none is set.

func (*PackageTranslator) Has

func (pt *PackageTranslator) Has(key string) bool

Has checks whether the namespaced key exists in the underlying translator. Returns false when the translator is nil.

func (*PackageTranslator) HasKey

func (pt *PackageTranslator) HasKey(key string) bool

HasKey implements core.TranslatorProvider. Delegates directly to the underlying translator's HasKey. Returns false if the translator is nil.

func (*PackageTranslator) SetLocale

func (pt *PackageTranslator) SetLocale(locale string)

SetLocale implements core.TranslatorProvider. Delegates to the underlying translator's SetLocale. No-op if the translator is nil.

func (*PackageTranslator) SetTranslator

func (pt *PackageTranslator) SetTranslator(t core.TranslatorProvider)

SetTranslator swaps the underlying translator. Nil is allowed and resets the PackageTranslator to defaults-only mode. This method is thread-safe.

func (*PackageTranslator) T

func (pt *PackageTranslator) T(key string) string

T translates a key by prepending the namespace prefix, delegating to the underlying translator, and falling back to defaults or the raw key.

func (*PackageTranslator) TF

func (pt *PackageTranslator) TF(key string, args ...interface{}) string

TF translates a key with format arguments by prepending the namespace prefix, delegating to the underlying translator, and falling back to formatting the default string or returning the raw key.

func (*PackageTranslator) Translate

func (pt *PackageTranslator) Translate(key string) string

Translate implements core.TranslatorProvider. The key is expected to be fully qualified (already includes namespace when called through the interface). This method does NOT double-prepend the namespace.

func (*PackageTranslator) TranslateGender

func (pt *PackageTranslator) TranslateGender(key string, gender core.GenderCategory) string

TranslateGender implements core.TranslatorProvider. Delegates to the underlying translator's TranslateGender. Returns the key if the translator is nil.

func (*PackageTranslator) TranslatePlural

func (pt *PackageTranslator) TranslatePlural(key string, count interface{}) string

TranslatePlural implements core.TranslatorProvider. Delegates to the underlying translator's TranslatePlural. Returns the key if the translator is nil.

func (*PackageTranslator) TranslateWithArgs

func (pt *PackageTranslator) TranslateWithArgs(key string, args ...interface{}) string

TranslateWithArgs implements core.TranslatorProvider. The key is expected to be fully qualified. This method does NOT double-prepend the namespace.

type Registry

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

Registry holds a map of file extensions to core.TranslationParser implementations. It provides thread-safe registration and lookup of parsers by file extension.

Registration typically happens in init() functions, which run in a single goroutine before main(). However, the Registry is safe for concurrent access at any time via its internal sync.RWMutex. Callers registering parsers concurrently outside init() should be aware of potential races between registration and first use if done in separate goroutines.

func NewRegistry

func NewRegistry() *Registry

NewRegistry returns an empty parser registry.

func (*Registry) GetParser

func (r *Registry) GetParser(ext string) (core.TranslationParser, error)

GetParser returns the core.TranslationParser registered for the given extension. Returns ErrUnknownFormat if no parser is registered for the extension.

func (*Registry) RegisterParser

func (r *Registry) RegisterParser(ext string, p core.TranslationParser) error

RegisterParser registers a core.TranslationParser for the given file extension. The extension must start with a dot followed by one or more lowercase alphanumeric characters (e.g., ".json", ".toml", ".yaml"). If a parser is already registered for the extension, it is overwritten (last-write-wins).

Returns an error if ext is invalid or p is nil.

External Module Pattern

A separate Go module can provide parsers for additional formats without adding any dependency to the core i18n module. The external module:

  1. Implements the core.TranslationParser interface
  2. Calls RegisterParser in its init() function

For example, a TOML parser module would contain:

func init() {
    i18n.RegisterParser(".toml", &TOMLParser{})
}

Application code activates the parser via a blank import:

import _ "github.com/0verkilll/i18n-toml"

The same pattern applies to YAML or any other format. External parsers should enforce equivalent size and nesting depth limits (see core.MaxJSONSize and core.MaxJSONDepth) to maintain security parity with the built-in JSON parser.

func (*Registry) RegisteredFormats

func (r *Registry) RegisteredFormats() []string

RegisteredFormats returns a sorted slice of all registered file extensions. The returned slice is safe to modify; it does not share memory with the registry internals.

type RegistryLoader

type RegistryLoader struct{}

RegistryLoader loads translation data from the global locale registry. Locale data is registered at init time by build-tag-selected locale files. This loader is a peer to FileSystemLoader and EmbedFSLoader; all three loaders remain available and interchangeable via the core.TranslationLoader interface.

func NewRegistryLoader

func NewRegistryLoader() *RegistryLoader

NewRegistryLoader creates a new RegistryLoader that reads from the global locale registry populated by build-tag-selected locale files.

func (*RegistryLoader) Load

func (l *RegistryLoader) Load(locale string) ([]byte, error)

Load retrieves translation data for the specified locale from the global registry. If the locale is registered, Load returns a defensive copy of the data to prevent caller mutation of registry contents. If the locale is not registered, Load returns a descriptive error that mentions the build-tag mechanism.

type StaticDetector

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

StaticDetector is a core.LocaleDetector that always returns a fixed locale. It is intended for testing and explicit locale overrides. Placing it first in a ChainDetector forces a specific locale regardless of other detectors.

func NewStaticDetector

func NewStaticDetector(locale string) *StaticDetector

NewStaticDetector creates a new StaticDetector that always returns the given locale from Detect. The locale is returned exactly as provided, without normalization.

Example usage:

translator, _ := engine.New(
    engine.WithFileSystemLoader("locales"),
    engine.WithLocaleDetector(engine.NewStaticDetector("fr-FR")),
)

func (*StaticDetector) Detect

func (s *StaticDetector) Detect() string

Detect returns the configured locale string exactly as provided to the constructor.

func (*StaticDetector) Normalize

func (s *StaticDetector) Normalize(locale string) string

Normalize converts a locale string to BCP 47 format using the shared normalization logic.

type StructTranslator

type StructTranslator[T any] struct {
	// contains filtered or unexported fields
}

StructTranslator provides zero-cost translation lookups using Go struct field access instead of map lookups. Each locale is a struct instance with string fields for each translation key. Locale switching is an atomic pointer swap.

Performance: 0.25 ns per lookup (vs 25 ns for cached map-based Translate). This is 100x faster and ideal for game loops and real-time applications.

Usage:

type Messages struct {
    Greeting  string
    Farewell  string
    ErrEmpty  string
}

var enUS = Messages{Greeting: "Hello", Farewell: "Goodbye", ErrEmpty: "cannot be empty"}
var esES = Messages{Greeting: "Hola", Farewell: "Adiós", ErrEmpty: "no puede estar vacío"}

var Msg = NewStructTranslator(&enUS)

// Read (0.25 ns, zero alloc):
fmt.Println(Msg.Get().Greeting)

// Switch locale (atomic, safe from any goroutine):
Msg.Set(&esES)

func NewStructTranslator

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

NewStructTranslator creates a StructTranslator with the given initial locale data.

func (*StructTranslator[T]) Get

func (st *StructTranslator[T]) Get() *T

Get returns a pointer to the active locale struct. The returned pointer is safe to read from any goroutine. Field access on the returned pointer is a single CPU instruction with zero overhead.

Do NOT cache the returned pointer across frames or requests — call Get() each time to respect locale switches.

func (*StructTranslator[T]) Set

func (st *StructTranslator[T]) Set(locale *T)

Set atomically switches the active locale to a new struct instance. This is safe to call from any goroutine. All subsequent Get() calls will return the new locale data.

type Translator

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

Translator is the main type for translation operations. It coordinates between loaders, parsers, and resolvers to provide a complete translation service with fallback support and thread safety.

func New

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

New creates a new Translator with the given options. At minimum, a loader must be provided (either through WithLoader or WithFileSystemLoader). Other components will use default implementations if not specified.

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, WithParser(BinaryParser), and WithDefaultLocale pre-applied. The BinaryParser is used because built-in locale data is stored in the compact binary format. Additional options can be provided to customize other components; a user-supplied WithParser overrides the default.

func (*Translator) GetLocale

func (t *Translator) GetLocale() string

GetLocale returns the current locale being used for translations.

func (*Translator) HasKey

func (t *Translator) HasKey(key string) bool

HasKey checks if a translation key exists in the current locale or its fallback chain.

func (*Translator) ReloadLocale

func (t *Translator) ReloadLocale(locale string) error

ReloadLocale forces a reload of translations for the given locale from the underlying loader. This clears both the internal translation cache for that locale and the resolved-translation cache.

func (*Translator) SetLocale

func (t *Translator) SetLocale(locale string)

SetLocale changes the current locale for translation lookups. The locale is normalized then validated; invalid locales are silently rejected (the current locale is preserved and a warning is logged). If a translation cache is configured, it is invalidated on a successful change.

NOTE: SetLocale mutates shared Translator state. In multi-tenant HTTP request scenarios where each request needs a distinct locale, prefer WithLocaleContext to obtain a per-request, locale-scoped view without mutating the shared Translator.

func (*Translator) Translate

func (t *Translator) Translate(key string) string

Translate looks up a translation key in the current locale. If the key is not found, it tries the fallback chain. If the key is still not found, it returns the key itself. When a translation cache is configured, resolved values are cached to skip fallback chain traversal on subsequent calls for the same key.

func (*Translator) TranslateGender

func (t *Translator) TranslateGender(key string, gender core.GenderCategory) string

TranslateGender looks up key.<gender>, falls back to key.other, sanitizes the output, and traverses the fallback chain. When a translation cache is configured, resolved values are cached with a gender-differentiated key to skip repeated lookups.

func (*Translator) TranslatePlural

func (t *Translator) TranslatePlural(key string, count interface{}) string

TranslatePlural resolves the plural category for the current locale and count, looks up key.<category> (falling back to key.other in the SAME locale, then walking the fallback chain), replaces # with the count, sanitizes the output, and traverses the fallback chain if needed. When a translation cache is configured, resolved values are cached with a count-differentiated key to skip repeated lookups.

func (*Translator) TranslatePluralWithArgs

func (t *Translator) TranslatePluralWithArgs(key string, count interface{}, args ...interface{}) string

TranslatePluralWithArgs resolves the plural category, looks up the translation, replaces # with count, then applies fmt.Sprintf with the provided args. This method is not individually cached because args are arbitrary interface{} values that are not safely serializable without reflect.

For a given locale, both key.<category> and key.other are tried before advancing to the next locale in the fallback chain.

func (*Translator) TranslateWithArgs

func (t *Translator) TranslateWithArgs(key string, args ...interface{}) string

TranslateWithArgs looks up a translation key and formats it with the given arguments. It uses fmt.Sprintf for formatting, and validates the format string before use. If the key is not found, it returns the key itself without formatting. This method is not individually cached because args are arbitrary interface{} values that are not safely serializable without reflect. It benefits indirectly from the Translate() cache on the format string lookup.

func (*Translator) TranslateWithMessage

func (t *Translator) TranslateWithMessage(key string, args map[string]interface{}) string

TranslateWithMessage resolves a key to an ICU MessageFormat template and evaluates it with the provided named arguments. For plural expressions, the Translator's PluralResolver determines the category. Falls back through the locale chain. This method is not individually cached because args are arbitrary interface{} values that are not safely serializable without reflect.

func (*Translator) WithContext

func (t *Translator) WithContext(ctx context.Context) *ContextTranslator

WithContext returns a ContextTranslator that passes ctx to the Translator's logger for trace correlation. The returned ContextTranslator delegates all translation operations to the underlying Translator using the Translator's current shared locale.

func (*Translator) WithLocaleContext

func (t *Translator) WithLocaleContext(ctx context.Context, locale string) *ContextTranslator

WithLocaleContext returns a ContextTranslator scoped to a specific locale AND context. Unlike SetLocale (which mutates shared Translator state), WithLocaleContext creates a lightweight per-call view whose translation operations use the supplied locale. This is the recommended approach for request-scoped locale selection (e.g., from an Accept-Language header) in concurrent HTTP handlers.

The locale is normalized and validated. An invalid locale logs a warning and falls back to the Translator's current shared locale.

Jump to

Keyboard shortcuts

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