GetTranslated.AI
Mobile App Development

How to Safely Translate Mobile Resource Files with AI (Platform-Specific Guide for strings.xml, .strings, and React Native JSON)

If you've read about why AI translation destroys mobile resource files, you know the problem is real. But the solution isn't to avoid AI translation entirely — it's to add the constraints and validation that make it safe.

The key insight: AI excels at translation but fails at format preservation without explicit guidance. Give it clear rules, validate the output, and you get the speed benefits without the structural disasters.


The Foundation: Constraint-Based Translation

Raw AI translation treats your strings.xml file like a text document, not structured data. It doesn't understand that <string name="welcome">Hello %s</string> has specific XML syntax, placeholder requirements, and naming constraints that must be preserved.

The solution is constraint-based translation: providing explicit instructions about what can and cannot change, then validating that those constraints were followed.

Here's the pattern that works across all platforms:

  1. Extract translatable content only — separate text from structure
  2. Apply platform-specific constraints — preserve placeholders, handle plurals correctly
  3. Validate structural integrity — catch corruption before it reaches your build
  4. Integrate with CI/CD — make validation automatic, not manual

Android strings.xml: Preserving Structure and Placeholders

Android's strings.xml format has several gotchas that AI translation commonly breaks. The most critical are format specifiers (%s, %1$d), CDATA sections, and plural forms.

Safe Translation Process

First, extract only the translatable content:

<!-- Original strings.xml -->
<resources>
    <string name="welcome_message">Welcome back, %s!</string>
    <string name="item_count">You have %1$d items in your cart</string>
    <string name="html_content"><![CDATA[Visit <a href="https://example.com">our website</a>]]></string>
    <plurals name="notification_count">
        <item quantity="one">%1$d notification</item>
        <item quantity="other">%1$d notifications</item>
    </plurals>
</resources>

Create a constraint-aware prompt:

Translate the following Android string resources to Spanish. CRITICAL REQUIREMENTS:
1. Preserve ALL format specifiers exactly (%s, %1$d, etc.)
2. Do not modify CDATA sections or HTML tags
3. Keep plural quantity attributes unchanged
4. Maintain the same number of plural items

Original: Welcome back, %s!
Translate: [AI provides translation while preserving %s]

Validation Script

import xml.etree.ElementTree as ET
import re

def validate_android_translation(original_file, translated_file):
    """Validate that translated strings.xml preserves critical structure"""

    original = ET.parse(original_file).getroot()
    translated = ET.parse(translated_file).getroot()

    errors = []

    for orig_string in original.findall('string'):
        name = orig_string.get('name')
        trans_string = translated.find(f"string[@name='{name}']")

        if trans_string is None:
            errors.append(f"Missing translation for: {name}")
            continue

        # Check format specifiers
        orig_placeholders = set(re.findall(r'%\w*[sd]', orig_string.text or ''))
        trans_placeholders = set(re.findall(r'%\w*[sd]', trans_string.text or ''))

        if orig_placeholders != trans_placeholders:
            errors.append(f"Placeholder mismatch in {name}: {orig_placeholders} vs {trans_placeholders}")

    # Validate plurals
    for orig_plural in original.findall('plurals'):
        name = orig_plural.get('name')
        trans_plural = translated.find(f"plurals[@name='{name}']")

        if trans_plural is None:
            errors.append(f"Missing plural translation: {name}")
            continue

        orig_quantities = {item.get('quantity') for item in orig_plural.findall('item')}
        trans_quantities = {item.get('quantity') for item in trans_plural.findall('item')}

        if orig_quantities != trans_quantities:
            errors.append(f"Plural quantity mismatch in {name}")

    return errors

iOS .strings and .stringsdict: Handling Placeholders and Plurals

iOS localization uses two file types: .strings for simple key-value pairs and .stringsdict for complex pluralization. Each has specific formatting requirements that AI commonly breaks.

.strings File Translation

/* Localizable.strings (English) */
"welcome_message" = "Welcome back, %@!";
"formatted_price" = "Price: $%.2f";
"user_stats" = "%1$@ has %2$d friends";

The constraint prompt for iOS:

Translate these iOS .strings entries to French. REQUIREMENTS:
1. Keep ALL format specifiers unchanged (%@, %.2f, %1$@, %2$d)
2. Preserve the key names (left side of =)
3. Keep quotes and semicolons exactly as shown
4. Maintain comment structure

"welcome_message" = "Welcome back, %@!";

.stringsdict Validation

iOS plural handling is more complex, using .stringsdict files:

<!-- Localizable.stringsdict -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>notification_count</key>
    <dict>
        <key>NSStringLocalizedFormatKey</key>
        <string>%#@count@</string>
        <key>count</key>
        <dict>
            <key>NSStringFormatSpecTypeKey</key>
            <string>NSStringPluralRuleType</string>
            <key>NSStringFormatValueTypeKey</key>
            <string>d</string>
            <key>one</key>
            <string>%d notification</string>
            <key>other</key>
            <string>%d notifications</string>
        </dict>
    </dict>
</plist>

The validation script for iOS:

import re
import plistlib

def validate_ios_strings(original_file, translated_file):
    """Validate .strings file translation"""
    errors = []

    with open(original_file, 'r') as f:
        original_content = f.read()
    with open(translated_file, 'r') as f:
        translated_content = f.read()

    # Extract key-value pairs
    orig_strings = re.findall(r'"([^"]+)"\s*=\s*"([^"]+)";', original_content)
    trans_strings = re.findall(r'"([^"]+)"\s*=\s*"([^"]+)";', translated_content)

    orig_dict = dict(orig_strings)
    trans_dict = dict(trans_strings)

    for key, orig_value in orig_dict.items():
        if key not in trans_dict:
            errors.append(f"Missing translation for key: {key}")
            continue

        trans_value = trans_dict[key]

        # Check format specifiers
        orig_specs = set(re.findall(r'%[@df]|%\d+\$[@df]|%\.\d+f', orig_value))
        trans_specs = set(re.findall(r'%[@df]|%\d+\$[@df]|%\.\d+f', trans_value))

        if orig_specs != trans_specs:
            errors.append(f"Format specifier mismatch in {key}")

    return errors

def validate_stringsdict(original_file, translated_file):
    """Validate .stringsdict file structure"""
    with open(original_file, 'rb') as f:
        original = plistlib.load(f)
    with open(translated_file, 'rb') as f:
        translated = plistlib.load(f)

    errors = []

    for key in original.keys():
        if key not in translated:
            errors.append(f"Missing stringsdict key: {key}")
            continue

        orig_rule = original[key]
        trans_rule = translated[key]

        # Check that format keys are preserved
        if 'NSStringLocalizedFormatKey' in orig_rule:
            if orig_rule['NSStringLocalizedFormatKey'] != trans_rule.get('NSStringLocalizedFormatKey'):
                errors.append(f"Format key changed in {key}")

    return errors

Check out our complete guide to iOS pluralization for deeper .stringsdict handling.


React Native JSON: Nested Structures and ICU Pluralization

React Native localization typically uses JSON files, often with nested structures and ICU message format for plurals. This creates unique validation challenges.

Typical JSON Structure

{
  "common": {
    "welcome": "Welcome back, {username}!",
    "itemCount": "You have {count} {count, plural, one {item} other {items}}"
  },
  "errors": {
    "network": "Network error occurred",
    "validation": "Please check {field} field"
  },
  "navigation": {
    "home": "Home",
    "profile": "Profile"
  }
}

ICU Plural Handling

React Native often uses ICU message format for pluralization, which has strict syntax requirements:

{
  "notifications": "{count, plural, =0 {No notifications} one {# notification} other {# notifications}}"
}

Constraint Prompt for JSON

Translate this React Native localization JSON to German. CRITICAL RULES:
1. Keep ALL keys unchanged (left side of colons)
2. Preserve placeholder syntax exactly: {username}, {count}, {field}
3. Keep ICU plural syntax intact: {count, plural, one {...} other {...}}
4. Maintain JSON structure and nesting
5. Only translate the text values (right side of colons)

Validation Script

import json
import re

def validate_react_native_json(original_file, translated_file):
    """Validate React Native JSON translation"""
    with open(original_file, 'r') as f:
        original = json.load(f)
    with open(translated_file, 'r') as f:
        translated = json.load(f)

    errors = []

    def validate_nested(orig_obj, trans_obj, path=""):
        if isinstance(orig_obj, dict):
            for key in orig_obj.keys():
                current_path = f"{path}.{key}" if path else key

                if key not in trans_obj:
                    errors.append(f"Missing key: {current_path}")
                    continue

                if isinstance(orig_obj[key], dict):
                    validate_nested(orig_obj[key], trans_obj[key], current_path)
                else:
                    validate_string_value(orig_obj[key], trans_obj[key], current_path)

    def validate_string_value(orig_value, trans_value, path):
        if not isinstance(orig_value, str) or not isinstance(trans_value, str):
            return

        # Check placeholders
        orig_placeholders = set(re.findall(r'\{[^}]+\}', orig_value))
        trans_placeholders = set(re.findall(r'\{[^}]+\}', trans_value))

        if orig_placeholders != trans_placeholders:
            errors.append(f"Placeholder mismatch in {path}: {orig_placeholders} vs {trans_placeholders}")

        # Check ICU plural syntax
        if ', plural,' in orig_value:
            if ', plural,' not in trans_value:
                errors.append(f"ICU plural syntax corrupted in {path}")
            else:
                # Validate plural forms are preserved
                orig_forms = set(re.findall(r'(one|other|few|many|=\d+)\s*\{', orig_value))
                trans_forms = set(re.findall(r'(one|other|few|many|=\d+)\s*\{', trans_value))
                if orig_forms != trans_forms:
                    errors.append(f"ICU plural forms changed in {path}")

    validate_nested(original, translated)
    return errors

For more details on React Native pluralization complexities, see our complete guide to React Native pluralization.


CI/CD Integration: Making Validation Automatic

Manual validation catches some issues, but the real safety comes from automating these checks in your CI/CD pipeline. Here's how to integrate validation into your workflow:

GitHub Actions Example

name: Validate Translations

on:
  pull_request:
    paths:
      - '**/*.xml'
      - '**/*.strings'
      - '**/*.json'

jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2

      - name: Set up Python
        uses: actions/setup-python@v2
        with:
          python-version: '3.9'

      - name: Install dependencies
        run: |
          pip install lxml

      - name: Validate Android translations
        run: |
          python scripts/validate_android.py \
            --original android/app/src/main/res/values/strings.xml \
            --translated android/app/src/main/res/values-es/strings.xml

      - name: Validate iOS translations
        run: |
          python scripts/validate_ios.py \
            --original ios/Localization/en.lproj/Localizable.strings \
            --translated ios/Localization/es.lproj/Localizable.strings

      - name: Validate React Native translations
        run: |
          python scripts/validate_rn.py \
            --original src/localization/en.json \
            --translated src/localization/es.json

Integration with Translation Services

If you're using a service like GetTranslated.AI, you can configure these validation rules as part of your translation workflow. The key is ensuring that any AI translation service understands your platform-specific constraints upfront, rather than trying to fix issues after translation.

For teams managing this process in-house, check out our guide on string extraction automation to see how validation fits into a complete CI/CD workflow.


Common Validation Patterns and Regex

Here are the most critical regex patterns for catching corruption across platforms:

Android Format Specifiers

# Standard format specifiers
android_formats = r'%[sd]|%\d+\$[sd]|%\.\d+f'

# Positional arguments (critical for translations)
positional_args = r'%\d+\$[sdcboxXeEfFgGaA]'

iOS Format Specifiers

# Objective-C style format specifiers
ios_formats = r'%[@sdcboxXeEfFgGaA]|%\d+\$[@sdcboxXeEfFgGaA]|%\.\d+[fFeEgGaA]'

# NSLocalizedString compatible
nslocalized_formats = r'%@|%\d+\$@'

ICU Message Format

# ICU placeholders
icu_placeholders = r'\{[^}]+\}'

# ICU plural syntax
icu_plurals = r'\{[^,]+,\s*plural,.*?\}'

# ICU select syntax
icu_select = r'\{[^,]+,\s*select,.*?\}'

The Payoff: Fast, Safe Translation at Scale

When you implement constraint-based AI translation with proper validation, you get the best of both worlds: the speed and cost benefits of AI with the reliability of human oversight on structure.

Teams using this approach report 80%+ time savings on translation tasks while maintaining the code quality that manual translation provides. The key is understanding that AI translation isn't about replacing human judgment — it's about automating the mechanical parts while preserving the critical structural elements that make your app function correctly.

The validation scripts might seem like overhead, but they prevent the kinds of production bugs that cost far more than the time invested in building them. And once they're in place, your translation workflow becomes both faster and more reliable than pure human translation.

For teams scaling beyond manual translation management, these techniques form the foundation of a sustainable localization strategy that won't break as you add languages, platforms, or team members.

Ready to localize your app?

Get started free — no credit card required.

Start Translating →