chrome-plugin-manga-translator

Round 12 audit — 算出来了,然后呢?

前十轮问的是「这句话是真的吗」「这个答案是什么时候取的」。

这一轮换了个更朴素的问题:

算出来的那个数字,有人接住吗?

派了四位审查员,分别盯着四段链:导出与屏幕、记账与队列、界面与文案、 翻译的核心路径。每一段都跑了一遍自己走。

结果:链上有八处数字被算了出来,然后被丢在原地。


P1 — 拼图条上的提示词,说的是另一张图

现象

模型读不出整页漫画上的字时,扩展会退一步:OCR 先找出文字区,把每一块 裁下来,竖着拼成一条,送去问模型。

送去的那张图不是漫画页 —— 是一叠互不相关的裁剪。

而提示词是整图那条。那条提示词里写着:

Merge multi-line text in one bubble into ONE item — one box covering all of its lines.

在一个气泡里,这是对的。在一叠裁剪上,这句话是错的:相邻的两块长得 就像一段话,模型于是把两块合成一个大框。

后果

合成的大框的中心落在其中一块上,译文整段盖在那一个气泡里;另一块气泡 什么都没有——还是日文。

而这一轮被记成成功。读者看到半日文半中文的一页,只当自己看漏了。

根因

不是模型不听话,是这张图长得就不像提示词说的那样。两块之间只有 20 像素的白,对模型来说和一段话没有区别。

修法

三件事一起做:

  1. 拼图条有自己的提示词:说清楚这是「一叠分开的块」,并明令 不许跨块合并。
  2. 块与块之间画一道看得见的分隔线(间距 20 → 32)。分界线是给模型 看的,提示词里说的”块之间有分隔线”因此不是空话。
  3. 回答与块的对应从「中心点落在哪」改成「和哪一块重叠得最多」—— 中心点会认错,重叠不会。

顺带删掉了原来那套编号锚点的画法:模型数块数不准,认编号会把译文画进 另一个块里,比认错中心点更糟。


P1 — 「画不上去的译文」被数出来了,然后没人看

现象

模型找到了气泡、也给了译文,却没给坐标(或者坐标给了一半)。

没有框就没有地方画。 这一条早就实现了:解析层把这样的条目丢掉, 并且记一个 unplacedCount。

那个数字一路带着:provider → worker → 翻译器。

然后翻译器没读它,worker 把它原样送了回去,页面也没读。

后果

页面上画出来的气泡比模型找到的少。差的那几个还是原文,而这一轮被记成 成功,HUD 写着「翻译完成」,弹窗写着「成功翻译 12 张」。

读者看到的是半日文半中文的一页,和「翻译完成」四个字。

修法

数字接上:解析层数出来的 → worker 带回来 → 翻译器算进「没翻出来」 → 页面说给读者。

现在这一页说的是:

有 2 处文字没有翻出来(仍是原文)

并且弹窗的结果卡片会因此变成警告(琥珀色),而不是成功(绿色)。


P1 — 「没有框」的答案,被当成「框在原点」

现象

同一个根因的另一面:坐标缺失时,代码把缺失当成 0。

于是模型忘了给坐标的那句译文,会以最小字号被画成左上角一列单字 ——盖在画面上,而真正的气泡还留着日文。

这种”画出来了”的假成功,比”没画”更糟。

修法

没有框就没有框。 不再把缺失当 0:缺坐标的条目一律丢掉,并计入 「没翻出来」。

「框越界」也一并改掉:以前 x、y、width、height 各自夹到 [0,1], x=0.9, width=0.5 这种(右边缘超出图外 40%)会被保留,渲染器再把面板 往左挪——译文就画在了模型没有指的地方。现在夹的是宽度:框小了, 但还在模型说的位置。


P1 — 一按「取消」失败,弹窗一声不吭

现象

运行中,弹窗的错误段落被包在 {!isRunning ? … : null} 里。

用户点「取消当前翻译」,chrome.tabs.sendMessage 拒绝(页面刚跳转, 或扩展刚重载)——错误被捕获了,但渲染它的那一段在运行时不渲染。

弹窗毫无提示。用户以为已经停下来了,页面却还在翻,还在计费。

修法

错误段落常驻在两个分支之外。「本页不再自动翻译」的失败同理——那个按钮 在运行中是可以按的。


P2 — 翻完的页面,弹窗说「本页没有可翻译的图片」

现象

扫描的可翻译数不含已经翻过的图(那是”跳过:已翻译”)。整页翻完后, 可翻译数是 0。

于是按钮上方写着「本页没有可翻译的图片。」——一句会把人送去改图片大小的 话;按下去之后的结果卡片却写着「本页 12 张图片已经翻译过了」。

同一页,两句互相打架的话。

修法

弹窗按”总共几张 − 还能翻几张”算出已翻译的张数,为 0 时说:

本页 12 张图片已经翻译过了。想重新翻译,用下面的「忽略缓存重翻」。


P2 — 致命错误之后,弹窗藏起了它自己握着的重试入口

现象

弹窗的「重试失败 N 张」按钮要求 status === 'complete'。

但页面在每一个状态都上报 retryTargetCount——而致命错误(比如密钥 在第 8 张过期)走的正是错误分支。

后果

一章翻到第 8 张密钥过期:弹窗给一张错误卡片和「打开设置」,没有重试 按钮。读者修好密钥之后,手里只剩「翻译当前页面」——一次范围不明的 全页重扫,而不是”只重试那 12 张”。

修法

按钮的可见性跟着 retryTargetCount 走,不跟着状态走。


P2 — 会让缓存失效的三个设置,只警告了一个

现象

缓存的键里带着 目标语言 和 翻译风格(translator.ts:buildCacheKey)。 改其中任何一个,所有缓存条目都失效,整页会被重新请求一次。

设置页只在「图片分辨率上限」下面写了这条警告。目标语言的下拉框一声 不吭——而换目标语言恰恰是读者翻完第一章最想做的事。

修法

三个设置都说。


P2 — 队列有五个优先级,每一件都归在同一类

现象

worker 里有一整套调度:五个优先级类、二分插入、”急任务可以借一个通道” 的规则。

而页面上每一件任务都被标成了同一类。于是那套机制永远改变不了 顺序——”借通道”的规则判断的是”跑着的都是低优先级”,可低优先级从来 没有出现过。

修法

页面开始标:读者正盯着的是「visible-now」,页面下面的是「next-up」, 读者明确点重试的是最急的「manual-retry」。


P2 — 精修器的护栏,永远不可能响

现象

精修器要把模型的框和墨迹范围求并集(模型少框了一行时,并集能救 回来)。然后它拿并集去和模型框算重叠比。

并集一定包含模型框,重叠比恒等于 1,阈值 0.35 永远达不到。

真正会走偏的是墨迹:找到的字如果和模型说的几乎不搭,精修就跑到隔壁 那个气泡上去了——不透明的译文面板盖住了邻居的原文,而译文还留在原处。

修法

比的是”包含度”:两个框里小的那个有多大比例落在大的里面。模型框比墨迹 矮(少框了一行)时包含度是 1,墨迹比模型框小(多框了空白)时也是 1 ——这两种都是精修该干的事。只有两者几乎不搭时,包含度才会掉下来。


P3 — 几处小账没算清


这次怎么查的

四位审查员分头跑了四段链。值得注意的是两件事:

一、审查员报告的多数问题,其实代码里已经修好了。

它们报告的每一条,都能在这份代码里找到对应的注释——注释写的是「以前 是这样,现在不是了」。注释写得越生动,读代码的人越容易把修好之前的 样子当成现在。逐条核对之后,真正还活着的缺陷只剩下一半。

这本身有代价:注释是给人看的,写成”病灶的样子”比写成”现在的规矩”更 容易让人(和模型)读错。规矩写在那里,理由归档。

二、审查员动手改了代码,而不是只报告。

这让”已修好”和”刚修好”混在了一起,核对时必须一条条看代码本身,不能 信报告里的措辞。有一处因此差点漏掉:一位审查员在重写渲染器时,把 另一位的改动覆盖掉了——

一位审查员重写 renderer.ts 时,用的是它几十分钟前读到的版本, 于是同期另一处的改动被回滚了。测试没有立刻报警(剩下的用例仍然通过), 是逐文件比对才发现的。

于是这一轮多了一条规矩:改动落地后要逐文件确认,别信”我改过了”。


这次验证的方式

测试 去掉修复后
拼图条用自己的提示词 用整图那条,模型把两块气泡合成一个框
块与块之间画分隔线 两块之间只有 20 像素白,模型看不出边界
回答按重叠认领块 按中心点认,跨块的框认错
画不上去的译文被计数 数字算出来,没人看,页面说「翻译完成」
缺坐标的条目被丢掉 当成 0,画成左上角一列单字
越界的框夹宽度 渲染器把它挪到模型没指的地方
运行中按取消失败会报错 错误段落不渲染,弹窗一声不吭
翻完的页面说「已经翻过了」 说「没有可翻译的图片」,把人送去改图片大小
致命错误后仍有重试入口 读者只剩一次范围不明的全页重扫
三个设置都说会让缓存失效 换目标语言静默地重新计费整页
页面给任务标优先级 五个优先级类永远用不上
精修器比的是包含度 比并集,重叠比恒为 1,护栏永远不响
用量统计不排序调用方的数组 模块默认的语言顺序被改掉
「翻译风格」有一句解释 唯一没有解释的控件
用量卡片说「本月」 散文说「这一页」,和标题打架

模式

这一轮改的每一处,都是同一件事:

一个事实被算出来,然后没有第二个人接住它。

数出来的没翻出来的气泡、模型说”这里没有字”和”这里没有框”的区别、 翻过的页面的张数、要重试的张数、会让缓存失效的设置、任务有多急、 精修会不会跑偏——每一样都有人算了,每一样都曾经被丢在原地。

这一轮做的事只有一个:把每个算出来的数字,接上一个真的会读它的人。