i18n(stretch): wire components to namespace — 34 strings cleared

Patches ListView, AssessmentWizard, ReminderManager, RoutineCreator,
SessionHistory, SessionPlayer, plus the /stretch route page title.

Locale JSONs landed in 421663ba3.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-04-27 00:49:23 +02:00
parent 421663ba3d
commit 722fe74ced
8 changed files with 164 additions and 76 deletions

View file

@ -3,6 +3,7 @@
Streak, quick-start routines, assessment recommendation, recent sessions.
-->
<script lang="ts">
import { _ } from 'svelte-i18n';
import { formatDate } from '$lib/i18n/format';
import {
useAllStretchExercises,
@ -99,17 +100,17 @@
<button
class="tab"
class:active={activeTab === 'dashboard'}
onclick={() => (activeTab = 'dashboard')}>Dashboard</button
onclick={() => (activeTab = 'dashboard')}>{$_('stretch.list_view.tab_dashboard')}</button
>
<button
class="tab"
class:active={activeTab === 'routines'}
onclick={() => (activeTab = 'routines')}>Routinen</button
onclick={() => (activeTab = 'routines')}>{$_('stretch.list_view.tab_routines')}</button
>
<button
class="tab"
class:active={activeTab === 'exercises'}
onclick={() => (activeTab = 'exercises')}>Übungen</button
onclick={() => (activeTab = 'exercises')}>{$_('stretch.list_view.tab_exercises')}</button
>
</div>
@ -118,15 +119,15 @@
<div class="stats-row">
<div class="stat-card">
<span class="stat-value">{streak}</span>
<span class="stat-label">Tage Streak</span>
<span class="stat-label">{$_('stretch.list_view.stat_streak')}</span>
</div>
<div class="stat-card">
<span class="stat-value">{todayMinutes}</span>
<span class="stat-label">Min heute</span>
<span class="stat-label">{$_('stretch.list_view.stat_today')}</span>
</div>
<div class="stat-card">
<span class="stat-value">{weekCount}</span>
<span class="stat-label">Diese Woche</span>
<span class="stat-label">{$_('stretch.list_view.stat_week')}</span>
</div>
</div>
@ -145,13 +146,17 @@
<!-- Quick Start -->
<div class="section">
<div class="section-header">
<span class="section-title">Schnellstart</span>
<span class="section-title">{$_('stretch.list_view.section_quickstart')}</span>
</div>
<div class="routine-grid">
{#each pinnedRoutines as routine (routine.id)}
<button class="routine-card" onclick={() => startRoutine(routine.id)}>
<span class="routine-name">{routine.name}</span>
<span class="routine-meta">{routine.estimatedDurationMin} Min</span>
<span class="routine-meta"
>{$_('stretch.list_view.routine_minutes', {
values: { count: routine.estimatedDurationMin },
})}</span
>
<span class="routine-type"
>{ROUTINE_TYPE_LABELS[routine.routineType]?.de ?? routine.routineType}</span
>
@ -165,26 +170,42 @@
<div class="recommendation">
<div class="rec-header">
{#if weakAreas.length > 0}
<span class="rec-title">Empfohlen für dich</span>
<span class="rec-title">{$_('stretch.list_view.rec_title_for_you')}</span>
<span class="rec-detail">
Schwachstellen: {weakAreas.map((r) => BODY_REGION_LABELS[r]?.de ?? r).join(', ')}
{$_('stretch.list_view.rec_weak_areas', {
values: {
areas: weakAreas.map((r) => BODY_REGION_LABELS[r]?.de ?? r).join(', '),
},
})}
</span>
{:else}
<span class="rec-title">Vorschlag</span>
<span class="rec-title">{$_('stretch.list_view.rec_title_suggestion')}</span>
{/if}
</div>
<button class="rec-btn" onclick={() => startRoutine(recommended.id)}>
{recommended.name} starten ({recommended.estimatedDurationMin} Min)
{$_('stretch.list_view.rec_btn_start', {
values: {
name: recommended.name,
minutes: recommended.estimatedDurationMin,
},
})}
</button>
</div>
{/if}
<!-- Assessment CTA -->
<button class="action-btn" onclick={() => (showAssessment = true)}>
{latestAssessment ? 'Bestandsaufnahme wiederholen' : 'Erste Bestandsaufnahme starten'}
{latestAssessment
? $_('stretch.list_view.assessment_btn_repeat')
: $_('stretch.list_view.assessment_btn_first')}
{#if latestAssessment}
<span class="action-meta"
>Letzte: {relativeDays(latestAssessment.assessedAt)} — Score: {latestAssessment.overallScore}%</span
>{$_('stretch.list_view.assessment_meta', {
values: {
when: relativeDays(latestAssessment.assessedAt),
score: latestAssessment.overallScore,
},
})}</span
>
{/if}
</button>
@ -193,13 +214,19 @@
{#if todaySessions.length > 0}
<div class="section">
<div class="section-header">
<span class="section-title">Heute</span>
<button class="link-btn" onclick={() => (showHistory = true)}>Alle</button>
<span class="section-title">{$_('stretch.list_view.section_today')}</span>
<button class="link-btn" onclick={() => (showHistory = true)}
>{$_('stretch.list_view.section_all_link')}</button
>
</div>
{#each todaySessions.slice(0, 3) as session (session.id)}
<div class="session-row">
<span class="session-name">{session.routineName}</span>
<span class="session-duration">{Math.round(session.totalDurationSec / 60)} Min</span>
<span class="session-duration"
>{$_('stretch.list_view.routine_minutes', {
values: { count: Math.round(session.totalDurationSec / 60) },
})}</span
>
<span class="session-time">{session.startedAt.split('T')[1]?.slice(0, 5)}</span>
</div>
{/each}
@ -208,8 +235,12 @@
<!-- Quick Actions -->
<div class="quick-actions">
<button class="action-link" onclick={() => (showReminders = true)}>Erinnerungen</button>
<button class="action-link" onclick={() => (showHistory = true)}>Verlauf</button>
<button class="action-link" onclick={() => (showReminders = true)}
>{$_('stretch.list_view.action_reminders')}</button
>
<button class="action-link" onclick={() => (showHistory = true)}
>{$_('stretch.list_view.action_history')}</button
>
</div>
{:else if activeTab === 'routines'}
<!-- All Routines -->
@ -221,13 +252,17 @@
<span class="rli-desc">{routine.description}</span>
</div>
<div class="rli-right">
<span class="rli-duration">{routine.estimatedDurationMin} Min</span>
<span class="rli-duration"
>{$_('stretch.list_view.routine_minutes', {
values: { count: routine.estimatedDurationMin },
})}</span
>
<span class="rli-type">{ROUTINE_TYPE_LABELS[routine.routineType]?.de ?? ''}</span>
</div>
</button>
{/each}
<button class="add-routine-btn" onclick={() => (showCreateRoutine = true)}>
+ Eigene Routine erstellen
{$_('stretch.list_view.action_create_routine')}
</button>
</div>
{:else if activeTab === 'exercises'}
@ -243,7 +278,9 @@
<span class="ex-region">{BODY_REGION_LABELS[ex.bodyRegion]?.de ?? ex.bodyRegion}</span
>
<span class="ex-duration"
>{ex.defaultDurationSec}s{ex.bilateral ? ' /Seite' : ''}</span
>{ex.defaultDurationSec}s{ex.bilateral
? $_('stretch.list_view.side_suffix')
: ''}</span
>
</div>
</div>

View file

@ -3,6 +3,7 @@
6 tests with score selection, pain region marking, and result summary.
-->
<script lang="ts">
import { _ } from 'svelte-i18n';
import {
ASSESSMENT_TESTS,
BODY_REGION_LABELS,
@ -106,7 +107,11 @@
{currentStep === 0 && !showResults ? '×' : '←'}
</button>
<span class="step-label">
{showResults ? 'Ergebnis' : `Schritt ${currentStep + 1} von ${totalSteps}`}
{showResults
? $_('stretch.assessment.result_label')
: $_('stretch.assessment.step_label', {
values: { current: currentStep + 1, total: totalSteps },
})}
</span>
</div>
@ -120,7 +125,7 @@
<div class="results-screen">
<div class="score-circle">
<span class="score-value">{overallScore()}%</span>
<span class="score-label">Beweglichkeit</span>
<span class="score-label">{$_('stretch.assessment.score_label')}</span>
</div>
<div class="results-grid">
@ -143,7 +148,7 @@
{#if weakAreas().length > 0}
<div class="weak-notice">
<span class="weak-title">Verbesserungsbedarf:</span>
<span class="weak-title">{$_('stretch.assessment.weak_title')}</span>
<span class="weak-areas">
{weakAreas()
.map((r) => BODY_REGION_LABELS[r]?.de ?? r)
@ -154,7 +159,7 @@
<!-- Pain Regions (optional) -->
<div class="pain-section">
<span class="pain-title">Schmerzbereiche (optional)</span>
<span class="pain-title">{$_('stretch.assessment.pain_title')}</span>
<div class="pain-add-row">
<select class="pain-select" bind:value={painRegion}>
{#each BODY_REGIONS.filter((r) => r !== 'full_body') as region}
@ -173,7 +178,9 @@
{/each}
</div>
<button class="save-btn" onclick={saveAndFinish}>Speichern</button>
<button class="save-btn" onclick={saveAndFinish}
>{$_('stretch.assessment.action_save')}</button
>
</div>
{:else if currentTest}
<!-- Test Step -->

View file

@ -2,6 +2,8 @@
ReminderManager — Configure stretch reminders (time, days, linked routine).
-->
<script lang="ts">
import { _ } from 'svelte-i18n';
import { untrack } from 'svelte';
import type { StretchReminder, StretchRoutine } from '../types';
import { stretchStore } from '../stores/stretch.svelte';
@ -14,12 +16,20 @@
let { reminders, routines, onClose }: Props = $props();
let showCreate = $state(false);
let newName = $state('Dehn-Erinnerung');
let newName = $state(untrack(() => $_('stretch.reminders.default_name')));
let newTime = $state('09:00');
let newDays = $state<number[]>([1, 2, 3, 4, 5]); // MonFri
let newRoutineId = $state<string | null>(null);
const DAY_LABELS = ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'];
const DAY_LABELS = $derived([
$_('stretch.reminders.day_short_0'),
$_('stretch.reminders.day_short_1'),
$_('stretch.reminders.day_short_2'),
$_('stretch.reminders.day_short_3'),
$_('stretch.reminders.day_short_4'),
$_('stretch.reminders.day_short_5'),
$_('stretch.reminders.day_short_6'),
]);
function toggleDay(day: number) {
if (newDays.includes(day)) {
@ -38,7 +48,7 @@
days: newDays,
});
showCreate = false;
newName = 'Dehn-Erinnerung';
newName = $_('stretch.reminders.default_name');
newTime = '09:00';
newDays = [1, 2, 3, 4, 5];
newRoutineId = null;
@ -56,7 +66,7 @@
<div class="reminder-overlay">
<div class="reminder-header">
<button class="back-btn" onclick={onClose}>←</button>
<span class="header-title">Erinnerungen</span>
<span class="header-title">{$_('stretch.reminders.header_title')}</span>
</div>
<div class="reminder-body">
@ -85,13 +95,20 @@
<span class="rem-routine">{linked.name}</span>
{/if}
{/if}
<button class="rem-delete" onclick={() => deleteReminder(reminder.id)}>Löschen</button>
<button class="rem-delete" onclick={() => deleteReminder(reminder.id)}
>{$_('stretch.reminders.action_delete')}</button
>
</div>
{/each}
{#if showCreate}
<div class="create-form">
<input class="form-input" type="text" placeholder="Name..." bind:value={newName} />
<input
class="form-input"
type="text"
placeholder={$_('stretch.reminders.placeholder_name')}
bind:value={newName}
/>
<input class="form-input time-input" type="time" bind:value={newTime} />
<div class="days-row">
{#each [0, 1, 2, 3, 4, 5, 6] as day}
@ -103,24 +120,28 @@
{/each}
</div>
<select class="form-select" bind:value={newRoutineId}>
<option value={null}>Keine Routine verknüpft</option>
<option value={null}>{$_('stretch.reminders.select_no_routine')}</option>
{#each routines as routine}
<option value={routine.id}>{routine.name}</option>
{/each}
</select>
<div class="form-actions">
<button class="btn-cancel" onclick={() => (showCreate = false)}>Abbrechen</button>
<button class="btn-cancel" onclick={() => (showCreate = false)}
>{$_('stretch.reminders.action_cancel')}</button
>
<button class="btn-save" onclick={handleCreate} disabled={newDays.length === 0}>
Erstellen
{$_('stretch.reminders.action_create')}
</button>
</div>
</div>
{:else}
<button class="add-btn" onclick={() => (showCreate = true)}> + Neue Erinnerung </button>
<button class="add-btn" onclick={() => (showCreate = true)}>
{$_('stretch.reminders.action_new')}
</button>
{/if}
{#if reminders.length === 0 && !showCreate}
<p class="empty-text">Noch keine Erinnerungen eingerichtet.</p>
<p class="empty-text">{$_('stretch.reminders.empty')}</p>
{/if}
</div>
</div>

View file

@ -2,6 +2,7 @@
RoutineCreator — Build a custom stretch routine from the exercise library.
-->
<script lang="ts">
import { _ } from 'svelte-i18n';
import type { StretchExercise, RoutineExercise, BodyRegion } from '../types';
import { BODY_REGION_LABELS } from '../types';
import { stretchStore } from '../stores/stretch.svelte';
@ -87,13 +88,13 @@
<div class="creator-overlay">
<div class="creator-header">
<button class="back-btn" onclick={onCancel}>×</button>
<span class="header-title">Neue Routine</span>
<span class="header-title">{$_('stretch.creator.header_title')}</span>
<button
class="save-btn"
onclick={handleSave}
disabled={!name.trim() || selectedExercises.length === 0}
>
Speichern
{$_('stretch.creator.action_save')}
</button>
</div>
@ -104,14 +105,14 @@
<input
class="name-input"
type="text"
placeholder="Name der Routine..."
placeholder={$_('stretch.creator.placeholder_name')}
bind:value={name}
autofocus
/>
<input
class="desc-input"
type="text"
placeholder="Beschreibung (optional)..."
placeholder={$_('stretch.creator.placeholder_description')}
bind:value={description}
/>
</div>
@ -120,7 +121,9 @@
{#if selectedExercises.length > 0}
<div class="selected-section">
<span class="section-label">
{selectedExercises.length} Übungen &middot; ~{estimatedMin} Min
{$_('stretch.creator.selected_summary', {
values: { count: selectedExercises.length, minutes: estimatedMin },
})}
</span>
{#each selectedExercises as slot, i (slot.exerciseId)}
{@const ex = exercises.find((e) => e.id === slot.exerciseId)}
@ -143,13 +146,13 @@
<!-- Exercise Picker -->
<div class="picker-section">
<span class="section-label">Übung hinzufügen</span>
<span class="section-label">{$_('stretch.creator.section_picker')}</span>
<!-- Region Filter -->
<div class="filter-row">
<button
class="filter-chip"
class:active={filterRegion === 'all'}
onclick={() => (filterRegion = 'all')}>Alle</button
onclick={() => (filterRegion = 'all')}>{$_('stretch.creator.filter_all')}</button
>
{#each ['neck', 'shoulders', 'upper_back', 'lower_back', 'hips', 'hamstrings', 'quads', 'full_body'] as region}
<button

View file

@ -2,6 +2,7 @@
SessionHistory — Past stretch sessions with calendar heatmap and stats.
-->
<script lang="ts">
import { _ } from 'svelte-i18n';
import type { StretchSession, StretchRoutine } from '../types';
import {
getSessionsPerDay,
@ -36,7 +37,7 @@
<div class="history-overlay">
<div class="history-header">
<button class="back-btn" onclick={onClose}>←</button>
<span class="header-title">Verlauf</span>
<span class="header-title">{$_('stretch.history.header_title')}</span>
</div>
<div class="history-body">
@ -44,21 +45,21 @@
<div class="stats-row">
<div class="stat">
<span class="stat-val">{totalSessions}</span>
<span class="stat-lbl">Sessions</span>
<span class="stat-lbl">{$_('stretch.history.stat_sessions')}</span>
</div>
<div class="stat">
<span class="stat-val">{totalMinutes}</span>
<span class="stat-lbl">Minuten</span>
<span class="stat-lbl">{$_('stretch.history.stat_minutes')}</span>
</div>
<div class="stat">
<span class="stat-val">{streak}</span>
<span class="stat-lbl">Streak</span>
<span class="stat-lbl">{$_('stretch.history.stat_streak')}</span>
</div>
</div>
<!-- 30-Day Heatmap -->
<div class="heatmap-section">
<span class="section-label">Letzte 30 Tage</span>
<span class="section-label">{$_('stretch.history.section_30_days')}</span>
<div class="heatmap-grid">
{#each last30 as day}
<div
@ -75,7 +76,7 @@
<!-- Body Region Balance -->
{#if regionBalance.length > 0}
<div class="balance-section">
<span class="section-label">Körperregion-Balance</span>
<span class="section-label">{$_('stretch.history.section_balance')}</span>
{#each regionBalance.slice(0, 6) as rb}
{@const maxCount = regionBalance[0]?.count ?? 1}
<div class="balance-row">
@ -91,14 +92,19 @@
<!-- Session List -->
<div class="session-list">
<span class="section-label">Alle Sessions</span>
<span class="section-label">{$_('stretch.history.section_all_sessions')}</span>
{#each sessions.slice(0, 50) as session (session.id)}
<div class="session-item">
<div class="si-left">
<span class="si-name">{session.routineName}</span>
<span class="si-meta">
{Math.round(session.totalDurationSec / 60)} Min &middot;
{session.completedExercises}/{session.totalExercises} Übungen
{$_('stretch.history.session_meta', {
values: {
minutes: Math.round(session.totalDurationSec / 60),
completed: session.completedExercises,
total: session.totalExercises,
},
})}
{#if session.mood}
&middot; {['😫', '😕', '😐', '😊', '🤩'][session.mood - 1]}
{/if}

View file

@ -3,6 +3,7 @@
Fullscreen overlay with exercise instructions, countdown, side-switch, skip/pause.
-->
<script lang="ts">
import { _ } from 'svelte-i18n';
import type { StretchRoutine, StretchExercise, RoutineExercise } from '../types';
import { BODY_REGION_LABELS } from '../types';
import { stretchStore } from '../stores/stretch.svelte';
@ -214,9 +215,13 @@
<h2 class="ready-title">{routine.name}</h2>
<p class="ready-desc">{routine.description}</p>
<p class="ready-meta">
{totalSlots} Übungen &middot; ~{routine.estimatedDurationMin} Min
{$_('stretch.player.ready_meta', {
values: { count: totalSlots, minutes: routine.estimatedDurationMin },
})}
</p>
<button class="start-btn" onclick={startSession}> Starten </button>
<button class="start-btn" onclick={startSession}>
{$_('stretch.player.action_start')}
</button>
</div>
</div>
{:else if phase === 'finished'}
@ -224,16 +229,23 @@
<div class="finish-screen">
<div class="finish-content">
<div class="finish-check">&#10003;</div>
<h2 class="finish-title">Geschafft!</h2>
<h2 class="finish-title">{$_('stretch.player.finish_title')}</h2>
<p class="finish-stats">
{completedCount} von {totalSlots} Übungen &middot;
{Math.round((Date.now() - sessionStartTime) / 60000)} Min
{$_('stretch.player.finish_stats', {
values: {
completed: completedCount,
total: totalSlots,
minutes: Math.round((Date.now() - sessionStartTime) / 60000),
},
})}
</p>
{#if skippedIds.length > 0}
<p class="finish-skipped">{skippedIds.length} übersprungen</p>
<p class="finish-skipped">
{$_('stretch.player.finish_skipped', { values: { count: skippedIds.length } })}
</p>
{/if}
<div class="mood-section">
<p class="mood-label">Wie fühlst du dich?</p>
<p class="mood-label">{$_('stretch.player.mood_label')}</p>
<div class="mood-row">
{#each [1, 2, 3, 4, 5] as val}
<button
@ -246,7 +258,9 @@
{/each}
</div>
</div>
<button class="done-btn" onclick={handleFinishWithMood}>Fertig</button>
<button class="done-btn" onclick={handleFinishWithMood}
>{$_('stretch.player.action_done')}</button
>
</div>
</div>
{:else}
@ -256,7 +270,7 @@
<button class="close-btn" onclick={handleCancel}>×</button>
<span class="exercise-counter">{currentIndex + 1} / {totalSlots}</span>
{#if isPaused}
<span class="pause-badge">Pause</span>
<span class="pause-badge">{$_('stretch.player.pause_badge')}</span>
{/if}
</div>
@ -264,7 +278,10 @@
{#if currentExercise}
<h2 class="exercise-name">{currentExercise.name}</h2>
{#if currentSide}
<span class="side-badge">{currentSide === 'left' ? 'Linke Seite' : 'Rechte Seite'}</span
<span class="side-badge"
>{currentSide === 'left'
? $_('stretch.player.side_left')
: $_('stretch.player.side_right')}</span
>
{/if}
<span class="exercise-region"
@ -274,9 +291,9 @@
{/if}
{#if phase === 'side_switch'}
<div class="side-switch-notice">Seitenwechsel...</div>
<div class="side-switch-notice">{$_('stretch.player.side_switch_notice')}</div>
{:else if phase === 'rest'}
<div class="rest-notice">Pause</div>
<div class="rest-notice">{$_('stretch.player.rest_notice')}</div>
{/if}
</div>
@ -291,12 +308,14 @@
<!-- Controls -->
<div class="controls">
<button class="ctrl-btn" onclick={previousExercise} disabled={currentIndex <= 0}>
&#9664; Zurück
{$_('stretch.player.action_back')}
</button>
<button class="ctrl-btn pause-btn" onclick={togglePause}>
{isPaused ? '&#9654; Weiter' : '&#10074;&#10074; Pause'}
{isPaused ? $_('stretch.player.action_resume') : $_('stretch.player.action_pause')}
</button>
<button class="ctrl-btn" onclick={skipExercise}>
{$_('stretch.player.action_next')}
</button>
<button class="ctrl-btn" onclick={skipExercise}> Weiter &#9654; </button>
</div>
<!-- Overall Progress -->

View file

@ -1,10 +1,11 @@
<script lang="ts">
import { _ } from 'svelte-i18n';
import ListView from '$lib/modules/stretch/ListView.svelte';
import { RoutePage } from '$lib/components/shell';
</script>
<svelte:head>
<title>Stretch - Mana</title>
<title>{$_('stretch.route.title')} - Mana</title>
</svelte:head>
<RoutePage appId="stretch">