Настройка i18n-фреймворка (next-intl) для Next.js

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

Разработка и обслуживание любых видов сайтов:

Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

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

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Настройка i18n-фреймворка (next-intl) для Next.js
Средняя
от 1 рабочего дня до 3 рабочих дней
Часто задаваемые вопросы

Наши компетенции:

Этапы разработки

Последние работы

  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    874
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    851

Настройка i18n-фреймворка (next-intl) для Next.js

next-intl — де-факто стандарт для i18n в Next.js App Router. Интегрируется с серверными компонентами (RSC), поддерживает Server Actions, работает со статической генерацией и стриминговым рендером. В отличие от конкурентов, переводы доступны в Server Components без клиентского JS.

Установка

npm install next-intl

Версия 3.x требует Next.js 13.4+ с App Router.

Структура файлов

messages/
  ru.json
  en.json
  de.json
src/
  app/
    [locale]/
      layout.tsx
      page.tsx
      catalog/
        page.tsx
  i18n.ts
  middleware.ts

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

// src/i18n.ts
import { getRequestConfig } from 'next-intl/server'

export default getRequestConfig(async ({ locale }) => ({
  messages: (await import(`../messages/${locale}.json`)).default,
  timeZone: 'Europe/Moscow',
  now: new Date(),
}))
// src/middleware.ts
import createMiddleware from 'next-intl/middleware'

export default createMiddleware({
  locales: ['ru', 'en', 'de', 'uk'],
  defaultLocale: 'ru',
  localePrefix: 'as-needed', // /ru/ опускается для defaultLocale
})

export const config = {
  matcher: ['/((?!api|_next|_vercel|.*\\..*).*)'],
}
// next.config.js
const withNextIntl = require('next-intl/plugin')('./src/i18n.ts')

module.exports = withNextIntl({
  // остальная конфигурация
})

Файлы переводов

// messages/ru.json
{
  "nav": {
    "catalog": "Каталог",
    "cart": "Корзина",
    "account": "Личный кабинет"
  },
  "catalog": {
    "title": "Каталог товаров",
    "items": "{count, plural, one {# товар} few {# товара} many {# товаров} other {# товаров}}",
    "filter": "Фильтры",
    "sort": "Сортировка",
    "sort_price_asc": "Дешевле",
    "sort_price_desc": "Дороже",
    "empty": "Товары не найдены"
  },
  "product": {
    "addToCart": "В корзину",
    "buyNow": "Купить сейчас",
    "inStock": "В наличии",
    "outOfStock": "Нет в наличии"
  }
}

Layout с провайдером

// src/app/[locale]/layout.tsx
import { NextIntlClientProvider } from 'next-intl'
import { getMessages, getTranslations } from 'next-intl/server'
import { notFound } from 'next/navigation'

const locales = ['ru', 'en', 'de', 'uk']

export async function generateStaticParams() {
  return locales.map(locale => ({ locale }))
}

export async function generateMetadata({ params: { locale } }: { params: { locale: string } }) {
  const t = await getTranslations({ locale, namespace: 'meta' })
  return {
    title: t('title'),
    description: t('description'),
  }
}

export default async function LocaleLayout({
  children,
  params: { locale },
}: {
  children: React.ReactNode
  params: { locale: string }
}) {
  if (!locales.includes(locale)) notFound()

  // Передаём сообщения в Client Components
  const messages = await getMessages()

  return (
    <html lang={locale}>
      <body>
        <NextIntlClientProvider messages={messages}>
          {children}
        </NextIntlClientProvider>
      </body>
    </html>
  )
}

Использование в Server Components

// src/app/[locale]/catalog/page.tsx
import { getTranslations } from 'next-intl/server'

export default async function CatalogPage({
  params: { locale },
  searchParams,
}: {
  params: { locale: string }
  searchParams: { page?: string }
}) {
  const t = await getTranslations('catalog')
  const products = await fetchProducts({ locale, page: Number(searchParams.page ?? 1) })

  return (
    <main>
      <h1>{t('title')}</h1>
      <p>{t('items', { count: products.total })}</p>
      {/* ... */}
    </main>
  )
}

Использование в Client Components

'use client'
import { useTranslations, useLocale, useFormatter } from 'next-intl'

function ProductCard({ product }: { product: Product }) {
  const t = useTranslations('product')
  const locale = useLocale()
  const format = useFormatter()

  return (
    <div>
      <h2>{product.title}</h2>
      <p>
        {format.number(product.price, {
          style: 'currency',
          currency: locale === 'ru' ? 'RUB' : 'USD',
          maximumFractionDigits: 0,
        })}
      </p>
      <span>{product.inStock ? t('inStock') : t('outOfStock')}</span>
      <button>{t('addToCart')}</button>
    </div>
  )
}

Локализованные маршруты (pathnames)

// src/navigation.ts
import { createLocalizedPathnamesNavigation } from 'next-intl/navigation'

export const { Link, redirect, usePathname, useRouter } =
  createLocalizedPathnamesNavigation({
    locales: ['ru', 'en', 'de'],
    pathnames: {
      '/':         '/',
      '/catalog':  { ru: '/catalog', en: '/catalog', de: '/katalog' },
      '/catalog/[slug]': {
        ru: '/catalog/[slug]',
        en: '/catalog/[slug]',
        de: '/katalog/[slug]',
      },
      '/checkout': { ru: '/checkout', en: '/checkout', de: '/kasse' },
    },
  })
// Используем локализованный Link вместо next/link
import { Link } from '@/navigation'

<Link href="/catalog">Каталог</Link>
// Для ru: /catalog
// Для de: /de/katalog

Статическая генерация с переводами

// Генерация статических страниц для всех языков
export async function generateStaticParams() {
  const products = await fetchAllProductSlugs()

  return ['ru', 'en', 'de'].flatMap(locale =>
    products.map(product => ({
      locale,
      slug: product.slugs[locale],
    }))
  )
}

TypeScript: автодополнение ключей переводов

// global.d.ts
import ru from './messages/ru.json'

declare module 'next-intl' {
  interface AppConfig {
    Messages: typeof ru
  }
}

Теперь t('nonexistent.key') — ошибка TypeScript.

Сроки

Базовая настройка next-intl с 2–3 языками — 1 день. С локализованными pathname, статической генерацией всех языковых версий и TypeScript-типизацией ключей — 2–3 дня.