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>.moCrucially 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
- Phonetic scripts (15): Greek, Arabic, Cyrillic, Bengali, Han transcription, Hangul, Katakana, Runic, Devanagari, Hebrew, Thai, Georgian, Armenian, Ge'ez, Glagolitic.
- Glosses: script +
(Afrikaans)or{l33t}so you can still read along. - Effects / memes: pure leetspeak (
af_US), fullwidth vapor (fullwidth), braille (⠅⠁⠝⠎), morse (−·− ·−), uwu.
What I learned
- Find the shared layer before you scale. The phoneme normalizer is the entire reason 42 packs is maintainable instead of 42 tangled parsers. The abstraction is the project.
- Script families are a real taxonomy. Implementing alphabets vs. abjads vs. abugidas vs. syllabaries side by side made the differences concrete in a way no linguistics article had.
- The OS is more hackable than it looks. gettext
.mofiles are just data; once you can rewrite them, the whole desktop is your canvas — reversibly.
A build-story · see the live project → · more writing →