---
title: Publish documentation in multiple languages
description: Add translated pages with build-time generation, automatic fallbacks, and a built-in language switcher.
url: https://pr-7-a9c4e9fe1b6c.thally.app/guides/multi-language
---

# Publish documentation in multiple languages

Add translated pages with build-time generation, automatic fallbacks, and a built-in language switcher.

Thally supports build-time multi-language documentation. Translated MDX files live in your repo alongside your primary content — fully version-controlled, statically generated, and indexed by search engines with real locale-specific URLs.

#### Real locale URLs

  `/es/introduction`, `/fr/guides/getting-started` — every locale gets its own
  indexable URL, never a client-side redirect.

#### AI-powered translation

  One command translates your entire docs site. Code blocks, component tags, and
  URLs are preserved automatically.

#### Automatic fallbacks

  Untranslated or stale pages fall back to the primary language gracefully, with
  informative banners instead of 404s.

#### Built-in language switcher

  A globe icon appears automatically in the top bar as soon as you configure two
  or more locales.

---

## URL structure

The default locale is served without a URL prefix — fully backward-compatible with any existing links. Secondary locales are prefixed with their locale code:

| URL                          | Content           |
| ---------------------------- | ----------------- |
| `/introduction`              | English (default) |
| `/es/introduction`           | Spanish           |
| `/fr/introduction`           | French            |
| `/guides/getting-started`    | English (default) |
| `/es/guides/getting-started` | Spanish           |

Adding a new locale never breaks existing URLs.

---

## Setup

#### Choose languages in Thally Cloud

For a managed site, open **Site settings → Languages**, choose every language
readers should see, and save. Thally publishes the selection with the site's
portable configuration; the language menu and locale routes update without a
repository edit.

English remains the source language at unprefixed URLs. Adding a language
makes its route available immediately, while missing translations display the
English source content.

#### Configure a self-hosted site

Self-hosted sites can configure the same behavior in `docs.json`:

```json
{
  "i18n": {
    "defaultLocale": "en",
    "locales": [
      { "code": "en", "label": "English" },
      { "code": "es", "label": "Español" },
      { "code": "fr", "label": "Français" }
    ]
  },
  "tabs": [...]
}
```

`defaultLocale` must match the repository's source content language. The
locales array controls the language switcher order.

Once this is saved, a globe icon appears in the top bar automatically.

#### Translate your pages with the CLI

Run the translate command to generate translated MDX files for a locale:

```bash
npx create-thally-docs translate --locale es
```

This calls Claude to translate every content page defined in your `docs.json` and writes the results to `src/content/es/`. You'll see a live progress list as each page completes, and a summary at the end.

**Translate specific pages only:**

```bash
npx create-thally-docs translate --locale es --pages introduction,guides/getting-started
```

**Overwrite existing translations:**

```bash
npx create-thally-docs translate --locale es --force
```

**All options:**

| Flag              | Description                                                                  |
| ----------------- | ---------------------------------------------------------------------------- |
| `--locale <code>` | Target locale — required. Must match a code in your `docs.json` i18n config. |
| `--pages <ids>`   | Comma-separated page IDs to translate. Omit to translate all pages.          |
| `--force`         | Overwrite existing translation files.                                        |
| `--api-key <key>` | Anthropic API key. Falls back to `ANTHROPIC_API_KEY` env var.                |
| `--model <id>`    | Claude model to use. Default: `claude-sonnet-4-6`.                           |
| `--yes`, `-y`     | Skip the confirmation prompt.                                                |

> **Note:**
  The translate command calls Claude via the Anthropic API. Set
  `ANTHROPIC_API_KEY` in your environment or pass `--api-key` directly.
  Translating a typical docs page costs a fraction of a cent.

#### Review and commit

After translation, your content directory will look like this:

```
src/content/
├── introduction.mdx            ← primary (English, unchanged)
├── guides/
│   └── getting-started.mdx
├── es/                         ← Spanish translations
│   ├── introduction.mdx
│   └── guides/
│       └── getting-started.mdx
└── fr/                         ← French translations
    └── introduction.mdx
```

Translated files are plain MDX. Edit them with the rest of your content, commit
them to the repository, and deploy normally.

---

## Translation quality

Claude translates with these rules applied automatically:

- **Translates:** all prose text, headings, frontmatter `title`, `description`, and `keywords`
- **Preserves:** MDX component tags verbatim (`<Note>`, `<Steps>`, `<CodeGroup>`, etc.)
- **Preserves:** all code blocks and inline code exactly as written
- **Preserves:** all URLs, file paths, and import statements
- **Preserves:** all frontmatter structure and non-text fields

If a translation needs adjusting, edit the file in `src/content/{locale}/` directly. You can also re-run the command for specific pages:

```bash
npx create-thally-docs translate --locale es --pages guides/getting-started --force
```

---

## Fallback behavior

Thally never returns a 404 for a missing translation. Instead, it serves the primary-language content with an informative banner:

**Missing translation** — the page hasn't been translated for that locale yet:

> ⚠️ This page hasn't been translated to **Español** yet. Showing the original **English** version.

**Stale translation** — the primary-language file was updated after the translation was last generated:

> This translation may be outdated. [View original →]

Both banners appear only on secondary-locale routes. The primary route is never affected.

Source-content fallbacks are canonicalized to the English page and marked
`noindex, follow`. Crawlers therefore discover real translated content without
indexing duplicate English prose under locale-prefixed URLs.

To fix a stale or missing translation, run the translate command for that page:

```bash
npx create-thally-docs translate --locale es --pages guides/getting-started
```

---

## Language switcher

Once you have two or more locales configured, a **globe icon** appears in the top bar automatically. Clicking it opens a dropdown listing all configured locales.

Selecting a locale navigates to the same page in that locale — for example, switching from `/es/guides/getting-started` to French takes you directly to `/fr/guides/getting-started`. The switcher always keeps you on the current page rather than sending you to a home page.

---

## API Reference

The API Reference tab is auto-generated from your OpenAPI spec. Its content is always in the primary language — OpenAPI specs define machine-readable contracts, not prose content.

When navigating to the API Reference tab from a locale page, the URL prefix is preserved so you remain in the locale context (`/es/api/...`). The sidebar and tab bar stay locale-aware; only the operation content itself comes from the spec unchanged.

> **Note:**
  To change the language of API Reference descriptions and summaries, update
  them directly in your OpenAPI spec file.

---

## SEO

When a translation exists, Thally automatically adds `hreflang` and
`canonical` link tags:

```html
<link rel="canonical" href="https://docs.example.com/es/introduction" />
<link
  rel="alternate"
  hreflang="en"
  href="https://docs.example.com/introduction"
/>
<link
  rel="alternate"
  hreflang="es"
  href="https://docs.example.com/es/introduction"
/>
<link
  rel="alternate"
  hreflang="fr"
  href="https://docs.example.com/fr/introduction"
/>
<link
  rel="alternate"
  hreflang="x-default"
  href="https://docs.example.com/introduction"
/>
```

Each translated page is self-canonical. Reciprocal `hreflang` links include
only authored translations, and `x-default` points to the source-language
page. Set `THALLY_SITE_URL` so the correct domain appears in these tags:

```bash
THALLY_SITE_URL=https://docs.example.com
```

---

## Using the MCP tool

If you use the Thally MCP server, you can translate docs directly from your AI assistant:

```json
{
  "name": "translate_docs",
  "arguments": {
    "projectDir": "/path/to/my-docs",
    "locale": "es",
    "force": false
  }
}
```

The MCP tool accepts the same options as the CLI. See [MCP Server](/guides/mcp-server) for setup instructions.

---

## Scaffold with i18n

When creating a new project, you can enable multi-language support from the start:

```bash
npx create-thally-docs my-docs
```

When asked **"Enable multi-language support?"**, enter `y` and provide your locale codes (e.g. `es,fr,de`). The `i18n` block is written to `docs.json` automatically.

---

#### [CLI Reference](/guides/cli-reference)

  Full reference for `translate` and all other create-thally-docs commands.

#### [MCP Server](/guides/mcp-server)

  Set up the Thally MCP server to translate docs from your AI assistant.