Comment comparer des chaînes en ignorant les différences de casse
Utilisez la comparaison adaptée à la locale pour gérer correctement la correspondance insensible à la casse dans différentes langues
Introduction
La comparaison de chaînes de caractères sans tenir compte de la casse apparaît fréquemment dans les applications web. Les utilisateurs saisissent des requêtes de recherche en casse mixte, entrent des noms d'utilisateur avec une capitalisation incohérente, ou remplissent des formulaires sans tenir compte de la casse des lettres. Votre application doit faire correspondre ces entrées correctement, que les utilisateurs tapent en majuscules, minuscules ou casse mixte.
L'approche simple consiste à convertir les deux chaînes en minuscules et à les comparer. Cela fonctionne pour le texte en anglais mais échoue pour les applications internationales. Différentes langues ont différentes règles pour convertir entre majuscules et minuscules. Une méthode de comparaison qui fonctionne pour l'anglais peut produire des résultats erronés pour le turc, l'allemand, le grec ou d'autres langues.
JavaScript fournit l'API Intl.Collator pour gérer correctement la comparaison insensible à la casse dans toutes les langues. Cette leçon explique pourquoi la simple conversion en minuscules échoue, comment fonctionne la comparaison adaptée à la locale, et quand utiliser chaque approche.
L'approche naïve avec toLowerCase
Convertir les deux chaînes en minuscules avant la comparaison est l'approche la plus courante pour la correspondance insensible à la casse :
const str1 = "Hello";
const str2 = "HELLO";
console.log(str1.toLowerCase() === str2.toLowerCase());
// true
Ce modèle fonctionne pour le texte ASCII et les mots anglais. La comparaison traite les versions majuscules et minuscules de la même lettre comme identiques.
Vous pouvez utiliser cette approche pour la recherche approximative :
const query = "apple";
const items = ["Apple", "Banana", "APPLE PIE", "Orange"];
const matches = items.filter(item =>
item.toLowerCase().includes(query.toLowerCase())
);
console.log(matches);
// ["Apple", "APPLE PIE"]
Le filtre trouve tous les éléments contenant la requête de recherche indépendamment de la casse. Cela fournit le comportement attendu pour les utilisateurs qui tapent des requêtes sans penser à la capitalisation.
Pourquoi l'approche naïve échoue pour le texte international
La méthode toLowerCase() convertit le texte selon les règles Unicode, mais ces règles ne fonctionnent pas de la même manière dans toutes les langues. L'exemple le plus célèbre est le problème du i turc.
En anglais, la lettre minuscule i se convertit en majuscule I. En turc, il existe deux lettres distinctes :
- Le
iminuscule avec point se convertit enİmajuscule avec point - Le
ıminuscule sans point se convertit enImajuscule sans point
Cette différence perturbe la comparaison insensible à la casse :
const word1 = "file";
const word2 = "FILE";
// Dans la locale anglaise (correct)
console.log(word1.toLowerCase() === word2.toLowerCase());
// true
// Dans la locale turque (incorrect)
console.log(word1.toLocaleLowerCase("tr") === word2.toLocaleLowerCase("tr"));
// false - "file" devient "fıle"
Lorsqu'on convertit FILE en minuscules selon les règles turques, le I devient ı (sans point), produisant fıle. Cela ne correspond pas à file (avec i pointé), donc la comparaison renvoie false même si les chaînes représentent le même mot.
D'autres langues présentent des problèmes similaires. L'allemand a le caractère ß qui se met en majuscule comme SS. Le grec a plusieurs formes minuscules du sigma (σ et ς) qui se mettent toutes deux en majuscule comme Σ. La simple conversion de casse ne peut pas gérer correctement ces règles spécifiques à chaque langue.
Utilisation d'Intl.Collator avec sensibilité de base pour la comparaison insensible à la casse
L'API Intl.Collator fournit une comparaison de chaînes adaptée à la locale avec une sensibilité configurable. L'option sensitivity contrôle quelles différences importent lors de la comparaison.
Pour une comparaison insensible à la casse, utilisez sensitivity: "base" :
const collator = new Intl.Collator("en", { sensitivity: "base" });
console.log(collator.compare("Hello", "hello"));
// 0 (les chaînes sont égales)
console.log(collator.compare("Hello", "HELLO"));
// 0 (les chaînes sont égales)
console.log(collator.compare("Hello", "Héllo"));
// 0 (les chaînes sont égales, les accents sont également ignorés)
La sensibilité de base ignore à la fois les différences de casse et d'accent. Seules les lettres de base importent. La comparaison renvoie 0 lorsque les chaînes sont équivalentes à ce niveau de sensibilité.
Cette approche gère correctement le problème du i turc :
const collator = new Intl.Collator("tr", { sensitivity: "base" });
console.log(collator.compare("file", "FILE"));
// 0 (correspondance correcte)
console.log(collator.compare("file", "FİLE"));
// 0 (correspondance correcte, même avec le İ pointé)
Le collator applique automatiquement les règles de pliage de casse turques. Les deux comparaisons reconnaissent les chaînes comme équivalentes, quel que soit le I majuscule qui apparaît dans l'entrée.
Utilisation de localeCompare avec l'option sensitivity
La méthode localeCompare() offre une alternative pour effectuer des comparaisons insensibles à la casse. Elle accepte les mêmes options que Intl.Collator :
const str1 = "Hello";
const str2 = "HELLO";
console.log(str1.localeCompare(str2, "en", { sensitivity: "base" }));
// 0 (les chaînes sont égales)
Cela produit le même résultat que l'utilisation d'Intl.Collator avec une sensibilité de base. La comparaison ignore les différences de casse et renvoie 0 pour les chaînes équivalentes.
Vous pouvez l'utiliser dans le filtrage de tableaux :
const query = "apple";
const items = ["Apple", "Banana", "APPLE PIE", "Orange"];
const matches = items.filter(item =>
item.localeCompare(query, "en", { sensitivity: "base" }) === 0 ||
item.toLowerCase().includes(query.toLowerCase())
);
console.log(matches);
// ["Apple"]
Cependant, localeCompare() ne renvoie 0 que pour les correspondances exactes au niveau de sensibilité spécifié. Elle ne prend pas en charge la correspondance partielle comme includes(). Pour la recherche de sous-chaînes, vous devez toujours utiliser la conversion en minuscules ou implémenter un algorithme de recherche plus sophistiqué.
Choisir entre la sensibilité de base et la sensibilité aux accents
L'option sensitivity accepte quatre valeurs qui contrôlent différents aspects de la comparaison de chaînes :
Sensibilité de base
La sensibilité de base ignore à la fois la casse et les accents :
const collator = new Intl.Collator("en", { sensitivity: "base" });
console.log(collator.compare("cafe", "café"));
// 0 (accents ignorés)
console.log(collator.compare("cafe", "Café"));
// 0 (casse et accents ignorés)
console.log(collator.compare("cafe", "CAFÉ"));
// 0 (casse et accents ignorés)
Cela fournit la correspondance la plus souple. Les utilisateurs qui ne peuvent pas taper de caractères accentués ou qui les omettent par commodité obtiennent toujours des correspondances correctes.
Sensibilité aux accents
La sensibilité aux accents ignore la casse mais prend en compte les accents :
const collator = new Intl.Collator("en", { sensitivity: "accent" });
console.log(collator.compare("cafe", "café"));
// -1 (les accents comptent)
console.log(collator.compare("cafe", "Café"));
// -1 (les accents comptent, casse ignorée)
console.log(collator.compare("Café", "CAFÉ"));
// 0 (casse ignorée, accents correspondants)
Cela traite les lettres accentuées et non accentuées comme différentes tout en ignorant la casse. Utilisez cette option lorsque les différences d'accent sont significatives mais que les différences de casse ne le sont pas.
Choisir la bonne sensibilité pour votre cas d'utilisation
Pour la plupart des besoins de comparaison insensible à la casse, la sensibilité de base offre la meilleure expérience utilisateur :
- Fonctionnalité de recherche où les utilisateurs tapent des requêtes sans accents
- Correspondance de noms d'utilisateur où la casse ne devrait pas importer
- Recherche approximative où vous souhaitez une flexibilité maximale
- Validation de formulaire où
Smithetsmithdevraient correspondre
Utilisez la sensibilité aux accents lorsque :
- La langue nécessite de distinguer les caractères accentués
- Vos données contiennent à la fois des versions accentuées et non accentuées avec des significations différentes
- Vous avez besoin d'une comparaison insensible à la casse mais sensible aux accents
Effectuer une recherche insensible à la casse avec includes
L'API Intl.Collator compare des chaînes complètes mais ne fournit pas de correspondance de sous-chaînes. Pour une recherche insensible à la casse, vous devez toujours combiner la comparaison adaptée à la locale avec d'autres approches.
Une option consiste à utiliser toLowerCase() pour la recherche de sous-chaînes, mais en acceptant ses limitations pour les textes internationaux :
function caseInsensitiveIncludes(text, query, locale = "en") {
return text.toLowerCase().includes(query.toLowerCase());
}
const text = "The Quick Brown Fox";
console.log(caseInsensitiveIncludes(text, "quick"));
// true
Pour des recherches plus sophistiquées qui traitent correctement les textes internationaux, vous devez itérer à travers les positions possibles de sous-chaînes et utiliser le collator pour chaque comparaison :
function localeAwareIncludes(text, query, locale = "en") {
const collator = new Intl.Collator(locale, { sensitivity: "base" });
for (let i = 0; i <= text.length - query.length; i++) {
const substring = text.slice(i, i + query.length);
if (collator.compare(substring, query) === 0) {
return true;
}
}
return false;
}
const text = "The Quick Brown Fox";
console.log(localeAwareIncludes(text, "quick"));
// true
Cette approche vérifie chaque sous-chaîne possible de la longueur correcte et utilise une comparaison adaptée à la locale pour chacune. Elle traite correctement les textes internationaux mais présente des performances moins bonnes que le simple includes().
Considérations de performance lors de l'utilisation d'Intl.Collator
La création d'une instance Intl.Collator implique le chargement de données de locale et le traitement d'options. Lorsque vous devez effectuer plusieurs comparaisons, créez le collator une seule fois et réutilisez-le :
// Inefficace : crée un collator pour chaque comparaison
function badCompare(items, target) {
return items.filter(item =>
new Intl.Collator("en", { sensitivity: "base" }).compare(item, target) === 0
);
}
// Efficace : crée un collator une fois, le réutilise
function goodCompare(items, target) {
const collator = new Intl.Collator("en", { sensitivity: "base" });
return items.filter(item =>
collator.compare(item, target) === 0
);
}
La version efficace crée le collator une seule fois avant le filtrage. Chaque comparaison utilise la même instance, évitant ainsi les frais généraux d'initialisation répétés.
Pour les applications qui effectuent des comparaisons fréquentes, créez des instances de collator au démarrage de l'application et exportez-les pour une utilisation dans l'ensemble de votre code :
// utils/collation.js
export const caseInsensitiveCollator = new Intl.Collator("en", {
sensitivity: "base"
});
export const accentInsensitiveCollator = new Intl.Collator("en", {
sensitivity: "accent"
});
// Dans votre code d'application
import { caseInsensitiveCollator } from "./utils/collation";
const isMatch = caseInsensitiveCollator.compare(input, expected) === 0;
Ce modèle maximise les performances et maintient un comportement de comparaison cohérent dans toute votre application.
Quand utiliser toLowerCase versus Intl.Collator
Pour les applications uniquement en anglais où vous contrôlez le contenu textuel et savez qu'il ne contient que des caractères ASCII, toLowerCase() fournit des résultats acceptables :
// Acceptable pour du texte uniquement en anglais, uniquement ASCII
const isMatch = str1.toLowerCase() === str2.toLowerCase();
Cette approche est simple, rapide et familière à la plupart des développeurs. Si votre application ne traite vraiment jamais de texte international, la complexité supplémentaire de la comparaison tenant compte des paramètres régionaux peut ne pas apporter de valeur.
Pour les applications internationales ou les applications où les utilisateurs saisissent du texte dans n'importe quelle langue, utilisez Intl.Collator avec une sensibilité appropriée :
// Requis pour le texte international
const collator = new Intl.Collator(userLocale, { sensitivity: "base" });
const isMatch = collator.compare(str1, str2) === 0;
Cela garantit un comportement correct quelle que soit la langue que les utilisateurs parlent ou tapent. Le petit coût de performance lié à l'utilisation d'Intl.Collator vaut la peine d'être supporté pour éviter des comparaisons incorrectes.
Même si votre application ne prend actuellement en charge que l'anglais, l'utilisation d'une comparaison tenant compte des paramètres régionaux dès le départ facilite l'internationalisation future. L'ajout de la prise en charge de nouvelles langues ne nécessite aucune modification de la logique de comparaison.
Cas d'utilisation pratiques pour la comparaison insensible à la casse
La comparaison insensible à la casse apparaît dans de nombreux scénarios courants :
Correspondance de nom d'utilisateur et d'email
Les utilisateurs saisissent des noms d'utilisateur et des adresses email avec une capitalisation incohérente :
const collator = new Intl.Collator("en", { sensitivity: "base" });
function findUserByEmail(users, email) {
return users.find(user =>
collator.compare(user.email, email) === 0
);
}
const users = [
{ email: "[email protected]", name: "John" },
{ email: "[email protected]", name: "Jane" }
];
console.log(findUserByEmail(users, "[email protected]"));
// { email: "[email protected]", name: "John" }
Cela trouve l'utilisateur quelle que soit la façon dont il capitalise son adresse email.
Autocomplétion de recherche
Les suggestions d'autocomplétion doivent correspondre à une saisie partielle sans tenir compte de la casse :
const collator = new Intl.Collator("en", { sensitivity: "base" });
function getSuggestions(items, query) {
const queryLower = query.toLowerCase();
return items.filter(item =>
item.toLowerCase().startsWith(queryLower)
);
}
const items = ["Apple", "Apricot", "Banana", "Cherry"];
console.log(getSuggestions(items, "ap"));
// ["Apple", "Apricot"]
Cela fournit des suggestions quelle que soit la casse saisie par les utilisateurs.
Correspondance de tags et de catégories
Les utilisateurs attribuent des tags ou des catégories au contenu sans capitalisation cohérente :
const collator = new Intl.Collator("en", { sensitivity: "base" });
function hasTag(item, tag) {
return item.tags.some(itemTag =>
collator.compare(itemTag, tag) === 0
);
}
const article = {
title: "My Article",
tags: ["JavaScript", "Tutorial", "Web Development"]
};
console.log(hasTag(article, "javascript"));
// true
Cela fait correspondre les tags indépendamment des différences de capitalisation.