Налаштування 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 дні.