Thomas Verhave

Writing · Build Notes

I turned Afrikaans into 42 writing systems and shipped each as a Linux locale

My previous toy wrote Afrikaans in Japanese hiragana.

My previous toy wrote Afrikaans in Japanese hiragana. Then I asked the obvious follow-up: why stop at one script? taalpakke transliterates Afrikaans into 42 different writing systems, effects, and accents — Greek, Arabic, Cyrillic, Bengali, Hangul, Devanagari, Hebrew, Thai, Georgian, Armenian, Ge'ez, Elder Futhark runes, plus meme layers like leetspeak, fullwidth vaporwave, braille, and morse — and ships each one as an installable Ubuntu locale.

Set your system language to af_GR and your whole desktop UI comes back in Greek-script Afrikaans. af_US gives you K4n53ll33r. It's gloriously useless, and building it taught me a real lesson about not repeating yourself.

The trap I avoided: don't respell 42 times

The naive design is one transliterator per script, each parsing Afrikaans spelling from scratch — every one re-handling that g is [x], that sj is [ʃ], that ie is a long vowel, that ng is [ŋ]. Forty-two copies of the same fiddly digraph logic. A maintenance nightmare.

So instead there's one shared normalizer that turns Afrikaans spelling into a stream of internal phoneme symbols. Every per-script renderer then maps sounds, not spelling:

afr_norm — turns Afrikaans spelling into phoneme symbols so the
per-script renderers only map sounds, not respell every digraph.

  a e i o u          short vowels
  A E I O            long vowels (aa/ee/ie/oo, ê/ô)
  G                  syllabic ng -> [ŋ]
  S                  [ʃ]  (sj, ch, sch)
  C                  [tʃ] (tj)
  ...  (g is the [x] fricative)

Now a "pack" is tiny — just a sound→glyph table. The Greek one is almost a one-liner per phoneme, and it's less lossy than the kana version because Greek is alphabetic:

import afr_norm
CONS = {
    'g': 'χ',   # [x] -> chi is literally [x], a perfect fit
    'v': 'φ', 'f': 'φ',   # Afrikaans v is [f]
    'w': 'β',             # modern Greek β is [v]
    'b': 'μπ', 'd': 'ντ', # digraphs give [b]/[d]
    # ng -> γγ (=[ŋg]),  long ee/oo -> η/ω (eta/omega carry the length)
}

Every script family reuses the same phoneme stream and just brings its own quirks — abjads drop vowels then add harakat/niqqud, abugidas hang vowel marks off consonants, syllabaries force consonant+vowel pairing, Han packs pick transcription characters by sound. One analysis, forty-two presentations.

Turning it into a real locale

The genuinely fun systems trick: these aren't apps, they're locales. Linux UI text comes from gettext .mo catalogs. So the build takes your system's real Afrikaans catalogs, transliterates only the translated strings, and recompiles — no third-party deps beyond gettext's own tools:

# for each af .mo catalog:
#   msgunfmt  ->  read the existing Afrikaans msgstr entries
#   transliterate each msgstr through the pack
#   msgfmt    ->  recompile into out/<code>/LC_MESSAGES/<domain>.mo

Crucially it's additive and reversible — the real af translations are never touched; each pack lands as a new locale (af_GR, af_EG, af_RU…) beside them. Install it, log in, and menus/buttons/dialogs render in the new script. Uninstall and it's gone without a trace.

The 42, briefly

What I learned

A build-story · see the live project → · more writing →