i18n(dreams): translate ListView via $_() — view tabs, insights, filters, editor, transcription badges

- View tabs (Träume/Symbole), insights ribbon, filter chips (Alle/Klarträume/Albträume/Wiederkehrend), search placeholder
- Inline editor: title placeholder, transcription status (transcribing/failed/done), content/symbols placeholders, sleep-row labels (Nacht/Ins Bett/Aufgewacht), sleep quality + star aria-label, Klartraum/Wiederkehrend toggles, Löschen/Fertig actions
- Dream-row: untitled fallback, transcribing/failed badge titles, STT-chip title
- FloatingInputBar placeholder + voice reason
- Mood labels routed through $_('dreams.moods.' + mood); MOOD_LABELS constant kept in types.ts for non-Svelte callers (SymbolDetailView)
- Context menu: edit/pin/unpin/delete via $_()

Baselines: hardcoded 1254 → 1242 (12 cleared); missing-keys baseline +1 (dreams.moods.* dynamic key).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-04-27 02:12:00 +02:00
parent 3e41b14a31
commit 779752e907
3 changed files with 67 additions and 40 deletions

View file

@ -18,6 +18,7 @@
import { useItemContextMenu } from '$lib/data/item-context-menu.svelte'; import { useItemContextMenu } from '$lib/data/item-context-menu.svelte';
import { PencilSimple, PushPin, Trash } from '@mana/shared-icons'; import { PencilSimple, PushPin, Trash } from '@mana/shared-icons';
import SymbolsView from './views/SymbolsView.svelte'; import SymbolsView from './views/SymbolsView.svelte';
import { _ } from 'svelte-i18n';
let { navigate, goBack, params }: ViewProps = $props(); let { navigate, goBack, params }: ViewProps = $props();
@ -139,7 +140,7 @@
? [ ? [
{ {
id: 'edit', id: 'edit',
label: 'Bearbeiten', label: $_('dreams.list_view.ctx_edit'),
icon: PencilSimple, icon: PencilSimple,
action: () => { action: () => {
const target = ctxMenu.state.target; const target = ctxMenu.state.target;
@ -148,7 +149,9 @@
}, },
{ {
id: 'pin', id: 'pin',
label: ctxMenu.state.target.isPinned ? 'Lösen' : 'Pinnen', label: ctxMenu.state.target.isPinned
? $_('dreams.list_view.ctx_unpin')
: $_('dreams.list_view.ctx_pin'),
icon: PushPin, icon: PushPin,
action: () => { action: () => {
const target = ctxMenu.state.target; const target = ctxMenu.state.target;
@ -158,7 +161,7 @@
{ id: 'div', label: '', type: 'divider' as const }, { id: 'div', label: '', type: 'divider' as const },
{ {
id: 'delete', id: 'delete',
label: 'Löschen', label: $_('dreams.list_view.ctx_delete'),
icon: Trash, icon: Trash,
variant: 'danger' as const, variant: 'danger' as const,
action: () => { action: () => {
@ -188,14 +191,14 @@
<!-- View switcher --> <!-- View switcher -->
<div class="view-tabs"> <div class="view-tabs">
<button class="view-tab" class:active={viewMode === 'list'} onclick={() => (viewMode = 'list')}> <button class="view-tab" class:active={viewMode === 'list'} onclick={() => (viewMode = 'list')}>
Träume {$_('dreams.list_view.tab_dreams')}
</button> </button>
<button <button
class="view-tab" class="view-tab"
class:active={viewMode === 'symbols'} class:active={viewMode === 'symbols'}
onclick={() => (viewMode = 'symbols')} onclick={() => (viewMode = 'symbols')}
> >
Symbole {$_('dreams.list_view.tab_symbols')}
</button> </button>
</div> </div>
@ -210,9 +213,15 @@
<!-- Insights ribbon --> <!-- Insights ribbon -->
{#if insights.total > 0} {#if insights.total > 0}
<div class="insights"> <div class="insights">
<span class="ins-stat">{insights.total} Träume</span> <span class="ins-stat"
>{$_('dreams.list_view.insights_total', { values: { n: insights.total } })}</span
>
{#if insights.lucidCount > 0} {#if insights.lucidCount > 0}
<span class="ins-stat">&#x2728; {insights.lucidCount} Klarträume</span> <span class="ins-stat"
>{$_('dreams.list_view.insights_lucid', {
values: { n: insights.lucidCount },
})}</span
>
{/if} {/if}
{#each insights.topSymbols as sym} {#each insights.topSymbols as sym}
<button <button
@ -224,7 +233,9 @@
</button> </button>
{/each} {/each}
{#if symbolFilter} {#if symbolFilter}
<button class="ins-clear" onclick={() => (symbolFilter = null)}>×&nbsp;Filter</button> <button class="ins-clear" onclick={() => (symbolFilter = null)}
>{$_('dreams.list_view.clear_filter')}</button
>
{/if} {/if}
</div> </div>
{/if} {/if}
@ -237,28 +248,28 @@
class:active={filterMode === 'all'} class:active={filterMode === 'all'}
onclick={() => (filterMode = 'all')} onclick={() => (filterMode = 'all')}
> >
Alle {$_('dreams.list_view.filter_all')}
</button> </button>
<button <button
class="filter-tab" class="filter-tab"
class:active={filterMode === 'lucid'} class:active={filterMode === 'lucid'}
onclick={() => (filterMode = 'lucid')} onclick={() => (filterMode = 'lucid')}
> >
&#x2728; Klarträume {$_('dreams.list_view.filter_lucid')}
</button> </button>
<button <button
class="filter-tab" class="filter-tab"
class:active={filterMode === 'nightmare'} class:active={filterMode === 'nightmare'}
onclick={() => (filterMode = 'nightmare')} onclick={() => (filterMode = 'nightmare')}
> >
Albträume {$_('dreams.list_view.filter_nightmare')}
</button> </button>
<button <button
class="filter-tab" class="filter-tab"
class:active={filterMode === 'recurring'} class:active={filterMode === 'recurring'}
onclick={() => (filterMode = 'recurring')} onclick={() => (filterMode = 'recurring')}
> >
Wiederkehrend {$_('dreams.list_view.filter_recurring')}
</button> </button>
</div> </div>
{/if} {/if}
@ -268,7 +279,7 @@
<input <input
class="search-input" class="search-input"
type="text" type="text"
placeholder="Träume durchsuchen..." placeholder={$_('dreams.list_view.search_placeholder')}
bind:value={searchQuery} bind:value={searchQuery}
/> />
{/if} {/if}
@ -293,36 +304,40 @@
class="ed-title" class="ed-title"
type="text" type="text"
bind:value={editTitle} bind:value={editTitle}
placeholder="Titel (optional)..." placeholder={$_('dreams.list_view.editor_title_placeholder')}
autofocus autofocus
/> />
{#if dream.processingStatus === 'transcribing'} {#if dream.processingStatus === 'transcribing'}
<div class="ed-status"> <div class="ed-status">
<span class="ed-status-dots">●●●</span> <span class="ed-status-dots">●●●</span>
Transkribiert deine Aufnahme… {$_('dreams.list_view.editor_transcribing')}
</div> </div>
{:else if dream.processingStatus === 'failed'} {:else if dream.processingStatus === 'failed'}
<div class="ed-status failed"> <div class="ed-status failed">
Transkription fehlgeschlagen{dream.processingError {$_('dreams.list_view.editor_transcribe_failed')}{dream.processingError
? `: ${dream.processingError}` ? `: ${dream.processingError}`
: ''} : ''}
</div> </div>
{:else if dream.transcript && dream.transcriptModel} {:else if dream.transcript && dream.transcriptModel}
<div class="ed-status muted" title="STT-Pipeline, die den Transkript erzeugt hat"> <div
Transkribiert via <strong>{dream.transcriptModel}</strong> class="ed-status muted"
title={$_('dreams.list_view.editor_stt_pipeline_title')}
>
{$_('dreams.list_view.editor_transcribed_via')}
<strong>{dream.transcriptModel}</strong>
</div> </div>
{/if} {/if}
<textarea <textarea
class="ed-content" class="ed-content"
bind:value={editContent} bind:value={editContent}
placeholder="Erzähl mir den Traum..." placeholder={$_('dreams.list_view.editor_content_placeholder')}
rows="5" rows="5"
></textarea> ></textarea>
<input <input
class="ed-symbols" class="ed-symbols"
type="text" type="text"
bind:value={editSymbols} bind:value={editSymbols}
placeholder="Symbole (Komma-getrennt): Wasser, Fliegen, Tür" placeholder={$_('dreams.list_view.editor_symbols_placeholder')}
/> />
<div class="ed-row"> <div class="ed-row">
@ -333,10 +348,10 @@
class:active={editMood === mood} class:active={editMood === mood}
style="--mood-color: {MOOD_COLORS[mood]}" style="--mood-color: {MOOD_COLORS[mood]}"
onclick={() => (editMood = editMood === mood ? null : mood)} onclick={() => (editMood = editMood === mood ? null : mood)}
title={MOOD_LABELS[mood]} title={$_('dreams.moods.' + mood)}
> >
<span class="mood-dot"></span> <span class="mood-dot"></span>
{MOOD_LABELS[mood]} {$_('dreams.moods.' + mood)}
</button> </button>
{/each} {/each}
</div> </div>
@ -344,29 +359,31 @@
<div class="ed-row sleep-row"> <div class="ed-row sleep-row">
<label class="ed-field"> <label class="ed-field">
<span class="ed-label">Nacht</span> <span class="ed-label">{$_('dreams.list_view.editor_label_night')}</span>
<input type="date" bind:value={editDreamDate} class="ed-input-sm" /> <input type="date" bind:value={editDreamDate} class="ed-input-sm" />
</label> </label>
<label class="ed-field"> <label class="ed-field">
<span class="ed-label">Ins Bett</span> <span class="ed-label">{$_('dreams.list_view.editor_label_bedtime')}</span>
<input type="time" bind:value={editBedtime} class="ed-input-sm" /> <input type="time" bind:value={editBedtime} class="ed-input-sm" />
</label> </label>
<label class="ed-field"> <label class="ed-field">
<span class="ed-label">Aufgewacht</span> <span class="ed-label">{$_('dreams.list_view.editor_label_wake')}</span>
<input type="time" bind:value={editWakeTime} class="ed-input-sm" /> <input type="time" bind:value={editWakeTime} class="ed-input-sm" />
</label> </label>
</div> </div>
<div class="ed-row"> <div class="ed-row">
<div class="ed-field"> <div class="ed-field">
<span class="ed-label">Schlafqualität</span> <span class="ed-label">{$_('dreams.list_view.editor_label_sleep_quality')}</span>
<div class="stars"> <div class="stars">
{#each [1, 2, 3, 4, 5] as q} {#each [1, 2, 3, 4, 5] as q}
<button <button
class="star" class="star"
class:filled={editSleepQuality !== null && editSleepQuality >= q} class:filled={editSleepQuality !== null && editSleepQuality >= q}
onclick={() => setSleepQuality(q as SleepQuality)} onclick={() => setSleepQuality(q as SleepQuality)}
aria-label={`${q} Sterne`} aria-label={$_('dreams.list_view.editor_stars_aria', {
values: { n: q },
})}
> >
</button> </button>
@ -376,19 +393,22 @@
<div class="toggles"> <div class="toggles">
<label class="lucid-toggle"> <label class="lucid-toggle">
<input type="checkbox" bind:checked={editIsLucid} /> <input type="checkbox" bind:checked={editIsLucid} />
&#x2728; Klartraum {$_('dreams.list_view.editor_lucid_toggle')}
</label> </label>
<label class="lucid-toggle"> <label class="lucid-toggle">
<input type="checkbox" bind:checked={editIsRecurring} /> <input type="checkbox" bind:checked={editIsRecurring} />
&#x21bb; Wiederkehrend {$_('dreams.list_view.editor_recurring_toggle')}
</label> </label>
</div> </div>
</div> </div>
<div class="ed-actions"> <div class="ed-actions">
<button class="ed-btn danger" onclick={() => handleDelete(dream.id)}>Löschen</button <button class="ed-btn danger" onclick={() => handleDelete(dream.id)}
>{$_('dreams.list_view.editor_action_delete')}</button
>
<button class="ed-btn primary" onclick={saveEdit}
>{$_('dreams.list_view.editor_action_done')}</button
> >
<button class="ed-btn primary" onclick={saveEdit}>Fertig</button>
</div> </div>
</div> </div>
{:else} {:else}
@ -414,11 +434,18 @@
<div class="dream-content"> <div class="dream-content">
<div class="dream-top"> <div class="dream-top">
<span class="dream-title">{dream.title || 'Traum ohne Titel'}</span> <span class="dream-title">{dream.title || $_('dreams.list_view.untitled')}</span>
{#if dream.processingStatus === 'transcribing'} {#if dream.processingStatus === 'transcribing'}
<span class="badge transcribing" title="Wird transkribiert…">●●●</span> <span
class="badge transcribing"
title={$_('dreams.list_view.badge_transcribing_title')}>●●●</span
>
{:else if dream.processingStatus === 'failed'} {:else if dream.processingStatus === 'failed'}
<span class="badge failed" title={dream.processingError ?? 'Fehler'}>!</span> <span
class="badge failed"
title={dream.processingError ?? $_('dreams.list_view.badge_failed_title')}
>!</span
>
{/if} {/if}
{#if dream.isLucid}<span class="badge lucid">&#x2728;</span>{/if} {#if dream.isLucid}<span class="badge lucid">&#x2728;</span>{/if}
{#if dream.isRecurring}<span class="badge">&#x21bb;</span>{/if} {#if dream.isRecurring}<span class="badge">&#x21bb;</span>{/if}
@ -432,7 +459,7 @@
<span>{formatDreamDate(dream.dreamDate)}</span> <span>{formatDreamDate(dream.dreamDate)}</span>
{#if dream.transcriptModel} {#if dream.transcriptModel}
<span class="dot">·</span> <span class="dot">·</span>
<span class="stt-chip" title="STT-Pipeline"> <span class="stt-chip" title={$_('dreams.list_view.stt_chip_title')}>
&#x1f3a4; {dream.transcriptModel} &#x1f3a4; {dream.transcriptModel}
</span> </span>
{/if} {/if}
@ -461,21 +488,21 @@
{/each} {/each}
{#if filtered.length === 0 && dreams.length > 0} {#if filtered.length === 0 && dreams.length > 0}
<p class="empty">Keine Treffer</p> <p class="empty">{$_('dreams.list_view.empty_no_match')}</p>
{/if} {/if}
</div> </div>
{#if dreams.length === 0} {#if dreams.length === 0}
<p class="empty">Erzähl deinen ersten Traum.</p> <p class="empty">{$_('dreams.list_view.empty_no_dreams')}</p>
{/if} {/if}
<FloatingInputBar <FloatingInputBar
bind:value={newTitle} bind:value={newTitle}
placeholder="Was hast du geträumt?" placeholder={$_('dreams.list_view.input_placeholder')}
onSubmit={handleQuickCreate} onSubmit={handleQuickCreate}
voice voice
voiceFeature="dreams-voice-capture" voiceFeature="dreams-voice-capture"
voiceReason="Sprach-Aufnahmen werden verschlüsselt in deinem persönlichen Tagebuch gespeichert. Dafür brauchst du ein Mana-Konto." voiceReason={$_('dreams.list_view.voice_reason')}
onVoiceComplete={handleVoiceComplete} onVoiceComplete={handleVoiceComplete}
/> />

View file

@ -117,7 +117,6 @@
"apps/mana/apps/web/src/lib/modules/core/widgets/RecentContactsWidget.svelte": 2, "apps/mana/apps/web/src/lib/modules/core/widgets/RecentContactsWidget.svelte": 2,
"apps/mana/apps/web/src/lib/modules/core/widgets/TasksTodayWidget.svelte": 1, "apps/mana/apps/web/src/lib/modules/core/widgets/TasksTodayWidget.svelte": 1,
"apps/mana/apps/web/src/lib/modules/core/widgets/UpcomingEventsWidget.svelte": 1, "apps/mana/apps/web/src/lib/modules/core/widgets/UpcomingEventsWidget.svelte": 1,
"apps/mana/apps/web/src/lib/modules/dreams/ListView.svelte": 12,
"apps/mana/apps/web/src/lib/modules/dreams/views/SymbolDetailView.svelte": 8, "apps/mana/apps/web/src/lib/modules/dreams/views/SymbolDetailView.svelte": 8,
"apps/mana/apps/web/src/lib/modules/drink/ListView.svelte": 5, "apps/mana/apps/web/src/lib/modules/drink/ListView.svelte": 5,
"apps/mana/apps/web/src/lib/modules/finance/ListView.svelte": 6, "apps/mana/apps/web/src/lib/modules/finance/ListView.svelte": 6,

View file

@ -6,6 +6,7 @@
"apps/mana/apps/web/src/lib/modules/ai-agents/ListView.svelte": 2, "apps/mana/apps/web/src/lib/modules/ai-agents/ListView.svelte": 2,
"apps/mana/apps/web/src/lib/modules/ai-missions/ListView.svelte": 2, "apps/mana/apps/web/src/lib/modules/ai-missions/ListView.svelte": 2,
"apps/mana/apps/web/src/lib/modules/credits/ListView.svelte": 1, "apps/mana/apps/web/src/lib/modules/credits/ListView.svelte": 1,
"apps/mana/apps/web/src/lib/modules/dreams/ListView.svelte": 1,
"apps/mana/apps/web/src/lib/modules/firsts/ListView.svelte": 2, "apps/mana/apps/web/src/lib/modules/firsts/ListView.svelte": 2,
"apps/mana/apps/web/src/lib/modules/invoices/components/StatusBadge.svelte": 1, "apps/mana/apps/web/src/lib/modules/invoices/components/StatusBadge.svelte": 1,
"apps/mana/apps/web/src/lib/modules/invoices/constants.ts": 1, "apps/mana/apps/web/src/lib/modules/invoices/constants.ts": 1,