Markdown & MDX Docs
Translate a docs site's Markdown and MDX pages with babelize translate: only the text readers see, code and JSX untouched, one paragraph at a time, in Fumadocs' i18n layout.
babelize translate translates documentation pages as well as app strings. Point it at your .md and .mdx files and it writes a translated copy of each page, translating only the text a reader sees. Code, imports, JSX and URLs are left exactly as written.
Docs translation runs locally, in the CLI and the MCP server. Cloud runs from the dashboard or the GitHub App don't translate docs yet.
Quick start
Add docs to .babelize/config.json:
{
"sourceLocale": "en",
"docs": { "include": ["content/docs/**"] }
}Then translate:
npx babelize translate --to es,ja --docs-only # only the docs
npx babelize translate --to es,ja # the app first, then the docsEach page gets a translated copy next to it, and .babelize/local/docs-lock.json records every paragraph's translation. Review the result with git diff, then commit both.
content/docs/install.mdx
content/docs/install.es.mdx ← new
content/docs/install.ja.mdx ← new
content/docs/meta.json
content/docs/meta.es.json ← new (sidebar labels)What gets translated
| Translated | Left exactly as written |
|---|---|
Frontmatter title, description, summary, sidebarTitle, label | Other frontmatter fields (icon, custom keys) |
| Headings, paragraphs, list items, table cells, blockquotes | Code blocks and inline code |
Readable JSX props: title, description, label, alt, placeholder, aria-label, caption, summary, heading, subtitle | import / export lines, {expressions}, comments |
Text inside JSX components (<Callout>…</Callout>) | JSX tags and other props, URLs, HTML inside <pre>, <code>, <kbd> |
meta.json sidebar labels: title, description, ---Separators---, link text | Page names, icons and link URLs in meta.json |
Inline formatting survives translation. Bold, italics, links and images travel to the translator as numbered tags (<1>the guide</1>), and inline code and other syntax as placeholders ({p0}). Every translation is checked on the way back. If a tag or placeholder was dropped or repeated, that paragraph keeps its English text, is listed in the summary, and is retried on the next run. Characters that would change the page's structure (<, {, }, | in tables, a leading #) are escaped, so the translated page always compiles.
The glossary, brand voice and formality from .babelize/config.json apply to docs just as they do to app strings.
Docs frameworks
| Framework | Status |
|---|---|
| Fumadocs | Supported. Both of its i18n parsers: dot (page.ja.mdx, the default) and dir (ja/page.mdx). meta.json sidebars are translated. |
| Docusaurus, Nextra, Mintlify, VitePress, Starlight, plain Markdown | Not tested. The translated pages are valid Markdown/MDX, but these tools expect translations in their own folder layout (Docusaurus: i18n/<locale>/docusaurus-plugin-content-docs/…). Set "anchors": false (see below) unless the tool reads [#id]. |
For the dir layout, give the docs root:
{
"docs": { "include": ["content/docs/**"], "layout": "dir", "root": "content/docs" }
}All docs keys are listed in Project Config.
Heading anchors
A translated heading would normally get a new id from its translated text, which breaks links like #installation. Babelize keeps the English heading's id instead, in Fumadocs syntax:
## Instalación [#installation]Repeated headings are numbered the way GitHub and Fumadocs number them (-1, -2), and headings that already have an id keep it. Turn this off with "anchors": false for docs tools that don't read [#id].
Re-runs and the lockfile
.babelize/local/docs-lock.json stores each paragraph's translation, keyed by its English text. Commit it.
- Edit a sentence and only that paragraph is translated again. Unchanged paragraphs come from the lockfile, which costs no credits or tokens.
- Text that no page contains any more is dropped from the lockfile at the end of a run. Translations into languages you didn't pass this run are kept.
- Nothing to translate means no changes. A run that finds every paragraph in the lockfile reports No changes: every page is up to date.
- Edits to a translated page are overwritten on the next run, because the page is rebuilt from the English page and the lockfile. To correct a translation, change its entry in
docs-lock.json. - A page where nothing could be translated is not written, for example when the translator is down. Your docs framework falls back to the English page, and the next run retries.
Formatting
Translated pages and meta.*.json files are formatted with your project's own Prettier or Biome, using your config and ignore file, before they're compared with what's on disk. They pass your format check, and an unchanged page isn't rewritten on every run.
Choosing a translator
Docs are long-form prose, so the translator matters more than for UI strings.
| Your own LLM key (BYOK) | Babelize-hosted | |
|---|---|---|
| Quality on prose | Natural. Keeps markup reliably. | Basic machine translation. Some paragraphs lose markup and stay in English. |
| Speed | Fast | About 4 s per short string, 8 s per paragraph |
| Best for | Docs | UI strings |
When the docs run on Babelize-hosted translation, the CLI says so and suggests a key. Set one of ANTHROPIC_API_KEY, OPENAI_API_KEY, GEMINI_API_KEY, GROQ_API_KEY or OPENROUTER_API_KEY (see BYOK).
Linking between languages
Babelize translates the text of links, not their targets: a link to /docs/install on the Japanese page still points to /docs/install. To keep readers in their language, prefix internal links with the page's locale when you render them. In Fumadocs, override the MDX a (and Card) components and map the page tree's URLs. babelize-elements does this in src/lib/localized-href.ts.
Limitations
- Translator quality. With Babelize-hosted translation, expect some paragraphs to stay in English and plainer wording. Use your own LLM key for docs.
- Fumadocs layouts only. Other docs tools need their own folder structure (see Docs frameworks).
- Line-based parsing. Babelize reads Markdown line by line, not as a full syntax tree. Ordinary docs work, but very unusual structure can be skipped or kept in English rather than broken: raw HTML blocks, deeply nested lists, JSX props split over several lines. Line breaks inside a paragraph are joined in the translation.
- Same-script text in another language (a French quote in English docs) is translated like the rest. Text in another script, such as an Arabic or Japanese sample, is left as written in app code, not in docs.
- Local only. Cloud runs and GitHub pull requests don't translate docs yet.
Example
elements.babelize.co is translated this way. Its docs in Spanish, Japanese and Arabic are translated from 13 MDX pages and 9 sidebar files, with Fumadocs i18n, per-language search and hreflang. The source is babelize/babelize-elements.
Last updated: 2026-11-01
File Formats
Babelize writes JSON locale files for JavaScript frameworks and gettext .po/.mo catalogues for Django, one per language, with placeholders preserved, and translates Markdown and MDX docs pages.
Versioning & Revisions
Every babel is kept in your project's history with its status, trigger, languages, credits and results. See past babels and how translations stay stable between them.