最可控方案是data-i18n标记+json语言包+显式同步lang属性;需为placeholder/title/alt等属性分别添加data-i18n-placeholder等后缀,同步更新所有元素lang值,动态dom插入后立即翻译,并做好语言包加载fallback。

直接用 data-i18n 标记 + 外部 JSON 语言包 + 显式同步 lang 属性,是当前 HTML 工程化体系中最可控、可调试、不依赖构建工具的扩展起点。硬编码文案、只改 document.documentElement.lang 或靠 CSS 类切换,会在动态 DOM、SEO、可访问性上持续出问题。
如何让 data-i18n 支持 placeholder/title/alt 等属性
只给元素加 data-i18n="btn_submit" 不够——textContent 更新不了 placeholder 这类属性,屏幕阅读器会读旧值。
- 必须为每个需翻译的属性单独加后缀:如
data-i18n-placeholder、data-i18n-title、data-i18n-alt -
value属性通常不翻译(属用户输入数据),但label文本必须标记,且其for必须与对应inputhttps://www.php.cn/link/93ac0c50dd620dc7b88e5fe05c70e15bid严格匹配 - 含 HTML 结构的文案(如“请阅读使用条款”)要用
innerHTML替换,但语言包里对应值必须是可信纯 HTML 片段,否则有 XSS 风险 - 别在
<script></script>、<style></style>、<pre class="brush:php;toolbar:false;"></pre>内部加data-i18n—— 这些节点不参与渲染,JS 替换无效
语言包加载失败时页面为啥全白
因为没做 fallback:fetch 失败、响应不是 JSON、或 key 缺失时,JS 查不到值就留空,用户看不到任何提示。
- 路径拼接用
./locales/${lang}.json,别写死成./zh.json - 用
try/catch包裹fetch()和response.json() - fallback 优先级:先试完整 BCP 47 码(如
zh-HK),再截主语言(zh),最后退到默认语言(如en) - 所有语言包结构必须扁平、键名完全一致;某语言暂未翻译,也要保留键并设为空字符串:
"btn_submit": "" - 服务器返回 JSON 时,
Content-Type必须是application/json,否则fetch可能静默失败
为什么只改 document.documentElement.lang 屏幕阅读器还是读中文
因为浏览器和辅助技术按每个元素自身的 lang 属性决定语音、字体回退、标点间距——不是继承来的。根节点改了,已渲染的子元素完全不受影响。
- 切换语言后,必须遍历所有已有
lang属性的元素(如<p lang="en">API</p>、<pre class="brush:php;toolbar:false;" lang="bash"></pre>),把它们的lang值也同步更新(除非明确要保留原语言) - 所有带文本内容的语义化容器(
<h1></h1>、<p></p>、<section></section>)都应显式加lang属性,值与当前语言包一致 - 对明确多语言混排的场景(如
<pre class="brush:php;toolbar:false;" lang="bash"></pre>),切换时需跳过覆盖——这是设计需求,不是 bug
动态插入的 DOM 怎么保证立刻翻译
弹窗、表格行、AJAX 返回内容不会自动被全局扫描函数捕获,必须在 appendChild() 或 insertAdjacentHTML() 后立即调用翻译函数。
- 不要等事件循环或 setTimeout;插入完成即刻执行,否则用户看到的是原始英文
- 若用框架(如 Vue/React),确保翻译逻辑注入到组件挂载或 DOM 提交后钩子中,而非仅靠 mounted/created
- 避免在插入前预处理 HTML 字符串再塞进去——容易漏掉
data-i18n属性或破坏事件绑定 - 对第三方组件(如日期选择器),需调用其内置 locale 切换方法(如
flatpickr.set("locale", "zh")),不能只靠 DOM 翻译
真正难的不是加标记或切语言,而是让每处 lang 属性、每个属性后缀、每次动态插入都保持同步——漏掉一个,可访问性或 SEO 就可能断掉一环。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











