最稳路径是data-i18n标记+独立json语言包+同步更新document.documentelement.lang;需显式标注所有文本节点及placeholder/title/alt等属性,语言包扁平结构、bcp47命名,切换时同步更新lang、localstorage、动态节点并确保fallback机制。

直接用 data-i18n 标记 + 独立 JSON 语言包 + 同步更新 document.documentElement.lang,是当前 HTML 项目做国际化迁移最稳、最易调试、不依赖构建工具或后端框架的路径。硬编码文案、只改根节点 lang、或靠 CSS 类显隐双语 DOM,会在 SEO、可访问性、动态内容和 fallback 场景中翻车。
怎么打 data-i18n 标记才不会漏关键属性
所有要翻译的文本节点(<h1></h1>、<p></p>、<button></button>、<label></label>)必须显式加 data-i18n,值为英文小写+下划线键名,例如 "nav_home";但仅设这个不够:
-
placeholder、title、alt这类属性不会被textContent覆盖,得额外加对应后缀:如data-i18n-placeholder="search_hint" -
value一般不翻译(表单输入值属于用户数据),跳过处理;但<label></label>的文字必须标记 - 含 HTML 结构的文案(如“请阅读使用条款”)需用
innerHTML替换,且语言包里对应值必须是可信纯 HTML 片段,否则有 XSS 风险 - 动态插入的 DOM(弹窗、AJAX 表格行)插入后必须立即调用翻译函数,否则不会自动生效
- 别在
<script></script>、<style></style>、<pre class="brush:php;toolbar:false;"></pre>内部加data-i18n——这些节点不参与渲染,JS 替换无效
fetch('./locales/zh.json') 加载失败时页面为啥全白
因为没做 fallback —— fetch 失败、响应不是 JSON、或 key 缺失时,JS 查不到值就留空,用户看到一堆 data-i18n="xxx" 键名。正确做法是:
- 路径拼接用
./locales/${lang}.json,别写死成./zh.json - 用
try/catch包住fetch()和response.json() - fallback 优先级:先试完整码(如
zh-HK),再截主语言(zh),最后退到默认语言(如en) - 所有语言包结构必须扁平、键名严格一致;某语言暂未翻译,也要保留键并设为空字符串:
"btn_submit": "" - 服务器返回 JSON 时,HTTP
Content-Type必须是application/json,否则fetch可能静默失败
切换语言时为什么屏幕阅读器还在读旧语音
因为浏览器和辅助技术按每个元素自身的 lang 属性决定语音、字体回退、标点间距——不是继承来的。只替换文本、只改 document.documentElement.lang,已渲染的子元素完全不受影响。
- 切换前必须遍历所有已带
lang属性的元素(如<p lang="en"></p>、<pre class="brush:php;toolbar:false;" lang="bash"></pre>),把它们的lang值也同步更新(除非明确要保留原语言) -
document.documentElement.lang必须设成 BCP 47 标准码(如zh-Hans、ja),zh_CN或chinese会被忽略,甚至触发 fallback 失败 - 更新 DOM 前,用
getComputedStyle记录滚动位置;更新完立刻window.scrollTo()恢复,否则页面跳回顶部
真正麻烦的不是加标记或换 JSON,而是那些没显式打 data-i18n 却又该翻译的地方:SVG 里的 <text></text>、select 的 option 文字、JS 动态生成的 alert 或 Toast。这些地方一旦漏掉,用户切语言后会突然冒出英文,而且很难排查。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











