跳到主要内容
知仓学习社ZHICANG

ios-localization

Implement, review, or improve localization and internationalization in iOS/macOS apps — String Catalogs (.xcstrings), generated localizable symbols,…

不碰外部(只输出文字)无严重或高危命中dpearson2699/swift-ios-skills

它会碰到什么

扫了多少4 个文本文件,61 KB
它会碰到什么不碰外部(只输出文字)
命中总数0 处
命中统计严重 0 · 高 0 · 中 0 · 低 0

这一栏是扫描器报的事实,不是结论。命中多不等于有毒(安全工具、规则库、示例脚本本来就会包含危险写法),命中少也不等于干净。它和你手上的凭据、文件、网络有什么关系,需要你自己看。

技能内容

iOS Localization & Internationalization

Localize Apple-platform apps with String Catalogs, modern string types,

locale-aware formatting, and right-to-left layout.

Contents

  • [String Catalogs and Generated Symbols](#string-catalogs-and-generated-symbols)
  • [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 and Generated Symbols

String Catalogs are the recommended Xcode 15+ workflow for new localization work. They keep localizable strings, pluralization rules, and device variations together in an Xcode-managed JSON file with a visual editor. Legacy .strings and .stringsdict files can coexist during migration, but new Swift and SwiftUI code should default to String Catalogs.

How automatic extraction works:

Xcode scans for these patterns on each build:

// SwiftUI -- automatically extracted (LocalizedStringKey)
Text("Welcome back")              // key: "Welcome back"
Label("Settings", systemImage: "gear")
Button("Save") { }
Toggle("Dark Mode", isOn: $dark)

// Programmatic -- automatically extracted
String(localized: "No items found")
LocalizedStringResource("Order placed")

// Plain String: not extracted or localized
let msg = "Hello"

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).

Generated symbols are an Xcode 26 typed-access layer on top of String Catalogs;

they do not change the catalog's Xcode 15 availability.

Enable: Build Settings > Localization > Generate String Catalog Symbols → Yes (on by default in new Xcode 26 projects). Requires catalog format version 1.1.

Workflow: Add a key manually via the (+) button in the String Catalog editor — manual keys have the Generate Swift Symbol checkbox enabled by default. Auto-extracted keys can also opt in via Refactor > Convert Strings to Symbols. Use stable manual keys for generated-symbol strings. Avoid source-copy-derived keys for API-facing strings because wording edits can rename generated identifiers and churn call sites.

// Generated from key "room_available" in Localizable.xcstrings
Text(.roomAvailable)

// Parameterized key "landmarks_count" with %1$(count)lld
Text(.landmarksCount(count: 42))

// Non-default table "Booking.xcstrings"
Text(.Booking.confirmBookingCta)

Xcode derives symbol names by camelCasing the key: settings.notifications.toggle.settingsNotificationsToggle. You can convert existing extracted strings to symbols via Refactor > Convert Strings to Symbols (reversible).

Generated symbols are internal. For cross-module access, create a public wrapper extension. For heavier multi-module setups, use xcstrings-tool instead.

For the full generated symbols reference — extraction states, symbol derivation rules, and cross-module patterns — see [references/string-catalogs.md](references/string-catalogs.md).

String Types -- Decision Guide

| Context | Type | Why |

|---|---|---|

| SwiftUI view text | LocalizedStringKey (implicit) | SwiftUI performs lookup |

| View models, services, and errors | String(localized:) | Resolves to String now |

| App Intents, widgets, deferred system UI | LocalizedStringResource | Carries localization until display |

| Non-user-facing logs and analytics | Plain String | No localization needed |

LocalizedStringKey (SwiftUI default)

SwiftUI views accept LocalizedStringKey for their text parameters. String literals are implicitly converted -- no extra work needed.

Text("Welcome back")
Button("Delete") { deleteItem() }

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. The literal/interpolated initializer is available iOS 15+; resolving a LocalizedStringResource is iOS 16+.

let title = String(localized: "Welcome back")
let msg = String(localized: "error.network",
                 defaultValue: "Check your internet connection")

For Swift package localization failures, answer with this explicit resource checklist before bundle debugging:

  1. Package.swift declares defaultLocalization.
  2. The target resources list processes the catalog location, such as .process("Resources").
  3. Localizable.xcstrings is actually inside that processed target-resource path.

Only after those pass, debug lookup with bundle: .module or Text(..., bundle: .module).

Existing NSLocalizedString literal keys can still be exported or migrated by Xcode tooling, but new Swift code should prefer String(localized:), SwiftUI literals, LocalizedStringResource, or generated symbols.

LocalizedStringResource -- Pass localization info without resolving

Use when a string must be carried as a localizable value for later resolution, especially for App Intents, widgets, notifications, generated localizable symbols, and system APIs that accept LocalizedStringResource directly. Use String(localized:) when code needs the resolved string immediately. Available iOS 16+.

struct OrderCoffeeIntent: AppIntent {
    static var title: LocalizedStringResource = "Order Coffee"
}

func showAlert(title: LocalizedStringResource, message: LocalizedStringResource) {
    let resolved = String(localized: title)
}

String Interpolation in Localized Strings

Interpolated values in localized strings become positional arguments that translators can reorder.

// 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):

// Interpolation provides type safety
String(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.

// Code -- single interpolation triggers plural support
Text("\(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):

// 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"

Use Foundation's automatic grammar agreement markup when nearby words must

inflect for a value's number or gender. Preserve the complete inflecting phrase

for translators; see Automatic Grammar Agreement.

FormatStyle -- Locale-Aware Formatting

Never hard-code user-facing formats. Use FormatStyle and test output under

contrasting locales such as en_US, de_DE, ar_SA, and ja_JP.

ios-localization owns FormatStyle guidance when the issue is locale-aware user-facing display, including numbers, dates, currency, units, names, lists, calendars, separators, and locale preview/testing. For custom FormatStyle, ParseableFormatStyle, parsing, Date.IntervalFormatStyle, URL.FormatStyle, or reusable formatter API design, route to swift-formatstyle; keep ios-localization advice to locale risks and testing unless implementation is explicitly requested.

Dates

let now = Date.now

// Preset styles
now.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-based
now.formatted(.dateTime.month(.wide).day().year())
// US: "January 15, 2026"

// In SwiftUI
Text(now, format: .dateTime.month().day().year())

Numbers

let count = 1234567
count.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)

// Currency
let 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

let 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"

Load [references/formatstyle-locale.md](references/formatstyle-locale.md) for

duration, names, lists, custom styles, variant matrices, and deeper RTL testing.

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

// Testing RTL in previews
MyView()
    .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 fixed-width layouts

// WRONG -- German text is ~30% longer than English
Text(title).frame(width: 120)

DO: Use flexible layouts

// CORRECT
Text(title).fixedSize(horizontal: false, vertical: true)
// Or use VStack/wrapping that accommodates expansion

DON'T: Skip pseudolocalization testing

Testing only in English hides truncation, layout, and RTL bugs.

DO: Test with German (long) and Arabic (RTL) at minimum

Use 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)
  • [ ] Manually-managed keys use stable symbol-style names, not English text as the key
  • [ ] Generate String Catalog Symbols enabled for targets with manually-managed keys
  • [ ] 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)

想直接用这个技能?

本站把开放许可(MIT / Apache 等)的技能按仓库打包整理到网盘,点一下转存到你自己的网盘,不用一个个从 GitHub 拉。许可未声明的技能只给原始仓库链接,不打包。