Как загружать переводы из файлов в Next.js (Pages Router) v16
Отделяйте переводимый контент от кода
Проблема
Жёстко прописывая строки для пользователей прямо в компонентах, вы сильно связываете контент с кодом. Каждый раз, когда нужно поменять текст, разработчикам приходится искать и править файлы реализации. Чтобы добавить второй язык, нужно найти все такие строки и обернуть их в условия. Для третьего языка логика усложняется ещё больше. Такой подход делает процесс перевода зависимым от релизов кода и не даёт нетехническим членам команды менять контент самостоятельно.
По мере роста приложения управлять переводами становится всё сложнее. Строки разбросаны по десяткам компонентов, их сложно проверить, найти дубли или обновить одинаково везде. Переводчики не могут работать параллельно с разработчиками, потому что им нужен доступ к самому коду.
Решение
Храните все пользовательские тексты во внешних ресурсных файлах, организованных по языкам — по одному JSON-файлу на каждый язык. Каждое сообщение определяется уникальным ключом, а не самим текстом. Компоненты ссылаются на эти ключи вместо жёстко прописанных строк.
Загружайте нужный файл перевода в зависимости от локали, которую отдаёт Next.js, и передавайте сообщения в провайдер react-intl. Приложение сможет переключать языки, просто подгружая другой файл, без изменений в коде компонентов. Это полностью отделяет контент от реализации, позволяя переводчикам работать с обычными JSON-файлами, а разработчикам — использовать стабильные ключи сообщений.
Шаги
1. Создайте файлы переводов по локалям
Поместите переводимые сообщения в JSON-файлы, разделённые по локалям. Каждый файл содержит пары ключ-значение, где ключ — это стабильный идентификатор, а значение — переведённая строка для этого языка.
{
"welcome": "Welcome back",
"greeting": "Hello, {name}",
"itemCount": "You have {count, plural, one {# item} other {# items}}"
}
Создайте отдельный файл для каждой поддерживаемой локали (например, messages/en.json, messages/es.json, messages/fr.json) в директории messages в корне вашего проекта. Используйте одинаковые ключи во всех файлах, чтобы react-intl мог находить правильный перевод для активной локали.
2. Загрузка сообщений в getStaticProps
Прочитайте файл перевода на основе локали, полученной от Next.js в getStaticProps. Это гарантирует, что сообщения будут доступны на сервере и переданы на страницу как props.
import { GetStaticProps } from "next";
export const getStaticProps: GetStaticProps = async (context) => {
const locale = context.locale || "en";
const messages = (await import(`../messages/${locale}.json`)).default;
return {
props: {
messages,
},
};
};
Динамический импорт загружает только файл для текущей локали. Next.js автоматически предоставляет значение locale на основе URL или предпочтений пользователя.
3. Передача сообщений в IntlProvider в _app
Оборачивайте корневой компонент в IntlProvider и настраивайте его с текущей локалью пользователя и соответствующими переведёнными сообщениями. Получайте сообщения из pageProps, чтобы каждая страница могла использовать свои переводы.
import { AppProps } from "next/app";
import { IntlProvider } from "react-intl";
import { useRouter } from "next/router";
export default function App({ Component, pageProps }: AppProps) {
const { locale, defaultLocale } = useRouter();
return (
<IntlProvider
locale={locale || "en"}
defaultLocale={defaultLocale || "en"}
messages={pageProps.messages}
>
<Component {...pageProps} />
</IntlProvider>
);
}
Провайдер делает сообщения доступными для всех компонентов в дереве. Каждая страница загружает свой файл сообщений через getStaticProps, а _app получает эти сообщения через pageProps.
4. Использование ключей сообщений в компонентах
Используйте компонент FormattedMessage или хук useIntl из react-intl для отображения переведённого текста. Обращайтесь к сообщениям по их ключу, а не прописывайте строки напрямую.
import { FormattedMessage, useIntl } from "react-intl";
export default function HomePage() {
const intl = useIntl();
const userName = "Alice";
return (
<div>
<h1>
<FormattedMessage id="welcome" />
</h1>
<p>
<FormattedMessage id="greeting" values={{ name: userName }} />
</p>
<input placeholder={intl.formatMessage({ id: "searchPlaceholder" })} />
</div>
);
}
React-intl ищет и форматирует переведённое сообщение по заданному id. Если перевод отсутствует, будет использовано defaultMessage, если оно указано. Переменные, переданные в prop values, подставляются в строку сообщения.