8.30-bob: i18n (ko'p til) backend
8-QISM — NestJS (chuqur) · 30-mavzu · Amaliy real mavzu
1. Kirish va motivatsiya
Bu — 8-QISM va "real amaliy mavzular" turkumining yakuniy bobi: i18n (internationalization — xalqarolashtirish, ko'p til). O'zbekistonda deyarli har ilova kamida uch tilda ishlashi kerak: o'zbek, rus, ingliz. Va xalqaro bozorga chiqsangiz — yana ko'proq. Ko'p dasturchi i18n'ni faqat frontend masalasi deb o'ylaydi (tugma, menyu tarjimasi), lekin backend ham ko'p tilni qo'llab-quvvatlashi kerak: xato xabarlari ("Email band" / "Email zanyat" / "Email taken"), email/SMS matni (8.10, 5.18), bildirishnoma 8.23-bob, kontent (mahsulot nomi/tavsifi ko'p tilda), va valyuta/sana/raqam formati.
Backend i18n'ning ikki qismi bor: statik tarjima (xato xabarlari, email shablonlari — kod bilan birga, kalit-qiymat fayllar) va dinamik kontent (mahsulot nomi, maqola — DB'da ko'p tilda saqlanadi). Birinchisi — nestjs-i18n kabi kutubxona (til faylllari); ikkinchisi — DB dizayni (har til uchun ustun yoki JSON). Va eng muhim — tilni aniqlash (foydalanuvchi qaysi tilni xohlaydi — header, profil, parametr) va format (sana, raqam, valyuta — har til/mamlakatda boshqacha).
Bu bob: nega backend i18n (faqat frontend emas), tilni aniqlash (Accept-Language/header/profil), statik tarjima (nestjs-i18n — xato/email), dinamik kontent (DB ko'p til — ustun vs JSON vs jadval), format (sana/raqam/valyuta — Intl), pluralization (ko'plik shakllari), va fallback (til topilmasa). Bu bob 8.5 (validatsiya xabari), 8.10 (email), 8.12 (bot i18n), 11 (frontend i18n) bilan bog'liq. i18n — keng auditoriya, professional ilova belgisi.
O'xshatish: i18n — xalqaro mehmonxona xizmati. Mehmon (foydalanuvchi) qaysi tilda gaplashishini aytadi (Accept-Language — "men ingliz tilida"), va mehmonxona unga o'sha tilda xizmat ko'rsatadi: belgilar (statik tarjima — xato xabarlari), menyu (dinamik kontent — taom nomlari), narx (valyuta formati — $/so'm), sana (format — 06/22 yoki 22.06). Yaxshi mehmonxona — har tilni biladi (yoki kamida asosiy tillarni — fallback). Til bilmaslik — mehmonni yo'qotish (foydalanuvchi tushunmaydi ketadi). Backend — ko'p tilli xizmatchi.
Nega muhim?
- O'zbekiston — o'zbek/rus/ingliz (deyarli har ilova).
- Backend ham — xato, email, kontent (faqat frontend emas).
- Keng auditoriya — ko'p til = ko'p foydalanuvchi.
- Professional — xalqaro, sifatli ilova belgisi.
2. Nazariya — chuqur tushuntirish
2.0. i18n vs l10n vs g11n — atamalar farqi
Ko'p manbada bir-biriga yaqin uchta atama uchraydi va ular ko'pincha aralashtiriladi. Aslida ular jarayonning turli bosqichlari:
i18n (internationalization — xalqarolashtirish):
Kodni har qanday til/mintaqaga MOSLASHUVCHAN qilib yozish.
Matnni koddan ajratish (kalit-qiymat), tilni parametr qilish,
formatlarni Intl'ga topshirish. "Kod tayyor, endi til qo'shsa bo'ladi".
18 harf "internationalization" so'zining i...n orasida (18 harf) i18n.
l10n (localization — lokalizatsiya):
Aniq bir til/mintaqa uchun MAZMUN tayyorlash.
uz.json'ni to'ldirish, rus tarjimoniga bermoq, so'm valyutasini
sozlash, sanani "22-iyun" ko'rinishida. "Endi rus tilini qo'shdik".
"localization" — 10 harf o...n orasida l10n.
g11n (globalization — globallashtirish):
i18n + l10n + biznes jarayoni (yuridik, to'lov, qo'llab-quvvatlash).
┌─────────────┬──────────────────────────┬─────────────────────┐
│ Atama │ Nima qiladi │ Kim qiladi │
├─────────────┼──────────────────────────┼─────────────────────┤
│ i18n │ Kodni moslashuvchan qil │ Dasturchi (bir mart)│
│ l10n │ Til/mintaqa mazmuni │ Tarjimon (har til) │
│ g11n │ Butun global biznes │ Jamoa │
└─────────────┴──────────────────────────┴─────────────────────┘i18n vs l10n: i18n (internationalization) — kodni tilga bog'liq bo'lmagan, moslashuvchan qilish (matn koddan ajratilgan, format
Intl'da) — dasturchi bir marta qiladi. l10n (localization) — aniq til/mintaqa uchun mazmun (tarjima, valyuta, sana ko'rinishi) — har til uchun qilinadi (tarjimon). Oddiy qilib: i18n — "til qo'shsa bo'ladigan" idish tayyorlash; l10n — o'sha idishga aniq tilni quyish. Bu bobda asosan i18n (idish — kod, nestjs-i18n, resolverlar), lekin l10n mazmuni (uz/ru/en fayllari, so'm formati) ham ko'riladi. Raqamlar — so'z harflari soni (i·18·n = "i" + 18 harf + "n").
2.1. Nega backend i18n (faqat frontend emas)
FRONTEND i18n yetmaydi — BACKEND ham ko'p til kerak:
Xato xabarlari (validatsiya, biznes — 8.5): "Email band"/"Email zanyat"
Email/SMS matni (8.10, 5.18): xabar foydalanuvchi tilida
Push bildirishnoma 8.23-bob
Dinamik kontent (mahsulot, maqola — DB ko'p til)
Format (sana, raqam, valyuta — server generatsiya: PDF, hisobot)
Bot (8.12 — to'liq backend i18n)
Backend foydalanuvchiga matn yuboradi u tilni bilishi kerakNega backend i18n: frontend i18n (tugma/menyu) yetmaydi — backend ham foydalanuvchiga matn yuboradi: xato xabarlari 8.5-bob, email/SMS (8.10, 5.18), push 8.23-bob, dinamik kontent (DB), format (PDF/hisobot — 8.21). Bot 8.12-bob — to'liq backend i18n (frontend yo'q). Backend foydalanuvchi tilini bilishi kerak (aks holda inglizcha xato o'zbek foydalanuvchiga — yomon UX). Backend i18n — to'liq ko'p tilning yarmi.
2.2. Tilni aniqlash (qaysi til)
FOYDALANUVCHI TILINI ANIQLASH (prioritet):
1. So'rov parametri: ?lang=uz (aniq — eng yuqori)
2. Foydalanuvchi profili: user.til (kirgan — saqlangan tanlov)
3. Header: Accept-Language: uz-UZ,ru;q=0.9 (brauzer)
4. Custom header: X-Lang: uz
5. Default: o'zbek (fallback — 2.7)
Aniq (parametr) > saqlangan (profil) > brauzer (header) > defaultTilni aniqlash (prioritet bilan): so'rov parametri (
?lang=uz— eng aniq), foydalanuvchi profili (saqlangan tanlov — kirgan), Accept-Language header (brauzer afzalligi), custom header, default (fallback — 2.7). Prioritet: aniq tanlov > saqlangan > brauzer > default. nestjs-i18n — resolverlar (har manbadan til). Til aniqlanmasa — default (hech qachon xato emas). Bu — i18n'ning birinchi qadami.
Resolverlar to'liq va prioritet mexanizmi
nestjs-i18n tilni bir nechta resolver orqali aniqlaydi. Har so'rovga resolverlar massivdagi tartib bo'yicha (yuqoridan pastga) yuriladi: birinchi resolver null bo'lmagan qiymat qaytarsa — shu til ishlatiladi, qolganlari tekshirilmaydi. Hech biri qiymat qaytarmasa — fallbackLanguage.
import {
QueryResolver, // ?lang=uz — so'rov parametri
HeaderResolver, // x-lang: uz — custom header
AcceptLanguageResolver, // Accept-Language: uz-UZ,ru;q=0.9 — brauzer
CookieResolver, // lang=uz — cookie (brauzer sessiyasi saqlaydi)
} from "nestjs-i18n";
resolvers: [
{ use: QueryResolver, options: ["lang", "l"] }, // 1. ?lang=uz yoki ?l=uz
new HeaderResolver(["x-lang"]), // 2. x-lang header (mobil app)
new CookieResolver(["lang"]), // 3. lang cookie
AcceptLanguageResolver, // 4. brauzer afzalligi
// 5. hech biri fallbackLanguage (forRoot'da)
],Har resolverning hayotiy holati:
QueryResolver—?lang=uz. Foydalanuvchi tilni ataylab tanlaganda (masalan, ilova ichida til almashtirgichdan link). Eng ustun — chunki bu aniq, joriy niyat.HeaderResolver—x-lang: uz. Mobil ilova/API mijozi har so'rovga o'z tilini qo'yadi (brauzer emas — dastur boshqaradi).CookieResolver—lang=uz. Brauzer sessiyasida til saqlanadi (foydalanuvchi bir marta tanlagach, keyingi so'rovlarga avtomatik qo'shiladi).AcceptLanguageResolver—Accept-Language: uz-UZ,ru;q=0.9,en;q=0.8. Brauzer OT tilidan avtomatik yuboradi.q=(quality) — afzallik og'irligi (uz > ru > en). Yangi mehmon uchun yaxshi taxmin (hali tanlamagan).
Custom resolver — foydalanuvchi profilidan. Ko'p ilovada eng ishonchli manba — login qilgan foydalanuvchi profilidagi til (user.til). Buni maxsus resolver bilan qo'shamiz:
import { Injectable, ExecutionContext } from "@nestjs/common";
import { I18nResolver } from "nestjs-i18n";
@Injectable()
export class UserLangResolver implements I18nResolver {
resolve(context: ExecutionContext): string | undefined {
const req = context.switchToHttp().getRequest();
// JwtAuthGuard req.user'ni to'ldiradi 8.7-bob — undagi til
return req.user?.til; // topilmasa undefined keyingi resolver
}
}
// forRoot resolvers ro'yxatida (prioritet joyiga qo'ying):
resolvers: [
{ use: QueryResolver, options: ["lang"] }, // 1. aniq tanlov (link bosdi)
UserLangResolver, // 2. profil (kirgan foydalanuvchi)
AcceptLanguageResolver, // 3. brauzer (mehmon)
],Resolver prioriteti: resolverlar massivdagi tartib bo'yicha tekshiriladi — birinchi
nullbo'lmagan qiymat g'olib. Shuning uchunQueryResolver(aniq, joriy niyat) yuqorida,AcceptLanguageResolver(taxmin) pastda.CookieResolver— brauzer til tanlovini eslab qoladi; customUserLangResolver— eng ishonchli (profil,user.til). Custom resolverI18nResolver'ni implement qiladi vaundefinedqaytarsa keyingi resolver tekshiriladi (zanjir). Tartibni siz belgilaysiz — bu i18n xatti-harakatining o'zagi.
@I18n() dekorator va I18nContext
Xatoni tarjima qilishda tilni qo'lda uzatish shart emas — nestjs-i18n joriy so'rov tilini avtomatik biladi. Uni olishning ikki yo'li:
1) @I18n() dekorator — kontroller/method argumentiga joriy tarjima kontekstini oladi:
import { I18n, I18nContext } from "nestjs-i18n";
@Get("salom")
salom(@I18n() i18n: I18nContext) {
return i18n.t("umumiy.salom"); // joriy tilda (resolver aniqlagan)
}2) I18nContext.current() — dekoratorsiz, chuqur servisda (masalan, subscriber, exception filter — argument uzatib bo'lmaydigan joyda) joriy kontekstni statik chaqiradi:
import { I18nContext } from "nestjs-i18n";
@Injectable()
export class NarxService {
hisobla() {
const lang = I18nContext.current()?.lang ?? "uz"; // joriy til
// ... shu til bilan format
}
}
@I18n()vsI18nContext.current():@I18n()— kontroller/method argumenti, o'qish oson va tavsiya etiladi.I18nContext.current()— argument uzatib bo'lmaydigan chuqur joy (filter, subscriber, guard) uchun statik kirish; u so'rov konteksti (AsyncLocalStorage) orqali joriy tilni topadi.I18nContext.current()faqat so'rov oqimida ishlaydi (masalan cron/queue ishchisida — 8.16 — so'rov yo'q, shuning uchun tilundefined; unda tilni qo'lda{ lang }bilan uzating).I18nService(DI) — universal, ammo tilni oshkora bermasangiz fallback ishlatadi.
2.3. Statik tarjima (nestjs-i18n)
// nestjs-i18n — til fayllaridan tarjima (xato, email)
import { I18nModule, AcceptLanguageResolver, QueryResolver } from "nestjs-i18n";
I18nModule.forRoot({
fallbackLanguage: "uz", // default (2.7)
loaderOptions: { path: "src/i18n/" }, // til fayllari
resolvers: [ // til aniqlash (2.2)
{ use: QueryResolver, options: ["lang"] }, // ?lang=uz
AcceptLanguageResolver, // header
],
});// src/i18n/uz/xato.json
{ "email_band": "Bu email allaqachon ro'yxatdan o'tgan", "topilmadi": "{{narsa}} topilmadi" }
// src/i18n/ru/xato.json
{ "email_band": "Etot email uje zaregistrirovan", "topilmadi": "{{narsa}} ne nayden" }
// src/i18n/en/xato.json
{ "email_band": "This email is already registered", "topilmadi": "{{narsa}} not found" }Statik tarjima (nestjs-i18n): til fayllari (uz/ru/en — kalit-qiymat JSON),
forRoot(fallback, resolverlar — 2.2). Xato/email matnlari kalit bilan (email_band) — har tilda tarjima.{{narsa}}— o'zgaruvchi (interpolyatsiya). Kod tilga bog'liq emas (kalit ishlatadi). Tarjimalar fayl/DB'da (kodda emas — qo'shish/o'zgartirish oson). Bu — statik matnlar (xato, email — 2.4).
Fayl tuzilishi, nested kalitlar va t() / translate()
Til fayllari loaderOptions.path katalogida til kodi bo'yicha papkalarga bo'linadi; har papkadagi fayl nomi — kalitning birinchi bo'lagi (namespace):
src/i18n/
├── uz/
│ ├── xato.json kalit: "xato.email_band"
│ ├── email.json kalit: "email.tasdiqlash_subject"
│ └── umumiy.json kalit: "umumiy.salom"
├── ru/ (aynan shu fayllar, ruscha qiymat)
└── en/ (aynan shu fayllar, inglizcha qiymat)Kalitlarni guruhlab (nested — ichma-ich) yozish tavsiya etiladi — bu tartibni saqlaydi:
// src/i18n/uz/xato.json — nested (guruhlangan) kalitlar
{
"email": {
"band": "Bu email allaqachon ro'yxatdan o'tgan",
"notogri": "Email formati noto'g'ri"
},
"auth": {
"ruxsat_yoq": "Bu amalga ruxsatingiz yo'q",
"parol_qisqa": "Parol kamida {{min}} ta belgidan iborat bo'lsin"
}
}Bunda kalit nuqta bilan bo'linadi: xato.email.band, xato.auth.parol_qisqa. Fayl kalibga aylanmaydi — chuqurroq daraxt tuziladi, bu 50+ kalitni boshqarishni osonlashtiradi.
I18nService'ning tarjima metodi ikki nom bilan mavjud — ular bir xil: translate() — to'liq nom, t() — qisqa taxallus (alias). Ikkalasi ham { lang, args } opsiyalarini oladi:
this.i18n.translate("xato.email.band"); // to'liq
this.i18n.t("xato.email.band"); // qisqa (alias) — bir xil
this.i18n.t("xato.auth.parol_qisqa", { args: { min: 8 } }); // interpolyatsiya
this.i18n.t("email.subject", { lang: "ru" }); // tilni majburlashNested kalitlar va
t/translate: til fayllaritil/fayl.jsonsxemasi bilan joylashadi; fayl nomi — kalitning namespace'i (xato.jsonxato.email.band). Kalitlarni guruhlab (nested obyekt) yozish tartibni saqlaydi.t()vatranslate()— bir xil metod (biri taxallus);{ lang }bilan tilni majburlash mumkin (masalan cron ishchisida so'rov konteksti yo'q — 2.2). Til fayllari barcha tilda bir xil kalit tuzilishiga ega bo'lishi kerak — birida kalit yo'q bo'lsa, o'sha tilda fallback ishga tushadi 2.7-bob.
Interpolatsiya — {{o'zgaruvchi}} vs ICU {o'zgaruvchi}
Tarjima matniga dinamik qiymat (ism, son, narsa nomi) qo'yishni interpolatsiya deyiladi. nestjs-i18n'da ikki xil qavs sintaksisi bor va ular aralashtirilmasligi kerak:
// src/i18n/uz/umumiy.json — ikki xil qavs
{
"salom": "Salom, {{ism}}!", // 1) ODATIY interpolatsiya — ikki qavs
"qoldiq": "Sizda {count, plural, one {# ta} other {# ta}} xabar bor" // 2) ICU — bitta qavs
}{{ism}}(ikki jingalak qavs) — oddiy o'rniga qo'yish. Qiymatargsorqali uzatiladi. Matn ichiga to'g'ridan-to'g'ri joylashadi. Aynan shu sintaksis xato/email kalitlarida ishlatiladi ({{narsa}},{{min}}).{ism}(bitta jingalak qavs) — ICU MessageFormat sintaksisi. Faqatplural/selectichida ishlatiladi 2.8-bob; u yerda#— joriy son,{count, plural, ...}— bitta qavs. Buni oddiy o'rniga qo'yishda ishlatib bo'lmaydi.
Ikki holatda ham qiymat bir xil — args obyekti orqali uzatiladi:
this.i18n.t("umumiy.salom", { args: { ism: "Dilnoza" } }); // "Salom, Dilnoza!"
this.i18n.t("umumiy.qoldiq", { args: { count: 3 } }); // ICU "Sizda 3 ta xabar bor"Interpolatsiya sintaksisi: oddiy o'rniga qo'yish —
{{ism}}(ikki qavs,argsbilan uzatiladi — xato/email matnlarida); ICU (plural/select) —{count, plural, ...}(bitta qavs, ichida#joriy son). Ikkisini aralashtirmang: oddiy matnda{{}}, ICU qoidalarida{}. Ikkalasiga ham qiymatt(kalit, { args: {...} })orqali beriladi. Til fayldagi qavsni noto'g'ri yozish — eng ko'p uchraydigan i18n xatosi (qiymat o'rniga literal{{ism}}ko'rinadi).
2.4. Xato va validatsiya tarjimasi (8.5)
@Injectable()
export class UsersService {
constructor(private i18n: I18nService) {}
async yarat(dto: CreateUserDto) {
const mavjud = await this.repo.findOneBy({ email: dto.email });
if (mavjud) {
throw new ConflictException(this.i18n.t("xato.email_band")); // joriy tilda (2.3)
}
// ...
}
}
// Validatsiya xabarlari ham (8.5 — class-validator i18n)
class CreateUserDto {
@IsEmail({}, { message: i18nValidationMessage("xato.email_notogri") })
email: string;
}Xato/validatsiya tarjimasi:
i18n.t("xato.email_band")— joriy tilda xato (8.5 — ConflictException). Validatsiya (class-validator) ham —i18nValidationMessage(DTO xabarlari ko'p tilda — 8.5). Foydalanuvchi o'z tilida xato ko'radi (inglizcha "Conflict" emas — o'zbekcha "Email band"). Bu — UX uchun muhim (foydalanuvchi xatoni tushunadi). Exception filter 8.6-bob bilan ham.
I18nValidationPipe va I18nValidationExceptionFilter — mexanizm
DTO validatsiyasi (class-validator, 8.5) main.ts'da global ValidationPipe bilan ishlaydi. i18n uchun uni I18nValidationPipe bilan almashtiramiz — u xato kalitlarini (message'dagi i18nValidationMessage) joriy tilga tarjima qiladi, so'ng I18nValidationExceptionFilter javobni chiroyli JSON'ga formatlaydi:
// main.ts
import { I18nValidationPipe, I18nValidationExceptionFilter } from "nestjs-i18n";
app.useGlobalPipes(new I18nValidationPipe()); // xato message kalitini tarjima
app.useGlobalFilters(
new I18nValidationExceptionFilter({
detailedErrors: false, // false sodda { property, errors: [...] } ; true to'liq
}),
);i18nValidationMessage("xato.parol_qisqa", { min: 8 }) argumentlari class-validator konstraintidan (masalan @MinLength(8)) avtomatik uzatiladi — min qiymati til faylidagi {{min}} o'rniga qo'yiladi. Natija (?lang=uz):
{ "statusCode": 400,
"message": [ { "property": "parol",
"errors": ["Parol kamida 8 ta belgidan iborat bo'lsin"] } ] }Validatsiya pipe/filter:
I18nValidationPipe— DTO xato kalitini (i18nValidationMessage) joriy tilga tarjima qiladi;I18nValidationExceptionFilter— javobni formatlaydi (detailedErrorsbilan batafsillik darajasi). Konstraint qiymatlari (@MinLength(8)'dagi 8){{min}}'ga avtomatik uzatiladi. Bu ikkisi birga ishlatiladi va odatiyValidationPipe'ni almashtiradi (aks holda xato kaliti tarjima qilinmay ketadi — foydalanuvchixato.parol_qisqa'ni ko'radi).
Biznes xatolarni tarjima — exception filter (8.6)
Validatsiya emas, biznes exception'lari (ConflictException, NotFoundException — 8.6) uchun ikki yondashuv bor:
- Xatoni service'da tarjima qilib tashlash (Misol 3) —
throw new ConflictException(this.i18n.t("xato.email.band")). Sodda, lekin har joydai18n'ni inject qilish kerak. - Kalitni tashlash, filter'da tarjima qilish — service faqat kalit tashlaydi (
throw new ConflictException("xato.email.band")), global exception filter (8.6)I18nContext.current()orqali joriy tilni oladi va xabarni tarjima qiladi. Bu — DRY (bir joyda tarjima), lekin filter murakkabroq:
@Catch(HttpException)
export class I18nHttpExceptionFilter implements ExceptionFilter {
catch(exception: HttpException, host: ArgumentsHost) {
const res = host.switchToHttp().getResponse();
const i18n = I18nContext.current(); // joriy til (2.2)
const kalit = exception.message; // service tashlagan kalit
const xabar = i18n?.t(kalit) ?? kalit; // tarjima (yoki kalit — fallback)
res.status(exception.getStatus()).json({ message: xabar });
}
}Biznes xato tarjimasi: ikki usul — (1) service'da darhol tarjima (
i18n.tbilan throw — sodda, oshkora); (2) service kalit tashlaydi, filter (8.6)I18nContext.current()orqali tilni olib tarjima qiladi (DRY — bir markazda). 2-usul katta loyihada afzal (tarjima mantiqi bir joyda), lekin filter joriy tilni topolmasa (so'rovdan tashqari) kalitni o'zi qaytaradi — fallback 2.7-bob.
2.5. Dinamik kontent (DB ko'p til)
DINAMIK KONTENT (mahsulot, maqola — DB'da ko'p til):
USUL 1 — JSON ustun (moslashuvchan — tavsiya):
@Column("jsonb") nom: { uz: "Telefon", ru: "Telefon", en: "Phone" }
bir ustun, barcha til (oson qo'shish)
USUL 2 — Til ustunlari (oddiy, lekin qat'iy):
nom_uz, nom_ru, nom_en (har til alohida ustun)
USUL 3 — Tarjima jadvali (normalizatsiya — ko'p til):
products + product_translations (productId, lang, nom) — N til
JSON (oz til, moslashuvchan); jadval (ko'p til, qidiruv)Dinamik kontent (DB ko'p til): JSON ustun (
{ uz, ru, en }— bir ustun, moslashuvchan, oson qo'shish — tavsiya oz til uchun); til ustunlari (nom_uz,nom_ru— oddiy, qat'iy); tarjima jadvali (product_translations— normalizatsiya — ko'p til, qidiruv oson — 6.9). Tanlov: oz til (3-4) JSON; ko'p til jadval. Mahsulot nomi/tavsifi, maqola — DB'da. Statik 2.3-bob bilan farqi: kontent o'zgaradi (admin kiritadi).
Uch strategiya — afzallik/kamchilik jadvali
┌──────────────┬──────────────────────┬──────────────────────────┐
│ Strategiya │ Afzallik │ Kamchilik │
├──────────────┼──────────────────────┼──────────────────────────┤
│ 1. JSONB │ • Bir ustun, bir │ • Til bo'yicha qidiruv │
│ ustun │ qator (JOIN yo'q) │ qiyin (JSONB indeks) │
│ {uz,ru,en} │ • Til qo'shish — │ • Har qatorda barcha til │
│ │ JSON kalit qo'shish│ (bo'sh til = bo'sh key) │
│ │ • Migratsiya yengil │ • ORM tip nazorati zaif │
├──────────────┼──────────────────────┼──────────────────────────┤
│ 2. Til │ • Oddiy, tushunarli │ • Yangi til = migratsiya │
│ ustunlari │ • Har ustunga indeks │ (nom_de, nom_fr ...) │
│ nom_uz, │ • Tip qat'iy (ORM) │ • Jadval kengayadi │
│ nom_ru,... │ • SQL qidiruv oson │ • Ko'p til = ko'p ustun │
├──────────────┼──────────────────────┼──────────────────────────┤
│ 3. Tarjima │ • Cheksiz til (qator │ • Har o'qishda JOIN │
│ jadvali │ qo'shish, sxema │ • So'rov murakkabroq │
│ (entity_id, │ o'zgarmaydi) │ • N+1 xavfi 8.4-bob │
│ locale, │ • Normalizatsiya │ • Ko'proq kod │
│ field,val) │ • Til bo'yicha indeks│ │
└──────────────┴──────────────────────┴──────────────────────────┘
QOIDA:
• 2-4 barqaror til, kontent kam qidiriladi JSONB (1)
• 2-4 til, tez SQL qidiruv/filtr shart til ustunlari (2)
• Til soni oshib boradi, cheksiz kengayish tarjima jadvali (3)Tarjima jadvali (3-usul) sxemasi — eng normalizatsiyalangan yechim:
@Entity()
export class ProductTranslation {
@PrimaryGeneratedColumn("uuid") id: string;
@ManyToOne(() => Product, (p) => p.translations, { onDelete: "CASCADE" })
product: Product;
@Index() // til bo'yicha tez qidiruv
@Column({ length: 5 }) locale: string; // "uz" | "ru" | "en" | "de" ...
@Column() nom: string;
@Column("text") tavsif: string;
// (product, locale) — noyob (bir til bir marta)
}
// Yangi til (nemis) qo'shish: yangi QATOR — sxema o'zgarmaydi, migratsiya yo'q.DB strategiya tanlovi: JSONB — bir ustun/qator, JOIN yo'q, til qo'shish yengil, ammo til bo'yicha qidiruv/indeks zaif oz, barqaror til uchun ideal. Til ustunlari — oddiy, SQL qidiruv oson, tip qat'iy, ammo yangi til = migratsiya 2-4 til, filtr ko'p bo'lsa. Tarjima jadvali — cheksiz til (qator qo'shiladi, sxema o'zgarmaydi), normalizatsiya, ammo JOIN va N+1 xavfi (8.4) til soni o'sadigan loyihada. Ko'pchilik O'zbekiston ilovasi uz/ru/en bilan cheklanadi JSONB eng sodda va yetarli.
2.6. Format (sana, raqam, valyuta — Intl)
2.6. Format (sana, raqam, valyuta — Intl)
// Intl — til/mamlakatga qarab format (built-in — JS)
const narx = 1500000;
new Intl.NumberFormat("uz-UZ", { style: "currency", currency: "UZS" }).format(narx);
// "1 500 000 so'm"
new Intl.NumberFormat("en-US", { style: "currency", currency: "USD" }).format(120);
// "$120.00"
const sana = new Date();
new Intl.DateTimeFormat("uz-UZ").format(sana); // "22.06.2026"
new Intl.DateTimeFormat("en-US").format(sana); // "6/22/2026"Format (sana/raqam/valyuta —
Intlbuilt-in): har til/mamlakatda boshqacha (1 500 000 so'mvs$120.00;22.06.2026vs6/22/2026).Intl.NumberFormat(valyuta, raqam — ming ajratish),Intl.DateTimeFormat(sana). Server generatsiyada muhim (PDF chek — 8.21, email — 8.10, hisobot). Qo'lda formatlamaslik (Intl — to'g'ri, til-aware). Format — i18n'ning ko'p unutiladigan qismi.
2.7. Fallback (til topilmasa)
FALLBACK — til/tarjima topilmasa, nima qilish:
- Til qo'llab-quvvatlanmasa default (o'zbek/ingliz)
- Tarjima kaliti yo'q fallback til (yoki kalit o'zi — debug)
- Dinamik kontent: so'ralgan til yo'q mavjud til (uz)
HECH QACHON bo'sh/xato ko'rsatma (fallback har doim)
"email_band" (kalit) ko'rinsa — tarjima yo'q (xato belgisi)Fallback (til topilmasa — muhim): til qo'llab-quvvatlanmasa default; tarjima kaliti yo'q fallback til (yoki kalit o'zi — debug uchun); dinamik kontent so'ralgan tilda yo'q mavjud til. Hech qachon bo'sh/xato ko'rsatmaslik (foydalanuvchi har doim biror matn ko'rishi kerak). Kalit (
email_band) ko'rinsa — tarjima yo'q (xato belgisi — tuzatish kerak). Fallback — ishonchli i18n (har holatda matn bor).
Fallback zanjiri — fallbacks map (uz ru en)
fallbackLanguage — bitta oxirgi tayanch (masalan uz). Ammo real hayotda oraliq tayanch kerak bo'ladi: rus tilida tarjima yo'q bo'lsa, inglizchaga o'tishdan oldin o'zbekchaga urinish mantiqiyroq (o'zbek foydalanuvchisi rus tilini tanlagan bo'lsa ham, o'zbekcha matnni tushunadi). Buning uchun nestjs-i18n fallbacks obyektini beradi — u til (yoki mintaqa) tayanch til mosligini belgilaydi:
I18nModule.forRoot({
fallbackLanguage: "uz", // eng oxirgi tayanch (hamma narsa qulasa)
fallbacks: { // zanjir: kalit topilmasa qaysi tilga o'tish
"ru-*": "ru", // ru-RU, ru-KZ ... ru
"en-*": "en", // en-US, en-GB ... en
"uz-*": "uz", // uz-UZ, uz-Cyrl ... uz
ru: "uz", // rus tarjimasi yo'q o'zbekchaga urinadi
en: "uz", // ingliz yo'q o'zbekchaga
},
loaderOptions: { path: "src/i18n/" },
});Ikki vazifa bir vaqtda hal bo'ladi:
- Mintaqa normalizatsiyasi:
Accept-Language: ru-RUkelsa,ru-*rufaylini ishlatadi (har mintaqa uchun alohida fayl kerak emas). - Kalit-darajali tayanch:
ru/xato.json'daemail.bandkaliti yo'q bo'lsa —ru: "uz"qoidasi bo'yichauz/xato.json'dan olinadi (bo'sh ko'rsatilmaydi). Zanjir shunday davom etadi:ru uz fallbackLanguage.
Fallback zanjiri (
fallbacks):fallbackLanguage— bitta oxirgi tayanch;fallbacksmap — oraliq tayanch va mintaqa normalizatsiyasi."ru-*": "ru"— mintaqa variantlarini (ru-RU, ru-KZ) bitta faylga yig'adi;ru: "uz"— rus tarjimasi topilmasa o'zbekchaga o'tadi (uz ru en emas, balki loyihaga mos zanjir: mahalliy auditoriya uchun uz oxirgi tayanch mantiqiyroq). Zanjir har kalit uchun alohida ishlaydi — bitta kalit yo'qligi butun tilni qulatmaydi, faqat o'sha kalit tayanch tildan olinadi.
2.8. Pluralization va kontekst (ko'plik)
PLURALIZATION — ko'plik shakllari (til-bog'liq):
Ingliz: "1 item" / "2 items" (2 shakl)
Rus: "1 tovar" / "2 tovara" / "5 tovarov" (3 shakl — murakkab)
O'zbek: "1 mahsulot" / "5 mahsulot" (oddiy — son + birlik)
nestjs-i18n / ICU format:
"{count, plural, one {# mahsulot} other {# mahsulot}}"
Til qoidasiga mos (har tilda boshqa ko'plik)Pluralization (ko'plik — til-bog'liq): har til boshqacha (ingliz — 2 shakl; rus — 3 shakl: 1/2/5 har xil; o'zbek — oddiy). Qo'lda
if (count === 1)— yetmaydi (rus uchun murakkab). ICU format ({count, plural, one {...} other {...}}) — til qoidasiga mos. nestjs-i18n qo'llab-quvvatlaydi. Ko'plik — i18n'ning nozik qismi (oddiy konkatenatsiya xato). To'g'ri pluralization — tabiiy matn.
ICU plural — til fayllari va t() amalda
ICU MessageFormat kategoriyalari: zero, one, two, few, many, other — har til shu kategoriyalarning qismini ishlatadi (CLDR qoidalari asosida). Ingliz — one/other; rus — one/few/many/other; o'zbek — other (ko'plik shakl o'zgarmaydi, faqat son qo'shiladi). # belgisi — joriy sonning o'rni.
// src/i18n/en/umumiy.json — ingliz: 2 shakl (one / other)
{ "xabar": "{count, plural, one {# message} other {# messages}}" }
// src/i18n/ru/umumiy.json — rus: 3 muhim shakl (one / few / many)
{ "xabar": "{count, plural, one {# soobshenie} few {# soobsheniya} many {# soobsheniy} other {# soobsheniya}}" }
// src/i18n/uz/umumiy.json — o'zbek: shakl o'zgarmaydi (other yetarli)
{ "xabar": "{count, plural, other {# ta xabar}}" }t() chaqiruvi — args ichida count:
this.i18n.t("umumiy.xabar", { lang: "ru", args: { count: 1 } }); // "1 soobshenie"
this.i18n.t("umumiy.xabar", { lang: "ru", args: { count: 3 } }); // "3 soobsheniya" (few)
this.i18n.t("umumiy.xabar", { lang: "ru", args: { count: 5 } }); // "5 soobsheniy" (many)
this.i18n.t("umumiy.xabar", { lang: "uz", args: { count: 5 } }); // "5 ta xabar"Rus qoidasi (nega 1/3/5 har xil): one — oxiri 1 (11 dan tashqari: 1, 21, 31); few — oxiri 2-4 (12-14 dan tashqari: 2, 3, 22); many — oxiri 0, 5-9 yoki 11-14 (5, 11, 25). ICU bu qoidani avtomatik qo'llaydi — siz faqat to'g'ri kategoriya matnini yozasiz, sonni tekshirmaysiz.
ICU plural amalda: til faylida
{count, plural, KATEGORIYA {matn} ...}— har til o'z kategoriyalarini (one/few/many/other) beradi;#— joriy son.t(kalit, { args: { count } })— nestjs-i18n CLDR qoidasi bo'yicha to'g'ri shaklni tanlaydi. Rus ko'pligini qo'lda (if) yozib bo'lmaydi (1 va 21 —one; 2 va 22 —few; 5 va 11 —many) — ICU'ga topshiring. O'zbek uchunotheryetarli, lekin fayl tuzilishi bir xil bo'lishi uchun ICU sintaksisini baribir ishlating.
2.9. Best practices (i18n)
Backend ham i18n (xato, email, kontent — faqat frontend emas — 2.1)
Tilni aniqlash (parametr > profil > header > default — 2.2)
Statik (fayl/nestjs-i18n) vs dinamik (DB — 2.3, 2.5)
Tarjima kodda emas (fayl/DB — qo'shish oson — 2.3)
Format Intl (sana/raqam/valyuta — 2.6)
Fallback (til/kalit topilmasa — hech qachon bo'sh — 2.7)
Pluralization (ICU — til qoidasi — 2.8)
DB dizayn (JSON oz til; jadval ko'p til — 2.5)
O'zbekiston: uz/ru/en (deyarli har ilova — 2.1)2.10. Yangi til qo'shish — bosqichma-bosqich
Loyihaga to'rtinchi til (masalan qozoq — kk) qo'shish jarayoni i18n arxitekturasi qanchalik yaxshi qurilganini sinaydi. To'g'ri qurilgan bo'lsa — bu deyarli kodsiz amal:
YANGI TIL (kk) QO'SHISH:
1. Til papkasi: src/i18n/kk/ — mavjud fayllarni (xato.json, email.json,
umumiy.json) NUSXALAB, tarjimonga bering (kalitlar bir xil qoladi).
2. Ruxsat etilgan tillar ro'yxatiga qo'shing:
["uz","ru","en","kk"] (til tanlash validatsiyasi — Misol 8).
3. fallbacks map'ga 2.7-bob: "kk-*": "kk", kk: "uz" (tayanch).
4. Format locale mapga 2.6-bob: { uz, ru, en, kk: "kk-KZ" }.
5. Dinamik kontent (DB):
- JSONB: nom.kk kalitini qo'shing (migratsiya YO'Q — 2.5).
- Til ustunlari: nom_kk ustuni (migratsiya KERAK).
- Tarjima jadvali: yangi QATORlar (locale="kk") — sxema o'zgarmaydi.
Statik (fayl) + JSONB/jadval kodsiz. Til ustunlari migratsiya.Kalitlarning to'liqligini tekshirish (yangi tilda unutilgan kalit — jimgina fallback'ga tushadi, sezilmaydi) uchun nestjs-i18n typesOutputPath bilan TypeScript tiplarini generatsiya qiladi yoki CI'da oddiy skript barcha til fayllari kalitlarini solishtiradi:
// forRoot — kalitlardan TS tip generatsiyasi (kompilyatsiyada yo'q kalit — xato)
I18nModule.forRoot({
// ...
typesOutputPath: path.join(__dirname, "../src/generated/i18n.generated.ts"),
});
// i18n.t("xato.yoq_kalit") — TypeScript XATO beradi (kalit mavjud emas)Yangi til qo'shish: to'g'ri qurilgan i18n'da bu kodsiz — (1) til papkasini nusxalab tarjima; (2) ruxsat ro'yxati; (3)
fallbacks; (4) format locale; (5) DB — JSONB/jadvalda kodsiz, til ustunlarida migratsiya.typesOutputPath— kalitlardan TS tip generatsiya qiladi, unutilgan/xato kalit kompilyatsiyada ushlanadi (runtime fallback'ga tushib ketmaydi). Yangi til qo'shish osonligi — i18n dizayni sifatining o'lchovi.
2.11. Sana/valyuta chuqur va RTL (o'ngdan-chapga)
Format (2.6) Intl bilan hal bo'ladi, lekin uch nozik jihat bor. Birinchi — valyuta tilga emas, mamlakatga bog'liq: rus tilida narxni ko'rsatganda ham valyuta so'm (UZS) bo'lishi mumkin (O'zbekistondagi rus foydalanuvchi). Shuning uchun locale (matn tili) va currency (valyuta) alohida parametr:
// til = ko'rsatish tili; valyuta = biznes qarori (aralashtirmang)
new Intl.NumberFormat("ru-RU", { style: "currency", currency: "UZS" }).format(1500000);
// "1 500 000 UZS" (ruscha ko'rinish, so'm valyutasi)Ikkinchi — vaqt mintaqasi (timezone): server odatda UTC'da ishlaydi 8.16-bob, lekin foydalanuvchiga sanani uning mintaqasida ko'rsatish kerak:
new Intl.DateTimeFormat("uz-UZ", {
timeZone: "Asia/Tashkent", // foydalanuvchi mintaqasi (UTC+5)
dateStyle: "long", timeStyle: "short",
}).format(new Date());
// "2026-yil 22-iyun, 14:30"Uchinchi — RTL (right-to-left, o'ngdan-chapga): arab, urdu, ibroniy tillari o'ngdan chapga yoziladi. Backend uchun bu asosan metama'lumot vazifasi — matn yo'nalishini frontend hal qiladi, lekin backend har til uchun dir (direction) ni bilishi va PDF/email HTML'da (8.21, 8.10) dir="rtl" qo'yishi kerak:
const RTL_TILLAR = new Set(["ar", "he", "fa", "ur"]);
export function matnYonalishi(lang: string): "rtl" | "ltr" {
return RTL_TILLAR.has(lang) ? "rtl" : "ltr";
}
// Email/PDF shabloniga: <html dir="{{dir}}"> — sana/narx joyi to'g'ri joylashadiFormat chuqur va RTL: valyutani tildan ajrating (
ru-RU+UZS— ruscha ko'rinish, so'm); sanagatimeZonebering (server UTC foydalanuvchi mintaqasi — 8.16); RTL tillar (ar/he/fa/ur) o'ngdan chapga — backenddirni aniqlab email/PDF HTML'ga (dir="rtl") qo'yadi. uz/ru/en — hammasi LTR, shuning uchun O'zbekiston loyihalarida RTL ko'pincha kerak emas, lekin xalqaro bozorga chiqsangiz (arab) —dirmetama'lumotini oldindan tayyorlang.
2.12. i18n test qilish
i18n xatolari jimgina kechadi (noto'g'ri til yoki fallback — sin,ovsiz sezilmaydi), shuning uchun test muhim. Uch narsani sinang: (1) resolver to'g'ri tilni tanlaydimi; (2) tarjima kaliti hamma tilda bormi; (3) fallback ishlaydimi.
// 1) Resolver — ?lang / header to'g'ri tilni beradimi (e2e — 8.14)
it("?lang=ru ruscha xato qaytaradi", async () => {
const res = await request(app.getHttpServer())
.post("/users").query({ lang: "ru" })
.send({ email: "band@mail.uz" }); // mavjud email
expect(res.body.message).toContain("uje zaregistrirovan"); // ruscha
});
// 2) Kalit to'liqligi — har til fayli bir xil kalitga egami (unit)
it("barcha til fayllari bir xil kalitga ega", () => {
const uz = Object.keys(flatten(require("./i18n/uz/xato.json")));
const ru = Object.keys(flatten(require("./i18n/ru/xato.json")));
expect(ru).toEqual(uz); // ru'da yo'q kalit test qulaydi
});
// 3) Fallback — qo'llab-quvvatlanmaydigan til default
it("?lang=de (yo'q til) o'zbekcha (fallback)", async () => {
const res = await request(app.getHttpServer())
.post("/users").query({ lang: "de" }).send({ email: "band@mail.uz" });
expect(res.body.message).toContain("ro'yxatdan o'tgan"); // o'zbekcha (fallback)
});i18n test: (1) resolver —
?lang=ru/header e2e 8.14-bob so'rovi to'g'ri tilda javob beradimi; (2) kalit to'liqligi — har til fayli bir xil kalit to'plamiga egami (unit — yo'q kalit test qulatadi); (3) fallback — qo'llab-quvvatlanmaydigan til default'ga tushadimi. Kalit to'liqligi testi eng qimmatli — u tarjimon unutgan kalitni CI'da ushlaydi (foydalanuvchi fallback tilni ko'rmasdan oldin). i18n testsiz — sifat sekin buziladi (yangi kalit qo'shiladi, faqat bir tilda).
2.13. Frontend bilan til sinxronizatsiyasi (11/12-QISM)
Backend va frontend i18n bir xil tilni ko'rsatishi shart — aks holda menyu inglizcha, xato xabari o'zbekcha bo'lib, chalkashlik chiqadi. Sinxronizatsiya yagona haqiqat manbai (single source of truth) orqali hal bo'ladi — foydalanuvchi profilidagi til 2.2-bob:
TIL SINXRON (frontend backend):
1. Foydalanuvchi tilni tanlaydi (frontend til almashtirgich — 11-QISM).
2. Frontend: (a) o'z UI'sini almashtiradi (i18next/react-intl);
(b) backend'ga saqlaydi: PATCH /profile/language (Misol 8).
3. Har API so'rovda frontend tilni yuboradi:
- Header: Accept-Language: ru YOKI x-lang: ru (2.2 resolver);
- yoki backend profil'dan oladi (UserLangResolver — 2.2).
4. Backend javoblari (xato, kontent) SHU tilda qaytadi.
Bitta manba: user.til (profil). Frontend UI + backend javob — mos.Amaliy naqsh: frontend HTTP mijozida (axios/fetch) interseptor har so'rovga joriy tilni qo'yadi, backend esa HeaderResolver/UserLangResolver bilan o'qiydi:
// Frontend (11-QISM) — axios interceptor: har so'rovga joriy til
axios.interceptors.request.use((config) => {
config.headers["x-lang"] = i18next.language; // frontend joriy tili
return config;
});
// Backend HeaderResolver(["x-lang"]) buni o'qiydi 2.2-bob javob shu tildaFrontend-backend til sinxron: yagona haqiqat manbai —
user.til(profil, 2.2). Foydalanuvchi tilni tanlaganda: frontend o'z UI'sini almashtiradi (i18next — 11-QISM) va backend profilga saqlaydi (Misol 8); har API so'rovda til header (x-lang/Accept-Language) orqali yuboriladi — backendHeaderResolver/UserLangResolverbilan o'qiydi. Til bir joyda saqlanmasa (frontend alohida, backend alohida) — rassinxron: UI bir tilda, xato boshqa tilda. Interseptor (axios) har so'rovga tilni avtomatik qo'shadi — qo'lda unutish xavfini yo'qotadi.
3. Sintaksis — tez ma'lumotnoma
// nestjs-i18n 2.3-bob: I18nModule.forRoot({ fallbackLanguage, loaderOptions, resolvers })
// Tarjima 2.4-bob: i18n.t("xato.email_band", { args: { narsa: "Mijoz" } })
// Til aniqlash 2.2-bob: QueryResolver(["lang"]) / AcceptLanguageResolver / HeaderResolver
// Dinamik 2.5-bob: @Column("jsonb") nom: { uz, ru, en }
// Format 2.6-bob: new Intl.NumberFormat("uz-UZ", { style: "currency", currency: "UZS" })4. Batafsil kod namunalari
Misol 1 — i18n setup (2.3)
// app.module.ts
import { I18nModule, QueryResolver, AcceptLanguageResolver, HeaderResolver } from "nestjs-i18n";
import * as path from "path";
@Module({
imports: [
I18nModule.forRoot({
fallbackLanguage: "uz", // default (2.7)
loaderOptions: { path: path.join(__dirname, "/i18n/"), watch: true },
resolvers: [ // prioritet tartibida (2.2)
{ use: QueryResolver, options: ["lang"] }, // ?lang=uz (eng yuqori)
new HeaderResolver(["x-lang"]), // X-Lang header
AcceptLanguageResolver, // Accept-Language (brauzer)
],
}),
],
})
export class AppModule {}Misol 2 — Til fayllari (2.3)
// src/i18n/uz/xato.json
{
"email_band": "Bu email allaqachon ro'yxatdan o'tgan",
"topilmadi": "{{narsa}} topilmadi",
"ruxsat_yoq": "Bu amalga ruxsatingiz yo'q",
"parol_qisqa": "Parol kamida {{min}} ta belgidan iborat bo'lsin"
}// src/i18n/ru/xato.json
{
"email_band": "Etot email uje zaregistrirovan",
"topilmadi": "{{narsa}} ne nayden",
"ruxsat_yoq": "U vas net prav na eto deystvie",
"parol_qisqa": "Parol doljen soderjat minimum {{min}} simvolov"
}// src/i18n/en/xato.json
{
"email_band": "This email is already registered",
"topilmadi": "{{narsa}} not found",
"ruxsat_yoq": "You don't have permission for this action",
"parol_qisqa": "Password must be at least {{min}} characters"
}Misol 3 — Xato tarjimasi (service — 2.4)
@Injectable()
export class UsersService {
constructor(
@InjectRepository(User) private repo: Repository<User>,
private i18n: I18nService,
) {}
async yarat(dto: CreateUserDto) {
const mavjud = await this.repo.findOneBy({ email: dto.email });
if (mavjud) {
throw new ConflictException(this.i18n.t("xato.email_band")); // joriy til
}
return this.repo.save(dto);
}
async bitta(id: string) {
const user = await this.repo.findOneBy({ id });
if (!user) {
throw new NotFoundException(
this.i18n.t("xato.topilmadi", { args: { narsa: "Foydalanuvchi" } }), // {{narsa}}
);
}
return user;
}
}
// ?lang=ru "Etot email uje zaregistrirovan"; ?lang=en "This email is already registered"Misol 4 — Validatsiya i18n (8.5 — 2.4)
import { i18nValidationMessage } from "nestjs-i18n";
class CreateUserDto {
@IsEmail({}, { message: i18nValidationMessage("xato.email_notogri") })
email: string;
@MinLength(8, { message: i18nValidationMessage("xato.parol_qisqa", { min: 8 }) })
parol: string;
}
// main.ts — i18n validation pipe (8.5)
app.useGlobalPipes(new I18nValidationPipe());
app.useGlobalFilters(new I18nValidationExceptionFilter({ detailedErrors: false }));
// validatsiya xatosi foydalanuvchi tilidaMisol 5 — Dinamik kontent JSON (DB — 2.5)
@Entity()
export class Product {
@PrimaryGeneratedColumn("uuid") id: string;
@Column("jsonb") // ko'p til (JSON ustun — 2.5)
nom: { uz: string; ru: string; en: string };
@Column("jsonb")
tavsif: { uz: string; ru: string; en: string };
@Column("bigint") narx: number;
}
// Service — joriy tilda qaytarish (transformer)
@Injectable()
export class ProductsService {
constructor(private i18n: I18nService) {}
async bitta(id: string, lang: string) {
const p = await this.repo.findOneBy({ id });
return {
id: p.id,
nom: p.nom[lang] || p.nom.uz, // fallback (2.7)
tavsif: p.tavsif[lang] || p.tavsif.uz,
narx: this.formatNarx(p.narx, lang), // format (2.6)
};
}
}Misol 6 — Format (Intl — 2.6)
@Injectable()
export class FormatService {
// Valyuta (til/mamlakatga qarab)
narx(miqdor: number, lang: string): string {
const locale = { uz: "uz-UZ", ru: "ru-RU", en: "en-US" }[lang] || "uz-UZ";
const valyuta = lang === "en" ? "USD" : "UZS";
return new Intl.NumberFormat(locale, { style: "currency", currency: valyuta }).format(miqdor);
}
// Sana
sana(date: Date, lang: string): string {
const locale = { uz: "uz-UZ", ru: "ru-RU", en: "en-US" }[lang] || "uz-UZ";
return new Intl.DateTimeFormat(locale, { dateStyle: "long" }).format(date);
}
// Raqam (ming ajratish)
raqam(n: number, lang: string): string {
return new Intl.NumberFormat({ uz: "uz-UZ", ru: "ru-RU", en: "en-US" }[lang]).format(n);
}
}
// narx(1500000, "uz") "1 500 000 so'm"; sana "22-iyun, 2026"Misol 7 — Email i18n (8.10 — 2.1)
@Injectable()
export class MailService {
constructor(private i18n: I18nService, private mailer: MailerService) {}
async tasdiqlashYubor(user: User) {
const lang = user.til || "uz"; // foydalanuvchi tili (2.2)
await this.mailer.sendMail({
to: user.email,
subject: this.i18n.t("email.tasdiqlash_subject", { lang }), // tilga qarab
template: `verification-${lang}`, // til shabloni (uz/ru/en)
context: {
ism: user.ism,
sana: this.formatService.sana(new Date(), lang), // format (2.6)
},
});
}
}
// o'zbek user — o'zbekcha email; rus user — ruschaMisol 8 — Foydalanuvchi til tanlovi (2.2)
// Foydalanuvchi tilini saqlash (profil — eng ishonchli manba)
@Entity()
export class User {
@Column({ default: "uz" }) til: string; // saqlangan tanlov
}
@Patch("profile/language")
@UseGuards(JwtAuthGuard)
async tilOzgartir(@Body() dto: { til: string }, @CurrentUser() user) {
if (!["uz", "ru", "en"].includes(dto.til)) throw new BadRequestException();
await this.usersService.tilSaqla(user.id, dto.til);
return { til: dto.til };
}
// keyingi email/push/xato shu tilda (profil — 2.2)Misol 9 — Bot i18n (8.12 — to'liq backend i18n)
// Telegram bot — to'liq backend i18n (frontend yo'q — 8.12)
@Update()
export class BotUpdate {
constructor(private i18n: I18nService, private botService: BotService) {}
@Start()
async start(@Ctx() ctx: Context) {
const lang = await this.botService.userTili(ctx.from.id); // saqlangan til
await ctx.reply(
this.i18n.t("bot.salom", { lang, args: { ism: ctx.from.first_name } }),
this.botService.menyu(lang), // tugmalar ham tilda (8.12)
);
}
@Action(/lang_(uz|ru|en)/)
async tilTanla(@Ctx() ctx: Context) {
const til = ctx.match[1];
await this.botService.tilSaqla(ctx.from.id, til);
await ctx.reply(this.i18n.t("bot.til_ozgartirildi", { lang: til }));
}
}Misol 10 — i18n modul (to'liq tuzilma)
src/
├── i18n/ (til fayllari — 2.3)
│ ├── uz/ (xato.json, email.json, bot.json)
│ ├── ru/ (...)
│ └── en/ (...)
├── common/
│ └── format.service.ts (Intl — Misol 6)
└── (har modul i18n.t ishlatadi)
Oqim:
- Til aniqlash (parametr/profil/header — 2.2) resolver
- Statik matn (xato/email) i18n.t (fayl — 2.3, 2.4)
- Dinamik kontent (mahsulot) DB JSON [lang] 2.5-bob
- Format (narx/sana) Intl 2.6-bob
- Fallback (til/kalit yo'q default — 2.7)
O'zbekiston: uz/ru/en; backend ham (xato/email/bot — 2.1)5. To'g'ri va noto'g'ri holatlar
1) Faqat frontend i18n
backend xato/email inglizcha (o'zbek user tushunmaydi — 2.1)
backend ham i18n2) Tarjima kodda (hardcode)
if (lang === "uz") return "Email band" (qo'shish qiyin — 2.3)
til fayllari (kalit)3) Qo'lda format
`${narx} so'm` (til/format e'tiborsiz — 2.6)
Intl (til-aware)4) Fallback yo'q
til topilmasa bo'sh/kalit ko'rinadi (2.7)
fallback (default til)5) Qo'lda pluralization
if (n === 1) (rus uchun xato — 2.8)
ICU plural6. Keng tarqalgan xatolar va yechimlari
Xato 1 — Xato xabar noto'g'ri tilda
Sababi: til aniqlanmagan 2.2-bob. Yechimi: resolver (parametr/profil/header).
Xato 2 — Kalit ko'rinadi (tarjima yo'q)
Sababi: tarjima/fallback yo'q 2.7-bob. Yechimi: tarjima qo'sh; fallback.
Xato 3 — Format noto'g'ri (sana/narx)
Sababi: qo'lda format 2.6-bob. Yechimi: Intl.
Xato 4 — Dinamik kontent bir tilda
Sababi: DB ko'p til dizayn yo'q 2.5-bob. Yechimi: JSON/jadval.
Xato 5 — Email/push noto'g'ri tilda
Sababi: foydalanuvchi tili olinmagan 2.2-bob. Yechimi: user.til (profil).
Xato 6 — Rus ko'plik xato
Sababi: oddiy if 2.8-bob. Yechimi: ICU plural.
7. Integratsiya — bu mavzu stack'ning qayerida uchraydi
- Validatsiya 8.5-bob: xato xabarlari i18n.
- Email 8.10-bob: ko'p til shablon.
- SMS 5.18-bob, Push 8.23-bob: xabar tili.
- Bot 8.12-bob: to'liq backend i18n.
- DB 8.3-bob: dinamik kontent (JSON/jadval).
- PDF/hisobot 8.21-bob: format (Intl).
- Frontend (11): mos i18n.
- Exception filter 8.6-bob: xato tarjima.
8. Eng yaxshi amaliyotlar (best practices)
- Backend ham i18n (xato, email, kontent — 2.1).
- Tilni aniqlash (parametr > profil > header > default — 2.2).
- Statik (fayl) vs dinamik (DB) (2.3, 2.5).
- Tarjima kodda emas (fayl/DB — 2.3).
- Format Intl (sana/raqam/valyuta — 2.6).
- Fallback (hech qachon bo'sh — 2.7).
- Pluralization (ICU — 2.8).
- DB dizayn (JSON oz til; jadval ko'p til — 2.5).
- Foydalanuvchi til tanlovi (profil — 2.2).
- O'zbekiston: uz/ru/en 2.1-bob.
9. Amaliy loyiha: "Ko'p Tilli Backend"
i18n'ni amalda mustahkamlash.
Maqsad
To'liq ko'p tilli backend: xato, email, dinamik kontent, format — uz/ru/en.
Talablar (requirements)
- i18n setup: nestjs-i18n + resolverlar (Misol 1, 2.3).
- Til fayllari: uz/ru/en (xato/email — Misol 2).
- Xato tarjimasi: service (Misol 3, 2.4).
- Validatsiya i18n: DTO (Misol 4, 8.5).
- Dinamik kontent: DB JSON (Misol 5, 2.5).
- Format: Intl (narx/sana — Misol 6, 2.6).
- Email i18n: ko'p til (Misol 7, 8.10).
- Til tanlovi: profil (Misol 8, 2.2).
- Bot i18n: to'liq (Misol 9, 8.12).
- Fallback: til/kalit topilmasa 2.7-bob.
Maslahatlar (hint)
- Resolver prioritet (2.2, 1-xato).
- Tarjima fayl (2.3, 2-holat).
- Intl format (2.6, 3-xato).
- DB JSON dinamik (2.5, 4-xato).
- Fallback (2.7, 2-xato).
- user.til (2.2, 5-xato).
"Tayyor" mezonlari (acceptance criteria)
- i18n setup.
- Til fayllari (uz/ru/en).
- Xato tarjimasi.
- Validatsiya i18n.
- Dinamik kontent.
- Format (Intl).
- Email i18n.
- Til tanlovi.
- Bot i18n.
- Fallback.
Yechim kodi ataylab berilmagan — bu loyihani o'zingiz yozib ko'ring.
10. Xulosa va keyingi bobga ko'prik
Bu bobda backend i18n'ni o'rgandik:
- Nega backend i18n (xato/email/kontent — 2.1); tilni aniqlash (parametr/profil/header — 2.2).
- Statik tarjima (nestjs-i18n — fayl — 2.3, 2.4); dinamik kontent (DB ko'p til — 2.5).
- Format (Intl — sana/raqam/valyuta — 2.6); fallback 2.7-bob; pluralization (ICU — 2.8).
8-QISM (NestJS) VA "REAL AMALIY MAVZULAR" TURKUMI TO'LIQ TUGADI! Endi siz backend'da hayotda uchraydigan deyarli har qanday loyihani qura olasiz: to'lov 8.19-bob, webhook 8.20-bob, hujjat 8.21-bob, qidiruv 8.22-bob, push 8.23-bob, rasm 8.24-bob, multi-tenancy 8.25-bob, audit 8.26-bob, versioning 8.27-bob, geo 8.28-bob, subscription 8.29-bob, i18n 8.30-bob — barchasi NestJS asosida.
Keyingi bob — 9.5: Monolith vs Microservices (9-QISM Arxitektura davomi). Backend amaliy mavzular tugadi; endi arxitektura yo'nalishida davom etamiz.
Foydalanilgan rasmiy/ishonchli manbalar
- nestjs-i18n — kutubxona hujjati:
I18nModule.forRoot, resolverlar (Query/Header/Cookie/AcceptLanguage),fallbacksmap,I18nValidationPipe,i18nValidationMessage,typesOutputPath, ICU plural. - NestJS — rasmiy hujjat: modullar, DI, exception filter, ValidationPipe (i18n bilan integratsiya asosi).
- MDN —
Intl—NumberFormat(valyuta, ming ajratish),DateTimeFormat(sana,timeZone), locale formatlash. - ICU MessageFormat (unicode-org) — plural/select sintaksisi va kategoriyalari (
one/few/many/other). - Unicode CLDR — plural qoidalari (rus 1/2/5 farqi, o'zbek/ingliz ko'plik kategoriyalari).
- class-validator — validatsiya konstraintlari (
@IsEmail,@MinLength) va custommessage(i18n kaliti bilan). - i18n/l10n best practices — fallback zanjiri, dinamik kontent strategiyalari (JSONB / til ustunlari / tarjima jadvali), RTL yo'nalishi.
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!