Overview
Voltro's i18n layer (@voltro/i18n) — an opinionated wrap over react-intl, auto-wired from a single app.config.ts field, with cookie + Accept-Language locale resolution.
Voltro ships internationalization in @voltro/i18n — an opinionated, thin wrap over react-intl plus framework auto-wiring. A web app gets a working <I18nProvider> from a single field in app.config.ts. You never write the provider, and you never import @voltro/i18n in your layout.
The public surface is intentionally small — <I18nProvider>, <T>, useT, useTFn, useLocale, useMessages, defineCatalog, defineLocale, pickCatalog. Power users who need an API the wrap doesn't expose (custom formatters, rich-text with React-element values, IntlProvider's timeZone / formats props) import { … } from 'react-intl' directly. The wrap is opt-in, not lock-in — the library is in your node_modules, the wrap is optional.
When to enable
Set locales in app.config.ts the moment your app ships more than one user-facing language, or expects to soon. The infrastructure has zero cost when locales is unset — no provider is generated, no bundle overhead. Enabling early avoids a painful retro-fit when the first translation request lands.
Don't enable just because the app could theoretically be translated. The framework's empty-i18n state is fine for English-only apps, and adding the wiring later is a one-config-field change.
Setup — auto-wiring
// apps/<project>/<app>/app.config.ts
export default {
type: 'web' as const,
name: 'myApp',
port: 5191,
// Codes the app SHIPS catalogs for. Every code listed here MUST
// have a corresponding `src/locales/<code>.ts` file or boot fails.
locales: ['en', 'de'] as const,
// Fallback locale when no cookie / Accept-Language matches a
// supported one. MUST be in `locales`. Pick your source-of-truth
// language — typically English.
defaultLocale: 'en' as const,
}The framework's generated .framework/app.tsx wraps the Router in <I18nProvider> automatically. You don't write the provider yourself; you don't import @voltro/i18n in your layout. Each code in locales must have a matching src/locales/<code>.ts catalog file — a missing file fails boot, not silently at runtime.
Locale resolution order
Server-side, the active locale is determined by, in priority order:
voltro:langcookie — the user's explicit choice (written by@voltro/ui-shadcn's ProfileMenu).Accept-Languageheader — the browser/OS preference, q-weighted and sorted per RFC 4647.defaultLocale— last-resort fallback.
The resolved locale is guaranteed to be one of the codes in locales. Any unsupported value (a cookie pointing at a code you no longer ship, a browser asking for xx-YY) falls through to the next signal. RFC 4647 lookup strips subtags one segment at a time — de-CH-1996 → de-CH → de — so a de catalog serves a de-CH browser.
The client side mirrors #1 (cookie) and #3 (default) for hydration safety. Accept-Language is server-only because navigator.languages can diverge from what the server saw, which would cause a hydration mismatch.
See Catalogs for the type-safe catalog convention and the component hooks, Plurals & formatting for CLDR plural selection and the Intl-backed date / number / relative-time hooks, and URL strategies for cookie-only vs URL-prefix routing.