Search

Search Kavel...

Documentation menu

Internationalization

Type-safe, URL-based localization with Paraglide, every message is a function, checked at compile time.

What you get

  • Curated message catalogs (English, Dutch, German, French, and Spanish) and you choose which to include at create time
  • The base locale served at unprefixed URLs and other locales under a /xx/ prefix (/de/…), resolved before routing, so route files stay locale-agnostic
  • A LocaleSwitcher component and a compiled, tree-shakeable message runtime

Choosing languages

When you select the i18n module, the CLI asks which languages to include, or pass them non-interactively. The first locale is the base (fallback); only the catalogs you pick are kept.

bash
bun create kavel@latest my-app --locales en,de,fr

Using messages

Messages compile to typed functions on the m object. Reference a key that doesn't exist and it won't compile:

tsx
import { m } from "@/paraglide/messages";

export function Hero() {
  return <h1>{m.hero_title()}</h1>;
}

Tip

Message keys are generated as stable, pseudo-random names (e.g. silver_lamp_cord_meadow) rather than describing the copy. This keeps keys stable when wording changes and avoids merge churn. Edit the text in the JSON catalogs, not the key.

Adding a locale

  1. Add the locale to project.inlang/settings.json and to the locales list in apps/web/src/lib/i18n.ts.
  2. Create messages/<locale>.json with the translated keys.
  3. Recompile the runtime.
bash
bun i18n:compile

Translated pathnames

The base locale is served unprefixed (/pricing); other locales sit under a /xx/ prefix. Each route keeps its path by default; to translate the path per locale (so Dutch gets /nl/prijzen), add an entry to routeLocalizations in src/lib/i18n.ts:

ts
export const routeLocalizations = {
  "/about": { en: "/about", nl: "/over-ons" },
};