国际化
String Catalog、运行时语言切换与新增语言。
应用提供英文与简体中文,并且能实时切换语言,无需重启——这是原生 SwiftUI
本地化无法免费给你的能力。本页讲清作为唯一事实来源的 String Catalog、让会话内切换得以
生效的 LocaleController、你必须遵守的那一条调用点规则,以及如何新增一种语言。
唯一事实来源:String Catalog
每一条面向用户的字符串都放在 Localizable.xcstrings(一个 String Catalog)中,
sourceLanguage 为 "en",每个键都有对应的 zh-Hans 译文。键采用点号分隔、按功能
命名空间:
auth.alreadyHaveAccount
todos.filterActive
tabs.home
profile.systemXcode 在构建时将目录编译成各语言的 .lproj 包。要新增一个键,在 Xcode 的 String
Catalog 编辑器(或原始 JSON)中编辑,并补全两种语言。
别在功能代码里散落自由书写的 String(localized:) 字面量。 先把键加进
Localizable.xcstrings,再通过 locale.t("your.key") 读取。未入目录的字面量既得不到
译文,也不会响应应用内的语言切换。
实时切换:LocaleController
语言能瞬间切换,靠的是 LocaleController——一个 @Observable,持有所选语言并在读取时
解析出正确的资源包:
@Observable
final class LocaleController {
var selectedLanguage: AppLanguage // .system | .english | .chineseSimplified
var effectiveLocale: Locale { selectedLanguage.locale }
var localizedBundle: Bundle {
guard
let code = selectedLanguage.resourceCode, // "en" | "zh-Hans"
let path = Bundle.main.path(forResource: code, ofType: "lproj"),
let bundle = Bundle(path: path)
else { return .main }
return bundle
}
}由于该控制器是 @Observable,任何调用它的视图 body 都会在 selectedLanguage 变化时
重新渲染。t(_:) 辅助方法把每次查找都路由到当前所选的包与语言:
extension LocaleController {
func t(_ key: String.LocalizationValue) -> String {
String(localized: key, bundle: localizedBundle, locale: effectiveLocale)
}
}唯一规则:始终走 locale.t(...)
// ✅ 会响应应用内语言切换
Text(locale.t("tabs.home"))
// ❌ 绑定到 Bundle.main + 启动时的语言——不会实时切换
Text("tabs.home")裸的 LocalizedStringKey 形式(Text("tabs.home"))会针对 Bundle.main 和启动时
固定的语言解析,因此会忽略会话内的变更。从环境中读取 LocaleController 并走 t(...):
@Environment(LocaleController.self) private var locale
// …
Text(locale.t("todos.filterActive"))对于从环境读取语言而非字符串键的 SwiftUI 界面——Date.FormatStyle、数字格式化、
Text(_, format:)——RootView 挂载了 .environment(\.locale, locale.effectiveLocale),
因此它们会自动遵循覆盖设置,无需逐调用点传参。
AppleLanguages 兜底
当你选择非系统语言时,LocaleController 也会写入
UserDefaults["AppleLanguages"]。这是双保险,并非实时机制:它只在下次冷启动时
生效,仅用于 SwiftUI 无法通过包触及的系统渲染界面——键盘候选、分享面板、某些系统弹窗。
应用内 UI 通过上面的包立即切换;这些"漏网之鱼"会在下次启动时跟上。
新增一种语言
把语言加进目录。 在 Xcode 中选中 Localizable.xcstrings → 语言列表里的 + →
例如 西班牙语(es)。翻译每个键(未翻译的键会回退到源语言)。
给 AppLanguage 加一个 case(Core/Locale/AppLanguage.swift)并补全全部四个
switch——resourceCode 必须与 Xcode 生成的 .lproj 目录名完全一致("es"),此外还有
locale、displayName 和 id:
case spanish
// resourceCode → "es"
// locale → Locale(identifier: "es")
// displayName → "Español"构建并验证。 新语言会出现在选择器中(由 AppLanguage.allCases 驱动)。选中它,
确认 UI 就地重新渲染——无需重启——并确认 Date/数字格式化随之变化。
resourceCode 是微妙之处:它必须与 Xcode 为该翻译生成的确切文件夹名一致
(zh-Hans,而非 zh)。若 localizedBundle 找不到该 .lproj,它会静默回退到
Bundle.main,语言便不会实时切换。
语言存在哪里
选择持久化在 AppPreferences.languageTag(一个 UserDefaults 键)中。.system 存
nil 并遵循设备语言。语言与主题并列存放于 AppPreferences,
Me 标签页同时暴露这两个选择器。
