Comparer des chaînes de caractères en ignorant les accents
Apprenez à comparer des chaînes de caractères tout en ignorant les signes diacritiques en utilisant la normalisation JavaScript et Intl.Collator
Introduction
Lors de la création d'applications qui fonctionnent avec plusieurs langues, vous avez souvent besoin de comparer des chaînes de caractères contenant des accents. Un utilisateur recherchant "cafe" devrait trouver des résultats pour "café". Une vérification de nom d'utilisateur pour "Jose" devrait correspondre à "José". La comparaison standard de chaînes traite ces éléments comme des chaînes différentes, mais la logique de votre application doit les traiter comme égales.
JavaScript propose deux approches pour résoudre ce problème. Vous pouvez normaliser les chaînes et supprimer les accents, ou utiliser l'API de collation intégrée pour comparer les chaînes avec des règles de sensibilité spécifiques.
Que sont les accents
Les accents sont des symboles placés au-dessus, en dessous ou à travers les lettres pour modifier leur prononciation ou leur signification. Ces marques sont appelées signes diacritiques. Les exemples courants incluent l'accent aigu dans "é", le tilde dans "ñ" et le tréma dans "ü".
En Unicode, ces caractères peuvent être représentés de deux façons. Un seul point de code peut représenter le caractère complet, ou plusieurs points de code peuvent combiner une lettre de base avec un accent séparé. La lettre "é" peut être stockée comme U+00E9 ou comme "e" (U+0065) plus un accent aigu combiné (U+0301).
Quand ignorer les accents dans les comparaisons
La fonctionnalité de recherche est le cas d'utilisation le plus courant pour la comparaison insensible aux accents. Les utilisateurs qui tapent des requêtes sans accents s'attendent à trouver du contenu contenant des caractères accentués. Une recherche pour "Muller" devrait trouver "Müller".
La validation des entrées utilisateur nécessite cette capacité lors de la vérification si des noms d'utilisateur, des adresses e-mail ou d'autres identifiants existent déjà. Vous voulez éviter les comptes en double pour "maria" et "maría".
Les comparaisons insensibles à la casse doivent souvent ignorer les accents en même temps. Lorsque vous vérifiez si deux chaînes correspondent indépendamment de la capitalisation, vous voulez généralement ignorer également les différences d'accent.
Supprimer les accents à l'aide de la normalisation
La première approche convertit les chaînes en une forme normalisée où les lettres de base et les marques d'accent sont séparées, puis supprime les marques d'accent.
La normalisation Unicode convertit les chaînes en une forme standard. La forme NFD (Décomposition Canonique) sépare les caractères combinés en leurs lettres de base et marques combinatoires. La chaîne "café" devient "cafe" suivie d'un caractère d'accent aigu combinatoire.
Après la normalisation, vous pouvez supprimer les marques combinatoires à l'aide d'une expression régulière. La plage Unicode U+0300 à U+036F contient les marques diacritiques combinatoires.
function removeAccents(str) {
return str.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
}
const text1 = 'café';
const text2 = 'cafe';
const normalized1 = removeAccents(text1);
const normalized2 = removeAccents(text2);
console.log(normalized1 === normalized2); // true
console.log(normalized1); // "cafe"
Cette méthode vous donne des chaînes sans accents que vous pouvez comparer à l'aide d'opérateurs d'égalité standard.
Vous pouvez combiner cela avec la conversion en minuscules pour des comparaisons insensibles à la casse et aux accents.
function normalizeForComparison(str) {
return str.normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase();
}
const search = 'muller';
const name = 'Müller';
console.log(normalizeForComparison(search) === normalizeForComparison(name)); // true
Cette approche fonctionne bien lorsque vous devez stocker ou indexer la version normalisée des chaînes pour une recherche efficace.
Comparer des chaînes à l'aide d'Intl.Collator
La deuxième approche utilise l'API Intl.Collator, qui fournit une comparaison de chaînes tenant compte de la locale avec des niveaux de sensibilité configurables.
L'objet Intl.Collator compare les chaînes selon des règles spécifiques à la langue. L'option de sensibilité contrôle quelles différences importent lors de la comparaison de chaînes.
Le niveau de sensibilité "base" ignore à la fois les marques d'accent et les différences de casse. Les chaînes qui diffèrent uniquement par les accents ou la capitalisation sont considérées comme égales.
const collator = new Intl.Collator('en', { sensitivity: 'base' });
console.log(collator.compare('café', 'cafe')); // 0 (égal)
console.log(collator.compare('Café', 'cafe')); // 0 (égal)
console.log(collator.compare('café', 'caff')); // -1 (le premier vient avant le second)
La méthode compare renvoie 0 lorsque les chaînes sont égales, un nombre négatif lorsque la première chaîne vient avant la seconde, et un nombre positif lorsque la première chaîne vient après la seconde.
Vous pouvez utiliser cela pour des vérifications d'égalité ou pour trier des tableaux.
const collator = new Intl.Collator('en', { sensitivity: 'base' });
function areEqualIgnoringAccents(str1, str2) {
return collator.compare(str1, str2) === 0;
}
console.log(areEqualIgnoringAccents('José', 'Jose')); // true
console.log(areEqualIgnoringAccents('naïve', 'naive')); // true
Pour le tri, vous pouvez passer la méthode compare directement à Array.sort.
const names = ['Müller', 'Martinez', 'Muller', 'Márquez'];
const collator = new Intl.Collator('en', { sensitivity: 'base' });
names.sort(collator.compare);
console.log(names); // Regroupe les variantes ensemble
L'API Intl.Collator fournit d'autres niveaux de sensibilité pour différents cas d'utilisation.
Le niveau "accent" ignore la casse mais respecte les différences d'accent. "Café" est égal à "café" mais pas à "cafe".
const accentCollator = new Intl.Collator('en', { sensitivity: 'accent' });
console.log(accentCollator.compare('Café', 'café')); // 0 (égal)
console.log(accentCollator.compare('café', 'cafe')); // 1 (non égal)
Le niveau "case" ignore les accents mais respecte les différences de casse. "café" est égal à "cafe" mais pas à "Café".
const caseCollator = new Intl.Collator('en', { sensitivity: 'case' });
console.log(caseCollator.compare('café', 'cafe')); // 0 (égal)
console.log(caseCollator.compare('café', 'Café')); // -1 (non égal)
Le niveau "variant" respecte toutes les différences. C'est le comportement par défaut.
const variantCollator = new Intl.Collator('en', { sensitivity: 'variant' });
console.log(variantCollator.compare('café', 'cafe')); // 1 (non égal)
Choisir entre la normalisation et le tri
Les deux méthodes produisent des résultats corrects pour la comparaison insensible aux accents, mais elles présentent des caractéristiques différentes.
La méthode de normalisation crée de nouvelles chaînes sans signes diacritiques. Utilisez cette approche lorsque vous devez stocker ou indexer les versions normalisées. Les moteurs de recherche et les bases de données stockent souvent du texte normalisé pour une recherche efficace.
La méthode Intl.Collator compare les chaînes sans les modifier. Utilisez cette approche lorsque vous devez comparer directement des chaînes, comme pour vérifier les doublons ou trier des listes. Le collator respecte les règles de tri spécifiques à la langue que la simple comparaison de chaînes ne peut pas gérer.
Les considérations de performance varient selon le cas d'utilisation. Créer un objet collator une fois et le réutiliser est efficace pour plusieurs comparaisons. Normaliser les chaînes est efficace lorsque vous normalisez une fois et comparez plusieurs fois.
La méthode de normalisation supprime définitivement les informations d'accent. La méthode de tri préserve les chaînes originales tout en les comparant selon les règles que vous spécifiez.
Filtrer les tableaux en utilisant une recherche insensible aux accents
Un cas d'utilisation courant consiste à filtrer un tableau d'éléments en fonction de l'entrée de l'utilisateur, en ignorant les différences d'accent.
const products = [
{ name: 'Café Latte', price: 4.50 },
{ name: 'Crème Brûlée', price: 6.00 },
{ name: 'Croissant', price: 3.00 },
{ name: 'Café Mocha', price: 5.00 }
];
function searchProducts(query) {
const collator = new Intl.Collator('en', { sensitivity: 'base' });
return products.filter(product => {
return collator.compare(product.name.slice(0, query.length), query) === 0;
});
}
console.log(searchProducts('cafe'));
// Renvoie à la fois Café Latte et Café Mocha
Pour la correspondance de sous-chaînes, l'approche de normalisation fonctionne mieux.
function removeAccents(str) {
return str.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
}
function searchProducts(query) {
const normalizedQuery = removeAccents(query.toLowerCase());
return products.filter(product => {
const normalizedName = removeAccents(product.name.toLowerCase());
return normalizedName.includes(normalizedQuery);
});
}
console.log(searchProducts('creme'));
// Renvoie Crème Brûlée
Cette approche vérifie si le nom de produit normalisé contient la requête de recherche normalisée en tant que sous-chaîne.
Gérer la correspondance des saisies de texte
Lors de la validation des saisies utilisateur par rapport aux données existantes, vous avez besoin d'une comparaison insensible aux accents pour éviter la confusion et les doublons.
const existingUsernames = ['José', 'María', 'François'];
function isUsernameTaken(username) {
const collator = new Intl.Collator('en', { sensitivity: 'base' });
return existingUsernames.some(existing =>
collator.compare(existing, username) === 0
);
}
console.log(isUsernameTaken('jose')); // true
console.log(isUsernameTaken('Maria')); // true
console.log(isUsernameTaken('francois')); // true
console.log(isUsernameTaken('pierre')); // false
Cela empêche les utilisateurs de créer des comptes avec des noms qui ne diffèrent que par les accents ou la casse des comptes existants.
Compatibilité avec les navigateurs et les environnements
La méthode String.prototype.normalize est prise en charge dans tous les navigateurs modernes et les environnements Node.js. Internet Explorer ne prend pas en charge cette méthode.
L'API Intl.Collator est prise en charge dans tous les navigateurs modernes et les versions de Node.js. Internet Explorer 11 inclut une prise en charge partielle.
Les deux approches fonctionnent de manière fiable dans les environnements JavaScript actuels. Si vous devez prendre en charge des navigateurs plus anciens, vous aurez besoin de polyfills ou d'implémentations alternatives.
Limitations de la suppression des accents
Certaines langues utilisent des signes diacritiques pour créer des lettres distinctes, pas seulement des variations d'accent. En turc, "i" et "ı" sont des lettres différentes. En allemand, "ö" est une voyelle distincte, pas un "o" accentué.
La suppression des accents modifie le sens dans ces cas. Considérez si la comparaison insensible aux accents est appropriée pour votre cas d'utilisation et les langues cibles.
L'approche de collation gère mieux ces cas car elle suit des règles spécifiques à la locale. Spécifier la locale correcte dans le constructeur Intl.Collator garantit des comparaisons culturellement appropriées.
const turkishCollator = new Intl.Collator('tr', { sensitivity: 'base' });
const germanCollator = new Intl.Collator('de', { sensitivity: 'base' });
Prenez toujours en compte les langues que votre application prend en charge lors du choix d'une stratégie de comparaison.