Multi-Language Support
Serve documentation in multiple languages with a language switcher. Each language gets its own navigation structure and translated content.
Define a separate navigation tree for each locale. Readers switch languages from a dropdown in the top bar.
Jamdesk can translate your pages for you: see AI Translation. This page covers the navigation and language-switcher configuration. It applies whether the translations come from Jamdesk, your own translators, or another tool.
Configuration
Wrap your navigation in a languages array. Give every language its own navigation structure:
{
"navigation": {
"languages": [
{
"language": "en",
"tabs": [
{
"tab": "Documentation",
"groups": [
{
"group": "Getting Started",
"pages": ["introduction", "quickstart"]
}
]
}
]
},
{
"language": "es",
"tabs": [
{
"tab": "Documentación",
"groups": [
{
"group": "Comenzar",
"pages": ["es/introduction", "es/quickstart"]
}
]
}
]
}
]
}
}Supported Languages
| Code | Language | Code | Language |
|---|---|---|---|
en | English | ko | Korean |
es | Spanish | pt-BR | Portuguese (Brazil) |
fr | French | ru | Russian |
de | German | ar | Arabic |
it | Italian | hi | Hindi |
jp | Japanese | id | Indonesian |
cn | Chinese (Simplified) | tr | Turkish |
zh-Hant | Chinese (Traditional) | vi | Vietnamese |
nl | Dutch | pl | Polish |
sv | Swedish | cs | Czech |
no | Norwegian | ro | Romanian |
he | Hebrew | ua | Ukrainian |
lv | Latvian | uz | Uzbek |
Directory Structure
Organize translated content in language-prefixed directories:
my-docs/
├── docs.json
├── introduction.mdx # English (default)
├── quickstart.mdx
├── es/
│ ├── introduction.mdx # Spanish
│ └── quickstart.mdx
├── fr/
│ ├── introduction.mdx # French
│ └── quickstart.mdx
└── de/
├── introduction.mdx # German
└── quickstart.mdx
Reference pages in navigation using their full path (with the language prefix):
{
"language": "es",
"tabs": [
{
"tab": "Documentación",
"groups": [
{
"group": "Comenzar",
"pages": ["es/introduction", "es/quickstart"]
}
]
}
]
}
Language-Specific Settings
Give a language its own configuration inside its entry:
{
"navigation": {
"languages": [
{
"language": "en",
"tabs": [...]
},
{
"language": "es",
"tabs": [...]
}
]
}
}
A language can also have its own banner. It replaces the top-level banner on that language's pages. Languages without one show the top-level banner:
{
"navigation": {
"languages": [
{
"language": "en",
"tabs": [...]
},
{
"language": "de",
"banner": {
"content": "Geplante Wartung am Samstag um 10:00 UTC.",
"type": "warning"
},
"tabs": [...]
}
]
}
}
AI translation keeps a banner you wrote by hand for a language. It doesn't copy your source language's banner into the languages it generates. See Banner for every field and for how dismissing works.
Give a Language Its Own Footer
Each language entry can have a footer with links, social icons, and a copyright line in that language. It uses the same fields as the top-level footer:
{
"navigation": {
"languages": [
{
"language": "en",
"tabs": [...]
},
{
"language": "de",
"tabs": [...],
"footer": {
"links": [
{
"header": "Unternehmen",
"items": [{ "label": "Über uns", "href": "https://example.com/de/about" }]
}
],
"copyright": "© 2026 Example GmbH"
}
},
{
"language": "jp",
"tabs": [...],
"footer": {}
}
]
}
}Jamdesk picks the footer for a page like this:
- A language's
footerreplaces the top-level footer for every page in that language, including its versions and products. Nothing gets merged. "footer": {}removes the links, social icons, and copyright line for that language. The "Powered by Jamdesk" link follows your branding setting either way.- A language with no
footeruses the top-level one.
AI translation doesn't translate footers. If you translate with it, add each language's footer to its entry yourself. Jamdesk keeps it there on later translation runs.
Translating Navbar Labels
Top-nav links and the primary CTA accept an optional labels object with per-language overrides. A language-prefixed URL (e.g. /fr/...) shows the matching override. Any other URL shows the default label.
{
"navbar": {
"links": [
{
"label": "Blog",
"labels": { "fr": "Blog", "es": "Blog" },
"href": "/blog"
},
{
"label": "Pricing",
"labels": { "fr": "Tarifs", "es": "Precios" },
"href": "/pricing"
}
],
"primary": {
"type": "button",
"label": "Dashboard",
"labels": { "fr": "Tableau de bord", "es": "Panel" },
"href": "https://app.example.com"
}
}
}Built-in UI strings (the Search button, Ask AI button, and the More tabs dropdown) are translated automatically for every supported language.
Default Language
The first language in the array is the default. Readers see it first and can change it from the language switcher.
URL Structure
Language prefixes appear in URLs:
| Language | URL |
|---|---|
| English (default) | docs.example.com/introduction |
| Spanish | docs.example.com/es/introduction |
| French | docs.example.com/fr/introduction |
Automatic language routing
Every visitor lands on your default language and switches manually. Turn on localization.autoRedirect to send first-time visitors to their browser's language:
{
"localization": {
"autoRedirect": true
}
}A browser that asks for French gets /fr instead of /. Jamdesk remembers the language for a year. A language picked from the switcher wins from then on.
Only your site root is ever redirected. Every other URL opens as requested:
- A language URL always opens in that language.
/fr/guides/authentication(or/fr) serves French whatever the browser asks for. A link pasted into a ticket or a search result opens the same page for whoever clicks it. - Deep links are never redirected.
/guides/authenticationserves that page for everyone. - Crawlers are never redirected. Google and other bots keep seeing your default language at the root, and your
hreflangtags keep deciding what gets indexed. - It never invents a language. A browser that asks for a language you don't publish stays on your default root.
Requires two or more entries in navigation.languages. On a single-language site the setting does nothing.
If you front your docs with your own Cloudflare Cache Rule, leave redirects uncached and don't enable Ignore cache-control header and use this TTL on these paths. Caching the root response makes the redirect fire for some readers and not others, depending on which one warmed the edge. The worker template Jamdesk ships already handles this.
llms.txt for Each Language
Every language you publish gets its own llms.txt index and full-content file. An AI tool can read one language without loading the others:
| Language | Index | Full content |
|---|---|---|
| English (default) | docs.example.com/en/llms.txt | docs.example.com/en/llms-full.txt |
| Spanish | docs.example.com/es/llms.txt | docs.example.com/es/llms-full.txt |
| French | docs.example.com/fr/llms.txt | docs.example.com/fr/llms-full.txt |
The root docs.example.com/llms.txt serves the same file as /en/llms.txt. The root docs.example.com/llms-full.txt still contains every language. A language gets its own files once it has at least one page. Multilingual sites →
Partial Translations
You don't need to translate every page. A page missing from a language shows a fallback message with a link to the English version.
For pages that shouldn't be translated (like API reference), reference the same page in every language:
{
"language": "es",
"tabs": [
{
"tab": "API",
"groups": [
{
"group": "Endpoints",
"pages": ["api/users", "api/posts"] // Same as English
}
]
}
]
}
Translating OpenAPI Specs
OpenAPI-driven endpoint pages (those with an openapi: frontmatter directive) render content from a YAML or JSON spec file. To translate the endpoint summary, descriptions, parameter hints, and schema field descriptions, provide a language-specific spec file alongside your English one.
File naming
Place the translated spec next to the source, inserting the language code before the extension:
openapi/
├── api.yaml # English (default)
├── api.fr.yaml # French
├── api.es.yaml # Spanish
└── api.zh.yaml # Simplified Chinese
No configuration or docs.json change is required. Jamdesk picks the spec at render time from the URL's language prefix. A page at /fr/api-reference/create-ticket looks for api.fr.yaml first. It falls back to api.yaml if no translation exists.
What to translate in the spec
Translate human-readable prose. Keep every structural value identical across languages.
| Translate | Keep identical |
|---|---|
info.title, info.description | openapi / swagger version, servers[*].url |
Per-operation summary, description | URL paths, HTTP methods, operationId, tags |
Per-parameter description | Parameter names (name), field names, schema property keys |
Per-response description | Status code keys ("200", "400", etc.) |
requestBody.description | enum values (low, normal, high), type, format |
Schema description, property description | $ref pointers, example / examples payload contents |
Fallback behavior
A localized URL with no translated spec renders the English spec inside the translated page shell. Readers see a mixed-language endpoint block rather than a 404. MDX pages behave the same way.
The jamdesk CLI's local preview (jamdesk dev) resolves OpenAPI specs by filename only. It doesn't yet apply the language suffix lookup. To test translations, use the production Jamdesk preview URL (<project>.jamdesk.app/<lang>/...) or swap the source file temporarily. Hosted and ISR renders pick the translated spec correctly.
Removing a language-specific spec
Deleting api.<lang>.yaml causes the page to fall back to the English spec on the next render (no rebuild required). To retranslate from scratch, delete the file and regenerate. Spec resolution uses filenames only, so no docs.json change is needed.
Example
Source openapi/tickets.yaml:
info:
title: Tickets API
description: Manage support tickets.
paths:
/tickets:
post:
summary: Create a ticket
description: Create a new support ticket.
operationId: createTicket
French translation openapi/tickets.fr.yaml:
info:
title: API Tickets
description: Gérez les tickets de support.
paths:
/tickets:
post:
summary: Créer un ticket
description: Créer un nouveau ticket de support.
operationId: createTicket
/tickets, post, and createTicket stay identical. Only the prose changes.
Translation Workflow
Write your docs in English first. English is the source of truth.
Create directories for each target language (es/, fr/, etc.).
Copy English files to language directories and translate. Keep filenames the same.
Add language entries to your navigation config.
RTL Languages
Jamdesk applies RTL styling to right-to-left languages like Arabic and Hebrew.
