ロケールルールに従ってテキストを大文字または小文字に変換する方法

JavaScriptを使用して異なる言語や文字体系に対してテキストケースを正しく変更する

はじめに

テキストを大文字と小文字の間で変換する際、この操作がすべての言語で同じように機能すると思うかもしれません。しかし、そうではありません。異なる文字体系では異なる大文字小文字変換ルールが適用され、これらのルールを考慮しないと予期しない結果が生じる可能性があります。

JavaScriptは標準のtoUpperCase()toLowerCase()メソッドを提供していますが、これらは英語では正しく機能しても、他の言語では不正確な結果を生じさせることがあります。ロケール対応メソッドであるtoLocaleUpperCase()toLocaleLowerCase()は、言語固有の大文字小文字変換ルールを適用し、言語に関係なくテキストが正しく変換されることを保証します。

このレッスンでは、大文字小文字変換が言語によって異なる理由を説明し、標準メソッドで発生する特定の問題を示し、国際的なアプリケーションで大文字小文字変換を正しく処理するためにロケール対応メソッドを使用する方法を紹介します。

ロケールによって大文字小文字変換が異なる理由

文字の大文字バージョンと小文字バージョンは、すべての文字体系で同じように機能する普遍的な概念ではありません。異なる言語では、歴史的な書記習慣や印刷慣行に基づいて、異なる大文字小文字変換ルールが発展してきました。

英語では、大文字小文字変換は単純明快です。文字iは大文字にするとIになり、Iは小文字にするとiになります。この関係は英語のアルファベット全体に当てはまります。

他の言語ではより複雑なルールがあります。トルコ語には2つではなく4つの異なる文字iがあります。ドイツ語にはß(エスツェット)という文字があり、大文字変換に特定のルールがあります。ギリシャ語では、単語の最後に現れるかどうかによってシグマの文字の形が異なります。

標準のJavaScriptメソッド(toUpperCase()toLowerCase()など)を使用すると、変換は英語のルールに従います。これにより、他の言語のテキストでは不正確な結果が生じます。ロケール対応メソッドは各言語に適切なルールを適用し、正確な変換を保証します。

トルコ語のiの問題

トルコ語は、ロケールが大文字小文字変換において重要である理由の最も明確な例を提供しています。英語とは異なり、トルコ語にはiに関連する4つの異なる文字があります:

  • 小文字の点付きi:i(U+0069)
  • 大文字の点付きİ:İ(U+0130)
  • 小文字の点なしı:ı(U+0131)
  • 大文字の点なしI:I(U+0049)

トルコ語では、小文字の点付きiは大文字の点付きİになります。小文字の点なしıは大文字の点なしIになります。これらは発音と意味が異なる2つの別々の文字ペアです。

標準のJavaScriptメソッドは英語のルールに従い、点付きiを点なしIに変換します。これによりトルコ語の単語の意味が変わり、不正確なテキストが生成されます。

const turkish = "istanbul";

console.log(turkish.toUpperCase());
// 出力:"ISTANBUL"(不正確 - 点なしIを使用)

console.log(turkish.toLocaleUpperCase("tr"));
// 出力:"İSTANBUL"(正確 - 点付きİを使用)

都市名イスタンブールには点付きiの文字が含まれています。トルコ語のルールに従って大文字に変換すると、点付きİを使用した「İSTANBUL」になります。標準のtoUpperCase()を使用すると、点なしIを使用した「ISTANBUL」が生成されますが、これはトルコ語では不正確です。

同様の問題は、トルコ語の大文字テキストを小文字に変換する際にも発生します。

const uppercase = "İSTANBUL";

console.log(uppercase.toLowerCase());
// 出力:"i̇stanbul"(不正確 - 結合ドット付きのiを作成)

console.log(uppercase.toLocaleLowerCase("tr"));
// 出力:"istanbul"(正確 - 点付きiを生成)

トルコ語では、点付きİは小文字にすると点付きiになるべきです。標準のtoLowerCase()はこれを正しく処理せず、結合ドット文字を持つ小文字iを生成することがあり、見た目は似ていますが技術的には不正確です。

その他のロケール固有の大文字小文字変換ルール

トルコ語だけが特別な大文字小文字変換ルールを持つわけではありません。他にもいくつかの言語がロケール固有の処理を必要とします。

ドイツ語には文字ß(シャープs)があり、従来は大文字形式がありませんでした。2017年にUnicodeは大文字のẞ文字を追加しましたが、多くのシステムでは大文字化する際にßをSSに変換します。

const german = "Straße";

console.log(german.toUpperCase());
// 出力:"STRASSE"(ßをSSに変換)

console.log(german.toLocaleUpperCase("de"));
// 出力:"STRASSE"(同様にßをSSに変換)

大部分のJavaScript環境では、両方のメソッドがドイツ語テキストに対して同じ結果を生成します。ロケールパラメータは出力を変更しませんが、ロケール対応メソッドを使用することで、将来の実装でUnicodeの処理が変更された場合でもコードが正しく動作することが保証されます。

ギリシャ語にはシグマ文字の3つの異なる形式があります。小文字形式では単語の途中でσを使用し、単語の最後でςを使用します。両方の形式は同じ大文字のΣに変換されます。

リトアニア語には点付き文字に関する特別なルールがあります。文字iは特定の発音区別符号と組み合わされた場合、大文字化されても点を保持します。これはロケール対応メソッドが特定の文字の組み合わせをどのように処理するかに影響します。

ロケール対応の大文字変換に toLocaleUpperCase を使用する

toLocaleUpperCase() メソッドは、ロケール固有の大文字変換ルールを使用して文字列を大文字に変換します。文字列に対してこのメソッドを呼び出し、オプションでロケール識別子を引数として渡すことができます。

const text = "istanbul";

const result = text.toLocaleUpperCase("tr");
console.log(result);
// 出力: "İSTANBUL"

これはトルコ語のルールを使用して文字列を大文字に変換します。ドット付きの i はドット付きの İ になり、トルコ語では正しい変換となります。

同じテキストを異なるロケールルールを使用して変換することもできます。

const text = "istanbul";

console.log(text.toLocaleUpperCase("tr"));
// 出力: "İSTANBUL" (トルコ語ルール - ドット付き İ)

console.log(text.toLocaleUpperCase("en"));
// 出力: "ISTANBUL" (英語ルール - ドットなし I)

ロケールパラメータによって、適用される大文字変換ルールが決まります。トルコ語のルールでは i のドットが保持されますが、英語のルールではドットは保持されません。

引数なしで toLocaleUpperCase() を呼び出すと、JavaScript ランタイム環境によって決定されるシステムロケールが使用されます。

const text = "istanbul";

const result = text.toLocaleUpperCase();
console.log(result);
// 出力はシステムロケールに依存します

出力は JavaScript 環境のデフォルトロケールに依存し、通常はユーザーのオペレーティングシステム設定に一致します。

ロケール対応の小文字変換に toLocaleLowerCase を使用する

toLocaleLowerCase() メソッドは、ロケール固有の小文字変換ルールを使用して文字列を小文字に変換します。toLocaleUpperCase() と同じように動作しますが、大文字ではなく小文字に変換します。

const text = "İSTANBUL";

const result = text.toLocaleLowerCase("tr");
console.log(result);
// 出力: "istanbul"

これはトルコ語のルールを使用してトルコ語の大文字テキストを小文字に変換します。ドット付きの İ はドット付きの i になり、正しい小文字形式が生成されます。

ロケールパラメータがない場合、標準の toLowerCase() またはデフォルトロケール設定での toLocaleLowerCase() はトルコ語の文字を正しく処理できない場合があります。

const text = "İSTANBUL";

console.log(text.toLowerCase());
// 出力: "i̇stanbul" (不正確 - 結合ドット付きの i)

console.log(text.toLocaleLowerCase("tr"));
// 出力: "istanbul" (正確 - ドット付きの i)

トルコ語のドット付き İ は、正しく変換するためにトルコ語の大文字小文字変換ルールが必要です。tr ロケールを指定したロケール対応メソッドを使用することで、正確な変換が保証されます。

トルコ語のドットなし I も処理できます。これは小文字に変換してもドットなしのままであるべきです。

const text = "IRAK";

console.log(text.toLocaleLowerCase("tr"));
// 出力: "ırak" (トルコ語ルール - ドットなし ı)

console.log(text.toLocaleLowerCase("en"));
// 出力: "irak" (英語ルール - ドット付き i)

「IRAK」(トルコ語でイラク)という単語はドットなし I を使用しています。トルコ語の大文字小文字変換ルールではドットなし ı に変換されますが、英語のルールではドット付き i に変換されます。

ロケール識別子の指定

toLocaleUpperCase()toLocaleLowerCase() の両方は、BCP 47 形式のロケール識別子を受け入れます。これらは Intl API や他の国際化機能全体で使用されている言語タグと同じものです。

const text = "Straße";

console.log(text.toLocaleUpperCase("de-DE"));
// 出力: "STRASSE"

console.log(text.toLocaleUpperCase("de-AT"));
// 出力: "STRASSE"

console.log(text.toLocaleUpperCase("de-CH"));
// 出力: "STRASSE"

これらの例では、ドイツ、オーストリア、スイスの異なるドイツ語ロケールを使用しています。大文字変換のルールは一般的に同じ言語の地域変種間で一貫しているため、3つすべて同じ出力を生成します。

ロケール識別子の配列を渡すこともできます。メソッドは配列内の最初のロケールを使用します。

const text = "istanbul";

const result = text.toLocaleUpperCase(["tr", "en"]);
console.log(result);
// 出力: "İSTANBUL"

tr が配列の最初のロケールであるため、メソッドはトルコ語のルールを適用します。ランタイムが最初のロケールをサポートしていない場合、配列内の後続のロケールにフォールバックします。

ブラウザのロケール設定の使用

ウェブアプリケーションでは、ユーザーのブラウザのロケール設定を使用して、どの大文字変換ルールを適用するかを決定できます。navigator.language プロパティはユーザーの優先言語を返します。

const userLocale = navigator.language;

const text = "istanbul";
const result = text.toLocaleUpperCase(userLocale);

console.log(result);
// 出力はユーザーのロケールによって異なります
// トルコ語ユーザーの場合: "İSTANBUL"
// 英語ユーザーの場合: "ISTANBUL"

これにより、ユーザーの言語設定に基づいて正しい大文字ルールが自動的に適用されます。トルコ語ユーザーはトルコ語のルールを使用して変換されたテキストを見、英語ユーザーは英語のルールを使用して変換されたテキストを見るなどです。

ロケール設定の配列全体を渡して、フォールバック動作を有効にすることもできます。

const text = "istanbul";
const result = text.toLocaleUpperCase(navigator.languages);

console.log(result);

このメソッドはユーザーの設定から最初のロケールを使用し、特定のロケールが利用できない場合により良いフォールバック処理を提供します。

標準メソッドとロケール対応メソッドの比較

標準のtoUpperCase()toLowerCase()メソッドは英語では正しく機能しますが、他の言語では失敗する可能性があります。ロケール対応メソッドのtoLocaleUpperCase()toLocaleLowerCase()は、ロケール固有のルールを適用することですべての言語を正しく処理します。

const turkish = "Diyarbakır";

// 標準メソッド(トルコ語では不正確)
console.log(turkish.toUpperCase());
// 出力: "DIYARBAKIR"(点のないI - 不正確)

console.log(turkish.toUpperCase().toLowerCase());
// 出力: "diyarbakir"(点のあるi - 点のないıが失われた)

// ロケール対応メソッド(トルコ語で正確)
console.log(turkish.toLocaleUpperCase("tr"));
// 出力: "DİYARBAKIR"(点のあるİと点のないI - 正確)

console.log(turkish.toLocaleUpperCase("tr").toLocaleLowerCase("tr"));
// 出力: "diyarbakır"(両方のiタイプを保持 - 正確)

トルコの都市名Diyarbakırには両方のタイプのiが含まれています。標準メソッドでは、大文字と小文字を変換する際にこの区別を保持できません。ロケール対応メソッドは両方向で正しい文字を維持します。

単純な大文字小文字ルールを持つ文字のみを含むテキストの場合、両方のアプローチは同じ結果を生成します。

const english = "Hello World";

console.log(english.toUpperCase());
// 出力: "HELLO WORLD"

console.log(english.toLocaleUpperCase("en"));
// 出力: "HELLO WORLD"

英語のテキストはどちらのメソッドでも同じように変換されます。英語のみのテキストではロケール対応バージョンは必要ありませんが、使用することでテキストに他の言語が含まれている場合でもコードが正しく機能することが保証されます。

ロケール対応の大文字小文字変換を使用するタイミング

ユーザー生成コンテンツや複数の言語を含む可能性のあるテキストを扱う場合は、ロケール対応メソッドを使用してください。これにより、テキストにどの言語が含まれていても、正しい大文字小文字変換が保証されます。

function normalizeUsername(username) {
  return username.toLocaleLowerCase();
}

ユーザー名、メールアドレス、検索語、その他のユーザー入力にはロケール対応の変換を使用する必要があります。これにより、国際的な文字が正しく処理され、トルコ語やその他の特殊なケースの問題が防止されます。

標準メソッドは、テキストに英語の文字のみが含まれていることが分かっていて、最大のパフォーマンスが必要な場合にのみ使用してください。標準メソッドはロケールルールをチェックする必要がないため、実行速度がわずかに速くなります。

const htmlTag = "<DIV>";
const normalized = htmlTag.toLowerCase();
// 出力: "<div>"

HTMLタグ名、CSSプロパティ、プロトコルスキーム、その他の技術的な識別子はASCII文字を使用し、ロケール対応は必要ありません。標準メソッドはこのようなコンテンツに対して正しく機能します。

変換後に文字列の長さが変わる可能性について

大文字小文字の変換は、必ずしも1対1の文字マッピングではありません。一部の文字は大文字に変換すると複数の文字に展開され、文字列の長さに影響を与えます。

const german = "groß";

console.log(german.length);
// 出力: 4

const uppercase = german.toLocaleUpperCase("de");
console.log(uppercase);
// 出力: "GROSS"

console.log(uppercase.length);
// 出力: 5

ドイツ語の「groß」は4文字です。大文字に変換すると、「ß」は「SS」になり、「GROSS」という5文字の単語になります。変換中に文字列の長さが1文字増加します。

これは文字列の長さや文字位置に依存する操作に影響します。大文字化または小文字化された文字列のバージョンが元の文字列と同じ長さであると仮定しないでください。

const text = "Maße";
const positions = [0, 1, 2, 3];

const uppercase = text.toLocaleUpperCase("de");
// "MASSE" (5文字)

// 元の位置マッピングはもはや有効ではない

位置2にある「ß」は大文字バージョンでは「SS」になり、後続のすべての文字がシフトします。元の文字列の文字位置は、変換された文字列の位置に対応しなくなります。

ロケールパラメータの再利用

同じロケールを使用して複数の文字列を変換する必要がある場合は、ロケール識別子を変数に格納して再利用できます。これによりコードの保守性が向上し、一貫したロケール処理が保証されます。

const userLocale = navigator.language;

const city = "istanbul";
const country = "türkiye";

console.log(city.toLocaleUpperCase(userLocale));
console.log(country.toLocaleUpperCase(userLocale));

このアプローチでは、ロケールの選択を一箇所に保持します。使用するロケールを変更する必要がある場合は、変数定義を更新するだけで済みます。

大量のテキストを処理するアプリケーションでは、これはパフォーマンス上の利点を提供しません。toLocaleUpperCase()またはtoLocaleLowerCase()の各呼び出しは、変換を独立して実行します。Intl APIフォーマッタとは異なり、再利用できるフォーマッタオブジェクトはありません。