Создание собственных плагинов

i18next построен как расширяемая система интернационализации, в которой значительная часть функциональности выносится за пределы ядра. Такой подход позволяет подключать дополнительные возможности без изменения базовой логики: обработчики загрузки ресурсов, постобработку переводов, интеграцию с фреймворками, кэширование и динамическое определение языка. Механизм плагинов формирует слой расширения, работающий через жизненный цикл экземпляра i18next и набор стандартных хуков.

Основной принцип расширения заключается в том, что любой плагин представляет собой объект с методом type, определяющим этап интеграции, и методом init, который вызывается при подключении через use(). В зависимости от типа плагина он может внедряться в загрузчик ресурсов, обработчик языка, интерполяцию или логирование.


Архитектура плагинов строится вокруг метода:

i18next.use(plugin).init();

При вызове use() плагин регистрируется внутри внутреннего массива middleware. Во время инициализации init() происходит последовательное применение всех подключённых модулей.

Минимальная структура плагина:

const myPlugin = {
  type: 'backend',
  init: (services, options = {}, i18next) => {
    // инициализация логики
  }
};

Ключевое значение имеет поле type, которое определяет точку подключения:

  • backend — загрузка переводов
  • languageDetector — определение языка пользователя
  • postProcessor — обработка переведённых строк
  • logger — логирование
  • cache — кэширование

Жизненный цикл плагина

После регистрации плагин проходит несколько стадий:

  1. Регистрация через use()
  2. Вызов init() при запуске i18next
  3. Инъекция в сервисный слой
  4. Использование во время выполнения переводов

Объект services, передаваемый в init(), содержит внутренние компоненты:

  • resourceStore — хранилище переводов
  • languageUtils — утилиты языка
  • logger — система логирования
  • pluralResolver — обработка множественных форм

Backend-плагины

Backend-плагины отвечают за загрузку переводов из внешних источников: API, файловой системы, базы данных.

Контракт backend-плагина

const backend = {
  type: 'backend',

  read: (language, namespace, callback) => {
    // загрузка ресурса
  },

  init: (services, options = {}, i18next) => {},

  create: (languages, namespace, key, fallbackValue) => {}
};

Метод read

Основной механизм получения переводов:

read(language, namespace, callback) {
  fetch(`/locales/${language}/${namespace}.json`)
    .then(res => res.json())
    .then(data => callback(null, data))
    .catch(err => callback(err));
}

Функция callback принимает два параметра:

  • error — ошибка загрузки
  • data — объект переводов

Language Detector плагины

Плагины определения языка позволяют динамически вычислять язык пользователя на основе окружения.

Источники могут включать:

  • navigator.language
  • cookies
  • localStorage
  • query string
  • HTTP headers (на сервере)

Структура детектора

const languageDetector = {
  type: 'languageDetector',

  init: (services, detectorOptions, i18nextOptions) => {},

  detect: () => {
    return localStorage.getItem('lang') || 'en';
  },

  cacheUserLanguage: (lng) => {
    localStorage.setItem('lang', lng);
  }
};

Логика приоритета

Детектор может возвращать массив стратегий:

detect() {
  return [
    localStorage.getItem('lang'),
    navigator.language,
    'en'
  ];
}

Первое валидное значение используется как активный язык.


PostProcessor плагины

PostProcessor применяется после получения перевода, но до возврата результата пользователю.

Используется для:

  • форматирования текста
  • шифрования/дешифрования
  • динамической модификации строк
  • вставки специальных токенов

Интерфейс postProcessor

const postProcessor = {
  type: 'postProcessor',

  process: (value, key, options, translator) => {
    return value.toUpperCase();
  }
};

Порядок выполнения

PostProcessor может быть цепочечным:

i18next.init({
  postProcess: ['uppercase', 'trim']
});

Каждый процессор применяется последовательно.


Logger-плагины

Logger позволяет заменить стандартный механизм логирования.

const logger = {
  type: 'logger',

  log: (...args) => console.log(...args),
  warn: (...args) => console.warn(...args),
  error: (...args) => console.error(...args)
};

Используется для:

  • интеграции с Sentry
  • отправки логов на сервер
  • подавления предупреждений в production

Кэширующие плагины

Cache backend используется для ускорения загрузки переводов.

Основная идея — сохранить уже загруженные ресурсы.

const cache = {
  type: 'backend',

  read: (language, namespace, callback) => {
    const key = `${language}-${namespace}`;
    const cached = localStorage.getItem(key);

    if (cached) {
      return callback(null, JSON.parse(cached));
    }

    callback(null, null);
  },

  create: (languages, namespace, key, fallbackValue) => {
    const storageKey = `${languages}-${namespace}`;
    localStorage.setItem(storageKey, JSON.stringify(fallbackValue));
  }
};

Взаимодействие плагинов между собой

Плагины не изолированы и могут взаимодействовать через общий сервисный слой.

Пример цепочки:

  1. LanguageDetector определяет язык
  2. Backend загружает переводы
  3. Cache проверяет наличие данных
  4. PostProcessor модифицирует результат
  5. Logger фиксирует события

Порядок подключения влияет на поведение системы:

i18next
  .use(languageDetector)
  .use(cache)
  .use(backend)
  .use(postProcessor)
  .init();

Расширение через собственные плагины

Создание собственного плагина требует соблюдения контрактов типов.

Плагин трансформации ключей

const keyTransformer = {
  type: 'postProcessor',

  process: (value, key) => {
    if (key.startsWith('upper:')) {
      return value.toUpperCase();
    }
    return value;
  }
};

i18next.use(keyTransformer);

Асинхронные плагины

Backend и detector часто работают асинхронно.

Особенности:

  • обязательное использование callback
  • поддержка Promise (в некоторых реализациях)
  • контроль таймаутов загрузки
read(language, namespace, callback) {
  setTimeout(() => {
    callback(null, { hello: 'world' });
  }, 1000);
}

Ошибки проектирования плагинов

Частые проблемы:

  • нарушение контракта type
  • отсутствие init
  • блокирующие операции в detect
  • отсутствие обработки ошибок в read
  • мутация внутренних сервисов i18next

Некорректный плагин может нарушить цепочку интернационализации и привести к частичной потере переводов.


Совместимость и версия API

Плагины привязаны к версии ядра. Изменения могут затрагивать:

  • сигнатуры init
  • структуру services
  • формат backend callback
  • поведение postProcessor chain

Поэтому плагины часто сопровождаются версионированием и проверкой совместимости через feature detection.


Интеграция с фреймворками через плагины

Плагины могут адаптировать i18next под:

  • React
  • Vue
  • Angular
  • Node.js SSR

Механизм остаётся одинаковым: внедрение через use() и проксирование переводчика в контекст фреймворка.


Композиция плагинов

Система допускает одновременное использование нескольких расширений одного типа.

Пример:

i18next
  .use(detectorA)
  .use(detectorB)
  .use(backendA)
  .use(backendB);

При этом порядок регистрации определяет приоритет обработки.


Внутренняя модель расширяемости

В основе лежит паттерн middleware. Каждый плагин расширяет поведение экземпляра i18next, не изменяя его исходный код. Все расширения работают через единый объект i18next.services, обеспечивая согласованность состояния и централизованный контроль жизненного цикла интернационализации.