i18n(body+mood+questions): translate picker/quick-log/question-detail via $_()

- body/components/ExercisePicker: route remaining German strings
  (dialog/close/filter_all/create-form) through body.exercisePicker.*;
  drops { default: '...' } fallbacks now that all keys resolve
- mood/components/QuickLog: route through mood.quick_log.*
- questions/views/DetailView: route through questions.detail.*
  + dynamic questions.status.<id> / questions.priority.<id> /
  questions.detail.depth_<id>; drops local statusLabels/priorityLabels/
  depthLabels const blocks (re-uses existing status+priority keys
  extended with `normal`/`urgent`)

Baseline 851 → 833 (-18).
This commit is contained in:
Till JS 2026-04-27 19:13:18 +02:00
parent 136d3fbf87
commit 7dfa1c74be
5 changed files with 91 additions and 70 deletions

View file

@ -95,20 +95,25 @@
onkeydown={handleKey}
role="dialog"
aria-modal="true"
aria-label="Übung auswählen"
aria-label={$_('body.exercisePicker.dialog_label')}
tabindex="-1"
>
<div class="sheet">
<header>
<h2>{$_('body.exercisePicker.title', { default: 'Übung wählen' })}</h2>
<button type="button" class="close" onclick={onClose} aria-label="Schließen">×</button>
<h2>{$_('body.exercisePicker.title')}</h2>
<button
type="button"
class="close"
onclick={onClose}
aria-label={$_('body.exercisePicker.close_aria')}>×</button
>
</header>
<!-- svelte-ignore a11y_autofocus -->
<input
class="search"
type="search"
placeholder={$_('body.exercisePicker.search', { default: 'Suchen…' })}
placeholder={$_('body.exercisePicker.search')}
bind:value={query}
autofocus
/>
@ -120,7 +125,7 @@
class:active={activeFilter === 'all'}
onclick={() => (activeFilter = 'all')}
>
Alle
{$_('body.exercisePicker.filter_all')}
</button>
{#each MUSCLE_GROUPS as g (g)}
<button
@ -137,7 +142,7 @@
<div class="results">
{#if filtered.length === 0}
<p class="empty">
{$_('body.exercisePicker.empty', { default: 'Nichts gefunden' })}
{$_('body.exercisePicker.empty')}
</p>
{:else}
<ul>
@ -168,18 +173,25 @@
<footer>
{#if creating}
<form class="create-form" onsubmit={createInline}>
<input type="text" placeholder="Name" bind:value={newName} required />
<input
type="text"
placeholder={$_('body.exercisePicker.placeholder_name')}
bind:value={newName}
required
/>
<select bind:value={newMuscle}>
{#each MUSCLE_GROUPS as g (g)}
<option value={g}>{$_(`body.muscle.${g}`, { default: g })}</option>
<option value={g}>{$_(`body.muscle.${g}`)}</option>
{/each}
</select>
<button type="submit" class="primary">Anlegen</button>
<button type="button" onclick={() => (creating = false)}>Abbrechen</button>
<button type="submit" class="primary">{$_('body.exercisePicker.action_create')}</button>
<button type="button" onclick={() => (creating = false)}
>{$_('body.exercisePicker.action_cancel')}</button
>
</form>
{:else}
<button type="button" class="add" onclick={() => (creating = true)}>
+ {$_('body.exercisePicker.create', { default: 'Neue Übung anlegen' })}
+ {$_('body.exercisePicker.create')}
</button>
{/if}
</footer>

View file

@ -12,6 +12,7 @@
type CoreEmotion,
type ActivityContext,
} from '../types';
import { _ } from 'svelte-i18n';
interface Props {
onComplete: () => void;
@ -27,9 +28,15 @@
let selectedTags = $state<string[]>([]);
let showDetails = $state(false);
let positiveEmotions = $derived(CORE_EMOTIONS.filter((e) => EMOTION_META[e].valence === 'positive'));
let neutralEmotions = $derived(CORE_EMOTIONS.filter((e) => EMOTION_META[e].valence === 'neutral'));
let negativeEmotions = $derived(CORE_EMOTIONS.filter((e) => EMOTION_META[e].valence === 'negative'));
let positiveEmotions = $derived(
CORE_EMOTIONS.filter((e) => EMOTION_META[e].valence === 'positive')
);
let neutralEmotions = $derived(
CORE_EMOTIONS.filter((e) => EMOTION_META[e].valence === 'neutral')
);
let negativeEmotions = $derived(
CORE_EMOTIONS.filter((e) => EMOTION_META[e].valence === 'negative')
);
function toggleTag(tag: string) {
if (selectedTags.includes(tag)) {
@ -69,7 +76,7 @@
<div class="quick-log">
<div class="ql-header">
<span class="ql-title">Wie geht es dir?</span>
<span class="ql-title">{$_('mood.quick_log.title')}</span>
<button class="ql-close" onclick={onCancel}>×</button>
</div>
@ -85,14 +92,14 @@
style:accent-color={levelColor(level)}
/>
<div class="level-labels">
<span>Schlecht</span>
<span>Super</span>
<span>{$_('mood.quick_log.level_low')}</span>
<span>{$_('mood.quick_log.level_high')}</span>
</div>
</div>
<!-- Emotion Picker -->
<div class="emotion-section">
<span class="section-label">Was fühlst du?</span>
<span class="section-label">{$_('mood.quick_log.section_emotion')}</span>
<div class="emotion-grid">
{#each [...positiveEmotions, ...neutralEmotions, ...negativeEmotions] as e}
<button
@ -110,12 +117,12 @@
<!-- Details Toggle -->
{#if !showDetails}
<button class="details-toggle" onclick={() => (showDetails = true)}>
+ Details hinzufügen
{$_('mood.quick_log.action_show_details')}
</button>
{:else}
<!-- Activity -->
<div class="activity-section">
<span class="section-label">Was machst du gerade?</span>
<span class="section-label">{$_('mood.quick_log.section_activity')}</span>
<div class="activity-grid">
{#each Object.entries(ACTIVITY_LABELS) as [key, meta]}
<button
@ -132,14 +139,14 @@
<!-- Tags -->
<div class="tags-section">
<span class="section-label">Tags</span>
<span class="section-label">{$_('mood.quick_log.section_tags')}</span>
<div class="tags-row">
{#each MOOD_TAG_PRESETS as tag}
<button
class="tag-chip"
class:active={selectedTags.includes(tag)}
onclick={() => toggleTag(tag)}
>{tag}</button>
onclick={() => toggleTag(tag)}>{tag}</button
>
{/each}
</div>
</div>
@ -147,7 +154,7 @@
<!-- Notes -->
<textarea
class="notes-input"
placeholder="Notizen (optional)..."
placeholder={$_('mood.quick_log.placeholder_notes')}
bind:value={notes}
rows="2"
></textarea>
@ -155,7 +162,7 @@
<!-- Save -->
<button class="save-btn" onclick={handleSave} disabled={!emotion}>
Speichern
{$_('mood.quick_log.action_save')}
</button>
</div>
@ -256,11 +263,15 @@
background: hsl(var(--color-background));
border: 2px solid transparent;
cursor: pointer;
transition: transform 0.1s, border-color 0.15s;
transition:
transform 0.1s,
border-color 0.15s;
color: hsl(var(--color-foreground));
}
.emotion-btn:hover { transform: scale(1.05); }
.emotion-btn:hover {
transform: scale(1.05);
}
.emotion-btn.selected {
border-color: #f59e0b;
@ -271,8 +282,15 @@
background: hsl(40 30% 15%);
}
.emo-emoji { font-size: 1rem; line-height: 1; }
.emo-label { font-size: 0.5rem; text-align: center; line-height: 1.1; }
.emo-emoji {
font-size: 1rem;
line-height: 1;
}
.emo-label {
font-size: 0.5rem;
text-align: center;
line-height: 1.1;
}
/* ── Activity ─────────────────────────────────── */
.activity-section {
@ -307,8 +325,12 @@
color: hsl(var(--color-foreground));
}
.act-emoji { font-size: 0.875rem; }
.act-label { line-height: 1.1; }
.act-emoji {
font-size: 0.875rem;
}
.act-label {
line-height: 1.1;
}
/* ── Tags & Notes ─────────────────────────────── */
.tags-section {

View file

@ -10,6 +10,7 @@
import DetailViewShell from '$lib/components/DetailViewShell.svelte';
import type { ViewProps } from '$lib/app-registry';
import type { LocalQuestion, QuestionStatus, QuestionPriority, ResearchDepth } from '../types';
import { _ } from 'svelte-i18n';
let { params, goBack }: ViewProps = $props();
let questionId = $derived(params.questionId as string);
@ -36,7 +37,7 @@
async function saveField() {
detail.blur();
const diff: Record<string, unknown> = {
title: editTitle.trim() || detail.entity?.title || 'Ohne Titel',
title: editTitle.trim() || detail.entity?.title || $_('questions.detail.title_fallback'),
description: editDescription.trim() || undefined,
status: editStatus,
priority: editPriority,
@ -60,45 +61,25 @@
});
}
const statusLabels: Record<QuestionStatus, string> = {
open: 'Offen',
researching: 'Recherche',
answered: 'Beantwortet',
archived: 'Archiviert',
};
const priorityLabels: Record<QuestionPriority, string> = {
low: 'Niedrig',
normal: 'Normal',
high: 'Hoch',
urgent: 'Dringend',
};
const priorityColors: Record<QuestionPriority, string> = {
low: '#9ca3af',
normal: '#3b82f6',
high: '#f59e0b',
urgent: '#ef4444',
};
const depthLabels: Record<ResearchDepth, string> = {
quick: 'Schnell',
standard: 'Standard',
deep: 'Tiefgehend',
};
</script>
<DetailViewShell
entity={detail.entity}
loading={detail.loading}
notFoundLabel="Frage nicht gefunden"
notFoundLabel={$_('questions.detail.not_found')}
confirmDelete={detail.confirmDelete}
onAskDelete={detail.askDelete}
onCancelDelete={detail.cancelDelete}
confirmDeleteLabel="Frage wirklich löschen?"
confirmDeleteLabel={$_('questions.detail.confirm_delete')}
onConfirmDelete={() =>
detail.deleteWithUndo({
label: 'Frage gelöscht',
label: $_('questions.detail.toast_deleted'),
delete: deleteQuestion,
goBack,
})}
@ -109,21 +90,21 @@
bind:value={editTitle}
onfocus={detail.focus}
onblur={saveField}
placeholder="Titel..."
placeholder={$_('questions.detail.placeholder_title')}
/>
<div class="properties">
<div class="prop-row">
<span class="prop-label">Status</span>
<span class="prop-label">{$_('questions.detail.prop_status')}</span>
<select class="prop-select" bind:value={editStatus} onchange={handleSelectChange}>
{#each ['open', 'researching', 'answered', 'archived'] as const as s}
<option value={s}>{statusLabels[s]}</option>
<option value={s}>{$_('questions.status.' + s)}</option>
{/each}
</select>
</div>
<div class="prop-row">
<span class="prop-label">Priorität</span>
<span class="prop-label">{$_('questions.detail.prop_priority')}</span>
<select
class="prop-select"
bind:value={editPriority}
@ -131,36 +112,36 @@
style="color: {priorityColors[editPriority]}"
>
{#each ['low', 'normal', 'high', 'urgent'] as const as p}
<option value={p}>{priorityLabels[p]}</option>
<option value={p}>{$_('questions.priority.' + p)}</option>
{/each}
</select>
</div>
<div class="prop-row">
<span class="prop-label">Recherchetiefe</span>
<span class="prop-label">{$_('questions.detail.prop_research_depth')}</span>
<select class="prop-select" bind:value={editResearchDepth} onchange={handleSelectChange}>
{#each ['quick', 'standard', 'deep'] as const as d}
<option value={d}>{depthLabels[d]}</option>
<option value={d}>{$_('questions.detail.depth_' + d)}</option>
{/each}
</select>
</div>
</div>
<div class="section">
<span class="section-label">Beschreibung</span>
<span class="section-label">{$_('questions.detail.section_description')}</span>
<textarea
class="description-input"
bind:value={editDescription}
onfocus={detail.focus}
onblur={saveField}
placeholder="Beschreibung hinzufügen..."
placeholder={$_('questions.detail.placeholder_description')}
rows={3}
></textarea>
</div>
{#if question.tags.length > 0}
<div class="section">
<span class="section-label">Tags</span>
<span class="section-label">{$_('questions.detail.section_tags')}</span>
<div class="tag-list">
{#each question.tags as tag}
<span class="tag">{tag}</span>
@ -170,9 +151,17 @@
{/if}
<div class="meta">
<span>Erstellt: {formatDate(new Date(question.createdAt ?? ''))}</span>
<span
>{$_('questions.detail.meta_created', {
values: { date: formatDate(new Date(question.createdAt ?? '')) },
})}</span
>
{#if question.updatedAt}
<span>Bearbeitet: {formatDate(new Date(question.updatedAt))}</span>
<span
>{$_('questions.detail.meta_updated', {
values: { date: formatDate(new Date(question.updatedAt)) },
})}</span
>
{/if}
</div>
{/snippet}

View file

@ -55,7 +55,6 @@
"apps/mana/apps/web/src/lib/modules/articles/widgets/ArticlesUnreadWidget.svelte": 1,
"apps/mana/apps/web/src/lib/modules/augur/SharedAugurEntryView.svelte": 1,
"apps/mana/apps/web/src/lib/modules/body/components/CalorieWeightChart.svelte": 1,
"apps/mana/apps/web/src/lib/modules/body/components/ExercisePicker.svelte": 6,
"apps/mana/apps/web/src/lib/modules/body/components/ExerciseProgressionChart.svelte": 1,
"apps/mana/apps/web/src/lib/modules/body/components/PhaseManager.svelte": 1,
"apps/mana/apps/web/src/lib/modules/body/components/RoutineManager.svelte": 3,
@ -125,7 +124,6 @@
"apps/mana/apps/web/src/lib/modules/meditate/components/SessionPlayer.svelte": 3,
"apps/mana/apps/web/src/lib/modules/meditate/components/StatsOverview.svelte": 3,
"apps/mana/apps/web/src/lib/modules/meditate/ListView.svelte": 1,
"apps/mana/apps/web/src/lib/modules/mood/components/QuickLog.svelte": 6,
"apps/mana/apps/web/src/lib/modules/moodlit/components/mood/CreateMoodDialog.svelte": 3,
"apps/mana/apps/web/src/lib/modules/music/ListView.svelte": 3,
"apps/mana/apps/web/src/lib/modules/news-research/ListView.svelte": 3,
@ -146,7 +144,6 @@
"apps/mana/apps/web/src/lib/modules/profile/ContextInterview.svelte": 6,
"apps/mana/apps/web/src/lib/modules/profile/MeImagesView.svelte": 2,
"apps/mana/apps/web/src/lib/modules/questions/ListView.svelte": 1,
"apps/mana/apps/web/src/lib/modules/questions/views/DetailView.svelte": 6,
"apps/mana/apps/web/src/lib/modules/quiz/ListView.svelte": 5,
"apps/mana/apps/web/src/lib/modules/quotes/views/DetailView.svelte": 2,
"apps/mana/apps/web/src/lib/modules/research-lab/components/CompareColumn.svelte": 2,

View file

@ -23,6 +23,7 @@
"apps/mana/apps/web/src/lib/modules/period/ListView.svelte": 1,
"apps/mana/apps/web/src/lib/modules/places/views/DetailView.svelte": 1,
"apps/mana/apps/web/src/lib/modules/plants/ListView.svelte": 5,
"apps/mana/apps/web/src/lib/modules/questions/views/DetailView.svelte": 3,
"apps/mana/apps/web/src/lib/modules/quiz/EditView.svelte": 1,
"apps/mana/apps/web/src/lib/modules/quotes/components/QuoteCard.svelte": 4,
"apps/mana/apps/web/src/lib/modules/recipes/ListView.svelte": 1,