A coding agent localizes a feature: confidence scores, review and the record, step by step
Coding-agent localization means the AI assistant that writes your feature also wires up its strings: it adds the t() calls, reports the new keys, and gets translations back. The interesting part is what happens after that. We ran it once, for real, on our Taskly demo app: Claude Code added a snooze feature, reported eleven keys to Locize over MCP, and had them back in six languages about eleven seconds later, every translation with a confidence score and a reason. This post shows each step with the actual output: what the scores caught, what they missed, what the agent fixed on its own, what the review still changed, and what the record says at the end.
The why behind this pattern (and when to skip it) is in when coding agents translate your app. This is the how.
- 11 keys reported by the agent, 69 AI translations back in 6 languages (German, Spanish, French, Italian, Japanese, Portuguese), about 11 seconds from report to published, scoring included.
- 0 of 69 scored below the default review threshold of 0.7. The lowest score (0.7) and both 0.8 scores all went to the same string: "Wake up".
- What the scores missed: a German term used inconsistently across keys, a bug in how the agent's code built a sentence, and a German string scored 1.0 that the review still rewrote. The agent caught the first; the review caught the other two.
- The record: every value ends with a review status in the provenance export (here: 9 human reviewed, 1 human edited, 11 written by the agent, 59 machine output nobody has checked yet), so "a person checked this" is something you can show per string, and so is its absence.
The setup
- The app: Taskly, a small Vite, React and TypeScript to-do app, internationalized with i18next and react-i18next. English source, six target languages in the Locize project (the repository ships five; Portuguese was added in Locize), one namespace of about a hundred keys, an informal style guide and a glossary that so far only protects the product name.
- The agent: Claude Code in the Claude desktop app, with the Locize MCP server added as a connector (
https://mcp.locize.app, OAuth sign-in). In the Claude Code CLI the same server isclaude mcp add --transport http locize https://mcp.locize.app, then/mcpto sign in. Cursor and other MCP clients work the same way. - The project settings: automatic translation on, Quality Estimation on, plus the Review AI workflow that routes low-confidence AI translations into review proposals.
- A branch: the agent created a branch of the project for the feature (
create_branch), so the new strings stay out of the main project until they are reviewed and merged. A branch takes over automatic translation, Quality Estimation and the Review AI workflow from the project when it is created.
Step 1: context before drafting
Before writing any string, the agent asked the project for its rules: get_styleguide returned tone: informal, audience: personal use, and get_glossary returned one term, the product name, to be kept as "Taskly" in every language. Nothing about "snooze". That gap matters later.
Step 2: write the code, report the keys
The feature: snooze a task until later today, tomorrow morning or next week, show when it comes back, count snoozed tasks in the sidebar, and bring a task back early. The agent wrote the component with t() calls and nested keys under snooze:
const OPTIONS = ['laterToday', 'tomorrowMorning', 'nextWeek'] as const
<button onClick={() => setOpen(true)}>{t('snooze.action')}</button>
<h3>{t('snooze.menuTitle')}</h3>
{OPTIONS.map((o) => (
<button key={o} onClick={() => snooze(task, o)}>{t(`snooze.${o}`)}</button>
))}
<span>{t('snooze.snoozedUntil', { date: formatShortDate(task.snoozedUntil) })}</span>
<span>{t('snooze.count', { count: snoozedCount })}</span>
<button onClick={() => wake(task)}>{t('snooze.wakeUp')}</button>
{snoozed.length === 0 && <p>{t('snooze.empty')}</p>}
// after snoozing
toast(t('snooze.toast', { title: task.title, when: t(`snooze.${o}`).toLowerCase() }))Then it reported the eleven English keys with report_missing_keys, each with one sentence of context for the translator (human or AI):
{
"snooze.wakeUp": {
"value": "Wake up",
"context": { "text": "Button on a snoozed task. Brings the task back to the active list right now." }
},
"snooze.toast": {
"value": "Snoozed “{{title}}”. We’ll nudge you {{when}}.",
"context": { "text": "Toast after snoozing a task. {{title}} is the task title. {{when}} is one of the snooze option labels in lower case, for example: tomorrow morning." }
}
}report_missing_keys only adds keys, it never overwrites. Reporting to the reference language is enough: automatic translation takes it from there.
Step 3: what came back
About eleven seconds later all six languages were translated, scored and (because the development version publishes automatically) on the CDN. 69 values from 11 English keys, because the plural forms follow each language, not the English source:
| Language | Values | Plural forms of snooze.count |
|---|---|---|
| German | 11 | one, other |
| Spanish, French, Italian, Portuguese | 12 each | one, many, other |
| Japanese | 10 | other only |
The agent wrote two English forms. French, Italian, Spanish and Portuguese have a third CLDR category, many (used for very large numbers such as a million), and Japanese has a single one. Locize created the forms each language needs; an agent writing locale files by hand has to know that.
Step 4: the confidence scores
Each AI translation got a confidence between 0 and 1 and a one-sentence reason. Since October 13, 2026 the agent can read both with get_translations and raw: true (qeScore, qeScoreReason), so it does not have to wait for a person to open the editor.
| Score | Values |
|---|---|
| 1.0 | 53 |
| 0.95 | 4 |
| 0.9 | 9 |
| 0.8 | 2 |
| 0.7 | 1 |
| below 0.7 | 0 |
Nothing was routed into review proposals: routing happens below the threshold (0.7 by default) or when the evaluator flags a major issue, and neither happened. The reasons still pointed at the weakest strings:
| Key | Language | Translation | Score | Reason (evaluator) |
|---|---|---|---|---|
snooze.wakeUp | Spanish | Despertar | 0.7 | Literal translation of "Wake up"; "Reactivar" is more idiomatic in task management |
snooze.wakeUp | German | Aufwecken | 0.8 | Literal translation; "Aktivieren" or "Wiederherstellen" fits a task UI better |
snooze.wakeUp | Japanese | 起こす | 0.8 | A bit unnatural; 今すぐ戻す fits better in a task app |
snooze.action | German | Schlummern | 0.9 | Technically correct, but "Zurückstellen" is more idiomatic for task management |
French, Italian and Portuguese got "Wake up" right without help (Réactiver and Riattiva at 1.0, Retomar at 0.9). The context sentence did its job there; in the other three languages the button still came out as waking someone up.
Step 5: the agent revises, and catches what the scores missed
Reading the scores, the agent found something no single score shows: German used two terms for the same action. Five keys said "Schlummern", the toast said "pausiert", and the evaluator had rated that toast 0.9, "high quality". Quality Estimation scores one string at a time, so consistency across keys is not something it can see.
The AI translation also varied between runs. We had reported the same keys with the same context once before (the keys were removed and reported again under the nested names): that first run chose "Zurückstellen", the second "Schlummern". Neither is wrong; shipping both is. Without a glossary entry, drafting varies between runs, the failure mode the category post describes, and the reason the glossary entry below is the real fix.
The agent pushed its revisions with update_translations and quality: "AI": German consistently "Zurückstellen" (including the toast), and "Wake up" as Reaktivieren, Reactivar and 今すぐ戻す. Declared as AI output, the revisions stay in the review queue instead of counting as approved. Then it started an AI review run (start_ai_review) to score them again, because any edit drops the old score:
- 10 values checked, 10 scored, 0 doubted, 1,746 AI tokens.
- German now scores 1.0 throughout except Reaktivieren (0.9); Spanish Reactivar 1.0, Japanese 今すぐ戻す 0.9.
- One honest footnote: on the second pass the evaluator remarked that "Aufwecken" would have been "more playful/direct". On the first pass it had called the same word a literal translation. A scorer is a router, not a judge.
The fix for the root cause is a glossary entry for "snooze" per language. The MCP server only reads the glossary, so adding the term is a person's step, and the right one: terminology is a decision, not a draft.
Step 6: the review
The German values went through the editor's review queue: each one next to its source, the agent's context sentence and the style guide. Nine were confirmed as they were. Two things had got past both the evaluator and the agent:
- A string scored 1.0, "Perfect translation": "Later today" had become "Heute später". Correct German, but as a menu option "Später heute" reads more naturally, so the review changed it. The edit removed the score, because a score only describes the text it was computed for.
- A bug in the agent's own code. The toast inserts a translated option label, lower-cased, into a sentence: fine for "tomorrow morning", wrong in German, where nouns are capitalized: "Wir erinnern dich nächste woche." No score can see this, because each string is correct on its own. The fix belongs in the code: one toast per option, using i18next context instead of string concatenation.
// before: toast(t('snooze.toast', { title, when: t(`snooze.${o}`).toLowerCase() }))
toast(t('snooze.toast', { context: o, title: task.title }))
// keys: snooze.toast_laterToday, snooze.toast_tomorrowMorning, snooze.toast_nextWeekThe German toast stayed unconfirmed: its text is fine, but the key is about to be replaced by the context keys above.
That is the division of labour the run suggests: the evaluator ranks single strings, the agent checks consistency across the batch and fixes what it is told, and the review catches what is right in isolation and wrong in use.

Step 7: the record
The provenance export (project overview, context menu of the version, provenance (CSV + JSON)) gave every key of the branch a review status. For the 80 values of the feature, the 11 English sources and the 69 translations:
| Review status | Values | What it was |
|---|---|---|
agentEdited | 11 | The English source texts the agent reported. The agent wrote the source too, and the record says so |
humanReviewed | 9 | German values confirmed in the review, each with the confidence score at the moment of the decision |
humanEdited | 1 | "Später heute" |
machineOnly | 59 | Every other translation: Spanish, French, Italian, Japanese and Portuguese, the agent's revisions, and the German toast. Each names the agent as the trigger and stays in the review queue |
Two rows from the JSON, shortened, the reviewer's name left out:
[{
"language": "de", "key": "snooze.wakeUp", "value": "Reaktivieren", "quality": "AI",
"reviewStatus": "humanReviewed",
"lastHumanAction": {
"type": "reviewConfirmed", "occurredAt": "2026-10-13T14:26:54.518Z",
"value": "Reaktivieren", "matchesCurrentValue": true,
"qeScore": 0.9, "qeScoreReason": "Meaning is clear, though 'Aufwecken' could have been more playful/direct, 'Reaktivieren' is professional."
}
},
{
"language": "es", "key": "snooze.wakeUp", "value": "Reactivar", "quality": "AI",
"reviewStatus": "machineOnly",
"lastHumanAction": {
"type": "machineWritten", "occurredAt": "2026-10-13T14:04:58.246Z",
"by": { "agent": "Claude (OAuth)" }, "quality": "AI"
},
"needsReview": true, "qeScore": 1
}]The 59 machine-only values are the honest part of the record: the agent fixed the Spanish and Japanese "Wake up", the scores say the fixes are good, and nobody who reads Spanish or Japanese has looked yet. The export does not round that up.

What it took, and what to watch
- Cost: translation and scoring run on the project's AI provider: Locize AI tokens, or your own OpenAI, Gemini or Mistral key. Scoring roughly doubles the AI calls of a translation run; the re-check of 10 revised values cost 1,746 tokens.
- Threshold: at 0.7 nothing was routed in this run, because short UI strings with context score high. If you want everything in front of a person for some languages, enable review proposals there and have the agent pass
autoTranslateReview: truetoreport_missing_keys: auto-translations triggered over MCP or the API only become proposals with that flag. - Publishing: the development version published every step automatically. Ship to production from a separate version after review; the Article 50 workflow guide covers the review setup and why review has to be the last touch.
- Agent writes are labelled: writes through the MCP server carry the agent's mark in the history (the name of the client that signed in, "Claude (OAuth)" in our run), so the export does not report them as a person's.
Try it on your project
- Connect the MCP server to your agent.
- In the project settings, enable automatic translation, Quality Estimation and the Review AI workflow.
- Ask the agent to build the feature on a branch, report the keys with one context sentence each, read back the scores and reasons, and fix what it can before you look.
- Review in the editor, then download the provenance export.
If your agent already writes your strings, this is the shortest way to see what it ships. Create a free project, connect the MCP server, and let the next feature go through it.
Frequently asked questions
How do I let Claude Code or Cursor localize a feature with Locize?
Connect the Locize MCP server, let the agent write the code with t() calls and report the new keys with report_missing_keys and one context sentence per key. With automatic translation on, the keys come back in every target language; with Quality Estimation on, each AI translation gets a confidence score and a reason that the agent reads with get_translations and raw: true. A branch keeps the strings out of the main project until you merge it, after review.
Does Quality Estimation catch every bad translation? No. It scores each string on its own, so it misses inconsistency across keys and code that builds sentences from translated pieces. In our run none of 69 AI translations scored below 0.7, the reasons still pointed at the weakest strings, and the review still changed a string scored 1.0. Quality Estimation decides where a person looks first; it does not replace the look.
What does the provenance export show for translations written by an AI agent? Text the agent pushed as AI output is reported as machine output with the agent named as the trigger; text a person confirmed is human reviewed; text a person changed is human edited. Agent writes and confirms that are not declared as machine output are reported as agentEdited and agentConfirmed, never as a person's action.
Why does the agent report keys instead of editing the locale files? Because files have no memory and no record. Reported keys are translated with the glossary and style guide as context, scored, by default kept in the review queue until a person confirms them, and each action is recorded in the history.
Do AI translations go live before anyone reviews them?
Only where you let them. The development version in our run published automatically, which is what you want while developing. Production is a separate version you publish after review. Or you enable review proposals for a language and have the agent pass autoTranslateReview: true when it reports keys, so the live value only changes when a person accepts.
Tired of managing translations by hand?
Locize is the translation management backend by the i18next team: CDN delivery, AI translation, in-context editing, no redeploys.
Start your free 14-day trial