msgcat

package module
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: Feb 25, 2026 License: MIT Imports: 9 Imported by: 16

README

msgcat

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/.

Installation

go get github.com/loopcontext/msgcat

Quick Start

1. Create message files

Default 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}}
2. Initialize catalog
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)
}
3. Resolve messages/errors from context
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

Features

  • 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).

API

Core interface
  • LoadMessages(lang string, messages []RawMessage) error
  • GetMessageWithCtx(ctx context.Context, msgCode int, msgParams ...interface{}) *Message
  • WrapErrorWithCtx(ctx context.Context, err error, msgCode int, msgParams ...interface{}) error
  • GetErrorWithCtx(ctx context.Context, msgCode int, msgParams ...interface{}) error
Helpers
  • msgcat.Reload(catalog MessageCatalog) error
  • msgcat.SnapshotStats(catalog MessageCatalog) (MessageCatalogStats, error)
  • msgcat.ResetStats(catalog MessageCatalog) error
  • msgcat.Close(catalog MessageCatalog) error
Constants
  • SystemMessageMinCode = 9000
  • SystemMessageMaxCode = 9999
  • CodeMissingMessage = 999999998
  • CodeMissingLanguage = 99999999

Observability

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
}

Production Notes

  • Keep DefaultLanguage explicit (en recommended).
  • Define FallbackLanguages intentionally (for example for regional traffic).
  • Use StrictTemplates: true in production to detect bad template usage early.
  • Set ObserverBuffer to avoid request-path pressure from slow observers.
  • Set StatsMaxKeys to 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 ReloadRetries and ReloadRetryDelay to reduce transient parse/read errors during rollout windows.
  • If observer is configured, call msgcat.Close(catalog) on service shutdown.
Runtime Contract
  • GetMessageWithCtx / GetErrorWithCtx / WrapErrorWithCtx are safe for concurrent use.
  • LoadMessages and Reload are safe concurrently with reads.
  • Reload keeps the last in-memory state if reload fails.
  • Observer callbacks are async and panic-protected; overflow is counted in DroppedEvents.

Benchmarks

Run:

go test -run ^$ -bench . -benchmem ./...

Integration Examples

  • HTTP language middleware sample: examples/http/main.go
  • Metrics/observer sample (expvar style): examples/metrics/main.go

Context7 / LLM Docs

For full machine-friendly docs, see docs/CONTEXT7.md. For retrieval-optimized chunks, see docs/CONTEXT7_RETRIEVAL.md.

Release + Migration

  • Changelog: docs/CHANGELOG.md
  • Migration guide: docs/MIGRATION.md
  • Release playbook: docs/RELEASE.md
  • Support policy: docs/SUPPORT.md

Documentation

Index

Constants

View Source
const (
	SystemMessageMinCode = 9000
	SystemMessageMaxCode = 9999
	CodeMissingMessage   = 999999998
	CodeMissingLanguage  = 99999999
)
View Source
const MessageCatalogNotFound = "Unexpected error in message catalog, language [%s] not found. %s"

Variables

This section is empty.

Functions

func Close

func Close(catalog MessageCatalog) error

func Reload

func Reload(catalog MessageCatalog) error

func ResetStats

func ResetStats(catalog MessageCatalog) error

Types

type Config

type Config struct {
	ResourcePath      string
	CtxLanguageKey    ContextKey
	DefaultLanguage   string
	FallbackLanguages []string
	StrictTemplates   bool
	Observer          Observer
	ObserverBuffer    int
	StatsMaxKeys      int
	ReloadRetries     int
	ReloadRetryDelay  time.Duration
	NowFn             func() time.Time
}

type ContextKey

type ContextKey string

type DefaultError

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

func (DefaultError) Error

func (ce DefaultError) Error() string

func (*DefaultError) ErrorCode

func (ce *DefaultError) ErrorCode() int

func (*DefaultError) GetLongMessage

func (ce *DefaultError) GetLongMessage() string

func (*DefaultError) GetShortMessage

func (ce *DefaultError) GetShortMessage() string

func (*DefaultError) Unwrap

func (ce *DefaultError) Unwrap() error

type DefaultMessageCatalog

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

func (*DefaultMessageCatalog) Close

func (dmc *DefaultMessageCatalog) Close()

func (*DefaultMessageCatalog) GetErrorWithCtx

func (dmc *DefaultMessageCatalog) GetErrorWithCtx(ctx context.Context, msgCode int, msgParams ...interface{}) error

func (*DefaultMessageCatalog) GetMessageWithCtx

func (dmc *DefaultMessageCatalog) GetMessageWithCtx(ctx context.Context, msgCode int, msgParams ...interface{}) *Message

func (*DefaultMessageCatalog) LoadMessages

func (dmc *DefaultMessageCatalog) LoadMessages(lang string, messages []RawMessage) error

func (*DefaultMessageCatalog) Reload

func (dmc *DefaultMessageCatalog) Reload() error

func (*DefaultMessageCatalog) ResetStats

func (dmc *DefaultMessageCatalog) ResetStats()

func (*DefaultMessageCatalog) SnapshotStats

func (dmc *DefaultMessageCatalog) SnapshotStats() MessageCatalogStats

func (*DefaultMessageCatalog) WrapErrorWithCtx

func (dmc *DefaultMessageCatalog) WrapErrorWithCtx(ctx context.Context, err error, msgCode int, msgParams ...interface{}) error

type Error

type Error interface {
	Error() string
	Unwrap() error
	ErrorCode() int
	GetShortMessage() string
	GetLongMessage() string
}

type Message

type Message struct {
	LongText  string
	ShortText string
	Code      int
}

type MessageCatalog

type MessageCatalog interface {
	// Allows to load more messages (9000 - 9999 - reserved to system messages)
	LoadMessages(lang string, messages []RawMessage) error
	GetMessageWithCtx(ctx context.Context, msgCode int, msgParams ...interface{}) *Message
	WrapErrorWithCtx(ctx context.Context, err error, msgCode int, msgParams ...interface{}) error
	GetErrorWithCtx(ctx context.Context, msgCode int, msgParams ...interface{}) error
}

func NewMessageCatalog

func NewMessageCatalog(cfg Config) (MessageCatalog, error)

type MessageCatalogStats

type MessageCatalogStats struct {
	LanguageFallbacks map[string]int
	MissingLanguages  map[string]int
	MissingMessages   map[string]int
	TemplateIssues    map[string]int
	DroppedEvents     map[string]int
	LastReloadAt      time.Time
}

func SnapshotStats

func SnapshotStats(catalog MessageCatalog) (MessageCatalogStats, error)

type MessageParams

type MessageParams struct {
	Params map[string]interface{}
}

type Messages

type Messages struct {
	Group   int                `yaml:"group"`
	Default RawMessage         `yaml:"default"`
	Set     map[int]RawMessage `yaml:"set"`
}

type Observer

type Observer interface {
	OnLanguageFallback(requestedLang string, resolvedLang string)
	OnLanguageMissing(lang string)
	OnMessageMissing(lang string, msgCode int)
	OnTemplateIssue(lang string, msgCode int, issue string)
}

type RawMessage

type RawMessage struct {
	LongTpl  string `yaml:"long"`
	ShortTpl string `yaml:"short"`
	Code     int
}

Directories

Path Synopsis
examples
http command
metrics command
mock
Package mock_msgcat is a generated GoMock package.
Package mock_msgcat is a generated GoMock package.

Jump to

Keyboard shortcuts

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