A Finnish learning dictionary that turns lookup into study.
Sanakota helps learners move from a real Finnish form in the wild to an understood, saved, and reviewable sense. It is built for inflected forms, compounds, phrases, partial understanding, and bilingual learner workflows instead of assuming that every search starts with a clean dictionary headword.
- Why Sanakota
- Current Scope
- Architecture
- Repository Layout
- Quick Start
- Dictionary Data
- Development
- Project Status
- Contributing
- License
Most language tools separate the lookup moment from the learning loop.
A dictionary can explain a word, but it often does not preserve which sense mattered in context, how the observed surface form maps back to a lemma, or whether the result should become study material. A flashcard tool can help later, but usually starts after the original reading or listening moment has already disappeared.
Sanakota is organized around a tighter loop:
search -> understand -> save -> review
Sanakota is currently a pnpm monorepo for a search-first Finnish learning dictionary. The active scope is intentionally focused:
- Learner lookup: lemma search, generated surface forms, close matches, compounds, collocations, constructions, and staged resolution through a dedicated dictionary service.
- Sense-level study: saved learning state is attached to senses rather than flattening a whole lexeme into one card.
- Bilingual presentation: learner-facing UI and dictionary payloads support English and Simplified Chinese, with persistent language selection in the web app.
- Dictionary-first data model: source imports, canonical dictionary rows, runtime releases, generated fallback payloads, and promotion workflows are kept as separate layers.
- Open development workflow: CI, CodeQL, Dependabot, issue templates, contribution guidance, security policy, and a root verification command are included.
Sanakota is not yet a production SaaS, a full Anki/FSRS replacement, a native mobile app, or a general-purpose Finnish NLP platform.
Sanakota models dictionary knowledge around SurfaceForm -> Lexeme -> Sense.
The surface form is what a learner saw or typed, the lexeme is the lemma-level
entry, and the sense is the unit that can be understood, saved, reviewed, and
eventually measured.
flowchart TB
browser["Learner browser"]
subgraph frontend["apps/web"]
web["React + Vite PWA\nsearch, detail, library, review"]
end
subgraph runtime["Runtime services"]
api["apps/api\nAuth, study state, preferences,\nlegacy dictionary/search surfaces"]
dictionary["apps/dictionary-service\nLookup, suggest, fallback jobs,\npromotion workflows"]
end
subgraph data["Data stores"]
appdb[("sanakota_db\nusers, saved senses,\nsource imports")]
dictdb[("sanakota_dictionary\nversioned releases,\nentry payloads, fallback jobs")]
meili[("Meilisearch\nrelease-scoped resolution index")]
end
subgraph build["Dictionary build path"]
imports["apps/api scripts\nFinnWordNet/Kielitoimisto import,\nmorphology, materialization"]
pipeline["packages/dictionary-pipeline\npublish release and index docs"]
end
providers["Optional fallback providers\nfixture, OpenAI, DeepSeek"]
shared["packages/shared\ncontracts, constants, types"]
browser --> web
web -->|auth, saved senses, study preferences| api
web -->|lookup, suggest, fallback status| dictionary
api --> appdb
dictionary --> dictdb
dictionary --> meili
dictionary --> providers
imports --> appdb
imports -->|normalized release inputs| pipeline
pipeline --> dictdb
pipeline --> meili
web -.-> shared
api -.-> shared
dictionary -.-> shared
pipeline -.-> shared
Design boundaries:
- Dictionary-first: structured dictionary data is the factual baseline.
- Study-by-sense: learning state belongs to senses, not undifferentiated words.
- Source-aware: imported source data, canonical runtime data, generated fallback data, and promoted release data remain auditable.
- Shared contracts: app boundaries communicate through
@sanakota/shared/*contracts instead of duplicate ad hoc DTOs.
For deeper implementation notes, see docs/architecture.md.
apps/web React + Vite frontend and PWA shell
apps/api Express API for auth, study state, preferences,
legacy search/detail surfaces, and source imports
apps/dictionary-service Express dictionary runtime for lookup, suggest,
fallback generation, and promotion workflows
packages/shared Shared contracts, constants, and public types
packages/dictionary-pipeline Dictionary release publishing and Meilisearch indexing
docker/postgres Local database bootstrap helpers
docs Architecture and maintainer-facing notes
- Node.js 20.20 or newer
- pnpm 10
- Docker, for the bundled PostgreSQL and Meilisearch development stack
pnpm installcp apps/api/.env.example apps/api/.env
cp apps/web/.env.example apps/web/.env
cp apps/dictionary-service/.env.example apps/dictionary-service/.envdocker compose up -d postgres meilisearchThe Compose stack exposes:
- PostgreSQL on
localhost:5432 - Meilisearch on
http://127.0.0.1:7700 - Application database
sanakota_db - Dictionary database
sanakota_dictionary
pnpm --filter sanakota-backend migrate
pnpm --filter sanakota-dictionary-service migrate
pnpm publish:dictionary:phase1apublish:dictionary:phase1a publishes the checked-in mini release fixture into
the dictionary database and Meilisearch. It is enough for local smoke testing
without private source data.
pnpm devOpen:
- Web app:
http://localhost:5173 - Core API:
http://localhost:5050 - Dictionary service:
http://localhost:5060
The repository keeps dictionary data work explicit and layered:
- Source import scripts load source-native records into
source_*tables. - Materialization scripts build canonical lexemes, senses, relations, and generated surface forms.
packages/dictionary-pipelinepublishes versioned runtime releases intosanakota_dictionary.- Release-scoped resolution documents are indexed in Meilisearch.
apps/dictionary-serviceserves lookup, suggest, fallback, and promotion flows against the active release.
Useful commands:
pnpm --filter sanakota-backend normalize-kielitoimisto --input=/absolute/path/to/raw-kielitoimisto.json --output=/absolute/path/to/kielitoimisto.entries.ndjson
pnpm --filter sanakota-backend import-finnwordnet
pnpm --filter sanakota-backend import-kielitoimisto --file=/absolute/path/to/kielitoimisto.entries.ndjson
pnpm --filter sanakota-backend materialize-canonical-dictionary
pnpm --filter sanakota-backend materialize-generated-surface-forms
pnpm publish:dictionary:phase1a
pnpm promotion:run
pnpm promotion:publishFallback generation can use the local fixture provider by default. OpenAI and
DeepSeek providers are optional and are configured through
apps/dictionary-service/.env; generated fallback payloads remain provisional
until they are explicitly reviewed and promoted.
Root commands are the source of truth:
pnpm build
pnpm lint
pnpm typecheck
pnpm test
pnpm verify
pnpm audit:prodTargeted commands:
pnpm dev:web
pnpm dev:api
pnpm dev:dictionary
pnpm test:web
pnpm test:api
pnpm test:dictionary
pnpm test:pipeline
pnpm test:sharedDatabase integration tests are opt-in:
pnpm test:api:dbSanakota is in active alpha. The web learner loop, core study state, dictionary runtime, fallback infrastructure, and publishing pipeline are under active development and covered by unit/contract/integration tests. The project is ready for focused contributions, but not for production deployment without additional hardening.
Current priorities:
- Improve Chinese and English learner-facing explanations.
- Strengthen Finnish morphology, compound handling, collocations, and construction coverage.
- Keep generated fallback data auditable and separate from canonical entries.
- Harden authentication, account management, rate limits, and operational deployment paths.
- Refine review scheduling while preserving the simple guest-compatible learner flow.
Contributions are welcome, especially small changes that improve lookup quality, study-state correctness, test coverage, documentation, and developer setup.
Before opening a pull request, please read CONTRIBUTING.md and run:
pnpm verifyPlease report security issues through the process in SECURITY.md instead of opening a public issue with exploit details.
Sanakota is licensed under GPL-3.0-only.
