最可控方案是data-i18n标记+json语言包+同步更新document.documentelement.lang;需显式为所有可见文本节点及placeholder/title/alt等属性添加对应后缀,语言包扁平结构、bcp 47命名,切换时同步更新lang属性、localstorage及动态节点,并确保含html文案用可信片段、动态dom手动翻译、script/style/pre内不加标记。

直接用 data-i18n 标记 + 外部 JSON 语言包 + 同步更新 document.documentElement.lang,是当前最可控、可调试、不依赖构建工具的落地方式。硬编码文案、只改根节点 lang、或靠 CSS 类显隐双语 DOM,会在可访问性、SEO、动态内容和 fallback 场景中出问题。
怎么给 HTML 元素打 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 加载 JSON 语言包时为什么常失败
加载失败不是 JS 写错了,而是路径、响应头或结构没对齐:
- 路径必须统一,推荐固定为
./locales/${lang}.json,比如./locales/zh-Hans.json;BCP 47 格式必须严格,zh_CN或chinese会被忽略 - 服务器返回 JSON 时,HTTP
Content-Type必须是application/json,否则fetch可能静默失败 - 语言包结构必须扁平、键名完全一致:所有文件字段对齐,某语言暂未翻译也要保留键,设为空字符串
"btn_submit": "",否则查不到 key 就留白 - 必须加
try/catch包裹fetch和response.json(),fallback 顺序应为:先试完整码(如zh-HK),再截主语言(zh),最后退到默认语言(如en)
为什么只改 textContent 页面会读错语音、字体回退异常
浏览器和屏幕阅读器按每个元素自身的 lang 属性决定行为,不是继承自根节点。只替换文本、只改 document.documentElement.lang,会导致:
- 已渲染的子元素
lang值不变,屏幕阅读器仍按旧语言朗读 - 中日韩混排时标点间距、字体 fallback 错乱(比如中文段落里嵌了英文术语,该
lang="en"的必须保留) - 切换前必须遍历所有已带
lang属性的元素(如<p lang="en"></p>、<pre class="brush:php;toolbar:false;" lang="bash"></pre>),把它们的lang值也同步更新(除非明确要保留原语言) - 更新 DOM 前建议用
getComputedStyle记录滚动位置,否则页面可能闪动或跳转
最容易被忽略的是:语言包里不能放运行时计算内容(如用户昵称、时间格式),这些必须交给 Intl.DateTimeFormat 或模板函数处理;还有 localStorage 存的不只是语言码,还得存 BCP 47 完整标识(如 zh-Hans 而非 zh),否则后续 fallback 和字体策略全乱。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











