应提取主语言码并逐级 fallback:优先用 navigator.languages[0](兼容 navigator.language),转小写并取主语言部分,过滤无效值,按完整标签、主语言、localstorage、默认语言顺序匹配支持列表,且需在 dom 渲染前同步执行。

不能直接用 navigator.language 的原始值去加载语言包,比如拿到 "zh-CN" 就去请求 zh-CN.json。它大概率会 404,页面文案变成裸露的 key,比如 welcome、submit。真正可靠的做法是提取主语言码、校验支持状态、再逐级 fallback。
先取最准的语言标识
优先使用 navigator.languages[0],它是用户浏览器语言偏好列表里的第一个,比 navigator.language 更反映真实设置。IE 不支持这个 API,所以要加一层兼容:
const lang = navigator.languages?.[0] || navigator.language || 'en'- 注意:不要用
navigator.userLanguage(已废弃且行为不一致)
标准化处理语言码
原始值可能是 "zh-CN"、"en-GB"、"ja",甚至小写的 "zh-cn"。必须归一化才能安全匹配:
- 统一转小写:
lang.toLowerCase() - 取主语言部分:
lang.split('-')[0],把"zh-TW"和"zh-Hans"都变成"zh" - 过滤无效值:排除
"und"、空字符串、"qps-ploc"这类占位符
按优先级检查并 fallback
光有主语言码还不够,得确认它是否在你实际支持的语言列表里。推荐用显式白名单 + 多层兜底:
- 定义支持列表:
const SUPPORTED_LANGS = ['zh', 'en', 'ja', 'es', 'fr'] - 先查完整标签(如
"zh-CN"),再查主语言(如"zh") - 都不匹配时,读
localStorage.getItem('preferred-lang') - 最后落到默认语言,比如
'en'
尽早执行,避免闪屏
语言探测必须在 DOM 渲染前完成,否则用户会看到英文文案一闪而过又变中文:
- 把逻辑写在 HTML 的第一个
<script></script>标签里(不要加defer或async) - 用全局变量暂存结果,例如
window.__INITIAL_LOCALE__ = 'zh' - i18n 库初始化时直接读取该变量,比如
i18next.init({ lng: window.__INITIAL_LOCALE__ })










