Claude Agent Skill · by Dpearson2699

Ios Localization

Install Ios Localization skill for Claude Code from dpearson2699/swift-ios-skills.

Install
Terminal · npx
$npx skills add https://github.com/vercel-labs/agent-skills --skill vercel-react-native-skills
Works with Paperclip

How Ios Localization fits into a Paperclip company.

Ios Localization drops into any Paperclip agent that handles this kind of work. Assign it to a specialist inside a pre-configured PaperclipOrg company and the skill becomes available on every heartbeat — no prompt engineering, no tool wiring.

S
SaaS FactoryPaired

Pre-configured AI company — 18 agents, 18 skills, one-time purchase.

$27$59
Explore pack
Source file
SKILL.md417 lines
Expand
---name: ios-localizationdescription: "Implement, review, or improve localization and internationalization in iOS/macOS apps — String Catalogs (.xcstrings), LocalizedStringKey, LocalizedStringResource, pluralization, FormatStyle for numbers/dates/measurements, right-to-left layout, Dynamic Type, and locale-aware formatting. Use when adding multi-language support, setting up String Catalogs, handling plural forms, formatting dates/numbers/currencies for different locales, testing localizations, or making UI work correctly in RTL languages like Arabic and Hebrew."--- # iOS Localization & Internationalization Localize iOS 26+ apps using String Catalogs, modern string types, FormatStyle, and RTL-aware layout. Localization mistakes cause App Store rejections in non-English markets, mistranslated UI, and broken layouts. Ship with correct localization from the start. ## Contents - [String Catalogs (.xcstrings)](#string-catalogs-xcstrings)- [String Types -- Decision Guide](#string-types-decision-guide)- [String Interpolation in Localized Strings](#string-interpolation-in-localized-strings)- [Pluralization](#pluralization)- [FormatStyle -- Locale-Aware Formatting](#formatstyle-locale-aware-formatting)- [Right-to-Left (RTL) Layout](#right-to-left-rtl-layout)- [Common Mistakes](#common-mistakes)- [Localization Review Checklist](#review-checklist)- [References](#references) ## String Catalogs (.xcstrings) String Catalogs replaced `.strings` and `.stringsdict` files starting in Xcode 15 / iOS 17. They unify all localizable strings, pluralization rules, and device variations into a single JSON-based file with a visual editor. **Why String Catalogs exist:**- `.strings` files required manual key management and fell out of sync- `.stringsdict` required complex XML for plurals- String Catalogs auto-extract strings from code, track translation state, and support plurals natively **How automatic extraction works:** Xcode scans for these patterns on each build: ```swift// SwiftUI -- automatically extracted (LocalizedStringKey)Text("Welcome back")              // key: "Welcome back"Label("Settings", systemImage: "gear")Button("Save") { }Toggle("Dark Mode", isOn: $dark) // Programmatic -- automatically extractedString(localized: "No items found")LocalizedStringResource("Order placed") // NOT extracted -- plain String, not localizedlet msg = "Hello"                 // just a String, invisible to Xcode``` Xcode adds discovered keys to the String Catalog automatically. Mark translations as Needs Review, Translated, or Stale in the editor. For detailed String Catalog workflows, migration, and testing strategies, see [references/string-catalogs.md](references/string-catalogs.md). ## String Types -- Decision Guide ### LocalizedStringKey (SwiftUI default) SwiftUI views accept `LocalizedStringKey` for their text parameters. String literals are implicitly converted -- no extra work needed. ```swift// These all create a LocalizedStringKey lookup automatically:Text("Welcome back")Label("Profile", systemImage: "person")Button("Delete") { deleteItem() }.navigationTitle("Home")``` Use `LocalizedStringKey` when passing strings directly to SwiftUI view initializers. Do not construct `LocalizedStringKey` manually in most cases. ### String(localized:) -- Modern NSLocalizedString replacement Use for any localized string outside a SwiftUI view initializer. Returns a plain `String`. Available iOS 16+. ```swift// Basiclet title = String(localized: "Welcome back") // With default value (key differs from English text)let msg = String(localized: "error.network",                 defaultValue: "Check your internet connection") // With table and bundlelet label = String(localized: "onboarding.title",                   table: "Onboarding",                   bundle: .module) // With comment for translatorslet btn = String(localized: "Save",                 comment: "Button title to save the current document")``` ### LocalizedStringResource -- Pass localization info without resolving Use when you need to pass a localized string to an API that resolves it later (App Intents, widgets, notifications, system frameworks). Available iOS 16+. ```swift// App Intents require LocalizedStringResourcestruct OrderCoffeeIntent: AppIntent {    static var title: LocalizedStringResource = "Order Coffee"} // Widgetsstruct MyWidget: Widget {    var body: some WidgetConfiguration {        StaticConfiguration(kind: "timer",                            provider: Provider()) { entry in            TimerView(entry: entry)        }        .configurationDisplayName(LocalizedStringResource("Timer"))    }} // Pass around without resolving yetfunc showAlert(title: LocalizedStringResource, message: LocalizedStringResource) {    // Resolved at display time with the user's current locale    let resolved = String(localized: title)}``` ### When to use each type | Context | Type | Why ||---------|------|-----|| SwiftUI view text parameters | `LocalizedStringKey` (implicit) | SwiftUI handles lookup automatically || Computed strings in view models / services | `String(localized:)` | Returns resolved `String` for logic || App Intents, widgets, system APIs | `LocalizedStringResource` | Framework resolves at display time || Error messages shown to users | `String(localized:)` | Resolved in catch blocks || Logging / analytics (not user-facing) | Plain `String` | No localization needed | ## String Interpolation in Localized Strings Interpolated values in localized strings become positional arguments that translators can reorder. ```swift// English: "Welcome, Alice! You have 3 new messages."// German:  "Willkommen, Alice! Sie haben 3 neue Nachrichten."// Japanese: "Alice さん、新しいメッセージが 3 件あります。"let text = String(localized: "Welcome, \(name)! You have \(count) new messages.")``` In the String Catalog, this appears with `%@` and `%lld` placeholders that translators can reorder:- English: `"Welcome, %@! You have %lld new messages."`- Japanese: `"%@さん、新しいメッセージが%lld件あります。"` **Type-safe interpolation** (preferred over format specifiers):```swift// Interpolation provides type safetyString(localized: "Score: \(score, format: .number)")String(localized: "Due: \(date, format: .dateTime.month().day())")``` ## Pluralization String Catalogs handle pluralization natively -- no `.stringsdict` XML required. ### Setup in String Catalog When a localized string contains an integer interpolation, Xcode detects it and offers plural variants in the String Catalog editor. Supply translations for each CLDR plural category: | Category | English example | Arabic example ||----------|----------------|----------------|| zero | (not used) | 0 items || one | 1 item | 1 item || two | (not used) | 2 items (dual) || few | (not used) | 3-10 items || many | (not used) | 11-99 items || other | 2+ items | 100+ items | English uses only `one` and `other`. Arabic uses all six. Always supply `other` as the fallback. ```swift// Code -- single interpolation triggers plural supportText("\(unreadCount) unread messages") // String Catalog entries (English)://   one:   "%lld unread message"//   other: "%lld unread messages"``` ### Device Variations String Catalogs support device-specific text (iPhone vs iPad vs Mac): ```swift// In String Catalog editor, enable "Vary by Device" for a key// iPhone: "Tap to continue"// iPad:   "Tap or click to continue"// Mac:    "Click to continue"``` ### Grammar Agreement (iOS 17+) Use `^[...]` inflection syntax for automatic grammatical agreement: ```swift// Automatically adjusts for gender/number in supported languagesText("^[\(count) \("photo")](inflect: true) added")// English: "1 photo added" / "3 photos added"// Spanish: "1 foto agregada" / "3 fotos agregadas"``` ## FormatStyle -- Locale-Aware Formatting Never hard-code date, number, or measurement formats. Use `FormatStyle` (iOS 15+) so formatting adapts to the user's locale automatically. ### Dates ```swiftlet now = Date.now // Preset stylesnow.formatted(date: .long, time: .shortened)// US: "January 15, 2026 at 3:30 PM"// DE: "15. Januar 2026 um 15:30"// JP: "2026年1月15日 15:30" // Component-basednow.formatted(.dateTime.month(.wide).day().year())// US: "January 15, 2026" // In SwiftUIText(now, format: .dateTime.month().day().year())``` ### Numbers ```swiftlet count = 1234567count.formatted()                     // "1,234,567" (US) / "1.234.567" (DE)count.formatted(.number.precision(.fractionLength(2)))count.formatted(.percent)             // For 0.85 -> "85%" (US) / "85 %" (FR) // Currencylet price = Decimal(29.99)price.formatted(.currency(code: "USD"))  // "$29.99" (US) / "29,99 $US" (FR)price.formatted(.currency(code: "EUR"))  // "29,99 EUR" (DE)``` ### Measurements ```swiftlet distance = Measurement(value: 5, unit: UnitLength.kilometers)distance.formatted(.measurement(width: .wide))// US: "3.1 miles" (auto-converts!) / DE: "5 Kilometer" let temp = Measurement(value: 22, unit: UnitTemperature.celsius)temp.formatted(.measurement(width: .abbreviated))// US: "72 F" (auto-converts!) / FR: "22 C"``` ### Duration, PersonName, Lists ```swift// Durationlet dur = Duration.seconds(3661)dur.formatted(.time(pattern: .hourMinuteSecond))  // "1:01:01" // Person nameslet name = PersonNameComponents(givenName: "John", familyName: "Doe")name.formatted(.name(style: .long))   // "John Doe" (US) / "Doe John" (JP) // Listslet items = ["Apples", "Oranges", "Bananas"]items.formatted(.list(type: .and))    // "Apples, Oranges, and Bananas" (EN)                                      // "Apples, Oranges et Bananas" (FR)``` For the complete FormatStyle reference, custom styles, and RTL layout, see [references/formatstyle-locale.md](references/formatstyle-locale.md). ## Right-to-Left (RTL) Layout SwiftUI automatically mirrors layouts for RTL languages (Arabic, Hebrew, Urdu, Persian). Most views require zero changes. ### What SwiftUI auto-mirrors - `HStack` children reverse order- `.leading` / `.trailing` alignment and padding swap sides- `NavigationStack` back button moves to trailing edge- `List` disclosure indicators flip- Text alignment follows reading direction ### What needs manual attention ```swift// Testing RTL in previewsMyView()    .environment(\.layoutDirection, .rightToLeft)    .environment(\.locale, Locale(identifier: "ar")) // Images that should mirror (directional arrows, progress indicators)Image(systemName: "chevron.right")    .flipsForRightToLeftLayoutDirection(true) // Images that should NOT mirror: logos, photos, clocks, music notes // Forced LTR for specific content (phone numbers, code)Text("+1 (555) 123-4567")    .environment(\.layoutDirection, .leftToRight)``` ### Layout rules - **DO** use `.leading` / `.trailing` -- they auto-flip for RTL- **DON'T** use `.left` / `.right` -- they are fixed and break RTL- **DO** use `HStack` / `VStack` -- they respect layout direction- **DON'T** use absolute `offset(x:)` for directional positioning ## Common Mistakes ### DON'T: Use NSLocalizedString in new code```swift// WRONG -- legacy API, verbose, no compiler integration with String Catalogslet title = NSLocalizedString("welcome_title", comment: "Welcome screen title")``` ### DO: Use String(localized:) or let SwiftUI handle it```swift// CORRECTlet title = String(localized: "welcome_title",                   defaultValue: "Welcome!",                   comment: "Welcome screen title")// Or in SwiftUI, just:Text("Welcome!")``` ### DON'T: Concatenate localized strings```swift// WRONG -- word order varies by languagelet greeting = String(localized: "Hello") + ", " + name + "!"``` ### DO: Use string interpolation```swift// CORRECT -- translators can reorder placeholderslet greeting = String(localized: "Hello, \(name)!")``` ### DON'T: Hard-code date/number formats```swift// WRONG -- US-only formatlet formatter = DateFormatter()formatter.dateFormat = "MM/dd/yyyy"  // Meaningless in most countries``` ### DO: Use FormatStyle```swift// CORRECT -- adapts to user localeText(date, format: .dateTime.month().day().year())``` ### DON'T: Use fixed-width layouts```swift// WRONG -- German text is ~30% longer than EnglishText(title).frame(width: 120)``` ### DO: Use flexible layouts```swift// CORRECTText(title).fixedSize(horizontal: false, vertical: true)// Or use VStack/wrapping that accommodates expansion``` ### DON'T: Use .left / .right for alignment```swift// WRONG -- does not flip for RTLHStack { Spacer(); text }.padding(.left, 16)``` ### DO: Use .leading / .trailing```swift// CORRECTHStack { Spacer(); text }.padding(.leading, 16)``` ### DON'T: Put user-facing strings as plain String outside SwiftUI```swift// WRONG -- not localizedlet errorMessage = "Something went wrong"showAlert(message: errorMessage)``` ### DO: Use LocalizedStringResource for deferred resolution```swift// CORRECTlet errorMessage = LocalizedStringResource("Something went wrong")showAlert(message: String(localized: errorMessage))``` ### DON'T: Skip pseudolocalization testingTesting only in English hides truncation, layout, and RTL bugs. ### DO: Test with German (long) and Arabic (RTL) at minimumUse Xcode scheme settings to override the app language without changing device locale. ## Review Checklist - [ ] All user-facing strings use localization (`LocalizedStringKey` in SwiftUI or `String(localized:)`)- [ ] No string concatenation for user-visible text- [ ] Dates and numbers use `FormatStyle`, not hardcoded formats- [ ] Pluralization handled via String Catalog plural variants (not manual if/else)- [ ] Layout uses `.leading` / `.trailing`, not `.left` / `.right`- [ ] UI tested with long text (German) and RTL (Arabic)- [ ] String Catalog includes all target languages- [ ] Images needing RTL mirroring use `.flipsForRightToLeftLayoutDirection(true)`- [ ] App Intents and widgets use `LocalizedStringResource`- [ ] No `NSLocalizedString` usage in new code- [ ] Comments provided for ambiguous keys (context for translators)- [ ] `@ScaledMetric` used for spacing that must scale with Dynamic Type- [ ] Currency formatting uses explicit currency code, not locale default- [ ] Pseudolocalization tested (accented, right-to-left, double-length)- [ ] Ensure localized string types are Sendable; use @MainActor for locale-change UI updates ## References - FormatStyle patterns: [references/formatstyle-locale.md](references/formatstyle-locale.md)- String Catalogs guide: [references/string-catalogs.md](references/string-catalogs.md)