基于AI我做了一个适合程序员的Chrome翻译插件
基于AI我做了一个适合程序员的Chrome翻译插件"
写在前面
这是一篇关于"折腾"的记录。
我用业余时间做了一个叫 HalfTrans 的 Chrome 翻译插件——它不是用来和 Google 翻译、沉浸式翻译竞争的,初衷其实很简单:我想自己用得舒服,顺便学一点新东西。
文章不打算贴大段代码(仓库都开源了,源码自己看更直观),主要聊三件事:
- 为什么我会想做这么一个插件
- 它大致是怎么跑起来的
- AI 这部分到底是怎么"调"的
一、起心动念:为什么做这个东西?
1. 学习的冲动
说实话,学习才是最初的动力。
平时工作里用的技术栈是固定的,写久了会有一种"温水煮青蛙"的钝感。Chrome Extension、Manifest V3、Service Worker、Content Script——这些词我在工作中很少正经碰,但浏览器又是每天打开时间最长的软件。做一个自己每天会用的浏览器插件,比看一百篇教程都来得扎实。
再加上这两年 LLM 太热闹了,我也想真正动手把"调 AI"这件事玩明白——不是停留在"调一个 chat 接口、把结果打印出来"那种 demo 级别,而是把它放进一个具体场景里,去面对 prompt 怎么写、上下文怎么喂、结果不稳定怎么办、速率限制怎么应付这些真问题。
一句话:学习不一定要有目的,但有个落地的东西会更香。
2. 现有翻译工具,真的没让我舒服过
学习是借口,痛点才是真正让我动手的理由。
作为一个每天要读不少英文文档、博客、GitHub Issue 的人,我用过的翻译方案大概是这几种:
-
全文翻译(Google 翻译、有道、DeepL……)
把一切都翻译成中文,包括那些我每天都在用英文思考的词。读到"事件循环负责处理异步回调"这种句子,我会下意识地在脑子里翻译回event loop和async callback才能继续读下去。翻译没帮我减负,反而多了一道"再翻译回来"的工序。 -
不翻译,硬读
英文还行的同学可能没问题。但碰到长句、生僻领域、或者只是单纯想快点读完的时候,整段英文还是会让大脑变慢。 -
沉浸式翻译这一类双语对照
设计很好,是我用过最久的方案。但它本质上还是"原文 + 全文翻译"两份内容并列,眼睛要在两行之间来回切换,专业术语依然被翻译成了中文。
看了一圈,发现它们都在解决同一个问题:怎么把英文变成中文。
但作为程序员,我们想要的根本不是中文。
想想我们平时怎么和同事说话:
“这个 bug 是 race condition 导致的,在 event loop 的 next tick 才 resolve。”
中英混着说,专业词留英文,连接词用中文——这是程序员日常最自然、认知负担最低的表达方式。
那为什么翻译工具不能直接给我这种格式呢?
HalfTrans 就是为了回答这个问题。
把"event loop"留着,把"is responsible for handling"翻成"负责处理",把"asynchronous callbacks"留着——这样一段话扫一眼就懂,眼睛不用做翻译,脑子也不用做翻译。
| 翻译方式 | 结果 |
|---|---|
| 原文 | The event loop is responsible for handling asynchronous callbacks in Node.js runtime. |
| Google 翻译 | 事件循环负责处理 Node.js 运行时中的异步回调。 |
| HalfTrans | event loop 负责处理 Node.js runtime 中的 asynchronous callbacks。 |
这一刻我才意识到一件事:这种"半翻译"以前做不了,是因为传统机器翻译没有"语义判断"的能力。哪些词该留、哪些该翻,需要根据上下文和领域来判断——而这正是 LLM 擅长的。
时机到了,我就开干。
二、整体思路:这个插件是怎么跑起来的?
不画架构图、不贴代码,用大白话说。
一个 Chrome 插件,本质上是几个角色配合:
- 页面里的"探子"(Content Script):能直接接触你当前打开的网页,能看到 DOM、能改 DOM。
- 后台的"调度员"(Service Worker):常驻在浏览器进程里,负责跑请求、做协调。页面里的探子不能直接调外部 API(涉及跨域、密钥安全等问题),得通过它转一手。
- 设置和弹窗(Popup / Options Page):用户配置 API Key、术语表、风格偏好的地方。
HalfTrans 的工作流大致是这样的:
- 你按下快捷键或点击图标,告诉插件:开始翻译这个页面。
- 探子开始扫描页面,找出"看起来是英文段落"的元素:
<p>、<li>、<h2>这些。 - 关键:只翻译你能看到的那部分。一篇长文可能有几百段,没必要一次全翻——既慢又费 token。只翻视口里的,滚动到哪翻到哪。
- 打包送给后台调度员。一次性塞十几段给它,让它批量发给 AI,省请求次数。
- 调度员调用 AI 接口,拿到翻译结果,再发回页面。
- 探子把翻译结果插进原段落里,原文不删——这样你随时可以对照原文,也不会破坏页面原有的链接、按钮、样式。
整个过程里,我自己觉得最有意思的两个设计点是:
"视口优先"是怎么想到的?
最早的版本是一上来就翻全文。结果在长博客上一次性发出去几十个请求,要么把 API 速率限制打爆,要么用户等了好久还是空白。
后来想清楚了一件事:用户根本不需要"整页翻译好",他需要的是"现在屏幕上这段已经翻译好"。其他段落等他滚到那里再说就行。
这其实是个非常老的工程经验——别做用户不需要的工作。但要在产品设计里时刻提醒自己,并不容易。
不替换原文,而是"追加"翻译结果
最开始我想的是"翻译完了把原文换成中文"。后来意识到这样有几个问题:
- 原文里的链接、
<code>标签、行内样式全没了 - 用户想看原文校对一下都做不到
- 万一翻译翻砸了,页面看起来就乱了
所以现在的做法是:原段落留着,翻译结果作为一个 <span> 追加在原段落里。视觉上看起来就是"英文 + 中文"挨着出现,但 DOM 上原文完全没动过。
这个决定让后续很多事都变得简单:要不要显示原文?给那个 span 加个 CSS class 就行。要不要清掉翻译?把 span 删掉就行。完全不需要"还原页面"这种危险操作。
三、和 AI 打交道:这部分最值得聊
这是整个项目我学到最多的地方,单独展开说说。
调 AI,远不止"发个请求"
刚开始做的时候,我以为"调 AI 翻译"就是:
请翻译这段话:xxx
发给 OpenAI 兼容接口,拿到结果就行。
真做起来才发现,这只是 1% 的工作量,剩下 99% 都是在解决"怎么让 AI 翻得让我满意"。
第一关:怎么写 prompt,让它知道我想要什么
“半翻译"这件事,我自己说着很顺,但 AI 不知道什么叫"半翻译”。你得用它能理解的语言把规则讲清楚。
我现在的 system prompt 大致分成几个部分(用分隔符明确分段,让 AI 一眼就知道每段是干嘛的):
- 硬规则:什么东西绝对不能翻——代码标识符、API 字段名、命令、固定搭配(
HTTP request这种)。这是兜底,必须最优先。 - 强术语表:一份我内置的 80 多个常见技术术语(
event loop、closure、middleware、pod、schema……),告诉 AI 在技术语境下这些词通常保留英文。 - 用户词库:用户自己加的词。这部分优先级最高——你说要留就留,你说要翻就翻,不接受 AI 反驳。
- 指导原则:理解语义优先、不要逐词翻译、同一概念前后一致、长度不要超过原文 1.3 倍等等。
- 输出格式:用 XML 标签包住上下文和待翻译文本,让 AI 清楚哪部分是参考、哪部分是任务。
写到这里,我才理解为什么大家都说 prompt engineering 是一门玄学——它不像写代码有编译报错,你只能不停地试、不停地观察输出、不停地补规则。我前后改过差不多十几个版本,每次以为"这次稳了",结果第二天用又会发现一种新的翻车姿势。
第二关:上下文这件事,AI 比你想象的更需要
你给 AI 的就一个孤零零的句子:
The container manages the lifecycle.
它怎么知道这个 container 该不该翻译?是 Docker 文档里的 container(保留),还是 CSS 里的 container(可能翻成"容器"),还是某个 Java 框架里的 container?
没有上下文,AI 就靠猜。
所以 HalfTrans 在每次翻译时都会顺手把这几样东西也喂给 AI:
- 页面是什么:页面标题、所在的导航路径(比如"Kubernetes 文档 → Concepts → Workloads")
- 当前段落在哪个章节:让它知道这段话在文章里的位置
- 前后几段在说什么:让它知道上下文在讨论什么主题
- 周围有没有代码块:邻近的代码片段是判断技术领域的最强信号
效果是显而易见的——加了上下文之后,AI 对"这个词在这里到底算不算专有名词"的判断准确率明显上了一个台阶。
我后来意识到,很多人觉得"AI 翻得不准",其实不是 AI 不行,而是你给它的信息太少。你自己在脑子里读这句话的时候,是带着对整篇文章的理解去读的;你只把一句话扔给 AI,等于要求它在没有上下文的情况下做得和你一样好——这不公平。
第三关:批量翻译,省钱也省心
每翻一段调一次 API,几百段就是几百个请求。慢、费钱、还容易被限流。
办法是把多段塞进一个请求里,让 AI 一次翻完,再用一个分隔符(我用的是 [SEP])把它们分开。
听起来简单,做起来有坑:不同的 LLM 对"用 [SEP] 分隔"这个指令的遵守程度参差不齐。有的稳如老狗,有的会偷偷换成换行,有的会忘了加。
所以我做了一个三级保底策略:
- 首选:按
[SEP]切,段数对得上就用。 - 次选:按双换行切,段数对得上就用。
- 兜底:上面都失败了?降级成"一段一段单独翻",慢一点但保证每段都翻得到。
这个设计后来给我上了一课:和 AI 协作的代码里,你永远要假设它会犯错,写代码的时候就要把"它没按格式输出"这件事当成一种正常状态来处理。这和我们以往写"调用 RESTful 接口拿 JSON"的心态完全不一样——传统接口你信它,AI 接口你不能全信。
第四关:并发,不能太贪心
视口里十几段、批量打包之后变成几个请求、几个请求又同时往外发——一个长文档轻轻松松就能把 API 速率限制(429)打出来。
我的做法是滑动窗口:最多同时跑 3 个批次,跑完一个再放一个进来。
数字是拍脑袋拍出来的,对绝大多数 API 都够用,也不会让用户等得太焦虑。这就是工程——理论上的最优 ≠ 体感上的最优。
四、做完之后的一些感受
关于"做一个东西"
我以前总觉得做一个完整的产品很麻烦,一直停在"想做"的阶段。这次硬着头皮做下来,才知道很多东西想 100 次不如做 1 次。
做的过程中你会自然遇到一堆原本想不到的问题——Service Worker 怎么休眠、innerText 为什么会把翻译结果也读进去、Manifest V3 的权限怎么配——这些都是你不动手永远不知道存在的细节。
关于 AI
调 AI 这事,写 prompt 比想象中要工程化得多。
它不像玄学,更像在写一份"特别能容忍歧义的需求文档"——你越能把规则写清楚、把例外说明白、把格式定死,AI 就越靠谱。
而那些 AI 翻得不好的地方,大多数时候不是模型的问题,是我没把话讲清楚。
关于这个插件
它现在不完美。术语判断偶尔翻车、跨段一致性还不够好、流式输出没做、缓存还没加……但它已经是我每天都在用的东西。
能解决自己问题的工具,做得糙一点也没关系;做不了的工具,做得再精致也没用。
如果你也受够了"事件循环"和"依赖注入",欢迎来试试。仓库地址在文末,MIT 协议,随便折腾。
效果图

开源地址:https://github.com/huolihua123-crypto/HalfTrans
技术栈:React 18 + TypeScript + Vite + Chrome Extension Manifest V3
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)