Skip to content

Add LXX2012+WEB Bible translation with UI/API selection - #156

Merged
brianglass merged 15 commits into
mainfrom
expose-translation-selection
Jul 31, 2026
Merged

Add LXX2012+WEB Bible translation with UI/API selection#156
brianglass merged 15 commits into
mainfrom
expose-translation-selection

Conversation

@brianglass

Copy link
Copy Markdown
Owner

Summary

  • Adds a second English Bible translation, pairing the public-domain LXX2012 (Septuagint, modernized) Old Testament with the public-domain WEB New Testament — both sourced from the same eBible.org/Michael Paul Johnson project.
  • Exposes translation selection end-to-end: a <select> dropdown on the readings page (English only, since Romanian/Serbian each still have one translation), a /readings/<tradition>/<cal>/<translation>/... URL segment remembered in session, and an optional ?translation= query parameter on the four Day-returning API endpoints (kept as a query param rather than a new required path segment, to avoid doubling the tradition route matrix).
  • Fixes two real USFX-parsing bugs surfaced by the new data: verse bridges (<v id="1-2">) crashing the integer verse field, and <x> cross-reference elements (e.g. "11:33 Daniel 6:22-23") leaking directly into verse content.
  • Retags 3 Reading rows (Aug 1 Martyrs, Oct 6 St Innocent, Oct 9 St Tikhon) from common to slavic, fixing cases where the Greek tradition view showed a duplicate/leftover Slavic-specific reading alongside its own correct one — verified against antiochian.org's own commemoration data date-by-date rather than by heuristic.
  • Adds a "New" status badge (replacing the old "(beta)" note on the Greek tradition option, which gets the same badge) so the new selector is discoverable.
  • Documents the new translation and its shared lineage with the WEB/EOB project on the about page.

Test plan

  • Full test suite passes (127 tests)
  • Manually verified via dev server: translation dropdown appears for English, hidden for Romanian/Serbian; switching translation via URL segment and API query param both work; OpenAPI docs show the new param with no duplicate operationIds
  • Manually verified the Aug 1 / Oct 6 / Oct 9 Reading fixes render correctly for both Slavic and Greek tradition, with no regressions to the unaffected date

🤖 Generated with Claude Code

https://claude.ai/code/session_01Hf6j2xXQXywHVh3HAVRxB3

Both texts are public domain and already in USFX format, so no new
parser is needed. Adds a translation field to Verse (existing rows
backfilled to kjv/rccv/srp1865 per language) and loads the new pairing
via a data migration, matching the existing migration-based ingestion
mechanism used for KJV/Romanian/Serbian.

Also fixes two parse.py/model bugs surfaced by this data:
- USFX verse bridges (e.g. id="1-2") crashed the integer verse field;
  now stored under the first verse number.
- A footnote's closing tag unconditionally set is_valid_content=True,
  corrupting the previous verse's content when a footnote appeared
  inside a Psalm title (<d>) block, which precedes the first <v> of
  the chapter.
Wires up the translation field added in the previous commit: a
<select> dropdown on the readings page (English only, since Romanian
and Serbian each still have exactly one translation), a matching
/readings/<tradition>/<cal>/<translation>/... URL segment remembered
in session like the existing cal/tradition axes, and an optional
?translation= query parameter on the four Day-returning API endpoints
(kept as a query param rather than a new required path segment, to
avoid doubling the already-large tradition route matrix).

Also fixes the readings page's "Scripture Readings (KJV)" heading,
previously hardcoded regardless of language, to reflect whichever
translation is actually being displayed.
… label

Moves the dropdown out of the cal/tradition nav block and into the
Scripture Readings section it actually controls, gives it page-matching
styling instead of default browser chrome, and shortens the LXX2012+WEB
label so the heading no longer nests parens inside parens.
The select box already indicates the chosen translation for English
readers, so the parenthetical is only shown for Romanian/Serbian, which
have no dropdown. Also bumps the picker's text/select size and darkens
its color slightly for better legibility.
WEB annotates verses with <x> cross-reference elements (e.g. pointing
Heb 11:33 back to Daniel 6:22-23) that aren't part of the verse text.
parse.py already suppressed <f> footnote content the same way; <x> was
missed, so its text (e.g. "11:33 Daniel 6:22-23") was leaking directly
into the stored verse content. KJV/Romanian/Serbian never use <x>, so
this only affected the new lxx2012-web data -- reloaded via migration
0004 (rolled back to 0003 and reapplied) to pick up the fix.
These two OCA-specific saints aren't in antiochian.org's own
commemoration list for these dates (confirmed via
data/antiochian_fixed_saints.json), so their readings were leaking
into the Greek view alongside the correct Greek-specific reading
(Thomas the Apostle / James, son of Alphaeus), which shared the same
Reading.ordering value by coincidence.

Note: this was originally a 12-date, 23-row fix, but 10 of those dates
turned out to be cases where Slavic and Greek traditions genuinely
share the same saint (confirmed against the same source file) -- the
shared ordering there was coincidental, not a signal that one reading
should replace the other. Reverted those 10 after cross-checking;
only Oct 6 and Oct 9 checked out as real Slavic-exclusive divergences.
OCA's own feast_name for Aug 1 ("Procession of the Lifegiving Cross; 7
Maccabean Martyrs") confirms the 'Martyrs' Reading rows (Heb 11.33-12.2
/ Matt 10.32-36) are OCA's own version of the Maccabees reading, not a
reference to Elesa or any other commemoration -- Antiochian's own
commemoration list for the date doesn't mention Elesa either. Since
Greek tradition already has its own version of this exact commemoration
(desc='Holy Maccabee Children', same Epistle, different Gospel), the
Slavic-labeled row was leaking into the Greek view as an apparent
duplicate Hebrews reading. Retagged to slavic so each tradition shows
only its own version, matching the same divergence pattern already
used for Oct 6/9 (St Innocent/St Tikhon).
Explains the new selectable translation alongside the existing KJV
description, tying it back to the pre-existing note that KJV's
Masoretic versification can make a handful of OT readings incorrect --
LXX2012, being a direct Septuagint translation, follows the same
versification and OT content (including the anagignoskomena) the
lectionary is actually based on.
Explains why WEB was chosen (Majority Text based, an update of the
1901 ASV, avoids interpretive biases of translations like the NIV),
notes that LXX2012 and WEB are both from Michael Paul Johnson's
public-domain modernization work via eBible.org, and switches both
translation links to their Wikipedia articles.
Notes that the Eastern Orthodox Bible (EOB) project began as a
revision of the WEB, and links eBible.org directly.
Mirrors the existing beta-note pattern used for the Greek tradition
toggle, so first-time visitors notice the newly-added translation
option rather than skipping past it.
Replaces the old italic "(beta)" note with the same red status-badge
treatment used for the new translation selector, unifying the visual
language for "this is a recent addition" across both. Renames
new-badge to the more general status-badge since it's now used for
both labels.

Both badge placements needed flexbox centering (inline-flex +
align-items: center) rather than plain vertical-align, since the
select box and the badge/text don't share a baseline reliably by
default -- vertical-align alone left the badges visibly low relative
to their sibling text. Applying flex to both the Slavic and Greek
tradition labels (not just Greek) was necessary too, since inline-flex
establishes its own baseline calculation that differs from a plain
inline label, and doing it asymmetrically threw the two labels out of
alignment with each other.
Every push to a non-main branch already triggers the push-event
workflow; once that branch has an open PR, the same commit also
triggers the pull_request-event workflow, running the full test suite
twice for one push. Adds a concurrency group keyed on the branch name
(github.head_ref for pull_request events, github.ref_name for push
events -- these must be the short name, not github.ref's refs/heads/
prefixed form, or the two event types never share a group) so the
older of the two redundant runs gets cancelled instead of both running
to completion.
The concurrency group (previous commit) reduced wasted CI time but
didn't fix the actual problem: branch protection requires a "test"
status check, and both the push- and pull_request-triggered runs
report under that same context for the same commit. A cancelled push
run could still show as a non-passing required check and block
merging, even when the pull_request run succeeded.

Restricting push to branches: [main] means only one event type ever
fires per commit once a PR exists (pull_request already re-triggers on
every push to the PR branch). Trade-off: a branch with no open PR gets
no CI run until a PR is opened for it.
@brianglass
brianglass merged commit 2666cdd into main Jul 31, 2026
4 checks passed
@brianglass brianglass linked an issue Jul 31, 2026 that may be closed by this pull request
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.

Add WEB translation

1 participant