只改document.documentElement.lang远远不够,必须同步更新所有含文本语义化标签的lang属性并严格遵循BCP 47格式;data-i18n需覆盖placeholder、alt、title等所有可翻译属性并带对应后缀;动态DOM插入后须手动触发翻译。

document.documentElement.lang 必须设,但远远不够
只执行 document.documentElement.lang = 'zh-Hans',页面文字不会变、屏幕阅读器仍读英文、中文顿号按英文间距渲染——浏览器根本不管根节点的 lang,它只看每个元素自己的 lang 属性。
所有含文本的语义化标签(<h1>、<p>、<section>、<footer>)都得显式写 lang,值必须与当前语言包严格一致(如 lang="zh-Hans")。
-
<pre lang="bash">、<code lang="sql">这类已有lang的特殊元素要保留原值,不能覆盖——这是多语言混排的合法需求 -
<title>、<meta name="description">完全不继承<html>的lang,必须单独设 -
<script>和<style>里写lang是无效的,它们不参与文本渲染
data-i18n 属性必须覆盖所有可翻译字段
data-i18n 默认只更新 textContent,对 placeholder、alt、title、aria-label 等属性完全无感。漏掉任何一个,对应内容就卡在旧语言里。
常见翻车点:<input placeholder="Search"> 切换后还是英文;<img alt="user avatar"> 的替代文本没更新;<label for="email">Email</label> 文字翻了但 for 没同步,点击失效。
立即学习“前端免费学习笔记(深入)”;
- 基础文案用
data-i18n="key" - 需更新
placeholder就加data-i18n-placeholder="search_hint" - 同理:
data-i18n-alt="avatar_desc"、data-i18n-title="tooltip_info"、data-i18n-aria-label="close_btn" -
value属性一般不翻译(属于用户输入数据),跳过处理;但<button>和<input type="submit">的显示文案建议统一走textContent更新
动态插入的 DOM 必须手动触发翻译
AJAX 加载的弹窗、分页表格新行、懒加载模块插入后,里面的 data-i18n 标记只是字符串,不会自动变成对应语言文本——DOM 插入和翻译是两件事,没有监听机制。
例如:<modal> 内部的 <h2 data-i18n="modal_title"> 不会响应语言切换;每页 AJAX 获取的新 <tr> 里的 data-i18n 也保持原始键名。
- 插入新节点后,立即调用你的翻译函数(如
translateNode(newElement)或translateAll(newElement.querySelectorAll('[data-i18n]'))) - 不要依赖 MutationObserver 自动监听——开销大、易漏、难调试;显式调用更可控
- 确保翻译函数能处理
innerHTML场景(如含<strong>的文案),且语言包中对应值是可信纯 HTML 片段,避免 XSS
语言包加载和 fallback 必须严格校验
加载失败或 key 缺失时,整页留白不是小问题,而是可访问性事故。JSON 文件结构、HTTP 响应头、键名对齐、fallback 顺序,一个环节松动就崩。
- 路径统一为
./locales/${lang}.json(如./locales/zh-Hans.json),HTTPContent-Type必须是application/json - 语言包结构扁平,字段名严格一致;某语言暂未翻译也要保留键,设为空字符串:
"btn_submit": "" - fallback 顺序必须是:先试完整 BCP 47 码(如
zh-HK),再截主语言码(zh),最后退到默认语言(如en) - fetch 失败、response.json() 报错、key 查不到,三者都要有兜底逻辑,统一退到
en或预设主语言
最易被忽略的是:BCP 47 格式错误比不写 lang 更糟——lang="ch" 或 lang="zh_CN" 会被浏览器静默忽略,导致标点、字体、语音全部回退到系统默认,且无法 debug。



















