--- name: Swift Localization description: Localization patterns using String Catalogs and modern APIs globs: ["**/*.swift", "**/*.xcstrings"] --- # 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: ```swift // 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:)`: ```swift // 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 ```swift // BAD - Old API let text = NSLocalizedString("Hello", comment: "Greeting") // GOOD - Modern API let text = String(localized: "Hello") ``` ## String Interpolation String Catalogs handle interpolation automatically: ```swift // 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: ```swift // 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: ```swift // 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: ```swift // 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: ```swift 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: ```swift 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 ```swift 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 ```swift 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: ```swift // 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 ```swift #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: 1. Edit Scheme → Run → Options 2. Set "Application Language" to a pseudo-language 3. Look for strings that don't expand properly ## Export/Import for Translation ```bash # Export for translators xcodebuild -exportLocalizations -project MyApp.xcodeproj -localizationPath ./Localizations # Import translations xcodebuild -importLocalizations -project MyApp.xcodeproj -localizationPath ./Localizations/es-MX.xcloc ```