Built for Agents
The Claims and Their Mechanisms
| Claim | Mechanism |
|---|---|
| A large result never floods a context window | Pass --output-file <path> (CLI) or outputFile (MCP) and the full JSON is written to disk; the caller receives { reportFile, summary }. Supported by missing, status, empty, check, scan, find-duplicates and remove-orphans, and their paired tools. Set reportOutput: true in your kit config to make it the default for every run, writing to .i18n-reports/<tool>.json |
| Report paths cannot escape the project | resolveOutputFile resolves relative paths against the project dir, not the process cwd, then rejects anything resolving outside it with a ToolError carrying code INVALID_REPORT_PATH |
| Third-party stdout cannot corrupt machine output | guardStdout() runs in the CLI entry point before any other module loads. It replaces process.stdout.write with a function that writes to stderr, and keeps the original bound reference. Command results go out through writeResult, which uses that captured reference. A Nuxt module that logs to stdout during config detection lands in stderr, and the JSON your agent parses stays one JSON document |
| Output is machine-readable without being asked | outputResult treats a non-TTY stdout as JSON mode. An agent spawning the CLI gets JSON without passing --json |
| A failed run still returns parseable JSON | In JSON mode, emitErrorResult writes { "error": { "code", "message" } } to the real stdout and sends the human-readable message to stderr. Zero bytes on stdout is a parse error for whatever is reading; an error object is not. Codes come from ToolError / FileIOError, from Node (ENOENT), or CONFIG_ERROR |
| Reads are targeted, not whole-file dumps | get --layer <l> --locale <c> --keys a.b,c.d returns those keys' values. search --query returns matches. missing returns what is absent. None of them require reading a locale file to find one string |
| Translate results account for every key | Per locale, missing = translated + wouldTranslate + failed + skipped. A run that quietly loses keys cannot balance, so an agent can verify completeness by arithmetic rather than by re-scanning. See translation modes |
| Failures are classified, not narrated | failed entries carry a reason from a closed set: provider-error, omitted-by-model, placeholder-mismatch, plural-mismatch, write-error, truncated. skipped entries carry no-provider, already-translated or protected-locale. An agent branches on the value instead of pattern-matching English |
| Provider failures are triaged before they cost you a run | HTTP status is mapped at the provider boundary: 401/403 becomes auth and aborts the whole run with one error rather than failing key by key; 429 becomes rate-limit and is retried with backoff; a response cut off at the token limit is detected from the finish reason and reported as truncated, which tells the caller to lower batchSize |
| Uncertainty never becomes deletion | remove-orphans defaults to dryRun: true, and remove_orphan_keys performs a dry run first. Keys reachable through a dynamic pattern, keys with ambiguous evidence (uncertainKeys) and keys used only from a non-consuming app (misplacedUsages) are reported in their own buckets and are never removed, including on a confirmed run |
| Exit codes are branchable | 0 succeeded with no gate tripped, 1 the run itself failed, 2 a requested gate tripped. The split lets a caller tell a bad API key from a project that has untranslated keys, without parsing the result |
| Which mode ran is always in the result | Every translate result carries mode: "provider" | "agent" | "dry-run", and discover reports translationMode before any translation runs, so an agent can check the configuration without spending a call on it |
| Project context reaches the agent, not only the model | discover loads your kit config — context, glossary, translationPrompt, localeNotes, layerRules — into the session, so terminology decisions are made with the same information whoever wrote the glossary had |
Agent Mode
With no provider configured — the default in an MCP host — the translate tools do
not fail and do not silently do nothing. They return per-locale fallbackContexts:
the source values plus the glossary, style notes and locale notes. Your agent
translates them inline, using the model it is already running on, and persists the
result through write_translations.
That is the difference between agent mode and provider mode, where the kit calls OpenAI, Anthropic or Google itself and writes the values directly. Both modes report themselves in the result — see translation modes for the full contract.
skipped with reason no-provider. A CI job that
forgets --provider therefore succeeds while translating zero keys — add
--fail-on-missing if that should block.Partial provider configuration — a provider with no model, or no key — logs a warning to stderr and falls back to agent mode. A misconfigured server degrades at startup rather than surprising a caller mid-request.
Writes an Agent Can Reason About
The write operations are deliberately narrow, so an agent picks by intent rather than by guessing at side effects:
| Tool | Does | Does not |
|---|---|---|
write_translations | Writes the exact values you pass | Call any model |
translate_missing | Fills target locales that have no value | Touch existing values |
translate_key | Translates one source key into target locales, refreshing stale values when overwrite=true | Touch other keys |
Values are validated before they are written: placeholder parity is checked per
vue-i18n plural variant ({placeholders}, @:linked.refs, and :params for PHP),
and the number of pipe-separated plural variants must match the source. A value that
fails validation is rejected into failed rather than written, so a model dropping
an interpolation cannot reach a locale file.
Limits
- Context discipline is opt-in per call. Without
--output-file/outputFileorreportOutputin the config, a large result is returned inline. SetreportOutput: trueonce rather than relying on remembering the flag. - The scanner is static and line-based. Keys built at runtime cannot be verified. They are classified as uncertain, which protects them from deletion but also means the orphan count is a floor, not a total.
- Classified failure reasons are a closed set, and reality is not. A cause with
no matching reason arrives as
provider-errorwith the provider's message attached. discoveronly knows what the config says. Layer rules and glossary terms nobody wrote down do not reach the agent.