国际化必须逐元素设置lang属性并显式标注data-i18n,否则屏幕阅读器误读、标点间距错乱、代码字体覆盖、动态内容不翻译;所有含文本标签需设lang,可翻译属性须用对应data-i18n-*标记,新DOM插入后需手动触发翻译。

只改 <html lang="zh-Hans"> 根节点,其他地方不加 lang,等于没做国际化——屏幕阅读器读错、顿号变英文间距、<pre lang="bash"> 被中文字体覆盖,全是必然结果。
lang 属性必须逐元素设置,不能依赖继承
浏览器和辅助技术(如 NVDA、VoiceOver)不看父级 lang,只检查每个含文本元素自身的 lang 值来决定语音引擎、字体 fallback、标点挤压规则。比如中文顿号「、」在 lang="zh-Hans" 下会压缩前后间距,但若 <p> 没显式设 lang,它就按默认语言(通常是英文)处理,视觉和语音全错位。
常见错误现象:
-
<p>欢迎使用</p>文字显示正常,但屏幕阅读器读成英文发音 -
<pre lang="bash">npm install</pre>代码被微软雅黑渲染,关键字模糊不清 -
<blockquote lang="ja">こんにちは</blockquote>切换主语言后被误覆盖为lang="en-US",日文段落朗读失效
实操建议:
立即学习“前端免费学习笔记(深入)”;
- 所有含文本的语义化标签(
<h1>、<p>、<section>、<footer>、<label>)都必须显式写lang,值与当前激活语言包一致,例如<p lang="zh-HK">歡迎使用</p> - 已有明确语言用途的元素(如
<pre lang="bash">、<code lang="sql">、<blockquote lang="ja">)切换语言时保留原lang值,这是合法混排,不是 bug -
<script>和<style>内部写lang完全无效,它们不参与文本渲染,别浪费字符
data-i18n 必须覆盖所有可翻译属性,不只是 textContent
data-i18n 默认只替换元素的 textContent,对 placeholder、alt、title、aria-label 等属性完全无效。漏掉任何一个,对应内容就会卡在旧语言。
常见错误现象:
-
<input placeholder="Search">切换语言后提示仍是英文 -
<img alt="user avatar">的替代文本未更新,屏幕阅读器播报错位 -
<label for="email">Email</label>文字翻了但for属性没同步,点击 label 失效
实操建议:
立即学习“前端免费学习笔记(深入)”;
- 每个要翻译的元素至少有一个基础
data-i18n键,例如<button data-i18n="btn_submit">提交</button> - 含
placeholder就额外加data-i18n-placeholder;同理用data-i18n-title、data-i18n-alt、data-i18n-aria-label -
value属性一般不翻译(属于用户输入数据),但<button>和<input type="submit">的显示文案建议统一走textContent更新,避免value被意外提交 - SVG 内的
<text>也要单独标data-i18n,不能靠父容器继承
动态插入的 DOM 必须手动补 lang 并触发翻译
AJAX 加载的弹窗、分页表格新行、懒加载模块插入后,data-i18n 标记只是字符串,不会自动变成对应语言文本。没人调用翻译函数,就不会替换;没人补 lang,辅助技术就无法识别语言上下文。
常见错误现象:
- 点击按钮打开的
<modal>里<h2 data-i18n="modal_title">保持原始键名 - 分页表格每页新
<tr>里的data-i18n始终是英文键 - DOM 深度超过 6 层(如六层嵌套
<div>)时,部分data-i18n节点被遍历逻辑跳过
实操建议:
立即学习“前端免费学习笔记(深入)”;
- 插入新 DOM 后立即遍历其子节点,调用翻译函数处理所有
data-i18n*属性 - 推荐封装成工具函数,例如
translateNode(node),支持递归处理子树 - 切换语言前先收集
document.querySelectorAll('[lang]'),对每个匹配元素执行el.lang = newLang;已带lang的代码块等例外项需白名单过滤 - 用
<main>、<section>等语义标签替代无意义<div>,天然中断嵌套深度,也利于 i18n 工具识别作用域
语言包加载必须 fetch + MIME 校验 + BCP 47 fallback
把语言包硬编码进 JS 或用 XMLHttpRequest 加载,容易静默失败:服务器返回 404 或 text/plain MIME 类型时,脚本照常执行,文案留空,用户看到的是 key 名。
实操建议:
立即学习“前端免费学习笔记(深入)”;
- 路径统一为
./locales/${lang}.json,例如./locales/zh-HK.json、./locales/en-US.json - 必须用
fetch()加载,外层包try/catch,内部检查response.ok和response.headers.get('content-type')?.includes('application/json') - fallback 顺序:先试完整 BCP 47 码(如
zh-HK),再截主语言(zh),最后兜底到en-US - 含 HTML 结构的文案(如“请阅读服务条款”)必须用
innerHTML替换,且语言包中对应值要是可信纯 HTML 片段(不能带用户输入、不执行 JS),否则有 XSS 风险
最易被忽略的点:DOM 深度超 6 层时,部分 data-i18n 节点会被跳过;lang 属性漏设在某个 <label> 上,会导致点击失效——这些问题不会报错,只会让 QA 测试时反复追问“为什么这个按钮没翻?”



















