Multi-Language Localization System

Whistl supports users worldwide with comprehensive localization. This technical guide explains the localization architecture, string management, pluralisation, date/time formatting, RTL support, and how Whistl adapts content for different cultures and regions.

Why Localization Matters

Financial behaviour varies across cultures:

  • Language: Users prefer apps in their native language
  • Currency: Local currency formatting expected
  • Date formats: DD/MM/YYYY vs MM/DD/YYYY
  • Cultural context: Examples and metaphors vary
  • Regulatory: Different regions have different requirements

Whistl supports 15+ languages with proper cultural adaptation.

Localization Architecture

Whistl uses platform-native localization systems:

File Structure

Localization/
├── en/
│   ├── Localizable.strings
│   ├── Localizable.stringsdict  (pluralisation)
│   └── InfoPlist.strings
├── es/
│   ├── Localizable.strings
│   └── Localizable.stringsdict
├── fr/
│   ├── Localizable.strings
│   └── Localizable.stringsdict
├── de/
├── it/
├── pt/
├── nl/
├── sv/
├── da/
├── no/
├── fi/
├── ja/
├── ko/
├── zh-Hans/
└── zh-Hant/

String Localization

All user-facing strings are externalized:

Strings File Format

// en/Localizable.strings
"welcome.title" = "Welcome to Whistl";
"welcome.subtitle" = "Your financial protection companion";
"button.get_started" = "Get Started";
"button.skip" = "Skip";

// Transaction categories
"category.gambling" = "Gambling";
"category.shopping" = "Shopping";
"category.dining" = "Dining";
"category.transport" = "Transport";

// Risk levels
"risk.low" = "Low Risk";
"risk.elevated" = "Elevated Risk";
"risk.high" = "High Risk";
"risk.critical" = "Critical Risk";

// es/Localizable.strings (Spanish)
"welcome.title" = "Bienvenido a Whistl";
"welcome.subtitle" = "Tu compañero de protección financiera";
"button.get_started" = "Comenzar";
"button.skip" = "Saltar";

SwiftUI Localization

import SwiftUI

struct WelcomeView: View {
    var body: some View {
        VStack {
            Text("welcome.title")
                .font(.largeTitle)
                .fontWeight(.bold)
            
            Text("welcome.subtitle")
                .font(.body)
                .foregroundColor(.secondary)
            
            Button("button.get_started") {
                // Action
            }
            .buttonStyle(.borderedProminent)
            
            Button("button.skip") {
                // Action
            }
        }
        .padding()
    }
}

// Alternative with String interpolation
Text("Hello, \(userName)")  // Automatically localized

Jetpack Compose Localization

import androidx.compose.runtime.*
import androidx.compose.ui.res.*

@Composable
fun WelcomeView() {
    val welcomeTitle = stringResource(R.string.welcome_title)
    val welcomeSubtitle = stringResource(R.string.welcome_subtitle)
    val getStarted = stringResource(R.string.button_get_started)
    val skip = stringResource(R.string.button_skip)
    
    Column(
        modifier = Modifier.padding(16.dp)
    ) {
        Text(
            text = welcomeTitle,
            style = MaterialTheme.typography.headlineLarge
        )
        
        Text(
            text = welcomeSubtitle,
            style = MaterialTheme.typography.bodyLarge
        )
        
        Button(onClick = { onGetStartedClick() }) {
            Text(getStarted)
        }
        
        TextButton(onClick = { onSkipClick() }) {
            Text(skip)
        }
    }
}

Pluralisation

Different languages have complex plural rules:

Stringsdict Format (iOS)

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" 
    "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>transaction_count</key>
    <string>%d transaction(s)</string>
    
    <key>days_remaining</key>
    <dict>
        <key>NSStringFormatSpecTypeKey</key>
        <string>NSStringPluralRuleType</string>
        <key>NSStringFormatValueTypeKey</key>
        <string>d</string>
        <key>one</key>
        <string>%d day remaining</string>
        <key>other</key>
        <string>%d days remaining</string>
        <key>zero</key>
        <string>No days remaining</string>
    </dict>
    
    <key>goals_completed</key>
    <dict>
        <key>NSStringFormatSpecTypeKey</key>
        <string>NSStringPluralRuleType</string>
        <key>NSStringFormatValueTypeKey</key>
        <string>d</string>
        <key>zero</key>
        <string>No goals completed yet</string>
        <key>one</key>
        <string>%d goal completed</string>
        <key>other</key>
        <string>%d goals completed</string>
    </dict>
</dict>
</plist>

Android Plurals

<!-- res/values/plurals.xml -->
<resources>
    <plurals name="days_remaining">
        <item quantity="one">%d day remaining</item>
        <item quantity="other">%d days remaining</item>
        <item quantity="zero">No days remaining</item>
    </plurals>
    
    <plurals name="goals_completed">
        <item quantity="zero">No goals completed yet</item>
        <item quantity="one">%d goal completed</item>
        <item quantity="other">%d goals completed</item>
    </plurals>
</resources>

// Usage in Compose
val daysRemaining = resources.getQuantityString(
    R.plurals.days_remaining, 
    days, 
    days
)

Currency Formatting

Currencies are formatted according to locale:

Currency Display

import Foundation

class CurrencyFormatter {
    private let formatter = NumberFormatter()
    
    func format(_ amount: Double, currency: String, locale: Locale) -> String {
        formatter.numberStyle = .currency
        formatter.currencyCode = currency
        formatter.locale = locale
        
        return formatter.string(from: NSNumber(value: amount)) ?? ""
    }
}

// Examples:
// USD, en_US: "$1,234.56"
// EUR, de_DE: "1.234,56 €"
// AUD, en_AU: "$1,234.56"
// JPY, ja_JP: "¥1,235"
// GBP, en_GB: "£1,234.56"

SwiftUI Currency

struct BalanceView: View {
    let amount: Double
    let currency: String
    
    var body: some View {
        Text(amount, format: .currency(code: currency))
            .font(.largeTitle)
            .fontWeight(.bold)
        
        // With locale
        Text(amount, format: .currency(code: currency))
            .environment(\.locale, Locale(identifier: "de_DE"))
        // Displays: "1.234,56 €"
    }
}

Date and Time Formatting

Dates are formatted according to locale conventions:

Date Formats by Locale

LocaleShort DateLong DateTime
en_US3/5/26March 5, 20262:30 PM
en_GB05/03/20265 March 202614:30
en_AU5/03/20265 March 20262:30 pm
de_DE05.03.265. März 202614:30 Uhr
fr_FR05/03/20265 mars 202614:30
ja_JP2026/03/052026 年 3 月 5 日14:30

Date Formatting Code

import Foundation

class DateFormatter {
    func formatTransactionDate(_ date: Date, locale: Locale) -> String {
        let formatter = DateFormatter()
        formatter.locale = locale
        formatter.dateStyle = .medium
        formatter.timeStyle = .short
        return formatter.string(from: date)
    }
    
    func formatRelativeDate(_ date: Date, locale: Locale) -> String {
        let formatter = RelativeDateTimeFormatter()
        formatter.locale = locale
        formatter.unitsStyle = .abbreviated
        
        let components = Calendar.current.dateComponents(
            [.day, .hour, .minute],
            from: date,
            to: Date()
        )
        
        if let days = components.day, days < 7 {
            return formatter.localizedString(fromValue: -Double(days), unit: .day)
        }
        
        return formatTransactionDate(date, locale: locale)
    }
}

Right-to-Left (RTL) Support

Arabic and Hebrew require mirrored layouts:

RTL Layout

// SwiftUI automatically handles RTL
struct TransactionRow: View {
    var body: some View {
        HStack {
            // Icon - stays on leading side
            Image(systemName: "creditcard")
            
            VStack(alignment: .leading) {
                Text(merchant)
                Text(category)
            }
            
            Spacer()
            
            // Amount - stays on trailing side
            Text(amount, format: .currency(code: "AUD"))
        }
    }
}

// In RTL (Arabic/Hebrew):
// - HStack automatically reverses direction
// - Leading becomes right, trailing becomes left
// - Icons and text remain properly aligned

RTL Considerations

  • Navigation: Back button on right side
  • Progress indicators: Fill right-to-left
  • Icons: Directional icons mirrored (arrows, cars)
  • Numbers: Still left-to-right in Arabic
  • Mixed text: Bidirectional text handling

Localization Workflow

Whistl uses a professional localization workflow:

Process

  1. String extraction: Automated extraction from code
  2. Translation: Professional translators (not machine)
  3. Review: Native speaker review for accuracy
  4. Context: Screenshots provided for context
  5. Testing: In-app testing with native speakers
  6. Update: Continuous updates as app evolves

Translation Management

// Localization script
#!/bin/bash

# Export strings for translation
xcodebuild -exportLocalizations \
    -localizationPath ./Localization \
    -exportLanguage en

# Upload to translation platform
# (e.g., Lokalise, Transifex, Crowdin)

# Download completed translations
# Import back into Xcode
xcodebuild -importLocalizations \
    -localizationPath ./Localization \
    -importLanguage es

Supported Languages

Whistl currently supports these languages:

Language List

LanguageCodeRegion
EnglishenGlobal (default)
SpanishesSpain, Latin America
FrenchfrFrance, Canada
GermandeGermany, Austria
ItalianitItaly
PortugueseptPortugal, Brazil
DutchnlNetherlands, Belgium
SwedishsvSweden
DanishdaDenmark
NorwegiannoNorway
FinnishfiFinland
JapanesejaJapan
KoreankoKorea
Simplified Chinesezh-HansChina, Singapore
Traditional Chinesezh-HantTaiwan, Hong Kong

Conclusion

Whistl's comprehensive localization system ensures users worldwide can access financial protection in their native language. Through proper string externalization, pluralisation, currency/date formatting, and RTL support, the app feels native in every supported language.

Localization isn't just translation—it's cultural adaptation that makes Whistl feel like it was built for each market.

Protection in Your Language

Whistl supports 15+ languages with proper localization. Download free and use Whistl in your preferred language.

Download Whistl Free

Related: Accessibility Support | GDPR & CCPA Compliance | Privacy-Compliant Analytics