lang属性必须严格遵循bcp 47标准(如zh-hans、ja),写错则:lang()不触发;需在具体元素上显式声明语言,避免继承导致英文被中文字体渲染;字体链须将对应语言专用字体置首、sans-serif置尾,并区分简繁及区域变体。

lang属性不写对,字体回退就失效
浏览器不是靠猜语言来选字体的。它只认lang属性值,且必须严格匹配 BCP 47 标准——比如zh-Hans、ja、ko,写成zh_CN或chinese,:lang(zh)规则根本不会触发。
常见错误是只在设一次,以为全页中文都走中文字体链。但页面里夹一段<p>API endpoint</p>,没显式加lang="en",这段英文就会继承zh-Hans,结果被塞进“微软雅黑”里渲染——字母间距挤压、标点错位、甚至部分字符显示为方块。
-
lang必须写在具体元素上:<p lang="en">Save draft</p>、<pre class="brush:php;toolbar:false;" lang="bash">npm run dev</pre> - 子标签大小写要一致:用
en-US就别混用en-us,某些旧版 Safari 会拒绝匹配 - 简繁必须区分:
zh-Hans和zh-Hant不能共用同一套字体声明,否则繁体字可能缺字
:lang()规则里字体链怎么排才不翻车
每个:lang()规则的font-family不是随便堆几个名字就行。浏览器按顺序尝试加载,一旦某个字体缺失,就跳到下一个;但如果整个链里没有该语言强支持的字体,最后 fallback 到sans-serif时,很可能用的是英文字体引擎,导致日文假名宽度异常、韩文音节断裂。
例如:lang(ja)写成font-family: "Helvetica", "Noto Sans CJK JP", sans-serif,Chrome 可能直接用Helvetica撑满整行,而Noto Sans CJK JP根本没机会生效——因为Helvetica虽不支持日文,但它是“可用”的。
- 把该语言专用字体放最前:
"Hiragino Kaku Gothic Pro"(日文)、"PingFang SC"(简体中文)、"Apple SD Gothic Neo"(韩文) - 避免跨语言字体混搭:不要在
:lang(zh)里塞"Meiryo",它对中文支持弱,易出字形错位 -
sans-serif必须放在末尾,不能放最前;更别用system-ui开头——它会绕过所有语言专用字体
为什么:lang(zh)有时有效,有时无效
这不是浏览器 bug,而是匹配逻辑差异。:lang(zh)理论上能匹配lang="zh"、lang="zh-CN"、lang="zh-TW",但实际取决于底层语言标签解析器是否把zh-CN识别为zh的子类。Chrome 和 Safari 行为不完全一致,尤其在 SSR 渲染后 JS 动态改lang时,:lang(zh)可能只命中根节点,不触发子元素重排。
- 稳妥做法是显式列出常用变体:
html:lang(zh-Hans), html:lang(zh-CN), html:lang(zh-SG) { ... } - 或用属性选择器兜底:
html[lang^="zh"],但注意它也会匹配lang="zhx"这种非法值 - 别依赖
:lang(zh)单独覆盖所有中文场景——企业级站点必须分简繁、分区域声明
单页应用切换语言时字体突然错乱
执行document.documentElement.lang = "ja"后,新插入的 DOM 能用日文字体,但已存在的<p lang="zh-Hans"></p>仍按旧规则渲染。更麻烦的是,第三方组件(如富文本编辑器输出的<div>)往往不带<code>lang,直接继承根节点,结果一堆中文被塞进日文字体链里,显示异常。
- 切换语言时,必须遍历并重设所有含语义的节点:
querySelectorAll("[lang], [data-i18n]") - 表单控件的
placeholder和验证提示不会自动更新lang,得手动同步:input.placeholder = "検索"; input.setAttribute("lang", "ja"); - SSR 渲染的页面,首次加载时
lang必须和服务端输出一致,否则客户端 hydration 后字体链会短暂错乱
真正容易被忽略的点是:字体回退不是纯 CSS 行为,它和lang属性、浏览器渲染管线、系统字体缓存深度耦合。哪怕规则写对了,只要有一个<span></span>漏了lang,那一小段文字就可能崩掉整行排版。











