QA & troubleshootingこの記事を日本語で読む

Duplicate keys in localization files: the bug that never throws an error

Most localization file formats — CSV, JSON, key-value resource files — do not require keys to be unique, and most parsers do not check. That combination is why duplicate keys are such a persistent, low-visibility bug: nothing crashes, nothing throws a parse error, the file loads and the game runs. The only symptom is that one of the two values for that key silently never gets used.

How duplicates actually get introduced

Duplicate keys are rarely typed in deliberately. They accumulate from ordinary workflow friction:

  • Spreadsheet copy-paste — a row gets duplicated while inserting new content above or below it, and the key column gets copied along with everything else
  • Merges — two branches each add a row for the same new key (often independently, for the same feature) and the merge keeps both instead of flagging a conflict
  • Concatenated exports — a build script joins several source files or spreadsheet tabs into one output file without checking whether any key appears in more than one of them

Why last-one-wins hides the problem

When a parser reads a file with a duplicate key, the typical behavior — for JSON objects, for a dictionary built from CSV rows, for most key-value loaders — is that each new value for the same key simply overwrites the previous one. Whichever entry appears last in the file wins, and the earlier one is discarded with no warning at any stage: not at parse time, not at build time, not at runtime.

This is the property that makes duplicates dangerous rather than merely untidy. A build with a duplicate key looks, from every observable signal, exactly like a build without one. There is no error to see and no obvious symptom to trace back — only a string that behaves as if it were never written, because functionally it wasn't.

The dangerous case: duplicates with divergent values

Not all duplicate keys are equally harmful. If two rows for the same key happen to hold the identical value, last-one-wins is functionally harmless — redundant, but not wrong. The dangerous case is two rows for the same key holding different values: an old draft of a line left in the file alongside its revision, or two translators independently filling in the same missing key with different wording. Here, which value ships is decided by file order rather than by anyone's intent, and it can silently flip between builds if the file gets re-sorted or re-exported in a different order.

Duplicates across files

The same failure mode shows up between files, not just within one. If a project splits localization content across multiple files — by screen, by feature, by content type — and the same key ends up defined in two of them with different values, the outcome depends entirely on the order those files are loaded or merged at build time. This is harder to catch by eye than an in-file duplicate, since no single file looks wrong on its own; the collision only exists at the level of the combined key space across the whole project.

Why parsers accept duplicates without complaint

This is not a bug in the parsers — it is a direct consequence of how the formats are commonly represented in memory. A JSON object and most in-language dictionary types are built to accept repeated key assignment as ordinary, later-write-wins behavior; that is standard, well-defined behavior for the data structure, not a parsing failure. CSV has no concept of keys at all until an application decides one column is the key. Uniqueness is a rule the localization workflow needs to enforce on top of the format — it is not something the format enforces on your behalf.

Detection on import, and prevention with a single source of truth

Because nothing in the format or the parser will surface a duplicate on its own, detection has to be a deliberate step: scan every file's key list for repeats at import time, and treat any hit as a blocking issue rather than a warning to skim past, since a duplicate can silently swap the value your build ships. This check is cheap — comparing a list of strings for repeats — and worth running on every import, not occasionally.

The more durable fix is structural: keep a single source of truth for the key list, generated or exported in one place, rather than letting keys be typed independently into multiple spreadsheets, branches, or files that later get combined. When there is exactly one place a key can be created, there is no seam left for the same key to be created twice.

Related articles