Назад

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

General Translation avatarGeneral Translation
Ernest McCarter avatarErnest McCarter
guideinternationalizationnextjsi18ngt-nextapp-routertutorial

Интернационализация в JavaScript устоялась вокруг несовершенной практики: вынести каждую пользовательскую строку интерфейса в JSON‑файл, назначить ей ключ и ссылаться на этот ключ из компонентов. t('home.hero.title') вместо самого текста. Ваш UI живёт в одном месте, ваш контент — в другом.

Это работает. Тысячи приложений выпускаются именно так. Но для разработчиков это не лучший опыт.

Чтение 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 gtx-cli@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 gtx-cli translate --publish && next build"
  }
}

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

Дальнейшие шаги