7 min readCase study, Language learning, i18n
Building Hebrabic: one dataset, two languages, both directions
How a typed corpus of 1,617 words teaches Arabic to Hebrew speakers and Hebrew to Arabic speakers from the same data — with dialect layers, three interface languages, and an honesty policy for cognates.
Hebrew and Arabic are sister Semitic languages. They build words from the same three-letter roots, their alphabets descend from a common ancestor, and their sounds line up through regular correspondences. Yet almost every course teaches either one as if the learner were starting from zero. Hebrabic starts from the other side: a Hebrew speaker meets each Arabic letter, word, root, and grammar rule next to its Hebrew counterpart — and, flipped, an Arabic speaker learns Hebrew the same way.
Hebrabic is a self-initiated studio project. It began in July 2026 as an Arabic course for Hebrew speakers. Since then it has added a mirrored direction, a choice of three spoken dialects, a full Hebrew and Arabic interface, and an optional accounts-and-billing layer — all on top of one typed dataset.
- Words
- 1,617
- With a Hebrew relative
- 1,010
- Directions
- 2
- Spoken dialects
- 3
Every word says how it's related
The heart of the product is the vocabulary: 1,617 entries across 28 categories, each a typed record that every module reads. Each entry has the Arabic word fully vowelled and bare, its root, the everyday Hebrew translation, and — the field everything else hangs on — a cognate tag naming the relationship between the two languages. There are five values: a true cognate (445 words), the same root with a shifted meaning (273), a loanword in either direction (255), a false friend (37), and none, a word with no useful Hebrew relative (607). That last group is the honest part: about 37% of the vocabulary is labelled “learn this one fresh” instead of being stretched into a connection.
Every tag except none needs a note explaining the link, and the schema adds fields for the cases that would otherwise mislead. Take Arabic laḥm, “meat.” Its Hebrew cousin is לחם, “bread”: the shared root once meant a staple food, and each language narrowed it differently. That is why the entry carries the cousin in its own field with its own gloss — so a card never labels a Hebrew word with the Arabic word's meaning.
Two directions from one dataset
The mirrored direction, added in August, is not a translated copy of the course. A single preference — the ע→ع pill in the header — flips which side of each record is the word being learned and which is the known language. A small direction module holds helpers like targetWord(entry, direction) and targetPhonetics(entry, direction). Components ask for “the target word” and never for “the Arabic,” so headwords, transliteration, audio, and drills flip together. Hebrew speakers get the 28 Arabic letters through their Hebrew cousins. Arabic speakers get the 22 Hebrew letters through their Arabic ones.
For that to work, the data had to hold up in both directions. Arabic already had its pronunciation written in Hebrew letters; the mirror needed every Hebrew string written out for an Arabic reader. All 1,617 entries now carry Modern Israeli pronunciation in Latin and in vowelled Arabic letters — שלום as shalomand شَالُوم. The Arabic rendering follows rules mechanical enough to be written down in the type definitions, so the same Latin always produces the same Arabic. The content validator enforces the rules: every Hebrew string needs both pronunciations, and the Arabic one must carry vowel marks and must not use pharyngeal or emphatic letters, because Israeli Hebrew doesn't have those sounds.
The mirror also caused our best bug. The direction preference lives on the device, but pages are rendered on the server with the default direction. Visitors who had stored the mirrored direction hit a hydration error on every visit to the vocabulary page, and React threw away and re-rendered the whole card tree on the client. The fix moved settings out of React context and into a module store read through useSyncExternalStore. React uses the server snapshot while hydrating and then switches to the stored preference, so the server HTML always matches and the stored setting takes over right after.
MSA as the spine, the street on top
Modern Standard Arabic is shared across the Arab world, but nobody orders coffee in it. So every lesson stands on MSA, and the learner picks a spoken dialect to sit on top: Levantine (the default), Egyptian, or Moroccan. In the data, a dialect form is an optional field on the entry, recorded only when the street word really differs from MSA — a different word, a different shape, or a pronunciation you wouldn't guess from the spelling. 770 entries carry at least one such form. The chosen dialect drives everything “on the street”: which form a vocabulary card leads with, what a letter card says about ق or ج, the verb tables' prefixes and negation, and an MSA ⇄ dialect toggle on each of the 14 dialogues.
Speech follows the same layering. Audio comes from the browser's built-in speech synthesis. For a dialect form, the app first looks for a voice from that dialect's region before falling back to any Arabic voice, because an Egyptian voice says ج the Egyptian way. The speaking practice runs the other way: it compares what the browser's speech recognizer heard with the word the learner was asked to say. Recognizers write plain spelling, without vowel marks or final letter forms, so both sides are first reduced to a spelling skeleton and then scored by edit distance.
Three interface languages, two of them RTL
The interface language is separate from the learning direction: a Hebrew speaker learning Arabic can read the app in English, Hebrew, or Arabic. Every route lives under one [lang] segment, and a path-only proxy keeps English on its original bare URLs. Hebrew and Arabic get a prefix and right-to-left layout. The proxy reads no cookies and doesn't sniff browser headers, so pages stay statically cacheable.
The hard part is that Hebrew, Arabic, and Latin text often share one line, card, or table cell, on pages that run in either direction. Two rules make that manageable. First, the English dictionary defines the shape of the Hebrew and Arabic ones, so a missing translation is a type error, not a blank button. Plurals use real plural rules, and Arabic needs six forms. Second, the codebase uses only logical layout utilities, start and end rather than left and right. A search of the components finds no physical margin, padding, or alignment classes.
Truth over charm, enforced in git
Cognate apps tend to collect pretty connections that don't hold up. Hebrabic's product rules say the opposite: if you're not sure, claim less, and an honest downgrade beats a flattering connection. The commit history shows that rule being applied. The first content pass was followed by a review with 71 linguistic corrections and 41 “honesty downgrades.” Two days later, the push past 1,500 words was reviewed again: 168 corrections and 13 cognate downgrades. In October, an item-by-item audit of every letter, root, lesson, conjugation, dialogue, and vocabulary category fixed about 185 more errors. Folk etymologies were removed, and some loans were relabelled as native words or true cognates.
Paid features are built the same cautious way. Accounts (Better Auth on Postgres with Drizzle), Stripe plans, classes, and a school console all exist in the code. Every paywall goes through a single can(user, feature) check, and the whole layer is switched on only by environment variables. Without them, sign-in and pricing return 404 and the app runs as the free version with no account, where progress stays on the device. At the time of writing, that is the version hebrabic.com serves.
One module is a tribute, not a feature: Aravrit, Liron Lavi Turkenich's half-and-half typeface, with Arabic on the upper half of each letter and Hebrew on the lower half. The site recreates the idea live with its own fonts. A build step composes each hybrid letter from real glyph outlines, and every word gets a fixed-size box, so dragging the fold slider never moves the rest of the page. It credits her throughout and points visitors to the real typeface.
What we'd tell a client
- If you might serve a second audience, design the data for both now. Having components ask for the target word, never for 'the Arabic', turned the mirror from a rewrite into a setting.
- Make honesty part of the data. A 'none' value and a required explanation note do more for credibility than any marketing copy.
- Write your conventions where the validator can check them. Rules for transliteration, vowel marks, and banned letters stay consistent because a script enforces them.
- Build optional services so they can be turned off. If billing is switched on by environment variables, you can ship the free product today and turn on payments without forking anything.
- Treat RTL as a constraint from the start: logical utilities, typed dictionaries, and server-rendered defaults that match what the client hydrates.
The bridge between Hebrew and Arabic is real, but it isn't everywhere. A good part of Hebrabic's work is knowing where it ends, and saying so on the card.