Skip to content

fix: report unknown script reading direction instead of guessing left-to-right - #153

Merged
hatton merged 1 commit into
mainfrom
rtl
Jul 27, 2026
Merged

fix: report unknown script reading direction instead of guessing left-to-right#153
hatton merged 1 commit into
mainfrom
rtl

Conversation

@andrew-polk

@andrew-polk andrew-polk commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

What

isRTLScript now returns boolean | undefined, where undefined means "we genuinely do not know this script's reading direction". Consumers omit isRtl entirely rather than storing a fabricated false.

Why

Intl.Locale answers either ltr or rtl for every code it accepts — it has no way to say "no idea". So placeholder script codes were getting a confident left-to-right answer:

Code Meaning Languages in our data
Zyyy undetermined script 358
Zxxx unwritten (mostly sign languages) 165
Zzzz uncoded script 28
QaaaQabx private use via typed tag
unregistered (e.g. Xyzw) not a real script via typed tag

A sign language has no reading direction at all, so isRtl: false was an assertion we had not earned. With undefined, a consumer storing a writing system's direction can leave an existing setting (or the user's own choice) alone instead of overwriting it with a guess. Anyone wanting a hard boolean writes script.isRtl ?? false, which behaves exactly as before.

Also fixed

Script variant relationships are now derived from ISO 15924's own names (Aran is "Arabic (Nastaliq variant)", Syrj is "Syriac (Western variant)"), so a variant inherits its parent's direction:

  • Western Neo-Aramaic (amw-Syrj) reported left-to-right despite Syriac being right-to-left. This is the one genuinely searchable bug fixed here.
  • Sidetic (xsd-Sidt) is pinned right-to-left. CLDR marks it RTL, but ICU builds predating Unicode 16 report it left-to-right. The langtags API 1.4 data merged into this branch makes Sidetic searchable, so this now matters in the UI.

Derivation resolves: AranArab, Syre/Syrj/SyrnSyrc, CyrsCyrl, Latf/LatgLatn, Hans/HantHani.

Behavior across the whole dataset

484 language-script pairs report RTL, 8075 LTR, 554 unknown.

Reviewer notes

  • Verified against CLDR release-48-2 scriptMetadata.txt (RTL column, derived from Unicode Bidi_Class): 0 mismatches across all 179 script codes CLDR has an explicit verdict for.
  • A regression test pins Todr (Todhri) as left-to-right — an earlier draft of this work wrongly forced it RTL, which would have misrendered Albanian.
  • isRtl was already declared isRtl?: boolean, so undefined was always representable; this is not a new shape.

This change is Reviewable


Devin review

@andrew-polk

Copy link
Copy Markdown
Contributor Author

Consulted Devin on 2026-07-27 21:39 UTC up to commit cc2261e20a5fbf972c8b83f6f8934b842eb52172.

It found no bugs. It raised one item to investigate and three informational observations.

  • Investigate — whether the Sidetic right-to-left pin is dead code. Answered: it isn't. Every script code this change depends on is present in the pinned iso-15924@3.2.0 registry copy, and the passing test is itself the proof. Replied and resolved: #discussion_r3661087137
  • Informational (not mirrored, all three assessed): that variant parent lookup is single-hop and derived from registry names (correct, and verified that no parent-of-a-parent chains exist); that isRtl being absent rather than false is a change to what external consumers see (correct — that is the intent of the change, and how to version it is going to the developer); and that deleting isRtl on the hook path is safe because deepStripDemarcation deep-copies first (correct, and independently confirmed during the local review).

CI (run-tests) passed. No other review bots are configured on this repo.

Claude Opus 5 (1M context)

…-to-right

BL-16593

isRTLScript now returns boolean | undefined, where undefined means the reading
direction is genuinely unknown, and consumers omit isRtl rather than storing a
fabricated false.

Intl.Locale answers either ltr or rtl for every code it accepts, so placeholder
script codes previously got a confident left-to-right answer: Zxxx (unwritten,
165 languages in our data, mostly sign languages), Zyyy (undetermined, 358),
Zzzz (uncoded, 28), the Qaaa-Qabx private use range, and well formed but
unregistered codes. Those now report undefined, so a consumer storing a writing
system's direction can leave an existing setting alone instead of overwriting it
with a guess. Consumers that want a hard boolean can use `script.isRtl ?? false`,
which behaves as before, as does `if (script.isRtl)`.

Script variant relationships are derived from ISO 15924's own names so a variant
inherits its parent's direction (Aran to Arab, Syre/Syrj/Syrn to Syrc, Cyrs to
Cyrl, Latf/Latg to Latn, Hans/Hant to Hani). This fixes Western Neo-Aramaic
(amw-Syrj), which reported left-to-right even though Syriac is right-to-left,
and pins Sidetic (Sidt), which CLDR marks right-to-left but which ICU builds
predating Unicode 16 report as left-to-right.

Verified against CLDR release-48-2 scriptMetadata.txt, whose RTL column is
derived from the Unicode Bidi_Class property: no mismatches across all 179
script codes CLDR gives an explicit verdict for. A regression test pins Todhri
(Todr) as left-to-right, since an earlier draft wrongly forced it right-to-left.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@andrew-polk

Copy link
Copy Markdown
Contributor Author

Consulted Devin on 2026-07-27 21:55 UTC up to commit 9de4456582546870bca7935e7ddca8adbd2aa6fc (the branch was squashed and rebased onto main, so this is a fresh review of the rewritten history).

No bugs. One item to investigate and four informational observations.

  • Investigate — whether the Sidetic right-to-left pin is dead code. Same finding as the previous review; already answered and resolved above, and that thread is still anchored correctly after the rebase since the file content is unchanged. Short version: all 12 script codes this change relies on are present in iso-15924@3.2.0, so the pin is reachable, and the passing test proves it.
  • Informational (not mirrored, all four assessed): variant parent lookup is single-hop and derived from registry names (correct — verified no parent-of-a-parent chains exist); isRtl being absent rather than false changes what external consumers see (correct, and intended — flagged to the developer, and noted on BL-16593 for the Bloom side); the Sidetic pin and the Book Pahlavi mapping depend on the host ICU/Unicode version (correct — that is why the pin exists, and its comment says to delete it once ICU catches up); and deleting isRtl on the hook path is safe because deepStripDemarcation deep-copies first (correct, independently confirmed during the local review).

No other review bots are configured on this repo.

Claude Opus 5 (1M context)

@andrew-polk
andrew-polk marked this pull request as ready for review July 27, 2026 22:20
@hatton
hatton merged commit 06ea4fd into main Jul 27, 2026
1 of 2 checks passed
@hatton
hatton deleted the rtl branch July 27, 2026 23:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants