SwiftUI 本地化参考
面向用户的文本指导:Text、Button、Label、导航/工具栏标题、alert 以及携带可本地化字符串的类型。关于单个Text上更窄的"verbatim 与本地化"决策,请参阅references/text-patterns.md。
目录
- SwiftUI 自动本地化字符串字面量
- String Catalogs
- Swift 包和框架的 Bundle
- 本地化变量和自定义类型
- 非视图类型的 LocalizedStringResource
- 插值 vs 拼接
- 大小写
- 格式化日期、数字和货币
- 为本地化设计布局
- 读取当前语言环境
- SwiftUI 视图之外的 String(localized:)
- 给翻译人员的注释
SwiftUI 自动本地化字符串字面量
接受LocalizedStringKey的初始化器(Text、Button、Label、.navigationTitle、alert 标题等)会自动将字符串字面量视为本地化键。不要将字面量包裹在NSLocalizedString、String(localized:)或LocalizedStringResource中——那会急切地解析字符串并忽略\.locale覆盖。
// 避免:双重工作,且急切解析Text(String(localized:"start_workout"))// 优先:直接传递字面量Text("start_workout")不透明的键("start_workout")和自然语言字符串("Start Workout")都可以作为键——选择项目已经使用的约定。只有当你想让字面量不参与本地化时才使用Text(verbatim:)(例如插值运行时值的调试标签)。当参数已经是String变量时,Text(value)会调用StringProtocol重载并自行跳过本地化。
String Catalogs
大多数项目通过 String Catalogs(.xcstrings)进行本地化。每次构建会将代码中的新键同步到 catalog 中,但 catalog 文件必须已经存在——Xcode 不会自动创建。如果项目已经使用.strings/.stringsdict,请在那里添加新字符串,而不是迁移。使用tableName:将字符串组路由到特定 catalog。
Text("Explore",tableName:"Navigation",comment:"Tab bar item title for the Explore screen.")Swift 包和框架的 Bundle
应用、应用扩展和 XPC 服务是它们自己的主 bundle,因此可以省略bundle。框架和 Swift 包需要显式的bundle:——没有它,SwiftUI 会在Bundle.main中查找字符串,查找会静默失败,字符串在运行时以未本地化形式出现。
// 避免(在框架/包内):搜索应用的 catalogText("Save to Favorites")// 优先:#bundle 解析到当前目标的 bundleText("Save to Favorites",bundle:#bundle,comment:"Button to bookmark a recipe.")#bundle是首选形式;Bundle.module和Bundle(for:)仍然有效,但属于较老的模式。
本地化变量和自定义类型
传递给Text的String变量会运行StringProtocol重载,并且不会被本地化。将其包裹在LocalizedStringKey(_:)中也没有帮助——Xcode 无法从运行时值提取字面量,因此不会进入 catalog。要本地化从已知集合中选择的值,请用暴露LocalizedStringResource的类型对其进行建模:
enumCategory{caseappetizers,mains,dessertsvarname:LocalizedStringResource{switchself{case.appetizers:"Appetizers"case.mains:"Mains"case.desserts:"Desserts"}}}Text(category.name)当视图或模型暴露面向用户的文本时,将属性类型设为LocalizedStringKey或LocalizedStringResource而不是String。每个接受本地化文本的 SwiftUI 视图都接受两者,因此在显示处延迟解析不花费任何成本,并保留 locale/bundle 上下文。
非视图类型的 LocalizedStringResource
当非视图类型携带面向用户的文本时——模型对象、提示、排队通知——使用LocalizedStringResource而不是String。它将解析延迟到显示时间,因此会遵循值实际渲染时生效的 locale,而不是创建时的 locale。
// 避免:在创建时解析,无法在另一种 locale 中重新渲染structTip{letheadline:String}lettip=Tip(headline:String(localized:"Tip of the Day"))// 优先:解析延迟到显示时间structTip{letheadline:LocalizedStringResource}lettip=Tip(headline:"Tip of the Day")在设计新类型或更改面向用户的文本时应用此做法——不要作为无关编辑的一部分扫荡现有的String属性。
插值 vs 拼接
字符串插值保留LocalizedStringKey并在 catalog 中生成格式字符串(例如"Welcome, %@")。使用+拼接会产生普通String,不会被本地化。绝不要将分别本地化的片段拼成句子——词序在不同语言中各不相同。
// 避免:+ 产生 String;句子组装破坏词序Text("Error: "+statusMessage)Text(String(localized:"Created by"))+Text(" ")+Text(authorName)// 优先:一个可让翻译人员重新排列的插值字符串Text("Error:\(statusMessage)")Text("Created by\(authorName)")大小写
将所需的大小写烧录到字符串中,而不是在运行时通过.textCase(_:)、.localizedUppercase或.localizedCapitalized转换。运行时转换会强制每种翻译都使用相同的大小写,让翻译人员没有余地按语言调整。
// 避免Text("Section Header").textCase(.uppercase)// 优先Text("SECTION HEADER")这适用于本地化字符串;用户输入的文本应原样显示。如果转换不可避免,优先使用.localizedUppercase/.localizedCapitalized,它们会遵循用户的 locale。
格式化日期、数字和货币
使用Text的format:参数或.formatted(),而不是带硬编码格式字符串的DateFormatter/NumberFormatter。格式样式会适应用户的 locale;硬编码的格式字符串不会。
// 避免letf=DateFormatter();f.dateFormat="MM/dd/yyyy"Text(f.string(from:workout.date))Text("$\(product.price,specifier:"%.2f")")// 优先Text(workout.date,format:.dateTime.month().day().year())Text(product.price,format:.currency(code:store.currencyCode))字段组件(.month()、.day())选择显示哪些字段;顺序由 locale 决定。对于列表,Array.formatted()会插入 locale 正确的分隔符和连接词,而不是joined(separator:)。当DateFormatter确实不可避免时,使用setLocalizedDateFormatFromTemplate(_:)而不是直接赋值dateFormat。
为本地化设计布局
- 使用
.leading/.trailing而不是.left/.right——它们会为从右到左的 locale 翻转。 - 不要为文本硬编码 frame 宽度/高度;翻译长度不一,脚本高度不一。当布局可能放不下更长的翻译时,使用
ViewThatFits。 - 使用文本样式(
.body、.headline)而不是固定的点大小,以便行高适应每种脚本。
// 优先Text(recipe.title).frame(maxWidth:.infinity,alignment:.leading)ViewThatFits{HStack{actionButtons}VStack{actionButtons}}读取当前语言环境
在视图中使用@Environment(\.locale)进行依赖 locale 的逻辑,而不是Locale.current——环境值会尊重预览覆盖和逐视图注入。
SwiftUI 视图之外的 String(localized:)
当你在视图之外需要本地化的String时,使用String(localized:),而不是NSLocalizedString。不要在NSLocalizedString内部插值——Xcode 在构建时从字面量提取键,无法提取插值。String(localized:)支持插值(它提取格式字符串并将值视为运行时参数),并且优于String(format:)——后者无论 locale 如何总是将数字渲染为 0–9。
// 优先lettitle=String(localized:"activity_summary",comment:"Dashboard header")给翻译人员的注释
添加描述 UI 元素及其用途的comment:,特别是对模棱两可的字符串。对于插值字符串,按位置描述每个占位符——翻译人员看不到 Swift 变量名。注释可以放在调用处,也可以放在 String Catalog 中每个字符串的 Comment 字段中;每个字符串保持单一信息来源。
// 避免:"Edit" 可能是名词或动词Text("Edit")// 优先Text("Edit",comment:"Toolbar button that enters editing mode for the list.")Text("Completed\(count)of\(total)",comment:"Progress label — first variable is finished items, second is the total.")汇总清单
- 字符串字面量直接传递给
Text/Button/Label(不包裹在NSLocalizedString/String(localized:)中) - 框架和 Swift 包内面向用户的字符串使用
bundle: #bundle - 模型/非视图类型上面向用户的文本类型为
LocalizedStringResource,而不是String - 动态字符串使用插值(而不是
+);不从片段组装句子 - 大小写烧录到字符串中,而不是通过
.textCase应用 - 日期/数字/货币使用带 locale 感知样式的
format:/.formatted() - 使用
.leading/.trailing(而不是.left/.right);不硬编码文本 frame 尺寸 - 视图中的 locale 逻辑使用
@Environment(\.locale) - 为模棱两可的字符串和插值占位符提供
comment: