多语言资源必须独立存放于项目根目录下的locales/子目录,文件名严格按BCP 47格式(如zh-HK.json),使用根相对路径fetch("/locales/zh.json")确保稳定加载;语言包字段需对齐、禁用.js后缀;data-i18n须覆盖placeholder/alt/title/aria-label等所有可翻译属性;HTML文件不拆分,但每个含文本的语义化标签须显式设置lang属性;构建时需确保locales/完整复制至dist/且服务器支持application/json MIME类型。

多语言资源必须独立存放于locales/目录
别把en.json和zh-Hans.json塞进js/或assets/里——路径一乱,fetch("./locales/en.json")就404。所有语言包统一放locales/子目录,文件名严格按BCP 47格式:小写字母、短横线分隔、无空格,如zh-HK.json、pt-BR.json,不写zh_CN.json或Chinese.json。
常见错误现象:fetch("lang/zh.json")在pages/about.html里执行时,实际请求的是pages/lang/zh.json;用根相对路径fetch("/locales/zh.json")才能稳定定位。
-
locales/必须是项目根目录下的直接子目录,不能嵌套在src/或assets/内 - 每个语言文件字段完全对齐,缺失翻译也保留键并设为空字符串,避免
undefined导致文本留白 - 不要用
.js后缀存语言包(如en.js),HTTP Content-Type会错,fetch()可能静默失败
data-i18n标记必须覆盖所有可翻译属性,不只是textContent
只给<h1 data-i18n="home.title">加属性,placeholder、alt、title、aria-label全都不变——这是最常被忽略的断裂点。浏览器不会自动把data-i18n映射到这些属性,必须显式声明后缀。
例如:<input placeholder="Search" data-i18n-placeholder="search_hint">,<img alt="User avatar" data-i18n-alt="avatar_desc">。否则切换语言后,输入框提示还是英文,图片替代文本仍无法被屏幕阅读器正确朗读。
立即学习“前端免费学习笔记(深入)”;
-
value属性一般不翻译(属于用户输入数据),但button和input[type="submit"]的显示文案建议用textContent更新,而非改value -
label的for属性不翻译,但其包裹的文字必须标记data-i18n,否则点击失效 - 含HTML结构的文案(如“请阅读服务条款”)需用
innerHTML替换,且语言包值必须是可信纯HTML片段,不能拼接用户输入
HTML文件本身不按语言拆分,但lang属性必须逐元素设置
别建index-en.html、index-zh.html——这会让路由、SEO、缓存、CI/CD全部复杂化。一个index.html配一套data-i18n标记 + 多份JSON语言包,才是轻量可控的做法。
但只改document.documentElement.lang = "zh-Hans"远远不够。浏览器和屏幕阅读器按每个元素自身的lang属性决定标点间距、字体回退、语音语调。没显式写的,继承自根节点;但已有lang的特殊元素(如<pre lang="bash">)必须保留原值,不能一并覆盖。
- 所有含文本的语义化标签(
<h1>、<p>、<section>、<footer>)都应带lang属性,值与当前激活语言包一致 -
<script>和<style>内部不写lang,它们不参与文本渲染 - 动态插入的DOM(AJAX弹窗、分页表格行)插入后必须立即遍历并调用翻译函数,MutationObserver不可靠
构建产物中locales/目录必须原样复制进dist/
用Vite或Webpack时,locales/默认不被识别为静态资源,容易漏进构建产物。结果本地file://能跑通,部署后fetch("/locales/en.json")返回404。
解决方法不是手动拷贝,而是配置构建工具明确包含该目录:
- Vite:在
vite.config.js中添加publicDir: 'public',把locales/移进public/,或用assetsInclude显式包含**/locales/** - Webpack:在
copy-webpack-plugin中添加{ from: 'locales', to: 'locales' } - 确保
dist/locales/存在且文件可读,HTTP服务器要允许application/jsonMIME类型
最容易被忽略的是:语言包加载失败时,fetch().catch()必须有兜底逻辑,否则整个页面文案变空,而不是回退到默认语言。



















