Files
Wabenhain/src/lib/email-strings.ts
T
Nick Szaunig 6143e5704d fix: Zahlungsmethoden-Text vereinheitlichen, Retry-Button in Zahlungsfehler-Mail
- Apple Pay/Google Pay geprueft (Stripe-Doku via `stripe docs`): bei der hier
  verwendeten vollstaendig gehosteten Checkout-Seite (kein payment_method_types,
  kein ui_mode: "embedded") bereits automatisch enthalten, sobald Kartenzahlung
  aktiv ist - keine Codeaenderung noetig, keine Domain-Registrierung noetig
  (die betrifft laut Stripe nur Elements/eingebettete Checkout-Seite). Klaerenden
  Kommentar in api/checkout/route.ts und README-Abschnitt "Stripe" ergaenzt.
- versand-zahlung-Seite nannte nur 4 von 6 tatsaechlich akzeptierten
  Zahlungsmethoden (SEPA-Lastschrift und Klarna fehlten, obwohl sie laut README
  aktiv unterstuetzt werden und auf der Checkout-Seite selbst bereits korrekt
  gelistet sind) - Text in beiden Sprachen an die vollstaendige Liste angeglichen.
- Transaktions-E-Mails durchgesehen: Layout/Branding/Track-Order-Button bei
  Bestellbestaetigung und Versand-Mail bereits solide, keine Aenderung noetig.
  Die Zahlungsfehler-Mail forderte im Fliesstext zur erneuten Bestellung auf,
  hatte dafuer aber keinen anklickbaren Link - jetzt ein echter Button zum Shop.

Verifiziert: tsc --noEmit sauber, pnpm test 151/151 gruen, pnpm build erfolgreich.
2026-09-06 03:16:34 +02:00

313 lines
15 KiB
TypeScript

import type { Locale } from "@/i18n/config";
/**
* Eigenstaendiges Uebersetzungs-Woerterbuch nur fuer Transaktions-E-Mails —
* bewusst getrennt vom UI-Dictionary (i18n/dictionary-type.ts), da E-Mails
* eigene, laengere Fliesstexte haben und unabhaengig vom UI-Kontext (z.B.
* beim Stripe-Webhook ohne Request/Cookie-Zugriff) gerendert werden.
*/
interface EmailStrings {
orderConfirmation: {
preheader: (orderNumber: string) => string;
heading: (firstName: string) => string;
intro: (orderNumber: string) => string;
subtotal: string;
shipping: string;
freeShipping: string;
discount: string;
total: string;
deliveryTo: string;
pickupNote: string;
giftNote: string;
trackOrder: string;
subject: (orderNumber: string) => string;
};
newsletterConfirm: {
preheader: string;
heading: string;
intro: string;
button: string;
ignoreNote: string;
subject: string;
};
passwordReset: {
preheader: string;
heading: string;
intro: string;
button: string;
ignoreNote: string;
subject: string;
};
securityAlert: {
preheader: string;
heading: string;
contactNote: string;
subject: string;
passwordChanged: string;
twoFactorDisabled: string;
accountDeleted: string;
};
emailVerification: {
preheader: string;
heading: string;
intro: string;
button: string;
ignoreNote: string;
subject: string;
};
shipment: {
preheader: (orderNumber: string) => string;
heading: (firstName: string) => string;
intro: (orderNumber: string) => string;
trackButton: string;
subject: (orderNumber: string) => string;
};
pickupReady: {
preheader: (orderNumber: string) => string;
heading: (firstName: string) => string;
intro: (orderNumber: string) => string;
subject: (orderNumber: string) => string;
};
backInStock: {
preheader: (name: string) => string;
heading: string;
intro: (name: string) => string;
button: string;
subject: (name: string) => string;
};
returnAccepted: {
preheader: (orderNumber: string) => string;
heading: (firstName: string) => string;
intro: (orderNumber: string) => string;
subject: (orderNumber: string) => string;
};
returnRejected: {
preheader: (orderNumber: string) => string;
heading: (firstName: string) => string;
intro: (orderNumber: string) => string;
subject: (orderNumber: string) => string;
};
returnRefunded: {
preheader: (orderNumber: string) => string;
heading: (firstName: string) => string;
intro: (orderNumber: string) => string;
subject: (orderNumber: string) => string;
};
paymentFailed: {
preheader: (orderNumber: string) => string;
heading: (firstName: string) => string;
intro: (orderNumber: string) => string;
retryNote: string;
button: string;
subject: (orderNumber: string) => string;
};
}
const de: EmailStrings = {
orderConfirmation: {
preheader: (orderNumber) => `Deine Bestellung ${orderNumber} ist bestätigt.`,
heading: (firstName) => `Danke für deine Bestellung, ${firstName}!`,
intro: (orderNumber) => `Wir haben deine Bestellung <strong>${orderNumber}</strong> erhalten und die Zahlung bestätigt.`,
subtotal: "Zwischensumme",
shipping: "Versand",
freeShipping: "kostenlos",
discount: "Rabatt",
total: "Gesamt",
deliveryTo: "Lieferung an:",
pickupNote: "Abholung in Altenmünster — wir melden uns, sobald deine Bestellung bereitsteht.",
giftNote: "🎁 Deine persönliche Nachricht wird von Hand auf eine Karte geschrieben und der Bestellung beigelegt.",
trackOrder: "BESTELLUNG VERFOLGEN",
subject: (orderNumber) => `Bestellbestätigung ${orderNumber} — Wabenhain`,
},
newsletterConfirm: {
preheader: "Bitte bestätige deine Anmeldung zur Erntepost.",
heading: "Fast geschafft!",
intro: "Bitte bestätige deine Anmeldung zur Erntepost, damit wir dich informieren dürfen, sobald neuer Honig verfügbar ist.",
button: "ANMELDUNG BESTÄTIGEN",
ignoreNote: "Falls du dich nicht angemeldet hast, kannst du diese E-Mail einfach ignorieren.",
subject: "Bitte bestätige deine Anmeldung — Wabenhain Erntepost",
},
passwordReset: {
preheader: "Setze dein Wabenhain-Passwort zurück.",
heading: "Passwort zurücksetzen",
intro: "Wir haben eine Anfrage erhalten, das Passwort für dein Wabenhain-Konto zurückzusetzen. Der Link ist eine Stunde gültig.",
button: "NEUES PASSWORT VERGEBEN",
ignoreNote: "Falls du das nicht warst, kannst du diese E-Mail ignorieren — dein Passwort bleibt unverändert.",
subject: "Passwort zurücksetzen — Wabenhain",
},
securityAlert: {
preheader: "Sicherheitshinweis zu deinem Wabenhain-Konto.",
heading: "Sicherheitshinweis",
contactNote: "Falls das nicht du warst, kontaktiere uns bitte umgehend unter [email protected].",
subject: "Sicherheitshinweis — Wabenhain",
passwordChanged: "Das Passwort für dein Wabenhain-Konto wurde soeben geändert. Alle anderen angemeldeten Geräte wurden abgemeldet.",
twoFactorDisabled: "Die Zwei-Faktor-Authentifizierung für dein Wabenhain-Konto wurde soeben deaktiviert.",
accountDeleted: "Dein Wabenhain-Konto und alle persönlichen Daten wurden soeben endgültig gelöscht.",
},
emailVerification: {
preheader: "Bitte bestätige deine E-Mail-Adresse.",
heading: "Willkommen bei Wabenhain!",
intro: "Bitte bestätige deine E-Mail-Adresse, damit du mit deinem Konto bestellen kannst. Der Link ist 24 Stunden gültig.",
button: "E-MAIL-ADRESSE BESTÄTIGEN",
ignoreNote: "Falls du dieses Konto nicht erstellt hast, kannst du diese E-Mail einfach ignorieren.",
subject: "Bitte bestätige deine E-Mail-Adresse — Wabenhain",
},
shipment: {
preheader: (orderNumber) => `Deine Bestellung ${orderNumber} ist unterwegs.`,
heading: (firstName) => `Deine Bestellung ist unterwegs, ${firstName}!`,
intro: (orderNumber) => `Wir haben deine Bestellung <strong>${orderNumber}</strong> soeben versendet.`,
trackButton: "SENDUNG VERFOLGEN",
subject: (orderNumber) => `Deine Bestellung ${orderNumber} wurde versendet — Wabenhain`,
},
pickupReady: {
preheader: (orderNumber) => `Deine Bestellung ${orderNumber} ist abholbereit.`,
heading: (firstName) => `Deine Bestellung wartet auf dich, ${firstName}!`,
intro: (orderNumber) => `Deine Bestellung <strong>${orderNumber}</strong> ist abholbereit.`,
subject: (orderNumber) => `Deine Bestellung ${orderNumber} ist abholbereit — Wabenhain`,
},
backInStock: {
preheader: (name) => `${name} ist wieder verfügbar.`,
heading: "Gute Nachrichten!",
intro: (name) => `<strong>${name}</strong> ist wieder auf Lager.`,
button: "JETZT BESTELLEN",
subject: (name) => `${name} ist wieder verfügbar — Wabenhain`,
},
returnAccepted: {
preheader: (orderNumber) => `Deine Rücksendeanfrage zu ${orderNumber} wurde angenommen.`,
heading: (firstName) => `Deine Rücksendung wurde angenommen, ${firstName}.`,
intro: (orderNumber) =>
`Wir haben deine Rücksendeanfrage zu Bestellung <strong>${orderNumber}</strong> angenommen. Bitte sende uns die Ware zurück — sobald sie bei uns eingegangen ist, veranlassen wir die Erstattung.`,
subject: (orderNumber) => `Rücksendung zu ${orderNumber} angenommen — Wabenhain`,
},
returnRejected: {
preheader: (orderNumber) => `Deine Rücksendeanfrage zu ${orderNumber} wurde abgelehnt.`,
heading: (firstName) => `Deine Rücksendeanfrage wurde leider abgelehnt, ${firstName}.`,
intro: (orderNumber) =>
`Wir haben deine Rücksendeanfrage zu Bestellung <strong>${orderNumber}</strong> geprüft und können sie leider nicht annehmen. Bei Fragen dazu melde dich gerne bei uns.`,
subject: (orderNumber) => `Rücksendung zu ${orderNumber} abgelehnt — Wabenhain`,
},
returnRefunded: {
preheader: (orderNumber) => `Deine Bestellung ${orderNumber} wurde erstattet.`,
heading: (firstName) => `Deine Erstattung ist unterwegs, ${firstName}.`,
intro: (orderNumber) =>
`Wir haben die Rückerstattung für deine Bestellung <strong>${orderNumber}</strong> veranlasst. Je nach Zahlungsmethode kann es einige Werktage dauern, bis der Betrag bei dir ankommt.`,
subject: (orderNumber) => `Bestellung ${orderNumber} erstattet — Wabenhain`,
},
paymentFailed: {
preheader: (orderNumber) => `Die Zahlung zu Bestellung ${orderNumber} konnte nicht abgeschlossen werden.`,
heading: (firstName) => `Leider hat die Zahlung nicht geklappt, ${firstName}.`,
intro: (orderNumber) =>
`Der Zahlungseinzug für deine Bestellung <strong>${orderNumber}</strong> konnte nicht abgeschlossen werden. Wir haben die Bestellung daher storniert, es sind dir keine Kosten entstanden.`,
retryNote: "Du kannst die Bestellung gerne erneut aufgeben, zum Beispiel mit einer anderen Zahlungsmethode.",
button: "ERNEUT BESTELLEN",
subject: (orderNumber) => `Zahlung zu Bestellung ${orderNumber} fehlgeschlagen — Wabenhain`,
},
};
const en: EmailStrings = {
orderConfirmation: {
preheader: (orderNumber) => `Your order ${orderNumber} is confirmed.`,
heading: (firstName) => `Thanks for your order, ${firstName}!`,
intro: (orderNumber) => `We've received your order <strong>${orderNumber}</strong> and confirmed payment.`,
subtotal: "Subtotal",
shipping: "Shipping",
freeShipping: "free",
discount: "Discount",
total: "Total",
deliveryTo: "Delivering to:",
pickupNote: "Pickup in Altenmünster — we'll be in touch once your order is ready.",
giftNote: "🎁 Your personal message will be handwritten on a card and included with your order.",
trackOrder: "TRACK ORDER",
subject: (orderNumber) => `Order confirmation ${orderNumber} — Wabenhain`,
},
newsletterConfirm: {
preheader: "Please confirm your harvest post subscription.",
heading: "Almost there!",
intro: "Please confirm your harvest post subscription so we can let you know as soon as new honey is available.",
button: "CONFIRM SUBSCRIPTION",
ignoreNote: "If you didn't sign up, you can simply ignore this email.",
subject: "Please confirm your subscription — Wabenhain harvest post",
},
passwordReset: {
preheader: "Reset your Wabenhain password.",
heading: "Reset password",
intro: "We received a request to reset the password for your Wabenhain account. This link is valid for one hour.",
button: "SET NEW PASSWORD",
ignoreNote: "If this wasn't you, you can ignore this email — your password stays unchanged.",
subject: "Reset password — Wabenhain",
},
securityAlert: {
preheader: "Security notice for your Wabenhain account.",
heading: "Security notice",
contactNote: "If this wasn't you, please contact us immediately at [email protected].",
subject: "Security notice — Wabenhain",
passwordChanged: "The password for your Wabenhain account was just changed. All other signed-in devices have been logged out.",
twoFactorDisabled: "Two-factor authentication for your Wabenhain account was just disabled.",
accountDeleted: "Your Wabenhain account and all personal data have just been permanently deleted.",
},
emailVerification: {
preheader: "Please confirm your email address.",
heading: "Welcome to Wabenhain!",
intro: "Please confirm your email address so you can order with your account. This link is valid for 24 hours.",
button: "CONFIRM EMAIL ADDRESS",
ignoreNote: "If you didn't create this account, you can simply ignore this email.",
subject: "Please confirm your email address — Wabenhain",
},
shipment: {
preheader: (orderNumber) => `Your order ${orderNumber} is on its way.`,
heading: (firstName) => `Your order is on its way, ${firstName}!`,
intro: (orderNumber) => `We've just shipped your order <strong>${orderNumber}</strong>.`,
trackButton: "TRACK PACKAGE",
subject: (orderNumber) => `Your order ${orderNumber} has shipped — Wabenhain`,
},
pickupReady: {
preheader: (orderNumber) => `Your order ${orderNumber} is ready for pickup.`,
heading: (firstName) => `Your order is waiting for you, ${firstName}!`,
intro: (orderNumber) => `Your order <strong>${orderNumber}</strong> is ready for pickup.`,
subject: (orderNumber) => `Your order ${orderNumber} is ready for pickup — Wabenhain`,
},
backInStock: {
preheader: (name) => `${name} is back in stock.`,
heading: "Good news!",
intro: (name) => `<strong>${name}</strong> is back in stock.`,
button: "ORDER NOW",
subject: (name) => `${name} is back in stock — Wabenhain`,
},
returnAccepted: {
preheader: (orderNumber) => `Your return request for ${orderNumber} was accepted.`,
heading: (firstName) => `Your return was accepted, ${firstName}.`,
intro: (orderNumber) =>
`We've accepted your return request for order <strong>${orderNumber}</strong>. Please send the item back to us — once it arrives, we'll process your refund.`,
subject: (orderNumber) => `Return for ${orderNumber} accepted — Wabenhain`,
},
returnRejected: {
preheader: (orderNumber) => `Your return request for ${orderNumber} was declined.`,
heading: (firstName) => `Your return request was unfortunately declined, ${firstName}.`,
intro: (orderNumber) =>
`We've reviewed your return request for order <strong>${orderNumber}</strong> and are unable to accept it. Feel free to reach out with any questions.`,
subject: (orderNumber) => `Return for ${orderNumber} declined — Wabenhain`,
},
returnRefunded: {
preheader: (orderNumber) => `Your order ${orderNumber} has been refunded.`,
heading: (firstName) => `Your refund is on its way, ${firstName}.`,
intro: (orderNumber) =>
`We've processed the refund for your order <strong>${orderNumber}</strong>. Depending on your payment method, it may take a few business days to arrive.`,
subject: (orderNumber) => `Order ${orderNumber} refunded — Wabenhain`,
},
paymentFailed: {
preheader: (orderNumber) => `Payment for order ${orderNumber} could not be completed.`,
heading: (firstName) => `Unfortunately your payment didn't go through, ${firstName}.`,
intro: (orderNumber) =>
`The payment for your order <strong>${orderNumber}</strong> could not be completed. We've cancelled the order, so no costs were incurred.`,
retryNote: "Feel free to place the order again, for example with a different payment method.",
button: "ORDER AGAIN",
subject: (orderNumber) => `Payment for order ${orderNumber} failed — Wabenhain`,
},
};
const emailStrings: Record<Locale, EmailStrings> = { de, en };
export function getEmailStrings(locale: Locale): EmailStrings {
return emailStrings[locale];
}