WWDC Quick Look 💓 By SwiftGGTeam
Localize your SwiftUI app

Localize your SwiftUI app

Watch original video

Highlight

SwiftUI complements localization capabilities in Text, LocalizedStringKey, Markdown, FormatStyle, keyboard shortcuts, and the Xcode 13 export process, allowing developers to leave strings, formats, and layouts to the system for processing, reducing manual corrections in multi-language adaptation.

Core Content

When many applications are localized for the first time, the problem lies in the details.

In the English interface, a button is written asDone, a label is written asIngredients, there seems to be no problem. When it comes to Russian, Arabic or Lithuanian, the text length, semantic context, writing direction, and keyboard layout all change.

In the past, developers often had to check for these changes themselves. Which strings can be exported to translators? WhichIngredientsWhich is the “ingredient” in the recipe? Which is the “ingredient” in the menu? How to display calorie units by region? Can shortcut keys be pressed on non-US keyboards? These problems can easily be delayed until release.

In this session, Apple used the Fruta sample application to demonstrate a new path: SwiftUI defaults toTextThe string literal is treated as a localization key; custom views can useLocalizedStringKeyKeep the same behavior; styles are left to Markdown; values ​​are left to the new formatting API; layout and keyboard shortcuts are left to the system to automatically adapt.

Xcode 13 also changes the export process. When exporting localized content, Xcode can build the target and extract it through the Swift compilerLocalizedStringKey. Multi-line strings and strings in SwiftUI custom views can be entered into the localization catalog more accurately.

As a result, developers don’t need to write a UI for every language. You just expose the translatable content, give the translation enough context, and use the system to format the data, and SwiftUI will handle a lot of the language differences.

Detailed Content

String literals will automatically check the localization table

(01:34) SwiftUITextWhen receiving string literals, localized string lookup will be done in the main bundle. Button titles, labels, and prompt copy can all start here.

import SwiftUI

struct RewardsSheet: View {
    let done: () -> Void

    var body: some View {
        Button(action: done) {
            Text("Done", comment: "Button title to dismiss rewards sheet")
        }
    }
}

Key points:

  • Button(action: done)Use the incomingdoneThe closure closes the reward page. -Text("Done", comment: ...)BundleDoneExposed as a localizable string. -commentTo give context to the translation: This is the title of the button that closes the bonus sheet.
  • At runtime, SwiftUI looks up the corresponding translation from the bundle based on the current language.

(01:58) String interpolation will also be extracted. Xcode 13 infers the format specifier based on the type of the interpolated variable.

import SwiftUI

struct RewardsCard: View {
    let totalStamps: Int

    var body: some View {
        Text("You are \(10 - totalStamps) points away from a free smoothie!")
    }
}

Key points:

  • totalStampsIs the number of points the user has earned. -10 - totalStampsCalculate how far away you are from a free smoothie.
  • Interpolation expressions go into exported localized string files or catalogs.
  • Xcode 13 basedIntType inference format placeholder to reduce manual writing errors%d%@opportunity.

(02:06) The same English word may require different translations in different scenarios.Textcan passtableNamePut the string into a separate table.

import SwiftUI

struct IngredientHeaders: View {
    var body: some View {
        VStack(alignment: .leading) {
            Text(
                "Ingredients.recipe",
                tableName: "Ingredients",
                comment: "Ingredients in a recipe. For languages that have different words for \"Ingredient\" based on semantic context."
            )
            Text(
                "Ingredients.menu",
                tableName: "Ingredients",
                comment: "Ingredients in a smoothie. For languages that have different words for \"Ingredient\" based on semantic context."
            )
        }
    }
}

Key points:

  • Both strings are placed inIngredientsThe table and the inside make it easy to manage the same type of copywriting. -Ingredients.recipeUsed in recipe context. -Ingredients.menuUsed in smoothie menu context. -commentTell the translator explicitly: Some languages ​​choose different words based on semantic context.

Custom SwiftUI view needs to receive LocalizedStringKey

(02:52) The problem often occurs with custom views. you putStringPassed to the component and then handed to it inside the componentText, Xcode may not be able to treat this literal as a SwiftUI localization key.

The solution is to change the parameter type toLocalizedStringKey

import SwiftUI

struct Card: View {
    var title: LocalizedStringKey
    var subtitle: LocalizedStringKey

    var body: some View {
        Circle()
            .fill(BackgroundStyle())
            .overlay(
                VStack(spacing: 16) {
                    Text(title)
                    Text(subtitle)
                }
            )
    }
}

struct OrderCompleteView: View {
    var body: some View {
        Card(
            title: "Thank you for your order!",
            subtitle: "We will notify you when your order is ready."
        )
    }
}

Key points:

  • titleandsubtitleuseLocalizedStringKey, retaining the localized semantics of SwiftUI. -Text(title)andText(subtitle)Read translations from bundle at runtime. -Card(...)The string literal is still passed to the calling site, and the writing method remains unchanged.
  • When Xcode exports localized content, it can be extracted and passed toCardtitle and subtitle.

(03:47) Xcode 13 can use the compiler when exporting localized content. Multi-line strings are also parsed correctly.

import SwiftUI

struct SmoothieDescription: View {
    var body: some View {
        Text("""
        A delicious blend of tropical fruits and blueberries will
        have you sambaing around like you never knew you could!
        """,
        comment: "Tropical Blue smoothie description")
    }
}

Key points:

  • Triple quotes declare multi-line strings, suitable for longer description copy. -TextStill receive localizable content. -commentMark this description as Tropical Blue smoothie.
  • Xcode 13 uses the Swift compiler to extract strings and reduce multi-line text parsing failures.

The layout supports longer text and right-to-left languages ​​by default

(04:12) After localization, the text usually becomes longer. SwiftUI controls will makeTextWrap lines to avoid direct cutting.

(04:26) Right-to-left language also affects layout direction. SwiftUI flips the table cell layout and mirrors tab bar symbols when appropriate.

Developers need to avoid hardcodingleftandright. Apple highlighted in its presentationleadingThis direction has nothing to do with writing.

import SwiftUI

struct SmoothieSummary: View {
    let smoothieTitle: LocalizedStringKey
    let ingredients: LocalizedStringKey

    var body: some View {
        VStack(alignment: .leading) {
            Text(smoothieTitle)
                .font(.headline)
            Text(ingredients)
        }
    }
}

Key points:

  • VStack(alignment: .leading)Use semantic orientation.
  • In left-to-right languages,leadingUsually corresponds to the left side.
  • In right-to-left languages, the system canleadingMap to the other side. -TextResponsible for displaying the localized smoothie name and ingredient text.

Markdown lets translation determine the style placement

(05:13) SwiftUITextLocalized strings with Markdown can be displayed directly. The point of doing this is to put the style and translation in the same string.

import SwiftUI

struct LemonberryDescription: View {
    var body: some View {
        Text(
            "A refreshing blend that's a *real kick*!",
            comment: "Lemonberry smoothie description"
        )
    }
}

Key points:

  • *real kick*Use Markdown emphasis.
  • Strings are still exported as localized content.
  • Translation can adjust emphasized words according to the target language.
  • SwiftUI directly renders styledText, no need to split multiple text fragments.

FormatStyle handles localized values ​​directly

(06:04) Prior to iOS 15, Fruta needed to createMeasurementFormatter, and then convert the calories into a string.

import Foundation
import SwiftUI

struct LegacyCaloriesView: View {
    let nutritionFactKilocalories: Double

    static let measurementFormatter: MeasurementFormatter = {
        let formatter = MeasurementFormatter()
        formatter.unitStyle = .long
        formatter.unitOptions = .providedUnit
        return formatter
    }()

    var body: some View {
        let calories = Measurement<UnitEnergy>(
            value: nutritionFactKilocalories,
            unit: .kilocalories
        )

        return VStack(alignment: .leading) {
            Text(Self.measurementFormatter.string(from: calories))
            Text("Energy: \(calories, formatter: Self.measurementFormatter)")
        }
    }
}

Key points:

  • Measurement<UnitEnergy>Represents an energy value with units. -MeasurementFormatterSet the unit style to.long
  • .providedUnitKeep the incoming.kilocaloriesunit.
  • firstTextFirst convert the measurement value into a string.
  • the secondTextUse formatter in interpolation.

(06:22) iOS 15’s new formatting API can declare formats on placements.

import Foundation
import SwiftUI

struct CaloriesView: View {
    let nutritionFactKilocalories: Double

    var body: some View {
        let calories = Measurement<UnitEnergy>(
            value: nutritionFactKilocalories,
            unit: .kilocalories
        )

        return VStack(alignment: .leading) {
            Text(calories.formatted(.measurement(width: .wide, usage: .food)))
            Text("Energy: \(calories, format: .measurement(width: .wide, usage: .food))")
        }
    }
}

Key points:

  • calories.formatted(...)Directly returns the display text suitable for the current locale. -.measurement(width: .wide, usage: .food)Declare the display width and usage scenarios of measured values. -usage: .foodTell the system this is a food energy scenario. -TextThe interpolation can be directly receivedformat:, the code is closer to UI semantics.

Keyboard shortcuts will be remapped according to the current keyboard layout

(06:53) macOS Monterey and iPadOS 15 will adjust shortcut keys defined in SwiftUI based on the user’s current keyboard layout.

Apple’s example isCommand +. On a US keyboard it’s easy to type; on a Lithuanian keyboard the original combination cannot be typed while holding down Command. The system will remap it into a combination that can be entered.

import SwiftUI

struct SmoothieCommands: Commands {
    let smoothie: Smoothie

    var body: some Commands {
        CommandMenu(Text("Smoothie", comment: "Menu title for smoothie-related actions")) {
            SmoothieFavoriteButton(smoothie)
                .keyboardShortcut("+")
        }
    }
}

struct Smoothie {}

struct SmoothieFavoriteButton: View {
    let smoothie: Smoothie

    init(_ smoothie: Smoothie) {
        self.smoothie = smoothie
    }

    var body: some View {
        Button("Favorite") {}
    }
}

Key points:

  • CommandMenuThe title usesText, you can add translation comments. -SmoothieFavoriteButton(smoothie)It is the action entry in the menu. -.keyboardShortcut("+")Define the shortcut key for adding favorites.
  • The system adjusts the input combinations according to the current keyboard layout, and developers do not need to write branches for each keyboard layout.

Xcode 13’s localization workflow is more compiler dependent

(08:47) Xcode 13 enhances Swift string extraction. Build Setting in the projectUse Compiler to Extract Swift StringsIt is enabled by default for new Swift projects, and can be opened manually for old SwiftUI projects.

import SwiftUI

struct StepperView: View {
    var label: LocalizedStringKey
    @Binding var value: Int

    var body: some View {
        Stepper(value: $value) {
            Text(label)
        }
    }
}

Key points:

  • labeluseLocalizedStringKey, to prevent custom views from swallowing localized information. -@Binding var valueReceive external status. -Stepper(value: $value)Modify the binding value. -Text(label)Display localizable labels next to steppers.

(13:53) The Xcode localization catalog will display key, source string, translation and comment. The translator cannot see variable names, so ambiguous strings must be commented.

import SwiftUI

struct FavoritesTab: View {
    var body: some View {
        Label {
            Text("Favorites", comment: "Tab title. Noun: the list of favorite smoothies.")
        } icon: {
            Image(systemName: "heart")
        }
    }
}

Key points:

  • LabelUsed for tab bar items.
  • for titleTextInitialization is required to providecomment.
  • NotesFavoritesis a noun that refers to a collection list.
  • Translators don’t have to guess whether it’s an action or a column name.

Core Takeaways

  • What to do: Perform a localization type audit on the custom component. Why it’s worth doing: This session makes it clear that custom SwiftUI views should useLocalizedStringKeyor receiveText, otherwise the string may not be exported correctly. How ​​to start: Search in the projectvar title: Stringlet label: String, first change the display parameters toLocalizedStringKey, and then use pseudo-language preview to check.

  • What to do: Supplement translation annotations for high-risk copywriting such as payment, collection, and subscription. Why it’s worth doing: The variable name cannot be seen in the translation in the Xcode catalog.Buy recipe for %@Such strings can be ambiguous. How ​​to start: PutText("Favorites")Change toText("Favorites", comment: "...")LabelNeed to useLabel { Text(...) } icon: { ... }Writing method.

  • What: Migrate numerical display from handwritten strings toFormatStyle. Why it’s worth doing: Calories, length, weight, date have different formats in different regions,Text("%lf Calories")Regional differences will be thrown to the translation. How ​​to start: PutMeasurementFormatterOr string concatenation is replaced byvalue.formatted(...),existTextused in interpolationformat:

  • What to do: Use Markdown to manage emphasis styles in localized text. Why it’s worth doing: Different languages ​​may emphasize different positions. Embed the style in a translatable string, and the translation can adjust the key words. How ​​to start: Put multipleTextSpliced ​​emphasis copy is combined into a Markdown string, e.g.Text("A *real kick*!")

  • WHAT: Add pseudo-language and keyboard layout checks before publishing. Why it’s worth doing: Pseudo-language can expose unextracted strings, and non-US keyboards can expose uninputable shortcut keys. How ​​to start: Select Accented Pseudolanguage in the App Language of the scheme editor; reserved for common commands.keyboardShortcut(...), letting macOS and iPadOS do layout remapping.

Comments

GitHub Issues · utterances