Render Lauds/Matins canticle titles bilingually
Deploy / deploy (push) Successful in 1m20s

English used the same id-derived Latin title (e.g. "Canticum Ezechiae")
for every language. Now English uses the authored name from
lauds-canticles.ts when one exists, falling back to the derived title
for names already conventional untranslated in English (Benedictus,
Magnificat, Nunc Dimittis) and Matins' scripture-ref-derived ids.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HWN869GrMFHqrer9fdCBbF
This commit is contained in:
2026-08-25 06:55:34 -04:00
parent d3ebd90967
commit 2d957ae111
2 changed files with 25 additions and 2 deletions
+9
View File
@@ -34,3 +34,12 @@ export function getCanticle(id: string): Canticle {
} }
return canticle; return canticle;
} }
// Non-throwing lookup for the UI's canticle title — unlike getCanticle,
// callers here may pass ids (nunc-dimittis, benedictus, magnificat, or a
// Matins nocturn-3 scripture-ref-derived id) that this store never holds,
// since only the 7 Lauds weekday OT canticles have an authored English
// `name` distinct from their Latin id.
export function tryGetCanticleName(id: string): string | undefined {
return canticles.get(id)?.name;
}
+16 -2
View File
@@ -2,6 +2,7 @@ import { getState } from '../app-state';
import { resolveOrdo } from '../hours'; import { resolveOrdo } from '../hours';
import type { ResolvedPart, ResolvedText, ResolvedVerse } from '../hours/types'; import type { ResolvedPart, ResolvedText, ResolvedVerse } from '../hours/types';
import { hourLabel } from './format'; import { hourLabel } from './format';
import { tryGetCanticleName } from '../hours/lauds-canticles';
function escapeHtml(value: string): string { function escapeHtml(value: string): string {
return value.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;'); return value.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
@@ -120,14 +121,27 @@ function renderPart(part: ResolvedPart, languages: readonly string[]): string {
`; `;
} }
case 'canticle': { case 'canticle': {
const title = part.canticleId // The Latin title is always the id capitalized (it's already Latin —
// canticum-ezechiae -> "Canticum Ezechiae"). English only matches
// that pattern for names already conventional untranslated in
// English (Benedictus, Magnificat, Nunc Dimittis); the 7 Lauds
// weekday OT canticles have a real English name (e.g. "Canticle of
// Ezechias") authored separately — see lauds-canticles.ts.
const derivedTitle = part.canticleId
.split('-') .split('-')
.map((w) => w.charAt(0).toUpperCase() + w.slice(1)) .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
.join(' '); .join(' ');
const title: Partial<Record<string, string>> = {
la: derivedTitle,
en: tryGetCanticleName(part.canticleId) ?? derivedTitle,
};
const titleCols = languages
.map((lang) => `<span class="lang-column" lang="${lang}">${escapeHtml(title[lang] ?? derivedTitle)}</span>`)
.join('');
return ` return `
<section class="ordo-part ordo-part-canticle"> <section class="ordo-part ordo-part-canticle">
${part.antiphon ? renderColumns(part.antiphon, languages) : ''} ${part.antiphon ? renderColumns(part.antiphon, languages) : ''}
<h3 class="ordo-part-label">${escapeHtml(title)}</h3> <h3 class="ordo-part-label lang-columns lang-columns-${languages.length}">${titleCols}</h3>
${renderCitation(part.text)} ${renderCitation(part.text)}
${renderColumns(part.text, languages)} ${renderColumns(part.text, languages)}
${part.gloriaPatri ? `<div class="ordo-part-gloria-patri">${renderColumns(part.gloriaPatri, languages)}</div>` : ''} ${part.gloriaPatri ? `<div class="ordo-part-gloria-patri">${renderColumns(part.gloriaPatri, languages)}</div>` : ''}