Daksh Pareek

Welcome to my personal portfolio website, showcasing my projects and blogs.

Improving German Search: Splitting The Compound Word

2026-09-25


Improving German Search - Part 1: Splitting The Compound Word

We have multi locale data with us and we use full text search(FTS) to query data and return results to user.

Problem

English user who comes to our gifting platform searches for Hot air balloon ride and FTS queries as hot + air + balloon + ride. Products title stored in database also uses same seperate words like Hot Air Balloon Ride. So, FTS works well for English users.

But German joins the same concept into one word and it can join it in different ways. For example, the German translation of Hot air balloon ride is Heißluftballonfahrt. If a German user searches for Heißluftballonfahrt, the FTS query will be heißluftballonfahrt, which may not match the product title stored in the database as Heißluftballon Fahrt or Heißluft Ballon Fahrt.

Solution

As we have seen Heißluftballonfahrt is actually a compound word made up of three words: Heißluft, Ballon, and Fahrt. So, we can split the compound word into its constituent words and then perform FTS query on those words. Using this we would able to match the product title stored in the database as Heißluftballon Fahrt or Heißluft Ballon Fahrt which means same as Heißluftballonfahrt.

Some German language points to know

  1. German joins words into compounds (Komposita)

    • Example: Heißluftballonfahrt is “hot air balloon ride” in one word.
    • Why it matters: FTS compares whole words and it cannot find a word inside a longer word.
  2. The last part carries the main meaning (the head) In a compound, the last part names the thing and the parts before it describes it.

    • Example: Paarmassage is a type of massage and Ballonfahrt is a type of ride (“Fahrt”).
    • Why it matters: The important word is at the end. A prefix match (ballon:*) finds “Ballonfahrt” but it cannot find “Heißluftballonfahrt”.
  3. Compounds can contain compounds

    • Example: “Heißluftballonfahrt” is made up of “Heißluft” and “Ballonfahrt”, and “Ballonfahrt” is made up of “Ballon” and “Fahrt”.
    • Why it matters: A compound can be made up of other compounds, so we need to split recursively.
  4. Joining letters (Fugenelemente) German sometimes puts a few letters between the parts of a compound: s, es, er, en, e, n, and so on.

    • Example: Eule + n + Motiv = Eulenmotiv. Tag + es + Zeit = Tageszeit.
    • Why it matters: The splitter must skip these letters otherwise “eule | nmotiv” fails.
  5. One concept, many spellings The same product can use a compound word, a hyphen or a phrase.

    • Example: “Ballonfahrt”, “Heißluftballonfahrt”, “Fahrt im Heißluftballon” or “Dinnershow” and “Dinner-Show”.
    • Why it matters: A visitor and a product title often use different forms. Splitting brings them to the same parts.
  6. Some words only look like compounds A word can have parts that are real words but it is not a compound of them.

    • City names: “Regensburg” is not “Regen” (rain) + “Burg” (castle). “Düsseldorf” is not “Düssel” + “Dorf”. “Frankfurt” is not “Frank” + “Furt”.
    • Other words: “Wahrzeichen” is not “wahr” (true) + “Zeichen” (sign).
    • English words: “Workshop” is not “work” + “shop”.
    • Why it matters: A computer cannot tell these words from real compounds and that’s why we have to build compound-stopwords.json

Implementation flow

Step 0: Fn tokenize(text)

Step 1: Iterating over text

Step 2: Creating STOPWORDS

Step 3: Building Compound Splitter function

splitter = new CompoundSplitter(lexicon, stopwords)
splitter.split("Heißluftballonfahrt") // returns ["Heißluft", "Ballon", "Fahrt"]

Three examples

heißluftballonfahrt
  heißluft | ballonfahrt         2 parts, least common part 3
  heißluftballon | fahrt         2 parts, least common part 26  ✓
  → heißluftballon + fahrt

eulenmotiv
  eule | nmotiv                  "nmotiv" not in list ✗
  eule | n | motiv               joining letter "n" ✓
  → eule + motiv

weinprobe
  wein | probe                   "probe" not in list ✗
  → no parts (a synonym handles this word)

Step 4: Where the splitter runs

The splitter runs at two different times:

Step 5: Building the query

Results that we see

What splitting does not solve