Cómo ordenar cadenas alfabéticamente por configuración regional en JavaScript
Usa Intl.Collator y localeCompare() para ordenar cadenas correctamente en cualquier idioma
Introducción
Cuando ordenas un array de strings en JavaScript, el comportamiento predeterminado compara las cadenas por sus valores de unidad de código UTF-16. Esto funciona para texto ASCII básico, pero falla al ordenar nombres, títulos de productos o cualquier texto que contenga caracteres acentuados, escrituras no latinas o letras con mezcla de mayúsculas y minúsculas.
Diferentes idiomas tienen diferentes reglas para el orden alfabético. El sueco coloca å, ä y ö al final del alfabeto después de la z. El alemán trata ä como equivalente a a en la mayoría de los contextos. El francés ignora los acentos en ciertos modos de comparación. Estas reglas lingüísticas determinan cómo las personas esperan ver las listas ordenadas en su idioma.
JavaScript proporciona dos APIs para la ordenación de cadenas según la configuración regional. El método String.prototype.localeCompare() maneja comparaciones simples. La API Intl.Collator ofrece mejor rendimiento al ordenar arrays grandes. Esta lección explica cómo funcionan ambos, cuándo usar cada uno y cómo configurar el comportamiento de ordenación para diferentes idiomas.
Por qué la ordenación predeterminada falla para texto internacional
El método predeterminado Array.sort() compara cadenas por sus valores de unidad de código UTF-16. Esto significa 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);
// Resultado: ['Anna', 'Bengt', 'Åsa', 'Ärla', 'Örjan']
Este resultado es incorrecto para el sueco. En sueco, å, ä y ö son letras separadas que pertenecen al final del alfabeto. El orden correcto debería colocar a Anna primero, luego Bengt, luego Åsa, Ärla y Örjan.
El problema ocurre porque la ordenación predeterminada compara valores de punto de código, no significado lingüístico. La letra Å tiene el punto de código U+00C5, que es mayor que el punto de código para z (U+007A). JavaScript no tiene forma de saber que los hablantes de sueco consideran Å una letra separada con una posición específica en el alfabeto.
La mezcla de mayúsculas y minúsculas crea otro problema.
const words = ['zebra', 'Apple', 'banana', 'Zoo'];
const sorted = words.sort();
console.log(sorted);
// Resultado: ['Apple', 'Zoo', 'banana', 'zebra']
Todas las letras mayúsculas tienen valores de punto de código más bajos que las letras minúsculas. Esto hace que Apple y Zoo aparezcan antes que banana, lo que no es un orden alfabético en ningún idioma.
Cómo localeCompare ordena cadenas según reglas lingüísticas
El método localeCompare() compara dos cadenas según las reglas de ordenación de una localidad específica. 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 cadena va después de la segunda.
const result = 'a'.localeCompare('b', 'en-US');
console.log(result);
// Salida: -1 (negativo significa que 'a' va antes que 'b')
Puedes usar localeCompare() directamente con Array.sort() pasándolo 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);
// Salida: ['Anna', 'Bengt', 'Åsa', 'Ärla', 'Örjan']
La localidad sueca coloca Anna y Bengt primero porque usan letras latinas estándar. Luego vienen Åsa, Ärla y Örjan con sus letras especiales suecas al final.
La misma lista ordenada con una localidad alemana produce resultados diferentes.
const names = ['Åsa', 'Anna', 'Örjan', 'Bengt', 'Ärla'];
const sorted = names.sort((a, b) => a.localeCompare(b, 'de-DE'));
console.log(sorted);
// Salida: ['Anna', 'Ärla', 'Åsa', 'Bengt', 'Örjan']
El alemán trata ä como equivalente a a para propósitos de ordenación. Esto coloca Ärla justo después de Anna, en lugar de al final como hace el sueco.
Cuándo usar localeCompare
Usa localeCompare() cuando necesites ordenar un array pequeño o comparar dos cadenas. Proporciona una API simple sin requerir que crees y gestiones un objeto collator.
const items = ['Banana', 'apple', 'Cherry'];
const sorted = items.sort((a, b) => a.localeCompare(b, 'en-US'));
console.log(sorted);
// Salida: ['apple', 'Banana', 'Cherry']
Este enfoque funciona bien para arrays con unas pocas docenas de elementos. El impacto en el rendimiento es insignificante para conjuntos de datos pequeños.
También puedes usar localeCompare() para verificar si una cadena va antes que otra sin ordenar un array completo.
const firstName = 'Åsa';
const secondName = 'Anna';
if (firstName.localeCompare(secondName, 'sv-SE') < 0) {
console.log(`${firstName} va antes que ${secondName}`);
} else {
console.log(`${secondName} va antes que ${firstName}`);
}
// Salida: "Anna va antes que Åsa"
Esta comparación respeta el orden alfabético sueco sin necesidad de ordenar un array completo.
Cómo Intl.Collator mejora el rendimiento
La API Intl.Collator crea una función de comparación reutilizable optimizada para uso repetido. Cuando ordenas arrays grandes o realizas muchas comparaciones, un collator es significativamente más rápido que llamar a localeCompare() para 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 flecha.
Crear un collator una vez y reutilizarlo para múltiples operaciones evita la sobrecarga de buscar 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 maneja ambos arrays sin necesidad de crear una nueva instancia.
Cuándo usar Intl.Collator
Usa Intl.Collator cuando ordenes arrays con cientos o miles de elementos. El beneficio de rendimiento aumenta con el tamaño del array 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 con 10,000 nombres de productos */];
const sorted = products.sort(collator.compare);
Para arrays con 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 necesites ordenar múltiples arrays con la misma configuración regional y opciones. Crear el collator una vez y reutilizarlo elimina las 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 bien cuando se construyen vistas de tablas u otras interfaces que muestran múltiples listas ordenadas.
Cómo especificar la configuración regional
Tanto localeCompare() como Intl.Collator aceptan un identificador de configuración regional como primer argumento. Este identificador utiliza el formato BCP 47, que generalmente combina un código de idioma y un código de región opcional.
const names = ['Åsa', 'Anna', 'Ärla'];
// Configuración regional sueca
const swedishSorted = names.sort((a, b) => a.localeCompare(b, 'sv-SE'));
console.log(swedishSorted);
// Resultado: ['Anna', 'Åsa', 'Ärla']
// Configuración regional alemana
const germanSorted = names.sort((a, b) => a.localeCompare(b, 'de-DE'));
console.log(germanSorted);
// Resultado: ['Anna', 'Ärla', 'Åsa']
La configuración regional determina qué reglas de ordenación se aplican. El sueco y el alemán tienen diferentes reglas para å y ä, lo que produce diferentes órdenes de clasificación.
Puedes omitir la configuración regional para usar la configuración predeterminada 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 codificar una configuración regional específica. El orden clasificado coincidirá con lo que el usuario espera según la configuración de su navegador.
También puedes pasar un array de configuraciones regionales para proporcionar opciones alternativas.
const collator = new Intl.Collator(['sv-SE', 'sv', 'en-US']);
La API utiliza la primera configuración regional compatible del array. Si el sueco de Suecia no está disponible, intenta con el sueco genérico y luego recurre al inglés estadounidense.
Cómo controlar la sensibilidad a mayúsculas y minúsculas
La opción sensitivity determina cómo la comparación trata las diferencias en mayúsculas/minúsculas y acentos. Acepta cuatro valores: base, accent, case y variant.
La sensibilidad base ignora tanto las mayúsculas/minúsculas como los acentos, comparando solo los caracteres base.
const collator = new Intl.Collator('en-US', { sensitivity: 'base' });
console.log(collator.compare('a', 'A'));
// Resultado: 0 (iguales)
console.log(collator.compare('a', 'á'));
// Resultado: 0 (iguales)
console.log(collator.compare('a', 'b'));
// Resultado: -1 (caracteres base diferentes)
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/minúsculas.
const collator = new Intl.Collator('en-US', { sensitivity: 'accent' });
console.log(collator.compare('a', 'A'));
// Resultado: 0 (iguales, mayúsculas/minúsculas ignoradas)
console.log(collator.compare('a', 'á'));
// Resultado: -1 (diferentes, los acentos importan)
La sensibilidad case considera las mayúsculas/minúsculas pero ignora los acentos.
const collator = new Intl.Collator('en-US', { sensitivity: 'case' });
console.log(collator.compare('a', 'A'));
// Resultado: -1 (diferentes, las mayúsculas/minúsculas importan)
console.log(collator.compare('a', 'á'));
// Resultado: 0 (iguales, acentos ignorados)
La sensibilidad variant (la predeterminada) considera todas las diferencias.
const collator = new Intl.Collator('en-US', { sensitivity: 'variant' });
console.log(collator.compare('a', 'A'));
// Resultado: -1 (diferentes)
console.log(collator.compare('a', 'á'));
// Resultado: -1 (diferentes)
Este modo proporciona la comparación más estricta, tratando cualquier diferencia como significativa.
Cómo ordenar cadenas con números incrustados
La opción numeric habilita la ordenación numérica para cadenas que contienen números. Cuando está habilitada, la comparación trata las secuencias de dígitos como valores numéricos en lugar de compararlos carácter por carácter.
const files = ['file1.txt', 'file10.txt', 'file2.txt', 'file20.txt'];
// Ordenación predeterminada (orden incorrecto)
const defaultSorted = [...files].sort();
console.log(defaultSorted);
// Salida: ['file1.txt', 'file10.txt', 'file2.txt', 'file20.txt']
// Ordenación numérica (orden correcto)
const collator = new Intl.Collator('en-US', { numeric: true });
const numericSorted = files.sort(collator.compare);
console.log(numericSorted);
// Salida: ['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 viene antes que 2 porque el primer carácter 1 tiene un punto de código menor que 2.
Con la ordenación numérica habilitada, el comparador reconoce 10 como el número diez y 2 como el número dos. Esto produce el orden de clasificación esperado donde 2 viene antes que 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);
// Salida: ['v1.2', 'v1.3', 'v1.10', 'v1.20']
Cómo controlar qué tipo de letra viene primero
La opción caseFirst determina si las letras mayúsculas o minúsculas se ordenan primero al comparar cadenas que difieren solo en el tipo de letra. Acepta tres valores: upper, lower o false.
const words = ['apple', 'Apple', 'APPLE'];
// Mayúsculas primero
const upperFirst = new Intl.Collator('en-US', { caseFirst: 'upper' });
const upperSorted = [...words].sort(upperFirst.compare);
console.log(upperSorted);
// Salida: ['APPLE', 'Apple', 'apple']
// Minúsculas primero
const lowerFirst = new Intl.Collator('en-US', { caseFirst: 'lower' });
const lowerSorted = [...words].sort(lowerFirst.compare);
console.log(lowerSorted);
// Salida: ['apple', 'Apple', 'APPLE']
// Predeterminado (dependiente de la configuración regional)
const defaultCase = new Intl.Collator('en-US', { caseFirst: 'false' });
const defaultSorted = [...words].sort(defaultCase.compare);
console.log(defaultSorted);
// La salida depende de la configuración regional
El valor false utiliza el orden de casos predeterminado de la configuración regional. La mayoría de las configuraciones regionales tratan las cadenas que difieren solo en el tipo de letra como iguales cuando se utilizan configuraciones de sensibilidad predeterminadas.
Esta opción solo tiene efecto cuando la opción sensitivity permite que las diferencias de tipo de letra sean relevantes.
Cómo ignorar la puntuación en la ordenación
La opción ignorePunctuation indica al ordenador que omita los signos de puntuación al comparar cadenas. Esto puede ser útil cuando se ordenan títulos o frases que pueden incluir o no signos de puntuación.
const titles = [
'The Old Man',
'The Old-Man',
'The Oldman',
];
// Predeterminado (la puntuación importa)
const defaultCollator = new Intl.Collator('en-US');
const defaultSorted = [...titles].sort(defaultCollator.compare);
console.log(defaultSorted);
// Salida: ['The Old Man', 'The Old-Man', 'The Oldman']
// Ignorar puntuación
const noPunctCollator = new Intl.Collator('en-US', { ignorePunctuation: true });
const noPunctSorted = [...titles].sort(noPunctCollator.compare);
console.log(noPunctSorted);
// Salida: ['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 todas fueran "TheOldMan".
Ordenación de nombres de usuarios de diferentes países
Al ordenar nombres de usuarios de todo el mundo, utiliza la configuración regional preferida del 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 la configuración regional del usuario desde el navegador y ordena los nombres en consecuencia. Un usuario alemán ve la lista ordenada según las reglas alemanas, mientras que un usuario sueco la ve ordenada según las reglas suecas.
Ordenación con cambio de configuración regional
Cuando tu aplicación permite a los usuarios cambiar de idioma, actualiza el ordenador cuando cambie la configuración regional.
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);
}
// El usuario cambia a sueco
setLocale('sv-SE');
const names = ['Åsa', 'Anna', 'Örjan'];
console.log(sortItems(names));
// Salida: ['Anna', 'Åsa', 'Örjan']
// El usuario cambia a alemán
setLocale('de-DE');
const germanNames = ['Über', 'Uhr', 'Udo'];
console.log(sortItems(germanNames));
// Salida: ['Udo', 'Uhr', 'Über']
Este patrón garantiza 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 una comparación rápida ocasional o estés ordenando un array pequeño con menos de 100 elementos. La sintaxis más simple es más fácil de leer 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 arrays grandes, realices muchas comparaciones, u ordenes múltiples arrays con la misma configuración regional y opciones. Crear el comparador una vez y reutilizarlo proporciona mejor rendimiento.
const collator = new Intl.Collator('en-US', { sensitivity: 'base', numeric: true });
const products = [/* array grande */];
const sorted = products.sort(collator.compare);
Ambos enfoques producen los mismos resultados. La elección depende de tus requisitos de rendimiento y preferencias de organización del código.