阅读文本 / Reading text
本文使用合成样本演示处理方法。代码已通过隔离环境中的示例检查;这不代表对所有真实文件或产品效果的保证。处理重要文件前,请保留原件,并按文中的边界与验收项复核。
术语表里明明有这个词,程序却说没有命中。把字符串打印出来看,两边似乎一模一样。原因可能不在翻译,而在 Unicode 表示:一个带重音字符既可以是单个码点,也可以由基础字母加组合符号组成。
先确认是表示差异,还是内容差异
比较前做 NFC 归一化,常用于把规范等价的表示对齐。NFKC 的范围更广,会把一些兼容字符折叠;这可能符合搜索需求,却不一定符合档案保存或技术符号比较的需求。不要把“更容易匹配”误当成“语义一定不变”。
合成样例里,
é
的两种写法应在 NFC 后一致;全角字母和上标字符则帮助展示 NFKC 会带来的变化。它们是具体例子,不是要求所有项目统一选某一种形式。
用代码看到原来没看见的差别
import unicodedata as ud
composed = "café"
decomposed = "cafe\u0301"
assert composed != decomposed
assert ud.normalize("NFC", composed) == ud.normalize("NFC", decomposed)
fullwidth = "ABC"
assert ud.normalize("NFC", fullwidth) == fullwidth
assert ud.normalize("NFKC", fullwidth) == "ABC"
assert ud.normalize("NFKC", "x²") == "x2"
def term_key(text):
return ud.normalize("NFC", text).casefold()
assert term_key("CAFÉ") == term_key(decomposed)
assert term_key("x²") != term_key("x2")
print([f"U+{ord(c):04X}" for c in decomposed])
样例给术语匹配选择了 NFC 加
casefold
,这是一个有意限定的匹配策略。
casefold
也会产生语言相关的折叠结果,所以不能因为它方便,就把生成的键直接替换掉所有原始文字。
原文和匹配键分开存
保留
original_text
,另建
normalized_key
。前者用于展示、追溯和复核,后者用于当前策略下的检索。记录策略名称与版本,未来改用另一种规则时才能重建索引,而不是发现原文已经被不可逆地覆盖。
观察
可以尝试的动作
不应顺手做的事
组合重音差异
按明确策略做 NFC
宣称字符数不再重要
全角半角差异
按业务需求评估 NFKC
直接覆盖技术文档原文
大小写差异
建立大小写不敏感匹配键
把展示文本全部小写
仍未命中
查看码点与实际上下文
不断叠加替换规则碰运气
归一化并不解决所有“看起来一样”
外形相近的拉丁字母与其他文字中的字符,未必具有规范或兼容等价关系。零宽字符、不同空格、OCR 把字母识成数字,也需要各自的诊断。若牵涉账号、文件标识符等安全敏感场景,不要把本篇术语匹配示例当成完整的混淆字符防护。
位置追溯也有一个容易忽略的点:归一化后长度可能变化。用匹配键中的字符偏移去裁原文,可能切错范围。需要高亮或回写时,应保留原文片段映射,或重新在原文层面定位。
把问题交给翻译前,先保留证据
评估
OmniDoc(产品入口)
的术语与翻译工作流时,可以准备两种 Unicode 表示的同一术语,记录源文本、策略和译文。这样发现不一致时,能区分是输入匹配问题还是译文选择问题,不会把所有差异都归咎于翻译模型。
参考:
Python unicodedata
。
- 1 -
更新于 2026-10-08