msgcat is a lightweight i18n message catalog for Go focused on APIs and error handling.
It loads messages from YAML by language, resolves language from context.Context, supports runtime message loading for system codes, and can wrap domain errors with localized short/long messages.
Maturity: production-ready (v1.x) with SemVer and release/migration docs in docs/.
go get github.com/loopcontext/msgcatDefault path:
./resources/messages
Example en.yaml:
group: 0
default:
short: Unexpected error
long: Unexpected message code [{{0}}] was received and was not found in this catalog
set:
1:
short: User created
long: User {{0}} was created successfully
2:
short: You have {{0}} {{plural:0|item|items}}
long: Total: {{num:1}} generated at {{date:2}}Example es.yaml:
group: 0
default:
short: Error inesperado
long: Se recibió un código de mensaje inesperado [{{0}}] y no se encontró en el catálogo
set:
1:
short: Usuario creado
long: Usuario {{0}} fue creado correctamente
2:
short: Tienes {{0}} {{plural:0|elemento|elementos}}
long: Total: {{num:1}} generado el {{date:2}}catalog, err := msgcat.NewMessageCatalog(msgcat.Config{
ResourcePath: "./resources/messages",
CtxLanguageKey: "language",
DefaultLanguage: "en",
FallbackLanguages: []string{"es"},
StrictTemplates: true,
ObserverBuffer: 1024,
StatsMaxKeys: 512,
ReloadRetries: 2,
ReloadRetryDelay: 50 * time.Millisecond,
})
if err != nil {
panic(err)
}ctx := context.WithValue(context.Background(), "language", "es-AR")
msg := catalog.GetMessageWithCtx(ctx, 1, "juan")
fmt.Println(msg.ShortText) // "Usuario creado"
err := catalog.WrapErrorWithCtx(ctx, errors.New("db timeout"), 2, 3, 12345.5, time.Now())
fmt.Println(err.Error()) // localized short message- Language resolution from context (typed key and string key compatibility).
- Language fallback chain: requested -> base (
es-ar->es) -> configured fallbacks -> default ->en. - YAML + runtime-loaded system messages (
9000-9999). - Template tokens:
{{0}},{{1}}, ... positional{{plural:i|singular|plural}}{{num:i}}localized number format{{date:i}}localized date format
- Strict template mode (
StrictTemplates) for missing parameters. - Error wrapping with localized short/long messages and error code.
- Concurrency-safe reads/writes.
- Runtime reload (
msgcat.Reload) preserving runtime-loaded messages. - Observability hooks and counters (
SnapshotStats).
LoadMessages(lang string, messages []RawMessage) errorGetMessageWithCtx(ctx context.Context, msgCode int, msgParams ...interface{}) *MessageWrapErrorWithCtx(ctx context.Context, err error, msgCode int, msgParams ...interface{}) errorGetErrorWithCtx(ctx context.Context, msgCode int, msgParams ...interface{}) error
msgcat.Reload(catalog MessageCatalog) errormsgcat.SnapshotStats(catalog MessageCatalog) (MessageCatalogStats, error)msgcat.ResetStats(catalog MessageCatalog) errormsgcat.Close(catalog MessageCatalog) error
SystemMessageMinCode = 9000SystemMessageMaxCode = 9999CodeMissingMessage = 999999998CodeMissingLanguage = 99999999
Provide an observer in config:
type Observer struct{}
func (Observer) OnLanguageFallback(requested, resolved string) {}
func (Observer) OnLanguageMissing(lang string) {}
func (Observer) OnMessageMissing(lang string, msgCode int) {}
func (Observer) OnTemplateIssue(lang string, msgCode int, issue string) {}Snapshot counters at runtime:
stats, err := msgcat.SnapshotStats(catalog)
if err == nil {
_ = stats.LanguageFallbacks
_ = stats.MissingLanguages
_ = stats.MissingMessages
_ = stats.TemplateIssues
_ = stats.DroppedEvents
_ = stats.LastReloadAt
}- Keep
DefaultLanguageexplicit (enrecommended). - Define
FallbackLanguagesintentionally (for example for regional traffic). - Use
StrictTemplates: truein production to detect bad template usage early. - Set
ObserverBufferto avoid request-path pressure from slow observers. - Set
StatsMaxKeysto cap cardinality (__overflow__key holds overflow counts). - Use
go test -race ./...in CI. - For periodic YAML refresh, call
msgcat.Reload(catalog)in a controlled goroutine and prefer atomic file replacement (write temp + rename). - Use
ReloadRetriesandReloadRetryDelayto reduce transient parse/read errors during rollout windows. - If observer is configured, call
msgcat.Close(catalog)on service shutdown.
GetMessageWithCtx/GetErrorWithCtx/WrapErrorWithCtxare safe for concurrent use.LoadMessagesandReloadare safe concurrently with reads.Reloadkeeps the last in-memory state if reload fails.- Observer callbacks are async and panic-protected; overflow is counted in
DroppedEvents.
Run:
go test -run ^$ -bench . -benchmem ./...- HTTP language middleware sample:
examples/http/main.go - Metrics/observer sample (expvar style):
examples/metrics/main.go
For full machine-friendly docs, see docs/CONTEXT7.md.
For retrieval-optimized chunks, see docs/CONTEXT7_RETRIEVAL.md.
- Changelog:
docs/CHANGELOG.md - Migration guide:
docs/MIGRATION.md - Release playbook:
docs/RELEASE.md - Support policy:
docs/SUPPORT.md