i18n(contacts): translate /contacts +page.svelte via $_() — header, page picker, modal form

Adds contacts.page sub-namespace covering page title, header (title +
"{n} Kontakte" stats + Suchen placeholder + Neu action), 9 PAGE_META
labels (Mein Profil/Alle Kontakte/Favoriten/etc) — refactored to titleKey
routing through $_(), Modal title (edit vs new), 7 form section labels,
21 input placeholders (firstName/lastName/email/phone/company/etc),
Cancel/Save actions, "Seite hinzufügen" page-picker label.

Baselines: hardcoded 982 → 975 (7 cleared); missing-keys baseline unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-04-27 15:44:38 +02:00
parent a5d4554c11
commit 4357433e7b
7 changed files with 331 additions and 45 deletions

View file

@ -215,5 +215,50 @@
"noResultsFor": "Keine Ergebnisse für \"{query}\"",
"tagSingular": "Tag",
"tagPlural": "Tags"
},
"page": {
"page_title_html": "Kontakte - Mana",
"title": "Kontakte",
"stats_count": "{n} Kontakte",
"placeholder_search": "Suchen...",
"action_new": "Neu",
"page_my_profile": "Mein Profil",
"page_all": "Alle Kontakte",
"page_favorites": "Favoriten",
"page_birthday_soon": "Bald Geburtstag",
"page_has_email": "Mit E-Mail",
"page_has_phone": "Mit Telefon",
"page_with_company": "Mit Unternehmen",
"page_with_address": "Mit Adresse",
"page_recent": "Kürzlich hinzugefügt",
"page_picker_add_label": "Seite hinzufügen",
"modal_edit_title": "Kontakt bearbeiten",
"modal_new_title": "Neuer Kontakt",
"section_name": "Name",
"section_contact": "Kontakt",
"section_work": "Arbeit",
"section_address": "Adresse",
"section_birthday": "🎂 Geburtstag",
"section_notes": "Notizen",
"section_social": "🔗 Social Media",
"placeholder_first_name": "Vorname",
"placeholder_last_name": "Nachname",
"placeholder_email": "E-Mail",
"placeholder_mobile": "Mobil",
"placeholder_phone": "Telefon",
"placeholder_company": "Unternehmen",
"placeholder_job_title": "Position",
"placeholder_website": "Website",
"placeholder_street": "Straße & Hausnummer",
"placeholder_postal_code": "PLZ",
"placeholder_city": "Stadt",
"placeholder_country": "Land",
"placeholder_notes": "Notizen zum Kontakt...",
"placeholder_linkedin": "LinkedIn URL",
"placeholder_twitter": "Twitter / X",
"placeholder_instagram": "Instagram",
"placeholder_github": "GitHub",
"action_cancel": "Abbrechen",
"action_save": "Speichern"
}
}

View file

@ -215,5 +215,50 @@
"noResultsFor": "No results for \"{query}\"",
"tagSingular": "Tag",
"tagPlural": "Tags"
},
"page": {
"page_title_html": "Contacts - Mana",
"title": "Contacts",
"stats_count": "{n} contacts",
"placeholder_search": "Search…",
"action_new": "New",
"page_my_profile": "My profile",
"page_all": "All contacts",
"page_favorites": "Favorites",
"page_birthday_soon": "Birthday soon",
"page_has_email": "With email",
"page_has_phone": "With phone",
"page_with_company": "With company",
"page_with_address": "With address",
"page_recent": "Recently added",
"page_picker_add_label": "Add page",
"modal_edit_title": "Edit contact",
"modal_new_title": "New contact",
"section_name": "Name",
"section_contact": "Contact",
"section_work": "Work",
"section_address": "Address",
"section_birthday": "🎂 Birthday",
"section_notes": "Notes",
"section_social": "🔗 Social media",
"placeholder_first_name": "First name",
"placeholder_last_name": "Last name",
"placeholder_email": "Email",
"placeholder_mobile": "Mobile",
"placeholder_phone": "Phone",
"placeholder_company": "Company",
"placeholder_job_title": "Position",
"placeholder_website": "Website",
"placeholder_street": "Street & number",
"placeholder_postal_code": "ZIP",
"placeholder_city": "City",
"placeholder_country": "Country",
"placeholder_notes": "Notes about this contact…",
"placeholder_linkedin": "LinkedIn URL",
"placeholder_twitter": "Twitter / X",
"placeholder_instagram": "Instagram",
"placeholder_github": "GitHub",
"action_cancel": "Cancel",
"action_save": "Save"
}
}

View file

@ -215,5 +215,50 @@
"noResultsFor": "Sin resultados para \"{query}\"",
"tagSingular": "Etiqueta",
"tagPlural": "Etiquetas"
},
"page": {
"page_title_html": "Contactos - Mana",
"title": "Contactos",
"stats_count": "{n} contactos",
"placeholder_search": "Buscar…",
"action_new": "Nuevo",
"page_my_profile": "Mi perfil",
"page_all": "Todos los contactos",
"page_favorites": "Favoritos",
"page_birthday_soon": "Cumpleaños próximo",
"page_has_email": "Con correo",
"page_has_phone": "Con teléfono",
"page_with_company": "Con empresa",
"page_with_address": "Con dirección",
"page_recent": "Añadidos recientemente",
"page_picker_add_label": "Añadir página",
"modal_edit_title": "Editar contacto",
"modal_new_title": "Nuevo contacto",
"section_name": "Nombre",
"section_contact": "Contacto",
"section_work": "Trabajo",
"section_address": "Dirección",
"section_birthday": "🎂 Cumpleaños",
"section_notes": "Notas",
"section_social": "🔗 Redes sociales",
"placeholder_first_name": "Nombre",
"placeholder_last_name": "Apellido",
"placeholder_email": "Correo",
"placeholder_mobile": "Móvil",
"placeholder_phone": "Teléfono",
"placeholder_company": "Empresa",
"placeholder_job_title": "Puesto",
"placeholder_website": "Web",
"placeholder_street": "Calle y número",
"placeholder_postal_code": "CP",
"placeholder_city": "Ciudad",
"placeholder_country": "País",
"placeholder_notes": "Notas sobre este contacto…",
"placeholder_linkedin": "URL de LinkedIn",
"placeholder_twitter": "Twitter / X",
"placeholder_instagram": "Instagram",
"placeholder_github": "GitHub",
"action_cancel": "Cancelar",
"action_save": "Guardar"
}
}

View file

@ -215,5 +215,50 @@
"noResultsFor": "Aucun résultat pour \"{query}\"",
"tagSingular": "Tag",
"tagPlural": "Tags"
},
"page": {
"page_title_html": "Contacts - Mana",
"title": "Contacts",
"stats_count": "{n} contacts",
"placeholder_search": "Rechercher…",
"action_new": "Nouveau",
"page_my_profile": "Mon profil",
"page_all": "Tous les contacts",
"page_favorites": "Favoris",
"page_birthday_soon": "Anniversaire proche",
"page_has_email": "Avec e-mail",
"page_has_phone": "Avec téléphone",
"page_with_company": "Avec entreprise",
"page_with_address": "Avec adresse",
"page_recent": "Ajoutés récemment",
"page_picker_add_label": "Ajouter une page",
"modal_edit_title": "Modifier le contact",
"modal_new_title": "Nouveau contact",
"section_name": "Nom",
"section_contact": "Contact",
"section_work": "Travail",
"section_address": "Adresse",
"section_birthday": "🎂 Anniversaire",
"section_notes": "Notes",
"section_social": "🔗 Réseaux sociaux",
"placeholder_first_name": "Prénom",
"placeholder_last_name": "Nom",
"placeholder_email": "E-mail",
"placeholder_mobile": "Mobile",
"placeholder_phone": "Téléphone",
"placeholder_company": "Entreprise",
"placeholder_job_title": "Poste",
"placeholder_website": "Site web",
"placeholder_street": "Rue et numéro",
"placeholder_postal_code": "CP",
"placeholder_city": "Ville",
"placeholder_country": "Pays",
"placeholder_notes": "Notes sur ce contact…",
"placeholder_linkedin": "URL LinkedIn",
"placeholder_twitter": "Twitter / X",
"placeholder_instagram": "Instagram",
"placeholder_github": "GitHub",
"action_cancel": "Annuler",
"action_save": "Enregistrer"
}
}

View file

@ -215,5 +215,50 @@
"noResultsFor": "Nessun risultato per \"{query}\"",
"tagSingular": "Tag",
"tagPlural": "Tag"
},
"page": {
"page_title_html": "Contatti - Mana",
"title": "Contatti",
"stats_count": "{n} contatti",
"placeholder_search": "Cerca…",
"action_new": "Nuovo",
"page_my_profile": "Il mio profilo",
"page_all": "Tutti i contatti",
"page_favorites": "Preferiti",
"page_birthday_soon": "Compleanno vicino",
"page_has_email": "Con e-mail",
"page_has_phone": "Con telefono",
"page_with_company": "Con azienda",
"page_with_address": "Con indirizzo",
"page_recent": "Aggiunti di recente",
"page_picker_add_label": "Aggiungi pagina",
"modal_edit_title": "Modifica contatto",
"modal_new_title": "Nuovo contatto",
"section_name": "Nome",
"section_contact": "Contatto",
"section_work": "Lavoro",
"section_address": "Indirizzo",
"section_birthday": "🎂 Compleanno",
"section_notes": "Note",
"section_social": "🔗 Social media",
"placeholder_first_name": "Nome",
"placeholder_last_name": "Cognome",
"placeholder_email": "E-mail",
"placeholder_mobile": "Cellulare",
"placeholder_phone": "Telefono",
"placeholder_company": "Azienda",
"placeholder_job_title": "Posizione",
"placeholder_website": "Sito web",
"placeholder_street": "Via e numero",
"placeholder_postal_code": "CAP",
"placeholder_city": "Città",
"placeholder_country": "Paese",
"placeholder_notes": "Note sul contatto…",
"placeholder_linkedin": "URL LinkedIn",
"placeholder_twitter": "Twitter / X",
"placeholder_instagram": "Instagram",
"placeholder_github": "GitHub",
"action_cancel": "Annulla",
"action_save": "Salva"
}
}

View file

@ -63,16 +63,16 @@
{ id: 'favorites', minimized: false },
]);
const PAGE_META: Record<string, { title: string; color: string }> = {
'my-profile': { title: 'Mein Profil', color: '#8B5CF6' },
all: { title: 'Alle Kontakte', color: '#3B82F6' },
favorites: { title: 'Favoriten', color: '#F59E0B' },
'birthday-soon': { title: 'Bald Geburtstag', color: '#EC4899' },
'has-email': { title: 'Mit E-Mail', color: '#6366F1' },
'has-phone': { title: 'Mit Telefon', color: '#22C55E' },
'with-company': { title: 'Mit Unternehmen', color: '#8B5CF6' },
'with-address': { title: 'Mit Adresse', color: '#F97316' },
recent: { title: 'Kürzlich hinzugefügt', color: '#6B7280' },
const PAGE_META: Record<string, { titleKey: string; color: string }> = {
'my-profile': { titleKey: 'contacts.page.page_my_profile', color: '#8B5CF6' },
all: { titleKey: 'contacts.page.page_all', color: '#3B82F6' },
favorites: { titleKey: 'contacts.page.page_favorites', color: '#F59E0B' },
'birthday-soon': { titleKey: 'contacts.page.page_birthday_soon', color: '#EC4899' },
'has-email': { titleKey: 'contacts.page.page_has_email', color: '#6366F1' },
'has-phone': { titleKey: 'contacts.page.page_has_phone', color: '#22C55E' },
'with-company': { titleKey: 'contacts.page.page_with_company', color: '#8B5CF6' },
'with-address': { titleKey: 'contacts.page.page_with_address', color: '#F97316' },
recent: { titleKey: 'contacts.page.page_recent', color: '#6B7280' },
};
let carouselPages = $derived<CarouselPage[]>(
@ -83,7 +83,7 @@
minimized: p.minimized,
maximized: p.maximized,
widthPx: p.widthPx ?? DEFAULT_WIDTH,
title: meta?.title ?? p.id,
title: meta?.titleKey ? $_(meta.titleKey) : p.id,
color: meta?.color ?? '#6B7280',
};
})
@ -126,16 +126,18 @@
</script>
<svelte:head>
<title>Kontakte - Mana</title>
<title>{$_('contacts.page.page_title_html')}</title>
</svelte:head>
<div class="contacts-board">
<!-- Header -->
<header class="contacts-header">
<div>
<h1 class="contacts-title">Kontakte</h1>
<h1 class="contacts-title">{$_('contacts.page.title')}</h1>
<p class="contacts-stats">
{allContacts.filter((c) => !c.isArchived).length} Kontakte
{$_('contacts.page.stats_count', {
values: { n: allContacts.filter((c) => !c.isArchived).length },
})}
</p>
</div>
<div class="header-actions">
@ -144,7 +146,7 @@
<MagnifyingGlass size={16} class="search-icon" />
<input
type="text"
placeholder="Suchen..."
placeholder={$_('contacts.page.placeholder_search')}
value={contactsFilterStore.searchQuery}
oninput={(e) => contactsFilterStore.setSearchQuery(e.currentTarget.value)}
class="search-input"
@ -157,7 +159,7 @@
</div>
<button class="new-btn" onclick={() => contactModalStore.open()}>
<Plus size={16} />
Neu
{$_('contacts.page.action_new')}
</button>
</div>
</header>
@ -171,7 +173,7 @@
onMaximize={handleMaximizePage}
onRemove={handleRemovePage}
onTogglePicker={() => (showPicker = !showPicker)}
addLabel="Seite hinzufügen"
addLabel={$_('contacts.page.page_picker_add_label')}
>
{#snippet page(p)}
<ContactPage
@ -218,7 +220,7 @@
class="sticky top-0 z-10 flex items-center justify-between border-b border-border bg-card px-5 py-3"
>
<h2 class="text-lg font-bold text-foreground">
{isEditing ? 'Kontakt bearbeiten' : 'Neuer Kontakt'}
{isEditing ? $_('contacts.page.modal_edit_title') : $_('contacts.page.modal_new_title')}
</h2>
<button
onclick={() => contactModalStore.close()}
@ -259,20 +261,20 @@
>
<div class="contact-section">
<div class="section-icon-row">
<span class="section-label">Name</span>
<span class="section-label">{$_('contacts.page.section_name')}</span>
</div>
<div class="grid grid-cols-2 gap-2">
<input
name="firstName"
type="text"
placeholder="Vorname"
placeholder={$_('contacts.page.placeholder_first_name')}
value={contactModalStore.prefillData?.firstName ?? ''}
class="contact-input"
/>
<input
name="lastName"
type="text"
placeholder="Nachname"
placeholder={$_('contacts.page.placeholder_last_name')}
value={contactModalStore.prefillData?.lastName ?? ''}
class="contact-input"
/>
@ -280,20 +282,27 @@
</div>
<div class="contact-section">
<div class="section-icon-row"><span class="section-label">Kontakt</span></div>
<div class="section-icon-row">
<span class="section-label">{$_('contacts.page.section_contact')}</span>
</div>
<input
name="email"
type="email"
placeholder="E-Mail"
placeholder={$_('contacts.page.placeholder_email')}
value={contactModalStore.prefillData?.email ?? ''}
class="contact-input"
/>
<div class="grid grid-cols-2 gap-2">
<input name="mobile" type="tel" placeholder="Mobil" class="contact-input" />
<input
name="mobile"
type="tel"
placeholder={$_('contacts.page.placeholder_mobile')}
class="contact-input"
/>
<input
name="phone"
type="tel"
placeholder="Telefon"
placeholder={$_('contacts.page.placeholder_phone')}
value={contactModalStore.prefillData?.phone ?? ''}
class="contact-input"
/>
@ -301,57 +310,110 @@
</div>
<div class="contact-section">
<div class="section-icon-row"><span class="section-label">Arbeit</span></div>
<div class="section-icon-row">
<span class="section-label">{$_('contacts.page.section_work')}</span>
</div>
<input
name="company"
type="text"
placeholder="Unternehmen"
placeholder={$_('contacts.page.placeholder_company')}
value={contactModalStore.prefillData?.company ?? ''}
class="contact-input"
/>
<input name="jobTitle" type="text" placeholder="Position" class="contact-input" />
<input name="website" type="url" placeholder="Website" class="contact-input" />
<input
name="jobTitle"
type="text"
placeholder={$_('contacts.page.placeholder_job_title')}
class="contact-input"
/>
<input
name="website"
type="url"
placeholder={$_('contacts.page.placeholder_website')}
class="contact-input"
/>
</div>
<div class="contact-section">
<div class="section-icon-row"><span class="section-label">Adresse</span></div>
<div class="section-icon-row">
<span class="section-label">{$_('contacts.page.section_address')}</span>
</div>
<input
name="street"
type="text"
placeholder="Straße & Hausnummer"
placeholder={$_('contacts.page.placeholder_street')}
class="contact-input"
/>
<div class="grid grid-cols-[5rem_1fr] gap-2">
<input name="postalCode" type="text" placeholder="PLZ" class="contact-input" />
<input name="city" type="text" placeholder="Stadt" class="contact-input" />
<input
name="postalCode"
type="text"
placeholder={$_('contacts.page.placeholder_postal_code')}
class="contact-input"
/>
<input
name="city"
type="text"
placeholder={$_('contacts.page.placeholder_city')}
class="contact-input"
/>
</div>
<input name="country" type="text" placeholder="Land" class="contact-input" />
<input
name="country"
type="text"
placeholder={$_('contacts.page.placeholder_country')}
class="contact-input"
/>
</div>
<div class="contact-section">
<div class="section-icon-row"><span class="section-label">🎂 Geburtstag</span></div>
<div class="section-icon-row">
<span class="section-label">{$_('contacts.page.section_birthday')}</span>
</div>
<input name="birthday" type="date" class="contact-input" />
</div>
<div class="contact-section">
<div class="section-icon-row"><span class="section-label">Notizen</span></div>
<div class="section-icon-row">
<span class="section-label">{$_('contacts.page.section_notes')}</span>
</div>
<textarea
name="notes"
rows="3"
placeholder="Notizen zum Kontakt..."
placeholder={$_('contacts.page.placeholder_notes')}
class="contact-input resize-none"
></textarea>
</div>
<details class="contact-section">
<summary class="section-icon-row cursor-pointer select-none">
<span class="section-label">🔗 Social Media</span>
<span class="section-label">{$_('contacts.page.section_social')}</span>
</summary>
<div class="mt-2 space-y-2">
<input name="linkedin" type="url" placeholder="LinkedIn URL" class="contact-input" />
<input name="twitter" type="text" placeholder="Twitter / X" class="contact-input" />
<input name="instagram" type="text" placeholder="Instagram" class="contact-input" />
<input name="github" type="text" placeholder="GitHub" class="contact-input" />
<input
name="linkedin"
type="url"
placeholder={$_('contacts.page.placeholder_linkedin')}
class="contact-input"
/>
<input
name="twitter"
type="text"
placeholder={$_('contacts.page.placeholder_twitter')}
class="contact-input"
/>
<input
name="instagram"
type="text"
placeholder={$_('contacts.page.placeholder_instagram')}
class="contact-input"
/>
<input
name="github"
type="text"
placeholder={$_('contacts.page.placeholder_github')}
class="contact-input"
/>
</div>
</details>
@ -361,13 +423,13 @@
onclick={() => contactModalStore.close()}
class="rounded-lg border border-border px-4 py-2 text-sm font-medium text-foreground hover:bg-muted"
>
Abbrechen
{$_('contacts.page.action_cancel')}
</button>
<button
type="submit"
class="rounded-lg bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90"
>
Speichern
{$_('contacts.page.action_save')}
</button>
</div>
</form>

View file

@ -221,7 +221,6 @@
"apps/mana/apps/web/src/routes/(app)/comic/new/+page.svelte": 1,
"apps/mana/apps/web/src/routes/(app)/companion/+page.svelte": 2,
"apps/mana/apps/web/src/routes/(app)/contacts/[id]/+page.svelte": 7,
"apps/mana/apps/web/src/routes/(app)/contacts/+page.svelte": 7,
"apps/mana/apps/web/src/routes/(app)/context/documents/[id]/+page.svelte": 3,
"apps/mana/apps/web/src/routes/(app)/context/documents/+page.svelte": 5,
"apps/mana/apps/web/src/routes/(app)/context/spaces/[id]/+page.svelte": 3,