Я не видел упоминаний в существующих ответах на вопросы, связанные с кодовыми точками астрального плана или интернационализацией. «Прописные» не означают одно и то же на каждом языке, использующем данный скрипт.
Изначально я не видел ответов на вопросы, связанные с кодовыми точками астрального плана. Там есть один , но он немного похоронен (как этот, я думаю!)
Большинство предложенных функций выглядят так:
function capitalizeFirstLetter(str) {
return str[0].toUpperCase() + str.slice(1);
}
Тем не менее, некоторые прописные символы находятся за пределами BMP (базовая многоязычная плоскость, кодовые точки от U + 0 до U + FFFF). Например, возьмите этот текст Дезерет:
capitalizeFirstLetter("??????"); // "??????"
Первый символ здесь не может быть написан заглавными буквами, потому что индексированные в массиве свойства строк не имеют доступа к символам или кодовым точкам. Они имеют доступ к кодовым единицам UTF-16. Это верно и при нарезке - значения индекса указывают на единицы кода.
Случается, что кодовые единицы UTF-16 составляют 1: 1 для кодовых точек для кодовых точек в двух диапазонах: от U + 0 до U + D7FF и от U + E000 до U + FFFF. Большинство заглавных персонажей попадают в эти два диапазона, но не все.
С ES2015 справиться с этим стало немного легче. String.prototype[@@iterator]
возвращает строки, соответствующие кодовым точкам *. Так, например, мы можем сделать это:
function capitalizeFirstLetter([ first, ...rest ]) {
return [ first.toUpperCase(), ...rest ].join('');
}
capitalizeFirstLetter("??????") // "??????"
Для более длинных строк это, вероятно, не очень эффективно ** - нам не нужно итерировать остаток. Мы могли бы использовать String.prototype.codePointAt
, чтобы получить эту первую (возможную) букву, но нам все еще нужно определить, где должен начинаться фрагмент. Один из способов избежать итерации остатка состоит в том, чтобы проверить, находится ли первая кодовая точка вне BMP; если это не так, срез начинается с 1, а если это так, срез начинается с 2.
function capitalizeFirstLetter(str) {
const firstCP = str.codePointAt(0);
const index = firstCP > 0xFFFF ? 2 : 1;
return String.fromCodePoint(firstCP).toUpperCase() + str.slice(index);
}
capitalizeFirstLetter("??????") // "??????"
Мы также можем сделать эту работу в ES5 и ниже, взяв эту логику немного дальше, если это необходимо. В ES5 нет встроенных методов для работы с кодовыми точками, поэтому мы должны вручную проверить, является ли первая единица кода суррогатом ***:
function capitalizeFirstLetter(str) {
var firstCodeUnit = str[0];
if (firstCodeUnit < '\uD800' || firstCodeUnit > '\uDFFF') {
return str[0].toUpperCase() + str.slice(1);
}
return str.slice(0, 2).toUpperCase() + str.slice(2);
}
capitalizeFirstLetter("??????") // "??????"
В начале я также упомянул вопросы интернационализации. Некоторые из них очень трудно объяснить, потому что они требуют знания не только того, что используется , но также могут требовать определенных знаний слов в языке. Например, ирландский орграф «mb» пишется с заглавной буквы как «mB» в начале слова, и хотя немецкий eszett никогда не начинает слово (afaik), это означает, что нижний регистр от «SS» на немецком языке требует дополнительных знаний (это может быть "ss" или это может быть "ß", в зависимости от слова).
Самый известный пример этой проблемы, вероятно, турецкий. В турецкой латыни заглавная форма i - это İ, а строчная форма I - ı - это две разные буквы. К счастью, у нас есть способ объяснить это:
function capitalizeFirstLetter([ first, ...rest ], locale) {
return [ first.toLocaleUpperCase(locale), ...rest ].join('');
}
capitalizeFirstLetter("italya", "en") // "Italya"
capitalizeFirstLetter("italya", "tr") // "İtalya"
В браузере наиболее предпочтительный языковой тег пользователя обозначен navigator.language
, список в порядке предпочтения находится на navigator.languages
, а язык данного элемента DOM можно получить с помощью Object(element.closest('[lang]')).lang || YOUR_DEFAULT_HERE
.
По всей вероятности, люди, задающие этот вопрос, не будут беспокоиться о капитализации или интернационализации Deseret. Но хорошо знать об этих проблемах, потому что есть большая вероятность, что вы в конечном итоге столкнетесь с ними, даже если в настоящее время они не беспокоят вас. Это не «крайние» случаи, или, скорее, они не по определению крайние случаи - есть целая страна, где большинство людей говорят на турецком языке, так или иначе, и объединение кодовых единиц с кодовыми точками довольно распространено источник ошибок (особенно в отношении смайликов). И строки, и язык довольно сложны!
* или суррогатные кодовые единицы, если они осиротели
** возможно. Я не проверял это. Если вы не определили, что использование заглавных букв является значимым узким местом, я бы, наверное, не потрудился бы над этим - выберите то, что вы считаете наиболее понятным и читабельным.
*** такая функция может захотеть проверить обапервый и второй кодовые блоки, а не только первый, поскольку возможно, что первый блок является суррогатом-сиротой. Например, ввод «\ uD800x» будет использовать X как есть, что можно или нельзя ожидать.