Search: highlighting and excerpts land on the wrong letters after a Turkish İ #377
Labels
No labels
accessibility
authentication
breaking change
bug
documentation
enhancement
interface
internationalisation
observability
performance
security
tier
1
tier
2
tier
3
tier/4
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
Postulo/postulo#377
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Found on 2026-09-29 in a read-only review of postulo at
95348908d, reading the search results' highlighting and checking it with a Turkish place name.Checked again on 2026-10-01 against
mainat0a5cad587: still present. The code is now atcore/templatetags/postulo.py:468-490forhighlight(#248 moved the flag lookup out of that file);excerptis unchanged atcore/search.py:84-100.highlightandexcerptfind the term intext.lower()and then slice the originaltextwith the positions found there. That assumes lower-casing keeps a string's length. It does not forİ(U+0130, the capital dotted I), which Python lowers to two code points, so every position after one is shifted. Turkish is a shipped language, and İstanbul and İzmir are common in job locations.core/templatetags/postulo.py:486-508(highlight, used attemplates/core/search.html:49-54on every hit's title, subtitle and excerpt);core/search.py:84-100(excerpt).highlight("İstanbul office", "stan")returnsİs<mark>tanb</mark>ul office: the mark covers letters that are not the term.highlight("Office in İzmir and İstanbul, stand-up daily", "stand")marksand-u; the shift grows with eachİbefore the match.excerpt("İ" * 100 + " needle here", "needle", radius=5)returns"…": the window is computed past the end of the text, and the passage that matched is not shown at all.The output stays escaped, so nothing unsafe is produced; the result is simply wrong. U+0130 is the only code point whose
str.lower()changes length.re.finditer(re.escape(query), text, re.IGNORECASE)inhighlight, andre.searchinexcerptfor the position. The query itself can containİ, so use the match's own span rather thanlen(query)for the end.Done when
tests/test_search.py(or the template tag's tests) checks thathighlight("İstanbul office", "stan")returnsİ<mark>stan</mark>bul office, and that an excerpt of a text with severalİbefore the match contains the match.