Я не бачив жодної згадки в існуючих відповідях на питання, пов'язані з кодовими астральними площинами або інтернаціоналізацією. "Прописні літери" не означають однакову річ у кожній мові з використанням заданого сценарію.
Спочатку я не бачив жодної відповіді, що стосувалася б питань, пов’язаних з кодовими точками астральної площини. Є такий , але він трохи закопаний (як, напевно, буде цей!)
Більшість запропонованих функцій виглядають так:
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" на початку слова. Інший приклад, німецький ессет, ніколи не починає слова (afaik), але все ж допомагає проілюструвати проблему. Ессет з нижнього регістру ("ß") з великої літери складається з "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 з посиланнями на властивості до еквівалентних шаблонів без них, але пам’ятайте, що отриманий код може бути величезним.
Ймовірно, люди, які задають це питання, не будуть стосуватися десеретської капіталізації чи інтернаціоналізації. Але добре бути в курсі цих питань, тому що є хороший шанс, що ви зіткнетесь з ними в підсумку, навіть якщо вони зараз не стосуються. Вони не є "крайніми" випадками, а точніше, вони не за визначенням крайових випадків - є ціла країна, де більшість людей розмовляють турецькою, так чи інакше, і поєднання кодових одиниць з кодовими точками є досить поширеним джерелом помилок (особливо з стосовно емоджи). І рядки, і мова досить складні!
* Кодові одиниці UTF-16 / UCS2 також є кодовими точками Unicode в тому сенсі, що, наприклад, U + D800 технічно є кодовою точкою, але це не те, що це "означає" тут ... начебто ... хоча це стає досить нечіткий. Однак сурогати, безумовно, не є USV (скалярні значення Unicode).
** Хоча якщо блок сурогатного коду є "сиротою" - тобто не є частиною логічної пари - ви все одно можете отримати сюрогати і тут.
*** можливо. Я цього не перевіряв. Якщо ви не визначили, що використання великих літер є вагомим вузьким місцем, я, мабуть, не став би це - вибирайте те, що ви вважаєте, найбільш зрозумілим і читабельним.
**** така функція, можливо, хотіла б протестувати як перший, так і другий кодові одиниці замість лише першого, оскільки можливо, що перший підрозділ є сиротом сурогатом. Наприклад, вхід "\ uD800x" буде використовувати великі букви X як є, чого можна або не очікувати.
***** Ось проблема Bugzilla, якщо ви хочете більше слідкувати за прогресом.