From c0008be39bc8a9d354e48ce8d6bd89662bc53ebb Mon Sep 17 00:00:00 2001 From: Luca <102960244+Luca-Timo@users.noreply.github.com> Date: Mon, 6 Jul 2026 22:10:18 +0200 Subject: [PATCH] fix(email): sibling billing emails follow customer language too MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses the PR #763 review: the invoice_sent / storno_issued / payment-check / paid-admin-notification emails share the identical event-first language bug and never set __language, so a German customer on an English-gallery event got an English email body with German-formatted amounts. Each call site already computes the locale it formats amounts in, so this is a one-liner per call — the body language now matches the amount formatting: - invoice_sent, storno_issued (sending.js) -> __language: ctx.locale - payment-check, invoice_paid_admin_notification (payments.js) -> __language: locale Leak-safe (no template references {{__language}}) and falls back to the existing event-first resolution when unset, per the mechanism added in #763. --- backend/src/services/invoice/payments.js | 5 +++++ backend/src/services/invoice/sending.js | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/backend/src/services/invoice/payments.js b/backend/src/services/invoice/payments.js index c8b3f0f8..05b6fdba 100644 --- a/backend/src/services/invoice/payments.js +++ b/backend/src/services/invoice/payments.js @@ -185,6 +185,8 @@ async function queueInvoicePaidAdminNotification({ || [customer?.first_name, customer?.last_name].filter(Boolean).join(' ') || customer?.email || '', event_name: invoice.event_name || '', + // Keep the body language consistent with the locale-formatted amounts. + __language: locale, total_amount: formatMajor(invoice.total_amount_minor, invoice.currency, locale), paid_amount: formatMajor(paidTotalMinor, invoice.currency, locale), payment_method: paymentMethod || '', @@ -285,6 +287,9 @@ async function queuePaymentCheckEmail(invoiceId, { skipThrottle = false } = {}) || [customer?.first_name, customer?.last_name].filter(Boolean).join(' ') || customer?.email || '', event_name: invoice.event_name || '', + // Keep the body language consistent with the locale the amounts are + // formatted in, instead of event-first resolution (admin-facing gate). + __language: locale, due_date: formatShortDate(invoice.due_date), total_amount: formatMajor(invoice.total_amount_minor, invoice.currency, locale), paid_amount: formatMajor(paidMinor, invoice.currency, locale), diff --git a/backend/src/services/invoice/sending.js b/backend/src/services/invoice/sending.js index 62bc8f45..d1055fa4 100644 --- a/backend/src/services/invoice/sending.js +++ b/backend/src/services/invoice/sending.js @@ -127,6 +127,9 @@ async function sendInvoice(id, adminId) { installment_label: invoice.installment_label || '', installment_index: invoice.installment_index + 1, installment_total: invoice.installment_total, + // Send in the customer's language (matches the ctx.locale-formatted amounts + // above) rather than the event-first default resolution. + __language: ctx.locale, cc: invoiceCc, attachments: [{ filename: `${invoice.invoice_number}.pdf`, @@ -368,6 +371,8 @@ async function sendStorno(stornoId, adminId) { original_issue_date: originalRow?.issue_date ? formatShortDate(originalRow.issue_date) : '', customer_name: customer.display_name || customer.first_name || customer.email.split('@')[0], total_amount: formatMajor(Math.abs(storno.total_amount_minor), storno.currency, ctx.locale), + // Match the customer's language (as with the ctx.locale-formatted amount). + __language: ctx.locale, cc: stornoCc, attachments: [{ filename: `${storno.invoice_number}.pdf`,