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

i18n System

Comprehensive guide to internationalization architecture. Includes automatic key typing, translation completeness checks, and dynamic resource loading.

i18n Architecture Patterns

Benefit: Total type safety — the project will not compile if a key is missing or translation structures differ across languages.

Vite — public/locales + react-i18next

Next.js — messages/ + next-intl

Naming and Location Conventions

  • Configuration:

    All settings are centralized in shared/config/i18n/. Each aspect (types, initialization, blocks) is separated into its own file.

  • Translation Files:

    Vite: public/locales/[lng]/. Next.js: messages/[lng]/ (recommended) or public/locales/[lng]/. Nested folders group namespaces (e.g., booking/order.json).

01i18nKey Wrapper

Identity function for typing string keys. Provides IDE autocompletion for a specific Namespace.
shared/config/i18n/i18n-key.ts
1export const i18nKey = 2 <Keys extends string>() => 3 <K extends Keys>(key: K) => 4 key;

02Type Magic

Recursive JSON object traversal via TNestedKeyOf to generate dot-notated key paths.
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];

03Resource Registry

In i18n.config.ts, import reference JSON via import type and build TResources. Vite: public/locales; Next: messages/ (recommended) or 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>;

04Translation Blocks

Configuring physical file locations and dynamic chunk loading in 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};

05i18next Initialization

Binding plugins and configuration into a single library instance in i18n.init.ts (Vite SPA stack).
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;

06Consistency Control

Static check in i18n.checker.ts: satisfies catches missing keys, an explicit TNestedKeyOf check catches extra keys. Guarantees that all languages have an identical key structure.
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>;

07UI Usage (pick your stack)

Same typed namespaces — different UI APIs and JSON roots. 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]/ (recommended) or public/locales/[lng]/ loaded via import() in 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};

08Language Switching

Vite: changeLanguage + localStorage. Next.js: locale routing / next-intl navigation (not 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};