Към съдържанието
PN Scripts

Разработка

Латински букви, скрити в кирилски думи, и българските кавички: как да ги намерите и поправите

  • PN Scripts
  • 8 мин четене

Търсачката на един магазин не намира „Поръчка“, въпреки че думата е на страницата. Ключ в превод се подрежда на грешно място. Проверката на правописа подчертава дума, която изглежда безупречно. Във всеки от тези случаи думата не е това, което изглежда: една от буквите ѝ е латинска. В българския има шест малки букви, които се рисуват точно като латинските (а, е, о, р, с, у, а в повечето шрифтове и х и к), и всеки, който превключва между българска и английска подредба на клавиатурата, рано или късно вкарва латинско „o“ в кирилска дума. Оттам копирането я разнася. Същите текстове обикновено носят и "прави" кавички, дефис на мястото на тире и „3км“ без интервал. Тук показваме как да намерите смесените думи с една команда, как да ги поправите с код и кои кавички, тирета и дефиси иска българският текст. JavaScript е пуснат на Node 22, а командата с GNU grep, точно както са отпечатани.

#Защо една скрита буква е важна

За компютъра „Пoръчка“ с латинско o (U+006F) и „Поръчка“ с кирилско о (U+043E) са различни думи. Търсенето, подреждането, проверката на правописа, пренасянето и екранните четци работят с буквите, а не с формата им, затова смесената дума не се намира, подрежда се на странни места, подчертава се и може да бъде прочетена буква по буква. Unicode има име за такива букви, confusables (лесни за объркване), а докладите му за сигурност описват думите със смесени писмености като предупредителен знак, защото същият номер се ползва, за да изглежда фалшив домейн като истински. В обикновения текст това обикновено е просто грешка на клавиатурата, но струва също толкова: продукт, който никой не намира през търсачката на сайта, е продукт, който никой не купува.

Случва се и обратното. „Mаil“ с кирилско а в английски бутон изглежда правилно и се проваля по същия начин.

#Как да намерите смесените думи с една команда

Дума, която съдържа и двете писмености, почти никога не е правилна, затова се търси лесно. Режимът Perl на GNU grep познава писменостите на Unicode:

grep -rnoP '[\p{L}]*(\p{Cyrillic}\p{L}*\p{Latin}|\p{Latin}\p{L}*\p{Cyrillic})[\p{L}]*' lang

Върху езиков файл на Laravel с две нарочно вкарани грешки командата отпечата lang/bg/shop.php:4:Пoръчка и lang/bg/shop.php:5:Mаil и не докосна правилния ред. Пуснете я върху преводите, статиите в Markdown и шаблоните си; в голям сайт първото пускане рядко не намира нищо. Ако grep се оплаче от локала, задайте първо UTF-8, например с LC_ALL=C.UTF-8 пред командата.

#Как да ги поправите с код

Поправката е думата да се сложи в една писменост. Коя писменост, решава просто гласуване: печели писмеността с повече букви в думата, а се разменят само букви, които имат двойник в другата писменост. Буква без двойник, като латинско i в българска дума, има нужда от човек.

// Latin letters that look like Cyrillic ones, and back.
const TO_CYR = { a: "а", c: "с", e: "е", o: "о", p: "р", x: "х", y: "у", k: "к",
  A: "А", B: "В", C: "С", E: "Е", H: "Н", K: "К", M: "М", O: "О", P: "Р", T: "Т", X: "Х", Y: "У" };
const TO_LAT = Object.fromEntries(Object.entries(TO_CYR).map(([l, c]) => [c, l]));
const CYR = /\p{Script=Cyrillic}/u;
const LAT = /\p{Script=Latin}/u;

export function fixWord(word) {
  const chars = [...word];
  const cyr = chars.filter((c) => CYR.test(c)).length;
  const lat = chars.filter((c) => LAT.test(c)).length;
  if (!cyr || !lat) return word;                      // one script: nothing to do
  const map = cyr >= lat ? TO_CYR : TO_LAT;           // the majority decides
  const fixed = chars.map((c) => map[c] ?? c).join("");
  return [...fixed].some((c) => CYR.test(c)) && [...fixed].some((c) => LAT.test(c)) ? null : fixed;
}

export function fixText(text) {
  return text
    .replace(/[\p{L}\p{M}]+/gu, (w) => fixWord(w) ?? w)
    .replace(/"([^"\n]*\p{Script=Cyrillic}[^"\n]*)"/gu, (_, quoted) => `„${quoted}“`);
}

for (const s of ["Пoръчка № 1024", "Mаil адрес", "Пиши ни на \"Контакти\"", "Пoртфолиo"]) {
  const word = s.split(" ")[0];
  console.log(JSON.stringify(s), "=>", JSON.stringify(fixText(s)),
    [...word].map((c) => c.codePointAt(0).toString(16).padStart(4, "0")).join(" "));
}

Кодът превръща „Пoръчка № 1024“ в „Поръчка № 1024“ и показва виновника в кодовете на първата дума: 041f 006f 0440, латинско 006f между две кирилски букви. „Mаil адрес“ става „Mail адрес“, защото думата има три латински букви и една кирилска. „Пoртфолиo“ с две латински букви се поправя наведнъж, а правите кавички около „Контакти“ стават „Контакти“. Когато fixWord() върне null, думата не може да се поправи с размяна и трябва да влезе в списък, който човек да прегледа.

Пускайте такава поправка върху текст, а не върху код: променлива или CSS клас с кирилска буква е отделна грешка, а поправката е на мястото, където текстът е написан, не само в изхода.

#Кавички, тирета и дефиси

  • Кавички. Българският текст иска „…“: долна отваряща кавичка (U+201E) и горна затваряща (U+201C), което дават и данните на Unicode CLDR за български. И "правите" кавички, и английските “извити” изглеждат чужди в български текст. « » се срещат в някои печатни книги; изберете един стил и го спазвайте.
  • Тирето между думи. Тире, което разделя части от изречението, се пише с интервал от двете страни: „Цената е важна – от нея зависи всичко.“ Дефис с интервали около него („важна - от нея“) е съкращение при писане, а не тире.
  • Дефисът в думите. Сравнителната и превъзходната степен на прилагателните и наречията се пишат с малко тире и без интервали: по-добър, най-важното, по-бързо. Съветът на Института за български език добавя и другата половина на правилото: при съществителни, глаголи и някои изрази „по“ и „най“ се пишат разделно, а „по“ получава ударение, както в „пò ми се иска“. Затова „по - добър“ с интервали е развален дефис и правилната поправка е „по-добър“, а не тире.
  • Числа и мерни единици. Между числото и мерната единица или съкращението има интервал: 2026 г., 3 км, 15 ч. Непрекъсваем интервал ги държи на един ред.
  • Препинателни знаци. Без интервал преди запетая, точка, двоеточие или въпросителен знак, един интервал след тях и многоточие (…) вместо три точки, ако стилът ви го ползва.

#Какво да оставите на мира

Автоматичните поправки имат нужда от граници. Интервалите от числа като 10-20, датите, телефонните номера и кодовете на продукти запазват дефисите си. Кавичките в програмния код са синтаксис, а екранираното \" в низ трябва да остане както е. Ключовете в езиков файл, адресите, CSS класовете и HTML атрибутите, които никой не чете, са код, докато alt, title, placeholder и aria-label са текст, който хората четат и слушат. Ред, пълен с ы и э, вероятно е на руски нарочно; едно ы в български ред е грешка на клавиатурата.

#Разширение за редактора, което прави това, докато пишете

Направихме Kavichka, разширение за Visual Studio Code за български текст в Markdown, езикови файлове, Blade, Vue, JSON и обикновен текст. То подчертава латински букви, скрити в кирилски думи, и обратното, случайни руски или украински букви, прави кавички, дефис на мястото на тире, липсващи интервали пред мерни единици, интервал пред запетая и още, предлага бърза поправка за всеки случай и поправя цял файл с една стъпка за отмяна. В кода гледа само в низовете и коментарите, в шаблоните само текста и атрибутите, които хората четат, а интервалите от числа оставя на мира. Освен това транслитерира от кирилица на латиница и прави адреси (slug). Тествано е с автоматични тестове и в VS Code 1.141 под Linux. Следва и правилото по-горе за „по“ и „най“: „по - добър“ и „най - важното“ стават „по-добър“ и „най-важното“, с дефис и без интервали, а не с тире. Разширението е обявено като „Очаквайте скоро“.

#Често задавани въпроси

Защо търсачката на сайта не намира дума, която е на страницата?

Често защото една буква от думата на страницата е латинска. Изглежда същата, но е друг знак, затова думата не съвпада с това, което хората пишат. Потърсете в източника думи със смесени писмености.

Какви кавички се пишат на български?

„…“, с отваряща кавичка долу и затваряща горе. Това са кавичките, които данните на Unicode CLDR дават за български, и тези, които ползват повечето български сайтове и издатели.

По-добър или по добър?

По-добър, с малко тире и без интервали, както при всяка сравнителна и превъзходна степен на прилагателно или наречие. „По“ и „най“ се пишат разделно, с ударение върху „по“, само пред съществителни, глаголи и в някои изрази.

Защо VS Code рисува рамки около кирилски букви?

VS Code отбелязва знаци, които могат да се объркат с други, като кирилските е и о в низове в кода. Това е друга, по-широка проверка; настройката editor.unicodeHighlight.ambiguousCharacters я изключва, ако инструмент, който съобщава само истински смесени думи, върши работата.

#Източници

Коментари

Коментари

Все още няма коментари. Задайте въпрос или споделете какво проработи при вас.

Оставете коментар

показва се до коментара; може и псевдоним
по избор; само за известия за отговори, не се показва
Обикновен текст с **удебелен**, *наклонен*, `код`, > цитати и връзки.

Следваща стъпка

pnscripts.com/bg/contact

Пишете на екипа

Попитайте за статия или ни разкажете за проект, който искате да изградим. Отговаряме до един работен ден.

Пишете ни

Семейството PN Scripts

Другите сайтове на PN Scripts

Хостингът, игрите и блогът имат свои сайтове, поддържани от същата компания.

  • pnscripts.com

    PN Scripts

    Софтуерно инженерство

    Уеб и мобилни приложения и API по поръчка, плюс нашите продукти и плъгини с отворен код.

  • games.pnscripts.com

    Игри

    Игри и игрови сървъри

    Домът на игрите на PN Scripts: безплатни браузърни игри, които се играят веднага, а игровите сървъри предстоят.

  • hosting.pnscripts.com

    Хостинг

    Хостинг и инфраструктура

    Споделен хостинг, KVM VPS, самостоятелни сървъри и домейни от същата компания, която изработва проекта ви.

  • blog.pnscripts.com

    Блог

    Статии и бележки

    Практични статии за разработка на софтуер, хостинг, отворен код и игри, писани от хората, които ги създават.

    Вие сте тук