A tiny web app for practicing French conversation with a warm, playful AI tutor. You chat in French; the tutor replies in English with a per-turn correction block (β
GOOD, π§ FIX, π¬ CORRECTED) and an A1-level follow-up question drawn from a structured curriculum.
Built around the Anthropic Messages API (Claude Haiku 4.5), Node.js, and a single static HTML page β no build step, no framework.
- A1 curriculum-driven prompts. Each session picks one module (salutations, passΓ© composΓ©, prΓ©positions, mΓ©tiers, β¦) and anchors the teacher's questions around it, so you practice concrete grammar instead of generic "how was your day" chatter.
- Structured corrections. The teacher response is parsed on the client into three sections: what you got right, what to fix, and the fully corrected sentence β rendered as clean cards, not a wall of text.
- Voice conversation. Tap the π€ mic button to speak your answer in French. A live status bar shows a pulsing dot and a real-time transcript preview so you always know when the app is listening. Speak naturally β continuous recognition captures your full sentence without cutting off mid-speech. Tap the mic again to send, or wait 2 seconds after you stop talking and it sends automatically. The tutor reads the corrected sentence and follow-up question aloud via
SpeechSynthesisat 0.85x speed. Type a message instead and the response stays text-only β mode is detected per message, no toggle needed. Voice uses free browser-native APIs (zero additional cost). Mic button is hidden on unsupported browsers (graceful fallback to text-only). - Pronunciation tips. When you speak your answer, the tutor adds a
π£οΈ SAY IT:line with phonetic hints for the trickiest words β nasal sounds, silent letters, liaisons (e.g. "je PRENDS (prahn) le bus"). - Teach-on-English. Drop an English word into your French ("je want du cafΓ©") and the tutor translates it into the corrected sentence and explains how to use it, instead of rejecting the turn.
- Session limits. Every 10 answers (voice and text combined) the composer is replaced with a live 3-hour countdown. State is persisted in
localStorage, so refreshing or closing the tab doesn't reset it. The 10th answer corrects without asking a new question. - Personalization that persists. The tutor quietly extracts short facts it learns (hobbies, family, job, city, β¦) via a hidden
<note>β¦</note>trailer, the client strips the tag and stores the facts inlocalStorage, and future prompts include them underKNOWN ABOUT <NAME>so questions feel personal across sessions. - Optional password gate for shared deployments.
- Mobile-friendly. The chat handles iOS/Android virtual-keyboard quirks with
visualViewporttracking and scroll-pinning.
git clone https://github.com/korcena/oohlalangue.git
cd oohlalangue
npm install
cp .env.example .env # fill in ANTHROPIC_API_KEY
npm startThen open http://localhost:3000.
- Node.js β₯ 18 (uses built-in
httpsonly β no nativefetchdependency). - An Anthropic API key. Get one at https://console.anthropic.com/.
| Variable | Required | Description |
|---|---|---|
ANTHROPIC_API_KEY |
yes | Your Anthropic API key. |
PORT |
no | HTTP port to listen on (default 3000). |
APP_PASSWORD |
no | If set, visitors must enter this password before they can chat. Leave blank to disable the gate. |
ββββββββββββββββ POST /api/chat ββββββββββββββββ ββββββββββββββββββ
β index.html β βββββββββββββββββββββββββββββββΆβ server.js β ββββββΆ β Anthropic API β
β (vanilla JS)β { messages, name, profile, β (Express) β β claude-haiku β
β ββββ skipQuestion, voiceMode } β βββββββ β 4.5 β
ββββββββββββββββ { text, raw } ββββββββββββββββ ββββββββββββββββββ
β
βββ Browser APIs
β SpeechRecognition (fr-FR) β voice input
β SpeechSynthesis (fr-FR) β voice output
β
βββ localStorage
β oohlalangue_name
β oohlalangue_password
β oohlalangue_profile (array of learned facts)
β oohlalangue_answers (0β10 shared counter)
β oohlalangue_pause_until (epoch ms)
βββ in-memory history (the Messages-API transcript)
server.jsis a thin Express server that:- Serves the static
index.html. - Gates
/api/chatbehind the optional password. - Builds the per-request system prompt (curriculum module, known facts, session-end instructions, pronunciation tips when
voiceModeis true) and forwards the trimmed conversation to Anthropic's Messages API.
- Serves the static
index.htmlholds everything else: chat UI, correction parser, localStorage profile, session-limit timer, voice I/O, mobile keyboard handling. No build step; edit and reload.
Each session randomly picks one module and anchors the teacher's questions around it. There are currently 38 modules spanning A1 grammar, vocabulary, and everyday situations.
| Module | Focus |
|---|---|
| Les Salutations | Formal vs informal greetings, "comment Γ§a va" |
| Les Nombres | Numbers 0β100 (age, phone, quantities) |
| Les Jours et Les Mois | Days of the week (lowercase!) and months |
| Les Fruits | Fruits with correct gender (le/la/un/une) |
| La Famille | Family vocabulary + possessives (mon/ma/mes) |
| Le Verbe ΓTRE | Γͺtre in present, c'est / c'Γ©tait |
| Les Verbes RΓ©flΓ©chis | SE verbs: se lever, s'appeler, se promener |
| Les Verbes Communs | Irregular verbs: aller, avoir, faire, prendre, vouloir |
| Les Pays | Countries with en / au / aux |
| Les Articles | Partitive du / de la / de l'; "de" after negation |
| Les Possessifs | mon/ma/mes, ton/ta/tes, son/sa/ses |
| Poser une Question | qui / quoi / quand / oΓΉ / comment / pourquoi / combien |
| Module | Focus |
|---|---|
| La Position | Prepositions: dans, sur, sous, devant, derrière, à côté de, chez |
| La Direction | Γ / au / aux / de / vers; contractions Γ +le=au, de+le=du |
| Les Lieux | Places in the city: boulangerie, poste, supermarchΓ© |
| Les Voies | Streets & waterways: rue, avenue, boulevard, pont, quai |
| Les Parties de la Ville | centre-ville, banlieue, quartier, zone piΓ©tonne |
| Les Lieux et les Monuments | Public buildings: mairie, gare, commissariat, banque |
| Module | Focus |
|---|---|
| La Vie Quotidienne | CafΓ© / restaurant vocabulary and drinks |
| Les MΓ©tiers | Jobs β no article after Γͺtre; chez + person |
| Les Loisirs | Hobbies with jouer Γ (sports) vs jouer de (instruments) |
| Les Adjectifs | Agreement (m/f) and BAGS rule |
| IL Y A | il y a / il n'y a pas de / il y a + time (ago) |
| La FrΓ©quence | toujours, souvent, parfois, rarement, neβ¦jamais; aussi after verb |
| La MΓ©tΓ©o | il fait, il pleut, il neige, il y a du vent |
| Le Temps | Time expressions: depuis, il y a, avant, après, pendant |
| Les Nombres Ordinaux | Ordinals and floors (premier, deuxième, rez-de-chaussée) |
| Module | Focus |
|---|---|
| Le PassΓ© ComposΓ© | avoir for most, Γͺtre for DR MRS VANDERTRAMP + SE verbs; feminine -e |
| Le Fonctionnement | Intensity adverbs: très, trop, assez, vraiment, beaucoup, un peu |
| L'ImpΓ©ratif | TU/VOUS/NOUS (no pronoun, -ER drops -s); sois/aie/va; pour, parce que, mais, sans, neβ¦que, pendant |
| Module | Focus |
|---|---|
| Le Transport | Vehicles (scooter, vΓ©lo, bus, mΓ©tro, voiture, avion), se dΓ©placer, monter, tourner Γ droite/gauche, tout droit |
| Les Distances et le Covoiturage | Which transport for which distance; trajet, itinΓ©raire, arrΓͺt, quai; Γ©conomiser, covoiturage |
| Module | Focus |
|---|---|
| La Nourriture | petit-déjeuner, poireau, poire, citrouille, haricots, maïs, ail, piment, pastèque, pamplemousse |
| Les Pas et Les Γtapes | Distinctions: les pas (walking), une Γ©tape (process), une marche (stairs) |
| La Routine et les Descriptions | se lever, se dΓ©pΓͺcher, se marier; retraitΓ©(e), bruyant, droitier/gaucher/ambidextre |
| Module | Focus |
|---|---|
| La MΓ©tΓ©o AvancΓ©e | orage, tonnerre, para- words (parapente, parasol, paratonnerre) |
| Les Verbes ClΓ©s | prendre (full conjugation), devoir, apporter, Γ©conomiser, voyager |
| Les Expressions Idiomatiques | peu m'importe, au bord du gouffre, c'est la fin des haricots, tu sens bon |
Each module in server.js is an object with a module, a focus line, and a short hint. Add entries to the CURRICULUM_MODULES array and optionally extend CURRICULUM_SUMMARY so the model has context when it drifts between topics.
The system prompt is built in buildSystemPrompt() (server.js). The EACH TURN block drives how the client parses the reply β if you change the emoji markers (β
GOOD:, π§ FIX:, π¬ CORRECTED:), update the regex map in parseTeacherReply() inside index.html to match.
In index.html:
const ANSWERS_PER_WINDOW = 10; // answers (voice + text) before 3h pause
const PAUSE_MS = 3 * 60 * 60 * 1000; // pause durationThe student can clear everything from DevTools:
localStorage.clear();It's a single Node process serving static files β deploy anywhere Node runs:
- Render / Railway / Fly.io: point the service at
npm start, set the env vars, and you're done. - VPS:
pm2 start server.js --name oohlalangue, put Nginx in front for TLS. - Shared link: set
APP_PASSWORDso visitors must enter it before any requests hit Anthropic on your API key.
Cost is bounded per turn: Haiku 4.5, max_tokens: 500, and the conversation history is trimmed to the last ~20 messages before each call.
.
βββ server.js # Express server + Anthropic proxy
βββ index.html # Single-page chat UI (HTML + CSS + vanilla JS)
βββ package.json
βββ .env.example
βββ README.md
UNLICENSED β this is a personal project. All rights reserved. Feel free to read the code for learning purposes.