fix(i18n): complete translations for production launch and fix CMS store context
Some checks failed
CI / ruff (push) Successful in 10s
CI / validate (push) Successful in 26s
CI / dependency-scanning (push) Successful in 28s
CI / pytest (push) Failing after 3h13m27s
CI / docs (push) Has been cancelled
CI / deploy (push) Has been cancelled

- Replace CMS custom get_store_context() with core utility (same fix as loyalty)
- Add 85 missing translation keys across fr/de/lb for core, tenancy, messaging,
  customers, and loyalty modules
- Convert 21 client-side $t() calls to server-side _() in 9 loyalty templates
- Fix 3 broken translation keys in store/cards.html

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-14 23:38:54 +01:00
parent bc7431943a
commit 6c07f6cbb2
27 changed files with 189 additions and 82 deletions

View File

@@ -78,6 +78,7 @@
"terminal": "Terminal",
"customer_cards": "Kundenkarten",
"statistics": "Statistiken",
"program": "Programm",
"overview": "Übersicht",
"settings": "Einstellungen"
},

View File

@@ -78,6 +78,7 @@
"terminal": "Terminal",
"customer_cards": "Clientekaarten",
"statistics": "Statistiken",
"program": "Programm",
"overview": "Iwwersiicht",
"settings": "Astellungen"
},

View File

@@ -13,7 +13,7 @@
{% block content %}
{% call detail_page_header("merchant?.name || 'Merchant Loyalty'", '/admin/loyalty/programs', subtitle_show='merchant') %}
<span x-text="program ? $t('loyalty.admin.merchant_detail.program_active') : $t('loyalty.admin.merchant_detail.no_program_subtitle')"></span>
<span x-text="program ? '{{ _('loyalty.admin.merchant_detail.program_active')|replace("'", "\\'") }}' : '{{ _('loyalty.admin.merchant_detail.no_program_subtitle')|replace("'", "\\'") }}'"></span>
{% endcall %}
{{ loading_state(_('loyalty.admin.merchant_detail.loading')) }}
@@ -205,14 +205,14 @@
<p class="text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase mb-2">{{ _('loyalty.admin.merchant_detail.self_enrollment') }}</p>
<span class="inline-flex items-center px-2 py-1 text-xs font-semibold leading-tight rounded-full"
:class="settings?.allow_self_enrollment ? 'text-green-700 bg-green-100 dark:bg-green-700 dark:text-green-100' : 'text-gray-700 bg-gray-100 dark:bg-gray-700 dark:text-gray-100'">
<span x-text="settings?.allow_self_enrollment ? $t('loyalty.admin.merchant_detail.allowed') : $t('loyalty.admin.merchant_detail.disabled')"></span>
<span x-text="settings?.allow_self_enrollment ? '{{ _('loyalty.admin.merchant_detail.allowed')|replace("'", "\\'") }}' : '{{ _('loyalty.admin.merchant_detail.disabled')|replace("'", "\\'") }}'"></span>
</span>
</div>
<div>
<p class="text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase mb-2">{{ _('loyalty.admin.merchant_detail.cross_location_redemption') }}</p>
<span class="inline-flex items-center px-2 py-1 text-xs font-semibold leading-tight rounded-full"
:class="settings?.allow_cross_location_redemption !== false ? 'text-green-700 bg-green-100 dark:bg-green-700 dark:text-green-100' : 'text-gray-700 bg-gray-100 dark:bg-gray-700 dark:text-gray-100'">
<span x-text="settings?.allow_cross_location_redemption !== false ? $t('loyalty.admin.merchant_detail.allowed') : $t('loyalty.admin.merchant_detail.disabled')"></span>
<span x-text="settings?.allow_cross_location_redemption !== false ? '{{ _('loyalty.admin.merchant_detail.allowed')|replace("'", "\\'") }}' : '{{ _('loyalty.admin.merchant_detail.disabled')|replace("'", "\\'") }}'"></span>
</span>
</div>
</div>

View File

@@ -170,7 +170,7 @@
:disabled="saving"
class="flex items-center px-4 py-2 text-sm font-medium text-white bg-purple-600 border border-transparent rounded-lg hover:bg-purple-700 focus:outline-none focus:shadow-outline-purple disabled:opacity-50">
<span x-show="saving" x-html="$icon('spinner', 'w-4 h-4 mr-2 animate-spin')"></span>
<span x-text="saving ? $t('loyalty.common.saving') : $t('loyalty.admin.merchant_settings.save_settings')"></span>
<span x-text="saving ? '{{ _('loyalty.common.saving')|replace("'", "\\'") }}' : '{{ _('loyalty.admin.merchant_settings.save_settings')|replace("'", "\\'") }}'"></span>
</button>
</div>
</form>

View File

@@ -12,7 +12,7 @@
{% block content %}
{% call detail_page_header("isNewProgram ? 'Create Program: ' + (merchant?.name || '') : 'Edit Program: ' + (merchant?.name || '')", '/admin/loyalty/merchants/' ~ merchant_id, subtitle_show='merchant') %}
<span x-text="isNewProgram ? $t('loyalty.admin.program_edit.create_subtitle') : $t('loyalty.admin.program_edit.edit_subtitle')"></span>
<span x-text="isNewProgram ? '{{ _('loyalty.admin.program_edit.create_subtitle')|replace("'", "\\'") }}' : '{{ _('loyalty.admin.program_edit.edit_subtitle')|replace("'", "\\'") }}'"></span>
{% endcall %}
{{ loading_state(_('loyalty.admin.program_edit.loading')) }}

View File

@@ -139,7 +139,7 @@
<div class="flex flex-col items-center">
<span x-html="$icon('gift', 'w-12 h-12 mb-2 text-gray-300')"></span>
<p class="font-medium">{{ _('loyalty.admin.programs.no_programs') }}</p>
<p class="text-xs mt-1" x-text="filters.search || filters.is_active ? $t('loyalty.admin.programs.adjust_filters') : $t('loyalty.admin.programs.no_merchants_yet')"></p>
<p class="text-xs mt-1" x-text="filters.search || filters.is_active ? '{{ _('loyalty.admin.programs.adjust_filters')|replace("'", "\\'") }}' : '{{ _('loyalty.admin.programs.no_merchants_yet')|replace("'", "\\'") }}'"></p>
</div>
</td>
</tr>
@@ -177,7 +177,7 @@
<span x-text="program.loyalty_type?.charAt(0).toUpperCase() + program.loyalty_type?.slice(1) || 'Unknown'"></span>
</span>
<p class="text-xs text-gray-500 mt-1" x-show="program.is_points_enabled">
<span x-text="program.points_per_euro"></span> <span x-text="$t('loyalty.admin.programs.pt_per_eur')"></span>
<span x-text="program.points_per_euro"></span> {{ _('loyalty.admin.programs.pt_per_eur') }}
</p>
</td>
@@ -201,7 +201,7 @@
<td class="px-4 py-3 text-xs">
<span class="inline-flex items-center px-2 py-1 font-semibold leading-tight rounded-full"
:class="program.is_active ? 'text-green-700 bg-green-100 dark:bg-green-700 dark:text-green-100' : 'text-gray-700 bg-gray-100 dark:text-gray-100 dark:bg-gray-700'">
<span x-text="program.is_active ? $t('loyalty.common.active') : $t('loyalty.common.inactive')"></span>
<span x-text="program.is_active ? '{{ _('loyalty.common.active')|replace("'", "\\'") }}' : '{{ _('loyalty.common.inactive')|replace("'", "\\'") }}'"></span>
</span>
</td>

View File

@@ -305,7 +305,7 @@
<button type="submit" :disabled="saving"
class="flex items-center px-6 py-2 text-sm font-medium text-white bg-purple-600 rounded-lg hover:bg-purple-700 disabled:opacity-50">
<span x-show="saving" x-html="$icon('spinner', 'w-4 h-4 mr-2 animate-spin')"></span>
<span x-text="saving ? $t('loyalty.common.saving') : (isNewProgram ? $t('loyalty.shared.program_form.create_program') : $t('loyalty.shared.program_form.save_changes'))"></span>
<span x-text="saving ? '{{ _('loyalty.common.saving')|replace("'", "\\'") }}' : (isNewProgram ? '{{ _('loyalty.shared.program_form.create_program')|replace("'", "\\'") }}' : '{{ _('loyalty.shared.program_form.save_changes')|replace("'", "\\'") }}')"></span>
</button>
</div>
</div>

View File

@@ -163,7 +163,7 @@
<p class="text-xs text-gray-500 dark:text-gray-400 mb-1">{{ _('loyalty.shared.program_view.staff_pin_required') }}</p>
<span class="inline-flex items-center px-2 py-1 text-xs font-semibold leading-tight rounded-full"
:class="program?.require_staff_pin ? 'text-green-700 bg-green-100 dark:bg-green-700 dark:text-green-100' : 'text-gray-700 bg-gray-100 dark:bg-gray-700 dark:text-gray-100'">
<span x-text="program?.require_staff_pin ? $t('loyalty.common.yes') : $t('loyalty.common.no')"></span>
<span x-text="program?.require_staff_pin ? '{{ _('loyalty.common.yes')|replace("'", "\\'") }}' : '{{ _('loyalty.common.no')|replace("'", "\\'") }}'"></span>
</span>
</div>
</div>

View File

@@ -122,8 +122,8 @@
<td colspan="6" class="px-4 py-8 text-center text-gray-600 dark:text-gray-400">
<div class="flex flex-col items-center">
<span x-html="$icon('users', 'w-12 h-12 mb-2 text-gray-300')"></span>
<p class="font-medium" x-text="$t('loyalty.store.cards.no_members_found')"></p>
<p class="text-xs mt-1" x-text="filters.search ? $t('loyalty.store.cards.try_adjusting_search') : $t('loyalty.store.cards.enroll_first_customer')"></p>
<p class="font-medium">{{ _('loyalty.store.cards.no_members') }}</p>
<p class="text-xs mt-1" x-text="filters.search ? '{{ _('loyalty.store.cards.adjust_search')|replace("'", "\\'") }}' : '{{ _('loyalty.store.cards.enroll_first')|replace("'", "\\'") }}'"></p>
</div>
</td>
</tr>
@@ -150,13 +150,12 @@
<td class="px-4 py-3 text-xs">
<span class="px-2 py-1 font-semibold leading-tight rounded-full"
:class="card.is_active ? 'text-green-700 bg-green-100 dark:bg-green-700 dark:text-green-100' : 'text-gray-700 bg-gray-100 dark:bg-gray-700 dark:text-gray-100'"
x-text="card.is_active ? $t('loyalty.common.active') : $t('loyalty.common.inactive')"></span>
x-text="card.is_active ? '{{ _('loyalty.common.active')|replace("'", "\\'") }}' : '{{ _('loyalty.common.inactive')|replace("'", "\\'") }}'"></span>
</td>
<td class="px-4 py-3">
<a :href="'/store/{{ store_code }}/loyalty/cards/' + card.id"
class="text-purple-600 hover:text-purple-700 dark:text-purple-400"
x-text="$t('loyalty.common.view')">
</a>
>{{ _('loyalty.common.view') }}</a>
</td>
</tr>
</template>

View File

@@ -108,7 +108,7 @@
<button type="submit" :disabled="enrolling || !form.first_name || !form.email"
class="flex items-center px-6 py-2 text-sm font-medium text-white bg-purple-600 rounded-lg hover:bg-purple-700 disabled:opacity-50">
<span x-show="enrolling" x-html="$icon('spinner', 'w-4 h-4 mr-2 animate-spin')"></span>
<span x-text="enrolling ? $t('loyalty.store.enroll.enrolling') : $t('loyalty.store.enroll.enroll_customer')"></span>
<span x-text="enrolling ? '{{ _('loyalty.store.enroll.enrolling')|replace("'", "\\'") }}' : '{{ _('loyalty.store.enroll.enroll_customer')|replace("'", "\\'") }}'"></span>
</button>
</div>
</form>

View File

@@ -82,7 +82,7 @@
class="w-full flex items-center justify-center px-4 py-3 text-sm font-medium text-white bg-purple-600 rounded-lg hover:bg-purple-700 focus:outline-none disabled:opacity-50"
>
<span x-show="lookingUp" x-html="$icon('spinner', 'w-5 h-5 mr-2 animate-spin')"></span>
<span x-text="lookingUp ? $t('loyalty.store.terminal.looking_up') : $t('loyalty.store.terminal.look_up_customer')"></span>
<span x-text="lookingUp ? '{{ _('loyalty.store.terminal.looking_up')|replace("'", "\\'") }}' : '{{ _('loyalty.store.terminal.look_up_customer')|replace("'", "\\'") }}'"></span>
</button>
<!-- Divider -->
@@ -137,7 +137,7 @@
<!-- Points balance (for points and hybrid) -->
<template x-if="program?.is_points_enabled">
<div>
<p class="text-sm font-medium text-gray-600 dark:text-gray-400" x-text="$t('loyalty.store.terminal.points_balance')"></p>
<p class="text-sm font-medium text-gray-600 dark:text-gray-400">{{ _('loyalty.store.terminal.points_balance') }}</p>
<p class="text-3xl font-bold"
:style="'color: ' + (program?.card_color || '#4F46E5')"
x-text="formatNumber(selectedCard?.points_balance || 0)"></p>
@@ -146,7 +146,7 @@
<!-- Stamps progress (for stamps and hybrid) -->
<template x-if="program?.is_stamps_enabled">
<div :class="program?.is_points_enabled ? 'mt-3 pt-3 border-t border-gray-200 dark:border-gray-700' : ''">
<p class="text-sm font-medium text-gray-600 dark:text-gray-400" x-text="$t('loyalty.store.terminal.stamps')"></p>
<p class="text-sm font-medium text-gray-600 dark:text-gray-400">{{ _('loyalty.store.terminal.stamps') }}</p>
<p class="text-3xl font-bold"
:style="'color: ' + (program?.card_color || '#4F46E5')"
x-text="(selectedCard?.stamp_count || 0) + ' / ' + (program?.stamps_target || 10)"></p>
@@ -175,7 +175,7 @@
{{ _('loyalty.store.terminal.add_stamp') }}
</button>
<template x-if="!selectedCard?.can_stamp && selectedCard?.cooldown_ends_at">
<p class="text-xs text-red-500 mt-2" x-text="$t('loyalty.store.terminal.cooldown_active')"></p>
<p class="text-xs text-red-500 mt-2">{{ _('loyalty.store.terminal.cooldown_active') }}</p>
</template>
</div>
</template>
@@ -357,7 +357,7 @@
:disabled="pinDigits.length !== 4 || processing"
class="px-4 py-2 text-sm font-medium text-white bg-purple-600 rounded-lg hover:bg-purple-700 disabled:opacity-50">
<span x-show="processing" x-html="$icon('spinner', 'w-4 h-4 mr-2 inline animate-spin')"></span>
<span x-text="processing ? $t('loyalty.store.terminal.processing') : $t('loyalty.store.terminal.confirm')"></span>
<span x-text="processing ? '{{ _('loyalty.store.terminal.processing')|replace("'", "\\'") }}' : '{{ _('loyalty.store.terminal.confirm')|replace("'", "\\'") }}'"></span>
</button>
</div>
</div>