---
name: localization-reuse-map
description: "Localization reuse map for one redesigned screen on iOS, Android and web: new keys set against each platform's legacy key, legacy values and the Figma-annotation CMS copy, with a reuse / review / new verdict, a key overlay, per-key shots, a CMS import sheet and a Confluence page. Use it to map old keys onto new ones, reuse legacy translations, pull CMS copy out of Figma annotations, or build a localization excel or CMS import sheet; also answers to /localization-reuse-map. Not for several screens at once, and not for minting or defining new keys (the resource-authoring flow)."
metadata:
  version: 1.0.0
  source: multi-agent-pipeline
---

# localization-reuse-map

One run covers one redesigned screen on every platform where it exists. The output is a table with a row per UI element that answers a single question for the content team: can this string reuse what the legacy app already ships, does it need a human look, or is it genuinely new?

Scope rules that hold for every run:

- **One screen per run.** Rolling several screens into one map is out of scope.
- **Recommend, never decide.** The verdict column only suggests. The decision belongs to the content team, so no verdict is ever presented as settled.
- **Never invent a value.** A backend serves the legacy translations; either fetch them, or keep the Old column empty and add a note. CMS copy comes from the Figma annotation or stays blank. A blank cell is better than a guess.
- **CMS copy is additive.** The annotation holds the copy the content team signed off. It sits in its own columns and never overwrites `new`. When it differs from `new`, the difference is shown with a warning mark, not hidden.
- **Figma live first.** CMS annotations and overlay geometry are read from Figma, over REST or through Figma MCP. The design export kept in the repo may be out of date, so it is used only if neither live route works.
- **Read-only everywhere** except two writes: the Confluence page and, optionally, a pull request that adds `localizations/<slug>/` to the specs repo.
- **Components are mapped once.** When a reusable component owns a key, the row's `newKey` carries `(owned by <Component>)` instead of the key being mapped again on each screen where the component appears.
- **Chrome language.** Author-written `element` labels and `note` text follow `--ui-lang`; keys and translation values stay verbatim.

It is not a key-minting tool: new keys and their values belong to the resource-authoring flow.

## Project settings

Nothing company-specific is built in. Establish these once per project (read them from the project's docs or ask), then reuse them for every screen:

| Setting | What it is | Example |
|---|---|---|
| Specs repo + design export | Screen frames, `components-used.md`, `screenshot.png`, `tree.json` | `design-export/<project>/screens/<slug>/` |
| Resources root | Suggested new values and the component registry | `resources/Localization/Suggested/<Key>.json` |
| Legacy label endpoint | For refreshing the legacy snapshot | `--endpoint` + `--headers-file` |
| Legacy key prefix | Display-only prefix the backend stores | mapping `legacyKeyPrefix`, e.g. `App-` |
| Web i18n convention | For legacy and/or new web apps | `t('key')` / `$t('key')`, keys in `locales/<lang>.json` |
| Confluence target | Base URL, space, parent page | `CONFLUENCE_BASE_URL` / `CONFLUENCE_SPACE` / `CONFLUENCE_PARENT` |
| CMS taxonomy | Property Group / Module buckets in the spreadsheet | `--taxonomy cms-taxonomy.json` |
| Document language | Headings, legend and labels | `--ui-lang en` (default) or `tr` |

Secrets live only in a keychain item or a `--headers-file`. They never go into the mapping, the taxonomy file, the docs or a command line.

At the start, ask the user only for what is still unknown: where the implemented screen lives, the Figma file key and node-ids (or a confirmed design-export slug), and, once it is time to publish, the Confluence target and the formats wanted.

## Where this sits

After the screen spec exists: screen spec, then this map, then publish. If the repo holds a snapshot, the registry and the Suggested files are taken from it (see [publish-and-snapshot](reference/publish-and-snapshot.md)).

## Steps

The helpers in `scripts/` use nothing beyond the Python 3 standard library, apart from one bash script. Commands and how each source is traced are covered in [sources-and-recipes](reference/sources-and-recipes.md).

1. **Enumerate new keys.** Take the union of three sources: the design export's `components-used.md` across every state frame, each component's registry `localizationKeys`, and the keys the screen code wires. Tag each key screen-specific, component-owned or shared. Every key is confirmed by comparing sources, never lifted from one of them.
2. **Catch what the cross-reference misses.** First find the type the project generates its keys into (for example `grep -rhoE '\b[A-Z][A-Za-z0-9_]*\.[A-Z][A-Za-z0-9_]*\.[A-Za-z_]+' <dir> | cut -d. -f1 | sort | uniq -c | sort -rn`) and set it as `"keyType"` in the mapping; the built-in default `L10n` is only a fallback, and a project that uses another accessor gets its real keys missed or downgraded to weak. Then run `scan-screen-keys.py --screen-path <dir> --key-type <KeyType>`. A `WARNING: no key chain of ...` line on stderr means the type is wrong: fix it before going on. Each `error` and `dynamic` hit becomes a mapping row; compare the `static` hits with the union from step 1.
3. **New values.** Run `resolve-new-values.py --keys "<all>" --resources-root <res> --langs all --catalog <shipped catalog>`; `--catalog` is never optional.
4. **CMS copy.** Run `fetch-annotations.py --mapping <map>.json [--local <export-screen-dir>]`: it reads REST and falls back to `--local` when REST fails. When REST is rate-limited or has no token, dump the annotations through Figma MCP and run it again with `--from-mcp <dump>.json`. Fill `cms` and `cmsNodeId` on each row, and send any TR without EN back to the content team.
5. **Trace legacy keys** per platform: spec, then view controller, view model and cell presentation models on iOS; layout `@string/` on Android; call sites on web.
6. **Legacy values.** Run `resolve-legacy-values.py --keys "<legacy>" --snapshot-root <res>/Localization/Legacy --langs all`; if the snapshot is out of date, `fetch-legacy-labels.py` refreshes it. A value that cannot be found stays blank and gets a note.
7. **Assemble the mapping JSON**: one row per element, plus `screenshot`, `figmaFileKey` and `legacyKeyPrefix` where they apply. Schema: [format-and-output](reference/format-and-output.md).
8. **Overlay.** Run `render-overlay.py --out <dir> --mapping <map>.json`; it produces `<slug>.overlay.png`, which the mapping's `overlay` field then names.
9. **Key screenshots.** Run `render-key-shots.py --out <dir> --mapping <map>.json [--slug]`; it fills `keyshots/` and writes the manifest, which the mapping's `keyshots` field then names.
10. **Artifacts.** `build-artifact.py <map>.json --out <dir>` (add `--docx`, `--pdf` or `--all` on request).
11. **Spreadsheet.** `build-spreadsheet.py <map>.json --out <dir>` (add `--csv` or `--csv-only`).
12. **Gate.** Run `verify-map.py --screen-path <dir> --mapping <map>.json [--resources-root <res>]`, with the accessor from step 2 in the mapping's `keyType` (or passed as `--key-type <KeyType>`). Exit code 2 stops publishing. A PASS that carries the key type `WARNING` is not a pass: set the accessor and run it again.
13. **Publish.** Ask which target(s): (A) Confluence via `publish-confluence.py`, (B) a specs-repo PR under `localizations/<slug>/`, or both. Details: [publish-and-snapshot](reference/publish-and-snapshot.md).

When the Figma frame carries no annotations, say so, explain that keys will be mapped by reading the screen, and confirm the screen state (mock or screenshot) and the anchored element list with the user before rendering.

## Choosing a verdict

| Verdict | When |
|---|---|
| `reuse` | Same function and the same or an equivalent value, or a single clean key that both legacy platforms share. The team adopts the legacy value or key and orders no new translation. |
| `review` | Same function, but the value drifted, the wording changed, the legacy key differs across platforms, or legacy covers too few languages. The team chooses. |
| `new` | Nothing in legacy corresponds to it, because the feature or the business rule is new. Both a key and a value have to be minted. |

Edge cases:

- **Component-owned key**: suffix the `newKey` with `(owned by <Component>)`, where the gate looks for it (a note alone does not exempt the row); do not map it twice.
- **Legacy keys differ across platforms**: keep every platform column that has a key, default to `review`.
- **`tr` equals `en`**: note it as untranslated whatever the verdict.
- **CMS present and different from `new`**: the renderer adds the warning mark; mention it in the note. CMS copy is context, not an input to the verdict.
- **Dynamic key** that cannot be resolved statically: map it with `verdict: review` and a note "dynamic - enumerate at runtime". Never drop it.
- **Partial legacy**: legacy covers only some languages (en and tr, for instance), or only one platform has a legacy key; record that in the note.
- **What reuse means**: reusing the value, or aliasing the legacy key. It is not string equality between a namespaced new key and a flat legacy key.

## Done means

- Each row carries a `verdict`, and no Old or CMS value is made up; an empty Old or CMS cell is an acceptable state.
- `verify-map.py` passes: every `newKey` is wired (or exempt as component-owned or dynamic), no wired key is unmapped, no empty or invented values, and every `error`/`dynamic` hit from `scan-screen-keys.py` is a row.
- `build-artifact.py` ran clean; the `.md` has the header, legend, totals and the fixed column order including the CMS columns.
- The overlay PNG exists (or the `.html` fallback is noted) and the mapping's `overlay` names it.
- One keyshot PNG per matched row; the mapping's `keyshots` names the manifest; every non-dash Screenshot cell resolves to a file; `keyshots/` holds no orphans. Renaming a key or reordering rows renumbers the files, so re-render instead of renaming by hand.
- The spreadsheet has one row per mapping row and the nine fixed columns, values from `new` and annotations from `cms`, and is attached at publish.
- A second publish reports `updated` and bumps the version of the existing page id; it never creates another page.

## References

- [sources-and-recipes](reference/sources-and-recipes.md): the origin of every column, legacy tracing on each platform, overlay and keyshot geometry, pitfalls.
- [format-and-output](reference/format-and-output.md): the mapping schema, artifact formats, spreadsheet columns and the script reference.
- [publish-and-snapshot](reference/publish-and-snapshot.md): the snapshot that keeps reads in one repo, Confluence publishing, and the specs-repo PR.
- [example mapping](example-mapping.json): a neutral mapping of nine rows that covers each edge case listed above.
