Как загружать переводы из файлов в Next.js (Pages Router) v16
Отделение переводимого контента от кода
Проблема
Жёсткое кодирование строк, отображаемых пользователю, непосредственно в компонентах создаёт сильную связь между содержимым и кодом. Каждый раз, когда текст изменяется, разработчикам приходится находить и изменять файлы реализации. Добавление поддержки второго языка требует поиска каждой жёстко закодированной строки и оборачивания её в условную логику. Третий язык ещё больше усложняет эту логику. Такой подход делает процессы перевода зависимыми от развертывания кода и не позволяет нетехническим членам команды обновлять содержимое самостоятельно.
По мере роста приложения управление переводами становится всё сложнее. Строки, разбросанные по десяткам компонентов, трудно проверять, дублировать или обновлять последовательно. Переводчики не могут работать параллельно с разработчиками, так как им нужен доступ к самому коду.
Решение
Храните весь текст, отображаемый пользователю, во внешних ресурсных файлах, организованных по языкам, с одним JSON-файлом на локаль. Каждое сообщение идентифицируется уникальным ключом, а не его буквальным текстом. Компоненты ссылаются на эти ключи вместо жёстко закодированных строк.
Загружайте соответствующий файл перевода на основе локали, полученной от Next.js, и передавайте сообщения в провайдер react-intl. Приложение может переключать языки, загружая другой файл, без изменения кода компонентов. Это отделяет содержимое от реализации, позволяя переводчикам работать со стандартными JSON-файлами, в то время как разработчики используют стабильные ключи сообщений.
Шаги
1. Создайте файлы переводов, организованные по локалям
Разместите сообщения для перевода в JSON-файлах, разделённых по локалям. Каждый файл содержит пары ключ-значение, где ключи — это стабильные идентификаторы, а значения — переведённые строки для данного языка.
{
"welcome": "С возвращением",
"greeting": "Привет, {name}",
"itemCount": "У вас {count, plural, one {# элемент} other {# элементов}}"
}
Создайте по одному файлу для каждой поддерживаемой локали (например, 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, если он предоставлен. Переменные, переданные в проп values, интерполируются в строку сообщения.