写回防护与修复

让模型生成文本很容易,难的是不让它破坏已有成果。插件在写回路径上串了一整条防护链:候选筛选 → 响应校验 → 内容校验 → 语义判定 → 结构修复 → 失败隔离。

防护一:只补缺失,绝不覆盖

条目状态是否进入翻译候选
status: initial 或无 target
status: translated,且判定为自翻译(见下)✅(Retranslate Self Translations 开启时)
status: translated,已有正常译文⛔ 跳过
status: reviewed / final永不触碰

插件不提供「覆盖已有译文」的开关。要重译某条,请在 .cliff 里把它改回 status: initial 或删除 target——这个动作会留痕、可评审。

NOTE

非原生 Culture 才参与翻译;原生语言由人工维护。

防护二:自翻译判定

问题:项目源码常常是混合语种撰写的(英文错误消息 + 中文 UI 标签),而 CLIFF header 的 source-language 取自 NativeCulture。此时「源语言 = 目标语言」,指令自相矛盾,模型可能把英文原样返回——这就是「自翻译」:target == source,且文本其实并不属于目标语言。

判定FCliffAITranslator::IsSelfTranslation):target == source 且源文本的 Unicode 脚本族(拉丁 / CJK / 阿拉伯 / 西里尔)与目标语言不符 → 视为自翻译,拒绝写回并计入失败。

重译候选IsRetranslationCandidate):status 恰为 translated、判定为自翻译、且 Retranslate Self Translations 开启 → 下次 AI 翻译时重新翻译。reviewed / final 即使满足条件也不重译。

提示词加固:system prompt 与 user content 都明确「条目可能与声明的源语言不同;凡不属于目标语言的条目必须翻译;已属于目标语言的原样返回;除该情形外禁止原样返回源文本」,并在 source == target 时追加说明。

防护三:空译文

模型返回 "" 时:

  • 源文本已属于目标语言 → 本地回填源文本(模型本应原样返回)
  • 否则 → 拒绝写回并走重试

导入器同样拒绝写入空译文,并把历史遗留的空译文 archive 条目就地恢复为源文本。原因是空译文会被编译成空字符串,Slate UI 直接显示空白,看起来像「漏翻」。

防护四:响应与内容校验

检查失败处理
响应是合法 JSON,且能从「JSON 数组」或对象中的 translations / results / data / items 取到条目该批请求重试(最多 Max Retries 次);仍失败则记 warning 并跳过该批
{"translations": []} 空数组视为合法(该批无需翻译),不再误报「响应无法解析」
每条含 id 且能在本批候选中匹配该条记为失败并跳过
translation 非空见「防护三」
占位符一致:译文占位符数量相等且源文中的每个 {…} 都出现在译文中(支持嵌套花括号)拒绝写回该条:Placeholder mismatch for '…'; skipped.
译文与源文完全相同且源文不属于目标语言拒绝写回该条:AI returned the source unchanged for '…'; self-translation refused.

单条失败不会中断整次任务:只记 warning,其余条目照常写回。一轮结束后若仍有失败条目,插件会自动再跑一轮(最多 Max Retries 轮),只处理仍然缺失译文的条目。

防护五:CLIFF 自动修复闭环

AI 产出的 .cliff 不假设第一次就合法:

① FCliffDocument::ParseAndValidate 校验
② 通过 → 进入导入
③ 失败且 Auto Repair CLIFF 开启:
     把「原始 CLIFF 文本 + issue 列表 + 修复要求」发回 DeepSeek
     (专用 system prompt:只修报告的问题、保持已正确内容不变、只返回 CLIFF 纯文本)
     要求:只修语法/结构/占位符/必填字段;
           不得改动已正确条目的 id / namespace / clan / source / 已有 target / status 语义
④ 收到修复结果后剥掉可能的 ``` 代码围栏,再次 ParseAndValidate
⑤ 最多 Max Repair Attempts 轮(默认 2)
⑥ 仍失败 → 该文件标记失败(记入 FailedFiles),**不写入、不导入**,避免污染官方数据
⑦ 修复成功后仍要过「防护三/四」的校验

部分成功与失败口径

情况结果
本轮部分条目失败告警 + 继续写回成功的条目,任务照常进入导入阶段
整次任务(含全部重试轮次)译文数为 0命令let 报错,任务失败
{"translations": []} 空数组响应视为合法结果(不再误报「响应无法解析」)
HTTP 401 / 403立即停止,提示检查 Key
HTTP 429Retry-After 或指数退避后重试
HTTP 5xx / 超时Max Retries 重试,仍失败则跳过该批
未配置 Key直接报错,不发起任何请求

IMPORTANT

判断失败的阈值是「整个任务的译文总数为 0」,而不是「某一批失败」。这条规则是为了让 导出 → AI 翻译 → 导入 管线不会因为个别批次抖动而中途放弃,导致已翻译的内容无法落盘。

日志与密钥安全

  • 日志绝不打印 API Key、Authorization 头,或含密钥的完整请求体
  • 失败信息只包含:目标语言、文件路径、条目 id、issue 类别与消息
  • 进度窗口的 Copy Log / Save Log... 可以直接归档整次执行的日志,便于复盘

与官方数据的隔离

AI 只写 .cliff;写入 manifest / archive 的动作仍然由 CliffImport 步骤完成(FCliffImporter),因此:

  • AI 永远不会直接改二进制 .locres
  • 修复失败的文件不会被导入
  • 任何时刻都可以用「重新导入上一版 .cliff」来回滚

相关阅读