diff --git a/apps/stage-tamagotchi/src/pages/settings/appearance/general.vue b/apps/stage-tamagotchi/src/pages/settings/appearance/general.vue index 84f8412a6..4a515b43b 100644 --- a/apps/stage-tamagotchi/src/pages/settings/appearance/general.vue +++ b/apps/stage-tamagotchi/src/pages/settings/appearance/general.vue @@ -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, })) }) diff --git a/apps/stage-web/src/modules/i18n.ts b/apps/stage-web/src/modules/i18n.ts index 5b444980a..b1f097314 100644 --- a/apps/stage-web/src/modules/i18n.ts +++ b/apps/stage-web/src/modules/i18n.ts @@ -15,8 +15,13 @@ const languageRemap: Record = { 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'] } diff --git a/packages/i18n/src/locales/en/settings.yaml b/packages/i18n/src/locales/en/settings.yaml index 73e43cdba..1766bffbe 100644 --- a/packages/i18n/src/locales/en/settings.yaml +++ b/packages/i18n/src/locales/en/settings.yaml @@ -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 diff --git a/packages/i18n/src/locales/zh-Hans/settings.yaml b/packages/i18n/src/locales/zh-Hans/settings.yaml index 990f66748..913626f49 100644 --- a/packages/i18n/src/locales/zh-Hans/settings.yaml +++ b/packages/i18n/src/locales/zh-Hans/settings.yaml @@ -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: 从文件加载 diff --git a/packages/stage-ui/src/stores/settings.ts b/packages/stage-ui/src/stores/settings.ts index 4468dc91c..225ec0ad1 100644 --- a/packages/stage-ui/src/stores/settings.ts +++ b/packages/stage-ui/src/stores/settings.ts @@ -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 = { + '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() - 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, diff --git a/packages/ui/src/components/Form/Combobox/Combobox.vue b/packages/ui/src/components/Form/Combobox/Combobox.vue index 552db492c..4b0a7b5c6 100644 --- a/packages/ui/src/components/Form/Combobox/Combobox.vue +++ b/packages/ui/src/components/Form/Combobox/Combobox.vue @@ -31,10 +31,10 @@ function toDisplayValue(value: any): string { > diff --git a/packages/ui/src/components/Form/Field/FieldSelect.vue b/packages/ui/src/components/Form/Field/FieldSelect.vue index 15b190bfe..5d82c4c89 100644 --- a/packages/ui/src/components/Form/Field/FieldSelect.vue +++ b/packages/ui/src/components/Form/Field/FieldSelect.vue @@ -19,17 +19,23 @@ const modelValue = defineModel({ required: false })