必须为每个元素单独设置 lang 属性,因为屏幕阅读器、浏览器翻译和 :lang() 选择器仅在 DOM 解析初期读取各节点自身 lang 值,根节点 lang 仅继承生效,无法动态影响已渲染节点。

只改 document.documentElement.lang 就切换语言,页面看起来变了,但屏幕阅读器、浏览器翻译、CSS :lang() 选择器全都不认——它只是改了个属性值,DOM 已渲染完成,没人重新解析。
为什么必须给每个元素单独设 lang 属性
浏览器和辅助技术(如 NVDA、VoiceOver)在 DOM 解析初期就读取每个节点自身的 lang 值,决定语音引擎、字体回退、标点间距和拼写检查。根节点的 lang 只影响未显式声明 lang 的子节点,且仅限继承场景。一旦你写了 <p lang="en">API</p>,它就彻底脱离根语言控制。
常见错误现象包括:
- 英文术语被中文语音引擎硬读成“阿皮”
-
<pre class="brush:php;toolbar:false;" lang="bash"></pre>被当成中文代码块,语法高亮错乱 -
:lang(zh-CN) { quotes: "「" "」"; }样式完全不生效,因为元素实际是lang="zh"或lang="zh-Hans"
data-i18n 标记怎么打才不漏关键内容
所有要动态替换的文本节点(<h1></h1>、<p></p>、<button></button>、<label></label>)必须显式加 data-i18n,值为语言包里的键名,比如 data-i18n="nav_home"。但仅这样不够:
立即学习“前端免费学习笔记(深入)”;
为跨境电子商务生成多语言、SEO 优化的产品描述。支持英语、西班牙语、俄语和阿拉伯语,并针对不同搜索引擎(Google / Bing、Yandex、中东市场)进行专项 SEO 优化。适用于以下场景:用户需为国际市场创建符合 SEO 规范的产品文案、批量生成产品描述,或针对特定搜索引擎及区域用户偏好优化内容。
-
placeholder、title、alt这类属性不会被textContent覆盖,得额外加后缀:如data-i18n-placeholder="search_hint" -
value一般不翻译(用户输入数据),跳过处理;但<label></label>文字必须标记 - 含 HTML 结构的文案(如“请阅读使用条款”)需用
innerHTML替换,且语言包对应值必须是可信纯 HTML,否则有 XSS 风险 - 别在
<script></script>、<style></style>、<pre class="brush:php;toolbar:false;"></pre>内部加data-i18n——这些节点不参与文本渲染,JS 替换无效
切换语言时如何同步更新所有 lang 值
切换前必须遍历所有已带 lang 属性的元素(如 <p lang="en"></p>、<blockquote lang="ja"></blockquote>),把它们的 lang 值也同步更新,除非你明确想保留原语言(比如一段日文引用始终该是日语)。
实操建议:
- 先用
getComputedStyle(document.body).scrollBehavior记录滚动位置,更新 DOM 后立刻window.scrollTo()恢复,否则页面跳回顶部 - 不要用
document.querySelectorAll('[lang]')然后暴力覆盖——有些lang是语义必需的(如<pre class="brush:php;toolbar:false;" lang="en"></pre>),应按业务逻辑分类处理 - 若页面含第三方组件(如地图、图表库),它们内部可能插入带
lang的 DOM,需在组件加载完成后手动补扫
JSON 语言包和加载逻辑最容易翻车的点
每个语言一个独立文件,路径统一为 ./locales/${lang}.json,例如 ./locales/zh-CN.json、./locales/en-US.json。结构必须扁平、键名严格一致:
- 所有文件字段完全对齐,某语言暂未翻译也要保留键,设为空字符串:
"btn_submit": "",否则查不到 key 就留白 - 加载必须用
fetch(),别硬编码对象到 JS 里;加try/catch包裹fetch和response.json() - fallback 顺序必须是:先试完整码(如
zh-HK),再截主语言(zh),最后退到默认语言(如en) - 服务器返回 JSON 时,
Content-Type必须是application/json,否则fetch可能静默失败
BCP 47 标准不能妥协:zh-CN ✅,zh_CN ❌,chinese ❌,zh-hans-cn ❌(IANA 不收录三段式,Chrome 会静默降级为 zh)。


















