One doc set can serve the same docs in several languages. List the languages in the set's larecipe.json:
{
"locales": [
{ "code": "en", "label": "English", "default": true },
{ "code": "ar", "label": "العربية", "dir": "rtl" }
]
}Then put each translation in a folder named after its code, inside the version, at the same path as its original:
- docs/3.x/
- index.md
- get-started/
- installation.md
- ar/
- index.md
- get-started/
- installation.md
The default language stays at the version root, and on a set without versions the language folders sit directly in docs/. A page you have not translated yet still opens in every language: readers get the original, with a note that says so.
This site serves English and Arabic, and it is fully translated: every page has an Arabic version, starting with the Arabic Introduction. A page added later shows the English original at its Arabic URL until its translation is added.
URLs
The language comes first, then the version, then the page: /{prefix}/{locale}/{version}/{path}. The default language has no segment. On this site, the installation guide is /3.x/get-started/installation in English and /ar/3.x/get-started/installation in Arabic. The Markdown twin, llms.txt and the search index follow the same shape.
Locale keys
| Key | Default | Meaning |
|---|---|---|
code |
required | The language code, used in folders and URLs: ar, fr, pt-BR. |
label |
the language's own name for common codes | What the language switcher shows. |
dir |
from the code | ltr or rtl. Arabic, Hebrew, Persian, Urdu and other right-to-left languages get rtl without being told. |
default |
false |
The original language. When no locale says true, the first one is the default. |
A locale can be a plain string ("locales": ["en", "ar"]) when the defaults fit. A set with one locale, or none, is not multilingual and its URLs never carry a language.
Right to left
The page's direction comes from the locale, and the stylesheets use logical properties, so the layout mirrors with no extra configuration. Write the page in Arabic and leave code, commands and component tags as they are. Thmanyah Sans sits in the default font stack right after Inter, so Arabic text is set in it while Latin words, numbers and code keep their Latin faces.
Keep translations current
A translation falls behind when its original changes and it does not. larecipe:affected finds those pages:
php artisan larecipe:affected --uncommitted --base=main --locale=arIt lists the Arabic pages whose original changed in your branch while the translation did not, and those whose original changed after the translation was last updated. A translation without sources: of its own uses its original's, so a code change that makes the English page stale flags the Arabic one too.
The larecipe-update-docs skill carries a translation workflow for coding agents: read the original's diff, translate only what changed, keep code, commands, paths and component tags exactly as they are, keep the headings in the same order so anchors line up, and say so instead of guessing when the agent cannot translate well.
Details
How a page that is not translated yet renders
Every page of the original exists in every language. When a translation is missing, the page renders the original article inside the reader's language: the page keeps lang="ar" and dir="rtl", the sidebar still links into the Arabic pages, the article stays in English with its own lang and dir, and a note above it reads "This page is not translated yet. You are reading the original." The Markdown twin falls back the same way.
The language switcher relies on this: switching language keeps you on the same page, translated or not. Each page also lists its hreflang alternates, one for every language it is written in, plus x-default for the original. A fallback is not an alternate; its canonical URL is the original page.
Navigation and links in each language
The sidebar of a language lists every page of the original, in the same order, with the translated title wherever a translation exists. It reads the version's navigation.json unless the language folder has its own (docs/3.x/ar/navigation.json), which can give the groups translated labels or a different order. A language's own file lists the same pages as the version's: entries for pages not translated yet show the original. This site's Arabic sidebar works that way, with Arabic group labels.
Link between pages the usual way, with relative .md links, from a translation too: a link to a page that exists only in the original reaches its fallback in the reader's language. larecipe:validate follows the same rule as the reader, so neither a navigation entry nor a link to an untranslated page is reported.
Search and agents in each language
Search, llms.txt and the MCP server each work inside one language, and cover the pages written in it:
- The search index for Arabic is built from the Arabic pages, so readers search the language they read.
/ar/llms.txtand/ar/3.x/llms.txtlist the Arabic pages; the root and version indexes end with a Languages section that links the other languages.- The MCP tools
search_docs,read_pageandlist_pagestake alocaleargument and use the default language without one.
Next
- Versions: languages live inside each version.
- Navigation:
navigation.json, per version and per language. - larecipe.json reference: every manifest key.