The slug rule that deleted a letter from the name of Allah

We needed URL slugs for a few hundred Arabic quotations. The rule looked simple enough: strip the ḥarakāt, keep the letters, turn everything else into a hyphen.

regexp_replace(text, '[^ء-يa-z0-9]+', '-', 'g')

ء to ي is U+0621 to U+064A — the Arabic letters. It produced clean, readable slugs. It was also quietly wrong on 102 of 250 rows.

What it did

SourceSlug
ٱلْعُسْرِلعسرal-ʿusr → l-ʿusr
ٱلْقُلُوبُلقلوبal-qulūb → l-qulūb
ٱللَّهِللهthe name of Allah, without its alif

The definite article had lost its alif — in a slug, on a public page, for a verse of the Qur'an.

Why the range was wrong

The Uthmani text does not spell the article's alif with ا (U+0627). It uses alif wasla, ٱ, U+0671 — which sits outside U+0621–064A and therefore outside the range we kept. So it was treated as punctuation and replaced with a separator, which then collapsed against the surrounding hyphens and vanished.

Alif wasla is a letter. It carries a diacritic-looking mark, but it is not a diacritic: it marks an alif whose vowel elides in connected speech. Strip it and you have changed the word.

The same range excluded the Qur'anic pause marks — ۖ ۗ ۙ ۚ ۟ ۢ, U+06D6 to U+06ED. Those genuinely are annotations and should have been stripped, but as separators they were splitting words in half instead.

The fix, and the way we should have got there

Normalise before stripping, and strip the annotation block rather than separating on it:

translate(text, 'ٱ', 'ا')                    -- alif wasla → alif
regexp_replace(…, '[ً-ٕٓ-ٗ٘-ٰـۖ-ۭ]', '', 'g')  -- harakat, dagger alif, Qur'anic marks
regexp_replace(…, '[^ء-يa-z0-9]+', '-', 'g')  -- then separate

The real lesson is not the range. It is that we guessed the character set the first time and enumerated it the second. One query over the actual corpus — every distinct non-ASCII character present — returns the exact list in a second, and it contained several things nobody would have thought to include.

A postscript on assertions

The obvious regression test is "no slug may contain لله". We wrote it, and it failed on five rows that were completely correct: لِلَّهِ, meaning "to Allah", is genuinely spelled lām-lām-hāʾ with no alif at all. The defect only exists when the source had the wasla, which is not a condition a simple pattern expresses.

So the test pins three known rows to three exact expected values instead. Less elegant, and it does not accuse correct data of being broken.

Notes from the workshop.