ГоловнаПісочницяВітринаДодатокДокументаціяБлог
    • 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

    Задайте питання та отримайте підсумок документа, вказавши цю сторінку та обраного вами постачальника штучного інтелекту

    Вміст цієї сторінки перекладено за допомогою штучного інтелекту.

    Переглянути останню версію оригінального вмісту англійською
    Редагувати цей документ

    Якщо у вас є ідея щодо покращення цієї документації, будь ласка, долучіться, надіславши pull request на 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.

    • Для junior-розробників: дотримуйтесь покрокових розділів, щоб отримати працездатний мультимовний додаток.
    • Для mid-level розробників: зверніть увагу на оптимізацію payload та розділення server/client.
    • Для senior-розробників: зверніть увагу на static generation, middleware, інтеграцію SEO та automation hooks.

    Що ми розглянемо:

    • Налаштування та структура файлів
    • Оптимізація завантаження повідомлень
    • Використання клієнтських та серверних компонентів
    • Метадані, 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) {  // Завантажуйте лише ті namespaces, які потрібні вашим layout/pages  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-компонент є серверним компонентом і може рендеритися під клієнтським компонентом (page → client → server). Зберігайте його синхронним, передаючи попередньо обчислені рядки.

    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)).
    • Уникайте передачі функцій або несеріалізованих об'єктів у server components.
    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 },    },  };}// ... Rest of the page code
    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|.*\\..*).*)"],};

    Найкращі практики

    • Встановіть атрибути lang та dir для html: У src/app/[locale]/layout.tsx обчисліть dir за допомогою getLocaleDirection(locale) і встановіть <html lang={locale} dir={dir}>.
    • Розділяйте повідомлення за просторами імен: Організуйте JSON за локалями та просторами імен (наприклад, 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,  },  plugins: [    // Підтримуйте структуру папок для кожного namespace синхронізованою з Intlayer    syncJSON({      format: "icu",      source: ({ key, locale }) => `./locales/${locale}/${key}.json`,    }),  ],};export default config;

    Додайте скрипти в package.json:

    package.json
    Копіювати код

    Скопіюйте код у буфер обміну

    {  "scripts": {    "i18n:fill": "intlayer fill",    "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) {  // Завантажуйте лише ті namespaces, які потрібні вашим layout/pages  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 },    },  };}// ... Rest of the page code
      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,  },  plugins: [    // Підтримуйте структуру папок для кожного namespace синхронізованою з Intlayer    syncJSON({      format: "icu",      source: ({ key, locale }) => `./locales/${locale}/${key}.json`,    }),  ],};export default config;
      {  "scripts": {    "i18n:fill": "intlayer fill",    "i18n:test": "intlayer test"  }}