Lukaisu (Finnish: a quick read-through) is a mobile app for learning languages by reading, in the tradition of LWT — Learning with Texts. Add a language, paste in a text, and read it with every word colour-coded by how well you know it. Tap a word to save a translation, then build your vocabulary with built-in spaced repetition (FSRS-based).
It's local-first: everything is stored on your device and the core read/save/review loop works fully offline, with no account and no server. Connecting a Lukaisu Server is optional, and unlocks the things a phone can't do well on its own: high-quality Chinese/Japanese word splitting, text-to-speech, audio transcription, and discovering new texts online — the same pattern as the Nextcloud mobile client (a free client for a self-hostable free server).
The app is named after lukaisu.org; its package name
(org.lukaisu.app) is permanent once published. MIT licensed.
- Read texts with tap-to-look-up words, colour-coded by how well you know them.
- Save words and review them with spaced repetition, fully offline.
- Create your own languages, or start from a built-in preset; a fresh install seeds a few sample texts so you can start reading immediately.
- Right-to-left languages (Arabic, Hebrew, ...) work offline.
- Optional: connect a Lukaisu Server for Chinese/Japanese word splitting, text-to-speech, transcription, and online text discovery — including plain-HTTP servers on your LAN.
- A thin Capacitor shell over the system WebView (no Chrome dependency, no Google Play Services, no tracking) — works on de-Googled phones.
- First run is local. A fresh install seeds a few language presets and sample texts into an on-device database and opens straight to your library — no server picker in the way.
- Reading happens on-device. Texts are parsed into words locally (the space-separated and right-to-left parsers run in TypeScript); word status, translations, and review scheduling are stored and updated in the local database (IndexedDB via Dexie).
- Connecting a server is optional. A bundled connect screen (reachable
from Settings) validates a Lukaisu Server by probing
GET /api/v1/version; once connected, the server-enhanced extras light up. With no server, CJK languages fall back to character-by-character parsing — functional, lower quality — and the app never blocks on the network.
This repo owns the reading frontend outright (webapp/ — TypeScript, Svelte
5, CSS themes): reader, library, on-device database, parsers, and the
local-first data layer all live and are built here. No other repository
checkout is involved in building the app.
A server is only needed for the enhanced features above. When you connect one:
- Point the app at a Lukaisu Server (needs
GET /api/v1/version; for multi-user login, the/connectflow). The API originated in LWT 3.x, so compatible LWT 3.1+ servers also work. - Plain-HTTP servers (e.g.
http://192.168.1.10:8010on your LAN) work — the app allows cleartext traffic. Self-signed HTTPS certificates do not work in the Android WebView; on a LAN prefer plain HTTP, or use a real certificate (e.g. Let's Encrypt / a reverse proxy). - The bundled frontend calls the server cross-origin, so the server must
allow the app's WebView origin via
CORS_ALLOWED_ORIGINS=https://localhost.
Requirements: Node 20+, JDK 21 (Gradle 8.14 does not run on Java 25), Android SDK (platform 36).
npm install
npm run apk:debug # build the app, bundle it, assemble the APK
# → android/app/build/outputs/apk/debug/app-debug.apkapk:debug chains sync — build (vite build --config webapp.vite.config.ts → dist/), build:themes (theme CSS the dark-mode
toggle loads at runtime), cap sync — then gradlew assembleDebug. Expects
JAVA_HOME/sdk.dir to be set up (android/local.properties holds
sdk.dir).
Dev loop: npm run dev (Vite HMR against webapp/). npm test runs the
frontend's vitest suite; npm run typecheck/npm run lint cover TS +
Svelte.
Additional scripts:
npm run apk:release— build a signed release APK (requires keystore setup)npm run e2e— run Cypress end-to-end testsnpm run test:watch— vitest in watch mode
To test against a local Lukaisu Server: from a lukaisu-server checkout,
docker compose up (http://localhost:8010); set MULTI_USER_ENABLED=true in
its .env to exercise the login flow. For phone-on-LAN testing use the
machine's LAN IP.
webapp/— the reading frontend (TypeScript, Svelte 5, CSS themes): reader, library, on-device database (IndexedDB/Dexie), parsers, router.webapp.vite.config.tsbuilds it,webapp-tests/tests it.webapp/app/— page entry points (HTML + TS) for each route, Svelte components, andsvelte.config.js.webapp/js/— shared TypeScript modules includingclient.ts(app bootstrap),sw.ts(service worker for PWA-like behavior), and the offline-first data layer.locale/en/— a frozen duplicate oflukaisu-server's English strings, bundled at build time as the offline i18n fallback for when no server is connected.public/— static WebView chrome (error.html, the app icon) copied intodist/at build time.capacitor.config.ts— app id/name (org.lukaisu.app), WebView navigation policy, cleartext permissions.android/— the Capacitor Android project (committed per Capacitor convention;local.propertiesand build outputs are ignored).scripts/— build utilities (e.g.,build-themes.jsfor dark/light theme CSS).cypress/— end-to-end test suite.fastlane/— F-Droid/Play store metadata (title, descriptions, changelog, icon).
Not yet published to F-Droid or the Play Store. See ROADMAP.md
for release status and what's next.
Lukaisu is free and non-profit. If you'd like to support its development, you can donate on Open Collective.