i18n(places): translate views/DetailView via $_() — header, fields, sections, meta

- Shell labels (notFound + confirmDelete + Unbenannt fallback)
- Name input placeholder, map iframe title
- 5 row labels (Sichtbarkeit/Kategorie/Adresse/Koordinaten/Beschreibung) + Link share row
- Category options routed via $_('places.categories.' + v) — CATEGORIES constant inlined as PlaceCategory[] array
- Address + address-search placeholders, Lat/Lng coords placeholders, resolve title
- Tags / Letzte Besuche section labels
- 4 meta-row keys with {n}/{date} interpolation; toLocaleDateString switched to get(locale) ?? 'de'

Baselines: hardcoded 1033 → 1025 (8 cleared); missing-keys baseline +1 (places.categories.* dynamic key).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-04-27 15:02:54 +02:00
parent 53cf17a886
commit 092c45c835
3 changed files with 49 additions and 35 deletions

View file

@ -26,6 +26,8 @@
import { useAllTags, getTagsByIds } from '@mana/shared-stores'; import { useAllTags, getTagsByIds } from '@mana/shared-stores';
import LinkedItems from '$lib/components/links/LinkedItems.svelte'; import LinkedItems from '$lib/components/links/LinkedItems.svelte';
import { removeTagIdWithUndo } from '$lib/data/tag-mutations'; import { removeTagIdWithUndo } from '$lib/data/tag-mutations';
import { _, locale } from 'svelte-i18n';
import { get } from 'svelte/store';
let { navigate, params, goBack }: ViewProps = $props(); let { navigate, params, goBack }: ViewProps = $props();
let placeId = $derived(params.placeId as string); let placeId = $derived(params.placeId as string);
@ -66,14 +68,14 @@
let placeTags = $derived(getTagsByIds(allTags, detail.entity?.tagIds ?? [])); let placeTags = $derived(getTagsByIds(allTags, detail.entity?.tagIds ?? []));
const CATEGORIES: { value: PlaceCategory; label: string }[] = [ const CATEGORY_VALUES: PlaceCategory[] = [
{ value: 'home', label: 'Zuhause' }, 'home',
{ value: 'work', label: 'Arbeit' }, 'work',
{ value: 'food', label: 'Essen' }, 'food',
{ value: 'shopping', label: 'Einkauf' }, 'shopping',
{ value: 'transit', label: 'Transit' }, 'transit',
{ value: 'leisure', label: 'Freizeit' }, 'leisure',
{ value: 'other', label: 'Sonstiges' }, 'other',
]; ];
// --- Reverse geocoding (coords → address) --- // --- Reverse geocoding (coords → address) ---
@ -145,7 +147,7 @@
const lat = parseFloat(editLatitude); const lat = parseFloat(editLatitude);
const lng = parseFloat(editLongitude); const lng = parseFloat(editLongitude);
await placesStore.updatePlace(placeId, { await placesStore.updatePlace(placeId, {
name: editName.trim() || 'Unbenannt', name: editName.trim() || $_('places.detail_view.untitled'),
description: editDescription.trim() || null, description: editDescription.trim() || null,
address: editAddress.trim() || null, address: editAddress.trim() || null,
category: editCategory, category: editCategory,
@ -192,7 +194,7 @@
} }
function formatDate(iso: string): string { function formatDate(iso: string): string {
return new Date(iso).toLocaleDateString('de', { return new Date(iso).toLocaleDateString(get(locale) ?? 'de', {
day: '2-digit', day: '2-digit',
month: '2-digit', month: '2-digit',
year: '2-digit', year: '2-digit',
@ -214,11 +216,11 @@
<DetailViewShell <DetailViewShell
entity={detail.entity} entity={detail.entity}
loading={detail.loading} loading={detail.loading}
notFoundLabel="Ort nicht gefunden" notFoundLabel={$_('places.detail_view.not_found')}
confirmDelete={detail.confirmDelete} confirmDelete={detail.confirmDelete}
onAskDelete={detail.askDelete} onAskDelete={detail.askDelete}
onCancelDelete={detail.cancelDelete} onCancelDelete={detail.cancelDelete}
confirmDeleteLabel="Ort wirklich löschen?" confirmDeleteLabel={$_('places.detail_view.confirm_delete')}
onConfirmDelete={deletePlace} onConfirmDelete={deletePlace}
> >
{#snippet body(place)} {#snippet body(place)}
@ -232,7 +234,7 @@
bind:value={editName} bind:value={editName}
onfocus={detail.focus} onfocus={detail.focus}
onblur={saveField} onblur={saveField}
placeholder="Name" placeholder={$_('places.detail_view.name_placeholder')}
/> />
</div> </div>
<button class="fav-btn" class:active={place.isFavorite} onclick={toggleFavorite}> <button class="fav-btn" class:active={place.isFavorite} onclick={toggleFavorite}>
@ -243,7 +245,7 @@
{#if mapUrl} {#if mapUrl}
<div class="map-container"> <div class="map-container">
<iframe <iframe
title="Kartenvorschau" title={$_('places.detail_view.map_title')}
src={mapUrl} src={mapUrl}
width="100%" width="100%"
height="160" height="160"
@ -255,13 +257,13 @@
<div class="fields"> <div class="fields">
<div class="field-row"> <div class="field-row">
<span class="field-label">Sichtbarkeit</span> <span class="field-label">{$_('places.detail_view.label_visibility')}</span>
<VisibilityPicker level={place.visibility ?? 'private'} onChange={handleVisibilityChange} /> <VisibilityPicker level={place.visibility ?? 'private'} onChange={handleVisibilityChange} />
</div> </div>
{#if place.visibility === 'unlisted' && place.unlistedToken && shareUrl} {#if place.visibility === 'unlisted' && place.unlistedToken && shareUrl}
<div class="field-row field-row--share"> <div class="field-row field-row--share">
<span class="field-label">Link</span> <span class="field-label">{$_('places.detail_view.label_share_link')}</span>
<SharedLinkControls <SharedLinkControls
token={place.unlistedToken} token={place.unlistedToken}
url={shareUrl} url={shareUrl}
@ -274,22 +276,22 @@
{/if} {/if}
<div class="field-row"> <div class="field-row">
<span class="field-label">Kategorie</span> <span class="field-label">{$_('places.detail_view.label_category')}</span>
<select class="field-select" value={editCategory} onchange={onCategoryChange}> <select class="field-select" value={editCategory} onchange={onCategoryChange}>
{#each CATEGORIES as cat} {#each CATEGORY_VALUES as v}
<option value={cat.value}>{cat.label}</option> <option value={v}>{$_('places.categories.' + v)}</option>
{/each} {/each}
</select> </select>
</div> </div>
<div class="field-row"> <div class="field-row">
<span class="field-label">Adresse</span> <span class="field-label">{$_('places.detail_view.label_address')}</span>
<input <input
class="field-input" class="field-input"
bind:value={editAddress} bind:value={editAddress}
onfocus={detail.focus} onfocus={detail.focus}
onblur={saveField} onblur={saveField}
placeholder="Adresse eingeben..." placeholder={$_('places.detail_view.placeholder_address')}
/> />
</div> </div>
@ -302,7 +304,7 @@
<input <input
class="address-search-input" class="address-search-input"
type="text" type="text"
placeholder="Adresse suchen..." placeholder={$_('places.detail_view.placeholder_address_search')}
bind:value={addressSearch} bind:value={addressSearch}
oninput={onAddressSearchInput} oninput={onAddressSearchInput}
onblur={onAddressSearchBlur} onblur={onAddressSearchBlur}
@ -325,14 +327,14 @@
</div> </div>
<div class="field-row"> <div class="field-row">
<span class="field-label">Koordinaten</span> <span class="field-label">{$_('places.detail_view.label_coordinates')}</span>
<div class="coords-row"> <div class="coords-row">
<input <input
class="field-input small" class="field-input small"
bind:value={editLatitude} bind:value={editLatitude}
onfocus={detail.focus} onfocus={detail.focus}
onblur={saveField} onblur={saveField}
placeholder="Lat" placeholder={$_('places.detail_view.placeholder_lat')}
type="number" type="number"
step="any" step="any"
/> />
@ -341,7 +343,7 @@
bind:value={editLongitude} bind:value={editLongitude}
onfocus={detail.focus} onfocus={detail.focus}
onblur={saveField} onblur={saveField}
placeholder="Lng" placeholder={$_('places.detail_view.placeholder_lng')}
type="number" type="number"
step="any" step="any"
/> />
@ -349,7 +351,7 @@
class="resolve-btn" class="resolve-btn"
onclick={resolveAddress} onclick={resolveAddress}
disabled={isResolving} disabled={isResolving}
title="Adresse aus Koordinaten ermitteln" title={$_('places.detail_view.resolve_address_title')}
> >
<ArrowsClockwise size={14} class={isResolving ? 'spinning' : ''} /> <ArrowsClockwise size={14} class={isResolving ? 'spinning' : ''} />
</button> </button>
@ -357,13 +359,13 @@
</div> </div>
<div class="field-row"> <div class="field-row">
<span class="field-label">Beschreibung</span> <span class="field-label">{$_('places.detail_view.label_description')}</span>
<textarea <textarea
class="description-input" class="description-input"
bind:value={editDescription} bind:value={editDescription}
onfocus={detail.focus} onfocus={detail.focus}
onblur={saveField} onblur={saveField}
placeholder="Notizen zum Ort..." placeholder={$_('places.detail_view.placeholder_description')}
rows={2} rows={2}
></textarea> ></textarea>
</div> </div>
@ -371,7 +373,7 @@
{#if placeTags.length > 0} {#if placeTags.length > 0}
<div class="section"> <div class="section">
<span class="section-label">Tags</span> <span class="section-label">{$_('places.detail_view.section_tags')}</span>
<div class="tags-list"> <div class="tags-list">
{#each placeTags as tag (tag.id)} {#each placeTags as tag (tag.id)}
<button <button
@ -392,7 +394,7 @@
{#if logs.length > 0} {#if logs.length > 0}
<div class="section"> <div class="section">
<span class="section-label">Letzte Besuche</span> <span class="section-label">{$_('places.detail_view.section_recent_visits')}</span>
<div class="log-list"> <div class="log-list">
{#each logs as log (log.id)} {#each logs as log (log.id)}
<div class="log-row"> <div class="log-row">
@ -408,16 +410,28 @@
<div class="meta"> <div class="meta">
{#if (place.visitCount ?? 0) > 0} {#if (place.visitCount ?? 0) > 0}
<span>Besuche: {place.visitCount}</span> <span>{$_('places.detail_view.meta_visits', { values: { n: place.visitCount } })}</span>
{/if} {/if}
{#if place.lastVisitedAt} {#if place.lastVisitedAt}
<span>Letzter Besuch: {formatDate(place.lastVisitedAt)}</span> <span
>{$_('places.detail_view.meta_last_visit', {
values: { date: formatDate(place.lastVisitedAt) },
})}</span
>
{/if} {/if}
{#if place.createdAt} {#if place.createdAt}
<span>Erstellt: {new Date(place.createdAt).toLocaleDateString('de')}</span> <span
>{$_('places.detail_view.meta_created', {
values: { date: new Date(place.createdAt).toLocaleDateString(get(locale) ?? 'de') },
})}</span
>
{/if} {/if}
{#if place.updatedAt} {#if place.updatedAt}
<span>Bearbeitet: {new Date(place.updatedAt).toLocaleDateString('de')}</span> <span
>{$_('places.detail_view.meta_updated', {
values: { date: new Date(place.updatedAt).toLocaleDateString(get(locale) ?? 'de') },
})}</span
>
{/if} {/if}
</div> </div>
{/snippet} {/snippet}

View file

@ -148,7 +148,6 @@
"apps/mana/apps/web/src/lib/modules/picture/components/ReferenceImagePicker.svelte": 1, "apps/mana/apps/web/src/lib/modules/picture/components/ReferenceImagePicker.svelte": 1,
"apps/mana/apps/web/src/lib/modules/picture/ListView.svelte": 5, "apps/mana/apps/web/src/lib/modules/picture/ListView.svelte": 5,
"apps/mana/apps/web/src/lib/modules/places/ListView.svelte": 2, "apps/mana/apps/web/src/lib/modules/places/ListView.svelte": 2,
"apps/mana/apps/web/src/lib/modules/places/views/DetailView.svelte": 8,
"apps/mana/apps/web/src/lib/modules/plants/views/DetailView.svelte": 1, "apps/mana/apps/web/src/lib/modules/plants/views/DetailView.svelte": 1,
"apps/mana/apps/web/src/lib/modules/playground/ListView.svelte": 2, "apps/mana/apps/web/src/lib/modules/playground/ListView.svelte": 2,
"apps/mana/apps/web/src/lib/modules/presi/ListView.svelte": 1, "apps/mana/apps/web/src/lib/modules/presi/ListView.svelte": 1,

View file

@ -18,6 +18,7 @@
"apps/mana/apps/web/src/lib/modules/invoices/views/DetailView.svelte": 1, "apps/mana/apps/web/src/lib/modules/invoices/views/DetailView.svelte": 1,
"apps/mana/apps/web/src/lib/modules/library/views/DetailView.svelte": 3, "apps/mana/apps/web/src/lib/modules/library/views/DetailView.svelte": 3,
"apps/mana/apps/web/src/lib/modules/period/ListView.svelte": 1, "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/plants/ListView.svelte": 5,
"apps/mana/apps/web/src/lib/modules/quiz/EditView.svelte": 1, "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/quotes/components/QuoteCard.svelte": 4,