2026-01-19 15:51:28 +07:00
|
|
|
<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
|
|
|
|
|
}))
|
|
|
|
|
})
|
|
|
|
|
|
2026-01-19 16:09:07 +07:00
|
|
|
// Get flag image path for a locale
|
|
|
|
|
const getFlagPath = (code: string) => `/flags/${code}.png`
|
2026-01-19 15:51:28 +07:00
|
|
|
|
|
|
|
|
// 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')"
|
|
|
|
|
>
|
2026-01-19 16:09:07 +07:00
|
|
|
<!-- Show current locale flag -->
|
|
|
|
|
<img
|
|
|
|
|
:src="getFlagPath(locale)"
|
|
|
|
|
:alt="locale.toUpperCase()"
|
|
|
|
|
class="flag-icon"
|
|
|
|
|
/>
|
2026-01-19 15:51:28 +07:00
|
|
|
|
|
|
|
|
<q-menu
|
|
|
|
|
anchor="bottom right"
|
|
|
|
|
self="top right"
|
|
|
|
|
class="language-menu"
|
2026-01-19 16:25:26 +07:00
|
|
|
:offset="[0, 8]"
|
2026-01-19 15:51:28 +07:00
|
|
|
>
|
2026-01-19 16:25:26 +07:00
|
|
|
<q-list style="min-width: 180px" class="py-1">
|
|
|
|
|
<q-item-label
|
|
|
|
|
header
|
|
|
|
|
class="text-xs font-black uppercase tracking-wider mb-1 px-4 pt-3"
|
|
|
|
|
style="color: var(--text-secondary)"
|
|
|
|
|
:class="['text-slate-600', 'dark:text-slate-400']"
|
|
|
|
|
>
|
2026-01-19 15:51:28 +07:00
|
|
|
{{ $t('language.label') }}
|
|
|
|
|
</q-item-label>
|
|
|
|
|
|
|
|
|
|
<q-item
|
|
|
|
|
v-for="loc in availableLocales"
|
|
|
|
|
:key="loc.code"
|
|
|
|
|
clickable
|
|
|
|
|
v-close-popup
|
|
|
|
|
@click="changeLocale(loc.code)"
|
2026-01-19 16:25:26 +07:00
|
|
|
class="language-item mx-2 mb-1 rounded-lg transition-colors duration-200"
|
|
|
|
|
:class="[
|
|
|
|
|
locale === loc.code
|
|
|
|
|
? 'bg-blue-50 dark:bg-blue-900/20'
|
|
|
|
|
: 'hover:bg-slate-50 dark:hover:bg-slate-800'
|
|
|
|
|
]"
|
2026-01-19 15:51:28 +07:00
|
|
|
>
|
2026-01-19 16:25:26 +07:00
|
|
|
<q-item-section avatar style="min-width: 0; padding-right: 12px;">
|
2026-01-19 16:09:07 +07:00
|
|
|
<img
|
|
|
|
|
:src="getFlagPath(loc.code)"
|
|
|
|
|
:alt="loc.code.toUpperCase()"
|
|
|
|
|
class="flag-icon-menu"
|
|
|
|
|
/>
|
2026-01-19 15:51:28 +07:00
|
|
|
</q-item-section>
|
2026-01-19 16:25:26 +07:00
|
|
|
|
2026-01-19 15:51:28 +07:00
|
|
|
<q-item-section>
|
2026-01-19 16:25:26 +07:00
|
|
|
<q-item-label
|
|
|
|
|
class="font-bold text-sm"
|
|
|
|
|
:class="[
|
|
|
|
|
locale === loc.code
|
|
|
|
|
? 'text-blue-700 dark:text-blue-300'
|
|
|
|
|
: 'text-slate-900 dark:text-slate-100'
|
|
|
|
|
]"
|
|
|
|
|
>
|
2026-01-19 15:51:28 +07:00
|
|
|
{{ loc.name }}
|
|
|
|
|
</q-item-label>
|
2026-01-19 16:25:26 +07:00
|
|
|
<q-item-label
|
|
|
|
|
caption
|
|
|
|
|
class="text-xs font-medium mt-0.5"
|
|
|
|
|
:class="[
|
|
|
|
|
locale === loc.code
|
|
|
|
|
? 'text-blue-600/80 dark:text-blue-300/70'
|
|
|
|
|
: 'text-slate-500 dark:text-slate-400'
|
|
|
|
|
]"
|
|
|
|
|
>
|
2026-01-19 15:51:28 +07:00
|
|
|
{{ loc.code.toUpperCase() }}
|
|
|
|
|
</q-item-label>
|
|
|
|
|
</q-item-section>
|
2026-01-19 16:25:26 +07:00
|
|
|
|
2026-01-19 15:51:28 +07:00
|
|
|
<q-item-section side v-if="locale === loc.code">
|
2026-01-19 16:25:26 +07:00
|
|
|
<q-icon
|
|
|
|
|
name="check"
|
|
|
|
|
size="18px"
|
|
|
|
|
:class="['text-blue-700', 'dark:text-blue-300']"
|
|
|
|
|
/>
|
2026-01-19 15:51:28 +07:00
|
|
|
</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;
|
2026-01-19 16:09:07 +07:00
|
|
|
background: #ffffff;
|
|
|
|
|
border: 2px solid var(--border-color);
|
2026-01-19 15:51:28 +07:00
|
|
|
transition: all 0.3s ease;
|
2026-01-19 16:09:07 +07:00
|
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
padding: 0;
|
2026-01-19 15:51:28 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.language-btn:hover {
|
|
|
|
|
transform: scale(1.05);
|
2026-01-19 16:09:07 +07:00
|
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
|
|
|
border-color: var(--primary);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.flag-icon {
|
|
|
|
|
width: 28px;
|
|
|
|
|
height: 28px;
|
|
|
|
|
object-fit: cover;
|
|
|
|
|
border-radius: 50%;
|
2026-01-19 15:51:28 +07:00
|
|
|
}
|
|
|
|
|
|
2026-01-19 16:09:07 +07:00
|
|
|
.flag-icon-menu {
|
|
|
|
|
width: 28px;
|
|
|
|
|
height: 20px;
|
|
|
|
|
object-fit: cover;
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
2026-01-19 15:51:28 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.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 {
|
2026-01-19 16:09:07 +07:00
|
|
|
background: #1e293b;
|
|
|
|
|
border-color: rgba(255, 255, 255, 0.15);
|
|
|
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
|
2026-01-19 15:51:28 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
:global(.dark) .language-btn:hover {
|
2026-01-19 16:09:07 +07:00
|
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
|
|
|
|
|
border-color: var(--primary);
|
2026-01-19 15:51:28 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
: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>
|