the-i18n-mcp
The-i18n-mcp is an MCP server that lets AI agents inspect, audit, translate, and refactor i18n translation files across every locale and layer of a (often monorepo/Nuxt) project.
Discover the setup —
discoverreturns project config (locales, default locale, layers, fallback chain, glossary, translation style), per-layer directory listings, key namespaces, the layer graph (including shared layers), and the active translation mode (provider vs agent).Explore keys —
list_namespacesgives a hierarchical key tree with counts;search_translationsdoes case-insensitive substring search over keys and/or values;get_translationsreads values for given keys, a layer, and one, many, or all (*) locales.Read/write translations —
write_translationsupserts/adds/updates key-value pairs (locale maps) with dry-run support;remove_translationsdeletes keys from all locale files in a layer.Audit coverage —
get_translation_statusreports per-locale/per-layer totals of translated, missing, and empty keys plus completion %;get_missing_translationsfinds keys absent in target locales;find_empty_translationsfinds keys whose value is an empty string.Translate with AI —
translate_missingandtranslate_keyfill in missing (or overwrite stale) translations, either by calling a configured LLM provider directly (provider mode) or by returning fallback contexts for the agent to translate inline (agent mode), honoring glossary, prompts, locale notes, and examples.Refactor keys and layers —
rename_translation_keyrenames within a layer;move_translation_keypromotes/demotes a key between layers, carrying all locales and refusing conflicting overwrites.Find code-level problems —
find_orphan_keys(defined but unused),find_undefined_keys(used in code but undefined), andfind_duplicate_keys(collisions across shared and app layers, optionally grouped by value with reuse/promote/consolidate advice), all scope-aware per app/layer with uncertain/dynamic key reporting.Clean up —
remove_orphan_keysfinds and deletes unused keys (dry-run first, never touching misplaced usages).Add new languages —
scaffold_localecreates empty JSON (Nuxt) or PHP (Laravel) locale files mirroring the default locale's key structure.Work at scale — most tools accept
layer,projectDir,outputFile(to avoid flooding context),dryRun, andexcludeDirs/scanDirsoptions for big monorepos.
the-i18n-kit
The i18n toolkit for AI agents and large monorepos. Find missing keys, remove dead ones, and rename across every locale and layer at once — from your agent, your terminal, or your pipeline.
📖 Documentation
Packages
Package | Version | What it is |
The CLI, and the library every other surface is built on | ||
MCP server, for AI coding agents | ||
Publishes the layer graph Nuxt already resolved |
A GitHub Action and a GitLab CI template ship from this repository too.
Related MCP server: i18n-mcp
Quick Start
npm install -g @the-i18n-kit/cli
the-i18n-cli init # write a config from what it detects
the-i18n-cli status # coverage per locale and per layer
the-i18n-cli missing # what is not translated yetFor an AI agent, point your MCP host at the server:
{
"mcpServers": {
"the-i18n-mcp": {
"command": "npx",
"args": ["@the-i18n-kit/mcp@latest"]
}
}
}→ Getting started · MCP setup · CLI reference
Documentation
Everything lives on the documentation site. The reference sections are generated from the source they describe, so they cannot drift from it.
Who it is for, and who it is not | |
The consumer graph, shared libraries, misplaced usages | |
Nuxt, Laravel, Vue, React/Next, generic | |
Where it lives, precedence, every field | |
The Action and the GitLab template | |
Generated reference |
Development
pnpm install
pnpm build
pnpm test
pnpm lint
pnpm typecheckDEBUG=1 enables verbose logging to stderr. See CONTRIBUTING.md.
License
Available Tools
15 toolsdiscoverDiscover i18n SetupARead-only
Describe the project: detected config, locale directories per layer with file counts and top-level namespaces, the layer graph, and the hand-maintained locales. Call this first to understand the project before reading or writing translations. The result also names the active translation mode ("provider" when the server has an env-configured LLM provider, "agent" otherwise). layerGraph answers where a new key belongs: a key used by more than one app belongs in a layer those apps share, and layerGraph.shared names those layers.
| Name | Required | Description | Default |
|---|---|---|---|
| projectDir | No | Absolute path to the project root. Defaults to I18N_PROJECT_DIR, then server cwd. Example: "/home/user/my-app". | |
| includeTranslationGuidance | No | Keep the translation prose in projectConfig — glossary, translationPrompt, localeNotes, examples and context. Default: true at a terminal, false for a tool call, which omits those five fields and sets projectConfig.translationGuidanceOmitted instead. Every structural field (layerRules, protectedLocales, declaredNamespaces, orphanScan, translationMemory) is returned either way. |
Output Schema
| Name | Required | Description |
|---|---|---|
| apps | Yes | Apps and the layers each consumes — the consumer graph orphan scoping reads. |
| layers | Yes | One entry per locale directory, with file counts and key namespaces. |
| locales | Yes | Every locale of the project. |
| rootDir | Yes | Absolute path to the project root. |
| framework | No | Detected framework, e.g. "nuxt", "laravel". Absent when nothing was detected. |
| layerGraph | Yes | Which layers are shared and which apps consume which layer — what answers where a new key belongs. |
| localeDirs | Yes | Every locale directory, one per layer, alias layers included. |
| defaultLocale | Yes | Default locale code — the source locale every translate call falls back to. |
| layerRootDirs | Yes | Absolute root directories of every layer, which is what source scanning walks. |
| projectConfig | No | The declared config from i18n-kit.config.ts or .i18n-mcp.json, as written — or without its translation prose, flagged. Absent when the project has none. |
| fallbackLocale | Yes | The framework's fallback chain. Empty when the framework declares none. |
| translationMode | No | Added by the server: whether it has an LLM provider configured ("provider") or hands back contexts to translate inline ("agent"). Check this before calling a translating tool. |
| localeFileFormat | No | Format of the locale files. Absent means the default, "json". |
| protectedLocales | Yes | Canonical codes of the locales the translate operations leave alone. Empty when none are protected. |
| translationModel | No | Added by the server: the configured model name. Absent in agent mode. |
| translationProvider | No | Added by the server: the configured provider name. Absent in agent mode. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true and openWorldHint=false, so safety is covered. The description adds genuine behavioral context beyond that: the returned translation mode ("provider" vs "agent") and the meaning of layerGraph.shared. It does not, however, disclose anything about cost, latency, or side effects of scanning a project.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the resource description, then usage, then the layerGraph semantics. The final layerGraph sentence is dense and slightly redundant with the earlier layer-graph mention, but every sentence carries information an agent needs.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With an output schema present and annotations covering the read-only profile, the description only needs to orient the agent and route it — which it does by marking this as the entry-point call and explaining the layer semantics. Nothing needed to invoke it correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, and both parameters (projectDir default resolution, includeTranslationGuidance terminal-vs-tool-call behavior) are already documented in the schema. The description adds no parameter-level detail beyond that, so the baseline of 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb (describe/discover) and enumerates exactly what is returned: detected config, locale directories per layer with file counts and namespaces, layer graph, and hand-maintained locales. This clearly distinguishes it from the read/write siblings like get_translations or list_namespaces.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
"Call this first to understand the project before reading or writing translations" gives an explicit ordering directive relative to all sibling tools. It further explains the concrete decision layerGraph supports (where a new key belongs, shared layers for multi-app keys), which is actionable when-to-use guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
find_duplicate_keysFind Duplicate Translation Keys Across LayersARead-only
Find translation keys defined in BOTH a shared layer and an app layer that consumes it. For example the same key in a monorepo root layer and in app-shop. At runtime the app layer's value shadows the shared one, so a collision with divergent values is the dangerous case: the shared value silently never shows. Compares one reference locale and reports each collision with both values and a divergent flag. Fix by deleting one side, never by moving.
| Name | Required | Description | Default |
|---|---|---|---|
| locale | No | Locale code to compare values in (e.g., "de", "en-US"). Defaults to the project default locale. | |
| byValue | No | Also group different keys carrying the same value — e.g. common.actions.save and calendar.views.save both "Speichern". Each group says what to do about it: "reuse" (a shared layer already has it — delete the app copies and repoint the call sites), "promote" (move one to a shared layer) or "consolidate" (duplication inside one layer). Default: false. | |
| outputFile | No | Absolute path to write the full JSON output to. Only a compact summary is returned to the caller, which is what you want for a result too large to read in one piece. Example: ".i18n-reports/duplicate-keys.json" | |
| projectDir | No | Absolute path to the project root. Defaults to I18N_PROJECT_DIR, then server cwd. Example: "/home/user/my-app". | |
| minValueLength | No | Shortest value worth grouping when byValue is set. Default: 4 — below that, values like "OK" repeat across unrelated namespaces legitimately. |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Beyond the readOnlyHint/openWorldHint annotations, it discloses runtime semantics (the app layer's value shadows the shared one), why a divergent collision is dangerous (the shared value silently never shows), what the output contains (both values plus a divergent flag), and remediation policy ('Fix by deleting one side, never by moving'). That is substantive behavioral context the annotations do not carry.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Five tight sentences, front-loaded with what is found, then the concrete example, then why it is dangerous, what is reported, and how to fix. The example and the shadowing explanation each earn their place; nothing is padding.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With an output schema present, return format need not be explained, and the annotations already cover the safety profile. The description supplies the runtime rationale and the remediation rule an agent needs to interpret and act on results, so nothing material is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100% and the schema already documents locale, byValue (including reuse/promote/consolidate), outputFile, projectDir and minValueLength in detail. The description only adds 'compares one reference locale', which restates the locale field rather than extending it, so the baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource (find translation keys defined in BOTH a shared layer and an app layer that consumes it) and gives a concrete example (monorepo root layer vs app-shop). This clearly distinguishes it from siblings like find_undefined_keys and find_orphan_keys, which target different defect classes.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description frames the situation precisely — keys colliding across layers, with divergent values being the dangerous case worth acting on — so an agent knows when the result matters. It does not, however, name or contrast any sibling tool (e.g. find_undefined_keys / find_orphan_keys), so there is clear context but no explicit exclusions or alternatives.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
find_orphan_keysFind Orphan Translation KeysADestructiveIdempotent
Report translation keys that no source code references. Nothing is deleted unless remove is set. Scans a specific layer or all layers, and also detects dynamic key patterns and uncertain matches. Scope-aware: each layer is checked only against the code of the apps that consume it (summary.scanScope shows each layer's effective scope), and keys referenced only from non-consuming apps are reported separately as misplacedUsages rather than as orphans. With remove the orphan keys are deleted from every locale file of their layer — uncertain keys and misplaced usages are never deleted, in any mode.
| Name | Required | Description | Default |
|---|---|---|---|
| layer | No | Layer name to scope this to (e.g., "root", "app-admin"). If omitted, every layer is included. Call discover to list the layers. | |
| locale | No | Locale code to read the translation keys from (e.g., "en", "en-US"). Defaults to the project default locale. | |
| remove | No | Permanently delete the orphan keys from every locale file of their layer. Default: false, which only reports them — run without it first and read the findings. Uncertain keys and misplaced usages are never deleted. | |
| scanDirs | No | Absolute paths of the directories to scan for source usage. Overrides scope-aware scanning: every layer is then checked against these directories alone. Example: ["/home/user/my-app/apps/admin"]. | |
| outputFile | No | Absolute path to write the full JSON output to. Only a compact summary is returned to the caller, which is what you want for a result too large to read in one piece. Example: ".i18n-reports/orphan-keys.json" | |
| projectDir | No | Absolute path to the project root. Defaults to I18N_PROJECT_DIR, then server cwd. Example: "/home/user/my-app". | |
| excludeDirs | No | Directory names to skip when scanning source files. Example: ["storybook", "__tests__", "node_modules"]. |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Goes well beyond the annotations by spelling out blast radius and exceptions: with remove, orphan keys are deleted 'from every locale file of their layer,' while uncertain keys and misplaced usages are 'never deleted, in any mode.' It also explains the scope-aware checking model and the misplacedUsages distinction, which the destructiveHint annotation alone cannot convey.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Four dense sentences, front-loaded with the purpose and the non-destructive default before the destructive mode. Every sentence carries information, though the scope-aware and misplacedUsages explanation is dense enough that it could be trimmed slightly.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so return values need not be explained, and the description still surfaces summary.scanScope and misplacedUsages as key result concepts. It fully covers the destructive semantics for a 7-parameter tool; only minor items like error behavior are absent.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so all seven parameters are already documented, making 3 the baseline. The description reinforces behavior around layer scoping ('a specific layer or all layers') and scanDirs overriding scope-aware scanning, but adds little syntax or format detail beyond the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource with a precise filter: 'Report translation keys that no source code references.' The 'orphan' framing implicitly and correctly distinguishes it from the sibling find_undefined_keys (the inverse problem), so an agent can pick between them without opening a schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives clear when-to-use guidance through the remove flag: it explains that the tool only reports by default and that deletion is opt-in, and states the condition under which removal is safe to consider. It does not name an explicit alternative (e.g. remove_translations) or a hard when-not-to-use case, so it falls short of a 5.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
find_undefined_keysFind Used-But-Undefined Translation KeysAIdempotent
Find keys referenced in source code but defined in NO locale layer the using app consumes — the direction that ships raw keys to production. The inverse of find_orphan_keys. Scope-aware: each scan unit (app) is checked against the layers it consumes (summary.searchedLayersByApp), so a key defined only in a layer the using app does not consume is still undefined for that app. Known limitation: extraction is line-based and static — dynamically built keys (template literals, concatenation) cannot be verified and are reported as uncertainKeys, never as hard findings. With write, the hard findings are also added to a locale file as empty translations, which is the first half of the fix; uncertain findings are never written.
| Name | Required | Description | Default |
|---|---|---|---|
| layer | No | Layer to write the undefined keys into (e.g., "root", "app-admin"). Only read together with write, and only needed when the using code resolves against more than one layer. Call discover to list the layers. | |
| write | No | Add every undefined key to a locale file, with an empty string as its value, in the project default locale only. Existing values are never touched, and uncertain findings are never written. The layer is the one the using code resolves against; when that is more than one layer, the run refuses and asks for a layer name. Default: false, which only reports. | |
| locale | No | Reference locale to resolve key definitions in (e.g., "en", "en-US"). Defaults to the project default locale. | |
| scanDirs | No | Absolute paths of the directories to scan for source usage. Overrides scope-aware scanning: every layer is then checked against these directories alone. Example: ["/home/user/my-app/apps/admin"]. | |
| outputFile | No | Absolute path to write the full JSON output to. Only a compact summary is returned to the caller, which is what you want for a result too large to read in one piece. Example: ".i18n-reports/undefined-keys.json" | |
| projectDir | No | Absolute path to the project root. Defaults to I18N_PROJECT_DIR, then server cwd. Example: "/home/user/my-app". | |
| excludeDirs | No | Directory names to skip when scanning source files. Example: ["storybook", "__tests__", "node_modules"]. |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Goes well beyond the annotations (readOnly=false, idempotent=true, destructive=false): it discloses that write mode adds empty translations and is only the first half of the fix, that existing values are never touched, that uncertain findings are never written, and that the run refuses when a key resolves against multiple layers. It also discloses the static, line-based extraction limitation and the uncertainKeys reporting behavior.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loads the core definition and scoping behavior before the limitation and write behavior. Dense but nearly every clause carries distinct information; the only mild redundancy is restating the scope/write refusal already in the schema.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so return values need no explanation, and the description still covers the essential behavioral contract — scope-aware matching, the static-analysis limitation, uncertain vs hard findings, and write-side effects. Nothing an agent needs to call it correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% and the inline parameter descriptions are already highly detailed (layer/write interaction, scanDirs override, defaults for locale/projectDir). The description reinforces scope-aware resolution but adds little beyond what the schema text already states, so the baseline of 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource — 'Find keys referenced in source code but defined in NO locale layer' — and immediately disambiguates from the sibling by calling itself 'the inverse of find_orphan_keys.' An agent can distinguish this from find_orphan_keys and get_missing_translations without opening any schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Clearly frames when to use it: the direction that ships raw keys to production, and explicitly names find_orphan_keys as the inverse, plus discover for listing layers. It stops short of explicit exclusions ('do not use when...'), but the direction and the named alternative give strong routing guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_missing_translationsGet Missing TranslationsARead-only
Find translation keys that exist in the reference locale but are missing in other locales. Scans a specific layer or all layers. summary.totalMissingKeys counts every missing key in the project; limit caps only how many of them are listed under missing, over the nested map flattened to one entry per locale, layer and key.
| Name | Required | Description | Default |
|---|---|---|---|
| layer | No | Layer name to scope this to (e.g., "root", "app-admin"). If omitted, every layer is included. Call discover to list the layers. | |
| limit | No | Maximum number of missing keys to return. Default: 100 for a tool call, unlimited at a terminal. When the cap applies the result carries truncated: true and nextOffset — call again with offset set to that value for the next page, or narrow the request instead. | |
| offset | No | Number of missing keys to skip before returning any. Default: 0. Pass the nextOffset of a truncated result to continue where it stopped. | |
| outputFile | No | Absolute path to write the full JSON output to. Only a compact summary is returned to the caller, which is what you want for a result too large to read in one piece. Example: ".i18n-reports/missing-translations.json" | |
| projectDir | No | Absolute path to the project root. Defaults to I18N_PROJECT_DIR, then server cwd. Example: "/home/user/my-app". | |
| targetLocales | No | Locale codes to check for missing keys (e.g., ["de", "fr", "es"]). Defaults to all locales except the reference. | |
| referenceLocale | No | Locale code used as the source of truth (e.g., "en", "en-US"). Defaults to the project default locale. |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already establish this as a safe, local read (readOnlyHint=true, openWorldHint=false), so the bar is lower, but the description adds genuinely non-obvious behavior: summary.totalMissingKeys counts all missing keys while limit only caps what is listed, and outputFile switches to a compact summary. That is real behavioral context beyond the annotations and schema.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three sentences, front-loaded with purpose and followed by the scoping and counting semantics. The final sentence is dense but each clause carries distinct, useful information; nothing is padding.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With an output schema present and full schema coverage, return values need no further explanation, and the description covers purpose, scoping, counting semantics, and the truncation contract. Only the routing against sibling discovery tools is left implicit.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3, but the description adds meaning the schema does not: it clarifies that limit bounds the listed entries rather than the computed count, and that the nested map is flattened to one entry per locale/layer/key. This disambiguates the most confusing parameter.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource with an exact defining condition: keys present in the reference locale but absent in other locales. That precision separates it from siblings like find_undefined_keys (used-in-code but undefined) and find_orphan_keys (unused keys) without the agent needing to open any schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It conveys scope ("Scans a specific layer or all layers") and the schema points to discover for layer names, but there is no explicit when-to-use/when-not guidance or routing against alternatives such as get_translation_status or find_undefined_keys. Usage is implied rather than stated.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_translationsGet TranslationsARead-only
Get translation values by key path or by key prefix, from one layer or from every layer that defines them. Use "*" as the locale to read from all locales. Pass keys for an explicit list, or keyPrefix to read a whole namespace at once — one of the two is required, and a call with neither fails with EARG. With layer, the result is locale → key → value, exactly as it always was — unless limit cut it short, in which case it answers in the { byLayer } shape below, which has room for truncated and nextOffset. Without layer, every non-alias layer is read and the result is { byLayer, layersSearched }: byLayer holds that same shape per layer and names only the layers defining at least one of the keys, which is what answers where a key lives. limit counts one key per layer read, so a prefix read of seven layers is capped across all of them rather than per layer.
| Name | Required | Description | Default |
|---|---|---|---|
| keys | No | Dot-separated key paths to read. Example: ["common.actions.save", "auth.login.title"]. Either this or keyPrefix is required. | |
| layer | No | Layer name from discover (e.g., "root", "app-admin"). Omit to read every layer and get the { byLayer } shape back. | |
| limit | No | Maximum number of keys to return. Default: 100 for a tool call, unlimited at a terminal. When the cap applies the result carries truncated: true and nextOffset — call again with offset set to that value for the next page, or narrow the request instead. | |
| locale | Yes | Locale code, locale file name, or "*" to read all locales. Examples: "en", "en-US", "en-US.json", "*". | |
| offset | No | Number of keys to skip before returning any. Default: 0. Pass the nextOffset of a truncated result to continue where it stopped. | |
| compact | No | When true and locale is "*", returns a summary grouped by key instead of per-locale detail. Default: false. | |
| keyPrefix | No | Namespace to read every leaf key under, dots included: "auth" reads auth.login.title and everything else below auth. Either this or keys is required; passing both reads the union. | |
| projectDir | No | Absolute path to the project root. Defaults to I18N_PROJECT_DIR, then server cwd. Example: "/home/user/my-app". |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint and openWorldHint, but the description adds substantial context beyond them: the EARG failure when neither selector is given, the two distinct result shapes depending on layer, truncation/nextOffset behavior, and the per-layer counting semantics of limit. This is exactly the behavioral disclosure the annotations cannot carry.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Purpose and required-selector rule are front-loaded, and every sentence carries real information about scope, shape, or pagination. It is a dense single block with several shape variants packed together, which is slightly heavy but not padded.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With annotations covering the safety profile, full schema coverage, and an output schema present, the description is more than complete: it still explains the layer-dependent return shapes and pagination flow. An agent has everything needed to call this correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3, but the description adds genuine meaning: limit counts one key per layer read (so a prefix read across seven layers is capped collectively, not per layer), keys plus keyPrefix reads the union, and layer omission changes both scope and return shape. These go beyond the schema text.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a specific verb and resource ("Get translation values") and immediately scopes it by key path or prefix, one layer or all layers. This is enough to separate it from siblings like search_translations or get_missing_translations without opening any schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It clarifies the internal choice between keys and keyPrefix and the requirement that one be present, which is useful invocation guidance. However, it never names an alternative sibling tool or states when to prefer get_translations over search_translations or get_missing_translations, so sibling routing is left to inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_translation_statusGet Translation StatusARead-only
Translation coverage in one call: per-locale and per-layer counts of total, translated, missing and empty keys, plus an overall completion percentage. Use this instead of calling get_missing_translations per layer and counting keys yourself. Empty-string values count as untranslated; set listEmpty to get the keys behind that count — they exist in the locale file, so they are never reported as missing, and they render as nothing in the UI. Locales listed in protectedLocales are reported but excluded from the overall figure, since they are maintained by hand. Where a translation memory lockfile exists, each locale and layer also carries stale — keys whose target was written from source text that has changed since — and the summary carries staleCount; translate with overwriteStale refreshes them.
| Name | Required | Description | Default |
|---|---|---|---|
| layer | No | Layer name to scope this to (e.g., "root", "app-admin"). If omitted, every layer is included. Call discover to list the layers. | |
| listEmpty | No | Also list the keys behind summary.emptyKeys under "empty" (locale → layer → keys), and keys that are empty in the reference locale itself under "emptyInReference" — useful after a scaffold or an interrupted translation run. Default: false, which returns counts only. | |
| outputFile | No | Absolute path to write the full JSON output to. Only a compact summary is returned to the caller, which is what you want for a result too large to read in one piece. Example: ".i18n-reports/translation-status.json" | |
| projectDir | No | Absolute path to the project root. Defaults to I18N_PROJECT_DIR, then server cwd. Example: "/home/user/my-app". | |
| referenceLocale | No | Locale code used as the source of truth (e.g., "en", "en-US"). Defaults to the project default locale. |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations only declare readOnlyHint/openWorldHint, so the description carries the real behavioral detail: empty-string values count as untranslated and never appear as missing, protectedLocales are reported but excluded from the overall figure, and stale keys plus staleCount behavior are disclosed. This is rich context beyond safety annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the core outcome, then layers in edge cases in dense, purposeful sentences. It is long but nearly every clause adds decision-relevant information; minor density rather than waste.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so return values need no explanation. The description covers the edge cases an agent must know (empty strings, protected locales, stale keys) and the routing to siblings, leaving nothing material unaddressed.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% so the baseline is 3, but the description adds real meaning beyond the schema — it explains what listEmpty surfaces and when it is useful (after a scaffold or interrupted run) and clarifies the empty-vs-missing distinction that governs the counts.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource: per-locale and per-layer counts of total/translated/missing/empty keys plus an overall completion percentage. It explicitly distinguishes itself from the sibling get_missing_translations by offering the aggregate in one call.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Names the alternative it replaces ('instead of calling get_missing_translations per layer and counting keys yourself') and routes the agent to the right follow-up ('translate with overwriteStale refreshes them'). Conditions for using listEmpty and outputFile are given.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_namespacesList NamespacesARead-only
List the translation key tree grouped by namespace prefix, with a count per namespace node. Use this to explore the available keys without guessing path prefixes. limit counts top-level namespace nodes across the scanned layers, each of which brings its whole subtree; totalNamespaces counts them all, whatever the limit let through.
| Name | Required | Description | Default |
|---|---|---|---|
| layer | No | Layer name to filter by (e.g., "root", "app-admin"). If omitted or "*", scans all layers. Call discover to list the layers. | |
| limit | No | Maximum number of top-level namespaces to return. Default: 100 for a tool call, unlimited at a terminal. When the cap applies the result carries truncated: true and nextOffset — call again with offset set to that value for the next page, or narrow the request instead. | |
| locale | No | Locale code to read from (e.g., "en", "en-US"). Defaults to the project default locale. Keys are the same across locales, so one is enough. | |
| offset | No | Number of top-level namespaces to skip before returning any. Default: 0. Pass the nextOffset of a truncated result to continue where it stopped. | |
| projectDir | No | Absolute path to the project root. Defaults to I18N_PROJECT_DIR, then server cwd. Example: "/home/user/my-app". |
Output Schema
| Name | Required | Description |
|---|---|---|
| layers | Yes | One entry per scanned layer. Alias layers are skipped. |
| message | No | The step to take next — how to continue a capped read. Present when there is one. |
| truncated | Yes | True when limit cut the result short. The totals still count everything. |
| nextOffset | No | The offset to pass to continue where this result stopped. Present only when truncated. |
| totalNamespaces | Yes | Top-level namespaces across every scanned layer, before limit. A namespace brings its whole subtree, so this is what limit counts. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true and openWorldHint=false, so the safety profile is covered. The description adds real behavioral context the annotations don't: the limit/totalNamespaces relationship and that each top-level node drags in its entire subtree. It does not mention authentication or performance on large trees, but the added pagination semantics are substantive.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three sentences, front-loaded with purpose before the usage hint and the limit semantics. The final clause ('whatever the limit let through') is slightly convoluted, but overall there is little waste.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With an output schema present, return-value explanation is unnecessary, and the description still covers purpose, exploration use case, and pagination semantics. For a five-parameter, zero-required, read-only listing tool, an agent has everything it needs to call it correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the baseline is 3, but the description goes beyond the schema by disambiguating what 'limit' actually counts — top-level namespace nodes, each carrying its whole subtree — versus totalNamespaces. That is genuine added meaning for the parameter an agent is most likely to misread.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('List the translation key tree grouped by namespace prefix, with a count per namespace node'), which is far more precise than the bare title 'List Namespaces'. It is clearly distinguishable from read-oriented siblings like get_translations or search_translations, though no sibling is named explicitly.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
'Use this to explore the available keys without guessing path prefixes' gives a concrete selection context. It stops short of naming alternatives (e.g., discover for layers, search_translations for lookups) or stating when this tool should not be used, so it falls just short of a 5.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
move_translation_keyMove or Rename a Translation KeyADestructive
Move a translation key to another layer, to another key path, or both, carrying every locale that defines it. Pass toLayer to promote an app-layer key to a shared layer once a second app needs it (or to demote a shared key that turned out to be app-specific); call discover first, layerGraph.shared names the layers more than one app consumes. Pass newKey alone to rename the key in place across every locale file of its layer. Writes nothing at all if the destination already holds the key with a different value in any locale; if it holds the same value, that locale is deduplicated instead. Use dryRun to preview the plan.
| Name | Required | Description | Default |
|---|---|---|---|
| key | Yes | Dot-separated key path to move. Example: "calendar.views.save". | |
| layer | Yes | Layer the key lives in today, from discover. Example: "app-admin". | |
| dryRun | No | Return the plan without writing any files. Default: false. | |
| newKey | No | Key path to give it. Example: "common.actions.save". Omit to keep the current path, which then requires toLayer. | |
| toLayer | No | Layer to move it to, from discover. Example: "root". Omit (or repeat layer) to rename the key within its current layer, which then requires newKey. | |
| projectDir | No | Absolute path to the project root. Defaults to I18N_PROJECT_DIR, then server cwd. Example: "/home/user/my-app". |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare destructiveHint=true, idempotentHint=false, and readOnlyHint=false, and the description goes well beyond them: it states that every defining locale is carried, that nothing is written when the destination holds a conflicting value in any locale, and that an identical value causes deduplication rather than a write. That is precisely the failure/side-effect detail an agent needs before a destructive mutation.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Four sentences, front-loaded with the core action and scope, then the two modes, then the conflict/dedup rule, then dryRun. No filler, and each sentence maps to a distinct decision the caller must make.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists so return values need no explanation; what remains — preconditions (discover), mode selection, conflict behavior, and the preview escape hatch — is all covered. Nothing an agent needs in order to invoke this destructive tool correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so baseline is 3, but the description adds genuine meaning: toLayer is framed by its promotion/demotion intent, newKey is framed as in-place rename across every locale file of the layer, and the two are described as mutually requiring each other. It stops short of explaining projectDir resolution beyond what the schema already says.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb (move) plus two modes (layer move, rename) and the resource (translation key), and explicitly distinguishes itself from siblings like discover and remove_translations. An agent knows exactly what this does versus the other key-manipulation tools without opening any schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives conditional routing: pass toLayer to promote/demote, pass newKey alone to rename in place, and calls out the prerequisite 'call discover first, layerGraph.shared names the layers more than one app consumes.' It also names dryRun as the preview path, so both when-to-use and how-to-decide are explicit.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
remove_translationsRemove TranslationsADestructiveIdempotent
Remove one or more translation keys from ALL locale files in the given layer. Use dryRun to preview the changes before applying them.
| Name | Required | Description | Default |
|---|---|---|---|
| keys | Yes | Dot-separated key paths to remove from every locale file in the layer. Example: ["common.actions.delete", "auth.errors.expired"]. | |
| layer | Yes | Layer name from discover (e.g., "root", "app-admin"). The keys are removed from ALL locale files in this layer. | |
| dryRun | No | Return a preview of what would be removed without writing any files. Default: false. | |
| projectDir | No | Absolute path to the project root. Defaults to I18N_PROJECT_DIR, then server cwd. Example: "/home/user/my-app". |
Output Schema
| Name | Required | Description |
|---|---|---|
| dryRun | No | True when nothing was removed because a preview was asked for. Absent otherwise. |
| message | No | The step to take next, as the surface the call ran on phrases it. Present only when there is no summary to carry it. |
| removed | No | Dot-path keys removed from at least one locale file. Absent on a dry run. |
| summary | No | Counts of what the run did. Absent on a dry run. |
| notFound | No | Requested keys no locale file of the layer defined. Absent when every key existed. |
| wouldRemove | No | What a dry run would remove. Present only with dryRun. |
| filesWritten | No | Number of locale files changed on disk. Absent on a dry run. |
| removedPerLocale | No | One "locale:key" entry per file-level removal. Absent on a dry run. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare destructiveHint=true and idempotentHint=true, so the safety profile is handled. The description adds real value beyond that by disclosing the blast radius ('ALL locale files in the layer') and the dryRun preview path, which is exactly the context an agent needs before a destructive multi-file write.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two sentences, zero filler, and the destructive scope is front-loaded before the mitigation advice. Nothing is padded or restated.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so return values need no explanation, and annotations cover the mutation semantics. The description covers scope and previewing, but omits whether removal is recoverable or what happens to keys absent from some locale files—minor gaps for a destructive write tool.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so all four parameters (keys, layer, dryRun, projectDir) are already fully documented in the schema. The description repeats the dryRun purpose but adds no format or syntax detail beyond what the schema provides, making the baseline 3 appropriate.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource (remove translation keys) plus the exact scope (ALL locale files in the given layer). That scope statement cleanly separates it from siblings like move_translation_key and write_translations without the agent needing to open schemas.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It gives one actionable usage instruction—use dryRun to preview before applying—which is genuinely helpful for a destructive tool. But it never says when to prefer this over alternatives such as move_translation_key, nor does it state prerequisites or when removal is inappropriate.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
scaffold_localeScaffold LocaleAIdempotent
Create empty locale files for new languages, copying the key structure of the default locale with every value set to an empty string. Supports both JSON (Nuxt) and PHP (Laravel) formats. Does NOT modify the framework configuration — add the locale there first, then call this.
| Name | Required | Description | Default |
|---|---|---|---|
| layer | No | Layer to scaffold in (e.g., "root", "app-admin"). If omitted, scaffolds across every layer. | |
| dryRun | No | Report the files that would be created without writing them. Default: false. | |
| locales | No | Locale codes to scaffold empty files for (e.g., ["sv", "ja", "pt-BR"]). If omitted, auto-detects the locales the config declares but has no files for. | |
| projectDir | No | Absolute path to the project root. Defaults to I18N_PROJECT_DIR, then server cwd. Example: "/home/user/my-app". |
Output Schema
| Name | Required | Description |
|---|---|---|
| dryRun | Yes | True when nothing was written because a preview was asked for. |
| created | Yes | Files that were created, or would be under dryRun. |
| skipped | Yes | Files that already existed and were left alone. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare non-read-only, idempotent, non-destructive behavior; the description adds meaningful detail beyond them — files are created with empty-string values, key structure mirrors the default locale, and config is deliberately left untouched. It doesn't say what happens if a locale file already exists, which is the one residual gap given idempotentHint=true.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three sentences, front-loaded with the action and the format scope, followed by the critical exclusion. Every clause carries information an agent needs; nothing is restated from the title or schema.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With annotations covering safety and idempotency and an output schema handling return values, the description supplies the remaining essentials: what is created, in which formats, with what content, and the config prerequisite. Nothing needed to invoke it correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so all four parameters (layer, dryRun, locales, projectDir) are already fully documented in the schema, including defaults and fallbacks. The description adds no syntax or format detail beyond that, so the baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb+resource ("Create empty locale files") and immediately scopes it: copies the default locale's key structure with empty values, supports JSON and PHP formats. It also draws an explicit boundary ("Does NOT modify the framework configuration"), which no sibling tool claims, so an agent can distinguish it from write_translations or translate_missing.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives a clear ordering prerequisite — add the locale to the framework config first, then call this — which is exactly the context needed before invocation. It doesn't name or contrast against any sibling tool, so it falls short of the 5 benchmark for explicit alternatives.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
search_translationsSearch TranslationsARead-only
Search translation files by key path or value, one compact row per matching key rather than one per key and locale. Useful for finding an existing translation before adding a duplicate of it. A key that seven layers and thirty locales define comes back as a single row: layers names every layer that defines it, which is what tells reuse from duplication, and value is the one the reference locale holds. Pass includeLocales for the detail rows — one per key and locale — when what each locale holds is the question. Matching is a case-insensitive substring unless matchMode says otherwise.
| Name | Required | Description | Default |
|---|---|---|---|
| layer | No | Layer name to search in (e.g., "root", "app-admin"), or "*" for all layers. If omitted, searches every layer. | |
| limit | No | Maximum number of matching rows to return. Default: 100 for a tool call, unlimited at a terminal. When the cap applies the result carries truncated: true and nextOffset — call again with offset set to that value for the next page, or narrow the request instead. | |
| query | Yes | Text to search for, matched against keys and/or values. Compared as a case-insensitive substring unless matchMode says otherwise. Example: "save" matches the key "common.actions.save" and the value "Save changes". | |
| locale | No | Locale code to search in (e.g., "en", "de"). If omitted, searches every locale. | |
| offset | No | Number of matching rows to skip before returning any. Default: 0. Pass the nextOffset of a truncated result to continue where it stopped. | |
| searchIn | No | Whether to search translation keys, values, or both. Default: "both". | |
| matchMode | No | How query is compared. "contains" is a case-insensitive substring, over every locale searched. "exact" is the whole string, and "fuzzy" also accepts near-misses in wording, both ignoring case, accents, punctuation and whitespace and both comparing against one locale only — locale when given, otherwise the project default. Default: "contains". | |
| outputFile | No | Absolute path to write the full JSON output to. Only a compact summary is returned to the caller, which is what you want for a result too large to read in one piece. Example: ".i18n-reports/search-results.json" | |
| projectDir | No | Absolute path to the project root. Defaults to I18N_PROJECT_DIR, then server cwd. Example: "/home/user/my-app". | |
| includeLocales | No | Return one row per key and locale — layer, locale, key, value — instead of one row per key. Several times the output for the same findings, so ask for it when the per-locale values are what you are after. Default: false. |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true and openWorldHint=false, so the safety profile is covered. The description adds real behavioral context beyond that: how a key defined across many layers and locales collapses into a single row, what the layers field signals about reuse versus duplication, and that value reflects the reference locale. It does not discuss cost, rate limits, or permission requirements.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is front-loaded — purpose and the compact-row contract come first, the includeLocales escape hatch and matching default follow. Sentences are long and clause-heavy, but each carries information; nothing is pure filler. Slightly dense for the number of points being made.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a 10-parameter read tool with full schema coverage and an output schema, the description covers everything an agent needs: what it returns, the deduplication semantics, when to expand to per-locale rows, and the default matching mode. Return-values detail is unnecessary given the output schema, and nothing material is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the baseline is 3, but the description genuinely adds meaning the schema does not: it explains the output-size tradeoff of includeLocales ("Several times the output for the same findings" — actually stated in schema too) and clarifies matching semantics (case-insensitive substring unless matchMode says otherwise) at the call level. The includeLocales guidance about when the per-locale detail is worth the extra rows is the strongest added value.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description names a specific verb (search) and resource (translation files by key path or value) and immediately states the distinctive output shape: one compact row per matching key rather than one per key and locale. That row-shape framing is exactly what separates it from get_translations and find_duplicate_keys in the sibling list, so an agent can route correctly without opening any schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It gives a concrete use case ("finding an existing translation before adding a duplicate of it") and an explicit condition for the alternate mode ("Pass includeLocales ... when what each locale holds is the question"), which is clear when-to-use guidance. It stops short of naming sibling tools such as get_translations or find_duplicate_keys as the alternative, so the routing is implied rather than explicit.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
translate_keyTranslate KeyADestructiveIdempotent
Add or update one source translation key and translate it into the target locales. Unlike translate_missing, this can overwrite an existing but stale target translation. Same two modes as translate_missing: provider mode (the server env-configured) translates directly; agent mode returns a fallbackContext — translate it inline and persist via write_translations.
| Name | Required | Description | Default |
|---|---|---|---|
| key | Yes | Dot-separated key path to translate. Example: "bookingCreator.options.removeSubResource". | |
| layer | Yes | Layer holding the key, from discover (e.g., "root", "app-admin"). | |
| dryRun | No | Report the source and target locales without writing files or calling the translation backend. Default: false. | |
| overwrite | No | Overwrite existing target translations. When false, only missing targets are filled. Default: true. | |
| projectDir | No | Absolute path to the project root. Defaults to I18N_PROJECT_DIR, then server cwd. Example: "/home/user/my-app". | |
| sourceValue | No | Source value to write before translating. If omitted, the existing source value is read. | |
| sourceLocale | Yes | Source locale ref. May be a code ("en-us"), a language ("en-US") or a file ("en-US.json"). | |
| targetLocales | No | Locales to translate into. Pass "all", or omit, for every locale except the source. | |
| includePreview | No | Include the translated values in the result. Default: false, which keeps the response compact. |
Output Schema
| Name | Required | Description |
|---|---|---|
| key | Yes | The key that was translated. |
| mode | Yes | How the run was executed: "provider" called the configured LLM, "agent" returned contexts to translate by hand, "dry-run" wrote nothing. |
| model | No | Model that produced the translations. Absent outside provider mode. |
| dryRun | Yes | True when nothing was written because a preview was asked for. |
| failed | Yes | Locales the run attempted and lost. |
| message | No | What to do next, when the run needs something from you — in agent mode, that the fallbackContext has to be translated and written back. |
| preview | No | The translated values. Present only when includePreview was passed. |
| skipped | Yes | Locales deliberately not translated. |
| translated | Yes | Locales whose value was written. |
| filesWritten | Yes | Number of locale files changed on disk. 0 on a dry run. |
| sourceLocale | Yes | The locale the translation was made from. |
| updatedSource | Yes | True when a sourceValue was written to the source locale before translating. |
| wouldTranslate | No | Locales a dry run would translate. Present only on a dry run. |
| fallbackContext | No | Context to translate inline and persist with write_translations. Present only in agent mode. |
| placeholderValidation | Yes | Placeholder comparison of the new values against the source value. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare destructiveHint=true, idempotentHint=true, and openWorldHint=true, so the safety profile is covered. The description still adds non-obvious behavior: the tool may overwrite existing stale translations, and its execution path diverges by mode — provider mode writes directly while agent mode returns a fallbackContext that the caller must translate and persist via write_translations. That dual-mode contract is the kind of context annotations cannot express.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the core action, then the sibling differentiation, then the mode mechanics. Three dense sentences with no filler, though the mode sentence packs several ideas (two modes, fallbackContext, persistence tool) tightly enough that it reads more like a spec fragment than prose.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so return values need no explanation, and the description covers the genuinely tricky part — the mode-dependent control flow and required follow-up call. Minor gaps remain, such as the default breadth of targetLocales and any permission prerequisites, but nothing essential for correct invocation is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the schema already documents all nine parameters, including overwrite, dryRun, and targetLocales defaults. The description restates the overwrite semantics and target locales at a high level but adds no syntax, format, or edge-case detail beyond the schema. Baseline 3 applies when the schema does the heavy lifting.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Add or update one source translation key') plus the downstream effect ('translate it into the target locales'). It also names the sibling it differs from (translate_missing) and the exact axis of difference (overwriting stale targets), so an agent can separate the two without opening either schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly routes the agent: 'Unlike translate_missing, this can overwrite an existing but stale target translation.' It further distinguishes the two operating modes (provider vs agent) and, for agent mode, names the follow-up tool (write_translations) needed to persist the result. Nothing about when to pick this tool is left to inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
translate_missingTranslate MissingAIdempotent
Find the keys missing in the target locales and translate them. Without a translation backend nothing is written: the result carries per-locale fallback contexts to translate by hand instead. Two modes: in provider mode (the server env-configured with I18N_PROVIDER, I18N_MODEL and an API key) it calls the LLM provider directly and writes the results; in agent mode it returns those fallbackContexts — translate them inline and persist via write_translations. Check the discover output for the active mode. Uses the project config (glossary, translation prompt, locale notes, examples) where there is one. Translates all locales concurrently, so pass every target locale at once.
| Name | Required | Description | Default |
|---|---|---|---|
| keys | No | Dot-path keys to translate (e.g., ["auth.login.title", "common.save"]). If omitted, translates every missing key in the layer. | |
| layer | No | Layer to translate (e.g., "root", "app-admin"). Omit to translate every locale-backed layer in one call — the recommended default for layered projects, which returns a result per layer plus an aggregated summary. | |
| dryRun | No | Return which keys would be translated without calling the provider or writing files. Default: false. | |
| compact | No | Return a compact summary (totalTranslated, totalFailed, byLocale) instead of full per-locale results. Default: false. | |
| batchSize | No | Maximum number of keys per provider request. Default: 50. A lower value reduces per-batch risk and increases round trips. | |
| projectDir | No | Absolute path to the project root. Defaults to I18N_PROJECT_DIR, then server cwd. Example: "/home/user/my-app". | |
| targetLocales | No | Locale codes to translate into (e.g., ["de", "fr", "sv"]). Defaults to all locales except the reference. | |
| overwriteStale | No | Also re-translate keys whose target value was written from source text that has changed since. Needs the translation memory, which is on unless translationMemory is false in the project config — without it nothing is known to be stale and this changes nothing. Default: false, which reports those keys under "stale" and leaves their values alone. | |
| referenceLocale | No | Locale code used as the translation source (e.g., "en", "en-US"). Defaults to the project default locale. |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The description discloses substantial behavior beyond annotations: the provider vs. agent mode distinction, the fact that without a backend nothing is written, concurrency across all locales, and use of project config resources. This goes well past the safety profile covered by annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loads the core action and the critical no-backend caveat, then explains modes. A few sentences are slightly dense but every piece earns its place – no filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool's complexity, the presence of an output schema, and the 100% schema coverage, the description covers all the behavioral and routing information an agent needs: modes, concurrency, config usage, and the next tool to call.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the schema already documents all 9 parameters thoroughly. The description adds no parameter-specific syntax or default information beyond what the schema provides; the baseline of 3 is appropriate.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb+resource: 'Find the keys missing in the target locales and translate them.' It clearly distinguishes itself from siblings like get_missing_translations (find only) and translate_key (single key), and the two-mode behavior further clarifies scope.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly describes when it writes vs. when it returns fallback contexts for manual translation, names the alternative tool (write_translations) for agent mode, tells the user to 'check the discover output for the active mode', and advises passing all target locales concurrently. This is rich, actionable guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
write_translationsWrite TranslationsADestructiveIdempotent
Write translation key-value pairs to a layer. Keys are inserted in alphabetical order. Mode "upsert" adds new keys and updates existing ones (default, most common). Mode "add" only creates new keys, skipping existing ones. Mode "update" only modifies existing keys, skipping missing ones. Use dryRun to preview without writing.
| Name | Required | Description | Default |
|---|---|---|---|
| mode | No | Write mode. "upsert": add-or-update (never fails). "add": only new keys. "update": only existing keys. Default: "upsert". | |
| layer | Yes | Layer name from discover (e.g., "root", "app-admin"). | |
| dryRun | No | Return a preview of what would be written without writing any files. Default: false. | |
| projectDir | No | Absolute path to the project root. Defaults to I18N_PROJECT_DIR, then server cwd. Example: "/home/user/my-app". | |
| translations | Yes | Map of dot-path keys to locale-value pairs. IMPORTANT: values must be locale maps, NOT plain strings. Locale refs may be a code ("en-us"), a language ("en-US") or a file ("en-US.json"). Wrong: { "auth.failed": "Login failed" }. Correct: { "auth.failed": { "en-US": "Login failed", "de-DE": "Anmeldung fehlgeschlagen" } } |
Output Schema
| Name | Required | Description |
|---|---|---|
| dryRun | No | True when nothing was written because a preview was asked for. Absent otherwise. |
| message | No | The step to take next, as the surface the call ran on phrases it. Present only when there is no summary to carry it. |
| skipped | Yes | Keys the write mode left alone — existing keys under mode "add", missing ones under mode "update". |
| summary | No | Counts of what the run did. Absent on a dry run. |
| written | No | Dot-path keys that were written. Absent on a dry run. |
| warnings | No | Non-fatal problems, e.g. a value written over a nested object. Absent when there are none. |
| wouldWrite | No | The writes a dry run would make. Present only with dryRun. |
| skippedKeys | No | The keys behind keysSkipped, when the mode skipped any. Absent when nothing was skipped. |
| filesWritten | No | Number of locale files changed on disk. Absent on a dry run. |
| ambiguousLocales | No | Locale refs that matched several locales, with the one precedence picked. Absent when every ref was unambiguous. |
| unresolvedLocales | No | Locale refs that matched no known locale; their values were dropped while other locales were still written. Absent when every ref resolved. |
| placeholderValidation | No | Placeholder comparison of the written values against the reference locale. Absent when nothing was comparable. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare destructiveHint=true, idempotentHint=true and readOnlyHint=false, so the write profile is partly covered. The description adds real behavioral value beyond that: keys are inserted in alphabetical order, upsert 'never fails', add/update skip disjoint key sets, and dryRun previews without touching files. It stops short of stating that existing values are overwritten by upsert, which is the key destructive consequence.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The leading sentence front-loads the action and the alphabetical-ordering constraint, and each following sentence covers one mode or dryRun with no filler. Mild redundancy exists because the three mode sentences duplicate the enum descriptions in the schema.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With an output schema present, return values need not be explained, and the annotations carry the safety profile. The description covers modes, ordering, and dryRun adequately for a 5-parameter mutation tool, though it omits what upsert does to pre-existing values and any scope/permission constraints on the layer.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the enum values, defaults, and the locale-map value shape are already fully documented in the schema. The description largely restates the mode semantics and confirms upsert as the default; it adds no format or syntax detail beyond the structured fields. Baseline 3 is appropriate.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Write translation key-value pairs to a layer') and immediately describes the operation's semantics. It is distinguishable from remove_translations by contrast, though it never names a sibling explicitly. Clear but lacks direct sibling differentiation.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The mode semantics amount to strong in-tool guidance (upsert is flagged as default and 'most common', add/update are described with their skip behavior) and dryRun is recommended for previewing. However, there is no guidance on when to choose this tool over siblings such as translate_missing, translate_key, or scaffold_locale, which also create translations. Usage is implied rather than stated.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
18 tool updates
v1.0.6- Changed
discover3 fields changed- added
Input schema / properties / includeTranslationGuidanceAdded value: +{ + "description": "Keep the translation prose in projectConfig — glossary, translationPrompt, localeNotes, examples and context. Default: true at a terminal, false for a tool call, which omits those five fields and sets projectConfig.translationGuidanceOmitted instead. Every structural field (layerRules, protectedLocales, declaredNamespaces, orphanScan, translationMemory) is returned either way.", + "type": "boolean" +} - changed
Input schema / properties / projectDir / descriptionPrevious value: -"Absolute path to the project root. Defaults to I18N_PROJECT_DIR, then server cwd."New value: +"Absolute path to the project root. Defaults to I18N_PROJECT_DIR, then server cwd. Example: \"/home/user/my-app\"." - changed
Output schema / (root)Previous value: -nullNew value: +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "additionalProperties": false, + "properties": { + "apps": { + "description": "Apps and the layers each consumes — the consumer graph orphan scoping reads.", + "items": { + "additionalProperties": false, + "properties": { + "layers": { + "description": "Layer names this app consumes — its own plus every shared layer it can render.", + "items": { + "type": "string" + }, + "type": "array" + }, + "name": { + "description": "App name, as orphan and status reports name it.", + "type": "string" + }, + "rootDir": { + "description": "Absolute path to the app's root directory.", + "type": "string" + }, + "source": { + "description": "Where the consumption edges came from when not the framework adapter: \"workspace\" from package.json inference, \"declared\" from the config. Absent means the adapter.", + "enum": [ + "workspace", + "declared" + ], + "type": "string" + } + }, + "required": [ + "name", + "rootDir", + "layers" + ], + "type": "object" + }, + "type": "array" + }, + "defaultLocale": { + "description": "Default locale code — the source locale every translate call falls back to.", + "type": "string" + }, + "fallbackLocale": { + "additionalProperties": { + "description": "Locale codes to fall back to, in order.", + "items": { + "type": "string" + }, + "type": "array" + }, + "description": "The framework's fallback chain. Empty when the framework declares none.", + "propertyNames": { + "description": "Locale code, or \"default\".", + "type": "string" + }, + "type": "object" + }, + "framework": { + "description": "Detected framework, e.g. \"nuxt\", \"laravel\". Absent when nothing was detected.", + "type": "string" + }, + "layerGraph": { + "additionalProperties": false, + "description": "Which layers are shared and which apps consume which layer — what answers where a new key belongs.", + "properties": { + "aliases": { + "additionalProperties": { + "description": "The canonical layer whose locale directory it points at.", + "type": "string" + }, + "description": "Alias layer → canonical layer. Empty when no layer aliases another.", + "propertyNames": { + "description": "Alias layer name.", + "type": "string" + }, + "type": "object" + }, + "canonical": { + "description": "Alias-free layer names, in config order.", + "items": { + "type": "string" + }, + "type": "array" + }, + "consumers": { + "additionalProperties": { + "description": "Apps that consume it; empty when nothing does.", + "items": { + "type": "string" + }, + "type": "array" + }, + "description": "Every canonical layer is a key, so \"no consumers\" and \"not computed\" cannot be confused.", + "propertyNames": { + "description": "Canonical layer name.", + "type": "string" + }, + "type": "object" + }, + "shared": { + "description": "Canonical layers more than one app consumes — where a key used by several apps belongs.", + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "canonical", + "shared", + "aliases", + "consumers" + ], + "type": "object" + }, + "layerRootDirs": { + "description": "Absolute root directories of every layer, which is what source scanning walks.", + "items": { + "type": "string" + }, + "type": "array" + }, + "layers": { + "description": "One entry per locale directory, with file counts and key namespaces.", + "items": { + "additionalProperties": false, + "properties": { + "aliasOf": { + "description": "The layer this one aliases. Absent for a layer of its own.", + "type": "string" + }, + "fileCount": { + "description": "Number of locale files in the directory. 0 for an alias layer.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "layer": { + "description": "Layer name. Pass this as the layer argument of any other call.", + "type": "string" + }, + "namespaces": { + "description": "Namespace file names of one locale directory, for a namespaced layout such as Laravel. Absent for a flat layout.", + "items": { + "type": "string" + }, + "type": "array" + }, + "path": { + "description": "Absolute path to the locale directory.", + "type": "string" + }, + "topLevelKeys": { + "description": "Top-level keys of one locale file, for a flat layout. Absent for a namespaced layout.", + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "layer", + "path", + "fileCount" + ], + "type": "object" + }, + "type": "array" + }, + "localeDirs": { + "description": "Every locale directory, one per layer, alias layers included.", + "items": { + "additionalProperties": false, + "properties": { + "aliasOf": { + "description": "The layer this one points at, when the directory is an alias of another layer's. Absent for a layer of its own.", + "type": "string" + }, + "layer": { + "description": "Layer name, e.g. \"root\", \"app-admin\".", + "type": "string" + }, + "layerRootDir": { + "description": "Absolute path to that layer's root directory.", + "type": "string" + }, + "path": { + "description": "Absolute path to the locale directory.", + "type": "string" + } + }, + "required": [ + "path", + "layer", + "layerRootDir" + ], + "type": "object" + }, + "type": "array" + }, + "localeFileFormat": { + "description": "Format of the locale files. Absent means the default, \"json\".", + "enum": [ + "json", + "php-array", + "yaml" + ], + "type": "string" + }, + "locales": { + "description": "Every locale of the project.", + "items": { + "additionalProperties": false, + "properties": { + "code": { + "description": "Locale code used in URLs and as the identifier everywhere else, e.g. \"de\".", + "type": "string" + }, + "file": { + "description": "Locale file name, e.g. \"de-DE.json\". Absent for directory-per-locale layouts.", + "type": "string" + }, + "language": { + "description": "BCP-47 language tag, e.g. \"de-DE\".", + "type": "string" + }, + "name": { + "description": "Human-readable language name. Absent unless the framework config names one.", + "type": "string" + } + }, + "required": [ + "code", + "language" + ], + "type": "object" + }, + "type": "array" + }, + "projectConfig": { + "anyOf": [ + { + "additionalProperties": false, + "properties": { + "$schema": { + "description": "Path or URL to the JSON schema for IDE autocompletion.", + "type": "string" + }, + "apps": { + "description": "Which app consumes which layers — the consumer graph behind app-scoped orphan detection, misplaced-usage reports and unconsumed-layer warnings. Declaring it overrides both framework discovery and workspace inference.", + "items": { + "additionalProperties": false, + "properties": { + "layers": { + "description": "Layer names this app can render — its own layer plus every shared layer it consumes.", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "name": { + "description": "App name, as it appears in orphan and status reports (e.g., 'shop', '@acme/admin').", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "name", + "layers" + ], + "type": "object" + }, + "type": "array" + }, + "consumerGraph": { + "description": "Whether to infer the consumer graph from the package manager workspace and package.json dependencies when the framework provides no app information: 'auto' (default) infers it, 'off' keeps every layer in one app.", + "enum": [ + "auto", + "off" + ], + "type": "string" + }, + "context": { + "description": "Free-form project background for the agent — business domain, user base, brand voice, anything that helps the agent understand the project.", + "type": "string" + }, + "declaredNamespaces": { + "description": "Namespaces whose keys exist by contract rather than by a call site — sent by a backend, keyed by runtime data, built from a registry. Their keys are never reported as orphans and never written by check --write, in every layer. Each declaration is reported with the keys it matches, so one that matches nothing is visible as stale.", + "items": { + "additionalProperties": false, + "properties": { + "pattern": { + "description": "Key pattern the declaration covers (e.g., \"views.defaults.**\"). Use * to match a single key segment, ** to match any depth.", + "minLength": 1, + "type": "string" + }, + "reason": { + "description": "What keeps these keys alive, named so the report can say why they are protected (e.g., \"sent by bookings-api as name_key\").", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "pattern", + "reason" + ], + "type": "object" + }, + "type": "array" + }, + "defaultLocale": { + "description": "Default locale code. Required for generic adapter activation.", + "minLength": 1, + "type": "string" + }, + "examples": { + "description": "Few-shot translation examples that demonstrate the project's style. The agent uses these as reference when generating translations.", + "items": { + "additionalProperties": { + "type": "string" + }, + "description": "A single translation example. 'key' holds the dot-path translation key (e.g., 'common.actions.save'), 'note' an optional style comment, and every other property is a locale code (e.g., 'de-DE', 'en-US') mapped to its translated value.", + "properties": { + "key": { + "description": "The dot-path translation key this example demonstrates (e.g., 'common.actions.save').", + "type": "string" + }, + "note": { + "description": "Optional style comment shown alongside the example.", + "type": "string" + } + }, + "required": [ + "key" + ], + "type": "object" + }, + "type": "array" + }, + "framework": { + "description": "Force framework detection instead of auto-detecting from project structure. Any registered adapter name is accepted — the suggestions are the adapters that ship today, not the only permitted values. 'vue' is accepted as an alias for 'generic', which resolves Vue projects: declare localeDirs when the locale files are not in a conventional directory.", + "type": "string" + }, + "glossary": { + "additionalProperties": { + "type": "string" + }, + "description": "Term dictionary for consistent translations. Keys are source terms, values describe the required translation or usage note.", + "propertyNames": { + "type": "string" + }, + "type": "object" + }, + "layerRules": { + "description": "Rules that help the agent decide which layer a new translation key belongs to. The agent interprets the natural-language 'when' field.", + "items": { + "additionalProperties": false, + "properties": { + "description": { + "description": "What this layer contains — key namespaces, domain scope, etc.", + "type": "string" + }, + "layer": { + "description": "Layer name (e.g., 'root', 'app-admin', 'app-shop', 'lang').", + "type": "string" + }, + "when": { + "description": "Natural-language rule describing when a key should go in this layer.", + "type": "string" + } + }, + "required": [ + "layer", + "description", + "when" + ], + "type": "object" + }, + "type": "array" + }, + "localeDirs": { + "description": "Locale directories for the generic adapter. Each entry is a path string (layer defaults to 'default') or an object with 'path' and 'layer' properties.", + "items": { + "anyOf": [ + { + "description": "Relative path to a locale directory. Layer name defaults to 'default'.", + "minLength": 1, + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "layer": { + "description": "Layer name for this locale directory.", + "minLength": 1, + "type": "string" + }, + "path": { + "description": "Relative path to a locale directory.", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "path", + "layer" + ], + "type": "object" + } + ] + }, + "type": "array" + }, + "localeFileFormat": { + "description": "Override the auto-detected locale file format. 'yaml' covers both .yaml and .yml files. Useful when several formats exist in one project or auto-detection picks wrong.", + "enum": [ + "json", + "php-array", + "yaml" + ], + "type": "string" + }, + "localeNotes": { + "additionalProperties": { + "type": "string" + }, + "description": "Per-locale context included in translation prompts. Each key is matched against a locale's code, language tag, or file name (with or without extension) — whichever the project uses, e.g. 'de', 'de-DE' or 'de-DE.json'. Values describe register, regional conventions, or other locale-specific guidance. A key that matches no locale is reported and ignored.", + "propertyNames": { + "type": "string" + }, + "type": "object" + }, + "locales": { + "description": "Explicit list of locale codes to operate on. If absent, locales are auto-discovered from files on disk.", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "orphanScan": { + "additionalProperties": { + "additionalProperties": {}, + "properties": { + "ignorePatterns": { + "description": "Glob patterns for translation keys to exclude from orphan detection in this layer (e.g., \"common.datetime.months.*\"). Use * to match a single key segment, ** to match any depth. For keys that exist by contract elsewhere, declare them under declaredNamespaces instead, which records why and reports what each pattern protects.", + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "description": "Per-layer configuration for orphan key detection. Map each layer name to its settings. Scan directories are automatically determined from each layer's root directory.", + "propertyNames": { + "type": "string" + }, + "type": "object" + }, + "protectedLocales": { + "description": "Human-maintained locales excluded from automatic translation. Entries may be any locale ref (code, language tag, or file name); entries that do not match a known locale are ignored with a warning. Explicitly naming a protected locale in targetLocales overrides the protection (with a warning).", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "providerBaseUrl": { + "description": "Base URL for the LLM provider — gateways, self-hosted model servers and corporate proxies that speak the provider's own protocol. Overrides the endpoint only, not the request shape or auth header. Overridden by the I18N_BASE_URL environment variable and by --baseUrl.", + "minLength": 1, + "type": "string" + }, + "reportOutput": { + "anyOf": [ + { + "const": true, + "description": "Set to true to write reports to the default '.i18n-reports/' directory.", + "type": "boolean" + }, + { + "description": "Custom directory path (relative to project root) where diagnostic tool reports are written.", + "minLength": 1, + "type": "string" + } + ], + "description": "Enable file output for diagnostic tools (get_missing_translations, search_translations, find_orphan_keys, find_undefined_keys). When set, each tool writes its full JSON report to <reportOutput>/<toolName>.json and returns only a summary in the MCP response. Set to true for the default '.i18n-reports/' directory, or a string for a custom path." + }, + "samplingPreferences": { + "deprecated": true, + "description": "Deprecated and ignored — MCP sampling was removed. Still accepted so existing config files keep validating. Configure a provider instead (e.g., I18N_PROVIDER/I18N_MODEL)." + }, + "translationMemory": { + "description": "Write a translation memory to '.i18n-kit.lock.json' at the project root, recording per layer, key and target locale a hash of the source text each translation was made from, so later runs can tell targets that are still current from ones whose source has changed since. On by default: the first translate run writes the file, which belongs in version control like any other lockfile. Set to false to disable it — nothing is then read or written, and an existing file is left untouched.", + "type": "boolean" + }, + "translationPrompt": { + "description": "System prompt prepended to all translation requests (provider mode and agent-mode fallback contexts). Sets tone, style, and constraints.", + "type": "string" + } + }, + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "$schema": { + "description": "Path or URL to the JSON schema for IDE autocompletion.", + "type": "string" + }, + "apps": { + "description": "Which app consumes which layers — the consumer graph behind app-scoped orphan detection, misplaced-usage reports and unconsumed-layer warnings. Declaring it overrides both framework discovery and workspace inference.", + "items": { + "additionalProperties": false, + "properties": { + "layers": { + "description": "Layer names this app can render — its own layer plus every shared layer it consumes.", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "name": { + "description": "App name, as it appears in orphan and status reports (e.g., 'shop', '@acme/admin').", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "name", + "layers" + ], + "type": "object" + }, + "type": "array" + }, + "consumerGraph": { + "description": "Whether to infer the consumer graph from the package manager workspace and package.json dependencies when the framework provides no app information: 'auto' (default) infers it, 'off' keeps every layer in one app.", + "enum": [ + "auto", + "off" + ], + "type": "string" + }, + "declaredNamespaces": { + "description": "Namespaces whose keys exist by contract rather than by a call site — sent by a backend, keyed by runtime data, built from a registry. Their keys are never reported as orphans and never written by check --write, in every layer. Each declaration is reported with the keys it matches, so one that matches nothing is visible as stale.", + "items": { + "additionalProperties": false, + "properties": { + "pattern": { + "description": "Key pattern the declaration covers (e.g., \"views.defaults.**\"). Use * to match a single key segment, ** to match any depth.", + "minLength": 1, + "type": "string" + }, + "reason": { + "description": "What keeps these keys alive, named so the report can say why they are protected (e.g., \"sent by bookings-api as name_key\").", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "pattern", + "reason" + ], + "type": "object" + }, + "type": "array" + }, + "defaultLocale": { + "description": "Default locale code. Required for generic adapter activation.", + "minLength": 1, + "type": "string" + }, + "framework": { + "description": "Force framework detection instead of auto-detecting from project structure. Any registered adapter name is accepted — the suggestions are the adapters that ship today, not the only permitted values. 'vue' is accepted as an alias for 'generic', which resolves Vue projects: declare localeDirs when the locale files are not in a conventional directory.", + "type": "string" + }, + "layerRules": { + "description": "Rules that help the agent decide which layer a new translation key belongs to. The agent interprets the natural-language 'when' field.", + "items": { + "additionalProperties": false, + "properties": { + "description": { + "description": "What this layer contains — key namespaces, domain scope, etc.", + "type": "string" + }, + "layer": { + "description": "Layer name (e.g., 'root', 'app-admin', 'app-shop', 'lang').", + "type": "string" + }, + "when": { + "description": "Natural-language rule describing when a key should go in this layer.", + "type": "string" + } + }, + "required": [ + "layer", + "description", + "when" + ], + "type": "object" + }, + "type": "array" + }, + "localeDirs": { + "description": "Locale directories for the generic adapter. Each entry is a path string (layer defaults to 'default') or an object with 'path' and 'layer' properties.", + "items": { + "anyOf": [ + { + "description": "Relative path to a locale directory. Layer name defaults to 'default'.", + "minLength": 1, + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "layer": { + "description": "Layer name for this locale directory.", + "minLength": 1, + "type": "string" + }, + "path": { + "description": "Relative path to a locale directory.", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "path", + "layer" + ], + "type": "object" + } + ] + }, + "type": "array" + }, + "localeFileFormat": { + "description": "Override the auto-detected locale file format. 'yaml' covers both .yaml and .yml files. Useful when several formats exist in one project or auto-detection picks wrong.", + "enum": [ + "json", + "php-array", + "yaml" + ], + "type": "string" + }, + "locales": { + "description": "Explicit list of locale codes to operate on. If absent, locales are auto-discovered from files on disk.", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "orphanScan": { + "additionalProperties": { + "additionalProperties": {}, + "properties": { + "ignorePatterns": { + "description": "Glob patterns for translation keys to exclude from orphan detection in this layer (e.g., \"common.datetime.months.*\"). Use * to match a single key segment, ** to match any depth. For keys that exist by contract elsewhere, declare them under declaredNamespaces instead, which records why and reports what each pattern protects.", + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "description": "Per-layer configuration for orphan key detection. Map each layer name to its settings. Scan directories are automatically determined from each layer's root directory.", + "propertyNames": { + "type": "string" + }, + "type": "object" + }, + "protectedLocales": { + "description": "Human-maintained locales excluded from automatic translation. Entries may be any locale ref (code, language tag, or file name); entries that do not match a known locale are ignored with a warning. Explicitly naming a protected locale in targetLocales overrides the protection (with a warning).", + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array" + }, + "providerBaseUrl": { + "description": "Base URL for the LLM provider — gateways, self-hosted model servers and corporate proxies that speak the provider's own protocol. Overrides the endpoint only, not the request shape or auth header. Overridden by the I18N_BASE_URL environment variable and by --baseUrl.", + "minLength": 1, + "type": "string" + }, + "reportOutput": { + "anyOf": [ + { + "const": true, + "description": "Set to true to write reports to the default '.i18n-reports/' directory.", + "type": "boolean" + }, + { + "description": "Custom directory path (relative to project root) where diagnostic tool reports are written.", + "minLength": 1, + "type": "string" + } + ], + "description": "Enable file output for diagnostic tools (get_missing_translations, search_translations, find_orphan_keys, find_undefined_keys). When set, each tool writes its full JSON report to <reportOutput>/<toolName>.json and returns only a summary in the MCP response. Set to true for the default '.i18n-reports/' directory, or a string for a custom path." + }, + "samplingPreferences": { + "deprecated": true, + "description": "Deprecated and ignored — MCP sampling was removed. Still accepted so existing config files keep validating. Configure a provider instead (e.g., I18N_PROVIDER/I18N_MODEL)." + }, + "translationGuidanceOmitted": { + "const": true, + "description": "The translation prose (context, glossary, translationPrompt, localeNotes, examples) exists but was left out. Ask with includeTranslationGuidance for it.", + "type": "boolean" + }, + "translationMemory": { + "description": "Write a translation memory to '.i18n-kit.lock.json' at the project root, recording per layer, key and target locale a hash of the source text each translation was made from, so later runs can tell targets that are still current from ones whose source has changed since. On by default: the first translate run writes the file, which belongs in version control like any other lockfile. Set to false to disable it — nothing is then read or written, and an existing file is left untouched.", + "type": "boolean" + } + }, + "required": [ + "translationGuidanceOmitted" + ], + "type": "object" + } + ], + "description": "The declared config from i18n-kit.config.ts or .i18n-mcp.json, as written — or without its translation prose, flagged. Absent when the project has none." + }, + "protectedLocales": { + "description": "Canonical codes of the locales the translate operations leave alone. Empty when none are protected.", + "items": { + "type": "string" + }, + "type": "array" + }, + "rootDir": { + "description": "Absolute path to the project root.", + "type": "string" + }, + "translationMode": { + "description": "Added by the server: whether it has an LLM provider configured (\"provider\") or hands back contexts to translate inline (\"agent\"). Check this before calling a translating tool.", + "enum": [ + "provider", + "agent" + ], + "type": "string" + }, + "translationModel": { + "description": "Added by the server: the configured model name. Absent in agent mode.", + "type": "string" + }, + "translationProvider": { + "description": "Added by the server: the configured provider name. Absent in agent mode.", + "type": "string" + } + }, + "required": [ + "rootDir", + "defaultLocale", + "fallbackLocale", + "locales", + "localeDirs", + "layerRootDirs", + "apps", + "protectedLocales", + "layers", + "layerGraph" + ], + "type": "object" +}
- Changed
find_duplicate_keys5 fields changed- changed
Input schema / properties / byValue / descriptionPrevious value: -"When true, also groups different keys carrying the same value — e.g. common.actions.save and calendar.views.save both \"Speichern\". Each group says what to do about it: \"reuse\" (a shared layer already has it — delete the app copies and repoint call sites), \"promote\" (move one to a shared layer with move_translation_key), or \"consolidate\" (duplication inside one layer). Default: false."New value: +"Also group different keys carrying the same value — e.g. common.actions.save and calendar.views.save both \"Speichern\". Each group says what to do about it: \"reuse\" (a shared layer already has it — delete the app copies and repoint the call sites), \"promote\" (move one to a shared layer) or \"consolidate\" (duplication inside one layer). Default: false." - changed
Input schema / properties / minValueLength / descriptionPrevious value: -"Shortest value worth grouping when byValue is set. Default: 4 — below it, values like \"OK\" repeat across unrelated namespaces legitimately."New value: +"Shortest value worth grouping when byValue is set. Default: 4 — below that, values like \"OK\" repeat across unrelated namespaces legitimately." - changed
Input schema / properties / outputFile / descriptionPrevious value: -"Absolute path to write full JSON output. Returns only a compact summary to the caller — use this for large outputs to avoid flooding the conversation context. Example: \"/tmp/duplicate-keys.json\""New value: +"Absolute path to write the full JSON output to. Only a compact summary is returned to the caller, which is what you want for a result too large to read in one piece. Example: \".i18n-reports/duplicate-keys.json\"" - changed
Input schema / properties / projectDir / descriptionPrevious value: -"Absolute path to the Nuxt project root. Defaults to I18N_PROJECT_DIR, then server cwd. Example: \"/home/user/my-app\"."New value: +"Absolute path to the project root. Defaults to I18N_PROJECT_DIR, then server cwd. Example: \"/home/user/my-app\"." - changed
Output schema / (root)Previous value: -nullNew value: +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "anyOf": [ + { + "additionalProperties": false, + "properties": { + "collisions": { + "description": "Keys defined in both a shared layer and a layer that consumes it.", + "items": { + "additionalProperties": false, + "properties": { + "childLayer": { + "description": "The consuming layer, whose value wins at runtime.", + "type": "string" + }, + "childValue": { + "description": "What the child layer holds — this is what renders." + }, + "divergent": { + "description": "True when the two values differ, which is the dangerous case: the shared value silently never shows. Fix by deleting one side, never by moving.", + "type": "boolean" + }, + "key": { + "description": "The key both layers define.", + "type": "string" + }, + "sharedLayer": { + "description": "The layer that is fallen through to.", + "type": "string" + }, + "sharedValue": { + "description": "What the shared layer holds." + } + }, + "required": [ + "key", + "sharedLayer", + "childLayer", + "sharedValue", + "childValue", + "divergent" + ], + "type": "object" + }, + "type": "array" + }, + "guidance": { + "description": "How to act on the findings, in one paragraph.", + "type": "string" + }, + "summary": { + "additionalProperties": false, + "description": "What the scan compared and what it found. This is what comes back when the full result is diverted to a file.", + "properties": { + "divergentCount": { + "description": "Of those, the ones whose values differ.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "locale": { + "description": "Locale the values were compared in.", + "type": "string" + }, + "message": { + "description": "One sentence stating what the scan found.", + "type": "string" + }, + "pairsChecked": { + "description": "(shared layer, consuming layer) pairs the scan compared. 0 for a single-layer project.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "reusableGroups": { + "description": "Of those, the ones a shared layer already covers. Present only when byValue was passed.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "totalCollisions": { + "description": "Keys defined in both a shared and a consuming layer.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "valueGroups": { + "description": "Groups of keys sharing a value. Present only when byValue was passed.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + } + }, + "required": [ + "totalCollisions", + "divergentCount", + "pairsChecked", + "locale" + ], + "type": "object" + }, + "valueDuplicates": { + "description": "Different keys carrying the same value. Present only when byValue was passed.", + "items": { + "additionalProperties": false, + "properties": { + "action": { + "description": "What to do: \"reuse\" a shared layer already has it, \"promote\" move one to a shared layer, \"consolidate\" duplication inside one layer.", + "enum": [ + "reuse", + "promote", + "consolidate" + ], + "type": "string" + }, + "members": { + "description": "Every key carrying the value, ordered so the shared ones come first.", + "items": { + "additionalProperties": false, + "properties": { + "key": { + "description": "A key carrying this value.", + "type": "string" + }, + "layer": { + "description": "The layer it lives in.", + "type": "string" + }, + "shared": { + "description": "True when other layers fall through to this one, so the key is already reachable from them.", + "type": "boolean" + } + }, + "required": [ + "key", + "layer", + "shared" + ], + "type": "object" + }, + "type": "array" + }, + "normalized": { + "description": "What the members were grouped by — trimmed, case-folded, punctuation-stripped.", + "type": "string" + }, + "value": { + "description": "The value as written, taken from the first member.", + "type": "string" + } + }, + "required": [ + "value", + "normalized", + "action", + "members" + ], + "type": "object" + }, + "type": "array" + } + }, + "required": [ + "collisions", + "guidance", + "summary" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "reportFile": { + "description": "Absolute path the full JSON result was written to. Read the file for the findings; the summary below is all that came back.", + "type": "string" + }, + "summary": { + "additionalProperties": false, + "description": "The counts from the full result, so a caller can act on them without reading the file.", + "properties": { + "divergentCount": { + "description": "Of those, the ones whose values differ.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "locale": { + "description": "Locale the values were compared in.", + "type": "string" + }, + "message": { + "description": "One sentence stating what the scan found.", + "type": "string" + }, + "pairsChecked": { + "description": "(shared layer, consuming layer) pairs the scan compared. 0 for a single-layer project.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "reusableGroups": { + "description": "Of those, the ones a shared layer already covers. Present only when byValue was passed.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "totalCollisions": { + "description": "Keys defined in both a shared and a consuming layer.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "valueGroups": { + "description": "Groups of keys sharing a value. Present only when byValue was passed.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + } + }, + "required": [ + "totalCollisions", + "divergentCount", + "pairsChecked", + "locale" + ], + "type": "object" + } + }, + "required": [ + "reportFile", + "summary" + ], + "type": "object" + } + ], + "type": "object" +}
- Removed
find_empty_translations - Changed
find_orphan_keys7 fields changed- changed
Input schema / properties / layer / descriptionPrevious value: -"Layer name to check for orphan keys (e.g., \"root\", \"app-admin\"). If omitted, checks all layers. Call discover to see available layers."New value: +"Layer name to scope this to (e.g., \"root\", \"app-admin\"). If omitted, every layer is included. Call discover to list the layers." - changed
Input schema / properties / locale / descriptionPrevious value: -"Locale code to read translation keys from (e.g., \"en\", \"en-US\"). Defaults to the project default locale."New value: +"Locale code to read the translation keys from (e.g., \"en\", \"en-US\"). Defaults to the project default locale." - changed
Input schema / properties / outputFile / descriptionPrevious value: -"Absolute path to write full JSON output. Returns only a compact summary to the caller — use this for large outputs to avoid flooding the conversation context. Example: \"/tmp/orphan-keys.json\""New value: +"Absolute path to write the full JSON output to. Only a compact summary is returned to the caller, which is what you want for a result too large to read in one piece. Example: \".i18n-reports/orphan-keys.json\"" - changed
Input schema / properties / projectDir / descriptionPrevious value: -"Absolute path to the Nuxt project root. Defaults to I18N_PROJECT_DIR, then server cwd. Example: \"/home/user/my-app\"."New value: +"Absolute path to the project root. Defaults to I18N_PROJECT_DIR, then server cwd. Example: \"/home/user/my-app\"." - added
Input schema / properties / removeAdded value: +{ + "description": "Permanently delete the orphan keys from every locale file of their layer. Default: false, which only reports them — run without it first and read the findings. Uncertain keys and misplaced usages are never deleted.", + "type": "boolean" +} - changed
Input schema / properties / scanDirs / descriptionPrevious value: -"Absolute paths to directories to scan for source code usage. Overrides scope-aware scanning: all layers are checked globally against these dirs. Example: [\"/home/user/my-app/apps/admin\"]."New value: +"Absolute paths of the directories to scan for source usage. Overrides scope-aware scanning: every layer is then checked against these directories alone. Example: [\"/home/user/my-app/apps/admin\"]." - changed
Output schema / (root)Previous value: -nullNew value: +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "anyOf": [ + { + "anyOf": [ + { + "additionalProperties": false, + "properties": { + "candidateOnlyKeys": { + "additionalProperties": { + "description": "Dot-path keys of that layer.", + "items": { + "type": "string" + }, + "type": "array" + }, + "description": "Layer → keys kept alive only by the bare-candidate net: a dotted string somewhere shares their name, but nothing a frontend calls a usage references them. Not orphans, but where dead references hide.", + "propertyNames": { + "description": "Layer name.", + "type": "string" + }, + "type": "object" + }, + "candidateOnlyNote": { + "description": "How to read the candidate-only keys. Present alongside them.", + "type": "string" + }, + "declaredNamespaceNote": { + "description": "How to read the declared namespaces. Present alongside them.", + "type": "string" + }, + "declaredNamespaces": { + "description": "Every declared namespace with the keys it covers — the keys this scan will not report. Present when the config declares any.", + "items": { + "additionalProperties": false, + "properties": { + "matchedKeys": { + "description": "Keys of the checked layers this pattern covers. Empty means the declaration is stale.", + "items": { + "type": "string" + }, + "type": "array" + }, + "pattern": { + "description": "The declaredNamespaces pattern from the project config, e.g. \"views.defaults.**\".", + "type": "string" + }, + "reason": { + "description": "What keeps these keys alive, as the config declares it.", + "type": "string" + } + }, + "required": [ + "pattern", + "reason", + "matchedKeys" + ], + "type": "object" + }, + "type": "array" + }, + "dynamicKeyWarning": { + "description": "Present when dynamic key expressions were found, which is when the orphan list is a candidate list rather than a verdict.", + "type": "string" + }, + "dynamicKeys": { + "description": "Dynamic key expressions found in source, with their call sites.", + "items": { + "additionalProperties": false, + "properties": { + "expression": { + "description": "The key expression as written in source, e.g. \"`errors.${code}`\".", + "type": "string" + }, + "file": { + "description": "Source file, relative to the project directory. Absent for context-free bare candidates, which have no single call site.", + "type": "string" + }, + "line": { + "description": "1-based line number in that file. Absent with file.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + } + }, + "required": [ + "expression" + ], + "type": "object" + }, + "type": "array" + }, + "linkedNote": { + "description": "Why keys linked with @: from another message's value are not orphans. Present when any is.", + "type": "string" + }, + "misplacedUsageNote": { + "description": "What to do about the misplaced usages. Present alongside them.", + "type": "string" + }, + "misplacedUsages": { + "description": "Keys referenced only from apps that do not consume the owning layer. Reported instead of being called orphans, and never deleted.", + "items": { + "additionalProperties": false, + "properties": { + "key": { + "description": "Dot-path key referenced only from outside its layer's consumption scope.", + "type": "string" + }, + "layer": { + "description": "Layer the key is defined in.", + "type": "string" + }, + "usingApps": { + "description": "Scan units (apps or layers) that reference it without consuming that layer.", + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "key", + "layer", + "usingApps" + ], + "type": "object" + }, + "type": "array" + }, + "orphanKeys": { + "additionalProperties": { + "description": "Dot-path keys of that layer.", + "items": { + "type": "string" + }, + "type": "array" + }, + "description": "Layer → keys no source code of a consuming app references. The only keys remove ever deletes.", + "propertyNames": { + "description": "Layer name.", + "type": "string" + }, + "type": "object" + }, + "summary": { + "additionalProperties": false, + "description": "What the scan covered and what it found. This is what comes back when the full result is diverted to a file.", + "properties": { + "candidateOnlyCount": { + "description": "Keys kept alive only by the bare-candidate net.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "declaredCount": { + "description": "Keys withheld from the orphan list by a declaredNamespaces entry.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "dirsScanned": { + "description": "Directories the source scan walked.", + "items": { + "type": "string" + }, + "type": "array" + }, + "dynamicMatchedCount": { + "description": "Keys kept alive by a dynamic key expression rather than a literal call.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "filesDeclined": { + "description": "Files a syntax frontend declined; pattern matching read them instead.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "filesScanned": { + "description": "Source files read.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "ignoredCount": { + "description": "Keys excluded by an orphanScan ignorePattern.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "layersChecked": { + "description": "Layer names the scan covered.", + "items": { + "type": "string" + }, + "type": "array" + }, + "linkedCount": { + "description": "Keys withheld because another message's value links to them with @:. Protected in every layer, never deleted.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "locale": { + "description": "Locale the translation keys were read from.", + "type": "string" + }, + "message": { + "description": "One sentence stating what the scan found.", + "type": "string" + }, + "misplacedCount": { + "description": "Keys used only from apps that do not consume their layer. Never deleted.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "orphanCount": { + "description": "Keys nothing references. The counter the orphan gate reads.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "scanScope": { + "additionalProperties": { + "description": "Directories that layer was checked against.", + "items": { + "type": "string" + }, + "type": "array" + }, + "description": "Each layer's effective scope: the code of the apps that consume it.", + "propertyNames": { + "description": "Layer name.", + "type": "string" + }, + "type": "object" + }, + "totalKeys": { + "description": "Translation keys of the checked layers.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "uncertainCount": { + "description": "Keys with ambiguous usage evidence. Never deleted, in any mode.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "usedCount": { + "description": "Keys with usage evidence in a consuming app.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + } + }, + "required": [ + "totalKeys", + "orphanCount", + "filesScanned" + ], + "type": "object" + }, + "uncertainKeys": { + "additionalProperties": { + "description": "Dot-path keys of that layer.", + "items": { + "type": "string" + }, + "type": "array" + }, + "description": "Layer → keys with ambiguous usage evidence. Never deleted, in any mode.", + "propertyNames": { + "description": "Layer name.", + "type": "string" + }, + "type": "object" + }, + "unresolvedKeyWarnings": { + "description": "Dynamic expressions that could not be resolved to concrete keys, each with an ignore pattern that would cover it.", + "items": { + "additionalProperties": false, + "properties": { + "callee": { + "description": "The translation function the expression was passed to, e.g. \"t\" or \"$t\".", + "type": "string" + }, + "expression": { + "description": "The dynamic key expression that could not be resolved to concrete keys.", + "type": "string" + }, + "file": { + "description": "Source file, relative to the project directory.", + "type": "string" + }, + "line": { + "description": "1-based line number of the call.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "suggestedIgnorePattern": { + "description": "A key glob for orphanScan.ignorePatterns that would cover this expression, when one can be derived.", + "type": "string" + } + }, + "required": [ + "expression", + "file", + "line", + "callee" + ], + "type": "object" + }, + "type": "array" + } + }, + "required": [ + "orphanKeys", + "summary" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "declaredNamespaceNote": { + "description": "How to read the declared namespaces. Present alongside them.", + "type": "string" + }, + "declaredNamespaces": { + "description": "Every declared namespace with the keys it covers — the keys this scan will not report. Present when the config declares any.", + "items": { + "additionalProperties": false, + "properties": { + "matchedKeys": { + "description": "Keys of the checked layers this pattern covers. Empty means the declaration is stale.", + "items": { + "type": "string" + }, + "type": "array" + }, + "pattern": { + "description": "The declaredNamespaces pattern from the project config, e.g. \"views.defaults.**\".", + "type": "string" + }, + "reason": { + "description": "What keeps these keys alive, as the config declares it.", + "type": "string" + } + }, + "required": [ + "pattern", + "reason", + "matchedKeys" + ], + "type": "object" + }, + "type": "array" + }, + "dynamicKeyWarning": { + "description": "Present when dynamic key expressions were found, which is when the orphan list is a candidate list rather than a verdict.", + "type": "string" + }, + "dynamicKeys": { + "description": "Dynamic key expressions found in source, with their call sites.", + "items": { + "additionalProperties": false, + "properties": { + "expression": { + "description": "The key expression as written in source, e.g. \"`errors.${code}`\".", + "type": "string" + }, + "file": { + "description": "Source file, relative to the project directory. Absent for context-free bare candidates, which have no single call site.", + "type": "string" + }, + "line": { + "description": "1-based line number in that file. Absent with file.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + } + }, + "required": [ + "expression" + ], + "type": "object" + }, + "type": "array" + }, + "misplacedUsageNote": { + "description": "What to do about the misplaced usages. Present alongside them.", + "type": "string" + }, + "misplacedUsages": { + "description": "Keys referenced only from apps that do not consume the owning layer. Reported instead of being called orphans, and never deleted.", + "items": { + "additionalProperties": false, + "properties": { + "key": { + "description": "Dot-path key referenced only from outside its layer's consumption scope.", + "type": "string" + }, + "layer": { + "description": "Layer the key is defined in.", + "type": "string" + }, + "usingApps": { + "description": "Scan units (apps or layers) that reference it without consuming that layer.", + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "key", + "layer", + "usingApps" + ], + "type": "object" + }, + "type": "array" + }, + "orphanKeys": { + "additionalProperties": { + "description": "Dot-path keys of that layer.", + "items": { + "type": "string" + }, + "type": "array" + }, + "description": "Layer → keys that were found unreferenced. Present on a dry run.", + "propertyNames": { + "description": "Layer name.", + "type": "string" + }, + "type": "object" + }, + "removed": { + "additionalProperties": { + "description": "Dot-path keys of that layer.", + "items": { + "type": "string" + }, + "type": "array" + }, + "description": "Layer → keys deleted from every locale file of that layer.", + "propertyNames": { + "description": "Layer name.", + "type": "string" + }, + "type": "object" + }, + "summary": { + "additionalProperties": false, + "description": "What the removal covered and what it deleted. This is what comes back when the full result is diverted to a file.", + "properties": { + "declaredCount": { + "description": "Keys withheld from the orphan list by a declaredNamespaces entry.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "dirsScanned": { + "description": "Directories the source scan walked.", + "items": { + "type": "string" + }, + "type": "array" + }, + "dryRun": { + "description": "True when nothing was deleted because a preview was asked for.", + "type": "boolean" + }, + "dynamicMatchedCount": { + "description": "Keys kept alive by a dynamic key expression rather than a literal call.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "filesScanned": { + "description": "Source files read.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "filesWritten": { + "description": "Locale files changed on disk.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "ignoredCount": { + "description": "Keys excluded by an orphanScan ignorePattern.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "layersChecked": { + "description": "Layer names the scan covered.", + "items": { + "type": "string" + }, + "type": "array" + }, + "linkedCount": { + "description": "Keys withheld because another message's value links to them with @:. Protected in every layer, never deleted.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "locale": { + "description": "Locale the translation keys were read from.", + "type": "string" + }, + "message": { + "description": "One sentence stating what the scan found.", + "type": "string" + }, + "misplacedCount": { + "description": "Keys used only from apps that do not consume their layer. Never deleted.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "orphanCount": { + "description": "Keys nothing references. The counter the orphan gate reads.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "remainingCount": { + "description": "Keys left in the layer after the removal.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "removedCount": { + "description": "Keys deleted from their layer.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "scanScope": { + "additionalProperties": { + "description": "Directories that layer was checked against.", + "items": { + "type": "string" + }, + "type": "array" + }, + "description": "Each layer's effective scope: the code of the apps that consume it.", + "propertyNames": { + "description": "Layer name.", + "type": "string" + }, + "type": "object" + }, + "totalKeys": { + "description": "Translation keys of the checked layers.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "uncertainCount": { + "description": "Keys with ambiguous usage evidence. Never deleted, in any mode.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "usedCount": { + "description": "Keys with usage evidence in a consuming app.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + } + }, + "required": [ + "totalKeys" + ], + "type": "object" + }, + "uncertainKeys": { + "additionalProperties": { + "description": "Dot-path keys of that layer.", + "items": { + "type": "string" + }, + "type": "array" + }, + "description": "Layer → keys with ambiguous usage evidence. Never deleted, in any mode.", + "propertyNames": { + "description": "Layer name.", + "type": "string" + }, + "type": "object" + }, + "unresolvedKeyWarnings": { + "description": "Dynamic expressions that could not be resolved to concrete keys, each with an ignore pattern that would cover it.", + "items": { + "additionalProperties": false, + "properties": { + "callee": { + "description": "The translation function the expression was passed to, e.g. \"t\" or \"$t\".", + "type": "string" + }, + "expression": { + "description": "The dynamic key expression that could not be resolved to concrete keys.", + "type": "string" + }, + "file": { + "description": "Source file, relative to the project directory.", + "type": "string" + }, + "line": { + "description": "1-based line number of the call.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "suggestedIgnorePattern": { + "description": "A key glob for orphanScan.ignorePatterns that would cover this expression, when one can be derived.", + "type": "string" + } + }, + "required": [ + "expression", + "file", + "line", + "callee" + ], + "type": "object" + }, + "type": "array" + } + }, + "required": [ + "summary" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "dynamicKeys": { + "description": "Dynamic expressions that could reach the requested keys.", + "items": { + "additionalProperties": false, + "properties": { + "expression": { + "description": "The key expression as written in source, e.g. \"`errors.${code}`\".", + "type": "string" + }, + "file": { + "description": "Source file, relative to the project directory. Absent for context-free bare candidates, which have no single call site.", + "type": "string" + }, + "line": { + "description": "1-based line number in that file. Absent with file.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + } + }, + "required": [ + "expression" + ], + "type": "object" + }, + "type": "array" + }, + "notFoundInCode": { + "description": "Requested keys with no reference anywhere in the scanned source.", + "items": { + "type": "string" + }, + "type": "array" + }, + "summary": { + "additionalProperties": false, + "description": "What the usage scan covered and what it found. This is what comes back when the full result is diverted to a file.", + "properties": { + "dirsScanned": { + "description": "Directories the scan walked.", + "items": { + "type": "string" + }, + "type": "array" + }, + "filesDeclined": { + "description": "Files a syntax frontend declined; pattern matching read them instead.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "filesScanned": { + "description": "Source files read.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "message": { + "description": "One sentence stating what the scan found.", + "type": "string" + }, + "totalReferences": { + "description": "Reference sites across every key.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "uniqueKeysFound": { + "description": "Distinct keys with at least one reference.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + } + }, + "required": [ + "uniqueKeysFound", + "totalReferences", + "filesScanned" + ], + "type": "object" + }, + "usages": { + "additionalProperties": { + "description": "Every place that key is referenced.", + "items": { + "additionalProperties": false, + "properties": { + "callee": { + "description": "The translation function the key was passed to, e.g. \"t\" or \"$t\".", + "type": "string" + }, + "file": { + "description": "Source file the key is referenced from, relative to the project directory.", + "type": "string" + }, + "line": { + "description": "1-based line number of the reference.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + } + }, + "required": [ + "file", + "line", + "callee" + ], + "type": "object" + }, + "type": "array" + }, + "description": "Key → its references in source. Only keys with at least one reference appear.", + "propertyNames": { + "description": "Dot-path key.", + "type": "string" + }, + "type": "object" + } + }, + "required": [ + "usages", + "summary" + ], + "type": "object" + } + ] + }, + { + "additionalProperties": false, + "properties": { + "reportFile": { + "description": "Absolute path the full JSON result was written to. Read the file for the findings; the summary below is all that came back.", + "type": "string" + }, + "summary": { + "anyOf": [ + { + "additionalProperties": false, + "description": "What the scan covered and what it found. This is what comes back when the full result is diverted to a file.", + "properties": { + "candidateOnlyCount": { + "description": "Keys kept alive only by the bare-candidate net.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "declaredCount": { + "description": "Keys withheld from the orphan list by a declaredNamespaces entry.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "dirsScanned": { + "description": "Directories the source scan walked.", + "items": { + "type": "string" + }, + "type": "array" + }, + "dynamicMatchedCount": { + "description": "Keys kept alive by a dynamic key expression rather than a literal call.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "filesDeclined": { + "description": "Files a syntax frontend declined; pattern matching read them instead.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "filesScanned": { + "description": "Source files read.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "ignoredCount": { + "description": "Keys excluded by an orphanScan ignorePattern.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "layersChecked": { + "description": "Layer names the scan covered.", + "items": { + "type": "string" + }, + "type": "array" + }, + "linkedCount": { + "description": "Keys withheld because another message's value links to them with @:. Protected in every layer, never deleted.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "locale": { + "description": "Locale the translation keys were read from.", + "type": "string" + }, + "message": { + "description": "One sentence stating what the scan found.", + "type": "string" + }, + "misplacedCount": { + "description": "Keys used only from apps that do not consume their layer. Never deleted.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "orphanCount": { + "description": "Keys nothing references. The counter the orphan gate reads.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "scanScope": { + "additionalProperties": { + "description": "Directories that layer was checked against.", + "items": { + "type": "string" + }, + "type": "array" + }, + "description": "Each layer's effective scope: the code of the apps that consume it.", + "propertyNames": { + "description": "Layer name.", + "type": "string" + }, + "type": "object" + }, + "totalKeys": { + "description": "Translation keys of the checked layers.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "uncertainCount": { + "description": "Keys with ambiguous usage evidence. Never deleted, in any mode.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "usedCount": { + "description": "Keys with usage evidence in a consuming app.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + } + }, + "required": [ + "totalKeys", + "orphanCount", + "filesScanned" + ], + "type": "object" + }, + { + "additionalProperties": false, + "description": "What the removal covered and what it deleted. This is what comes back when the full result is diverted to a file.", + "properties": { + "declaredCount": { + "description": "Keys withheld from the orphan list by a declaredNamespaces entry.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "dirsScanned": { + "description": "Directories the source scan walked.", + "items": { + "type": "string" + }, + "type": "array" + }, + "dryRun": { + "description": "True when nothing was deleted because a preview was asked for.", + "type": "boolean" + }, + "dynamicMatchedCount": { + "description": "Keys kept alive by a dynamic key expression rather than a literal call.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "filesScanned": { + "description": "Source files read.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "filesWritten": { + "description": "Locale files changed on disk.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "ignoredCount": { + "description": "Keys excluded by an orphanScan ignorePattern.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "layersChecked": { + "description": "Layer names the scan covered.", + "items": { + "type": "string" + }, + "type": "array" + }, + "linkedCount": { + "description": "Keys withheld because another message's value links to them with @:. Protected in every layer, never deleted.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "locale": { + "description": "Locale the translation keys were read from.", + "type": "string" + }, + "message": { + "description": "One sentence stating what the scan found.", + "type": "string" + }, + "misplacedCount": { + "description": "Keys used only from apps that do not consume their layer. Never deleted.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "orphanCount": { + "description": "Keys nothing references. The counter the orphan gate reads.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "remainingCount": { + "description": "Keys left in the layer after the removal.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "removedCount": { + "description": "Keys deleted from their layer.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "scanScope": { + "additionalProperties": { + "description": "Directories that layer was checked against.", + "items": { + "type": "string" + }, + "type": "array" + }, + "description": "Each layer's effective scope: the code of the apps that consume it.", + "propertyNames": { + "description": "Layer name.", + "type": "string" + }, + "type": "object" + }, + "totalKeys": { + "description": "Translation keys of the checked layers.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "uncertainCount": { + "description": "Keys with ambiguous usage evidence. Never deleted, in any mode.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "usedCount": { + "description": "Keys with usage evidence in a consuming app.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + } + }, + "required": [ + "totalKeys" + ], + "type": "object" + }, + { + "additionalProperties": false, + "description": "What the usage scan covered and what it found. This is what comes back when the full result is diverted to a file.", + "properties": { + "dirsScanned": { + "description": "Directories the scan walked.", + "items": { + "type": "string" + }, + "type": "array" + }, + "filesDeclined": { + "description": "Files a syntax frontend declined; pattern matching read them instead.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "filesScanned": { + "description": "Source files read.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "message": { + "description": "One sentence stating what the scan found.", + "type": "string" + }, + "totalReferences": { + "description": "Reference sites across every key.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "uniqueKeysFound": { + "description": "Distinct keys with at least one reference.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + } + }, + "required": [ + "uniqueKeysFound", + "totalReferences", + "filesScanned" + ], + "type": "object" + } + ], + "description": "The counts from the full result, so a caller can act on them without reading the file." + } + }, + "required": [ + "reportFile", + "summary" + ], + "type": "object" + } + ], + "type": "object" +}
- Changed
find_undefined_keys6 fields changed- added
Input schema / properties / layerAdded value: +{ + "description": "Layer to write the undefined keys into (e.g., \"root\", \"app-admin\"). Only read together with write, and only needed when the using code resolves against more than one layer. Call discover to list the layers.", + "type": "string" +} - changed
Input schema / properties / outputFile / descriptionPrevious value: -"Absolute path to write full JSON output. Returns only a compact summary to the caller — use this for large outputs to avoid flooding the conversation context. Example: \"/tmp/undefined-keys.json\""New value: +"Absolute path to write the full JSON output to. Only a compact summary is returned to the caller, which is what you want for a result too large to read in one piece. Example: \".i18n-reports/undefined-keys.json\"" - changed
Input schema / properties / projectDir / descriptionPrevious value: -"Absolute path to the Nuxt project root. Defaults to I18N_PROJECT_DIR, then server cwd. Example: \"/home/user/my-app\"."New value: +"Absolute path to the project root. Defaults to I18N_PROJECT_DIR, then server cwd. Example: \"/home/user/my-app\"." - changed
Input schema / properties / scanDirs / descriptionPrevious value: -"Absolute paths to directories to scan for source code usage. Overrides scope-aware scanning: every layer counts as resolvable from these dirs. Example: [\"/home/user/my-app/apps/admin\"]."New value: +"Absolute paths of the directories to scan for source usage. Overrides scope-aware scanning: every layer is then checked against these directories alone. Example: [\"/home/user/my-app/apps/admin\"]." - added
Input schema / properties / writeAdded value: +{ + "description": "Add every undefined key to a locale file, with an empty string as its value, in the project default locale only. Existing values are never touched, and uncertain findings are never written. The layer is the one the using code resolves against; when that is more than one layer, the run refuses and asks for a layer name. Default: false, which only reports.", + "type": "boolean" +} - changed
Output schema / (root)Previous value: -nullNew value: +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "anyOf": [ + { + "additionalProperties": false, + "properties": { + "limitation": { + "description": "What this scan cannot see, in one paragraph. Read it before acting on the uncertain findings.", + "type": "string" + }, + "summary": { + "additionalProperties": false, + "description": "What the scan covered and what it found. This is what comes back when the full result is diverted to a file.", + "properties": { + "declaredCount": { + "description": "Unresolvable keys covered by a declaredNamespaces entry — defined by contract, never written.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "filesDeclined": { + "description": "Files a syntax frontend declined; pattern matching read them instead.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "filesScanned": { + "description": "Source files read.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "ignoredCount": { + "description": "Unresolvable keys excluded by an orphanScan ignorePattern.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "locale": { + "description": "Locale the key definitions were resolved in.", + "type": "string" + }, + "message": { + "description": "One sentence stating what the scan found.", + "type": "string" + }, + "searchedLayersByApp": { + "additionalProperties": { + "description": "Layers searched for that unit's key usages.", + "items": { + "type": "string" + }, + "type": "array" + }, + "description": "What \"undefined\" meant per scan unit: a key defined only in a layer the unit does not consume is still undefined for it.", + "propertyNames": { + "description": "Scan unit name.", + "type": "string" + }, + "type": "object" + }, + "uncertainCount": { + "description": "Findings static extraction could not verify.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "undefinedCount": { + "description": "Keys that render raw at runtime, which is the counter the always-on gate reads. After a write run this counts the ones still undefined.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "usedKeysChecked": { + "description": "Distinct statically referenced keys across every scan unit.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "writtenCount": { + "description": "Keys extracted into a locale file. Present only alongside written.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + } + }, + "required": [ + "usedKeysChecked", + "undefinedCount", + "uncertainCount", + "ignoredCount", + "declaredCount", + "filesScanned", + "filesDeclined", + "locale", + "searchedLayersByApp", + "message" + ], + "type": "object" + }, + "uncertainKeys": { + "description": "Findings static extraction cannot verify. Never written, never counted by the gate.", + "items": { + "additionalProperties": false, + "properties": { + "app": { + "description": "Scan unit the usage lives in — an app name, a layer name, or \"project-root\".", + "type": "string" + }, + "key": { + "description": "The key source code calls and no consumed layer defines.", + "type": "string" + }, + "reason": { + "description": "Why this is not a hard finding — a dynamically built key, an existence check, a vendor namespace.", + "type": "string" + }, + "searchedLayers": { + "description": "Layers that unit can resolve keys from. All were searched.", + "items": { + "type": "string" + }, + "type": "array" + }, + "usages": { + "description": "Every call site of the key.", + "items": { + "additionalProperties": false, + "properties": { + "file": { + "description": "Source file, relative to the project directory.", + "type": "string" + }, + "line": { + "description": "1-based line number of the reference.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + } + }, + "required": [ + "file", + "line" + ], + "type": "object" + }, + "type": "array" + } + }, + "required": [ + "key", + "app", + "searchedLayers", + "usages", + "reason" + ], + "type": "object" + }, + "type": "array" + }, + "undefinedKeys": { + "description": "Hard findings: keys that render raw at runtime. A write run leaves them listed, because the call sites are what a reader has to visit either way.", + "items": { + "additionalProperties": false, + "properties": { + "app": { + "description": "Scan unit the usage lives in — an app name, a layer name, or \"project-root\".", + "type": "string" + }, + "key": { + "description": "The key source code calls and no consumed layer defines.", + "type": "string" + }, + "searchedLayers": { + "description": "Layers that unit can resolve keys from. All were searched.", + "items": { + "type": "string" + }, + "type": "array" + }, + "usages": { + "description": "Every call site of the key.", + "items": { + "additionalProperties": false, + "properties": { + "file": { + "description": "Source file, relative to the project directory.", + "type": "string" + }, + "line": { + "description": "1-based line number of the reference.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + } + }, + "required": [ + "file", + "line" + ], + "type": "object" + }, + "type": "array" + } + }, + "required": [ + "key", + "app", + "searchedLayers", + "usages" + ], + "type": "object" + }, + "type": "array" + }, + "written": { + "additionalProperties": false, + "description": "Present only when write was asked for and the scan found something to write.", + "properties": { + "keys": { + "description": "The keys that reached the file, alphabetically.", + "items": { + "type": "string" + }, + "type": "array" + }, + "layer": { + "description": "Layer the keys were added to.", + "type": "string" + }, + "locale": { + "description": "The project default locale — the only one written, and the source every other is filled from.", + "type": "string" + } + }, + "required": [ + "layer", + "locale", + "keys" + ], + "type": "object" + } + }, + "required": [ + "undefinedKeys", + "uncertainKeys", + "limitation", + "summary" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "reportFile": { + "description": "Absolute path the full JSON result was written to. Read the file for the findings; the summary below is all that came back.", + "type": "string" + }, + "summary": { + "additionalProperties": false, + "description": "The counts from the full result, so a caller can act on them without reading the file.", + "properties": { + "declaredCount": { + "description": "Unresolvable keys covered by a declaredNamespaces entry — defined by contract, never written.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "filesDeclined": { + "description": "Files a syntax frontend declined; pattern matching read them instead.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "filesScanned": { + "description": "Source files read.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "ignoredCount": { + "description": "Unresolvable keys excluded by an orphanScan ignorePattern.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "locale": { + "description": "Locale the key definitions were resolved in.", + "type": "string" + }, + "message": { + "description": "One sentence stating what the scan found.", + "type": "string" + }, + "searchedLayersByApp": { + "additionalProperties": { + "description": "Layers searched for that unit's key usages.", + "items": { + "type": "string" + }, + "type": "array" + }, + "description": "What \"undefined\" meant per scan unit: a key defined only in a layer the unit does not consume is still undefined for it.", + "propertyNames": { + "description": "Scan unit name.", + "type": "string" + }, + "type": "object" + }, + "uncertainCount": { + "description": "Findings static extraction could not verify.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "undefinedCount": { + "description": "Keys that render raw at runtime, which is the counter the always-on gate reads. After a write run this counts the ones still undefined.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "usedKeysChecked": { + "description": "Distinct statically referenced keys across every scan unit.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "writtenCount": { + "description": "Keys extracted into a locale file. Present only alongside written.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + } + }, + "required": [ + "usedKeysChecked", + "undefinedCount", + "uncertainCount", + "ignoredCount", + "declaredCount", + "filesScanned", + "filesDeclined", + "locale", + "searchedLayersByApp", + "message" + ], + "type": "object" + } + }, + "required": [ + "reportFile", + "summary" + ], + "type": "object" + } + ], + "type": "object" +}
- Changed
get_missing_translations6 fields changed- changed
Input schema / properties / layer / descriptionPrevious value: -"Layer name to scan (e.g., \"root\", \"app-admin\"). If omitted, scans all layers. Call discover to discover available layers."New value: +"Layer name to scope this to (e.g., \"root\", \"app-admin\"). If omitted, every layer is included. Call discover to list the layers." - added
Input schema / properties / limitAdded value: +{ + "description": "Maximum number of missing keys to return. Default: 100 for a tool call, unlimited at a terminal. When the cap applies the result carries truncated: true and nextOffset — call again with offset set to that value for the next page, or narrow the request instead.", + "maximum": 9007199254740991, + "minimum": 1, + "type": "integer" +} - added
Input schema / properties / offsetAdded value: +{ + "description": "Number of missing keys to skip before returning any. Default: 0. Pass the nextOffset of a truncated result to continue where it stopped.", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" +} - changed
Input schema / properties / outputFile / descriptionPrevious value: -"Absolute path to write full JSON output. Returns only a compact summary to the caller — use this for large outputs to avoid flooding the conversation context. Example: \"/tmp/missing-translations.json\""New value: +"Absolute path to write the full JSON output to. Only a compact summary is returned to the caller, which is what you want for a result too large to read in one piece. Example: \".i18n-reports/missing-translations.json\"" - changed
Input schema / properties / projectDir / descriptionPrevious value: -"Absolute path to the Nuxt project root. Defaults to I18N_PROJECT_DIR, then server cwd. Example: \"/home/user/my-app\"."New value: +"Absolute path to the project root. Defaults to I18N_PROJECT_DIR, then server cwd. Example: \"/home/user/my-app\"." - changed
Output schema / (root)Previous value: -nullNew value: +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "anyOf": [ + { + "additionalProperties": false, + "properties": { + "message": { + "description": "The step to take next — how to continue a capped read. Present when there is one.", + "type": "string" + }, + "missing": { + "additionalProperties": { + "additionalProperties": { + "description": "Dot-path keys of that layer.", + "items": { + "type": "string" + }, + "type": "array" + }, + "propertyNames": { + "description": "Layer name.", + "type": "string" + }, + "type": "object" + }, + "description": "Locale → layer → keys the reference locale defines and this locale does not. A locale with nothing missing is absent.", + "propertyNames": { + "description": "Locale code.", + "type": "string" + }, + "type": "object" + }, + "nextOffset": { + "description": "The offset to pass to continue where this result stopped. Present only when truncated.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "summary": { + "additionalProperties": false, + "description": "What was compared, and how much of it is missing. This is what comes back when the full result is diverted to a file.", + "properties": { + "layersScanned": { + "description": "Layer names the scan covered.", + "items": { + "type": "string" + }, + "type": "array" + }, + "message": { + "description": "The step to take next, as the surface the call ran on phrases it. Present when there is one.", + "type": "string" + }, + "referenceLocale": { + "anyOf": [ + { + "description": "Locale code, e.g. \"de\".", + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "code": { + "description": "Canonical locale code, e.g. \"en-US\". This is the spelling to pass back to another call.", + "type": "string" + }, + "file": { + "description": "Locale file name, e.g. \"en-US.json\". Absent for directory-per-locale layouts such as Laravel.", + "type": "string" + }, + "language": { + "description": "BCP-47 language tag, e.g. \"en-GB\". Absent when the framework config carries none.", + "type": "string" + }, + "name": { + "description": "Human-readable language name, e.g. \"Deutsch\". Absent unless the framework config names one.", + "type": "string" + } + }, + "required": [ + "code" + ], + "type": "object" + } + ], + "description": "The locale the missing keys were compared against." + }, + "targetLocales": { + "description": "The locales that were checked.", + "items": { + "anyOf": [ + { + "description": "Locale code, e.g. \"de\".", + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "code": { + "description": "Canonical locale code, e.g. \"en-US\". This is the spelling to pass back to another call.", + "type": "string" + }, + "file": { + "description": "Locale file name, e.g. \"en-US.json\". Absent for directory-per-locale layouts such as Laravel.", + "type": "string" + }, + "language": { + "description": "BCP-47 language tag, e.g. \"en-GB\". Absent when the framework config carries none.", + "type": "string" + }, + "name": { + "description": "Human-readable language name, e.g. \"Deutsch\". Absent unless the framework config names one.", + "type": "string" + } + }, + "required": [ + "code" + ], + "type": "object" + } + ], + "description": "The locale, either as a bare code or resolved to its code, language tag and file." + }, + "type": "array" + }, + "totalMissingKeys": { + "description": "Missing keys across every locale and layer. The counter the missing gate reads.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + } + }, + "required": [ + "referenceLocale", + "targetLocales", + "layersScanned", + "totalMissingKeys" + ], + "type": "object" + }, + "truncated": { + "description": "True when limit cut the result short. The totals still count everything.", + "type": "boolean" + } + }, + "required": [ + "truncated", + "missing", + "summary" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "reportFile": { + "description": "Absolute path the full JSON result was written to. Read the file for the findings; the summary below is all that came back.", + "type": "string" + }, + "summary": { + "additionalProperties": false, + "description": "The counts from the full result, so a caller can act on them without reading the file.", + "properties": { + "layersScanned": { + "description": "Layer names the scan covered.", + "items": { + "type": "string" + }, + "type": "array" + }, + "message": { + "description": "The step to take next, as the surface the call ran on phrases it. Present when there is one.", + "type": "string" + }, + "referenceLocale": { + "anyOf": [ + { + "description": "Locale code, e.g. \"de\".", + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "code": { + "description": "Canonical locale code, e.g. \"en-US\". This is the spelling to pass back to another call.", + "type": "string" + }, + "file": { + "description": "Locale file name, e.g. \"en-US.json\". Absent for directory-per-locale layouts such as Laravel.", + "type": "string" + }, + "language": { + "description": "BCP-47 language tag, e.g. \"en-GB\". Absent when the framework config carries none.", + "type": "string" + }, + "name": { + "description": "Human-readable language name, e.g. \"Deutsch\". Absent unless the framework config names one.", + "type": "string" + } + }, + "required": [ + "code" + ], + "type": "object" + } + ], + "description": "The locale the missing keys were compared against." + }, + "targetLocales": { + "description": "The locales that were checked.", + "items": { + "anyOf": [ + { + "description": "Locale code, e.g. \"de\".", + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "code": { + "description": "Canonical locale code, e.g. \"en-US\". This is the spelling to pass back to another call.", + "type": "string" + }, + "file": { + "description": "Locale file name, e.g. \"en-US.json\". Absent for directory-per-locale layouts such as Laravel.", + "type": "string" + }, + "language": { + "description": "BCP-47 language tag, e.g. \"en-GB\". Absent when the framework config carries none.", + "type": "string" + }, + "name": { + "description": "Human-readable language name, e.g. \"Deutsch\". Absent unless the framework config names one.", + "type": "string" + } + }, + "required": [ + "code" + ], + "type": "object" + } + ], + "description": "The locale, either as a bare code or resolved to its code, language tag and file." + }, + "type": "array" + }, + "totalMissingKeys": { + "description": "Missing keys across every locale and layer. The counter the missing gate reads.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + } + }, + "required": [ + "referenceLocale", + "targetLocales", + "layersScanned", + "totalMissingKeys" + ], + "type": "object" + } + }, + "required": [ + "reportFile", + "summary" + ], + "type": "object" + } + ], + "type": "object" +}
- Changed
get_translation_status5 fields changed- changed
Input schema / properties / layer / descriptionPrevious value: -"Layer name to scan (e.g., \"root\", \"app-admin\"). If omitted, scans all layers."New value: +"Layer name to scope this to (e.g., \"root\", \"app-admin\"). If omitted, every layer is included. Call discover to list the layers." - added
Input schema / properties / listEmptyAdded value: +{ + "description": "Also list the keys behind summary.emptyKeys under \"empty\" (locale → layer → keys), and keys that are empty in the reference locale itself under \"emptyInReference\" — useful after a scaffold or an interrupted translation run. Default: false, which returns counts only.", + "type": "boolean" +} - changed
Input schema / properties / outputFile / descriptionPrevious value: -"Absolute path to write the full per-locale and per-layer breakdown. Returns only the summary to the caller."New value: +"Absolute path to write the full JSON output to. Only a compact summary is returned to the caller, which is what you want for a result too large to read in one piece. Example: \".i18n-reports/translation-status.json\"" - changed
Input schema / properties / projectDir / descriptionPrevious value: -"Absolute path to the project root. Defaults to I18N_PROJECT_DIR, then server cwd."New value: +"Absolute path to the project root. Defaults to I18N_PROJECT_DIR, then server cwd. Example: \"/home/user/my-app\"." - changed
Output schema / (root)Previous value: -nullNew value: +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "anyOf": [ + { + "additionalProperties": false, + "properties": { + "empty": { + "additionalProperties": { + "additionalProperties": { + "description": "Dot-path keys of that layer.", + "items": { + "type": "string" + }, + "type": "array" + }, + "propertyNames": { + "description": "Layer name.", + "type": "string" + }, + "type": "object" + }, + "description": "Locale → layer → the keys behind summary.emptyKeys. Present only when the caller asked for them.", + "propertyNames": { + "description": "Locale code.", + "type": "string" + }, + "type": "object" + }, + "emptyInReference": { + "additionalProperties": { + "description": "Dot-path keys of that layer.", + "items": { + "type": "string" + }, + "type": "array" + }, + "description": "Layer → keys whose value is empty in the reference locale itself, so there is nothing to translate from. Present only alongside empty, and only when there are any.", + "propertyNames": { + "description": "Layer name.", + "type": "string" + }, + "type": "object" + }, + "layers": { + "description": "Coverage per layer, summed over the locales checked.", + "items": { + "additionalProperties": false, + "properties": { + "completion": { + "description": "translated ÷ total as a percentage, 0–100.", + "type": "number" + }, + "consumedBy": { + "description": "Apps whose declared layers include this one. Empty means either no app information exists or nothing consumes the layer.", + "items": { + "type": "string" + }, + "type": "array" + }, + "empty": { + "description": "Of those, the ones present with an empty-string value.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "layer": { + "description": "Layer name.", + "type": "string" + }, + "missing": { + "description": "Of those, the ones absent from the locale file.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "stale": { + "description": "Keys of this layer, over the locales checked, whose value was written from source text that has changed since. Present only when a translation memory lockfile exists.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "total": { + "description": "Keys the reference locale defines in this layer, times the locales checked.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "translated": { + "description": "Of those, the ones holding a non-empty value.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + } + }, + "required": [ + "layer", + "total", + "translated", + "missing", + "empty", + "completion", + "consumedBy" + ], + "type": "object" + }, + "type": "array" + }, + "locales": { + "description": "Coverage per locale, protected locales included and marked.", + "items": { + "additionalProperties": false, + "properties": { + "code": { + "description": "Canonical locale code, e.g. \"en-US\". This is the spelling to pass back to another call.", + "type": "string" + }, + "completion": { + "description": "translated ÷ total as a percentage, 0–100.", + "type": "number" + }, + "empty": { + "description": "Keys present with an empty-string value — scaffolded and never filled. They render as nothing, and are never reported as missing.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "excludedFromOverall": { + "const": true, + "description": "Present when the locale is kept out of summary.completionPercent, which is what protection means for the overall figure.", + "type": "boolean" + }, + "file": { + "description": "Locale file name, e.g. \"en-US.json\". Absent for directory-per-locale layouts such as Laravel.", + "type": "string" + }, + "language": { + "description": "BCP-47 language tag, e.g. \"en-GB\". Absent when the framework config carries none.", + "type": "string" + }, + "missing": { + "description": "Keys absent from this locale's files.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "name": { + "description": "Human-readable language name, e.g. \"Deutsch\". Absent unless the framework config names one.", + "type": "string" + }, + "protected": { + "const": true, + "description": "Present when the locale is listed in protectedLocales, so translation leaves it alone.", + "type": "boolean" + }, + "stale": { + "description": "Keys whose value was written from source text that has changed since, per the translation memory. Present only when a lockfile exists; translate with overwriteStale refreshes them.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "total": { + "description": "Keys the reference locale defines in the scanned layers.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "translated": { + "description": "Keys this locale holds a non-empty value for.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + } + }, + "required": [ + "code", + "total", + "translated", + "missing", + "empty", + "completion" + ], + "type": "object" + }, + "type": "array" + }, + "summary": { + "additionalProperties": false, + "description": "Project-wide coverage in one object. This is what comes back when the full result is diverted to a file.", + "properties": { + "completionPercent": { + "description": "Overall completion, 0–100, protected locales excluded. The counter the completion gate reads.", + "type": "number" + }, + "emptyKeys": { + "description": "Of those, the ones present with an empty-string value.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "layersScanned": { + "description": "Layer names the scan covered.", + "items": { + "type": "string" + }, + "type": "array" + }, + "localesChecked": { + "description": "Number of locales measured, protected ones included.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "missingKeys": { + "description": "Of those, the ones absent from their locale file.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "protectedLocales": { + "description": "Canonical codes kept out of the overall figure because they are maintained by hand.", + "items": { + "type": "string" + }, + "type": "array" + }, + "referenceLocale": { + "additionalProperties": false, + "description": "The locale coverage was measured against.", + "properties": { + "code": { + "description": "Canonical locale code, e.g. \"en-US\". This is the spelling to pass back to another call.", + "type": "string" + }, + "file": { + "description": "Locale file name, e.g. \"en-US.json\". Absent for directory-per-locale layouts such as Laravel.", + "type": "string" + }, + "language": { + "description": "BCP-47 language tag, e.g. \"en-GB\". Absent when the framework config carries none.", + "type": "string" + }, + "name": { + "description": "Human-readable language name, e.g. \"Deutsch\". Absent unless the framework config names one.", + "type": "string" + } + }, + "required": [ + "code" + ], + "type": "object" + }, + "staleCount": { + "description": "Of those, the ones written from source text that has changed since, per the translation memory. Present only when a lockfile exists.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "totalKeys": { + "description": "Keys expected across every checked locale and layer.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "translatedKeys": { + "description": "Of those, the ones holding a non-empty value.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "unconsumedLayers": { + "description": "Scanned layers no app consumes — keys nothing can render. Empty unless the project declares more than one app.", + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "referenceLocale", + "layersScanned", + "unconsumedLayers", + "localesChecked", + "protectedLocales", + "totalKeys", + "translatedKeys", + "missingKeys", + "emptyKeys", + "completionPercent" + ], + "type": "object" + } + }, + "required": [ + "locales", + "layers", + "summary" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "reportFile": { + "description": "Absolute path the full JSON result was written to. Read the file for the findings; the summary below is all that came back.", + "type": "string" + }, + "summary": { + "additionalProperties": false, + "description": "The counts from the full result, so a caller can act on them without reading the file.", + "properties": { + "completionPercent": { + "description": "Overall completion, 0–100, protected locales excluded. The counter the completion gate reads.", + "type": "number" + }, + "emptyKeys": { + "description": "Of those, the ones present with an empty-string value.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "layersScanned": { + "description": "Layer names the scan covered.", + "items": { + "type": "string" + }, + "type": "array" + }, + "localesChecked": { + "description": "Number of locales measured, protected ones included.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "missingKeys": { + "description": "Of those, the ones absent from their locale file.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "protectedLocales": { + "description": "Canonical codes kept out of the overall figure because they are maintained by hand.", + "items": { + "type": "string" + }, + "type": "array" + }, + "referenceLocale": { + "additionalProperties": false, + "description": "The locale coverage was measured against.", + "properties": { + "code": { + "description": "Canonical locale code, e.g. \"en-US\". This is the spelling to pass back to another call.", + "type": "string" + }, + "file": { + "description": "Locale file name, e.g. \"en-US.json\". Absent for directory-per-locale layouts such as Laravel.", + "type": "string" + }, + "language": { + "description": "BCP-47 language tag, e.g. \"en-GB\". Absent when the framework config carries none.", + "type": "string" + }, + "name": { + "description": "Human-readable language name, e.g. \"Deutsch\". Absent unless the framework config names one.", + "type": "string" + } + }, + "required": [ + "code" + ], + "type": "object" + }, + "staleCount": { + "description": "Of those, the ones written from source text that has changed since, per the translation memory. Present only when a lockfile exists.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "totalKeys": { + "description": "Keys expected across every checked locale and layer.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "translatedKeys": { + "description": "Of those, the ones holding a non-empty value.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "unconsumedLayers": { + "description": "Scanned layers no app consumes — keys nothing can render. Empty unless the project declares more than one app.", + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "referenceLocale", + "layersScanned", + "unconsumedLayers", + "localesChecked", + "protectedLocales", + "totalKeys", + "translatedKeys", + "missingKeys", + "emptyKeys", + "completionPercent" + ], + "type": "object" + } + }, + "required": [ + "reportFile", + "summary" + ], + "type": "object" + } + ], + "type": "object" +}
- Changed
get_translations9 fields changed- changed
Input schema / properties / compact / descriptionPrevious value: -"When true and locale is \"*\", returns a compact summary grouped by key instead of per-locale detail. Default: false."New value: +"When true and locale is \"*\", returns a summary grouped by key instead of per-locale detail. Default: false." - added
Input schema / properties / keyPrefixAdded value: +{ + "description": "Namespace to read every leaf key under, dots included: \"auth\" reads auth.login.title and everything else below auth. Either this or keys is required; passing both reads the union.", + "type": "string" +} - changed
Input schema / properties / keys / descriptionPrevious value: -"Dot-separated key paths to read. Example: [\"common.actions.save\", \"auth.login.title\"]."New value: +"Dot-separated key paths to read. Example: [\"common.actions.save\", \"auth.login.title\"]. Either this or keyPrefix is required." - changed
Input schema / properties / layer / descriptionPrevious value: -"Layer name from discover (e.g., \"root\", \"app-admin\"). Call discover to discover available layers."New value: +"Layer name from discover (e.g., \"root\", \"app-admin\"). Omit to read every layer and get the { byLayer } shape back." - added
Input schema / properties / limitAdded value: +{ + "description": "Maximum number of keys to return. Default: 100 for a tool call, unlimited at a terminal. When the cap applies the result carries truncated: true and nextOffset — call again with offset set to that value for the next page, or narrow the request instead.", + "maximum": 9007199254740991, + "minimum": 1, + "type": "integer" +} - added
Input schema / properties / offsetAdded value: +{ + "description": "Number of keys to skip before returning any. Default: 0. Pass the nextOffset of a truncated result to continue where it stopped.", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" +} - changed
Input schema / properties / projectDir / descriptionPrevious value: -"Absolute path to the Nuxt project root. Defaults to I18N_PROJECT_DIR, then server cwd. Example: \"/home/user/my-app\"."New value: +"Absolute path to the project root. Defaults to I18N_PROJECT_DIR, then server cwd. Example: \"/home/user/my-app\"." - changed
Input schema / requiredPrevious value: -[ - "layer", - "locale", - "keys" -]New value: +[ + "locale" +] - changed
Output schema / (root)Previous value: -nullNew value: +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "anyOf": [ + { + "additionalProperties": { + "additionalProperties": { + "description": "The value that locale holds, or null when the key is not defined there. In compact mode, a per-key digest: status (\"ok\" | \"partial\" | \"missing\"), totalPresent, and the locales the key is empty or missing in." + }, + "propertyNames": { + "description": "The dot-path key as it was requested, or the key being summarised in compact mode.", + "type": "string" + }, + "type": "object" + }, + "description": "Locale code → requested key → value. With compact and locale \"*\", one entry keyed \"byKey\" holding a digest per key instead.", + "propertyNames": { + "description": "Locale code, or \"byKey\" in compact mode.", + "type": "string" + }, + "type": "object" + }, + { + "additionalProperties": false, + "description": "The shape a read with no layer answers with.", + "properties": { + "byLayer": { + "additionalProperties": { + "additionalProperties": { + "additionalProperties": { + "description": "The value that locale holds, or null when the key is not defined there. In compact mode, a per-key digest: status (\"ok\" | \"partial\" | \"missing\"), totalPresent, and the locales the key is empty or missing in." + }, + "propertyNames": { + "description": "The dot-path key as it was requested, or the key being summarised in compact mode.", + "type": "string" + }, + "type": "object" + }, + "description": "Locale code → requested key → value. With compact and locale \"*\", one entry keyed \"byKey\" holding a digest per key instead.", + "propertyNames": { + "description": "Locale code, or \"byKey\" in compact mode.", + "type": "string" + }, + "type": "object" + }, + "description": "One entry per layer that defines at least one of the keys, each exactly what a read of that layer alone returns.", + "propertyNames": { + "description": "Layer name.", + "type": "string" + }, + "type": "object" + }, + "layersSearched": { + "description": "Every layer that was read, the ones defining none of the keys included.", + "items": { + "type": "string" + }, + "type": "array" + }, + "message": { + "description": "The step to take next — how to continue a capped read. Present when there is one.", + "type": "string" + }, + "nextOffset": { + "description": "The offset to pass to continue where this result stopped. Present only when truncated.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "truncated": { + "description": "True when limit cut the result short. The totals still count everything.", + "type": "boolean" + } + }, + "required": [ + "byLayer", + "layersSearched", + "truncated" + ], + "type": "object" + } + ], + "description": "With a layer: locale → key → value. Without one: the same per layer that defines the keys, under byLayer.", + "type": "object" +}
- Changed
list_namespaces5 fields changed- changed
Input schema / properties / layer / descriptionPrevious value: -"Layer name to filter by (e.g., \"root\", \"app-admin\"). If omitted or \"*\", scans all layers. Call discover to discover available layers."New value: +"Layer name to filter by (e.g., \"root\", \"app-admin\"). If omitted or \"*\", scans all layers. Call discover to list the layers." - added
Input schema / properties / limitAdded value: +{ + "description": "Maximum number of top-level namespaces to return. Default: 100 for a tool call, unlimited at a terminal. When the cap applies the result carries truncated: true and nextOffset — call again with offset set to that value for the next page, or narrow the request instead.", + "maximum": 9007199254740991, + "minimum": 1, + "type": "integer" +} - changed
Input schema / properties / locale / descriptionPrevious value: -"Locale to read keys from (e.g., \"en\"). Defaults to the project default locale. Keys are the same across locales — only one is needed."New value: +"Locale code to read from (e.g., \"en\", \"en-US\"). Defaults to the project default locale. Keys are the same across locales, so one is enough." - added
Input schema / properties / offsetAdded value: +{ + "description": "Number of top-level namespaces to skip before returning any. Default: 0. Pass the nextOffset of a truncated result to continue where it stopped.", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" +} - changed
Output schema / (root)Previous value: -nullNew value: +{ + "$defs": { + "__schema0": { + "additionalProperties": false, + "properties": { + "children": { + "additionalProperties": { + "$ref": "#/$defs/__schema0" + }, + "description": "Nested namespaces. Absent at a leaf, where the segment holds keys and no further nesting.", + "propertyNames": { + "description": "Next path segment.", + "type": "string" + }, + "type": "object" + }, + "keyCount": { + "description": "Translation keys under this namespace, including every nested one.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + } + }, + "required": [ + "keyCount" + ], + "type": "object" + } + }, + "$schema": "https://json-schema.org/draft/2020-12/schema", + "additionalProperties": false, + "properties": { + "layers": { + "additionalProperties": { + "additionalProperties": false, + "properties": { + "namespaces": { + "additionalProperties": { + "$ref": "#/$defs/__schema0" + }, + "description": "The key tree of that layer, one entry per top-level segment.", + "propertyNames": { + "description": "Top-level key segment, e.g. \"auth\".", + "type": "string" + }, + "type": "object" + } + }, + "required": [ + "namespaces" + ], + "type": "object" + }, + "description": "One entry per scanned layer. Alias layers are skipped.", + "propertyNames": { + "description": "Layer name.", + "type": "string" + }, + "type": "object" + }, + "message": { + "description": "The step to take next — how to continue a capped read. Present when there is one.", + "type": "string" + }, + "nextOffset": { + "description": "The offset to pass to continue where this result stopped. Present only when truncated.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "totalNamespaces": { + "description": "Top-level namespaces across every scanned layer, before limit. A namespace brings its whole subtree, so this is what limit counts.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "truncated": { + "description": "True when limit cut the result short. The totals still count everything.", + "type": "boolean" + } + }, + "required": [ + "layers", + "totalNamespaces", + "truncated" + ], + "type": "object" +}
- Changed
move_translation_key8 fields changed- changed
Input schema / properties / dryRun / descriptionPrevious value: -"When true, returns the plan without writing any files. Default: false."New value: +"Return the plan without writing any files. Default: false." - removed
Input schema / properties / fromLayerRemoved value: -{ - "description": "Layer the key lives in today, from discover. Example: \"app-admin\".", - "type": "string" -} - added
Input schema / properties / layerAdded value: +{ + "description": "Layer the key lives in today, from discover. Example: \"app-admin\".", + "type": "string" +} - changed
Input schema / properties / newKey / descriptionPrevious value: -"Key path in the target layer, when the move also renames it. Example: \"common.actions.save\". Defaults to the same path."New value: +"Key path to give it. Example: \"common.actions.save\". Omit to keep the current path, which then requires toLayer." - changed
Input schema / properties / projectDir / descriptionPrevious value: -"Absolute path to the project root. Defaults to I18N_PROJECT_DIR, then server cwd."New value: +"Absolute path to the project root. Defaults to I18N_PROJECT_DIR, then server cwd. Example: \"/home/user/my-app\"." - changed
Input schema / properties / toLayer / descriptionPrevious value: -"Layer to move it to, from discover. Example: \"root\". Must differ from fromLayer — to rename within one layer, use rename_translation_key."New value: +"Layer to move it to, from discover. Example: \"root\". Omit (or repeat layer) to rename the key within its current layer, which then requires newKey." - changed
Input schema / requiredPrevious value: -[ - "fromLayer", - "toLayer", - "key" -]New value: +[ + "layer", + "key" +] - changed
Output schema / (root)Previous value: -nullNew value: +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "anyOf": [ + { + "additionalProperties": false, + "properties": { + "conflictsInLocales": { + "description": "Locales where the destination holds a different value. Nothing is written at all when this is non-empty.", + "items": { + "type": "string" + }, + "type": "array" + }, + "deduplicatedLocales": { + "description": "Locales where the destination already held this value, so only the source copy was dropped.", + "items": { + "type": "string" + }, + "type": "array" + }, + "dryRun": { + "description": "True when nothing was written because a plan was asked for. Absent otherwise.", + "type": "boolean" + }, + "filesWritten": { + "description": "Number of locale files changed on disk. Absent on a dry run.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "fromLayer": { + "description": "Layer the key was moved out of.", + "type": "string" + }, + "key": { + "description": "The key as it was before the move.", + "type": "string" + }, + "message": { + "description": "The step to take next, as the surface the call ran on phrases it. Present only when there is no summary to carry it.", + "type": "string" + }, + "movedLocales": { + "description": "Locales whose value was written to the destination layer. Absent on a dry run.", + "items": { + "type": "string" + }, + "type": "array" + }, + "newKey": { + "description": "The key path it now has. Equal to key when only the layer changed.", + "type": "string" + }, + "notFoundInLocales": { + "description": "Locales whose source layer does not define the key at all.", + "items": { + "type": "string" + }, + "type": "array" + }, + "summary": { + "additionalProperties": false, + "description": "Counts of what the run did.", + "properties": { + "localesAffected": { + "description": "Number of locales whose files changed, or would change.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "message": { + "description": "One sentence stating what the run did.", + "type": "string" + }, + "warning": { + "description": "Present when the run wrote less than asked — a conflict, or a key some locales do not define.", + "type": "string" + } + }, + "required": [ + "localesAffected", + "message" + ], + "type": "object" + }, + "toLayer": { + "description": "Layer the key was moved into.", + "type": "string" + }, + "wouldMove": { + "description": "The plan, one entry per locale. Present only with dryRun.", + "items": { + "additionalProperties": false, + "properties": { + "action": { + "description": "\"move\" writes the target and drops the source; \"deduplicate\" finds the target already holding the same value, so only the source is dropped.", + "enum": [ + "move", + "deduplicate" + ], + "type": "string" + }, + "locale": { + "description": "Locale this entry is about.", + "type": "string" + }, + "value": { + "description": "The value that would be carried over." + } + }, + "required": [ + "locale", + "value", + "action" + ], + "type": "object" + }, + "type": "array" + } + }, + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "conflictsInLocales": { + "description": "Locales that already hold a different value under the new key.", + "items": { + "type": "string" + }, + "type": "array" + }, + "dryRun": { + "description": "True when nothing was written because a plan was asked for. Absent otherwise.", + "type": "boolean" + }, + "filesWritten": { + "description": "Number of locale files changed on disk. Absent on a dry run.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "message": { + "description": "The step to take next, as the surface the call ran on phrases it. Present only when there is no summary to carry it.", + "type": "string" + }, + "newKey": { + "description": "The key path after it.", + "type": "string" + }, + "notFoundInLocales": { + "description": "Locales that do not define the key at all.", + "items": { + "type": "string" + }, + "type": "array" + }, + "oldKey": { + "description": "The key path before the rename.", + "type": "string" + }, + "renamed": { + "description": "Locales whose file was rewritten with the new key. Absent on a dry run.", + "items": { + "type": "string" + }, + "type": "array" + }, + "skippedDueToConflict": { + "description": "Locales left untouched because of such a conflict.", + "items": { + "type": "string" + }, + "type": "array" + }, + "summary": { + "additionalProperties": false, + "description": "Counts of what the run did.", + "properties": { + "localesAffected": { + "description": "Number of locales whose files changed, or would change.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "message": { + "description": "One sentence stating what the run did.", + "type": "string" + }, + "warning": { + "description": "Present when the run wrote less than asked — a conflict, or a key some locales do not define.", + "type": "string" + } + }, + "required": [ + "localesAffected", + "message" + ], + "type": "object" + }, + "wouldRename": { + "description": "The plan, one entry per locale. Present only with dryRun.", + "items": { + "additionalProperties": false, + "properties": { + "locale": { + "description": "Locale this entry is about.", + "type": "string" + }, + "newKey": { + "description": "The key path it would get.", + "type": "string" + }, + "oldKey": { + "description": "The key path today.", + "type": "string" + }, + "value": { + "description": "The value that would move with it." + } + }, + "required": [ + "locale", + "oldKey", + "newKey", + "value" + ], + "type": "object" + }, + "type": "array" + } + }, + "type": "object" + } + ], + "type": "object" +}
- Removed
remove_orphan_keys - Changed
remove_translations4 fields changed- changed
Input schema / properties / dryRun / descriptionPrevious value: -"When true, returns a preview of what would be removed without writing any files. Default: false."New value: +"Return a preview of what would be removed without writing any files. Default: false." - changed
Input schema / properties / layer / descriptionPrevious value: -"Layer name from discover (e.g., \"root\", \"app-admin\"). The key will be removed from ALL locale files in this layer."New value: +"Layer name from discover (e.g., \"root\", \"app-admin\"). The keys are removed from ALL locale files in this layer." - changed
Input schema / properties / projectDir / descriptionPrevious value: -"Absolute path to the Nuxt project root. Defaults to I18N_PROJECT_DIR, then server cwd. Example: \"/home/user/my-app\"."New value: +"Absolute path to the project root. Defaults to I18N_PROJECT_DIR, then server cwd. Example: \"/home/user/my-app\"." - changed
Output schema / (root)Previous value: -nullNew value: +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "additionalProperties": false, + "properties": { + "dryRun": { + "description": "True when nothing was removed because a preview was asked for. Absent otherwise.", + "type": "boolean" + }, + "filesWritten": { + "description": "Number of locale files changed on disk. Absent on a dry run.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "message": { + "description": "The step to take next, as the surface the call ran on phrases it. Present only when there is no summary to carry it.", + "type": "string" + }, + "notFound": { + "description": "Requested keys no locale file of the layer defined. Absent when every key existed.", + "items": { + "type": "string" + }, + "type": "array" + }, + "removed": { + "description": "Dot-path keys removed from at least one locale file. Absent on a dry run.", + "items": { + "type": "string" + }, + "type": "array" + }, + "removedPerLocale": { + "description": "One \"locale:key\" entry per file-level removal. Absent on a dry run.", + "items": { + "type": "string" + }, + "type": "array" + }, + "summary": { + "additionalProperties": false, + "description": "Counts of what the run did. Absent on a dry run.", + "properties": { + "keysFound": { + "description": "Requested keys that existed and were removed.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "message": { + "description": "One sentence stating what the run did.", + "type": "string" + } + }, + "required": [ + "keysFound", + "message" + ], + "type": "object" + }, + "wouldRemove": { + "description": "What a dry run would remove. Present only with dryRun.", + "items": { + "additionalProperties": false, + "properties": { + "key": { + "description": "Dot-path key that would be removed.", + "type": "string" + }, + "locale": { + "description": "Locale the key would be removed from.", + "type": "string" + }, + "oldValue": { + "description": "The value that would be lost." + } + }, + "required": [ + "locale", + "key", + "oldValue" + ], + "type": "object" + }, + "type": "array" + } + }, + "type": "object" +}
- Removed
rename_translation_key - Changed
scaffold_locale4 fields changed- changed
Input schema / properties / dryRun / descriptionPrevious value: -"When true, returns what files would be created without writing them. Default: false."New value: +"Report the files that would be created without writing them. Default: false." - changed
Input schema / properties / layer / descriptionPrevious value: -"Scope scaffolding to a single layer (e.g., \"root\", \"app-admin\"). If omitted, scaffolds across all layers. Call discover to discover available layers."New value: +"Layer to scaffold in (e.g., \"root\", \"app-admin\"). If omitted, scaffolds across every layer." - changed
Input schema / properties / locales / descriptionPrevious value: -"Locale codes to scaffold empty files for (e.g., [\"sv\", \"ja\", \"pt-BR\"]). If omitted, auto-detects locales defined in config that are missing locale files."New value: +"Locale codes to scaffold empty files for (e.g., [\"sv\", \"ja\", \"pt-BR\"]). If omitted, auto-detects the locales the config declares but has no files for." - changed
Output schema / (root)Previous value: -nullNew value: +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "additionalProperties": false, + "properties": { + "created": { + "description": "Files that were created, or would be under dryRun.", + "items": { + "additionalProperties": false, + "properties": { + "file": { + "description": "Absolute path of the locale file.", + "type": "string" + }, + "keys": { + "description": "Keys copied from the default locale, all with an empty-string value.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "layer": { + "description": "Layer the file belongs to.", + "type": "string" + }, + "locale": { + "description": "Locale code the file is for.", + "type": "string" + }, + "namespace": { + "description": "Namespace this file holds, for a namespaced layout such as Laravel. Absent for a flat layout.", + "type": "string" + } + }, + "required": [ + "locale", + "layer", + "file", + "keys" + ], + "type": "object" + }, + "type": "array" + }, + "dryRun": { + "description": "True when nothing was written because a preview was asked for.", + "type": "boolean" + }, + "skipped": { + "description": "Files that already existed and were left alone.", + "items": { + "additionalProperties": false, + "properties": { + "file": { + "description": "Absolute path of the locale file.", + "type": "string" + }, + "keys": { + "description": "Keys copied from the default locale, all with an empty-string value.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "layer": { + "description": "Layer the file belongs to.", + "type": "string" + }, + "locale": { + "description": "Locale code the file is for.", + "type": "string" + }, + "namespace": { + "description": "Namespace this file holds, for a namespaced layout such as Laravel. Absent for a flat layout.", + "type": "string" + } + }, + "required": [ + "locale", + "layer", + "file", + "keys" + ], + "type": "object" + }, + "type": "array" + } + }, + "required": [ + "created", + "skipped", + "dryRun" + ], + "type": "object" +}
- Changed
search_translations11 fields changed- added
Input schema / properties / includeLocalesAdded value: +{ + "description": "Return one row per key and locale — layer, locale, key, value — instead of one row per key. Several times the output for the same findings, so ask for it when the per-locale values are what you are after. Default: false.", + "type": "boolean" +} - changed
Input schema / properties / layer / descriptionPrevious value: -"Layer name to search in (e.g., \"root\", \"app-admin\"), or \"*\" for all layers. If omitted, searches all layers. Call discover to discover available layers."New value: +"Layer name to search in (e.g., \"root\", \"app-admin\"), or \"*\" for all layers. If omitted, searches every layer." - added
Input schema / properties / limitAdded value: +{ + "description": "Maximum number of matching rows to return. Default: 100 for a tool call, unlimited at a terminal. When the cap applies the result carries truncated: true and nextOffset — call again with offset set to that value for the next page, or narrow the request instead.", + "maximum": 9007199254740991, + "minimum": 1, + "type": "integer" +} - changed
Input schema / properties / locale / descriptionPrevious value: -"Locale code to search in (e.g., \"en\", \"de\"). If omitted, searches all locales."New value: +"Locale code to search in (e.g., \"en\", \"de\"). If omitted, searches every locale." - added
Input schema / properties / matchModeAdded value: +{ + "description": "How query is compared. \"contains\" is a case-insensitive substring, over every locale searched. \"exact\" is the whole string, and \"fuzzy\" also accepts near-misses in wording, both ignoring case, accents, punctuation and whitespace and both comparing against one locale only — locale when given, otherwise the project default. Default: \"contains\".", + "enum": [ + "contains", + "exact", + "fuzzy" + ], + "type": "string" +} - added
Input schema / properties / offsetAdded value: +{ + "description": "Number of matching rows to skip before returning any. Default: 0. Pass the nextOffset of a truncated result to continue where it stopped.", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" +} - changed
Input schema / properties / outputFile / descriptionPrevious value: -"Absolute path to write full JSON output. Returns only a compact summary to the caller — use this for large outputs to avoid flooding the conversation context. Example: \"/tmp/search-results.json\""New value: +"Absolute path to write the full JSON output to. Only a compact summary is returned to the caller, which is what you want for a result too large to read in one piece. Example: \".i18n-reports/search-results.json\"" - changed
Input schema / properties / projectDir / descriptionPrevious value: -"Absolute path to the Nuxt project root. Defaults to I18N_PROJECT_DIR, then server cwd. Example: \"/home/user/my-app\"."New value: +"Absolute path to the project root. Defaults to I18N_PROJECT_DIR, then server cwd. Example: \"/home/user/my-app\"." - changed
Input schema / properties / query / descriptionPrevious value: -"Substring to search for. Matched against translation keys and/or string values. Case-insensitive. Example: \"save\" matches key \"common.actions.save\" or value \"Save changes\"."New value: +"Text to search for, matched against keys and/or values. Compared as a case-insensitive substring unless matchMode says otherwise. Example: \"save\" matches the key \"common.actions.save\" and the value \"Save changes\"." - changed
Input schema / properties / searchIn / descriptionPrevious value: -"Whether to search in translation keys, values, or both. Default: \"both\"."New value: +"Whether to search translation keys, values, or both. Default: \"both\"." - changed
Output schema / (root)Previous value: -nullNew value: +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "anyOf": [ + { + "additionalProperties": false, + "properties": { + "matches": { + "anyOf": [ + { + "items": { + "additionalProperties": false, + "properties": { + "key": { + "description": "The matching dot-path key.", + "type": "string" + }, + "layers": { + "description": "Every searched layer that defines it. More than one means the key is duplicated across layers.", + "items": { + "type": "string" + }, + "type": "array" + }, + "locale": { + "description": "Which locale value was read from: the reference locale where it defines the key, otherwise the first searched locale that does.", + "type": "string" + }, + "localeCount": { + "description": "How many of the searched locales define the key.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "value": { + "description": "What the locale named below holds for the key." + } + }, + "required": [ + "key", + "layers", + "value", + "locale", + "localeCount" + ], + "type": "object" + }, + "type": "array" + }, + { + "items": { + "additionalProperties": false, + "properties": { + "key": { + "description": "The matching dot-path key.", + "type": "string" + }, + "layer": { + "description": "Layer the match was found in.", + "type": "string" + }, + "locale": { + "description": "Locale the match was found in.", + "type": "string" + }, + "value": { + "description": "What that locale holds for the key." + } + }, + "required": [ + "layer", + "locale", + "key", + "value" + ], + "type": "object" + }, + "type": "array" + } + ], + "description": "One row per key by default; one row per key and locale when includeLocales was passed." + }, + "message": { + "description": "The step to take next — how to continue a capped read. Present when there is one.", + "type": "string" + }, + "nextOffset": { + "description": "The offset to pass to continue where this result stopped. Present only when truncated.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "totalMatches": { + "description": "Number of rows the search found, whichever shape they are in — before limit, so it exceeds the rows in matches when truncated.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "truncated": { + "description": "True when limit cut the result short. The totals still count everything.", + "type": "boolean" + } + }, + "required": [ + "matches", + "totalMatches", + "truncated" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "reportFile": { + "description": "Absolute path the full JSON result was written to. Read the file for the findings; the summary below is all that came back.", + "type": "string" + }, + "summary": { + "additionalProperties": false, + "description": "The counts from the full result, so a caller can act on them without reading the file.", + "properties": { + "nextOffset": { + "description": "The offset to pass to continue where this result stopped. Present only when truncated.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "totalMatches": { + "description": "Number of rows the search found, whichever shape they are in — before limit, so it exceeds the rows in matches when truncated.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "truncated": { + "const": true, + "description": "Present when the diverted search was cut by limit.", + "type": "boolean" + } + }, + "required": [ + "totalMatches" + ], + "type": "object" + } + }, + "required": [ + "reportFile", + "summary" + ], + "type": "object" + } + ], + "type": "object" +}
- Changed
translate_key9 fields changed- changed
Input schema / properties / dryRun / descriptionPrevious value: -"When true, previews source/target locales without writing files or calling the translation backend."New value: +"Report the source and target locales without writing files or calling the translation backend. Default: false." - changed
Input schema / properties / includePreview / descriptionPrevious value: -"When true, include translated values in output. Default false to keep responses compact."New value: +"Include the translated values in the result. Default: false, which keeps the response compact." - changed
Input schema / properties / layer / descriptionPrevious value: -"Layer name from discover to update (e.g., \"root\", \"app-admin\")."New value: +"Layer holding the key, from discover (e.g., \"root\", \"app-admin\")." - changed
Input schema / properties / overwrite / descriptionPrevious value: -"When true, overwrite existing target translations. When false, only fill missing targets. Default: true."New value: +"Overwrite existing target translations. When false, only missing targets are filled. Default: true." - changed
Input schema / properties / projectDir / descriptionPrevious value: -"Absolute path to the Nuxt project root. Defaults to I18N_PROJECT_DIR, then server cwd."New value: +"Absolute path to the project root. Defaults to I18N_PROJECT_DIR, then server cwd. Example: \"/home/user/my-app\"." - changed
Input schema / properties / sourceLocale / descriptionPrevious value: -"Source locale ref. May be code (\"en-us\"), language (\"en-US\"), or file (\"en-US.json\")."New value: +"Source locale ref. May be a code (\"en-us\"), a language (\"en-US\") or a file (\"en-US.json\")." - changed
Input schema / properties / sourceValue / descriptionPrevious value: -"Optional source value. If provided, source locale is added/updated before translating. If omitted, existing source value is read."New value: +"Source value to write before translating. If omitted, the existing source value is read." - changed
Input schema / properties / targetLocales / descriptionPrevious value: -"Target locales to translate into. Use \"all\" or omit for all locales except source."New value: +"Locales to translate into. Pass \"all\", or omit, for every locale except the source." - changed
Output schema / (root)Previous value: -nullNew value: +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "additionalProperties": false, + "properties": { + "dryRun": { + "description": "True when nothing was written because a preview was asked for.", + "type": "boolean" + }, + "failed": { + "description": "Locales the run attempted and lost.", + "items": { + "additionalProperties": false, + "properties": { + "detail": { + "description": "The underlying message, when there is one worth passing on.", + "type": "string" + }, + "locale": { + "description": "Locale that could not be translated.", + "type": "string" + }, + "reason": { + "anyOf": [ + { + "description": "Why the key could not be translated. A closed set — branch on the value.", + "enum": [ + "provider-error", + "omitted-by-model", + "placeholder-mismatch", + "plural-mismatch", + "write-error", + "truncated" + ], + "type": "string" + }, + { + "const": "read-error", + "type": "string" + } + ], + "description": "Why it failed. \"read-error\" means the locale file could not be read at all." + } + }, + "required": [ + "locale", + "reason" + ], + "type": "object" + }, + "type": "array" + }, + "fallbackContext": { + "additionalProperties": { + "description": "The value of that field, as the prompt builder produced it." + }, + "description": "Context to translate inline and persist with write_translations. Present only in agent mode.", + "propertyNames": { + "description": "Context field, e.g. \"keysToTranslate\", \"glossary\", \"instructions\".", + "type": "string" + }, + "type": "object" + }, + "filesWritten": { + "description": "Number of locale files changed on disk. 0 on a dry run.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "key": { + "description": "The key that was translated.", + "type": "string" + }, + "message": { + "description": "What to do next, when the run needs something from you — in agent mode, that the fallbackContext has to be translated and written back.", + "type": "string" + }, + "mode": { + "description": "How the run was executed: \"provider\" called the configured LLM, \"agent\" returned contexts to translate by hand, \"dry-run\" wrote nothing.", + "enum": [ + "provider", + "agent", + "dry-run" + ], + "type": "string" + }, + "model": { + "description": "Model that produced the translations. Absent outside provider mode.", + "type": "string" + }, + "placeholderValidation": { + "additionalProperties": false, + "description": "Placeholder comparison of the new values against the source value.", + "properties": { + "errors": { + "description": "One entry per translation that did not match. Empty when ok is true.", + "items": { + "additionalProperties": false, + "properties": { + "extra": { + "description": "Placeholders the translation invented and the source does not have.", + "items": { + "type": "string" + }, + "type": "array" + }, + "key": { + "description": "Dot-path key of the mismatching translation.", + "type": "string" + }, + "kind": { + "description": "What failed: a placeholder set mismatch (the default) or a vue-i18n plural variant-count mismatch.", + "enum": [ + "placeholder", + "plural-count" + ], + "type": "string" + }, + "locale": { + "description": "Locale code the mismatch was found in.", + "type": "string" + }, + "missing": { + "description": "Placeholders the source has and the translation dropped.", + "items": { + "type": "string" + }, + "type": "array" + }, + "sourceVariants": { + "description": "Plural variants in the source value. Present only for kind \"plural-count\".", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "targetVariants": { + "description": "Plural variants in the translated value. Present only for kind \"plural-count\".", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + } + }, + "required": [ + "locale", + "key", + "missing", + "extra" + ], + "type": "object" + }, + "type": "array" + }, + "ok": { + "description": "True when every checked translation carries the same placeholders as its source.", + "type": "boolean" + }, + "placeholders": { + "description": "Placeholder names found in the source values, e.g. [\"{name}\"].", + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "ok", + "placeholders", + "errors" + ], + "type": "object" + }, + "preview": { + "additionalProperties": { + "description": "The value written for that locale.", + "type": "string" + }, + "description": "The translated values. Present only when includePreview was passed.", + "propertyNames": { + "description": "Locale code.", + "type": "string" + }, + "type": "object" + }, + "skipped": { + "description": "Locales deliberately not translated.", + "items": { + "additionalProperties": false, + "properties": { + "locale": { + "description": "Locale that was left alone.", + "type": "string" + }, + "reason": { + "description": "Why the key or locale was deliberately not attempted. A closed set — branch on the value.", + "enum": [ + "no-provider", + "already-translated", + "protected-locale" + ], + "type": "string" + }, + "stale": { + "description": "Refines \"already-translated\": true when the existing value was written from source text that has changed since. Translation memory only.", + "type": "boolean" + } + }, + "required": [ + "locale", + "reason" + ], + "type": "object" + }, + "type": "array" + }, + "sourceLocale": { + "additionalProperties": false, + "description": "The locale the translation was made from.", + "properties": { + "code": { + "description": "Canonical locale code, e.g. \"en-US\". This is the spelling to pass back to another call.", + "type": "string" + }, + "file": { + "description": "Locale file name, e.g. \"en-US.json\". Absent for directory-per-locale layouts such as Laravel.", + "type": "string" + }, + "language": { + "description": "BCP-47 language tag, e.g. \"en-GB\". Absent when the framework config carries none.", + "type": "string" + }, + "name": { + "description": "Human-readable language name, e.g. \"Deutsch\". Absent unless the framework config names one.", + "type": "string" + } + }, + "required": [ + "code" + ], + "type": "object" + }, + "translated": { + "description": "Locales whose value was written.", + "items": { + "type": "string" + }, + "type": "array" + }, + "updatedSource": { + "description": "True when a sourceValue was written to the source locale before translating.", + "type": "boolean" + }, + "wouldTranslate": { + "description": "Locales a dry run would translate. Present only on a dry run.", + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "key", + "sourceLocale", + "updatedSource", + "mode", + "translated", + "skipped", + "failed", + "filesWritten", + "dryRun", + "placeholderValidation" + ], + "type": "object" +}
- Changed
translate_missing12 fields changed- changed
Input schema / properties / batchSize / descriptionPrevious value: -"Max keys per provider request (provider mode only). Default: 50. Lower values reduce per-batch risk but increase round trips."New value: +"Maximum number of keys per provider request. Default: 50. A lower value reduces per-batch risk and increases round trips." - added
Input schema / properties / batchSize / maximumAdded value: +9007199254740991 - added
Input schema / properties / batchSize / minimumAdded value: +1 - changed
Input schema / properties / batchSize / typePrevious value: -"number"New value: +"integer" - changed
Input schema / properties / compact / descriptionPrevious value: -"When true, returns a compact summary (totalTranslated, totalFailed, byLocale) instead of full per-locale results. Default: false."New value: +"Return a compact summary (totalTranslated, totalFailed, byLocale) instead of full per-locale results. Default: false." - changed
Input schema / properties / dryRun / descriptionPrevious value: -"When true, returns which keys would be translated without calling the LLM or writing files. Default: false."New value: +"Return which keys would be translated without calling the provider or writing files. Default: false." - changed
Input schema / properties / keys / descriptionPrevious value: -"Specific dot-path keys to translate (e.g., [\"auth.login.title\", \"common.save\"]). If omitted, translates all missing keys in the layer."New value: +"Dot-path keys to translate (e.g., [\"auth.login.title\", \"common.save\"]). If omitted, translates every missing key in the layer." - changed
Input schema / properties / layer / descriptionPrevious value: -"Layer name from discover to translate (e.g., \"root\", \"app-admin\"). Omit to translate every locale-backed layer in one call — the recommended default for layered projects, which returns a result per layer plus an aggregated summary."New value: +"Layer to translate (e.g., \"root\", \"app-admin\"). Omit to translate every locale-backed layer in one call — the recommended default for layered projects, which returns a result per layer plus an aggregated summary." - added
Input schema / properties / overwriteStaleAdded value: +{ + "description": "Also re-translate keys whose target value was written from source text that has changed since. Needs the translation memory, which is on unless translationMemory is false in the project config — without it nothing is known to be stale and this changes nothing. Default: false, which reports those keys under \"stale\" and leaves their values alone.", + "type": "boolean" +} - changed
Input schema / properties / projectDir / descriptionPrevious value: -"Absolute path to the Nuxt project root. Defaults to I18N_PROJECT_DIR, then server cwd. Example: \"/home/user/my-app\"."New value: +"Absolute path to the project root. Defaults to I18N_PROJECT_DIR, then server cwd. Example: \"/home/user/my-app\"." - changed
Input schema / properties / referenceLocale / descriptionPrevious value: -"Locale code used as translation source (e.g., \"en\", \"en-US\"). Defaults to the project default locale."New value: +"Locale code used as the translation source (e.g., \"en\", \"en-US\"). Defaults to the project default locale." - changed
Output schema / (root)Previous value: -nullNew value: +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "anyOf": [ + { + "additionalProperties": false, + "properties": { + "fallbackContexts": { + "additionalProperties": { + "additionalProperties": { + "description": "The value of that field, as the prompt builder produced it." + }, + "propertyNames": { + "description": "Context field, e.g. \"keysToTranslate\", \"glossary\", \"instructions\".", + "type": "string" + }, + "type": "object" + }, + "description": "Per-locale context to translate inline and persist with write_translations. Present only in agent mode.", + "propertyNames": { + "description": "Target locale code.", + "type": "string" + }, + "type": "object" + }, + "results": { + "additionalProperties": { + "additionalProperties": false, + "properties": { + "batches": { + "description": "Provider requests this locale took. Absent outside provider mode.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "failed": { + "description": "Keys the run attempted and lost. They are still missing; a re-run retries them.", + "items": { + "additionalProperties": false, + "properties": { + "key": { + "description": "The key that could not be translated.", + "type": "string" + }, + "reason": { + "description": "Why the key could not be translated. A closed set — branch on the value.", + "enum": [ + "provider-error", + "omitted-by-model", + "placeholder-mismatch", + "plural-mismatch", + "write-error", + "truncated" + ], + "type": "string" + } + }, + "required": [ + "key", + "reason" + ], + "type": "object" + }, + "type": "array" + }, + "missing": { + "description": "Keys missing for this locale. Always equals translated + wouldTranslate + failed + skipped.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "mode": { + "description": "How the run was executed: \"provider\" called the configured LLM, \"agent\" returned contexts to translate by hand, \"dry-run\" wrote nothing.", + "enum": [ + "provider", + "agent", + "dry-run" + ], + "type": "string" + }, + "model": { + "description": "Model that produced the translations. Absent outside provider mode.", + "type": "string" + }, + "placeholderValidation": { + "additionalProperties": false, + "description": "Placeholder comparison of the new values against their source. Absent when nothing was translated.", + "properties": { + "errors": { + "description": "One entry per translation that did not match. Empty when ok is true.", + "items": { + "additionalProperties": false, + "properties": { + "extra": { + "description": "Placeholders the translation invented and the source does not have.", + "items": { + "type": "string" + }, + "type": "array" + }, + "key": { + "description": "Dot-path key of the mismatching translation.", + "type": "string" + }, + "kind": { + "description": "What failed: a placeholder set mismatch (the default) or a vue-i18n plural variant-count mismatch.", + "enum": [ + "placeholder", + "plural-count" + ], + "type": "string" + }, + "locale": { + "description": "Locale code the mismatch was found in.", + "type": "string" + }, + "missing": { + "description": "Placeholders the source has and the translation dropped.", + "items": { + "type": "string" + }, + "type": "array" + }, + "sourceVariants": { + "description": "Plural variants in the source value. Present only for kind \"plural-count\".", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "targetVariants": { + "description": "Plural variants in the translated value. Present only for kind \"plural-count\".", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + } + }, + "required": [ + "locale", + "key", + "missing", + "extra" + ], + "type": "object" + }, + "type": "array" + }, + "ok": { + "description": "True when every checked translation carries the same placeholders as its source.", + "type": "boolean" + }, + "placeholders": { + "description": "Placeholder names found in the source values, e.g. [\"{name}\"].", + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "ok", + "placeholders", + "errors" + ], + "type": "object" + }, + "skipped": { + "description": "Keys deliberately not attempted.", + "items": { + "additionalProperties": false, + "properties": { + "key": { + "description": "The key that was not attempted.", + "type": "string" + }, + "reason": { + "description": "Why the key or locale was deliberately not attempted. A closed set — branch on the value.", + "enum": [ + "no-provider", + "already-translated", + "protected-locale" + ], + "type": "string" + } + }, + "required": [ + "key", + "reason" + ], + "type": "object" + }, + "type": "array" + }, + "stale": { + "description": "Keys whose target was written from source text that has changed since, left untouched by this run. Translation memory only, and outside the missing invariant — these keys are translated, just outdated.", + "items": { + "type": "string" + }, + "type": "array" + }, + "translated": { + "description": "Keys translated and written.", + "items": { + "type": "string" + }, + "type": "array" + }, + "wouldTranslate": { + "description": "Keys a dry run would translate. Present only on a dry run.", + "items": { + "type": "string" + }, + "type": "array" + }, + "writeError": { + "description": "Present when translations were produced but writing the locale file failed.", + "type": "string" + } + }, + "required": [ + "mode", + "missing", + "translated", + "failed", + "skipped" + ], + "type": "object" + }, + "description": "Full per-locale results. Absent in compact mode, which returns summary.byLocale instead.", + "propertyNames": { + "description": "Target locale code.", + "type": "string" + }, + "type": "object" + }, + "summary": { + "additionalProperties": false, + "description": "What the run did across every locale of the layer.", + "properties": { + "byLocale": { + "description": "A per-locale digest in place of full results. Present only in compact mode.", + "items": { + "additionalProperties": false, + "properties": { + "batches": { + "description": "Provider requests this locale took. Absent outside provider mode.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "failed": { + "description": "Of those, the ones attempted and lost.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "locale": { + "description": "Locale code this digest is about.", + "type": "string" + }, + "missing": { + "description": "Keys missing for this locale.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "mode": { + "description": "How the run was executed: \"provider\" called the configured LLM, \"agent\" returned contexts to translate by hand, \"dry-run\" wrote nothing.", + "enum": [ + "provider", + "agent", + "dry-run" + ], + "type": "string" + }, + "model": { + "description": "Model that produced the translations. Absent outside provider mode.", + "type": "string" + }, + "skipped": { + "description": "Of those, the ones deliberately not attempted.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "stale": { + "description": "Keys left untouched as outdated. Translation memory only.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "translated": { + "description": "Of those, the ones translated and written.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "wouldTranslate": { + "description": "Keys a dry run would translate. Present only on a dry run.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "writeError": { + "description": "Present when writing the locale file failed.", + "type": "string" + } + }, + "required": [ + "locale", + "mode", + "missing", + "translated", + "failed", + "skipped" + ], + "type": "object" + }, + "type": "array" + }, + "dryRun": { + "description": "True when nothing was written because a preview was asked for.", + "type": "boolean" + }, + "layer": { + "description": "The layer that was translated.", + "type": "string" + }, + "message": { + "description": "What to do next: how to persist the fallback contexts in agent mode, or which locales lost keys after a partial failure. Absent when a run needs nothing from you.", + "type": "string" + }, + "mode": { + "description": "How the run was executed: \"provider\" called the configured LLM, \"agent\" returned contexts to translate by hand, \"dry-run\" wrote nothing.", + "enum": [ + "provider", + "agent", + "dry-run" + ], + "type": "string" + }, + "referenceLocale": { + "anyOf": [ + { + "description": "Locale code, e.g. \"de\".", + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "code": { + "description": "Canonical locale code, e.g. \"en-US\". This is the spelling to pass back to another call.", + "type": "string" + }, + "file": { + "description": "Locale file name, e.g. \"en-US.json\". Absent for directory-per-locale layouts such as Laravel.", + "type": "string" + }, + "language": { + "description": "BCP-47 language tag, e.g. \"en-GB\". Absent when the framework config carries none.", + "type": "string" + }, + "name": { + "description": "Human-readable language name, e.g. \"Deutsch\". Absent unless the framework config names one.", + "type": "string" + } + }, + "required": [ + "code" + ], + "type": "object" + } + ], + "description": "The locale the translations were made from." + }, + "staleCount": { + "description": "Stale keys left untouched across every locale. Translation memory only.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "targetLocales": { + "description": "The locales that were translated into.", + "items": { + "anyOf": [ + { + "description": "Locale code, e.g. \"de\".", + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "code": { + "description": "Canonical locale code, e.g. \"en-US\". This is the spelling to pass back to another call.", + "type": "string" + }, + "file": { + "description": "Locale file name, e.g. \"en-US.json\". Absent for directory-per-locale layouts such as Laravel.", + "type": "string" + }, + "language": { + "description": "BCP-47 language tag, e.g. \"en-GB\". Absent when the framework config carries none.", + "type": "string" + }, + "name": { + "description": "Human-readable language name, e.g. \"Deutsch\". Absent unless the framework config names one.", + "type": "string" + } + }, + "required": [ + "code" + ], + "type": "object" + } + ], + "description": "The locale, either as a bare code or resolved to its code, language tag and file." + }, + "type": "array" + }, + "totalFailed": { + "description": "Keys attempted and lost across every locale. The counter the translate gate reads.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "totalSkipped": { + "description": "Keys deliberately not attempted across every locale.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "totalTranslated": { + "description": "Keys translated and written across every locale.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "totalWouldTranslate": { + "description": "Keys a dry run would translate. Present only on a dry run.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + } + }, + "required": [ + "mode", + "totalTranslated", + "totalFailed", + "totalSkipped", + "layer", + "referenceLocale", + "targetLocales", + "dryRun" + ], + "type": "object" + } + }, + "required": [ + "summary" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "layers": { + "additionalProperties": { + "additionalProperties": false, + "properties": { + "fallbackContexts": { + "additionalProperties": { + "additionalProperties": { + "description": "The value of that field, as the prompt builder produced it." + }, + "propertyNames": { + "description": "Context field, e.g. \"keysToTranslate\", \"glossary\", \"instructions\".", + "type": "string" + }, + "type": "object" + }, + "description": "Per-locale context to translate inline and persist with write_translations. Present only in agent mode.", + "propertyNames": { + "description": "Target locale code.", + "type": "string" + }, + "type": "object" + }, + "results": { + "additionalProperties": { + "additionalProperties": false, + "properties": { + "batches": { + "description": "Provider requests this locale took. Absent outside provider mode.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "failed": { + "description": "Keys the run attempted and lost. They are still missing; a re-run retries them.", + "items": { + "additionalProperties": false, + "properties": { + "key": { + "description": "The key that could not be translated.", + "type": "string" + }, + "reason": { + "description": "Why the key could not be translated. A closed set — branch on the value.", + "enum": [ + "provider-error", + "omitted-by-model", + "placeholder-mismatch", + "plural-mismatch", + "write-error", + "truncated" + ], + "type": "string" + } + }, + "required": [ + "key", + "reason" + ], + "type": "object" + }, + "type": "array" + }, + "missing": { + "description": "Keys missing for this locale. Always equals translated + wouldTranslate + failed + skipped.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "mode": { + "description": "How the run was executed: \"provider\" called the configured LLM, \"agent\" returned contexts to translate by hand, \"dry-run\" wrote nothing.", + "enum": [ + "provider", + "agent", + "dry-run" + ], + "type": "string" + }, + "model": { + "description": "Model that produced the translations. Absent outside provider mode.", + "type": "string" + }, + "placeholderValidation": { + "additionalProperties": false, + "description": "Placeholder comparison of the new values against their source. Absent when nothing was translated.", + "properties": { + "errors": { + "description": "One entry per translation that did not match. Empty when ok is true.", + "items": { + "additionalProperties": false, + "properties": { + "extra": { + "description": "Placeholders the translation invented and the source does not have.", + "items": { + "type": "string" + }, + "type": "array" + }, + "key": { + "description": "Dot-path key of the mismatching translation.", + "type": "string" + }, + "kind": { + "description": "What failed: a placeholder set mismatch (the default) or a vue-i18n plural variant-count mismatch.", + "enum": [ + "placeholder", + "plural-count" + ], + "type": "string" + }, + "locale": { + "description": "Locale code the mismatch was found in.", + "type": "string" + }, + "missing": { + "description": "Placeholders the source has and the translation dropped.", + "items": { + "type": "string" + }, + "type": "array" + }, + "sourceVariants": { + "description": "Plural variants in the source value. Present only for kind \"plural-count\".", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "targetVariants": { + "description": "Plural variants in the translated value. Present only for kind \"plural-count\".", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + } + }, + "required": [ + "locale", + "key", + "missing", + "extra" + ], + "type": "object" + }, + "type": "array" + }, + "ok": { + "description": "True when every checked translation carries the same placeholders as its source.", + "type": "boolean" + }, + "placeholders": { + "description": "Placeholder names found in the source values, e.g. [\"{name}\"].", + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "ok", + "placeholders", + "errors" + ], + "type": "object" + }, + "skipped": { + "description": "Keys deliberately not attempted.", + "items": { + "additionalProperties": false, + "properties": { + "key": { + "description": "The key that was not attempted.", + "type": "string" + }, + "reason": { + "description": "Why the key or locale was deliberately not attempted. A closed set — branch on the value.", + "enum": [ + "no-provider", + "already-translated", + "protected-locale" + ], + "type": "string" + } + }, + "required": [ + "key", + "reason" + ], + "type": "object" + }, + "type": "array" + }, + "stale": { + "description": "Keys whose target was written from source text that has changed since, left untouched by this run. Translation memory only, and outside the missing invariant — these keys are translated, just outdated.", + "items": { + "type": "string" + }, + "type": "array" + }, + "translated": { + "description": "Keys translated and written.", + "items": { + "type": "string" + }, + "type": "array" + }, + "wouldTranslate": { + "description": "Keys a dry run would translate. Present only on a dry run.", + "items": { + "type": "string" + }, + "type": "array" + }, + "writeError": { + "description": "Present when translations were produced but writing the locale file failed.", + "type": "string" + } + }, + "required": [ + "mode", + "missing", + "translated", + "failed", + "skipped" + ], + "type": "object" + }, + "description": "Full per-locale results. Absent in compact mode, which returns summary.byLocale instead.", + "propertyNames": { + "description": "Target locale code.", + "type": "string" + }, + "type": "object" + }, + "summary": { + "additionalProperties": false, + "description": "What the run did across every locale of the layer.", + "properties": { + "byLocale": { + "description": "A per-locale digest in place of full results. Present only in compact mode.", + "items": { + "additionalProperties": false, + "properties": { + "batches": { + "description": "Provider requests this locale took. Absent outside provider mode.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "failed": { + "description": "Of those, the ones attempted and lost.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "locale": { + "description": "Locale code this digest is about.", + "type": "string" + }, + "missing": { + "description": "Keys missing for this locale.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "mode": { + "description": "How the run was executed: \"provider\" called the configured LLM, \"agent\" returned contexts to translate by hand, \"dry-run\" wrote nothing.", + "enum": [ + "provider", + "agent", + "dry-run" + ], + "type": "string" + }, + "model": { + "description": "Model that produced the translations. Absent outside provider mode.", + "type": "string" + }, + "skipped": { + "description": "Of those, the ones deliberately not attempted.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "stale": { + "description": "Keys left untouched as outdated. Translation memory only.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "translated": { + "description": "Of those, the ones translated and written.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "wouldTranslate": { + "description": "Keys a dry run would translate. Present only on a dry run.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "writeError": { + "description": "Present when writing the locale file failed.", + "type": "string" + } + }, + "required": [ + "locale", + "mode", + "missing", + "translated", + "failed", + "skipped" + ], + "type": "object" + }, + "type": "array" + }, + "dryRun": { + "description": "True when nothing was written because a preview was asked for.", + "type": "boolean" + }, + "layer": { + "description": "The layer that was translated.", + "type": "string" + }, + "message": { + "description": "What to do next: how to persist the fallback contexts in agent mode, or which locales lost keys after a partial failure. Absent when a run needs nothing from you.", + "type": "string" + }, + "mode": { + "description": "How the run was executed: \"provider\" called the configured LLM, \"agent\" returned contexts to translate by hand, \"dry-run\" wrote nothing.", + "enum": [ + "provider", + "agent", + "dry-run" + ], + "type": "string" + }, + "referenceLocale": { + "anyOf": [ + { + "description": "Locale code, e.g. \"de\".", + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "code": { + "description": "Canonical locale code, e.g. \"en-US\". This is the spelling to pass back to another call.", + "type": "string" + }, + "file": { + "description": "Locale file name, e.g. \"en-US.json\". Absent for directory-per-locale layouts such as Laravel.", + "type": "string" + }, + "language": { + "description": "BCP-47 language tag, e.g. \"en-GB\". Absent when the framework config carries none.", + "type": "string" + }, + "name": { + "description": "Human-readable language name, e.g. \"Deutsch\". Absent unless the framework config names one.", + "type": "string" + } + }, + "required": [ + "code" + ], + "type": "object" + } + ], + "description": "The locale the translations were made from." + }, + "staleCount": { + "description": "Stale keys left untouched across every locale. Translation memory only.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "targetLocales": { + "description": "The locales that were translated into.", + "items": { + "anyOf": [ + { + "description": "Locale code, e.g. \"de\".", + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "code": { + "description": "Canonical locale code, e.g. \"en-US\". This is the spelling to pass back to another call.", + "type": "string" + }, + "file": { + "description": "Locale file name, e.g. \"en-US.json\". Absent for directory-per-locale layouts such as Laravel.", + "type": "string" + }, + "language": { + "description": "BCP-47 language tag, e.g. \"en-GB\". Absent when the framework config carries none.", + "type": "string" + }, + "name": { + "description": "Human-readable language name, e.g. \"Deutsch\". Absent unless the framework config names one.", + "type": "string" + } + }, + "required": [ + "code" + ], + "type": "object" + } + ], + "description": "The locale, either as a bare code or resolved to its code, language tag and file." + }, + "type": "array" + }, + "totalFailed": { + "description": "Keys attempted and lost across every locale. The counter the translate gate reads.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "totalSkipped": { + "description": "Keys deliberately not attempted across every locale.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "totalTranslated": { + "description": "Keys translated and written across every locale.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "totalWouldTranslate": { + "description": "Keys a dry run would translate. Present only on a dry run.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + } + }, + "required": [ + "mode", + "totalTranslated", + "totalFailed", + "totalSkipped", + "layer", + "referenceLocale", + "targetLocales", + "dryRun" + ], + "type": "object" + } + }, + "required": [ + "summary" + ], + "type": "object" + }, + "description": "One full result per locale-backed layer.", + "propertyNames": { + "description": "Layer name.", + "type": "string" + }, + "type": "object" + }, + "summary": { + "additionalProperties": false, + "description": "What the run did across every layer and locale, with the per-layer split under byLayer.", + "properties": { + "byLayer": { + "description": "The same totals split per layer.", + "items": { + "additionalProperties": false, + "properties": { + "layer": { + "description": "Layer these totals are for.", + "type": "string" + }, + "totalFailed": { + "description": "Keys attempted and lost in that layer.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "totalSkipped": { + "description": "Keys deliberately not attempted in that layer.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "totalTranslated": { + "description": "Keys translated and written in that layer.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "totalWouldTranslate": { + "description": "Keys a dry run would translate. 0 outside a dry run.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + } + }, + "required": [ + "layer", + "totalTranslated", + "totalFailed", + "totalSkipped", + "totalWouldTranslate" + ], + "type": "object" + }, + "type": "array" + }, + "dryRun": { + "description": "True when nothing was written because a preview was asked for.", + "type": "boolean" + }, + "layers": { + "description": "Layer names that were translated.", + "items": { + "type": "string" + }, + "type": "array" + }, + "message": { + "description": "What to do next: how to persist the fallback contexts in agent mode, or which locales lost keys after a partial failure. Absent when a run needs nothing from you.", + "type": "string" + }, + "mode": { + "description": "How the run was executed: \"provider\" called the configured LLM, \"agent\" returned contexts to translate by hand, \"dry-run\" wrote nothing.", + "enum": [ + "provider", + "agent", + "dry-run" + ], + "type": "string" + }, + "referenceLocale": { + "anyOf": [ + { + "description": "Locale code, e.g. \"de\".", + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "code": { + "description": "Canonical locale code, e.g. \"en-US\". This is the spelling to pass back to another call.", + "type": "string" + }, + "file": { + "description": "Locale file name, e.g. \"en-US.json\". Absent for directory-per-locale layouts such as Laravel.", + "type": "string" + }, + "language": { + "description": "BCP-47 language tag, e.g. \"en-GB\". Absent when the framework config carries none.", + "type": "string" + }, + "name": { + "description": "Human-readable language name, e.g. \"Deutsch\". Absent unless the framework config names one.", + "type": "string" + } + }, + "required": [ + "code" + ], + "type": "object" + } + ], + "description": "The locale the translations were made from." + }, + "staleCount": { + "description": "Stale keys left untouched across every layer and locale. Translation memory only.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "targetLocales": { + "description": "The locales that were translated into.", + "items": { + "anyOf": [ + { + "description": "Locale code, e.g. \"de\".", + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "code": { + "description": "Canonical locale code, e.g. \"en-US\". This is the spelling to pass back to another call.", + "type": "string" + }, + "file": { + "description": "Locale file name, e.g. \"en-US.json\". Absent for directory-per-locale layouts such as Laravel.", + "type": "string" + }, + "language": { + "description": "BCP-47 language tag, e.g. \"en-GB\". Absent when the framework config carries none.", + "type": "string" + }, + "name": { + "description": "Human-readable language name, e.g. \"Deutsch\". Absent unless the framework config names one.", + "type": "string" + } + }, + "required": [ + "code" + ], + "type": "object" + } + ], + "description": "The locale, either as a bare code or resolved to its code, language tag and file." + }, + "type": "array" + }, + "totalFailed": { + "description": "Keys attempted and lost across every layer and locale. The counter the translate gate reads.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "totalSkipped": { + "description": "Keys deliberately not attempted across every layer and locale.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "totalTranslated": { + "description": "Keys translated and written across every layer and locale.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "totalWouldTranslate": { + "description": "Keys a dry run would translate. Present only on a dry run.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + } + }, + "required": [ + "mode", + "totalTranslated", + "totalFailed", + "totalSkipped", + "layers", + "byLayer", + "dryRun" + ], + "type": "object" + } + }, + "required": [ + "layers", + "summary" + ], + "type": "object" + } + ], + "type": "object" +}
- Changed
write_translations5 fields changed- changed
Input schema / properties / dryRun / descriptionPrevious value: -"When true, returns a preview of what would be written without writing any files. Default: false."New value: +"Return a preview of what would be written without writing any files. Default: false." - changed
Input schema / properties / layer / descriptionPrevious value: -"Layer name (e.g., \"root\", \"app-admin\"). Discover layers via the discover tool."New value: +"Layer name from discover (e.g., \"root\", \"app-admin\")." - changed
Input schema / properties / projectDir / descriptionPrevious value: -"Absolute path to the Nuxt project root. Defaults to I18N_PROJECT_DIR, then server cwd. Example: \"/home/user/my-app\"."New value: +"Absolute path to the project root. Defaults to I18N_PROJECT_DIR, then server cwd. Example: \"/home/user/my-app\"." - changed
Input schema / properties / translations / descriptionPrevious value: -"Map of dot-path keys to locale-value pairs. IMPORTANT: values must be locale maps, NOT plain strings. Locale refs may be code (\"en-us\"), language (\"en-US\"), or file (\"en-US.json\"). Wrong: { \"auth.failed\": \"Login failed\" }. Correct: { \"auth.failed\": { \"en-US\": \"Login failed\", \"de-DE\": \"Anmeldung fehlgeschlagen\" } }"New value: +"Map of dot-path keys to locale-value pairs. IMPORTANT: values must be locale maps, NOT plain strings. Locale refs may be a code (\"en-us\"), a language (\"en-US\") or a file (\"en-US.json\"). Wrong: { \"auth.failed\": \"Login failed\" }. Correct: { \"auth.failed\": { \"en-US\": \"Login failed\", \"de-DE\": \"Anmeldung fehlgeschlagen\" } }" - changed
Output schema / (root)Previous value: -nullNew value: +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "additionalProperties": false, + "properties": { + "ambiguousLocales": { + "description": "Locale refs that matched several locales, with the one precedence picked. Absent when every ref was unambiguous.", + "items": { + "additionalProperties": false, + "properties": { + "candidates": { + "description": "Codes of every locale the ref matched, in config order.", + "items": { + "type": "string" + }, + "type": "array" + }, + "matchedBy": { + "description": "Which field of the locale the ref matched on, in resolution precedence order.", + "enum": [ + "code", + "language", + "file" + ], + "type": "string" + }, + "ref": { + "description": "The locale ref as the caller wrote it.", + "type": "string" + }, + "resolvedTo": { + "description": "The code that was used — the first candidate.", + "type": "string" + } + }, + "required": [ + "ref", + "matchedBy", + "candidates", + "resolvedTo" + ], + "type": "object" + }, + "type": "array" + }, + "dryRun": { + "description": "True when nothing was written because a preview was asked for. Absent otherwise.", + "type": "boolean" + }, + "filesWritten": { + "description": "Number of locale files changed on disk. Absent on a dry run.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "message": { + "description": "The step to take next, as the surface the call ran on phrases it. Present only when there is no summary to carry it.", + "type": "string" + }, + "placeholderValidation": { + "additionalProperties": false, + "description": "Placeholder comparison of the written values against the reference locale. Absent when nothing was comparable.", + "properties": { + "errors": { + "description": "One entry per translation that did not match. Empty when ok is true.", + "items": { + "additionalProperties": false, + "properties": { + "extra": { + "description": "Placeholders the translation invented and the source does not have.", + "items": { + "type": "string" + }, + "type": "array" + }, + "key": { + "description": "Dot-path key of the mismatching translation.", + "type": "string" + }, + "kind": { + "description": "What failed: a placeholder set mismatch (the default) or a vue-i18n plural variant-count mismatch.", + "enum": [ + "placeholder", + "plural-count" + ], + "type": "string" + }, + "locale": { + "description": "Locale code the mismatch was found in.", + "type": "string" + }, + "missing": { + "description": "Placeholders the source has and the translation dropped.", + "items": { + "type": "string" + }, + "type": "array" + }, + "sourceVariants": { + "description": "Plural variants in the source value. Present only for kind \"plural-count\".", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "targetVariants": { + "description": "Plural variants in the translated value. Present only for kind \"plural-count\".", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + } + }, + "required": [ + "locale", + "key", + "missing", + "extra" + ], + "type": "object" + }, + "type": "array" + }, + "ok": { + "description": "True when every checked translation carries the same placeholders as its source.", + "type": "boolean" + }, + "placeholders": { + "description": "Placeholder names found in the source values, e.g. [\"{name}\"].", + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "ok", + "placeholders", + "errors" + ], + "type": "object" + }, + "skipped": { + "description": "Keys the write mode left alone — existing keys under mode \"add\", missing ones under mode \"update\".", + "items": { + "type": "string" + }, + "type": "array" + }, + "skippedKeys": { + "description": "The keys behind keysSkipped, when the mode skipped any. Absent when nothing was skipped.", + "items": { + "type": "string" + }, + "type": "array" + }, + "summary": { + "additionalProperties": false, + "description": "Counts of what the run did. Absent on a dry run.", + "properties": { + "keysSkipped": { + "description": "Number of keys the write mode left alone.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "keysWritten": { + "description": "Number of keys written across every locale.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "message": { + "description": "One sentence stating what the run did.", + "type": "string" + } + }, + "required": [ + "keysWritten", + "keysSkipped", + "message" + ], + "type": "object" + }, + "unresolvedLocales": { + "description": "Locale refs that matched no known locale; their values were dropped while other locales were still written. Absent when every ref resolved.", + "items": { + "additionalProperties": false, + "properties": { + "keys": { + "description": "Dot-path keys whose value for this ref was dropped.", + "items": { + "type": "string" + }, + "type": "array" + }, + "ref": { + "description": "The locale ref that matched no known locale. Nothing was written for it.", + "type": "string" + }, + "suggestion": { + "description": "\"Did you mean …?\", when a near match exists.", + "type": "string" + } + }, + "required": [ + "ref", + "keys" + ], + "type": "object" + }, + "type": "array" + }, + "warnings": { + "description": "Non-fatal problems, e.g. a value written over a nested object. Absent when there are none.", + "items": { + "type": "string" + }, + "type": "array" + }, + "wouldWrite": { + "description": "The writes a dry run would make. Present only with dryRun.", + "items": { + "additionalProperties": false, + "properties": { + "key": { + "description": "Dot-path key that would be written.", + "type": "string" + }, + "locale": { + "description": "Locale code the value would be written to.", + "type": "string" + }, + "value": { + "description": "The value that would be written.", + "type": "string" + } + }, + "required": [ + "locale", + "key", + "value" + ], + "type": "object" + }, + "type": "array" + }, + "written": { + "description": "Dot-path keys that were written. Absent on a dry run.", + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "skipped" + ], + "type": "object" +}
2 tool updates
v1.0.4- Changed
find_duplicate_keys2 fields changed- added
Input schema / properties / byValueAdded value: +{ + "description": "When true, also groups different keys carrying the same value — e.g. common.actions.save and calendar.views.save both \"Speichern\". Each group says what to do about it: \"reuse\" (a shared layer already has it — delete the app copies and repoint call sites), \"promote\" (move one to a shared layer with move_translation_key), or \"consolidate\" (duplication inside one layer). Default: false.", + "type": "boolean" +} - added
Input schema / properties / minValueLengthAdded value: +{ + "description": "Shortest value worth grouping when byValue is set. Default: 4 — below it, values like \"OK\" repeat across unrelated namespaces legitimately.", + "maximum": 9007199254740991, + "minimum": 1, + "type": "integer" +}
- Added
move_translation_key
17 tool updates
v1.0.3- Changed
discover2 fields changed- changed
Input schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema" - changed
Input schema / properties / projectDir / descriptionPrevious value: -"Absolute path to the project root. Defaults to server cwd."New value: +"Absolute path to the project root. Defaults to I18N_PROJECT_DIR, then server cwd."
- Added
find_duplicate_keys - Added
find_empty_translations - Changed
find_orphan_keys3 fields changed- changed
Input schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema" - changed
Input schema / properties / projectDir / descriptionPrevious value: -"Absolute path to the Nuxt project root. Defaults to server cwd. Example: \"/home/user/my-app\"."New value: +"Absolute path to the Nuxt project root. Defaults to I18N_PROJECT_DIR, then server cwd. Example: \"/home/user/my-app\"." - changed
Input schema / properties / scanDirs / descriptionPrevious value: -"Absolute paths to directories to scan for source code usage. Defaults to all layer root directories. Example: [\"/home/user/my-app/apps/admin\"]."New value: +"Absolute paths to directories to scan for source code usage. Overrides scope-aware scanning: all layers are checked globally against these dirs. Example: [\"/home/user/my-app/apps/admin\"]."
- Added
find_undefined_keys - Changed
get_missing_translations2 fields changed- changed
Input schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema" - changed
Input schema / properties / projectDir / descriptionPrevious value: -"Absolute path to the Nuxt project root. Defaults to server cwd. Example: \"/home/user/my-app\"."New value: +"Absolute path to the Nuxt project root. Defaults to I18N_PROJECT_DIR, then server cwd. Example: \"/home/user/my-app\"."
- Added
get_translation_status - Changed
get_translations2 fields changed- changed
Input schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema" - changed
Input schema / properties / projectDir / descriptionPrevious value: -"Absolute path to the Nuxt project root. Defaults to server cwd. Example: \"/home/user/my-app\"."New value: +"Absolute path to the Nuxt project root. Defaults to I18N_PROJECT_DIR, then server cwd. Example: \"/home/user/my-app\"."
- Changed
list_namespaces2 fields changed- changed
Input schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema" - changed
Input schema / properties / projectDir / descriptionPrevious value: -"Absolute path to the project root. Defaults to server cwd. Example: \"/home/user/my-app\"."New value: +"Absolute path to the project root. Defaults to I18N_PROJECT_DIR, then server cwd. Example: \"/home/user/my-app\"."
- Changed
remove_orphan_keys3 fields changed- changed
Input schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema" - changed
Input schema / properties / projectDir / descriptionPrevious value: -"Absolute path to the Nuxt project root. Defaults to server cwd. Example: \"/home/user/my-app\"."New value: +"Absolute path to the Nuxt project root. Defaults to I18N_PROJECT_DIR, then server cwd. Example: \"/home/user/my-app\"." - changed
Input schema / properties / scanDirs / descriptionPrevious value: -"Absolute paths to directories to scan for source code usage. Defaults to all layer root directories. Example: [\"/home/user/my-app/apps/admin\"]."New value: +"Absolute paths to directories to scan for source code usage. Overrides scope-aware scanning: all layers are checked globally against these dirs. Example: [\"/home/user/my-app/apps/admin\"]."
- Changed
remove_translations2 fields changed- changed
Input schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema" - changed
Input schema / properties / projectDir / descriptionPrevious value: -"Absolute path to the Nuxt project root. Defaults to server cwd. Example: \"/home/user/my-app\"."New value: +"Absolute path to the Nuxt project root. Defaults to I18N_PROJECT_DIR, then server cwd. Example: \"/home/user/my-app\"."
- Changed
rename_translation_key2 fields changed- changed
Input schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema" - changed
Input schema / properties / projectDir / descriptionPrevious value: -"Absolute path to the Nuxt project root. Defaults to server cwd. Example: \"/home/user/my-app\"."New value: +"Absolute path to the Nuxt project root. Defaults to I18N_PROJECT_DIR, then server cwd. Example: \"/home/user/my-app\"."
- Changed
scaffold_locale2 fields changed- changed
Input schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema" - changed
Input schema / properties / projectDir / descriptionPrevious value: -"Absolute path to the project root. Defaults to server cwd. Example: \"/home/user/my-app\"."New value: +"Absolute path to the project root. Defaults to I18N_PROJECT_DIR, then server cwd. Example: \"/home/user/my-app\"."
- Changed
search_translations2 fields changed- changed
Input schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema" - changed
Input schema / properties / projectDir / descriptionPrevious value: -"Absolute path to the Nuxt project root. Defaults to server cwd. Example: \"/home/user/my-app\"."New value: +"Absolute path to the Nuxt project root. Defaults to I18N_PROJECT_DIR, then server cwd. Example: \"/home/user/my-app\"."
- Changed
translate_key3 fields changed- changed
Input schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema" - changed
Input schema / properties / dryRun / descriptionPrevious value: -"When true, previews source/target locales without writing files or calling sampling."New value: +"When true, previews source/target locales without writing files or calling the translation backend." - changed
Input schema / properties / projectDir / descriptionPrevious value: -"Absolute path to the Nuxt project root. Defaults to server cwd."New value: +"Absolute path to the Nuxt project root. Defaults to I18N_PROJECT_DIR, then server cwd."
- Changed
translate_missing5 fields changed- changed
Input schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema" - changed
Input schema / properties / batchSize / descriptionPrevious value: -"Max keys per LLM sampling request. Default: 50. Lower values reduce per-batch risk but increase round trips."New value: +"Max keys per provider request (provider mode only). Default: 50. Lower values reduce per-batch risk but increase round trips." - changed
Input schema / properties / layer / descriptionPrevious value: -"Layer name from discover to translate (e.g., \"root\", \"app-admin\"). Call discover to discover available layers."New value: +"Layer name from discover to translate (e.g., \"root\", \"app-admin\"). Omit to translate every locale-backed layer in one call — the recommended default for layered projects, which returns a result per layer plus an aggregated summary." - changed
Input schema / properties / projectDir / descriptionPrevious value: -"Absolute path to the Nuxt project root. Defaults to server cwd. Example: \"/home/user/my-app\"."New value: +"Absolute path to the Nuxt project root. Defaults to I18N_PROJECT_DIR, then server cwd. Example: \"/home/user/my-app\"." - removed
Input schema / requiredRemoved value: -[ - "layer" -]
- Changed
write_translations2 fields changed- changed
Input schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema" - changed
Input schema / properties / projectDir / descriptionPrevious value: -"Absolute path to the Nuxt project root. Defaults to server cwd. Example: \"/home/user/my-app\"."New value: +"Absolute path to the Nuxt project root. Defaults to I18N_PROJECT_DIR, then server cwd. Example: \"/home/user/my-app\"."
13 tool updates
v1.0.0- First observed
discover - First observed
find_orphan_keys - First observed
get_missing_translations - First observed
get_translations - First observed
list_namespaces - First observed
remove_orphan_keys - First observed
remove_translations - First observed
rename_translation_key - First observed
scaffold_locale - First observed
search_translations - First observed
translate_key - First observed
translate_missing - First observed
write_translations
TDQS
Scored across 15 tools
Each tool targets a distinct i18n operation: discovery, listing, reading, writing, removing, moving, translating, scaffolding, and four different key-health checks. The only mild overlap is get_translations vs search_translations, but their descriptions clearly separate exact/prefix retrieval from value-based search. No two tools appear to do the same thing.
The set is predominantly verb_noun snake_case (list_namespaces, get_translations, write_translations, find_undefined_keys). Minor deviations: 'discover' is a bare verb, and pluralization varies (translations vs translation_key/status). The verb-led convention is still clear and predictable overall.
15 tools is well within the ideal 3-15 range and each one maps to a distinct operation in translation management. The set feels purpose-built rather than padded or thin.
Covers the full lifecycle: discover, list/search/get, write/remove, move/rename, translate, scaffold, and comprehensive health checks (missing, undefined, orphan, duplicate, status). No obvious dead ends for the stated i18n management purpose; framework config edits are intentionally left out.
Maintenance
Related MCP Connectors
MCP server for AI agents to plan, verify, and deploy Cloudflare-native apps.
MCP server connecting AI agents to 100+ apps (Gmail, Slack, Notion, GitHub) via one-click OAuth.
Nifty's MCP server — exposes tasks, projects, messages, and files as tools for AI agents.
MCP server for secureFlows: token-free URL builders and integration-linting tools for AI agents.
Related MCP Servers
- AlicenseAqualityBmaintenanceMCP server for AI-powered translation management in i18n projects, enabling automated locale detection, translation status checks, and sync via LangAPI.440 npm1MIT
- AlicenseAqualityBmaintenanceMCP server for managing i18n JSON translation files. Provides Claude with structured read/write access to translation files for adding keys, checking coverage, and finding duplicates.1214 npm1MIT
- AlicenseNot gradedqualityDmaintenanceAn MCP server that lets AI agents read and write locale JSON translation files directly from the conversation without loading the whole catalog into context.8 npm1ISC
- AlicenseNot gradedqualityAmaintenanceOpen-source, self-hosted translation management system with an MCP server that enables AI agents to manage projects, locales, keys, and translations via natural language, and includes a CLI for deterministic file syncing.1MIT