Matn
Kirill ↔ lotin, o'zbekcha qidiruv, slugify va matnni qisqartirish.
O'zbek matni bir necha shaklda yoziladi: lotin va kirill, apostrofning to'rt-besh xil varianti
(' ʻ ‘ ’), katta-kichik harflar. Bu funksiyalar shu farqlarni bir xil tushunadi. Hammasi
sof funksiyalar: React'siz, bog'liqliksiz va SSR'da xavfsiz.
import {
cyrToLat,
latToCyr,
normalizeUz,
searchMatch,
fuzzyMatch,
splitByRanges,
slugify,
truncate,
truncateMiddle,
capitalize,
} from '@akhatmirzo/core';Transliteratsiya
Matnni yozing yoki pastdagi namunalardan birini tanlang:
- cyrToLat
- G'uzor tumani
- latToCyr
- Ғузор тумани
- normalizeUz
- guzor tumani
'use client';
import { cyrToLat, latToCyr, normalizeUz } from '@akhatmirzo/core';
import { Badge, Field, Input } from '@akhatmirzo/ui';
import { useState } from 'react';
const SAMPLES = ['Ғузор тумани', "O'zbekiston", 'Gʻuzor', 'ер, поезд, маъно', 'SHAHAR'];
export default function CoreTranslit() {
const [text, setText] = useState('Ғузор тумани');
const rows = [
{ name: 'cyrToLat', value: cyrToLat(text) },
{ name: 'latToCyr', value: latToCyr(text) },
{ name: 'normalizeUz', value: normalizeUz(text) },
];
return (
<div className="flex w-full max-w-md flex-col gap-4">
<Field label="Matn">
<Input value={text} onChange={(e) => setText(e.target.value)} />
</Field>
<div className="flex flex-wrap gap-2">
{SAMPLES.map((s) => (
<button key={s} type="button" onClick={() => setText(s)} className="cursor-pointer">
<Badge variant="outline">{s}</Badge>
</button>
))}
</div>
<dl className="divide-y divide-border rounded-xl border border-border bg-surface text-sm">
{rows.map((row) => (
<div key={row.name} className="flex items-center justify-between gap-4 px-3 py-2">
<dt className="font-mono text-xs text-text-muted">{row.name}</dt>
<dd className="font-mono break-all text-text">{row.value || '—'}</dd>
</div>
))}
</dl>
</div>
);
}
cyrToLat
function cyrToLat(s: string, options?: TranslitOptions): string;O'zbek kirill matnini lotin yozuviga o'giradi. Imlo qoidalari hisobga olinadi: so'z boshidagi
е → ye, ц → ts, ъ → ', ь tushib qoladi. Katta harflar saqlanadi — butunlay katta
so'zda Ш → SH, aks holda Sh. Kirill bo'lmagan belgilar o'zgarmaydi.
cyrToLat('Шаҳар'); // 'Shahar'
cyrToLat('ЎЗБЕКИСТОН'); // "O'ZBEKISTON"
cyrToLat('ер, поезд, маъно'); // "yer, poyezd, ma'no"
cyrToLat('центр'); // 'tsentr'
cyrToLat('Ғузор', { apostrophe: 'ʻ' }); // 'Gʻuzor'| Prop | Tip | Standart | Tavsif |
|---|---|---|---|
smajburiy | string | — | Kirill matn |
options.apostrophe | string | "'" | o', g' va ъ uchun ishlatiladigan belgi, masalan 'ʻ' |
latToCyr
function latToCyr(s: string): string;O'zbek lotin matnini kirillga o'giradi. o' va g' da apostrofning istalgan varianti qabul
qilinadi. Boshqa joydagi apostrof tutuq belgisi ъ ga aylanadi, so'z boshidagi e esa э ga.
s'h kabi ajratilgan juftliklar to'g'ri o'qiladi.
latToCyr("O'zbekiston"); // 'Ўзбекистон'
latToCyr('Gʻuzor'); // 'Ғузор'
latToCyr('SHAHAR'); // 'ШАҲАР'
latToCyr("ma'no"); // 'маъно'
latToCyr('ekran'); // 'экран'
latToCyr("Is'hoq"); // 'Исҳоқ'ts → ц tiklanmaydi
Lotin yozuvida ts ham ц, ham т + с bo'lishi mumkin, shuning uchun latToCyr('tsex') →
'тсех'. Rus tilidan kirgan so'zlarda natijani tekshiring.
normalizeUz
function normalizeUz(s: string | null | undefined): string;Qidiruv kaliti yasaydi: kichik harf, kirill → lotin, barcha apostroflar olib tashlanadi. Ikki
satrni "bir xilmi" deb solishtirish yoki o'z qidiruvingizni yozish uchun. null va undefined →
''.
normalizeUz('Gʻuzor'); // 'guzor'
normalizeUz("G'uzor"); // 'guzor'
normalizeUz('Ғузор'); // 'guzor'
normalizeUz("O'zbekiston"); // 'ozbekiston'cyrToLat bilan farqi
normalizeUz da kirill е doim e bo'ladi (so'z boshida ham): normalizeUz('Ер юзи') → 'er yuzi', cyrToLat esa 'Yer yuzi' qaytaradi. Shunda so'rov bo'lagi so'z o'rtasidagi harflarni
ham topadi.
Qidiruv
searchMatch
function searchMatch(
text: string | null | undefined,
query: string | null | undefined,
): SearchMatchResult; // { match: boolean; ranges: TextRange[] }Qism satr qidiruvi: registr, apostrof varianti va yozuv (kirill/lotin) farqi hisobga olinmaydi.
ranges — asl matndagi barcha mosliklar ([start, end), slice bilan mos), shuning uchun
kirillcha matnda lotincha so'rovni ham to'g'ri ajratib ko'rsatish mumkin. Bo'sh so'rov hamma
narsaga mos keladi. Combobox qidiruvi va DataTable filtri shu funksiyada ishlaydi.
Sinab ko'ring: chir, ғуз, fargona, qo'qon
- Shahrisabz
- Шаҳрихон
'use client';
import { searchMatch, splitByRanges } from '@akhatmirzo/core';
import { Field, Input } from '@akhatmirzo/ui';
import { useState } from 'react';
const CITIES = [
'Toshkent',
'Samarqand',
'Buxoro',
"Farg'ona",
'Qo‘qon',
'Shahrisabz',
'Chirchiq',
'Ғузор',
'Ўрта Чирчиқ',
'Шаҳрихон',
];
export default function CoreSearch() {
const [query, setQuery] = useState('шаҳ');
const results = CITIES.map((city) => ({ city, ...searchMatch(city, query) })).filter(
(r) => r.match,
);
return (
<div className="flex w-full max-w-md flex-col gap-3">
<Field label="Shahar qidirish" description="Sinab ko'ring: chir, ғуз, fargona, qo'qon">
<Input value={query} onChange={(e) => setQuery(e.target.value)} clearable />
</Field>
<ul className="flex flex-wrap gap-2">
{results.map(({ city, ranges }) => (
<li key={city} className="rounded-lg border border-border bg-surface px-3 py-1.5 text-sm">
{splitByRanges(city, ranges).map((part, i) =>
part.match ? (
<mark key={i} className="rounded-sm bg-primary-soft text-primary-soft-foreground">
{part.text}
</mark>
) : (
<span key={i}>{part.text}</span>
),
)}
</li>
))}
{results.length === 0 && <li className="text-sm text-text-muted">Hech narsa topilmadi</li>}
</ul>
</div>
);
}
searchMatch('Toshkent', 'тош'); // { match: true, ranges: [[0, 4]] }
searchMatch('Ғузор тумани', "g'uzor"); // { match: true, ranges: [[0, 5]] }
searchMatch("Farg'ona", 'fargona'); // { match: true, ranges: [[0, 8]] }
searchMatch('Ota-ona, ota', 'ota'); // { match: true, ranges: [[0, 3], [9, 12]] }
searchMatch('Shahrisabz', 'xiva'); // { match: false, ranges: [] }
searchMatch('Toshkent', ''); // { match: true, ranges: [] }fuzzyMatch
function fuzzyMatch(
text: string | null | undefined,
query: string | null | undefined,
): FuzzyMatchResult | null; // { score: number; ranges: TextRange[] }Noaniq qidiruv: so'rov harflari matnda shu tartibda uchrashi kerak, orasida boshqa harflar
bo'lishi mumkin. So'z boshidagi va ketma-ket mosliklar yuqoriroq ball oladi. Mos kelmasa —
null. Normallashtirish searchMatch bilan bir xil, so'rovdagi bo'shliqlar e'tiborga olinmaydi.
CommandPalette shu bilan ishlaydi.
Harflar tartibi muhim: mq, hsb, prof
- Yangi buyurtma23
'use client';
import { fuzzyMatch, splitByRanges } from '@akhatmirzo/core';
import { Field, Input } from '@akhatmirzo/ui';
import { useState } from 'react';
const COMMANDS = [
'Yangi buyurtma',
"Buyurtmalar ro'yxati",
"Mijoz qo'shish",
'Hisobotlar',
'Sozlamalar',
'Profilni tahrirlash',
'Mavzuni almashtirish',
];
export default function CoreFuzzy() {
const [query, setQuery] = useState('ybu');
const results = COMMANDS.flatMap((label) => {
const result = fuzzyMatch(label, query);
return result ? [{ label, ...result }] : [];
}).sort((a, b) => b.score - a.score);
return (
<div className="flex w-full max-w-md flex-col gap-3">
<Field label="Buyruq" description="Harflar tartibi muhim: mq, hsb, prof">
<Input value={query} onChange={(e) => setQuery(e.target.value)} clearable />
</Field>
<ul className="divide-y divide-border rounded-xl border border-border bg-surface text-sm">
{results.map(({ label, ranges, score }) => (
<li key={label} className="flex items-center justify-between gap-3 px-3 py-2">
<span>
{splitByRanges(label, ranges).map((part, i) =>
part.match ? (
<strong key={i} className="font-semibold text-primary-soft-foreground">
{part.text}
</strong>
) : (
<span key={i}>{part.text}</span>
),
)}
</span>
<span className="font-mono text-xs text-text-muted">{score}</span>
</li>
))}
{results.length === 0 && <li className="px-3 py-2 text-text-muted">Mos buyruq yo'q</li>}
</ul>
</div>
);
}
fuzzyMatch('Yangi buyurtma', 'ybu'); // { score: 23, ranges: [[0, 1], [6, 8]] }
fuzzyMatch('Yangi buyurtma', 'ybuy'); // { score: 29, ranges: [[0, 1], [6, 9]] }
fuzzyMatch('Sozlamalar', 'xyz'); // nullscore ning mutlaq qiymati ma'no bermaydi — undan faqat natijalarni o'zaro saralash uchun
foydalaning: results.sort((a, b) => b.score - a.score).
splitByRanges
function splitByRanges(text: string, ranges: readonly TextRange[]): TextSegment[];
// TextSegment = { text: string; match: boolean }Matnni searchMatch yoki fuzzyMatch qaytargan ranges bo'yicha bo'laklarga ajratadi —
topilgan qismni <mark> bilan chizish uchun.
splitByRanges('Toshkent', [[0, 4]]);
// [{ text: 'Tosh', match: true }, { text: 'kent', match: false }]import { searchMatch, splitByRanges } from '@akhatmirzo/core';
export function Highlight({ text, query }: { text: string; query: string }) {
const { ranges } = searchMatch(text, query);
return (
<>
{splitByRanges(text, ranges).map((part, i) =>
part.match ? <mark key={i}>{part.text}</mark> : <span key={i}>{part.text}</span>,
)}
</>
);
}Matn yordamchilari
slugify: fargona-viloyati-2026
- truncateMiddle
- shartnoma-mchj…ngan.docx
- truncate
- shartnoma-mchj-akhatmir…
'use client';
import { slugify, truncate, truncateMiddle } from '@akhatmirzo/core';
import { Field, Input } from '@akhatmirzo/ui';
import { useState } from 'react';
const FILE = 'shartnoma-mchj-akhatmirzo-imzolangan.docx';
export default function CoreTruncate() {
const [title, setTitle] = useState("Farg'ona viloyati — 2026");
const [max, setMax] = useState(24);
return (
<div className="flex w-full max-w-md flex-col gap-4">
<Field label="Sarlavha" description={`slugify: ${slugify(title) || '—'}`}>
<Input value={title} onChange={(e) => setTitle(e.target.value)} />
</Field>
<label className="flex flex-col gap-2 text-sm font-medium text-text">
Maksimal uzunlik: {max}
<input
type="range"
min={6}
max={44}
value={max}
onChange={(e) => setMax(Number(e.target.value))}
className="w-full accent-primary"
/>
</label>
<dl className="divide-y divide-border rounded-xl border border-border bg-surface text-sm">
<div className="flex flex-col gap-1 px-3 py-2">
<dt className="font-mono text-xs text-text-muted">truncateMiddle</dt>
<dd className="font-mono text-text">{truncateMiddle(FILE, max)}</dd>
</div>
<div className="flex flex-col gap-1 px-3 py-2">
<dt className="font-mono text-xs text-text-muted">truncate</dt>
<dd className="font-mono text-text">{truncate(FILE, max)}</dd>
</div>
</dl>
</div>
);
}
slugify
function slugify(s: string | null | undefined): string;URL uchun slug: o'zbekcha matn (kirill ham) lotinga o'giriladi, apostroflar olib tashlanadi,
qolgan belgilar - bilan almashtiriladi.
slugify("Farg'ona viloyati"); // 'fargona-viloyati'
slugify('Тошкент шаҳри'); // 'toshkent-shahri'
slugify('Ер юзи'); // 'yer-yuzi'
slugify("Farg'ona viloyati — 2026"); // 'fargona-viloyati-2026'truncate
function truncate(s: string, max: number, ellipsis?: string): string;Matnni oxiridan qisqartiradi. Natija ellipsis bilan birga max belgidan oshmaydi, oxirgi
bo'shliq tashlanadi. Belgilar kod nuqtasi bo'yicha sanaladi — emoji o'rtasidan bo'linmaydi.
truncate('Salom dunyo', 7); // 'Salom…'
truncate('Salom dunyo', 8, '...'); // 'Salom...'
truncate('Salom', 10); // 'Salom'truncateMiddle
function truncateMiddle(s: string, max: number, ellipsis?: string): string;Matnni o'rtasidan qisqartiradi. Fayl nomida kengaytma va nomning oxiri saqlanadi — bir xil
boshlanadigan fayllarni ajratish oson. FileUpload ro'yxati shundan foydalanadi.
truncateMiddle('hisobot-oktabr-2026.pdf', 22); // 'hisobot-oktab…2026.pdf'
truncateMiddle('shartnoma-mchj-akhatmirzo-imzolangan.docx', 24); // 'shartnoma-mchj…ngan.docx'| Prop | Tip | Standart | Tavsif |
|---|---|---|---|
smajburiy | string | — | Asl matn |
maxmajburiy | number | — | Natijaning maksimal uzunligi (ellipsis bilan birga) |
ellipsis | string | '…' | Qisqartirilgan joy belgisi |
capitalize
function capitalize(s: string): string;Birinchi harfni katta qiladi, qolganiga tegmaydi.
capitalize("o'zbekiston"); // "O'zbekiston"
capitalize('toshkent shahri'); // 'Toshkent shahri'isApostrophe va UZ_APOSTROPHES
const UZ_APOSTROPHES: string; // "ʻʼ‘’`´'"
function isApostrophe(ch: string | undefined): boolean;O'zbek yozuvida apostrof o'rnida uchraydigan barcha belgilar va ularni tekshirish funksiyasi.
isApostrophe('ʻ'); // true
isApostrophe('a'); // falseTiplar
type TextRange = [start: number, end: number]; // asl matndagi UTF-16 indekslar
interface TextSegment {
text: string;
match: boolean;
}
interface SearchMatchResult {
match: boolean;
ranges: TextRange[];
}
interface FuzzyMatchResult {
score: number;
ranges: TextRange[];
}
interface TranslitOptions {
apostrophe?: string;
}