Does your WooCommerce search find Herrenschuhe for Schuhe?
German compound nouns expose what product search really stores. See how decompounding, filter order, and an asymmetric analyzer make a base noun reach the right products.
A German shopper types Schuhe. Your catalog contains Herrenschuhe, Kinderschuhe, and Fußballschuhe. Whether those products come back is decided before the query is run—when the search engine cuts each title into tokens.
The 60-second model
- Compound matching is a tokenization problem, not a filter-UI feature.
- A generic search analyzer can find fewer German products than SQL substring matching.
- Decompounding belongs at index time; the shopper’s query stays narrow.
- Filter order decides whether the split works, and a wrong order can fail silently.
- The final section gives you a test you can run on your own catalog.
Three engines can store one German word in three different ways
Search succeeds only when the query token intersects the tokens stored for the product.
The word Herrenschuhe is a single orthographic word made of Herren and Schuhe. German builds nouns this way constantly, especially in product catalogs: Winterjacke, Waschmaschinenzubehör, Damenmantel, Sicherheitsschuhe, and Kühlschrankdichtung.
A SQL condition such as LIKE '%schuhe%' can find the substring inside Herrenschuhe. It did not understand the compound; it happened to scan across it. A generic tokenizing analyzer instead stores one whole token. The query Schuhe produces a different token, so the two sets never intersect.
A German analyzer with decompounding stores the whole word plus useful subwords. The query can then reach the product through a real token match that can be scored, boosted, and combined with structured filters.
| Engine | Herrenschuhe becomes | Does Schuhe match? | Main trade-off |
|---|---|---|---|
| SQL substring match | Raw text, no tokens | Yes, as a substring | No language analysis or controlled relevance |
| Generic analyzer | herrenschuh | No | Can rank matches, but this match disappeared |
| German decompounding chain | herrenschuh, herren, schuh | Yes, as a token | Requires curated artifacts and reindexing to change them |
A better search engine can return fewer German products when its analyzer is wrong.
The engine is not the language model; the analyzer chain is
Hyphenation proposes the split; the dictionary decides which subwords survive
Useful decompounding is a controlled language artifact, not every possible substring.
Elasticsearch offers a dictionary decompounder and a hyphenation decompounder. The first tries substrings against a word list and can emit noise. The second uses a hyphenation grammar to propose linguistically plausible split points, then retains the candidates that exist in a curated dictionary.
The current WPXFacets configuration uses:
"de_decompound": {
"type": "hyphenation_decompounder",
"hyphenation_patterns_path": "analysis/de_DR.xml",
"word_list_path": "analysis/de_words.txt",
"only_longest_match": true,
"min_subword_size": 4
}
Both files must exist in $ES_PATH_CONF/analysis/ on every node. Elasticsearch refuses to create an index when a named analysis file is absent. The analyzer profile and server provisioning are therefore one deliverable, not two.
The artifacts must remain reproducible and licensable
The current de-v1 artifact set is produced by a committed build pipeline: the OFFO FOP v1.2 de_DR.xml grammar, a normalized word list derived from igerman98, plus version, licensing, and checksum records. A second build produced byte-identical output.
The license summary still needs its publication-day review. The operational rule is already clear: a handed-over node, backup, or future self-hosted edition would require a fresh distribution-compliance check.
Normalization must happen before the dictionary sees the word
The same filters in the wrong sequence can silently turn German decompounding off.
The German index chain currently runs in this order:
german_normalization runs before de_decompound. The word list is normalized with the same filter at build time: ä becomes a, ö becomes o, and ß becomes ss. Reverse those positions and the decompounder compares incompatible forms. It returns no useful split and raises no obvious error.
asciifolding follows decompounding. It can still make Café reachable from Cafe without changing the representation that the German dictionary expects. The word delimiter leads the chain because the verified fixture showed that A55 survives intact as a55 and also produces a and 55, without changing the compound outputs.
Herren to herr and Schuhe to schuh, while preserving A55 as a55 and also emitting a and 55.Expand the index, but keep the shopper’s query narrow
Index-time decompounding creates paths to a product; query-time decompounding throws away the specificity a shopper typed.
Index: word delimiter → lowercase → German normalization
→ decompound → ASCII folding → light German stemmer
Search: lowercase → German normalization → ASCII folding
→ light German stemmer
de_decompound is deliberately absent from the search analyzer. If a query for Winterjacke were split into winter and jacke, it could retrieve Winterhose, Winterstiefel, and every other winter product. Recall would expand by sacrificing the precision of the original term.
The rule is asymmetric because the two sides have different jobs. Splitting stored product text adds legitimate ways to reach that product. Splitting the shopper’s query broadens an explicit request. The fixture suite locks this difference in so a future cleanup cannot make the two chains symmetrical by accident.
Stopword removal can erase valid product queries
The German, Romanian, and neutral profiles do not remove stopwords. A German stop list contains terms such as man, war, will, die, so, and also. In a mixed-language catalog, those are ordinary queries. An analyzed query can collapse to no tokens and return no products without an obvious error.
The test suite records both the working compounds and the known miss
Passing examples establish token and matching behavior; the failing example keeps the current dictionary boundary visible.
On August 10, 2026, the chain was checked on an isolated node with inline _analyze fixtures, the files loaded from that node, and a temporary index built from the plugin’s real product mapping and queried through its real query builder. Temporary indexes were deleted after each run and did not touch a live product index.
| Query | Asserted result | What it proves | Current caveat |
|---|---|---|---|
Schuhe | Finds Herrenschuhe, Fußballschuhe, and Kinderschuhe | The base noun reaches several compounds | Match presence, not ranked quality |
Herren / Kinder | Finds the corresponding compound | Both sides of useful splits remain searchable | Fixture catalog is intentionally small |
Winterjacke | Returns Winterjacke only | The query analyzer does not decompound | Does not measure relevance across a retail catalog |
Cafe / A55 | Finds Café Mantel / Herrenschuhe A55 | Folding and SKU-shaped tokens survive | Only the asserted forms are established |
Fußball | Does not find Fußballschuhe | The current dictionary has a known boundary | Cause has not yet been isolated |
The plausible explanation for the final miss is the interaction between only_longest_match and the words available around that split point. That is an inference, not a measured finding. It requires dictionary work against a real German catalog before it can be closed.
What the fixture does not prove
It verifies tokens and matching, not result ordering. No German golden-query set, MRR, nDCG@10, or before/after retail comparison exists yet. The English phone demo is an interface example, not evidence of German relevance.
Dictionary changes require a new index; synonyms can change without one
The reindex boundary determines which relevance improvements can be made routinely on a live catalog.
Analyzers are frozen when an index is created. Changing the decompounding dictionary, filter order, or analyzer profile means building a new index and reindexing every product. That is a scheduled operation on a large catalog, not an inline edit.
A synonym set deployed through the Elasticsearch Synonyms API can reload the search analyzers without closing the index or reindexing. Synonyms are therefore the better tool for knowledge learned continually: brand variants, regional terms such as Brötchen, Semmel, and Schrippe, Anglicisms, and recurring zero-result terms.
There are implementation boundaries. A graph-producing word delimiter cannot precede synonym_graph while synonym rules are parsed, so the search analyzer drops that delimiter. Synonyms also reach the main text fields immediately, while some dynamic meta and taxonomy fields pick them up only after the next full sync.
One analyzer profile cannot honestly solve a multilingual catalog
A language-aware German index is useful precisely because it does not pretend the same rules are correct for every language.
An Elasticsearch field has one analyzer chain. In a genuinely multilingual catalog, whichever language owns that chain makes the other languages accept its stemming and normalization rules. WPXFacets currently resolves a profile from the WordPress locale and falls back to a neutral chain—no stemming and no stopwords—when a multilingual installation is detectable.
German locales including de, de_AT, and de_CH resolve to the German profile. Installations whose catalog language cannot be inferred have a validated profile filter. The cleaner long-term model is one searchable product document per language, with language as a required constraint and a corresponding analyzer. That remains a design direction, not current WPXFacets behavior.
The plugin can declare the artifact version expected by an index, but Elasticsearch does not expose an analysis file for checksum verification. The authoritative check is behavioral: ask the cluster to analyze a known compound and compare the emitted tokens.
Ask your own index what it stores before changing boosts or fuzziness
Two Analyze API calls and one negative test separate a tokenization problem from a ranking problem.
If your search is SQL-based, inspect the generated condition first. A leading-wildcard substring condition may find the compound without providing language analysis or controlled relevance. The exact current WooCommerce SQL behavior still needs its publication review before the article makes that implementation claim final.
If you already run Elasticsearch, ask the product field what it does with the compound:
GET /<your-index>/_analyze
{
"field": "post_title",
"text": "Herrenschuhe"
}
Then analyze the shopper’s query through the search analyzer:
GET /<your-index>/_analyze
{
"analyzer": "<your-search-analyzer>",
"text": "Schuhe"
}
Compare the token lists. If the query token does not appear in the tokens stored for the title, field boosts and fuzziness are working one layer too late. Then run the negative test:
GET /<your-index>/_analyze
{
"analyzer": "<your-search-analyzer>",
"text": "Winterjacke"
}
If the query returns separate winter and jacke tokens, the search-time chain is decompounding and precision may leak. Finish with ten real compounds from your best-selling categories and write down the shorter terms shoppers actually use. That catalog-specific list is more useful than a generic vendor benchmark.
Your acceptance condition
The base query token appears in the indexed compound’s token set, while a specific compound query remains intact at search time. Record both the positive and negative examples so future dictionary changes cannot move the boundary silently.
The fix is an inspectable analysis chain, not a faster search box
German compounds show why product discovery depends on how language is represented before any ranking formula runs.
A search engine added without a German analyzer can reduce recall compared with the naive substring matching it replaces. Decompounding belongs at index time, the query should stay narrow, and filter order is part of the behavior—not an implementation detail that can be rearranged freely.
The current fixtures establish token and matching correctness for a small set of compounds and keep one failure visible. They do not establish ranked relevance across a real German retail catalog. That next claim needs a dedicated demo shop and versioned golden queries.
Does the query token exist in the product tokens your engine actually stored?
Ask this before tuning boosts, fuzziness, or synonyms
Sources and evidence
Analyzer behavior and Elasticsearch defaults can change. Recheck these sources against the exact version you run.
- Elasticsearch: hyphenation decompounder
- Elasticsearch: dictionary decompounder
- Elasticsearch: German normalization
- Elasticsearch: light German stemmer
- Elasticsearch: ASCII folding
- Elasticsearch: word delimiter graph
- Elasticsearch: synonym graph
- Elasticsearch: Synonyms API
- Elasticsearch: Analyze API
- OFFO hyphenation patterns
- igerman98 German dictionary