Skip to main content

LocalizeMe CLI

Pull and push LocalizeMe translations from your build or CI, as the files your app compiles and where your project keeps them: Android values-de/strings.xml, iOS Localizable.xcstrings or de.lproj, Flutter app_de.arb, web locales/de.json.

No runtime dependencies — everything it needs is in the Python standard library, so a CI step that installs it cannot break because of somebody else's release. Python 3.10 or newer.

pipx install localizeme

Setup

Create an API key under Developer access in the dashboard. A read-only key, which is what the dashboard makes unless you switch it, is all pull and init need; push needs one with full access. Limit it to the projects the repository uses, and the secret in its CI reaches nothing else in your account. Then:

export LOCALIZEME_API_KEY=lz_live_...
localizeme init --list                      # which projects can this key see?
localizeme init --project 42 --format xliff --path locales

init writes a localizeme.json you commit with the project:

{
  "project": 42,
  "format": "xliff",
  "path": "locales"
}

The API key is deliberately not in it. Settings resolve flag → environment → localizeme.json → default, so the file holds the boring facts and the credential stays in the environment. Paths in the file are relative to the file itself, so localizeme pull from a subdirectory writes where the file says.

Variable Purpose
LOCALIZEME_API_KEY Your lz_ key. Required.
LOCALIZEME_API_URL Override the API base, e.g. to point at sandbox.
LOCALIZEME_PROJECT Project id, if you would rather not commit one.

Where files go

path takes one of three shapes:

path Layout
locales A directory. Files keep the names the API gives them: translations_de.json, strings_de.xml.
src/locales/{code}.json A template. One file per language, named by filling in the placeholder.
ios/App/Localizable.xcstrings One file: a catalog that carries every language, or a target pinned to one language.

A template can use any of these placeholders, each a different spelling of the same language code:

Placeholder de pt-BR zh-Hans For
{code} de pt-BR zh-Hans Web frameworks, iOS .lproj directories
{locale} de pt_BR zh_Hans gettext, Java .properties, Flutter .arb
{android_code} de pt-rBR b+zh+Hans Android resource directories

An Android app keeps its default strings in values/, not values-en/, so a template takes a source_path for the project's source language:

{
  "project": 42,
  "format": "android-xml",
  "platform": "android",
  "path": "app/src/main/res/values-{android_code}/strings.xml",
  "source_path": "app/src/main/res/values/strings.xml"
}

The same with init:

localizeme init --project 42 --format android-xml --platform android \
  --path 'app/src/main/res/values-{android_code}/strings.xml' \
  --source-path app/src/main/res/values/strings.xml

codes gives a language a different code in this target's file names, when the platform wants one other than the project's. {"codes": {"zh": "zh-Hans"}} writes zh-Hans.lproj where {code}.lproj would have written zh.lproj.

Several apps in one repo

targets lists several sets of files, each with its own format, layout and platform. A repo with an Android app and an iOS app:

{
  "project": 42,
  "targets": [
    {
      "name": "android",
      "format": "android-xml",
      "platform": "android",
      "path": "app/src/main/res/values-{android_code}/strings.xml",
      "source_path": "app/src/main/res/values/strings.xml"
    },
    {
      "name": "ios",
      "format": "ios-xcstrings",
      "platform": "ios",
      "path": "ios/App/Localizable.xcstrings"
    }
  ]
}

Each target pulls its own platform's values, so a string with an iOS-specific value reaches the iOS app and the Android app gets its own. pull and push work on every target; --target ios picks one by its name. --format, --path, --platform, --source-path and --file describe a single target, so with several they need --target too. --language applies to all of them.

Commands

localizeme pull                     # every language, every target
localizeme pull --language de       # just one
localizeme pull --target ios        # just one target
localizeme pull --group Checkout    # just one feature's keys

localizeme push --dry-run           # what would change, writing nothing
localizeme push --overwrite         # replace existing values

With a template, push finds the files the template describes and reads each one's language from its path, so values-pt-rBR/strings.xml is Brazilian Portuguese. A directory that is not a language, like values-night/, is skipped with a note. With source_path, the source language is pushed from that file alone, so a values-en/ holding a few English-only strings beside values/ is left out. --language pushes just that language's file.

With a directory, push reads each file's language from its name, so translations_de.xlf, messages_pt_BR.properties, strings_pt-BR.xml and a plain de.json all resolve. It checks each one against the languages the project actually has, and a name for a region the project lacks is not guessed into the base language: strings_en-GB.xml in a project with only en fails with a message rather than putting British strings into en. Here --language overrides the name for every file. --file pushes specific paths instead of scanning.

An .xcstrings catalog carries every language, so it is pushed as it is, with no language to work out.

pull writes nothing until every target's export has arrived, so a failure part way through leaves your files as they were rather than half from each pull. push works out every target's files before it sends any.

push writes to the target's platform. A value that matches the shared one is left as it is, and one that differs becomes that platform's own value, so editing a string in the Android file changes it for Android only.

--group narrows a pull to the keys in a key group, by name. It is repeatable and a key in any of the named groups is pulled, so --group Checkout --group Errors gets both. --group none pulls the keys that are in no group. The files are still whole documents of their format, just covering fewer keys — which is what makes it useful for shipping one feature's strings on their own.

A language can answer to more than one code, so a file named for a second spelling lands on the language it belongs to rather than making a new one: no.json and nb.json both reach Norwegian, and so do values-no/ and values-nb/. If both exist with different strings, push stops for that language rather than guess; identical copies are pushed once. pull --language takes any of the codes too, and names the file after the code you asked for, which is how one set of strings ships as no on Android and nb on iOS.

Formats

json, po, ios-strings, ios-xcstrings, android-xml, csv, yaml, xliff (XLIFF 1.2), arb (Flutter), properties (Java) — the same set the dashboard offers. push also finds .yml and .xliff files.

Exit codes

The exit code is the real interface. A CI step reads it, not the prose.

Code Meaning
0 Fine.
1 The command ran, but some of the work in it failed.
2 The command was used or configured wrongly.
3 The API could not be reached, or refused the request.

1 and 3 are kept apart on purpose. A build that fails because the network blipped must not look like a build that failed because a file in it was bad.

In CI

pull is a step inside a build you already have, not a workflow of its own. The files it writes are worth nothing on their own — they matter to whatever compiles or bundles them next, so put it before that:

      # in an existing job, ahead of the step that builds your app
      - run: pipx install localizeme
      - run: localizeme pull
        env:
          LOCALIZEME_API_KEY: ${{ secrets.LOCALIZEME_API_KEY }}

About LocalizeMe

LocalizeMe is a localization platform for product teams: every string your apps ship, in every language, in one place. Translators work in an editor with review statuses, screenshots that show where a string appears, key groups and the full history of every change.

  • Unlimited keys and languages on every plan, the free one included. Paid plans are one flat price per workspace, not per seat or per string.
  • The files every platform uses: Android strings.xml, iOS .xcstrings and .strings, Flutter ARB, JSON, YAML, CSV, XLIFF 1.2, gettext PO and Java .properties, with a separate value per platform where iOS and Android need to differ.
  • Strings over the air: the iOS and Android SDKs update an app's text without a store release.
  • For developers and their agents: this CLI for builds and CI, a REST API, and an MCP server that Claude, Cursor and other coding agents can work with.

Start free, no credit card needed · Pricing · Developers

License

Source-available under the PolyForm Shield License 1.0.0, the same terms as the LocalizeMe mobile SDKs. Use it in any build or pipeline, commercial or not, and read and change it as you need. What it does not allow is using the CLI, or anything made from it, to provide a product that competes with the CLI or with LocalizeMe. Keep the LICENSE file, or its link and the Required Notice line, with any copy you pass on.

Development

pip install -e .
PYTHONPATH=. python -m unittest discover -s tests

Tests run a real HTTPServer on a loopback port rather than stubbing urllib, so the multipart encoding, auth header, zip handling and exit codes are all exercised the way a shell exercises them.

Metadata

Release files for localizeme 0.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for localizeme 0.1.1
File Size Uploaded
localizeme-0.1.1.tar.gz 37.5 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for localizeme 0.1.1
File Interpreter ABI Platform
localizeme-0.1.1-py3-none-any.whl Python 3 none any Details

Total release size: 67.7 kB

Release files / localizeme-0.1.1.tar.gz

Download URL localizeme-0.1.1.tar.gz
Size 37.5 kB
Tags Source
SHA-256 checksum
How to use checksums
0f909a864d4b5d46e65e84ca0f30de968db52948e44621881a80080f253b473f
BLAKE2b-256 checksum
How to use checksums
c70a532ca95394a7989cf61568a7c2710e1c6a3f9044733d526a9cdae3d6f572
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 30, 2026.

Transparency log

Release files / localizeme-0.1.1-py3-none-any.whl

Download URL localizeme-0.1.1-py3-none-any.whl
Size 30.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
50428ce813a40f99831b6811738e9ca1e07886eecf37152dcb3f20fc7ca71b8f
BLAKE2b-256 checksum
How to use checksums
222b0e6e37cfcabf6799f19383c9412b70fd9d7a52794c2816d762ea532dd12d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 30, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.1 This release

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page