Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

Несколько языков: обзор

Интернационализация (i18n) — подготовка сайта к работе на нескольких языках. В App Router нет встроенного переключения языков: его собирают из уже знакомых частей — динамического сегмента, файлов с переводами и прокси (proxy.ts). Здесь показана общая схема.

Язык в адресе

Язык делают первым сегментом адреса: /ru/posts, /en/posts. Для этого все страницы переносят в папку app/[lang]:

app/
└── [lang]/
    ├── layout.tsx
    ├── page.tsx           /ru, /en
    └── posts/
        └── page.tsx       /ru/posts, /en/posts

Теперь каждая страница получает lang в params.

Словари

Переводы хранят в JSON-файлах, по одному на язык:

{
  "posts": "Статьи",
  "newPost": "Новая статья"
}

Это файл dictionaries/ru.json; в dictionaries/en.json те же ключи с английским текстом. Функция загрузки словаря:

// lib/dictionaries.ts
import 'server-only';
 
const dictionaries = {
  ru: () => import('@/dictionaries/ru.json').then((module) => module.default),
  en: () => import('@/dictionaries/en.json').then((module) => module.default),
};
 
export type Locale = keyof typeof dictionaries;
 
export async function getDictionary(locale: Locale) {
  return dictionaries[locale]();
}

Пакет server-only (устанавливается командой npm install server-only) гарантирует, что файл не попадёт в браузер.

Перевод на странице

// app/[lang]/posts/page.tsx
import { getDictionary, type Locale } from '@/lib/dictionaries';
 
export default async function PostsPage({ params }: { params: Promise<{ lang: Locale }> }) {
  const { lang } = await params;
  const t = await getDictionary(lang);
 
  return <h1>{t.posts}</h1>;
}

/ru/posts покажет «Статьи», /en/posts — «Posts». Атрибут lang тега html в макете app/[lang]/layout.tsx тоже берут из params.

Выбор языка по умолчанию

Адрес /posts без языка обрабатывает прокси: читает заголовок accept-language, выбирает подходящий язык и перенаправляет, например, на /ru/posts. Если язык браузера не поддерживается, берут язык по умолчанию.

Готовые библиотеки

Ручная схема годится для пары языков и простых строк. Для форм множественного числа («1 статья», «5 статей»), дат, чисел и переключателя языка удобнее библиотека next-intl — она построена на тех же принципах, но многое делает за вас.

Частая ошибка

Забыть проверить значение lang. Адрес /de/posts тоже попадёт в папку [lang], и dictionaries['de'] окажется undefined — страница упадёт с ошибкой. Проверяйте язык по списку поддерживаемых и вызывайте notFound(), если его там нет.

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.