Skip to main content
Glama
fabkho
by fabkho

Move or Rename a Translation Key

move_translation_key
Destructive

Move a translation key to another layer, key path, or both while carrying all locales that define it. Rename keys in place or promote/demote across shared layers, with dry-run preview.

Instructions

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.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
keyYesDot-separated key path to move. Example: "calendar.views.save".
layerYesLayer the key lives in today, from discover. Example: "app-admin".
dryRunNoReturn the plan without writing any files. Default: false.
newKeyNoKey path to give it. Example: "common.actions.save". Omit to keep the current path, which then requires toLayer.
toLayerNoLayer to move it to, from discover. Example: "root". Omit (or repeat layer) to rename the key within its current layer, which then requires newKey.
projectDirNoAbsolute path to the project root. Defaults to I18N_PROJECT_DIR, then server cwd. Example: "/home/user/my-app".

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault

No arguments

Schema Changelog

Changes observed during successful MCP inspections.

  1. Changed8 schema fields changedv1.0.6
    • changedInput schema / properties / dryRun / description
      Previous value: -"When true, returns the plan without writing any files. Default: false."New value: +"Return the plan without writing any files. Default: false."
    • removedInput schema / properties / fromLayer
      Removed value: -{
      -  "description": "Layer the key lives in today, from discover. Example: \"app-admin\".",
      -  "type": "string"
      -}
    • addedInput schema / properties / layer
      Added value: +{
      +  "description": "Layer the key lives in today, from discover. Example: \"app-admin\".",
      +  "type": "string"
      +}
    • changedInput schema / properties / newKey / description
      Previous 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."
    • changedInput schema / properties / projectDir / description
      Previous 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\"."
    • changedInput schema / properties / toLayer / description
      Previous 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."
    • changedInput schema / required
      Previous value: -[
      -  "fromLayer",
      -  "toLayer",
      -  "key"
      -]New value: +[
      +  "layer",
      +  "key"
      +]
    • changedOutput 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"
      +}
  2. Addedv1.0.4

TDQS

A4.9/5.0
Behavior5/5

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.

Conciseness5/5

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.

Completeness5/5

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.

Parameters4/5

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.

Purpose5/5

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.

Usage Guidelines5/5

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.