为什么你的输入长度限制是错误的
Source: Dev.to
理解问题
你可能在使用数据库字段、文本输入框,或带有 maxlength 属性的 <input>。很可能你的代码计算长度的方式对全球用户来说是不正确的。
我们将从实用、结构化的角度来探讨文本长度,重点关注一种叫做 grapheme cluster(字素簇) 的概念。阅读完本指南后,你将明白标准字符串长度属性为何失效,以及如何修复它们以兼容国际用户。
当你让用户输入姓名并限制为 20 个“字符”时,你到底指的是什么?
- 在 JavaScript 中,字符串的
length属性衡量的是 UTF‑16 代码单元。 - 在 Java、C# 等语言中,通常也衡量代码单元。
- 在 MySQL 等数据库中,你可能在衡量 字节 或 Unicode 代码点。
这些都不是用户所理解的“字符”。
字素簇(Grapheme Cluster)
在 Unicode 术语中,用户感知的单个可视文本单元称为 字素簇。字素簇由一个基字符加零个或多个组合字符组成,这些组合字符会对基字符进行修饰。
示例
- 字母
é可以表示为单个代码点 (U+00E9),也可以表示为基字母e(U+0065) 加上组合重音符◌́(U+0301)。视觉上它是 1 个字素簇,但朴素的string.length检查会得到 2。 - 在用于印地语的天城文中,视觉单元
क्ष्म由多个代码点组成,却只代表 1 个字素簇。 - “女农夫”表情符 👩🌾 由 “女性”表情符 (U+1F469)、零宽连接符 (U+200D) 和 “稻穗”表情符 (U+1F33E) 组合而成。它是 3 个代码点(以及更多字节!),但只算作 1 个用户感知的字符。
想了解 Unicode 在底层是如何工作的,请参阅 W3C Character encodings essential concepts。
为什么你的输入限制会出错
当你严格强制输入长度限制时,可能会在字素簇中间截断文本。
想象一下你的数据库有硬性限制。用户粘贴的字符串在他们看来是 10 个字符,但实际占用了 12 个代码点。如果后端在任意字节或代码点边界切割字符串,你可能会把组合重音符从基字母上分离,或把一个家庭表情符拆成漂浮的孤立头部。
这会导致:
- 数据库记录损坏
- 用户界面出现乱码
- 对使用阿拉伯文、印度文、东南亚文字或旗帜表情符的用户造成不可访问的体验
实用解决方案
为了构建健壮、可靠的国际化系统,需要使用字素簇来度量长度。现代编程环境提供了内置、符合标准的工具来实现这一点。
在 Web 开发中,最有效的准确计数字素簇的方法是使用 ECMAScript 国际化 API 中的 Intl.Segmenter。
// A string with an emoji, a combined diacritic, and standard Latin.
const userInput = "👩🌾éx";
// Code units (UTF‑16)
console.log(userInput.length); // 7
// Correct method: Using Intl.Segmenter
const segmenter = new Intl.Segmenter('en', { granularity: 'grapheme' });
const segments = segmenter.segment(userInput);
// Count the actual grapheme clusters
let graphemeCount = 0;
for (const _ of segments) {
graphemeCount++;
}
console.log(graphemeCount); // 3通过标准化使用 Intl.Segmenter,你可以确保字素簇计数的准确性。
我们有责任构建能够在任何用户语言环境下可靠运行的系统。想更深入了解在 Web 应用中实现全球最佳实践,请参考 W3C Internationalization techniques documentation。