Lector is a language-learning reader. It is an open-source alternative to LingQ and Clozemaster. Use Lector Cloud, or self-host it with one Docker Compose file.
Import the text that you want to read. Tap a word for a translation. Save it. Practise it. Send it to Anki. A self-host stores data in one SQLite file.
Try the hosted app · Docs · Discord
A local dictionary covers common words with no API key.
Optional local models run through the bundled Ollama service.
Optional cloud models cover rare words and the tutor.
| App | Host | Database | Anki |
|---|---|---|---|
| Lector | Lector Cloud, or one Compose file | SQLite | Two-way (beta) |
| LingQ | Hosted only | Cloud | File export |
| Clozemaster | Hosted only | Cloud | No documented export |
| Lute | pip, Docker, or source | Local files | One-way AnkiConnect |
| LinguaCafe | Four Compose services | MySQL | One-way AnkiConnect |
Full pages live at lector.dev/vs.
You need Docker and Docker Compose.
git clone https://github.com/heuwels/lector.git
cd lector
docker compose up -dOpen http://localhost:3400.
The UI listens on port 3400. The API listens on port 3457. The browser calls the API directly, so both ports must be reachable.
If the browser is not on the server, set API_URL to the origin that the browser uses for the API. Example: http://192.168.1.10:3457.
# optional: cloud translation for rare words
export ANTHROPIC_API_KEY=sk-ant-...
docker compose up -dThe image is registry.lector.dev/lector:latest. That is our own address in
front of GitHub's registry, and pulling through it is the only way we have any
idea how many installs exist — GitHub publishes no pull statistics. No IP
address is stored; the privacy policy explains
what is recorded and how. To stay out of those numbers, pull
ghcr.io/heuwels/lector:latest instead — it is the identical image, and both
Compose files carry it as a commented-out line.
A production Compose file with health checks lives in deploy/. Full
environment notes live in deploy/README.md.
If you do not want to run a server, use the hosted app at app.lector.dev. Paid plans start at $5 per month.
-
Reader. Import a file or a stream. Each word has a state: new, learning, or known. Tap a word for a translation. The reader accepts:
- an EPUB file
- a Markdown file
- a web article
- pasted text
- a YouTube transcript
- a podcast
-
Cloze practice. Frequency-ordered sentences. Choose an answer from a list, or type the missing word. Spaced repetition (SRS) with mastery levels.
-
Vocabulary. Save words as you read. Track known and learning states. Save phrases as well as single words.
-
Anki. The Lector Sync add-on on AnkiWeb is the recommended integration. The add-on code is
1098736891. Cloud mode and a remote HTTPS self-host always use it. A local self-host can instead push cards to AnkiConnect on the computer that runs Anki. Reviews in Anki can update mastery in Lector. -
Tutor and journal. Ask grammar questions in plain language. Write in the target language. The tutor returns corrections. Use the Claude API or a local model.
-
Listen. Optional text-to-speech (TTS). YouTube captions stay timestamped. A podcast upload can become a transcript and a listen-along lesson.
-
Data. SQLite on your server. Export and restore from Settings. The self-host does not need a cloud account.
Language packs ship for:
- Afrikaans
- Bengali
- Czech
- Dutch
- Esperanto
- Finnish
- French
- German
- Greek
- Hindi
- Hungarian
- Indonesian
- Italian
- Japanese
- Koine Greek
- Korean
- Latin
- Mandarin Chinese
- Norwegian
- Polish
- Portuguese
- Russian
- Spanish
- Swedish
- Turkish
- Ukrainian
A pack includes a dictionary, frequency data, and cloze sentences. Japanese and Korean have a dictionary. They do not have a cloze bank yet. The reader still works for a language with no pack. Depth is lower without a pack.
The language picker lists only the languages that you study. Open the picker and select Add a language to start another one. Open Settings, then Languages, to add a language or to remove one from the list. A language that you remove keeps its words, texts, and cards.
The image holds no dictionaries. Lector downloads the dictionary for a language when you add that language. It keeps the file in the lector-dict volume, so an image update does not remove it. The download runs in the background. The language works at once, because a word with no dictionary entry yet goes to the AI lookup.
The ghcr.io/heuwels/lector:full tag holds every dictionary. It is about 2.6 GB larger. Use it for an air-gapped install. Set DICT_FETCH=0 with it.
Afrikaans was the first pack. It remains the most complete reference set. It is not the product. The product is the reader for any language that you study.
See lector.dev/docs/languages for pack status.
Two transports exist. Open Settings, then Anki Integration, then Connection.
AnkiConnect for a local self-host. The browser talks to AnkiConnect on localhost:8765. Install the AnkiConnect add-on. Allow your app origin:
{
"webCorsOriginList": ["http://localhost:3400", "http://localhost:3456"]
}Lector Sync add-on for cloud mode or a remote HTTPS self-host. A page on HTTPS cannot call localhost on the computer that runs Anki. The add-on runs inside Anki Desktop. It pulls queued cards onto Lector note types. It writes review states back to Lector. Point api_url at your Lector origin.
If you want a file, copy .env.example to .env. Compose also reads the process environment.
| Variable | Purpose |
|---|---|
ANTHROPIC_API_KEY |
Cloud translation and tutor. Optional. The local dictionary covers common words. |
API_URL |
Browser-facing API origin. Required when the browser is not on the server. |
LECTOR_MODE |
selfhost (default, one user, no login) or cloud (accounts). |
LLM_PROVIDER |
anthropic (default) or an OpenAI-compatible backend. |
OPENAI_COMPAT_URL |
Local model endpoint. The bundled Ollama service is http://ollama:11434. |
CLASSIFY_WORKER |
Set to 1 to fill the fluency radar. Compose sets this for you. |
DICT_LANGS |
Dictionaries to download at start. Use all for every language. Unset downloads only what you add in the picker. |
DICT_FETCH |
Set to 0 to never download a dictionary. Use it with the :full image. |
DICT_DIR |
Where the dictionaries live. The default is the lector-dict volume. |
TRANSCRIBE_WORKER |
Set to 1 to transcribe podcast uploads. Needs a Whisper endpoint. See deploy/README.md. |
The app runs with no API keys. Claude is only required for rare words, phrase translation, and the tutor.
The app caches TTS audio under DATA_DIR/tts-cache. Classification can use a provider batch API at half the synchronous price. Details live in deploy/README.md.
LECTOR_MODE=cloud enables accounts. The self-host stays free. Cloud mode is also the multi-user option on your server.
Required:
BETTER_AUTH_SECRET: generate withopenssl rand -base64 32. Cloud mode does not start without it.BETTER_AUTH_URL: public origin for auth links, for examplehttps://app.example.com.
Optional:
GITHUB_CLIENT_IDandGITHUB_CLIENT_SECRET: Sign in with GitHub.- To sign in with an identity provider, set the OpenID Connect (OIDC) values
OIDC_ISSUER,OIDC_CLIENT_ID, andOIDC_CLIENT_SECRET. RESEND_API_KEY: verification mail. Without it, mail lands in the server log.TURNSTILE_SITE_KEYandTURNSTILE_SECRET_KEY: bot protection on sign-up.
CAUTION: Do not change LECTOR_MODE before you read Move self-host data to an account. If you switch an existing self-host database to cloud mode, the old library stays in the database. The new account does not see it until you move it.
LECTOR_CLOUD_GATE=external sends login to a gateway such as Cloudflare Access. The AWS path lives in deploy/cloud/.
In self-host mode every row belongs to the implicit local user. Cloud mode shows only rows that the signed-in account owns. An empty library after the switch is not data loss.
adopt-local-data moves every row from local to one fresh account. If the target account already owns rows, the script refuses. The default run does not write data.
-
Stop the app.
-
Copy
DATA_DIRto a backup. Includelector.db. See Backups. -
Set
LECTOR_MODE=cloud. -
Set the auth variables.
-
Start the app.
-
Create the target account in the browser.
-
Check the mail link.
-
List accounts:
docker compose exec lector sh -c \ 'cd /app/api && DATA_DIR=/app/data bun run src/scripts/adopt-local-data.ts --list'
-
Run the move with no write. The command prints per-table counts:
docker compose exec lector sh -c \ 'cd /app/api && DATA_DIR=/app/data bun run src/scripts/adopt-local-data.ts --to you@example.com'
-
Run the same command with
--committo apply it. -
Sign in.
-
Confirm the library, the vocabulary, and the stats.
-
Keep the backup.
To roll back:
- Stop the app.
- Restore
DATA_DIR. - Unset
LECTOR_MODE.
-
In-app export. Open Settings, then Learning data, then Export all learning data. This is a JSON export of the library, vocabulary, SRS state, journal, and stats. Restore it with the matching import.
-
Volume copy. If the app is stopped, copy
DATA_DIR. If the app still runs, create a SQLite checkpoint first:sqlite3 "$DATA_DIR/lector.db" "PRAGMA wal_checkpoint(TRUNCATE)" && cp -a "$DATA_DIR" /path/to/backups/
The hosted app streams writes to object storage with Litestream. See deploy/cloud/.
To run from source, read CONTRIBUTING.md. That file also holds the folder styleguide and the OpenAPI rules.
npm install
npm run dev:api # Hono API on :3457
npm run dev # Next.js UI on :3456Open http://localhost:3456.
- Sentence banks. Tatoeba, CC BY 2.0 FR.
- Dictionaries and frequency lists. Wiktionary extracts through kaikki.org, Wikipedia dumps, and OpenSubtitles, plus pack-specific sources listed on lector.dev.
Copyright © 2026 Luke Boyle.
Licensed under the GNU Affero General Public License v3.0 (AGPLv3). See LICENSE. You may use, self-host, study, modify, and redistribute Lector. If you run a modified version as a network service, you must offer the matching source to its users. See AGPL section 13.
