NookDocs treats languages the same way it treats versions: a top-level switcher in the navbar, URL-prefix routing (/fr/..., /ar/...), and side-by-side folders in the repo. Your default locale has no prefix; other locales live under folders named after their ISO code.
Declare languages
In Configurations → Languages (or direct nookdocs.config.json):
{
"languages": [
{ "language": "en", "default": true, "label": "English" },
{ "language": "fr", "label": "Français" },
{ "language": "es", "label": "Español" },
{ "language": "de", "label": "Deutsch" },
{ "language": "ja", "label": "日本語" },
{ "language": "ar", "label": "العربية" },
{ "language": "zh-CN", "label": "简体中文" }
]
}languagestringrequiredISO 639-1 code (en, es, de, fr, ja, ar, zh…) or BCP 47 tag (zh-CN, pt-BR, en-GB). Used as both the discriminator and the default URL slug.
labelstringDisplay name in the switcher. Write native names (Français, 日本語, العربية) rather than English translations.
defaultbooleanExactly one language must be the default. Its URLs serve at / (no prefix). Others route to /<language>/....
dir'ltr' | 'rtl'Explicit text direction. Auto-inferred from the code — ar, he, fa, ur default to rtl, everything else to ltr. Set explicitly for edge cases.
Repo layout
your-repo/ # the docs base
├── nookdocs.config.json
├── introduction.mdx ← English (default)
├── quickstart.mdx
├── guides/
│ └── authentication.mdx
├── fr/ ← French, URL prefix /fr
│ ├── introduction.mdx
│ ├── quickstart.mdx
│ └── guides/
│ └── authentication.mdx
└── ar/ ← Arabic, URL prefix /ar
├── introduction.mdx
└── guides/
└── authentication.mdxEach locale folder mirrors the structure of the default. You don't need every page translated — untranslated pages simply 404 in that locale, or you can link them back to the English version via a Banner at the top.
Pages that don't need translating
Some pages are meant to be language-neutral — API reference pages generated from OpenAPI, code samples, changelogs. Keep them ONLY in the default locale and skip them from the translated trees. They'll 404 under /fr/... but that's expected; you can soft-redirect via the redirects config:
{
"redirects": [
{ "source": "/fr/api/*", "destination": "/api/*" },
{ "source": "/ar/api/*", "destination": "/api/*" }
]
}This way /fr/api/authentication gracefully falls back to /api/authentication (always the default-locale spec) regardless of active locale.
Frontmatter stays the same
The MDX frontmatter schema is identical across locales. Only the content body changes:
---
title: Authentication
description: How to authenticate API requests.
icon: lock
---
Every API request needs a Bearer token...---
title: Authentification
description: Comment authentifier les requêtes API.
icon: lock
---
Chaque requête API nécessite un jeton Bearer...The icon, tag, hidden, and other meta fields stay in English (they're internal keys). Only user-visible copy gets translated.
RTL support
Languages flagged as dir: "rtl" (either explicitly or via auto-inference from ar, he, fa, ur) render with:
dir="rtl"on the root HTML element.Sidebar on the right, content on the left.
ps-4/pe-4/start-0/end-0CSS logical properties resolve correctly (our components use logical properties throughout — no layout breakage).Code blocks and tables stay LTR (technically correct for code / data).
Test your RTL layout by visiting the locale's URL in the browser. Take a screenshot; nothing should visually misalign.
Translation workflow
We don't ship a built-in translation manager. The recommended workflow for small teams:
Translate in-repo with PRs
Open a branch per locale (l10n/tr, l10n/ar). Translator works against it; you review + merge. Simple, version-controlled, free.
Use Crowdin / Lokalise for larger teams
Both tools have GitHub integration that opens PRs from translation memory. Point them at docs/<locale>/**/*.mdx and they'll keep translations in sync with source changes.
Machine-translated fallback
Copy docs/ → docs/fr/ and run each file through a translation API as a draft. Mark pages with a Banner ("Machine-translated — help us improve") and invite community edits.
Per-language chrome
Config supports per-language navbar / footer / banner overrides (planned but not yet wired up):
{
"languages": [
{
"language": "fr",
"label": "Français",
"navbar": {
"links": [
{ "label": "Tarifs", "href": "https://example.com/fr/tarifs" }
]
},
"footer": {
"social": { "twitter": "https://twitter.com/example_fr" }
}
}
]
}For now, the top-level navbar / footer / banner apply to every locale. Per-locale overrides ship when this item lands in the roadmap.
Navigation per language
Same caveat as versioning — the current renderer uses the top-level navigation config for every locale. Per-language nav tree scoping is queued.
Until then, the sidebar labels stay in the default language. This is surprisingly OK in practice because:
Group / tab labels tend to be English-ish anyway ("Guides", "API Reference")
Page titles in the sidebar come from each MDX file's frontmatter — so translated pages DO show translated titles
If mixed-language sidebars bother you, the workaround is to write every group / tab label in the default language, and let frontmatter carry translated titles.
The AI Assistant and i18n
The chat widget already handles multilingual queries — a non-English question triggers a translation hop so retrieval still works against English-indexed content, and the final answer comes back in the user's language. See AI Assistant.
Retrieval is not yet scoped by active locale (v2 follow-up). A user on /fr/... asking a question in French may get results from the English tree; the answer still comes in French.