@nstc-cli/i18n
v0.2.2
Published
> TODO: description
Readme
i18n 脚手架(@nstc-cli/i18n)
目标:为 Vue2(可逐步扩展到 Vue3/React) 项目提供一套“初始化配置 → 扫描提取中文 → 生成/合并多语言资源 → 自动替换源码为
$l()调用 → 构建产物/校验”的 i18n 工具链。本 README 是架构设计文档 + 使用说明,会随着实现迭代更新。
1. 现状与问题(基于当前代码)
当前 packages/i18n 已具备:
- 命令入口:
nstc i18n [name],已实现子命令:init:在项目根生成language.config.js(默认配置写死在lib/create.js)etl:读取配置后递归遍历目录(lib/core/index.js),对js/ts调用extractJs(目前仅 parse 并打印 AST)
- 配置缓存:把执行参数 merge 到
~/.nstc-cli/.cache/i18n_cache.yaml - 基础工具:
findFiles(glob)、hasChinese/isChineseChar(中文检测)
当前缺口:
etl只 parse,不产出“中文条目清单/资源文件”。ts/build还未落地(README 中仅有命令草案)。- Vue 文件处理、JS/TS AST 遍历、替换写回、备份、dry-run、资源合并策略等未定。
2. 总体架构(确定版)
把 i18n 流程拆为 4 个阶段,对应 4 个子命令:
init:初始化工程 i18n 配置(生成language.config.js)etl:抽取(Extract)——扫描源码,提取中文与上下文,产出“词条表”ts:转换(Transform)——把源码中文替换为$l(key),并确保 import/依赖注入(本工程要求:ts只做替换,不写资源文件)build:构建(Build)——将词条表与已有资源合并,生成最终资源文件(如src/i18n.json或拆分多文件),并做校验
推荐的运行方式(最常见):
# 1) 初始化(只需一次)
nstc i18n init -s .
# 2) 抽取中文(每次开发/合并都可跑)
nstc i18n etl -s .
# 3) 转换源码(通常在“抽取结果确认后”执行)
nstc i18n ts -s .
# 4) 生成/合并资源(CI 或本地都可跑)
nstc i18n build -s .3. 命令设计
3.1 顶层命令
当前入口:nstc i18n [name]
name:子命令名(init|etl|ts|build)
建议对 packages/i18n/lib/index.js 做扩展:
case "ts"→tsCreate(opts)case "build"→buildCreate(opts)- 将未知命令提示改为包含
init, etl, ts, build
目前仅实现了
init与etl。
3.2 通用参数(建议统一)
| 参数 | 说明 | 默认值 |
| ------------------------- | ------------------ | ----------------------------- |
| -s, --source <path> | 源码根目录 | process.cwd() |
| -e, --exclude <pattern> | 额外排除(glob) | "" |
| --config <path> | 指定配置文件路径 | language.config.js |
| -r, --dry-run | 仅预览不写回 | false |
| -b, --no-backup | 不生成 .bak 备份 | true(注意 commander 语义) |
3.3 init
职责:
- 在
source目录生成language.config.js(若不存在) - 写入默认配置模板(允许后续自定义)
- 将本次 opts merge 到全局 cache(当前已实现)
输出:
language.config.js
3.4 etl
职责(确定版):
- 扫描
basePath(默认./src)下的源码文件 - 识别中文字符串/文本节点
- 产物路径跟随
language.config.js的output - 如果
output已存在:执行 合并去重(保留旧翻译值,补齐新增 key)
产物:
output对应的资源文件(例如./src/i18n.json)
说明:由于 key = 中文原文,本阶段在抽取的同时即可直接补齐资源 key;资源合并策略与 build 一致(不覆盖旧翻译)。
3.5 ts
职责(确定版):
- 读取
extract.json - 仅做“源码替换”:把中文替换为
$l(key) - 插入/补齐 import(如
$l从n20-common-lib引入) - 写回文件(dry-run 只打印 diff/patch)
- 可选生成
.bak
重要约束:
ts不负责生成/合并资源文件,不写i18n.json。
3.6 build
职责(确定版):
- 读取
extract.json - 读取已有资源文件(例如
./src/i18n.json) - 按语言维度合并并输出
- 校验:
- key 唯一
- 资源覆盖率
- 是否存在
$l()引用但资源缺失
4. 配置协议(language.config.js)
当前默认模板(来自 lib/create.js):
output: './src/i18n.json'exclude: 默认排除 node_modules/dist/... 等languages: ['en', 'th', 'vi']rules.vue.label: '$l'rules.vue.importOriginal: "import { $l } from 'n20-common-lib'"
4.1 key 规则(本工程确定:key = 中文原文)
你已经确认:key 直接使用中文原文。
这会带来几个必须明确的规则:
- 稳定性:同样的中文原文,在任何文件中出现,都使用同一个 key(即 key 复用)。
- 可表示性:key 作为 JSON 对象的属性名必须可序列化。
- 规范化(Normalize):为了避免“看起来一样但实际上不同”的 key,必须做规范化:
- 去掉首尾空白:
trim() - 将连续空白压缩为单个空格(可选,但建议开启)
- 换行统一为
\n
- 去掉首尾空白:
- 非法/高风险 key 处理:
- 空字符串:丢弃
- 超长文本(例如 > 200 字):建议提示并跳过,避免资源文件膨胀
- 含有大量变量拼接/模板:不建议直接作为 key(建议交给开发手工改造)
注意:key=中文会导致资源文件的 key 体积较大,但优点是“可读、无需维护映射表”。这是你当前选择的权衡。
4.2 建议扩展字段(保持向后兼容)
module.exports = {
// 扫描根目录(相对 source)
basePath: "./src",
// 资源输出(build 的输出)
output: "./src/i18n.json",
// 中间产物目录
cacheDir: "./.cache/i18n",
// 排除
exclude: ["**/node_modules/**", "**/dist/**"],
// 要生成的语言
languages: ["zh-CN", "en", "th", "vi"],
// key 策略:固定为 chinese(即 key=text)
key: {
strategy: "chinese",
},
rules: {
vue: {
label: "$l",
importOriginal: "import { $l } from 'n20-common-lib'",
translateAttrs: ["title", "placeholder", "label"],
},
js: {
label: "$l",
importOriginal: "import { $l } from 'n20-common-lib'",
},
},
};5. 中间产物(extract.json)
当 key=中文时,中间产物建议变更为:
key=normalizedTexttext仍保留原文(用于定位差异)
示例:
{
"meta": {
"createdAt": "2026-01-22T00:00:00.000Z",
"basePath": "./src",
"toolVersion": "0.0.1",
"keyStrategy": "chinese"
},
"items": [
{
"id": "...",
"key": "中文原文",
"text": "中文原文",
"file": "src/views/a.vue",
"type": "vue-template-text",
"loc": {
"start": { "line": 10, "column": 5 },
"end": { "line": 10, "column": 12 }
},
"context": {
"snippet": "<div>中文原文</div>"
}
}
]
}etl 输出还应额外产出一个“去重后的词典视图”(可选,但强烈建议),便于 build 合并:
dict.json:{ [key: string]: { occurrences: number, files: string[] } }
6. 资源文件结构(你们当前规范:结构2 / key 顶层)
你已经确认:output 资源文件采用 结构2:
{
"确定": { "en": "Confirm" },
"取消": { "en": "Cancel" }
}说明:
- 顶层 key = 中文原文(规范化后)
- value 必须是对象:
{ [lang]: translation }(不允许 string/number 等其它类型) zh-CN语言通常可省略;当缺失时,默认含义为“中文=key”(但为了工具链一致性,etl/build在需要时会补齐zh-CN)
合并策略(etl/build 一致):
- 若
output已存在:- 保留已有翻译(不覆盖已有字段)
- 对每个新 key:补齐缺失语言字段(
zh-CN默认=key,其它语言默认空串) - 可选:输出 unused key 报告(源码已无引用的 key)
7. ts 替换策略(key=中文 的关键点)
7.1 替换规则
$l(key)的key直接使用中文原文:- 推荐输出:
$l('中文') - 若包含单引号
':改用双引号包裹:$l("中'文") - 若同时包含单双引号:使用转义(最终由生成器保证合法 JS 字符串)
- 推荐输出:
7.2 同文案复用
当不同文件出现同样的中文:
extract.json会生成同一个 keyts替换输出相同$l(key)build资源也只维护一条 key
7.3 不替换场景(建议)
- 注释中的中文(默认不替换)
- console/debug 文案(可配置是否替换)
- 单文件内超长大段文本(提示人工处理)
8. 与现有工程的对齐点
- CLI 框架:
@nstc-cli/command+commander - 全局 cache:
~/.nstc-cli/.cache/i18n_cache.yaml - 项目内配置:
language.config.js
建议补齐:
globalConfig()目前只返回 config 文件内容,未将 cache/cli opts 统一 merge 到“最终 config”。建议调整为:- 明确优先级:CLI opts > config 文件 > 全局 cache
- 最终返回
finalConfig
9. Roadmap(实现顺序建议)
- ✅
init(已有) etl:先实现 扫描 js/ts 字符串字面量 中的中文并输出extract.json + dict.jsonbuild:把extract.json/dict.json合并生成i18n.json(zh-CN默认=key,其它语言空串)ts:基于extract.json做源码替换(先从 js/ts 入手,再扩展 vue template;vue template 侧的抽取已升级为 AST 方案)- Vue SFC:template 解析(已采用
@vue/compiler-sfc+@vue/compiler-dom做 AST 级抽取,替换了早期正则 MVP 方案) - 校验与 CI:missing key / unused key 检测
