140 lines
3.5 KiB
Vue
140 lines
3.5 KiB
Vue
<script setup lang="ts">
|
|
/**
|
|
* @file LanguageSwitcher.vue
|
|
* @description Language switcher component using Quasar dropdown.
|
|
* Allows switching between Thai (th) and English (en) locales.
|
|
*/
|
|
|
|
const { locale, setLocale, locales } = useI18n()
|
|
|
|
// Get available locales with their names
|
|
const availableLocales = computed(() => {
|
|
return (locales.value as Array<{ code: string; name: string }>).map((loc) => ({
|
|
code: loc.code,
|
|
name: loc.name
|
|
}))
|
|
})
|
|
|
|
// Get current locale display (TH or EN)
|
|
const currentLocaleDisplay = computed(() => {
|
|
return locale.value.toUpperCase()
|
|
})
|
|
|
|
// Handle locale change
|
|
const changeLocale = async (code: string) => {
|
|
await setLocale(code as 'th' | 'en')
|
|
// Cookie is automatically handled by @nuxtjs/i18n with detectBrowserLanguage.useCookie
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<q-btn
|
|
round
|
|
flat
|
|
class="language-btn"
|
|
:aria-label="$t('language.label')"
|
|
>
|
|
<span class="language-text">{{ currentLocaleDisplay }}</span>
|
|
|
|
<q-menu
|
|
anchor="bottom right"
|
|
self="top right"
|
|
class="language-menu"
|
|
>
|
|
<q-list style="min-width: 150px">
|
|
<q-item-label header class="text-xs font-bold uppercase tracking-wider" style="color: var(--text-secondary);">
|
|
{{ $t('language.label') }}
|
|
</q-item-label>
|
|
|
|
<q-item
|
|
v-for="loc in availableLocales"
|
|
:key="loc.code"
|
|
clickable
|
|
v-close-popup
|
|
@click="changeLocale(loc.code)"
|
|
:class="{ 'active-locale': locale === loc.code }"
|
|
class="language-item"
|
|
>
|
|
<q-item-section avatar>
|
|
<span class="text-lg">{{ loc.code === 'th' ? '🇹🇭' : '🇺🇸' }}</span>
|
|
</q-item-section>
|
|
<q-item-section>
|
|
<q-item-label class="font-semibold" style="color: var(--text-primary);">
|
|
{{ loc.name }}
|
|
</q-item-label>
|
|
<q-item-label caption style="color: var(--text-secondary);">
|
|
{{ loc.code.toUpperCase() }}
|
|
</q-item-label>
|
|
</q-item-section>
|
|
<q-item-section side v-if="locale === loc.code">
|
|
<q-icon name="check" color="primary" />
|
|
</q-item-section>
|
|
</q-item>
|
|
</q-list>
|
|
</q-menu>
|
|
|
|
<q-tooltip>{{ $t('language.label') }}</q-tooltip>
|
|
</q-btn>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.language-btn {
|
|
width: 40px;
|
|
height: 40px;
|
|
background: linear-gradient(135deg, var(--primary) 0%, #2f5ed7 100%);
|
|
color: white;
|
|
transition: all 0.3s ease;
|
|
box-shadow: 0 2px 8px rgba(75, 130, 247, 0.3);
|
|
}
|
|
|
|
.language-btn:hover {
|
|
transform: scale(1.05);
|
|
box-shadow: 0 4px 12px rgba(75, 130, 247, 0.4);
|
|
}
|
|
|
|
.language-text {
|
|
font-size: 12px;
|
|
font-weight: 800;
|
|
letter-spacing: 0.5px;
|
|
}
|
|
|
|
.language-item {
|
|
border-radius: 8px;
|
|
margin: 4px 8px;
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
.language-item:hover {
|
|
background: var(--bg-secondary);
|
|
}
|
|
|
|
.active-locale {
|
|
background: rgba(75, 130, 247, 0.1);
|
|
}
|
|
|
|
:deep(.language-menu) {
|
|
border-radius: 16px;
|
|
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.15);
|
|
background: var(--bg-surface);
|
|
border: 1px solid var(--border-color);
|
|
}
|
|
|
|
:global(.dark) .language-btn {
|
|
background: linear-gradient(135deg, #3b82f6 0%, #1e40af 100%);
|
|
box-shadow: 0 2px 8px rgba(59, 130, 246, 0.4);
|
|
}
|
|
|
|
:global(.dark) .language-btn:hover {
|
|
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.5);
|
|
}
|
|
|
|
:global(.dark) :deep(.language-menu) {
|
|
background: #1e293b;
|
|
border-color: rgba(255, 255, 255, 0.1);
|
|
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.4);
|
|
}
|
|
|
|
:global(.dark) .active-locale {
|
|
background: rgba(59, 130, 246, 0.2);
|
|
}
|
|
</style>
|