Before You Paste Anything Into ChatGPT: A Pre-Translation Checklist for Mobile Localization Files (strings.xml, .strings, .stringsdict, JSON)
90% of AI translation disasters are set up before the first token is generated. The LLM isn't the problem — what you hand it, and how, is. A structured pre-translation checklist catches the conditions that guarantee bad output, so you can actually use AI's speed without the debugging session that follows when you skip it.
This is the third post in this series. If you haven't read the first two, post one covers plural failure modes across platforms and post two is the debugging playbook for after a bad AI translation run. If you're already dealing with broken output, start there. If you want to prevent it, keep reading.
Why Pre-Translation Prep Is the Highest-Leverage Step
LLMs are pattern-matchers. They're very good at producing output that looks correct, which is a distinct and considerably more dangerous property than actually being correct. The gap between those two things is where localization bugs live.
There are four categories of failure that manifest before a single string is processed:
- Missing context. The LLM doesn't know if a string is a button label, an error message, or a dialog title. It will guess, and it will often be wrong in ways that look plausible until a native speaker sees them.
- Unprepared file structure. Malformed XML, nested JSON without documented hierarchy, plural blocks mixed with flat strings — all of these cause structural corruption in output that your validators will catch only if you run them.
- No terminology constraints. Without a glossary, the LLM will translate brand names, invent preferred translations for technical terms, and apply whatever formality register it thinks fits the vibe.
- No locale-specific rules loaded. CLDR plural categories, RTL requirements, positional placeholder ordering — none of this is reliably inferred from locale code alone.
What follows is a checklist organized around those four failure categories, plus two supporting sections on output contracts and post-translation validation. Work through these before you send a single batch.
Structural issues in your source file become structural issues in your translated file. This section is not glamorous, but skipping it is how you end up with a strings.xml that crashes the build parser.
Android strings.xml
- [ ] Validate XML is well-formed before you touch it. Run
xmllint --noout res/values/strings.xml and fix any errors before extraction. Sending malformed XML to an LLM produces malformed XML back — the model will reconstruct structure from pattern, not from rules.
- [ ] Escape all apostrophes and ampersands in source strings. Apostrophes require a backslash (
don\'t) and ampersands require &. LLMs will sometimes drop escaping in output, especially when the translated language uses different quotation conventions.
- [ ] Mark
translatable="false" on strings that must not be translated, then strip them from the payload you send. Brand names, deep-link URLs, analytics event names, and non-translatable tokens should never reach the LLM. Tag them at the source.
<!-- These should never leave your codebase in a translation payload -->
<string name="app_name" translatable="false">Acme</string>
<string name="deep_link_scheme" translatable="false">acme://</string>
- [ ] List every
<plurals> block separately. Do not embed plural blocks in a wall of regular strings. Send them as a distinct batch with explicit instructions. Mixed batches routinely produce output where plural structure is flattened into a single string.
<!-- Send this separately, with explicit CLDR category instructions -->
<plurals name="items_selected">
<item quantity="one">%d item selected</item>
<item quantity="other">%d items selected</item>
</plurals>
For a deeper look at Android plural edge cases, see Android plurals explained.
iOS .strings
- [ ] Run
plutil -lint Localizable.strings on source first. A lint-clean source file is the minimum bar.
- [ ] Extract comment blocks and send them alongside their string. The
/* comment */ format in .strings files is your context layer. This is the single highest-value piece of information you can provide and the one most commonly stripped before sending to an LLM.
/* Title of the subscription paywall screen, shown after free trial expires */
"paywall_title" = "Your free trial has ended.";
/* CTA button on the paywall screen — keep short, max 3 words */
"paywall_cta" = "Start free trial";
- [ ] Flag strings with
%@ vs %1$@ positional variants explicitly. Positional specifiers exist because word order changes between locales. Tell the LLM which format you're using and that the specifier must appear in the output unchanged.
iOS .stringsdict
This format is the most structurally fragile of the four. An LLM that doesn't understand plist structure will corrupt it silently.
- [ ] Validate plist structure before extraction. Run
plutil -lint Localizable.stringsdict. Fix any errors.
- [ ] For each target locale, pre-populate the key scaffold with the correct CLDR categories before sending. Do not let the LLM infer which plural categories are needed. Russian requires
one, few, many, and other. Arabic requires all six. Look these up at cldr.unicode.org and hard-code the scaffold.
- [ ] Lock the
NSStringPluralRuleType key names as literals in your prompt. They must not be translated. NSStringPluralRuleType, one, other, few, many, zero are structural keys, not natural language.
For more on .stringsdict structure and the failure modes around it, iOS pluralization with .stringsdict covers the full surface area.
React Native JSON (i18next / react-intl)
- [ ] Flatten nested JSON or document the nesting depth explicitly. If you send nested JSON without telling the LLM the nesting must be preserved, you will occasionally get back a flattened structure, a re-nested structure with different key names, or both.
// If your structure looks like this, document it or flatten before sending
{
"settings": {
"notifications": {
"title": "Notifications",
"push_label": "Push Notifications"
}
}
}
- [ ] Identify ICU MessageFormat strings and handle them in a separate batch. ICU syntax (
{count, plural, one {# item} other {# items}}) requires different handling than plain strings. Mixing them into a general batch produces unpredictable results. See React Native pluralization explained for the specifics.
- [ ] Pin your interpolation delimiter syntax in the prompt. i18next uses
{{variable}}. react-intl uses {variable}. Python-style libraries use %(variable)s. Specify exactly which delimiter your app uses and that it must appear verbatim in output. This is a documented source of placeholder corruption.
Section 2: Context Preparation
Context is what separates a translation that's technically correct from one that's actually usable. LLMs are capable of good contextual translation — but only if you provide the context. They won't ask.
- [ ] Attach a screen description or screenshot reference to each string, or each batch of related strings. "This appears on the paywall CTA button" changes the translation dramatically compared to sending the string alone. A button CTA and an in-line instruction might be identical in English and require completely different forms in Japanese.
- [ ] Specify the UI surface explicitly. Button labels should be terse. Error messages should be empathetic. Tooltips should be informative. These aren't stylistic preferences — they're functional requirements that differ by locale.
- [ ] Specify grammatical gender requirements if your app exposes them. Some locales require gender agreement across multiple strings (e.g., French, Spanish, Arabic). If the user's selected gender is passed into a string, every string in that set needs to be translated consistently.
- [ ] Note character or line length constraints. Watch face complications, push notification titles, widget labels, and app store short descriptions all have hard limits that a translator working from a desktop doesn't see. Push notifications in particular have well-known length constraints that vary by platform.
Section 3: Terminology Constraints
Without a glossary, an LLM will make terminology decisions on every run, and they won't be consistent across runs. For product-specific terms, that's a problem on day one. For established technical or category terms, it becomes apparent when native speakers notice you've used an uncommon phrasing for something the whole ecosystem calls something else.
- [ ] Prepare a glossary of app-specific terms with do-not-translate or preferred-translation flags. Feature names, brand names, and technical terms should be locked. Format it in whatever way is easiest to include inline in your prompt — a simple key-value list is fine.
Glossary (enforce strictly):
- "Acme Pro" → do not translate
- "Workspace" → [French] "Espace de travail" (not "Espace de travaux")
- "Dashboard" → [French] "Tableau de bord" (not "Panneau de bord")
- [ ] Include competitor or category terms that have established translations in the target locale. What your industry calls something matters. If you're in fintech, the established French translation of "savings account" is not something you want to improvise.
- [ ] Specify formality register.
tu vs. vous in French is not a stylistic question — it defines your product's relationship to the user. Korean and Japanese have multiple levels of speech formality. Specify what you want, in the prompt, every time.
Section 4: Locale-Specific Rules to Load Into Your Prompt
This section is the one that feels like over-engineering until you've shipped a language with wrong plural forms or broken bidirectional text. Then it feels obvious.
- [ ] Look up CLDR plural categories for the target locale and include them explicitly in the prompt. Don't assume. Polish has five categories. Chinese has two. Arabic has six. Mandating the correct set is your responsibility, not the LLM's.
- [ ] Note RTL requirement if targeting Arabic, Hebrew, Persian, or Urdu. Flag any strings with embedded directionality characters (
\u200F, \u200E, \u202A–\u202E). The RTL localization pre-release checklist covers the full scope of what RTL introduces.
- [ ] Note number, currency, and date format expectations if any strings contain format patterns. If your strings include format patterns like
MMM d, yyyy or #,##0.00, note what the target locale expects. Date, number, and currency formatting across platforms covers the specifics in detail.
- [ ] Flag locales where your placeholder format has known LLM confusion risk. Positional specifiers like
%1$s and %2$s in German are a well-documented failure point: the LLM will sometimes reorder the specifiers to match German word order, which is precisely what positional specifiers exist to prevent.
Section 5: Output Contract (What You Tell the LLM to Return)
Specifying what you want back is as important as specifying what you're sending. An output contract removes ambiguity from the LLM's completion behavior and makes your post-processing deterministic.
- [ ] Specify exact output format. Return only the resource file format. No explanation. No markdown wrapping. No commentary. If you're sending
strings.xml, you want strings.xml back — not a code block inside a paragraph of notes.
- [ ] Specify that all XML/plist/JSON structure must be preserved exactly. Attribute order, indentation, and element structure should be unchanged. A diff against your source structure should show only the string values modified.
- [ ] Specify that placeholder tokens must appear in the output verbatim, unchanged. Name them explicitly in the prompt if your batch contains any: "The token
%1$s must appear in every translated string that contains it in the source. Do not alter, remove, or reorder it."
- [ ] Ask the LLM to flag any string it is uncertain about rather than guess. A flagged string you can review is better than a confidently-wrong string you ship. Include a specific flag format so you can parse it programmatically:
[UNCERTAIN: <reason>] works well.
- [ ] Set a maximum batch size. Recommend 50 strings per call as a ceiling. Context drift and hallucination risk both increase with batch size. Longer contexts also increase the likelihood that the LLM drops structural elements or ignores constraints stated earlier in the prompt.
Section 6: Post-Translation Validation Gate (Don't Skip This)
The checklist above reduces the probability of bad output. This section is how you catch what still gets through.
Before committing any LLM-translated output to your codebase:
- Re-run your format validator.
xmllint --noout for Android, plutil -lint for iOS .strings and .stringsdict, JSON.parse() for JSON. A structurally invalid file that reaches CI will fail the build — but it's faster and cheaper to catch it here.
- Run a placeholder parity check. Every placeholder in the source string should appear in the translated string. Missing or added placeholders are the most common class of LLM output error. See post two in this series for a script that does this.
- Run a CLDR plural category completeness check. For every
<plurals> block and .stringsdict entry, verify that the translated file contains exactly the required categories for the target locale — no more, no fewer. Extra categories are silently ignored on some platforms. Missing categories produce runtime fallback behavior that may or may not be visible to users.
For teams looking to automate this validation step rather than run it manually, the solutions page on AI translation problems covers tooling that integrates into the pipeline.
Also worth noting: build-time locale validation in CI can catch a meaningful percentage of these issues automatically. Eight checks your CI pipeline should run before every release covers the implementation.
The Honest Bottom Line
This checklist is significant effort. That's intentional, not incidental.
Safe one-shot LLM translation isn't a lightweight shortcut — it's a workflow with real setup cost. Teams that treat it as a lightweight shortcut are the ones writing debugging playbooks afterward.
For a one-off localization push into a new language, the checklist is probably worth maintaining manually. For teams translating at scale, on a recurring cadence, or across more than three or four locales simultaneously, the calculus shifts. A managed platform that bakes context extraction, terminology enforcement, format validation, and CLDR rule compliance into the pipeline is often faster and cheaper than maintaining this infrastructure yourself — and the risk profile is substantially different.
If you're evaluating that decision, the safe AI translation solutions page covers what that architecture looks like in practice.
Conclusion: The Full Checklist
Everything above, condensed into a copyable reference:
PRE-TRANSLATION CHECKLIST — MOBILE LOCALIZATION FILES
FILE FORMAT PREPARATION
Android strings.xml
[ ] Validate XML with xmllint --noout before extraction
[ ] Escape all apostrophes (\') and ampersands (&) in source
[ ] Tag translatable="false" on brand names, URLs, tokens — strip from payload
[ ] Extract <plurals> blocks into a separate batch
iOS .strings
[ ] Run plutil -lint on source file
[ ] Include /* comment */ blocks alongside each string in your payload
[ ] Flag %@ vs %1$@ positional variants explicitly in prompt
iOS .stringsdict
[ ] Validate plist with plutil -lint
[ ] Pre-populate CLDR plural category scaffold for target locale before sending
[ ] Lock NSStringPluralRuleType key names as do-not-translate literals
React Native JSON
[ ] Flatten nested JSON or document nesting depth explicitly
[ ] Send ICU MessageFormat strings in a separate batch
[ ] Pin interpolation delimiter syntax in prompt ({{var}} vs {var} vs %(var)s)
CONTEXT PREPARATION
[ ] Include screen/surface description with each string or string batch
[ ] Specify UI surface type: button, error, tooltip, notification, etc.
[ ] Specify grammatical gender requirements if applicable
[ ] Note character/line length constraints for constrained surfaces
TERMINOLOGY CONSTRAINTS
[ ] Provide app-specific glossary with do-not-translate or preferred-translation flags
[ ] Include established locale-specific category terms where relevant
[ ] Specify formality register (tu/vous, informal/honorific, etc.)
LOCALE-SPECIFIC RULES
[ ] Look up and specify CLDR plural categories for target locale
[ ] Flag RTL requirement and embedded directionality characters (Arabic, Hebrew, Persian, Urdu)
[ ] Note number/currency/date format expectations if strings contain format patterns
[ ] Flag positional placeholder locales with known word-order risk
OUTPUT CONTRACT
[ ] Specify output format exactly — resource file only, no explanation, no markdown
[ ] Require all XML/plist/JSON structure preserved exactly
[ ] Require all placeholder tokens appear verbatim and unchanged
[ ] Ask LLM to flag uncertain strings with [UNCERTAIN: reason] rather than guess
[ ] Cap batch size at ≤50 strings per call
POST-TRANSLATION VALIDATION
[ ] Re-run xmllint / plutil -lint / JSON.parse on output
[ ] Run placeholder parity check (every placeholder in source appears in translation)
[ ] Run CLDR plural category completeness check for every plural block
For the full context on where these checks come from and what they catch: post one on plural failure modes covers the category-level failures, and post two on debugging bad AI translation runs covers recovery. This checklist is what you run before you get to either of those.
Ready to localize your app?
Get started free — no credit card required.
Start Translating →