Strona głównaPiaskownicaPrezentacjaAplikacjaDokumentacjaBlog
    • Englishangielski
      EN
    • русскийrosyjski
      RU
    • 日本語japoński
      JA
    • françaisfrancuski
      FR
    • 한국어koreański
      KO
    • 中文chiński
      ZH
    • españolhiszpański
      ES
    • Deutschniemiecki
      DE
    • العربيةarabski
      AR
    • italianowłoski
      IT
    • British Englishangielski brytyjski
      EN-GB
    • portuguêsportugalski
      PT
    • हिन्दीhindi
      HI
    • Türkçeturecki
      TR
    • polskipolski
      PL
    • Indonesiaindonezyjski
      ID
    • Tiếng Việtwietnamski
      VI
    • українськаukraiński
      UK
    /
    Filtruj dokumenty według frameworka
    Alt+←
    Dlaczego Intlayer?
    Zacząć
    Koncepcja
    • Jak działa Intlayer
    • Konfiguracja
    • TestFillBuildWatchExtractLoginPushPullConfigurationListVersionEditorLiveDebugDoc ReviewDoc TranslateSDK
    • Edytor wizualny
    • CMS
    • Integracja CI/CD
    • TłumaczenieLiczba mnogaWyliczenieWarunekPłećWstawieniePlikZagnieżdżanieMarkdownHTMLPobieranie funkcji
    • Plik dla każdej lokalizacji
    • Kompilator
    • Automatyczne wypełnianie
    • Testowanie
    • Optymalizacja pakietu
    Środowisko
    • Next.js 14 i App Router
      Next.js 15
      Next.js bez locale URL
      Next.js dan Page Router
      Kompilator
    • Tanstack Start Solid
    • Astro dan React
      Astro dan Svelte
      Astro dan Vue
      Astro dan Solid
      Astro dan Preact
      Astro dan Lit
      Astro dan Vanilla JS
    • React Router v7
      React Router v7 (fs-routes)
      Compiler
    • Nuxt dan Vue
    • Vite dan Solid
    • SvelteKit
    • Vite dan Preact
    • Vite dan Vanilla JS
    • Vite dan Lit
    • Angular 19 (Webpack)
      Analog
    • React CRA
    • React Native dan Expo
    • Express.js
      NestJS
      Fastify
      Hono
      Adonis
    • Lynx dan React
    Plugins
    • JSON
    • gettext (.po)
    Rozszerzenie VS Code
    Agent
    • Serwer MCP
    • Umiejętności agenta
    Wersje
    • v8
    • v7
    • v6
    Benchmark
    • Next.js
    • TanStack
    • Vue
    • Solid
    • Svelte
    Blog
    Zadaj pytanie
    1. Documentation
    2. Next intl
    Data utworzenia:2025-10-05Ostatnia aktualizacja:2025-10-05
    Zobacz szablon aplikacji na GitHubie

    Na tej stronie dostępny jest szablon aplikacji.

    Prześlij ten dokument do swojego ulubionego asystenta AI
    ChatGPT
    Claude
    DeepSeek
    Google AI mode
    Gemini
    Perplexity
    Mistral
    Grok

    Zadaj pytanie i otrzymaj streszczenie dokumentu, odwołując się do tej strony i wybranego dostawcy AI

    Treść tej strony została przetłumaczona przy użyciu sztucznej inteligencji.

    Zobacz ostatnią wersję oryginalnej treści w języku angielskim
    Edytuj tę dokumentację

    Jeśli masz pomysł na ulepszenie tej dokumentacji, zachęcamy do przesłania pull requesta na GitHubie.

    Link do dokumentacji na GitHubie
    Kopiuj

    Kopiuj dokument Markdown do schowka

    Tłumaczenie Twojej aplikacji Next.js 15 za pomocą next-intl i Intlayer | Internacjonalizacja (i18n)

    Ten przewodnik przeprowadzi Cię przez najlepsze praktyki next-intl w aplikacji Next.js 15 (App Router) oraz pokaże, jak nałożyć Intlayer, aby uzyskać solidne zarządzanie tłumaczeniami i automatyzację.

    Zobacz porównanie w next-i18next vs next-intl vs Intlayer.

    • Dla juniorów: postępuj krok po kroku, aby uzyskać działającą aplikację wielojęzyczną.
    • Dla programistów średniego szczebla: zwróć uwagę na optymalizację payloadu oraz rozdzielenie serwera i klienta.
    • Dla seniorów: zwróć uwagę na generowanie statyczne, middleware, integrację SEO oraz haki automatyzacji.

    Co omówimy:

    • Konfiguracja i struktura plików
    • Optymalizacja ładowania wiadomości
    • Użycie komponentów klienta i serwera
    • Metadane, sitemap, robots dla SEO
    • Middleware do routingu lokalizacji
    • Dodanie Intlayer na wierzch (CLI i automatyzacja)

    Skonfiguruj swoją aplikację za pomocą next-intl

    Zainstaluj zależności next-intl -

    bash
    Kopiuj kod

    Skopiuj kod do schowka

    npm install next-intl
    bash
    Kopiuj kod

    Skopiuj kod do schowka

    .├── 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

    Konfiguracja i ładowanie zawartości

    Ładuj tylko te przestrzenie nazw, których potrzebują Twoje trasy, i wczesne waliduj lokalizacje. Utrzymuj komponenty serwera synchroniczne, gdy to możliwe, i przesyłaj do klienta tylko wymagane wiadomości.

    src/i18n.ts
    Kopiuj kod

    Skopiuj kod do schowka

    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) {  // Załaduj tylko przestrzenie nazw potrzebne dla twojego layoutu/stron  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
    Kopiuj kod

    Skopiuj kod do schowka

    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;  // Ustaw aktywny język żądania dla tego renderowania po stronie serwera (RSC)  unstable_setRequestLocale(locale);  const dir = getLocaleDirection(locale);  return (    <html lang={locale} dir={dir}>      <body>{children}</body>    </html>  );}
    src/app/[locale]/about/page.tsx
    Kopiuj kod

    Skopiuj kod do schowka

    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;  // Wiadomości są ładowane po stronie serwera. Przekaż do klienta tylko to, co jest potrzebne.  const messages = await getMessages();  const clientMessages = pick(messages, ["common", "about"]);  // Tłumaczenia/formatowanie wyłącznie po stronie serwera  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>  );}

    Użycie w komponencie klienckim

    Weźmy przykład komponentu klienckiego renderującego licznik.

    Tłumaczenia (struktura powtórzona; załaduj je do wiadomości next-intl według własnego uznania)

    locales/en/about.json
    Kopiuj kod

    Skopiuj kod do schowka

    {  "counter": {    "label": "Counter",    "increment": "Increment"  }}
    locales/fr/about.json
    Kopiuj kod

    Skopiuj kod do schowka

    {  "counter": {    "label": "Compteur",    "increment": "Incrémenter"  }}

    Komponent kliencki

    src/components/ClientComponentExample.tsx
    Kopiuj kod

    Skopiuj kod do schowka

    "use client";import React, { useState } from "react";import { useTranslations, useFormatter } from "next-intl";const ClientComponentExample = () => {  // Bezpośredni zakres do zagnieżdżonego obiektu  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>  );};

    Nie zapomnij dodać komunikatu "about" w wiadomościach klienta na stronie (dołącz tylko przestrzenie nazw, których faktycznie potrzebuje twój klient).

    Użycie w komponencie serwerowym

    Ten komponent UI jest komponentem serwerowym i może być renderowany pod komponentem klienckim (strona → klient → serwer). Zachowaj synchroniczność, przekazując wcześniej obliczone ciągi znaków.

    src/components/ServerComponent.tsx
    Kopiuj kod

    Skopiuj kod do schowka

    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>  );};

    Uwagi:

    • Oblicz formattedCount po stronie serwera (np. const initialFormattedCount = format.number(0)).
    • Unikaj przekazywania funkcji lub obiektów niepodlegających serializacji do komponentów serwerowych.
    src/app/[locale]/about/layout.tsx
    Kopiuj kod

    Skopiuj kod do schowka

    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 },    },  };}// ... Reszta kodu strony
    src/app/sitemap.ts
    Kopiuj kod

    Skopiuj kod do schowka

    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
    Kopiuj kod

    Skopiuj kod do schowka

    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 do routingu lokalizacji

    Dodaj middleware do obsługi wykrywania i routingu lokalizacji:

    src/middleware.ts
    Kopiuj kod

    Skopiuj kod do schowka

    import createMiddleware from "next-intl/middleware";import { locales, defaultLocale } from "@/i18n";export default createMiddleware({  locales: [...locales],  defaultLocale,  localeDetection: true,});export const config = {  // Pomijaj API, wewnętrzne elementy Next i zasoby statyczne  matcher: ["/((?!api|_next|.*\\..*).*)"],};

    Najlepsze praktyki

    • Ustaw html lang i dir: W src/app/[locale]/layout.tsx oblicz dir za pomocą getLocaleDirection(locale) i ustaw <html lang={locale} dir={dir}>.
    • Podziel wiadomości według przestrzeni nazw: Organizuj JSON według lokalizacji i przestrzeni nazw (np. common.json, about.json).
    • Minimalizuj payload klienta: Na stronach wysyłaj do NextIntlClientProvider tylko wymagane namespace (np. pick(messages, ['common', 'about'])).
    • Preferuj strony statyczne: Eksportuj export const dynamic = 'force-static' i generuj statyczne parametry dla wszystkich locales.
    • Synchroniczne komponenty serwera: Przekazuj wcześniej obliczone łańcuchy znaków (przetłumaczone etykiety, sformatowane liczby) zamiast wywołań asynchronicznych lub funkcji nieserializowalnych.

    Implementacja Intlayer na bazie next-intl

    Zainstaluj zależności intlayer:

    bash
    Kopiuj kod

    Skopiuj kod do schowka

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

    Utwórz plik konfiguracyjny intlayer:

    intlayer.config.ts
    Kopiuj kod

    Skopiuj kod do schowka

    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: [    // Zachowaj strukturę folderów per namespace w synchronizacji z Intlayer    syncJSON({      format: "icu",      source: ({ key, locale }) => `./locales/${locale}/${key}.json`,    }),  ],};export default config;

    Dodaj skrypty do package.json:

    package.json
    Kopiuj kod

    Skopiuj kod do schowka

    {  "scripts": {    "i18n:fill": "intlayer fill",    "i18n:test": "intlayer test"  }}

    Notatki:

    • intlayer fill: używa Twojego dostawcy AI do uzupełniania brakujących tłumaczeń na podstawie skonfigurowanych lokalizacji.
    • intlayer test: sprawdza brakujące/nieprawidłowe tłumaczenia (używaj tego w CI).

    Możesz konfigurować argumenty i dostawców; zobacz Intlayer CLI.

    Dlaczego Intlayer?
    Alt+→

    Na tej stronie

      Dyskusje są anonimowe i regularnie przeglądane w celu rozwiązania typowych problemów. Podziel się pomysłami na funkcje, opinią o dokumentacji lub czymkolwiek związanym z Intlayer, wykorzystujemy te informacje do kształtowania naszej mapy drogowej i ulepszania produktu.

      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) {  // Załaduj tylko przestrzenie nazw potrzebne dla twojego layoutu/stron  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;  // Ustaw aktywny język żądania dla tego renderowania po stronie serwera (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;  // Wiadomości są ładowane po stronie serwera. Przekaż do klienta tylko to, co jest potrzebne.  const messages = await getMessages();  const clientMessages = pick(messages, ["common", "about"]);  // Tłumaczenia/formatowanie wyłącznie po stronie serwera  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": "Counter",    "increment": "Increment"  }}
      {  "counter": {    "label": "Compteur",    "increment": "Incrémenter"  }}
      "use client";import React, { useState } from "react";import { useTranslations, useFormatter } from "next-intl";const ClientComponentExample = () => {  // Bezpośredni zakres do zagnieżdżonego obiektu  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 },    },  };}// ... Reszta kodu strony
      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 = {  // Pomijaj API, wewnętrzne elementy Next i zasoby statyczne  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: [    // Zachowaj strukturę folderów per namespace w synchronizacji z Intlayer    syncJSON({      format: "icu",      source: ({ key, locale }) => `./locales/${locale}/${key}.json`,    }),  ],};export default config;
      {  "scripts": {    "i18n:fill": "intlayer fill",    "i18n:test": "intlayer test"  }}