Как переводить метаданные страницы в React Router v7
Переводим метаданные для поиска и соцсетей
Проблема
Метаданные страницы — заголовки и описания — отображаются вне самой страницы: во вкладках браузера, закладках, результатах поиска и превью в соцсетях. Если язык метаданных не совпадает с языком страницы, возникает резкий диссонанс. Например, испанская страница с английским заголовком сбивает пользователя с толку ещё до просмотра контента. Поисковые системы воспринимают такое несоответствие как признак плохой локализации или низкого качества, что может понизить позицию страницы в результатах поиска на нужном языке. Пользователь может закрыть страницу, даже не дождавшись загрузки, решив, что она не на его языке.
Решение
Переводите метаданные страницы под текущий язык, экспортируя функцию meta из модуля маршрута. Используйте API formatMessage из react-intl с Message Descriptors для перевода заголовков и описаний, чтобы метаданные использовали те же ресурсы переводов, что и контент страницы. Это обеспечит единообразие между тем, что видно во вкладках браузера, результатах поиска и на самой странице.
Шаги
1. Создайте хелпер для доступа к intl вне компонентов
Объект intl предоставляет formatMessage и доступен через хук useIntl в компонентах или напрямую через createIntl вне React. Так как функция 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,
);
}
Этот хелпер создаёт экземпляр intl, который может форматировать сообщения в любой функции, а не только в React-компонентах.
2. Загружайте сообщения в загрузчике родительского маршрута
Загрузчики маршрутов возвращают данные, к которым компоненты получают доступ через props 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-функцию из модуля маршрута, которая возвращает массив объектов мета-описания. Получайте данные загрузчика родителя из 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 принимает Message Descriptor с id и defaultMessage и возвращает переведённую строку для текущей локали.
4. Добавьте переведённые строки метаданных в ваши message-файлы
Добавьте ключи перевода метаданных в message-файл каждой локали, чтобы formatMessage мог их найти.
{
"product.meta.title": "Détails du produit",
"product.meta.description": "Voir les informations détaillées sur ce produit"
}
Когда пользователь переходит по этому маршруту, компонент Meta в корневом layout рендерит все мета-теги, созданные экспортами meta из маршрутов, отображая переведённые заголовки и описания, соответствующие языку страницы.