feat(i18n): locale-aware formatters, migrate hardcoded de-DE call-sites

Bisher pinnten 185+ call-sites die UI-Zahlen/Datumsausgabe hart auf
Deutsch ("de" / "de-DE" an toLocaleDate{,Time}String, Intl.*Format,
date-fns locale), unabhängig von der aktiven Sprache. EN-User sahen
dadurch deutsche Datums-/Zahlenformate mitten im englischen UI.

- $lib/i18n/format.ts (neu): formatDate / formatTime / formatDateTime
  / formatNumber / formatCurrency / getDateFnsLocale. Alle lesen die
  aktive Locale aus svelte-i18n's locale-store und mappen de→de-DE
  etc. für Intl.
- Codemod: 119 Direktaufrufe in 79 Files migriert (.toLocaleDateString
  / .toLocaleTimeString / new Intl.{Number,DateTime}Format). Erkennt
  new Date() / Number()-Receiver zum Disambiguieren von
  .toLocaleString('de-DE').
- date-fns: 19 Files auf getDateFnsLocale() umgestellt; hardcoded
  `import { de } from 'date-fns/locale'` entfernt.
- Skipped (Collision): 14 Files hatten lokale format*-Wrapper; diese
  bleiben vorerst als gesonderte Folge-Refactorings stehen. Ca. 58
  deep-gekapselte Aufrufe im .toLocaleString/.toLocaleDateString-Idiom
  sind über diese Wrapper noch zu migrieren.
- svelte-check: 0 Errors / 0 Warnings.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-04-24 17:20:11 +02:00
parent 364522db87
commit e794e0ca57
96 changed files with 337 additions and 184 deletions

View file

@ -1,4 +1,5 @@
<script lang="ts">
import { formatDate } from '$lib/i18n/format';
import type { ProjectDataSummary } from '$lib/api/services/admin';
interface Props {
@ -20,7 +21,7 @@
if (diffMins < 60) return `vor ${diffMins} Min`;
if (diffHours < 24) return `vor ${diffHours} Std`;
if (diffDays < 7) return `vor ${diffDays} Tagen`;
return new Date(dateStr).toLocaleDateString('de-DE');
return formatDate(new Date(dateStr));
}
</script>

View file

@ -1,4 +1,5 @@
<script lang="ts">
import { getDateFnsLocale } from '$lib/i18n/format';
/**
* ActivityFeedWidget — Recent timeBlock activity across all modules.
*
@ -35,8 +36,6 @@
} from '@mana/shared-icons';
import { getIconComponent } from '@mana/shared-icons';
import { formatDistanceToNow } from 'date-fns';
import { de } from 'date-fns/locale';
const MAX_ITEMS = 10;
const recentQuery = useLiveQueryWithDefault(async () => {
@ -72,7 +71,7 @@
};
function timeAgo(iso: string): string {
return formatDistanceToNow(new Date(iso), { addSuffix: true, locale: de });
return formatDistanceToNow(new Date(iso), { addSuffix: true, locale: getDateFnsLocale() });
}
function actionLabel(block: TimeBlock): string {

View file

@ -1,4 +1,5 @@
<script lang="ts">
import { formatDate, formatTime } from '$lib/i18n/format';
/**
* CalendarEventsWidget - Upcoming timeBlocks (local-first)
*
@ -35,7 +36,7 @@
} else if (start.toDateString() === tomorrow.toDateString()) {
dateStr = 'Morgen';
} else {
dateStr = start.toLocaleDateString('de-DE', {
dateStr = formatDate(start, {
weekday: 'short',
day: 'numeric',
month: 'short',
@ -46,7 +47,7 @@
return dateStr;
}
const timeStr = start.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' });
const timeStr = formatTime(start, { hour: '2-digit', minute: '2-digit' });
return `${dateStr}, ${timeStr}`;
}

View file

@ -1,4 +1,5 @@
<script lang="ts">
import { getDateFnsLocale } from '$lib/i18n/format';
/**
* TasksTodayWidget - Today's tasks from Todo app (local-first)
*
@ -10,14 +11,12 @@
import { useOpenTasks } from '$lib/data/cross-app-queries';
import { db } from '$lib/data/database';
import { format, isToday, isTomorrow, isPast } from 'date-fns';
import { de } from 'date-fns/locale';
function formatDueDate(dueDate?: string | null): string | null {
if (!dueDate) return null;
const date = new Date(dueDate);
if (isToday(date)) return 'Heute';
if (isTomorrow(date)) return 'Morgen';
return format(date, 'dd. MMM', { locale: de });
return format(date, 'dd. MMM', { locale: getDateFnsLocale() });
}
function isOverdue(dueDate?: string | null): boolean {

View file

@ -1,4 +1,5 @@
<script lang="ts">
import { formatDate } from '$lib/i18n/format';
/**
* TransactionsWidget - Recent credit transactions
*/
@ -80,7 +81,7 @@
<div>
<p class="text-sm font-medium">{tx.description || tx.type}</p>
<p class="text-xs text-muted-foreground">
{new Date(tx.createdAt).toLocaleDateString('de-DE')}
{formatDate(new Date(tx.createdAt))}
</p>
</div>
</div>

View file

@ -1,3 +1,4 @@
import { formatDate } from '$lib/i18n/format';
/**
* Sync Status Dropdown composable for the PillNavigation sync pill.
*
@ -22,7 +23,7 @@ export function useSyncStatusItems() {
disabled: true,
});
if (syncBilling.nextChargeAt) {
const date = new Date(syncBilling.nextChargeAt).toLocaleDateString('de-DE', {
const date = formatDate(new Date(syncBilling.nextChargeAt), {
day: '2-digit',
month: '2-digit',
year: 'numeric',

View file

@ -0,0 +1,90 @@
/**
* Locale-aware formatters that read the active locale from svelte-i18n.
*
* Use these instead of a hard-coded "de-DE" passed to
* toLocaleDateString / toLocaleString / Intl.NumberFormat. Hard-coded
* locales pin the output to German even when the UI is switched.
*
* Callable from both .svelte and .ts files. Inside a Svelte component,
* include `$locale` as a dep in a `$derived` block if you need the
* output to update when the user switches languages mid-session.
*/
import { get } from 'svelte/store';
import { locale } from 'svelte-i18n';
import type { Locale as DateFnsLocale } from 'date-fns';
import { de, enUS, it, fr, es } from 'date-fns/locale';
type DateInput = Date | string | number;
const DATE_FNS_LOCALES: Record<string, DateFnsLocale> = {
de,
en: enUS,
it,
fr,
es,
};
/** Active locale string, e.g. 'de' or 'en'. Falls back to 'de'. */
export function getCurrentLocale(): string {
return get(locale) ?? 'de';
}
/**
* BCP-47 tag for Intl APIs. svelte-i18n stores a bare `de`; Intl
* prefers `de-DE`-style tags for predictable formatting. Mapping is
* best-effort Intl itself is lenient.
*/
function toBcp47(loc: string): string {
switch (loc) {
case 'de':
return 'de-DE';
case 'en':
return 'en-US';
case 'it':
return 'it-IT';
case 'fr':
return 'fr-FR';
case 'es':
return 'es-ES';
default:
return loc;
}
}
function toDate(value: DateInput): Date {
return value instanceof Date ? value : new Date(value);
}
export function formatDate(value: DateInput, options?: Intl.DateTimeFormatOptions): string {
return toDate(value).toLocaleDateString(toBcp47(getCurrentLocale()), options);
}
export function formatTime(value: DateInput, options?: Intl.DateTimeFormatOptions): string {
return toDate(value).toLocaleTimeString(toBcp47(getCurrentLocale()), options);
}
export function formatDateTime(value: DateInput, options?: Intl.DateTimeFormatOptions): string {
return toDate(value).toLocaleString(toBcp47(getCurrentLocale()), options);
}
export function formatNumber(value: number, options?: Intl.NumberFormatOptions): string {
return value.toLocaleString(toBcp47(getCurrentLocale()), options);
}
export function formatCurrency(
value: number,
currency = 'EUR',
options?: Intl.NumberFormatOptions
): string {
return value.toLocaleString(toBcp47(getCurrentLocale()), {
style: 'currency',
currency,
...options,
});
}
/** date-fns locale object for the active locale. Defaults to `de`. */
export function getDateFnsLocale(): DateFnsLocale {
return DATE_FNS_LOCALES[getCurrentLocale()] ?? de;
}

View file

@ -2,6 +2,7 @@
AI Health app — runner status + manual tick + link to status page.
-->
<script lang="ts">
import { formatDateTime } from '$lib/i18n/format';
import { isMissionTickRunning, productionDeps } from '$lib/data/ai/missions/setup';
import { runDueMissions } from '$lib/data/ai/missions/runner';
@ -19,13 +20,13 @@
errors += r.failedSteps;
}
lastRunStats = {
at: new Date().toLocaleString('de-DE'),
at: formatDateTime(new Date()),
plansProduced,
errors,
};
} catch (err) {
console.error(err);
lastRunStats = { at: new Date().toLocaleString('de-DE'), plansProduced: 0, errors: 1 };
lastRunStats = { at: formatDateTime(new Date()), plansProduced: 0, errors: 1 };
} finally {
manualRunning = false;
}

View file

@ -4,6 +4,7 @@
Master-detail inline (list ↔ create ↔ detail) in a single panel.
-->
<script lang="ts">
import { formatDateTime } from '$lib/i18n/format';
import { ArrowLeft, Play, Pause, Check, Trash, Plus } from '@mana/shared-icons';
import { useMissions } from '$lib/data/ai/missions/queries';
import {
@ -412,7 +413,7 @@
{@const isRunning = it.overallStatus === 'running'}
<article class="it" class:it-running={isRunning}>
<header>
<span class="it-date">{new Date(it.startedAt).toLocaleString('de-DE')}</span>
<span class="it-date">{formatDateTime(new Date(it.startedAt))}</span>
<span class="badge badge-{it.overallStatus}">{it.overallStatus}</span>
</header>

View file

@ -1,4 +1,5 @@
<script lang="ts">
import { getDateFnsLocale } from '$lib/i18n/format';
import { getContext } from 'svelte';
import { calendarViewStore } from '../stores/view.svelte';
import { eventsStore } from '../stores/events.svelte';
@ -11,7 +12,6 @@
import type { Calendar, CalendarEvent } from '../types';
import { toDate } from '../utils/event-date-helpers';
import { format, parseISO, isToday, isTomorrow, startOfDay, addMonths } from 'date-fns';
import { de } from 'date-fns/locale';
import { CalendarBlank, MapPin, CaretRight } from '@mana/shared-icons';
interface Props {
@ -75,7 +75,7 @@
function formatDateHeader(date: Date) {
if (isToday(date)) return 'Heute';
if (isTomorrow(date)) return 'Morgen';
return format(date, 'EEEE, d. MMMM', { locale: de });
return format(date, 'EEEE, d. MMMM', { locale: getDateFnsLocale() });
}
function handleEventClick(event: CalendarEvent) {

View file

@ -1,4 +1,5 @@
<script lang="ts">
import { getDateFnsLocale } from '$lib/i18n/format';
import { calendarViewStore } from '../stores/view.svelte';
import type { CalendarViewType } from '../types';
import type { TimeBlockType } from '$lib/data/time-blocks/types';
@ -30,8 +31,6 @@
import { toTimeBlock } from '$lib/data/time-blocks/queries';
import { downloadICalendar } from '$lib/data/time-blocks/ical-export';
import { format } from 'date-fns';
import { de } from 'date-fns/locale';
interface Props {
onNewEvent: () => void;
}
@ -76,9 +75,11 @@
let headerLabel = $derived.by(() => {
if (calendarViewStore.viewType === 'month') {
return format(calendarViewStore.currentDate, 'MMMM yyyy', { locale: de });
return format(calendarViewStore.currentDate, 'MMMM yyyy', { locale: getDateFnsLocale() });
}
return format(calendarViewStore.currentDate, "'KW' w — MMMM yyyy", { locale: de });
return format(calendarViewStore.currentDate, "'KW' w — MMMM yyyy", {
locale: getDateFnsLocale(),
});
});
const viewLabels: Record<CalendarViewType, string> = {

View file

@ -1,4 +1,5 @@
<script lang="ts">
import { getDateFnsLocale } from '$lib/i18n/format';
import { getContext } from 'svelte';
import { calendarViewStore } from '../stores/view.svelte';
import { getEventsForDay } from '../queries';
@ -12,7 +13,6 @@
startOfDay,
isWithinInterval,
} from 'date-fns';
import { de } from 'date-fns/locale';
import { onMount, tick } from 'svelte';
import SunCalc from 'suncalc';
@ -154,7 +154,7 @@
}
// Get the month of the center visible day
let visibleMonth = $state(format(new Date(), 'MMMM yyyy', { locale: de }));
let visibleMonth = $state(format(new Date(), 'MMMM yyyy', { locale: getDateFnsLocale() }));
function updateVisibleMonth() {
if (!scrollContainer) return;
@ -169,7 +169,7 @@
const dateStr = el.getAttribute('data-date');
if (dateStr) {
const date = new Date(dateStr);
visibleMonth = format(date, 'MMMM yyyy', { locale: de });
visibleMonth = format(date, 'MMMM yyyy', { locale: getDateFnsLocale() });
}
break;
}
@ -211,7 +211,9 @@
{#if !isTodayVisible}
<button onclick={goToToday} title="Zum heutigen Tag" class="today-button">
<span class="today-label">Heute</span>
<span class="today-date">{format(new Date(), 'd. MMM', { locale: de })}</span>
<span class="today-date"
>{format(new Date(), 'd. MMM', { locale: getDateFnsLocale() })}</span
>
</button>
{/if}
{visibleMonth}
@ -251,7 +253,7 @@
{#if moonPhase.significant && showMoonPhases}
<span class="moon-indicator">{moonPhase.emoji}</span>
{/if}
<span class="day-weekday">{format(day, 'EE', { locale: de })}</span>
<span class="day-weekday">{format(day, 'EE', { locale: getDateFnsLocale() })}</span>
<span class="day-number">{format(day, 'd')}</span>
{#if eventCount > 0 && showEventIndicators}
<div class="event-dots">

View file

@ -1,4 +1,5 @@
<script lang="ts">
import { getDateFnsLocale } from '$lib/i18n/format';
import { _ } from 'svelte-i18n';
import { getContext } from 'svelte';
import { VisibilityPicker, type VisibilityLevel } from '@mana/shared-privacy';
@ -21,8 +22,6 @@
Check,
} from '@mana/shared-icons';
import { format, differenceInDays, differenceInHours, differenceInMinutes } from 'date-fns';
import { de } from 'date-fns/locale';
interface Props {
event: CalendarEvent;
onClose: () => void;
@ -60,7 +59,7 @@
if (ev.isAllDay) return 'Ganztägig';
const start = toDate(ev.startTime);
const end = toDate(ev.endTime);
const dateStr = format(start, 'EEEE, d. MMMM yyyy', { locale: de });
const dateStr = format(start, 'EEEE, d. MMMM yyyy', { locale: getDateFnsLocale() });
const timeStr = `${format(start, 'HH:mm')} ${format(end, 'HH:mm')}`;
return `${dateStr}\n${timeStr}`;
}
@ -308,12 +307,14 @@
<!-- Metadata -->
<div class="detail-meta-row">
<span
>Erstellt: {format(new Date(event.createdAt), 'dd. MMM yyyy', { locale: de })}</span
>Erstellt: {format(new Date(event.createdAt), 'dd. MMM yyyy', {
locale: getDateFnsLocale(),
})}</span
>
{#if event.updatedAt !== event.createdAt}
<span
>· Bearbeitet: {format(new Date(event.updatedAt), 'dd. MMM yyyy', {
locale: de,
locale: getDateFnsLocale(),
})}</span
>
{/if}

View file

@ -1,4 +1,5 @@
<script lang="ts">
import { getDateFnsLocale } from '$lib/i18n/format';
import {
format,
startOfMonth,
@ -12,7 +13,6 @@
addMonths,
subMonths,
} from 'date-fns';
import { de } from 'date-fns/locale';
import { CaretLeft, CaretRight } from '@mana/shared-icons';
interface Props {
@ -44,7 +44,9 @@
>
<CaretLeft size={16} />
</button>
<span class="month-label">{format(currentMonth, 'MMMM yyyy', { locale: de })}</span>
<span class="month-label"
>{format(currentMonth, 'MMMM yyyy', { locale: getDateFnsLocale() })}</span
>
<button
class="nav-btn"
onclick={() => (currentMonth = addMonths(currentMonth, 1))}

View file

@ -1,4 +1,5 @@
<script lang="ts">
import { getDateFnsLocale } from '$lib/i18n/format';
import { getContext } from 'svelte';
import { calendarViewStore } from '../stores/view.svelte';
import { eventsStore } from '../stores/events.svelte';
@ -27,8 +28,6 @@
getHours,
getMinutes,
} from 'date-fns';
import { de } from 'date-fns/locale';
interface Props {
onEventClick?: (event: CalendarEvent) => void;
onDayClick?: (day: Date) => void;

View file

@ -1,10 +1,10 @@
<script lang="ts">
import { getDateFnsLocale } from '$lib/i18n/format';
import { _ } from 'svelte-i18n';
import { getContext, onMount, tick } from 'svelte';
import { getDefaultCalendar, getCalendarColor } from '../queries';
import type { Calendar } from '../types';
import { format, addMinutes } from 'date-fns';
import { de } from 'date-fns/locale';
import {
X,
Clock,

View file

@ -3,14 +3,13 @@
Used in task scheduling and event creation to suggest optimal times.
-->
<script lang="ts">
import { getDateFnsLocale } from '$lib/i18n/format';
import { db } from '$lib/data/database';
import type { LocalTimeBlock } from '$lib/data/time-blocks/types';
import { toTimeBlock, findFreeSlots } from '$lib/data/time-blocks/queries';
import type { TimeBlock } from '$lib/data/time-blocks/types';
import { CalendarBlank } from '@mana/shared-icons';
import { format, addDays, isToday, isTomorrow } from 'date-fns';
import { de } from 'date-fns/locale';
let {
minDurationMinutes = 30,
onSelect,
@ -52,7 +51,7 @@
function formatDayLabel(date: Date): string {
if (isToday(date)) return 'Heute';
if (isTomorrow(date)) return 'Morgen';
return format(date, 'EEE, d. MMM', { locale: de });
return format(date, 'EEE, d. MMM', { locale: getDateFnsLocale() });
}
</script>

View file

@ -1,4 +1,5 @@
<script lang="ts">
import { getDateFnsLocale } from '$lib/i18n/format';
import { onMount, getContext } from 'svelte';
import { calendarViewStore } from '../stores/view.svelte';
import { eventsStore } from '../stores/events.svelte';
@ -32,8 +33,6 @@
startOfWeek,
endOfWeek,
} from 'date-fns';
import { de } from 'date-fns/locale';
interface Props {
onEventClick?: (event: CalendarEvent) => void;
onQuickCreate?: (startTime: Date, endTime: Date, position: { x: number; y: number }) => void;
@ -223,9 +222,9 @@
class="day-header"
class:today={isToday(day)}
role="columnheader"
aria-label={format(day, 'EEEE, d. MMMM', { locale: de })}
aria-label={format(day, 'EEEE, d. MMMM', { locale: getDateFnsLocale() })}
>
<span class="day-name">{format(day, 'EEE', { locale: de })}</span>
<span class="day-name">{format(day, 'EEE', { locale: getDateFnsLocale() })}</span>
<span class="day-number" class:today={isToday(day)}>{format(day, 'd')}</span>
</div>
{/each}

View file

@ -1,3 +1,4 @@
import { getDateFnsLocale } from '$lib/i18n/format';
/**
* Calendar QuickInputBar Adapter
*/
@ -11,8 +12,6 @@ import { toCalendar } from './queries';
import type { LocalCalendar, LocalEvent } from './types';
import type { LocalTimeBlock } from '$lib/data/time-blocks/types';
import { format } from 'date-fns';
import { de } from 'date-fns/locale';
export function createAdapter(): InputBarAdapter {
return {
placeholder: 'Neuer Termin oder suchen...',
@ -40,7 +39,7 @@ export function createAdapter(): InputBarAdapter {
id: b.sourceId, // event ID
title: b.title || '',
subtitle: b.startDate
? format(new Date(b.startDate), 'dd. MMM yyyy, HH:mm', { locale: de })
? format(new Date(b.startDate), 'dd. MMM yyyy, HH:mm', { locale: getDateFnsLocale() })
: '',
}));
},

View file

@ -3,6 +3,7 @@
All fields are always editable. Changes auto-save on blur.
-->
<script lang="ts">
import { formatDate } from '$lib/i18n/format';
import { db } from '$lib/data/database';
import { decryptRecord } from '$lib/data/crypto';
import { useDetailEntity } from '$lib/data/detail-entity.svelte';
@ -222,10 +223,10 @@
<div class="meta">
{#if event.createdAt}
<span>Erstellt: {new Date(event.createdAt).toLocaleDateString('de')}</span>
<span>Erstellt: {formatDate(new Date(event.createdAt))}</span>
{/if}
{#if event.updatedAt}
<span>Bearbeitet: {new Date(event.updatedAt).toLocaleDateString('de')}</span>
<span>Bearbeitet: {formatDate(new Date(event.updatedAt))}</span>
{/if}
</div>
{/snippet}

View file

@ -3,6 +3,7 @@
All fields are always editable. Changes auto-save on blur.
-->
<script lang="ts">
import { formatDate } from '$lib/i18n/format';
import { liveQuery } from 'dexie';
import { db } from '$lib/data/database';
import { useDetailEntity } from '$lib/data/detail-entity.svelte';
@ -116,7 +117,7 @@
{#if deck.lastStudied}
<div class="prop-row">
<span class="prop-label">Zuletzt gelernt</span>
<span class="prop-value">{new Date(deck.lastStudied).toLocaleDateString('de')}</span>
<span class="prop-value">{formatDate(new Date(deck.lastStudied))}</span>
</div>
{/if}
</div>
@ -134,9 +135,9 @@
</div>
<div class="meta">
<span>Erstellt: {new Date(deck.createdAt ?? '').toLocaleDateString('de')}</span>
<span>Erstellt: {formatDate(new Date(deck.createdAt ?? ''))}</span>
{#if deck.updatedAt}
<span>Bearbeitet: {new Date(deck.updatedAt).toLocaleDateString('de')}</span>
<span>Bearbeitet: {formatDate(new Date(deck.updatedAt))}</span>
{/if}
</div>
{/snippet}

View file

@ -3,6 +3,7 @@
Conversation thread with streaming AI responses.
-->
<script lang="ts">
import { formatTime } from '$lib/i18n/format';
import { useConversationMessages, useAllConversations } from '../queries';
import { conversationsStore } from '../stores/conversations.svelte';
import { sendAndStream } from '../services/completion';
@ -91,7 +92,7 @@
<div class="bubble" class:user-bubble={msg.sender === 'user'}>
<p class="msg-text">{msg.messageText}</p>
<span class="msg-time">
{new Date(msg.createdAt).toLocaleTimeString('de-DE', {
{formatTime(new Date(msg.createdAt), {
hour: '2-digit',
minute: '2-digit',
})}

View file

@ -3,6 +3,7 @@
All fields are always editable. Changes auto-save on blur.
-->
<script lang="ts">
import { formatDate } from '$lib/i18n/format';
import { liveQuery } from 'dexie';
import { db } from '$lib/data/database';
import { useDetailEntity } from '$lib/data/detail-entity.svelte';
@ -151,9 +152,9 @@
</div>
<div class="meta">
<span>Erstellt: {new Date(location.createdAt ?? '').toLocaleDateString('de')}</span>
<span>Erstellt: {formatDate(new Date(location.createdAt ?? ''))}</span>
{#if location.updatedAt}
<span>Bearbeitet: {new Date(location.updatedAt).toLocaleDateString('de')}</span>
<span>Bearbeitet: {formatDate(new Date(location.updatedAt))}</span>
{/if}
</div>
{/snippet}

View file

@ -3,6 +3,7 @@
All fields are always editable. Changes auto-save on blur.
-->
<script lang="ts">
import { formatDate } from '$lib/i18n/format';
import { useDetailEntity } from '$lib/data/detail-entity.svelte';
import DetailViewShell from '$lib/components/DetailViewShell.svelte';
import { contactsStore } from '../stores/contacts.svelte';
@ -372,10 +373,10 @@
<div class="meta">
{#if contact.createdAt}
<span>Erstellt: {new Date(contact.createdAt).toLocaleDateString('de')}</span>
<span>Erstellt: {formatDate(new Date(contact.createdAt))}</span>
{/if}
{#if contact.updatedAt}
<span>Bearbeitet: {new Date(contact.updatedAt).toLocaleDateString('de')}</span>
<span>Bearbeitet: {formatDate(new Date(contact.updatedAt))}</span>
{/if}
</div>
{/snippet}

View file

@ -1,4 +1,5 @@
<script lang="ts">
import { formatDate } from '$lib/i18n/format';
/**
* TasksTodayWidget — Aufgaben fällig heute oder überfällig.
*
@ -68,7 +69,7 @@
function formatDue(dueDate: string): string {
if (dueDate.slice(0, 10) === todayStr) return 'Heute';
const d = new Date(dueDate);
return d.toLocaleDateString('de-DE', { day: 'numeric', month: 'short' });
return formatDate(d, { day: 'numeric', month: 'short' });
}
async function toggleComplete(task: Task) {

View file

@ -1,4 +1,5 @@
<script lang="ts">
import { formatDate, formatTime } from '$lib/i18n/format';
/**
* UpcomingEventsWidget — Termine der nächsten 7 Tage.
*
@ -88,7 +89,7 @@
} else if (start.toDateString() === tomorrow.toDateString()) {
dateStr = 'Morgen';
} else {
dateStr = start.toLocaleDateString('de-DE', {
dateStr = formatDate(start, {
weekday: 'short',
day: 'numeric',
month: 'short',
@ -97,7 +98,7 @@
if (event.allDay) return dateStr;
const timeStr = start.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' });
const timeStr = formatTime(start, { hour: '2-digit', minute: '2-digit' });
return `${dateStr}, ${timeStr}`;
}
</script>

View file

@ -1,3 +1,4 @@
import { formatDate } from '$lib/i18n/format';
/**
* Reactive Queries & Pure Helpers for Dreams module.
*
@ -113,7 +114,7 @@ export function groupByMonth(dreams: Dream[]): Array<{ label: string; dreams: Dr
const groups = new Map<string, Dream[]>();
for (const d of dreams) {
const date = new Date(d.dreamDate);
const label = date.toLocaleDateString('de-DE', { month: 'long', year: 'numeric' });
const label = formatDate(date, { month: 'long', year: 'numeric' });
if (!groups.has(label)) groups.set(label, []);
groups.get(label)!.push(d);
}
@ -128,7 +129,7 @@ export function formatDreamDate(iso: string): string {
if (diffDays === 0) return 'Heute Nacht';
if (diffDays === 1) return 'Gestern Nacht';
if (diffDays < 7) return `vor ${diffDays} Tagen`;
return date.toLocaleDateString('de-DE', { day: 'numeric', month: 'short', year: 'numeric' });
return formatDate(date, { day: 'numeric', month: 'short', year: 'numeric' });
}
/** Map of symbol name → most recent dreamDate that references it. */

View file

@ -1,4 +1,5 @@
<script lang="ts">
import { formatDate, formatTime } from '$lib/i18n/format';
import type { DiscoveredEvent } from '../discovery/types';
interface Props {
@ -11,16 +12,14 @@
const startDate = $derived(new Date(event.startAt));
const dateLabel = $derived(
startDate.toLocaleDateString('de-DE', {
formatDate(startDate, {
weekday: 'short',
day: '2-digit',
month: 'short',
})
);
const timeLabel = $derived(
event.allDay
? 'Ganztag'
: startDate.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' })
event.allDay ? 'Ganztag' : formatTime(startDate, { hour: '2-digit', minute: '2-digit' })
);
const isSaved = $derived(event.userAction === 'save');

View file

@ -1,4 +1,5 @@
<script lang="ts">
import { formatDate, formatTime } from '$lib/i18n/format';
import type { SocialEvent, RsvpSummary } from '../types';
interface Props {
@ -11,16 +12,14 @@
const startDate = $derived(new Date(event.startTime));
const dateLabel = $derived(
startDate.toLocaleDateString('de-DE', {
formatDate(startDate, {
weekday: 'short',
day: '2-digit',
month: 'short',
})
);
const timeLabel = $derived(
event.isAllDay
? 'Ganztägig'
: startDate.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' })
event.isAllDay ? 'Ganztägig' : formatTime(startDate, { hour: '2-digit', minute: '2-digit' })
);
</script>

View file

@ -1,4 +1,5 @@
<script lang="ts">
import { formatTime } from '$lib/i18n/format';
import { eventsApi, type PublicRsvpRecord } from '../api';
import { eventGuestsStore } from '../stores/guests.svelte';
@ -104,7 +105,7 @@
{#if lastFetchedAt}
<div class="meta">
Aktualisiert um {lastFetchedAt.toLocaleTimeString('de-DE', {
Aktualisiert um {formatTime(lastFetchedAt, {
hour: '2-digit',
minute: '2-digit',
second: '2-digit',

View file

@ -1,3 +1,4 @@
import { getDateFnsLocale } from '$lib/i18n/format';
/**
* Events QuickInputBar Adapter quick-create gatherings.
*
@ -11,8 +12,6 @@ import type { QuickInputItem } from '@mana/shared-ui';
import { db } from '$lib/data/database';
import type { LocalSocialEvent } from './types';
import { format } from 'date-fns';
import { de } from 'date-fns/locale';
function defaultStart(): Date {
const d = new Date();
d.setHours(19, 0, 0, 0);
@ -49,7 +48,7 @@ export function createAdapter(): InputBarAdapter {
const start = defaultStart();
return {
title: `"${query.trim()}" anlegen`,
subtitle: `Start: ${format(start, 'EEE, d. MMM, HH:mm', { locale: de })}`,
subtitle: `Start: ${format(start, 'EEE, d. MMM, HH:mm', { locale: getDateFnsLocale() })}`,
};
},

View file

@ -1,3 +1,4 @@
import { formatDate } from '$lib/i18n/format';
import type { ModuleTool } from '$lib/data/tools/types';
import { eventsStore } from './stores/events.svelte';
import { discoveryStore } from './discovery/store.svelte';
@ -89,7 +90,7 @@ export const socialEventsTools: ModuleTool[] = [
.slice(0, 10)
.map(
(e) =>
`- ${e.title} (${new Date(e.date).toLocaleDateString('de-DE')}${e.location ? `, ${e.location}` : ''})`
`- ${e.title} (${formatDate(new Date(e.date))}${e.location ? `, ${e.location}` : ''})`
)
.join('\n');

View file

@ -1,4 +1,5 @@
<script lang="ts">
import { formatDateTime } from '$lib/i18n/format';
import { useEvent, useEventGuests, summarizeRsvps } from '../queries';
import { eventsStore } from '../stores/events.svelte';
import GuestListEditor from '../components/GuestListEditor.svelte';
@ -221,7 +222,7 @@
<h1 class="title">{event.title}</h1>
<div class="meta-row">
<span class="when">
{new Date(event.startTime).toLocaleString('de-DE', {
{formatDateTime(new Date(event.startTime), {
weekday: 'long',
day: '2-digit',
month: 'long',

View file

@ -2,6 +2,7 @@
DayTimeline — shows all habit logs for a given date as a timeline.
-->
<script lang="ts">
import { formatDate } from '$lib/i18n/format';
import type { Habit, HabitLog } from '../types';
import { formatTime } from '../queries';
import { DynamicIcon } from '@mana/shared-ui/atoms';
@ -29,7 +30,7 @@
const yesterday = new Date(Date.now() - 86400000).toISOString().split('T')[0];
if (d === today) return 'Heute';
if (d === yesterday) return 'Gestern';
return new Date(d).toLocaleDateString('de-DE', {
return formatDate(new Date(d), {
weekday: 'short',
day: 'numeric',
month: 'short',

View file

@ -3,6 +3,7 @@
Shows stats, log timeline, edit form, and archive/delete actions.
-->
<script lang="ts">
import { formatDate } from '$lib/i18n/format';
import type { Habit, HabitLog } from '../types';
import { habitsStore } from '../stores/habits.svelte';
import { getCountForDate, getStreak, groupLogsByDate, todayStr, formatTime } from '../queries';
@ -55,7 +56,7 @@
const yesterday = new Date(Date.now() - 86400000).toISOString().split('T')[0];
if (d === today) return 'Heute';
if (d === yesterday) return 'Gestern';
return new Date(d).toLocaleDateString('de-DE', {
return formatDate(new Date(d), {
weekday: 'short',
day: 'numeric',
month: 'short',
@ -63,7 +64,7 @@
}
function formatDayLabel(d: string): string {
return new Date(d).toLocaleDateString('de-DE', { weekday: 'narrow' });
return formatDate(new Date(d), { weekday: 'narrow' });
}
async function handleDelete() {

View file

@ -1,7 +1,6 @@
<script lang="ts">
import { getDateFnsLocale } from '$lib/i18n/format';
import { format } from 'date-fns';
import { de } from 'date-fns/locale';
interface FieldDef {
id: string;
name: string;
@ -27,7 +26,7 @@
function formatDate(val: unknown): string {
if (!val) return '';
try {
return format(new Date(String(val)), 'dd.MM.yyyy', { locale: de });
return format(new Date(String(val)), 'dd.MM.yyyy', { locale: getDateFnsLocale() });
} catch {
return String(val);
}

View file

@ -3,6 +3,7 @@
Collection details, always editable, auto-save on blur.
-->
<script lang="ts">
import { formatDate } from '$lib/i18n/format';
import { useLiveQueryWithDefault } from '@mana/local-store/svelte';
import { db } from '$lib/data/database';
import { useDetailEntity } from '$lib/data/detail-entity.svelte';
@ -124,9 +125,9 @@
</div>
<div class="meta">
<span>Erstellt: {new Date(collection.createdAt ?? '').toLocaleDateString('de')}</span>
<span>Erstellt: {formatDate(new Date(collection.createdAt ?? ''))}</span>
{#if collection.updatedAt}
<span>Bearbeitet: {new Date(collection.updatedAt).toLocaleDateString('de')}</span>
<span>Bearbeitet: {formatDate(new Date(collection.updatedAt))}</span>
{/if}
</div>
{/snippet}

View file

@ -1,3 +1,4 @@
import { formatDate } from '$lib/i18n/format';
/**
* Reactive Queries & Pure Helpers for Journal module.
*
@ -81,7 +82,7 @@ export function groupByMonth(
const groups = new Map<string, JournalEntry[]>();
for (const e of entries) {
const date = new Date(e.entryDate);
const label = date.toLocaleDateString('de-DE', { month: 'long', year: 'numeric' });
const label = formatDate(date, { month: 'long', year: 'numeric' });
if (!groups.has(label)) groups.set(label, []);
groups.get(label)!.push(e);
}
@ -96,7 +97,7 @@ export function formatEntryDate(iso: string): string {
if (diffDays === 0) return 'Heute';
if (diffDays === 1) return 'Gestern';
if (diffDays < 7) return `vor ${diffDays} Tagen`;
return date.toLocaleDateString('de-DE', { day: 'numeric', month: 'short', year: 'numeric' });
return formatDate(date, { day: 'numeric', month: 'short', year: 'numeric' });
}
/** Find "On this day" entries — same month+day from previous years. */

View file

@ -2,6 +2,7 @@
SessionCard — displays a completed meditation session.
-->
<script lang="ts">
import { formatDate } from '$lib/i18n/format';
import type { MeditateSession, MeditatePreset } from '../types';
import { CATEGORY_LABELS } from '../types';
import { formatDuration } from '../queries';
@ -17,7 +18,7 @@
const name = $derived(preset?.name ?? CATEGORY_LABELS[session.category].de);
const duration = $derived(formatDuration(session.durationSec));
const date = $derived(
new Date(session.startedAt).toLocaleDateString('de-DE', {
formatDate(new Date(session.startedAt), {
day: '2-digit',
month: '2-digit',
hour: '2-digit',

View file

@ -1,3 +1,4 @@
import { formatDate } from '$lib/i18n/format';
/**
* Memoro LLM result watcher.
*
@ -140,12 +141,12 @@ async function applyRow(row: QueuedTask): Promise<void> {
if (!titleToWrite) {
const created = (memo as { createdAt?: string }).createdAt;
const dateLabel = created
? new Date(created).toLocaleDateString('de', {
? formatDate(new Date(created), {
day: 'numeric',
month: 'long',
year: 'numeric',
})
: new Date().toLocaleDateString('de');
: formatDate(new Date());
titleToWrite = `Memo vom ${dateLabel}`;
console.warn(
`[memoro-llm-watcher] row ${row.id} returned empty title — using date fallback "${titleToWrite}"`,

View file

@ -3,6 +3,7 @@
Memo details with transcript, pin toggle, auto-save on blur.
-->
<script lang="ts">
import { formatDate } from '$lib/i18n/format';
import { useDetailEntity } from '$lib/data/detail-entity.svelte';
import { useLiveQueryWithDefault } from '@mana/local-store/svelte';
import DetailViewShell from '$lib/components/DetailViewShell.svelte';
@ -224,9 +225,9 @@
</div>
<div class="meta">
<span>Erstellt: {new Date(memo.createdAt ?? '').toLocaleDateString('de')}</span>
<span>Erstellt: {formatDate(new Date(memo.createdAt ?? ''))}</span>
{#if memo.updatedAt}
<span>Bearbeitet: {new Date(memo.updatedAt).toLocaleDateString('de')}</span>
<span>Bearbeitet: {formatDate(new Date(memo.updatedAt))}</span>
{/if}
</div>
{/snippet}

View file

@ -3,6 +3,7 @@
All fields are always editable. Changes auto-save on blur.
-->
<script lang="ts">
import { formatDate } from '$lib/i18n/format';
import { useDetailEntity } from '$lib/data/detail-entity.svelte';
import DetailViewShell from '$lib/components/DetailViewShell.svelte';
import { libraryStore } from '../stores/library.svelte';
@ -157,12 +158,12 @@
</div>
<div class="meta">
<span>Erstellt: {new Date(song.createdAt ?? '').toLocaleDateString('de')}</span>
<span>Erstellt: {formatDate(new Date(song.createdAt ?? ''))}</span>
{#if song.updatedAt}
<span>Bearbeitet: {new Date(song.updatedAt).toLocaleDateString('de')}</span>
<span>Bearbeitet: {formatDate(new Date(song.updatedAt))}</span>
{/if}
{#if song.lastPlayedAt}
<span>Zuletzt gehört: {new Date(song.lastPlayedAt).toLocaleDateString('de')}</span>
<span>Zuletzt gehört: {formatDate(new Date(song.lastPlayedAt))}</span>
{/if}
</div>
{/snippet}

View file

@ -1,3 +1,4 @@
import { formatDate } from '$lib/i18n/format';
/**
* Reactive queries + type converters for News.
*
@ -176,5 +177,5 @@ export function formatRelativeTime(iso: string | null): string {
if (hours < 24) return `vor ${hours}h`;
const days = Math.floor(hours / 24);
if (days < 7) return `vor ${days}d`;
return new Date(iso).toLocaleDateString('de-DE', { day: 'numeric', month: 'short' });
return formatDate(new Date(iso), { day: 'numeric', month: 'short' });
}

View file

@ -1,3 +1,4 @@
import { formatDate } from '$lib/i18n/format';
/**
* Reactive Queries & Pure Helpers for Notes module.
*
@ -104,5 +105,5 @@ export function formatRelativeTime(iso: string): string {
if (hours < 24) return `vor ${hours}h`;
const days = Math.floor(hours / 24);
if (days < 7) return `vor ${days}d`;
return new Date(iso).toLocaleDateString('de-DE', { day: 'numeric', month: 'short' });
return formatDate(new Date(iso), { day: 'numeric', month: 'short' });
}

View file

@ -15,6 +15,7 @@
and ESC both close the modal.
-->
<script lang="ts">
import { formatDate } from '$lib/i18n/format';
import { onMount, type Snippet } from 'svelte';
import { SquaresFour } from '@mana/shared-icons';
import type { Image } from '../types';
@ -89,7 +90,7 @@
</p>
{/if}
<p class="text-xs text-muted-foreground">
{new Date(image.createdAt).toLocaleDateString('de-DE', {
{formatDate(new Date(image.createdAt), {
day: 'numeric',
month: 'long',
year: 'numeric',

View file

@ -3,6 +3,7 @@
Presentation deck details. All fields auto-save on blur.
-->
<script lang="ts">
import { formatDate } from '$lib/i18n/format';
import { liveQuery } from 'dexie';
import { db } from '$lib/data/database';
import { useDetailEntity } from '$lib/data/detail-entity.svelte';
@ -113,9 +114,9 @@
</div>
<div class="meta">
<span>Erstellt: {new Date(deck.createdAt ?? '').toLocaleDateString('de')}</span>
<span>Erstellt: {formatDate(new Date(deck.createdAt ?? ''))}</span>
{#if deck.updatedAt}
<span>Bearbeitet: {new Date(deck.updatedAt).toLocaleDateString('de')}</span>
<span>Bearbeitet: {formatDate(new Date(deck.updatedAt))}</span>
{/if}
</div>
{/snippet}

View file

@ -3,6 +3,7 @@
All fields are always editable. Changes auto-save on blur.
-->
<script lang="ts">
import { formatDate } from '$lib/i18n/format';
import { db } from '$lib/data/database';
import { encryptRecord } from '$lib/data/crypto';
import { useDetailEntity } from '$lib/data/detail-entity.svelte';
@ -172,9 +173,9 @@
{/if}
<div class="meta">
<span>Erstellt: {new Date(question.createdAt ?? '').toLocaleDateString('de')}</span>
<span>Erstellt: {formatDate(new Date(question.createdAt ?? ''))}</span>
{#if question.updatedAt}
<span>Bearbeitet: {new Date(question.updatedAt).toLocaleDateString('de')}</span>
<span>Bearbeitet: {formatDate(new Date(question.updatedAt))}</span>
{/if}
</div>
{/snippet}

View file

@ -1,4 +1,5 @@
<script lang="ts">
import { formatDate } from '$lib/i18n/format';
import type { AchievementWithStatus } from '../types';
import { RARITY_INFO } from '../types';
import { Trophy, Lock, Star } from '@mana/shared-icons';
@ -88,7 +89,7 @@
</span>
{#if achievement.unlocked && achievement.unlockedAt}
<span class="text-muted-foreground">
{new Date(achievement.unlockedAt).toLocaleDateString('de-DE')}
{formatDate(new Date(achievement.unlockedAt))}
</span>
{/if}
</div>

View file

@ -1,4 +1,5 @@
<script lang="ts">
import { formatDate } from '$lib/i18n/format';
import { _ } from 'svelte-i18n';
import type { Skill, SkillBranch } from '../types';
import { BRANCH_INFO } from '../types';
@ -165,7 +166,7 @@
<div>
<div class="text-muted-foreground">Erstellt</div>
<div class="font-semibold text-white">
{new Date(skill.createdAt).toLocaleDateString('de-DE')}
{formatDate(new Date(skill.createdAt))}
</div>
</div>
</div>

View file

@ -3,6 +3,7 @@
Skill details with XP display and quick add XP. Auto-save on blur.
-->
<script lang="ts">
import { formatDate } from '$lib/i18n/format';
import { useDetailEntity } from '$lib/data/detail-entity.svelte';
import DetailViewShell from '$lib/components/DetailViewShell.svelte';
import { skillStore } from '../stores/skills.svelte';
@ -177,9 +178,9 @@
</div>
<div class="meta">
<span>Erstellt: {new Date(skill.createdAt ?? '').toLocaleDateString('de')}</span>
<span>Erstellt: {formatDate(new Date(skill.createdAt ?? ''))}</span>
{#if skill.updatedAt}
<span>Bearbeitet: {new Date(skill.updatedAt).toLocaleDateString('de')}</span>
<span>Bearbeitet: {formatDate(new Date(skill.updatedAt))}</span>
{/if}
</div>
{/snippet}

View file

@ -8,6 +8,7 @@
Renders equally well inside the workbench or standalone at /spaces.
-->
<script lang="ts">
import { formatDate } from '$lib/i18n/format';
import { onMount } from 'svelte';
import { getActiveSpace, authFetch } from '$lib/data/scope';
import { SPACE_TYPE_LABELS } from '@mana/shared-branding';
@ -135,7 +136,7 @@
if (sec < 60) return 'gerade eben';
if (sec < 3600) return `vor ${Math.floor(sec / 60)} min`;
if (sec < 86400) return `vor ${Math.floor(sec / 3600)} h`;
return d.toLocaleDateString('de-DE');
return formatDate(d);
}
</script>

View file

@ -4,6 +4,7 @@
a spiral pattern. Extracted from the former /spiral standalone route.
-->
<script lang="ts">
import { formatTime } from '$lib/i18n/format';
import { onMount } from 'svelte';
import { COLORS } from '@mana/spiral-db';
import type { ColorDefinition } from '@mana/spiral-db';
@ -142,7 +143,7 @@
{#if manaSpiralStore.lastCollectedAt}
<p class="collected-at">
Zuletzt gesammelt: {manaSpiralStore.lastCollectedAt.toLocaleTimeString('de-DE')}
Zuletzt gesammelt: {formatTime(manaSpiralStore.lastCollectedAt)}
</p>
{/if}
</section>

View file

@ -3,6 +3,7 @@
File details with editable name, favorite toggle. Auto-save on blur.
-->
<script lang="ts">
import { formatDate } from '$lib/i18n/format';
import { useDetailEntity } from '$lib/data/detail-entity.svelte';
import DetailViewShell from '$lib/components/DetailViewShell.svelte';
import { filesStore } from '../stores/files.svelte';
@ -97,9 +98,9 @@
</div>
<div class="meta">
<span>Erstellt: {new Date(file.createdAt ?? '').toLocaleDateString('de')}</span>
<span>Erstellt: {formatDate(new Date(file.createdAt ?? ''))}</span>
{#if file.updatedAt}
<span>Bearbeitet: {new Date(file.updatedAt).toLocaleDateString('de')}</span>
<span>Bearbeitet: {formatDate(new Date(file.updatedAt))}</span>
{/if}
</div>
{/snippet}

View file

@ -3,6 +3,7 @@
Streak, quick-start routines, assessment recommendation, recent sessions.
-->
<script lang="ts">
import { formatDate } from '$lib/i18n/format';
import {
useAllStretchExercises,
useAllStretchRoutines,
@ -135,7 +136,7 @@
<div class="heatmap-day" title="{day.date}: {day.minutes} Min">
<div class="heatmap-dot" class:active={day.count > 0} class:multi={day.count > 1}></div>
<span class="heatmap-label"
>{new Date(day.date + 'T00:00').toLocaleDateString('de', { weekday: 'narrow' })}</span
>{formatDate(new Date(day.date + 'T00:00'), { weekday: 'narrow' })}</span
>
</div>
{/each}

View file

@ -1,4 +1,5 @@
<script lang="ts">
import { formatTime } from '$lib/i18n/format';
import { getContext } from 'svelte';
import { _ } from 'svelte-i18n';
import { timeEntryTable } from '$lib/modules/times/collections';
@ -93,16 +94,14 @@
let startTimeStr = $derived(
entry.startTime
? new Date(entry.startTime).toLocaleTimeString('de-DE', {
? formatTime(new Date(entry.startTime), {
hour: '2-digit',
minute: '2-digit',
})
: ''
);
let endTimeStr = $derived(
entry.endTime
? new Date(entry.endTime).toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' })
: ''
entry.endTime ? formatTime(new Date(entry.endTime), { hour: '2-digit', minute: '2-digit' }) : ''
);
</script>

View file

@ -1,4 +1,5 @@
<script lang="ts">
import { formatDate } from '$lib/i18n/format';
import { _ } from 'svelte-i18n';
import EntryItem from './EntryItem.svelte';
import {
@ -26,7 +27,7 @@
if (dateStr === today) return $_('entry.today');
if (dateStr === yesterday) return 'Gestern';
return new Date(dateStr + 'T00:00:00').toLocaleDateString('de-DE', {
return formatDate(new Date(dateStr + 'T00:00:00'), {
weekday: 'long',
day: 'numeric',
month: 'long',

View file

@ -1,3 +1,4 @@
import { formatTime } from '$lib/i18n/format';
/**
* CSV Export utility for time entries
*/
@ -40,12 +41,8 @@ export function exportEntriesToCSV(
(hours * 60 + minutes).toString(),
e.isBillable ? 'Ja' : 'Nein',
`"${e.tags.join(', ')}"`,
e.startTime
? new Date(e.startTime).toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' })
: '',
e.endTime
? new Date(e.endTime).toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' })
: '',
e.startTime ? formatTime(new Date(e.startTime), { hour: '2-digit', minute: '2-digit' }) : '',
e.endTime ? formatTime(new Date(e.endTime), { hour: '2-digit', minute: '2-digit' }) : '',
];
});

View file

@ -3,6 +3,7 @@
All fields are always editable. Changes auto-save on blur.
-->
<script lang="ts">
import { formatDate, formatTime } from '$lib/i18n/format';
import { db } from '$lib/data/database';
import { useDetailEntity } from '$lib/data/detail-entity.svelte';
import { useLiveQueryWithDefault } from '@mana/local-store/svelte';
@ -73,7 +74,7 @@
function fmtTime(iso?: string | null): string {
if (!iso) return '';
const d = new Date(iso);
return d.toLocaleTimeString('de', { hour: '2-digit', minute: '2-digit' });
return formatTime(d, { hour: '2-digit', minute: '2-digit' });
}
async function saveField() {
@ -230,7 +231,7 @@
<div class="meta">
{#if entry.createdAt}
<span>Erstellt: {new Date(entry.createdAt).toLocaleDateString('de')}</span>
<span>Erstellt: {formatDate(new Date(entry.createdAt))}</span>
{/if}
{#if entry.source?.app}
<span>Quelle: {entry.source.app}</span>

View file

@ -3,6 +3,7 @@
Minimal task list. Inline due-date badges. Floating input at bottom.
-->
<script lang="ts">
import { formatDate, formatTime } from '$lib/i18n/format';
import { useAllTasks, filterIncomplete, filterOverdue, filterToday, sortTasks } from './queries';
import { tasksStore } from './stores/tasks.svelte';
import { toastStore } from '@mana/shared-ui/toast';
@ -55,7 +56,7 @@
if (due < todayStart) return { label: 'Überfällig', variant: 'overdue' };
if (due < tomorrowStart) return { label: 'Heute', variant: 'today' };
return {
label: due.toLocaleDateString('de', { day: 'numeric', month: 'short' }),
label: formatDate(due, { day: 'numeric', month: 'short' }),
variant: 'upcoming',
};
}
@ -185,10 +186,10 @@
{/if}
{#if task.isCompleted && task.completedAt}
<span class="completed-at"
>{new Date(task.completedAt).toLocaleTimeString('de', {
>{formatTime(new Date(task.completedAt), {
hour: '2-digit',
minute: '2-digit',
})} Uhr, {new Date(task.completedAt).toLocaleDateString('de', {
})} Uhr, {formatDate(new Date(task.completedAt), {
day: 'numeric',
month: 'short',
})}</span

View file

@ -1,4 +1,5 @@
<script lang="ts">
import { formatDate } from '$lib/i18n/format';
import { _ } from 'svelte-i18n';
import { tasksStore } from '../stores/tasks.svelte';
import {
@ -141,7 +142,7 @@
{#if task.dueDate}
<span class="inline-flex items-center gap-0.5 text-amber-500">
<CalendarBlank size={10} />
{task.dueDate.toLocaleDateString('de-DE', { day: 'numeric', month: 'short' })}
{formatDate(task.dueDate, { day: 'numeric', month: 'short' })}
{#if task.dueTime}
{task.dueTime}
{/if}

View file

@ -1,11 +1,10 @@
<script lang="ts">
import { getDateFnsLocale } from '$lib/i18n/format';
import type { Task, TaskPriority } from '../types';
import { getPriorityLabel, getPriorityColor } from '../queries';
import { Check, Circle, CalendarBlank, CheckSquare } from '@mana/shared-icons';
import { TagChip } from '@mana/shared-ui';
import { isToday, isPast, format } from 'date-fns';
import { de } from 'date-fns/locale';
interface Props {
task: Task;
tags?: { id: string; name: string; color: string }[];
@ -38,7 +37,7 @@
const overdue = isPast(d) && !isToday(d) && !task.isCompleted;
const today = isToday(d);
return {
text: today ? 'Heute' : format(d, 'd. MMM', { locale: de }),
text: today ? 'Heute' : format(d, 'd. MMM', { locale: getDateFnsLocale() }),
overdue,
today,
};

View file

@ -1,7 +1,7 @@
<script lang="ts">
import { getDateFnsLocale } from '$lib/i18n/format';
import type { Task } from '../../types';
import { isToday, isPast, format } from 'date-fns';
import { de } from 'date-fns/locale';
import { Check, Circle, CalendarBlank, CheckSquare, Flag, Trash } from '@mana/shared-icons';
import { TagChip } from '@mana/shared-ui';
@ -31,7 +31,7 @@
const overdue = isPast(d) && !isToday(d) && !task.isCompleted;
const today = isToday(d);
return {
text: today ? 'Heute' : format(d, 'd. MMM', { locale: de }),
text: today ? 'Heute' : format(d, 'd. MMM', { locale: getDateFnsLocale() }),
overdue,
today,
};

View file

@ -1,3 +1,4 @@
import { formatDate } from '$lib/i18n/format';
/**
* Todo QuickInputBar Adapter
*/
@ -33,7 +34,7 @@ export function createAdapter(): InputBarAdapter {
.map((t) => ({
id: t.id,
title: t.title || '',
subtitle: t.dueDate ? new Date(t.dueDate).toLocaleDateString('de-DE') : 'Keine Frist',
subtitle: t.dueDate ? formatDate(new Date(t.dueDate)) : 'Keine Frist',
}));
},

View file

@ -3,6 +3,7 @@
All fields are always editable. Changes auto-save on blur.
-->
<script lang="ts">
import { formatDate } from '$lib/i18n/format';
import { db } from '$lib/data/database';
import { decryptRecord } from '$lib/data/crypto';
import { useDetailEntity } from '$lib/data/detail-entity.svelte';
@ -325,9 +326,9 @@
{/if}
<div class="meta">
<span>Erstellt: {new Date(task.createdAt ?? '').toLocaleDateString('de')}</span>
<span>Erstellt: {formatDate(new Date(task.createdAt ?? ''))}</span>
{#if task.updatedAt}
<span>Bearbeitet: {new Date(task.updatedAt).toLocaleDateString('de')}</span>
<span>Bearbeitet: {formatDate(new Date(task.updatedAt))}</span>
{/if}
</div>
{/snippet}

View file

@ -3,6 +3,7 @@
All fields are always editable. Changes auto-save on blur.
-->
<script lang="ts">
import { formatDate } from '$lib/i18n/format';
import { db } from '$lib/data/database';
import { encryptRecord } from '$lib/data/crypto';
import { useDetailEntity } from '$lib/data/detail-entity.svelte';
@ -162,9 +163,9 @@
</div>
<div class="meta">
<span>Erstellt: {new Date(link.createdAt ?? '').toLocaleDateString('de')}</span>
<span>Erstellt: {formatDate(new Date(link.createdAt ?? ''))}</span>
{#if link.updatedAt}
<span>Bearbeitet: {new Date(link.updatedAt).toLocaleDateString('de')}</span>
<span>Bearbeitet: {formatDate(new Date(link.updatedAt))}</span>
{/if}
</div>
{/snippet}

View file

@ -1,4 +1,5 @@
<script lang="ts">
import { formatDate } from '$lib/i18n/format';
import { useAllSites } from './queries';
const sites = useAllSites();
@ -13,7 +14,7 @@
if (diffH < 24) return `vor ${diffH} Std`;
const diffD = Math.floor(diffH / 24);
if (diffD < 30) return `vor ${diffD} Tg`;
return new Date(iso).toLocaleDateString('de-DE');
return formatDate(new Date(iso));
}
</script>

View file

@ -1,3 +1,4 @@
import { formatDateTime } from '$lib/i18n/format';
/**
* Module-embed resolvers client-side functions that walk Dexie to
* pre-fetch data for `moduleEmbed` blocks at publish time.
@ -260,11 +261,11 @@ function formatEventSubtitle(
location: string | null | undefined
): string {
const start = new Date(startIso);
const dateParts = new Intl.DateTimeFormat('de-DE', {
const dateParts = formatDateTime(start, {
day: '2-digit',
month: 'short',
year: 'numeric',
}).format(start);
});
let timePart = '';
if (!allDay) {

View file

@ -3,6 +3,7 @@
conditions, wind, humidity, pressure, UV index, and last-updated time.
-->
<script lang="ts">
import { formatTime } from '$lib/i18n/format';
import type { CurrentWeather } from '../types';
import { getWeatherIcon, getWeatherLabel, windDirectionLabel } from '../weather-icons';
@ -23,7 +24,7 @@
let lastUpdated = $derived(() => {
if (!fetchedAt) return '';
return new Date(fetchedAt).toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' });
return formatTime(new Date(fetchedAt), { hour: '2-digit', minute: '2-digit' });
});
</script>

View file

@ -2,6 +2,7 @@
7-day daily forecast — shows min/max temp, weather icon, precipitation.
-->
<script lang="ts">
import { formatDate } from '$lib/i18n/format';
import type { DailyForecast } from '../types';
import { getWeatherIcon, getWeatherLabel } from '../weather-icons';
@ -16,7 +17,7 @@
function dayLabel(dateStr: string, idx: number): string {
if (idx === 0) return 'Heute';
if (idx === 1) return 'Morgen';
return new Date(dateStr).toLocaleDateString('de-DE', { weekday: 'short' });
return formatDate(new Date(dateStr), { weekday: 'short' });
}
function tempBarStyle(min: number, max: number): string {

View file

@ -2,6 +2,7 @@
Horizontal scrolling hourly forecast — next 24 hours.
-->
<script lang="ts">
import { formatTime } from '$lib/i18n/format';
import type { HourlyForecast } from '../types';
import { getWeatherIcon } from '../weather-icons';
@ -18,7 +19,7 @@
<span class="section-label">Stundenvorhersage</span>
<div class="hourly-scroll">
{#each visibleHours as hour (hour.time)}
{@const time = new Date(hour.time).toLocaleTimeString('de-DE', {
{@const time = formatTime(new Date(hour.time), {
hour: '2-digit',
minute: '2-digit',
})}

View file

@ -2,6 +2,7 @@
Rain nowcast — bar chart showing minute-level precipitation forecast.
-->
<script lang="ts">
import { formatTime } from '$lib/i18n/format';
import type { RainNowcast } from '../types';
interface Props {
@ -20,7 +21,7 @@
<div class="nowcast-chart">
{#each nowcast.minutely as point (point.time)}
{@const height = Math.max(2, (point.precipitation / maxPrecip) * 100)}
{@const time = new Date(point.time).toLocaleTimeString('de-DE', {
{@const time = formatTime(new Date(point.time), {
hour: '2-digit',
minute: '2-digit',
})}
@ -31,13 +32,13 @@
</div>
<div class="nowcast-time-labels">
<span
>{new Date(nowcast.minutely[0].time).toLocaleTimeString('de-DE', {
>{formatTime(new Date(nowcast.minutely[0].time), {
hour: '2-digit',
minute: '2-digit',
})}</span
>
<span
>{new Date(nowcast.minutely[nowcast.minutely.length - 1].time).toLocaleTimeString('de-DE', {
>{formatTime(new Date(nowcast.minutely[nowcast.minutely.length - 1].time), {
hour: '2-digit',
minute: '2-digit',
})}</span

View file

@ -4,6 +4,7 @@
Open-Meteo Best Match) stacked for easy comparison.
-->
<script lang="ts">
import { formatDate, formatTime } from '$lib/i18n/format';
import { getComparison, type CompareResponse, type ModelComparison } from '../api';
import { getWeatherIcon, getWeatherLabel } from '../weather-icons';
@ -122,7 +123,7 @@
? 'Heute'
: dayIdx === 1
? 'Morgen'
: new Date(dateStr).toLocaleDateString('de-DE', { weekday: 'short', day: 'numeric' })}
: formatDate(new Date(dateStr), { weekday: 'short', day: 'numeric' })}
<div class="day-compare">
<div class="day-compare-header">{dayLabel}</div>
<div class="day-models">
@ -176,7 +177,7 @@
{/if}
<div class="fetched-at">
Abgerufen: {new Date(data.fetchedAt).toLocaleTimeString('de-DE')}
Abgerufen: {formatTime(new Date(data.fetchedAt))}
<button class="refresh-btn" onclick={() => loadComparison(lat, lon)} disabled={loading}>
Aktualisieren
</button>

View file

@ -1,3 +1,4 @@
import { formatDate, formatTime } from '$lib/i18n/format';
/**
* Wetter AI tools expose weather data to the AI companion.
* Both tools are read-only (auto policy) and run during the reasoning loop.
@ -75,7 +76,7 @@ export const wetterTools: ModuleTool[] = [
lines.push('## 7-Tage-Vorhersage');
for (const d of forecast.daily.slice(0, 7)) {
const day = new Date(d.date).toLocaleDateString('de-DE', {
const day = formatDate(new Date(d.date), {
weekday: 'short',
day: 'numeric',
month: 'short',
@ -145,7 +146,7 @@ export const wetterTools: ModuleTool[] = [
lines.push('Kein Niederschlag erwartet.');
} else {
for (const m of withRain.slice(0, 12)) {
const t = new Date(m.time).toLocaleTimeString('de-DE', {
const t = formatTime(new Date(m.time), {
hour: '2-digit',
minute: '2-digit',
});

View file

@ -1,4 +1,5 @@
<script lang="ts">
import { formatDate, formatDateTime } from '$lib/i18n/format';
import { goto } from '$app/navigation';
import { page } from '$app/stores';
import { ArrowLeft, Check, Trash, Plus, Link as LinkIcon, Star, X } from '@mana/shared-icons';
@ -296,7 +297,7 @@
{#each priceChecks.value.slice(0, 10) as check (check.id)}
<div class="flex items-center justify-between text-sm">
<span class="truncate text-[hsl(var(--color-muted-foreground))]">
{new Date(check.checkedAt).toLocaleDateString('de-DE')}
{formatDate(new Date(check.checkedAt))}
</span>
<span
class="font-medium {check.available
@ -328,7 +329,7 @@
>
<p>{note.content}</p>
<p class="mt-1 text-[10px] text-[hsl(var(--color-muted-foreground))]">
{new Date(note.createdAt).toLocaleString('de-DE')}
{formatDateTime(new Date(note.createdAt))}
</p>
</li>
{/each}

View file

@ -1,4 +1,5 @@
<script lang="ts">
import { formatDate } from '$lib/i18n/format';
import StatusBadge from './StatusBadge.svelte';
import { KIND_LABELS } from '../constants';
import type { Draft, DraftVersion } from '../types';
@ -32,7 +33,7 @@
if (hrs < 24) return `vor ${hrs} h`;
const days = Math.round(hrs / 24);
if (days < 30) return `vor ${days} d`;
return d.toLocaleDateString('de-DE');
return formatDate(d);
}
function open() {

View file

@ -7,6 +7,7 @@
place).
-->
<script lang="ts">
import { formatDateTime } from '$lib/i18n/format';
import { goto } from '$app/navigation';
import { VisibilityPicker, type VisibilityLevel } from '@mana/shared-privacy';
import BriefingForm from '../components/BriefingForm.svelte';
@ -338,10 +339,7 @@
<div class="published-row">
<span class="published-label">📤 Veröffentlicht:</span>
{#each draft.publishedTo as target (`${target.module}:${target.targetId}`)}
<span
class="published-chip"
title={new Date(target.publishedAt).toLocaleString('de-DE')}
>
<span class="published-chip" title={formatDateTime(new Date(target.publishedAt))}>
{#if target.module === 'articles'}
📚 <a href={`/articles/${target.targetId}`}>Artikel</a>
{:else if target.module === 'website'}

View file

@ -1,4 +1,5 @@
<script lang="ts">
import { getDateFnsLocale } from '$lib/i18n/format';
import { _ } from 'svelte-i18n';
import { page } from '$app/stores';
import { goto } from '$app/navigation';
@ -7,7 +8,6 @@
import { getEventById, getCalendarById, getCalendarColor } from '$lib/modules/calendar/queries';
import type { Calendar, CalendarEvent } from '$lib/modules/calendar/types';
import { format } from 'date-fns';
import { de } from 'date-fns/locale';
import { CaretLeft, Trash, PencilSimple, MapPin, Clock } from '@mana/shared-icons';
import { RoutePage } from '$lib/components/shell';
@ -239,7 +239,11 @@
<div class="flex items-center gap-2 text-foreground">
<Clock size={18} class="text-muted-foreground" />
<div>
<div>{format(new Date(event.startTime), 'EEEE, d. MMMM yyyy', { locale: de })}</div>
<div>
{format(new Date(event.startTime), 'EEEE, d. MMMM yyyy', {
locale: getDateFnsLocale(),
})}
</div>
{#if event.isAllDay}
<div class="text-sm text-muted-foreground">Ganztägig</div>
{:else}

View file

@ -1,4 +1,5 @@
<script lang="ts">
import { formatDate } from '$lib/i18n/format';
import { getContext } from 'svelte';
import { ChartBar } from '@mana/shared-icons';
import type { Deck } from '$lib/modules/cards/types';
@ -67,7 +68,7 @@
</div>
<div class="text-right">
<div class="text-sm text-muted-foreground">
{new Date(deck.updatedAt).toLocaleDateString('de-DE', {
{formatDate(new Date(deck.updatedAt), {
day: '2-digit',
month: 'short',
})}

View file

@ -1,4 +1,5 @@
<script lang="ts">
import { formatTime } from '$lib/i18n/format';
import { page } from '$app/stores';
import { goto } from '$app/navigation';
import { getContext } from 'svelte';
@ -200,7 +201,7 @@
>
<p class="whitespace-pre-wrap">{msg.messageText}</p>
<p class="mt-1 text-[10px] opacity-60">
{new Date(msg.createdAt).toLocaleTimeString('de-DE', {
{formatTime(new Date(msg.createdAt), {
hour: '2-digit',
minute: '2-digit',
})}

View file

@ -1,4 +1,5 @@
<script lang="ts">
import { formatDate } from '$lib/i18n/format';
import { _ } from 'svelte-i18n';
import { page } from '$app/stores';
import { goto } from '$app/navigation';
@ -570,7 +571,7 @@
<div class="flex items-center gap-3">
<Cake size={16} class="flex-shrink-0 text-muted-foreground" />
<span class="text-sm text-foreground">
{new Date(contact.birthday).toLocaleDateString('de-DE', {
{formatDate(new Date(contact.birthday), {
day: 'numeric',
month: 'long',
year: 'numeric',
@ -675,7 +676,7 @@
<div class="grid grid-cols-2 gap-y-2 text-xs text-muted-foreground">
<span>Erstellt</span>
<span
>{new Date(contact.createdAt).toLocaleDateString('de-DE', {
>{formatDate(new Date(contact.createdAt), {
day: 'numeric',
month: 'short',
year: 'numeric',
@ -683,7 +684,7 @@
>
<span>Aktualisiert</span>
<span
>{new Date(contact.updatedAt).toLocaleDateString('de-DE', {
>{formatDate(new Date(contact.updatedAt), {
day: 'numeric',
month: 'short',
year: 'numeric',

View file

@ -1,4 +1,5 @@
<script lang="ts">
import { formatDate } from '$lib/i18n/format';
import { Folder, FileText, Plus } from '@mana/shared-icons';
import {
useAllSpaces,
@ -174,7 +175,7 @@
{/each}
{/if}
<span class="ml-auto">
{new Date(doc.updated_at).toLocaleDateString('de')}
{formatDate(new Date(doc.updated_at))}
</span>
</div>
</a>

View file

@ -1,4 +1,5 @@
<script lang="ts">
import { formatDate } from '$lib/i18n/format';
import { goto } from '$app/navigation';
import { Plus, MagnifyingGlass, FileText } from '@mana/shared-icons';
import {
@ -209,7 +210,7 @@
{/each}
{/if}
<span class="ml-auto">
{new Date(doc.updated_at).toLocaleDateString('de')}
{formatDate(new Date(doc.updated_at))}
</span>
</div>
</div>

View file

@ -1,4 +1,5 @@
<script lang="ts">
import { formatDate } from '$lib/i18n/format';
import { page } from '$app/stores';
import { goto } from '$app/navigation';
import { ArrowLeft, Trash } from '@mana/shared-icons';
@ -200,10 +201,10 @@
<span>{doc.metadata.word_count} Woerter</span>
{/if}
<span>
Erstellt: {new Date(doc.created_at).toLocaleDateString('de')}
Erstellt: {formatDate(new Date(doc.created_at))}
</span>
<span>
Aktualisiert: {new Date(doc.updated_at).toLocaleDateString('de')}
Aktualisiert: {formatDate(new Date(doc.updated_at))}
</span>
</div>
{/if}

View file

@ -1,4 +1,5 @@
<script lang="ts">
import { formatDate } from '$lib/i18n/format';
import { Plus, MagnifyingGlass } from '@mana/shared-icons';
import { useAllSpaces } from '$lib/modules/context/queries';
import { contextSpaceTable } from '$lib/modules/context/collections';
@ -192,7 +193,7 @@
</div>
</div>
<div class="mt-3 text-xs opacity-40">
Erstellt: {new Date(space.created_at).toLocaleDateString('de')}
Erstellt: {formatDate(new Date(space.created_at))}
</div>
</div>
{/each}

View file

@ -1,4 +1,5 @@
<script lang="ts">
import { formatDate } from '$lib/i18n/format';
import { _ } from 'svelte-i18n';
import { page } from '$app/stores';
import { goto } from '$app/navigation';
@ -260,7 +261,7 @@
</div>
</div>
<div class="mt-2 text-xs opacity-40">
{new Date(doc.updated_at).toLocaleDateString('de')}
{formatDate(new Date(doc.updated_at))}
</div>
</div>
{/each}

View file

@ -1,4 +1,5 @@
<script lang="ts">
import { formatDate } from '$lib/i18n/format';
import { getContext } from 'svelte';
import type { Observable } from 'dexie';
import type { Transaction, FinanceCategory, TransactionType } from '$lib/modules/finance/types';
@ -83,7 +84,7 @@
}
let monthLabel = $derived(
new Date(month + '-01').toLocaleDateString('de-DE', { month: 'long', year: 'numeric' })
formatDate(new Date(month + '-01'), { month: 'long', year: 'numeric' })
);
let maxSpend = $derived(Math.max(1, ...spending.values()));

View file

@ -1,4 +1,5 @@
<script lang="ts">
import { formatDate } from '$lib/i18n/format';
import { _ } from 'svelte-i18n';
import { page } from '$app/stores';
import { goto } from '$app/navigation';
@ -323,7 +324,7 @@
<div>
<p class="text-sm text-[hsl(var(--color-foreground))]">{note.content}</p>
<p class="mt-1 text-xs text-[hsl(var(--color-muted-foreground))]">
{new Date(note.createdAt).toLocaleDateString('de-DE')}
{formatDate(new Date(note.createdAt))}
</p>
</div>
<button

View file

@ -1,4 +1,5 @@
<script lang="ts">
import { formatDate } from '$lib/i18n/format';
import { page } from '$app/stores';
import { goto } from '$app/navigation';
import { getContext } from 'svelte';
@ -143,7 +144,7 @@
</button>
{/if}
<p class="text-sm text-[hsl(var(--color-muted-foreground))]">
{new Date(memo.createdAt).toLocaleDateString('de-DE', {
{formatDate(new Date(memo.createdAt), {
day: '2-digit',
month: 'long',
year: 'numeric',

View file

@ -1,4 +1,5 @@
<script lang="ts">
import { formatDate } from '$lib/i18n/format';
import { _ } from 'svelte-i18n';
import { goto } from '$app/navigation';
import { getContext } from 'svelte';
@ -129,7 +130,7 @@
<div class="mt-2 flex items-center justify-between text-xs text-muted-foreground">
<span>{board.itemCount} {board.itemCount === 1 ? 'Element' : 'Elemente'}</span>
<span>{new Date(board.updatedAt).toLocaleDateString('de-DE')}</span>
<span>{formatDate(new Date(board.updatedAt))}</span>
</div>
<!-- Actions -->

View file

@ -1,4 +1,5 @@
<script lang="ts">
import { formatDate } from '$lib/i18n/format';
import { skillStore } from '$lib/modules/skilltree/stores/skills.svelte';
import {
achievementStore,
@ -336,7 +337,7 @@
</div>
</div>
<span class="text-sm text-muted-foreground">
{new Date(activity.timestamp).toLocaleDateString('de-DE')}
{formatDate(new Date(activity.timestamp))}
</span>
</div>
{/if}

View file

@ -3,6 +3,7 @@
"What did I do today?" as a standalone page.
-->
<script lang="ts">
import { getDateFnsLocale } from '$lib/i18n/format';
import { _ } from 'svelte-i18n';
import { useLiveQueryWithDefault } from '@mana/local-store/svelte';
import { db } from '$lib/data/database';
@ -22,7 +23,6 @@
Funnel,
} from '@mana/shared-icons';
import { format, addDays, subDays, isToday, isTomorrow, isYesterday } from 'date-fns';
import { de } from 'date-fns/locale';
import { RoutePage } from '$lib/components/shell';
let currentDate = $state(new Date());
@ -78,7 +78,7 @@
if (isToday(date)) return 'Heute';
if (isTomorrow(date)) return 'Morgen';
if (isYesterday(date)) return 'Gestern';
return format(date, 'EEEE, d. MMMM yyyy', { locale: de });
return format(date, 'EEEE, d. MMMM yyyy', { locale: getDateFnsLocale() });
}
function formatBlockTime(block: TimeBlock): string {

View file

@ -3,6 +3,7 @@
Shows time breakdown, daily trends, habit heatmap, and plan adherence.
-->
<script lang="ts">
import { getDateFnsLocale } from '$lib/i18n/format';
import { useLiveQueryWithDefault } from '@mana/local-store/svelte';
import { db } from '$lib/data/database';
import type { LocalTimeBlock } from '$lib/data/time-blocks/types';
@ -20,7 +21,6 @@
} from '$lib/data/time-blocks/analytics';
import { Clock, TrendUp, Fire, Target } from '@mana/shared-icons';
import { format, subDays } from 'date-fns';
import { de } from 'date-fns/locale';
import { RoutePage } from '$lib/components/shell';
let periodDays = $state(7);
@ -156,7 +156,7 @@
</div>
</div>
<span class="daily-label">
{format(new Date(day.date), 'EEE', { locale: de })}
{format(new Date(day.date), 'EEE', { locale: getDateFnsLocale() })}
</span>
</div>
{/each}

View file

@ -1,4 +1,5 @@
<script lang="ts">
import { formatDate, formatTime } from '$lib/i18n/format';
import { Clock, Bell, Timer, Hourglass, Globe } from '@mana/shared-icons';
import { RoutePage } from '$lib/components/shell';
@ -53,14 +54,14 @@
</div>
<div>
<div class="text-4xl font-bold tabular-nums text-foreground">
{new Date().toLocaleTimeString('de-DE', {
{formatTime(new Date(), {
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
})}
</div>
<div class="text-muted-foreground">
{new Date().toLocaleDateString('de-DE', {
{formatDate(new Date(), {
weekday: 'long',
year: 'numeric',
month: 'long',

View file

@ -1,4 +1,5 @@
<script lang="ts">
import { formatDate } from '$lib/i18n/format';
import { getContext } from 'svelte';
import { _ } from 'svelte-i18n';
import type { TimeEntry, Project, Client } from '$lib/modules/times/types';
@ -69,7 +70,7 @@
const dayEntries = entries().filter((e) => e.date === dateStr);
days.push({
date: dateStr,
label: d.toLocaleDateString('de-DE', { weekday: 'short', day: 'numeric' }),
label: formatDate(d, { weekday: 'short', day: 'numeric' }),
duration: getTotalDuration(dayEntries),
});
}

View file

@ -1,4 +1,5 @@
<script lang="ts">
import { formatDate } from '$lib/i18n/format';
import { _ } from 'svelte-i18n';
import {
useAllLinks,
@ -527,8 +528,7 @@
{#if link.expiresAt}
<span
class="shrink-0 rounded bg-orange-100 px-1.5 py-0.5 text-xs text-orange-700 dark:bg-orange-900 dark:text-orange-300"
title="Laeuft ab: {new Date(link.expiresAt).toLocaleDateString('de')}"
>Ablauf</span
title="Laeuft ab: {formatDate(new Date(link.expiresAt))}">Ablauf</span
>
{/if}
</div>

View file

@ -1,4 +1,5 @@
<script lang="ts">
import { formatDate } from '$lib/i18n/format';
import { page } from '$app/stores';
import { onMount } from 'svelte';
import { CaretLeft } from '@mana/shared-icons';
@ -129,7 +130,7 @@
<div class="rounded-xl border border-border/10 bg-muted/5 p-5">
<p class="text-xs font-medium uppercase tracking-wider text-muted-foreground">Erstellt</p>
<p class="mt-1 text-lg font-bold text-white">
{new Date(link.createdAt).toLocaleDateString('de')}
{formatDate(new Date(link.createdAt))}
</p>
</div>
</div>
@ -185,7 +186,7 @@
{#if link.expiresAt}
<div class="flex items-center justify-between text-sm">
<span class="text-muted-foreground">Laeuft ab</span>
<span class="text-white">{new Date(link.expiresAt).toLocaleDateString('de')}</span>
<span class="text-white">{formatDate(new Date(link.expiresAt))}</span>
</div>
{/if}
{#if link.maxClicks}