Back to Blog
Guide
Featured

Mobile App Localization: The Complete 2024 Guide

Everything you need to know about localizing iOS and Android apps. From planning to implementation, with best practices and tools.

IntlPull Team
IntlPull Team
Engineering
November 15, 202414 min read

Why Mobile Localization Matters

  • 75% of users prefer apps in their native language
  • Localized apps see up to 128% more downloads
  • Revenue increases 25-40% when adding key languages
  • App store rankings improve in local markets
  • Mobile Localization Fundamentals

    iOS Localization

    iOS uses .strings files and .stringsdict for plurals:

    // Localizable.strings (English)
    "welcome_title" = "Welcome to Our App";
    "items_count" = "%d items";
    "settings_button" = "Settings";
    // Usage in Swift
    let title = NSLocalizedString("welcome_title", comment: "Welcome screen title")
    // Or with SwiftUI
    Text("welcome_title")

    File Structure:

    /YourApp
      /en.lproj
        Localizable.strings
        InfoPlist.strings
      /es.lproj
        Localizable.strings
        InfoPlist.strings
      /ja.lproj
        Localizable.strings
        InfoPlist.strings

    Android Localization

    Android uses XML resource files:

    <!-- res/values/strings.xml (English) -->
    <resources>
        <string name="welcome_title">Welcome to Our App</string>
        <string name="items_count">%d items</string>
        <string name="settings_button">Settings</string>
    </resources>
    <!-- res/values-es/strings.xml (Spanish) -->
    <resources>
        <string name="welcome_title">Bienvenido a Nuestra App</string>
        <string name="items_count">%d elementos</string>
        <string name="settings_button">Configuración</string>
    </resources>
    // Usage in Kotlin
    val title = getString(R.string.welcome_title)

    React Native Localization

    Common approach with react-i18next:

    // i18n.js
    import i18n from 'i18next';
    import { initReactI18next } from 'react-i18next';
    
    i18n.use(initReactI18next).init({
      resources: {
        en: { translation: require('./locales/en.json') },
        es: { translation: require('./locales/es.json') },
      },
      lng: 'en',
      fallbackLng: 'en',
    });
    // Component usage
    import { useTranslation } from 'react-i18next';
    
    function WelcomeScreen() {
      const { t } = useTranslation();
      return <Text>{t('welcome_title')}</Text>;
    }

    The App Store Problem

    Traditional mobile localization has a major issue: updating translations requires a new app release.

    The painful process:

  • Translator makes changes
  • Developer exports translations
  • Build new app version
  • Submit to App Store/Play Store
  • Wait 1-7 days for review
  • Hope users actually update
  • What can go wrong:

  • Typo in production? Wait a week to fix it.
  • Seasonal campaign? Hope it's approved in time.
  • Wrong translation? Users see it for days.
  • A/B test copy? Build two app versions.
  • OTA Translation Updates: The Solution

    Over-the-Air (OTA) updates let you push translation changes directly to users without app store releases.

    How It Works

  • Translations are hosted on a CDN
  • App checks for updates at launch
  • Only changed strings are downloaded (delta updates)
  • New translations appear instantly
  • IntlPull OTA Implementation

    iOS (Swift):

    import IntlPull
    
    // Initialize at app launch
    IntlPull.configure(
        projectId: "your-project-id",
        apiKey: "your-api-key"
    )
    
    // Use translations
    let title = IntlPull.t("welcome_title")
    
    // Translations update automatically in background

    Android (Kotlin):

    import com.intlpull.sdk.IntlPull
    
    // Initialize in Application class
    IntlPull.configure(
        projectId = "your-project-id",
        apiKey = "your-api-key"
    )
    
    // Use translations
    val title = IntlPull.t("welcome_title")

    React Native:

    import IntlPull from '@intlpull/react-native';
    
    // Initialize
    IntlPull.configure({
      projectId: 'your-project-id',
      apiKey: 'your-api-key',
    });
    
    // Use hook
    function WelcomeScreen() {
      const { t } = useIntlPull();
      return <Text>{t('welcome_title')}</Text>;
    }

    Benefits of OTA Updates

    AspectTraditionalOTA
    Update Time1-7 daysInstant
    User ActionMust update appAutomatic
    Typo FixNew release30 seconds
    A/B TestingMultiple buildsDashboard toggle
    Seasonal ContentPlan weeks aheadUpdate anytime
    Update Time1-7 daysInstant
    User ActionMust update appAutomatic
    Typo FixNew release30 seconds
    A/B TestingMultiple buildsDashboard toggle
    Seasonal ContentPlan weeks aheadUpdate anytime
    Update Time1-7 daysInstant
    User ActionMust update appAutomatic
    Typo FixNew release30 seconds
    A/B TestingMultiple buildsDashboard toggle
    Seasonal ContentPlan weeks aheadUpdate anytime
    User ActionMust update appAutomatic
    Typo FixNew release30 seconds
    A/B TestingMultiple buildsDashboard toggle
    Seasonal ContentPlan weeks aheadUpdate anytime
    Typo FixNew release30 seconds
    A/B TestingMultiple buildsDashboard toggle
    Seasonal ContentPlan weeks aheadUpdate anytime
    A/B TestingMultiple buildsDashboard toggle
    Seasonal ContentPlan weeks aheadUpdate anytime
    Seasonal ContentPlan weeks aheadUpdate anytime

    Localization Best Practices

    1. Design for Localization

    Allow for text expansion:

  • German text is ~30% longer than English
  • Use flexible layouts
  • Avoid fixed-width text containers
  • // Bad
    label.frame = CGRect(x: 0, y: 0, width: 100, height: 20)
    
    // Good
    label.sizeToFit()
    // or use Auto Layout constraints

    2. Handle Pluralization

    English has 2 plural forms. Arabic has 6. Russian has 3.

    iOS (stringsdict):

    <dict>
        <key>items_count</key>
        <dict>
            <key>NSStringLocalizedFormatKey</key>
            <string>%#@items@</string>
            <key>items</key>
            <dict>
                <key>NSStringFormatSpecTypeKey</key>
                <string>NSStringPluralRuleType</string>
                <key>one</key>
                <string>%d item</string>
                <key>other</key>
                <string>%d items</string>
            </dict>
        </dict>
    </dict>

    Android:

    <plurals name="items_count">
        <item quantity="one">%d item</item>
        <item quantity="other">%d items</item>
    </plurals>

    3. Format Numbers, Dates, Currencies

    // iOS
    let formatter = NumberFormatter()
    formatter.numberStyle = .currency
    formatter.locale = Locale.current
    let price = formatter.string(from: 99.99)
    // US: "$99.99", Germany: "99,99 €", Japan: "¥100"
    // Android
    val format = NumberFormat.getCurrencyInstance(Locale.getDefault())
    val price = format.format(99.99)

    4. Handle RTL Languages

    Arabic, Hebrew, and Persian read right-to-left.

    // iOS - mostly automatic, but check:
    view.semanticContentAttribute = .forceRightToLeft
    <!-- Android - enable RTL support -->
    <application android:supportsRtl="true">

    5. Localize Images and Media

    Some images contain text or culturally-specific content:

    // iOS
    let imageName = NSLocalizedString("hero_image", comment: "")
    imageView.image = UIImage(named: imageName)

    6. Test with Pseudolocalization

    Before real translations, test with pseudo-translations:

    "Welcome" → "[Ẃéĺćőḿé !!!]"

    This helps find:

  • Hardcoded strings
  • Layout issues with longer text
  • Missing translations
  • Translation Workflow

    1. Extract Strings

    Manual:

  • Search for hardcoded strings
  • Move to resource files
  • Hope you didn't miss any
  • With IntlPull CLI:

    # Automatically find and extract strings
    npx intlpull scan --platform ios
    npx intlpull scan --platform android

    2. Manage Translations

    Without a TMS:

  • Email strings to translators
  • Receive translations back
  • Manually merge into resource files
  • Track what's translated in spreadsheets
  • With IntlPull:

  • Upload once to dashboard
  • Translators work in web UI
  • AI fills in missing translations
  • Pull translations with one command
  • 3. Deploy Translations

    Traditional:

    # Build new app version
    xcodebuild archive...
    # Submit to App Store
    # Wait...

    With OTA:

    # Just push to IntlPull
    npx intlpull push
    
    # Or click "Publish" in dashboard
    # Users get updates immediately

    Common Mistakes

    1. Concatenating Strings

    // Bad - word order varies by language
    let message = "Hello " + name + ", welcome!"
    
    // Good
    let message = String(format: NSLocalizedString("greeting", comment: ""), name)
    // "greeting" = "Hello %@, welcome!"

    2. Assuming Text Length

    // Bad
    button.setTitle("OK", for: .normal)
    button.frame.size.width = 40 // Breaks in German: "Einverstanden"
    
    // Good
    button.sizeToFit()
    // or use >= constraints

    3. Hardcoding Date Formats

    // Bad - US format won't work everywhere
    let dateString = "12/31/2024"
    
    // Good
    let formatter = DateFormatter()
    formatter.dateStyle = .medium
    formatter.locale = Locale.current

    4. Forgetting App Store Metadata

    Don't forget to localize:

  • App name
  • Description
  • Keywords
  • Screenshots
  • What's New text
  • Tools Comparison

    ToolOTAAICLIPrice
    IntlPullYesYesYes$11-115/mo
    LokaliseNoYesYes$90+/mo
    CrowdinNoBasicYes$40/user
    PhraseNoYesYes$125+/mo
    IntlPullYesYesYes$11-115/mo
    LokaliseNoYesYes$90+/mo
    CrowdinNoBasicYes$40/user
    PhraseNoYesYes$125+/mo
    IntlPullYesYesYes$11-115/mo
    LokaliseNoYesYes$90+/mo
    CrowdinNoBasicYes$40/user
    PhraseNoYesYes$125+/mo
    LokaliseNoYesYes$90+/mo
    CrowdinNoBasicYes$40/user
    PhraseNoYesYes$125+/mo
    CrowdinNoBasicYes$40/user
    PhraseNoYesYes$125+/mo
    PhraseNoYesYes$125+/mo

    Getting Started

    Step 1: Audit Current State

  • How many strings do you have?
  • How many languages?
  • How often do you update translations?
  • Step 2: Choose Your Tools

    For most mobile teams, IntlPull offers the best combination:

  • OTA updates (exclusive feature)
  • AI translation
  • CLI automation
  • Affordable pricing
  • Step 3: Set Up

    # Install CLI
    npm install -g @intlpull/cli
    
    # Initialize project
    intlpull init
    
    # Scan for strings
    intlpull scan --platform ios
    intlpull scan --platform android
    
    # Add OTA SDK to your app
    # iOS: pod 'IntlPull'
    # Android: implementation 'com.intlpull:sdk:1.0.0'

    Step 4: Integrate OTA

    Follow platform-specific guides in our documentation.

    Conclusion

    Mobile localization doesn't have to be painful. With the right approach:

  • Design for flexibility from the start
  • Use proper localization APIs (not string concatenation)
  • Implement OTA updates to iterate quickly
  • Automate with CLI tools to save time
  • Ready to simplify mobile localization? Start free with IntlPull and ship translations without app store delays.

    mobile
    ios
    android
    localization
    react-native
    guide
    Share:

    Ready to simplify your i18n workflow?

    Start managing translations with IntlPull. Free tier included.