OpenDerja_TN
2026 — ongoingCore backend complete · opening to open-source contributors
Twelve million people write this language every day and there's almost no proper record of it. Contributors write, correct and confirm Derja from their own region, and out of it comes a citable dataset, a lexicon, and a translator built on the corpus. Non-profit by design. The backend is done — a framework-free domain core, eleven NestJS modules, a 42-model schema, provider OAuth, a pg-boss job queue — and the project is opening up to contributors now instead of staying solo.
Role — Founder & maintainer — non-profit from the first line of code, now bringing contributors on
- Data models
- 42
- Test suites
- 59 · 302 tests
- Backend modules
- 11
- Regions modelled
- 4
- Language planes
- 3
The constraint that shaped it
A dialect is a continuum, and a schema that forces a single answer throws away information you can't get back. Nearly every interesting decision in the data model comes from that.
The architecture
The data model — the genuinely specific part
Region is a set, not a value. A segment can belong to two regions at once, and storing one region would force a false choice on every row. A separate field records whether the item is regional at all, because "which regions" and "is this regional" are different questions.
Annotations always attach to character spans — including the ones that apply to a whole segment, which are written as a span covering the full text rather than a null span. One shape for every annotation means one consensus mechanism and one query path, instead of every consumer handling two cases forever.
Tokens are annotations, not a hierarchy. Standardisation rewrites text. Offset-anchored annotations migrate with a rewrite; a parallel token hierarchy silently desyncs.
Consensus is keyed by the full span, not by segment and type. Otherwise two annotations on different parts of one sentence overwrite each other with no error.
Task idempotency keys carry a mandatory slot. Empty for record-scoped tasks, the character range for span-scoped ones, an ordinal for the N-of-a-kind case. Making it mandatory rather than optional is the whole decision: an optional field with a sensible default is a field somebody omits, and the failure here is silent.
The train/dev/test split is set at insert and never changed. It's the only thing that makes an accuracy claim honest.
Machine output is never fact. Model output enters as a suggestion, attributed to a system user and flagged, and needs human confirmation before it reaches a canonical field.
Restraint as a design decision
Three planes exist — TypeScript, Rust, Python — but only the TypeScript one has code in it. A stage moves to Rust when it's measured as a bottleneck at real corpus scale, not when it seems like it would be faster. The Rust modules are scaffolded as empty seams so the move stays cheap, and the fact that they're still empty is written into the project ledger so nobody later reads them as a gap.
No ML model, no Python inference and no GPU dependency before the corpus is real. Nothing in a request path is allowed to await inference. The nlp and media services are health-check-only stubs until their phase arrives.
Every contributor screen is one question, three seconds, no explanation needed. Trust levels, task types and routing are never visible to a contributor. The corpus only gets built if people who aren't linguists can contribute without learning the system first.
Built solo, opened up on purpose
Phases one and two are scoped so that if the project stops after phase two, it has still produced something that didn't exist before: a published, citable, regionally-tagged Derja dataset, and a public tool a non-technical Tunisian can use. Defining a stopping point that still counts as success is what makes a project with no funding and no deadline survivable.
The backend is written and tested. The next work — the contributor front-end, the scraper sources, the translator on top of the corpus — is where contributors come in. There's a bilingual architecture ledger (EN + FR) recording the non-negotiable rules and why each one exists, so a new contributor doesn't have to re-derive a settled decision. The repository is private for now and goes public with that opening.
How it was built
25 August 2026
OpenDerja_TN begins
An open, regionally balanced corpus of Tunisian Derja. Twelve million people write this language daily and there is almost no proper record of it.
Problems that came out of this project4
- A test suite that got smaller instead of failing2026-08-25The 2026-08-25 restructure into control-plane / data-plane / intelligence-plane moved a lot of paths. Afterwards, 13 of 57 suites couldn't resolve @nestjs/ or @open-derja/ imports.
- Endpoints that returned success and did nothingengineeringTwo features looked finished from the API's point of view and were not: each had a working synchronous half and an asynchronous half that had never been built, so the endpoint returned success and nothing happened.
- Jest crashing on every decoratorinfrastructureSetting up the first real test run, the decorators that make NestJS work blew up before any test logic ran. The setup needed reflect-metadata polyfilled explicitly. Not interesting in itself, but it's the kind of thing that blocks everythin
- Testing guards with the real thing, not a mockengineeringWhile building out auth coverage, I deliberately tested the guard chain against a real Reflector rather than a mock. Guards are almost entirely about reading decorator metadata, so a mocked reflector tests the mock's idea of what the decora