The Lingo.dev CLI translates static files in your repository - Markdown, MDX, Markdoc, JSON, YAML, subtitles, and more - through a configured localization engine. Point it at your content, run once, and get translated files alongside your source.
Supported Content Types#
The CLI detects each file's format from its extension - there is no bucket type to configure. Locale lives in the path (content/en/x.md becomes content/de/x.md), so no [locale] placeholder is needed.
| Content type | Format | Example path |
|---|---|---|
| Documentation | Markdown | docs/en/getting-started.md |
| Documentation | MDX | docs/en/getting-started.mdx |
| Documentation | Markdoc | docs/en/getting-started.mdoc |
| Structured data | JSON | data/en.json |
| Structured data | YAML | data/en.yaml |
| Blog posts | Markdown / MDX | blog/en/post-slug.md |
| Localization | Gettext PO | locale/en/messages.po |
| Localization | XLIFF | locale/en.xliff |
| Subtitles | SRT | subs/en/intro.srt |
See the formats reference for the full list of supported file types.
Not yet supported in the new CLI
CSV (csv-per-locale), VTT subtitles, plain-text .txt, Java .properties, and locale-root-key YAML are not supported by the new CLI yet. Keep those files on the legacy CLI for now and follow the changelog for updates.
Prerequisites#
Every run sends content through a localization engine - the configuration that determines which LLM model, glossary, brand voice, and instructions apply. Create one in the Lingo.dev dashboard, then set up the CLI (Node 22+):
npm install -g @lingo.dev/cli
lingo login
lingo init
lingo linklingo init and lingo link create .lingo/config.json, wiring the CLI to your organization and engine. Commit this file so every environment shares the same configuration.
{
"orgId": "org_abc123",
"engineId": "eng_abc123",
"sourceLocale": "en",
"targetLocales": ["es", "fr", "de", "ja"],
"files": [{ "pattern": "docs/en/getting-started.md" }]
}In CI, skip lingo login and provide LINGO_API_KEY as an environment variable instead. Generate one from API keys.
Documentation Sites#
Most documentation frameworks organize translated content in per-locale directories. Add a pattern per source file (or a glob) to files. The CLI preserves frontmatter, code blocks, and component syntax while translating Markdown, MDX, and Markdoc.
{
"orgId": "org_abc123",
"engineId": "eng_abc123",
"sourceLocale": "en",
"targetLocales": ["es", "fr", "de", "ja"],
"files": [
{ "pattern": "docs/en/getting-started.md" },
{ "pattern": "docs/en/setup.mdx" }
]
}Run the first translation, backfilling every target locale:
lingo push --backfill-missingOn later runs, lingo push translates only what changed. Use lingo pull to fetch translations produced elsewhere.
Adjust the source path to match your framework's directory convention:
| Framework | Locale directory convention | Reference |
|---|---|---|
| Docusaurus | i18n/[locale]/docusaurus-plugin-content-docs/current/ | Docusaurus i18n guide |
| Nextra | Per-locale pages or JSON dictionaries | Nextra documentation |
| Hugo | content/[locale]/ | Hugo multilingual guide |
| Astro | src/content/[locale]/ or JSON dictionaries | Astro i18n guide |
| VitePress | [locale]/ directory prefix | VitePress i18n |
| MkDocs | Per-locale docs/ with i18n plugin | MkDocs i18n plugin |
MDX components
MDX translation preserves JSX component syntax. Custom components like <Callout>, <Tabs>, and <CodeBlock> pass through unchanged - only the text content inside them is translated.
Structured Data#
JSON and YAML files translate automatically from their extension. Use key controls to prevent non-translatable values (IDs, URLs, configuration flags) from being modified.
{
"orgId": "org_abc123",
"engineId": "eng_abc123",
"sourceLocale": "en",
"targetLocales": ["es", "fr", "de", "ja"],
"files": [
{ "pattern": "content/en.json" },
{ "pattern": "data/en.yaml" }
]
}Generic YAML needs no format field. Only yaml-openapi and android require an explicit "format" on the file entry.
Locale-root-key YAML not supported yet
YAML files that use the locale code as the root key (common in Rails and Hugo) are not supported by the new CLI yet. Keep those on the legacy CLI and follow the changelog for updates.
Subtitles#
SRT subtitle files translate from their extension. The CLI preserves all timing data, cue indices, and formatting tags - only the text content is translated.
{
"orgId": "org_abc123",
"engineId": "eng_abc123",
"sourceLocale": "en",
"targetLocales": ["es", "fr", "de", "ja"],
"files": [{ "pattern": "subs/en/intro.srt" }]
}VTT not supported yet
WebVTT (.vtt) subtitles are not supported by the new CLI yet. Keep VTT files on the legacy CLI and follow the changelog for updates.
Working with Large Content#
Static content repositories can contain thousands of files. The CLI handles this efficiently:
| Mechanism | How it helps |
|---|---|
| Run state | .lingo/lock.json tracks fingerprints of source content, so lingo push only translates new or modified files. Commit it; it is regenerated on each push. |
| Server-side parallelism | The engine parallelizes translation for you - there is no concurrency flag to tune. |
| Targeted runs | Scope a run to specific files with a glob: lingo push "docs/en/**". |
To verify translations are up to date without writing files - useful as a CI gate - run lingo check.
