Skip to content

CLI reference

Pull, Push & Import

pull writes translations into your repository; push uploads new strings from it. export and import handle one-off files.

All four commands take the project, format and paths from langctl.json when you don’t pass them.

pull

terminal
$ langctl pull
+ src/locales/en.json  created
+ src/locales/es.json  created  41/45 translated
~ src/locales/fr.json  updated
= src/locales/de.json  unchanged
web-app: 45 published keys, 4 language(s), format json

+ created, ~ updated, = unchanged. Files are only rewritten when their translations change; a file you reformatted by hand but that holds the same translations is left alone. The 41/45 translated note shows languages with missing translations.

OptionDescription
[project]Project slug (default: project in langctl.json)
-l, --languages <codes>Comma-separated languages (default: all)
-f, --format <format>json, nested-json, android, ios, arb
-o, --output <template>Path template, e.g. src/locales/{lang}.json
-m, --module <name>Only keys from one module
--include-draftsInclude unpublished keys (excluded by default)
--checkDon’t write anything; exit 7 if any file is out of date
--dry-runShow what would change without writing
--require-completeExit 1 if any language is missing translations

Only published keys are pulled

New keys start as drafts. They appear in pull and export once published — in the dashboard, with langctl keys publish, or with push --publish. Use --include-drafts to preview them.

AI translations awaiting review are left out

AI translations that nobody has approved or edited yet are not pulled, so your app falls back to the default language for those strings until someone approves them in the dashboard. CLI 0.4.0 (not yet published to npm) adds a warning with the number held back, a --include-unreviewed flag, and langctl review to approve them from the terminal.

Platform layouts

Without an output, each format uses its platform’s standard location:

terminal
langctl pull --format json          # locales/en.json, locales/es.json
langctl pull --format android       # res/values/strings.xml, res/values-es/strings.xml
langctl pull --format ios           # en.lproj/Localizable.strings, es.lproj/Localizable.strings
langctl pull --format arb           # lib/l10n/app_en.arb, lib/l10n/app_es.arb

Point them anywhere with a template:

terminal
langctl pull --format android --output "app/src/main/res/{android}/strings.xml"
langctl pull --format ios --output "MyApp/Resources/{lang}.lproj/Localizable.strings"

An output without {lang} is only allowed for a single language — otherwise languages would overwrite each other:

terminal
langctl pull --languages es --output es.json

In CI

terminal
langctl pull --check               # fail (exit 7) if committed files are stale
langctl pull --require-complete    # fail (exit 1) if any language is incomplete

See CI/CD Integration for complete pipelines.

push

push reads your translation files and uploads them. By default it uploads only the source language (the project’s default language, or sourceLanguage in langctl.json), so it never overwrites translators’ work in other languages.

terminal
$ langctl push --dry-run
en  src/locales/en.json  3 new, 0 updated, 42 unchanged  (dry run)

$ langctl push --publish
en  src/locales/en.json  3 new, 0 updated, 42 unchanged, 45 published
OptionDescription
[project]Project slug (default: from langctl.json)
-l, --languages <codes>Languages to upload, or all (default: source language)
-f, --format <format>File format (default: langctl.json, else from the file extension)
-i, --input <template>Files to read (default: output from langctl.json)
-m, --module <name>Assign new keys to a module
--overwriteReplace existing translations (default: only add new keys and fill missing languages)
--publishPublish the uploaded keys
--dry-runCompare with the server and report changes without uploading

Placeholders from Android and iOS files

Android (%1$s) and iOS (%1$@) files don’t store placeholder names, so pushing them uploads {{1}}, {{2}}… langctl prints a warning when this happens. JSON and ARB files keep names like {{name}}.

export

A one-off export, for when you just need a file:

terminal
langctl export web-app --language es --format android --output strings.xml
langctl export web-app --format json --output "exports/{lang}.json"
langctl export web-app --language de --module checkout --include-drafts --output de-checkout.json

export accepts -l, --language, --languages, -f, --format, -o, --output, -m, --module and --include-drafts. For files that live in your repository, prefer pull.

import

Upload a single file. The project can come from langctl.json; the format is detected from the extension (.json, .xml, .strings, .arb):

terminal
langctl import web-app es.json --language es
langctl import fr.json --language fr --dry-run
langctl import web-app strings.xml --language de --overwrite --publish
OptionDescription
-l, --language <code>Language of the file (default: the project’s default language)
-f, --format <format>Override format detection
-m, --module <name>Assign new keys to a module
--overwriteReplace existing translations
--publishPublish the imported keys
--dry-runReport what would change without uploading

Nested JSON is flattened on import ({"home": {"title": "…"}} becomes home.title). Only string values can be imported; anything else is reported with its key.

Machine-readable output

Every command supports --json:

terminal
$ langctl pull --check --json
{
"project": "web-app",
"format": "json",
"publishedOnly": true,
"keys": 45,
"dryRun": true,
"files": [
  { "path": "src/locales/en.json", "language": "en", "status": "unchanged", "keys": 45, "translated": 45, "total": 45 }
],
"changed": 0
}