IlmHamroh
JavaScript Full-stack/23-qism. Next.js34/43-dars19 daqiqa
Mundarija (34)

Next.js i18n: next-intl bilan [locale] segmenti, tarjimalar va hreflang

Qisqacha: Next.js'da til URL'da yashaydi: app/[locale]/… papkasi /haqimda (o'zbekcha) va /en/haqimda (inglizcha) ni bitta kod bilan beradi. next-intl uch qismdan iborat: routing (qaysi tillar, prefiks qanday), proxy (manzilni tilga moslash) va getTranslations (serverda tarjima xabarlari). Hamma tarjima serverda bajarilsa, brauzerga tarjima kodi umuman bormaydi va sahifalar statik qoladi. Har sahifaga canonical va hreflang beriladi — Google qaysi til qaysi manzilda ekanini biladi.

Bu darsda

  • portfolio ga ingliz tilini qo'shasiz: [locale] segmenti, routing, request.ts, tarjima fayllari.
  • localePrefix: "as-needed" va localeDetection: false nima berishini haqiqiy javoblar bilan ko'rasiz.
  • Tarjimani serverda olib, client komponentlarga prop bilan berasiz — va bu brauzer JS'iga qanday ta'sir qilishini o'lchov bilan bilasiz.
  • Har sahifaga hreflang va til bo'yicha Open Graph qo'shasiz.
  • Uchta haqiqiy tuzoqni bilasiz: <Link locale> ikki header, openGraph.locale almashishi, Server Action'da til.

Oldin bilishingiz kerak: i18n I: react-i18next, proxy.ts, Metadata API, generateStaticParams, Navigatsiya.

1. Nega bu kerak?

i18n I darsida vazifalar ni react-i18next bilan ko'p tilli qilgansiz. U SPA edi: til localStorage da, tarjimalar brauzerda yuklanadi, URL bir xil. SEO uchun bu yomon: Google /haqimda ni bir marta ko'radi — qaysi tilda?

portfolio — ochiq sayt, uni xorijiy ish beruvchi ham o'qishi mumkin. Talablar:

  • har tilning o'z manzili: /haqimda va /en/haqimda — ulashsa, o'sha tilda ochiladi;
  • o'zbekcha — prefikssiz (asosiy auditoriya), inglizcha — /en;
  • sahifalar statik qoladi (○), brauzerga ortiqcha JS ketmaydi;
  • Google ikki versiya bir-birining tarjimasi ekanini bilsin (hreflang).

Hayotiy o'xshatish: ikki tilli muzey. Har zalda bitta eksponat, lekin ikki xil yozuv taxtachasi. Kirishda — til tanlovi, va zal raqamida til belgisi: «12» — o'zbekcha yo'lak, «EN-12» — inglizcha. Eksponat (komponent kodi) bitta, taxtachalar (tarjima fayllari) ikkita.

2. Tuzilma

2.1 [locale] segmenti

Root layout endi app/[locale]/layout.tsx da — chunki <html lang> tilga bog'liq:

text
src/
├─ app/
│  ├─ [locale]/
│  │  ├─ layout.tsx          ← root layout, <html lang={til}>
│  │  ├─ not-found.tsx
│  │  ├─ error.tsx
│  │  ├─ [...yoq]/page.tsx   ← mos kelmagan yo'l → notFound()
│  │  └─ (sayt)/…            ← hamma sahifalar
│  ├─ sitemap.ts, robots.ts, manifest.ts, opengraph-image.tsx
│  ├─ blog/rss.xml/route.ts, api/…
├─ i18n/
│  ├─ routing.ts, request.ts, navigation.ts
│  ├─ metadata.ts, yol.ts, tur.d.ts
└─ proxy.ts
messages/
├─ uz.json, en.json          ← 106 kalit, ikkalasida bir xil

Tilga bog'liq bo'lmagan fayllar (sitemap, RSS, API) root'da qoldi. [...yoq] — hammasini ushlovchi segment (catch-all): /en/umuman-yoq kabi yo'llar ham til layout'i bilan 404 sahifaga tushishi uchun (next-intl hujjatidagi naqsh).

2.2 routing.ts

ts
import { defineRouting } from "next-intl/routing";

export const routing = defineRouting({
  locales: ["uz", "en"],
  defaultLocale: "uz",
  localePrefix: "as-needed",
  localeDetection: false,
});
Sozlama Ma'nosi
localePrefix: "as-needed" sukut tili prefikssiz (/haqimda), boshqasi /en/…
localeDetection: false brauzer tiliga qarab yo'naltirmaslik, NEXT_LOCALE cookie yo'q

localeDetection: false — ongli qaror. Yoqilsa, next-intl Accept-Language sarlavhasini o'qib, ingliz tilidagi brauzerni /en ga yo'naltiradi va tanlovni cookie'ga yozadi. Natija: sahifa javobi so'rovga bog'liq — cookie banner kerak bo'lishi mumkin, kesh murakkablashadi. Kanon sodda yo'lni tanladi: til faqat URL'dan, foydalanuvchi o'zi almashtiradi.

Haqiqiy natija (natijalar/34-i18n.txt): /uz/… ga kirgan odam kanonik manzilga yo'naltiriladi:

text
/uz: 307  /
/uz/haqimda: 307  /haqimda

2.3 request.ts va next/root-params

request.ts — next-intl har so'rovda qaysi til va qaysi xabarlarni ishlatishini shu yerdan oladi:

ts
import { hasLocale } from "next-intl";
import { getRequestConfig } from "next-intl/server";
import { notFound } from "next/navigation";
import * as rootParams from "next/root-params";
import { routing } from "./routing";

export default getRequestConfig(async ({ locale }) => {
  if (!locale) {
    const qiymat = await rootParams.locale();
    if (!hasLocale(routing.locales, qiymat)) notFound();
    locale = qiymat;
  }
  return {
    locale,
    messages: (await import(`../../messages/${locale}.json`)).default,
  };
});

next/root-params — Next 16.3 da qo'shilgan API: root segment parametri ([locale]) ni istalgan Server Component'dan (layout, sahifa, ichma-ich komponent) prop'siz o'qish (rootParams.locale()). Hujjatga ko'ra u client komponentda, Server Action'da va Route Handler'da ishlamaydi — «Server Action'da til» bo'limida buning oqibatini ko'ramiz. Avval next-intl'da har sahifa boshida setRequestLocale(locale) yozish kerak edi — aks holda statik render buzilardi. Endi kerak emas. hasLocale — "de" kabi noma'lum tilni ushlaydi.

next.config.ts da plagin ulanadi:

ts
import createNextIntlPlugin from "next-intl/plugin";

const withNextIntl = createNextIntlPlugin("./src/i18n/request.ts");
export default withNextIntl(nextConfig);

Statik render uchun root layout ikkala tilni build'ga beradi:

tsx
export function generateStaticParams() {
  return routing.locales.map((locale) => ({ locale }));
}

Build jadvali (tekshiruv-34.txt, ko'chirma):

text
├   /[locale]/haqimda
│ ├ ◐ /[locale]/haqimda
│ ├ ○ /uz/haqimda                                         1h      1d
│ └ ○ /en/haqimda                                         1h      1d

Ikkala til ham ○ — build'da tayyor. So'rov yo'li:

flowchart LR
  A["GET /haqimda"] --> P["proxy: slug tekshiruvi,<br/>next-intl"]
  P -->|"rewrite /uz/haqimda"| L["[locale]/layout<br/>lang=uz"]
  L --> T["getTranslations<br/>messages/uz.json"]
  B["GET /en/haqimda"] --> P
  P -->|"o'zgarishsiz"| L2["[locale]/layout<br/>lang=en"]
  L2 --> T2["messages/en.json"]

Ikkala yo'l ham build'da tayyorlangan HTML'ga tushadi — server tarjimani har so'rovda qayta bajarmaydi. Jami 63 sahifa prerender. /haqimda manzili ichkarida /uz/haqimda ga rewrite qilinadi (proxy).

3. Tarjima xabarlari

3.1 JSON fayllar

messages/uz.json va en.json — bir xil kalitlar, guruhlarga (namespace) bo'lingan:

json
{
  "Menyu": {
    "nomi": "Asosiy menyu",
    "loyihalar": "Loyihalar",
    "boshqaTil": "English"
  },
  "Loyihalar": {
    "kirish": "{soni} ta loyiha: har birida qisqa tavsif, sana va texnologiyalar."
  }
}

{soni} — ICU formatidagi o'zgaruvchi (react-i18next dagi {{soni}} ning bitta qavsli ko'rinishi). 106 kalit ikkala faylda bir xil — kanon buni skript bilan tekshirdi.

3.2 Serverda — getTranslations

tsx
export default async function LoyihalarSahifasi() {
  const t = await getTranslations("Loyihalar");
  return (
    <main id="asosiy">
      <h1>{t("sarlavha")}</h1>
      <p>{t("kirish", { soni: LOYIHALAR.length })}</p>
    </main>
  );
}

Server Component'da tarjima — oddiy funksiya chaqiruvi. Natija HTML'ga tayyor matn bo'lib tushadi; tarjima kodi ham, JSON ham brauzerga bormaydi.

Type-safe kalitlar — tur.d.ts (react-i18next dagi declare module ning next-intl versiyasi):

ts
declare module "next-intl" {
  interface AppConfig {
    Locale: (typeof routing.locales)[number];
    Messages: typeof uz;
  }
}

Endi t("Menyu.loyhalar") (xato kalit) — tsc xatosi, saytda bo'sh joy emas.

3.3 Client komponentlarga — prop bilan

Interaktiv komponentlar (TemaTanlov, TegFiltri, AloqaFormasi …) brauzerda ishlaydi. Ularda useTranslations ishlatsa bo'ladi — lekin buning uchun hamma xabarlar brauzerga yuborilishi kerak. Kanon boshqa yo'l tanladi: matnni serverda olib, prop bilan beradi:

tsx
// Server Component (SaytSarlavhasi)
<TilAlmashtirgich yorliq={t("til")} matn={t("boshqaTil")} />

Client komponent faqat ikkita satr oladi — butun lug'at emas. Bu Server va Client kompozitsiyasi qoidasining o'zi: ma'lumotni serverda tayyorlab, client'ga minimal narsa berish.

Tekshirib ko'ring: AloqaFormasi da 15 ta matn bor (yorliqlar, xatolar, tugma). Prop bilan berish noqulay emasmi? Muqobil nima?

Javob

Noqulayroq, lekin arzon: 15 satr — bir necha yuz bayt. Muqobil — NextIntlClientProvider messages={pick(messages, "Aloqa")}: faqat bitta namespace'ni brauzerga berib, formada useTranslations("Aloqa"). Bu ham to'g'ri yo'l. Lekin u next-intl'ning client kodini (IntlMessageFormat) brauzerga olib keladi — keyingi bo'limdagi o'lchov uning narxini ko'rsatadi.

4. Navigatsiya va til almashtirgich

/en/haqimda sahifasidagi menyuda <Link href="/loyihalar"> — /loyihalar ga (o'zbekchaga) olib boradi. Kerakli manzil — /en/loyihalar. Prefiksni qo'shadigan toza funksiya (src/i18n/yol.ts):

js
const DEFAULT = "uz";

function tilYoli(til, yol) {
  if (til === DEFAULT) return yol;
  return yol === "/" ? `/${til}` : `/${til}${yol}`;
}

console.log(tilYoli("uz", "/loyihalar")); // /loyihalar
console.log(tilYoli("en", "/loyihalar")); // /en/loyihalar
console.log(tilYoli("en", "/")); // /en
console.log(tilYoli("uz", "/")); // /

/ uchun alohida shart: /en/ emas, /en (oxirida slash'siz — canonical bilan bir xil).

Holat 34 da kanon next-intl'ning tayyor yechimini ishlatdi — createNavigation(routing): u Link, usePathname, useRouter ning tilni biladigan versiyalarini beradi. Bu client komponentlar uchun next-intl konteksti talab qildi. Birinchi build'da sababi yozilmagan Error: chiqdi, dev'da esa aniq xabar:

text
No intl context found

Yechim — root layout'da <NextIntlClientProvider messages={null}>: kontekst bor, xabarlar yo'q. Narx o'lchandi:

Bosh sahifa: brauzer yuklagan JS (gzip)
  • holat 33 (bir til)146,5 KB
  • holat 34: next-intl Link+15.2161,7 KB
  • holat 38: o'z Link−12.1149,6 KB

Manba: IlmHamroh kanoni, portfolio, headless Chrome 155, next start; tekshiruv-33, tekshiruv-34, tekshiruv-38, 2026-10-21

+15.2 KB — next-intl'ning client qismi (IntlMessageFormat), garchi xabarlar null bo'lsa ham. Kanon keyinroq (Next.js performance darsida) createNavigation ni ~30 qatorli o'z Link va usePathname bilan almashtirdi: next/link + tilYoli. Tarjima (getTranslations) serverda qoldi.

Kursdagi yakuniy qaror: next-intl — faqat serverda, navigatsiya — o'zimizniki. createNavigation esa shu darsdagi tajriba bo'lib qoladi: u ishlaydi, lekin narxini o'lchab, undan voz kechdik.

O'z Link ning g'oyasini next/link o'quv modeli bilan ko'ramiz. Model sozlamasi: ih.boshla("/en", [...shablonlar]) — /en sahifasidan boshlaymiz, useParams esa [locale] ni shablondan oladi (bu Next API emas — modelning tugmasi). Modelda manzil satri yo'q — joriy yo'lni ekranga chiqaramiz:

tsx
import NextLink from "next/link";
import { ih, useParams, usePathname } from "next/navigation";
import type { ReactNode } from "react";

ih.boshla("/en", ["/[locale]/loyihalar", "/[locale]"]);

const TILLAR = ["uz", "en"];
const MATN: Record<string, Record<string, string>> = {
  uz: {
    bosh: "Bosh sahifa",
    loyihalar: "Loyihalar",
    boshqa: "English",
  },
  en: { bosh: "Home", loyihalar: "Projects", boshqa: "O'zbekcha" },
};

function tilYoli(til: string, yol: string) {
  if (til === "uz") return yol;
  return yol === "/" ? `/${til}` : `/${til}${yol}`;
}

function useTil(): string {
  const t = useParams().locale;
  return typeof t === "string" && TILLAR.includes(t) ? t : "uz";
}

// Tilni biladigan Link (kanondagi holat 38 g'oyasi)
type LinkProps = { href: string; children: ReactNode };

function Link({ href, children }: LinkProps) {
  const til = useTil();
  return <NextLink href={tilYoli(til, href)}>{children}</NextLink>;
}

// Prefikssiz yo'l: /en/loyihalar → /loyihalar
function useIchkiYol(): string {
  const yol = usePathname();
  const m = /^\/(en|uz)(\/.*)?$/.exec(yol);
  return m ? (m[2] ?? "/") : yol;
}

function Switcher() {
  const til = useTil();
  const boshqa = til === "en" ? "uz" : "en";
  const href = tilYoli(boshqa, useIchkiYol());
  return <NextLink href={href}>{MATN[til].boshqa}</NextLink>;
}

export default function App() {
  const til = useTil();
  return (
    <>
      <nav>
        <Link href="/">{MATN[til].bosh}</Link>{" "}
        <Link href="/loyihalar">{MATN[til].loyihalar}</Link>{" "}
        <Switcher />
      </nav>
      <p>Manzil: {usePathname()}</p>
      <p>Til: {til} | ichki yo'l: {useIchkiYol()}</p>
    </>
  );
}

Ekranda:

text
Bosh sahifa Loyihalar English
Manzil: /loyihalar
Til: uz | ichki yo'l: /loyihalar

Qadamma-qadam:

Qadam Manzil Menyu tili Almashtirgich
ochildi /en Home, Projects O'zbekcha → /
«Projects» /en/loyihalar inglizcha O'zbekcha → /loyihalar
«O'zbekcha» /loyihalar o'zbekcha English → /en/loyihalar

Almashtirgich o'sha sahifaning boshqa tildagi nusxasiga olib bordi — bosh sahifaga emas. useTil dagi TILLAR.includes tekshiruvi — hasLocale ning kichik nusxasi: modelda /loyihalar ni /[locale] shabloni "locale = loyihalar" deb o'qiydi, tekshiruv uni sukut tiliga qaytaradi. Haqiqiy Next'da proxy /loyihalar ni /uz/loyihalar ga rewrite qiladi va locale doim to'g'ri.

Modelda almashtirgich — <Link>. Kanonda esa — oddiy <a href>. Nega? Holat 34 ning oraliq variantida almashtirgich next-intl'ning <Link locale="en"> edi. Client navigatsiyada root layout almashadi ([locale] — root segment). Next 16.4 eski daraxtni tashlamadi (natijalar/tajriba/34-link-ikki-header.txt):

text
almashgandan keyin: ["en|BODY|Aziz KarimovHomeAboutProjectsB|hidden=0","en|BODY|Aziz KarimovBosh sahifaMen haq|hidden=0"] | navigatsiya turi: /loyihalar/vazifalar
# ya'ni: hujjat yangi yuklanmagan (navigation entry eski URL), <body> da ikkala til header'i — eski daraxt tashlanmagan

<body> da ikkita header: inglizcha va o'zbekcha. Tuzatish — oddiy <a href={tilYoli(boshqa, yol)}>: til almashganda sahifa to'liq yuklanadi. Keyin (natijalar/34-header.txt):

text
almashgandan keyin: ["en|BODY|Aziz KarimovHomeAboutProjectsB|hidden=0"] | navigatsiya turi: /en/loyihalar/vazifalar

Bitta header. Til almashtirish kamdan-kam bo'ladi — to'liq yuklashning narxi sezilmaydi, ishonchlilik esa muhimroq. Kanon komponenti hrefLang va lang atributlarini ham qo'yadi — ekran o'quvchi "English" so'zini inglizcha talaffuz qiladi.

5. Metadata va SEO

5.1 hreflang

hreflang (SPA uchun SEO) — Google'ga "bu sahifaning boshqa tildagi nusxasi mana bu manzilda" deyish. Kanon yordamchisi (src/i18n/metadata.ts) har sahifa uchun canonical va tillar ro'yxatini yasaydi. Uning mantiqini toza JS'da:

js
const TILLAR = ["uz", "en"];
const DEFAULT = "uz";
const tilYoli = (til, yol) =>
  til === DEFAULT ? yol : yol === "/" ? `/${til}` : `/${til}${yol}`;

function alternativlar(joriyTil, yol) {
  const languages = {};
  for (const l of TILLAR) languages[l] = tilYoli(l, yol);
  languages["x-default"] = tilYoli(DEFAULT, yol);
  return { canonical: tilYoli(joriyTil, yol), languages };
}

console.log(alternativlar("en", "/haqimda"));

Konsolda:

text
{
  canonical: '/en/haqimda',
  languages: { uz: '/haqimda', en: '/en/haqimda', 'x-default': '/haqimda' }
}

x-default — "til mos kelmasa, shu". Kanonda til rootParams.locale() dan olinadi, sahifada esa: alternates: await alternativlar("/haqimda"). Haqiqiy natija (natijalar/34-i18n.txt, qisqartirilgan):

text
/en/haqimda 200 {"lang":"en","title":"About me — Aziz Karimov","canonical":"https://example.com/en/haqimda","hreflang":"uz=/haqimda en=/en/haqimda x-default=/haqimda","ogLocale":"en_US"}

Sitemap'da ham: 22 URL, 44 hreflang havolasi.

5.2 Tuzoq takrorlandi: openGraph.locale

Root layout openGraph: { siteName, locale: "en_US" } beradi, loyiha sahifasi esa o'z openGraph ini. Metadata API darsidagi tuzoq qaytdi: sahifaning openGraph i layout'nikini almashtirdi — inglizcha sahifada og:locale yo'qoldi. Yechim ham o'sha: OG_ASOS o'rniga endi funksiya — ogAsos(), joriy tildan og:locale ni yasaydi va har openGraph da yoyiladi:

ts
export async function ogAsos() {
  const til = await rootParams.locale();
  const locale = til === "en" ? "en_US" : "uz_UZ";
  return { siteName: "Aziz Karimov", locale };
}

5.3 Kontent tili o'zbekcha qoladi

Menyu, sarlavhalar, forma — tarjima qilindi. Loyiha va blog matni esa faqat o'zbekcha: tarjimasi — alohida katta ish. Kursdagi qaror: /en sahifalarida bu matn o'zbekcha qoladi, lekin halol belgilanadi:

  • matn bloki lang="uz" ichida — ekran o'quvchi uni o'zbekcha talaffuz qiladi (WCAG 3.1.2, "qismlar tili");
  • ustida ko'rinadigan izoh: "This text is in Uzbek.";
  • oy nomlari — oyniYoz(sana, til): Chrome Intl o'zbek lotinini to'g'ri chiqarmaydi (21-qism topilmasi), shuning uchun qo'lda jadval.

Tarjima — sizning mashqingiz (4-mashq).

6. Proxy va Server Action

6.1 Proxy: ikki vazifa bitta faylda

proxy.ts dagi slug tekshiruvi endi /en/ prefiksini ham taniydi, keyin next-intl'ning proxy funksiyasiga uzatadi:

ts
const tilYonaltir = createMiddleware(routing);
const SLUGLI = /^(?:\/(en|uz))?\/(loyihalar|blog)\/([a-z0-9-]+)(?:\/rasm)?$/;

export async function proxy(request: NextRequest) {
  const m = SLUGLI.exec(request.nextUrl.pathname);
  if (m) {
    const [, til = routing.defaultLocale, bolim, slug = ""] = m;
    const bormi = /* … LOYIHALAR yoki postlar … */ true;
    if (!bormi) {
      const ichki = new URL(`/${til}/_topilmadi`, request.url);
      return NextResponse.rewrite(ichki);
    }
  }
  return tilYonaltir(request);
}

export const config = {
  matcher: "/((?!api|_next|opengraph-image|.*\\..*).*)",
};

createMiddleware — nomi eski (next-intl hali "middleware" deydi), ishi proxy. U /haqimda ni ichkarida /uz/haqimda ga rewrite qiladi, /uz/… ni 307 bilan prefikssizga yo'naltiradi. Matcher endi "inkor" shaklida: API, _next, OG rasm va nuqtali har qanday fayl proxy'dan o'tmaydi — next/image darsidagi saboq.

404 natijalari (34-i18n.txt):

text
404 /en/loyihalar/yoq: 404 lang=en h1=Page not found
404 /blog/yoq: 404 lang=uz h1=Sahifa topilmadi
404 /en/umuman-yoq: 404 lang=en h1=Page not found

404 sahifasi ham to'g'ri tilda.

6.2 Server Action'da til

Hujjat bo'yicha next/root-params Server Action ichida ishlamaydi: action — sahifa render'i emas, alohida POST. Kanon yechimi — formada yashirin maydon:

tsx
<input type="hidden" name="til" value={til} />

Action'da u tekshiriladi (forma ma'lumoti — foydalanuvchi kiritgan narsa):

ts
const kelganTil = String(forma.get("til") ?? "");
const locale = hasLocale(routing.locales, kelganTil)
  ? kelganTil
  : routing.defaultLocale;
const tx = await getTranslations({ locale, namespace: "Xato" });

Zod xabarlari ham endi matn emas, tarjima kaliti: sxema "ismQisqa" qaytaradi, action uni tx(kalit) bilan tilga aylantiradi. Inglizcha forma natijasi:

text
en server xatolari: ["Please check the highlighted fields.","Please enter your name (at least 2 letters)","Email address is invalid","Message must be at least 10 characters"]
en yuborildi: Thank you, John! Your message was received — I usually reply within a day.

7. Portfolio qadami: ikki til va commit

7.1 Asosiy o'zgarishlar (holat 34)

Qism Fayllar
Paket next-intl 4.14.9 (--save-exact)
Sozlama src/i18n/routing.ts, request.ts, tur.d.ts, next.config.ts plagini
Tuzilma sahifalar app/[locale]/(sayt)/ ga; [locale]/layout.tsx — root layout
Matnlar messages/uz.json, en.json (106 kalit); sahifalarda getTranslations
Navigatsiya navigation.ts (createNavigation), TilAlmashtirgich — <a href>
SEO metadata.ts (alternativlar, ogAsos), sitemap'da ikki til
Proxy slug tekshiruvi + createMiddleware(routing)
Forma yashirin til, Zod — kalitlar

7.2 Tekshiruv

bash
npm run lint
npm run tip
npm run build
npm run start

Kanon o'lchovi: 63 sahifa prerender, axe 0 (9 sahifa), hydration 0; regressiya (filtr, modal, tema, forma) — holat 23 bilan bir xil. JS: / 161.7 KB gzip (+15.2 — next-intl client qismi; holat 38 da −12.1).

7.3 Commit

bash
git add -A
git commit

Commit (kanon): i18n: next-intl (uz prefikssiz, /en), root-params, tarjima xabarlari, hreflang, til almashtirgich.

8. Ko'p uchraydigan xatolar

Belgi Sabab Davosi
No intl context found next-intl client hook'i provider'siz NextIntlClientProvider yoki o'z Link
/en sahifalari ƒ bo'lib qoldi Til headers()/cookie'dan generateStaticParams + next/root-params
Menyu havolasi /en dan o'zbekchaga olib ketadi Oddiy next/link Tilni biladigan Link (tilYoli)
Til almashganda ikki header <Link> bilan root layout almashdi <a href> — to'liq yuklash
og:locale yo'q Sahifa openGraph i almashtirdi ...(await ogAsos())
Forma xatolari doim o'zbekcha Action'da root-params yo'q Yashirin til + hasLocale
Bitta tilda kalit yo'q — bo'sh joy JSON fayllar farq qiladi AppConfig turlari + kalit tekshiruvi skripti
/en/umuman-yoq — tilsiz 404 Hammasini ushlovchi segment yo'q [locale]/[...yoq]/page.tsx → notFound()

9. Mashqlar

Mashqlar kurs/mashqlar/23/34-next-intl/ da. Jonli yechimlarni «Ishga tushir» bilan sinang.

1-mashq (oson): manzillar

localePrefix: "as-needed", sukut uz:

  • O'zbekcha «Men haqimda» —
  • Inglizcha «Men haqimda» —
  • /uz/haqimda ga kirilsa — 307 bilan
  • Inglizcha bosh sahifa —
Yechim
  • /haqimda — sukut tili prefikssiz.
  • /en/haqimda — boshqa til prefiks bilan.
  • /haqimda — bir sahifaning ikki manzili bo'lmasligi uchun next-intl kanonik manzilga yo'naltiradi.
  • /en — /en/ emas: oxirida slash yo'q, canonical bilan bir xil.

2-mashq (o'rta): rus tili

tilYoli va alternativlar ni uch tilga kengaytiring: ["uz", "ru", "en"]. /loyihalar uchun rus tilidagi sahifaning alternativlarini chiqaring. Keyin bitta qo'shimcha: x-default ingliz tili bo'lsin (xorijiy auditoriya uchun) — natija qanday o'zgaradi?

Yechim
js
const TILLAR = ["uz", "ru", "en"];
const DEFAULT = "uz";
const tilYoli = (til, yol) =>
  til === DEFAULT ? yol : yol === "/" ? `/${til}` : `/${til}${yol}`;

function alternativlar(joriyTil, yol, xDefault = DEFAULT) {
  const languages = {};
  for (const l of TILLAR) languages[l] = tilYoli(l, yol);
  languages["x-default"] = tilYoli(xDefault, yol);
  return { canonical: tilYoli(joriyTil, yol), languages };
}

console.log(alternativlar("ru", "/loyihalar"));
const enSukut = alternativlar("ru", "/loyihalar", "en");
console.log(enSukut.languages["x-default"]);

Konsolda:

text
{
  canonical: '/ru/loyihalar',
  languages: {
    uz: '/loyihalar',
    ru: '/ru/loyihalar',
    en: '/en/loyihalar',
    'x-default': '/loyihalar'
  }
}
/en/loyihalar

Yangi til — TILLAR ga bitta qator va bitta JSON fayl; funksiyalar o'zgarmaydi. x-default — sukut tilidan alohida qaror: sayt o'zbekcha prefikssiz bo'lishi mumkin, lekin tilni bilmaydigan mehmon uchun inglizcha yaxshiroq bo'lishi mumkin. Kanonda ikkalasi uz.

3-mashq (qiyin): kalitlar mosligi

Kanon uz.json va en.json da 106 kalit bir xilligini skript bilan tekshirgan. Shunday funksiya yozing: kalitlar(obyekt) — ichma-ich obyektdan "Menyu.nomi" kabi to'liq kalitlar ro'yxati; farq(a, b) — a da bor, b da yo'q kalitlar. Ikki kichik lug'at bilan sinang. Rekursiya — rekursiya asoslari.

Yechim
js
function kalitlar(obyekt, oldi = "") {
  const natija = [];
  for (const [k, v] of Object.entries(obyekt)) {
    const tolik = oldi ? `${oldi}.${k}` : k;
    if (typeof v === "object" && v !== null) {
      natija.push(...kalitlar(v, tolik));
    } else {
      natija.push(tolik);
    }
  }
  return natija;
}

function farq(a, b) {
  const bor = new Set(kalitlar(b));
  return kalitlar(a).filter((k) => !bor.has(k));
}

const uz = {
  Menyu: { nomi: "Asosiy menyu", blog: "Blog", aloqa: "Aloqa" },
  Sayt: { ism: "Aziz Karimov" },
};
const en = {
  Menyu: { nomi: "Main menu", blog: "Blog" },
  Sayt: { ism: "Aziz Karimov", kasb: "frontend developer" },
};

console.log("uz da bor, en da yo'q:", farq(uz, en));
console.log("en da bor, uz da yo'q:", farq(en, uz));
console.log("uz kalitlari:", kalitlar(uz).length);

Konsolda:

text
uz da bor, en da yo'q: [ 'Menyu.aloqa' ]
en da bor, uz da yo'q: [ 'Sayt.kasb' ]
uz kalitlari: 4

Ikki yo'nalishda ham tekshirish kerak: bir tilda ortiqcha kalit ham xato (eskirgan yoki imlo xatoli). Bunday skript npm test ga qo'shilsa, tarjimasiz kalit CI'da ushlanadi. AppConfig turi faqat kod ishlatgan kalitlarni tekshiradi — JSON fayllar orasidagi farqni esa shu skript.

4-mashq: Portfolio qadami

portfolio ga ikki tilni qo'shing («Portfolio qadami» jadvali). Tekshiring: /en/haqimda — lang="en", inglizcha sarlavha, hreflang uchala qatori; /uz/haqimda — 307; almashtirgich o'sha sahifaga olib boradi va header bitta. Qo'shimcha (kanonda ataylab qilinmagan — sizning mashqingiz): bitta loyihaning case study matnini inglizchaga tarjima qiling va /en da lang="uz" izohini shu loyiha uchun olib tashlang.

Yechim

Kutilgan natija — «hreflang» bo'limidagi 34-i18n.txt qatoriga o'xshash. Tarjima uchun eng sodda yo'l: LOYIHALAR dagi matn maydonlarini { uz: "…", en: "…" } ko'rinishiga keltirish va sahifada loyiha.qisqa[til] ?? loyiha.qisqa.uz — tarjima bo'lmasa o'zbekcha va lang="uz". Har loyiha uchun kontentLang() mantiqini shu bilan bog'lang: tarjima bor — lang kerak emas, yo'q — lang="uz" va izoh. Commit — o'zingizniki.

10. Real ishda

  • next-intl — App Router uchun eng keng tarqalgan i18n kutubxonasi; muqobillari — Paraglide (kompilyatsiya qilinadigan, juda yengil), next-i18next (Pages Router merosi).
  • O'zbekistondagi saytlar odatda uch tilli: uz, ru, en (ba'zan o'zbek kirill ham). Prefiks strategiyasi va x-default — har loyihada alohida qaror.
  • Katta jamoalarda tarjimalar TMS'da (Crowdin, Lokalise) yuritiladi va JSON fayllar avtomatik sinxronlanadi — 3-mashqdagi tekshiruv CI'da turadi.
  • Intervyu savoli: "Next'da i18n'ni statik saqlab qanday qilasiz?" — [locale] + generateStaticParams + server tarjimasi; "hreflang nima?"; "Nega Accept-Language bo'yicha yo'naltirish SEO va keshga ta'sir qiladi?"

Xulosa

  • Til — URL'da: app/[locale]/, localePrefix: "as-needed" (uz prefikssiz, /en), localeDetection: false — cookie va yo'naltirishsiz.
  • request.ts + next/root-params — setRequestLocale siz statik render; 63 sahifa ○.
  • Tarjima serverda (getTranslations), client komponentlarga — prop; type-safe kalitlar AppConfig bilan.
  • next-intl client navigatsiyasi +15.2 KB; kanon keyin o'z Link bilan −12.1 KB qildi. <Link locale> ikki header berdi — almashtirgich <a href>.
  • hreflang va canonical har sahifada, og:locale — ogAsos() bilan; Server Action'da til — yashirin maydon + hasLocale.

Keyingi dars: Xatolar, loglar va instrumentation — server xatolarini onRequestError bilan tuzilgan JSON log qilamiz va maxfiy tafsilot brauzerga chiqmasligini tekshiramiz.

Manbalar

  • next-intl 4 hujjati: "App Router setup", "Routing" (defineRouting, localePrefix, localeDetection), "Middleware/proxy", "Navigation" (createNavigation), "Server & Client Components" — next-intl.dev/docs (2026-10-21 da o'qildi)
  • Next.js hujjati (16.4.0): next/root-params API reference — nextjs.org/docs/app/api-reference/functions/next-root-params
  • Kanon natijalari: natijalar/34-i18n.txt, natijalar/34-header.txt, natijalar/tajriba/34-link-ikki-header.txt, natijalar/tekshiruv-33.txt, tekshiruv-34.txt, natijalar/tekshiruv-38.txt
  • WCAG 2.2, 3.1.2 "Language of Parts" — w3.org/WAI/WCAG22/Understanding/language-of-parts
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
Next.js i18n: next-intl bilan [locale] segmenti, tarjimalar va hreflang — IlmHamroh