bext.dev
DocsApplication Toolkit
Application Toolkit2 min read
On this page

Localization (i18n)

@bext-stack/framework/i18n gives PRISM apps translation the Laravel way: per-language message catalogs, t(key, params) with {placeholder} interpolation, pluralization, a fallback locale, and locale detection from the request. It's the shared replacement for the i18n.ts copies each site was hand-rolling, and the app-developer counterpart to the Rust I18n capability.

Setup#

ts
import { createI18n } from "@bext-stack/framework/i18n";

const i18n = createI18n({
  default: "en",
  fallback: "en",                     // consulted when a key is missing in the active locale
  locales: {
    en: {
      greeting: "Hello, {name}!",
      cart: "Your cart is empty | 1 item | {count} items",   // zero | one | other
    },
    fr: {
      greeting: "Bonjour, {name} !",
      cart: "Panier vide | 1 article | {count} articles",
    },
  },
});

Translating#

ts
export async function loader({ request }) {
  const t = i18n.for(i18n.detect(request));   // a Translator for the request's locale

  return {
    hi:   t.t("greeting", { name: "Ada" }),   // → "Hello, Ada!"    (interpolation)
    cart: t.n("cart", count),                 // → "3 items"        (pluralization)
  };
}
Member Does
i18n.for(locale) a Translator bound to a locale (unknown → default)
i18n.detect(request) resolve the request's locale (see below)
t.t(key, params?) translate + interpolate {placeholder}
t.n(key, count, params?) pluralize (one|other or zero|one|other), {count} interpolated
t.has(key) is the key present (incl. fallback)?

Missing keys fall back to the fallback locale, then to the key itself — so a partial translation degrades gracefully instead of throwing.

Locale detection#

detect(request) resolves in this order, first hit wins:

  1. Path prefix/fr/dashboardfr.
  2. Cookie — a locale cookie (name configurable).
  3. Accept-Language — respects q weights and matches the base language (fr-CAfr).
  4. Default.
Tip

For site-wide prefixed routing (/en/…, /fr/…) you can also configure [i18n] in bext.config.toml (the Rust capability). This module is the in-code path for translating strings and for custom/page-scoped detection.

Try It#

The live i18n demo switches across en/fr/es/ja with live interpolation and pluralization. Source in sites/demo/src/app/examples/i18n/page.tsx.

See Also#

  • I18n — the Rust capability + bext.config.toml routing.
  • Validation — localise validation messages via the { required, invalid } options.
  • Application Toolkit — the rest of the TypeScript app layer.
Edit this page ↗Need a hand? ↗
FIND YOUR NEXT STEP

Start with a topic, a command, or a question.