Начало работы с интернационализацией (i18n) с Intlayer и Next.js 14 с App Router

    Что такое Intlayer?

    Intlayer — это инновационная библиотека с открытым исходным кодом для интернационализации (i18n), разработанная для упрощения поддержки нескольких языков в современных веб-приложениях. Intlayer бесшовно интегрируется с последней версией Next.js 14, включая его мощный App Router. Она оптимизирована для работы с Server Components для эффективного рендеринга и полностью совместима с Turbopack (начиная с Next.js >= 15).

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

    • Легко управлять переводами с использованием декларативных словарей на уровне компонентов.
    • Динамически локализовать метаданные, маршруты и контент.
    • Получать доступ к переводам как в компонентах на стороне клиента, так и на стороне сервера.
    • Обеспечить поддержку TypeScript с автогенерируемыми типами, улучшая автозаполнение и обнаружение ошибок.
    • Использовать расширенные функции, такие как динамическое определение и переключение локали.

    Intlayer совместим с Next.js 12, 13, 14 и 15. Если вы используете Next.js Page Router, вы можете обратиться к этому руководству. Для Next.js 15 с Turbopack или без него, обратитесь к этому руководству.


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

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

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

    bash
    npm install intlayer next-intlayer
    • intlayer

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

    • next-intlayer

      Пакет, интегрирующий Intlayer с Next.js. Он предоставляет провайдеры контекста и хуки для интернационализации в Next.js. Кроме того, он включает плагин Next.js для интеграции Intlayer с Webpack или Turbopack, а также middleware для определения предпочтительной локали пользователя, управления cookies и обработки перенаправлений 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, имена cookies, расположение и расширение ваших деклараций контента, отключить логи Intlayer в консоли и многое другое. Для полного списка доступных параметров обратитесь к документации по конфигурации.

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

    Настройте ваш проект Next.js для использования Intlayer:

    next.config.mjs
    import { withIntlayer } from "next-intlayer/server";/** @type {import('next').NextConfig} */const nextConfig = {};export default withIntlayer(nextConfig);

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

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

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

    src/middleware.ts
    export { intlayerMiddleware as middleware } from "next-intlayer/middleware";export const config = {  matcher:    "/((?!api|static|assets|robots|sitemap|sw|service-worker|manifest|.*\..*|_next).*)",};

    intlayerMiddleware используется для определения предпочтительной локали пользователя и перенаправления его на соответствующий URL, как указано в конфигурации. Кроме того, он позволяет сохранять предпочтительную локаль пользователя в cookie.

    Адаптируйте параметр matcher для соответствия маршрутам вашего приложения. Для получения дополнительной информации обратитесь к документации Next.js по настройке matcher.

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

    Удалите все из RootLayout и замените следующим кодом:

    src/app/layout.tsx
    import type { PropsWithChildren, FC } from "react";import "./globals.css";const RootLayout: FC<PropsWithChildren> = ({ children }) => children;export default RootLayout;

    Оставляя компонент RootLayout пустым, вы можете установить атрибуты lang и dir для тега <html>.

    Для реализации динамической маршрутизации укажите путь для локали, добавив новый layout в вашу директорию [locale]:

    src/app/[locale]/layout.tsx
    import type { Next14LayoutIntlayer } from "next-intlayer";import { Inter } from "next/font/google";import { getHTMLTextDir } from "intlayer";const inter = Inter({ subsets: ["latin"] });const LocaleLayout: Next14LayoutIntlayer = ({  children,  params: { locale },}) => (  <html lang={locale} dir={getHTMLTextDir(locale)}>    <body className={inter.className}>{children}</body>  </html>);

    import { Inter } from "next/font/google"; import { getHTMLTextDir } from "intlayer";

    const inter = Inter({ subsets: ["latin"] });

    const LocaleLayout = ({ children, params: { locale } }) => (

    {children}

    );

    export default LocaleLayout;

    ```jsx fileName="src/app/[locale]/layout.csx" codeFormat="commonjs" const { Inter } = require("next/font/google"); const { getHTMLTextDir } = require("intlayer"); const inter = Inter({ subsets: ["latin"] }); const LocaleLayout = ({ children, params: { locale } }) => ( <html lang={locale} dir={getHTMLTextDir(locale)}> <body className={inter.className}>{children}</body> </html> ); module.exports = LocaleLayout;

    Сегмент пути [locale] используется для определения локали. Пример: /en-US/about будет относиться к en-US, а /fr/about к fr.

    Затем реализуйте функцию generateStaticParams в вашем приложении Layout.

    src/app/[locale]/layout.tsx
    export { generateStaticParams } from "next-intlayer"; // Строка для вставкиconst LocaleLayout: Next14LayoutIntlayer = ({  children,  params: { locale },}) => {  /*... Остальной код*/};export default LocaleLayout;

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

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

    Создайте и управляйте вашими декларациями контента для хранения переводов:

    src/app/[locale]/page.content.ts
    import { t, type Dictionary } from "intlayer";const pageContent = {  key: "page",  content: {    getStarted: {      main: t({        ru: "Начните с редактирования",        en: "Get started by editing",        fr: "Commencez par éditer",        es: "Comience por editar",      }),      pageLink: "src/app/page.tsx",    },  },} satisfies Dictionary;export default pageContent;

    Ваши декларации контента могут быть определены в любом месте вашего приложения, если они включены в каталог contentDir (по умолчанию, ./src) и соответствуют расширению файла декларации контента (по умолчанию, .content.{ts,tsx,js,jsx,mjs,cjs}). Для получения дополнительной информации обратитесь к документации по декларации контента.

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

    Получите доступ к словарям контента в вашем приложении:

    src/app/[locale]/page.tsx
    import { ClientComponentExample } from "@components/ClientComponentExample";import { ServerComponentExample } from "@components/ServerComponentExample";import { type Next14PageIntlayer, IntlayerClientProvider } from "next-intlayer";import { IntlayerServerProvider, useIntlayer } from "next-intlayer/server";const Page: Next14PageIntlayer = ({ params: { locale } }) => {  const content = useIntlayer("page", locale);  return (    <>      <p>        {content.getStarted.main}        <code>{content.getStarted.pageLink}</code>      </p>      <IntlayerServerProvider locale={locale}>        <IntlayerClientProvider locale={locale}>          <ServerComponentExample />          <ClientComponentExample />        </IntlayerClientProvider>      </IntlayerServerProvider>    </>  );};export default Page;
    • IntlayerClientProvider используется для предоставления локали клиентским компонентам. Он может быть размещен в любом родительском компоненте, включая layout. Однако рекомендуется размещать его в layout, так как Next.js использует общий код layout для страниц, что делает его более эффективным. Используя IntlayerClientProvider в layout, вы избегаете его повторной инициализации для каждой страницы, улучшая производительность и поддерживая единый контекст локализации во всем приложении.
    • IntlayerServerProvider используется для предоставления локали серверным дочерним элементам. Он не может быть установлен в layout.
    src/components/ClientComponentExample.tsx
    "use client";import type { FC } from "react";import { useIntlayer } from "next-intlayer";const ClientComponentExample: FC = () => {  const content = useIntlayer("client-component-example"); // Создание связанной декларации контента  return (    <div>      <h2>{content.title} </h2>      <p>{content.content}</p>    </div>  );};
    src/components/ServerComponentExample.tsx
    import type { FC } from "react";import { useIntlayer } from "next-intlayer/server";const ServerComponentExample: FC = () => {  const content = useIntlayer("server-component-example"); // Создание связанной декларации контента  return (    <div>      <h2>{content.title} </h2>      <p>{content.content}</p>    </div>  );};

    Если вы хотите использовать ваш контент в атрибуте string, таком как alt, title, href, aria-label и т.д., вы должны вызвать значение функции, например:

    jsx
    <img src={content.image.src.value} alt={content.image.value} />

    Чтобы узнать больше о хуке useIntlayer, обратитесь к документации.

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

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

    src/app/[locale]/layout.tsx or src/app/[locale]/page.tsx
    import {  type IConfigLocales,  getTranslation,  getMultilingualUrls,} from "intlayer";import type { Metadata } from "next";import type { LocalParams } from "next-intlayer";export const generateMetadata = ({  params: { locale },}: LocalParams): Metadata => {  const t = <T>(content: IConfigLocales<T>) => getTranslation(content, locale);  /**   * Генерирует объект, содержащий все URL для каждого локаля.   *   * Пример:   * ```ts   *  getMultilingualUrls('/about');   *   *  // Возвращает   *  // {   *  //   en: '/about',   *  //   fr: '/fr/about',   *  //   es: '/es/about',   *  // }   * ```   */  const multilingualUrls = getMultilingualUrls("/");  return {    title: t<string>({      ru: "Мой заголовок",      en: "My title",      fr: "Mon titre",      es: "Mi título",    }),    description: t({      ru: "Мое описание",      en: "My description",      fr: "Ma description",      es: "Mi descripción",    }),    alternates: {      canonical: "/",      languages: { ...multilingualUrls, "x-default": "/" },    },    openGraph: {      url: multilingualUrls[locale],    },  };};// ... Остальной код

    Узнайте больше об оптимизации метаданных в официальной документации Next.js.

    (Опционально) Шаг 9: Интернационализация вашего sitemap.xml и robots.txt

    Для интернационализации вашего sitemap.xml и robots.txt вы можете использовать функцию getMultilingualUrls, предоставляемую Intlayer. Эта функция позволяет генерировать мультиязычные URL для вашего sitemap.

    src/app/sitemap.ts
    import { getMultilingualUrls } from "intlayer";import type { MetadataRoute } from "next";const sitemap = (): MetadataRoute.Sitemap => [  {    url: "https://example.com",    alternates: {      languages: getMultilingualUrls("https://example.com"),    },  },  {    url: "https://example.com/login",    alternates: {      languages: getMultilingualUrls("https://example.com/login"),    },  },  {    url: "https://example.com/register",    alternates: {      languages: getMultilingualUrls("https://example.com/register"),    },  },];export default sitemap;import { getMultilingualUrls } from "intlayer";const sitemap = () => [  {    url: "https://example.com",    alternates: {      languages: getMultilingualUrls("https://example.com"),    },  },  {    url: "https://example.com/login",    alternates: {      languages: getMultilingualUrls("https://example.com/login"),    },  },  {    url: "https://example.com/register",    alternates: {      languages: getMultilingualUrls("https://example.com/register"),    },  },];export default sitemap;
    src/app/robots.ts
    import type { MetadataRoute } from "next";import { getMultilingualUrls } from "intlayer";const getAllMultilingualUrls = (urls: string[]) =>  urls.flatMap((url) => Object.values(getMultilingualUrls(url)) as string[]);const robots = (): MetadataRoute.Robots => ({  rules: {    userAgent: "*",    allow: ["/"],    disallow: getAllMultilingualUrls(["/login", "/register"]),  },  host: "https://example.com",  sitemap: `https://example.com/sitemap.xml`,});export default robots;

    Узнайте больше об оптимизации карты сайта в официальной документации Next.js. Узнайте больше об оптимизации robots.txt в официальной документации Next.js.

    (Опционально) Шаг 10: Изменение языка вашего контента

    Чтобы изменить язык вашего контента, вы можете использовать функцию setLocale, предоставляемую хуком useLocale. Эта функция позволяет установить локаль приложения и обновить контент соответствующим образом.

    src/components/LocaleSwitcher.tsx
    "use client";import {  Locales,  getHTMLTextDir,  getLocaleName,  getLocalizedUrl,} from "intlayer";import { useLocale } from "next-intlayer";import { type FC } from "react";import Link from "next/link";const LocaleSwitcher: FC = () => {  const { locale, pathWithoutLocale, availableLocales, setLocale } =    useLocale();  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={(e) => {              e.preventDefault();              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>  );};

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

    (Опционально) Шаг 11: Создание локализованного компонента ссылки

    Чтобы обеспечить, что навигация вашего приложения учитывает текущую локаль, вы можете создать пользовательский компонент 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`, чтобы добавить префикс локали к URL (например, /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 и удобство использования.

    Настройка TypeScript

    Intlayer использует расширение модулей для получения преимуществ TypeScript и усиления вашего кода.

    alt text

    alt text

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

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

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

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

    Для этого вы можете добавить следующие инструкции в ваш файл .gitignore:

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

    Дальнейшие шаги

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

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