最轻量多语言方案是用data-i18n标记+json语言包+localstorage持久化;需显式标注所有文本节点及placeholder/title/alt等属性,语言包扁平结构、bcp 47命名,切换时同步更新document.documentelement.lang、localstorage、动态节点及intl实例。

用 data-i18n 替代硬编码文本,不改结构也能切语言
硬写 document.getElementById('btn').textContent = 'Submit' 这类逻辑,加一种语言就得全局搜改十几处,漏改一处就错乱。直接在 HTML 里用 data-i18n 标记翻译键,比如:<button data-i18n="form.submit">提交</button>,JS 加载对应语言包后统一遍历更新,HTML 结构完全不动。
常见错误现象:有人把整个 <div> 包进 <code>innerHTML 替换,结果表单输入框失去焦点、日期组件重置、事件监听器消失;正确做法是只改 textContent,需要保留内嵌标签时才用 innerHTML,且必须确保语言包值已做过 HTML 转义(如 "<strong>{text}</strong>")。
- 支持多属性绑定:
data-i18n-title自动设title,data-i18n-placeholder自动设placeholder - IE11 不支持
element.dataset.i18n,得兜底用element.getAttribute('data-i18n') - 跳过
<input>的value属性更新——它属于用户输入状态,不是文案内容
语言包按模块拆分 JSON,避免全量加载拖慢首屏
把所有翻译塞进一个 zh.json 文件,切换语言时 fetch 整个几百 KB 的包,弱网下卡顿明显;但若每个页面都单独加载语言包,又重复请求公共文案(如“首页”“返回”)。
推荐按功能域拆分:common.json(按钮、导航)、form.json(校验提示)、error.json(接口错误码),再用 Promise.all 并行加载当前页所需模块。首页只拉 common.json + home.json,后台页拉 common.json + admin.json。
- 字段名统一用点分隔:
header.logo.alt、table.empty.text,避免嵌套过深 - JSON 文件本身必须纯 UTF-8 编码(无 BOM),否则
fetch解析失败报SyntaxError: Unexpected token - 服务端可预判用户语言,提前
preload对应语言包:<link rel="preload" href="lang/zh/common.json" as="fetch">
语言检测顺序不能只看 navigator.language
navigator.language 返回 zh-CN,但用户可能实际要 zh-HK;安卓 WebView 甚至返回空字符串,直接 fallback 到 en 就错失本地化机会。
真实生效的语言判定链必须是:URL 参数(?lang=ja) → localStorage.getItem('preferred-lang') → document.documentElement.lang(SSR 注入)→ navigator.language → 默认 en。其中 document.documentElement.lang 是关键桥梁,服务端通过 Accept-Language 头解析后写入,保证 CSR/SSR 渲染一致。
- 不要用
localStorage覆盖服务端判断——它可能过期,新设备首次访问没值 - 用户手动切换语言后,必须同步调用
fetch('/api/locale', { method: 'POST', body: JSON.stringify({ lang: 'ja' }) }),让后端后续接口也返回日语错误消息 - 切换时需重置
Intl.DateTimeFormat实例,旧实例不会自动响应语言变更
UTF-8 编码不统一,data-i18n 值会乱码或解析失败
HTML 文件存为 UTF-8 with BOM,但 CSS 或 JS 文件是 ANSI 编码,里面含中文注释或伪元素内容(如 content: "搜索";),浏览器会报 InvalidCharacterError;更隐蔽的是:VS Code 显示 “UTF-8”,实际保存为 “UTF-8 with BOM”,而 <meta charset="UTF-8"> 在 BOM 后面,导致前 1024 字节扫描失效,回退到系统默认编码(Windows 是 GBK),data-i18n 键名直接变成乱码无法匹配。
所有前端资源(.html / .css / .js / .json)必须统一为 UTF-8 无 BOM 编码,且 <meta charset="UTF-8"> 紧贴 开头,前面不能有任何字符(包括空格、BOM、注释)。
- VS Code:右下角编码显示 → “Save with Encoding” → 选 “UTF-8”(不带 BOM)
- 检查文件头:用 hex editor 看前 3 字节,UTF-8 无 BOM 应是
3C 21 44(即),有 BOM 则是 <code>EF BB BF - HTTP 响应头
Content-Type: text/html; charset=UTF-8必须与<meta>一致,Nginx 配置里charset utf-8;比add_header更可靠
<meta charset> 位置的物理一致性——它们不在同一编码平面,data-i18n 键根本查不到。











