Internationalization
Serve your docs in multiple languages with locale-aware routing, per-language navigation, translated UI, and SEO — all convention-first.
Blume serves one project in many languages. Drop translated files into the right place and Blume wires up routing, the language switcher, per-locale navigation, and SEO for you — there’s no separate routing layer to maintain. It’s opt-in: without an i18n block, your site stays single-language exactly as before.
Enable it
Add an i18n block listing your locales and which one is the default:
i18n: {
defaultLocale: "en",
locales: [
{ code: "en", label: "English" },
{ code: "fr", label: "Français" },
{ code: "ar", label: "العربية", dir: "rtl" },
],
}
Each locale has a code (used in URLs), a label (shown in the language switcher), and an optional dir for right-to-left scripts ("ltr" by default).
Organize translated content
The default locale lives at your content root. Every other locale is a top-level folder named by its code, mirroring the default structure:
docs/
index.mdx -> /
guides/quickstart.mdx -> /guides/quickstart
fr/
index.mdx -> /fr
guides/quickstart.mdx -> /fr/guides/quickstart
ar/
index.mdx -> /ar
| File | Route |
|---|---|
docs/index.mdx |
/ |
docs/guides/quickstart.mdx |
/guides/quickstart |
docs/fr/guides/quickstart.mdx |
/fr/guides/quickstart |
You only translate the files you want — everything else falls back automatically (see Fallbacks).
Filename suffixes
Prefer to keep translations next to the original? Set parser: "dot" and name files with a locale suffix instead of using folders:
docs/
guides/quickstart.mdx -> /guides/quickstart (default)
guides/quickstart.fr.mdx -> /fr/guides/quickstart (French)
Good for sparse translations — colocate the few pages you’ve translated without mirroring the whole tree.
Shared files
For content that’s the same in every language — a changelog, a status page — add a $ marker so one file serves all locales without duplication:
docs/changelog.$.mdx -> /changelog and /fr/changelog (same content)
docs/guides/meta.$.ts (folder meta applied to every locale)
A locale-specific meta.ts still overrides the shared one for that language.
Default-locale URLs
By default the default locale has no URL prefix (/, /guides/quickstart) while other locales are prefixed (/fr/…). This keeps your primary language’s URLs clean. To prefix every locale, including the default:
i18n: {
// …
hideDefaultLocalePrefix: false, // /en/…, /fr/…
}
Per-locale navigation
Each language gets its own sidebar, built from that locale’s files — so translations can diverge in structure, ordering, or labels. Folder meta.ts files resolve per locale, too: under the default dir parser, put a meta.ts under fr/guides/ to order the French group independently. Under the dot parser translations sit next to the originals, so a folder’s meta.ts applies to every locale. Everything else about navigation works the same, per language.
Fallbacks
When a page isn’t translated yet, Blume renders the fallback locale’s content at the localized URL — so the link works, the page is fully pre-rendered, and search engines aren’t sent to a dead end. The fallback defaults to your defaultLocale:
i18n: {
// …
fallbackLocale: "en", // default; set to null to 404 instead
}
Fallback pages are excluded from the search index and aren’t advertised as real translations in hreflang, so untranslated content doesn’t compete for ranking. They still appear in that locale’s sidebar, so navigation stays complete — a reader can reach every page in any language.
The language switcher
When i18n is on, a language switcher appears in the header automatically, generated from your locales. For each page it links the matching translation in every language; where a translation is missing it links the fallback page and marks it as not translated. There’s nothing to configure.
Translated UI
Blume ships built-in translations for its own interface chrome — “On this page”, “Search”, “Edit on GitHub”, and the rest — so a locale with a built-in pack gets translated UI out of the box. You only translate your content.
Packs ship for 36 languages — Arabic, Bengali, Bulgarian, Catalan, Chinese (Simplified and Traditional), Croatian, Czech, Danish, Dutch, Finnish, French, German, Greek, Hebrew, Hindi, Hungarian, Indonesian, Italian, Japanese, Korean, Norwegian, Persian, Polish, Portuguese (and Brazilian Portuguese), Romanian, Russian, Serbian, Slovak, Spanish, Swedish, Thai, Turkish, Ukrainian, and Vietnamese. They’re community-maintained — open a PR to add a locale or sharpen a translation.
Missing or unshipped strings fall back to the default locale, then to English. To override a string or supply your own language, set i18n.ui, keyed by locale:
i18n: {
// …
ui: {
fr: {
search: { button: "Rechercher", placeholder: "Rechercher…" },
page: { previous: "Précédent", next: "Suivant" },
},
},
}
SEO
Localized SEO is handled for you — no per-page metadata to write:
<html lang>anddirare set from the active locale.hreflangalternates link every real translation of a page, plus anx-defaultpointing at the default locale.- Canonical URLs are locale-correct, and JSON-LD carries
inLanguage.
Set deployment.site so these can be emitted as absolute URLs.
Search
Search is scoped to the active language: on a /fr/… page the dialog returns French results, with an All languages toggle to search across every locale at once. The default (Orama) and FlexSearch indexes filter in the browser; hosted providers carry a locale facet on each record.
Right-to-left
Set dir: "rtl" on a locale and Blume mirrors the whole interface — the sidebar, header, table of contents, pagination, search, and menus — and sets <html dir> to match. Two things deliberately stay left-to-right: code blocks (code reads LTR in any language) and fallback content — an untranslated page keeps the direction of the language it’s actually written in, so English shown under an RTL locale still reads correctly while the surrounding chrome mirrors.