Comprehensive guide to internationalization architecture. Includes automatic key typing, translation completeness checks, and dynamic resource loading.
Benefit: Total type safety — the project will not compile if a key is missing or translation structures differ across languages.
All settings are centralized in shared/config/i18n/. Each aspect (types, initialization, blocks) is separated into its own file.
Located in public/locales/[lng]/. Use nested folders to group namespaces (e.g., booking/order.json).
1export const i18nKey =
2 <Keys extends string>() =>
3 <K extends Keys>(key: K) =>
4 key;TNestedKeyOf to generate dot-notated key paths.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];i18n.config.ts, we import reference JSON files and create a master type TResources.1import common from "../../../../public/locales/en/common.json";
2import header from "../../../../public/locales/en/header.json";
3
4export type TCommon = typeof common;
5export type THeader = typeof header;
6
7export type TResources = {
8 common: TCommon;
9 header: THeader;
10};
11
12export const NS = ["common", "header"] as const;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};i18n.init.ts.1import i18n from "i18next";
2import { initReactI18next } from "react-i18next";
3import { NS } from "./i18n.config";
4
5i18n.use(initReactI18next).init({
6 fallbackLng: "en",
7 ns: NS,
8 backend: {
9 loadPath: (lngs, nss) => getNamespacePath(lngs[0], nss[0])
10 }
11});
12export default i18n;i18n.checker.ts. Guarantees that all languages have an identical key structure.1import type { TResources } from "./i18n.config";
2import common_ru from "../../../../public/locales/ru/common.json";
3
4// Статическая проверка: если в RU нет ключа из EN - будет ошибка
5export const RU_TRANSLATION_CHECKER: TResources = {
6 common: common_ru,
7};useTranslation hook and the Trans component for working with dynamic content.1import { useTranslations } from "next-intl";
2
3export const MyComponent = () => {
4 const t = useTranslations("common");
5
6 return <h1>{t("header.title")}</h1>;
7};1
2import { useTranslations } from "next-intl";
3
4export const MyComponent = () => {
5 const t = useTranslations("common");
6
7 return (
8 <div>
9 <h1>{t("header.title")}</h1>
10 {t.rich("description.text", {
11 0: (chunks) => <strong>{chunks}</strong/>,
12 1: (chunks) => <br>{chunks}</br/>,
13 })}
14 </div>
15 );
16};changeLanguage utility for synchronous library state and localStorage updates.1import i18n from "./i18n.init";
2
3export const changeLanguage = (lng: string) => {
4 i18n.changeLanguage(lng);
5 localStorage.setItem("i18nextLng", lng);
6};