Настройка типов для i18next

TypeScript-типизация в связке с i18next строится вокруг расширения модулей, описания структуры ресурсов перевода и корректной настройки generics, которые позволяют получить автодополнение ключей и строгую проверку доступных переводов на этапе компиляции. Основная ценность типизации заключается в том, что строки ключей перестают быть «магическими значениями» и превращаются в часть контрактов приложения.


i18next изначально поставляется с собственными TypeScript-типами, но их поведение по умолчанию максимально общее. Без дополнительной настройки ключи переводов воспринимаются как string, а структура ресурсов не проверяется.

Установка основных пакетов:

npm install i18next
npm install -D typescript @types/node

В большинстве современных версий отдельные @types/i18next не требуются, поскольку типы уже встроены в библиотеку.


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

Типизация начинается с описания структуры словаря. На практике чаще всего используется формат resources, где языки и namespaces определяются явно.

export const resources = {
  en: {
    common: {
      welcome: "Welcome",
      logout: "Logout",
    },
    auth: {
      login: "Login",
      register: "Register",
    },
  },
  ru: {
    common: {
      welcome: "Добро пожаловать",
      logout: "Выйти",
    },
    auth: {
      login: "Войти",
      register: "Регистрация",
    },
  },
} as const;

Ключевой момент заключается в использовании as const. Это превращает структуру в неизменяемый литеральный тип, позволяющий TypeScript вывести точные строки ключей вместо обобщённых string.


Выведение типов ресурсов

На основе структуры ресурсов можно получить типы языков, namespaces и ключей.

type Resources = typeof resources;

type AppLanguages = keyof Resources; 
// "en" | "ru"

Namespaces извлекаются аналогично:

type Namespace = keyof Resources["en"];
// "common" | "auth"

Доступ к ключам внутри namespace:

type CommonKeys = keyof Resources["en"]["common"];
// "welcome" | "logout"

Эта структура становится фундаментом для строгой типизации i18next-инстанса.


Расширение типов i18next через module augmentation

Основной механизм интеграции типов заключается в расширении интерфейсов библиотеки через декларативное расширение модулей.

Создаётся файл деклараций, например:

i18next.d.ts

Содержимое:

import "i18next";
import { resources } from "./resources";

declare module "i18next" {
  interface CustomTypeOptions {
    defaultNS: "common";
    resources: typeof resources["en"];
  }
}

Здесь происходит ключевое связывание:

  • defaultNS определяет namespace по умолчанию
  • resources фиксирует структуру переводов для одного языка (обычно базового)

TypeScript начинает использовать эти данные для проверки ключей в t().


Типизация функции t()

После настройки module augmentation функция t приобретает строгую сигнатуру.

Пример использования:

import i18next from "i18next";

i18next.t("welcome");

Если ключ существует в defaultNS, TypeScript его распознаёт. Однако для мульти-namespace структуры необходимо явно указывать пространство имён.

i18next.t("auth:login");

Типизация теперь предотвращает:

  • использование несуществующих ключей
  • опечатки в namespace
  • попытки обращения к неописанным структурам

Типизация namespace через createInstance

При создании экземпляра i18next типы могут быть усилены через generics.

import i18next from "i18next";
import { resources } from "./resources";

const instance = i18next.createInstance();

instance.init({
  resources,
  lng: "en",
  defaultNS: "common",
});

Однако без дополнительных расширений TypeScript не всегда связывает resources с типами напрямую. Для усиления строгости используется явное указание типов:

instance.init<typeof resources.en>({
  resources,
  lng: "en",
});

Типизация через интерфейс ресурсов

Более строгий подход заключается в создании собственного интерфейса ресурсов, который описывает всю структуру переводов.

interface AppResources {
  common: {
    welcome: string;
    logout: string;
  };
  auth: {
    login: string;
    register: string;
  };
}

После этого i18next можно связать через module augmentation:

declare module "i18next" {
  interface CustomTypeOptions {
    resources: AppResources;
  }
}

Такой подход избавляет от зависимости от конкретного объекта resources и делает типы стабильными.


Типизация ключей через шаблонные литералы

i18next поддерживает строковые ключи формата:

namespace:key

TypeScript может описывать такие конструкции через template literal types:

type Namespace = "common" | "auth";
type CommonKeys = "welcome" | "logout";
type AuthKeys = "login" | "register";

type Keys =
  | `common:${CommonKeys}`
  | `auth:${AuthKeys}`;

Далее можно ограничить функцию:

function t(key: Keys): string {
  return i18next.t(key);
}

Такой подход особенно полезен при создании собственных обёрток над i18next.


Типизация параметров интерполяции

i18next активно использует интерполяцию:

t("welcome_user", { name: "Alex" });

Для строгой типизации параметров создаются интерфейсы:

interface AuthParams {
  welcome_user: {
    name: string;
  };
}

Далее можно описать вспомогательную обёртку:

function typedT<K extends keyof AuthParams>(
  key: K,
  params: AuthParams[K]
) {
  return i18next.t(key, params);
}

Это исключает передачу лишних или неправильных параметров.


Глобальное расширение типов через декларации

Для крупных проектов типизация часто централизуется в одном месте:

declare module "i18next" {
  interface CustomTypeOptions {
    resources: {
      common: {
        welcome: string;
      };
    };
  }
}

Такой подход снижает гибкость, но повышает строгость.


Интеграция с React и типами (i18next + react-i18next)

Хотя основная логика остаётся в i18next, при использовании react-обвязки добавляется типизация хуков:

import { useTranslation } from "react-i18next";

const { t } = useTranslation();

Без дополнительной настройки t остаётся слаботипизированной функцией. Для улучшения используются те же CustomTypeOptions.


Типизация fallbackLng и языков

Типизация языков помогает избежать ошибок конфигурации:

type Languages = "en" | "ru";

interface Config {
  lng: Languages;
  fallbackLng: Languages;
}

Это предотвращает случайное добавление неподдерживаемых локалей.


Строгая типизация namespace-aware функций

При расширенной архитектуре можно создавать собственные типизированные функции:

type Namespace = "common" | "auth";

function tn<N extends Namespace, K extends string>(
  ns: N,
  key: K
) {
  return i18next.t(`${ns}:${key}`);
}

Более продвинутый вариант связывает ключи с namespace:

type TranslationMap = {
  common: "welcome" | "logout";
  auth: "login" | "register";
};

function tns<N extends keyof TranslationMap>(
  ns: N,
  key: TranslationMap[N]
) {
  return i18next.t(`${ns}:${key}`);
}

Типизация и строгий режим компилятора

TypeScript в строгом режиме (strict: true) усиливает эффект от типизации i18next:

  • исключается неявный any
  • повышается чувствительность к отсутствующим ключам
  • усиливается контроль generics

Особенно важны флаги:

{
  "strict": true,
  "noImplicitAny": true,
  "strictNullChecks": true
}

Организация типовых файлов в проекте

Типизация i18next обычно выносится в отдельную структуру:

src/
  i18n/
    resources.ts
    types.ts
    index.ts
  i18next.d.ts

Разделение позволяет:

  • централизовать описание переводов
  • отделить runtime-инициализацию от типов
  • избежать циклических зависимостей

Ограничения типизации i18next

Несмотря на мощь TypeScript, существуют ограничения:

  • динамически загружаемые namespaces сложно типизировать полностью
  • JSON-ресурсы без as const теряют точность типов
  • сложные fallback-цепочки не всегда отражаются в типах
  • runtime-расширение переводов не синхронизируется с компилятором

Эти ограничения компенсируются частичной типизацией и строгими контрактами в кодовой базе.


Типизация динамических ключей

Для случаев, когда ключи формируются динамически:

function getKey(section: string, key: string) {
  return i18next.t(`${section}:${key}`);
}

TypeScript в таком случае теряет строгость. Частичное решение:

type Section = "common" | "auth";
type Keys = {
  common: "welcome" | "logout";
  auth: "login" | "register";
};

function t<S extends Section>(
  section: S,
  key: Keys[S]
) {
  return i18next.t(`${section}:${key}`);
}

Вывод архитектуры типизации

Типизация i18next в TypeScript строится вокруг трёх уровней:

  • описание структуры ресурсов через as const или интерфейсы
  • расширение типов библиотеки через module augmentation
  • создание строгих обёрток над t() для критичных мест

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