最可控方案是data-i18n标记+json语言包+localstorage记忆,需显式为每个可翻译节点添加data-i18n属性并同步更新document.documentelement.lang及子元素lang属性。

直接用 data-i18n 属性标记可翻译节点,配合 JSON 语言包和轻量 JS 驱动,是唯一能兼顾语义、SEO、可访问性与运行时切换的 HTML 原生方案。其他靠 class 显隐、硬编码双语 DOM 或只改 lang 属性的做法,都会在屏幕阅读器、字体回退或搜索引擎抓取上出问题。
怎么给元素加 data-i18n 才真正生效
不是所有标签都适合加,也不是加了就自动更新——关键看它是否承载用户可见的文本内容:
-
data-i18n只作用于元素自身的文本节点(textContent),比如<h1 data-i18n="header.title"></h1>;<div>、<code><p></p>、<button></button>这类容器可以,但<script></script>、<style></style>、<pre class="brush:php;toolbar:false;"></pre>不行 - 表单控件的属性必须显式标注后缀:
data-i18n-placeholder、data-i18n-title、data-i18n-alt,不能指望一个data-i18n覆盖全部 - 带内联 HTML 的文案(如
"请@#@#@#@#@#@#@#@#@#@0继续")需用innerHTML替换,但语言包里对应值必须是可信的纯 HTML 字符串,否则有 XSS 风险 - 键名必须全小写+英文点号分隔(如
form.submit),禁止空格、中文或驼峰,否则后续对接工具或服务端会错位 -
document.documentElement.lang影响全局:SEO 抓取语言识别、默认拼写检查、字体 fallback 顺序(比如设为ja,浏览器才可能启用 Meiryo 或 Noto Sans CJK JP) - 但局部多语言内容(如
<p lang="en">API</p>、<pre class="brush:php;toolbar:false;" lang="bash"></pre>)不会继承根lang,必须手动保留或重设其原有lang值,否则会被覆盖成主语言 - IE11 不支持
element.dataset.i18n,得用element.getAttribute('data-i18n')兜底,否则属性读不到 - 文件路径必须动态拼接:
./locales/${lang}.json,别写死成./zh.json;BCP 47 标准码优先用zh-Hans、en-US,禁用zh_CN或chinese - fetch 加载必须包
try/catch,且 response 检查ok和Content-Type: application/json,否则网络抖动或 CDN 缓存脏数据会导致白屏 - fallback 逻辑要三层:用户选了
zh-HK→ 尝试加载zh-HK.json→ 失败则降级zh.json→ 再失败强制en.json,不能只 fallback 一次 - 所有语言包键名必须完全一致,哪怕某语言暂未翻译,也要留
"form.submit": "",否则 JS 查不到 key 就返回undefined
为什么 document.documentElement.lang 必须同步更新
只替换文字不改 lang,等于告诉浏览器“内容变了,但语言没变”——屏幕阅读器仍按旧语言朗读,Chrome 翻译按钮不会激活,中日韩混排时字体回退链也失效。
JSON 语言包加载和 fallback 怎么不翻车
路径拼错、MIME 类型不对、键名不一致,任何一个环节出错,整页文案就会显示为 "header.title" 这种原始 key——不是报错,而是静默失败。
最容易被忽略的是动态插入的 DOM:JS 弹窗、AJAX 表格行、第三方组件内部文本——它们不会自动响应语言切换,必须在插入后立刻调用翻译函数,且要过滤掉非文本节点(如 script、textarea 的 value)。这步漏掉,用户永远看不到新语言的弹窗提示。











