StringLane

    Type to search titles, sections and body text across all 58 pages.

    Reference

    CLI Command Reference

    Every stringlane command and flag, what each one writes, and the exit code it sets. Transcribed from the shipped binary at version 0.9.0.

    Last updated

    Every command except upgrade and telemetry takes an optional [project] argument, which defaults to the current directory. Those two are not about a folder and refuse one with exit 2.

    This page is transcribed from stringlane --help and each command's own --help at version 0.9.0. Where it disagrees with your terminal, your terminal is right: run stringlane <command> --help.

    Reading commands#

    These write nothing inside your project: not a lock file, not a re-saved locale file, not a byte. They do write to StringLane's own state directory, outside your repository, so there is nothing to gitignore:

    • install.json, a random install ID, created by the first command on a machine. Not when DO_NOT_TRACK=1, STRINGLANE_TELEMETRY=0 or a CI runner turns usage data off: then nothing is written to the state directory at all. A stored stringlane telemetry off does not stop the file being created; it stops anything being sent.
    • update-check.json, the date and result of the update check. The background check does not run when STRINGLANE_NO_UPDATE_CHECK=1, DO_NOT_TRACK=1, STRINGLANE_TELEMETRY=0 or a CI runner turns it off; stringlane upgrade writes it regardless.
    • From check and scan, projects/<hash>/usage-cache-v1.json: each scanned source file's path inside the project, size and modification time, and every key name found in it with its line number.

    STRINGLANE_STATE_HOME moves the directory. Install and update lists everything it can hold.

    stringlane inspect [project]#

    Reports what the project holds: every locale, how complete it is, and which files were read.

    FlagMeaning
    --jsonEmit the raw result envelope instead of human text
    --output <file>Write output to a file rather than stdout

    stringlane check [project]#

    The inspect report plus every validation finding, and an exit code.

    FlagMeaning
    --jsonEmit the raw result envelope instead of human text
    --output <file>Write output to a file rather than stdout
    --fail-on <codes>Only these issue codes set exit 1 (comma-separated)
    --no-scanSkip the source-code scan for unused keys
    --positionsReport the file and line each issue sits on
    --ignore-key-pattern <glob>Do not report usage on keys matching this glob; repeatable
    --ignore-file <glob>Do not read source files matching this glob; repeatable

    check also runs the source-code scan described under scan. Its two findings are reported at info severity, so adopting a newer CLI does not turn a pipeline red and a recorded baseline does not flip. Opt in with --fail-on unused_key, or skip the scan entirely with --no-scan. Passing both is refused rather than accepted: with the scan off nothing can emit that code, so the gate could never fire.

    --positions adds the file and line to each finding, as de/app_name (Localizable.xcstrings:15) in the text report and a position object with file and line in --json.

    If .stringlane/baseline.yaml exists it is read automatically, and only findings not in it decide the exit code. --fail-on filters what is left, so an accepted finding stays accepted either way.

    --fail-on decides which findings the exit code is computed from, in both directions. Naming a code that is present exits 1, even for an info finding that would not fail on its own, and naming codes that are all absent exits 0. It cannot change a 2: a run that could not read your project still exits 2 whatever you pass.

    With sourceStamps: true in stringlane.yaml, check also reports translation_stale, a translation whose base text changed since it was confirmed, and translation_unconfirmed, one nobody confirmed or whose text changed since. Both are warnings, so a stale translation makes check exit 1, and both work with --fail-on. stringlane accept confirms them. See the validation codes.

    check also exits 2 in three cases that come from source stamps:

    • sourceStamps: true is set and .stringlane/source-stamps.json is missing, unreadable, malformed or not a plain file. In CI that usually means the file was never committed, or a .gitignore hides it.
    • The baseline holds a translation_stale or translation_unconfirmed entry. A baseline cannot hide a stale translation; run stringlane baseline write again to regenerate it.
    • --fail-on names translation_stale or translation_unconfirmed while sourceStamps is off, so the gate could never fire.

    When the skills stringlane setup skills wrote are older than the CLI running check, it prints one line about it on stderr. The report, --json, --output and the exit code are unaffected.

    stringlane plan [project]#

    The exact list of what needs translating, for an agent to work from.

    FlagMeaning
    --jsonEmit the raw result envelope instead of human text
    --output <file>Write output to a file rather than stdout
    --out <file>Alias for --output
    --target <locales>Only plan these locales (comma-separated)

    --out alone writes the human report. Pass --json as well to write the plan itself, which is the file validate and apply read. It refuses any path this project loaded as a translation file.

    Exit 0 even when there is work to do, and 1 only when a file failed to parse.

    An empty plan says why it is empty, because zero items can mean five different things and each needs a different next step. --json carries the reason as emptyReason:

    emptyReasonMeaningNext step
    incompleteThe project did not fully load: a locale file failed to parsestringlane check names the file
    no-targetsThere is no target locale to translate intostringlane add-locale --locale <code>
    empty-sourceThe base has no value for the outstanding keysFill in the base, or remove the keys
    unroutableA locale has no file for one of the project's originsThe report's warning names the origin
    completeEvery target locale is translatedNothing to do

    Only complete means the project is done. With sourceStamps: true, complete means nothing is left to translate; stringlane check can still list translations a person has to confirm.

    A cell an earlier apply wrote that a revert emptied again is offered as missing. A translation whose base text changed since it was confirmed is offered again as source-changed, and one an earlier apply wrote whose context or description changed since as context-changed. Without sourceStamps: true, only cells an apply wrote are covered. With it, a translation the stamps file records as confirmed is offered once its base text moves, carrying the old text as previousSourceValue when the stamps file holds it; one never confirmed is reported by check instead.

    stringlane validate [project]#

    Checks an agent's translations without touching a file.

    FlagMeaning
    --jsonEmit the raw result envelope instead of human text
    --plan <file>The plan file to work against (required)
    --candidates <file>The candidate translations to read (required)

    Exit 0 clean, 1 the candidates have problems, 2 could not run. A clean validate is not permission to write: see How the write gate works.

    stringlane scan [project]#

    Reads your source code and reports two things: keys the project defines that no call site names, and keys the code calls that the project never defined.

    FlagMeaning
    --jsonEmit the raw result envelope instead of human text
    --output <file>Write output to a file rather than stdout
    --ignore-key-pattern <glob>Do not report on keys matching this glob; repeatable
    --ignore-file <glob>Do not read source files matching this glob; repeatable

    The text report caps both lists so its caveat stays on screen; --json carries them whole. Both flags add to ignoreKeyPatterns and ignoreFilePatterns in stringlane.yaml rather than replacing them.

    "Not referenced" is what the scanner saw, not a verdict. See Unused keys for what it cannot see and why it refuses rather than guessing.

    Writing commands#

    Each asks before writing, and refuses when there is no terminal to ask at unless --yes is passed.

    stringlane init [project]#

    Detects the project and writes stringlane.yaml, with source stamps on for a new config.

    FlagMeaning
    --yesSkip the confirmation prompt
    --forceOverwrite an existing stringlane.yaml

    In a folder with no stringlane.yaml, init writes two files after one question that names both:

    • .stringlane/source-stamps.json, written first. It stamps every translation the project already holds as current against today's base text, and says so: StringLane cannot know whether they were current before today. A String Catalog unit Xcode marks needs_review is left unstamped, and init says how many.
    • stringlane.yaml, written last, with sourceStamps: true.

    Commit both. With the flag on, check, plan and apply read the stamps file, and a project without it exits 2. init warns when a line in the project's own .gitignore hides .stringlane/ or the stamps file. A project that did not fully load gets a stringlane.yaml without the flag and no stamps file. With --force, init replaces an existing stringlane.yaml, writes no stamps file and does not add the flag; an existing sourceStamps line is carried over.

    stringlane apply [project]#

    Writes an agent's translations into your locale files.

    FlagMeaning
    --plan <file>The plan file to work against (required)
    --candidates <file>The candidate translations to read (required)
    --jsonEmit the raw result envelope instead of human text
    --yesSkip the confirmation prompt
    --force-unlockTake the apply lock even if another process still holds it

    Re-runs every validate check, and additionally verifies under a per-project lock that no file the plan fingerprinted has changed since the plan was made: every locale file the project loads, not only the targets, plus the .stringsdict beside each .strings file, stringlane.yaml and .stringlane/metadata.yaml. From desktop 1.3.0 the app's own saves take the same lock, so an apply and the app never write one project at the same time. All or nothing. Exit 0 applied, 1 the candidates have problems, 2 could not run.

    With sourceStamps: true it also writes .stringlane/source-stamps.json, and exits 2 when that file is missing, unreadable, malformed or not a plain file.

    Before opening any file, apply and validate both refuse a plan that names a file the project does not manage: a locale file deleted, renamed or broken since the plan was made, or a plan edited by hand. That is STALE_PLAN, exit 2. Build a new plan.

    stringlane update [project]#

    Translates the missing strings using your own AI provider, then writes them through the same gate.

    FlagMeaning
    --provider <name>AI provider (default openai, or $STRINGLANE_AI_PROVIDER)
    --model <id>Model id (default: the provider's, or $STRINGLANE_AI_MODEL)
    --target <locales>Only translate these locales (comma-separated)
    --out-dir <dir>Also write the plan and candidates used, for auditing
    --parallelism <n>Model calls in flight at once, 1 to 8 (default 2, or $STRINGLANE_AI_PARALLELISM)
    --jsonEmit the raw result envelope instead of human text
    --yesSkip the confirmation prompt

    The only command that spends money. Keys are read from the environment only: see Translate with your own key.

    --parallelism wins over STRINGLANE_AI_PARALLELISM. A value that is not a whole number from 1 to 8 exits 2 before any credential is read or any request is made; it is never clamped. The spend question says how many items replace a translation that is already there, and --json carries that count as retranslatedCount.

    stringlane describe [project]#

    Writes key descriptions, so translations are made with context.

    FlagMeaning
    --descriptions <file>JSON file of key and description entries to write
    --jsonEmit the raw result envelope instead of human text
    --yesSkip the confirmation prompt

    In a project that spreads its base locale across several files, such as a Gradle project with several modules, describe refuses with exit 2 and writes nothing, because it cannot yet choose which file each description belongs in. Add those descriptions in the desktop app. A project split into i18next namespaces is not refused: its descriptions go to .stringlane/metadata.yaml. One unknown key refuses the whole set.

    stringlane add-locale [project]#

    Creates the file for a locale this project does not have yet, in the right place and format.

    FlagMeaning
    --locale <code>The locale code to create, e.g. uk or fr-CA (required)
    --jsonEmit the raw result envelope instead of human text
    --yesSkip the confirmation prompt

    The file it creates is empty apart from the locale marker, on purpose: every key then shows up as missing in the next plan, rather than looking like translations you chose to leave empty. In a project with more than one origin it creates one file per origin, in a single atomic write: either the locale exists everywhere it should or it exists nowhere. It does not make your app offer the language.

    stringlane add-key [project]#

    Adds a new key and its source string to the base locale.

    FlagMeaning
    --key <key>The key to create, e.g. checkout.title (required)
    --value <string>What the key says in your base locale (required)
    --description <text>What the key is for, so translations are made with context
    --jsonEmit the raw result envelope instead of human text
    --yesSkip the confirmation prompt

    Base locale only: every other locale then reports the key as missing in the next plan, and the ordinary plan, validate and apply chain fills it. The confirmation prompt names the key and the string. --key and --value are both required, because an empty value is what an untranslated cell looks like. --description is optional and is stored where the format keeps one. A key name the format cannot carry is refused before it breaks a build.

    In a project that spreads its keys across several files, add-key refuses with exit 2 and writes nothing, because it cannot yet choose which file a new key belongs in. That includes a project split into i18next namespaces as well as a Gradle project with several modules. Add the key in the desktop app, then plan again.

    stringlane pluralize [project]#

    Replaces a key's single string with its plural forms in the base locale. It fixes a count_without_plural finding, and works in i18next projects only.

    FlagMeaning
    --key <key>The key as stringlane check quotes it in its message, namespace included (required)
    --zero <text>, --one <text>, --two <text>, --few <text>, --many <text>, --other <text>The text of each plural form your base language uses: --one and --other for English
    --jsonEmit the raw result envelope instead of human text
    --yesSkip the confirmation prompt
    stringlane pluralize . --key translation:savedSearches.nGenres \
      --one "{{count}} genre" --other "{{count}} genres" --yes
    Added plural forms (one, other) to "translation:savedSearches.nGenres" in "public/locales/en/translation.json".
     
    Still to translate:
      "ru": one, few, many, other
     
    Run stringlane update, or prepare_translation_plan over MCP, to fill them.

    Base file only: every other locale keeps its old string and reports the new forms as missing, and stringlane update, or stringlane plan then stringlane apply, fills them. In a project split into i18next namespaces the forms go into the file that holds the key. A missing or empty form, a form the language does not use, a form that drops a placeholder the current text has or adds one it does not, a form that writes a placeholder differently from the current text ({{count}} where it has {{- count}}), and a key that already has plural forms are each refused, and nothing is written. The one form may leave out the count, as in one genre; the other form may not. The confirmation prompt shows the file and the text of each form. With --json and no --yes it prints what it would change and writes nothing, even on a terminal. Exit 0 when it wrote, previewed or you declined; 2 when it refused or could not run. With no terminal it refuses unless you pass --yes. It needs no AI provider key.

    stringlane accept [project]#

    Confirms translations against the current base text, so check stops reporting them as translation_stale or translation_unconfirmed. It writes .stringlane/source-stamps.json.

    FlagMeaning
    --allConfirm every translation. Cannot be combined with the selectors
    --key <key>A key to confirm, exactly as stringlane check reports it, origin prefix included; repeatable
    --locale <code>A locale to confirm; repeatable. The base locale has nothing to confirm
    --quantity <form>A plural form to confirm, such as one, other or =1; repeatable
    --jsonEmit the raw result envelope instead of human text. Pass --yes with it
    --yesSkip the confirmation prompt
    stringlane accept . --all
    stringlane accept . --key checkout.title --locale de --locale fr

    It needs sourceStamps: true in stringlane.yaml and never edits that file: without the flag it prints the line to add and exits 2. It creates the stamps file when there is none yet, and refuses a stamps file it cannot trust rather than overwriting it. Different selector flags narrow each other, so --key a --locale de is key a in German only, and without --quantity every plural form of the selected keys is included. --all stamps translations nobody checked, and says so: StringLane cannot know whether they were current before today.

    It asks first, counting what it will mark current per locale by its status now. With no terminal it refuses unless you pass --yes. For an .xcstrings unit Xcode marks needs_review, named with --key, it also sets the unit's state in the catalog to translated; --all leaves those units alone and says how many. --yes typed in a shell is not visible to the write guard that makes an agent ask before it edits your files, so review the stamps file in the pull request. There is no MCP tool for accept, on purpose. Exit 0 written or declined, 2 refused or could not run.

    stringlane context [project]#

    Shows, or sets, the product, branding, locale and terminology context stored in stringlane.yaml.

    FlagMeaning
    --from <file>JSON file of context fields to write; omit to show what is set
    --yesSkip the confirmation prompt

    stringlane baseline write [project]#

    Records today's issues to .stringlane/baseline.yaml, so CI can start clean.

    FlagMeaning
    --yesSkip the confirmation prompt

    It records every finding except translation_stale and translation_unconfirmed, and says how many it left out, because one recorded staleness would silence every later one for that cell. Confirm those translations with stringlane accept. check refuses a baseline that holds either code and exits 2.

    Agent and setup commands#

    stringlane mcp [project]#

    Serves the project to an AI agent over MCP on stdio. A server, not a command: it stays running until the agent disconnects. It has no flags of its own.

    The --host values#

    Six commands take --host, and they do not all accept the same names:

    CommandHosts
    setup mcp, setup rules, setup skillsAll eight: claude-code · codex · vscode · gemini-cli · cursor · windsurf · zed · opencode
    setup hooks, hookThe six with a hook StringLane answers: claude-code · codex · vscode · gemini-cli · cursor · windsurf
    setup guardThe two without one: zed · opencode

    A name a command does not take is refused with exit 2, and the message lists the ones it does.

    setup mcp prints a real configuration for the exact file each of the eight hosts reads. What differs is whether --apply then writes it. StringLane will not merge into another program's config file until somebody has run that program and recorded what it actually writes, and today that is claude-code. On the other seven, --apply prints and exits non-zero: the entry is still there to copy, and the exit code is the command telling you it changed nothing.

    Three exceptions, all deliberate. setup rules and setup skills apply everywhere, because they write into your repository: AGENTS.md, and .agents/skills. --remove works everywhere too: refusing to install is cautious, and refusing to uninstall is a trap. See Agent hosts.

    stringlane setup mcp#

    Shows, or with --apply writes, your agent's MCP configuration.

    FlagMeaning
    --host <name>Which agent host; see above
    --scope <scope>Which config to write: user (default) or project
    --applyWrite the change instead of printing it
    --forceReplace an existing StringLane entry

    Without --apply it prints the entry and the exact file, and writes nothing. The entry is given in that host's own shape: these hosts want it under four different container keys, and one under the wrong key is not an error on any of them. The host starts, lists no StringLane tools, and says nothing about why. With --apply on Claude Code it backs the file up first and tells you where the backup went. Every report ends by naming the command that installs that host's write guard, which is a separate step on every host but the two plugin hosts: stringlane setup hooks --host <host>, or stringlane setup guard --host zed|opencode.

    stringlane setup hooks#

    Shows, or with --apply writes, the write guard in your agent's settings.

    FlagMeaning
    --host <name>One of the six hook hosts; see above
    --scope <scope>user (default) or project
    --applyWrite the change instead of printing it
    --removeTake StringLane's entries back out, leaving every other one

    Six hosts speak a hook StringLane answers: Claude Code, Codex, VS Code Copilot, Gemini CLI, Cursor and Windsurf. On Codex, Cursor, Gemini CLI and Windsurf there is no "ask", so the guard refuses the edit and the refusal names the way out: --remove for a hook you pasted, /hooks as well on Codex, and the plugin under Customize for the hook the Cursor plugin ships. VS Code's hook installs but guards nothing yet, and the command prints that warning under the document. Zed and OpenCode have no hook StringLane speaks; setup guard is theirs.

    stringlane setup rules#

    Shows, or with --apply writes, three localization rules into your AGENTS.md, between two HTML-comment markers.

    FlagMeaning
    --host <name>Adds that host's own note about how it reads AGENTS.md. Changes nothing that is written
    --applyWrite the change instead of printing it
    --removeTake the marked block back out

    Everything outside the markers is left byte for byte as it was, and running it twice still leaves one block. It refuses rather than guessing when the markers are ambiguous, and names the line numbers so you can see what it saw. With setup skills, it is one of the two setup commands whose --apply writes on every host.

    stringlane setup skills [project]#

    Shows, or with --apply writes, the agent plugin's skills into your repository's .agents/skills, one stringlane-* folder each. It is for agents with no StringLane plugin.

    FlagMeaning
    --host <name>Adds that host's own note: whether it reads .agents/skills, and whether a plugin brings the skills instead. Changes nothing that is written
    --applyWrite the change instead of printing it
    --removeTake StringLane's entries back out, leaving every other one

    Every file is stamped with the CLI version that wrote it, so running --apply again after an upgrade replaces a stale copy. A folder without the stamp, a copy somebody has edited, or a copy a newer CLI wrote is left alone and named, so a teammate a release behind cannot downgrade the committed files. --remove deletes only unedited folders that carry the stamp. StringLane's host table records every host but Claude Code as reading .agents/skills, but none has been confirmed doing so in a recorded session, so check that the skills show up in yours.

    stringlane setup guard#

    Prints the permission block that makes Zed or OpenCode confirm before an agent edits a locale file, in that host's own pattern language. Zed's patterns are regular expressions, OpenCode's are globs.

    FlagMeaning
    --host <name>zed or opencode

    Print only: both files are JSONC, and a JSON writer would discard your comments on the way back out. It is a snapshot of the files loaded when you ran it, so a locale file added later is not covered until you run it again, and the block says so in its own first line. Exit 0 with the block on stdout; 2 if you passed --apply, or if the folder is not a project or has no locale files.

    stringlane hook#

    Answers an agent host's pre-write hook. Reads stdin; not run by hand. It answers for every host that has a dialect, recorded or not, because a printed hook document pointing at a command that refuses it would be a dead end rather than an escape hatch.

    stringlane upgrade#

    Asks the npm registry what the latest version is and prints the command to install it. It never installs anything itself.

    Most commands also check quietly, at most once a day, printing a one-off notice on stderr when you are behind. hook never does, because it runs on every file an agent touches, and mcp checks when it starts rather than when it ends. Any of STRINGLANE_NO_UPDATE_CHECK=1, DO_NOT_TRACK=1, STRINGLANE_TELEMETRY=0, or a CI runner (CI=true, or one of the runner variables below) turns that background check off; a stored stringlane telemetry off does not. This command ignores all of them, because you typed it.

    Exit 0 whether you are current or behind, and 3 when the registry gave no answer. Not 2, which would say you invoked it wrong.

    stringlane telemetry status | on | off#

    Reports or changes whether usage data is sent. status also shows the random install ID every event carries, and names any environment variable currently overriding the stored setting. Under DO_NOT_TRACK=1, STRINGLANE_TELEMETRY=0 or a CI environment, status creates no ID and shows none on a machine that has none; without one of those, the first command run on a machine creates the ID, status included. Writes only to StringLane's own state directory, never to your project.

    Global flags#

    FlagMeaning
    --helpShow help. Works per command
    --versionPrint the version

    Environment variables#

    VariableEffect
    STRINGLANE_STATE_HOMEAbsolute path for StringLane's own state. A relative value is ignored
    STRINGLANE_TELEMETRY=0Disable usage reporting and the background update check. No install ID is created
    DO_NOT_TRACK=1Disable usage reporting and the background update check. No install ID is created
    CI=trueDisable usage reporting and the background update check. No install ID is created. Set by most CI providers already
    TF_BUILD, BUILDKITE, TEAMCITY_VERSION, CODEBUILD_BUILD_ID, JENKINS_URL, bamboo_buildKeyThe same, read from each runner's own variable, because Azure Pipelines, TeamCity, AWS CodeBuild, Jenkins and Bamboo do not set CI. Any value but empty, 0, false, no or off counts
    STRINGLANE_NO_UPDATE_CHECK=1Disable the background update check only, leaving telemetry as it is
    STRINGLANE_AI_PROVIDERDefault provider for update
    STRINGLANE_AI_MODELDefault model for update
    STRINGLANE_AI_PARALLELISMDefault model calls in flight for update, 1 to 8. --parallelism wins over it
    STRINGLANE_AI_BASE_URLBase URL for a local, OpenAI-compatible runner
    STRINGLANE_<PROVIDER>_API_KEYProvider key, preferred over the conventional name

    No environment variable can turn telemetry on over a stored opt-out.

    Free · In your coding agent

    Run the same checks from your coding agent

    Paste this into Claude Code, Cursor, Codex or five other agents. It sets up the CLI and MCP server for this project, proposing each command and waiting for your yes.

    Read https://stringlane.app/docs/cli/agent-setup.md and set up StringLane for this project.

    Or install it yourself:

    npm install -g @stringlane/cli