i18n(dreams): translate SymbolDetailView via $_() — header, merge dialog, sections, dream list

- Back button (← Symbole), Gespeichert hint, Zusammenführen…/Löschen actions
- Merge panel: label with {name} interpolation, "– Symbol wählen –" placeholder, OK/Abbrechen
- Empty: "Symbol nicht gefunden."
- Editable header: name placeholder, "Traum"/"Träume" via count_singular/plural
- Color picker: aria with {color} interpolation
- 4 section labels (Meine Bedeutung / Stimmungs-Verteilung / Häufig zusammen mit / Träume mit diesem Symbol) + meaning placeholder
- Mood label routed via $_('dreams.moods.' + mood) with valid-mood guard; "Unbekannt" fallback via symbol_detail.mood_unknown
- Co-occurring chip title with {name} interpolation
- Confirms: delete + merge with {name}/{source}/{target} interpolation
- Dream-ref title fallback via dreams.list_view.untitled
- MOOD_LABELS import dropped (constant kept in types.ts for non-Svelte callers)

Baselines: hardcoded 1074 → 1066 (8 cleared); missing-keys baseline +0 (dreams.moods.* dynamic key already baselined).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-04-27 14:41:30 +02:00
parent 2491649767
commit 258edaa07d
3 changed files with 42 additions and 25 deletions

View file

@ -12,7 +12,8 @@
useAllDreamSymbols, useAllDreamSymbols,
} from '../queries'; } from '../queries';
import { dreamsStore } from '../stores/dreams.svelte'; import { dreamsStore } from '../stores/dreams.svelte';
import { MOOD_COLORS, MOOD_LABELS, type Dream, type DreamMood } from '../types'; import { MOOD_COLORS, type Dream, type DreamMood } from '../types';
import { _ } from 'svelte-i18n';
let { let {
symbolId, symbolId,
@ -107,7 +108,7 @@
async function handleDelete() { async function handleDelete() {
if (!symbol) return; if (!symbol) return;
const ok = confirm( const ok = confirm(
`Symbol "${symbol.name}" wirklich löschen? Es wird aus allen Träumen entfernt.` $_('dreams.symbol_detail.confirm_delete', { values: { name: symbol.name } })
); );
if (!ok) return; if (!ok) return;
await dreamsStore.deleteSymbol(symbol.id); await dreamsStore.deleteSymbol(symbol.id);
@ -119,7 +120,9 @@
const target = symbols.find((s) => s.id === mergeTargetId); const target = symbols.find((s) => s.id === mergeTargetId);
if (!target) return; if (!target) return;
const ok = confirm( const ok = confirm(
`"${symbol.name}" in "${target.name}" zusammenführen? Alle Träume werden umgeschrieben.` $_('dreams.symbol_detail.confirm_merge', {
values: { source: symbol.name, target: target.name },
})
); );
if (!ok) return; if (!ok) return;
await dreamsStore.mergeSymbols(symbol.id, mergeTargetId); await dreamsStore.mergeSymbols(symbol.id, mergeTargetId);
@ -139,45 +142,54 @@
} }
function moodLabel(mood: string): string { function moodLabel(mood: string): string {
if (mood in MOOD_LABELS) return MOOD_LABELS[mood as DreamMood]; const valid: DreamMood[] = ['angenehm', 'neutral', 'unangenehm', 'albtraum'];
return 'Unbekannt'; if (valid.includes(mood as DreamMood)) return $_('dreams.moods.' + mood);
return $_('dreams.symbol_detail.mood_unknown');
} }
</script> </script>
<div class="detail-view"> <div class="detail-view">
<div class="header"> <div class="header">
<button class="back-btn" onclick={onBack}> Symbole</button> <button class="back-btn" onclick={onBack}>{$_('dreams.symbol_detail.back')}</button>
<div class="header-actions"> <div class="header-actions">
{#if savedHint} {#if savedHint}
<span class="saved-hint">Gespeichert</span> <span class="saved-hint">{$_('dreams.symbol_detail.saved')}</span>
{/if} {/if}
{#if symbol && mergeCandidates.length > 0} {#if symbol && mergeCandidates.length > 0}
<button class="meta-btn" onclick={() => (mergeOpen = !mergeOpen)}>Zusammenführen…</button> <button class="meta-btn" onclick={() => (mergeOpen = !mergeOpen)}
>{$_('dreams.symbol_detail.action_merge')}</button
>
{/if} {/if}
{#if symbol} {#if symbol}
<button class="del-btn" onclick={handleDelete}>Löschen</button> <button class="del-btn" onclick={handleDelete}
>{$_('dreams.symbol_detail.action_delete')}</button
>
{/if} {/if}
</div> </div>
</div> </div>
{#if mergeOpen && symbol} {#if mergeOpen && symbol}
<div class="merge-panel"> <div class="merge-panel">
<span class="merge-label">"{symbol.name}" zusammenführen mit:</span> <span class="merge-label"
>{$_('dreams.symbol_detail.merge_label', { values: { name: symbol.name } })}</span
>
<select class="merge-select" bind:value={mergeTargetId}> <select class="merge-select" bind:value={mergeTargetId}>
<option value=""> Symbol wählen </option> <option value="">{$_('dreams.symbol_detail.merge_select_default')}</option>
{#each mergeCandidates as c} {#each mergeCandidates as c}
<option value={c.id}>{c.name} ({c.count})</option> <option value={c.id}>{c.name} ({c.count})</option>
{/each} {/each}
</select> </select>
<button class="merge-confirm" disabled={!mergeTargetId} onclick={handleMerge}>OK</button> <button class="merge-confirm" disabled={!mergeTargetId} onclick={handleMerge}
>{$_('dreams.symbol_detail.merge_confirm')}</button
>
<button class="merge-cancel" onclick={() => ((mergeOpen = false), (mergeTargetId = ''))} <button class="merge-cancel" onclick={() => ((mergeOpen = false), (mergeTargetId = ''))}
>Abbrechen</button >{$_('dreams.symbol_detail.merge_cancel')}</button
> >
</div> </div>
{/if} {/if}
{#if !symbol} {#if !symbol}
<p class="empty">Symbol nicht gefunden.</p> <p class="empty">{$_('dreams.symbol_detail.empty_not_found')}</p>
{:else} {:else}
<!-- Editable header --> <!-- Editable header -->
<div class="sym-header"> <div class="sym-header">
@ -185,10 +197,15 @@
class="name-input" class="name-input"
type="text" type="text"
bind:value={editName} bind:value={editName}
placeholder="Symbolname" placeholder={$_('dreams.symbol_detail.name_placeholder')}
style="color: {editColor}" style="color: {editColor}"
/> />
<span class="count-badge">{symbol.count} {symbol.count === 1 ? 'Traum' : 'Träume'}</span> <span class="count-badge"
>{symbol.count}
{symbol.count === 1
? $_('dreams.symbol_detail.count_singular')
: $_('dreams.symbol_detail.count_plural')}</span
>
</div> </div>
<!-- Color picker --> <!-- Color picker -->
@ -199,18 +216,18 @@
class:active={editColor === color} class:active={editColor === color}
style="background: {color}" style="background: {color}"
onclick={() => (editColor = color)} onclick={() => (editColor = color)}
aria-label={`Farbe ${color}`} aria-label={$_('dreams.symbol_detail.color_aria', { values: { color } })}
></button> ></button>
{/each} {/each}
</div> </div>
<!-- Meaning --> <!-- Meaning -->
<div class="section"> <div class="section">
<span class="section-label">Meine Bedeutung</span> <span class="section-label">{$_('dreams.symbol_detail.label_meaning')}</span>
<textarea <textarea
class="meaning-input" class="meaning-input"
bind:value={editMeaning} bind:value={editMeaning}
placeholder="Was bedeutet dieses Symbol für dich? (optional)" placeholder={$_('dreams.symbol_detail.meaning_placeholder')}
rows="3" rows="3"
></textarea> ></textarea>
</div> </div>
@ -218,7 +235,7 @@
<!-- Mood distribution --> <!-- Mood distribution -->
{#if moodDist.length > 0} {#if moodDist.length > 0}
<div class="section"> <div class="section">
<span class="section-label">Stimmungs-Verteilung</span> <span class="section-label">{$_('dreams.symbol_detail.label_mood_dist')}</span>
<div class="bars"> <div class="bars">
{#each moodDist as m} {#each moodDist as m}
<div class="bar-row"> <div class="bar-row">
@ -239,13 +256,13 @@
<!-- Co-occurring --> <!-- Co-occurring -->
{#if cooccurring.length > 0} {#if cooccurring.length > 0}
<div class="section"> <div class="section">
<span class="section-label">Häufig zusammen mit</span> <span class="section-label">{$_('dreams.symbol_detail.label_cooccurring')}</span>
<div class="cooc-row"> <div class="cooc-row">
{#each cooccurring as c} {#each cooccurring as c}
<button <button
class="cooc-chip" class="cooc-chip"
onclick={() => navigateToCooccurring(c.name)} onclick={() => navigateToCooccurring(c.name)}
title={`Zu "${c.name}" wechseln`} title={$_('dreams.symbol_detail.cooccurring_title', { values: { name: c.name } })}
> >
{c.name} <span class="cooc-count">{c.count}</span> {c.name} <span class="cooc-count">{c.count}</span>
</button> </button>
@ -257,7 +274,7 @@
<!-- Dream list --> <!-- Dream list -->
{#if dreamsWithSymbol.length > 0} {#if dreamsWithSymbol.length > 0}
<div class="section"> <div class="section">
<span class="section-label">Träume mit diesem Symbol</span> <span class="section-label">{$_('dreams.symbol_detail.label_dream_list')}</span>
<div class="dream-refs"> <div class="dream-refs">
{#each dreamsWithSymbol as d (d.id)} {#each dreamsWithSymbol as d (d.id)}
<button class="dream-ref" onclick={() => onOpenDream?.(d)} disabled={!onOpenDream}> <button class="dream-ref" onclick={() => onOpenDream?.(d)} disabled={!onOpenDream}>
@ -267,7 +284,7 @@
<span class="ref-dot empty"></span> <span class="ref-dot empty"></span>
{/if} {/if}
<div class="ref-content"> <div class="ref-content">
<span class="ref-title">{d.title || 'Traum ohne Titel'}</span> <span class="ref-title">{d.title || $_('dreams.list_view.untitled')}</span>
<span class="ref-date">{formatDreamDate(d.dreamDate)}</span> <span class="ref-date">{formatDreamDate(d.dreamDate)}</span>
</div> </div>
</button> </button>

View file

@ -110,7 +110,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/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,
"apps/mana/apps/web/src/lib/modules/goals/ListView.svelte": 1, "apps/mana/apps/web/src/lib/modules/goals/ListView.svelte": 1,

View file

@ -10,6 +10,7 @@
"apps/mana/apps/web/src/lib/modules/broadcast/views/DetailView.svelte": 1, "apps/mana/apps/web/src/lib/modules/broadcast/views/DetailView.svelte": 1,
"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/dreams/ListView.svelte": 1,
"apps/mana/apps/web/src/lib/modules/dreams/views/SymbolDetailView.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,