Askr
Server & APIs

Internationalization

Use internationalization with the current published Askr packages.

  • @askrjs/i18n0.0.2

How to use internationalization

Create the platform service at the application composition root and pass its provider-neutral contract into routes or components.

  1. Import the published @askrjs/i18n entrypoint.
  2. Keep internationalization configuration next to the component, route, or server composition root that owns it.
  3. Handle pending, unavailable, cancellation, and failure states, then verify the production path.
import { createI18n } from '@askrjs/i18n';

export const i18n = createI18n('en', {
  en: { greeting: (name: string) => 'Hello, ' + name },
  fr: { greeting: (name: string) => 'Bonjour, ' + name },
});

<i18n.Scope locale="en">
  {i18n.text('greeting', 'Askr')}
</i18n.Scope>

Locale ownership

`@askrjs/i18n` deliberately doesn't decide which locale is active — no cookie parsing, no `Accept-Language` sniffing, no global state. Your app resolves the locale however it wants (URL prefix, subdomain, user profile) and installs it by rendering `<i18n.Scope locale="fr" dir="ltr">` around the relevant subtree. Because `Scope` is a component rather than a module-level singleton, you can nest scopes and run several locales in the same tree if you need to.

Message lookup

Catalog entries are typed functions, not string templates — `welcome: ({ name }: { name: string }) => ...` — so `i18n.text('welcome', { name: 'Ada' })` is checked against the exact parameter type of that message. `CatalogKey` and `MessageArgs` are derived automatically from your catalogs, so autocomplete and argument checking work without any codegen step. `i18n.format(locale, key, ...args)` gives you the same lookup for a specific locale rather than whichever one is currently scoped.

Formatting

Number, date, and currency formatting isn't a separate API — it's just `Intl` calls written inside your catalog functions, as shown in the README's `total` message using `Intl.NumberFormat`. This keeps formatting rules colocated with the locale that defines them instead of scattered through a generic formatter service, and it means anything `Intl` supports is available with no wrapper API to learn.

SSR consistency

`i18n.dehydrate()` captures the active locale, text direction, and catalog identity as a versioned snapshot (`I18nHydration`), which you serialize into the server-rendered page. On the client, pass that same snapshot to `<i18n.Scope hydration={snapshot}>` instead of `locale`/`dir`, and the two render passes stay in sync without re-deriving the locale from scratch in the browser. `I18nScopeProps` enforces this at the type level — you provide either `locale`/`dir` or `hydration`, never a mix of both.