elearning/Frontend-Learner/components/common/LanguageSwitcher.vue

161 lines
3.8 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 flag image path for a locale
const getFlagPath = (code: string) => `/flags/${code}.png`
// 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')"
>
<!-- Show current locale flag -->
<img
:src="getFlagPath(locale)"
:alt="locale.toUpperCase()"
class="flag-icon"
/>
<q-menu
anchor="bottom right"
self="top right"
class="language-menu"
>
<q-list style="min-width: 180px">
<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>
<img
:src="getFlagPath(loc.code)"
:alt="loc.code.toUpperCase()"
class="flag-icon-menu"
/>
</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: #ffffff;
border: 2px solid var(--border-color);
transition: all 0.3s ease;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
overflow: hidden;
padding: 0;
}
.language-btn:hover {
transform: scale(1.05);
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%;
}
.flag-icon-menu {
width: 28px;
height: 20px;
object-fit: cover;
border-radius: 4px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.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: #1e293b;
border-color: rgba(255, 255, 255, 0.15);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
}
:global(.dark) .language-btn:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
border-color: var(--primary);
}
: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>