fixed paddle locale

This commit is contained in:
Codex Agent
2025-10-27 21:05:06 +01:00
parent 5432456ffd
commit f29067f570
6 changed files with 146 additions and 66 deletions

View File

@@ -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<void>; disabled: boolean;
const { t } = useTranslation('marketing');
return (
<Button size="lg" className="w-full" disabled={disabled} onClick={onCheckout}>
<Button size="lg" className="w-full sm:w-auto" disabled={disabled} onClick={onCheckout}>
{isProcessing && <LoaderCircle className="mr-2 h-4 w-4 animate-spin" />}
{t('checkout.payment_step.pay_with_paddle')}
</Button>
@@ -82,7 +102,7 @@ const PaddleCta: React.FC<{ onCheckout: () => Promise<void>; 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<PaymentStatus>('idle');
const [message, setMessage] = useState<string>('');
@@ -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 (
<div className="space-y-6">
<p className="text-sm text-muted-foreground">
{t('checkout.payment_step.paddle_intro')}
</p>
<div className="space-y-4">
<div className="rounded-lg border bg-card p-6 shadow-sm">
<div className="space-y-4">
{!inlineActive && (
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
<p className="text-sm text-muted-foreground">
{t('checkout.payment_step.paddle_intro')}
</p>
<PaddleCta
onCheckout={startPaddleCheckout}
disabled={status === 'processing'}
isProcessing={status === 'processing'}
/>
</div>
)}
{status !== 'idle' && (
<Alert variant={status === 'error' ? 'destructive' : 'secondary'}>
<AlertTitle>
{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')}
</AlertTitle>
<AlertDescription className="flex items-center gap-3">
<span>{message}</span>
{status === 'processing' && <LoaderCircle className="h-4 w-4 animate-spin" />}
</AlertDescription>
</Alert>
)}
{status !== 'idle' && (
<Alert variant={status === 'error' ? 'destructive' : 'secondary'}>
<AlertTitle>
{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')}
</AlertTitle>
<AlertDescription className="flex items-center gap-3">
<span>{message}</span>
{status === 'processing' && <LoaderCircle className="h-4 w-4 animate-spin" />}
</AlertDescription>
</Alert>
)}
<div className="rounded-lg border bg-card p-6 shadow-sm space-y-4">
<div className={`${checkoutContainerClass} min-h-[200px]`} />
{!inlineActive && (
<PaddleCta
onCheckout={startPaddleCheckout}
disabled={status === 'processing'}
isProcessing={status === 'processing'}
/>
)}
<div className={`${checkoutContainerClass} min-h-[360px]`} />
<p className="text-xs text-muted-foreground">
{t('checkout.payment_step.paddle_disclaimer')}
</p>
<p className={`text-xs text-muted-foreground ${inlineActive ? 'text-center' : 'sm:text-right'}`}>
{t('checkout.payment_step.paddle_disclaimer')}
</p>
</div>
</div>
</div>
);