Languages
Name the directories directly under docs/ after language codes and JAAD
builds one documentation site per language, with a switcher in the header.
docs/
en/
01-introduction.md → /
02-guides/
01-setup.md → /guides/setup
it/
01-introduzione.md → /it
02-guide/
01-installazione.md → /it/guide/installazione
Nothing needs configuring. With one language, or with no locale directory at all, everything behaves exactly as it did before.
Detection
A directory is a locale when its name is an ISO 639-1 language, optionally with
a script and a region: it, pt-BR, zh-Hans. A three-letter directory such
as api is never a locale.
Two directories make the tree localised. A single one counts only when
lang names it — so docs/en/ with lang: "en" is a first language you are
about to translate, while a lone docs/it/ on an English site stays the
chapter an “IT” section depends on.
A locale directory with no markdown in it is not a language: it would be offered in the switcher and then answer 404. It is dropped, and if that leaves one language the documentation stays localised — the remaining directory is still stripped from the URLs, so nothing moves.
Once the tree is localised, every entry under docs/ has to be a locale
directory. A stray one stops the build and is named:
jaad: mixed documentation tree
docs/guides is not a locale, but en, it are
Move every page under a locale directory, or set locales: false.
The nesting limit applies inside each language, not to the tree as a whole:
it/guides/setup.md is a chapter inside Italian, and still only one level.
The default language
lang names the language that keeps the unprefixed URLs:
defineJaadConfig({ title: 'My Project', lang: 'en' })
/guide is English and /it/guide is Italian. Adding a second language to an
existing site therefore changes none of its URLs. lang must match one of the
directories, or the build stops and lists what it found.
Overriding detection
locales is only for when detection guesses wrong:
defineJaadConfig({ title: 'My Project', locales: ['en', 'it'] })
defineJaadConfig({ title: 'My Project', locales: false })
A list pins the set. false switches detection off, so every directory is a
chapter again.
Pairing translations
Two pages are translations of each other when their slugs match:
en/guides/setup.md and it/guides/setup.md both resolve to guides/setup.
That is the whole rule, and it is why nothing pairs when a language translates
its own file and directory names.
To translate the URLs too, give both files the same translationKey:
---
translationKey: setup
---
docs/en/02-handbook/01-setup.md → /handbook/setup
docs/it/02-guida/01-installazione.md → /it/guida/installazione
With the key the two link to each other in the switcher and in hreflang;
without it neither would know the other existed. Two pages of one language
claiming the same key stop the build.
Untranslated pages
Each language has exactly the pages in its own directory. There is no fallback:
a page that exists only in English is not built under /it, does not appear in
the Italian sidebar, and is not in the Italian search index or llms.txt.
A language whose every page is a draft empties out in the production build, and drops out of the switcher there too.
The switcher accounts for this. On a page with no counterpart in a language, it links to that language’s opening page rather than to a URL that would 404.
The switcher
It sits next to the theme switcher and shows the current language’s flag and code, with the languages’ own names in the menu. It renders only when there is more than one language.
Flags come from the region: explicit in pt-BR, and otherwise from the
language’s usual one. A language with no usual region shows its code alone —
better than a wrong flag.
Metadata
Per-language metadata is handled for you:
| Tag | Value |
|---|---|
<html lang> |
The page’s language |
og:locale |
The page’s language |
og:locale:alternate |
Every language that has this page |
<link rel="alternate"> |
hreflang per translation, x-default |
hreflang links need absolute URLs, so they are emitted only when site is
set, like canonical.
Everything in frontmatter is per file, so
description, keywords and the rest are already per language: write them in
the language of the file they sit in.
Each language also gets its own search index and llms.txt, at
/it/llms.txt and so on. Search only ever returns pages in the language the
reader is in.
The interface
JAAD ships its own interface in English and Italian. It picks the catalogue
from each page’s locale automatically, so Italian documentation needs no UI
configuration. Regional locales inherit their base language: it-IT and
it-CH both use Italian.
Use ui only for wording you want to replace:
defineJaadConfig({
title: 'My Project',
ui: {
it: {
'search.trigger': 'Cerca…',
'page.copy': 'Copia la pagina',
'breadcrumb.root': 'Documentazione',
},
},
})
Overrides are matched case-insensitively. An exact override comes first, then
one for the base language, then the bundled catalogue. A locale without a
bundled catalogue falls back to English. For example, ui["it-IT"] wins over
ui.it, while the latter also applies to it-CH.
An override can therefore stay partial without blanking or changing any other label. These are all the keys and their English fallback values:
ui: {
en: {
"breadcrumb.label": "Breadcrumb",
"breadcrumb.root": "Docs",
home: "Home",
"locale.change": "Change language",
"nav.documentation": "Documentation navigation",
"nav.jumpToSection": "Jump to section...",
"nav.mobile": "Toggle mobile menu",
"nav.page": "Page navigation",
"nav.selectPage": "Select page",
"page.copied": "Copied!",
"page.copy": "Copy page",
"page.edit": "Edit this page",
"page.lastUpdated": "Last updated on",
scrollTop: "Scroll to top",
"search.close": "close",
"search.empty": "No results for",
"search.label": "Search documentation",
"search.loading": "Loading...",
"search.navigate": "navigate",
"search.open": "open",
"search.placeholder": "Search documentation...",
"search.results": "Search results",
"search.trigger": "Search docs...",
"theme.toggle": "Toggle color theme",
"toc.label": "Table of contents",
"toc.title": "On This Page",
},
},
To add another bundled language, see the translation contribution guide.
Your documentation, its navigation, its chapter names and its metadata come from your files and are already in whatever language you wrote them.