Install any skill in seconds. Free to start, no credit card required.
Get Started Free →处理前端国际化翻译工作。当用户提到需要做国际化、i18n、翻译、多语言支持时使用此 skill。主要功能:1) 识别代码中的硬编码静态文字;2) 判断是否应使用现有公共翻译或需要新增;3) 将翻译添加到对应的语言文件中;4) 将硬编码替换为国际化调用;5) 检查翻译完整性和正确性。
.claude/skills/dy-skill-i18n/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-02 | ✗→✓ | ▲ Improved | — | — |
| case-04 | ✓→✓ | = Same ✓ | — | — |
| case-14 | ✓→✓ | = Same ✓ | — | — |
| case-17 | ✗→✗ | = Same ✗ | — | — |
| case-09 | ✗→✗ | = Same ✗ | — | — |
此 skill 用于处理前端项目的国际化翻译工作。当你需要将代码中的硬编码中文(或英文)文字转换为国际化支持时使用。
前提:以实际项目结构为准,不破坏现有项目规则。
首先探索项目的国际化配置:
✅ 需要翻译:
❌ 不需要翻译:
根据项目实际结构判断翻译应该添加到哪里:
lang/
├── common/ # 公共翻译
│ ├── zh.ts
│ └── en.ts
├── module/ # 模块翻译
│ ├── product/
│ ├── order/
│ └── ...
└── index.ts决策规则:
common/lang/
├── zh_CN.ts
├── en_US.ts
└── index.ts决策规则:
product.*, order.*, common.*根据项目实际结构灵活处理:
添加位置判断:
IF 项目有 common/ 或类似的公共目录 THEN
IF 翻译是通用性质的 THEN
添加到公共目录
ELSE
添加到对应模块目录
ELSE
添加到主语言文件中,使用模块前缀区分根据项目实际的调用方式替换。常见使用方式如下:
vue<!-- Template 中使用 $t --> {{ $t("key.path") }} <!-- 在 script 中使用 --> <script setup> const { t } = useI18n(); const text = t("key.path"); </script> <!-- 属性绑定 --> <el-input :placeholder="t('key.path')" />
通用性质 = 任何模块都可能用到
- 操作类:新增、编辑、删除、查看、导入、导出、提交、审核
- 提示类:确定、取消、关闭,保存、成功、失败
- 占位符类:请输入、请选择、开始日期、结束日期
- 表格类:序号、操作、状态
模块特有 = 只有特定模块使用
- 产品名称、产品编码、产品分类
- 订单号、订单状态
- 客户名称、客户电话直接添加到主语言文件中,使用模块前缀区分:
typescript// zh_CN.ts export default { common: { action: { add: "新增", edit: "编辑" }, }, product: { name: "产品名称", code: "产品编码", }, order: { no: "订单号", status: "订单状态", }, };
如果业务确实需要独立管理(如不同模块由不同人维护),可以在模块目录中重复定义,但建议优先复用公共翻译。
如果翻译中需要动态参数(如用户名、数量、时间等):
typescript // zh_CN.ts export default { deleteSuccess: "删除成功,共 {count} 条记录", welcome: "欢迎 {username} 登录系统", selectedCount: "已选择 {n} 项", };
vue <!-- 使用对象参数 --> {{ $t("deleteSuccess", { count: deletedCount }) }}
<!-- 在 script 中 --> <script setup> const { t } = useI18n(); const message = t("deleteSuccess", { count: deletedCount }); </script>
{count} 或 %{count})如果项目使用 vue-i18n 的复数功能:
typescript// zh_CN.ts(中文通常不需要复数变化,使用相同形式) export default { itemCount: "{n} 个项目 | {n} 个项目", }; // en_US.ts export default { itemCount: "{n} item | {n} items", };
使用方式:
vue{{ $t("itemCount", { n: count }, count) }}
vue-i18n 会根据 count 的值自动选择单数或复数形式。
注意:如果项目没有使用复数功能,将复数和单数分别定义为不同的 key,如 itemCount 和 itemCount_plural。
日期、数字、货币通常需要专门的格式化方法,不要使用简单的翻译:
dayjs, Intl.NumberFormat 等typescript // 仅翻译格式说明文字,不翻译实际日期 dateFormat: { year: '年', month: '月', day: '日' }
typescript // 建议使用专门的格式化,不走翻译 // 如:Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' })
对于 Element Plus、Ant Design 等 UI 框架的组件:
empty-text, confirm-btn-text 等)vue<!-- Element Plus 示例 --> <el-table :data="list" :empty-text="$t('common.table.empty')"> </el-table>
typescript // 错误码映射 error: { 1001: '用户名不存在', 1002: '密码错误', 1003: '账号已被锁定' }
项目中可能存在部分已国际化但风格不一致的代码:
对于 scoped slot 中的静态文字:
vue<!-- 转换前 --> <template #default="{ row }"> <span>产品名称:{{ row.name }}</span> </template> <!-- 转换后 --> <template #default="{ row }"> <span>{{ $t("product.name") }}:{{ row.name }}</span> </template>
vue<script setup> // useI18n 通常由项目自动导入,无需手动 import // 如项目无自动导入,则需要:import { useI18n } from 'vue-i18n' const { t } = useI18n(); // 在方法中使用 function handleClick() { const message = t("common.message.success"); ElMessage.success(message); } // 在计算属性中使用 const statusText = computed(() => { return t(`product.status.${status.value}`); }); </script>
谨慎使用 v-html:
vue<!-- 不推荐:可能有 XSS 风险 --> <span v-html="$t('message.withHtml')"></span> <!-- 推荐:分开处理 --> <span>{{ $t('message.prefix') }}<a :href="url">{{ $t('message.link') }}</a></span>
如果必须使用 v-html,确保内容来自可信来源。
对于使用 render 函数或 JSX 的组件:
typescript// 使用 this.$t(需要确保组件可访问到 i18n 实例) export default { render() { return h("span", this.$t("key.path")); }, }; // 或者通过 provide/inject 传递翻译函数 // 父组件 const { t } = useI18n(); provide("i18n", { t }); // 子组件 const { t } = inject("i18n"); return h("span", t("key.path"));
使用 keep-alive 缓存的组件在切换语言后可能不会重新渲染:
vue<script setup> import { watch } from "vue"; import { useI18n } from "vue-i18n"; const { locale } = useI18n(); // 监听语言变化,强制刷新 watch(locale, () => { // 强制组件重新渲染 refreshData(); }); </script>
vue<keep-alive> <component :is="component" :key="locale" /> </keep-alive>
异步组件的翻译需要在主应用或单独加载:
typescript// 懒加载翻译 const messages = { en: () => import("./locales/en.json"), zh: () => import("./locales/zh.json"), };
作为经验丰富的翻译专家,你需要遵循以下严格要求:
翻译前必须理解文字的使用场景:
| 场景 | 示例 | 翻译注意事项 | | ---------- | ---------------------------- | -------------------- | | 按钮文字 | "提交"、"确认"、"取消" | 简洁有力,通常用动词 | | 表单标签 | "产品名称"、"创建时间" | 名词短语,明确指示 | | 占位符 | "请输入名称"、"请选择日期" | 引导性语句 | | 提示信息 | "操作成功"、"保存失败" | 结果导向,说明状态 | | 对话框标题 | "新增产品"、"编辑品牌" | 动作 + 对象 | | 表格列 | "状态"、"创建人"、"更新时间" | 简洁名词 |
同一个中文词在不同场景可能有不同译法:
示例:中文 "状态"
├── 表格列标题 → "Status"
├── 业务流程中 → "State" 或 "Process Status"
├── 设备状态 → "Device Status"
└── 订单状态 → "Order Status"
示例:中文 "确认"
├── 按钮文字 → "Confirm" 或 "Submit"
├── 二次确认提示 → "Are you sure?"
└── 确认框标题 → "Confirmation"
示例:中文 "查看"
├── 表格操作列 → "View"
├── 查看详情 → "Details"
├── 查看记录 → "View Record"
└── 查看图片 → "Preview"✅ 正确:所有"产品名称"都翻译为 "Product Name"
❌ 错误:有时翻译为 "Product Name",有时翻译为 "Name"示例:
- SKU → Stock Keeping Unit(库存单位)
- MDM → Master Data Management(主数据管理)
- CRM → Customer Relationship Management(客户关系管理)遵循项目的命名规范,项目没有特殊规定时参考以下原则:
命名原则:
- 使用小写字母
- 使用点分隔层级
- 保持简洁但有意义
- 避免缩写(除非是公认的缩写)
层级结构建议:
- 动作相关:{模块}.action.{具体动作}
product.action.add, product.action.edit, product.action.delete
- 字段相关:{模块}.{实体}.{字段名}
product.product.name, product.product.code
- 提示相关:{模块}.message.{类型}
product.message.success, product.message.error
- 占位符:common.placeholder.{类型}
common.placeholder.input, common.placeholder.select
公共部分:common.{类型}.{具体项}
common.action.add, common.action.edit
common.table.index, common.table.action
common.message.success, common.message.error当遇到翻译时,按以下流程决策:
1. 理解文字含义和用途
↓
2. 查看项目现有翻译库
├── 已有相同翻译?→ 直接复用
└── 没有?→ 继续
↓
3. 分析使用场景
├── 什么类型的UI元素?
├── 在什么上下文中使用?
└── 用户期望什么语气?
↓
4. 选择最准确的翻译
↓
5. 判断翻译放哪里
├── 通用性质?→ 公共目录
└── 模块特有?→ 模块目录
↓
6. 检查一致性
├── 是否与现有术语冲突?
└── 是否需要更新术语表?修改完成后,必须验证:
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-17 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-09 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-15 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-16 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-02 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-21 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-10 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-20 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-12 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-19 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-22 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-11 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-08 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-05 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-03 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-13 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-06 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-04 | pass→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-01 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-07 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-18 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-14 | pass→pass | — | — | — | — | — | — | — | — | — | — | — | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of 0 percentage points is the difference between those two pass rates over the 22 comparable cases. 1 case got worse with the skill loaded, and it is included in that figure.
The per-case answers from this run were removed by the retention sweep, so the case table below shows the verdicts without the text either arm produced. The counts above were recorded at the time and are unaffected. Answers are now kept for 180 days.
Other measured skills in the registry, with their headline benchmark lift.