网页表单报错信息生硬,怎么用原生方法实现多语言自定义提示?

3,611字
15–23 分钟
in

很多网站的表单提交时,弹出来的报错提示都是浏览器自带那一套——要么是“请填写此字段”,要么是英文的“Please fill out this field”。如果碰到需要切换语言的场景,比如做外贸站或者多语言社区,这些默认提示就显得特别呆板。实际上HTML原生自带了一套表单验证机制,配合一个叫 Constraints API 的小工具,完全能自己定制报错文案,甚至实现多语言切换。下面直接上硬核操作。

目录

啥是Constraints API

这玩意儿就像浏览器给每个表单控件装了个“对讲机”。默认情况下,对讲机里传出来的话是固定的(比如“必须填写”)。调用 setCustomValidity() 方法,就能把这句话换成自己想说的。举个例子:一个必填的姓名输入框,原本没填就报“请填写此字段”,通过 setCustomValidity("老铁,这里不能空着呀") 改完之后,报错就变成了自定义的文案。不过有个坑:一旦设置了自定义消息,表单会一直拦着不让提交,直到把消息清空(重新设为空字符串)才行。

动手撸个基础版

先搭个最简单的表单架子:

<form id="demoForm">
  <label>全名</label>
  <input type="text" id="fullName" required>
  <button type="submit">提交</button>
</form>

接着写JS来劫持报错:

const nameInput = document.getElementById("fullName");
nameInput.setCustomValidity("这把必须填个真名,不能写'佚名'嗷");

当点击提交按钮且输入框为空时,页面会弹出上面那句自定义话术。但光这样还不够,因为消息是写死的。想要支持多语言,得搞两套更骚的方案。

方案一:蹭浏览器的语言设置

浏览器自带 navigator.language 属性,能拿到当前环境语言(比如 zh-CNen-US)。写一个翻译词典,根据这个属性自动匹配报错文案。

搞个翻译库

const langPack = {
  zh: { required: "兄弟,这儿不能留白" },
  en: { required: "Hey, this field cannot be empty" },
  ja: { required: "ここは必須ですよ" }
};

写个语言嗅探函数

const supportLangs = Object.keys(langPack);
function getBrowserLang() {
  const rawLang = navigator.language.split('-')[0];  // 截掉后面的地区码
  return supportLangs.includes(rawLang) ? rawLang : 'en';
}

挂载到表单上

const form = document.getElementById("demoForm");
const nameField = document.getElementById("fullName");
const currentPack = langPack[getBrowserLang()];

form.addEventListener("submit", (e) => {
  if (!nameField.value) {
    nameField.setCustomValidity(currentPack.required);
  } else {
    nameField.setCustomValidity("");  // 清空自定义消息,否则永远拦着
  }
});

这里有个容易翻车的小细节:setCustomValidity() 一旦塞了非空字符串,表单就会认为“验证不通过”。所以在用户补填内容后,必须手动把它重置成空字符串,否则即使填了东西也会一直报错。另外 navigator.language 返回的可能是 zh-TWen-GB,用 split('-')[0] 只取前半部分,避免匹配不到。

方案二:让用户自己在页面上选语言

有时候浏览器语言是英文,但用户想看中文报错。这种场景下,在页面上放个下拉菜单,把选中的语言存到 localStorage 里,下次刷新页面直接读取。

页面加个语言选择器

<select id="langSwitcher">
  <option value="zh">中文</option>
  <option value="en">English</option>
  <option value="ja">日本語</option>
</select>

<form id="smartForm">
  <input type="text" id="userName" required>
  <button type="submit">走你</button>
</form>

读写本地存储

const selector = document.getElementById("langSwitcher");
const smartForm = document.getElementById("smartForm");
const userNameField = document.getElementById("userName");

const msgMap = {
  zh: { required: "喂,这块不能空" },
  en: { required: "Yo, this field is required" },
  ja: { required: "入力してください" }
};

function getStoredLang() {
  const saved = localStorage.getItem("preferLang");
  if (saved && msgMap[saved]) return saved;
  // 后备逻辑:看浏览器语言
  const fallback = navigator.language.split('-')[0];
  return msgMap[fallback] ? fallback : 'en';
}

function applyLang(langCode) {
  selector.value = langCode;
  localStorage.setItem("preferLang", langCode);
}

// 初始化
const initLang = getStoredLang();
applyLang(initLang);

// 监听下拉菜单变化
selector.addEventListener("change", (e) => {
  applyLang(e.target.value);
});

// 提交时用当前选中的语言
smartForm.addEventListener("submit", (e) => {
  const currentLang = selector.value;
  const tip = msgMap[currentLang].required;
  if (!userNameField.value) {
    userNameField.setCustomValidity(tip);
  } else {
    userNameField.setCustomValidity("");
  }
});

这套方案的灵魂在于:用户选完语言后,刷新页面还能记住。比如第一次进站选“日本語”,关掉浏览器再打开,下拉菜单自动切到日语,报错也是日语。不过得注意,localStorage 存的值如果被用户手动清空,需要自动降级到浏览器语言。上面代码里 getStoredLang() 已经做了这个兜底。

两种方案哪个更香

对比项蹭浏览器语言本地存储选择
上手难度简单稍微多两行代码
用户控制力无,改不了随便切
适用场景单语言站或懒人版多语言切换站

举个打比方:方案一像自动挡汽车,踩油门就走;方案二像带换挡拨片的,用户能自己拨一下。如果做跨境电商后台,建议上方案二,因为运营同学可能用英文系统但想看中文报错。如果只是个人博客,方案一完全够用。

实际开发中还有个隐藏技巧:表单里如果有多个输入框,不要一股脑全设自定义消息。可以遍历所有 input,针对 requiredpatternminlength 等不同校验类型分别设置。比如邮箱格式错了就提示“邮箱后缀得带@”,长度不够就提示“最少5个字符”。用 validity 对象判断具体是哪种校验失败:

if (input.validity.valueMissing) {
  input.setCustomValidity("老哥,填一下呗");
} else if (input.validity.patternMismatch) {
  input.setCustomValidity("格式不对,按示例来");
}

另外 setCustomValidity() 在移动端也管用,不过Safari对自定义样式的支持有点抽风,报错气泡的样式没法大改,但文案替换是没问题的。最后多嘴一句:千万别在 setCustomValidity() 里塞空字符串以外的任何东西,否则表单会一直卡着不让提交,那场面就跟电梯门一直关不上似的,贼尴尬。