Askr documentation
Server & APIs

Internationalization

Internationalization at the Askr server boundary, with validated input and explicit failure states.

Example

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

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.