Jamdesk Documentation logo

Multi-Language Support

Serve documentation in multiple languages with a language switcher. Each language gets its own navigation structure and translated content.

If your docs need to reach users in more than one language, you can define separate navigation trees per locale and let readers switch with 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, which applies whether the translations come from Jamdesk, your own translators, or another tool.

Configuration

Wrap your navigation in a languages array, with each language containing its own navigation structure:

docs.json
{
  "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

CodeLanguageCodeLanguage
enEnglishkoKorean
esSpanishpt-BRPortuguese (Brazil)
frFrenchruRussian
deGermanarArabic
itItalianhiHindi
jpJapaneseidIndonesian
cnChinese (Simplified)trTurkish
zh-HantChinese (Traditional)viVietnamese
nlDutchplPolish
svSwedishcsCzech
noNorwegianroRomanian
heHebrewuaUkrainian
lvLatvianuzUzbek

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

Each language can have its own configuration:

{
  "navigation": {
    "languages": [
      {
        "language": "en",
        "tabs": [...]
      },
      {
        "language": "es",
        "tabs": [...]
      }
    ]
  }
}

Banners are set globally at the top level of docs.json (see Banner), not per language. One banner shows on every page across all languages.

Translating Navbar Labels

Top-nav links and the primary CTA accept an optional labels object with per-language overrides. When the reader is on a language-prefixed URL (e.g. /fr/...), the matching override is used; otherwise the default label is shown.

docs.json
{
  "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. You don't need to configure those.

Default Language

The first language in the array is the default. Users landing on your docs see this language first. The language switcher allows them to change.

URL Structure

Language prefixes appear in URLs:

LanguageURL
English (default)docs.example.com/introduction
Spanishdocs.example.com/es/introduction
Frenchdocs.example.com/fr/introduction

Automatic language routing

By default everyone lands on your default language and switches manually. Turn on localization.autoRedirect to send first-time visitors straight to their own language:

docs.json
{
  "localization": {
    "autoRedirect": true
  }
}

A visitor whose browser asks for French gets /fr instead of /. Their language is remembered for a year, so they're only matched once, and if they pick a language from the switcher that wins from then on.

Only your site root is ever redirected. Everything else is left exactly as the visitor asked for it:

  • A language URL always opens in that language. /fr/guides/authentication — or just /fr — serves French to everyone, whatever their browser asks for. Someone chose that URL, so a link you paste into a ticket or a search result opens the same page for the person who clicks it.
  • Deep links are never redirected. /guides/authentication serves that page for everyone.
  • Crawlers are never redirected. Google and other bots keep seeing your default language at the root, and your hreflang tags keep deciding what gets indexed.
  • It never invents a language. If the visitor's browser asks for a language you don't publish, nothing happens — they stay 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, so an AI tool can read your docs in one language without loading the others:

LanguageIndexFull content
English (default)docs.example.com/en/llms.txtdocs.example.com/en/llms-full.txt
Spanishdocs.example.com/es/llms.txtdocs.example.com/es/llms-full.txt
Frenchdocs.example.com/fr/llms.txtdocs.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. If a page doesn't exist in a language, users see a fallback message with a link to the English version.

For pages that shouldn't be translated (like API reference), you can reference the same page across languages:

{
  "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 resolves the matching spec at render time based on the URL's language prefix. A page at /fr/api-reference/create-ticket looks for api.fr.yaml first, falling 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.

TranslateKeep identical
info.title, info.descriptionopenapi / swagger version, servers[*].url
Per-operation summary, descriptionURL paths, HTTP methods, operationId, tags
Per-parameter descriptionParameter names (name), field names, schema property keys
Per-response descriptionStatus code keys ("200", "400", etc.)
requestBody.descriptionenum values (low, normal, high), type, format
Schema description, property description$ref pointers, example / examples payload contents

Fallback behavior

If a page is requested under a localized URL but no translated spec exists, Jamdesk renders the English spec inside the translated page shell. Users see a mixed-language endpoint block rather than a 404. This matches the broader partial-translations behavior for MDX pages.

The jamdesk CLI's local preview (jamdesk dev) resolves OpenAPI specs by filename only. It doesn't yet apply the language suffix lookup. When iterating on translations locally, use the production Jamdesk preview URL (<project>.jamdesk.app/<lang>/...) or swap the source file temporarily. This only affects local development; 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. No docs.json change is needed — spec resolution is purely filename-based.

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

Notice that /tickets, post, and createTicket stay identical. Only the prose changes.

Translation Workflow

1
Start with English

Write your docs in English first. This becomes your source of truth.

2
Add language directories

Create directories for each target language (es/, fr/, etc.).

3
Translate content

Copy English files to language directories and translate. Keep filenames the same.

4
Update docs.json

Add language entries to your navigation config.

RTL Languages

Right-to-left languages like Arabic and Hebrew are supported. Jamdesk automatically applies RTL styling when these languages are active.

What's Next?

Navigation Overview

Configure tabs, groups, and page structure

docs.json Reference

Full configuration options