mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 20:01:09 +02:00
feat(calendar): full i18n coverage across 17 files — DE/EN/ES/FR/IT
Calendar already had a `calendar` namespace, but ~70 strings were hardcoded across EventForm, EventDetailModal, CustomRecurrenceBuilder, CalendarHeader (15 block-type filter chips), QuickEventPopover, AgendaView, EventCard, SlotSuggestions, MiniCalendar, DateStrip, ListView, SharedEventView, the inline DetailView, and 3 routes. - Extended namespace with `event_form.*`, `event_card.*`, `event_modal.*`, `agenda.*`, `recurrence.*` (custom builder + preview format), `weekday_short.*` / `weekday_long.*`, `header.*` (15 block-type labels + 4 ARIA), `date_strip.*`, `mini_cal.*`, `slots.*`, `quick_event.*`, `list_view.*`, `detail_route.*`, `detail_view.*`, `calendars_route.*`, `shared_view.*` — ~172 new keys × 5 locales = ~860 translations. - Recurrence preview formatters in EventForm + EventDetailModal + CustomRecurrenceBuilder all rebuilt around `recurrence.every_n_unit` / `weekly_with_days` / weekday-short maps. - Locale-aware Intl.DateTimeFormat in SharedEventView (was hardcoded 'de-DE'). - Baseline ratchet: 1753 → 1687 (66 calendar strings cleared, 16 files fully clean). - validate:i18n-parity: 40 namespaces × 5 locales — 3768 keys aligned - svelte-check: 0 new errors from i18n changes (pre-existing drift in unrelated modules unchanged) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
723a64808c
commit
4e31c8d736
22 changed files with 1429 additions and 277 deletions
|
|
@ -29,8 +29,10 @@
|
|||
},
|
||||
"calendar": {
|
||||
"today": "Heute",
|
||||
"tomorrow": "Morgen",
|
||||
"newEvent": "Neuer Termin",
|
||||
"noEvents": "Keine Termine",
|
||||
"noEventsInRange": "Keine Termine in diesem Zeitraum",
|
||||
"allDay": "Ganztägig",
|
||||
"myCalendars": "Meine Kalender",
|
||||
"sharedCalendars": "Geteilte Kalender",
|
||||
|
|
@ -58,6 +60,210 @@
|
|||
"changeStartTime": "Startzeit ändern",
|
||||
"changeEndTime": "Endzeit ändern"
|
||||
},
|
||||
"event_form": {
|
||||
"aria_create": "Termin erstellen",
|
||||
"aria_edit": "Termin bearbeiten",
|
||||
"label_title_required": "Titel *",
|
||||
"placeholder_title": "Terminname eingeben",
|
||||
"label_time": "Uhrzeit",
|
||||
"label_recurrence": "Wiederholung",
|
||||
"placeholder_location": "Ort eingeben...",
|
||||
"placeholder_description": "Beschreibung hinzufügen",
|
||||
"label_tags": "Tags",
|
||||
"recur_edit_suffix": "{preview} — Bearbeiten"
|
||||
},
|
||||
"event_card": {
|
||||
"new_event_label": "Neuer Termin",
|
||||
"recurring_title": "Wiederkehrend",
|
||||
"linked_title": "Durchgeführt"
|
||||
},
|
||||
"event_modal": {
|
||||
"editing_title": "Termin bearbeiten",
|
||||
"copy_title": "Kopieren",
|
||||
"label_visibility": "Sichtbarkeit",
|
||||
"label_share_link": "Link",
|
||||
"created_at": "Erstellt: {date}",
|
||||
"updated_at": "· Bearbeitet: {date}",
|
||||
"confirm_delete_single": "Diesen Termin löschen?",
|
||||
"clipboard_location_prefix": "Ort: {location}",
|
||||
"recur_edit_title": "Wiederkehrenden Termin bearbeiten",
|
||||
"recur_edit_text": "Möchtest du nur diesen Termin oder alle zukünftigen bearbeiten?",
|
||||
"recur_edit_only_this": "Nur diesen Termin",
|
||||
"recur_edit_all_future": "Alle zukünftigen Termine",
|
||||
"recur_delete_title": "Wiederkehrenden Termin löschen",
|
||||
"recur_delete_text": "Möchtest du nur diesen Termin oder die gesamte Serie löschen?",
|
||||
"recur_delete_only_this": "Nur diesen Termin",
|
||||
"recur_delete_all": "Alle Termine der Serie"
|
||||
},
|
||||
"agenda": {
|
||||
"empty": "Keine Termine in diesem Zeitraum",
|
||||
"open_details_aria": "Details öffnen"
|
||||
},
|
||||
"recurrence": {
|
||||
"none": "Keine Wiederholung",
|
||||
"daily": "Täglich",
|
||||
"weekly": "Wöchentlich",
|
||||
"monthly": "Monatlich",
|
||||
"yearly": "Jährlich",
|
||||
"every_2_weeks": "Alle 2 Wochen",
|
||||
"custom": "Benutzerdefiniert...",
|
||||
"recurring_fallback": "Wiederkehrend",
|
||||
"weekly_with_days": "Wöchentlich ({days})",
|
||||
"every_n_unit": "Alle {n} {unit}",
|
||||
"every_unit": "Alle {unit}",
|
||||
"unit_days": "Tage",
|
||||
"unit_weeks": "Wochen",
|
||||
"unit_months": "Monate",
|
||||
"unit_years": "Jahre",
|
||||
"unit_freq_daily": "Tag(e)",
|
||||
"unit_freq_weekly": "Woche(n)",
|
||||
"unit_freq_monthly": "Monat(e)",
|
||||
"unit_freq_yearly": "Jahr(e)",
|
||||
"preview_at_days": " an {days}",
|
||||
"preview_count_suffix": ", {count}x",
|
||||
"preview_until_suffix": " bis {date}",
|
||||
"builder_title": "Benutzerdefinierte Wiederholung",
|
||||
"builder_every_label": "Alle",
|
||||
"builder_weekdays_label": "Wochentage",
|
||||
"builder_end_label": "Endet",
|
||||
"builder_end_never": "Nie",
|
||||
"builder_end_after": "Nach",
|
||||
"builder_end_after_unit": "Terminen",
|
||||
"builder_end_until": "Am",
|
||||
"builder_apply": "Übernehmen"
|
||||
},
|
||||
"weekday_short": {
|
||||
"mon": "Mo",
|
||||
"tue": "Di",
|
||||
"wed": "Mi",
|
||||
"thu": "Do",
|
||||
"fri": "Fr",
|
||||
"sat": "Sa",
|
||||
"sun": "So"
|
||||
},
|
||||
"weekday_long": {
|
||||
"sun": "Sonntag",
|
||||
"mon": "Montag",
|
||||
"tue": "Dienstag",
|
||||
"wed": "Mittwoch",
|
||||
"thu": "Donnerstag",
|
||||
"fri": "Freitag",
|
||||
"sat": "Samstag"
|
||||
},
|
||||
"header": {
|
||||
"today": "Heute",
|
||||
"new_event": "Termin",
|
||||
"aria_prev": "Zurück",
|
||||
"aria_next": "Weiter",
|
||||
"aria_filter": "Filter",
|
||||
"aria_export": "Exportieren",
|
||||
"block_event": "Termine",
|
||||
"block_task": "Aufgaben",
|
||||
"block_time_entry": "Zeiten",
|
||||
"block_habit": "Habits",
|
||||
"block_body": "Training",
|
||||
"block_watering": "Gießen",
|
||||
"block_sleep": "Schlaf",
|
||||
"block_practice": "Übung",
|
||||
"block_period": "Periode",
|
||||
"block_guide": "Guides",
|
||||
"block_visit": "Besuche",
|
||||
"block_study": "Lernen",
|
||||
"block_listening": "Musik",
|
||||
"block_mood": "Stimmung",
|
||||
"block_rehearsal": "Probe"
|
||||
},
|
||||
"date_strip": {
|
||||
"today_aria": "Zum heutigen Tag",
|
||||
"today_label": "Heute"
|
||||
},
|
||||
"mini_cal": {
|
||||
"prev_aria": "Vorheriger Monat",
|
||||
"next_aria": "Nächster Monat"
|
||||
},
|
||||
"slots": {
|
||||
"loading": "Suche freie Zeiten...",
|
||||
"empty": "Keine freien Slots gefunden",
|
||||
"label": "Freie Zeiten",
|
||||
"duration_suffix": "{n}min"
|
||||
},
|
||||
"quick_event": {
|
||||
"aria_dialog": "Termin erstellen",
|
||||
"header_new": "Neuer Termin",
|
||||
"placeholder_title": "Titel hinzufügen",
|
||||
"type_event": "Termin",
|
||||
"type_time_entry": "Zeiterfassung",
|
||||
"type_habit": "Habit",
|
||||
"placeholder_location": "Ort hinzufügen",
|
||||
"placeholder_description": "Beschreibung"
|
||||
},
|
||||
"list_view": {
|
||||
"placeholder_new_event": "Neuer Termin...",
|
||||
"voice_reason": "Termine werden verschlüsselt gespeichert. Dafür brauchst du ein Mana-Konto.",
|
||||
"empty_label": "Termine",
|
||||
"empty_text": "Keine Termine",
|
||||
"open_action": "Öffnen",
|
||||
"delete_action": "Löschen"
|
||||
},
|
||||
"detail_route": {
|
||||
"doc_title": "Kalender - Mana",
|
||||
"create_modal_title": "Neuer Termin",
|
||||
"event_doc_title": "{title} - Kalender - Mana",
|
||||
"event_doc_title_fallback": "Termin",
|
||||
"back_to_calendar": "Zurück zum Kalender",
|
||||
"not_found": "Termin nicht gefunden",
|
||||
"edit_title": "Termin bearbeiten",
|
||||
"label_title": "Titel",
|
||||
"label_description": "Beschreibung",
|
||||
"label_date": "Datum",
|
||||
"label_from": "Von",
|
||||
"label_to": "Bis",
|
||||
"label_location": "Ort",
|
||||
"placeholder_location": "Ort eingeben...",
|
||||
"submit_save": "Speichern",
|
||||
"submit_cancel": "Abbrechen",
|
||||
"time_suffix_uhr": "Uhr"
|
||||
},
|
||||
"detail_view": {
|
||||
"toast_deleted": "Termin gelöscht",
|
||||
"not_found": "Termin nicht gefunden",
|
||||
"confirm_delete": "Termin wirklich löschen?",
|
||||
"placeholder_title": "Titel...",
|
||||
"label_visibility": "Sichtbarkeit",
|
||||
"label_allday": "Ganztägig",
|
||||
"placeholder_location": "Ort hinzufügen...",
|
||||
"section_tags": "Tags",
|
||||
"section_description": "Beschreibung",
|
||||
"placeholder_description": "Beschreibung hinzufügen...",
|
||||
"meta_created": "Erstellt: {date}",
|
||||
"meta_updated": "Bearbeitet: {date}",
|
||||
"untitled_fallback": "Untitled"
|
||||
},
|
||||
"calendars_route": {
|
||||
"doc_title": "Kalender verwalten - Mana",
|
||||
"back_to_calendar": "Zurück zum Kalender",
|
||||
"page_title": "Meine Kalender",
|
||||
"new_calendar": "Neuer Kalender",
|
||||
"label_name": "Name",
|
||||
"placeholder_name": "z.B. Arbeit, Sport, Familie...",
|
||||
"label_color": "Farbe",
|
||||
"aria_pick_color": "Farbe wählen",
|
||||
"submit_create": "Erstellen",
|
||||
"empty": "Noch keine Kalender vorhanden.",
|
||||
"badge_default": "(Standard)",
|
||||
"aria_hide": "Ausblenden",
|
||||
"aria_show": "Einblenden",
|
||||
"aria_set_default": "Als Standard setzen",
|
||||
"confirm_delete": "Kalender wirklich löschen? Alle zugehörigen Termine gehen verloren."
|
||||
},
|
||||
"shared_view": {
|
||||
"kind": "Termin",
|
||||
"label_when": "Wann",
|
||||
"label_where": "Wo",
|
||||
"timezone_prefix": "Zeitzone: {tz}",
|
||||
"add_to_calendar": "📅 Zum eigenen Kalender hinzufügen",
|
||||
"expiry": "Dieser Link läuft am {date} ab."
|
||||
},
|
||||
"repeat": {
|
||||
"none": "Nicht wiederholen",
|
||||
"daily": "Täglich",
|
||||
|
|
|
|||
|
|
@ -29,8 +29,10 @@
|
|||
},
|
||||
"calendar": {
|
||||
"today": "Today",
|
||||
"tomorrow": "Tomorrow",
|
||||
"newEvent": "New Event",
|
||||
"noEvents": "No events",
|
||||
"noEventsInRange": "No events in this range",
|
||||
"allDay": "All day",
|
||||
"myCalendars": "My Calendars",
|
||||
"sharedCalendars": "Shared Calendars",
|
||||
|
|
@ -58,6 +60,210 @@
|
|||
"changeStartTime": "Change start time",
|
||||
"changeEndTime": "Change end time"
|
||||
},
|
||||
"event_form": {
|
||||
"aria_create": "Create event",
|
||||
"aria_edit": "Edit event",
|
||||
"label_title_required": "Title *",
|
||||
"placeholder_title": "Enter event name",
|
||||
"label_time": "Time",
|
||||
"label_recurrence": "Recurrence",
|
||||
"placeholder_location": "Enter location...",
|
||||
"placeholder_description": "Add a description",
|
||||
"label_tags": "Tags",
|
||||
"recur_edit_suffix": "{preview} — Edit"
|
||||
},
|
||||
"event_card": {
|
||||
"new_event_label": "New event",
|
||||
"recurring_title": "Recurring",
|
||||
"linked_title": "Done"
|
||||
},
|
||||
"event_modal": {
|
||||
"editing_title": "Edit event",
|
||||
"copy_title": "Copy",
|
||||
"label_visibility": "Visibility",
|
||||
"label_share_link": "Link",
|
||||
"created_at": "Created: {date}",
|
||||
"updated_at": "· Edited: {date}",
|
||||
"confirm_delete_single": "Delete this event?",
|
||||
"clipboard_location_prefix": "Location: {location}",
|
||||
"recur_edit_title": "Edit recurring event",
|
||||
"recur_edit_text": "Edit only this event or all future ones?",
|
||||
"recur_edit_only_this": "Only this event",
|
||||
"recur_edit_all_future": "All future events",
|
||||
"recur_delete_title": "Delete recurring event",
|
||||
"recur_delete_text": "Delete only this event or the entire series?",
|
||||
"recur_delete_only_this": "Only this event",
|
||||
"recur_delete_all": "All events in the series"
|
||||
},
|
||||
"agenda": {
|
||||
"empty": "No events in this range",
|
||||
"open_details_aria": "Open details"
|
||||
},
|
||||
"recurrence": {
|
||||
"none": "No recurrence",
|
||||
"daily": "Daily",
|
||||
"weekly": "Weekly",
|
||||
"monthly": "Monthly",
|
||||
"yearly": "Yearly",
|
||||
"every_2_weeks": "Every 2 weeks",
|
||||
"custom": "Custom...",
|
||||
"recurring_fallback": "Recurring",
|
||||
"weekly_with_days": "Weekly ({days})",
|
||||
"every_n_unit": "Every {n} {unit}",
|
||||
"every_unit": "Every {unit}",
|
||||
"unit_days": "days",
|
||||
"unit_weeks": "weeks",
|
||||
"unit_months": "months",
|
||||
"unit_years": "years",
|
||||
"unit_freq_daily": "day(s)",
|
||||
"unit_freq_weekly": "week(s)",
|
||||
"unit_freq_monthly": "month(s)",
|
||||
"unit_freq_yearly": "year(s)",
|
||||
"preview_at_days": " on {days}",
|
||||
"preview_count_suffix": ", {count}x",
|
||||
"preview_until_suffix": " until {date}",
|
||||
"builder_title": "Custom recurrence",
|
||||
"builder_every_label": "Every",
|
||||
"builder_weekdays_label": "Weekdays",
|
||||
"builder_end_label": "Ends",
|
||||
"builder_end_never": "Never",
|
||||
"builder_end_after": "After",
|
||||
"builder_end_after_unit": "events",
|
||||
"builder_end_until": "On",
|
||||
"builder_apply": "Apply"
|
||||
},
|
||||
"weekday_short": {
|
||||
"mon": "Mon",
|
||||
"tue": "Tue",
|
||||
"wed": "Wed",
|
||||
"thu": "Thu",
|
||||
"fri": "Fri",
|
||||
"sat": "Sat",
|
||||
"sun": "Sun"
|
||||
},
|
||||
"weekday_long": {
|
||||
"sun": "Sunday",
|
||||
"mon": "Monday",
|
||||
"tue": "Tuesday",
|
||||
"wed": "Wednesday",
|
||||
"thu": "Thursday",
|
||||
"fri": "Friday",
|
||||
"sat": "Saturday"
|
||||
},
|
||||
"header": {
|
||||
"today": "Today",
|
||||
"new_event": "Event",
|
||||
"aria_prev": "Previous",
|
||||
"aria_next": "Next",
|
||||
"aria_filter": "Filter",
|
||||
"aria_export": "Export",
|
||||
"block_event": "Events",
|
||||
"block_task": "Tasks",
|
||||
"block_time_entry": "Time",
|
||||
"block_habit": "Habits",
|
||||
"block_body": "Training",
|
||||
"block_watering": "Watering",
|
||||
"block_sleep": "Sleep",
|
||||
"block_practice": "Practice",
|
||||
"block_period": "Period",
|
||||
"block_guide": "Guides",
|
||||
"block_visit": "Visits",
|
||||
"block_study": "Study",
|
||||
"block_listening": "Music",
|
||||
"block_mood": "Mood",
|
||||
"block_rehearsal": "Rehearsal"
|
||||
},
|
||||
"date_strip": {
|
||||
"today_aria": "Go to today",
|
||||
"today_label": "Today"
|
||||
},
|
||||
"mini_cal": {
|
||||
"prev_aria": "Previous month",
|
||||
"next_aria": "Next month"
|
||||
},
|
||||
"slots": {
|
||||
"loading": "Searching free time...",
|
||||
"empty": "No free slots found",
|
||||
"label": "Free slots",
|
||||
"duration_suffix": "{n}min"
|
||||
},
|
||||
"quick_event": {
|
||||
"aria_dialog": "Create event",
|
||||
"header_new": "New event",
|
||||
"placeholder_title": "Add a title",
|
||||
"type_event": "Event",
|
||||
"type_time_entry": "Time entry",
|
||||
"type_habit": "Habit",
|
||||
"placeholder_location": "Add location",
|
||||
"placeholder_description": "Description"
|
||||
},
|
||||
"list_view": {
|
||||
"placeholder_new_event": "New event...",
|
||||
"voice_reason": "Events are stored encrypted. You need a Mana account for that.",
|
||||
"empty_label": "Events",
|
||||
"empty_text": "No events",
|
||||
"open_action": "Open",
|
||||
"delete_action": "Delete"
|
||||
},
|
||||
"detail_route": {
|
||||
"doc_title": "Calendar - Mana",
|
||||
"create_modal_title": "New event",
|
||||
"event_doc_title": "{title} - Calendar - Mana",
|
||||
"event_doc_title_fallback": "Event",
|
||||
"back_to_calendar": "Back to calendar",
|
||||
"not_found": "Event not found",
|
||||
"edit_title": "Edit event",
|
||||
"label_title": "Title",
|
||||
"label_description": "Description",
|
||||
"label_date": "Date",
|
||||
"label_from": "From",
|
||||
"label_to": "To",
|
||||
"label_location": "Location",
|
||||
"placeholder_location": "Enter location...",
|
||||
"submit_save": "Save",
|
||||
"submit_cancel": "Cancel",
|
||||
"time_suffix_uhr": ""
|
||||
},
|
||||
"detail_view": {
|
||||
"toast_deleted": "Event deleted",
|
||||
"not_found": "Event not found",
|
||||
"confirm_delete": "Really delete event?",
|
||||
"placeholder_title": "Title...",
|
||||
"label_visibility": "Visibility",
|
||||
"label_allday": "All day",
|
||||
"placeholder_location": "Add location...",
|
||||
"section_tags": "Tags",
|
||||
"section_description": "Description",
|
||||
"placeholder_description": "Add description...",
|
||||
"meta_created": "Created: {date}",
|
||||
"meta_updated": "Edited: {date}",
|
||||
"untitled_fallback": "Untitled"
|
||||
},
|
||||
"calendars_route": {
|
||||
"doc_title": "Manage calendars - Mana",
|
||||
"back_to_calendar": "Back to calendar",
|
||||
"page_title": "My calendars",
|
||||
"new_calendar": "New calendar",
|
||||
"label_name": "Name",
|
||||
"placeholder_name": "e.g. Work, Sport, Family...",
|
||||
"label_color": "Color",
|
||||
"aria_pick_color": "Pick color",
|
||||
"submit_create": "Create",
|
||||
"empty": "No calendars yet.",
|
||||
"badge_default": "(Default)",
|
||||
"aria_hide": "Hide",
|
||||
"aria_show": "Show",
|
||||
"aria_set_default": "Set as default",
|
||||
"confirm_delete": "Really delete calendar? All associated events will be lost."
|
||||
},
|
||||
"shared_view": {
|
||||
"kind": "Event",
|
||||
"label_when": "When",
|
||||
"label_where": "Where",
|
||||
"timezone_prefix": "Time zone: {tz}",
|
||||
"add_to_calendar": "📅 Add to my calendar",
|
||||
"expiry": "This link expires on {date}."
|
||||
},
|
||||
"repeat": {
|
||||
"none": "Don't repeat",
|
||||
"daily": "Daily",
|
||||
|
|
|
|||
|
|
@ -29,8 +29,10 @@
|
|||
},
|
||||
"calendar": {
|
||||
"today": "Hoy",
|
||||
"tomorrow": "Mañana",
|
||||
"newEvent": "Nuevo evento",
|
||||
"noEvents": "Sin eventos",
|
||||
"noEventsInRange": "Sin eventos en este rango",
|
||||
"allDay": "Todo el día",
|
||||
"myCalendars": "Mis calendarios",
|
||||
"sharedCalendars": "Calendarios compartidos",
|
||||
|
|
@ -58,6 +60,210 @@
|
|||
"changeStartTime": "Cambiar hora de inicio",
|
||||
"changeEndTime": "Cambiar hora de fin"
|
||||
},
|
||||
"event_form": {
|
||||
"aria_create": "Crear evento",
|
||||
"aria_edit": "Editar evento",
|
||||
"label_title_required": "Título *",
|
||||
"placeholder_title": "Introduce el nombre del evento",
|
||||
"label_time": "Hora",
|
||||
"label_recurrence": "Repetición",
|
||||
"placeholder_location": "Introduce la ubicación...",
|
||||
"placeholder_description": "Añade una descripción",
|
||||
"label_tags": "Etiquetas",
|
||||
"recur_edit_suffix": "{preview} — Editar"
|
||||
},
|
||||
"event_card": {
|
||||
"new_event_label": "Nuevo evento",
|
||||
"recurring_title": "Recurrente",
|
||||
"linked_title": "Realizado"
|
||||
},
|
||||
"event_modal": {
|
||||
"editing_title": "Editar evento",
|
||||
"copy_title": "Copiar",
|
||||
"label_visibility": "Visibilidad",
|
||||
"label_share_link": "Enlace",
|
||||
"created_at": "Creado: {date}",
|
||||
"updated_at": "· Editado: {date}",
|
||||
"confirm_delete_single": "¿Eliminar este evento?",
|
||||
"clipboard_location_prefix": "Ubicación: {location}",
|
||||
"recur_edit_title": "Editar evento recurrente",
|
||||
"recur_edit_text": "¿Editar solo este evento o todos los futuros?",
|
||||
"recur_edit_only_this": "Solo este evento",
|
||||
"recur_edit_all_future": "Todos los eventos futuros",
|
||||
"recur_delete_title": "Eliminar evento recurrente",
|
||||
"recur_delete_text": "¿Eliminar solo este evento o toda la serie?",
|
||||
"recur_delete_only_this": "Solo este evento",
|
||||
"recur_delete_all": "Todos los eventos de la serie"
|
||||
},
|
||||
"agenda": {
|
||||
"empty": "Sin eventos en este rango",
|
||||
"open_details_aria": "Abrir detalles"
|
||||
},
|
||||
"recurrence": {
|
||||
"none": "Sin repetición",
|
||||
"daily": "Diario",
|
||||
"weekly": "Semanal",
|
||||
"monthly": "Mensual",
|
||||
"yearly": "Anual",
|
||||
"every_2_weeks": "Cada 2 semanas",
|
||||
"custom": "Personalizado...",
|
||||
"recurring_fallback": "Recurrente",
|
||||
"weekly_with_days": "Semanal ({days})",
|
||||
"every_n_unit": "Cada {n} {unit}",
|
||||
"every_unit": "Cada {unit}",
|
||||
"unit_days": "días",
|
||||
"unit_weeks": "semanas",
|
||||
"unit_months": "meses",
|
||||
"unit_years": "años",
|
||||
"unit_freq_daily": "día(s)",
|
||||
"unit_freq_weekly": "semana(s)",
|
||||
"unit_freq_monthly": "mes(es)",
|
||||
"unit_freq_yearly": "año(s)",
|
||||
"preview_at_days": " los {days}",
|
||||
"preview_count_suffix": ", {count}x",
|
||||
"preview_until_suffix": " hasta {date}",
|
||||
"builder_title": "Repetición personalizada",
|
||||
"builder_every_label": "Cada",
|
||||
"builder_weekdays_label": "Días de la semana",
|
||||
"builder_end_label": "Termina",
|
||||
"builder_end_never": "Nunca",
|
||||
"builder_end_after": "Tras",
|
||||
"builder_end_after_unit": "eventos",
|
||||
"builder_end_until": "El",
|
||||
"builder_apply": "Aplicar"
|
||||
},
|
||||
"weekday_short": {
|
||||
"mon": "Lun",
|
||||
"tue": "Mar",
|
||||
"wed": "Mié",
|
||||
"thu": "Jue",
|
||||
"fri": "Vie",
|
||||
"sat": "Sáb",
|
||||
"sun": "Dom"
|
||||
},
|
||||
"weekday_long": {
|
||||
"sun": "Domingo",
|
||||
"mon": "Lunes",
|
||||
"tue": "Martes",
|
||||
"wed": "Miércoles",
|
||||
"thu": "Jueves",
|
||||
"fri": "Viernes",
|
||||
"sat": "Sábado"
|
||||
},
|
||||
"header": {
|
||||
"today": "Hoy",
|
||||
"new_event": "Evento",
|
||||
"aria_prev": "Anterior",
|
||||
"aria_next": "Siguiente",
|
||||
"aria_filter": "Filtro",
|
||||
"aria_export": "Exportar",
|
||||
"block_event": "Eventos",
|
||||
"block_task": "Tareas",
|
||||
"block_time_entry": "Tiempos",
|
||||
"block_habit": "Hábitos",
|
||||
"block_body": "Entrenamiento",
|
||||
"block_watering": "Riego",
|
||||
"block_sleep": "Sueño",
|
||||
"block_practice": "Práctica",
|
||||
"block_period": "Periodo",
|
||||
"block_guide": "Guías",
|
||||
"block_visit": "Visitas",
|
||||
"block_study": "Estudio",
|
||||
"block_listening": "Música",
|
||||
"block_mood": "Estado",
|
||||
"block_rehearsal": "Ensayo"
|
||||
},
|
||||
"date_strip": {
|
||||
"today_aria": "Ir a hoy",
|
||||
"today_label": "Hoy"
|
||||
},
|
||||
"mini_cal": {
|
||||
"prev_aria": "Mes anterior",
|
||||
"next_aria": "Mes siguiente"
|
||||
},
|
||||
"slots": {
|
||||
"loading": "Buscando huecos libres...",
|
||||
"empty": "No se han encontrado huecos libres",
|
||||
"label": "Huecos libres",
|
||||
"duration_suffix": "{n}min"
|
||||
},
|
||||
"quick_event": {
|
||||
"aria_dialog": "Crear evento",
|
||||
"header_new": "Nuevo evento",
|
||||
"placeholder_title": "Añadir un título",
|
||||
"type_event": "Evento",
|
||||
"type_time_entry": "Tiempo",
|
||||
"type_habit": "Hábito",
|
||||
"placeholder_location": "Añadir ubicación",
|
||||
"placeholder_description": "Descripción"
|
||||
},
|
||||
"list_view": {
|
||||
"placeholder_new_event": "Nuevo evento...",
|
||||
"voice_reason": "Los eventos se guardan cifrados. Necesitas una cuenta Mana para esto.",
|
||||
"empty_label": "Eventos",
|
||||
"empty_text": "Sin eventos",
|
||||
"open_action": "Abrir",
|
||||
"delete_action": "Eliminar"
|
||||
},
|
||||
"detail_route": {
|
||||
"doc_title": "Calendario - Mana",
|
||||
"create_modal_title": "Nuevo evento",
|
||||
"event_doc_title": "{title} - Calendario - Mana",
|
||||
"event_doc_title_fallback": "Evento",
|
||||
"back_to_calendar": "Volver al calendario",
|
||||
"not_found": "Evento no encontrado",
|
||||
"edit_title": "Editar evento",
|
||||
"label_title": "Título",
|
||||
"label_description": "Descripción",
|
||||
"label_date": "Fecha",
|
||||
"label_from": "Desde",
|
||||
"label_to": "Hasta",
|
||||
"label_location": "Ubicación",
|
||||
"placeholder_location": "Introduce la ubicación...",
|
||||
"submit_save": "Guardar",
|
||||
"submit_cancel": "Cancelar",
|
||||
"time_suffix_uhr": ""
|
||||
},
|
||||
"detail_view": {
|
||||
"toast_deleted": "Evento eliminado",
|
||||
"not_found": "Evento no encontrado",
|
||||
"confirm_delete": "¿Eliminar el evento de verdad?",
|
||||
"placeholder_title": "Título...",
|
||||
"label_visibility": "Visibilidad",
|
||||
"label_allday": "Todo el día",
|
||||
"placeholder_location": "Añadir ubicación...",
|
||||
"section_tags": "Etiquetas",
|
||||
"section_description": "Descripción",
|
||||
"placeholder_description": "Añadir descripción...",
|
||||
"meta_created": "Creado: {date}",
|
||||
"meta_updated": "Editado: {date}",
|
||||
"untitled_fallback": "Sin título"
|
||||
},
|
||||
"calendars_route": {
|
||||
"doc_title": "Gestionar calendarios - Mana",
|
||||
"back_to_calendar": "Volver al calendario",
|
||||
"page_title": "Mis calendarios",
|
||||
"new_calendar": "Nuevo calendario",
|
||||
"label_name": "Nombre",
|
||||
"placeholder_name": "p. ej. Trabajo, Deporte, Familia...",
|
||||
"label_color": "Color",
|
||||
"aria_pick_color": "Elegir color",
|
||||
"submit_create": "Crear",
|
||||
"empty": "Aún no hay calendarios.",
|
||||
"badge_default": "(Predeterminado)",
|
||||
"aria_hide": "Ocultar",
|
||||
"aria_show": "Mostrar",
|
||||
"aria_set_default": "Establecer como predeterminado",
|
||||
"confirm_delete": "¿Eliminar el calendario de verdad? Se perderán todos los eventos asociados."
|
||||
},
|
||||
"shared_view": {
|
||||
"kind": "Evento",
|
||||
"label_when": "Cuándo",
|
||||
"label_where": "Dónde",
|
||||
"timezone_prefix": "Zona horaria: {tz}",
|
||||
"add_to_calendar": "📅 Añadir a mi calendario",
|
||||
"expiry": "Este enlace caduca el {date}."
|
||||
},
|
||||
"repeat": {
|
||||
"none": "No repetir",
|
||||
"daily": "Diario",
|
||||
|
|
|
|||
|
|
@ -29,8 +29,10 @@
|
|||
},
|
||||
"calendar": {
|
||||
"today": "Aujourd'hui",
|
||||
"tomorrow": "Demain",
|
||||
"newEvent": "Nouvel événement",
|
||||
"noEvents": "Aucun événement",
|
||||
"noEventsInRange": "Aucun événement dans cette période",
|
||||
"allDay": "Toute la journée",
|
||||
"myCalendars": "Mes calendriers",
|
||||
"sharedCalendars": "Calendriers partagés",
|
||||
|
|
@ -58,6 +60,210 @@
|
|||
"changeStartTime": "Changer l'heure de début",
|
||||
"changeEndTime": "Changer l'heure de fin"
|
||||
},
|
||||
"event_form": {
|
||||
"aria_create": "Créer un événement",
|
||||
"aria_edit": "Modifier l'événement",
|
||||
"label_title_required": "Titre *",
|
||||
"placeholder_title": "Saisis le nom de l'événement",
|
||||
"label_time": "Heure",
|
||||
"label_recurrence": "Répétition",
|
||||
"placeholder_location": "Saisis le lieu...",
|
||||
"placeholder_description": "Ajoute une description",
|
||||
"label_tags": "Tags",
|
||||
"recur_edit_suffix": "{preview} — Modifier"
|
||||
},
|
||||
"event_card": {
|
||||
"new_event_label": "Nouvel événement",
|
||||
"recurring_title": "Récurrent",
|
||||
"linked_title": "Réalisé"
|
||||
},
|
||||
"event_modal": {
|
||||
"editing_title": "Modifier l'événement",
|
||||
"copy_title": "Copier",
|
||||
"label_visibility": "Visibilité",
|
||||
"label_share_link": "Lien",
|
||||
"created_at": "Créé : {date}",
|
||||
"updated_at": "· Modifié : {date}",
|
||||
"confirm_delete_single": "Supprimer cet événement ?",
|
||||
"clipboard_location_prefix": "Lieu : {location}",
|
||||
"recur_edit_title": "Modifier l'événement récurrent",
|
||||
"recur_edit_text": "Modifier seulement cet événement ou tous les futurs ?",
|
||||
"recur_edit_only_this": "Seulement cet événement",
|
||||
"recur_edit_all_future": "Tous les événements futurs",
|
||||
"recur_delete_title": "Supprimer l'événement récurrent",
|
||||
"recur_delete_text": "Supprimer seulement cet événement ou toute la série ?",
|
||||
"recur_delete_only_this": "Seulement cet événement",
|
||||
"recur_delete_all": "Tous les événements de la série"
|
||||
},
|
||||
"agenda": {
|
||||
"empty": "Aucun événement dans cette période",
|
||||
"open_details_aria": "Ouvrir les détails"
|
||||
},
|
||||
"recurrence": {
|
||||
"none": "Pas de répétition",
|
||||
"daily": "Quotidien",
|
||||
"weekly": "Hebdomadaire",
|
||||
"monthly": "Mensuel",
|
||||
"yearly": "Annuel",
|
||||
"every_2_weeks": "Toutes les 2 semaines",
|
||||
"custom": "Personnalisé...",
|
||||
"recurring_fallback": "Récurrent",
|
||||
"weekly_with_days": "Hebdomadaire ({days})",
|
||||
"every_n_unit": "Tous les {n} {unit}",
|
||||
"every_unit": "Tous les {unit}",
|
||||
"unit_days": "jours",
|
||||
"unit_weeks": "semaines",
|
||||
"unit_months": "mois",
|
||||
"unit_years": "ans",
|
||||
"unit_freq_daily": "jour(s)",
|
||||
"unit_freq_weekly": "semaine(s)",
|
||||
"unit_freq_monthly": "mois",
|
||||
"unit_freq_yearly": "an(s)",
|
||||
"preview_at_days": " le(s) {days}",
|
||||
"preview_count_suffix": ", {count}x",
|
||||
"preview_until_suffix": " jusqu'au {date}",
|
||||
"builder_title": "Répétition personnalisée",
|
||||
"builder_every_label": "Tous les",
|
||||
"builder_weekdays_label": "Jours de la semaine",
|
||||
"builder_end_label": "Se termine",
|
||||
"builder_end_never": "Jamais",
|
||||
"builder_end_after": "Après",
|
||||
"builder_end_after_unit": "événements",
|
||||
"builder_end_until": "Le",
|
||||
"builder_apply": "Appliquer"
|
||||
},
|
||||
"weekday_short": {
|
||||
"mon": "Lun",
|
||||
"tue": "Mar",
|
||||
"wed": "Mer",
|
||||
"thu": "Jeu",
|
||||
"fri": "Ven",
|
||||
"sat": "Sam",
|
||||
"sun": "Dim"
|
||||
},
|
||||
"weekday_long": {
|
||||
"sun": "Dimanche",
|
||||
"mon": "Lundi",
|
||||
"tue": "Mardi",
|
||||
"wed": "Mercredi",
|
||||
"thu": "Jeudi",
|
||||
"fri": "Vendredi",
|
||||
"sat": "Samedi"
|
||||
},
|
||||
"header": {
|
||||
"today": "Aujourd'hui",
|
||||
"new_event": "Événement",
|
||||
"aria_prev": "Précédent",
|
||||
"aria_next": "Suivant",
|
||||
"aria_filter": "Filtre",
|
||||
"aria_export": "Exporter",
|
||||
"block_event": "Événements",
|
||||
"block_task": "Tâches",
|
||||
"block_time_entry": "Temps",
|
||||
"block_habit": "Habitudes",
|
||||
"block_body": "Entraînement",
|
||||
"block_watering": "Arrosage",
|
||||
"block_sleep": "Sommeil",
|
||||
"block_practice": "Pratique",
|
||||
"block_period": "Cycle",
|
||||
"block_guide": "Guides",
|
||||
"block_visit": "Visites",
|
||||
"block_study": "Étude",
|
||||
"block_listening": "Musique",
|
||||
"block_mood": "Humeur",
|
||||
"block_rehearsal": "Répétition"
|
||||
},
|
||||
"date_strip": {
|
||||
"today_aria": "Aller à aujourd'hui",
|
||||
"today_label": "Aujourd'hui"
|
||||
},
|
||||
"mini_cal": {
|
||||
"prev_aria": "Mois précédent",
|
||||
"next_aria": "Mois suivant"
|
||||
},
|
||||
"slots": {
|
||||
"loading": "Recherche de créneaux libres...",
|
||||
"empty": "Aucun créneau libre trouvé",
|
||||
"label": "Créneaux libres",
|
||||
"duration_suffix": "{n}min"
|
||||
},
|
||||
"quick_event": {
|
||||
"aria_dialog": "Créer un événement",
|
||||
"header_new": "Nouvel événement",
|
||||
"placeholder_title": "Ajouter un titre",
|
||||
"type_event": "Événement",
|
||||
"type_time_entry": "Temps",
|
||||
"type_habit": "Habitude",
|
||||
"placeholder_location": "Ajouter un lieu",
|
||||
"placeholder_description": "Description"
|
||||
},
|
||||
"list_view": {
|
||||
"placeholder_new_event": "Nouvel événement...",
|
||||
"voice_reason": "Les événements sont stockés chiffrés. Tu as besoin d'un compte Mana pour ça.",
|
||||
"empty_label": "Événements",
|
||||
"empty_text": "Aucun événement",
|
||||
"open_action": "Ouvrir",
|
||||
"delete_action": "Supprimer"
|
||||
},
|
||||
"detail_route": {
|
||||
"doc_title": "Calendrier - Mana",
|
||||
"create_modal_title": "Nouvel événement",
|
||||
"event_doc_title": "{title} - Calendrier - Mana",
|
||||
"event_doc_title_fallback": "Événement",
|
||||
"back_to_calendar": "Retour au calendrier",
|
||||
"not_found": "Événement introuvable",
|
||||
"edit_title": "Modifier l'événement",
|
||||
"label_title": "Titre",
|
||||
"label_description": "Description",
|
||||
"label_date": "Date",
|
||||
"label_from": "De",
|
||||
"label_to": "À",
|
||||
"label_location": "Lieu",
|
||||
"placeholder_location": "Saisis le lieu...",
|
||||
"submit_save": "Enregistrer",
|
||||
"submit_cancel": "Annuler",
|
||||
"time_suffix_uhr": ""
|
||||
},
|
||||
"detail_view": {
|
||||
"toast_deleted": "Événement supprimé",
|
||||
"not_found": "Événement introuvable",
|
||||
"confirm_delete": "Vraiment supprimer l'événement ?",
|
||||
"placeholder_title": "Titre...",
|
||||
"label_visibility": "Visibilité",
|
||||
"label_allday": "Toute la journée",
|
||||
"placeholder_location": "Ajouter un lieu...",
|
||||
"section_tags": "Tags",
|
||||
"section_description": "Description",
|
||||
"placeholder_description": "Ajouter une description...",
|
||||
"meta_created": "Créé : {date}",
|
||||
"meta_updated": "Modifié : {date}",
|
||||
"untitled_fallback": "Sans titre"
|
||||
},
|
||||
"calendars_route": {
|
||||
"doc_title": "Gérer les calendriers - Mana",
|
||||
"back_to_calendar": "Retour au calendrier",
|
||||
"page_title": "Mes calendriers",
|
||||
"new_calendar": "Nouveau calendrier",
|
||||
"label_name": "Nom",
|
||||
"placeholder_name": "p. ex. Travail, Sport, Famille...",
|
||||
"label_color": "Couleur",
|
||||
"aria_pick_color": "Choisir la couleur",
|
||||
"submit_create": "Créer",
|
||||
"empty": "Pas encore de calendriers.",
|
||||
"badge_default": "(Par défaut)",
|
||||
"aria_hide": "Masquer",
|
||||
"aria_show": "Afficher",
|
||||
"aria_set_default": "Définir comme défaut",
|
||||
"confirm_delete": "Vraiment supprimer le calendrier ? Tous les événements associés seront perdus."
|
||||
},
|
||||
"shared_view": {
|
||||
"kind": "Événement",
|
||||
"label_when": "Quand",
|
||||
"label_where": "Où",
|
||||
"timezone_prefix": "Fuseau horaire : {tz}",
|
||||
"add_to_calendar": "📅 Ajouter à mon calendrier",
|
||||
"expiry": "Ce lien expire le {date}."
|
||||
},
|
||||
"repeat": {
|
||||
"none": "Ne pas répéter",
|
||||
"daily": "Quotidien",
|
||||
|
|
|
|||
|
|
@ -29,8 +29,10 @@
|
|||
},
|
||||
"calendar": {
|
||||
"today": "Oggi",
|
||||
"tomorrow": "Domani",
|
||||
"newEvent": "Nuovo evento",
|
||||
"noEvents": "Nessun evento",
|
||||
"noEventsInRange": "Nessun evento in questo intervallo",
|
||||
"allDay": "Tutto il giorno",
|
||||
"myCalendars": "I miei calendari",
|
||||
"sharedCalendars": "Calendari condivisi",
|
||||
|
|
@ -58,6 +60,210 @@
|
|||
"changeStartTime": "Cambia ora di inizio",
|
||||
"changeEndTime": "Cambia ora di fine"
|
||||
},
|
||||
"event_form": {
|
||||
"aria_create": "Crea evento",
|
||||
"aria_edit": "Modifica evento",
|
||||
"label_title_required": "Titolo *",
|
||||
"placeholder_title": "Inserisci il nome dell'evento",
|
||||
"label_time": "Ora",
|
||||
"label_recurrence": "Ripetizione",
|
||||
"placeholder_location": "Inserisci il luogo...",
|
||||
"placeholder_description": "Aggiungi una descrizione",
|
||||
"label_tags": "Tag",
|
||||
"recur_edit_suffix": "{preview} — Modifica"
|
||||
},
|
||||
"event_card": {
|
||||
"new_event_label": "Nuovo evento",
|
||||
"recurring_title": "Ricorrente",
|
||||
"linked_title": "Eseguito"
|
||||
},
|
||||
"event_modal": {
|
||||
"editing_title": "Modifica evento",
|
||||
"copy_title": "Copia",
|
||||
"label_visibility": "Visibilità",
|
||||
"label_share_link": "Link",
|
||||
"created_at": "Creato: {date}",
|
||||
"updated_at": "· Modificato: {date}",
|
||||
"confirm_delete_single": "Eliminare questo evento?",
|
||||
"clipboard_location_prefix": "Luogo: {location}",
|
||||
"recur_edit_title": "Modifica evento ricorrente",
|
||||
"recur_edit_text": "Modificare solo questo evento o tutti quelli futuri?",
|
||||
"recur_edit_only_this": "Solo questo evento",
|
||||
"recur_edit_all_future": "Tutti gli eventi futuri",
|
||||
"recur_delete_title": "Elimina evento ricorrente",
|
||||
"recur_delete_text": "Eliminare solo questo evento o tutta la serie?",
|
||||
"recur_delete_only_this": "Solo questo evento",
|
||||
"recur_delete_all": "Tutti gli eventi della serie"
|
||||
},
|
||||
"agenda": {
|
||||
"empty": "Nessun evento in questo intervallo",
|
||||
"open_details_aria": "Apri dettagli"
|
||||
},
|
||||
"recurrence": {
|
||||
"none": "Nessuna ripetizione",
|
||||
"daily": "Giornaliero",
|
||||
"weekly": "Settimanale",
|
||||
"monthly": "Mensile",
|
||||
"yearly": "Annuale",
|
||||
"every_2_weeks": "Ogni 2 settimane",
|
||||
"custom": "Personalizzato...",
|
||||
"recurring_fallback": "Ricorrente",
|
||||
"weekly_with_days": "Settimanale ({days})",
|
||||
"every_n_unit": "Ogni {n} {unit}",
|
||||
"every_unit": "Ogni {unit}",
|
||||
"unit_days": "giorni",
|
||||
"unit_weeks": "settimane",
|
||||
"unit_months": "mesi",
|
||||
"unit_years": "anni",
|
||||
"unit_freq_daily": "giorno/i",
|
||||
"unit_freq_weekly": "settimana/e",
|
||||
"unit_freq_monthly": "mese/i",
|
||||
"unit_freq_yearly": "anno/i",
|
||||
"preview_at_days": " il {days}",
|
||||
"preview_count_suffix": ", {count}x",
|
||||
"preview_until_suffix": " fino al {date}",
|
||||
"builder_title": "Ripetizione personalizzata",
|
||||
"builder_every_label": "Ogni",
|
||||
"builder_weekdays_label": "Giorni della settimana",
|
||||
"builder_end_label": "Termina",
|
||||
"builder_end_never": "Mai",
|
||||
"builder_end_after": "Dopo",
|
||||
"builder_end_after_unit": "eventi",
|
||||
"builder_end_until": "Il",
|
||||
"builder_apply": "Applica"
|
||||
},
|
||||
"weekday_short": {
|
||||
"mon": "Lun",
|
||||
"tue": "Mar",
|
||||
"wed": "Mer",
|
||||
"thu": "Gio",
|
||||
"fri": "Ven",
|
||||
"sat": "Sab",
|
||||
"sun": "Dom"
|
||||
},
|
||||
"weekday_long": {
|
||||
"sun": "Domenica",
|
||||
"mon": "Lunedì",
|
||||
"tue": "Martedì",
|
||||
"wed": "Mercoledì",
|
||||
"thu": "Giovedì",
|
||||
"fri": "Venerdì",
|
||||
"sat": "Sabato"
|
||||
},
|
||||
"header": {
|
||||
"today": "Oggi",
|
||||
"new_event": "Evento",
|
||||
"aria_prev": "Indietro",
|
||||
"aria_next": "Avanti",
|
||||
"aria_filter": "Filtro",
|
||||
"aria_export": "Esporta",
|
||||
"block_event": "Eventi",
|
||||
"block_task": "Attività",
|
||||
"block_time_entry": "Tempi",
|
||||
"block_habit": "Abitudini",
|
||||
"block_body": "Allenamento",
|
||||
"block_watering": "Annaffiature",
|
||||
"block_sleep": "Sonno",
|
||||
"block_practice": "Esercizio",
|
||||
"block_period": "Ciclo",
|
||||
"block_guide": "Guide",
|
||||
"block_visit": "Visite",
|
||||
"block_study": "Studio",
|
||||
"block_listening": "Musica",
|
||||
"block_mood": "Umore",
|
||||
"block_rehearsal": "Prova"
|
||||
},
|
||||
"date_strip": {
|
||||
"today_aria": "Vai a oggi",
|
||||
"today_label": "Oggi"
|
||||
},
|
||||
"mini_cal": {
|
||||
"prev_aria": "Mese precedente",
|
||||
"next_aria": "Mese successivo"
|
||||
},
|
||||
"slots": {
|
||||
"loading": "Ricerca slot liberi...",
|
||||
"empty": "Nessuno slot libero trovato",
|
||||
"label": "Slot liberi",
|
||||
"duration_suffix": "{n}min"
|
||||
},
|
||||
"quick_event": {
|
||||
"aria_dialog": "Crea evento",
|
||||
"header_new": "Nuovo evento",
|
||||
"placeholder_title": "Aggiungi un titolo",
|
||||
"type_event": "Evento",
|
||||
"type_time_entry": "Tempo",
|
||||
"type_habit": "Abitudine",
|
||||
"placeholder_location": "Aggiungi luogo",
|
||||
"placeholder_description": "Descrizione"
|
||||
},
|
||||
"list_view": {
|
||||
"placeholder_new_event": "Nuovo evento...",
|
||||
"voice_reason": "Gli eventi sono salvati cifrati. Per questo serve un account Mana.",
|
||||
"empty_label": "Eventi",
|
||||
"empty_text": "Nessun evento",
|
||||
"open_action": "Apri",
|
||||
"delete_action": "Elimina"
|
||||
},
|
||||
"detail_route": {
|
||||
"doc_title": "Calendario - Mana",
|
||||
"create_modal_title": "Nuovo evento",
|
||||
"event_doc_title": "{title} - Calendario - Mana",
|
||||
"event_doc_title_fallback": "Evento",
|
||||
"back_to_calendar": "Torna al calendario",
|
||||
"not_found": "Evento non trovato",
|
||||
"edit_title": "Modifica evento",
|
||||
"label_title": "Titolo",
|
||||
"label_description": "Descrizione",
|
||||
"label_date": "Data",
|
||||
"label_from": "Da",
|
||||
"label_to": "A",
|
||||
"label_location": "Luogo",
|
||||
"placeholder_location": "Inserisci il luogo...",
|
||||
"submit_save": "Salva",
|
||||
"submit_cancel": "Annulla",
|
||||
"time_suffix_uhr": ""
|
||||
},
|
||||
"detail_view": {
|
||||
"toast_deleted": "Evento eliminato",
|
||||
"not_found": "Evento non trovato",
|
||||
"confirm_delete": "Eliminare davvero l'evento?",
|
||||
"placeholder_title": "Titolo...",
|
||||
"label_visibility": "Visibilità",
|
||||
"label_allday": "Tutto il giorno",
|
||||
"placeholder_location": "Aggiungi luogo...",
|
||||
"section_tags": "Tag",
|
||||
"section_description": "Descrizione",
|
||||
"placeholder_description": "Aggiungi descrizione...",
|
||||
"meta_created": "Creato: {date}",
|
||||
"meta_updated": "Modificato: {date}",
|
||||
"untitled_fallback": "Senza titolo"
|
||||
},
|
||||
"calendars_route": {
|
||||
"doc_title": "Gestisci calendari - Mana",
|
||||
"back_to_calendar": "Torna al calendario",
|
||||
"page_title": "I miei calendari",
|
||||
"new_calendar": "Nuovo calendario",
|
||||
"label_name": "Nome",
|
||||
"placeholder_name": "es. Lavoro, Sport, Famiglia...",
|
||||
"label_color": "Colore",
|
||||
"aria_pick_color": "Scegli colore",
|
||||
"submit_create": "Crea",
|
||||
"empty": "Ancora nessun calendario.",
|
||||
"badge_default": "(Predefinito)",
|
||||
"aria_hide": "Nascondi",
|
||||
"aria_show": "Mostra",
|
||||
"aria_set_default": "Imposta come predefinito",
|
||||
"confirm_delete": "Eliminare davvero il calendario? Tutti gli eventi associati andranno persi."
|
||||
},
|
||||
"shared_view": {
|
||||
"kind": "Evento",
|
||||
"label_when": "Quando",
|
||||
"label_where": "Dove",
|
||||
"timezone_prefix": "Fuso orario: {tz}",
|
||||
"add_to_calendar": "📅 Aggiungi al mio calendario",
|
||||
"expiry": "Questo link scade il {date}."
|
||||
},
|
||||
"repeat": {
|
||||
"none": "Non ripetere",
|
||||
"daily": "Giornaliero",
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
Mini week strip + today's events. Floating input at bottom.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { _ } from 'svelte-i18n';
|
||||
import { db } from '$lib/data/database';
|
||||
import { eventsStore } from './stores/events.svelte';
|
||||
import { useAllCalendarItems } from './queries';
|
||||
|
|
@ -61,16 +62,24 @@
|
|||
return new Date(iso).toLocaleTimeString('de', { hour: '2-digit', minute: '2-digit' });
|
||||
}
|
||||
|
||||
const WEEKDAYS = [
|
||||
'Sonntag',
|
||||
'Montag',
|
||||
'Dienstag',
|
||||
'Mittwoch',
|
||||
'Donnerstag',
|
||||
'Freitag',
|
||||
'Samstag',
|
||||
];
|
||||
const dayNames = ['Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa', 'So'];
|
||||
const WEEKDAYS = $derived([
|
||||
$_('calendar.weekday_long.sun'),
|
||||
$_('calendar.weekday_long.mon'),
|
||||
$_('calendar.weekday_long.tue'),
|
||||
$_('calendar.weekday_long.wed'),
|
||||
$_('calendar.weekday_long.thu'),
|
||||
$_('calendar.weekday_long.fri'),
|
||||
$_('calendar.weekday_long.sat'),
|
||||
]);
|
||||
const dayNames = $derived([
|
||||
$_('calendar.weekday_short.mon'),
|
||||
$_('calendar.weekday_short.tue'),
|
||||
$_('calendar.weekday_short.wed'),
|
||||
$_('calendar.weekday_short.thu'),
|
||||
$_('calendar.weekday_short.fri'),
|
||||
$_('calendar.weekday_short.sat'),
|
||||
$_('calendar.weekday_short.sun'),
|
||||
]);
|
||||
|
||||
function formatDateLabel(iso: string): string {
|
||||
const date = new Date(iso);
|
||||
|
|
@ -78,8 +87,9 @@
|
|||
const todayDate = new Date(now);
|
||||
const tomorrowDate = new Date(todayDate);
|
||||
tomorrowDate.setDate(tomorrowDate.getDate() + 1);
|
||||
if (dateStr === todayStr) return 'Heute';
|
||||
if (dateStr === tomorrowDate.toISOString().split('T')[0]) return 'Morgen';
|
||||
if (dateStr === todayStr) return $_('calendar.calendar.today');
|
||||
if (dateStr === tomorrowDate.toISOString().split('T')[0])
|
||||
return $_('calendar.calendar.tomorrow');
|
||||
// Within 7 days: show weekday name
|
||||
const diffMs = date.getTime() - todayDate.getTime();
|
||||
const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
|
||||
|
|
@ -94,7 +104,7 @@
|
|||
? [
|
||||
{
|
||||
id: 'open',
|
||||
label: 'Öffnen',
|
||||
label: $_('calendar.list_view.open_action'),
|
||||
icon: PencilSimple,
|
||||
action: () => {
|
||||
const target = ctxMenu.state.target;
|
||||
|
|
@ -104,7 +114,7 @@
|
|||
{ id: 'div', label: '', type: 'divider' as const },
|
||||
{
|
||||
id: 'delete',
|
||||
label: 'Löschen',
|
||||
label: $_('calendar.list_view.delete_action'),
|
||||
icon: Trash,
|
||||
variant: 'danger' as const,
|
||||
action: () => {
|
||||
|
|
@ -211,7 +221,7 @@
|
|||
<span class="event-date">{formatDateLabel(event.startTime)}</span>
|
||||
<span class="event-time">
|
||||
{#if event.isAllDay}
|
||||
Ganztägig
|
||||
{$_('calendar.event.allDay')}
|
||||
{:else}
|
||||
{formatTime(event.startTime)}
|
||||
{/if}
|
||||
|
|
@ -222,20 +232,20 @@
|
|||
|
||||
{#if upcomingEvents.length === 0}
|
||||
{#if hasActiveSceneScope()}
|
||||
<ScopeEmptyState label="Termine" />
|
||||
<ScopeEmptyState label={$_('calendar.list_view.empty_label')} />
|
||||
{:else}
|
||||
<p class="empty">Keine Termine</p>
|
||||
<p class="empty">{$_('calendar.list_view.empty_text')}</p>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<FloatingInputBar
|
||||
bind:value={newTitle}
|
||||
placeholder="Neuer Termin..."
|
||||
placeholder={$_('calendar.list_view.placeholder_new_event')}
|
||||
onSubmit={createEvent}
|
||||
voice
|
||||
voiceFeature="calendar-voice-capture"
|
||||
voiceReason="Termine werden verschlüsselt gespeichert. Dafür brauchst du ein Mana-Konto."
|
||||
voiceReason={$_('calendar.list_view.voice_reason')}
|
||||
onVoiceComplete={handleVoiceComplete}
|
||||
/>
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,9 @@
|
|||
in light + dark (via prefers-color-scheme in the layout).
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { _, locale } from 'svelte-i18n';
|
||||
import { get } from 'svelte/store';
|
||||
|
||||
interface EventBlob {
|
||||
title: string;
|
||||
startTime: string;
|
||||
|
|
@ -35,7 +38,7 @@
|
|||
const end = $derived(new Date(event.endTime));
|
||||
|
||||
function formatDate(d: Date): string {
|
||||
return new Intl.DateTimeFormat('de-DE', {
|
||||
return new Intl.DateTimeFormat(get(locale) ?? 'de', {
|
||||
weekday: 'long',
|
||||
day: '2-digit',
|
||||
month: 'long',
|
||||
|
|
@ -44,7 +47,7 @@
|
|||
}
|
||||
|
||||
function formatTime(d: Date): string {
|
||||
return new Intl.DateTimeFormat('de-DE', {
|
||||
return new Intl.DateTimeFormat(get(locale) ?? 'de', {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
}).format(d);
|
||||
|
|
@ -52,7 +55,7 @@
|
|||
|
||||
const dateLabel = $derived(formatDate(start));
|
||||
const timeLabel = $derived(
|
||||
event.isAllDay ? 'Ganztägig' : `${formatTime(start)} – ${formatTime(end)}`
|
||||
event.isAllDay ? $_('calendar.event.allDay') : `${formatTime(start)} – ${formatTime(end)}`
|
||||
);
|
||||
|
||||
// Same-day range = compact; otherwise show two dates
|
||||
|
|
@ -84,38 +87,46 @@
|
|||
</svelte:head>
|
||||
|
||||
<article class="event">
|
||||
<span class="event__kind">Termin</span>
|
||||
<span class="event__kind">{$_('calendar.shared_view.kind')}</span>
|
||||
<h1 class="event__title">{event.title}</h1>
|
||||
|
||||
<dl class="event__meta">
|
||||
<div class="event__row">
|
||||
<dt>Wann</dt>
|
||||
<dt>{$_('calendar.shared_view.label_when')}</dt>
|
||||
<dd>
|
||||
<div class="event__date">{dateRangeLabel}</div>
|
||||
<div class="event__time">{timeLabel}</div>
|
||||
{#if event.timezone}
|
||||
<div class="event__tz">Zeitzone: {event.timezone}</div>
|
||||
<div class="event__tz">
|
||||
{$_('calendar.shared_view.timezone_prefix', { values: { tz: event.timezone } })}
|
||||
</div>
|
||||
{/if}
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
{#if event.location}
|
||||
<div class="event__row">
|
||||
<dt>Wo</dt>
|
||||
<dt>{$_('calendar.shared_view.label_where')}</dt>
|
||||
<dd>{event.location}</dd>
|
||||
</div>
|
||||
{/if}
|
||||
</dl>
|
||||
|
||||
<a class="event__ics" href={icsUrl} download="event.ics">📅 Zum eigenen Kalender hinzufügen</a>
|
||||
<a class="event__ics" href={icsUrl} download="event.ics"
|
||||
>{$_('calendar.shared_view.add_to_calendar')}</a
|
||||
>
|
||||
|
||||
{#if expiresAt}
|
||||
<p class="event__expiry">
|
||||
Dieser Link läuft am {new Intl.DateTimeFormat('de-DE', {
|
||||
{$_('calendar.shared_view.expiry', {
|
||||
values: {
|
||||
date: new Intl.DateTimeFormat(get(locale) ?? 'de', {
|
||||
day: '2-digit',
|
||||
month: 'long',
|
||||
year: 'numeric',
|
||||
}).format(new Date(expiresAt))} ab.
|
||||
}).format(new Date(expiresAt)),
|
||||
},
|
||||
})}
|
||||
</p>
|
||||
{/if}
|
||||
</article>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
<script lang="ts">
|
||||
import { getDateFnsLocale } from '$lib/i18n/format';
|
||||
import { _ } from 'svelte-i18n';
|
||||
import { getContext } from 'svelte';
|
||||
import { calendarViewStore } from '../stores/view.svelte';
|
||||
import { eventsStore } from '../stores/events.svelte';
|
||||
|
|
@ -73,8 +74,8 @@
|
|||
});
|
||||
|
||||
function formatDateHeader(date: Date) {
|
||||
if (isToday(date)) return 'Heute';
|
||||
if (isTomorrow(date)) return 'Morgen';
|
||||
if (isToday(date)) return $_('calendar.calendar.today');
|
||||
if (isTomorrow(date)) return $_('calendar.calendar.tomorrow');
|
||||
return format(date, 'EEEE, d. MMMM', { locale: getDateFnsLocale() });
|
||||
}
|
||||
|
||||
|
|
@ -108,7 +109,7 @@
|
|||
{#if groupedEvents.length === 0}
|
||||
<div class="empty-state">
|
||||
<CalendarBlank size={64} />
|
||||
<p>Keine Termine in diesem Zeitraum</p>
|
||||
<p>{$_('calendar.agenda.empty')}</p>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="event-list">
|
||||
|
|
@ -125,7 +126,7 @@
|
|||
<div class="event-content">
|
||||
<div class="event-time">
|
||||
{#if event.isAllDay}
|
||||
Ganztägig
|
||||
{$_('calendar.event.allDay')}
|
||||
{:else}
|
||||
{format(toDate(event.startTime), 'HH:mm')} - {format(
|
||||
toDate(event.endTime),
|
||||
|
|
@ -155,8 +156,8 @@
|
|||
<button
|
||||
class="expand-btn"
|
||||
onclick={() => handleEventClick(event)}
|
||||
title="Details öffnen"
|
||||
aria-label="Details öffnen"
|
||||
title={$_('calendar.agenda.open_details_aria')}
|
||||
aria-label={$_('calendar.agenda.open_details_aria')}
|
||||
>
|
||||
<CaretRight size={16} />
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
<script lang="ts">
|
||||
import { getDateFnsLocale } from '$lib/i18n/format';
|
||||
import { _ } from 'svelte-i18n';
|
||||
import { calendarViewStore } from '../stores/view.svelte';
|
||||
import type { CalendarViewType } from '../types';
|
||||
import type { TimeBlockType } from '$lib/data/time-blocks/types';
|
||||
|
|
@ -39,23 +40,25 @@
|
|||
|
||||
let showFilters = $state(false);
|
||||
|
||||
const blockTypeConfig: { type: TimeBlockType; label: string; icon: typeof CalendarBlank }[] = [
|
||||
{ type: 'event', label: 'Termine', icon: CalendarBlank },
|
||||
{ type: 'task', label: 'Aufgaben', icon: CheckSquare },
|
||||
{ type: 'timeEntry', label: 'Zeiten', icon: Timer },
|
||||
{ type: 'habit', label: 'Habits', icon: Heart },
|
||||
{ type: 'body', label: 'Training', icon: Barbell },
|
||||
{ type: 'watering', label: 'Gießen', icon: Drop },
|
||||
{ type: 'sleep', label: 'Schlaf', icon: Moon },
|
||||
{ type: 'practice', label: 'Übung', icon: GraduationCap },
|
||||
{ type: 'period', label: 'Periode', icon: FlowerLotus },
|
||||
{ type: 'guide', label: 'Guides', icon: Compass },
|
||||
{ type: 'visit', label: 'Besuche', icon: MapPin },
|
||||
{ type: 'study', label: 'Lernen', icon: BookOpen },
|
||||
{ type: 'listening', label: 'Musik', icon: MusicNote },
|
||||
{ type: 'mood', label: 'Stimmung', icon: SunHorizon },
|
||||
{ type: 'rehearsal', label: 'Probe', icon: Presentation },
|
||||
];
|
||||
const blockTypeConfig = $derived<
|
||||
{ type: TimeBlockType; label: string; icon: typeof CalendarBlank }[]
|
||||
>([
|
||||
{ type: 'event', label: $_('calendar.header.block_event'), icon: CalendarBlank },
|
||||
{ type: 'task', label: $_('calendar.header.block_task'), icon: CheckSquare },
|
||||
{ type: 'timeEntry', label: $_('calendar.header.block_time_entry'), icon: Timer },
|
||||
{ type: 'habit', label: $_('calendar.header.block_habit'), icon: Heart },
|
||||
{ type: 'body', label: $_('calendar.header.block_body'), icon: Barbell },
|
||||
{ type: 'watering', label: $_('calendar.header.block_watering'), icon: Drop },
|
||||
{ type: 'sleep', label: $_('calendar.header.block_sleep'), icon: Moon },
|
||||
{ type: 'practice', label: $_('calendar.header.block_practice'), icon: GraduationCap },
|
||||
{ type: 'period', label: $_('calendar.header.block_period'), icon: FlowerLotus },
|
||||
{ type: 'guide', label: $_('calendar.header.block_guide'), icon: Compass },
|
||||
{ type: 'visit', label: $_('calendar.header.block_visit'), icon: MapPin },
|
||||
{ type: 'study', label: $_('calendar.header.block_study'), icon: BookOpen },
|
||||
{ type: 'listening', label: $_('calendar.header.block_listening'), icon: MusicNote },
|
||||
{ type: 'mood', label: $_('calendar.header.block_mood'), icon: SunHorizon },
|
||||
{ type: 'rehearsal', label: $_('calendar.header.block_rehearsal'), icon: Presentation },
|
||||
]);
|
||||
|
||||
let allActive = $derived(
|
||||
blockTypeConfig.every((c) => calendarViewStore.visibleBlockTypes.has(c.type))
|
||||
|
|
@ -82,22 +85,32 @@
|
|||
});
|
||||
});
|
||||
|
||||
const viewLabels: Record<CalendarViewType, string> = {
|
||||
week: 'Woche',
|
||||
month: 'Monat',
|
||||
agenda: 'Agenda',
|
||||
};
|
||||
const viewLabels = $derived<Record<CalendarViewType, string>>({
|
||||
week: $_('calendar.views.week'),
|
||||
month: $_('calendar.views.month'),
|
||||
agenda: $_('calendar.views.agenda'),
|
||||
});
|
||||
</script>
|
||||
|
||||
<header class="calendar-header">
|
||||
<div class="header-left">
|
||||
<h1 class="header-label">{headerLabel}</h1>
|
||||
<div class="nav-buttons">
|
||||
<button onclick={() => calendarViewStore.goToPrevious()} class="nav-btn" aria-label="Zurück">
|
||||
<button
|
||||
onclick={() => calendarViewStore.goToPrevious()}
|
||||
class="nav-btn"
|
||||
aria-label={$_('calendar.header.aria_prev')}
|
||||
>
|
||||
<CaretLeft size={18} />
|
||||
</button>
|
||||
<button onclick={() => calendarViewStore.goToToday()} class="today-btn"> Heute </button>
|
||||
<button onclick={() => calendarViewStore.goToNext()} class="nav-btn" aria-label="Weiter">
|
||||
<button onclick={() => calendarViewStore.goToToday()} class="today-btn">
|
||||
{$_('calendar.header.today')}
|
||||
</button>
|
||||
<button
|
||||
onclick={() => calendarViewStore.goToNext()}
|
||||
class="nav-btn"
|
||||
aria-label={$_('calendar.header.aria_next')}
|
||||
>
|
||||
<CaretRight size={18} />
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -120,18 +133,22 @@
|
|||
onclick={() => (showFilters = !showFilters)}
|
||||
class="filter-btn"
|
||||
class:active={!allActive}
|
||||
aria-label="Filter"
|
||||
aria-label={$_('calendar.header.aria_filter')}
|
||||
>
|
||||
<Funnel size={16} />
|
||||
</button>
|
||||
|
||||
<button class="filter-btn" onclick={handleExport} aria-label="Exportieren">
|
||||
<button
|
||||
class="filter-btn"
|
||||
onclick={handleExport}
|
||||
aria-label={$_('calendar.header.aria_export')}
|
||||
>
|
||||
<Export size={16} />
|
||||
</button>
|
||||
|
||||
<button onclick={onNewEvent} class="new-event-btn">
|
||||
<Plus size={16} />
|
||||
Termin
|
||||
{$_('calendar.header.new_event')}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
<script lang="ts">
|
||||
import { _ } from 'svelte-i18n';
|
||||
import { RRule } from 'rrule';
|
||||
|
||||
interface Props {
|
||||
|
|
@ -20,22 +21,22 @@
|
|||
let count = $state(parsed?.count ?? 10);
|
||||
let untilDate = $state(parsed?.until ?? '');
|
||||
|
||||
const DAYS = [
|
||||
{ value: 0, short: 'So', rrule: 'SU' },
|
||||
{ value: 1, short: 'Mo', rrule: 'MO' },
|
||||
{ value: 2, short: 'Di', rrule: 'TU' },
|
||||
{ value: 3, short: 'Mi', rrule: 'WE' },
|
||||
{ value: 4, short: 'Do', rrule: 'TH' },
|
||||
{ value: 5, short: 'Fr', rrule: 'FR' },
|
||||
{ value: 6, short: 'Sa', rrule: 'SA' },
|
||||
];
|
||||
const DAYS = $derived([
|
||||
{ value: 0, short: $_('calendar.weekday_short.sun'), rrule: 'SU' },
|
||||
{ value: 1, short: $_('calendar.weekday_short.mon'), rrule: 'MO' },
|
||||
{ value: 2, short: $_('calendar.weekday_short.tue'), rrule: 'TU' },
|
||||
{ value: 3, short: $_('calendar.weekday_short.wed'), rrule: 'WE' },
|
||||
{ value: 4, short: $_('calendar.weekday_short.thu'), rrule: 'TH' },
|
||||
{ value: 5, short: $_('calendar.weekday_short.fri'), rrule: 'FR' },
|
||||
{ value: 6, short: $_('calendar.weekday_short.sat'), rrule: 'SA' },
|
||||
]);
|
||||
|
||||
const FREQ_LABELS: Record<string, string> = {
|
||||
DAILY: 'Tag(e)',
|
||||
WEEKLY: 'Woche(n)',
|
||||
MONTHLY: 'Monat(e)',
|
||||
YEARLY: 'Jahr(e)',
|
||||
};
|
||||
const FREQ_LABELS = $derived<Record<string, string>>({
|
||||
DAILY: $_('calendar.recurrence.unit_freq_daily'),
|
||||
WEEKLY: $_('calendar.recurrence.unit_freq_weekly'),
|
||||
MONTHLY: $_('calendar.recurrence.unit_freq_monthly'),
|
||||
YEARLY: $_('calendar.recurrence.unit_freq_yearly'),
|
||||
});
|
||||
|
||||
function toggleDay(day: number) {
|
||||
if (selectedDays.includes(day)) {
|
||||
|
|
@ -112,35 +113,43 @@
|
|||
|
||||
// Preview text
|
||||
let preview = $derived.by(() => {
|
||||
let text = `Alle ${interval > 1 ? interval + ' ' : ''}${FREQ_LABELS[freq]}`;
|
||||
const unit = FREQ_LABELS[freq];
|
||||
let text =
|
||||
interval > 1
|
||||
? $_('calendar.recurrence.every_n_unit', { values: { n: interval, unit } })
|
||||
: $_('calendar.recurrence.every_unit', { values: { unit } });
|
||||
if (freq === 'WEEKLY' && selectedDays.length > 0 && selectedDays.length < 7) {
|
||||
text += ` an ${selectedDays.map((d) => DAYS[d].short).join(', ')}`;
|
||||
text += $_('calendar.recurrence.preview_at_days', {
|
||||
values: { days: selectedDays.map((d) => DAYS[d].short).join(', ') },
|
||||
});
|
||||
}
|
||||
if (endType === 'count') text += `, ${count}x`;
|
||||
else if (endType === 'until' && untilDate) text += ` bis ${untilDate}`;
|
||||
if (endType === 'count')
|
||||
text += $_('calendar.recurrence.preview_count_suffix', { values: { count } });
|
||||
else if (endType === 'until' && untilDate)
|
||||
text += $_('calendar.recurrence.preview_until_suffix', { values: { date: untilDate } });
|
||||
return text;
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="recurrence-builder">
|
||||
<div class="builder-header">Benutzerdefinierte Wiederholung</div>
|
||||
<div class="builder-header">{$_('calendar.recurrence.builder_title')}</div>
|
||||
|
||||
<!-- Frequency + Interval -->
|
||||
<div class="builder-row">
|
||||
<span class="row-label">Alle</span>
|
||||
<span class="row-label">{$_('calendar.recurrence.builder_every_label')}</span>
|
||||
<input type="number" class="interval-input" bind:value={interval} min="1" max="99" />
|
||||
<select class="freq-select" bind:value={freq}>
|
||||
<option value="DAILY">Tag(e)</option>
|
||||
<option value="WEEKLY">Woche(n)</option>
|
||||
<option value="MONTHLY">Monat(e)</option>
|
||||
<option value="YEARLY">Jahr(e)</option>
|
||||
<option value="DAILY">{$_('calendar.recurrence.unit_freq_daily')}</option>
|
||||
<option value="WEEKLY">{$_('calendar.recurrence.unit_freq_weekly')}</option>
|
||||
<option value="MONTHLY">{$_('calendar.recurrence.unit_freq_monthly')}</option>
|
||||
<option value="YEARLY">{$_('calendar.recurrence.unit_freq_yearly')}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Weekday picker (only for WEEKLY) -->
|
||||
{#if freq === 'WEEKLY'}
|
||||
<div class="builder-section">
|
||||
<span class="section-label">Wochentage</span>
|
||||
<span class="section-label">{$_('calendar.recurrence.builder_weekdays_label')}</span>
|
||||
<div class="day-picker">
|
||||
{#each DAYS as day}
|
||||
<button
|
||||
|
|
@ -158,23 +167,23 @@
|
|||
|
||||
<!-- End condition -->
|
||||
<div class="builder-section">
|
||||
<span class="section-label">Endet</span>
|
||||
<span class="section-label">{$_('calendar.recurrence.builder_end_label')}</span>
|
||||
<div class="end-options">
|
||||
<label class="radio-label">
|
||||
<input type="radio" bind:group={endType} value="never" />
|
||||
<span>Nie</span>
|
||||
<span>{$_('calendar.recurrence.builder_end_never')}</span>
|
||||
</label>
|
||||
<label class="radio-label">
|
||||
<input type="radio" bind:group={endType} value="count" />
|
||||
<span>Nach</span>
|
||||
<span>{$_('calendar.recurrence.builder_end_after')}</span>
|
||||
{#if endType === 'count'}
|
||||
<input type="number" class="count-input" bind:value={count} min="1" max="999" />
|
||||
<span>Terminen</span>
|
||||
<span>{$_('calendar.recurrence.builder_end_after_unit')}</span>
|
||||
{/if}
|
||||
</label>
|
||||
<label class="radio-label">
|
||||
<input type="radio" bind:group={endType} value="until" />
|
||||
<span>Am</span>
|
||||
<span>{$_('calendar.recurrence.builder_end_until')}</span>
|
||||
{#if endType === 'until'}
|
||||
<input type="date" class="until-input" bind:value={untilDate} />
|
||||
{/if}
|
||||
|
|
@ -187,8 +196,11 @@
|
|||
|
||||
<!-- Actions -->
|
||||
<div class="builder-actions">
|
||||
<button type="button" class="btn btn-secondary" onclick={onCancel}>Abbrechen</button>
|
||||
<button type="button" class="btn btn-primary" onclick={handleApply}>Übernehmen</button>
|
||||
<button type="button" class="btn btn-secondary" onclick={onCancel}>{$_('common.cancel')}</button
|
||||
>
|
||||
<button type="button" class="btn btn-primary" onclick={handleApply}
|
||||
>{$_('calendar.recurrence.builder_apply')}</button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
<script lang="ts">
|
||||
import { getDateFnsLocale } from '$lib/i18n/format';
|
||||
import { _ } from 'svelte-i18n';
|
||||
import { getContext } from 'svelte';
|
||||
import { calendarViewStore } from '../stores/view.svelte';
|
||||
import { getEventsForDay } from '../queries';
|
||||
|
|
@ -209,8 +210,12 @@
|
|||
<div class="month-header">
|
||||
<span class="month-label">
|
||||
{#if !isTodayVisible}
|
||||
<button onclick={goToToday} title="Zum heutigen Tag" class="today-button">
|
||||
<span class="today-label">Heute</span>
|
||||
<button
|
||||
onclick={goToToday}
|
||||
title={$_('calendar.date_strip.today_aria')}
|
||||
class="today-button"
|
||||
>
|
||||
<span class="today-label">{$_('calendar.date_strip.today_label')}</span>
|
||||
<span class="today-date"
|
||||
>{format(new Date(), 'd. MMM', { locale: getDateFnsLocale() })}</span
|
||||
>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
<script lang="ts">
|
||||
import { _ } from 'svelte-i18n';
|
||||
import type { CalendarEvent } from '../types';
|
||||
import { eventsStore } from '../stores/events.svelte';
|
||||
import {
|
||||
|
|
@ -96,7 +97,7 @@
|
|||
style:background-color={color}
|
||||
role="button"
|
||||
tabindex="0"
|
||||
aria-label={event.title || 'Neuer Termin'}
|
||||
aria-label={event.title || $_('calendar.event_card.new_event_label')}
|
||||
onpointerdown={handlePointerDown}
|
||||
onclick={handleClick}
|
||||
onkeydown={handleKeydown}
|
||||
|
|
@ -107,7 +108,7 @@
|
|||
class="resize-handle top"
|
||||
onpointerdown={handleResizeTop}
|
||||
role="slider"
|
||||
aria-label="Startzeit ändern"
|
||||
aria-label={$_('calendar.event.changeStartTime')}
|
||||
aria-valuenow={0}
|
||||
tabindex="-1"
|
||||
></div>
|
||||
|
|
@ -130,13 +131,19 @@
|
|||
|
||||
<span class="event-time">{formattedTime}</span>
|
||||
{#if event.parentBlockId}
|
||||
<span class="repeat-icon" title="Wiederkehrend"><ArrowsClockwise size={9} /></span>
|
||||
<span class="repeat-icon" title={$_('calendar.event_card.recurring_title')}
|
||||
><ArrowsClockwise size={9} /></span
|
||||
>
|
||||
{/if}
|
||||
{#if event.linkedBlockId}
|
||||
<span class="linked-badge" title="Durchgeführt"><CheckCircle size={10} weight="fill" /></span>
|
||||
<span class="linked-badge" title={$_('calendar.event_card.linked_title')}
|
||||
><CheckCircle size={10} weight="fill" /></span
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
<span class="event-title">{event.title || (isDraft ? 'Neuer Termin' : '')}</span>
|
||||
<span class="event-title"
|
||||
>{event.title || (isDraft ? $_('calendar.event_card.new_event_label') : '')}</span
|
||||
>
|
||||
{#if event.location}
|
||||
<span class="event-location">{event.location}</span>
|
||||
{/if}
|
||||
|
|
@ -146,7 +153,7 @@
|
|||
class="resize-handle bottom"
|
||||
onpointerdown={handleResizeBottom}
|
||||
role="slider"
|
||||
aria-label="Endzeit ändern"
|
||||
aria-label={$_('calendar.event.changeEndTime')}
|
||||
aria-valuenow={0}
|
||||
tabindex="-1"
|
||||
></div>
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@
|
|||
|
||||
// Format time display
|
||||
function formatEventTime(ev: CalendarEvent): string {
|
||||
if (ev.isAllDay) return 'Ganztägig';
|
||||
if (ev.isAllDay) return $_('calendar.event.allDay');
|
||||
const start = toDate(ev.startTime);
|
||||
const end = toDate(ev.endTime);
|
||||
const dateStr = format(start, 'EEEE, d. MMMM yyyy', { locale: getDateFnsLocale() });
|
||||
|
|
@ -102,32 +102,36 @@
|
|||
|
||||
function formatRecurrence(rule: string): string {
|
||||
if (!rule) return '';
|
||||
if (rule.includes('FREQ=DAILY')) return 'Täglich';
|
||||
if (rule.includes('FREQ=DAILY')) return $_('calendar.recurrence.daily');
|
||||
if (rule.includes('FREQ=WEEKLY')) {
|
||||
if (rule.includes('INTERVAL=2')) return 'Alle 2 Wochen';
|
||||
if (rule.includes('INTERVAL=2')) return $_('calendar.recurrence.every_2_weeks');
|
||||
if (rule.includes('BYDAY=')) {
|
||||
const days = rule.match(/BYDAY=([A-Z,]+)/)?.[1];
|
||||
if (days) {
|
||||
const dayMap: Record<string, string> = {
|
||||
MO: 'Mo',
|
||||
TU: 'Di',
|
||||
WE: 'Mi',
|
||||
TH: 'Do',
|
||||
FR: 'Fr',
|
||||
SA: 'Sa',
|
||||
SU: 'So',
|
||||
MO: $_('calendar.weekday_short.mon'),
|
||||
TU: $_('calendar.weekday_short.tue'),
|
||||
WE: $_('calendar.weekday_short.wed'),
|
||||
TH: $_('calendar.weekday_short.thu'),
|
||||
FR: $_('calendar.weekday_short.fri'),
|
||||
SA: $_('calendar.weekday_short.sat'),
|
||||
SU: $_('calendar.weekday_short.sun'),
|
||||
};
|
||||
return `Wöchentlich (${days
|
||||
return $_('calendar.recurrence.weekly_with_days', {
|
||||
values: {
|
||||
days: days
|
||||
.split(',')
|
||||
.map((d) => dayMap[d] || d)
|
||||
.join(', ')})`;
|
||||
.join(', '),
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
return 'Wöchentlich';
|
||||
return $_('calendar.recurrence.weekly');
|
||||
}
|
||||
if (rule.includes('FREQ=MONTHLY')) return 'Monatlich';
|
||||
if (rule.includes('FREQ=YEARLY')) return 'Jährlich';
|
||||
return 'Wiederkehrend';
|
||||
if (rule.includes('FREQ=MONTHLY')) return $_('calendar.recurrence.monthly');
|
||||
if (rule.includes('FREQ=YEARLY')) return $_('calendar.recurrence.yearly');
|
||||
return $_('calendar.recurrence.recurring_fallback');
|
||||
}
|
||||
|
||||
async function handleSave(data: Parameters<typeof eventsStore.updateEvent>[1]) {
|
||||
|
|
@ -170,7 +174,7 @@
|
|||
if (isRecurring || hasParent) {
|
||||
showDeleteOptions = true;
|
||||
} else {
|
||||
if (confirm('Diesen Termin löschen?')) {
|
||||
if (confirm($_('calendar.event_modal.confirm_delete_single'))) {
|
||||
eventsStore.deleteEvent(event.id);
|
||||
onClose();
|
||||
}
|
||||
|
|
@ -181,8 +185,12 @@
|
|||
const start = toDate(event.startTime);
|
||||
const text = [
|
||||
event.title,
|
||||
event.isAllDay ? 'Ganztägig' : `${format(start, 'dd.MM.yyyy HH:mm')}`,
|
||||
event.location ? `Ort: ${event.location}` : '',
|
||||
event.isAllDay ? $_('calendar.event.allDay') : `${format(start, 'dd.MM.yyyy HH:mm')}`,
|
||||
event.location
|
||||
? $_('calendar.event_modal.clipboard_location_prefix', {
|
||||
values: { location: event.location },
|
||||
})
|
||||
: '',
|
||||
event.description || '',
|
||||
]
|
||||
.filter(Boolean)
|
||||
|
|
@ -222,7 +230,7 @@
|
|||
<div class="modal-header">
|
||||
<div class="header-left">
|
||||
<h2 id="modal-title" class="modal-title">
|
||||
{isEditing ? 'Termin bearbeiten' : event.title}
|
||||
{isEditing ? $_('calendar.event_modal.editing_title') : event.title}
|
||||
</h2>
|
||||
{#if !isEditing && calendarName}
|
||||
<span class="calendar-badge">
|
||||
|
|
@ -233,7 +241,11 @@
|
|||
</div>
|
||||
<div class="modal-actions">
|
||||
{#if !isEditing}
|
||||
<button class="btn btn-ghost" onclick={copyToClipboard} title="Kopieren">
|
||||
<button
|
||||
class="btn btn-ghost"
|
||||
onclick={copyToClipboard}
|
||||
title={$_('calendar.event_modal.copy_title')}
|
||||
>
|
||||
{#if copied}<Check size={16} />{:else}<Copy size={16} />{/if}
|
||||
</button>
|
||||
<button class="btn btn-ghost" onclick={handleEditClick} title={$_('common.edit')}>
|
||||
|
|
@ -266,7 +278,7 @@
|
|||
<div class="event-details">
|
||||
<!-- Visibility -->
|
||||
<div class="detail-row">
|
||||
<span class="detail-label">Sichtbarkeit</span>
|
||||
<span class="detail-label">{$_('calendar.event_modal.label_visibility')}</span>
|
||||
<div class="detail-content">
|
||||
<VisibilityPicker level={event.visibility} onChange={handleVisibilityChange} />
|
||||
</div>
|
||||
|
|
@ -275,7 +287,7 @@
|
|||
<!-- Share link (only when visibility = unlisted) -->
|
||||
{#if event.visibility === 'unlisted' && event.unlistedToken && shareUrl}
|
||||
<div class="detail-row">
|
||||
<span class="detail-label">Link</span>
|
||||
<span class="detail-label">{$_('calendar.event_modal.label_share_link')}</span>
|
||||
<div class="detail-content">
|
||||
<SharedLinkControls
|
||||
token={event.unlistedToken}
|
||||
|
|
@ -347,14 +359,22 @@
|
|||
<!-- Metadata -->
|
||||
<div class="detail-meta-row">
|
||||
<span
|
||||
>Erstellt: {format(new Date(event.createdAt), 'dd. MMM yyyy', {
|
||||
>{$_('calendar.event_modal.created_at', {
|
||||
values: {
|
||||
date: format(new Date(event.createdAt), 'dd. MMM yyyy', {
|
||||
locale: getDateFnsLocale(),
|
||||
}),
|
||||
},
|
||||
})}</span
|
||||
>
|
||||
{#if event.updatedAt !== event.createdAt}
|
||||
<span
|
||||
>· Bearbeitet: {format(new Date(event.updatedAt), 'dd. MMM yyyy', {
|
||||
>{$_('calendar.event_modal.updated_at', {
|
||||
values: {
|
||||
date: format(new Date(event.updatedAt), 'dd. MMM yyyy', {
|
||||
locale: getDateFnsLocale(),
|
||||
}),
|
||||
},
|
||||
})}</span
|
||||
>
|
||||
{/if}
|
||||
|
|
@ -377,14 +397,14 @@
|
|||
<!-- svelte-ignore a11y_interactive_supports_focus -->
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<div class="delete-dialog" role="dialog" aria-modal="true" onclick={(e) => e.stopPropagation()}>
|
||||
<h3 class="delete-title">Wiederkehrenden Termin bearbeiten</h3>
|
||||
<p class="delete-text">Möchtest du nur diesen Termin oder alle zukünftigen bearbeiten?</p>
|
||||
<h3 class="delete-title">{$_('calendar.event_modal.recur_edit_title')}</h3>
|
||||
<p class="delete-text">{$_('calendar.event_modal.recur_edit_text')}</p>
|
||||
<div class="delete-actions">
|
||||
<button class="btn btn-outline" onclick={() => startEdit('single')}>
|
||||
Nur diesen Termin
|
||||
{$_('calendar.event_modal.recur_edit_only_this')}
|
||||
</button>
|
||||
<button class="btn btn-primary-full" onclick={() => startEdit('all')}>
|
||||
Alle zukünftigen Termine
|
||||
{$_('calendar.event_modal.recur_edit_all_future')}
|
||||
</button>
|
||||
<button class="btn btn-ghost" onclick={() => (showEditOptions = false)}>
|
||||
{$_('common.cancel')}
|
||||
|
|
@ -405,14 +425,14 @@
|
|||
>
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<div class="delete-dialog" role="dialog" aria-modal="true" onclick={(e) => e.stopPropagation()}>
|
||||
<h3 class="delete-title">Wiederkehrenden Termin löschen</h3>
|
||||
<p class="delete-text">Möchtest du nur diesen Termin oder die gesamte Serie löschen?</p>
|
||||
<h3 class="delete-title">{$_('calendar.event_modal.recur_delete_title')}</h3>
|
||||
<p class="delete-text">{$_('calendar.event_modal.recur_delete_text')}</p>
|
||||
<div class="delete-actions">
|
||||
<button class="btn btn-outline" onclick={() => handleDelete('this')}>
|
||||
Nur diesen Termin
|
||||
{$_('calendar.event_modal.recur_delete_only_this')}
|
||||
</button>
|
||||
<button class="btn btn-destructive" onclick={() => handleDelete('all')}>
|
||||
Alle Termine der Serie
|
||||
{$_('calendar.event_modal.recur_delete_all')}
|
||||
</button>
|
||||
<button class="btn btn-ghost" onclick={() => (showDeleteOptions = false)}>
|
||||
{$_('common.cancel')}
|
||||
|
|
|
|||
|
|
@ -112,14 +112,14 @@
|
|||
|
||||
// Recurrence options
|
||||
const CUSTOM_VALUE = '__custom__';
|
||||
const recurrenceOptions = [
|
||||
{ value: '', label: 'Keine Wiederholung' },
|
||||
{ value: 'FREQ=DAILY', label: 'Täglich' },
|
||||
{ value: 'FREQ=WEEKLY', label: 'Wöchentlich' },
|
||||
{ value: 'FREQ=MONTHLY', label: 'Monatlich' },
|
||||
{ value: 'FREQ=YEARLY', label: 'Jährlich' },
|
||||
{ value: CUSTOM_VALUE, label: 'Benutzerdefiniert...' },
|
||||
];
|
||||
const recurrenceOptions = $derived([
|
||||
{ value: '', label: $_('calendar.recurrence.none') },
|
||||
{ value: 'FREQ=DAILY', label: $_('calendar.recurrence.daily') },
|
||||
{ value: 'FREQ=WEEKLY', label: $_('calendar.recurrence.weekly') },
|
||||
{ value: 'FREQ=MONTHLY', label: $_('calendar.recurrence.monthly') },
|
||||
{ value: 'FREQ=YEARLY', label: $_('calendar.recurrence.yearly') },
|
||||
{ value: CUSTOM_VALUE, label: $_('calendar.recurrence.custom') },
|
||||
]);
|
||||
|
||||
let showCustomBuilder = $state(false);
|
||||
|
||||
|
|
@ -156,30 +156,32 @@
|
|||
.map((p) => p.split('='))
|
||||
);
|
||||
const freqMap: Record<string, string> = {
|
||||
DAILY: 'Täglich',
|
||||
WEEKLY: 'Wöchentlich',
|
||||
MONTHLY: 'Monatlich',
|
||||
YEARLY: 'Jährlich',
|
||||
DAILY: $_('calendar.recurrence.daily'),
|
||||
WEEKLY: $_('calendar.recurrence.weekly'),
|
||||
MONTHLY: $_('calendar.recurrence.monthly'),
|
||||
YEARLY: $_('calendar.recurrence.yearly'),
|
||||
};
|
||||
let text = freqMap[parts.FREQ] ?? 'Wiederkehrend';
|
||||
let text = freqMap[parts.FREQ] ?? $_('calendar.recurrence.recurring_fallback');
|
||||
if (parts.INTERVAL && parseInt(parts.INTERVAL) > 1) {
|
||||
const unitMap: Record<string, string> = {
|
||||
DAILY: 'Tage',
|
||||
WEEKLY: 'Wochen',
|
||||
MONTHLY: 'Monate',
|
||||
YEARLY: 'Jahre',
|
||||
DAILY: $_('calendar.recurrence.unit_days'),
|
||||
WEEKLY: $_('calendar.recurrence.unit_weeks'),
|
||||
MONTHLY: $_('calendar.recurrence.unit_months'),
|
||||
YEARLY: $_('calendar.recurrence.unit_years'),
|
||||
};
|
||||
text = `Alle ${parts.INTERVAL} ${unitMap[parts.FREQ] ?? ''}`;
|
||||
text = $_('calendar.recurrence.every_n_unit', {
|
||||
values: { n: parts.INTERVAL, unit: unitMap[parts.FREQ] ?? '' },
|
||||
});
|
||||
}
|
||||
if (parts.BYDAY) {
|
||||
const dayMap: Record<string, string> = {
|
||||
MO: 'Mo',
|
||||
TU: 'Di',
|
||||
WE: 'Mi',
|
||||
TH: 'Do',
|
||||
FR: 'Fr',
|
||||
SA: 'Sa',
|
||||
SU: 'So',
|
||||
MO: $_('calendar.weekday_short.mon'),
|
||||
TU: $_('calendar.weekday_short.tue'),
|
||||
WE: $_('calendar.weekday_short.wed'),
|
||||
TH: $_('calendar.weekday_short.thu'),
|
||||
FR: $_('calendar.weekday_short.fri'),
|
||||
SA: $_('calendar.weekday_short.sat'),
|
||||
SU: $_('calendar.weekday_short.sun'),
|
||||
};
|
||||
text += ` (${parts.BYDAY.split(',')
|
||||
.map((d: string) => dayMap[d] || d)
|
||||
|
|
@ -192,23 +194,25 @@
|
|||
<form
|
||||
onsubmit={handleSubmit}
|
||||
class="event-form"
|
||||
aria-label={mode === 'create' ? 'Termin erstellen' : 'Termin bearbeiten'}
|
||||
aria-label={mode === 'create'
|
||||
? $_('calendar.event_form.aria_create')
|
||||
: $_('calendar.event_form.aria_edit')}
|
||||
>
|
||||
<div class="field">
|
||||
<label for="title" class="label">Titel *</label>
|
||||
<label for="title" class="label">{$_('calendar.event_form.label_title_required')}</label>
|
||||
<input
|
||||
type="text"
|
||||
id="title"
|
||||
class="input"
|
||||
bind:value={title}
|
||||
placeholder="Terminname eingeben"
|
||||
placeholder={$_('calendar.event_form.placeholder_title')}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
{#if mode === 'create' && calendarOptions.length > 1}
|
||||
<div class="field">
|
||||
<label for="calendar" class="label">Kalender</label>
|
||||
<label for="calendar" class="label">{$_('calendar.event.calendar')}</label>
|
||||
<select id="calendar" class="input" bind:value={calendarId}>
|
||||
{#each calendarOptions as cal}
|
||||
<option value={cal.id}>{cal.name}</option>
|
||||
|
|
@ -220,18 +224,18 @@
|
|||
<div class="field">
|
||||
<label class="checkbox-label">
|
||||
<input type="checkbox" bind:checked={isAllDay} class="checkbox" />
|
||||
<span>Ganztägig</span>
|
||||
<span>{$_('calendar.event.allDay')}</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="field-row">
|
||||
<div class="field flex-1">
|
||||
<label for="startDate" class="label">Beginn</label>
|
||||
<label for="startDate" class="label">{$_('calendar.event.start')}</label>
|
||||
<input type="date" id="startDate" class="input" bind:value={startDate} required />
|
||||
</div>
|
||||
{#if !isAllDay}
|
||||
<div class="field flex-1">
|
||||
<label for="startTime" class="label">Uhrzeit</label>
|
||||
<label for="startTime" class="label">{$_('calendar.event_form.label_time')}</label>
|
||||
<input type="time" id="startTime" class="input" bind:value={startTime} required />
|
||||
</div>
|
||||
{/if}
|
||||
|
|
@ -239,12 +243,12 @@
|
|||
|
||||
<div class="field-row">
|
||||
<div class="field flex-1">
|
||||
<label for="endDate" class="label">Ende</label>
|
||||
<label for="endDate" class="label">{$_('calendar.event.end')}</label>
|
||||
<input type="date" id="endDate" class="input" bind:value={endDate} required />
|
||||
</div>
|
||||
{#if !isAllDay}
|
||||
<div class="field flex-1">
|
||||
<label for="endTime" class="label">Uhrzeit</label>
|
||||
<label for="endTime" class="label">{$_('calendar.event_form.label_time')}</label>
|
||||
<input type="time" id="endTime" class="input" bind:value={endTime} required />
|
||||
</div>
|
||||
{/if}
|
||||
|
|
@ -257,7 +261,7 @@
|
|||
{/if}
|
||||
|
||||
<div class="field">
|
||||
<label for="recurrence" class="label">Wiederholung</label>
|
||||
<label for="recurrence" class="label">{$_('calendar.event_form.label_recurrence')}</label>
|
||||
<select id="recurrence" class="input" value={selectValue} onchange={handleRecurrenceChange}>
|
||||
{#each recurrenceOptions as opt}
|
||||
<option value={opt.value}>{opt.label}</option>
|
||||
|
|
@ -265,7 +269,9 @@
|
|||
</select>
|
||||
{#if isCustomRule && !showCustomBuilder}
|
||||
<button type="button" class="custom-rule-preview" onclick={() => (showCustomBuilder = true)}>
|
||||
{formatCustomPreview(recurrenceRule)} — Bearbeiten
|
||||
{$_('calendar.event_form.recur_edit_suffix', {
|
||||
values: { preview: formatCustomPreview(recurrenceRule) },
|
||||
})}
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
|
|
@ -279,29 +285,29 @@
|
|||
{/if}
|
||||
|
||||
<div class="field">
|
||||
<label for="location" class="label">Ort</label>
|
||||
<label for="location" class="label">{$_('calendar.event.location')}</label>
|
||||
<input
|
||||
type="text"
|
||||
id="location"
|
||||
class="input"
|
||||
bind:value={location}
|
||||
placeholder="Ort eingeben..."
|
||||
placeholder={$_('calendar.event_form.placeholder_location')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label for="description" class="label">Beschreibung</label>
|
||||
<label for="description" class="label">{$_('calendar.event.description')}</label>
|
||||
<textarea
|
||||
id="description"
|
||||
class="input textarea"
|
||||
rows="3"
|
||||
bind:value={description}
|
||||
placeholder="Beschreibung hinzufügen"
|
||||
placeholder={$_('calendar.event_form.placeholder_description')}
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<span class="label">Tags</span>
|
||||
<span class="label">{$_('calendar.event_form.label_tags')}</span>
|
||||
<TagField
|
||||
tags={allTags.value}
|
||||
selectedIds={selectedTagIds}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
<script lang="ts">
|
||||
import { getDateFnsLocale } from '$lib/i18n/format';
|
||||
import { _ } from 'svelte-i18n';
|
||||
import {
|
||||
format,
|
||||
startOfMonth,
|
||||
|
|
@ -32,7 +33,15 @@
|
|||
return eachDayOfInterval({ start: calendarStart, end: calendarEnd });
|
||||
});
|
||||
|
||||
const weekDays = ['Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa', 'So'];
|
||||
const weekDays = $derived([
|
||||
$_('calendar.weekday_short.mon'),
|
||||
$_('calendar.weekday_short.tue'),
|
||||
$_('calendar.weekday_short.wed'),
|
||||
$_('calendar.weekday_short.thu'),
|
||||
$_('calendar.weekday_short.fri'),
|
||||
$_('calendar.weekday_short.sat'),
|
||||
$_('calendar.weekday_short.sun'),
|
||||
]);
|
||||
</script>
|
||||
|
||||
<div class="mini-calendar">
|
||||
|
|
@ -40,7 +49,7 @@
|
|||
<button
|
||||
class="nav-btn"
|
||||
onclick={() => (currentMonth = subMonths(currentMonth, 1))}
|
||||
aria-label="Vorheriger Monat"
|
||||
aria-label={$_('calendar.mini_cal.prev_aria')}
|
||||
>
|
||||
<CaretLeft size={16} />
|
||||
</button>
|
||||
|
|
@ -50,7 +59,7 @@
|
|||
<button
|
||||
class="nav-btn"
|
||||
onclick={() => (currentMonth = addMonths(currentMonth, 1))}
|
||||
aria-label="Nächster Monat"
|
||||
aria-label={$_('calendar.mini_cal.next_aria')}
|
||||
>
|
||||
<CaretRight size={16} />
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -72,14 +72,14 @@
|
|||
|
||||
const calendarColor = $derived(getCalendarColor(calendarsCtx.value, calendarId || ''));
|
||||
|
||||
const RECURRENCE_OPTIONS = [
|
||||
{ value: '', label: 'Keine Wiederholung' },
|
||||
{ value: 'FREQ=DAILY', label: 'Täglich' },
|
||||
{ value: 'FREQ=WEEKLY', label: 'Wöchentlich' },
|
||||
{ value: 'FREQ=WEEKLY;INTERVAL=2', label: 'Alle 2 Wochen' },
|
||||
{ value: 'FREQ=MONTHLY', label: 'Monatlich' },
|
||||
{ value: 'FREQ=YEARLY', label: 'Jährlich' },
|
||||
];
|
||||
const RECURRENCE_OPTIONS = $derived([
|
||||
{ value: '', label: $_('calendar.recurrence.none') },
|
||||
{ value: 'FREQ=DAILY', label: $_('calendar.recurrence.daily') },
|
||||
{ value: 'FREQ=WEEKLY', label: $_('calendar.recurrence.weekly') },
|
||||
{ value: 'FREQ=WEEKLY;INTERVAL=2', label: $_('calendar.recurrence.every_2_weeks') },
|
||||
{ value: 'FREQ=MONTHLY', label: $_('calendar.recurrence.monthly') },
|
||||
{ value: 'FREQ=YEARLY', label: $_('calendar.recurrence.yearly') },
|
||||
]);
|
||||
|
||||
function handleSubmit(e: Event) {
|
||||
e.preventDefault();
|
||||
|
|
@ -143,7 +143,7 @@
|
|||
class="popover"
|
||||
style="top: {popoverPos.top}px; left: {popoverPos.left}px;"
|
||||
role="dialog"
|
||||
aria-label="Termin erstellen"
|
||||
aria-label={$_('calendar.quick_event.aria_dialog')}
|
||||
>
|
||||
<!-- Color accent bar -->
|
||||
<div class="accent-bar" style="background-color: {calendarColor};"></div>
|
||||
|
|
@ -151,7 +151,7 @@
|
|||
<form onsubmit={handleSubmit}>
|
||||
<!-- Header -->
|
||||
<div class="popover-header">
|
||||
<span class="header-title">Neuer Termin</span>
|
||||
<span class="header-title">{$_('calendar.quick_event.header_new')}</span>
|
||||
<button type="button" class="close-btn" onclick={onClose} aria-label={$_('common.close')}>
|
||||
<X size={16} />
|
||||
</button>
|
||||
|
|
@ -164,7 +164,7 @@
|
|||
bind:this={titleInput}
|
||||
bind:value={title}
|
||||
type="text"
|
||||
placeholder="Titel hinzufügen"
|
||||
placeholder={$_('calendar.quick_event.placeholder_title')}
|
||||
class="title-input"
|
||||
required
|
||||
/>
|
||||
|
|
@ -177,7 +177,8 @@
|
|||
class:active={blockType === 'event'}
|
||||
onclick={() => (blockType = 'event')}
|
||||
>
|
||||
<CalendarBlank size={12} /> Termin
|
||||
<CalendarBlank size={12} />
|
||||
{$_('calendar.quick_event.type_event')}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
|
|
@ -185,7 +186,8 @@
|
|||
class:active={blockType === 'timeEntry'}
|
||||
onclick={() => (blockType = 'timeEntry')}
|
||||
>
|
||||
<Timer size={12} /> Zeiterfassung
|
||||
<Timer size={12} />
|
||||
{$_('calendar.quick_event.type_time_entry')}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
|
|
@ -193,7 +195,8 @@
|
|||
class:active={blockType === 'habit'}
|
||||
onclick={() => (blockType = 'habit')}
|
||||
>
|
||||
<Heart size={12} /> Habit
|
||||
<Heart size={12} />
|
||||
{$_('calendar.quick_event.type_habit')}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
|
@ -217,7 +220,7 @@
|
|||
<!-- All-day toggle -->
|
||||
<label class="form-row clickable">
|
||||
<CalendarBlank size={16} class="row-icon-el" />
|
||||
<span class="row-label">Ganztägig</span>
|
||||
<span class="row-label">{$_('calendar.event.allDay')}</span>
|
||||
<input type="checkbox" bind:checked={isAllDay} class="toggle-cb" />
|
||||
</label>
|
||||
|
||||
|
|
@ -226,14 +229,14 @@
|
|||
<Clock size={16} class="row-icon-el" />
|
||||
<div class="datetime-fields">
|
||||
<div class="dt-group">
|
||||
<span class="dt-label">Beginn</span>
|
||||
<span class="dt-label">{$_('calendar.event.start')}</span>
|
||||
<input type="date" bind:value={startDateStr} class="dt-input" />
|
||||
{#if !isAllDay}
|
||||
<input type="time" bind:value={startTimeStr} class="dt-input time" />
|
||||
{/if}
|
||||
</div>
|
||||
<div class="dt-group">
|
||||
<span class="dt-label">Ende</span>
|
||||
<span class="dt-label">{$_('calendar.event.end')}</span>
|
||||
<input type="date" bind:value={endDateStr} class="dt-input" />
|
||||
{#if !isAllDay}
|
||||
<input type="time" bind:value={endTimeStr} class="dt-input time" />
|
||||
|
|
@ -269,7 +272,12 @@
|
|||
<!-- Location -->
|
||||
<div class="form-row">
|
||||
<MapPin size={16} class="row-icon-el" />
|
||||
<input bind:value={location} type="text" placeholder="Ort hinzufügen" class="field-input" />
|
||||
<input
|
||||
bind:value={location}
|
||||
type="text"
|
||||
placeholder={$_('calendar.quick_event.placeholder_location')}
|
||||
class="field-input"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Description -->
|
||||
|
|
@ -277,7 +285,7 @@
|
|||
<TextAlignLeft size={16} class="row-icon-el" />
|
||||
<textarea
|
||||
bind:value={description}
|
||||
placeholder="Beschreibung"
|
||||
placeholder={$_('calendar.quick_event.placeholder_description')}
|
||||
rows="2"
|
||||
class="field-input field-textarea"
|
||||
></textarea>
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
-->
|
||||
<script lang="ts">
|
||||
import { getDateFnsLocale } from '$lib/i18n/format';
|
||||
import { _ } from 'svelte-i18n';
|
||||
import { db } from '$lib/data/database';
|
||||
import type { LocalTimeBlock } from '$lib/data/time-blocks/types';
|
||||
import { toTimeBlock, findFreeSlots } from '$lib/data/time-blocks/queries';
|
||||
|
|
@ -49,19 +50,19 @@
|
|||
}
|
||||
|
||||
function formatDayLabel(date: Date): string {
|
||||
if (isToday(date)) return 'Heute';
|
||||
if (isTomorrow(date)) return 'Morgen';
|
||||
if (isToday(date)) return $_('calendar.calendar.today');
|
||||
if (isTomorrow(date)) return $_('calendar.calendar.tomorrow');
|
||||
return format(date, 'EEE, d. MMM', { locale: getDateFnsLocale() });
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if loading}
|
||||
<div class="slot-loading">Suche freie Zeiten...</div>
|
||||
<div class="slot-loading">{$_('calendar.slots.loading')}</div>
|
||||
{:else if slots.length === 0}
|
||||
<div class="slot-empty">Keine freien Slots gefunden</div>
|
||||
<div class="slot-empty">{$_('calendar.slots.empty')}</div>
|
||||
{:else}
|
||||
<div class="slot-list">
|
||||
<span class="slot-label">Freie Zeiten</span>
|
||||
<span class="slot-label">{$_('calendar.slots.label')}</span>
|
||||
{#each slots as slot}
|
||||
<button class="slot-btn" onclick={() => onSelect(slot.start, slot.end)}>
|
||||
<CalendarBlank size={12} />
|
||||
|
|
@ -69,7 +70,9 @@
|
|||
<span class="slot-time">
|
||||
{format(slot.start, 'HH:mm')}–{format(slot.end, 'HH:mm')}
|
||||
</span>
|
||||
<span class="slot-duration">{slot.durationMinutes}min</span>
|
||||
<span class="slot-duration"
|
||||
>{$_('calendar.slots.duration_suffix', { values: { n: slot.durationMinutes } })}</span
|
||||
>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
All fields are always editable. Changes auto-save on blur.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { _ } from 'svelte-i18n';
|
||||
import { formatDate } from '$lib/i18n/format';
|
||||
import { db } from '$lib/data/database';
|
||||
import { decryptRecord } from '$lib/data/crypto';
|
||||
|
|
@ -86,7 +87,8 @@
|
|||
const startTime = editAllDay ? `${editDate}T00:00:00` : `${editDate}T${editStartTime}:00`;
|
||||
const endTime = editAllDay ? `${editDate}T23:59:59` : `${editDate}T${editEndTime}:00`;
|
||||
await eventsStore.updateEvent(eventId, {
|
||||
title: editTitle.trim() || detail.entity?.title || 'Untitled',
|
||||
title:
|
||||
editTitle.trim() || detail.entity?.title || $_('calendar.detail_view.untitled_fallback'),
|
||||
startTime,
|
||||
endTime,
|
||||
isAllDay: editAllDay,
|
||||
|
|
@ -126,8 +128,8 @@
|
|||
const id = eventId;
|
||||
await eventsStore.deleteEvent(id);
|
||||
goBack();
|
||||
toastStore.undo('Termin gelöscht', () => {
|
||||
db.table('events').update(id, { deletedAt: undefined, updatedAt: new Date().toISOString() });
|
||||
toastStore.undo($_('calendar.detail_view.toast_deleted'), () => {
|
||||
db.table('events').update(id, { deletedAt: undefined });
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
|
@ -135,11 +137,11 @@
|
|||
<DetailViewShell
|
||||
entity={detail.entity}
|
||||
loading={detail.loading}
|
||||
notFoundLabel="Termin nicht gefunden"
|
||||
notFoundLabel={$_('calendar.detail_view.not_found')}
|
||||
confirmDelete={detail.confirmDelete}
|
||||
onAskDelete={detail.askDelete}
|
||||
onCancelDelete={detail.cancelDelete}
|
||||
confirmDeleteLabel="Termin wirklich löschen?"
|
||||
confirmDeleteLabel={$_('calendar.detail_view.confirm_delete')}
|
||||
onConfirmDelete={deleteEvent}
|
||||
>
|
||||
{#snippet body(event)}
|
||||
|
|
@ -148,12 +150,12 @@
|
|||
bind:value={editTitle}
|
||||
onfocus={detail.focus}
|
||||
onblur={saveField}
|
||||
placeholder="Titel..."
|
||||
placeholder={$_('calendar.detail_view.placeholder_title')}
|
||||
/>
|
||||
|
||||
<div class="properties">
|
||||
<div class="prop-row prop-row--labeled">
|
||||
<span class="prop-label">Sichtbarkeit</span>
|
||||
<span class="prop-label">{$_('calendar.detail_view.label_visibility')}</span>
|
||||
<VisibilityPicker level={event.visibility ?? 'private'} onChange={handleVisibilityChange} />
|
||||
</div>
|
||||
|
||||
|
|
@ -201,7 +203,7 @@
|
|||
{/if}
|
||||
<label class="allday-label">
|
||||
<input type="checkbox" bind:checked={editAllDay} onchange={handleAllDayChange} />
|
||||
Ganztägig
|
||||
{$_('calendar.detail_view.label_allday')}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -213,7 +215,7 @@
|
|||
bind:value={editLocation}
|
||||
onfocus={detail.focus}
|
||||
onblur={saveField}
|
||||
placeholder="Ort hinzufügen..."
|
||||
placeholder={$_('calendar.detail_view.placeholder_location')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
@ -227,7 +229,7 @@
|
|||
|
||||
{#if eventTags.length > 0}
|
||||
<div class="section">
|
||||
<span class="section-label">Tags</span>
|
||||
<span class="section-label">{$_('calendar.detail_view.section_tags')}</span>
|
||||
<div class="tags-list">
|
||||
{#each eventTags as tag (tag.id)}
|
||||
<button
|
||||
|
|
@ -247,23 +249,31 @@
|
|||
<LinkedItems recordRef={{ app: 'calendar', collection: 'events', id: eventId }} {navigate} />
|
||||
|
||||
<div class="section">
|
||||
<span class="section-label">Beschreibung</span>
|
||||
<span class="section-label">{$_('calendar.detail_view.section_description')}</span>
|
||||
<textarea
|
||||
class="description-input"
|
||||
bind:value={editDescription}
|
||||
onfocus={detail.focus}
|
||||
onblur={saveField}
|
||||
placeholder="Beschreibung hinzufügen..."
|
||||
placeholder={$_('calendar.detail_view.placeholder_description')}
|
||||
rows={3}
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
<div class="meta">
|
||||
{#if event.createdAt}
|
||||
<span>Erstellt: {formatDate(new Date(event.createdAt))}</span>
|
||||
<span
|
||||
>{$_('calendar.detail_view.meta_created', {
|
||||
values: { date: formatDate(new Date(event.createdAt)) },
|
||||
})}</span
|
||||
>
|
||||
{/if}
|
||||
{#if event.updatedAt}
|
||||
<span>Bearbeitet: {formatDate(new Date(event.updatedAt))}</span>
|
||||
<span
|
||||
>{$_('calendar.detail_view.meta_updated', {
|
||||
values: { date: formatDate(new Date(event.updatedAt)) },
|
||||
})}</span
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
{/snippet}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
<script lang="ts">
|
||||
import { _ } from 'svelte-i18n';
|
||||
import { getContext, onMount } from 'svelte';
|
||||
import { dropTarget } from '@mana/shared-ui/dnd';
|
||||
import type { DragPayload, TagDragData } from '@mana/shared-ui/dnd';
|
||||
|
|
@ -215,7 +216,7 @@
|
|||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Kalender - Mana</title>
|
||||
<title>{$_('calendar.detail_route.doc_title')}</title>
|
||||
</svelte:head>
|
||||
|
||||
<RoutePage appId="calendar">
|
||||
|
|
@ -272,7 +273,7 @@
|
|||
role="presentation"
|
||||
>
|
||||
<div class="modal-container" role="dialog" aria-modal="true">
|
||||
<h2 class="modal-title">Neuer Termin</h2>
|
||||
<h2 class="modal-title">{$_('calendar.detail_route.create_modal_title')}</h2>
|
||||
<EventForm
|
||||
mode="create"
|
||||
initialStartTime={createStartTime}
|
||||
|
|
|
|||
|
|
@ -44,13 +44,13 @@
|
|||
}
|
||||
|
||||
async function handleDelete(id: string) {
|
||||
if (!confirm('Kalender wirklich löschen? Alle zugehörigen Termine gehen verloren.')) return;
|
||||
if (!confirm($_('calendar.calendars_route.confirm_delete'))) return;
|
||||
await calendarsStore.deleteCalendar(id);
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Kalender verwalten - Mana</title>
|
||||
<title>{$_('calendar.calendars_route.doc_title')}</title>
|
||||
</svelte:head>
|
||||
|
||||
<RoutePage appId="calendar" backHref="/calendar">
|
||||
|
|
@ -60,17 +60,19 @@
|
|||
class="mb-4 inline-flex items-center gap-1 text-sm text-muted-foreground hover:text-foreground transition-colors"
|
||||
>
|
||||
<CaretLeft size={16} />
|
||||
Zurück zum Kalender
|
||||
{$_('calendar.calendars_route.back_to_calendar')}
|
||||
</a>
|
||||
|
||||
<div class="mb-6 flex items-center justify-between">
|
||||
<h1 class="text-2xl font-bold text-foreground">Meine Kalender</h1>
|
||||
<h1 class="text-2xl font-bold text-foreground">
|
||||
{$_('calendar.calendars_route.page_title')}
|
||||
</h1>
|
||||
<button
|
||||
onclick={() => (showCreateForm = !showCreateForm)}
|
||||
class="flex items-center gap-1.5 rounded-lg bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground hover:bg-primary/90 transition-colors"
|
||||
>
|
||||
<Plus size={16} />
|
||||
Neuer Kalender
|
||||
{$_('calendar.calendars_route.new_calendar')}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
|
@ -85,25 +87,28 @@
|
|||
class="space-y-3"
|
||||
>
|
||||
<div>
|
||||
<label for="cal-name" class="mb-1 block text-sm font-medium text-foreground">Name</label
|
||||
<label for="cal-name" class="mb-1 block text-sm font-medium text-foreground"
|
||||
>{$_('calendar.calendars_route.label_name')}</label
|
||||
>
|
||||
<input
|
||||
id="cal-name"
|
||||
type="text"
|
||||
bind:value={newName}
|
||||
placeholder="z.B. Arbeit, Sport, Familie..."
|
||||
placeholder={$_('calendar.calendars_route.placeholder_name')}
|
||||
required
|
||||
class="w-full rounded-lg border border-border bg-background px-3 py-2 text-foreground placeholder:text-muted-foreground focus:border-primary focus:ring-1 focus:ring-primary"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<span class="mb-2 block text-sm font-medium text-foreground">Farbe</span>
|
||||
<span class="mb-2 block text-sm font-medium text-foreground"
|
||||
>{$_('calendar.calendars_route.label_color')}</span
|
||||
>
|
||||
<div class="flex gap-2">
|
||||
{#each PRESET_COLORS as color}
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Farbe wählen"
|
||||
aria-label={$_('calendar.calendars_route.aria_pick_color')}
|
||||
onclick={() => (newColor = color)}
|
||||
class="h-8 w-8 rounded-full border-2 transition-transform hover:scale-110 {newColor ===
|
||||
color
|
||||
|
|
@ -121,14 +126,14 @@
|
|||
onclick={() => (showCreateForm = false)}
|
||||
class="flex-1 rounded-lg border border-border px-3 py-2 text-sm font-medium text-foreground hover:bg-muted transition-colors"
|
||||
>
|
||||
Abbrechen
|
||||
{$_('common.cancel')}
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={!newName.trim()}
|
||||
class="flex-1 rounded-lg bg-primary px-3 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 transition-colors disabled:opacity-50"
|
||||
>
|
||||
Erstellen
|
||||
{$_('calendar.calendars_route.submit_create')}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
|
@ -138,7 +143,9 @@
|
|||
<!-- Calendar List -->
|
||||
<div class="space-y-2">
|
||||
{#if calendarsCtx.value.length === 0}
|
||||
<div class="py-12 text-center text-muted-foreground">Noch keine Kalender vorhanden.</div>
|
||||
<div class="py-12 text-center text-muted-foreground">
|
||||
{$_('calendar.calendars_route.empty')}
|
||||
</div>
|
||||
{:else}
|
||||
{#each calendarsCtx.value as cal (cal.id)}
|
||||
<div class="flex items-center gap-3 rounded-lg border border-border bg-card p-3">
|
||||
|
|
@ -150,7 +157,9 @@
|
|||
<div class="font-medium text-foreground">
|
||||
{cal.name}
|
||||
{#if cal.isDefault}
|
||||
<span class="ml-1 text-xs text-primary">(Standard)</span>
|
||||
<span class="ml-1 text-xs text-primary"
|
||||
>{$_('calendar.calendars_route.badge_default')}</span
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -158,7 +167,9 @@
|
|||
<button
|
||||
onclick={() => handleToggleVisibility(cal.id)}
|
||||
class="rounded-lg p-1.5 text-muted-foreground hover:text-foreground transition-colors"
|
||||
title={cal.isVisible ? 'Ausblenden' : 'Einblenden'}
|
||||
title={cal.isVisible
|
||||
? $_('calendar.calendars_route.aria_hide')
|
||||
: $_('calendar.calendars_route.aria_show')}
|
||||
>
|
||||
{#if cal.isVisible}
|
||||
<Eye size={16} />
|
||||
|
|
@ -170,7 +181,7 @@
|
|||
<button
|
||||
onclick={() => handleSetDefault(cal.id)}
|
||||
class="rounded-lg p-1.5 text-muted-foreground hover:text-amber-500 transition-colors"
|
||||
title="Als Standard setzen"
|
||||
title={$_('calendar.calendars_route.aria_set_default')}
|
||||
>
|
||||
<Star size={16} />
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -66,7 +66,11 @@
|
|||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>{event?.title ?? 'Termin'} - Kalender - Mana</title>
|
||||
<title
|
||||
>{$_('calendar.detail_route.event_doc_title', {
|
||||
values: { title: event?.title ?? $_('calendar.detail_route.event_doc_title_fallback') },
|
||||
})}</title
|
||||
>
|
||||
</svelte:head>
|
||||
|
||||
<RoutePage appId="calendar" backHref="/calendar" title="Event">
|
||||
|
|
@ -77,20 +81,22 @@
|
|||
class="mb-4 flex items-center gap-1 text-sm text-muted-foreground hover:text-foreground transition-colors"
|
||||
>
|
||||
<CaretLeft size={16} />
|
||||
Zurück zum Kalender
|
||||
{$_('calendar.detail_route.back_to_calendar')}
|
||||
</button>
|
||||
|
||||
{#if !event}
|
||||
<div class="py-16 text-center">
|
||||
<p class="text-lg text-muted-foreground">Termin nicht gefunden</p>
|
||||
<p class="text-lg text-muted-foreground">{$_('calendar.detail_route.not_found')}</p>
|
||||
<button onclick={() => goto('/calendar')} class="mt-4 text-sm text-primary hover:underline">
|
||||
Zurück zum Kalender
|
||||
{$_('calendar.detail_route.back_to_calendar')}
|
||||
</button>
|
||||
</div>
|
||||
{:else if isEditing}
|
||||
<!-- Edit Form -->
|
||||
<div class="rounded-xl border border-border bg-card p-6">
|
||||
<h2 class="mb-4 text-xl font-semibold text-foreground">Termin bearbeiten</h2>
|
||||
<h2 class="mb-4 text-xl font-semibold text-foreground">
|
||||
{$_('calendar.detail_route.edit_title')}
|
||||
</h2>
|
||||
|
||||
<form
|
||||
onsubmit={(e) => {
|
||||
|
|
@ -101,7 +107,7 @@
|
|||
>
|
||||
<div>
|
||||
<label for="edit-title" class="mb-1 block text-sm font-medium text-foreground"
|
||||
>Titel</label
|
||||
>{$_('calendar.detail_route.label_title')}</label
|
||||
>
|
||||
<input
|
||||
id="edit-title"
|
||||
|
|
@ -114,7 +120,7 @@
|
|||
|
||||
<div>
|
||||
<label for="edit-desc" class="mb-1 block text-sm font-medium text-foreground"
|
||||
>Beschreibung</label
|
||||
>{$_('calendar.detail_route.label_description')}</label
|
||||
>
|
||||
<textarea
|
||||
id="edit-desc"
|
||||
|
|
@ -126,7 +132,7 @@
|
|||
|
||||
<div>
|
||||
<label for="edit-date" class="mb-1 block text-sm font-medium text-foreground"
|
||||
>Datum</label
|
||||
>{$_('calendar.detail_route.label_date')}</label
|
||||
>
|
||||
<input
|
||||
id="edit-date"
|
||||
|
|
@ -139,14 +145,14 @@
|
|||
|
||||
<label class="flex items-center gap-2 text-sm text-foreground">
|
||||
<input type="checkbox" bind:checked={editAllDay} class="rounded" />
|
||||
Ganztägig
|
||||
{$_('calendar.event.allDay')}
|
||||
</label>
|
||||
|
||||
{#if !editAllDay}
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<label for="edit-start" class="mb-1 block text-sm font-medium text-foreground"
|
||||
>Von</label
|
||||
>{$_('calendar.detail_route.label_from')}</label
|
||||
>
|
||||
<input
|
||||
id="edit-start"
|
||||
|
|
@ -157,7 +163,7 @@
|
|||
</div>
|
||||
<div>
|
||||
<label for="edit-end" class="mb-1 block text-sm font-medium text-foreground"
|
||||
>Bis</label
|
||||
>{$_('calendar.detail_route.label_to')}</label
|
||||
>
|
||||
<input
|
||||
id="edit-end"
|
||||
|
|
@ -171,13 +177,13 @@
|
|||
|
||||
<div>
|
||||
<label for="edit-location" class="mb-1 block text-sm font-medium text-foreground"
|
||||
>Ort</label
|
||||
>{$_('calendar.detail_route.label_location')}</label
|
||||
>
|
||||
<input
|
||||
id="edit-location"
|
||||
type="text"
|
||||
bind:value={editLocation}
|
||||
placeholder="Ort eingeben..."
|
||||
placeholder={$_('calendar.detail_route.placeholder_location')}
|
||||
class="w-full rounded-lg border border-border bg-background px-3 py-2 text-foreground placeholder:text-muted-foreground focus:border-primary focus:ring-1 focus:ring-primary"
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -188,13 +194,13 @@
|
|||
onclick={() => (isEditing = false)}
|
||||
class="flex-1 rounded-lg border border-border px-4 py-2 text-sm font-medium text-foreground hover:bg-muted transition-colors"
|
||||
>
|
||||
Abbrechen
|
||||
{$_('calendar.detail_route.submit_cancel')}
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
class="flex-1 rounded-lg bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 transition-colors"
|
||||
>
|
||||
Speichern
|
||||
{$_('calendar.detail_route.submit_save')}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
|
@ -245,13 +251,14 @@
|
|||
})}
|
||||
</div>
|
||||
{#if event.isAllDay}
|
||||
<div class="text-sm text-muted-foreground">Ganztägig</div>
|
||||
<div class="text-sm text-muted-foreground">{$_('calendar.event.allDay')}</div>
|
||||
{:else}
|
||||
<div class="text-sm text-muted-foreground">
|
||||
{format(new Date(event.startTime), 'HH:mm')} – {format(
|
||||
new Date(event.endTime),
|
||||
'HH:mm'
|
||||
)} Uhr
|
||||
)}
|
||||
{$_('calendar.detail_route.time_suffix_uhr')}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -74,19 +74,6 @@
|
|||
"apps/mana/apps/web/src/lib/modules/broadcast/views/DetailView.svelte": 9,
|
||||
"apps/mana/apps/web/src/lib/modules/broadcast/widgets/BroadcastsWidget.svelte": 3,
|
||||
"apps/mana/apps/web/src/lib/modules/calc/ListView.svelte": 1,
|
||||
"apps/mana/apps/web/src/lib/modules/calendar/components/AgendaView.svelte": 4,
|
||||
"apps/mana/apps/web/src/lib/modules/calendar/components/CalendarHeader.svelte": 2,
|
||||
"apps/mana/apps/web/src/lib/modules/calendar/components/CustomRecurrenceBuilder.svelte": 8,
|
||||
"apps/mana/apps/web/src/lib/modules/calendar/components/DateStrip.svelte": 1,
|
||||
"apps/mana/apps/web/src/lib/modules/calendar/components/EventCard.svelte": 5,
|
||||
"apps/mana/apps/web/src/lib/modules/calendar/components/EventDetailModal.svelte": 4,
|
||||
"apps/mana/apps/web/src/lib/modules/calendar/components/EventForm.svelte": 10,
|
||||
"apps/mana/apps/web/src/lib/modules/calendar/components/MiniCalendar.svelte": 2,
|
||||
"apps/mana/apps/web/src/lib/modules/calendar/components/QuickEventPopover.svelte": 6,
|
||||
"apps/mana/apps/web/src/lib/modules/calendar/components/SlotSuggestions.svelte": 3,
|
||||
"apps/mana/apps/web/src/lib/modules/calendar/ListView.svelte": 1,
|
||||
"apps/mana/apps/web/src/lib/modules/calendar/SharedEventView.svelte": 2,
|
||||
"apps/mana/apps/web/src/lib/modules/calendar/views/DetailView.svelte": 5,
|
||||
"apps/mana/apps/web/src/lib/modules/cards/components/CreateDeckModal.svelte": 3,
|
||||
"apps/mana/apps/web/src/lib/modules/cards/views/DetailView.svelte": 6,
|
||||
"apps/mana/apps/web/src/lib/modules/chat/ListView.svelte": 1,
|
||||
|
|
@ -295,9 +282,6 @@
|
|||
"apps/mana/apps/web/src/routes/(app)/broadcasts/new/+page.svelte": 1,
|
||||
"apps/mana/apps/web/src/routes/(app)/calc/+page.svelte": 1,
|
||||
"apps/mana/apps/web/src/routes/(app)/calc/standard/+page.svelte": 2,
|
||||
"apps/mana/apps/web/src/routes/(app)/calendar/+page.svelte": 1,
|
||||
"apps/mana/apps/web/src/routes/(app)/calendar/calendars/+page.svelte": 6,
|
||||
"apps/mana/apps/web/src/routes/(app)/calendar/event/[id]/+page.svelte": 6,
|
||||
"apps/mana/apps/web/src/routes/(app)/cards/+page.svelte": 2,
|
||||
"apps/mana/apps/web/src/routes/(app)/cards/decks/[id]/+page.svelte": 6,
|
||||
"apps/mana/apps/web/src/routes/(app)/cards/decks/+page.svelte": 3,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue