アストラルプレーンのコードポイントや国際化に関連する問題の既存の回答には何も言及されていません。「大文字」は、特定のスクリプトを使用するすべての言語で同じことを意味するわけではありません。
最初は、アストラルプレーンのコードポイントに関連する問題に対処する回答はありませんでした。1つありますが、少し埋まっています(このようになると思います!)
提案された関数のほとんどは次のようになります。
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の2つの範囲内にあります。ほとんどの大文字と小文字が区別される文字は、これらの2つの範囲に分類されますが、すべてではありません。
ES2015以降、これへの対処は少し簡単になりました。String.prototype[@@iterator]
コードポイント**に対応する文字列を生成します。たとえば、次のようにできます。
function capitalizeFirstLetter([ first, ...rest ]) {
return [ first.toUpperCase(), ...rest ].join('');
}
capitalizeFirstLetter("𐐶𐐲𐑌𐐼𐐲𐑉") // "𐐎𐐲𐑌𐐼𐐲𐑉"
より長い文字列の場合、これはおそらくそれほど効率的ではありません*** —残りの部分を繰り返す必要はありません。String.prototype.codePointAt
最初の(可能性のある)文字を取得するために使用できますが、スライスの開始位置を決定する必要があります。残りの反復を回避する1つの方法は、最初のコードポイントがBMPの外側にあるかどうかをテストすることです。そうでない場合、スライスは1から始まり、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」として大文字になります。もう1つの例、ドイツ語のeszettは、単語(afaik)を開始することはありませんが、問題の説明に役立ちます。小文字のeszett(「ß」)は「SS」を大文字にしますが、「SS」は「ß」または「ss」のいずれかに小文字にすることができます。どちらが正しいかを知るには、ドイツ語の帯域外の知識が必要です。
この種の問題の最も有名な例は、おそらくトルコ語です。トルコ語ラテン語では、iの大文字の形式はisですが、Iの小文字の形式はıです—これらは2つの異なる文字です。幸いなことに、これを説明する方法があります。
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
多言語ドキュメントで取得できます。
ES2018で導入されたRegExpのUnicodeプロパティ文字クラスをサポートするエージェントでは、関心のある文字を直接表現することで、さらにクリーンアップできます。
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年2月)では、Firefox / Spidermonkeyは、過去2年間に導入されたRegExp機能をまだ実装していません*****。この機能の現在のステータスは、Kangax compat tableで確認できます。Babelは、RegExpリテラルを、それらを含まない同等のパターンへのプロパティ参照でコンパイルできますが、結果のコードが膨大になる可能性があることに注意してください。
おそらく、この質問をする人々は、Deseretの大文字化または国際化に関与しません。ただし、これらの問題は、現時点では問題になっていなくても、最終的には発生する可能性が高いため、注意が必要です。それらは「エッジ」ケースではなく、むしろ定義によるエッジケースではありません。とにかくほとんどの人がトルコ語を話す国があり、コードポイントとコードユニットを融合させることは、かなり一般的なバグのソースです(特に絵文字に関して)。文字列と言語はどちらもかなり複雑です!
* UTF-16 / UCS2のコード単位は、たとえばU + D800が技術的にコードポイントであるという意味でUnicodeコードポイントでもありますが、ここでは「意味する」ことではありません...ファジー。サロゲートが間違いなくそうではないのは、USV(Unicodeスカラー値)です。
**サロゲートコードユニットが「孤立」している場合、つまり論理ペアの一部ではない場合でも、ここでもサロゲートを取得できます。
*** 多分。私はそれをテストしていません。大文字の使用が意味のあるボトルネックであると判断しない限り、私はおそらくそれを気にすることはありません。最も明確で読みやすいと思われるものを選択してください。
****最初のユニットが孤立したサロゲートである可能性があるため、このような関数では、最初のコードユニットだけでなく、最初と2番目のコードユニットの両方をテストする場合があります。たとえば、「\ uD800x」と入力すると、Xがそのまま大文字になります。
***** 進行状況をより直接追跡したい場合のBugzillaの問題は次のとおりです。