Cómo ordenar cadenas alfabéticamente por idioma en JavaScript
Usa Intl.Collator y localeCompare() para ordenar cadenas correctamente en cualquier idioma
Introducción
Cuando ordenas un arreglo de cadenas en JavaScript, el comportamiento por defecto compara las cadenas según los valores de sus unidades de código UTF-16. Esto funciona para texto ASCII básico, pero falla al ordenar nombres, títulos de productos o cualquier texto con caracteres acentuados, alfabetos no latinos o letras mayúsculas y minúsculas mezcladas.
Cada idioma tiene reglas propias para el orden alfabético. En sueco, å, ä y ö van al final del alfabeto después de la z. En alemán, ä suele considerarse equivalente a a, según el contexto. El francés ignora los acentos en ciertos modos de comparación. Estas reglas lingüísticas definen cómo las personas esperan ver ordenadas las listas en su idioma.
JavaScript ofrece dos APIs para ordenar cadenas teniendo en cuenta el idioma. El método String.prototype.localeCompare() sirve para comparaciones simples. La API Intl.Collator ofrece mejor rendimiento al ordenar arreglos grandes. En esta lección veremos cómo funcionan ambas, cuándo conviene usar cada una y cómo configurar la ordenación para distintos idiomas.
Por qué el ordenamiento por defecto falla con texto internacional
El método Array.sort() compara las cadenas según los valores de sus unidades de código UTF-16. Esto implica que las letras mayúsculas siempre aparecen antes que las minúsculas y los caracteres acentuados se ordenan después de la z.
const names = ['Åsa', 'Anna', 'Örjan', 'Bengt', 'Ärla'];
const sorted = names.sort();
console.log(sorted);
// Output: ['Anna', 'Bengt', 'Åsa', 'Ärla', 'Örjan']
Este resultado es incorrecto en sueco. En sueco, å, ä y ö son letras independientes que deben ir al final del alfabeto. El orden correcto sería primero Anna, luego Bengt, y después Åsa, Ärla y Örjan.
El problema ocurre porque la ordenación por defecto compara valores de puntos de código, no el significado lingüístico. La letra Å tiene el punto de código U+00C5, que es mayor que el punto de código de z (U+007A). JavaScript no tiene forma de saber que para los suecos, Å es una letra distinta con una posición concreta en el alfabeto.
El uso de mayúsculas y minúsculas crea otro problema.
const words = ['zebra', 'Apple', 'banana', 'Zoo'];
const sorted = words.sort();
console.log(sorted);
// Output: ['Apple', 'Zoo', 'banana', 'zebra']
Todas las letras en mayúsculas tienen valores de código inferiores a las letras en minúsculas. Esto hace que Apple y Zoo aparezcan antes que banana, lo cual no es un orden alfabético correcto en ningún idioma.
Cómo localeCompare ordena cadenas según reglas lingüísticas
El método localeCompare() compara dos cadenas de texto según las reglas de ordenamiento de un locale específico. Devuelve un número negativo si la primera cadena va antes que la segunda, cero si son equivalentes y un número positivo si la primera va después de la segunda.
const result = 'a'.localeCompare('b', 'en-US');
console.log(result);
// Output: -1 (negative means 'a' comes before 'b')
Puedes usar localeCompare() directamente con Array.sort() pasándola como función de comparación.
const names = ['Åsa', 'Anna', 'Örjan', 'Bengt', 'Ärla'];
const sorted = names.sort((a, b) => a.localeCompare(b, 'sv-SE'));
console.log(sorted);
// Output: ['Anna', 'Bengt', 'Åsa', 'Ärla', 'Örjan']
El locale sueco coloca primero a Anna y Bengt porque usan letras latinas estándar. Luego vienen Åsa, Ärla y Örjan con sus letras suecas especiales al final.
La misma lista ordenada con el locale alemán produce resultados diferentes.
const names = ['Åsa', 'Anna', 'Örjan', 'Bengt', 'Ärla'];
const sorted = names.sort((a, b) => a.localeCompare(b, 'de-DE'));
console.log(sorted);
// Output: ['Anna', 'Ärla', 'Åsa', 'Bengt', 'Örjan']
En alemán, ä se trata como equivalente a a a efectos de ordenamiento. Esto coloca a Ärla justo después de Anna, en lugar de al final como hace el sueco.
Cuándo usar localeCompare
Utiliza localeCompare() cuando necesites ordenar un arreglo pequeño o comparar dos cadenas. Ofrece una API sencilla sin requerir que crees ni administres un objeto collator.
const items = ['Banana', 'apple', 'Cherry'];
const sorted = items.sort((a, b) => a.localeCompare(b, 'en-US'));
console.log(sorted);
// Output: ['apple', 'Banana', 'Cherry']
Este enfoque funciona bien en arreglos con algunas docenas de elementos. El impacto en el rendimiento es insignificante para conjuntos de datos pequeños.
También puedes usar localeCompare() para comprobar si una cadena va antes que otra sin tener que ordenar todo un arreglo.
const firstName = 'Åsa';
const secondName = 'Anna';
if (firstName.localeCompare(secondName, 'sv-SE') < 0) {
console.log(`${firstName} comes before ${secondName}`);
} else {
console.log(`${secondName} comes before ${firstName}`);
}
// Output: "Anna comes before Åsa"
Esta comparación respeta el orden alfabético sueco sin necesidad de ordenar todo el arreglo.
Cómo Intl.Collator mejora el rendimiento
La API Intl.Collator crea una función de comparación reutilizable, optimizada para usarse repetidas veces. Cuando ordenas arreglos grandes o realizas muchas comparaciones, un collator es significativamente más rápido que llamar a localeCompare() en cada comparación.
const collator = new Intl.Collator('sv-SE');
const names = ['Åsa', 'Anna', 'Örjan', 'Bengt', 'Ärla'];
const sorted = names.sort(collator.compare);
console.log(sorted);
// Output: ['Anna', 'Bengt', 'Åsa', 'Ärla', 'Örjan']
La propiedad collator.compare devuelve una función de comparación que funciona directamente con Array.sort(). No necesitas envolverla en una función de flecha.
Crear un collator una sola vez y reutilizarlo para varias operaciones evita la sobrecarga de buscar los datos de localización en cada comparación.
const collator = new Intl.Collator('de-DE');
const germanCities = ['München', 'Berlin', 'Köln', 'Hamburg'];
const sortedCities = germanCities.sort(collator.compare);
const germanNames = ['Müller', 'Schmidt', 'Schröder', 'Fischer'];
const sortedNames = germanNames.sort(collator.compare);
console.log(sortedCities);
// Output: ['Berlin', 'Hamburg', 'Köln', 'München']
console.log(sortedNames);
// Output: ['Fischer', 'Müller', 'Schmidt', 'Schröder']
El mismo collator puede manejar ambos arreglos sin necesidad de crear una nueva instancia.
Cuándo usar Intl.Collator
Usa Intl.Collator al ordenar arreglos con cientos o miles de elementos. El beneficio en rendimiento aumenta con el tamaño del arreglo, porque la función de comparación se llama muchas veces durante la ordenación.
const collator = new Intl.Collator('en-US');
const products = [/* array with 10,000 product names */];
const sorted = products.sort(collator.compare);
Para arreglos de más de unos cientos de elementos, el collator puede ser varias veces más rápido que localeCompare().
También usa Intl.Collator cuando necesitas ordenar varios arreglos con el mismo locale y opciones. Crear el collator una vez y reutilizarlo elimina búsquedas repetidas de datos de localización.
const collator = new Intl.Collator('fr-FR');
const firstNames = ['Amélie', 'Bernard', 'Émilie', 'François'];
const lastNames = ['Dubois', 'Martin', 'Lefèvre', 'Bernard'];
const sortedFirstNames = firstNames.sort(collator.compare);
const sortedLastNames = lastNames.sort(collator.compare);
Este patrón funciona muy bien para construir vistas de tablas u otras interfaces que muestran varias listas ordenadas.
Cómo especificar el locale
Tanto localeCompare() como Intl.Collator aceptan un identificador de locale como su primer argumento. Este identificador utiliza el formato BCP 47, que normalmente combina un código de idioma y un código de región opcional.
const names = ['Åsa', 'Anna', 'Ärla'];
// Swedish locale
const swedishSorted = names.sort((a, b) => a.localeCompare(b, 'sv-SE'));
console.log(swedishSorted);
// Output: ['Anna', 'Åsa', 'Ärla']
// German locale
const germanSorted = names.sort((a, b) => a.localeCompare(b, 'de-DE'));
console.log(germanSorted);
// Output: ['Anna', 'Ärla', 'Åsa']
El locale determina qué reglas de ordenación se aplican. El sueco y el alemán tienen reglas diferentes para å y ä, lo que produce diferentes órdenes de clasificación.
Puedes omitir el locale para usar el locale predeterminado del usuario desde el navegador.
const collator = new Intl.Collator();
const names = ['Åsa', 'Anna', 'Ärla'];
const sorted = names.sort(collator.compare);
Este enfoque respeta las preferencias de idioma del usuario sin fijar un locale específico. El orden resultante coincidirá con lo que el usuario espera según la configuración de su navegador.
También puedes pasar un array de locales para ofrecer opciones de respaldo.
const collator = new Intl.Collator(['sv-SE', 'sv', 'en-US']);
La API utiliza el primer locale compatible del array. Si el sueco de Suecia no está disponible, prueba con el sueco genérico y luego recurre al inglés de EE. UU.
Cómo controlar la sensibilidad a mayúsculas y acentos
La opción sensitivity determina cómo la comparación trata las diferencias de mayúsculas y acentos. Acepta cuatro valores: base, accent, case y variant.
La sensibilidad base ignora tanto mayúsculas como acentos, comparando solo los caracteres base.
const collator = new Intl.Collator('en-US', { sensitivity: 'base' });
console.log(collator.compare('a', 'A'));
// Output: 0 (equal)
console.log(collator.compare('a', 'á'));
// Output: 0 (equal)
console.log(collator.compare('a', 'b'));
// Output: -1 (different base characters)
Este modo trata a, A y á como idénticos porque comparten el mismo carácter base.
La sensibilidad accent considera los acentos pero ignora las mayúsculas.
const collator = new Intl.Collator('en-US', { sensitivity: 'accent' });
console.log(collator.compare('a', 'A'));
// Output: 0 (equal, case ignored)
console.log(collator.compare('a', 'á'));
// Output: -1 (different, accent matters)
La sensibilidad case tiene en cuenta las mayúsculas pero ignora los acentos.
const collator = new Intl.Collator('en-US', { sensitivity: 'case' });
console.log(collator.compare('a', 'A'));
// Output: -1 (different, case matters)
console.log(collator.compare('a', 'á'));
// Output: 0 (equal, accent ignored)
La sensibilidad variant (la predeterminada) considera todas las diferencias.
const collator = new Intl.Collator('en-US', { sensitivity: 'variant' });
console.log(collator.compare('a', 'A'));
// Output: -1 (different)
console.log(collator.compare('a', 'á'));
// Output: -1 (different)
Este modo ofrece la comparación más estricta, tratando cualquier diferencia como significativa.
Cómo ordenar cadenas que contienen números
La opción numeric habilita la ordenación numérica para las cadenas que contienen números. Cuando está activada, la comparación trata las secuencias de dígitos como valores numéricos en vez de compararlas carácter por carácter.
const files = ['file1.txt', 'file10.txt', 'file2.txt', 'file20.txt'];
// Default sorting (wrong order)
const defaultSorted = [...files].sort();
console.log(defaultSorted);
// Output: ['file1.txt', 'file10.txt', 'file2.txt', 'file20.txt']
// Numeric sorting (correct order)
const collator = new Intl.Collator('en-US', { numeric: true });
const numericSorted = files.sort(collator.compare);
console.log(numericSorted);
// Output: ['file1.txt', 'file2.txt', 'file10.txt', 'file20.txt']
Sin la ordenación numérica, las cadenas se ordenan carácter por carácter. La cadena 10 aparece antes que 2 porque el primer carácter 1 tiene un valor de código menor que 2.
Con la ordenación numérica activada, el collator reconoce 10 como el número diez e 2 como el número dos. Esto produce el orden esperado donde 2 aparece antes de 10.
Esta opción es útil para ordenar nombres de archivos, números de versión o cualquier cadena que mezcle texto y números.
const versions = ['v1.10', 'v1.2', 'v1.20', 'v1.3'];
const collator = new Intl.Collator('en-US', { numeric: true });
const sorted = versions.sort(collator.compare);
console.log(sorted);
// Output: ['v1.2', 'v1.3', 'v1.10', 'v1.20']
Cómo controlar qué tipo de mayúsculas va primero
La opción caseFirst determina si las letras mayúsculas o minúsculas se ordenan primero al comparar cadenas que solo difieren en el uso de mayúsculas. Acepta tres valores: upper, lower o false.
const words = ['apple', 'Apple', 'APPLE'];
// Uppercase first
const upperFirst = new Intl.Collator('en-US', { caseFirst: 'upper' });
const upperSorted = [...words].sort(upperFirst.compare);
console.log(upperSorted);
// Output: ['APPLE', 'Apple', 'apple']
// Lowercase first
const lowerFirst = new Intl.Collator('en-US', { caseFirst: 'lower' });
const lowerSorted = [...words].sort(lowerFirst.compare);
console.log(lowerSorted);
// Output: ['apple', 'Apple', 'APPLE']
// Default (locale-dependent)
const defaultCase = new Intl.Collator('en-US', { caseFirst: 'false' });
const defaultSorted = [...words].sort(defaultCase.compare);
console.log(defaultSorted);
// Output depends on locale
El valor false utiliza el orden predeterminado de mayúsculas del locale. En la mayoría de los locales, las cadenas que solo difieren en mayúsculas se consideran iguales cuando se usan las opciones de sensibilidad predeterminadas.
Esta opción solo tiene efecto cuando la opción sensitivity permite que las diferencias de mayúsculas se tengan en cuenta.
Cómo ignorar la puntuación al ordenar
La opción ignorePunctuation le indica al comparador que ignore los signos de puntuación al comparar cadenas. Esto puede ser útil al ordenar títulos o frases que pueden o no incluir puntuación.
const titles = [
'The Old Man',
'The Old-Man',
'The Oldman',
];
// Default (punctuation matters)
const defaultCollator = new Intl.Collator('en-US');
const defaultSorted = [...titles].sort(defaultCollator.compare);
console.log(defaultSorted);
// Output: ['The Old Man', 'The Old-Man', 'The Oldman']
// Ignore punctuation
const noPunctCollator = new Intl.Collator('en-US', { ignorePunctuation: true });
const noPunctSorted = [...titles].sort(noPunctCollator.compare);
console.log(noPunctSorted);
// Output: ['The Old Man', 'The Old-Man', 'The Oldman']
Cuando se ignora la puntuación, la comparación trata el guion en "Old-Man" como si no existiera, haciendo que las cadenas se comparen como si fueran "TheOldMan".
Ordenando nombres de usuarios de diferentes países
Al ordenar nombres de usuarios de todo el mundo, usa el locale preferido de cada usuario para respetar sus expectativas lingüísticas.
const userLocale = navigator.language;
const collator = new Intl.Collator(userLocale);
const users = [
{ name: 'Müller', country: 'Germany' },
{ name: 'Martin', country: 'France' },
{ name: 'Andersson', country: 'Sweden' },
{ name: 'García', country: 'Spain' },
];
const sorted = users.sort((a, b) => collator.compare(a.name, b.name));
sorted.forEach(user => {
console.log(`${user.name} (${user.country})`);
});
Este código detecta el locale del usuario desde el navegador y ordena los nombres en consecuencia. Un/a usuario/a alemán/a verá la lista ordenada según las reglas alemanas, mientras que un/a usuario/a sueco/a la verá ordenada por las reglas suecas.
Ordenar con cambio de locale
Cuando tu aplicación permita a los usuarios cambiar de idioma, actualiza el comparador cuando cambie el locale.
let currentLocale = 'en-US';
let collator = new Intl.Collator(currentLocale);
function setLocale(newLocale) {
currentLocale = newLocale;
collator = new Intl.Collator(currentLocale);
}
function sortItems(items) {
return items.sort(collator.compare);
}
// User switches to Swedish
setLocale('sv-SE');
const names = ['Åsa', 'Anna', 'Örjan'];
console.log(sortItems(names));
// Output: ['Anna', 'Åsa', 'Örjan']
// User switches to German
setLocale('de-DE');
const germanNames = ['Über', 'Uhr', 'Udo'];
console.log(sortItems(germanNames));
// Output: ['Udo', 'Uhr', 'Über']
Este patrón asegura que las listas ordenadas se actualicen para coincidir con el idioma seleccionado por el usuario.
Elegir entre localeCompare e Intl.Collator
Usa localeCompare() cuando necesites hacer una comparación puntual o estés ordenando un arreglo pequeño con menos de 100 elementos. La sintaxis más simple facilita la lectura y la diferencia de rendimiento es insignificante para conjuntos de datos pequeños.
const items = ['banana', 'Apple', 'cherry'];
const sorted = items.sort((a, b) => a.localeCompare(b, 'en-US'));
Usa Intl.Collator cuando ordenes arreglos grandes, realices muchas comparaciones o debas ordenar varios arreglos con la misma configuración de idioma y opciones. Crear el collator una vez y reutilizarlo ofrece un mejor rendimiento.
const collator = new Intl.Collator('en-US', { sensitivity: 'base', numeric: true });
const products = [/* large array */];
const sorted = products.sort(collator.compare);
Ambos enfoques producen los mismos resultados. La elección depende de tus necesidades de rendimiento y tus preferencias para organizar el código.