Author: Aymeric PINEAU
    Creation:2026-04-20Last update:2026-05-18

    Бібліотеки i18n для TanStack Start - Звіт бенчмарку 2026

    Ця сторінка є звітом бенчмарку i18n рішень на TanStack Start.

    Зміст

    Інтерактивний бенчмарк

    Довідка за результатами:

    intlayer.org
    Переглянути повні дані бенчмарку

    Дивіться повний репозиторій бенчмарку тут.

    Вступ

    Рішення для інтернаціоналізації є одними з найважчих залежностей у React-додатках. У TanStack Start основний ризик полягає в передачі зайвого контенту: перекладів для інших сторінок та інших локалей у бандлі одного маршруту.

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

    На практиці для найменш оптимізованих реалізацій інтернаціоналізована сторінка може стати в кілька разів важчою за версію без i18n.

    Інший вплив стосується досвіду розробника (DX): як ви оголошуєте контент, типи, організація просторів імен, динамічне завантаження та реактивність при зміні мови.

    TL;DR

    • Intlayer: Забезпечує найкращу продуктивність і найменший розмір бандла (v8.7.12) для TanStack Start.
    • react-i18next та use-intl: Зрілі альтернативи з великими екосистемами, але значно важчі та складніші в оптимізації.
    • Paraglide: Інноваційна ідея tree-shaking, яка не працює на практиці. Складний DX та накладні витрати реактивності в TanStack Start.
    • Уникайте: General Translation (GT) та Lingo.dev через серйозні проблеми з продуктивністю, ліміти ШІ та прив'язку до вендора (vendor lock-in).

    Протестуйте свій додаток

    Щоб швидко виявити проблеми з витоком i18n, я налаштував безкоштовний сканер, доступний тут.

    intlayer.org

    Проблема

    Два важелі є важливими для обмеження вартості багатомовного додатку:

    • Розділяйте контент за сторінками / просторами імен, щоб не завантажувати цілі словники, коли вони не потрібні.
    • Динамічно завантажуйте потрібну локаль лише тоді, коли це необхідно.

    Розуміння технічних обмежень цих підходів:

    Динамічне завантаження

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

    З динамічним завантаженням ви приймаєте компроміс: менше початкового JS, але іноді додатковий запит при зміні мови.

    Розділення контенту (Content splitting)

    Синтаксиси навколо const t = useTranslation() + t('a.b.c') дуже зручні, але часто заохочують зберігання великих JSON-об'єктів під час виконання (runtime). Така модель ускладнює видалення невикористаного коду (tree-shaking), якщо бібліотека не пропонує реальної стратегії розділення сторінок.

    Методологія

    Для цього бенчмарку ми порівняли наступні бібліотеки:

    • Base App (Без бібліотеки i18n)
    • react-intlayer (v8.7.12)
    • react-i18next (v17.0.2)
    • use-intl (v4.9.1)
    • @lingui/core (v5.3.0)
    • @inlang/paraglide-js (v2.15.1)
    • @tolgee/react (v7.0.0)
    • react-intl (v10.1.1)
    • wuchale (v0.22.11)
    • gt-react (vlatest)
    • lingo.dev (v0.133.9)

    Фреймворк - TanStack Start з багатомовним додатком на 10 сторінок та 10 мов.

    Ми порівняли чотири стратегії завантаження:

    Стратегія Без просторів імен (global) З просторами імен (scoped)
    Статичне завантаження Static: Все в пам'яті при запуску. Scoped static: Розділено за простором імен; завантаження при запуску.
    Динамічне завантаження Dynamic: Завантаження за запитом для кожної локалі. Scoped dynamic: Гранулярне завантаження за простором імен та локаллю.

    Резюме стратегій

    • Static: Просто; відсутня мережева затримка після початкового завантаження. Мінус: великий розмір бандла.
    • Dynamic: Зменшує початкову вагу (lazy-loading). Ідеально, якщо у вас багато локалей.
    • Scoped static: Зберігає код структурованим (логічний поділ) без складних мережевих запитів.
    • Scoped dynamic: Найкращий підхід для розділення коду (code splitting) та продуктивності. Мінімізує обсяг пам'яті, завантажуючи лише те, що потрібно поточному перегляду та активній локалі.

    Зірки на GitHub

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

    Star History Chart

    Детальні результати

    1 - Рішення, яких слід уникати

    Деяких рішень, таких як gt-react або lingo.dev, явно варто триматися подалі. Вони поєднують прив'язку до вендора із засміченням вашої кодової бази. Гірше того: попри багато годин спроб їх впровадження, я так і не зміг забезпечити їх коректну роботу на TanStack Start (аналогічно до gt-next на Next.js).

    Виявлені проблеми:

    (General Translation) (gt-react@latest):

    • Для додатку розміром близько 110 КБ gt-react може додати понад 440 КБ зайвих даних (порядок величини зафіксовано в реалізації Next.js у тому ж бенчмарку).
    • Quota Exceeded, please upgrade your plan вже під час першої збірки.
    • Переклади не рендериться; отримую помилку Error: <T> used on the client-side outside of <GTProvider>, що схоже на баг бібліотеки.
    • При впровадженні gt-tanstack-start-react я також стикнувся з проблемою бібліотеки: помилка does not provide an export named 'printAST' - @formatjs/icu-messageformat-parser, через яку додаток падав. Після звіту про помилку розробник виправив її протягом 24 годин.
    • Ці бібліотеки використовують антипатерн через функцію initializeGT(), що заважає чистому видаленню невикористаного коду (tree-shaking) бандла.

    (Lingo.dev) ([email protected]):

    • Перевищено квоту AI (або блокування серверної залежності), що робить збірку / продакшн ризикованим без оплати.
    • Компілятор пропустив майже 40% перекладеного контенту. Мені довелося переписувати всі .map у плоскі блоки компонентів, щоб це запрацювало.
    • Їхній CLI працює нестабільно і часто без причини скидає конфігураційний файл.
    • Під час збірки він повністю стирав згенеровані JSON-файли при додаванні нового контенту. У результаті лише кілька ключів могли знищити сотні наявних.
    • Я стикнувся з проблемами реактивності бібліотеки в TanStack Start: при зміні локалі довелося примусово перерендерити провайдер, щоб це запрацювало.

    2 - Експериментальні рішення

    (Wuchale) ([email protected]):

    Ідея Wuchale цікава, але поки що не є життєздатним рішенням. Я зіткнувся з проблемами реактивності бібліотеки і був змушений примусово перерендерити провайдер, щоб додаток запрацював на TanStack Start. Документація також досить нечітка, що ускладнює освоєння.

    3 - Прийнятні рішення

    (Paraglide) (@inlang/[email protected]):

    Paraglide пропонує інноваційний, добре продуманий підхід. Проте в цьому тесті рекламований компанією tree-shaking не спрацював ні для моєї реалізації на Next.js, ні для TanStack Start. Робочий процес і DX також складніші за інші варіанти. Особисто я не прихильник необхідності регенерувати JS-файли перед кожним пушем, що створює постійний ризик конфліктів під час злиття для розробників через PR.

    Примітка щодо paraglide: це рішення впорскує код у вашу кодову базу для імпорту; як наслідок, метрика 'lib size' у звіті бенчмарку становить майже 0. Генерація коду - це добре, оскільки використовувана функція включатиме лише необхідну логіку (префікс усюди проти відсутності префікса, куки проти сховища тощо). Для порівняння, Intlayer виконує це фільтрування за допомогою впорскування змінних оточення в збірку, щоб змусити бандлер виконати tree-shake контенту залежно від логіки. Завдяки цьому paraglide та intlayer врешті-решт стають у 6–10 разів легшими рішеннями, ніж i18next або next-intl.

    (Tolgee) (@tolgee/[email protected]):

    Tolgee вирішує багато зі згаданих проблем. Проте почати роботу з ним здалося мені важче, ніж з іншими інструментами з подібними підходами. Він не забезпечує типізацію, що значно ускладнює виявлення відсутніх ключів під час компіляції. Мені довелося обертати Tolgee API своїми рішеннями, щоб додати перевірку відсутніх ключів.

    На TanStack Start я також мав проблеми з реактивністю: при зміні локалі довелося примусово перерендерити провайдер і підписуватися на події зміни локалі, щоб завантаження іншою мовою працювало коректно.

    (use-intl) ([email protected]):

    use-intl - це зараз наймодніша частина "intl" в екосистемі React (та ж родина, що й next-intl), яку часто просувають ШІ-агенти, але на мій погляд - помилково в контексті продуктивності. Почати роботу досить просто. На практиці ж процес оптимізації та обмеження витоків є досить складним. Аналогічно, поєднання динамічного завантаження + просторів імен + типів TypeScript сильно сповільнює розробку.

    У TanStack Start ви уникаєте специфічних пасток Next.js (setRequestLocale, статичний рендеринг), але основна проблема та ж сама: без суворої дисципліни бандл швидко перевантажується повідомленнями, а підтримка просторів імен для кожного маршруту стає виснажливою.

    (react-i18next) ([email protected]):

    react-i18next - це, мабуть, найпопулярніший варіант, оскільки він був одним із перших, хто задовольнив потреби i18n у JS-додатках. Він також має великий набір плагінів від спільноти для конкретних проблем.

    Проте він має ті ж недоліки, що й стеки на базі t('a.b.c'): оптимізація можлива, але забирає багато часу, а великі проекти ризикують скотитися до поганих практик (простори імен + динамічне завантаження + типи).

    Формати повідомлень також відрізняються: use-intl використовує ICU MessageFormat, тоді як i18next має власний формат - це ускладнює інструментарій або міграцію, якщо ви їх змішуєте.

    (Lingui) (@lingui/[email protected]):

    Lingui часто хвалять. Особисто мені робочий процес із lingui extract / lingui compile здався складнішим за інші підходи, без явних переваг у цьому тесті TanStack Start. Також помічені непослідовні синтаксиси, які плутають ШІ (наприклад, t(), t'', i18n.t(), <Trans>).

    (react-intl) ([email protected]):

    react-intl - це продуктивна реалізація від команди Format.js. DX залишається багатослівним: const intl = useIntl() + intl.formatMessage({ id: "xx.xx" }) додає складності, додаткової роботи JavaScript та прив'язує глобальний екземпляр i18n до багатьох вузлів у React-дереві.

    4 - Рекомендації

    Цей бенчмарк TanStack Start не має прямого еквівалента next-translate (Next.js плагін + getStaticProps). Командам, які справді хочуть API у стилі t() зі зрілою екосистемою, react-i18next та use-intl залишаються "розумним" вибором, але будьте готові витратити багато часу на оптимізацію для уникнення витоків.

    (Intlayer) ([email protected]):

    Я не буду особисто оцінювати react-intlayer заради об’єктивності, оскільки це моє власне рішення.

    Особиста замітка

    Ця замітка є особистою і не впливає на результати бенчмарку. Тим не менш, у світі i18n часто спостерігається консенсус навколо використання const t = useTranslation('xx') + <>{t('xx.xx')}</> для перекладеного контенту.

    У React-додатках передача функції як ReactNode, на мою думку, є антипатерном. Це також додає зайвої складності та накладних витрат на виконання JavaScript (навіть якщо це ледь помітно).