Перейти к содержанию
Innovialabs

Транслитерация узбекского латиница–кириллица в коде: правила и ловушки

Практика5 мин

Коротко

Узбекскую латиницу можно надёжно перевести в кириллицу таблицей с порядком «сначала двухсимвольные сочетания», если заранее привести все апострофы к одному символу; обратный перевод теряет информацию (ц, ъ после ў), поэтому для поиска храните нормализованный ключ.

Коротко: как устроена транслитерация

Большинство букв узбекской латиницы и кириллицы соответствуют друг другу один к одному. Сложности дают три вещи:

  • апострофы: в o‘ и g‘ пользователи вставляют до пяти разных символов;
  • диграфы: sh, ch, yo, ya нужно распознать раньше одиночных букв;
  • контекст: е, э и ц зависят от позиции в слове, а часть информации при переводе с латиницы теряется.

Рабочая схема: нормализовать апострофы → идти по строке, проверяя сначала длинные сочетания → сохранять регистр → для спорных слов держать словарь исключений.

Таблица соответствий

Прямые пары: a–а, b–б, d–д, f–ф, g–г, i–и, j–ж, k–к, l–л, m–м, n–н, o–о, p–п, r–р, s–с, t–т, u–у, v–в, x–х, y–й, z–з.

ЛатиницаКириллицаКомментарий
oʻ, gʻў, ғАпостроф входит в букву
hҳНе путать с x = х
qқ
sh, chш, чДиграфы, проверять раньше s, c
ngнгВ обеих системах две буквы
yo, yu, yaё, ю, я
ye / eе / эСм. правила ниже
s / tsцТолько при переводе из кириллицы
ʼъТутук белгиси (знак разделения)
—ьВ латинице опускается

Апострофы: o‘ и g‘

В реальных данных встречаются: ASCII ' (U+0027), обратный апостроф, ‘ (U+2018), ’ (U+2019), ʻ (U+02BB) и ʼ (U+02BC). Для букв oʻ, gʻ обычно рекомендуют U+02BB, для тутук белгиси — U+02BC.

Правило нормализации простое: любой апостроф между буквами сначала превращаем в U+02BC, а после o и g — в U+02BB. Апострофы вне слов, например кавычки, не трогаем.

Ловушки диграфов и контекста

  • yoʻl → йўл, а не ёл: если после yo стоит U+02BB, это y + oʻ.
  • mingʻirlamoq → минғирламоқ: не склеивайте n и g в «ng», если за g идёт апостроф.
  • Isʼhoq → Исҳоқ: апостроф между s и h разделяет буквы, а не означает ш или ъ.
  • е/э: из латиницы e в начале слова или после гласной — э (ertak → эртак), иначе е; ye → е (yer → ер). Из кириллицы наоборот: е в начале слова и после гласной — ye (поезд → poyezd).
  • ц: из кириллицы — s в начале слова и после согласной (цирк → sirk), ts после гласной (лицей → litsey). Обратно восстановить ц автоматически нельзя: ketsa — это кетса. Нужен словарь заимствований.
  • ё в кириллице всегда даёт yo.
  • Регистр: Ш перед строчной — Sh, внутри слова капслоком — SH.

Функция латиница → кириллица

const OKINA = "ʻ"; // ʻ in oʻ, gʻ
const TUTUQ = "ʼ"; // ʼ tutuq belgisi

function normalizeLatin(s) {
  return s
    .normalize("NFC")
    .replace(/(?<=\p{L})['`´‘’ʻʼ](?=\p{L})/gu, TUTUQ)
    .replace(/([oOgG])ʼ/g, "$1" + OKINA);
}

const L2C = [
  ["s" + TUTUQ + "h", "сҳ"], ["o" + OKINA, "ў"], ["g" + OKINA, "ғ"],
  ["sh", "ш"], ["ch", "ч"], ["yo", "ё"], ["yu", "ю"], ["ya", "я"], ["ye", "е"],
  ["a", "а"], ["b", "б"], ["d", "д"], ["f", "ф"], ["g", "г"], ["h", "ҳ"],
  ["i", "и"], ["j", "ж"], ["k", "к"], ["l", "л"], ["m", "м"], ["n", "н"],
  ["o", "о"], ["p", "п"], ["q", "қ"], ["r", "р"], ["s", "с"], ["t", "т"],
  ["u", "у"], ["v", "в"], ["x", "х"], ["y", "й"], ["z", "з"], [TUTUQ, "ъ"],
];

function latToCyr(input) {
  const s = normalizeLatin(input);
  let out = "";
  let i = 0;
  while (i < s.length) {
    const prev = s[i - 1] || "";
    let hit = null;
    for (const [lat, cyr] of L2C) {
      if (s.slice(i, i + lat.length).toLowerCase() !== lat) continue;
      if (lat === "yo" && s[i + 2] === OKINA) continue; // yoʻl -> йўл
      hit = [lat, cyr];
      break;
    }
    if (!hit && s[i].toLowerCase() === "e") {
      const startOrVowel = !/\p{L}/u.test(prev) || /[aeiou]/i.test(prev);
      hit = ["e", startOrVowel ? "э" : "е"];
    }
    if (!hit) { out += s[i++]; continue; }
    const [lat, cyr] = hit;
    const src = s.slice(i, i + lat.length);
    out += src[0] !== src[0].toLowerCase() ? cyr[0].toUpperCase() + cyr.slice(1) : cyr;
    i += lat.length;
  }
  return out;
}

Минимальные тесты, которые стоит держать в проекте:

import assert from "node:assert/strict";
assert.equal(latToCyr("O'zbekiston"), "Ўзбекистон");
assert.equal(latToCyr("O’zbekiston"), "Ўзбекистон");
assert.equal(latToCyr("yo'l"), "йўл");
assert.equal(latToCyr("ma'no"), "маъно");
assert.equal(latToCyr("Is'hoq"), "Исҳоқ");
assert.equal(latToCyr("ertak"), "эртак");
assert.equal(latToCyr("SHAHAR"), "ШАҲАР");

Нормализация для поиска

Пользователи ищут как удобно: латиницей без апострофов, кириллицей с русской раскладки без ў, қ, ғ, ҳ. Поэтому сравнивайте не исходные строки, а поисковый ключ:

const FOLD = { ў: "у", қ: "к", ғ: "г", ҳ: "х", ъ: "", ь: "", ё: "е", э: "е" };

function searchKey(text) {
  return latToCyr(text)
    .toLowerCase()
    .replace(/[ўқғҳъьёэ]/g, (c) => FOLD[c])
    .replace(/\s+/g, " ")
    .trim();
}
// searchKey("Qo‘qon") === searchKey("Кукон")

Советы:

  • Храните ключ в отдельном поле и индексируйте его, а не считайте на лету.
  • Оригинальный текст не меняйте: ключ нужен только для сравнения.
  • Если запрос вида Ozbekiston без апострофа частый, добавьте второй ключ, где апострофы просто удаляются.

Частые вопросы

Для хранения выберите один канонический вариант, обычно U+02BB, и приводите к нему все входящие данные. Для отображения можно использовать и типографский ‘, главное — не смешивать варианты в базе.

Поможем с этим

Если хотите не разбираться самим, а получить результат — этим мы занимаемся каждый день.

Обсудить проект

Читайте также

Есть идея или задача? Расскажите нам

Ответим в течение рабочего дня и предложим решение. Консультация бесплатная.