5.1 KiB
| name | description | globs | ||
|---|---|---|---|---|
| Swift Localization | Localization patterns using String Catalogs and modern APIs |
|
Localization with String Catalogs
Use String Catalogs (.xcstrings files) for localization in modern Swift projects.
Language Support
At minimum, support the base development language (typically English). Add additional locales based on your app's target markets. Common additions include Spanish and French variants.
How String Catalogs Work
Automatic Extraction
SwiftUI Text views with string literals are automatically extracted:
// Automatically added to String Catalog
Text("Hello, World!")
Text("Welcome back, \(user.name)!")
Manual Extraction for Non-Text Strings
For strings outside of Text views, use String(localized:):
// Use String(localized:) for alerts, buttons, accessibility
let title = String(localized: "Delete Item")
let message = String(localized: "Are you sure you want to delete this item?")
// With comments for translators
let greeting = String(
localized: "greeting_message",
defaultValue: "Hello!",
comment: "Greeting shown on the home screen"
)
Never Use NSLocalizedString
// BAD - Old API
let text = NSLocalizedString("Hello", comment: "Greeting")
// GOOD - Modern API
let text = String(localized: "Hello")
String Interpolation
String Catalogs handle interpolation automatically:
// In Swift
Text("You have \(count) items")
// In String Catalog, translators see:
// "You have %lld items"
// They can reorder: "Items: %lld" for languages that need different order
Pluralization
Use automatic grammar agreement for plurals:
// Automatic pluralization
Text("^[\(count) item](inflect: true)")
// Result:
// count = 1: "1 item"
// count = 5: "5 items"
For complex pluralization rules, define in String Catalog with plural variants.
Formatting Numbers, Dates, Currency
Always use formatters - they respect locale automatically:
// Numbers
Text(price, format: .currency(code: "USD"))
Text(percentage, format: .percent)
Text(count, format: .number)
// Dates
Text(date, format: .dateTime.month().day().year())
Text(date, format: .relative(presentation: .named))
// Measurements
let distance = Measurement(value: 5, unit: UnitLength.miles)
Text(distance, format: .measurement(width: .abbreviated))
Localized String Keys
Use meaningful keys for complex strings:
// For simple UI text, use the text itself
Text("Settings")
Text("Cancel")
// For complex or contextual strings, use keys
Text("home.welcome.title") // Key in String Catalog
Text("profile.empty.message")
Accessibility Labels
Localize all accessibility content:
Image(systemName: "heart.fill")
.accessibilityLabel(String(localized: "Favorite"))
Button { } label: {
Image(systemName: "trash")
}
.accessibilityLabel(String(localized: "Delete"))
.accessibilityHint(String(localized: "Removes this item permanently"))
String Catalog Organization
File Structure
App/
├── Localizable.xcstrings # Main strings
├── InfoPlist.xcstrings # Info.plist strings (app name, permissions)
└── Intents.xcstrings # Siri/Shortcuts strings (if applicable)
Comments for Translators
Add comments to help translators understand context:
Text("Save", comment: "Button to save the current document")
Text("Save", comment: "Menu item to save all changes")
// These become separate entries with context
Common Patterns
Error Messages
enum AppError: LocalizedError {
case networkUnavailable
case invalidData
var errorDescription: String? {
switch self {
case .networkUnavailable:
return String(localized: "error.network.unavailable")
case .invalidData:
return String(localized: "error.data.invalid")
}
}
}
Attributed Strings
var attributedGreeting: AttributedString {
var string = AttributedString(localized: "Welcome to **MyApp**!")
// Markdown formatting is preserved
return string
}
Dynamic Strings from Server
For server-provided strings that need localization:
// Use a mapping approach
let serverKey = response.messageKey // e.g., "subscription_expired"
let localizedMessage = String(localized: String.LocalizationValue(serverKey))
Testing Localization
Preview with Different Locales
#Preview {
ContentView()
.environment(\.locale, Locale(identifier: "es-MX"))
}
#Preview {
ContentView()
.environment(\.locale, Locale(identifier: "fr-CA"))
}
Pseudo-Localization
Enable in scheme to find truncation and layout issues:
- Edit Scheme → Run → Options
- Set "Application Language" to a pseudo-language
- Look for strings that don't expand properly
Export/Import for Translation
# Export for translators
xcodebuild -exportLocalizations -project MyApp.xcodeproj -localizationPath ./Localizations
# Import translations
xcodebuild -importLocalizations -project MyApp.xcodeproj -localizationPath ./Localizations/es-MX.xcloc