Codex桌面端汉化全攻略:从菜单到主界面的完整实现思路

Electron汉化app.asarCodex桌面端
于 2026-10-01 03:48:08 修改
·本内容遵循CC 4.0 BY-SA版权协议

Codex 桌面端一出来我就装上了,界面干净、交互也顺手,但有个特别现实的问题:整个界面几乎全是英文。Dashboard、New Chat、Settings、Attach files……常年混英文界面的开发者倒还好,但团队里不少同事装上之后第一句话就是——这玩意儿能汉化吗?

答案是能,而且不用等官方的中文版。Codex 桌面端是典型的 Electron 应用,界面本质就是一个跑在本地 Chromium 里的网页,所有文本资源都打包在一个叫 app.asar 的文件里。汉化的完整思路其实就一句话:解开 asar,找到文本,替换成中文,重新打包。但真正动起手来,从系统菜单到主界面中间有一堆细节问题,比如文本散落在哪些文件、菜单栏为什么和普通界面不一样、替换之后签名失效怎么办、应用自动更新会不会把汉化冲掉。这篇文章就把这套从菜单到主界面的实现思路完整拆开讲。

1. 为什么汉化 Codex 桌面端,本质上是在动"应用外壳"里的文本

1.1 Electron 应用给汉化带来的好消息和坏消息

Electron 应用的结构可以理解为:一个 Chromium 浏览器内核负责画界面,一个 Node.js 环境负责调系统能力,中间由主进程和渲染进程配合干活。Codex 桌面端就是这种结构,前端界面使用 HTML/CSS/JS 渲染,打包时这些文件会被封装进 app.asar。

这对汉化来说是双重影响。

好消息是,界面文本不是编译进二进制文件里的,而是以字符串形式保存在 JS、HTML、CSS 文件中。只要你能打开 asar 包,就能像改网页一样改文本,不需要逆向、不需要搞懂汇编,门槛比传统原生应用低得多。

坏消息也很直接:文本不是像传统软件那样集中放在一个 Language 或者 locale 目录里,而是散落在各个 JS bundle 中。你可能在一个文件里改 10 个词,隔壁文件里还藏着另外 20 个词。所以汉化过程更像"在包装箱里翻找每一张标签",而不是"打开一个语言包直接翻译"。

1.2 界面文本的三种藏身形式,先判断再动手

拿到一个 Electron 应用,不要上来就满文件搜单词,先判断文本属于哪一类。我从实际经验里总结出三种常见形式:

文本形式 典型特征 汉化优先级 说明
硬编码字符串 直接在 JS/HTML 里写死的英文 最高 绝大多数 Electron 应用的主战场
独立 locale 文件 存在 locales/、i18n/、lang/ 目录的 JSON/PO 文件 高,改动最安全 如果应用本身做了 i18n,优先改这里
远端下发内容 由服务端返回的公告、提示、运营位文案 无法在本地改 不要花时间折腾,本地改不了

判断方法很简单:解包之后搜索一个你确定会在界面上看到的词,比如 Settings。如果这个词出现在 .js 文件里一大串压缩代码中,说明是硬编码类型;如果出现在 locales/en.json 里,说明有独立语言包;如果解包目录里根本搜不到这个词但界面上却能看到,那基本就是远端下发内容。

Codex 桌面端目前以硬编码字符串为主,所以这篇文章后面讲的方法都是围绕"如何在压缩混淆后的 JS 文件里安全地做字符串替换"。

1.3 为什么菜单栏是汉化的分水岭

菜单栏和普通界面有一个本质区别:普通界面的文本跑在渲染进程,也就是网页层;而系统菜单栏的文本是在主进程里用 Electron 的 Menu API 创建的 label 定义的。

这就导致一个很常见的问题:你辛辛苦苦把主界面按钮都改成中文了,打开菜单栏一看,还是英文。这不是因为你漏改了,而是因为你根本没找到主进程那部分代码。

macOS 的顶部菜单栏、Windows 上的应用菜单、右键上下文菜单,这些区域的文本一般都在主进程入口文件里。Electron 的 MenuItem 有 role 概念,role: 'quit'、role: 'copy' 这类项目在某些系统语言环境下会自动本地化,但只要应用本身没有适配中文 locale,大部分情况下仍然以硬编码 label 为准。所以汉化菜单栏需要在主进程代码中硬改 label,这一步做完,整个应用的"本地化完成度"才真正上了一个台阶。

这也意味着,汉化思路可以做一个清晰的拆解:第一层是系统菜单和右键菜单,对应主进程代码;第二层是侧边栏、会话区、设置面板,对应渲染进程代码。两条线都要走一遍,才算是完整的桌面端汉化。

2. 解包前的准备工作:找到安装目录、拆开 asar、建立替换基线

2.1 定位 Codex 桌面端的安装目录与资源包

不管哪个系统,Electron 应用安装后一定会有一个 resources 目录,里面放着核心资源。

  • macOS 路径通常为:/Applications/Codex.app/Contents/Resources
  • Windows 路径通常为:C:\Users\<用户名>\AppData\Local\Programs\codex\resources 或安装时自选的目录

打开 resources 目录后,核心关注两个东西:app.asar 和 app.asar.unpacked。app.asar 是主程序资源包,界面文本基本都在里面;app.asar.unpacked 一般放原生 .node 模块和需要直接访问的文件,汉化通常不用碰它。

动手之前先做基线备份,把整个 resources 目录复制一份到别的路径。这一步很多人省略,等到改坏了才发现没有回滚手段。

2.2 解包 app.asar 的正确姿势

使用 @electron/asar 这个官方工具来解包和重打包。没有 Node 环境的话先装一个 Node.js LTS 版本,然后用 npx 直接执行:

BASH
# 在 resources 目录下执行
npx @electron/asar extract app.asar app.src

解包完成后会生成一个 app.src 目录,里面就是完整的应用源代码。目录里通常能看到 package.json、dist、build、out 等文件夹,界面文本一般集中在 dist 或 out 下的 JS/HTML 文件里。

搜索文本资源时用下面两种方式之一:

BASH
# macOS / Linux
grep -r "Settings" --include="*.js" --include="*.html" app.src
 
# Windows PowerShell
findstr /S /I "Settings" app.src\*.js app.src\*.html

压缩混淆过的 bundle 常常一整个文件只有一行,grep 输出会非常长。可以用 -o 只显示匹配片段周围的字符:

BASH
grep -o ".\{0,30\}Settings.\{0,30\}" app.src/dist/renderer.js

这样能快速看到匹配词上下文,判断它是用户可见文案还是内部标识符。

2.3 用 DevTools 远程调试端口精准定位界面元素

有些文本在代码里是动态拼接的,或者被拆成了多段,直接搜可能搜不到完整文案。这时可以借助 Electron 的远程调试能力。

在终端里手动启动 Codex,并加上远程调试参数:

BASH
# macOS
/Applications/Codex.app/Contents/MacOS/Codex --remote-debugging-port=9222
 
# Windows
codex.exe --remote-debugging-port=9222

然后打开本机 Chrome 访问 http://localhost:9222,会看到应用当前打开的页面列表,点击 inspect 就能进入 DevTools。用元素选择器点界面上的英文文本,直接看它对应的是哪个 DOM 节点、挂在哪个 CSS 类上,再到源码里搜相关关键词,定位效率会高很多。

需要提醒一句:远程调试端口会暴露应用内部状态,只建议在本地临时使用,用完就关,不要在公共网络环境里带着这个参数启动。

2.4 重打包前先建立一个只读基线

解包后不要立刻开改。先把关键文件的哈希值记下来,后续定位问题时能快速判断"我到底改过哪个文件":

BASH
find app.src -type f \( -name "*.js" -o -name "*.html" -o -name "*.css" \) -exec md5 {} \; > before.txt

改动过程中如果出现启动崩溃,把这个哈希列表和当前文件对比,能省掉大量排查时间。这个小动作成本极低,收益极高。

3. 从菜单栏开始汉化:系统菜单、右键菜单与侧边栏导航

3.1 系统菜单:在主进程代码里改 label

菜单栏文本藏在主进程代码中,通常以 label: 的形式出现。在解包目录里搜 label 时,目标文件一般是入口文件或主进程 bundle。常见菜单项包括 File、Edit、View、Window、Help,macOS 上还会有以应用名命名的 App 菜单。

搜索时可以这样执行:

BASH
grep -o "label: *['\"][^'\"]*['\"]" app.src/dist/main.js | sort | uniq -c | sort -rn

你会看到一个 label 清单,按出现次数排列。汉化时逐条替换为中文,像这样:

JAVASCRIPT
// 修改前
{
label: 'File',
submenu: [...]
}
 
// 修改后
{
label: '文件(&F)',
submenu: [...]
}

(&F) 这种写法是助记符,Windows 和 Linux 下用户可以用 Alt+F 展开菜单,macOS 下一般显示为快捷键提示,保留它是比较专业的做法。

这里有个经验:菜单 label 并不全在同一个文件里。有些应用为了减少主进程体积,会把菜单模板单独拆成一个 menu.js 或 menu.ts 编译产物。如果主文件里搜不到,就到 dist 目录里再搜一遍 buildFromTemplate 或 Menu.setApplicationMenu 附近的代码。

3.2 右键菜单和上下文菜单:另一个高频文本区

右键菜单是汉化时最容易被遗漏的区域。它在界面上不会一直显示,等真正需要复制、粘贴、检查元素时才冒出来,而且很多应用在右键菜单里混用了硬编码文本和 role 类型。

右键菜单文本通常包括:Copy、Paste、Cut、Select All、Inspect Element、Reload 等。在主进程代码里搜索这些词时,建议使用带引号的完整匹配,例如搜 'Copy' 而非 Copy,避免匹配到变量名或函数名。

替换方式和系统菜单一致,找到 label 赋值即可。但要注意:如果某项菜单使用 role: 'copy' 且没有显式 label,Electron 会按系统 UI 语言提供默认文案。此时在中文系统上可能已经自动显示为"拷贝",不需要额外改。真正需要改的是那些 role 和 label 同时出现、label 硬编码为英文的项。

3.3 侧边栏与工作区导航:建立高频词替换表

侧边栏是界面文本最密集的区域之一。以我拿到的 Codex 桌面端版本为例,侧边栏包含工作区切换、新建会话、历史记录、设置入口等导航项。这些文本在渲染进程的 bundle 里,搜 Dashboard、New Session、History、Settings 等词就能定位。

这里强烈建议建一张替换表,而不是想起一个词改一个词。表格长这样:

原始文本 替换文本 出现文件 备注
Dashboard 仪表盘 renderer.js 侧边栏导航
New Session 新建会话 renderer.js 侧边栏按钮
History 历史记录 renderer.js 侧边栏导航
Settings 设置 settings.js 按钮与标题
Attach files 添加附件 chat.js 输入框附件按钮
Send message 发送消息 chat.js 输入框动作

把替换表维护好,后面写自动脚本时直接用这张表批量执行,比手动改稳妥得多。而且这张表能帮你控制替换范围——看到某个词在表格里没出现过,就不要顺手改掉,宁可漏掉也不要误伤。

4. 主界面汉化的核心区域:会话区、设置面板与状态提示

4.1 会话区:从 placeholder 到按钮文本的逐项替换

主界面最显眼的部分是会话区。输入框的 placeholder、发送按钮文本、会话标题、附件的提示文案,这些都是用户每次打开都会看到的。搜索关键词时建议从高频词入手:placeholder=、aria-label=、title= 这些属性往往藏着不少文本。

一个很实用的技巧:JS 的压缩代码里字符串可能同时出现在单引号、双引号和反引号中。比如同一类文本,可能在 A 处写作 "New Chat",在 B 处写作 'New Chat'。搜索时要注意覆盖三种形式,替换时也要三种情况都处理。

替换时还有一个容易踩的区分难点:同一串英文,可能在界面其他地方以内部 key 的形式存在,比如 id: "settings"、eventName: "settings"。这类内部标识符绝对不能改成中文,否则事件监听就找不到了。怎么判断?看上下文。如果字符串前后有 label、title、placeholder、textContent,基本可以放心改;如果前后有 key、id、type、event,不要动。

4.2 设置面板:最深的坑是那些不显眼的属性文本

设置面板的文本层级比会话区深得多,除了可见的标签,还有大量藏在属性里的辅助文本。title 属性决定鼠标悬停时的提示,aria-label 决定读屏器朗读内容,alt 是图标替代文本。这些区域漏改了,用户每鼠标悬停一次就看到一次英文,整体观感会瞬间打回原形。

处理设置面板时我习惯用 DevTools 远程调试配合操作:打开设置面板,逐项鼠标悬停、点击、展开下拉,把界面上出现的所有英文记录到替换表中,再去源码里搜索。

设置面板的下拉选项也值得注意,<option> 标签里的文本、select 的默认项、单选多选旁的说明文字,都属于硬编码文本。有些配置项在展开子菜单后才显示英文,不实际操作一遍根本发现不了。

4.3 状态提示与错误信息:能汉化的就汉化,不能汉化的先放过

状态提示、错误弹窗、加载提示这类动态文本处理起来要谨慎。比如请求失败时显示的 Request failed with status 500,前半段可以翻译成"请求失败",但 status 500 作为状态码最好不要动,否则排查问题时难以对应文档。

我的处理原则是:纯展示性的欢迎语、空状态提示、加载文案,直接翻译;混合了错误码、堆栈信息、变量占位符的报错信息,只翻译固定描述部分,保留参数和代码。汉化不是越彻底越好,保留一部分英文术语反而对日常开发排查更友好。

另外,错误信息经常是模板字符串拼出来的,源码里可能是:

JAVASCRIPT
`Failed to connect to ${host}: ${error}`

替换时把 Failed to connect to 改成 连接失败,保留后面的模板变量即可。=${host} 这类占位符如果被改动,程序很可能在渲染时直接报模板解析错误。

5. 重打包、签名失效与显示问题的排查链路

5.1 重打包命令与安装目录替换

改完文本后,进入最关键的步骤:把 app.src 重新打包替换原文件。

BASH
npx @electron/asar pack app.src app.asar

打包完成后把生成的 app.asar 放回 resources 目录。替换之前必须完全退出 Codex,Windows 下如果提示文件被占用,检查后台进程是否真的结束。

macOS 上这里往往会出现一个门槛:应用签名失效。Electron 应用分发时带开发者签名,修改 app.asar 后签名校验过不去,系统可能直接拒绝启动,或提示应用已损坏。常见的处理方式是重新签名:

BASH
codesign --deep --force --sign - "/Applications/Codex.app"

这是 ad-hoc 签名方式,没有 Apple Developer 证书也能在本机执行。签名完成后建议执行一次验证:

BASH
codesign --verify "/Applications/Codex.app"

Windows 下一般没有这么强的签名校验机制,直接替换文件启动即可。如果双击没有反应,建议打开终端手动运行可执行文件,看标准输出里有没有报错信息,这一步比任何日志分析都直接。

5.2 中文乱码、按钮溢出和快捷键失效的定向排查

替换完重新启动,最可能遇到三类问题:

第一类是中文乱码或界面出现类似 \u5370\u8c61 的转义字符。 这通常是因为原始字符串在 JS 里就是以 Unicode 转义形式存储的,你只改了可见的英文部分,没有处理转义片段。排查方法是搜索汉化时对应的英文词,再看相邻代码是否是 \u 开头。如果是,说明源码里本身就是转义存储,需要你把中文一并转成 Unicode 形式再替换,或者直接替换转义序列对应的中文。

第二类是按钮文字溢出。 中文字符宽度明显大于英文字符,一个 8 字符的英文按钮"Settings",换成"设置"两个字不会变窄,但"New Dashboard"换成"新建仪表盘"后按钮就可能放不下。Electron 应用布局是网页流式布局,大部分按钮会自动撑开,但碰到固定宽度或 white-space: nowrap 的样式时就会溢出。这种时候需要在 DevTools 里查看按钮的样式,到 CSS 文件里调整 min-width 或 padding。改动 CSS 的流程和改文本一样,改完重新打包。

第三类是快捷键提示消失。 如果原文本里有 &F、&S 这类助记符写法,替换成中文时要保留。同时留意快捷键本身是否绑定在菜单项上,accelerator: 'CmdOrCtrl+N' 这种属性不属于文本,千万不要动。

5.3 启动即崩溃时的二分回退法

如果重打包后应用一启动就崩溃,不要慌,几乎可以肯定是一次错误的字符串替换导致代码逻辑断裂。最典型的场景是:某个字符串同时被用作事件名、存储 key 或路由标识,你把它改了,内部代码找不到对应关系。

排查方法不要用"抓瞎式搜索",用二分回退法更高效:

  1. 将刚才做过的替换记录按文件拆成几组;
  2. 先把分组后的文件恢复为原始状态,只保留第一组的替换内容,重新打包测试;
  3. 如果能正常启动,说明问题不在第一组,继续加第二组;
  4. 加到某组后崩溃,问题就锁定在那一组对应的文件或字符串中;
  5. 在该组内逐条替换字符串,最终锁定"罪魁祸首"。

这一步并不复杂,但很多人在崩溃后直接全部还原,把之前的汉化工作推倒重来,然后在第二遍时又踩同一个坑。保留替换记录并做分组,是更专业的工作方式。

6. 让汉化成果可维护:脚本化重放与更新覆盖问题

6.1 用 Node 脚本把替换流程固定下来

手动汉化一遍之后,Codex 只要更新一版,所有改动就会全部被新版本覆盖。最有效的应对不是"每次重新手动改",而是把替换流程脚本化——解包后,跑一个脚本,自动完成所有文本替换,再重新打包。

下面是一个基础版本的替换脚本,实际使用时把 replacements 数组维护成你的完整替换表:

JAVASCRIPT
const fs = require('fs');
const path = require('path');
 
const replacements = [
['"Dashboard"', '"仪表盘"'],
['"New Session"', '"新建会话"'],
['"Settings"', '"设置"'],
['"History"', '"历史记录"'],
["'Attach files'", "'添加附件'"],
['"Send message"', '"发送消息"'],
];
 
function walk(dir) {
const entries = fs.readdirSync(dir, { withFileTypes: true });
for (const entry of entries) {
const full = path.join(dir, entry.name);
if (entry.isDirectory()) {
walk(full);
} else if (/\.(js|html|css|cjs|mjs)$/.test(entry.name)) {
let text = fs.readFileSync(full, 'utf8');
const original = text;
for (const [from, to] of replacements) {
text = text.split(from).join(to);
}
if (text !== original) {
fs.writeFileSync(full, text);
console.log(`已处理: ${full}`);
}
}
}
}
 
walk('./app.src');

这里我强烈建议替换表里写带引号的完整字符串,例如 '"Dashboard"' 而不是 Dashboard,原因前面已经说过了:不带引号会误伤大量内部标识符。使用 split 和 join 而不是正则表达式,是为了避免英文文本里的特殊字符(如 (、$)对正则表达式造成干扰,字符串替换是最粗暴也最安全的方式。

6.2 自动更新覆盖后的处理思路

Codex 桌面端默认开启自动更新。应用一旦更新,新的 app.asar 会覆盖你改过的版本,汉化失效。这个问题无法在文件层面彻底绕开,但有几条务实的处理路径:

一是优先看应用设置里是否提供关闭自动更新的选项,有的话直接关掉,保持当前版本稳定运行。

二是不要把汉化做成一次性手工活。沿用上面的脚本后,新版本发布时只需要三步:解包、跑脚本、重新打包。每轮花费五分钟,远低于重新手工改一遍的代价。

三是如果想长期保持汉化状态,又不想频繁手动操作,可以考虑只在确认新版本值得升级时才更新,而不是跟着每个小版本跑。

6.3 风险边界:哪些内容不该动

汉化过程中有几个雷区,踩到不只是界面问题,可能导致应用无法使用:

内部标识符不能改。 事件名、存储 key、API 路径、路由名、状态枚举值,看起来像英文,实际上是程序和服务器约定好的协议,改了以后轻则功能失效,重则登录态错乱。判断标准很简单:如果这个字符串在代码里还被另一个地方引用,那么它大概率不是纯展示文本。

远端内容不要试图本地 hack。 服务端返回的提示、模板变量、动态消息,本地改不了,界面该显示英文就显示英文,没有必要为了一两个词把请求结果拦截后再替换,那样做不仅复杂,还容易在版本更新时出现严重兼容问题。

错误堆栈和日志信息建议保留原文。 这类文本的价值在于排查问题,汉化后反而增加理解成本。处理原则是把面向用户的提示翻译好,面向开发者的信息保持原样。

另外说一句关于使用边界的个人看法:汉化补丁或者修改后的安装包,自己留着用就好,不要四处传播。应用本身有官方分发渠道,修改版传播既有违软件使用条款,也容易引入安全风险。自己动手折腾理解原理是一回事,把修改产物做成"绿色汉化版"分发是另一回事。

我自己跑完整条流程后最大的感受是:Electron 应用的汉化,真正考验人的不是替换字符串,而是判断"哪个字符串能改、哪个不能改"。把替换表维护好、脚本写好、基线和回滚手段准备好,一次汉化就不再是"一次性破解"式的操作,而是一条可以不断重放的工程流程。对 Codex 桌面端来说,这套思路短期内不会被官方更新击穿——即使被新版本覆盖,五分钟后又是一条好汉。如果你也正在折腾手头的 Electron 工具,建议从菜单栏先下手,那里往往是最容易见效也最容易建立信心的起点。