Ключевые возможности библиотеки

Библиотека i18next предназначена для интернационализации и локализации приложений на JavaScript. Она предоставляет гибкую систему перевода интерфейсов, поддержку нескольких языков, динамическую загрузку переводов, работу с форматированием строк, множественными формами, контекстами и интеграцию с популярными фреймворками.

Архитектура библиотеки построена вокруг нескольких ключевых компонентов:

  • менеджер языков;
  • хранилище ресурсов переводов;
  • механизм интерполяции;
  • система fallback-языков;
  • плагины загрузки;
  • интеграции с UI-фреймворками;
  • форматирование значений.

Инициализация библиотеки

Базовая инициализация выглядит следующим образом:

import i18next from 'i18next';

i18next.init({
  lng: 'ru',
  fallbackLng: 'en',

  resources: {
    ru: {
      translation: {
        welcome: 'Добро пожаловать'
      }
    },
    en: {
      translation: {
        welcome: 'Welcome'
      }
    }
  }
});

Получение перевода выполняется через функцию t():

console.log(i18next.t('welcome'));

Система ресурсов переводов

Переводы в I18next организуются в виде объектов ресурсов.

Структура ресурсов:

{
  язык: {
    namespace: {
      ключ: значение
    }
  }
}

Пример:

resources: {
  ru: {
    common: {
      save: 'Сохранить',
      cancel: 'Отмена'
    }
  },
  en: {
    common: {
      save: 'Save',
      cancel: 'Cancel'
    }
  }
}

Использование namespace:

i18next.init({
  ns: ['common'],
  defaultNS: 'common'
});

i18next.t('save');

Либо:

i18next.t('common:save');

Пространства имён (Namespaces)

Namespaces позволяют разделять переводы по модулям приложения.

Типичная структура:

locales/
├── en/
│   ├── common.json
│   ├── auth.json
│   └── dashboard.json
└── ru/
    ├── common.json
    ├── auth.json
    └── dashboard.json

Пример загрузки:

i18next.init({
  ns: ['common', 'auth'],
  defaultNS: 'common'
});

Использование:

i18next.t('auth:login');

Преимущества namespace:

  • уменьшение размера загружаемых файлов;
  • модульность;
  • разделение ответственности;
  • удобство командной разработки;
  • ленивые загрузки.

Fallback-языки

Fallback используется, если перевод отсутствует.

Настройка:

i18next.init({
  lng: 'fr',
  fallbackLng: 'en'
});

Если во французском языке ключ отсутствует, библиотека возьмёт значение из английского.

Поддерживаются массивы fallback-языков:

fallbackLng: ['en', 'ru']

Можно задавать fallback по регионам:

fallbackLng: {
  'de-CH': ['fr', 'it'],
  default: ['en']
}

Вложенные ключи

I18next поддерживает древовидную структуру ключей.

Пример:

resources: {
  ru: {
    translation: {
      user: {
        profile: {
          title: 'Профиль'
        }
      }
    }
  }
}

Использование:

i18next.t('user.profile.title');

Это особенно полезно для больших приложений.


Интерполяция значений

Интерполяция позволяет вставлять динамические данные в строки перевода.

Пример:

resources: {
  ru: {
    translation: {
      greeting: 'Привет, {{name}}'
    }
  }
}

Использование:

i18next.t('greeting', {
  name: 'Алексей'
});

Результат:

Привет, Алексей

Несколько параметров:

welcome: 'Пользователь {{name}} имеет {{count}} сообщений'
i18next.t('welcome', {
  name: 'Иван',
  count: 10
});

Экранирование HTML

По умолчанию I18next экранирует HTML для защиты от XSS.

interpolation: {
  escapeValue: true
}

Пример:

message: 'Привет {{name}}'
i18next.t('message', {
  name: '<script>alert(1)</script>'
});

Результат будет безопасным.

Отключение экранирования:

interpolation: {
  escapeValue: false
}

Использовать такую настройку необходимо осторожно.


Множественные формы (Pluralization)

Библиотека поддерживает правила множественного числа для разных языков.

Пример для английского:

resources: {
  en: {
    translation: {
      item_one: '{{count}} item',
      item_other: '{{count}} items'
    }
  }
}

Использование:

i18next.t('item', { count: 1 });
i18next.t('item', { count: 5 });

Для русского языка формы сложнее:

resources: {
  ru: {
    translation: {
      item_one: '{{count}} товар',
      item_few: '{{count}} товара',
      item_many: '{{count}} товаров',
      item_other: '{{count}} товара'
    }
  }
}

Примеры:

i18next.t('item', { count: 1 });
i18next.t('item', { count: 2 });
i18next.t('item', { count: 5 });

I18next автоматически выбирает нужную форму.


Контексты переводов

Контексты используются для выбора перевода в зависимости от ситуации.

Пример:

resources: {
  ru: {
    translation: {
      friend_male: 'Друг',
      friend_female: 'Подруга'
    }
  }
}

Использование:

i18next.t('friend', {
  context: 'male'
});

И:

i18next.t('friend', {
  context: 'female'
});

Контексты можно комбинировать с множественными формами.


Форматирование значений

I18next поддерживает форматирование через Intl.

Пример:

interpolation: {
  format(value, format, lng) {
    if (format === 'currency') {
      return new Intl.NumberFormat(lng, {
        style: 'currency',
        currency: 'USD'
      }).format(value);
    }

    return value;
  }
}

Использование:

price: 'Цена: {{value, currency}}'
i18next.t('price', {
  value: 1500
});

Работа с датами

Форматирование дат:

interpolation: {
  format(value, format, lng) {
    if (value instanceof Date) {
      return new Intl.DateTimeFormat(lng).format(value);
    }

    return value;
  }
}

Использование:

today: 'Сегодня {{date}}'
i18next.t('today', {
  date: new Date()
});

Динамическое переключение языка

Язык можно изменять во время работы приложения.

i18next.changeLanguage('en');

Пример:

button.addEventListener('click', () => {
  i18next.changeLanguage('ru');
});

После смены языка интерфейс может быть автоматически обновлён через интеграции с фреймворками.


Определение языка пользователя

Для автоматического определения языка используется плагин i18next-browser-languagedetector.

Пример:

import LanguageDetector from 'i18next-browser-languagedetector';

i18next
  .use(LanguageDetector)
  .init({
    fallbackLng: 'en'
  });

Источники определения:

  • navigator.language;
  • cookies;
  • localStorage;
  • URL;
  • HTML-атрибуты;
  • query parameters.

Асинхронная загрузка переводов

Для загрузки переводов по сети используется i18next-http-backend.

Установка backend:

import Backend from 'i18next-http-backend';

i18next
  .use(Backend)
  .init({
    backend: {
      loadPath: '/locales/{{lng}}/{{ns}}.json'
    }
  });

Файл перевода:

/locales/ru/common.json

Содержимое:

{
  "hello": "Привет"
}

Ленивая загрузка namespace

Загрузка переводов только при необходимости:

i18next.loadNamespaces('dashboard');

Такой подход уменьшает стартовый размер приложения.


Кэширование переводов

I18next поддерживает кэширование через localStorage.

Пример:

import ChainedBackend from 'i18next-chained-backend';
import LocalStorageBackend from 'i18next-localstorage-backend';
import HttpBackend from 'i18next-http-backend';

i18next
  .use(ChainedBackend)
  .init({
    backend: {
      backends: [
        LocalStorageBackend,
        HttpBackend
      ],

      backendOptions: [
        {
          expirationTime: 7 * 24 * 60 * 60 * 1000
        },
        {
          loadPath: '/locales/{{lng}}/{{ns}}.json'
        }
      ]
    }
  });

React-интеграция

Для React используется библиотека react-i18next.

Инициализация:

import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';

i18n
  .use(initReactI18next)
  .init({
    lng: 'ru'
  });

Использование хука:

import { useTranslation } from 'react-i18next';

function App() {
  const { t } = useTranslation();

  return <h1>{t('welcome')}</h1>;
}

Suspense и асинхронные переводы

React-интеграция поддерживает Suspense.

<Suspense fallback="Loading...">
  <App />
</Suspense>

Это позволяет ожидать загрузку переводов до отображения интерфейса.


Компонент Trans

Компонент Trans позволяет использовать HTML и React-компоненты внутри переводов.

Пример перевода:

{
  "description": "Перейдите <1>по ссылке</1>"
}

Использование:

<Trans i18nKey="description">
  Перейдите <a href="/home">по ссылке</a>
</Trans>

Формат ICU

Через плагин ICU библиотека получает расширенные возможности форматирования.

Пример:

import ICU from 'i18next-icu';

i18next.use(ICU).init();

Перевод:

{
  "message": "{count, plural, one {# сообщение} few {# сообщения} many {# сообщений}}"
}

Постобработка переводов

I18next поддерживает post processors.

Пример:

i18next.use({
  type: 'postProcessor',

  name: 'uppercase',

  process(value) {
    return value.toUpperCase();
  }
});

Использование:

i18next.t('hello', {
  postProcess: 'uppercase'
});

Работа с fallback-ключами

Можно указывать запасные ключи:

i18next.t(['error.404', 'error.unspecific']);

Если первый ключ отсутствует, используется второй.


Проверка существования ключей

Метод exists():

i18next.exists('profile.title');

Пример:

if (i18next.exists('new.feature')) {
  renderFeature();
}

Возврат объектов

I18next умеет возвращать целые объекты.

Пример:

resources: {
  ru: {
    translation: {
      menu: {
        home: 'Главная',
        about: 'О нас'
      }
    }
  }
}

Использование:

i18next.t('menu', {
  returnObjects: true
});

Массивы переводов

Пример:

resources: {
  ru: {
    translation: {
      errors: [
        'Ошибка сети',
        'Ошибка сервера'
      ]
    }
  }
}

Получение массива:

i18next.t('errors', {
  returnObjects: true
});

События библиотеки

I18next предоставляет систему событий.

Пример:

i18next.on('languageChanged', (lng) => {
  console.log('Язык изменён:', lng);
});

Другие события:

  • initialized;
  • loaded;
  • failedLoading;
  • missingKey.

Обработка отсутствующих переводов

Настройка:

saveMissing: true

Пример:

i18next.init({
  saveMissing: true
});

Можно отправлять отсутствующие ключи на сервер.


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

Пример backend:

const Backend = {
  type: 'backend',

  read(language, namespace, callback) {
    fetch(`/api/translations/${language}/${namespace}`)
      .then((response) => response.json())
      .then((data) => callback(null, data))
      .catch((error) => callback(error, false));
  }
};

Подключение:

i18next.use(Backend);

Middleware для Node.js

Для серверных приложений существует middleware.

Пример с Express:

import middleware from 'i18next-http-middleware';

app.use(middleware.handle(i18next));

Получение перевода:

req.t('welcome');

SSR и серверный рендеринг

I18next поддерживает SSR.

Основные задачи SSR:

  • определение языка до рендера;
  • загрузка namespace;
  • сериализация переводов;
  • синхронизация клиента и сервера.

Для React часто используется связка:

  • React;
  • react-i18next;
  • Next.js.

Интеграция с Next.js

Для Next.js применяется библиотека next-i18next.

Пример конфигурации:

module.exports = {
  i18n: {
    defaultLocale: 'en',
    locales: ['en', 'ru']
  }
};

Использование:

import { useTranslation } from 'next-i18next';

const { t } = useTranslation('common');

Поддержка TypeScript

I18next предоставляет типизацию.

Пример:

import i18next from 'i18next';

i18next.t('welcome');

Расширение типов:

declare module 'i18next' {
  interface CustomTypeOptions {
    defaultNS: 'common';

    resources: {
      common: {
        welcome: string;
      };
    };
  }
}

Преимущества:

  • автодополнение;
  • проверка ключей;
  • снижение количества ошибок;
  • безопасный рефакторинг.

Производительность

Основные методы оптимизации:

  • разделение namespace;
  • lazy loading;
  • кэширование;
  • минимизация JSON-файлов;
  • предварительная загрузка популярных языков;
  • CDN для переводов.

Организация структуры переводов

Рекомендуемая структура:

src/
├── locales/
│   ├── en/
│   │   ├── common.json
│   │   ├── auth.json
│   │   └── dashboard.json
│   └── ru/
│       ├── common.json
│       ├── auth.json
│       └── dashboard.json

Рекомендации:

  • использовать осмысленные ключи;
  • избегать дублирования;
  • хранить UI-переводы отдельно от бизнес-текстов;
  • поддерживать одинаковую структуру языков;
  • избегать автоматической генерации нечитабельных ключей.

Типичные проблемы локализации

Жёстко закодированные строки

Плохо:

<button>Сохранить</button>

Хорошо:

<button>{t('save')}</button>

Склеивание строк

Плохо:

'Привет ' + name

Хорошо:

t('hello', { name })

Игнорирование множественных форм

Неправильно:

`${count} товаров`

Правильно:

t('items', { count })

Отсутствие namespace

Большие приложения без namespace быстро становятся неуправляемыми.


Преимущества I18next

Основные достоинства библиотеки:

  • поддержка десятков языков;
  • гибкая архитектура;
  • поддержка браузера и Node.js;
  • интеграции с React, Vue, Angular;
  • мощная система pluralization;
  • расширяемость через плагины;
  • поддержка SSR;
  • асинхронные загрузки;
  • высокая производительность;
  • поддержка ICU;
  • развитая экосистема;
  • хорошая типизация TypeScript.