Fix historical Apple default note migration
8 files modified
1 files added
| New file |
| | |
| | | # 构建发布与 QA |
| | | |
| | | - 整理日期:2026-06-11 |
| | | - 复核日期:2026-06-15 |
| | | - 依据产品版本:TagLauncher 7.9.1,Build 20260614.1454 |
| | | - 版本依据:`src/Apptag/Info.plist`、`src/CHANGELOG.md`、`src/Release/AppStore-7.9.1-20260614.1454/QA_RELEASE_EVIDENCE.md` |
| | | - 配套图示:[07-构建发布与QA.drawio](./07-构建发布与QA.drawio) |
| | | |
| | | ## 构建方式 |
| | | |
| | | 项目没有 Xcode 工程构建入口,`src/build.sh` 是主构建脚本。它执行: |
| | | |
| | | 1. 读取 `src/Apptag/Info.plist` 的 `CFBundleShortVersionString`。 |
| | | 2. 使用 `APP_BUILD` 环境变量或当前时间生成 build 号,格式必须是 `YYYYMMDD.HHMM`。 |
| | | 3. 清理并重建 `src/build/TagLauncher.app` bundle 结构。 |
| | | 4. 调用 `swiftc` 编译全部 Swift 文件: |
| | | - frameworks:AppKit、SwiftUI、Carbon、CoreServices |
| | | - library:compression |
| | | - target:`arm64-apple-macosx15.0` |
| | | - optimization:`-Osize` |
| | | 5. 拷贝 Info.plist,并只修改 app bundle 内的 `CFBundleVersion`。 |
| | | 6. 从 asset catalog 生成 `AppIcon.icns`。 |
| | | 7. 拷贝菜单栏 SVG、本地化 JSON、Apple 默认应用资源、Smart Start 资源。 |
| | | 8. 将多语言 catalog 压缩为 `.json.deflate`。 |
| | | 9. 拦截禁止进入 bundle 的开发/源资源。 |
| | | 10. 写入 `PkgInfo`、设置 executable、strip debug symbols、清 xattrs。 |
| | | 11. 按 `APP_STORE` 和 `CODESIGN_IDENTITY` 决定签名方式。 |
| | | 12. 校验源码 `Info.plist` hash 未被构建过程修改。 |
| | | |
| | | ## 资源打包边界 |
| | | |
| | | 运行时资源来自源码树下的 Research,但进入 app bundle 后名称有调整: |
| | | |
| | | | 源资源 | Bundle 资源 | |
| | | | --- | --- | |
| | | | `Research/AppleDefaultApps/AppleDefaultApps.base.json` | `AppleDefaultApps.base.json` | |
| | | | `Research/AppleDefaultApps/AppleDefaultApps.manifest.json` | `AppleDefaultApps.manifest.json` | |
| | | | `Research/AppleDefaultApps/AppleDefaultApps.localizations.<lang>.json` | `AppleDefaultApps.localizations.<lang>.json.deflate` | |
| | | | `Research/SmartStart/UltimateDefaultCatalog/SmartStart_UltimateDefaultCatalog.base.json` | `SmartStartUltimateDefaultCatalog.base.json` | |
| | | | `Research/SmartStart/UltimateDefaultCatalog/SmartStart_UltimateDefaultCatalog.manifest.json` | `SmartStartUltimateDefaultCatalog.manifest.json` | |
| | | | `Research/SmartStart/UltimateDefaultCatalog/SmartStart_UltimateDefaultCatalog.notes.<lang>.json` | `SmartStartUltimateDefaultCatalog.notes.<lang>.json.deflate` | |
| | | | `Apptag/Localization/*.json` | `Localization/*.json` | |
| | | |
| | | `build.sh` 禁止把以下源/临时资源直接放入 bundle: |
| | | |
| | | - `*TranslationCache*` |
| | | - `*invalid*` |
| | | - 未压缩的 `AppleDefaultApps.localizations.*.json` |
| | | - 旧单体 `SmartStartUltimateDefaultCatalog.json` |
| | | - SmartStart CSV |
| | | - 未压缩的 notes JSON |
| | | |
| | | ## DMG 构建 |
| | | |
| | | `src/make_dmg.sh` 依赖已存在的 `src/build/TagLauncher.app`: |
| | | |
| | | 1. 创建 `build/dmg_staging`。 |
| | | 2. 复制 `TagLauncher.app`。 |
| | | 3. 创建 `/Applications` symlink。 |
| | | 4. 用 `hdiutil create -format UDZO -imagekey zlib-level=9` 生成 `build/TagLauncher.dmg`。 |
| | | 5. 清理 staging。 |
| | | |
| | | 发布 archive 中的 DMG 会按版本/build 重命名,例如: |
| | | |
| | | `TagLauncher-7.9.1-build20260614.1454.dmg` |
| | | |
| | | ## App Store / Sandbox |
| | | |
| | | `APP_STORE=1` 时: |
| | | |
| | | - 要求 `src/Apptag/TagLauncher.entitlements` 存在。 |
| | | - 如果未设置 `CODESIGN_IDENTITY`,脚本会 ad-hoc sign with entitlements,并打印警告。 |
| | | - 设置 `CODESIGN_IDENTITY` 后执行带 entitlements 的 codesign。 |
| | | |
| | | 7.9.1 QA 证据中记录:App Store `.pkg` 已生成,Installer certificate signature、Bundle ID、版本/build、sandbox entitlement、user-selected file read/write entitlement、Keychain access group、arm64 架构和 payload AppleDouble entries 均通过检查。 |
| | | |
| | | ## 当前 release 证据 |
| | | |
| | | `src/Release/AppStore-7.9.1-20260614.1454/QA_RELEASE_EVIDENCE.md` 记录: |
| | | |
| | | - Version:7.9.1 |
| | | - Build:20260614.1454 |
| | | - Bundle ID:com.taglauncher.app |
| | | - Source commit:`bd792cd36d57b8f0741af9a6a64e59969cdce490` |
| | | |
| | | 自动 QA 通过项: |
| | | |
| | | - `Scripts/usage_tips_qa.sh` |
| | | - `Scripts/app_ordering_data_qa.sh` |
| | | - `Scripts/macos14_availability_typecheck_qa.sh` |
| | | - `Scripts/macos14_build_metadata_qa.sh` |
| | | - `Scripts/quick_search_system_app_qa.sh` |
| | | - `Scripts/smartstart_catalog_resource_qa.sh` |
| | | - `Scripts/quick_search_app_name_qa.sh`:本机缺少 Sunlogin wrapper fixture,记录为 SKIP |
| | | - `hdiutil verify`、App Store `.pkg` 签名和 payload 检查 |
| | | |
| | | 发布产物: |
| | | |
| | | - Archive DMG:`Archive/TagLauncher-7.9.1-build20260614.1454.dmg` |
| | | - Upload PKG:`Upload/TagLauncher-7.9.1-build20260614.1454.pkg` |
| | | - DMG SHA256:`2cb29528ef1c137db330ff8a28baa3c622705916f8368b28c373ca95aefd6981` |
| | | - PKG SHA256:`7ad097a23c736fc7a7b34a9575348cbc52a968d5fbf433584ace353b120a32ef` |
| | | |
| | | ## QA 脚本分层 |
| | | |
| | | | 脚本 | 关注点 | |
| | | | --- | --- | |
| | | | `Scripts/window_logic_qa.sh` | Overlay/Quick Search/Settings/文件面板/Dock/全屏/Split View/重复实例 | |
| | | | `Scripts/quick_search_app_name_qa.sh` | 多语言显示名、wrapper/helper 过滤、Quick Search 文档过滤 | |
| | | | `Scripts/quick_search_system_app_qa.sh` | 系统 App 扫描路径、Keychain Access、中文/拼音搜索候选 | |
| | | | `Scripts/smartstart_catalog_resource_qa.sh` | Smart Start 资源完整性、语言覆盖、Apple 边界、runtime loader | |
| | | | `Scripts/apple_default_apps_resource_qa.sh` | Apple catalog 资源质量、系统扫描覆盖、多语言备注质量、build 边界 | |
| | | | `Scripts/apple_default_note_policy_qa.sh` | Apple/SmartStart/manual 备注来源隔离和语言切换重本地化 | |
| | | | `Scripts/apple_default_note_migration_qa.sh` | 旧版 Apple 初始化备注迁移、历史默认备注 fixture、手写备注保护 | |
| | | | `Scripts/app_ordering_data_qa.sh` | `containerAppOrder` schema、导入导出、备份恢复、SmartStart 清理、拖拽 payload 静态门禁 | |
| | | | `Scripts/usage_tips_qa.sh` | usage tips 多语言文案、分隔符、两行渲染约束 | |
| | | | `Scripts/macos14_availability_typecheck_qa.sh` | macOS 14 可用性和 Swift 类型检查 | |
| | | | `Scripts/macos14_build_metadata_qa.sh` | macOS 14 最低版本、Info.plist、build metadata 边界 | |
| | | |
| | | ## 发布前建议门禁 |
| | | |
| | | 发布或修改以下区域时,应按风险扩大 QA: |
| | | |
| | | - 窗口、快捷键、Dock、activation policy、Quick Search panel:跑 `window_logic_qa.sh`,并人工 smoke `Fn+Space` 连续开关、立即输入、Esc、点击结果。 |
| | | - 名称解析、AppIndexer、Quick Search 文档:跑两个 Quick Search QA。 |
| | | - Smart Start 或 Apple catalog:跑三个资源/备注 QA。 |
| | | - Apple 默认应用备注属于初始化内容,任一语种都不允许混入其他语种正文。修改 Apple catalog 或默认备注迁移时,`apple_default_apps_resource_qa.sh` 必须同时检查 translations 和运行用 localizations 的跨语种脚本污染;`apple_default_note_migration_qa.sh` 必须覆盖已知历史默认备注,不能只测单个样本。 |
| | | - App Grid 容器排序:跑 `app_ordering_data_qa.sh`,并人工验证同容器拖拽排序、跨容器移动、Option 复制和拖空白移除互不串扰。 |
| | | - 使用技巧悬浮条或 usage tips 文案:跑 `usage_tips_qa.sh`,并人工验证翻页按钮、hover 期间备注气泡抑制、Quick Search only 会话不显示悬浮条。 |
| | | - 构建脚本或资源路径:跑资源 QA、`APP_BUILD=<目标build> bash build.sh`,必要时 `make_dmg.sh` 和 `hdiutil verify`。 |
| | | - 发布包:记录 version/build、commit、命令、结果、产物路径和 SHA256。 |
| | | |
| | | ## 修改风险提示 |
| | | |
| | | - 不要让构建脚本修改 tracked source `Info.plist`;当前 hash 校验是保护边界。 |
| | | - 不要把未压缩多语言 catalog 放入 bundle;运行时 loader 优先 `.json.deflate`,QA 也以此为边界。 |
| | | - 不要把 build 目录普通产物混入源码提交,除非明确作为 release archive。 |
| | | - 不要只跑构建不跑专项 QA;TagLauncher 的高风险主要在窗口时序、搜索名称和资源质量,不是单纯编译。 |
| | |
| | | private static let cacheLock = NSLock() |
| | | private static var cachedBaseSnapshot: BaseSnapshot? |
| | | private static var cachedLocalizationSnapshots: [String: LocalizationSnapshot] = [:] |
| | | private static let legacyDefaultNoteFingerprintsByBundleIdentifier: [String: Set<String>] = [ |
| | | "com.apple.chess": [ |
| | | "9f72f1448c7e9b47", |
| | | "a3b754a4f06eee33", |
| | | "abc8528f6a2ca356" |
| | | ], |
| | | "com.apple.freeform": [ |
| | | "32fee0886cf2c6bb", |
| | | "503c25567e9640c2", |
| | | "aee5e9a7730252f7" |
| | | ], |
| | | "com.apple.home": [ |
| | | "003e996159a187cc", |
| | | "2707ce5206adfb2a", |
| | | "5b0207586af532a9" |
| | | ], |
| | | "com.apple.music": [ |
| | | "545fa7c8a0d216fa", |
| | | "8867798c868c8c30", |
| | | "bce11863b08fac8b" |
| | | ], |
| | | "com.apple.passwords": [ |
| | | "58b38191593ec9b9", |
| | | "a72f91e414c90254", |
| | | "fa1ce5e1bb5998cd" |
| | | ], |
| | | "com.apple.mobilephone": [ |
| | | "703bd99a660e80e4", |
| | | "80d0df54c7ddf2d1", |
| | | "9d57741d3114a1a2" |
| | | ], |
| | | "com.apple.tv": [ |
| | | "6a29d8ece97cd432", |
| | | "e1efb690bf1c6b94", |
| | | "e28785e11f914401" |
| | | ], |
| | | "com.apple.backup.launcher": [ |
| | | "78edffa39f28cee2", |
| | | "cbf81a2b5e77725b", |
| | | "dc568bb30164e897" |
| | | ], |
| | | "com.apple.diskutility": [ |
| | | "77324bc5de72e176", |
| | | "c43f8e117573f5ed", |
| | | "fd581938fd34e888" |
| | | ], |
| | | "com.apple.screencontinuity": [ |
| | | "171a629bd17970fa", |
| | | "2965e73ff5566d19", |
| | | "bb4662c4f15ab83c" |
| | | ] |
| | | ] |
| | | |
| | | static func isKnownAppleApp(_ app: AppInfo) -> Bool { |
| | | entry(for: app) != nil |
| | |
| | | result.insert(TagDatabase.noteFingerprint(variant)) |
| | | } |
| | | } |
| | | result.formUnion(legacyDefaultNoteFingerprintsByBundleIdentifier[bundleIdentifier] ?? []) |
| | | return result |
| | | } |
| | | |
| | |
| | | <key>CFBundlePackageType</key> |
| | | <string>APPL</string> |
| | | <key>CFBundleShortVersionString</key> |
| | | <string>7.9.3</string> |
| | | <string>7.9.4</string> |
| | | <key>CFBundleVersion</key> |
| | | <string>20260619.2142</string> |
| | | <string>20260619.2221</string> |
| | | <key>LSApplicationCategoryType</key> |
| | | <string>public.app-category.utilities</string> |
| | | <key>LSMinimumSystemVersion</key> |
| | |
| | | # TagLauncher Changelog |
| | | |
| | | ## [7.9.4] — 2026-06-19 |
| | | |
| | | - 补修 Apple 自带应用默认备注迁移:覆盖 7.9 之前已写入用户库、但没有 metadata 的历史默认文案,修复 Chess、TV、Phone、Freeform、Music、Time Machine 等应用切换到非中文语言后仍显示旧中文备注的问题 |
| | | - Apple 默认备注迁移改为历史 fingerprint 白名单,不在运行时代码里维护旧中文展示文案,也不放宽为“看到中文就覆盖”,继续保护用户手写备注 |
| | | - 加强 Apple 默认应用资源 QA:29 个语种的初始化备注禁止出现其他语种脚本污染,translations 和运行用 localizations 双重检查 |
| | | - 加强迁移 QA:用真实暴露的旧中文默认备注作为 fixture,逐个验证迁移到 29 个语种后不会保留旧中文 |
| | | - 版本号更新为 `7.9.4`,Build 更新为 `20260619.2221` |
| | | |
| | | ## [7.9.3] — 2026-06-19 |
| | | |
| | | - 修复 Apple 自带应用默认备注在切换语言后仍显示旧中文的问题;旧版本写入但缺少来源 metadata 的默认备注现在会按 bundle 精确匹配默认备注 fingerprint 后迁移为当前语言 |
| | |
| | | | SmartStart 资源边界 | `bash Scripts/smartstart_catalog_resource_qa.sh` | PASS | |
| | | | 使用技巧回归 | `bash Scripts/usage_tips_qa.sh` | PASS | |
| | | | macOS 14 typecheck | `bash Scripts/macos14_availability_typecheck_qa.sh` | PASS | |
| | | | 构建 | `APP_BUILD=20260619.2142 bash build.sh` | PASS | |
| | | | 真实用户库只读模拟 | Python 读取当前 `tags.json`,模拟 `ms/ja/ar/en` 迁移 | PASS | |
| | | | 构建 | `APP_BUILD=20260619.2221 bash build.sh` | PASS | |
| | | | 构建 metadata | `bash Scripts/macos14_build_metadata_qa.sh` | PASS | |
| | | | 签名验证 | `codesign --verify --deep --strict --verbose=2 build/TagLauncher.app` | PASS | |
| | | | DMG | `bash make_dmg.sh` + `hdiutil verify build/TagLauncher.dmg` | PASS | |
| | | |
| | | ## 7.9.3 漏测复盘 |
| | | |
| | | - 漏测现象:Chess、TV、Phone、Freeform、Music、Time Machine 等旧版本初始化备注在切换到非中文语言后仍显示中文。 |
| | | - 漏测原因:上一版迁移 QA 只覆盖了 ColorSync 的旧中文默认备注和句末标点变体,没有覆盖 7.9 之前另一批已经写入用户库的历史默认文案。 |
| | | - 修正后的 QA 原则:Apple 自带应用备注属于初始化内容,任何语种资源中都不允许出现其他语种正文;真实迁移 fixture 必须覆盖已知历史默认备注,而不是只测一个样本。 |
| | | |
| | | ## 覆盖的关键场景 |
| | | |
| | | - `com.apple.colorsyncutility` 的旧中文默认备注带历史句号 `。`,无 metadata,能被识别为 Apple 默认备注。 |
| | | - Chess、TV、Phone、Freeform、Music、Time Machine、Home、Passwords、Disk Utility、iPhone Mirroring 的历史中文默认备注无 metadata 时,能被识别为 Apple 默认备注。 |
| | | - 对当前真实 `tags.json` 做只读模拟:上述 10 条 stale Apple 备注在 `ms`、`ja`、`ar`、`en` 下都会迁移到目标语种,不保留旧中文。 |
| | | - 同样的备注 fingerprint 只在同 bundle 范围内生效,不能跨 bundle 误匹配。 |
| | | - 用户追加内容后的中文备注不匹配 Apple 默认备注集合,不会被迁移。 |
| | | - 29 个语种的 Apple 初始化备注均执行跨语种脚本污染检查,translations 和 localizations 任一失败都会阻断发布。 |
| | | - `com.apple.campo` 在 base、translations 和每个 localization 文件中都存在。 |
| | | - 产物版本与源码版本一致:`7.9.3 / 20260619.2142`。 |
| | | - 产物版本与源码版本一致:`7.9.4 / 20260619.2221`。 |
| | | - DMG:`/Users/ar/Projects/Taglauncher/src/build/TagLauncher.dmg` |
| | | - DMG SHA256:`e6a5b2d1aab96cfd8e8a09edda7ae64b0319c044c2517ad9527887f020507d41` |
| | | |
| | | ## 结论 |
| | | |
| | | 本轮修复满足发布前自查要求。自动化未发现 Apple 默认备注迁移、资源完整性、macOS 14 构建 metadata 或签名问题。 |
| | | 本轮修复满足发布前自查要求。自动化未发现 Apple 默认备注迁移、跨语种资源污染、macOS 14 构建 metadata 或签名问题。 |
| | |
| | | - 本轮修复只按同 bundle 的 Apple 默认备注 fingerprint 迁移旧数据,不按中文字符判断。 |
| | | - Arabic 等非中文语言资源中 Apple 默认备注保持本地语言;旧中文默认备注会迁移到当前语言。 |
| | | - 用户手写备注和手动清空备注仍由 `.manual` metadata 保护,不会被本轮迁移覆盖。 |
| | | |
| | | ## 2026-06-19 22:21 |
| | | |
| | | ### 用户复测失败 |
| | | |
| | | - 用户截图显示,在非中文语言下,Chess、TV、Phone、Freeform、Music、Time Machine 等 Apple 自带应用仍显示旧中文备注。 |
| | | - 本机 `tags.json` 复查确认这些应用的备注为旧中文默认文案,且 `appNoteMetadata` 为 `None`。 |
| | | - 7.9.3 的迁移只覆盖当前 29 语种资源和句末标点变体,未覆盖 7.9 之前已经写入用户库的另一批历史默认文案。 |
| | | |
| | | ### 补修 |
| | | |
| | | - `AppleDefaultAppCatalog` 增加历史默认备注 fingerprint 白名单,覆盖真实暴露的旧中文默认备注。 |
| | | - 白名单按 bundle id 精确匹配,不使用“包含中文就覆盖”的宽泛规则。 |
| | | - 运行时代码保存 fingerprint,不保存旧中文展示文案。 |
| | | - `apple_default_note_migration_qa.sh` 增加真实历史默认备注 fixture,并验证迁移到 29 个语种后不会保留旧中文。 |
| | | - `apple_default_apps_resource_qa.sh` 增加跨语种脚本污染检查:Apple 初始化备注在 translations 和 localizations 中都不允许混入其他语种正文。 |
| | | - 版本推进到 `7.9.4 / 20260619.2221`。 |
| | | |
| | | ### 补充 QA 证据 |
| | | |
| | | - `bash Scripts/apple_default_apps_resource_qa.sh`:PASS |
| | | - `bash Scripts/apple_default_note_migration_qa.sh`:PASS |
| | | - `bash Scripts/apple_default_note_policy_qa.sh`:PASS |
| | | - `bash Scripts/smartstart_catalog_resource_qa.sh`:PASS |
| | | - `bash Scripts/usage_tips_qa.sh`:PASS |
| | | - `bash Scripts/macos14_availability_typecheck_qa.sh`:PASS |
| | | - 当前真实 `tags.json` 只读模拟:Chess、Freeform、Home、Music、Passwords、Phone、TV、Time Machine、Disk Utility、iPhone Mirroring 共 10 条 stale Apple 备注,在 `ms/ja/ar/en` 下均会迁移到目标语种。 |
| | | - `APP_BUILD=20260619.2221 bash build.sh`:PASS |
| | | - `bash Scripts/macos14_build_metadata_qa.sh`:PASS |
| | | - `codesign --verify --deep --strict --verbose=2 build/TagLauncher.app`:PASS |
| | | - `bash make_dmg.sh`:PASS,生成 `/Users/ar/Projects/Taglauncher/src/build/TagLauncher.dmg` |
| | | - `hdiutil verify build/TagLauncher.dmg`:PASS |
| | | - DMG SHA256:`e6a5b2d1aab96cfd8e8a09edda7ae64b0319c044c2517ad9527887f020507d41` |
| | | |
| | | ### 追加 QA 原则 |
| | | |
| | | - Apple 自带应用备注属于初始化内容,不是用户内容。 |
| | | - 初始化备注必须按当前语言显示;任一语种资源出现其他语种正文均视为 QA FAIL。 |
| | | - QA 不能只验证资源存在或单个样本迁移,必须覆盖真实用户库里已知历史默认备注。 |
| | |
| | | if not isinstance(notes_by_language, dict): |
| | | fail("Apple translations notesByLanguage must be an object") |
| | | |
| | | script_patterns = { |
| | | "cjk": re.compile(r"[\u4e00-\u9fff]"), |
| | | "kana": re.compile(r"[\u3040-\u30ff]"), |
| | | "hangul": re.compile(r"[\uac00-\ud7af]"), |
| | | "cyrillic": re.compile(r"[\u0400-\u04ff]"), |
| | | "arabic": re.compile(r"[\u0600-\u06ff]"), |
| | | "thai": re.compile(r"[\u0e00-\u0e7f]"), |
| | | } |
| | | |
| | | expected_script_requirements = { |
| | | "zh-Hans": ("cjk",), |
| | | "zh-Hant": ("cjk",), |
| | | "ja": ("kana",), |
| | | "ko": ("hangul",), |
| | | "ar": ("arabic",), |
| | | "ar-Najdi": ("arabic",), |
| | | "ru": ("cyrillic",), |
| | | "uk": ("cyrillic",), |
| | | "sr-Cyrl": ("cyrillic",), |
| | | "th": ("thai",), |
| | | } |
| | | |
| | | forbidden_scripts_by_language = { |
| | | "zh-Hans": ("kana", "hangul", "cyrillic", "arabic", "thai"), |
| | | "zh-Hant": ("kana", "hangul", "cyrillic", "arabic", "thai"), |
| | | "ja": ("hangul", "cyrillic", "arabic", "thai"), |
| | | "ko": ("cjk", "kana", "cyrillic", "arabic", "thai"), |
| | | "ar": ("cjk", "kana", "hangul", "cyrillic", "thai"), |
| | | "ar-Najdi": ("cjk", "kana", "hangul", "cyrillic", "thai"), |
| | | "ru": ("cjk", "kana", "hangul", "arabic", "thai"), |
| | | "uk": ("cjk", "kana", "hangul", "arabic", "thai"), |
| | | "sr-Cyrl": ("cjk", "kana", "hangul", "arabic", "thai"), |
| | | "th": ("cjk", "kana", "hangul", "cyrillic", "arabic"), |
| | | } |
| | | |
| | | for code in l10n_codes: |
| | | forbidden_scripts_by_language.setdefault( |
| | | code, |
| | | ("cjk", "kana", "hangul", "cyrillic", "arabic", "thai"), |
| | | ) |
| | | |
| | | def assert_note_language_clean(code: str, note: str, context: str) -> None: |
| | | for script_name in expected_script_requirements.get(code, ()): |
| | | if not script_patterns[script_name].search(note): |
| | | fail(f"{context} does not contain expected {script_name} script for {code}") |
| | | for script_name in forbidden_scripts_by_language.get(code, ()): |
| | | if script_patterns[script_name].search(note): |
| | | fail(f"{context} contains unexpected {script_name} script for {code}: {note!r}") |
| | | |
| | | for code in l10n_codes: |
| | | display_names = display_names_by_language.get(code) |
| | | notes = notes_by_language.get(code) |
| | |
| | | fail(f"Apple translations note for {code}/{normalized} is identical to zh-Hans") |
| | | if code not in {"zh-Hans", "zh-Hant", "ja"} and re.search(r"[\u4e00-\u9fff]", note): |
| | | fail(f"Apple translations note for {code}/{normalized} contains Chinese characters") |
| | | assert_note_language_clean(code, note, f"Apple translations note for {code}/{normalized}") |
| | | |
| | | bad_fragments = [ |
| | | "Apple 內建 App", |
| | |
| | | "null", |
| | | ] |
| | | |
| | | script_requirements = { |
| | | "zh-Hans": re.compile(r"[\u4e00-\u9fff]"), |
| | | "zh-Hant": re.compile(r"[\u4e00-\u9fff]"), |
| | | "ja": re.compile(r"[\u3040-\u30ff\u4e00-\u9fff]"), |
| | | "ko": re.compile(r"[\uac00-\ud7af]"), |
| | | "ar": re.compile(r"[\u0600-\u06ff]"), |
| | | "ar-Najdi": re.compile(r"[\u0600-\u06ff]"), |
| | | "ru": re.compile(r"[\u0400-\u04ff]"), |
| | | "uk": re.compile(r"[\u0400-\u04ff]"), |
| | | "sr-Cyrl": re.compile(r"[\u0400-\u04ff]"), |
| | | "th": re.compile(r"[\u0e00-\u0e7f]"), |
| | | } |
| | | |
| | | for code in l10n_codes: |
| | | path = apple_dir / f"AppleDefaultApps.localizations.{code}.json" |
| | | if not path.is_file(): |
| | |
| | | for fragment in bad_fragments: |
| | | if fragment in note: |
| | | fail(f"{path.name} note for {bundle} contains blocked fragment: {fragment!r}") |
| | | if code in script_requirements and not script_requirements[code].search(note): |
| | | fail(f"{path.name} note for {bundle} does not contain expected script for {code}") |
| | | assert_note_language_clean(code, note, f"{path.name} note for {bundle}") |
| | | missing = bundle_ids - set(entries_by_bundle) |
| | | if missing: |
| | | fail(f"{path.name} is missing {len(missing)} Apple entries; first missing: {sorted(missing)[:5]}") |
| | |
| | | languages = base.get("supportedLanguages", []) |
| | | note_limit = int(base.get("noteLimit", 80)) |
| | | entries = base.get("entries", []) |
| | | bundle_ids = {entry.get("bundleIdentifier") for entry in entries} |
| | | bundle_ids = { |
| | | entry.get("bundleIdentifier").lower() |
| | | for entry in entries |
| | | if isinstance(entry.get("bundleIdentifier"), str) |
| | | } |
| | | normalized_names = {entry.get("normalizedName") for entry in entries} |
| | | |
| | | for bundle in ["com.apple.colorsyncutility", "com.apple.campo", "com.apple.siri.launcher"]: |
| | |
| | | path = apple_dir / f"AppleDefaultApps.localizations.{language}.json" |
| | | catalog = load_json(path) |
| | | localizations[language] = { |
| | | item["bundleIdentifier"]: item |
| | | item["bundleIdentifier"].lower(): item |
| | | for item in catalog.get("entries", []) |
| | | if isinstance(item, dict) and isinstance(item.get("bundleIdentifier"), str) |
| | | } |
| | |
| | | fingerprints.add(note_fingerprint(variant, note_limit)) |
| | | known_fingerprints_by_bundle[bundle] = fingerprints |
| | | |
| | | historical_default_notes = { |
| | | "com.apple.chess": "系统自带国际象棋,摸鱼时比刷网页更优雅。", |
| | | "com.apple.freeform": "无限白板,适合头脑风暴、草图和乱七八糟的想法。", |
| | | "com.apple.home": "控制 HomeKit 设备,让 Mac 也能管家里的灯和空调。", |
| | | "com.apple.music": "本地音乐和 Apple Music 的播放器。", |
| | | "com.apple.passwords": "系统密码管家,账号、验证码和通行密钥集中管。", |
| | | "com.apple.mobilephone": "通过 iPhone 在 Mac 上接打电话。", |
| | | "com.apple.tv": "Apple TV 和本地影视内容的入口。", |
| | | "com.apple.backup.launcher": "系统备份工具,后悔药就靠它提前准备。", |
| | | "com.apple.diskutility": "管磁盘、分区、格式化和急救,别乱点但很重要。", |
| | | "com.apple.screencontinuity": "在 Mac 上直接操作 iPhone,少一次伸手拿手机。", |
| | | } |
| | | for bundle, note in historical_default_notes.items(): |
| | | if bundle not in known_fingerprints_by_bundle: |
| | | fail(f"historical default bundle is not in Apple base: {bundle}") |
| | | for variant in legacy_variants(note, note_limit): |
| | | known_fingerprints_by_bundle[bundle].add(note_fingerprint(variant, note_limit)) |
| | | |
| | | legacy_zh_note_with_period = zh_default_note + "。" |
| | | if note_fingerprint(legacy_zh_note_with_period, note_limit) not in known_fingerprints_by_bundle[colorsync_bundle]: |
| | | fail("legacy zh-Hans ColorSync note with historical period must match known Apple defaults") |
| | | |
| | | for bundle, note in historical_default_notes.items(): |
| | | if note_fingerprint(note, note_limit) not in known_fingerprints_by_bundle[bundle]: |
| | | fail(f"historical default note must match for {bundle}") |
| | | for language in languages: |
| | | localized_entry = localizations[language].get(bundle) |
| | | if not localized_entry: |
| | | fail(f"historical default note target is missing localization for {language}/{bundle}") |
| | | localized_note = normalized_note(localized_entry.get("note"), note_limit) |
| | | if not localized_note: |
| | | fail(f"historical default note target is empty for {language}/{bundle}") |
| | | if language not in {"zh-Hans", "zh-Hant"} and localized_note == note: |
| | | fail( |
| | | "historical default note migration would keep Chinese text " |
| | | f"for {language}/{bundle}: {localized_note!r}" |
| | | ) |
| | | |
| | | manual_chinese_note = zh_default_note + " 我自己加的备注" |
| | | if note_fingerprint(manual_chinese_note, note_limit) in known_fingerprints_by_bundle[colorsync_bundle]: |
| | |
| | | || fail "AppleDefaultAppCatalog must match legacy notes against known Apple defaults" |
| | | rg -Fq 'legacyDefaultNoteVariants' "$APPLE_CATALOG_SWIFT" \ |
| | | || fail "AppleDefaultAppCatalog must include historical punctuation variants" |
| | | rg -Fq 'legacyDefaultNoteFingerprintsByBundleIdentifier' "$APPLE_CATALOG_SWIFT" \ |
| | | || fail "AppleDefaultAppCatalog must include historical Apple default notes" |
| | | rg -Fq 'a3b754a4f06eee33' "$APPLE_CATALOG_SWIFT" \ |
| | | || fail "AppleDefaultAppCatalog must include pre-metadata Chess default note" |
| | | rg -Fq 'metadata?.origin == .manual' "$APPLE_CATALOG_SWIFT" \ |
| | | || fail "AppleDefaultAppCatalog must protect manual notes" |
| | | rg -Fq 'apple-default-note-migration' "$APPLE_CATALOG_SWIFT" \ |
| | |
| | | || fail "Apple relocalization must adopt legacy default notes by exact known-default fingerprint" |
| | | rg -Fq 'legacyDefaultNoteVariants' "$APPLE_CATALOG_SWIFT" \ |
| | | || fail "Apple relocalization must include historical default-note punctuation variants" |
| | | rg -Fq 'legacyDefaultNoteFingerprintsByBundleIdentifier' "$APPLE_CATALOG_SWIFT" \ |
| | | || fail "Apple relocalization must include pre-metadata historical Apple default notes" |
| | | rg -Fq 'apple-default-note-migration' "$APPLE_CATALOG_SWIFT" \ |
| | | || fail "Apple relocalization must back up before adopting legacy no-metadata notes" |
| | | rg -Fq 'origin: .appleDefault' "$APPLE_CATALOG_SWIFT" \ |