Wie teile ich Text in Sätze auf?

Verwenden Sie Intl.Segmenter, um Text in Sätze zu unterteilen, mit sprachspezifischer Grenzerkennung, die Zeichensetzung, Abkürzungen und sprachspezifische Regeln berücksichtigt.

Einführung

Wenn Sie Text für Übersetzungen, Analysen oder zur Anzeige verarbeiten, müssen Sie diesen oft in einzelne Sätze aufteilen. Ein naiver Ansatz mit regulären Ausdrücken scheitert, da Satzgrenzen komplexer sind als Punkte gefolgt von Leerzeichen. Sätze können mit Fragezeichen, Ausrufezeichen oder Auslassungspunkten enden. Punkte erscheinen in Abkürzungen wie "Dr." oder "Inc." ohne Sätze zu beenden. Verschiedene Sprachen verwenden unterschiedliche Satzzeichen als Satzbegrenzer.

Die Intl.Segmenter-API löst dieses Problem, indem sie eine sprachspezifische Erkennung von Satzgrenzen bietet. Sie versteht die Regeln zur Identifizierung von Satzgrenzen in verschiedenen Sprachen und behandelt Sonderfälle wie Abkürzungen, Zahlen und komplexe Zeichensetzung automatisch.

Das Problem beim Aufteilen nach Punkten

Man könnte versuchen, Text in Sätze zu unterteilen, indem man nach Punkten gefolgt von Leerzeichen trennt.

const text = "Hello world. How are you? I am fine.";
const sentences = text.split(". ");
console.log(sentences);
// ["Hello world", "How are you? I am fine."]

Dieser Ansatz hat mehrere Probleme. Erstens werden Fragezeichen oder Ausrufezeichen nicht berücksichtigt. Zweitens funktioniert er nicht bei Abkürzungen, die Punkte enthalten. Drittens entfernt er den Punkt aus jedem Satz außer dem letzten. Viertens funktioniert er nicht, wenn mehrere Leerzeichen nach Punkten stehen.

const text = "Dr. Smith works at Acme Inc. He starts at 9 a.m.";
const sentences = text.split(". ");
console.log(sentences);
// ["Dr", "Smith works at Acme Inc", "He starts at 9 a.m."]

Der Text wird fälschlicherweise bei "Dr." und "Inc." geteilt, da diese Abkürzungen Punkte enthalten. Sie benötigen einen intelligenteren Ansatz, der die Regeln für Satzgrenzen versteht.

Verwendung eines komplexeren regulären Ausdrucks

Sie können den regulären Ausdruck verbessern, um mehr Fälle zu behandeln.

const text = "Hello world. How are you? I am fine!";
const sentences = text.split(/[.?!]\s+/);
console.log(sentences);
// ["Hello world", "How are you", "I am fine", ""]

Dies teilt den Text bei Punkten, Fragezeichen und Ausrufezeichen, gefolgt von Leerzeichen. Es behandelt mehr Fälle, versagt aber immer noch bei Abkürzungen und erzeugt leere Strings. Außerdem entfernt es die Interpunktion aus jedem Satz.

const text = "Dr. Smith works at Acme Inc. He starts at 9 a.m.";
const sentences = text.split(/[.?!]\s+/);
console.log(sentences);
// ["Dr", "Smith works at Acme Inc", "He starts at 9 a", "m", ""]

Der Regex-Ansatz kann nicht zuverlässig zwischen Punkten, die Sätze beenden, und Punkten, die in Abkürzungen vorkommen, unterscheiden. Es wird unpraktisch, einen umfassenden regulären Ausdruck zu erstellen, der alle Sonderfälle behandelt. Sie benötigen eine Lösung, die linguistische Regeln versteht.

Verwendung von Intl.Segmenter für die Satztrennung

Der Konstruktor Intl.Segmenter erstellt einen Segmentierer, der Text basierend auf lokalspezifischen Regeln aufteilt. Sie geben eine Locale an und setzen die Option granularity auf "sentence".

const segmenter = new Intl.Segmenter("en", { granularity: "sentence" });
const text = "Hello world. How are you? I am fine!";
const segments = segmenter.segment(text);

for (const segment of segments) {
  console.log(segment.segment);
}
// "Hello world. "
// "How are you? "
// "I am fine!"

Die Methode segment() gibt ein iterierbares Objekt zurück, das Segmentobjekte liefert. Jedes Segmentobjekt hat eine Eigenschaft segment, die den Text dieses Segments enthält. Der Segmentierer behält die Interpunktion und Leerzeichen am Ende jedes Satzes bei.

Sie können die Segmente mit Array.from() in ein Array umwandeln.

const segmenter = new Intl.Segmenter("en", { granularity: "sentence" });
const text = "Hello world. How are you? I am fine!";
const segments = segmenter.segment(text);
const sentences = Array.from(segments, s => s.segment);
console.log(sentences);
// ["Hello world. ", "How are you? ", "I am fine!"]

Dies erstellt ein Array, bei dem jedes Element ein Satz mit seiner ursprünglichen Interpunktion und Leerzeichen ist.

Wie Intl.Segmenter mit Abkürzungen umgeht

Der Segmenter versteht gängige Abkürzungsmuster und trennt nicht an Punkten, die innerhalb von Abkürzungen erscheinen.

const segmenter = new Intl.Segmenter("en", { granularity: "sentence" });
const text = "Dr. Smith works at Acme Inc. He starts at 9 a.m.";
const segments = segmenter.segment(text);
const sentences = Array.from(segments, s => s.segment);
console.log(sentences);
// ["Dr. Smith works at Acme Inc. ", "He starts at 9 a.m."]

Der Text wird korrekt in zwei Sätze aufgeteilt. Die Punkte in "Dr.", "Inc." und "a.m." lösen keine Satztrennungen aus, da der Segmenter diese als Abkürzungen erkennt. Diese automatische Behandlung von Sonderfällen ist der Grund, warum Intl.Segmenter Regex-Ansätzen überlegen ist.

Leerzeichen aus Sätzen entfernen

Der Segmenter schließt nachfolgende Leerzeichen in jeden Satz ein. Bei Bedarf können Sie diese Leerzeichen entfernen.

const segmenter = new Intl.Segmenter("en", { granularity: "sentence" });
const text = "Hello world. How are you? I am fine!";
const segments = segmenter.segment(text);
const sentences = Array.from(segments, s => s.segment.trim());
console.log(sentences);
// ["Hello world.", "How are you?", "I am fine!"]

Die trim()-Methode entfernt führende und nachfolgende Leerzeichen aus jedem Satz. Dies ist nützlich, wenn Sie saubere Satzgrenzen ohne zusätzliche Leerzeichen benötigen.

Segment-Metadaten abrufen

Jedes Segment-Objekt enthält Metadaten über die Position des Segments im Originaltext.

const segmenter = new Intl.Segmenter("en", { granularity: "sentence" });
const text = "Hello world. How are you?";
const segments = segmenter.segment(text);

for (const segment of segments) {
  console.log({
    text: segment.segment,
    index: segment.index,
    input: segment.input
  });
}
// { text: "Hello world. ", index: 0, input: "Hello world. How are you?" }
// { text: "How are you?", index: 13, input: "Hello world. How are you?" }

Die index-Eigenschaft gibt an, wo das Segment im Originaltext beginnt. Die input-Eigenschaft enthält den vollständigen Originaltext. Diese Metadaten sind nützlich, wenn Sie Satzpositionen verfolgen oder den Originaltext rekonstruieren müssen.

Sätze in verschiedenen Sprachen aufteilen

Verschiedene Sprachen haben unterschiedliche Regeln für Satzgrenzen. Der Segmentierer passt sein Verhalten basierend auf der angegebenen Locale an.

Im Japanischen können Sätze mit einem vollbreiten Punkt enden, der als Kuten bezeichnet wird.

const segmenter = new Intl.Segmenter("ja", { granularity: "sentence" });
const text = "私は猫です。名前はまだない。";
const segments = segmenter.segment(text);
const sentences = Array.from(segments, s => s.segment);
console.log(sentences);
// ["私は猫です。", "名前はまだない。"]

Der Text wird korrekt an den japanischen Satzbegrenzern geteilt. Ein für Englisch konfigurierter Segmentierer würde diese Grenzen nicht korrekt erkennen.

In Hindi können Sätze mit einem vertikalen Balken enden, der als Purna Viram bezeichnet wird.

const segmenter = new Intl.Segmenter("hi", { granularity: "sentence" });
const text = "यह एक वाक्य है। यह दूसरा वाक्य है।";
const segments = segmenter.segment(text);
const sentences = Array.from(segments, s => s.segment);
console.log(sentences);
// ["यह एक वाक्य है। ", "यह दूसरा वाक्य है।"]

Der Segmentierer erkennt den Devanagari-Punkt als Satzgrenze. Dieses locale-bewusste Verhalten ist entscheidend für die internationalisierte Textverarbeitung.

Verwendung der korrekten Locale für mehrsprachigen Text

Wenn Sie Text verarbeiten, der mehrere Sprachen enthält, wählen Sie die Locale, die der Hauptsprache des Textes entspricht. Der Segmentierer verwendet die angegebene Locale, um zu bestimmen, welche Grenzregeln anzuwenden sind.

const englishText = "Hello world. How are you?";
const japaneseText = "私は猫です。名前はまだない。";

const englishSegmenter = new Intl.Segmenter("en", { granularity: "sentence" });
const japaneseSegmenter = new Intl.Segmenter("ja", { granularity: "sentence" });

const englishSentences = Array.from(
  englishSegmenter.segment(englishText),
  s => s.segment
);

const japaneseSentences = Array.from(
  japaneseSegmenter.segment(japaneseText),
  s => s.segment
);

console.log(englishSentences);
// ["Hello world. ", "How are you?"]

console.log(japaneseSentences);
// ["私は猫です。", "名前はまだない。"]

Die Erstellung separater Segmentierer für jede Sprache gewährleistet eine korrekte Grenzerkennung. Wenn Sie Text verarbeiten, bei dem die Sprache unbekannt ist, können Sie eine generische Locale wie "en" als Fallback verwenden, obwohl dies die Genauigkeit für nicht-englischen Text reduziert.

Umgang mit Text ohne Satzgrenzen

Wenn Text keine Satzendzeichen enthält, gibt der Segmenter den gesamten Text als ein einzelnes Segment zurück.

const segmenter = new Intl.Segmenter("en", { granularity: "sentence" });
const text = "Hello world";
const segments = segmenter.segment(text);
const sentences = Array.from(segments, s => s.segment);
console.log(sentences);
// ["Hello world"]

Dieses Verhalten ist korrekt, da der Text keine Satzgrenzen enthält. Der Segmenter teilt Text, der einen einzelnen Satz bildet, nicht künstlich auf.

Umgang mit leeren Strings

Der Segmenter behandelt leere Strings, indem er einen leeren Iterator zurückgibt.

const segmenter = new Intl.Segmenter("en", { granularity: "sentence" });
const text = "";
const segments = segmenter.segment(text);
const sentences = Array.from(segments, s => s.segment);
console.log(sentences);
// []

Dies erzeugt ein leeres Array, was das erwartete Ergebnis für eine leere Eingabe ist.

Wiederverwendung von Segmentern für bessere Performance

Das Erstellen eines Segmenters verursacht einen gewissen Overhead. Wenn Sie mehrere Texte mit derselben Locale und denselben Optionen segmentieren müssen, erstellen Sie den Segmenter einmal und verwenden Sie ihn wieder.

const segmenter = new Intl.Segmenter("en", { granularity: "sentence" });

const texts = [
  "First text. With two sentences.",
  "Second text. With three sentences. And more.",
  "Third text."
];

texts.forEach(text => {
  const sentences = Array.from(segmenter.segment(text), s => s.segment);
  console.log(sentences);
});
// ["First text. ", "With two sentences."]
// ["Second text. ", "With three sentences. ", "And more."]
// ["Third text."]

Die Wiederverwendung des Segmenters ist effizienter als das Erstellen eines neuen für jeden Text.

Erstellen einer Funktion zum Zählen von Sätzen

Sie können den Segmenter verwenden, um Sätze im Text zu zählen.

function countSentences(text, locale = "en") {
  const segmenter = new Intl.Segmenter(locale, { granularity: "sentence" });
  const segments = segmenter.segment(text);
  return Array.from(segments).length;
}

console.log(countSentences("Hello world. How are you?"));
// 2

console.log(countSentences("Dr. Smith works at Acme Inc. He starts at 9 a.m."));
// 2

console.log(countSentences("Single sentence"));
// 1

console.log(countSentences("私は猫です。名前はまだない。", "ja"));
// 2

Diese Funktion erstellt einen Segmenter, teilt den Text auf und gibt die Anzahl der Segmente zurück. Sie behandelt Abkürzungen und sprachspezifische Grenzen korrekt.

Funktion zur Satzextraktion erstellen

Sie können eine Funktion erstellen, die einen bestimmten Satz anhand des Index aus einem Text extrahiert.

function getSentence(text, index, locale = "en") {
  const segmenter = new Intl.Segmenter(locale, { granularity: "sentence" });
  const segments = Array.from(segmenter.segment(text), s => s.segment);
  return segments[index] || null;
}

const text = "First sentence. Second sentence. Third sentence.";

console.log(getSentence(text, 0));
// "First sentence. "

console.log(getSentence(text, 1));
// "Second sentence. "

console.log(getSentence(text, 2));
// "Third sentence."

console.log(getSentence(text, 3));
// null

Diese Funktion gibt den Satz am angegebenen Index zurück oder null, wenn der Index außerhalb des gültigen Bereichs liegt.

Überprüfung der Browser- und Laufzeitunterstützung

Die Intl.Segmenter-API ist in modernen Browsern und Node.js verfügbar. Sie wurde im April 2024 Teil der Web-Plattform-Baseline und wird von allen wichtigen Browser-Engines unterstützt.

Sie können überprüfen, ob die API verfügbar ist, bevor Sie sie verwenden.

if (typeof Intl.Segmenter !== "undefined") {
  const segmenter = new Intl.Segmenter("en", { granularity: "sentence" });
  const text = "Hello world. How are you?";
  const sentences = Array.from(segmenter.segment(text), s => s.segment);
  console.log(sentences);
} else {
  console.log("Intl.Segmenter is not supported");
}

Für Umgebungen ohne Unterstützung müssen Sie eine Fallback-Lösung bereitstellen. Ein einfacher Fallback verwendet eine grundlegende Regex-Aufteilung, obwohl dabei die Genauigkeit der lokalisierungsabhängigen Segmentierung verloren geht.

function splitSentences(text, locale = "en") {
  if (typeof Intl.Segmenter !== "undefined") {
    const segmenter = new Intl.Segmenter(locale, { granularity: "sentence" });
    return Array.from(segmenter.segment(text), s => s.segment);
  }

  // Fallback für ältere Umgebungen
  return text.split(/[.!?]\s+/).filter(s => s.length > 0);
}

console.log(splitSentences("Hello world. How are you?"));
// ["Hello world. ", "How are you?"]

Diese Funktion verwendet Intl.Segmenter, wenn verfügbar, und greift in älteren Umgebungen auf Regex-Splitting zurück. Der Fallback verliert Funktionen wie die Behandlung von Abkürzungen und sprachspezifische Grenzerkennung, bietet aber grundlegende Funktionalität.