ラテン文字、キリル文字、アラビア文字などの文字体系名を表示する方法

Intl.DisplayNamesを使用して、文字体系コードを任意の言語で人間が読める文字体系名に変換します。

はじめに

文字体系とは表記体系のことです。ラテン文字は英語、フランス語、スペイン語で使用される文字体系です。キリル文字はロシア語、ブルガリア語、ウクライナ語で使用される文字体系です。アラビア文字はアラビア語、ペルシア語、ウルドゥー語で使用される文字体系です。文字体系は言語とは異なります。同じ言語が複数の文字体系で表記されることがあるためです。セルビア語はキリル文字とラテン文字の両方を使用します。セルビアのユーザーは好みの文字体系を選択します。

言語セレクター、フォントピッカー、テキスト入力コントロールを構築する際、ユーザーが表記体系を識別できるように文字体系名を表示する必要があります。Intl.DisplayNames APIは、翻訳テーブルを維持することなく、文字体系コードをローカライズされた人間が読める名前に変換します。

文字体系と言語の理解

文字体系と言語は同じものではありません。言語は人々が話すものです。文字体系は人々がそれを書く方法です。

英語は言語です。ラテン文字は文字体系です。英語はラテン文字を使用しますが、スペイン語、フランス語、ドイツ語、ベトナム語、トルコ語など、他の数十の言語も同様にラテン文字を使用します。

セルビア語は2つの文字体系で表記できる言語です。キリル文字で書かれたセルビア語は「Српски」のように見えます。ラテン文字で書かれたセルビア語は「Srpski」のように見えます。どちらも同じ言語を表し、同じ単語と文法を持ちます。違いは表記体系だけです。

中国語には2つの一般的な文字体系のバリエーションがあります。簡体字中国語は簡体字漢字を使用します。繁体字中国語は繁体字漢字を使用します。同じ文は使用する文字体系によって異なる表示になります。

この区別は、インターフェースを構築する際に重要です。セルビア語のユーザーは、ラテン文字よりもキリル文字のテキストを好む場合があります。中国語のユーザーは、簡体字と繁体字のどちらかを選択する必要があります。ユーザーがこれらの選択を行えるように、インターフェースには文字体系名を表示する必要があります。

文字体系名をハードコーディングする問題

文字体系コードを文字体系名にマッピングするルックアップテーブルを作成できます。

const scriptNames = {
  Latn: "Latin",
  Cyrl: "Cyrillic",
  Arab: "Arabic",
  Hans: "Simplified Chinese",
  Hant: "Traditional Chinese"
};

console.log(scriptNames.Latn);
// "Latin"

このアプローチは英語話者に対してのみ機能します。他の言語を話すユーザーには、理解できない可能性のある英語の文字体系名が表示されます。サポートするすべての言語に対して翻訳が必要になります。

const scriptNames = {
  en: {
    Latn: "Latin",
    Cyrl: "Cyrillic",
    Arab: "Arabic"
  },
  es: {
    Latn: "latino",
    Cyrl: "cirílico",
    Arab: "árabe"
  },
  fr: {
    Latn: "latin",
    Cyrl: "cyrillique",
    Arab: "arabe"
  }
};

これはすぐに保守不可能になります。新しい言語を追加するたびに、完全な翻訳セットが必要になります。新しい文字体系を追加するたびに、すべての言語にエントリが必要になります。より良いソリューションが必要です。

Intl.DisplayNamesを使用して文字体系名を取得する

Intl.DisplayNamesコンストラクタは、文字体系コードを人間が読める名前に変換するフォーマッターを作成します。ロケールを指定し、タイプを"script"に設定します。

const names = new Intl.DisplayNames(["en"], { type: "script" });
console.log(names.of("Latn"));
// "Latin"

最初の引数はロケール識別子の配列です。2番目の引数はオプションオブジェクトで、type: "script"はフォーマッターに文字体系名が必要であることを伝えます。of()メソッドは文字体系コードを受け取り、その名前を返します。

文字体系コードはISO 15924標準に従います。各文字体系には4文字のコードがあり、最初の文字は大文字、残りの3文字は小文字です。ラテン文字はLatnです。キリル文字はCyrlです。アラビア文字はArabです。

const names = new Intl.DisplayNames(["en"], { type: "script" });

console.log(names.of("Latn"));
// "Latin"

console.log(names.of("Cyrl"));
// "Cyrillic"

console.log(names.of("Arab"));
// "Arabic"

console.log(names.of("Hani"));
// "Han"

console.log(names.of("Hira"));
// "Hiragana"

console.log(names.of("Kana"));
// "Katakana"

フォーマッターは、文字体系名の翻訳を維持するすべての複雑さを処理します。文字体系コードを提供するだけで済みます。

一般的なスクリプトコード

ISO 15924規格は160以上のスクリプトのコードを定義しています。以下は最も一般的に使用されるコードです。

const names = new Intl.DisplayNames(["en"], { type: "script" });

console.log(names.of("Latn"));
// "Latin"

console.log(names.of("Cyrl"));
// "Cyrillic"

console.log(names.of("Arab"));
// "Arabic"

console.log(names.of("Hebr"));
// "Hebrew"

console.log(names.of("Deva"));
// "Devanagari"

console.log(names.of("Thai"));
// "Thai"

console.log(names.of("Hani"));
// "Han"

console.log(names.of("Hans"));
// "Simplified Han"

console.log(names.of("Hant"));
// "Traditional Han"

console.log(names.of("Hang"));
// "Hangul"

console.log(names.of("Hira"));
// "Hiragana"

console.log(names.of("Kana"));
// "Katakana"

console.log(names.of("Beng"));
// "Bengali"

console.log(names.of("Grek"));
// "Greek"

Latinは西ヨーロッパのほとんどの言語をカバーします。Cyrillicはロシア語、ブルガリア語、ウクライナ語、その他のスラブ語をカバーします。Arabicはアラビア語、ペルシア語、ウルドゥー語をカバーします。Hanは中国語をカバーし、Hansは簡体字中国語、Hantは繁体字中国語を表します。Hangulは韓国語をカバーします。HiraganaとKatakanaは日本語のスクリプトです。

異なるロケールでのスクリプト名の表示

スクリプト名は表示ロケールに基づいてローカライズされます。異なるロケールでフォーマッターを作成すると、異なる言語で名前を表示できます。

const enNames = new Intl.DisplayNames(["en"], { type: "script" });
const esNames = new Intl.DisplayNames(["es"], { type: "script" });
const frNames = new Intl.DisplayNames(["fr"], { type: "script" });
const jaNames = new Intl.DisplayNames(["ja"], { type: "script" });

console.log(enNames.of("Latn"));
// "Latin"

console.log(esNames.of("Latn"));
// "latino"

console.log(frNames.of("Latn"));
// "latin"

console.log(jaNames.of("Latn"));
// "ラテン文字"

各フォーマッターは、その表示ロケールでスクリプト名を返します。これにより、スクリプト名の翻訳を維持する複雑さがすべて処理されます。

同じパターンは任意のスクリプトコードに対して機能します。

const enNames = new Intl.DisplayNames(["en"], { type: "script" });
const deNames = new Intl.DisplayNames(["de"], { type: "script" });
const zhNames = new Intl.DisplayNames(["zh"], { type: "script" });

console.log(enNames.of("Cyrl"));
// "Cyrillic"

console.log(deNames.of("Cyrl"));
// "Kyrillisch"

console.log(zhNames.of("Cyrl"));
// "西里尔文"

console.log(enNames.of("Arab"));
// "Arabic"

console.log(deNames.of("Arab"));
// "Arabisch"

console.log(zhNames.of("Arab"));
// "阿拉伯文"

フォーマッターは各言語に対して正しい言語規則を自動的に適用します。

言語バリアント用のスクリプトセレクターの構築

一部の言語では、ユーザーにスクリプトの選択肢を提供します。セルビア語はキリル文字またはラテン文字で書くことができます。中国語は簡体字または繁体字で書くことができます。ユーザーが選択できるように、これらのオプションを表示する必要があります。

function getScriptOptions(language, userLocale) {
  const names = new Intl.DisplayNames([userLocale], { type: "script" });

  if (language === "sr") {
    return [
      { code: "Cyrl", name: names.of("Cyrl") },
      { code: "Latn", name: names.of("Latn") }
    ];
  }

  if (language === "zh") {
    return [
      { code: "Hans", name: names.of("Hans") },
      { code: "Hant", name: names.of("Hant") }
    ];
  }

  return [];
}

console.log(getScriptOptions("sr", "en"));
// [
//   { code: "Cyrl", name: "Cyrillic" },
//   { code: "Latn", name: "Latin" }
// ]

console.log(getScriptOptions("zh", "en"));
// [
//   { code: "Hans", name: "Simplified Han" },
//   { code: "Hant", name: "Traditional Han" }
// ]

console.log(getScriptOptions("zh", "es"));
// [
//   { code: "Hans", name: "han simplificado" },
//   { code: "Hant", name: "han tradicional" }
// ]

この関数は、ユーザーのインターフェース言語でスクリプトオプションを返します。セルビア語ユーザーにはキリル文字とラテン文字のオプションが表示されます。中国語ユーザーには簡体字と繁体字のオプションが表示されます。名前はユーザーが理解できる言語で表示されます。

スクリプト情報を含む完全なロケール識別子の表示

ロケール識別子には、書記体系を区別するためのスクリプトコードを含めることができます。形式はlanguage-script-regionで、sr-Cyrl-RSはセルビアでキリル文字で書かれたセルビア語、zh-Hans-CNは中国の簡体字中国語を表します。

これらのロケール識別子を表示する際は、スクリプトコードを抽出し、読みやすい名前に変換します。

function parseLocaleWithScript(locale) {
  const parts = locale.split("-");

  if (parts.length < 2) {
    return null;
  }

  const [language, script] = parts;

  if (script.length === 4) {
    return {
      language,
      script: script.charAt(0).toUpperCase() + script.slice(1).toLowerCase()
    };
  }

  return null;
}

function formatLocaleWithScriptName(locale, displayLocale) {
  const parsed = parseLocaleWithScript(locale);

  if (!parsed) {
    return locale;
  }

  const languageNames = new Intl.DisplayNames([displayLocale], {
    type: "language"
  });
  const scriptNames = new Intl.DisplayNames([displayLocale], { type: "script" });

  const languageName = languageNames.of(parsed.language);
  const scriptName = scriptNames.of(parsed.script);

  return `${languageName} (${scriptName})`;
}

console.log(formatLocaleWithScriptName("sr-Cyrl", "en"));
// "Serbian (Cyrillic)"

console.log(formatLocaleWithScriptName("sr-Latn", "en"));
// "Serbian (Latin)"

console.log(formatLocaleWithScriptName("zh-Hans", "en"));
// "Chinese (Simplified Han)"

console.log(formatLocaleWithScriptName("zh-Hant", "en"));
// "Chinese (Traditional Han)"

console.log(formatLocaleWithScriptName("sr-Cyrl", "es"));
// "serbio (cirílico)"

このパターンにより、言語名とスクリプト名を組み合わせることで、ロケール識別子が人間にとって読みやすくなります。ユーザーには「sr-Cyrl」ではなく「セルビア語(キリル文字)」と表示されます。

スクリプト名を使用したフォントセレクターの作成

フォント選択インターフェースでは、サポートする文字体系ごとにフォントをグループ化することがよくあります。各フォントがカバーする文字体系をユーザーが理解できるように、スクリプト名を表示する必要があります。

function createFontOptions() {
  const fonts = [
    {
      name: "Arial",
      scripts: ["Latn", "Cyrl", "Grek", "Hebr", "Arab"]
    },
    {
      name: "Noto Sans CJK",
      scripts: ["Hans", "Hant", "Hira", "Kana", "Hang"]
    },
    {
      name: "Noto Sans Devanagari",
      scripts: ["Deva"]
    }
  ];

  const names = new Intl.DisplayNames(["en"], { type: "script" });

  return fonts.map((font) => ({
    name: font.name,
    scripts: font.scripts.map((code) => names.of(code))
  }));
}

console.log(createFontOptions());
// [
//   {
//     name: "Arial",
//     scripts: ["Latin", "Cyrillic", "Greek", "Hebrew", "Arabic"]
//   },
//   {
//     name: "Noto Sans CJK",
//     scripts: ["Simplified Han", "Traditional Han", "Hiragana", "Katakana", "Hangul"]
//   },
//   {
//     name: "Noto Sans Devanagari",
//     scripts: ["Devanagari"]
//   }
// ]

これにより、サポートされている文字体系を人間が読める形式で表示したフォントのリストが作成されます。ユーザーは必要な書記体系に基づいてフォントを選択できます。

スクリプト別の利用可能な入力方式の表示

オペレーティングシステムやブラウザは、さまざまな文字体系に対応した入力方式を提供しています。日本語入力方式はラテン文字をひらがな、カタカナ、または漢字に変換します。中国語入力方式はピンインを簡体字または繁体字の中国語文字に変換します。利用可能な入力方式をスクリプト名とともに表示できます。

function getInputMethods(userLocale) {
  const inputMethods = [
    { id: "latin-ime", script: "Latn" },
    { id: "japanese-ime", script: "Hira" },
    { id: "chinese-pinyin-simplified", script: "Hans" },
    { id: "chinese-pinyin-traditional", script: "Hant" },
    { id: "korean-ime", script: "Hang" },
    { id: "arabic-ime", script: "Arab" },
    { id: "hebrew-ime", script: "Hebr" }
  ];

  const names = new Intl.DisplayNames([userLocale], { type: "script" });

  return inputMethods.map((method) => ({
    id: method.id,
    name: names.of(method.script)
  }));
}

console.log(getInputMethods("en"));
// [
//   { id: "latin-ime", name: "Latin" },
//   { id: "japanese-ime", name: "Hiragana" },
//   { id: "chinese-pinyin-simplified", name: "Simplified Han" },
//   { id: "chinese-pinyin-traditional", name: "Traditional Han" },
//   { id: "korean-ime", name: "Hangul" },
//   { id: "arabic-ime", name: "Arabic" },
//   { id: "hebrew-ime", name: "Hebrew" }
// ]

console.log(getInputMethods("ja"));
// [
//   { id: "latin-ime", name: "ラテン文字" },
//   { id: "japanese-ime", name: "ひらがな" },
//   { id: "chinese-pinyin-simplified", name: "簡体字" },
//   { id: "chinese-pinyin-traditional", name: "繁体字" },
//   { id: "korean-ime", name: "ハングル" },
//   { id: "arabic-ime", name: "アラビア文字" },
//   { id: "hebrew-ime", name: "ヘブライ文字" }
// ]

これにより、入力方式名がユーザーの言語で表示されます。インターフェースが英語の場合は「Hiragana」、日本語の場合は「ひらがな」と表示されます。

スクリプトコードの大文字・小文字表記の理解

スクリプトコードは特定の大文字・小文字表記パターンに従います。最初の文字は大文字です。残りの3文字は小文字です。Latnが正しい表記です。LATNlatnLaTnは標準的ではありません。

of()メソッドは、大文字・小文字表記に関係なくスクリプトコードを受け入れます。

const names = new Intl.DisplayNames(["en"], { type: "script" });

console.log(names.of("Latn"));
// "Latin"

console.log(names.of("LATN"));
// "Latin"

console.log(names.of("latn"));
// "Latin"

console.log(names.of("LaTn"));
// "Latin"

フォーマッターはすべてのバリエーションを正しく処理します。ただし、標準的な大文字・小文字表記パターンを使用することで、コードがより読みやすくなり、ISO 15924標準との一貫性が保たれます。

フォールバックロケールの処理

Intl.DisplayNamesコンストラクタは、ロケールの配列を受け取ります。最初のロケールが利用できない場合、フォーマッタは配列内の次のロケールにフォールバックします。

const names = new Intl.DisplayNames(["xx-XX", "en"], { type: "script" });
console.log(names.of("Latn"));
// "Latin"

フォーマッタは最初に"xx-XX"を試みますが、これは存在しないため、"en"にフォールバックします。これにより、要求されたロケールが利用できない場合でもコードが動作することが保証されます。

フォーマッタが実際に使用しているロケールは、resolvedOptions()メソッドで確認できます。

const names = new Intl.DisplayNames(["xx-XX", "en"], { type: "script" });
console.log(names.resolvedOptions().locale);
// "en"

これは、フォールバック後にフォーマッタが英語に解決されたことを示しています。

多言語コンテンツ管理システムの構築

複数の文字体系をサポートするコンテンツ管理システムでは、各コンテンツに対してどの文字体系が利用可能かを表示する必要があります。文字体系名を表示することで、コンテンツ編集者が正しいバージョンを選択できるようになります。

function getContentVersions(contentId, userLocale) {
  const versions = [
    { script: "Latn", url: `/content/${contentId}/latn` },
    { script: "Cyrl", url: `/content/${contentId}/cyrl` },
    { script: "Arab", url: `/content/${contentId}/arab` }
  ];

  const names = new Intl.DisplayNames([userLocale], { type: "script" });

  return versions.map((version) => ({
    script: version.script,
    name: names.of(version.script),
    url: version.url
  }));
}

console.log(getContentVersions("article-123", "en"));
// [
//   { script: "Latn", name: "Latin", url: "/content/article-123/latn" },
//   { script: "Cyrl", name: "Cyrillic", url: "/content/article-123/cyrl" },
//   { script: "Arab", name: "Arabic", url: "/content/article-123/arab" }
// ]

このパターンにより、コンテンツ編集者はどの文字体系バージョンが存在するかを確認し、それらの間を移動できます。

ブラウザサポート

文字体系タイプをサポートするIntl.DisplayNames APIは、すべてのモダンブラウザで利用可能です。Chrome、Firefox、Safari、Edgeを含む主要ブラウザで2021年3月からサポートされています。

使用する前にAPIが利用可能かどうかを確認できます。

if (typeof Intl.DisplayNames !== "undefined") {
  const names = new Intl.DisplayNames(["en"], { type: "script" });
  console.log(names.of("Latn"));
} else {
  console.log("Intl.DisplayNames is not supported");
}

古いブラウザの場合は、フォールバックを提供するか、ポリフィルを使用する必要があります。シンプルなフォールバックでは、文字体系コードから名前へのハードコードされたマッピングを使用します。

function getScriptName(code, locale) {
  if (typeof Intl.DisplayNames !== "undefined") {
    const names = new Intl.DisplayNames([locale], { type: "script" });
    return names.of(code);
  }

  const fallbackNames = {
    Latn: "Latin",
    Cyrl: "Cyrillic",
    Arab: "Arabic",
    Hans: "Simplified Han",
    Hant: "Traditional Han"
  };

  return fallbackNames[code] || code;
}

console.log(getScriptName("Latn", "en"));
// "Latin"

これにより、Intl.DisplayNamesをサポートしていないブラウザでもコードが動作することが保証されますが、自動ローカライゼーション機能は失われます。