scripture: add a dedicated non-Psalm scripture store, migrate pure-scripture readings
Deploy / deploy (push) Successful in 46s
Deploy / deploy (push) Successful in 46s
Adds src/scripture/ (types.ts, index.ts), a verse-addressable store mirroring
psalter/index.ts's own pattern exactly -- getScriptureVerses(book, chapter,
range) -- for non-Psalm scripture (Wisdom, Canticles, Ezekiel, Acts, Joel,
etc.). Kept as its own module, not folded into the Psalter, per 2026-08
discussion ("the psalms kind of work differently even if the logic is the
same, they do need their own store").
Octave readings can now declare `passages: [{ book, chapter, verses }]`
instead of embedding text -- each entry becomes its own paragraph, letting a
reading built from several verse ranges keep its original paragraph breaks.
getOctaveReading resolves either shape (embedded text or passages) into the
same OctaveReadingText callers already expect, so nothing downstream needs
to know which was used. Scope, per discussion: only readings that are
Scripture start to finish get this treatment -- prose that merely quotes a
verse or two (most of the patristic sermons pulled so far) keeps its
quotations embedded inline, as before.
Migrates the 9 already-committed octave readings that are pure Scripture
(Wisdom 4:7-20, Canticles 1 and 8, seven different Ezekiel excerpts across
All Saints' octave -- one of which, day 7, genuinely spans two chapters --
and Acts 19/Joel 2 for Pentecost's octave) to the new passages notation,
verified to resolve to character-identical text as before.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -8,6 +8,15 @@
|
||||
// broken up the way the sources present it — a project-specific
|
||||
// simplification, not how these are actually prayed historically.
|
||||
import type { LanguageCode, TranslationStatus } from '../psalter/types';
|
||||
import { getScriptureVerses } from '../scripture';
|
||||
|
||||
/** A citation into scripture/index.ts's own store — `verses` e.g. "1-13",
|
||||
* omitted for a whole (authored) chapter. */
|
||||
export interface ScriptureCitation {
|
||||
book: string;
|
||||
chapter: number;
|
||||
verses?: string;
|
||||
}
|
||||
|
||||
export interface OctaveReadingText {
|
||||
id: string;
|
||||
@@ -26,14 +35,64 @@ export interface OctaveReadingText {
|
||||
status: Partial<Record<LanguageCode, TranslationStatus>>;
|
||||
}
|
||||
|
||||
const octaveReadingModules = import.meta.glob<{ default: OctaveReadingText }>(
|
||||
/**
|
||||
* The shape actually authored in data/propers/octave-readings/*.yml: either
|
||||
* `text` directly (patristic prose that isn't scripture itself, embedded
|
||||
* per this project's usual duplication convention — 2026-08 decision: "we
|
||||
* actually want the scripture there in the way those readings cite it" for
|
||||
* quotes *within* prose, so those stay inline) or `passages`, a "pull this
|
||||
* passage" reference into the scripture store for readings that
|
||||
* are scripture start to finish — each entry becomes its own paragraph, in
|
||||
* order, letting a reading built from several verse ranges (e.g. three
|
||||
* consecutive Nocturn-I lessons) keep its original paragraph breaks.
|
||||
* Exactly one of the two is present; `getOctaveReading` always hands
|
||||
* callers a fully-resolved `OctaveReadingText` regardless of which was
|
||||
* used, so nothing downstream needs to know the difference.
|
||||
*/
|
||||
interface OctaveReadingRecord {
|
||||
id: string;
|
||||
source: string;
|
||||
text?: Partial<Record<LanguageCode, string>>;
|
||||
passages?: ScriptureCitation[];
|
||||
responsory?: Partial<Record<LanguageCode, string>>;
|
||||
status: Partial<Record<LanguageCode, TranslationStatus>>;
|
||||
}
|
||||
|
||||
function resolvePassages(passages: ScriptureCitation[]): Partial<Record<LanguageCode, string>> {
|
||||
const laParagraphs: string[] = [];
|
||||
const enParagraphs: string[] = [];
|
||||
for (const citation of passages) {
|
||||
const verses = getScriptureVerses(citation.book, citation.chapter, citation.verses);
|
||||
const la = verses
|
||||
.map((v) => (v.text.la ? `${v.n} ${v.text.la}` : undefined))
|
||||
.filter((s): s is string => !!s)
|
||||
.join(' ');
|
||||
const en = verses
|
||||
.map((v) => (v.text.en ? `${v.n} ${v.text.en}` : undefined))
|
||||
.filter((s): s is string => !!s)
|
||||
.join(' ');
|
||||
if (la) laParagraphs.push(la);
|
||||
if (en) enParagraphs.push(en);
|
||||
}
|
||||
const result: Partial<Record<LanguageCode, string>> = {};
|
||||
if (laParagraphs.length) result.la = laParagraphs.join('\n\n');
|
||||
if (enParagraphs.length) result.en = enParagraphs.join('\n\n');
|
||||
return result;
|
||||
}
|
||||
|
||||
function resolveReading(record: OctaveReadingRecord): OctaveReadingText {
|
||||
const { passages, text, ...rest } = record;
|
||||
return { ...rest, text: text ?? (passages ? resolvePassages(passages) : {}) };
|
||||
}
|
||||
|
||||
const octaveReadingModules = import.meta.glob<{ default: OctaveReadingRecord }>(
|
||||
'../data/propers/octave-readings/*.yml',
|
||||
{ eager: true },
|
||||
);
|
||||
|
||||
const octaveReadingsById = new Map<string, OctaveReadingText>();
|
||||
for (const mod of Object.values(octaveReadingModules)) {
|
||||
octaveReadingsById.set(mod.default.id, mod.default);
|
||||
octaveReadingsById.set(mod.default.id, resolveReading(mod.default));
|
||||
}
|
||||
|
||||
/** Undefined, not a "missing" placeholder, when this octave day has no
|
||||
|
||||
Reference in New Issue
Block a user