---
createdAt: 2025-11-24
updatedAt: 2025-11-24
title: Компилятор против декларативного i18n
description: Исследование архитектурных компромиссов между «магической» интернационализацией на основе компилятора и явным декларативным управлением контентом.
keywords:
  - Intlayer
  - Интернационализация
  - Блог
  - Next.js
  - JavaScript
  - React
  - i18n
  - Компилятор
  - Декларативный
slugs:
  - blog
  - compiler-vs-declarative-i18n
---

# Аргументы за и против интернационализации на основе компилятора

Если вы разрабатываете веб-приложения более десяти лет, вы знаете, что интернационализация (i18n) всегда была проблемной зоной. Это часто та задача, которую никто не хочет выполнять, извлечение строк, управление JSON-файлами и заботы о правилах множественного числа.

В последнее время появилась новая волна **инструментов i18n на основе компилятора**, обещающих избавить от этих проблем. Обещание звучит заманчиво: **просто пишите текст в своих компонентах, а остальное пусть делает сборщик.** Без ключей, без импортов, просто магия.

Но, как и со всеми абстракциями в программной инженерии, магия имеет свою цену.

В этом блоге мы рассмотрим переход от декларативных библиотек к подходам на основе компилятора, скрытые архитектурные долги, которые они вносят, и почему «скучный» способ может оставаться лучшим для профессиональных приложений.

## Содержание

<TOC/>

## Краткая история интернационализации

Чтобы понять, где мы сейчас, нужно оглянуться назад, с чего мы начинали.

Около 2011–2012 годов ландшафт JavaScript был кардинально другим. Такие сборщики, как мы их знаем сегодня (Webpack, Vite), либо не существовали, либо только начинали развиваться. Мы склеивали скрипты прямо в браузере. В эту эпоху появились библиотеки, такие как **i18next**.

Они решили проблему единственным возможным на тот момент способом: **словарями во время выполнения**. Вы загружали огромный JSON-объект в память, и функция искала ключи на лету. Это было надежно, явно и работало везде.

Перенесемся в сегодняшний день. У нас есть мощные компиляторы (SWC, сборщики на базе Rust), которые могут парсить абстрактные синтаксические деревья (AST) за миллисекунды. Эта мощь породила новую идею: _Почему мы вручную управляем ключами? Почему компилятор не может просто увидеть текст "Hello World" и заменить его за нас?_

Так родилась интернационализация на основе компилятора.

> **Пример компиляторного i18n:**
>
> - Paraglide (Модули с tree-shaking, которые компилируют каждое сообщение в маленькую ESM-функцию, чтобы сборщики могли автоматически исключать неиспользуемые локали и ключи. Вы импортируете сообщения как функции вместо поиска по строковым ключам.)
> - LinguiJS (Компилятор макросов в функции, который переписывает макросы сообщений, такие как `<Trans>`, в обычные вызовы JS-функций во время сборки. Вы получаете синтаксис ICU/MessageFormat с очень маленьким runtime-футпринтом.)
> - Lingo.dev (Сфокусирован на автоматизации локализационного пайплайна путем внедрения переведенного контента непосредственно во время сборки вашего React-приложения. Может автоматически генерировать переводы с помощью ИИ и интегрироваться напрямую в CI/CD.)
> - Wuchale (Препроцессор, ориентированный на Svelte, который извлекает встроенный текст из файлов .svelte и компилирует его в функции перевода без обёрток. Избегает использования строковых ключей и полностью отделяет логику извлечения контента от основного runtime приложения.)
> - Intlayer (Компилятор / CLI для извлечения, который парсит ваши компоненты, генерирует типизированные словари и может опционально переписывать код для использования явного контента Intlayer. Цель, использовать компилятор для скорости, сохраняя декларативное, фреймворк-независимое ядро.)

> **Пример декларативного i18n:**
>
> - i18next / react-i18next / next-i18next (Зрелый промышленный стандарт, использующий JSON-словарь во время выполнения и обширную экосистему плагинов)
> - react-intl (Часть библиотеки FormatJS, ориентированная на стандартный синтаксис сообщений ICU и строгий формат данных)
> - next-intl (Оптимизирован специально для Next.js с интеграцией для App Router и React Server Components)
> - vue-i18n / @nuxt/i18n (Стандартное решение экосистемы Vue, предлагающее блоки перевода на уровне компонентов и тесную интеграцию реактивности)
> - svelte-i18n (Легковесная обёртка вокруг Svelte stores для реактивных переводов во время выполнения)
> - angular-translate (Устаревшая библиотека динамического перевода, основанная на поиске ключей во время выполнения, а не на слиянии во время сборки)
> - angular-i18n (Нативный подход Angular с предварительной компиляцией, объединяющий XLIFF файлы непосредственно в шаблоны во время сборки)
> - Tolgee (Сочетает декларативный код с SDK для редактирования «клик для перевода» непосредственно в интерфейсе)
> - Intlayer (Подход на уровне компонентов с использованием файлов деклараций контента, обеспечивающих нативный tree-shaking и валидацию TypeScript)

## Компилятор Intlayer

Хотя **Intlayer**, это решение, которое в основе своей поощряет **декларативный подход** к вашему контенту, оно включает компилятор, который помогает ускорить разработку или облегчить быстрое прототипирование.

Компилятор Intlayer проходит по AST (Абстрактному Синтаксическому Дереву) ваших компонентов React, Vue или Svelte, а также других файлов JavaScript/TypeScript. Его задача, обнаруживать жестко закодированные строки и извлекать их в выделенные декларации `.content`.

> Для получения дополнительной информации ознакомьтесь с документацией: [Документация компилятора Intlayer](https://github.com/aymericzip/intlayer/blob/main/docs/docs/ru/compiler.md)

## Привлекательность компилятора (подход "магии")

Есть причина, по которой этот новый подход становится популярным. Для разработчика опыт кажется невероятным.

### 1. Скорость и "поток"

Когда вы полностью погружены в работу, остановка, чтобы придумать семантическое имя переменной (`home_hero_title_v2`), прерывает ваш поток. С подходом компилятора вы просто вводите `<p>Welcome back</p>` и продолжаете работать. Трения нет.

### 2. Миссия по спасению наследия

Представьте, что вы унаследовали огромную кодовую базу с 5000 компонентов и отсутствием переводов. Переделка этого с помощью ручной системы на основе ключей, это кошмар, который может занять месяцы. Инструмент на основе компилятора выступает как спасательная стратегия, мгновенно извлекая тысячи строк без необходимости вручную трогать ни один файл.

### 3. Эра ИИ

Это современное преимущество, которое не стоит упускать из виду. Помощники по программированию на базе ИИ (такие как Copilot или ChatGPT) естественным образом генерируют стандартный JSX/HTML. Они не знают вашу конкретную схему ключей перевода.

- **Декларативный подход:** Вам нужно переписать вывод ИИ, чтобы заменить текст на ключи.
- **Компилятор:** Вы копируете и вставляете код ИИ, и он просто работает.

## Проверка реальности: почему «магия» опасна

Хотя «магия» и привлекательна, абстракция протекает. Полагаться на инструмент сборки, чтобы он понимал человеческие намерения, приводит к архитектурной хрупкости.

### Эвристическая хрупкость (Игра в угадайку)

Компилятору приходится угадывать, что является контентом, а что, кодом. Это приводит к крайним случаям, когда вы в итоге «боретесь» с инструментом.

Рассмотрим эти сценарии:

- Будет ли извлечён `<span className="active"></span>`? (Это строка, но скорее всего класс).
- Будет ли извлечён `<span status="pending"></span>`? (Это значение пропса).
- Будет ли извлечён `<span>{"Hello World"}</span>`? (Это JS-выражение).
- Будет ли извлечён `<span>Hello {name}. How are you?</span>`? (Интерполяция сложна).
- Будет ли извлечён `<span aria-label="Image of cat"></span>`? (Атрибуты доступности требуют перевода).
- Извлекается ли `<span data-testid="my-element"></span>`? (Идентификаторы тестов НЕ должны переводиться).
- Извлекается ли `<MyComponent errorMessage="An error occurred" />`?
- Извлекается ли `<p>This is a paragraph{" "}\n containing multiple lines</p>`?
- Извлекается ли результат функции `<p>{getStatusMessage()}</p>`?
- Извлекается ли `<div>{isLoading ? "The page is loading" : <MyComponent/>} </div>`?
- Извлекается ли идентификатор продукта, например `<span>AX-99</span>`?

В конечном итоге вы неизбежно добавляете специальные комментарии (например, `// ignore-translation` или специальные пропсы, такие как `data-compiler-ignore="true"`), чтобы предотвратить нарушение логики вашего приложения.

### Как Intlayer справляется с этой сложностью?

Intlayer использует смешанный подход для определения, следует ли извлекать поле для перевода, пытаясь минимизировать ложные срабатывания:

1.  **Анализ AST:** Проверяется тип элемента (например, различие между `reactNode`, `label` или пропсом `title`).
2.  **Распознавание шаблонов:** Определяется, начинается ли строка с заглавной буквы или содержит пробелы, что указывает на то, что это, скорее всего, читаемый человеком текст, а не идентификатор кода.

### Жесткое ограничение для динамических данных

Извлечение компилятором основано на **статическом анализе**. Он должен видеть буквальную строку в вашем коде, чтобы сгенерировать стабильный ID.
Если ваш API возвращает строку с кодом ошибки, например `server_error`, вы не можете перевести её с помощью компилятора, потому что компилятор не знает о существовании этой строки во время сборки. Вам придется создать вторичную систему, работающую только во время выполнения, специально для динамических данных.

### Отсутствие разбиения на чанки

Некоторые компиляторы не разбивают переводы по страницам. Если ваш компилятор генерирует большой JSON-файл на каждый язык (например, `./lang/en.json`, `./lang/fr.json` и т.д.), вы, вероятно, загрузите контент со всех ваших страниц при посещении только одной. Кроме того, каждый компонент, использующий ваш контент, скорее всего, будет гидратирован с гораздо большим объемом данных, чем необходимо, что потенциально может вызвать проблемы с производительностью.

Также будьте осторожны с динамической загрузкой переводов. Если этого не сделать, вы загрузите контент для всех языков помимо текущего.

> Чтобы проиллюстрировать проблему, рассмотрим сайт с 10 страницами и 10 языками (все 100% уникальные). Вы загрузите контент для 99 дополнительных страниц (10 × 10 - 1).

### «Взрыв чанков» и сетевые водопады

Для решения проблемы чанкинга некоторые решения предлагают делить контент на чанки по компонентам или даже по ключам. Однако проблема решается лишь частично. Основным аргументом в пользу таких решений часто является утверждение «Ваш контент подвергается tree-shaking».

Действительно, если вы загружаете контент статически, ваше решение удалит неиспользуемый контент, но в итоге вы всё равно загрузите контент со всех языков вместе с вашим приложением.

Так почему бы не загружать контент динамически? Да, в этом случае вы загрузите больше, чем необходимо, но это не обходится без компромиссов.

Динамическая загрузка контента изолирует каждый фрагмент контента в отдельный чанк, который загружается только при рендеринге компонента. Это означает, что вы будете делать один HTTP-запрос на каждый текстовый блок. 1000 текстовых блоков на вашей странице? → 1000 HTTP-запросов к вашим серверам. И чтобы ограничить ущерб и оптимизировать время первого рендера вашего приложения, вам придется вставить несколько границ Suspense или Skeleton Loaders.

> Примечание: Даже с Next.js и SSR ваши компоненты будут гидратированы после загрузки, поэтому HTTP-запросы все равно будут сделаны.

Решение? Использовать подход, который позволяет объявлять локализованные декларации контента, как это делают `i18next`, `next-intl` или `intlayer`.

> Примечание: `i18next` и `next-intl` требуют от вас вручную управлять импортами пространств имён / сообщений для каждой страницы, чтобы оптимизировать размер бандла. Рекомендуется использовать анализатор бандлов, такой как `rollup-plugin-visualizer` (vite), `@next/bundle-analyzer` (next.js) или `webpack-bundle-analyzer` (React CRA / Angular / и т.д.), чтобы определить, не засоряете ли вы бандл неиспользуемыми переводами.

### Накладные расходы на производительность во время выполнения

Чтобы сделать переводы реактивными (чтобы они обновлялись мгновенно при переключении языков), компилятор часто внедряет хуки управления состоянием в каждый компонент.

- **Стоимость:** Если вы рендерите список из 5000 элементов, вы инициализируете 5000 хуков `useState` и `useEffect` исключительно для текста. React должен идентифицировать и повторно отрендерить всех 5000 потребителей одновременно. Это вызывает масштабную блокировку "Главного потока", замораживая UI во время переключения. Это потребляет память и ресурсы CPU, которые декларативные библиотеки (которые обычно используют одного провайдера Context) экономят.

> Обратите внимание, что подобная проблема возникает и в других фреймворках, не только в React.

## Ловушка: Зависимость от поставщика

Будьте осторожны при выборе решения для i18n, которое позволяет извлекать или мигрировать ключи переводов.

В случае декларативной библиотеки ваш исходный код явно содержит ваш переводческий замысел: это ваши ключи, и вы ими управляете. Если вы хотите сменить библиотеку, обычно достаточно просто обновить импорт.

При использовании подхода с компилятором ваш исходный код может содержать просто обычный английский текст, без следов логики перевода: всё скрыто в конфигурации сборочного инструмента. Если этот плагин перестанет поддерживаться или вы захотите сменить решение, вы можете оказаться в затруднительном положении. Нет простого способа «выйти»: в вашем коде нет используемых ключей, и возможно, вам придётся заново сгенерировать все переводы для новой библиотеки.

Некоторые решения также предлагают сервисы генерации переводов. Нет кредитов? Нет переводов.

Компиляторы часто хешируют текст (например, `"Hello World"` -> `x7f2a`). Ваши файлы переводов выглядят как `{ "x7f2a": "Hola Mundo" }`. Ловушка: если вы смените библиотеку, новая библиотека увидит `"Hello World"` и будет искать этот ключ. Она не найдёт его, потому что ваш файл перевода заполнен хешами (`x7f2a`).

### Привязка к платформе

Выбирая подход на основе компилятора, вы привязываете себя к базовой платформе. Например, некоторые компиляторы недоступны для всех бандлеров (таких как Vite, Turbopack или Metro). Это может затруднить будущие миграции, и вам может потребоваться принять несколько решений для покрытия всех ваших приложений.

## Обратная сторона: риски декларативного подхода

Если быть честным, традиционный декларативный способ тоже не идеален. У него есть свои "подводные камни".

1.  **Адская путаница с пространствами имён:** часто приходится вручную управлять тем, какие JSON-файлы загружать (`common.json`, `dashboard.json`, `footer.json`). Если вы забудете один, пользователь увидит необработанные ключи.
2.  **Перегрузка запросов:** Без тщательной настройки очень легко случайно загрузить _все_ ваши ключи перевода для _всех_ страниц при первоначальной загрузке, что увеличивает размер вашего бандла.
3.  **Синхронизационный дрейф:** Часто ключи остаются в JSON-файле долго после того, как компонент, использующий их, был удалён. Ваши файлы перевода растут бесконечно, заполненные "зомби-ключами".

## Средний путь Intlayer

Именно здесь инструменты, такие как **Intlayer**, пытаются внести инновации. Intlayer понимает, что хотя компиляторы мощные, неявная магия опасна.

Intlayer предлагает смешанный подход, позволяющий вам воспользоваться преимуществами обоих подходов: декларативное управление контентом, также совместимое с его компилятором для экономии времени разработки.

И даже если вы не используете компилятор Intlayer, Intlayer предлагает команду `transform` (также доступную через расширение VSCode). Вместо того чтобы просто делать магию на скрытом этапе сборки, она фактически **переписывает ваш код компонента**. Она сканирует ваш текст и заменяет его явными декларациями контента в вашей кодовой базе.

Это дает вам лучшее из обоих миров:

1.  **Гранулярность:** Вы держите переводы рядом с вашими компонентами (улучшая модульность и tree-shaking).
2.  **Безопасность:** Перевод становится явным кодом, а не скрытой магией времени сборки.
3.  **Отсутствие привязки:** Поскольку код преобразуется в декларативную структуру внутри вашего репозитория, вы можете легко нажать tab или использовать copilot вашего IDE для генерации деклараций контента, вы не прячете логику в плагине webpack.

## Заключение

Итак, что же выбрать?

**Если вы создаёте MVP или хотите двигаться быстро:**
Подход на основе компилятора, это разумный выбор. Он позволяет двигаться невероятно быстро. Вам не нужно беспокоиться о структуре файлов или ключах. Просто создавайте. Технический долг, это проблема для «будущего вас».

**Если вы младший разработчик или не заботитесь об оптимизации:**
Если вы хотите минимизировать ручное управление, подход на основе компилятора, вероятно, лучший вариант. Вам не нужно будет самостоятельно обрабатывать ключи или файлы переводов, просто пишите текст, а компилятор автоматизирует остальное. Это снижает усилия по настройке и уменьшает распространённые ошибки i18n, связанные с ручными шагами.

**Если вы интернационализируете существующий проект, который уже включает тысячи компонентов для рефакторинга:**
Компиляторный подход может быть прагматичным выбором в этом случае. Начальная фаза извлечения может сэкономить недели или месяцы ручной работы. Однако рассмотрите возможность использования инструмента, такого как команда `transform` в Intlayer, которая может извлекать строки и преобразовывать их в явные декларативные объявления контента. Это дает вам скорость автоматизации при сохранении безопасности и переносимости декларативного подхода. Вы получаете лучшее из обоих миров: быструю начальную миграцию без долгосрочного архитектурного долга.

**Если вы создаете профессиональное приложение корпоративного уровня:**
Магия обычно, плохая идея. Вам нужен контроль.

- Вам нужно обрабатывать динамические данные с бэкендов.
- Вам нужно обеспечить производительность на устройствах с низкими характеристиками (избегая взрывов хуков).
- Вам нужно убедиться, что вы не окажетесь навсегда привязаны к конкретному инструменту сборки.

Для профессиональных приложений **Декларативное управление контентом** (например, Intlayer или проверенные библиотеки) остается золотым стандартом. Оно разделяет ваши задачи, сохраняет архитектуру чистой и гарантирует, что способность вашего приложения поддерживать несколько языков не зависит от «черного ящика» компилятора, который пытается угадать ваши намерения.
