Back to directory

dsh-inpage-translator

Maintenance: Active

7gzgn/dsh-inpage-translator

Put a translate button into the page the agent is looking at: one click replaces every visible text node in place, another click restores the original. Long pages are chunked, repeats are translated once, and the host LLM service does the work (the plugin holds no API key).

View on GitHub
$ dsh plugin add dsh-inpage-translator

Install

dsh has no central install command — add this plugin’s entry (documented in its README below) to your profile or patch config, then restart.

How installs work

0

stars

0

forks

JavaScript

Language

MIT

License

2026-10-16

Created

2026-10-17

Last push

README

dsh-inpage-translator(整页翻译)

你把页面给 AI 看,它替你把整页翻成中文。

按钮是常驻的:插件后台每 1.5 秒扫一遍所有活着的浏览器会话,自动把「🈯 译」放进每一张有内容的页面 —— 你不用先叫 AI 做事,看到它点一下,页面上每一个可见文本节点原地换成译文;再点一下,回到原文。 页面导航了?下一次巡场会把按钮重新装上去。

按钮可以长按拖动(按住 320ms 进入拖拽),拖到左右边缘 48px 内会自动吸成贴边的小把手,点一下再展开 —— 它不会一直挡着你的正文。

它是第 8 个作品,也是第一个"动别人的网页"的插件: 前 7 个动的是本机、文件、窗口、发布链路,这个动的是一张陌生的页面 —— 所以两条边界写在最前面:只改文本、只加内联样式;插件自己不持有任何 API key。


它做什么

步 动作 细节
① 找会话 枚举所有活着的浏览器会话,自动挑最活跃、有页面的那一个(session 参数可显式指定)
①' 后台巡场 每 1.5 秒扫一遍:补按钮(导航后重装)+ 读点击(点了就翻,不用等工具调用)—— 这就是"常驻"
② 装浮层按钮 内联样式 + position:fixed + z-index:2147483647,不注入 <style>(页面 CSP 管不到它)
③ 打标 TreeWalker 遍历文本节点,可译的父元素记下原始文本与编号(data-dsh-pt="12")
④ 等点击(action=install) 每 1.2 秒读一次页面状态,默认等 60 秒;用户点了就在同一次调用里翻好
⑤ 分块送翻 相同字符串只翻一次;翻过的串按「语言+原文」缓存(同页翻第二次几乎瞬时);分块 3 路并行;译文由宿主 LLM 服务生成
⑥ 回填 按编号写回 textContent,并记下译文(再点按钮即还原)
⑦ 还原 点按钮或 action='restore' → 全部回到原文

回执里一定有:送翻条数、回填条数、模型调用次数、送翻字符数与本次上限、唯一串数、 缓存命中数、超限丢弃段数、兜底原文条数、跨域 iframe 个数,以及任何一条警告。不编数字,不静默丢。

已知边界(先说清,再说好用)

边界 说明
不是原生工具栏按钮 内置浏览器的工具栏是宿主硬编码的 HTML(dsh-builtin-browser/host-main.js:123 的 TOOLBAR_HTML),宿主不读任何外部字段 —— 第三方插件没有加工具栏按钮的扩展点。所以按钮做进页面里。
导航后按钮会消失(但位置会记住) 注入本身是运行期一次性的(宿主没有 addScriptToEvaluateOnNewDocument 这类通道)。v0.2 起由后台巡场自动重装;位置按站点记在 localStorage(dsh-ipt-pos),导航/重开都落回原处。
跨域 iframe 翻不到 只能进当前文档的 DOM。回执会明确报「这一页有 N 个跨域 iframe 没翻」,不静默跳过;同源 iframe 里的文字目前也不在整页翻译范围内。
只翻可见文本节点 图片、background-image、canvas 里的文字不动;<input> / <textarea> 的值不动。
字符上限默认自适应 maxChars 不传时按页面唯一文本量自适应(4000 ~ 24000 字符):小页面不被预留浪费、大页面不被 6000 硬截断;超出自适应上限的部分如实回报并跳过。给正数则按该值硬截断。
文本节点被包了一层 <span> 元素上只打标、不重排子树(span 用 display:inherit),但页面自己重写这段 DOM 时译文可能被覆盖 —— 那时重跑一次即可。

它依赖什么

  • 内置浏览器插件(ctx.get('browser'))—— 没有时明确报错,不会假装能用。
  • 宿主的 LLM 服务(ctx.get('llm'))—— 翻译由它完成,插件不读任何 API key、不直接调任何 HTTP。 调用形状与宿主里已验证的两个先例一致(dsh-meme 的识图、dsh-better-sidebar 的提交信息): resolveCallConfig → prepareCall → stream。

安装

市场收录后在 DSH 里搜 dsh-inpage-translator 安装。手动安装:

New-Item -ItemType Junction -Path "$env:USERPROFILE\.dsh\profiles\desktop\node_modules\dsh-inpage-translator" -Target "D:\dsh\projects\dsh-inpage-translator"
# profile 的 package.json:dependencies 加 "dsh-inpage-translator": "link:D:/dsh/projects/dsh-inpage-translator"
# 并把 "dsh-inpage-translator" 加进 dsh.profile.bundles(漏了不加载)→ 重启 DSH

用法

translate_page action=install        # 确保按钮在,并等用户点(默认等 60 秒)
translate_page action=translate      # 不等点击,立刻翻
translate_page action=restore        # 回到原文
translate_page action=status         # 只读状态(含活会话清单)
translate_page action=sessions       # 只读:列出所有活着的浏览器会话

v0.2 起 install 不再是必须的前置步骤:后台巡场会自动把按钮装到每张有内容的页面上。 install 现在只用于"想在同一次调用里等用户点一下"的场合。

可选参数:target(zh 默认 / en / ja / ko / ru)、 mode(replace 默认 / bilingual 双语对照)、maxChars(不传 = 自适应)、waitSec、 session(不传就自动用最活跃的那个会话)、provider、model。

怎么点这个按钮

手势 结果
点一下 翻当前页面(原文 → 译文);已经是译文时点一下回到原文
长按 320ms 进入拖拽:松手前拖到哪,它就停在哪
拖到左右边缘 48px 内 自动吸成贴边的小把手(只留 🈯,不挡正文)
点一下贴边的小把手 展开回正常胶囊形状(不触发翻译)
拖过一次之后 位置按站点记下来(localStorage),导航/重开都落回原处

拖拽时松手那一下不会误触发翻译 —— 这是"长按才拖"的设计目的。

典型流程:

  1. AI 打开你要读的页面(浏览器窗口就在你眼前)——按钮已经自动在上面了;
  2. 你点右下角的「🈯 译」;
  3. 大约一两秒,页面就变成中文了;
  4. 想对照原文,再点一下按钮(或让 AI 调 restore)。

三条设计取舍(都被现场逼出来的)

  1. 按钮为什么在页面里、而不是工具栏里 宿主工具栏没有第三方扩展点(见上表),而"往页面里放一个浮层"是宿主自己就在用的姿势 (dsh-builtin-browser 的 virtual-cursor.ts:69-81 就是这么画那个虚拟光标的)。 零宿主改动、可随时卸载、升级不会被打回。

  2. 按钮点击为什么由插件处理,而不是页面自己翻译 页面里没有到宿主的 bridge(也没有凭据,也不该有)。所以按钮只写一个请求标记 (data-dsh-pt-req),插件轮询读到后去翻译 —— 这样凭据永远留在宿主侧。

  3. 为什么"相同文本只翻一次",还要按串缓存 网页里菜单/按钮/表头经常重复几十次。去重发生在限额之前, 所以同样的字符额度能覆盖的页面比不去重时大得多。 v0.2.2 又加了一层跨调用缓存:翻过的串在进程内记住(按「模型路线 + 语言 + 原文」,LRU 4000 条), 同一页面翻第二次、或另一个页面有相同的串,都不再请求模型。 键里带模型路线而不带 llm 对象 —— 宿主每次给到的 llm 不一定是同一个对象,按对象分桶会永远不命中(真机踩过)。

  4. 为什么会话是"枚举"出来的,而不是靠 open() 传进去(v0.2 的关键改动) BrowserProvider.open(label) 的语义就是每次新开一个会话,服务也没有 listSessions 之类的接口 —— 插件根本拿不到"用户正在看的那一页",上一版就卡在这儿:按钮装到了另一个窗口。 v0.2 改成只读地枚举运行时里真实存在的会话表(runtime.providers → provider.sessions), 严格 try/catch:宿主内部结构一变就当作"没发现",退回 open() 的旧路径 —— 绝不因为读了一层私有结构,就把插件弄崩。

安全与隐私

  • 不发外部请求:插件自己只做两件事 —— 在页面里跑脚本、调用宿主 LLM 服务; 没有任何 fetch、没有本地 HTTP 端口、没有凭据读写。
  • 不改页面结构以外的东西:只写文本、只加内联样式与三个 data-dsh-pt* 属性; 位置记忆只写 localStorage 的一个键(dsh-ipt-pos,存 left/top/edge,不存页面内容)。
  • 翻译内容会经过你本机配置的模型(DSH 的 provider)—— 这是这类功能的固有代价, 页面上若有敏感信息,请自行判断要不要翻。

测试

node test/smoke.mjs        # 75 项:纯逻辑 / 页面脚本 / 编排层(假 LLM)/ 驱动层(假浏览器与假页面)/ 会话发现与后台巡场 / 输出 schema 对齐

测试不联网、不碰真实浏览器、不读任何凭据。 在活代码树(D:\dsh\projects\dsh-inpage-translator)里跑会直接退出, 请先复制到 D:\dsh\sandbox\ 再跑(避免在真宿主上生效)。

许可

MIT

AD

DSH Plugins is an independent community directory of DeepSeek Harness plugins. Not affiliated with or endorsed by DeepSeek. Third-party plugins are not security-audited — review the source before installing.

New DeepSeek Harness plugins, weekly. No spam.