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) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-03-24 10:06:41 +01:00
parent 5b77369547
commit d7cef38379
8 changed files with 144 additions and 29 deletions

View file

@ -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",

View file

@ -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",

View file

@ -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",

View file

@ -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",

View file

@ -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",

View file

@ -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<CalendarEvent[]>([]);
@ -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) {

View file

@ -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<ExternalCalendar[]>([]);
@ -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

View file

@ -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<Map<string, CalendarShare[]>>(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;