Documentation

A clear path from strings to shipped.

Set up your repository, connect your coding agent, and bring reviewed translations back to your app.

On this pageGuides and reference

Guides and reference

CLI guide: setup, sync, translation, CI and troubleshooting

Every CLI command and option

MCP guide: local and hosted setup, tool schemas, permissions and examples

Create an account first, then pair the CLI and configure the repository. init stays offline; project create creates a hosted project in the workspace you name. Choose your own workspace slug and target languages.

Code
lingvo login --device
lingvo init
lingvo project create --workspace my-workspace --locale de --locale ja
lingvo push

Install

On macOS, Homebrew installs the Java 21 runtime with it, so this is the whole thing:

Code
brew install merkost/lingvo/lingvo

Everywhere else, an install script that verifies its own download and writes only inside your home directory. It needs a Java 21 runtime already present — `actions/setup-java@v4` in CI, or your distribution's `openjdk-21-jre-headless` — and checks for one before it downloads anything.

Code
curl -fsSL https://lingvo.dev/install.sh | sh

Both land the same build. Pin one with LINGVO_VERSION=0.1.0, and check what you have with lingvo --version — every published version stays at its own URL for ever, so a pinned CI job keeps resolving to the bytes it was tested against.

None of this is required to try Lingvo: importing and exporting live on each project's Data page. The CLI is for putting that same loop in CI.

Authenticate

For interactive setup, run lingvo login --device and approve your own pairing in the browser. For CI, use a project-scoped token with the permissions the job needs.

Code
lingvo login --device

Create a token in the web app under Settings → API tokens. It is shown exactly once. Credentials resolve in a fixed order and the first hit wins.

Credential sources, in resolution order
Source How to set it Use when
LINGVO_TOKEN export LINGVO_TOKEN=lgo_… CI, containers, agents. Nothing is written to disk.
~/.lingvo/credentials lingvo login --token-stdin A developer's own machine. The file is created mode 600.

Logging in without leaking the secret

Piping the token in means it never reaches argv, shell history or the process list. Passing it as a flag works and warns, because those three places all keep a copy.

Code
# Best: never touches argv, history, or the process list
printf '%s' "$LINGVO_API_TOKEN" | lingvo login --token-stdin

# Also fine: read from a named environment variable
lingvo login --token-env LINGVO_API_TOKEN

# Check what would be used — no network call, never prints the token
lingvo auth status

Configure — lingvo.yaml

The config lives at the repository root. Running lingvo init writes one for you by detecting the localization files already present and inferring the project kind.

Code
project: my-project-slug        # required — the project slug in Lingvo
source_locale: en               # required — the locale your source files are written in

files:                          # required — at least one entry
  - source: composeResources/values/strings.xml
    translation: composeResources/values-%locale%/strings.xml
    format: android_xml

  - source: iosApp/Localizable.strings
    translation: iosApp/%locale%.lproj/Localizable.strings
    format: ios_strings

generate:                       # optional — typed key accessors
  target: kotlin-object         # kotlin-object | swift-enum
  output: shared/src/commonMain/kotlin/app/Strings.kt
  package: app                  # kotlin-object only

%locale% is replaced with each target locale. Android uses its own qualifier form (values-pt-rBR), which lingvo init writes correctly for android_xml paths.

Commit .lingvo-lock.json

It is written next to lingvo.yaml and records the content hash of every synced file. Without it, sync cannot tell "unchanged" from "edited locally", and a local edit gets silently overwritten.

The sync loop

Three commands cover day-to-day use. None of them is metered on any plan — Lingvo does not charge for keys, languages or words.

Everyday commands
Command Key options What it does
lingvo status — Per-locale progress for the project.
lingvo push --dry-run --file --keep-obsolete --confirm-obsolete --translations --locale Uploads source files as keys. Keys absent from the payload are marked obsolete unless --keep-obsolete. A push dropping 5+ keys and over 20% of a file is refused until you pass --confirm-obsolete with the exact number the dry run reported.
lingvo pull --locale --fallback --format Writes translated files to the configured paths. Untranslated keys are omitted, never written empty.
lingvo sync --dry-run --adopt-local --locale --fallback --keep-obsolete --confirm-obsolete push then pull in one pass. A translation file edited locally is reported as a conflict; --adopt-local uploads it instead of overwriting. The obsoletion guard applies exactly as it does to push, so a release that deletes a screen needs --confirm-obsolete or --keep-obsolete.
lingvo scan --include --snippets --dry-run Records where each string is used in your source, so a translator sees checkout/CartScreen.kt:214 beside the string. Paths and line numbers only — pass --snippets to also send the matching line of code. Vendored and build directories, generated lines and your own translation files are skipped. Re-running replaces what was there, so a string that has left the code stops being listed.
lingvo watch — Re-pushes source files when they change. Interactive dev loop only — not for CI.

Always --dry-run the first push in an unfamiliar repo

Without --keep-obsolete, keys that exist in Lingvo but not in the pushed file are marked obsolete. The dry run prints the exact counts, including obsoletions, and changes nothing.

Reading the counts

Every push and sync reports five counts. The one to watch is skipped: it means the file held keys this project does not define, so their translations went nowhere. A non-zero skipped on a translation push almost always means the wrong destination file, or a source push that never happened — not "already up to date".

Inspect and export

Inspection commands
Command Key options What it does
lingvo keys list --file --search --state --limit Lists and filters keys.
lingvo qa --locale --fail-on-error Lists QA issues. With --fail-on-error, exits 1 when any ERROR-severity issue exists — the CI gate.
lingvo export --locale --file --format --bundle --out Writes one exported file, or a zip of every locale.
lingvo usage — Plan, billable seats, and machine-translation credits left this month.

Machine translation

Six providers are supported. mymemory and libretranslate need no key, so machine translation works before any setup at all.

Machine-translation commands
Command Key options What it does
lingvo providers list — Shows configured providers. Never prints keys.
lingvo providers set <id> --key-env --base-url --model --disable Configures a provider. The secret is read from stdin or --key-env, never argv.
lingvo providers test <id> — One-string round trip against the provider.
lingvo translate --locale --provider --only-empty --tone --dry-run Machine-translates and streams progress. Results failing placeholder or length validation are reported, never saved.

Provider ids: claude, openai, deepl, google, libretranslate, mymemory.

What credits are

One credit is one string of up to 1,000 characters, machine-translated into one language; Claude and OpenAI cost three. Longer strings cost one more credit per further 1,000 characters, and 2,000 characters is the per-string limit — past that the model drifts and the result is not reviewable. A plural is charged once, for its longest form, not once per category. A project using its own provider key spends none, because Lingvo is not paying for those calls. Running out never stops you translating by hand, importing, exporting or running QA.

Typed keys

lingvo generate reads the generate: block and emits typed accessors, so a renamed key becomes a compile error instead of a blank label in production. Name collisions are a hard error naming both keys — never a silent rename.

Code
generate:
  - target: kotlin-object
    output: shared/src/commonMain/kotlin/app/Strings.kt
    package: app
  - target: swift-enum
    output: iosApp/Generated/L10n.swift

Keys are fetched once and written to every target. Two targets pointing at the same file is a configuration error, since whichever ran last would silently win.

In CI

Set LINGVO_TOKEN as a secret and nothing is written to disk. Branch on exit codes rather than message text — they are a stable contract.

Code
- name: Pull translations and gate on QA
  env:
    LINGVO_TOKEN: ${{ secrets.LINGVO_TOKEN }}
  run: |
    lingvo pull
    lingvo qa --fail-on-error
Exit codes
Code Meaning Retry?
0 Succeeded. —
1 Ran, and reported an expected failure: QA errors with --fail-on-error, sync conflicts, rejected machine translations. No — fix the content.
2 Bad flag, bad argument, or invalid lingvo.yaml. No — fix the command.
3 No credential, or the server rejected it (HTTP 401/403). No — re-authenticate.
4 Server unreachable, timed out, rate-limited, or 5xx. Yes, with backoff.

Every command also takes --json, which prints a single stable envelope on stdout while human-readable progress stays on stderr.

Formats

Every format works in both directions, so you keep the native file type for each platform instead of reshaping your localization stack around the tool.

  • android_xml — Android string resources, including plurals and locale qualifiers
  • ios_strings, ios_stringsdict, xcstrings — Apple .strings, .stringsdict and String Catalogs
  • json_nested, json_flat — the two shapes web projects actually use
  • properties — Java and Spring resource bundles
  • yaml — Rails and generic YAML catalogues
  • csv — spreadsheets, for the people who insist
  • xliff, po — the standard interchange formats, for moving work in or out

What each format carries — plurals, comments, placeholder syntax, and what does not survive a round trip — is read from the parsers themselves. Read the format matrix.

Coding agents

Read the complete MCP setup guide and tool reference

Lingvo speaks Model Context Protocol, so an agent working in your repository can read and write strings the same way you do from the CLI. One command sets it up:

Code
lingvo agent install --client claude

That writes the client's config and a skill generated from the CLI's own command classification, so the instructions cannot describe a command that does not exist or miss one that does.

The thing worth knowing is what the agent gets to read. translation_brief hands it the same context bundle Lingvo assembles for a paid machine-translation provider — your style guide, the glossary terms that occur in the text, the CLDR plural categories the language actually uses, and for each string its developer context, its length budget, its placeholders and where it appears in your code. It is free and it spends no credits.

And an agent proposes; a person approves. There is no tool on this surface that can approve a translation, delete anything, or touch members, billing or provider keys — those do not exist here rather than being refused, which is the only defence against prompt injection that does not depend on a model behaving.

For a fleet that runs without you, create an agent connection in Settings → Agents. It carries its own scopes, a project allowlist, an autonomy level and a credit budget, all resolved on every call.

The full page: Coding agents.