flutter_localisation_cli 1.8.0 copy "flutter_localisation_cli: ^1.8.0" to clipboard
flutter_localisation_cli: ^1.8.0 copied to clipboard

Terminal CLI (fl) and MCP server (fl_mcp) for FlutterLocalisation — add, edit, delete and AI-translate localization keys from the shell or from Claude, then pull the ARBs. Pure Dart; pairs with the fl [...]

flutter_localisation_cli #

Terminal CLI (fl) and MCP server (fl_mcp) for FlutterLocalisation — add, edit, delete and AI-translate your app's localization keys from the shell or from Claude, then pull the ARBs. No dashboard round-trip.

Pure Dart — no Flutter SDK required. This is the tooling companion to the flutter_localisation runtime package (which your app depends on to load the ARBs). Strings live in the FlutterLocalisation backend; this package changes them and the backend regenerates + pushes the ARBs to your git repo.

Install #

dart pub global activate flutter_localisation_cli

This installs the fl and fl_mcp executables in ~/.pub-cache/bin (put that on your PATH).

Auth #

Create a scoped API token in the dashboard → API Keys (flk_live_…), then either:

fl login --token flk_live_xxx      # stored in ~/.config/flutterlocalisation (chmod 600)
# or: export FL_API_TOKEN=flk_live_xxx     # or pass --token on each call

The token is workspace-scoped, so you pick projects by name — no numeric ids.

CLI #

fl projects                              # list your projects (name, flavors, locales)
fl status --project "Chat Bot"           # completion % per locale
fl add greeting --value "Hello" -t       # add key + AI-translate every other locale
fl edit greeting --locale fr --value "Bonjour"
fl import strings.arb -t                 # bulk-create MANY keys from one ARB, then translate
fl translate greeting --missing          # fill only empty locales
fl languages                             # list locales + how many keys each has filled
fl languages add id -t                   # add a locale and AI-fill EVERY existing key
fl languages remove id                   # drop a locale and its translations
fl delete greeting                       # whole key (or --locale fr for one locale)
fl repo                                  # print the ARB git repo URL (from the dashboard)
fl clone                                 # first-time: clone that repo into arb_dir
fl pull                                  # sync arb_dir with the service (git or API)
fl sync app_en.arb                       # import, pull and regenerate the Dart, in one
fl guard                                 # stop AI agents editing ARBs + generated Dart
fl native                                # tell iOS + Android about the locales, translate
                                         # the iOS permission alerts

fl import <file.arb> bulk-creates every key in an ARB in one request (instead of many fl add calls) — the right tool for large migrations. --overwrite replaces existing values; -t/--translate batch-fills the other locales afterwards; --language <code> sets the locale the file represents (default: the base language). Preview with --dry-run.

Bulk rules — read before scripting this CLI #

Work must cost O(1) requests, not O(N). Two commands cover every bulk case:

Goal Use Cost
Add / change many keys fl import file.arb -t 1 request
Populate new locales fl languages add <c1> <c2> <c3> -t 1 per locale, one command (backend back-fills every key)
Repair every locale fl retranslate (no --locale) batched
Accept the loanword rows fl retranslate --accept-unchanged 1 request
Add one key fl add 1 request

Never loop fl add, fl edit, fl translate, fl languages add or fl retranslate --locale. Each is already the bulk form of itself. Looping one is refused after the fourth single-target call in 15 minutes, and the refusal prints the command you should have run. If a request times out, do not lower the batch size — that multiplies request count; the client halves and retries by itself.

Never loop fl add, fl edit or fl translate over a list of keys. Each fl add -t is ~6 backend requests (resolve, create, batch-translate, verify). A 115-key ARB done that way is ~700 requests — enough to exhaust a whole day's quota for the account. The same job via fl import is one.

This is enforced, not merely advised:

  • the backend rate-limits single-key creation to 10/min and answers with a 429 that names the bulk endpoint to use instead;
  • the MCP server refuses add_string after a handful of applied calls in a session.

A 429 from either is not a signal to sleep and retry — backing off runs the same wrong algorithm slower. Switch to the bulk call.

Likewise, if fl import -t reports that nothing was translated, that is now a hard error naming the locales that were left unseeded (usually: re-run the import against the base language so every locale gets rows). Fix the bulk call; do not fall back to per-key writes. That silent "nothing to translate" success was the original trap.

fl pull / fl sync — getting the ARBs into the repo #

The ARB directory is generated output the backend owns. Nothing in it is a local contribution, so fl pull does not merge or rebase: it fetches and hard-resets onto the upstream branch. A dirty file or a stale local commit cannot block it, and whatever it discards is parked under refs/fl/pre-sync/<stamp>, printed so it can be read back with git show. You never need to run git in that directory yourself — that is the point.

When the git mirror cannot deliver (no repo linked, repository deleted, the wrong git credential active on the machine, no git at all), pull writes the same files from the API instead and says which path it took. The two writers are byte-identical, so failing over does not produce a diff.

fl pull                # sync arb_dir: git mirror if usable, API otherwise
fl pull --no-git       # skip the mirror, write from the API
fl sync app_en.arb     # import that file, pull, then regenerate the Dart
fl export path/to/dir  # just the write, to any directory

Keys that exist only on disk stop a sync. Both paths rewrite the template from the service, so a key staged by hand for an import that never ran would vanish — it has happened, and it took an app's build with it. pull and export name those keys and refuse; fl pull --adopt imports them first, --discard-local takes the service copy on purpose.

This is the dashboard's Export, from the terminal. The ARBs are the backend's data, not hand-written: @@locale is written in the same underscored form as the file name (gen_l10n compares the two and aborts the build when they disagree), and @key metadata goes in the template ARB only, which is where gen_l10n reads it from.

Prefer pull when the repo is linked — it is the path the backend pushes to.

fl native — the half of localisation that is not the ARBs #

Translating an app is three jobs, and the ARBs are only the first.

The UI comes from the ARBs. The declarations do not: CFBundleLocalizations in Info.plist and res/xml/locales_config.xml on Android. Neither translates anything — they are what puts every language in the App Store product page's Languages row and the per-app language picker in Settings. With the iOS key absent the bundle reads as English-only, so an app translated into 31 languages advertises one in every storefront. The permission alerts do not either: NSCameraUsageDescription and its siblings are drawn by iOS itself, from the bundle, before the Dart isolate exists. No runtime lookup can reach them. The only way to translate them is one <code>.lproj/InfoPlist.strings per language, registered with the Xcode target — an unregistered file is never copied into the bundle, which looks exactly like never having translated it.

All three rot silently. Nothing fails; the app just stops being translated where nobody looks. So fl native generates them from the locales the project already ships (read from the generated Dart, so it needs no ARB checkout, no token and no network):

fl native                # write everything
fl native --check        # exit 1 if anything is stale — for CI
fl native --dry-run      # list what would change
fl native --init-copy    # scaffold the permission-copy file

The permission sentences live in ios/Runner/InfoPlist.copy.json, keyed by locale:

{
  "en": { "NSCameraUsageDescription": "Trace over what your camera sees." },
  "fr": { "NSCameraUsageDescription": "Décalquez ce que voit votre appareil photo." }
}

They are not dashboard keys. Nothing in that file ever reaches Dart, so putting it through the ARB pipeline would buy nothing and cost a translation round-trip; translate it once and commit it. A locale in the app with no entry here is a hard failure rather than a silent fallback to English — a fluent- looking alert in the wrong language is the bug this command exists to prevent. --init-copy writes a starter from whatever your Info.plist says today.

fl native also writes locales_config.xml if the project has none and points <application android:localeConfig> at it, since a file nothing references is the same as no file.

First-time setup on a new machine or project #

The backend pushes generated ARBs to a git repo you link in the dashboard (Project → Git). fl pull only updates a repo that is already on disk, so bootstrap with clone:

fl repo --project "V-Shape"      # → https://github.com/you/shape_arbs  (github)
fl clone --project "V-Shape"     # clones it into arb_dir (default: ./arbs)
echo '/arbs/' >> .gitignore      # the backend owns that repo; don't vendor it

Then point l10n.yaml at the clone and run codegen:

arb-dir: arbs/Default
output-dir: lib/localization/generated
output-localization-file: app_localizations.dart
nullable-getter: false

fl repo reads the linked provider off the project detail endpoint (github, gitlab, bitbucket or bitbucket_server) and warns when the git token needs reconnecting. --json gives {project, repository: {provider, url, needs_reauth}}; it exits non-zero when no provider is linked. fl clone [<dir>] refuses to clobber a non-empty directory — use fl pull to update an existing clone. Preview either with --dry-run. If the clone never happens, or the machine's git credential cannot see the repo, fl pull still works: it writes the ARBs from the API.

fl guard writes permissions.deny rules into .claude/settings.json so Claude Code refuses to Edit/Write the backend-managed files — the ARB directory (arb_dir + l10n.yaml arb-dir/output-dir) and lib/generated_translation_methods.dart. Those may only change via fl / flutter_localisation + fl pull; a direct AI edit is silently overwritten on the next sync (or lost, for the gitignored ARB repo). Run it once per project (--dry-run to preview).

A project-local flutterlocalisation.json is optional — set it to avoid --project each time:

{ "project": "Chat Bot", "flavor": "Default", "arb_dir": "widget_chat_arbs" }

Global flags: --project <name|id>, --flavor, --config, --dry-run, --json.

MCP server (Claude) #

fl_mcp lets Claude manage your translations. Mutating tools are preview-by-default — they only write when Claude passes apply: true.

Claude Code:

claude mcp add flutter-localisation --env FL_API_TOKEN=flk_live_xxx -- fl_mcp

Claude Desktop (use the absolute path — Desktop's PATH often omits ~/.pub-cache/bin):

{
  "mcpServers": {
    "flutter-localisation": {
      "command": "/Users/you/.pub-cache/bin/fl_mcp",
      "env": { "FL_API_TOKEN": "flk_live_xxx" }
    }
  }
}

Then ask: "list my projects", then "in Chat Bot, add checkout_button = 'Buy now' and translate it to all locales." Tools: list_projects, list_status, list_languages, add_language, project_repo, add_string, edit_string, delete_string, translate_key, retranslate, import_arb, pull_arbs (each takes an optional project/flavor), and sync_native_locales — the MCP face of fl native, which needs no token and takes an optional project_root. project_repo returns the linked ARB git repo so the agent can clone it before codegen; pull_arbs writes the ARBs into arb_dir the same way fl pull does.

How strings reach your app #

fl add / Claude ─▶ backend creates/translates ─▶ backend pushes ARBs to your git repo
                                                            │
                                     fl pull / fl sync  ◀───┘ ─▶ codegen ─▶ typed strings

License #

MIT.

0
likes
140
points
101
downloads

Documentation

Documentation
API reference

Publisher

verified publishertajaouart.com

Weekly Downloads

Terminal CLI (fl) and MCP server (fl_mcp) for FlutterLocalisation — add, edit, delete and AI-translate localization keys from the shell or from Claude, then pull the ARBs. Pure Dart; pairs with the flutter_localisation runtime package.

Homepage
Repository (GitHub)
View/report issues

Topics

#localization #i18n #cli #mcp

License

MIT (license)

Dependencies

args, http, path

More

Packages that depend on flutter_localisation_cli