最可靠的做法是结合 html 解析器遍历 dom 提取 data-i18n 属性,并用 ast 分析 js/ts 中 t() 等函数调用,仅提取静态字符串参数,同时人工审核 v-html 等内联 html 文案。

直接用正则从 HTML 源码里扫 t('key') 或 data-i18n 是最常见也最容易翻车的做法——它不处理嵌套、忽略注释干扰、无法识别动态拼接,且对 SSR/CSR 混合场景完全失效。
提取 data-i18n 属性时必须遍历 DOM 树,不能靠字符串匹配
HTML 中的 data-i18n 是运行时生效的标记,源码里可能被模板引擎生成、JS 动态插入,甚至藏在 v-if 或 *ngIf 条件块里。单纯 grep 或正则扫描 data-i18n=".*?" 会漏掉大量真实可翻译节点。
- 正确做法是:在构建阶段用真实 HTML 解析器(如
jsdom或cheerio)加载完整 HTML,再document.querySelectorAll('[data-i18n]')遍历获取所有键名 - 要额外检查
data-i18n-placeholder、data-i18n-title等带后缀属性,它们对应不同 DOM 属性,需单独提取 -
<script></script>和<pre class="brush:php;toolbar:false;"></pre>里的data-i18n无效,解析时应跳过这些节点类型 - 若项目用 Vue 或 React,HTML 文件本身只是模板,真正渲染结构得走框架的编译产物(如 Vue 的
.vueSFC 经过vue-template-compiler处理后的 AST)
t() 和 useTranslation() 函数调用必须结合 AST 分析
JS/TS 文件里的 t('nav.home') 或 {t('error.network')} 不能靠正则硬切——比如 t(`hello ${name}`)、t(keys[i])、t(https://www.php.cn/link/263b1243ca2dbeb358777ceabc4a2e4cargs) 全部无法被简单正则捕获,且容易误匹配字符串字面量。
- 推荐用
@babel/parser+@babel/traverse构建 AST 遍历器,只抓取明确调用t、translate、useTranslation等函数的 CallExpression 节点 - 参数必须是静态字符串字面量(
StringLiteral),否则跳过——动态 key 不可翻译,强行提取只会制造空值或 runtime 错误 - React 中的
useTranslationhook 返回的t是闭包变量,需追踪其绑定来源;若t被重命名(如const tr = useTranslation().t),就得同步识别别名 - i18next-cli 默认只支持
t()和key参数为字面量的情况,遇到解构赋值或默认参数展开(const { t } = useTranslation(); t('x'))需确认 CLI 版本是否 ≥ 24.0.0
HTML 内联模板(如 v-html、innerHTML)的文案必须人工审核,不可自动提取
含 HTML 结构的文案(如 "点击@#@#@#@#@#@#@#@#@#@0即表示同意")若直接放进语言包并用 innerHTML 渲染,存在 XSS 风险;若拆成纯文本又丢失语义。这类内容无法安全自动化提取。
- 必须在语言包中用独立字段标识,例如
"tos_link": { "type": "html", "value": "点击@#@#@#@#@#@#@#@#@#@1即表示同意" } - 运行时需用专用渲染函数(如
dangerouslySetInnerHTML加白名单校验,或预编译成 DOM 片段) - 提取工具遇到
v-html、ng-bind-html、innerHTML=等属性时,应报 warning 并跳过,而不是尝试解析其中文本 - 这类文案的 key 命名需带前缀(如
html.tos_agreement),避免和普通文本 key 混淆,也方便后续审计
真正可靠的提取不是“扫出所有字符串”,而是“确认每个字符串在运行时确实会被 i18n 系统消费”。路径、编码、模板语法、框架生命周期——任何一个环节断掉,提取结果就变成一堆无法映射的孤儿 key。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











