Namespace в i18next представляет собой логическое разделение ресурсов перевода на изолированные области. Такая структура позволяет масштабировать мультиязычные приложения, разделяя ключи по функциональным зонам: общие строки интерфейса, страницы, компоненты, модули доменной логики. В контексте TypeScript ключевым аспектом становится строгая типизация этих пространств имён, обеспечивающая контроль над доступными ключами перевода на этапе компиляции.
В базовой конфигурации i18next ресурсы описываются как вложенный объект, где язык содержит набор namespace, а каждый namespace содержит словарь переводов:
const resources = {
ru: {
common: {
ok: "Ок",
cancel: "Отмена"
},
auth: {
login: "Вход",
logout: "Выход"
}
}
}
Здесь common и auth — это namespace. Именно
на этом уровне возникает необходимость типизации, поскольку ключи внутри
каждого namespace должны быть строго ограничены заранее определённой
структурой.
Без TypeScript система i18next позволяет обращаться к любому ключу:
t("auth.signin.button")
Если ключ отсутствует, ошибка обнаруживается только во время выполнения. При масштабировании проекта это приводит к накоплению «мёртвых» ключей и трудноуловимым ошибкам локализации.
TypeScript позволяет перенести проверку в фазу компиляции, но для этого требуется явно описать структуру namespace.
Основной механизм типизации основан на declaration merging —
расширении типов через модуль i18next.
import "i18next";
declare module "i18next" {
interface CustomTypeOptions {
defaultNS: "common";
resources: {
common: typeof common;
auth: typeof auth;
};
}
}
Где common и auth — это объекты или типы,
описывающие структуру переводов.
const common = {
ok: "Ок",
cancel: "Отмена"
} as const;
const auth = {
login: "Вход",
logout: "Выход"
} as const;
Использование as const фиксирует литеральные типы строк,
что позволяет TypeScript извлекать точные ключи.
После объявления ресурсов становится возможным извлечение типов ключей:
type CommonKeys = keyof typeof common;
// "ok" | "cancel"
Это позволяет ограничить функцию перевода допустимыми значениями:
t("common.ok"); // корректно
t("common.cancel"); // корректно
t("common.save"); // ошибка TypeScript
В реальных приложениях переводы часто вложены:
const auth = {
form: {
login: "Войти",
password: "Пароль"
},
errors: {
required: "Обязательное поле"
}
} as const;
TypeScript автоматически строит глубокую структуру типов:
type AuthKeys =
| "form.login"
| "form.password"
| "errors.required";
Такая модель позволяет формировать строгую систему ключей с поддержкой вложенных namespace.
i18next поддерживает defaultNS, который используется при
отсутствии явного указания пространства имён:
i18n.init({
defaultNS: "common",
ns: ["common", "auth"]
});
В типах это отражается через:
interface CustomTypeOptions {
defaultNS: "common";
}
Теперь вызовы без namespace интерпретируются как относящиеся к
common:
t("ok"); // интерпретируется как common.ok
Это поведение также типизируется, исключая необходимость явного префикса.
Наиболее строгий вариант типизации предполагает явное описание всех namespace:
interface Resources {
common: {
ok: string;
cancel: string;
};
auth: {
login: string;
logout: string;
};
}
И последующее подключение:
declare module "i18next" {
interface CustomTypeOptions {
resources: Resources;
}
}
Такой подход полностью исключает возможность обращения к несуществующим namespace или ключам.
При росте приложения ручное описание типов становится избыточным. Тогда используется генерация типов из JSON-файлов переводов.
Пример структуры файлов:
locales/
ru/
common.json
auth.json
Скрипт генерации формирует типы:
type Resources = {
common: typeof import("./locales/ru/common.json");
auth: typeof import("./locales/ru/auth.json");
};
Это обеспечивает синхронизацию runtime и compile-time структуры.
i18next поддерживает обращение с явным namespace через двоеточие:
t("auth:login");
В TypeScript это также может быть отражено через шаблонные типы:
type NamespacedKey<N extends string, K extends string> = `${N}:${K}`;
Пример:
type AuthLoginKey = NamespacedKey<"auth", "login">;
// "auth:login"
Это позволяет строго моделировать строковый API i18next.
В архитектуре крупных приложений namespace часто соответствуют модулям:
common — общие элементы UIauth — авторизацияdashboard — панель управленияprofile — профиль пользователяКаждый модуль хранит собственные типы:
export const dashboard = {
title: "Панель",
widgets: {
cpu: "ЦП",
memory: "Память"
}
} as const;
Типы объединяются на уровне глобального ресурса:
type AppResources = {
common: typeof common;
auth: typeof auth;
dashboard: typeof dashboard;
};
Одним из ключевых преимуществ типизации namespace является безопасный рефакторинг. При изменении структуры переводов:
// было
auth.login
// стало
auth.form.login
TypeScript автоматически выявляет все устаревшие обращения:
t("auth.login"); // ошибка компиляции
Это предотвращает скрытые ошибки локализации в runtime.
Несмотря на мощь TypeScript, существуют ограничения:
Для динамических сценариев остаётся fallback:
t(dynamicKey as string);
что частично снижает безопасность.
В сложных интерфейсах ключи могут зависеть от контекста:
type PageNamespace = "auth" | "dashboard";
function translate<N extends PageNamespace>(ns: N, key: string) {
return t(`${ns}:${key}`);
}
Такой подход сохраняет баланс между гибкостью и типовой безопасностью, хотя и жертвует полной проверкой ключей.
Namespace в типизированном виде становится не просто организационной единицей, а частью архитектуры приложения. Он связывает:
tПри правильной настройке типизация превращает локализацию в строго формализованный слой приложения, исключающий целый класс ошибок, связанных с несоответствием ключей и ресурсов.