Je n'ai vu aucune mention dans les réponses existantes de problèmes liés aux points de code du plan astral ou à l' internationalisation. «Majuscule» ne signifie pas la même chose dans toutes les langues utilisant un script donné.
Au début, je n'ai vu aucune réponse concernant les problèmes liés aux points de code du plan astral. Il y en a un , mais il est un peu enterré (comme celui-ci, je suppose!)
La plupart des fonctions proposées ressemblent à ceci:
function capitalizeFirstLetter(str) {
return str[0].toUpperCase() + str.slice(1);
}
Cependant, certains caractères placés en dehors du BMP (plan multilingue de base, points de code U + 0 à U + FFFF). Par exemple, prenez ce texte Deseret:
capitalizeFirstLetter("𐐶𐐲𐑌𐐼𐐲𐑉"); // "𐐶𐐲𐑌𐐼𐐲𐑉"
Le premier caractère ici ne parvient pas à mettre en majuscule parce que les propriétés indexées de tableau des chaînes n'accèdent pas aux «caractères» ou aux points de code *. Ils accèdent aux unités de code UTF-16. Cela est également vrai lors du découpage - les valeurs d'index pointent vers les unités de code.
Il se trouve que les unités de code UTF-16 sont 1: 1 avec des points de code USV dans deux plages, U + 0 à U + D7FF et U + E000 à U + FFFF inclus. La plupart des personnages encaissés entrent dans ces deux gammes, mais pas tous.
À partir d'ES2015, le traitement de ce problème est devenu un peu plus facile. String.prototype[@@iterator]
renvoie des chaînes correspondant aux points de code **. Ainsi, par exemple, nous pouvons le faire:
function capitalizeFirstLetter([ first, ...rest ]) {
return [ first.toUpperCase(), ...rest ].join('');
}
capitalizeFirstLetter("𐐶𐐲𐑌𐐼𐐲𐑉") // "𐐎𐐲𐑌𐐼𐐲𐑉"
Pour les cordes plus longues, ce n'est probablement pas terriblement efficace *** - nous n'avons pas vraiment besoin d'itérer le reste. Nous pourrions utiliser String.prototype.codePointAt
pour obtenir cette première lettre (possible), mais nous aurions encore besoin de déterminer où la tranche devrait commencer. Une façon d'éviter d'itérer le reste serait de tester si le premier point de code est en dehors du BMP; si ce n'est pas le cas, la tranche commence à 1, et si c'est le cas, la tranche commence à 2.
function capitalizeFirstLetter(str) {
const firstCP = str.codePointAt(0);
const index = firstCP > 0xFFFF ? 2 : 1;
return String.fromCodePoint(firstCP).toUpperCase() + str.slice(index);
}
capitalizeFirstLetter("𐐶𐐲𐑌𐐼𐐲𐑉") // "𐐎𐐲𐑌𐐼𐐲𐑉"
Vous pouvez utiliser des mathématiques au niveau du bit au lieu de > 0xFFFF
là, mais c'est probablement plus facile à comprendre de cette façon et l'une ou l'autre obtiendrait la même chose.
Nous pouvons également faire ce travail dans ES5 et ci-dessous en poussant un peu plus loin cette logique si nécessaire. Il n'y a pas de méthodes intrinsèques dans ES5 pour travailler avec les points de code, nous devons donc tester manuellement si la première unité de code est un substitut ****:
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("𐐶𐐲𐑌𐐼𐐲𐑉") // "𐐎𐐲𐑌𐐼𐐲𐑉"
Au début, j'ai également mentionné des considérations d'internationalisation. Certains d' entre eux sont très difficiles à expliquer parce qu'ils ont besoin de connaissances non seulement de ce que la langue est utilisée, mais peut aussi exiger des connaissances spécifiques des mots dans la langue. Par exemple, le digraphe irlandais "mb" est mis en majuscule comme "mB" au début d'un mot. Un autre exemple, l'eszett allemand, ne commence jamais un mot (afaik), mais aide toujours à illustrer le problème. L'eszett en minuscule («ß») met en majuscule «SS», mais «SS» peut minuscule en «ß» ou «ss» - vous avez besoin d'une connaissance hors bande de la langue allemande pour savoir ce qui est correct!
L'exemple le plus célèbre de ce genre de problèmes est probablement le turc. En latin turc, la forme majuscule de i est ©, tandis que la forme minuscule de I est ı - ce sont deux lettres différentes. Heureusement, nous avons un moyen de tenir compte de cela:
function capitalizeFirstLetter([ first, ...rest ], locale) {
return [ first.toLocaleUpperCase(locale), ...rest ].join('');
}
capitalizeFirstLetter("italy", "en") // "Italy"
capitalizeFirstLetter("italya", "tr") // "İtalya"
Dans un navigateur, la balise de langue préférée de l'utilisateur est indiquée par navigator.language
, une liste par ordre de préférence est trouvée sur navigator.languages
, et la langue d'un élément DOM donné peut être obtenue (généralement) avec Object(element.closest('[lang]')).lang || YOUR_DEFAULT_HERE
dans des documents multilingues.
Dans les agents qui prennent en charge les classes de caractères de propriété Unicode dans RegExp, qui ont été introduites dans ES2018, nous pouvons nettoyer davantage les choses en exprimant directement les caractères qui nous intéressent:
function capitalizeFirstLetter(str, locale=navigator.language) {
return str.replace(/^\p{CWU}/u, char => char.toLocaleUpperCase(locale));
}
Cela pourrait être légèrement modifié pour gérer également plusieurs mots en majuscule dans une chaîne avec une assez bonne précision. La propriété de caractère CWU
ou Changes_When_Uppercased correspond à tous les points de code qui, ainsi, changent lorsqu'ils sont en majuscule. Nous pouvons essayer cela avec un caractère digraphique en titane comme le néerlandais ij par exemple:
capitalizeFirstLetter('ijsselmeer'); // "IJsselmeer"
Au moment de la rédaction (février 2020), Firefox / Spidermonkey n'avait encore implémenté aucune des fonctionnalités RegExp introduites au cours des deux dernières années *****. Vous pouvez vérifier l'état actuel de cette fonctionnalité dans le tableau compat Kangax . Babel est capable de compiler des littéraux RegExp avec des références de propriété à des modèles équivalents sans eux, mais sachez que le code résultant peut être énorme.
Selon toute vraisemblance, les personnes qui poseront cette question ne seront pas concernées par la capitalisation ou l'internationalisation de Deseret. Mais il est bon d'être conscient de ces problèmes, car il y a de fortes chances que vous les rencontriez éventuellement même si ce ne sont pas des problèmes pour le moment. Ce ne sont pas des cas marginaux, ou plutôt, ce ne sont pas des cas marginaux par définition - il y a tout un pays où la plupart des gens parlent le turc, de toute façon, et la fusion des unités de code avec des points de code est une source assez courante de bogues (en particulier avec en ce qui concerne les emoji). Les chaînes et la langue sont assez compliquées!
* Les unités de code de l'UTF-16 / UCS2 sont également des points de code Unicode dans le sens où, par exemple, U + D800 est techniquement un point de code, mais ce n'est pas ce que cela "signifie" ici ... en quelque sorte ... même si cela devient joli flou. Ce que les substituts ne sont certainement pas, cependant, ce sont les USV (valeurs scalaires Unicode).
** Bien que si une unité de code de substitution est «orpheline» - c'est-à-dire qu'elle ne fait pas partie d'une paire logique - vous pouvez toujours obtenir des substituts ici aussi.
*** peut être. Je ne l'ai pas testé. À moins que vous n'ayez déterminé que la capitalisation est un goulot d'étranglement significatif, je ne le suerais probablement pas - choisissez ce que vous pensez être le plus clair et le plus lisible.
**** une telle fonction pourrait souhaiter tester à la fois la première et la seconde unités de code au lieu de la première, car il est possible que la première unité soit un substitut orphelin. Par exemple, l'entrée "\ uD800x" mettrait en majuscule le X tel quel, ce qui peut ou non être attendu.
***** Voici le problème de Bugzilla si vous souhaitez suivre la progression plus directement.