feat(SPA): i18n internationzalization, english and french
This commit is contained in:
1 parent
7f1e8e572d
commit
721311a643
88 files changed
+4438
-1148
No files matched your search
@@ -1,7 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
import { t } from '@/i18n'
|
||||
|
||||
import { computed, ref } from 'vue'
|
||||
import { useMediaQuery } from '@vueuse/core'
|
||||
import { useEventListener, useMediaQuery } from '@vueuse/core'
|
||||
import { colorModeOptions, useAccountMenu } from '@/composables/useAccountMenu'
|
||||
import LanguagePicker from '@/components/LanguagePicker.vue'
|
||||
|
||||
const DESKTOP_QUERY = '(min-width: 64rem)'
|
||||
const ITEM_CLASS = 'h-8 items-center py-0'
|
||||
@@ -23,11 +26,35 @@ const { colorMode, name, initials, onProfile, goToProfile } = useAccountMenu(()
|
||||
const isDesktop = useMediaQuery(DESKTOP_QUERY)
|
||||
const open = ref(false)
|
||||
const chip = ref<{ $el: HTMLElement } | null>(null)
|
||||
const languagePicker = ref<{ $el: HTMLElement } | null>(null)
|
||||
|
||||
function focusLanguage(event: KeyboardEvent) {
|
||||
const target = event.target
|
||||
if (!(target instanceof HTMLElement)) return
|
||||
if (target.closest('[role="menu"]') !== languagePicker.value?.$el.closest('[role="menu"]'))
|
||||
return
|
||||
if (target.closest('[role="combobox"]')) return
|
||||
const entries = Array.from(
|
||||
target
|
||||
.closest('[role="menu"]')
|
||||
?.querySelectorAll<HTMLElement>('[role="menuitem"], [role="menuitemcheckbox"]') ?? [],
|
||||
)
|
||||
const movesToLanguage =
|
||||
event.key === 'End' ||
|
||||
(event.key === 'ArrowDown' && target === entries[entries.length - 1]) ||
|
||||
(event.key === 'ArrowUp' && target === entries[0])
|
||||
if (movesToLanguage) {
|
||||
event.preventDefault()
|
||||
languagePicker.value?.$el.querySelector<HTMLElement>('[role="combobox"]')?.focus()
|
||||
}
|
||||
}
|
||||
|
||||
useEventListener(document, 'keydown', focusLanguage, { capture: true })
|
||||
|
||||
const chipActive = computed(() => open.value || onProfile.value)
|
||||
|
||||
const triggerAriaLabel = computed(() =>
|
||||
props.collapsed ? `${name.value} — account menu` : undefined,
|
||||
props.collapsed ? t('{0} — account menu', [name.value]) : undefined,
|
||||
)
|
||||
|
||||
const chipClass = computed(() => [
|
||||
@@ -59,7 +86,9 @@ const items = computed(() => [
|
||||
class: 'mb-1 border-b border-default px-2 pt-2 pb-2.5',
|
||||
},
|
||||
{
|
||||
label: 'Profile',
|
||||
get label() {
|
||||
return t('Profile')
|
||||
},
|
||||
icon: 'i-lucide-circle-user',
|
||||
class: onProfile.value ? `${ITEM_CLASS} bg-elevated` : ITEM_CLASS,
|
||||
onSelect: goToProfile,
|
||||
@@ -79,7 +108,9 @@ const items = computed(() => [
|
||||
})),
|
||||
[
|
||||
{
|
||||
label: 'Log out',
|
||||
get label() {
|
||||
return t('Log out')
|
||||
},
|
||||
icon: 'i-lucide-log-out',
|
||||
class: ITEM_CLASS,
|
||||
onSelect: () => emit('logout'),
|
||||
@@ -101,7 +132,7 @@ const items = computed(() => [
|
||||
color="neutral"
|
||||
variant="ghost"
|
||||
block
|
||||
title="Account"
|
||||
:title="t('Account')"
|
||||
:aria-expanded="chipExpanded"
|
||||
:class="chipClass"
|
||||
:trailing-icon="collapsed ? undefined : 'i-lucide-chevrons-up-down'"
|
||||
@@ -128,5 +159,8 @@ const items = computed(() => [
|
||||
<span class="truncate text-xs font-normal text-muted">{{ email }}</span>
|
||||
</span>
|
||||
</template>
|
||||
<template #content-bottom>
|
||||
<LanguagePicker ref="languagePicker" />
|
||||
</template>
|
||||
</UDropdownMenu>
|
||||
</template>
|
||||
Reference in new issue
Block a user