Вы неправильно делаете i18n в Next.js
В интернационализации JavaScript закрепилась неудачная практика: выносить каждую пользовательскую строку в JSON-файл, присваивать ей ключ и ссылаться на этот ключ в компонентах. t('home.hero.title') вместо самого текста. Интерфейс у вас в одном месте, а контент — в другом.
Это работает. Так выходят тысячи приложений. Но с точки зрения разработчика это далеко не лучший опыт.
Когда в код-ревью вы видите t('checkout.summary.total'), это ничего вам не говорит — чтобы понять, что изменилось, приходится открывать JSON-файл. Ключи нужно придумывать, разбивать по пространствам имён и поддерживать в актуальном состоянии. Устаревшие переводы накапливаются, потому что никто не знает, какие ключи всё ещё используются. Проблема настолько распространена, что для её решения появились целые категории инструментов: расширения для IDE с автоподсказкой ключей, генераторы типов для их проверки, линтеры, которые находят неиспользуемые ключи. Все эти инструменты решают проблему, которую породила неудачная парадигма.
Компонент <T>
Содержимое не должно быть отделено от места, где оно используется. Компонент, который рендерит заголовок, абзац и кнопку, должен быть единым источником истины для текста этих элементов, а не прокси, указывающим на строки, хранящиеся где-то в другом месте. Когда код и переводы существуют как две параллельные системы, они неизбежно начинают расходиться.
Что, если бы библиотека работала наоборот — подстраивалась под ваш код, а не требовала перестраивать его? Вот как должна выглядеть i18n.
import { T } from 'gt-next';
function Hero() {
return (
<T>
<h1>Ship your product worldwide</h1>
<p>Reach every market without rewriting your app.</p>
</T>
);
}Оберните JSX в <T>. Английский текст остаётся именно там, где вы его написали. Если пользователь откроет страницу на испанском или японском, содержимое внутри <T> будет переведено — вместе со структурой, форматированием и всем остальным.
Никаких ключей. Никаких JSON-файлов. Никаких перекрёстных ссылок. Единственный источник истины — ваш код.
Сетап
Приведённый выше синтаксис взят из gt-next — библиотеки i18n с открытым исходным кодом для Next.js App Router. Для начала достаточно одной команды:
npx gt@latest initМастер настройки устанавливает зависимости, оборачивает конфигурацию Next.js с помощью withGTConfig, добавляет GTProvider в корневой layout, создаёт gt.config.json с вашими локалями, настраивает dev API-ключи для горячей перезагрузки переводов и хранение переводов в CDN — и всё это в интерактивном режиме.
После этого оберните содержимое в <T>, запустите dev-сервер и используйте компонент <LocaleSelector>, чтобы переключаться между языками:
import { LocaleSelector } from 'gt-next';
function Header() {
return (
<header>
<nav>{/* ... */}</nav>
<LocaleSelector />
</header>
);
}В режиме разработки переводы выполняются по запросу, поэтому вы можете сразу увидеть своё приложение на любом языке.
Развертывание
В рабочей среде переводы генерируются заранее.
-
Получите production API key на dash.generaltranslation.com. production-ключи начинаются с
gtx-api-(в отличие от ключейgtx-dev-, которые используются локально). -
Добавьте шаг
translateв процесс сборки:
{
"scripts": {
"build": "npx gt translate --publish && next build"
}
}Команда translate сканирует вашу кодовую базу на наличие всех использований <T>, генерирует переводы и публикует их в CDN. При сборке приложения все локали уже готовы.
Следующие шаги
- Переменные компоненты — работайте с динамическим содержимым внутри
<T>с помощью<Var>,<Num>и<Currency> - Компоненты ветвления — условно отображайте содержимое в зависимости от локали с помощью
<Plural>и<Branch> useGTиgetGT— переводите простые строки для атрибутов, заполнителей и метаданных- Автономный режим — используйте gt-next без платформы General Translation