Как преобразовать текст в верхний или нижний регистр по правилам локали
Используйте JavaScript для правильного изменения регистра текста для разных языков и систем письма
Введение
Когда вы преобразуете текст из верхнего регистра в нижний и наоборот, вы можете предположить, что эта операция работает одинаково для всех языков. Это не так. Различные системы письма следуют разным правилам преобразования регистра, и эти правила могут приводить к неожиданным результатам, если их не учитывать.
JavaScript предоставляет стандартные методы toUpperCase() и toLowerCase(), которые работают корректно для английского языка, но могут давать некорректные результаты для других языков. Локализованные методы toLocaleUpperCase() и toLocaleLowerCase() применяют языковые правила преобразования регистра, гарантируя правильное преобразование текста независимо от языка.
Этот урок объясняет, почему преобразование регистра различается в зависимости от языка, демонстрирует конкретные проблемы, возникающие при использовании стандартных методов, и показывает, как использовать локализованные методы для правильного преобразования регистра в международных приложениях.
Почему преобразование регистра зависит от языка
Верхний и нижний регистры букв — это не универсальные концепции, которые работают одинаково во всех системах письма. Разные языки разработали свои правила преобразования регистра, основанные на их исторических традициях письма и типографических практиках.
В английском языке преобразование регистра довольно простое. Буква i становится I при преобразовании в верхний регистр, а I становится i при преобразовании в нижний регистр. Это правило действует для всего английского алфавита.
В других языках правила более сложные. В турецком языке есть четыре различных символа для буквы i вместо двух. В немецком языке есть буква ß (эсцет), которая имеет особые правила преобразования в верхний регистр. В греческом языке форма буквы сигма зависит от того, находится ли она в конце слова.
Когда вы используете стандартные методы JavaScript, такие как toUpperCase() и toLowerCase(), преобразование следует английским правилам. Это приводит к некорректным результатам для текста на других языках. Локализованные методы применяют соответствующие правила для каждого языка, обеспечивая правильное преобразование.
Проблема с буквой i в турецком языке
Турецкий язык предоставляет самый яркий пример того, почему локаль важна для преобразования регистра. В отличие от английского, в турецком языке есть четыре различных буквы, связанные с i:
- Строчная i с точкой:
i(U+0069) - Прописная i с точкой:
İ(U+0130) - Строчная i без точки:
ı(U+0131) - Прописная i без точки:
I(U+0049)
В турецком языке строчная i с точкой становится прописной i с точкой İ. Строчная i без точки ı становится прописной i без точки I. Это две отдельные пары букв с разным произношением и значением.
Стандартные методы JavaScript следуют правилам английского языка и преобразуют i с точкой в i без точки I. Это изменяет значение турецких слов и приводит к некорректному тексту.
const turkish = "istanbul";
console.log(turkish.toUpperCase());
// Вывод: "ISTANBUL" (некорректно - используется i без точки)
console.log(turkish.toLocaleUpperCase("tr"));
// Вывод: "İSTANBUL" (корректно - используется i с точкой)
Название города Стамбул содержит букву i с точкой. При преобразовании в верхний регистр по турецким правилам оно становится İSTANBUL с i с точкой. Использование стандартного метода toUpperCase() приводит к ISTANBUL с i без точки, что некорректно в турецком языке.
Та же проблема возникает в обратном направлении при преобразовании турецкого текста из верхнего регистра в нижний.
const uppercase = "İSTANBUL";
console.log(uppercase.toLowerCase());
// Вывод: "i̇stanbul" (некорректно - создается i с комбинированной точкой сверху)
console.log(uppercase.toLocaleLowerCase("tr"));
// Вывод: "istanbul" (корректно - создается i с точкой)
Прописная i с точкой должна становиться строчной i с точкой при преобразовании в нижний регистр в турецком языке. Стандартный метод toLowerCase() не обрабатывает это корректно и может создавать строчную i с комбинированной точкой сверху, которая выглядит похоже, но технически некорректна.
Другие правила преобразования регистра для локалей
Турецкий язык не единственный, где есть особые правила преобразования регистра. Несколько других языков требуют обработки, зависящей от локали.
В немецком языке есть буква ß (эсцет), которая традиционно не имела прописной формы. В 2017 году в Unicode была добавлена прописная буква ẞ, но многие системы по-прежнему преобразуют ß в SS при переводе в верхний регистр.
const german = "Straße";
console.log(german.toUpperCase());
// Вывод: "STRASSE" (преобразует ß в SS)
console.log(german.toLocaleUpperCase("de"));
// Вывод: "STRASSE" (также преобразует ß в SS)
Оба метода дают одинаковый результат для немецкого текста в большинстве сред JavaScript. Параметр локали не изменяет вывод, но использование метода, учитывающего локаль, гарантирует, что ваш код останется корректным, если обработка Unicode изменится в будущих реализациях.
В греческом языке есть три различных формы буквы сигма. Строчная форма использует σ в середине слов и ς в конце слов. Обе формы преобразуются в одну и ту же прописную Σ.
В литовском языке есть особые правила для букв с точками. Буква i сохраняет свою точку при сочетании с определенными диакритическими знаками, даже при преобразовании в верхний регистр. Это влияет на то, как методы, учитывающие локаль, обрабатывают определенные комбинации символов.
Использование toLocaleUpperCase для преобразования в верхний регистр с учетом локали
Метод toLocaleUpperCase() преобразует строку в верхний регистр, используя правила преобразования регистра, специфичные для локали. Вы вызываете его для строки и, при необходимости, передаете идентификатор локали в качестве аргумента.
const text = "istanbul";
const result = text.toLocaleUpperCase("tr");
console.log(result);
// Вывод: "İSTANBUL"
Это преобразует строку в верхний регистр с использованием турецких правил. Точечная i становится точечной İ, что правильно для турецкого языка.
Вы можете преобразовать тот же текст, используя правила другой локали.
const text = "istanbul";
console.log(text.toLocaleUpperCase("tr"));
// Вывод: "İSTANBUL" (турецкие правила - точечная İ)
console.log(text.toLocaleUpperCase("en"));
// Вывод: "ISTANBUL" (английские правила - без точек I)
Параметр локали определяет, какие правила преобразования регистра применяются. Турецкие правила сохраняют точку на i, в то время как английские правила этого не делают.
Если вы вызываете toLocaleUpperCase() без аргументов, используется системная локаль, определяемая средой выполнения JavaScript.
const text = "istanbul";
const result = text.toLocaleUpperCase();
console.log(result);
// Вывод зависит от системной локали
Вывод зависит от локали по умолчанию в среде JavaScript, которая обычно соответствует настройкам операционной системы пользователя.
Использование toLocaleLowerCase для преобразования в нижний регистр с учетом локали
Метод toLocaleLowerCase() преобразует строку в нижний регистр, используя правила преобразования регистра, специфичные для локали. Он работает так же, как toLocaleUpperCase(), но преобразует в нижний регистр вместо верхнего.
const text = "İSTANBUL";
const result = text.toLocaleLowerCase("tr");
console.log(result);
// Вывод: "istanbul"
Это преобразует турецкий текст в верхнем регистре в нижний регистр с использованием турецких правил. Точечная İ становится точечной i, что дает правильную форму в нижнем регистре.
Без параметра локали стандартный toLowerCase() или toLocaleLowerCase() с настройками локали по умолчанию может некорректно обработать турецкие символы.
const text = "İSTANBUL";
console.log(text.toLowerCase());
// Вывод: "i̇stanbul" (некорректно - i с добавленной точкой сверху)
console.log(text.toLocaleLowerCase("tr"));
// Вывод: "istanbul" (корректно - точечная i)
Турецкая точечная İ требует турецких правил преобразования регистра для корректного преобразования. Использование метода с учетом локали и локали tr обеспечивает правильное преобразование.
Вы также можете обработать безточечную I в турецком языке, которая должна оставаться без точек при преобразовании в нижний регистр.
const text = "IRAK";
console.log(text.toLocaleLowerCase("tr"));
// Вывод: "ırak" (турецкие правила - безточечная ı)
console.log(text.toLocaleLowerCase("en"));
// Вывод: "irak" (английские правила - точечная i)
Слово IRAK (Ирак на турецком) использует безточечную I. Турецкие правила преобразования регистра преобразуют его в нижний регистр безточечную ı, в то время как английские правила преобразуют его в точечную i.
Указание идентификаторов локали
Методы toLocaleUpperCase() и toLocaleLowerCase() принимают идентификаторы локали в формате BCP 47. Это те же языковые теги, которые используются в API Intl и других функциях интернационализации.
const text = "Straße";
console.log(text.toLocaleUpperCase("de-DE"));
// Вывод: "STRASSE"
console.log(text.toLocaleUpperCase("de-AT"));
// Вывод: "STRASSE"
console.log(text.toLocaleUpperCase("de-CH"));
// Вывод: "STRASSE"
В этих примерах используются разные немецкие локали для Германии, Австрии и Швейцарии. Правила преобразования регистра, как правило, одинаковы для региональных вариантов одного языка, поэтому во всех трёх случаях результат одинаковый.
Вы также можете передать массив идентификаторов локали. Метод использует первую локаль в массиве.
const text = "istanbul";
const result = text.toLocaleUpperCase(["tr", "en"]);
console.log(result);
// Вывод: "İSTANBUL"
Метод применяет турецкие правила, так как tr является первой локалью в массиве. Если среда выполнения не поддерживает первую локаль, она переходит к следующим локалям в массиве.
Использование предпочтений локали браузера
В веб-приложениях вы можете использовать предпочтения локали браузера пользователя, чтобы определить, какие правила преобразования регистра применять. Свойство navigator.language возвращает предпочитаемый язык пользователя.
const userLocale = navigator.language;
const text = "istanbul";
const result = text.toLocaleUpperCase(userLocale);
console.log(result);
// Вывод зависит от локали пользователя
// Для турецких пользователей: "İSTANBUL"
// Для англоязычных пользователей: "ISTANBUL"
Это автоматически применяет правильные правила преобразования регистра на основе языковых настроек пользователя. Турецкие пользователи видят текст, преобразованный с использованием турецких правил, англоязычные пользователи — с использованием английских правил и так далее.
Вы также можете передать весь массив предпочтений локали, чтобы включить поведение с резервным вариантом.
const text = "istanbul";
const result = text.toLocaleUpperCase(navigator.languages);
console.log(result);
Метод использует первую локаль из предпочтений пользователя, обеспечивая лучшее поведение с резервным вариантом, если определённые локали недоступны.
Сравнение стандартных и локализованных методов
Стандартные методы toUpperCase() и toLowerCase() работают корректно для английского языка, но могут давать сбои для других языков. Локализованные методы toLocaleUpperCase() и toLocaleLowerCase() обрабатывают все языки правильно, применяя правила, специфичные для локали.
const turkish = "Diyarbakır";
// Стандартные методы (некорректно для турецкого)
console.log(turkish.toUpperCase());
// Вывод: "DIYARBAKIR" (без точек над I - некорректно)
console.log(turkish.toUpperCase().toLowerCase());
// Вывод: "diyarbakir" (потерялась точка над ı)
// Локализованные методы (корректно для турецкого)
console.log(turkish.toLocaleUpperCase("tr"));
// Вывод: "DİYARBAKIR" (с точкой над İ и без точки над I - корректно)
console.log(turkish.toLocaleUpperCase("tr").toLocaleLowerCase("tr"));
// Вывод: "diyarbakır" (сохраняются оба типа i - корректно)
Название турецкого города Diyarbakır содержит оба типа буквы i. Стандартные методы не могут сохранить это различие при преобразовании регистра. Локализованные методы сохраняют правильные символы в обоих направлениях.
Для текста, содержащего только символы с простыми правилами изменения регистра, оба подхода дают одинаковые результаты.
const english = "Hello World";
console.log(english.toUpperCase());
// Вывод: "HELLO WORLD"
console.log(english.toLocaleUpperCase("en"));
// Вывод: "HELLO WORLD"
Английский текст преобразуется одинаково с использованием любого метода. Локализованная версия не требуется для текста только на английском, но её использование гарантирует корректную работу кода, если текст содержит другие языки.
Когда использовать локализованное преобразование регистра
Используйте локализованные методы при работе с пользовательским контентом или текстом, который может включать несколько языков. Это обеспечивает корректное преобразование регистра независимо от языка текста.
function normalizeUsername(username) {
return username.toLocaleLowerCase();
}
Имена пользователей, адреса электронной почты, поисковые запросы и другие пользовательские данные должны использовать локализованное преобразование. Это корректно обрабатывает международные символы и предотвращает проблемы с турецким и другими особыми случаями.
Используйте стандартные методы только в том случае, если вы уверены, что текст содержит только английские символы, и вам нужна максимальная производительность. Стандартные методы работают немного быстрее, так как им не нужно проверять правила локали.
const htmlTag = "<DIV>";
const normalized = htmlTag.toLowerCase();
// Вывод: "<div>"
Имена HTML-тегов, свойства CSS, схемы протоколов и другие технические идентификаторы используют символы ASCII и не требуют локализованности. Стандартные методы работают корректно для такого контента.
Как изменяется длина строки после преобразования
Преобразование регистра не всегда является однозначным отображением символов. Некоторые символы преобразуются в несколько символов при переводе в верхний регистр, что влияет на длину строки.
const german = "groß";
console.log(german.length);
// Вывод: 4
const uppercase = german.toLocaleUpperCase("de");
console.log(uppercase);
// Вывод: "GROSS"
console.log(uppercase.length);
// Вывод: 5
Немецкое слово groß состоит из четырех символов. При преобразовании в верхний регистр символ ß становится SS, образуя GROSS из пяти символов. Длина строки увеличивается на один символ при преобразовании.
Это влияет на операции, зависящие от длины строки или позиций символов. Не следует предполагать, что строка в верхнем или нижнем регистре имеет ту же длину, что и оригинал.
const text = "Maße";
const positions = [0, 1, 2, 3];
const uppercase = text.toLocaleUpperCase("de");
// "MASSE" (5 символов)
// Первоначальное отображение позиций больше не актуально
Символ ß на позиции 2 становится SS в верхнем регистре, сдвигая все последующие символы. Позиции символов из оригинальной строки не соответствуют позициям в преобразованной строке.
Повторное использование параметров локали
Если вам нужно преобразовать несколько строк с использованием одной и той же локали, вы можете сохранить идентификатор локали в переменной и использовать его повторно. Это делает ваш код более поддерживаемым и обеспечивает единообразную обработку локалей.
const userLocale = navigator.language;
const city = "istanbul";
const country = "türkiye";
console.log(city.toLocaleUpperCase(userLocale));
console.log(country.toLocaleUpperCase(userLocale));
Этот подход позволяет сосредоточить выбор локали в одном месте. Если вам нужно изменить используемую локаль, достаточно обновить определение переменной.
Для приложений, обрабатывающих большие объемы текста, это не дает преимущества в производительности. Каждый вызов toLocaleUpperCase() или toLocaleLowerCase() выполняет преобразование независимо. В отличие от форматтеров API Intl, здесь нет объекта форматтера для повторного использования.