☰
MORT 实战解析:用 Chrome 内嵌 AI(Translator API 与 LanguageModel)构建本地游戏实时翻译桥
2026/10/14 17:25:07 网站建设 项目流程
  • 桌面应用
  • OCR

【免费下载链接】MORT

MORT 번역기 프로젝트 - Real-time game translator with OCR

项目地址:https://gitcode.com/gh_mirrors/mort/MORT
点击查看免费下载

本文基于 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 有两条外部程序无法绕过的限制:

  1. 不能在 Web Worker 中使用——必须有一个真实存活的活动文档(页面);
  2. 尚未下载过的模型,在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,无需改代码。

四、使用方法:三步走,且"应用"与"开始翻译"分离

  1. 在翻译方式里选择"Chrome 本地翻译器"并应用——此时不会弹出 Chrome 窗,只落盘设置;
  2. 开始翻译——此时 MORT 才打开本地服务器并唤起 Chrome 窗。窗口一打开就会自行连接并尝试准备模型;
  3. 翻译面板状态变为"桥接激活"即完成,之后无需任何点击。

这个"弹窗时机放在开始翻译而非应用"的设计在源码中有明确注释: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 有两条性质完全不同的路径,对韩语用户的差别很大:

引擎说明
translatorTranslator API,专用翻译模型,支持 ko、ja、zh、en 等 38 种语言;速度快、结果稳定
llmLanguageModel(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() 始终需要用户手势",实际实现比文档宽松。)因此桥接页面打开后的自动流程是:

  1. 建立 WebSocket 连接——无需手势,MORT 在窗打开瞬间就能"看见"桥接窗;
  2. 调用availability()检查——无需手势;
  3. 尝试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 MB

v3Nano即 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 的类注释中:

  1. 不用HttpListener,直接TcpListener收——HttpListener在非管理员账户下有时要求 URL ACL 注册;
  2. 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 中可对应找到:

  1. 语言对钉进会话: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 用在目标为日/英的场景。被拒是正常现象,只写 [详细] 日志、不升为警告。语言对变化时丢弃旧会话重建(系统提示词里钉着旧对)。

  2. 原文之后再说一遍指令:只把指令放在原文前会被模型忘掉;小模型对最后读到的句子服从度最高。

  3. 按字符检测 + 一次重试:结果中源文字符占比高于目标语言字符即视为"翻译未完成"——整句原样返回、以及"攻击力提升了!"这种半翻译都能被抓住。保留原文中的专名("美咲(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):

    1. 已连接则不新开;
    2. 刚开未连接则在 12 秒冷却期(LaunchCooldown)内等待——防止"开始翻译"与 [打开设置] 并发开出两个窗;
    3. 若第二个窗仍接入,旧窗收到关闭原因"another tab connected"(ReplacedCloseReason,ChromeBridgeHub.cs)后自行停止重连并关闭——死窗留在画面上会分不清哪个活着。这个原因字符串两端必须一致,否则双方各 3 秒互推、期间的请求全部失败。
  • 窗口默认最小化:开始翻译新开时弹出即最小化(后台任务MinimizeAfterLaunch,ChromeBridgeWindow.cs,先轮询等到窗口出现、再留 400ms 落定、然后SW_MINIMIZE);[打开设置] 唤起时展开前置;模型缺失询问时展开前置(下载按钮只在那里)。原因:每次翻译开始都把窗口顶到前面会抢走游戏焦点,全屏游戏甚至因此被踢回窗口模式。

    最小化不影响翻译,实测数据:

    可见状态 96ms (首条请求,含模型加载) 最小化 +3秒 4ms 最小化 +63秒 4ms 最小化 +3分钟 5ms

    Chrome 的后台延迟只拖慢定时器;翻译由 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

项目地址:https://gitcode.com/gh_mirrors/mort/MORT
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询