Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 949196b847 | |||
| 74ddd61aee | |||
| f3cf30c1c7 | |||
| b163f502b8 | |||
| 7ab517cbe3 | |||
| a3a9d568ed |
@@ -0,0 +1,27 @@
|
||||
# Verified against Divinum Officium (Tridentine 1906/1910 and Divino
|
||||
# Afflatu 1954, `[Ant Vespera]`), Latin/Sancti/08-29.txt +
|
||||
# English/Sancti/08-29.txt, 2026-08-29 — Lauds reuses the Vespers set
|
||||
# verbatim (`[Ant Laudes]` -> `@:Ant Vespera`).
|
||||
id: decollation-of-st-john-baptist
|
||||
groups:
|
||||
- psalms: [92]
|
||||
antiphon:
|
||||
la: "Heródes enim ténuit * et ligávit Joánnem et pósuit in cárcerem propter Herodíadem."
|
||||
en: "For Herod had laid hold upon John, * and bound him, and put him in prison, for Herodias' sake."
|
||||
- psalms: [99]
|
||||
antiphon:
|
||||
la: "Dómine mi rex, * da mihi in disco caput Joánnis Baptístæ."
|
||||
en: "O Lord my King, * give me in a charger the head of John the Baptist."
|
||||
- psalms: [62]
|
||||
antiphon:
|
||||
la: "Puéllæ saltánti * imperávit mater: Nihil áliud petas, nisi caput Joánnis."
|
||||
en: "The damsel danced, * and her mother charged her, saying: See thou ask nothing, but only the head of John."
|
||||
canticle:
|
||||
id: canticum-trium-puerorum
|
||||
antiphon:
|
||||
la: "Arguébat * Heródem Joánnes propter Herodíadem, quam túlerat fratri suo Philíppo uxórem."
|
||||
en: "John had rebuked Herod for Herodias' sake, * his brother Philip's wife, for he had taken her."
|
||||
laudate:
|
||||
antiphon:
|
||||
la: "Da mihi in disco * caput Joánnis Baptístæ. Et contristátus est rex propter jusjurándum."
|
||||
en: "Give me in a charger the head of John the Baptist. * And the King was sorry, for his oath's sake."
|
||||
@@ -0,0 +1,29 @@
|
||||
# Verified against Divinum Officium (Tridentine 1906/1910 and Divino
|
||||
# Afflatu 1954, `[Ant Vespera]`), Latin/Sancti/08-29.txt +
|
||||
# English/Sancti/08-29.txt, 2026-08-29. The reference's own `[Rule]`
|
||||
# ("Psalm5 Vespera=116") is a secular five-psalm-Vespers detail that
|
||||
# doesn't apply to vu's Monastic-1617-structured (four-psalm) Vespers —
|
||||
# same 109/111/112/115 baseline as common-of-a-martyr.yml's own Vespers
|
||||
# psalmody, with this feast's own first four proper antiphons (of its
|
||||
# five, `[Ant Vespera]` 1-4) substituted in; the fifth ("Da mihi in
|
||||
# disco...") has no corresponding slot here and is used only at Lauds'
|
||||
# Laudate psalm instead (see lauds-psalmody-overrides/decollation-of-st-
|
||||
# john-baptist.yml).
|
||||
id: decollation-of-st-john-baptist
|
||||
groups:
|
||||
- psalms: [109]
|
||||
antiphon:
|
||||
la: "Heródes enim ténuit * et ligávit Joánnem et pósuit in cárcerem propter Herodíadem."
|
||||
en: "For Herod had laid hold upon John, * and bound him, and put him in prison, for Herodias' sake."
|
||||
- psalms: [111]
|
||||
antiphon:
|
||||
la: "Dómine mi rex, * da mihi in disco caput Joánnis Baptístæ."
|
||||
en: "O Lord my King, * give me in a charger the head of John the Baptist."
|
||||
- psalms: [112]
|
||||
antiphon:
|
||||
la: "Puéllæ saltánti * imperávit mater: Nihil áliud petas, nisi caput Joánnis."
|
||||
en: "The damsel danced, * and her mother charged her, saying: See thou ask nothing, but only the head of John."
|
||||
- psalms: [115]
|
||||
antiphon:
|
||||
la: "Arguébat * Heródem Joánnes propter Herodíadem, quam túlerat fratri suo Philíppo uxórem."
|
||||
en: "John had rebuked Herod for Herodias' sake, * his brother Philip's wife, for he had taken her."
|
||||
+33
-16
@@ -4,7 +4,7 @@ import { resolveDay } from '../calendar';
|
||||
import { getDayLabel } from '../calendar/day-label';
|
||||
import { getPsalmVerses } from '../psalter';
|
||||
import { getCanticle } from './lauds-canticles';
|
||||
import { getLaudsPsalmodyOverride, type LaudsPsalmodyOverride } from './lauds-psalmody-overrides';
|
||||
import { getLaudsSaintOverride, getLaudsCommonOverride, type LaudsPsalmodyOverride } from './lauds-psalmody-overrides';
|
||||
import { getOpeningVersicleId } from './opening-versicle';
|
||||
import { getMarianAntiphonId, getMarianAntiphonLabel } from './marian-antiphon';
|
||||
import { applyFlexaMark } from './antiphon';
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
seasonalOfficeSuffix,
|
||||
isFerialOrVigil,
|
||||
getOfficeOverrideId,
|
||||
getPsalmodyProperOverrideId,
|
||||
resolveResponsory,
|
||||
resolveOfficeBundle,
|
||||
resolveSuffrages,
|
||||
@@ -49,24 +50,40 @@ interface PsalmodyBlock {
|
||||
const laudsAntiphons = laudsAntiphonsData as Record<Weekday, PsalmodyBlock>;
|
||||
|
||||
/**
|
||||
* A duplex-majus+ saint's own proper psalmody (per-feast, not per-Common —
|
||||
* explicit choice), or one of the named temporal feasts eligible via
|
||||
* resolve-common.ts's getOfficeOverrideId. Falls back to `undefined` — the
|
||||
* plain weekday default — for an eligible feast with no override authored
|
||||
* yet, same honest incremental-content convention as everywhere else in
|
||||
* this codebase; it's not gated behind whether content exists, just
|
||||
* behind whether it's *eligible* to override at all.
|
||||
* Two independent tiers, tried in order:
|
||||
*
|
||||
* getOfficeOverrideId itself checks resolveOfficeWinner(day), not the raw
|
||||
* `day.winner` — on an octave day this is the octave's own feast (e.g. St.
|
||||
* Lawrence, days 2-8 of his own octave), which is exactly what should
|
||||
* supply the psalmody override there too, live-verified alongside the
|
||||
* collect/Benedictus-antiphon fix (see resolve-common.ts's
|
||||
* 1. **Proper** — this feast's own authored antiphons (per-feast, not
|
||||
* per-Common — explicit choice), eligible at *any rank* via
|
||||
* resolve-common.ts's getPsalmodyProperOverrideId. A saint's own real
|
||||
* proper text is never gated behind rank — see that function's own
|
||||
* doc comment for why this used to be wrongly bundled with tier 2's
|
||||
* gate (fixed 2026-08-29).
|
||||
* 2. **Common category** — a generic-by-category substitute, still
|
||||
* gated at duplex-majus+ via getOfficeOverrideId (a real, distinct,
|
||||
* weaker tier: same conventional psalm numbers, not this feast's own
|
||||
* text).
|
||||
*
|
||||
* Falls back to `undefined` — the plain weekday default — for an eligible
|
||||
* feast with no override authored yet in either tier, same honest
|
||||
* incremental-content convention as everywhere else in this codebase;
|
||||
* it's not gated behind whether content exists, just behind whether it's
|
||||
* *eligible* to override at all.
|
||||
*
|
||||
* Both id-eligibility functions check resolveOfficeWinner(day), not the
|
||||
* raw `day.winner` — on an octave day this is the octave's own feast
|
||||
* (e.g. St. Lawrence, days 2-8 of his own octave), which is exactly what
|
||||
* should supply the psalmody override there too, live-verified alongside
|
||||
* the collect/Benedictus-antiphon fix (see resolve-common.ts's
|
||||
* resolveOfficeWinner doc comment).
|
||||
*/
|
||||
function getPsalmodyOverrideFor(day: LiturgicalDay): LaudsPsalmodyOverride | undefined {
|
||||
const id = getOfficeOverrideId(day);
|
||||
return id ? getLaudsPsalmodyOverride(id) : undefined;
|
||||
const properId = getPsalmodyProperOverrideId(day);
|
||||
const proper = properId ? getLaudsSaintOverride(properId) : undefined;
|
||||
if (proper) {
|
||||
return proper;
|
||||
}
|
||||
const categoryId = getOfficeOverrideId(day);
|
||||
return categoryId ? getLaudsCommonOverride(categoryId) : undefined;
|
||||
}
|
||||
|
||||
// Mon-Fri share one capitulum verbatim (confirmed against the live engine
|
||||
@@ -128,7 +145,7 @@ function canticleText(canticleId: string, slice?: [number, number]): ResolvedTex
|
||||
}
|
||||
status[lang] = worst;
|
||||
}
|
||||
return { text, status };
|
||||
return { text, status, citation: { la: canticle.citation, en: canticle.citation } };
|
||||
}
|
||||
|
||||
/** Ps 66 through the Laudate psalms — see hours/types.ts's 'lauds-psalmody'
|
||||
|
||||
@@ -7,6 +7,7 @@ import { resolveActiveOctave, activeOctavesFor, octaveGoverningPrivilegedDay, is
|
||||
import { getTemporalFeastRecord } from '../calendar/temporal-feasts';
|
||||
import { isInTriduum } from '../calendar/temporal';
|
||||
import { splitAntiphon, isDoubleOrHigher } from './antiphon';
|
||||
import { getLaudsSaintOverride } from './lauds-psalmody-overrides';
|
||||
import vespersMagnificatAntiphonsData from '../data/hours/vespers-magnificat-antiphons.yml';
|
||||
|
||||
type BilingualText = Partial<Record<string, string>>;
|
||||
@@ -180,6 +181,34 @@ export function getOfficeOverrideId(day: LiturgicalDay): string | undefined {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* The id to look up for today's winner's own *proper* Lauds/Vespers
|
||||
* psalmody antiphons (`hours/lauds.ts`'s and `hours/vespers.ts`'s own
|
||||
* `getPsalmodyOverrideFor`) — eligible at **any rank**, unlike
|
||||
* `getOfficeOverrideId` above. A saint's own authored proper antiphon
|
||||
* text isn't something rank should ever gate — rank only has standing to
|
||||
* gate the weaker, generic Common-category substitute (still looked up
|
||||
* via `getOfficeOverrideId`'s duplex-majus+ threshold, a real distinct
|
||||
* tier: same conventional psalm numbers, a generic-by-category antiphon,
|
||||
* not this feast's own text). Mirrors `getMinorHourOverrideId`'s identical
|
||||
* "any sanctoral winner is eligible, it's the caller that decides whether
|
||||
* anything's actually authored for the id" reasoning — the psalmody
|
||||
* override table previously reused `getOfficeOverrideId`'s single
|
||||
* duplex-majus+ gate for both its proper and Common tiers at once, which
|
||||
* silently discarded a proper antiphon whenever it happened to be
|
||||
* authored for a sub-duplex-majus feast (fixed 2026-08-29).
|
||||
*/
|
||||
export function getPsalmodyProperOverrideId(day: LiturgicalDay): string | undefined {
|
||||
const winner = resolveOfficeWinner(day);
|
||||
if (winner.kind === 'temporal' && ALWAYS_OVERRIDE_TEMPORAL_IDS.has(winner.id)) {
|
||||
return winner.id;
|
||||
}
|
||||
if (winner.kind === 'sanctoral') {
|
||||
return winner.id;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* The id to look up for today's office winner's (resolveOfficeWinner)
|
||||
* minor-hour content (antiphon + chapter) — `${hourId}-antiphon-${id}`/
|
||||
@@ -328,19 +357,52 @@ function withPaschaltideAlleluia(resolved: ResolvedText, day: LiturgicalDay): Re
|
||||
return appendPaschaltideAlleluia(resolved);
|
||||
}
|
||||
|
||||
// The reference engine's own `getanthoras()` (specials.pl): when a saint
|
||||
// has no dedicated per-hour minor-hour antiphon of its own, one is
|
||||
// derived from that saint's 5-antiphon Lauds/Vespers set (`[Ant Laudes]`,
|
||||
// which for most saints is `@:Ant Vespera`) by fixed index — Prime takes
|
||||
// the first (the same antiphon as the first psalm-group), Terce/Sext the
|
||||
// second/third, None the fifth (the Laudate antiphon); the fourth
|
||||
// (canticle) antiphon is never used for a minor hour. vu's own
|
||||
// `LaudsPsalmodyOverride` already stores that same 5-antiphon set in that
|
||||
// exact order (`groups[0..2]`, `canticle`, `laudate`), so once a saint's
|
||||
// Lauds proper is authored, no separate per-hour file is needed for this
|
||||
// tier — same "mechanism first, content incrementally" pattern as
|
||||
// everywhere else in this codebase.
|
||||
const MINOR_HOUR_LAUDS_ANTIPHON_INDEX: Record<string, number> = { prime: 0, terce: 1, sext: 2, none: 4 };
|
||||
|
||||
function deriveMinorHourAntiphonFromLauds(hourId: string, overrideId: string): ResolvedText | undefined {
|
||||
const index = MINOR_HOUR_LAUDS_ANTIPHON_INDEX[hourId];
|
||||
if (index === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
const lauds = getLaudsSaintOverride(overrideId);
|
||||
if (!lauds) {
|
||||
return undefined;
|
||||
}
|
||||
const antiphon = index === 4 ? lauds.laudate.antiphon : lauds.groups[index]?.antiphon;
|
||||
if (!antiphon) {
|
||||
return undefined;
|
||||
}
|
||||
return verifiedText(antiphon);
|
||||
}
|
||||
|
||||
/** A Little Hour's (or Prime's) plain per-weekday antiphon, overridden by
|
||||
* a duplex-majus+ feast's own proper (`${hourId}-antiphon-${id}`) when
|
||||
* authored, then by that saint's shared Common (`${hourId}-antiphon-
|
||||
* authored, then by that saint's own Lauds/Vespers antiphon set (see
|
||||
* `deriveMinorHourAntiphonFromLauds` above — a real liturgical derivation,
|
||||
* not a guess), then by that saint's shared Common (`${hourId}-antiphon-
|
||||
* ${minorHoursCommon}`, see SaintRecord's own doc comment) — tried first
|
||||
* in its Paschaltide-variant form (`${minorHoursCommon}-paschaltide`)
|
||||
* when the day falls in Paschaltide and that variant has been authored,
|
||||
* per PASCHALTIDE_SEASONS's own doc comment — when a proper one hasn't
|
||||
* been authored — same honest "not gated behind whether content exists,
|
||||
* just eligible to override at all" fallback as every other override in
|
||||
* this codebase: an eligible feast with none of these authored yet just
|
||||
* falls through to the plain weekday default silently. The plain (non-
|
||||
* `-paschaltide`) Common fallback itself still picks up a seasonal change
|
||||
* in Paschaltide — see `withPaschaltideAlleluia`.
|
||||
* per PASCHALTIDE_SEASONS's own doc comment — when neither of the first
|
||||
* two has been authored — same honest "not gated behind whether content
|
||||
* exists, just eligible to override at all" fallback as every other
|
||||
* override in this codebase: an eligible feast with none of these
|
||||
* authored yet just falls through to the plain weekday default silently.
|
||||
* The plain (non-`-paschaltide`) Common fallback itself still picks up a
|
||||
* seasonal change in Paschaltide — see `withPaschaltideAlleluia`. (The
|
||||
* derived-from-Lauds tier does not get that treatment — see TODO.md.)
|
||||
*/
|
||||
export function resolveMinorHourAntiphon(
|
||||
hourId: string,
|
||||
@@ -353,6 +415,10 @@ export function resolveMinorHourAntiphon(
|
||||
if (proper.status.la !== 'missing' || proper.status.en !== 'missing') {
|
||||
return proper;
|
||||
}
|
||||
const derived = deriveMinorHourAntiphonFromLauds(hourId, overrideId);
|
||||
if (derived) {
|
||||
return derived;
|
||||
}
|
||||
const commonId = getSaintRecord(overrideId)?.minorHoursCommon;
|
||||
if (commonId) {
|
||||
if (PASCHALTIDE_SEASONS.has(day.season)) {
|
||||
@@ -542,7 +608,16 @@ function sanctoralCommemorationPart(commemoration: Extract<Commemoration, { kind
|
||||
if (combined.status.la !== 'missing' || combined.status.en !== 'missing') {
|
||||
return { kind: 'preces', text: combined, label };
|
||||
}
|
||||
return { kind: 'preces', text: resolveCommon(`${saint.propers}-collect`), label };
|
||||
const properCollect = resolveCommon(`${saint.propers}-collect`);
|
||||
if (properCollect.status.la !== 'missing' || properCollect.status.en !== 'missing') {
|
||||
return { kind: 'preces', text: properCollect, label };
|
||||
}
|
||||
}
|
||||
if (saint?.collectCommon && saint.collectName) {
|
||||
const template = resolveCommon(saint.collectCommon);
|
||||
if (template.status.la !== 'missing' || template.status.en !== 'missing') {
|
||||
return { kind: 'preces', text: substituteName(template, saint.collectName), label };
|
||||
}
|
||||
}
|
||||
return { kind: 'preces', text: { text: {}, status: { la: 'missing', en: 'missing' } }, label };
|
||||
}
|
||||
|
||||
+26
-14
@@ -5,7 +5,7 @@ import { weekdayOf } from '../calendar/weekday';
|
||||
import { getDayLabel } from '../calendar/day-label';
|
||||
import { getPsalmVerses } from '../psalter';
|
||||
import { getOpeningVersicleId } from './opening-versicle';
|
||||
import { getVespersPsalmodyOverride } from './vespers-psalmody-overrides';
|
||||
import { getVespersSaintOverride, getVespersCommonOverride } from './vespers-psalmody-overrides';
|
||||
import { applyFlexaMark } from './antiphon';
|
||||
import {
|
||||
resolveCommon,
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
seasonalOfficeSuffix,
|
||||
isFerialOrVigil,
|
||||
getOfficeOverrideId,
|
||||
getPsalmodyProperOverrideId,
|
||||
resolveResponsory,
|
||||
resolveOfficeBundle,
|
||||
resolveSuffrages,
|
||||
@@ -70,20 +71,31 @@ function psalmParts(group: VespersGroup, day: LiturgicalDay): ResolvedPart[] {
|
||||
return parts;
|
||||
}
|
||||
|
||||
/** A duplex-majus+ saint's own proper Vespers psalmody (none authored
|
||||
* yet), or their Common's (live-verified 2026-08-28, see
|
||||
* hours/vespers-psalmody-overrides.ts's own doc comment for why this
|
||||
* mechanism is genuinely per-Common — unlike Lauds, the psalm *numbers*
|
||||
* themselves differ by Common here, not just the antiphons). Eligibility
|
||||
* follows the evening's governing `day` (same getOfficeOverrideId test
|
||||
* as the chapter/responsory/hymn/versicle bundle below) — a First
|
||||
* Vespers of a majus+ feast should get that feast's own psalms, same as
|
||||
* it gets that feast's own office. Falls back to `undefined` for an
|
||||
* eligible feast with no override authored yet, same honest incremental-
|
||||
* content convention as everywhere else. */
|
||||
/** Two independent tiers, tried in order — same split as hours/lauds.ts's
|
||||
* own getPsalmodyOverrideFor, see its doc comment for the full rationale:
|
||||
*
|
||||
* 1. **Proper** — this feast's own authored psalmody, eligible at *any
|
||||
* rank* via resolve-common.ts's getPsalmodyProperOverrideId (a saint's
|
||||
* own real proper text is never gated behind rank).
|
||||
* 2. **Common category** — per hours/vespers-psalmody-overrides.ts's own
|
||||
* doc comment, genuinely per-Common here (the psalm *numbers*
|
||||
* themselves differ by Common, not just the antiphons, unlike Lauds)
|
||||
* — still gated at duplex-majus+ via getOfficeOverrideId.
|
||||
*
|
||||
* Eligibility follows the evening's governing `day` (same
|
||||
* resolveOfficeWinner-based test as the chapter/responsory/hymn/versicle
|
||||
* bundle below) — a First Vespers of a feast should get that feast's own
|
||||
* psalms, same as it gets that feast's own office. Falls back to
|
||||
* `undefined` for an eligible feast with no override authored in either
|
||||
* tier, same honest incremental-content convention as everywhere else. */
|
||||
function getPsalmodyOverrideFor(day: LiturgicalDay): VespersDay | undefined {
|
||||
const id = getOfficeOverrideId(day);
|
||||
return id ? getVespersPsalmodyOverride(id) : undefined;
|
||||
const properId = getPsalmodyProperOverrideId(day);
|
||||
const proper = properId ? getVespersSaintOverride(properId) : undefined;
|
||||
if (proper) {
|
||||
return proper;
|
||||
}
|
||||
const categoryId = getOfficeOverrideId(day);
|
||||
return categoryId ? getVespersCommonOverride(categoryId) : undefined;
|
||||
}
|
||||
|
||||
/** The 4 weekday-variable psalm groups — no fixed leading/trailing psalm
|
||||
|
||||
+13
-7
@@ -26,12 +26,18 @@ function renderColumns(resolved: ResolvedText | ResolvedVerse, languages: readon
|
||||
}
|
||||
|
||||
// Scripture quotations always carry a full citation (book, chapter, verse) —
|
||||
// shown once, deduplicated across languages when they happen to match.
|
||||
function renderCitation(text: ResolvedText): string {
|
||||
// shown once per currently-displayed language, deduplicated when they
|
||||
// happen to match (e.g. a plain "Psalm 118" citation reused verbatim in
|
||||
// both columns) — restricted to `languages` so a language-specific
|
||||
// abbreviation (Latin "Jac 1:12" vs. English "Jas 1:12") never leaks into
|
||||
// a single-language view.
|
||||
function renderCitation(text: ResolvedText, languages: readonly string[]): string {
|
||||
if (!text.citation) {
|
||||
return '';
|
||||
}
|
||||
const values = [...new Set(Object.values(text.citation).filter((v): v is string => Boolean(v)))];
|
||||
const values = [
|
||||
...new Set(languages.map((lang) => text.citation![lang]).filter((v): v is string => Boolean(v))),
|
||||
];
|
||||
return values.length ? `<p class="ordo-part-citation">${escapeHtml(values.join(' / '))}</p>` : '';
|
||||
}
|
||||
|
||||
@@ -64,7 +70,7 @@ function renderPart(part: ResolvedPart, languages: readonly string[]): string {
|
||||
return `
|
||||
<section class="ordo-part ordo-part-${part.kind}">
|
||||
<h3 class="ordo-part-label">${hourLabel(part.kind)}</h3>
|
||||
${renderCitation(part.text)}
|
||||
${renderCitation(part.text, languages)}
|
||||
${renderColumns(part.text, languages)}
|
||||
</section>
|
||||
`;
|
||||
@@ -93,7 +99,7 @@ function renderPart(part: ResolvedPart, languages: readonly string[]): string {
|
||||
return `
|
||||
<section class="ordo-part ordo-part-lesson">
|
||||
${renderLessonLabel(part.label, languages)}
|
||||
${renderCitation(part.text)}
|
||||
${renderCitation(part.text, languages)}
|
||||
${renderColumns(part.text, languages)}
|
||||
${part.responsory ? `<div class="ordo-part-responsory">${renderColumns(part.responsory, languages)}</div>` : ''}
|
||||
</section>
|
||||
@@ -110,7 +116,7 @@ function renderPart(part: ResolvedPart, languages: readonly string[]): string {
|
||||
return `
|
||||
<section class="ordo-part ordo-part-lesson ordo-part-gospel">
|
||||
<h3 class="ordo-part-label">Gospel</h3>
|
||||
${renderCitation(part.text)}
|
||||
${renderCitation(part.text, languages)}
|
||||
${renderColumns(part.text, languages)}
|
||||
${part.source ? `<p class="ordo-part-citation">${escapeHtml(part.source)}</p>` : ''}
|
||||
${part.responsory ? `<div class="ordo-part-responsory">${renderColumns(part.responsory, languages)}</div>` : ''}
|
||||
@@ -170,7 +176,7 @@ function renderPart(part: ResolvedPart, languages: readonly string[]): string {
|
||||
<section class="ordo-part ordo-part-canticle">
|
||||
${part.antiphon ? renderColumns(part.antiphon, languages) : ''}
|
||||
<h3 class="ordo-part-label lang-columns lang-columns-${languages.length}">${titleCols}</h3>
|
||||
${renderCitation(part.text)}
|
||||
${renderCitation(part.text, languages)}
|
||||
${renderColumns(part.text, languages)}
|
||||
${part.gloriaPatri ? `<div class="ordo-part-gloria-patri">${renderColumns(part.gloriaPatri, languages)}</div>` : ''}
|
||||
</section>
|
||||
|
||||
Reference in New Issue
Block a user