Skip to content

hitf-cli 翻译功能设计 ​

很多前端项目一开始并不会完整规划国际化。业务推进一段时间后,页面、弹窗、按钮、状态、提示语已经散落在 Vue、React、JSX、工具函数和配置对象里。此时再手工迁移,工作量大,而且容易漏。

hitf-cli 的翻译能力就是为这个阶段设计的:它不是简单地搜索中文再替换,而是尽量理解不同文件里的语法边界,把源语言文案迁移成国际化调用,并生成可维护的语言包。

核心流程 ​

一次 hitf tf 大致会经历四步:

  1. 读取源文件,判断文件类型。
  2. 按源码语言提取文案,保留每段文案在源码中的位置。
  3. 生成 key,查询缓存或翻译服务,写入语言包。
  4. 把源文件中的中文替换为 $t 或 t 调用。

这个流程看起来直接,但难点都藏在"文案边界"里。

为什么不能只靠正则 ​

中文可能出现在很多地方:

vue
<button>登录</button>
vue
<input placeholder="请输入用户名" />
jsx
<h1>留言({remarkList ? remarkList.length : 0})</h1>
js
const message = '保存成功'

它们替换后的语法并不一样。Vue 模板文本需要 {{ $t(...) }},属性需要变成动态绑定,JSX 文本需要 {t(...)},JS 字符串需要表达式调用。

如果只用正则,很容易把文件路径、正则表达式、注释、样式类名、TypeScript 类型、插值标点一起误处理。

缓存优先 ​

真实项目里,机器翻译只能解决一部分问题。菜单名、状态名、按钮名、行业术语通常需要统一表达。

因此 hitf-cli 支持先导入公司翻译表:

sh
hitf tci .\.hitf\company.xlsx

推荐表格:

zh-CNen-USja-JP
登录Loginログイン
正常状态Normal Status正常状態

导入后,缓存会按语言方向组织:

json
{
  "zh-CN|en-US": {
    "登录": "Login"
  },
  "zh-CN|ja-JP": {
    "登录": "ログイン"
  }
}

这样翻译过程可以优先使用人工词库,缺失时再调用翻译服务或回退原文。

面向边界的取词 ​

翻译迁移真正影响体验的往往是边界细节。

例如:

vue
<h1>留言({{ remarkList ? remarkList.length : 0 }})</h1>

理想结果不是把左括号吞进文案:

vue
<h1>{{ $t('demo.leave_a_message') }}{{ remarkList ? remarkList.length : 0 }})</h1>

而是:

vue
<h1>{{ $t('demo.leave_a_message') }}({{ remarkList ? remarkList.length : 0 }})</h1>

另一个例子是资源路径:

vue
<img src="@/assets/images/ssHome/供应商总数量.png" alt="供应商总数量" />

src 是文件引用,不能翻译;alt 是可见语义,可以翻译。

现在的取词会进一步处理:

  • JS/TS/JSX/TSX 和 Vue SFC
  • Vue <script setup lang="jsx"> 与 <script setup lang="tsx">
  • 模板字符串、可选链、|| 和 ?? 兜底文案
  • JSX/TSX 文本节点和自定义展示属性
  • Vue 模板展示文本和展示属性
  • 已处理的 $t(...)、this.$t(...)、t(...) 跳过
  • 注释、正则、样式、class/className、style、资源路径、TS 类型语法跳过

当某段代码确实不希望自动处理时,可以使用:

js
// @hitf-i18n-ignore-next

/* @hitf-i18n-ignore-start */
/* @hitf-i18n-ignore-end */

// @hitf-i18n-ignore-file

报告和一致性检查 ​

批量迁移时,取词是否全面、失败文件有哪些、语言包是否缺 key 都需要机器可读的结果。

tf / tfo 支持输出 JSON 报告:

sh
hitf tfo src\views views --dry-run --report .hitf\reports\views.json

-d / --dry-run 仅预览翻译计划:终端环境弹出只读树形界面展示「目录 > 文件 > 文本」三级结构(↑/↓ 移动、←/→ 展开/折叠、输入搜索、Enter 结束),非 TTY 环境自动回退为静态树形打印;不执行翻译、不写文件,报告的 mode 为 dry-run。

翻译后可以执行:

sh
hitf tcheck src --report .hitf\reports\tcheck.json

它会检查源码引用了但语言包缺失的 key、语言包中未被源码引用的 key、不同 locale JSON 之间不一致的 key,以及无法解析的 JSON 文件。

忽略注释 ​

当某些文本不希望被自动处理时,可以在源码中添加忽略注释:

vue
<!-- @hitf-i18n-ignore-next -->
<div>{{ brandName }}</div>

<!-- @hitf-i18n-ignore-start -->
<div>
  <span>不翻译</span>
  <span>也不翻译</span>
</div>
<!-- @hitf-i18n-ignore-end -->
js
// @hitf-i18n-ignore-file
const msg = '整个文件跳过翻译'

这些注释也可以由 -i 交互取消选中时自动插入:JS / TS 用 /* @hitf-i18n-ignore-start */ … /* @hitf-i18n-ignore-end */,Vue 模板用 <!-- -->,JSX 用 {/* */},Vue 模板静态属性用 <!-- @hitf-i18n-ignore:prop:<attrName> --> 置于标签正上方。JSON 文件不支持注释,取消选中项会记录到 .hitf/i18n-ignore.json,后续提取自动过滤。

还可以生成 IDE 代码片段来快速插入这些注释:

sh
hitf codesnippets        # 自动检测 IDE
hitf codesnippets vscode # 手动指定

支持 VS Code、WebStorm、Cursor、Sublime Text。

多项目支持 ​

在 monorepo 场景下,outDir 支持对象格式,按项目名映射输出目录:

json
{
  "translationSetting": {
    "outDir": {
      "main": "apps/main/src/locale/module",
      "web": "apps/web/src/locale/module"
    }
  }
}

使用 -p, --project 参数指定项目:

sh
hitf tf apps/main/src/views/index.vue -p main
hitf tfo apps/web/src/views views -p web

不指定时默认使用对象中第一个 key。

CLI 语言切换 ​

hitf 支持中英文界面切换:

sh
hitf lang -l         # 查看当前语言
hitf lang zh-CN      # 切换为中文
hitf lang en         # 切换为英文

一次 `hitf tf` 大致会经历四步:

1. 读取源文件,判断文件类型。
2. 按源码语言提取文案,保留每段文案在源码中的位置。
3. 生成 key,查询缓存或翻译服务,写入语言包。
4. 把源文件中的中文替换为 `$t` 或 `t` 调用。

这个流程看起来直接,但难点都藏在“文案边界”里。

## 为什么不能只靠正则

中文可能出现在很多地方:

```vue
<button>登录</button>
vue
<input placeholder="请输入用户名" />
jsx
<h1>留言({remarkList ? remarkList.length : 0})</h1>
js
const message = '保存成功'

它们替换后的语法并不一样。Vue 模板文本需要 {{ $t(...) }},属性需要变成动态绑定,JSX 文本需要 {t(...)},JS 字符串需要表达式调用。

如果只用正则,很容易把文件路径、正则表达式、注释、样式类名、TypeScript 类型、插值标点一起误处理。

缓存优先 ​

真实项目里,机器翻译只能解决一部分问题。菜单名、状态名、按钮名、行业术语通常需要统一表达。

因此 hitf-cli 支持先导入公司翻译表:

sh
hitf tci .\.hitf\company.xlsx

推荐表格:

zh-CNen-USja-JP
登录Loginログイン
正常状态Normal Status正常状態

导入后,缓存会按语言方向组织:

json
{
  "zh-CN|en-US": {
    "登录": "Login"
  },
  "zh-CN|ja-JP": {
    "登录": "ログイン"
  }
}

这样翻译过程可以优先使用人工词库,缺失时再调用翻译服务或回退原文。

面向边界的取词 ​

翻译迁移真正影响体验的往往是边界细节。

例如:

vue
<h1>留言({{ remarkList ? remarkList.length : 0 }})</h1>

理想结果不是把左括号吞进文案:

vue
<h1>{{ $t('demo.leave_a_message') }}{{ remarkList ? remarkList.length : 0 }})</h1>

而是:

vue
<h1>{{ $t('demo.leave_a_message') }}({{ remarkList ? remarkList.length : 0 }})</h1>

另一个例子是资源路径:

vue
<img src="@/assets/images/ssHome/供应商总数量.png" alt="供应商总数量" />

src 是文件引用,不能翻译;alt 是可见语义,可以翻译。

现在的取词会进一步处理:

  • JS/TS/JSX/TSX 和 Vue SFC
  • Vue <script setup lang="jsx"> 与 <script setup lang="tsx">
  • 模板字符串、可选链、|| 和 ?? 兜底文案
  • JSX/TSX 文本节点和自定义展示属性
  • Vue 模板展示文本和展示属性
  • 已处理的 $t(...)、this.$t(...)、t(...) 跳过
  • 注释、正则、样式、class/className、style、资源路径、TS 类型语法跳过

当某段代码确实不希望自动处理时,可以使用:

js
// @hitf-i18n-ignore-next

/* @hitf-i18n-ignore-start */
/* @hitf-i18n-ignore-end */

// @hitf-i18n-ignore-file

报告和一致性检查 ​

批量迁移时,取词是否全面、失败文件有哪些、语言包是否缺 key 都需要机器可读的结果。

tf / tfo 支持输出 JSON 报告:

sh
hitf tfo src\views views --dry-run --report .hitf\reports\views.json

-d / --dry-run 仅预览翻译计划:终端环境弹出只读树形界面展示「目录 > 文件 > 文本」三级结构(↑/↓ 移动、←/→ 展开/折叠、输入搜索、Enter 结束),非 TTY 环境自动回退为静态树形打印;不执行翻译、不写文件,报告的 mode 为 dry-run。

翻译后可以执行:

sh
hitf tcheck src --report .hitf\reports\tcheck.json

它会检查源码引用了但语言包缺失的 key、语言包中未被源码引用的 key、不同 locale JSON 之间不一致的 key,以及无法解析的 JSON 文件。

源文本导出 ​

在翻译之前,可以先导出源语言文本用于人工审核或公司内部翻译流程:

sh
hitf export-source src/views      # 从源码提取
hitf export-translated            # 从已翻译语言包读取

两个命令共享同一输出文件 .hitf/source-texts-<locale>.xlsx,增量合并去重。输出格式兼容 cache-import,方便后续导入。

回滚机制 ​

翻译操作可能影响大量文件,因此提供了回滚能力:

sh
hitf rollback -l          # 查看可用备份
hitf rollback              # 回滚最近一次
hitf rollback -n xxx       # 回滚指定备份
hitf rollback -k 5         # 仅保留最近 5 个备份

每次翻译前会自动创建备份,确保可以恢复到翻译前的状态。

忽略注释 ​

当某些文本不希望被自动处理时,可以在源码中添加忽略注释:

vue
<!-- @hitf-i18n-ignore-next -->
<div>{{ brandName }}</div>

<!-- @hitf-i18n-ignore-start -->
<div>
  <span>不翻译</span>
  <span>也不翻译</span>
</div>
<!-- @hitf-i18n-ignore-end -->
js
// @hitf-i18n-ignore-file
const msg = '整个文件跳过翻译'

这些注释也可以由 -i 交互取消选中时自动插入:JS / TS 用 /* @hitf-i18n-ignore-start */ … /* @hitf-i18n-ignore-end */,Vue 模板用 <!-- -->,JSX 用 {/* */},Vue 模板静态属性用 <!-- @hitf-i18n-ignore:prop:<attrName> --> 置于标签正上方。JSON 文件不支持注释,取消选中项会记录到 .hitf/i18n-ignore.json,后续提取自动过滤。

还可以生成 IDE 代码片段来快速插入这些注释:

sh
hitf codesnippets        # 自动检测 IDE
hitf codesnippets vscode # 手动指定

支持 VS Code、WebStorm、Cursor、Sublime Text。

多项目支持 ​

在 monorepo 场景下,outDir 支持对象格式,按项目名映射输出目录:

json
{
  "translationSetting": {
    "outDir": {
      "main": "apps/main/src/locale/module",
      "web": "apps/web/src/locale/module"
    }
  }
}

使用 -p, --project 参数指定项目:

sh
hitf tf apps/main/src/views/index.vue -p main
hitf tfo apps/web/src/views views -p web

不指定时默认使用对象中第一个 key。

CLI 语言切换 ​

hitf 支持中英文界面切换:

sh
hitf lang -l         # 查看当前语言
hitf lang zh-CN      # 切换为中文
hitf lang en         # 切换为英文

Released under the MIT License.