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

    i18next против Intlayer | Бенчмарк интернационализации (i18n) для React и Next.js

    i18next - самый распространенный i18n-фреймворк в экосистеме JavaScript. Через react-i18next и next-i18next он используется в огромном количестве приложений на React и Next.js. Intlayer представляет собой альтернативу на основе компилятора с изолированной областью видимости для каждого компонента.

    В этой статье они сравниваются на основе реальных замеров, а не списков возможностей. Данные взяты из Benchmark Bloom - открытого набора тестов, собирающего одно и то же приложение на каждой библиотеке и фиксирующего то, что браузер фактически загружает по сети.

    tl;dr: i18next оказался самым тяжелым runtime в бенчмарке: +77 KB gzip на страницу в Next.js при базовой конфигурации, +22 KB после полной оптимизации пространств имен и ленивой загрузки. Intlayer добавляет всего +0.3 KB. Каждая конфигурация i18next, кроме полностью изолированной (scoped), отдает ~90% строк с посторонних страниц; Intlayer отдает 0% по умолчанию. Переключение языка с динамической подгрузкой через бэкенд заняло 123-185 ms в react-i18next против 3-4 ms в Intlayer. Адаптер @intlayer/next-i18next сохраняет API i18next и продемонстрировал результат 150.7 KB на страницу против 218.5 KB в оригинале.

    Вкратце

    • i18next / react-i18next / next-i18next - Зрелый, богатый плагинами, независимый от фреймворка инструмент. Пространства имен, детекторы языка, бэкенды, ICU через плагины, компонент <Trans> для сложной разметки. Контент хранится централизованно в locales/{lng}/{ns}.json. Мощный, однако каждая оптимизация (разделение пространств имен, постраничная загрузка, типобезопасность) требует ручной настройки и поддержки.
    • Intlayer - Модель с упором на компоненты. Словари .content.ts располагаются рядом с компонентом, для которого предназначены. Компилятор во время сборки выполняет tree-shaking и загружает их лениво для каждого компонента и языка. Строгие типы TypeScript формируются автоматически, а пропущенные переводы вызывают ошибки сборки. Включает middleware, SEO-утилиты, визуальный редактор / CMS и перевод с помощью ИИ.
    БиблиотекаЗвезды GitHubВсего коммитовПоследний коммитПервая версияВерсия NPMЗагрузки NPM
    aymericzip/intlayerGitHub Repo starsGitHub commit activityLast CommitАпрель 2024npmnpm downloads
    i18next/i18nextGitHub Repo starsGitHub commit activityLast CommitЯнварь 2012npmnpm downloads
    i18next/react-i18nextGitHub Repo starsGitHub commit activityLast CommitДекабрь 2015npmnpm downloads
    i18next/next-i18nextGitHub Repo starsGitHub commit activityLast CommitНоябрь 2018npmnpm downloads
    Значки обновляются автоматически. Показатели со временем меняются.

    Сравнение возможностей

    ВозможностьIntlayer (react-intlayer / next-intlayer)i18next (react-i18next / next-i18next)
    Переводы рядом с компонентами✅ Да, .content.ts располагается рядом с компонентом❌ Нет, централизованно в locales/{lng}/{ns}.json
    Интеграция с TypeScript✅ Строгие типы создаются автоматически из контента⚠️ Базовая; требует расширения CustomTypeOptions и описания ресурсов
    Поиск пропущенных переводов✅ Ошибка TypeScript + ошибка/предупреждение при сборке⚠️ Заглушка в runtime (saveMissing, возврат ключа)
    Сложный контент (JSX / Markdown)✅ Прямая поддержка⚠️ <Trans> с числовыми индексами
    Поддержка ICU⚠️ В разработке⚠️ Через плагин (i18next-icu)
    Плюрализация✅ Шаблоны на основе перечислений✅ Суффиксы _one / _other (Intl.PluralRules)
    Форматирование (даты, числа, валюты)useNumber, useDate, ... (встроенный Intl)⚠️ Форматировщики интерполяции или ручной вызов Intl.*
    Локализованная маршрутизация и middleware✅ Встроенный прокси/middleware, getMultilingualUrls⚠️ Не входит в ядро; сторонние библиотеки или собственный код
    SEO-утилиты (hreflang, sitemap, robots)✅ Встроенные помощники❌ Вручную
    Синхронные серверные компонентыuseIntlayer из next-intlayer/server доступен в любом серверном компоненте⚠️ getFixedT на странице и проброс t через props
    Tree-shaking (только нужный контент)✅ Для каждого компонента и языка, автоматически компилятором⚠️ Вручную: namespaces + список ns на страницу + бэкенд
    Lazy loadingimportMode: 'dynamic' (одна строка конфигурации)✅ Через плагины бэкенда (i18next-resources-to-backend, i18next-http-backend)
    Очистка неиспользуемого контента✅ Лишние словари отсекаются на этапе сборки❌ Не предусмотрено
    Проверка пропущенных строк (CLI / CI)npx intlayer content test⚠️ i18next-parser / сторонние утилиты
    Перевод с помощью ИИ✅ Встроен, использует ваши собственные API-ключи❌ Нет (Locize - отдельный платный сервис)
    Визуальный редактор / CMS✅ Бесплатный Visual Editor + опциональная CMS❌ Нет (Locize / внешние системы)
    Сервер MCP и навыки агентов (Agent Skills)✅ Да❌ Нет
    Экосистема и сообщество⚠️ Моложе, но быстро развивается✅ Самое масштабное и проверенное временем

    Бенчмарк

    Что измерялось

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

    СтратегияОписаниеГде применяется
    staticВсе языки и страницы упакованы вместе (resources встроены в init())Быстрые прототипы, код от ИИ
    dynamicТолько активный язык загружается через бэкенд, но все пространства имен сразуБольшинство проектов
    scoped-staticОдно пространство имен на маршрут, все упакованы заранееРедко
    scoped-dynamicОдно пространство имен на маршрут + ленивая загрузка бэкенда. Только текущая страница и языкПроекты со строгим лимитом производительности

    У Intlayer нет варианта "scoped": компилятор изолирует контент для каждого компонента автоматически, поэтому строки static и dynamic уже оптимизированы.

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

    • Lib size: gzip-размер пустого компонента, импортирующего только библиотеку i18n. Постоянный вес runtime.
    • 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 и intlayer 9.5.1. Тестовое приложение намеренно компактно (несколько десятков строк на язык), поэтому проценты утечек отражают тенденцию: они увеличиваются с ростом контента, тогда как вес runtime остается неизменным.

    Результаты на Next.js (next-i18next)

    БиблиотекаСтратегияLib size (gz)Page JS avg (gz)Locale leakPage leakComponent avg (gz)E2E-реактивностьГидратация
    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
    next-intlayerstatic5.5 KB141.3 KB0.0%0.0%8.5 KB15.5 ms16.9 ms
    next-intlayerdynamic5.5 KB141.3 KB0.0%0.0%6.9 KB15.3 ms15.9 ms
    @intlayer/next-i18next (compat)static9.4 KB150.7 KB0.0%0.0%9.7 KB10.7 ms11.3 ms
    @intlayer/next-i18next (compat)dynamic9.4 KB150.7 KB0.0%0.0%9.7 KB11.9 ms10.6 ms

    Как читать результаты

    • Вес runtime. Ядро i18next вместе с react-i18next - самый объемный runtime в тесте: 19.7 KB gzip для пустого компонента против 5.5 KB у next-intlayer.
    • Базовая конфигурация обходится дорого. Встраивание resources в init() дает 218.5 KB на страницу, что на +77.5 KB больше базового приложения. Каждая страница загружает все пространства имен.
    • Оптимизация требует значительных усилий. Подключение бэкенда (dynamic) экономит 49 KB, но по-прежнему оставляет 90% утечки строк других страниц, причем в этой конфигурации половина строк принадлежит не тому языку. Добавление разделения пространств имен по маршрутам (scoped-dynamic) позволяет достичь 0% утечки при весе 163.4 KB, что все равно на +22.4 KB на страницу больше, чем у Intlayer (141.3 KB), не требовавшего никакой ручной настройки.
    • Размер компонентов. Компонент с вызовом useTranslation() компилируется в 26-79 KB; тот же компонент с useIntlayer() весит 6.9 KB.
    • Гидратация возрастает до 27.7 ms в варианте dynamic: экземпляр i18next инициализируется и запрашивает бэкенд на стороне клиента до того, как React завершит гидратацию.

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

    То же приложение на TanStack Start со стандартным react-i18next, что исключает специфику Next.js из сравнения.

    БиблиотекаСтратегияLib size (gz)Page JS avg (gz)Locale leakPage leakComponent avg (gz)E2E-реактивностьГидратация
    base (без i18n)-0.0 KB111.0 KB0.0%0.0%0.7 KB8.1 ms21.6 ms
    react-i18nextstatic18.4 KB180.3 KB50.0%89.8%24.3 KB12.9 ms85.1 ms
    react-i18nextdynamic18.4 KB136.4 KB23.1%89.8%24.8 KB123.1 ms32.9 ms
    react-i18nextscoped-static18.4 KB184.2 KB50.7%89.8%25.3 KB185.1 ms25.2 ms
    react-i18nextscoped-dynamic18.4 KB127.2 KB0.0%0.0%26.7 KB17.6 ms11.3 ms
    intlayerstatic5.0 KB125.8 KB50.0%0.0%8.1 KB3.2 ms11.5 ms
    intlayerdynamic5.0 KB118.6 KB0.0%0.0%6.3 KB3.6 ms14.1 ms

    Как читать результаты

    • Неоптимизированное приложение на react-i18next отдает на +69 KB на страницу больше базы, а гидратация занимает 85 ms (в 4 раза дольше), поскольку дерево ресурсов полностью анализируется и регистрируется на клиенте до первичного рендера.
    • Смена языка подчеркивает задержку ленивой загрузки. Если ресурсы загружаются по запросу, смена языка влечет сетевой запрос до обновления html[lang]: 123 ms в dynamic, 185 ms в scoped-static. Intlayer обновляет DOM за 3-4 ms в обоих режимах: переключение выполняется мгновенно и не блокируется сетью.
    • Полностью оптимизированный вариант scoped-dynamic достигает 0% утечки при весе 127.2 KB, что все еще на +8.6 KB больше строки dynamic у Intlayer, при этом потребовав маппинга маршрутов к пространствам имен, бэкенда ресурсов и оберток Suspense.
    • Строка static у Intlayer уже дает 0% утечки страниц, так как в сборку входят только словари, импортированные компонентами этой страницы. Включение importMode: 'dynamic' устраняет и утечку языков.
    • Размер компонентов: 24-27 KB на компонент в react-i18next против 6-8 KB в Intlayer. useTranslation() связывает каждый компонент с глобальным экземпляром i18next.

    Причина различий: глобальный экземпляр против скомпилированных словарей

    i18next был создан в 2012 году как runtime: глобальный объект содержит хранилище ресурсов, плагины расширяют его возможности, а t() ищет ключи при рендере. Это обеспечивает гибкость (любой фреймворк, бэкенд и формат), но ведет к утяжелению:

    bash
    .
    ├── i18n.ts                      # createInstance().use(...).use(...).init({...})
    └── src
        ├── locales
       ├── en
       ├── common.json
       ├── home.json
       └── about.json
       └── fr
           ├── common.json
           ├── home.json
           └── about.json
        ├── components
       └── Counter.tsx          # useTranslation("about") + t("counter.label")
        └── app
            └── [locale]
                └── about
                    └── page.tsx     # должна знать, что нужны ["common", "about"]
    

    Экземпляр не может знать заранее, какие ключи понадобятся компоненту, поэтому держит в памяти все загруженные пространства имен. Оптимизация означает, что вы делите каталоги, вы перечисляете пространства имен для каждой страницы и вы вручную обновляете этот список при перемещении компонентов. Как отмечено в заметках бенчмарка: "поддерживать типобезопасность и точно знать, какое пространство имен подключить к странице - это сущий кошмар".

    Intlayer отказывается от глобального экземпляра. Контент объявляется рядом с компонентом, а компилятор строит граф зависимостей при сборке:

    bash
    .
    ├── intlayer.config.ts
    └── src
        ├── components
       └── Counter
           ├── index.tsx        # useIntlayer("counter")
           └── index.content.ts
        └── app
            └── [locale]
                └── about
                    ├── page.tsx
                    └── page.content.ts
    

    @intlayer/swc / @intlayer/babel видит, какой компонент импортирует конкретный словарь, упаковывает только его, исключительно для активной локали, и удаляет неиспользуемое. Модель "scoped-dynamic" становится прямым следствием сборки, а не обременительным регламентом команды.

    Чтобы повторить показатели строки dynamic, укажите dictionary.importMode: 'dynamic' в intlayer.config.ts. Подробнее читайте в документации по оптимизации bundle.

    Опыт разработчика

    Настройка

    next-i18next (App Router)

    src/app/i18n/server.ts
    import { createInstance } from "i18next";
    import { initReactI18next } from "react-i18next/initReactI18next";
    import resourcesToBackend from "i18next-resources-to-backend";
    import { defaultLocale } from "@/i18n.config";
    
    const backend = resourcesToBackend(
      (locale: string, namespace: string) =>
        import(`../../locales/${locale}/${namespace}.json`)
    );
    
    export const initI18next = async (
      locale: string,
      namespaces: string[] = ["common"]
    ) => {
      const i18n = createInstance();
      await i18n
        .use(initReactI18next)
        .use(backend)
        .init({
          lng: locale,
          fallbackLng: defaultLocale,
          ns: namespaces,
          defaultNS: "common",
          interpolation: { escapeValue: false },
          react: { useSuspense: false },
        });
      return i18n;
    };
    

    Дополнительно требуется клиентский I18nProvider, воссоздающий экземпляр с теми же параметрами, generateStaticParams и список namespaces на каждой странице.

    Intlayer

    intlayer.config.ts
    import { type IntlayerConfig, Locales } from "intlayer";
    
    const config: IntlayerConfig = {
      internationalization: {
        locales: [Locales.ENGLISH, Locales.FRENCH],
        defaultLocale: Locales.ENGLISH,
      },
    };
    
    export default config;
    
    src/app/[locale]/layout.tsx
    import { getHTMLTextDir } from "intlayer";
    import { IntlayerClientProvider, type NextLayoutIntlayer } from "next-intlayer";
    
    const LocaleLayout: NextLayoutIntlayer = async ({ children, params }) => {
      const { locale } = await params;
    
      return (
        <html lang={locale} dir={getHTMLTextDir(locale)}>
          <body>
            <IntlayerClientProvider locale={locale}>
              {children}
            </IntlayerClientProvider>
          </body>
        </html>
      );
    };
    
    export default LocaleLayout;
    

    Клиентский компонент

    react-i18next

    src/locales/en/about.json
    {
      "counter": {
        "label": "Counter",
        "increment": "Increment"
      }
    }
    
    src/components/Counter.tsx
    "use client";
    
    import { useState } from "react";
    import { useTranslation } from "react-i18next";
    
    export const Counter = () => {
      const { t, i18n } = useTranslation("about");
      const [count, setCount] = useState(0);
      const numberFormat = new Intl.NumberFormat(i18n.language);
    
      return (
        <div>
          <p>{numberFormat.format(count)}</p>
          <button
            aria-label={t("counter.label")}
            onClick={() => setCount((c) => c + 1)}
          >
            {t("counter.increment")}
          </button>
        </div>
      );
    };
    
    Страница, отображающая этот компонент, обязана загрузить пространство имен about, а t("counter.label") будет обычной строкой без строгой типизации, если не дополнить интерфейс CustomTypeOptions.

    Intlayer

    src/components/Counter/index.content.ts
    import { t, type Dictionary } from "intlayer";
    
    const counterContent = {
      key: "counter",
      content: {
        label: t({ en: "Counter", fr: "Compteur" }),
        increment: t({ en: "Increment", fr: "Incrémenter" }),
      },
    } satisfies Dictionary;
    
    export default counterContent;
    
    src/components/Counter/index.tsx
    "use client";
    
    import { useState } from "react";
    import { useIntlayer } from "next-intlayer";
    import { useNumber } from "next-intlayer/format";
    
    export const Counter = () => {
      const { label, increment } = useIntlayer("counter");
      const number = useNumber();
      const [count, setCount] = useState(0);
    
      return (
        <div>
          <p>{number(count)}</p>
          <button aria-label={label} onClick={() => setCount((c) => c + 1)}>
            {increment}
          </button>
        </div>
      );
    };
    

    label и increment строго типизированы: опечатка в имени вызовет ошибку TypeScript, а отсутствие французского текста остановит сборку.

    Синхронный серверный компонент

    next-i18next

    src/components/ServerCounter.tsx
    type ServerCounterProps = {
      t: (key: string) => string;
      locale: string;
      count: number;
    };
    
    export const ServerCounter = ({ t, locale, count }: ServerCounterProps) => (
      <div>
        <p>{new Intl.NumberFormat(locale).format(count)}</p>
        <button aria-label={t("counter.label")}>{t("counter.increment")}</button>
      </div>
    );
    

    Страница вызывает i18n.getFixedT(locale, "about") и пробрасывает t и locale вниз через props.

    Intlayer

    src/components/ServerCounter.tsx
    import { useIntlayer } from "next-intlayer/server";
    import { useNumber } from "next-intlayer/server/format";
    
    export const ServerCounter = ({ count }: { count: number }) => {
      const { label, increment } = useIntlayer("counter");
      const number = useNumber();
    
      return (
        <div>
          <p>{number(count)}</p>
          <button aria-label={label}>{increment}</button>
        </div>
      );
    };
    

    Сохраните API i18next, получите производительность Intlayer

    Вам не нужно переписывать компоненты, чтобы получить показатели из бенчмарка выше. @intlayer/i18next, @intlayer/react-i18next и @intlayer/next-i18next являются готовыми адаптерами: useTranslation, t(), <Trans>, {{interpolation}}, формы множественного числа _one / _other, суффиксы контекста и returnObjects продолжают работать, получая данные из скомпилированных словарей Intlayer.

    next.config.ts
    import type { NextConfig } from "next";
    import { createNextI18nPlugin } from "@intlayer/next-i18next/plugin";
    
    const withIntlayer = createNextI18nPlugin();
    
    const nextConfig: NextConfig = {};
    
    export default withIntlayer(nextConfig);
    
    vite.config.ts
    import { defineConfig } from "vite";
    import { reactI18nextVitePlugin } from "@intlayer/react-i18next/plugin";
    
    export default defineConfig({
      plugins: [reactI18nextVitePlugin()],
    });
    

    В бенчмарке адаптерная версия того же приложения Next.js уменьшилась с 218.5 KB до 150.7 KB на страницу, с 78.5 KB до 9.7 KB на компонент, утечка упала с ~90% до 0%, а гидратация ускорилась с 15.6 ms до 11.3 ms при полностью неизменном коде компонентов. Ваши существующие файлы locales/{lng}/{ns}.json могут оставаться источником данных через плагин синхронизации JSON.

    Смотрите руководства по миграции: i18next, react-i18next, next-i18next.

    Что и когда выбирать?

    • Выбирайте i18next, если вам необходима его экосистема плагинов (детекторы, бэкенды, ICU, Locize), вы локализуете решения вне React (сервисы Node, чистый JS, другие фреймворки), у команды уже есть практический опыт или переводческая платформа ожидает формат locales/{lng}/{ns}.json. Заложите время на организацию пространств имен, настройку бэкенда и ручную поддержку карты маршрутов, если важна скорость.
    • Выбирайте Intlayer, если вам нужны контент внутри компонентов, строгий TypeScript, ошибки сборки при нехватке строк, автоматический tree-shaking и lazy loading, мгновенное переключение языка, синхронные серверные компоненты и встроенные редакционные инструменты (Visual Editor, CMS, перевод через ИИ, сервер MCP). Идеально подходит для модульных кодовых баз и дизайн-систем.
    • Выбирайте адаптеры @intlayer/*-i18next, если вы уже используете i18next и хотите получить экономию в размере бандла и прирост скорости без масштабного рефакторинга.

    Похожие сравнения

    Звезды на GitHub

    Звезды на GitHub наглядно демонстрируют популярность проекта, доверие разработчиков и перспективы его развития. Хотя они не являются прямой оценкой качества кода, они показывают заинтересованность сообщества и готовность к внедрению.

    График динамики звезд

    Заключение

    i18next по праву занял лидирующие позиции: он поддерживается на любых платформах, располагает плагинами под любые сценарии и развивается более десяти лет. Бенчмарк наглядно раскрывает цену архитектуры, завязанной на runtime. Типичная сборка большинства команд добавляет +70-77 KB gzip на страницу, передает ~90% контента с других страниц, а смена языка с ленивой загрузкой занимает свыше 100 ms. Добиться 0% утечки возможно, однако это требует бэкенда, ручной разбивки на namespaces по маршрутам, и все равно оставляет вес на +9-22 KB больше, чем у Intlayer.

    Intlayer переносит всю эту нагрузку на компилятор. Словари для компонентов, ленивая подгрузка по языкам и удаление неиспользуемых текстов являются прямым результатом сборки. На том же приложении: +0.3 KB на страницу, 0% утечек, компоненты в 3-10 раз меньше, а язык переключается за 3-4 ms.

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

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

    Комментарии

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

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

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