fix: i18n selector
This commit is contained in:
@@ -15,7 +15,7 @@ const dark = useDark()
|
||||
|
||||
const languages = computed(() => {
|
||||
return Object.keys(messages.value).map(lang => ({
|
||||
label: t(`settings.language.${lang}`),
|
||||
label: t(`settings.language.options.${lang}`),
|
||||
value: lang,
|
||||
}))
|
||||
})
|
||||
|
||||
@@ -15,8 +15,13 @@ const languageRemap: Record<string, string> = {
|
||||
|
||||
function getLocale() {
|
||||
let language = localStorage.getItem('settings/language')
|
||||
const languages = Object.keys(messages!)
|
||||
|
||||
if (!language) {
|
||||
// Fallback to browser language
|
||||
language = navigator.language || 'en'
|
||||
}
|
||||
|
||||
const languages = Object.keys(messages!)
|
||||
if (languageRemap[language || 'en'] != null) {
|
||||
language = languageRemap[language || 'en']
|
||||
}
|
||||
|
||||
@@ -27,16 +27,17 @@ dialogs:
|
||||
start: Let's do it!
|
||||
select-model: Choose model
|
||||
language:
|
||||
chinese: 简体中文
|
||||
zh-CN: 简体中文
|
||||
zh-Hans: 简体中文
|
||||
english: English
|
||||
en: English
|
||||
en-US: English (US)
|
||||
title: Language
|
||||
description: >
|
||||
Change the language of the AIRI interface. This will not affect the language
|
||||
of the character's responses.
|
||||
options:
|
||||
chinese: 简体中文
|
||||
zh-CN: 简体中文
|
||||
zh-Hans: 简体中文
|
||||
english: English
|
||||
en: English
|
||||
en-US: English (US)
|
||||
live2d:
|
||||
change-model:
|
||||
from-file: Load from File
|
||||
|
||||
@@ -25,14 +25,15 @@ dialogs:
|
||||
start: 开始吧!
|
||||
select-model: 选择模型
|
||||
language:
|
||||
chinese: 简体中文
|
||||
english: English
|
||||
title: 语言
|
||||
zh-Hans: 简体中文
|
||||
zh-CN: 中文
|
||||
en-US: English (US)
|
||||
en: English
|
||||
description: 切换显示界面的语言
|
||||
options:
|
||||
chinese: 简体中文
|
||||
english: English
|
||||
zh-Hans: 简体中文
|
||||
zh-CN: 中文
|
||||
en-US: English (US)
|
||||
en: English
|
||||
live2d:
|
||||
change-model:
|
||||
from-file: 从文件加载
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import messages from '@proj-airi/i18n/locales'
|
||||
|
||||
import { useLocalStorage } from '@vueuse/core'
|
||||
import { converter } from 'culori'
|
||||
import { defineStore } from 'pinia'
|
||||
@@ -5,6 +7,17 @@ import { onMounted, ref, watch } from 'vue'
|
||||
|
||||
import { useAudioDevice } from './audio'
|
||||
|
||||
const languageRemap: Record<string, string> = {
|
||||
'zh-CN': 'zh-Hans',
|
||||
'zh-TW': 'zh-Hans', // TODO: remove this when zh-Hant is supported
|
||||
'zh-HK': 'zh-Hans', // TODO: remove this when zh-Hant is supported
|
||||
'zh-Hant': 'zh-Hans', // TODO: remove this when zh-Hant is supported
|
||||
'en-US': 'en',
|
||||
'en-GB': 'en',
|
||||
'en-AU': 'en',
|
||||
'en': 'en',
|
||||
}
|
||||
|
||||
export const DEFAULT_THEME_COLORS_HUE = 220.44
|
||||
|
||||
const convert = converter('oklch')
|
||||
@@ -13,7 +26,7 @@ const getHueFrom = (color?: string) => color ? convert(color)?.h : DEFAULT_THEME
|
||||
export const useSettings = defineStore('settings', () => {
|
||||
const selectedAudioDevice = ref<MediaDeviceInfo>()
|
||||
|
||||
const language = useLocalStorage('settings/language', 'en')
|
||||
const language = useLocalStorage('settings/language', '')
|
||||
|
||||
const stageView = useLocalStorage('settings/stage/view/model-renderer', '2d')
|
||||
const stageViewControlsEnabled = ref(false)
|
||||
@@ -27,6 +40,24 @@ export const useSettings = defineStore('settings', () => {
|
||||
|
||||
const allowVisibleOnAllWorkspaces = useLocalStorage('settings/allow-visible-on-all-workspaces', true)
|
||||
|
||||
function getLanguage() {
|
||||
let language = localStorage.getItem('settings/language')
|
||||
|
||||
if (!language) {
|
||||
// Fallback to browser language
|
||||
language = navigator.language || 'en'
|
||||
}
|
||||
|
||||
const languages = Object.keys(messages!)
|
||||
if (languageRemap[language || 'en'] != null) {
|
||||
language = languageRemap[language || 'en']
|
||||
}
|
||||
if (language && languages.includes(language))
|
||||
return language
|
||||
|
||||
return 'en'
|
||||
}
|
||||
|
||||
function setThemeColorsHue(hue = DEFAULT_THEME_COLORS_HUE) {
|
||||
themeColorsHue.value = hue
|
||||
themeColorsHueDynamic.value = false
|
||||
@@ -56,6 +87,10 @@ export const useSettings = defineStore('settings', () => {
|
||||
return hueDifference < 0.01 || hueDifference > 359.99
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
language.value = getLanguage()
|
||||
})
|
||||
|
||||
return {
|
||||
disableTransitions,
|
||||
usePageSpecificTransitions,
|
||||
|
||||
@@ -31,10 +31,10 @@ function toDisplayValue(value: any): string {
|
||||
>
|
||||
<ComboboxAnchor
|
||||
:class="[
|
||||
'min-w-[160px] w-full inline-flex items-center justify-between rounded-lg border px-2 leading-none h-[35px] gap-[5px] outline-none',
|
||||
'w-full inline-flex items-center justify-between rounded-lg border px-2 leading-none h-[35px] gap-[5px] outline-none',
|
||||
'text-xs text-neutral-700 dark:text-neutral-200 data-[placeholder]:text-neutral-200',
|
||||
'bg-white dark:bg-neutral-900 disabled:bg-neutral-100 hover:bg-neutral-50 dark:disabled:bg-neutral-900 dark:hover:bg-neutral-700',
|
||||
'border-neutral-300 dark:border-neutral-800 border-solid border-2 focus:border-neutral-400 dark:focus:border-neutral-600',
|
||||
'border-neutral-200 dark:border-neutral-800 border-solid border-2 focus:border-neutral-300 dark:focus:border-neutral-600',
|
||||
'shadow-sm focus:shadow-[0_0_0_2px] focus:shadow-black',
|
||||
'transition-colors duration-200 ease-in-out',
|
||||
]"
|
||||
@@ -66,7 +66,7 @@ function toDisplayValue(value: any): string {
|
||||
'absolute z-10 w-full mt-1 min-w-[160px] overflow-hidden rounded-lg shadow-sm border will-change-[opacity,transform] max-h-50dvh',
|
||||
'data-[side=top]:animate-slideDownAndFade data-[side=right]:animate-slideLeftAndFade data-[side=bottom]:animate-slideUpAndFade data-[side=left]:animate-slideRightAndFade',
|
||||
'bg-white dark:bg-neutral-900',
|
||||
'border-neutral-300 dark:border-neutral-800 border-solid border-2 focus:border-neutral-400 dark:focus:border-neutral-600',
|
||||
'border-neutral-200 dark:border-neutral-800 border-solid border-2 focus:border-neutral-300 dark:focus:border-neutral-600',
|
||||
]"
|
||||
>
|
||||
<ComboboxViewport class="p-[2px]">
|
||||
|
||||
@@ -19,17 +19,23 @@ const modelValue = defineModel<string>({ required: false })
|
||||
<template>
|
||||
<label flex="~ col gap-4">
|
||||
<div
|
||||
class="items-center justify-center"
|
||||
:class="[
|
||||
props.layout === 'horizontal' ? 'flex flex-row items-center justify-between gap-2' : 'flex flex-col items-start justify-center gap-2',
|
||||
props.layout === 'horizontal' ? 'grid grid-cols-3 gap-2' : 'grid grid-cols-2 gap-2',
|
||||
]"
|
||||
>
|
||||
<div class="min-w-[max-content] flex-1">
|
||||
<div class="flex items-center gap-1 text-sm font-medium">
|
||||
<div
|
||||
class="w-full"
|
||||
:class="[
|
||||
props.layout === 'horizontal' ? 'col-span-2' : 'row-span-1',
|
||||
]"
|
||||
>
|
||||
<div class="flex items-center gap-1 break-words text-sm font-medium">
|
||||
<slot name="label">
|
||||
{{ props.label }}
|
||||
</slot>
|
||||
</div>
|
||||
<div class="text-xs text-neutral-500 dark:text-neutral-400">
|
||||
<div class="break-words text-xs text-neutral-500 dark:text-neutral-400">
|
||||
<slot name="description">
|
||||
{{ props.description }}
|
||||
</slot>
|
||||
@@ -42,7 +48,12 @@ const modelValue = defineModel<string>({ required: false })
|
||||
:placeholder="props.placeholder"
|
||||
:disabled="props.disabled"
|
||||
:title="label"
|
||||
:class="props.selectClass"
|
||||
:class="[
|
||||
...(props.selectClass
|
||||
? (typeof props.selectClass === 'string' ? [props.selectClass] : props.selectClass)
|
||||
: []),
|
||||
props.layout === 'horizontal' ? 'col-span-1' : 'row-span-2',
|
||||
]"
|
||||
>
|
||||
<template #default="{ value }">
|
||||
{{ props.options?.find(option => option.value === value)?.label || props.placeholder }}
|
||||
|
||||
Reference in New Issue
Block a user