mirror of
https://github.com/zyycn/codex-proxy-rs.git
synced 2026-11-15 00:06:24 +00:00
Unify theme namespaces and plugin theme delivery, improve validation and empty states, and pin UI v0.8.0 plus plugin workbench v0.9.0. Co-authored-by: Codex <noreply@openai.com>
86 lines
3.2 KiB
Vue
86 lines
3.2 KiB
Vue
<script setup lang="ts">
|
|
import type { IconButtonProps } from '@codex-proxy/ui'
|
|
import type { Locale } from '@/i18n/locale'
|
|
import { ZIconButton, ZMenuItem, ZPopover } from '@codex-proxy/ui'
|
|
import { Check, Languages, LoaderCircle } from '@lucide/vue'
|
|
import chineseFlag from 'circle-flags/flags/cn.svg?url&no-inline'
|
|
import englishFlag from 'circle-flags/flags/us.svg?url&no-inline'
|
|
import { computed, shallowRef, useTemplateRef } from 'vue'
|
|
import { useI18n } from 'vue-i18n'
|
|
import { useLocale } from '@/composables/useLocale'
|
|
import { notification } from '@/utils/notification'
|
|
|
|
withDefaults(defineProps<{ size?: IconButtonProps['size'], variant?: IconButtonProps['variant'] }>(), {
|
|
size: 'small',
|
|
variant: 'text',
|
|
})
|
|
|
|
const { t } = useI18n({ useScope: 'global' })
|
|
const { locale, localeOptions, setLocale } = useLocale()
|
|
const pendingLocale = shallowRef<Locale>()
|
|
const localeFlags: Record<Locale, string> = {
|
|
'zh-Hans': chineseFlag,
|
|
'zh-Hant': chineseFlag,
|
|
'en': englishFlag,
|
|
}
|
|
const choices = useTemplateRef<HTMLElement>('choices')
|
|
const label = computed(() => t('navigation.switchLanguage', {
|
|
value: localeOptions.find(option => option.value === locale.value)?.label ?? 'English',
|
|
}))
|
|
|
|
function focusSelected() {
|
|
choices.value?.querySelector<HTMLButtonElement>('button[aria-pressed="true"]')?.focus()
|
|
}
|
|
|
|
async function selectLocale(value: Locale, close: () => void) {
|
|
pendingLocale.value = value
|
|
try {
|
|
if (await setLocale(value))
|
|
close()
|
|
}
|
|
catch {
|
|
notification.error({
|
|
message: t('navigation.languageLoadFailed'),
|
|
})
|
|
}
|
|
finally {
|
|
if (pendingLocale.value === value)
|
|
pendingLocale.value = undefined
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<ZPopover placement="top-end" @opened="focusSelected">
|
|
<template #reference="{ id, open }">
|
|
<ZIconButton :size="size" :variant="variant" :aria-label="label" :aria-expanded="open" :aria-controls="id">
|
|
<Languages :size="size === 'small' ? 18 : 19" />
|
|
</ZIconButton>
|
|
</template>
|
|
<template #default="{ close }">
|
|
<div ref="choices" role="group" :aria-label="t('common.language')" class="grid min-w-40 gap-1 p-1.5">
|
|
<ZMenuItem
|
|
v-for="option in localeOptions"
|
|
:key="option.value"
|
|
:aria-pressed="option.value === locale"
|
|
:disabled="pendingLocale === option.value"
|
|
:aria-busy="pendingLocale === option.value || undefined"
|
|
class="aria-pressed:bg-z-control-item-bg-active aria-pressed:text-z-primary-text aria-pressed:hover:bg-z-control-item-bg-active"
|
|
@click="selectLocale(option.value, close)"
|
|
>
|
|
<template #icon>
|
|
<img :src="localeFlags[option.value]" alt="" aria-hidden="true" class="size-4.5 shrink-0">
|
|
</template>
|
|
<span class="flex items-center justify-between gap-3">
|
|
<span :lang="option.value">{{ option.label }}</span>
|
|
<span class="inline-grid size-4 shrink-0 place-items-center" aria-hidden="true">
|
|
<LoaderCircle v-if="pendingLocale === option.value" class="size-4 animate-spin motion-reduce:animate-none" />
|
|
<Check v-else-if="option.value === locale" class="size-4" />
|
|
</span>
|
|
</span>
|
|
</ZMenuItem>
|
|
</div>
|
|
</template>
|
|
</ZPopover>
|
|
</template>
|