正确国际化需为每个含文本的语义化标签显式设置lang属性,data-i18n仅作用于textcontent,placeholder/alt/title等需加对应后缀,动态dom须手动触发翻译,语言包应扁平结构并带fallback机制。

只改 document.documentElement.lang 会让屏幕阅读器读错、标点乱、字体回退失效
这不是国际化,只是改了个标签。浏览器和屏幕阅读器按每个元素自身的 lang 属性决定标点宽度、连字规则、语音朗读方式——不继承父级。所以 <p>你好</p> 即使在 下,若没显式写 <p lang="zh-Hans"></p>,顿号仍按英文间距渲染,<pre class="brush:php;toolbar:false;" lang="bash"></pre> 里的代码字体也可能被中文字体覆盖。
常见错误现象:<p>登录失败,请检查网络</p> 切换语言后仍是中文,但屏幕阅读器读成英文;<img alt="logo"> 的 alt 文本没更新;<pre class="brush:php;toolbar:false;" lang="sql"></pre> 被误设为 lang="zh-Hans",导致语法高亮字体异常。
- 所有含文本的语义化标签(
<h1></h1>、<p></p>、<section></section>、<footer></footer>)都必须显式写lang,值与当前语言包一致(如lang="zh-Hans") - 已有
lang的特殊元素(如<pre class="brush:php;toolbar:false;" lang="bash"></pre>、<code lang="sql">)切换语言时保留原值,这是多语言混排的合法场景 -
<script></script>和<style></style>内部不要写lang,它们不参与文本渲染,设了也白设
data-i18n 只作用于 textContent,对 placeholder/alt/title 完全无效
很多团队标记了 <input placeholder="Search"> 的 data-i18n,却没加后缀,结果切换语言后输入框提示还是英文。这是因为 data-i18n 默认只替换元素的文本内容,而 placeholder、alt、title、aria-label 等是独立属性,需显式声明对应后缀。
正确做法是:<input data-i18n-placeholder="search_hint" placeholder="Search">、<img data-i18n-alt="avatar_desc" alt="user avatar">、<label data-i18n="email_label" for="email">Email</label> —— 注意 for 属性本身不能翻译,但若 for 值和 id 不同步(比如从 email 改成 email_zh),点击 label 就会失效。
-
value属性一般不翻译(属于用户输入数据),跳过处理;但<button></button>和<input type="submit">的显示文案建议统一用textContent更新,避免value被意外提交 - 含 HTML 结构的文案(如“请阅读 服务条款”)必须用
innerHTML替换,且语言包里对应值要是可信纯 HTML 片段(不能带用户输入、不执行 JS),否则有 XSS 风险 - 别在
<script></script>、<style></style>、<pre class="brush:php;toolbar:false;"></pre>内部加data-i18n—— 这些节点不参与渲染,JS 替换无效
动态插入的 DOM 必须手动触发翻译,不会自动监听
使用 AJAX 加载的弹窗、表格行、懒加载模块插入后,如果没调用翻译函数,里面的 data-i18n 标记就只是字符串,不会变成对应语言文本。点击按钮打开的 <modal></modal>,内部 <h2 data-i18n="modal_title"></h2> 不会自动更新;分页表格每页 AJAX 获取新 <tr>,新行里的 <code>data-i18n 保持原始键名。
不要依赖 MutationObserver 自动监听——它无法区分哪些节点是翻译目标,也无法保证语言包已加载完成,容易漏译或重复执行。
- 弹窗插入后立即遍历其子节点,调用翻译函数(如
translateNode(modalEl)) - AJAX 成功回调里,在
appendChild或insertAdjacentHTML后立刻执行翻译逻辑 - 翻译函数应支持传入根节点范围,避免全局扫描拖慢性能
JSON 语言包结构扁平、键名对齐、加载加 try/catch
语言包文件路径统一为 ./locales/${lang}.json(如 ./locales/zh-Hans.json),结构必须扁平、键名严格一致。某语言暂未翻译也要保留键,设为空字符串:"btn_submit": "",否则查不到 key 就留白。
服务器返回 JSON 时,HTTP Content-Type 必须是 application/json,否则 fetch() 可能静默失败;加载时必须用 try/catch 包裹 fetch 和 response.json(),防止因网络或格式错误导致整站 i18n 失效。
- fallback 顺序必须是:先试完整码(如
zh-HK),再截主语言(zh),最后退到默认语言(如en) - 不要硬编码语言对象到 JS 里,不利于热更新和 CDN 缓存分离
- 避免嵌套过深的键(如
{"form": {"login": {"submit": "提交"}}}),增加查找成本和维护难度
真正难的不是把文本替掉,而是让每个 lang 属性、每个 data-i18n-* 后缀、每次动态插入都跟语言状态实时对齐——漏一个,辅助技术就读错,字体就回退错,用户就卡在那一处。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











