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
| Locale | Short Date | Long Date | Time |
|---|---|---|---|
| en_US | 3/5/26 | March 5, 2026 | 2:30 PM |
| en_GB | 05/03/2026 | 5 March 2026 | 14:30 |
| en_AU | 5/03/2026 | 5 March 2026 | 2:30 pm |
| de_DE | 05.03.26 | 5. März 2026 | 14:30 Uhr |
| fr_FR | 05/03/2026 | 5 mars 2026 | 14:30 |
| ja_JP | 2026/03/05 | 2026 年 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
- String extraction: Automated extraction from code
- Translation: Professional translators (not machine)
- Review: Native speaker review for accuracy
- Context: Screenshots provided for context
- Testing: In-app testing with native speakers
- 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
| Language | Code | Region |
|---|---|---|
| English | en | Global (default) |
| Spanish | es | Spain, Latin America |
| French | fr | France, Canada |
| German | de | Germany, Austria |
| Italian | it | Italy |
| Portuguese | pt | Portugal, Brazil |
| Dutch | nl | Netherlands, Belgium |
| Swedish | sv | Sweden |
| Danish | da | Denmark |
| Norwegian | no | Norway |
| Finnish | fi | Finland |
| Japanese | ja | Japan |
| Korean | ko | Korea |
| Simplified Chinese | zh-Hans | China, Singapore |
| Traditional Chinese | zh-Hant | Taiwan, 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 FreeRelated: Accessibility Support | GDPR & CCPA Compliance | Privacy-Compliant Analytics