¿Cómo dividir texto en palabras en JavaScript?
Utiliza Intl.Segmenter para extraer palabras de texto en cualquier idioma, incluyendo 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 el inglés, pero falla completamente para idiomas que no utilizan espacios entre palabras. El chino, japonés, tailandés y otros idiomas escriben texto continuamente sin separadores de palabras, sin embargo, los usuarios perciben palabras distintas en ese texto.
La API Intl.Segmenter resuelve este problema. Identifica los límites de las palabras según los estándares Unicode y las reglas lingüísticas para cada idioma. Puedes extraer palabras de un 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 textos internacionales, cómo funcionan los límites de palabras en diferentes sistemas de escritura y cómo usar Intl.Segmenter para dividir correctamente el texto en palabras para todos los idiomas.
Por qué dividir por espacios falla
El método split() divide una cadena en cada ocurrencia 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 chino no incluye espacios entre palabras.
const text = "你好世界";
const words = text.split(" ");
console.log(words);
// ["你好世界"]
El usuario ve dos palabras distintas, pero split() devuelve toda la cadena como un solo elemento porque no hay espacios para dividir.
El texto japonés mezcla múltiples escrituras y no usa espacios entre palabras.
const text = "今日は良い天気です";
const words = text.split(" ");
console.log(words);
// ["今日は良い天気です"]
Esta frase contiene múltiples palabras, pero dividir por espacios produce un solo elemento.
El texto tailandés también escribe palabras continuamente sin espacios.
const text = "สวัสดีครับ";
const words = text.split(" ");
console.log(words);
// ["สวัสดีครับ"]
El texto contiene dos palabras, pero split() devuelve un solo elemento.
Para estos idiomas, necesitas un enfoque diferente para identificar los límites de las palabras.
Por qué las expresiones regulares fallan para los límites de palabras
Los límites de palabras en expresiones regulares utilizan el patrón \b para coincidir con posiciones entre caracteres de palabra y no palabra. Esto funciona para el 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 regex no reconoce los límites de palabras en escrituras como el chino, japonés o tailandés.
const text = "你好世界";
const words = text.match(/\b\w+\b/g);
console.log(words);
// ["你好世界"]
El regex trata toda la cadena como una sola palabra porque no comprende los límites de palabras en chino.
Incluso para el inglés, los patrones regex 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 termina una palabra y comienza otra. Diferentes sistemas de escritura utilizan diferentes convenciones para los límites de palabras.
Los idiomas separados por espacios como el inglés, español, francés y alemán utilizan espacios para marcar los límites de palabras. La palabra "hola" está separada de "mundo" por un espacio.
Los idiomas de scriptio continua como el chino, japonés y tailandés no utilizan espacios entre palabras. Los límites de palabras existen basados en 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 a través de separadores visuales.
Algunos idiomas utilizan convenciones mixtas. El japonés combina caracteres kanji, hiragana y katakana, y los límites de palabras ocurren en las transiciones entre tipos de caracteres o basados en la estructura gramatical.
El Estándar Unicode define reglas de límites de palabras en UAX 29. Estas reglas especifican cómo identificar límites de palabras para todos los sistemas de escritura. Las reglas consideran propiedades de caracteres, tipos de escritura y patrones lingüísticos para determinar dónde comienzan y terminan las palabras.
Uso de 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. Se especifica 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 las 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 segmentoindex: la posición en la cadena original donde comienza este segmentoinput: la cadena original que se está segmentandoisWordLike: si este segmento es una palabra o contenido que no es una palabra
Entendiendo 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
Utiliza la propiedad isWordLike para filtrar segmentos de palabras de la puntuación y espacios en blanco. Esto te da 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 scripts 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 partículas como "は" son palabras separadas y que 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 separación de espacios y sin separación de 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.
Contando palabras con precisión 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 recuentos precisos de palabras 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 recuentos coinciden con la percepción del usuario sobre los límites de las palabras en cada idioma.
Encontrando 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 ha clickeado.
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 }
Utiliza esto para funciones de editor de texto como selección de palabras con doble clic, menús contextuales basados en la posición del cursor o 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 normalmente se dividen 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, necesitarás lógica adicional para fusionar segmentos basados en apóstrofes.
Para la mayoría de los casos de uso, contar segmentos similares a palabras te proporciona recuentos significativos de palabras incluso cuando las contracciones están divididas.
Cómo el locale afecta a la segmentación de palabras
El locale que pasas al segmentador afecta a cómo se determinan los límites de las palabras. Diferentes locales pueden tener diferentes reglas para el mismo texto.
Para idiomas con reglas de límites de palabras bien definidas, el locale 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);
// ["你好", "世界"]
El locale inglés no reconoce los límites de palabras chinas y trata toda la cadena como una sola palabra. El locale chino aplica las reglas de límites de palabras chinas e identifica correctamente dos palabras.
Siempre utiliza el locale apropiado para el idioma del texto que se está segmentando.
Creación de segmentadores reutilizables para mejorar el rendimiento
Crear un segmentador no es costoso, pero puedes reutilizar segmentadores en múltiples cadenas para obtener 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 del locale, 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 un 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.
Comprobando la 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" });
// Usar segmenter
} else {
// Alternativa para navegadores antiguos
}
Para aplicaciones en producción dirigidas a navegadores más antiguos, proporciona una implementación alternativa. Una alternativa simple utiliza 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);
}
// Alternativa: solo funciona para idiomas separados por espacios
return text.split(/\s+/).filter(word => word.length > 0);
}
Esto asegura que tu código se ejecute en navegadores más antiguos, aunque con funcionalidad reducida para idiomas que no separan palabras con espacios.
Errores comunes a evitar
No dividas el texto multilingüe por espacios o patrones regex. 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 el locale incorrecto al segmentar texto. El locale determina qué reglas de límites de palabras se aplican. Usar un locale inglés para texto 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. Utiliza la segmentación adaptada al locale para manejar estas diferencias.
No cuentes palabras usando split(" ").length para texto internacional. Esto solo funciona para idiomas separados por espacios y produce recuentos 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
- Construir herramientas de análisis de texto que procesen texto internacional
- Crear características 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 aceptan 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 caracteres.
No uses la segmentación de palabras para dividir oraciones. Usa la granularidad de oraciones 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 APIs en esta familia manejan diferentes aspectos de la internacionalización:
Intl.DateTimeFormat: Formatea fechas y horas según el localeIntl.NumberFormat: Formatea números, monedas y unidades según el localeIntl.Collator: Ordena y compara cadenas según el localeIntl.PluralRules: Determina formas plurales para números en diferentes idiomas
En conjunto, estas APIs proporcionan las herramientas necesarias para construir aplicaciones que funcionen correctamente para usuarios de todo el mundo. Usa Intl.Segmenter con granularidad de palabras cuando necesites identificar límites de palabras, y usa las otras APIs de Intl para formateo y comparación.