В JavaScript операции сравнения строк в контексте разных языков
выходят далеко за рамки простого === или
лексикографического порядка по Unicode-кодам. Одной из ключевых проблем
становится корректная обработка регистра и
диакритических знаков, поскольку одинаковые с точки
зрения пользователя строки могут иметь различное внутреннее
представление.
Стандарт ECMAScript решает эту задачу через Internationalization API,
где центральную роль играет механизм коллации (collation), реализованный
в Intl.Collator.
Строки в JavaScript представлены в UTF-16, но визуально одинаковые символы могут кодироваться по-разному.
Пример:
"é" !== "e\u0301"
Первый вариант — предкомпозированный символ (NFC), второй — буква
e + комбинирующий акцент (NFD). С точки зрения пользователя
они идентичны, но побайтовое сравнение даёт различие.
Для устранения подобных расхождений применяется нормализация Unicode:
const a = "é";
const b = "e\u0301";
a === b; // false
a.normalize("NFC") === b.normalize("NFC"); // true
Однако нормализация решает только проблему представления, но не семантики сравнения, где важны регистр и диакритика.
Intl.Collator предоставляет механизм локализованного
сравнения строк с учётом языковых правил.
const collator = new Intl.Collator("en");
collator.compare("a", "b"); // -1
Главное отличие от обычного сравнения — использование языковых правил, а не кодовых точек.
Регистрозависимость управляется параметром sensitivity.
Он определяет, насколько различия в регистре влияют на сравнение
строк.
base — игнорируются диакритика и регистрaccent — учитываются диакритика, но игнорируется
регистрcase — учитывается регистр, но игнорируются
диакритикаvariant — учитываются и регистр, и диакритика
(максимальная строгость)При базовом уровне сравнения различия в регистре и диакритике игнорируются:
const collator = new Intl.Collator("en", { sensitivity: "base" });
collator.compare("a", "A"); // 0
collator.compare("cafe", "café"); // 0
Такой режим используется в поиске, где важно совпадение по смыслу, а не по написанию.
Диакритика учитывается, но регистр игнорируется:
const collator = new Intl.Collator("en", { sensitivity: "accent" });
collator.compare("cafe", "café"); // -1
collator.compare("A", "a"); // 0
Этот режим полезен в сценариях, где важно различать акценты, но не регистр, например в лингвистических приложениях.
Регистр становится значимым фактором, но диакритика игнорируется:
const collator = new Intl.Collator("en", { sensitivity: "case" });
collator.compare("a", "A"); // -1
collator.compare("cafe", "café"); // 0
Такой режим часто используется в системах, где различие между заглавными и строчными символами несёт смысловую нагрузку, например в идентификаторах или технических интерфейсах.
Максимально строгий режим сравнения:
const collator = new Intl.Collator("en", { sensitivity: "variant" });
collator.compare("a", "A"); // -1
collator.compare("cafe", "café"); // -1
Учитываются все различия: регистр, диакритика и дополнительные языковые вариации.
Диакритики (акценты, умляуты, тильды) в разных языках могут:
Примеры:
В некоторых языках диакритика критична, в других — факультативна, что делает невозможным универсальное сравнение без учета локали.
Разные языки по-разному трактуют регистр и диакритические знаки.
const collator = new Intl.Collator("de", { sensitivity: "base" });
В немецком языке:
ä может рассматриваться как ae в
сортировкеß может сопоставляться с ssВ шведском:
å, ä, ö имеют собственный
порядок сортировкиМетод localeCompare использует те же механизмы, что и
Intl.Collator, но в более компактной форме.
"a".localeCompare("A", "en", { sensitivity: "base" }); // 0
Поведение полностью эквивалентно:
const collator = new Intl.Collator("en", { sensitivity: "base" });
collator.compare("a", "A");
В реальных приложениях часто требуется поиск без учета регистра и акцентов.
Пример фильтрации массива:
const collator = new Intl.Collator("ru", { sensitivity: "base" });
const data = ["Ёж", "еж", "ель", "яблоко"];
const result = data.filter(item => collator.compare(item, "ЕЖ") === 0);
Здесь строки считаются равными независимо от регистра и диакритики.
Коллация применяется не только для сравнения, но и для сортировки:
const collator = new Intl.Collator("en", { sensitivity: "base" });
const words = ["Zebra", "apple", "Álpha", "banana"];
words.sort(collator.compare);
Результат зависит от выбранной локали и уровня чувствительности, а не от Unicode-порядка.
Частый сценарий — сортировка без учета регистра, но с сохранением акцентов:
const collator = new Intl.Collator("fr", { sensitivity: "accent" });
Здесь:
e и E считаются равнымиe и é различаютсяЭто соответствует французским орфографическим правилам, где акценты несут смысловую нагрузку, но регистр вторичен.
Турецкая локаль демонстрирует важность языковых правил:
i → İI → ıconst collator = new Intl.Collator("tr", { sensitivity: "case" });
Простое приведение к нижнему регистру (toLowerCase)
здесь даёт некорректные результаты, поэтому используется только
локализованная коллация.
При реализации поиска часто требуется «мягкое» сравнение:
const collator = new Intl.Collator("en", { sensitivity: "base" });
function matches(query, value) {
return collator.compare(query, value) === 0;
}
Такой подход позволяет игнорировать:
Хотя основной фокус — регистр и диакритика, на поведение влияет и дополнительная настройка:
new Intl.Collator("en", {
sensitivity: "base",
numeric: true,
ignorePunctuation: true
});
numeric: true изменяет сравнение числовых подстрок:
ignorePunctuation: true исключает знаки препинания из
сравнения.
Intl.Collator работает поверх Unicode, но не заменяет
нормализацию.
Корректный подход:
const collator = new Intl.Collator("en", { sensitivity: "base" });
function compare(a, b) {
return collator.compare(a.normalize("NFC"), b.normalize("NFC"));
}
Это особенно важно при работе с внешними источниками данных, где форма Unicode может быть нестабильной.
Режим variant используется в ситуациях, где требуется
точное различие всех символов:
const collator = new Intl.Collator("en", { sensitivity: "variant" });
Любое отличие, включая регистр и акценты, приводит к неравенству.
Механизм обработки регистра и диакритики в Intl строится
на трёх уровнях:
sensitivity)Именно комбинация этих уровней позволяет получать предсказуемое поведение строк в многоязычных системах, где простое побайтовое сравнение оказывается недостаточным.