AI & Technology

Why Translated Localization Files Break Apps (and What Must Stay Untouched)

Sep 23, 2026

Why Translated Localization Files Break Apps (and What Must Stay Untouched)

A Localization File Is Code With Sentences Inside It

PO, YAML, .properties, RESX, XLIFF, and JSON locale files look like lists of sentences, but only the values are prose. The keys are identifiers the code looks up; the placeholders are slots the code fills at runtime; the escapes and markup are syntax the parser depends on. A translator who treats the whole line as text will produce a file that reads beautifully and fails to load, or loads and then crashes the first time a screen formats a number. The failure usually shows up in one language only, which makes it the hardest kind of bug to catch in review.

Five Things That Must Survive Translation Unchanged

  • Keys and resource names — the code looks strings up by key, so a translated key is simply a missing string.
  • Placeholders — %s, %1$d, {0}, {name}, and ICU arguments are filled in by code; renaming or dropping one breaks formatting or throws at runtime.
  • Escapes and encoding rules — a .properties file read with the classic Properties.load(InputStream) method is treated as Latin-1, with other characters written as \uXXXX escapes, so raw non-Latin text can turn into mojibake.
  • Markup inside strings — HTML tags, XLIFF inline codes, and Markdown have to stay balanced, or the rendered string breaks the page around it.
  • File structure — YAML indentation, block scalars, anchors and aliases, and XML nesting are syntax; a shifted indent or a translated anchor name changes what the file means.

Plurals Are Not a Singular Plus an "s"

English has two plural forms, so it is tempting to think every language does. The GNU gettext manual shows why that fails: each language declares its own Plural-Forms rule, English uses nplurals=2, and Russian needs three forms chosen by the last digits of the number. A PO file translated into Russian with only two forms filled in compiles, runs, and prints the wrong grammar for every count ending in 2, 3, or 4. Correct plural handling means producing the number of msgstr entries the target language requires, not the number the source language happened to have.

Placeholders Sometimes Have to Move

Word order differs between languages, so a sentence with two values often has to put them the other way round. Positional placeholders such as %1$s and %2$s, or named ones such as {count} and {user}, exist exactly for that: the translation may reorder them, but every one must still be present exactly once. Anonymous placeholders such as a bare %s cannot be reordered safely, which is a problem worth fixing in the source strings before translation rather than after.

Format Rules Worth Knowing Before You Translate

Each format has its own sharp edges. In YAML, the specification gives a literal block (|) and a folded block (>) different line-break behaviour, and anchors (&) and aliases (*) let one node reuse another, so both must come through untouched. XLIFF 2.1 wraps original formatting in inline elements such as <ph> and <pc>, which a translation must keep in place around the right words. In .NET, RESX resources are looked up by name at runtime for the current UI culture, so a renamed data element silently falls back to the default language. And a Java .properties file treats the first unescaped =, :, or whitespace as the key separator, so a stray colon in a translated key splits it in two.

A Review Checklist That Catches Most Breakage

  1. Count placeholders in every source and target string — the multiset must match, even when the order does not.
  2. Diff the keys, not the values — any key present in the source and missing or renamed in the target is a bug.
  3. Load the translated file with the real parser (msgfmt, a YAML loader, ResXResourceReader) before shipping it.
  4. Check plural entries against the target language rule, not against the source language.
  5. Render the longest translation in the tightest UI element — text in other languages often runs longer than the English.
Why does an app crash in one language but not in others?

Almost always because a placeholder was changed in that language only — renamed, dropped, or duplicated. The code passes the same arguments to every language, so the one translation that no longer matches them fails when that screen is formatted.

Why does Russian need three plural strings when English needs two?

Plural rules belong to the language, not to the software. Gettext lets each language declare its own rule; Russian chooses between three forms based on the last digits of the number, so a translation with only two forms prints the wrong grammar for many counts.

Why do accented characters turn into garbage in a Java .properties file?

Because the classic Properties.load(InputStream) reads the file as Latin-1. Characters outside that range have to be written as \uXXXX escapes, or the file has to be loaded with a reader that uses the right encoding.

Can a translation change the order of placeholders?

Yes, when they are positional (%1$s, %2$s) or named ({count}, {user}) — reordering is often required for natural word order. Every placeholder must still appear exactly once. Bare %s placeholders cannot be reordered safely.

Why should keys never be translated?

Keys are identifiers, not text. The code asks for a string by its key; a translated key means the lookup fails and the app shows a raw key or falls back to the default language.

Translate the Values, Keep the Code

Upload a PO, YAML, .properties, or RESX file and get it back with keys, placeholders, escapes, and structure left exactly where they were.

Translate a Locale File Free