lang属性必须写在<html>标签上,因屏幕阅读器仅在初始解析时读取该值以加载整页语音引擎;写在<body>、<div>或<meta>中均无效,错标、空标或非法格式(如下划线、空格)将导致中文被误读为乱码或日语腔调拼音。

lang 属性不是“加个标签就完事”的可选配置,它直接决定屏幕阅读器是否能把中文读对——写错、写空、写在错位置,用户听到的就是一串乱码或日语腔调的拼音。
lang必须写在标签上,其他地方都无效
屏幕阅读器(NVDA、VoiceOver、JAWS)只在页面初始解析时读取 <html> 的 lang 值,用它加载整页默认语音引擎。写在 <body>、<div lang="zh-CN"> 或 <meta http-equiv="Content-Language"> 里,等于没写。
常见错误包括:
-
<html lang="zh">:太宽泛,旧版 JAWS 和 iOS VoiceOver 可能 fallback 到英文 TTS -
<html lang="zh_CN">:下划线非法,BCP 47 要求连字符,浏览器静默忽略 -
<html lang="zh-CN ">:末尾空格导致解析失败,Lighthouse 报 “invalid language subtag” -
<meta charset="UTF-8">后面加<meta http-equiv="Content-Language" content="zh-CN">:HTML5 已废弃,完全不生效
zh-CN 和 zh-Hans 该选哪个?看兼容性水位线
对主流读屏器来说,zh-CN 和 zh-Hans 在发音和声调处理上几乎一致,但底层词典加载行为有分水岭:
立即学习“前端免费学习笔记(深入)”;
-
zh-CN是 IETF 推荐事实标准,Windows Narrator、iOS VoiceOver、NVDA 2022+ 全部稳定加载普通话语音库 -
zh-Hans更强调“简体字形”,适合大陆+新加坡+马来西亚等多地区部署,但某些旧版 JAWS(如 v2018)可能无法匹配完整词典 -
zh-Hans-CN是非法组合,IANA 子标签注册库不接受,浏览器直接丢弃
繁体场景优先用 zh-TW(台湾本地适配最强),zh-Hant 可用于港澳通用,但部分翻译插件支持不稳定。
中英文混排时,只设根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">包裹多个段落 —— 语义不清,还可能干扰 CSS 的:lang(zh)选择器匹配
JS 动态改 lang 对已渲染内容完全无效
React/Vue 应用里执行 document.documentElement.lang = "en-US",只影响后续插入的 DOM 节点,对页面已存在的文本、已挂载的组件、已绑定的 CSS 伪类(如 :lang(en))均无重计算效果。
这意味着:
- SSR 或首屏 HTML 必须带对
lang,否则初始朗读已错,用户第一印象就是乱码 - 语言切换后,需配合
aria-live主动播报变更,否则视障用户毫无感知 - 不要用 JS 拼接
lang值(如lang="${lang}"),模板里就得写死合法值
最易被忽略的一点:lang 不是“写了就行”,而是“写错即失效”——它不像 CSS 那样容错,一个空格、一个下划线、一个未注册的子标签,都会让整个语音链崩掉。



















