Match keywords typed in Arabic script, and stop splitting combining marks - #48
Conversation
…arks
Two bugs, one pipeline.
stripSpecialCharacters kept `\p{L}` and `\p{N}` and turned everything else
into a space. A combining mark is neither, so every mark became a space
*before* foldDiacritics ran. That silently defeated the diacritic folding
this module advertises: "senor" typed in NFD, which Instagram does return,
became "sen or" and stopped matching a "senor" keyword. Devanagari and Thai
vowel signs were split off their base letters the same way. Adding `\p{M}`
to the keep-set lets marks reach foldDiacritics, which already knows which
ones are safe to drop per script.
The second bug is Arabic script. foldDiacritics deliberately leaves
non-Latin marks alone because they are load bearing in Devanagari, Thai and
Japanese. In Arabic script they are not, and neither are several letter
variants: an Iranian commenter typing on an Arabic keyboard sends U+064A and
U+0643 where the account owner typed U+06CC and U+06A9. The two strings
render identically and never compare equal, so a Persian campaign keyed on
"لینک" missed every one of them. Same for Persian-Indic digits against an
ASCII-digit keyword, for the ZWNJ spelling split ("قیمتها" vs "قیمتها"),
for kashida stretching, and for harakat.
normalizeArabicScript folds those on both sides of the comparison, so it
never matters which form was typed into the campaign builder. Latin,
Cyrillic and CJK are untouched.
16 tests added; every case in them returned false before this change.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
@hhakamian82 is attempting to deploy a commit to the diwenne's projects Team on Vercel. A member of the Team first needs to authorize it. |
|
What this does — 2 fixes, 1 pipeline line: 1. 2. Arabic-script campaigns never matched across keyboard layouts. Safe for existing users? Yes. Idempotent? Yes.
Merging. |
diwenne
left a comment
There was a problem hiding this comment.
Reviewed locally: 184/184 tests pass, typecheck/lint clean. \p{M} fix repairs NFD diacritic folding; normalizeArabicScript is narrowly scoped to Arabic-script codepoints, applied both sides, no effect on Latin/Cyrillic/CJK. Safe + idempotent — no migration, pure match-time normalization.
Two bugs that share one line of the matching pipeline. Both are reproduced by the tests in this PR; every assertion added here returned
falsebefore the change.1.
stripSpecialCharactersdestroys combining marks beforefoldDiacriticscan fold themstripSpecialCharacterskeeps\p{L}and\p{N}and turns everything else into a space. A combining mark is neither, so marks became spaces beforefoldDiacriticsran — which defeats the diacritic folding this module exists to provide:The module's own docstring notes that "Instagram returns both" precomposed and decomposed forms, so this is reachable in production, not a theoretical case. The same replace split Devanagari and Thai vowel signs off their base letters (
"किताब"→"क त ब").Fix: add
\p{M}to the keep-set. Marks now survive tofoldDiacritics, which already decides per script which ones are safe to drop.2. Arabic-script text never matches across keyboard layouts
foldDiacriticsdeliberately leaves non-Latin marks alone, because they are load bearing in Devanagari, Thai and Japanese. In the Arabic script they are not — and neither are several letter variants.A commenter typing Persian on an Arabic keyboard sends U+064A (ي) and U+0643 (ك) where the account owner typed U+06CC (ی) and U+06A9 (ک). The strings render identically and never compare equal, so a campaign keyed on
لینکmisses every one of those commenters. This is not an edge case for a Persian-speaking account; both layouts are in everyday use.Cases that were all silently missing:
لينك بدهلینکكمكکمککد۵کد5قیمتهاقیمتهالیــنکلینکلِینکلینکnormalizeArabicScriptfolds yeh/kaf/alef/teh-marbuta variants, drops harakat, tatweel and ZWNJ, and maps Persian and Arabic-Indic digits to ASCII. It is applied to both sides of the comparison, so it never matters which form the account owner typed into the campaign builder. Latin, Cyrillic and CJK are byte-for-byte untouched — there is an assertion for that.ZWNJ is deleted rather than turned into a space because the no-separator spelling is the fallback people actually type.
Behaviour preserved
لینکدونیdoes not matchلینکwithwholeWordMatch, and does with partial matching.keyword-matcher.test.tsis unchanged and still passes.Verification
🤖 Generated with Claude Code