API Intl.Collator

Ordena y compara cadenas correctamente en diferentes idiomas

Introducción

Ordenar cadenas en JavaScript parece sencillo hasta que te encuentras con texto internacional. La comparación de cadenas predeterminada utiliza valores de puntos de código Unicode, produciendo resultados incorrectos para muchos idiomas. La API Intl.Collator proporciona comparación de cadenas consciente de la configuración regional que respeta las reglas de ordenación cultural y maneja correctamente los caracteres especiales.

Por qué falla la ordenación predeterminada

Considera ordenar una lista de nombres alemanes:

const names = ["Zoe", "Ava", "Ärzte", "Änder"];
console.log(names.sort());
// ["Ava", "Zoe", "Änder", "Ärzte"]

Este resultado es incorrecto para los hablantes de alemán. En alemán, los caracteres con diéresis como ä deberían ordenarse cerca de su letra base a, no al final. El problema proviene de que JavaScript compara valores de puntos de código Unicode, donde Ä (U+00C4) viene después de Z (U+005A).

Diferentes idiomas tienen diferentes reglas de ordenación. El sueco ordena ä al final del alfabeto, el alemán lo ordena cerca de a, y el francés trata los caracteres acentuados de manera diferente. La comparación binaria ignora estas convenciones culturales.

Cómo funciona la intercalación de cadenas

La intercalación es el proceso de comparar y ordenar cadenas según reglas específicas del idioma. El algoritmo de intercalación Unicode define cómo comparar cadenas analizando caracteres, diacríticos, mayúsculas y puntuación por separado.

Al comparar dos cadenas, una función de intercalación devuelve un número:

  • Valor negativo: la primera cadena va antes que la segunda
  • Cero: las cadenas son equivalentes para el nivel de sensibilidad actual
  • Valor positivo: la primera cadena va después de la segunda

Este patrón de comparación de tres vías funciona con Array.sort y permite un control preciso sobre qué diferencias importan.

Uso de localeCompare para ordenación básica consciente de la configuración regional

El método localeCompare proporciona comparación de cadenas consciente de la configuración regional:

const names = ["Zoe", "Ava", "Ärzte", "Änder"];
console.log(names.sort((a, b) => a.localeCompare(b, "de")));
// ["Ava", "Änder", "Ärzte", "Zoe"]

Esto produce una ordenación alemana correcta. El primer parámetro especifica la configuración regional, y localeCompare maneja las reglas culturales automáticamente.

Puedes pasar opciones como tercer parámetro:

const items = ["File10", "File2", "File1"];
console.log(items.sort((a, b) =>
  a.localeCompare(b, "en", { numeric: true })
));
// ["File1", "File2", "File10"]

La opción numeric habilita la ordenación natural donde "2" viene antes que "10". Sin ella, "10" se ordenaría antes que "2" porque "1" viene antes que "2".

El problema de rendimiento con localeCompare repetido

Cada llamada a localeCompare procesa la configuración regional desde cero. Al ordenar arrays grandes, esto crea una sobrecarga significativa:

// Inefficient: processes locale for every comparison
const sorted = items.sort((a, b) => a.localeCompare(b, "de"));

Ordenar 1000 elementos requiere aproximadamente 10 000 comparaciones. Cada comparación recrea la configuración regional, multiplicando el coste de rendimiento. Esta sobrecarga se vuelve notable en interfaces de usuario con grandes conjuntos de datos.

Uso de Intl.Collator para comparación eficiente de cadenas

Intl.Collator crea un objeto de comparación reutilizable que procesa la configuración regional una sola vez:

const collator = new Intl.Collator("de");
const sorted = items.sort((a, b) => collator.compare(a, b));

La instancia del collator almacena la configuración regional y las reglas de comparación. El método compare utiliza estas reglas precomputadas para cada comparación, eliminando la sobrecarga de inicialización repetida.

Las mejoras de rendimiento oscilan entre el 60% y el 80% al ordenar arrays grandes en comparación con llamadas repetidas a localeCompare.

Acceso directo al método compare

Puedes pasar el método compare directamente a sort:

const collator = new Intl.Collator("de");
const sorted = items.sort(collator.compare);

Esto funciona porque compare está vinculado a la instancia del collator. El método recibe dos cadenas y devuelve el resultado de la comparación, coincidiendo con la firma que Array.sort espera.

Comprensión de los niveles de sensibilidad

La opción sensitivity controla qué diferencias de caracteres importan durante la comparación. Hay cuatro niveles:

Sensibilidad base

La sensibilidad base ignora acentos y mayúsculas:

const collator = new Intl.Collator("en", { sensitivity: "base" });

console.log(collator.compare("a", "a")); // 0
console.log(collator.compare("a", "á")); // 0
console.log(collator.compare("a", "A")); // 0
console.log(collator.compare("a", "b")); // -1

Solo difieren las letras base. Este nivel funciona bien para búsquedas difusas donde los usuarios podrían no escribir los acentos correctamente.

Sensibilidad a acentos

La sensibilidad a acentos considera los acentos pero ignora las mayúsculas:

const collator = new Intl.Collator("en", { sensitivity: "accent" });

console.log(collator.compare("a", "a")); // 0
console.log(collator.compare("a", "á")); // -1
console.log(collator.compare("a", "A")); // 0
console.log(collator.compare("á", "A")); // 1

Los caracteres acentuados y no acentuados difieren. Las versiones en mayúsculas y minúsculas de la misma letra coinciden.

Sensibilidad a mayúsculas

La sensibilidad a mayúsculas considera las mayúsculas pero ignora los acentos:

const collator = new Intl.Collator("en", { sensitivity: "case" });

console.log(collator.compare("a", "a")); // 0
console.log(collator.compare("a", "á")); // 0
console.log(collator.compare("a", "A")); // -1
console.log(collator.compare("á", "Á")); // -1

Las diferencias de mayúsculas importan pero los acentos se ignoran. Este nivel es menos común en la práctica.

Sensibilidad a variantes

La sensibilidad a variantes considera todas las diferencias:

const collator = new Intl.Collator("en", { sensitivity: "variant" });

console.log(collator.compare("a", "a")); // 0
console.log(collator.compare("a", "á")); // -1
console.log(collator.compare("a", "A")); // -1
console.log(collator.compare("á", "Á")); // -1

Este es el valor predeterminado para ordenar. Cada diferencia de carácter produce un resultado de comparación distinto.

Elegir la sensibilidad según el caso de uso

Diferentes escenarios necesitan diferentes niveles de sensibilidad:

  • Ordenar listas: usa sensibilidad a variantes para mantener un orden estricto
  • Buscar contenido: usa sensibilidad base para coincidir independientemente de acentos o mayúsculas
  • Filtrar opciones: usa sensibilidad a acentos cuando las mayúsculas no deban importar
  • Búsqueda sensible a mayúsculas: usa sensibilidad a mayúsculas cuando los acentos no deban importar

La opción de uso proporciona configuraciones de sensibilidad predeterminadas para escenarios comunes.

Usar el modo de uso para ordenar y buscar

La opción de uso optimiza el comportamiento del collator para ordenar o buscar:

// Optimized for sorting
const sortCollator = new Intl.Collator("en", { usage: "sort" });

// Optimized for searching
const searchCollator = new Intl.Collator("en", { usage: "search" });

El uso de ordenación usa por defecto sensibilidad a variantes, asegurando que cada diferencia produzca un orden consistente. El uso de búsqueda optimiza para encontrar coincidencias, típicamente usando una sensibilidad más relajada.

Para búsqueda insensible a mayúsculas y acentos:

const collator = new Intl.Collator("en", {
  usage: "search",
  sensitivity: "base"
});

const items = ["Apple", "Äpfel", "Banana"];
const matches = items.filter(item =>
  collator.compare(item, "apple") === 0
);
console.log(matches); // ["Apple"]

Este patrón habilita coincidencias difusas donde los usuarios no necesitan escribir caracteres exactos.

Habilitar ordenación numérica para orden natural

La opción numérica trata los números incrustados como valores numéricos:

const collator = new Intl.Collator("en", { numeric: true });

const files = ["File1", "File10", "File2"];
console.log(files.sort(collator.compare));
// ["File1", "File2", "File10"]

Sin ordenación numérica, "File10" se ordenaría antes que "File2" porque la cadena "10" comienza con "1". La ordenación numérica analiza secuencias de números y las compara matemáticamente.

Esto produce un orden natural que coincide con las expectativas humanas para nombres de archivo, números de versión y listas numeradas.

Manejo de números decimales con ordenación numérica

La ordenación numérica tiene una limitación con los números decimales:

const collator = new Intl.Collator("en", { numeric: true });

const values = ["1.5", "1.10", "1.2"];
console.log(values.sort(collator.compare));
// ["1.2", "1.5", "1.10"]

El punto decimal se trata como puntuación, no como parte del número. Cada segmento entre signos de puntuación se ordena por separado. Para ordenar números decimales, analiza los valores como números y usa comparación numérica.

Control del orden de mayúsculas y minúsculas con caseFirst

La opción caseFirst determina si las letras mayúsculas o minúsculas se ordenan primero:

// Uppercase first
const upperFirst = new Intl.Collator("en", { caseFirst: "upper" });
console.log(["a", "A", "b", "B"].sort(upperFirst.compare));
// ["A", "a", "B", "b"]

// Lowercase first
const lowerFirst = new Intl.Collator("en", { caseFirst: "lower" });
console.log(["a", "A", "b", "B"].sort(lowerFirst.compare));
// ["a", "A", "b", "B"]

El valor predeterminado es false, que usa el orden predeterminado de la configuración regional. Esta opción no tiene efecto cuando sensitivity es base o accent porque esos niveles ignoran las mayúsculas y minúsculas.

Ignorar la puntuación durante la comparación

La opción ignorePunctuation omite los signos de puntuación durante la comparación:

const collator = new Intl.Collator("en", { ignorePunctuation: true });

console.log(collator.compare("hello", "he-llo")); // 0
console.log(collator.compare("hello", "hello!")); // 0

Esta opción tiene como valor predeterminado true para tailandés y false para otros idiomas. Úsala cuando la puntuación no deba afectar el orden o la coincidencia de cadenas.

Especificación de tipos de intercalación para reglas específicas del idioma

Algunas configuraciones regionales admiten múltiples tipos de intercalación para ordenación especializada:

// Chinese pinyin ordering
const pinyin = new Intl.Collator("zh-CN-u-co-pinyin");

// German phonebook ordering
const phonebook = new Intl.Collator("de-DE-u-co-phonebk");

// Emoji grouping
const emoji = new Intl.Collator("en-u-co-emoji");

El tipo de intercalación se especifica en la cadena de configuración regional usando la sintaxis de extensión Unicode. Los tipos comunes incluyen:

  • pinyin: ordenación china por pronunciación romanizada
  • stroke: ordenación china por número de trazos
  • phonebk: orden de guía telefónica alemana
  • trad: reglas de ordenación tradicionales para ciertos idiomas
  • emoji: agrupa emojis por categoría

Consulta Intl.supportedValuesOf para conocer los tipos de intercalación disponibles en tu entorno.

Reutilización de instancias de collator en tu aplicación

Crea instancias de collator una vez y reutilízalas en toda tu aplicación:

// utils/collation.js
export const germanCollator = new Intl.Collator("de");
export const searchCollator = new Intl.Collator("en", {
  sensitivity: "base"
});
export const numericCollator = new Intl.Collator("en", {
  numeric: true
});

// In your components
import { germanCollator } from "./utils/collation";

const sorted = names.sort(germanCollator.compare);

Este patrón maximiza el rendimiento y mantiene un comportamiento de comparación consistente en toda tu base de código.

Ordenación de arrays de objetos por propiedad

Usa el collator en una función de comparación que acceda a las propiedades del objeto:

const collator = new Intl.Collator("de");

const users = [
  { name: "Zoe" },
  { name: "Änder" },
  { name: "Ava" }
];

const sorted = users.sort((a, b) =>
  collator.compare(a.name, b.name)
);

Este enfoque funciona para cualquier estructura de objeto. Extrae las cadenas a comparar y pásalas al collator.

Comparación del rendimiento de Intl.Collator con localeCompare

Intl.Collator proporciona mejor rendimiento al ordenar grandes conjuntos de datos:

// Slower: recreates locale settings for each comparison
const slow = items.sort((a, b) => a.localeCompare(b, "de"));

// Faster: reuses precomputed locale settings
const collator = new Intl.Collator("de");
const fast = items.sort(collator.compare);

Para arrays pequeños (menos de 100 elementos), la diferencia es insignificante. Para arrays grandes (miles de elementos), Intl.Collator puede ser entre un 60 y un 80 % más rápido.

Existe una excepción en navegadores basados en V8 como Chrome. localeCompare tiene una optimización para cadenas exclusivamente ASCII mediante tablas de búsqueda. Al ordenar cadenas puramente ASCII, localeCompare puede tener un rendimiento comparable a Intl.Collator.

Saber cuándo usar Intl.Collator frente a localeCompare

Utiliza Intl.Collator cuando:

  • Ordenes arrays grandes (cientos o miles de elementos)
  • Ordenes repetidamente (el usuario cambia el orden, listas virtuales)
  • Construyas utilidades de comparación reutilizables
  • El rendimiento sea importante para tu caso de uso

Utiliza localeCompare cuando:

  • Hagas comparaciones puntuales
  • Ordenes arrays pequeños (menos de 100 elementos)
  • La simplicidad sea más importante que las preocupaciones de rendimiento
  • Necesites comparación en línea sin configuración

Ambas API admiten las mismas opciones y producen resultados idénticos. La diferencia es puramente sobre rendimiento y organización del código.

Verificación de opciones resueltas

El método resolvedOptions devuelve las opciones reales utilizadas por el collator:

const collator = new Intl.Collator("de", { sensitivity: "base" });
console.log(collator.resolvedOptions());
// {
//   locale: "de",
//   usage: "sort",
//   sensitivity: "base",
//   ignorePunctuation: false,
//   collation: "default",
//   numeric: false,
//   caseFirst: "false"
// }

Esto ayuda a depurar el comportamiento de la colación y comprender los valores predeterminados. El locale resuelto puede diferir del locale solicitado si el sistema no admite el locale exacto.

Verificación de compatibilidad con locales

Comprueba qué locales son compatibles en el entorno actual:

const supported = Intl.Collator.supportedLocalesOf(["de", "fr", "xx"]);
console.log(supported); // ["de", "fr"]

Los locales no compatibles recurren al predeterminado del sistema. Este método ayuda a detectar cuándo tu locale solicitado no está disponible.

Compatibilidad con navegadores y entornos

Intl.Collator ha sido ampliamente compatible desde septiembre de 2017. Todos los navegadores modernos y versiones de Node.js lo admiten. La API funciona de manera consistente en todos los entornos.

Algunos tipos de intercalación y opciones pueden tener soporte limitado en navegadores antiguos. Prueba la funcionalidad crítica o consulta las tablas de compatibilidad de MDN si necesitas dar soporte a entornos más antiguos.

Errores comunes que debes evitar

No crees un nuevo collator para cada comparación:

// Wrong: creates collator repeatedly
items.sort((a, b) => new Intl.Collator("de").compare(a, b));

// Right: create once, reuse
const collator = new Intl.Collator("de");
items.sort(collator.compare);

No asumas que la ordenación predeterminada funciona para texto internacional:

// Wrong: breaks for non-ASCII characters
names.sort();

// Right: use locale-aware sorting
names.sort(new Intl.Collator("de").compare);

No olvides especificar la sensibilidad para búsquedas:

// Wrong: variant sensitivity requires exact match
const collator = new Intl.Collator("en");
items.filter(item => collator.compare(item, "apple") === 0);

// Right: base sensitivity for fuzzy matching
const collator = new Intl.Collator("en", { sensitivity: "base" });
items.filter(item => collator.compare(item, "apple") === 0);

Casos de uso prácticos

Usa Intl.Collator para:

  • Ordenar contenido generado por usuarios (nombres, títulos, direcciones)
  • Implementar funciones de búsqueda y autocompletado
  • Construir tablas de datos con columnas ordenables
  • Crear listas filtradas y opciones de menús desplegables
  • Ordenar nombres de archivos y números de versión
  • Navegación alfabética en listas de contactos
  • Interfaces de aplicaciones multiidioma

Cualquier interfaz que muestre texto ordenado a los usuarios se beneficia de la intercalación consciente de la configuración regional. Esto garantiza que tu aplicación se sienta nativa y correcta independientemente del idioma del usuario.