ГлавнаяПесочницаВитринаПриложениеДокументБлог
    • Englishанглийский
      EN
    • русскийрусский
      RU
    • 日本語японский
      JA
    • françaisфранцузский
      FR
    • 한국어корейский
      KO
    • 中文китайский
      ZH
    • españolиспанский
      ES
    • Deutschнемецкий
      DE
    • العربيةарабский
      AR
    • italianoитальянский
      IT
    • British Englishбританский английский
      EN-GB
    • portuguêsпортугальский
      PT
    • हिन्दीхинди
      HI
    • Türkçeтурецкий
      TR
    • polskiпольский
      PL
    • Indonesiaиндонезийский
      ID
    • Tiếng Việtвьетнамский
      VI
    • українськаукраинский
      UK
    /
    Фильтровать документы по фреймворку
    Alt+←
    Почему Intlayer?
    Начать
    Концепция
    • Как работает Intlayer
    • Конфигурация
    • TestFillBuildWatchExtractLoginPushPullConfigurationListVersionEditorLiveDebugDoc ReviewDoc TranslateSDK
    • Визуальный редактор
    • CMS
    • Интеграция CI/CD
    • ПереводМножественное числоПеречислениеУсловиеПолВставкаФайлВложенностьMarkdownHTMLПолучение функции
    • Файл для каждой локали
    • Компилятор
    • Автозаполнение
    • Тестирование
    • Оптимизация пакета
    Окружающая среда
    • Next.js 14 и App Router
      Next.js 15
      Next.js без locale URL
      Next.js и Page Router
      Compiler
    • Tanstack Start Solid
    • Astro и React
      Astro и Svelte
      Astro и Vue
      Astro и Solid
      Astro и Preact
      Astro и Lit
      Astro и Vanilla JS
    • React Router v7
      React Router v7 (fs-routes)
      Compiler
    • Nuxt и Vue
    • Vite и Solid
    • SvelteKit
    • Vite и Preact
    • Vite и Vanilla JS
    • Vite и Lit
    • Angular 19 (Webpack)
      Analog
    • React CRA
    • React Native и Expo
    • Express.js
      NestJS
      Fastify
      Hono
      Adonis
    • Lynx и React
    Plugins
    • JSON
    • gettext (.po)
    Расширение VS Code
    Агент
    • Сервер MCP
    • Навики агента
    Релизы
    • v8
    • v7
    • v6
    Бенчмарк
    • Next.js
    • TanStack
    • Vue
    • Solid
    • Svelte
    Блог
    Задать вопрос
    1. Documentation
    2. Next intl
    Создание:2025-10-05Последнее обновление:2025-10-05
    Посмотреть шаблон приложения на GitHub

    Для этой страницы доступен шаблон приложения.

    Ссылайтесь на этот документ на ваш любимый ассистент AI
    ChatGPT
    Claude
    DeepSeek
    Google AI mode
    Gemini
    Perplexity
    Mistral
    Grok

    Спросите свой вопрос и получите сводку документа, используя эту страницу и выбранного вами поставщика AI

    Содержимое этой страницы было переведено с помощью ИИ.

    Смотреть последнюю версию оригинального контента на английском
    Изменить эту документацию

    Если у вас есть идея по улучшению этой документации, не стесняйтесь внести свой вклад, подав запрос на вытягивание на GitHub.

    Ссылка на документацию GitHub
    Копировать

    Копировать Markdown документа в буфер обмена

    Перевод вашего сайта на Next.js 15 с использованием next-intl и Intlayer | Интернационализация (i18n)

    Это руководство проведет вас через лучшие практики использования next-intl в приложении Next.js 15 (App Router) и покажет, как наложить Intlayer сверху для надежного управления переводами и автоматизации.

    Смотрите сравнение в next-i18next vs next-intl vs Intlayer.

    • Для начинающих: следуйте пошаговым разделам, чтобы получить рабочее многоязычное приложение.
    • Для разработчиков среднего уровня: обратите внимание на оптимизацию payload и разделение серверной/клиентской части.
    • Для опытных разработчиков: обратите внимание на статическую генерацию, middleware, интеграцию SEO и хуки автоматизации.

    Что мы рассмотрим:

    • Настройка и структура файлов
    • Оптимизация загрузки сообщений
    • Использование клиентских и серверных компонентов
    • Метаданные, sitemap, robots для SEO
    • Middleware для маршрутизации по локали
    • Добавление Intlayer сверху (CLI и автоматизация)

    Настройка вашего приложения с использованием next-intl

    Установите зависимости next-intl -

    bash
    Копировать код

    Копировать код в буфер обмена

    npm install next-intl
    bash
    Копировать код

    Копировать код в буфер обмена

    .├── locales│   ├── en│   │  ├── common.json│   │  └── about.json│   ├── fr│   │  ├── common.json│   │  └── about.json│   └── es│      ├── common.json│      └── about.json└── src    ├── i18n.ts    ├── middleware.ts    ├── app    │   └── [locale]    │       ├── layout.tsx    │       └── about    │           └── page.tsx    └── components        ├── ClientComponentExample.tsx        └── ServerComponent.tsx

    Настройка и загрузка контента

    Загружайте только те пространства имён, которые нужны вашим маршрутам, и проверяйте локали на раннем этапе. По возможности держите серверные компоненты синхронными и отправляйте на клиент только необходимые сообщения.

    src/i18n.ts
    Копировать код

    Копировать код в буфер обмена

    import { getRequestConfig } from "next-intl/server";import { notFound } from "next/navigation";export const locales = ["en", "fr", "es"] as const;export const defaultLocale = "en" as const;async function loadMessages(locale: string) {  // Загружайте только те пространства имён, которые нужны вашему layout/страницам  const [common, about] = await Promise.all([    import(`../locales/${locale}/common.json`).then((m) => m.default),    import(`../locales/${locale}/about.json`).then((m) => m.default),  ]);  return { common, about } as const;}export default getRequestConfig(async ({ locale }) => {  if (!locales.includes(locale)) notFound();  return {    messages: await loadMessages(locale),  };});
    src/app/[locale]/layout.tsx
    Копировать код

    Копировать код в буфер обмена

    import type { ReactNode } from "react";import { locales } from "@/i18n";import {  getLocaleDirection,  unstable_setRequestLocale,} from "next-intl/server";export const dynamic = "force-static";export function generateStaticParams() {  return locales.map((locale) => ({ locale }));}export default async function LocaleLayout({  children,  params,}: {  children: ReactNode;  params: { locale: string };}) {  const { locale } = params;  // Установить активную локаль запроса для этого серверного рендера (RSC)  unstable_setRequestLocale(locale);  const dir = getLocaleDirection(locale);  return (    <html lang={locale} dir={dir}>      <body>{children}</body>    </html>  );}
    src/app/[locale]/about/page.tsx
    Копировать код

    Копировать код в буфер обмена

    import { getTranslations, getMessages, getFormatter } from "next-intl/server";import { NextIntlClientProvider } from "next-intl";import pick from "lodash/pick";import ServerComponent from "@/components/ServerComponent";import ClientComponentExample from "@/components/ClientComponentExample";export const dynamic = "force-static";export default async function AboutPage({  params,}: {  params: { locale: string };}) {  const { locale } = params;  // Сообщения загружаются на стороне сервера. Отправляем клиенту только необходимое.  const messages = await getMessages();  const clientMessages = pick(messages, ["common", "about"]);  // Строго серверные переводы/форматирование  const tAbout = await getTranslations("about");  const tCounter = await getTranslations("about.counter");  const format = await getFormatter();  const initialFormattedCount = format.number(0);  return (    <NextIntlClientProvider locale={locale} messages={clientMessages}>      <main>        <h1>{tAbout("title")}</h1>        <ClientComponentExample />        <ServerComponent          formattedCount={initialFormattedCount}          label={tCounter("label")}          increment={tCounter("increment")}        />      </main>    </NextIntlClientProvider>  );}

    Использование в клиентском компоненте

    Рассмотрим пример клиентского компонента, который отображает счетчик.

    Переводы (структура повторяется; загружайте их в сообщения next-intl по вашему усмотрению)

    locales/en/about.json
    Копировать код

    Копировать код в буфер обмена

    {  "counter": {    "label": "Счетчик",    "increment": "Увеличить"  }}
    locales/fr/about.json
    Копировать код

    Копировать код в буфер обмена

    {  "counter": {    "label": "Счётчик",    "increment": "Увеличить"  }}

    Клиентский компонент

    src/components/ClientComponentExample.tsx
    Копировать код

    Копировать код в буфер обмена

    "use client";import React, { useState } from "react";import { useTranslations, useFormatter } from "next-intl";const ClientComponentExample = () => {  // Область видимости непосредственно для вложенного объекта  const t = useTranslations("about.counter");  const format = useFormatter();  const [count, setCount] = useState(0);  return (    <div>      <p>{format.number(count)}</p>      <button        aria-label={t("label")}        onClick={() => setCount((count) => count + 1)}      >        {t("increment")}      </button>    </div>  );};

    Не забудьте добавить сообщение "about" в клиентские сообщения страницы (включайте только те пространства имён, которые действительно нужны вашему клиенту).

    Использование в серверном компоненте

    Этот UI-компонент является серверным компонентом и может быть отрендерен внутри клиентского компонента (страница → клиент → сервер). Поддерживайте его синхронным, передавая заранее вычисленные строки.

    src/components/ServerComponent.tsx
    Копировать код

    Копировать код в буфер обмена

    type ServerComponentProps = {  formattedCount: string;  label: string;  increment: string;};const ServerComponent = ({  formattedCount,  label,  increment,}: ServerComponentProps) => {  return (    <div>      <p>{formattedCount}</p>      <button aria-label={label}>{increment}</button>    </div>  );};

    Примечания:

    • Вычисляйте formattedCount на стороне сервера (например, const initialFormattedCount = format.number(0)).
    • Избегайте передачи функций или несериализуемых объектов в серверные компоненты.
    src/app/[locale]/about/layout.tsx
    Копировать код

    Копировать код в буфер обмена

    import type { Metadata } from "next";import { locales, defaultLocale } from "@/i18n";import { getTranslations } from "next-intl/server";function localizedPath(locale: string, path: string) {  return locale === defaultLocale ? path : "/" + locale + path;}export async function generateMetadata({  params,}: {  params: { locale: string };}): Promise<Metadata> {  const { locale } = params;  const t = await getTranslations({ locale, namespace: "about" });  const url = "/about";  const languages = Object.fromEntries(    locales.map((locale) => [locale, localizedPath(locale, url)])  );  return {    title: t("title"),    description: t("description"),    alternates: {      canonical: localizedPath(locale, url),      languages: { ...languages, "x-default": url },    },  };}// ... Остальная часть кода страницы
    src/app/sitemap.ts
    Копировать код

    Копировать код в буфер обмена

    import type { MetadataRoute } from "next";import { locales, defaultLocale } from "@/i18n";const origin = "https://example.com";const formatterLocalizedPath = (locale: string, path: string) =>  locale === defaultLocale ? origin + path : origin + "/" + locale + path;export default function sitemap(): MetadataRoute.Sitemap {  const aboutLanguages = Object.fromEntries(    locales.map((l) => [l, formatterLocalizedPath(l, "/about")])  );  return [    {      url: formatterLocalizedPath(defaultLocale, "/about"),      lastModified: new Date(),      changeFrequency: "monthly",      priority: 0.7,      alternates: { languages: aboutLanguages },    },  ];}
    src/app/robots.ts
    Копировать код

    Копировать код в буфер обмена

    import type { MetadataRoute } from "next";import { locales, defaultLocale } from "@/i18n";const origin = "https://example.com";// Функция для генерации путей со всеми локалямиconst withAllLocales = (path: string) => [  path,  ...locales    .filter((locale) => locale !== defaultLocale)    .map((locale) => "/" + locale + path),];export default function robots(): MetadataRoute.Robots {  // Запрет доступа к определённым путям для всех локалей  const disallow = [    ...withAllLocales("/dashboard"),    ...withAllLocales("/admin"),  ];  return {    rules: { userAgent: "*", allow: ["/"], disallow },    host: origin,    sitemap: origin + "/sitemap.xml",  };}

    Middleware для маршрутизации локалей

    Добавьте middleware для обработки определения локали и маршрутизации:

    src/middleware.ts
    Копировать код

    Копировать код в буфер обмена

    import createMiddleware from "next-intl/middleware";import { locales, defaultLocale } from "@/i18n";export default createMiddleware({  locales: [...locales],  defaultLocale,  localeDetection: true,});export const config = {  // Пропустить API, внутренние части Next и статические ресурсы  matcher: ["/((?!api|_next|.*\\..*).*)"],};

    Лучшие практики

    • Установите html атрибуты lang и dir: В src/app/[locale]/layout.tsx вычисляйте dir с помощью getLocaleDirection(locale) и задавайте <html lang={locale} dir={dir}>.
    • Разделяйте сообщения по namespace: Организуйте JSON по локалям и namespace (например, common.json, about.json).
    • Минимизируйте нагрузку на клиент: На страницах отправляйте в NextIntlClientProvider только необходимые пространства имён (например, pick(messages, ['common', 'about'])).
    • Предпочитайте статические страницы: Экспортируйте export const dynamic = 'force-static' и генерируйте статические параметры для всех locales.
    • Синхронные серверные компоненты: Передавайте заранее вычисленные строки (переведённые метки, отформатированные числа), а не асинхронные вызовы или несериализуемые функции.

    Реализация Intlayer поверх next-intl

    Установите зависимости intlayer:

    bash
    Копировать код

    Копировать код в буфер обмена

    npm install intlayer @intlayer/sync-json-plugin --save-dev

    Создайте файл конфигурации intlayer:

    intlayer.config.ts
    Копировать код

    Копировать код в буфер обмена

    import { type IntlayerConfig, Locales } from "intlayer";import { syncJSON } from "@intlayer/sync-json-plugin";const config: IntlayerConfig = {  internationalization: {    locales: [Locales.ENGLISH, Locales.FRENCH, Locales.SPANISH], // локали для интернационализации    defaultLocale: Locales.ENGLISH, // локаль по умолчанию  },  ai: {    apiKey: process.env.OPENAI_API_KEY, // ключ API для AI  },  plugins: [    // Синхронизируйте структуру папок по namespace с Intlayer    syncJSON({      format: "icu",      source: ({ key, locale }) => `./locales/${locale}/${key}.json`, // путь к JSON-файлам с переводами    }),  ],};export default config;

    Добавьте скрипты в package.json:

    package.json
    Копировать код

    Копировать код в буфер обмена

    {  "scripts": {    "i18n:fill": "intlayer fill", // заполнение переводов с помощью AI    "i18n:test": "intlayer test" // проверка отсутствующих/некорректных переводов  }}

    Примечания:

    • intlayer fill: использует вашего AI-провайдера для заполнения отсутствующих переводов на основе настроенных локалей.
    • intlayer test: проверяет отсутствующие/недействительные переводы (используйте в CI).

    Вы можете настроить аргументы и провайдеров; смотрите Intlayer CLI.

    Почему Intlayer?
    Alt+→

    На этой странице

      Обсуждения анонимны и регулярно просматриваются для решения распространённых проблем. Не стесняйтесь делиться идеями функций, отзывами о документации или чем-либо, связанным с Intlayer, мы используем эту информацию для формирования нашей дорожной карты и улучшения продукта.

      npm install next-intl
      .├── locales│   ├── en│   │  ├── common.json│   │  └── about.json│   ├── fr│   │  ├── common.json│   │  └── about.json│   └── es│      ├── common.json│      └── about.json└── src    ├── i18n.ts    ├── middleware.ts    ├── app    │   └── [locale]    │       ├── layout.tsx    │       └── about    │           └── page.tsx    └── components        ├── ClientComponentExample.tsx        └── ServerComponent.tsx
      import { getRequestConfig } from "next-intl/server";import { notFound } from "next/navigation";export const locales = ["en", "fr", "es"] as const;export const defaultLocale = "en" as const;async function loadMessages(locale: string) {  // Загружайте только те пространства имён, которые нужны вашему layout/страницам  const [common, about] = await Promise.all([    import(`../locales/${locale}/common.json`).then((m) => m.default),    import(`../locales/${locale}/about.json`).then((m) => m.default),  ]);  return { common, about } as const;}export default getRequestConfig(async ({ locale }) => {  if (!locales.includes(locale)) notFound();  return {    messages: await loadMessages(locale),  };});
      import type { ReactNode } from "react";import { locales } from "@/i18n";import {  getLocaleDirection,  unstable_setRequestLocale,} from "next-intl/server";export const dynamic = "force-static";export function generateStaticParams() {  return locales.map((locale) => ({ locale }));}export default async function LocaleLayout({  children,  params,}: {  children: ReactNode;  params: { locale: string };}) {  const { locale } = params;  // Установить активную локаль запроса для этого серверного рендера (RSC)  unstable_setRequestLocale(locale);  const dir = getLocaleDirection(locale);  return (    <html lang={locale} dir={dir}>      <body>{children}</body>    </html>  );}
      import { getTranslations, getMessages, getFormatter } from "next-intl/server";import { NextIntlClientProvider } from "next-intl";import pick from "lodash/pick";import ServerComponent from "@/components/ServerComponent";import ClientComponentExample from "@/components/ClientComponentExample";export const dynamic = "force-static";export default async function AboutPage({  params,}: {  params: { locale: string };}) {  const { locale } = params;  // Сообщения загружаются на стороне сервера. Отправляем клиенту только необходимое.  const messages = await getMessages();  const clientMessages = pick(messages, ["common", "about"]);  // Строго серверные переводы/форматирование  const tAbout = await getTranslations("about");  const tCounter = await getTranslations("about.counter");  const format = await getFormatter();  const initialFormattedCount = format.number(0);  return (    <NextIntlClientProvider locale={locale} messages={clientMessages}>      <main>        <h1>{tAbout("title")}</h1>        <ClientComponentExample />        <ServerComponent          formattedCount={initialFormattedCount}          label={tCounter("label")}          increment={tCounter("increment")}        />      </main>    </NextIntlClientProvider>  );}
      {  "counter": {    "label": "Счетчик",    "increment": "Увеличить"  }}
      {  "counter": {    "label": "Счётчик",    "increment": "Увеличить"  }}
      "use client";import React, { useState } from "react";import { useTranslations, useFormatter } from "next-intl";const ClientComponentExample = () => {  // Область видимости непосредственно для вложенного объекта  const t = useTranslations("about.counter");  const format = useFormatter();  const [count, setCount] = useState(0);  return (    <div>      <p>{format.number(count)}</p>      <button        aria-label={t("label")}        onClick={() => setCount((count) => count + 1)}      >        {t("increment")}      </button>    </div>  );};
      type ServerComponentProps = {  formattedCount: string;  label: string;  increment: string;};const ServerComponent = ({  formattedCount,  label,  increment,}: ServerComponentProps) => {  return (    <div>      <p>{formattedCount}</p>      <button aria-label={label}>{increment}</button>    </div>  );};
      import type { Metadata } from "next";import { locales, defaultLocale } from "@/i18n";import { getTranslations } from "next-intl/server";function localizedPath(locale: string, path: string) {  return locale === defaultLocale ? path : "/" + locale + path;}export async function generateMetadata({  params,}: {  params: { locale: string };}): Promise<Metadata> {  const { locale } = params;  const t = await getTranslations({ locale, namespace: "about" });  const url = "/about";  const languages = Object.fromEntries(    locales.map((locale) => [locale, localizedPath(locale, url)])  );  return {    title: t("title"),    description: t("description"),    alternates: {      canonical: localizedPath(locale, url),      languages: { ...languages, "x-default": url },    },  };}// ... Остальная часть кода страницы
      import type { MetadataRoute } from "next";import { locales, defaultLocale } from "@/i18n";const origin = "https://example.com";const formatterLocalizedPath = (locale: string, path: string) =>  locale === defaultLocale ? origin + path : origin + "/" + locale + path;export default function sitemap(): MetadataRoute.Sitemap {  const aboutLanguages = Object.fromEntries(    locales.map((l) => [l, formatterLocalizedPath(l, "/about")])  );  return [    {      url: formatterLocalizedPath(defaultLocale, "/about"),      lastModified: new Date(),      changeFrequency: "monthly",      priority: 0.7,      alternates: { languages: aboutLanguages },    },  ];}
      import type { MetadataRoute } from "next";import { locales, defaultLocale } from "@/i18n";const origin = "https://example.com";// Функция для генерации путей со всеми локалямиconst withAllLocales = (path: string) => [  path,  ...locales    .filter((locale) => locale !== defaultLocale)    .map((locale) => "/" + locale + path),];export default function robots(): MetadataRoute.Robots {  // Запрет доступа к определённым путям для всех локалей  const disallow = [    ...withAllLocales("/dashboard"),    ...withAllLocales("/admin"),  ];  return {    rules: { userAgent: "*", allow: ["/"], disallow },    host: origin,    sitemap: origin + "/sitemap.xml",  };}
      import createMiddleware from "next-intl/middleware";import { locales, defaultLocale } from "@/i18n";export default createMiddleware({  locales: [...locales],  defaultLocale,  localeDetection: true,});export const config = {  // Пропустить API, внутренние части Next и статические ресурсы  matcher: ["/((?!api|_next|.*\\..*).*)"],};
      npm install intlayer @intlayer/sync-json-plugin --save-dev
      import { type IntlayerConfig, Locales } from "intlayer";import { syncJSON } from "@intlayer/sync-json-plugin";const config: IntlayerConfig = {  internationalization: {    locales: [Locales.ENGLISH, Locales.FRENCH, Locales.SPANISH], // локали для интернационализации    defaultLocale: Locales.ENGLISH, // локаль по умолчанию  },  ai: {    apiKey: process.env.OPENAI_API_KEY, // ключ API для AI  },  plugins: [    // Синхронизируйте структуру папок по namespace с Intlayer    syncJSON({      format: "icu",      source: ({ key, locale }) => `./locales/${locale}/${key}.json`, // путь к JSON-файлам с переводами    }),  ],};export default config;
      {  "scripts": {    "i18n:fill": "intlayer fill", // заполнение переводов с помощью AI    "i18n:test": "intlayer test" // проверка отсутствующих/некорректных переводов  }}