mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 20:01:09 +02:00
feat(events): full i18n coverage across 12 files — DE/EN/ES/FR/IT
Events (party/RSVP) module had ~38 hardcoded German strings across DetailView (13 incl. share row + map labels), SourceManager (5), DiscoveredEventCard (3), DiscoveryTab (3), EventCard (3), RsvpSummary (3), ListView (3), BringListEditor (2), GuestListEditor (2), PublicRsvpList (2), DiscoverySetup (2), RegionPicker (1). New `events` namespace with 119 keys × 5 locales: - `list_view.*`, `detail_view.*` (incl. share/publish/map sections), `event_card.*` (status badges + summary), `discovered_card.*`, `discovery_tab.*`, `discovery_setup.*`, `region_picker.*`, `source_manager.*` (incl. errors_count + last_scan), `bring_list_editor.*`, `guest_list_editor.*` (RSVP options), `public_rsvp_list.*` (status labels + meta), `rsvp_summary.*` (yes/maybe/no/pending labels). - SourceManager.formatDate now uses get(locale) instead of hardcoded 'de-DE' for last-scan timestamps. - Baseline ratchet: 1640 → 1602 (38 strings cleared) - validate:i18n-parity: 42 namespaces × 5 locales — 4100 keys aligned - svelte-check: no new errors Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
c07db300b0
commit
c7d80e3423
18 changed files with 975 additions and 139 deletions
145
apps/mana/apps/web/src/lib/i18n/locales/events/de.json
Normal file
145
apps/mana/apps/web/src/lib/i18n/locales/events/de.json
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
{
|
||||
"list_view": {
|
||||
"doc_title": "Events - Mana",
|
||||
"tab_mine": "Meine Events",
|
||||
"tab_discover": "Entdecken",
|
||||
"subtitle_count": "{upcoming} bevorstehend · {past} vergangen",
|
||||
"new_event_btn": "+ Neues Event",
|
||||
"cancel_btn": "Abbrechen",
|
||||
"placeholder_title": "Worum geht's? (z. B. Geburtstag Anna)",
|
||||
"placeholder_location": "Ort (optional)",
|
||||
"submit_create": "Event anlegen",
|
||||
"section_upcoming": "Bevorstehend",
|
||||
"section_past": "Vergangen",
|
||||
"empty_upcoming": "Keine bevorstehenden Events. Zeit für eine Party?"
|
||||
},
|
||||
"detail_view": {
|
||||
"loading": "Lade Event...",
|
||||
"back": "← Zurück",
|
||||
"action_edit": "Bearbeiten",
|
||||
"action_delete": "Löschen",
|
||||
"confirm_delete": "Event \"{title}\" wirklich löschen?",
|
||||
"placeholder_title": "Event-Titel",
|
||||
"placeholder_description": "Beschreibung",
|
||||
"placeholder_location": "Ort — tippe eine Adresse...",
|
||||
"location_pinned_title": "Koordinaten gesetzt",
|
||||
"label_start": "Start",
|
||||
"label_end": "Ende",
|
||||
"label_all_day": "Ganztägig",
|
||||
"action_cancel": "Abbrechen",
|
||||
"action_save": "Speichern",
|
||||
"map_iframe_title": "Event-Ort auf Karte",
|
||||
"map_open": "In OpenStreetMap öffnen →",
|
||||
"label_visibility": "Sichtbarkeit",
|
||||
"section_rsvps": "RSVPs",
|
||||
"section_guests": "Gäste",
|
||||
"section_bring_list": "Bring-Liste",
|
||||
"section_share": "Teilen",
|
||||
"action_copy_link": "Kopieren",
|
||||
"action_make_private": "Privat machen",
|
||||
"share_hint": "Antworten erscheinen automatisch unten in „Antworten via Link\" (Polling alle 30s).",
|
||||
"action_publish": "Event veröffentlichen & Link generieren"
|
||||
},
|
||||
"event_card": {
|
||||
"badge_draft": "Entwurf",
|
||||
"badge_cancelled": "Abgesagt",
|
||||
"badge_published": "Geteilt",
|
||||
"all_day": "Ganztägig",
|
||||
"yes_count": "{count} kommen",
|
||||
"pending_count": "· {count} offen"
|
||||
},
|
||||
"discovered_card": {
|
||||
"all_day": "Ganztag",
|
||||
"source_fallback": "Quelle",
|
||||
"saved_label": "Gespeichert",
|
||||
"action_save": "Merken",
|
||||
"action_dismiss": "Ausblenden"
|
||||
},
|
||||
"discovery_tab": {
|
||||
"loading": "Lade...",
|
||||
"action_refresh": "Aktualisieren",
|
||||
"action_sources": "Quellen",
|
||||
"action_sources_count": "Quellen ({count})",
|
||||
"loading_events": "Lade Events...",
|
||||
"empty_title": "Noch keine Events gefunden",
|
||||
"empty_hint": "Füge iCal-Feeds von Venues oder Vereinen hinzu, um Events zu entdecken.",
|
||||
"action_manage_sources": "Quellen verwalten",
|
||||
"action_load_more": "Mehr laden"
|
||||
},
|
||||
"discovery_setup": {
|
||||
"title": "Event-Entdeckung einrichten",
|
||||
"step1_desc": "Welche Regionen sollen nach Events durchsucht werden?",
|
||||
"action_next": "Weiter",
|
||||
"step2_desc": "Was interessiert dich?",
|
||||
"placeholder_freetext": "Weitere Interessen (kommagetrennt, z.B. Impro-Theater, Rust Meetups)",
|
||||
"action_back": "Zurück",
|
||||
"action_finish": "Fertig"
|
||||
},
|
||||
"region_picker": {
|
||||
"radius_unit": "{km} km",
|
||||
"add_region": "+ Region",
|
||||
"placeholder_search": "Stadt oder Region suchen...",
|
||||
"radius_label": "Radius: {km} km",
|
||||
"searching": "Suche...",
|
||||
"action_cancel": "Abbrechen"
|
||||
},
|
||||
"source_manager": {
|
||||
"section_title": "Quellen",
|
||||
"action_discover": "Automatisch finden",
|
||||
"action_discovering": "Suche...",
|
||||
"action_add_ical": "+ iCal-Feed",
|
||||
"placeholder_name": "Name (z.B. Jazzhaus Freiburg)",
|
||||
"placeholder_url": "iCal URL (.ics)",
|
||||
"action_submit_add": "Hinzufügen",
|
||||
"action_cancel": "Abbrechen",
|
||||
"section_suggested": "Vorgeschlagene Quellen",
|
||||
"action_activate": "Aktivieren",
|
||||
"action_reject": "x",
|
||||
"empty": "Noch keine Quellen. Nutze \"Automatisch finden\" oder füge iCal-Feeds manuell hinzu.",
|
||||
"meta_last_scan": "Letzter Scan: {date}",
|
||||
"never_scanned": "nie",
|
||||
"errors_count": "{count} Fehler",
|
||||
"action_scan": "Scannen",
|
||||
"action_scan_title": "Jetzt scannen",
|
||||
"action_remove_title": "Entfernen"
|
||||
},
|
||||
"bring_list_editor": {
|
||||
"placeholder_label": "z. B. Salat, Wein, Lautsprecher",
|
||||
"placeholder_quantity": "Anzahl",
|
||||
"action_add": "Hinzufügen",
|
||||
"option_no_one": "— Niemand —",
|
||||
"action_remove_title": "Entfernen",
|
||||
"empty": "Noch nichts auf der Liste.",
|
||||
"claimed_via_link": "{name} (via Link)",
|
||||
"unknown_assignee": "?"
|
||||
},
|
||||
"guest_list_editor": {
|
||||
"placeholder_name": "Name",
|
||||
"placeholder_email": "E-Mail (optional)",
|
||||
"action_add": "Hinzufügen",
|
||||
"action_remove_title": "Entfernen",
|
||||
"empty": "Noch keine Gäste hinzugefügt.",
|
||||
"rsvp_pending": "Offen",
|
||||
"rsvp_yes": "Ja",
|
||||
"rsvp_maybe": "Vielleicht",
|
||||
"rsvp_no": "Nein"
|
||||
},
|
||||
"public_rsvp_list": {
|
||||
"title": "Antworten via Link",
|
||||
"action_refresh": "Neu laden",
|
||||
"err_load": "Fehler beim Laden",
|
||||
"empty": "Noch keine Antworten via Share-Link.",
|
||||
"status_yes": "Ja",
|
||||
"status_no": "Nein",
|
||||
"status_maybe": "Vielleicht",
|
||||
"meta_updated": "Aktualisiert um {time} · Auto-Refresh alle 30s",
|
||||
"action_import_title": "Zur Gästeliste"
|
||||
},
|
||||
"rsvp_summary": {
|
||||
"label_yes": "Ja",
|
||||
"label_maybe": "Vielleicht",
|
||||
"label_no": "Nein",
|
||||
"label_pending": "Offen",
|
||||
"attending": "kommen"
|
||||
}
|
||||
}
|
||||
145
apps/mana/apps/web/src/lib/i18n/locales/events/en.json
Normal file
145
apps/mana/apps/web/src/lib/i18n/locales/events/en.json
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
{
|
||||
"list_view": {
|
||||
"doc_title": "Events - Mana",
|
||||
"tab_mine": "My events",
|
||||
"tab_discover": "Discover",
|
||||
"subtitle_count": "{upcoming} upcoming · {past} past",
|
||||
"new_event_btn": "+ New event",
|
||||
"cancel_btn": "Cancel",
|
||||
"placeholder_title": "What's it about? (e.g. Anna's birthday)",
|
||||
"placeholder_location": "Location (optional)",
|
||||
"submit_create": "Create event",
|
||||
"section_upcoming": "Upcoming",
|
||||
"section_past": "Past",
|
||||
"empty_upcoming": "No upcoming events. Time for a party?"
|
||||
},
|
||||
"detail_view": {
|
||||
"loading": "Loading event...",
|
||||
"back": "← Back",
|
||||
"action_edit": "Edit",
|
||||
"action_delete": "Delete",
|
||||
"confirm_delete": "Really delete event \"{title}\"?",
|
||||
"placeholder_title": "Event title",
|
||||
"placeholder_description": "Description",
|
||||
"placeholder_location": "Location — type an address...",
|
||||
"location_pinned_title": "Coordinates set",
|
||||
"label_start": "Start",
|
||||
"label_end": "End",
|
||||
"label_all_day": "All day",
|
||||
"action_cancel": "Cancel",
|
||||
"action_save": "Save",
|
||||
"map_iframe_title": "Event location on map",
|
||||
"map_open": "Open in OpenStreetMap →",
|
||||
"label_visibility": "Visibility",
|
||||
"section_rsvps": "RSVPs",
|
||||
"section_guests": "Guests",
|
||||
"section_bring_list": "Bring list",
|
||||
"section_share": "Share",
|
||||
"action_copy_link": "Copy",
|
||||
"action_make_private": "Make private",
|
||||
"share_hint": "Replies appear automatically below in \"Replies via link\" (polling every 30s).",
|
||||
"action_publish": "Publish event & generate link"
|
||||
},
|
||||
"event_card": {
|
||||
"badge_draft": "Draft",
|
||||
"badge_cancelled": "Cancelled",
|
||||
"badge_published": "Shared",
|
||||
"all_day": "All day",
|
||||
"yes_count": "{count} attending",
|
||||
"pending_count": "· {count} pending"
|
||||
},
|
||||
"discovered_card": {
|
||||
"all_day": "All day",
|
||||
"source_fallback": "Source",
|
||||
"saved_label": "Saved",
|
||||
"action_save": "Save",
|
||||
"action_dismiss": "Dismiss"
|
||||
},
|
||||
"discovery_tab": {
|
||||
"loading": "Loading...",
|
||||
"action_refresh": "Refresh",
|
||||
"action_sources": "Sources",
|
||||
"action_sources_count": "Sources ({count})",
|
||||
"loading_events": "Loading events...",
|
||||
"empty_title": "No events found yet",
|
||||
"empty_hint": "Add iCal feeds from venues or clubs to discover events.",
|
||||
"action_manage_sources": "Manage sources",
|
||||
"action_load_more": "Load more"
|
||||
},
|
||||
"discovery_setup": {
|
||||
"title": "Set up event discovery",
|
||||
"step1_desc": "Which regions should be searched for events?",
|
||||
"action_next": "Next",
|
||||
"step2_desc": "What interests you?",
|
||||
"placeholder_freetext": "More interests (comma-separated, e.g. improv theatre, Rust meetups)",
|
||||
"action_back": "Back",
|
||||
"action_finish": "Done"
|
||||
},
|
||||
"region_picker": {
|
||||
"radius_unit": "{km} km",
|
||||
"add_region": "+ Region",
|
||||
"placeholder_search": "Search city or region...",
|
||||
"radius_label": "Radius: {km} km",
|
||||
"searching": "Searching...",
|
||||
"action_cancel": "Cancel"
|
||||
},
|
||||
"source_manager": {
|
||||
"section_title": "Sources",
|
||||
"action_discover": "Find automatically",
|
||||
"action_discovering": "Searching...",
|
||||
"action_add_ical": "+ iCal feed",
|
||||
"placeholder_name": "Name (e.g. Jazzhaus Freiburg)",
|
||||
"placeholder_url": "iCal URL (.ics)",
|
||||
"action_submit_add": "Add",
|
||||
"action_cancel": "Cancel",
|
||||
"section_suggested": "Suggested sources",
|
||||
"action_activate": "Activate",
|
||||
"action_reject": "x",
|
||||
"empty": "No sources yet. Use \"Find automatically\" or add iCal feeds manually.",
|
||||
"meta_last_scan": "Last scan: {date}",
|
||||
"never_scanned": "never",
|
||||
"errors_count": "{count} errors",
|
||||
"action_scan": "Scan",
|
||||
"action_scan_title": "Scan now",
|
||||
"action_remove_title": "Remove"
|
||||
},
|
||||
"bring_list_editor": {
|
||||
"placeholder_label": "e.g. salad, wine, speaker",
|
||||
"placeholder_quantity": "Count",
|
||||
"action_add": "Add",
|
||||
"option_no_one": "— No one —",
|
||||
"action_remove_title": "Remove",
|
||||
"empty": "Nothing on the list yet.",
|
||||
"claimed_via_link": "{name} (via link)",
|
||||
"unknown_assignee": "?"
|
||||
},
|
||||
"guest_list_editor": {
|
||||
"placeholder_name": "Name",
|
||||
"placeholder_email": "Email (optional)",
|
||||
"action_add": "Add",
|
||||
"action_remove_title": "Remove",
|
||||
"empty": "No guests added yet.",
|
||||
"rsvp_pending": "Pending",
|
||||
"rsvp_yes": "Yes",
|
||||
"rsvp_maybe": "Maybe",
|
||||
"rsvp_no": "No"
|
||||
},
|
||||
"public_rsvp_list": {
|
||||
"title": "Replies via link",
|
||||
"action_refresh": "Reload",
|
||||
"err_load": "Loading error",
|
||||
"empty": "No replies via share link yet.",
|
||||
"status_yes": "Yes",
|
||||
"status_no": "No",
|
||||
"status_maybe": "Maybe",
|
||||
"meta_updated": "Updated at {time} · Auto-refresh every 30s",
|
||||
"action_import_title": "Add to guest list"
|
||||
},
|
||||
"rsvp_summary": {
|
||||
"label_yes": "Yes",
|
||||
"label_maybe": "Maybe",
|
||||
"label_no": "No",
|
||||
"label_pending": "Pending",
|
||||
"attending": "attending"
|
||||
}
|
||||
}
|
||||
145
apps/mana/apps/web/src/lib/i18n/locales/events/es.json
Normal file
145
apps/mana/apps/web/src/lib/i18n/locales/events/es.json
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
{
|
||||
"list_view": {
|
||||
"doc_title": "Eventos - Mana",
|
||||
"tab_mine": "Mis eventos",
|
||||
"tab_discover": "Descubrir",
|
||||
"subtitle_count": "{upcoming} próximos · {past} pasados",
|
||||
"new_event_btn": "+ Nuevo evento",
|
||||
"cancel_btn": "Cancelar",
|
||||
"placeholder_title": "¿De qué se trata? (p. ej. cumpleaños de Anna)",
|
||||
"placeholder_location": "Lugar (opcional)",
|
||||
"submit_create": "Crear evento",
|
||||
"section_upcoming": "Próximos",
|
||||
"section_past": "Pasados",
|
||||
"empty_upcoming": "No hay eventos próximos. ¿Tiempo para una fiesta?"
|
||||
},
|
||||
"detail_view": {
|
||||
"loading": "Cargando evento...",
|
||||
"back": "← Atrás",
|
||||
"action_edit": "Editar",
|
||||
"action_delete": "Eliminar",
|
||||
"confirm_delete": "¿Eliminar de verdad el evento \"{title}\"?",
|
||||
"placeholder_title": "Título del evento",
|
||||
"placeholder_description": "Descripción",
|
||||
"placeholder_location": "Lugar — escribe una dirección...",
|
||||
"location_pinned_title": "Coordenadas establecidas",
|
||||
"label_start": "Inicio",
|
||||
"label_end": "Fin",
|
||||
"label_all_day": "Todo el día",
|
||||
"action_cancel": "Cancelar",
|
||||
"action_save": "Guardar",
|
||||
"map_iframe_title": "Lugar del evento en el mapa",
|
||||
"map_open": "Abrir en OpenStreetMap →",
|
||||
"label_visibility": "Visibilidad",
|
||||
"section_rsvps": "RSVPs",
|
||||
"section_guests": "Invitados",
|
||||
"section_bring_list": "Lista de cosas",
|
||||
"section_share": "Compartir",
|
||||
"action_copy_link": "Copiar",
|
||||
"action_make_private": "Hacer privado",
|
||||
"share_hint": "Las respuestas aparecen automáticamente abajo en «Respuestas vía enlace» (polling cada 30s).",
|
||||
"action_publish": "Publicar evento y generar enlace"
|
||||
},
|
||||
"event_card": {
|
||||
"badge_draft": "Borrador",
|
||||
"badge_cancelled": "Cancelado",
|
||||
"badge_published": "Compartido",
|
||||
"all_day": "Todo el día",
|
||||
"yes_count": "{count} asisten",
|
||||
"pending_count": "· {count} pendientes"
|
||||
},
|
||||
"discovered_card": {
|
||||
"all_day": "Todo el día",
|
||||
"source_fallback": "Fuente",
|
||||
"saved_label": "Guardado",
|
||||
"action_save": "Guardar",
|
||||
"action_dismiss": "Descartar"
|
||||
},
|
||||
"discovery_tab": {
|
||||
"loading": "Cargando...",
|
||||
"action_refresh": "Actualizar",
|
||||
"action_sources": "Fuentes",
|
||||
"action_sources_count": "Fuentes ({count})",
|
||||
"loading_events": "Cargando eventos...",
|
||||
"empty_title": "Aún no se han encontrado eventos",
|
||||
"empty_hint": "Añade feeds iCal de salas o clubes para descubrir eventos.",
|
||||
"action_manage_sources": "Gestionar fuentes",
|
||||
"action_load_more": "Cargar más"
|
||||
},
|
||||
"discovery_setup": {
|
||||
"title": "Configurar descubrimiento de eventos",
|
||||
"step1_desc": "¿En qué regiones se deben buscar eventos?",
|
||||
"action_next": "Siguiente",
|
||||
"step2_desc": "¿Qué te interesa?",
|
||||
"placeholder_freetext": "Más intereses (separados por coma, p. ej. teatro improv, meetups Rust)",
|
||||
"action_back": "Atrás",
|
||||
"action_finish": "Listo"
|
||||
},
|
||||
"region_picker": {
|
||||
"radius_unit": "{km} km",
|
||||
"add_region": "+ Región",
|
||||
"placeholder_search": "Buscar ciudad o región...",
|
||||
"radius_label": "Radio: {km} km",
|
||||
"searching": "Buscando...",
|
||||
"action_cancel": "Cancelar"
|
||||
},
|
||||
"source_manager": {
|
||||
"section_title": "Fuentes",
|
||||
"action_discover": "Buscar automáticamente",
|
||||
"action_discovering": "Buscando...",
|
||||
"action_add_ical": "+ Feed iCal",
|
||||
"placeholder_name": "Nombre (p. ej. Jazzhaus Freiburg)",
|
||||
"placeholder_url": "URL iCal (.ics)",
|
||||
"action_submit_add": "Añadir",
|
||||
"action_cancel": "Cancelar",
|
||||
"section_suggested": "Fuentes sugeridas",
|
||||
"action_activate": "Activar",
|
||||
"action_reject": "x",
|
||||
"empty": "Aún no hay fuentes. Usa \"Buscar automáticamente\" o añade feeds iCal manualmente.",
|
||||
"meta_last_scan": "Último escaneo: {date}",
|
||||
"never_scanned": "nunca",
|
||||
"errors_count": "{count} errores",
|
||||
"action_scan": "Escanear",
|
||||
"action_scan_title": "Escanear ahora",
|
||||
"action_remove_title": "Quitar"
|
||||
},
|
||||
"bring_list_editor": {
|
||||
"placeholder_label": "p. ej. ensalada, vino, altavoz",
|
||||
"placeholder_quantity": "Cantidad",
|
||||
"action_add": "Añadir",
|
||||
"option_no_one": "— Nadie —",
|
||||
"action_remove_title": "Quitar",
|
||||
"empty": "Aún no hay nada en la lista.",
|
||||
"claimed_via_link": "{name} (vía enlace)",
|
||||
"unknown_assignee": "?"
|
||||
},
|
||||
"guest_list_editor": {
|
||||
"placeholder_name": "Nombre",
|
||||
"placeholder_email": "Correo electrónico (opcional)",
|
||||
"action_add": "Añadir",
|
||||
"action_remove_title": "Quitar",
|
||||
"empty": "Aún no se han añadido invitados.",
|
||||
"rsvp_pending": "Pendiente",
|
||||
"rsvp_yes": "Sí",
|
||||
"rsvp_maybe": "Quizás",
|
||||
"rsvp_no": "No"
|
||||
},
|
||||
"public_rsvp_list": {
|
||||
"title": "Respuestas vía enlace",
|
||||
"action_refresh": "Recargar",
|
||||
"err_load": "Error al cargar",
|
||||
"empty": "Aún no hay respuestas vía enlace.",
|
||||
"status_yes": "Sí",
|
||||
"status_no": "No",
|
||||
"status_maybe": "Quizás",
|
||||
"meta_updated": "Actualizado a las {time} · Auto-refresco cada 30s",
|
||||
"action_import_title": "A la lista de invitados"
|
||||
},
|
||||
"rsvp_summary": {
|
||||
"label_yes": "Sí",
|
||||
"label_maybe": "Quizás",
|
||||
"label_no": "No",
|
||||
"label_pending": "Pendiente",
|
||||
"attending": "asisten"
|
||||
}
|
||||
}
|
||||
145
apps/mana/apps/web/src/lib/i18n/locales/events/fr.json
Normal file
145
apps/mana/apps/web/src/lib/i18n/locales/events/fr.json
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
{
|
||||
"list_view": {
|
||||
"doc_title": "Événements - Mana",
|
||||
"tab_mine": "Mes événements",
|
||||
"tab_discover": "Découvrir",
|
||||
"subtitle_count": "{upcoming} à venir · {past} passés",
|
||||
"new_event_btn": "+ Nouvel événement",
|
||||
"cancel_btn": "Annuler",
|
||||
"placeholder_title": "De quoi s'agit-il ? (p. ex. anniversaire d'Anna)",
|
||||
"placeholder_location": "Lieu (optionnel)",
|
||||
"submit_create": "Créer l'événement",
|
||||
"section_upcoming": "À venir",
|
||||
"section_past": "Passés",
|
||||
"empty_upcoming": "Aucun événement à venir. C'est l'heure d'une fête ?"
|
||||
},
|
||||
"detail_view": {
|
||||
"loading": "Chargement de l'événement...",
|
||||
"back": "← Retour",
|
||||
"action_edit": "Modifier",
|
||||
"action_delete": "Supprimer",
|
||||
"confirm_delete": "Vraiment supprimer l'événement « {title} » ?",
|
||||
"placeholder_title": "Titre de l'événement",
|
||||
"placeholder_description": "Description",
|
||||
"placeholder_location": "Lieu — saisis une adresse...",
|
||||
"location_pinned_title": "Coordonnées définies",
|
||||
"label_start": "Début",
|
||||
"label_end": "Fin",
|
||||
"label_all_day": "Toute la journée",
|
||||
"action_cancel": "Annuler",
|
||||
"action_save": "Enregistrer",
|
||||
"map_iframe_title": "Lieu de l'événement sur la carte",
|
||||
"map_open": "Ouvrir dans OpenStreetMap →",
|
||||
"label_visibility": "Visibilité",
|
||||
"section_rsvps": "RSVPs",
|
||||
"section_guests": "Invités",
|
||||
"section_bring_list": "Liste à apporter",
|
||||
"section_share": "Partager",
|
||||
"action_copy_link": "Copier",
|
||||
"action_make_private": "Rendre privé",
|
||||
"share_hint": "Les réponses apparaissent automatiquement ci-dessous dans « Réponses via le lien » (polling toutes les 30s).",
|
||||
"action_publish": "Publier l'événement & générer le lien"
|
||||
},
|
||||
"event_card": {
|
||||
"badge_draft": "Brouillon",
|
||||
"badge_cancelled": "Annulé",
|
||||
"badge_published": "Partagé",
|
||||
"all_day": "Toute la journée",
|
||||
"yes_count": "{count} viennent",
|
||||
"pending_count": "· {count} en attente"
|
||||
},
|
||||
"discovered_card": {
|
||||
"all_day": "Toute la journée",
|
||||
"source_fallback": "Source",
|
||||
"saved_label": "Enregistré",
|
||||
"action_save": "Enregistrer",
|
||||
"action_dismiss": "Masquer"
|
||||
},
|
||||
"discovery_tab": {
|
||||
"loading": "Chargement...",
|
||||
"action_refresh": "Actualiser",
|
||||
"action_sources": "Sources",
|
||||
"action_sources_count": "Sources ({count})",
|
||||
"loading_events": "Chargement des événements...",
|
||||
"empty_title": "Aucun événement trouvé pour l'instant",
|
||||
"empty_hint": "Ajoute des flux iCal de salles ou d'associations pour découvrir des événements.",
|
||||
"action_manage_sources": "Gérer les sources",
|
||||
"action_load_more": "Charger plus"
|
||||
},
|
||||
"discovery_setup": {
|
||||
"title": "Configurer la découverte d'événements",
|
||||
"step1_desc": "Quelles régions doivent être recherchées ?",
|
||||
"action_next": "Suivant",
|
||||
"step2_desc": "Qu'est-ce qui t'intéresse ?",
|
||||
"placeholder_freetext": "Autres centres d'intérêt (séparés par virgule, p. ex. impro-théâtre, meetups Rust)",
|
||||
"action_back": "Retour",
|
||||
"action_finish": "Terminé"
|
||||
},
|
||||
"region_picker": {
|
||||
"radius_unit": "{km} km",
|
||||
"add_region": "+ Région",
|
||||
"placeholder_search": "Rechercher une ville ou région...",
|
||||
"radius_label": "Rayon : {km} km",
|
||||
"searching": "Recherche...",
|
||||
"action_cancel": "Annuler"
|
||||
},
|
||||
"source_manager": {
|
||||
"section_title": "Sources",
|
||||
"action_discover": "Trouver automatiquement",
|
||||
"action_discovering": "Recherche...",
|
||||
"action_add_ical": "+ Flux iCal",
|
||||
"placeholder_name": "Nom (p. ex. Jazzhaus Freiburg)",
|
||||
"placeholder_url": "URL iCal (.ics)",
|
||||
"action_submit_add": "Ajouter",
|
||||
"action_cancel": "Annuler",
|
||||
"section_suggested": "Sources suggérées",
|
||||
"action_activate": "Activer",
|
||||
"action_reject": "x",
|
||||
"empty": "Pas encore de sources. Utilise « Trouver automatiquement » ou ajoute des flux iCal manuellement.",
|
||||
"meta_last_scan": "Dernier scan : {date}",
|
||||
"never_scanned": "jamais",
|
||||
"errors_count": "{count} erreurs",
|
||||
"action_scan": "Scanner",
|
||||
"action_scan_title": "Scanner maintenant",
|
||||
"action_remove_title": "Retirer"
|
||||
},
|
||||
"bring_list_editor": {
|
||||
"placeholder_label": "p. ex. salade, vin, enceinte",
|
||||
"placeholder_quantity": "Quantité",
|
||||
"action_add": "Ajouter",
|
||||
"option_no_one": "— Personne —",
|
||||
"action_remove_title": "Retirer",
|
||||
"empty": "Rien sur la liste pour l'instant.",
|
||||
"claimed_via_link": "{name} (via lien)",
|
||||
"unknown_assignee": "?"
|
||||
},
|
||||
"guest_list_editor": {
|
||||
"placeholder_name": "Nom",
|
||||
"placeholder_email": "E-mail (optionnel)",
|
||||
"action_add": "Ajouter",
|
||||
"action_remove_title": "Retirer",
|
||||
"empty": "Aucun invité ajouté pour l'instant.",
|
||||
"rsvp_pending": "En attente",
|
||||
"rsvp_yes": "Oui",
|
||||
"rsvp_maybe": "Peut-être",
|
||||
"rsvp_no": "Non"
|
||||
},
|
||||
"public_rsvp_list": {
|
||||
"title": "Réponses via lien",
|
||||
"action_refresh": "Recharger",
|
||||
"err_load": "Erreur de chargement",
|
||||
"empty": "Pas encore de réponses via lien.",
|
||||
"status_yes": "Oui",
|
||||
"status_no": "Non",
|
||||
"status_maybe": "Peut-être",
|
||||
"meta_updated": "Mis à jour à {time} · Auto-rafraîchissement toutes les 30s",
|
||||
"action_import_title": "Vers la liste d'invités"
|
||||
},
|
||||
"rsvp_summary": {
|
||||
"label_yes": "Oui",
|
||||
"label_maybe": "Peut-être",
|
||||
"label_no": "Non",
|
||||
"label_pending": "En attente",
|
||||
"attending": "viennent"
|
||||
}
|
||||
}
|
||||
145
apps/mana/apps/web/src/lib/i18n/locales/events/it.json
Normal file
145
apps/mana/apps/web/src/lib/i18n/locales/events/it.json
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
{
|
||||
"list_view": {
|
||||
"doc_title": "Eventi - Mana",
|
||||
"tab_mine": "I miei eventi",
|
||||
"tab_discover": "Scopri",
|
||||
"subtitle_count": "{upcoming} in arrivo · {past} passati",
|
||||
"new_event_btn": "+ Nuovo evento",
|
||||
"cancel_btn": "Annulla",
|
||||
"placeholder_title": "Di cosa si tratta? (es. compleanno di Anna)",
|
||||
"placeholder_location": "Luogo (opzionale)",
|
||||
"submit_create": "Crea evento",
|
||||
"section_upcoming": "In arrivo",
|
||||
"section_past": "Passati",
|
||||
"empty_upcoming": "Nessun evento in arrivo. Tempo di una festa?"
|
||||
},
|
||||
"detail_view": {
|
||||
"loading": "Caricamento evento...",
|
||||
"back": "← Indietro",
|
||||
"action_edit": "Modifica",
|
||||
"action_delete": "Elimina",
|
||||
"confirm_delete": "Eliminare davvero l'evento \"{title}\"?",
|
||||
"placeholder_title": "Titolo dell'evento",
|
||||
"placeholder_description": "Descrizione",
|
||||
"placeholder_location": "Luogo — digita un indirizzo...",
|
||||
"location_pinned_title": "Coordinate impostate",
|
||||
"label_start": "Inizio",
|
||||
"label_end": "Fine",
|
||||
"label_all_day": "Tutto il giorno",
|
||||
"action_cancel": "Annulla",
|
||||
"action_save": "Salva",
|
||||
"map_iframe_title": "Luogo dell'evento sulla mappa",
|
||||
"map_open": "Apri in OpenStreetMap →",
|
||||
"label_visibility": "Visibilità",
|
||||
"section_rsvps": "RSVPs",
|
||||
"section_guests": "Ospiti",
|
||||
"section_bring_list": "Lista da portare",
|
||||
"section_share": "Condividi",
|
||||
"action_copy_link": "Copia",
|
||||
"action_make_private": "Rendi privato",
|
||||
"share_hint": "Le risposte appaiono automaticamente sotto in «Risposte via link» (polling ogni 30s).",
|
||||
"action_publish": "Pubblica evento & genera link"
|
||||
},
|
||||
"event_card": {
|
||||
"badge_draft": "Bozza",
|
||||
"badge_cancelled": "Annullato",
|
||||
"badge_published": "Condiviso",
|
||||
"all_day": "Tutto il giorno",
|
||||
"yes_count": "{count} vengono",
|
||||
"pending_count": "· {count} in sospeso"
|
||||
},
|
||||
"discovered_card": {
|
||||
"all_day": "Tutto il giorno",
|
||||
"source_fallback": "Fonte",
|
||||
"saved_label": "Salvato",
|
||||
"action_save": "Salva",
|
||||
"action_dismiss": "Nascondi"
|
||||
},
|
||||
"discovery_tab": {
|
||||
"loading": "Caricamento...",
|
||||
"action_refresh": "Aggiorna",
|
||||
"action_sources": "Fonti",
|
||||
"action_sources_count": "Fonti ({count})",
|
||||
"loading_events": "Caricamento eventi...",
|
||||
"empty_title": "Ancora nessun evento trovato",
|
||||
"empty_hint": "Aggiungi feed iCal di locali o associazioni per scoprire eventi.",
|
||||
"action_manage_sources": "Gestisci fonti",
|
||||
"action_load_more": "Carica altri"
|
||||
},
|
||||
"discovery_setup": {
|
||||
"title": "Imposta scoperta eventi",
|
||||
"step1_desc": "In quali regioni cercare eventi?",
|
||||
"action_next": "Avanti",
|
||||
"step2_desc": "Cosa ti interessa?",
|
||||
"placeholder_freetext": "Altri interessi (separati da virgola, es. teatro improv, meetup Rust)",
|
||||
"action_back": "Indietro",
|
||||
"action_finish": "Fatto"
|
||||
},
|
||||
"region_picker": {
|
||||
"radius_unit": "{km} km",
|
||||
"add_region": "+ Regione",
|
||||
"placeholder_search": "Cerca città o regione...",
|
||||
"radius_label": "Raggio: {km} km",
|
||||
"searching": "Ricerca...",
|
||||
"action_cancel": "Annulla"
|
||||
},
|
||||
"source_manager": {
|
||||
"section_title": "Fonti",
|
||||
"action_discover": "Trova automaticamente",
|
||||
"action_discovering": "Ricerca...",
|
||||
"action_add_ical": "+ Feed iCal",
|
||||
"placeholder_name": "Nome (es. Jazzhaus Freiburg)",
|
||||
"placeholder_url": "URL iCal (.ics)",
|
||||
"action_submit_add": "Aggiungi",
|
||||
"action_cancel": "Annulla",
|
||||
"section_suggested": "Fonti suggerite",
|
||||
"action_activate": "Attiva",
|
||||
"action_reject": "x",
|
||||
"empty": "Ancora nessuna fonte. Usa \"Trova automaticamente\" o aggiungi feed iCal manualmente.",
|
||||
"meta_last_scan": "Ultima scansione: {date}",
|
||||
"never_scanned": "mai",
|
||||
"errors_count": "{count} errori",
|
||||
"action_scan": "Scansiona",
|
||||
"action_scan_title": "Scansiona ora",
|
||||
"action_remove_title": "Rimuovi"
|
||||
},
|
||||
"bring_list_editor": {
|
||||
"placeholder_label": "es. insalata, vino, casse",
|
||||
"placeholder_quantity": "Quantità",
|
||||
"action_add": "Aggiungi",
|
||||
"option_no_one": "— Nessuno —",
|
||||
"action_remove_title": "Rimuovi",
|
||||
"empty": "Ancora niente nella lista.",
|
||||
"claimed_via_link": "{name} (via link)",
|
||||
"unknown_assignee": "?"
|
||||
},
|
||||
"guest_list_editor": {
|
||||
"placeholder_name": "Nome",
|
||||
"placeholder_email": "E-mail (opzionale)",
|
||||
"action_add": "Aggiungi",
|
||||
"action_remove_title": "Rimuovi",
|
||||
"empty": "Ancora nessun ospite aggiunto.",
|
||||
"rsvp_pending": "In sospeso",
|
||||
"rsvp_yes": "Sì",
|
||||
"rsvp_maybe": "Forse",
|
||||
"rsvp_no": "No"
|
||||
},
|
||||
"public_rsvp_list": {
|
||||
"title": "Risposte via link",
|
||||
"action_refresh": "Ricarica",
|
||||
"err_load": "Errore di caricamento",
|
||||
"empty": "Ancora nessuna risposta via link.",
|
||||
"status_yes": "Sì",
|
||||
"status_no": "No",
|
||||
"status_maybe": "Forse",
|
||||
"meta_updated": "Aggiornato alle {time} · Auto-refresh ogni 30s",
|
||||
"action_import_title": "Alla lista ospiti"
|
||||
},
|
||||
"rsvp_summary": {
|
||||
"label_yes": "Sì",
|
||||
"label_maybe": "Forse",
|
||||
"label_no": "No",
|
||||
"label_pending": "In sospeso",
|
||||
"attending": "vengono"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { _ } from 'svelte-i18n';
|
||||
import { useUpcomingEvents, usePastEvents, useGuestsByEvent, summarizeRsvps } from './queries';
|
||||
import { eventsStore } from './stores/events.svelte';
|
||||
import { drainTombstones } from './tombstones';
|
||||
|
|
@ -59,30 +60,35 @@
|
|||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Events - Mana</title>
|
||||
<title>{$_('events.list_view.doc_title')}</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="events-page">
|
||||
<div class="tab-bar">
|
||||
<button class="tab" class:active={activeTab === 'mine'} onclick={() => (activeTab = 'mine')}>
|
||||
Meine Events
|
||||
{$_('events.list_view.tab_mine')}
|
||||
</button>
|
||||
<button
|
||||
class="tab"
|
||||
class:active={activeTab === 'discover'}
|
||||
onclick={() => (activeTab = 'discover')}
|
||||
>
|
||||
Entdecken
|
||||
{$_('events.list_view.tab_discover')}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{#if activeTab === 'mine'}
|
||||
<header class="events-header">
|
||||
<p class="page-subtitle">
|
||||
{(upcoming.value ?? []).length} bevorstehend · {(past.value ?? []).length} vergangen
|
||||
{$_('events.list_view.subtitle_count', {
|
||||
values: {
|
||||
upcoming: (upcoming.value ?? []).length,
|
||||
past: (past.value ?? []).length,
|
||||
},
|
||||
})}
|
||||
</p>
|
||||
<button class="new-btn" onclick={() => (showCreate = !showCreate)}>
|
||||
{showCreate ? 'Abbrechen' : '+ Neues Event'}
|
||||
{showCreate ? $_('events.list_view.cancel_btn') : $_('events.list_view.new_event_btn')}
|
||||
</button>
|
||||
</header>
|
||||
|
||||
|
|
@ -91,22 +97,28 @@
|
|||
<input
|
||||
class="input"
|
||||
bind:value={newTitle}
|
||||
placeholder="Worum geht's? (z. B. Geburtstag Anna)"
|
||||
placeholder={$_('events.list_view.placeholder_title')}
|
||||
required
|
||||
/>
|
||||
<div class="form-row">
|
||||
<input class="input" type="date" bind:value={newDate} required />
|
||||
<input class="input" type="time" bind:value={newTime} />
|
||||
<input class="input" bind:value={newLocation} placeholder="Ort (optional)" />
|
||||
<input
|
||||
class="input"
|
||||
bind:value={newLocation}
|
||||
placeholder={$_('events.list_view.placeholder_location')}
|
||||
/>
|
||||
</div>
|
||||
<button type="submit" class="action-btn primary">Event anlegen</button>
|
||||
<button type="submit" class="action-btn primary">
|
||||
{$_('events.list_view.submit_create')}
|
||||
</button>
|
||||
</form>
|
||||
{/if}
|
||||
|
||||
<section class="event-section">
|
||||
<h2 class="section-title">Bevorstehend</h2>
|
||||
<h2 class="section-title">{$_('events.list_view.section_upcoming')}</h2>
|
||||
{#if (upcoming.value ?? []).length === 0}
|
||||
<p class="empty">Keine bevorstehenden Events. Zeit fur eine Party?</p>
|
||||
<p class="empty">{$_('events.list_view.empty_upcoming')}</p>
|
||||
{:else}
|
||||
<div class="event-list">
|
||||
{#each upcoming.value ?? [] as event (event.id)}
|
||||
|
|
@ -119,7 +131,7 @@
|
|||
|
||||
{#if (past.value ?? []).length > 0}
|
||||
<section class="event-section">
|
||||
<h2 class="section-title">Vergangen</h2>
|
||||
<h2 class="section-title">{$_('events.list_view.section_past')}</h2>
|
||||
<div class="event-list">
|
||||
{#each past.value ?? [] as event (event.id)}
|
||||
<EventCard {event} onclick={() => open(event)} />
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
<script lang="ts">
|
||||
import { _ } from 'svelte-i18n';
|
||||
import { useEventItems, useEventGuests } from '../queries';
|
||||
import { eventItemsStore } from '../stores/items.svelte';
|
||||
import { eventsStore } from '../stores/events.svelte';
|
||||
|
|
@ -20,10 +21,14 @@
|
|||
|
||||
function assigneeLabel(item: EventItem): string | null {
|
||||
if (item.assignedGuestId) {
|
||||
return guestNameById.get(item.assignedGuestId) ?? '?';
|
||||
return (
|
||||
guestNameById.get(item.assignedGuestId) ?? $_('events.bring_list_editor.unknown_assignee')
|
||||
);
|
||||
}
|
||||
if (item.claimedByName) {
|
||||
return `${item.claimedByName} (via Link)`;
|
||||
return $_('events.bring_list_editor.claimed_via_link', {
|
||||
values: { name: item.claimedByName },
|
||||
});
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
|
@ -50,7 +55,7 @@
|
|||
<input
|
||||
type="text"
|
||||
bind:value={newLabel}
|
||||
placeholder="z. B. Salat, Wein, Lautsprecher"
|
||||
placeholder={$_('events.bring_list_editor.placeholder_label')}
|
||||
class="input label-input"
|
||||
required
|
||||
/>
|
||||
|
|
@ -58,11 +63,11 @@
|
|||
type="number"
|
||||
min="1"
|
||||
max="999"
|
||||
placeholder="Anzahl"
|
||||
placeholder={$_('events.bring_list_editor.placeholder_quantity')}
|
||||
bind:value={newQuantity}
|
||||
class="input qty-input"
|
||||
/>
|
||||
<button type="submit" class="add-btn">Hinzufügen</button>
|
||||
<button type="submit" class="add-btn">{$_('events.bring_list_editor.action_add')}</button>
|
||||
</form>
|
||||
|
||||
<ul class="item-list">
|
||||
|
|
@ -94,7 +99,7 @@
|
|||
value={item.assignedGuestId ?? ''}
|
||||
onchange={(e) => eventItemsStore.assign(item.id, e.currentTarget.value || null)}
|
||||
>
|
||||
<option value="">— Niemand —</option>
|
||||
<option value="">{$_('events.bring_list_editor.option_no_one')}</option>
|
||||
{#each guests.value ?? [] as g (g.id)}
|
||||
<option value={g.id}>{g.name}</option>
|
||||
{/each}
|
||||
|
|
@ -103,7 +108,7 @@
|
|||
<button
|
||||
class="remove-btn"
|
||||
onclick={() => eventItemsStore.deleteItem(item.id)}
|
||||
title="Entfernen"
|
||||
title={$_('events.bring_list_editor.action_remove_title')}
|
||||
>
|
||||
×
|
||||
</button>
|
||||
|
|
@ -111,7 +116,7 @@
|
|||
{/each}
|
||||
|
||||
{#if (items.value ?? []).length === 0}
|
||||
<li class="empty">Noch nichts auf der Liste.</li>
|
||||
<li class="empty">{$_('events.bring_list_editor.empty')}</li>
|
||||
{/if}
|
||||
</ul>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
<script lang="ts">
|
||||
import { formatDate, formatTime } from '$lib/i18n/format';
|
||||
import { _ } from 'svelte-i18n';
|
||||
import type { DiscoveredEvent } from '../discovery/types';
|
||||
|
||||
interface Props {
|
||||
|
|
@ -19,7 +20,9 @@
|
|||
})
|
||||
);
|
||||
const timeLabel = $derived(
|
||||
event.allDay ? 'Ganztag' : formatTime(startDate, { hour: '2-digit', minute: '2-digit' })
|
||||
event.allDay
|
||||
? $_('events.discovered_card.all_day')
|
||||
: formatTime(startDate, { hour: '2-digit', minute: '2-digit' })
|
||||
);
|
||||
|
||||
const isSaved = $derived(event.userAction === 'save');
|
||||
|
|
@ -54,14 +57,20 @@
|
|||
{event.sourceName}
|
||||
</a>
|
||||
{:else}
|
||||
<a class="source-link" href={event.sourceUrl} target="_blank" rel="noopener"> Quelle </a>
|
||||
<a class="source-link" href={event.sourceUrl} target="_blank" rel="noopener">
|
||||
{$_('events.discovered_card.source_fallback')}
|
||||
</a>
|
||||
{/if}
|
||||
<div class="actions">
|
||||
{#if isSaved}
|
||||
<span class="saved-label">Gespeichert</span>
|
||||
<span class="saved-label">{$_('events.discovered_card.saved_label')}</span>
|
||||
{:else}
|
||||
<button class="action-btn save" onclick={onSave}>Merken</button>
|
||||
<button class="action-btn dismiss" onclick={onDismiss}>Ausblenden</button>
|
||||
<button class="action-btn save" onclick={onSave}>
|
||||
{$_('events.discovered_card.action_save')}
|
||||
</button>
|
||||
<button class="action-btn dismiss" onclick={onDismiss}>
|
||||
{$_('events.discovered_card.action_dismiss')}
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
<script lang="ts">
|
||||
import { _ } from 'svelte-i18n';
|
||||
import { discoveryStore } from '../discovery/store.svelte';
|
||||
import { EVENT_CATEGORIES } from '../discovery/types';
|
||||
import RegionPicker from './RegionPicker.svelte';
|
||||
|
|
@ -44,17 +45,19 @@
|
|||
</script>
|
||||
|
||||
<div class="setup">
|
||||
<h2 class="setup-title">Event-Entdeckung einrichten</h2>
|
||||
<h2 class="setup-title">{$_('events.discovery_setup.title')}</h2>
|
||||
|
||||
{#if step === 1}
|
||||
<div class="step">
|
||||
<p class="step-desc">Welche Regionen sollen nach Events durchsucht werden?</p>
|
||||
<p class="step-desc">{$_('events.discovery_setup.step1_desc')}</p>
|
||||
<RegionPicker regions={discoveryStore.regions} />
|
||||
<button class="next-btn" disabled={!canProceed} onclick={() => (step = 2)}> Weiter </button>
|
||||
<button class="next-btn" disabled={!canProceed} onclick={() => (step = 2)}>
|
||||
{$_('events.discovery_setup.action_next')}
|
||||
</button>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="step">
|
||||
<p class="step-desc">Was interessiert dich?</p>
|
||||
<p class="step-desc">{$_('events.discovery_setup.step2_desc')}</p>
|
||||
<div class="category-grid">
|
||||
{#each EVENT_CATEGORIES as cat}
|
||||
<button
|
||||
|
|
@ -69,11 +72,15 @@
|
|||
<input
|
||||
class="input"
|
||||
bind:value={freetext}
|
||||
placeholder="Weitere Interessen (kommagetrennt, z.B. Impro-Theater, Rust Meetups)"
|
||||
placeholder={$_('events.discovery_setup.placeholder_freetext')}
|
||||
/>
|
||||
<div class="step-actions">
|
||||
<button class="back-btn" onclick={() => (step = 1)}>Zuruck</button>
|
||||
<button class="next-btn" disabled={!canFinish} onclick={finishSetup}> Fertig </button>
|
||||
<button class="back-btn" onclick={() => (step = 1)}>
|
||||
{$_('events.discovery_setup.action_back')}
|
||||
</button>
|
||||
<button class="next-btn" disabled={!canFinish} onclick={finishSetup}>
|
||||
{$_('events.discovery_setup.action_finish')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { _ } from 'svelte-i18n';
|
||||
import { discoveryStore } from '../discovery/store.svelte';
|
||||
import DiscoverySetup from './DiscoverySetup.svelte';
|
||||
import DiscoveredEventCard from './DiscoveredEventCard.svelte';
|
||||
|
|
@ -29,7 +30,7 @@
|
|||
|
||||
<div class="discovery-tab">
|
||||
{#if !initialized}
|
||||
<p class="loading">Lade...</p>
|
||||
<p class="loading">{$_('events.discovery_tab.loading')}</p>
|
||||
{:else if !discoveryStore.isSetUp}
|
||||
<DiscoverySetup onComplete={handleSetupComplete} />
|
||||
{:else}
|
||||
|
|
@ -37,14 +38,18 @@
|
|||
<RegionPicker regions={discoveryStore.regions} />
|
||||
<div class="control-row">
|
||||
<button class="control-btn" onclick={() => discoveryStore.refreshFeed()}>
|
||||
Aktualisieren
|
||||
{$_('events.discovery_tab.action_refresh')}
|
||||
</button>
|
||||
<button
|
||||
class="control-btn"
|
||||
class:active={showSources}
|
||||
onclick={() => (showSources = !showSources)}
|
||||
>
|
||||
Quellen {discoveryStore.sources.length > 0 ? `(${discoveryStore.sources.length})` : ''}
|
||||
{discoveryStore.sources.length > 0
|
||||
? $_('events.discovery_tab.action_sources_count', {
|
||||
values: { count: discoveryStore.sources.length },
|
||||
})
|
||||
: $_('events.discovery_tab.action_sources')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -54,18 +59,16 @@
|
|||
{/if}
|
||||
|
||||
{#if discoveryStore.loading}
|
||||
<p class="loading">Lade Events...</p>
|
||||
<p class="loading">{$_('events.discovery_tab.loading_events')}</p>
|
||||
{:else if discoveryStore.error}
|
||||
<p class="error-msg">{discoveryStore.error}</p>
|
||||
{:else if discoveryStore.feed.length === 0}
|
||||
<div class="empty">
|
||||
<p class="empty-title">Noch keine Events gefunden</p>
|
||||
<p class="empty-hint">
|
||||
Fuge iCal-Feeds von Venues oder Vereinen hinzu, um Events zu entdecken.
|
||||
</p>
|
||||
<p class="empty-title">{$_('events.discovery_tab.empty_title')}</p>
|
||||
<p class="empty-hint">{$_('events.discovery_tab.empty_hint')}</p>
|
||||
{#if !showSources}
|
||||
<button class="action-btn" onclick={() => (showSources = true)}>
|
||||
Quellen verwalten
|
||||
{$_('events.discovery_tab.action_manage_sources')}
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
|
|
@ -83,7 +86,7 @@
|
|||
class="load-more"
|
||||
onclick={() => discoveryStore.refreshFeed({ offset: discoveryStore.feed.length })}
|
||||
>
|
||||
Mehr laden
|
||||
{$_('events.discovery_tab.action_load_more')}
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
<script lang="ts">
|
||||
import { formatDate, formatTime } from '$lib/i18n/format';
|
||||
import { _ } from 'svelte-i18n';
|
||||
import type { SocialEvent, RsvpSummary } from '../types';
|
||||
|
||||
interface Props {
|
||||
|
|
@ -19,7 +20,9 @@
|
|||
})
|
||||
);
|
||||
const timeLabel = $derived(
|
||||
event.isAllDay ? 'Ganztägig' : formatTime(startDate, { hour: '2-digit', minute: '2-digit' })
|
||||
event.isAllDay
|
||||
? $_('events.event_card.all_day')
|
||||
: formatTime(startDate, { hour: '2-digit', minute: '2-digit' })
|
||||
);
|
||||
</script>
|
||||
|
||||
|
|
@ -32,11 +35,11 @@
|
|||
<div class="title-row">
|
||||
<h3 class="title">{event.title}</h3>
|
||||
{#if event.status === 'draft'}
|
||||
<span class="status-badge draft">Entwurf</span>
|
||||
<span class="status-badge draft">{$_('events.event_card.badge_draft')}</span>
|
||||
{:else if event.status === 'cancelled'}
|
||||
<span class="status-badge cancelled">Abgesagt</span>
|
||||
<span class="status-badge cancelled">{$_('events.event_card.badge_cancelled')}</span>
|
||||
{:else if event.isPublished}
|
||||
<span class="status-badge published">Geteilt</span>
|
||||
<span class="status-badge published">{$_('events.event_card.badge_published')}</span>
|
||||
{/if}
|
||||
</div>
|
||||
{#if event.location}
|
||||
|
|
@ -44,9 +47,17 @@
|
|||
{/if}
|
||||
{#if summary}
|
||||
<div class="summary-row">
|
||||
<span class="yes-count">{summary.totalAttending} kommen</span>
|
||||
<span class="yes-count"
|
||||
>{$_('events.event_card.yes_count', {
|
||||
values: { count: summary.totalAttending },
|
||||
})}</span
|
||||
>
|
||||
{#if summary.pending > 0}
|
||||
<span class="pending-count">· {summary.pending} offen</span>
|
||||
<span class="pending-count"
|
||||
>{$_('events.event_card.pending_count', {
|
||||
values: { count: summary.pending },
|
||||
})}</span
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
<script lang="ts">
|
||||
import { _ } from 'svelte-i18n';
|
||||
import { eventGuestsStore } from '../stores/guests.svelte';
|
||||
import { useEventGuests } from '../queries';
|
||||
import type { RsvpStatus } from '../types';
|
||||
|
|
@ -14,12 +15,12 @@
|
|||
let newName = $state('');
|
||||
let newEmail = $state('');
|
||||
|
||||
const RSVP_OPTIONS: { value: RsvpStatus; label: string }[] = [
|
||||
{ value: 'pending', label: 'Offen' },
|
||||
{ value: 'yes', label: 'Ja' },
|
||||
{ value: 'maybe', label: 'Vielleicht' },
|
||||
{ value: 'no', label: 'Nein' },
|
||||
];
|
||||
const RSVP_OPTIONS = $derived<{ value: RsvpStatus; label: string }[]>([
|
||||
{ value: 'pending', label: $_('events.guest_list_editor.rsvp_pending') },
|
||||
{ value: 'yes', label: $_('events.guest_list_editor.rsvp_yes') },
|
||||
{ value: 'maybe', label: $_('events.guest_list_editor.rsvp_maybe') },
|
||||
{ value: 'no', label: $_('events.guest_list_editor.rsvp_no') },
|
||||
]);
|
||||
|
||||
async function handleAdd(e: SubmitEvent) {
|
||||
e.preventDefault();
|
||||
|
|
@ -37,14 +38,20 @@
|
|||
|
||||
<div class="guest-editor">
|
||||
<form class="add-row" onsubmit={handleAdd}>
|
||||
<input type="text" bind:value={newName} placeholder="Name" class="input name-input" required />
|
||||
<input
|
||||
type="text"
|
||||
bind:value={newName}
|
||||
placeholder={$_('events.guest_list_editor.placeholder_name')}
|
||||
class="input name-input"
|
||||
required
|
||||
/>
|
||||
<input
|
||||
type="email"
|
||||
bind:value={newEmail}
|
||||
placeholder="E-Mail (optional)"
|
||||
placeholder={$_('events.guest_list_editor.placeholder_email')}
|
||||
class="input email-input"
|
||||
/>
|
||||
<button type="submit" class="add-btn">Hinzufügen</button>
|
||||
<button type="submit" class="add-btn">{$_('events.guest_list_editor.action_add')}</button>
|
||||
</form>
|
||||
|
||||
<ul class="guest-list">
|
||||
|
|
@ -86,7 +93,7 @@
|
|||
<button
|
||||
class="remove-btn"
|
||||
onclick={() => eventGuestsStore.deleteGuest(guest.id)}
|
||||
title="Entfernen"
|
||||
title={$_('events.guest_list_editor.action_remove_title')}
|
||||
>
|
||||
×
|
||||
</button>
|
||||
|
|
@ -95,7 +102,7 @@
|
|||
{/each}
|
||||
|
||||
{#if (guests.value ?? []).length === 0}
|
||||
<li class="empty">Noch keine Gäste hinzugefügt.</li>
|
||||
<li class="empty">{$_('events.guest_list_editor.empty')}</li>
|
||||
{/if}
|
||||
</ul>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
<script lang="ts">
|
||||
import { formatTime } from '$lib/i18n/format';
|
||||
import { _ } from 'svelte-i18n';
|
||||
import { eventsApi, type PublicRsvpRecord } from '../api';
|
||||
import { eventGuestsStore } from '../stores/guests.svelte';
|
||||
|
||||
|
|
@ -30,7 +31,7 @@
|
|||
consecutiveFailures = 0;
|
||||
lastFetchedAt = new Date();
|
||||
} catch (e) {
|
||||
lastErrorMessage = e instanceof Error ? e.message : 'Fehler beim Laden';
|
||||
lastErrorMessage = e instanceof Error ? e.message : $_('events.public_rsvp_list.err_load');
|
||||
consecutiveFailures++;
|
||||
} finally {
|
||||
loading = false;
|
||||
|
|
@ -64,16 +65,16 @@
|
|||
{#if isPublished}
|
||||
<div class="public-rsvps">
|
||||
<div class="header-row">
|
||||
<h3>Antworten via Link</h3>
|
||||
<h3>{$_('events.public_rsvp_list.title')}</h3>
|
||||
<button class="refresh" onclick={fetchRsvps} disabled={loading}>
|
||||
{loading ? '…' : 'Neu laden'}
|
||||
{loading ? '…' : $_('events.public_rsvp_list.action_refresh')}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{#if showError}
|
||||
<p class="error">{lastErrorMessage}</p>
|
||||
{:else if rsvps.length === 0 && !loading}
|
||||
<p class="empty">Noch keine Antworten via Share-Link.</p>
|
||||
<p class="empty">{$_('events.public_rsvp_list.empty')}</p>
|
||||
{:else}
|
||||
<ul class="rsvp-list">
|
||||
{#each rsvps as r (r.id)}
|
||||
|
|
@ -82,7 +83,11 @@
|
|||
<div class="name-row">
|
||||
<span class="name">{r.name}</span>
|
||||
<span class="status status-{r.status}">
|
||||
{r.status === 'yes' ? 'Ja' : r.status === 'no' ? 'Nein' : 'Vielleicht'}
|
||||
{r.status === 'yes'
|
||||
? $_('events.public_rsvp_list.status_yes')
|
||||
: r.status === 'no'
|
||||
? $_('events.public_rsvp_list.status_no')
|
||||
: $_('events.public_rsvp_list.status_maybe')}
|
||||
</span>
|
||||
{#if r.plusOnes > 0}
|
||||
<span class="plus">+{r.plusOnes}</span>
|
||||
|
|
@ -95,7 +100,11 @@
|
|||
<div class="note">„{r.note}“</div>
|
||||
{/if}
|
||||
</div>
|
||||
<button class="import-btn" onclick={() => importToGuestList(r)} title="Zur Gästeliste">
|
||||
<button
|
||||
class="import-btn"
|
||||
onclick={() => importToGuestList(r)}
|
||||
title={$_('events.public_rsvp_list.action_import_title')}
|
||||
>
|
||||
→
|
||||
</button>
|
||||
</li>
|
||||
|
|
@ -105,11 +114,15 @@
|
|||
|
||||
{#if lastFetchedAt}
|
||||
<div class="meta">
|
||||
Aktualisiert um {formatTime(lastFetchedAt, {
|
||||
{$_('events.public_rsvp_list.meta_updated', {
|
||||
values: {
|
||||
time: formatTime(lastFetchedAt, {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
})} · Auto-Refresh alle 30s
|
||||
}),
|
||||
},
|
||||
})}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
<script lang="ts">
|
||||
import { _ } from 'svelte-i18n';
|
||||
import type { DiscoveryRegion } from '../discovery/types';
|
||||
import { discoveryStore } from '../discovery/store.svelte';
|
||||
|
||||
|
|
@ -70,12 +71,16 @@
|
|||
{#each regions as region (region.id)}
|
||||
<div class="region-chip">
|
||||
<span class="region-label">{region.label}</span>
|
||||
<span class="region-radius">{region.radiusKm} km</span>
|
||||
<span class="region-radius"
|
||||
>{$_('events.region_picker.radius_unit', { values: { km: region.radiusKm } })}</span
|
||||
>
|
||||
<button class="remove-btn" onclick={() => removeRegion(region.id)}>x</button>
|
||||
</div>
|
||||
{/each}
|
||||
{#if !showForm}
|
||||
<button class="add-btn" onclick={() => (showForm = true)}>+ Region</button>
|
||||
<button class="add-btn" onclick={() => (showForm = true)}
|
||||
>{$_('events.region_picker.add_region')}</button
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
|
|
@ -85,10 +90,12 @@
|
|||
class="input"
|
||||
bind:value={searchQuery}
|
||||
oninput={onSearchInput}
|
||||
placeholder="Stadt oder Region suchen..."
|
||||
placeholder={$_('events.region_picker.placeholder_search')}
|
||||
/>
|
||||
<div class="radius-row">
|
||||
<label class="radius-label" for="region-radius">Radius: {radiusKm} km</label>
|
||||
<label class="radius-label" for="region-radius"
|
||||
>{$_('events.region_picker.radius_label', { values: { km: radiusKm } })}</label
|
||||
>
|
||||
<input id="region-radius" type="range" min="5" max="100" step="5" bind:value={radiusKm} />
|
||||
</div>
|
||||
{#if suggestions.length > 0}
|
||||
|
|
@ -103,7 +110,7 @@
|
|||
</ul>
|
||||
{/if}
|
||||
{#if searching}
|
||||
<p class="searching-hint">Suche...</p>
|
||||
<p class="searching-hint">{$_('events.region_picker.searching')}</p>
|
||||
{/if}
|
||||
<button
|
||||
class="cancel-btn"
|
||||
|
|
@ -113,7 +120,7 @@
|
|||
suggestions = [];
|
||||
}}
|
||||
>
|
||||
Abbrechen
|
||||
{$_('events.region_picker.action_cancel')}
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
<script lang="ts">
|
||||
import { _ } from 'svelte-i18n';
|
||||
import type { RsvpSummary } from '../types';
|
||||
|
||||
interface Props {
|
||||
|
|
@ -12,26 +13,26 @@
|
|||
<div class="rsvp-summary">
|
||||
<div class="badge yes">
|
||||
<span class="count">{summary.yes}</span>
|
||||
<span class="label">Ja</span>
|
||||
<span class="label">{$_('events.rsvp_summary.label_yes')}</span>
|
||||
</div>
|
||||
<div class="badge maybe">
|
||||
<span class="count">{summary.maybe}</span>
|
||||
<span class="label">Vielleicht</span>
|
||||
<span class="label">{$_('events.rsvp_summary.label_maybe')}</span>
|
||||
</div>
|
||||
<div class="badge no">
|
||||
<span class="count">{summary.no}</span>
|
||||
<span class="label">Nein</span>
|
||||
<span class="label">{$_('events.rsvp_summary.label_no')}</span>
|
||||
</div>
|
||||
<div class="badge pending">
|
||||
<span class="count">{summary.pending}</span>
|
||||
<span class="label">Offen</span>
|
||||
<span class="label">{$_('events.rsvp_summary.label_pending')}</span>
|
||||
</div>
|
||||
<div class="total">
|
||||
<strong>{summary.totalAttending}</strong>
|
||||
{#if capacity}
|
||||
<span class="muted">/ {capacity}</span>
|
||||
{/if}
|
||||
<span class="muted">kommen</span>
|
||||
<span class="muted">{$_('events.rsvp_summary.attending')}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
<script lang="ts">
|
||||
import { _, locale } from 'svelte-i18n';
|
||||
import { get } from 'svelte/store';
|
||||
import type { DiscoverySource, DiscoveryRegion } from '../discovery/types';
|
||||
import { discoveryStore } from '../discovery/store.svelte';
|
||||
|
||||
|
|
@ -61,8 +63,8 @@
|
|||
}
|
||||
|
||||
function formatDate(iso: string | null): string {
|
||||
if (!iso) return 'nie';
|
||||
return new Date(iso).toLocaleString('de-DE', {
|
||||
if (!iso) return $_('events.source_manager.never_scanned');
|
||||
return new Date(iso).toLocaleString(get(locale) ?? 'de', {
|
||||
day: '2-digit',
|
||||
month: '2-digit',
|
||||
hour: '2-digit',
|
||||
|
|
@ -73,17 +75,21 @@
|
|||
|
||||
<div class="source-manager">
|
||||
<div class="header">
|
||||
<h3 class="section-title">Quellen</h3>
|
||||
<h3 class="section-title">{$_('events.source_manager.section_title')}</h3>
|
||||
<div class="header-actions">
|
||||
<button
|
||||
class="discover-btn"
|
||||
onclick={handleDiscover}
|
||||
disabled={discovering || regions.length === 0}
|
||||
>
|
||||
{discovering ? 'Suche...' : 'Automatisch finden'}
|
||||
{discovering
|
||||
? $_('events.source_manager.action_discovering')
|
||||
: $_('events.source_manager.action_discover')}
|
||||
</button>
|
||||
{#if !showForm}
|
||||
<button class="add-btn" onclick={() => (showForm = true)}>+ iCal-Feed</button>
|
||||
<button class="add-btn" onclick={() => (showForm = true)}
|
||||
>{$_('events.source_manager.action_add_ical')}</button
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -93,10 +99,16 @@
|
|||
<input
|
||||
class="input"
|
||||
bind:value={newName}
|
||||
placeholder="Name (z.B. Jazzhaus Freiburg)"
|
||||
placeholder={$_('events.source_manager.placeholder_name')}
|
||||
required
|
||||
/>
|
||||
<input
|
||||
class="input"
|
||||
bind:value={newUrl}
|
||||
placeholder={$_('events.source_manager.placeholder_url')}
|
||||
type="url"
|
||||
required
|
||||
/>
|
||||
<input class="input" bind:value={newUrl} placeholder="iCal URL (.ics)" type="url" required />
|
||||
{#if regions.length > 1}
|
||||
<select class="input" bind:value={newRegionId}>
|
||||
{#each regions as r (r.id)}
|
||||
|
|
@ -105,9 +117,11 @@
|
|||
</select>
|
||||
{/if}
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="action-btn primary">Hinzufugen</button>
|
||||
<button type="submit" class="action-btn primary"
|
||||
>{$_('events.source_manager.action_submit_add')}</button
|
||||
>
|
||||
<button type="button" class="action-btn" onclick={() => (showForm = false)}
|
||||
>Abbrechen</button
|
||||
>{$_('events.source_manager.action_cancel')}</button
|
||||
>
|
||||
</div>
|
||||
</form>
|
||||
|
|
@ -115,7 +129,7 @@
|
|||
|
||||
{#if suggestedSources.length > 0}
|
||||
<div class="suggestions-section">
|
||||
<h4 class="sub-title">Vorgeschlagene Quellen</h4>
|
||||
<h4 class="sub-title">{$_('events.source_manager.section_suggested')}</h4>
|
||||
<div class="source-list">
|
||||
{#each suggestedSources as source (source.id)}
|
||||
<div class="source-item suggested">
|
||||
|
|
@ -132,9 +146,11 @@
|
|||
</div>
|
||||
<div class="source-actions">
|
||||
<button class="icon-btn activate" onclick={() => handleActivate(source.id)}
|
||||
>Aktivieren</button
|
||||
>{$_('events.source_manager.action_activate')}</button
|
||||
>
|
||||
<button class="icon-btn danger" onclick={() => handleReject(source.id)}
|
||||
>{$_('events.source_manager.action_reject')}</button
|
||||
>
|
||||
<button class="icon-btn danger" onclick={() => handleReject(source.id)}>x</button>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
|
|
@ -143,9 +159,7 @@
|
|||
{/if}
|
||||
|
||||
{#if activeSources.length === 0 && suggestedSources.length === 0}
|
||||
<p class="empty">
|
||||
Noch keine Quellen. Nutze "Automatisch finden" oder fuge iCal-Feeds manuell hinzu.
|
||||
</p>
|
||||
<p class="empty">{$_('events.source_manager.empty')}</p>
|
||||
{:else if activeSources.length > 0}
|
||||
<div class="source-list">
|
||||
{#each activeSources as source (source.id)}
|
||||
|
|
@ -153,9 +167,15 @@
|
|||
<div class="source-info">
|
||||
<div class="source-name">{source.name}</div>
|
||||
<div class="source-meta">
|
||||
{source.type.toUpperCase()} · Letzter Scan: {formatDate(source.lastCrawledAt)}
|
||||
{source.type.toUpperCase()} · {$_('events.source_manager.meta_last_scan', {
|
||||
values: { date: formatDate(source.lastCrawledAt) },
|
||||
})}
|
||||
{#if source.errorCount > 0}
|
||||
<span class="error-badge">{source.errorCount} Fehler</span>
|
||||
<span class="error-badge"
|
||||
>{$_('events.source_manager.errors_count', {
|
||||
values: { count: source.errorCount },
|
||||
})}</span
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
{#if source.lastError}
|
||||
|
|
@ -163,13 +183,17 @@
|
|||
{/if}
|
||||
</div>
|
||||
<div class="source-actions">
|
||||
<button class="icon-btn" onclick={() => handleCrawl(source.id)} title="Jetzt scannen">
|
||||
Scannen
|
||||
<button
|
||||
class="icon-btn"
|
||||
onclick={() => handleCrawl(source.id)}
|
||||
title={$_('events.source_manager.action_scan_title')}
|
||||
>
|
||||
{$_('events.source_manager.action_scan')}
|
||||
</button>
|
||||
<button
|
||||
class="icon-btn danger"
|
||||
onclick={() => handleRemove(source.id)}
|
||||
title="Entfernen"
|
||||
title={$_('events.source_manager.action_remove_title')}
|
||||
>
|
||||
x
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
<script lang="ts">
|
||||
import { formatDateTime } from '$lib/i18n/format';
|
||||
import { _ } from 'svelte-i18n';
|
||||
import { useEvent, useEventGuests, summarizeRsvps } from '../queries';
|
||||
import { eventsStore } from '../stores/events.svelte';
|
||||
import GuestListEditor from '../components/GuestListEditor.svelte';
|
||||
|
|
@ -136,7 +137,8 @@
|
|||
|
||||
async function handleDelete() {
|
||||
if (!event) return;
|
||||
if (!confirm(`Event "${event.title}" wirklich löschen?`)) return;
|
||||
if (!confirm($_('events.detail_view.confirm_delete', { values: { title: event.title } })))
|
||||
return;
|
||||
await eventsStore.deleteEvent(event.id);
|
||||
goBack();
|
||||
}
|
||||
|
|
@ -154,21 +156,33 @@
|
|||
</script>
|
||||
|
||||
{#if !event}
|
||||
<div class="loading">Lade Event...</div>
|
||||
<div class="loading">{$_('events.detail_view.loading')}</div>
|
||||
{:else}
|
||||
<div class="detail">
|
||||
<header class="detail-header">
|
||||
<button class="back-btn" onclick={goBack}>← Zurück</button>
|
||||
<button class="back-btn" onclick={goBack}>{$_('events.detail_view.back')}</button>
|
||||
<div class="header-actions">
|
||||
<button class="action-btn" onclick={startEdit}>Bearbeiten</button>
|
||||
<button class="action-btn danger" onclick={handleDelete}>Löschen</button>
|
||||
<button class="action-btn" onclick={startEdit}>
|
||||
{$_('events.detail_view.action_edit')}
|
||||
</button>
|
||||
<button class="action-btn danger" onclick={handleDelete}>
|
||||
{$_('events.detail_view.action_delete')}
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{#if editing}
|
||||
<div class="edit-form">
|
||||
<input class="title-input" bind:value={titleDraft} placeholder="Event-Titel" />
|
||||
<textarea class="desc-input" bind:value={descDraft} rows="3" placeholder="Beschreibung"
|
||||
<input
|
||||
class="title-input"
|
||||
bind:value={titleDraft}
|
||||
placeholder={$_('events.detail_view.placeholder_title')}
|
||||
/>
|
||||
<textarea
|
||||
class="desc-input"
|
||||
bind:value={descDraft}
|
||||
rows="3"
|
||||
placeholder={$_('events.detail_view.placeholder_description')}
|
||||
></textarea>
|
||||
<div class="loc-wrapper">
|
||||
<input
|
||||
|
|
@ -179,10 +193,10 @@
|
|||
onfocus={() => {
|
||||
if (addressSuggestions.length > 0) showAddressSuggestions = true;
|
||||
}}
|
||||
placeholder="Ort — tippe eine Adresse..."
|
||||
placeholder={$_('events.detail_view.placeholder_location')}
|
||||
/>
|
||||
{#if locationLatDraft && locationLonDraft}
|
||||
<span class="loc-pinned" title="Koordinaten gesetzt">
|
||||
<span class="loc-pinned" title={$_('events.detail_view.location_pinned_title')}>
|
||||
<MapPin size={12} weight="fill" />
|
||||
</span>
|
||||
{/if}
|
||||
|
|
@ -206,21 +220,25 @@
|
|||
</div>
|
||||
<div class="time-row">
|
||||
<label>
|
||||
<span>Start</span>
|
||||
<span>{$_('events.detail_view.label_start')}</span>
|
||||
<input type="datetime-local" bind:value={startDraft} />
|
||||
</label>
|
||||
<label>
|
||||
<span>Ende</span>
|
||||
<span>{$_('events.detail_view.label_end')}</span>
|
||||
<input type="datetime-local" bind:value={endDraft} />
|
||||
</label>
|
||||
<label class="all-day">
|
||||
<input type="checkbox" bind:checked={allDayDraft} />
|
||||
<span>Ganztägig</span>
|
||||
<span>{$_('events.detail_view.label_all_day')}</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<button class="action-btn" onclick={() => (editing = false)}>Abbrechen</button>
|
||||
<button class="action-btn primary" onclick={saveEdit}>Speichern</button>
|
||||
<button class="action-btn" onclick={() => (editing = false)}>
|
||||
{$_('events.detail_view.action_cancel')}
|
||||
</button>
|
||||
<button class="action-btn primary" onclick={saveEdit}>
|
||||
{$_('events.detail_view.action_save')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
|
|
@ -247,7 +265,7 @@
|
|||
{#if mapUrl}
|
||||
<div class="event-map">
|
||||
<iframe
|
||||
title="Event-Ort auf Karte"
|
||||
title={$_('events.detail_view.map_iframe_title')}
|
||||
src={mapUrl}
|
||||
width="100%"
|
||||
height="180"
|
||||
|
|
@ -260,7 +278,7 @@
|
|||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
In OpenStreetMap öffnen →
|
||||
{$_('events.detail_view.map_open')}
|
||||
</a>
|
||||
</div>
|
||||
{/if}
|
||||
|
|
@ -269,7 +287,7 @@
|
|||
|
||||
<section class="section">
|
||||
<div class="visibility-row">
|
||||
<span class="visibility-label">Sichtbarkeit</span>
|
||||
<span class="visibility-label">{$_('events.detail_view.label_visibility')}</span>
|
||||
<VisibilityPicker
|
||||
level={event.visibility ?? 'space'}
|
||||
onChange={handleVisibilityChange}
|
||||
|
|
@ -279,17 +297,17 @@
|
|||
</section>
|
||||
|
||||
<section class="section">
|
||||
<h2>RSVPs</h2>
|
||||
<h2>{$_('events.detail_view.section_rsvps')}</h2>
|
||||
<RsvpSummaryView {summary} capacity={event.capacity} />
|
||||
</section>
|
||||
|
||||
<section class="section">
|
||||
<h2>Gäste</h2>
|
||||
<h2>{$_('events.detail_view.section_guests')}</h2>
|
||||
<GuestListEditor eventId={event.id} />
|
||||
</section>
|
||||
|
||||
<section class="section">
|
||||
<h2>Bring-Liste</h2>
|
||||
<h2>{$_('events.detail_view.section_bring_list')}</h2>
|
||||
<BringListEditor eventId={event.id} />
|
||||
</section>
|
||||
|
||||
|
|
@ -300,19 +318,21 @@
|
|||
{/if}
|
||||
|
||||
<section class="section">
|
||||
<h2>Teilen</h2>
|
||||
<h2>{$_('events.detail_view.section_share')}</h2>
|
||||
{#if event.isPublished && event.publicToken}
|
||||
<div class="share-row">
|
||||
<code class="share-link">{window.location.origin}/rsvp/{event.publicToken}</code>
|
||||
<button class="action-btn" onclick={copyShareLink}>Kopieren</button>
|
||||
<button class="action-btn" onclick={handlePublish}>Privat machen</button>
|
||||
<button class="action-btn" onclick={copyShareLink}>
|
||||
{$_('events.detail_view.action_copy_link')}
|
||||
</button>
|
||||
<button class="action-btn" onclick={handlePublish}>
|
||||
{$_('events.detail_view.action_make_private')}
|
||||
</button>
|
||||
</div>
|
||||
<p class="share-hint">
|
||||
Antworten erscheinen automatisch unten in „Antworten via Link“ (Polling alle 30s).
|
||||
</p>
|
||||
<p class="share-hint">{$_('events.detail_view.share_hint')}</p>
|
||||
{:else}
|
||||
<button class="action-btn primary" onclick={handlePublish}>
|
||||
Event veröffentlichen & Link generieren
|
||||
{$_('events.detail_view.action_publish')}
|
||||
</button>
|
||||
{/if}
|
||||
</section>
|
||||
|
|
|
|||
|
|
@ -97,6 +97,10 @@
|
|||
"apps/mana/apps/web/src/lib/modules/comic/views/DetailCharacterView.svelte": 6,
|
||||
"apps/mana/apps/web/src/lib/modules/comic/views/DetailView.svelte": 7,
|
||||
"apps/mana/apps/web/src/lib/modules/comic/views/ListView.svelte": 2,
|
||||
"apps/mana/apps/web/src/lib/modules/community/components/ItemCard.svelte": 1,
|
||||
"apps/mana/apps/web/src/lib/modules/community/views/DetailView.svelte": 1,
|
||||
"apps/mana/apps/web/src/lib/modules/community/views/ListView.svelte": 1,
|
||||
"apps/mana/apps/web/src/lib/modules/community/views/RoadmapView.svelte": 1,
|
||||
"apps/mana/apps/web/src/lib/modules/companion/components/CompanionChat.svelte": 1,
|
||||
"apps/mana/apps/web/src/lib/modules/companion/components/RitualRunner.svelte": 5,
|
||||
"apps/mana/apps/web/src/lib/modules/companion/ListView.svelte": 1,
|
||||
|
|
@ -117,18 +121,6 @@
|
|||
"apps/mana/apps/web/src/lib/modules/dreams/ListView.svelte": 12,
|
||||
"apps/mana/apps/web/src/lib/modules/dreams/views/SymbolDetailView.svelte": 8,
|
||||
"apps/mana/apps/web/src/lib/modules/drink/ListView.svelte": 5,
|
||||
"apps/mana/apps/web/src/lib/modules/events/components/BringListEditor.svelte": 2,
|
||||
"apps/mana/apps/web/src/lib/modules/events/components/DiscoveredEventCard.svelte": 3,
|
||||
"apps/mana/apps/web/src/lib/modules/events/components/DiscoverySetup.svelte": 2,
|
||||
"apps/mana/apps/web/src/lib/modules/events/components/DiscoveryTab.svelte": 3,
|
||||
"apps/mana/apps/web/src/lib/modules/events/components/EventCard.svelte": 3,
|
||||
"apps/mana/apps/web/src/lib/modules/events/components/GuestListEditor.svelte": 2,
|
||||
"apps/mana/apps/web/src/lib/modules/events/components/PublicRsvpList.svelte": 2,
|
||||
"apps/mana/apps/web/src/lib/modules/events/components/RegionPicker.svelte": 1,
|
||||
"apps/mana/apps/web/src/lib/modules/events/components/RsvpSummary.svelte": 3,
|
||||
"apps/mana/apps/web/src/lib/modules/events/components/SourceManager.svelte": 5,
|
||||
"apps/mana/apps/web/src/lib/modules/events/ListView.svelte": 3,
|
||||
"apps/mana/apps/web/src/lib/modules/events/views/DetailView.svelte": 13,
|
||||
"apps/mana/apps/web/src/lib/modules/finance/ListView.svelte": 6,
|
||||
"apps/mana/apps/web/src/lib/modules/firsts/ListView.svelte": 15,
|
||||
"apps/mana/apps/web/src/lib/modules/goals/GoalEditor.svelte": 15,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue