HTML 国际化这个环节,许多开发团队都曾遭遇过棘手问题,常常是“看起来该做的都做了”,结果一上线就暴露出各种隐患。以下是几个最容易被忽视的关键细节:

仅仅修改 document.documentElement.lang,文本并不会自动更新,标点符号、字体渲染以及屏幕阅读器的行为也不会自行修复——这个操作非常普遍,但本质上只是“自我安慰式的国际化”。
lang 属性若写在 charset 之前,易引发乱码及解析失败
浏览器解析 HTML 时,前 1024 字节内必须包含 ,否则浏览器可能会回退到系统默认编码(例如 Windows 上的 GBK),导致 lang 值本身被错误解码变成乱码,后续所有依赖该值的逻辑(包括 i18n 框架的语言判断)都会崩溃。
必须放在之后,且应紧贴开头,前面不能有空格、BOM 或注释- VS Code 显示 “UTF-8 with BOM” 时,实际保存的是带有 EF BB BF 头的文件,此时
会失效;务必选择 “UTF-8”(无 BOM) - 使用
curl -I或 Chrome DevTools 的 Network → Headers 查看响应头中的Content-Type,确认其包含charset=UTF-8,否则服务端配置(如 Nginx 的charset utf-8;)优先级高于 HTML 中的meta
data-i18n 仅处理 textContent,其他属性需显式标记
一个常见的陷阱:给 添加了 data-i18n="search",但 placeholder 丝毫不变——data-i18n 默认只更新元素的文本内容,对 placeholder、alt、title、aria-label 这些属性完全无效。
- 必须使用
data-i18n-placeholder="search_hint"、data-i18n-alt="avatar_desc"、data-i18n-title="tooltip_info"等专用属性 value属性通常不翻译(属于用户输入数据),但和的显示文案建议统一用textContent更新,避免value被意外提交- 包含 HTML 结构的文案(例如
"请阅读服务条款")必须使用innerHTML替换,且语言包中对应的值必须是可信的纯 HTML 片段(不可拼接用户输入,否则存在 XSS 风险)
动态插入的 DOM 不会自动翻译
通过 AJAX 加载的弹窗、分页表格新行、懒加载模块插入后,其中的 data-i18n 标记仅作为字符串存在,不会自动转换为对应语言文本——没有监听机制,也不会触发重渲染。
- 每次插入新 DOM 后,必须手动调用翻译函数遍历并替换,例如
translateElement(modalEl)或translateElements(newRow.querySelectorAll('[data-i18n]')) - 不要依赖 MutationObserver 自动扫描:开销大、容易遗漏、时机不可控;明确在插入后立即处理更为可靠
- 如果使用了第三方组件(如日期选择器),切换语言时需同步调用其 locale 方法(例如
flatpickr.localize()),不能仅刷新页面文本
lang 属性不继承,每个语义化标签都得显式声明
很多团队设置了 document.documentElement.lang = 'zh-Hans' 就觉得万无一失,结果 里的顿号按英文间距渲染、 的代码字体被中文字体覆盖、 的 alt 文本仍被读作英文——因为浏览器和屏幕阅读器只看每个元素自身的 lang 属性。
- 所有包含文本的语义化标签(
、、、等)都必须显式添加lang,属性值与当前语言包一致(例如lang="zh-Hans") - 已有
lang的特殊元素(如、)切换语言时保留原值,这是多语言混排的合法场景 和内部不要写lang,它们不参与文本渲染,写了也没用
最容易被忽略的是:lang 属性错误(例如写成 zh-CN 而非 zh-Hans)比不写更糟糕——它会强制浏览器按错误规则渲染标点、连字、语音,并且无法回退。BCP 47 格式必须严格校验,不能仅凭肉眼判断。
