i18n(faith): migrate streak components, BibleModal, katechese notices

Adds streak/angelus and Bible-modal keys to the faith dictionary, plus
the three-fragment "this catechesis is only available in German" notice
used by both katechese pages. Pluralization for day/days handled by two
explicit keys (day_singular/day_plural) chosen at the call site —
Latin's "Dies" is invariant so both keys hold the same string.

StreakCounter and AngelusStreakCounter collapse their per-component
labels objects into direct t.foo lookups; the rosary page's BibleModal
call site now passes the typed `lang` derived value (was data.lang as
plain string, didn't satisfy the tightened FaithLang prop type).

BibleModal isn't actually used in Latin context, but the dict requires
every key in every locale, so reasonable Latin equivalents got filled
in for completeness.
This commit is contained in:
2026-05-01 13:10:29 +02:00
parent 28b96a8dc0
commit 3dcb5c7f2b
10 changed files with 108 additions and 50 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "homepage",
"version": "1.55.0",
"version": "1.55.1",
"private": true,
"type": "module",
"scripts": {
@@ -5,6 +5,7 @@ import StreakAura from '$lib/components/faith/StreakAura.svelte';
import Coffee from '@lucide/svelte/icons/coffee';
import Sun from '@lucide/svelte/icons/sun';
import Moon from '@lucide/svelte/icons/moon';
import { m, type FaithLang } from '$lib/js/faithI18n';
import { tick, onMount } from 'svelte';
let burst = $state(false);
@@ -13,14 +14,13 @@ let selectedSlot = $state<TimeSlot>('morning');
interface Props {
streakData?: { streak: number; lastComplete: string | null; todayPrayed: number; todayDate: string | null } | null;
lang?: 'de' | 'en' | 'la';
lang?: FaithLang;
isLoggedIn?: boolean;
}
let { streakData = null, lang = 'de', isLoggedIn = false }: Props = $props();
const isEnglish = $derived(lang === 'en');
const isLatin = $derived(lang === 'la');
const t = $derived(m[lang]);
// Display values: store when available, SSR fallback
const displayStreak = $derived(store?.streak ?? streakData?.streak ?? 0);
@@ -52,20 +52,12 @@ const slots: { key: TimeSlot; icon: typeof Coffee; color: string }[] = [
{ key: 'evening', icon: Moon, color: 'var(--nord15)' }
];
const labels = $derived({
days: isLatin ? 'Dies' : isEnglish ? (displayStreak === 1 && !showFraction ? 'Day' : 'Days') : (displayStreak === 1 && !showFraction ? 'Tag' : 'Tage'),
pray: isLatin ? 'Oravi' : isEnglish ? 'Prayed' : 'Gebetet',
done: isLatin ? 'Hodie completa' : isEnglish ? 'Done today' : 'Heute fertig',
morning: isLatin ? 'Mane' : isEnglish ? 'Morning' : 'Morgens',
noon: isLatin ? 'Meridie' : isEnglish ? 'Noon' : 'Mittags',
evening: isLatin ? 'Vespere' : isEnglish ? 'Evening' : 'Abends',
ariaLabel: isLatin ? 'Orationem notatam fac' : isEnglish ? 'Mark prayer as prayed' : 'Gebet als gebetet markieren'
});
const dayLabel = $derived(displayStreak === 1 && !showFraction ? t.day_singular : t.day_plural);
const slotLabels: Record<TimeSlot, string> = $derived({
morning: labels.morning,
noon: labels.noon,
evening: labels.evening
morning: t.morning,
noon: t.noon,
evening: t.evening
});
function isSlotPrayed(slot: TimeSlot): boolean {
@@ -105,7 +97,7 @@ async function pray() {
{displayStreak}{#if showFraction}<span class="fraction"><span class="num">{partialCount}</span><span class="slash">/</span><span class="den">3</span></span>{/if}
</span>
</StreakAura>
<span class="streak-label">{labels.days}</span>
<span class="streak-label">{dayLabel}</span>
</div>
<div class="prayer-controls">
@@ -132,12 +124,12 @@ async function pray() {
class="pray-button"
type="submit"
disabled={todayComplete || selectedSlotPrayed}
aria-label={labels.ariaLabel}
aria-label={t.mark_prayer}
>
{#if todayComplete}
{labels.done}
{t.done_today}
{:else}
{labels.pray}
{t.prayed}
{/if}
</button>
</form>
+7 -6
View File
@@ -1,5 +1,6 @@
<script lang="ts">
import type { VerseData } from '$lib/data/mysteryDescriptions';
import { m, type FaithLang } from '$lib/js/faithI18n';
let {
reference = '',
@@ -11,11 +12,11 @@
reference?: string,
title?: string,
verseData?: VerseData | null,
lang?: string,
lang?: FaithLang,
onClose: () => void
} = $props();
const isEnglish = $derived(lang === 'en');
const t = $derived(m[lang]);
// svelte-ignore state_referenced_locally
let book: string = $state(verseData?.book || '');
@@ -25,7 +26,7 @@
let verses: Array<{ verse: number; text: string }> = $state(verseData?.verses || []);
let loading = $state(false);
// svelte-ignore state_referenced_locally
let error = $state(verseData ? '' : (lang === 'en' ? 'No verse data available' : 'Keine Versdaten verfügbar'));
let error = $state(verseData ? '' : m[lang].no_verse_data);
function handleBackdropClick(event: MouseEvent) {
if (event.target === event.currentTarget) {
@@ -57,7 +58,7 @@
{/if}
<p class="modal-reference">{reference}</p>
</div>
<button class="close-button" onclick={onClose} aria-label={isEnglish ? 'Close' : 'Schliessen'}>
<button class="close-button" onclick={onClose} aria-label={t.close}>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
@@ -67,7 +68,7 @@
<div class="modal-body">
{#if loading}
<p class="loading">{isEnglish ? 'Loading...' : 'Lädt...'}</p>
<p class="loading">{t.loading}</p>
{:else if error}
<p class="error">{error}</p>
{:else if verses.length > 0}
@@ -80,7 +81,7 @@
{/each}
</div>
{:else}
<p class="error">{isEnglish ? 'No verses found' : 'Keine Verse gefunden'}</p>
<p class="error">{t.no_verses_found}</p>
{/if}
</div>
</div>
+8 -14
View File
@@ -2,6 +2,7 @@
import { browser } from '$app/environment';
import { getRosaryStreak } from '$lib/stores/rosaryStreak.svelte';
import StreakAura from '$lib/components/faith/StreakAura.svelte';
import { m, type FaithLang } from '$lib/js/faithI18n';
import { tick, onMount } from 'svelte';
let burst = $state(false);
@@ -9,26 +10,19 @@ let streak = $state<ReturnType<typeof getRosaryStreak> | null>(null);
interface Props {
streakData?: { length: number; lastPrayed: string | null } | null;
lang?: 'de' | 'en' | 'la';
lang?: FaithLang;
isLoggedIn?: boolean;
}
let { streakData = null, lang = 'de', isLoggedIn = false }: Props = $props();
const isEnglish = $derived(lang === 'en');
const t = $derived(m[lang]);
// Derive display values: use store when available, fall back to server data for SSR
let displayLength = $derived(streak?.length ?? streakData?.length ?? 0);
let prayedToday = $derived(streak?.prayedToday ?? (streakData?.lastPrayed === new Date().toISOString().split('T')[0]));
// Labels need to come after displayLength since they depend on it
const isLatin = $derived(lang === 'la');
const labels = $derived({
days: isLatin ? (displayLength === 1 ? 'Dies' : 'Dies') : isEnglish ? (displayLength === 1 ? 'Day' : 'Days') : (displayLength === 1 ? 'Tag' : 'Tage'),
prayed: isLatin ? 'Oravi' : isEnglish ? 'Prayed' : 'Gebetet',
prayedToday: isLatin ? 'Hodie oravi' : isEnglish ? 'Prayed today' : 'Heute gebetet',
ariaLabel: isLatin ? 'Orationem notatam fac' : isEnglish ? 'Mark prayer as prayed' : 'Gebet als gebetet markieren'
});
const dayLabel = $derived(displayLength === 1 ? t.day_singular : t.day_plural);
// Initialize store on mount (client-side only)
// Init with server data BEFORE assigning to streak, so displayLength
@@ -50,7 +44,7 @@ async function pray() {
<div class="streak-container" class:no-js-hidden={!isLoggedIn}>
<div class="streak-display">
<StreakAura value={displayLength} {burst} />
<span class="streak-label">{labels.days}</span>
<span class="streak-label">{dayLabel}</span>
</div>
<form method="POST" action="?/pray" onsubmit={(e) => { e.preventDefault(); pray(); }
}>
@@ -58,12 +52,12 @@ async function pray() {
class="streak-button"
type="submit"
disabled={prayedToday}
aria-label={labels.ariaLabel}
aria-label={t.mark_prayer}
>
{#if prayedToday}
{labels.prayedToday}
{t.prayed_today}
{:else}
{labels.prayed}
{t.prayed}
{/if}
</button>
</form>
+23 -1
View File
@@ -19,5 +19,27 @@ export const de = {
alex_pick: "Alex' Wahl",
arguments_title: 'Apologetik',
evidences: 'Belege',
positive_case: 'Positives'
positive_case: 'Positives',
// Streak counters (rosary, angelus)
day_singular: 'Tag',
day_plural: 'Tage',
prayed: 'Gebetet',
prayed_today: 'Heute gebetet',
mark_prayer: 'Gebet als gebetet markieren',
done_today: 'Heute fertig',
morning: 'Morgens',
noon: 'Mittags',
evening: 'Abends',
// Bible modal
close: 'Schliessen',
loading: 'Lädt…',
no_verses_found: 'Keine Verse gefunden',
no_verse_data: 'Keine Versdaten verfügbar',
// Language-availability notice (catechesis is German-only)
only_german_pre: 'Diese Katechese ist nur auf ',
only_german_link: 'Deutsch',
only_german_post: ' verfügbar.'
} as const;
+23 -1
View File
@@ -19,5 +19,27 @@ export const en = {
alex_pick: "Alex's pick",
arguments_title: 'Arguments',
evidences: 'Evidences',
positive_case: 'Positive case'
positive_case: 'Positive case',
// Streak counters
day_singular: 'Day',
day_plural: 'Days',
prayed: 'Prayed',
prayed_today: 'Prayed today',
mark_prayer: 'Mark prayer as prayed',
done_today: 'Done today',
morning: 'Morning',
noon: 'Noon',
evening: 'Evening',
// Bible modal
close: 'Close',
loading: 'Loading…',
no_verses_found: 'No verses found',
no_verse_data: 'No verse data available',
// Language-availability notice (catechesis is German-only)
only_german_pre: 'This catechesis is only available in ',
only_german_link: 'German',
only_german_post: '.'
} as const satisfies Record<keyof typeof de, string>;
+23 -1
View File
@@ -19,5 +19,27 @@ export const la = {
alex_pick: 'Alexandri delectus',
arguments_title: 'Apologia',
evidences: 'Argumenta',
positive_case: 'Argumenta pro'
positive_case: 'Argumenta pro',
// Streak counters — Latin "Dies" is invariant
day_singular: 'Dies',
day_plural: 'Dies',
prayed: 'Oravi',
prayed_today: 'Hodie oravi',
mark_prayer: 'Orationem notatam fac',
done_today: 'Hodie completa',
morning: 'Mane',
noon: 'Meridie',
evening: 'Vespere',
// Bible modal — not used in Latin context but the dict requires every key
close: 'Claude',
loading: 'Carico…',
no_verses_found: 'Versus non inventi',
no_verse_data: 'Nulli versus praesto',
// Language-availability notice (catechesis is German-only)
only_german_pre: 'Haec catechesis tantum in ',
only_german_link: 'lingua Germanica',
only_german_post: ' praesto est.'
} as const satisfies Record<keyof typeof de, string>;
@@ -1156,5 +1156,5 @@ h1 {
<!-- Bible citation modal -->
{#if showModal}
<BibleModal reference={selectedReference} title={selectedTitle} verseData={selectedVerseData} lang={data.lang} onClose={() => showModal = false} />
<BibleModal reference={selectedReference} title={selectedTitle} verseData={selectedVerseData} {lang} onClose={() => showModal = false} />
{/if}
@@ -1,9 +1,12 @@
<script>
import { resolve } from '$app/paths';
import LinksGrid from '$lib/components/LinksGrid.svelte';
import { m } from '$lib/js/faithI18n';
/** @typedef {import('$lib/js/faithI18n').FaithLang} FaithLang */
let { data } = $props();
const isGerman = $derived(data.lang === 'de');
const isLatin = $derived(data.lang === 'la');
const lang = $derived(/** @type {FaithLang} */ (data.lang));
const t = $derived(m[lang]);
const isGerman = $derived(lang === 'de');
</script>
<svelte:head>
@@ -49,7 +52,7 @@
<h1>Katechese</h1>
{#if !isGerman}
<p class="lang-notice">{isLatin ? 'Haec catechesis tantum in ' : 'This catechesis is only available in '}<a href={resolve('/glaube/katechese')}>{isLatin ? 'lingua Germanica' : 'German'}</a>{isLatin ? ' praesto est.' : '.'}</p>
<p class="lang-notice">{t.only_german_pre}<a href={resolve('/glaube/katechese')}>{t.only_german_link}</a>{t.only_german_post}</p>
{/if}
<p>
Aufgearbeitete Lehrinhalte aus dem Glaubenskurs von P. Martin Ramm FSSP.
@@ -5,10 +5,12 @@
import ArrowLeft from '@lucide/svelte/icons/arrow-left';
import { page } from '$app/state';
import ApologetikToc from '$lib/components/faith/ApologetikToc.svelte';
import { m, langFromFaithSlug } from '$lib/js/faithI18n';
/** @type {number | string | null} */
let expanded = $state(null);
const isGerman = $derived(page.url.pathname.startsWith('/glaube'));
const isLatin = $derived(page.url.pathname.startsWith('/fides'));
const lang = $derived(langFromFaithSlug(page.url.pathname.split('/')[1]));
const t = $derived(m[lang]);
const isGerman = $derived(lang === 'de');
/** @param {number | string} id */
function toggle(id) {
@@ -93,7 +95,7 @@
</header>
{#if !isGerman}
<p class="lang-notice">{isLatin ? 'Haec catechesis tantum in ' : 'This catechesis is only available in '}<a href={resolve('/glaube/katechese/zehn-gebote')}>{isLatin ? 'lingua Germanica' : 'German'}</a>{isLatin ? ' praesto est.' : '.'}</p>
<p class="lang-notice">{t.only_german_pre}<a href={resolve('/glaube/katechese/zehn-gebote')}>{t.only_german_link}</a>{t.only_german_post}</p>
{/if}
<section id="ursprung">