i18n¶
Internationalisation with Accept-Language detection and go-i18n translations.
Package: github.com/oliverandrich/burrow/contrib/i18n
Depends on: none
Setup¶
Adding Translations¶
Apps contribute translations by implementing the burrow.HasTranslations interface. The i18n app auto-discovers all registered apps during Configure() and loads their translation files:
//go:embed translations
var translationFS embed.FS
func (a *App) TranslationFS() fs.FS { return translationFS }
The returned fs.FS must contain a translations/ directory with TOML files (see Translation Files below).
Translation Files¶
Translation files are TOML files in a translations/ directory:
Example translations/active.en.toml:
[welcome]
other = "Welcome, {{.Name}}!"
[notes_count]
one = "{{.Count}} note"
other = "{{.Count}} notes"
Example translations/active.de.toml:
[welcome]
other = "Willkommen, {{.Name}}!"
[notes_count]
one = "{{.Count}} Notiz"
other = "{{.Count}} Notizen"
Using in Templates¶
The i18n app implements HasRequestFuncMap and provides these functions in all templates:
| Function | Description |
|---|---|
{{ lang }} |
Current locale string (e.g., "en", "de") |
{{ t "key" }} |
Simple translation lookup |
{{ tData "key" .DataMap }} |
Translation with template data |
{{ tPlural "key" .Count }} |
Pluralised translation |
{{ define "notes/list" -}}
<html lang="{{ lang }}">
<body>
<h1>{{ t "notes-title" }}</h1>
<p>{{ tData "welcome" .WelcomeData }}</p>
<span>{{ tPlural "notes_count" .Count }}</span>
</body>
{{- end }}
Go API¶
T — Simple Translation¶
import "github.com/oliverandrich/burrow/contrib/i18n"
msg := i18n.T(ctx, "welcome")
// "Welcome, {{.Name}}!" (raw, no data substitution)
TData — Translation with Template Data¶
TPlural — Pluralised Translation¶
msg := i18n.TPlural(ctx, "notes_count", 5)
// "5 notes"
msg = i18n.TPlural(ctx, "notes_count", 1)
// "1 note"
Locale — Current Locale¶
All functions fall back to the message ID if no translation is found.
Language Matching & Fallback¶
The middleware reads the browser's Accept-Language header and matches it against the configured supported languages using Go's golang.org/x/text/language matcher. If no match is found, the default language is used.
Examples (with default config en,de):
| Accept-Language | Resolved Locale | Reason |
|---|---|---|
de-AT,de;q=0.9 |
de |
Regional variant matches base language |
fr-FR,fr;q=0.9 |
en |
French not supported, falls back to default |
de;q=0.8,en;q=0.9 |
en |
English has higher quality value |
| (empty) | en |
No header, uses default |
This means you never need to handle unsupported languages yourself — the matcher always resolves to one of the configured languages.
Auto-Discovery¶
During Configure(), the i18n app iterates all registered apps and loads translations from any that implement burrow.HasTranslations (see Adding Translations above). The auth app uses this pattern to contribute its English and German translations automatically.
Translating Validation Errors¶
When using validation, error messages default to English. TranslateValidationErrors translates them in-place using the current locale:
import "github.com/oliverandrich/burrow/contrib/i18n"
func (h *Handlers) Create(w http.ResponseWriter, r *http.Request) error {
var req struct {
Name string `form:"name" validate:"required"`
Email string `form:"email" validate:"required,email"`
}
if err := burrow.Bind(r, &req); err != nil {
var ve *burrow.ValidationError
if errors.As(err, &ve) {
i18n.TranslateValidationErrors(r.Context(), ve)
return burrow.RenderTemplate(w, r, http.StatusUnprocessableEntity, "myapp/form", map[string]any{
"Errors": ve,
})
}
return err
}
// ...
}
Built-in Translation Keys¶
The i18n app ships with English and German translations for all built-in validation tags:
| Key | English Template |
|---|---|
validation-required |
{{.Field}} is required |
validation-email |
{{.Field}} must be a valid email address |
validation-min |
{{.Field}} must be at least {{.Param}} |
validation-max |
{{.Field}} must be at most {{.Param}} |
validation-len |
{{.Field}} must be exactly {{.Param}} characters |
validation-gte |
{{.Field}} must be greater than or equal to {{.Param}} |
validation-lte |
{{.Field}} must be less than or equal to {{.Param}} |
validation-url |
{{.Field}} must be a valid URL |
Template variables: {{.Field}} is the field name, {{.Param}} is the tag parameter (e.g., "3" for min=3).
Overriding Translations¶
Your app's translation files are loaded after the built-in ones (last-loaded wins). To customise a validation message, define the same key in your TOML file:
Fallback Behaviour¶
- No localiser in context: original English message is preserved
- Unknown validation tag: the key
validation-{tag}won't match any translation, so the original message is preserved
Configuration¶
| Flag | Env Var | Default | Description |
|---|---|---|---|
--i18n-default-language |
I18N_DEFAULT_LANGUAGE |
en |
Default language |
--i18n-supported-languages |
I18N_SUPPORTED_LANGUAGES |
en,de |
Comma-separated supported languages |
Interfaces Implemented¶
| Interface | Description |
|---|---|
burrow.App |
Required: Name(), Register() |
Configurable |
Language configuration flags |
HasMiddleware |
Locale detection middleware |
HasRequestFuncMap |
Provides lang, t, tData, tPlural to templates |