¿Cómo divido el texto en oraciones?
Usa Intl.Segmenter para dividir texto en oraciones con detección de límites consciente de la configuración regional que maneja puntuación, abreviaturas y reglas específicas del idioma.
Introducción
Cuando procesas texto para traducción, análisis o visualización, a menudo necesitas dividirlo en oraciones individuales. Un enfoque ingenuo usando expresiones regulares falla porque los límites de las oraciones son más complejos que puntos seguidos de espacios. Las oraciones pueden terminar con signos de interrogación, signos de exclamación o puntos suspensivos. Los puntos aparecen en abreviaturas como "Dr." o "Inc." sin terminar oraciones. Diferentes idiomas usan diferentes signos de puntuación como terminadores de oraciones.
La API Intl.Segmenter resuelve este problema proporcionando detección de límites de oraciones consciente de la configuración regional. Entiende las reglas para identificar límites de oraciones en diferentes idiomas y maneja casos especiales como abreviaturas, números y puntuación compleja automáticamente.
El problema de dividir por puntos
Puedes intentar dividir texto en oraciones dividiéndolo por puntos seguidos de espacios.
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."]
Este enfoque tiene múltiples problemas. Primero, no maneja signos de interrogación o exclamación. Segundo, se rompe en abreviaturas que contienen puntos. Tercero, elimina el punto de cada oración excepto la última. Cuarto, no funciona cuando hay múltiples espacios después de los puntos.
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."]
El texto se divide incorrectamente en "Dr." e "Inc." porque estas abreviaturas contienen puntos. Necesitas un enfoque más inteligente que entienda las reglas de límites de oraciones.
Uso de una expresión regular más compleja
Puedes mejorar la expresión regular para manejar más casos.
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", ""]
Esto divide por puntos, signos de interrogación y signos de exclamación seguidos de espacios en blanco. Maneja más casos pero aún falla con abreviaturas y crea cadenas vacías. También elimina la puntuación de cada oración.
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", ""]
El enfoque con regex no puede distinguir de manera confiable entre puntos que terminan oraciones y puntos que aparecen en abreviaturas. Construir una expresión regular exhaustiva que maneje todos los casos extremos se vuelve impráctico. Necesitas una solución que comprenda las reglas lingüísticas.
Uso de Intl.Segmenter para dividir oraciones
El constructor Intl.Segmenter crea un segmentador que divide texto basándose en reglas específicas del idioma. Especificas un idioma y estableces la opción granularity en "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!"
El método segment() devuelve un iterable que produce objetos de segmento. Cada objeto de segmento tiene una propiedad segment que contiene el texto de ese segmento. El segmentador preserva la puntuación y los espacios en blanco al final de cada oración.
Puedes convertir los segmentos en un array usando 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!"]
Esto crea un array donde cada elemento es una oración con su puntuación y espaciado originales.
Cómo Intl.Segmenter maneja las abreviaturas
El segmentador comprende patrones comunes de abreviaturas y no divide en puntos que aparecen dentro de abreviaturas.
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."]
El texto se divide correctamente en dos oraciones. Los puntos en "Dr.", "Inc." y "a.m." no desencadenan saltos de oración porque el segmentador reconoce estos como abreviaturas. Este manejo automático de casos extremos es la razón por la cual Intl.Segmenter es superior a los enfoques con regex.
Eliminación de espacios en blanco de las oraciones
El segmentador incluye espacios en blanco finales en cada oración. Puedes eliminar estos espacios en blanco si es necesario.
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!"]
El método trim() elimina los espacios en blanco iniciales y finales de cada oración. Esto es útil cuando necesitas límites de oración limpios sin espaciado adicional.
Obtención de metadatos del segmento
Cada objeto de segmento incluye metadatos sobre la posición del segmento en el texto 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 propiedad index indica dónde comienza el segmento en el texto original. La propiedad input contiene el texto original completo. Estos metadatos son útiles cuando necesitas rastrear las posiciones de las oraciones o reconstruir el texto original.
Dividir oraciones en diferentes idiomas
Los diferentes idiomas tienen reglas distintas para los límites de las oraciones. El segmentador adapta su comportamiento según la configuración regional especificada.
En japonés, las oraciones pueden terminar con un punto de ancho completo 。 llamado 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);
// ["私は猫です。", "名前はまだない。"]
El texto se divide correctamente en los terminadores de oración japoneses. Un segmentador configurado para inglés no reconocería estos límites correctamente.
En hindi, las oraciones pueden terminar con una barra vertical । llamada 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);
// ["यह एक वाक्य है। ", "यह दूसरा वाक्य है।"]
El segmentador reconoce el punto completo devanagari como un límite de oración. Este comportamiento consciente de la configuración regional es fundamental para el procesamiento de texto internacionalizado.
Usar la configuración regional correcta para texto multilingüe
Cuando procesas texto que contiene varios idiomas, elige la configuración regional que coincida con el idioma principal del texto. El segmentador utiliza la configuración regional especificada para determinar qué reglas de límites aplicar.
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);
// ["私は猫です。", "名前はまだない。"]
Crear segmentadores separados para cada idioma garantiza una detección correcta de los límites. Si procesas texto donde el idioma es desconocido, puedes usar una configuración regional genérica como "en" como alternativa, aunque esto reduce la precisión para texto que no sea en inglés.
Manejar texto sin límites de oración
Cuando el texto no contiene terminadores de oración, el segmentador devuelve el texto completo como un único segmento.
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"]
Este comportamiento es correcto porque el texto no contiene ningún límite de oración. El segmentador no divide artificialmente el texto que forma una sola oración.
Manejo de cadenas vacías
El segmentador maneja cadenas vacías devolviendo un iterador vacío.
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);
// []
Esto produce un array vacío, que es el resultado esperado para una entrada vacía.
Reutilización de segmentadores para mejor rendimiento
Crear un segmentador tiene cierta sobrecarga. Cuando necesites segmentar múltiples textos con la misma configuración regional y opciones, crea el segmentador una vez y reutilízalo.
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."]
Reutilizar el segmentador es más eficiente que crear uno nuevo para cada texto.
Construcción de una función de conteo de oraciones
Puedes usar el segmentador para contar oraciones en un texto.
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
Esta función crea un segmentador, divide el texto y devuelve el número de segmentos. Maneja abreviaturas y límites específicos del idioma correctamente.
Construcción de una función de extracción de oraciones
Puedes crear una función que extraiga una oración específica de un texto por índice.
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
Esta función devuelve la oración en el índice especificado, o null si el índice está fuera de los límites.
Verificación de compatibilidad con navegadores y entornos de ejecución
La API Intl.Segmenter está disponible en navegadores modernos y Node.js. Se convirtió en parte de la línea base de la plataforma web en abril de 2024 y es compatible con todos los motores de navegador principales.
Puedes verificar si la API está disponible antes de usarla.
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");
}
Para entornos sin compatibilidad, necesitas proporcionar una alternativa. Una alternativa simple usa una división básica con regex, aunque esto pierde la precisión de la segmentación consciente de la configuración regional.
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 for older environments
return text.split(/[.!?]\s+/).filter(s => s.length > 0);
}
console.log(splitSentences("Hello world. How are you?"));
// ["Hello world. ", "How are you?"]
Esta función usa Intl.Segmenter cuando está disponible y recurre a la división con regex en entornos más antiguos. La alternativa pierde características como el manejo de abreviaturas y la detección de límites específicos del idioma, pero proporciona funcionalidad básica.