如何翻译 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 函数运行之前获取数据,允许您将动态值插入到翻译后的元数据模板中。