Problem: BSB and Statenvertaling are hardcoded as verses.text and verses.text_nl columns. Adding a third, fourth, or fifth translation by adding more columns to the verses table would be unsustainable. BibleWeb needs a scalable system for adding new Bible translations without schema changes.
Solution: A normalized verse_texts table that stores translation text keyed by (translation_id, book_id, chapter, verse), paired with a bible_translations registry. Any new translation can be added by inserting a row into the registry and populating verse_texts — no code changes needed.
Not included: A UI for importing or managing translations (admin panel). Currently, adding a translation requires running a script and rebuilding the FTS5 index.
The system allows multiple Bible translations to coexist. Users can select their preferred translation from a dropdown in the Settings page. The reader, search, and all text-displaying features automatically use the selected translation.
User flow:
How a new translation would be added (developer flow):
bible_translations (code, name, language)verse_texts with all verse texts for that translationEdge cases:
verse_texts table for performance — they read directly from verses.text / verses.text_nl?translation=X > localStorage > translation cookie > default 'BSB'Settings dropdown:
<select> element/api/translations on page loadNo other UI currently — the infrastructure is in place but there's no admin interface for adding translations.
Database schema (packages/db/src/schema/bible.ts):
bible_translations table: id (PK), code (UNIQUE, e.g., 'BSB'), name, language, isDefault (boolean)verse_texts table: composite PK (translation_id, book_id, chapter, verse), text (NOT NULL)verse_texts_fts: FTS5 virtual table with porter and unicode61 tokenizers — indexes text from verse_texts for full-text search across all translationsQuery layer (apps/web/src/lib/server/queries/verses.ts):
getTranslations() — fetches all rows from bible_translations (cached in memory)getVersesByChapter(bookId, chapter, translationCode):verses rows directly (performance short-circuit)verse_texts, builds a Map, overlays text onto verses rows (preserving IDs and metadata)API (apps/web/src/routes/api/translations/+server.ts):
GET /api/translations — returns translation list; falls back to hardcoded [BSB, SV] if table is emptyCache-Control: public, max-age=86400Settings persistence (apps/web/src/lib/stores/settings.svelte.ts):
setTranslation(code) writes to settings state, localStorage, and a translation cookie (1-year expiry, SameSite=Lax)Migration history:
migrate-phase2-6.ts created the initial tablesmigrate-verse-texts.ts rebuilt with coordinate-based schema (replacing earlier verse_id FK design) and rebuilt FTS5 indexCurrent data: BSB (id=1) and SV (id=2) are seeded. Adding a 3rd translation requires: INSERT INTO bible_translations, populate verse_texts, rebuild FTS5.
Files:
packages/db/src/schema/bible.ts — bibleTranslations, verseTexts table definitionsapps/web/src/lib/server/queries/verses.ts — getTranslations(), external translation lookupapps/web/src/routes/api/translations/+server.ts — GET endpointapps/web/src/lib/stores/settings.svelte.ts — setTranslation(), cookie/localStorageapps/web/src/routes/(app)/settings/+page.svelte — translation dropdown UIscripts/migrate-verse-texts.ts — migration that built current schemascripts/migrate-phase2-6.ts — initial tables migrationCurrent: IN_PROGRESS Milestone: Foundation (pre-v1) Priority: Medium — infrastructure is complete, but no 3rd translation has been added yet
What's done:
What remains:
History:
verse_id foreign key in verse_texts — replaced with coordinate-based PK (translation_id, book_id, chapter, verse) for simpler importsverses.text / verses.text_nl — the verse_texts table was added later for scalability and FTS5 supportDependencies: