flutter_localisation_cli 1.5.0
flutter_localisation_cli: ^1.5.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 [...]
Changelog #
1.5.0 - 2026-08-22 #
Fixed #
- Regional and script locales are handled as one identity. Locale codes
arrived as whatever was typed, so
fl languages add pt_brcomparedpt_bragainst the project'spt-BR, found no match, and asked the API to create a locale that already existed. Every locale argument is now canonicalised to BCP-47 (pt_br/PT-BR→pt-BR,zh_hant→zh-Hant,es_419unchanged) before it is compared or sent. Junk is passed through untouched so the API's own error still names the field. - The ARB uploaded by
importcarries the filename gen_l10n reads. A variant producedapp_pt-BR.arb, which Flutter's generator does not pick up at all; it is nowapp_pt_BR.arb. add --auto-translateno longer reports a success as a failure. The server-side fill of a large project can outlive the socket, and the retry then reached a locale the first attempt had already created. That 400 is now followed by the same verify-and-report read as the happy path.- A rejected request shows the server's own reason. Errors carrying
errorrather thanmessage/detailprinted "POST /api/... failed" while the server had said exactly what was wrong.
1.4.0 - 2026-08-20 #
Changed #
- A value that comes back identical to its source is no longer reported as a
failure. It is almost always the correct answer — "OK", "Premium", "Normal",
"Start", French Nature, German Name — and calling it a failure cost more
than tidiness. It trained readers to skip the number; it hid the genuine
errors sitting in the same count; and because identical-to-source is
retranslate's selection criterion, it left those rows queued to be re-translated and re-reported on every run for ever.translate,add,importandretranslatenow reporttranslated/failed/unchangedseparately, and only a real error affects the exit code. - The backend's own per-row reasons are printed instead of summed. A count
of failures cannot distinguish a malformed plural row from a dead service;
the reason string can, and it was already being parsed and then discarded.
This applies to the whole-batch rejection path too, where the detail lives in
results.failedbehind a top-level "All translations failed."
Added #
fl retranslate --accept-unchanged(accept_unchangedon MCP) marks every value that came back identical to its source as checked, whichstatusandretranslatealready honour. This is what makes the queue closable: nothing in the pipeline previously set checked, so a deliberately-identical row could never leave the work list. Reviewers can flip any of them back withfl edit --no-checked.fl editgained the plural fields:--singular-case,--plural-case,--zero-case,--two-case,--few-case,--many-case,--plural-param,--plural. The backend stores plural cases separately and rejects a--valuecarrying the placeholder, so a bad plural translation previously could not be repaired from the terminal at all.- The client models every CLDR plural category. It had
zero,one(singular_case) andother(plural_case) only — notwo,fewormany. French, Spanish, Italian, Portuguese, Polish and Czech all requiremany; Polish and Czech also requirefew; Arabic requirestwo. Because the backend re-validates the whole row on any write, a row missing a category its locale requires could not be saved at all — not even an update touching nothing butis_checked. Those rows were unfixable through the CLI and unexplainable from its output, since the reason was being discarded.
Fixed #
import --translateno longer reports a successful import as a total failure. The import is written before the translate phase runs, so a timeout there — the likeliest outcome for a large import, and the one that actually happened with 837 keys — surfaced as a bareError: Gateway Time-outfrom a run whose keys were all safely created. Nothing said the keys were in, and nothing named the one-command remedy. It now reports what was imported, what failed, and points atfl retranslate, which chunks and therefore survives what one large request does not.- A whole-batch 400 whose rows are all identical-to-source no longer aborts
the run. "Every row in the chunk failed" is a 400 rather than a 207, and it
is the normal outcome once the only gap left is loanwords — so a project in
exactly that state could never complete a run, and therefore could never
reach
--accept-unchangedto get out of it. A rejection carrying no per-row detail is still fatal, so a genuine outage is not absorbed.
1.3.0 - 2026-07-31 #
Added #
fl guardnow teaches, not just blocks. Alongside the.claude/settings.jsondeny rules, it writes an AI-guidance block intoAGENTS.mdandCLAUDE.mdin the consuming repo, stating that the FlutterLocalisation SaaS is the source of truth and showing the correctfl add/fl edit/fl pullworkflow. A bare write refusal made agents route around the guard (writing the string elsewhere); the docs turn the block into an instruction. The block is delimited by<!-- fl:guard:start/end -->markers and refreshed idempotently, so a project's own notes in those files are preserved.- MCP server advertises
instructionsoninitialize. Any AI client connecting tofl_mcpnow receives top-level guidance (SaaS is authoritative, never hand-edit ARBs, mutate via tools thenfl pull, preview-before-apply) before it calls a tool.
1.2.1 - 2026-07-21 #
Fixed #
import --language <code>now writes ONLY that locale. It was sendingapply_to_all_languages=trueregardless, so a targeted per-locale import clobbered every other language with the file's values. A base-language import (no--language) still seeds all locales so fresh keys stay translatable.
1.2.0 - 2026-07-21 #
Added #
- ARB
@keymetadata now round-trips through the API.add/fl addandadd_string(MCP) gained--placeholder name:type[:format](repeatable) and--description, so a key can be created with typed placeholders (e.g.count:int:decimalPattern) and an ICU@key.description.edit/fl editandedit_string(MCP) gained--description.TranslationEntrynow readsdescription+placeholdersback from the backend.
Fixed #
edit_string(MCP) no longer drops placeholders. The edit dispatch previously omitted placeholder metadata entirely; it now forwardsplaceholders+is_placeholders_enabledalongside the newdescription.
1.1.0 - 2026-07-14 #
Fixed #
--translateno longer over-reports success.add/translate/import --translatetrusted the backend'ssuccess_count, which countssource == outputas translated (a silent false success). The CLI now re-fetches after translating and flags any locale whose value still equals its key's non-empty base text as failed, sotranslated N/Nnever lies.import --translate'sfailedfield is now a list (matchingadd/translate).
Added #
- Bulk ARB import —
import_arbMCP tool +fl importCLI command. Instead of many per-keyadd_string/fl addcalls, put all strings in an ARB JSON and import it in a single request (pathto a file or inlinecontentfor the MCP tool;fl import <file.arb>for the CLI).translate=true/-tthen batch-translates the other locales in one pass;overwrite/--overwritereplaces existing values;--language <code>sets the file's locale (default: base). Preview by default (apply=true/ no--dry-runto write). Backed byManagementClient.importArb()(multipart to/api/project/{id}/import-arb/) andOperations.importArb(). Requires astrings:writetoken. fl guardcommand — lock the backend-managed files against AI edits. Writespermissions.denyrules into the project's.claude/settings.jsonso Claude Code refuses toEdit/Writethe ARB directory (arb_dir+l10n.yamlarb-dir/output-dir) andlib/generated_translation_methods.dart— files that only ever change via the SaaS tooling +git pull, and whose hand-edits are otherwise silently overwritten on the next sync. Idempotent; merges into existing settings;--dry-runpreviews.
1.0.1 - 2026-07-14 #
Fixed #
fl_mcpnow finds the token fromfl login. It previously read only$FL_API_TOKENand ignored~/.config/flutterlocalisation/credentials.json, forcing users to duplicate the token into their MCP client config. It now uses the same resolution as theflCLI (explicit →$FL_API_TOKEN→ credentials file).Credentials.resolvegained an injectablecredentialsFilefor tests.
1.0.0 - 2026-07-07 #
Added #
- Initial release. The
flCLI andfl_mcpMCP server, extracted from theflutter_localisationpackage into a standalone pure-Dart package so they can be installed and run without the Flutter SDK (dart pub global runnow works, and the runtime library no longer carries CLI dependencies). flcommands:login,projects,add(--translate),edit,delete,translate,status,pull; global--project,--flavor,--config,--dry-run,--json.fl_mcp: stdio MCP server exposinglist_projects,list_status,add_string,edit_string,delete_string,translate_key— preview-by-default (only writes withapply: true), project selection by name.- Auth via a scoped
flk_live_API token; project config file is optional (workspace-level commands and--project <name>work token-only).