Comment diviser un texte en mots en JavaScript ?

Utilisez Intl.Segmenter pour extraire les mots d'un texte dans n'importe quelle langue, y compris les langues sans espaces entre les mots.

Introduction

Lorsque vous devez extraire des mots d'un texte, l'approche courante consiste à diviser sur les espaces en utilisant split(" "). Cela fonctionne pour l'anglais, mais échoue complètement pour les langues qui n'utilisent pas d'espaces entre les mots. Le chinois, le japonais, le thaï et d'autres langues écrivent le texte de manière continue sans séparateurs de mots, pourtant les utilisateurs perçoivent des mots distincts dans ce texte.

L'API Intl.Segmenter résout ce problème. Elle identifie les limites de mots selon les normes Unicode et les règles linguistiques de chaque langue. Vous pouvez extraire des mots d'un texte indépendamment du fait que la langue utilise des espaces, et le segmenteur gère la complexité de déterminer où les mots commencent et se terminent.

Cet article explique pourquoi la division basique de chaînes échoue pour le texte international, comment les limites de mots fonctionnent à travers différents systèmes d'écriture, et comment utiliser Intl.Segmenter pour diviser correctement un texte en mots pour toutes les langues.

Pourquoi la division sur les espaces échoue

La méthode split() divise une chaîne à chaque occurrence d'un séparateur. Pour le texte anglais, diviser sur les espaces extrait les mots.

const text = "Hello world";
const words = text.split(" ");
console.log(words);
// ["Hello", "world"]

Cette approche suppose que les mots sont séparés par des espaces. De nombreuses langues ne suivent pas ce modèle.

Le texte chinois n'inclut pas d'espaces entre les mots.

const text = "你好世界";
const words = text.split(" ");
console.log(words);
// ["你好世界"]

L'utilisateur voit deux mots distincts, mais split() renvoie la chaîne entière comme un seul élément car il n'y a pas d'espaces sur lesquels diviser.

Le texte japonais mélange plusieurs scripts et n'utilise pas d'espaces entre les mots.

const text = "今日は良い天気です";
const words = text.split(" ");
console.log(words);
// ["今日は良い天気です"]

Cette phrase contient plusieurs mots, mais la division sur les espaces produit un seul élément.

Le texte thaï écrit également les mots de manière continue sans espaces.

const text = "สวัสดีครับ";
const words = text.split(" ");
console.log(words);
// ["สวัสดีครับ"]

Le texte contient deux mots, mais split() renvoie un seul élément.

Pour ces langues, vous avez besoin d'une approche différente pour identifier les limites de mots.

Pourquoi les expressions régulières échouent pour les limites de mots

Les limites de mots des expressions régulières utilisent le motif \b pour correspondre aux positions entre les caractères de mots et de non-mots. Cela fonctionne pour l'anglais.

const text = "Hello world!";
const words = text.match(/\b\w+\b/g);
console.log(words);
// ["Hello", "world"]

Ce motif échoue pour les langues sans espaces car le moteur d'expressions régulières ne reconnaît pas les limites de mots dans les écritures comme le chinois, le japonais ou le thaï.

const text = "你好世界";
const words = text.match(/\b\w+\b/g);
console.log(words);
// ["你好世界"]

L'expression régulière traite la chaîne entière comme un seul mot car elle ne comprend pas les limites de mots en chinois.

Même pour l'anglais, les motifs d'expressions régulières peuvent produire des résultats incorrects avec la ponctuation, les contractions ou les caractères spéciaux. Les expressions régulières ne sont pas conçues pour gérer la segmentation linguistique des mots dans tous les systèmes d'écriture.

Ce que sont les limites de mots à travers les langues

Une limite de mot est une position dans le texte où un mot se termine et un autre commence. Différents systèmes d'écriture utilisent différentes conventions pour les limites de mots.

Les langues séparées par des espaces comme l'anglais, l'espagnol, le français et l'allemand utilisent des espaces pour marquer les limites de mots. Le mot « hello » est séparé de « world » par un espace.

Les langues en scriptio continua comme le chinois, le japonais et le thaï n'utilisent pas d'espaces entre les mots. Les limites de mots existent en fonction de règles sémantiques et morphologiques, mais ces limites ne sont pas marquées visuellement dans le texte. Un lecteur chinois reconnaît où un mot se termine et un autre commence grâce à sa familiarité avec la langue, et non par des séparateurs visuels.

Certaines langues utilisent des conventions mixtes. Le japonais combine des caractères kanji, hiragana et katakana, et les limites de mots se produisent lors des transitions entre types de caractères ou en fonction de la structure grammaticale.

Le standard Unicode définit les règles de limites de mots dans UAX 29. Ces règles spécifient comment identifier les limites de mots pour tous les systèmes d'écriture. Les règles prennent en compte les propriétés des caractères, les types de scripts et les modèles linguistiques pour déterminer où les mots commencent et se terminent.

Utiliser Intl.Segmenter pour diviser le texte en mots

Le constructeur Intl.Segmenter crée un objet segmenteur qui divise le texte selon les règles Unicode. Vous spécifiez une locale et une granularité.

const segmenter = new Intl.Segmenter("en", { granularity: "word" });
const text = "Hello world!";
const segments = segmenter.segment(text);

Le premier argument est l'identifiant de locale. Le second argument est un objet d'options où granularity: "word" indique au segmenteur de diviser aux limites de mots.

La méthode segment() retourne un objet itérable contenant des segments. Vous pouvez itérer sur les segments en utilisant for...of.

const segmenter = new Intl.Segmenter("en", { granularity: "word" });
const text = "Hello world!";

for (const segment of segmenter.segment(text)) {
  console.log(segment);
}
// { segment: "Hello", index: 0, input: "Hello world!", isWordLike: true }
// { segment: " ", index: 5, input: "Hello world!", isWordLike: false }
// { segment: "world", index: 6, input: "Hello world!", isWordLike: true }
// { segment: "!", index: 11, input: "Hello world!", isWordLike: false }

Chaque objet segment contient des propriétés :

  • segment : le texte de ce segment
  • index : la position dans la chaîne d'origine où ce segment commence
  • input : la chaîne d'origine en cours de segmentation
  • isWordLike : indique si ce segment est un mot ou du contenu non-mot

Comprendre la propriété isWordLike

Lorsque vous segmentez du texte par mots, le segmenteur retourne à la fois des segments de mots et des segments non-mots. Les mots incluent les lettres, les chiffres et les caractères idéographiques. Les segments non-mots incluent les espaces, la ponctuation et autres séparateurs.

La propriété isWordLike indique si un segment est un mot. Cette propriété est true pour les segments qui contiennent des caractères de mots, et false pour les segments qui contiennent uniquement des espaces, de la ponctuation ou d'autres caractères non-mots.

const segmenter = new Intl.Segmenter("en", { granularity: "word" });
const text = "Hello, world!";

for (const { segment, isWordLike } of segmenter.segment(text)) {
  console.log(segment, isWordLike);
}
// "Hello" true
// "," false
// " " false
// "world" true
// "!" false

Utilisez la propriété isWordLike pour filtrer les segments de mots de la ponctuation et des espaces. Cela vous donne uniquement les mots sans séparateurs.

const segmenter = new Intl.Segmenter("en", { granularity: "word" });
const text = "Hello, world!";
const segments = segmenter.segment(text);
const words = Array.from(segments)
  .filter(s => s.isWordLike)
  .map(s => s.segment);

console.log(words);
// ["Hello", "world"]

Ce modèle fonctionne pour toutes les langues, y compris celles sans espaces.

Extraire les mots d'un texte sans espaces

Le segmenteur identifie correctement les limites de mots dans les langues qui n'utilisent pas d'espaces. Pour le texte chinois, le segmenteur divise aux limites de mots en fonction des règles Unicode et des modèles linguistiques.

const segmenter = new Intl.Segmenter("zh", { granularity: "word" });
const text = "你好世界";

for (const { segment, isWordLike } of segmenter.segment(text)) {
  console.log(segment, isWordLike);
}
// "你好" true
// "世界" true

Le segmenteur identifie deux mots dans ce texte. Il n'y a pas d'espaces, mais le segmenteur comprend les limites de mots chinois et divise le texte de manière appropriée.

Pour le texte japonais, le segmenteur gère la complexité des scripts mixtes et identifie les limites de mots.

const segmenter = new Intl.Segmenter("ja", { granularity: "word" });
const text = "今日は良い天気です";

for (const { segment, isWordLike } of segmenter.segment(text)) {
  console.log(segment, isWordLike);
}
// "今日" true
// "は" true
// "良い" true
// "天気" true
// "です" true

Le segmenteur divise cette phrase en cinq segments de mots. Il reconnaît que les particules comme "は" sont des mots séparés et que les mots composés comme "天気" forment des unités uniques.

Pour le texte thaï, le segmenteur identifie les limites de mots sans espaces.

const segmenter = new Intl.Segmenter("th", { granularity: "word" });
const text = "สวัสดีครับ";

for (const { segment, isWordLike } of segmenter.segment(text)) {
  console.log(segment, isWordLike);
}
// "สวัสดี" true
// "ครับ" true

Le segmenteur identifie correctement deux mots dans cette salutation.

Créer une fonction d'extraction de mots

Créez une fonction qui extrait les mots d'un texte dans n'importe quelle langue.

function getWords(text, locale) {
  const segmenter = new Intl.Segmenter(locale, { granularity: "word" });
  const segments = segmenter.segment(text);
  return Array.from(segments)
    .filter(s => s.isWordLike)
    .map(s => s.segment);
}

Cette fonction fonctionne pour les langues avec et sans espaces.

getWords("Hello, world!", "en");
// ["Hello", "world"]

getWords("你好世界", "zh");
// ["你好", "世界"]

getWords("今日は良い天気です", "ja");
// ["今日", "は", "良い", "天気", "です"]

getWords("Bonjour le monde!", "fr");
// ["Bonjour", "le", "monde"]

getWords("สวัสดีครับ", "th");
// ["สวัสดี", "ครับ"]

La fonction renvoie un tableau de mots quelle que soit la langue ou le système d'écriture.

Compter les mots avec précision dans toutes les langues

Créez un compteur de mots qui fonctionne pour toutes les langues en comptant les segments de type mot.

function countWords(text, locale) {
  const segmenter = new Intl.Segmenter(locale, { granularity: "word" });
  const segments = segmenter.segment(text);
  return Array.from(segments).filter(s => s.isWordLike).length;
}

Cette fonction produit des comptes de mots précis pour le texte dans n'importe quelle langue.

countWords("Hello world", "en");
// 2

countWords("你好世界", "zh");
// 2

countWords("今日は良い天気です", "ja");
// 5

countWords("Bonjour le monde", "fr");
// 3

countWords("สวัสดีครับ", "th");
// 2

Les décomptes correspondent à la perception des limites de mots par l'utilisateur dans chaque langue.

Trouver quel mot contient une position

La méthode containing() trouve le segment qui inclut un index spécifique dans la chaîne. Cela est utile pour déterminer dans quel mot se trouve le curseur ou quel mot a été cliqué.

const segmenter = new Intl.Segmenter("en", { granularity: "word" });
const text = "Hello world";
const segments = segmenter.segment(text);

const segment = segments.containing(7);
console.log(segment);
// { segment: "world", index: 6, input: "Hello world", isWordLike: true }

L'index 7 se situe dans le mot "world", qui commence à l'index 6. La méthode renvoie l'objet segment pour ce mot.

Si l'index se situe dans un espace blanc ou une ponctuation, la méthode renvoie ce segment avec isWordLike: false.

const segment = segments.containing(5);
console.log(segment);
// { segment: " ", index: 5, input: "Hello world", isWordLike: false }

Utilisez ceci pour les fonctionnalités d'éditeur de texte comme la sélection de mot par double-clic, les menus contextuels basés sur la position du curseur, ou la mise en évidence du mot actuel.

Gestion de la ponctuation et des contractions

Le segmenteur traite la ponctuation comme des segments séparés. Les contractions en anglais sont généralement divisées en plusieurs segments.

const segmenter = new Intl.Segmenter("en", { granularity: "word" });
const text = "I can't do it.";

for (const { segment, isWordLike } of segmenter.segment(text)) {
  console.log(segment, isWordLike);
}
// "I" true
// " " false
// "can" true
// "'" false
// "t" true
// " " false
// "do" true
// " " false
// "it" true
// "." false

La contraction "can't" est divisée en "can", "'" et "t". Si vous devez conserver les contractions comme des mots uniques, vous avez besoin d'une logique supplémentaire pour fusionner les segments basés sur les apostrophes.

Pour la plupart des cas d'usage, compter les segments de type mot vous donne des décomptes de mots significatifs même lorsque les contractions sont divisées.

Comment la locale affecte la segmentation des mots

La locale que vous passez au segmenteur affecte la façon dont les limites de mots sont déterminées. Différentes locales peuvent avoir des règles différentes pour le même texte.

Pour les langues avec des règles de limites de mots bien définies, la locale garantit que les règles correctes sont appliquées.

const segmenterEn = new Intl.Segmenter("en", { granularity: "word" });
const segmenterZh = new Intl.Segmenter("zh", { granularity: "word" });

const text = "你好世界";

const wordsEn = Array.from(segmenterEn.segment(text))
  .filter(s => s.isWordLike)
  .map(s => s.segment);

const wordsZh = Array.from(segmenterZh.segment(text))
  .filter(s => s.isWordLike)
  .map(s => s.segment);

console.log(wordsEn);
// ["你好世界"]

console.log(wordsZh);
// ["你好", "世界"]

La locale anglaise ne reconnaît pas les limites de mots chinois et traite la chaîne entière comme un seul mot. La locale chinoise applique les règles de limites de mots chinois et identifie correctement deux mots.

Utilisez toujours la locale appropriée pour la langue du texte à segmenter.

Créer des segmenteurs réutilisables pour les performances

La création d'un segmenteur n'est pas coûteuse, mais vous pouvez réutiliser les segmenteurs sur plusieurs chaînes pour de meilleures performances.

const enSegmenter = new Intl.Segmenter("en", { granularity: "word" });
const zhSegmenter = new Intl.Segmenter("zh", { granularity: "word" });
const jaSegmenter = new Intl.Segmenter("ja", { granularity: "word" });

function getWords(text, locale) {
  const segmenter = locale === "zh" ? zhSegmenter
    : locale === "ja" ? jaSegmenter
    : enSegmenter;

  return Array.from(segmenter.segment(text))
    .filter(s => s.isWordLike)
    .map(s => s.segment);
}

Cette approche crée les segmenteurs une seule fois et les réutilise pour tous les appels à getWords(). Le segmenteur met en cache les données de locale, donc la réutilisation des instances évite l'initialisation répétée.

Exemple pratique : créer un analyseur de fréquence de mots

Combinez la segmentation de mots avec le comptage pour analyser la fréquence des mots dans un texte.

function getWordFrequency(text, locale) {
  const segmenter = new Intl.Segmenter(locale, { granularity: "word" });
  const segments = segmenter.segment(text);
  const words = Array.from(segments)
    .filter(s => s.isWordLike)
    .map(s => s.segment.toLowerCase());

  const frequency = {};
  for (const word of words) {
    frequency[word] = (frequency[word] || 0) + 1;
  }

  return frequency;
}

const text = "Hello world! Hello everyone in this world.";
const frequency = getWordFrequency(text, "en");
console.log(frequency);
// { hello: 2, world: 2, everyone: 1, in: 1, this: 1 }

Cette fonction divise le texte en mots, normalise en minuscules et compte les occurrences. Elle fonctionne pour n'importe quelle langue.

const textZh = "你好世界!你好大家!";
const frequencyZh = getWordFrequency(textZh, "zh");
console.log(frequencyZh);
// { "你好": 2, "世界": 1, "大家": 1 }

La même logique gère le texte chinois sans modification.

Vérifier la compatibilité du navigateur

L'API Intl.Segmenter a atteint le statut Baseline en avril 2024. Elle fonctionne dans les versions actuelles de Chrome, Firefox, Safari et Edge. Les navigateurs plus anciens ne la prennent pas en charge.

Vérifiez la compatibilité avant d'utiliser l'API.

if (typeof Intl.Segmenter !== "undefined") {
  const segmenter = new Intl.Segmenter("en", { granularity: "word" });
  // Use segmenter
} else {
  // Fallback for older browsers
}

Pour les applications de production ciblant les navigateurs plus anciens, fournissez une implémentation de secours. Un secours simple utilise split() pour le texte anglais et retourne la chaîne entière pour les autres langues.

function getWords(text, locale) {
  if (typeof Intl.Segmenter !== "undefined") {
    const segmenter = new Intl.Segmenter(locale, { granularity: "word" });
    return Array.from(segmenter.segment(text))
      .filter(s => s.isWordLike)
      .map(s => s.segment);
  }

  // Fallback: only works for space-separated languages
  return text.split(/\s+/).filter(word => word.length > 0);
}

Cela garantit que votre code s'exécute dans les navigateurs plus anciens, bien qu'avec des fonctionnalités réduites pour les langues sans séparation par espaces.

Erreurs courantes à éviter

Ne divisez pas sur les espaces ou les motifs regex pour le texte multilingue. Ces approches ne fonctionnent que pour un sous-ensemble de langues et échouent pour le chinois, le japonais, le thaï et d'autres langues sans espaces.

N'oubliez pas de filtrer par isWordLike lors de l'extraction de mots. Sans ce filtre, vous obtenez des espaces, de la ponctuation et d'autres segments non-mots dans vos résultats.

N'utilisez pas la mauvaise locale lors de la segmentation du texte. La locale détermine quelles règles de limites de mots s'appliquent. Utiliser une locale anglaise pour du texte chinois produit des résultats incorrects.

Ne supposez pas que toutes les langues définissent les mots de la même manière. Les limites de mots varient selon le système d'écriture et les conventions linguistiques. Utilisez une segmentation tenant compte de la locale pour gérer ces différences.

Ne comptez pas les mots en utilisant split(" ").length pour du texte international. Cela ne fonctionne que pour les langues séparées par des espaces et produit des comptages erronés pour les autres.

Quand utiliser la segmentation de mots

Utilisez la segmentation de mots lorsque vous devez :

  • Compter les mots dans du contenu généré par les utilisateurs dans plusieurs langues
  • Implémenter des fonctionnalités de recherche et de surlignage qui fonctionnent avec n'importe quel système d'écriture
  • Créer des outils d'analyse de texte qui traitent du texte international
  • Créer des fonctionnalités de navigation ou d'édition basées sur les mots dans les éditeurs de texte
  • Extraire des mots-clés ou des termes de documents multilingues
  • Valider les limites de nombre de mots dans des formulaires acceptant n'importe quelle langue

N'utilisez pas la segmentation de mots lorsque vous avez seulement besoin de compter les caractères. Utilisez la segmentation de graphèmes pour les opérations au niveau des caractères.

N'utilisez pas la segmentation de mots pour diviser les phrases. Utilisez la granularité de phrase à cette fin.

Comment la segmentation de mots s'intègre dans l'internationalisation

L'API Intl.Segmenter fait partie de l'API d'internationalisation ECMAScript. D'autres API de cette famille gèrent différents aspects de l'internationalisation :

  • Intl.DateTimeFormat : formate les dates et heures selon la locale
  • Intl.NumberFormat : formate les nombres, devises et unités selon la locale
  • Intl.Collator : trie et compare les chaînes selon la locale
  • Intl.PluralRules : détermine les formes plurielles des nombres dans différentes langues

Ensemble, ces API fournissent les outils nécessaires pour créer des applications qui fonctionnent correctement pour les utilisateurs du monde entier. Utilisez Intl.Segmenter avec une granularité au niveau des mots lorsque vous devez identifier les limites de mots, et utilisez les autres API Intl pour le formatage et la comparaison.