- 桌面应用
- OCR
【免费下载链接】MORT
MORT 번역기 프로젝트 - Real-time game translator with OCR
本文基于 MORT 仓库中的 chrome-llm-bridge.md 整理并深入展开:它讲解如何把 Chrome 桌面版内置的本地 AI(Translator API 与 Prompt API/LanguageModel)作为 MORT 游戏实时 OCR 翻译器的一种"无 API 密钥、纯本地"翻译后端。读完后你将掌握这套"进程内本地服务器 + WebSocket 桥接 Chrome 应用窗"的完整架构、双引擎(translator / llm / auto)选型逻辑、模型下载与语言包机制、/translateHTTP 通道的手动接入方式,以及全部高级配置项的默认值与行为边界,并能对照仓库源码理解每一处设计决策背后的约束。
一、整体架构:MORT 进程内服务器 + Chrome 桥接窗
Chrome 里内置了运行在用户设备上的翻译模型与 LLM。在 MORT 的翻译方式中选定"Chrome 本地翻译器"后,翻译即走这条完全本地的路径:不需要 API 密钥,不依赖互联网,所有推理都发生在用户自己的 PC 内。整体链路如下:
MORT ──▶ 本地服务器(MORT 进程内) ──WebSocket──▶ Chrome 桥接窗 │ Translator / LanguageModel API │ MORT ◀────────────────────────────────────────┘这里有一个重要的架构事实:没有独立的桥接可执行文件。服务器运行在 MORT 进程内部,MORT 退出时服务器与桥接窗一起关闭。源码中这一点在 ChromeBridgeService.cs 的注释里写得很直白:"크롬 창은 MORT가 죽여도 남지만, 서버가 없으면 그 창은 아무것도 하지 못한다"(即使 Chrome 窗还在,服务器没了那个窗什么也做不了)。因此 MORT 停止时会主动向标题匹配的桥接窗发送关闭消息——只关那一扇窗,不杀 Chrome 进程,用户正在看的其他标签页不受影响。窗口查找与关闭的实现在 ChromeBridgeWindow.cs:它枚举chrome进程的窗口、按标题"MORT 크롬 브릿지"/"MORT Chrome Bridge"前缀匹配,然后仅对该句柄PostMessage(WM_CLOSE)。
二、为什么必须开一个 Chrome 窗:两个硬性约束
这是理解整个方案的前提。Chrome 的内嵌 AI API 有两条外部程序无法绕过的限制:
- 不能在 Web Worker 中使用——必须有一个真实存活的活动文档(页面);
- 尚未下载过的模型,在
create()调用之前必须存在用户手势(user gesture)。而已下载好的模型不需要任何操作即可创建。
因此"MORT 直接调用模型"的架构根本不成立,必须经过一个活着的 Chrome 文档。即便做成浏览器扩展,这两条限制依然存在。由此产生一个使用上的直接结论:桥接窗一关,翻译就停——这不是 bug,是上述约束的必然结果。
文档还记录了一次有说服力的排查实验:能不能用 WebView2 内嵌到 MORT 里做成单窗?结论是"没有模型"。在 WebView2 运行时 152.0.4191.62 上实测:
LanguageModel → 객체 자체가 없음(对象本身不存在) Translator → 对象存在 Translator.availability(ja→ko) → "unavailable" Translator.create(ja→ko) → NotSupportedError: Unable to create translator for the given source and target language.也就是说 API 表面只在 Edge 侧暴露、却没有挂载设备内置模型;模型只存在于 Chrome 中,所以桥接窗必须是真正的 Chrome 窗。这条证据链解释了为什么 MORT 要专门写一个 Chrome 启动器:ChromeLauncher.cs 先从注册表App Paths\chrome.exe读取路径,找不到再依次尝试 Program Files / Program Files (x86) / LocalAppData 三个候选位置,并以--app=参数启动(无地址栏的应用窗,减少用户误操作到其他地址导致连接中断)。找不到 Chrome 时才退回默认浏览器并记日志提示"不是 Chrome 就用不了内嵌 AI"。
三、准备条件:版本要求
- 桌面版 Chrome,Android / iOS 不支持;
- Translator API:Chrome 138 及以上;
- Prompt API(LanguageModel):Chrome 148 及以上。
Chrome 内嵌 AI 的模型由 Google 更新。比如 Gemma 4 是否被采用取决于 Chrome 的版本与开关,而 MORT 无论挂上什么模型都只是调用同一套 JS API,无需改代码。
四、使用方法:三步走,且"应用"与"开始翻译"分离
- 在翻译方式里选择"Chrome 本地翻译器"并应用——此时不会弹出 Chrome 窗,只落盘设置;
- 开始翻译——此时 MORT 才打开本地服务器并唤起 Chrome 窗。窗口一打开就会自行连接并尝试准备模型;
- 翻译面板状态变为"桥接激活"即完成,之后无需任何点击。
这个"弹窗时机放在开始翻译而非应用"的设计在源码中有明确注释:TranslationProcessInitializationService.cs 的PrepareChromeBridge()在每次初始化翻译流程时才调用service.Prepare(端口, 自动运行开关, out error),注释写明"크롬 창은 번역을 실제로 시작할 때만 띄운다. 설정 적용만 했는데 브라우저가 뜨면 성가시다"(只在真正开始翻译时开窗,光应用设置就跳出浏览器很烦人)。
其他使用细节:
- 翻译面板的提示语只有一句:需要安装 Chrome。
- 如果模型已经下载过,全程零点击;没下载过则 Chrome 窗会弹"要下载吗?"对话框(见第七节)。
- 窗被关掉了或想重开,点翻译面板的 [打开设置];引擎选择与模型准备全部在那个 Chrome 窗内完成。
- 源/目标语言直接沿用 MORT 中选择的值,Chrome 窗里没有语言选择项。对应界面是"翻译标签 > 翻译语言",例如"用[中文(繁体)]→[韩文]翻译"。原本各翻译器各自独立的语言选择已合并为一处,改动并应用后 Chrome 本地翻译器也会立即生效。
- 语言码转换是两级映射:各翻译器之间的代码转换由
TranslateLanguageService负责;Chrome 桥接再把拿到的 Google 码(zh-TW、pt-BR等)转成 Chrome 认识的 BCP-47 形式(zh-Hant、pt)。这层归一化在 ChromeBridgeLanguage.cs 中实现:内置映射表覆盖zh-cn/zh-hans → zh、zh-tw/zh-hant → zh-Hant、pt-br → pt、en-us → en等;未命中的值若带两位地区后缀则截取前缀(保留zh-Hant这类脚本后缀原样)。auto的目标语言最终统一落到ko(韩语),符合 MORT 的典型使用场景。 - 配置会主动推送:MORT 在桥接窗连接上和设置应用时都会把语言配置发给窗口(
type: "config"消息,见 ChromeBridgeHub.cs 的SendConfigAsync)。如果不发,窗口在首条翻译到来之前不知道该准备哪个语言对的模型,会以页面测试框的默认值(ja→ko)为基准,准备错模型、显示错状态。 - Chrome 窗底部"直接试验"区域的语种选择仅用于手动测试,不影响 MORT 的翻译。
- Chrome 窗自身的显示语言(韩/英)由窗口右上角 [KR] [EN] 按钮切换:初始跟随 MORT 的 UI 语言(体现在地址
?lang=ko|en参数上,即 ChromeBridgeServer.cs 的PageUrl属性),按钮点击后写入 Chrome 的localStorage,之后以其为准;切换时不刷新页面、只换文案。刷新会断开 WebSocket,造成该时段的翻译请求失败、进行中的模型准备与翻译记录丢失。
五、引擎选择:这个翻译器里最重要的决策
Chrome 内嵌 AI 有两条性质完全不同的路径,对韩语用户的差别很大:
| 引擎 | 说明 |
|---|---|
translator | Translator API,专用翻译模型,支持 ko、ja、zh、en 等 38 种语言;速度快、结果稳定 |
llm | LanguageModel(Prompt API),即所谓本地 LLM;会结合上下文翻译,但官方支持语言只有 en / ja / es / de / fr,韩语不在列表内,韩语输出质量无保证 |
auto | 语言对能被 Translator 支持就走 Translator,否则交给 LLM。默认值 |
三者的常量定义与归一化(接受prompt、languagemodel、translate等别名)都在 ChromeBridgeLanguage.cs 的ChromeBridgeEngineMode中。
auto 的路径记忆:auto 为某个语言对选定一次的路径会被记住(按语言对粒度),之后的翻译直接沿用。每翻一条都重新探测模型会变慢、日志也会被同一条消息刷屏。记忆只在该窗口存活期间有效,刷新页面会重新判断。用户手动指定引擎时不做记忆——那已是固定选择,没有可记忆的东西。若该语言对的模型后来被下载了,记忆会解除(否则会一直错误地用 LLM)。
对"日语音游翻译成韩语"这种典型 MORT 用户,auto(实际上就是 Translator)是正确选择;LLM 路径只在需要 en/ja 输出或想试验上下文翻译时使用。文档给出的实测对照:
新的游戏を開始しますか?/設定を保存しました。 → 新的游戏要开始吗?设置已保存。 (translator) 魔王を倒すには、まず村の長老に話しかけてください。 → 要击败魔王,请先与村子里的长老对话。 (translator) Save the game before you leave. → 离开游戏英雄前请存档。 (llm)第三条展示了 LLM 路径的韩语质量问题:"the game" 被译成了"游戏英雄"。另有一个已知行为:Translator 会把换行合并成空格。对以 OCR 块为单位翻译的 MORT 结构没有影响,但值得知道。
六、模型下载:用户手势、双语模型状态与语言包布局
6.1 用户手势只在"首次下载"时要求
当create()在无用户手势情况下被调用时,Chromium 返回的原文是:
Requires a user gesture when availability is "downloading" or "downloadable".即只有尚未下载的模型才需要用户操作;已下载的模型无需手势直接创建成功。(MDN 上写"create() 始终需要用户手势",实际实现比文档宽松。)因此桥接页面打开后的自动流程是:
- 建立 WebSocket 连接——无需手势,MORT 在窗打开瞬间就能"看见"桥接窗;
- 调用
availability()检查——无需手势; - 尝试
create()——模型已下载则直接成功,需下载则仅此处被拒。
第 3 步被拒后,Chrome 窗内弹出对话框:
┌────────────────────────────────────────────────┐ │ 没有翻译模型 │ │ 需要下载对应翻译模型 (ru → ko)。 │ │ 要下载吗? │ │ 没有翻译模型时将用 LLM 处理。 │ │ [稍后] [下载] │ └────────────────────────────────────────────────┘这个对话框必须出现在 Chrome 窗内:从 MORT 侧弹出的确认点击不会被 Chrome 视为用户手势,下载无法启动,MORT 也不可能替用户去点。文案设计也讲究:第一行说"要下载什么"(保留语言对ru → ko,否则用户下载后无法确认下对了没有),第二行说"不下载会怎样";塞进一句里会导致按钮不醒目。
[下载] 实际只下对话框说的那一样,对应关系:
引擎 llm → LLM 模型 引擎 translator → 翻译语言包 auto + 缺语言包 → 只下翻译语言包;LLM 用 [下载 LLM 模型] 单独下 auto + 仅缺 LLM → 无可下载项(翻译走语言包即可),只弹 [确定] 不支持的语言对 → 无可下载项(该语言对根本没有语言包)"文案与实际下载物不一致"曾经真实发生过:auto 模式弹窗写着"需要下载 LLM 模型",点 [下载] 后只碰了已存在的语言包、日志还留下"模型准备完成"。因此对话框对象内部携带了下载目标(translator / llm / none),实际下载只认这个值。auto 模式会先同时检查翻译模型与 LLM,再按缺失情况给出不同的第二行文案:
- 只缺翻译模型:"没有翻译模型时将用 LLM 处理。"
- 两者都缺:"LLM 模型也没有,现在无法翻译。LLM 是附加功能,请用 [下载 LLM 模型] 单独下载。"
6.2 模型缺失时 MORT 的反应:停翻译 + 前置窗口
进入需要下载的状态时,MORT 会暂停翻译并把桥接窗提到前台(不弹 MORT 自己的弹窗——确认按钮只在桥接窗里,MORT 弹窗只能当过场)。桥接窗上报的原因会写进 MORT 日志,翻译面板状态变为"没有模型"。停翻译的原因:不停的话 auto 会静默降级到 LLM,持续产出低质量结果而用户毫无察觉。这条链路在 ChromeBridgeHub.cs 中可见:收到ready消息且状态进入need-model时触发ModelMissing事件(同一状态期间只通知一次,避免每个请求都弹窗)。
把窗口提到前台时会同时取消已排定的最小化(ChromeBridgeWindow.KeepVisible(),见 ChromeBridgeService.cs)。因为"要询问"恰恰发生在窗口刚弹出之后,若最小化照旧执行,会把需要点击的对话框打进任务栏——这个竞态是真实发生过的。另外,同样的问题不会问两遍:曾经存在"窗口打开时问一次、首条翻译请求又问一次"的 bug,修复方式是按"下载对象 + 语言对"组合记忆 [稍后] 的选择,模型准备好或语言对变化时清除记忆。Translator 完全不支持的语言对不在此列——那种情况走 LLM 就是正常路径,不会停翻译。
按了 [稍后] 或想预先下载,可用 Chrome 窗内的手动下载按钮:
- [下载翻译语言对]:下载 MORT 实际请求过的语言对;若还没有请求,则下载测试框所选的对。必须以 MORT 发送的值为基准——若以测试框为准,需要的语言对会一直缺失、每次都降级到 LLM。
- [下载 LLM 模型]:下载 LLM 模型,体积很大(约 4GB)。
6.3 模型状态分开显示 + "已下载模型列表"
窗体顶部有两格独立状态:
● 翻译模型:有 (ru → ko) ● LLM 模型:无(附加功能 · 4GB)早期只有一格"模型就绪 / 需要模型",但两个模型共用一格导致"LLM 有、翻译模型缺"这种常见状态被显示成"需要模型",看起来像出了故障。现在下载中、准备失败等状态都写进对应模型格;翻译模型按语言对区分,所以状态里同时标出当前语言对;完全不支持的对显示"此语言对没有语言包"而非"无"——"下载了也没用"和"下载了就能用"是两回事。
"已下载模型"一行显示当前实际使用的模型,例如:
LLM : v3Nano (2025.06.30.1229, 4.0 GB) 翻译语言包 : en_ja 75.9 MB, en_ko 75.4 MB, en_zh 76.6 MBv3Nano即 Gemini Nano 第 3 代。挂什么模型由 Chrome 决定,MORT 不选。JS API 不提供模型名(LanguageModel.params()只返回 topK/temperature),所以MORT 从 Chrome 的用户数据目录读manifest.json再传给页面。读取逻辑在 ChromeModelInfo.cs:
- LLM:
%LOCALAPPDATA%\Google\Chrome\User Data\OptGuideOnDeviceModel\<版本>\manifest.json的BaseModelSpec.name(取创建时间最新的版本目录,同时统计目录总大小); - 翻译语言包:
User Data\TranslateKit\models\<语言包>\<版本>\。
通过 ChromeBridgeHub.cs 的SendModelsAsync以type: "models"消息发给页面——页面是网页,读不到磁盘文件,只能由 MORT 代读。只在窗口连接时和用户点 [刷新列表] 时发送(要扫盘);模型下载完成后页面发refresh-models消息触发自动更新。Chrome 没装在默认路径时读不到,页面显示"未找到",不影响翻译本身。
语言包以英语为轴:Translator 语言包不是按任意语言对存储,而是按"与英语的配对"存储:
%LOCALAPPDATA%\Google\Chrome\User Data\TranslateKit\models\ en_ja\...\en_ja_nmt, ja_en_nmt en_ko\...\en_ko_nmt, ko_en_nmt像ja → ko这种不含英语的配对,会同时使用en_ja与en_ko,经英语中转翻译。所以准备一对语言可能落下两个包;zh → ko需要单独的中文包,缺了就要下。
已下载的模型直接复用:即使从未在这个窗口点过准备,只要 Chrome 已下载过该语言对,availability就返回available,此时create()无需手势成功。所以翻译请求到来时先查availability,available就立即创建使用。如果错误地把"本窗口从未准备过 = 没有模型"当成立,就会把好好的模型弃用、每次降级到 LLM,表现为"换引擎结果也不变、设置像失效"。模型准备耗时且可能失败,但连接始终保活——先建连接、后台再准备,是刻意的顺序。
七、设置项与默认值
偏好引擎在"高级设置 > 翻译设置 > Chrome 本地翻译器"里选择:
偏好引擎 自动 / LLM / Translator API。默认自动点应用后无需再次应用翻译设置,打开的 Chrome 窗会立即收到新配置。其余项只存在于高级设置文件中,默认值可直接使用:
| 配置键 | 说明 | 默认值 |
|---|---|---|
@CHROME_BRIDGE_PORT | 本地服务器端口 | 16899 |
@CHROME_BRIDGE_MODE | 引擎默认值:auto / translator / llm("偏好引擎"读写此值) | auto |
@CHROME_BRIDGE_AUTO_RUN | 开始翻译时自动打开 Chrome 窗 | true |
@CHROME_BRIDGE_TIMEOUT | 单条翻译最长等待秒数 | 30 |
键名常量与默认值注册分别在 AdvencedOptionManager.cs 与 AdvencedOptionManager.cs;默认值本身定义在 ChromeBridgeService.cs(DefaultPort = 16899、DefaultMode = auto、DefaultTimeout = 30)。
一个容易踩坑的优先级规则:MORT 会把mode随配置一起下发,所以默认情况下 Chrome 窗引擎跟随 MORT 设置;但一旦在 Chrome 窗里手动选了引擎,窗内选择从此优先。否则用户在窗里换引擎会"什么都不发生",看起来像设置失效。
八、本地 HTTP 服务器:接口、语义与安全边界
Chrome 本地翻译器开启期间,本地服务器随之在线:
GET / 桥接页面 GET /status 连接状态 (JSON) GET /ws 桥接页面接入的 WebSocket POST /translate 翻译请求MORT 自身不经过这个 HTTP,而是直接调用进程内的 Hub(ChromeBridgeHub.TranslateAsync);/translate是留给你把它挂到 MORT 的"自定义 API"预设或手动验证的通道。请求/响应契约:
请求 { "text": "原文", "source": "ja", "target": "ko", "mode": "auto" } 成功 { "result": "译文", "engine": "translator" } 失败 { "error": "人类可读的失败原因" } (HTTP 503 / 400)这个 HTTP 端点不是包装的快捷方式,而是与 WebSocket 同一套 Hub 的另一个入口。从 ChromeBridgeServer.cs 的HandleTranslateAsync可以看到完整实现细节:
- 字段名有别名容错:
text/q/OCR_TEXT/content、source/sourceLanguage/from/SOURCE_CODE、target/targetLanguage/to/RESULT_CODE、mode/engine,方便对接不同的自定义 API 预设格式; - 语言码同样经过
ChromeBridgeLanguage.Normalize,target为auto时落到ko; - 超时取高级设置的
@CHROME_BRIDGE_TIMEOUT;失败返回503 + error,JSON 解析失败或缺text返回400,正文超过 1MB(MaxBodyBytes)返回413; /status返回{ connected, status, port }。
实现上有两个值得注意的工程决策,都写在 ChromeBridgeServer.cs 的类注释中:
- 不用
HttpListener,直接TcpListener收——HttpListener在非管理员账户下有时要求 URL ACL 注册; - WebSocket 只在此处完成握手(自行计算
Sec-WebSocket-Accept,即 SHA1(key + 魔数 GUID) 的 Base64,见 ChromeBridgeServer.cs),帧处理交给WebSocket.CreateFromStream。
服务器只绑定127.0.0.1(new TcpListener(IPAddress.Loopback, port)),外部无法访问。
Hub 的请求/响应模型值得单独看:ChromeBridgeHub.cs 的TranslateAsync为每条请求分配自增id,放入ConcurrentDictionary<long, TaskCompletionSource<ChromeBridgeResult>>挂起表,发送{type:"translate", id, text, source, target, mode}后用Task.WhenAny(挂起任务, 超时延时)等待;超时则记日志[ChromeBridge] 翻译无响应 : ja → ko / auto / 42字 / 30초 超时并返回失败。断线时所有挂起请求被FailAllPending统一失败。页面回包类型见 ChromeBridgeHub.cs 的HandleMessage:ready(状态同步)、log(页面日志,转发到 MORT 日志)、refresh-models、result、error。单条消息上限 4MB,超限丢弃。
九、翻译记录、LLM 语言保持与重试机制
9.1 双份日志与翻译记录
日志落在两处:
- Chrome 窗底部 [日志]——该窗口发生的全部事件;
- MORT 日志——其中仅警告与错误(桥接窗平时是最小化的,若只写进窗内日志等于没记)。MORT 日志中的样式:
[ChromeBridge][page] [警告] Translator 路径失败 → 交给 LLM (ja>hu) : … [ChromeBridge][page] [错误] 翻译失败 (ja → hu) : 翻译模型(ja>hu)尚未下载。开启 [详细] 后还会记录请求到达、源语言检测及置信度、路径选择、耗时——平时不记是因为一条请求占好几行日志,会淹没真正该看的警告。日志截断在 400 行,[复制] 把整段日志写入剪贴板。MORT 侧另记两类:
[ChromeBridge] 翻译无响应 : ja → ko / auto / 42字 / 30초 超时 [ChromeBridge] 窗口未连接,未能处理翻译请求。…"未连接"只在断线后的第一条请求记一次,重连后复位(_loggedDisconnected标志,见 ChromeBridgeHub.cs),否则 OCR 每轮都会刷同一句。
Chrome 窗 [翻译记录] 每行的格式:
12:44:20 [LLM] [重试成功] ja → ko · 检测 ja 98% · 842ms · 15字 MORT 请求 : 自动 · Translator 失败转 LLM : … · 重跑后修复 村里的入口处请等候。 请在村口等候。仅凭路径徽章回答不了"为什么只有这句异常",下面那行补充说明:是 MORT 指定还是窗内所选、走了语言包还是 Translator 失败降级、auto 是否先前已为该语言对判定 LLM。[重试成功]= LLM 未按目标语言作答、重试后修复;[未翻译]= 两次都失败、原样返回原文。
9.2 LLM 返回原文问题的三重修复
LLM 会无视目标语言、把原文原样返回——真实发生过:ja → ko 发出去、日文原样回来。Translator 模型的语言对是烧进模型里的,不会有这问题;LLM 只是服从指令,一旦把语言对当成资料读进去,就会照抄原文。模型越小越常见。同一句("村里的入口处请等候。")重复实测:
修复前 : 5 次中 1 次输出韩语 修复后 : 12/12(另混 10 句跑完,未触发重试)修复手段有三层,在桥接页 bridge.html 中可对应找到:
语言对钉进会话:
LanguageModel.create时传expectedInputs/expectedOutputs(bridge.html),系统提示词也写明"你只能用韩语作答"——后者比提示词句子更有效。但 Chrome 的某些构建或语言下 hint 会被拒绝,被拒时回退到不带 hint 重建。目标为韩语时 Chrome 实际会拒绝:The requested language options are not supported.因为 Prompt API 官方支持输出语言只有 en/ja/es/de/fr,所以韩语下真正生效的是第 2、3 层;hint 用在目标为日/英的场景。被拒是正常现象,只写 [详细] 日志、不升为警告。语言对变化时丢弃旧会话重建(系统提示词里钉着旧对)。
原文之后再说一遍指令:只把指令放在原文前会被模型忘掉;小模型对最后读到的句子服从度最高。
按字符检测 + 一次重试:结果中源文字符占比高于目标语言字符即视为"翻译未完成"——整句原样返回、以及"攻击力提升了!"这种半翻译都能被抓住。保留原文中的专名("美咲(Misaki)的剑")是正常现象,所以目标语言字符更多就放行。重试使用无历史的新会话副本:在原会话上追问会被看到刚才的答案而原样复述(两次同为原文时实际观察到)。两次都失败则原样返回原文并写 MORT 日志——不升级为错误,因为屏幕上显示原文总比显示错误文案好。
不判定的组合:使用相同字符的语言对(如英 → 法)、表中没有的语言、以及中 → 日(纯汉字日文句子与中文字符相同,无法区分)。误判丢掉正常翻译的损失更大。有 Translator 语言包的语言对根本不会走到这里——这是"缺包语言对"的专属问题;同一症状反复出现时,下载对应语言包最稳妥。
9.3 删除已下载的模型
用 Chrome 窗的 [模型管理 / 删除]。两个模型的删除入口完全不同,所以分开展示(合成一块会导致用户不知道按哪个):
① 翻译语言包 — en_ja 75.9 MB, en_ko 75.4 MB chrome://on-device-translation-internals/ 实际目录 : …\User Data\TranslateKit\models ② LLM 模型 — v3Nano 4.0 GB 在 Chrome 设置 > 系统 > 关闭设备内 AI 即被删除 …\User Data\OptGuideOnDeviceModel\2025.8.8.1141语言包在chrome://on-device-translation-internals/安装/卸载,地址会复制到剪贴板并直接显示在画面(chrome://地址既不能被网页打开也不能挂链接,只能粘贴到地址栏;剪贴板被禁的环境也能靠肉眼抄录)。LLM 模型(4GB)不走那个页面,而是在 Chrome 设置 > 系统 > 关闭"设备内 AI",关闭期间 Chrome 也不会重新下载。模型实际所在目录一并给出——Chrome 设置里不写位置,要核对容量或确认删除是否生效必须找目录;而页面是网页读不到文件,路径由 MORT 读取后经models消息下发。
MORT 不直接删模型的原因有三:JS API 没有删除功能(destroy()只释放内存会话);直接删文件不被 Google 推荐、Chrome 运行中文件被锁定、且组件更新器会重新下载;翻译器本不该动用户的浏览器安装。
十、运维要点:单窗保证、最小化策略与区域分隔
窗口生命周期规则("注意事项"章节的核心,源码一一对应):
关窗或跳转其他地址会停翻译,用 [打开设置] 重开;连接断开后3 秒重连一次(bridge.html 的
setTimeout(connect, 3000))。Chrome 窗的 X 按钮 MORT 拦不住——窗属于 Chrome。平时最小化所以误关概率低。
桥接窗永远只保留一个,三层防护(ChromeBridgeService.cs 的
OpenPage):- 已连接则不新开;
- 刚开未连接则在 12 秒冷却期(
LaunchCooldown)内等待——防止"开始翻译"与 [打开设置] 并发开出两个窗; - 若第二个窗仍接入,旧窗收到关闭原因
"another tab connected"(ReplacedCloseReason,ChromeBridgeHub.cs)后自行停止重连并关闭——死窗留在画面上会分不清哪个活着。这个原因字符串两端必须一致,否则双方各 3 秒互推、期间的请求全部失败。
窗口默认最小化:开始翻译新开时弹出即最小化(后台任务
MinimizeAfterLaunch,ChromeBridgeWindow.cs,先轮询等到窗口出现、再留 400ms 落定、然后SW_MINIMIZE);[打开设置] 唤起时展开前置;模型缺失询问时展开前置(下载按钮只在那里)。原因:每次翻译开始都把窗口顶到前面会抢走游戏焦点,全屏游戏甚至因此被踢回窗口模式。最小化不影响翻译,实测数据:
可见状态 96ms (首条请求,含模型加载) 最小化 +3秒 4ms 最小化 +63秒 4ms 最小化 +3分钟 5msChrome 的后台延迟只拖慢定时器;翻译由 WebSocket 数据到达唤醒并发起原生调用,不受其影响。刻意不用完全隐藏(
SW_HIDE):那样 MORT 崩溃时用户找不到窗、无从关闭,而最小化的窗仍在任务栏可找回。用户关窗后再翻译不会自动重开(视为用户意愿,避免窗不断盖到游戏上)。注:源码中
EnsurePageAsync会在翻译前等待断线窗口重连(ChromeBridgeService.cs),文档口径以"手动打开"为准——遇到版本差异时以仓库当前代码行为为准。Translator.availability()无响应的 Chrome 构建真实存在。所有涉及模型的调用都挂了超时,卡住时跳过检查继续或记录原因后失败,翻译链路整体不会挂死。翻译请求在 Chrome 窗内串行处理,OCR 高频刷新也不会交叉。
多 OCR 区域的分块策略是 Chrome 桥接独有的一条特殊路径:像 overlay 那样存在多个 OCR 区域时,MORT 常规做法是用分隔 token("//////",定义于 Util.cs)把区域拼成一条整体翻译、再按 token 拆回。但 Chrome 内嵌 AI 不守这个 token——Translator 把换行并成空格("//////\n"变"////// "),LLM 甚至会整块删掉标记;不处理的话所有译文会全堆进第一个区域。因此 ChromeBridgeTranslateAPI.cs 的做法是:按 token 先拆开,逐块单独翻译,再按原样拼回。代价是每块多一次往返,换来区域精确分隔、分隔符不进入模型输入、结果更干净。
十一、故障排查速查表
文档给出的状态-处置对照(保留原文案,便于与界面/日志直接比对):
| 状态 / 报错 | 含义与处置 |
|---|---|
| "开始翻译时连接" | 本地服务器还没起。开始翻译或按 [打开设置] |
| "初始化等待中" | 服务器已起、Chrome 窗还没连上 |
| "桥接激活" | 窗已连上且有可用模型,可翻译 |
| "Chrome 桥接窗未连接" | 窗被关了。用 [打开设置] 重开 |
| "翻译模型(ja>ko)尚未下载" | 按第六节在 Chrome 窗中准备对应语言对 |
| "Chrome 桥接 30 秒内未响应" | 正在下载模型或窗口被延迟。前置窗口看其底部日志 |
| "此浏览器没有内嵌 AI API" | 不是 Chrome 或版本过低。核对第三节的版本条件 |
| 日志提示端口无法打开 | 其他程序占用了 16899。改@CHROME_BRIDGE_PORT |
失败原因分落在两处:MORT 日志里是服务器视角,Chrome 窗底部日志里是模型视角。两份日志对照即可判断链路断在哪一端。
十二、小结:这套桥接方案的设计取舍
MORT 的 Chrome 本地翻译器本质上是一个"受浏览器 API 约束驱动的分布式小系统":两条不可绕过的浏览器约束(必须有活动文档、首下模型必须用户手势)决定了架构只能是"进程内服务器 + 常驻 Chrome 应用窗",而后续所有细节——窗内对话框、单窗三层互斥、最小化/前置竞态、配置主动推送、语言对会话钉扎、字符级重试、逐块翻译——都是在"游戏场景下不打扰用户"与"失败可见可诊断"两个目标之间做的取舍。对开发者的可借鉴点在于:用TcpListener手写一个最小 HTTP + WebSocket 服务器避免 ACL 问题(ChromeBridgeServer.cs);用id → TaskCompletionSource挂起表把异步回包变成await友好的TranslateAsync(ChromeBridgeHub.cs);以及把"网页读不到磁盘"这类平台边界用宿主代读(models消息)补齐。全部实现集中在 MORT/Service/ChromeBridge/ 目录(含 bridge.html 桥接页)与 ChromeBridgeTranslateAPI.cs,可直接对照本文逐节阅读。
- 桌面应用
- OCR
【免费下载链接】MORT
MORT 번역기 프로젝트 - Real-time game translator with OCR
相关推荐
MORT实时游戏翻译器安装与配置指南
MORT实时游戏翻译器安装与配置指南 1. 项目基础介绍 MORT(Multiple OCR Translation)是一个开源的实时游戏翻译器,它可以通过光学
桌面应用OCR【亲测免费】 MORT 实时游戏翻译器使用教程
MORT 实时游戏翻译器使用教程 1. 项目介绍 MORT(实时游戏翻译器)是一个开源项目,它使用OCR(光学字符识别)技术实时提取屏幕上的对话,并通过数据库或
桌面应用OCRMORT 实时游戏翻译工具:OCR 识别、机器翻译管线、Custom API 接入与开发指南
MORT 实时游戏翻译工具:OCR 识别、机器翻译管线、Custom API 接入与开发指南 MORT 是一个 Windows 平台的游戏实时翻译工具:它通过
桌面应用OCR
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考