i18n(music+profile): translate detail/hub views via $_()

- music/views/DetailView: route through music.detail.* (new namespace)
- profile/ListView: route through profile.hub.* (new sub-namespace)
  + reuse existing profile.* keys for account-section actions; TABS
  refactored from literal label → labelKey routing through $_()

Baseline 881 → 869 (-12).
This commit is contained in:
Till JS 2026-04-27 18:41:43 +02:00
parent fa401cfeec
commit a5cef980ae
3 changed files with 61 additions and 45 deletions

View file

@ -10,6 +10,7 @@
import { Heart } from '@mana/shared-icons';
import type { ViewProps } from '$lib/app-registry';
import type { LocalSong } from '../types';
import { _ } from 'svelte-i18n';
let { params, goBack }: ViewProps = $props();
let songId = $derived(params.songId as string);
@ -38,7 +39,7 @@
async function saveField() {
detail.blur();
await libraryStore.updateMetadata(songId, {
title: editTitle.trim() || detail.entity?.title || 'Ohne Titel',
title: editTitle.trim() || detail.entity?.title || $_('music.detail.title_fallback'),
artist: editArtist.trim() || undefined,
album: editAlbum.trim() || undefined,
genre: editGenre.trim() || undefined,
@ -62,14 +63,14 @@
<DetailViewShell
entity={detail.entity}
loading={detail.loading}
notFoundLabel="Song nicht gefunden"
notFoundLabel={$_('music.detail.not_found')}
confirmDelete={detail.confirmDelete}
onAskDelete={detail.askDelete}
onCancelDelete={detail.cancelDelete}
confirmDeleteLabel="Song wirklich löschen?"
confirmDeleteLabel={$_('music.detail.confirm_delete')}
onConfirmDelete={() =>
detail.deleteWithUndo({
label: 'Song gelöscht',
label: $_('music.detail.toast_deleted'),
delete: () => libraryStore.delete(songId),
goBack,
})}
@ -81,7 +82,7 @@
bind:value={editTitle}
onfocus={detail.focus}
onblur={saveField}
placeholder="Titel..."
placeholder={$_('music.detail.placeholder_title')}
/>
<button class="fav-btn" class:active={song.favorite} onclick={toggleFavorite}>
<Heart size={18} weight={song.favorite ? 'fill' : 'regular'} />
@ -90,18 +91,18 @@
<div class="properties">
<div class="prop-row">
<span class="prop-label">Künstler</span>
<span class="prop-label">{$_('music.detail.prop_artist')}</span>
<input
class="prop-input"
bind:value={editArtist}
onfocus={detail.focus}
onblur={saveField}
placeholder="Unbekannt"
placeholder={$_('music.detail.prop_artist_placeholder')}
/>
</div>
<div class="prop-row">
<span class="prop-label">Album</span>
<span class="prop-label">{$_('music.detail.prop_album')}</span>
<input
class="prop-input"
bind:value={editAlbum}
@ -112,7 +113,7 @@
</div>
<div class="prop-row">
<span class="prop-label">Genre</span>
<span class="prop-label">{$_('music.detail.prop_genre')}</span>
<input
class="prop-input"
bind:value={editGenre}
@ -123,7 +124,7 @@
</div>
<div class="prop-row">
<span class="prop-label">Jahr</span>
<span class="prop-label">{$_('music.detail.prop_year')}</span>
<input
type="number"
class="prop-input"
@ -135,7 +136,7 @@
</div>
<div class="prop-row">
<span class="prop-label">BPM</span>
<span class="prop-label">{$_('music.detail.prop_bpm')}</span>
<input
type="number"
class="prop-input"
@ -147,23 +148,35 @@
</div>
<div class="prop-row">
<span class="prop-label">Dauer</span>
<span class="prop-label">{$_('music.detail.prop_duration')}</span>
<span class="prop-value">{formatDuration(song.duration)}</span>
</div>
<div class="prop-row">
<span class="prop-label">Wiedergaben</span>
<span class="prop-label">{$_('music.detail.prop_play_count')}</span>
<span class="prop-value">{song.playCount}</span>
</div>
</div>
<div class="meta">
<span>Erstellt: {formatDate(new Date(song.createdAt ?? ''))}</span>
<span
>{$_('music.detail.meta_created', {
values: { date: formatDate(new Date(song.createdAt ?? '')) },
})}</span
>
{#if song.updatedAt}
<span>Bearbeitet: {formatDate(new Date(song.updatedAt))}</span>
<span
>{$_('music.detail.meta_updated', {
values: { date: formatDate(new Date(song.updatedAt)) },
})}</span
>
{/if}
{#if song.lastPlayedAt}
<span>Zuletzt gehört: {formatDate(new Date(song.lastPlayedAt))}</span>
<span
>{$_('music.detail.meta_last_played', {
values: { date: formatDate(new Date(song.lastPlayedAt)) },
})}</span
>
{/if}
</div>
{/snippet}

View file

@ -17,6 +17,7 @@
import ContextFreeform from './ContextFreeform.svelte';
import { useUserContext } from './queries';
import { getProgress } from './questions';
import { _ } from 'svelte-i18n';
type Tab = 'overview' | 'interview' | 'freeform' | 'account';
type InterviewStartMode = 'text' | 'voice' | 'conversation';
@ -44,11 +45,11 @@
}
});
const TABS: { key: Tab; label: string }[] = [
{ key: 'overview', label: 'Übersicht' },
{ key: 'interview', label: 'Interview' },
{ key: 'freeform', label: 'Freitext' },
{ key: 'account', label: 'Konto' },
const TABS: { key: Tab; labelKey: string }[] = [
{ key: 'overview', labelKey: 'profile.hub.tab_overview' },
{ key: 'interview', labelKey: 'profile.hub.tab_interview' },
{ key: 'freeform', labelKey: 'profile.hub.tab_freeform' },
{ key: 'account', labelKey: 'profile.hub.tab_account' },
];
function startInterview(mode: InterviewStartMode) {
@ -58,15 +59,15 @@
function handleProfileUpdate(user: ApiUserProfile) {
apiProfile = user;
toast.success('Profil erfolgreich aktualisiert');
toast.success($_('profile.profile_updated'));
}
function handlePasswordChange() {
toast.success('Passwort erfolgreich geändert');
toast.success($_('profile.password_changed'));
}
async function handleAccountDeleted() {
toast.info('Konto wird gelöscht...');
toast.info($_('profile.hub.toast_account_deleting'));
await authStore.signOut();
goto('/login');
}
@ -89,7 +90,7 @@
if (tab.key !== 'interview') interviewStartMode = null;
}}
>
{tab.label}
{$_(tab.labelKey)}
</button>
{/each}
</nav>
@ -102,12 +103,14 @@
<!-- Interview start hero -->
<div class="interview-hero">
<div class="hero-header">
<h3 class="hero-title">Interview starten</h3>
<h3 class="hero-title">{$_('profile.hub.hero_title')}</h3>
<p class="hero-subtitle">
{#if progress.percent > 0}
{progress.answered} von {progress.total} Fragen beantwortet — mach weiter!
{$_('profile.hub.hero_subtitle_progress', {
values: { answered: progress.answered, total: progress.total },
})}
{:else}
Erzähl Mana mehr über dich, damit die App besser zu dir passt.
{$_('profile.hub.hero_subtitle_initial')}
{/if}
</p>
{#if progress.percent > 0}
@ -133,8 +136,8 @@
</svg>
</span>
<span class="hero-option-text">
<strong>Per Text</strong>
<span>Fragen lesen und tippen</span>
<strong>{$_('profile.hub.option_text_title')}</strong>
<span>{$_('profile.hub.option_text_hint')}</span>
</span>
</button>
<button class="hero-option voice" onclick={() => startInterview('voice')}>
@ -156,8 +159,8 @@
</svg>
</span>
<span class="hero-option-text">
<strong>Per Sprache</strong>
<span>Fragen hören und sprechen</span>
<strong>{$_('profile.hub.option_voice_title')}</strong>
<span>{$_('profile.hub.option_voice_hint')}</span>
</span>
</button>
<button class="hero-option conversation" onclick={() => startInterview('conversation')}>
@ -176,8 +179,8 @@
</svg>
</span>
<span class="hero-option-text">
<strong>Als Gespräch</strong>
<span>Fließend — Antworten werden automatisch gespeichert</span>
<strong>{$_('profile.hub.option_conversation_title')}</strong>
<span>{$_('profile.hub.option_conversation_hint')}</span>
</span>
</button>
</div>
@ -197,7 +200,11 @@
<div class="account-card">
<div class="account-header">
{#if apiProfile?.image}
<img src={apiProfile.image} alt="Avatar" class="account-avatar" />
<img
src={apiProfile.image}
alt={$_('profile.hub.avatar_alt')}
class="account-avatar"
/>
{:else}
<div class="account-avatar-placeholder">
{(apiProfile?.name ?? 'U').slice(0, 2).toUpperCase()}
@ -212,16 +219,14 @@
<div class="account-actions">
<button class="account-btn" onclick={() => goto('/profile/me-images')}>
Meine Bilder
<span class="account-btn-hint">
Gesichts- und Ganzkörperbilder für KI-Bildgenerierung
</span>
{$_('profile.hub.action_my_images')}
<span class="account-btn-hint">{$_('profile.hub.action_my_images_hint')}</span>
</button>
<button class="account-btn" onclick={() => (showEditModal = true)}>
Profil bearbeiten
{$_('profile.edit')}
</button>
<button class="account-btn" onclick={() => (showPasswordModal = true)}>
Passwort ändern
{$_('profile.change_password')}
</button>
<button
class="account-btn"
@ -230,10 +235,10 @@
goto('/login');
}}
>
Abmelden
{$_('profile.logout')}
</button>
<button class="account-btn danger" onclick={() => (showDeleteModal = true)}>
Konto löschen
{$_('profile.delete_account')}
</button>
</div>
</div>

View file

@ -130,7 +130,6 @@
"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/music/views/DetailView.svelte": 6,
"apps/mana/apps/web/src/lib/modules/news-research/ListView.svelte": 3,
"apps/mana/apps/web/src/lib/modules/news/widgets/NewsUnreadWidget.svelte": 2,
"apps/mana/apps/web/src/lib/modules/notes/ListView.svelte": 4,
@ -147,7 +146,6 @@
"apps/mana/apps/web/src/lib/modules/profile/components/MeImageSlotCard.svelte": 3,
"apps/mana/apps/web/src/lib/modules/profile/components/MeImageTile.svelte": 3,
"apps/mana/apps/web/src/lib/modules/profile/ContextInterview.svelte": 6,
"apps/mana/apps/web/src/lib/modules/profile/ListView.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,