- 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.
313 lines
15 KiB
TypeScript
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];
|
|
}
|