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.d.ts
Содержимое:
import "i18next";
import { resources } from "./resources";
declare module "i18next" {
interface CustomTypeOptions {
defaultNS: "common";
resources: typeof resources["en"];
}
}
Здесь происходит ключевое связывание:
defaultNS определяет namespace по умолчаниюresources фиксирует структуру переводов для одного
языка (обычно базового)TypeScript начинает использовать эти данные для проверки ключей в
t().
После настройки module augmentation функция t
приобретает строгую сигнатуру.
Пример использования:
import i18next from "i18next";
i18next.t("welcome");
Если ключ существует в defaultNS, TypeScript его
распознаёт. Однако для мульти-namespace структуры необходимо явно
указывать пространство имён.
i18next.t("auth:login");
Типизация теперь предотвращает:
При создании экземпляра 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;
};
};
}
}
Такой подход снижает гибкость, но повышает строгость.
Хотя основная логика остаётся в i18next, при использовании react-обвязки добавляется типизация хуков:
import { useTranslation } from "react-i18next";
const { t } = useTranslation();
Без дополнительной настройки t остаётся
слаботипизированной функцией. Для улучшения используются те же
CustomTypeOptions.
Типизация языков помогает избежать ошибок конфигурации:
type Languages = "en" | "ru";
interface Config {
lng: Languages;
fallbackLng: Languages;
}
Это предотвращает случайное добавление неподдерживаемых локалей.
При расширенной архитектуре можно создавать собственные типизированные функции:
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Особенно важны флаги:
{
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true
}
Типизация i18next обычно выносится в отдельную структуру:
src/
i18n/
resources.ts
types.ts
index.ts
i18next.d.ts
Разделение позволяет:
Несмотря на мощь TypeScript, существуют ограничения:
as const теряют точность типовЭти ограничения компенсируются частичной типизацией и строгими контрактами в кодовой базе.
Для случаев, когда ключи формируются динамически:
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 или
интерфейсыt() для критичных
местТакая комбинация превращает систему переводов в формально проверяемую часть приложения, где строки перестают быть источником ошибок времени выполнения и переходят в область статически анализируемого кода.