feat(faith): render 1962 Mass propers with scripture refs and Bible fallback
Show propers text for each 1962 celebration with scripture reference pills grouping each block. When a translated proper is missing, fall back to the local-language Bible (Douay-Rheims for en, Allioli for de), showing a note above the translated column. Handles multi-segment refs (e.g. "Ps 118:85; 118:46") with inherited book/chapter, and shifts Vulgate→Hebrew psalm numbering for Allioli. Also restructures date navigation as folder-based optional params (/yyyy/mm/dd) with the rite forced as a required path segment so day/month navigation stays within the active rite.
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
import { error, redirect } from '@sveltejs/kit';
|
||||
import type { PageServerLoad } from './$types';
|
||||
import { expectedSlug } from './calendarI18n';
|
||||
|
||||
export const load: PageServerLoad = async ({ params, url }) => {
|
||||
const slug = expectedSlug(params.faithLang);
|
||||
if (slug === null) throw error(404, 'Not found');
|
||||
if (params.calendar !== slug) {
|
||||
throw redirect(307, `/${params.faithLang}/${slug}`);
|
||||
}
|
||||
const search = url.search ?? '';
|
||||
throw redirect(307, `/${params.faithLang}/${params.calendar}/1962${search}`);
|
||||
};
|
||||
+125
-29
@@ -15,7 +15,11 @@ import {
|
||||
season1962Label,
|
||||
type CalendarLang,
|
||||
type Rite
|
||||
} from '../calendarI18n';
|
||||
} from '../../../../calendarI18n';
|
||||
import { getProperSegments } from '$lib/server/romcal1962Refs';
|
||||
import { lookupReference } from '$lib/server/bible';
|
||||
import { translateRefToTarget } from '$lib/server/bibleRefLatin';
|
||||
import { resolve as resolvePath } from 'path';
|
||||
|
||||
export interface CalendarDay {
|
||||
iso: string;
|
||||
@@ -71,7 +75,6 @@ const localeBundles = {
|
||||
la: GeneralRoman_La
|
||||
};
|
||||
|
||||
// Cache: lang -> Romcal instance
|
||||
const romcalByLang = new Map<CalendarLang, Romcal>();
|
||||
function getRomcal(lang: CalendarLang): Romcal {
|
||||
let r = romcalByLang.get(lang);
|
||||
@@ -81,7 +84,6 @@ function getRomcal(lang: CalendarLang): Romcal {
|
||||
return r;
|
||||
}
|
||||
|
||||
// Cache: lang|year -> Map<iso, CalendarDay>
|
||||
const yearCache = new Map<string, Map<string, CalendarDay>>();
|
||||
|
||||
async function getYear(lang: CalendarLang, year: number): Promise<Map<string, CalendarDay>> {
|
||||
@@ -142,10 +144,21 @@ const PROPER_ORDER = [
|
||||
|
||||
type ProperKey = (typeof PROPER_ORDER)[number];
|
||||
|
||||
export interface ProperSection {
|
||||
key: string;
|
||||
export interface ProperSegment {
|
||||
refs: string[];
|
||||
la: string;
|
||||
local?: string;
|
||||
// When true, `local` text comes from the Bible translation lookup because
|
||||
// the propers dataset had no localized text for this segment.
|
||||
fromBible?: boolean;
|
||||
}
|
||||
|
||||
export interface ProperSection {
|
||||
key: string;
|
||||
segments: ProperSegment[];
|
||||
// Aggregate list of refs across segments (for quick checks)
|
||||
refs: string[];
|
||||
fromBible?: boolean;
|
||||
}
|
||||
|
||||
const COLOR_KEY_1962: Record<string, string> = {
|
||||
@@ -190,15 +203,93 @@ function textOf(dict: Record<string, string> | undefined, locale: string): strin
|
||||
return v && v.trim() ? v : '';
|
||||
}
|
||||
|
||||
function bibleTextFor(ref: string, targetLang: 'en' | 'de'): string | null {
|
||||
const tsvPath = resolvePath(targetLang === 'de' ? 'static/allioli.tsv' : 'static/drb.tsv');
|
||||
const segments = ref.split(';').map((s) => s.trim()).filter(Boolean);
|
||||
if (!segments.length) return null;
|
||||
|
||||
let lastBook: string | null = null;
|
||||
let lastChapter: string | null = null;
|
||||
const parts: string[] = [];
|
||||
|
||||
for (const seg of segments) {
|
||||
// Detect optional leading book (letters, optional leading digit like "1 Cor")
|
||||
const bookMatch = seg.match(/^(\d?\s?[A-Za-z]+\.?)\s+(.*)$/);
|
||||
let book: string | null = null;
|
||||
let rest = seg;
|
||||
if (bookMatch) {
|
||||
book = bookMatch[1];
|
||||
rest = bookMatch[2].trim();
|
||||
}
|
||||
if (book) lastBook = book;
|
||||
if (!lastBook) continue;
|
||||
|
||||
let chapter: string;
|
||||
let verseRange: string;
|
||||
// Accept "118:85", "118, 85", "118:85-90", or bare "85" (inherit chapter)
|
||||
const normalized = rest.replace(/\s*,\s*/, ':').replace(/\s+/g, ' ').trim();
|
||||
if (normalized.includes(':')) {
|
||||
const [c, v] = normalized.split(':');
|
||||
chapter = c.trim();
|
||||
verseRange = v.trim();
|
||||
lastChapter = chapter;
|
||||
} else if (lastChapter) {
|
||||
chapter = lastChapter;
|
||||
verseRange = normalized;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
const fullRef = `${lastBook} ${chapter}:${verseRange}`;
|
||||
const translated = translateRefToTarget(fullRef, targetLang);
|
||||
if (!translated) continue;
|
||||
|
||||
try {
|
||||
const result = lookupReference(translated, tsvPath);
|
||||
if (result && result.verses.length) {
|
||||
parts.push(result.verses.map((v) => `${v.verse}. ${v.text}`).join(' '));
|
||||
}
|
||||
} catch {
|
||||
// skip this segment
|
||||
}
|
||||
}
|
||||
|
||||
return parts.length ? parts.join(' ') : null;
|
||||
}
|
||||
|
||||
function propersOf(p: Celebration1962, lang: CalendarLang): ProperSection[] {
|
||||
const out: ProperSection[] = [];
|
||||
const m = p.propers;
|
||||
if (!m) return out;
|
||||
const source = p.properRef.source;
|
||||
for (const key of PROPER_ORDER) {
|
||||
const la = textOf(m[key as ProperKey], 'la');
|
||||
const local = lang === 'la' ? '' : textOf(m[key as ProperKey], lang);
|
||||
if (!la && !local) continue;
|
||||
out.push({ key, la, ...(local ? { local } : {}) });
|
||||
const rawSegs = getProperSegments(source, key, lang);
|
||||
if (!rawSegs || !rawSegs.length) continue;
|
||||
|
||||
const segments: ProperSegment[] = [];
|
||||
const allRefs: string[] = [];
|
||||
let sectionFromBible = false;
|
||||
|
||||
for (const raw of rawSegs) {
|
||||
const seg: ProperSegment = { refs: raw.refs, la: raw.la };
|
||||
if (lang !== 'la' && raw.local) seg.local = raw.local;
|
||||
|
||||
// Bible fallback: only for this segment, using only its own refs
|
||||
if (!seg.local && raw.refs.length && lang !== 'la') {
|
||||
const bible = bibleTextFor(raw.refs.join('; '), lang);
|
||||
if (bible) {
|
||||
seg.local = bible;
|
||||
seg.fromBible = true;
|
||||
sectionFromBible = true;
|
||||
}
|
||||
}
|
||||
|
||||
allRefs.push(...raw.refs);
|
||||
if (seg.la || seg.local || seg.refs.length) segments.push(seg);
|
||||
}
|
||||
|
||||
if (!segments.length) continue;
|
||||
const section: ProperSection = { key, segments, refs: allRefs };
|
||||
if (sectionFromBible) section.fromBible = true;
|
||||
out.push(section);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
@@ -209,14 +300,17 @@ function extraSectionsOf(p: Celebration1962, lang: CalendarLang): ProperSection[
|
||||
const out: ProperSection[] = [];
|
||||
for (const [key, block] of Object.entries(extras)) {
|
||||
const buckets: Record<string, string[]> = {};
|
||||
const refs: string[] = [];
|
||||
for (const item of block) {
|
||||
if (item.type !== 'text') continue;
|
||||
(buckets[item.lang] ??= []).push(item.value);
|
||||
if (item.type === 'text') (buckets[item.lang] ??= []).push(item.value);
|
||||
else if (item.type === 'scriptureRef') refs.push(item.ref);
|
||||
}
|
||||
const la = (buckets['la'] ?? []).join('\n\n').trim();
|
||||
const local = lang === 'la' ? '' : (buckets[lang] ?? []).join('\n\n').trim();
|
||||
if (!la && !local) continue;
|
||||
out.push({ key, la, ...(local ? { local } : {}) });
|
||||
if (!la && !local && refs.length === 0) continue;
|
||||
const segment: ProperSegment = { refs, la };
|
||||
if (local) segment.local = local;
|
||||
out.push({ key, segments: [segment], refs });
|
||||
}
|
||||
return out;
|
||||
}
|
||||
@@ -300,21 +394,21 @@ export const load: PageServerLoad = async ({ params, url, locals }) => {
|
||||
const lang: CalendarLang =
|
||||
params.faithLang === 'faith' ? 'en' : params.faithLang === 'fides' ? 'la' : 'de';
|
||||
|
||||
const rite: Rite = params.rite === '1969' ? '1969' : '1962';
|
||||
|
||||
// Reject mm without yyyy, dd without yyyy+mm. Sveltekit optional routes let
|
||||
// gaps through so we normalize here.
|
||||
if ((params.mm && !params.yyyy) || (params.dd && !params.mm)) {
|
||||
throw error(404, 'Not found');
|
||||
}
|
||||
|
||||
const today = new Date();
|
||||
// Rite lives in the optional [[year]] route segment (1962 | 1969). When
|
||||
// absent we default to 1962, the new tridentine calendar.
|
||||
const rite: Rite = params.year === '1969' ? '1969' : '1962';
|
||||
|
||||
const yParam = url.searchParams.get('y');
|
||||
const mParam = url.searchParams.get('m');
|
||||
const selectedDateParam = url.searchParams.get('d');
|
||||
|
||||
const minYear = rite === '1962' ? 1900 : 1969;
|
||||
const y = yParam !== null ? Number(yParam) : NaN;
|
||||
const m = mParam !== null ? Number(mParam) : NaN;
|
||||
const yParam = params.yyyy ? Number(params.yyyy) : NaN;
|
||||
const mParam = params.mm ? Number(params.mm) - 1 : NaN;
|
||||
|
||||
const year = Number.isFinite(y) && y >= minYear && y <= 2100 ? y : today.getFullYear();
|
||||
const month = Number.isFinite(m) && m >= 0 && m <= 11 ? m : today.getMonth();
|
||||
const year = Number.isFinite(yParam) && yParam >= minYear && yParam <= 2100 ? yParam : today.getFullYear();
|
||||
const month = Number.isFinite(mParam) && mParam >= 0 && mParam <= 11 ? mParam : today.getMonth();
|
||||
|
||||
const yearMap =
|
||||
rite === '1962' ? await getYear1962(lang, year) : await getYear(lang, year);
|
||||
@@ -348,8 +442,10 @@ export const load: PageServerLoad = async ({ params, url, locals }) => {
|
||||
const todayEntry = todayYearMap.get(todayIso) ?? null;
|
||||
|
||||
let selectedIso: string;
|
||||
if (selectedDateParam && /^\d{4}-\d{2}-\d{2}$/.test(selectedDateParam)) {
|
||||
selectedIso = selectedDateParam;
|
||||
if (params.dd) {
|
||||
const dayNum = Number(params.dd);
|
||||
if (dayNum < 1 || dayNum > daysInMonth) throw error(404, 'Not found');
|
||||
selectedIso = isoFor(year, month, dayNum);
|
||||
} else if (todayEntry && today.getFullYear() === year && today.getMonth() === month) {
|
||||
selectedIso = todayIso;
|
||||
} else {
|
||||
+131
-40
@@ -13,7 +13,7 @@
|
||||
t1962,
|
||||
properLabel,
|
||||
type CalendarLang
|
||||
} from '../calendarI18n';
|
||||
} from '../../../../calendarI18n';
|
||||
|
||||
let { data }: { data: PageData } = $props();
|
||||
|
||||
@@ -41,40 +41,42 @@
|
||||
return (firstDow + 6) % 7;
|
||||
});
|
||||
|
||||
const rite = $derived(data.rite);
|
||||
const wip = $derived(data.wip);
|
||||
const riteSubtitle = $derived(t(rite === '1962' ? 'rite1962Long' : 'rite1969Long', lang));
|
||||
|
||||
function pad(n: number) {
|
||||
return String(n).padStart(2, '0');
|
||||
}
|
||||
|
||||
// URL: /{faithLang}/{calendar}/{rite}/{yyyy}/{mm}/{dd} — rite is a required
|
||||
// path segment so day/month nav stays inside the active rite.
|
||||
const riteBase = $derived(`/${page.params.faithLang}/${page.params.calendar}/${rite}`);
|
||||
const calendarBase = $derived(`/${page.params.faithLang}/${page.params.calendar}`);
|
||||
|
||||
function dayHref(iso: string) {
|
||||
return `?y=${year}&m=${month}&d=${iso}`;
|
||||
const [yy, mm, dd] = iso.split('-');
|
||||
return `${riteBase}/${yy}/${mm}/${dd}`;
|
||||
}
|
||||
|
||||
function monthHref(y: number, m: number) {
|
||||
return `?y=${y}&m=${m}`;
|
||||
return `${riteBase}/${y}/${pad(m + 1)}`;
|
||||
}
|
||||
|
||||
const todayHref = $derived.by(() => {
|
||||
const now = new Date();
|
||||
return `?y=${now.getFullYear()}&m=${now.getMonth()}&d=${todayIso}`;
|
||||
return `${riteBase}/${now.getFullYear()}/${pad(now.getMonth() + 1)}/${pad(now.getDate())}`;
|
||||
});
|
||||
|
||||
const pageTitle = $derived(t('calendar', lang));
|
||||
|
||||
const rite = $derived(data.rite);
|
||||
const wip = $derived(data.wip);
|
||||
const riteSubtitle = $derived(t(rite === '1962' ? 'rite1962Long' : 'rite1969Long', lang));
|
||||
|
||||
function firstOr(arr: string[], fallback = ''): string {
|
||||
return arr && arr.length ? arr[0] : fallback;
|
||||
}
|
||||
|
||||
const calendarBase = $derived(
|
||||
page.url.pathname.replace(/\/(1962|1969)\/?$/, '').replace(/\/$/, '')
|
||||
);
|
||||
|
||||
function riteHref(r: '1969' | '1962') {
|
||||
const seg = r === '1962' ? '' : '/1969';
|
||||
const params = new URLSearchParams();
|
||||
params.set('y', String(year));
|
||||
params.set('m', String(month));
|
||||
if (selectedIso) params.set('d', selectedIso);
|
||||
return `${calendarBase}${seg}?${params.toString()}`;
|
||||
const dd = selectedIso.slice(8, 10);
|
||||
return `${calendarBase}/${r}/${year}/${pad(month + 1)}/${dd}`;
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -311,13 +313,33 @@
|
||||
<h4>{t1962('propers', lang)}</h4>
|
||||
{#each d.propers as section (section.key)}
|
||||
<div class="proper-block">
|
||||
<div class="proper-label">{properLabel(section.key, lang)}</div>
|
||||
<div class="proper-cols" class:single={lang === 'la' || !section.local}>
|
||||
<div class="proper-col proper-col-la" lang="la">{section.la}</div>
|
||||
{#if lang !== 'la' && section.local}
|
||||
<div class="proper-col proper-col-local" lang={lang}>{section.local}</div>
|
||||
{/if}
|
||||
<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>
|
||||
{/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}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/each}
|
||||
</section>
|
||||
@@ -327,13 +349,26 @@
|
||||
<h4>{t1962('extraSections', lang)}</h4>
|
||||
{#each d.extraSections as section (section.key)}
|
||||
<div class="proper-block">
|
||||
<div class="proper-label">{properLabel(section.key, lang)}</div>
|
||||
<div class="proper-cols" class:single={lang === 'la' || !section.local}>
|
||||
<div class="proper-col proper-col-la" lang="la">{section.la}</div>
|
||||
{#if lang !== 'la' && section.local}
|
||||
<div class="proper-col proper-col-local" lang={lang}>{section.local}</div>
|
||||
{/if}
|
||||
<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>
|
||||
{/each}
|
||||
</div>
|
||||
{/each}
|
||||
</section>
|
||||
@@ -372,7 +407,6 @@
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* --- Rite toggle (segmented pill) --- */
|
||||
.rite-toggle {
|
||||
display: inline-flex;
|
||||
gap: 0.25rem;
|
||||
@@ -411,7 +445,6 @@
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
/* --- WIP placeholder --- */
|
||||
.wip {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -441,7 +474,6 @@
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* --- 1962 accuracy disclaimer --- */
|
||||
.disclaimer {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
@@ -471,7 +503,6 @@
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* --- Today hero --- */
|
||||
.today-hero {
|
||||
position: relative;
|
||||
background: var(--color-surface);
|
||||
@@ -547,7 +578,6 @@
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/* --- Month nav --- */
|
||||
.month-nav {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -603,7 +633,6 @@
|
||||
transform: scale(1.03);
|
||||
}
|
||||
|
||||
/* --- Grid --- */
|
||||
.grid {
|
||||
background: var(--color-surface);
|
||||
border-radius: var(--radius-card);
|
||||
@@ -716,7 +745,6 @@
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* --- Detail panel --- */
|
||||
.detail {
|
||||
background: var(--color-surface);
|
||||
border-radius: var(--radius-card);
|
||||
@@ -846,22 +874,80 @@
|
||||
.proper-block {
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
.proper-label-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
.proper-label {
|
||||
font-weight: 600;
|
||||
font-size: 0.8rem;
|
||||
color: var(--color-text-secondary);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.03em;
|
||||
margin-bottom: 0.2rem;
|
||||
}
|
||||
.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;
|
||||
}
|
||||
.proper-segment:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
.proper-segment + .proper-segment {
|
||||
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;
|
||||
gap: 0.75rem;
|
||||
column-gap: 0.75rem;
|
||||
row-gap: 0;
|
||||
align-items: start;
|
||||
}
|
||||
.proper-col-la {
|
||||
grid-column: 1;
|
||||
grid-row: 2;
|
||||
}
|
||||
.proper-col-local {
|
||||
grid-column: 2;
|
||||
grid-row: 2;
|
||||
}
|
||||
.proper-cols.single {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.proper-cols.single .proper-col-la,
|
||||
.proper-cols.single .proper-col-local {
|
||||
grid-column: 1;
|
||||
grid-row: auto;
|
||||
}
|
||||
.proper-col {
|
||||
white-space: pre-wrap;
|
||||
font-size: 0.92rem;
|
||||
@@ -879,9 +965,14 @@
|
||||
.proper-cols {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.proper-cols .proper-col-la,
|
||||
.proper-cols .proper-col-local,
|
||||
.proper-cols .proper-fallback-note {
|
||||
grid-column: 1;
|
||||
grid-row: auto;
|
||||
}
|
||||
}
|
||||
|
||||
/* --- Responsive --- */
|
||||
@media (max-width: 560px) {
|
||||
.cal-wrap {
|
||||
padding: 0.5rem 0.5rem 3rem;
|
||||
@@ -197,11 +197,16 @@ export const ui1962 = {
|
||||
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' }
|
||||
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.'
|
||||
}
|
||||
} as const;
|
||||
|
||||
export function t1962(key: keyof typeof ui1962, lang: CalendarLang): string {
|
||||
return ui1962[key][lang] ?? ui1962[key].en;
|
||||
const entry = ui1962[key] as Record<string, string | undefined>;
|
||||
return entry[lang] ?? entry.en ?? '';
|
||||
}
|
||||
|
||||
const PROPER_LABEL: Record<string, Record<CalendarLang, string>> = {
|
||||
|
||||
Reference in New Issue
Block a user