Swift Localization
Lifecycle Position
Phase 3 (Implement). Load when adding user-facing text to ensure localization-readiness from the start.
Modern API (String Catalogs)
String(localized:)
// Basic localized string
let greeting = String(localized: "welcome_message")
// With default value and comment for translators
let greeting = String(localized: "welcome_message",
defaultValue: "Welcome back!",
comment: "Greeting shown on home screen after login")
// String interpolation (automatically handled)
let message = String(localized: "items_in_cart \(count) items")
LocalizedStringResource
// For deferred localization (resolved later in the correct context)
struct Notification {
let title: LocalizedStringResource
let body: LocalizedStringResource
}
let notification = Notification(
title: "new_message_title",
body: "new_message_body \(senderName)"
)
// Resolve when displaying
Text(notification.title)
String Catalogs (.xcstrings)
Xcode provides String Catalogs — a visual editor replacing .strings and .stringsdict files.
Setup
- File → New → File → String Catalog
- Name it
Localizable.xcstrings - Xcode auto-extracts
Text(),String(localized:), and LocalizedStringKey usage - Add languages in Project → Info → Localizations
- Translate directly in the String Catalog editor
Benefits over .strings
- Visual editor with translation status indicators
- Automatic extraction of new strings on build
- Built-in plural support (no separate .stringsdict)
- Export/import for professional translation (XLIFF)
- Compile-time validation of string references
Text Localization in SwiftUI
Automatic Localization
// Text automatically localizes string literals
Text("Hello") // Looks up "Hello" in String Catalog
// Explicit non-localized text
Text(verbatim: "Version 1.2.3") // Never localized (version numbers, codes)
// Variable text is NOT automatically localized
let name = "John"
Text(name) // NOT localized (String variable)
Text("Hello \(name)") // IS localized (string literal with interpolation)
LocalizedStringKey
// Button, Label, Toggle etc. accept LocalizedStringKey
Button("Save") { save() } // "Save" is looked up in catalog
Toggle("Enable notifications", isOn: $enabled) // Localized
// Programmatic LocalizedStringKey
let key: LocalizedStringKey = "dynamic_key"
Text(key)
Pluralization
String Catalog Plural Rules
In the String Catalog editor, mark a string as "Varies by Plural" and provide variants:
| Category | English Example | Arabic Example |
|---|---|---|
zero |
"No items" | (specific form) |
one |
"1 item" | (specific form) |
two |
— | (specific form) |
few |
— | (3-10) |
many |
— | (11-99) |
other |
"%lld items" | (100+) |
// In code — just use interpolation
Text("item_count \(items.count)")
// String Catalog handles the plural form automatically based on locale
// English: "1 item" / "5 items"
// Arabic: different forms for 0, 1, 2, 3-10, 11-99, 100+
Legacy .stringsdict
<key>item_count</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@count@</string>
<key>count</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>lld</string>
<key>one</key>
<string>%lld item</string>
<key>other</key>
<string>%lld items</string>
</dict>
</dict>
Number and Date Formatting
// Numbers — always locale-aware
Text(price, format: .currency(code: "USD")) // "$1,234.56" or "1.234,56 $"
Text(count, format: .number) // "1,234" or "1.234"
Text(ratio, format: .percent) // "85%" or "85 %"
// Dates — always locale-aware
Text(date, format: .dateTime.day().month().year()) // "Jan 15, 2025" or "15 janv. 2025"
Text(date, format: .relative(presentation: .named)) // "yesterday" or "hier"
// Measurements
let distance = Measurement(value: 5, unit: UnitLength.kilometers)
Text(distance, format: .measurement(width: .abbreviated)) // "5 km" or "3.1 mi"
Never use String(format:) for user-facing numbers/dates — it ignores locale.
RTL Layout Support
// SwiftUI handles RTL automatically when using:
// - .leading/.trailing instead of .left/.right
// - HStack (reverses in RTL)
// - padding(.leading) (becomes right in RTL)
// Explicit direction control (rare)
@Environment(\.layoutDirection) var layoutDirection
// Flip images for RTL
Image("arrow")
.flipsForRightToLeftLayoutDirection(true)
// Force LTR for specific content (e.g., code, phone numbers)
Text("+1 (555) 123-4567")
.environment(\.layoutDirection, .leftToRight)
Asset Catalog Localization
- Images: Select image → Localize → provide per-locale variants (e.g., screenshots with localized UI)
- Colors: Named colors can have per-locale variants (rare but useful for culturally significant colors)
- App Icon: Same across locales (use Icon Composer for Liquid Glass)
Common Mistakes
- Hardcoded user-facing strings (
"Save"instead ofString(localized:)) — untranslatable - String concatenation for sentences (
"Hello " + name + "!") — word order varies by language. Use interpolation:"greeting \(name)" String(format:)for numbers/dates — ignores user locale. UseText(value, format:)- Assuming text length — German text is ~30% longer than English. Don't set fixed widths on labels
- Using
.left/.rightinstead of.leading/.trailing— breaks RTL layouts Text(variable)expecting localization — only string literals are auto-localized. UseText(LocalizedStringKey(variable))orString(localized:)
Checklist
- No hardcoded user-facing strings — all use
String(localized:)orText("literal") - Plurals handled via String Catalog plural variants (not conditional logic)
- Numbers and dates use
Text(value, format:)notString(format:) - All
.left/.rightreplaced with.leading/.trailing - Text containers allow for ~30% text expansion (no fixed widths on labels)
- RTL layout tested (change language in scheme settings)
- Non-localizable text uses
Text(verbatim:)(version numbers, codes, identifiers) - String Catalog has translator comments for ambiguous strings
Cross-References
swiftui-typography-api— Dynamic Type and text layout for localized textswiftui-input-api— localized form labels and placeholder textswift-app-lifecycle— app entry point where locale is determinedcode-analyzer— checks for hardcoded strings in review
Templates
Reusable Swift files in templates/ — copy and adapt:
LocalizationManager.swift—@MainActor @Observablemanager with runtime language switching,EnvironmentKeyinjectionLocalizedStrings.swift— Type-safeL10nenum for compile-time checked localization keysLocalizedPreview.swift— SwiftUI preview helper for testing multiple locales