阅读文本 / Reading text
本文使用合成样本演示处理方法。代码已通过隔离环境中的示例检查;这不代表对所有真实文件或产品效果的保证。处理重要文件前,请保留原件,并按文中的边界与验收项复核。
“欢迎,{name}” 翻译成另一种语言以后,把变量名一起翻了,页面通常会在真正填值时才报错。更早的检查点是翻译交付时:把格式字符串中的字段取出来,核对字段身份、出现次数和格式规格,而不只是数花括号。
先选定格式语法
下面只处理 Python
str.format
风格,不覆盖 JavaScript 模板、ICU MessageFormat 或其他占位符系统。不同系统的嵌套、转义和复数规则不同,不能用一个正则声称全部支持。示例明确要求命名字段,拒绝自动编号字段。
转义的花括号不是变量
{{name}}
表示显示文字
{name}
,不应算成字段。格式规格里也可能嵌套字段,例如
{value:{width}}
。下面递归读取规格,并对同一字段的重复出现保留计数。
from string import Formatter
from collections import Counter
def contract(text, depth=0):
if depth > 5:
raise ValueError("nested format budget")
result = Counter()
for literal, field, spec, conversion in Formatter().parse(text):
if field is None:
continue
if not field or field != field.strip():
raise ValueError("named field without surrounding whitespace required")
result[(field, conversion, spec)] += 1
if spec:
result.update(contract(spec, depth + 1))
return result
assert contract("Hi {name}, total {amount:.2f}") == contract("{name}:金额 {amount:.2f}")
assert contract("{{name}}") == Counter()
assert contract("{name} {name}") != contract("{name}")
assert contract("{value:{width}}")[("width", None, "")] == 1
assert contract("{amount:.2f}") != contract("{amount:.0f}")
for bad in ["Hi {name", "{ name}", "{}"]:
try:
contract(bad)
except ValueError:
pass
else:
raise AssertionError("invalid template accepted")
这个契约刻意偏严格
这里把格式规格也当作受保护内容,因此改变小数位会被标记。某些本地化任务确实需要改变数字呈现,应为它建立独立规则,而不是取消所有占位符检查。嵌套深度限制是示例预算,不是语言规范要求。
变化
本例结果
审校动作
字段换位置
接受
阅读语序是否自然
同字段少一次
拒绝契约
检查是否漏译
变量名被翻译
拒绝契约
恢复接口字段名
小数规格变化
标记不一致
按本地化协议确认
不执行不受信任模板
这段代码只解析,不实际调用
format
填入对象。它也不是模板安全沙箱。后续渲染还需限制可用字段、对象访问与输出上下文,特别是 HTML 页面中的转义规则。
翻译与上线之间留一道检查
使用
OmniDoc(产品入口)
处理含模板内容的材料时,先列出哪些字段必须保持不变。译后把结构检查与语言审校分开做:一句话读起来顺,并不能证明变量契约仍然有效。
参考:
Python string 与 Formatter
。
- 1 -
更新于 2026-10-08