工程实践

译文读着很顺,型号却少了一次:用 Counter 检查翻译占位符

占位符是否还在,不该只用集合判断。用多重集检测重复项丢失、金额变化和新增标记,同时明确程序无法判断语义正确。

阅读文本 / Reading text
译文读着很顺,型号却少了一次:用 Counter 检查翻译占位符 产品说明书翻译完,标题、段落和语气都没有问题,但同一个型号在表格里少了一次。人工顺着句子读,往往不容易发现;程序如果只检查“AX-19 是否出现过”,同样会放过它。这类问题适合在交付前增加一道确定性的检查:不仅比较有哪些不可翻译片段,还比较每个片段出现了几次。 先约定哪些东西不能被改写 本文使用 [[SKU:AX-19]] 和 [[AMOUNT:1200.50]] 作为教学用保留标记。它们不是任何产品的内置协议。上游先识别真正需要保护的字段,为其生成标记并保留映射;检查通过后才恢复显示文字。正文里的普通数字、单位和日期不能随手全替换:日期格式可能合理变化,货币也可能有专门的本地化规则。 相同的型号,出现次数也要相同 可点击的教学示例;不上传文档,不调用接口。 全屏 图 1 可切换的判断示意。仅演示处理规则,不代表产品界面或真实效果。 表 1 检查通过与不能证明的事 检查项目 发现什么 没有证明什么 标记多重集 缺失、重复、改写、新增 译文语义正确 标记语法 保留分隔符损坏 所有字段都已正确标记 逐段关联 字段出现在错误段落 术语选用自然 一段能复现错误的标准库代码 保存为 marker_audit.py,运行 python marker_audit.py。仅使用 Python 标准库。正则表达式刻意只接受本例的 ASCII 标记协议;遇到保留分隔符却无法解析时直接报错,而不是悄悄忽略。Counter 对每个不同标记记录次数,减法得到缺少或多出的次数。 import re from collections import Counter TOKEN = re.compile(r"\[\[[A-Z][A-Z0-9_]*:[A-Za-z0-9_.-]+\]\]") def tokens(text): found = TOKEN.findall(text) remainder = TOKEN.sub("", text) if "[[" in remainder or "]]" in remainder: raise ValueError("malformed reserved marker") return Counter(found) def compare(source, target): before, after = tokens(source), tokens(target) return dict(before - after), dict(after - before) source = "[[SKU:AX-19]] / [[AMOUNT:1200.50]] / [[SKU:AX-19]]" good = "金额 [[AMOUNT:1200.50]];型号 [[SKU:AX-19]],再列 [[SKU:AX-19]]" assert compare(source, good) == ({}, {}) missing = good.replace(",再列 [[SKU:AX-19]]", "") assert compare(source, missing)[0] == {"[[SKU:AX-19]]": 1} changed = good.replace("1200.50", "12000.50") lost, added = compare(source, changed) assert lost == {"[[AMOUNT:1200.50]]": 1} assert added == {"[[AMOUNT:12000.50]]": 1} extra = good + " [[SKU:AX-19]]" assert compare(source, extra)[1] == {"[[SKU:AX-19]]": 1} try: tokens("[[SKU:AX 19]]") raise AssertionError("malformed token accepted") except ValueError: pass print("PASS reordered markers") print("PASS missing, changed, extra and malformed markers") 本机实际执行得到两行 PASS:顺序重排被接受;缺失、改写、重复和格式破损被识别。特别注意第一个测试:允许改变顺序是为了容纳语言表达差异,不代表我们允许改变字段与句子的关系。不要把此测试输出当成翻译质量评分。 为什么全篇计数仍然不够 假设一份报告有两个产品段落,原文各出现一次相同型号;译文把两次都挤到第一段,总计数不会变。解决方法不是把正则写得更长,而是给每个结构单元分配稳定 ID,在段落、单元格或标题级别比较,再汇总文档结果。段落被合法拆分或合并时,需要显式映射关系,而不能按数组下标生硬配对。 还有一种更隐蔽的情况:金额标记被完整保留,但“含税”变成“不含税”。计数检查当然会通过。此类语义关系应进入人工审阅或专门的规则检查。因此,自动门禁最好输出“保留字段一致”,不要输出“译文正确”。 失败时不要自动拿原文覆盖 当检查失败,保存原文单元、译文单元、缺失和新增清单,让编辑决定如何修复。如果系统直接把缺失型号粘到段尾,文本虽然通过了下一次计数检查,含义可能更糟。修复后重新执行检查,保留前后版本和处理人,避免审阅过程变成无法追踪的改写。 对接 OmniDoc 的文档翻译流程时,可以把这个检查放在译文产物返回之后、公开发布之前。原生 UDOC 负责保留可编辑文档;质量规则负责指出哪些字段需要复核。二者解决的问题不同,不能用排版保真替代内容核对。本文不依赖未公开的翻译接口,也不虚构自动通过率。 参考与产品入口 Python:Counter 官方文档 Python:正则表达式官方文档 OmniDoc 产品入口 - 1 -
更新于 2026-10-10