出海资料本地化体检
定位与服务边界
这个 Skill 只做一件事——在把资料发给海外客户之前,把肉眼扫不出来的硬伤一次挑干净。
它不做翻译,也不判断译文好不好。它找的是那些一旦漏掉就会出事的东西:
- 金额被读错 1000 倍(同一份文件里
1,234和1.234两种千分位并存) - 交期被读反(
03/04/2026到底是 3 月 4 日还是 4 月 3 日) - 对俄资料里混着没翻的中文
- 阿文页面里拉丁型号因为没有方向控制符而显示错位
- 报价单上留着
TODO: уточнить
设计红线:脚本里没有任何「某国必须用某种格式」的事实库。
所有规则都只基于「同一份资料内部是否自相矛盾」——这是确定性检查,不需要外部知识,也不会给出错误断言。
长度问题只在用户同时提供源文目录时,用实际字符数比值判断,不用经验系数。
触发条件
- 准备把本地化资料发给海外客户 / 上架海外站点前的最后一道检查
- 收到翻译公司交付的多语种稿件,要验收
- 怀疑有漏译、术语不统一、单位混乱
- 阿文/希伯来文等 RTL 资料的混排检查
- 已有术语表,想确认各语种稿件有没有各翻各的
不触发边界(重要)
「不触发」不是客气话,是能力边界。下面这些请求都该明确说做不到,硬做只会给出看起来像结论的错误答案。
| 你的请求 | 本工具 | 该换成什么 |
|---|---|---|
| 「把这份中文说明书翻成俄文」 | ❌ 不触发 | 先让人或翻译工具产出译稿,再用本工具扫硬伤 |
| 「这句英文写得地不地道」 | ❌ 不触发 | 母语审校;本工具只查编码与文本层硬伤 |
| 「阿文页面排版好不好看」 | ❌ 不触发 | 本工具只报「缺方向控制符」这类确定性缺失 |
| 「这个译法在沙特会不会犯忌」 | ❌ 不触发 | 目标市场本地同事;本工具不内置任何文化/宗教事实 |
| 「这份合同有没有法律风险」 | ❌ 不触发 | 法务;出海合同条款请走合同体检类工具 |
| 「产品能不能进这个国家」 | ❌ 不触发 | 准入/认证机构;本工具不查认证 |
| 「扫描件 PDF 里的字有没有错」 | ❌ 扫不到 | 先导出成文本,见下节扫描范围 |
| 「Excel 报价表里的数字对不对」 | ❌ 扫不到 | 导出成 .csv,或直接走报价比价类工具 |
> 判断不清时用一句澄清:「您是要我在发出去前把硬伤扫一遍,还是要人做一轮母语审校?」
触发信号(命中任一即触发):本地化质检 / 翻译稿验收 / 出海资料能不能发 / 俄文阿文资料检查 /
术语不一致 / RTL 排版检查 / 漏译检查 / 交付前最后一道检查。
三分钟上手(渐进路径)
第 1 分钟|看报告长什么样
python scripts/loc_audit.py --demo --out ./体检输出
自动生成一套含俄文/阿文/英文的示例资料并出报告,不用准备任何数据。
第 2 分钟|扫自己的资料
python scripts/loc_audit.py --path ./待发资料 --lang ru --out ./体检输出
--lang 必须给:决定「中文残留」「全角标点」这类规则是否启用。
第 3 分钟|加术语表,查译法是否统一
python scripts/loc_audit.py --template > terms.json
python scripts/loc_audit.py --path ./待发资料 --lang ru --terms terms.json --out ./体检输出
术语表格式:{组名: [译法1, 译法2, ...]}。同一组内出现两种以上译法即判不一致。
第 4 分钟|接进 CI / 交付门禁
python scripts/loc_audit.py --path ./待发资料 --lang ru --out ./体检输出 --fail-on-block
加了 --fail-on-block 后,存在阻断项就返回 2,流水线直接挂住。
想先看报告再决定,就不加这个参数(退出码恒为 0)。
检查项清单(11 类,全为确定性规则)
| # | 检查项 | 级别 | 判定依据 |
|---|---|---|---|
| 1 | 替换字符 U+FFFD | 阻断 | 文件曾被错误编码保存 |
| 2 | 疑似乱码(UTF-8 被当 Latin-1 读取的特征串) | 阻断 | 逐行匹配特征串 |
| 3 | 漏译残留中文 | 警告 | lang 非中日韩时出现汉字 |
| 4 | 单位制混用(英制 + 公制并存) | 警告 | 词边界匹配,避免 mainland 命中 in |
| 5 | 多币种符号并列 | 警告 | 同文件出现 ≥2 种货币符号 |
| 6 | 千分位风格冲突 | 阻断 | 同时出现 1,234 与 1.234 |
| 7 | 日期格式歧义 | 阻断 / 警告 | a>12 只可能是 DD/MM,b>12 只可能是 MM/DD;两者并存 = 顺序冲突(阻断);全部 ≤12 = 无法判定(警告) |
| 8 | TODO / TBD / 待补充 残留 | 警告 | 否定语境自动降级为提示 |
| 9 | 全角标点出现在非中日韩资料 | 警告 | ,。;:?!()“” 等 |
| 10 | 阿拉伯-印度数字与拉丁数字混用 | 警告 | 同现即报 |
| 11 | RTL 与拉丁混排缺方向控制符 | 警告 | 有阿/希伯来字符 + 拉丁字符,且无 U+200E/200F/202A-202E |
可选第 12 项·长度比:给 --source-dir 后,按同相对路径配对源文与译文,
用实际字符数比值判断;比值超出 --ratio-lo(默认 0.60)~ --ratio-hi(默认 2.00)时告警。
阈值只是「离谱到需要人看一眼」的提示线,不代表译文应该多长,可用参数覆盖。
扫描范围与漏检边界(读报告前必看)
这一节决定你怎么解读「报告里没有问题」这句话。
会扫(文本白名单,16 种扩展名)
.txt .md .markdown .rst .json .csv .tsv .html .htm .xml
.yml .yaml .ini .properties .po .strings
不会扫,且不会报错(静默跳过,只进「已跳过」表)
| 不扫什么 | 后果 | 怎么办 |
|---|---|---|
.pdf .docx .xlsx .pptx 等二进制文档 |
整份文件一条都没查 | 先导出成 .txt / .csv 再扫 |
| 图片、扫描件里的文字 | 一律看不见 | 先做 OCR 导出文本 |
白名单外的扩展名(.log .doc .rtf 等) |
进「已跳过」表,报告里能看到 | 改扩展名或加进白名单 |
.git / __pycache__ / 点开头的隐藏目录 |
刻意跳过 | — |
| 解出 NUL 字节的二进制伪装文件 | 进「已跳过」表 | 确认是不是放错了文件 |
判不了(就算报告 0 问题,这些也仍然要人看)
- 译文对错与语法 —— 本工具只查文本层硬伤
- 术语该选哪个译法 —— 只报「同一组里出现了两种以上」,不判断哪种对
- 任何国家/语言的书写习惯 —— 脚本里没有事实库,这是刻意的
- 文化禁忌、宗教敏感、政治表述 —— 需要目标市场本地人
- 数字是否算错(单价×数量对不对)—— 属报价比价类工具
> 一句话:报告写「无阻断项」只代表上表 11 类规则都没命中,
> 不代表这份资料可以发。它替代的是「肉眼扫一遍」,不是「母语者读一遍」。
反模式(错误做法 vs 正确做法)
| # | 错误做法 | 为什么错 | 正确做法 |
|---|---|---|---|
| 1 | 直接扫 .pdf 资料目录,看到「已检 0 个文件」以为资料没问题 |
0 个文件 = 一个字都没扫,不是没查出问题 | 先导出成文本;扫描范围见上节 |
| 2 | 不给 --lang 就跑,拿「未翻译残留」的结果下结论 |
不给 lang 时按「非中日韩」处理,中文残留一定会报,语种判断全靠猜 | 显式给 --lang ru / ar / en;漏了会给 [警告] |
| 3 | 术语表写成 {"空气炸锅": "air fryer"} |
值不是数组 → 一条都没比对却不吭声(v1.0.1 起报结构错误) | {"空气炸锅": ["air fryer", "airfryer"]},用 --template 生成 |
| 4 | 看到「提示:占位符残留」就跳过不看 | 提示级只说明它出现在否定语境(如「不要留 TODO」);同一份文件可能还有真残留 | 提示项同样要看原文片段 |
| 5 | 报告 0 阻断就发出去 | 见上节「判不了」5 条 | 阻断清零后仍需母语者通读一轮 |
| 6 | 把 --out 指向一个已存在的文件 |
会被当成目录,报环境错误(rc=3) | --out 只给目录;不存在时自动创建 |
| 7 | 为了让报告干净,把 --ratio-hi 调到 99 |
长度比检查形同虚设 | 阈值调宽可以,但要记在报告里说明原因 |
| 8 | 扫完不看「已跳过」表 | 跳过 30 个文件可能正是你要查的那批 | 每次先看概览的「跳过」计数 |
输出契约
每次体检产出两份文件:
本地化体检报告.md—— 概览(文件数 / 阻断 / 警告 / 提示)+ 结论一句话 + 逐文件明细表(严重度|行|类型|原文片段|建议)+ 已跳过文件表 + 交付前自检清单本地化体检报告.json——{"root", "lang", "reports": [...], "skipped": [...]},供 CI 消费
退出码:0 = 体检完成(无论有没有问题)/2 = 参数或数据错误/3 = 环境错误。
接 CI 时加 --fail-on-block,存在阻断项则返回 2。
常见错误与应对
| 现象 | 原因 | 应对 |
|---|---|---|
| 报「没有可检的文本文件」 | 目录里全是二进制,或被隐藏目录规则过滤 | 确认有文本白名单里的 16 种扩展名(见「扫描范围」一节);.pdf/.docx/.xlsx 一律不扫 |
| 报「路径不存在」 | 路径写错 | 检查路径;不会误报成别的错误 |
| 术语表报 JSON 解析失败 | 末尾多了逗号或编码异常 | 本工具已做 utf-8-sig / utf-8 / gbk / utf-16 四档兜底 |
| 术语不一致没报出来 | 术语组只给了 1 个变体,或变体没在文本里出现 | 术语表每组至少给 2 个变体才有比对意义 |
| 「未翻译残留」误报 | --lang 没给或给错(给了 zh 就不会查汉字) |
明确给 --lang,如 ru / ar / en |
| 「占位符残留」标成了提示 | 命中位置前 8 字符内有「不要/勿/避免」等否定词 | 这是刻意设计:在讲规则不等于违规残留 |
| 想只看严重问题 | 报告已按 阻断 → 警告 → 提示 分级 | 先看「阻断」,其余人工确认 |
| 报「术语表组…的值必须是数组」 | 术语表写成了 {"组": "译法"} |
改成 {"组": ["译法1", "译法2"]};只给 1 个变体是允许的(不比对、不误报) |
| 报「术语表路径是目录,不是文件」 | --terms 给到了文件夹 |
给到 terms.json 这个文件的完整路径 |
| 某个文件没出现在报告里 | 进了「已跳过」表(扩展名不在白名单 / 疑似二进制) | 看报告第三章;跳过不等于没问题 |
打印了 [警告] 未给 --lang |
你没给语种 | 补 --lang ru;不补也能跑,但语种相关规则按「非中日韩」处理 |
异常与兜底
| 异常场景 | 行为 | 退出码 |
|---|---|---|
| 单个文件读不动(损坏/权限/异常二进制) | 记入「已跳过」并继续扫下一个,不中断整批 | 0 |
| 源文目录里配对文件读不动 | 同上,记入 skipped | 0 |
目录含 __pycache__ / .git / 点开头文件 |
自动跳过,不打进结果 | 0 |
参数错误、--help |
argparse 抛的 SystemExit 被显式捕获(SystemExit 是 BaseException,except Exception 抓不到) |
2 / 0 |
--demo 与 --path 同传 |
明确报错,不静默丢一个 | 2 |
--ratio-lo 为负或 ≥ --ratio-hi |
报错 | 2 |
| 术语表不是对象 | 报结构错误,不抛裸 AttributeError |
2 |
术语表组的值不是数组({"g": "abc"}) |
报结构错误并给出正确格式 —— 不能静默跳过,否则用户以为比对过了 | 2 |
--terms 指向目录 |
报「路径是目录,不是文件」,不再抛 PermissionError 后被顶层兜底误报成「未预期异常」 |
2 |
文件解出 NUL 字节(二进制伪装成 .txt) |
判为疑似二进制,记入「已跳过」并继续,不把垃圾当正文扫 | 0 |
未给 --lang |
打印 [警告] 说明按「非中日韩」处理,不阻断(给了才能查漏译) |
0 |
| 带 BOM 的文件 | utf-8-sig 优先解码(utf-8 解 BOM 不报错但留 U+FEFF,会让后续匹配全部偏移) |
0 |
| GBK 编码文件 | 四档编码兜底读取 | 0 |
| 输出目录不可写 / 创建失败 | 报环境错误并提示换目录 | 3 |
| 产物写入后为空或缺失 | 成功路径也二次校验产物 | 3 |
| 未预期异常 | 顶层兜底输出 [错误] 未预期异常,不抛裸 traceback |
2 |
退出码封闭为 0 / 2 / 3(--fail-on-block 命中阻断时返回 2)。
实战案例(真实跑通,数字为脚本实跑输出)
需求:一批俄文/阿文产品资料明天要发给客户,发出前扫一遍硬伤。
选型:有具体文件、有目标语种、有术语表 → 走 --path + --lang,不走 --demo。
数据:4 个示例文件(ru/product_ru.txt、ru/catalog_ru.md、ar/page_ar.html、en/spec_en.txt),
目标语种 ru。
命令:
python scripts/loc_audit.py --path ./待发资料 --lang ru --out ./体检输出
产出(脚本实跑):已检 4 个文件|阻断 0|警告 9|提示 2。抓到的典型问题:
| 文件 | 问题 |
|---|---|
| ru/product_ru.txt | 单位制混用(inch 与 cm)、货币符号混用($ 与 ₽)、日期 03/04/2026 无法判定顺序、第 6 行留着 TODO: уточнить |
| ru/catalog_ru.md | 第 4 行残留中文 + 全角冒号;不要留 TODO 因否定语境被正确降级为提示 |
| ar/page_ar.html | 阿拉伯数字与拉丁数字混用、RTL 与拉丁混排缺方向控制符 |
| en/spec_en.txt | 单位制混用 |
另一组对照(含阻断项的两行文本):已检 2 个文件|阻断 2|警告 0——
千分位风格冲突(1,234 与 1.234 并存)与日期顺序冲突(03/04 与 20/04 并存)。
这两条正是「金额被读错 1000 倍、交期被读反」的典型成因。
已验证清单(全部 2026-10-01 实跑,未跑的不写)
| # | 验证项 | 结果 |
|---|---|---|
| 1 | --selftest 自检 |
30/30 通过(v1.0.1) |
| 2 | U+FFFD 替换字符判阻断 | 通过 |
| 3 | 乱码特征串判阻断 | 通过 |
| 4 | 中文残留在 ru 资料告警;lang=zh 时不告警(参数真生效) |
通过 |
| 5 | 单位制混用告警 | 通过 |
| 6 | 词边界:mainland 不命中 in(子串抢占反例) |
通过 |
| 7 | 千分位风格冲突判阻断 | 通过 |
| 8 | 日期:全部 ≤12 判警告;顺序冲突判阻断 | 通过 |
| 9 | 占位符残留判警告;否定语境降级为提示 | 通过 |
| 10 | 全角标点在非中日韩资料告警 | 通过 |
| 11 | 阿拉伯数字与拉丁数字混用告警 | 通过 |
| 12 | RTL 混排缺方向标记告警;补上标记后不再告警 | 通过 |
| 13 | 术语不一致告警;只给 1 个变体时不误报 | 通过 |
| 14 | 长度比:超区间告警 / 正常区间不告警 | 通过 |
| 15 | GBK 编码文件可正常读取 | 通过 |
| 16 | BOM 检测 | 通过 |
| 17 | walk 跳过隐藏文件、__pycache__、非白名单扩展名 |
通过 |
| 18 | 单条读取失败不中断整批(坏文件进 skipped) | 通过 |
| 19 | --demo 出 md + json 双份产物 |
通过 |
| 20 | 12 条错误路径(缺 --out / demo+path 互斥 / 路径不存在 / 无参数 / 术语表缺失 / ratio 非法 / 源文目录不存在 / 坏术语表 JSON 等) | 全部 rc=2,无 traceback |
| 21 | --fail-on-block 有阻断时返回 2、无阻断时返回 0 |
通过 |
| 22 | 成功路径二次校验产物非空 | 通过 |
| 23 | 解出 NUL 字节的二进制伪装 .txt 被拒(不再被 latin/utf-16 兜底当正文扫) |
通过(v1.0.1 新增) |
| 24 | 真·单条失败不中断:坏文件进「已跳过」,好文件照常出报告 | 通过(v1.0.1 新增;原第 20 条实际测的是「两个都被读出来」,没测到跳过路径) |
| 25 | 术语表组值写成字符串 {"g": "abc"} → 报结构错误 rc=2;写成数组则放行;单变体不报错 |
通过(v1.0.1 新增) |
| 26 | --terms 指向目录 → 报「路径是目录,不是文件」,不再报 PermissionError |
通过(v1.0.1 新增) |
| 27 | 未给 --lang → 打印 [警告] 且 rc=0,不阻断 |
通过(v1.0.1 新增) |
| 28 | 连跑两次产物字节完全一致(含时间戳,幂等) | 通过(v1.0.1 实测) |
| 29 | .log 在白名单外 → 进「已跳过」表,md 报告含「已跳过」章节 |
通过 |
| 30 | 超长行(20 万字符)不卡死、正常出报告 | 通过 |
发布前质量门:frontmatter 四字段(slug/displayName/version/description)齐全且
tags 为行内数组 / 目录无点开头文件、__pycache__、*.pyc、*.bak.* / 冒烟零报错 /
全文档异常 Unicode 扫描通过 / 行首围栏偶数且行内裸围栏为 0。