bb92095ba4
Adds a collapsible Interlinear section beneath the Scripture text (stacked and split layouts) showing each original-language word with its BSB gloss, transliteration, parsing, and Strong's number on hover. Data is generated offline from the Berean Standard Bible translation tables via scripts/build-interlinear.mjs into per-book JSON files.
97 lines
3.8 KiB
JavaScript
97 lines
3.8 KiB
JavaScript
// One-time/offline data prep: convert the BSB Translation Tables (bereanbible.com/bsb_tables.tsv)
|
|
// into per-book JSON files of word-by-word interlinear data for /public/interlinear/.
|
|
//
|
|
// Usage: node scripts/build-interlinear.mjs /path/to/bsb_tables.tsv
|
|
|
|
import fs from 'fs';
|
|
import path from 'path';
|
|
import { parse } from 'csv-parse/sync';
|
|
|
|
const BOOK_ABBREV = {
|
|
Genesis: 'GEN', Exodus: 'EXO', Leviticus: 'LEV', Numbers: 'NUM', Deuteronomy: 'DEU',
|
|
Joshua: 'JOS', Judges: 'JDG', Ruth: 'RUT', '1 Samuel': '1SA', '2 Samuel': '2SA',
|
|
'1 Kings': '1KI', '2 Kings': '2KI', '1 Chronicles': '1CH', '2 Chronicles': '2CH',
|
|
Ezra: 'EZR', Nehemiah: 'NEH', Esther: 'EST', Job: 'JOB', Psalm: 'PSA', Proverbs: 'PRO',
|
|
Ecclesiastes: 'ECC', 'Song of Solomon': 'SNG', Isaiah: 'ISA', Jeremiah: 'JER',
|
|
Lamentations: 'LAM', Ezekiel: 'EZK', Daniel: 'DAN', Hosea: 'HOS', Joel: 'JOL',
|
|
Amos: 'AMO', Obadiah: 'OBA', Jonah: 'JON', Micah: 'MIC', Nahum: 'NAM', Habakkuk: 'HAB',
|
|
Zephaniah: 'ZEP', Haggai: 'HAG', Zechariah: 'ZEC', Malachi: 'MAL',
|
|
Matthew: 'MAT', Mark: 'MRK', Luke: 'LUK', John: 'JHN', Acts: 'ACT', Romans: 'ROM',
|
|
'1 Corinthians': '1CO', '2 Corinthians': '2CO', Galatians: 'GAL', Ephesians: 'EPH',
|
|
Philippians: 'PHP', Colossians: 'COL', '1 Thessalonians': '1TH', '2 Thessalonians': '2TH',
|
|
'1 Timothy': '1TI', '2 Timothy': '2TI', Titus: 'TIT', Philemon: 'PHM', Hebrews: 'HEB',
|
|
James: 'JAS', '1 Peter': '1PE', '2 Peter': '2PE', '1 John': '1JN', '2 John': '2JN',
|
|
'3 John': '3JN', Jude: 'JUD', Revelation: 'REV',
|
|
};
|
|
|
|
const inputPath = process.argv[2];
|
|
if (!inputPath) {
|
|
console.error('Usage: node scripts/build-interlinear.mjs /path/to/bsb_tables.tsv');
|
|
process.exit(1);
|
|
}
|
|
|
|
const outDir = path.resolve('public/interlinear');
|
|
fs.mkdirSync(outDir, { recursive: true });
|
|
|
|
const raw = fs.readFileSync(inputPath, 'utf-8');
|
|
const records = parse(raw, { delimiter: '\t', columns: false, relax_column_count: true });
|
|
const header = records[0];
|
|
const idx = Object.fromEntries(header.map((h, i) => [h.trim(), i]));
|
|
|
|
// books[ABBREV][chapter][verse] = [{ o, t, p, s, g }]
|
|
const books = {};
|
|
let curVerseId = '';
|
|
|
|
for (let i = 1; i < records.length; i++) {
|
|
const row = records[i];
|
|
const verseId = row[idx.VerseId];
|
|
if (verseId) curVerseId = verseId;
|
|
if (!curVerseId) continue;
|
|
|
|
const lang = row[idx.Language];
|
|
const original = (lang === 'Hebrew' || lang === 'Aramaic')
|
|
? row[idx['WLC / Nestle Base TR RP WH NE NA SBL']]
|
|
: row[idx['WLC / Nestle Base TR RP WH NE NA SBL']];
|
|
if (!original || !original.trim()) continue;
|
|
|
|
const strongs = row[idx['Str Heb']] || row[idx['Str Grk']] || '';
|
|
const gloss = (row[idx['BSB version']] || '').replace(/\s+/g, ' ').trim();
|
|
const translit = (row[idx.Translit] || '').trim();
|
|
const parsing = (row[idx.Parsing] || '').trim();
|
|
const sortKey = (lang === 'Hebrew' || lang === 'Aramaic')
|
|
? row[idx['Heb Sort']]
|
|
: row[idx['Greek Sort']];
|
|
|
|
const m = curVerseId.match(/^(.*) (\d+):(\d+)$/);
|
|
if (!m) continue;
|
|
const [, bookName, chapter, verse] = m;
|
|
const abbrev = BOOK_ABBREV[bookName];
|
|
if (!abbrev) continue;
|
|
|
|
books[abbrev] ??= {};
|
|
books[abbrev][chapter] ??= {};
|
|
books[abbrev][chapter][verse] ??= [];
|
|
books[abbrev][chapter][verse].push({
|
|
sort: Number(sortKey) || 0,
|
|
o: original.trim(),
|
|
t: translit,
|
|
p: parsing,
|
|
s: strongs ? `${(lang === 'Hebrew' || lang === 'Aramaic') ? 'H' : 'G'}${strongs}` : '',
|
|
g: gloss,
|
|
});
|
|
}
|
|
|
|
let fileCount = 0;
|
|
for (const [abbrev, chapters] of Object.entries(books)) {
|
|
for (const chapter of Object.values(chapters)) {
|
|
for (const verse of Object.values(chapter)) {
|
|
verse.sort((a, b) => a.sort - b.sort);
|
|
for (const w of verse) delete w.sort;
|
|
}
|
|
}
|
|
fs.writeFileSync(path.join(outDir, `${abbrev}.json`), JSON.stringify(chapters));
|
|
fileCount++;
|
|
}
|
|
|
|
console.log(`Wrote ${fileCount} book files to ${outDir}`);
|