lingo-free.openapps.xyz

Source

This app’s code and revision history are public. Download a copy or explore the files below.

Public
The app’s code and revision history are public.
Owned app · rev 8af89d2cc9f8
Run it yourself
Take the whole tree and run it on your own machine. The download needs no account. Cloning needs a sign-in.
Download .zip
bounded clone 7efc97d0dde0b2a3e729ccf9
A copy you download is not bounded. The guarantees this app runs under - declared egress, proven invariants, writes that fail closed - live in the runtime serving this page, not in the files. What you get is auditable source you can read and run, with no runtime guarantees attached.
← All files
AGENTS.md
# Lingo

Lingo is a free language learning app, a free alternative to Duolingo. English speakers learn Spanish and French (full beginner A1 courses, 8 units of 5 lessons) and German, Italian and Brazilian Portuguese (first 3 units). It is an OpenApp in the OpenApps free apps store (https://openapps.free/), owned by the OpenApps team.

## What it promises

- Free, with no hearts, no energy limit, no ads and no subscription. Never add a cap on lessons or mistakes, an upsell or tracking.
- No sign-up to start. Every visitor is a guest account created in the browser; progress is kept on the device and in that account.
- "Keep my progress on all devices" signs in with Bounded hosted email login and copies the guest's progress into the account.
- The weekly league stays fair: league XP can only grow with an XP log entry, and the log is capped at 600 XP per learner per hour.
- Course content is correct, natural and everyday. Fixing a wrong sentence beats adding a feature.
- AI assistants can read every course through the public API (`functions/api.ts`), documented in `public/llms.txt`. Keep that file true whenever the API changes.
- If AI conversation practice is ever added, it is optional, charged at cost, and says so.

## How it is built

- `index.html` is the static landing page that search engines and AI crawlers read. Its language picker links straight into the first lesson. Keep its copy, comparison, FAQ and JSON-LD in step with what the app does.
- `functions/courses/<id>.json` holds the hand-written courses (types in `functions/lib/course.ts`). They live under `functions/` because the API imports them and functions can only import files inside `functions/`; the app loads each one on demand. Each lesson has 5 to 7 words and 7 to 9 sentences; nouns carry their article; `ea`/`ta` list other correct English and target-language answers. Run `node scripts/check-courses.mjs [id] --coverage` after any content change.
- `src/lib/exercises.ts` turns a lesson's words and sentences into 12 to 15 exercises (pick the meaning, match pairs, word tiles, listen and tap or type, fill in the blank, translate by typing, speak). Practice, mistakes review and jump tests use the same builders.
- `src/lib/answer.ts` grades typed answers: case and punctuation never matter, accents are optional with a note, English contractions count, and a one-letter slip inside a longer word passes as a typo (but not in a word's ending, where gender and number live).
- `src/lib/speech.ts` uses the browser's own voices and speech recognition. Without a voice for the language, listening exercises are swapped out; without recognition, speaking is.
- `src/lib/srs.ts` is SM-2 spaced repetition: every word and sentence a learner meets gets a review card.
- `src/lib/progress.ts` is the learner's state (settings, XP per day, streak and freezes, lessons, cards, mistakes) in localStorage, with a merge that never loses a finished lesson. `src/lib/sync.ts` syncs it to Bounded, writes league XP, and runs the guest-to-account copy.
- `src/Session.tsx` runs a lesson: missed exercises come back at the end, nothing ends a lesson early except a jump test (more than 3 mistakes).
- `policy.json`:
  - `learners/<userId>` and `learners/<userId>/courses/<lang>`: the learner's progress as JSON strings, private to the learner.
  - `learners/<userId>/xp/<logId>`: append-only league XP log with the `xp_per_hour` rollingSum (600 per hour per learner).
  - `weeks/<weekStart>/learners/<userId>`: public league rows {name, lang, xp, last, hidden}; `weekStart` is Monday 00:00 UTC in unix seconds and must be the current week; `xp` only grows by a fresh log entry written in the same batch (`last` names it). `hidden` hides a guest row whose XP moved to the learner's account.
  - Room to grow: AI conversation practice would add `learners/<userId>/chats/<id>` and a paid function, without changing what exists.
- `src/sw.js` becomes `/sw.js` at build (`vite.config.ts` fills in the file list) and keeps the app shell and every course on the device. `public/manifest.webmanifest` and `public/icons/` make Lingo installable.

## Working on it

`npm install && npm run build` typechecks and builds `dist/`. Ship changes through previews and releases as your runtime instructions describe; the app is an OpenApp, so nobody deploys it directly.

Check a change by starting a course from the landing, finishing a lesson with right and wrong answers of every type, seeing XP, streak and the league row update, running a practice review and a mistakes review, reloading, and calling the API routes with curl.

## Voice

Plain words, short sentences, warm and encouraging, no jargon, no em dashes. Say what happens.

Change history

Point OpenApps links to openapps.free
8af89d2cc9 · 17 hours ago
Bind agent address in policy
0018350275 · 19 hours ago
Use OpenApps service URLs and add contribution strip
805365daf4 · 19 hours ago
Bounded source sync
3f2fb3b265 · 2 days ago
Bounded source sync
7dcb580324 · 2 days ago
Bounded source sync
dcd05c0944 · 2 days ago