lang属性必须精确到区域,如zh-cn而非zh;禁用innerhtml动态翻译,改用textcontent或dompurify;多语言html用封装;语言包需构建时静态校验键名一致性。

lang 属性必须精确到区域,不能只写 lang="zh"
浏览器和辅助技术依赖 lang 的完整值做语音合成、字体回退、断行规则等判断。写成 lang="zh" 会导致部分屏幕阅读器加载默认英文语音库,中文播报失真;CSS 中的 :lang(zh) 选择器也无法匹配 lang="zh-CN" 元素。真实项目中常见错误是后端模板硬编码 ,但 locale 变量只传了 zh,没带区域码。
正确做法是:
- 服务端或构建脚本必须输出带区域的完整语言标签,如
zh-CN、en-US、ja-JP - 多语言切换时,同步更新
和<meta name="viewport">后的<meta charset="UTF-8">位置(它必须在前 1024 字节内) - CI 流程用正则校验所有 HTML 文件:是否含
,拒绝lang="zh"或lang="en"这类模糊值
HTML 文件不能靠 innerHTML 动态注入多语言内容
很多团队用 JS 加载 JSON 语言包后,遍历 DOM 写 el.innerHTML = translatedText,这会破坏已绑定的事件监听、丢失表单状态、触发重排重绘,且对 <input>、<select></select> 等控件的 value 值无感知。更严重的是,若翻译文本含未转义的 HTML 字符(如用户输入的 <script></script>),直接 innerHTML 就等于 XSS 漏洞入口。
安全可靠的替换方式只有两种:
- 对纯文本节点:用
textContent替代innerHTML,强制转义所有 HTML 实体 - 对需保留 HTML 结构的字段(如富文本说明):必须经由
DOMPurify.sanitize()过滤后再设为innerHTML,且禁止开放ALLOWED_TAGS列表外的标签 - 避免在
<script></script>标签内拼接翻译字符串——JS 字符串插值无法自动转义,应改用data-*属性挂载原始值,运行时再取用
多语言 HTML 片段必须用 <template></template> 封装,禁用 display: none
当页面含多个语言版本的静态模块(如多语种页脚、法律声明),有人会把不同语言的 HTML 写进同一个文件,用 CSS 控制显隐:<div class="footer-zh" style="display:none">...。这种写法会让所有语言内容都参与 DOM 构建、触发资源加载(图片、字体)、占用内存,首屏性能直接受损。<p><code><template></template> 是唯一符合规范的解决方案:
- 浏览器不解析
<template></template>内部的<script></script>、<style></style>,也不会加载其中的<img src> - JS 提取时用
template.content.cloneNode(true),天然过滤注释和空白文本节点,比正则或 innerHTML 更可靠 - CI 阶段可加校验:所有
<template data-lang="xx"></template>必须有对应语言包键名,缺失则报错阻断构建
语言包 JSON 必须通过构建时静态校验,而非运行时 fallback
开发常设兜底逻辑:dict[key] || key,上线后才发现大量 "submit_button": "submit_button" 这类“伪翻译”。这不是体验问题,而是质量失控信号——说明语言包没进 CI 流程,或校验只跑在本地。
真正有效的保障是构建阶段介入:
- Webpack/Vite 插件扫描所有
.html中的data-i18n属性值,在构建时比对语言包 JSON 的 keys,缺失项立即抛错 - 语言包本身需含
"_meta": {"version": "2.3.1", "last_updated": "2026-07-05"}字段,CI 校验各语言版本号一致,防止中英文包不同步 - 禁止在 JS 中动态生成 key 名(如
dict[`${type}_${status}`]),这类拼接无法被静态分析捕获,必须拆成明确枚举
语言包键名和 HTML 中的引用点脱节,是国际化交付中最隐蔽也最难追溯的问题。它不会报错,却让 QA 无法覆盖全部文案路径,上线后靠用户反馈才发现漏翻——这种滞后性,恰恰说明质量保证没落在构建链路里。











