Struct validation backed by go-playground/validator/v10 with xerrors integration and pluggable i18n message providers. What's included: - Validator interface with Struct(v any) error method - New(...Option) constructor with WithMessageProvider functional option - MessageProvider interface for i18n; DefaultMessages (EN) and SpanishMessages (ES) built in - ValidationErrors mapped to xerrors.ErrInvalidInput with field and tag context keys - InvalidValidationError (non-struct input) mapped to xerrors.ErrInternal - Full playground.ValidationErrors attached via WithError for callers needing all failures Tested-via: todo-api POC integration Reviewed-against: docs/adr/
2.4 KiB
ADR-003: MessageProvider Pattern for i18n
Status: Accepted Date: 2026-03-18
Context
Validation error messages shown to end users must be human-readable. Applications targeting different locales need messages in different languages. Hardcoding English messages inside the Validator implementation would make internationalization impossible without forking the package.
At the same time, the majority of applications use a single language throughout their lifetime. Requiring every caller to configure a message provider would be boilerplate-heavy for the common case.
Decision
A MessageProvider interface is defined:
type MessageProvider interface {
Message(field, tag, param string) string
}
Message receives the failing field name, the rule tag, and the rule parameter (e.g., "5" for min=5, or "" if none), and returns a human-readable string.
Two built-in implementations are provided as package-level variables:
DefaultMessages— English, used automatically when no option is passed toNew().SpanishMessages— Spanish, available as an opt-in viaWithMessageProvider(valid.SpanishMessages).
Custom implementations are supported by passing any value that satisfies MessageProvider to WithMessageProvider.
The New() constructor defaults to DefaultMessages and applies options via a config struct, following the functional options pattern. This means zero boilerplate for the common (English) case and a single option call for overrides.
Consequences
- Positive: English is the zero-configuration default —
valid.New()requires no arguments. - Positive: Spanish is available without any external dependency — just
valid.SpanishMessages. - Positive: Applications can supply their own
MessageProviderfor any other language or for message formats that include the failing value, link to docs, etc. - Negative: The built-in providers handle only four tags (
required,email,min,max) explicitly; all others fall through to a generic fallback message. Applications using many custom tags should supply a custom provider. - Note: Message formatting uses the struct field name as returned by
go-playground/validator(the Go field name, e.g."Email"), not a JSON tag. If user-facing messages must show the JSON key name, a customMessageProvidercombined with a registered tag name function on the playground validator would be needed.