Installation
Lector is distributed as a Docker image. The recommended way to run it is with Docker Compose.
Docker Compose
Create a docker-compose.yml file:
services:
lector:
image: ghcr.io/3stacks/lector:latest
container_name: lector
restart: unless-stopped
ports:
- "3400:3000"
volumes:
- ./data:/app/data
environment:
- NODE_ENV=production
Then start it:
docker compose up -d
Lector will be available at http://your-server:3400. All data is stored in a SQLite database inside the data directory.
Updating
# Pull the latest image
docker compose pull
# Restart with the new version
docker compose up -d
Your data is stored in a bind mount and will persist across updates.
Self-hosting
Lector is designed to run on your home network. Your reading history, vocabulary, and learning progress stay on hardware you control. There are no cloud accounts, no telemetry, and no external dependencies beyond optional AI translation.
Network access
Lector is designed to be accessed via a WireGuard VPN. We use Tailscale, which takes about five minutes to set up and gives every device on your tailnet a stable IP. This means your data never leaves your network.
With Tailscale, you can access Lector from your phone, from work, or from anywhere, without exposing any ports to the public internet.
Reverse proxy (HTTPS)
If you want HTTPS within your network (recommended), put Lector behind a reverse proxy. Caddy handles automatic TLS with minimal configuration:
lector.home.yourdomain.com {
reverse_proxy lector:3000
}
Caddy will automatically provision and renew certificates. Pair this with a local DNS entry (e.g. via Pi-hole or your router) pointing lector.home.yourdomain.com to your server's IP.
Backups
Lector stores everything in a SQLite database and your imported books inside the data directory. To back it up:
# Copy the data directory
cp -r ./data ./backup-$(date +%Y%m%d)
Or include the bind mount path in your regular backup routine (rsync, borg, restic, etc.).
Reading
Lector supports two content formats:
- EPUB files: upload directly through the library interface
- Web articles: paste a URL and Lector extracts the content via Readability
You can also paste raw text directly for quick reading practice.
Click-to-translate
Click any word while reading to see its translation. If you have an ANTHROPIC_API_KEY configured, Lector uses Claude for context-aware translation of uncommon words and phrases. Without an API key, it falls back to a built-in dictionary of the top 2000 words.
Translation quality depends on which AI provider you have configured. See the LLM Providers section below for setup instructions.
Word states
Every word you encounter is tracked with a state:
- New: you haven't interacted with this word yet
- Learning: you've looked it up or saved it
- Known: you've marked it as known
Words are highlighted in your text based on their state, so you can see at a glance how much of a passage you already know.
Cloze Practice
The cloze practice system ships with ~2900 Afrikaans-English sentence pairs from Tatoeba, ordered by word frequency so you learn common words first.
Practice modes
- Multiple choice: pick the correct word from four options
- Typing: type the missing word (with optional hard mode that disables hints)
SRS scheduling
Sentences are scheduled using spaced repetition with five mastery levels:
| Mastery | Interval |
|---|---|
| 0% | Immediate |
| 25% | 1 day |
| 50% | 3 days |
| 75% | 7 days |
| 100% | 14 days |
LLM Providers
Lector uses a large language model for context-aware translations when you click words and phrases. You can choose between three providers in Settings → AI Provider.
A note on smaller models: 8B-parameter models (like Llama 3.1 8B via Ollama) work reasonably well for major languages but struggle with more obscure languages like Afrikaans. If translation quality is important to you, use Anthropic or Apfel.
Anthropic (cloud)
The highest-quality option. Uses Claude for context-aware translation of words, phrases, and grammar explanations.
Lector supports two authentication methods:
- API key: Get one from console.anthropic.com. Set
ANTHROPIC_API_KEYin your environment, or enter it in Settings. Pay-as-you-go billing. - OAuth token (Pro/Team plan): Uses your existing Claude Pro or Team subscription credits. Set
CLAUDE_CODE_OAUTH_TOKENin your environment. Note: OAuth connections have slower initial startup times compared to API keys.
- Best translation quality, especially for less common languages
- Requires internet access and an Anthropic account
Apfel (Apple Intelligence)
Runs Apple's on-device Foundation Model through an OpenAI-compatible API. No API keys, no cloud, no cost. Requires a Mac with Apple Silicon.
Requirements
- macOS 26 (Tahoe) or newer
- Apple Silicon (M1 or later)
- Apple Intelligence enabled: System Settings → Apple Intelligence & Siri → enable
Setup
# Install
brew install Arthur-Ficial/tap/apfel
# Verify the model is available
apfel --model-info
# Start as a background service
brew services start arthur-ficial/tap/apfel
Then in Lector, go to Settings → AI Provider, select Apfel (self-hosted), and set:
- Server URL:
http://localhost:11434 - Model:
apple-foundationmodel
Click Test Connection to verify. You should see a green "Connected" status.
Apfel is significantly stronger than local 8B Ollama models for translation quality, and runs entirely on-device with no API keys or internet required. If you don't have an Anthropic API key, Apfel is the best local option for Mac users.
Ollama (local)
Runs open-source models locally via Ollama. No API keys or cloud required, but translation quality depends heavily on model size.
- Install Ollama and pull a model:
ollama pull llama3.1:8b - Select a model in Lector's settings
- Works on any platform (macOS, Linux, Windows)
Quality warning: 8B models produce noticeably weaker translations for less common languages. For Afrikaans and similar languages, prefer Anthropic or Apfel if available.
AnkiConnect
Lector connects directly to AnkiConnect running on your local machine. This lets you push vocabulary cards from Lector straight into Anki Desktop. No proxy server, no cloud sync.
Setup
- Install the AnkiConnect add-on in Anki Desktop
- In AnkiConnect's config, add your Lector origin to the CORS allowlist:
{
"webCorsOriginList": ["http://localhost:3000"]
}
If you're accessing Lector via a reverse proxy, add that origin too (e.g. https://lector.home.yourdomain.com).
Note: AnkiConnect runs on localhost:8765 by default. The connection is browser-to-localhost, so Anki Desktop must be running on the same machine as your browser.
Configuration
Environment variables
| Variable | Default | Description |
|---|---|---|
NODE_ENV |
production |
Set to production for deployed instances |
LLM_PROVIDER |
anthropic |
AI provider for translations: anthropic, apfel, or ollama |
ANTHROPIC_API_KEY |
— | Claude API key (only needed when LLM_PROVIDER=anthropic) |
CLAUDE_CODE_OAUTH_TOKEN |
— | OAuth token for Pro/Team plan credits (alternative to API key, slower startup) |
APFEL_URL |
http://localhost:11434 |
Apfel server URL (only needed when LLM_PROVIDER=apfel) |
OLLAMA_URL |
http://localhost:11434 |
Ollama server URL (only needed when LLM_PROVIDER=ollama) |
OLLAMA_MODEL |
llama3.1:8b |
Ollama model name |
GOOGLE_CLOUD_API_KEY |
— | Optional. Google Cloud API key for text-to-speech pronunciation. |
Full Docker Compose example
Here's a complete example with all optional environment variables:
services:
lector:
image: ghcr.io/3stacks/lector:${LECTOR_VERSION:-latest}
container_name: lector
restart: unless-stopped
ports:
- "3400:3000"
volumes:
- ./data:/app/data
environment:
- NODE_ENV=production
- LLM_PROVIDER=${LLM_PROVIDER:-anthropic}
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
- CLAUDE_CODE_OAUTH_TOKEN=${CLAUDE_CODE_OAUTH_TOKEN}
- APFEL_URL=${APFEL_URL}
- OLLAMA_URL=${OLLAMA_URL}
- OLLAMA_MODEL=${OLLAMA_MODEL}
- GOOGLE_CLOUD_API_KEY=${GOOGLE_CLOUD_API_KEY}
Supported languages
Lector's reader works with any language, but language packs provide sentence banks and dictionaries for cloze practice.
| Language pair | Status | Sentences |
|---|---|---|
| Afrikaans → English | Available | ~2900 |
| Spanish → English | Coming soon | — |
| German → English | Coming soon | — |
All sentence data is sourced from Tatoeba. Adding a new language pair is straightforward. If you'd like to see your language supported, open a PR on GitHub. We'd love contributions.
Sentence bank
The cloze practice system ships with ~2900 Afrikaans-English sentence pairs sourced from Tatoeba. Each sentence is tagged with word frequency data so you learn the most common words first.
To regenerate from Tatoeba's latest data dumps:
npm run fetch-sentences
This downloads Tatoeba's per-language TSV exports, joins Afrikaans sentences with English translations, and tags each with word frequency data.
Data attribution: Sentence bank sourced from Tatoeba, licensed under CC-BY 2.0 FR. Word frequency dictionary compiled from publicly available frequency lists.
Tech stack
- Framework: Next.js 16 with React 19
- Database: SQLite (better-sqlite3) server-side, Dexie (IndexedDB) client-side
- Styling: Tailwind CSS v4
- AI: Anthropic Claude, Apple Intelligence (Apfel), or Ollama (all optional)
- TTS: Google Cloud Text-to-Speech (optional)
- Container: Docker (single image, bind mount for data)