Как загружать переводы из файлов в React Router v7
Отделение переводимого контента от кода
Проблема
Жёсткое кодирование строк, отображаемых пользователю, непосредственно в компонентах создаёт сильную связь между содержимым и кодом. Для добавления каждого нового языка разработчикам приходится изменять файлы реализации, усложняя логику условий и увеличивая сложность. При изменении текста, даже при незначительных корректировках, требуется развертывание кода. Такой подход делает процессы перевода зависимыми от циклов разработки и не позволяет нетехническим членам команды управлять содержимым самостоятельно.
По мере роста приложений разбросанные строковые литералы становится сложно отслеживать и поддерживать. Поиск всех вхождений фразы в кодовой базе подвержен ошибкам, а обеспечение согласованности между похожими сообщениями становится практически невозможным без централизованного источника данных.
Решение
Вынесите все переводимые строки во внешние JSON-файлы, организованные по языкам, по одному файлу на локаль. Замените жёстко закодированные строки в компонентах на идентификаторы сообщений, которые ссылаются на записи в этих файлах. Во время выполнения приложение загружает соответствующий файл перевода в зависимости от локали пользователя и передаёт эти сообщения библиотеке интернационализации, которая преобразует идентификаторы в их переведённые значения.
Такое разделение позволяет переводчикам работать напрямую с JSON-файлами, не касаясь кода, упрощает обновление содержимого через простые изменения файлов и предоставляет единый источник данных для строк каждого языка.
Шаги
1. Создайте файлы перевода для каждой локали
Организуйте файлы перевода в отдельной директории, по одному JSON-файлу на язык. Структурируйте каждый файл как плоский объект, сопоставляющий идентификаторы сообщений с переведёнными строками.
{
"welcome.title": "С возвращением",
"welcome.subtitle": "Продолжите с того места, где остановились",
"nav.home": "Главная",
"nav.about": "О нас",
"nav.contact": "Контакты"
}
Сохраните этот файл как app/translations/en.json для английского языка, затем создайте аналогичные файлы, такие как app/translations/es.json и app/translations/fr.json, с переводами для других языков. Используйте единообразные ключи во всех файлах, чтобы один и тот же идентификатор соответствовал нужному переводу в каждой локали.
2. Загрузка переводов в загрузчике маршрутов
Используйте загрузчик маршрутов, чтобы получить файл перевода для текущей локали перед рендерингом. Это гарантирует, что сообщения будут доступны при монтировании компонентов.
import type { Route } from "./+types/root";
import enMessages from "./translations/en.json";
import esMessages from "./translations/es.json";
import frMessages from "./translations/fr.json";
const messages: Record<string, Record<string, string>> = {
en: enMessages,
es: esMessages,
fr: frMessages,
};
export async function loader({ request }: Route.LoaderArgs) {
const url = new URL(request.url);
const locale = url.searchParams.get("locale") || "en";
return {
locale,
messages: messages[locale] || messages.en,
};
}
Загрузчик считывает локаль из параметра запроса URL и возвращает как локаль, так и соответствующие ей сообщения. Компоненты могут получить доступ к этим данным через loaderData для настройки провайдера интернационализации.
3. Настройка IntlProvider с загруженными сообщениями
Оборачивайте ваше приложение в IntlProvider из react-intl, передавая локаль и сообщения из данных загрузчика.
import { IntlProvider } from "react-intl";
import { Outlet } from "react-router";
import type { Route } from "./+types/root";
export default function Root({ loaderData }: Route.ComponentProps) {
return (
<IntlProvider locale={loaderData.locale} messages={loaderData.messages}>
<html lang={loaderData.locale}>
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body>
<Outlet />
</body>
</html>
</IntlProvider>
);
}
IntlProvider делает локаль и сообщения доступными для всех дочерних компонентов через контекст React. Дочерние маршруты рендерятся через Outlet и наследуют доступ к данным перевода.
4. Использование сообщений по идентификатору в компонентах
Замените жестко закодированные строки на компоненты FormattedMessage, которые ссылаются на идентификаторы сообщений из ваших файлов перевода.
import { FormattedMessage } from "react-intl";
export default function Welcome() {
return (
<div>
<h1>
<FormattedMessage id="welcome.title" />
</h1>
<p>
<FormattedMessage id="welcome.subtitle" />
</p>
<nav>
<a href="/">
<FormattedMessage id="nav.home" />
</a>
<a href="/about">
<FormattedMessage id="nav.about" />
</a>
<a href="/contact">
<FormattedMessage id="nav.contact" />
</a>
</nav>
</div>
);
}
Каждый компонент FormattedMessage ищет свой id в объекте сообщений, предоставленном IntlProvider, и отображает соответствующую переведенную строку. Когда локаль изменяется и загрузчик запускается снова с другими сообщениями, все компоненты автоматически отображают новые переводы без изменений в коде.