Skip to content

hitf-cli Translation Feature Design ​

Many frontend projects don't fully plan internationalization from the start. After some business development, pages, dialogs, buttons, status messages, and prompts are scattered across Vue, React, JSX, utility functions, and configuration objects. Manual migration at this point is labor-intensive and easy to miss.

hitf-cli's translation capability is designed for this stage: it doesn't simply search for Chinese and replace it, but tries to understand syntax boundaries in different files, migrating source language text into internationalization calls and generating maintainable locale files.

Core Flow ​

A single hitf tf roughly follows four steps:

  1. Read the source file and determine file type.
  2. Extract text based on source language, preserving each text's position in source.
  3. Generate keys, query cache or translation service, write locale files.
  4. Replace Chinese in source file with $t or t calls.

This flow seems straightforward, but the difficulty lies in "text boundaries."

Why Regex Alone Isn't Enough ​

Chinese can appear in many places:

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

Their replacement syntax differs. Vue template text needs {{ $t(...) }}, attributes need dynamic binding, JSX text needs {t(...)}, JS strings need expression calls.

Using regex alone easily mishandles file paths, regex expressions, comments, style class names, TypeScript types, and interpolation punctuation.

Cache First ​

In real projects, machine translation only solves part of the problem. Menu names, status names, button names, and industry terminology usually need consistent expressions.

Therefore hitf-cli supports importing company translation tables first:

sh
hitf tci .\.hitf\company.xlsx

Recommended format:

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

After import, the cache is organized by language direction:

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

This way the translation process prioritizes the human glossary, and calls the translation service or falls back to source text when missing.

Boundary-Oriented Extraction ​

What really affects the experience in translation migration is often the boundary details.

For example:

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

The ideal result isn't swallowing the left parenthesis into the text:

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

But rather:

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

Another example is resource paths:

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

src is a file reference and shouldn't be translated; alt is visible semantics and can be translated.

Current extraction further handles:

  • JS/TS/JSX/TSX and Vue SFC
  • Vue <script setup lang="jsx"> and <script setup lang="tsx">
  • Template literals, optional chaining, || and ?? fallback text
  • JSX/TSX text nodes and custom display attributes
  • Vue template display text and display attributes
  • Already processed $t(...), this.$t(...), t(...) are skipped
  • Comments, regex, styles, class/className, style, resource paths, TS type syntax are skipped

When you don't want certain code processed automatically, use:

js
// @hitf-i18n-ignore-next

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

// @hitf-i18n-ignore-file

Reports and Consistency Checks ​

During batch migration, whether extraction is comprehensive, which files failed, and whether locale files are missing keys all need machine-readable results.

tf / tfo support JSON report output:

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

-d / --dry-run previews the translation plan only: in a TTY it opens a read-only tree showing the three-level directory > file > text structure (↑/↓ move, ←/→ collapse/expand, type to search, Enter to finish); in a non-TTY environment it falls back to a static tree printout. No translation is performed and no files are written; the report mode is dry-run.

After translation, run:

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

It checks for keys referenced in source but missing from locale files, keys present in locale files but unreferenced in source, keys inconsistent across locale JSON files, and unparseable JSON files.

Multi-Project Support ​

In monorepo scenarios, outDir supports object format to map output directories per project:

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

Use the -p, --project flag to specify the project:

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

If not specified, defaults to the first key in the object.

CLI Language Switching ​

hitf supports switching between Chinese and English interfaces:

sh
hitf lang -l         # show current language
hitf lang zh-CN      # switch to Chinese
hitf lang en         # switch to English

A single hitf tf roughly follows four steps:

  1. Read the source file and determine file type.
  2. Extract text based on source language, preserving each text's position in source.
  3. Generate keys, query cache or translation service, write locale files.
  4. Replace Chinese in source file with $t or t calls.

This flow seems straightforward, but the difficulty lies in "text boundaries."

Why Regex Alone Isn't Enough ​

Chinese can appear in many places:

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

Their replacement syntax differs. Vue template text needs {{ $t(...) }}, attributes need dynamic binding, JSX text needs {t(...)}, JS strings need expression calls.

Using regex alone easily mishandles file paths, regex expressions, comments, style class names, TypeScript types, and interpolation punctuation.

Cache First ​

In real projects, machine translation only solves part of the problem. Menu names, status names, button names, and industry terminology usually need consistent expressions.

Therefore hitf-cli supports importing company translation tables first:

sh
hitf tci .\.hitf\company.xlsx

Recommended format:

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

After import, the cache is organized by language direction:

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

This way the translation process prioritizes the human glossary, and calls the translation service or falls back to source text when missing.

Boundary-Oriented Extraction ​

What really affects the experience in translation migration is often the boundary details.

For example:

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

The ideal result isn't swallowing the left parenthesis into the text:

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

But rather:

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

Another example is resource paths:

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

src is a file reference and shouldn't be translated; alt is visible semantics and can be translated.

Current extraction further handles:

  • JS/TS/JSX/TSX and Vue SFC
  • Vue <script setup lang="jsx"> and <script setup lang="tsx">
  • Template literals, optional chaining, || and ?? fallback text
  • JSX/TSX text nodes and custom display attributes
  • Vue template display text and display attributes
  • Already processed $t(...), this.$t(...), t(...) are skipped
  • Comments, regex, styles, class/className, style, resource paths, TS type syntax are skipped

When you don't want certain code processed automatically, use:

js
// @hitf-i18n-ignore-next

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

// @hitf-i18n-ignore-file

Reports and Consistency Checks ​

During batch migration, whether extraction is comprehensive, which files failed, and whether locale files are missing keys all need machine-readable results.

tf / tfo support JSON report output:

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

-d / --dry-run previews the translation plan only: in a TTY it opens a read-only tree showing the three-level directory > file > text structure (↑/↓ move, ←/→ collapse/expand, type to search, Enter to finish); in a non-TTY environment it falls back to a static tree printout. No translation is performed and no files are written; the report mode is dry-run.

After translation, run:

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

It checks for keys referenced in source but missing from locale files, keys present in locale files but unreferenced in source, keys inconsistent across locale JSON files, and unparseable JSON files.

Source Text Export ​

Before translation, you can export source language texts for manual review or company internal translation workflows:

sh
hitf export-source src/views      # Extract from source code
hitf export-translated            # Read from translated locale JSONs

Both commands share the same output file .hitf/source-texts-<locale>.xlsx with incremental merge and deduplication. The output format is compatible with cache-import.

Rollback Mechanism ​

Translation operations may affect many files, so a rollback capability is provided:

sh
hitf rollback -l          # List available backups
hitf rollback              # Rollback the latest
hitf rollback -n xxx       # Rollback a specific backup
hitf rollback -k 5         # Keep only 5 most recent backups

A backup is automatically created before each translation operation, ensuring you can restore to the pre-translation state.

Ignore Comments ​

When certain text should not be automatically processed, add ignore comments in the source:

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

<!-- @hitf-i18n-ignore-start -->
<div>
  <span>skipped</span>
  <span>also skipped</span>
</div>
<!-- @hitf-i18n-ignore-end -->
js
// @hitf-i18n-ignore-file
const msg = 'entire file is skipped'

These comments can also be inserted automatically by -i deselection: /* @hitf-i18n-ignore-start */ … /* @hitf-i18n-ignore-end */ for JS / TS, <!-- --> for Vue templates, {/* */} for JSX, and <!-- @hitf-i18n-ignore:prop:<attrName> --> above the target tag for Vue template static attributes. JSON files cannot contain comments, so deselected items are recorded in .hitf/i18n-ignore.json and filtered automatically on later runs.

You can also generate IDE code snippets to quickly insert these comments:

sh
hitf codesnippets        # auto-detect IDE
hitf codesnippets vscode # manual selection

Supports VS Code, WebStorm, Cursor, and Sublime Text.

Multi-Project Support ​

In monorepo scenarios, outDir supports object format to map output directories per project:

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

Use the -p, --project flag to specify the project:

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

If not specified, defaults to the first key in the object.

CLI Language Switching ​

hitf supports switching between Chinese and English interfaces:

sh
hitf lang -l         # show current language
hitf lang zh-CN      # switch to Chinese
hitf lang en         # switch to English

Released under the MIT License.