From 4dcee143fdb2ece0e0f409fc9b27ae8121ddbeed Mon Sep 17 00:00:00 2001 From: Padreug Date: Sat, 23 May 2026 21:11:05 +0200 Subject: [PATCH] fix(activities): i18n keys + retry useOwnedTickets after transient failure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit i18n: add the missing keys the ticket purchase + owned-tickets surfaces use across en/es/fr — activities.detail.{buyTicket, buyAnotherTicket, viewMyTickets, ticketsOwned, unlimitedTickets} and activities.filters.myTickets. Without these the runtime fell back to the literal key strings + spammed [intlify] warnings; the filter chip rendered the bare key text on logged-in sessions. ticketsOwned uses i18n pluralization so "You have 1 ticket" vs "You have 5 tickets" both come out correct. useOwnedTickets: the hasAutoLoaded guard prevented retries after a transient backend failure (e.g. an LNbits restart mid-fetch). The composable would stay stuck with tickets = [] forever, so the buyer landing on a fresh detail page right after a transient error saw no badges anywhere. Detect the "previous load didn't actually hydrate" state (lastLoadedUserId still null while authenticated) and retry on the next useOwnedTickets() call. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/i18n/locales/en.ts | 6 ++++++ src/i18n/locales/es.ts | 6 ++++++ src/i18n/locales/fr.ts | 6 ++++++ src/i18n/types.ts | 6 ++++++ .../activities/composables/useOwnedTickets.ts | 15 +++++++++++++-- 5 files changed, 37 insertions(+), 2 deletions(-) diff --git a/src/i18n/locales/en.ts b/src/i18n/locales/en.ts index daeb845..38bb64a 100644 --- a/src/i18n/locales/en.ts +++ b/src/i18n/locales/en.ts @@ -57,6 +57,7 @@ const messages: LocaleMessages = { tomorrow: 'Tomorrow', thisWeek: 'This Week', thisMonth: 'This Month', + myTickets: 'My tickets', }, categories: { concert: 'Concert', @@ -96,6 +97,11 @@ const messages: LocaleMessages = { when: 'When', tickets: 'Tickets', ticketsAvailable: '{count} tickets available', + ticketsOwned: 'You have {count} ticket | You have {count} tickets', + unlimitedTickets: 'Unlimited tickets', + buyTicket: 'Buy ticket', + buyAnotherTicket: 'Buy another ticket', + viewMyTickets: 'View in My Tickets', soldOut: 'Sold Out', free: 'Free', }, diff --git a/src/i18n/locales/es.ts b/src/i18n/locales/es.ts index 76a5f56..b2603e9 100644 --- a/src/i18n/locales/es.ts +++ b/src/i18n/locales/es.ts @@ -57,6 +57,7 @@ const messages: LocaleMessages = { tomorrow: 'Mañana', thisWeek: 'Esta semana', thisMonth: 'Este mes', + myTickets: 'Mis boletos', }, categories: { concert: 'Concierto', @@ -96,6 +97,11 @@ const messages: LocaleMessages = { when: 'Cuándo', tickets: 'Boletos', ticketsAvailable: '{count} boletos disponibles', + ticketsOwned: 'Tienes {count} boleto | Tienes {count} boletos', + unlimitedTickets: 'Boletos ilimitados', + buyTicket: 'Comprar boleto', + buyAnotherTicket: 'Comprar otro boleto', + viewMyTickets: 'Ver en Mis boletos', soldOut: 'Agotado', free: 'Gratis', }, diff --git a/src/i18n/locales/fr.ts b/src/i18n/locales/fr.ts index 19b8ece..388f602 100644 --- a/src/i18n/locales/fr.ts +++ b/src/i18n/locales/fr.ts @@ -57,6 +57,7 @@ const messages: LocaleMessages = { tomorrow: 'Demain', thisWeek: 'Cette semaine', thisMonth: 'Ce mois-ci', + myTickets: 'Mes billets', }, categories: { concert: 'Concert', @@ -96,6 +97,11 @@ const messages: LocaleMessages = { when: 'Quand', tickets: 'Billets', ticketsAvailable: '{count} billets disponibles', + ticketsOwned: 'Vous avez {count} billet | Vous avez {count} billets', + unlimitedTickets: 'Billets illimités', + buyTicket: 'Acheter un billet', + buyAnotherTicket: 'Acheter un autre billet', + viewMyTickets: 'Voir dans Mes billets', soldOut: 'Épuisé', free: 'Gratuit', }, diff --git a/src/i18n/types.ts b/src/i18n/types.ts index 0ca44dd..d44f236 100644 --- a/src/i18n/types.ts +++ b/src/i18n/types.ts @@ -58,6 +58,7 @@ export interface LocaleMessages { tomorrow: string thisWeek: string thisMonth: string + myTickets: string } categories: Record detail: { @@ -71,6 +72,11 @@ export interface LocaleMessages { when: string tickets: string ticketsAvailable: string + ticketsOwned: string + unlimitedTickets: string + buyTicket: string + buyAnotherTicket: string + viewMyTickets: string soldOut: string free: string } diff --git a/src/modules/activities/composables/useOwnedTickets.ts b/src/modules/activities/composables/useOwnedTickets.ts index 5ea5d44..e540fb3 100644 --- a/src/modules/activities/composables/useOwnedTickets.ts +++ b/src/modules/activities/composables/useOwnedTickets.ts @@ -81,8 +81,8 @@ function paidCount(activityId: string): number { export function useOwnedTickets() { const { isAuthenticated, currentUser } = useAuth() - // First call kicks off the initial load. Subsequent calls just - // attach to the shared state. + // First call kicks off the initial load + sets up the auth-change + // watcher. Subsequent calls attach to the shared state. if (!hasAutoLoaded) { hasAutoLoaded = true fetchTickets() @@ -97,6 +97,17 @@ export function useOwnedTickets() { if (id !== lastLoadedUserId) fetchTickets() }, ) + } else if ( + !isLoading.value && + isAuthenticated.value && + currentUser.value && + lastLoadedUserId !== currentUser.value.id + ) { + // A previous load failed (lastLoadedUserId stayed null) or the + // user changed identity while the singleton was idle. Retry — + // the buyer landing on a fresh detail page after a transient + // backend hiccup shouldn't be stuck with empty tickets. + fetchTickets() } return {