¿Cómo divido texto en palabras en JavaScript?

Usa Intl.Segmenter para extraer palabras de texto en cualquier idioma, incluidos idiomas sin espacios entre palabras.

Introducción

Cuando necesitas extraer palabras de un texto, el enfoque común es dividir por espacios usando split(" "). Esto funciona para inglés, pero falla completamente para idiomas que no usan espacios entre palabras. El chino, japonés, tailandés y otros idiomas escriben texto de forma continua sin separadores de palabras, pero los usuarios perciben palabras distintas en ese texto.

La API Intl.Segmenter resuelve este problema. Identifica límites de palabras según los estándares Unicode y las reglas lingüísticas de cada idioma. Puedes extraer palabras del texto independientemente de si el idioma usa espacios, y el segmentador maneja la complejidad de determinar dónde comienzan y terminan las palabras.

Este artículo explica por qué la división básica de cadenas falla para texto internacional, cómo funcionan los límites de palabras en diferentes sistemas de escritura y cómo usar Intl.Segmenter para dividir texto en palabras correctamente para todos los idiomas.

Por qué dividir por espacios falla

El método split() divide una cadena en cada aparición de un separador. Para texto en inglés, dividir por espacios extrae palabras.

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

Este enfoque asume que las palabras están separadas por espacios. Muchos idiomas no siguen este patrón.

El texto en chino no incluye espacios entre palabras.

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

El usuario ve dos palabras distintas, pero split() devuelve la cadena completa como un solo elemento porque no hay espacios para dividir.

El texto en japonés mezcla múltiples sistemas de escritura y no usa espacios entre palabras.

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

Esta oración contiene múltiples palabras, pero dividir por espacios produce un solo elemento.

El texto tailandés también escribe palabras de forma continua sin espacios.

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

El texto contiene dos palabras, pero split() devuelve un elemento.

Para estos idiomas, necesitas un enfoque diferente para identificar los límites de palabras.

Por qué las expresiones regulares fallan para los límites de palabras

Los límites de palabras en expresiones regulares usan el patrón \b para coincidir con posiciones entre caracteres de palabra y no palabra. Esto funciona para inglés.

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

Este patrón falla para idiomas sin espacios porque el motor de expresiones regulares no reconoce límites de palabras en sistemas de escritura como chino, japonés o tailandés.

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

La expresión regular trata toda la cadena como una palabra porque no entiende los límites de palabras en chino.

Incluso para inglés, los patrones de expresiones regulares pueden producir resultados incorrectos con puntuación, contracciones o caracteres especiales. Las expresiones regulares no están diseñadas para manejar la segmentación lingüística de palabras en todos los sistemas de escritura.

Qué son los límites de palabras en diferentes idiomas

Un límite de palabra es una posición en el texto donde una palabra termina y otra comienza. Diferentes sistemas de escritura usan diferentes convenciones para los límites de palabras.

Los idiomas separados por espacios como inglés, español, francés y alemán usan espacios para marcar límites de palabras. La palabra "hello" está separada de "world" por un espacio.

Los idiomas de scriptio continua como chino, japonés y tailandés no usan espacios entre palabras. Los límites de palabras existen según reglas semánticas y morfológicas, pero estos límites no están marcados visualmente en el texto. Un lector chino reconoce dónde termina una palabra y comienza otra a través de la familiaridad con el idioma, no mediante separadores visuales.

Algunos idiomas usan convenciones mixtas. El japonés combina caracteres kanji, hiragana y katakana, y los límites de palabras ocurren en transiciones entre tipos de caracteres o según la estructura gramatical.

El estándar Unicode define las reglas de límites de palabras en UAX 29. Estas reglas especifican cómo identificar los límites de palabras para todos los sistemas de escritura. Las reglas consideran las propiedades de los caracteres, los tipos de escritura y los patrones lingüísticos para determinar dónde comienzan y terminan las palabras.

Usar Intl.Segmenter para dividir texto en palabras

El constructor Intl.Segmenter crea un objeto segmentador que divide el texto según las reglas de Unicode. Debes especificar una configuración regional y una granularidad.

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

El primer argumento es el identificador de configuración regional. El segundo argumento es un objeto de opciones donde granularity: "word" indica al segmentador que divida en los límites de palabras.

El método segment() devuelve un objeto iterable que contiene segmentos. Puedes iterar sobre los segmentos usando 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 }

Cada objeto de segmento contiene propiedades:

  • segment: el texto de este segmento
  • index: la posición en la cadena original donde comienza este segmento
  • input: la cadena original que se está segmentando
  • isWordLike: si este segmento es una palabra o contenido que no es palabra

Comprender la propiedad isWordLike

Cuando segmentas texto por palabras, el segmentador devuelve tanto segmentos de palabras como segmentos que no son palabras. Las palabras incluyen letras, números y caracteres ideográficos. Los segmentos que no son palabras incluyen espacios, puntuación y otros separadores.

La propiedad isWordLike indica si un segmento es una palabra. Esta propiedad es true para segmentos que contienen caracteres de palabras, y false para segmentos que contienen solo espacios, puntuación u otros caracteres que no son palabras.

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

Usa la propiedad isWordLike para filtrar segmentos de palabras de la puntuación y los espacios en blanco. Esto te proporciona solo las palabras sin separadores.

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

Este patrón funciona para cualquier idioma, incluidos aquellos sin espacios.

Extracción de palabras de texto sin espacios

El segmentador identifica correctamente los límites de palabras en idiomas que no utilizan espacios. Para texto en chino, el segmentador divide en los límites de palabras basándose en reglas Unicode y patrones lingüísticos.

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

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

El segmentador identifica dos palabras en este texto. No hay espacios, pero el segmentador comprende los límites de palabras en chino y divide el texto apropiadamente.

Para texto en japonés, el segmentador maneja la complejidad de los sistemas de escritura mixtos e identifica los límites de palabras.

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

El segmentador divide esta oración en cinco segmentos de palabras. Reconoce que las partículas como "は" son palabras separadas y que las palabras compuestas como "天気" forman unidades individuales.

Para texto en tailandés, el segmentador identifica los límites de palabras sin espacios.

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

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

El segmentador identifica correctamente dos palabras en este saludo.

Construcción de una función de extracción de palabras

Crea una función que extraiga palabras de texto en cualquier idioma.

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

Esta función funciona para idiomas con y sin separación por espacios.

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

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

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

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

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

La función devuelve un array de palabras independientemente del idioma o sistema de escritura.

Conteo preciso de palabras en todos los idiomas

Construye un contador de palabras que funcione para todos los idiomas contando segmentos similares a palabras.

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

Esta función produce conteos de palabras precisos para texto en cualquier idioma.

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

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

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

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

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

Los conteos coinciden con la percepción del usuario de los límites de palabras en cada idioma.

Encontrar qué palabra contiene una posición

El método containing() encuentra el segmento que incluye un índice específico en la cadena. Esto es útil para determinar en qué palabra está el cursor o qué palabra se hizo clic.

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 }

El índice 7 cae dentro de la palabra "world", que comienza en el índice 6. El método devuelve el objeto de segmento para esa palabra.

Si el índice cae dentro de un espacio en blanco o puntuación, el método devuelve ese segmento con isWordLike: false.

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

Usa esto para funciones de editor de texto como la selección de palabras con doble clic, menús contextuales basados en la posición del cursor o el resaltado de la palabra actual.

Manejo de puntuación y contracciones

El segmentador trata la puntuación como segmentos separados. Las contracciones en inglés se dividen típicamente en múltiples segmentos.

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 contracción "can't" se divide en "can", "'" y "t". Si necesitas mantener las contracciones como palabras únicas, necesitas lógica adicional para fusionar segmentos basándote en apóstrofes.

Para la mayoría de los casos de uso, contar segmentos similares a palabras te proporciona recuentos de palabras significativos incluso cuando las contracciones están divididas.

Cómo afecta la configuración regional a la segmentación de palabras

La configuración regional que pasas al segmentador afecta cómo se determinan los límites de las palabras. Diferentes configuraciones regionales pueden tener reglas diferentes para el mismo texto.

Para idiomas con reglas de límites de palabras bien definidas, la configuración regional asegura que se apliquen las reglas correctas.

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 configuración regional en inglés no reconoce los límites de palabras en chino y trata toda la cadena como una palabra. La configuración regional en chino aplica las reglas de límites de palabras en chino e identifica correctamente dos palabras.

Usa siempre la configuración regional apropiada para el idioma del texto que se está segmentando.

Creación de segmentadores reutilizables para rendimiento

Crear un segmentador no es costoso, pero puedes reutilizar segmentadores en múltiples cadenas para un mejor rendimiento.

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

Este enfoque crea segmentadores una vez y los reutiliza para todas las llamadas a getWords(). El segmentador almacena en caché los datos de configuración regional, por lo que reutilizar instancias evita la inicialización repetida.

Ejemplo práctico: construcción de un analizador de frecuencia de palabras

Combina la segmentación de palabras con el conteo para analizar la frecuencia de palabras en el texto.

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 }

Esta función divide el texto en palabras, normaliza a minúsculas y cuenta las ocurrencias. Funciona para cualquier idioma.

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

La misma lógica maneja texto en chino sin modificaciones.

Verificación de compatibilidad del navegador

La API Intl.Segmenter alcanzó el estado Baseline en abril de 2024. Funciona en las versiones actuales de Chrome, Firefox, Safari y Edge. Los navegadores más antiguos no la soportan.

Verifica la compatibilidad antes de usar la API.

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

Para aplicaciones en producción dirigidas a navegadores más antiguos, proporciona una implementación alternativa. Una alternativa simple usa split() para texto en inglés y devuelve la cadena completa para otros idiomas.

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

Esto garantiza que tu código se ejecute en navegadores más antiguos, aunque con funcionalidad reducida para idiomas sin separación por espacios.

Errores comunes que debes evitar

No dividas por espacios o patrones regex para texto multilingüe. Estos enfoques solo funcionan para un subconjunto de idiomas y fallan para chino, japonés, tailandés y otros idiomas sin espacios.

No olvides filtrar por isWordLike al extraer palabras. Sin este filtro, obtendrás espacios, puntuación y otros segmentos que no son palabras en tus resultados.

No uses la configuración regional incorrecta al segmentar texto. La configuración regional determina qué reglas de límites de palabras se aplican. Usar una configuración regional en inglés para texto en chino produce resultados incorrectos.

No asumas que todos los idiomas definen las palabras de la misma manera. Los límites de palabras varían según el sistema de escritura y la convención lingüística. Usa segmentación consciente de la configuración regional para manejar estas diferencias.

No cuentes palabras usando split(" ").length para texto internacional. Esto solo funciona para idiomas separados por espacios y produce conteos incorrectos para otros.

Cuándo usar la segmentación de palabras

Usa la segmentación de palabras cuando necesites:

  • Contar palabras en contenido generado por usuarios en múltiples idiomas
  • Implementar funciones de búsqueda y resaltado que funcionen con cualquier sistema de escritura
  • Crear herramientas de análisis de texto que procesen texto internacional
  • Crear funciones de navegación o edición basadas en palabras en editores de texto
  • Extraer palabras clave o términos de documentos multilingües
  • Validar límites de recuento de palabras en formularios que acepten cualquier idioma

No uses la segmentación de palabras cuando solo necesites contar caracteres. Usa la segmentación de grafemas para operaciones a nivel de carácter.

No uses la segmentación de palabras para dividir oraciones. Usa la granularidad de oración para ese propósito.

Cómo encaja la segmentación de palabras en la internacionalización

La API Intl.Segmenter es parte de la API de internacionalización de ECMAScript. Otras API de esta familia manejan diferentes aspectos de la internacionalización:

  • Intl.DateTimeFormat: formatea fechas y horas según la configuración regional
  • Intl.NumberFormat: formatea números, monedas y unidades según la configuración regional
  • Intl.Collator: ordena y compara cadenas según la configuración regional
  • Intl.PluralRules: determina las formas plurales de los números en diferentes idiomas

En conjunto, estas API proporcionan las herramientas necesarias para crear aplicaciones que funcionen correctamente para usuarios de todo el mundo. Usa Intl.Segmenter con granularidad de palabra cuando necesites identificar límites de palabras, y usa las otras API Intl para formateo y comparación.