feat(faith): adopt flat-id romcal fork and simplify 1962 calendar rendering

Switch the romcal dependency to AlexBocken/romcal (monorepo fork with
collapsed bucket prefixes) and strip the runtime prefix-fallback chain
from liturgicalCalendar.ts — name/propers lookups now use a single
flat id. The 1962 data model shrinks to just what the rendering uses
(commem {id,name}, detail carrying propers as {key, la[], local[]})
and the detail + overview pages drop the rubrics/octave/properSource
fields that never got wired in.
This commit is contained in:
2026-04-18 22:28:48 +02:00
parent e036588795
commit e37d41b180
9 changed files with 305 additions and 597 deletions
@@ -23,7 +23,6 @@ import type { CalendarDay, SeasonArc, YearDay } from '$lib/calendarTypes';
export type {
CalendarDay,
ProperSection,
ProperSegment,
Rite1962Commem,
Rite1962Detail,
SeasonArc,
@@ -238,22 +238,11 @@
<span class="tc-tag">{t('cycle', lang)}: {humanizeSundayCycle(hero.sundayCycle)}</span>
{/if}
</div>
{#if hero.rite1962}
{@const r = hero.rite1962.rubrics}
<div class="tc-rubrics">
<span class="tc-rubric" class:on={r.gloria}>
<span class="tc-rubric-dot"></span>
<b>{t1962('gloria', lang)}</b>
<span class="tc-state">{r.gloria ? t1962('yes', lang) : t1962('no', lang)}</span>
</span>
<span class="tc-rubric" class:on={r.credo}>
<span class="tc-rubric-dot"></span>
<b>{t1962('credo', lang)}</b>
<span class="tc-state">{r.credo ? t1962('yes', lang) : t1962('no', lang)}</span>
</span>
{#if r.preface}
<span class="tc-preface"><em>{t1962('preface', lang)}:</em> {r.preface}</span>
{/if}
{#if hero.rite1962 && hero.rite1962.commemorations.length}
<div class="tc-commems">
{#each hero.rite1962.commemorations as c (c.id)}
<span class="tc-commem">{c.name}</span>
{/each}
</div>
{/if}
<span class="tc-arrow" aria-hidden="true"></span>
@@ -281,19 +270,30 @@
{lang === 'de' ? 'Monat' : lang === 'la' ? 'Mensis' : 'Month'}
</button>
</div>
<div class="legend" aria-hidden={!Object.keys(LIT_COLOR_VAR).length}>
{#each Object.keys(LIT_COLOR_VAR) as key (key)}
{@const label =
lang === 'de'
? { WHITE: 'Weiß', RED: 'Rot', GREEN: 'Grün', PURPLE: 'Violett', ROSE: 'Rosa', BLACK: 'Schwarz', GOLD: 'Gold' }[key]
: lang === 'la'
? { WHITE: 'Albus', RED: 'Ruber', GREEN: 'Viridis', PURPLE: 'Violaceus', ROSE: 'Rosaceus', BLACK: 'Niger', GOLD: 'Aureus' }[key]
: { WHITE: 'White', RED: 'Red', GREEN: 'Green', PURPLE: 'Violet', ROSE: 'Rose', BLACK: 'Black', GOLD: 'Gold' }[key]}
<span class="swatch">
<span class="sq" style="background: {litBg(key)}"></span>
<span>{label}</span>
</span>
{/each}
<div class="overview-right">
<div class="legend" aria-hidden={!Object.keys(LIT_COLOR_VAR).length}>
{#each Object.keys(LIT_COLOR_VAR) as key (key)}
{@const label =
lang === 'de'
? { WHITE: 'Weiß', RED: 'Rot', GREEN: 'Grün', PURPLE: 'Violett', ROSE: 'Rosa', BLACK: 'Schwarz' }[key]
: lang === 'la'
? { WHITE: 'Albus', RED: 'Ruber', GREEN: 'Viridis', PURPLE: 'Violaceus', ROSE: 'Rosaceus', BLACK: 'Niger' }[key]
: { WHITE: 'White', RED: 'Red', GREEN: 'Green', PURPLE: 'Violet', ROSE: 'Rose', BLACK: 'Black' }[key]}
<span class="swatch">
<span class="sq" style="background: {litBg(key)}"></span>
<span>{label}</span>
</span>
{/each}
</div>
<a
class="jump-btn jump-btn-gold"
href={todayHref}
data-sveltekit-noscroll
data-sveltekit-replacestate
>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>
{t('jumpToToday', lang)}
</a>
</div>
</div>
@@ -333,13 +333,6 @@
</a>
</nav>
<div class="today-jump-row">
<a class="jump-btn" href={todayHref} data-sveltekit-noscroll>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>
{t('jumpToToday', lang)}
</a>
</div>
<div class="grid">
<div class="grid-header" role="row">
{#each weekdayLabels as wd (wd)}
@@ -642,60 +635,21 @@
letter-spacing: 0.06em;
text-transform: uppercase;
}
.tc-rubrics {
.tc-commems {
margin-top: 1.4rem;
padding-top: 1.1rem;
border-top: 1px solid rgba(255, 255, 255, 0.22);
display: flex;
flex-wrap: wrap;
gap: 0.5rem 1.1rem;
align-items: center;
gap: 0.4rem 0.6rem;
}
.tc-rubric {
display: inline-flex;
align-items: center;
gap: 0.45rem;
padding: 0.35rem 0.75rem 0.35rem 0.6rem;
.tc-commem {
padding: 0.3rem 0.7rem;
border-radius: var(--radius-pill);
background: rgba(255, 255, 255, 0.18);
border: 1px solid rgba(255, 255, 255, 0.22);
font-size: 0.82rem;
}
.tc-rubric b {
font-weight: 700;
}
.tc-rubric .tc-state {
font-size: 0.72rem;
opacity: 0.72;
font-style: italic;
}
.tc-rubric-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.55);
box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.15);
}
.tc-rubric.on .tc-rubric-dot {
background: #8be78b;
box-shadow: 0 0 0 2px rgba(139, 231, 139, 0.22);
}
.tc-rubric:not(.on) b {
opacity: 0.65;
}
.tc-preface {
font-size: 0.85rem;
opacity: 0.9;
}
.tc-preface em {
font-style: normal;
font-weight: 700;
font-size: 0.68rem;
letter-spacing: 0.1em;
text-transform: uppercase;
margin-right: 0.4rem;
opacity: 0.72;
}
.tc-arrow {
position: absolute;
bottom: 1.1rem;
@@ -760,10 +714,17 @@
.view-switcher button:active {
transform: scale(0.95);
}
.overview-right {
display: flex;
flex-direction: column;
gap: 0.5rem;
align-items: flex-end;
}
.legend {
display: flex;
gap: 0.4rem;
flex-wrap: wrap;
justify-content: flex-end;
}
.legend .swatch {
display: inline-flex;
@@ -837,11 +798,6 @@
box-shadow: var(--shadow-hover);
}
.today-jump-row {
display: flex;
justify-content: center;
margin-bottom: 1rem;
}
.jump-btn {
display: inline-flex;
align-items: center;
@@ -851,12 +807,25 @@
color: var(--color-text-secondary);
border-radius: var(--radius-pill);
font-size: var(--text-sm);
transition: background var(--transition-fast), transform var(--transition-fast);
box-shadow: var(--shadow-sm);
transition: background var(--transition-fast), transform var(--transition-fast),
box-shadow var(--transition-fast);
}
.jump-btn:hover {
background: var(--color-bg-elevated);
color: var(--color-text-primary);
transform: scale(1.03);
box-shadow: var(--shadow-hover);
}
.jump-btn-gold {
background: var(--lit-gold);
color: var(--lit-gold-ink);
font-weight: 600;
}
.jump-btn-gold:hover {
background: var(--lit-gold);
color: var(--lit-gold-ink);
filter: brightness(1.08);
}
.grid {
@@ -12,6 +12,12 @@
t1962,
type CalendarLang
} from '../../../../../calendarI18n';
function kindLabel(kind: 'tempora' | 'sancti', l: CalendarLang): string {
if (kind === 'tempora')
return l === 'de' ? 'Temporale' : l === 'la' ? 'Temporale' : 'Temporal';
return l === 'de' ? 'Sanktorale' : l === 'la' ? 'Sanctorale' : 'Sanctoral';
}
import { litBg, litInk } from '../../../../../calendarColors';
let { data }: { data: PageData } = $props();
@@ -115,7 +121,7 @@
<dl class="detail-extras">
<div>
<dt>{t1962('source', lang)}</dt>
<dd>{d.kind}{d.properSource ? ` · ${d.properSource}` : ''}{d.communeSlug ? ` (${d.communeSlug})` : ''}</dd>
<dd>{kindLabel(d.kind, lang)}</dd>
</div>
{#if d.vigilOf}
<div>
@@ -126,7 +132,7 @@
{#if d.octave}
<div>
<dt>{t1962('octave', lang)}</dt>
<dd>{d.octave.id} · {t1962('octaveDay', lang)} {d.octave.day} · {d.octave.rank}</dd>
<dd>{d.octave.ofId} · {t1962('octaveDay', lang)} {d.octave.day}</dd>
</div>
{/if}
{#if d.transferredFrom}
@@ -136,32 +142,13 @@
</div>
{/if}
</dl>
<div class="rubrics-grid">
<h4>{t1962('rubrics', lang)}</h4>
<div class="rubric-row">
<span class="rubric-chip" class:on={d.rubrics.gloria}>{t1962('gloria', lang)}: {d.rubrics.gloria ? t1962('yes', lang) : t1962('no', lang)}</span>
<span class="rubric-chip" class:on={d.rubrics.credo}>{t1962('credo', lang)}: {d.rubrics.credo ? t1962('yes', lang) : t1962('no', lang)}</span>
{#if d.rubrics.preface}
<span class="rubric-chip on">{t1962('preface', lang)}: {d.rubrics.preface}</span>
{/if}
{#if d.rubrics.lastGospel}
<span class="rubric-chip on">{t1962('lastGospel', lang)}: {d.rubrics.lastGospel}</span>
{/if}
{#if d.rubrics.ite}
<span class="rubric-chip on">{t1962('ite', lang)}: {d.rubrics.ite}</span>
{/if}
</div>
</div>
{#if d.commemorations.length}
<div class="commems">
<h4>{t1962('commemorations', lang)}</h4>
<ul>
{#each d.commemorations as c (c.key)}
{@const cHex = hexFor(c.colorKeys)}
{#each d.commemorations as c (c.id)}
<li>
<span class="color-swatch" style="background: {cHex}"></span>
<span class="commem-name">{c.name}</span>
<span class="commem-rank">{c.rankName}</span>
</li>
{/each}
</ul>
@@ -171,62 +158,26 @@
<section class="propers">
<h4>{t1962('propers', lang)}</h4>
{#each d.propers as section (section.key)}
{@const rows = Math.max(section.la.length, section.local.length)}
<div class="proper-block">
<div class="proper-label-row">
<span class="proper-label">{properLabel(section.key, lang)}</span>
</div>
{#each section.segments as seg, segIdx (segIdx)}
<div class="proper-segment">
{#if seg.refs && seg.refs.length}
<div class="proper-segment-refs">
{#each seg.refs as r (r)}
<span class="proper-ref">{r}</span>
{/each}
</div>
{/if}
{#if seg.la || seg.local}
<div class="proper-cols" class:single={lang === 'la' || !seg.local}>
{#if lang !== 'la' && seg.local && seg.fromBible}
<p class="proper-fallback-note">{t1962('bibleFallbackNote', lang)}</p>
{#each Array(rows) as _, i (i)}
{@const la = section.la[i] ?? ''}
{@const local = section.local[i] ?? ''}
{#if la || local}
<div class="proper-segment">
<div class="proper-cols" class:single={lang === 'la' || !local}>
{#if la}
<div class="proper-col proper-col-la" lang="la">{la}</div>
{/if}
{#if seg.la}
<div class="proper-col proper-col-la" lang="la">{seg.la}</div>
{/if}
{#if lang !== 'la' && seg.local}
<div class="proper-col proper-col-local" lang={lang}>{seg.local}</div>
{#if lang !== 'la' && local}
<div class="proper-col proper-col-local" lang={lang}>{local}</div>
{/if}
</div>
{/if}
</div>
{/each}
</div>
{/each}
</section>
{/if}
{#if d.extraSections.length}
<section class="propers">
<h4>{t1962('extraSections', lang)}</h4>
{#each d.extraSections as section (section.key)}
<div class="proper-block">
<div class="proper-label-row">
<span class="proper-label">{properLabel(section.key, lang)}</span>
</div>
{#each section.segments as seg, segIdx (segIdx)}
<div class="proper-segment">
{#if seg.refs && seg.refs.length}
<div class="proper-segment-refs">
{#each seg.refs as r (r)}
<span class="proper-ref">{r}</span>
{/each}
</div>
{/if}
<div class="proper-cols" class:single={lang === 'la' || !seg.local}>
<div class="proper-col proper-col-la" lang="la">{seg.la}</div>
{#if lang !== 'la' && seg.local}
<div class="proper-col proper-col-local" lang={lang}>{seg.local}</div>
{/if}
</div>
</div>
{/if}
{/each}
</div>
{/each}
@@ -399,10 +350,6 @@
margin: 0;
color: var(--color-text-primary);
}
.rubrics-grid {
margin-top: 0.75rem;
}
.rubrics-grid h4,
.commems h4,
.propers h4 {
margin: 0.5rem 0 0.4rem;
@@ -412,24 +359,6 @@
color: var(--color-text-secondary);
font-weight: 600;
}
.rubric-row {
display: flex;
flex-wrap: wrap;
gap: 0.4rem;
}
.rubric-chip {
padding: 0.25rem 0.6rem;
border-radius: var(--radius-pill);
background: var(--color-bg-tertiary);
color: var(--color-text-secondary);
font-size: 0.78rem;
border: 1px solid var(--color-border);
}
.rubric-chip.on {
background: color-mix(in srgb, var(--accent) 15%, var(--color-bg-tertiary));
color: var(--color-text-primary);
border-color: color-mix(in srgb, var(--accent) 30%, var(--color-border));
}
.commems {
margin-top: 0.75rem;
}
@@ -450,18 +379,10 @@
border-radius: var(--radius-sm, 6px);
font-size: 0.85rem;
}
.commems .color-swatch {
border-color: var(--color-border);
}
.commem-name {
flex: 1 1 auto;
color: var(--color-text-primary);
}
.commem-rank {
font-size: 0.72rem;
color: var(--color-text-secondary);
white-space: nowrap;
}
.propers {
margin-top: 1rem;
@@ -485,15 +406,6 @@
text-transform: uppercase;
letter-spacing: 0.03em;
}
.proper-ref {
display: inline-block;
padding: 0.1rem 0.5rem;
border-radius: var(--radius-pill);
font-size: 0.72rem;
background: var(--color-bg-tertiary);
color: var(--color-text-primary);
border: 1px solid var(--color-border);
}
.proper-segment {
margin-top: 0.5rem;
}
@@ -504,24 +416,6 @@
padding-top: 0.5rem;
border-top: 1px dashed var(--color-border);
}
.proper-segment-refs {
display: flex;
flex-wrap: wrap;
gap: 0.35rem;
margin-bottom: 0.35rem;
}
.proper-fallback-note {
grid-column: 2 / 3;
grid-row: 1;
margin: 0 0 0.25rem;
padding: 0.4rem 0.6rem;
border-left: 2px solid color-mix(in srgb, var(--orange) 55%, transparent);
background: color-mix(in srgb, var(--orange) 8%, transparent);
border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
font-size: 0.78rem;
line-height: 1.4;
color: var(--color-text-secondary);
}
.proper-cols {
display: grid;
grid-template-columns: 1fr 1fr;
@@ -531,12 +425,10 @@
}
.proper-col-la {
grid-column: 1;
grid-row: 2;
font-style: italic;
}
.proper-col-local {
grid-column: 2;
grid-row: 2;
}
.proper-cols.single {
grid-template-columns: 1fr;
@@ -544,7 +436,6 @@
.proper-cols.single .proper-col-la,
.proper-cols.single .proper-col-local {
grid-column: 1;
grid-row: auto;
}
.proper-col {
white-space: pre-wrap;
@@ -564,10 +455,8 @@
grid-template-columns: 1fr;
}
.proper-cols .proper-col-la,
.proper-cols .proper-col-local,
.proper-cols .proper-fallback-note {
.proper-cols .proper-col-local {
grid-column: 1;
grid-row: auto;
}
}
@media (max-width: 560px) {
@@ -1,14 +1,15 @@
// Liturgical color tokens used by the overview design.
// Maps romcal color keys to the CSS variables defined on the calendar page.
// Romcal never emits GOLD for either rite, so it is excluded from the legend
// and lookup here. The --lit-gold token still exists for today-pin styling.
export const LIT_COLOR_VAR: Record<string, string> = {
WHITE: '--lit-white',
RED: '--lit-red',
GREEN: '--lit-green',
PURPLE: '--lit-violet',
ROSE: '--lit-rose',
BLACK: '--lit-black',
GOLD: '--lit-gold'
BLACK: '--lit-black'
};
export const LIT_INK_VAR: Record<string, string> = {
@@ -17,8 +18,7 @@ export const LIT_INK_VAR: Record<string, string> = {
GREEN: '--lit-green-ink',
PURPLE: '--lit-violet-ink',
ROSE: '--lit-rose-ink',
BLACK: '--lit-black-ink',
GOLD: '--lit-gold-ink'
BLACK: '--lit-black-ink'
};
export function litBg(colorKey: string | undefined): string {
@@ -59,11 +59,18 @@ export function hexFor(colorKeys: string[]): string {
return colorHex[first] ?? '#27AE60';
}
// Rank emphasis for visual weighting of cells
// Rank emphasis for visual weighting of cells. Accepts both 1969 rank keys and
// 1962 class labels (ClassI..IV), since 1962 days are emphasized similarly.
export function rankEmphasis(rank: string): number {
if (rank === 'SOLEMNITY') return 3;
if (rank === 'FEAST' || rank === 'SUNDAY' || rank === 'HOLY_DAY_OF_OBLIGATION') return 2;
if (rank === 'MEMORIAL') return 1;
if (rank === 'SOLEMNITY' || rank === 'ClassI') return 3;
if (
rank === 'FEAST' ||
rank === 'SUNDAY' ||
rank === 'HOLY_DAY_OF_OBLIGATION' ||
rank === 'ClassII'
)
return 2;
if (rank === 'MEMORIAL' || rank === 'ClassIII') return 1;
return 0;
}
@@ -245,7 +252,14 @@ const SEASON_LABEL: Record<string, Record<CalendarLang, string>> = {
EasterWeek: { en: 'Easter Week', de: 'Osteroktav', la: 'Hebdomada Paschae' },
Paschaltide: { en: 'Eastertide', de: 'Osterzeit', la: 'Tempus Paschale' },
Pentecost: { en: 'Pentecost Week', de: 'Pfingstoktav', la: 'Hebdomada Pentecostes' },
TimeAfterPentecost: { en: 'after Pentecost', de: 'nach Pfingsten', la: 'post Pentecosten' }
TimeAfterPentecost: { en: 'after Pentecost', de: 'nach Pfingsten', la: 'post Pentecosten' },
// romcal 3 emits 1969-style SCREAMING_SNAKE season keys even for the 1962 calendar.
ADVENT: { en: 'Advent', de: 'Advent', la: 'Adventus' },
CHRISTMAS_TIME: { en: 'Christmastide', de: 'Weihnachtszeit', la: 'Tempus Nativitatis' },
LENT: { en: 'Lent', de: 'Fastenzeit', la: 'Quadragesima' },
PASCHAL_TRIDUUM: { en: 'Paschal Triduum', de: 'Ostertriduum', la: 'Triduum Paschale' },
EASTER_TIME: { en: 'Eastertide', de: 'Osterzeit', la: 'Tempus Paschale' },
ORDINARY_TIME: { en: 'after Pentecost', de: 'nach Pfingsten', la: 'post Pentecosten' }
};
export function season1962Label(season: string, lang: CalendarLang): string {
@@ -268,26 +282,12 @@ export function colorLabel1962(colorKey: string, lang: CalendarLang): string {
export const ui1962 = {
commemorations: { en: 'Commemorations', de: 'Kommemorationen', la: 'Commemorationes' },
rubrics: { en: 'Rubrics', de: 'Rubriken', la: 'Rubricæ' },
gloria: { en: 'Gloria', de: 'Gloria', la: 'Gloria' },
credo: { en: 'Credo', de: 'Credo', la: 'Credo' },
preface: { en: 'Preface', de: 'Präfation', la: 'Præfatio' },
lastGospel: { en: 'Last Gospel', de: 'Letztes Evangelium', la: 'Ultimum Evangelium' },
ite: { en: 'Dismissal', de: 'Entlassung', la: 'Dimissio' },
octave: { en: 'Octave', de: 'Oktav', la: 'Octava' },
octaveDay: { en: 'day', de: 'Tag', la: 'dies' },
vigilOf: { en: 'Vigil of', de: 'Vigil von', la: 'Vigilia' },
transferredFrom: { en: 'Transferred from', de: 'Übertragen von', la: 'Translatum ex' },
source: { en: 'Source', de: 'Quelle', la: 'Fons' },
yes: { en: 'yes', de: 'ja', la: 'sic' },
no: { en: 'no', de: 'nein', la: 'non' },
properRef: { en: 'Proper', de: 'Proprium', la: 'Proprium' },
propers: { en: 'Mass propers', de: 'Messproprium', la: 'Propria Missæ' },
extraSections: { en: 'Additional readings', de: 'Zusätzliche Lesungen', la: 'Lectiones additae' },
bibleFallbackNote: {
en: 'Translation taken from the Douay-Rheims Bible, since no translated proper is published for this section. Wording will differ from authoritative missals.',
de: 'Übersetzung stammt aus der Allioli-Bibel, da für diesen Abschnitt kein übersetztes Proprium veröffentlicht ist. Wortlaut weicht von maßgeblichen Messbüchern ab.'
}
propers: { en: 'Mass propers', de: 'Messproprium', la: 'Propria Missæ' }
} as const;
export function t1962(key: keyof typeof ui1962, lang: CalendarLang): string {
@@ -296,19 +296,18 @@ export function t1962(key: keyof typeof ui1962, lang: CalendarLang): string {
}
const PROPER_LABEL: Record<string, Record<CalendarLang, string>> = {
introit: { en: 'Introit', de: 'Introitus', la: 'Introitus' },
collect: { en: 'Collect', de: 'Kollekte', la: 'Collecta' },
epistle: { en: 'Epistle', de: 'Epistel', la: 'Epistola' },
gradual: { en: 'Gradual', de: 'Graduale', la: 'Graduale' },
alleluia: { en: 'Alleluia', de: 'Alleluja', la: 'Alleluia' },
tract: { en: 'Tract', de: 'Tractus', la: 'Tractus' },
sequence: { en: 'Sequence', de: 'Sequenz', la: 'Sequentia' },
gospel: { en: 'Gospel', de: 'Evangelium', la: 'Evangelium' },
offertory: { en: 'Offertory', de: 'Offertorium', la: 'Offertorium' },
secret: { en: 'Secret', de: 'Stillgebet', la: 'Secreta' },
preface: { en: 'Preface', de: 'Präfation', la: 'Præfatio' },
communion: { en: 'Communion', de: 'Kommunion', la: 'Communio' },
postcommunion: { en: 'Postcommunion', de: 'Schlussgebet', la: 'Postcommunio' }
Introitus: { en: 'Introit', de: 'Introitus', la: 'Introitus' },
Oratio: { en: 'Collect', de: 'Kollekte', la: 'Oratio' },
Lectio: { en: 'Epistle', de: 'Epistel', la: 'Lectio' },
Graduale: { en: 'Gradual', de: 'Graduale', la: 'Graduale' },
GradualeF: { en: 'Alleluia', de: 'Alleluja', la: 'Alleluia' },
Tractus: { en: 'Tract', de: 'Tractus', la: 'Tractus' },
Sequentia: { en: 'Sequence', de: 'Sequenz', la: 'Sequentia' },
Evangelium: { en: 'Gospel', de: 'Evangelium', la: 'Evangelium' },
Offertorium: { en: 'Offertory', de: 'Offertorium', la: 'Offertorium' },
Secreta: { en: 'Secret', de: 'Stillgebet', la: 'Secreta' },
Communio: { en: 'Communion', de: 'Kommunion', la: 'Communio' },
Postcommunio: { en: 'Postcommunion', de: 'Schlussgebet', la: 'Postcommunio' }
};
export function properLabel(key: string, lang: CalendarLang): string {