Arabic Tutor
A web app for English speakers to learn Modern Standard Arabic (MSA) —
script, vocabulary, and sentence-building — across 100 levels.
Status: the full 100-level curriculum is authored and the app shell is
complete — interactive exercises, an on-screen Arabic keyboard,
spaced-repetition review, real exercise depth, live audio, and a progress
dashboard. All 100 levels of curriculum content exist as structured JSON —
1367 exercises across them, generated so every letter and vocab word gets
at least one dedicated question, not just the illustrative few each level
started with. A React/Vite app loads and displays them with
clickable/typeable exercises and live scoring, finishing a level feeds its
letters/vocab/patterns into a spaced-repetition queue you work through at
/review, 🔊 buttons speak Arabic text aloud using the browser's built-in
Web Speech API — free, no account, no API key, no pre-generated files
(though it does need an Arabic voice installed at the OS level — see
"Audio" below) — and /progress shows levels completed, average score,
items learned, SRS mastery breakdown, a day streak, and a review-load
forecast. See Next steps for what's left now that content is complete
(native-speaker review, polish items).
Running it
npm install
npm run dev
Then open the printed localhost URL. npm run build produces a static
dist/ you can deploy anywhere (Vercel, Netlify, GitHub Pages, ...).
Audio
🔊 buttons speak Arabic text live via the browser's Web Speech API
(src/lib/speech.ts) — nothing to set up, nothing to generate, but it does
need an Arabic voice available at the OS level. Verified directly (not
assumed): Chrome's own "remote" voice list is a small fixed set that does
not reliably include Arabic, so don't count on it working out of the
box. If LevelList shows "No Arabic voice found," install one:
- Windows: Settings → Time & Language → Language & region → Add a
language → pick an Arabic variant → make sure Speech/text-to-speech is
included in the download → restart the browser afterward.
- Other OSes: check the system language/speech/accessibility settings for
installing an Arabic voice, then restart the browser.
Until then, 🔊 buttons no-op silently rather than erroring.
An offline pre-generation pipeline for Google Cloud TTS
(scripts/generate-audio.mjs) still exists as a documented fallback if you
ever want downloadable/offline-consistent MP3s instead — it was shelved
because Google Cloud requires a billing account (card on file) even for
free-tier usage, which was more setup friction than the Web Speech API for
no real quality gain. To use it: cp .env.example .env, fill in
GOOGLE_TTS_API_KEY, then npm run generate-audio (supports --dry-run
and --force). PlayAudioButton would need a small change to prefer a
static file when one exists, since it currently always calls
speak().
Install and offline
The app installs from the browser's "Install app" / "Add to Home Screen"
option and then works with no connection: a service worker
(vite-plugin-pwa, configured in vite.config.ts) precaches the build,
which already contains the whole curriculum, and caches the Google Fonts
so Arabic keeps rendering in Noto Naskh Arabic rather than falling back to
a system font. Progress was always local, in IndexedDB.
A new deploy never reloads the page by itself — UpdatePrompt offers a
Reload instead, since a part-finished level lives in component state until
its last exercise is answered. Manifest fields and icons live in
src/lib/pwaManifest.ts.
Audio is the one thing a connection can still affect: speech uses the
voices installed on the device, so it works offline with a local Arabic
voice but not with a browser's network-based voices.
Tests
npm test # run once
npm run test:watch
Vitest, no browser needed. The suite is deliberately weighted toward the
things that are easy to break silently:
- Content validation (
src/lib/content.test.ts) — id uniqueness across
every content type, refIds resolving to something introduced at or before
their own level, sentence-build tiles reassembling into their answer,
arabicNoDiacritics matching what arabic actually strips to, and the
diacritics fade schedule. This ran as an ad-hoc script during every
authoring batch and caught a real bug each time; now that all 100 levels
are written the content can only regress.
- Undiacritized collisions — asserts the set of vocabulary that collapses
to identical letters is exactly the four documented, unavoidable cases,
so a new collision fails the build rather than shipping an unanswerable
multiple-choice question.
- Review generation — every item at every point in its mode cycle, which
is ~3,400 exercises. Guards that review never emits a
speaking exercise
(it reports unscored, which the grader would record as a miss), that
options never contain visual duplicates after diacritics are stripped, and
that Arabic is rendered at the diacritic level of the level that taught it.
- SRS scheduling, backup round-trip, and glossary search folding.
Generating exercises
npm run generate-exercises # write changes to content/levels/*.json
npm run generate-exercises -- --dry-run # preview counts, no writes
Scans each level for vocab/letters not yet tested by their own dedicated
exercise and generates what's missing (meaning + recall/typing for vocab,
recognition for letters), spliced into the level JSON alongside the
hand-authored ones. Idempotent — safe to re-run after authoring new levels,
since already-covered items are skipped.
Design at a glance
- Dialect: Modern Standard Arabic.
- Curriculum shape: script, vocabulary, and sentences are interleaved
from level 1, not taught in separate phases. Full alphabet coverage lands
around level 7.
- Retention: a lightweight SM-2-style spaced-repetition queue
(
src/lib/srs.ts) tracks every letter, word, and sentence pattern as an
individually reviewable item. Finishing a level seeds its items into the
queue; correct answers grow the review interval (1 day → 6 days →
interval×ease), misses reset it for immediate re-review. Work through
what's due at /review — reachable via the "🔁 Review N items" banner
on the level list.
- Platform: static client-side app, no accounts — progress lives in the
browser. No backend required.
- Audio: live via the browser's Web Speech API (
src/lib/speech.ts) —
free, no account, no pre-generation step. See "Audio" above for the
cloud-TTS fallback path if that's ever needed instead.
- Exercises: multiple-choice/matching, typed Arabic input (with an
on-screen Arabic keyboard), and self-check speaking practice (record
yourself vs. reference audio). Every letter and vocab word gets at least
one dedicated exercise per
scripts/generate-exercises.mjs — see
"Generating exercises" above.
- Progress dashboard (
/progress, src/lib/progress.ts): computed
entirely from data already in IndexedDB, no separate tracking needed —
levels completed, average score, items learned by type, an SRS mastery
breakdown (new/learning/mastered, by repetitions), a day streak (derived
from SRS review + level-completion timestamps), and a review-load
forecast (due today / this week / later).
Full rationale for these decisions — including why letters are taught in
shape families instead of dictionary order, and why vocabulary isn't
strictly gated behind letter progress — is in
docs/content-model.md.
Repo layout
docs/content-model.md Design rationale + schema reference
src/types/content.ts TypeScript types for the content model
src/lib/content.ts Loads content/levels/*.json at build time
src/lib/db.ts Dexie (IndexedDB) schema for progress/SRS
src/lib/arabic.ts Diacritics-stripping normalization for grading
src/lib/srs.ts Spaced-repetition scheduling (lightweight SM-2)
src/lib/reviewExercise.ts Turns a due SRS item into a synthetic quiz question
src/lib/speech.ts Web Speech API wrapper (speak, voice detection)
src/lib/progress.ts Dashboard stats computed from IndexedDB (no separate tracking)
src/pages/ LevelList, LevelDetail, Review, Progress
src/components/ ArabicText, ExerciseCard, PlayAudioButton, ArabicKeyboard
content/levels/*.json Authored level content (all 100 levels, complete)
scripts/generate-audio.mjs Offline Google Cloud TTS pipeline — fallback, unused
scripts/generate-exercises.mjs Coverage-based exercise generator (idempotent)
Each content/levels/level-XX.json follows the Level type in
src/types/content.ts: new letters, new vocab, new sentence patterns, a
diacritics-display setting, and exercises (a hand-authored core set plus
generated coverage exercises — 1367 total across all 100 levels).
All 100 levels
| # | Theme |
|---|
| 1 | Greetings & basics |
| 2 | Family |
| 3 | Everyday objects |
| 4 | Descriptions (adjectives) |
| 5 | Demonstratives — "This is..." |
| 6 | Questions — "What is this?" |
| 7 | Pronouns — alphabet complete |
| 8 | Numbers 1–5 & hamza forms |
| 9 | Possession — "This is my..." |
| 10 | Negation & review |
| 11 | The definite article ال — sun/moon letters |
| 12 | Verbs — "to go" (I / you) |
| 13 | Verbs — "to go" (he / she / we) |
| 14 | Numbers 6–10 |
| 15 | Verbs — "to want" (full paradigm) |
| 16 | Colors |
| 17 | Days of the week |
| 18 | Verbs — "to read" (full paradigm) |
| 19 | Sound plurals |
| 20 | "And" (وَ) & review |
| 21 | Broken plurals |
| 22 | Past tense — "to go" |
| 23 | Past tense — "to want" |
| 24 | Past tense — "to read" |
| 25 | Verbs — "to eat" (present tense) |
| 26 | Verbs — "to drink" (present tense) |
| 27 | Broken plurals II — cities & mountains |
| 28 | Past tense — "to eat" |
| 29 | Past tense — "to drink" |
| 30 | Review — levels 21–30 |
| 31 | Idafa — construct phrases |
| 32 | Negating the past and future (لم / لن) |
| 33 | Verbs — "to work" (present tense) |
| 34 | Verbs — "to write" (present tense) |
| 35 | Ordinal numbers (masc.) |
| 36 | Telling time |
| 37 |
Next steps
- Review the pacing. Read through
content/levels/level-01.json →
level-100.json (or the summary table in docs/content-model.md) —
the full, complete curriculum — and sanity-check that the progression
feels right end to end.
Scaffold the app. Done — npm run dev to try it.
2b. Wire up interactive exercises. Done — clickable options, a tile-based
sentence builder, a typing input (compared leniently, diacritics
stripped) backed by an on-screen Arabic keyboard, and a self-check for
speaking exercises. Level completion + score persist to IndexedDB
(src/lib/db.ts) and show as a badge on the level list.
2c. Build the SRS review queue. Done — src/lib/srs.ts schedules items
with a lightweight SM-2 variant, src/lib/reviewExercise.ts generates
quiz questions on the fly from due items (reusing ExerciseCard, no new
UI needed), and /review (src/pages/Review.tsx) runs the session.
Verified end-to-end: completing a level seeds the right item count,
correct/incorrect answers reschedule items exactly per the algorithm,
and the level-list due-count banner tracks it live.
Get audio working. Done, via a different route than originally
planned — the Google Cloud TTS pipeline needed a billing account even
for free usage, so switched to the browser's built-in Web Speech API
instead: zero setup, zero account, zero cost (see "Audio" above).
PlayAudioButton now takes the Arabic text to speak directly rather
than a pre-generated file id. Letter audio still uses the letter's full
diacritized name (e.g. "بَاء" bāʾ) rather than the bare glyph, since
speech synthesis can't meaningfully pronounce a lone unvoweled consonant
— that fix was provider-agnostic and carried over. LevelList shows a
notice if no Arabic voice is detected in the browser. The Google Cloud
pipeline is kept as a documented, unused fallback.
Generate exercises at scale. Done — scripts/generate-exercises.mjs
(see "Generating exercises" above) took the 10 levels from 42 hand-authored
sample exercises to 133, ensuring every letter and vocab word has at
least one dedicated question. Verified in the browser: answered all 17
of level 1's exercises via a scripted click-through, got a clean 17/17,
and confirmed SRS seeding still counts by unique content item (not
exercise count), so this didn't perturb the review-queue math.
Give speaking exercises real feedback. Done — SpeakingExercise
(ExerciseCard.tsx) now plays the target word/phrase via speak()
before the self-check, reusing the same reference-audio pattern as
. Also authored 116 exercises across all
20 levels (one per vocab word) via , since none
existed in any level before this. True automated pronunciation scoring
(recording + grading the learner's own voice) remains a much bigger
separate feature, deliberately out of scope.
What's left, now that content is complete
The 100-level curriculum is done. What remains is no longer "more
levels" — it's validation and polish:
- Native-speaker content review. All 446 vocabulary items, 93
grammar patterns, and their explanations were authored and
self-verified, not fact-checked by a fluent/native Arabic speaker. This
is the single highest-value remaining task if a reviewer is available.
- Deliberately excluded, and documented as such (not gaps to
eventually fill): case endings/إعراب (invisible once diacritics fade at
level 16 — a permanent design ceiling, not a bug); Form IX verbs
(اِفْعَلَّ, restricted almost entirely to colors/physical traits);
passive voice (فُعِلَ/يُفْعَلُ changes only internal short vowels, so it
would be textually identical to the active voice the moment diacritics
fade — genuinely incompatible with this course's diacritics-fade
design, not merely deferred); full numbers 3–19 gender-polarity
agreement; the dual beyond its light nominative form; أَنْتُنَّ and the
remaining object-pronoun suffixes (-كِ, -كُمْ, -هُنَّ).
- Lower-priority polish: true pronunciation scoring (today's speaking
exercises are reference-audio self-check only), and a fuller
accessibility audit — exercise results are now
announced and Arabic is tagged
lang="ar", but nothing has been tested
against a real screen reader end to end.