fix(checkout): failure banner rendered "undefined" on full-page return before i18n loaded#4455
Conversation
…before i18n loaded After the #4449 redirect-mode switch, a failed payment does a full-page redirect back to the dashboard, and panel-layout renders the checkout-failure banner during the cold init — before i18next's full bundle has loaded. `t('components.checkoutFailureBanner.message')` returned undefined, so the user saw a red "undefined" banner (confirmed on prod after a real declined 3DS payment). The success banner is unaffected — its strings are hardcoded, and it also defers render via waitForEntitlement. Give every t() call in the banner a defaultValue (English, matching en.json) so it renders correctly regardless of i18n readiness and still picks up translations once the bundle loads. Refs #4449 #4454
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
| <span>${t('components.checkoutFailureBanner.message', { defaultValue: "Payment couldn't be completed. No charge was made." })}</span> | ||
| ${retryButton} | ||
| <button id="cf-dismiss-btn" aria-label="${t('components.checkoutFailureBanner.dismiss')}" style="background:transparent;color:#fff;border:none;cursor:pointer;font-size:18px;padding:0 4px;line-height:1;">×</button> | ||
| <button id="cf-dismiss-btn" aria-label="${t('components.checkoutFailureBanner.dismiss', { defaultValue: 'Dismiss' })}" style="background:transparent;color:#fff;border:none;cursor:pointer;font-size:18px;padding:0 4px;line-height:1;">×</button> |
There was a problem hiding this comment.
Static message/dismiss strings won't be re-localized after bundle loads for non-English users
The message span and dismiss aria-label are baked into the static HTML string at render time. The wm:i18n:resources-loaded healer (handleI18nResourcesLoaded) is scoped to this.state.container, which explicitly excludes body-level overlays — and this banner appends to document.body directly. So a French user who lands on this banner during the cold-init window will see English text permanently; the healer never reaches it. The retry/retrying strings are fine since they're set on click (after the bundle has loaded).
This is a pre-existing architectural limitation of the container-scoped healer, not introduced by this PR, and English is strictly better than "undefined". Worth noting if full localization of this path is ever required — the fix would be to extend the healer's scope or re-invoke showCheckoutFailureBanner on the wm:i18n:resources-loaded event (after removing the idempotency guard).
|
Reviewed Greptile's note (verified at head Confirmed: Net: for the statically-rendered Decision: ship as-is. English-on-a-narrow-cold-init-failure is strictly better than If we want perfect i18n there later, the clean follow-up is a one-time |
The bug (prod, post-#4454)
After the redirect-mode switch (#4449/#4454), a failed payment does a full-page redirect back to the dashboard.
panel-layoutrenders the checkout-failure banner immediately during the cold init — before i18next's full bundle has loaded (i18n loads a shell subset first, then the full bundle async). Sot('components.checkoutFailureBanner.message')returnedundefinedand the user saw a red "undefined" banner (confirmed on prod after a real declined 3DS payment).The success banner is immune — its strings are hardcoded English (
checkout.ts:1221), and it defers render viawaitForEntitlement. Redirect mode is what made the immediate cold-load failure-banner path common (the old overlay never reloaded the page).Fix
Give every
t()call incheckout-failure-banner.tsadefaultValue(English, matchingen.json):message,retry,retrying,dismiss. The banner now renders correct English regardless of i18n readiness, and still picks up translations once the bundle loads. 5 lines, lint clean, no test asserted the raw key.Context
This is the only residual from the 3DS redirect work — which is confirmed working end-to-end on prod: 3DS now runs top-level (Stripe 3DS + bank ACS), success shows the proper green banner, and failures return to
/dashboard?wm_checkout=return. Part of #4440.Refs #4449 #4454