Comparer des chaînes en ignorant les signes diacritiques
Apprenez à comparer des chaînes en ignorant les signes diacritiques à l'aide de la normalisation JavaScript et d'Intl.Collator
Introduction
Lors de la création d'applications fonctionnant avec plusieurs langues, vous devez souvent comparer des chaînes contenant des signes diacritiques. 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 de chaînes standard les traite 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 signes diacritiques, 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 signes diacritiques
Les signes diacritiques 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 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 manières. 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 signe diacritique séparé. La lettre "é" peut être stockée comme U+00E9 ou comme "e" (U+0065) plus un accent aigu combinant (U+0301).
Quand ignorer les signes diacritiques dans les comparaisons
La fonctionnalité de recherche est le cas d'usage le plus courant pour la comparaison insensible aux accents. Les utilisateurs saisissant des requêtes sans signes diacritiques s'attendent à trouver du contenu contenant des caractères accentués. Une recherche de "Muller" devrait trouver "Müller".
La validation des entrées utilisateur nécessite cette capacité lors de la vérification de l'existence de noms d'utilisateur, d'adresses e-mail ou d'autres identifiants. Vous souhaitez empêcher les comptes en double pour "maria" et "maría".
Les comparaisons insensibles à la casse nécessitent souvent d'ignorer les accents en même temps. Lorsque vous vérifiez si deux chaînes correspondent indépendamment de la capitalisation, vous souhaitez généralement ignorer également les différences d'accents.
Supprimer les signes diacritiques à 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 signes diacritiques sont séparés, puis supprime les signes diacritiques.
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 leurs marques combinatoires. La chaîne "café" devient "cafe" suivie d'un caractère accent aigu combinatoire.
Après 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 signes diacritiques que vous pouvez comparer à l'aide des 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 sensible aux paramètres régionaux 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 comptent lors de la comparaison de chaînes.
Le niveau de sensibilité "base" ignore à la fois les signes diacritiques et les différences de casse. Les chaînes qui ne diffèrent que 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 (equal)
console.log(collator.compare('Café', 'cafe')); // 0 (equal)
console.log(collator.compare('café', 'caff')); // -1 (first comes before 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 ceci pour les 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); // Groups variants together
L'API Intl.Collator fournit d'autres niveaux de sensibilité pour différents cas d'usage.
Le niveau "accent" ignore la casse mais respecte les différences d'accents. "Café" est égal à "café" mais pas à "cafe".
const accentCollator = new Intl.Collator('en', { sensitivity: 'accent' });
console.log(accentCollator.compare('Café', 'café')); // 0 (equal)
console.log(accentCollator.compare('café', 'cafe')); // 1 (not equal)
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 (equal)
console.log(caseCollator.compare('café', 'Café')); // -1 (not equal)
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 (not equal)
Choisir entre normalisation et collation
Les deux méthodes produisent des résultats corrects pour la comparaison insensible aux accents, mais elles ont 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 des chaînes directement, comme pour vérifier les doublons ou trier des listes. Le collator respecte les règles de tri spécifiques à chaque langue que la simple comparaison de chaînes ne peut pas gérer.
Les considérations de performance varient selon le cas d'usage. Créer un objet collator une fois et le réutiliser est efficace pour plusieurs comparaisons. Normaliser des chaînes est efficace lorsque vous normalisez une fois et comparez plusieurs fois.
La méthode de normalisation supprime définitivement les informations d'accentuation. La méthode de collation préserve les chaînes originales tout en les comparant selon les règles que vous spécifiez.
Filtrer des tableaux avec une recherche insensible aux accents
Un cas d'usage courant consiste à filtrer un tableau d'éléments en fonction de la saisie utilisateur, en ignorant les différences d'accents.
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'));
// Returns both Café Latte and Café Mocha
Pour la correspondance de sous-chaînes, l'approche par 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'));
// Returns 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 de saisie de texte
Lors de la validation de la saisie 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 des comptes existants que par les accents ou la casse.
Support des navigateurs et 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 avez besoin de polyfills ou d'implémentations alternatives.
Limites de la suppression des accents
Certaines langues utilisent des signes diacritiques pour créer des lettres distinctes, et non simplement des variations d'accents. En turc, "i" et "ı" sont des lettres différentes. En allemand, "ö" est une voyelle distincte, et non un "o" accentué.
La suppression des accents modifie le sens dans ces cas. Déterminez si la comparaison insensible aux accents est appropriée pour votre cas d'usage et vos langues cibles.
L'approche par collation gère mieux ces cas car elle suit des règles spécifiques aux paramètres régionaux. Spécifier les paramètres régionaux corrects 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' });
Tenez toujours compte des langues prises en charge par votre application lors du choix d'une stratégie de comparaison.