lang属性需逐节点更新,不能仅改根节点;data-i18n必须显式标记每个可翻译节点;语言包须扁平结构、键名对齐、UTF-8无BOM;切换语言应局部刷新,动态DOM需手动翻译。

lang 属性必须逐节点更新,不能只改 document.documentElement.lang
浏览器和屏幕阅读器不继承 lang,每个节点的语音引擎、字体 fallback、标点间距、断行规则都取决于它自己的 lang 值。只设 document.documentElement.lang = 'zh-CN',对已渲染的 <p lang="ja"> 或 <code lang="en"> 完全无效。
切换语言时必须同步遍历所有带 lang 属性的元素,按语义保留或重置:
- 主语言变更:更新根节点 + 所有未显式声明
lang的文本容器(如<div>、<p>) - 局部多语言内容(引用、代码块、术语)要保留原
lang,比如<blockquote lang="fr">不能被批量替换成zh-CN -
lang值必须符合 BCP 47 标准:用zh-CN,不用zh_CN或chinese;ja-JP比ja更兼容 iOS VoiceOver
data-i18n 标记必须显式打在每个可翻译节点上
JS 不会自动识别 class、id 或 DOM 结构来决定哪段文本该换——它只认 data-i18n 属性。漏标一个 <button> 或 <input placeholder>,切换语言后就留白。
常见遗漏点:
立即学习“前端免费学习笔记(深入)”;
将调研报告、方案对比、数据分析、需求梳理、会议纪要、设计说明、代码评审、学习笔记等任务产物输出为可在浏览器直接打开的单文件 HTML,替代 Markdown 作为最终交付物。Markdown 在信息密度、视觉清晰度和可分享性上已跟不上 AI 产物的丰富度,HTML 能同时承载排版、表格、SVG 等。
-
<input data-i18n-placeholder="search_hint">—— placeholder 需带后缀,data-i18n默认只更新textContent -
<select>的<option>必须单独加data-i18n,父<select>上标没用 -
<svg><text data-i18n="chart.title"></text></svg>—— SVG 内文本节点也要标 - 含 HTML 结构的文案(如「服务条款即表示同意」)要用
innerHTML替换,但语言包里对应值必须是可信的纯 HTML 片段,否则触发 XSS
语言包 JSON 文件必须结构扁平、键名严格对齐
别把语言包写成嵌套对象:{"ui": {"header": {"title": "..."}}}。这种结构会导致 key 路径不可预测,容易因某语言缺失某层而查不到值,最终渲染为空。
正确做法是每个语言一个文件,且键名完全一致:
-
locales/en.json:{"nav.home": "Home", "form.submit": "Submit", "error.required": "Required"} -
locales/zh.json:{"nav.home": "首页", "form.submit": "提交", "error.required": "必填项"} - 某语言暂未翻译,也得留空字符串:
"nav.home": "",而不是直接删掉这个 key - 所有文件必须 UTF-8 编码(无 BOM),否则
fetch()读取时可能解析失败或乱码
切换语言时必须保留状态,不能 reload 页面
页面闪动、表单输入清空、滚动位置重置,都是因为用了 window.location.reload() 或跳转到新 URL。真实场景下,语言切换应是一次局部刷新:
- 只遍历所有
[data-i18n]元素,查表更新内容 - 调用
Intl.DateTimeFormat(locale).format(new Date())重格式化时间字段 - 调用
Intl.NumberFormat(locale).format(12345.67)重格式化数字、货币 - 保持当前路由、
localStorage记忆的语言偏好、第三方组件(如日历控件)的 locale 配置同步更新
最易被忽略的是:动态插入的 DOM(比如 JS 创建的弹窗、懒加载模块)不会自动绑定 data-i18n,需要在插入后手动调用翻译函数,否则新内容永远显示原始语言。


















