From d7cef38379717273cc238c549a01d3dce984fd6d Mon Sep 17 00:00:00 2001 From: Till JS Date: Tue, 24 Mar 2026 10:06:41 +0100 Subject: [PATCH] feat(calendar): localize all toast messages with i18n Replace ~20 hardcoded German toast strings in events, shares, and external-calendars stores with svelte-i18n keys (5 languages). Co-Authored-By: Claude Opus 4.6 (1M context) --- .../apps/web/src/lib/i18n/locales/de.json | 23 ++++++++++++++++++- .../apps/web/src/lib/i18n/locales/en.json | 23 ++++++++++++++++++- .../apps/web/src/lib/i18n/locales/es.json | 23 ++++++++++++++++++- .../apps/web/src/lib/i18n/locales/fr.json | 23 ++++++++++++++++++- .../apps/web/src/lib/i18n/locales/it.json | 23 ++++++++++++++++++- .../apps/web/src/lib/stores/events.svelte.ts | 16 +++++++------ .../lib/stores/external-calendars.svelte.ts | 20 ++++++++++------ .../apps/web/src/lib/stores/shares.svelte.ts | 22 ++++++++++-------- 8 files changed, 144 insertions(+), 29 deletions(-) diff --git a/apps/calendar/apps/web/src/lib/i18n/locales/de.json b/apps/calendar/apps/web/src/lib/i18n/locales/de.json index 41c73fd83..da701739a 100644 --- a/apps/calendar/apps/web/src/lib/i18n/locales/de.json +++ b/apps/calendar/apps/web/src/lib/i18n/locales/de.json @@ -94,6 +94,26 @@ "password": "Passwort", "forgotPassword": "Passwort vergessen?" }, + "toast": { + "eventLoadError": "Termine konnten nicht geladen werden", + "eventUpdateError": "Termin konnte nicht aktualisiert werden", + "eventDeleteError": "Termin konnte nicht gelöscht werden", + "eventDeleted": "Termin gelöscht", + "error": "Fehler", + "calendarShared": "Kalender mit {email} geteilt", + "shareLinkCreated": "Freigabe-Link erstellt", + "inviteAccepted": "Einladung angenommen", + "shareRemoved": "Freigabe entfernt", + "shareError": "Freigabe fehlgeschlagen", + "updateError": "Aktualisierung fehlgeschlagen", + "removeError": "Entfernen fehlgeschlagen", + "declineError": "Ablehnung fehlgeschlagen", + "calendarConnected": "{name} verbunden", + "calendarDisconnected": "{name} getrennt", + "syncCompleted": "Synchronisation abgeschlossen", + "connectionError": "Verbindung fehlgeschlagen", + "syncError": "Sync fehlgeschlagen" + }, "common": { "save": "Speichern", "cancel": "Abbrechen", @@ -103,7 +123,8 @@ "close": "Schließen", "search": "Suchen", "error": "Fehler", - "success": "Erfolgreich" + "success": "Erfolgreich", + "calendar": "Kalender" }, "errors": { "loadEvents": "Termine konnten nicht geladen werden", diff --git a/apps/calendar/apps/web/src/lib/i18n/locales/en.json b/apps/calendar/apps/web/src/lib/i18n/locales/en.json index ecf11e8ce..64d4cdaf5 100644 --- a/apps/calendar/apps/web/src/lib/i18n/locales/en.json +++ b/apps/calendar/apps/web/src/lib/i18n/locales/en.json @@ -94,6 +94,26 @@ "password": "Password", "forgotPassword": "Forgot password?" }, + "toast": { + "eventLoadError": "Failed to load events", + "eventUpdateError": "Failed to update event", + "eventDeleteError": "Failed to delete event", + "eventDeleted": "Event deleted", + "error": "Error", + "calendarShared": "Calendar shared with {email}", + "shareLinkCreated": "Share link created", + "inviteAccepted": "Invitation accepted", + "shareRemoved": "Share removed", + "shareError": "Sharing failed", + "updateError": "Update failed", + "removeError": "Remove failed", + "declineError": "Decline failed", + "calendarConnected": "{name} connected", + "calendarDisconnected": "{name} disconnected", + "syncCompleted": "Sync completed", + "connectionError": "Connection failed", + "syncError": "Sync failed" + }, "common": { "save": "Save", "cancel": "Cancel", @@ -103,7 +123,8 @@ "close": "Close", "search": "Search", "error": "Error", - "success": "Success" + "success": "Success", + "calendar": "Calendar" }, "errors": { "loadEvents": "Failed to load events", diff --git a/apps/calendar/apps/web/src/lib/i18n/locales/es.json b/apps/calendar/apps/web/src/lib/i18n/locales/es.json index 803c66120..74f88d420 100644 --- a/apps/calendar/apps/web/src/lib/i18n/locales/es.json +++ b/apps/calendar/apps/web/src/lib/i18n/locales/es.json @@ -87,6 +87,26 @@ "password": "Contraseña", "forgotPassword": "¿Olvidaste tu contraseña?" }, + "toast": { + "eventLoadError": "No se pudieron cargar los eventos", + "eventUpdateError": "No se pudo actualizar el evento", + "eventDeleteError": "No se pudo eliminar el evento", + "eventDeleted": "Evento eliminado", + "error": "Error", + "calendarShared": "Calendario compartido con {email}", + "shareLinkCreated": "Enlace de compartir creado", + "inviteAccepted": "Invitación aceptada", + "shareRemoved": "Compartir eliminado", + "shareError": "Error al compartir", + "updateError": "Error al actualizar", + "removeError": "Error al eliminar", + "declineError": "Error al rechazar", + "calendarConnected": "{name} conectado", + "calendarDisconnected": "{name} desconectado", + "syncCompleted": "Sincronización completada", + "connectionError": "Error de conexión", + "syncError": "Error de sincronización" + }, "common": { "save": "Guardar", "cancel": "Cancelar", @@ -96,7 +116,8 @@ "close": "Cerrar", "search": "Buscar", "error": "Error", - "success": "Éxito" + "success": "Éxito", + "calendar": "Calendario" }, "error": { "notFound": "Página no encontrada", diff --git a/apps/calendar/apps/web/src/lib/i18n/locales/fr.json b/apps/calendar/apps/web/src/lib/i18n/locales/fr.json index dea43a0bc..d4505eaa1 100644 --- a/apps/calendar/apps/web/src/lib/i18n/locales/fr.json +++ b/apps/calendar/apps/web/src/lib/i18n/locales/fr.json @@ -87,6 +87,26 @@ "password": "Mot de passe", "forgotPassword": "Mot de passe oublié?" }, + "toast": { + "eventLoadError": "Impossible de charger les événements", + "eventUpdateError": "Impossible de mettre à jour l'événement", + "eventDeleteError": "Impossible de supprimer l'événement", + "eventDeleted": "Événement supprimé", + "error": "Erreur", + "calendarShared": "Calendrier partagé avec {email}", + "shareLinkCreated": "Lien de partage créé", + "inviteAccepted": "Invitation acceptée", + "shareRemoved": "Partage supprimé", + "shareError": "Échec du partage", + "updateError": "Échec de la mise à jour", + "removeError": "Échec de la suppression", + "declineError": "Échec du refus", + "calendarConnected": "{name} connecté", + "calendarDisconnected": "{name} déconnecté", + "syncCompleted": "Synchronisation terminée", + "connectionError": "Échec de la connexion", + "syncError": "Échec de la synchronisation" + }, "common": { "save": "Enregistrer", "cancel": "Annuler", @@ -96,7 +116,8 @@ "close": "Fermer", "search": "Rechercher", "error": "Erreur", - "success": "Succès" + "success": "Succès", + "calendar": "Calendrier" }, "error": { "notFound": "Page non trouvée", diff --git a/apps/calendar/apps/web/src/lib/i18n/locales/it.json b/apps/calendar/apps/web/src/lib/i18n/locales/it.json index 5fae7ff68..a2fdb415d 100644 --- a/apps/calendar/apps/web/src/lib/i18n/locales/it.json +++ b/apps/calendar/apps/web/src/lib/i18n/locales/it.json @@ -87,6 +87,26 @@ "password": "Password", "forgotPassword": "Password dimenticata?" }, + "toast": { + "eventLoadError": "Impossibile caricare gli eventi", + "eventUpdateError": "Impossibile aggiornare l'evento", + "eventDeleteError": "Impossibile eliminare l'evento", + "eventDeleted": "Evento eliminato", + "error": "Errore", + "calendarShared": "Calendario condiviso con {email}", + "shareLinkCreated": "Link di condivisione creato", + "inviteAccepted": "Invito accettato", + "shareRemoved": "Condivisione rimossa", + "shareError": "Condivisione fallita", + "updateError": "Aggiornamento fallito", + "removeError": "Rimozione fallita", + "declineError": "Rifiuto fallito", + "calendarConnected": "{name} connesso", + "calendarDisconnected": "{name} disconnesso", + "syncCompleted": "Sincronizzazione completata", + "connectionError": "Connessione fallita", + "syncError": "Sincronizzazione fallita" + }, "common": { "save": "Salva", "cancel": "Annulla", @@ -96,7 +116,8 @@ "close": "Chiudi", "search": "Cerca", "error": "Errore", - "success": "Successo" + "success": "Successo", + "calendar": "Calendario" }, "error": { "notFound": "Pagina non trovata", diff --git a/apps/calendar/apps/web/src/lib/stores/events.svelte.ts b/apps/calendar/apps/web/src/lib/stores/events.svelte.ts index b94168578..ce66a859f 100644 --- a/apps/calendar/apps/web/src/lib/stores/events.svelte.ts +++ b/apps/calendar/apps/web/src/lib/stores/events.svelte.ts @@ -9,6 +9,8 @@ import { format, isWithinInterval, isSameDay, differenceInMilliseconds } from 'd import { toDate } from '$lib/utils/eventDateHelpers'; import { toastStore } from '@manacore/shared-ui'; import { CalendarEvents } from '@manacore/shared-utils/analytics'; +import { get } from 'svelte/store'; +import { _ } from 'svelte-i18n'; // State let events = $state([]); @@ -96,7 +98,7 @@ export const eventsStore = { if (result.error) { error = result.error.message; - toastStore.error(`Termine konnten nicht geladen werden: ${result.error.message}`); + toastStore.error(get(_)('toast.eventLoadError') + ': ' + result.error.message); } else { // API returns events array directly (already extracted in api/events.ts) const eventsData = result.data as CalendarEvent[] | null; @@ -182,7 +184,7 @@ export const eventsStore = { const result = await api.updateEvent(id, data); if (result.error) { - toastStore.error(`Termin konnte nicht aktualisiert werden: ${result.error.message}`); + toastStore.error(get(_)('toast.eventUpdateError') + ': ' + result.error.message); } else if (result.data) { events = events.map((e) => (e.id === id ? result.data! : e)); CalendarEvents.eventUpdated(); @@ -206,10 +208,10 @@ export const eventsStore = { if (eventToDelete) { events = [...events, eventToDelete]; } - toastStore.error(`Termin konnte nicht gelöscht werden: ${result.error.message}`); + toastStore.error(get(_)('toast.eventDeleteError') + ': ' + result.error.message); } else { CalendarEvents.eventDeleted(); - toastStore.success('Termin gelöscht'); + toastStore.success(get(_)('toast.eventDeleted')); } return result; @@ -330,13 +332,13 @@ export const eventsStore = { }); if (result.error) { - toastStore.error(`Fehler: ${result.error.message}`); + toastStore.error(get(_)('toast.error') + ': ' + result.error.message); // Refetch to restore state if (loadedRange) { this.fetchEvents(loadedRange.start, loadedRange.end); } } else { - toastStore.success('Termin gelöscht'); + toastStore.success(get(_)('toast.eventDeleted')); } return result; @@ -358,7 +360,7 @@ export const eventsStore = { const result = await api.updateEvent(parentId, data); if (result.error) { - toastStore.error(`Fehler: ${result.error.message}`); + toastStore.error(get(_)('toast.error') + ': ' + result.error.message); } else { // Refetch to regenerate occurrences if (loadedRange) { diff --git a/apps/calendar/apps/web/src/lib/stores/external-calendars.svelte.ts b/apps/calendar/apps/web/src/lib/stores/external-calendars.svelte.ts index e72ffb738..5c0338ba1 100644 --- a/apps/calendar/apps/web/src/lib/stores/external-calendars.svelte.ts +++ b/apps/calendar/apps/web/src/lib/stores/external-calendars.svelte.ts @@ -5,6 +5,8 @@ import type { ExternalCalendar, ConnectExternalCalendarInput } from '@calendar/shared'; import * as api from '$lib/api/sync'; import { toastStore } from '@manacore/shared-ui'; +import { get } from 'svelte/store'; +import { _ } from 'svelte-i18n'; // State let externalCalendars = $state([]); @@ -53,10 +55,10 @@ export const externalCalendarsStore = { const result = await api.connectExternalCalendar(data); if (result.error) { - toastStore.error(`Verbindung fehlgeschlagen: ${result.error.message}`); + toastStore.error(get(_)('toast.connectionError') + ': ' + result.error.message); } else if (result.data) { externalCalendars = [...externalCalendars, result.data]; - toastStore.success(`${data.name} verbunden`); + toastStore.success(get(_)('toast.calendarConnected', { values: { name: data.name } })); } return result; @@ -66,7 +68,7 @@ export const externalCalendarsStore = { const result = await api.updateExternalCalendar(id, data); if (result.error) { - toastStore.error(`Aktualisierung fehlgeschlagen: ${result.error.message}`); + toastStore.error(get(_)('toast.updateError') + ': ' + result.error.message); } else if (result.data) { externalCalendars = getArray().map((c) => (c.id === id ? result.data! : c)); } @@ -79,10 +81,14 @@ export const externalCalendarsStore = { const result = await api.disconnectExternalCalendar(id); if (result.error) { - toastStore.error(`Trennung fehlgeschlagen: ${result.error.message}`); + toastStore.error(get(_)('toast.connectionError') + ': ' + result.error.message); } else { externalCalendars = getArray().filter((c) => c.id !== id); - toastStore.success(`${cal?.name || 'Kalender'} getrennt`); + toastStore.success( + get(_)('toast.calendarDisconnected', { + values: { name: cal?.name || get(_)('common.calendar') }, + }) + ); } return result; @@ -94,13 +100,13 @@ export const externalCalendarsStore = { const result = await api.triggerSync(id); if (result.error) { - toastStore.error(`Sync fehlgeschlagen: ${result.error.message}`); + toastStore.error(get(_)('toast.syncError') + ': ' + result.error.message); // Update last sync error in local state externalCalendars = getArray().map((c) => c.id === id ? { ...c, lastSyncError: result.error!.message } : c ); } else { - toastStore.success('Synchronisation abgeschlossen'); + toastStore.success(get(_)('toast.syncCompleted')); // Update local state with new sync time externalCalendars = getArray().map((c) => c.id === id ? { ...c, lastSyncAt: new Date().toISOString(), lastSyncError: null } : c diff --git a/apps/calendar/apps/web/src/lib/stores/shares.svelte.ts b/apps/calendar/apps/web/src/lib/stores/shares.svelte.ts index 672b66f05..e3a8ff54f 100644 --- a/apps/calendar/apps/web/src/lib/stores/shares.svelte.ts +++ b/apps/calendar/apps/web/src/lib/stores/shares.svelte.ts @@ -5,6 +5,8 @@ import type { CalendarShare, CalendarShareWithDetails } from '@calendar/shared'; import * as api from '$lib/api/shares'; import { toastStore } from '@manacore/shared-ui'; +import { get } from 'svelte/store'; +import { _ } from 'svelte-i18n'; // State let shares = $state>(new Map()); @@ -56,9 +58,9 @@ export const sharesStore = { const result = await api.createShare(calendarId, { calendarId, email, permission }); if (result.error) { - toastStore.error(`Freigabe fehlgeschlagen: ${result.error.message}`); + toastStore.error(get(_)('toast.shareError') + ': ' + result.error.message); } else { - toastStore.success(`Kalender mit ${email} geteilt`); + toastStore.success(get(_)('toast.calendarShared', { values: { email } })); await this.fetchSharesForCalendar(calendarId); } @@ -73,9 +75,9 @@ export const sharesStore = { }); if (result.error) { - toastStore.error(`Link-Erstellung fehlgeschlagen: ${result.error.message}`); + toastStore.error(get(_)('toast.shareError') + ': ' + result.error.message); } else { - toastStore.success('Freigabe-Link erstellt'); + toastStore.success(get(_)('toast.shareLinkCreated')); await this.fetchSharesForCalendar(calendarId); } @@ -86,9 +88,9 @@ export const sharesStore = { const result = await api.acceptShare(shareId); if (result.error) { - toastStore.error(`Annahme fehlgeschlagen: ${result.error.message}`); + toastStore.error(get(_)('toast.shareError') + ': ' + result.error.message); } else { - toastStore.success('Einladung angenommen'); + toastStore.success(get(_)('toast.inviteAccepted')); invitations = invitations.filter((i) => i.id !== shareId); await this.fetchSharedWithMe(); } @@ -100,7 +102,7 @@ export const sharesStore = { const result = await api.declineShare(shareId); if (result.error) { - toastStore.error(`Ablehnung fehlgeschlagen: ${result.error.message}`); + toastStore.error(get(_)('toast.declineError') + ': ' + result.error.message); } else { invitations = invitations.filter((i) => i.id !== shareId); } @@ -112,9 +114,9 @@ export const sharesStore = { const result = await api.deleteShare(calendarId, shareId); if (result.error) { - toastStore.error(`Entfernen fehlgeschlagen: ${result.error.message}`); + toastStore.error(get(_)('toast.removeError') + ': ' + result.error.message); } else { - toastStore.success('Freigabe entfernt'); + toastStore.success(get(_)('toast.shareRemoved')); const current = shares.get(calendarId) || []; shares = new Map(shares).set( calendarId, @@ -129,7 +131,7 @@ export const sharesStore = { const result = await api.updateShare(shareId, { permission }); if (result.error) { - toastStore.error(`Aktualisierung fehlgeschlagen: ${result.error.message}`); + toastStore.error(get(_)('toast.updateError') + ': ' + result.error.message); } return result;