npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

alifbo

v0.6.0

Published

Uzbek transliteration between Cyrillic, old Latin, and the 2026 Latin alphabet

Readme

alifbo

CI npm license

alifbo is a zero-runtime-dependency TypeScript library for converting Uzbek text between Cyrillic, the previous Latin alphabet, and the Latin alphabet approved by Uzbekistan's Senate in September 2026. It is designed to expose uncertainty instead of hiding it.

Try it in your browser: azakapro.github.io/alifbo (no install; your text never leaves the page). The demo counts anonymous visits and feature use with GoatCounter (no cookies, no IP addresses, and never any text or file names; Do Not Track is respected). The npm and Python libraries send nothing.

The package works in Node 20+ and browsers, and publishes ESM, CommonJS, and TypeScript declarations. The core API is pure: it performs no I/O, network access, or environment inspection.

Legal status: the Senate approved the amending law, but the new alphabet was not yet in force at the time of this release. Follow the official Senate announcement and the QL-236 legislative record on lex.uz for authoritative updates.

Alphabet change

| Previous Latin | New Latin | Lower / upper codepoints | | -------------- | --------- | ------------------------ | | oʻ | ö | U+00F6 / U+00D6 | | gʻ | ğ | U+011F / U+011E | | sh | ş | U+015F / U+015E | | ch | ç | U+00E7 / U+00C7 |

The ş emitted by this package is U+015F LATIN SMALL LETTER S WITH CEDILLA, never U+0219 (s with comma below). Fonts often make the two look nearly identical, but search, equality, and database indexes do not. U+0219/U+0218 input is accepted and folded to U+015F/U+015E.

Install and use

npm install alifbo
import { foldSearchKey, fromCyrillic, toNewLatin } from 'alifbo';

toNewLatin("O'zbekiston shaharlari");
// { text: 'Özbekiston şaharlari', warnings: [] }

fromCyrillic('Елена');
// {
//   text: 'Yelena',
//   warnings: [{ rule: 'cyrillic.e.positional', alternatives: ['e', 'ye'], ... }]
// }

foldSearchKey('Шавкат') === foldSearchKey('Shavkat'); // true

CommonJS is supported too:

const { toNewLatin } = require('alifbo');

Python

A Python 3.9+ port with the same behavior and snake_case names lives in python/:

pip install alifbo
from alifbo import to_new_latin

to_new_latin("O'zbekiston shaharlari").text  # 'Özbekiston şaharlari'

Its warning offsets are Python string indexes rather than UTF-16 offsets; see the Python README for details.

The public functions are:

  • toNewLatin(text, options) — previous Latin to new Latin.
  • toOldLatin(text, options) — new Latin to previous Latin.
  • fromCyrillic(text, options) — Cyrillic to new Latin, with ambiguity warnings.
  • toCyrillic(text, options) — new Latin to Cyrillic, with ambiguity warnings.
  • foldSearchKey(text) — canonical new-Latin, NFC, locale-independent lowercase key.
  • foldSearchKeyLoose(text) — additionally strips diacritics; useful for typo-tolerant search but deliberately lossier.
  • normalizeApostrophes(text) — positionally folds apostrophe-like characters to U+02BB or U+02BC.
  • normalizeConfusables(text) — NFC-normalizes and folds known visual confusables.
  • detectAlphabet(text) — best-effort routing hint with a confidence score.

Options

interface ConversionOptions {
  protectSpans?: boolean; // default true
  protectedTerms?: string[];
  exceptions?: Record<string, string>;
  ngAsDigraph?: boolean; // default true
  foreignWords?: 'keep' | 'transliterate'; // default 'keep', toCyrillic only
}

URLs, email addresses, backtick code spans, and seeded foreign names (Shakespeare, Chelsea, and Photoshop) are protected by default. protectedTerms adds exact literal spans. Set protectSpans: false to convert everything. User exceptions are word-level, take precedence over seeded exceptions, and are applied longest-first.

toCyrillic keeps a word as written when the Uzbek alphabet cannot spell it: a letter Uzbek does not use (Windows, Zürich, the bare c in Microsoft) or a capital after a lowercase letter (iPhone, SiO2). Converting such a word letter by letter would only mix two alphabets (Wиндоwс). The whole word is kept, suffixes and hyphenated parts included (Windowsda, Wi-Fi-ga), and reported once as latin.foreign with the word and its letter-by-letter conversion as alternatives. Well-known brand and product names spelled entirely with Uzbek letters (google, apple, zoom, telegram) are kept the same way, matched as whole words in any case and with an Uzbek case or plural suffix (googleda, Telegram-ga); the list is src/data/foreign-words.json, and every entry was checked against Uzbek text so that words such as metall, safari or uzum still convert. Pure Uzbek text is unaffected. foreignWords: 'transliterate' restores the letter-by-letter output of versions before 0.5.0.

Explicit conversion pipeline

Each conversion uses these stages:

  1. Extract protected spans.
  2. Normalize unprotected text to NFC.
  3. Fold confusables such as U+0219 to U+015F.
  4. Normalize apostrophes positionally.
  5. Apply word exceptions, longest match first.
  6. Protect the s + U+02BC tutuq + h morpheme boundary, then process digraphs.
  7. Convert oʻ to ö and gʻ to ğ (performed in the same single-pass rule engine as step 6).
  8. Restore protected spans byte-for-byte.

Protected spans are restored literally. Consequently, an intentionally protected span containing NFD text remains NFD even though converted text is always NFC.

Apostrophes and the sʼh boundary

The input characters U+02BB, U+02BC, U+0027, U+2018, U+2019, U+0060, U+00B4, U+2032, and U+FF07 are accepted. Immediately after o or g (in either case), they normalize to U+02BB, the letter modifier. Elsewhere they normalize to U+02BC, the tutuq belgisi.

asʼhob and Isʼhoq contain s and h separated by a real morpheme boundary, so they must not become aşob or Işoq. When the tutuq has already been dropped, the distinction cannot be recovered by a general rule. The package therefore includes a deliberately small seed exception dictionary. It is incomplete; contributions backed by examples are welcome.

Cyrillic warnings

Cyrillic conversion cannot be fully reversible. Positional е, ambiguous ц, щ, the hard and soft signs, and expanded ё, ю, and я produce structured warnings. Reverse conversion likewise warns when choosing among е/э, щ/шч, ц/тс, one-letter iotated forms, and interpretations of the tutuq sign. A word the Uzbek alphabet cannot spell is kept whole and reported as latin.foreign (see Options); with foreignWords: 'transliterate', each Latin letter with no Cyrillic counterpart (such as w) is instead reported as latin.unmapped, with no alternatives, as is a stray ʻ in either mode. Warning indexes and lengths use JavaScript UTF-16 string offsets into the text you passed in, even when normalization, exceptions, or the old-to-new Latin pre-pass change lengths.

The default positional rules are intentionally mechanical:

  • е becomes ye at a word start, after a vowel, or after ъ/ь; otherwise it becomes e.
  • ц becomes s at a word start (and for the second letter of цц); otherwise it becomes ts.
  • ъ becomes U+02BC; ь is dropped.
  • щ, ё, ю, and я become şç, yo, yu, and ya.

toCyrillic applies the inverse rule, so words round-trip: at a word start, after a vowel, or after the tutuq sign, ye becomes е and a bare e becomes э (Yevropa → Европа, ekran → экран, poet → поэт, podʼyezd → подъезд); elsewhere e becomes е.

Inspect warnings whenever converting to or from Cyrillic, and use a word exception when the default is wrong for a known term.

Locale-independent casing

The implementation never calls toUpperCase() or toLowerCase(). It uses explicit case tables for Latin and Cyrillic. This avoids the Turkish-locale i → İ and I → ı trap, which can silently corrupt Uzbek identifiers and search keys.

The ng rule

Reporting on the Senate-approved text says that ng is removed from the alphabet as a letter, but the letter combination itself is kept and governed by spelling rules. Either way, ng is written as ng. This still needs to be confirmed against the enacted law text in the QL-236 record on lex.uz. Until then, ngAsDigraph defaults to true. With that setting it is consumed atomically and left unchanged. Setting it to false treats n and g as ordinary adjacent letters; the visible result is currently still ng. The behavior is isolated behind the single NG_DIGRAPH_OUTPUT data constant so an authoritative replacement requires no pipeline refactor.

Deliberate non-goals

alifbo does not guess whether an unmarked word is foreign, reconstruct arbitrary dropped apostrophes, perform morphological analysis, correct spelling, or promise lossless Cyrillic round-trips. Brand names and other foreign terms must be protected explicitly. foldSearchKeyLoose can merge genuinely distinct words (for example ol and öl) and must not be used as a unique database key.

CLI

The CLI is a separate Node-only entry point; the browser-safe core never imports it.

alifbo convert --to new-latin file.txt
cat file.txt | alifbo convert --to cyrillic
cat file.txt | alifbo convert --to cyrillic --foreign-words transliterate
alifbo convert --to old-latin < input.txt > output.txt

Input files are read without modification. Converted text is written to standard output. Ambiguity warnings are written separately to standard error, so redirected output remains clean:

alifbo: warning [cyrillic.e.positional] at 0:1: ...

Author

Built by Azizullo Temirov. I share progress on alifbo and other projects here:

Telegram · YouTube · Instagram · LinkedIn

Found a wrong conversion? Open an issue with the exact input and the expected output.

Development

npm ci
npm run check

Using an AI coding assistant? Give it the AI integration guide. Agents modifying this repository should follow AGENTS.md.

See the contribution guide for adding exceptions and golden cases, the changelog for release history, and the security policy for vulnerability reporting. Released under the MIT License.