Задайте питання та отримайте підсумок документа, вказавши цю сторінку та обраного вами постачальника штучного інтелекту
Історія версій
- "Додати порівняння зірок GitHub"v8.9.818.05.2026
- "Ініціалізація бенчмарку"v8.7.506.01.2026
Вміст цієї сторінки перекладено за допомогою штучного інтелекту.
Переглянути останню версію оригінального вмісту англійськоюЯкщо у вас є ідея щодо покращення цієї документації, будь ласка, долучіться, надіславши pull request на GitHub.
Посилання на документацію на GitHubСкопіювати документацію у форматі Markdown в буфер обміну
Бібліотеки i18n для Next.js - Звіт бенчмарку 2026
Ця сторінка є звітом бенчмарку i18n рішень на Next.js.
Зміст
Інтерактивний бенчмарк
Динамічне завантаження JSON
Ледаче завантаження перекладів під час виконання
Обмежений JSON (простори імен)
Простори імен перекладу для кожної сторінки
Бенчмарк продуктивності I18n
Що це за метрика?
Загальний стиснений у gzip розмір пакета бібліотеки інтернаціоналізації. Він включає лише провайдер та логіку отримання контенту після tree-shaking та мініфікації.
Чому це важливо?
Менший розмір бібліотеки зменшує початкове завантаження JavaScript, що призводить до швидшого завантаження та виконання на клієнті.
Перегляд як
Довідка за результатами:
Переглянути повні дані бенчмарку
Дивіться повний репозиторій бенчмарку тут.
Вступ
Бібліотеки інтернаціоналізації мають великий вплив на ваш додаток. Основний ризик полягає в завантаженні контенту для кожної сторінки та кожної мови, коли користувач відвідує лише одну сторінку.
У міру зростання додатку розмір бандла може збільшуватися в геометричній прогресії, що помітно погіршує продуктивність.
Наприклад, у найгірших випадках інтернаціоналізована сторінка може стати майже в 4 рази важчою.
Інший вплив бібліотек i18n - сповільнення розробки. Перетворення компонентів на багатомовний контент багатьма мовами займає багато часу.
Оскільки це складна проблема, існує багато рішень - деякі зосереджені на DX (досвід розробника), інші на продуктивності чи масштабованості тощо.
Intlayer намагається оптимізувати всі ці параметри одночасно.
TL;DR
- Intlayer та next-translate: Найкращі варіанти для продуктивності Next.js, що забезпечують мінімальний розмір та найкращу підтримку статичного рендерингу.
- next-intl: Найбільш трендовий варіант на сьогодні, проте він важкий і складний в оптимізації для великих додатків.
- next-i18next: Популярний та багатий на плагіни, але має значну вагу бандла (~3× Intlayer).
- Уникайте: gt-next та lingo.dev через серйозні проблеми з продуктивністю, прив'язку до вендора (vendor lock-in) та критичні помилки при збірці.
Протестуйте свій додаток
Щоб виявити ці проблеми, я створив безкоштовний сканер, який ви можете спробувати тут.
Проблема
Є два основні способи обмежити вплив багатомовного додатку на ваш бандл:
- Розділіть ваш JSON (або контент) на файли / змінні / простори імен (namespaces), щоб бандлер міг видалити невикористаний контент (tree-shaking) для конкретної сторінки.
- Динамічно завантажуйте контент сторінки лише мовою користувача.
Технічні обмеження цих підходів:
Динамічне завантаження
Навіть якщо ви оголошуєте роути на кшталт [locale]/page.tsx, використовуючи Webpack або Turbopack, і навіть якщо визначено generateStaticParams, бандлер не розглядає locale як статичну константу. Це означає, що він може додати контент для всіх мов у кожну сторінку. Основний спосіб обмежити це - завантажувати контент через динамічний імпорт (наприклад, import('./locales/${locale}.json')).
На етапі збірки (build time) Next.js створює один JS-бандл на локаль (наприклад, ./locales_uk_12345.js). Після того, як сайт надіслано клієнту, під час запуску сторінки браузер робить додатковий HTTP-запит для потрібного JS-файлу.
Інший спосіб вирішення цієї проблеми - використанняfetch()для динамічного завантаження JSON. Саме так працюєTolgee, коли JSON знаходиться в/public, абоnext-translate, який використовуєgetStaticPropsдля завантаження контенту. Процес ідентичний: браузер робить додатковий HTTP-запит для завантаження ресурсу.
Розділення контенту (Content splitting)
Якщо ви використовуєте синтаксис на кшталт const t = useTranslation() + t('my-object.my-sub-object.my-key'), зазвичай весь JSON має бути в бандлі, щоб бібліотека могла розібрати його та знайти ключ. Велика частина цього контенту передається клієнту, навіть якщо вона не використовується на сторінці.
Щоб мінімізувати це, деякі бібліотеки просять вас вказувати для кожної сторінки, які простори імен завантажувати - наприклад, next-i18next, next-intl, lingui, next-translate, next-international.
Натомість Paraglide додає додатковий крок перед збіркою, щоб перетворити JSON на плоскі символи на кшталт const en_my_var = () => 'my value'. Теоретично це дозволяє видаляти невикористаний контент на сторінці. Як ми побачимо, цей метод все одно має свої компроміси.
Нарешті, Intlayer застосовує оптимізацію на етапі збірки, завдяки чому useIntlayer('my-key') замінюється безпосередньо відповідним контентом.
Методологія
Для цього бенчмарку ми порівняли наступні бібліотеки:
Base App(Без бібліотеки i18n)next-intlayer(v8.7.12)next-i18next(v16.0.5)next-intl(v4.9.1)@lingui/core(v5.3.0)next-translate(v3.1.2)next-international(v1.3.1)@inlang/paraglide-js(v2.15.1)@tolgee/react(v7.0.0)@lingo.dev/compiler(v0.4.0)wuchale(v0.22.11)gt-next(v6.16.5)
Я використовував Next.js версії 16.2.4 з App Router.
Я побудував багатомовний додаток з 10 сторінками та 10 мовами.
Я порівняв чотири стратегії завантаження:
| Стратегія | Без просторів імен (global) | З просторами імен (scoped) |
|---|---|---|
| Статичне завантаження | Static: Все в пам'яті при запуску. | Scoped static: Розділено за простором імен; завантаження при запуску. |
| Динамічне завантаження | Dynamic: Завантаження за запитом для кожної локалі. | Scoped dynamic: Гранулярне завантаження за простором імен та локаллю. |
Резюме стратегій
- Static: Просто; відсутня мережева затримка після початкового завантаження. Мінус: великий розмір бандла.
- Dynamic: Зменшує початкову вагу (lazy-loading). Ідеально, якщо у вас багато локалей.
- Scoped static: Зберігає код структурованим (логічний поділ) без складних мережевих запитів.
- Scoped dynamic: Найкращий підхід для розділення коду (code splitting) та продуктивності. Мінімізує обсяг пам'яті, завантажуючи лише те, що потрібно поточному перегляду та активній локалі.
Що я вимірював:
Я запускав один і той самий багатомовний додаток у реальному браузері для кожного стеку, а потім фіксував, що насправді передавалося через мережу і скільки часу це займало. Розміри вказані після звичайного веб-стиснення, оскільки це ближче до того, що люди реально завантажують.
Розмір бібліотеки інтернаціоналізації: Після складання, видалення невикористаного коду та мінімізації, розмір бібліотеки i18n - це розмір коду провайдерів (наприклад,
NextIntlClientProvider) + хуків (наприклад,useTranslations) у порожньому компоненті. Це не включає завантаження файлів перекладу. Це показує, наскільки "важкою" є бібліотека ще до появи вашого контенту.JavaScript на сторінку: Для кожного тестового маршруту - скільки скриптів завантажує браузер під час візиту, усереднено за сторінками тесту (і за локалями). Важкі сторінки - це повільні сторінки.
Витік з інших локалей (Leakage): Це контент тієї ж сторінки, але іншою мовою, який помилково завантажується на сторінку, що перевіряється. Цей контент є зайвим і його слід уникати (наприклад, контент сторінки
/fr/aboutу бандлі сторінки/en/about).Витік з інших маршрутів: Та ж ідея для інших екранів у додатку: чи передаються їхні тексти, коли ви відкрили лише одну сторінку (наприклад, контент сторінки
/en/aboutу бандлі сторінки/en/contact). Високий показник свідчить про слабке розділення або занадто широкі бандли.Середній розмір бандла компонента: Окремі UI-елементи вимірюються по одному, а не ховаються всередині одного гігантського загального показника додатку. Це показує, чи інтернаціоналізація "роздуває" звичайні компоненти. Наприклад, якщо ваш компонент перерендериться, він завантажуватиме всі ці дані з пам'яті. Прив'язка гігантського JSON до будь-якого компонента схожа на підключення великого сховища невикористаних даних, що сповільнює продуктивність ваших компонентів.
Швидкість реакції на перемикання мови: Я перемикаю мову через власний інтерфейс додатку і заміряю час до моменту, коли сторінка явно оновилася - те, що помітить відвідувач.
Робота рендерингу після зміни мови: Вужчий показник: скільки зусиль витратив інтерфейс на перемальовування для нової мови після початку перемикання. Корисно, коли "відчутний" час і вартість роботи фреймворку відрізняються.
Час початкового завантаження сторінки: Від навігації до моменту, коли браузер вважає сторінку повністю завантаженою для тестованих сценаріїв. Корисно для порівняння "холодних стартів".
Час гідратації (Hydration): Час, який клієнт витрачає на перетворення HTML від сервера на інтерактивний інтерфейс. Прочерк у таблицях означає, що дана реалізація не надала надійних даних про гідратацію в цьому тесті.
Зірки на GitHub
Зірки на GitHub є потужним індикатором популярності проекту, довіри спільноти та довгострокової актуальності. Хоча вони не є прямим показником технічної якості, вони відображають, скільки розробників вважають проект корисним, стежать за його розвитком і, ймовірно, впровадять його. Для оцінки цінності проекту зірки допомагають порівняти інтерес до альтернатив і дають уявлення про зростання екосистеми.
Детальні результати
1 - Рішення, яких слід уникати
Деяких рішень, таких як gt-next або lingo.dev, явно варто уникати. Вони поєднують прив'язку до вендора (vendor lock-in) із засміченням кодової бази. Попри багато годин спроб їх впровадження, я так і не зміг забезпечити їх стабільну роботу - ні на TanStack Start, ні на Next.js.
Виявлені проблеми:
(General Translation) ([email protected]):
- Для додатку розміром 110 КБ
gt-nextдодає понад 440 КБ зайвих даних. Quota Exceeded, please upgrade your planвже під час першої збірки.- Переклади не рендериться; отримую помилку
Error: <T> used on the client-side outside of <GTProvider>, що схоже на баг бібліотеки. - При впровадженні gt-next я також стикнувся з проблемою бібліотеки: помилка
does not provide an export named 'printAST' - @formatjs/icu-messageformat-parser, через яку додаток падав. Після звіту про помилку розробник виправив її протягом 24 годин. - Бібліотека блокує статичний рендеринг сторінок Next.js.
(Lingo.dev) (@lingo.dev/[email protected]):
- Перевищено квоту AI, що повністю блокує збірку - ви не можете випустити продукт у продакшн без оплати.
- Компілятор пропустив майже 40% перекладеного контенту. Мені довелося переписувати всі
.mapу плоскі блоки компонентів, щоб це запрацювало. - Їхній CLI працює нестабільно і часто без причини скидає конфігураційний файл.
- Під час збірки він повністю стирав згенеровані JSON-файли при додаванні нового контенту. У результаті кілька ключів могли знищити понад 300 наявних.
2 - Експериментальні рішення
(Wuchale) ([email protected]):
Ідея Wuchale цікава, але поки не життєздатна. Я зіткнувся з проблемами реактивності та був змушений примусово перерендерити провайдер, щоб додаток запрацював. Документація також досить нечітка, що ускладнює освоєння.
(Paraglide) (@inlang/[email protected]):
Paraglide пропонує інноваційний, добре продуманий підхід. Проте в цьому тесті рекламований tree-shaking не спрацював для моїх налаштувань Next.js або TanStack Start. Робочий процес і DX складніші за інші варіанти.
Особисто мені не подобається необхідність регенерувати JS-файли перед кожним пушем, що створює постійний ризик конфліктів під час злиття через PR. Інструмент також здається більш орієнтованим на Vite, ніж на Next.js.
Нарешті, порівняно з іншими рішеннями, Paraglide не використовує стор (наприклад, React context) для отримання поточної локалі для рендерингу. Для кожного обробленого вузла він запитує локаль з localStorage / cookie тощо. Це призводить до виконання непотрібної логіки, що впливає на реактивність компонентів.
Примітка щодо paraglide: це рішення впроваджує код у вашу кодову базу для імпорту, в результаті чого показник 'розмір бібліотеки' у звіті бенчмарку становить майже 0. Генерація коду - це добре, оскільки функція, що використовується, міститиме лише необхідну логіку (повний префікс проти відсутності префікса, кукі проти сховища тощо). Для порівняння, Intlayer виконує цю фільтрацію через впровадження змінних середовища під час збірки, щоб змусити бандлер видалити невикористаний контент (tree-shaking) залежно від логіки. Завдяки цьому paraglide та intlayer виявляються в 6-10 разів легшими рішеннями, ніж i18next або next-intl.
3 - Прийнятні рішення
(Tolgee) (@tolgee/[email protected]):
Tolgee вирішує багато зі згаданих проблем. Проте його впровадження здалося мені складнішим, ніж у подібних інструментів. Він не забезпечує типізацію (type safety), що ускладнює виявлення відсутніх ключів під час компіляції. Мені довелося обертати функції Tolgee своїми, щоб додати перевірку відсутніх ключів.
(Next Intl) ([email protected]):
next-intl - це зараз найбільш трендовий варіант, який ШІ-агенти просувають найбільше, але на мій погляд, помилково. Почати роботу легко. На практиці ж оптимізація для обмеження витоків є складною. Поєднання динамічного завантаження + просторів імен + типів TypeScript сильно сповільнює розробку. Пакет також досить важкий (~13 КБ для NextIntlClientProvider + useTranslations, що майже вдвічі більше за next-intlayer). next-intl раніше блокував статичний рендеринг сторінок Next.js. Він надає хелпер setRequestLocale(). Здається, це частково вирішено для централізованих файлів на кшталт en.json / fr.json, але статичний рендеринг все одно ламається, коли контент розділений на простори імен, як-от en/shared.json / fr/shared.json / es/shared.json.
(Next I18next) ([email protected]):
next-i18next - це, мабуть, найпопулярніший варіант, оскільки він був одним із перших рішень i18n для JS-додатків. Він має багато плагінів від спільноти. Має ті ж основні недоліки, що й next-intl. Пакет особливо важкий (~18 КБ для I18nProvider + useTranslation, приблизно втричі більше за next-intlayer).
Формати повідомлень також відрізняються: next-intl використовує ICU MessageFormat, тоді як i18next використовує власний формат.
(Next International) ([email protected]):
next-international також намагається вирішити ці проблеми, але не сильно відрізняється від next-intl або next-i18next. Він включає scopedT() для перекладів у межах просторів імен, але це практично не впливає на розмір бандла.
(Lingui) (@lingui/[email protected]):
Lingui часто хвалять. Особисто мені робочий процес із lingui extract / lingui compile здався складнішим за альтернативи, без явних переваг. Також помічені непослідовні синтаксиси, які плутають ШІ (наприклад, t(), t'', i18n.t(), <Trans>).
4 - Рекомендації
(Next Translate) ([email protected]):
next-translate - моя основна рекомендація, якщо вам подобається API у стилі t(). Він елегантно працює через next-translate-plugin, завантажуючи простори імен через getStaticProps за допомогою завантажувача Webpack / Turbopack. Це також найлегший варіант у цьому списку (~2.5 КБ). Визначення просторів імен для сторінок або маршрутів у конфігу добре продумане і простіше в підтримці, ніж у next-intl чи next-i18next. У версії 3.1.2 я помітив, що статичний рендеринг не працював; Next.js переходив до динамічного рендерингу.
(Intlayer) ([email protected]):
Я не буду особисто оцінювати next-intlayer заради об’єктивності, оскільки це моє власне рішення.
Особиста замітка
Ця замітка є особистою і не впливає на результати бенчмарку. Тим не менш, у світі i18n часто спостерігається консенсус навколо використання const t = useTranslation('xx') + <>{t('xx.xx')}</> для перекладеного контенту.
У React-додатках передача функції як ReactNode, на мою думку, є антипатерном. Це також додає зайвої складності та накладних витрат на виконання JavaScript (навіть якщо це ледь помітно).