Translating App UI Strings With LLMs in October 2026: Protect Placeholders, Handle Plurals Correctly, and Enforce a Glossary Before You Ship

Machine translation of user interface strings used to be a quick way to get a rough first pass and a lot of cleanup. Large language models change that tradeoff: they follow instructions, keep a consistent tone, and can use context like a screenshot description or a developer note. They also fail in ways that are specific to software. A translated string that breaks a placeholder, drops a plural form, or renames your product can crash a screen or confuse every user in that locale.

This guide covers a practical pipeline for translating app strings with an LLM: what to send the model, how to protect the parts of a string that code depends on, how to handle plurals and gender, how to keep terminology consistent, and how to check the output automatically before it lands in a release.

A metro gate sign in Singapore showing the same instruction in several languages
Photo: Singapore Metro Gate Sign by Augapfel, CC BY 2.0, via Wikimedia Commons

Why UI strings are harder than ordinary text

An article or an email can be translated as one flowing piece. UI strings are short, isolated, and full of things that are not language at all. A typical resource file has entries like these:

"cart.items": "{count, plural, one {# item} other {# items}} in your cart",
"welcome": "Welcome back, {name}!",
"terms": "By continuing you agree to our <a href=\"/terms\">Terms</a>.",
"save": "Save"

Each one has a different risk. The first uses ICU MessageFormat plural syntax, which must stay structurally valid. The second has a named placeholder the code fills in at runtime. The third contains markup that must survive untouched. The last is a single word with no context: "Save" could mean store a file, rescue someone, or keep money, and many languages use different words for each.

So the job is not "translate this text." It is "translate the human-readable parts, preserve everything the code depends on, and pick the right meaning for a fragment you can barely see."

Step 1: Give the model context for every string

The biggest quality improvement usually comes from context, not from a bigger model. For each string, send:

  • The key. A key like settings.account.delete_button already tells the model it is a button label in account settings.
  • A developer note. Most localization formats support a comment field. Use it: "Button that permanently deletes the user's account. Keep under 20 characters."
  • Length limits. If the string sits in a fixed-width button or a tab bar, say so with a number.
  • Neighboring strings. Translating the whole screen or file section together lets the model keep wording consistent across a dialog's title, body, and buttons.
  • Audience and register. Formal or informal address matters in many languages (for example, du versus Sie in German). Decide once per locale and put it in the system prompt.

Batching by screen or feature beats sending strings one at a time. It is cheaper, and it gives the model the surrounding context a human translator would ask for.

Step 2: Protect placeholders and markup before the model sees them

Do not rely on an instruction like "don't change the placeholders." Models usually comply, but "usually" is not good enough when a broken placeholder throws an exception at runtime. Instead, replace protected spans with opaque tokens before translation and restore them afterward.

  1. Parse each string and find placeholders ({name}, %s, %1$d, {{count}}), HTML or XML tags, and anything else your format treats as code.
  2. Replace each with a numbered token such as ⟦0⟧, ⟦1⟧, and keep a map from token to original.
  3. Tell the model the tokens must appear exactly once each in the output, and that it may move them to fit the target grammar.
  4. After translation, verify every token is present exactly once, then swap the originals back in.

Allowing tokens to move is important. Word order differs between languages, so "Welcome back, {name}!" may need the name in a different position. What must not change is the set of tokens. For paired tags like <a>...</a>, use paired tokens and check they still nest correctly.

Step 3: Handle plurals with the target language's rules

English has two plural categories for most purposes: one and other. Many languages have more. The Unicode CLDR plural rules define the categories each locale uses, such as zero, one, two, few, many, and other. Polish, Russian, and Arabic all need categories that English does not have, while Japanese and Chinese typically use only other.

The practical approach:

  • Look up the required categories for the target locale from CLDR data (most i18n libraries ship it) rather than asking the model which ones exist.
  • Tell the model exactly which categories to produce: "Return ICU plural forms for these categories: one, few, many, other."
  • Validate the result with a real ICU MessageFormat parser. If a required category is missing or an extra one appears, reject and retry.

Gender and select expressions follow the same pattern. If your source uses {gender, select, female {...} male {...} other {...}}, the output must keep the same select keys, and the parser should confirm it.

Step 4: Enforce a glossary and a do-not-translate list

Inconsistent terminology is the most common complaint about machine-translated apps. The same feature gets three different names across screens, or the product name gets translated literally.

Keep two lists per locale:

  • Do-not-translate terms: product names, brand names, feature names you treat as proper nouns, and technical identifiers.
  • Glossary terms: source term, approved translation, and a short note. For example, "workspace" maps to one approved word in each language and stays that way everywhere.

Only send the glossary entries that actually appear in the current batch. A long glossary pasted into every prompt adds cost and dilutes attention. A simple case-insensitive match on the source text, plus stemming for languages where it matters, is enough to pick the relevant entries.

Then check the output: if a source string contains a glossary term, the translation should contain the approved target term. This check is fuzzy in languages with heavy inflection, so treat failures as warnings for review rather than hard rejections unless the term is in the do-not-translate list, which should match exactly.

Step 5: Request structured output and validate everything

Ask for a JSON object keyed by string key, and use your provider's structured output or JSON schema feature if it has one. That way you can match every result to its source without guessing. Then run automatic checks on each string:

  • All protected tokens present exactly once, tags properly paired.
  • The string parses as valid ICU MessageFormat (or whatever format you use).
  • Required plural and select categories present.
  • Length within the stated limit, measured in characters or, better, rendered width for the target font.
  • Do-not-translate terms preserved exactly.
  • No leftover source-language text, unless the source string was a name or code.
  • No added content: a translation that is much longer than typical for that language pair is worth flagging.

Strings that fail get retried once with the specific error included in the prompt ("Token ⟦1⟧ is missing"). If they fail again, route them to a human instead of retrying forever.

The Rosetta Stone on display in a museum case, showing the same decree written in three scripts
Photo: Rosetta Stone display in 1985 by RickDikeman, CC0, via Wikimedia Commons

Step 6: Translate only what changed

Re-translating the entire resource file on every release wastes money and, worse, churns wording that users and reviewers already accepted. Keep a translation memory: a store of source string, its hash, the locale, and the approved translation.

  • If the source text and its developer note are unchanged, reuse the stored translation.
  • If the source changed, translate it again and mark it for review.
  • If only the key was renamed, carry the translation over.

This also makes the pipeline easy to run in CI. A pull request that adds five strings triggers five translations, not five thousand.

Step 7: Put humans where they add the most value

An LLM pipeline with good checks can handle much of the volume, but it should not be the last word on everything. A reasonable split:

  • Auto-accept low-risk strings that pass every check: routine labels, tooltips, and settings descriptions in well-supported languages.
  • Human review for legal text, pricing and billing copy, safety warnings, marketing headlines, anything that failed a check twice, and languages your team cannot evaluate at all.
  • In-context review using screenshots of the translated build. Many problems, such as truncation, awkward line breaks, or a word that is correct in isolation but wrong next to an icon, only show up on screen.

When reviewers correct a translation, write the correction back to the translation memory and, if it reflects a terminology decision, to the glossary. Those corrections are the most valuable input you have for the next batch.

A minimal prompt template

System:
You translate software UI strings from English to {target_locale}.
Register: {formal_or_informal}.
Rules:
- Tokens like ⟦0⟧ must each appear exactly once. You may reorder them.
- Never translate these terms: {do_not_translate}.
- Use these approved translations: {glossary_subset}.
- Plural strings must use ICU syntax with categories: {cldr_categories}.
- Respect max_length when given.
Return JSON: {"key": "translation", ...}

User:
[{"key": "...", "source": "...", "note": "...", "max_length": 20}, ...]

Keep the template in version control alongside your glossary, and test changes to it against a fixed set of tricky strings before rolling them out to every locale.

Common mistakes to avoid

  • Translating strings with no context. Single words like "Open," "Post," or "Clear" are ambiguous in most languages. Add notes.
  • Trusting the prompt to protect placeholders. Tokenize and verify in code.
  • Asking the model for plural categories. Use CLDR data and tell the model which ones to produce.
  • Concatenating translated fragments at runtime. Building "You have " + count + " new messages" from pieces cannot be translated correctly in many languages. Fix the source string to use a single message with placeholders.
  • Skipping right-to-left checks. For Arabic and Hebrew, test the rendered UI. Mixed-direction strings with numbers, URLs, or product names can display in an unexpected order.

The short version

LLMs are good at the language part of localization. Your pipeline needs to handle the software part: give each string context, hide placeholders and markup behind tokens, generate plural forms from real CLDR rules, enforce your glossary, validate every result with a parser, translate only what changed, and send the risky strings to people. Do that, and machine translation stops being a rough draft and becomes a dependable part of shipping in more than one language.

Comments

Popular posts from this blog

Grok Bot - a step closer to AGI

Structured Outputs and Constrained Decoding for Reliable LLM APIs in Production (September 2026)

Tencent Hy4 Preview: Open 770B MoE Built for Real Work