国际化

String Catalog、运行时语言切换与新增语言。

应用提供英文简体中文,并且能实时切换语言,无需重启——这是原生 SwiftUI 本地化无法免费给你的能力。本页讲清作为唯一事实来源的 String Catalog、让会话内切换得以 生效的 LocaleController、你必须遵守的那一条调用点规则,以及如何新增一种语言。

唯一事实来源:String Catalog

每一条面向用户的字符串都放在 Localizable.xcstrings(一个 String Catalog)中, sourceLanguage"en",每个键都有对应的 zh-Hans 译文。键采用点号分隔、按功能 命名空间:

auth.alreadyHaveAccount
todos.filterActive
tabs.home
profile.system

Xcode 在构建时将目录编译成各语言的 .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 加一个 caseCore/Locale/AppLanguage.swift)并补全全部四个 switch——resourceCode 必须与 Xcode 生成的 .lproj 目录名完全一致("es"),此外还有 localedisplayNameid

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 键)中。.systemnil 并遵循设备语言。语言与主题并列存放于 AppPreferences, Me 标签页同时暴露这两个选择器。

相关页面

On this page