Add bilingual day-label headers with Latin weekday/season/ordinal vocabulary
Builds every day label as a {en, la} pair (weekdays, ranks, season/ordinal
phrasing authored in Latin; proper names without an authored Latin form
fall back to their English string) and merges the date+day-label into one
header in the day-nav bar.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+18
-3
@@ -1,13 +1,28 @@
|
||||
import { getState, shiftDate, goToToday } from '../app-state';
|
||||
import { formatDateLong } from './format';
|
||||
import { resolveDay } from '../calendar';
|
||||
import { getDayLabel } from '../calendar/day-label';
|
||||
import { formatDateLong, renderBilingual } from './format';
|
||||
|
||||
/** Renders navigation plus the merged "when" header: the civil date/
|
||||
* weekday and the liturgical day-being-celebrated, together in one place
|
||||
* — previously the date lived here while the day-being-celebrated text
|
||||
* was repeated separately inside every hour's own view. The day-being-
|
||||
* celebrated text is hour-independent (the same LiturgicalDay for every
|
||||
* hour on a given date), so it's resolved directly here via `resolveDay`
|
||||
* rather than through any particular hour's `resolveOrdo`. */
|
||||
export function renderDayNav(container: HTMLElement): void {
|
||||
const { date } = getState();
|
||||
const { date, languages } = getState();
|
||||
const day = resolveDay(date);
|
||||
const dateLabel = formatDateLong(date);
|
||||
const dayLabel = getDayLabel(day);
|
||||
container.innerHTML = `
|
||||
<nav class="day-nav" aria-label="Day navigation">
|
||||
<button type="button" class="day-nav-btn" data-action="prev" aria-label="Previous day">←</button>
|
||||
<button type="button" class="day-nav-btn day-nav-today" data-action="today">Today</button>
|
||||
<span class="day-nav-date">${formatDateLong(date)}</span>
|
||||
<div class="day-nav-header">
|
||||
${renderBilingual(dateLabel, languages, 'day-nav-date')}
|
||||
${renderBilingual(dayLabel, languages, 'day-nav-label')}
|
||||
</div>
|
||||
<button type="button" class="day-nav-btn" data-action="next" aria-label="Next day">→</button>
|
||||
</nav>
|
||||
`;
|
||||
|
||||
+50
-5
@@ -1,17 +1,62 @@
|
||||
/** Formats an ISO date as e.g. "Sunday, August 9, 2026" — always in UTC so
|
||||
* it matches calendar/weekday.ts's interpretation regardless of the
|
||||
* viewer's local timezone. */
|
||||
export function formatDateLong(isoDate: string): string {
|
||||
// Latin month names have no Intl locale data, so the genitive forms below
|
||||
// (used the same way English "of August" would be, e.g. "18 Augusti 2026")
|
||||
// are authored directly here.
|
||||
const MONTH_LABELS_LA = [
|
||||
'Ianuarii',
|
||||
'Februarii',
|
||||
'Martii',
|
||||
'Aprilis',
|
||||
'Maii',
|
||||
'Iunii',
|
||||
'Iulii',
|
||||
'Augusti',
|
||||
'Septembris',
|
||||
'Octobris',
|
||||
'Novembris',
|
||||
'Decembris',
|
||||
];
|
||||
|
||||
const WEEKDAY_LABELS_LA = ['Dominica', 'Feria Secunda', 'Feria Tertia', 'Feria Quarta', 'Feria Quinta', 'Feria Sexta', 'Sabbato'];
|
||||
|
||||
/** Formats an ISO date as e.g. "Sunday, August 9, 2026" (`en`) / "Dominica,
|
||||
* die 9 Augusti 2026" (`la`) — always in UTC so it matches
|
||||
* calendar/weekday.ts's interpretation regardless of the viewer's local
|
||||
* timezone. Weekday naming mirrors calendar/day-label.ts's own
|
||||
* `WEEKDAY_LABELS` (spelled-out Latin ferial names, not roman numerals). */
|
||||
export function formatDateLong(isoDate: string): Partial<Record<string, string>> {
|
||||
const date = new Date(`${isoDate}T00:00:00Z`);
|
||||
return new Intl.DateTimeFormat('en-US', {
|
||||
const en = new Intl.DateTimeFormat('en-US', {
|
||||
weekday: 'long',
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
timeZone: 'UTC',
|
||||
}).format(date);
|
||||
|
||||
const day = date.getUTCDate();
|
||||
const month = MONTH_LABELS_LA[date.getUTCMonth()];
|
||||
const year = date.getUTCFullYear();
|
||||
const weekday = WEEKDAY_LABELS_LA[date.getUTCDay()];
|
||||
const la = `${weekday}, die ${day} ${month} ${year}`;
|
||||
|
||||
return { en, la };
|
||||
}
|
||||
|
||||
export function hourLabel(hourId: string): string {
|
||||
return hourId.charAt(0).toUpperCase() + hourId.slice(1);
|
||||
}
|
||||
|
||||
function escapeHtml(value: string): string {
|
||||
return value.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
||||
}
|
||||
|
||||
/** Renders one `<div class="lang-column">` per currently-active language
|
||||
* for a bilingual text map — the same pattern hour-view.ts's own
|
||||
* `renderColumns` uses for resolved liturgical text, shared here so
|
||||
* day-nav.ts's header can reuse it instead of re-implementing it. */
|
||||
export function renderBilingual(text: Partial<Record<string, string>>, languages: readonly string[], className: string): string {
|
||||
const cols = languages
|
||||
.map((lang) => `<div class="lang-column" lang="${lang}">${escapeHtml(text[lang] ?? '')}</div>`)
|
||||
.join('');
|
||||
return `<div class="${className} lang-columns lang-columns-${languages.length}">${cols}</div>`;
|
||||
}
|
||||
|
||||
@@ -149,7 +149,6 @@ export function renderHourView(container: HTMLElement): void {
|
||||
container.innerHTML = `
|
||||
<div class="hour-view">
|
||||
<h2>${hourLabel(selectedHour)}</h2>
|
||||
${ordo.dayLabel ? `<p class="day-label">${escapeHtml(ordo.dayLabel)}</p>` : ''}
|
||||
${ordo.parts.map((part) => renderPart(part, languages)).join('')}
|
||||
</div>
|
||||
`;
|
||||
|
||||
+16
-10
@@ -165,12 +165,26 @@ button:focus-visible {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.day-nav-date {
|
||||
font-weight: 600;
|
||||
.day-nav-header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 0.15rem;
|
||||
min-width: 16rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.day-nav-date {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.day-nav-label {
|
||||
font-family: var(--font-ui);
|
||||
font-size: 0.85rem;
|
||||
font-style: italic;
|
||||
color: var(--color-brass);
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------------
|
||||
Layout: hour list + hour view
|
||||
-------------------------------------------------------------------------- */
|
||||
@@ -261,14 +275,6 @@ button:focus-visible {
|
||||
margin: 0 0 var(--space-1);
|
||||
}
|
||||
|
||||
.day-label {
|
||||
font-family: var(--font-ui);
|
||||
font-size: 0.9em;
|
||||
font-style: italic;
|
||||
color: var(--color-brass);
|
||||
margin: 0 0 var(--space-2);
|
||||
}
|
||||
|
||||
.ordo-part-citation {
|
||||
font-family: var(--font-ui);
|
||||
font-size: 0.85em;
|
||||
|
||||
Reference in New Issue
Block a user