Normalization of ä, ö,ü and ß in filters and search of duplicates

This commit is contained in:
2026-08-03 21:17:32 +00:00
parent b54b180115
commit 9b080bc860
9 changed files with 95 additions and 15 deletions
+38 -6
View File
@@ -4868,16 +4868,48 @@ def _software_job_filter_values(request: Request) -> dict[str, str]:
}
def _normalize_filter_text(value: Any) -> str:
"""Normalize user-facing text for accent- and German umlaut-aware search."""
import unicodedata
text = str(value or "").translate(
str.maketrans(
{
"Ä": "Ae",
"Ö": "Oe",
"Ü": "Ue",
"ä": "ae",
"ö": "oe",
"ü": "ue",
"": "SS",
"ß": "ss",
}
)
)
return "".join(
character
for character in unicodedata.normalize("NFKD", text.casefold())
if not unicodedata.combining(character)
)
def _sql_normalized_filter_text(column: Any):
"""Return the SQL equivalent of :func:`_normalize_filter_text`."""
expression = func.lower(func.coalesce(cast(column, String), ""))
for source, target in (("ä", "ae"), ("ö", "oe"), ("ü", "ue"), ("ß", "ss")):
expression = func.replace(expression, source, target)
return expression
def _sql_text_contains(column: Any, value: str):
"""Case-insensitive literal substring search for SQL text expressions."""
"""Literal substring search with umlaut and ASCII spelling equivalence."""
escaped = (
str(value or "")
.casefold()
_normalize_filter_text(value)
.replace("\\", "\\\\")
.replace("%", "\\%")
.replace("_", "\\_")
)
return func.lower(func.coalesce(cast(column, String), "")).like(
return _sql_normalized_filter_text(column).like(
f"%{escaped}%",
escape="\\",
)
@@ -4894,7 +4926,7 @@ def _translated_filter_codes(
The translation dictionaries are cached on the request so one filter
request does not open a database session for every possible status value.
"""
needle = str(search_value or "").strip().casefold()
needle = _normalize_filter_text(str(search_value or "").strip())
if not needle:
return []
@@ -4920,7 +4952,7 @@ def _translated_filter_codes(
for code in codes:
key = f"{prefix}.{code}"
label = cache["own"].get(key) or cache["en"].get(key) or code
if needle in code.casefold() or needle in str(label).casefold():
if needle in _normalize_filter_text(code) or needle in _normalize_filter_text(label):
matches.append(code)
return matches