为什么你的聊天框会把半句日文发出去
如果你的聊天框在用户按回车时发送消息,它就会把没打完的日文发出去。用日文、中文、韩文打字的人都用输入法,他们按回车是为了确认一个词的转换,而不是发送。解决办法是在输入法组字期间忽略回车:检查 event.isComposing,同时为 Safari 检查 keyCode === 229。
我住在东京,几乎每天都用四种语言打字。在亚洲以外做的 AI 产品里,这是我最常遇到的 bug,其中不乏融资很多的产品。你用平假名打了几个词,按回车想把它们转成汉字,消息却带着错误的字符发了出去,而模型已经开始回答一个你还没问完的问题。
输入法怎么使用回车键
在电脑上打日文分两步。你在拉丁字母键盘上打出读音,输入法把它显示成带下划线的平假名,按空格在汉字候选之间切换,再按回车确认想要的那个。只有确认之后,文字才真正进入输入框。
这个带下划线的阶段叫作组字(composition)。浏览器在开始时触发 compositionstart,在文字确认时触发 compositionend。组字期间,回车键属于输入法。一句普通长度的话,在用户想发送之前,可能要按五六次这样的回车。
聊天式界面让问题更严重。十年前大多数表单需要点一下提交按钮。AI 产品沿用了聊天应用“回车发送、Shift 加回车换行”的模式,而很多这样的处理代码只用英文写、只用英文测。
代码里的解决办法
MDN 在 keydown 事件页面上直接给出了这个写法。检查 isComposing,再检查 keyCode 是否为 229,这是浏览器给经过输入法处理的按键事件设置的值:
textarea.addEventListener("keydown", (event) => {
if (event.isComposing || event.keyCode === 229) return
if (event.key === "Enter" && !event.shiftKey) {
event.preventDefault()
send()
}
})
第二个检查是必需的。MDN 指出,compositionend 可能在关闭输入法那次按键的 keydown 之前触发,于是恰恰在你关心的那次回车上,isComposing 是 false。keyCode 已被弃用,但这种情况下它仍然是 229。
为什么 Safari 需要额外的检查
MDN 背后的浏览器兼容性数据把 Safari 对 isComposing 的支持标为部分支持。说明里写道:对于完成组字的那次按键,keydown 和 input 事件在 compositionend 之后而不是之前派发,所以本该是 true 的 isComposing 变成了 false。Chrome 和 Firefox 的行为符合预期。
这在 Mac 上很重要,在 iPhone 和 iPad 上也一样,因为 Safari 和大多数其他浏览器在那里仍然运行在 WebKit 上。一个在 Windows 笔记本上用 Chrome 和日文输入法测试的团队,会看到第一个检查生效然后上线。用 MacBook 上 Safari 的日本用户照样会遇到这个 bug。
如果你用框架,先确认它替你做了什么。有些 UI 库在文本输入组件里处理了这个问题,有些留给你自己。React 通过 event.nativeEvent 提供原生事件,所以同样的两个检查在那里也能用。
像日本用户一样测试
测试这个不需要懂日文。在 Mac 或 PC 上添加日文输入法并切换过去,在聊天框里打 konnichiha,按空格转换,再按一次回车。这时什么都不应该发出去。再按一次回车,消息才应该发送。
在 Safari、Chrome 和使用日文键盘的 iPhone 上各做一遍。在手机上也试试中文拼音键盘,中文输入有同样的组字步骤,也会出同样的问题。
还有两个情况值得花一分钟。粘贴一句日文然后按回车,这时没有组字,应该发送。再在还带着下划线的组字状态下点发送按钮,有意识地决定它是确认文字还是等待。
对 AI 产品更大的启示
现在的大语言模型日文已经很好。于是很容易以为,只要回答是流畅的日文,产品在日本就能用。真正出问题的是输入框,而且在模型参与之前就已经坏了。
这和我常写的一个主题有关:日本用户会根据一个产品处理小细节的用心程度来判断它,一个出错的回车键会让人觉得这个产品不是为他们做的。我在日本 UX 有什么不同里写过整体规律,在关于日本英语写作分数的文章里写过为什么文字界面这么重要。输入法检查只有四行代码,而对日本用户来说,它往往是有人用他们的语言测过这个产品的第一个信号。