Merge pull request #637 from the-luap/fix/i18n-activity-types-comprehensive

fix(i18n): sweep activity-type translations + Events / API Tokens / Webhooks settings tabs
This commit is contained in:
Paul Nothaft
2026-06-18 21:30:44 +02:00
committed by GitHub
6 changed files with 518 additions and 67 deletions
@@ -183,7 +183,7 @@ export const ApiTokensTab: React.FC = () => {
<tr className="text-left text-neutral-500 dark:text-neutral-400 border-b border-neutral-200 dark:border-neutral-700">
<th className="py-2 pr-3">{t('settings.apiTokens.name', 'Name')}</th>
<th className="py-2 pr-3">{t('settings.apiTokens.scopes', 'Scopes')}</th>
<th className="py-2 pr-3">Preview</th>
<th className="py-2 pr-3">{t('settings.apiTokens.preview', 'Preview')}</th>
<th className="py-2 pr-3">{t('settings.apiTokens.lastUsed', 'Last used')}</th>
<th className="py-2 pr-3">{t('settings.apiTokens.created', 'Created')}</th>
<th className="py-2 pr-3">{t('settings.apiTokens.status', 'Status')}</th>
@@ -228,7 +228,7 @@ export const ApiTokensTab: React.FC = () => {
variant="ghost"
leftIcon={<Trash2 className="w-4 h-4" />}
onClick={() => {
if (confirm(t('settings.apiTokens.confirmRevoke', `Revoke "${token.name}"? Existing integrations using this token will start getting 401.`))) {
if (confirm(t('settings.apiTokens.confirmRevoke', { name: token.name, defaultValue: `Revoke "${token.name}"? Existing integrations using this token will start getting 401.` }))) {
revokeMutation.mutate(token.id);
}
}}
@@ -67,7 +67,7 @@ export const WebhooksTab: React.FC = () => {
try {
parsedFilter = JSON.parse(trimmed);
} catch {
setFilterError('Filter must be valid JSON');
setFilterError(t('settings.webhooks.filterError', 'Filter must be valid JSON'));
throw new Error('Invalid filter JSON');
}
}
@@ -89,7 +89,7 @@ export const WebhooksTab: React.FC = () => {
queryClient.invalidateQueries({ queryKey: ['admin-webhooks'] });
},
onError: (err: any) => {
toast.error(err?.response?.data?.errors?.[0]?.msg || err?.response?.data?.error || 'Failed to create webhook');
toast.error(err?.response?.data?.errors?.[0]?.msg || err?.response?.data?.error || t('settings.webhooks.createError', 'Failed to create webhook'));
},
});
@@ -97,16 +97,16 @@ export const WebhooksTab: React.FC = () => {
mutationFn: async ({ id, active }: { id: number; active: boolean }) =>
api.put(`/admin/webhooks/${id}`, { active }),
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['admin-webhooks'] }),
onError: () => toast.error('Failed to update webhook'),
onError: () => toast.error(t('settings.webhooks.updateError', 'Failed to update webhook')),
});
const deleteMutation = useMutation({
mutationFn: async (id: number) => api.delete(`/admin/webhooks/${id}`),
onSuccess: () => {
toast.success('Webhook deleted');
toast.success(t('settings.webhooks.deletedToast', 'Webhook deleted'));
queryClient.invalidateQueries({ queryKey: ['admin-webhooks'] });
},
onError: () => toast.error('Failed to delete webhook'),
onError: () => toast.error(t('settings.webhooks.deleteError', 'Failed to delete webhook')),
});
const toggleEvent = (e: WebhookEventType) => {
@@ -165,16 +165,16 @@ export const WebhooksTab: React.FC = () => {
onClick={async () => {
try {
await navigator.clipboard.writeText(justCreatedSecret);
toast.success('Copied');
toast.success(t('settings.webhooks.copied', 'Copied'));
} catch {
toast.error('Copy failed');
toast.error(t('settings.webhooks.copyFailed', 'Copy failed'));
}
}}
>
Copy
{t('settings.webhooks.copyButton', 'Copy')}
</Button>
<Button size="sm" variant="ghost" onClick={() => setJustCreatedSecret(null)}>
Dismiss
{t('settings.webhooks.dismiss', 'Dismiss')}
</Button>
</div>
</div>
@@ -188,13 +188,13 @@ export const WebhooksTab: React.FC = () => {
<label className="block text-sm font-medium text-neutral-700 dark:text-neutral-300 mb-1">
{t('settings.webhooks.name', 'Name')}
</label>
<Input value={name} onChange={(e) => setName(e.target.value)} placeholder="e.g. n8n WhatsApp" />
<Input value={name} onChange={(e) => setName(e.target.value)} placeholder={t('settings.webhooks.namePlaceholder', 'e.g. n8n WhatsApp')} />
</div>
<div>
<label className="block text-sm font-medium text-neutral-700 dark:text-neutral-300 mb-1">
{t('settings.webhooks.url', 'Receiver URL')}
</label>
<Input value={url} onChange={(e) => setUrl(e.target.value)} placeholder="https://n8n.example.com/webhook/picpeak" />
<Input value={url} onChange={(e) => setUrl(e.target.value)} placeholder={t('settings.webhooks.urlPlaceholder', 'https://n8n.example.com/webhook/picpeak')} />
</div>
</div>
@@ -222,7 +222,7 @@ export const WebhooksTab: React.FC = () => {
onClick={() => setShowAdvanced((prev) => !prev)}
className="text-sm text-primary-600 dark:text-primary-400 hover:underline self-start"
>
{showAdvanced ? ' Hide advanced (filter, template)' : '+ Advanced (filter, template)'}
{showAdvanced ? t('settings.webhooks.hideAdvanced', ' Hide advanced (filter, template)') : t('settings.webhooks.showAdvanced', '+ Advanced (filter, template)')}
</button>
{showAdvanced && (
@@ -239,7 +239,7 @@ export const WebhooksTab: React.FC = () => {
className="w-full px-3 py-2 border border-neutral-300 dark:border-neutral-700 dark:bg-neutral-800 rounded text-sm font-mono"
/>
<p className="text-xs text-neutral-500 mt-1">
Dot-path expected value. All keys must match (AND). Use an array for "any of": <code>{'{"type": ["event.published", "event.archived"]}'}</code>
{t('settings.webhooks.filterHelp', 'Dot-path → expected value. All keys must match (AND). Use an array for "any of".')} <code>{'{"type": ["event.published", "event.archived"]}'}</code>
</p>
{filterError && <p className="text-xs text-red-600 mt-1">{filterError}</p>}
</div>
@@ -251,12 +251,12 @@ export const WebhooksTab: React.FC = () => {
<textarea
value={template}
onChange={(e) => setTemplate(e.target.value)}
placeholder={'New gallery: ${data.event.event_name} → ${data.event.share_url}'}
placeholder={t('settings.webhooks.templatePlaceholder', 'New gallery: ${data.event.event_name} → ${data.event.share_url}')}
rows={3}
className="w-full px-3 py-2 border border-neutral-300 dark:border-neutral-700 dark:bg-neutral-800 rounded text-sm font-mono"
/>
<p className="text-xs text-neutral-500 mt-1">
Replaces the default JSON envelope as the request body. <code>${'{dot.path}'}</code> substitution from the payload only no logic, no expressions.
{t('settings.webhooks.templateHelp', 'Replaces the default JSON envelope as the request body. ${dot.path} substitution from the payload only — no logic, no expressions.')}
</p>
</div>
</div>
@@ -282,12 +282,12 @@ export const WebhooksTab: React.FC = () => {
<table className="w-full text-sm">
<thead>
<tr className="text-left text-neutral-500 dark:text-neutral-400 border-b border-neutral-200 dark:border-neutral-700">
<th className="py-2 pr-3">Name</th>
<th className="py-2 pr-3">URL</th>
<th className="py-2 pr-3">Events</th>
<th className="py-2 pr-3">Last delivery</th>
<th className="py-2 pr-3">Status</th>
<th className="py-2 text-right">Actions</th>
<th className="py-2 pr-3">{t('settings.webhooks.colName', 'Name')}</th>
<th className="py-2 pr-3">{t('settings.webhooks.colUrl', 'URL')}</th>
<th className="py-2 pr-3">{t('settings.webhooks.colEvents', 'Events')}</th>
<th className="py-2 pr-3">{t('settings.webhooks.colLastDelivery', 'Last delivery')}</th>
<th className="py-2 pr-3">{t('settings.webhooks.colStatus', 'Status')}</th>
<th className="py-2 text-right">{t('settings.webhooks.colActions', 'Actions')}</th>
</tr>
</thead>
<tbody>
@@ -300,7 +300,7 @@ export const WebhooksTab: React.FC = () => {
<td className="py-3 pr-3 font-medium">{wh.name}</td>
<td className="py-3 pr-3 text-xs font-mono text-neutral-600 dark:text-neutral-400 max-w-xs truncate" title={wh.url}>{wh.url}</td>
<td className="py-3 pr-3 text-xs text-neutral-500">
{Array.isArray(wh.events) ? wh.events.length : 0} subscribed
{t('settings.webhooks.eventsSubscribed', { count: Array.isArray(wh.events) ? wh.events.length : 0 })}
</td>
<td className="py-3 pr-3 text-xs text-neutral-500">
{lastEither === 'success' && lastSuccess && (
@@ -325,9 +325,9 @@ export const WebhooksTab: React.FC = () => {
? 'bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-300'
: 'bg-neutral-200 dark:bg-neutral-700 text-neutral-600 dark:text-neutral-400'
}`}
title={wh.active ? 'Click to disable' : 'Click to enable'}
title={wh.active ? t('settings.webhooks.toggleToDisable', 'Click to disable') : t('settings.webhooks.toggleToEnable', 'Click to enable')}
>
{wh.active ? 'Active' : 'Disabled'}
{wh.active ? t('settings.webhooks.statusActive', 'Active') : t('settings.webhooks.statusDisabled', 'Disabled')}
</button>
</td>
<td className="py-3 text-right">
@@ -337,19 +337,19 @@ export const WebhooksTab: React.FC = () => {
className="inline-flex items-center gap-1 px-2 py-1 text-xs text-neutral-600 dark:text-neutral-400 hover:text-neutral-900 dark:hover:text-neutral-100"
>
<Activity className="w-3.5 h-3.5" />
Deliveries
{t('settings.webhooks.deliveriesLink', 'Deliveries')}
</Link>
<Button
size="sm"
variant="ghost"
leftIcon={<Trash2 className="w-4 h-4" />}
onClick={() => {
if (confirm(`Delete "${wh.name}"? Pending deliveries are also removed.`)) {
if (confirm(t('settings.webhooks.confirmDelete', { name: wh.name, defaultValue: `Delete "${wh.name}"? Pending deliveries are also removed.` }))) {
deleteMutation.mutate(wh.id);
}
}}
>
Delete
{t('settings.webhooks.delete', 'Delete')}
</Button>
</div>
</td>
+200 -18
View File
@@ -1312,7 +1312,9 @@
"showGalleryFilterBar": "Filterleiste in Galerien anzeigen",
"showGalleryFilterBarHelp": "Dateiname-Suche und Sortiersteuerungen über Raster-Galerien anzeigen. Deaktivieren für ein übersichtlicheres Layout.",
"enablePhoneField": "Telefonnummer-Feld aktivieren",
"enablePhoneFieldHelp": "Fügt ein optionales Telefonnummer-Eingabefeld zum Veranstaltungsformular hinzu. Nützlich für Folgeautomatisierungen wie WhatsApp-Zustellung via n8n. Immer optional, auch wenn aktiviert."
"enablePhoneFieldHelp": "Fügt ein optionales Telefonnummer-Eingabefeld zum Veranstaltungsformular hinzu. Nützlich für Folgeautomatisierungen wie WhatsApp-Zustellung via n8n. Immer optional, auch wenn aktiviert.",
"defaultFeedbackEnabled": "Gäste-Feedback standardmäßig aktivieren",
"defaultFeedbackEnabledHelp": "Vorbelegt \"Gäste-Feedback\" beim Erstellen neuer Events. Einzelne Feedback-Optionen (Likes, Bewertungen, Kommentare) bleiben pro Event anpassbar."
},
"imageSecurity": {
"title": "Bildschutz",
@@ -1560,9 +1562,10 @@
"statusRevoked": "Widerrufen",
"statusExpired": "Abgelaufen",
"statusActive": "Aktiv",
"confirmRevoke": "Diesen Token wirklich widerrufen?",
"confirmRevoke": "\"{{name}}\" widerrufen? Bestehende Integrationen, die dieses Token nutzen, erhalten ab sofort 401.",
"revoke": "Widerrufen",
"empty": "Noch keine Tokens. Generieren Sie oben einen, um zu beginnen."
"empty": "Noch keine Tokens. Generieren Sie oben einen, um zu beginnen.",
"preview": "Vorschau"
},
"webhooks": {
"title": "Webhooks",
@@ -1576,7 +1579,38 @@
"template": "Vorlage (optional)",
"create": "Webhook erstellen",
"existing": "Vorhandene Webhooks",
"empty": "Noch keine Webhooks. Erstellen Sie oben einen, um Ereignisbenachrichtigungen zu erhalten."
"empty": "Noch keine Webhooks. Erstellen Sie oben einen, um Ereignisbenachrichtigungen zu erhalten.",
"namePlaceholder": "z. B. n8n WhatsApp",
"urlPlaceholder": "https://n8n.example.com/webhook/picpeak",
"showAdvanced": "+ Erweitert (Filter, Vorlage)",
"hideAdvanced": " Erweitert ausblenden (Filter, Vorlage)",
"filterHelp": "Dot-Pfad → erwarteter Wert. Alle Schlüssel müssen übereinstimmen (AND). Array für \"eines aus\".",
"filterError": "Filter muss gültiges JSON sein",
"templatePlaceholder": "Neue Galerie: ${data.event.event_name} → ${data.event.share_url}",
"templateHelp": "Ersetzt den Standard-JSON-Umschlag als Request-Body. Nur ${dot.path}-Substitution aus der Payload — keine Logik, keine Ausdrücke.",
"copyButton": "Kopieren",
"dismiss": "Schließen",
"copied": "Kopiert",
"copyFailed": "Kopieren fehlgeschlagen",
"colName": "Name",
"colUrl": "URL",
"colEvents": "Events",
"colLastDelivery": "Letzte Zustellung",
"colStatus": "Status",
"colActions": "Aktionen",
"eventsSubscribed_one": "{{count}} abonniert",
"eventsSubscribed_other": "{{count}} abonniert",
"statusActive": "Aktiv",
"statusDisabled": "Deaktiviert",
"toggleToDisable": "Klicken zum Deaktivieren",
"toggleToEnable": "Klicken zum Aktivieren",
"deliveriesLink": "Zustellungen",
"delete": "Löschen",
"confirmDelete": "\"{{name}}\" löschen? Ausstehende Zustellungen werden ebenfalls entfernt.",
"createError": "Webhook konnte nicht erstellt werden",
"updateError": "Webhook konnte nicht aktualisiert werden",
"deletedToast": "Webhook gelöscht",
"deleteError": "Webhook konnte nicht gelöscht werden"
},
"sectionLabel": "Einstellungsbereich",
"navAriaLabel": "Einstellungsnavigation",
@@ -2180,7 +2214,72 @@
"categoryHeroUpdated": "Kategorie-Titelbild aktualisiert",
"publicSiteResetToDefault": "Öffentliche Seite auf Standard zurückgesetzt",
"cmsPageLogoUploaded": "CMS-Seiten-Logo hochgeladen: {{slug}}",
"adminPasswordResetByAdmin": "Admin-Passwort durch anderen Admin zurückgesetzt: {{username}}"
"adminPasswordResetByAdmin": "Admin-Passwort durch anderen Admin zurückgesetzt: {{username}}",
"contractCreated": "Vertrag erstellt: {{contractNumber}}",
"contractCreatedFromQuote": "Vertrag aus Angebot erstellt: {{contractNumber}}",
"contractUpdated": "Vertrag aktualisiert: {{contractNumber}}",
"contractSent": "Vertrag gesendet: {{contractNumber}}",
"contractResentSigned": "Unterschriebener Vertrag erneut gesendet: {{contractNumber}}",
"contractSignedByCustomer": "Vertrag vom Kunden unterschrieben: {{contractNumber}}",
"contractSignedPdfUploaded": "Unterschriebenes Vertrag-PDF hochgeladen: {{contractNumber}}",
"contractSignaturesRestamped": "Vertragsunterschriften neu gestempelt: {{contractNumber}}",
"contractCancelled": "Vertrag storniert: {{contractNumber}}",
"contractConvertedToEvent": "Vertrag in Event umgewandelt: {{contractNumber}}",
"contractConvertedToEmptyEvent": "Vertrag in leeres Event umgewandelt: {{contractNumber}}",
"contractConvertedToInvoices": "Vertrag in Rechnungen umgewandelt: {{contractNumber}}",
"contractConvertedToEmptyInvoice": "Vertrag in leere Rechnung umgewandelt: {{contractNumber}}",
"quoteCreated": "Angebot erstellt: {{quoteNumber}}",
"quoteSent": "Angebot gesendet: {{quoteNumber}}",
"quoteUpdated": "Angebot aktualisiert: {{quoteNumber}}",
"quoteAcceptedByAdmin": "Angebot angenommen: {{quoteNumber}}",
"quoteDeclinedByAdmin": "Angebot abgelehnt: {{quoteNumber}}",
"quoteConverted": "Angebot umgewandelt: {{quoteNumber}}",
"quoteConvertedInvoicesOnly": "Angebot in Rechnungen umgewandelt: {{quoteNumber}}",
"invoiceCreated": "Rechnung erstellt: {{invoiceNumber}}",
"invoiceSent": "Rechnung gesendet: {{invoiceNumber}}",
"invoiceScheduled": "Rechnung geplant: {{invoiceNumber}}",
"invoiceCancelled": "Rechnung storniert: {{invoiceNumber}}",
"invoiceCancelledViaStorno": "Rechnung per Storno storniert: {{invoiceNumber}}",
"invoiceReissued": "Rechnung neu ausgestellt: {{invoiceNumber}}",
"invoicePaidAdminNotified": "Rechnung als bezahlt markiert: {{invoiceNumber}}",
"invoicePaymentCheckRecorded": "Zahlungsprüfung erfasst für Rechnung: {{invoiceNumber}}",
"invoicePaymentCheckSent": "Zahlungsprüfung gesendet für Rechnung: {{invoiceNumber}}",
"invoiceReleasedForDelivery": "Rechnung zur Lieferung freigegeben: {{invoiceNumber}}",
"invoiceReminderSent": "Rechnungserinnerung gesendet: {{invoiceNumber}}",
"stornoSent": "Storno gesendet: {{invoiceNumber}}",
"monthlyBillIssued": "Monatsrechnung für Kunden ausgestellt",
"monthlyBillSkippedEmpty": "Monatsrechnung übersprungen (keine Einträge)",
"monthlyBillTriggeredManually": "Monatsrechnung manuell ausgelöst",
"monthlyBillingItemsQueued": "Monatsabrechnungsposten in Warteschlange",
"installmentPlanUpdated": "Ratenplan aktualisiert",
"expenseCreated": "Ausgabe erstellt",
"expenseUpdated": "Ausgabe aktualisiert",
"expensePaid": "Ausgabe als bezahlt markiert",
"expenseInvoiced": "Ausgabe in Rechnung gestellt",
"hourEntryLogged": "Stundeneintrag erfasst",
"hourEntryUpdated": "Stundeneintrag aktualisiert",
"hourEntryDeleted": "Stundeneintrag gelöscht",
"hourEntryLoggedToMonthlyDraft": "Stundeneintrag in Monatsentwurf erfasst",
"hourEntriesBilled": "Stundeneinträge an Kunden abgerechnet",
"incomingInvoiceCaptured": "Eingangsrechnung erfasst",
"incomingInvoiceCategorized": "Eingangsrechnung kategorisiert",
"incomingInvoiceUpdated": "Eingangsrechnung aktualisiert",
"incomingInvoiceRebilled": "Eingangsrechnung an Kunden weiterverrechnet",
"incomingInvoiceSupplierPayment": "Lieferantenzahlung erfasst",
"incomingMailConfigUpdated": "Eingangs-Mail-Konfiguration aktualisiert",
"customerCreatedPassive": "Passiver Kunde erstellt: {{email}}",
"adminUserActivated": "Admin-Konto aktiviert: {{username}}",
"adminUserDeleted": "Admin-Konto gelöscht: {{username}}",
"adminPasswordReset": "Admin-Passwort zurückgesetzt: {{username}}",
"bulkArchiveCompleted": "Massen-Archivierung abgeschlossen: {{count}} Events archiviert",
"emailQueueFlushed": "E-Mail-Warteschlange geleert",
"emailResent": "Erstellungs-E-Mail erneut gesendet für {{eventName}}",
"emailTemplateCreated": "E-Mail-Vorlage erstellt: {{template_key}}",
"eventDuplicated": "Event dupliziert aus {{source_event_name}}",
"feedbackDeleted": "Feedback gelöscht",
"feedbackModerated": "Feedback moderiert",
"feedbackSettingsUpdated": "Feedback-Einstellungen aktualisiert für {{eventName}}",
"wordFilterAdded": "Wortfilter hinzugefügt: {{word}}"
},
"notificationToasts": {
"markedAllRead": "Alle Benachrichtigungen als gelesen markiert",
@@ -2375,7 +2474,65 @@
"photo_uploaded": "Foto hochgeladen in {{eventName}}",
"category_hero_updated": "Kategorie-Titelbild aktualisiert",
"public_site_reset_to_default": "Öffentliche Seite auf Standard zurückgesetzt",
"cms_page_logo_uploaded": "CMS-Seiten-Logo hochgeladen: {{slug}}"
"cms_page_logo_uploaded": "CMS-Seiten-Logo hochgeladen: {{slug}}",
"contract_created": "Vertrag erstellt: {{contractNumber}}",
"contract_created_from_quote": "Vertrag aus Angebot erstellt: {{contractNumber}}",
"contract_updated": "Vertrag aktualisiert: {{contractNumber}}",
"contract_sent": "Vertrag gesendet: {{contractNumber}}",
"contract_resent_signed": "Unterschriebener Vertrag erneut gesendet: {{contractNumber}}",
"contract_signed_by_customer": "Vertrag vom Kunden unterschrieben: {{contractNumber}}",
"contract_signed_pdf_uploaded": "Unterschriebenes Vertrag-PDF hochgeladen: {{contractNumber}}",
"contract_signatures_restamped": "Vertragsunterschriften neu gestempelt: {{contractNumber}}",
"contract_cancelled": "Vertrag storniert: {{contractNumber}}",
"contract_converted_to_event": "Vertrag in Event umgewandelt: {{contractNumber}}",
"contract_converted_to_empty_event": "Vertrag in leeres Event umgewandelt: {{contractNumber}}",
"contract_converted_to_invoices": "Vertrag in Rechnungen umgewandelt: {{contractNumber}}",
"contract_converted_to_empty_invoice": "Vertrag in leere Rechnung umgewandelt: {{contractNumber}}",
"quote_created": "Angebot erstellt: {{quoteNumber}}",
"quote_sent": "Angebot gesendet: {{quoteNumber}}",
"quote_updated": "Angebot aktualisiert: {{quoteNumber}}",
"quote_accepted_by_admin": "Angebot angenommen: {{quoteNumber}}",
"quote_declined_by_admin": "Angebot abgelehnt: {{quoteNumber}}",
"quote_converted": "Angebot umgewandelt: {{quoteNumber}}",
"quote_converted_invoices_only": "Angebot in Rechnungen umgewandelt: {{quoteNumber}}",
"invoice_created": "Rechnung erstellt: {{invoiceNumber}}",
"invoice_sent": "Rechnung gesendet: {{invoiceNumber}}",
"invoice_scheduled": "Rechnung geplant: {{invoiceNumber}}",
"invoice_cancelled": "Rechnung storniert: {{invoiceNumber}}",
"invoice_cancelled_via_storno": "Rechnung per Storno storniert: {{invoiceNumber}}",
"invoice_reissued": "Rechnung neu ausgestellt: {{invoiceNumber}}",
"invoice_paid_admin_notified": "Rechnung als bezahlt markiert: {{invoiceNumber}}",
"invoice_payment_check_recorded": "Zahlungsprüfung erfasst für Rechnung: {{invoiceNumber}}",
"invoice_payment_check_sent": "Zahlungsprüfung gesendet für Rechnung: {{invoiceNumber}}",
"invoice_released_for_delivery": "Rechnung zur Lieferung freigegeben: {{invoiceNumber}}",
"invoice_reminder_sent": "Rechnungserinnerung gesendet: {{invoiceNumber}}",
"storno_sent": "Storno gesendet: {{invoiceNumber}}",
"monthly_bill_issued": "Monatsrechnung für Kunden ausgestellt",
"monthly_bill_skipped_empty": "Monatsrechnung übersprungen (keine Einträge)",
"monthly_bill_triggered_manually": "Monatsrechnung manuell ausgelöst",
"monthly_billing_items_queued": "Monatsabrechnungsposten in Warteschlange",
"installment_plan_updated": "Ratenplan aktualisiert",
"expense_created": "Ausgabe erstellt",
"expense_updated": "Ausgabe aktualisiert",
"expense_paid": "Ausgabe als bezahlt markiert",
"expense_invoiced": "Ausgabe in Rechnung gestellt",
"hour_entry_logged": "Stundeneintrag erfasst",
"hour_entry_updated": "Stundeneintrag aktualisiert",
"hour_entry_deleted": "Stundeneintrag gelöscht",
"hour_entry_logged_to_monthly_draft": "Stundeneintrag in Monatsentwurf erfasst",
"hour_entries_billed": "Stundeneinträge an Kunden abgerechnet",
"incoming_invoice_captured": "Eingangsrechnung erfasst",
"incoming_invoice_categorized": "Eingangsrechnung kategorisiert",
"incoming_invoice_updated": "Eingangsrechnung aktualisiert",
"incoming_invoice_rebilled": "Eingangsrechnung an Kunden weiterverrechnet",
"incoming_invoice_supplier_payment": "Lieferantenzahlung erfasst",
"incoming_mail_config_updated": "Eingangs-Mail-Konfiguration aktualisiert",
"customer_created_passive": "Passiver Kunde erstellt: {{email}}",
"admin_user_activated": "Admin-Konto aktiviert: {{username}}",
"admin_user_deleted": "Admin-Konto gelöscht: {{username}}",
"email_queue_flushed": "E-Mail-Warteschlange geleert",
"email_template_created": "E-Mail-Vorlage erstellt: {{template_key}}",
"event_duplicated": "Event dupliziert aus {{source_event_name}}"
}
},
"acceptInvitation": {
@@ -3956,10 +4113,15 @@
"settingsSaved": "Zuordnungen gespeichert.",
"saveDefaults": "Zuordnungen speichern",
"accountType": {
"asset": "Aktiven", "liability": "Passiven", "equity": "Eigenkapital", "revenue": "Ertrag", "expense": "Aufwand"
"asset": "Aktiven",
"liability": "Passiven",
"equity": "Eigenkapital",
"revenue": "Ertrag",
"expense": "Aufwand"
},
"vatDirection": {
"input": "Vorsteuer", "output": "Umsatzsteuer"
"input": "Vorsteuer",
"output": "Umsatzsteuer"
},
"defaults": {
"title": "Standard- & Systemkonten",
@@ -3974,20 +4136,40 @@
"ledger_account_per_diem": "Spesenpauschalen-Konto",
"ledger_account_rebilled_revenue": "Weiterverrechnete Spesen (Ertrag)"
},
"vatMap": { "title": "MWST-Code nach steuerlicher Behandlung (Kosten)" },
"outputVatMap": { "title": "MWST-Code nach Umsatzsatz", "empty": "Fügen Sie oben Ausgangs-MWST-Codes hinzu, um pro Umsatzsatz einen Code festzulegen." },
"categoryMap": { "title": "Aufwandkategorie → Konto" },
"accounts": { "title": "Kontenplan" },
"vatCodes": { "title": "MWST-Codes" },
"vatMap": {
"title": "MWST-Code nach steuerlicher Behandlung (Kosten)"
},
"outputVatMap": {
"title": "MWST-Code nach Umsatzsatz",
"empty": "Fügen Sie oben Ausgangs-MWST-Codes hinzu, um pro Umsatzsatz einen Code festzulegen."
},
"categoryMap": {
"title": "Aufwandkategorie → Konto"
},
"accounts": {
"title": "Kontenplan"
},
"vatCodes": {
"title": "MWST-Codes"
},
"account": {
"addTitle": "Konto hinzufügen", "editTitle": "Konto bearbeiten",
"number": "Kontonummer", "name": "Name", "type": "Art",
"addTitle": "Konto hinzufügen",
"editTitle": "Konto bearbeiten",
"number": "Kontonummer",
"name": "Name",
"type": "Art",
"confirmDelete": "Dieses Konto löschen?"
},
"vat": {
"addTitle": "MWST-Code hinzufügen", "editTitle": "MWST-Code bearbeiten",
"code": "Code", "name": "Name", "rate": "Satz %", "direction": "Richtung",
"account": "MWST-Konto", "noAccount": "— keines —", "confirmDelete": "Diesen MWST-Code löschen?",
"addTitle": "MWST-Code hinzufügen",
"editTitle": "MWST-Code bearbeiten",
"code": "Code",
"name": "Name",
"rate": "Satz %",
"direction": "Richtung",
"account": "MWST-Konto",
"noAccount": "— keines —",
"confirmDelete": "Diesen MWST-Code löschen?",
"legacyRate": "{{rate}}% (nicht konfiguriert)"
},
"export": {
+200 -18
View File
@@ -933,7 +933,9 @@
"showGalleryFilterBar": "Show filter bar in galleries",
"showGalleryFilterBarHelp": "Display the search-by-filename and sort controls above grid-layout galleries. Disable for a cleaner layout.",
"enablePhoneField": "Enable phone number field",
"enablePhoneFieldHelp": "Adds an optional phone number input to the event form. Useful for downstream automations like WhatsApp delivery via n8n. Always optional even when enabled."
"enablePhoneFieldHelp": "Adds an optional phone number input to the event form. Useful for downstream automations like WhatsApp delivery via n8n. Always optional even when enabled.",
"defaultFeedbackEnabled": "Enable Guest Feedback by default",
"defaultFeedbackEnabledHelp": "Pre-check \"Guest Feedback\" when creating new events. Individual feedback options (likes, ratings, comments) can still be customised per event."
},
"imageSecurity": {
"title": "Image Protection",
@@ -1118,9 +1120,10 @@
"statusRevoked": "Revoked",
"statusExpired": "Expired",
"statusActive": "Active",
"confirmRevoke": "Do you want to revoke this token? This action cannot be undone.",
"confirmRevoke": "Revoke \"{{name}}\"? Existing integrations using this token will start getting 401.",
"revoke": "Revoke",
"empty": "No tokens yet. Generate one above to get started."
"empty": "No tokens yet. Generate one above to get started.",
"preview": "Preview"
},
"webhooks": {
"title": "Webhooks",
@@ -1134,7 +1137,38 @@
"template": "Template (optional)",
"create": "Create Webhook",
"existing": "Existing webhooks",
"empty": "No webhooks yet. Create one above to start receiving event notifications."
"empty": "No webhooks yet. Create one above to start receiving event notifications.",
"namePlaceholder": "e.g. n8n WhatsApp",
"urlPlaceholder": "https://n8n.example.com/webhook/picpeak",
"showAdvanced": "+ Advanced (filter, template)",
"hideAdvanced": " Hide advanced (filter, template)",
"filterHelp": "Dot-path → expected value. All keys must match (AND). Use an array for \"any of\".",
"filterError": "Filter must be valid JSON",
"templatePlaceholder": "New gallery: ${data.event.event_name} → ${data.event.share_url}",
"templateHelp": "Replaces the default JSON envelope as the request body. ${dot.path} substitution from the payload only — no logic, no expressions.",
"copyButton": "Copy",
"dismiss": "Dismiss",
"copied": "Copied",
"copyFailed": "Copy failed",
"colName": "Name",
"colUrl": "URL",
"colEvents": "Events",
"colLastDelivery": "Last delivery",
"colStatus": "Status",
"colActions": "Actions",
"eventsSubscribed_one": "{{count}} subscribed",
"eventsSubscribed_other": "{{count}} subscribed",
"statusActive": "Active",
"statusDisabled": "Disabled",
"toggleToDisable": "Click to disable",
"toggleToEnable": "Click to enable",
"deliveriesLink": "Deliveries",
"delete": "Delete",
"confirmDelete": "Delete \"{{name}}\"? Pending deliveries are also removed.",
"createError": "Failed to create webhook",
"updateError": "Failed to update webhook",
"deletedToast": "Webhook deleted",
"deleteError": "Failed to delete webhook"
},
"sectionLabel": "Settings section",
"navAriaLabel": "Settings navigation",
@@ -1767,7 +1801,72 @@
"categoryHeroUpdated": "Category hero photo updated",
"publicSiteResetToDefault": "Public site reset to default",
"cmsPageLogoUploaded": "CMS page logo uploaded: {{slug}}",
"adminPasswordResetByAdmin": "Admin password reset by another admin: {{username}}"
"adminPasswordResetByAdmin": "Admin password reset by another admin: {{username}}",
"contractCreated": "Contract created: {{contractNumber}}",
"contractCreatedFromQuote": "Contract created from quote: {{contractNumber}}",
"contractUpdated": "Contract updated: {{contractNumber}}",
"contractSent": "Contract sent: {{contractNumber}}",
"contractResentSigned": "Signed contract resent: {{contractNumber}}",
"contractSignedByCustomer": "Contract signed by customer: {{contractNumber}}",
"contractSignedPdfUploaded": "Signed contract PDF uploaded: {{contractNumber}}",
"contractSignaturesRestamped": "Contract signatures re-stamped: {{contractNumber}}",
"contractCancelled": "Contract cancelled: {{contractNumber}}",
"contractConvertedToEvent": "Contract converted to event: {{contractNumber}}",
"contractConvertedToEmptyEvent": "Contract converted to empty event: {{contractNumber}}",
"contractConvertedToInvoices": "Contract converted to invoices: {{contractNumber}}",
"contractConvertedToEmptyInvoice": "Contract converted to empty invoice: {{contractNumber}}",
"quoteCreated": "Quote created: {{quoteNumber}}",
"quoteSent": "Quote sent: {{quoteNumber}}",
"quoteUpdated": "Quote updated: {{quoteNumber}}",
"quoteAcceptedByAdmin": "Quote accepted: {{quoteNumber}}",
"quoteDeclinedByAdmin": "Quote declined: {{quoteNumber}}",
"quoteConverted": "Quote converted: {{quoteNumber}}",
"quoteConvertedInvoicesOnly": "Quote converted to invoices: {{quoteNumber}}",
"invoiceCreated": "Invoice created: {{invoiceNumber}}",
"invoiceSent": "Invoice sent: {{invoiceNumber}}",
"invoiceScheduled": "Invoice scheduled: {{invoiceNumber}}",
"invoiceCancelled": "Invoice cancelled: {{invoiceNumber}}",
"invoiceCancelledViaStorno": "Invoice cancelled via Storno: {{invoiceNumber}}",
"invoiceReissued": "Invoice reissued: {{invoiceNumber}}",
"invoicePaidAdminNotified": "Invoice marked paid: {{invoiceNumber}}",
"invoicePaymentCheckRecorded": "Payment-check recorded for invoice: {{invoiceNumber}}",
"invoicePaymentCheckSent": "Payment-check sent for invoice: {{invoiceNumber}}",
"invoiceReleasedForDelivery": "Invoice released for delivery: {{invoiceNumber}}",
"invoiceReminderSent": "Invoice reminder sent: {{invoiceNumber}}",
"stornoSent": "Storno sent: {{invoiceNumber}}",
"monthlyBillIssued": "Monthly bill issued for customer",
"monthlyBillSkippedEmpty": "Monthly bill skipped (no entries)",
"monthlyBillTriggeredManually": "Monthly bill triggered manually",
"monthlyBillingItemsQueued": "Monthly billing items queued",
"installmentPlanUpdated": "Installment plan updated",
"expenseCreated": "Expense created",
"expenseUpdated": "Expense updated",
"expensePaid": "Expense marked paid",
"expenseInvoiced": "Expense invoiced",
"hourEntryLogged": "Hour entry logged",
"hourEntryUpdated": "Hour entry updated",
"hourEntryDeleted": "Hour entry deleted",
"hourEntryLoggedToMonthlyDraft": "Hour entry logged to monthly draft",
"hourEntriesBilled": "Hour entries billed to customer",
"incomingInvoiceCaptured": "Incoming invoice captured",
"incomingInvoiceCategorized": "Incoming invoice categorised",
"incomingInvoiceUpdated": "Incoming invoice updated",
"incomingInvoiceRebilled": "Incoming invoice re-billed to customer",
"incomingInvoiceSupplierPayment": "Supplier payment recorded",
"incomingMailConfigUpdated": "Incoming mail configuration updated",
"customerCreatedPassive": "Passive customer created: {{email}}",
"adminUserActivated": "Admin user activated: {{username}}",
"adminUserDeleted": "Admin user deleted: {{username}}",
"adminPasswordReset": "Admin password reset: {{username}}",
"bulkArchiveCompleted": "Bulk archive completed: {{count}} events archived",
"emailQueueFlushed": "Email queue flushed",
"emailResent": "Creation email resent for {{eventName}}",
"emailTemplateCreated": "Email template created: {{template_key}}",
"eventDuplicated": "Event duplicated from {{source_event_name}}",
"feedbackDeleted": "Feedback deleted",
"feedbackModerated": "Feedback moderated",
"feedbackSettingsUpdated": "Feedback settings updated for {{eventName}}",
"wordFilterAdded": "Word filter added: {{word}}"
},
"notificationToasts": {
"markedAllRead": "All notifications marked as read",
@@ -1964,7 +2063,65 @@
"photo_uploaded": "Photo uploaded to {{eventName}}",
"category_hero_updated": "Category hero photo updated",
"public_site_reset_to_default": "Public site reset to default",
"cms_page_logo_uploaded": "CMS page logo uploaded: {{slug}}"
"cms_page_logo_uploaded": "CMS page logo uploaded: {{slug}}",
"contract_created": "Contract created: {{contractNumber}}",
"contract_created_from_quote": "Contract created from quote: {{contractNumber}}",
"contract_updated": "Contract updated: {{contractNumber}}",
"contract_sent": "Contract sent: {{contractNumber}}",
"contract_resent_signed": "Signed contract resent: {{contractNumber}}",
"contract_signed_by_customer": "Contract signed by customer: {{contractNumber}}",
"contract_signed_pdf_uploaded": "Signed contract PDF uploaded: {{contractNumber}}",
"contract_signatures_restamped": "Contract signatures re-stamped: {{contractNumber}}",
"contract_cancelled": "Contract cancelled: {{contractNumber}}",
"contract_converted_to_event": "Contract converted to event: {{contractNumber}}",
"contract_converted_to_empty_event": "Contract converted to empty event: {{contractNumber}}",
"contract_converted_to_invoices": "Contract converted to invoices: {{contractNumber}}",
"contract_converted_to_empty_invoice": "Contract converted to empty invoice: {{contractNumber}}",
"quote_created": "Quote created: {{quoteNumber}}",
"quote_sent": "Quote sent: {{quoteNumber}}",
"quote_updated": "Quote updated: {{quoteNumber}}",
"quote_accepted_by_admin": "Quote accepted: {{quoteNumber}}",
"quote_declined_by_admin": "Quote declined: {{quoteNumber}}",
"quote_converted": "Quote converted: {{quoteNumber}}",
"quote_converted_invoices_only": "Quote converted to invoices: {{quoteNumber}}",
"invoice_created": "Invoice created: {{invoiceNumber}}",
"invoice_sent": "Invoice sent: {{invoiceNumber}}",
"invoice_scheduled": "Invoice scheduled: {{invoiceNumber}}",
"invoice_cancelled": "Invoice cancelled: {{invoiceNumber}}",
"invoice_cancelled_via_storno": "Invoice cancelled via Storno: {{invoiceNumber}}",
"invoice_reissued": "Invoice reissued: {{invoiceNumber}}",
"invoice_paid_admin_notified": "Invoice marked paid: {{invoiceNumber}}",
"invoice_payment_check_recorded": "Payment-check recorded for invoice: {{invoiceNumber}}",
"invoice_payment_check_sent": "Payment-check sent for invoice: {{invoiceNumber}}",
"invoice_released_for_delivery": "Invoice released for delivery: {{invoiceNumber}}",
"invoice_reminder_sent": "Invoice reminder sent: {{invoiceNumber}}",
"storno_sent": "Storno sent: {{invoiceNumber}}",
"monthly_bill_issued": "Monthly bill issued for customer",
"monthly_bill_skipped_empty": "Monthly bill skipped (no entries)",
"monthly_bill_triggered_manually": "Monthly bill triggered manually",
"monthly_billing_items_queued": "Monthly billing items queued",
"installment_plan_updated": "Installment plan updated",
"expense_created": "Expense created",
"expense_updated": "Expense updated",
"expense_paid": "Expense marked paid",
"expense_invoiced": "Expense invoiced",
"hour_entry_logged": "Hour entry logged",
"hour_entry_updated": "Hour entry updated",
"hour_entry_deleted": "Hour entry deleted",
"hour_entry_logged_to_monthly_draft": "Hour entry logged to monthly draft",
"hour_entries_billed": "Hour entries billed to customer",
"incoming_invoice_captured": "Incoming invoice captured",
"incoming_invoice_categorized": "Incoming invoice categorised",
"incoming_invoice_updated": "Incoming invoice updated",
"incoming_invoice_rebilled": "Incoming invoice re-billed to customer",
"incoming_invoice_supplier_payment": "Supplier payment recorded",
"incoming_mail_config_updated": "Incoming mail configuration updated",
"customer_created_passive": "Passive customer created: {{email}}",
"admin_user_activated": "Admin user activated: {{username}}",
"admin_user_deleted": "Admin user deleted: {{username}}",
"email_queue_flushed": "Email queue flushed",
"email_template_created": "Email template created: {{template_key}}",
"event_duplicated": "Event duplicated from {{source_event_name}}"
}
},
"acceptInvitation": {
@@ -3956,10 +4113,15 @@
"settingsSaved": "Mappings saved.",
"saveDefaults": "Save mappings",
"accountType": {
"asset": "Asset", "liability": "Liability", "equity": "Equity", "revenue": "Revenue", "expense": "Expense"
"asset": "Asset",
"liability": "Liability",
"equity": "Equity",
"revenue": "Revenue",
"expense": "Expense"
},
"vatDirection": {
"input": "Input (Vorsteuer)", "output": "Output (Umsatzsteuer)"
"input": "Input (Vorsteuer)",
"output": "Output (Umsatzsteuer)"
},
"defaults": {
"title": "Default & system accounts",
@@ -3974,20 +4136,40 @@
"ledger_account_per_diem": "Per-diem account",
"ledger_account_rebilled_revenue": "Re-billed expenses (revenue)"
},
"vatMap": { "title": "VAT code by tax treatment (costs)" },
"outputVatMap": { "title": "VAT code by revenue rate", "empty": "Add output VAT codes above to configure a code per revenue rate." },
"categoryMap": { "title": "Expense category → account" },
"accounts": { "title": "Chart of accounts" },
"vatCodes": { "title": "VAT codes" },
"vatMap": {
"title": "VAT code by tax treatment (costs)"
},
"outputVatMap": {
"title": "VAT code by revenue rate",
"empty": "Add output VAT codes above to configure a code per revenue rate."
},
"categoryMap": {
"title": "Expense category → account"
},
"accounts": {
"title": "Chart of accounts"
},
"vatCodes": {
"title": "VAT codes"
},
"account": {
"addTitle": "Add account", "editTitle": "Edit account",
"number": "Account number", "name": "Name", "type": "Type",
"addTitle": "Add account",
"editTitle": "Edit account",
"number": "Account number",
"name": "Name",
"type": "Type",
"confirmDelete": "Delete this account?"
},
"vat": {
"addTitle": "Add VAT code", "editTitle": "Edit VAT code",
"code": "Code", "name": "Name", "rate": "Rate %", "direction": "Direction",
"account": "VAT account", "noAccount": "— none —", "confirmDelete": "Delete this VAT code?",
"addTitle": "Add VAT code",
"editTitle": "Edit VAT code",
"code": "Code",
"name": "Name",
"rate": "Rate %",
"direction": "Direction",
"account": "VAT account",
"noAccount": "— none —",
"confirmDelete": "Delete this VAT code?",
"legacyRate": "{{rate}}% (not configured)"
},
"export": {
+72
View File
@@ -445,6 +445,78 @@ export const adminService = {
'event_type_updated': `Event type updated: ${md.name || ''}`,
'event_type_deleted': `Event type deleted: ${md.name || ''}`,
'event_types_reordered': 'Event types reordered',
// CRM — Contracts.
'contract_created': `Contract created: ${md.contractNumber || ''}`,
'contract_created_from_quote': `Contract created from quote: ${md.contractNumber || ''}`,
'contract_updated': `Contract updated: ${md.contractNumber || ''}`,
'contract_sent': `Contract sent: ${md.contractNumber || ''}`,
'contract_resent_signed': `Signed contract resent: ${md.contractNumber || ''}`,
'contract_signed_by_customer': `Contract signed by customer: ${md.contractNumber || ''}`,
'contract_signed_pdf_uploaded': `Signed contract PDF uploaded: ${md.contractNumber || ''}`,
'contract_signatures_restamped': `Contract signatures re-stamped: ${md.contractNumber || ''}`,
'contract_cancelled': `Contract cancelled: ${md.contractNumber || ''}`,
'contract_converted_to_event': `Contract converted to event: ${md.contractNumber || ''}`,
'contract_converted_to_empty_event': `Contract converted to empty event: ${md.contractNumber || ''}`,
'contract_converted_to_invoices': `Contract converted to invoices: ${md.contractNumber || ''}`,
'contract_converted_to_empty_invoice': `Contract converted to empty invoice: ${md.contractNumber || ''}`,
// CRM — Quotes.
'quote_created': `Quote created: ${md.quoteNumber || ''}`,
'quote_sent': `Quote sent: ${md.quoteNumber || ''}`,
'quote_updated': `Quote updated: ${md.quoteNumber || ''}`,
'quote_accepted_by_admin': `Quote accepted: ${md.quoteNumber || ''}`,
'quote_declined_by_admin': `Quote declined: ${md.quoteNumber || ''}`,
'quote_converted': `Quote converted: ${md.quoteNumber || ''}`,
'quote_converted_invoices_only': `Quote converted to invoices: ${md.quoteNumber || ''}`,
// CRM — Invoices / Storno.
'invoice_created': `Invoice created: ${md.invoiceNumber || ''}`,
'invoice_sent': `Invoice sent: ${md.invoiceNumber || ''}`,
'invoice_scheduled': `Invoice scheduled: ${md.invoiceNumber || ''}`,
'invoice_cancelled': `Invoice cancelled: ${md.invoiceNumber || ''}`,
'invoice_cancelled_via_storno': `Invoice cancelled via Storno: ${md.invoiceNumber || ''}`,
'invoice_reissued': `Invoice reissued: ${md.invoiceNumber || ''}`,
'invoice_paid_admin_notified': `Invoice marked paid: ${md.invoiceNumber || ''}`,
'invoice_payment_check_recorded': `Payment-check recorded for invoice: ${md.invoiceNumber || ''}`,
'invoice_payment_check_sent': `Payment-check sent for invoice: ${md.invoiceNumber || ''}`,
'invoice_released_for_delivery': `Invoice released for delivery: ${md.invoiceNumber || ''}`,
'invoice_reminder_sent': `Invoice reminder sent: ${md.invoiceNumber || ''}`,
'storno_sent': `Storno sent: ${md.invoiceNumber || ''}`,
// CRM — Monthly billing.
'monthly_bill_issued': 'Monthly bill issued for customer',
'monthly_bill_skipped_empty': 'Monthly bill skipped (no entries)',
'monthly_bill_triggered_manually': 'Monthly bill triggered manually',
'monthly_billing_items_queued': 'Monthly billing items queued',
'installment_plan_updated': 'Installment plan updated',
// Accounting — Expenses + Hours + Incoming invoices.
'expense_created': 'Expense created',
'expense_updated': 'Expense updated',
'expense_paid': 'Expense marked paid',
'expense_invoiced': 'Expense invoiced',
'hour_entry_logged': 'Hour entry logged',
'hour_entry_updated': 'Hour entry updated',
'hour_entry_deleted': 'Hour entry deleted',
'hour_entry_logged_to_monthly_draft': 'Hour entry logged to monthly draft',
'hour_entries_billed': 'Hour entries billed to customer',
'incoming_invoice_captured': 'Incoming invoice captured',
'incoming_invoice_categorized': 'Incoming invoice categorised',
'incoming_invoice_updated': 'Incoming invoice updated',
'incoming_invoice_rebilled': 'Incoming invoice re-billed to customer',
'incoming_invoice_supplier_payment': 'Supplier payment recorded',
'incoming_mail_config_updated': 'Incoming mail configuration updated',
// Customers + Admin user mgmt.
'customer_created_passive': `Passive customer created: ${md.email || ''}`,
'admin_user_activated': `Admin user activated: ${md.username || ''}`,
'admin_user_deleted': `Admin user deleted: ${md.username || ''}`,
'admin_password_reset': `Admin password reset: ${md.username || ''}`,
// Misc / legacy.
'bulk_archive_completed': `Bulk archive completed: ${md.count || 0} events archived`,
'email_queue_flushed': 'Email queue flushed',
'email_resent': `Creation email resent for ${activity.eventName || ''}`,
'email_template_created': `Email template created: ${md.template_key || ''}`,
'event_duplicated': `Event duplicated from ${md.source_event_name || ''}`,
'feedback_deleted': 'Feedback deleted',
'feedback_moderated': 'Feedback moderated',
'feedback_settings_updated': `Feedback settings updated for ${activity.eventName || ''}`,
'word_filter_added': `Word filter added: ${md.word || ''}`,
};
return messages[activity.type] || activity.type;
+17 -2
View File
@@ -288,12 +288,27 @@ export const notificationsService = {
slug: notification.metadata.slug,
});
default:
// Log unknown notification types for debugging
default: {
// Smart fallback: try to resolve admin.notificationMessages.<camelCase>
// directly before giving up to the systemActivity template. Most CRM
// and accounting activity types follow a stable shape (a doc number /
// customer email / count interpolated into a short sentence), so we
// add their translation entries to the locale files and rely on this
// default to pick them up — no per-type switch case needed.
const camelCase = notification.type.replace(/_(\w)/g, (_match, c) => c.toUpperCase());
const key = `admin.notificationMessages.${camelCase}`;
const translated = t(key, {
eventName: notification.eventName,
actorName: notification.actorName,
...notification.metadata,
});
if (translated && translated !== key) return translated as string;
// Truly unknown — log and render the legacy systemActivity template.
console.warn('Unknown notification type:', notification.type, notification);
return notification.metadata.message || t('admin.notificationMessages.systemActivity', {
type: notification.type.replace(/_/g, ' ')
});
}
}
},