CLI
The easyi18n command downloads published files, synchronizes Xcode String
Catalogs, and registers strings extracted from Dart tr() calls. Start with
Getting started to create your
project and configure its languages and output formats. To let an AI coding
agent run these workflows, see AI agents. The
command reference lists every command and option. To call the
server without the CLI, see the REST API.
This reference describes CLI 0.12.0, published on
pub.dev on September 23, 2026.
Earlier versions lack formats add, stack-aware initialization, JSON output,
and the Mode A pull completeness gate. Check easyi18n --version and your
installed command's --help before using these workflows.
Install and authenticate
The CLI requires Dart 3.9 or later within Dart 3.x, or one of the prebuilt binaries that need no Dart. Install the published package globally for a shell or CI environment:
dart pub global activate easyi18n_cli
easyi18n --version
easyi18n --help
Make sure Dart's global executable directory is on your PATH. In a Flutter
project, you can instead keep the CLI as a development dependency:
dart pub add dev:easyi18n_cli
dart run easyi18n_cli:easyi18n --version
The examples below use the global executable. With a project dependency,
replace easyi18n with dart run easyi18n_cli:easyi18n.
Create a key under project Settings → API keys → New API key. Supply it
through your shell or CI secret named EASYI18N_TOKEN; commands also accept
--token, which takes precedence. The CLI does not save the credential in its
configuration. Keep it out of source control and client application code.
| Operation | Required scopes |
|---|---|
pull, status, apply | read |
doctor published coverage in Mode A | read; other checks are local or use public delivery |
push | translate |
push --publish | translate, publish; local Mode A also needs read |
sync with publication | translate, publish, read |
sync --no-publish | translate |
formats add | write |
rollback | publish |
Automatic adoption of a missing output format also needs write. The offline
commands init and extract do not require an API key.
Configure a project
Use the workspace handle and project slug from your app URL. For
@acme/mobile-app, initialize a Flutter file integration with:
easyi18n init --workspace acme --project mobile-app --mode a
This creates easyi18n.yaml and ignores derived .easyi18n/ state in Git:
workspace: acme
project: mobile-app
mode: a
baseUrl: https://api.easyi18n.com
format: arb
output: lib/l10n
Use projectId instead of the workspace/project pair if you have an opaque
project ID; init --project-id accepts it. Do not combine the two forms.
output is a directory, relative to the config file. Set it to the arb-dir
in your own l10n.yaml; the default is lib/l10n.
init defaults to the Flutter adapter. It detects Mode A from l10n.yaml,
otherwise Mode B; --mode a or --mode b makes your choice explicit. Xcode
setup uses --stack xcode; --stack auto detects Flutter before Xcode.
Preview changes with init --dry-run. Re-running init uses an existing
config; --force rewrites that config from the flags. An unknown YAML setting
produces a warning. Omitting mode in a hand-written config means Mode B.
The local mode does not change the project's dashboard setting.
Use the global --config option before the command for another config path:
easyi18n --config config/easyi18n.yaml status
Pull published files
After enabling your output format and publishing in the app:
easyi18n pull
flutter gen-l10n
The second command applies to Flutter ARB projects. Other stacks run their own localization build step. Pull reads published snapshots, not draft edits.
| Option | Behavior |
|---|---|
--version 2026.09.19.1 | Download that published version; the default is latest. |
--lang es | Download one locale. Not supported for .xcstrings. |
--dry-run | Fetch and report the proposed files without writing them. |
--force | Replace a file that changed since the pull that wrote it. |
--watch | Follow new published versions until stopped. |
--interval 30 | Watch polling interval in seconds; minimum 5, default 30. |
--require-complete | Check the published version's target-language coverage in Mode A. |
Watch cannot combine with --version, --dry-run, --require-complete, or
--json. It can wait for the first publication if the project is not yet
published.
Pull records its writes in .easyi18n/state.json. On a later full pull into
the same format and directory, it removes previously tracked files that the
server no longer serves. A single-language pull does not prune files, and
switching formats or output directories does not delete files from the old
location. Keep .easyi18n/ ignored.
The record also holds a digest of each file the pull wrote. When one of them
changed since, the next pull names it, writes and removes nothing, and exits
with status 1 and the code edited_targets; --dry-run reports the same.
With --json the files are listed under edited, beside error.
Reformatting is not a change: JSON and ARB files are compared decoded, with
their keys sorted, and other formats with their line ends folded. The
@@last_modified stamp of an ARB file is not compared.
To keep the change, make it in the dashboard, publish, and pull again: a file
that already says what the pull serves is kept. To discard it, run
easyi18n pull --force. The record belongs to one checkout, so a file that
arrived through Git from a pull on another machine reads as changed too. A
String Catalog is merged, not replaced, and has no digest.
Check completeness in CI
For a Mode A project, check without changing the checkout:
easyi18n pull --dry-run --require-complete
This exits 1 for missing translations or when completeness cannot be verified.
The published snapshot must report coverage based on Skip and have at least
one target language. Source-fallback snapshots, unstamped older snapshots,
unrecognized coverage semantics, and backends without coverage are refused.
--lang may select a target language, but selecting the base is refused.
The check concerns the selected published snapshot, including alias entries;
it does not grade unpublished edits. A normal pull with --require-complete
can write files before reporting the failed gate. Use --dry-run when CI must
leave the checkout untouched.
status compares published version identifiers with the last pull. It does
not check translation completeness or local file edits. It exits 1 if the
project has never been published, has never been pulled, or is behind.
Sync an Xcode String Catalog
Initialize from the root of your Xcode project:
easyi18n init --stack xcode --workspace acme --project ios-app
easyi18n sync --dry-run
easyi18n sync --yes --max-credits 50 --require-complete
Xcode initialization selects Mode A and format: xcstrings. It looks for an
existing catalog, preferring Localizable.xcstrings and excluding
InfoPlist.xcstrings from discovery. Use --catalog MyApp/Localizable.xcstrings
to select the file explicitly. It does not create a catalog or add Flutter
runtime files.
workspace: acme
project: ios-app
mode: a
format: xcstrings
catalog: MyApp/Localizable.xcstrings
catalog is a file path, relative to the config. sync supports this
single-file catalog format and the per-locale JSON files of a web project,
described below; an ARB directory is not a sync catalog.
Sync previews changes and estimated credits, asks for consent, uploads the
catalog, waits for translation, publishes, and writes the result back to the
same catalog. --yes supplies consent for CI; choose a --max-credits value
appropriate to your budget. The dry run still calls the server to classify and
price the upload, but does not apply it or spend credits.
| Option | Behavior |
|---|---|
--no-publish | Upload and translate without publishing or downloading the result. |
--require-complete | Fail if translation remains incomplete, including exhausted pairs; with publication enabled, stop before publishing. |
--on-conflict server | Keep newer project text and report conflicts; this is the default. |
--on-conflict file | Prefer catalog text on conflicts, including over a source correction. |
--max-credits 50 | Refuse work exceeding the credit budget; also checked in dry run. |
--force | Replace a locale file that changed since the pull that wrote it. |
In a project with one file per locale, the write-back replaces the locale
files. Sync stops before it uploads anything when one of them changed since
the pull that wrote it, with the code edited_targets. Run easyi18n pull
first, or pass --force; see Pull.
Read language and parse diagnostics in the preview. Languages present only in the catalog do not become project translation targets automatically.
For a readable local catalog, pull and sync merge served translations into
.xcstrings, preserving local entries and Xcode metadata. Pull can append keys
created in the dashboard; sync keeps the key set from the catalog it uploaded. They do not delete a key merely because it
is absent from the server. Xcode extraction remains responsible for removing
obsolete local entries. Read any base-text replacement warnings before accepting
the diff. pull --lang is refused because the catalog holds all languages
together.
Sync a web project with JSON catalogs
For an app that keeps one JSON file per locale and reads it with Transloco, ngx-translate or i18next:
easyi18n init --stack web --workspace acme --project web-app
easyi18n sync --dry-run
easyi18n sync --yes --max-credits 50
workspace: acme
project: web-app
mode: a
format: json
output: public/i18n
source: public/i18n/en.json
sourceRoot: public/i18n
sourceLayout: locale-files
source is the base-language file, relative to the config. sync uploads it,
waits for the translation, publishes, and writes one file per target language
into output. sync and pull never write the file source names.
sync refuses a project whose base language is split into several files, such
as i18next namespaces or Transloco scopes, before it uploads anything. The
error names the files. Both sourceRoot and sourceLayout are required;
existing configurations must add them. The contract is unreleased and requires
a build containing the final redesign of PR #2098. Web apps with JSON catalogs covers
the libraries, the ICU plugin each one needs and the limits.
Push strings from the Flutter runtime SDK
For a project using context.tr() or 'literal'.tr():
easyi18n init --workspace acme --project mobile-app --mode b --dry-run
easyi18n init --workspace acme --project mobile-app --mode b
flutter pub get
easyi18n extract
easyi18n push --dry-run
easyi18n push --yes --max-credits 50 --publish --require-complete
Mode B initialization adds the runtime dependency and asset directory, patches
macOS network entitlements when present, and attempts to wrap runApp in
Easyi18nScope. Read its report: when automatic wiring is not possible, it
prints a snippet for you to apply. doctor checks the resulting integration.
For SDK usage and bundled offline translations, see
Flutter SDK.
extract scans lib offline. It reports extractable strings, dynamic calls,
new entries, and orphans. Use --source-dir to scan another directory, relative
to the config, or --fail-on-orphans to fail on strings removed from source.
Variables and Dart interpolation cannot be extracted as static literals; use
placeholders, for example context.tr('Welcome {name}', args: {'name': name}).
Push previews cost before registering and translating strings. Without
--publish, it does not publish or wait for the fill to complete.
| Option | Behavior |
|---|---|
--dry-run | Preview estimated work; register and translate nothing. |
--yes, -y | Accept the operation without an interactive prompt. |
--max-credits 50 | Set a credit budget; decimal amounts are accepted. |
--lang es --lang fr | Restrict translation targets. Explicitly selecting a manual-fill language is refused; default target selection skips manual languages. |
--source-dir lib | Choose the source directory to scan. |
--prune | Remove orphan entries from the local lockfile; never delete server translations. |
--publish | Wait for fill and publish a new version. |
--approved-only | With --publish, count unapproved translations as missing. |
--require-complete | With --publish, stop if extracted strings remain untranslated after retries. A scan with no extractable strings is refused. |
push --publish cannot combine with --dry-run. With --json, its record
reports the published version in a published block. Without
--require-complete, publication can proceed with missing translations:
local Mode A requests Skip; otherwise the server uses the project's mode.
Publication may also report values refused by validation, so read its warnings.
Commit easyi18n.lock, which tracks registered strings. For ARB projects whose
strings live in files rather than tr() calls, use the pull completeness gate.
Enable output formats
easyi18n formats add arb
easyi18n --json formats add xcstrings
This requires CLI 0.12.0 and a backend supporting the output-formats API. It
adds a format without removing existing formats. A repeated add is safe; the
response's created field says whether configuration changed. It does not
publish or prove that a version is downloadable.
File formats lists the format IDs and what each
file contains.
Sync with publication and Mode A push --publish require readable format
metadata and output-formats API version 1 before cost preview. If no format is
configured, they can adopt the requested format after budget checks and consent,
using write scope. If a different format is already configured, correct your
local format or explicitly add the intended one.
Pull can recover an eligible missing-format error by adopting the format and
retrying once. A read-only key cannot do that; ask a write-scope holder to enable
it. A dry run never adopts and reports output_format_pending when needed.
Configuration changes affect downloads of pinned versions too, and remain in
place if a later translation or publication step fails.
Diagnose and maintain an integration
| Command | Use |
|---|---|
doctor | Check config, stack wiring, ICU syntax, and delivery. Mode A also reads published coverage with your read credential. It never spends credits. |
status | Compare the last pulled version with the currently published version. |
apply | Preview source-review corrections to Dart literals; apply --write writes them. Requires read and spends no credits. Review the diff, then push. |
rollback | Restore the preceding published version as a new version. Supply a version argument to restore a specific one. Requires publish. |
mcp | Run the MCP server over standard input/output for an agent client; this is not a JSON batch command. See AI agents. |
Doctor exits 1 for blocking findings. An unverified check can still exit 0; read the report instead of treating its exit code as a completeness gate. A base-only project has nothing to grade in doctor, but the pull completeness gate refuses it.
Rollback changes live delivery and keeps version history. Current validation rules can omit cells from the restored version; inspect its warnings.
Structured output
Put global options before the command:
easyi18n --json status
easyi18n --json pull --dry-run --require-complete
easyi18n --json sync --dry-run
easyi18n --json push --yes --max-credits 50
JSON is supported by init, doctor, one-shot pull, extract, push,
sync, apply, rollback, status, formats add, and languages add.
Normal reports go to stdout and diagnostics to stderr. JSON workflows cannot
prompt for consent; pass --yes for real push or sync work that requires it.
Command failures use an error object containing message, code,
statusCode, and retryable. A usage error, such as an unknown option or a
value the parser refuses, is the same record with code usage_error and exit
64 when --json is its own argument before any --. Without --json it is
plain text on stderr with exit 64. Check the process exit status as well as JSON:
some commands, such as status, return a report with a nonzero exit code.
Do not blindly retry a publish or rollback after an ambiguous network failure;
inspect project history to find out whether it completed.