Internationalization — next-intl bilan ko'p tilli ilova
Muammodan boshlaylik
Do'kon o'zbek tilida. Endi rus va ingliz tillari ham kerak. Talablar:
- Har bir til — o'z URL i:
/uz/katalog,/ru/katalog,/en/katalog— Google har birini alohida indekslashi uchun - Matnlar Server Component larda ham tarjima qilinsin (JavaScript siz)
- Sana, son, valyuta — har tilning o'z formatida
- Foydalanuvchi birinchi kirganda — brauzer tiliga qarab yo'naltirish
React SPA uchun react-i18next bor, lekin App Router, Server Component va middleware bilan next-intl tabiiyroq ishlaydi.
Hayotiy o'xshatma
Ko'p tilli muzey. Har bir zal (sahifa) bir xil, lekin kirishda tilni tanlaysiz — va barcha yozuvlar, audio-gid, xaritalar shu tilda. Har bir til uchun alohida kirish eshigi (/uz, /ru) — chet ellik turist darhol o'z tilidagi eshikni topadi.
Tuzilma
npm install next-intl
messages/
├── uz.json
├── ru.json
└── en.json
i18n/
├── routing.ts
├── request.ts
└── navigation.ts
middleware.ts
app/
└── [locale]/
├── layout.tsx
└── page.tsx
Barcha sahifalar app/[locale]/ ichida — til URL ning birinchi segmenti.
Tarjimalar
// messages/uz.json
{
"Bosh": {
"sarlavha": "Xush kelibsiz!",
"salom": "Salom, {ism}!"
},
"Savat": {
"soni": "{count, plural, =0 {Savat bo'sh} other {Savatda # ta mahsulot}}",
"jami": "Jami: {summa, number} so'm"
}
}
// messages/en.json
{
"Bosh": {
"sarlavha": "Welcome!",
"salom": "Hello, {ism}!"
},
"Savat": {
"soni": "{count, plural, =0 {Your cart is empty} one {# item in cart} other {# items in cart}}",
"jami": "Total: {summa, number} UZS"
}
}
ICU formati: {ism} — o'zgaruvchi, plural — ko'plik shakllari (har tilda o'z qoidalari: inglizchada one/other, ruschada one/few/many/other), number — mahalliy son formati.
Sozlash
// i18n/routing.ts
import { defineRouting } from 'next-intl/routing'
export const routing = defineRouting({
locales: ['uz', 'ru', 'en'],
defaultLocale: 'uz',
})
// i18n/request.ts
import { getRequestConfig } from 'next-intl/server'
import { hasLocale } from 'next-intl'
import { routing } from './routing'
export default getRequestConfig(async ({ requestLocale }) => {
const soralgan = await requestLocale
const locale = hasLocale(routing.locales, soralgan) ? soralgan : routing.defaultLocale
return {
locale,
messages: (await import(`../messages/${locale}.json`)).default,
}
})
// i18n/navigation.ts
import { createNavigation } from 'next-intl/navigation'
import { routing } from './routing'
export const { Link, redirect, usePathname, useRouter, getPathname } = createNavigation(routing)
// middleware.ts
import createMiddleware from 'next-intl/middleware'
import { routing } from './i18n/routing'
export default createMiddleware(routing)
export const config = {
matcher: '/((?!api|_next|_vercel|.*\\..*).*)',
}
// next.config.ts
import createNextIntlPlugin from 'next-intl/plugin'
const withNextIntl = createNextIntlPlugin('./i18n/request.ts')
export default withNextIntl({})
Middleware: /katalog → brauzer tili (Accept-Language) yoki cookie bo'yicha /uz/katalog ga yo'naltiradi.
Layout
// app/[locale]/layout.tsx
import { NextIntlClientProvider, hasLocale } from 'next-intl'
import { setRequestLocale } from 'next-intl/server'
import { notFound } from 'next/navigation'
import { routing } from '@/i18n/routing'
export function generateStaticParams() {
return routing.locales.map((locale) => ({ locale }))
}
export default async function LocaleLayout({
children,
params,
}: {
children: React.ReactNode
params: Promise<{ locale: string }>
}) {
const { locale } = await params
if (!hasLocale(routing.locales, locale)) notFound()
setRequestLocale(locale)
return (
<html lang={locale}>
<body>
<NextIntlClientProvider>{children}</NextIntlClientProvider>
</body>
</html>
)
}
<html lang={locale}>— ekran o'quvchilar va SEO uchungenerateStaticParams+setRequestLocale— sahifalar har bir til uchun statik render bo'lishi mumkin
Server Component da
// app/[locale]/page.tsx
import { getTranslations, setRequestLocale } from 'next-intl/server'
export default async function Bosh({ params }: { params: Promise<{ locale: string }> }) {
const { locale } = await params
setRequestLocale(locale)
const t = await getTranslations('Bosh')
return <h1>{t('sarlavha')}</h1>
}
Client Component da
'use client'
import { useTranslations, useFormatter } from 'next-intl'
export function SavatXulosa({ soni, summa }: { soni: number; summa: number }) {
const t = useTranslations('Savat')
const format = useFormatter()
return (
<div>
<p>{t('soni', { count: soni })}</p>
<p>{t('jami', { summa })}</p>
<time>{format.dateTime(new Date(), { dateStyle: 'long' })}</time>
</div>
)
}
Til almashtirish va havolalar
'use client'
import { useLocale } from 'next-intl'
import { usePathname, useRouter } from '@/i18n/navigation'
export function TilTanlash() {
const locale = useLocale()
const pathname = usePathname() // tilsiz yo'l: '/katalog'
const router = useRouter()
return (
<select value={locale} onChange={(e) => router.replace(pathname, { locale: e.target.value })} aria-label="Til">
<option value="uz">O'zbekcha</option>
<option value="ru">Ruscha</option>
<option value="en">English</option>
</select>
)
}
import { Link } from '@/i18n/navigation'
<Link href="/katalog">...</Link> // avtomatik /uz/katalog yoki /ru/katalog
[!WARNING] Ichki havolalar uchun
next/linkemas,@/i18n/navigationdagiLinkni ishlating. Aks holda havola joriy til prefiksini yo'qotadi va har bosishda middleware qayta yo'naltiradi.
SEO
export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }) {
const { locale } = await params
const t = await getTranslations({ locale, namespace: 'Bosh' })
return {
title: t('sarlavha'),
alternates: {
canonical: `https://dokon.uz/${locale}`,
languages: { uz: 'https://dokon.uz/uz', ru: 'https://dokon.uz/ru', en: 'https://dokon.uz/en' },
},
}
}
alternates.languages — hreflang teglari: Google har bir til versiyasini to'g'ri auditoriyaga ko'rsatadi.
[!TIP] Ma'lumotlar bazasidagi kontent (mahsulot nomi va tavsifi) ham tarjima talab qiladi — JSON fayllar faqat interfeys matnlari uchun. Mahsulotlar uchun:
nomi: { uz, ru, en }kabi maydonlar yoki alohida tarjimalar jadvali.
Tez-tez uchraydigan xatolar
1. Sahifalar app/[locale] dan tashqarida
Til segmenti yo'q — middleware yo'naltiradi, sahifa topilmaydi.
2. Tarjima kaliti faqat bitta faylda
ru.json da kalit yo'q — ekranda Savat.jami kabi kalit nomi chiqadi. TypeScript bilan kalitlarni tekshirish yoki tarjimalarni sinxronlash vositalaridan foydalaning.
3. Matnlarni bo'laklab birlashtirish
t('salom') + ism + '!' — har tilda so'z tartibi boshqa. O'zgaruvchilar bilan: t('salom', { ism }).
Xulosa
next-intl: messages/{til}.json, ICU formati (o'zgaruvchi, plural, number)
i18n/routing.ts — tillar; request.ts — tarjimalarni yuklash; navigation.ts — Link, useRouter
middleware — til bo'yicha yo'naltirish; app/[locale]/ — barcha sahifalar
Server: getTranslations + setRequestLocale; Client: useTranslations, useFormatter
<html lang={locale}>; Link — @/i18n/navigation dan
SEO: alternates.languages (hreflang)
Bazadagi kontent — alohida tarjima maydonlari
Keyingi dars
71-dars: next.config.js — redirects, rewrites, headers, env
Next.js ning asosiy konfiguratsiya fayli va eng foydali sozlamalari.