Polish stemming and lemmatization
Open-source options for normalizing inflected Polish for search (7 cases ×
2 numbers × genders → one word has dozens of forms). Researched 2026-09-13
after Quartz search showed FlexSearch missing inflected queries.
Related: Search solutions for static sites.
Stemmers (rule/table-based, fast, no dictionary dependency)
- Snowball Polish — official algorithm
(snowballstem.org/algorithms/polish/stemmer.html), implementations in
C/Java/JS/Python/Rust/Go/Zig via the Snowball compiler. This is what
Pagefind, Typesense and lunr-languages ship. Light stemmer: strips
suffixes by rules; quality is “decent, not great” for Polish. - Stempel — the serious one. Algorithmic table-driven stemmer from the
Egothor project (Andrzej Białecki), shipped in Apache Lucene /
Elasticsearch since forever. Trained on corpora; markedly better than
rule-only stemmers on Polish.- Original: Java (Lucene
stempelmodule) - pystempel (Python port, verified 100% identical output to Java on
331k sjp.pl words;Stemmer.default()table 0.3 MB /Stemmer.polimorf()
table 2.2 MB trained on 259k Polimorf entries, ~50–60k words/sec) - Go port exists (used as the base of
polish_yarovoyin Rust’s
tantivy-stemmers collection)
- Original: Java (Lucene
Lemmatizers (dictionary-based, exact base forms)
- Morfeusz 2 (morfeusz.sgjp.pl) — the Polish morphological analyzer,
from the SGJP (Grammatical Dictionary of Polish) team. True
lemmatization:analizy → analizawith full tag interpretation.
Bindings: C, Python, Java. The dictionary (Polimorf = SGJP + Morfologik
merge, ~216k lexemes / 3.4M forms) is the training data everything else
uses. - Morfologik — ispell-format Polish dictionary + Java tooling; the
other half of Polimorf.
Rule of thumb: stemmers for indexing speed (one pass, no lookup table
beyond the training table), lemmatizers for quality (dictionary
lookup, needs disambiguation for ambiguous forms). For search engines the
stemmer is usually enough; for analytics/ML pipelines use Morfeusz.
Practical integration paths
- Build-time pre-stemming for a static site: run pystempel (or the
Snowball JS output) over the corpus duringquartz build-style indexing
→ store stemmed tokens incontentIndex.json→ stem the query the same
way client-side. Zero runtime deps; makes even FlexSearch match
inflections. - ZBSearch (Orama fork) custom stemmer: the tokenizer accepts any
(word) => stringstemmer; built-ins are Snowball-compiled JS.
Compile Snowball’spolish.sblto JavaScript → drop in as
components.tokenizer.stemmer. Best in-repo reference:
ZBSearch’ssvk.jsSlovak stemmer — hand-written because no Snowball
algorithm exists, and its comments document the methodology (why the
Czech stemmer is not a stand-in; diacritic-folded vs accented paths need
different aggression). Upstreamable as a PR. - Engine-side: anything Snowball-based (Pagefind, Typesense) already
stems PL. Meilisearch: charabia normalization + explicitpollocale. - Reference implementation to study: Czech support everywhere (Snowball
has official Czech; Orama ships Czech tokenizer+stopwords+stemmer) shows
the shape of “full” support for a West-Slavic language — a reference only,
our targets are EN+PL. Slovak has no Snowball algorithm — only Meilisearch
lists it (slk).
Cross-refs: Search solutions for static sites, Quartz search,
RAG, QMD.