Я не видел никаких упоминаний в существующих ответах на вопросы, касающиеся кодовых точек астрального плана или интернационализации. «Прописные буквы» не означают одно и то же в каждом языке, использующем данный скрипт.
Изначально я не видел ответов на вопросы, связанные с кодовыми точками астрального плана. Там является один , но это немного похоронен (как это один будет, я думаю!)
Большинство предлагаемых функций выглядят так:
function capitalizeFirstLetter(str) {
return str[0].toUpperCase() + str.slice(1);
}
Тем не менее, некоторые прописные символы находятся за пределами BMP (базовая многоязычная плоскость, кодовые точки от U + 0 до U + FFFF). Например, возьмите этот текст Deseret:
capitalizeFirstLetter("𐐶𐐲𐑌𐐼𐐲𐑉"); // "𐐶𐐲𐑌𐐼𐐲𐑉"
Первый символ здесь не может быть написан заглавными буквами, потому что индексированные по массиву свойства строк не имеют доступа к «символам» или кодовым точкам *. Они имеют доступ к кодовым единицам UTF-16. Это верно и при разрезании - значения индекса указывают на единицы кода.
Случается, что кодовые единицы UTF-16 имеют размер 1: 1 с кодовыми точками USV в двух диапазонах, от 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("𐐶𐐲𐑌𐐼𐐲𐑉") // "𐐎𐐲𐑌𐐼𐐲𐑉"
Вы могли бы использовать побитовую математику вместо > 0xFFFF
этого, но, вероятно, легче понять этот путь, и любой из них достиг бы того же самого.
Мы также можем сделать эту работу в 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), но все же помогает проиллюстрировать проблему. Строчные буквы eszett («ß») пишутся с заглавной буквы «SS», но «SS» может быть строчными или «ß», или «ss» - вам необходимо внеполосное знание немецкого языка, чтобы знать, что правильно!
Наиболее известный пример такого рода проблем, вероятно, турецкий. В турецкой латыни заглавная форма i - это İ, а строчная форма I - ı - это две разные буквы. К счастью, у нас есть способ объяснить это:
function capitalizeFirstLetter([ first, ...rest ], locale) {
return [ first.toLocaleUpperCase(locale), ...rest ].join('');
}
capitalizeFirstLetter("italy", "en") // "Italy"
capitalizeFirstLetter("italya", "tr") // "İtalya"
В браузере наиболее предпочтительный языковой тег пользователя обозначен значком navigator.language
, в нем находится список в порядке предпочтения navigator.languages
, а язык данного элемента DOM может быть получен (обычно) Object(element.closest('[lang]')).lang || YOUR_DEFAULT_HERE
в многоязычных документах.
В агентах, которые поддерживают классы символов свойств Unicode в RegExp, которые были представлены в ES2018, мы можем очистить вещи дальше, напрямую выражая, какие символы нас интересуют:
function capitalizeFirstLetter(str, locale=navigator.language) {
return str.replace(/^\p{CWU}/u, char => char.toLocaleUpperCase(locale));
}
Это можно немного подправить, чтобы обрабатывать несколько слов в строке с большой точностью. CWU
Или Changes_When_Uppercased свойство символов соответствует всем точкам кода , которые, ну, когда изменения в верхнем регистре. Мы можем попробовать это с помощью заглавных букв, таких как голландский ij, например:
capitalizeFirstLetter('ijsselmeer'); // "IJsselmeer"
На момент написания статьи (февраль 2020 г.) Firefox / Spidermonkey еще не реализовал ни одной из функций RegExp, представленных за последние два года *****. Вы можете проверить текущее состояние этой функции в таблице сравнения Kangax . Babel может компилировать литералы RegExp со ссылками на свойства для эквивалентных шаблонов без них, но имейте в виду, что результирующий код может быть огромным.
По всей вероятности, люди, задающие этот вопрос, не будут беспокоиться о капитализации или интернационализации Deseret. Но хорошо знать об этих проблемах, потому что есть большая вероятность, что вы в конечном итоге столкнетесь с ними, даже если в настоящее время они не являются проблемой. Это не «крайние» случаи, или, скорее, они не являются крайними случаями по определению - во всяком случае, существует целая страна, где большинство людей говорят по-турецки, и объединение блоков кода с кодовыми точками является довольно распространенным источником ошибок (особенно в что касается смайликов). И строки, и язык довольно сложны!
* Кодовые единицы UTF-16 / UCS2 также являются кодовыми точками Unicode в том смысле, что, например, U + D800 технически является кодовой точкой, но это не то, что здесь «означает» ... вроде ... хотя это и получается довольно нечеткая. Что суррогаты определенно не являются, тем не менее, являются USV (скалярные значения Unicode).
** Хотя, если единица суррогатного кода «осиротела», то есть не является частью логической пары, вы все равно можете получить здесь и суррогаты.
*** может быть. Я не проверял это. Если вы не определили, что использование заглавных букв является значительным узким местом, я бы, наверное, не потел бы с этим - выбирайте то, что вы считаете наиболее понятным и читабельным.
**** такая функция может захотеть проверить как первую, так и вторую кодовые единицы, а не только первую, поскольку вполне возможно, что первая единица является суррогатом-сиротой. Например, ввод «\ uD800x» будет использовать заглавную букву X как есть, чего можно ожидать или не ожидать.
***** Вот проблема Bugzilla, если вы хотите следить за прогрессом более непосредственно.