Locale-sensitive text segmentation in JavaScript with Intl.Segmenter title. A vibrant gradient with a JavaScript logo in the bottom-left corner and a graphic representing localization in the top-right corner.

使用 Intl.Segmenter 在 JavaScript 中进行区域敏感文本分段

阅读时间 5 分钟

今年早些时候,JavaScript 的 Intl.Segmenter 对象获得了所有三大浏览器引擎的支持,这意味着它已经达到了“新可用”的 基线状态。现在,在最新浏览器中,您的应用程序可以本地化地从各种地区字符串中检索有意义的信息。这对于开发人员来说是个好消息,特别是那些正在构建支持地区感知(locale-aware)的应用程序或 UI,并为此目的编写自定义处理程序或使用第三方库的开发者。让我们通过一些实际示例来探讨它带来的可能性。

文本分段的用途是什么?

文本分段是将文本划分为字符、单词和句子等单元的方法。假设您有以下日文文本,并希望进行字数统计。

吾辈は猫である。名前はたぬき。

如果您不熟悉日语,您可能会尝试使用内置的字符串方法。对于英文文本,大致计算单词的方法是按空格分割。

js
const str = "How many words. Are there?";
const words = str.split(" ");
console.log(words);
// ["How","many","words.","Are","there?"]
console.log(words.length);
// 5

标点符号混在单词匹配中,这会不准确,但这是一个不错的近似。问题在于日文文本中没有任何空格分隔字符。也许您的下一个想法是使用 str.length 来计算字符。使用字符串长度,您将得到 15,如果您去掉句号 (),您可能会猜测是 **13 个单词**。

问题是我们实际上在不带标点符号的字符串中有 **8 个单词**:'吾輩' 'は' '猫' 'で' 'ある' '名前' 'は' 'たぬき'。如果您依赖字符串方法进行字数统计,您将很快遇到麻烦,因为您无法可靠地按特定字符分割,也无法像英语那样使用空格作为分隔符。

这正是为地区敏感分段而设计的。在 Intl 命名空间中创建分段器的格式如下:

js
new Intl.Segmenter(locales, options);

让我们尝试使用日语的 ja-JP 地区将字符串传递给分段器,并明确设置每个分段为单词级别的粒度。

js
const jaSegmenter = new Intl.Segmenter("ja-JP", { granularity: "word" });
const segments = jaSegmenter.segment("吾輩は猫である。名前はたぬき。");

console.log(Array.from(segments));

此示例将以下数组记录到控制台:

js
[
  {
    "segment": "吾輩",
    "index": 0,
    "input": "吾輩は猫である。名前はたぬき。",
    "isWordLike": true
  },
  {
    "segment": "は",
    "index": 2,
    "input": "吾輩は猫である。名前はたぬき。",
    "isWordLike": true
  },
  {
    "segment": "猫",
    "index": 3,
    "input": "吾輩は猫である。名前はたぬき。",
    "isWordLike": true
  },
  // etc.

对于数组中的每个项目,我们都会获得分段、它在原始字符串中的索引、完整的输入字符串以及一个布尔值 isWordLike,用于区分单词和标点符号等。现在,我们有了一种强大而结构化的方式来与单词进行交互,并且这种方式是地区感知的。在此示例中,分段器的粒度为 word,因此我们可以根据 isWordLike 的值来过滤每个项目,以忽略标点符号。

js
const jaString = "吾輩は猫である。名前はたぬき。";

const jaSegmenter = new Intl.Segmenter("ja-JP", { granularity: "word" });
const segments = jaSegmenter.segment(jaString);

const words = Array.from(segments)
  .filter((item) => item.isWordLike)
  .map((item) => item.segment);

console.log(words);
// ["吾輩","は","猫","で","ある","名前","は","たぬき"]
console.log(words.length);
// 8

这看起来好多了。我们得到了一个包含日语单词的数组,可以使用分段器,并准备好为我们的应用程序添加地区感知的字数统计功能。我们将在接下来的部分中通过一个小型示例进一步探讨这一用例。在此之前,我们将查看您可以传递给分段器的其他选项。

Intl.Segmenter 选项和配置

我们上面已经看到,您可以根据地区将输入按 word 分割。如果您不传递任何选项,默认行为是按 grapheme(用户感知的**字符**)分割。如果您在处理不同编码或字符由组合字符组成的语言(如印地语中的 किंतु)时进行字符计数,这将很有用。

js
const str = "किंतु";
console.log(str.length);
// 5 <- oops

const hindiSegmenter = new Intl.Segmenter("hi");
const hindiSegments = hindiSegmenter.segment(str);
const hiGraphemes = Array.from(hindiSegments).map((item) => item.segment);

console.log(hiGraphemes);
// ["किं","तु"]
console.log(hiGraphemes.length);
// 2 <- looks better

您可能需要的最后一个选项是按句子分割文本,如果您不想跟踪特定于语言的句号,这也非常方便。某些语言可能使用句号 .,但这并不总是符合的。让我们看下面的例子:

js
const hindiText = "वाक्य एक। वाक्य दो।"; // <- what do I split on here?

const hiSegmenter = new Intl.Segmenter("hi", { granularity: "sentence" });
const hiSegments = hiSegmenter.segment(hindiText);
const hiSentences = Array.from(hiSegments).map((item) => item.segment);

console.log(hiSentences);
// ["वाक्य एक। ","वाक्य दो।"]
console.log(hiSentences.length);
// 2

在另一个印地语示例中,我们有一个看起来像竖线的字符 () 分隔句子。现在您不必跟踪西式句号或其他特定于地区的等效项来将文本分割成句子。

如果您想检查地区支持情况,可以使用 supportedLocalesOf。它会返回一个包含所提供地区(这些地区在分段中受支持,无需回退到默认地区)的数组。以下代码检查分段器是否可以使用印地语、日语和德语进行分段:

js
console.log(Intl.Segmenter.supportedLocalesOf(["hi", "ja-JP", "de"]));
// Array ["hi", "ja-JP", "de"] <- all are supported

日文地区词语计数示例

如果您的浏览器支持 Intl.Segmenter,您可以尝试以下示例。下面有一些来自维基百科的日文文本,以及一个 <pre> 元素来显示我们脚本的输出。

html
<p id="text-content">
  ウィキペディア日本語版用のウィキは2001年5月頃に開設されましたが、当初はソフトウェアが日本語の文字に対応していなかったため、ローマ字で書かれていました。日本語版としての実質的な執筆・編集が開始されたのは、日本語の文字が使用出来る様になった2002年9月以降のことです。現在では1,428,684項目の記事が作成されており、各言語版の中でも規模の大きい物の一つになっています。ウィキペディア日本語版の歩みについてはWikipedia:発表をご参照ください。
</p>
<pre id="word-count">Word count: 0</pre>

如果您已经仔细阅读了前面的所有代码片段,您应该不会在此处看到任何意外之处。唯一不同的是,我们使用 window.getSelection 来获取选定的文本,然后再将其传递给我们包装在函数中的分段器。之后,我们在段落上监听 mouseup 事件,并在事件触发时将 countSelection 函数的输出添加到 <pre> 元素中。

js
function countSelection() {
  const selection = window.getSelection();
  const selectedText = selection.toString();

  const jaSegmenter = new Intl.Segmenter("ja-JP", { granularity: "word" });
  const segments = jaSegmenter.segment(selectedText);

  const words = Array.from(segments)
    .filter((item) => item.isWordLike)
    .map((item) => item.segment);

  document.getElementById("word-count").textContent =
    `Word count: ${words.length}\n - "${words}"`;
}

document
  .getElementById("text-content")
  .addEventListener("mouseup", countSelection);

要进行尝试,请用鼠标选中下面的日文文本。在 mouseup 时,我们将记录单词计数以及分段器的输出(以单词级别粒度)。

延伸阅读

如果您想了解更多关于 Intl.Segmenter 的信息,可以阅读以下资源:

总结

现在,开发人员在最新浏览器中通过 JavaScript 对地区敏感的文本分段有了更好的用户体验。这项功能对于处理非拉丁语系语言尤其有用,因为通常的字符串处理方法在这些语言中将不可靠。如果您的应用程序需要处理多种地区,并且您经常进行文本处理,Intl.Segmenter 可以根据地区按单词、字符或句子分段文本。这简化了诸如单词或字符计数、句子分割、字符串比较以及更高级的文本处理等任务。

请随时 与我们联系,让我知道您的想法或是否有遗漏之处。希望您喜欢这篇博文,并祝您在应用程序和网页中愉快地添加更多国际化功能。