Skip to content
Engineering1 min read

Internationalization

Path-based locales, synchronous pre-rendering, strict key parity and hreflang.

Updated Nov 6, 20251 min readEdit this page
On this page

Locale is part of the URL. English is unprefixed; Chinese lives under /zh, so every page is shareable, cacheable and indexable in both languages.

First visit#

A tiny script in <head> reads the stored preference, then navigator.languages. A Chinese browser entering an unprefixed URL is sent to /zh before first paint; an English browser stays. The redirect is deliberately one-way: a /zh URL is never redirected, same-site deep links keep the language being read, and crawlers are skipped.

  • A rendered locale is stored, and the language menu writes it before navigating.
  • The redirect preserves the path, query and hash.
  • Both locales are pre-rendered; the script only chooses an entry point, never generates a page.

Routing#

Locale helperstypescript
localePath('zh', '/docs/mcp') // '/zh/docs/mcp'stripLocale('/zh/docs/mcp')    // '/docs/mcp'switchLocalePath('/lumo', 'zh') // '/zh/lumo'

The route table is generated per locale, so a product page and its translation are the same component. The locale provider derives the current language from useLocation() and selects the matching i18next instance.

Synchronous pre-render#

i18next is initialized with initAsync: false and bundled resources. That makes initialization synchronous, which is required for static generation: a Chinese route must contain Chinese HTML at build time, not after hydration.

One instance per localetypescript
const instance = i18next.createInstance();instance.init({  lng: locale,  resources,  initAsync: false,});

Content#

  • UI copy lives in app/lib/i18n/resources/{en,zh}.ts.
  • Long-form docs live in app/content/docs/{en,zh}.ts using the same DocBlock model.
  • A test compares the English and Chinese key trees; a missing key fails before runtime.
  • Technical identifiers (chrome.userScripts, GM_setValue) stay in English.

SEO#

  • Each route emits its own title, description, canonical, Open Graph and Twitter tags.
  • hreflang pairs both languages and includes x-default for the English page.
  • The sitemap is generated from the pre-rendered HTML after the build.

Add a locale#

  1. 1

    Add the resource

    Create the translation object, typed against the English shape.

  2. 2

    Extend the manifest

    Add the locale to LOCALES and LOCALE_META. Route generation and the language menu pick it up.

  3. 3

    Add content

    Translate the docs collection. The integrity test will list missing slugs.

  4. 4

    Pre-render

    Add the locale to the doc-path list in react-router.config.ts.