GetTranslated.AI
AI Translation & Automation

The Right Way to Use AI for Mobile App Translation: Platform-Specific Guide for strings.xml, .strings, and React Native JSON

Getting AI translation right for mobile apps isn't just about feeding your resource files into ChatGPT and hoping for the best. After showing you all the ways AI can destroy your strings.xml and .strings files, it's time for the practical solution: how to actually use AI translation safely without breaking your builds or corrupting your localization files.

This isn't about replacing professional translation workflows entirely — it's about doing AI translation correctly when you choose to use it, with proper safeguards and validation at every step.


Pre-processing: Extract Only What Needs Translation

The first rule of safe AI translation is never send your entire resource file to an AI model. You need to extract translatable content while preserving the structural elements that AI models love to mangle.

Here's what proper extraction looks like for each platform:

Android strings.xml Extraction

import xml.etree.ElementTree as ET
import re

def extract_android_strings(xml_file_path):
    """Extract translatable strings while preserving structure"""
    tree = ET.parse(xml_file_path)
    root = tree.getroot()

    translatable_content = {}

    for string_elem in root.findall('.//string'):
        # Skip non-translatable strings
        if string_elem.get('translatable') == 'false':
            continue

        key = string_elem.get('name')
        text = string_elem.text or ''

        # Preserve placeholders and formatting
        translatable_content[key] = {
            'text': text,
            'placeholders': re.findall(r'%\d+\$[sd]|%[sd]|{[^}]+}', text),
            'formatting': re.findall(r'<[^>]+>', text)
        }

    return translatable_content

def create_translation_input(content_dict, source_lang, target_lang):
    """Create clean input for AI translation"""
    translation_map = {}

    for key, data in content_dict.items():
        # Replace placeholders with safe tokens
        safe_text = data['text']
        placeholder_map = {}

        for i, placeholder in enumerate(data['placeholders']):
            token = f"PLACEHOLDER_{i}"
            placeholder_map[token] = placeholder
            safe_text = safe_text.replace(placeholder, token)

        translation_map[key] = {
            'original': safe_text,
            'placeholder_map': placeholder_map,
            'formatting': data['formatting']
        }

    return translation_map

iOS .strings Extraction

import re

def extract_ios_strings(strings_file_path):
    """Extract translatable content from .strings files"""
    with open(strings_file_path, 'r', encoding='utf-8') as f:
        content = f.read()

    # Match .strings format: "key" = "value";
    pattern = r'"([^"]+)"\s*=\s*"([^"]*(?:\\.[^"]*)*)"\s*;'
    matches = re.findall(pattern, content)

    translatable_content = {}

    for key, value in matches:
        # Preserve iOS-specific placeholders
        placeholders = re.findall(r'%[@sdfdioxX]|%\d+\$[@sdfdioxX]', value)

        translatable_content[key] = {
            'text': value,
            'placeholders': placeholders
        }

    return translatable_content

React Native JSON Extraction

import json

def extract_rn_strings(json_file_path):
    """Extract translatable content from React Native i18n JSON"""
    with open(json_file_path, 'r', encoding='utf-8') as f:
        data = json.load(f)

    def extract_recursive(obj, prefix=''):
        translatable = {}

        for key, value in obj.items():
            current_key = f"{prefix}.{key}" if prefix else key

            if isinstance(value, str):
                # Extract React i18next placeholders
                placeholders = re.findall(r'{{[^}]+}}|\$t\([^)]+\)', value)

                translatable[current_key] = {
                    'text': value,
                    'placeholders': placeholders
                }
            elif isinstance(value, dict):
                translatable.update(extract_recursive(value, current_key))

        return translatable

    return extract_recursive(data)

Safe Prompting Techniques That Actually Work

The key to safe AI translation is being extremely explicit about what the model should and shouldn't touch. Generic prompts lead to corrupted files — specific instructions prevent disasters.

The Safe Translation Prompt Template

def create_safe_translation_prompt(content_dict, source_lang, target_lang):
    """Generate explicit translation instructions"""

    prompt = f"""
TRANSLATION TASK: Translate mobile app strings from {source_lang} to {target_lang}

CRITICAL RULES:
1. NEVER modify text that starts with "PLACEHOLDER_" - these are protected tokens
2. NEVER change XML tags, HTML tags, or formatting markup
3. NEVER add or remove quotation marks, semicolons, or brackets
4. NEVER translate technical terms like variable names or API endpoints
5. Preserve all whitespace and line breaks exactly as shown

INPUT FORMAT: Each line shows KEY: TEXT_TO_TRANSLATE

TRANSLATION RULES FOR {target_lang}:
- Maintain formal/informal tone appropriate for mobile apps
- Use platform-standard terminology (e.g., "Settings" not "Preferences")
- Keep button text concise (mobile screen constraints)

EXAMPLE:
KEY: welcome_message
ORIGINAL: "Welcome to our app, PLACEHOLDER_0!"
TRANSLATION: "Bienvenue dans notre application, PLACEHOLDER_0!"

NOW TRANSLATE THESE STRINGS:

"""

    for key, data in content_dict.items():
        prompt += f"KEY: {key}\n"
        prompt += f"ORIGINAL: \"{data['original']}\"\n"
        prompt += f"TRANSLATION: \n\n"

    return prompt

def parse_translation_response(response_text, original_dict):
    """Parse AI response back into structured format"""
    translations = {}

    # Extract translations using pattern matching
    pattern = r'KEY:\s*([^\n]+)\s*\n.*?TRANSLATION:\s*"([^"]*(?:\\.[^"]*)*)"'
    matches = re.findall(pattern, response_text, re.DOTALL)

    for key, translation in matches:
        key = key.strip()
        if key in original_dict:
            # Restore placeholders
            restored_translation = translation
            for token, placeholder in original_dict[key]['placeholder_map'].items():
                restored_translation = restored_translation.replace(token, placeholder)

            translations[key] = restored_translation

    return translations

Post-processing Validation: Catch Errors Before They Break Production

Validation is where most teams fail with AI translation. You need automated checks that catch structural corruption, missing placeholders, and format violations.

Comprehensive Validation Scripts

class TranslationValidator:
    def __init__(self, platform_type):
        self.platform = platform_type
        self.errors = []
        self.warnings = []

    def validate_android_xml(self, original_content, translated_content):
        """Validate Android strings.xml translations"""
        for key in original_content:
            if key not in translated_content:
                self.errors.append(f"Missing translation for key: {key}")
                continue

            original = original_content[key]['text']
            translation = translated_content[key]

            # Check placeholder integrity
            orig_placeholders = set(original_content[key]['placeholders'])
            trans_placeholders = set(re.findall(r'%\d+\$[sd]|%[sd]|{[^}]+}', translation))

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

            # Check for XML corruption
            if '<string' in translation or 'name=' in translation:
                self.errors.append(f"XML structure found in translation content: {key}")

            # Check for untranslated placeholders tokens
            if 'PLACEHOLDER_' in translation:
                self.errors.append(f"Untranslated placeholder token in {key}: {translation}")

            # Platform-specific validations
            if re.search(r'%\d+\$[sd]', original) and not re.search(r'%\d+\$[sd]', translation):
                self.warnings.append(f"Positional parameter format may be lost in {key}")

    def validate_ios_strings(self, original_content, translated_content):
        """Validate iOS .strings translations"""
        for key in original_content:
            if key not in translated_content:
                self.errors.append(f"Missing translation for key: {key}")
                continue

            original = original_content[key]['text']
            translation = translated_content[key]

            # Check iOS-specific placeholders
            orig_placeholders = set(re.findall(r'%[@sdfdioxX]|%\d+\$[@sdfdioxX]', original))
            trans_placeholders = set(re.findall(r'%[@sdfdioxX]|%\d+\$[@sdfdioxX]', translation))

            if orig_placeholders != trans_placeholders:
                self.errors.append(f"iOS placeholder mismatch in {key}")

            # Check for .strings format corruption
            if '" = "' in translation or '";' in translation:
                self.errors.append(f".strings format found in translation content: {key}")

    def validate_react_native_json(self, original_content, translated_content):
        """Validate React Native JSON translations"""
        for key in original_content:
            if key not in translated_content:
                self.errors.append(f"Missing translation for key: {key}")
                continue

            original = original_content[key]['text']
            translation = translated_content[key]

            # Check React i18next placeholders
            orig_placeholders = set(re.findall(r'{{[^}]+}}|\$t\([^)]+\)', original))
            trans_placeholders = set(re.findall(r'{{[^}]+}}|\$t\([^)]+\)', translation))

            if orig_placeholders != trans_placeholders:
                self.errors.append(f"React Native placeholder mismatch in {key}")

            # Check for JSON corruption
            if translation.startswith('{') or translation.endswith('}'):
                self.warnings.append(f"Possible JSON structure in translation: {key}")

    def get_validation_report(self):
        """Return comprehensive validation results"""
        return {
            'valid': len(self.errors) == 0,
            'errors': self.errors,
            'warnings': self.warnings,
            'error_count': len(self.errors),
            'warning_count': len(self.warnings)
        }

Platform-Specific Considerations That Matter

Each platform has quirks that will bite you if you're not careful. Here's what actually matters in production:

Android XML Specifics

Android's string resources have several gotchas that AI models consistently mess up:

def reconstruct_android_xml(original_tree, translations):
    """Safely rebuild Android strings.xml with translations"""
    root = original_tree.getroot()

    for string_elem in root.findall('.//string'):
        key = string_elem.get('name')

        if key in translations and string_elem.get('translatable') != 'false':
            # Preserve CDATA if present
            if string_elem.text and string_elem.text.strip().startswith('<![CDATA['):
                string_elem.text = f"<![CDATA[{translations[key]}]]>"
            else:
                string_elem.text = translations[key]

            # Preserve all attributes
            # AI often strips formatted="false" or other important attrs

    return ET.tostring(root, encoding='unicode')

iOS .strings Edge Cases

iOS has some specific formatting requirements that break easily:

def reconstruct_ios_strings(original_file_path, translations):
    """Rebuild .strings file preserving comments and formatting"""
    with open(original_file_path, 'r', encoding='utf-8') as f:
        content = f.read()

    # Preserve comments and empty lines
    lines = content.split('\n')
    result_lines = []

    for line in lines:
        # Check if it's a translation line
        match = re.match(r'^"([^"]+)"\s*=\s*"([^"]*)"\s*;\s*$', line.strip())

        if match and match.group(1) in translations:
            key = match.group(1)
            # Preserve original formatting/spacing
            spacing = re.match(r'^(\s*)', line).group(1)
            result_lines.append(f'{spacing}"{key}" = "{translations[key]}";')
        else:
            # Preserve comments, empty lines, etc.
            result_lines.append(line)

    return '\n'.join(result_lines)

React Native JSON Reconstruction

JSON requires careful handling to preserve nested structure:

def reconstruct_rn_json(original_data, translations):
    """Rebuild React Native JSON preserving structure"""

    def update_recursive(obj, prefix=''):
        for key, value in obj.items():
            current_key = f"{prefix}.{key}" if prefix else key

            if isinstance(value, str) and current_key in translations:
                obj[key] = translations[current_key]
            elif isinstance(value, dict):
                update_recursive(value, current_key)

    # Work on a copy to avoid mutating original
    result = json.loads(json.dumps(original_data))
    update_recursive(result)

    return result

CI/CD Integration That Actually Works

The final piece is integrating this safely into your development workflow. You need validation that runs automatically and fails fast when something goes wrong.

GitHub Actions Workflow

name: AI Translation Validation

on:
  pull_request:
    paths:
      - 'android/app/src/main/res/values*/**'
      - 'ios/App/App/Supporting Files/*.lproj/**'
      - 'src/i18n/*.json'

jobs:
  validate-translations:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v3

      - name: Setup Python
        uses: actions/setup-python@v4
        with:
          python-version: '3.9'

      - name: Install dependencies
        run: |
          pip install xml-parser requests

      - name: Validate Android strings
        run: |
          python scripts/validate_translations.py \
            --platform android \
            --source android/app/src/main/res/values/strings.xml \
            --target android/app/src/main/res/values-*/strings.xml

      - name: Validate iOS strings  
        run: |
          python scripts/validate_translations.py \
            --platform ios \
            --source ios/App/App/Supporting\ Files/en.lproj/Localizable.strings \
            --target ios/App/App/Supporting\ Files/*.lproj/Localizable.strings

      - name: Validate React Native JSON
        run: |
          python scripts/validate_translations.py \
            --platform react-native \
            --source src/i18n/en.json \
            --target src/i18n/*.json

      - name: Comment validation results
        if: failure()
        uses: actions/github-script@v6
        with:
          script: |
            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: 'Translation validation failed. Check the workflow logs for details.'
            })

Automated Translation Script

#!/usr/bin/env python3
"""
Safe AI translation script for mobile resource files
Usage: python translate_resources.py --platform android --source en --target fr
"""

import argparse
import sys
from pathlib import Path

def main():
    parser = argparse.ArgumentParser(description='Safely translate mobile resources with AI')
    parser.add_argument('--platform', choices=['android', 'ios', 'react-native'], required=True)
    parser.add_argument('--source', required=True, help='Source language code')
    parser.add_argument('--target', required=True, help='Target language code') 
    parser.add_argument('--validate-only', action='store_true', help='Only validate existing translations')

    args = parser.parse_args()

    # Initialize platform-specific handlers
    if args.platform == 'android':
        from android_translator import AndroidTranslator
        translator = AndroidTranslator()
    elif args.platform == 'ios':
        from ios_translator import IOSTranslator  
        translator = IOSTranslator()
    elif args.platform == 'react-native':
        from rn_translator import ReactNativeTranslator
        translator = ReactNativeTranslator()

    if args.validate_only:
        validator = TranslationValidator(args.platform)
        result = validator.validate_existing_translations(args.source, args.target)

        if not result['valid']:
            print(f"Validation failed with {result['error_count']} errors:")
            for error in result['errors']:
                print(f"  - {error}")
            sys.exit(1)
        else:
            print("All translations valid!")
            sys.exit(0)

    # Run full translation pipeline
    try:
        translator.translate_resources(args.source, args.target)
        print(f"Successfully translated {args.platform} resources from {args.source} to {args.target}")
    except Exception as e:
        print(f"Translation failed: {e}")
        sys.exit(1)

if __name__ == '__main__':
    main()

Putting It All Together: Production-Ready Workflow

Here's how this all works in a real development workflow. When you need to add a new language or update existing translations:

  1. Extract: Run the extraction script to pull translatable content from your source files
  2. Translate: Use the safe prompting template with explicit instructions for your AI model
  3. Validate: Run comprehensive validation to catch any structural issues or corrupted output
  4. Reconstruct: Rebuild your resource files preserving all original formatting and structure
  5. Test: Run your automated tests to ensure the app still builds and functions correctly

This approach gives you the speed benefits of AI translation while maintaining the reliability you need for production apps. The key insight is that AI translation isn't about replacing your entire localization workflow — it's about doing the translation step safely when you choose to use AI.

For teams managing more complex localization needs, including the broader workflow challenges around string management across platforms and preventing localization technical debt, AI translation is just one piece of a larger puzzle. But when you need that piece, doing it right matters.

The scripts and techniques here prevent the common corruption patterns that break builds and frustrate teams. Use them as a foundation, adapt them to your specific needs, and always validate before pushing to production.

Ready to localize your app?

Get started free — no credit card required.

Start Translating →