Platform
    • Naming & Linter Rules
    • Styling Standards
    • Theme
    • Zod & Typing
    • Internationalization
Projects
  • API Connections & Tools
  1. Documentation
  2. I18n Master Guide

Система i18n

Руководство по архитектуре интернационализации. Включает автоматическую типизацию ключей, проверку полноты перевода и динамическую загрузку ресурсов.

Шаблоны архитектуры i18n

Преимущество: Полная типобезопасность — при отсутствии ключа или несовпадении структуры перевода в разных языках проект не скомпилируется.

Vite — public/locales + react-i18next

Next.js — messages/ + next-intl

Соглашения и расположение

  • Конфигурация:

    Все настройки сосредоточены в shared/config/i18n/. Каждый аспект (типы, инициализация, блоки) вынесен в отдельный файл.

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

    Vite: public/locales/[lng]/. Next.js: messages/[lng]/ (рекомендуется) или public/locales/[lng]/. Вложенные папки группируют namespaces (например, booking/order.json).

01Обертка i18nKey

Identity function для типизации строковых ключей. Обеспечивает автодополнение в IDE для конкретного Namespace.
shared/config/i18n/i18n-key.ts
1export const i18nKey = 2 <Keys extends string>() => 3 <K extends Keys>(key: K) => 4 key;

02Магия типов

Рекурсивный обход JSON-объектов через TNestedKeyOf для генерации путей к ключам через точку.
shared/config/i18n/i18n.types.ts
1export type TDotPrefix<T extends string, P extends string> = P extends "" 2 ? T 3 : `${P}.${T}`; 4 5export type TNestedKeyOf<ObjectType extends object> = { 6 [Key in keyof ObjectType & string]: ObjectType[Key] extends object 7 ? TDotPrefix<TNestedKeyOf<ObjectType[Key]>, Key> 8 : Key; 9}[keyof ObjectType & string];

03Реестр ресурсов

В i18n.config.ts эталонные JSON через import type → TResources. Vite: public/locales; Next: messages/ (рекомендуется) или public/locales.
Vite — public/locales → i18n.config.ts
1import type common from "../../../../public/locales/en/common.json"; 2import type header from "../../../../public/locales/en/header.json"; 3import type { TNestedKeyOf } from "./i18n.types"; 4 5export type TCommon = typeof common; 6export type THeader = typeof header; 7 8export type TResources = { 9 common: TCommon; 10 header: THeader; 11}; 12 13export const NS = ["common", "header"] as const; 14export type TCommonKeys = TNestedKeyOf<TCommon>;
Next.js — messages/ → i18n.config.ts
1import type common from "../../../../messages/en/common.json"; 2import type header from "../../../../messages/en/header.json"; 3import type { TNestedKeyOf } from "./i18n.types"; 4 5export type TCommon = typeof common; 6export type THeader = typeof header; 7 8export type TResources = { 9 common: TCommon; 10 header: THeader; 11}; 12 13export const NS = ["common", "header"] as const; 14export type TCommonKeys = TNestedKeyOf<TCommon>;

04Группировка блоков

Настраиваем физическое расположение файлов и динамическую подгрузку чанков в i18n.blocks.ts.
shared/config/i18n/i18n.blocks.ts
1export const TRANSLATION_BLOCKS = { 2 booking: { folder: "booking", namespaces: ["orders_page"] }, 3 shared: { folder: "", namespaces: ["header", "common"] } 4}; 5 6export const getNamespacePath = (lng: string, ns: string): string => { 7 // ... логика определения пути к JSON 8 return `/locales/${lng}/${ns}.json`; 9};

05Инициализация i18next

Связываем плагины и конфигурацию в единый экземпляр библиотеки в i18n.init.ts (стек Vite SPA).
shared/config/i18n/i18n.init.ts
1import i18n from "i18next"; 2import HttpBackend from "i18next-http-backend"; 3import { initReactI18next } from "react-i18next"; 4import { getNamespacePath } from "./i18n.blocks"; 5import { NS } from "./i18n.config"; 6 7i18n.use(HttpBackend) 8 .use(initReactI18next) 9 .init({ 10 fallbackLng: "en", 11 ns: [...NS], 12 defaultNS: NS[0], 13 backend: { 14 // без allowMultiLoading loadPath вызывается на каждую пару язык + namespace 15 loadPath: (lngs: string[], nss: string[]) => 16 getNamespacePath(lngs[0], nss[0]) 17 } 18 }); 19export default i18n;

06Контроль консистентности

Статическая проверка в i18n.checker.ts: satisfies ловит отсутствующие ключи, явная проверка через TNestedKeyOf — лишние. Гарантирует, что все языки имеют идентичную структуру ключей.
shared/config/i18n/i18n.checker.ts
1import type { TCommonKeys, TResources } from "./i18n.config"; 2import type { TNestedKeyOf } from "./i18n.types"; 3import type common_ru from "../../../../public/locales/ru/common.json"; 4 5/** Ошибка компиляции, если T не never — в сообщении видны проблемные ключи */ 6type TAssertNoKeys<T extends never> = T; 7 8// Нет ключа из EN или не совпал тип значения → ошибка 9export const RU_TRANSLATION_CHECKER = { 10 common: {} as typeof common_ru, 11} satisfies TResources; 12 13// satisfies не видит лишние ключи — проверяем обе стороны явно 14export type TRuCommonMissingKeys = TAssertNoKeys< 15 Exclude<TCommonKeys, TNestedKeyOf<typeof common_ru>> 16>; 17export type TRuCommonExtraKeys = TAssertNoKeys< 18 Exclude<TNestedKeyOf<typeof common_ru>, TCommonKeys> 19>;

07Использование в UI (выбери стек)

Одни typed namespaces — разный UI API и корень JSON. Vite SPA → react-i18next + public/locales. Next.js App Router → next-intl + messages.

Vite — react-i18next

Vite SPA: useTranslation + Trans. JSON: public/locales/[lng]/.

Vite — react-i18next + public/locales
1import { useTranslation, Trans } from "react-i18next"; 2 3export const MyComponent = () => { 4 const { t } = useTranslation("common"); 5 6 return ( 7 <div> 8 <h1>{t("header.title")}</h1> 9 <Trans 10 ns="common" 11 i18nKey="description.text" 12 components={[<strong key="0" />, <br key="1" />]} 13 /> 14 </div> 15 ); 16};

Next.js — next-intl

Next.js App Router: useTranslations + t.rich. JSON: messages/[lng]/ (рекомендуется) или public/locales/[lng]/ через import() в request.ts.

Next.js — next-intl + messages/
1"use client"; 2 3import { useTranslations } from "next-intl"; 4 5export const MyComponent = () => { 6 const t = useTranslations("common"); 7 8 return ( 9 <div> 10 <h1>{t("header.title")}</h1> 11 {t.rich("description.text", { 12 strong: (chunks) => <strong>{chunks}</strong>, 13 br: () => <br /> 14 })} 15 </div> 16 ); 17};

08Смена языка

Vite: changeLanguage + localStorage. Next.js: locale routing / next-intl navigation (не i18next.changeLanguage).
shared/config/i18n/change-language.ts
1import i18n from "./i18n.init"; 2 3export const changeLanguage = (lng: string) => { 4 i18n.changeLanguage(lng); 5 localStorage.setItem("i18nextLng", lng); 6};