Задайте питання та отримайте підсумок документа, вказавши цю сторінку та обраного вами постачальника штучного інтелекту
Вміст цієї сторінки перекладено за допомогою штучного інтелекту.
Переглянути останню версію оригінального вмісту англійськоюЯкщо у вас є ідея щодо покращення цієї документації, будь ласка, долучіться, надіславши pull request на GitHub.
Посилання на документацію на GitHubСкопіювати документацію у форматі Markdown в буфер обміну
next-intl VS @intlayer/next-intl | Один API, різні Bundle
@intlayer/next-intl - це адаптер сумісності: він надає API next-intl (useTranslations, getTranslations, useLocale, t.rich(), ICU plurals, NextIntlClientProvider...) та обслуговує його з словників, скомпільованих Intlayer. Код застосунку не змінюється. Змінюється bundle.
У цій статті порівнюються обидва на одному й тому ж Next.js застосунку, побудованому один раз з next-intl та один раз з адаптером. Цифри взяті з Benchmark Bloom, open-source набору, який записує, що насправді завантажує браузер. Якщо вам потрібне порівняння next-intl та Intlayer як бібліотек, прочитайте next-intl vs Intlayer. Ця стаття про те, як адаптер змінює все, коли ви зберігаєте компоненти як вони є.
tl;dr: На тій же Next.js програмі замінаnext-intlна@intlayer/next-intlзменшила JavaScript на сторінку з 153.6 KB до 147.5 KB gzip, середній компонент з 21.8 KB до 8.1 KB, витік рядків іноземних сторінок з ~90% до 0% та гідрацію з 14.7 ms до 12.8 ms, без редагування компонентів. На TanStack Start еквівалентuse-intl(@intlayer/use-intl) зменшив компоненти з 76-87 KB до 9-11 KB та перемикання локалі з 7-21 ms до 4-9 ms. Адаптер коштує 8.0 KB runtime порівняно з 14.7 KB дляnext-intlта 5.5 KB для нативногоnext-intlayer. Навігація та middleware переімплементовані на конфігурації маршрутизації Intlayer; локалізованіpathnames- це одна функція, яка не перенесена.
Що таке @intlayer/next-intl
next-intl - це runtime: getRequestConfig завантажує messages/{locale}.json на запит, NextIntlClientProvider відправляє його на клієнт, а useTranslations("about") читає ключі з цього об'єкту під час рендерингу. Кожна оптимізація (namespaces, pick(messages, [...]) на сторінку, ліниве завантаження) - це ваша робота.
@intlayer/next-intl зберігає першу та останню частину цього ланцюга й замінює середину. Ваші компоненти все ще викликають useTranslations("about"); те, що вони отримують, походить із словника Intlayer, скомпільованого під час збирання, обмеженого цією компонентою, лише в активній мові.
Три механізми роблять це можливим:
- Import aliasing.
createNextIntlPlugin()з@intlayer/next-intl/pluginобертаєwithIntlayerі додає aliases у Webpack / Turbopack, щобnext-intl,next-intl/server,next-intl/navigationтаnext-intl/middlewareбули перенаправлені на@intlayer/next-intl. Жоден import у вашій codebase не перейменовується. - JSON як джерело істини. The
syncJSONplugin читає ваш існуючийmessages/{locale}.json, ділить його top-level ключі на один словник на кожний namespace, і записує переклади назад в ті самі файли, коли CLI або CMS оновлює їх. Робочий процес ваших перекладачів залишається незмінним. - Call-site binding. The Intlayer optimize pass (Babel or SWC) rewrites
useTranslations("about")into a call that receives theaboutdictionary directly. The component no longer reaches a global message tree; it reaches its own content.
Скопіюйте код у буфер обміну
Скопіюйте код у буфер обміну
Це переписування - причина, чому колонки розміру компонента та витоку сторінки нижче зміщуються: сторінка завантажує лише словники компонентів, які вона рендерує, і тільки на мові, яка подається.
Що адаптер зберігає, ігнорує та не замінює
Відкрийте таблицю в модальному вікні, щоб чітко переглянути всі дані
next-intl API | З @intlayer/next-intl |
|---|---|
useTranslations("ns") / getTranslations("ns") | ✅ Збережено. Прив'язано до словника ns під час збірки. Ключи набиралися на основі вашого вмісту. |
getTranslations({ locale, namespace }) | ✅ Збережено |
t("key", { name }), t.rich(), t.markup(), t.raw() | ✅ Збережено. ICU plurals, select, selectordinal, #, {ts, date, long} обробляються через ICU resolver Intlayer |
useLocale() / getLocale() / setRequestLocale() / setLocale | ✅ Збережено |
useFormatter() | ✅ Збережено. dateTime, number, relativeTime, list, dateTimeRange підтримують нативний Intl |
NextIntlClientProvider | ✅ Збережено. Props messages, timeZone та now приймаються, але ігноруються (девелопер отримує попередження) |
getMessages() | ✅ Збережено для сумісності; більше не потрібен |
getRequestConfig() у src/i18n.ts | ⚠️ Не потрібен. Словники компілюються під час збірки; немає завантаження повідомлень для кожного запиту |
defineRouting() | ✅ Збережено. Пропущені поля (locales, defaultLocale, localePrefix) читаються з intlayer.config.ts |
createNavigation(), Link, redirect, usePathname, useRouter | ✅ Збережено. Перереалізовано на базі конфігурації маршрутизації Intlayer; аргумент routing приймається, але ігнорується |
pathnames (локалізовані імена маршрутів) | ❌ Приймається для типізації, не інтерпольовується. Зберігайте звичайні імена маршрутів або перенесіть це відображення на Intlayer's rewrite |
createMiddleware() | ✅ Збережено. Повертає проксі Intlayer; встановлює cookie NEXT_LOCALE, щоб useLocale() та ваш перемикач продовжували працювати |
NEXT_LOCALE cookie | ✅ Читається за замовчуванням (якщо ви не налаштуєте routing.storage самостійно) |
Простий useTranslations() без namespace | ⚠️ Працює, але місце виклику не пов'язане: розв'язується через реєстр runtime. Передайте namespace для отримання переваг bundle |
Бенчмарк
Що було вимірено
Набір 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.
next-intl було створено з чотирма стратегіями завантаження, від наївної конфігурації (messages/{locale}.json завантажується повністю) до оптимальної (one namespace per route + per-page pick()). Адаптер був створений на тих же компонентах, що й наівна конфігурація, з тільки змінами в next.config.ts та intlayer.config.ts. Він не має варіанту "scoped": компілятор охоплює контент на рівні компонента, тому його рядки static та dynamic вже охоплені.
Для кожної збірки набір тестів записує:
- Lib size: gzip розмір порожнього компонента, який лише імпортує бібліотеку i18n. Фіксована вартість runtime.
- Page JS: gzip JavaScript, завантажений на сторінку, усереднений по всіх сторінках і локалях.
- Locale leak %: частка перекладених рядків, знайдених у завантаженому JS, які належать мові, яку користувач не переглядає.
- Page leak %: частка перекладених рядків, знайдених у завантаженому JS, які належать сторінці, на якій користувач не знаходиться.
- Component avg: середній розмір gzip кожного компонента, скомпільованого окремо. Показує, скільки i18n runtime та каталогу витягує один компонент.
- E2E reactivity: час від моменту вибору нової мови до оновлення
html[lang]у DOM (Playwright, 5 ітерацій). - Hydration: тривалість фази React hydration.
Числа нижче отримані з запуску від 2026-09-12 зnext-intl/use-intl4.14.2 та@intlayer/*9.5.1. Тестовий застосунок навмисне малий (кілька десятків рядків на кожну мову), тому відсотки витоку описують закономірність: вони зростають з вашим контентом, тоді як витрати runtime залишаються постійними.
Результати на Next.js
Відкрийте таблицю в модальному вікні, щоб чітко переглянути всі дані
| Setup | Strategy | Lib size (gz) | Page JS avg (gz) | Locale leak | Page leak | Component avg (gz) | E2E reactivity | Hydration |
|---|---|---|---|---|---|---|---|---|
| base (no i18n) | - | 0.0 KB | 141.0 KB | 0.0% | 0.0% | 0.9 KB | 13.4 ms | 11.8 ms |
next-intl | static | 14.7 KB | 153.6 KB | 4.2% | 89.8% | 21.8 KB | 16.0 ms | 14.7 ms |
next-intl | dynamic | 14.7 KB | 153.6 KB | 9.7% | 89.9% | 21.8 KB | 15.6 ms | 14.8 ms |
next-intl | scoped-static | 14.7 KB | 153.6 KB | 0.0% | 0.0% | 80.1 KB | 17.9 ms | 17.4 ms |
next-intl | scoped-dynamic | 14.7 KB | 153.6 KB | 0.0% | 0.0% | 22.9 KB | 17.8 ms | 16.8 ms |
@intlayer/next-intl | static | 8.0 KB | 147.5 KB | 0.0% | 0.0% | 8.1 KB | 14.5 ms | 12.8 ms |
@intlayer/next-intl | dynamic | 8.0 KB | 148.7 KB | 0.0% | 0.0% | 8.1 KB | 11.7 ms | 12.8 ms |
next-intlayer (native) | static | 5.5 KB | 141.3 KB | 0.0% | 0.0% | 8.5 KB | 15.5 ms | 16.9 ms |
next-intlayer (native) | dynamic | 5.5 KB | 141.3 KB | 0.0% | 0.0% | 6.9 KB | 15.3 ms | 15.9 ms |
Як це читати
- Ті самі компоненти, на 6 KB менше на сторінку. Адаптер build наївної програми приходить до 147.5 KB, менше ніж будь-яка конфігурація
next-intl, включаючи повністю оптимізовану (153.6 KB). Сам runtime є різницею: 8.0 KB проти 14.7 KB, сплачується на кожній сторінці. - Витік коду йде до 0% без змін компонентів. Наївне налаштування
next-intlпоставляє ~90% рядків іноземних сторінок на кожну сторінку. Досягнення 0% за допомогоюnext-intlозначає налаштуванняscoped-*: один простір імен на маршрут іpick(messages, [...])на кожній сторінці. Адаптер досягає 0% з наївного коду, оскільки крок оптимізації прив'язує коженuseTranslations("ns")до свого словника. - Компоненти зменшуються в 2,7 разу. Компонент, скомпільований окремо, в середньому становить 21,8 KB з
next-intl(він досягає провайдера та дерева повідомлень) і 8,1 KB з адаптером. У налаштуванніscoped-staticnext-intlце число зростає до 80 KB, оскільки файл простору імен кожного маршруту стає доступним зі сторінки, яка його вибирає. - Гідрація на 2 мс швидша (12.8 vs 14.7 мс): немає об'єкта повідомлень для десеріалізації з RSC payload перед тим, як React може гідрувати.
- Адаптер - це не native runtime.
next-intlayerзаймає 141.3 KB, +0.3 KB понад базову програму, з 5.5 KB runtime. Адаптер несе поверхню APInext-intl(useFormatter,t.rich, ICU resolver) поверх ядра Intlayer, звідси 8.0 KB і +6 KB за сторінку. Це міст, а не пункт призначення.
Результати на TanStack Start (use-intl)
use-intl - це framework-агностичне ядро next-intl. Його адаптер @intlayer/use-intl слідує тому ж дизайну з Vite плагіном (@intlayer/use-intl/plugin).
Відкрийте таблицю в модальному вікні, щоб чітко переглянути всі дані
| Налаштування | Стратегія | Розмір Lib (gz) | Page JS середньо (gz) | Витік локалі | Page витік | Component середньо (gz) | E2E реактивність | Гідратація |
|---|---|---|---|---|---|---|---|---|
| base (без i18n) | - | 0.0 KB | 111.0 KB | 0.0% | 0.0% | 0.7 KB | 8.1 ms | 21.6 ms |
use-intl | static | 14.1 KB | 179.8 KB | 50.0% | 89.8% | 76.0 KB | 6.7 ms | 15.3 ms |
use-intl | dynamic | 14.1 KB | 119.4 KB | 0.0% | 89.8% | 75.9 KB | 7.0 ms | 15.4 ms |
use-intl | scoped-static | 14.1 KB | 128.7 KB | 0.0% | 0.0% | 87.1 KB | 20.9 ms | 24.8 ms |
use-intl | scoped-dynamic | 14.1 KB | 128.7 KB | 0.0% | 0.0% | 87.1 KB | 13.3 ms | 25.9 ms |
@intlayer/use-intl | static | 7.3 KB | 135.8 KB | 49.7% | 0.0% | 10.9 KB | 4.2 ms | 10.5 ms |
@intlayer/use-intl | dynamic | 7.3 KB | 129.7 KB | 0.0% | 0.0% | 9.3 KB | 8.7 ms | 16.1 ms |
intlayer (native) | static | 5.0 KB | 125.8 KB | 50.0% | 0.0% | 8.1 KB | 3.2 ms | 11.5 ms |
intlayer (native) | dynamic | 5.0 KB | 118.6 KB | 0.0% | 0.0% | 6.3 KB | 3.6 ms | 14.1 ms |
Як читати це
- Байти на сторінку практично однакові з оптимізованим
use-intl.@intlayer/use-intlу режиміdynamic(129.7 KB) знаходиться в межах 1 KB відuse-intl'sscoped-dynamic(128.7 KB) і на 10 KB вище звичайногоdynamicвідuse-intl(119.4 KB). Той звичайний рядокdynamicвсе ще витікає 90% рядків зі сторінок інших мов; кількість байтів низька, тому що тестовий додаток має малий обсяг вмісту. Адаптер 0% залишається постійним у міру зростання вмісту. - Компоненти мають розмір у 7-9 разів менший. Компоненти
use-intlмають середній розмір 76-87 KB у кожній стратегії, оскількиuseTranslationsпривʹязаний до цілого об'єкту message провайдера. Адаптер має середній розмір 9-11 KB. - Переключення локалі працює швидше. Оптимізовані налаштування
use-intlпотребують 13-21 мс для оновленняhtml[lang]; адаптер потребує 4-9 мс. Менше компонентів перерендерюється, і ніщо не переважається із дерева повідомлень. staticзберігає кожну локаль. Рядок адаптераstaticпоказує витік локалі 49.7%, той же, що й у нативному Intlayer у режиміstatic: усі локалі bundled, але лише словники сторінки. Один рядок конфігурації (importMode: 'dynamic') видаляє його.
Чому змінюються цифри
Ніщо в компоненті не змінилось, тому виграш повністю походить від того, до чого привʹязаний useTranslations.
З next-intl, binding це провайдер. NextIntlClientProvider отримує весь об'єкт messages для локалі; кожен useTranslations("about") читає з нього. Bundler бачить один компонент, що імпортує один hook, який читає один контекст, і не може знати, що використовується тільки гілка about. Маршрути нижче всі ділять один і той же об'єкт messages, тому колонка page-leak читає ~90%, доки ви самі не розділите файл.
Скопіюйте код у буфер обміну
З @intlayer/next-intl, привʼязка - це словник. syncJSON перетворює messages/en.json на один словник для кожного ключа верхнього рівня; компілятор визначає, який компонент викликає useTranslations("about"), і передає йому about безпосередньо, активною мовою, як імпорт, який bundler може розпізнати та розділити.
Скопіюйте код у буфер обміну
src/i18n.ts та props messages зникають. Все інше залишається ідентичним.
Міграція в три кроки
Встановлення
bashКопіювати кодСкопіюйте код у буфер обміну
Команда виявляє
next-intlі встановлюєintlayer,next-intlayer,@intlayer/next-intlта@intlayer/sync-json-plugin. Тримайтеnext-intlвстановленим: це peer dependency адаптера і надає типи.Вкажіть Intlayer на ваші повідомлення
intlayer.config.tsКопіювати кодСкопіюйте код у буфер обміну
messages/{locale}.jsonзалишається на місці. Кожен ключ верхнього рівня стає словником;useTranslations("about")відображається на словникabout.Обгорнути next.config.ts
next.config.tsКопіювати кодСкопіюйте код у буфер обміну
createNextIntlPlugin()компонуєwithIntlayer(спостереження за контентом, компіляція словників, оптимізаційний прохід) таnext-intl→@intlayer/next-intlaliases для Webpack та Turbopack. Збуйте, і цифри в таблицях вище будуть вашими.
Що ви можете видалити потім
Відкрийте таблицю в модальному вікні, щоб чітко переглянути всі дані
| Файл / шаблон | Чому |
|---|---|
getRequestConfig в src/i18n.ts | Немає завантаження повідомлень за запитом. Збережіть файл лише якщо він також експортує createNavigation помічники |
messages={...} на NextIntlClientProvider | Адаптер читає скомпільовані результати; властивість ігнорується та логує попередження під час розробки |
await getMessages() у layouts | З тієї ж причини |
Per-page pick(messages, [...]) | Компілятор виконує вибір для кожного компонента |
Що ви отримуєте крім зменшення розміру
- Типізовані ключі.
useTranslations("about")типізована проти скомпільованого словникаabout.t("does.not.exist")- це помилка TypeScript, а не fallback під час виконання. npx intlayer testне проходить CI, коли локалі не вистачає ключа.npx intlayer fillперекладає відсутні за допомогою обраного провайдера (OpenAI, Anthropic, Mistral, Gemini...) використовуючи ваш власний ключ, і записує результат назад уmessages/{locale}.json.- Visual Editor і CMS працюють з одними й тими ж словниками, тому розробники, що не є розробниками, можуть редагувати
messages/fr.jsonчерез UI і файл оновлюється. - Постійний перехід до
.content.ts. Будь-який компонент може перейти зuseTranslations("about")наuseIntlayer("about")з co-located файлом вмісту, один за одним. JSON і.content.tsсловники співіснують і об'єднуються.
Обмеження, які варто знати перед початком
- Routing config переміщується до
intlayer.config.ts.createNavigation(routing)таcreateMiddleware(routing)зберігають свою сигнатуру, але ігнорують аргумент: locales, default locale та стратегія префіксу беруться з Intlayer'sroutingconfig. Якщо ви використовуєтеnext-intl's локалізованіpathnames(/about→/a-propos), адаптер їх не інтерполює; Intlayer'srouting.rewriteохоплює цей випадок, але це окрема зміна. useTranslations()без namespace не прив'язаний. Optimize pass потребує static namespace, щоб знати який dictionary імпортувати. Bare call все ще працює через runtime registry, який посилається на кожен dictionary, що є точно витоком, який ви намагалися видалити. Передавайте namespace.- Адаптер не безкоштовний. 8.0 КБ runtime проти 5.5 КБ для
next-intlayer, та +6-7 КБ на сторінку над нативною збіркою. Це плата заnext-intlAPI surface. Якщо ви дійшли до точки, де кожний компонент був перенесений наuseIntlayer, відмовтеся від адаптера. messages,timeZone,nowна провайдері ігноруються. Форматери підтримуються нативнимIntlі лише locale впливає на їх вихід; якщо ви покладаєтеся на примусову часову зону або фіксованийnowдля hydration-стійких дат, обробіть це на місці виклику.
Коли використовувати що?
- Залишайтеся на
next-intlякщо ваш додаток малий, ваш bundle не є проблемою, і ваша команда комфортно володіє namespaces таpick()на сторінку. - Використовуйте
@intlayer/next-intl, якщо ви вже використовуєтеnext-intlі хочете отримати переваги у bundle, витоки та hydration, типізовані ключі та інструменти CLI / CMS без переписування. Це рекомендована точка входу для будь-якої існуючої codebasenext-intl. - Перейдіть на native (
next-intlayer) для нових проектів або після того, як адаптер виконав свою роботу. Це найлегший із трьох (5.5 KB, +0.3 KB на сторінку) і розблоковує синхронні server components, файли.content.tsза компонентом та повний набір функцій.
Пов'язані порівняння
- next-intl vs Intlayer (бібліотеки, той же бенчмарк)
- i18next vs @intlayer/i18next (та ж серія адаптерів)
- Lingui vs @intlayer/lingui (та ж серія адаптерів)
- vue-i18n vs @intlayer/vue-i18n (та ж серія адаптерів)
- Посібник з міграції: next-intl на Intlayer
- Довідка адаптера сумісності: next-intl
Висновок
@intlayer/next-intl робить одне: змінює те, до чого привязується useTranslations, від provider'а, який містить кожне повідомлення, до dictionary, скомпільованого для цього компонента. На тій же Next.js додатку, що коштує 6 KB на сторінку, компоненти в 2.7x менші, 0% витоку та 2 ms гідратації, перед тим, як хтось відкриє файл компонента. Navigation і middleware зберігають свій API поверх конфігу маршрутизації Intlayer, а нативний next-intlayer runtime залишається ще легшим.
Усі вихідні дані, тестові додатки та скрипти знаходяться в репозиторії Benchmark Bloom. Запустіть це самостійно.
Зверніться до документації 'Why Intlayer?' для більш детальної інформації.
Коментарі
Поки що немає коментарів. Будьте першим, хто поділиться своїми думками.
