首页 › tutorials › 造i18n技能:硬编码转多语言

造i18n技能:硬编码转多语言

概述

本教程带你从零构建一个名为 i18n-extract 的 Claude Code 自定义技能,它能扫描源码中散落的硬编码用户文案,自动抽成翻译 key、改写成 t('...') 调用、并播种多语言翻译文件。适合已写过几个 Skill、想掌握「目录结构 + 支撑文件 + 内嵌脚本 + 权限预授权 + 描述调优」完整闭环的开发者。学完你能独立造出任何「知识库 + 脚本」型技能。

前置条件

一、先搞懂:什么时候该造一个 Skill,而不是写进 CLAUDE.md

很多人会把所有规范都塞进 CLAUDE.md,结果文件膨胀到几千行,每次会话都全量加载、持续烧 token。官方文档对二者的分工给了一条清晰的判据:

当你反复把同一段指令、清单或多步流程粘贴进对话,或者 CLAUDE.md 的某一节从「事实」长成了「流程」时,就把它抽成 Skill。与 CLAUDE.md 不同,Skill 的正文只有在被使用时才加载,长篇参考资料在用不到时几乎零成本。

i18n 国际化正是这种典型场景:它不是一条「事实」(像「我们用 React 18」),而是一套多步流程(扫字符串 → 提 key → 改代码 → 生翻译文件 → 校验),偶尔才用一次,但一旦用起来要执行十几个精确步骤。把它做成 Skill,平时只占一行 description,触发时才把整套流程连同脚本、词表一起灌进上下文。

判断清单(满足任意一条就值得抽成 Skill):

信号 例子
反复粘贴同一套步骤 每次发版前手动跑国际化检查清单
需要附带脚本/数据文件 一个 Python 抽取器 + 一份术语对照表
要按需加载、平时不占上下文 国际化只在接入新语言时才做
想让 Claude 自动判断何时触发 用户说「把这些文案翻译成英文」时自动启用

二、解剖 SKILL.md:frontmatter + 正文两段式

每个 Skill 就是一个目录,入口文件必须叫 SKILL.md,由两部分组成:

  1. YAML frontmatter(--- 之间):告诉 Claude「这是什么、何时用、谁触发」。
  2. Markdown 正文:Claude 真正执行的指令,可引用目录内的其它文件。

官方给的最小骨架长这样:

---
description: Summarizes uncommitted changes and flags anything risky.
---
## Current changes
!`git diff HEAD`
## Instructions
Summarize the changes above in two or three bullet points...

关键 frontmatter 字段(全部可选,只有 description 强烈推荐):

字段 作用
name 显示名,默认用目录名
description Claude 用来判断何时自动加载;关键用例放最前面
when_to_use 触发短语/示例,拼接到 description 后,共享 1536 字符上限
allowed-tools 技能激活时免审批可用的工具
disable-model-invocation 设 true 则只能手动 / 调用
user-invocable 设 false 则从 / 菜单隐藏,只给 Claude 自动用
argument-hint / arguments 自动补全提示与具名位置参数
model / effort 激活期间覆盖会话模型与思考强度

正文里还能用一组替换变量,本教程会用到三个:

还有一个极其重要的语法:!`命令` 动态上下文注入。它在正文发给 Claude 之前先把命令跑一遍、用输出替换占位符。下文会用它把脚本扫描结果直接灌进 prompt。

三、实战:从零造 i18n-extract 技能

最终目录结构:

~/.claude/skills/i18n-extract/
├── SKILL.md                      # 入口:流程总览 + 导航
├── references/
│   ├── extraction-rules.md       # 什么算「用户可见文案」
│   └── key-naming.md             # key 命名约定
├── data/
│   └── glossary.yml              # 通用术语中英对照
└── scripts/
    ├── extract_strings.py        # 硬编码字符串抽取器
    └── verify_keys.py            # 翻译文件缺 key 校验

放在 ~/.claude/skills/ 下是「个人级」,对你所有项目生效;想只对某个项目生效,就放该项目的 .claude/skills/。

步骤 1:建目录、写最小骨架

mkdir -p ~/.claude/skills/i18n-extract/{references,data,scripts}

先写一个能跑的最小 SKILL.md,验证触发链路通了再迭代:

---
description: 抽取代码里的硬编码用户文案,生成 i18n 翻译 key 并改写调用。
---
扫描当前项目,找出所有写死的用户可见字符串,提议翻译 key。

在任意项目里 claude 启动后问一句「帮我把代码里写死的中文抽成翻译 key」。如果它自动启用或你能 /i18n-extract 调出,骨架就通了。

预期结果:输入 / 应能在补全里看到 i18n-extract;问「抽文案」时 Claude 会自动加载该技能。若没出现,多半是 YAML 解析失败——加 --debug 启动可看到解析报错。

步骤 2:打磨 description,让它精准触发

description 是技能的「广告位」:Claude 拿它和你的话做语义匹配。差的描述会导致两类问题——不触发(关键词没对上用户原话)或误触发(描述太宽,啥都往里套)。

官方提示:把最关键的用例放最前面,且 description + when_to_use 合计上限 1536 字符。重写如下:

---
description: >
  扫描前端源码中写死的用户可见文案(中文/英文),抽取为 i18n 翻译 key,
  把硬编码改写成 t('key') 调用,并按术语表播种多语言翻译文件。
  Use when the user wants to internationalize a project, extract hardcoded
  UI strings, add multi-language support, generate translation files,
  or translate user-facing text into other languages.
  Triggers: "国际化"、"i18n"、"多语言"、"这些文案翻译"、"硬编码文案"、"提取翻译 key"。
disable-model-invocation: false
allowed-tools: Bash(python3 *) Read Edit Write Grep Glob
---

写 description 的三条经验:

  1. 中英混合 + 触发词列表示例:把用户可能说的原话(中英文)都塞进 when_to_use,命中率最高。
  2. 动词开头说清产出:「抽取…改写…播种…」比「一个 i18n 工具」具体得多。
  3. 限定作用域防误触:加「前端源码」「用户可见文案」,避免在改后端日志时被误启用。

步骤 3:加支撑文件,做渐进披露

正文里只放「总览 + 导航」,把长篇规则、术语表挪到 references/ 和 data/,Claude 按需读取。这能省下大量常驻 token(官方建议 SKILL.md 控制在 500 行内)。

references/extraction-rules.md(节选):

# 用户可见文案判定规则
## 必须抽取
- JSX 文本节点:<button>提交</button>
- 表单属性:placeholder、title、aria-label、alt
- 弹窗/通知:alert(message)、toast(message)、console.warn 之外的用户提示
- 路由/菜单 label、表头、按钮文案

## 不要抽取
- console.log / 调试日志
- API 路径、CSS 类名、事件名
- 已在 t() / i18n() / $t() 内的 key
- 注释里的中文

references/key-naming.md:

# Key 命名约定
结构:scope.context.element[.state]
- common.button.submit
- auth.login.placeholder.email
- order.detail.status.pending
全小写,点分层级,禁止中文/空格。

data/glossary.yml(术语对照表,脚本播种翻译时优先套用):

submit: 提交
cancel: 取消
confirm: 确认
loading: 加载中
email: 邮箱
password: 密码
welcome: 欢迎

然后在 SKILL.md 里挂上导航指针:

## 参考资料
- 判定哪些字符串算「用户可见」:见 [references/extraction-rules.md](references/extraction-rules.md)
- key 怎么命名:见 [references/key-naming.md](references/key-naming.md)
- 术语优先翻译:使用 [data/glossary.yml](data/glossary.yml)

步骤 4:挂抽取脚本,用 ${CLAUDE_SKILL_DIR} 定位

国际化最容易踩的坑是 Claude 「猜」字符串——它看不到全仓每个 JSX 节点。正确做法是让脚本先扫一遍,把真实结果灌进 prompt。这就是动态上下文注入 + 内嵌脚本的组合拳。

scripts/extract_strings.py(仅标准库,可独立运行):

#!/usr/bin/env python3
"""扫描前端源码,列出疑似硬编码的用户可见文案。"""
import re, json, sys
from pathlib import Path

SKIP_DIRS = {'.git', 'node_modules', 'dist', 'build', '.next', '__pycache__'}
EXTS = {'.js', '.jsx', '.ts', '.tsx', '.vue'}
# JSX 文本节点 / 常见用户可见属性
PATTERNS = [
    re.compile(r'>([\u4e00-\u9fa5][^<]{0,40})<'),                 # <tag>中文</tag>
    re.compile(r'placeholder="([^"]{1,60})"'),
    re.compile(r'title="([^"]{1,60})"'),
    re.compile(r'aria-label="([^"]{1,60})"'),
    re.compile(r'alt="([^"]{1,60})"'),
    re.compile(r"""alert\(['"]([^'"]{1,60})['"]\)"""),
]

def scan(root: Path):
    hits = []
    for p in root.rglob('*'):
        if any(part in SKIP_DIRS for part in p.parts):
            continue
        if p.suffix not in EXTS:
            continue
        for i, line in enumerate(p.read_text(encoding='utf-8', errors='ignore').splitlines(), 1):
            for pat in PATTERNS:
                for m in pat.finditer(line):
                    val = m.group(1).strip()
                    if val and not re.match(r'^[a-zA-Z0-9_\-/.]+$', val):  # 排除纯标识符
                        hits.append({'value': val, 'file': str(p), 'line': i})
    # 按值去重
    seen, uniq = set(), []
    for h in hits:
        if h['value'] not in seen:
            seen.add(h['value']); uniq.append(h)
    return uniq

if __name__ == '__main__':
    root = Path(sys.argv[1]).resolve()
    print(json.dumps(scan(root), ensure_ascii=False, indent=2))

在 SKILL.md 正文里用 !`...` 把扫描结果直接注入(注意:反引号注入只在行首或空白后才会执行):

## 第 1 步:扫描硬编码文案
先运行抽取脚本,结果已注入下方:

```!
python3 ${CLAUDE_SKILL_DIR}/scripts/extract_strings.py ${CLAUDE_PROJECT_DIR}
```

## 第 2 步:生成 key 并改写
- 对上面每条 `value`,按 [key-naming.md](references/key-naming.md) 生成 key
- 用 Edit 把对应位置的硬编码替换为 `t('scope.context.xxx')`
- 翻译时优先套用 [glossary.yml](data/glossary.yml) 中的固定译法

## 第 3 步:播种翻译文件
生成/更新 `locales/zh-CN.json` 与 `locales/en-US.json`,结构为 `{ "common.button.submit": "提交" }`。

## 第 4 步:校验完整性
```bash
python3 ${CLAUDE_SKILL_DIR}/scripts/verify_keys.py ${CLAUDE_PROJECT_DIR}/locales
```

注意三个易错点:${CLAUDE_SKILL_DIR} 让脚本路径不依赖当前工作目录;多行命令必须用 ```! 围栏而非内联 !` `;注入只跑一遍,命令输出不会被二次扫描。

步骤 5:用 allowed-tools 预授权,省掉审批弹窗

技能要连读带改还要跑脚本,默认每步都弹权限确认很烦。allowed-tools 在技能激活期间给指定工具免审批通行证:

allowed-tools: Bash(python3 *) Read Edit Write Grep Glob

它只「授予」不「限制」——没列的工具仍按你全局权限规则走。对项目级 .claude/skills/ 里的技能,首次需通过工作区信任对话框才生效,所以审阅团队仓库的技能时务必看一眼它的 allowed-tools,一个技能可以给自己授予很宽的工具权限。

步骤 6:测试、迭代、用 evals 量化

技能「被触发」不等于「做对了」。官方推荐的验证方法是基线对比:准备几条真实 prompt,分别在「开启技能」和「禁用技能」的新会话里各跑一遍,对比结果。一定要用新会话,否则你写技能时残留的上下文会掩盖指令的漏洞。

手动流程可以,但更省事的是装官方 skill-creator 插件自动化这条循环:

/plugin install skill-creator@claude-plugins-official
/reload-plugins

然后说「用 skill-creator 评估我的 i18n-extract 技能」。它会:

对 i18n 技能,典型 eval 用例包括:正向(「把这些中文按钮文案抽成翻译 key」应触发并输出 key)、负向(「把后端日志里的 ERROR 翻译一下」不应触发、或触发后应明确拒绝抽取日志)。

四、端到端案例:把一个迷你 React 项目国际化

假设有 src/Login.jsx:

export function Login() {
  return (
    <form>
      <h2>用户登录</h2>
      <input placeholder="请输入邮箱" />
      <input placeholder="请输入密码" type="password" />
      <button onClick={() => alert('登录成功')}>提交</button>
    </form>
  );
}

在项目根执行 /i18n-extract,预期会发生:

  1. 脚本注入扫描结果:用户登录、请输入邮箱、请输入密码、登录成功、提交 五条。
  2. Claude 生成 key 并改写源码:
export function Login() {
  const { t } = useTranslation();
  return (
    <form>
      <h2>{t('auth.login.title')}</h2>
      <input placeholder={t('auth.login.placeholder.email')} />
      <input placeholder={t('auth.login.placeholder.password')} type="password" />
      <button onClick={() => alert(t('auth.login.success'))}>{t('common.button.submit')}</button>
    </form>
  );
}
  1. 套用 glossary(submit→提交)播种 locales/:
// locales/zh-CN.json
{ "auth.login.title": "用户登录",
  "auth.login.placeholder.email": "请输入邮箱",
  "auth.login.placeholder.password": "请输入密码",
  "auth.login.success": "登录成功",
  "common.button.submit": "提交" }
// locales/en-US.json
{ "auth.login.title": "User Login",
  "auth.login.placeholder.email": "Enter your email",
  "auth.login.placeholder.password": "Enter your password",
  "auth.login.success": "Login successful",
  "common.button.submit": "Submit" }
  1. verify_keys.py 比对两个 locale 文件 key 集合一致,输出 ✅ key 对齐。

整个过程从硬编码到双语言可发布,一条 /i18n-extract 搞定。

五、技能加载与触发的完整生命周期

flowchart TD
    A[会话启动] --> B[扫描 skills 目录]
    B --> C[只把 name+description 装进上下文<br/>正文暂不加载]
    C --> D{用户说话 / 输入斜杠命令}
    D -->|语义匹配 description| E[Claude 决定自动加载]
    D -->|/i18n-extract| F[手动加载]
    E --> G[注入一次 SKILL.md 正文]
    F --> G
    G --> H[先跑 !`...` 动态注入<br/>脚本扫描结果]
    H --> I[Claude 读 references/data 按需取用]
    I --> J[用 allowed-tools 免审批执行]
    J --> K[正文留在上下文<br/>后续轮次持续生效]
    K --> L{上下文压缩?}
    L -->|是| M[每个技能保留前 5000 token<br/>共享 25000 token 预算]
    L -->|否| K

两个要记住的机制:正文一旦加载,整段会留在上下文直到会话结束(所以正文要精炼、说做什么而非怎么做);压缩时每个被调过的技能保留最近一次的前 5000 token,多个共享 25000 上限,最近调用的优先保留,老旧的可能被整段丢掉——所以压缩后若感觉技能「失效」,重新 /i18n-extract 一次即可恢复全文。

六、常见坑与排查

症状 原因 解决
/ 菜单里找不到技能 YAML 解析失败,元数据为空 claude --debug 启动看解析报错,修缩进/引号
该触发却不触发 description 关键词没对上用户原话 补 when_to_use 触发短语,关键用例前置
不该触发却乱触发 description 太宽泛 收窄作用域词(如「前端」「用户可见」)
描述被截断 技能太多超出预算(约上下文窗口的 1%) /doctor 看哪些被裁;skillOverrides 把低优先级设 name-only
脚本路径找不到 用了相对路径 改用 ${CLAUDE_SKILL_DIR}/scripts/...
!`cmd` 没执行 ! 前有非空白字符(如 KEY=!\cmd`) | 让!处于行首或空白后;多行用 ``! 围栏
动态注入被禁用 全局设了 disableSkillShellExecution 该策略把注入替换为禁用提示文本,改设置或换纯指令
压缩后技能「失效」 正文被压缩预算裁掉 重新 /i18n-extract 调用恢复全文

排查触发问题最有用的一招:直接问 Claude「你有哪些可用的 skills?」确认它能看到 i18n-extract 及其完整 description。

进阶方向

延伸阅读

训练营适用性评估

维度 评估
适合训练营 中级「Claude Code 进阶开发营」「Agent 工程实战营」
前置要求 学员需先做过至少一个 Hello World 级 Skill
建议教学时长 3.5–4 小时(含动手改造自己的项目)
实操占比 约 60% 代码编写 + 40% 概念与调优
可裁剪点 时间紧可跳过 evals 量化与 context: fork 进阶,保留前 5 步即出可用技能
考核点 学员能独立造出一个「自带脚本 + 数据表 + 精准 description」的领域技能,并通过基线对比验证

参考来源

  1. Extend Claude with skills — Claude Code Docs — 官方技能总览,frontmatter 全字段表、动态上下文注入、生命周期与触发控制
  2. Skill authoring best practices — Claude Platform Docs — 跨产品的技能编写指导,description 与正文写作规范
  3. Evaluating skill output quality — agentskills.io — evals 文件格式与 skill-creator 迭代工作流
  4. Progressive Disclosure Architecture — DeepWiki — 渐进披露在技能目录结构中的工程化实践
  5. Core Concepts and Progressive Disclosure — Anthropic Cookbooks — Skills 系统核心架构与按需加载机制解析

[!tip] 51学通信知识星球 星主:爱卫生(微信:gprshome201101)——公众号「51学通信」创办者,19 年通信行业运维与讲师经验,著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书(各大电商平台有售)。 星球定位:深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域,主打运维岗位与信令分析。 当前规模:5200 篇主题 / 990 个回答,198 元/年。 加入福利:加入星球后请加爱卫生微信,拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题,另有群内分享。 AI 加持:51学通信知识星球已作为 skill 接入 WorkBuddy,使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例,做总结、找文档和 PPT 等场景全覆盖。

51学通信知识星球海报-扫码加入