Mobile App Internationalisation Beyond Translation

Most developers think internationalisation means translating strings. They set up a localisation file, send it to translators, and call it done. Then they launch in Japan and discover that their carefully designed layout breaks because Japanese text is 30% longer in vertical space. Or they expand to the Middle East and find their entire UI is backwards because they never considered right-to-left layouts.

Proper internationalisation (i18n) goes far deeper than translation. It encompasses date and time formatting, number and currency display, text direction, cultural conventions, legal requirements, and even colour associations. For Australian apps expanding into Asian and Pacific markets, getting this right is the difference between a successful launch and an embarrassing one.

The Internationalisation Stack

Think of i18n as four layers:

  1. Internationalisation (i18n): Engineering your app to support multiple locales
  2. Localisation (l10n): Translating and adapting content for specific locales
  3. Cultural adaptation: Adjusting UX patterns for cultural expectations
  4. Legal compliance: Meeting local regulatory requirements

Most teams only address layer 2. This guide covers all four.

Locale-Aware Fo

rmatting

Numbers and Currency

Never format numbers manually. Always use the platform’s locale-aware formatters:

// iOS - Locale-aware number formatting
let formatter = NumberFormatter()
formatter.numberStyle = .currency

// Australian user
formatter.locale = Locale(identifier: "en_AU")
formatter.string(from: 1234.56)  // "$1,234.56"

// Japanese user
formatter.locale = Locale(identifier: "ja_JP")
formatter.string(from: 1234.56)  // "¥1,235" (no decimals for yen)

// German user
formatter.locale = Locale(identifier: "de_DE")
formatter.string(from: 1234.56)  // "1.234,56 €" (comma decimal, period grouping)

// Indian user
formatter.locale = Locale(identifier: "en_IN")
formatter.string(from: 1234567.89)  // "₹12,34,567.89" (lakh/crore grouping)
// Android - Locale-aware formatting
val amount = 1234.56

// Use locale from system settings
val australianFormat = NumberFormat.getCurrencyInstance(Locale("en", "AU"))
australianFormat.format(amount)  // "$1,234.56"

val japaneseFormat = NumberFormat.getCurrencyInstance(Locale.JAPAN)
japaneseFormat.format(amount)  // "¥1,235"

// For display-only (non-currency) numbers
val numberFormat = NumberFormat.getNumberInstance(Locale.getDefault())
numberFormat.format(1234567)  // Respects locale grouping

Dates and Times

Date formatting varies dramatically across locales:

// iOS date formatting
let date = Date()
let formatter = DateFormatter()
formatter.dateStyle = .medium
formatter.timeStyle = .short

formatter.locale = Locale(identifier: "en_AU")
// "18 Jul 2023, 2:30 pm"

formatter.locale = Locale(identifier: "en_US")
// "Jul 18, 2023, 2:30 PM"

formatter.locale = Locale(identifier: "ja_JP")
// "2023/07/18 14:30"

formatter.locale = Locale(identifier: "de_DE")
// "18.07.2023, 14:30"

Critical rule: Never construct date strings manually. “Month/Day/Year” is an American convention that confuses everyone else. Always use formatters.

Relative Time

“2 hours ago” is more user-friendly than a timestamp, but it must be localised:

let relativeFormatter = RelativeDateTimeFormatter()
relativeFormatter.unitsStyle = .full

relativeFormatter.locale = Locale(identifier: "en_AU")
relativeFormatter.localizedString(for: twoHoursAgo, relativeTo: now)
// "2 hours ago"

relativeFormatter.locale = Locale(identifier: "ja_JP")
relativeFormatter.localizedString(for: twoHoursAgo, relativeTo: now)
// "2時間前"

relativeFormatter.locale = Locale(identifier: "ar")
relativeFormatter.localizedString(for: twoHoursAgo, relativeTo: now)
// "قبل ساعتين"

String Localisation Do

ne Right

Pluralisation

English has two plural forms (singular and plural). Other languages have different rules. Arabic has six. Russian has three. Use stringsdict (iOS) or quantity strings (Android) to handle this correctly:

{/* iOS: Localizable.stringsdict */}
<dict>
    <key>items_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>zero</key>
            <string>No items</string>
            <key>one</key>
            &lt;string&gt;1 item&lt;/string&gt;
            <key>other</key>
            <string>%d items</string>
        </dict>
    </dict>
</dict>
{/* Android: res/values/strings.xml */}
<plurals name="items_count">
    <item quantity="zero">No items</item>
    <item quantity="one">%d item</item>
    <item quantity="other">%d items</item>
</plurals>

{/* Arabic would need: zero, one, two, few, many, other */}

String Interpolation

Avoid concatenating strings. Word order differs between languages:

// BAD: Word order is fixed
let message = "Welcome, " + userName + ". You have " + String(count) + " notifications."

// GOOD: Translators can reorder parameters
let message = String(
    format: NSLocalizedString(
        "welcome_message",
        comment: "Welcome message with user name and notification count"
    ),
    userName,
    count
)
// English: "Welcome, %1$@. You have %2$d notifications."
// Japanese: "%1$@さん、%2$d件の通知があります。" (different word order)

Context for Translators

Always provide context. The word “Post” could mean a blog post, a mail post, or the verb “to post”:

NSLocalizedString(
    "post_button",
    comment: "Button label to submit a new blog post"
)

Right-to-Left (RTL

) Layout Support

Arabic, Hebrew, Persian, and Urdu are written right-to-left. Your entire UI must mirror:

iOS RTL Support

SwiftUI handles RTL automatically for most layouts. Use .leading and .trailing instead of .left and .right:

// CORRECT: Flips automatically in RTL
VStack(alignment: .leading) {
    Text("Title")
    Text("Description")
}
.padding(.leading, 16)

// WRONG: Fixed position, doesn't flip
VStack(alignment: .left) {  // Compiler warning
    Text("Title")
}
.padding(.left, 16)  // Won't flip in RTL

Android RTL Support

Enable RTL support in your manifest and use start/end instead of left/right:

{/* AndroidManifest.xml */}
<application
    android:supportsRtl="true">
// Compose - use Arrangement.Start/End
Row(
    modifier = Modifier.fillMaxWidth(),
    horizontalArrangement = Arrangement.Start  // Flips in RTL
) {
    Icon(Icons.Default.Person, contentDescription = null)
    Spacer(modifier = Modifier.width(8.dp))
    Text("User Name")
}

// XML layouts - use start/end
<TextView
    android:layout_marginStart="16dp"
    android:drawableStart="@drawable/ic_user"
    android:textAlignment="viewStart" />

Icons and Images

Some icons need mirroring in RTL: back arrows, progress indicators, list bullets. Others should NOT mirror: clocks, media playback controls, checkmarks.

Image(systemName: "arrow.left")
    .environment(\.layoutDirection, .rightToLeft)
    // System images auto-mirror when appropriate

Cultural Adaptation

Colour Meanings

Colours carry different meanings across cultures:

  • Red: Danger in Western cultures, luck and prosperity in China
  • White: Purity in the West, mourning in some Asian cultures
  • Green: Go/positive globally, but sacred in Islam

Avoid relying solely on colour to convey meaning. Always pair colour with text or icons.

Name Formatting

Not everyone has a “first name” and “last name”:

let formatter = PersonNameComponentsFormatter()
formatter.style = .default

var components = PersonNameComponents()
components.givenName = "Ash"
components.familyName = "Ganda"

formatter.locale = Locale(identifier: "en_AU")
formatter.string(from: components)  // "Ash Ganda"

formatter.locale = Locale(identifier: "ja_JP")
formatter.string(from: components)  // "Ganda Ash" (family name first)

formatter.locale = Locale(identifier: "zh_CN")
formatter.string(from: components)  // "Ganda Ash" (family name first, no space in Chinese)

Address Formats

Australian addresses follow a specific format, but other countries differ significantly:

let formatter = CNPostalAddressFormatter()
// Let the system format addresses according to locale conventions
formatter.string(from: postalAddress)

Japanese addresses go from largest to smallest unit (prefecture, city, district, block, building). German addresses put the house number after the street name. Never hardcode address field ordering.

Measurement Systems

Australia uses metric, but some export markets use imperial. Use the platform’s measurement formatters:

let measurement = Measurement(value: 5.0, unit: UnitLength.kilometers)
let formatter = MeasurementFormatter()
formatter.unitOptions = .providedUnit
formatter.locale = Locale(identifier: "en_AU")
formatter.string(from: measurement)  // "5 km"

formatter.locale = Locale(identifier: "en_US")
formatter.unitOptions = .naturalScale
formatter.string(from: measurement)  // "3.107 mi"

Testing Internationalisation

Pseudolocalisation

Use pseudolocalisation to find layout issues before sending to translators. It replaces characters with accented versions and adds padding to simulate longer translations:

Original: "Settings"
Pseudo:   "[Šéttîñgš  !!!]"  (30% longer, accented, bracketed)

Both Xcode and Android Studio support pseudolocales for testing.

Layout Testing

  • Test with the longest supported language (German text is often 30% longer than English)
  • Test with RTL languages (Arabic, Hebrew)
  • Test with CJK languages (Chinese, Japanese, Korean — different line breaking rules)
  • Test with scripts that use tall characters (Thai, Arabic)

Internationalisation is an investment that pays off the moment you expand beyond your home market. For Australian mobile app development teams targeting the Asia-Pacific region, the effort is especially worthwhile—you are surrounded by markets with diverse languages, scripts, and cultural conventions. Learn more about mobile app localization for the Australian market.

Frequently Asked Questions About Mobile App Internationalization

What’s the difference between internationalization and localization in mobile apps?

Internationalization (i18n) is engineering your mobile app development architecture to support multiple locales without code changes. Localization (l10n) is the actual process of adapting content, formatting, and cultural elements for specific markets. I18n enables l10n.

Do I need RTL support for my mobile app?

If you’re targeting Arabic, Hebrew, Persian, or Urdu markets, RTL (right-to-left) support is essential for mobile app development. These languages represent 400+ million potential users. Both iOS and Android provide automatic RTL layout mirroring when properly implemented.

How do I handle date formatting in international mobile apps?

Never hardcode date formats in mobile app development. Use platform locale formatters (DateFormatter on iOS, DateTimeFormatter on Android) that automatically respect user preferences. DD/MM/YYYY (Australia) vs MM/DD/YYYY (US) confusion causes genuine user frustration.

What are the biggest internationalization mistakes in mobile app development?

The biggest mobile app development i18n mistakes are: hardcoding date/number formats, concatenating translated strings (word order varies), using “first name/last name” fields globally (many cultures use different name structures), and relying solely on color to convey meaning across cultures.

Should I build internationalization from day one?

Yes, building i18n into your mobile app development architecture from day one costs 20% more upfront but saves 300-500% in retrofitting costs later. Even if you’re launching in one market, using locale formatters and externalizing strings prevents painful rewrites during expansion.

Internationalization Best Practices for Mobile Apps

75% of consumers prefer mobile apps in their native language, making internationalization essential for global mobile app development success beyond English-speaking markets.

RTL language support unlocks 400+ million users in Arabic, Hebrew, Persian, and Urdu markets—often overlooked by Western mobile app development teams despite being relatively straightforward to implement.

Proper date and number formatting prevents 80% of internationalization complaints because users immediately notice when mobile apps display dates as MM/DD/YYYY in Australia or use comma decimals in US English contexts.


Expanding your app internationally? Our team at eawesome builds globally-ready mobile applications from day one.