Comment diviser un texte en phrases ?

Utilisez Intl.Segmenter pour diviser le texte en phrases avec une détection des limites adaptée à la locale qui gère la ponctuation, les abréviations et les règles spécifiques à chaque langue.

Introduction

Lorsque vous traitez du texte pour la traduction, l'analyse ou l'affichage, vous devez souvent le diviser en phrases individuelles. Une approche naïve utilisant des expressions régulières échoue car les limites des phrases sont plus complexes que des points suivis d'espaces. Les phrases peuvent se terminer par des points d'interrogation, des points d'exclamation ou des points de suspension. Les points apparaissent dans des abréviations comme "Dr." ou "Inc." sans terminer les phrases. Différentes langues utilisent différents signes de ponctuation comme terminateurs de phrases.

L'API Intl.Segmenter résout ce problème en fournissant une détection des limites de phrases adaptée à la locale. Elle comprend les règles d'identification des limites de phrases dans différentes langues et gère automatiquement les cas particuliers comme les abréviations, les nombres et la ponctuation complexe.

Le problème de la division sur les points

Vous pouvez essayer de diviser un texte en phrases en le séparant aux points suivis d'espaces.

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."]

Cette approche présente plusieurs problèmes. Premièrement, elle ne gère pas les points d'interrogation ou d'exclamation. Deuxièmement, elle échoue sur les abréviations qui contiennent des points. Troisièmement, elle supprime le point de chaque phrase sauf la dernière. Quatrièmement, elle ne fonctionne pas lorsqu'il y a plusieurs espaces après les points.

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."]

Le texte se divise incorrectement à "Dr." et "Inc." car ces abréviations contiennent des points. Vous avez besoin d'une approche plus intelligente qui comprend les règles de délimitation des phrases.

Utilisation d'une expression régulière plus complexe

Vous pouvez améliorer la regex pour gérer plus de cas.

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", ""]

Ceci divise le texte selon les points, points d'interrogation et points d'exclamation suivis d'espaces. Cette méthode gère plus de cas mais échoue toujours avec les abréviations et crée des chaînes vides. Elle supprime également la ponctuation de chaque phrase.

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", ""]

L'approche par regex ne peut pas distinguer de manière fiable entre les points qui terminent les phrases et ceux qui apparaissent dans les abréviations. Construire une regex complète qui gère tous les cas particuliers devient impraticable. Vous avez besoin d'une solution qui comprend les règles linguistiques.

Utilisation d'Intl.Segmenter pour la division des phrases

Le constructeur Intl.Segmenter crée un segmenteur qui divise le texte selon des règles spécifiques à la locale. Vous spécifiez une locale et définissez l'option granularity à "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!"

La méthode segment() renvoie un itérable qui produit des objets de segment. Chaque objet de segment possède une propriété segment contenant le texte de ce segment. Le segmenteur préserve la ponctuation et les espaces à la fin de chaque phrase.

Vous pouvez convertir les segments en tableau en utilisant Array.from().

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!"]

Cela crée un tableau où chaque élément est une phrase avec sa ponctuation et ses espaces d'origine.

Comment Intl.Segmenter gère les abréviations

Le segmenteur comprend les modèles d'abréviation courants et ne divise pas sur les points qui apparaissent dans les abréviations.

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."]

Le texte se divise correctement en deux phrases. Les points dans "Dr.", "Inc." et "a.m." ne déclenchent pas de coupures de phrase car le segmenteur reconnaît ces éléments comme des abréviations. Cette gestion automatique des cas particuliers est ce qui rend Intl.Segmenter supérieur aux approches par expressions régulières.

Supprimer les espaces blancs des phrases

Le segmenteur inclut les espaces blancs de fin dans chaque phrase. Vous pouvez supprimer ces espaces si nécessaire.

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!"]

La méthode trim() supprime les espaces blancs au début et à la fin de chaque phrase. C'est utile lorsque vous avez besoin de limites de phrases propres sans espacement supplémentaire.

Obtenir les métadonnées des segments

Chaque objet segment inclut des métadonnées sur la position du segment dans le texte original.

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?" }

La propriété index indique où le segment commence dans le texte original. La propriété input contient le texte original complet. Ces métadonnées sont utiles lorsque vous devez suivre les positions des phrases ou reconstruire le texte original.

Segmentation des phrases dans différentes langues

Différentes langues ont différentes règles de délimitation des phrases. Le segmenteur adapte son comportement en fonction de la locale spécifiée.

En japonais, les phrases peuvent se terminer par un point à pleine largeur appelé kuten.

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);
// ["私は猫です。", "名前はまだない。"]

Le texte se divise correctement aux terminateurs de phrase japonais. Un segmenteur configuré pour l'anglais ne reconnaîtrait pas correctement ces limites.

En hindi, les phrases peuvent se terminer par une barre verticale appelée purna viram.

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);
// ["यह एक वाक्य है। ", "यह दूसरा वाक्य है।"]

Le segmenteur reconnaît le point final devanagari comme limite de phrase. Ce comportement adapté à la locale est essentiel pour le traitement de texte internationalisé.

Utilisation de la locale appropriée pour le texte multilingue

Lorsque vous traitez du texte contenant plusieurs langues, choisissez la locale qui correspond à la langue principale du texte. Le segmenteur utilise la locale spécifiée pour déterminer quelles règles de délimitation appliquer.

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);
// ["私は猫です。", "名前はまだない。"]

Créer des segmenteurs distincts pour chaque langue garantit une détection correcte des limites. Si vous traitez du texte dont la langue est inconnue, vous pouvez utiliser une locale générique comme "en" comme solution de repli, bien que cela réduise la précision pour les textes non anglais.

Gestion de texte sans délimitation de phrase

Lorsqu'un texte ne contient pas de terminateurs de phrase, le segmenteur renvoie le texte entier comme un segment unique.

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"]

Ce comportement est correct car le texte ne contient pas de délimitations de phrase. Le segmenteur ne divise pas artificiellement un texte qui forme une phrase unique.

Gestion des chaînes vides

Le segmenteur traite les chaînes vides en renvoyant un itérateur vide.

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);
// []

Cela produit un tableau vide, ce qui est le résultat attendu pour une entrée vide.

Réutilisation des segmenteurs pour de meilleures performances

La création d'un segmenteur implique une certaine surcharge. Lorsque vous devez segmenter plusieurs textes avec la même locale et les mêmes options, créez le segmenteur une seule fois et réutilisez-le.

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."]

Réutiliser le segmenteur est plus efficace que d'en créer un nouveau pour chaque texte.

Construction d'une fonction de comptage de phrases

Vous pouvez utiliser le segmenteur pour compter les phrases dans un texte.

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

Cette fonction crée un segmenteur, divise le texte et renvoie le nombre de segments. Elle gère correctement les abréviations et les délimitations spécifiques à chaque langue.

Construction d'une fonction d'extraction de phrases

Vous pouvez créer une fonction qui extrait une phrase spécifique d'un texte par index.

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

Cette fonction renvoie la phrase à l'index spécifié, ou null si l'index est hors limites.

Vérification de la compatibilité des navigateurs et des environnements d'exécution

L'API Intl.Segmenter est disponible dans les navigateurs modernes et Node.js. Elle est devenue partie intégrante de la base de la plateforme web en avril 2024 et est prise en charge par tous les principaux moteurs de navigateur.

Vous pouvez vérifier si l'API est disponible avant de l'utiliser.

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");
}

Pour les environnements sans support, vous devez fournir une solution de repli. Une solution simple utilise une division par expression régulière basique, bien que cela perde la précision de la segmentation adaptée à la locale.

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);
  }

  // Solution de repli pour les environnements plus anciens
  return text.split(/[.!?]\s+/).filter(s => s.length > 0);
}

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

Cette fonction utilise Intl.Segmenter lorsqu'il est disponible et se replie sur la division par regex dans les environnements plus anciens. La solution de repli perd des fonctionnalités comme la gestion des abréviations et la détection des limites spécifiques à la langue, mais fournit une fonctionnalité de base.