i18n
Installation
SKILL.md
i18n
Locales are declared in config/i18n.ts (supportedLocales, defaultLocale, loaders). Translation JSON lives per module at app/<mod>/resources/lang/<locale>/<mod>.json. Keys are namespaced by module (common.layout.navMain.dashboard), so the file at app/common/resources/lang/en/common.json doesn't repeat common. inside — the loader adds it. Backend uses ctx.i18n.t('key'); frontend uses useTranslation(). Locale detection runs as an HTTP middleware that prefers an X-User-Language header → a user-locale cookie → the browser's Accept-Language. Authenticated users have User.locale as the authoritative source: the switch endpoint writes both cookie and DB; every login flow syncs the cookie from user.locale if set.
Rules
- Adding a key requires an entry in every supported locale. There is no auto-generate. Missing keys fall back to the default locale at runtime — the mixed-language UI is the symptom.
- Key naming:
<mod>.<section>.<key>. The<mod>prefix comes from the file location; inside<mod>.jsonyou don't repeat it. E.g. insidecommon.json:
and consumed as{ "layout": { "navMain": { "dashboard": "Dashboard" } } }t('common.layout.navMain.dashboard'). - Frontend:
useTranslation()returns{ t, changeLanguage, language }.t(key, params?)supports ICU interpolation. - Backend:
ctx.i18n.t('key', params?). The i18n instance is bound toHttpContextby the locale-detection middleware. Use in controllers, actions (ifi18nwas in the input), and mail classes. - ICU interpolation:
{name}in the string,t('key', { name: 'Alice' })at the call site. Also supports plurals and selectors. - Validation messages: VineJS uses the request's i18n instance through the messages provider bound in the locale-detection middleware. Localize by adding keys under
<field>.<rule>in<mod>/resources/lang/<locale>/validator.json. - User preference:
User.localeis a nullable column. When authenticated, the switch endpoint updates both the cookie anduser.locale. Login flows syncuser-localecookie fromuser.localeif set. Pre-login (marketing/auth pages) uses cookie /Accept-Language, never DB. - Locale switcher UI:
<LanguageSwitcher />lives in every logged-in shell — see [[layout-shells]]. - Adding a locale means updating
supportedLocalesinconfig/i18n.ts, adding a folder under every module'sresources/lang/, and confirming every key exists in the new locale.