WWDC Quick Look đź’“ By SwiftGGTeam
Code-along: Explore localization with Xcode

Code-along: Explore localization with Xcode

Watch original video

Highlight

Xcode 26 uses an on-device model to auto-generate translation comments for String Catalogs, and can compile strings into type-safe Swift symbols.


Core content

Translators usually do not see the code, and they do not touch the running app. A bare “Landmarks” string sent over for translation forces them to guess: is it the app name, or a landmark on a map? Likewise, ”%@ is contained in %@” has two placeholders that no one can translate correctly without context. This is the most common pain point in localization projects: developers write the strings and walk away, and translators keep coming back to ask.

Andreas walks through Xcode’s localization pipeline from scratch using the Landmarks sample project, with the focus on two new tools in Xcode 26: automatic comment generation and symbol generation. The first uses an on-device model to look at where each string appears in the code and writes descriptions like “The text label on a button to cancel the deletion of a collection.” The second compiles entries in a String Catalog into static properties or functions on LocalizedStringResource, so you get autocomplete and compile-time checks while writing code. Both features solve the same problem: keep strings and their context tied together.


Details

Getting started: let Xcode collect the strings (01:34)

Create a new String Catalog called Localizable, build once, and Xcode scans the code and writes every localizable string into the catalog. Most SwiftUI APIs (Text, Button, etc.) go through the localization channel by default. On the Foundation side, mark strings explicitly with String(localized:).

// import SwiftUI
Text("Featured Landmark", comment: "Big headline in the hero image of featured landmarks.")

Button("Keep") { }

// import Foundation
String(localized: "New Collection", comment: "Default name for a new user-created collection.")

Key points:

  • Text("Featured Landmark", comment:): SwiftUI’s Text takes the string literal as the localization key, and the comment argument is written into the String Catalog as context for translators.
  • Button("Keep") { }: the title argument of Button is also localized by default, with no extra annotation needed.
  • String(localized:): the matching API on the Foundation side, used in ViewModel, Model, helper functions, and other non-SwiftUI code.

When a string contains a placeholder like %lld, right-click and pick “Vary by Plural” to fill in the one / other forms separately. The runtime then picks the right form based on the number.

Give translators context (06:00)

Andreas breaks a good comment into three parts: which UI element it is on (button, subtitle, tab bar), what UI elements surround it, and what content the placeholders will hold.

Text("Delete",
comment: "Delete button shown in an alert asking for confirmation to delete the collection.")

String(localized: "Shared by Friends", comment: "Subtitle of post that was shared by friends.")

Key points:

  • The first comment makes it clear that “Delete” appears in an alert and is the button that confirms deletion, so translators do not mistake it for a verb heading.
  • The second comment says “Shared by Friends” is the subtitle of a post, which lets translators pick a more conversational phrasing.

Xcode 26 adds “Generate Comment” to the right-click menu of a string. It uses an on-device model to look at the code around the string and writes a comment, which the developer can accept, rewrite, or extend. Turn on “automatically generate string catalog comments” in Settings → Editing, and every newly extracted string gets a comment.

In the exported XLIFF file, auto-generated comments carry an auto-generated mark, so translation tools can recognize them (09:13):

<trans-unit id="Grand Canyon" xml:space="preserve">
<source>Grand Canyon</source>
<target state="new">Grand Canyon</target>
<note from="auto-generated">Suggestion for searching landmarks</note>
</trans-unit>

Multi-target projects: #bundle and tableName (09:58)

Once a project splits into a framework or Swift Package, each target may have its own String Catalog. Localization APIs need a bundle argument to find the right resources at runtime.

// In the main app
Text("My Collections",
comment: "Section title above user-created collections.")

// In a Swift Package or Framework
Text("My Collections",
bundle: #bundle,
comment: "Section title above user-created collections.")

Key points:

  • Inside the main app, leaving out bundle is the same as Bundle.main, and Xcode looks up resources in the main bundle.
  • #bundle is a macro introduced in Xcode 26. It expands at compile time to the bundle of the current target, so when you write it inside a framework or Swift Package, it finds that framework’s own resources.
  • #bundle works on older OS versions too, so no availability checks are needed when migrating.

To split strings into separate files by feature, add the tableName argument (10:56):

Text("My Collections",
tableName: "Discover",
comment: "Section title above user-created collections.")

Text("My Collections",
tableName: "Discover",
bundle: #bundle,
comment: "Section title above user-created collections.")

Key points:

  • tableName: "Discover" puts this string into Discover.xcstrings instead of the default Localizable.xcstrings.
  • One String Catalog file is one table. Use tables to group strings by feature, screen, or user flow, so a single file does not get out of hand.

Type-safe symbol generation (17:31)

Xcode 26 supports the reverse flow: add keys (such as TITLE, SUBTITLE) by hand in the String Catalog, and Xcode generates matching static properties or functions on LocalizedStringResource. Entries with placeholders become functions, with placeholder names as argument labels.

// Using symbols in SwiftUI
Text(.introductionTitle)

.navigationSubtitle(.subtitle(friendsPosts: 42))


// Using symbols in Foundation
String(localized: .curatedCollection)


// A custom type that takes LocalizedStringResource
struct CollectionDetailEditingView: View {
    let title: LocalizedStringResource

    init(title: LocalizedStringResource) {
        self.title = title
    }
}
CollectionDetailEditingView(title: .editingTitle)

Key points:

  • Text(.introductionTitle): a key without placeholders becomes a static property. Prefix it with . to call it directly, and the type is inferred as LocalizedStringResource.
  • .subtitle(friendsPosts: 42): a key with placeholders becomes a function. The placeholder name friendsPosts is the argument label, and types are checked at compile time.
  • String(localized: .curatedCollection): Foundation’s String(localized:) also takes a LocalizedStringResource, so the same symbols work in both SwiftUI and non-SwiftUI code.
  • Symbols for the default Localizable table sit directly on LocalizedStringResource. Symbols for a custom table (such as Discover) are nested under the LocalizedStringResource.Discover namespace and need .Discover.xxx to access.
  • New projects have this on by default. For older projects, turn on the build setting “Generate String Catalog Symbols”.

You can switch between the two workflows with “Refactor > Convert Strings to Symbols”, and Xcode previews how each call site will be rewritten (19:37).


Takeaways

  • What to do: turn on “automatically generate string catalog comments” by default in your team’s Xcode Settings.

    • Why it pays off: most rework from translators comes from missing context. The on-device model writes descriptions that cover most everyday strings, and developers only tweak the ones that are off.
    • How to start: Settings → Editing → check the option. When you export XLIFF, confirm that entries carry the auto-generated tag so translation tools know it is a machine first draft.
  • What to do: replace hard-coded Bundle(for:) calls in multi-target projects with the #bundle macro.

    • Why it pays off: #bundle expands at compile time, so it skips runtime reflection lookups by type and removes the boilerplate helper you would otherwise write for each framework.
    • How to start: search for Bundle(for: and Bundle.module across frameworks and Swift Packages, swap each one for bundle: #bundle, and build once to confirm the bundle column in the String Catalog points to the right target.
  • What to do: split a catalog with more than 200 strings into tables, named by feature (such as Discover, Settings, Profile).

    • Why it pays off: a single oversized String Catalog makes Xcode editing sluggish and creates merge conflicts in translation. Splitting tables lets several people edit different features without stepping on each other.
    • How to start: add <Feature>.xcstrings to each feature module, pass tableName: "<Feature>" at the call sites, and pair it with #bundle so frameworks find their own resources.
  • What to do: convert mature features to symbol references with “Refactor > Convert Strings to Symbols”.

    • Why it pays off: symbols decouple keys from values, so copy changes only touch the catalog, not the code. Autocomplete cuts down typos, and placeholder types are checked at compile time.
    • How to start: pilot it in a new feature first, with the build setting “Generate String Catalog Symbols” turned on. Use the preview to confirm each key name (for example, rename an auto-generated feedTitle to something more meaningful) before you commit.

Comments

GitHub Issues · utterances