关于 enhanced-message-context
enhanced-message-context 是在实现或审查 Lingui 国际化消息时添加译者注释的技能,帮助团队识别含义不明或依赖上下文的 UI 文案,同时避免处理自明文本和供应商组件。
主要功能
- 01按歧义和上下文确定优先级
- 02通过 Lingui 宏添加注释
- 03指导注释的措辞和长度
- 04按优先级审查提取后的目录
- 05跳过供应商 UI 组件
- 06使用 ESLint require-comment 规则检查
适用场景
- 01为简短或含义不明的 UI 标签补充说明
- 02解释占位符所代表的内容
- 03提取后审查 Lingui 消息目录
- 04使用 ESLint 检查源消息
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ lingui/ skills --skill enhanced-message-context安装后在 Claude Code 中输入/enhanced-message-context确认已加载。
详细介绍
增强消息上下文
实现 Lingui i18n 时,为需要上下文的消息添加译者注释,让译者能选择恰当的语气、长度和措辞。哪些消息需要注释正是关键问题;下文的分级给出了答案:简短标签本身几乎不提供上下文,而完整句子则提供了大部分上下文,因此两者需要截然不同的处理方式。
先了解应用领域
编写注释前,先确认产品的用途——查看 package.json 中的 description、README,以及路由和组件名称。领域信息有助于消除原本无法判断的歧义:在停车应用中,“Park” 指停车位,而非自然公园;在社交应用中,“Post” 很可能是名词。遇到依赖领域判断含义的词语时,应在注释中说明领域。
何时添加注释
按以下优先级处理:
必须添加注释 — 没有注释就可能翻译错误:
- 有歧义的短字符串:含义或词性不止一种的 1-2 个词的短语
- “Back”(名词还是动词?),“Delete”(按钮还是确认操作?),“Close”(动词还是形容词?)
- 没有可见对象的操作标签:代码知道操作对象是什么,译者却看不到
- “Remove”(移除什么?),“Apply”(应用到什么?)
- 依赖领域的词语:含义取决于产品领域的词语
- “Post”(动词还是名词?),“Tag”(名词还是动词?),“Park”(停车位还是绿地?)
- 取决于语法性别的内容:译文取决于消息所指对象
- “Selected”(阳性、阴性还是中性,取决于选中的对象)
- 含义不明确的占位符:无法从名称看出其中内容的占位符
{count}(什么的数量?){name}(用户名、文件名还是项目名?)
建议添加注释 — 可显著提升质量:
- UI 行话:“Toast”、“Drawer”、“Chip”、“Modal”——译者可能按字面理解的组件名称
- 缩写:“Qty”、“Avg”、“N/A”
- 句子片段:需要由周围 UI 补全的文本(“per month”“of 24”)
- 脱离上下文的标签:表格列标题、工具提示、菜单项
无需注释 — 消息本身已提供上下文:
- 完整且含义清晰的句子。“We couldn't reach the server. Check your connection and try again.” 已向译者说明所有必要信息,无需再添加位置说明。只有在语气或长度确实受限时才添加注释,例如 40 个字符的表格单元格,或要求特定语体的法律用语。
- **注释只会复述文本或文件路径的字符串。**为一段三句话的段落添加“功能页上的功能卡片正文”这样的注释,只有成本,没有信息。
复数分支无需分别添加注释——一条适用于整条消息的注释即可涵盖所有形式。
追求覆盖,而非饱和
几乎每条消息都有注释的目录,说明这些分级被忽略了,而非质量更高。目标是 100% 覆盖必须添加注释的内容,而不是 100% 覆盖整个目录。
有两个实际成本,而且承担成本的是人,而非工具:每条消息都加注释,会让译者习惯性跳过注释,导致关键注释不再被阅读;此外,UI 变动后,每条注释都可能变成过时的描述。
审查现有目录时,应专门衡量必须添加注释的内容,而非整体的 #. 覆盖率——真正需要关注的是没有注释的短消息,而一个总体百分比会掩盖这些问题。以下命令会列出每条没有注释的条目及其 #: 引用,足以快速按优先级分类:
awk -v RS='' '/msgid "[^"]/ && !/#\./' src/locales/en/messages.po
段落模式(RS='')会匹配整个条目,因此不要给匹配模式添加锚点——^msgid 只会匹配以 msgid 开头的条目,而大多数条目都以 #: 引用行开头。
t tagged 模板不能携带自己的注释
这是包装处理后最终没有上下文的最常见原因:tagged 模板形式没有地方可以添加注释。
// ❌ No comment possible — there is no argument to attach one to
<img alt={t`Company logo`} />
// ✅ Object form takes `comment`
<img alt={t({ comment: "Alt text for the logo in the site header", message: "Company logo" })} />
msg`…` 与 msg({ … }) 的区别也一样,裸用 plural(count, { … }) 时也是如此。
在包装时决定注释,而不是事后再补。 对于任何必须添加注释的内容,第一次就使用对象形式。tagged 模板形式是最自然的输入方式,因此把它留到后面处理,就会把一个决定变成机械修改,遍及代码库中的每个属性、占位符和 toast。
Trans 没有这类限制:comment 只是一个 prop,因此 JSX 内容随时都可以就地添加注释。
<Trans comment="Button in the toolbar that returns to the previous page">Back</Trans>
指令替代方案
lingui-set 指令会为文件中其后的每个宏设置 comment,包括 tagged 模板。它是唯一一种无需重写就能为 t`…` 添加注释的方式:
// lingui-set comment="Alt text for the logos in the site header"
<img alt={t`Company logo`} />
<img alt={t`Partner logo`} />
// lingui-reset
只有当一组消息确实共用一个描述时才使用它,例如工具栏、表格或空状态。跨越整个文件的指令会把相同的文字附加到所有互不相关的字符串上;这正是这些级别旨在防止的注释泛滥,而不是对象形式的捷径。
使用 lingui-reset 关闭指令。未关闭的指令会持续作用于之后添加到文件中的消息,因此它的注释最终会描述一些无人打算让它描述的字符串;如果这些字符串也继承了 context,它还会改变提取出的 ID。
不要改动 vendored 组件库
components/ui/** 中的副本——shadcn/ui 或任何由生成器 vendored 的 Radix 封装——不属于你,因此不要为它添加注释或进行包装。shadcn add 会整体覆盖这些文件,因此添加在其中的宏或注释会在下次更新时悄然消失,而且构建仍会通过。
这条规则也适用于本技能的审计流程:当目录条目的 #: 引用指向 vendored 目录时,将其记录为已知遗留项,然后继续检查下一条——不要改动该文件。
如果确实需要翻译这些副本,正确做法是创建由项目维护的包装组件,在其中保存字符串,并将展示工作委托给基础组件。这是项目有意做出的设计决定,不应由上下文处理流程引入。
编写有效的注释
好的翻译注释包括:
-
位置:消息在 UI 中出现的位置
- “顶部导航栏中的按钮”
- “保存图标的工具提示”
- “用户表格中的列标题”
-
操作/用途:发生什么,或它表示什么
- “返回上一页”
- “永久删除所选项目”
- “显示未读通知的数量”
-
消歧:说明词性或含义
- “用作动词,而非名词”
- “指电子邮件地址,而非邮政地址”
- “单数形式;用户看到的是 ‘item’ 还是 ‘items’ 取决于数量”
质量规则
- 描述它出现的位置以及它指代的内容,而不是词语的含义。
- 不佳:“Save——表示存储”
- 良好:“文档编辑器工具栏中的 Save 按钮”
- 控制在约 80 个字符以内。 注释是提示,不是文档。
- 当术语与领域相关时,注明应用领域:“Park——指停车位,不是自然公园”
- 使用项目源语言编写注释。
- 保持术语一致(同一 UI 区域始终使用相同词语)。
comment 与 context
它们解决的是不同问题,不要混为一谈:
comment是给译者的建议。它不会改变消息标识。context会改变消息 ID:相同文本使用两个不同的context值,就会成为两个分别翻译的目录条目。只有当相同源文本确实需要不同译文时才使用它(例如,表示方向的“right”和表示正确性的“right”)。- 绝不要将
context用作命名空间(auth.login、settings.title)。含义相同的相同字符串应共用一个目录条目;命名空间会将它们拆成重复的翻译工作。
API 参考
Lingui 提供了三种添加译者注释的方式:
1. JS 宏(t)
适用于 JSX 之外的 JavaScript 代码:
import { t } from "@lingui/core/macro";
// With comment
const backLabel = t({
comment: "Button in the navigation bar that returns to the previous page",
message: "Back",
});
// With comment and variable
const uploadSuccess = t({
comment: "Success message showing the name of the file that was uploaded",
message: `File ${fileName} uploaded successfully`,
});
2. React 宏(Trans)
适用于 JSX 元素:
import { Trans } from "@lingui/react/macro";
// With comment
<Trans comment="Button that deletes the selected email message">Delete</Trans>
// With comment in a component
<button>
<Trans comment="Label for button that saves changes to user profile">
Save
</Trans>
</button>
3. 延迟/惰性消息(defineMessage / msg)
适用于在使用位置之外单独定义的消息:
import { defineMessage } from "@lingui/core/macro";
const messages = {
deleteButton: defineMessage({
comment: "Button that permanently removes the item from the database",
message: "Delete",
}),
statusLabel: defineMessage({
comment: "Shows whether the service is currently operational. Values: 'Active', 'Inactive', 'Pending'",
message: "Status: {status}",
}),
};
示例
示例 1:含义模糊的短词
之前(无上下文):
<button onClick={goBack}>
<Trans>Back</Trans>
</button>
之后(带上下文):
<button onClick={goBack}>
<Trans comment="Button in the toolbar that navigates to the previous page">
Back
</Trans>
</button>
示例 2:没有上下文的 UI 标签
之前(无上下文):
const columns = [
{ key: "name", label: t`Name` },
{ key: "status", label: t`Status` },
];
之后(带上下文):
const columns = [
{
key: "name",
label: t({
comment: "Column header in the projects table showing project name",
message: "Name"
})
},
{
key: "status",
label: t({
comment: "Column header showing project status: Active, Inactive, or Archived",
message: "Status"
})
},
{
key: "created",
label: t({
comment: "Column header showing the date when the project was created",
message: "Created"
})
},
];
示例 3:特定领域术语
之前(含义模糊):
<button onClick={handlePost}>
<Trans>Post</Trans>
</button>
之后(明确为动词):
<button onClick={handlePost}>
<Trans comment="Button that publishes the content. Used as a verb (to post), not a noun (a post)">
Post
</Trans>
</button>
示例 4:含义不明确的变量
之前(不清楚计数代表什么):
const message = t`${count} items selected`;
之后(已明确):
const message = t({
comment: "Shows the number of email messages currently selected in the inbox",
message: `${count} items selected`,
});
示例 5:不言自明的消息(优先级较低,但仍有价值)
// Message is clear on its own; adding a comment with location still helps translators
<Trans comment="Validation hint shown below the password field on the sign-up form">
Your password must contain at least 8 characters, including one uppercase letter and one number.
</Trans>
工作流
实现或审查 Lingui 消息时:
- 了解领域:明确应用的用途(见上文),以便通过注释消除领域术语的歧义
- 阅读消息:查看字符串本身
- 检查上下文:考虑它在代码中的使用位置和方式
- 思考:“如果看不到 UI,译者会不会误解这段内容?”
- 如果会:在包装消息时,使用对象形式(
t({ comment, message })、Trans comment=…),并说明其位置、用途以及任何需要消除的歧义 - 如果不会:不添加注释,继续处理。不言自明的句子无需位置说明;添加说明带来的成本大于收益
提取后的审查流程
完成一批 i18n 工作后,应审查目录,而不要假设过程中已添加了注释:
- 运行
lingui extract - 扫描源语言
.po文件中没有#.行的条目(comment会写入此处) - 按优先级分类处理——不要把每个没有注释的条目都当作缺陷。 短字符串或含义模糊的字符串、含义不明确的占位符以及领域术语应回到源代码中添加注释。完整且不言自明的句子是正确结果,而不是遗漏
- 跳过
#:引用指向第三方目录的条目(components/ui/**),并将其记录为已知遗留项 - 再次运行
lingui extract,并确认出现了#.行
报告结果时,应说明必须添加注释的条目的覆盖情况,以及有意不处理的内容——例如“所有短标签都添加了注释,120 条不言自明的句子保持原样,25 条第三方目录遗留项”——而不是只给出整个目录的单一百分比,因为这种百分比无法区分这三类条目。
使用 ESLint 强制执行
eslint-plugin-lingui 0.16 及更高版本附带 require-comment 规则,可在源码中报告没有说明的消息,并指出文件和行号,而不是等到提取后才报告。空注释也会被报告。
此规则默认关闭,且开箱即用的配置要求每条消息都有注释——这正是各层级所反对的全面覆盖。全局启用它并非目标。以下两种用法比较合适:
- 临时用作审查步骤。 在某个目录中以
warn级别启用它,列出所有没有注释的消息,按层级分类处理,然后关闭规则。这可以替代上面的awk扫描,并且会在lingui extract之前运行。 - 限定在必须注释的消息密集的目录中——工具栏、表格和列定义、图标按钮标签、空状态。在这些目录中,“每条消息”与“每条需要注释的消息”几乎是同一批,因此永久添加 ESLint
overrides条目的成本很低。
对于本指南的建议,有两个选项值得关注:allowContext: true 可接受使用 context 代替 comment,而 ignorePatterns 会剥离 [CHAR_LIMIT=40] 之类的元数据标签,这样只有元数据的注释仍会被报告为缺少说明。
lingui-set comment="…" 指令可满足其覆盖消息的规则要求。如果以这种方式使用指令,请同时启用 require-directive-reset,该规则会报告未闭合的 lingui-set。
#. Button in the toolbar that navigates to the previous page
#: src/components/Toolbar.tsx:24
msgid "Back"
msgstr ""
注意事项
- 注释会被提取到消息目录中,供译者参考
- 注释会从生产构建中剥离——没有任何运行时成本,但这并不等于没有成本:成本体现在译者的注意力和维护者的维护工作上,而这些正是各层级要合理分配的
- 注释会显示在翻译管理系统(TMS)中
- 在项目的所有注释中使用一致的术语