Skip to main content

arabic-lint

Finds Arabic text that was corrupted before it was stored — in your JSON, your localisation files, your database exports, your source code.

pip install arabic-lint
arabic-lint ./src
src/strings.json:3:20: 21 Arabic presentation forms stored  [UNSAFE TO AUTO-FIX]
    found     : ﺓﺪﺤﺘﻤﻟﺍ ﺔﻴﺑﺮﻌﻟﺍ ﺕﺍﺭﺎﻣﻹﺍ
    would be  : اإلمارات العربية المتحدة
    contains a lam-alef ligature; NFKC decomposition reorders the pair, so this
    recovery is wrong even though it looks like Arabic

3 corrupted span(s) in 1 file(s); 1 cannot be auto-fixed safely.

Exit code 1 when anything is found, so it drops into CI unchanged. MIT. Zero dependencies. Python 3.9+.

What it actually detects

The most widely copied recipe for "making Arabic work" in Python is:

text = get_display(arabic_reshaper.reshape(text))

Those two calls do the job a text engine is supposed to do: substitute each letter for its contextual presentation form, and reorder the string into visual order. If your renderer already does complex text layout — Pillow with Raqm, matplotlib, any browser — the work happens twice and the output is wrong.

The real damage is when that string gets written back: to a config, an export, a translation file. Now the corruption is at rest and every downstream reader inherits it. It renders as clean-looking Arabic, so nobody who does not read the script will ever notice.

The signature is unambiguous: Arabic Presentation Forms-B codepoints (U+FE70–U+FEFF) in stored text. Those exist for legacy-encoding compatibility; correctly authored modern Arabic never contains them.

Why it reports instead of fixing

You would think you could just undo it: NFKC maps every presentation form back to its base letter, and reversing undoes the visual reordering. That round-trips exactly — until the span contains a lam-alef ligature.

A lam-alef ligature (لا, لأ, لإ, لآ) is one codepoint standing for two letters. NFKC expands it in logical order while the text around it is still in visual order, so the pair comes out reversed relative to its neighbours:

original naive "fix"
الإمارات اإلمارات not a word
السلام السالم a real but different word

That second row is the whole reason this tool exists rather than a sed command. The output is still pronounceable Arabic, so it survives a proofread — and the definite article followed by alef is one of the most common sequences in the language, so this is not a corner case.

arabic-lint shows you the candidate recovery and tells you when it is unsafe. It never rewrites your files.

Verification

  • 10/10 tests, no runtime dependency on arabic_reshaper or python-bidi (fixtures are recorded from a real run of both).
  • Block boundaries were measured, not assumed: over a wide Arabic sample, arabic_reshaper 3.0.0 emits 53 distinct codepoints from Presentation Forms-B and never emits U+FEFF.
  • Validated against 3,826 real files — the false positives that scan found are now regression tests:
    • U+FEFF sits inside Forms-B but is the byte order mark. Excluded.
    • Presentation Forms-A is deliberately not a signal. The reshaper emits exactly one codepoint from it (U+FDF2, the Allah ligature), and that character — like U+FDFA, U+FDFB and U+FDFD — is used intentionally in ordinary Arabic writing. Treating the block as corruption flags correct religious and formal text.

Known limits

  • A document whose only Arabic is a standalone Allah ligature is missed. That is the deliberate trade above; any corrupted phrase around it still trips Forms-B.
  • The recovery direction assumes bidi was applied. Text that was reshaped but not reordered recovers reversed. The tool shows you the candidate so you can see which case you have; it does not guess.
  • It detects corruption that is already stored. It cannot tell you whether your rendering pipeline is about to create some — for that, check PIL.features.check("raqm") at runtime in the environment doing the rendering.

Related

Part of a series measuring where Arabic silently breaks in software. See also arabic-tts-frontend — numerals, dates and currency converted to spoken Arabic before synthesis.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

arabic_lint-0.1.0.tar.gz (9.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

arabic_lint-0.1.0-py3-none-any.whl (9.6 kB view details)

Uploaded Python 3

File details

Details for the file arabic_lint-0.1.0.tar.gz.

File metadata

  • Download URL: arabic_lint-0.1.0.tar.gz
  • Upload date:
  • Size: 9.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.14.6

File hashes

Hashes for arabic_lint-0.1.0.tar.gz
Algorithm Hash digest
SHA256 030156546a247ba41b4c96bc9c76c8144c790a5f7ea77c0795dc5271795fdb1c
MD5 f40ac1c0fbc34cc4cdaa1a2900f75236
BLAKE2b-256 7b08edd02cec28247a9f4b0a4f058981b2c0dad18eab07ff367eb75746321938

See more details on using hashes here.

File details

Details for the file arabic_lint-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: arabic_lint-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 9.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.14.6

File hashes

Hashes for arabic_lint-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 0af7af318a4a9f6470c3ef73b054eeb96a83f793568eae25cdec7105093962d9
MD5 380bcc3ed6e3cc1874bd6dd29b5eda56
BLAKE2b-256 ea3fec9293f6742163c026e518f10d6aaaa38bf2a271397c55f239ec78d26a87

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page