diff --git a/public/lang/de/marketing.json b/public/lang/de/marketing.json index d96221e..5a93d07 100644 --- a/public/lang/de/marketing.json +++ b/public/lang/de/marketing.json @@ -379,8 +379,9 @@ "title": "Konto", "subtitle": "Anmelden oder Registrieren", "description": "Erstellen Sie ein Konto oder melden Sie sich an, um mit dem Kauf fortzufahren.", - "already_logged_in_title": "Bereits eingeloggt", - "already_logged_in_desc": "Sie sind bereits als {email} eingeloggt.", + "already_logged_in_title": "Willkommen zurück!", + "already_logged_in_body": "Du bist bereits mit {{email}} angemeldet. Wir haben deine Daten übernommen, damit du ohne Umwege weitermachen kannst.", + "already_logged_in_hint": "Möchtest du ein anderes Konto nutzen? Melde dich kurz ab und starte den Checkout anschließend erneut.", "next_to_payment": "Weiter zur Zahlung", "switch_to_register": "Registrieren", "switch_to_login": "Anmelden", @@ -400,7 +401,7 @@ "activate_package": "Paket aktivieren", "loading_payment": "Zahlungsdaten werden geladen...", "secure_payment_desc": "Sichere Zahlung über Paddle.", - "paddle_intro": "Wir öffnen den Paddle-Checkout direkt hier im Wizard, damit du im Ablauf bleibst.", + "paddle_intro": "Starte den Paddle-Checkout direkt hier im Wizard – ganz ohne Seitenwechsel.", "paddle_preparing": "Paddle-Checkout wird vorbereitet…", "paddle_overlay_ready": "Der Paddle-Checkout läuft jetzt in einem Overlay. Schließe die Zahlung dort ab und kehre anschließend hierher zurück.", "paddle_ready": "Paddle-Checkout wurde in einem neuen Tab geöffnet. Schließe die Zahlung dort ab und kehre dann hierher zurück.", @@ -441,22 +442,24 @@ "confirmation_step": { "title": "Bestätigung", "subtitle": "Alles erledigt!", - "description": "Ihr Paket ist aktiviert. Überprüfen Sie Ihre E-Mail für Details.", - "welcome": "Willkommen bei Fotospiel", + "description": "Dein Paket ist aktiviert. Prüfe deine E-Mails für Details.", + "welcome": "Danke, dass du FotoSpiel gewählt hast!", + "package_summary": "Dein Paket {name} ist jetzt freigeschaltet. Du kannst sofort mit der Einrichtung loslegen.", + "email_followup": "Wir haben dir gerade alle Details per E-Mail geschickt – inklusive Rechnung und den nächsten Schritten.", "package_activated": "Ihr Paket '{name}' ist aktiviert.", "email_sent": "Wir haben Ihnen eine Bestätigungs-E-Mail gesendet.", "open_profile": "Profil öffnen", "to_admin": "Zum Admin-Bereich" }, "confirmation": { - "welcome": "Willkommen bei Fotospiel", - "package_activated": "Ihr Paket '{name}' ist aktiviert.", - "email_sent": "Wir haben Ihnen eine Bestätigungs-E-Mail gesendet.", + "welcome": "Danke, dass du FotoSpiel gewählt hast!", + "package_activated": "Dein Paket {name} ist jetzt freigeschaltet.", + "email_sent": "Wir haben dir alle Details per E-Mail geschickt.", "open_profile": "Profil öffnen", "to_admin": "Zum Admin-Bereich" }, "auth": { - "already_logged_in": "Sie sind bereits als {email} eingeloggt.", + "already_logged_in": "Du bist bereits mit {{email}} angemeldet.", "switch_to_register": "Registrieren", "switch_to_login": "Anmelden", "continue_with_google": "Mit Google fortfahren", diff --git a/public/lang/en/marketing.json b/public/lang/en/marketing.json index 9ac9c8c..467a63b 100644 --- a/public/lang/en/marketing.json +++ b/public/lang/en/marketing.json @@ -373,8 +373,9 @@ "title": "Account", "subtitle": "Login or Register", "description": "Create an account or log in to continue with your purchase.", - "already_logged_in_title": "Already Logged In", - "already_logged_in_desc": "You are already logged in as {email}.", + "already_logged_in_title": "Welcome back!", + "already_logged_in_body": "You're already signed in as {{email}}. Your details are all set so you can continue without interruption.", + "already_logged_in_hint": "Need to switch accounts? Sign out briefly and restart the checkout.", "next_to_payment": "Next to Payment", "switch_to_register": "Register", "switch_to_login": "Login", @@ -394,7 +395,7 @@ "activate_package": "Activate Package", "loading_payment": "Payment data is loading...", "secure_payment_desc": "Secure payment with Paddle.", - "paddle_intro": "We open Paddle's secure checkout directly inside this wizard so you never leave the flow.", + "paddle_intro": "Launch the Paddle checkout right here in the wizard—no page changes required.", "paddle_preparing": "Preparing Paddle checkout…", "paddle_overlay_ready": "Paddle checkout is running in a secure overlay. Complete the payment there and then continue here.", "paddle_ready": "Paddle checkout opened in a new tab. Complete the payment and then continue here.", @@ -436,21 +437,23 @@ "title": "Confirmation", "subtitle": "All Done!", "description": "Your package is activated. Check your email for details.", - "welcome": "Welcome to FotoSpiel", + "welcome": "Thank you for choosing FotoSpiel!", + "package_summary": "Your {name} package is now active. You're ready to get everything set up.", + "email_followup": "We've just sent a confirmation email with your receipt and the next steps.", "package_activated": "Your package '{name}' is activated.", "email_sent": "We have sent you a confirmation email.", "open_profile": "Open Profile", "to_admin": "To Admin Area" }, "confirmation": { - "welcome": "Welcome to FotoSpiel", - "package_activated": "Your package '{name}' is activated.", - "email_sent": "We have sent you a confirmation email.", + "welcome": "Thank you for choosing FotoSpiel!", + "package_activated": "Your {name} package is active.", + "email_sent": "We've emailed you all the details.", "open_profile": "Open Profile", "to_admin": "To Admin Area" }, "auth": { - "already_logged_in": "You are already logged in as {email}.", + "already_logged_in": "You're already signed in as {{email}}.", "switch_to_register": "Register", "switch_to_login": "Login", "continue_with_google": "Continue with Google", diff --git a/resources/js/pages/marketing/checkout/__tests__/PaymentStep.locale.test.ts b/resources/js/pages/marketing/checkout/__tests__/PaymentStep.locale.test.ts new file mode 100644 index 0000000..6af7887 --- /dev/null +++ b/resources/js/pages/marketing/checkout/__tests__/PaymentStep.locale.test.ts @@ -0,0 +1,20 @@ +import { describe, expect, it } from 'vitest'; +import { resolvePaddleLocale } from '../steps/PaymentStep'; + +describe('resolvePaddleLocale', () => { + it('returns short locale when given region-specific tag', () => { + expect(resolvePaddleLocale('de-DE')).toBe('de'); + expect(resolvePaddleLocale('en-US')).toBe('en'); + }); + + it('falls back to english when locale unsupported', () => { + expect(resolvePaddleLocale('jp')).toBe('en'); + expect(resolvePaddleLocale('xx-YY')).toBe('en'); + expect(resolvePaddleLocale(undefined)).toBe('en'); + }); + + it('keeps supported locale codes untouched', () => { + expect(resolvePaddleLocale('fr')).toBe('fr'); + expect(resolvePaddleLocale('es')).toBe('es'); + }); +}); diff --git a/resources/js/pages/marketing/checkout/steps/AuthStep.tsx b/resources/js/pages/marketing/checkout/steps/AuthStep.tsx index c1f744a..3cc8cbb 100644 --- a/resources/js/pages/marketing/checkout/steps/AuthStep.tsx +++ b/resources/js/pages/marketing/checkout/steps/AuthStep.tsx @@ -6,7 +6,7 @@ import { useCheckoutWizard } from "../WizardContext"; import type { GoogleProfilePrefill } from '../types'; import LoginForm, { AuthUserPayload } from "../../../auth/LoginForm"; import RegisterForm, { RegisterSuccessPayload } from "../../../auth/RegisterForm"; -import { useTranslation } from 'react-i18next'; +import { Trans, useTranslation } from 'react-i18next'; import toast from 'react-hot-toast'; import { LoaderCircle } from "lucide-react"; @@ -104,10 +104,20 @@ export const AuthStep: React.FC = ({ privacyHtml, googleProfile, if (isAuthenticated && authUser) { return (
- - {t('checkout.auth_step.already_logged_in_title')} - - {t('checkout.auth_step.already_logged_in_desc', { email: authUser?.email || '' })} + + + {t('checkout.auth_step.already_logged_in_title')} + + + }} + values={{ email: authUser?.email || '' }} + /> +

+ {t('checkout.auth_step.already_logged_in_hint')} +

diff --git a/resources/js/pages/marketing/checkout/steps/ConfirmationStep.tsx b/resources/js/pages/marketing/checkout/steps/ConfirmationStep.tsx index 8a7203a..f4746ef 100644 --- a/resources/js/pages/marketing/checkout/steps/ConfirmationStep.tsx +++ b/resources/js/pages/marketing/checkout/steps/ConfirmationStep.tsx @@ -2,7 +2,7 @@ import React from "react"; import { Button } from "@/components/ui/button"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; import { useCheckoutWizard } from "../WizardContext"; -import { useTranslation } from 'react-i18next'; +import { Trans, useTranslation } from 'react-i18next'; interface ConfirmationStepProps { onViewProfile?: () => void; @@ -28,13 +28,24 @@ export const ConfirmationStep: React.FC = ({ onViewProfil window.location.href = '/event-admin'; }, [onGoToAdmin]); + const packageName = selectedPackage?.name ?? ''; + return (
- - {t('checkout.confirmation_step.welcome')} - - {t('checkout.confirmation_step.package_activated', { name: selectedPackage?.name || '' })} - {t('checkout.confirmation_step.email_sent')} + + + {t('checkout.confirmation_step.welcome')} + + + }} + values={{ name: packageName }} + /> +

+ {t('checkout.confirmation_step.email_followup')} +

diff --git a/resources/js/pages/marketing/checkout/steps/PaymentStep.tsx b/resources/js/pages/marketing/checkout/steps/PaymentStep.tsx index 8efa98d..7d61b79 100644 --- a/resources/js/pages/marketing/checkout/steps/PaymentStep.tsx +++ b/resources/js/pages/marketing/checkout/steps/PaymentStep.tsx @@ -22,6 +22,26 @@ declare global { } const PADDLE_SCRIPT_URL = 'https://cdn.paddle.com/paddle/v2/paddle.js'; +const PADDLE_SUPPORTED_LOCALES = ['en', 'de', 'fr', 'es', 'it', 'nl', 'pt', 'sv', 'da', 'fi', 'no']; + +export function resolvePaddleLocale(rawLocale?: string | null): string { + if (!rawLocale) { + return 'en'; + } + + const normalized = rawLocale.toLowerCase(); + + if (PADDLE_SUPPORTED_LOCALES.includes(normalized)) { + return normalized; + } + + const short = normalized.split('-')[0]; + if (short && PADDLE_SUPPORTED_LOCALES.includes(short)) { + return short; + } + + return 'en'; +} type PaddleEnvironment = 'sandbox' | 'production'; @@ -74,7 +94,7 @@ const PaddleCta: React.FC<{ onCheckout: () => Promise; disabled: boolean; const { t } = useTranslation('marketing'); return ( - @@ -82,7 +102,7 @@ const PaddleCta: React.FC<{ onCheckout: () => Promise; disabled: boolean; }; export const PaymentStep: React.FC = () => { - const { t } = useTranslation('marketing'); + const { t, i18n } = useTranslation('marketing'); const { selectedPackage, nextStep, paddleConfig, authUser, setPaymentCompleted } = useCheckoutWizard(); const [status, setStatus] = useState('idle'); const [message, setMessage] = useState(''); @@ -92,6 +112,11 @@ export const PaymentStep: React.FC = () => { const eventCallbackRef = useRef<(event: any) => void>(); const checkoutContainerClass = 'paddle-checkout-container'; + const paddleLocale = useMemo(() => { + const sourceLocale = i18n.language || (typeof document !== 'undefined' ? document.documentElement.lang : null); + return resolvePaddleLocale(sourceLocale ?? undefined); + }, [i18n.language]); + const isFree = useMemo(() => (selectedPackage ? Number(selectedPackage.price) <= 0 : false), [selectedPackage]); const handleFreeActivation = async () => { @@ -149,10 +174,11 @@ export const PaymentStep: React.FC = () => { frameInitialHeight: '550', frameStyle: 'width: 100%; min-width: 320px; background-color: transparent; border: none;', theme: 'light', - locale: typeof document !== 'undefined' ? document.documentElement.lang ?? 'de' : 'de', + locale: paddleLocale, }, customData: { package_id: String(selectedPackage.id), + locale: paddleLocale, }, }; @@ -182,6 +208,7 @@ export const PaymentStep: React.FC = () => { }, body: JSON.stringify({ package_id: selectedPackage.id, + locale: paddleLocale, }), }); @@ -283,6 +310,7 @@ export const PaymentStep: React.FC = () => { // eslint-disable-next-line no-console console.info('[Checkout] Initializing Paddle.js', { environment, hasToken: Boolean(clientToken) }); } + paddle.Initialize({ token: clientToken, checkout: { @@ -291,11 +319,12 @@ export const PaymentStep: React.FC = () => { frameTarget: checkoutContainerClass, frameInitialHeight: '550', frameStyle: 'width: 100%; min-width: 320px; background-color: transparent; border: none;', - locale: typeof document !== 'undefined' ? document.documentElement.lang ?? 'de' : 'de', + locale: paddleLocale, }, }, eventCallback: (event: any) => eventCallbackRef.current?.(event), }); + inlineReady = true; } @@ -313,7 +342,7 @@ export const PaymentStep: React.FC = () => { return () => { cancelled = true; }; - }, [paddleConfig?.environment, paddleConfig?.client_token, setPaymentCompleted, t]); + }, [paddleConfig?.environment, paddleConfig?.client_token, paddleLocale, setPaymentCompleted, t]); useEffect(() => { setPaymentCompleted(false); @@ -345,42 +374,46 @@ export const PaymentStep: React.FC = () => { } return ( -
-

- {t('checkout.payment_step.paddle_intro')} -

+
+
+
+ {!inlineActive && ( +
+

+ {t('checkout.payment_step.paddle_intro')} +

+ +
+ )} - {status !== 'idle' && ( - - - {status === 'processing' - ? t('checkout.payment_step.status_processing_title') - : status === 'ready' - ? t('checkout.payment_step.status_ready_title') - : status === 'error' - ? t('checkout.payment_step.status_error_title') - : t('checkout.payment_step.status_info_title')} - - - {message} - {status === 'processing' && } - - - )} + {status !== 'idle' && ( + + + {status === 'processing' + ? t('checkout.payment_step.status_processing_title') + : status === 'ready' + ? t('checkout.payment_step.status_ready_title') + : status === 'error' + ? t('checkout.payment_step.status_error_title') + : t('checkout.payment_step.status_info_title')} + + + {message} + {status === 'processing' && } + + + )} -
-
- {!inlineActive && ( - - )} +
-

- {t('checkout.payment_step.paddle_disclaimer')} -

+

+ {t('checkout.payment_step.paddle_disclaimer')} +

+
);