diff --git a/src/components/base/LanguageSelectButtonBase.ts b/src/components/base/LanguageSelectButtonBase.ts new file mode 100644 index 00000000..0446d771 --- /dev/null +++ b/src/components/base/LanguageSelectButtonBase.ts @@ -0,0 +1,72 @@ +import { computed } from 'vue'; + +import type { LanguageOption } from '@/locales/index.ts'; +import { useI18n } from '@/locales/helpers.ts'; + +import { useSettingsStore } from '@/stores/setting.ts'; + +export interface LanguageSelectButtonBaseProps { + disabled?: boolean; + useModelValue?: boolean; + modelValue?: string; +} + +export interface LanguageSelectButtonBaseEmits { + (e: 'update:modelValue', value: string): void; +} + +export function useLanguageSelectButtonBase(props: LanguageSelectButtonBaseProps, emit: LanguageSelectButtonBaseEmits) { + const { getCurrentLanguageTag, getCurrentLanguageDisplayName, getAllLanguageOptions, getLanguageInfo, setLanguage } = useI18n(); + + const settingsStore = useSettingsStore(); + + const allLanguages = computed(() => getAllLanguageOptions(false)); + + const currentLocale = computed({ + get: () => getCurrentLanguageTag(), + set: (value: string) => { + const localeDefaultSettings = setLanguage(value); + settingsStore.updateLocalizedDefaultSettings(localeDefaultSettings); + } + }); + + const currentLanguageName = computed(() => { + if (props.useModelValue && props.modelValue) { + const languageInfo = getLanguageInfo(props.modelValue); + + if (!languageInfo) { + return ''; + } + + return languageInfo.displayName; + } else { + return getCurrentLanguageDisplayName() + } + }); + + function updateLanguage(languageTag: string): void { + if (props.useModelValue) { + emit('update:modelValue', languageTag); + } else { + currentLocale.value = languageTag; + } + } + + function isLanguageSelected(languageTag: string): boolean { + if (props.useModelValue) { + return props.modelValue === languageTag; + } else { + return currentLocale.value === languageTag; + } + } + + return { + // computed states + allLanguages, + currentLocale, + currentLanguageName, + // functions + updateLanguage, + isLanguageSelected + } +} diff --git a/src/components/desktop/LanguageSelectButton.vue b/src/components/desktop/LanguageSelectButton.vue new file mode 100644 index 00000000..13a489d9 --- /dev/null +++ b/src/components/desktop/LanguageSelectButton.vue @@ -0,0 +1,37 @@ + + + diff --git a/src/components/mobile/LanguageSelectButton.vue b/src/components/mobile/LanguageSelectButton.vue new file mode 100644 index 00000000..85ebae18 --- /dev/null +++ b/src/components/mobile/LanguageSelectButton.vue @@ -0,0 +1,32 @@ + + + diff --git a/src/components/mobile/ListItemSelectionSheet.vue b/src/components/mobile/ListItemSelectionSheet.vue index 2ffeea57..1505e316 100644 --- a/src/components/mobile/ListItemSelectionSheet.vue +++ b/src/components/mobile/ListItemSelectionSheet.vue @@ -14,6 +14,7 @@ (() => getServerTipContent(getLoginPageTips())); - function changeLanguage(locale: string): void { - const localeDefaultSettings = setLanguage(locale); - settingsStore.updateLocalizedDefaultSettings(localeDefaultSettings); - } - function doAfterLogin(authResponse: AuthResponse): void { if (authResponse.user) { const localeDefaultSettings = setLanguage(authResponse.user.language); @@ -76,11 +71,11 @@ export function useLoginPageBase() { twoFAVerifyType, logining, verifying, + // computed states inputIsEmpty, twoFAInputIsEmpty, tips, // functions - changeLanguage, doAfterLogin } } diff --git a/src/views/base/SignupPageBase.ts b/src/views/base/SignupPageBase.ts index 8cb0eea2..796a866c 100644 --- a/src/views/base/SignupPageBase.ts +++ b/src/views/base/SignupPageBase.ts @@ -23,6 +23,16 @@ export function useSignupPageBase() { const user = ref(userStore.generateNewUserModel(getCurrentLanguageTag())); const submitting = ref(false); + const languageTitle = computed(() => { + const languageInCurrentLanguage = tt('Language'); + + if (languageInCurrentLanguage !== 'Language') { + return `${languageInCurrentLanguage} / Language`; + } + + return languageInCurrentLanguage; + }); + const currentLocale = computed({ get: () => getCurrentLanguageTag(), set: (value: string) => { @@ -118,6 +128,7 @@ export function useSignupPageBase() { user, submitting, // computed states + languageTitle, currentLocale, currentLanguageName, inputEmptyProblemMessage, diff --git a/src/views/base/UnlockPageBase.ts b/src/views/base/UnlockPageBase.ts index 4b202064..c0be1b55 100644 --- a/src/views/base/UnlockPageBase.ts +++ b/src/views/base/UnlockPageBase.ts @@ -38,11 +38,6 @@ export function useUnlockPageBase() { return !!pinCode && pinCode.length === 6; } - function changeLanguage(locale: string): void { - const localeDefaultSettings = setLanguage(locale); - settingsStore.updateLocalizedDefaultSettings(localeDefaultSettings); - } - function doAfterUnlocked(): void { transactionsStore.initTransactionDraft(); tokensStore.refreshTokenAndRevokeOldToken().then(response => { @@ -82,7 +77,6 @@ export function useUnlockPageBase() { isWebAuthnAvailable, // methods isPinCodeValid, - changeLanguage, doAfterUnlocked, doRelogin }; diff --git a/src/views/desktop/ForgetPasswordPage.vue b/src/views/desktop/ForgetPasswordPage.vue index 36c10777..f19e5be8 100644 --- a/src/views/desktop/ForgetPasswordPage.vue +++ b/src/views/desktop/ForgetPasswordPage.vue @@ -67,22 +67,7 @@ - - - - - - {{ lang.displayName }} - - - - + @@ -100,23 +85,19 @@ - diff --git a/src/views/desktop/LoginPage.vue b/src/views/desktop/LoginPage.vue index 6c89bbfa..db039764 100644 --- a/src/views/desktop/LoginPage.vue +++ b/src/views/desktop/LoginPage.vue @@ -129,22 +129,7 @@ - - - - - - {{ lang.displayName }} - - - - + @@ -175,7 +160,6 @@ import { ref, computed, useTemplateRef, nextTick } from 'vue'; import { useRouter } from 'vue-router'; import { useTheme } from 'vuetify'; -import type { LanguageOption } from '@/locales/index.ts'; import { useI18n } from '@/locales/helpers.ts'; import { useLoginPageBase } from '@/views/base/LoginPageBase.ts'; @@ -196,7 +180,7 @@ type SnackBarType = InstanceType; const router = useRouter(); const theme = useTheme(); -const { tt, getCurrentLanguageDisplayName, getAllLanguageOptions } = useI18n(); +const { tt } = useI18n(); const rootStore = useRootStore(); @@ -213,7 +197,6 @@ const { inputIsEmpty, twoFAInputIsEmpty, tips, - changeLanguage, doAfterLogin } = useLoginPageBase(); @@ -224,9 +207,7 @@ const snackbar = useTemplateRef('snackbar'); const show2faInput = ref(false); const showMobileQrCode = ref(false); -const allLanguages = computed(() => getAllLanguageOptions(false)); const isDarkMode = computed(() => theme.global.name.value === ThemeType.Dark); -const currentLanguageName = computed(() => getCurrentLanguageDisplayName()); function login(): void { if (!username.value) { diff --git a/src/views/desktop/ResetPasswordPage.vue b/src/views/desktop/ResetPasswordPage.vue index 81327522..9b2458ae 100644 --- a/src/views/desktop/ResetPasswordPage.vue +++ b/src/views/desktop/ResetPasswordPage.vue @@ -92,22 +92,7 @@ - - - - - - {{ lang.displayName }} - - - - + @@ -139,11 +124,9 @@ import { ref, computed, useTemplateRef } from 'vue'; import { useRouter } from 'vue-router'; import { useTheme } from 'vuetify'; -import type { LanguageOption } from '@/locales/index.ts'; import { useI18n } from '@/locales/helpers.ts'; import { useRootStore } from '@/stores/index.ts'; -import { useSettingsStore } from '@/stores/setting.ts'; import { APPLICATION_LOGO_PATH } from '@/consts/asset.ts'; import { ThemeType } from '@/core/theme.ts'; @@ -163,10 +146,9 @@ const props = defineProps<{ const router = useRouter(); const theme = useTheme(); -const { tt, getCurrentLanguageDisplayName, getAllLanguageOptions, setLanguage } = useI18n(); +const { tt } = useI18n(); const rootStore = useRootStore(); -const settingsStore = useSettingsStore(); const version = `v${getVersion()}`; @@ -181,9 +163,7 @@ const confirmPassword = ref(''); const updating = ref(false); const passwordChanged = ref(false); -const allLanguages = computed(() => getAllLanguageOptions(false)); const isDarkMode = computed(() => theme.global.name.value === ThemeType.Dark); -const currentLanguageName = computed(() => getCurrentLanguageDisplayName()); const inputProblemMessage = computed(() => { if (!email.value) { @@ -201,11 +181,6 @@ const inputProblemMessage = computed(() => { } }); -function changeLanguage(locale: string): void { - const localeDefaultSettings = setLanguage(locale); - settingsStore.updateLocalizedDefaultSettings(localeDefaultSettings); -} - function onSnackbarShowStateChanged(newValue: boolean): void { if (!newValue && passwordChanged.value) { router.replace('/login'); diff --git a/src/views/desktop/SignupPage.vue b/src/views/desktop/SignupPage.vue index 5a863e42..293b0c56 100644 --- a/src/views/desktop/SignupPage.vue +++ b/src/views/desktop/SignupPage.vue @@ -91,14 +91,29 @@ + > + + @@ -146,22 +161,8 @@ v-model="usePresetCategories"/> - - - - - - {{ lang.displayName }} - - - - + @@ -273,8 +274,8 @@ const { tt, getAllLanguageOptions, getAllCurrencies, getAllWeekDays, getAllTrans const { user, submitting, + languageTitle, currentLocale, - currentLanguageName, inputEmptyProblemMessage, inputInvalidProblemMessage, getCategoryTypeName, diff --git a/src/views/desktop/UnlockPage.vue b/src/views/desktop/UnlockPage.vue index b1892b3a..499c08d5 100644 --- a/src/views/desktop/UnlockPage.vue +++ b/src/views/desktop/UnlockPage.vue @@ -68,22 +68,7 @@ - - - - - - {{ lang.displayName }} - - - - + @@ -114,7 +99,6 @@ import { ref, computed, useTemplateRef } from 'vue'; import { useRouter } from 'vue-router'; import { useTheme } from 'vuetify'; -import type { LanguageOption } from '@/locales/index.ts'; import { useI18n } from '@/locales/helpers.ts'; import { useUnlockPageBase } from '@/views/base/UnlockPageBase.ts'; @@ -141,8 +125,8 @@ type SnackBarType = InstanceType; const router = useRouter(); const theme = useTheme(); -const { tt, getCurrentLanguageDisplayName, getAllLanguageOptions } = useI18n(); -const { version, pinCode, isWebAuthnAvailable, isPinCodeValid, changeLanguage, doAfterUnlocked, doRelogin } = useUnlockPageBase(); +const { tt } = useI18n(); +const { version, pinCode, isWebAuthnAvailable, isPinCodeValid, doAfterUnlocked, doRelogin } = useUnlockPageBase(); const settingsStore = useSettingsStore(); const userStore = useUserStore(); @@ -152,9 +136,7 @@ const snackbar = useTemplateRef('snackbar'); const verifyingByWebAuthn = ref(false); -const allLanguages = computed(() => getAllLanguageOptions(false)); const isDarkMode = computed(() => theme.global.name.value === ThemeType.Dark); -const currentLanguageName = computed(() => getCurrentLanguageDisplayName()); function unlockByWebAuthn(): void { const webAuthnCredentialId = getWebAuthnCredentialId(); diff --git a/src/views/desktop/VerifyEmailPage.vue b/src/views/desktop/VerifyEmailPage.vue index 76385ea7..2f56c982 100644 --- a/src/views/desktop/VerifyEmailPage.vue +++ b/src/views/desktop/VerifyEmailPage.vue @@ -71,22 +71,7 @@ - - - - - - {{ lang.displayName }} - - - - + @@ -117,11 +102,9 @@ import { ref, computed, useTemplateRef } from 'vue'; import { useRouter } from 'vue-router'; import { useTheme } from 'vuetify'; -import type { LanguageOption } from '@/locales/index.ts'; import { useI18n } from '@/locales/helpers.ts'; import { useRootStore } from '@/stores/index.ts'; -import { useSettingsStore } from '@/stores/setting.ts'; import { APPLICATION_LOGO_PATH } from '@/consts/asset.ts'; import { ThemeType } from '@/core/theme.ts'; @@ -145,10 +128,9 @@ const props = defineProps<{ const router = useRouter(); const theme = useTheme(); -const { tt, te, getCurrentLanguageDisplayName, getAllLanguageOptions, setLanguage } = useI18n(); +const { tt, te } = useI18n(); const rootStore = useRootStore(); -const settingsStore = useSettingsStore(); const version = `v${getVersion()}`; @@ -161,9 +143,7 @@ const resending = ref(false); const verified = ref(false); const errorMessage = ref(''); -const allLanguages = computed(() => getAllLanguageOptions(false)); const isDarkMode = computed(() => theme.global.name.value === ThemeType.Dark); -const currentLanguageName = computed(() => getCurrentLanguageDisplayName()); function init(): void { verified.value = false; @@ -210,11 +190,6 @@ function resendEmail(): void { }); } -function changeLanguage(locale: string): void { - const localeDefaultSettings = setLanguage(locale); - settingsStore.updateLocalizedDefaultSettings(localeDefaultSettings); -} - function onSnackbarShowStateChanged(newValue: boolean): void { if (!newValue && verified.value && isUserLogined()) { router.replace('/'); diff --git a/src/views/desktop/categories/list/dialogs/PresetDialog.vue b/src/views/desktop/categories/list/dialogs/PresetDialog.vue index 4cac72cc..ab25d069 100644 --- a/src/views/desktop/categories/list/dialogs/PresetDialog.vue +++ b/src/views/desktop/categories/list/dialogs/PresetDialog.vue @@ -11,19 +11,8 @@

{{ getCategoryTypeName(categoryType) }}

- - - - - - {{ lang.displayName }} - - - - +
@@ -76,7 +65,6 @@ import { useI18n } from '@/locales/helpers.ts'; import { useTransactionCategoriesStore } from '@/stores/transactionCategory.ts'; import type { PartialRecord } from '@/core/base.ts'; -import type { LanguageOption } from '@/locales/index.ts'; import { type LocalizedPresetCategory, CategoryType } from '@/core/category.ts'; import { categorizedArrayToPlainArray } from '@/lib/common.ts'; @@ -93,7 +81,7 @@ const emit = defineEmits<{ (e: 'category:saved', event: { message: string }): void; }>(); -const { tt, getCurrentLanguageTag, getAllLanguageOptions, getAllTransactionDefaultCategories, getLanguageInfo } = useI18n(); +const { tt, getCurrentLanguageTag, getAllTransactionDefaultCategories } = useI18n(); const transactionCategoriesStore = useTransactionCategoriesStore(); @@ -102,7 +90,6 @@ const snackbar = useTemplateRef('snackbar'); const currentLocale = ref(getCurrentLanguageTag()); const submitting = ref(false); -const allLanguages = computed(() => getAllLanguageOptions(false)); const allPresetCategories = computed>(() => getAllTransactionDefaultCategories(props.categoryType, currentLocale.value)); const showState = computed({ @@ -110,16 +97,6 @@ const showState = computed({ set: (value) => emit('update:show', value) }); -const currentLanguageName = computed(() => { - const languageInfo = getLanguageInfo(currentLocale.value); - - if (!languageInfo) { - return ''; - } - - return languageInfo.displayName; -}); - function getCategoryTypeName(categoryType: CategoryType): string { switch (categoryType) { case CategoryType.Income: diff --git a/src/views/desktop/user/settings/tabs/UserBasicSettingTab.vue b/src/views/desktop/user/settings/tabs/UserBasicSettingTab.vue index 417a336d..8da29458 100644 --- a/src/views/desktop/user/settings/tabs/UserBasicSettingTab.vue +++ b/src/views/desktop/user/settings/tabs/UserBasicSettingTab.vue @@ -117,7 +117,7 @@ + > + + @@ -351,7 +366,8 @@ import { isUserVerifyEmailEnabled } from '@/lib/server_settings.ts'; import { mdiAccount, - mdiAccountEditOutline + mdiAccountEditOutline, + mdiCheck } from '@mdi/js'; type ConfirmDialogType = InstanceType; diff --git a/src/views/mobile/LoginPage.vue b/src/views/mobile/LoginPage.vue index 867111e0..896bd8d3 100644 --- a/src/views/mobile/LoginPage.vue +++ b/src/views/mobile/LoginPage.vue @@ -56,7 +56,7 @@ - + - +