不改 Redis Insight 原有代码,把它汉化了:DOM 整段匹配 + XHR 拦截 + asar 重打包

不改 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,流程是:

  1. 第一次注入时备份 app.asar.bak,连同 app.asar.unpacked 一起备份;
  2. 每次注入都从备份重新解包 ,写入钩子和词典,再用 @electron/asar 打包回去。所以重复执行结果一致,切换简繁也只是换一份词典重新生成;
  3. 打包完核对 unpacked 文件集合:better-sqlite3、keytar 这些原生模块必须放在 asar 外面,少一个应用就启动不了。集合和原始的不一致,就自动回滚;
  4. 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 安装。


相关推荐
flash俊杰13 分钟前
v2.34-02 静默截断:10,000 个会话与 500 条消息是怎么被发现的
后端
vaskilab22 分钟前
Agent 上线后翻车的 6 种死法:一个 20 万人规模项目的四版重构实录
后端
鶴哥只手遮天1 小时前
《rustc 内幕》第 1 章 全景:rustc 编译流水线与 crate 版图
后端·rust·开源
starzy19901 小时前
深入理解Scala隐式值:编译期自动装配
开发语言·后端·scala
时间的拾荒人2 小时前
Redis 哨兵(Sentinel)详解:原理、选举机制与实战部署
redis·面试·架构·sentinel
黄连木笛2 小时前
Redis Insight 汉化教程:Windows / macOS / Linux 设置中文界面(附繁體、教程汉化、关闭自动更新)
数据库·redis·缓存
装不满的克莱因瓶3 小时前
设计模式之策略模式核心概念和应用场景
java·后端·设计模式·架构·系统架构·策略模式
特立独行的猫A9 小时前
用仓颉语言 cj-tauri 框架,五分钟给娃做了个鸿蒙形近字游戏
java·后端·python
码事漫谈12 小时前
我花了一周时间,看AI公司怎么"管"AI,结果越看越后背发凉
后端