最近看到一个拆分字符串的新方式,就是使用Intl.Segmenter将 emoji 字符串分割成字形的方法。
我以前都没用过这个Intl
对象,现在我们一起来看看。
假设你想把用户输入拆分成句子,看起来是一个简单的 split()
任务…但这个问题有很多细微之处。
'Hello! How are you?'.split(/[.!?]/); // ['Hello', ' How are you', '']
使用 split()
会丢失定义的分隔符,并在所有地方包含这些空格。而且因为它依赖于硬编码的分隔符,所以对语言不敏感。
我不懂日语,但你会如何尝试将下面的字符串分割成单词或句子?
// I am a cat. My name is Tanuki. '吾輩は猫である。名前はたぬき。'
普通的字符串方法在这里是没有用的,但是Intl JavaScript API
确能解决这个问题。
Intl.Segmenter 来救场
Intl.Segmenter 是一个 JavaScript 对象,用于对文本进行区域设置敏感的分段。它可以帮助我们从字符串中提取有意义的项目,如单词、句子或字形。它的使用方式类似于其他的构造函数,可以使用 new
关键字来创建一个 Intl.Segmenter 对象。
const segmenter = new Intl.Segmenter(locale, { granularity: "word" });
在上面的代码中,locale
是字符串,表示要使用的区域设置。granularity
是字符串,表示分段的粒度。它可以是 “grapheme”(字形)、”word”(单词)或 “sentence”(句子)之一。
Intl.Segmenter 有一个很有用的方法叫做 segment()
,它可以将文本拆分为有意义的段。
const segments = segmenter.segment(text);
在上面的代码中,text
是要拆分的文本,segments
是返回的段的迭代器。你可以使用 for-of
循环来遍历段,或者使用 Array.from()
将它们转换为数组。
const text = "Hello, world! How are you today?"; const segmenter = new Intl.Segmenter("en-US", { granularity: "sentence" }); const segments = segmenter.segment(text); for (const segment of segments) { console.log(segment); } // Output: // { index: 0, value: "Hello, world!", breakType: "", breakIndex: 12 } // { index: 13, value: "How are you today?", breakType: "", breakIndex: 31 }
Intl.Segmenter 对象还有其他一些有用的方法,比如 breakType
,用于检索分段的类型(例如,句子的末尾是否包含句号)。另一个有用的方法是 breakType
,用于检索分段的类型。
例如:
const text = "Hello, world! How are you today?"; const segmenter = new Intl.Segmenter("en-US", { granularity: "sentence" }); const segments = segmenter.segment(text); for (const segment of segments) { console.log(segment.breakType); } // Output: // "exclamation" // "question"
Intl.Segmenter 还有一个很有用的静态方法叫做 supportedLocalesOf()
,它可以帮助你确定浏览器是否支持特定的区域设置。
const supported = Intl.Segmenter.supportedLocalesOf(["en-US", "zh-CN"]); console.log(supported); // Output: // ['en-US', 'zh-CN']
在上面的代码中,supported
数组包含浏览器支持的区域设置。
如果你想要对文本进行更细粒度的分段,你可以使用 Intl.ListFormat
对象。它可以帮助你将文本拆分为有意义的列表项。
使用方式类似于 Intl.Segmenter
,你可以使用 new
关键字创建一个 Intl.ListFormat
对象。
const listFormat = new Intl.ListFormat(locale, { style: "long", type: "conjunction" });
在上面的代码中,locale 是字符串,表示要使用的区域设置。style 和 type 是对象的属性,用于指定列表格式。style 可以是 “long” 或 “short”,type 可以是 “conjunction”(并列)或 “disjunction”(或)。
Intl.ListFormat
有一个很有用的方法叫做 format()
,它可以将数组转换为有意义的列表。
const list = ["apple", "banana", "orange"]; const formatted = listFormat.format(list); console.log(formatted); // Output: // "apple, banana, and orange"
在上面的代码中,formatted
是转换后的列表字符串。
Word 的颗粒度带有一个额外的 isWordLike 属性
如果把一个字符串分割成单词,所有的片段都包括空格和换行符。使用isWordLike
属性将它们过滤掉。
const segmenterDe = new Intl.Segmenter('de', { granularity: 'word' }); const segmentsDe = segmenterDe.segment('Was geht ab?'); console.log([...segmentsDe]); // [ // { segment: 'Was', index: 0, input: 'Was geht ab?', isWordLike: true }, // { segment: ' ', index: 3, input: 'Was geht ab?', isWordLike: false }, // ... // ] console.log([...segmentsDe].filter(s => s.isWordLike)); // [ // { segment: 'Was', index: 0, input: 'Was geht ab?', isWordLike: true}, // { segment: 'geht', index: 4, input: 'Was geht ab?', isWordLike: true }, // { segment: 'ab', index: 9, input: 'Was geht ab?', isWordLike: true } // ]
上面通过isWordLike
进行过滤会删除标点符号,如.
、-
、或?
。
使用 Intl.Segmenter 来分割 emojis
如果你想把一个字符串分割成可视化的 emojis,Intl.Segmenter
也是一个很好的帮助。
const emojis = '🫣🫵👨👨👦👦'; // ---- // Split by code units console.log(emojis.split('')); // ['uD83E', 'uDEE3', 'uD83E', 'uDEF5', 'uD83D', 'uDE48'] // ---- // Split by code points console.log([...emojis]); // ['🫣', '🫵', '👨', '', '👨', '', '👦', '', '👦'] // ---- // Split by graphemes const segmenter = new Intl.Segmenter('en', { granularity: 'grapheme' }); const segments = segmenter.segment(emojis); console.log(Array.from( segmenter.segment(emojis), s => s.segment )); // ['🫣', '🫵', '👨👨👦👦']
请注意,字形也包括空格和 “正常 “字符。