Locale Routing

The active locale lives in the URL path. Routing is built into the core @opentf/afw router, so a single locale-agnostic route table serves every language.

prefix_except_default

The default locale is served at the bare path; every other locale is prefixed:

Routeen (default)frja
///fr/ja
/pricing/pricing/fr/pricing/ja/pricing
/blog/hello/blog/hello/fr/blog/hello/ja/blog/hello

Define the locales in afw.config.js:

JavaScriptafw.config.js
export default {
  i18n: {
    locales: ["en", "fr", "ja"],
    defaultLocale: "en",
  }
};

Pass that configuration to mountApp in your browser entry. The compiler plugin does not read it automatically:

JavaScriptentry.js
import { mountApp } from "@opentf/afw";
import { pages, guard } from "@afw/routes";
import config from "./afw.config.js";

mountApp({ pages, guard, i18n: config.i18n, target: document.getElementById("app") });

Use the same locales in createI18n and pass i18n: config.i18n to your prerender helper or request renderer.

Switching locale is a navigation

There is no in-place locale toggle — to change language you navigate to the localized URL. Because each URL is exactly one locale, every page is independently cacheable, SEO-friendly, and free of hydration locale mismatches.

Reading the locale

router.locale is reactive — read it in any binding and it updates on navigation:

JSX
import { router } from "@opentf/afw";

<span class="badge">{router.locale}</span>

<Link> automatically keeps navigation in the active locale — no per-link work:

JSX
import { Link } from "@opentf/afw";

// On /fr/… this renders href="/fr/pricing"; on the default locale, "/pricing".
<Link href="/pricing">Pricing</Link>

Need to build a localized path yourself (e.g. a language switcher)? Use localizePath — the default locale stays bare, others get a prefix, and any existing prefix is replaced:

JavaScript
import { localizePath } from "@opentf/afw";

localizePath("/pricing", "fr");   // "/fr/pricing"
localizePath("/pricing", "en");   // "/pricing"   (default → bare)
localizePath("/fr/pricing", "ja"); // "/ja/pricing" (re-pointed)

A language switcher, then, is just links to the current path under each locale:

JSX
<Link href={localizePath(router.pathname, "fr")}>Français</Link>
<Link href={localizePath(router.pathname, "ja")}>日本語</Link>

Static generation (SSG)

The prerender helper, supplied with the i18n configuration, renders every route once per locale — the default bare, others under their prefix — each with the right <html lang> and hreflang alternates for SEO:

dist/
├── index.html            → en
├── pricing/index.html    → en
├── fr/index.html         → fr
├── fr/pricing/index.html → fr
└── ja/…                  → ja

Server rendering (SSR) + detection

An app-provided request server can derive the locale from the URL prefix and use Accept-Language or an afw_locale cookie to redirect a bare path to a non-default locale. This requires server wiring; the native compiler plugin does not install locale-detection middleware automatically.

Programmatic control

In a routed app the locale comes from the URL. For previews, tests, or a custom flow, setLocale sets router.locale directly without navigating:

JavaScript
import { setLocale } from "@opentf/afw";

setLocale("fr"); // router.locale → "fr"; t()/fmt re-render
Last updated on
Edit this page