lang属性必须写在标签上,因屏幕阅读器仅在初始解析时读取该属性以确定整页语音引擎;写在、或中均无效。

lang必须写在html>标签上,否则屏幕阅读器基本不认
屏幕阅读器(NVDA、VoiceOver、JAWS)只在页面初始解析时读取 元素的 lang 属性,这是它决定整页默认语音引擎的唯一依据。写在 、<div> 或用 <code><meta http-equiv="Content-Language"> 都无效——前者被当作局部语言覆盖,后者在 HTML5 中已被废弃。
常见错误包括:
-
:太宽泛,部分旧版 JAWS 和 iOS VoiceOver 会 fallback 到英文 TTS 引擎 -
:下划线非法,BCP 47 要求连字符,浏览器静默忽略,等效于无lang -
:非标准标签,完全不被识别 -
:末尾空格导致解析失败,Lighthouse 会报“invalid language subtag”
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()—— 比<span></span>更语义准确,部分读屏对<code>有特殊停顿处理 - 整段英文说明:
<p lang="en">The response is a JSON object.</p> - 避免滥用:
<div lang="en"> 包裹多个段落 —— 语义不清,且可能干扰 CSS 的 <code>:lang(zh)选择器匹配JS 动态改 lang 对已渲染内容完全无效
React/Vue 应用里执行
document.documentElement.lang = "en-US",只影响后续插入的 DOM 节点,对页面已存在的文本、已挂载的组件、已绑定的 CSS 伪类(如:lang(en))均无重计算效果。真实问题场景和应对方式:
- SPA 多语言切换:不能靠状态更新 + patch root lang,必须服务端渲染不同
lang的 HTML(如 Next.js 的getServerSideProps根据Accept-Language头输出) - 纯客户端应用:语言判断逻辑必须前置到
index.html的内联脚本中,在开始渲染前就写入正确值 - CMS 或用户输入内容:若后端返回的富文本含外文片段,前端需在插入 DOM 前遍历并为对应节点注入
lang,而不是依赖全局替换
最易被忽略的一点:无障碍测试不能只看 DevTools 里
lang是否存在,必须用真实屏幕阅读器逐字朗读中英混排句,比如 “点击按钮(Button)”,确认括号内是否按英文发音。自动化工具(Lighthouse、axe)只能验格式,不验语音行为。 - SPA 多语言切换:不能靠状态更新 + patch root lang,必须服务端渲染不同











