按需加载多语言脚本的核心是仅加载用户当前选择的语言包。需结合 localstorage 缓存判断、fetch 网络请求、utf-8 无 bom 编码校验;超大包禁用 localstorage,改用 dynamic import 加载含逻辑的 js 模块;解析 data-i18n 时过滤 script/pre/style 标签,使用扁平键名,同步更新 document.lang,并在首屏先渲染默认语言防乱码。

按需加载多语言脚本,本质是「只加载用户当前选中的语言包」,而不是把所有语言 JSON 一次性拉下来。关键在加载时机、缓存策略和 fallback 处理——做错一点,切换语言就白屏或漏翻译。
用 fetch + localStorage 判断是否已加载过语言包
每次切换语言都重新 fetch 是低效且易出错的。浏览器不会自动缓存 JSON 响应(除非服务端配了 Cache-Control),重复请求可能触发 CORS 或限流。
- 先查
localStorage.getItem('i18n_zh')是否存在完整语言对象,有就直接用,跳过网络请求 - 没命中再发
fetch('/locales/zh.json'),成功后用localStorage.setItem('i18n_zh', JSON.stringify(data))存下 - 注意:JSON 文件必须是纯 UTF-8 编码,BOM 头会导致
JSON.parse()报SyntaxError: Unexpected token \ufeff - 不要用
localStorage存超大语言包(>1MB),iOS Safari 私模式下会直接拒绝写入
动态 import() 加载带逻辑的语言模块(非纯 JSON)
如果你的语言包不只是键值对,还包含格式化函数(比如日期、数字本地化),就得用 import() 加载 JS 模块,而不是 fetch JSON。
- 写成
const zh = await import('./locales/zh.js');,模块导出默认对象:export default { welcome: '你好', formatPrice: (n) => `¥${n}` } - 路径必须是静态字符串,
import(`./locales/${lang}.js`)会构建失败——Webpack/Vite 无法分析动态路径依赖 - 模块里别直接操作 DOM,保持纯数据+函数;DOM 更新交给统一的
renderI18n()函数处理 - 加
try/catch,捕获ChunkLoadError(文件 404 或网络中断),降级到en并提示用户“语言加载失败”
data-i18n 属性解析时忽略 script/pre 标签内容
data-i18n 只该作用于可见文本节点。但很多人误给 <script></script> 或 <pre class="brush:php;toolbar:false;"></pre> 加这个属性,结果遍历替换时把代码删了。
- 遍历元素时加过滤:
if (el.tagName === 'SCRIPT' || el.tagName === 'PRE' || el.tagName === 'STYLE') continue; -
data-i18n的值是 key,不是路径——data-i18n="header.title"这种嵌套写法需要额外解析,容易出错;建议扁平键名:data-i18n="header_title" - 如果元素含 HTML 结构(如带
<a></a>的提示语),用el.innerHTML = langPack[key],但必须确保语言包里该字段是可信 HTML,否则要过DOMPurify.sanitize() - 别忘了同步更新
document.documentElement.lang和所有带lang属性的子元素,否则屏幕阅读器读错音调、字体 fallback 失效
最常被忽略的是:语言包加载完成前,页面不该显示未翻译的 key(比如 home_welcome)。得在 DOM ready 后先占位渲染一次默认语言,再异步加载用户偏好语言——否则首屏体验就是乱码。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











