lang 属性仅在 标签中显式声明且符合 bcp 47 规范(如 zh-cn)时才生效,其他位置的 lang 仅影响局部,根节点缺失或错误将导致 :lang() 失效、翻译/读屏等功能异常。

lang 属性不继承,但浏览器会基于语义“推断”语言上下文——这不是 DOM 属性复制,而是渲染和辅助技术在查找时向上遍历的逻辑行为。真正起作用的只有显式声明的 lang 值,且必须写在 上才构成文档默认语言源。
为什么lang写在<div>里基本没用<p>屏幕阅读器、Chrome 翻译按钮、CSS <code>:lang() 伪类、拼写检查都只认 这个位置。写在 <div lang="en"> 或 <code><p lang="ja"></p> 只影响该元素及其未显式设 lang 的后代,但前提是根元素已声明合法语言值。否则,整个文档语言为“未知”,连 :lang(zh) 都不会匹配任何元素。
-
→ 被忽略,无效
-
<meta http-equiv="Content-Language" content="zh-CN"> → HTML5 已弃用,所有现代浏览器无视
-
→ 大小写错误,BCP 47 要求全小写,静默失效
-
→ 下划线非法,等同于没写
:lang(zh) 匹配失败的真正原因
→ 被忽略,无效<meta http-equiv="Content-Language" content="zh-CN"> → HTML5 已弃用,所有现代浏览器无视 → 大小写错误,BCP 47 要求全小写,静默失效 → 下划线非法,等同于没写:lang(zh) 匹配失败的真正原因写了 CSS 规则却没生效,90% 是因为 HTML 根节点缺失或值不合规,而不是选择器写错了。注意::lang(zh) 和 [lang="zh"] 完全是两套匹配逻辑:
-
:lang(zh)向上查找最近合法lang值(支持zh、zh-CN、zh-Hans),命中后可穿透到子元素(即使子元素没写lang) -
[lang="zh"]是精确字符串匹配,只抓lang="zh",不兼容zh-CN,也不继承 - 混排场景下,
<p>Hello 世界</p>中的「世界」不会被:lang(zh)选中,除非整个<p></p>显式带lang="zh",或「世界」被包在<span lang="zh"></span>里
动态改document.documentElement.lang为什么常白忙活
JS 执行 document.documentElement.lang = "ja-JP" 只更新 DOM 属性,但不会触发已挂载元素的语言重解析:
- 已存在的
<p>こんにちは</p>仍按旧lang渲染,屏幕阅读器不会切换语音引擎 - CSS
:lang(ja)不会重新应用到已有元素,除非强制重绘(不现实) - 正确做法是:给目标文本容器显式加
lang="ja",例如<blockquote lang="ja"></blockquote> - SPA 多语言切换时,更稳妥的是服务端或构建时输出正确
;若必须前端控制,应配合document.title = document.title强制读屏软件重读根节点
哪些地方必须显式写lang,不能靠“继承”
所谓“继承”只是语义推断,对无障碍、翻译、字体回退等关键路径毫无帮助。以下内容必须单独标注:
- 外文引文:
<blockquote lang="fr">Merci beaucoup.</blockquote> - 代码注释或标识符:
<pre class="brush:php;toolbar:false;" lang="en"># Initialize counter</pre>(不是lang="bash") - 嵌入的专有名词或术语:
<abbr lang="en">API</abbr>,避免中文 TTS 错读成“阿皮” - 双语对照表格中的单列:
<td lang="en">English</td> <td lang="zh">中文</td> - 不要给
<div> 或 <code><span></span>这类无语义容器加lang,除非它包裹的内容确实有独立语言上下文最易被忽略的点:CMS 导入的富文本、用户提交的评论、第三方 SDK 插入的文案——这些内容几乎从不带
lang,但它们一旦混在主语言页面里,就会破坏语音朗读节奏和拼写检查准确性。这不是“加个属性就完事”的问题,而是要建立内容注入时的语言元数据校验机制。











