The making of an IME

The making of an IME

Vietnamese IMEs (Input Method Editors) are incredibly popular in Vietnam — nearly everyone who types Vietnamese uses one. They’ve been around almost as long as computing in Vietnam, going through countless versions and iterations across different operating systems and architectures. Some names you might know: Vietkey, Unikey, gotiengviet, EVkey, Openkey.

The problem? Almost all of them are either dead or in hibernation. Their authors eventually couldn’t keep up with OS updates. Maintaining an IME is surprisingly complex.

Then AI happened. I thought: why not give this old problem a shot?

Gokey icon

The status quo

I’d been using EVkey for years — on both Windows and Mac, simultaneously. What I loved about it was its “return to English” feature: when it detected the current word wasn’t valid Vietnamese, it would just output raw text. No more typing “windows” and getting “windows”. It also had a decent autocorrect system where I could add my common typos.

It was great… until it wasn’t. The author stopped development, macOS updated, and EVkey stopped working. No big deal — Openkey was there. It’s practically EVkey’s twin, just buggier. Unfortunately, its author also stopped active maintenance years ago.

The start

With AI at hand, I wanted to take a swing at it.

It all started with a throwaway prompt: “fix this Openkey bug.” Claude Opus 4.8 fixed two bugs I hated in one go — the sleep crash and the macro issue. If I pushed that fix upstream, who knows when it’d get merged and built. Would the author even accept AI-written code? Or should I just build my own?

I’m a UX/UI/Product designer, and my fingers were itching. So I told the AI: build an IME that runs on Windows, Mac, and eventually Linux — shared engine. A few minutes later I had a working app. Buggy as hell, but it typed.

Simple enough: I QC’d and told it to fix things. It did, but it never felt safe to use, even after I built a basic QC kit. By then it was day 3 or 4 of me throwing evenings and spare moments at this thing.

The real difficulty? I’m a native Vietnamese speaker, but I don’t fully know the rules. If I wanted to do this properly, I’d need research — not just vibes.

Learning from predecessors

I cracked open Openkey’s source and started reading up on Vietnamese orthography. Turns out it’s way more complex than I thought — but not Chinese/Japanese level complex.

What I really learned from Openkey wasn’t the app architecture — it was the accent rules. Thank you, TuyenVM. I borrowed those Vietnamese linguistic rules from your code.

Engine rewrite number one.

Now the typing felt right accent-wise. But bugs kept popping up. Fix one thing, break another. The code was drowning in if-else spaghetti.

I also looked at Unikey’s engine and Gonhanh’s (no idea where that popped up — clearly AI-generated, downloaded it anyway).

My personal takes:

Unikey

Huge respect for the author — especially the optimization work on accent placement for old machines. Unicode-era Vietnam owes a lot to this IME. Rock-solid on Windows, the gold standard. Architecture is straightforward: Key → Rule → Output. Rule is the core. iOS might even be using its engine for the default Vietnamese keyboard.

Openkey

A big step up from Unikey, especially in extra features. The author clearly learned from EVkey. Though oddly, they kept a bunch of ancient features: VIQR input mode, VN-locale 1258, VNI code conversion. Features that were state-of-the-art… last century.

I really like this one. It gave me almost everything I loved about EVkey. I just couldn’t customize it the way I wanted.

Architecture: Key → Parser → Validation → Dictionary → Output. Output is based on Vietnamese syllable structure.

Gonhanh

Looks polished at first glance. Made by someone prominent in the AI scene, with a proper contributors page.

The code is crazy complex — reads like brute-force. It lets you type anything and uses a massive dictionary to figure out what should be printed.

Architecture: Key → Generate Candidates → Dictionary → Scoring → Output. It’s search-focused. I looked at it and noped out — it felt like there was an LLM hiding in there. Not my cup of tea.

But honestly, what I hated most — coming from my designer gut — was the icon. Way too bright (haha).

FSM engine: modular, zero tolerance for errors

After 1-2 days of wrestling with bugs and losing control, I flipped the table.

Engine rewrite number two.

This time, I’d control exactly how the engine works. First step: write a clear spec. No more letting the AI dream up some black box.

The Concept

This came straight from my product designer mindset.

When you’re typing a Vietnamese word, you always know what you’re about to write next. It’s natural.

Example: with a normal IME, disp = disp is fine. But type “displ” and you still get “disp”? That’s absurd. From a product designer’s perspective, you wouldn’t let the user walk into a dead end if you could stop them upfront.

When you start typing a word starting with ‘n’:

START

 └── 'n'

      ├── 'g' ──► NG
      │             │
      │             └── 'h' ──► NGH
      │                          │
      │                          ├── a e ê i o ô ơ u ư y
      │                          │
      │                          └── ...

      ├── 'h' ──► NH
      │             │
      │             ├── a e ê i o ô ơ u ư y
      │             └── ...

      ├── 'a' ──► NA
      │             │
      │             ├── c
      │             ├── m
      │             ├── n
      │             ├── ng
      │             ├── nh
      │             ├── i
      │             ├── o
      │             └── (END)

      ├── 'e' ──► NE
      │             ├── m
      │             ├── n
      │             ├── o
      │             └── (END)

      ├── 'i' ──► NI
      │             ├── h
      │             ├── n
      │             ├── t
      │             └── (END)

      ├── 'o' ──► NO
      │             ├── c
      │             ├── m
      │             ├── n
      │             ├── ng
      │             └── (END)

      ├── 'u' ──► NU
      │             ├── c
      │             ├── n
      │             ├── ng
      │             ├── ô
      │             ├── ơ
      │             └── (END)

      └── 'y' ──► NY
                    └── ...
 ...

For every character typed, we know what’s valid next. If the first key is ‘t’ and the next is ‘k’, it’s definitely not Vietnamese. But ‘h’, ‘r’, ‘a’, ‘e’, ‘u’, ‘i’, ‘o’? All valid.

That’s when my FSM (Finite State Machine) engine was born.

The FSM does one thing: constantly checks whether the current word can still form valid Vietnamese.

If the FSM is “optimistic” about the current buffer being Vietnamese, it allows accent processing. The moment the FSM finds no valid Vietnamese continuation, it releases everything as raw text.

That’s when ‘display’ snaps back to ‘display’ — because ‘displ’ isn’t valid Vietnamese.

The full engine pipeline:

Buffer → FSM → TonePlacement → Transform → Commit

Module breakdown:

Results & QC

Very promising.

The engine worked exactly as designed. By extracting typing rules into standalone text files, I could tweak Vietnamese rules without touching any code.

The hardest part is yet to come

Making an IME run is one thing. Making it stable across every weird app with non-standard input handling? That’s the real challenge. This project is still young, and there are many hard problems ahead.

But regardless — approaching the problem from a completely different angle, keeping it lightweight and buttery smooth — that’s what makes this fun.

The future

One thing’s for sure: beyond the homegrown engine, Gokey will have proper UI =))))

My roadmap:

Things I’m not daring to touch yet: