lang属性必须显式设在每个含文本的语义化标签上,仅改document.documentelement.lang无效;浏览器、屏幕阅读器、字体回退及标点间距均依赖各元素自身lang值,不继承父级。

lang 属性必须显式设在每个含文本的语义化标签上
只改 document.documentElement.lang 是无效操作。浏览器、屏幕阅读器、字体回退链、标点间距规则,全部依赖**每个元素自身的 lang 属性值**,不继承父级。哪怕你写了 ,<p></p> 里顿号仍按英文窄间距渲染,<pre class="brush:php;toolbar:false;" lang="bash"></pre> 的代码字体被中文字体覆盖,<img alt="logo"> 的 alt 文本仍被读作英文。
必须手动为所有含文本的语义化标签显式加 lang:
-
<h1 lang="zh-Hans"></h1>、<p lang="zh-Hans"></p>、<section lang="zh-Hans"></section> - 已有特殊语言的元素(如
<pre class="brush:php;toolbar:false;" lang="bash"></pre>、<code lang="sql">)要保留原值,这是合法混排场景 -
<script></script>和<style></style>内部不要写lang,它们不参与文本渲染
data-i18n 只作用于 textContent,其他属性需显式后缀
data-i18n 默认只替换元素的 textContent。它对 placeholder、title、alt、aria-label 等属性完全无效——这是最常踩的坑。
常见错误现象:<input placeholder="Search"> 切换语言后还是英文;<img alt="user avatar"> 的替代文本没更新;<label for="email">Email</label> 文字翻了但 for 属性没同步,点击 label 失效。
正确做法是显式加对应后缀:
data-i18n-placeholder="search_hint"data-i18n-alt="avatar_desc"data-i18n-title="tooltip_info"data-i18n-aria-label="close_dialog"
value 属性一般不翻译(属于用户输入数据),跳过处理;但 <button></button> 和 <input type="submit"> 的显示文案建议统一用 textContent 更新,避免 value 被意外提交。
JSON 语言包必须扁平 + 键名对齐 + BCP 47 命名
语言包结构决定可维护性。嵌套键(如 {"ui": {"header": {"title": "Home"}})会导致路径不可预测、key 查找失败、diff 工具失效。
工程化要求:
- 每个语言一个文件:
./locales/zh-Hans.json、./locales/en-US.json、./locales/ja.json - 所有文件结构扁平,键名严格一致:
"btn_submit": "提交"、"btn_submit": "Submit" - 某语言暂未翻译,也要保留键并设为空字符串:
"btn_submit": "",否则 JS 查不到就留白 - 文件名和
lang值必须符合 BCP 47 标准:zh-Hans✅,zh_CN❌,chinese❌
加载时用 fetch(),加 try/catch 包裹;fallback 顺序:先试完整码(zh-HK),再截主语言(zh),最后退到默认语言(en)。
动态插入的 DOM 必须手动触发翻译
AJAX 加载的弹窗、表格行、懒加载模块插入后,data-i18n 标记只是字符串,不会自动变成对应语言文本。
典型场景:
- 点击按钮打开的
<modal></modal>,内部<h2 data-i18n="modal_title"></h2>不会自动更新 - 分页表格每页 AJAX 获取新
<tr>,新行里的 <code>data-i18n保持原始键名 - 富文本编辑器插入的内容、第三方组件挂载的节点,都需插入后立即遍历并调用翻译函数
不要依赖 MutationObserver 自动监听——它无法感知语言上下文变更,也无法保证执行时机早于渲染,容易漏掉或重复执行。
真正难的不是加 data-i18n,而是确保每次 DOM 插入、属性变更、lang 同步、格式化重建都落在正确的生命周期里。漏掉任意一环,就会出现文字翻了但语音读错、标点乱、字体糊、输入框提示仍是英文的现象。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











