如何在 React Router v7 中翻译页面元数据

为搜索和社交平台翻译元数据

问题

页面元数据(标题和描述)显示在页面本身之外,例如浏览器标签、书签、搜索结果和社交媒体预览中。如果这些元数据与页面语言不一致,会造成明显的不协调。例如,西班牙语页面却显示英文标题,会让用户在看到内容前就感到困惑。搜索引擎会将这种不匹配视为本地化质量差或页面质量低的信号,可能导致该页面在特定语言搜索结果中的排名下降。用户甚至可能在页面加载前就离开,误以为内容不是他们的语言。

解决方案

通过在路由模块中导出 meta 函数,将页面元数据翻译为当前语言。使用 react-intl 的 formatMessage API 和 Message Descriptor 来翻译标题和描述字符串,确保元数据与页面内容使用相同的翻译资源。这样可以保证浏览器标签、搜索结果和页面内容之间的一致性。

步骤

1. 创建辅助方法以在组件外访问 intl

intl 对象提供 formatMessage 方法,可以通过组件中的 useIntl hook 获取,也可以在非 React 环境下用 createIntl 直接创建。由于 meta 函数运行在 React 组件树之外,需要创建一个辅助方法,从你的 messages 构建 intl 实例。

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

const cache = createIntlCache();

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

该辅助方法会创建一个 intl 实例,使任何函数(不仅限于 React 组件)都能格式化消息。

2. 在父路由 loader 中加载消息

路由 loader 返回的数据可以通过 loaderData props 被组件访问。应在父路由中加载翻译消息,以便子路由也能使用。

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 参数,其中包含所有匹配路由的 loader 数据,使父级 loader 数据能够被子路由的 meta 函数访问。

3. 导出一个用于翻译元数据的 meta 函数

从你的路由模块中导出一个 meta 函数,该函数返回一个 meta 描述对象数组。通过 matches 访问父级 loader 数据,并使用你的 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 的消息描述对象,并返回当前语言环境下的翻译字符串。

4. 将翻译后的元数据字符串添加到消息文件中

将元数据的翻译键添加到每个语言环境的消息文件中,以便 formatMessage 能够找到对应内容。

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

当用户导航到此路由时,根布局中的 Meta 组件会渲染所有由路由 meta 导出创建的 meta 标签,显示与页面语言相匹配的翻译标题和描述。