React Router v7でページメタデータを翻訳する方法

検索とソーシャルメディア向けにメタデータを翻訳

問題

ページメタデータ(タイトルと説明)は、ページ自体の外側、つまりブラウザのタブ、ブックマーク、検索結果、ソーシャルメディアのプレビューに表示されます。このメタデータがページの言語と一致しない場合、不快な不整合が生じます。英語のタイトルを持つスペイン語のページは、ユーザーがコンテンツを見る前に混乱させます。検索エンジンは、この不一致をページが適切にローカライズされていない、または品質が低いというシグナルとして解釈し、言語固有の検索結果でのランキングを下げる可能性があります。ユーザーは、ページが自分の言語ではないと判断し、読み込まれる前にページを離れる可能性があります。

解決策

ルートモジュールからmeta関数をエクスポートすることで、現在の言語に合わせてページメタデータを翻訳します。react-intlのformatMessage APIをMessage Descriptorsと共に使用してタイトルと説明文字列を翻訳し、メタデータがページコンテンツと同じ翻訳リソースを使用するようにします。これにより、ブラウザのタブ、検索結果、ページ自体に表示される内容の一貫性が保たれます。

手順

1. コンポーネント外でintlにアクセスするヘルパーを作成

intlオブジェクトはformatMessageを提供し、コンポーネント内ではuseIntlフックを介してアクセスできます。また、React以外の環境ではcreateIntlで直接作成できます。meta関数はReactコンポーネントツリーの外で実行されるため、メッセージからintlインスタンスを構築するヘルパーを作成します。

import { createIntl, createIntlCache } from "react-intl";

const cache = createIntlCache();

export function createIntlForLocale(
  locale: string,
  messages: Record<string, string>,
) {
  return createIntl(
    {
      locale,
      messages,
    },
    cache,
  );
}

このヘルパーは、Reactコンポーネントだけでなく、あらゆる関数でメッセージをフォーマットできるintlインスタンスを作成します。

2. 親ルートのローダーでメッセージを読み込む

ルートローダーは、コンポーネントがloaderDataプロップを介してアクセスするデータを返します。子ルートで使用できるように、親ルートで翻訳メッセージを読み込みます。

import type { Route } from "./+types/root";

export async function loader({ request }: Route.LoaderArgs) {
  const url = new URL(request.url);
  const locale = url.pathname.split("/")[1] || "en";

  const messages = await import(`../translations/${locale}.json`);

  return {
    locale,
    messages: messages.default,
  };
}

meta関数は、一致したすべてのルートからのローダーデータを含むmatchesパラメータを受け取り、親ローダーデータを子ルートのmeta関数からアクセス可能にします。

3. メタデータを翻訳するmeta関数をエクスポートする

ルートモジュールからmeta記述子オブジェクトの配列を返すmeta関数をエクスポートします。matchesから親ローダーデータにアクセスし、intlヘルパーを使用して文字列を翻訳します。

import type { Route } from "./+types/product";
import { createIntlForLocale } from "~/utils/intl";

export function meta({ matches }: Route.MetaArgs) {
  const rootMatch = matches.find((match) => match.id === "root");
  const { locale, messages } = rootMatch?.data || {
    locale: "en",
    messages: {},
  };

  const intl = createIntlForLocale(locale, messages);

  return [
    {
      title: intl.formatMessage({
        id: "product.meta.title",
        defaultMessage: "Product Details",
      }),
    },
    {
      name: "description",
      content: intl.formatMessage({
        id: "product.meta.description",
        defaultMessage: "View detailed information about this product",
      }),
    },
  ];
}

formatMessage関数は、idとdefaultMessageを持つMessage Descriptorを受け取り、現在のロケールに対応する翻訳済み文字列を返します。

4. 翻訳されたメタデータ文字列をメッセージファイルに追加する

formatMessageが見つけられるように、各ロケールのメッセージファイルにメタデータ翻訳キーを追加します。

{
  "product.meta.title": "Détails du produit",
  "product.meta.description": "Voir les informations détaillées sur ce produit"
}

ユーザーがこのルートに移動すると、ルートレイアウトのMetaコンポーネントがルートのmetaエクスポートによって作成されたすべてのメタタグをレンダリングし、ページの言語に一致する翻訳済みのタイトルと説明を表示します。