DeepSeek Harness macOS 菜单仍是英文?CFBundleLocalizations 丢失排查与补回
如果 macOS 已经是简体中文,Desktop 自己的界面也是中文,但「文件」菜单里的系统项和「添加工作区」的文件夹对话框仍是英文,先去读已安装应用的 Info.plist,看 CFBundleLocalizations 在不在。 据讨论 #10343,0.2.1-rc.2 的包里没有这个键,Contents/Resources 也没有 .lproj 目录,系统因此把应用当成纯英文。根因不在 electron-builder,而在构建配置里同一个对象写了两个 extendInfo。
DeepSeek Harness Desktop 的 Info.plist 里有没有 CFBundleLocalizations
验证先跑两条命令,确认事实再改配置,避免把系统语言问题当成打包问题。 装好的应用和构建产物都可以这样查:
- 执行
plutil -p <应用路径>/Contents/Info.plist | grep CFBundleLocalizations,把<应用路径>换成实际安装位置,预期打印数组或什么都不打印。 - 观察输出,预期没有任何输出说明键缺失;若打印出数组,则问题不在这个键。
- 执行
ls <应用路径>/Contents/Resources | grep lproj,预期打印.lproj目录或什么都不打印。 - 观察输出,预期同样为空,说明既没声明语言、也没带本地化目录,系统只能按英文处理。
- 再检查系统「语言与地区」里简体中文是否排在首位,预期首选语言确实是中文,排除配置本身的问题。
DeepSeek Harness 缺这个键时哪些文本会变
缺这个键时,受影响的不是应用自己渲染的界面,而是系统代替应用本地化的部分。 据讨论,范围大致如下:
- macOS 追加的菜单项,例如「服务」「显示所有窗口」这类系统项,预期显示为英文。
- 「添加工作区」打开的文件夹选择对话框,预期按钮与标签由系统按英文提供。
- 系统弹出的权限请求、文件位置等标准对话框,预期同样是英文。
- 应用自己写的界面文案仍按应用内 i18n 显示,预期出现「一半中文一半英文」。
- 把系统语言切回英文再切回中文,预期只有系统代本地化部分跟着切换,应用内文案仍按应用设置走。
DeepSeek Harness 为什么会丢:同一对象里的两个 extendInfo 键
据讨论中社区核对源码,electron-builder-config.mjs 的同一个对象字面量里出现了两个 extendInfo,后一个把前一个覆盖了。 这不是 electron-builder 的合并 bug,而是普通 JavaScript 语义。打包链路的其他排查可以对照 macOS Desktop 故障说明 看:
- 第一个
extendInfo带着CFBundleLocalizations和麦克风用途说明,预期它本来会进入构建输入。 - 后续提交把它拆成两个键,第二个只保留
NSMicrophoneUsageDescription,预期前一个键被同名属性覆盖。 - 对象字面量求值时,后面的同名键替换前面的,预期
CFBundleLocalizations从未进入构建输入。 .oxlintrc.json忽略了**/*.mjs,预期no-dupe-keys规则不会在这个文件上运行。- 现有签名测试只断言麦克风描述,预期两个检查都没拦住。
DeepSeek Harness Desktop 补回 CFBundleLocalizations 的三条路径
三条路都能把语言声明补回包里,差别在改构建配置、补已生成的产物,还是收敛配置来源。 这不是 DSH插件 的问题,而是 Desktop 打包配置的问题,按你能否重新打包来选。
路径一:合并 extendInfo,保留原注释
这是最直接的根修:一个 extendInfo 同时带两个键,并把解释「为什么必须放在 bundle 里」的注释一起留下。 改动只在构建配置,不涉及运行时。
- 打开
apps/desktop/scripts/electron-builder-config.mjs,定位两处extendInfo,预期两处都能找到。 - 删除第二处,把
NSMicrophoneUsageDescription并入第一处,预期只剩一个键且带两个属性。 - 保留原来关于 macOS 按 bundle 匹配语言、而不是读 Electron Framework 资源的注释,预期理由仍留在代码里。
- 重新打包,按上一节的两条命令检查产物,预期
Info.plist同时出现CFBundleLocalizations与麦克风描述。 - 在中文系统下启动,预期系统菜单变为中文。
- 把产物装到另一台中文 Mac 上复测,预期菜单语言同样跟随首选语言,而不是只在开发机生效。
extendInfo: {
CFBundleLocalizations: ['en', 'zh_CN'],
NSMicrophoneUsageDescription:
'DeepSeek Harness uses your microphone to transcribe speech into message drafts.',
},
路径二:打包后脚本注入 plist
如果暂时不能改构建配置,可以在产物上补键,但必须重新签名,否则应用会被系统拒绝。 适合本机验证或内部测试包:
- 先复制一份产物做备份,预期注入失败后还能恢复原包。
- 用
/usr/libexec/PlistBuddy -c "Add :CFBundleLocalizations array"创建数组,预期键以空数组存在。 - 继续用
-c "Add :CFBundleLocalizations:0 string en"和-c "Add :CFBundleLocalizations:1 string zh_CN"写入两项,预期数组包含en与zh_CN。 - 执行
plutil -lint,预期 plist 语法正确、解析无错误。 - 用原有身份重新执行
codesign,保留 hardened runtime 与 entitlements,预期签名覆盖修改后的 plist。 - 预期结果:
codesign --verify通过,应用可启动,系统菜单语言跟随首选语言。
路径三:改用 mac.extendInfo 单一来源
要防止下次再出现重复键,就把这段配置收敛成一个变量,只在一个位置引用。 这样即使以后再加键,也不会出现两个同名属性:
- 在配置模块顶部定义
const macExtendInfo = { ... },集中放语言与权限描述,预期一个对象持有全部 mac bundle 键。 - 在 mac 配置里只写一次
extendInfo: macExtendInfo,预期配置里只有一个extendInfo属性。 - 若构建脚本还有其他分支需要扩展,用对象展开合并,而不是再写一个
extendInfo键,预期扩展都并进同一个对象。 - 给
.mjs文件补上 lint 覆盖,或单独对构建脚本跑一次no-dupe-keys,预期重复键在打包前被报出来。 - 重新打包并读一次产物,预期
extendInfo仍只出现一次,新增键没有覆盖旧键。
DeepSeek Harness 本地化判别的回归与签名检查
同样显示英文,修法不同:前者改 plist,后者改翻译资源。 先用四个对照区分来源,再按后面的顺序验收:
- 应用主界面是中文、系统菜单是英文,预期指向 bundle 语言声明。
- 应用主界面本身也有英文,预期指向应用内翻译缺条目,与 plist 无关。
- 只有某个第三方对话框英文,预期是该组件自己的语言包,不要改
CFBundleLocalizations。 - 换一个 macOS 用户、首选语言设为中文再测,预期结果随系统变,进一步确认是系统代本地化。
- 增加一个测试读取构建后的
Info.plist,断言CFBundleLocalizations等于预期数组,同时断言NSMicrophoneUsageDescription仍存在,预期两个键都被覆盖。 - 在简体中文系统上启动,检查文件夹对话框与系统追加菜单,再跑
codesign --verify --deep --strict与spctl,预期语言、权限描述、签名三项同时通过。 - 把
Info.plist检查放进打包脚本尾部,预期构建失败时也能看到缺了哪个键。
常见问题
DeepSeek Harness Desktop 多半不是界面没翻译,而是 bundle 没声明支持的语言。据讨论 #10343,Desktop 自身界面可以是中文,但 macOS 和 Chromium 代本地化的系统菜单、文件夹对话框会跟随应用的 CFBundleLocalizations;这个键缺失时系统按英文应用处理,所以追加菜单保持英文。
DeepSeek Harness 直接读已安装应用的 Info.plist 最准。执行 plutil -p /Applications/DeepSeek\ Harness.app/Contents/Info.plist 后查找 CFBundleLocalizations,没有这个键就是 bundle 没声明;再看 Contents/Resources 下有没有 .lproj 目录,两者都缺即可排除系统语言因素。
DeepSeek Harness 的 electron-builder-config.mjs 在同一个对象字面量里出现了两个 extendInfo 键。JavaScript 后写的键覆盖先写的,只有麦克风描述那一个生效,CFBundleLocalizations 被丢掉;electron-builder 本身没收到两条记录。同时 .oxlintrc.json 忽略了 *.mjs,no-dupe-keys 规则没有机会报错。
DeepSeek Harness 的 Info.plist 属于签名内容的一部分,改动会让原有签名失效。修改后必须重新签名并保留 hardened runtime 设置,再在目标机器上验证;临时脚本注入适合本机验证或内部构建,不适合直接替换正式签名产物。
DeepSeek Harness 只改应用内翻译不能解决系统菜单英文,因为系统菜单和文件夹对话框走的是 CFBundleLocalizations,不是应用内 i18n。要把语言声明补进 Info.plist 并重新签名,改翻译资源只会影响应用自己渲染的文案。
相关术语
- CFBundleLocalizations
- CFBundleLocalizations 是 Info.plist 中声明应用支持语言的数组键。macOS 用它匹配系统首选语言,决定系统菜单、对话框等框架代本地化内容显示哪种语言。— https://github.com/deepseek-ai/deepseek-harness/discussions/10343
- extendInfo
- extendInfo 是 electron-builder 的 mac 配置项,用于把自定义键合并进生成的 Info.plist。它是构建配置里的一个 JavaScript 对象属性,同名属性重复时只有最后一个生效。— https://github.com/deepseek-ai/deepseek-harness/discussions/10343
- Info.plist
- Info.plist 是 macOS 应用包根部的属性列表文件,记录 bundle 标识、版本、支持语言与权限用途说明。系统读取它来决定应用的本地化与权限行为。— https://github.com/deepseek-ai/deepseek-harness/discussions/10343
- .lproj
- .lproj 是 macOS 应用包内按语言存放资源的目录后缀,例如 zh_CN.lproj。目录存在只说明带了资源,系统是否采用仍取决于 Info.plist 的语言声明。— https://github.com/deepseek-ai/deepseek-harness/discussions/10343