Internationalization (i18n)

On this page

Internationalization (i18n)

Translate your docs into other languages, each getting its own URL prefix, its own <html lang dir>, and an automatic language switcher - no plugin, no separate build step.

Adding a locale

Translated content lives in docs/i18n/<code>/, mirroring your regular docs/ tree page-for-page:

docs/
โ”œโ”€โ”€ index.md
โ”œโ”€โ”€ guides/
โ”‚   โ””โ”€โ”€ setup.md
โ””โ”€โ”€ i18n/
    โ”œโ”€โ”€ es/
    โ”‚   โ”œโ”€โ”€ index.md
    โ”‚   โ””โ”€โ”€ guides/
    โ”‚       โ””โ”€โ”€ setup.md
    โ””โ”€โ”€ ar/
        โ””โ”€โ”€ index.md

<code> becomes both the folder name and the built URL prefix (docs/i18n/es/guides/setup.md โ†’ /es/guides/setup/), so keep it short - a bare language code (es, fr) or a language-region pair (pt-BR, zh-Hans) both work, letters/digits/hyphens only. Your regular docs/ tree is always the default locale, built unprefixed at the site root exactly as it is today - adding docs/i18n/ doesn't change anything about it.

Give each locale a display label (and, for a right-to-left language, its own direction) in bxsites.json:

{
	"i18n": {
		"defaultLocale": { "code": "en", "label": "English" },
		"locales": [
			{ "code": "es", "label": "Espaรฑol" },
			{ "code": "ar", "label": "ุงู„ุนุฑุจูŠุฉ", "dir": "rtl" },
			{ "code": "pt-BR", "label": "Portuguรชs (Brasil)", "flag": "๐Ÿ‡ง๐Ÿ‡ท" }
		]
	}
}

defaultLocale only needs setting if your default locale isn't English; locales is the list of everything else. Every docs/i18n/<code>/ folder builds automatically once it exists - locales just supplies its display label and text direction. A folder with no matching locales entry still builds (using its bare code as its own label), so this is metadata, not what turns the feature on or off.

flag is optional - the switcher already picks a sensible flag emoji for ~40 common language codes on its own (checking a region code like pt-BR first, then falling back to the base language pt). Set flag yourself only to override that guess, or for a code the built-in lookup doesn't recognize (it falls back to a plain ๐ŸŒ in that case).

What gets built

Each locale is a real, fully independent build - its own search-index.json, its own assets/, everything a normal build produces - written under site/<code>/ (site/es/, site/ar/). Nothing needs enabling per-locale: once docs/i18n/es/ exists, bxSites build picks it up on its own.

Untranslated pages

A locale doesn't need every page translated before it's usable. A page missing from docs/i18n/es/ still builds at its expected URL - showing the default locale's own content, with a small notice at the top of the page saying it hasn't been translated yet. Nothing 404s, nothing looks half-built while a translation is in progress.

Every locale's nav is always the exact same shape as the default locale's own - same pages, same order, same nesting (whatever docs/'s own folder structure, or an explicit nav, already produces) - just with each page's title/content swapped in from its own translation where one exists. This is also what makes the language switcher work: switching languages lands you on the same page, translated or not, never that locale's homepage.

The language switcher

Once more than one locale exists, every theme renders a flag-icon language dropdown in the header automatically - nothing to opt into, same as the version switcher. It shows the current locale's flag as a trigger; opening it lists every locale with its own flag and label, the current one marked active. Pick a locale you're not currently building and it simply won't render at all.

Versioned and translated docs

See Versioning for docs/versions/<name>/ itself. Versions and locales compose one level: put a docs/versions/<name>/i18n/<code>/ folder next to a version's own pages, mirroring that version's own structure exactly the same way a top-level docs/i18n/<code>/ mirrors docs/ itself:

docs/
  versions/
    2.0/
      index.md
      guides/
        setup.md
      i18n/
        es/
          index.md          # translated
          guides/
            setup.md        # untranslated pages still fall back, same as top-level i18n

This builds site/versions/2.0/es/. A version's own default-locale pages (site/versions/2.0/) get a language switcher too, listing only the locales that version itself has translations for - a version with no i18n/ subfolder of its own renders exactly as it did before this existed, no switcher shown. Switching version always drops back to that version's own default locale (never assumes the target version has the same translation); switching locale always stays on the current version.

What's out of scope (for now)

  • Theme chrome stays in English. "Edit this page," "Last updated," the search placeholder, and similar UI strings aren't translated per locale yet - only your own page content is. A locale's actual reading experience is fully translated; the surrounding theme furniture isn't.
  • RTL layout mirroring is baseline, not pixel-perfect. dir="rtl" is set correctly, and the sidebar/header genuinely mirror, but a few decorative details (an admonition's accent-bar side, for instance) don't flip yet.
  • No automated translation. Every docs/i18n/<code>/ file is authored by hand, the same as any other markdown page - there's no machine-translation step.

Custom icons and includes

A custom: icon reference and a ::: include both resolve against your project's own docs/assets//reusable-content, regardless of which locale is being built - these are shared assets, not something a translator needs to duplicate per locale.

SEO

Every locale's pages are included in sitemap.xml and llms.txt alongside the default locale's own, the same way versioned pages are.

Edit this page Download Markdown Last updated Aug 23, 2026, 2:17:28 AM