@fieldlyab/translator-cli
v0.12.1
Published
Fieldly Translator developer CLI (ft) — push/pull/status translations between repos and Fieldly Translator.
Maintainers
Readme
ft — Fieldly Translator CLI
A thin command-line transport between your repo's translation files and Fieldly Translator (FT). It does not parse translation formats — it reads raw files at configured paths and POSTs/GETs them; FT's server-side engine handles every format (Rails YAML, ngx-translate/Transloco JSON, Android XML, iOS strings/stringsdict/xcstrings).
Cross-platform (macOS / Windows / Linux) via Node ≥ 18.
Fieldly Translator: ft.fieldly.com
Install
npm install -g @fieldlyab/translator-clior run without installing:
npx @fieldlyab/translator-cli loginThe binary is exposed as both ft and fieldly-translator.
Requirements
Fieldly Translator is invite-only for now — there is no self-service sign-up yet, so installing this CLI on its own does not grant access. You need an existing Fieldly Translator account that an admin, from Fieldly, has added. If you don't have access, contact your Fieldly Translator administrator.
Run ft from a repo that has a fieldly-translator.yml file at its root.
Commands
ft login [--token [T]] [--host URL] sign in via browser SSO, or paste a token
ft push [--locales L] [--branch B] [--force] [--dry-run] send new/changed source+authored keys up
ft pull [--locales L] [--branch B] [--release V] [--force] [--dry-run] write FT-owned target locales
ft status [--branch B] [--locales L] [--min-status S] report completeness; non-zero exit gates CI
ft get KEY [--locale L] [--namespace N] fetch a single key
ft set KEY code="text" [...] [--branch B] [--namespace N] [--status S] [--no-write|--write] hand-fill locale values on a key
ft create KEY "Source" [code="text" ...] [--branch B] [--namespace N] [--description D] [--status S] [--no-write|--write] [--wait] create a key with source + values
ft config push | check apply this repo's locale roles to FT, or report drift
ft init PROJECT [--force] write a fieldly-translator.yml from the project
ft request [--locales L] [--all] trigger translations for missing targets
ft --version | -v print the ft version
ft --help | -h show usageA flag this list does not name is a usage error (exit 2), never something
ft drops on the floor — the closest real flag is suggested. Before 0.12.0 an
unrecognised flag was accepted and ignored, so ft pull --brnach x quietly
pulled live head and ft pull --dry-runn quietly wrote your locale files.
--flag=value works the same as --flag value, and a boolean flag no longer
swallows the word after it.
The CLI sends User-Agent: ft/<version> on every request, and the server answers
with the oldest version it considers safe. If yours is below it, ft prints a
warning to stderr naming what is at risk — once per run, never changing the exit
code, and never blocking the command. The floor moves only when an older CLI
would corrupt a repo or lose data, not for new features.
Run ft <command> --help for the full option list.
Working on a feature branch
Name your branch and the strings a feature adds stay out of everyone else's way until it ships:
ft push --branch fp-1234-new-quotes # new keys are STAGED on that branch
ft create checkout.title "Checkout" --branch fp-1234-new-quotes
ft pull --branch fp-1234-new-quotes # main + branch: your branch's strings included
ft status --branch fp-1234-new-quotes # gate CI on just this feature's stringsA key born under --branch is staged: stored in FT, translated by AI and
reviewed as usual, but absent from ft pull on main, from exports, and from
completeness — until someone merges the branch in FT, which promotes it to live.
So a delayed feature is just an unmerged branch, and no half-finished wording
leaks into production.
ft pull --branch B is the developer's view of that: live head plus B's
staged keys, with B's staged edits overriding head values — everything the merge
will produce. A plain ft pull stays pure live head. --branch and --release
are mutually exclusive: a release is an immutable snapshot.
Naming a branch FT has never heard of does not fail the pull — "main +
branch" where the branch contributes nothing is just live head, so
ft pull --branch $(git branch --show-current) is safe in a script on a branch
that has not staged a key yet. It warns and names the branch you meant, so a
typo is still obvious rather than quietly handing you head:
Branch "fp-4048-quotes-calclation" is not in FT — pulling live head only.
Did you mean "fp-4048-quotes-calculation"?The match runs server-side across every open branch, so it finds the one you meant however long ago it was pushed. When nothing is close enough to be a typo, no guess is offered and the project's open branches are listed instead.
ft status --branch is stricter and still fails on an unknown branch: its
fallback would be a gate over zero keys, which passes CI green for a branch
nobody checked. It names the branch you meant too, so the failure tells you
what to fix.
Writing a value to a key that already exists on live (ft set --branch,
or ft push --branch over unchanged keys) is not staging — the value goes live
immediately and the branch is recorded for reporting, so ft status --branch
and the GitHub PR check cover it.
Hand-filled target values land as needs_review by default; --status is
honored when you're signed in via SSO and have editor rights on the locale.
AI fill (ft request) only ever fills missing values, so it never overwrites
what you set.
Both commands also update the mapped local files (skip with --no-write):
target files are re-exported like a scoped ft pull, while the repo-owned
source file gets just the new key spliced in — your unpushed source edits
survive. YAML/JSON formats only for now.
How much of that you want is a setting, because the two halves cost very
differently — the splice is local and instant, the target re-export is one
request per (locale, file block), so a batch of ft creates spends most of its
time re-downloading catalogues:
local_write: all # all (default) | authored | offauthored keeps the splice and leaves target files to your next ft pull;
off writes nothing. Per run, --no-write means off and --write means
all, whatever the file says.
ft pull (and the target half of the local write) merges rather than
overwriting: entries FT cannot store — Rails' date.day_names, number.format.
precision, and anything else the import reported as a skipped non-string value —
are kept in the file. FT still wins on every key it holds, so a key FT dropped
still disappears, and a file with nothing to preserve is written exactly as
exported.
Configuration
A single fieldly-translator.yml at the repo root declares the FT project, the
locale roles (source / authored / target), and the translation file paths per
platform and app.
When a repo's filenames disagree with FT's canonical locale code — Rails writes
en_au.yml, FT stores the BCP-47 en-AU — map them per format block:
targets: [en-AU] # the CANONICAL code
formats:
- platform: rails
format: rails-yaml
path: config/locales/{locale}.yml
locale_map:
en-AU: en_au # canonical -> on-disk filenamelocale_map changes the filename only, never the file contents. Declaring
the on-disk spelling in targets: instead creates a second locale in FT, and the
two then split the same language's translations between them.
