Введение

Каждый, кто делал мультиязычные приложения на React/Next.js, знает эту боль. Ты создаешь новый компонент, пишешь t("Submit Button"), а потом...

  1. Идешь в locales/en/common.json.

  2. Добавляешь ключ вручную.

  3. Идешь в locales/ru/common.json.

  4. Добавляешь перевод.

  5. Повторяешь для казахского языка.

А если ты хочешь разбить переводы по файлам (Namespaces)? Тогда начинается ад с импортами и путаницей в путях.

Я устал от этого. Два года я решал эту проблему с помощью самописных Gulp-скриптов. Но в 2025 году я решил: хватит. Пора делать по-взрослому.

Я переписал всё на TypeScript, использовал AST-парсинг и создал smart-i18next-cli — инструмент, который делает всю грязную работу за меня.

В этой статье расскажу, как я хакнул парсер i18next, подружил его с TFunction и заставил DeepL переводить мой проект одной командой.


Проблема 1: Ад с Неймспейсами

В больших проектах файл common.json разрастается до тысяч строк. Работать с ним невозможно.

Я хотел, чтобы структура JSON-файлов один-в-один повторяла структуру моих компонентов.

  • Файл: src/features/auth/Login.tsx

  • Нужен JSON: locales/en/features.auth.Login.json

Решение:

Стандартный i18next-cli (и старый scanner) требует явно писать useTranslation('ns'). Я же хотел, чтобы CLI сам понимал, в каком файле мы находимся.

Я написал плагин, который во время парсинга смотрит на путь файла и автоматически "привязывает" найденные ключи к нужному неймспейсу.

Проблема 2: Типизация и "Слепые" Парсеры

В TypeScript мы часто передаем функцию перевода как проп:

TypeScript

export const schema = (t: TFunction<"features.auth">) => { ... }

Проблема в том, что ни один парсер (ни i18next-scanner, ни новый i18next-cli) не видит ключи внутри таких конструкций, потому что там нет явного вызова useTranslation.

Моё решение (The "Smart" Hack):

Я не стал писать свой сложный AST-анализатор с нуля. Вместо этого я сделал "Inject" на лету.

Мой CLI читает файл, и если находит TFunction<"my.ns">, он виртуально (в памяти, не меняя файл на диске) добавляет туда код:

TypeScript

// In-memory injection
const { t } = useTranslation("my.ns");

Парсер i18next-cli "проглатывает" этот код и — вуаля! — ключи и неймспейсы определяются корректно. Это позволило добиться 100% точности экстракции без магии в рантайме.

Проблема 3: Лень переводить (Killer Feature)

Когда я добавляю новый ключ, я хочу видеть его в интерфейсе сразу, хотя бы на английском. А еще лучше — сразу на всех языках.

Я написал команду translate. Она работает так:

  1. Сканирует все JSON-файлы.

  2. Ищет ключи, где key === value (мой признак "непереведенного" ключа).

  3. Отправляет их в RapidAPI (DeepL).

  4. Сохраняет результат.

Теперь я пишу код, запускаю pnpm i18n:translate, и через секунду у меня готова казахская и русская локализация.

Итог: Архитектура v1.0.0

В итоге родился @sayyyat/smart-i18next-cli.

Это Open Source инструмент, который работает как обертка (wrapper) над официальным i18next-cli.

  • Ядро: Официальный i18next-cli (для надежного парсинга).

  • Плагин: Моя логика для 1-to-1 неймспейсинга и инъекций кода.

  • CLI: Commander.js для удобных команд init, sync, translate.

Теперь мой workflow выглядит так:

  1. Пишу код: t("Hello World").

  2. Сохраняю.

  3. CLI сам создает файл app.page.json.

  4. CLI сам генерирует строгие типы TypeScript.

Если вам тоже надоело ручное управление JSON-ами в Next.js — попробуйте.

🔗 GitHub

📦 NPM