关于 find-unwrapped-strings
find-unwrapped-strings 是一项用于检查 Lingui 项目中未加本地化包装的用户可见字符串的技能。它规定了使用 ESLint 扫描、逐项判断、分批修复及报告结果的流程。
主要功能
- 01使用 Lingui 规则运行 ESLint 扫描
- 02按字符串用途审核扫描结果
- 03提供使用宏包装字符串的指引
- 04限制修复轮次并重新扫描
- 05记录处理结论及遗留项
适用场景
- 01Lingui 迁移后检查本地化覆盖
- 02发现仅检查目录时遗漏的字符串
- 03审核数据与配置中的字符串
- 04跟踪尚未解决的审计结果
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ lingui/ skills --skill find-unwrapped-strings安装后在 Claude Code 中输入/find-unwrapped-strings确认已加载。
附带文件references/eslint-config.md
详细介绍
查找未封装的字符串
何时运行
- 完成 Lingui 设置或迁移到 Lingui 后——两者都会封装自己处理的内容,但都不能证明代码库的其余部分也已处理。
- 当报告显示“构建通过、目录也已填满,但这个屏幕在所有语言环境中仍显示英文”时。
- 在已建立 i18n 的项目中,定期进行覆盖率审计。
漏掉的字符串有规律可循,并非随机出现。JSX 中的字符串会被封装,因为它们看起来像 UI;剩下的则是不像 UI 的显示文案——例如数据模块中的内容(export const products = [{ name: "AeroPress Go" }])、toast 和错误辅助函数中的消息映射,以及配置对象中的标签。
扫描器有意设计得宁可多报。 精准度来自逐条依据跳过列表判断命中项,而不是不断调整扫描器,直到它不再报告结果。安静的扫描器和干净的代码库无法区分,而只有后者值得拥有。
判断时看字符串的作用,而不是所用语言。扫描器会根据结构标记字符串字面量,跳过列表则决定哪些字符串面向用户——字符串看起来像是用哪种自然语言写成的,并非判断依据,弄清这一点纯属额外开销。
步骤 1 — 确保防护措施就位
安装 eslint-plugin-lingui,并按照 references/eslint-config.md 中调优后的选项,将 no-unlocalized-strings 启用为 warn:
npm install --save-dev eslint-plugin-lingui
使用当前发布版本即可——它只是一个 lint 开发依赖,无需用固定版本范围来提供保护。
这项安装是永久性的。 审计只是一次性扫描;该规则才能阻止下一次回归。审计找出 30 个字符串,却没有留下任何防护措施,只能管用几周。
请显式启用该规则。插件的 recommended 和 flat/recommended 预设均不包含此规则,因此扩展预设并不代表获得了覆盖——详情见参考文档。
如果用户拒绝新增开发依赖,还有一次性模式——在仓库外编写调优后的配置,并让 ESLint 使用它,不改动项目:
npx eslint -c /tmp/lingui-audit.config.js --no-config-lookup 'src/**/*.{ts,tsx}' --format json
只有在用户拒绝后才提供此方案,并说明代价:审计可以正常进行,但没有任何措施能阻止下一个未封装的字符串,下一次审计也得从零开始。建议安装依赖。
无论采用哪种方式,只要扫描成功运行且规则报告了结果,此步骤就完成了。
步骤 2 — 扫描
npx eslint 'src/**/*.{ts,tsx}' --format json > /tmp/lingui-scan.json
筛选出 ruleId === "lingui/no-unlocalized-strings" 的结果,并精简为 file:line 加上违规源代码行——参考文档中提供了 jq 命令。这个列表就是待处理清单;从上到下逐项处理。
将规则选项保留在配置文件中,运行 CLI 时不要传入规则标志。带选项的 --rule 会替换已配置的选项,而不是与其合并,导致命中列表大约翻倍,增加类名和日志参数——更长的列表看起来似乎更全面,于是其中的垃圾结果就会通过封装标识符来“修复”。测量数据见参考文档。
如果扫描结果干净,但你有理由怀疑代码库存在问题,请先检查 glob,再相信结果:files 模式 src/**/*.ts 永远不会 lint .tsx,但运行仍会以 0 退出。
步骤 3 — 判断每个命中项
每个命中项都必须有一个处置结果——已包装,或已跳过并记录原因。没有任何命中项可以在未确定处置结果的情况下离开此步骤。
跳过列表位于 lingui-best-practices 技能的“Don't Wrap Non-UI Strings”一节——它是决定哪些内容应保持未包装状态的权威依据。确定处置结果前,请先阅读该节。如果无法查阅,则依据下方的角色测试进行判断。
处置结果取决于字符串的角色,而非它的形式。在数据模块中的 "Best seller" 是购物者会看到的徽标文字——应包装。同一对象中的 "aero-press-go" 是 URL 路径段——应跳过。以下两个问题可以判定有歧义的情况:
- 翻译人员能否在不造成任何问题的情况下更改它?(作为状态值的
"DRAFT":不能。) - 如果这段内容以日语显示,结果是正确的还是错误?(
sku显示成日语就是错误。)
如果仅凭当前代码行无法判断,就查看使用它的地方。字符串的角色由它的使用位置决定,而不是定义位置。
步骤 4 — 分批修复,控制范围
按照 lingui-best-practices 中的宏决策树进行包装。本次审查中最常见的情况:
- JSX 内容 →
Trans - 组件内的字符串(属性、
alert、函数参数)→useLingui()+t`…` - 组件外的字符串 — 数据模块、模块级映射、配置对象 → 使用来自
@lingui/core/macro的msg描述符,并在使用该字符串的位置解析。
最后一种情况正是本次审查要处理的内容,也是最容易修错的情况。模块作用域中的 t 会在导入时,根据当时恰好处于启用状态的区域设置解析一次,此后便不会改变。用 msg 定义,并在渲染时解析:
// src/data/products.ts — definition
import { msg } from "@lingui/core/macro";
export const products = [
{ sku: "sku-1001", slug: "aero-press-go", name: msg`AeroPress Go` },
];
// consuming component — resolution
const { t } = useLingui();
<h2>{t(products[0].name)}</h2>;
字段类型会从 string 变为 MessageDescriptor,因此 TypeScript 会指出所有使用位置——逐一处理。在没有渲染时机的非 React 代码中,i18n._(descriptor) 会在调用时解析,这正是 toast 辅助函数所需的行为。
包装时,按照 enhanced-message-context 添加翻译人员注释:数据模块标签和单词徽标是必须添加注释的情况,因为它们没有周边 UI 可供推断含义。
然后,每轮执行一次:
npx lingui extract --clean
npx lingui compile
并从步骤 2 重新扫描。
范围限制:最多 2 轮修复,或约 40 个文件。 达到任一上限后,停止并报告。人工可以重新运行审查,但无法审查 200 个文件的差异。
步骤 5 — 结束
满足以下任一条件时停止,并说明是哪一种:
- 扫描结果干净。
- 某轮没有包装任何新字符串 — 所有剩余命中项都已确认是误报。根据参考文档规定的规范,将这些具体情况加入规则配置的忽略列表,让防护规则保持安静,同时继续报告其他所有内容。严重级别保持为
warn,规则继续启用,命中项应使用忽略项,而不是eslint-disable注释。 - 已达到上限(2 轮 / 约 40 个文件)。
步骤 6 — 报告
每次扫描中的每个命中项都应归入以下三个类别之一,并注明 file:line:
- 已包装 — 说明更改内容以及使用的宏。
- 已跳过 — 注明跳过列表中的原因(
slug、enum value、log output)。 - 剩余项 — 仍未处理的命中项,无论是否已审查都要逐项列出,以便下次从这里开始。
同时报告防护规则的状态:严重级别,以及你添加的忽略项和每项对应的依据。
在宣称审查已完成之前,请说明每个命中项的处理结果。“我包装了 16 个字符串”不算结果;“包装了 16 个,将 16 个作为标识符和日志输出跳过,剩余 0 个,规则保持为 warn”才算。未报告的剩余项正是这项技能要防止的唯一结果——这些遗漏最初之所以看不见,恰恰是因为没人记录它们。
由其他工作流调用?
编排器(设置技能、TMS 连接技能)可能会提供范围(仅限 src/features/checkout,或迁移涉及的文件)、来自项目规则文件的额外跳过列表条目(品牌名称、产品代码),或已知遗漏(“前一轮检查遗漏了数据层”)。
请遵循这三项要求。流水线和报告约定保持不变:范围会缩小扫描内容,但不会改变报告内容。范围之外的发现应报告为范围外,而不是省略。
相关技能
- lingui-best-practices — 负责维护跳过列表,以及步骤 3 和步骤 4 所用的宏决策树。
- enhanced-message-context — 为这项审查发现的简短、缺乏上下文的标签提供翻译者注释。