ГлавнаяПесочницаВитринаПриложениеДокументБлог
    • 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+←
    Что такое интернационализация (i18n)?
    SEO и Интернационализация
    Руководство
    • i18n с помощью next-i18next
    • i18n с помощью next-intl
    Используйте Intlayer в вашем решении
    • Автоматизировать next-i18next
    • Автоматизировать react-i18next
    • Автоматизировать next-intl
    • Автоматизировать react-intl
    • Автоматизировать vue-i18n
    Сравнения
    • next-i18next vs next-intl vs Intlayer
    • react-i18next vs react-intl vs Intlayer
    Документация
    1. Blog
    2. Next i18next vs next intl vs intlayer
    Создание:2025-08-23Последнее обновление:2025-09-29
    Ссылайтесь на этот документ на ваш любимый ассистент AI
    ChatGPT
    Claude
    DeepSeek
    Google AI mode
    Gemini
    Perplexity
    Mistral
    Grok

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

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

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

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

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

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

    next-i18next ПРОТИВ next-intl ПРОТИВ intlayer | Интернационализация (i18n) Next.js

    Давайте рассмотрим сходства и различия между тремя вариантами i18n для Next.js: next-i18next, next-intl и Intlayer.

    Это не полный учебник. Это сравнение, чтобы помочь вам сделать выбор.

    Мы сосредотачиваемся на Next.js 13+ App Router (с React Server Components) и оцениваем:

    1. Архитектура и организация контента
    2. TypeScript и безопасность
    3. Обработка отсутствующих переводов
    4. Маршрутизация и промежуточное ПО
    5. Производительность и поведение при загрузке
    6. Опыт разработчика (DX), инструменты и сопровождение
    7. SEO и масштабируемость для крупных проектов
    Кратко: Все три решения могут локализовать приложение Next.js. Если вам нужен контент, ограниченный компонентом, строгие типы TypeScript, проверки отсутствующих ключей во время сборки, дерево-шейкнутые словари и первоклассная поддержка App Router + SEO помощников, то Intlayer, самый полный и современный выбор.
    Одна из распространённых ошибок разработчиков, считать, что next-intl, это версия react-intl для Next.js. Это не так, next-intl поддерживается Amann, а react-intl поддерживается FormatJS.

    Кратко

    • next-intl, лёгкое и простое форматирование сообщений с надёжной поддержкой Next.js. Обычно используются централизованные каталоги; опыт разработчика (DX) простой, но безопасность и масштабное сопровождение в основном остаются вашей ответственностью.
    • next-i18next, i18next в обёртке Next.js. Зрелая экосистема и функции через плагины (например, ICU), но конфигурация может быть многословной, а каталоги имеют тенденцию к централизации по мере роста проектов.
    • Intlayer, компонентно-ориентированная модель контента для Next.js, строгая типизация TS, проверки во время сборки, tree-shaking, встроенные middleware и SEO-помощники, опциональный Визуальный редактор/CMS и переводы с помощью ИИ.

    Показать все данные таблицы

    Открыть таблицу в модальном окне для четкого просмотра всех данных

    Library GitHub Stars Total Commits Last Commit First Version NPM Version NPM Downloads
    aymericzip/intlayer GitHub Repo stars GitHub commit activity Last Commit April 2024 npm npm downloads
    amannn/next-intl GitHub Repo stars GitHub commit activity Last Commit Nov 2020 npm npm downloads
    i18next/i18next GitHub Repo stars GitHub commit activity Last Commit Jan 2012 npm npm downloads
    i18next/next-i18next GitHub Repo stars GitHub commit activity Last Commit Nov 2018 npm npm downloads
    Значки обновляются автоматически. Снимки будут меняться со временем.

    Сравнение функций бок о бок (с фокусом на Next.js)

    | Функция | next-intlayer (Intlayer) | next-intl | next-i18next |

    Значки обновляются автоматически. Снимки будут меняться со временем.

    Сравнение функций бок о бок (с акцентом на Next.js)

    Показать все данные таблицы

    Открыть таблицу в модальном окне для четкого просмотра всех данных

    Функция next-intlayer (Intlayer) next-intl next-i18next
    Переводы рядом с компонентами ✅ Да, контент расположен вместе с каждым компонентом ❌ Нет ❌ Нет
    Интеграция с TypeScript ✅ Продвинутая, автоматически сгенерированные строгие типы ✅ Хорошая ⚠️ Базовая
    Обнаружение отсутствующих переводов ✅ Подсветка ошибок TypeScript и ошибки/предупреждения во время сборки ⚠️ Запасной вариант во время выполнения ⚠️ Запасной вариант во время выполнения
    Богатый контент (JSX/Markdown/компоненты) ✅ Прямая поддержка ❌ Не предназначено для сложных узлов ⚠️ Ограниченно
    Перевод с использованием ИИ ✅ Да, поддерживает нескольких провайдеров ИИ. Можно использовать с собственными API-ключами. Учитывает контекст вашего приложения и объем контента ❌ Нет ❌ Нет
    Визуальный редактор ✅ Да, локальный визуальный редактор + опциональная CMS; может вынести содержимое кодовой базы; встраиваемый ❌ Нет / доступно через внешние платформы локализации ❌ Нет / доступно через внешние платформы локализации
    Локализованный роутинг ✅ Да, поддерживает локализованные пути из коробки (работает с Next.js и Vite) ✅ Встроенный, App Router поддерживает сегмент [locale] ✅ Встроенный
    Генерация динамических маршрутов ✅ Да ✅ Да ✅ Да
    Плюрализация ✅ Шаблоны на основе перечислений ✅ Хорошо ✅ Хорошо
    Форматирование (даты, числа, валюты) ✅ Оптимизированные форматтеры (Intl под капотом) ✅ Хорошо (помощники Intl) ✅ Хорошо (помощники Intl)
    Формат контента ✅ .tsx, .ts, .js, .json, .md, .txt, (.yaml в разработке) ✅ .json, .js, .ts ⚠️ .json
    Поддержка ICU ⚠️ В разработке ✅ Да ⚠️ Через плагин (i18next-icu)
    SEO помощники (hreflang, sitemap) ✅ Встроенные инструменты: помощники для sitemap, robots.txt, метаданных ✅ Хорошо ✅ Хорошо
    Экосистема / Сообщество ⚠️ Меньше, но быстро растет и активно развивается ✅ Хорошо ✅ Хорошо
    Серверный рендеринг и серверные компоненты ✅ Да, оптимизировано для SSR / React Server Components ⚠️ Поддерживается на уровне страниц, но необходимо передавать t-функции в дерево компонентов для дочерних серверных компонентов ⚠️ Поддерживается на уровне страниц, но необходимо передавать t-функции в дерево компонентов для дочерних серверных компонентов
    Tree-shaking (загрузка только используемого контента) ✅ Да, по компонентам во время сборки через плагины Babel/SWC ⚠️ Частично ⚠️ Частично
    Ленивая загрузка ✅ Да, по локалям / по словарям ✅ Да (по маршруту/по локали), требуется управление пространствами имён ✅ Да (по маршруту/по локали), требуется управление пространствами имён
    Очистка неиспользуемого контента ✅ Да, по словарю во время сборки ❌ Нет, можно управлять вручную с помощью управления пространствами имён ❌ Нет, можно управлять вручную с помощью управления пространствами имён
    Управление крупными проектами ✅ Поощряет модульность, подходит для дизайн-систем ✅ Модульное с настройкой ✅ Модульное с настройкой
    Тестирование отсутствующих переводов (CLI/CI) ✅ CLI: npx intlayer content test (аудит, удобный для CI) ⚠️ Не встроено; в документации предлагается npx @lingual/i18n-check ⚠️ Не встроено; полагаться на инструменты i18next / runtime saveMissing

    Введение

    Next.js предоставляет встроенную поддержку интернационализированной маршрутизации (например, сегменты локали). Но эта функция сама по себе не выполняет переводы. Вам все равно нужна библиотека для отображения локализованного контента вашим пользователям.

    Существует множество библиотек для i18n, но в мире Next.js сегодня три из них набирают популярность: next-i18next, next-intl и Intlayer.


    Архитектура и масштабируемость

    • next-intl / next-i18next: По умолчанию используют централизованные каталоги для каждой локали (плюс пространства имён в i18next). Хорошо работает на начальных этапах, но часто становится большой общей областью с растущей связанностью и частой сменой ключей.
    • Intlayer: Поощряет использование словарей на уровне компонентов (или функций), расположенных рядом с кодом, который они обслуживают. Это снижает когнитивную нагрузку, облегчает дублирование/миграцию частей интерфейса и уменьшает конфликты между командами. Неиспользуемый контент естественным образом легче обнаружить и удалить.

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


    Размеры бандлов и зависимости

    После сборки приложения, бандл, это JavaScript, который браузер загрузит для отображения страницы. Размер бандла, следовательно, важен для производительности приложения.

    В контексте бандла многоязычного приложения важны два компонента:

    • Код приложения
    • Контент, загружаемый браузером

    Код приложения

    Важность кода приложения в данном случае минимальна. Все три решения поддерживают tree-shaking, что означает, что неиспользуемые части кода не включаются в бандл.

    Вот сравнение размера JavaScript-бандла, загружаемого браузером для многоязычного приложения с использованием трех решений.

    Если в приложении не нужен никакой форматтер, список экспортируемых функций после tree-shaking будет следующим:

    • next-intlayer: useIntlayer, useLocale, NextIntlClientProvider, (Размер бандла 180.6 кБ -> 78.6 кБ (gzip))
    • next-intl: useTranslations, useLocale, NextIntlClientProvider, (Размер бандла 101.3 кБ -> 31.4 кБ (gzip))
    • next-i18next: useTranslation, useI18n, I18nextProvider, (Размер бандла 80.7 кБ -> 25.5 кБ (gzip))

    Эти функции являются лишь обёртками вокруг контекста/состояния React, поэтому общий вклад библиотеки i18n в размер бандла минимален.

    Intlayer немного больше, чем next-intl и next-i18next, потому что включает больше логики в функции useIntlayer. Это связано с интеграцией markdown и intlayer-editor.

    Контент и переводы

    Эта часть часто игнорируется разработчиками, но давайте рассмотрим случай приложения, состоящего из 10 страниц на 10 языках. Предположим, что каждая страница содержит 100% уникального контента для упрощения расчёта (на самом деле, много контента повторяется между страницами, например, заголовок страницы, шапка, подвал и т.д.).

    Пользователь, желающий посетить страницу /fr/about, загрузит контент одной страницы на данном языке. Игнорирование оптимизации контента означало бы ненужную загрузку 8200% ((1 + (((10 страниц - 1) × (10 языков - 1)))) × 100) контента приложения. Видите проблему? Даже если этот контент остаётся текстом, и хотя вы, вероятно, предпочитаете думать об оптимизации изображений на вашем сайте, вы отправляете бесполезный контент по всему миру и заставляете компьютеры пользователей обрабатывать его зря.

    Две важные задачи:

    • Разделение по маршруту:

      Если я нахожусь на странице /about, я не хочу загружать содержимое страницы /home
    • Разделение по локали:

      Если я нахожусь на странице /fr/about, я не хочу загружать содержимое страницы /en/about

    Опять же, все три решения осведомлены об этих проблемах и позволяют управлять этими оптимизациями. Разница между тремя решениями заключается в опыте разработчика (DX).

    next-intl и next-i18next используют централизованный подход к управлению переводами, позволяющий разделять JSON по локалям и по подфайлам. В next-i18next мы называем JSON-файлы «пространствами имён» (namespaces); next-intl позволяет объявлять сообщения. В intlayer мы называем JSON-файлы «словарями» (dictionaries).

    • В случае с next-intl, как и с next-i18next, контент загружается на уровне страницы/макета, затем этот контент загружается в провайдер контекста. Это означает, что разработчик должен вручную управлять JSON-файлами, которые будут загружены для каждой страницы.
    На практике это означает, что разработчики часто пропускают эту оптимизацию, предпочитая для простоты загружать весь контент в провайдер контекста страницы.
    • В случае с intlayer весь контент загружается в приложении. Затем плагин (@intlayer/babel / @intlayer/swc) заботится об оптимизации бандла, загружая только тот контент, который используется на странице. Следовательно, разработчику не нужно вручную управлять словарями, которые будут загружены. Это позволяет добиться лучшей оптимизации, лучшей поддерживаемости и сокращает время разработки.

    По мере роста приложения (особенно когда над приложением работают несколько разработчиков) часто забывают удалять из JSON-файлов контент, который больше не используется.

    Обратите внимание, что во всех случаях загружается весь JSON (next-intl, next-i18next, intlayer).

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

    Также важно, как библиотека обрабатывает fallback. Рассмотрим, что приложение по умолчанию на английском языке, и пользователь заходит на страницу /fr/about. Если переводы на французском отсутствуют, будет использоваться английский fallback.

    В случае с next-intl и next-i18next библиотека требует загрузки JSON, связанного с текущей локалью, а также с локалью-резервом. Таким образом, учитывая, что весь контент переведен, каждая страница загрузит 100% ненужного контента. В сравнении с этим, intlayer обрабатывает резервный вариант на этапе сборки словаря. Таким образом, каждая страница загрузит только используемый контент.

    Вот пример влияния оптимизации размера бандла с использованием intlayer в приложении на vite + react:

    Показать все данные таблицы

    Открыть таблицу в модальном окне для четкого просмотра всех данных

    Оптимизированный бандл Неоптимизированный бандл
    оптимизированный бандл неоптимизированный бандл

    TypeScript и безопасность

    next-intl

    • Надёжная поддержка TypeScript, но ключи по умолчанию не строго типизированы; вам придётся вручную поддерживать паттерны безопасности.

    next-i18next

    • Базовые типы для хуков; строгая типизация ключей требует дополнительного инструментария/конфигурации.

    intlayer

    • Генерирует строгие типы на основе вашего контента. Автодополнение в IDE и ошибки на этапе компиляции ловят опечатки и отсутствующие ключи до деплоя.

    Почему это важно: Строгая типизация сдвигает ошибки влево (CI/сборка), а не вправо (во время выполнения).


    Обработка отсутствующих переводов

    next-intl

    • Опирается на запасные варианты во время выполнения (например, показывает ключ или локаль по умолчанию). Сборка не прерывается.

    next-i18next

    • Опирается на запасные варианты во время выполнения (например, показывает ключ или локаль по умолчанию). Сборка не прерывается.

    intlayer

    • Обнаружение на этапе сборки с предупреждениями/ошибками для отсутствующих локалей или ключей.

    Почему это важно: Обнаружение пробелов во время сборки предотвращает появление «непонятных строк» в продакшене и соответствует строгим требованиям к выпуску.


    Маршрутизация, middleware и стратегия URL

    next-intl

    • Работает с локализованной маршрутизацией Next.js на App Router.

    next-i18next

    • Работает с локализованной маршрутизацией Next.js на App Router.

    intlayer

    • Всё вышеперечисленное, плюс i18n middleware (определение локали через заголовки/куки) и хелперы для генерации локализованных URL и тегов <link rel="alternate" hreflang="…">.

    Почему это важно: Меньше пользовательских прослоек; последовательный UX и чистое SEO для всех локалей.


    Совместимость с серверными компонентами (RSC)

    next-intl

    • Поддерживает Next.js 13+. Часто требует передачи t-функций/форматтеров через дерево компонентов в гибридных настройках.

    next-i18next

    • Поддерживает Next.js 13+. Аналогичные ограничения при передаче утилит перевода через границы.

    intlayer

    • Поддерживает Next.js 13+ и сглаживает границу сервер/клиент с помощью единого API и провайдеров, ориентированных на RSC, избегая передачи форматтеров или функций t.

    Почему это важно: Более чистая ментальная модель и меньше крайних случаев в гибридных деревьях.


    DX, инструменты и сопровождение

    next-intl

    • Обычно используется вместе с внешними платформами локализации и редакционными рабочими процессами.

    next-i18next

    • Обычно используется вместе с внешними платформами локализации и редакционными рабочими процессами.

    intlayer

    • Поставляется с бесплатным Визуальным Редактором и опциональной CMS (дружелюбной к Git или внешней), а также с расширением для VSCode и переводами с помощью ИИ, использующими ваши собственные ключи провайдера.

    Почему это важно: Снижает операционные затраты и сокращает цикл взаимодействия между разработчиками и авторами контента.

    Интеграция с платформами локализации (TMS)

    Крупные организации часто используют Системы Управления Переводами (TMS), такие как Crowdin, Phrase, Lokalise, Localizely или Localazy.

    • Почему это важно для компаний

      • Сотрудничество и роли: В процесс вовлечены разные участники: разработчики, менеджеры продуктов, переводчики, рецензенты, маркетинговые команды.
      • Масштаб и эффективность: непрерывная локализация, проверка в контексте.
    • next-intl / next-i18next

      • Обычно используют централизованные JSON-каталоги, поэтому экспорт/импорт с TMS является простым.
      • Зрелые экосистемы и примеры/интеграции для перечисленных платформ.
    • Intlayer

      • Поощряет использование децентрализованных словарей по компонентам и поддерживает контент в форматах TypeScript/TSX/JS/JSON/MD.
      • Это улучшает модульность кода, но может усложнить интеграцию с TMS, когда инструмент ожидает централизованные, плоские JSON-файлы.
      • Intlayer предлагает альтернативы: переводы с помощью ИИ (с использованием ваших собственных ключей провайдера), Визуальный редактор/CMS и рабочие процессы CLI/CI для обнаружения и предварительного заполнения пробелов.
    Примечание: next-intl и i18next также поддерживают каталоги на TypeScript. Если ваша команда хранит сообщения в файлах .ts или децентрализует их по функционалу, вы можете столкнуться с аналогичными трудностями при интеграции с TMS. Однако многие настройки next-intl остаются централизованными в папке locales/, что немного облегчает рефакторинг в JSON для TMS.

    Опыт разработчика

    В этой части проводится глубокое сравнение трех решений. Вместо рассмотрения простых случаев, описанных в документации "начало работы" для каждого решения, мы рассмотрим реальный кейс, более похожий на настоящий проект.

    Структура приложения

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

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

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

    .├── i18n.config.ts└── src  ├── locales  │   ├── en  │   │  ├── common.json  │   │  └── about.json  │   └── fr  │      ├── common.json  │      └── about.json  ├── app  │   ├── i18n  │   │   └── server.ts  │   └── [locale]  │       ├── layout.tsx  │       └── about.tsx  └── components      ├── I18nProvider.tsx      ├── ClientComponent.tsx      └── ServerComponent.tsx
    bash
    Копировать код

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

    .├── i18n.ts├── locales│   ├── en│   │  ├── home.json│   │  └── navbar.json│   ├── fr│   │  ├── home.json│   │  └── navbar.json│   └── es│      ├── home.json│      └── navbar.json└── src  ├── middleware.ts  ├── app  │   ├── i18n  │   │   └── server.ts  │   └── [locale]  │       └── home.tsx  └── components      └── Navbar          └── index.tsx
    bash
    Копировать код

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

    .├── intlayer.config.ts└── src  ├── middleware.ts  ├── app  │   └── [locale]  │       ├── layout.tsx  │       └── home  │           ├── index.tsx  │           └── index.content.ts  └── components      └── Navbar          ├── index.tsx          └── index.content.ts

    Сравнение

    • next-intl / next-i18next: Централизованные каталоги (JSON; пространства имён/сообщения). Чёткая структура, хорошо интегрируется с платформами перевода, но может привести к большему количеству изменений в разных файлах по мере роста приложений.
    • Intlayer: Словари .content.{ts|js|json} для каждого компонента, расположенные рядом с компонентами. Облегчает повторное использование компонентов и локальное понимание; добавляет файлы и опирается на инструменты сборки.

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

    Как упоминалось ранее, вы должны оптимизировать способ импорта каждого JSON-файла в ваш код. Важен способ, которым библиотека обрабатывает загрузку контента.

    next-i18next.config.js
    Копировать код

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

    module.exports = {i18n: {  locales: ["en", "fr", "es"],  defaultLocale: "en",},};
    src/app/_app.tsx
    Копировать код

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

    import { appWithTranslation } from "next-i18next";const MyApp = ({ Component, pageProps }) => <Component {...pageProps} />;export default appWithTranslation(MyApp);
    src/app/[locale]/about/page.tsx
    Копировать код

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

    import type { GetStaticProps } from "next";import { serverSideTranslations } from "next-i18next/serverSideTranslations";import { useTranslation } from "next-i18next";import { I18nextProvider, initReactI18next } from "react-i18next";import { createInstance } from "i18next";import { ClientComponent, ServerComponent } from "@components";export default function HomePage({ locale }: { locale: string }) {// Явно укажите пространство имён, используемое этим компонентомconst resources = await loadMessagesFor(locale); // ваш загрузчик (JSON и т.д.)const i18n = createInstance();i18n.use(initReactI18next).init({  lng: locale,  fallbackLng: "en",  resources,  ns: ["common", "about"],  defaultNS: "common",  interpolation: { escapeValue: false },});const { t } = useTranslation("about");return (  <I18nextProvider i18n={i18n}>    <main>      <h1>{t("title")}</h1>      <ClientComponent />      <ServerComponent />    </main>  </I18nextProvider>);}export const getStaticProps: GetStaticProps = async ({ locale }) => {// Предзагружайте только необходимые пространства имён для ЭТОЙ страницыreturn {  props: {    ...(await serverSideTranslations(locale ?? "en", ["common", "about"])),  },};};
    i18n.ts
    Копировать код

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

    import { getRequestConfig } from "next-intl/server";import { notFound } from "next/navigation";// Может быть импортировано из общей конфигурацииconst locales = ["en", "fr", "es"];export default getRequestConfig(async ({ locale }) => {// Проверяем, что входящий параметр `locale` является допустимымif (!locales.includes(locale)) notFound();return {  messages: (await import(`../messages/${locale}.json`)).default,};});
    src/app/[locale]/about/layout.tsx
    Копировать код

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

    import { NextIntlClientProvider } from "next-intl";import { getMessages, unstable_setRequestLocale } from "next-intl/server";import pick from "lodash/pick";export default async function LocaleLayout({children,params,}: {children: React.ReactNode;params: { locale: string };}) {const { locale } = params;// Установить активную локаль запроса для этого серверного рендера (RSC)unstable_setRequestLocale(locale);// Сообщения загружаются на стороне сервера через src/i18n/request.ts// (см. документацию next-intl). Здесь мы передаем на клиент// только подмножество, необходимое для клиентских компонентов (оптимизация нагрузки).const messages = await getMessages();const clientMessages = pick(messages, ["common", "about"]);const rtlLocales = ["ar", "he", "fa", "ur"];return (  <html lang={locale} dir={rtlLocales.includes(locale) ? "rtl" : "ltr"}>    <body>      <NextIntlClientProvider locale={locale} messages={clientMessages}>        {children}      </NextIntlClientProvider>    </body>  </html>);}
    src/app/[locale]/about/page.tsx
    Копировать код

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

    import { getTranslations } from "next-intl/server";import { ClientComponent, ServerComponent } from "@components";export default async function LandingPage({params,}: {params: { locale: string };}) {// Загрузка строго на стороне сервера (без гидратации на клиенте)const t = await getTranslations("about");return (  <main>    <h1>{t("title")}</h1>    <ClientComponent />    <ServerComponent />  </main>);}
    intlayer.config.ts
    Копировать код

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

    export default {internationalization: {  locales: ["en", "fr", "es"],  defaultLocale: "en",},};
    src/app/[locale]/layout.tsx
    Копировать код

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

    import { getHTMLTextDir } from "intlayer";import {IntlayerClientProvider,generateStaticParams,type NextLayoutIntlayer,} from "next-intlayer";export const dynamic = "force-static";const LandingLayout: NextLayoutIntlayer = async ({ children, params }) => {const { locale } = await params;return (  <html lang={locale} dir={getHTMLTextDir(locale)}>    <IntlayerClientProvider locale={locale}>      {children}    </IntlayerClientProvider>  </html>);};export default LandingLayout;
    src/app/[locale]/about/page.tsx
    Копировать код

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

    import { PageContent } from "@components/PageContent";import type { NextPageIntlayer } from "next-intlayer";import { IntlayerServerProvider, useIntlayer } from "next-intlayer/server";import { ClientComponent, ServerComponent } from "@components";const LandingPage: NextPageIntlayer = async ({ params }) => {const { locale } = await params;const { title } = useIntlayer("about", locale);return (  <IntlayerServerProvider locale={locale}>    <main>      <h1>{title}</h1>      <ClientComponent />      <ServerComponent />    </main>  </IntlayerServerProvider>);};export default LandingPage;

    Сравнение

    Все три решения поддерживают загрузку контента и провайдеры для каждой локали.

    • С next-intl/next-i18next вы обычно загружаете выбранные сообщения/пространства имён для каждого маршрута и размещаете провайдеры там, где это необходимо.

    • С Intlayer добавляется анализ во время сборки для определения использования, что может сократить ручное подключение и позволить использовать один корневой провайдер.

    Выбирайте между явным контролем и автоматизацией в зависимости от предпочтений вашей команды.

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

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

    Переводы (должны быть настоящим JSON в public/locales/...)

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

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

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

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

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

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

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

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

    "use client";import React, { useMemo, useState } from "react";import { useTranslation } from "next-i18next";const ClientComponentExample = () => {const { t, i18n } = useTranslation("about");const [count, setCount] = useState(0);// next-i18next не предоставляет useNumber; используйте Intl.NumberFormatconst numberFormat = new Intl.NumberFormat(i18n.language);return (  <div>    <p>{numberFormat.format(count)}</p>    <button      aria-label={t("counter.label")}      onClick={() => setCount((count) => count + 1)}    >      {t("counter.increment")}    </button>  </div>);};

    Не забудьте добавить пространство имён "about" на странице serverSideTranslations
    Здесь используется версия React 19.x.x, но для более низких версий вам потребуется использовать useMemo для хранения экземпляра форматтера, так как это тяжёлая функция

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

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

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

    {"counter": {  "label": "Counter",  "increment": "Increment"}}
    locales/fr/about.json
    Копировать код

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

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

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

    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" в клиентские сообщения страницы

    Содержание

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

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

    import { t, type Dictionary } from "intlayer";const counterContent = {key: "counter",content: {  label: t({ ru: "Счётчик", en: "Counter", fr: "Compteur" }),  increment: t({ ru: "Увеличить", en: "Increment", fr: "Incrémenter" }),},} satisfies Dictionary;export default counterContent;

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

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

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

    "use client";import React, { useState } from "react";import { useNumber, useIntlayer } from "next-intlayer";const ClientComponentExample = () => {const [count, setCount] = useState(0);const { label, increment } = useIntlayer("counter"); // возвращает строкиconst { number } = useNumber();return (  <div>    <p>{number(count)}</p>    <button aria-label={label} onClick={() => setCount((count) => count + 1)}>      {increment}    </button>  </div>);};

    Сравнение

    • Форматирование чисел

      • next-i18next: нет useNumber; используется Intl.NumberFormat (или i18next-icu).
      • next-intl: useFormatter().number(value).
      • Intlayer: встроенный useNumber().
    • Ключи

      • Сохраняйте вложенную структуру (about.counter.label) и используйте хук с соответствующей областью (useTranslation("about") + t("counter.label") или useTranslations("about.counter") + t("label")).
    • Расположение файлов

      • next-i18next ожидает JSON в public/locales/{lng}/{ns}.json.
      • next-intl гибок; загружайте сообщения как настроите.
      • Intlayer хранит контент в словарях TS/JS и разрешает по ключу.

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

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

    src/pages/about.tsx
    Копировать код

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

    import type { GetStaticProps } from "next";import { useTranslation } from "next-i18next";type ServerComponentProps = {count: number;};const ServerComponent = ({ count }: ServerComponentProps) => {const { t, i18n } = useTranslation("about");const formatted = new Intl.NumberFormat(i18n.language).format(count);return (  <div>    <p>{formatted}</p>    <button aria-label={t("counter.label")}>{t("counter.increment")}</button>  </div>);};
    src/components/ServerComponent.tsx
    Копировать код

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

    type ServerComponentProps = {count: number;t: (key: string) => string;formatter: Intl.NumberFormat;};const ServerComponent = ({ t, count, formatter }: ServerComponentProps) => {const formatted = formatter.format(count);return (  <div>    <p>{formatted}</p>    <button aria-label={t("label")}>{t("increment")}</button>  </div>);};

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

    • const t = await getTranslations("about.counter");
    • const formatter = await getFormatter().then((formatter) => formatter.number());
    src/components/ServerComponent.tsx
    Копировать код

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

    import { useIntlayer, useNumber } from "next-intlayer/server";const ServerComponent = ({ count }: { count: number }) => {const { label, increment } = useIntlayer("counter");const { number } = useNumber();return (  <div>    <p>{number(count)}</p>    <button aria-label={label}>{increment}</button>  </div>);};
    Intlayer предоставляет безопасные для сервера хуки через next-intlayer/server. Для работы useIntlayer и useNumber используют синтаксис, похожий на хуки клиента, но в основе зависят от серверного контекста (IntlayerServerProvider).

    Метаданные / Sitemap / Robots

    Перевод контента, это отлично. Но часто забывают, что главная цель интернационализации, сделать ваш сайт более видимым для всего мира. I18n, это невероятный рычаг для улучшения видимости вашего сайта.

    Вот список лучших практик по многоязычному SEO.

    • устанавливайте метатеги hreflang в теге <head> > Это помогает поисковым системам понять, какие языки доступны на странице
    • перечислите все переводы страниц в sitemap.xml, используя XML-схему http://www.w3.org/1999/xhtml >
    • не забудьте исключить страницы с префиксами из robots.txt (например, /dashboard, а также /fr/dashboard, /es/dashboard) >
    • используйте пользовательский компонент Link для перенаправления на наиболее локализованную страницу (например, на французском <a href="/fr/about">A propos</a>) >

    Разработчики часто забывают правильно ссылаться на свои страницы в разных локалях.

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

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

    export const locales = ["en", "fr"] as const;export type Locale = (typeof locales)[number];export const defaultLocale: Locale = "en";export function localizedPath(locale: string, path: string) { return locale === defaultLocale ? path : "/" + locale + path;}const ORIGIN = "https://example.com";export function abs(locale: string, path: string) { return ORIGIN + localizedPath(locale, path);}
    src/app/[locale]/about/layout.tsx
    Копировать код

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

    import type { Metadata } from "next";import { locales, defaultLocale, localizedPath } from "@/i18n.config";export async function generateMetadata({ params,}: { params: { locale: string };}): Promise<Metadata> { const { locale } = params; // Динамически импортировать правильный JSON файл const messages = (   await import("@/../public/locales/" + locale + "/about.json") ).default; const languages = Object.fromEntries(   locales.map((locale) => [locale, localizedPath(locale, "/about")]) ); return {   title: messages.title,   description: messages.description,   alternates: {     canonical: localizedPath(locale, "/about"),     languages: { ...languages, "x-default": "/about" },   }, };}export default async function AboutPage() { return <h1>О нас</h1>; // Заголовок страницы "О нас"}
    src/app/sitemap.ts
    Копировать код

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

    import type { MetadataRoute } from "next";import { locales, defaultLocale, abs } from "@/i18n.config";export default function sitemap(): MetadataRoute.Sitemap { const languages = Object.fromEntries(   locales.map((locale) => [locale, abs(locale, "/about")]) ); return [   {     url: abs(defaultLocale, "/about"),     lastModified: new Date(), // Дата последнего изменения     changeFrequency: "monthly", // Частота обновления: ежемесячно     priority: 0.7, // Приоритет страницы     alternates: { languages }, // Альтернативные языковые версии   }, ];}
    src/app/robots.ts
    Копировать код

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

    import type { MetadataRoute } from "next";import { locales, defaultLocale, localizedPath } from "@/i18n.config";const ORIGIN = "https://example.com";// Функция для расширения пути на все локалиconst expandAllLocales = (path: string) => [ localizedPath(defaultLocale, path), ...locales   .filter((locale) => locale !== defaultLocale)   .map((locale) => localizedPath(locale, path)),];export default function robots(): MetadataRoute.Robots { // Запрет доступа к определённым путям для всех локалей const disallow = [   ...expandAllLocales("/dashboard"),   ...expandAllLocales("/admin"), ]; return {   rules: { userAgent: "*", allow: ["/"], disallow },   host: ORIGIN,   sitemap: ORIGIN + "/sitemap.xml", };}
    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", };}
    src/app/[locale]/about/layout.tsx
    Копировать код

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

    import { getIntlayer, getMultilingualUrls } from "intlayer";import type { Metadata } from "next";import type { LocalPromiseParams } from "next-intlayer";export const generateMetadata = async ({ params,}: LocalPromiseParams): Promise<Metadata> => { const { locale } = await params; const metadata = getIntlayer("page-metadata", locale); const multilingualUrls = getMultilingualUrls("/about"); return {   ...metadata,   alternates: {     canonical: multilingualUrls[locale as keyof typeof multilingualUrls],     languages: { ...multilingualUrls, "x-default": "/about" },   }, };};// ... Остальная часть кода страницы
    src/app/sitemap.ts
    Копировать код

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

    import { getMultilingualUrls } from "intlayer";import type { MetadataRoute } from "next";const sitemap = (): MetadataRoute.Sitemap => [ {   url: "https://example.com/about",   alternates: {     languages: { ...getMultilingualUrls("https://example.com/about") },   }, },];
    src/app/robots.ts
    Копировать код

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

    import { getMultilingualUrls } from "intlayer";import type { MetadataRoute } from "next";// Получить все многоязычные URL из массива URLconst getAllMultilingualUrls = (urls: string[]) => urls.flatMap((url) => Object.values(getMultilingualUrls(url)) as string[]);// Конфигурация для robots.txt с правилами доступаconst robots = (): MetadataRoute.Robots => ({ rules: {   userAgent: "*",   allow: ["/"],   disallow: getAllMultilingualUrls(["/dashboard"]), }, host: "https://example.com", sitemap: "https://example.com/sitemap.xml",});export default robots;
    Intlayer предоставляет функцию getMultilingualUrls для генерации многоязычных URL для вашей карты сайта.

    И победитель…

    Это не просто. У каждого варианта есть свои компромиссы. Вот как я это вижу:

    next-intl

    • самый простой, легковесный, с меньшим количеством навязанных решений. Если вы хотите минимальное решение, вам комфортно с централизованными каталогами, и ваше приложение маленькое или среднего размера.

    next-i18next

    • зрелый, полнофункциональный, с множеством плагинов сообщества, но с более высокой стоимостью настройки. Если вам нужна экосистема плагинов i18next (например, расширенные правила ICU через плагины) и ваша команда уже знакома с i18next, принимая больше конфигураций ради гибкости.

    Intlayer

    • создан для современного Next.js, с модульным контентом, типобезопасностью, инструментами и меньшим количеством шаблонного кода. Если вы цените контент, ограниченный компонентом, строгий TypeScript, гарантии на этапе сборки, tree-shaking и встроенные маршрутизацию/SEO/редакторские инструменты, особенно для Next.js App Router, дизайн-систем и больших, модульных кодовых баз.

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

    Альтернатива для корпоративных команд: Если вам нужно проверенное решение, которое отлично работает с устоявшимися платформами локализации, такими как Crowdin, Phrase или другими профессиональными системами управления переводами, рассмотрите next-intl или next-i18next за их зрелую экосистему и проверенные интеграции.
    План развития: Intlayer также планирует разработать плагины, которые будут работать поверх решений i18next и next-intl. Это даст вам преимущества Intlayer в автоматизации, синтаксисе и управлении контентом, сохраняя при этом безопасность и стабильность, обеспечиваемые этими устоявшимися решениями в вашем коде приложения.

    GitHub ЗВЁЗДЫ

    Звёзды на GitHub являются сильным индикатором популярности проекта, доверия сообщества и его долгосрочной актуальности. Хотя они не являются прямой мерой технического качества, они отражают, сколько разработчиков считают проект полезным, следят за его развитием и, вероятно, будут его использовать. Для оценки ценности проекта звёзды помогают сравнивать популярность среди альтернатив и дают представление о росте экосистемы.

    График истории звёзд


    Заключение

    Все три библиотеки успешно справляются с основной локализацией. Разница в том, сколько работы вам придется выполнить, чтобы добиться надежной и масштабируемой настройки в современном Next.js:

    • С Intlayer модульный контент, строгий TS, безопасность на этапе сборки, tree-shaken бандлы и первоклассный App Router + инструменты SEO являются стандартом, а не рутиной.
    • Если ваша команда ценит поддерживаемость и скорость в многоязычном приложении, ориентированном на компоненты, Intlayer предлагает сегодня самый полный опыт.

    Обратитесь к документу «Почему Intlayer?» для получения дополнительной информации.

    Автоматизировать vue-i18n
    react-i18next vs react-intl vs Intlayer
    Alt+→

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

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

      .├── i18n.config.ts└── src  ├── locales  │   ├── en  │   │  ├── common.json  │   │  └── about.json  │   └── fr  │      ├── common.json  │      └── about.json  ├── app  │   ├── i18n  │   │   └── server.ts  │   └── [locale]  │       ├── layout.tsx  │       └── about.tsx  └── components      ├── I18nProvider.tsx      ├── ClientComponent.tsx      └── ServerComponent.tsx
      .├── i18n.ts├── locales│   ├── en│   │  ├── home.json│   │  └── navbar.json│   ├── fr│   │  ├── home.json│   │  └── navbar.json│   └── es│      ├── home.json│      └── navbar.json└── src  ├── middleware.ts  ├── app  │   ├── i18n  │   │   └── server.ts  │   └── [locale]  │       └── home.tsx  └── components      └── Navbar          └── index.tsx
      .├── intlayer.config.ts└── src  ├── middleware.ts  ├── app  │   └── [locale]  │       ├── layout.tsx  │       └── home  │           ├── index.tsx  │           └── index.content.ts  └── components      └── Navbar          ├── index.tsx          └── index.content.ts
      module.exports = {i18n: {  locales: ["en", "fr", "es"],  defaultLocale: "en",},};
      import { appWithTranslation } from "next-i18next";const MyApp = ({ Component, pageProps }) => <Component {...pageProps} />;export default appWithTranslation(MyApp);
      import type { GetStaticProps } from "next";import { serverSideTranslations } from "next-i18next/serverSideTranslations";import { useTranslation } from "next-i18next";import { I18nextProvider, initReactI18next } from "react-i18next";import { createInstance } from "i18next";import { ClientComponent, ServerComponent } from "@components";export default function HomePage({ locale }: { locale: string }) {// Явно укажите пространство имён, используемое этим компонентомconst resources = await loadMessagesFor(locale); // ваш загрузчик (JSON и т.д.)const i18n = createInstance();i18n.use(initReactI18next).init({  lng: locale,  fallbackLng: "en",  resources,  ns: ["common", "about"],  defaultNS: "common",  interpolation: { escapeValue: false },});const { t } = useTranslation("about");return (  <I18nextProvider i18n={i18n}>    <main>      <h1>{t("title")}</h1>      <ClientComponent />      <ServerComponent />    </main>  </I18nextProvider>);}export const getStaticProps: GetStaticProps = async ({ locale }) => {// Предзагружайте только необходимые пространства имён для ЭТОЙ страницыreturn {  props: {    ...(await serverSideTranslations(locale ?? "en", ["common", "about"])),  },};};
      import { getRequestConfig } from "next-intl/server";import { notFound } from "next/navigation";// Может быть импортировано из общей конфигурацииconst locales = ["en", "fr", "es"];export default getRequestConfig(async ({ locale }) => {// Проверяем, что входящий параметр `locale` является допустимымif (!locales.includes(locale)) notFound();return {  messages: (await import(`../messages/${locale}.json`)).default,};});
      import { NextIntlClientProvider } from "next-intl";import { getMessages, unstable_setRequestLocale } from "next-intl/server";import pick from "lodash/pick";export default async function LocaleLayout({children,params,}: {children: React.ReactNode;params: { locale: string };}) {const { locale } = params;// Установить активную локаль запроса для этого серверного рендера (RSC)unstable_setRequestLocale(locale);// Сообщения загружаются на стороне сервера через src/i18n/request.ts// (см. документацию next-intl). Здесь мы передаем на клиент// только подмножество, необходимое для клиентских компонентов (оптимизация нагрузки).const messages = await getMessages();const clientMessages = pick(messages, ["common", "about"]);const rtlLocales = ["ar", "he", "fa", "ur"];return (  <html lang={locale} dir={rtlLocales.includes(locale) ? "rtl" : "ltr"}>    <body>      <NextIntlClientProvider locale={locale} messages={clientMessages}>        {children}      </NextIntlClientProvider>    </body>  </html>);}
      import { getTranslations } from "next-intl/server";import { ClientComponent, ServerComponent } from "@components";export default async function LandingPage({params,}: {params: { locale: string };}) {// Загрузка строго на стороне сервера (без гидратации на клиенте)const t = await getTranslations("about");return (  <main>    <h1>{t("title")}</h1>    <ClientComponent />    <ServerComponent />  </main>);}
      export default {internationalization: {  locales: ["en", "fr", "es"],  defaultLocale: "en",},};
      import { getHTMLTextDir } from "intlayer";import {IntlayerClientProvider,generateStaticParams,type NextLayoutIntlayer,} from "next-intlayer";export const dynamic = "force-static";const LandingLayout: NextLayoutIntlayer = async ({ children, params }) => {const { locale } = await params;return (  <html lang={locale} dir={getHTMLTextDir(locale)}>    <IntlayerClientProvider locale={locale}>      {children}    </IntlayerClientProvider>  </html>);};export default LandingLayout;
      import { PageContent } from "@components/PageContent";import type { NextPageIntlayer } from "next-intlayer";import { IntlayerServerProvider, useIntlayer } from "next-intlayer/server";import { ClientComponent, ServerComponent } from "@components";const LandingPage: NextPageIntlayer = async ({ params }) => {const { locale } = await params;const { title } = useIntlayer("about", locale);return (  <IntlayerServerProvider locale={locale}>    <main>      <h1>{title}</h1>      <ClientComponent />      <ServerComponent />    </main>  </IntlayerServerProvider>);};export default LandingPage;
      {"counter": {  "label": "Счётчик",  "increment": "Увеличить"}}
      {"counter": {  "label": "Счётчик",  "increment": "Увеличить"}}
      "use client";import React, { useMemo, useState } from "react";import { useTranslation } from "next-i18next";const ClientComponentExample = () => {const { t, i18n } = useTranslation("about");const [count, setCount] = useState(0);// next-i18next не предоставляет useNumber; используйте Intl.NumberFormatconst numberFormat = new Intl.NumberFormat(i18n.language);return (  <div>    <p>{numberFormat.format(count)}</p>    <button      aria-label={t("counter.label")}      onClick={() => setCount((count) => count + 1)}    >      {t("counter.increment")}    </button>  </div>);};
      {"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 = () => {// Область видимости непосредственно для вложенного объекта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>);};
      import { t, type Dictionary } from "intlayer";const counterContent = {key: "counter",content: {  label: t({ ru: "Счётчик", en: "Counter", fr: "Compteur" }),  increment: t({ ru: "Увеличить", en: "Increment", fr: "Incrémenter" }),},} satisfies Dictionary;export default counterContent;
      "use client";import React, { useState } from "react";import { useNumber, useIntlayer } from "next-intlayer";const ClientComponentExample = () => {const [count, setCount] = useState(0);const { label, increment } = useIntlayer("counter"); // возвращает строкиconst { number } = useNumber();return (  <div>    <p>{number(count)}</p>    <button aria-label={label} onClick={() => setCount((count) => count + 1)}>      {increment}    </button>  </div>);};
      import type { GetStaticProps } from "next";import { useTranslation } from "next-i18next";type ServerComponentProps = {count: number;};const ServerComponent = ({ count }: ServerComponentProps) => {const { t, i18n } = useTranslation("about");const formatted = new Intl.NumberFormat(i18n.language).format(count);return (  <div>    <p>{formatted}</p>    <button aria-label={t("counter.label")}>{t("counter.increment")}</button>  </div>);};
      type ServerComponentProps = {count: number;t: (key: string) => string;formatter: Intl.NumberFormat;};const ServerComponent = ({ t, count, formatter }: ServerComponentProps) => {const formatted = formatter.format(count);return (  <div>    <p>{formatted}</p>    <button aria-label={t("label")}>{t("increment")}</button>  </div>);};
      import { useIntlayer, useNumber } from "next-intlayer/server";const ServerComponent = ({ count }: { count: number }) => {const { label, increment } = useIntlayer("counter");const { number } = useNumber();return (  <div>    <p>{number(count)}</p>    <button aria-label={label}>{increment}</button>  </div>);};
      export const locales = ["en", "fr"] as const;export type Locale = (typeof locales)[number];export const defaultLocale: Locale = "en";export function localizedPath(locale: string, path: string) { return locale === defaultLocale ? path : "/" + locale + path;}const ORIGIN = "https://example.com";export function abs(locale: string, path: string) { return ORIGIN + localizedPath(locale, path);}
      import type { Metadata } from "next";import { locales, defaultLocale, localizedPath } from "@/i18n.config";export async function generateMetadata({ params,}: { params: { locale: string };}): Promise<Metadata> { const { locale } = params; // Динамически импортировать правильный JSON файл const messages = (   await import("@/../public/locales/" + locale + "/about.json") ).default; const languages = Object.fromEntries(   locales.map((locale) => [locale, localizedPath(locale, "/about")]) ); return {   title: messages.title,   description: messages.description,   alternates: {     canonical: localizedPath(locale, "/about"),     languages: { ...languages, "x-default": "/about" },   }, };}export default async function AboutPage() { return <h1>О нас</h1>; // Заголовок страницы "О нас"}
      import type { MetadataRoute } from "next";import { locales, defaultLocale, abs } from "@/i18n.config";export default function sitemap(): MetadataRoute.Sitemap { const languages = Object.fromEntries(   locales.map((locale) => [locale, abs(locale, "/about")]) ); return [   {     url: abs(defaultLocale, "/about"),     lastModified: new Date(), // Дата последнего изменения     changeFrequency: "monthly", // Частота обновления: ежемесячно     priority: 0.7, // Приоритет страницы     alternates: { languages }, // Альтернативные языковые версии   }, ];}
      import type { MetadataRoute } from "next";import { locales, defaultLocale, localizedPath } from "@/i18n.config";const ORIGIN = "https://example.com";// Функция для расширения пути на все локалиconst expandAllLocales = (path: string) => [ localizedPath(defaultLocale, path), ...locales   .filter((locale) => locale !== defaultLocale)   .map((locale) => localizedPath(locale, path)),];export default function robots(): MetadataRoute.Robots { // Запрет доступа к определённым путям для всех локалей const disallow = [   ...expandAllLocales("/dashboard"),   ...expandAllLocales("/admin"), ]; return {   rules: { userAgent: "*", allow: ["/"], disallow },   host: ORIGIN,   sitemap: ORIGIN + "/sitemap.xml", };}
      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 { getIntlayer, getMultilingualUrls } from "intlayer";import type { Metadata } from "next";import type { LocalPromiseParams } from "next-intlayer";export const generateMetadata = async ({ params,}: LocalPromiseParams): Promise<Metadata> => { const { locale } = await params; const metadata = getIntlayer("page-metadata", locale); const multilingualUrls = getMultilingualUrls("/about"); return {   ...metadata,   alternates: {     canonical: multilingualUrls[locale as keyof typeof multilingualUrls],     languages: { ...multilingualUrls, "x-default": "/about" },   }, };};// ... Остальная часть кода страницы
      import { getMultilingualUrls } from "intlayer";import type { MetadataRoute } from "next";const sitemap = (): MetadataRoute.Sitemap => [ {   url: "https://example.com/about",   alternates: {     languages: { ...getMultilingualUrls("https://example.com/about") },   }, },];
      import { getMultilingualUrls } from "intlayer";import type { MetadataRoute } from "next";// Получить все многоязычные URL из массива URLconst getAllMultilingualUrls = (urls: string[]) => urls.flatMap((url) => Object.values(getMultilingualUrls(url)) as string[]);// Конфигурация для robots.txt с правилами доступаconst robots = (): MetadataRoute.Robots => ({ rules: {   userAgent: "*",   allow: ["/"],   disallow: getAllMultilingualUrls(["/dashboard"]), }, host: "https://example.com", sitemap: "https://example.com/sitemap.xml",});export default robots;