对于拥有框架所有面板的 Mac App,在两个层面定义回退:每个 bundle 选择哪种语言,以及当消息查找失败时每条消息返回什么。对你拥有的消息使用英语开发本地化和显式英语默认值。

本指南为一个简单的桌面窗口实现该契约,该窗口包含 App 所有的标签 Workspace 和框架所有的标签 Panel ready。这些标签是合成的。任务是在语言或翻译缺失时保持这些消息可读,同时在翻译存在时保留正常本地化。

回退契约应承诺什么?

为 App 所有和框架所有的消息采用此契约:使用通过系统语言匹配选取的可用本地化;当无首选本地化匹配时使用英语作为开发本地化;如果字符串查找失败则返回消息的显式英语默认值。每条消息必须标识其所属 bundle 和字符串表。

语言选择考虑用户的首选语言和可用本地化。缺失首选语言不一定意味着立即出现英语:另一首选语言可能先匹配。Apple 的已归档 bundle 指南描述了该选择序列,随后是在无首选语言匹配时使用开发本地化。当前文档将 CFBundleDevelopmentRegion 定义为 bundle 的回退语言标识符。访问 Bundle 的内容,CFBundleDevelopmentRegion。

定义预期结果时使用下面的决策表。它将资源选择与消息查找分开,而不是将每个缺失翻译视为同一条件。

条件 你拥有的消息的契约 要检查的内容
某首选语言有可用翻译 显示该翻译 Bundle、表和键
首选语言不可用,但另一首选语言匹配 允许系统语言匹配选择可用本地化 偏好顺序和已交付资源
无首选本地化匹配 使用 bundle 的英语开发资源 开发语言和英语资源
选定的查找无法解析消息 返回其显式英语默认值 消息的 defaultValue
框架查找意外指向 App bundle 修正 bundle 参数 资源所有权
翻译存在但包含不合适文本 修正翻译 目录内容和审查

该表将 Apple 的资源选择规则与 Swift 初始化器文档化的默认值行为结合。它是源自那些 API 的实现策略,而非 String Catalogs 中单独的回退功能。包含不合适文本的成功查找不满足初始化器文档中使用默认值的条件:查找失败。String.init(localized:defaultValue:table:bundle:locale:comment:)。

在每个拥有目标中配置英语源资源

本例使用当前 Xcode 安装、面向 macOS 13 或更高版本的 macOS SwiftUI App,以及一个名为 PanelKit 的嵌入式动态 Swift 框架目标。在 App 中链接并嵌入该框架。这些是示例项目先决条件;框架名称不标识现有产品。

将英语设为两个目标的开发语言,并验证每个构建产物 bundle 的 CFBundleDevelopmentRegion 解析为 en。在两个目标中提供相应的英语源资源。开发语言标识符描述回退语言;它不提供缺失消息内容。Apple 将 en 记录为英语语言标识符。CFBundleDevelopmentRegion。

为每个目标创建单独的 Localizable.xcstrings 目录。在 Xcode 中选择 File > New > File from Template,选择 String Catalog,命名为 Localizable。将一个目录分配给 App 目标,另一个分配给框架目标。分离资源所有权使得能够独立验证每个查找。Apple 记录了目录创建工作流并支持多个目录。使用字符串目录本地化和变体文本。

添加下面的代码后,选择 Product > Build。Xcode 将识别的可本地化调用提取到目录中。确认 workspace.title 出现在 App 目录中,panel.ready 出现在框架目录中,并带有预期的英语源值。使用目录侧边栏的添加按钮添加支持的语言,并为相关消息输入翻译。使用字符串目录本地化和变体文本。

目录完整性和运行时查找正确性需要单独检查。框架目录中的已翻译条目无法帮助搜索 App bundle 的调用。Apple 明确指示加载主 bundle 之外字符串的代码传递与该目标关联的 bundle。准备 App 文本以供翻译。

为每条消息指定 bundle、键、表和默认值

使用稳定的合成键和可读的英语默认值。接受 defaultValue 的 Swift 初始化器支持与显示源文本不同的键。当从 bundle 查找本地化字符串失败时返回其默认值。table 参数选择字符串表;省略它则选择 Localizable。String.init(localized:defaultValue:table:bundle:locale:comment:)。

将框架消息放在框架中

将此完整 Swift 源文件添加到动态 PanelKit 框架目标。标记类在该目标中定义,因此 Bundle(for:) 标识其 bundle。Apple 将这种基于类的方法记录为当前 #bundle 宏的替代方案。准备 App 文本以供翻译。

import Foundation

private final class PanelBundleAnchor: NSObject {}

public enum PanelText {
    public static var resourceBundle: Bundle {
        Bundle(for: PanelBundleAnchor.self)
    }

    public static var ready: String {
        String(
            localized: "panel.ready",
            defaultValue: "Panel ready",
            table: "Localizable",
            bundle: resourceBundle,
            comment: "Status label above the framework panel."
        )
    }
}

框架在向 App 返回 String 前解析其消息。这建立了清晰的所有权边界:框架控制键、源文本、表和资源 bundle。App 接收生成的显示文本。

同时显示 App 和框架消息

用此完整示例替换生成的 App 入口点文件。它假设链接的框架模块名为 PanelKit 且包含前面的文件。在 App 目标中仅保留一个 App 入口点。

import Foundation
import SwiftUI
import PanelKit

@main
struct FallbackDemoApp: App {
    var body: some Scene {
        WindowGroup {
            VStack(alignment: .leading, spacing: 12) {
                Text(verbatim: String(
                    localized: "workspace.title",
                    defaultValue: "Workspace",
                    table: "Localizable",
                    bundle: .main,
                    comment: "Heading for the main workspace."
                ))

                Text(verbatim: PanelText.ready)
            }
            .padding(24)
        }
    }
}

App 查找显式使用 .main;框架查找使用其自己的资源 bundle。两者都提供英语默认值。这遵循 Apple 文档化的 bundle 选择和默认值参数。在 Xcode 中构建并运行项目以验证完整目标配置;此处未执行这些示例。String.init(localized:defaultValue:table:bundle:locale:comment:)。

对于使用 Bundle.localizedString(forKey:value:table:) 的现有实现,当你需要可读的缺失键结果时,提供非空可读的 value。Apple 记录:缺失键在 value 缺失或为空时返回键本身,在非空时返回 value。因此像 panel.ready 这样的键若暴露该标识符会违反消息契约,则需要显式回退。localizedString(forKey:value:table:)。

决定框架是否可选择不同语言

传递正确的 bundle 回答了在哪里搜索。混合本地化配置影响本地化选择如何与容器 App 关联。Apple 暴露布尔值 CFBundleAllowMixedLocalizations,显示为 Localized resources can be mixed,用于从框架检索本地化字符串。CFBundleAllowMixedLocalizations。

Swift 论坛对此行为的解释称:在 App 的 Info.plist 中启用该键允许其他 bundle 选择主 App 不支持的本地化。若无该选择加入,主 bundle 会约束它们的选择。这是一篇较早的解释性讨论,可配合 Apple 当前的键参考一并参考。Swift 包管理器本地化。

对于示例窗口,建议 App 和框架在支持的语言覆盖上保持一致,两者都有英语开发资源。将独立框架语言选择排除在初始契约之外。如果你的预期行为是让框架使用额外翻译,将 App 的 CFBundleAllowMixedLocalizations 布尔值设为 YES 并显式测试该配置。Swift 包管理器本地化。

启用此键不提供翻译或缺失消息默认值。这些责任仍属于资源文件和查找调用。同样,提供给你自己的调用的默认值仅定义该调用的查找失败结果;它不为每个系统控件或框架消息建立通用的英语回退。

避免将此 String 初始化器的 locale 参数用作翻译语言开关。Apple 明确记录它控制插值值(如数字)的本地化,并不更改用于查找本地化字符串的 locale。请使用支持的语言配置和测试控件完成此任务。String.init(localized:defaultValue:table:bundle:locale:comment:)。

将缺失语言测试与缺失消息测试分开

通过 Product > Scheme > Edit Scheme 运行本地化检查。选择 Run,打开 Options,选择 App Language,关闭对话框,再次运行 Mac App。Apple 记录了此工作流,用于在不更改整个系统设置的情况下练习本地化。调查纯语言问题时保持区域选择不变。运行 App 时测试本地化。

将以下测试矩阵作为项目特定的诊断辅助。对于 bundle 路由检查,临时在受支持的非源本地化中使用不同的英语测试标记,如 App translation sample 和 Framework translation sample。这些是诊断内容,非翻译或语言标识符。

  1. 验证源行为。 选择英语运行。期望 Workspace 和 Panel ready。
  2. 验证翻译资源路由。 选择受支持的测试本地化。期望来自两个拥有目录的不同标记。
  3. 验证缺失消息默认值。 在一次性测试编辑中,将框架查找键改为 panel.ready.probe,保持 defaultValue 不变,并阻止该探测条目被包含在测试目录中。查找失败时期望 Panel ready。之后恢复原键。
  4. 验证不支持语言选择。 使用无支持语言匹配的偏好列表进行练习。检查两个拥有 bundle 均使用其英语开发资源。同时测试不支持的首选语言后跟支持的偏好,因为该情况有不同预期结果。
  5. 验证启用混合本地化时的行为。 练习仅在框架中可用的语言。记录预期行为并针对 App 的混合本地化设置进行测试。

第三项检查隔离失败查找;第四项隔离语言匹配。仅测试未翻译的目录行会模糊这些条件,因为生成的资源配置也会影响查找。预期的缺失键结果来自 Swift 初始化器,而首选语言选择遵循 bundle 规则。String.init(localized:defaultValue:table:bundle:locale:comment:),访问 Bundle 的内容。

结果不同时,检查实际拥有 bundle。developmentLocalization 报告对应 CFBundleDevelopmentRegion 的值;preferredLocalizations 按语言偏好顺序报告可用本地化。在 Bundle.main 和 PanelText.resourceBundle 上比较这些属性。它们有助于诊断配置,但显示的消息仍是每个查找的最终检验。developmentLocalization,preferredLocalizations。

一个有用的回退契约使每个结果可解释:选定语言来自 bundle 匹配,消息来自其拥有表,查找失败返回显式可读默认值。将这些规则应用到你拥有的每个 App 和框架消息,然后在用户遇到它们的桌面窗口中一起验证。

来源