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

JavaScriptを使用して、異なる言語と表記体系に対してテキストの大文字小文字を正しく変更する

はじめに

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

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

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

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

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

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

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

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

トルコ語の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());
// Output: "ISTANBUL" (incorrect - uses dotless I)

console.log(turkish.toLocaleUpperCase("tr"));
// Output: "İSTANBUL" (correct - uses dotted İ)

都市名Istanbulにはドット付きiの文字が含まれています。トルコ語のルールを使用して大文字に変換すると、ドット付きİを含むİSTANBULになります。標準的なtoUpperCase()を使用すると、ドットなしIを含むISTANBULが生成され、トルコ語では不正確です。

トルコ語の大文字テキストを小文字に変換する際にも、同じ問題が逆方向で発生します。

const uppercase = "İSTANBUL";

console.log(uppercase.toLowerCase());
// Output: "i̇stanbul" (incorrect - creates i with combining dot above)

console.log(uppercase.toLocaleLowerCase("tr"));
// Output: "istanbul" (correct - produces dotted i)

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

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

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

ドイツ語にはß(エスツェット)という文字があり、伝統的に大文字形式がありませんでした。2017年にUnicodeが大文字のẞ文字を追加しましたが、多くのシステムでは依然としてßを大文字化する際にSSに変換します。

const german = "Straße";

console.log(german.toUpperCase());
// Output: "STRASSE" (converts ß to SS)

console.log(german.toLocaleUpperCase("de"));
// Output: "STRASSE" (also converts ß to SS)

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

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

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

ロケールを考慮した大文字変換のためのtoLocaleUpperCaseの使用

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

const text = "istanbul";

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

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

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

const text = "istanbul";

console.log(text.toLocaleUpperCase("tr"));
// Output: "İSTANBUL" (Turkish rules - dotted İ)

console.log(text.toLocaleUpperCase("en"));
// Output: "ISTANBUL" (English rules - dotless I)

localeパラメータは、どの大文字・小文字変換ルールが適用されるかを決定します。トルコ語のルールではiの点が保持されますが、英語のルールでは保持されません。

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

const text = "istanbul";

const result = text.toLocaleUpperCase();
console.log(result);
// Output depends on system locale

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

ロケールを考慮した小文字変換にtoLocaleLowerCaseを使用する

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

const text = "İSTANBUL";

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

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

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

const text = "İSTANBUL";

console.log(text.toLowerCase());
// Output: "i̇stanbul" (incorrect - i with combining dot above)

console.log(text.toLocaleLowerCase("tr"));
// Output: "istanbul" (correct - dotted i)

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

トルコ語の点なしIも処理できます。これは小文字化されても点なしのままである必要があります。

const text = "IRAK";

console.log(text.toLocaleLowerCase("tr"));
// Output: "ırak" (Turkish rules - dotless ı)

console.log(text.toLocaleLowerCase("en"));
// Output: "irak" (English rules - dotted i)

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

ロケール識別子の指定

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

const text = "Straße";

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

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

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

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

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

const text = "istanbul";

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

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

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

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

const userLocale = navigator.language;

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

console.log(result);
// Output varies by user's locale
// For Turkish users: "İSTANBUL"
// For English users: "ISTANBUL"

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

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

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

console.log(result);

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

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

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

const turkish = "Diyarbakır";

// Standard methods (incorrect for Turkish)
console.log(turkish.toUpperCase());
// Output: "DIYARBAKIR" (dotless I - incorrect)

console.log(turkish.toUpperCase().toLowerCase());
// Output: "diyarbakir" (dotted i - lost the dotless ı)

// Locale-aware methods (correct for Turkish)
console.log(turkish.toLocaleUpperCase("tr"));
// Output: "DİYARBAKIR" (dotted İ and dotless I - correct)

console.log(turkish.toLocaleUpperCase("tr").toLocaleLowerCase("tr"));
// Output: "diyarbakır" (preserves both i types - correct)

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

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

const english = "Hello World";

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

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

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

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

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

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

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

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

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

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

変換後に文字長が変化する仕組み

大文字・小文字変換は、常に1対1の文字マッピングとは限りません。一部の文字は、大文字に変換されると複数の文字に展開され、文字列の長さに影響します。

const german = "groß";

console.log(german.length);
// Output: 4

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

console.log(uppercase.length);
// Output: 5

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

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

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

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

// Original position mapping no longer valid

位置2のßは大文字版でSSになり、それ以降のすべての文字がシフトします。元の文字列の文字位置は、変換後の文字列の位置と対応しません。

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

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

const userLocale = navigator.language;

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

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

このアプローチにより、ロケールの選択を1か所にまとめることができます。使用するロケールを変更する必要がある場合は、変数定義を更新するだけで済みます。

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