fix(storefront-i18n): dashboard widgets translate + correct customer-module key paths
Some checks failed
Some checks failed
Two bugs from Test 5.1 on FR storefront dashboard: 1. Loyalty + Orders dashboard cards (`StorefrontDashboardCard.title`/ `subtitle`/`value_label`) were hardcoded English. Added `language` to `WidgetContext`; customer dashboard route passes `request.state.language` through; loyalty and orders widget providers now call `translate(..., context.language)` with new `widget.*` i18n keys × 4 locales each. 2. Customer-module locale JSON has redundant top-level `customers` wrapper, so after the module-locale loader auto-namespaces under module code `customers`, the actual key path is `customers.customers.customer_number` (matches the existing `loyalty.loyalty.wallet.apple` pattern). My earlier sweep used the single-prefix path for 8 references — fixed all to double-prefix. Both bugs were visible end-of-day yesterday after the api container recreate landed `1bade6e6`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -72,12 +72,14 @@ class WidgetContext:
|
|||||||
date_to: End of date range filter
|
date_to: End of date range filter
|
||||||
limit: Maximum number of items for list widgets
|
limit: Maximum number of items for list widgets
|
||||||
include_details: Whether to include extra details (may be expensive)
|
include_details: Whether to include extra details (may be expensive)
|
||||||
|
language: Storefront/dashboard locale (e.g. "fr") for i18n of card strings
|
||||||
"""
|
"""
|
||||||
|
|
||||||
date_from: datetime | None = None
|
date_from: datetime | None = None
|
||||||
date_to: datetime | None = None
|
date_to: datetime | None = None
|
||||||
limit: int = 5
|
limit: int = 5
|
||||||
include_details: bool = False
|
include_details: bool = False
|
||||||
|
language: str | None = None
|
||||||
|
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|||||||
@@ -196,6 +196,7 @@ async def shop_account_dashboard_page(
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Collect dashboard cards from enabled modules via widget protocol
|
# Collect dashboard cards from enabled modules via widget protocol
|
||||||
|
from app.modules.contracts.widgets import WidgetContext
|
||||||
from app.modules.core.services.widget_aggregator import widget_aggregator
|
from app.modules.core.services.widget_aggregator import widget_aggregator
|
||||||
|
|
||||||
store = getattr(request.state, "store", None)
|
store = getattr(request.state, "store", None)
|
||||||
@@ -207,6 +208,9 @@ async def shop_account_dashboard_page(
|
|||||||
store_id=store.id,
|
store_id=store.id,
|
||||||
customer_id=current_customer.id,
|
customer_id=current_customer.id,
|
||||||
platform_id=platform.id,
|
platform_id=platform.id,
|
||||||
|
context=WidgetContext(
|
||||||
|
language=getattr(request.state, "language", None)
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
return templates.TemplateResponse(
|
return templates.TemplateResponse(
|
||||||
|
|||||||
@@ -156,12 +156,12 @@
|
|||||||
<!-- Name Row -->
|
<!-- Name Row -->
|
||||||
<div class="grid grid-cols-2 gap-4">
|
<div class="grid grid-cols-2 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">{{ _('customers.first_name') }} *</label>
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">{{ _('customers.customers.first_name') }} *</label>
|
||||||
<input type="text" x-model="addressForm.first_name" required
|
<input type="text" x-model="addressForm.first_name" required
|
||||||
class="block w-full rounded-md border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-white shadow-sm focus:border-primary focus:ring-primary sm:text-sm">
|
class="block w-full rounded-md border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-white shadow-sm focus:border-primary focus:ring-primary sm:text-sm">
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">{{ _('customers.last_name') }} *</label>
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">{{ _('customers.customers.last_name') }} *</label>
|
||||||
<input type="text" x-model="addressForm.last_name" required
|
<input type="text" x-model="addressForm.last_name" required
|
||||||
class="block w-full rounded-md border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-white shadow-sm focus:border-primary focus:ring-primary sm:text-sm">
|
class="block w-full rounded-md border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-white shadow-sm focus:border-primary focus:ring-primary sm:text-sm">
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -105,7 +105,7 @@
|
|||||||
<p class="text-lg font-semibold text-gray-900 dark:text-white">{{ user.created_at.strftime('%B %Y') }}</p>
|
<p class="text-lg font-semibold text-gray-900 dark:text-white">{{ user.created_at.strftime('%B %Y') }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p class="text-sm text-gray-600 dark:text-gray-400 mb-1">{{ _('customers.customer_number') }}</p>
|
<p class="text-sm text-gray-600 dark:text-gray-400 mb-1">{{ _('customers.customers.customer_number') }}</p>
|
||||||
<p class="text-lg font-semibold text-gray-900 dark:text-white">{{ user.customer_number }}</p>
|
<p class="text-lg font-semibold text-gray-900 dark:text-white">{{ user.customer_number }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -66,7 +66,7 @@
|
|||||||
<!-- First Name -->
|
<!-- First Name -->
|
||||||
<div>
|
<div>
|
||||||
<label for="first_name" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
<label for="first_name" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
||||||
{{ _('customers.first_name') }} <span class="text-red-500">*</span>
|
{{ _('customers.customers.first_name') }} <span class="text-red-500">*</span>
|
||||||
</label>
|
</label>
|
||||||
<input type="text" id="first_name" x-model="profileForm.first_name" required
|
<input type="text" id="first_name" x-model="profileForm.first_name" required
|
||||||
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm
|
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm
|
||||||
@@ -78,7 +78,7 @@
|
|||||||
<!-- Last Name -->
|
<!-- Last Name -->
|
||||||
<div>
|
<div>
|
||||||
<label for="last_name" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
<label for="last_name" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
||||||
{{ _('customers.last_name') }} <span class="text-red-500">*</span>
|
{{ _('customers.customers.last_name') }} <span class="text-red-500">*</span>
|
||||||
</label>
|
</label>
|
||||||
<input type="text" id="last_name" x-model="profileForm.last_name" required
|
<input type="text" id="last_name" x-model="profileForm.last_name" required
|
||||||
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm
|
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm
|
||||||
@@ -263,7 +263,7 @@
|
|||||||
<h3 class="text-sm font-medium text-gray-700 dark:text-gray-300 mb-4">{{ _('customers.storefront.pages.profile.account_info') }}</h3>
|
<h3 class="text-sm font-medium text-gray-700 dark:text-gray-300 mb-4">{{ _('customers.storefront.pages.profile.account_info') }}</h3>
|
||||||
<dl class="grid grid-cols-1 sm:grid-cols-2 gap-4 text-sm">
|
<dl class="grid grid-cols-1 sm:grid-cols-2 gap-4 text-sm">
|
||||||
<div>
|
<div>
|
||||||
<dt class="text-gray-500 dark:text-gray-400">{{ _('customers.customer_number') }}</dt>
|
<dt class="text-gray-500 dark:text-gray-400">{{ _('customers.customers.customer_number') }}</dt>
|
||||||
<dd class="mt-1 text-gray-900 dark:text-white font-medium" x-text="profile?.customer_number || '-'"></dd>
|
<dd class="mt-1 text-gray-900 dark:text-white font-medium" x-text="profile?.customer_number || '-'"></dd>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@@ -271,11 +271,11 @@
|
|||||||
<dd class="mt-1 text-gray-900 dark:text-white font-medium" x-text="formatDate(profile?.created_at)"></dd>
|
<dd class="mt-1 text-gray-900 dark:text-white font-medium" x-text="formatDate(profile?.created_at)"></dd>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<dt class="text-gray-500 dark:text-gray-400">{{ _('customers.total_orders') }}</dt>
|
<dt class="text-gray-500 dark:text-gray-400">{{ _('customers.customers.total_orders') }}</dt>
|
||||||
<dd class="mt-1 text-gray-900 dark:text-white font-medium" x-text="profile?.total_orders || 0"></dd>
|
<dd class="mt-1 text-gray-900 dark:text-white font-medium" x-text="profile?.total_orders || 0"></dd>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<dt class="text-gray-500 dark:text-gray-400">{{ _('customers.total_spent') }}</dt>
|
<dt class="text-gray-500 dark:text-gray-400">{{ _('customers.customers.total_spent') }}</dt>
|
||||||
<dd class="mt-1 text-gray-900 dark:text-white font-medium" x-text="formatPrice(profile?.total_spent)"></dd>
|
<dd class="mt-1 text-gray-900 dark:text-white font-medium" x-text="formatPrice(profile?.total_spent)"></dd>
|
||||||
</div>
|
</div>
|
||||||
</dl>
|
</dl>
|
||||||
|
|||||||
@@ -1,4 +1,12 @@
|
|||||||
{
|
{
|
||||||
|
"widget": {
|
||||||
|
"rewards": {
|
||||||
|
"title": "Treueprogramm",
|
||||||
|
"subtitle_member": "Punkte und Prämien anzeigen",
|
||||||
|
"subtitle_join": "Unserem Treueprogramm beitreten",
|
||||||
|
"value_label": "Punktestand"
|
||||||
|
}
|
||||||
|
},
|
||||||
"loyalty": {
|
"loyalty": {
|
||||||
"module": {
|
"module": {
|
||||||
"name": "Treueprogramme",
|
"name": "Treueprogramme",
|
||||||
|
|||||||
@@ -1,4 +1,12 @@
|
|||||||
{
|
{
|
||||||
|
"widget": {
|
||||||
|
"rewards": {
|
||||||
|
"title": "Loyalty Rewards",
|
||||||
|
"subtitle_member": "View your points & rewards",
|
||||||
|
"subtitle_join": "Join our rewards program",
|
||||||
|
"value_label": "Points Balance"
|
||||||
|
}
|
||||||
|
},
|
||||||
"loyalty": {
|
"loyalty": {
|
||||||
"module": {
|
"module": {
|
||||||
"name": "Loyalty Programs",
|
"name": "Loyalty Programs",
|
||||||
|
|||||||
@@ -1,4 +1,12 @@
|
|||||||
{
|
{
|
||||||
|
"widget": {
|
||||||
|
"rewards": {
|
||||||
|
"title": "Programme de fidélité",
|
||||||
|
"subtitle_member": "Voir vos points et récompenses",
|
||||||
|
"subtitle_join": "Rejoindre notre programme de fidélité",
|
||||||
|
"value_label": "Solde de points"
|
||||||
|
}
|
||||||
|
},
|
||||||
"loyalty": {
|
"loyalty": {
|
||||||
"module": {
|
"module": {
|
||||||
"name": "Programmes de Fidélité",
|
"name": "Programmes de Fidélité",
|
||||||
|
|||||||
@@ -1,4 +1,12 @@
|
|||||||
{
|
{
|
||||||
|
"widget": {
|
||||||
|
"rewards": {
|
||||||
|
"title": "Treieprogramm",
|
||||||
|
"subtitle_member": "Är Punkten a Belounungen kucken",
|
||||||
|
"subtitle_join": "Eisem Treieprogramm bäitrieden",
|
||||||
|
"value_label": "Punktenzuel"
|
||||||
|
}
|
||||||
|
},
|
||||||
"loyalty": {
|
"loyalty": {
|
||||||
"module": {
|
"module": {
|
||||||
"name": "Treieprogrammer",
|
"name": "Treieprogrammer",
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ class LoyaltyWidgetProvider:
|
|||||||
) -> list[StorefrontDashboardCard]:
|
) -> list[StorefrontDashboardCard]:
|
||||||
"""Provide the Loyalty Rewards card for the customer dashboard."""
|
"""Provide the Loyalty Rewards card for the customer dashboard."""
|
||||||
from app.modules.loyalty.models.loyalty_card import LoyaltyCard
|
from app.modules.loyalty.models.loyalty_card import LoyaltyCard
|
||||||
|
from app.utils.i18n import translate
|
||||||
|
|
||||||
card = (
|
card = (
|
||||||
db.query(LoyaltyCard)
|
db.query(LoyaltyCard)
|
||||||
@@ -62,17 +63,26 @@ class LoyaltyWidgetProvider:
|
|||||||
)
|
)
|
||||||
|
|
||||||
points = card.points_balance if card else None
|
points = card.points_balance if card else None
|
||||||
subtitle = "View your points & rewards" if card else "Join our rewards program"
|
lang = context.language if context else None
|
||||||
|
subtitle_key = (
|
||||||
|
"loyalty.widget.rewards.subtitle_member"
|
||||||
|
if card
|
||||||
|
else "loyalty.widget.rewards.subtitle_join"
|
||||||
|
)
|
||||||
|
|
||||||
return [
|
return [
|
||||||
StorefrontDashboardCard(
|
StorefrontDashboardCard(
|
||||||
key="loyalty.rewards",
|
key="loyalty.rewards",
|
||||||
icon="gift",
|
icon="gift",
|
||||||
title="Loyalty Rewards",
|
title=translate("loyalty.widget.rewards.title", lang),
|
||||||
subtitle=subtitle,
|
subtitle=translate(subtitle_key, lang),
|
||||||
route="account/loyalty",
|
route="account/loyalty",
|
||||||
value=points,
|
value=points,
|
||||||
value_label="Points Balance" if points is not None else None,
|
value_label=(
|
||||||
|
translate("loyalty.widget.rewards.value_label", lang)
|
||||||
|
if points is not None
|
||||||
|
else None
|
||||||
|
),
|
||||||
order=30,
|
order=30,
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,4 +1,11 @@
|
|||||||
{
|
{
|
||||||
|
"widget": {
|
||||||
|
"summary": {
|
||||||
|
"title": "Bestellungen",
|
||||||
|
"subtitle": "Bestellverlauf anzeigen",
|
||||||
|
"value_label": "Bestellungen gesamt"
|
||||||
|
}
|
||||||
|
},
|
||||||
"orders": {
|
"orders": {
|
||||||
"title": "Bestellungen",
|
"title": "Bestellungen",
|
||||||
"order": "Bestellung",
|
"order": "Bestellung",
|
||||||
|
|||||||
@@ -1,4 +1,11 @@
|
|||||||
{
|
{
|
||||||
|
"widget": {
|
||||||
|
"summary": {
|
||||||
|
"title": "Orders",
|
||||||
|
"subtitle": "View order history",
|
||||||
|
"value_label": "Total Orders"
|
||||||
|
}
|
||||||
|
},
|
||||||
"orders": {
|
"orders": {
|
||||||
"title": "Orders",
|
"title": "Orders",
|
||||||
"order": "Order",
|
"order": "Order",
|
||||||
|
|||||||
@@ -1,4 +1,11 @@
|
|||||||
{
|
{
|
||||||
|
"widget": {
|
||||||
|
"summary": {
|
||||||
|
"title": "Commandes",
|
||||||
|
"subtitle": "Voir l'historique des commandes",
|
||||||
|
"value_label": "Total des commandes"
|
||||||
|
}
|
||||||
|
},
|
||||||
"orders": {
|
"orders": {
|
||||||
"title": "Commandes",
|
"title": "Commandes",
|
||||||
"order": "Commande",
|
"order": "Commande",
|
||||||
|
|||||||
@@ -1,4 +1,11 @@
|
|||||||
{
|
{
|
||||||
|
"widget": {
|
||||||
|
"summary": {
|
||||||
|
"title": "Bestellungen",
|
||||||
|
"subtitle": "Bestellhistorik kucken",
|
||||||
|
"value_label": "Bestellunge gesamt"
|
||||||
|
}
|
||||||
|
},
|
||||||
"orders": {
|
"orders": {
|
||||||
"title": "Bestellungen",
|
"title": "Bestellungen",
|
||||||
"order": "Bestellung",
|
"order": "Bestellung",
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ class OrderWidgetProvider:
|
|||||||
) -> list[StorefrontDashboardCard]:
|
) -> list[StorefrontDashboardCard]:
|
||||||
"""Provide the Orders card for the customer dashboard."""
|
"""Provide the Orders card for the customer dashboard."""
|
||||||
from app.modules.orders.models.customer_order_stats import CustomerOrderStats
|
from app.modules.orders.models.customer_order_stats import CustomerOrderStats
|
||||||
|
from app.utils.i18n import translate
|
||||||
|
|
||||||
stats = (
|
stats = (
|
||||||
db.query(CustomerOrderStats)
|
db.query(CustomerOrderStats)
|
||||||
@@ -62,16 +63,17 @@ class OrderWidgetProvider:
|
|||||||
)
|
)
|
||||||
|
|
||||||
total_orders = stats.total_orders if stats else 0
|
total_orders = stats.total_orders if stats else 0
|
||||||
|
lang = context.language if context else None
|
||||||
|
|
||||||
return [
|
return [
|
||||||
StorefrontDashboardCard(
|
StorefrontDashboardCard(
|
||||||
key="orders.summary",
|
key="orders.summary",
|
||||||
icon="shopping-bag",
|
icon="shopping-bag",
|
||||||
title="Orders",
|
title=translate("orders.widget.summary.title", lang),
|
||||||
subtitle="View order history",
|
subtitle=translate("orders.widget.summary.subtitle", lang),
|
||||||
route="account/orders",
|
route="account/orders",
|
||||||
value=total_orders,
|
value=total_orders,
|
||||||
value_label="Total Orders",
|
value_label=translate("orders.widget.summary.value_label", lang),
|
||||||
order=10,
|
order=10,
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
Reference in New Issue
Block a user