对于可复用的 macOS 标签,请使用 LocalizedStringResource 接收可本地化消息,为字面文本保留单独的 verbatim: 路径,并在本地化完成前保留插值。本教程为桌面项目计数器构建这套契约,并分别检查提取和显示。
可复用的文本 API 应作出什么承诺?
假设某个 Mac 应用需要一个简洁摘要,用来显示项目数量和一个虚构的集合标签,例如 Sample Set。可复用组件必须区分三种输入:需要翻译的应用编写消息、现有的 SwiftUI 本地化键,以及必须按原样显示的文本。
Apple 建议使用 LocalizedStringResource 来表示可本地化字符串,并通过自定义视图和其他 Swift 代码传递它们。Apple 当前的指南展示了如何在自定义视图中声明这种类型的属性,并通过 Text 显示。设计新组件时,应将它作为主要消息契约。准备应用文本以供翻译。
下表是根据这些已记录行为推导出的 API 设计。明确的参数标签让调用方的意图一目了然,无需依赖两个相互竞争且没有标签的字符串重载。
| 调用方的意图 | 建议的入口 | 渲染路径 |
|---|---|---|
| 传入可带有可选上下文的可翻译消息 | SummaryLabel(localized:) |
Text(resource) |
| 传入 SwiftUI 键,包括 SwiftUI 插值 | SummaryLabel(key:) |
Text(key, tableName:bundle:) |
| 显示精确标签 | SummaryLabel(verbatim:) |
Text(verbatim:) |
| 提供调用方已组合好的文本 | SummaryLabel(text:) |
存储传入的 Text |
当组件确实需要 SwiftUI 基于键的行为时,应保留 LocalizedStringKey。Apple 说明,直接传给 Text 的字符串字面量会创建本地化键,而 String 变量通常会走绕过本地化的路径。因此,只接受 String 的包装器会改变那些看起来相似的调用所表达的含义。LocalizedStringKey。
使用明确的入口构建完整组件
在 Xcode 26 或更高版本中使用 macOS SwiftUI 应用项目,并将本示例的部署目标设为 macOS 26。用下面的完整程序替换自动生成的应用和内容视图声明;目标中只保留一个 @main 声明。所有标签和计数都是虚构数据。代码已根据文档中的 API 进行检查,但由于此环境没有 Swift 编译器,因此未在此处编译。
import Foundation
import SwiftUI
struct SummaryLabel: View {
private let text: Text
init(localized resource: LocalizedStringResource) {
text = Text(resource)
}
init(
key: LocalizedStringKey,
tableName: String? = nil,
bundle: Bundle? = nil,
comment: StaticString? = nil
) {
text = Text(
key,
tableName: tableName,
bundle: bundle,
comment: comment
)
}
init(verbatim value: String) {
text = Text(verbatim: value)
}
init(text: Text) {
self.text = text
}
var body: some View {
text
.font(.callout)
.padding(8)
}
}
struct ContentView: View {
@State private var itemCount = 2
private let collectionLabel = "Sample Set"
var body: some View {
VStack(alignment: .leading, spacing: 12) {
Stepper(value: $itemCount, in: 0...12) {
Text("Item count")
}
SummaryLabel(
localized: LocalizedStringResource(
"summary.item-count",
defaultValue: "\(itemCount) items",
comment: "Summary below the item-count control. The integer is the number of items."
)
)
SummaryLabel(
key: "Collection: \(collectionLabel)"
)
SummaryLabel(verbatim: collectionLabel)
SummaryLabel(
text: Text(
"Preview",
comment: "Heading for a preview of the current item selection."
).bold()
)
}
.padding(20)
.frame(minWidth: 320)
}
}
@main
struct TextContractDemoApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
资源初始化器特意将稳定键 summary.item-count 与默认消息 \(itemCount) items 分开。Apple 说明,这个初始化器支持静态键、可本地化默认值、表、区域设置、包以及译者注释。当指定表中没有该键时,将使用默认值。LocalizedStringResource 初始化器。
verbatim: 初始化器作出同样明确的承诺:按原样显示传入字符串,不进行本地化。它既适用于字面量参数,也适用于变量。当虚构集合标签的准确拼写就是要显示的内容时,应使用此路径。Text.init(verbatim:)。
text: 入口允许调用方先构造本地化文本或设置样式,再将其传入。此组件会添加统一的排版和间距;消息语义仍由调用方决定。传入 Text 是一种逃生通道,因此,如果组件必须强制执行更严格的契约,可以省略此初始化器。
提取与运行时格式化有何不同?
将本地化视为两个独立的检查步骤。构建时,Xcode 会发现可本地化的源代码表达式并更新字符串目录。运行时,应用会解析消息并替换其中的参数。Apple 的字符串目录课程明确说明,Xcode 可以识别自定义视图调用位置中用于实例化 LocalizedStringResource 的字面量。课程还指出了 Use Compiler to Extract Swift Strings 构建设置。发现字符串目录。
插值必须先传递给支持本地化的类型,然后才能成为普通的完整字符串。Apple 解释说,SwiftUI 的插值构造器会将格式键和参数值分开,从而允许先进行本地化,再替换参数。将已插值的 String 转成键,无法还原原先带类型的参数。Swift 新特性。
下面的诊断代码片段展示了这种区别;它不是另一个应用。片段中使用的所有值都在本地定义。
import SwiftUI
let count = 2
// Ordinary String interpolation has already produced "2 items".
let completed: String = "\(count) items"
let exactDisplay = Text(completed)
// Looks up the completed value as a key.
// This does not restore a count placeholder.
let completedValueLookup = Text(LocalizedStringKey(completed))
// Preserves localization-aware interpolation from the beginning.
let messageKey: LocalizedStringKey = "\(count) items"
let interpolatedDisplay = Text(messageKey)
从变量创建 LocalizedStringKey 是一种有文档说明的请求运行时查找的方式。如果变量有意包含一个已知键,这种方式很有用。但它无法保证 Xcode 已将该变量的所有可能值都提取到目录中。应显式维护有意使用的动态键,或者为应用拥有的有限消息使用字面量资源声明。查找行为和回退方式见 Text.init(_:tableName:bundle:comment:)。
应将完整消息及其标点和占位符位置保留在同一个可本地化表达式中。本示例中,应将 Collection: \(collectionLabel) 作为整体保留。若通过分别解析多个片段来拼接完整消息,调用方就必须负责排列这些片段并处理空格。
添加目录并配置复数形式
首先,通过 File > New > File from Template 添加字符串目录,选择 String Catalog,并将其命名为 Localizable。然后选择 Product > Build。Apple 说明,构建会从可本地化 API 填充目录,并在源消息发生变化时更新目录。使用字符串目录本地化和调整文本。
在本示例中,检查 summary.item-count 条目及其中插入的整数。按住 Control 键并点按该条目,然后选择 Vary by Plural。在英语源语言本地化中,将 One 设为 %lld item,将 Other 设为 %lld items,并保留 Xcode 提供的整数占位符。添加其他本地化语言时,也要添加所需的形式;复数类别因语言而异。使用字符串目录本地化和调整文本。
插值会提供数量,但本身不会编写单数措辞。Apple 的跟做课程演示了如何在目录中添加复数变体,以及系统如何在运行时选择匹配的变体。因此,构建成功并提取出消息只是验证工作的一部分。跟做课程:使用 Xcode 探索本地化。
配置源语言的复数形式后,请使用这个简短的验收矩阵。以下是此示例程序的预期结果,并非实际运行结果。
| 检查项 | 预期结果 |
|---|---|
构建并检查 summary.item-count |
一个带整数替换项的消息条目 |
将计数设为 0 |
0 items |
将计数设为 1 |
1 item |
将计数设为 2 |
2 items |
| 检查精确标签行 | Sample Set 保持为字面文本 |
| 检查集合消息 | 标签作为参数包含在完整消息中 |
分别诊断缺失条目和显示错误
如果目录中缺少消息,先检查它的源代码类型。String 参数可能会在消息到达组件之前抹去可本地化表达式。请检查目录所属的目标,构建包含该调用位置的方案,并确认已启用 Use Compiler to Extract Swift Strings。Apple 说明,提取针对当前方案和平台进行,因此应检查实际编译该消息的配置。发现字符串目录。
如果条目存在,但应用显示的是键或回退文本,请检查查找配置。基于键的 Text 初始化器会搜索指定的表和包;省略这些值时,会使用默认表和主包。若键或表不存在,就会显示键的字符串表示形式。因此,可读的回退文本可能会掩盖查找配置不完整的问题。Text.init(_:tableName:bundle:comment:)。
将组件移入框架或包时,应决定消息归属。组件自有的消息需要使用组件的资源包;调用方提供的资源应保留其指定的查找信息。Apple 当前的指南支持在本地化 API 中传入 #bundle,以指定包含该资源的目标包。命名表也必须有匹配的目录。准备应用文本以供翻译。
最后,不要假设 SwiftUI 接受的所有插值都适用于 Foundation 资源。LocalizedStringKey 有自己的插值方法,包括格式样式和格式器变体。组合表达式前应先选择消息类型;如果某个参数无法通过编译,请查阅该类型支持的插值操作。LocalizedStringKey.StringInterpolation。
当调用方能清晰表达意图,并能在两个阶段进行验证时,可复用文本组件就准备就绪了:构建会生成预期的目录消息,而运行中的 Mac 应用会显示正确的替换内容、复数形式或精确标签。每次更改组件时,都应一并检查这些内容和 API 契约。
来源
- 准备应用文本以供翻译 — Apple
- LocalizedStringKey — Apple
- init(_:defaultValue:table:locale:bundle:comment:) — Apple
- init(verbatim:) — Apple
- 发现字符串目录 — Apple
- Swift 新特性 — Apple
- init(_:tableName:bundle:comment:) — Apple
- 使用字符串目录本地化和调整文本 — Apple
- 跟做课程:使用 Xcode 探索本地化 — Apple
- LocalizedStringKey.StringInterpolation — Apple