如何翻译 TanStack Start v1 中的页面元数据

为搜索和社交翻译元数据

问题

页面元数据——标题和描述——会出现在浏览器标签、书签、搜索结果和社交媒体预览中。当元数据语言与页面内容语言不匹配时,用户在查看页面之前就会感受到明显的不一致性。在搜索结果中,一个西班牙语页面却显示英文标题,这表明本地化质量较差。搜索引擎可能将这种不匹配视为排名信号,从而可能降低在特定语言搜索结果中的可见性。

这种脱节在发现阶段就破坏了用户体验。用自己偏好的语言进行搜索的用户期望元数据与页面内容一致,而不一致会在用户点击之前就削弱信任感。

解决方案

使用与页面内容相同的翻译资源来翻译页面元数据。在路由配置中定义一个 head 函数,该函数访问翻译后的字符串并返回本地化的标题和描述元数据。这确保了浏览器界面、搜索结果和渲染页面之间的一致性。

通过在 head 函数中使用 react-intl 的消息格式化功能,元数据可以与翻译工作流保持同步,并在语言环境更改时自动更新。

步骤

1. 创建一个在 React 组件外格式化消息的辅助工具

head 函数运行在 React 组件树之外,无法使用 hooks。创建一个使用 react-intl 的 createIntl 格式化消息的工具。

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

const cache = createIntlCache();

export function formatMetadataMessage(
  locale: string,
  messages: Record<string, string>,
  id: string,
  values?: Record<string, string | number>,
): string {
  const intl = createIntl({ locale, messages }, cache);
  return intl.formatMessage({ id }, values);
}

这个辅助工具按需创建一个 intl 实例,用于非 React 上下文中,例如 head 函数。

2. 定义元数据翻译键

为每个需要本地化元数据的页面,在翻译文件中添加标题和描述键。

export const enMessages = {
  "page.home.title": "Welcome to Our Site",
  "page.home.description": "Discover amazing content in your language",
  "page.about.title": "About Us",
  "page.about.description": "Learn more about our mission and team",
};

export const esMessages = {
  "page.home.title": "Bienvenido a Nuestro Sitio",
  "page.home.description": "Descubre contenido increíble en tu idioma",
  "page.about.title": "Acerca de Nosotros",
  "page.about.description": "Conoce más sobre nuestra misión y equipo",
};

这些键遵循与组件翻译相同的模式,将所有本地化内容集中在一个地方。

3. 为路由添加 head 函数

createFileRoute 中使用 head 选项返回翻译后的元数据。从路由参数或加载器数据中访问当前语言环境,然后使用您的助手格式化消息。

import { createFileRoute } from "@tanstack/react-router";
import { formatMetadataMessage } from "../utils/formatMetadataMessage";
import { enMessages, esMessages } from "../i18n/messages";

const messagesByLocale = {
  en: enMessages,
  es: esMessages,
};

export const Route = createFileRoute("/$locale/about")({
  head: ({ params }) => {
    const locale = params.locale || "en";
    const messages = messagesByLocale[locale] || messagesByLocale.en;

    return {
      meta: [
        {
          title: formatMetadataMessage(locale, messages, "page.about.title"),
        },
        {
          name: "description",
          content: formatMetadataMessage(
            locale,
            messages,
            "page.about.description",
          ),
        },
      ],
    };
  },
  component: AboutPage,
});

function AboutPage() {
  return <div>About content</div>;
}

head 函数在路由匹配期间运行,并返回 TanStack Start 渲染到文档头部的元数据对象。

4. 使用加载器数据生成动态元数据

当元数据依赖于获取的数据时,可以在 head 函数中访问 loaderData,将动态内容与翻译模板结合。

import { createFileRoute } from "@tanstack/react-router";
import { formatMetadataMessage } from "../utils/formatMetadataMessage";
import { enMessages, esMessages } from "../i18n/messages";

const messagesByLocale = {
  en: enMessages,
  es: esMessages,
};

export const Route = createFileRoute("/$locale/posts/$postId")({
  loader: async ({ params }) => {
    const post = await fetchPost(params.postId);
    return { post };
  },
  head: ({ params, loaderData }) => {
    const locale = params.locale || "en";
    const messages = messagesByLocale[locale] || messagesByLocale.en;
    const { post } = loaderData;

    return {
      meta: [
        {
          title: formatMetadataMessage(locale, messages, "page.post.title", {
            title: post.title,
          }),
        },
        {
          name: "description",
          content: post.excerpt,
        },
      ],
    };
  },
  component: PostPage,
});

function PostPage() {
  const { post } = Route.useLoaderData();
  return <article>{post.content}</article>;
}

加载器在 head 函数运行之前获取数据,允许您将动态值插入到翻译后的元数据模板中。