Internationalization
VEF includes an application i18n layer for framework messages, validation output, and error responses.
Supported Languages
The framework currently embeds:
| Language code |
|---|
zh-CN |
en |
Default application language:
| Setting | Value |
|---|---|
| default language | zh-CN |
i18n.GetSupportedLanguages() returns a copy of the supported-language slice, so modifying the returned slice does not mutate framework state. The current shipped en.json and zh-CN.json catalogs contain the same 195 message IDs.
Public API
The complete public API for github.com/coldsmirk/vef-framework-go/i18n and github.com/coldsmirk/vef-framework-go/i18n/locales is:
| API | Purpose |
|---|---|
i18n.DefaultLanguage | default language constant (zh-CN) |
i18n.GetSupportedLanguages() | return a copy of the supported language codes (zh-CN, en) |
i18n.IsLanguageSupported(code) | report whether a language code is supported |
i18n.CurrentLanguage() | read the current global language code |
i18n.SetLanguage(code) | atomically switch the process-level global translator |
i18n.T(messageID, data...) | translate with graceful fallback to the message ID |
i18n.Te(messageID, data...) | translate with explicit error reporting |
i18n.New(config) | create a dedicated translator instance from i18n.Config |
i18n.Config | constructor config type for dedicated translators |
i18n.Config.Locales | embedded locale file set used by i18n.New(config) |
i18n.Translator | interface implemented by dedicated translator instances |
i18n.Translator.T(messageID, data...) | translate with graceful fallback on a dedicated translator |
i18n.Translator.Te(messageID, data...) | translate with explicit errors on a dedicated translator |
i18n.ErrUnsupportedLanguage | sentinel wrapped when a requested language is unsupported |
i18n.ErrMessageIDEmpty | sentinel returned when Te receives an empty message ID |
locales.EmbedLocales | framework-shipped embedded locale file set |
Most application code only needs i18n.T(...).
Translator Model
The public abstraction is i18n.Translator:
| Method | Purpose |
|---|---|
T(messageID, data...) | translate and fall back to the message ID |
Te(messageID, data...) | translate and return explicit errors |
Translation behavior is intentionally simple:
| Call | Behavior |
|---|---|
i18n.T("known_key") | returns the localized message from the active global translator |
i18n.T("missing_key") | logs the translation error and returns "missing_key" as the fallback |
i18n.Te("known_key") | returns the localized message and a nil error |
i18n.Te("missing_key") | returns "" and an error wrapping the localization failure |
i18n.Te("") | returns "" and i18n.ErrMessageIDEmpty |
templateData is variadic for call-site convenience, but the implementation uses only the first map[string]any value. Additional maps are ignored.
Configuration Model
The constructor config type is:
| Type | Purpose |
|---|---|
i18n.Config | supplies embedded locale files through Locales embed.FS |
locales.EmbedLocales | default embedded locale file set |
locales.EmbedLocales embeds the framework's shipped *.json locale files,
including the built-in zh-CN and en catalogs.
i18n.New(config) creates an independent i18n.Translator. It chooses the language from VEF_I18N_LANGUAGE or i18n.DefaultLanguage when the translator is constructed, and it does not change the process-level global translator used by package-level i18n.T(...) and i18n.Te(...).
i18n.Config.Locales must provide every supported locale file. With the current supported-language list, that means both zh-CN.json and en.json; an empty embed.FS or a missing supported file makes i18n.New(config) return an error.
Error Sentinels
| Error | Meaning |
|---|---|
i18n.ErrUnsupportedLanguage | requested language code is not supported |
i18n.ErrMessageIDEmpty | translation message ID is empty |
i18n.SetLanguage(...) wraps i18n.ErrUnsupportedLanguage, so callers can use errors.Is(err, i18n.ErrUnsupportedLanguage). i18n.Te("") returns i18n.ErrMessageIDEmpty directly, so errors.Is(err, i18n.ErrMessageIDEmpty) also works.
Language Selection
At startup, the framework chooses the language in this order:
| Priority | Source |
|---|---|
| 1 | VEF_I18N_LANGUAGE |
| 2 | framework default language (zh-CN) |
VEF_I18N_LANGUAGE is exposed by the config package as config.EnvI18NLanguage; it is not an i18n package API.
During package initialization, the global translator is created from locales.EmbedLocales. If the embedded catalogs cannot be loaded, initialization panics because framework messages would otherwise be unusable.
i18n.SetLanguage(...) can switch the process-level translator later, which is especially useful in tests. Passing an empty string to i18n.SetLanguage("") re-reads VEF_I18N_LANGUAGE and falls back to i18n.DefaultLanguage if the environment variable is empty. When the language changes, the active locale file set is preserved.
Package-level i18n.T(...) and i18n.Te(...) load the active translator through an atomic pointer, and i18n.SetLanguage(...) publishes a fresh immutable translation state through the same pointer. This makes concurrent translation calls and language switches race-free, although a request can naturally observe either the old language or the new language around the switch boundary.
Where i18n Appears Automatically
Even if you never call the i18n package directly, it already affects:
| Area | Effect |
|---|---|
result.Ok(...) and result.Err(...) | localized success and error messages |
| validation output | translated field labels and custom rule messages |
| built-in resource errors | localized framework responses |
So i18n in VEF is not only for UI text. It is part of the backend response contract.
Minimal Example
package userinfo
import (
"github.com/gofiber/fiber/v3"
"github.com/coldsmirk/vef-framework-go/i18n"
"github.com/coldsmirk/vef-framework-go/result"
"github.com/coldsmirk/vef-framework-go/security"
)
func UserInfoNotImplemented(ctx fiber.Ctx) error {
return result.ErrNotImplemented(
i18n.T(security.ErrMessageUserInfoLoaderNotImplemented),
)
}
Validation Integration
Validation integrates with i18n in two important ways:
| Mechanism | Effect |
|---|---|
label_i18n | field labels are translated |
| custom validator rule translations | framework custom rules return localized messages |
That makes request validation output much more readable in real applications.
Practical Advice
- use
i18n.T(...)for ordinary application code - use
i18n.Te(...)only when you need explicit translation error handling - treat
i18n.SetLanguage(...)as mainly a testing or controlled runtime helper - remember that result messages are already localized, so backend responses are part of your i18n surface
Next Step
Read Validation to see how translated field labels surface in request errors.