navigator.language不能直接当语言码用,因其返回BCP 47标签(如zh-CN、fr-CA)与资源文件名(zh.json)不匹配,易致404;且存在大小写不一、WebView返回und/空值、浏览器间差异等问题,必须经截断主语言码、映射非标值、多级fallback三步处理才可靠。

自动检测语言不能只靠 navigator.language,它返回的值不统一、不可靠,必须做截断、映射、fallback 三层处理才能用。
为什么 navigator.language 不能直接当语言码用
浏览器返回的是完整 BCP 47 标签(如 zh-CN、fr-CA、zh-Hans),但你的语言包文件名大概率是 zh.json 或 zh-Hans.json。直接拼接请求会 404;更麻烦的是 Safari 在简体中文系统下可能返回 zh-Hans,安卓 WebView 有时返回空字符串或 und。
-
navigator.language是浏览器 UI 语言,不是用户内容偏好语言 - 不同浏览器对同一系统设置返回值不一致(比如 macOS 上 Chrome 和 Safari 可能不同)
- 某些 WebView 环境(尤其旧版 Android)根本不支持该属性,或返回
undefined
怎么安全截取主语言码并 fallback
拿到 navigator.language 后,必须先做标准化,再 fallback 到备用源。关键不是“取前两位”,而是按语义归一化。
- 用
.split('-')[0]截取主语言子标签(zh-CN→zh,en-US→en) - 加映射表处理非标准返回:
{ 'zh-Hans': 'zh', 'zh-Hant': 'zh-TW', 'pt-BR': 'pt' },避免漏匹配 - fallback 顺序必须是:
URL 参数 lang=xxx→localStorage.getItem('preferred-lang')→navigator.language || 'en' - 最终语言码必须符合 BCP 47:用
zh-Hans而不是zh_CN,否则<html lang="">无效
加载语言包时怎么防白屏和闪动
检测和加载都是异步的,HTML 里写死文案必然闪动。核心是“先藏后显”,且藏法要兼顾可访问性。
立即学习“前端免费学习笔记(深入)”;
- 初始 HTML 所有文本留空或用占位符:
<h1 data-i18n="home.title"></h1>,别放默认中文 - 不要用
display: none隐藏整个<body>,会导致屏幕阅读器跳过——改用visibility: hidden+ loading 指示器 - fetch 语言包失败时,必须 fallback 到
en.json(或你设定的主语言包),不能留空;否则整页textContent为空 - 千万别在
<script>里写document.body.innerHTML = ...,会清掉所有事件监听和动态 DOM
document.documentElement.lang 必须同步更新
只换文案不改 lang 属性,等于没切换——屏幕阅读器照旧读中文,日文假名显示为方块,Chrome 翻译识别错乱。
- 更新文案和设置
document.documentElement.lang = 'zh-Hans'必须在同一次 DOM 批量操作中完成 - 已渲染的子元素如果有显式
lang(如<pre lang="bash">),也要单独更新,不能指望继承 - 切换后不要指望浏览器自动重译已渲染内容,要刷新或手动触发翻译 API(极少见)
- SEO 不认
lang值是否“正确”,只认是否与 hreflang 声明一致;所以<link rel="alternate" hreflang="zh-Hans">对应页必须设<html lang="zh-Hans">
真正容易被忽略的不是怎么取语言码,而是“取到之后怎么让整个页面语义层真正切换过去”——文案、lang 属性、子元素 lang、字体链、屏幕阅读器引擎,这五者必须同步,缺一不可。



















