应使用 data-i18n 属性标记需翻译节点,按模块拆分语言包并 Promise.all 并行加载,服务端注入 lang 属性、前端局部刷新 DOM 与 Intl 实例,确保格式、状态、无障碍全面适配。

用 data-i18n 标记文本节点,别塞进 class 或 id 里
把翻译键写在 class="home-title" 或 id="welcome-msg" 里,等于把语义和逻辑混在一起——JS 查找时得靠猜,CSS 改名就断,多人协作时容易覆盖或误删。而 data-i18n 是专为国际化设计的语义锚点,不参与样式、不触发渲染、不会被全局规则干扰。
常见错误现象:document.querySelector('.header-text') 在改版后失效;id="login-btn" 被循环渲染重复,getElementById() 只返回第一个。
- 所有需翻译的元素都加
data-i18n="auth.login.button",键名保持层级清晰,避免扁平化(如不用"login",而用"auth.login.button") - 不依赖
textContent以外的属性做定位,比如不要用data-i18n和class做双重判断 - 禁用
innerHTML直接替换整块内容——会清空事件监听器或已初始化的子组件(如日期选择器)
语言包按模块拆成 JSON 文件,用 Promise.all 并行加载
把所有翻译塞进一个 en.json,看似省事,实际导致首屏加载慢、热更新困难、协作冲突频繁。模块化拆分不是为了“看着整齐”,而是让改动只影响对应区域。
使用场景:表单校验提示、导航栏文案、模态框按钮、错误消息——这些语义边界明确,适合独立文件。
立即学习“前端免费学习笔记(深入)”;
- 拆分示例:
common.json(通用词)、form.json(表单字段与提示)、error.json(API 错误码映射) - 加载时用
Promise.all([fetch('common.json'), fetch('form.json')]),避免串行等待 - 不要在运行时动态拼接路径如
fetch(lang + '/form.json'),路径应由构建工具注入或硬编码为确定结构,防止 404 或 CSP 拦截
切换语言时只刷新带 data-i18n 的节点,不重载页面
调用 window.location.reload() 看似简单,但会丢失表单输入、滚动位置、路由状态、第三方组件(如地图、图表)的实例。真正的切换是“局部刷新”+“格式重建”。
容易踩的坑:只换文字,没同步更新 Intl.DateTimeFormat 实例,结果时间还是旧语言格式;或用了 moment.js 却忘了调用 moment.locale('zh')。
- 遍历所有
[data-i18n]元素,查当前语言包更新textContent(纯文本)或innerHTML(含标签,但必须确保值已 HTML 转义) - 同步更新
document.documentElement.lang,保障无障碍读屏和 SEO - 重建所有
Intl格式化器:重新 newIntl.DateTimeFormat(lang)、Intl.NumberFormat(lang),旧实例不能复用
服务端注入 lang 属性,前端 fallback 有顺序
仅靠 navigator.language 不可靠——隐私模式下为空、用户手动改过设置、移动端 UA 模糊。纯前端检测也撑不起 SSR 场景,首次渲染语言错乱直接影响 SEO 和用户体验。
关键点在于:服务端通过 Accept-Language 头解析首选语言,并写入 HTML 根节点,前端初始化时优先读它。
- 服务端(如 Node.js)用
accepts库解析,比正则更准,支持权重排序(如zh-CN;q=0.9,en;q=0.8) - 前端初始化时顺序读取:
document.documentElement.lang→navigator.language→ 默认语言(如'en') - 不要用
localStorage的值覆盖服务端判断——它可能过期,且新设备首次访问没有该值 - 用户手动切换语言后,应发请求到
/api/locale同步服务端偏好,后续接口错误消息才能本地化
data-i18n 遍历,格式化归 Intl 重建,语言来源归服务端注入,状态保持归前端局部刷新逻辑。漏掉任何一个,多语言就只是“看起来能切”。



















