ai-docs/assets/skills/swift-localization/SKILL.md
Matt Bruce 88e4402d38 fix: use tool-agnostic ~/.agents/ as default install path
Copilot, Claude, Cursor, and others all read from ~/.agents/.
The npx skills CLI handles fan-out to tool-specific directories.
2026-02-11 12:13:20 -06:00

5.1 KiB

name description globs
Swift Localization Localization patterns using String Catalogs and modern APIs
**/*.swift
**/*.xcstrings

Localization with String Catalogs

Use String Catalogs (.xcstrings files) for localization in modern Swift projects.

Required Language Support

At minimum, support these languages:

  • English (en) - Base language
  • Spanish - Mexico (es-MX)
  • French - Canada (fr-CA)

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:

  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

# Export for translators
xcodebuild -exportLocalizations -project MyApp.xcodeproj -localizationPath ./Localizations

# Import translations
xcodebuild -importLocalizations -project MyApp.xcodeproj -localizationPath ./Localizations/es-MX.xcloc