feat: Implement internationalization with English and Thai locales and a language switcher.
This commit is contained in:
parent
d6769ca1a9
commit
ada40b05e8
8 changed files with 1951 additions and 3 deletions
|
|
@ -26,18 +26,21 @@ const emit = defineEmits<{
|
|||
</div>
|
||||
|
||||
<!-- Right Actions -->
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<!-- Search Bar (Optional) -->
|
||||
<div v-if="showSearch !== false" class="relative hidden-mobile" style="width: 300px;">
|
||||
<input
|
||||
type="text"
|
||||
class="input-field"
|
||||
placeholder="ค้นหาคอร์ส..."
|
||||
:placeholder="$t('menu.searchCourses')"
|
||||
style="padding-left: 36px;"
|
||||
>
|
||||
<span style="position: absolute; left: 12px; top: 10px; color: var(--text-secondary);">🔍</span>
|
||||
</div>
|
||||
|
||||
<!-- Language Switcher (Left of Avatar) -->
|
||||
<LanguageSwitcher />
|
||||
|
||||
<!-- User Profile Dropdown -->
|
||||
<UserMenu />
|
||||
</div>
|
||||
|
|
|
|||
140
Frontend-Learner/components/common/LanguageSwitcher.vue
Normal file
140
Frontend-Learner/components/common/LanguageSwitcher.vue
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
<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>
|
||||
Loading…
Add table
Add a link
Reference in a new issue