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:
@@ -4,39 +4,39 @@
|
||||
<UCard class="border border-default bg-default shadow-sm">
|
||||
<template #header>
|
||||
<div class="space-y-2">
|
||||
<UBadge label="Staff Access" color="primary" variant="soft" class="rounded-full" />
|
||||
<UBadge :label="t('login.badge')" color="primary" variant="soft" class="rounded-full" />
|
||||
<h1 class="text-3xl font-bold text-highlighted">
|
||||
Login to the management system
|
||||
{{ t('login.title') }}
|
||||
</h1>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<UForm :state="form" :validate="validateLogin" class="space-y-5" @submit="onSubmit">
|
||||
<UFormField name="username" label="Username" required>
|
||||
<UFormField name="username" :label="t('login.username')" required>
|
||||
<UInput
|
||||
v-model="form.username"
|
||||
type="text"
|
||||
size="xl"
|
||||
class="w-full"
|
||||
placeholder="Enter your username"
|
||||
:placeholder="t('login.usernamePlaceholder')"
|
||||
/>
|
||||
</UFormField>
|
||||
|
||||
<UFormField name="password" label="Password" required>
|
||||
<UFormField name="password" :label="t('login.password')" required>
|
||||
<UInput
|
||||
v-model="form.password"
|
||||
type="password"
|
||||
size="xl"
|
||||
class="w-full"
|
||||
placeholder="Enter your password"
|
||||
:placeholder="t('login.passwordPlaceholder')"
|
||||
/>
|
||||
</UFormField>
|
||||
|
||||
<UCheckbox v-model="form.remember" label="Remember this device" />
|
||||
<UCheckbox v-model="form.remember" :label="t('login.remember')" />
|
||||
|
||||
<UButton
|
||||
type="submit"
|
||||
label="Sign In"
|
||||
:label="t('login.signIn')"
|
||||
size="xl"
|
||||
:loading="passwordPending"
|
||||
class="w-full justify-center"
|
||||
@@ -45,13 +45,13 @@
|
||||
|
||||
<div class="my-6 flex items-center gap-3">
|
||||
<div class="h-px flex-1 bg-default" />
|
||||
<span class="text-xs font-semibold uppercase tracking-[0.2em] text-muted">or</span>
|
||||
<span class="text-xs font-semibold uppercase tracking-[0.2em] text-muted">{{ t('login.or') }}</span>
|
||||
<div class="h-px flex-1 bg-default" />
|
||||
</div>
|
||||
|
||||
<div class="space-y-4">
|
||||
<UButton
|
||||
label="Sign In With Passkey"
|
||||
:label="t('login.passkey')"
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
size="xl"
|
||||
@@ -81,6 +81,7 @@ const toast = useToast()
|
||||
const router = useRouter()
|
||||
const auth = useAuth()
|
||||
const apiClient = useApiClient()
|
||||
const { t } = useLocale()
|
||||
|
||||
const form = reactive({
|
||||
username: '',
|
||||
@@ -94,11 +95,11 @@ function validateLogin(state: typeof form): FormError[] {
|
||||
const errors: FormError[] = []
|
||||
|
||||
if (!state.username.trim()) {
|
||||
errors.push({ name: 'username', message: 'Please enter your username.' })
|
||||
errors.push({ name: 'username', message: t('login.usernameRequired') })
|
||||
}
|
||||
|
||||
if (!state.password.trim()) {
|
||||
errors.push({ name: 'password', message: 'Please enter your password.' })
|
||||
errors.push({ name: 'password', message: t('login.passwordRequired') })
|
||||
}
|
||||
|
||||
return errors
|
||||
@@ -135,8 +136,8 @@ async function onSubmit(event: FormSubmitEvent<typeof form>) {
|
||||
await finishLogin(response.user)
|
||||
} catch (error: any) {
|
||||
toast.add({
|
||||
title: 'Login failed',
|
||||
description: getErrorMessage(error, 'Unable to sign in with username and password.'),
|
||||
title: t('login.failed'),
|
||||
description: getErrorMessage(error, t('login.failedDescription')),
|
||||
color: 'error',
|
||||
icon: 'i-lucide-circle-alert'
|
||||
})
|
||||
@@ -181,8 +182,8 @@ async function loginWithPasskey() {
|
||||
await finishLogin(verification.user)
|
||||
} catch (error: any) {
|
||||
toast.add({
|
||||
title: 'Passkey login failed',
|
||||
description: getErrorMessage(error, 'Unable to complete passkey login.'),
|
||||
title: t('login.passkeyFailed'),
|
||||
description: getErrorMessage(error, t('login.passkeyFailedDescription')),
|
||||
color: 'error',
|
||||
icon: 'i-lucide-circle-alert'
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user