Localized apps see 128% more downloads and 26% higher revenue per user in international markets. Yet 75% of apps support only English. This guide shows you how to internationalize your codebase and localize content for global success.
Why Localization Matters
Business Impact
- More downloads: 128% increase in non-English markets
- Higher revenue: 26% more revenue per user
- Better engagement: 2.5x higher retention
- Market expansion: Access 6+ billion non-English speakers
- Competitive advantage: Most apps don't localize
- App Store featuring: Localized apps get preferential treatment
Internationalization vs Localization
Internationalization (i18n):
- Preparing code to support multiple languages
- Extracting strings from code
- Handling different formats
- Supporting RTL layouts
- One-time development effort
Localization (l10n):
- Translating content
- Adapting culturally
- Testing in each language
- Ongoing process per language
Process:
1. Internationalize codebase (i18n)
2. Localize content (l10n)
3. Test and refine
4. Maintain translations
iOS Internationalization
Setup
// 1. Add languages in Xcode
// Project → Info → Localizations → + Button
// Add: Spanish, French, German, Japanese, etc.
// 2. Create Localizable.strings
// New File → Strings File → "Localizable.strings"
// Localize it (File Inspector → Localize)
// 3. Base language (en)
// Localizable.strings (English)
"welcome_message" = "Welcome to our app!";
"login_button" = "Log In";
"logout_button" = "Log Out";
"settings_title" = "Settings";
"error_network" = "Network error. Please try again.";
// With parameters
"items_count" = "%d items";
"greeting" = "Hello, %@!";
// 4. Spanish translation
// Localizable.strings (Spanish)
"welcome_message" = "¡Bienvenido a nuestra aplicación!";
"login_button" = "Iniciar sesión";
"logout_button" = "Cerrar sesión";
"settings_title" = "Configuración";
"error_network" = "Error de red. Por favor, inténtelo de nuevo.";
"items_count" = "%d artículos";
"greeting" = "¡Hola, %@!";
Using Localized Strings
import UIKit
// Basic usage
let message = NSLocalizedString("welcome_message", comment: "Welcome screen message")
label.text = message
// With parameters
let count = 5
let text = String(format: NSLocalizedString("items_count", comment: ""), count)
// Result: "5 items" (English) or "5 artículos" (Spanish)
let name = "John"
let greeting = String(format: NSLocalizedString("greeting", comment: ""), name)
// Result: "Hello, John!" or "¡Hola, John!"
// Pluralization
let format = String.localizedStringWithFormat(
NSLocalizedString("items_count", comment: ""),
count
)
// Custom strings file
func localizedString(_ key: String, table: String = "Localizable") -> String {
NSLocalizedString(key, tableName: table, comment: "")
}
// Usage
let errorMsg = localizedString("error_network", table: "Errors")
Plural Rules
// Localizable.stringsdict
items_count
NSStringLocalizedFormatKey
%#@items@
items
NSStringFormatSpecTypeKey
NSStringPluralRuleType
NSStringFormatValueTypeKey
d
zero
No items
one
1 item
other
%d items
// Usage
let count = 0
let text = String.localizedStringWithFormat(
NSLocalizedString("items_count", comment: ""),
count
)
// count = 0: "No items"
// count = 1: "1 item"
// count = 5: "5 items"
Date and Number Formatting
import Foundation
// Date formatting
let date = Date()
let formatter = DateFormatter()
formatter.dateStyle = .medium
formatter.timeStyle = .short
formatter.locale = Locale.current
let dateString = formatter.string(from: date)
// English: "Dec 31, 2025 at 3:45 PM"
// Spanish: "31 dic 2025, 15:45"
// Japanese: "2025/12/31 15:45"
// Relative date
let relativeFormatter = RelativeDateTimeFormatter()
relativeFormatter.unitsStyle = .full
let relativeString = relativeFormatter.localizedString(for: date, relativeTo: Date())
// "in 2 hours" or "hace 2 horas"
// Number formatting
let number = 1234.56
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .decimal
let formattedNumber = numberFormatter.string(from: NSNumber(value: number))
// English: "1,234.56"
// German: "1.234,56"
// French: "1 234,56"
// Currency formatting
let price = 99.99
let currencyFormatter = NumberFormatter()
currencyFormatter.numberStyle = .currency
currencyFormatter.locale = Locale.current
let priceString = currencyFormatter.string(from: NSNumber(value: price))
// US: "$99.99"
// UK: "£99.99"
// Japan: "¥100"
// Euro: "99,99 €"
// Percentage
let percent = 0.85
let percentFormatter = NumberFormatter()
percentFormatter.numberStyle = .percent
let percentString = percentFormatter.string(from: NSNumber(value: percent))
// "85%"
RTL Support
import UIKit
// Auto-Layout handles RTL automatically if done correctly
// Use leading/trailing instead of left/right
// ✓ Good
label.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16)
label.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16)
// ✗ Bad
label.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 16)
label.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -16)
// Text alignment
label.textAlignment = .natural // Auto-adjusts for RTL
// Image flipping
imageView.image = UIImage(named: "arrow")?.imageFlippedForRightToLeftLayoutDirection()
// Check current direction
let isRTL = UIView.userInterfaceLayoutDirection(for: view.semanticContentAttribute) == .rightToLeft
// Force specific direction
view.semanticContentAttribute = .forceLeftToRight // Always LTR
view.semanticContentAttribute = .forceRightToLeft // Always RTL
// Test RTL in simulator
// Scheme → Options → App Language → Arabic/Hebrew
Android Internationalization
Setup
My App
Welcome to our app!
Log In
Log Out
Settings
Network error. Please try again.
%d items
Hello, %s!
Mi aplicación
¡Bienvenido a nuestra aplicación!
Iniciar sesión
Cerrar sesión
Configuración
Error de red. Por favor, inténtelo de nuevo.
%d artículos
¡Hola, %s!
Using Localized Strings
import android.content.Context
// In Activity/Fragment
val message = getString(R.string.welcome_message)
textView.text = message
// With parameters
val count = 5
val text = getString(R.string.items_count, count)
// Result: "5 items"
val name = "John"
val greeting = getString(R.string.greeting, name)
// Result: "Hello, John!"
// From Context
fun getLocalizedString(context: Context, resId: Int, vararg args: Any): String {
return context.getString(resId, *args)
}
Plural Rules
- No items
- 1 item
- %d items
- %d предмет
- %d предмета
- %d предметов
- %d предметов
// Usage
val count = 5
val text = resources.getQuantityString(R.plurals.items_count, count, count)
// English: "5 items"
// Russian: "5 предметов"
Date and Number Formatting
import java.text.DateFormat
import java.text.NumberFormat
import java.util.*
// Date formatting
val date = Date()
val dateFormat = DateFormat.getDateTimeInstance(
DateFormat.MEDIUM,
DateFormat.SHORT,
Locale.getDefault()
)
val dateString = dateFormat.format(date)
// English: "Dec 31, 2025, 3:45 PM"
// Spanish: "31 dic 2025, 15:45"
// Relative date
val now = System.currentTimeMillis()
val yesterday = now - 24 * 60 * 60 * 1000
val relativeDate = android.text.format.DateUtils.getRelativeTimeSpanString(
yesterday,
now,
android.text.format.DateUtils.DAY_IN_MILLIS
)
// "Yesterday" or "Ayer"
// Number formatting
val number = 1234.56
val numberFormat = NumberFormat.getNumberInstance(Locale.getDefault())
val formattedNumber = numberFormat.format(number)
// English: "1,234.56"
// German: "1.234,56"
// Currency formatting
val price = 99.99
val currencyFormat = NumberFormat.getCurrencyInstance(Locale.getDefault())
val priceString = currencyFormat.format(price)
// US: "$99.99"
// UK: "£99.99"
// Euro: "99,99 €"
// Percentage
val percent = 0.85
val percentFormat = NumberFormat.getPercentInstance(Locale.getDefault())
val percentString = percentFormat.format(percent)
// "85%"
RTL Support
// Check RTL in code
val isRTL = resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_RTL
// Force specific direction
view.layoutDirection = View.LAYOUT_DIRECTION_LTR // Always LTR
view.layoutDirection = View.LAYOUT_DIRECTION_RTL // Always RTL
// Test RTL
// Settings → Developer Options → Force RTL layout direction
Translation Management
Translation Workflow
1. Extract strings from code
- iOS: Export for localization (Xcode)
- Android: Use strings.xml
2. Prepare for translation
- Context comments
- Character limits
- Screenshots
- Glossary
3. Send to translators
- Professional translation services
- Community translation (Crowdin, Lokalise)
- Machine translation (initial draft only)
4. Review translations
- Native speaker review
- Context verification
- Cultural appropriateness
5. Import translations
- iOS: Import localizations
- Android: Add strings.xml files
6. Test in each language
- UI layout (text overflow)
- RTL layouts
- Date/number formats
- Cultural appropriateness
7. Continuous updates
- Track new strings
- Update existing translations
- Version management
Translation Services
Professional Services:
- Gengo: $0.06-0.10 per word
- One Hour Translation: Fast turnaround
- SDL: Enterprise solutions
Platforms:
- Crowdin: Collaborative translation
- Lokalise: Developer-focused
- Phrase: Translation management
- POEditor: Simple interface
DIY Options:
- Community volunteers (open source)
- Google Translate (draft only, not production)
- DeepL (better than Google, still review needed)
Best practice:
Professional translation for:
✓ Marketing copy
✓ App Store listings
✓ Critical UI text
Community/machine for:
✓ Initial drafts
✓ Less critical content
✓ Quick prototypes
Cultural Adaptation
Beyond Translation
Consider cultural differences:
Colors:
- Red: Danger (Western) vs Good luck (China)
- White: Purity (Western) vs Death (Asia)
- Yellow: Caution (Western) vs Sacred (Buddhism)
Symbols:
- Thumbs up: OK (Western) vs Offensive (Middle East)
- OK sign: Good (US) vs Rude (Brazil)
- Left hand: Normal (Western) vs Unclean (Middle East)
Images:
- Showing skin: Normal (Western) vs Conservative (Middle East)
- Religious symbols: Context-dependent
- Hand gestures: Vary by culture
Text:
- Formal vs informal: Varies by language
- Idioms don't translate: Use simple language
- Humor: Often culture-specific
Examples to adapt:
US → Japan:
- Direct → Indirect communication
- Aggressive CTAs → Polite suggestions
- Individual focus → Group harmony
US → Middle East:
- Left-to-right → Right-to-left
- Casual imagery → Conservative
- Weekend = Sat-Sun → Thu-Fri
US → Germany:
- Informal → Formal tone
- Marketing speak → Technical accuracy
- Subjective claims → Objective facts
Testing Checklist
For each language:
Layout:
□ Text fits without truncation
□ Buttons sized correctly
□ Line breaks appropriate
□ Images not cut off
□ Spacing looks good
□ RTL layout correct (if applicable)
Functionality:
□ All features work
□ Forms submit correctly
□ Search works with language
□ Sorting works correctly
□ Date/time displays correctly
Content:
□ Translation accurate
□ Tone appropriate
□ No offensive content
□ Currency correct
□ Units correct (metric/imperial)
Cultural:
□ Images appropriate
□ Colors appropriate
□ Symbols appropriate
□ Examples relevant
App Store Localization
Localized Metadata
For each language, localize:
✓ App name (if appropriate)
✓ Subtitle/Short description
✓ Description
✓ Keywords (iOS)
✓ What's new (release notes)
✓ Screenshots
✓ Preview video (if applicable)
✓ Support URL
✓ Privacy policy URL
iOS App Store:
- 30 chars: App name
- 30 chars: Subtitle
- 100 chars: Keywords
- 4000 chars: Description
- 4000 chars: What's new
Google Play:
- 30 chars: Title
- 80 chars: Short description
- 4000 chars: Full description
- 500 chars: What's new
Screenshot guidelines:
- Show app in target language
- Use relevant examples for culture
- 5-8 screenshots per language
- Text overlays in target language
ROI of localized metadata:
- 767% increase in downloads (study)
- Higher conversion rate
- Better search ranking in local stores
Maintenance
Keeping Translations Updated
Workflow for updates:
1. Track new strings
- Mark untranslated strings
- Version strings
- Track changes
2. Batch translations
- Accumulate changes
- Send batch to translators
- More cost-effective
3. Versioning
- Tag strings by app version
- Know what needs translation
- Rollback if needed
4. Automated checks
- Missing translations
- Unused strings
- Format string mismatches
5. Continuous improvement
- User feedback
- In-context translation issues
- A/B test translations
Conclusion
Localization unlocks global markets and dramatically increases your app's reach and revenue. While internationalization requires upfront technical work, proper implementation makes adding new languages straightforward. Start with i18n best practices from day one, prioritize key markets, use professional translation for critical content, and continuously improve based on user feedback. The investment pays off through increased downloads, better engagement, and higher revenue across international markets.
Localized apps need localized support. Our support URL generator creates support pages in multiple languages, ensuring your international users get the same great support experience as your domestic audience.