Non ho visto alcuna menzione nelle risposte esistenti di problemi relativi ai punti di codice del piano astrale o all'internazionalizzazione . "Maiuscolo" non significa la stessa cosa in ogni lingua usando un determinato script.
Inizialmente non ho visto alcuna risposta per risolvere i problemi relativi ai punti di codice del piano astrale. Ce n'è uno , ma è un po 'sepolto (come questo, immagino!)
La maggior parte delle funzioni proposte è simile a questa:
function capitalizeFirstLetter(str) {
return str[0].toUpperCase() + str.slice(1);
}
Tuttavia, alcuni caratteri racchiusi non rientrano nel BMP (piano multilingue di base, punti codice da U + 0 a U + FFFF). Ad esempio prendi questo testo Deseret:
capitalizeFirstLetter("𐐶𐐲𐑌𐐼𐐲𐑉"); // "𐐶𐐲𐑌𐐼𐐲𐑉"
Il primo carattere qui non riesce a scrivere in maiuscolo perché le proprietà delle stringhe indicizzate in array non accedono a "caratteri" o punti di codice *. Accedono alle unità di codice UTF-16. Questo vale anche durante lo slicing: i valori dell'indice puntano su unità di codice.
Capita che le unità di codice UTF-16 siano 1: 1 con punti di codice USV entro due intervalli, da U + 0 a U + D7FF e da U + E000 a U + FFFF inclusi. La maggior parte dei personaggi racchiusi in questi due range, ma non tutti.
Da ES2015 in poi, affrontarlo è diventato un po 'più semplice. String.prototype[@@iterator]
restituisce stringhe corrispondenti a punti di codice **. Quindi, ad esempio, possiamo fare questo:
function capitalizeFirstLetter([ first, ...rest ]) {
return [ first.toUpperCase(), ...rest ].join('');
}
capitalizeFirstLetter("𐐶𐐲𐑌𐐼𐐲𐑉") // "𐐎𐐲𐑌𐐼𐐲𐑉"
Per stringhe più lunghe, questo probabilmente non è terribilmente efficiente *** - non abbiamo davvero bisogno di ripetere il resto. Potremmo usare String.prototype.codePointAt
per arrivare a quella prima (possibile) lettera, ma dovremmo ancora determinare da dove dovrebbe iniziare la sezione. Un modo per evitare di ripetere il resto sarebbe verificare se il primo punto di codice è esterno al BMP; in caso contrario, la sezione inizia da 1 e, in caso affermativo, la sezione inizia da 2.
function capitalizeFirstLetter(str) {
const firstCP = str.codePointAt(0);
const index = firstCP > 0xFFFF ? 2 : 1;
return String.fromCodePoint(firstCP).toUpperCase() + str.slice(index);
}
capitalizeFirstLetter("𐐶𐐲𐑌𐐼𐐲𐑉") // "𐐎𐐲𐑌𐐼𐐲𐑉"
Potresti usare la matematica bit a bit invece di > 0xFFFF
lì, ma probabilmente è più facile da capire in questo modo e raggiungerebbe la stessa cosa.
Possiamo anche farlo funzionare in ES5 e in basso, portando questa logica un po 'più in là, se necessario. Non esistono metodi intrinseci in ES5 per lavorare con i punti di codice, quindi dobbiamo testare manualmente se la prima unità di codice è un surrogato ****:
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("𐐶𐐲𐑌𐐼𐐲𐑉") // "𐐎𐐲𐑌𐐼𐐲𐑉"
All'inizio ho citato anche considerazioni sull'internazionalizzazione. Alcuni di questi sono molto difficili da conto perché richiedono la conoscenza non solo di ciò che è in uso la lingua, ma anche possono richiedere conoscenze specifiche delle parole nella lingua. Ad esempio, il digraph irlandese "mb" capitalizza come "mB" all'inizio di una parola. Un altro esempio, il tedesco eszett, non inizia mai una parola (afaik), ma aiuta ancora a illustrare il problema. Il minuscolo eszett ("ß") diventa "SS", ma "SS" può minuscolare in "ß" o "ss" - è necessaria una conoscenza fuori banda della lingua tedesca per sapere quale sia corretta!
L'esempio più famoso di questo tipo di problemi, probabilmente, è il turco. In latino turco, la forma maiuscola di I è İ, mentre la forma minuscola di I è ı - sono due lettere diverse. Fortunatamente abbiamo un modo per rendere conto di questo:
function capitalizeFirstLetter([ first, ...rest ], locale) {
return [ first.toLocaleUpperCase(locale), ...rest ].join('');
}
capitalizeFirstLetter("italy", "en") // "Italy"
capitalizeFirstLetter("italya", "tr") // "İtalya"
In un browser, il tag della lingua più preferita dell'utente è indicato da navigator.language
, un elenco in ordine di preferenza è disponibile all'indirizzo navigator.languages
e la lingua di un determinato elemento DOM può essere ottenuta (di solito) con Object(element.closest('[lang]')).lang || YOUR_DEFAULT_HERE
documenti in più lingue .
Negli agenti che supportano le classi di caratteri delle proprietà Unicode in RegExp, introdotte in ES2018, possiamo ripulire ulteriormente le cose esprimendo direttamente quali caratteri ci interessano:
function capitalizeFirstLetter(str, locale=navigator.language) {
return str.replace(/^\p{CWU}/u, char => char.toLocaleUpperCase(locale));
}
Questo potrebbe essere modificato un po 'per gestire anche la maiuscola di più parole in una stringa con una precisione abbastanza buona. Il CWU
o proprietà del carattere Changes_When_Uppercased corrisponde a tutti i punti di codice che, bene, cambiano quando vengono maiuscoli. Possiamo provarlo con un personaggio digraph titolato come l'olandese ij per esempio:
capitalizeFirstLetter('ijsselmeer'); // "IJsselmeer"
Al momento della stesura (febbraio 2020), Firefox / Spidermonkey non ha ancora implementato nessuna delle funzionalità RegExp introdotte negli ultimi due anni *****. Puoi controllare lo stato corrente di questa funzione nella tabella di compatibilità Kangax . Babel è in grado di compilare letterali RegExp con riferimenti di proprietà a schemi equivalenti senza di essi, ma sii consapevole che il codice risultante potrebbe essere enorme.
Con ogni probabilità, le persone che pongono questa domanda non si occuperanno di capitalizzazione o internazionalizzazione del Deseret. Ma è bene essere consapevoli di questi problemi perché c'è una buona probabilità che li incontri alla fine anche se al momento non sono preoccupazioni. Non sono casi "marginali", o meglio, non lo sono casi limite per definizione - c'è un intero paese in cui la maggior parte delle persone parla turco, e comunque, la fusione di unità di codice con punti di codice è una fonte abbastanza comune di bug (specialmente con riguardo alle emoji). Sia le stringhe che il linguaggio sono piuttosto complicati!
* Le unità di codice di UTF-16 / UCS2 sono anche punti di codice Unicode, nel senso che ad esempio U + D800 è tecnicamente un punto di codice, ma non è quello che "significa" qui ... in un certo senso ... anche se diventa piuttosto sfocata. Ciò che i surrogati sicuramente non sono, tuttavia, sono USV (valori scalari Unicode).
** Tuttavia, se un'unità di codice surrogato è “orfana”, ovvero non fa parte di una coppia logica, è possibile ottenere anche surrogati qui.
*** può essere. Non l'ho provato. A meno che tu non abbia determinato che la capitalizzazione è un collo di bottiglia significativo, probabilmente non lo farei sudare: scegli quello che ritieni più chiaro e leggibile.
**** tale funzione potrebbe voler testare sia la prima che la seconda unità di codice anziché solo la prima, poiché è possibile che la prima unità sia un surrogato orfano. Ad esempio, l'ingresso "\ uD800x" renderebbe maiuscola la X così com'è, cosa che può o non può essere prevista.
***** Ecco il problema Bugzilla se vuoi seguire i progressi più direttamente.