如何在 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 实例,便于在 head 函数等非 React 场景下使用。
2. 定义元数据翻译键
为每个需要本地化元数据的页面,在翻译文件中添加 title 和 description 键。
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 函数
在 head 选项中使用 createFileRoute,以返回已翻译的元数据。可从路由参数或 loader 数据中获取当前 locale,然后使用你的辅助函数格式化消息。
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 渲染到文档 head 的元数据对象。
4. 使用 loader 数据生成动态元数据
当元数据依赖于获取的数据时,可在 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>;
}
loader 会在 head 函数运行前获取数据,使你能够将动态值插入到已翻译的元数据模板中。