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

@@ -4,6 +4,7 @@ import {
formatBookingCurrency
} from '~~/shared/booking'
import { hasValidFullName, isValidPhoneNumber, normalizeFullName, normalizePhoneNumber } from '~~/shared/auth'
import { resolveLocale } from '~~/shared/i18n'
import { assertBadRequest } from './http'
@@ -14,6 +15,7 @@ export function parseCreateBookingInput(body: {
quantity?: number
ticketType?: TicketType
personInChargeId?: string
locale?: string | null
}) {
const customerName = normalizeFullName(body.customerName || '')
const customerPhone = normalizePhoneNumber(body.customerPhone || '')
@@ -21,6 +23,7 @@ export function parseCreateBookingInput(body: {
const ticketType = typeof body.ticketType === 'string' ? body.ticketType.trim().toLowerCase() : body.ticketType
const quantity = Number(body.quantity)
const personInChargeId = (body.personInChargeId || '').trim()
const locale = resolveLocale(body.locale)
assertBadRequest(hasValidFullName(customerName), 'Guest or organizer name must be at least 2 characters')
assertBadRequest(isValidPhoneNumber(customerPhone), 'Phone number must include a country code, e.g. +60123456789')
@@ -35,7 +38,8 @@ export function parseCreateBookingInput(body: {
bookingMode,
quantity,
ticketType,
personInChargeId
personInChargeId,
locale
}
}
@@ -66,6 +70,21 @@ export function parseBookingPicTransferInput(body: {
}
export function buildBookingMessage(booking: PublicBooking, confirmationUrl: string) {
if (booking.locale === 'zh') {
return [
`我想预订 ${booking.event.title} 的票券。`,
'',
`姓名:${booking.customerName}`,
`联络号码:${booking.customerPhone}`,
`座位:${booking.seatCount}`,
`票券类别:${booking.ticketLabel || booking.ticketType.toUpperCase()}`,
`总价:${formatBookingCurrency(booking.totalPrice, booking.locale)}`,
'',
'负责人确认链接:',
confirmationUrl
].join('\n')
}
return [
`I'd like to book tickets for the ${booking.event.title}.`,
'',
@@ -73,7 +92,7 @@ export function buildBookingMessage(booking: PublicBooking, confirmationUrl: str
`Phone Number: ${booking.customerPhone}`,
`Seats: ${booking.seatCount}`,
`Ticket Category: ${booking.ticketLabel || booking.ticketType.toUpperCase()}`,
`Total Price: ${formatBookingCurrency(booking.totalPrice)}`,
`Total Price: ${formatBookingCurrency(booking.totalPrice, booking.locale)}`,
'',
'PIC confirmation link:',
confirmationUrl