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 stempel module)
    • 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_yarovoy in Rust’s
      tantivy-stemmers collection)

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 → analiza with 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 during quartz build-style indexing
    → store stemmed tokens in contentIndex.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) => string stemmer; built-ins are Snowball-compiled JS.
    Compile Snowball’s polish.sbl to JavaScript → drop in as
    components.tokenizer.stemmer. Best in-repo reference:
    ZBSearch’s svk.js Slovak 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 + explicit pol locale.
  • 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.