Автор:
    Создание:2026-09-13Последнее обновление:2026-09-27

    i18next VS @intlayer/i18next: Тот же API, другой бандл

    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 vs Intlayer. Этот материал посвящен тому, что именно меняет адаптер, если вы оставляете свой код в исходном виде.

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

    Что такое @intlayer/i18next

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

    Адаптеры сохраняют 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. Связывание на месте вызова. Этап оптимизации 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 для снижения веса бандла
    I18nextProvider i18n={i18n}⚠️ Рендерит IntlayerProvider; проп i18n игнорируется. В App Router передайте локаль (см. ниже)
    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": компилятор изолирует контент для каждого компонента автоматически.

    Для каждой сборки фиксируются показатели:

    • Размер библиотеки: gzip-размер пустого компонента, который импортирует только библиотеку i18n.
    • JS на страницу: средний объем gzip JavaScript, загружаемый на страницу по всем маршрутам и локалям.
    • % утечки локали: доля строк в загруженном JS, относящаяся к языку, который пользователь в данный момент не просматривает.
    • % утечки страницы: доля строк в загруженном JS, относящаяся к странице, на которой пользователь в данный момент не находится.
    • Средний вес компонента: средний gzip-размер каждого изолированно скомпилированного компонента.
    • E2E-реактивность: реальное время между выбором новой локали и обновлением атрибута html[lang] в DOM (Playwright, 5 итераций).
    • Гидратация: продолжительность фазы гидратации React.
    Приведенные ниже цифры получены в прогоне от 12.09.2026 с версиями next-i18next 16.3.0 (react-i18next 17.0.13, i18next 26.4.2) и @intlayer/next-i18next 9.5.1. Тестовое приложение намеренно компактное (несколько десятков строк на локаль), поэтому проценты утечки отражают тенденцию: они масштабируются вместе с вашим контентом, тогда как стоимость рантайма остается неизменной.

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

    Выберите метрики и библиотеки, которые вас интересуют:

    Метрика

    Динамическая загрузка JSON

    Ленивая загрузка переводов во время выполнения

    Ограниченный JSON (пространства имен)

    Пространства имен перевода для каждой страницы

    Что это за метрика?

    Общий размер пакета библиотеки интернационализации в формате gzip. Он включает в себя только провайдер и логику извлечения контента после tree-shaking и минификации.

    Почему это важно?

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

    Вид

    КонфигурацияСтратегияРазмер либы (gz)JS стр сред (gz)Утечка локалиУтечка стрКомп сред (gz)E2E-реактивностьГидратация
    base (без i18n)-0.0 КБ141.0 КБ0.0%0.0%0.9 КБ13.4 мс11.8 мс
    next-i18nextstatic19.7 КБ218.5 КБ0.0%89.8%78.5 КБ16.4 мс15.6 мс
    next-i18nextdynamic19.7 КБ169.5 КБ50.0%89.8%26.1 КБ15.4 мс27.7 мс
    next-i18nextscoped-static19.7 КБ220.1 КБ0.0%89.8%78.9 КБ16.4 мс14.7 мс
    next-i18nextscoped-dynamic19.7 КБ163.4 КБ0.0%0.0%27.1 КБ15.9 мс15.1 мс
    @intlayer/next-i18nextstatic9.4 КБ150.7 КБ0.0%0.0%9.7 КБ10.7 мс11.3 мс
    @intlayer/next-i18nextdynamic9.4 КБ150.7 КБ0.0%0.0%9.7 КБ11.9 мс10.6 мс
    next-intlayer (native)static5.5 КБ141.3 КБ0.0%0.0%8.5 КБ15.5 мс16.9 мс
    next-intlayer (native)dynamic5.5 КБ141.3 КБ0.0%0.0%6.9 КБ15.3 мс15.9 мс

    Как читать эти данные

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

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

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

    БиблиотекаСтратегияРазмер либы (gz)JS стр сред (gz)Утечка локалиУтечка стрКомп сред (gz)E2E-реактивностьГидратация
    base (без i18n)-0.0 КБ111.0 КБ0.0%0.0%0.7 КБ8.1 мс21.6 мс
    react-i18nextdynamic18.4 КБ136.4 КБ23.1%89.8%24.8 КБ123.1 мс32.9 мс
    intlayerdynamic5.0 КБ118.6 КБ0.0%0.0%6.3 КБ3.6 мс14.1 мс
    Полная таблица в отчете о бенчмарке TanStack Start.
    Адаптер react-i18next на Vite / TanStack Start не входил в этот тестовый прогон. Базовые замеры для react-i18next на TanStack Start можно найти в статье i18next vs Intlayer: 127-184 КБ на страницу и 123-185 мс задержки переключения языка при отложенном бэкенде.

    Почему изменяются показатели

    The Intlayer compiler extracts content from components

    В директории components/ ничего не менялось, поэтому весь выигрыш достигается за счет того, к чему привязан useTranslation.

    В случае i18next привязка идет к глобальному экземпляру. Все, что было в него загружено (все языки в static, всё пространство имен активного языка в dynamic), доступно из любого компонента, вызывающего useTranslation(). Бандлер не может разделить код тоньше того, что удерживает экземпляр, а среда выполнения не знает, какие ключи понадобятся при рендеринге.

    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 КБ.

    Миграция в три шага

    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 и предоставляют типы.

    2. Настройка путей к файлам локалей

      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 на локаль (дефолтный namespace в 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 не принимает проп локали, поэтому замените его единожды в файле провайдера:

      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 КБ
    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, позволяя переводчикам вносить правки через UI с фиксацией в Git.
    • Постепенный переход на .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 КБ9.4 КБ5.5 КБ
    Утечка строк других страниц без ручных 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 КБ на страницу) и открывает доступ к синхронным серверным компонентам и файлам .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 КБ и добавляет синхронные серверные компоненты и файлы .content.ts рядом с компонентами. Вы можете мигрировать покомпонентно, так как словари JSON и .content.ts сосуществуют.

    Да. locales/{lng}/{ns}.json остается источником истины: syncJSON считывает его с диалектом i18next и записывает переводы обратно при обновлении через CLI или CMS.

    Связанные сравнения

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

    Прямое сравнение библиотек:

    Справочная документация:

    Compat adapters:

    Migration guides:

    Чтобы понять, откуда взялись эти библиотеки, прочитайте историю i18n в JavaScript.

    Заключение

    i18next оказался самым тяжелым рантаймом в этом бенчмарке, а адаптеры снимают подавляющую часть его нагрузки, сохраняя привычный API. На одном и том же приложении Next.js это дает на 68 КБ меньше на страницу по сравнению с наивной настройкой, на 12.7 КБ меньше, чем в самой оптимизированной ручной сборке, в 8 раз более компактные компоненты, 0% утечек и на 4 мс более быструю гидратацию ценой одного конфига, одной строки плагина и замены провайдера. Бэкенды и детекторы становятся неактивными, resources игнорируется, а нативный next-intlayer остается еще на 9 КБ легче.

    Все сырые данные, тестовые приложения и скрипты опубликованы в репозитории Benchmark Bloom.

    Подробности смотрите в документе Почему Intlayer?.

    Комментарии

    Пока нет комментариев. Будьте первым, кто поделится своими мыслями.

    Похожие сообщения

    Последние сообщения