fix: i18n selector

This commit is contained in:
Neko Ayaka
2025-07-28 13:16:49 +08:00
parent 06b20b61be
commit daea2d52f5
7 changed files with 76 additions and 23 deletions
@@ -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,
}))
})
+6 -1
View File
@@ -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']
}
+7 -6
View File
@@ -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: 从文件加载
+36 -1
View 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 }}