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.
Why Mobile Localization Matters
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.stringsAndroid 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:
What can go wrong:
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
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 backgroundAndroid (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
| Aspect | Traditional | OTA |
|---|---|---|
| Update Time | 1-7 days | Instant |
| User Action | Must update app | Automatic |
| Typo Fix | New release | 30 seconds |
| A/B Testing | Multiple builds | Dashboard toggle |
| Seasonal Content | Plan weeks ahead | Update anytime |
| Update Time | 1-7 days | Instant |
|---|---|---|
| User Action | Must update app | Automatic |
| Typo Fix | New release | 30 seconds |
| A/B Testing | Multiple builds | Dashboard toggle |
| Seasonal Content | Plan weeks ahead | Update anytime |
| Update Time | 1-7 days | Instant |
|---|---|---|
| User Action | Must update app | Automatic |
| Typo Fix | New release | 30 seconds |
| A/B Testing | Multiple builds | Dashboard toggle |
| Seasonal Content | Plan weeks ahead | Update anytime |
| User Action | Must update app | Automatic |
|---|---|---|
| Typo Fix | New release | 30 seconds |
| A/B Testing | Multiple builds | Dashboard toggle |
| Seasonal Content | Plan weeks ahead | Update anytime |
| Typo Fix | New release | 30 seconds |
|---|---|---|
| A/B Testing | Multiple builds | Dashboard toggle |
| Seasonal Content | Plan weeks ahead | Update anytime |
| A/B Testing | Multiple builds | Dashboard toggle |
|---|---|---|
| Seasonal Content | Plan weeks ahead | Update anytime |
| Seasonal Content | Plan weeks ahead | Update anytime |
|---|
Localization Best Practices
1. Design for Localization
Allow for text expansion:
// Bad
label.frame = CGRect(x: 0, y: 0, width: 100, height: 20)
// Good
label.sizeToFit()
// or use Auto Layout constraints2. 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:
Translation Workflow
1. Extract Strings
Manual:
With IntlPull CLI:
# Automatically find and extract strings
npx intlpull scan --platform ios
npx intlpull scan --platform android2. Manage Translations
Without a TMS:
With IntlPull:
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 immediatelyCommon 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 >= constraints3. 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.current4. Forgetting App Store Metadata
Don't forget to localize:
Tools Comparison
| Tool | OTA | AI | CLI | Price |
|---|---|---|---|---|
| IntlPull | Yes | Yes | Yes | $11-115/mo |
| Lokalise | No | Yes | Yes | $90+/mo |
| Crowdin | No | Basic | Yes | $40/user |
| Phrase | No | Yes | Yes | $125+/mo |
| IntlPull | Yes | Yes | Yes | $11-115/mo |
|---|---|---|---|---|
| Lokalise | No | Yes | Yes | $90+/mo |
| Crowdin | No | Basic | Yes | $40/user |
| Phrase | No | Yes | Yes | $125+/mo |
| IntlPull | Yes | Yes | Yes | $11-115/mo |
|---|---|---|---|---|
| Lokalise | No | Yes | Yes | $90+/mo |
| Crowdin | No | Basic | Yes | $40/user |
| Phrase | No | Yes | Yes | $125+/mo |
| Lokalise | No | Yes | Yes | $90+/mo |
|---|---|---|---|---|
| Crowdin | No | Basic | Yes | $40/user |
| Phrase | No | Yes | Yes | $125+/mo |
| Crowdin | No | Basic | Yes | $40/user |
|---|---|---|---|---|
| Phrase | No | Yes | Yes | $125+/mo |
| Phrase | No | Yes | Yes | $125+/mo |
|---|
Getting Started
Step 1: Audit Current State
Step 2: Choose Your Tools
For most mobile teams, IntlPull offers the best combination:
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:
Ready to simplify mobile localization? Start free with IntlPull and ship translations without app store delays.