fix(email,ui): billing emails follow customer language + readable payment-check confirmation

- Billing/dunning emails no longer render in the gallery event's language.
  emailProcessor now honors an explicit `__language` in the email data
  (else falls back to the event-first recipient resolution), and the invoice
  reminder passes the customer/invoice locale (customer.preferred_language
  || invoice.language || 'de'). Fixes German customers getting English
  dunning notices. (#760)
- Payment-check confirmation card ("Action recorded") is now theme-adaptive
  (green tint + readable text on both light and dark surfaces) instead of a
  hardcoded light-green mix + dark-green title that vanished in dark mode. (#759)
- The per-customer "Preferred language" field already exists
  (CustomerDetailPage) plus the business-profile default; updated the helper
  text to note billing emails now honor it too. (#761)
This commit is contained in:
Luca
2026-07-06 19:27:57 +02:00
parent a52317d8a5
commit fcc3e9195d
6 changed files with 20 additions and 12 deletions
+1 -1
View File
@@ -4074,7 +4074,7 @@
"noEvents": "Noch keinem Event zugewiesen. Fügen Sie diesen Kunden über das Event-Formular hinzu.",
"email": "E-Mail",
"preferredLanguage": "Bevorzugte Sprache",
"preferredLanguageHint": "Steuert die Portal-Sprache sowie die Sprache von Angebots- und Rechnungs-PDFs. Neue Kunden erben standardmässig die Sprache aus dem Geschäftsprofil ({{lang}}); hier kann pro Kunde überschrieben werden.",
"preferredLanguageHint": "Steuert die Portal-Sprache, Angebots-/Rechnungs-PDFs sowie Rechnungs-E-Mails (Erinnerungen/Mahnungen). Neue Kunden erben standardmässig die Sprache aus dem Geschäftsprofil ({{lang}}); hier kann pro Kunde überschrieben werden.",
"salutation": "Anrede",
"salutationNone": "—",
"firstName": "Vorname",
+1 -1
View File
@@ -4074,7 +4074,7 @@
"noEvents": "Not assigned to any events yet. Add this customer to an event from the event form.",
"email": "Email",
"preferredLanguage": "Preferred language",
"preferredLanguageHint": "Drives portal UI and quote/invoice PDF locale. New customers default to the business-profile language ({{lang}}); override here per customer.",
"preferredLanguageHint": "Drives portal UI, quote/invoice PDFs, and billing emails (reminders/dunning). New customers default to the business-profile language ({{lang}}); override here per customer.",
"salutation": "Salutation",
"salutationNone": "—",
"firstName": "First name",
@@ -360,7 +360,7 @@ export const CustomerDetailPage: React.FC = () => {
</select>
<p className="text-xs text-neutral-500 mt-1">
{t('customers.detail.preferredLanguageHint',
'Drives portal UI and quote/invoice PDF locale. New customers default to the business-profile language ({{lang}}); override here per customer.',
'Drives portal UI, quote/invoice PDFs, and billing emails (reminders/dunning). New customers default to the business-profile language ({{lang}}); override here per customer.',
{ lang: LOCALE_LABELS[profileDefaultLocale] || profileDefaultLocale.toUpperCase() })}
</p>
</div>
@@ -373,15 +373,18 @@ const ResultBox: React.FC<{
<div
className="rounded-lg border p-6"
style={{
borderColor: '#bbf7d0',
backgroundColor: 'color-mix(in srgb, #dcfce7 50%, var(--color-surface))',
// Theme-adaptive success card — a light-green tint on light surfaces,
// a dark-green tint on dark ones (was a hardcoded light-green mix +
// dark-green title that went unreadable in dark mode, #759).
borderColor: 'color-mix(in srgb, #16a34a 35%, var(--color-surface))',
backgroundColor: 'color-mix(in srgb, #16a34a 12%, var(--color-surface))',
}}
>
<CheckCircle2 className="w-10 h-10 mb-3" style={{ color: '#16a34a' }} />
<h1 className="text-lg font-bold mb-1" style={{ color: '#166534' }}>
<h1 className="text-lg font-bold mb-1" style={{ color: 'var(--color-text)' }}>
{t('paymentCheck.result.title', 'Action recorded')}
</h1>
<p className="text-sm">
<p className="text-sm" style={{ color: 'var(--color-text)' }}>
{result.applied === 'paid_full' && t('paymentCheck.result.paid',
'Invoice {{n}} marked as paid in full.', { n: inv.invoiceNumber })}
{result.applied === 'paid_with_skonto' && t('paymentCheck.result.paidSkonto',