b52b202909
The heading above each Matins octave reading was a bare Latin string (e.g. "Sermo sancti Bernárdi Abbátis") rendered as-is in both the Latin and English columns, since the app shows both languages side by side rather than toggling. Widen OctaveReadingText.source and the lesson part's label to carry per-language text, and render it as its own lang-columns row like the body text beneath it. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L3wxvz3mPXiHxkPB5JpnmD
111 lines
5.2 KiB
TypeScript
111 lines
5.2 KiB
TypeScript
// Matins readings for octave days — a separate small store from
|
|
// common/temporal propers because these are keyed by
|
|
// `${octaveId}-octave-day-${dayNumber}` (day 1 = the feast's own day, not
|
|
// normally authored here since that day already has its own real content
|
|
// elsewhere) rather than a flat id, and because unlike a collect, a
|
|
// day's worth of historical lessons (usually 3, sometimes 9) is
|
|
// deliberately collapsed into one continuous reading here rather than kept
|
|
// 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;
|
|
/** Attribution for the reading — author and work, e.g. "St. John
|
|
* Damascene, 2nd Sermon on the Dormition of the Mother of God". Bilingual
|
|
* like everything else here since it's rendered as the reading's own
|
|
* heading in both language columns (2026-08-21 fix: it used to be a bare
|
|
* Latin incipit shown verbatim in the English column too). */
|
|
source: Partial<Record<LanguageCode, string>>;
|
|
text: Partial<Record<LanguageCode, string>>;
|
|
/** The first responsory following the reading in the source, where one
|
|
* was found — which one to use when several lessons (and several
|
|
* responsories) collapse into a single reading is an open question
|
|
* (2026-08 discussion); "the first one" is the working answer. Omitting
|
|
* the closing Gloria Patri outside Septuagesima-Holy Saturday is a
|
|
* rendering concern, not captured here. */
|
|
responsory?: Partial<Record<LanguageCode, string>>;
|
|
status: Partial<Record<LanguageCode, TranslationStatus>>;
|
|
}
|
|
|
|
/**
|
|
* 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: Partial<Record<LanguageCode, string>>;
|
|
text?: Partial<Record<LanguageCode, string>>;
|
|
passages?: ScriptureCitation[];
|
|
responsory?: Partial<Record<LanguageCode, string>>;
|
|
status: Partial<Record<LanguageCode, TranslationStatus>>;
|
|
}
|
|
|
|
/** Exported for reuse by src/propers/bible-plan.ts and
|
|
* src/propers/nocturn-readings.ts, which need the same "pull several
|
|
* scripture passages and join them as paragraphs" behavior for Matins
|
|
* readings outside the octave-day-keyed store this file owns. */
|
|
export 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, resolveReading(mod.default));
|
|
}
|
|
|
|
/** Undefined, not a "missing" placeholder, when this octave day has no
|
|
* reading authored — normal and expected (not every day has one), unlike
|
|
* getCommonProper/getTemporalProper's ids, which are always expected to
|
|
* resolve to *something* eventually. */
|
|
export function getOctaveReading(octaveId: string, dayNumber: number): OctaveReadingText | undefined {
|
|
return octaveReadingsById.get(`${octaveId}-octave-day-${dayNumber}`);
|
|
}
|