@intlayer/docs 8.7.4 → 8.7.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/blog/de/next-i18next_vs_next-intl_vs_intlayer.md +0 -2
- package/blog/en-GB/next-i18next_vs_next-intl_vs_intlayer.md +0 -2
- package/blog/es/next-i18next_vs_next-intl_vs_intlayer.md +0 -2
- package/blog/fr/next-i18next_vs_next-intl_vs_intlayer.md +0 -2
- package/blog/id/list_i18n_technologies/frameworks/svelte.md +0 -2
- package/blog/it/next-i18next_vs_next-intl_vs_intlayer.md +0 -2
- package/blog/ja/next-i18next_vs_next-intl_vs_intlayer.md +0 -2
- package/blog/ko/next-i18next_vs_next-intl_vs_intlayer.md +0 -2
- package/blog/pl/list_i18n_technologies/frameworks/svelte.md +0 -2
- package/blog/pt/next-i18next_vs_next-intl_vs_intlayer.md +0 -2
- package/blog/ru/next-i18next_vs_next-intl_vs_intlayer.md +0 -2
- package/blog/vi/list_i18n_technologies/frameworks/svelte.md +0 -2
- package/blog/zh/next-i18next_vs_next-intl_vs_intlayer.md +0 -2
- package/dist/cjs/generated/docs.entry.cjs +60 -0
- package/dist/cjs/generated/docs.entry.cjs.map +1 -1
- package/dist/esm/generated/docs.entry.mjs +60 -0
- package/dist/esm/generated/docs.entry.mjs.map +1 -1
- package/dist/types/generated/docs.entry.d.ts +3 -0
- package/dist/types/generated/docs.entry.d.ts.map +1 -1
- package/docs/ar/benchmark/index.md +29 -0
- package/docs/ar/benchmark/nextjs.md +227 -0
- package/docs/ar/benchmark/tanstack.md +193 -0
- package/docs/ar/intlayer_with_tanstack.md +0 -2
- package/docs/de/benchmark/index.md +29 -0
- package/docs/de/benchmark/nextjs.md +227 -0
- package/docs/de/benchmark/tanstack.md +193 -0
- package/docs/en/benchmark/___NOTE.md +82 -0
- package/docs/en/benchmark/___nextjs.md +195 -0
- package/docs/en/benchmark/___tanstack.md +187 -0
- package/docs/en/benchmark/index.md +29 -0
- package/docs/en/benchmark/nextjs.md +228 -0
- package/docs/en/benchmark/tanstack.md +217 -0
- package/docs/en-GB/benchmark/index.md +29 -0
- package/docs/en-GB/benchmark/nextjs.md +228 -0
- package/docs/en-GB/benchmark/tanstack.md +193 -0
- package/docs/es/benchmark/index.md +29 -0
- package/docs/es/benchmark/nextjs.md +226 -0
- package/docs/es/benchmark/tanstack.md +193 -0
- package/docs/fr/benchmark/index.md +29 -0
- package/docs/fr/benchmark/nextjs.md +227 -0
- package/docs/fr/benchmark/tanstack.md +193 -0
- package/docs/hi/benchmark/index.md +29 -0
- package/docs/hi/benchmark/nextjs.md +227 -0
- package/docs/hi/benchmark/tanstack.md +193 -0
- package/docs/id/benchmark/index.md +29 -0
- package/docs/id/benchmark/nextjs.md +227 -0
- package/docs/id/benchmark/tanstack.md +193 -0
- package/docs/id/intlayer_with_react_native+expo.md +0 -2
- package/docs/it/benchmark/index.md +29 -0
- package/docs/it/benchmark/nextjs.md +227 -0
- package/docs/it/benchmark/tanstack.md +193 -0
- package/docs/ja/benchmark/index.md +29 -0
- package/docs/ja/benchmark/nextjs.md +227 -0
- package/docs/ja/benchmark/tanstack.md +193 -0
- package/docs/ko/benchmark/index.md +29 -0
- package/docs/ko/benchmark/nextjs.md +227 -0
- package/docs/ko/benchmark/tanstack.md +193 -0
- package/docs/ko/intlayer_with_tanstack.md +0 -2
- package/docs/pl/benchmark/index.md +29 -0
- package/docs/pl/benchmark/nextjs.md +227 -0
- package/docs/pl/benchmark/tanstack.md +193 -0
- package/docs/pt/benchmark/index.md +29 -0
- package/docs/pt/benchmark/nextjs.md +227 -0
- package/docs/pt/benchmark/tanstack.md +193 -0
- package/docs/ru/benchmark/index.md +29 -0
- package/docs/ru/benchmark/nextjs.md +227 -0
- package/docs/ru/benchmark/tanstack.md +193 -0
- package/docs/tr/benchmark/index.md +29 -0
- package/docs/tr/benchmark/nextjs.md +227 -0
- package/docs/tr/benchmark/tanstack.md +193 -0
- package/docs/uk/benchmark/index.md +29 -0
- package/docs/uk/benchmark/nextjs.md +227 -0
- package/docs/uk/benchmark/tanstack.md +193 -0
- package/docs/vi/benchmark/index.md +29 -0
- package/docs/vi/benchmark/nextjs.md +227 -0
- package/docs/vi/benchmark/tanstack.md +193 -0
- package/docs/zh/benchmark/index.md +29 -0
- package/docs/zh/benchmark/nextjs.md +227 -0
- package/docs/zh/benchmark/tanstack.md +193 -0
- package/package.json +6 -6
- package/src/generated/docs.entry.ts +60 -0
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
---
|
|
2
|
+
createdAt: 2026-04-20
|
|
3
|
+
updatedAt: 2026-04-21
|
|
4
|
+
title: Найкраще i18n рішення для Next.js у 2026 році - Звіт бенчмарку
|
|
5
|
+
description: Порівняйте бібліотеки інтернаціоналізації (i18n) для Next.js, такі як next-intl, next-i18next та Intlayer. Детальний звіт про продуктивність за розміром бандла, витоком та реактивністю.
|
|
6
|
+
keywords:
|
|
7
|
+
- benchmark
|
|
8
|
+
- i18n
|
|
9
|
+
- intl
|
|
10
|
+
- nextjs
|
|
11
|
+
- продуктивність
|
|
12
|
+
- intlayer
|
|
13
|
+
slugs:
|
|
14
|
+
- doc
|
|
15
|
+
- benchmark
|
|
16
|
+
- nextjs
|
|
17
|
+
author: Aymeric PINEAU
|
|
18
|
+
applicationTemplate: https://github.com/intlayer-org/benchmark-i18n
|
|
19
|
+
history:
|
|
20
|
+
- version: 8.7.5
|
|
21
|
+
date: 2026-01-06
|
|
22
|
+
changes: "Ініціалізація бенчмарку"
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
# Бібліотеки i18n для Next.js — Звіт бенчмарку 2026
|
|
26
|
+
|
|
27
|
+
Ця сторінка є звітом бенчмарку i18n рішень на Next.js.
|
|
28
|
+
|
|
29
|
+
## Зміст
|
|
30
|
+
|
|
31
|
+
<Toc/>
|
|
32
|
+
|
|
33
|
+
## Інтерактивний бенчмарк
|
|
34
|
+
|
|
35
|
+
<I18nBenchmark framework="nextjs" vertical/>
|
|
36
|
+
|
|
37
|
+
## Довідка за результатами:
|
|
38
|
+
|
|
39
|
+
<iframe
|
|
40
|
+
src="https://intlayer.org/markdown?url=https%3A%2F%2Fraw.githubusercontent.com%2Fintlayer-org%2Fbenchmark-i18n%2Fmain%2Freport%2Fscripts%2Fsummarize-nextjs.md"
|
|
41
|
+
width="100%"
|
|
42
|
+
height="600px"
|
|
43
|
+
style="border:none;">
|
|
44
|
+
</iframe>
|
|
45
|
+
|
|
46
|
+
> https://intlayer.org/markdown?url=https%3A%2F%2Fraw.githubusercontent.com%2Fintlayer-org%2Fbenchmark-i18n%2Fmain%2Freport%2Fscripts%2Fsummarize-nextjs.md
|
|
47
|
+
|
|
48
|
+
Дивіться повний репозиторій бенчмарку [тут](https://github.com/intlayer-org/benchmark-i18n).
|
|
49
|
+
|
|
50
|
+
## Вступ
|
|
51
|
+
|
|
52
|
+
Бібліотеки інтернаціоналізації мають великий вплив на ваш додаток. Основний ризик полягає в завантаженні контенту для кожної сторінки та кожної мови, коли користувач відвідує лише одну сторінку.
|
|
53
|
+
|
|
54
|
+
У міру зростання додатку розмір бандла може збільшуватися в геометричній прогресії, що помітно погіршує продуктивність.
|
|
55
|
+
|
|
56
|
+
Наприклад, у найгірших випадках інтернаціоналізована сторінка може стати майже в 4 рази важчою.
|
|
57
|
+
|
|
58
|
+
Інший вплив бібліотек i18n — сповільнення розробки. Перетворення компонентів на багатомовний контент багатьма мовами займає багато часу.
|
|
59
|
+
|
|
60
|
+
Оскільки це складна проблема, існує багато рішень — деякі зосереджені на DX (досвід розробника), інші на продуктивності чи масштабованості тощо.
|
|
61
|
+
|
|
62
|
+
Intlayer намагається оптимізувати всі ці параметри одночасно.
|
|
63
|
+
|
|
64
|
+
## Протестуйте свій додаток
|
|
65
|
+
|
|
66
|
+
Щоб виявити ці проблеми, я створив безкоштовний сканер, який ви можете спробувати [тут](https://intlayer.org/i18n-seo-scanner).
|
|
67
|
+
|
|
68
|
+
<iframe src="https://intlayer.org/i18n-seo-scanner" width="100%" height="600px" style="border:none;"/>
|
|
69
|
+
|
|
70
|
+
## Проблема
|
|
71
|
+
|
|
72
|
+
Є два основні способи обмежити вплив багатомовного додатку на ваш бандл:
|
|
73
|
+
|
|
74
|
+
- Розділіть ваш JSON (або контент) на файли / змінні / простори імен (namespaces), щоб бандлер міг видалити невикористаний контент (tree-shaking) для конкретної сторінки.
|
|
75
|
+
- Динамічно завантажуйте контент сторінки лише мовою користувача.
|
|
76
|
+
|
|
77
|
+
Технічні обмеження цих підходів:
|
|
78
|
+
|
|
79
|
+
**Динамічне завантаження**
|
|
80
|
+
|
|
81
|
+
Навіть якщо ви оголошуєте роути на кшталт `[locale]/page.tsx`, використовуючи Webpack або Turbopack, і навіть якщо визначено `generateStaticParams`, бандлер не розглядає `locale` як статичну константу. Це означає, що він може додати контент для всіх мов у кожну сторінку. Основний спосіб обмежити це — завантажувати контент через динамічний імпорт (наприклад, `import('./locales/${locale}.json')`).
|
|
82
|
+
|
|
83
|
+
На етапі збірки (build time) Next.js створює один JS-бандл на локаль (наприклад, `./locales_uk_12345.js`). Після того, як сайт надіслано клієнту, під час запуску сторінки браузер робить додатковий HTTP-запит для потрібного JS-файлу.
|
|
84
|
+
|
|
85
|
+
> Інший спосіб вирішення цієї проблеми — використання `fetch()` для динамічного завантаження JSON. Саме так працює `Tolgee`, коли JSON знаходиться в `/public`, або `next-translate`, який використовує `getStaticProps` для завантаження контенту. Процес ідентичний: браузер робить додатковий HTTP-запит для завантаження ресурсу.
|
|
86
|
+
|
|
87
|
+
**Розділення контенту (Content splitting)**
|
|
88
|
+
|
|
89
|
+
Якщо ви використовуєте синтаксис на кшталт `const t = useTranslation()` + `t('my-object.my-sub-object.my-key')`, зазвичай весь JSON має бути в бандлі, щоб бібліотека могла розібрати його та знайти ключ. Велика частина цього контенту передається клієнту, навіть якщо вона не використовується на сторінці.
|
|
90
|
+
|
|
91
|
+
Щоб мінімізувати це, деякі бібліотеки просять вас вказувати для кожної сторінки, які простори імен завантажувати — наприклад, `next-i18next`, `next-intl`, `lingui`, `next-translate`, `next-international`.
|
|
92
|
+
|
|
93
|
+
Натомість `Paraglide` додає додатковий крок перед збіркою, щоб перетворити JSON на плоскі символи на кшталт `const en_my_var = () => 'my value'`. Теоретично це дозволяє видаляти невикористаний контент на сторінці. Як ми побачимо, цей метод все одно має свої компроміси.
|
|
94
|
+
|
|
95
|
+
Нарешті, `Intlayer` застосовує оптимізацію на етапі збірки, завдяки чому `useIntlayer('my-key')` замінюється безпосередньо відповідним контентом.
|
|
96
|
+
|
|
97
|
+
## Методологія
|
|
98
|
+
|
|
99
|
+
Для цього бенчмарку ми порівняли наступні бібліотеки:
|
|
100
|
+
|
|
101
|
+
- `Base App` (Без бібліотеки i18n)
|
|
102
|
+
- `next-intlayer` (v8.7.5)
|
|
103
|
+
- `next-i18next` (v16.0.5)
|
|
104
|
+
- `next-intl` (v4.9.1)
|
|
105
|
+
- `@lingui/core` (v5.3.0)
|
|
106
|
+
- `next-translate` (v3.1.2)
|
|
107
|
+
- `next-international` (v1.3.1)
|
|
108
|
+
- `@inlang/paraglide-js` (v2.15.1)
|
|
109
|
+
- `tolgee` (v7.0.0)
|
|
110
|
+
- `@lingo.dev/compiler` (v0.4.0)
|
|
111
|
+
- `wuchale` (v0.22.11)
|
|
112
|
+
- `gt-next` (v6.16.5)
|
|
113
|
+
|
|
114
|
+
Я використовував `Next.js` версії `16.2.4` з App Router.
|
|
115
|
+
|
|
116
|
+
Я побудував багатомовний додаток з **10 сторінками** та **10 мовами**.
|
|
117
|
+
|
|
118
|
+
Я порівняв **чотири стратегії завантаження**:
|
|
119
|
+
|
|
120
|
+
| Стратегія | Без просторів імен (global) | З просторами імен (scoped) |
|
|
121
|
+
| :------------------------- | :------------------------------------------------------ | :------------------------------------------------------------------------ |
|
|
122
|
+
| **Статичне завантаження** | **Static**: Все в пам'яті при запуску. | **Scoped static**: Розділено за простором імен; завантаження при запуску. |
|
|
123
|
+
| **Динамічне завантаження** | **Dynamic**: Завантаження за запитом для кожної локалі. | **Scoped dynamic**: Гранулярне завантаження за простором імен та локаллю. |
|
|
124
|
+
|
|
125
|
+
## Резюме стратегій
|
|
126
|
+
|
|
127
|
+
- **Static**: Просто; відсутня мережева затримка після початкового завантаження. Мінус: великий розмір бандла.
|
|
128
|
+
- **Dynamic**: Зменшує початкову вагу (lazy-loading). Ідеально, якщо у вас багато локалей.
|
|
129
|
+
- **Scoped static**: Зберігає код структурованим (логічний поділ) без складних мережевих запитів.
|
|
130
|
+
- **Scoped dynamic**: Найкращий підхід для _розділення коду_ (code splitting) та продуктивності. Мінімізує обсяг пам'яті, завантажуючи лише те, що потрібно поточному перегляду та активній локалі.
|
|
131
|
+
|
|
132
|
+
### Що я вимірював:
|
|
133
|
+
|
|
134
|
+
Я запускав один і той самий багатомовний додаток у реальному браузері для кожного стеку, а потім фіксував, що насправді передавалося через мережу і скільки часу це займало. Розміри вказані **після звичайного веб-стиснення**, оскільки це ближче до того, що люди реально завантажують.
|
|
135
|
+
|
|
136
|
+
- **Розмір бібліотеки інтернаціоналізації**: Після складання, видалення невикористаного коду та мінімізації, розмір бібліотеки i18n — це розмір коду провайдерів (наприклад, `NextIntlClientProvider`) + хуків (наприклад, `useTranslations`) у порожньому компоненті. Це не включає завантаження файлів перекладу. Це показує, наскільки "важкою" є бібліотека ще до появи вашого контенту.
|
|
137
|
+
|
|
138
|
+
- **JavaScript на сторінку**: Для кожного тестового маршруту — скільки скриптів завантажує браузер під час візиту, усереднено за сторінками тесту (і за локалями). Важкі сторінки — це повільні сторінки.
|
|
139
|
+
|
|
140
|
+
- **Витік з інших локалей (Leakage)**: Це контент тієї ж сторінки, але іншою мовою, який помилково завантажується на сторінку, що перевіряється. Цей контент є зайвим і його слід уникати (наприклад, контент сторінки `/fr/about` у бандлі сторінки `/en/about`).
|
|
141
|
+
|
|
142
|
+
- **Витік з інших маршрутів**: Та ж ідея для **інших екранів** у додатку: чи передаються їхні тексти, коли ви відкрили лише одну сторінку (наприклад, контент сторінки `/en/about` у бандлі сторінки `/en/contact`). Високий показник свідчить про слабке розділення або занадто широкі бандли.
|
|
143
|
+
|
|
144
|
+
- **Середній розмір бандла компонента**: Окремі UI-елементи вимірюються **по одному**, а не ховаються всередині одного гігантського загального показника додатку. Це показує, чи інтернаціоналізація "роздуває" звичайні компоненти. Наприклад, якщо ваш компонент перерендериться, він завантажуватиме всі ці дані з пам'яті. Прив'язка гігантського JSON до будь-якого компонента схожа на підключення великого сховища невикористаних даних, що сповільнює продуктивність ваших компонентів.
|
|
145
|
+
|
|
146
|
+
- **Швидкість реакції на перемикання мови**: Я перемикаю мову через власний інтерфейс додатку і заміряю час до моменту, коли сторінка явно оновилася — те, що помітить відвідувач.
|
|
147
|
+
|
|
148
|
+
- **Робота рендерингу після зміни мови**: Вужчий показник: скільки зусиль витратив інтерфейс на перемальовування для нової мови після початку перемикання. Корисно, коли "відчутний" час і вартість роботи фреймворку відрізняються.
|
|
149
|
+
|
|
150
|
+
- **Час початкового завантаження сторінки**: Від навігації до моменту, коли браузер вважає сторінку повністю завантаженою для тестованих сценаріїв. Корисно для порівняння "холодних стартів".
|
|
151
|
+
|
|
152
|
+
- **Час гідратації (Hydration)**: Час, який клієнт витрачає на перетворення HTML від сервера на інтерактивний інтерфейс. Прочерк у таблицях означає, що дана реалізація не надала надійних даних про гідратацію в цьому тесті.
|
|
153
|
+
|
|
154
|
+
## Детальні результати
|
|
155
|
+
|
|
156
|
+
### 1 — Рішення, яких слід уникати
|
|
157
|
+
|
|
158
|
+
Деяких рішень, таких як `gt-next` або `lingo.dev`, явно варто уникати. Вони поєднують прив'язку до вендора (vendor lock-in) із засміченням кодової бази. Попри багато годин спроб їх впровадження, я так і не зміг забезпечити їх стабільну роботу — ні на TanStack Start, ні на Next.js.
|
|
159
|
+
|
|
160
|
+
Виявлені проблеми:
|
|
161
|
+
|
|
162
|
+
**(General Translation)** (`gt-next@6.16.5`):
|
|
163
|
+
|
|
164
|
+
- Для додатку розміром 110 КБ `gt-react` додає понад 440 КБ зайвих даних.
|
|
165
|
+
- `Quota Exceeded, please upgrade your plan` вже під час першої збірки.
|
|
166
|
+
- Переклади не рендериться; отримую помилку `Error: <T> used on the client-side outside of <GTProvider>`, що схоже на баг бібліотеки.
|
|
167
|
+
- При впровадженні **gt-tanstack-start-react** я також стикнувся з [проблемою](https://github.com/generaltranslation/gt/issues/1210#event-24510646961) бібліотеки: помилка `does not provide an export named 'printAST' - @formatjs/icu-messageformat-parser`, через яку додаток падав. Після звіту про помилку розробник виправив її протягом 24 годин.
|
|
168
|
+
- Бібліотека блокує статичний рендеринг сторінок Next.js.
|
|
169
|
+
|
|
170
|
+
**(Lingo.dev)** (`@lingo.dev/compiler@0.4.0`):
|
|
171
|
+
|
|
172
|
+
- Перевищено квоту AI, що повністю блокує збірку — ви не можете випустити продукт у продакшн без оплати.
|
|
173
|
+
- Компілятор пропустив майже 40% перекладеного контенту. Мені довелося переписувати всі `.map` у плоскі блоки компонентів, щоб це запрацювало.
|
|
174
|
+
- Їхній CLI працює нестабільно і часто без причини скидає конфігураційний файл.
|
|
175
|
+
- Під час збірки він повністю стирав згенеровані JSON-файли при додаванні нового контенту. У результаті кілька ключів могли знищити понад 300 наявних.
|
|
176
|
+
|
|
177
|
+
### 2 — Експериментальні рішення
|
|
178
|
+
|
|
179
|
+
**(Wuchale)** (`wuchale@0.22.11`):
|
|
180
|
+
|
|
181
|
+
Ідея `Wuchale` цікава, але поки не життєздатна. Я зіткнувся з проблемами реактивності та був змушений примусово перерендерити провайдер, щоб додаток запрацював. Документація також досить нечітка, що ускладнює освоєння.
|
|
182
|
+
|
|
183
|
+
**(Paraglide)** (`@inlang/paraglide-js@2.15.1`):
|
|
184
|
+
|
|
185
|
+
`Paraglide` пропонує інноваційний, добре продуманий підхід. Проте в цьому тесті рекламований tree-shaking не спрацював для моїх налаштувань Next.js або TanStack Start. Робочий процес і DX складніші за інші варіанти.
|
|
186
|
+
Особисто мені не подобається необхідність регенерувати JS-файли перед кожним пушем, що створює постійний ризик конфліктів під час злиття через PR. Інструмент також здається більш орієнтованим на Vite, ніж на Next.js.
|
|
187
|
+
Нарешті, порівняно з іншими рішеннями, Paraglide не використовує стор (наприклад, React context) для отримання поточної локалі для рендерингу. Для кожного обробленого вузла він запитує локаль з localStorage / cookie тощо. Це призводить до виконання непотрібної логіки, що впливає на реактивність компонентів.
|
|
188
|
+
|
|
189
|
+
### 3 — Прийнятні рішення
|
|
190
|
+
|
|
191
|
+
**(Tolgee)** (`tolgee@7.0.0`):
|
|
192
|
+
|
|
193
|
+
`Tolgee` вирішує багато зі згаданих проблем. Проте його впровадження здалося мені складнішим, ніж у подібних інструментів. Він не забезпечує типізацію (type safety), що ускладнює виявлення відсутніх ключів під час компіляції. Мені довелося обертати функції Tolgee своїми, щоб додати перевірку відсутніх ключів.
|
|
194
|
+
|
|
195
|
+
**(Next Intl)** (`next-intl@4.9.1`):
|
|
196
|
+
|
|
197
|
+
`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`.
|
|
198
|
+
|
|
199
|
+
**(Next I18next)** (`next-i18next@16.0.5`):
|
|
200
|
+
|
|
201
|
+
`next-i18next` — це, мабуть, найпопулярніший варіант, оскільки він був одним із перших рішень i18n для JS-додатків. Він має багато плагінів від спільноти. Має ті ж основні недоліки, що й `next-intl`. Пакет особливо важкий (~18 КБ для `I18nProvider` + `useTranslation`, приблизно втричі більше за `next-intlayer`).
|
|
202
|
+
|
|
203
|
+
Формати повідомлень також відрізняються: `next-intl` використовує ICU MessageFormat, тоді як `i18next` використовує власний формат.
|
|
204
|
+
|
|
205
|
+
**(Next International)** (`next-international@1.3.1`):
|
|
206
|
+
|
|
207
|
+
`next-international` також намагається вирішити ці проблеми, але не сильно відрізняється від `next-intl` або `next-i18next`. Він включає `scopedT()` для перекладів у межах просторів імен, але це практично не впливає на розмір бандла.
|
|
208
|
+
|
|
209
|
+
**(Lingui)** (`@lingui/core@5.3.0`):
|
|
210
|
+
|
|
211
|
+
`Lingui` часто хвалять. Особисто мені робочий процес із `lingui extract` / `lingui compile` здався складнішим за альтернативи, без явних переваг. Також помічені непослідовні синтаксиси, які плутають ШІ (наприклад, `t()`, `t''`, `i18n.t()`, `<Trans>`).
|
|
212
|
+
|
|
213
|
+
### 4 — Рекомендації
|
|
214
|
+
|
|
215
|
+
**(Next Translate)** (`next-translate@3.1.2`):
|
|
216
|
+
|
|
217
|
+
`next-translate` — моя основна рекомендація, якщо вам подобається API у стилі `t()`. Він елегантно працює через `next-translate-plugin`, завантажуючи простори імен через `getStaticProps` за допомогою завантажувача Webpack / Turbopack. Це також найлегший варіант у цьому списку (~2.5 КБ). Визначення просторів імен для сторінок або маршрутів у конфігу добре продумане і простіше в підтримці, ніж у **next-intl** чи **next-i18next**. У версії `3.1.2` я помітив, що статичний рендеринг не працював; Next.js переходив до динамічного рендерингу.
|
|
218
|
+
|
|
219
|
+
**(Intlayer)** (`next-intlayer@8.7.5`):
|
|
220
|
+
|
|
221
|
+
Я не буду особисто оцінювати `next-intlayer` заради об’єктивності, оскільки це моє власне рішення.
|
|
222
|
+
|
|
223
|
+
### Особиста замітка
|
|
224
|
+
|
|
225
|
+
Ця замітка є особистою і не впливає на результати бенчмарку. Тим не менш, у світі i18n часто спостерігається консенсус навколо використання `const t = useTranslation('xx')` + `<>{t('xx.xx')}</>` для перекладеного контенту.
|
|
226
|
+
|
|
227
|
+
У React-додатках передача функції як `ReactNode`, на мою думку, є антипатерном. Це також додає зайвої складності та накладних витрат на виконання JavaScript (навіть якщо це ледь помітно).
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
---
|
|
2
|
+
createdAt: 2026-04-20
|
|
3
|
+
updatedAt: 2026-04-21
|
|
4
|
+
title: Найкраще i18n рішення для TanStack Start у 2026 році - Звіт бенчмарку
|
|
5
|
+
description: Порівняйте бібліотеки інтернаціоналізації для TanStack Start, такі як react-i18next, use-intl та Intlayer. Детальний звіт про продуктивність за розміром бандла, витоком та реактивністю.
|
|
6
|
+
keywords:
|
|
7
|
+
- benchmark
|
|
8
|
+
- i18n
|
|
9
|
+
- intl
|
|
10
|
+
- tanstack
|
|
11
|
+
- продуктивність
|
|
12
|
+
- intlayer
|
|
13
|
+
slugs:
|
|
14
|
+
- doc
|
|
15
|
+
- benchmark
|
|
16
|
+
- tanstack
|
|
17
|
+
author: Aymeric PINEAU
|
|
18
|
+
applicationTemplate: https://github.com/intlayer-org/benchmark-i18n-tanstack-start-template
|
|
19
|
+
history:
|
|
20
|
+
- version: 8.7.5
|
|
21
|
+
date: 2026-01-06
|
|
22
|
+
changes: "Ініціалізація бенчмарку"
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
# Бібліотеки i18n для TanStack Start — Звіт бенчмарку 2026
|
|
26
|
+
|
|
27
|
+
Ця сторінка є звітом бенчмарку i18n рішень на TanStack Start.
|
|
28
|
+
|
|
29
|
+
## Зміст
|
|
30
|
+
|
|
31
|
+
<Toc/>
|
|
32
|
+
|
|
33
|
+
## Інтерактивний бенчмарк
|
|
34
|
+
|
|
35
|
+
<I18nBenchmark framework="tanstack" vertical/>
|
|
36
|
+
|
|
37
|
+
## Довідка за результатами:
|
|
38
|
+
|
|
39
|
+
<iframe
|
|
40
|
+
src="https://intlayer.org/markdown?url=https%3A%2F%2Fraw.githubusercontent.com%2Fintlayer-org%2Fbenchmark-i18n%2Fmain%2Freport%2Fscripts%2Fsummarize-tanstack.md"
|
|
41
|
+
width="100%"
|
|
42
|
+
height="600px"
|
|
43
|
+
style="border:none;">
|
|
44
|
+
</iframe>
|
|
45
|
+
|
|
46
|
+
> https://intlayer.org/markdown?url=https%3A%2F%2Fraw.githubusercontent.com%2Fintlayer-org%2Fbenchmark-i18n%2Fmain%2Freport%2Fscripts%2Fsummarize-tanstack.md
|
|
47
|
+
|
|
48
|
+
Дивіться повний репозиторій бенчмарку [тут](https://github.com/intlayer-org/benchmark-i18n/tree/main).
|
|
49
|
+
|
|
50
|
+
## Вступ
|
|
51
|
+
|
|
52
|
+
Рішення для інтернаціоналізації є одними з найважчих залежностей у React-додатках. У TanStack Start основний ризик полягає в передачі зайвого контенту: перекладів для інших сторінок та інших локалей у бандлі одного маршруту.
|
|
53
|
+
|
|
54
|
+
У міру зростання додатку ця проблема може швидко роздути обсяг JavaScript, що надсилається клієнту, і сповільнити навігацію.
|
|
55
|
+
|
|
56
|
+
На практиці для найменш оптимізованих реалізацій інтернаціоналізована сторінка може стати в кілька разів важчою за версію без i18n.
|
|
57
|
+
|
|
58
|
+
Інший вплив стосується досвіду розробника (DX): як ви оголошуєте контент, типи, організація просторів імен, динамічне завантаження та реактивність при зміні мови.
|
|
59
|
+
|
|
60
|
+
## Протестуйте свій додаток
|
|
61
|
+
|
|
62
|
+
Щоб швидко виявити проблеми з витоком i18n, я налаштував безкоштовний сканер, доступний [тут](https://intlayer.org/i18n-seo-scanner).
|
|
63
|
+
|
|
64
|
+
<iframe src="https://intlayer.org/i18n-seo-scanner" width="100%" height="600px" style="border:none;"/>
|
|
65
|
+
|
|
66
|
+
## Проблема
|
|
67
|
+
|
|
68
|
+
Два важелі є важливими для обмеження вартості багатомовного додатку:
|
|
69
|
+
|
|
70
|
+
- Розділяйте контент за сторінками / просторами імен, щоб не завантажувати цілі словники, коли вони не потрібні.
|
|
71
|
+
- Динамічно завантажуйте потрібну локаль лише тоді, коли це необхідно.
|
|
72
|
+
|
|
73
|
+
Розуміння технічних обмежень цих підходів:
|
|
74
|
+
|
|
75
|
+
**Динамічне завантаження**
|
|
76
|
+
|
|
77
|
+
Без динамічного завантаження більшість рішень зберігають повідомлення в пам'яті з першого рендерингу, що додає значні накладні витрати для додатків з великою кількістю маршрутів та локалей.
|
|
78
|
+
|
|
79
|
+
З динамічним завантаженням ви приймаєте компроміс: менше початкового JS, але іноді додатковий запит при зміні мови.
|
|
80
|
+
|
|
81
|
+
**Розділення контенту (Content splitting)**
|
|
82
|
+
|
|
83
|
+
Синтаксиси навколо `const t = useTranslation()` + `t('a.b.c')` дуже зручні, але часто заохочують зберігання великих JSON-об'єктів під час виконання (runtime). Така модель ускладнює видалення невикористаного коду (tree-shaking), якщо бібліотека не пропонує реальної стратегії розділення сторінок.
|
|
84
|
+
|
|
85
|
+
## Методологія
|
|
86
|
+
|
|
87
|
+
Для цього бенчмарку ми порівняли наступні бібліотеки:
|
|
88
|
+
|
|
89
|
+
- `Base App` (Без бібліотеки i18n)
|
|
90
|
+
- `react-intlayer` (v8.7.5-canary.0)
|
|
91
|
+
- `react-i18next` (v17.0.2)
|
|
92
|
+
- `use-intl` (v4.9.1)
|
|
93
|
+
- `@lingui/core` (v5.3.0)
|
|
94
|
+
- `@inlang/paraglide-js` (v2.15.1)
|
|
95
|
+
- `tolgee` (v7.0.0)
|
|
96
|
+
- `react-intl` (v10.1.1)
|
|
97
|
+
- `wuchale` (v0.22.11)
|
|
98
|
+
- `gt-react` (vlatest)
|
|
99
|
+
- `lingo.dev` (v0.133.9)
|
|
100
|
+
|
|
101
|
+
Фреймворк — `TanStack Start` з багатомовним додатком на **10 сторінок** та **10 мов**.
|
|
102
|
+
|
|
103
|
+
Ми порівняли **чотири стратегії завантаження**:
|
|
104
|
+
|
|
105
|
+
| Стратегія | Без просторів імен (global) | З просторами імен (scoped) |
|
|
106
|
+
| :------------------------- | :------------------------------------------------------ | :------------------------------------------------------------------------ |
|
|
107
|
+
| **Статичне завантаження** | **Static**: Все в пам'яті при запуску. | **Scoped static**: Розділено за простором імен; завантаження при запуску. |
|
|
108
|
+
| **Динамічне завантаження** | **Dynamic**: Завантаження за запитом для кожної локалі. | **Scoped dynamic**: Гранулярне завантаження за простором імен та локаллю. |
|
|
109
|
+
|
|
110
|
+
## Резюме стратегій
|
|
111
|
+
|
|
112
|
+
- **Static**: Просто; відсутня мережева затримка після початкового завантаження. Мінус: великий розмір бандла.
|
|
113
|
+
- **Dynamic**: Зменшує початкову вагу (lazy-loading). Ідеально, якщо у вас багато локалей.
|
|
114
|
+
- **Scoped static**: Зберігає код структурованим (логічний поділ) без складних мережевих запитів.
|
|
115
|
+
- **Scoped dynamic**: Найкращий підхід для _розділення коду_ (code splitting) та продуктивності. Мінімізує обсяг пам'яті, завантажуючи лише те, що потрібно поточному перегляду та активній локалі.
|
|
116
|
+
|
|
117
|
+
## Детальні результати
|
|
118
|
+
|
|
119
|
+
### 1 — Рішення, яких слід уникати
|
|
120
|
+
|
|
121
|
+
Деяких рішень, таких як `gt-react` або `lingo.dev`, явно варто триматися подалі. Вони поєднують прив'язку до вендора із засміченням вашої кодової бази. Гірше того: попри багато годин спроб їх впровадження, я так і не зміг забезпечити їх коректну роботу на TanStack Start (аналогічно до `gt-next` на Next.js).
|
|
122
|
+
|
|
123
|
+
Виявлені проблеми:
|
|
124
|
+
|
|
125
|
+
**(General Translation)** (`gt-react@latest`):
|
|
126
|
+
|
|
127
|
+
- Для додатку розміром близько 110 КБ `gt-react` може додати понад 440 КБ зайвих даних (порядок величини зафіксовано в реалізації Next.js у тому ж бенчмарку).
|
|
128
|
+
- `Quota Exceeded, please upgrade your plan` вже під час першої збірки.
|
|
129
|
+
- Переклади не рендериться; отримую помилку `Error: <T> used on the client-side outside of <GTProvider>`, що схоже на баг бібліотеки.
|
|
130
|
+
- При впровадженні **gt-tanstack-start-react** я також стикнувся з [проблемою](https://github.com/generaltranslation/gt/issues/1210#event-24510646961) бібліотеки: помилка `does not provide an export named 'printAST' - @formatjs/icu-messageformat-parser`, через яку додаток падав. Після звіту про помилку розробник виправив її протягом 24 годин.
|
|
131
|
+
- Ці бібліотеки використовують антипатерн через функцію `initializeGT()`, що заважає чистому видаленню невикористаного коду (tree-shaking) бандла.
|
|
132
|
+
|
|
133
|
+
**(Lingo.dev)** (`lingo.dev@0.133.9`):
|
|
134
|
+
|
|
135
|
+
- Перевищено квоту AI (або блокування серверної залежності), що робить збірку / продакшн ризикованим без оплати.
|
|
136
|
+
- Компілятор пропустив майже 40% перекладеного контенту. Мені довелося переписувати всі `.map` у плоскі блоки компонентів, щоб це запрацювало.
|
|
137
|
+
- Їхній CLI працює нестабільно і часто без причини скидає конфігураційний файл.
|
|
138
|
+
- Під час збірки він повністю стирав згенеровані JSON-файли при додаванні нового контенту. У результаті лише кілька ключів могли знищити сотні наявних.
|
|
139
|
+
- Я стикнувся з проблемами реактивності бібліотеки в TanStack Start: при зміні локалі довелося примусово перерендерити провайдер, щоб це запрацювало.
|
|
140
|
+
|
|
141
|
+
### 2 — Експериментальні рішення
|
|
142
|
+
|
|
143
|
+
**(Wuchale)** (`wuchale@0.22.11`):
|
|
144
|
+
|
|
145
|
+
Ідея `Wuchale` цікава, але поки що не є життєздатним рішенням. Я зіткнувся з проблемами реактивності бібліотеки і був змушений примусово перерендерити провайдер, щоб додаток запрацював на TanStack Start. Документація також досить нечітка, що ускладнює освоєння.
|
|
146
|
+
|
|
147
|
+
### 3 — Прийнятні рішення
|
|
148
|
+
|
|
149
|
+
**(Paraglide)** (`@inlang/paraglide-js@2.15.1`):
|
|
150
|
+
|
|
151
|
+
`Paraglide` пропонує інноваційний, добре продуманий підхід. Проте в цьому тесті рекламований компанією tree-shaking не спрацював ні для моєї реалізації на Next.js, ні для TanStack Start. Робочий процес і DX також складніші за інші варіанти. Особисто я не прихильник необхідності регенерувати JS-файли перед кожним пушем, що створює постійний ризик конфліктів під час злиття для розробників через PR.
|
|
152
|
+
|
|
153
|
+
**(Tolgee)** (`tolgee@7.0.0`):
|
|
154
|
+
|
|
155
|
+
`Tolgee` вирішує багато зі згаданих проблем. Проте почати роботу з ним здалося мені важче, ніж з іншими інструментами з подібними підходами. Він не забезпечує типізацію, що значно ускладнює виявлення відсутніх ключів під час компіляції. Мені довелося обертати Tolgee API своїми рішеннями, щоб додати перевірку відсутніх ключів.
|
|
156
|
+
|
|
157
|
+
На TanStack Start я також мав проблеми з реактивністю: при зміні локалі довелося примусово перерендерити провайдер і підписуватися на події зміни локалі, щоб завантаження іншою мовою працювало коректно.
|
|
158
|
+
|
|
159
|
+
**(use-intl)** (`use-intl@4.9.1`):
|
|
160
|
+
|
|
161
|
+
`use-intl` — це зараз наймодніша частина "intl" в екосистемі React (та ж родина, що й `next-intl`), яку часто просувають ШІ-агенти, але на мій погляд — помилково в контексті продуктивності. Почати роботу досить просто. На практиці ж процес оптимізації та обмеження витоків є досить складним. Аналогічно, поєднання динамічного завантаження + просторів імен + типів TypeScript сильно сповільнює розробку.
|
|
162
|
+
|
|
163
|
+
У TanStack Start ви уникаєте специфічних пасток Next.js (`setRequestLocale`, статичний рендеринг), але основна проблема та ж сама: без суворої дисципліни бандл швидко перевантажується повідомленнями, а підтримка просторів імен для кожного маршруту стає виснажливою.
|
|
164
|
+
|
|
165
|
+
**(react-i18next)** (`react-i18next@17.0.2`):
|
|
166
|
+
|
|
167
|
+
`react-i18next` — це, мабуть, найпопулярніший варіант, оскільки він був одним із перших, хто задовольнив потреби i18n у JS-додатках. Він також має великий набір плагінів від спільноти для конкретних проблем.
|
|
168
|
+
|
|
169
|
+
Проте він має ті ж недоліки, що й стеки на базі `t('a.b.c')`: оптимізація можлива, але забирає багато часу, а великі проекти ризикують скотитися до поганих практик (простори імен + динамічне завантаження + типи).
|
|
170
|
+
|
|
171
|
+
Формати повідомлень також відрізняються: `use-intl` використовує ICU MessageFormat, тоді як `i18next` має власний формат — це ускладнює інструментарій або міграцію, якщо ви їх змішуєте.
|
|
172
|
+
|
|
173
|
+
**(Lingui)** (`@lingui/core@5.3.0`):
|
|
174
|
+
|
|
175
|
+
`Lingui` часто хвалять. Особисто мені робочий процес із `lingui extract` / `lingui compile` здався складнішим за інші підходи, без явних переваг у цьому тесті TanStack Start. Також помічені непослідовні синтаксиси, які плутають ШІ (наприклад, `t()`, `t''`, `i18n.t()`, `<Trans>`).
|
|
176
|
+
|
|
177
|
+
**(react-intl)** (`react-intl@10.1.1`):
|
|
178
|
+
|
|
179
|
+
`react-intl` — це продуктивна реалізація від команди Format.js. DX залишається багатослівним: `const intl = useIntl()` + `intl.formatMessage({ id: "xx.xx" })` додає складності, додаткової роботи JavaScript та прив'язує глобальний екземпляр i18n до багатьох вузлів у React-дереві.
|
|
180
|
+
|
|
181
|
+
### 4 — Рекомендації
|
|
182
|
+
|
|
183
|
+
Цей бенчмарк TanStack Start не має прямого еквівалента `next-translate` (Next.js плагін + `getStaticProps`). Командам, які справді хочуть API у стилі `t()` зі зрілою екосистемою, `react-i18next` та `use-intl` залишаються "розумним" вибором, але будьте готові витратити багато часу на оптимізацію для уникнення витоків.
|
|
184
|
+
|
|
185
|
+
**(Intlayer)** (`react-intlayer@8.7.5-canary.0`):
|
|
186
|
+
|
|
187
|
+
Я не буду особисто оцінювати `react-intlayer` заради об’єктивності, оскільки це моє власне рішення.
|
|
188
|
+
|
|
189
|
+
### Особиста замітка
|
|
190
|
+
|
|
191
|
+
Ця замітка є особистою і не впливає на результати бенчмарку. Тим не менш, у світі i18n часто спостерігається консенсус навколо використання `const t = useTranslation('xx')` + `<>{t('xx.xx')}</>` для перекладеного контенту.
|
|
192
|
+
|
|
193
|
+
У React-додатках передача функції як `ReactNode`, на мою думку, є антипатерном. Це також додає зайвої складності та накладних витрат на виконання JavaScript (навіть якщо це ледь помітно).
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
---
|
|
2
|
+
createdAt: 2026-04-20
|
|
3
|
+
updatedAt: 2026-04-20
|
|
4
|
+
title: Điểm chuẩn các thư viện i18n
|
|
5
|
+
description: Tìm hiểu cách Intlayer so sánh với các thư viện i18n khác về hiệu năng và kích thước gói bundle.
|
|
6
|
+
keywords:
|
|
7
|
+
- benchmark
|
|
8
|
+
- i18n
|
|
9
|
+
- intl
|
|
10
|
+
- nextjs
|
|
11
|
+
- tanstack
|
|
12
|
+
- intlayer
|
|
13
|
+
slugs:
|
|
14
|
+
- doc
|
|
15
|
+
- benchmark
|
|
16
|
+
history:
|
|
17
|
+
- version: 8.7.5
|
|
18
|
+
date: 2026-01-06
|
|
19
|
+
changes: "Khởi tạo benchmark"
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
# Benchmark - Báo cáo
|
|
23
|
+
|
|
24
|
+
Benchmark Bloom là bộ công cụ đo hiệu năng, đánh giá tác động thực tế của các thư viện i18n (quốc tế hóa) trên nhiều framework React và chiến lược tải.
|
|
25
|
+
|
|
26
|
+
Báo cáo chi tiết và tài liệu kỹ thuật cho từng framework nằm bên dưới:
|
|
27
|
+
|
|
28
|
+
- [**Báo cáo benchmark Next.js**](https://github.com/aymericzip/intlayer/blob/main/docs/docs/vi/benchmark/nextjs.md)
|
|
29
|
+
- [**Báo cáo benchmark TanStack Start**](https://github.com/aymericzip/intlayer/blob/main/docs/docs/vi/benchmark/tanstack.md)
|