Creation:2024-12-07Last update:2026-05-06

    Переведите ваш Next.js and Page Router с Intlayer | Интернационализация (i18n)

    ide.intlayer.org

    Что такое Intlayer?

    Intlayer - это инновационная, с открытым исходным кодом библиотека интернационализации (i18n), разработанная для упрощения поддержки многоязычности в современных веб-приложениях. Intlayer бесшовно интегрируется с последними версиями фреймворка Next.js, включая его традиционный Page Router.

    С помощью Intlayer вы можете:

    • Легко управлять переводами с использованием декларативных словарей на уровне компонентов.
    • Динамически локализовать метаданные, маршруты и контент.
    • Обеспечить поддержку TypeScript с автогенерируемыми типами, улучшая автодополнение и обнаружение ошибок.
    • Воспользоваться расширенными возможностями, такими как динамическое определение и переключение локали.
    Intlayer совместим с Next.js 12, 13, 14 и 15. Если вы используете Next.js App Router, обратитесь к руководству по App Router. Для Next.js 15 следуйте этому руководству.

    Пошаговое руководство по настройке Intlayer в приложении Next.js с использованием Page Router

    Шаг 1: Установка зависимостей

    Установите необходимые пакеты с помощью предпочитаемого менеджера пакетов:

    bash
    npm install intlayer next-intlayernpx intlayer init
    • intlayer

      Основной пакет, предоставляющий инструменты интернационализации для управления конфигурацией, перевода, объявления контента, транспиляции и CLI-команд.

    • next-intlayer

      Пакет, который интегрирует Intlayer с Next.js. Он предоставляет провайдеры контекста и хуки для интернационализации в Next.js. Кроме того, включает плагин Next.js для интеграции Intlayer с Webpack или Turbopack, а также middleware для определения предпочтительного языка пользователя, управления куки и обработки перенаправлений URL.

    Шаг 2: Настройте ваш проект

    Создайте файл конфигурации, чтобы определить языки, поддерживаемые вашим приложением:

    intlayer.config.ts
    import { Locales, type IntlayerConfig } from "intlayer";
    
    const config: IntlayerConfig = {
      internationalization: {
        locales: [
          Locales.ENGLISH,
          Locales.FRENCH,
          Locales.SPANISH,
          // Добавьте здесь другие ваши локали
        ],
        defaultLocale: Locales.ENGLISH,
      },
    };
    
    export default config;
    С помощью этого файла конфигурации вы можете настроить локализованные URL-адреса, перенаправление через middleware, имена cookie, расположение и расширение ваших деклараций контента, отключить логи Intlayer в консоли и многое другое. Для полного списка доступных параметров обратитесь к документации по конфигурации.

    Шаг 3: Интеграция Intlayer с конфигурацией Next.js

    Измените конфигурацию Next.js, чтобы включить Intlayer:

    next.config.mjs
    import { withIntlayer } from "next-intlayer/server";/** @type {import('next').NextConfig} */const nextConfig = {  // Ваша существующая конфигурация Next.js};export default withIntlayer(nextConfig);
    Плагин Next.js withIntlayer() используется для интеграции Intlayer с Next.js. Он обеспечивает сборку файлов деклараций контента и их мониторинг в режиме разработки. Определяет переменные окружения Intlayer в средах Webpack или Turbopack. Кроме того, предоставляет алиасы для оптимизации производительности и гарантирует совместимость с серверными компонентами.

    Шаг 4: Настройка Middleware для определения локали

    Настройте middleware для автоматического определения и обработки предпочтительной локали пользователя:

    src/middleware.ts
    export { intlayerProxy as middleware } from "next-intlayer/middleware";
    
    export const config = {
      matcher:
        "/((?!api|static|assets|robots|sitemap|sw|service-worker|manifest|.*\\..*|_next).*)",
    };
    Адаптируйте параметр matcher так, чтобы он соответствовал маршрутам вашего приложения. Для получения дополнительной информации обратитесь к документации Next.js по настройке matcher.

    Шаг 5: Определение динамических маршрутов локализации

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

    1. Создайте страницы для конкретных локалей:

      Переименуйте основной файл страницы, добавив динамический сегмент [locale].

      bash
      mv src/pages/index.tsx src/pages/[locale]/index.tsx
    2. Обновите _app.tsx для поддержки локализации:

      Измените ваш _app.tsx, чтобы включить провайдеры Intlayer.

      src/pages/_app.tsx
      import type { FC } from "react";import type { AppProps } from "next/app";import { IntlayerClientProvider } from "next-intlayer";const App = FC<AppProps>({ Component, pageProps }) => {  const { locale } = pageProps; // извлекаем локаль из свойств страницы  return (    <IntlayerClientProvider locale={locale}>      <Component {...pageProps} />    </IntlayerClientProvider>  );}export default MyApp;
    3. Настройка getStaticPaths и getStaticProps:

      В вашем файле [locale]/index.tsx определите пути и свойства для обработки разных локалей.

      src/pages/[locale]/index.tsx
      import type { FC } from "react";import type { GetStaticPaths, GetStaticProps } from "next";import { type Locales, getConfiguration } from "intlayer";const HomePage: FC = () => <div>{/* Ваш контент здесь */}</div>;export const getStaticPaths: GetStaticPaths = () => {  const { internationalization } = getConfiguration();  const { locales } = internationalization;  const paths = locales.map((locale) => ({    params: { locale },  }));  return { paths, fallback: false };};export const getStaticProps: GetStaticProps = ({ params }) => {  const locale = params?.locale as string;  return {    props: {      locale,    },  };};export default HomePage;
    getStaticPaths и getStaticProps обеспечивают предварительную сборку необходимых страниц для всех локалей в Next.js Page Router. Такой подход снижает вычислительную нагрузку во время выполнения и улучшает пользовательский опыт. Для получения дополнительной информации обратитесь к документации Next.js по getStaticPaths и getStaticProps.

    Шаг 6: Объявите Ваш Контент

    Создайте и управляйте объявлениями контента для хранения переводов.

    src/pages/[locale]/home.content.ts
    import { t, type Dictionary } from "intlayer";
    
    const homeContent = {
      key: "home",
      content: {
        title: t({
          en: "Welcome to My Website",
          fr: "Bienvenue sur mon site Web",
          es: "Bienvenido a mi sitio web",
          ru: "Добро пожаловать на мой сайт",
        }),
        description: t({
          en: "Get started by editing this page.",
          fr: "Commencez par éditer cette page.",
          es: "Comience por editar esta página.",
          ru: "Начните с редактирования этой страницы.",
        }),
      },
    } satisfies Dictionary;
    
    export default homeContent;

    Для получения дополнительной информации о декларации контента обратитесь к руководству по декларации контента.

    Шаг 7: Использование контента в вашем коде

    Получайте доступ к словарям контента по всему вашему приложению для отображения переведённого контента.

    src/pages/[locale]/index.tsx
    import type { FC } from "react";
    import { useIntlayer } from "next-intlayer";
    import { ComponentExample } from "@components/ComponentExample";
    
    const HomePage: FC = () => {
      const content = useIntlayer("home");
    
      return (
        <div>
          <h1>{content.title}</h1>
          <p>{content.description}</p>
          <ComponentExample />
          {/* Дополнительные компоненты */}
        </div>
      );
    };
    
    // ... Остальная часть кода, включая getStaticPaths и getStaticProps
    
    export default HomePage;
    src/components/ComponentExample.tsx
    import type { FC } from "react";
    import { useIntlayer } from "next-intlayer";
    
    export const ComponentExample: FC = () => {
      const content = useIntlayer("component-example"); // Убедитесь, что у вас есть соответствующая декларация контента
    
      return (
        <div>
          <h2>{content.title}</h2>
          <p>{content.content}</p>
        </div>
      );
    };
    src/components/ComponentExample.tsx
    import type { FC } from "react";
    import { useIntlayer } from "next-intlayer";
    
    export const ComponentExample: FC = () => {
      const content = useIntlayer("component-example"); // Убедитесь, что у вас есть соответствующее объявление контента
    
      return (
        <div>
          <h2>{content.title}</h2>
          <p>{content.content}</p>
        </div>
      );
    };
    При использовании переводов в атрибутах типа string (например, alt, title, href, aria-label), вызывайте
    значение функции следующим образом:
    html
    <img src="{content.image.src.value}" alt="{content.image.value}" /><img src="{content.image.src.toString()}" alt="{content.image.toString()}" /><img src="{String(content.image.src)}" alt="{String(content.image)}" />
    Чтобы узнать больше о хуке useIntlayer, обратитесь к документации.

    (Необязательно) Шаг 8: Интернационализация ваших метаданных

    Если вы хотите интернационализировать ваши метаданные, такие как заголовок страницы, вы можете использовать функцию getStaticProps, предоставляемую маршрутизатором страниц Next.js. Внутри вы можете получить содержимое с помощью функции getIntlayer для перевода ваших метаданных.

    src/pages/[locale]/metadata.content.ts
    import { type Dictionary, t } from "intlayer";
    import { type Metadata } from "next";
    
    const metadataContent = {
      key: "page-metadata",
      content: {
        title: t({
          en: "Create Next App",
          fr: "Создать приложение Next.js",
          es: "Создать приложение Next.js",
        }),
        description: t({
          en: "Generated by create next app",
          fr: "Сгенерировано с помощью create next app",
          es: "Сгенерировано с помощью create next app",
        }),
      },
    } satisfies Dictionary<Metadata>;
    
    export default metadataContent;
    src/pages/[locale]/index.tsx
    import { GetStaticPaths, GetStaticProps } from "next";
    import { getIntlayer, getMultilingualUrls } from "intlayer";
    import { useIntlayer } from "next-intlayer";
    import Head from "next/head";
    import type { FC } from "react";
    
    interface HomePageProps {
      locale: string;
      metadata: {
        title: string;
        description: string;
      };
      multilingualUrls: Record<string, string>;
    }
    
    const HomePage: FC<HomePageProps> = ({
      metadata,
      multilingualUrls,
      locale,
    }) => {
      const content = useIntlayer("page");
    
      return (
        <div>
          <Head>
            <title>{metadata.title}</title>
            <meta name="description" content={metadata.description} />
            {/* Генерация тегов hreflang для SEO */}
            {Object.entries(multilingualUrls).map(([lang, url]) => (
              <link key={lang} rel="alternate" hrefLang={lang} href={url} />
            ))}
            <link rel="canonical" href={multilingualUrls[locale]} />
          </Head>
    
          {/* Содержимое страницы */}
          <main>{/* Ваше содержимое страницы здесь */}</main>
        </div>
      );
    };
    
    export const getStaticProps: GetStaticProps<HomePageProps> = async ({
      params,
    }) => {
      const locale = params?.locale as string;
    
      const metadata = getIntlayer("page-metadata", locale);
    
      /**
       * Генерирует объект, содержащий все URL для каждого языка.
       *
       * Пример:
       * ```ts
       *  getMultilingualUrls('/about');
       *
       *  // Возвращает
       *  // {
       *  //   en: '/about',
       *  //   fr: '/fr/about',
       *  //   es: '/es/about',
       *  // }
       * ```
       */
      const multilingualUrls = getMultilingualUrls("/");
    
      return {
        props: {
          locale,
          metadata,
          multilingualUrls,
        },
      };
    };
    
    export default HomePage;
    
    // ... Остальная часть кода, включая getStaticPaths
    Обратите внимание, что функция getIntlayer, импортируемая из next-intlayer, возвращает ваш контент, обернутый в IntlayerNode, что позволяет интегрироваться с визуальным редактором. В то время как функция getIntlayer, импортируемая из intlayer, возвращает ваш контент напрямую без дополнительных свойств.

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

    src/pages/[locale]/index.tsx
    import { GetStaticPaths, GetStaticProps } from "next";
    import {
      type IConfigLocales,
      getTranslation,
      getMultilingualUrls,
    } from "intlayer";
    import { useIntlayer } from "next-intlayer";
    import Head from "next/head";
    import type { FC } from "react";
    
    interface HomePageProps {
      locale: string;
      metadata: {
        title: string;
        description: string;
      };
      multilingualUrls: Record<string, string>;
    }
    
    const HomePage: FC<HomePageProps> = ({ metadata, multilingualUrls, locale }) => {
      const content = useIntlayer("page");
    
      return (
        <div>
          <Head>
            <title>{metadata.title}</title>
            <meta name="description" content={metadata.description} />
            {/* Генерация тегов hreflang для SEO */}
            {Object.entries(multilingualUrls).map(([lang, url]) => (
              <link
                key={lang}
                rel="alternate"
                hrefLang={lang}
                href={url}
              />
            ))}
            <link rel="canonical" href={multilingualUrls[locale]} />
          </Head>
    
          {/* Содержимое страницы */}
          <main>
            {/* Ваше содержимое страницы здесь */}
          </main>
        </div>
      );
    };
    
    export const getStaticProps: GetStaticProps<HomePageProps> = async ({
      params
    }) => {
      const locale = params?.locale as string;
      const t = <T>(content: IConfigLocales<T>) => getTranslation(content, locale);
    
      const metadata = {
        title: t<string>({
          en: "My title",
          fr: "Mon titre",
          es: "Mi título",
        }),
        description: t({
          en: "My description",
          fr: "Ma description",
          es: "Mi descripción",
        }),
      };
    
      const multilingualUrls = getMultilingualUrls("/");
    
      return {
        props: {
          locale,
          metadata,
          multilingualUrls,
        },
      };
    };
    
    export default HomePage;
    
    // ... Остальная часть кода, включая getStaticPaths
    Узнайте больше об оптимизации метаданных в официальной документации Next.js.

    (Необязательно) Шаг 9: Изменение языка вашего контента

    Для изменения языка вашего контента в Next.js рекомендуется использовать компонент Link для перенаправления пользователей на соответствующую локализованную страницу. Компонент Link позволяет предварительно загружать страницу, что помогает избежать полной перезагрузки страницы.

    src/components/LanguageSwitcher.tsx
    import {
      Locales,
      getHTMLTextDir,
      getLocaleName,
      getLocalizedUrl,
    } from "intlayer";
    import { useLocalePageRouter } from "next-intlayer";
    import { type FC } from "react";
    import Link from "next/link";
    
    const LocaleSwitcher: FC = () => {
      const { locale, pathWithoutLocale, availableLocales } = useLocalePageRouter();
    
      return (
        <div>
          <button popoverTarget="localePopover">{getLocaleName(locale)}</button>
          <div id="localePopover" popover="auto">
            {availableLocales.map((localeItem) => (
              <Link
                href={getLocalizedUrl(pathWithoutLocale, localeItem)}
                hrefLang={localeItem}
                key={localeItem}
                aria-current={locale === localeItem ? "page" : undefined}
                onClick={() => setLocale(localeItem)}
              >
                <span>
                  {/* Локаль - например, FR */}
                  {localeItem}
                </span>
                <span>
                  {/* Язык на его собственной локали - например, Français */}
                  {getLocaleName(localeItem, locale)}
                </span>
                <span dir={getHTMLTextDir(localeItem)} lang={localeItem}>
                  {/* Язык на текущем языке локали - например, Francés при установленной локали Locales.SPANISH */}
                  {getLocaleName(localeItem)}
                </span>
                <span dir="ltr" lang={Locales.ENGLISH}>
                  {/* Язык на английском - например, French */}
                  {getLocaleName(localeItem, Locales.ENGLISH)}
                </span>
              </Link>
            ))}
          </div>
        </div>
      );
    };
    Альтернативный способ - использовать функцию setLocale, предоставляемую хуком useLocale. Эта функция не позволит предварительно загрузить страницу и приведёт к её перезагрузке.
    В этом случае, без перенаправления с помощью router.push, только ваш серверный код изменит локаль содержимого.
    src/components/LocaleSwitcher.tsx
    "use client";import { useRouter } from "next/navigation";import { useLocale } from "next-intlayer";import { getLocalizedUrl } from "intlayer";// ... Остальная часть кодаconst router = useRouter();const { setLocale } = useLocale({  onLocaleChange: (locale) => {    router.push(getLocalizedUrl(pathWithoutLocale, locale));  },});return (  <button onClick={() => setLocale(Locales.FRENCH)}>    Переключить на французский  </button>);
    API useLocalePageRouter идентичен useLocale. Чтобы узнать больше о хуке useLocale, обратитесь к документации.

    Ссылки на документацию:

    Чтобы обеспечить навигацию вашего приложения с учётом текущей локали, вы можете создать пользовательский компонент Link. Этот компонент автоматически добавляет префикс текущего языка к внутренним URL, например, когда франкоязычный пользователь нажимает на ссылку на страницу "О нас", его перенаправляет на /fr/about вместо /about.

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

    • SEO и удобство для пользователя: Локализованные URL помогают поисковым системам правильно индексировать страницы на конкретных языках и предоставляют пользователям контент на их предпочтительном языке.
    • Последовательность: Используя локализованные ссылки по всему приложению, вы гарантируете, что навигация останется в рамках текущей локали, предотвращая неожиданные переключения языка.
    • Поддерживаемость: Централизация логики локализации в одном компоненте упрощает управление URL, делая ваш код более удобным для поддержки и расширения по мере роста приложения.

    Ниже приведена реализация локализованного компонента Link на TypeScript:

    src/components/Link.tsx
    "use client";
    
    import { getLocalizedUrl } from "intlayer";
    import NextLink, { type LinkProps as NextLinkProps } from "next/link";
    import { useLocale } from "next-intlayer";
    import { forwardRef, PropsWithChildren, type ForwardedRef } from "react";
    
    /**
     * Утилита для проверки, является ли данный URL внешним.
     * Если URL начинается с http:// или https://, он считается внешним.
     */
    export const checkIsExternalLink = (href?: string): boolean =>
      /^https?:\/\//.test(href ?? "");
    
    /**
     * Кастомный компонент Link, который адаптирует атрибут href в зависимости от текущей локали.
     * Для внутренних ссылок используется `getLocalizedUrl`, чтобы добавить префикс локали (например, /fr/about).
     * Это гарантирует, что навигация останется в контексте текущей локали.
     */
    export const Link = forwardRef<
      HTMLAnchorElement,
      PropsWithChildren<NextLinkProps>
    >(({ href, children, ...props }, ref: ForwardedRef<HTMLAnchorElement>) => {
      const { locale } = useLocale();
      const isExternalLink = checkIsExternalLink(href.toString());
    
      // Если ссылка внутренняя и предоставлен действительный href, получить локализованный URL.
      const hrefI18n: NextLinkProps["href"] =
        href && !isExternalLink ? getLocalizedUrl(href.toString(), locale) : href;
    
      return (
        <NextLink href={hrefI18n} ref={ref} {...props}>
          {children}
        </NextLink>
      );
    });
    
    Link.displayName = "Link";

    Как это работает

    • Определение внешних ссылок:
      Вспомогательная функция checkIsExternalLink определяет, является ли URL внешним. Внешние ссылки остаются без изменений, так как им не требуется локализация.

    • Получение текущей локали:
      Хук useLocale предоставляет текущую локаль (например, fr для французского).

    • Локализация URL:
      Для внутренних ссылок (то есть не внешних) используется функция getLocalizedUrl, которая автоматически добавляет префикс текущей локали к URL. Это означает, что если пользователь использует французский язык, передача /about в качестве href преобразуется в /fr/about.

    • Возврат ссылки:
      Компонент возвращает элемент <a> с локализованным URL, обеспечивая согласованную навигацию в соответствии с локалью.

    Интегрируя этот компонент Link по всему вашему приложению, вы поддерживаете единый и ориентированный на язык пользовательский опыт, а также улучшаете SEO и удобство использования.

    (Необязательно) Шаг 11: Оптимизация размера бандла

    При использовании next-intlayer словари по умолчанию включаются в бандл для каждой страницы. Чтобы оптимизировать размер бандла, Intlayer предоставляет необязательный плагин SWC, который интеллектуально заменяет вызовы useIntlayer с помощью макросов. Это гарантирует, что словари включаются только в бандлы тех страниц, которые действительно их используют.

    Чтобы включить эту оптимизацию, установите пакет @intlayer/swc. После установки next-intlayer автоматически обнаружит и использует плагин:

    bash
    npm install @intlayer/swc --save-dev
    Примечание: Эта оптимизация доступна только для Next.js версии 13 и выше.
    Примечание: Этот пакет не устанавливается по умолчанию, так как плагины SWC в Next.js всё ещё находятся в экспериментальной стадии. Это может измениться в будущем.

    Настройка TypeScript

    Intlayer использует расширение модулей (module augmentation), чтобы использовать преимущества TypeScript и сделать ваш код более надёжным.

    Autocompletion

    Translation error

    Убедитесь, что ваша конфигурация TypeScript включает автоматически сгенерированные типы.

    tsconfig.json
    {  // ... Ваши существующие настройки TypeScript  "include": [    // ... Ваши существующие настройки TypeScript    ".intlayer/**/*.ts", // Включить автоматически сгенерированные типы  ],}

    Конфигурация Git

    Чтобы поддерживать ваш репозиторий в чистоте и избежать коммита сгенерированных файлов, рекомендуется игнорировать файлы, создаваемые Intlayer.

    Добавьте следующие строки в ваш файл .gitignore:

    .gitignore
    # Игнорировать файлы, сгенерированные Intlayer.intlayer

    Расширение для VS Code

    Для улучшения вашего опыта разработки с Intlayer вы можете установить официальное расширение Intlayer для VS Code.

    Установить из VS Code Marketplace

    Это расширение предоставляет:

    • Автодополнение для ключей переводов.
    • Обнаружение ошибок в реальном времени для отсутствующих переводов.
    • Встроенный просмотр переведённого контента.
    • Быстрые действия для лёгкого создания и обновления переводов.

    Для получения дополнительной информации о том, как использовать расширение, обратитесь к документации по расширению Intlayer для VS Code.

    Дополнительные ресурсы

    Следуя этому руководству, вы сможете эффективно интегрировать Intlayer в ваше приложение Next.js с использованием Page Router, обеспечивая надежную и масштабируемую поддержку интернационализации для ваших веб-проектов.

    Продвинутые возможности

    Чтобы пойти дальше, вы можете реализовать визуальный редактор или вынести ваш контент, используя CMS.