# Update customiser language translations (`update_customiser_language_translations`)

Writes draft values for up to 100 translation keys of one customiser language per call. Keys the call does not name keep their values, a value replaces the current draft, and draft_value null clears a key back to blank. Unknown and retired keys are refused with invalid_translation_key, and a value that drops a placeholder its source copy needs is refused with validation_failed. Nothing a shopper sees changes until publish_customiser_language. The result reports counts and whether the language can now be published, not the values. It requires the customisers:write scope; idempotency_key is optional, and dry_run true checks the write without saving anything.

- Source URL: https://www.signcustomiser.com/help/mcp/tools/update_customiser_language_translations/
- Markdown URL: https://www.signcustomiser.com/help/mcp/tools/update_customiser_language_translations.md
- MCP endpoint: https://mcp.signcustomiser.com
- Required scope: `customisers:write`
- Behaviour: destructive write

## Purpose

Translate a language a page at a time, or correct individual draft values, before the language is published.

## Annotations

| Hint | Value |
| --- | --- |
| readOnlyHint | false |
| destructiveHint | true |
| idempotentHint | false |
| openWorldHint | false |

## Prerequisites

- A customiser id from list_customisers and a language code from list_customiser_languages.
- The keys to write, from get_customiser_language with include translations.
- A merchant has connected this store and approved the customisers:write scope.

## Side effects

- Replaces the draft value of each named key, or clears it when draft_value is null. Published copy does not change.
- Clears the customiser cached storefront configuration and runs the language sync.
- Claims the idempotency_key for at least 24 hours when one is sent.

## Arguments

| Property | Type | Required | Description |
| --- | --- | --- | --- |
| `customiser_id` | integer | yes | The customiser id, from list_customisers. |
| `language_code` | string | yes | The BCP 47 language tag the language is addressed by, such as fr or pt-BR, from list_customiser_languages. |
| `translations` | array of object | yes | The keys to write, between 1 and 100 per call, each with its draft value. A key may appear once. Keys this list does not name are unchanged. Send larger dictionaries in several calls. |
| `translations[].key` | string | yes | A translation key this language carries, such as selection:12:title, from get_customiser_language with include translations. |
| `translations[].draft_value` | string or null | yes | The draft value to write. Null clears it back to blank, which makes a required key block publishing again. |
| `idempotency_key` | string | no | An optional client-generated key unique to this logical write, such as a UUID. Repeating the call with the same key and the same arguments replays the original result; the route does not require one. |
| `dry_run` | boolean | no | When true, the write is checked against the keys the language accepts and the placeholders each source copy needs is checked and nothing is written: the result is the same verdict the apply would have reached, and a problem the apply would have raised comes back as the same tool error. Defaults to false. A dry run claims no key. |

## Result

How many keys were given a value and how many were cleared, the count of required keys still blank, and whether the language can now be published. A dry run returns the write verdict instead and saves nothing.

| Property | Type | Required | Description |
| --- | --- | --- | --- |
| `request_id` | string | yes |  |
| `store` | object | yes |  |
| `store.id` | integer | yes |  |
| `store.name` | string | yes |  |
| `customiser_id` | integer | yes |  |
| `language_code` | string | yes |  |
| `written_key_count` | integer | no | How many of the named keys were given a value. Absent on a dry run. |
| `cleared_key_count` | integer | no | How many of the named keys were cleared back to blank. Absent on a dry run. |
| `missing_required_key_count` | integer | no | How many required keys of the language still have a blank draft after the write. Absent on a dry run. |
| `publishable` | boolean | no | Whether publish_customiser_language would now pass the required-copy check: true exactly when missing_required_key_count is zero. Absent on a dry run. |
| `dry_run` | boolean | no | Present and true only when dry_run was requested. |
| `valid` | boolean | no | Present only on a dry run, and always true: a failing dry run returns a tool error instead. |
| `verdict` | object | no | Present only on a dry run: the write verdict. |
| `idempotent_replay` | boolean | no | True when this call replayed a previously recorded response for the same idempotency_key instead of applying the write again. |

## Error cases

| Code | Recovery |
| --- | --- |
| `idempotency_key_conflict` | The key was already used with other arguments. Re-send the original arguments or use a new key. |
| `insufficient_scope` | The connection was approved without customisers:write. |
| `invalid_translation_key` | Read invalid_keys. Each named key is unknown to this language or retired. Read the current keys with get_customiser_language. |
| `resource_not_found` | No customiser with that id, or no language with that code on it. |
| `validation_failed` | A value is too long, a key appears twice, an entry names a read-only property, or a value drops a placeholder such as {price} that its source copy needs. Read errors for the pointer. |

## Example: Translate two keys and clear a third

Request:

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "update_customiser_language_translations",
    "arguments": {
      "customiser_id": 42,
      "language_code": "fr",
      "translations": [
        {
          "key": "selection:12:title",
          "draft_value": "Couleur"
        },
        {
          "key": "selection:13:title",
          "draft_value": "Police"
        },
        {
          "key": "selection:14:title",
          "draft_value": null
        }
      ],
      "idempotency_key": "3c4d5e6f-7081-4923-a4b5-c6d7e8f90a1b"
    }
  }
}
```

Response:

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "structuredContent": {
      "request_id": "req_01jz9x2k7c8f3m5n6p7q8r9s0t",
      "store": {
        "id": 12,
        "name": "Demo Signs"
      },
      "customiser_id": 42,
      "language_code": "fr",
      "written_key_count": 2,
      "cleared_key_count": 1,
      "missing_required_key_count": 83,
      "publishable": false,
      "idempotent_replay": false
    }
  }
}
```

## Example: A key the language does not carry (error)

Request:

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "update_customiser_language_translations",
    "arguments": {
      "customiser_id": 42,
      "language_code": "fr",
      "translations": [
        {
          "key": "selection:999:title",
          "draft_value": "Couleur"
        }
      ],
      "dry_run": true
    }
  }
}
```

Response:

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "isError": true,
    "structuredContent": {
      "error": {
        "code": "invalid_translation_key",
        "title": "Invalid translation key",
        "detail": "The write names translation keys this language cannot accept. Read the translations to see the keys it carries, then retry with only those.",
        "recovery": "Remove or correct each key in invalid_keys. Read the language translations with get_customiser_language and include translations to see the keys it accepts.",
        "invalid_keys": [
          {
            "key": "selection:999:title",
            "reason": "unknown"
          }
        ]
      }
    }
  }
}
```
