data-i18n仅替换元素textcontent,不作用于属性或特殊标签;需用data-i18n-xxx显式声明placeholder/alt/title/value等;键名须全小写点分隔且与语言包严格一致;ie11需getattribute兜底,动态dom需手动调用翻译。

data-i18n 只作用于 textContent,不是万能替换器
加了 data-i18n="header.title",不代表所有文字和属性都会自动更新。它只负责替换元素自身的 textContent,比如 <h1 data-i18n="header.title">首页</h1> 里的“首页”会被替换成语言包里 header.title 对应的值。
但以下情况完全不受影响,必须单独处理:
-
<input placeholder="搜索">→ 必须写成<input data-i18n-placeholder="search.hint"> -
<img alt="图标">→ 必须写成<img data-i18n-alt="icon.home"> -
<button title="提交">Submit</button>→ 必须加data-i18n-title="btn.submit" -
<button value="提交"></button>→ 表单控件的value默认不响应textContent更新,得用data-i18n-value
漏掉任一后缀,切换后对应内容就卡在旧语言里——这不是 JS 没跑,而是你没告诉它“这里也要换”。
哪些标签能加 data-i18n,哪些加了也白加
能加且有效的:承载用户可见文本的常规容器,如 <h1></h1>、<p></p>、<div>、<code><span></span>、<button></button>、<label></label>、<option></option>(需单独遍历)。
加了也白加的:
-
<script></script>、<style></style>、<pre class="brush:php;toolbar:false;"></pre>—— 这些节点不渲染为用户可见文本,JS 找不到可替换目标 -
<textarea></textarea>—— 其内容是用户输入态,一般不主动覆盖;若确需翻译占位符,用data-i18n-placeholder - 仅靠
id="header"或class="title"推断键名 —— JS 不会猜,必须显式写data-i18n="header.title"
常见翻车点:给 <pre class="brush:php;toolbar:false;" lang="bash">curl -X GET ...</pre> 加 data-i18n,结果啥也没变——因为那是代码,不是文案。
键名命名和语言包结构必须严格对齐
键名不是随便起的,错一个字符整个 key 就查不到:
- 必须全小写 + 英文点号或下划线分隔:
form.submit✅,formSubmit❌,form submit❌,form_submit✅(但点号更通用) - 禁止中文、空格、驼峰、连字符(避免和 BCP 47 语言码如
zh-Hans冲突) - JSON 文件必须扁平:
{"header.title": "Home", "btn.submit": "Submit"}✅;嵌套结构如{"en": {"header.title": "Home"}}❌ - 所有语言文件(
en.json、zh-Hans.json、ja.json)键名必须完全一致;新增语言时,要复制结构并留空未翻译项,否则缺失 key 就显示字面量"header.title"
路径也得动态拼:./locales/${lang}.json,别硬写成 ./zh.json —— 否则切到 zh-HK 就 404。
IE11 和动态 DOM 是两个隐藏雷区
IE11 不支持 element.dataset.i18n 这种驼峰访问,必须兜底用 element.getAttribute('data-i18n'),否则属性读不到,整页静默失效。
动态插入的 DOM(比如通过 innerHTML += '...' 或框架 append)不会自动被翻译逻辑捕获。你得在插入后手动调用翻译函数,例如:
const el = document.createElement('p');
el.setAttribute('data-i18n', 'new.message');
el.textContent = 'placeholder';
container.appendChild(el);
translateElement(el); // 必须显式触发
另外,<svg></svg> 里的 <text></text> 节点需要特殊处理——它不走标准 textContent 流程,得用 textContent 或 firstChild.nodeValue 单独设值,且不能依赖通用遍历逻辑。











