---
title: Multi-Language Support
description: 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.

<Note>
  Jamdesk does not translate content for you. You provide the translated MDX files; Jamdesk handles routing, navigation, the language switcher, and RTL styling. Bring translations from your own workflow (human translators, machine translation, or an LLM) and drop them into language-prefixed directories.
</Note>

## Configuration

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

```json 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

| 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:

```bash
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):

```json
{
  "language": "es",
  "tabs": [
    {
      "tab": "Documentación",
      "groups": [
        {
          "group": "Comenzar",
          "pages": ["es/introduction", "es/quickstart"]
        }
      ]
    }
  ]
}
```

## Language-Specific Settings

Each language can have its own configuration:

```json
{
  "navigation": {
    "languages": [
      {
        "language": "en",
        "banner": {
          "content": "Version 2.0 is now live! See our [changelog](/changelog).",
          "dismissible": true
        },
        "tabs": [...]
      },
      {
        "language": "es",
        "banner": {
          "content": "¡La versión 2.0 está disponible! Ver [registro de cambios](/es/changelog).",
          "dismissible": true
        },
        "tabs": [...]
      }
    ]
  }
}
```

## 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.

```json 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:

| Language | URL |
|----------|-----|
| English (default) | `docs.example.com/introduction` |
| Spanish | `docs.example.com/es/introduction` |
| French | `docs.example.com/fr/introduction` |

## 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:

```json
{
  "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:

```bash
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.

| 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

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.

<Note>
  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.
</Note>

### 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`:

```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`:

```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

<Steps>
  <Step title="Start with English">
    Write your docs in English first. This becomes your source of truth.
  </Step>
  <Step title="Add language directories">
    Create directories for each target language (`es/`, `fr/`, etc.).
  </Step>
  <Step title="Translate content">
    Copy English files to language directories and translate. Keep filenames the same.
  </Step>
  <Step title="Update docs.json">
    Add language entries to your navigation config.
  </Step>
</Steps>

## 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?

<Columns cols={2}>
  <Card title="Navigation Overview" icon="sitemap" href="/navigation/overview">
    Configure tabs, groups, and page structure
  </Card>
  <Card title="docs.json Reference" icon="gear" href="/config/docs-json-reference">
    Full configuration options
  </Card>
</Columns>
