安全提取 data-i18n 文本需:①用 queryselectorall + mutationobserver 覆盖动态/shadow dom;②只取 textcontent 防 xss;③按点分割键名查语言包并 fallback;④延迟至异步组件挂载后执行;⑤处理 html 响应的 bom 和编码问题;⑥domparser 后判空;⑦避免正则误匹配绑定语法。

如何安全提取 data-i18n 标记的文本节点
直接遍历 document.querySelectorAll('[data-i18n]') 是最轻量的做法,但容易漏掉动态插入的元素或 Shadow DOM 内容。必须配合 MutationObserver 监听后续 DOM 变化,否则语言切换后新挂载的组件不会被识别。
关键点在于:只提取 textContent,不碰 innerHTML —— 否则会把内联样式、子标签甚至未转义的 HTML 实体一起带进来,破坏结构或引入 XSS 风险。
- 若元素含
<strong>欢迎</strong>,应保留原始结构,靠语言包里对应键值的 HTML 片段来替换,而非用textContent粗暴覆盖 - 遇到
data-i18n="form.label.required"这类嵌套键名,需按.分割路径,在 JSON 语言包中逐层查找,缺失时 fallback 到 key 字面值 - 不要在
DOMContentLoaded后立刻执行提取 —— 等待所有异步组件(如 Web Components 或 React 子树)完成挂载后再扫描
DOMParser 解析外部 HTML 文件时的编码陷阱
从 fetch('lang/zh.html') 拿到的响应文本,若原始文件是 UTF-8 BOM 编码,DOMParser 在某些旧版 Safari 中会把 BOM 当作不可见字符塞进第一个文本节点开头,导致翻译键匹配失败。
解决方法不是删 BOM,而是统一用 response.text() 后手动剥离:
const htmlText = (await response.text()).replace(/^\uFEFF/, '');
更稳妥的方式是改用 response.arrayBuffer() + TextDecoder 显式声明编码:
const buffer = await response.arrayBuffer();
const htmlText = new TextDecoder('utf-8').decode(buffer);
-
DOMParser.parseFromString(htmlText, 'text/html')不会自动继承<meta charset>,它只认传入字符串的实际字节流 - 如果语言包 HTML 文件里有
<script></script>标签,DOMParser会解析但不执行 —— 这是好事,避免意外运行未授权 JS - 解析后的
doc.body可能为null(比如响应是纯文本或格式错误),务必判空再调用querySelectorAll
正则提取 <lang></lang> 标签内容时的边界问题
像 <lang :count>共{count}条记录</lang> 这种带绑定语法的标签,用 /(?]*>).*?(?=)/g 会因贪婪匹配跨标签失效 —— 比如中间穿插了 <span></span> 就会截断。
真正可靠的是用 DOM 方式解析,而非正则。但若必须用正则(如构建构建时提取工具),得改成非贪婪+排除 的组合:
/(?]*>)[^)/g
- 这个表达式仍无法处理嵌套
<lang></lang>(虽然业务上不该出现),一旦存在就彻底失效 - 遇到换行或空白符较多的 HTML,
[^ 会吞掉所有空格,导致提取出的文本前后无缩进,和原始排版不一致 - 若标签属性含双引号且未转义(如
<lang title="user's name"></lang>),正则中的[^>]*会在第一个"处截断,造成属性解析错误
切换语言时保留用户输入与滚动位置
单纯替换文本会导致 <input> 值丢失、select 选项重置、textarea 光标跳到开头 —— 这不是 bug,是 textContent 或 innerHTML 替换的必然副作用。
正确做法是跳过表单控件,只更新纯展示型节点:
nodes.forEach(node => {
if (node.tagName && ['INPUT', 'TEXTAREA', 'SELECT'].includes(node.tagName)) return;
node.textContent = translatedText;
});
- 滚动位置不能只靠
window.scrollY记录 —— 如果页面有 sticky header 或 fixed footer,需用element.getBoundingClientRect().top锚定具体区块 - 第三方库(如日期选择器)的语言切换必须调用其 API,例如
flatpickr.set('locale', 'zh'),不能只改 DOM 文本 - 服务端注入的
document.documentElement.lang值,前端初始化时要同步给Intl实例,否则new Date().toLocaleDateString()仍用浏览器默认语言
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











