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

@@ -1,3 +1,5 @@
import type { AppLocale } from './i18n'
export type BookingMode = string
export type TicketType = string
export type BookingStatus = 'pending' | 'confirmed'
@@ -41,6 +43,7 @@ export interface PublicBooking {
event: DinnerEvent
customerName: string
customerPhone: string
locale: AppLocale
bookingModeId: string | null
bookingMode: BookingMode
bookingModeLabel: string
@@ -68,6 +71,7 @@ export interface ReceiptBooking {
event: DinnerEvent
customerName: string
customerPhone: string
locale: AppLocale
bookingModeId: string | null
bookingMode: BookingMode
bookingModeLabel: string
@@ -158,11 +162,15 @@ export function isBookingStatus(value: string | null | undefined): value is Book
return value === 'pending' || value === 'confirmed'
}
export function getBookingStatusLabel(value: BookingStatus | string, label?: string | null) {
if (label) {
export function getBookingStatusLabel(value: BookingStatus | string, label?: string | null, locale: AppLocale = 'en') {
if (label && locale !== 'zh') {
return label
}
if (locale === 'zh') {
return value === 'confirmed' ? '已确认' : '等待负责人确认'
}
return value === 'confirmed' ? 'Confirmed' : 'Pending PIC confirmation'
}
@@ -178,8 +186,8 @@ export function getBookingTicketLabel(booking: Pick<PublicBooking | ReceiptBooki
return booking.ticketLabel || booking.ticketType.toUpperCase()
}
export function formatBookingCurrency(value: number) {
return new Intl.NumberFormat('en-MY', {
export function formatBookingCurrency(value: number, locale: AppLocale = 'en') {
return new Intl.NumberFormat(locale === 'zh' ? 'zh-MY' : 'en-MY', {
style: 'currency',
currency: 'MYR',
minimumFractionDigits: 0,
@@ -187,8 +195,8 @@ export function formatBookingCurrency(value: number) {
}).format(value)
}
export function getSeatLabel(seatNumber: number) {
return `Seat ${seatNumber}`
export function getSeatLabel(seatNumber: number, locale: AppLocale = 'en') {
return locale === 'zh' ? `座位 ${seatNumber}` : `Seat ${seatNumber}`
}
export function calculateBookingInventorySummary(