From aa8ca59493bc90ee802a9d694d7f496feadc9d88 Mon Sep 17 00:00:00 2001 From: Samir Boulahtit Date: Sat, 23 May 2026 23:16:32 +0200 Subject: [PATCH] fix(loyalty-terminal): localise cooldown toast (was raw English) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When earn-points or add-stamp was rejected by the new cooldown enforcement, the terminal showed the raw English error message from the backend in the toast, even on FR / DE / LB locales: "Transaction failed: Please wait 15 minutes between point-earning..." Two-part fix: 1. static/shared/js/api-client.js — when raising apiError on non-OK responses, also propagate the `details` payload from the response body (alongside the existing errorCode). Without this the catch sites had no structured access to e.g. cooldown_minutes. 2. loyalty-terminal.js — in the catch around the transaction dispatch, when error.errorCode is POINTS_COOLDOWN or STAMP_COOLDOWN, render a new localised key loyalty.store.terminal.cooldown_wait_minutes with {minutes} interpolated from error.details.cooldown_minutes (with a fallback to this.program.cooldown_minutes). Toast type switches to 'warning' since the rejection is soft (try again later) rather than a hard failure. Other errors keep the existing 'transaction_failed' path so nothing else regresses. Added the new key in en / fr / de / lb under the existing loyalty.store.terminal.* namespace (sibling of the existing cooldown_active label). Co-Authored-By: Claude Opus 4.7 (1M context) --- app/modules/loyalty/static/store/js/loyalty-terminal.js | 9 ++++++++- static/shared/js/api-client.js | 4 ++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/app/modules/loyalty/static/store/js/loyalty-terminal.js b/app/modules/loyalty/static/store/js/loyalty-terminal.js index 688e0f7e..e6dcc314 100644 --- a/app/modules/loyalty/static/store/js/loyalty-terminal.js +++ b/app/modules/loyalty/static/store/js/loyalty-terminal.js @@ -275,7 +275,14 @@ function storeLoyaltyTerminal() { await this.loadRecentTransactions(); } catch (error) { - Utils.showToast(I18n.t('loyalty.store.terminal.transaction_failed', {message: error.message}), 'error'); + // Localise well-known errors by error_code instead of + // showing the raw English message from the API. + if (error.errorCode === 'POINTS_COOLDOWN' || error.errorCode === 'STAMP_COOLDOWN') { + const minutes = error.details?.cooldown_minutes ?? this.program?.cooldown_minutes ?? ''; + Utils.showToast(I18n.t('loyalty.store.terminal.cooldown_wait_minutes', {minutes}), 'warning'); + } else { + Utils.showToast(I18n.t('loyalty.store.terminal.transaction_failed', {message: error.message}), 'error'); + } loyaltyTerminalLog.error('Transaction failed:', error); } finally { this.processing = false; diff --git a/static/shared/js/api-client.js b/static/shared/js/api-client.js index 8d44d57d..a3b5fca4 100644 --- a/static/shared/js/api-client.js +++ b/static/shared/js/api-client.js @@ -171,6 +171,10 @@ class APIClient { const apiError = new Error(errorMessage); apiError.status = response.status; apiError.errorCode = data.error_code; + // Propagate the details payload so callers can localise the + // toast (e.g. "cooldown_ends" / "cooldown_minutes" for + // POINTS_COOLDOWN / STAMP_COOLDOWN). + apiError.details = data.details || null; throw apiError; }