Skip to content
All posts

7 min readCase study, Language learning, Data modelling

Building Cognata: seven languages taught through each other

A cognate dataset where relations are computed, not typed in; sound laws as data; FSRS scheduling; and a Claude grader that degrades gracefully to a heuristic.


Latin CLĀVEM, “key”, became llave in Spanish, clé in French, chiave in Italian, chave in Portuguese, cheie in Romanian and clauin Catalan. Those differences aren't random. Each language wore the same spoken Latin down by its own regular rules, and English borrowed thousands of the results through French and Latin. So whichever of the seven you speak, you already know a big share of the other six. You just need to know which share, which words only look shared, and which rules turn one form into another.

Cognata is the app we built around that idea. Pick the language you speak and one or more you want to learn, and every word, lesson and drill is explained through the one you know. It's a self-initiated studio project. It started as a fork of our Arabic–Hebrew app, Hebrabic, and kept its accounts, billing and classroom plumbing.

Words in all seven
2,264
Sound laws
33
False friends
87
Language pairings
42

Store the origins, compute the relations

Each of the 2,264 entries holds the word in all six Romance languages and English, each with IPA, plus the source it came from (usually a Latin form like OCULUM) and how each language got its word. A Romance form is inherited (worn down by speech), learned (taken back from written Latin), borrowed, or from some other source. The English word is shared (borrowed from the same source), kindred (native, related only through Indo-European, like night and NOCTEM), native, or other.

What the data never stores is the relation between two languages. Whether Spanish and Italian share a word as a cognate, a distant cognate, or not at all is computed by one small function from the two origins. That one choice pays off everywhere. With 42 possible pairings (any of seven languages as the base, any of the other six as the target), storing relations by hand would mean thousands of judgments that drift out of sync. Because they're derived, fixing one origin updates every badge, quiz and statistic at once. The Atlas uses the same function across the whole word bank. In this dataset, for example, Spanish and Italian share a source for 75.5% of entries, while Romanian and Portuguese share one for 56.4%.

Inherited-versus-learned is the most important distinction in the data, so it gets its own editorial rule. Spanish ojo and ocular both come from OCULUS, but only the first was shaped by the sound laws, and treating them alike would teach wrong patterns. The content guide puts it bluntly: a wrong cognate claim is the worst bug the app can ship. When the history is uncertain, the entry claims less. It drops the shared source or marks the form other with an honest note.

A word bank built in checked batches

The word bank started as a hand-written core of 689 words in 22 categories. It then grew in 50 frequency-driven batches, and later Portuguese, Romanian and Catalan forms were added for every word. None of this is a scraped dictionary. It's typed TypeScript checked against a strict contract, and nothing joins the bank without passing a validator:

  • Batch checks: check-batch rejects a new batch with duplicate ids, missing fields, unknown sound-law ids, or a borrowed or other-origin form with no stated source.
  • Sister checks: a word without its Portuguese, Romanian and Catalan forms is left out of the bank entirely, not shown half-finished. The full content check lists every such word.
  • IPA that parses:every transcription runs through the app's own phonetics module, and any symbol it doesn't recognize is an error. The module also enforces conventions like no stress marks in French, and warns on polysyllables without one elsewhere.

The repo is also upfront about what's still open. The etymologies, IPA and lessons were written and cross-checked by AI agents against the content guide, and a review checklist lists the conventions and claims a Romance philologist and native reviewers should still sign off on. Examples include the Italian voiced s, and whether “semi-learned” needs to be its own category. We think shipping that list does more for trust than hiding it.

Sound laws as data, not prose

The 33 sound laws are grouped as 6 vowel laws, 11 consonant laws, 9 cluster laws and 7 ending laws. Each is a structured record: the Latin starting point, the outcome in each of the six languages, a summary, worked examples (207 across all the laws, at least four per law), the exceptions, and a one-line “decoder” such as: Spanish ll- at the start of a word? Try cl- for French. Because the laws are data, 878 vocabulary entries are tagged with the laws that explain them, which is what makes a sound-law flashcard deck and a sound-law quiz possible.

The same phonetics module measures how far words drifted. It segments IPA into sounds described by articulatory features (place, manner, voicing, vowel height and so on), then runs an edit distance where swapping [t] for [d] costs little and a consonant for a vowel costs the most. The Atlas uses it to rank the most worn-down inheritances. Top of the list for French against Italian: AQUAM gave Italian acqua but French eau, pronounced as a single vowel, [o].

Scheduling and grading that degrade gracefully

Flashcards are scheduled with FSRS through ts-fsrs. That's the memory model behind modern Anki: each card tracks a stability and a difficulty, and the next review is set for when predicted recall drops to 90%. The app first shipped with SM-2, so we didn't throw old progress away. Old cards are migrated on their next review: the SM-2 interval becomes the starting stability and the ease becomes the starting difficulty, and a test covers that migration. Progress lives on the device by default. With an account it syncs to Postgres through Drizzle, and the server and client run the same pure merge functions, so two devices pushing at once never overwrite each other.

Claude is used in exactly one place: grading a learner's free translation of a line in Scenes, the module that quizzes you on short clips played through YouTube's official embed. Since it's an endpoint on a free, account-less site, it's fenced in:

  • Inputs are validated with Zod and capped at 400 characters per field.
  • The reply must match a Zod schema (verdict, feedback, a better phrasing), so the UI never parses free text.
  • The system prompt treats the line, the reference and the attempt as data, and never follows instructions found inside them.
  • A per-IP budget of 40 calls per 10 minutes. It's in-memory, so it stops casual abuse on one instance; it isn't a hard quota.
  • A refusal, an API error, a rate-limited IP or a missing API key all return the same response shape from a word-overlap heuristic.

That last point is the design rule for the whole app: no environment variables are required. Without them, Cognata runs as the free, account-less version with every module, and accounts, billing, email and AI grading switch on one key at a time.

Any language as the base

“Italian for English speakers” is the easy case. The hard part was making all seven languages equal. A learner's setup is one known language, one or more targets, and the language in focus right now. Views that compare languages show every target side by side; single-question views like a flashcard use the focus. The languages you aren't studying stay one click away, because seeing them together is how the patterns show up. Some modules, like the sound laws and the Morph lab, only cover Romance languages. When English is the target, they quietly switch to a Romance language the learner knows or studies.

Each pairing gets its own landing page, such as /learn/italian-for-spanish-speakers, built from the data: the share of cognates, the everyday words the two languages share, the sound laws between them, their false friends, and the words to learn fresh. The interface itself comes in all seven languages, under /es, /fr, /pt and so on, with English at the root. Lesson prose stays in English for now, and the repo says so.

What we'd tell a client

  • Store the facts and compute the judgments. Relations derived from one source of truth can't drift apart.
  • Put your content behind a validator as strict as your type checker. With 2,264 entries in seven languages, a reviewer will miss what a script won't.
  • Write down what still needs expert review. An honest list of open questions builds more trust than a polished claim.
  • Give every AI feature a non-AI fallback with the same response shape, and use structured output so the model can't break your UI.
The app's claim is that you already know more than you think. The engineering job was making sure that claim stays true for every one of the 42 pairings.