界面词条与动态翻译两条调用路径

架构主题封面|确定性渲染

摘要| 同一个多语言页面里,“保存”按钮和刚提交的客户留言都需要另一种语言,但它们不该走同一条调用链。固定文案需要稳定的词条与版本;动态内容需要受控的翻译服务、失败状态和写回规则。把两者分开,页面才不会把每次渲染都变成一次外部请求。

01|按钮变慢,是把两个问题放在了一起

设想一个订单页面:按钮、字段名称和提示语早已确定,客户备注却每次都不同。把这些文字统一送去翻译,看起来只有一个入口,实际却把界面显示与外部服务的可用性绑在了一起。

固定文案还承担产品术语的含义。“提交”和“保存草稿”不是随意互换的近义词;同一按钮在列表、弹窗和移动页面中,应当保持一致。动态翻译能提供译文,却不能替产品团队决定这份术语约定。

先问文本从哪里来| 界面预先定义的文字进入词条;业务运行时产生的文字,才进入动态翻译。

本文从 Microi吾码AI 使用的翻译引擎出发,分析当前源码中的两条路径。讨论重点是调用与失败边界,不是比较不同翻译模型的语感或给线上服务打分。

02|词条读取,不需要等待供应商

固定界面文案使用稳定 Key。代码只引用 NoAuth 这类标识,对应中文、英文等语言值由词条维护;文案调整时,变化发生在词条内容,而不是散落在业务脚本里的原句。

当前 GetLang 先解析执行租户,再委托 DiyMessage 取出语言文本;GetLangData 和 GetLangCode 则读取同一词条集合中的对象或 Code。它们不进入动态翻译供应商分支。

var message = V8.TranslateEngine.GetLang('NoAuth', 'en');
return { Code: 1, Data: message };

这段后端 V8 示例只演示词条读取。Key 是否存在、各语言值是否齐全,仍要在当前租户中检查。词条缺失应当被产品测试发现,不能靠每次显示时临时生成一句不同的话掩盖。

缓存的职责| 词条缓存帮助读取已确定的内容。它不能替代词条维护、配置更新后的失效处理,也不能让不同节点长期各用一个版本。

03|动态内容沿另一条链路运行

架构解释图|当前 LibreTranslate 文本分支,依据源码确定性绘制。

架构解释图|当前 LibreTranslate 文本分支,依据源码确定性绘制。

TranslateText 接收单条文本或文本列表,解析当前执行租户后先检查输入,再读取该租户的供应商配置。只有条件满足,才向服务端配置的地址发出请求,并把供应商响应整理成统一结构。

旧入口 Translate 的 Data 是单个译文字符串;批量内容应读取 TranslateText 的 TranslatedTexts。混用两个返回契约,容易把对象当字符串展示,或把一组结果误当成一条结果。

var r = V8.TranslateEngine.TranslateText({
  SourceTexts: ['你好', '世界'],
  FromLang: 'auto', Lang: 'en',
  Format: 'text', Alternatives: 0
});
if (r.Code !== 1) {
  return { Code: 0, Msg: '暂时无法翻译,请保留原文稍后再试' };
}
return { Code: 1, Data: r.Data.TranslatedTexts };

这里保留了原文与失败状态,不把原文包装成“翻译成功”。实际业务应把输入记录与结果位置对应起来;语言对是否可用,则通过当前服务的 GetLanguages 返回值确认。

04|租户身份不能由翻译参数决定

如果调用方能在文本旁边任意填一个租户标识,就可能把别人的供应商配置当成公共资源。当前 V8 路径通过 ResolveExecutionOsClient 进入 V8TenantContext 的租户约束,普通脚本传入的目标不能取代可信执行上下文。

GetProviderConfigs 只从解析后的租户添加配置。普通租户没有配置时,TranslateText 返回未配置状态,代码中没有再去借用主租户配置的分支。本文聚焦的 LibreTranslate 路径也不让业务参数携带任意请求地址或密钥。

  • 身份来自已建立的执行上下文,文本参数只表达翻译需求。
  • 供应商地址和认证信息留在服务端租户配置。
  • 配置缺失是可诊断状态,不能自动跨租户寻找可用服务。

区分信任边界| 可信平台控制面可有明确授权的管理能力;普通 V8 参数本身不能建立这种授权。

05|失败发生在哪里,就在哪里保留含义

失败与恢复解释图|输入、配置、供应商与写回分别处理。

失败与恢复解释图|输入、配置、供应商与写回分别处理。

当前文本契约限制单条最多五万字符,单批最多五十条、总计二十万字符,候选数最多十个。SourceText 与 SourceTexts 必须二选一。超过上限时,请求在输入检查处结束,不必先等供应商拒绝。

这些数值是源码边界,不是吞吐量承诺。业务分批还要考虑服务容量、语言对和超时;把一百条拆成两批,仅解决条数约束,并不证明两批能同时稳定运行。

  • 输入不合格:修正数据或拆分批次,不原样无限重试。
  • 当前租户未配置:补齐配置并回读,再恢复对应任务。
  • 供应商暂时不可用:保留原文,记录失败,用有上限的业务重试恢复。
  • 源内容已经变化:丢弃旧结果或重建任务,避免覆盖新版本。

源码与业务设计分开| 输入上限和租户配置读取是当前实现事实;队列重试、结果版本比较与人工术语审核,需要业务流程另行实现。

06|两项测试证明了什么

本地聚焦测试结果图|根据本次真实测试输出确定性绘制。

本地聚焦测试结果图|根据本次真实测试输出确定性绘制。

本次运行现有 TranslateGatewayTests,两个测试均通过。测试使用本机模拟 LibreTranslate 服务,返回预先设置的译文;它检查调用契约与隔离行为,不依赖真实翻译模型给出好看的句子。

第一项检查批量译文、检测结果和候选结构,也从 Jint 中调用 V8 入口,并把伪造租户参数带入请求。服务端收到的请求不包含那个伪造租户。第二项用五十一条输入触发上限,同时检查源码没有主租户配置回退入口、语言缓存使用密钥摘要。

证据能到哪里| 本地运行证明本次代码与测试样例相符。它没有测量真实供应商准确率、生产并发能力、多节点缓存刷新,也没有替线上部署做验收。

因此,本文不提供延迟下降百分比或节省费用的数字。若要回答这些问题,应在自己的语言对、文本长度和部署环境中单独采样,把测试对象与结论对应起来。

07|后台翻译还要防止旧结果写回来

长描述、商品目录和历史内容适合进入持久任务。请求发出之后,编辑人员可能已经修改原文;旧请求即便翻译成功,也不应直接覆盖新版内容。这属于业务一致性问题,不能交给语言模型判断。

任务保存:记录ID、源版本、目标语言
执行翻译:读取任务对应的源文本
准备写回:重新读取当前源版本
版本相同:保存译文并标记完成
版本不同:放弃旧结果,按新版本另建任务

以上是可改造的处理步骤,不是宣称引擎已经替所有业务实现的机制。实际落库应把版本比较与更新做成一次受控操作,并给任务设置稳定身份,避免两个工作节点重复写入同一份结果。

监控也应按路径拆开:词条关注缺失项和版本一致性;动态翻译关注成功状态、失败类型、等待时长和积压。原文内容与认证信息不应为了方便排查而进入普通日志。

恢复目标| 恢复的是某个明确文本版本的译文任务,不是盲目重复整页所有翻译。

08|让稳定文案与变化内容各得其所

多语言系统的维护成本,往往取决于是否划清了变化范围。词条让产品表达保持稳定,动态翻译处理运行时的新内容;两者共用租户边界,却有不同的缓存、失败和验收方式。

落地时可以从一个页面开始,把按钮、字段、提示语列为词条,把客户留言等内容列为动态文本,再分别验证缺失词条、供应商未配置和源版本变化。一次把一个边界讲清,比把所有文字塞进同一个翻译函数更容易维护。

继续阅读| 对应资料为吾码官方《翻译引擎》《V8 函数列表·后端》与 LibreTranslate API 文档。实际安装语言以当前服务回读为准。

本文信息流卡片底图由AI生成;架构图由源码信息确定性绘制,测试图来自本地运行结果。概念底图只用于解释主题,不作为产品界面或运行证据。

Logo

宁波官方开源宣传和活动阵地,欢迎各位和我们共建开源生态体系!

更多推荐