Next.js (Pages Router) v16で多言語コンテンツをマークする方法

アクセシビリティのために異なる言語のテキストをマークする

問題

ページに複数の言語のテキストが含まれている場合、ブラウザと支援技術はすべてのコンテンツをページの主要言語に属するものとして扱います。英語用に設定されたスクリーンリーダーは、フランス語のフレーズ、スペイン語の書籍タイトル、ドイツ語の会社名を英語の音声規則で発音しようとし、音声に依存するユーザーにとって理解不能な出力を生成します。ブラウザは誤った言語に基づいてスペルチェックと組版規則を適用し、正しく綴られた外国語の単語をエラーとしてフラグ付けし、言語固有の句読点と書式設定の規則を誤って処理します。

これにより、正確な発音に依存する視覚障害のあるユーザーにとって障壁が生じ、誤ったスペルチェック警告と不適切なテキストレンダリングによる視覚的ノイズを導入することで、すべてのユーザーのエクスペリエンスが低下します。

解決策

外国語テキストを含む要素にHTML lang属性を適用し、そのコンテンツの言語を明示的に宣言します。この属性は、マークされたテキストがページの主要言語ではなく、宣言された言語の規則を使用して処理されるべきであることをブラウザと支援技術に通知します。スクリーンリーダーは適切な発音エンジンに切り替わり、ブラウザは正しいスペルチェック辞書を適用し、組版エンジンは言語固有の書式設定規則を使用します。

外国語テキストを正しいlang属性を持つ要素でラップすることで、多言語テキストの整合性を保持するコンテンツ内に明確な言語境界を作成します。

手順

1. 言語マーク付きテキストコンポーネントを作成する

外国語コンテンツを適切なlang属性を持つspan要素でラップするReactコンポーネントを構築します。

interface ForeignTextProps {
  lang: string;
  children: React.ReactNode;
}

export function ForeignText({ lang, children }: ForeignTextProps) {
  return <span lang={lang}>{children}</span>;
}

このコンポーネントは言語コードを受け取り、子要素をlang属性が設定されたspanでラップすることで、支援技術やブラウザが認識できる言語境界を作成します。

2. コンポーネントを使用して外国語フレーズをマークする

適切なISO 639-1言語コードを指定して、コンテンツ内の外国語テキストをコンポーネントでラップします。

export default function ArticlePage() {
  return (
    <article>
      <h1>Understanding French Cuisine</h1>
      <p>
        The concept of <ForeignText lang="fr">mise en place</ForeignText> is
        fundamental to professional cooking. It means having all ingredients
        prepared and organized before you begin.
      </p>
      <p>
        The restaurant <ForeignText lang="fr">Le Bernardin</ForeignText> in New
        York has maintained three Michelin stars for decades.
      </p>
    </article>
  );
}

各外国語フレーズは言語コードと共にコンポーネントでラップされ、スクリーンリーダーが正しく発音し、ブラウザが適切な言語ルールを適用できるようにします。

3. より長い外国語の文章を処理する

複数の文から成る外国語コンテンツの場合は、言語コンテキストを断片化しないよう、文章全体を単一の言語マーク要素でラップします。

export default function QuotePage() {
  return (
    <article>
      <h1>Universal Declaration of Human Rights</h1>
      <h2>Article 1</h2>
      <blockquote lang="es">
        <p>
          Todos los seres humanos nacen libres e iguales en dignidad y derechos
          y, dotados como están de razón y conciencia, deben comportarse
          fraternalmente los unos con los otros.
        </p>
      </blockquote>
    </article>
  );
}

blockquotepのようなブロックレベル要素に直接langを適用することで、文章全体がマークされ、スクリーンリーダーが全体を通して一貫した発音を維持し、ブラウザが完全なコンテキストに言語ルールを適用できるようになります。

4. フォーマット済みメッセージ内の外国語テキストをマークする

翻訳されたメッセージ内に外国語コンテンツが表示される場合は、メッセージのリッチテキストフォーマット内でコンポーネントを使用します。

import { FormattedMessage } from "react-intl";

export default function RecipePage() {
  return (
    <div>
      <FormattedMessage
        id="recipe.description"
        defaultMessage="This dish is called {dishName} in French cuisine."
        values={{
          dishName: <ForeignText lang="fr">coq au vin</ForeignText>,
        }}
      />
    </div>
  );
}

このコンポーネントはreact-intlのリッチテキストフォーマットと統合され、翻訳されたコンテンツ内の外国語用語をマークしながら、支援技術のための言語境界を保持できます。

5. 意味的な強調のためのバリアントを作成する

外国語テキストが強調や慣用的なスタイリングも必要とする場合は、セマンティックHTML要素を使用するようにパターンを拡張します。

interface ForeignEmphasisProps {
  lang: string;
  children: React.ReactNode;
}

export function ForeignEmphasis({ lang, children }: ForeignEmphasisProps) {
  return <i lang={lang}>{children}</i>;
}

i要素は、別の声やムードのテキストを意味的に表現するため、周囲のテキストから区別される外国語用語に適しています。lang属性は正しい発音を保証し、要素は意味的な意義を提供します。

6. サポートする言語コードを定義する

アプリケーションがサポートする言語コードを定義する型または定数を作成し、一貫性を確保し、開発時にエラーを検出できるようにします。

export const SUPPORTED_LANGUAGES = {
  FRENCH: "fr",
  SPANISH: "es",
  GERMAN: "de",
  ITALIAN: "it",
  JAPANESE: "ja",
  CHINESE: "zh",
} as const;

type LanguageCode =
  (typeof SUPPORTED_LANGUAGES)[keyof typeof SUPPORTED_LANGUAGES];

interface ForeignTextProps {
  lang: LanguageCode | string;
  children: React.ReactNode;
}

export function ForeignText({ lang, children }: ForeignTextProps) {
  return <span lang={lang}>{children}</span>;
}

サポートする言語を定数として定義することで、エディタでオートコンプリートが利用でき、アプリケーション全体で使用される言語コードを文書化できます。また、必要に応じて任意の言語コードも使用可能です。