Задайте питання та отримайте підсумок документа, вказавши цю сторінку та обраного вами постачальника штучного інтелекту
Вміст цієї сторінки перекладено за допомогою штучного інтелекту.
Переглянути останню версію оригінального вмісту англійськоюЯкщо у вас є ідея щодо покращення цієї документації, будь ласка, долучіться, надіславши pull request на GitHub.
Посилання на документацію на GitHubСкопіювати документацію у форматі Markdown в буфер обміну
Lingui проти @intlayer/lingui | Ті самі макроси, інший рантайм
@intlayer/lingui - це адаптер сумісності (compat adapter) для @lingui/core та @lingui/react. Ваші виклики t`...` , <Trans>, useLingui() та i18n._() залишаються повністю без змін; макроси продовжують компілюватися як зазвичай; єдине, що змінюється - це джерело повідомлень під час виконання (runtime). Замість одного загального скомпільованого каталогу на локаль кожне місце виклику прив'язується до словника Intlayer, скомпільованого персонально для нього.
У цій статті оцінюється така заміна на одному й тому самому застосунку TanStack Start, зібраному спочатку з чистим Lingui, а потім з адаптером. Дані отримано з Benchmark Bloom. Для безпосереднього порівняння двох бібліотек прочитайте Lingui проти Intlayer. Тут мова піде про те, що саме змінює адаптер і в яких випадках він не надає переваг.
Стисло (tl;dr): На тому самому застосунку TanStack Start@intlayer/linguiзменшив середній розмір компонента з 85,5 КБ до 12,8 КБ gzip, скоротив час гідратації з 28 мс до 19,7 мс, а перемикання мови - з 5,9 мс до 2,9 мс, не змінюючи макроси. У базовій конфігурації (всі каталоги завантажуються на старті) він також усунув 90% витоку сторінок і заощадив 12 КБ на кожній сторінці. Проте в конфігурації з лінивим завантаженням (lazy loading) він передає 137 КБ на сторінку проти 115 КБ у чистого Lingui: адаптер розбирає синтаксис ICU в рантаймі, тоді як Lingui постачає попередньо скомпільовані масиви токенів. Витік вихідної мови (~9-10%) однаковий з обох боків, оскільки виникає через вбудований у компоненти резервний текстmessage, а не через рантайм. Адаптер створено як плагін для Vite; вимірювання проводилися на TanStack Start.
Що являє собою @intlayer/lingui
Lingui поєднує компілятор і середовище виконання. Макроси у вашому вихідному коді вилучаються в каталог .po (або JSON) для кожної мови, компілюються в окремий JS-модуль на локаль і завантажуються в глобальний екземпляр I18n за допомогою i18n.load() + i18n.activate(). Кожен виклик useLingui() підписується на цей екземпляр; кожен виклик _() шукає свій ідентифікатор в активному каталозі.
@intlayer/lingui зберігає макроси та API, замінюючи логіку пошуку в каталозі:
- Аліаси імпортів (Import aliasing). Плагін
lingui()з пакета@intlayer/lingui/pluginогортаєvite-intlayerі додає записиresolve.alias, щоб@lingui/coreта@lingui/reactспрямовувалися на@intlayer/lingui. Імпорти у вашому коді залишаються без змін. - Каталоги як єдине джерело правди. Плагін
syncJSON(абоsyncPOдля файлів.po) зчитує наявні каталоги та перетворює їх на словники Intlayer, записуючи переклади назад у файли, коли CLI або CMS вносить зміни. Завдяки параметруsplitKeys: "key-prefix"плаский каталог з ідентифікаторами через крапку (footer.github,hero.title) розділяється на невеликі словники за префіксами замість одного важкого файлу розміром 244 КБ. - Прив'язка до місця виклику. Етап оптимізації Intlayer збирає ідентифікатори, передані в
_,tта<Trans>у кожному файлі, і надає компоненту лише відповідні словники.<Trans id="hero.title">прив'язується автономно;useLingui()зв'язується з усіма префіксами, використаними у файлі. Ідентифікатори без крапок (хешовані id,mockBanner) звертаються до єдиного резервного словникаmessagesLingui.
Скопіюйте код у буфер обміну
Скопіюйте код у буфер обміну
Компонент більше не взаємодіє з глобальним екземпляром і масивним каталогом за ним. Він звертається суто до словника hero. Саме цим зумовлене 7-кратне зменшення розміру компонентів у таблиці нижче.
Що адаптер зберігає, ігнорує і не замінює
Відкрийте таблицю в модальному вікні, щоб чітко переглянути всі дані
| API Lingui | З @intlayer/lingui |
|---|---|
Макроси t`...` , msg, plural, select, <Trans> | ✅ Збережено. Залиште @lingui/babel-plugin-lingui-macro або @lingui/swc-plugin перед кроком Intlayer |
useLingui() → { i18n, _, t } | ✅ Збережено. Працює і поза Provider (локаль визначається з react-intlayer) |
i18n._(id, values), i18n.t() | ✅ Збережено. Розв'язує як явні, так і хешовані ідентифікатори |
Форми множини ICU, select, selectordinal, # | ✅ Збережено, через вбудований резолвер ICU в Intlayer |
i18n.date(), i18n.number(), formats | ✅ Збережено, на базі нативного Intl |
I18nProvider | ✅ Збережено. Огортає IntlayerProvider; слухає i18n.on("change") для повторного рендеру за activate() |
i18n.activate(locale) | ✅ Збережено |
i18n.load(locale, messages) / loadAndActivate() | ⚠️ Приймається як резервний варіант у рантаймі. Скомпільовані словники мають пріоритет; dev-попередження |
setupI18n({ messages, missing }) | ⚠️ messages об'єднуються як резерв; параметр missing ігнорується |
lingui extract / lingui compile | ✅ Ваш звичний робочий процес залишається. Спрямуйте syncPO / syncJSON на отримані каталоги |
defaultComponent на I18nProvider | ⚠️ Зберігається в контексті, але не застосовується під час рендерингу |
| Next.js | ❌ Плагін огортає vite-intlayer. Підтримуються лише Vite, TanStack Start та React Router |
Порівняльний бенчмарк
Що саме вимірювалося
Тестовий набір Benchmark Bloom створює один і той самий застосунок у кожній конфігурації: 10 сторінок (home, about, blog, careers, contact, FAQ, pricing, products, settings, team), 10 мов (en, fr, es, de, it, pt, zh, ja, ko, ru), ідентичні компоненти та ідентичний контент. Сторінки вимірювалися для мов en та fr.
Lingui тестувався у чотирьох стратегіях завантаження: від початкового імпорту всіх скомпільованих каталогів (static) до лінивого завантаження каталогу для кожного маршруту (scoped-dynamic). Адаптер тестувався на тих самих компонентах, у яких змінювалися лише файли vite.config.ts та intlayer.config.ts. Рядок static містить усі мови; рядок dynamic (importMode: 'dynamic') завантажує активну мову за вимогою. Окремий варіант "scoped" відсутній, оскільки оптимізатор автоматично ізолює залежності на рівні місць викликів.
Для кожної збірки фіксуються такі показники:
- Lib size: розмір gzip порожнього компонента, який імпортує лише бібліотеку i18n.
- Page JS: середній розмір gzip JavaScript, завантаженого на сторінку для всіх сторінок і локалей.
- Locale leak %: частка перекладених рядків у завантаженому JS, які належать до мов, що наразі не переглядаються користувачем.
- Page leak %: частка перекладених рядків у завантаженому JS, які належать до сторінок, на яких користувач зараз не перебуває.
- Component avg: середній розмір gzip кожного компонента, скомпільованого окремо.
- E2E reactivity: реальний час між вибором нової мови та оновленням тегу
html[lang]у DOM (Playwright, 5 повторень). - Hydration: тривалість фази гідратації React.
Цифри нижче отримані під час тестування від 2026-09-12 з використанням@lingui/react6.6.0 та@intlayer/lingui9.5.1. Тестовий застосунок навмисно компактний (кілька десятків рядків на мову), тому відсотки витоку показують структурну закономірність: вони масштабуються разом зі збільшенням контенту, тоді як базові витрати рантайму залишаються стабільними.
Результати на TanStack Start
Відкрийте таблицю в модальному вікні, щоб чітко переглянути всі дані
| Конфігурація | Стратегія | Lib size (gz) | Сер. JS сторінки (gz) | Витік мови | Витік сторінок | Сер. компонент (gz) | E2E реактивність | Гідратація |
|---|---|---|---|---|---|---|---|---|
| база (без i18n) | - | 0,0 КБ | 111,0 КБ | 0,0% | 0,0% | 0,7 КБ | 8,1 мс | 21,6 мс |
| Lingui | static | 11,2 КБ | 152,2 КБ | 50,0% | 90,0% | 58,0 КБ | 3,9 мс | 19,9 мс |
| Lingui | dynamic | 11,2 КБ | 115,2 КБ | 9,3% | 0,0% | 85,5 КБ | 5,9 мс | 28,0 мс |
| Lingui | scoped-static | 11,2 КБ | 120,8 КБ | 4,0% | 0,0% | 147,9 КБ | 7,1 мс | 33,9 мс |
| Lingui | scoped-dynamic | 11,2 КБ | 120,2 КБ | 8,6% | 0,0% | 83,7 КБ | 42,1 мс | 32,9 мс |
@intlayer/lingui | static | 10,3 КБ | 140,5 КБ | 50,0% | 0,0% | 14,9 КБ | 3,3 мс | 11,3 мс |
@intlayer/lingui | dynamic | 10,3 КБ | 137,0 КБ | 9,9% | 0,0% | 12,8 КБ | 2,9 мс | 19,7 мс |
intlayer (нативний) | static | 5,0 КБ | 125,8 КБ | 50,0% | 0,0% | 8,1 КБ | 3,2 мс | 11,5 мс |
intlayer (нативний) | dynamic | 5,0 КБ | 118,6 КБ | 0,0% | 0,0% | 6,3 КБ | 3,6 мс | 14,1 мс |
Аналіз отриманих результатів
- Компоненти: у 7 разів менші. Це головний результат переходу на адаптер. Компонент Lingui, скомпільований ізольовано, займає в середньому від 58 до 148 КБ залежно від стратегії, оскільки
useLingui()звертається до глобального екземпляра і кожного завантаженого в нього каталогу. Той самий компонент з адаптером важить у середньому 12,8-14,9 КБ: він підключає лише свої словники та ICU-резолвер. - Гідратація: на 8-14 мс швидша. Методи
i18n.load()+i18n.activate()виконуються на боці клієнта до того, як React зможе гідратувати інтерфейс. Чим більше Lingui налаштований на ліниве завантаження, тим довше триває цей процес (28-34 мс). З адаптером словники надходять як звичайні імпорти, заздалегідь розміщені бандлером у чанку сторінки: 11,3 мс у режиміstatic, 19,7 мс у режиміdynamic. - Перемикання мови: вдвічі швидше і без ривків. Оптимізована конфігурація
scoped-dynamicу Lingui витрачає 42 мс на оновленняhtml[lang], оскільки каталог маршруту має бути отриманий, завантажений та активований до того, як зміни відобразяться. Адаптер стабільно тримається на рівні 2,9-3,3 мс в обох режимах. - Базове налаштування виправляється автоматично. Статичний Lingui доставляє всі каталоги на кожну сторінку: 152,2 КБ та 90% витоку сторінок. Статичний адаптер: 140,5 КБ, 0% витоку сторінок, на тих самих компонентах.
- Вага кожної сторінки: Lingui виграє в
dynamicна 22 КБ. Це важливий нюанс, який слід враховувати. Lingui транслює повідомлення в масиви токенів ще під час збирання й додає легкий рантайм на 11 КБ, який лише проходить по них. Адаптер постачає ICU-резолвер Intlayer (приблизно на 15 КБ більше коду@intlayer/coreпорівняно з нативною збіркою), шар адаптера (~10 КБ) таreact-intlayer(~6 КБ). У даному застосунку це дає 137,0 КБ проти 115,2 КБ. Якщо мінімальна вага кожної сторінки є вашим єдиним пріоритетом і ви вже налаштували Lingui з лінивим завантаженням, адаптер не допоможе зменшити цю цифру. - Витік локалі однаковий з обох боків. 9,3% для Lingui та 9,9% для адаптера в режимі
dynamic. Причина криється в коді компонентів: викликi18n._({ id: "careers-benefits.pay", message: "Top-of-market compensation" })містить англійський оригінал як резервний варіант, так само як і вивід макросів, якщо поле message не видалене. Цей англійський текст потрапляє в чанкfrнезалежно від середовища виконання. Нативний Intlayer (.content.ts, де немає вбудованого тексту в коді) досягає 0%.
Чому змінюються цифри і чому один показник залишається сталим
Ці значення визначаються двома чинниками: до чого прив'язаний компонент та в якому форматі передаються повідомлення.
Прив'язка. У Lingui базовою одиницею поділу виступає вся мова цілком. Файл messages.mjs для fr є неподільним модулем; будь-який компонент, що імпортує екземпляр, має доступ до всього змісту, тому бандлер не може розбити його дрібніше за рівень локалі. В адаптері одиницею стає конкретне місце виклику: частини hero та footer є окремими імпортами, які бандлер розділяє й підвантажує для кожного компонента індивідуально. Звідси економія у вазі компонентів, гідратації та усуненні витоку сторінок.
Скопіюйте код у буфер обміну
Скопіюйте код у буфер обміну
Формат. Компілятор Lingui трансформує вираз {count, plural, one {# item} other {# items}} у масив токенів; рантайму не потрібно парсити синтаксис ICU. Адаптер зберігає повідомлення як текст і розбирає його за допомогою ICU-резолвера Intlayer. Це створює фіксований оверхед близько 15 КБ один раз на сторінку. Саме тому рядок dynamic поступається за обсягом байтів, виграючи за всіма іншими критеріями. Нативний Intlayer уникає цього оверхеду, оскільки в словниках .content.ts використовуються вузли enu() / insert(), попередньо розраховані компілятором.
Міграція у три кроки
Встановлення
bashКопіювати кодСкопіюйте код у буфер обміну
Ця команда розпізнає Lingui, аналізує
lingui.config.tsдля виборуsyncPO(для каталогів.po) абоsyncJSON(для каталогів JSON), встановлює пакетиintlayer,react-intlayer,@intlayer/linguiта відповідний плагін синхронізації, а також замінює@lingui/vite-pluginна плагін адаптера у файліvite.config.ts. Залиште встановленими@lingui/core,@lingui/reactта плагін макросів: макроси компілюються як раніше, а адаптер спирається на типи Lingui.Підключення Intlayer до каталогів
Для каталогів JSON (коли
format: "minimal"уlingui.config.ts):intlayer.config.tsКопіювати кодСкопіюйте код у буфер обміну
Для каталогів
.poзамінітьsyncJSONнаsyncPOз пакета@intlayer/sync-po-pluginз аналогічним шаблономsourceта розширенням.po. Дивіться документацію плагіна Sync PO.Параметр
splitKeys: "key-prefix"є вирішальним фактором для радикального полегшення компонентів. Сам файл каталогу зберігає пласку структуру; поділ існує тільки у згенерованих словниках, а зворотна синхронізація автоматично об'єднує ключі.Додавання плагіна
vite.config.tsКопіювати кодСкопіюйте код у буфер обміну
Плагін
lingui()об'єднуєvite-intlayer(відстеження файлів, компіляція словників, оптимізація) та перенаправляє імпорти@lingui/coreі@lingui/reactна адаптер. Запустіть збирання проєкту, і наведені вище результати стануть доступними для вашого застосунку.
Що можна видалити після переходу
Відкрийте таблицю в модальному вікні, щоб чітко переглянути всі дані
| Файл / патерн | Причина |
|---|---|
await import(`./locales/${locale}/messages.mjs`) | Словники імпортуються компонентами напряму. Метод i18n.load() стає резервним варіантом | |
i18n.load() / i18n.loadAndActivate() | Залиште i18n.activate(locale); видаліть ручне завантаження каталогів |
lingui compile у сценаріях збирання | Тільки якщо ви обрали JSON або .po як джерело і більше не імпортуєте скомпільовані модулі |
Що ви отримуєте крім економії кілобайтів
- Виявлення пропущених перекладів. Команда
npx intlayer testперериває пайплайн CI, якщо для певної мови відсутній ключ;lingui extractлише показує статистичні дані. - Автозаповнення через
npx intlayer fill. Перекладає відсутні ключі за допомогою обраного AI-провайдера (OpenAI, Anthropic, Mistral, Gemini...) і зберігає їх безпосередньо у ваших каталогах. - Візуальний редактор і CMS. Працюють з тими самими словниками, дозволяючи менеджерам редагувати файли
.poта JSON через зручний інтерфейс. - Поступова міграція на
.content.ts. Будь-який компонент можна будь-коли перевести зuseLingui()наuseIntlayer("hero")з окремим файлом декларації контенту. Обидва типи словників чудово співіснують.
Обмеження, про які варто знати заздалегідь
- Накладні витрати на сторінку в режимі
dynamic. Як зазначено вище: очікуйте приблизно +20 КБ на сторінку порівняно з лінивим Lingui на невеликому застосунку. Ця різниця не збільшується зі зростанням обсягу контенту (вона зумовлена резолвером, а не каталогами), але й не зменшується. - Витік вихідної мови залишається. Дескриптори повідомлень і скомпільовані макроси містять англійський текст як страховку від помилок. Якщо потрібно позбутися цього повністю, доведеться очистити поле
messageабо перевести компонент на.content.ts. i18n.load()є резервним механізмом. Якщо продовжувати імпортувати скомпільовані каталоги та викликатиload(), завантажуватимуться і старий, і новий бандли водночас. Видаліть ці імпорти.- Лише для екосистеми Vite. Окремого плагіна для Next.js у складі
@intlayer/linguiнемає. Проєктам на Next.js із Lingui варто відразу розглядати перехід наnext-intlayer. - Властивість
defaultComponentне задіяна. Якщо ви покладалися на неї для автоматичного загортання кожного<Trans>, додайте цей контейнер явно всередині компонентів.
Що обрати для вашого проєкту?
- Залишайтеся на Lingui, якщо у вас уже налаштована схема
scoped-dynamic, єдиним пріоритетом є мінімальний розмір сторінки в кілобайтах, а перемикання мови за 42 мс і гідратація за 30 мс цілком прийнятні для вашого застосунку. - Обирайте
@intlayer/lingui, якщо ви використовуєте Lingui та бажаєте отримати легші компоненти, швидку гідратацію та зміну мови, 0% витоку сторінок у базових схемах, типізовані ідентифікатори, перевірки в CI та ШІ-переклад без потреби змінювати макроси. Це ідеальний міст для оновлення поточної кодової бази. - Переходьте на нативний Intlayer (
react-intlayer), коли почнеться запланований рефакторинг компонентів. Це єдиний варіант у таблиці, що забезпечує 0% витоку локалі, 5 КБ рантайму і лише +7,6 КБ на сторінку порівняно з базовим застосунком.
Схожі порівняльні огляди
- Lingui проти Intlayer (порівняння бібліотек у рамках того самого бенчмарка)
- next-intl проти @intlayer/next-intl (матеріал із серії про адаптери)
- i18next проти @intlayer/i18next (матеріал із серії про адаптери)
- vue-i18n проти @intlayer/vue-i18n (матеріал із серії про адаптери)
- Довідник з адаптера сумісності: Lingui
- Компілятор проти декларативної i18n
Висновок
@intlayer/lingui докорінно змінює те, до чого прив'язуються місця виклику в Lingui: замість глобального екземпляра та монолітного мовного каталогу кожен компонент отримує словник, підготовлений виключно для нього. На тому самому застосунку TanStack Start це забезпечує зменшення компонентів у 7 разів, прискорення гідратації на 8-14 мс, вдвічі швидше перемикання мови та усунення 42-мілісекундної затримки, не вимагаючи редагування макросів. Рішення не змінює резервні тексти всередині компонентів (тому витік вихідної мови залишається) і розбирає ICU на льоту (динамічна схема завантажує приблизно на 20 КБ більше на сторінку, ніж чистий Lingui). Оцініть пріоритетні показники вашого проєкту перед тим, як зробити остаточний вибір.
Усі первинні дані, тестові застосунки та скрипти розміщені у репозиторії Benchmark Bloom. Ви можете самостійно відтворити ці вимірювання.
Ознайомтеся з матеріалом 'Чому Intlayer?' для отримання додаткової інформації.
Коментарі
Поки що немає коментарів. Будьте першим, хто поділиться своїми думками.
