Não vi menção nas respostas existentes de questões relacionadas a pontos de código do plano astral ou à internacionalização. "Maiúsculas" não significa a mesma coisa em todos os idiomas usando um determinado script.
Inicialmente, não encontrei respostas abordando questões relacionadas aos pontos de código do plano astral. Há um , mas está um pouco enterrado (como este, eu acho!)
A maioria das funções propostas é assim:
function capitalizeFirstLetter(str) {
return str[0].toUpperCase() + str.slice(1);
}
No entanto, alguns caracteres em caixa ficam fora do BMP (plano multilíngue básico, pontos de código U + 0 a U + FFFF). Por exemplo, pegue este texto Deseret:
capitalizeFirstLetter("𐐶𐐲𐑌𐐼𐐲𐑉"); // "𐐶𐐲𐑌𐐼𐐲𐑉"
O primeiro caractere aqui falha ao capitalizar porque as propriedades de cadeias indexadas por matriz não acessam "caracteres" ou pontos de código *. Eles acessam unidades de código UTF-16. Isso também é verdade ao fatiar - os valores do índice apontam para as unidades de código.
Acontece que as unidades de código UTF-16 são 1: 1 com pontos de código USV em dois intervalos, U + 0 a U + D7FF e U + E000 a U + FFFF, inclusive. A maioria dos personagens incluídos se encaixa nesses dois intervalos, mas não em todos eles.
A partir do ES2015, lidar com isso ficou um pouco mais fácil. String.prototype[@@iterator]
produz strings correspondentes aos pontos de código **. Então, por exemplo, podemos fazer isso:
function capitalizeFirstLetter([ first, ...rest ]) {
return [ first.toUpperCase(), ...rest ].join('');
}
capitalizeFirstLetter("𐐶𐐲𐑌𐐼𐐲𐑉") // "𐐎𐐲𐑌𐐼𐐲𐑉"
Para seqüências mais longas, isso provavelmente não é muito eficiente *** - não precisamos iterar o restante. Poderíamos usar String.prototype.codePointAt
para chegar à primeira letra (possível), mas ainda precisaríamos determinar onde a fatia deveria começar. Uma maneira de evitar a repetição do restante seria testar se o primeiro ponto de código está fora do BMP; se não estiver, a fatia começará em 1 e, se estiver, a fatia começará em 2.
function capitalizeFirstLetter(str) {
const firstCP = str.codePointAt(0);
const index = firstCP > 0xFFFF ? 2 : 1;
return String.fromCodePoint(firstCP).toUpperCase() + str.slice(index);
}
capitalizeFirstLetter("𐐶𐐲𐑌𐐼𐐲𐑉") // "𐐎𐐲𐑌𐐼𐐲𐑉"
Você pode usar a matemática bit a bit em vez de > 0xFFFF
lá, mas provavelmente é mais fácil entender dessa maneira e conseguiria a mesma coisa.
Também podemos fazer isso funcionar no ES5 e abaixo, levando essa lógica um pouco mais longe, se necessário. Não há métodos intrínsecos no ES5 para trabalhar com pontos de código, portanto, precisamos testar manualmente se a primeira unidade de código é uma substituta ****:
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("𐐶𐐲𐑌𐐼𐐲𐑉") // "𐐎𐐲𐑌𐐼𐐲𐑉"
No início, também mencionei considerações sobre internacionalização. Algumas delas são muito difíceis de serem explicadas porque exigem conhecimento não apenas de qual idioma está sendo usado, mas também podem exigir conhecimento específico das palavras no idioma. Por exemplo, o dígrafo irlandês "mb" coloca em maiúscula como "mB" no início de uma palavra. Outro exemplo, o eszett alemão, nunca inicia uma palavra (afaik), mas ainda ajuda a ilustrar o problema. O eszett em minúsculas ("ß") coloca em maiúscula em "SS", mas "SS" pode em minúsculas em "ß" ou "ss" - é necessário conhecimento fora da banda do idioma alemão para saber qual é o correto!
O exemplo mais famoso desses tipos de questões, provavelmente, é o turco. No latim turco, a forma maiúscula de i é ©, enquanto a minúscula de I é i - são duas letras diferentes. Felizmente, temos uma maneira de explicar isso:
function capitalizeFirstLetter([ first, ...rest ], locale) {
return [ first.toLocaleUpperCase(locale), ...rest ].join('');
}
capitalizeFirstLetter("italy", "en") // "Italy"
capitalizeFirstLetter("italya", "tr") // "İtalya"
Em um navegador, a tag de idioma mais preferida do usuário é indicada por navigator.language
, uma lista em ordem de preferência é encontrada em navigator.languages
e um determinado idioma do elemento DOM pode ser obtido (geralmente) Object(element.closest('[lang]')).lang || YOUR_DEFAULT_HERE
em documentos em vários idiomas.
Nos agentes que oferecem suporte às classes de caracteres de propriedade Unicode no RegExp, introduzidos no ES2018, podemos limpar ainda mais as coisas expressando diretamente em quais caracteres estamos interessados:
function capitalizeFirstLetter(str, locale=navigator.language) {
return str.replace(/^\p{CWU}/u, char => char.toLocaleUpperCase(locale));
}
Isso pode ser ajustado um pouco para também manipular letras maiúsculas em várias palavras com uma precisão bastante boa. A propriedade de caractere CWU
ou Changes_When_Uppercased corresponde a todos os pontos de código que, assim, mudam quando estão em maiúsculas. Podemos tentar isso com caracteres digigráficos com letras maiúsculas como o holandês ij, por exemplo:
capitalizeFirstLetter('ijsselmeer'); // "IJsselmeer"
No momento da redação deste artigo (fevereiro de 2020), o Firefox / Spidermonkey ainda não havia implementado nenhum dos recursos RegExp introduzidos nos últimos dois anos *****. Você pode verificar o status atual desse recurso na tabela de compatibilidade Kangax . Babel é capaz de compilar literais RegExp com referências de propriedades a padrões equivalentes sem eles, mas esteja ciente de que o código resultante pode ser enorme.
Com toda a probabilidade, as pessoas que fazem essa pergunta não se preocuparão com a capitalização ou internacionalização da Deseret. Mas é bom estar ciente desses problemas, porque há uma boa chance de que você os encontre eventualmente, mesmo que não sejam preocupações no momento. Eles não são casos extremos, ou melhor, não são casos extremos por definição - existe um país inteiro onde a maioria das pessoas fala turco, de qualquer maneira, e a confluência de unidades de código com pontos de código é uma fonte bastante comum de bugs (especialmente com em relação a emoji). Ambas as strings e a linguagem são bastante complicadas!
* As unidades de código do UTF-16 / UCS2 também são pontos de código Unicode no sentido de que, por exemplo, o U + D800 é tecnicamente um ponto de código, mas não é isso que "significa" aqui ... mais ou menos ... embora fique bonito difusa. O que os substitutos definitivamente não são, no entanto, são os USVs (valores escalares Unicode).
** Embora se uma unidade de código substituto for "órfã" - isto é, não faz parte de um par lógico - você ainda poderá obter substitutos aqui também.
*** talvez. Eu não testei. A menos que você tenha determinado que a capitalização é um gargalo significativo, eu provavelmente não a suoria - escolha o que você achar mais claro e legível.
**** Essa função pode desejar testar a primeira e a segunda unidades de código em vez de apenas a primeira, pois é possível que a primeira unidade seja uma substituta órfã. Por exemplo, a entrada "\ uD800x" colocaria em maiúscula o X como está, o que pode ou não ser esperado.
***** Aqui está a questão do Bugzilla, se você quiser acompanhar o progresso mais diretamente.