lang 必须写在 标签上,因屏幕阅读器仅在初始解析时读取该声明以加载默认语音引擎;写在其他标签或用 meta 声明均无效。

lang 必须写在 标签上,否则屏幕阅读器根本“听不到”
屏幕阅读器(NVDA、VoiceOver、JAWS)只在初始解析 HTML 时读取 这个声明,用它加载整页默认语音引擎。写在 、<div> 或 <code><meta http-equiv="Content-Language"> 都无效——前者被当作局部覆盖,后者在 HTML5 中已被废弃。
常见错误包括:
-
:太宽泛,旧版 JAWS 和 iOS VoiceOver 可能 fallback 到英文 TTS -
:下划线非法,BCP 47 要求连字符,浏览器静默忽略,等效于无lang -
:非标准标签,完全不被识别 -
:末尾空格导致解析失败,Lighthouse 会报"invalid language subtag"
中英文混排时只设根 lang,等于没设
根 lang="zh-CN" 只管默认行为;遇到 API、React、fetch() 或整段英文说明,屏幕阅读器仍会用中文规则硬读,导致 “API” 读成 “阿皮”、“React” 读成 “瑞克特”。
必须显式标注子元素语言,且注意粒度和语义:
- 单个外文词:
<span lang="en">HTTPS</span>—— 触发英文音素切分 - 技术术语块:
<code lang="en">fetch()—— 比<span></span>更语义准确,部分读屏对<code>有特殊停顿处理 - 整段英文说明:
<p lang="en">The response is a JSON object.</p> - 避免滥用:
<div lang="en"> 包裹多个段落 —— 语义不清,且可能干扰 CSS 的 <code>:lang(zh)选择器匹配动态渲染页面(React/Vue)中运行时改
lang属性基本无效服务端没输出正确的
lang,JS 再补也晚了——屏幕阅读器已按初始值加载语音模型,后续修改document.documentElement.lang不会触发语音引擎重载。尤其要注意:
- SSR/SSG 首屏 HTML 必须包含正确
- JS 动态插入的文本(如翻译按钮点击后替换的段落),必须同步设置新节点的
lang属性,不能依赖继承 - 后端返回
zh_ch这类非标准格式,前端需标准化为zh-CN后再赋值,否则浏览器静默忽略
别把
lang和SpeechSynthesisUtterance.lang混为一谈lang是 HTML 层语义,决定屏幕阅读器如何读;SpeechSynthesisUtterance.lang是 JS 层控制,只影响speechSynthesis.speak()主动触发的朗读——两者作用域、触发时机、兼容性完全不同。即使你用 JS 设置
utterance.lang = "zh-CN",如果页面没设lang,屏幕阅读器仍会默认用英文引擎读页面正文。更现实的问题是:
- Chrome 和 Edge 支持
getVoices()返回多语言语音,但 Safari 只返回当前系统语言对应的语音,且必须由用户手势触发 - 别在
onload里自动调用speak():iOS 和部分 Android 浏览器会静音,且违反 WCAG 2.1 的“用户控制”原则
最常被忽略的一点是:语音合成是否自然,80% 取决于
lang是否准确、是否嵌套得当,而不是用了多高级的 TTS 库。一个没lang的页面,再好的语音 API 也救不回发音错乱的体验。 - SSR/SSG 首屏 HTML 必须包含正确











