应归一化 navigator.language 主语言码并设计 fallback 链:转小写、取 split("-")[0]、过滤无效值;优先 navigator.languages[0],再 localStorage,最后默认 en;异步静默加载,容错缓存;支持嵌套 key 与插值。

不能直接用 navigator.language 的原始值去加载语言包,它返回的是 BCP 47 格式字符串(如 "zh-CN"、"en-GB"、"ja"),而你的语言资源文件名通常只用主语言码(如 zh.json、en.json)。跳过标准化和 fallback,大概率触发 404,页面文案变成裸 key(如 "welcome")或空白。
提取并归一化主语言码
浏览器语言标识需清洗后才能安全使用:
- 统一转小写(避免
"ZH-cn"匹配失败) - 用
.split("-")[0]取主语言部分("zh-TW"→"zh","pt-BR"→"pt") - 过滤无效值:
""、"und"、"qps-ploc"等占位符应排除
按优先级 fallback 加载语言包
单靠 navigator.language 不可靠,必须设计降级链:
- 第一步:取
navigator.languages?.[0] || navigator.language(前者更准,但 IE 不支持) - 第二步:提取主语言码,并检查是否在你支持的列表中(如
["zh", "en", "ja", "es"]) - 第三步:不在支持列表?读
localStorage.getItem("lang")(用户上次手动选择) - 第四步:仍无匹配?回退到默认语言(如
"en")
静默加载:不阻塞首屏,失败不中断
语言包加载必须异步、容错、可缓存:
- 不要
await fetch()在 DOMContentLoaded 里 —— 否则弱网下白屏明显 - 改用
fetch(url).then(r => r.json()).catch(() => ({})),失败返回空对象,避免报错中断流程 - 首次渲染可用内联默认语言对象(如
const en = { welcome: "Welcome" }),保底不空 - 加
cache: "force-cache"或配合 Service Worker 缓存已加载包,减少重复请求
更新已有 DOM 元素时处理嵌套与插值
仅替换 textContent 不够,需支持结构化 key 和运行时参数:
-
data-i18n="form.login.button"应按点号拆解,递归取值:messages[lang].form?.login?.button - 允许传参:
data-i18n-params='{"name": "Alice"}',解析后注入模板字符串(如"Hello {name}"→"Hello Alice") - 更新时机建议放在 DOM 就绪后,用 Promise 状态控制,而非强依赖
document.querySelectorAll立即执行

















