Mundarija (43)
- Bu darsda
- 1. Nega bu kerak?
- 2. Birinchi tarjima
- 2.1 Ikki paket
- 2.2 init, useTranslation, t()
- 2.3 Kalit hech qayerda yo'q bo'lsa
- 3. Interpolatsiya: matn ichidagi qiymat
- 3.1 {{ism}}
- 3.2 escapeValue nega false
- 4. Ko'plik shakllari
- 4.1 Kalit + qo'shimcha
- 4.2 Nol uchun alohida matn: _zero
- 5. Namespace'lar: tarjimalarni fayllarga bo'lish
- 5.1 Nega bitta fayl emas
- 5.2 Kalitlarni qanday nomlash
- 6. Trans: havola yoki qalin so'z bor gap
- 6.1 Muammo
- 6.2 Yechim: belgilar tarjimaning ichida
- 7. Til: qayerdan olinadi va qayerda saqlanadi
- 7.1 Til almashganda nima bo'ladi
- 7.2 Tilni aniqlash: detector
- 8. Boshqa tillarni kech yuklash
- 8.1 Hamma tilni birdaniga yuklamang
- 8.2 Jonli: til fayli kelguncha
- 9. Vazifalar qadami: ikki tilli interfeys (holat 02)
- 9.1 Branch va paketlar
- 9.2 src/i18n/index.ts
- 9.3 main.tsx: o'zbekcha — darhol, boshqasi — fayl kelgach
- 9.4 Tarjima fayllari va ko'plik
- 9.5 Til tanlovi
- 9.6 Testlar va TypeScript
- 9.7 Natija va narx
- 9.8 Commit
- 10. Hujumchi nigohi
- 11. Ko'p uchraydigan xatolar
- 12. Mashqlar
- 1-mashq (oson): bo'sh joylarni to'ldiring
- 2-mashq (o'rta): «Bahor» savatchasi ruscha
- 3-mashq (qiyin): Trans va kech keladigan til
- 4-mashq: vazifalar qadami
- 13. Real ishda
- Xulosa
- Manbalar
i18n React'da: react-i18next bilan ko'p tilli ilova — t(), namespace, ko'plik va Trans
Qisqacha: i18n — interfeys matnlarini koddan tarjima fayllariga chiqarish. Komponent matnni emas, kalitni aytadi:
t("hisob.qoldi", { count: 2 }). Kutubxona (i18next) tanlangan tilning faylidan matnni topadi va{{count}}o'rniga sonni qo'yadi. Ko'plik shaklini uIntl.PluralRulesbilan tanlaydi:2 ta vazifa qoldi,Осталось 2 задачи,Осталось 5 задач. React tomoni (react-i18next) esauseTranslationhook'ini beradi — til almashsa, komponent qayta chiziladi.vazifalarda narxi — birinchi yuklashga +17.1 KB gzip.
Bu darsda
- i18next va react-i18next ni ulaysiz:
init,useTranslation,t(),changeLanguage. - Interpolatsiya (
{{ism}}) va nega React'daescapeValue: falsexavfsiz ekanini tushuntirasiz. - Ko'plik shakllarini (
_one,_few,_many,_other,_zero) o'zbek, rus va ingliz tillari uchun yozasiz. - Matnlarni namespace'larga bo'lasiz, havola yoki qalin so'z bor gapni
Transbilan tarjima qilasiz. - Tilni qanday aniqlash, qayerda saqlash va boshqa til faylini kech yuklashni bilasiz;
vazifalarni ikki tilli qilasiz.
Oldin bilishingiz kerak: Intl.PluralRules, ListFormat, DisplayNames, Intl.NumberFormat, Context I, Frontend xavfsizligi React'da, lazy va code splitting.
1. Nega bu kerak?
O'tgan darsda «Bahor»ga bron kalendari qo'shdik. Endi Jasur aka yangi iltimos bilan keldi: mehmonlarning yarmi ruscha so'raydi, turistlar inglizcha. Menyu va bron sahifasi uch tilda bo'lsin.
Birinchi o'ylaydigan yechim — har joyda shart:
<h1>{til === "ru" ? "Меню" : til === "en" ? "Menu" : "Menyu"}</h1>Ilovada esa yuzlab matn bor, har birida shu ? : zanjiri. To'rtinchi til qo'shilsa — hamma joyni qayta ochish kerak. Tarjimon esa kodga tegolmaydi.
Ikkinchi muammo — ko'plik. vazifalar dagi hisob shunday yozilgan:
{`${qoldi} ta vazifa qoldi, ${bajarildi} ta bajarildi`}O'zbekchada son qanday bo'lmasin, "ta vazifa" o'zgarmaydi. Ruschada esa uch shakl: 1 задача, 2 задачи, 5 задач. 21 — yana задача. Bu qoidani Intl.PluralRules darsida ko'rgan edik. Har komponentda uni qo'lda yozish — xato manbai.
Yechim — i18n (internationalization — xalqarolashtirish; "i", keyin 18 harf, keyin "n"). Ilova matnlarni koddan tashqariga chiqaradi. Kod faqat kalitni aytadi, matn esa har til uchun alohida faylda.
Hayotiy o'xshatish: restoran menyusi. Oshpaz "7-taom" deydi. Ofitsiant mehmonning tilidagi varaqdan o'qiydi: "Osh", Плов, "Pilaf". Oshxona bir xil ishlaydi, varaqlar alohida. Kalit — taom raqami, tarjima fayli — o'sha tildagi varaq.
Yonida yana bir atama uchraydi: l10n (localization — mahalliylashtirish). i18n — ilovani ko'p tilga tayyorlash (kalitlar, fayllar, ko'plik). l10n — aniq bir tilga moslash: tarjima, sana va pul formati.
2. Birinchi tarjima
2.1 Ikki paket
React'da eng ko'p ishlatiladigan i18n yo'li — ikki paket:
| Paket | Versiya (kanon) | Nima qiladi |
|---|---|---|
i18next |
26.4.3 | yadro: tillar, fayllar, kalit qidirish, ko'plik, interpolatsiya. React'siz ham ishlaydi (Node, Vue) |
react-i18next |
17.0.16 | React ulagichi: useTranslation hook'i, Trans komponenti, til almashganda qayta chizish |
npm i -E i18next@26.4.3 react-i18next@17.0.16Natija oynasida ikkalasi bor — quyidagi misollar jonli.
2.2 init, useTranslation, t()
import i18next from "i18next";
import { initReactI18next, useTranslation } from "react-i18next";
i18next.use(initReactI18next).init({
lng: "uz", // boshlang'ich til
fallbackLng: "uz", // kalit topilmasa — shu tildan
resources: {
uz: {
translation: {
menyu: { sarlavha: "Menyu", savat: "Savatcha" },
til: "Til",
},
},
ru: {
translation: {
menyu: { sarlavha: "Меню", savat: "Корзина" },
},
},
},
interpolation: { escapeValue: false }, // nega — keyingi bo'limda
});
function App() {
const { t, i18n } = useTranslation();
return (
<section>
<h2>{t("menyu.sarlavha")}</h2>
<p>
{t("menyu.savat")} · {t("til")}
</p>
<button onClick={() => i18n.changeLanguage("uz")}>
O'zbekcha
</button>{" "}
<button onClick={() => i18n.changeLanguage("ru")}>
Русский
</button>
</section>
);
}Ekranda:
Меню
Корзина · Til
O'zbekcha РусскийQatorma-qator:
i18next.use(initReactI18next)— yadroga React ulagichini ulaydi. Shundan keyinuseTranslationshu nusxani topadi.Provideryozish shart emas, lekin React'dagi Context g'oyasi ichida o'sha: komponentlar umumiy nusxaga obuna bo'ladi.resources— tarjimalar:til → namespace → kalitlar.translation— standart namespace nomi (namespace'lar haqida — alohida bo'limda).t("menyu.sarlavha")— nuqta ichma-ich obyektga kiradi. Kalitlar guruhlanadi:menyu.*,xato.*.i18n.changeLanguage("ru")— tilni almashtiradi.useTranslationishlatgan har komponent qayta chiziladi.
Ikkinchi qatorga qarang: Корзина · Til. Ruscha faylda til kaliti yo'q. i18next fallbackLng dagi o'zbekchadan oldi. Tarjima to'liq bo'lmasa ham ilova buzilmaydi — faqat bitta so'z o'zbekcha qoladi.
2.3 Kalit hech qayerda yo'q bo'lsa
import i18next from "i18next";
i18next.init({
lng: "uz",
resources: { uz: { translation: { salom: "Salom!" } } },
});
console.log(i18next.t("salom"));
console.log(i18next.t("menyu.sarlavha"));
console.log(i18next.options.fallbackLng);Konsolda:
Salom!
menyu.sarlavha
(1) ['dev']Topilmagan kalit — kalitning o'zi qaytadi. Xato tashlanmaydi, konsolda ham jim. Ekranda "menyu.sarlavha" ko'rinsa — tarjima fayliga qarang.
Uchinchi qator — tuzoq. fallbackLng berilmasa, sukut bo'yicha ["dev"] — mavjud bo'lmagan "dev" tili. Tillarni serverdan yuklasangiz, i18next dev faylini ham so'raydi. Shuning uchun fallbackLng ni doim o'z asosiy tilingiz bilan yozing.
useTranslation siz ham ishlaydi: i18next.t() — oddiy funksiya. React tashqarisida (masalan Zod xabarlarida yoki document.title da) shundan foydalaniladi. Lekin komponent ichida — faqat hook: i18next.t til almashganini React'ga aytmaydi.
Tekshirib ko'ring: Ruscha faylda
menyu.savatyo'q, o'zbekchada bor.fallbackLng: "uz". Til —ru.t("menyu.savat")nima qaytaradi?fallbackLngumuman yozilmagan bo'lsa-chi?
Javob
Birinchi holatda — «Savatcha»: ruschada topilmadi, o'zbekchadan olindi. Ikkinchisida — «menyu.savat»: zaxira til "dev", unda hech narsa yo'q, kalitning o'zi qaytdi. Shuning uchun fallbackLng — har loyihada birinchi yoziladigan sozlama.
3. Interpolatsiya: matn ichidagi qiymat
3.1 {{ism}}
Ko'p matnda o'zgaruvchi qism bor: "Salom, Ali!", "Osh — 35 000 so'm". Tarjimada joyi ham o'zgaradi: o'zbekchada "Ali, xush kelibsiz", inglizchada "Welcome, Ali". Shuning uchun qiymat satrga qo'shib yozilmaydi — tarjimaning o'zida joy qoldiriladi. Bu — interpolatsiya (interpolation): {{ism}} o'rniga t() ga berilgan qiymat qo'yiladi.
import i18next from "i18next";
import { initReactI18next, useTranslation } from "react-i18next";
i18next.use(initReactI18next).init({
lng: "uz",
fallbackLng: "uz",
resources: {
uz: {
translation: {
xush: "{{ism}}, xush kelibsiz!",
narx: "{{taom}} — {{narx}} so'm",
},
},
},
interpolation: { escapeValue: false },
});
function App() {
const { t } = useTranslation();
return (
<>
<p>{t("xush", { ism: "Malika" })}</p>
<p>{t("narx", { taom: "Osh", narx: "35 000" })}</p>
</>
);
}Ekranda:
Malika, xush kelibsiz!
Osh — 35 000 so'mNarxni bu yerda tayyor satr qilib berdik. Sonni tilga qarab formatlash (35 000 / 35,000) — Intl.NumberFormat ishi. i18next uni tarjima ichida ham chaqira oladi — buni i18n II darsida ko'ramiz.
3.2 escapeValue nega false
i18next sukut bo'yicha qiymatni ekranlaydi: < → <. Bu XSS dan himoya — natija innerHTML ga yozilsa, <script> ishlamasin. Lekin React matnni o'zi ekranlaydi (Frontend xavfsizligi). Ikki marta ekranlash nima beradi? Ikkita alohida nusxa bilan sinaymiz:
import i18next from "i18next";
const resources = {
uz: { translation: { xush: "{{ism}}, xush kelibsiz!" } },
};
// sukut: escapeValue true
const standard = i18next.createInstance();
standard.init({ lng: "uz", resources });
// React uchun: escapeValue false
const forReact = i18next.createInstance();
forReact.init({
lng: "uz",
resources,
interpolation: { escapeValue: false },
});
const ism = "<b>Ali</b>";
console.log(standard.t("xush", { ism }));
console.log(forReact.t("xush", { ism }));
export default function App() {
return <p>{standard.t("xush", { ism })}</p>;
}Konsolda:
<b>Ali</b>, xush kelibsiz!
<b>Ali</b>, xush kelibsiz!Ekranda:
<b>Ali</b>, xush kelibsiz!Ekranga qarang: foydalanuvchi <b> ni ko'rdi. i18next ekranladi, React esa natijani yana matn qilib chiqardi — ekranlash belgilari ekranga tushdi. escapeValue: false bilan React ekranga <b>Ali</b> ni matn qilib chiqaradi: teg emas, qalin shrift emas. Himoya o'z joyida — React'da.
createInstance() — alohida i18next nusxasi. Kanon ham shunday qiladi: global i18next emas, o'z nusxasi. Testlarda va bitta sahifadagi ikki mustaqil vidjetda bu qulay.
Diqqat:
escapeValue: falsefaqat natija React orqali chiqqanda xavfsiz.t()natijasiniinnerHTMLyokidangerouslySetInnerHTMLga yozsangiz, himoya yo'qoladi — «Hujumchi nigohi» bo'limida.
4. Ko'plik shakllari
4.1 Kalit + qo'shimcha
i18next'da ko'plik uchun bitta kalitning bir nechta varianti yoziladi. Qo'shimcha — Intl.PluralRules kategoriyasi: _one, _few, _many, _other. t() ga count berilsa, i18next kategoriyani o'zi so'raydi va mos variantni oladi:
flowchart TD
A["t('qoldi', count: 5)"] --> B["til: ru"]
B --> C["PluralRules('ru')<br/>select(5)"]
C --> D["'many'"]
D --> E["qoldi_many"]
E --> F["'Осталось {{count}} задач'"]
F --> G["Осталось 5 задач"]Nimaga qarang: tilning qoidasini i18next o'zi bilmaydi — brauzerdagi Intl.PluralRules dan so'raydi. Biz faqat har kategoriyaga matn yozamiz.
Kategoriyalar soni tilga bog'liq (Intl.PluralRules darsidagi jadval): o'zbek va ingliz — one, other; rus — one, few, many, other.
import i18next from "i18next";
import { initReactI18next, useTranslation } from "react-i18next";
i18next.use(initReactI18next).init({
lng: "ru",
fallbackLng: "uz",
resources: {
uz: {
translation: {
qoldi_one: "{{count}} ta vazifa qoldi",
qoldi_other: "{{count}} ta vazifa qoldi",
},
},
ru: {
translation: {
qoldi_one: "Осталась {{count}} задача",
qoldi_few: "Осталось {{count}} задачи",
qoldi_many: "Осталось {{count}} задач",
qoldi_other: "Осталось {{count}} задачи",
},
},
},
interpolation: { escapeValue: false },
});
function App() {
const { t } = useTranslation();
return (
<ul>
{[1, 2, 5, 21, 0, 1.5].map((n) => (
<li key={n}>{t("qoldi", { count: n })}</li>
))}
</ul>
);
}Ekranda:
Осталась 1 задача
Осталось 2 задачи
Осталось 5 задач
Осталась 21 задача
Осталось 0 задач
Осталось 1.5 задачиcount |
PluralRules("ru") |
Kalit |
|---|---|---|
| 1, 21 | one |
qoldi_one |
| 2 | few |
qoldi_few |
| 5, 0 | many |
qoldi_many |
| 1.5 | other |
qoldi_other |
21 — "bir" bilan tugaydi, shuning uchun one (задача), garchi 21 ta bo'lsa ham. Kasr son ruschada other. vazifalar da kasr bo'lmaydi, lekin _other ni baribir yozing: u har tilda bor va oxirgi zaxira.
O'zbekchada esa ikkala kalit bir xil matn. Nega yozamiz? one va other — o'zbek tilining CLDR kategoriyalari. _one bo'lmasa, count: 1 da ekranga kalitning o'zi — qoldi chiqadi.
4.2 Nol uchun alohida matn: _zero
Осталось 0 задач — to'g'ri, lekin quruq. Ko'pincha 0 uchun boshqa gap kerak: "Hammasi bajarildi!". i18next'da buning uchun _zero qo'shimchasi bor. U tilning qoidasidan qat'i nazar, count: 0 da birinchi tekshiriladi:
import i18next from "i18next";
i18next.init({
lng: "uz",
resources: {
uz: {
translation: {
savat_zero: "Savatcha bo'sh",
savat_one: "Savatchada {{count}} ta taom",
savat_other: "Savatchada {{count}} ta taom",
},
},
},
});
for (const count of [0, 1, 3]) {
console.log(i18next.t("savat", { count }));
}Konsolda:
Savatcha bo'sh
Savatchada 1 ta taom
Savatchada 3 ta taom_zero — Intl kategoriyasi emas, i18next'ning qo'shimchasi. Yozilmasa, 0 tilning odatdagi qoidasiga tushadi (ruschada many).
Tekshirib ko'ring: Ingliz tili uchun
qoldi_one: "{{count}} task left"vaqoldi_other: "{{count}} tasks left"yozildi.count: 0da nima chiqadi?
Javob
«0 tasks left». Ingliz tilida PluralRules("en").select(0) — other. _zero yozilmagan, shuning uchun qoldi_other olinadi. "No tasks left" kerak bo'lsa — qoldi_zero qo'shing.
5. Namespace'lar: tarjimalarni fayllarga bo'lish
5.1 Nega bitta fayl emas
Ilova o'sgani sari tarjima fayli ham o'sadi: yuzlab kalit. Hammasini birinchi sahifada yuklash shart emas. Sozlamalar sahifasidagi matnlar faqat o'sha sahifa ochilganda kerak. Tarjimonlar ham bo'lim bo'yicha ishlaydi.
Namespace (nomlar fazosi) — tarjimalarning alohida guruhi, odatda alohida fayl: uz/umumiy.json, uz/sozlamalar.json. Komponent qaysi guruhdan o'qishini useTranslation("sozlamalar") bilan aytadi:
import i18next from "i18next";
import { initReactI18next, useTranslation } from "react-i18next";
i18next.use(initReactI18next).init({
lng: "uz",
fallbackLng: "uz",
ns: ["umumiy", "sozlamalar"],
defaultNS: "umumiy",
resources: {
uz: {
umumiy: { sahifaNomi: "{{sahifa}} — Vazifalar" },
sozlamalar: { sarlavha: "Sozlamalar", tema: { nomi: "Tema" } },
},
},
interpolation: { escapeValue: false },
});
function SettingsPage() {
const { t } = useTranslation("sozlamalar");
const pageTitle = t("umumiy:sahifaNomi", { sahifa: t("sarlavha") });
console.log(pageTitle);
return (
<>
<h2>{t("sarlavha")}</h2>
<p>{t("tema.nomi")}</p>
</>
);
}
export default SettingsPage;Konsolda:
Sozlamalar — VazifalarEkranda:
Sozlamalar
TemauseTranslation("sozlamalar")— bu komponentdagit()sukut bo'yichasozlamalardan o'qiydi.t("umumiy:sahifaNomi")— ikki nuqta bilan boshqa namespace'ga murojaat. Sahifa nomi andozasi umumiy — har sahifa o'z nomini unga qo'yadi.defaultNS: "umumiy"— namespace aytilmaganuseTranslation()shu yerdan o'qiydi.
Bu misol kanon kodidan olingan: SozlamalarSahifasi.tsx dagi <title> aynan shunday yozilgan («Vazifalar qadami» bo'limida).
5.2 Kalitlarni qanday nomlash
Ba'zi loyihalar matnning o'zini kalit qiladi: t("Ro'yxatga qaytish"). Lekin o'zbekcha matn o'zgarsa, kalit ham o'zgaradi va barcha tarjimalar "uziladi". Kanon ma'noli kalitlarni joy bo'yicha guruhlaydi: bolimlar.*, statistika.*, topilmadi.*.
6. Trans: havola yoki qalin so'z bor gap
6.1 Muammo
Gap: "Buyurtma berish uchun kiring", "kiring" — havola. Uni uch bo'lakka bo'lish mumkin:
// ❌ gap bo'laklarga bo'lindi
{t("boshi")} <a href="/kirish">{t("havola")}</a> {t("oxiri")}Tarjimon "boshi" va "oxiri" ni alohida ko'radi — gapni butunligicha ko'rmaydi. Ruschada so'z tartibi boshqa, havola gap boshiga o'tishi mumkin. Bo'laklar esa qotib qolgan.
6.2 Yechim: belgilar tarjimaning ichida
Trans komponenti gapni butunligicha tarjimada saqlaydi. Havola o'rnini esa nomli belgi bilan ko'rsatadi:
import i18next from "i18next";
import { initReactI18next, Trans } from "react-i18next";
i18next.use(initReactI18next).init({
lng: "ru",
fallbackLng: "uz",
resources: {
uz: {
translation: {
kirish: "Buyurtma uchun <havola>kiring</havola>.",
},
},
ru: {
translation: {
kirish: "<havola>Войдите</havola>, чтобы заказать.",
},
},
},
interpolation: { escapeValue: false },
});
export default function App() {
return (
<p ref={(el) => el && console.log(el.innerHTML)}>
<Trans
i18nKey="kirish"
components={{ havola: <a href="/kirish" /> }}
/>
</p>
);
}Konsolda:
<a href="/kirish">Войдите</a>, чтобы заказать.Ekranda:
Войдите, чтобы заказать.Ruschada havola gap boshiga o'tdi — tarjimon buni o'zi hal qildi. <havola> — HTML teg emas, faqat belgi: Trans uni components dagi <a> elementi bilan almashtirdi. Tarjima fayliga haqiqiy <a href> yozilmaydi. Manzil — kodda, matn — faylda.
<havola>…</havola> dagi belgi nomi components obyektidagi kalit bilan bir xil bo'lishi shart.
Diqqat: Belgiga HTML'ning bo'sh (void) elementlari nomini bermang:
link,img,br,input,meta. Biz birinchi variantda<link>Войдите</link>yozgan edik — natija<a href="/kirish"></a>Войдитеbo'ldi: havola bo'sh, so'z undan tashqarida.Transtarjimani HTML qoidalari bilan o'qiydi, HTML'da esa<link>yopilmaydigan teg. Xato tashlanmaydi — faqat havola "yo'qoladi". Shuning uchun belgilarga o'zbekcha nom beramiz:<havola>,<qalin>.
Tekshirib ko'ring: Nega tarjima fayliga
"<a href=\"/kirish\">kiring</a>"yozib,dangerouslySetInnerHTMLbilan chiqarish yomon fikr?
Javob
Uch sabab. Birinchisi — xavfsizlik: tarjima fayli HTML bo'lib qoladi, uni tahrirlagan har kim sahifaga skript qo'sha oladi («Hujumchi nigohi»). Ikkinchisi — vazifalar da Trusted Types satrni innerHTML ga yozishni umuman to'sadi (XSS va DOM xavfsizligi). Uchinchisi — manzil har tilda takrorlanadi: /kirish o'zgarsa, hamma faylni tuzatish kerak. Trans da manzil bitta joyda, kodda.
7. Til: qayerdan olinadi va qayerda saqlanadi
7.1 Til almashganda nima bo'ladi
changeLanguage dan keyin ikki ish qilinishi kerak: interfeys qayta chiziladi (buni react-i18next qiladi) va <html lang> yangilanadi. Ikkinchisini biz qilamiz. lang atributiga ekran o'quvchi talaffuzi, brauzer tarjimoni, imlo tekshiruvi va CSS :lang() qaraydi (Ko'p tilli matn). i18next til almashganda languageChanged hodisasini chiqaradi:
import i18next from "i18next";
import { initReactI18next, useTranslation } from "react-i18next";
i18next.use(initReactI18next).init({
lng: "uz",
fallbackLng: "uz",
resources: {
uz: { translation: { salom: "Xush kelibsiz" } },
ru: { translation: { salom: "Добро пожаловать" } },
},
});
i18next.on("languageChanged", (lng) => {
document.documentElement.lang = lng;
console.log("html lang:", document.documentElement.lang);
});
function App() {
const { t, i18n } = useTranslation();
async function choose(lng) {
await i18n.changeLanguage(lng);
// i18n — hook bergan nusxa: eski render'niki
console.log("hook nusxasi:", i18n.language);
console.log("i18next:", i18next.language);
}
return (
<>
<p>{t("salom")}</p>
<button onClick={() => choose("ru")}>Русский</button>
</>
);
}Konsolda:
html lang: ru
hook nusxasi: uz
i18next: ruEkranda:
Добро пожаловать
РусскийIkkinchi qatorga qarang — kutilmagan narsa. await dan keyin hook bergan i18n.language hali ham uz. react-i18next 17 har til uchun i18n ning yangi "nusxa-o'ramini" beradi. Shu sabab React til almashganini obyekt o'zgarganidan sezadi. Bizning choose funksiyamiz esa eski render'da yaratilgan — u eski o'ramni ushlab turibdi. Bu 17-qismdagi eskirgan qiymat (stale closure) bilan bir xil holat. Qoida: yangi tilni keyingi render'da i18n.language dan yoki i18next.language dan o'qing.
7.2 Tilni aniqlash: detector
Foydalanuvchi birinchi marta kirdi. Qaysi tilda ko'rsatamiz? Tayyor plagin — i18next-browser-languagedetector. U tilni ketma-ket bir necha joydan qidiradi va tanlovni eslab qoladi (hujjat bo'yicha, kanonda ishlatilmagan):
import LanguageDetector from "i18next-browser-languagedetector";
i18next.use(LanguageDetector).init({
fallbackLng: "uz",
supportedLngs: ["uz", "ru"],
detection: {
// ?lng=ru → saqlangan tanlov → brauzer tili
order: ["querystring", "localStorage", "navigator"],
caches: ["localStorage"],
},
});navigator — brauzer sozlamasidagi til (navigator.languages). Kanon esa uni ataylab o'qimaydi. Izohdan (i18n/index.ts): "O'zbekistonda brauzerlarning ko'pi en-US yoki ru — foydalanuvchi o'zbekcha saytni ochgan, tilni o'zi tanlaydi". Brauzer tili — telefon sotib olinganda qo'yilgan sozlama, odamning xohlagan tili emas.
Kanon qoidasi:
- Birinchi kirish — har doim o'zbekcha (asosiy til).
- Foydalanuvchi tanlasa —
localStoragega (vazifalar:til) yoziladi. - Keyingi kirishda — saqlangan til.
Qaysi yo'l to'g'ri — auditoriyaga bog'liq. Xalqaro do'kon uchun brauzer tili yaxshi taxmin. O'zbek tilidagi xizmat uchun esa o'zbekcha birinchi bo'lishi kerak. Tilni manzilda saqlash (/ru/…) — keyingi qadam: havolani ulashsa, til ham birga ketadi. Kanon buni i18n II da qiladi.
8. Boshqa tillarni kech yuklash
8.1 Hamma tilni birdaniga yuklamang
O'zbekcha foydalanuvchiga ruscha va inglizcha fayllar kerak emas. Har til fayli — qo'shimcha baytlar. Yechim — backend plagini: i18next tarjima kerak bo'lganda uni read() orqali so'raydi.
Mashhur variant — i18next-http-backend: u fayllarni serverdan fetch bilan oladi, manzil andozasi loadPath: "/locales/{{lng}}/{{ns}}.json" (hujjat bo'yicha, kanon o'lchamagan).
Kanon boshqa yo'lni tanladi — o'z kichik backend'i, import() bilan. Code splitting darsidagi dinamik import har til faylini alohida chunk qiladi. Fayl nomida hash bor, kesh muammosi yo'q, CSP o'zgarmaydi — fayl o'z origin'imizdan keladi ("Vazifalar qadami" da kodi).
8.2 Jonli: til fayli kelguncha
Natija oynasida dinamik import() ishlamaydi. Shuning uchun faylni setTimeout bilan "kech keladigan" qilib taqlid qilamiz (o'quv modeli):
import { useState } from "react";
import i18next from "i18next";
import { initReactI18next, useTranslation } from "react-i18next";
const FILES = {
ru: { translation: { menyu: "Меню", savat: "Корзина" } },
};
// O'quv modeli: kanondagi import() o'rniga 200 ms kechikish
const slowBackend = {
type: "backend",
init() {},
read(lng, ns, done) {
console.log(`yuklanmoqda: ${lng}/${ns}`);
setTimeout(() => done(null, FILES[lng][ns]), 200);
},
};
i18next
.use(slowBackend)
.use(initReactI18next)
.init({
lng: "uz",
fallbackLng: "uz",
// o'zbekcha — shu yerda, boshqasi — backend'dan
resources: {
uz: { translation: { menyu: "Menyu", savat: "Savatcha" } },
},
partialBundledLanguages: true,
react: { useSuspense: false },
});
function App() {
const { t, i18n } = useTranslation();
const [loading, setLoading] = useState(false);
async function choose(lng) {
setLoading(true);
await i18n.changeLanguage(lng);
setLoading(false);
}
return (
<div>
<button onClick={() => choose("ru")}>Русский</button>
<p>
{loading ? "Yuklanmoqda…" : t("menyu")} · {t("savat")}
</p>
</div>
);
}Konsolda:
yuklanmoqda: ru/translationEkranda:
Русский
Меню · Корзина| Qadam | Til | Ekranda |
|---|---|---|
| boshida | uz (fayl resources da) |
Menyu · Savatcha |
Русский bosildi |
hali uz, ru yuklanmoqda |
Yuklanmoqda… · Savatcha |
| 200 ms dan keyin | ru |
Меню · Корзина |
Nimaga qarang: til fayli kelgandan keyin almashdi. Yarim o'zbekcha, yarim ruscha ekran bo'lmadi — changeLanguage avval faylni kutadi.
Uch sozlama:
partialBundledLanguages: true— "ba'zi tillarresourcesda, qolgani backend'dan". Usizresourcesberilgan bo'lsa, backend umuman chaqirilmaydi.fallbackLng: "uz"— yozilmasa, backenddev/translationni ham so'raydi. BizningFILES.devyo'q — blokTypeErrorbilan yiqilardi (sinab ko'rdik).useSuspense: false— sukut bo'yichatrue: tarjima tayyor bo'lmasa,useTranslationkomponentni Suspense ga "to'xtatadi" va eng yaqin fallback chiqadi. Kanonfalseqo'ygan va birinchi chizishdan oldin tilni o'zi yuklaydi («Vazifalar qadami»).
9. Vazifalar qadami: ikki tilli interfeys (holat 02)
9.1 Branch va paketlar
- Branch:
feat/i18n
git switch -c feat/i18n
npm i -E i18next@26.4.3 react-i18next@17.0.16| Fayl | O'zgarish |
|---|---|
src/i18n/index.ts |
yangi: nusxa, kech yuklash backend'i, <html lang>, til tanlash |
src/i18n/uz/*.json, src/i18n/ru/*.json |
yangi: umumiy va sozlamalar namespace'lari |
komponentlar/TilTanlov.tsx |
yangi: til tanlovi (radio) |
main.tsx |
birinchi chizishdan oldin tilni tayyorlash |
Qobiq.tsx, Statistika.tsx, TemaTanlov.tsx, 3 ta sahifa |
matnlar → t() |
tsconfig.app.json, tekshiruv/vitest-sozlash.ts, asos.css |
JSON importi, testlarda i18n, #til-tanlov uslubi |
9.2 src/i18n/index.ts
Kanondan, ikki bo'lak (izohlar qisqartirilgan). Birinchisi — tillar va saqlangan tanlov:
import i18next from "i18next";
import type { BackendModule, ReadCallback } from "i18next";
import { initReactI18next } from "react-i18next";
import umumiy from "./uz/umumiy.json";
import sozlamalar from "./uz/sozlamalar.json";
export const TILLAR = ["uz", "ru"] as const;
export type Til = (typeof TILLAR)[number];
const ASOSIY_TIL: Til = "uz";
// Til nomi — o'sha tilning o'zida (autonym)
export const TIL_NOMLARI: Readonly<Record<Til, string>> = Object.freeze(
{
uz: "O'zbekcha",
ru: "Русский",
},
);
const TIL_KALITI = "vazifalar:til";
/** Satr bizning tillardan birimi
* (localStorage, URL — tashqi qiymat) */
export function tilmi(qiymat: unknown): qiymat is Til {
return TILLAR.some((til) => til === qiymat);
}
// navigator.language ATAYLAB o'qilmaydi (izohi — «Tilni aniqlash»)
function saqlanganTil(): Til {
try {
const til = localStorage.getItem(TIL_KALITI);
return tilmi(til) ? til : ASOSIY_TIL;
} catch {
return ASOSIY_TIL; // xotira yopiq (maxfiy rejim, sozlama)
}
}Uch narsaga qarang:
- O'zbekcha JSON statik import qilingan — u asosiy build'ga tushadi va birinchi chizish tarjimani kutmaydi.
tilmi— type guard:localStoragedagi satr — tashqi qiymat, unga ishonilmaydi."de"yoki buzuq satr — o'zbekcha.try/catch— maxfiy rejimdalocalStoragexato tashlashi mumkin (localStorage).
Ikkinchi bo'lak — kech yuklash va sozlash:
// import() — Vite har til/namespace faylini alohida chunk qiladi
// (http backend kerak emas: fayl o'z origin'imizdan, CSP o'zgarmaydi)
const kechYuklash: BackendModule = {
type: "backend",
init() {},
read(til: string, ns: string, tayyor: ReadCallback) {
import(`./${til}/${ns}.json`).then(
(modul: { default: Record<string, unknown> }) =>
tayyor(null, modul.default),
(xato: unknown) => tayyor(xato as Error, false),
);
},
};
const i18n = i18next.createInstance();
void i18n
.use(kechYuklash)
.use(initReactI18next)
.init({
lng: saqlanganTil(),
fallbackLng: ASOSIY_TIL,
supportedLngs: TILLAR,
ns: ["umumiy", "sozlamalar"],
defaultNS: "umumiy",
// O'zbekcha — shu yerda; boshqa til — kechYuklash orqali
resources: { uz: { umumiy, sozlamalar } },
partialBundledLanguages: true,
// React o'zi ekranlaydi (XSS'dan) — ikkinchi marta kerak emas
interpolation: { escapeValue: false },
// Til fayli kelguncha komponent Suspense'ga tushmasin: main.tsx
// birinchi chizishdan oldin tilni yuklab oladi (tilniTayyorla)
react: { useSuspense: false },
});
// <html lang>: ekran o'quvchi talaffuzi, brauzer tarjimoni, imlo
// tekshiruvi, CSS :lang() — hammasi shunga qaraydi
i18n.on("languageChanged", (til) => {
document.documentElement.lang = til;
});Darsdagi hamma sozlama shu yerda: fallbackLng, ns/defaultNS, partialBundledLanguages, escapeValue: false, useSuspense: false, languageChanged. Jonli misoldagi setTimeout o'rnida — haqiqiy import(). supportedLngs — ro'yxatdan tashqari til (de) so'ralsa, i18next uni qabul qilmaydi.
Faylning oxirida ikki funksiya:
export function tilniTayyorla(): Promise<unknown> | null {
document.documentElement.lang = i18n.language;
return i18n.language === ASOSIY_TIL
? null
: i18n.loadNamespaces(["umumiy", "sozlamalar"]);
}
/** Til tanlandi: saqlanadi va interfeys o'sha tilga o'tadi */
export async function tilniAlmashtir(til: Til): Promise<void> {
try {
localStorage.setItem(TIL_KALITI, til);
} catch {
// saqlanmasa ham — shu sahifada til o'zgaradi
}
await i18n.changeLanguage(til);
}9.3 main.tsx: o'zbekcha — darhol, boshqasi — fayl kelgach
const tilTayyor = tilniTayyorla();
if (tilTayyor === null) {
chiz(ildiz);
} else {
// Til fayli kelmasa (oflayn, birinchi tashrif) — o'zbekcha bilan
tilTayyor.finally(() => chiz(ildiz)).catch(() => {});
}O'zbekcha foydalanuvchi uchun ilova avvalgidek sinxron chiziladi (13-qismdagi blocking="render", CLS 0). Ruscha tanlagan foydalanuvchi avval kichik faylni kutadi, keyin darhol ruscha sahifani ko'radi. Fayl kelmasa (oflayn) — ilova o'zbekcha ochiladi, yiqilmaydi.
9.4 Tarjima fayllari va ko'plik
src/i18n/ru/umumiy.json dan (hisob qismi):
"hisob": {
"matn": "{{qoldi}}, {{bajarildi}}",
"qoldi_one": "Осталась {{count}} задача",
"qoldi_few": "Осталось {{count}} задачи",
"qoldi_many": "Осталось {{count}} задач",
"qoldi_other": "Осталось {{count}} задачи",
"bajarildi_one": "{{count}} выполнена",
"bajarildi_few": "{{count}} выполнены",
"bajarildi_many": "{{count}} выполнено",
"bajarildi_other": "{{count}} выполнено"
}O'zbekchasida — faqat _one va _other, matni bir xil («{{count}} ta vazifa qoldi»). Statistika.tsx dagi o'zgarish:
- {`${qoldi} ta vazifa qoldi, ${bajarildi} ta bajarildi`}
+ {t("hisob.matn", {
+ qoldi: t("hisob.qoldi", { count: qoldi }),
+ bajarildi: t("hisob.bajarildi", { count: bajarildi }),
+ })}Gap ikki ko'plikli bo'lakdan yig'iladi. Har bo'lak o'z soni bilan o'z shaklini oladi: Осталось 5 задач, 2 выполнены. Bo'laklarni birlashtiradigan andoza (hisob.matn) ham tarjimada — tartibni tarjimon o'zgartira oladi.
9.5 Til tanlovi
TilTanlov.tsx — TemaTanlov kabi radio guruh (kanondan, qisqartirilgan):
const { t, i18n } = useTranslation("sozlamalar");
// ...
{TILLAR.map((til) => (
<Fragment key={til}>
<input
type="radio"
name="til"
id={`til-${til}`}
value={til}
checked={i18n.resolvedLanguage === til}
onChange={(event) => {
const tanlangan = event.target.value;
if (tilmi(tanlangan)) {
void tilniAlmashtir(tanlangan);
}
}}
/>
{/* lang — til nomi o'z tilida o'qilsin
("Русский" ruscha) */}
<label htmlFor={`til-${til}`} lang={til}>
{TIL_NOMLARI[til]}
</label>
</Fragment>
))}- Til nomlari o'z tilida: «O'zbekcha»,
Русский. Ruscha bilgan, lekin o'zbekchani tushunmaydigan odam "Ruscha" so'zini o'qiy olmasligi mumkin —Русскийni esa taniydi. lang={til}— Malikaning ekran o'quvchisiРусскийni ruscha talaffuz bilan o'qiydi.i18n.resolvedLanguage— hook bergan nusxadan, render paytida o'qiladi. Bu «Til almashganda nima bo'ladi» bo'limidagi tuzoqqa tushmaydi.tilmi— radio qiymati ham satr, tekshirib o'tkaziladi.
9.6 Testlar va TypeScript
Ikki kichik, lekin majburiy o'zgarish. tsconfig.app.json ga "resolveJsonModule": true qo'shildi. Testlar sozlamasiga esa i18n importi:
// tekshiruv/vitest-sozlash.ts oxiri
// 21/#02: komponentlar matnni t() dan oladi — i18next sozlangan
// bo'lishi kerak (aks holda t() kalitning o'zini qaytaradi:
// "hisob.matn").
import "../src/i18n/index.ts";Importsiz Vitest'dagi komponent testlari "hisob.matn" ni ko'rib yiqilardi — «Kalit hech qayerda yo'q bo'lsa» bo'limidagi xulq. resolveJsonModule siz esa import umumiy from "./uz/umumiy.json" — tsc xatosi.
9.7 Natija va narx
Kanon tekshiruvi (natijalar/tekshiruv-02.txt): build, node:test 203, Vitest 65, lint, format, tip, knip — toza. Build bitta ogohlantirish berdi:
[INEFFECTIVE_DYNAMIC_IMPORT] src/i18n/uz/umumiy.json is dynamically imported by src/i18n/index.ts but also statically imported by src/i18n/index.ts, dynamic import will not move module into another chunk.Tarjimasi: "uz/umumiy.json dinamik ham, statik ham import qilingan — dinamik import uni alohida chunk'ga ajratmaydi." import(\./${til}/${ns}.json`)andozasi o'zbekcha fayllarni ham qamraydi, ular esa statik import qilingan. Bu ataylab: o'zbekcha baribir asosiy build'da bo'lishi kerak. Ogohlantirish zararsiz, ruscha fayllar esa alohida chunk bo'ldi:umumiy— 0.64 KB,sozlamalar` — 0.34 KB gzip.
Birinchi yuklash JS (gzip): 201.8 → 219.0 KB — i18next va react-i18next uchun +17.1 KB. Bu — shu qismdagi eng katta birinchi yuklash narxi. Kanon muqobillarni ham o'lchadi (kutubxonalar-21.txt, bitta ruscha ko'plikli gap):
- o'z t() + Intl.PluralRules0,1 KB
- Lingui 6.9.1 (runtime)kompilyatsiya qadami kerak2,3 KB
- react-intl 12.1.414 KB
- i18next + react-i18next15,4 KB
Manba: O'lchandi: kanon tajribasi, Vite 8.3.3, React 19.3.0, gzip -9, Chrome 155, natijalar/tajriba/kutubxonalar-21.txt, 2026-10-17
O'z t() — atigi 0.1 KB: Intl.PluralRules brauzerda bor. Nega kanon baribir i18next'ni oldi? Namespace'lar, kech yuklash, Trans, zaxira til, formatlash va tarjima xizmatlari bilan integratsiya — hammasi tayyor. Bu yo'l eng keng tarqalgan, yangi dasturchi uni taniydi. Narx halol yozildi (TEXNIK-QARZ 61), qaror: qabul. Lingui va FormatJS'ning ICU sintaksisi — i18n II da.
Hamma matn ham ko'chmadi. Tarjima qilingan: qobiq (menyu, skip link), ro'yxat hisobi, statistika sahifasi, sozlamalar, 404. Formalar, e'lonlar, xatolar, dialoglar — hozircha o'zbekcha (TEXNIK-QARZ 60). Sabab: ~900 qatorli App va 60 dan ortiq komponent. Real loyihada ham i18n shunday kiradi — bosqichma-bosqich, har yangi komponent t() bilan.
Brauzer tekshiruvi — 21-qismning yangi e2e-21.mjs, Chrome 155 (brauzer-tekshiruv/natija-21-02.txt, 16 qatordan 10 tasi):
✅ o'zbekcha: html lang, menyu, hisob: ["uz",["Ro'yxat","Statistika","Sozlamalar"],"2 ta vazifa qoldi, 1 ta bajarildi"]
✅ ruscha chunk hali yuklanmagan: []
✅ ruscha: lang, menyu, sarlavha, title, legend: [["Список","Статистика","Настройки"],"Настройки","Настройки — Задачи","Язык","Тема"]
✅ ruscha fayllar — tanlangandan keyin, bir marta: ["sozlamalar","umumiy"]
✅ CSP buzilishi yo'q: []
✅ ruscha ko'plik: 21/1: "Осталась 21 задача, 1 выполнена"
✅ ruscha ko'plik: 5/2: "Осталось 5 задач, 2 выполнены"
✅ ruscha ko'plik: 0/5: "Осталось 0 задач, 5 выполнено"
✅ noma'lum til → o'zbekcha: ["uz","2 ta vazifa qoldi, 1 ta bajarildi"]
✅ konsolda xato yo'q: []
HAMMASI ✅Ikkinchi qator — "ruscha chunk hali yuklanmagan": o'zbekcha foydalanuvchi ruscha fayllar uchun bir bayt ham to'lamaydi. 13–20-qismlarning 159, 72, 100 va 77 tekshiruvi ham o'tdi. axe — 0 buzilish, jumladan yangi «sozlamalar — ruscha interfeys» ko'rinishida.
Yo'l-yo'lakay axe eski xatoni topdi: skip link boshqa sahifalarda yo'q #royxat ga ko'rsatardi (v10 dan beri). Endi u yerda — #ilova (Qobiq.tsx).
9.8 Commit
git add -A
git commit -m "feat: react-i18next — qobiq, hisob (ko'plik), statistika, sozlamalar; ruscha kech yuklanadi"10. Hujumchi nigohi
Tarjima fayllari ko'pincha dasturchilar qo'lida emas: tarjimonlar, tashqi xizmatlar (Crowdin, Locize), ba'zan ko'ngillilar tahrirlaydi. Demak tarjima — tashqi ma'lumot. Hujumchi tarjimaga shunday "gap" qo'shadi:
{ "kirish": "Kiring <img src=x onerror=\"alert(document.cookie)\">" }Kod uni dangerouslySetInnerHTML={{ __html: t("kirish") }} bilan chiqarsa, har tashrif buyuruvchining brauzerida skript ishlaydi.
Himoya qatlamlari:
- Tarjimani faqat matn sifatida chiqaring:
{t("…")}— React ekranlaydi. Belgi kerak bo'lsa —Transvacomponents(teglarni kod beradi, tarjima faqat nomini aytadi). escapeValue: false— faqat React bilan.t()natijasiinnerHTML,document.writeyoki shablon satri bilan HTML'ga ketsa —escapeValueni yoqing yoki DOM'ni boshqacha quring.- Trusted Types (
vazifalarda yoqilgan) —innerHTMLga oddiy satr yozishni brauzer darajasida to'sadi. Yuqoridagi xato kod ishlamaydi, konsolda buzilish chiqadi.
11. Ko'p uchraydigan xatolar
| Belgi | Sabab | Davosi |
|---|---|---|
Ekranda menyu.sarlavha |
kalit tarjimada yo'q yoki i18n sozlanmagan (testda ham) | faylni tekshiring; testlarda i18n'ni import qiling |
Ekranda <b> |
escapeValue sukut bo'yicha true, React ikkinchi marta ekranladi |
React bilan — escapeValue: false |
TypeError yoki dev fayliga so'rov |
fallbackLng yozilmagan — sukut ["dev"] |
fallbackLng: "uz" |
Ruschada Осталось 21 задач |
ko'plik qo'lda (count > 4) yoki _one yo'q |
count + _one/_few/_many/_other |
| Til almashdi, komponent eski tilda | i18next.t() komponent ichida (obuna yo'q) |
useTranslation() dan t |
await changeLanguage() dan keyin i18n.language eski |
hook bergan o'ram — eski render'niki | keyingi render'da yoki i18next.language |
| Til fayli kelguncha bo'sh ekran / fallback | useSuspense: true (sukut) va Suspense yo'q |
Suspense qo'ying yoki tilni chizishdan oldin yuklang |
12. Mashqlar
Mashqlar kurs/mashqlar/21/02-react-i18next/ da. Jonli yechimlarni «Ishga tushir» bilan sinang.
1-mashq (oson): bo'sh joylarni to'ldiring
- Komponentga
tni beradigan hook: - Ruscha
t("qoldi", { count: 5 })qaysi qo'shimchali kalitni oladi:qoldi_ - Boshqa namespace'dagi kalitda namespace va kalit orasidagi belgi (
umumiy?sahifaNomi): count: 0uchun alohida matn qo'shimchasi:_- React bilan ishlaganda
escapeValueqiymati:
Yechim
useTranslation — t va i18n ni beradi va til almashganda komponentni qayta chizadi. 5 ruschada many (5 задач). Namespace — ikki nuqta bilan: umumiy:sahifaNomi. _zero — i18next qo'shimchasi, Intl kategoriyasi emas. escapeValue: false — React o'zi ekranlaydi, ikkinchi marta kerak emas.
2-mashq (o'rta): «Bahor» savatchasi ruscha
«Bahor» savatchasi uchun savat kalitini uch tilda yozing:
- o'zbekcha: 0 → «Savatcha bo'sh», boshqasi → «Savatchada N ta taom»;
- ruscha: 0 →
Корзина пуста, 1 →В корзине 1 блюдо, 2 →В корзине 2 блюда, 5 →В корзине 5 блюд; - inglizcha: 0 → «Your cart is empty», 1 → «1 dish in your cart», 3 → «3 dishes in your cart».
Komponent til tugmalarini va [0, 1, 2, 5, 21] sonlari uchun ro'yxatni chizsin. Русский bosilganda ekranda ruscha ro'yxat bo'lsin.
Ishora: ko'plik qo'shimchalari — «Ko'plik shakllari» bo'limidagi jadval; _zero — o'sha bo'limning oxirida.
Yechim
import { Fragment } from "react";
import i18next from "i18next";
import { initReactI18next, useTranslation } from "react-i18next";
i18next.use(initReactI18next).init({
lng: "uz",
fallbackLng: "uz",
resources: {
uz: {
translation: {
savat_zero: "Savatcha bo'sh",
savat_one: "Savatchada {{count}} ta taom",
savat_other: "Savatchada {{count}} ta taom",
},
},
ru: {
translation: {
savat_zero: "Корзина пуста",
savat_one: "В корзине {{count}} блюдо",
savat_few: "В корзине {{count}} блюда",
savat_many: "В корзине {{count}} блюд",
savat_other: "В корзине {{count}} блюда",
},
},
en: {
translation: {
savat_zero: "Your cart is empty",
savat_one: "{{count}} dish in your cart",
savat_other: "{{count}} dishes in your cart",
},
},
},
interpolation: { escapeValue: false },
});
const LANGS = [
["uz", "O'zbekcha"],
["ru", "Русский"],
["en", "English"],
];
function App() {
const { t, i18n } = useTranslation();
return (
<>
<p>
{LANGS.map(([lng, name]) => (
<Fragment key={lng}>
<button
lang={lng}
onClick={() => i18n.changeLanguage(lng)}
>
{name}
</button>{" "}
</Fragment>
))}
</p>
<ul>
{[0, 1, 2, 5, 21].map((n) => (
<li key={n}>{t("savat", { count: n })}</li>
))}
</ul>
</>
);
}Ekranda:
O'zbekcha Русский English
Корзина пуста
В корзине 1 блюдо
В корзине 2 блюда
В корзине 5 блюд
В корзине 21 блюдо21 — one (блюдо). savat_other ruschada kasr sonlar uchun (1.5 блюда) — bu ro'yxatda ishlatilmadi, lekin har tilda bo'lishi kerak. Tugmalarda lang — har til nomi o'z talaffuzi bilan o'qiladi.
3-mashq (qiyin): Trans va kech keladigan til
Kirish taklifi: «Buyurtmalar tarixi uchun kiring» — «kiring» havola (/kirish). Talablar:
- o'zbekcha —
resourcesda, ruscha (<havola>Войдите</havola>, чтобы увидеть историю заказов.) — «Boshqa tillarni kech yuklash» bo'limidagi kabi 200 ms kechikadigan backend'dan; Русскийbosilganda fayl kelguncha tugmadisabledbo'lsin va matni «…» ga almashsin;- til almashganda
<html lang>yangilansin va konsolga chiqsin; - havola
Trans+componentsbilan.
Sinov: Русский ni bosing, oraliqda «…» ko'rinsin, oxirida ruscha gap va havola.
Yechim
import { useEffect, useRef, useState } from "react";
import i18next from "i18next";
import { initReactI18next, Trans } from "react-i18next";
import { useTranslation } from "react-i18next";
const UZ = "Buyurtmalar tarixi uchun <havola>kiring</havola>.";
const RU = "<havola>Войдите</havola>, чтобы увидеть историю заказов.";
const FILES = { ru: { translation: { kirish: RU } } };
i18next
.use({
type: "backend",
init() {},
read(lng, ns, done) {
setTimeout(() => done(null, FILES[lng][ns]), 200);
},
})
.use(initReactI18next)
.init({
lng: "uz",
fallbackLng: "uz",
resources: { uz: { translation: { kirish: UZ } } },
partialBundledLanguages: true,
interpolation: { escapeValue: false },
react: { useSuspense: false },
});
i18next.on("languageChanged", (lng) => {
document.documentElement.lang = lng;
console.log("lang:", lng);
});
function App() {
const { i18n } = useTranslation();
const [loading, setLoading] = useState(false);
const textRef = useRef(null);
useEffect(() => {
console.log(textRef.current.innerHTML);
}, [i18n.language]);
async function choose(lng) {
setLoading(true);
await i18next.changeLanguage(lng);
setLoading(false);
}
return (
<>
<button disabled={loading} onClick={() => choose("ru")}>
{loading ? "…" : "Русский"}
</button>
<p ref={textRef}>
<Trans
i18nKey="kirish"
components={{ havola: <a href="/kirish" /> }}
/>
</p>
</>
);
}Konsolda:
Buyurtmalar tarixi uchun <a href="/kirish">kiring</a>.
lang: ru
<a href="/kirish">Войдите</a>, чтобы увидеть историю заказов.Ekranda:
Русский
Войдите, чтобы увидеть историю заказов.Effekt i18n.language ga bog'langan: til almashgan render'dan keyin bir marta ishlaydi va DOM'dagi tayyor havolani ko'rsatadi. lang: ru undan oldin chiqdi — languageChanged hodisasi React chizishidan oldin keladi. Til fayli kelguncha tugma disabled — ikki marta bosib ikkita so'rov yuborib bo'lmaydi.
4-mashq: vazifalar qadami
feat/i18n branch'ida «Vazifalar qadami» ni bajaring: paketlar, src/i18n/ (index.ts, uz/ va ru/ — umumiy, sozlamalar), TilTanlov, main.tsx dagi tilniTayyorla, qobiq/statistika/sozlamalar/404 matnlari t() ga, vitest-sozlash.ts va resolveJsonModule. npm run check toza bo'lsin. Keyin:
- Sozlamalarda
Русскийni tanlang va DevTools → Network'da qaysi fayllar yuklanganini ko'ring. - Ro'yxatga 21 ta vazifa qo'shing (yoki 5 tasini bajaring) — ruscha hisobni o'qing.
vitest-sozlash.tsdagi i18n importini vaqtincha olib tashlab,npm testni ishga tushiring.
Yechim
- Ikki kichik chunk:
umumiy-….jsvasozlamalar-….js(gzip 0.64 va 0.34 KB), bir martadan. O'zbekcha sahifada ular umuman yuklanmagan edi. Осталась 21 задача, …— 21onekategoriyasi. 5 bajarilsa —5 выполнено(many).- Hisob matnini tekshiradigan komponent testlari yiqiladi: ekranda «2 ta vazifa qoldi» o'rniga kalit —
hisob.matn. i18next sozlanmagan,t()kalitning o'zini qaytaradi. Importni qaytaring.
13. Real ishda
- Tarjima jarayoni: kodda faqat o'zbekcha (asosiy til) fayl yoziladi. Qolganini tarjimonlar maxsus xizmatlarda (Crowdin, Locize, Tolgee) to'ldiradi, keyin fayllar repoga qaytadi. Kalitlarni koddan avtomatik yig'ish (i18next-cli) va kalit turlari — i18n II da.
- O'zbekistondagi loyihalar odatda uch tilli: o'zbek (lotin, ba'zan kirill), rus, ingliz. Ruscha ko'plik — har intervyu topshirig'ida "tuzoq" bo'lishi mumkin.
- Intervyu savollari: "i18n va l10n farqi?", "Ruscha ko'plikni qanday yozasiz?", "Nega
escapeValue: false?", "Transnima uchun kerak?", "Til faylini qanday kech yuklaysiz?", "Tilni qayerda saqlaysiz?".
Xulosa
- i18n: komponent kalit aytadi (
t("hisob.qoldi")), matn — tarjima faylida;fallbackLng— doim o'z asosiy tilingiz. useTranslationtil almashganda komponentni qayta chizadi;i18next.t()— faqat React tashqarisida.- Interpolatsiya
{{ism}}; React bilanescapeValue: falsexavfsiz — chunki React ekranlaydi,innerHTMLbilan esa yo'q. - Ko'plik —
Intl.PluralRuleskategoriyasi qo'shimcha bo'lib: o'zbekcha_one/_other, ruscha_one/_few/_many/_other, nol uchun_zero. - Namespace — fayl bo'yicha guruh (
useTranslation("sozlamalar"),t("umumiy:…")); havolali gap —Trans+components. vazifalar: o'zbekcha build'da va sinxron, ruscha —import()bilan kech chunk; birinchi yuklash +17.1 KB (o'zt()+0.1) — halol, qabul qilingan.
Keyingi dars: i18n II: o'zbek lotin/kirill, URL va type-safe kalitlar — til manzilga ko'chadi (/ru/…), kirill varianti skript bilan yasaladi. Noto'g'ri kalit tsc xatosi beradi, sana va foiz Intl bilan formatlanadi.
Manbalar
- i18next: "Configuration Options" (
fallbackLng,partialBundledLanguages,supportedLngs), "Plurals", "Interpolation", "Namespaces", "Add or Load Translations" — i18next.com - react-i18next: "useTranslation (hook)", "Trans Component", "Using with Suspense" — react.i18next.com
- i18next-browser-languagedetector, i18next-http-backend — github.com/i18next
- Unicode CLDR: "Language Plural Rules" — unicode.org/cldr
- MDN:
Intl.PluralRules,langglobal atribut — developer.mozilla.org - Kanon:
vazifalarholat 02 (src/i18n/,TilTanlov.tsx,Statistika.tsx),natijalar/tekshiruv-02.txt,02-axe.txt,brauzer-tekshiruv/natija-21-02.txt,natijalar/tajriba/kutubxonalar-21.txt, TEXNIK-QARZ 60–61 (2026-10-17)
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!