必须写在<html>标签上,因屏幕阅读器仅初始解析时读取该声明以初始化整页语音引擎;写在<body>、<meta>或<div>中均无效,且lang值须严格符合BCP 47标准(如zh-CN),错误则静默忽略并fallback英文。

lang 属性不写在 <html> 标签上,屏幕阅读器就根本不会按你期望的语言读——它不是“效果差”,而是直接 fallback 到英文引擎,中文被逐字拼读成 “B-e-i-j-i-n-g”。
为什么必须写在 <html> 标签上
屏幕阅读器(NVDA、VoiceOver、JAWS)只在初始解析 HTML 时读取 <html lang="zh-CN"> 这一个声明,用它初始化整页语音引擎。其他位置全无效:
-
<body lang="zh-CN">:被当作局部覆盖,不影响全局语音选择 -
<meta http-equiv="Content-Language">:HTML5 已废弃,现代 AT 完全忽略 -
<div lang="en">:只对内部文本生效,且前提是根lang已正确设置
lang 值必须严格符合 BCP 47 标准
错一个字符,浏览器就静默忽略,等效于没写。常见非法写法:
-
lang="zh":太宽泛,旧版 JAWS 和 iOS VoiceOver 直接 fallback 英文 -
lang="zh_CN":下划线非法,必须用短横线zh-CN -
lang="Chinese":非标准标签,完全不识别 -
lang="zh-CN ":末尾空格导致解析失败,Lighthouse 报invalid language subtag
推荐值:zh-CN(大陆简体)、zh-TW(台湾繁体)、zh-HK(香港繁体)。zh-Hans 可用但兼容性略窄,zh-Hans-CN 是非法组合。
立即学习“前端免费学习笔记(深入)”;
中英文混排时,只靠根 lang 不够
根 lang="zh-CN" 只管默认行为;遇到英文单词、代码、引文,屏幕阅读器仍会用中文规则硬读,比如 “API” 变 “阿皮”,“React” 变 “瑞克特”。必须显式标注:
- 单个外文词:
<span lang="en">HTTPS</span> - 代码标识符:
<code lang="en">fetch()</code>(比<span>更语义准确,部分读屏对<code>有特殊停顿) - 整段英文:
<p lang="en">The response is a JSON object.</p> - 避免滥用:
<div lang="en">包裹多段落——语义不清,还可能干扰:lang(en)CSS 选择器匹配
JS 动态改 lang 对已渲染内容无效
React/Vue 中执行 document.documentElement.lang = "en-US",只影响后续插入的 DOM 节点:
- 页面已存在的文本、已挂载组件、已绑定的
:lang(zh)CSS 伪类,全部不会重计算 - SSR 场景下,若首屏 HTML 没带
lang,爬虫和 AT 拿到的就是空值,JS 补救为时已晚 - 服务端模板里用
lang="{{ locale }}"动态插入时,必须过滤非法值(如后端返回"zh")
最麻烦的不是写错,而是写了但没测——不同 AT 对嵌套 lang 的解析深度不一致,NVDA 和 VoiceOver 在同一段嵌套三层的混排文本中,发音表现可能完全不同。



















