|
Documentation
Book a DemoPlatform
PlatformMCPCLI
APIWorkflows
GuidesChangelog

Overview

  • @lingo.dev/cli

Getting started

  • Quickstart
  • Configuration
  • Examples

Reference

  • lingo push
  • lingo pull
  • lingo purge
  • Other commands

Configuration

  • Key controls
  • Formats
  • Locales

Guides

  • Adding a locale
  • Existing translations
  • Retranslation
  • Translator notes
  • Runs, state, and recovery
  • CI/CD
  • Monorepos
  • Large projects

Looking for the older CLI (v0)? See the legacy CLI docs

Formats

The CLI translates eighteen file formats. The format is inferred from the file extension; set format on a files[] entry to override it — or for the three formats that always need it (yaml-openapi, yaml-root-key, android).

FormatExtensionsformat valueNotes
JSON.jsonjsonKey/value. Supports key controls.
JSONC.jsoncjsoncJSON with comments. Comments survive and double as translator notes.
YAML.yaml, .ymlyamlGeneric YAML. Every string value is translated; keys and structure preserved.
OpenAPI YAML.yaml, .ymlyaml-openapiOpenAPI specs. Set format explicitly — plain .yaml auto-detects as yaml.
Locale-root-key YAML.yaml, .ymlyaml-root-keyLocale is the YAML root key (Rails config/locales). The root key is rewritten to the target locale. Set format explicitly.
Markdown.mdmdProse translated; frontmatter opt-in.
MDX.mdxmdxMarkdown + JSX. Component props opt-in.
Markdoc.mdocmarkdocMarkdown + tags. Frontmatter + tag attributes.
TypeScript.ts, .mts, .ctstypescriptLocale modules (export default { … }). String literals translated; code preserved.
Gettext PO.popomsgstr translated; msgid, comments, and headers preserved.
Flutter ARB.arbflutterString values translated; @-metadata and {placeholders} preserved.
Android.xmlandroidstrings.xml. Set format explicitly — .xml isn't auto-detected.
Xcode strings.stringsxcode-stringsValues translated; keys preserved.
Xcode String Catalog.xcstringsxcode-xcstringsOne file holds every locale — targets are written back into the same file (see below).
Xcode stringsdict.stringsdictxcode-stringsdictPlural strings translated; format control keys preserved.
XLIFF.xlf, .xliffxliff<source> kept, <target> written per unit. Versions 1.2 and 2.0.
SubRip.srtsrtSubtitle text translated; indices and timecodes untouched.
PHP.phpphpLaravel return [ … ]. String values translated; keys, numbers, and structure preserved.

See it end to end

The demo project is a small sandbox covering the document and data formats — JSON, JSONC, Markdown, MDX, Markdoc, and OpenAPI YAML — each with the exact config it needs. Clone it with npx degit lingodotdev/lingo.dev/demo/new-cli my-lingo-demo and run a push. The app and framework formats are not in it — for those, Example projects has a full repository per framework, and the per-format snippets below cover the config.

bash
npx degit lingodotdev/lingo.dev/demo/new-cli my-lingo-demo
cd my-lingo-demo

JSON and JSONC#

Plain key/value translation. Every string value is translated unless a key control says otherwise.

json
{ "pattern": "content/en/app.json", "lockedKeys": ["meta.version"] }

JSONC additionally preserves comments, which the engine reads as context — see translator notes.

json
{ "pattern": "content/en/settings.jsonc", "preservedKeys": ["featureFlags"] }

Markdown, MDX, and Markdoc#

Body prose is translated by default. Frontmatter and embedded components are not translated unless you opt in.

Frontmatter#

List the frontmatter fields to translate with translateFrontmatterFields:

json
{
  "pattern": "content/en/guide.md",
  "translateFrontmatterFields": ["title", "description"]
}

MDX component props#

For MDX, translate specific props on specific components with translateComponentProps:

json
{
  "pattern": "content/en/landing.mdx",
  "translateFrontmatterFields": ["title"],
  "translateComponentProps": [{ "component": ["Hero", "Callout"], "props": ["title", "body"] }]
}

This translates the title and body props on <Hero> and <Callout> and leaves all other props untouched.

Markdoc#

Markdoc works like Markdown, with frontmatter and tag attributes preserved:

json
{
  "pattern": "content/en/changelog.mdoc",
  "translateFrontmatterFields": ["title"]
}

YAML#

Generic YAML is auto-detected from .yaml/.yml — every string value is translated, keys and structure preserved:

json
{ "pattern": "content/en/strings.yaml" }

OpenAPI specs are a special case: they share the .yaml extension but need an explicit format so the engine translates only human-facing fields (summaries, descriptions) and leaves schema keys, paths, and operation IDs intact:

json
{ "pattern": "content/en/api.yaml", "format": "yaml-openapi" }

Rails-style locale files are the other special case: the locale is the YAML root key (en:) and the target file must be rooted at the target locale (es:). Set format explicitly for those too:

json
{ "pattern": "config/locales/en.yml", "format": "yaml-root-key" }

App and framework formats#

PO, Flutter ARB, Xcode .strings, Xcode .stringsdict, TypeScript locale modules, XLIFF, SubRip, and PHP all follow the same rule: translatable text is translated, everything structural is preserved (keys, IDs, metadata, placeholders, timecodes, code, formatting). Each is auto-detected from its extension — just point a pattern at the source file:

json
{ "pattern": "lib/l10n/app_en.arb" }
{ "pattern": "locales/en.po" }
{ "pattern": "Localizable.strings" }
{ "pattern": "l10n/en.xlf" }

One needs an explicit format because its extension is too ambiguous to auto-detect:

json
{ "pattern": "res/values/strings.xml", "format": "android" }

Xcode String Catalogs#

A .xcstrings (String Catalog) file holds all locales in a single file. There's no per-locale output path — the CLI reads the source locale and writes every target back into the same file:

json
{ "pattern": "Localizable.xcstrings" }

Strings flagged "shouldTranslate": false in the catalog are skipped — the CLI leaves them untranslated and untouched.

Output paths#

The pattern names your source file; every target path is derived from it. Four rules apply, in order:

  1. A whole path segment or filename that is the locale — the usual case. content/en/app.json → content/de/app.json, locales/en.json → locales/de.json.
  2. A locale at the tail of a segment or filename, for the layouts that spell it that way — Android, Xcode .strings/.stringsdict, Flutter, and yaml-root-key. res/values-en/ → res/values-de/, app_en.arb → app_de.arb, devise.en.yml → devise.de.yml.
  3. A platform's reserved name for the default language, with no locale in the path at all. Android's bare res/values/ becomes res/values-de/, and Xcode's Base.lproj becomes de.lproj.
  4. The String Catalog, where the target path is the source path, because one file holds every locale.

Android is the one platform whose target paths are not BCP 47: the CLI writes the resource qualifier Android actually reads, so pt-BR lands in values-pt-rBR/ and zh-Hans in values-b+zh+Hans/.

If none of the rules match — the source locale appears nowhere in the path — the CLI falls back to inventing a <locale>/ directory next to the file, and lingo push tells you it guessed. Treat that warning as a sign the pattern is wrong. See Configuration.

Coming from the legacy CLI?#

Most of the legacy CLI's formats have landed in the current CLI (PO, XLIFF, Android/Xcode strings, Flutter ARB, and more — see the table above). A few less-common formats (e.g. CSV, HTML, MJML, .properties) aren't supported yet; until yours lands, the legacy CLI docs cover it.

Was this page helpful?

Max PrilutskiyMax Prilutskiy·Updated 10 days ago·5 min read