Skip to main content

Полное руководство по интернационализации Next.js

Настройте next-intl с App Router, сконфигурируйте маршрутизацию локалей и автоматизируйте перевод с помощью ИИ.

1

Установить next-intl

next-intl — единый пакет, который обрабатывает маршрутизацию локалей, загрузку сообщений и хуки перевода для App Router в Next.js.

Terminal
npm install next-intl
Почему next-intl, а не next-i18next? next-intl создан для App Router и серверных компонентов. next-i18next проектировался для Pages Router и предлагает ограниченную поддержку App Router.
2

Создать конфигурацию запросов i18n

Создайте два файла: src/i18n/request.ts для загрузки сообщений и src/i18n/routing.ts для определения локалей. Они задают, как next-intl разрешает сообщения и маршруты.

src/i18n/routing.ts
// src/i18n/routing.ts
import { defineRouting } from 'next-intl/routing';
import { createNavigation } from 'next-intl/navigation';

export const routing = defineRouting({
  locales: ['en', 'de', 'ja', 'es'],
  defaultLocale: 'en',
  localePrefix: 'as-needed',  // /about for en, /de/about for de
});

export const { Link, redirect, usePathname, useRouter } =
  createNavigation(routing);
Для Turbopack, сборщика по умолчанию в Next.js 15, требуется experimental.turbo.resolveAlias в next.config.js. Без него возникают ошибки "Couldn't find next-intl config file".
3

Настроить промежуточное ПО

Добавьте middleware.ts для определения локали, перезаписи URL и перенаправлений. Промежуточное ПО перехватывает каждый запрос и обеспечивает применение правильной локали.

middleware.ts
// middleware.ts  <- Must be in project ROOT, not src/
import createMiddleware from 'next-intl/middleware';
import { routing } from './src/i18n/routing';

export default createMiddleware(routing);

export const config = {
  matcher: ['/((?!api|_next|.*\\..*).*)'],
};
middleware.ts ОБЯЗАТЕЛЬНО должен находиться в корневом каталоге проекта, а не внутри src/. Это самая распространённая ошибка конфигурации next-intl.
4

Настроить структуру папки [locale]

Переместите маршруты приложения внутрь app/[locale]/. Добавьте generateStaticParams, чтобы во время сборки создавать страницы для каждой локали. В результате получится структура URL /en/about, /de/about и так далее.

app/[locale]/layout.tsx
// app/[locale]/layout.tsx
import { routing } from '@/i18n/routing';

export function generateStaticParams() {
  return routing.locales.map((locale) => ({ locale }));
}
Необходимо вызывать setRequestLocale(locale) в каждом page.tsx и layout.tsx, использующем переводы. Без этого Next.js переходит на динамическую отрисовку, а производительность сборки значительно снижается.
5

Обновить корневой макет

Загрузите сообщения с помощью getMessages() и передайте их в NextIntlClientProvider в корневом макете локали. Задайте атрибут lang элемента html из параметра locale.

app/[locale]/layout.tsx
// app/[locale]/layout.tsx
import { NextIntlClientProvider } from 'next-intl';
import { getMessages, setRequestLocale } from 'next-intl/server';
import { routing } from '@/i18n/routing';
import { notFound } from 'next/navigation';

export function generateStaticParams() {
  return routing.locales.map((locale) => ({ locale }));
}

export default async function LocaleLayout({
  children,
  params,
}: {
  children: React.ReactNode;
  params: { locale: string };
}) {
  const { locale } = await params;
  if (!routing.locales.includes(locale as any)) notFound();

  setRequestLocale(locale);
  const messages = await getMessages();

  return (
    <html lang={locale}>
      <body>
        <NextIntlClientProvider locale={locale} messages={messages}>
          {children}
        </NextIntlClientProvider>
      </body>
    </html>
  );
}
Для NextIntlClientProvider необходимо явно задать свойство locale. Если его пропустить, в клиентских компонентах возникают трудно диагностируемые ошибки.
6

Использовать переводы в компонентах

Серверные компоненты используют асинхронную функцию getTranslations с await. Клиентские компоненты используют хук useTranslations. Выбирайте в зависимости от места отрисовки компонента: серверные компоненты полностью исключают переводы из пакета JavaScript.

app/[locale]/page.tsx
// Server Component (default)
import { getTranslations, setRequestLocale } from 'next-intl/server';

export default async function AboutPage({
  params,
}: { params: { locale: string } }) {
  const { locale } = await params;
  setRequestLocale(locale);
  const t = await getTranslations('AboutPage');

  return <h1>{t('title')}</h1>;
}

// Client Component ('use client')
'use client';
import { useTranslations } from 'next-intl';

export default function SearchBar() {
  const t = useTranslations('SearchBar');
  return <input placeholder={t('placeholder')} />;
}
Для переведённого содержимого выбирайте серверные компоненты. Они не включают строки перевода в клиентский пакет JavaScript, сокращая время загрузки для пользователей.
7

Добавить SEO: метаданные и hreflang

Используйте generateMetadata для создания заголовков и описаний страниц для каждой локали. Добавьте alternates.languages для тегов hreflang, чтобы поисковые системы находили все языковые версии каждой страницы.

app/[locale]/layout.tsx
// app/[locale]/layout.tsx or any page.tsx
import { getTranslations } from 'next-intl/server';
import { routing } from '@/i18n/routing';

export async function generateMetadata({
  params,
}: { params: { locale: string } }) {
  const { locale } = await params;
  const t = await getTranslations({ locale, namespace: 'Metadata' });

  return {
    title: t('title'),
    description: t('description'),
    alternates: {
      languages: Object.fromEntries(
        routing.locales.map((l) => [l, `/${l}`])
      ),
    },
  };
}
На Vercel сборки могут создать localhost как канонический URL, если metadataBase не задан. Всегда указывайте в metadataBase корневого макета свой рабочий домен.
8

Обработать страницы ошибок и отсутствующих ресурсов

error.tsx и not-found.tsx требуют специальной обработки, поскольку могут отрисовываться вне обычного макета локали. Корневому not-found.tsx нужна собственная настройка поставщика i18n для отображения локализованных сообщений об ошибках.

app/[locale]/error.tsx
// app/[locale]/error.tsx
'use client';
import { useTranslations } from 'next-intl';

export default function Error() {
  const t = useTranslations('Error');
  return (
    <div>
      <h1>{t('title')}</h1>
      <p>{t('description')}</p>
    </div>
  );
}

// app/not-found.tsx (root level -- needs own provider)
import { routing } from '@/i18n/routing';

export default async function GlobalNotFound() {
  return (
    <html lang={routing.defaultLocale}>
      <body>
        <h1>404 - Page Not Found</h1>
      </body>
    </html>
  );
}
Next.js отрисовывает Вашу локализованную страницу 404, только если в коде явно вызывается notFound(). Для неизвестных маршрутов без совпадающей страницы отображается стандартная страница 404 Next.js, а не Ваша локализованная версия.
9

Автоматизировать перевод

После завершения настройки i18n переводите файлы сообщений с помощью ИИ прямо из IDE или используйте CLI i18n Agent в конвейере CI/CD для автоматического перевода при каждом развёртывании.

Terminal
# In your IDE, ask your AI assistant:
> Translate messages/en.json to German, Japanese, and Spanish

✓ messages/de.json created (1.1s)
✓ messages/ja.json created (1.4s)
✓ messages/es.json created (1.0s)
Используйте next-intl-localechain для умных резервных локалей: если бразильский португальский недоступен, пользователь pt-BR увидит перевод pt-PT вместо перехода на английский.

Автоматизировать контроль качества перевода

Выявляйте отсутствующие ключи и нарушенные заполнители до выпуска с помощью i18n-validate. Тестируйте интерфейс с искусственными переводами через i18n-pseudo, пока настоящие ещё не готовы.

Инструменты с открытым исходным кодом для i18n Next.js

Эти пакеты с открытым исходным кодом решают распространённые проблемы в процессах интернационализации Next.js.

next-intl-localechain

Стандартный next-intl при отсутствии перевода сразу переходит на локаль по умолчанию. Пользователь бразильского португальского видит английский вместо подходящего перевода pt-PT. next-intl-localechain добавляет умные цепочки резервных локалей: глубоко объединяет переводы из родственных локалей, чтобы региональные пользователи всегда видели ближайший доступный перевод.

src/i18n/request.ts
import { getRequestConfig } from 'next-intl/server';
import { withLocaleChain } from 'next-intl-localechain';

export default getRequestConfig(withLocaleChain({
  loadMessages: (locale) =>
    import(`../../messages/${locale}.json`).then(m => m.default),
  defaultLocale: 'en'
}));
Автоматически глубоко объединяет переводы в цепочках локалей
Встроенные цепочки для португальского, испанского, французского, немецкого и других языков
Корректно пропускает отсутствующие файлы сообщений без ошибок
Настройка одной строкой: оборачивает существующий getRequestConfig
Открыть на GitHub

@i18n-agent/cli

Инструмент командной строки для перевода файлов сообщений Next.js без выхода из терминала. Переводите файлы напрямую, проверяйте состояние заданий и скачивайте результаты. Работает в конвейерах CI/CD с аутентификацией по ключу API для полностью автоматизированной локализации.

Terminal
# Install the CLI
npm install -g @i18n-agent/cli

# Authenticate
i18nagent login

# Translate your message files
i18nagent translate ./messages/en.json --lang de,ja,es

# Or use in CI/CD with an API key
export I18N_AGENT_API_KEY=your-key-here
i18nagent translate ./messages/en.json --lang de,ja,es
Перевод JSON, YAML, PO и других форматов файлов i18n из терминала
Готов к CI/CD: аутентификация через переменную среды для автоматизированных конвейеров
Отслеживание состояния заданий, возобновление заданий после сбоя и скачивание результатов
Машиночитаемый вывод JSON для сценариев и автоматизации
Открыть на GitHub

Распространённые ошибки

"Unable to find next-intl locale"

Промежуточное ПО не сопоставило запрос. Проверьте: находится ли middleware.ts в корне проекта? Правильно ли шаблон matcher исключает статические файлы? Включена ли локаль в конфигурацию маршрутизации?

Неожиданная динамическая отрисовка

В странице или макете отсутствует setRequestLocale(locale). Без него next-intl определяет локаль по заголовкам или cookie, что принудительно включает динамическую отрисовку и препятствует статическому созданию.

Параллельные маршруты нарушаются при использовании i18n

У параллельных маршрутов (@modal) и перехватывающих маршрутов ((.)photo) есть известные проблемы совместимости с динамическим сегментом [locale]. Для таких сложных шаблонов используйте маршрутизацию на основе промежуточного ПО в качестве обходного решения.

При переключении языка теряется текущий маршрут

При переключении локалей сохраните текущий pathname с помощью usePathname() и замените только сегмент локали. Будьте внимательны с параметрами динамического маршрута: их необходимо повторно разрешить для новой локали.

Рекомендуемая структура файлов

Project Structure
my-nextjs-app/
├── middleware.ts              # Locale routing (project root!)
├── next.config.mjs
├── messages/
│   ├── en.json                # Source messages
│   ├── de.json
│   └── ja.json
├── src/
│   ├── i18n/
│   │   ├── request.ts         # Message loading config
│   │   └── routing.ts         # Locale definitions
│   └── app/
│       └── [locale]/
│           ├── layout.tsx     # Root locale layout
│           ├── page.tsx       # Home page
│           ├── error.tsx      # Localized error page
│           ├── not-found.tsx  # Localized 404
│           └── about/
│               └── page.tsx
└── package.json

Попробовать i18n Agent

Перетащите сюда файл перевода

JSON, YAML, PO, XML, CSV, Markdown, Properties

или нажмите, чтобы выбрать

Целевые языки

Регистрация не требуетсяМгновенный расчёт

Резервные локали с next-intl-localechain

Когда в региональной локали, например pt-BR, отсутствует ключ перевода, next-intl сразу переходит на локаль по умолчанию, не проверяя сначала родительскую локаль pt.

Terminal
npm install next-intl-localechain
Configuration
import { withLocaleChain } from 'next-intl-localechain';

export default withLocaleChain({
  fallbacks: {
    'pt-BR': ['pt', 'en'],
    'zh-Hant-HK': ['zh-Hant', 'zh', 'en'],
  },
  defaultLocale: 'en',
  loadMessages: (locale) => import(`./messages/${locale}.json`),
});

Полный список поддерживаемых фреймворков и 75 встроенных цепочек приведён в нашем руководстве по резервным локалям. Learn more →

Часто задаваемые вопросы