Comment trier les chaînes de caractères alphabétiquement par locale en JavaScript
Utilisez Intl.Collator et localeCompare() pour trier correctement les chaînes dans n'importe quelle langue
Introduction
Lorsque vous triez un tableau de chaînes de caractères en JavaScript, le comportement par défaut compare les chaînes selon leurs valeurs d'unités de code UTF-16. Cela fonctionne pour le texte ASCII de base, mais échoue lors du tri de noms, de titres de produits ou de tout texte contenant des caractères accentués, des scripts non latins ou des lettres de casse mixte.
Différentes langues ont différentes règles pour l'ordre alphabétique. Le suédois place å, ä et ö à la fin de l'alphabet après z. L'allemand traite ä comme équivalent à a dans la plupart des contextes. Le français ignore les accents dans certains modes de comparaison. Ces règles linguistiques déterminent comment les gens s'attendent à voir des listes triées dans leur langue.
JavaScript fournit deux API pour le tri de chaînes adapté à la locale. La méthode String.prototype.localeCompare() gère les comparaisons simples. L'API Intl.Collator offre de meilleures performances lors du tri de grands tableaux. Cette leçon explique comment les deux fonctionnent, quand utiliser chacune d'elles et comment configurer le comportement de tri pour différentes langues.
Pourquoi le tri par défaut échoue pour le texte international
La méthode Array.sort() par défaut compare les chaînes selon leurs valeurs d'unités de code UTF-16. Cela signifie que les lettres majuscules viennent toujours avant les lettres minuscules, et les caractères accentués se trient après z.
const names = ['Åsa', 'Anna', 'Örjan', 'Bengt', 'Ärla'];
const sorted = names.sort();
console.log(sorted);
// Résultat: ['Anna', 'Bengt', 'Åsa', 'Ärla', 'Örjan']
Ce résultat est incorrect pour le suédois. En suédois, å, ä et ö sont des lettres distinctes qui appartiennent à la fin de l'alphabet. L'ordre correct devrait placer Anna en premier, puis Bengt, puis Åsa, Ärla et Örjan.
Le problème se produit car le tri par défaut compare les valeurs de points de code, et non la signification linguistique. La lettre Å a le point de code U+00C5, qui est supérieur au point de code pour z (U+007A). JavaScript n'a aucun moyen de savoir que les locuteurs suédois considèrent Å comme une lettre distincte avec une position spécifique dans l'alphabet.
La casse mixte crée un autre problème.
const words = ['zebra', 'Apple', 'banana', 'Zoo'];
const sorted = words.sort();
console.log(sorted);
// Résultat: ['Apple', 'Zoo', 'banana', 'zebra']
Toutes les lettres majuscules ont des valeurs de point de code inférieures aux lettres minuscules. Cela fait apparaître Apple et Zoo avant banana, ce qui n'est pas l'ordre alphabétique dans aucune langue.
Comment localeCompare trie les chaînes selon les règles linguistiques
La méthode localeCompare() compare deux chaînes selon les règles de tri d'une locale spécifique. Elle renvoie un nombre négatif si la première chaîne précède la seconde, zéro si elles sont équivalentes, et un nombre positif si la première chaîne suit la seconde.
const result = 'a'.localeCompare('b', 'en-US');
console.log(result);
// Résultat : -1 (négatif signifie que 'a' précède 'b')
Vous pouvez utiliser localeCompare() directement avec Array.sort() en la passant comme fonction de comparaison.
const names = ['Åsa', 'Anna', 'Örjan', 'Bengt', 'Ärla'];
const sorted = names.sort((a, b) => a.localeCompare(b, 'sv-SE'));
console.log(sorted);
// Résultat : ['Anna', 'Bengt', 'Åsa', 'Ärla', 'Örjan']
La locale suédoise place Anna et Bengt en premier car ils utilisent des lettres latines standard. Ensuite viennent Åsa, Ärla et Örjan avec leurs lettres spéciales suédoises à la fin.
La même liste triée avec une locale allemande produit des résultats différents.
const names = ['Åsa', 'Anna', 'Örjan', 'Bengt', 'Ärla'];
const sorted = names.sort((a, b) => a.localeCompare(b, 'de-DE'));
console.log(sorted);
// Résultat : ['Anna', 'Ärla', 'Åsa', 'Bengt', 'Örjan']
L'allemand traite ä comme équivalent à a pour le tri. Cela place Ärla juste après Anna, au lieu de le placer à la fin comme le fait le suédois.
Quand utiliser localeCompare
Utilisez localeCompare() lorsque vous devez trier un petit tableau ou comparer deux chaînes. Elle fournit une API simple sans nécessiter la création et la gestion d'un objet collator.
const items = ['Banana', 'apple', 'Cherry'];
const sorted = items.sort((a, b) => a.localeCompare(b, 'en-US'));
console.log(sorted);
// Résultat : ['apple', 'Banana', 'Cherry']
Cette approche fonctionne bien pour les tableaux contenant quelques dizaines d'éléments. L'impact sur les performances est négligeable pour les petits ensembles de données.
Vous pouvez également utiliser localeCompare() pour vérifier si une chaîne précède une autre sans trier un tableau entier.
const firstName = 'Åsa';
const secondName = 'Anna';
if (firstName.localeCompare(secondName, 'sv-SE') < 0) {
console.log(`${firstName} précède ${secondName}`);
} else {
console.log(`${secondName} précède ${firstName}`);
}
// Résultat : "Anna précède Åsa"
Cette comparaison respecte l'ordre alphabétique suédois sans avoir besoin de trier un tableau complet.
Comment Intl.Collator améliore les performances
L'API Intl.Collator crée une fonction de comparaison réutilisable optimisée pour une utilisation répétée. Lorsque vous triez de grands tableaux ou effectuez de nombreuses comparaisons, un collator est significativement plus rapide que l'appel à localeCompare() pour chaque comparaison.
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 propriété collator.compare renvoie une fonction de comparaison qui fonctionne directement avec Array.sort(). Vous n'avez pas besoin de l'encapsuler dans une fonction fléchée.
Créer un collator une fois et le réutiliser pour plusieurs opérations évite la surcharge liée à la recherche des données de locale à chaque comparaison.
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']
Le même collator gère les deux tableaux sans avoir besoin de créer une nouvelle instance.
Quand utiliser Intl.Collator
Utilisez Intl.Collator lors du tri de tableaux contenant des centaines ou des milliers d'éléments. Le bénéfice de performance augmente avec la taille du tableau car la fonction de comparaison est appelée de nombreuses fois pendant le tri.
const collator = new Intl.Collator('en-US');
const products = [/* tableau avec 10 000 noms de produits */];
const sorted = products.sort(collator.compare);
Pour les tableaux de plus de quelques centaines d'éléments, le collator peut être plusieurs fois plus rapide que localeCompare().
Utilisez également Intl.Collator lorsque vous devez trier plusieurs tableaux avec la même locale et les mêmes options. Créer le collator une fois et le réutiliser élimine les recherches répétées de données de locale.
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);
Ce modèle fonctionne bien lors de la construction de vues tabulaires ou d'autres interfaces qui affichent plusieurs listes triées.
Comment spécifier la locale
Les méthodes localeCompare() et Intl.Collator acceptent toutes deux un identifiant de locale comme premier argument. Cet identifiant utilise le format BCP 47, combinant généralement un code de langue et un code de région optionnel.
const names = ['Åsa', 'Anna', 'Ärla'];
// Locale suédoise
const swedishSorted = names.sort((a, b) => a.localeCompare(b, 'sv-SE'));
console.log(swedishSorted);
// Résultat: ['Anna', 'Åsa', 'Ärla']
// Locale allemande
const germanSorted = names.sort((a, b) => a.localeCompare(b, 'de-DE'));
console.log(germanSorted);
// Résultat: ['Anna', 'Ärla', 'Åsa']
La locale détermine quelles règles de collation s'appliquent. Le suédois et l'allemand ont des règles différentes pour å et ä, ce qui produit des ordres de tri différents.
Vous pouvez omettre la locale pour utiliser la locale par défaut de l'utilisateur depuis le navigateur.
const collator = new Intl.Collator();
const names = ['Åsa', 'Anna', 'Ärla'];
const sorted = names.sort(collator.compare);
Cette approche respecte les préférences linguistiques de l'utilisateur sans coder en dur une locale spécifique. L'ordre de tri correspondra à ce que l'utilisateur attend en fonction des paramètres de son navigateur.
Vous pouvez également passer un tableau de locales pour fournir des options de repli.
const collator = new Intl.Collator(['sv-SE', 'sv', 'en-US']);
L'API utilise la première locale prise en charge dans le tableau. Si le suédois de Suède n'est pas disponible, elle essaie le suédois générique, puis se replie sur l'anglais américain.
Comment contrôler la sensibilité à la casse
L'option sensitivity détermine comment la comparaison traite les différences de casse et d'accents. Elle accepte quatre valeurs : base, accent, case et variant.
La sensibilité base ignore à la fois la casse et les accents, comparant uniquement les caractères de base.
const collator = new Intl.Collator('en-US', { sensitivity: 'base' });
console.log(collator.compare('a', 'A'));
// Résultat: 0 (égal)
console.log(collator.compare('a', 'á'));
// Résultat: 0 (égal)
console.log(collator.compare('a', 'b'));
// Résultat: -1 (caractères de base différents)
Ce mode traite a, A et á comme identiques car ils partagent le même caractère de base.
La sensibilité accent prend en compte les accents mais ignore la casse.
const collator = new Intl.Collator('en-US', { sensitivity: 'accent' });
console.log(collator.compare('a', 'A'));
// Résultat: 0 (égal, casse ignorée)
console.log(collator.compare('a', 'á'));
// Résultat: -1 (différent, l'accent compte)
La sensibilité case prend en compte la casse mais ignore les accents.
const collator = new Intl.Collator('en-US', { sensitivity: 'case' });
console.log(collator.compare('a', 'A'));
// Résultat: -1 (différent, la casse compte)
console.log(collator.compare('a', 'á'));
// Résultat: 0 (égal, accent ignoré)
La sensibilité variant (par défaut) prend en compte toutes les différences.
const collator = new Intl.Collator('en-US', { sensitivity: 'variant' });
console.log(collator.compare('a', 'A'));
// Résultat: -1 (différent)
console.log(collator.compare('a', 'á'));
// Résultat: -1 (différent)
Ce mode fournit la comparaison la plus stricte, traitant toute différence comme significative.
Comment trier des chaînes contenant des nombres
L'option numeric permet le tri numérique pour les chaînes qui contiennent des nombres. Lorsqu'elle est activée, la comparaison traite les séquences de chiffres comme des valeurs numériques au lieu de les comparer caractère par caractère.
const files = ['file1.txt', 'file10.txt', 'file2.txt', 'file20.txt'];
// Tri par défaut (ordre incorrect)
const defaultSorted = [...files].sort();
console.log(defaultSorted);
// Résultat: ['file1.txt', 'file10.txt', 'file2.txt', 'file20.txt']
// Tri numérique (ordre correct)
const collator = new Intl.Collator('en-US', { numeric: true });
const numericSorted = files.sort(collator.compare);
console.log(numericSorted);
// Résultat: ['file1.txt', 'file2.txt', 'file10.txt', 'file20.txt']
Sans tri numérique, les chaînes sont triées caractère par caractère. La chaîne 10 vient avant 2 car le premier caractère 1 a un point de code inférieur à 2.
Avec le tri numérique activé, le collator reconnaît 10 comme le nombre dix et 2 comme le nombre deux. Cela produit l'ordre de tri attendu où 2 vient avant 10.
Cette option est utile pour trier des noms de fichiers, des numéros de version ou toute chaîne qui mélange texte et nombres.
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);
// Résultat: ['v1.2', 'v1.3', 'v1.10', 'v1.20']
Comment contrôler quelle casse vient en premier
L'option caseFirst détermine si les lettres majuscules ou minuscules sont triées en premier lors de la comparaison de chaînes qui ne diffèrent que par la casse. Elle accepte trois valeurs : upper, lower ou false.
const words = ['apple', 'Apple', 'APPLE'];
// Majuscules d'abord
const upperFirst = new Intl.Collator('en-US', { caseFirst: 'upper' });
const upperSorted = [...words].sort(upperFirst.compare);
console.log(upperSorted);
// Résultat: ['APPLE', 'Apple', 'apple']
// Minuscules d'abord
const lowerFirst = new Intl.Collator('en-US', { caseFirst: 'lower' });
const lowerSorted = [...words].sort(lowerFirst.compare);
console.log(lowerSorted);
// Résultat: ['apple', 'Apple', 'APPLE']
// Par défaut (dépend de la locale)
const defaultCase = new Intl.Collator('en-US', { caseFirst: 'false' });
const defaultSorted = [...words].sort(defaultCase.compare);
console.log(defaultSorted);
// Le résultat dépend de la locale
La valeur false utilise l'ordre de casse par défaut de la locale. La plupart des locales traitent les chaînes qui ne diffèrent que par la casse comme égales lorsqu'on utilise les paramètres de sensibilité par défaut.
Cette option n'a d'effet que lorsque l'option sensitivity permet aux différences de casse d'être prises en compte.
Comment ignorer la ponctuation dans le tri
L'option ignorePunctuation indique au collator d'ignorer les signes de ponctuation lors de la comparaison de chaînes. Cela peut être utile lors du tri de titres ou de phrases qui peuvent inclure ou non de la ponctuation.
const titles = [
'The Old Man',
'The Old-Man',
'The Oldman',
];
// Par défaut (la ponctuation compte)
const defaultCollator = new Intl.Collator('en-US');
const defaultSorted = [...titles].sort(defaultCollator.compare);
console.log(defaultSorted);
// Résultat : ['The Old Man', 'The Old-Man', 'The Oldman']
// Ignorer la ponctuation
const noPunctCollator = new Intl.Collator('en-US', { ignorePunctuation: true });
const noPunctSorted = [...titles].sort(noPunctCollator.compare);
console.log(noPunctSorted);
// Résultat : ['The Old Man', 'The Old-Man', 'The Oldman']
Lorsque la ponctuation est ignorée, la comparaison traite le trait d'union dans "Old-Man" comme s'il n'existait pas, faisant en sorte que les chaînes soient comparées comme si elles étaient toutes "TheOldMan".
Tri des noms d'utilisateurs de différents pays
Lors du tri des noms d'utilisateurs du monde entier, utilisez la locale préférée de l'utilisateur pour respecter ses attentes linguistiques.
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})`);
});
Ce code détecte la locale de l'utilisateur depuis le navigateur et trie les noms en conséquence. Un utilisateur allemand voit la liste triée selon les règles allemandes, tandis qu'un utilisateur suédois la voit triée selon les règles suédoises.
Tri avec changement de locale
Lorsque votre application permet aux utilisateurs de changer de langue, mettez à jour le collator lorsque la locale change.
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);
}
// L'utilisateur passe au suédois
setLocale('sv-SE');
const names = ['Åsa', 'Anna', 'Örjan'];
console.log(sortItems(names));
// Résultat : ['Anna', 'Åsa', 'Örjan']
// L'utilisateur passe à l'allemand
setLocale('de-DE');
const germanNames = ['Über', 'Uhr', 'Udo'];
console.log(sortItems(germanNames));
// Résultat : ['Udo', 'Uhr', 'Über']
Ce modèle garantit que les listes triées sont mises à jour pour correspondre à la langue sélectionnée par l'utilisateur.
Choisir entre localeCompare et Intl.Collator
Utilisez localeCompare() lorsque vous avez besoin d'une comparaison ponctuelle rapide ou que vous triez un petit tableau de moins de 100 éléments. La syntaxe plus simple est plus facile à lire et la différence de performance est négligeable pour les petits ensembles de données.
const items = ['banana', 'Apple', 'cherry'];
const sorted = items.sort((a, b) => a.localeCompare(b, 'en-US'));
Utilisez Intl.Collator lors du tri de grands tableaux, de l'exécution de nombreuses comparaisons ou du tri de plusieurs tableaux avec les mêmes paramètres régionaux et options. Créer le collator une seule fois et le réutiliser offre de meilleures performances.
const collator = new Intl.Collator('en-US', { sensitivity: 'base', numeric: true });
const products = [/* grand tableau */];
const sorted = products.sort(collator.compare);
Les deux approches produisent les mêmes résultats. Le choix dépend de vos exigences de performance et de vos préférences d'organisation du code.