Package i18n (stdlib only) loads one JSON catalog per locale from resources/lang/<locale>.json (en.json, pt-BR.json; pt_BR.json is accepted). Keys are nested objects or dotted names; values are strings with {name} placeholders, or plural objects.
{
"welcome": "Hello, {name}!",
"posts": {
"created": "Post created.",
"count": { "zero": "No posts", "one": "{count} post", "other": "{count} posts" }
}
}app, err := vukem.New(
vukem.WithTranslations(os.DirFS("resources/lang")), // app.I18n; APP_LOCALE is the default (en)
vukem.WithViews(resources.Views()),
vukem.WithRoutes(routes), // routes call vukem.DefaultMiddleware
)WithTranslations loads the bundle at boot (bad JSON or file names fail New), gives views the t and locale functions and makes vukem.DefaultMiddleware install the locale middleware after sessions.
Translating#
c.Flash("status", i18n.T(c.Context(), "posts.created")) // the request's locale
b.T("pt-BR", "posts.count", "count", 3) // a Bundle directly<html lang="{{locale}}">
<p>{{t "welcome" "name" .User.Name}} · {{t "posts.count" "count" (len .Posts)}}</p>- Fallback chain: the locale, its shorter forms, then the default (
pt-BR→pt→en). A missing key returns the key and callsOptions.OnMissingwhen set; translating never panics. - Arguments: key/value pairs (
"name", "Ana") or onemap[string]any; unknown placeholders stay as written. Output is plain text, sohtml/templateescapes it. - Plurals: an object keyed by CLDR categories (
zero,one,two,few,many,other;otheris required), chosen by the numericcountargument. Rules cover most European languages, Arabic, Hebrew and the CJK/South-East Asian languages; others use the English rule. An explicitzeroform wins for 0 in every language.i18n.PluralCategory(locale, n)exposes the rules. - Vue / React / Svelte (Inertia):
inertia.I18n()shares the catalog with your components; see Translations in Inertia.
Locale resolution#
mw, err := i18n.Middleware(bundle, i18n.MiddlewareConfig{}) builds a middleware that picks the locale, in order, from:
?lang=- the
langcookie - the session value
locale(when sessions run first) Accept-Language(q-values, at most 16 entries)- the default locale
Candidates must be valid tags matching an available catalog (exact, shorter form, then same language); invalid input is ignored. The middleware stores the bundle and locale in the context (i18n.Locale, i18n.WithLocale, i18n.BundleFrom), sets Content-Language, and adds Vary: Cookie when the cookie or session was consulted and Vary: Accept-Language when the header was (a locale from the query needs no Vary: the URL is the cache key). Rename or disable ("-") the sources with Query, Cookie and SessionKey. To remember a user's choice, set the cookie or session value yourself.
Order picks the sources and their order (a source left out is off), and Locales limits the locales a request can select (it must include the default, which stays the fallback). cfg.Validate(bundle) reports unknown or repeated sources and locales without a catalog. An invalid config fails closed: i18n.Middleware returns the error (i18n.NewMiddleware is a deprecated alias that go fix rewrites). vukem.DefaultMiddlewareWith takes the config as MiddlewareOptions.I18n (and APIMiddlewareWith as APIMiddlewareOptions.I18n) and returns the error at boot:
err := vukem.DefaultMiddlewareWith(app, r, vukem.MiddlewareOptions{
I18n: &i18n.MiddlewareConfig{
Order: []i18n.Source{i18n.SourceQuery, i18n.SourceHeader}, // no cookie, no session
Locales: []string{"en", "pt-BR"},
},
})Validation messages#
The middleware also translates web.Parse/Bind validation messages from validation.* keys in your catalogs, so validation errors follow the request's locale.
Framework messages#
Errors from auth, social and orm.Unique/Exists carry message keys and keep their English text as the fallback: auth.failed, auth.unauthenticated, auth.unverified, auth.token_ability ({ability}), social.unknown_provider, social.state, social.denied, social.failed, validation.unique and validation.exists ({field}). WithTranslations adds built-in English and Brazilian Portuguese catalogs for them (Bundle.AddBuiltin) to the catalogs your app already has (en; pt-BR and pt); a key in your own catalog always wins. Your own errors can do the same with errs.NewKey(status, key, fallback, params) or errs.Translate(ctx, key, fallback, params).