Most people who write an Indian language on a phone or a laptop don't use its script keyboard. They type it in English letters: "kemon acho", "kem cho", "eppadi irukkeenga". Turning those letters into the language's own script is called transliteration, and the tools that do it well usually run on a server or carry word lists of hundreds of megabytes.
Lekhani started on 30 October with Hindi, from a 0.87 MB download that runs inside a web page. Telugu, Tamil and Kannada followed. Today it covers 12 languages: Hindi, Bengali, Marathi, Tamil, Telugu, Urdu, Gujarati, Kannada, Malayalam, Punjabi, Odia and Assamese. Each one is under 1 MB, and all of it runs on your own device, so nothing you type is sent anywhere.
What you get
| You type | Lekhani writes | Language |
|---|---|---|
| kemon acho | কেমন আছো | Bengali |
| kasa ahes | कसा आहेस | Marathi |
| aap kaise hain | آپ کیسے ہیں | Urdu |
| kem cho | કેમ છો | Gujarati |
| mazha peyyunnu | മഴ പെയ്യുന്നു | Malayalam |
| sat sri akal | ਸਤ ਸ੍ਰੀ ਅਕਾਲ | Punjabi |
| kemiti achha | କେମିତି ଅଛ | Odia |
| kene asa | কেনে আছা | Assamese |
| eppadi irukkeenga | எப்படி இருக்கீங்க | Tamil |
| ela unnaru | ఎలా ఉన్నారు | Telugu |
| hegiddira | ಹೇಗಿದ್ದೀರಾ | Kannada |
| aap kaise ho | आप कैसे हो | Hindi |
All lowercase, typed the way people text. Each of these was typed on the page itself before this post went up.
How it works
Each language has its own small model, 0.8M or 1.2M parameters stored at 4 bits, plus a list of that language's common words. The model reads a word letter by letter and proposes its 8 best spellings; the list moves a common real word up when the model is unsure. A word that isn't in the list, such as a new name, still works from the model alone.
One runner, 15.8 KB of JavaScript (6.6 KB compressed) with no dependencies, serves all 12. Everything a page loads for one language (the model, its word list, two small settings files and the runner) is between 0.71 and 1.0 MB.
Use it in your own page
Download translit.js and the folder for your language from Hugging Face
or GitHub, serve them next to your page (any static host works), and:
import { Transliterator } from "./translit.js";
const tr = await Transliterator.load({
model: "tamil/model.q4.gz", json: "tamil/model.json.gz",
words: "tamil/words.bin.gz", rerank: "tamil/rerank.json",
});
tr.text("eppadi irukkeenga"); // எப்படி இருக்கீங்க
tr.text() writes every run of English letters in the language's script and leaves everything else as it is.
tr.word() returns the 8 spellings the model considered, best first, if you want to show suggestions as a keyboard does.
The same runner works in Node 18 or newer; the README has both examples. Nothing is sent to a server.
The result
We scored every language on the Aksharantar test (AI4Bharat): every romanised word in the test, top-1 exact match against any reference spelling, the same words and the same scorer for every system. Each language's settings were chosen on the validation set and the test was scored once, between 28 October and 13 November 2026.
| Language | Test words | Lekhani | IndicXlit's model | Gap (paired 95% interval) |
|---|---|---|---|---|
| Hindi | 9,991 | 65.9% | 58.9% | +7.0 (+6.1 to +8.0) |
| Tamil | 11,405 | 74.2% | 64.2% | +10.0 (+9.3 to +10.7) |
| Marathi | 12,059 | 70.9% | 63.2% | +7.8 (+7.0 to +8.6) |
| Bengali | 13,367 | 70.2% | 55.4% | +14.8 (+14.0 to +15.6) |
| Urdu | 14,617 | 57.6% | 44.4% | +13.2 (+12.4 to +14.0) |
| Kannada | 11,165 | 79.4% | 72.7% | +6.7 (+6.0 to +7.4) |
| Telugu | 9,936 | 81.2% | 72.6% | +8.5 (+7.7 to +9.3) |
| Malayalam | 12,343 | 67.1% | 59.5% | +7.6 (+6.9 to +8.4) |
| Gujarati | 17,402 | 72.8% | 61.2% | +11.6 (+10.9 to +12.3) |
| Punjabi | 10,949 | 55.7% | 44.5% | +11.2 (+10.3 to +12.0) |
| Odia | 4,201 | 49.9% | 45.6% | +4.2 (+3.0 to +5.5) |
| Assamese | 5,476 | 58.6% | 53.1% | +5.5 (+4.4 to +6.7) |
IndicXlit's model on its own has 11.5M parameters, run with its default settings. It is the comparison we set for every language before training, and every paired interval above is clear of zero.
Where it falls short
- IndicXlit's full Python package is level or ahead in ten of the twelve. It adds a word list of millions of words per language (358 MB in memory for Hindi). Lekhani is ahead of it only in Bengali (+2.8) and Urdu (+9.4), level in Tamil, Telugu, Gujarati and Punjabi, and behind in Hindi (-2.8), Marathi (-1.8), Kannada (-1.4), Malayalam (-2.1), Odia (-2.1) and Assamese (-1.8). If you can hold those lists in memory, the package is the more accurate choice.
- Names are weak, except in Hindi. In the other eleven languages, the test's name rows get 25% to 60% right on the first guess.
- Odia, Punjabi, Urdu and Assamese are weak in absolute terms: 50%, 56%, 58% and 59% of test words right on the first guess.
- Odia and Assamese have no real-typing test. Their scores cover the test's dictionary-style romanisation, not words as people type them in chat.
- One word at a time, no sentence context, and no native speaker has reviewed the misses yet; some may be accepted spellings that the answer lists don't include.
Try it
- In your browser: udgamlabs.com/models/lekhani
- Model and files: huggingface.co/udgamlabs/Lekhani
- Demo: huggingface.co/spaces/udgamlabs/Lekhani
- Code and examples: github.com/udgamlabs/lekhani
Code and weights are open under Apache-2.0. If you write one of these languages every day, tell us which words it gets wrong; they are what we want to fix next.
Thank you to AI4Bharat for Aksharantar, IndicXlit and IndicCorp, which this work stands on.