feat(i18n): add multi-language support (en/zh) across app and server

Implement useLocale composable and shared translation dictionaries
Translate public pages, booking flow, and receipt views
Store booking locale to send localized WhatsApp notifications
This commit is contained in:
2026-05-08 15:31:44 +08:00
parent b05cfd2c0e
commit 1318e766d5
14 changed files with 789 additions and 209 deletions

View File

@@ -14,8 +14,10 @@ import type {
TicketCatalogItem,
TicketType
} from '~~/shared/booking'
import type { AppLocale } from '~~/shared/i18n'
import { calculateBookingInventorySummary, getBookingStatusLabel, isBookingStatus } from '~~/shared/booking'
import { resolveLocale } from '~~/shared/i18n'
import { randomToken, toIsoString } from './base64url'
import { ensureDatabaseReady } from './db-init'
@@ -32,6 +34,7 @@ type DbBookingRow = {
event_venue: string
customer_name: string
customer_phone: string
locale: AppLocale | string | null
booking_mode_id: string | null
booking_mode: string
booking_mode_label: string | null
@@ -125,6 +128,7 @@ function bookingSelectColumns(sql: any) {
dinner_events.venue as event_venue,
bookings.customer_name,
bookings.customer_phone,
bookings.locale,
bookings.booking_mode_id,
coalesce(booking_modes.code, bookings.booking_mode) as booking_mode,
booking_modes.label as booking_mode_label,
@@ -219,6 +223,7 @@ function mapBooking(row: DbBookingRow): PublicBooking {
event: mapDinnerEventFromBooking(row),
customerName: row.customer_name,
customerPhone: row.customer_phone,
locale: resolveLocale(row.locale),
bookingModeId: row.booking_mode_id,
bookingMode,
bookingModeLabel: row.booking_mode_label || bookingMode,
@@ -253,6 +258,7 @@ function mapReceiptBooking(row: DbBookingRow | DbBookingSeatWithBookingRow): Rec
event: mapDinnerEventFromBooking(row),
customerName: row.customer_name,
customerPhone: row.customer_phone,
locale: resolveLocale(row.locale),
bookingModeId: row.booking_mode_id,
bookingMode,
bookingModeLabel: row.booking_mode_label || bookingMode,
@@ -278,6 +284,7 @@ function mapPublicBookingToReceiptBooking(booking: PublicBooking): ReceiptBookin
event: booking.event,
customerName: booking.customerName,
customerPhone: booking.customerPhone,
locale: booking.locale,
bookingModeId: booking.bookingModeId,
bookingMode: booking.bookingMode,
bookingModeLabel: booking.bookingModeLabel,
@@ -461,6 +468,7 @@ export async function createBooking(input: {
eventId: string
customerName: string
customerPhone: string
locale: AppLocale
bookingModeId: string
bookingMode: BookingMode
quantity: number
@@ -487,6 +495,7 @@ export async function createBooking(input: {
event_id,
customer_name,
customer_phone,
locale,
booking_mode_id,
booking_mode,
quantity,
@@ -506,6 +515,7 @@ export async function createBooking(input: {
${input.eventId},
${input.customerName},
${input.customerPhone},
${input.locale},
${input.bookingModeId},
${input.bookingMode},
${input.quantity},
@@ -748,6 +758,7 @@ export async function getSeatReceiptBySeatToken(seatToken: string): Promise<{
dinner_events.venue as event_venue,
bookings.customer_name,
bookings.customer_phone,
bookings.locale,
bookings.booking_mode_id,
coalesce(booking_modes.code, bookings.booking_mode) as booking_mode,
booking_modes.label as booking_mode_label,