最轻量多语言方案是用data-i18n标记+json语言包+localstorage持久化;需显式标注所有文本节点及属性,语言包扁平结构、bcp 47命名,切换时同步更新lang属性、localstorage等四要素。

直接用 data-i18n 标记 + JSON 语言包 + localStorage 持久化,是最轻量、可调试、不依赖框架的实现路径。硬编码文案、只改 document.documentElement.lang 或靠 CSS 显隐双语 DOM,都会在可访问性、SEO 或维护性上出问题。
怎么给 HTML 元素打 data-i18n 标记才不会漏翻
所有要动态换语言的文本节点(<h1></h1>、<p></p>、<button></button>、<input> 等)必须显式加 data-i18n 属性,值为语言包里的键名,比如 "header_title"。
-
data-i18n只影响元素自身的textContent;placeholder、title、alt、value这类属性得单独用data-i18n-placeholder、data-i18n-title等后缀属性标记,否则切换后这些属性仍是旧值 - 别用
id或class当翻译键——它们语义不明确,JS 无法自动映射,后期改文案极易漏掉 - 键名统一用英文小写+下划线,如
"search_hint",禁用空格、中文、驼峰或连字符,避免和 BCP 47 语言码(如zh-Hans)混淆 -
<script></script>、<pre class="brush:php;toolbar:false;"></pre>、<textarea></textarea>里加data-i18n基本无效——这些节点不参与文本渲染,加了也不触发替换
JSON 语言包怎么组织才不崩、不丢 key
每个语言一个独立文件,路径固定,结构扁平,键名严格对齐。加载失败或 key 缺失时若无 fallback,整页文案会变为空白或显示 key 字符串本身。
- 文件命名必须符合 BCP 47 标准:
locales/en.json、locales/zh-Hans.json、locales/ja.json——zh_CN、chinese、zh都不可靠,可能被忽略或触发降级失败 - 所有 JSON 文件内容必须是顶层键值对,禁止嵌套:
{"header_title":"欢迎","btn_submit":"提交"},不能写成{"zh":{"header_title":"欢迎"}} - 新增语言时,必须复制一份已有文件结构并留空未翻译项,否则 JS 查不到 key 就返回
undefined,最终显示"header_title"字面量 - 用
fetch('./locales/${lang}.json')加载,别硬编码路径;try/catch包裹整个 fetch +response.json()流程,网络中断或 JSON 格式错不能让整页翻译挂掉
切换语言时必须同步更新的 4 个关键点
只替换文本是假切换。屏幕阅读器、字体回退、拼写检查、标点间距全靠 lang 属性驱动,不是继承来的,也不是“视觉上看着像”就行。
- 必须设
document.documentElement.lang = 'zh-Hans'(不是'zh'或'zh-CN'),这是浏览器识别主语言的唯一权威依据 -
localStorage.setItem('lang', 'zh-Hans')要在语言包成功加载、文本替换完成之后再执行,否则用户刷新页面会拿到未就绪的语言状态 - 所有显式写了
lang的子元素(比如<pre class="brush:php;toolbar:false;" lang="bash"></pre>、<q lang="en"></q>)不能被根节点覆盖——它们得手动保留原lang值,否则代码块变中文语音、引文丢失英文标点 - 带 HTML 结构的文案(如
"请查看 <strong>帮助文档</strong>")要用innerHTML替换,但语言包里对应值必须是可信的纯 HTML 片段,否则有 XSS 风险
最易被忽略的是:语言包加载成功前,页面已按默认语言渲染完毕,此时 document.documentElement.lang 如果没提前设好,屏幕阅读器一进页面就读错语种;而动态插入的 DOM(弹窗、表格行)如果没在插入后立刻调用翻译函数,就会永远显示原始 key。这两处不处理,多语言就只是“看起来能切”,实际体验断层严重。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











