不改 Redis Insight 原有代码,把它汉化了:DOM 整段匹配 + XHR 拦截 + asar 重打包
Redis Insight 是 Redis 官方的桌面可视化工具,界面只有英文。3.8.0 的源码里其实已经接入了 i18next,可语言包只有 en 和 bg(保加利亚语)两份,「语言」设置项还被 dev-language 这个开发者开关挡着,灰度比例是 0。也就是说,短期内官方中文是指望不上了。
所以我写了 redis-insight-chinese:不修改 Redis Insight 原有的任何代码或字符串,只在 app.asar 里加几个运行时钩子。 这篇讲讲实现上的几个关键点。
1. 为什么不直接改 bundle 里的字符串
最直观的汉化方式,是在打包后的 JS 里全局替换英文字符串。这么做有两个硬伤:
- 误伤代码 :
"Use"换成"使用",结果startsWith变成了starts使用,应用直接白屏; - 大部分文案没走 i18n :Redis Insight 是边做边迁移的,很多文案还硬编码在组件里,有的走
i18n.t(),有的直接写在 JSX 里,替换的规则很难统一。
我换成了在渲染结果这一层做:不管文案从哪来,最终都会变成 DOM 里的文本节点。只要在 DOM 上做替换,代码本身一个字都不用动。
2. 渲染进程:MutationObserver + 整段精确匹配
注入程序在 dist/renderer/ 写入钩子脚本 ri-chinese.js,然后在 index.html 里加一行 <script>。钩子做的事情是:
js
const observer = new MutationObserver((muts) => {
for (const m of muts) {
if (m.type === 'characterData') doText(m.target);
else if (m.type === 'attributes') doAttrs(m.target);
else m.addedNodes.forEach(walk);
}
});
observer.observe(document.documentElement, {
subtree: true, childList: true, characterData: true,
attributes: true, attributeFilter: ['placeholder', 'title', 'aria-label', 'alt', 'label'],
});
(以上是示意代码,真实实现见仓库里的 src/hooks/ri-chinese.js。)
关键在于只做整段精确匹配 :一个文本节点的完整内容等于词典里的某个 key,才替换。Delete 会被翻译,Delete key "user:1" 不会被拆开乱翻。这样能保证:
- 不会碰用户的键名、键值、命令输出;
- 不会改坏 Monaco 编辑器、输入框里的内容。
整段匹配解决了大部分问题,但还剩三类特殊情况。
带占位的模板。 比如 Delete {{count}} keys,对应 i18next 的插值,词典里存成模板,编译成正则去匹配。这里有个坑:数量类占位({{count}}、{{total}} 这类)只允许匹配数字,否则 {{count}} databases 会把 Avoid using logical databases 这种普通句子也吞进去。
拼接句里的短词。 React 经常把一句话拆成几个文本节点,比如 1、of、17。of、now、entries 这些词太短,绝对不能全局替换。所以单独写了一份上下文规则 fragments.json:看前后兄弟节点是什么,再决定译成「1 / 17」「(0 条)」还是「刚刚」。
中日韩汉字的字形。 简繁共用的汉字,在不同的 lang 下字形不一样。钩子会把 <html lang> 设成 zh-CN 或 zh-TW,让浏览器选对字体。
3. 内置教程:拦截 XHR,在渲染前改 Markdown
Explore 面板里的教程,不在安装包里。Redis Insight 运行时从 GitHub Release 下载 data.zip,解压到 ~/.redis-insight/tutorials,再用 axios 去请求 static/tutorials/**.md,在前端渲染成页面。
渲染后的教程 DOM 结构很碎(标题、列表、行内代码、链接混在一起),在 DOM 层做整段匹配效果很差。更好的时机是在 Markdown 源文本到达渲染器之前 就替换掉。axios 底层用的是 XHR,所以钩子包装了 XMLHttpRequest.prototype.open,对匹配教程 URL 的请求,把 responseText 和 response 换成带翻译的 getter:
js
XP.open = function (method, url) {
const xhr = this;
if (TUT_URL_RE.test(String(url))) {
const tr = (v) => (xhr.readyState === 4 && typeof v === 'string')
? MD.translate(v, TUT).text : v;
Object.defineProperty(xhr, 'responseText', { get: () => tr(dText.get.call(xhr)) });
Object.defineProperty(xhr, 'response', { get: () => tr(dResp.get.call(xhr)) });
}
return origOpen.apply(this, arguments);
};
MD.translate 用的是一个 Markdown 段落切分器(md-segments.cjs):按标题、段落、列表、表格、引用块切成段,以整段为 key 查词典。词典的 key 是英文原文,所以远端教程改了哪段,哪段就是新 key,只有那段会显示英文,其余照样是中文,而且不用重新注入。
代码块是个特例。教程里的 Redis 命令块长这样:
redis
FT.CREATE idx:bicycle // idx:bicycle name
ON JSON PREFIX 1 bicycle:
// 后面的注释也需要翻译。这样改不会影响命令运行,因为 Workbench 执行前会把注释剥掉。命令本身、键名、JSONPath 则原样保留。
同一套切分器还给翻译流程用:抓取新版教程、切段、和上一版做 diff,只翻新增的段落,再按规则校验(行数不变、行内代码一个不少、链接地址不变......),不合格就拒收。
4. 主进程:原生菜单、对话框,以及 & 助记符
「窗口 / 视图 / 帮助」这些菜单和确认框,是主进程用 Electron 原生 API 画的,DOM 钩子管不到。注入程序在主进程入口 main.js 最前面插一行 require,包装 Menu.buildFromTemplate 和 dialog.showMessageBox。
菜单有个细节:label 里的 &Close 表示 Alt+C 快捷键。直接查词典会查不到,所以先剥掉 & 再查(&& 是字面量的 &,不能剥)。查到之后,按 Windows 中文界面的惯例补成 关闭(&C),显示为「关闭(C)」,Alt 快捷键照样能用。

顺带一提,这个主进程钩子还承担了「关闭自动更新」的功能:加 --no-app-update 时,它会在 Redis Insight 读取更新地址之前,把 RI_MANUAL_UPGRADES_LINK 指向一个不可达的本机地址,并设置 RI_DISABLE_AUTO_UPGRADE=true。这样更新检查只会静默失败,不弹窗。
5. 处理 app.asar:备份、幂等、原生模块校验
钩子要写进 resources/app.asar,流程是:
- 第一次注入时备份
app.asar.bak,连同app.asar.unpacked一起备份; - 每次注入都从备份重新解包 ,写入钩子和词典,再用
@electron/asar打包回去。所以重复执行结果一致,切换简繁也只是换一份词典重新生成; - 打包完核对 unpacked 文件集合:
better-sqlite3、keytar这些原生模块必须放在 asar 外面,少一个应用就启动不了。集合和原始的不一致,就自动回滚; restore直接拿备份覆盖回去。
6. 简体转繁體:OpenCC + 修正表
繁體译文不是手翻的。先用 OpenCC(cn → twp,附带台湾惯用词转换)生成,再按一张修正表把 IT 术语改成软件界面的惯用说法,比如 控制臺→主控台、名稱空間→命名空間、消費者組→消費者群組。修正表按长词优先,一次扫描完成,不会连锁替换。
7. 译文热更新
词典放在仓库的 locales/ 下。默认分支每次改动词典,CI 就把全部译文和钩子打成 ri-update.json.gz,推到 update-data 分支。用户每次运行注入程序时,会同时从 GitHub raw 和 jsDelivr 拉取,取最新的一份缓存到本机。所以译文修好就能拿到,不用等工具发版。
程序自更新也是类似的做法:GitHub 直连和镜像同时下载,谁先完成用谁,按 Release 附带的 SHA256SUMS.txt 校验后再替换自身。
用法
bash
# 先完全退出 Redis Insight
npx redis-insight-chinese # 注入(支持 3.x)
npx redis-insight-chinese --lang zh-TW # 繁體中文
npx redis-insight-chinese restore # 还原英文
也可以去 Release 下载单文件可执行程序(无需 Node),或者用 Scoop、Homebrew 安装。
- GitHub:github.com/hlmd/redis-...
- Postman 汉化:github.com/hlmd/postma...