Language switcher (languageListBlock)
Use docsOutput.postProcessing.languageListBlock when translated markdown files should include a "Read in other languages" row of links — one link per locale, with href values computed relative to each output file.
This repository uses it for README.md (flat output under translated-docs/). After translate-docs, each translated copy gets a refreshed block; for example translated-docs/README.de.md links to sibling locale files under translated-docs/ and back to the English source at the repo root.
Requires docsOutput.style = "flat" (or another layout where sibling locale files are addressable by relative path). See Output layouts.
1. Mark the block in source markdown
Wrap the switcher in HTML (or any lines) bounded by start and end substring markers. This repo uses:
<small>**Read in other languages:** </small>
<small id="lang-list">[English (GB)](/) · [Deutsch](./README.de.md) · …</small>The initial link text is only a placeholder. translate-docs replaces the whole slice from the first line containing start through the first later line containing end (markers inside fenced code blocks are ignored so config examples in the same file do not match).
2. Configure the block
start and end are arbitrary substring markers — they do not have to be <small id="lang-list"> / </small>. Pick any opening and closing text that appears only on the language-switcher slice: another HTML tag (<div class="lang-switcher"> … </div>), HTML comments (<!-- lang-list --> … <!-- /lang-list -->), or markdown-only boundaries (for example a line **Languages:** through a line ---). Set start and end in config to match exactly what you put in the source file.
Root config (ai-i18n-tools.config.json):
"postProcessing": {
"languageListBlock": {
"start": "<small id=\"lang-list\">",
"end": "</small>",
"separator": " · "
}
}| Field | Role |
|---|---|
start | Substring that identifies the opening line of the block |
end | Substring on the closing line (can be the same line as start when both appear on one line) |
separator | Text between generated [label](href) links (this repo uses " · ") |
label | Optional: "local" (default) uses each locale endonym from the manifest; "english" uses englishName |
3. What happens at runtime
- Extraction — the language-list slice is not sent to the model (
translatable: false). - Per translated file — after segment translation and optional flat link rewriting,
postProcessingrebuilds the block: one markdown link per locale, labels fromui-languages.jsonwhen present (else bundled master catalog, elselocaleDisplayNames), paths relative to the file being written. - Source refresh — at the end of a
translate-docs/syncdocs pass, the same canonical block is written back into English source files incontentPathsso adding a locale updates the switcher in the repo without hand-editing every link.
If a file has no matching block, the CLI logs a warning (when --verbose) and leaves the body unchanged.
4. Label manifest
For endonym labels (label: "local"), generate or maintain ui-languages.json via generate-ui-languages (written to languagesManifestPath, which defaults to {ui.flatOutputDir}/ui-languages.json). This repo's docs-only config has no UI pipeline and no project manifest on disk, so labels come from the bundled master catalog for sourceLocale + targetLocales.
5. Examples in this repository
| Example | Files |
|---|---|
| This package (flat README + VitePress site) | ai-i18n-tools.config.json (README block: docsOutput.style = "flat"; site block: docsOutput.style = "vitepress" + vitepressThemeCatalog) |
| Flat README + Docusaurus docs | examples/nextjs-app/ai-i18n-tools.config.json (second block: docsOutput.style = "flat"; first block: docsOutput.style = "docusaurus") |
| Docusaurus docs only | examples/docusaurus-docs/ai-i18n-tools.config.json (docsOutput.style = "docusaurus" + docusaurusCatalogDir) |
| VitePress docs (minimal demo) | examples/vitepress-docs/ai-i18n-tools.config.json (docsOutput.style = "vitepress" + vitepressThemeCatalog) |
The line immediately before <small id="lang-list"> (for example **Read in other languages:**) is a normal translatable segment and is localised in each target locale; only the link row inside the markers is regenerated verbatim apart from href and manifest-driven labels.