只设置 documentElement.lang 无效,必须为每个含文本的语义化标签显式声明 lang 属性,且值需符合 BCP 47 标准(如 zh-Hans),动态插入的 DOM 需手动同步 lang 才能确保标点、字体、语音正确。

只改 <html lang="zh-Hans"> 根节点,其他地方不加 lang,等于没做国际化——浏览器、屏幕阅读器、字体回退链全按默认语言处理,顿号变英文间距、<pre lang="bash"> 里的代码被中文字体覆盖、<img alt> 还是读英文。
为什么 documentElement.lang 赋值后页面文字不变、语音还错
浏览器和辅助技术(VoiceOver、TalkBack)不继承 lang,而是逐个检查每个含文本元素自身的 lang 属性来决定标点间距、字体 fallback、连字规则和语音朗读引擎。只执行 document.documentElement.lang = "ja-JP",不会触发任何已有 DOM 的重渲染或语音切换。
常见错误现象:
-
<p>北京、上海</p>中顿号仍按西文窄间距排版 -
<pre lang="bash">curl -X GET</pre>被中文字体渲染,符号变形 -
<img alt="user avatar">的替代文本仍被屏幕阅读器读作英文
实操建议:
立即学习“前端免费学习笔记(深入)”;
- 所有含文本的语义化标签(
<h1>、<p>、<section>、<label>、<footer>)都必须显式写lang,值与当前语言包一致,如<p lang="zh-Hans">欢迎</p> - 已有明确用途的
lang(如<pre lang="bash">、<code lang="sql">)保留原值,这是合法混排,不是 bug -
<script>和<style>内部不要加lang,它们不参与文本渲染
lang 值必须符合 BCP 47 标准,否则等于没写
浏览器和爬虫只接受小写字母 + 连字符 + 地区码(可选)格式,比如 zh-CN、en-GB、ja-JP。写成 zh_cn(下划线)、ZH-CN(大写)、zh-CN (末尾空格)或 Chinese,都会被忽略。
容易踩的坑:
- 后端返回
zh_ch,前端没标准化就直接赋给document.documentElement.lang - 用
lang="ja"模糊声明:iOS VoiceOver 某些版本会降级为英语发音,lang="ja-JP"更稳妥 - 用
lang="zh":无法区分简繁,zh-Hans和zh-Hant触发的拼音标注、语音引擎逻辑不同
实操建议:
立即学习“前端免费学习笔记(深入)”;
- 语言切换时,先对输入值做标准化:
lang.replace(/_/g, '-').toLowerCase().trim() - 校验是否匹配正则
/^[a-z]{2,3}(-[a-zA-Z]{2,3})?$/,不通过则 fallback 到默认语言
动态插入的 DOM 必须手动同步 lang 属性
AJAX 加载弹窗、分页表格新行、Capacitor 插件生成的 <img>,插入后若不手动设置 lang,就会沿用父容器或文档根节点的旧值,导致语音错、字体错、标点错。
例如:
-
modalEl.innerHTML = '<h2 data-i18n="title"></h2>';后,<h2>没lang,VoiceOver 仍按英文读 - Capacitor 相机回调生成的
<img src="..." alt="photo">,alt文本被读错
实操建议:
立即学习“前端免费学习笔记(深入)”;
- 插入新节点后,立即遍历其子树中所有含文本的语义化标签,批量设置
el.setAttribute('lang', currentLang) - 跳过已带
lang的特殊节点(如<pre lang="bash">),避免覆盖合法混排 - 封装工具函数,如
setLangRecursively(node, lang),支持递归 + 过滤条件
真正麻烦的不是写 lang,而是每次 DOM 变动(尤其是深层嵌套或第三方组件注入)后,漏掉某个 <label> 或 <figcaption> 的 lang 设置——它不会报错,但会在语音、SEO、字体渲染上静默失效。



















