Автор:
    Дата створення:2026-09-13Останнє оновлення:2026-09-27

    i18next проти @intlayer/i18next: Однаковий API, інший bundle

    i18next VS Intlayer

    @intlayer/i18next, @intlayer/react-i18next та @intlayer/next-i18next - це адаптери сумісності. Вони надають API i18next, який ваш код уже використовує (useTranslation, t(), <Trans>, i18n.changeLanguage(), getFixedT, serverSideTranslations...) і постачають його зі словників, скомпільованих Intlayer. Компоненти не змінюються. Змінюється середовище виконання (runtime) під ними.

    У цій статті вимірюється така заміна на одному й тому ж додатку Next.js, зібраному один раз із next-i18next і один раз із @intlayer/next-i18next. Показники отримані з Benchmark Bloom. Для порівняння i18next та Intlayer як бібліотек прочитайте i18next проти Intlayer. Ця стаття присвячена тому, що саме змінює адаптер, якщо ви зберігаєте свій код без змін.

    tl;dr: На тому ж додатку Next.js заміна next-i18next на @intlayer/next-i18next зменшила обсяг JavaScript на сторінку з 218.5 KB до 150.7 KB gzip (базова конфігурація) і перевершила повністю оптимізовану конфігурацію next-i18next (163.4 KB) на 12.7 KB. Середній компонент зменшився з 78.5 KB до 9.7 KB, витік рядків з інших сторінок скоротився з ~90% до 0%, гідратація прискорилася з 15.6 ms до 11.3 ms, а runtime зменшився з 19.7 KB до 9.4 KB. Жоден компонент не редагувався; змінено лише один файл провайдера. Плагіни i18next (бекенди, детектори мови) приймаються, але нічого не роблять: під час виконання більше нічого завантажувати або визначати.

    Що таке @intlayer/i18next

    i18next - це середовище виконання (runtime). i18n.init({ resources }) або плагін бекенда завантажує locales/{lng}/{ns}.json у глобальний екземпляр; useTranslation("about") підписує компонент на нього; t("title") шукає ключ під час рендерингу. Простори імен (namespaces), ліниве завантаження (lazy loading), списки просторів імен для кожної сторінки та типобезпека залишаються вашою турботою щодо налаштування та підтримки.

    Адаптери зберігають API і замінюють екземпляр:

    1. Аліаси імпорту. createNextI18nPlugin() з @intlayer/next-i18next/plugin (або withI18next) огортає withIntlayer та додає аліаси Webpack / Turbopack, завдяки чому next-i18next, react-i18next та i18next резолвляться до відповідників @intlayer/*. У Vite reactI18nextVitePlugin() з @intlayer/react-i18next/plugin робить те саме. Жоден імпорт не перейменовується.
    2. JSON як єдине джерело правди. Плагін syncJSON зчитує наявні файли locales/{lng}/{ns}.json з format: "i18next" (завдяки чому {{name}}, вкладеність $t(), суфікси _one / _other і контексти парсяться коректно) і записує переклади назад, коли CLI або CMS оновлюють їх.
    3. Зв'язування у місці виклику (call-site binding). Етап оптимізації Intlayer переписує useTranslation("about") у виклик, який отримує словник about напряму, в активній локалі. Компонент більше не звертається до глобального сховища.
    components/About.tsx
    // Ваш код, без змін
    import { useTranslation } from "react-i18next";
    
    const About = () => {
      const { t } = useTranslation("about");
      return <h1>{t("title")}</h1>;
    };
    
    Що генерує компілятор (спрощено)
    import _dicHash_about from "../.intlayer/dictionaries/about.mjs";
    import { useDictionary as useTranslation } from "@intlayer/react-i18next";
    
    const About = () => {
      const { t } = useTranslation(_dicHash_about);
      return <h1>{t("title")}</h1>;
    };
    

    Саме це переписування визначає показники у стовпчиках розміру компонентів та витоку сторінок нижче.

    Що адаптери зберігають, ігнорують і не замінюють

    API i18nextЗ @intlayer/*
    useTranslation("ns"), useTranslation("ns", { keyPrefix })✅ Збережено. Прив'язано до словника ns під час збірки; ключі типізовані відповідно до контенту
    t("key", { name }), {{interpolation}}, вкладеність $t(key)✅ Збережено
    Множина key_one / key_other, контекст key_male, returnObjects✅ Збережено. Множина обчислюється за допомогою Intl.PluralRules
    <Trans> з components, нумеровані теги <1>...</1>, values✅ Збережено
    withTranslation, Translation, I18nContext✅ Збережено
    i18n.changeLanguage(), i18n.language, i18n.dir(), on("languageChanged")✅ Збережено. changeLanguage керує локаллю Intlayer
    getFixedT(lng, ns, keyPrefix), i18n.exists(), hasLoadedNamespace()✅ Збережено
    i18n.use(Backend).use(LanguageDetector).init({...})⚠️ use() викликає init плагіна і повертає результат; бекендам і детекторам нічого завантажувати чи визначати
    init({ resources }), addResourceBundle()⚠️ resources ігнорується з попередженням dev; видаліть імпорти JSON, щоб отримати оптимізацію bundle
    I18nextProvider i18n={i18n}⚠️ Рендерить IntlayerProvider; проп i18n ігнорується. В App Router передавайте locale (див. нижче)
    serverSideTranslations(locale, ["common"]) (next-i18next)⚠️ Повертає очікувану структуру і нічого не завантажує. Безпечно залишити або видалити
    appWithTranslation(App) (next-i18next)✅ Збережено
    next-i18next.config.js⚠️ Не зчитується. Локалі надходять з intlayer.config.ts
    Простий useTranslation() без простору імен✅ Працює зі словником translation для всього файлу (splitKeys: false)

    Бенчмарк

    Що вимірювалося

    Набір тестів Benchmark Bloom збирає один і той самий додаток з кожною конфігурацією: 10 сторінок (головна, про нас, блог, кар'єра, контакти, FAQ, ціни, продукти, налаштування, команда), 10 локалей (en, fr, es, de, it, pt, zh, ja, ko, ru), однакові компоненти й однаковий вміст. Сторінки вимірюються в en та fr.

    next-i18next було зібрано за чотирма стратегіями завантаження: від імпорту JSON кожної локалі в resources (static) до одного простору імен на маршрут з лінивим завантаженням через бекенд (scoped-dynamic). Адаптер зібрано на тих самих компонентах, що й базову конфігурацію, зі зміненими next.config.ts, intlayer.config.ts та файлом провайдера. Він не має варіанту "scoped": компілятор самостійно обмежує контент для кожного компонента.

    Для кожної збірки фіксуються такі показники:

    • Lib size: розмір gzip порожнього компонента, який імпортує лише бібліотеку i18n.
    • Page JS: обсяг JavaScript gzip, завантажений на сторінку, усереднений для всіх сторінок і локалей.
    • Locale leak %: частка перекладених рядків у завантаженому JS, що належать локалі, яку користувач не переглядає.
    • Page leak %: частка перекладених рядків у завантаженому JS, що належать сторінці, на якій користувач не перебуває.
    • Component avg: середній розмір gzip кожного компонента, скомпільованого окремо.
    • E2E reactivity: реальний час між вибором нової локалі та оновленням html[lang] у DOM (Playwright, 5 ітерацій).
    • Hydration: тривалість фази гідратації React.
    Наведені нижче дані отримані під час тестування від 2026-09-12 з next-i18next 16.3.0 (react-i18next 17.0.13, i18next 26.4.2) та @intlayer/next-i18next 9.5.1. Тестовий додаток навмисно невеликий (кілька десятків рядків на локаль), тому відсотки витоку показують закономірність: вони зростають разом із вашим контентом, тоді як накладні витрати runtime залишаються фіксованими.

    Результати на Next.js

    Виберіть метрики та бібліотеки, які вас цікавлять:

    Метрика

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

    Ледаче завантаження перекладів під час виконання

    Обмежений JSON (простори імен)

    Простори імен перекладу для кожної сторінки

    Що це за метрика?

    Загальний стиснений у gzip розмір пакета бібліотеки інтернаціоналізації. Він включає лише провайдер та логіку отримання контенту після tree-shaking та мініфікації.

    Чому це важливо?

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

    Перегляд як

    КонфігураціяСтратегіяLib size (gz)Page JS avg (gz)Locale leakPage leakComponent avg (gz)E2E reactivityHydration
    base (без i18n)-0.0 KB141.0 KB0.0%0.0%0.9 KB13.4 ms11.8 ms
    next-i18nextstatic19.7 KB218.5 KB0.0%89.8%78.5 KB16.4 ms15.6 ms
    next-i18nextdynamic19.7 KB169.5 KB50.0%89.8%26.1 KB15.4 ms27.7 ms
    next-i18nextscoped-static19.7 KB220.1 KB0.0%89.8%78.9 KB16.4 ms14.7 ms
    next-i18nextscoped-dynamic19.7 KB163.4 KB0.0%0.0%27.1 KB15.9 ms15.1 ms
    @intlayer/next-i18nextstatic9.4 KB150.7 KB0.0%0.0%9.7 KB10.7 ms11.3 ms
    @intlayer/next-i18nextdynamic9.4 KB150.7 KB0.0%0.0%9.7 KB11.9 ms10.6 ms
    next-intlayer (native)static5.5 KB141.3 KB0.0%0.0%8.5 KB15.5 ms16.9 ms
    next-intlayer (native)dynamic5.5 KB141.3 KB0.0%0.0%6.9 KB15.3 ms15.9 ms

    Як інтерпретувати результати

    • На 68 KB менше на сторінку порівняно з базовою конфігурацією. resources: { en, fr, ... } передає кожну локаль і кожен простір імен на кожній сторінці: 218.5 KB. Збірка адаптера для тих самих компонентів становить 150.7 KB. Вона також перевершує найкращу конфігурацію next-i18next (163.4 KB, один простір імен на маршрут, ліниве завантаження) на 12.7 KB, оскільки лише runtime i18next важить 19.7 KB проти 9.4 KB.
    • Витік знижується до 0% без жодних змін у компонентах. Кожна конфігурація next-i18next, крім повністю ізольованої (scoped), передає ~90% рядків сторонніх сторінок. Рядок dynamic виглядає гірше, ніж здається: він не зменшує витік сторінок і додає 50% витоку локалей, оскільки бекенд для кожної локалі все одно підтягує весь простір імен translation. Адаптер досягає 0% / 0% безпосередньо з вихідного коду.
    • Компоненти: у 8 разів менші. Компонент з useTranslation(), скомпільований ізольовано, важить у середньому 78.5 KB із вбудованими resources та 26-27 KB з бекендом, оскільки t прив'язаний до глобального сховища. З адаптером він важить у середньому 9.7 KB.
    • Гідратація та перемикання відбуваються швидше. Гідратація скорочується з 15.6 ms до 11.3 ms (та з 27.7 ms у конфігурації dynamic, де запит до бекенда перебуває на критичному шляху). Перемикання локалі прискорюється з 15-16 ms до 11-12 ms.
    • Адаптер - це не нативний runtime. next-intlayer займає 141.3 KB, лише +0.3 KB над базовим додатком. Адаптер несе поверхню API i18next (діалект інтерполяції, суфікси множини та контексту, парсинг тегів <Trans>) поверх ядра Intlayer: 9.4 KB та +9.4 KB на сторінку проти нативного варіанту. Це міст для переходу, а не кінцева точка.
    Повна таблиця, кожна бібліотека та кожна стратегія, у звіті про бенчмарк Next.js.

    Результати на TanStack Start (react-i18next)

    Для Vite і TanStack Start бенчмарк порівнює чистий react-i18next з intlayer:

    БібліотекаСтратегіяLib size (gz)Page JS avg (gz)Locale leakPage leakComponent avg (gz)E2E reactivityHydration
    base (без i18n)-0.0 KB111.0 KB0.0%0.0%0.7 KB8.1 ms21.6 ms
    react-i18nextdynamic18.4 KB136.4 KB23.1%89.8%24.8 KB123.1 ms32.9 ms
    intlayerdynamic5.0 KB118.6 KB0.0%0.0%6.3 KB3.6 ms14.1 ms
    Повна таблиця у звіті про бенчмарк TanStack Start.
    Адаптер react-i18next на Vite / TanStack Start не брав участі у цьому тестуванні. Базові показники react-i18next на TanStack Start наведено у статті i18next проти Intlayer: 127-184 KB на сторінку та перемикання локалі за 123-185 ms за наявності лінивого бекенда.

    Чому змінюються цифри

    The Intlayer compiler extracts content from components

    У каталозі components/ нічого не змінилося, тож оптимізація пов'язана з тим, до чого саме прив'язаний useTranslation.

    З i18next прив'язка здійснюється до глобального екземпляра. Усе, що було в нього завантажено (усі локалі в static, увесь простір імен активної локалі в dynamic), доступне кожному компоненту, що викликає useTranslation(). Бандлер не може розбити бандл нижче за вміст екземпляра, а runtime не може знати наперед, які ключі знадобляться компоненту.

    bash
    .
    ├── next-i18next.config.js
    ├── public/locales
    │   ├── en/translation.json           # рядки для кожної сторінки
    │   └── fr/translation.json
    ├── i18n/i18n.ts                      # i18n.use(initReactI18next).init({ resources })
    └── components
        ├── AppProviders.tsx              # <I18nextProvider i18n={i18n}>
        └── About.tsx                     # useTranslation(); t("about.title")
    

    Все, що містить екземпляр, надсилається на кожну сторінку, і витрати зростають за двома осями, сторінки та локалі:

    Theoretical content leakage by architecture

    З @intlayer/next-i18next прив'язка здійснюється до словника. syncJSON перетворює кожен файл простору імен на словник; оптимізаційний етап передає компоненту саме той словник, який йому потрібен, у вигляді імпорту, що бандлер може відстежити й розділити за сторінками та локалями.

    bash
    .
    ├── intlayer.config.ts                # syncJSON({ format: "i18next", source: ... })
    ├── public/locales
    │   ├── en/translation.json           # без змін, залишається джерелом правди
    │   └── fr/translation.json
    ├── .intlayer/                        # згенеровано: один словник на простір імен для кожної локалі
    └── components
        ├── AppProviders.tsx              # <IntlayerClientProvider locale={locale}>
        └── About.tsx                     # useTranslation(); t("about.title")  ← без змін
    

    Файл i18n/i18n.ts та його імпорт resources стають мертвим кодом. Звідси й беруться ті самі 68 KB.

    Міграція у три кроки

    1. Встановлення

      bash
      npx intlayer init --interactive
      

      Команда виявляє i18next / react-i18next / next-i18next, встановлює intlayer, пакет відповідного фреймворку (next-intlayer або react-intlayer), сумісний адаптер @intlayer/* та @intlayer/sync-json-plugin, а також формує базовий intlayer.config.ts. Залиште оригінальні пакети встановленими: вони є peer dependencies та постачають типи TypeScript.

    2. Вкажіть Intlayer на файли локалей

      intlayer.config.ts
      import { Locales, type IntlayerConfig } from "intlayer";
      import { syncJSON } from "@intlayer/sync-json-plugin";
      
      const config: IntlayerConfig = {
        internationalization: {
          locales: [Locales.ENGLISH, Locales.FRENCH, Locales.SPANISH],
          defaultLocale: Locales.ENGLISH,
        },
        dictionary: {
          importMode: "dynamic",
          format: "i18next",
        },
        plugins: [
          syncJSON({
            // Діалект i18next: {{name}}, $t(key), key_one / key_other, key_male
            format: "i18next",
            // Один файл на простір імен: `useTranslation("about")` → about.json
            source: ({ locale, key }) => `./public/locales/${locale}/${key}.json`,
            location: "public/locales",
          }),
        ],
      };
      
      export default config;
      

      Якщо у вас є один файл translation.json на локаль (простір імен за замовчуванням в i18next), установіть splitKeys: false, щоб увесь файл залишався єдиним словником і звичайний виклик useTranslation() продовжував коректно працювати.

    3. Додайте плагін

      next.config.ts
      import type { NextConfig } from "next";
      import { withI18next } from "@intlayer/next-i18next/plugin";
      
      const nextConfig: NextConfig = {};
      
      export default withI18next(nextConfig);
      

      В App Router клієнтські компоненти отримують свою локаль із сегмента [locale]. Компонент I18nextProvider адаптера не приймає проп locale, тому замініть його один раз у файлі провайдера:

      components/AppProviders.tsx
      "use client";
      
      import { IntlayerClientProvider } from "next-intlayer";
      import type { LocalesValues } from "intlayer";
      
      export const AppProviders = ({
        locale,
        children,
      }: {
        locale: LocalesValues;
        children: React.ReactNode;
      }) => (
        <IntlayerClientProvider locale={locale}>{children}</IntlayerClientProvider>
      );
      

      Кожен компонент нижче в дереві продовжує викликати useTranslation().

      vite.config.ts
      import { defineConfig } from "vite";
      import react from "@vitejs/plugin-react";
      import { reactI18nextVitePlugin } from "@intlayer/react-i18next/plugin";
      
      export default defineConfig({
        plugins: [react(), reactI18nextVitePlugin()],
      });
      

      reactI18nextVitePlugin() огортає vite-intlayer та створює аліаси для react-i18next і i18next. Для проєктів без React плагін i18nextVitePlugin() з @intlayer/i18next/plugin створює аліас тільки для i18next.

    Що можна видалити після цього

    Файл / патернПричина
    resources: { en, fr, ... } та імпорти JSONІгноруються адаптером. Саме тут крилися зайві 68 KB
    i18next-http-backend, i18next-resources-to-backendБільше нічого не потрібно завантажувати під час виконання
    i18next-browser-languagedetectorВизначення локалі виконується маршрутизацією Intlayer (префікс URL, cookie, заголовок)
    serverSideTranslations() у getStaticPropsПовертає порожню структуру; безпечно, але більше непотрібно
    next-i18next.config.jsНе зчитується. Локалі налаштовуються в intlayer.config.ts
    Списки ns: [...] для кожної сторінкиКомпілятор самостійно призначає простори імен для кожного компонента

    Що ви отримуєте, крім зекономлених байтів

    • Типізовані ключі. useTranslation("about") строго типізується на основі скомпільованого словника about; t("does.not.exist") спричинить помилку TypeScript замість повернення рядка ключа.
    • npx intlayer test завершує перевірку помилкою в CI, якщо відсутній ключ у будь-якій локалі. npx intlayer fill перекладає відсутні ключі за допомогою вашого API-ключа постачальника (OpenAI, Anthropic, Mistral, Gemini...) та зберігає їх назад у locales/{lng}/{ns}.json.
    • Візуальний редактор і CMS працюють із тими самими файлами JSON, тому перекладачі можуть редагувати контент через інтерфейс користувача, а файли оновлюються автоматично.
    • Поступовий перехід на .content.ts. Будь-який компонент можна перевести з useTranslation("about") на useIntlayer("about") із супутнім файлом контенту. Словники JSON та .content.ts можуть мирно співіснувати.

    Обмеження, які слід врахувати перед початком

    i18n.use(HttpBackend) викликає init плагіна і більше нічого. Якщо ваш додаток покладався на отримання перекладів з CMS під час виконання, цей процес більше не працює; використовуйте Intlayer CMS або команди intlayer pull / push. Визначення локалі стає конфігурацією маршрутизації Intlayer (префікс URL, cookie, заголовок).

    На відміну від деяких інших адаптерів, @intlayer/i18next не використовує вбудовані resources як резервний варіант. Кожен ключ повинен існувати в синхронізованих словниках, що перевіряє intlayer test.

    Один файл, показаний вище. Pages Router з appWithTranslation не вимагає жодних змін.

    localePath, fallbackLng, reloadOnPrerender та аналоги не мають еквівалентів; локалі та резервні варіанти надходять з intlayer.config.ts.

    9.4 КБ рантайму та +9.4 КБ на сторінку порівняно з next-intlayer. Як тільки кожен компонент перейде на useIntlayer, видаліть його.

    Порівняння можливостей

    Окрім байтів, що дає кожен варіант:

    Можливістьi18next / react-i18next / next-i18nextАдаптери @intlayer/*Нативний Intlayer
    Ваші виклики t(), useTranslation, <Trans>✅✅ Без змін❌ Перенесено на useIntlayer
    Розмір runtime (gzip, Next.js)19.7 KB9.4 KB5.5 KB
    Витік інших сторінок без ручних namespaces~90%0%0%
    Типізовані ключі⚠️ Ручне оголошення✅ Зі скомпільованих словників✅ Генеруються автоматично
    Runtime-бекенди та плагіни✅ Повна екосистема плагінів❌ Неактивні❌ Не застосовується, використовуйте CMS
    Контент поруч із компонентами❌ Централізований JSON⚠️ JSON, .content.ts може співіснувати✅ .content.ts поруч із кожним компонентом
    Відсутні переклади в CI⚠️ Немає вбудованої підтримки✅ npx intlayer test✅ npx intlayer test
    AI-переклад❌ Ні✅ npx intlayer fill✅ npx intlayer fill
    Візуальний редактор / CMS❌ Через зовнішні платформи✅ На тому самому JSON✅ Так
    Екосистема / спільнота✅ Дуже велика⚠️ Менша, швидко зростає⚠️ Менша, швидко зростає
    Розміри runtime взято з описаного вище прогону на Next.js.

    Коли що використовувати?

    Ваш додаток залежить від бекендів часу виконання (переклади, що надаються CMS під час запиту), від екосистеми плагінів або від платформи без React, яку адаптери не підтримують.

    Ви використовуєте react-i18next / next-i18next і хочете отримати 68 КБ економії, у 8 разів менші компоненти, 0% витоків, типізовані ключі та перевірки CI без переписування коду. Це відправна точка для існуючої кодової бази i18next.

    Для нових проєктів або коли адаптер виконав своє завдання. Він має найлегший рантайм (5.5 КБ, +0.3 КБ на сторінку) і відкриває доступ до синхронних Server Components та файлів .content.ts для кожного компонента. Почніть з Intlayer з Next.js або з Vite та React.

    Часті запитання

    З resources: { en, fr, ... }. Стандартне налаштування next-i18next імпортує JSON кожної локалі в init(), тому кожна сторінка містить кожен простір імен на кожній мові: 218.5 КБ на сторінку. Адаптер ніколи не включає цей блок повністю; він передає кожному компоненту лише названий словник на активній мові.

    Так, з components, нумерованими тегами <1>...</1> та values. Також підтримуються {{interpolation}}, вкладеність $t(key), форми множини key_one / key_other (обчислювані за допомогою Intl.PluralRules), суфікси контексту та returnObjects.

    Встановіть splitKeys: false у плагіні syncJSON. Весь файл залишається одним словником, і звичайний виклик useTranslation() продовжує розпізнаватися відносно нього.

    Ні, це міст. Адаптер зберігає API i18next і потребує 9.4 КБ рантайму; нативний next-intlayer коштує 5.5 КБ і додає синхронні Server Components та файли .content.ts поруч із компонентами. Ви можете мігрувати покомпонентно, оскільки словники JSON та .content.ts співіснують.

    Так. locales/{lng}/{ns}.json залишається джерелом правди: syncJSON зчитує його з діалектом i18next і записує переклади назад при оновленні через CLI або CMS.

    Схожі порівняння

    Та ж серія адаптерів:

    Пряме порівняння бібліотек:

    Довідкова документація:

    Compat adapters:

    Migration guides:

    Щоб зрозуміти, звідки взялися ці бібліотеки, прочитайте історію i18n у JavaScript.

    Висновок

    i18next - найважчий runtime у цьому бенчмарку, і адаптери усувають більшу частину цього тягаря, не вимагаючи відмовлятися від знайомого API. На одному й тому ж додатку Next.js ви отримуєте на 68 KB менше на кожну сторінку, ніж у базовому налаштуванні, на 12.7 KB менше, ніж у найкращій оптимізованій вручну версії, у 8 разів менші компоненти, 0% витоків та на 4 ms швидшу гідратацію - ціною одного конфігураційного файлу, одного рядка плагіна та налаштування одного провайдера. Бекенди та детектори стають безпечними no-op, resources ігнорується замість об'єднання, а нативний runtime next-intlayer залишається ще на 9 KB легшим.

    Усі необроблені дані, тестові додатки та скрипти доступні у репозиторії Benchmark Bloom. Запустіть і перевірте самі.

    Докладніше дивіться в розділі 'Чому Intlayer?'.

    Коментарі

    Поки що немає коментарів. Будьте першим, хто поділиться своїми думками.

    Схожі публікації

    Останні публікації