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

# Аргументи за та проти компіляторного i18n

Якщо ви будуєте вебдодатки більше ніж десятиліття, ви знаєте, що інтернаціоналізація (i18n) завжди була точкою тертя. Зазвичай це завдання, яке ніхто не хоче виконувати — витягування рядків, керування JSON-файлами та турботи про правила множини.

Нещодавно з'явилася нова хвиля **"Compiler-based" i18n tools**, які обіцяють зробити цей біль минулим. Пропозиція приваблива: **Просто вписуйте текст у ваші компоненти, а build tool зробить усе інше.** Жодних ключів, жодних імпортів — лише магія.

Але, як і з усіма абстракціями в software engineering, магія має свою ціну.

У цьому дописі в блозі ми розглянемо перехід від декларативних бібліотек до компіляторних підходів, приховані архітектурні борги, які вони породжують, і чому «нудний» підхід може досі бути найкращим для професійних застосунків.

## Зміст

<TOC/>

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

Щоб зрозуміти, де ми зараз, потрібно поглянути назад, до того, з чого ми починали.

Приблизно в 2011–2012 роках ландшафт JavaScript був значно іншим. Bundlers, як ми їх знаємо (Webpack, Vite), не існували або були на ранніх стадіях розвитку. Ми з'єднували скрипти прямо в браузері. У цю епоху з'явилися бібліотеки на кшталт **i18next**.

Вони вирішували проблему єдиним можливим тоді способом: **Runtime Dictionaries**. Ви завантажували величезний об'єкт JSON у пам'ять, а функція шукала ключі на льоту. Це було надійно, явно і працювало скрізь.

Перенесімося в сьогодення. У нас є потужні компілятори (SWC, Rust-based bundlers), які можуть розбирати абстрактні синтаксичні дерева (AST) за мілісекунди. Ця міць породила нову ідею: _Навіщо нам вручну керувати ключами? Чому компілятор не може просто побачити текст "Hello World" і замінити його за нас?_

Отже, з'явилося Compiler-based i18n.

> **Приклад компіляторного i18n:**
>
> - Paraglide (tree-shaken модулі, які компілюють кожне повідомлення в маленьку ESM-функцію, щоб bundler-и могли автоматично видаляти невикористані локалі та ключі. Ви імпортуєте повідомлення як функції замість пошуку за рядковими ключами.)
> - LinguiJS (компілятор макросів у функції, який переписує макроси повідомлень типу `<Trans>` у звичайні виклики JS-функцій під час збірки. Ви отримуєте синтаксис ICU/MessageFormat з невеликим runtime-відбитком.)
> - Lingo.dev (зосереджується на автоматизації пайплайна локалізації шляхом інʼєкції перекладеного вмісту безпосередньо під час збірки вашого React-додатка. Може автоматично генерувати переклади з використанням AI та інтегруватися безпосередньо в CI/CD.)
> - Wuchale (Svelte-first preprocessor that extracts inline text in .svelte files and compiles it into zero-wrapper translation functions. It avoids string keys, and separates the content extraction logic completely from the main application runtime.)
> - Intlayer (Compiler / Extract CLI that parses your components, generates typed dictionaries, and can optionally rewrite code to use explicit Intlayer content. The goal is to use the compiler for velocity while keeping a declarative, framework-agnostic core.)
>
> **Приклад декларативного i18n:**
>
> - i18next / react-i18next / next-i18next (визнаний галузевий стандарт, що використовує runtime 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 з попередньою компіляцією (ahead-of-time), який зливає файли XLIFF безпосередньо в шаблони під час збірки)
> - Tolgee (Поєднує декларативний код з in-context SDK для редагування "click-to-translate" безпосередньо в UI)
> - Intlayer (Підхід на рівні компонентів, із файлами декларацій контенту, які дозволяють нативний tree-shaking і валідацію TypeScript)

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

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

Компілятор Intlayer обходить AST (Abstract Syntax Tree) ваших компонентів React, Vue або Svelte, а також інші файли JavaScript/TypeScript. Його роль — виявляти жорстко закодовані рядки та витягувати їх у спеціальні декларації `.content`.

> Для детальнішої інформації перегляньте документацію: [Документація компілятора Intlayer](https://github.com/aymericzip/intlayer/blob/main/docs/docs/uk/compiler.md)

## Принада компілятора (підхід «магія»)

Існує причина, чому цей новий підхід набирає популярності. Для розробника досвід відчувається неймовірно.

### 1. Швидкість і «потік»

Коли ви в роботі й повністю зосереджені, зупинка, щоб придумати семантичну назву змінної (`home_hero_title_v2`), порушує ваш потік. За підходу з компілятором ви просто вводите `<p>Welcome back</p>` і продовжуєте рух. Тертя відсутнє.

### 2. Місія порятунку для legacy-коду

Уявіть, що ви успадкували величезну codebase з 5 000 компонентів і жодного перекладу. Оснащення цього вручну на основі ключів — це кошмар на кілька місяців роботи. Інструмент на базі компілятора виступає як стратегія порятунку, миттєво витягуючи тисячі рядків, не вимагаючи від вас жодного ручного редагування файлів.

### 3. Ера AI

Це сучасна перевага, яку не варто ігнорувати. AI coding assistants (наприклад, Copilot або ChatGPT) природно генерують стандартний JSX/HTML. Вони не знають вашої конкретної схеми ключів для перекладів.

- **Declarative:** Вам доведеться переписувати вихідний код, згенерований AI, замінюючи текст на ключі.
- **Compiler:** Ви копіюєте та вставляєте код від AI — і все працює одразу.

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

Хоча "магія" приваблює, абстракція просочується. Залежність від інструменту збірки, який має зрозуміти людський намір, призводить до архітектурної крихкості.

### Евристична крихкість (гра вгадування)

Компілятор має вгадувати, що є контентом, а що — кодом. Це призводить до крайніх випадків, де ви в кінцевому підсумку "боретеся" з інструментом.

Розгляньте ці сценарії:

- Чи буде витягнуто `<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>`?

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

### Як Intlayer справляється з цією складністю?

Intlayer використовує комбінований підхід для визначення, чи слід витягувати поле для перекладу, намагаючись мінімізувати false positives:

1.  **Аналіз AST:** Він перевіряє тип елемента (наприклад, розрізняє `reactNode`, `label` або проп `title`).
2.  **Розпізнавання шаблонів:** Воно визначає, чи рядок починається з великої літери або містить пробіли, що вказує на те, що це, ймовірно, текст, читабельний людиною, а не ідентифікатор коду.

### Жорстке обмеження динамічних даних

Видобування компілятором покладається на **статичний аналіз**. Він повинен бачити буквальний рядок у вашому коді, щоб згенерувати стабільний ідентифікатор.
Якщо ваш API повертає рядок коду помилки на кшталт `server_error`, ви не зможете перекласти його за допомогою компілятора, оскільки компілятор не знає про існування цього рядка під час збірки. Ви змушені створити вторинну систему, що працює тільки під час виконання ("runtime-only"), тільки для динамічних даних.

### Відсутність чанкування

Деякі компілятори не розбивають переклади на чанки по сторінках. Якщо ваш компілятор генерує великий JSON-файл для кожної мови (наприклад, `./lang/en.json`, `./lang/fr.json` тощо), ймовірно, ви в кінцевому підсумку завантажите контент усіх ваших сторінок для однієї відвідуваної сторінки. Також кожен компонент, що використовує цей контент, ймовірно буде гідратований значно більшим обсягом даних, ніж необхідно, що потенційно може призвести до проблем з продуктивністю.

Також будьте уважні з динамічним завантаженням ваших перекладів. Якщо цього не робити, ви завантажите вміст для всіх мов поряд із поточною.

> Щоб проілюструвати проблему, уявіть сайт з 10 сторінками й 10 мовами (усі повністю унікальні). Ви завантажите вміст для ще 99 сторінок (10 × 10 - 1).

### «Вибух чанків» та мережеві водоспади

Щоб вирішити проблему чанкінгу, деякі рішення пропонують розбивку на чанки на рівні компонентів або навіть ключів. Проте проблема вирішується лише частково. Основний аргумент на користь таких рішень часто звучить як «Ваш контент буде tree-shaken».

Дійсно, якщо ви завантажуєте контент статично, ваше рішення видалить невикористовуваний вміст через tree-shaking, але в підсумку ви все одно опинитеся з вмістом усіх мов, завантаженим разом із вашим застосунком.

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

Динамічне завантаження контенту ізолює кожен фрагмент у власний chunk, який буде завантажений лише тоді, коли компонент відрендериться. Це означає, що ви здійсните один HTTP-запит на кожний текстовий блок. 1,000 текстових блоків на вашій сторінці? → 1,000 HTTP-запитів до ваших серверів. І щоб зменшити шкоду та оптимізувати час першого рендерингу вашого додатка, вам доведеться вставити кілька Suspense boundaries або Skeleton Loaders.

> Note: Навіть з Next.js та SSR ваші компоненти все одно будуть гідратовані після завантаження, тож HTTP-запити все одно будуть здійснені.

> Note: Щоб відстежити кожен запит чанка, ви можете перевірити вкладку `network` у інструментах розробника вашого браузера.

Рішення? Використати підхід, що дозволяє оголошувати контент з обмеженою областю дії, як це роблять `i18next`, `next-intl` або `intlayer`.

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

### Навантаження на продуктивність під час виконання

Щоб зробити переклади реактивними (щоб вони оновлювалися миттєво при зміні мови), компілятор часто вставляє хуки для керування станом у кожен компонент.

Якщо ви рендерите список з 5 000 елементів, ви ініціалізуєте 5 000 хуків `useState` і `useEffect` виключно для тексту. React має ідентифікувати та повторно відрендерити всіх 5 000 споживачів одночасно. Це спричиняє масштабне блокування "Main Thread", яке заморожує UI під час переключення. Це витрачає пам'ять і процесорні цикли, які заощаджують декларативні бібліотеки (які зазвичай використовують один провайдер `Context`).

> Зауважте, що ця проблема подібна і для інших фреймворків, не лише React.

## Пастка: Vendor Lock-in

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

У випадку декларативної бібліотеки ваш вихідний код явно містить наміри перекладу: це ваші ключі, і ви їх контролюєте. Якщо ви хочете змінити бібліотеку, зазвичай потрібно лише оновити імпорт.

З компіляторним підходом ваш вихідний код може бути просто англійським текстом, без жодних слідів логіки перекладу: усе приховано в конфігурації інструмента збірки. Якщо цей плагін перестане підтримуватися або ви захочете змінити рішення, ви можете опинитися заблокованими. Немає простого способу «eject»: у вашому коді немає придатних ключів, і, можливо, доведеться заново згенерувати всі переклади для нової бібліотеки.

Деякі рішення також пропонують сервіси генерації перекладів. Закінчилися кредити? Більше немає перекладів.

Компілери часто хешують текст (наприклад, `"Hello World"` -> `x7f2a`). Ваші файли перекладів виглядають як `{ "x7f2a": "Привіт, світ" }`. Пастка: якщо ви зміните бібліотеку, нова бібліотека побачить `"Hello World"` і шукатиме цей ключ. Вона його не знайде, бо ваш файл перекладів заповнений хешами (`x7f2a`).

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

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

Але ширше кажучи, навіть без обмежень конкретного bundler'а, кожне i18n-рішення вводить власні конвенції для оголошення та управління контентом. Внаслідок цього будь-який підхід до i18n апріорі створює певний рівень залежності від постачальника (vendor lock-in) саме цього рішення. Крім того, кілька провідних i18n-бібліотек спонсоруються, фінансуються або підтримуються компаніями, що працюють у галузі локалізації.

## Інша сторона: ризики декларативного підходу

Чесно кажучи, традиційний декларативний підхід теж не ідеальний. У нього є свої «підводні камені».

1.  **Пекло неймспейсів:** Часто доводиться вручну керувати тим, які JSON-файли завантажувати (`common.json`, `dashboard.json`, `footer.json`). Якщо забути один — користувач побачить сирі ключі.
2.  **Надмірне завантаження:** Без ретельної конфігурації дуже легко випадково завантажити _усі_ ваші ключі перекладу для _всіх_ сторінок при початковому завантаженні, що роздутує розмір вашого бандла.
3.  **Дрейф синхронізації:** Часто ключі залишаються в JSON-файлі задовго після того, як компонент, який їх використовував, був видалений. Ваші файли перекладів ростуть нескінченно, заповнені "zombie keys."

## Проміжний підхід Intlayer

Саме тут інструменти на кшталт **Intlayer** намагаються інновувати. Intlayer розуміє, що хоч компілятори й потужні, неявна магія є небезпечною.

Intlayer пропонує змішаний підхід, який дозволяє скористатися перевагами обох підходів: декларативного управління контентом, сумісного з його компілятором, щоб заощадити час розробки.

Навіть якщо ви не використовуєте компілятор Intlayer, Intlayer пропонує команду `transform` (також доступну через розширення для VSCode). Замість того, щоб робити магію в прихованому кроці збірки, вона фактично може **перезаписати код ваших компонентів**. Вона сканує ваш текст і замінює його на явні декларації контенту у вашому кодовому сховищі.

Це дає вам найкраще з обох підходів:

1.  **Гранулярність:** Ви зберігаєте переклади поруч із компонентами (підвищуючи модульність і ефективність tree-shaking).
2.  **Безпека:** Переклад стає явним кодом, а не прихованою магією на етапі збірки.
3.  **Відсутність lock-in:** Оскільки код перетворюється в декларативну структуру у вашому репозиторії, ви можете легко натиснути Tab або скористатися Copilot вашого IDE, щоб згенерувати декларації контенту — ви не ховаєте логіку в плагіні webpack.

## Висновок

Отже, що обрати?

**Якщо ви створюєте MVP або хочете рухатися швидко:**
Підхід на основі компілятора — цілком прийнятний вибір. Він дозволяє працювати надзвичайно швидко. Вам не потрібно хвилюватися про структури файлів чи ключі. Ви просто розробляєте. Технічний борг — це проблема «майбутнього вас».

**Якщо ви Junior Developer або вам не важлива оптимізація:**
Якщо ви прагнете мінімального ручного управління, підхід на основі компілятора, ймовірно, найкращий. Вам не доведеться самостійно працювати з ключами чи файлами перекладів — просто пишіть текст, а компілятор автоматизує решту. Це зменшує зусилля налаштування й типові помилки i18n, пов'язані з ручними кроками.

**Якщо ви інтернаціоналізуєте існуючий проєкт, який уже містить тисячі компонентів для рефакторингу:**
Підхід, заснований на компіляторі, може бути прагматичним вибором у цій ситуації. Початкова фаза витягування може заощадити тижні або місяці ручної роботи. Однак розгляньте використання інструмента, такого як команда Intlayer `transform`, яка може витягувати рядки й перетворювати їх на явні декларативні оголошення контенту. Це дає вам швидкість автоматизації, одночасно зберігаючи безпеку та портативність декларативного підходу. Ви отримуєте найкраще з обох світів: швидку початкову міграцію без довгострокового архітектурного боргу.

**Якщо ви розробляєте професійний додаток корпоративного рівня:**
«Магія» зазвичай — погана ідея. Вам потрібен контроль.

- Потрібно обробляти динамічні дані з бекендів.
- Потрібно забезпечити продуктивність на слабких пристроях (уникати hook explosions).
- Необхідно переконатися, що ви не залишитеся прив'язаними до конкретного інструмента збірки назавжди.

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