Agent skills for iOS, iPadOS, Swift, SwiftUI, and modern Apple framework development.
71
89%
Does it follow best practices?
Impact
—
No eval scenarios have been run
Advisory
Suggest reviewing before use
A String Catalog is a single .xcstrings file (JSON-based) that holds every localizable string in a target, along with all translations, plural forms, and device variations. It replaces the combination of .strings and .stringsdict files that previously required manual synchronization.
Availability: Xcode 15+, all Apple platforms.
Localizable.xcstrings (the default table name, matching the legacy Localizable.strings)For a non-default table name (e.g., Onboarding.xcstrings), reference it explicitly:
String(localized: "welcome.title", table: "Onboarding")On every build, Xcode scans source files and extracts strings from known localizable initializers. Extraction is compiler-driven -- it recognizes these patterns:
Text("Hello, world") // extracted
Label("Settings", systemImage: "gear") // extracted
Button("Save") { } // extracted
Toggle("Enable notifications", isOn: $on) // extracted
.navigationTitle("Home") // extracted
Section("Account") { } // extracted
// NOT extracted -- computed or variable strings
Text(viewModel.title) // not extracted (runtime value)
Text(verbatim: "v1.2.3") // not extracted (verbatim skips localization)String(localized: "No results found") // extracted
String(localized: "error.title",
defaultValue: "Something went wrong",
comment: "Generic error alert title") // extracted with default + commentLocalizedStringResource("Order placed") // extracted
static var title: LocalizedStringResource = "Title" // extractedlet x: String = "Not localized" // plain String assignment
print("debug info") // not user-facing
NSLocalizedString("legacy", comment: "") // NOT auto-extracted into .xcstringsIf automatic extraction misses a string, add it manually in the String Catalog editor.
Open the .xcstrings file in Xcode to use the visual editor:
For manually-managed strings, use stable symbol-style keys rather than English text as the key. This prevents silent localization breaks when UI copy changes (a typo or rewording just creates a new key and stales the old one — no compiler error). With Xcode 26's generated symbols, stable keys also produce readable, predictable Swift accessors.
onboarding.welcome.title -> "Welcome"
onboarding.welcome.subtitle -> "Get started in minutes"
settings.notifications.toggle -> "Enable Notifications"
error.network.title -> "Connection Error"
error.network.message -> "Check your internet and try again"Use String(localized:defaultValue:) when you want a structured key that differs from the English text:
let title = String(localized: "error.network.title",
defaultValue: "Connection Error",
comment: "Title for network error alert")For SwiftUI auto-extracted strings, the literal text IS the key by default. This is fine for simple views. For any string you manage manually — shared keys, keys referenced across modules, or keys where copy changes frequently — use a stable key instead.
class OrderService {
func statusMessage(for order: Order) -> String {
switch order.status {
case .shipped:
return String(localized: "order.status.shipped",
defaultValue: "Your order has shipped!",
comment: "Order status when item is in transit")
case .delivered:
return String(localized: "order.status.delivered",
defaultValue: "Delivered on \(order.deliveryDate!, format: .dateTime.month().day())",
comment: "Order status with delivery date")
case .processing:
return String(localized: "order.status.processing",
defaultValue: "Processing your order...",
comment: "Order status while being prepared")
}
}
}// From a specific table
String(localized: "greeting",
table: "Onboarding",
comment: "First-launch greeting")
// From a specific bundle (framework or Swift package)
String(localized: "button.save",
table: "SharedUI",
bundle: .module,
comment: "Save button in shared component")// Uses Bundle.main by default -- no bundle argument needed
String(localized: "Hello")// .module refers to the package's resource bundle
String(localized: "Hello", bundle: .module)
// In SwiftUI, Text uses the module's bundle automatically
// if the .xcstrings file is in the package's resources
Text("Hello") // looks up in the package's Localizable.xcstrings// Reference the framework's bundle
let frameworkBundle = Bundle(for: MyFrameworkClass.self)
String(localized: "Hello",
bundle: .init(frameworkBundle.bundleURL))Each Swift package target that contains user-facing strings needs its own String Catalog.
.target(
name: "SharedUI",
dependencies: [],
resources: [
.process("Resources") // Localizable.xcstrings goes here
]
)Sources/
SharedUI/
Resources/
Localizable.xcstrings <- String Catalog for this module
Views/
ButtonStyles.swift// Inside the package -- .module resolves automatically
public struct SaveButton: View {
public var body: some View {
Button(String(localized: "Save", bundle: .module)) { }
}
}Important: SwiftUI Text("Save") inside an SPM target looks up in .module automatically only if the .xcstrings file is properly included in the target's resources. Verify by checking that Xcode shows the file under the target in the project navigator.
Text("\(itemCount) items in your cart")one: "%lld item in your cart"
other: "%lld items in your cart"zero: "لا توجد عناصر في سلتك"
one: "عنصر واحد في سلتك"
two: "عنصران في سلتك"
few: "%lld عناصر في سلتك" (3-10)
many: "%lld عنصرًا في سلتك" (11-99)
other: "%lld عنصر في سلتك" (100+)When a string has two integer interpolations, the String Catalog shows a matrix of plural combinations:
Text("\(photoCount) photos in \(albumCount) albums")
// English needs: one/one, one/other, other/one, other/otherEnable "Vary by Device" for a key to provide different text on iPhone, iPad, Apple Watch, Mac, Apple TV, and Apple Vision Pro.
// Code is the same everywhere:
Text("Tap to continue")
// String Catalog provides:
// iPhone: "Tap to continue"
// iPad: "Tap or click to continue"
// Mac: "Click to continue"
// Vision: "Look and tap to continue"xcodebuild -exportLocalizations).xcloc bundles (one per language).xcloc files to translators (they contain XLIFF 1.2 inside)xcodebuild -exportLocalizations \
-project MyApp.xcodeproj \
-localizationPath ./Localizations \
-exportLanguage de -exportLanguage ja -exportLanguage ar.xcloc filexcodebuild -importLocalizations \
-project MyApp.xcodeproj \
-localizationPath ./Localizations/de.xclocThe .xcstrings file is JSON. Understanding the structure enables programmatic manipulation (CI validation, batch updates, translation memory integration).
{
"sourceLanguage": "en",
"version": "1.0",
"strings": {
"Welcome, %@!": {
"comment": "Greeting shown on home screen with user name",
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Welcome, %@!"
}
},
"de": {
"stringUnit": {
"state": "translated",
"value": "Willkommen, %@!"
}
}
}
},
"room_available": {
"comment": "Button label on room search results",
"extractionState": "manual",
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Book this room"
}
}
}
},
"%lld items": {
"localizations": {
"en": {
"variations": {
"plural": {
"one": {
"stringUnit": {
"state": "translated",
"value": "%lld item"
}
},
"other": {
"stringUnit": {
"state": "translated",
"value": "%lld items"
}
}
}
}
}
}
}
}
}Note the "room_available" key above: it uses "extractionState": "manual" and a stable symbol-style key with the English text in "value", not in the key itself. This is the pattern that enables generated symbols in Xcode 26+.
"new" -- Xcode extracted the key but no translation exists"translated" -- Translation provided"needs_review" -- Marked for review (source string changed or manual flag)"stale" -- Key no longer found in code (removed on next clean build)The extractionState field (separate from translation state) tracks how a key entered the catalog:
| Value | Meaning |
|---|---|
extracted_with_value | Xcode found the string in source code and extracted it automatically |
manual | Added by hand via the (+) button — not discovered from code. Xcode will never update or remove manual keys during build sync |
stale | Previously extracted from code, but Xcode can no longer find it. Orphaned translations still exist |
migrated | Converted from a legacy .strings or .stringsdict file |
The manual state is significant: manual keys have the Generate Swift Symbol checkbox enabled by default, so they automatically produce compiler-checked LocalizedStringResource accessors when the build setting is on. Auto-extracted keys can also generate symbols — enable the checkbox per-key or use Refactor > Convert Strings to Symbols.
Xcode 26 generates type-safe Swift symbols from String Catalog keys, replacing stringly-typed localization access with compiler-checked LocalizedStringResource properties and functions.
Yes (on by default in new Xcode 26 projects)"1.1" — Xcode 26 writes this automatically when symbol generation metadata is presentXcode camelCases the key name, lowercasing the first segment:
| Catalog key | Generated symbol |
|---|---|
room_available | .roomAvailable |
settings.notifications.toggle | .settingsNotificationsToggle |
TITLE | .title |
Keys with format specifiers become functions. Use named placeholders %(name)lld for descriptive argument labels; bare %lld produces generic labels:
| Catalog key | Format | Generated symbol |
|---|---|---|
landmarks_count | %(count)lld | .landmarksCount(count: Int) |
greeting | %@ | .greeting(_ param1: String) |
You can rename parameters during refactoring for more descriptive signatures.
// Simple key — static property
Text(.roomAvailable)
// Parameterized key — function
Text(.landmarksCount(count: 42))
// Non-default table (Booking.xcstrings)
Text(.Booking.confirmBookingCta)
// In non-SwiftUI code
let title = String(localized: .roomAvailable)
let attributed = AttributedString(localized: .greeting(userName))Code completion supports generated symbols — type . and choose from the menu.
Select one or more keys in the String Catalog editor, Control-click, and choose Refactor > Convert Strings to Symbols. Xcode replaces string literal usage in code with the generated symbol. This is reversible via Convert Symbols to Strings.
Generated symbols are declared internal. Code in other modules cannot access them directly. Default to a public wrapper; reach for xcstrings-tool if the wrapper becomes unwieldy across many modules:
LocalizedStringResource that delegates to the internal symbols.xcstrings files — use this for heavier multi-module setups where maintaining manual wrappers becomes tediousFor Swift Packages, the generated symbols use the .module bundle automatically. The internal visibility means only code within the same package target can reference them.
Edit Scheme > Run > Options > App Language. Choose any added language to launch the app in that locale without changing the device/simulator system language.
Xcode provides built-in pseudolocalization modes (Edit Scheme > Run > Options > App Language):
| Option | Effect | Catches |
|---|---|---|
| Accented Pseudolanguage | Adds accents: "Hello" -> "[Hellо]" | Hardcoded strings (unlocalized text is obvious) |
| Right-to-Left Pseudolanguage | Forces RTL layout | Layout mirroring bugs |
| Double-Length Pseudolanguage | Doubles all strings | Truncation and overflow |
| Bounded String Pseudolanguage | Wraps strings in brackets | Missing localizations |
func testGermanLayout() {
let app = XCUIApplication()
app.launchArguments += ["-AppleLanguages", "(de)"]
app.launchArguments += ["-AppleLocale", "de_DE"]
app.launch()
// Verify no truncation on key screens
let saveButton = app.buttons["Speichern"]
XCTAssertTrue(saveButton.exists)
XCTAssertTrue(saveButton.isHittable)
}Use a snapshot testing library to capture screenshots in multiple locales and compare them for layout regressions:
let locales = ["en_US", "de_DE", "ar_SA", "ja_JP"]
for locale in locales {
app.launchArguments = ["-AppleLanguages", "(\(locale.prefix(2)))"]
app.launch()
// Capture and compare snapshot
}Check that all keys are translated before release:
# Parse the .xcstrings JSON and check for "new" or empty states
python3 -c "
import json, sys
with open('Localizable.xcstrings') as f:
data = json.load(f)
missing = []
for key, info in data['strings'].items():
for lang, loc in info.get('localizations', {}).items():
unit = loc.get('stringUnit', {})
if unit.get('state') in ('new', None) or not unit.get('value'):
missing.append(f'{lang}: {key}')
if missing:
print('Missing translations:')
for m in missing: print(f' {m}')
sys.exit(1)
print('All translations complete.')
".strings file in the project navigator.xcstrings file with all existing keys and translations.strings / .stringsdict files from the targetIf automatic migration fails (complex bundle setups, CocoaPods):
Localizable.xcstrings.strings files into the String Catalog editor.stringsdict into plural variants.strings keys present in the new String Catalog.stringsdict plural rules converted to String Catalog plural variants.strings and .stringsdict files from the target.xcstrings file (it is JSON, diffs well in version control)String Catalogs and .strings files can coexist in the same target during migration. Xcode resolves keys from the String Catalog first, then falls back to .strings. Remove legacy files after verifying the migration.
Localizable.xcstrings as the single source of truth for each target..xcloc bundles to translators after each sprint or feature merge.skills
accessorysetupkit
references
activitykit
references
adattributionkit
references
alarmkit
references
app-clips
app-intents
references
app-store-optimization
app-store-review
apple-on-device-ai
appmigrationkit
references
audioaccessorykit
references
authentication
references
avkit
references
background-processing
references
browserenginekit
references
callkit
references
carplay
references
cloudkit
references
contacts-framework
references
core-bluetooth
references
core-data
core-motion
references
core-nfc
references
coreml
references
cryptokit
references
cryptotokenkit
references
debugging-instruments
device-integrity
references
dockkit
references
energykit
references
eventkit
references
financekit
references
focus-engine
gamekit
references
healthkit
references
homekit
references
ios-accessibility
ios-localization
ios-networking
ios-simulator
references
mapkit
metrickit
references
musickit
references
natural-language
references
paperkit
references
passkit
references
pdfkit
references
pencilkit
references
permissionkit
references
photokit
push-notifications
realitykit
references
relevancekit
references
scenekit
references
sensorkit
references
speech-recognition
spritekit
references
storekit
swift-api-design-guidelines
swift-architecture
swift-charts
references
swift-codable
swift-concurrency
swift-formatstyle
swift-language
swift-security
references
swift-testing
swiftdata
swiftlint
swiftui-animation
swiftui-gestures
references
swiftui-layout-components
swiftui-liquid-glass
references
swiftui-patterns
swiftui-performance
swiftui-uikit-interop
swiftui-webkit
tabletopkit
references
tipkit
references
vision-framework
weatherkit
references
widgetkit
references