很多网站的表单提交时,弹出来的报错提示都是浏览器自带那一套——要么是“请填写此字段”,要么是英文的“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-CN、en-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-TW 或 en-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,针对 required、pattern、minlength 等不同校验类型分别设置。比如邮箱格式错了就提示“邮箱后缀得带@”,长度不够就提示“最少5个字符”。用 validity 对象判断具体是哪种校验失败:
if (input.validity.valueMissing) {
input.setCustomValidity("老哥,填一下呗");
} else if (input.validity.patternMismatch) {
input.setCustomValidity("格式不对,按示例来");
}另外 setCustomValidity() 在移动端也管用,不过Safari对自定义样式的支持有点抽风,报错气泡的样式没法大改,但文案替换是没问题的。最后多嘴一句:千万别在 setCustomValidity() 里塞空字符串以外的任何东西,否则表单会一直卡着不让提交,那场面就跟电梯门一直关不上似的,贼尴尬。
