# kokey

kokey — Open source by DevsLab

**Open source by [DevsLab](https://devslab.kr/)** · [OSS brand guide](https://devslab.kr/brand/open-source/) · Registry O05 [![npm](https://img.shields.io/npm/v/%40devslab%2Fkokey)](https://www.npmjs.com/package/@devslab/kokey) [![CI](https://github.com/devslab-kr/kokey/actions/workflows/ci.yml/badge.svg)](https://github.com/devslab-kr/kokey/actions/workflows/ci.yml) ![TypeScript](https://img.shields.io/badge/TypeScript-Ready-3178C6?logo=typescript&logoColor=white) [![License](https://img.shields.io/badge/License-MIT-blue)](./LICENSE) [![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz_small.svg)](https://stackblitz.com/github/devslab-kr/kokey/tree/main/examples/vanilla) Keyboard layout converter — restore text typed with the wrong layout. Korean (Dubeolsik ↔ QWERTY) built in; Russian, Ukrainian, Hebrew, Greek, Thai, Arabic and Georgian available as tree-shakeable subpath imports. TypeScript-first, **zero dependencies**, ESM/CJS dual package. **English** · [한국어](./README.ko.md) · [Русский](./README.ru.md) · [Українська](./README.uk.md) · [עברית](./README.he.md) · [Ελληνικά](./README.el.md) · [ไทย](./README.th.md) · [العربية](./README.ar.md) · [ქართული](./README.ka.md) · [Live demo](https://devslab-kr.github.io/kokey/) Try it online: [⚡ StackBlitz — Vanilla](https://stackblitz.com/github/devslab-kr/kokey/tree/main/examples/vanilla) · [Vue](https://stackblitz.com/github/devslab-kr/kokey/tree/main/examples/vue) · [React](https://stackblitz.com/github/devslab-kr/kokey/tree/main/examples/react) · [Svelte](https://stackblitz.com/github/devslab-kr/kokey/tree/main/examples/svelte) · [Solid](https://stackblitz.com/github/devslab-kr/kokey/tree/main/examples/solid) | [📦 CodeSandbox](https://codesandbox.io/s/github/devslab-kr/kokey/tree/main/examples/vanilla)

kokey demo — dkssud → 안녕, ghbdtn → привет, wrong-layout text restored live

Ever typed `dkssud` when you meant `안녕`, or scanned a barcode while the Korean IME was on and got `ㅇㄴㅁ쇼2068601` instead of `DSATY2068601`? `kokey` converts between what was typed and what was meant — in both directions, exactly the way a Dubeolsik IME composes Hangul. The same slip exists in every language that toggles a non-Latin layout with QWERTY: Russians type `ghbdtn` for `привет`, Israelis `akuo` for `שלום`, Thais scan barcodes with Kedmanee on. `kokey` covers those layouts too — see [Beyond Korean](#beyond-korean--any-registered-layout). *Numeric fields in the same form — amounts that need live comma grouping, a stable caret, and right alignment? That's kokey's sibling, [numkey](https://github.com/devslab-kr/numkey).* ## Install ```sh npm install @devslab/kokey ``` Or straight from a CDN — no build step, everything under the `kokey` global: ```html ``` ## Usage ```ts import { koToEn, enToKo } from '@devslab/kokey' // Hangul → the QWERTY keystrokes that produced it koToEn('안녕') // 'dkssud' koToEn('값없는 닭갈비') // 'rkqtdjqtsms ekfrrkfql' koToEn('ㅇㄴㅁ쇼2068601') // 'dsaty2068601' (wedge-scanner rescue) // QWERTY keystrokes → composed Hangul (full IME automaton) enToKo('dkssud') // '안녕' enToKo('gksrmf') // '한글' enToKo('ekfrl') // '달기' (compound-final split, like a real IME) ``` ### Details that matter - **Shift is honored**: `R` → ㄲ, `r` → ㄱ, `koToEn('뛰다') === 'Enlek'` - **Compound vowels/finals**: ㅘ ↔ `hk`, ㄵ ↔ `sw`, … - **받침 넘김 (final-consonant carry-over)**: `enToKo('dkswk') === '안자'` - **Pass-through**: digits, punctuation, and unmapped letters are left as-is - Round-trip safe for Korean text: `enToKo(koToEn(s)) === s` ### Beyond Korean — any registered layout Each layout is a subpath import (unused ones never reach your bundle) and plugs into the same machinery: ```ts import { register, toEn, fromEn } from '@devslab/kokey' import { ru, ruToEn, enToRu } from '@devslab/kokey/ru' import { he } from '@devslab/kokey/he' // direct, per-layout ruToEn('привет') // 'ghbdtn' — the Punto Switcher classic enToRu('ghbdtn') // 'привет' // or register + auto-detect by script, mixed strings included register(ru, he) toEn('안녕 привет שלום') // 'dkssud ghbdtn akuo' fromEn('ghbdtn', 'ru') // 'привет' ``` | Layout | Import | Notes | | --- | --- | --- | | Korean 두벌식 | built-in (`ko`) | full IME composition automaton | | Russian ЙЦУКЕН | `@devslab/kokey/ru` | moved punctuation (ё on backtick, №, `.` on `/`) mapped faithfully | | Ukrainian Enhanced | `@devslab/kokey/uk` | і/є/ї; AltGr-only ґ restored in reverse; ru/uk auto-disambiguated | | Hebrew | `@devslab/kokey/he` | final forms, swapped brackets, caps-lock safe | | Greek | `@devslab/kokey/el` | tonos/dialytika dead keys (`;a` → ά), final sigma | | Thai Kedmanee | `@devslab/kokey/th` | full digit-row remap — barcode rescue works for Thai too | | Arabic (101) | `@devslab/kokey/ar` | lam-alef لا on `b`, hamza forms, tashkeel | | Georgian QWERTY | `@devslab/kokey/ka` | near-phonetic (`gamarjoba` ↔ გამარჯობა) | Languages whose IME needs a candidate-selection step (Chinese pinyin, Japanese kanji) are out of scope by construction — the keystroke ↔ text relation there isn't deterministic. Need another deterministic layout? It's one `defineLayout({ id, script, fromKey })` table — [PRs welcome](https://github.com/devslab-kr/kokey/pulls). ### DOM layer — enforce an input mode Force an ``/`