必须显式设置每个含文本元素的lang属性、全覆盖data-i18n标记(含placeholder/title/alt等后缀)、动态dom插入后立即手动翻译、语言包扁平结构+多级fallback——四者缺一不可。

大型HTML项目里做国际化,不是加个 data-i18n 标记、换一次 document.documentElement.lang 就能跑通的。真正在意可访问性、SEO、动态内容和长期维护的团队,必须把 lang 属性、标记覆盖、DOM 更新时机、语言包结构四者绑死——漏掉任何一环,都会在用户看不见的地方出问题。
lang 属性必须逐层显式设置,不能只改根节点
只执行 document.documentElement.lang = 'zh-Hans',页面文字看起来变了,但屏幕阅读器仍在读英文、中文顿号被当英文逗号渲染、<pre class="brush:php;toolbar:false;" lang="bash"></pre> 里的代码字体被中文字体覆盖——因为浏览器和辅助技术完全不看继承,只查每个元素自身的 lang 属性。
- 所有含文本的语义化标签(
<h1></h1>、<p></p>、<section></section>、<footer></footer>)都得显式写lang="zh-Hans",值必须与当前语言包严格一致 - 已有特殊
lang的元素(如<pre class="brush:php;toolbar:false;" lang="bash"></pre>、<code lang="sql">)要保留原值,这是合法混排,不是 bug -
<script></script>和<style></style>内部写lang没意义,它们不参与文本渲染
data-i18n 标记必须覆盖所有可翻译属性,不只是 textContent
data-i18n 默认只更新元素的 textContent,对 placeholder、title、alt、aria-label 等属性完全无效。常见现象是:切换语言后输入框提示仍是英文、图片替代文本没变、label 点击失效(因 for 属性没同步)。
- 每个要翻译的元素至少有 1 个基础
data-i18n键;若含placeholder,额外加data-i18n-placeholder;同理data-i18n-title、data-i18n-alt -
value属性一般不翻译(属用户输入数据),但<button></button>和<input type="submit">的显示文案建议统一用textContent更新 - 含 HTML 结构的文案(如“请阅读服务条款”)必须用
innerHTML替换,且语言包里对应值要是可信纯 HTML 片段(不能含用户输入、不执行 JS),否则有 XSS 风险
动态插入的 DOM 必须手动触发翻译,MutationObserver 不可靠
AJAX 加载的弹窗、分页表格新行、IntersectionObserver 触发的懒加载模块,插入后不会自动识别 data-i18n。你看到的“键名还在那儿”,本质是没调用翻译函数,不是框架没监听到。
- 弹窗打开后,在
modal.appendChild(content)后立即调用翻译函数遍历子节点 - 每页 AJAX 获取的
<tr> 行,插入 <code>tbody后立刻执行翻译逻辑,别等全局扫描 - 不要依赖
MutationObserver自动监听——它无法区分插入的是翻译目标还是脚本/样式节点,且首次插入时可能错过时机 - 路径统一为
./locales/${lang}.json(如./locales/zh-HK.json),加载必须用fetch()+try/catch包裹 - 结构必须扁平,所有语言文件键名严格一致;某语言暂未翻译,也要保留键并设为空字符串:
"btn_submit": "" - fallback 顺序:先试完整码(
zh-HK),再截主语言(zh),最后退到默认语言(en);服务器返回 JSON 时Content-Type必须是application/json,否则fetch可能静默失败
JSON 语言包加载必须带 fallback 且结构扁平,硬编码或嵌套结构会翻车
把语言包直接写进 JS 或用嵌套 JSON(如 {"ui": {"header": {"title": "Home"}}),会导致构建体积膨胀、键名对不齐、fallback 失效、热更新困难。
最常被忽略的是:语言切换时,既要更新文本,又要同步所有已存在的 lang 属性——哪怕只是改了一个 <p></p> 标签,它旧的 lang="en" 还在生效,标点、字体、语音就全乱了。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











