Назад

Вы неправильно делаете i18n в Next.js

Team avatarTeam
Ernest McCarter avatarErnest McCarter
guideinternationalizationnextjsi18ngt-nextapp-routertutorial

В интернационализации 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>
  );
}

В режиме разработки переводы выполняются по запросу, поэтому вы можете сразу увидеть своё приложение на любом языке.

Развертывание

В рабочей среде переводы генерируются заранее.

  1. Получите production API key на dash.generaltranslation.com. production-ключи начинаются с gtx-api- (в отличие от ключей gtx-dev-, которые используются локально).

  2. Добавьте шаг translate в процесс сборки:

{
  "scripts": {
    "build": "npx gt translate --publish && next build"
  }
}

Команда translate сканирует вашу кодовую базу на наличие всех использований <T>, генерирует переводы и публикует их в CDN. При сборке приложения все локали уже готовы.

Следующие шаги