mirror of
https://github.com/mayswind/ezbookkeeping.git
synced 2026-05-20 01:34:24 +08:00
migrate i18n helper.js some code to typescript and migrate vue file to composition API and typescript
This commit is contained in:
+80
-84
@@ -4,10 +4,10 @@
|
|||||||
</v-app>
|
</v-app>
|
||||||
<v-snackbar class="cursor-pointer" color="notification-background" location="top"
|
<v-snackbar class="cursor-pointer" color="notification-background" location="top"
|
||||||
:multi-line="true" :timeout="-1" :close-on-content-click="true" v-model="showNotification">
|
:multi-line="true" :timeout="-1" :close-on-content-click="true" v-model="showNotification">
|
||||||
<v-tooltip activator="parent">{{ $t('Click to close') }}</v-tooltip>
|
<v-tooltip activator="parent">{{ tt('Click to close') }}</v-tooltip>
|
||||||
<div class="d-inline-flex">
|
<div class="d-inline-flex">
|
||||||
<img alt="logo" class="notification-logo" :src="ezBookkeepingLogoPath" />
|
<img alt="logo" class="notification-logo" :src="APPLICATION_LOGO_PATH" />
|
||||||
<span class="ml-2">{{ $t('global.app.title') }}</span>
|
<span class="ml-2">{{ tt('global.app.title') }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{{ currentNotificationContent }}
|
{{ currentNotificationContent }}
|
||||||
@@ -15,11 +15,14 @@
|
|||||||
</v-snackbar>
|
</v-snackbar>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup lang="ts">
|
||||||
|
import { type Ref, ref, computed, watch, onMounted } from 'vue';
|
||||||
|
|
||||||
import { useTheme } from 'vuetify';
|
import { useTheme } from 'vuetify';
|
||||||
import { register } from 'register-service-worker';
|
import { register } from 'register-service-worker';
|
||||||
|
|
||||||
import { mapStores } from 'pinia';
|
import { useI18n } from '@/locales/helpers.ts';
|
||||||
|
|
||||||
import { useRootStore } from '@/stores/index.js';
|
import { useRootStore } from '@/stores/index.js';
|
||||||
import { useSettingsStore } from '@/stores/setting.ts';
|
import { useSettingsStore } from '@/stores/setting.ts';
|
||||||
import { useUserStore } from '@/stores/user.ts';
|
import { useUserStore } from '@/stores/user.ts';
|
||||||
@@ -33,90 +36,83 @@ import { initMapProvider } from '@/lib/map/index.ts';
|
|||||||
import { isUserLogined, isUserUnlocked } from '@/lib/userstate.ts';
|
import { isUserLogined, isUserUnlocked } from '@/lib/userstate.ts';
|
||||||
import { getSystemTheme, setExpenseAndIncomeAmountColor } from '@/lib/ui/common.ts';
|
import { getSystemTheme, setExpenseAndIncomeAmountColor } from '@/lib/ui/common.ts';
|
||||||
|
|
||||||
export default {
|
const { tt, getCurrentLanguageInfo, setLanguage, initLocale } = useI18n();
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
showNotification: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
...mapStores(useRootStore, useSettingsStore, useUserStore, useTokensStore, useExchangeRatesStore),
|
|
||||||
ezBookkeepingLogoPath() {
|
|
||||||
return APPLICATION_LOGO_PATH;
|
|
||||||
},
|
|
||||||
currentNotificationContent() {
|
|
||||||
return this.rootStore.currentNotification;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
currentNotificationContent: function (newValue) {
|
|
||||||
this.showNotification = !!newValue;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
const self = this;
|
|
||||||
const theme = useTheme();
|
|
||||||
|
|
||||||
if (self.settingsStore.appSettings.theme === ThemeType.Light) {
|
const theme = useTheme();
|
||||||
theme.global.name.value = ThemeType.Light;
|
|
||||||
} else if (self.settingsStore.appSettings.theme === ThemeType.Dark) {
|
const rootStore = useRootStore();
|
||||||
|
const settingsStore = useSettingsStore();
|
||||||
|
const userStore = useUserStore();
|
||||||
|
const tokensStore = useTokensStore();
|
||||||
|
const exchangeRatesStore = useExchangeRatesStore();
|
||||||
|
|
||||||
|
const showNotification: Ref<boolean> = ref(false);
|
||||||
|
|
||||||
|
const currentNotificationContent = computed<string | null>(() => rootStore.currentNotification);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
const languageInfo = getCurrentLanguageInfo();
|
||||||
|
initMapProvider(languageInfo?.alternativeLanguageTag);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(currentNotificationContent, (newValue) => {
|
||||||
|
showNotification.value = !!newValue;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (settingsStore.appSettings.theme === ThemeType.Light) {
|
||||||
|
theme.global.name.value = ThemeType.Light;
|
||||||
|
} else if (settingsStore.appSettings.theme === ThemeType.Dark) {
|
||||||
|
theme.global.name.value = ThemeType.Dark;
|
||||||
|
} else {
|
||||||
|
theme.global.name.value = getSystemTheme();
|
||||||
|
}
|
||||||
|
|
||||||
|
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', function (e) {
|
||||||
|
if (settingsStore.appSettings.theme === 'auto') {
|
||||||
|
if (e.matches) {
|
||||||
theme.global.name.value = ThemeType.Dark;
|
theme.global.name.value = ThemeType.Dark;
|
||||||
} else {
|
} else {
|
||||||
theme.global.name.value = getSystemTheme();
|
theme.global.name.value = ThemeType.Light;
|
||||||
}
|
}
|
||||||
|
|
||||||
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', function (e) {
|
|
||||||
if (self.settingsStore.appSettings.theme === 'auto') {
|
|
||||||
if (e.matches) {
|
|
||||||
theme.global.name.value = ThemeType.Dark;
|
|
||||||
} else {
|
|
||||||
theme.global.name.value = ThemeType.Light;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let localeDefaultSettings = self.$locale.initLocale(self.userStore.currentUserLanguage, self.settingsStore.appSettings.timeZone);
|
|
||||||
self.settingsStore.updateLocalizedDefaultSettings(localeDefaultSettings);
|
|
||||||
|
|
||||||
setExpenseAndIncomeAmountColor(self.userStore.currentUserExpenseAmountColor, self.userStore.currentUserIncomeAmountColor);
|
|
||||||
|
|
||||||
if (isUserLogined()) {
|
|
||||||
if (!self.settingsStore.appSettings.applicationLock || isUserUnlocked()) {
|
|
||||||
// refresh token if user is logined
|
|
||||||
self.tokensStore.refreshTokenAndRevokeOldToken().then(response => {
|
|
||||||
if (response.user) {
|
|
||||||
localeDefaultSettings = self.$locale.setLanguage(response.user.language);
|
|
||||||
self.settingsStore.updateLocalizedDefaultSettings(localeDefaultSettings);
|
|
||||||
|
|
||||||
setExpenseAndIncomeAmountColor(response.user.expenseAmountColor, response.user.incomeAmountColor);
|
|
||||||
|
|
||||||
if (response.notificationContent) {
|
|
||||||
self.rootStore.setNotificationContent(response.notificationContent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// auto refresh exchange rates data
|
|
||||||
if (self.settingsStore.appSettings.autoUpdateExchangeRatesData) {
|
|
||||||
self.exchangeRatesStore.getLatestExchangeRates({ silent: true, force: false });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isProduction()) {
|
|
||||||
register('./sw.js', {
|
|
||||||
registrationOptions: {
|
|
||||||
scope: './'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
|
||||||
const languageInfo = this.$locale.getCurrentLanguageInfo();
|
|
||||||
initMapProvider(languageInfo ? languageInfo.alternativeLanguageTag : null);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let localeDefaultSettings = initLocale(userStore.currentUserLanguage, settingsStore.appSettings.timeZone);
|
||||||
|
settingsStore.updateLocalizedDefaultSettings(localeDefaultSettings);
|
||||||
|
|
||||||
|
setExpenseAndIncomeAmountColor(userStore.currentUserExpenseAmountColor, userStore.currentUserIncomeAmountColor);
|
||||||
|
|
||||||
|
if (isUserLogined()) {
|
||||||
|
if (!settingsStore.appSettings.applicationLock || isUserUnlocked()) {
|
||||||
|
// refresh token if user is logined
|
||||||
|
tokensStore.refreshTokenAndRevokeOldToken().then(response => {
|
||||||
|
if (response.user) {
|
||||||
|
localeDefaultSettings = setLanguage(response.user.language);
|
||||||
|
settingsStore.updateLocalizedDefaultSettings(localeDefaultSettings);
|
||||||
|
|
||||||
|
setExpenseAndIncomeAmountColor(response.user.expenseAmountColor, response.user.incomeAmountColor);
|
||||||
|
|
||||||
|
if (response.notificationContent) {
|
||||||
|
rootStore.setNotificationContent(response.notificationContent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// auto refresh exchange rates data
|
||||||
|
if (settingsStore.appSettings.autoUpdateExchangeRatesData) {
|
||||||
|
exchangeRatesStore.getLatestExchangeRates({ silent: true, force: false });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isProduction()) {
|
||||||
|
register('./sw.js', {
|
||||||
|
registrationOptions: {
|
||||||
|
scope: './'
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
+195
-194
@@ -4,11 +4,16 @@
|
|||||||
</f7-app>
|
</f7-app>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup lang="ts">
|
||||||
|
import { type Ref, ref, computed, watch, onMounted } from 'vue';
|
||||||
|
|
||||||
|
import type { Notification } from 'framework7/components/notification';
|
||||||
|
import type { Actions, Dialog, Popover, Popup, Sheet } from 'framework7/types';
|
||||||
import { f7ready } from 'framework7-vue';
|
import { f7ready } from 'framework7-vue';
|
||||||
import routes from './router/mobile.js';
|
import routes from './router/mobile.js';
|
||||||
|
|
||||||
import { mapStores } from 'pinia';
|
import { useI18n } from '@/locales/helpers.ts';
|
||||||
|
|
||||||
import { useRootStore } from '@/stores/index.js';
|
import { useRootStore } from '@/stores/index.js';
|
||||||
import { useSettingsStore } from '@/stores/setting.ts';
|
import { useSettingsStore } from '@/stores/setting.ts';
|
||||||
import { useUserStore } from '@/stores/user.ts';
|
import { useUserStore } from '@/stores/user.ts';
|
||||||
@@ -24,10 +29,23 @@ import { isUserLogined, isUserUnlocked } from '@/lib/userstate.ts';
|
|||||||
import { setExpenseAndIncomeAmountColor } from '@/lib/ui/common.ts';
|
import { setExpenseAndIncomeAmountColor } from '@/lib/ui/common.ts';
|
||||||
import { isModalShowing, setAppFontSize } from '@/lib/ui/mobile.js';
|
import { isModalShowing, setAppFontSize } from '@/lib/ui/mobile.js';
|
||||||
|
|
||||||
export default {
|
const { tt, getCurrentLanguageInfo, setLanguage, initLocale } = useI18n();
|
||||||
data() {
|
|
||||||
const self = this;
|
const rootStore = useRootStore();
|
||||||
let darkMode = 'auto';
|
const settingsStore = useSettingsStore();
|
||||||
|
const userStore = useUserStore();
|
||||||
|
const tokensStore = useTokensStore();
|
||||||
|
const exchangeRatesStore = useExchangeRatesStore();
|
||||||
|
|
||||||
|
const f7params: Ref<object> = ref({
|
||||||
|
name: 'ezBookkeeping',
|
||||||
|
theme: 'ios',
|
||||||
|
colors: {
|
||||||
|
primary: '#c67e48'
|
||||||
|
},
|
||||||
|
routes: routes,
|
||||||
|
darkMode: (() => {
|
||||||
|
let darkMode: boolean | string = 'auto';
|
||||||
|
|
||||||
if (getTheme() === ThemeType.Light) {
|
if (getTheme() === ThemeType.Light) {
|
||||||
darkMode = false;
|
darkMode = false;
|
||||||
@@ -35,207 +53,190 @@ export default {
|
|||||||
darkMode = true;
|
darkMode = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return darkMode;
|
||||||
notification: null,
|
})(),
|
||||||
f7params: {
|
touch: {
|
||||||
name: 'ezBookkeeping',
|
disableContextMenu: true,
|
||||||
theme: 'ios',
|
tapHold: true
|
||||||
colors: {
|
|
||||||
primary: '#c67e48'
|
|
||||||
},
|
|
||||||
routes: routes,
|
|
||||||
darkMode: darkMode,
|
|
||||||
touch: {
|
|
||||||
disableContextMenu: true,
|
|
||||||
tapHold: true
|
|
||||||
},
|
|
||||||
serviceWorker: {
|
|
||||||
path: isProduction() ? './sw.js' : undefined,
|
|
||||||
scope: './',
|
|
||||||
},
|
|
||||||
actions: {
|
|
||||||
animate: isEnableAnimate(),
|
|
||||||
backdrop: true,
|
|
||||||
closeOnEscape: true
|
|
||||||
},
|
|
||||||
dialog: {
|
|
||||||
animate: isEnableAnimate(),
|
|
||||||
backdrop: true
|
|
||||||
},
|
|
||||||
popover: {
|
|
||||||
animate: isEnableAnimate(),
|
|
||||||
backdrop: true,
|
|
||||||
closeOnEscape: true
|
|
||||||
},
|
|
||||||
popup: {
|
|
||||||
animate: isEnableAnimate(),
|
|
||||||
backdrop: true,
|
|
||||||
closeOnEscape: true,
|
|
||||||
swipeToClose: true
|
|
||||||
},
|
|
||||||
sheet: {
|
|
||||||
animate: isEnableAnimate(),
|
|
||||||
backdrop: true,
|
|
||||||
closeOnEscape: true
|
|
||||||
},
|
|
||||||
smartSelect: {
|
|
||||||
routableModals: false
|
|
||||||
},
|
|
||||||
view: {
|
|
||||||
animate: isEnableAnimate(),
|
|
||||||
browserHistory: !self.isiOSHomeScreenMode(),
|
|
||||||
browserHistoryInitialMatch: true,
|
|
||||||
browserHistoryAnimate: false,
|
|
||||||
iosSwipeBackAnimateShadow: false,
|
|
||||||
mdSwipeBackAnimateShadow: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
isDarkMode: undefined,
|
|
||||||
hasPushPopupBackdrop: undefined,
|
|
||||||
hasBackdrop: undefined
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
computed: {
|
serviceWorker: {
|
||||||
...mapStores(useRootStore, useSettingsStore, useUserStore, useTokensStore, useExchangeRatesStore),
|
path: isProduction() ? './sw.js' : undefined,
|
||||||
currentNotificationContent() {
|
scope: './',
|
||||||
return this.rootStore.currentNotification;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
watch: {
|
actions: {
|
||||||
currentNotificationContent: function (newValue) {
|
animate: isEnableAnimate(),
|
||||||
const self = this;
|
backdrop: true,
|
||||||
|
closeOnEscape: true
|
||||||
|
},
|
||||||
|
dialog: {
|
||||||
|
animate: isEnableAnimate(),
|
||||||
|
backdrop: true
|
||||||
|
},
|
||||||
|
popover: {
|
||||||
|
animate: isEnableAnimate(),
|
||||||
|
backdrop: true,
|
||||||
|
closeOnEscape: true
|
||||||
|
},
|
||||||
|
popup: {
|
||||||
|
animate: isEnableAnimate(),
|
||||||
|
backdrop: true,
|
||||||
|
closeOnEscape: true,
|
||||||
|
swipeToClose: true
|
||||||
|
},
|
||||||
|
sheet: {
|
||||||
|
animate: isEnableAnimate(),
|
||||||
|
backdrop: true,
|
||||||
|
closeOnEscape: true
|
||||||
|
},
|
||||||
|
smartSelect: {
|
||||||
|
routableModals: false
|
||||||
|
},
|
||||||
|
view: {
|
||||||
|
animate: isEnableAnimate(),
|
||||||
|
browserHistory: !isiOSHomeScreenMode(),
|
||||||
|
browserHistoryInitialMatch: true,
|
||||||
|
browserHistoryAnimate: false,
|
||||||
|
iosSwipeBackAnimateShadow: false,
|
||||||
|
mdSwipeBackAnimateShadow: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
if (self.notification) {
|
const notification: Ref<Notification.Notification | null> = ref(null);
|
||||||
self.notification.close();
|
|
||||||
self.notification.destroy();
|
const isDarkMode: Ref<boolean | undefined> = ref(undefined);
|
||||||
self.notification = null;
|
const hasPushPopupBackdrop: Ref<boolean | undefined> = ref(undefined);
|
||||||
|
const hasBackdrop: Ref<boolean | undefined> = ref(undefined);
|
||||||
|
const currentNotificationContent = computed<string | null>(() => rootStore.currentNotification);
|
||||||
|
|
||||||
|
function isiOSHomeScreenMode(): boolean {
|
||||||
|
if ((/iphone|ipod|ipad/gi).test(navigator.platform) && (/Safari/i).test(navigator.appVersion) &&
|
||||||
|
window.matchMedia && window.matchMedia('(display-mode: standalone)').matches
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setThemeColorMeta(darkMode: boolean | undefined): void {
|
||||||
|
if (hasPushPopupBackdrop.value) {
|
||||||
|
document.querySelector('meta[name=theme-color]')?.setAttribute('content', '#000');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (darkMode) {
|
||||||
|
if (hasBackdrop.value) {
|
||||||
|
document.querySelector('meta[name=theme-color]')?.setAttribute('content', '#0b0b0b');
|
||||||
|
} else {
|
||||||
|
document.querySelector('meta[name=theme-color]')?.setAttribute('content', '#121212');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (hasBackdrop.value) {
|
||||||
|
document.querySelector('meta[name=theme-color]')?.setAttribute('content', '#949495');
|
||||||
|
} else {
|
||||||
|
document.querySelector('meta[name=theme-color]')?.setAttribute('content', '#f6f6f8');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onBackdropChanged(element: { push?: boolean, opened?: boolean }): void {
|
||||||
|
if (element.push) {
|
||||||
|
hasPushPopupBackdrop.value = element.opened;
|
||||||
|
} else {
|
||||||
|
hasBackdrop.value = element.opened;
|
||||||
|
}
|
||||||
|
|
||||||
|
setThemeColorMeta(isDarkMode.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
setAppFontSize(settingsStore.appSettings.fontSize);
|
||||||
|
|
||||||
|
f7ready((f7) => {
|
||||||
|
isDarkMode.value = f7.darkMode;
|
||||||
|
setThemeColorMeta(f7.darkMode);
|
||||||
|
|
||||||
|
f7.on('actionsOpen', (actions: Actions.Actions) => onBackdropChanged(actions));
|
||||||
|
f7.on('actionsClose', (actions: Actions.Actions) => onBackdropChanged(actions));
|
||||||
|
f7.on('dialogOpen', (dialog: Dialog.Dialog) => onBackdropChanged(dialog));
|
||||||
|
f7.on('dialogClose', (dialog: Dialog.Dialog) => onBackdropChanged(dialog));
|
||||||
|
f7.on('popoverOpen', (popover: Popover.Popover) => onBackdropChanged(popover));
|
||||||
|
f7.on('popoverClose', (popover: Popover.Popover) => onBackdropChanged(popover));
|
||||||
|
f7.on('popupOpen', (popup: Popup.Popup) => onBackdropChanged(popup));
|
||||||
|
f7.on('popupClose', (popup: Popup.Popup) => onBackdropChanged(popup));
|
||||||
|
f7.on('sheetOpen', (sheet: Sheet.Sheet) => onBackdropChanged(sheet));
|
||||||
|
f7.on('sheetClose', (sheet: Sheet.Sheet) => onBackdropChanged(sheet));
|
||||||
|
|
||||||
|
f7.on('pageBeforeOut', () => {
|
||||||
|
if (isModalShowing()) {
|
||||||
|
f7.actions.close('.actions-modal.modal-in', false);
|
||||||
|
f7.dialog.close('.dialog.modal-in', false);
|
||||||
|
f7.popover.close('.popover.modal-in', false);
|
||||||
|
f7.popup.close('.popup.modal-in', false);
|
||||||
|
f7.sheet.close('.sheet-modal.modal-in', false);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
if (newValue) {
|
f7.on('darkModeChange', (darkMode) => {
|
||||||
f7ready((f7) => {
|
isDarkMode.value = darkMode;
|
||||||
self.notification = f7.notification.create({
|
setThemeColorMeta(darkMode);
|
||||||
icon: `<img alt="logo" src="${APPLICATION_LOGO_PATH}" />`,
|
});
|
||||||
title: self.$t('global.app.title'),
|
});
|
||||||
text: newValue,
|
|
||||||
closeOnClick: true,
|
|
||||||
on: {
|
|
||||||
close() {
|
|
||||||
self.rootStore.setNotificationContent(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}).open();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
const self = this;
|
|
||||||
|
|
||||||
let localeDefaultSettings = self.$locale.initLocale(self.userStore.currentUserLanguage, self.settingsStore.appSettings.timeZone);
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
self.settingsStore.updateLocalizedDefaultSettings(localeDefaultSettings);
|
const languageInfo = getCurrentLanguageInfo();
|
||||||
|
initMapProvider(languageInfo?.alternativeLanguageTag);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
setExpenseAndIncomeAmountColor(self.userStore.currentUserExpenseAmountColor, self.userStore.currentUserIncomeAmountColor);
|
watch(currentNotificationContent, (newValue) => {
|
||||||
|
if (notification.value) {
|
||||||
if (isUserLogined()) {
|
notification.value.close();
|
||||||
if (!self.settingsStore.appSettings.applicationLock || isUserUnlocked()) {
|
notification.value.destroy();
|
||||||
// refresh token if user is logined
|
notification.value = null;
|
||||||
self.tokensStore.refreshTokenAndRevokeOldToken().then(response => {
|
}
|
||||||
if (response.user) {
|
|
||||||
localeDefaultSettings = self.$locale.setLanguage(response.user.language);
|
|
||||||
self.settingsStore.updateLocalizedDefaultSettings(localeDefaultSettings);
|
|
||||||
|
|
||||||
setExpenseAndIncomeAmountColor(response.user.expenseAmountColor, response.user.incomeAmountColor);
|
|
||||||
|
|
||||||
if (response.notificationContent) {
|
|
||||||
self.rootStore.setNotificationContent(response.notificationContent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// auto refresh exchange rates data
|
|
||||||
if (self.settingsStore.appSettings.autoUpdateExchangeRatesData) {
|
|
||||||
self.exchangeRatesStore.getLatestExchangeRates({ silent: true, force: false });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
setAppFontSize(this.settingsStore.appSettings.fontSize);
|
|
||||||
|
|
||||||
|
if (newValue) {
|
||||||
f7ready((f7) => {
|
f7ready((f7) => {
|
||||||
this.isDarkMode = f7.darkMode;
|
notification.value = f7.notification.create({
|
||||||
this.setThemeColorMeta(f7.darkMode);
|
icon: `<img alt="logo" src="${APPLICATION_LOGO_PATH}" />`,
|
||||||
|
title: tt('global.app.title'),
|
||||||
f7.on('actionsOpen', (event) => this.onBackdropChanged(event));
|
text: newValue,
|
||||||
f7.on('actionsClose', (event) => this.onBackdropChanged(event));
|
closeOnClick: true,
|
||||||
f7.on('dialogOpen', (event) => this.onBackdropChanged(event));
|
on: {
|
||||||
f7.on('dialogClose', (event) => this.onBackdropChanged(event));
|
close() {
|
||||||
f7.on('popoverOpen', (event) => this.onBackdropChanged(event));
|
rootStore.setNotificationContent(null);
|
||||||
f7.on('popoverClose', (event) => this.onBackdropChanged(event));
|
}
|
||||||
f7.on('popupOpen', (event) => this.onBackdropChanged(event));
|
|
||||||
f7.on('popupClose', (event) => this.onBackdropChanged(event));
|
|
||||||
f7.on('sheetOpen', (event) => this.onBackdropChanged(event));
|
|
||||||
f7.on('sheetClose', (event) => this.onBackdropChanged(event));
|
|
||||||
|
|
||||||
f7.on('pageBeforeOut', () => {
|
|
||||||
if (isModalShowing()) {
|
|
||||||
f7.actions.close('.actions-modal.modal-in', false);
|
|
||||||
f7.dialog.close('.dialog.modal-in', false);
|
|
||||||
f7.popover.close('.popover.modal-in', false);
|
|
||||||
f7.popup.close('.popup.modal-in', false);
|
|
||||||
f7.sheet.close('.sheet-modal.modal-in', false);
|
|
||||||
}
|
}
|
||||||
});
|
}).open();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
f7.on('darkModeChange', (isDarkMode) => {
|
let localeDefaultSettings = initLocale(userStore.currentUserLanguage, settingsStore.appSettings.timeZone);
|
||||||
this.isDarkMode = isDarkMode;
|
settingsStore.updateLocalizedDefaultSettings(localeDefaultSettings);
|
||||||
this.setThemeColorMeta(isDarkMode);
|
|
||||||
});
|
setExpenseAndIncomeAmountColor(userStore.currentUserExpenseAmountColor, userStore.currentUserIncomeAmountColor);
|
||||||
|
|
||||||
|
if (isUserLogined()) {
|
||||||
|
if (!settingsStore.appSettings.applicationLock || isUserUnlocked()) {
|
||||||
|
// refresh token if user is logined
|
||||||
|
tokensStore.refreshTokenAndRevokeOldToken().then(response => {
|
||||||
|
if (response.user) {
|
||||||
|
localeDefaultSettings = setLanguage(response.user.language);
|
||||||
|
settingsStore.updateLocalizedDefaultSettings(localeDefaultSettings);
|
||||||
|
|
||||||
|
setExpenseAndIncomeAmountColor(response.user.expenseAmountColor, response.user.incomeAmountColor);
|
||||||
|
|
||||||
|
if (response.notificationContent) {
|
||||||
|
rootStore.setNotificationContent(response.notificationContent);
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
// auto refresh exchange rates data
|
||||||
const languageInfo = this.$locale.getCurrentLanguageInfo();
|
if (settingsStore.appSettings.autoUpdateExchangeRatesData) {
|
||||||
initMapProvider(languageInfo ? languageInfo.alternativeLanguageTag : null);
|
exchangeRatesStore.getLatestExchangeRates({ silent: true, force: false });
|
||||||
});
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
isiOSHomeScreenMode() {
|
|
||||||
if ((/iphone|ipod|ipad/gi).test(navigator.platform) && (/Safari/i).test(navigator.appVersion) &&
|
|
||||||
window.matchMedia && window.matchMedia('(display-mode: standalone)').matches
|
|
||||||
) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
onBackdropChanged(event) {
|
|
||||||
if (event.push) {
|
|
||||||
this.hasPushPopupBackdrop = event.opened;
|
|
||||||
} else {
|
|
||||||
this.hasBackdrop = event.opened;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setThemeColorMeta(this.isDarkMode);
|
|
||||||
},
|
|
||||||
setThemeColorMeta(isDarkMode) {
|
|
||||||
if (this.hasPushPopupBackdrop) {
|
|
||||||
document.querySelector('meta[name=theme-color]').setAttribute('content', '#000');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isDarkMode) {
|
|
||||||
if (this.hasBackdrop) {
|
|
||||||
document.querySelector('meta[name=theme-color]').setAttribute('content', '#0b0b0b');
|
|
||||||
} else {
|
|
||||||
document.querySelector('meta[name=theme-color]').setAttribute('content', '#121212');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (this.hasBackdrop) {
|
|
||||||
document.querySelector('meta[name=theme-color]').setAttribute('content', '#949495');
|
|
||||||
} else {
|
|
||||||
document.querySelector('meta[name=theme-color]').setAttribute('content', '#f6f6f8');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { type Ref, ref, computed, useTemplateRef } from 'vue';
|
import { type Ref, ref, computed, useTemplateRef } from 'vue';
|
||||||
|
|
||||||
import { useI18n } from '@/locales/helper.js';
|
import { useI18n } from '@/locales/helpers.ts';
|
||||||
|
|
||||||
import { copyObjectTo } from '@/lib/common.ts';
|
import { copyObjectTo } from '@/lib/common.ts';
|
||||||
import type { MapInstance, MapPosition } from '@/lib/map/base.ts';
|
import type { MapInstance, MapPosition } from '@/lib/map/base.ts';
|
||||||
|
|||||||
@@ -7,8 +7,8 @@
|
|||||||
<v-card-text v-if="textContent" class="pa-4 pb-6">{{ textContent }}</v-card-text>
|
<v-card-text v-if="textContent" class="pa-4 pb-6">{{ textContent }}</v-card-text>
|
||||||
<v-card-actions class="px-4 pb-4">
|
<v-card-actions class="px-4 pb-4">
|
||||||
<v-spacer></v-spacer>
|
<v-spacer></v-spacer>
|
||||||
<v-btn color="gray" @click="cancel">{{ $t('Cancel') }}</v-btn>
|
<v-btn color="gray" @click="cancel">{{ tt('Cancel') }}</v-btn>
|
||||||
<v-btn :color="finalColor" @click="confirm">{{ $t('OK') }}</v-btn>
|
<v-btn :color="finalColor" @click="confirm">{{ tt('OK') }}</v-btn>
|
||||||
</v-card-actions>
|
</v-card-actions>
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-dialog>
|
</v-dialog>
|
||||||
@@ -17,7 +17,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { type Ref, ref, watch } from 'vue';
|
import { type Ref, ref, watch } from 'vue';
|
||||||
|
|
||||||
import { useI18n } from '@/locales/helper.js';
|
import { useI18n } from '@/locales/helpers.ts';
|
||||||
|
|
||||||
import { isString } from '@/lib/common.ts';
|
import { isString } from '@/lib/common.ts';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
@@ -46,7 +47,7 @@ function open(titleOrText: string, textOrOptions: string | Record<string, unknow
|
|||||||
|
|
||||||
if (isString(textOrOptions)) { // second parameter is text
|
if (isString(textOrOptions)) { // second parameter is text
|
||||||
titleContent.value = tt(titleOrText, options);
|
titleContent.value = tt(titleOrText, options);
|
||||||
textContent.value = tt(textOrOptions, options);
|
textContent.value = tt(textOrOptions as string, options);
|
||||||
} else { // second parameter is options
|
} else { // second parameter is options
|
||||||
const actualOptions = textOrOptions as Record<string, unknown>;
|
const actualOptions = textOrOptions as Record<string, unknown>;
|
||||||
titleContent.value = tt('global.app.title');
|
titleContent.value = tt('global.app.title');
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
{{ messageContent }}
|
{{ messageContent }}
|
||||||
|
|
||||||
<template #actions>
|
<template #actions>
|
||||||
<v-btn color="primary" variant="text" @click="showState = false">{{ $t('Close') }}</v-btn>
|
<v-btn color="primary" variant="text" @click="showState = false">{{ tt('Close') }}</v-btn>
|
||||||
</template>
|
</template>
|
||||||
</v-snackbar>
|
</v-snackbar>
|
||||||
</template>
|
</template>
|
||||||
@@ -11,8 +11,9 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { type Ref, ref, watch } from 'vue';
|
import { type Ref, ref, watch } from 'vue';
|
||||||
|
|
||||||
|
import { useI18n } from '@/locales/helpers.ts';
|
||||||
|
|
||||||
import { isObject } from '@/lib/common.ts';
|
import { isObject } from '@/lib/common.ts';
|
||||||
import { useI18n } from '@/locales/helper.js';
|
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'update:show', value: boolean): void
|
(e: 'update:show', value: boolean): void
|
||||||
|
|||||||
@@ -3,11 +3,11 @@
|
|||||||
<v-card class="pa-2 pa-sm-4 pa-md-4">
|
<v-card class="pa-2 pa-sm-4 pa-md-4">
|
||||||
<template #title>
|
<template #title>
|
||||||
<div class="d-flex align-center justify-center">
|
<div class="d-flex align-center justify-center">
|
||||||
<h4 class="text-h4">{{ $t('Use on Mobile Device') }}</h4>
|
<h4 class="text-h4">{{ tt('Use on Mobile Device') }}</h4>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #subtitle>
|
<template #subtitle>
|
||||||
<div class="text-body-1 text-center text-wrap mt-4">{{ $t('You can scan the QR code below on your mobile device.') }}</div>
|
<div class="text-body-1 text-center text-wrap mt-4">{{ tt('You can scan the QR code below on your mobile device.') }}</div>
|
||||||
</template>
|
</template>
|
||||||
<v-card-text class="mb-md-4">
|
<v-card-text class="mb-md-4">
|
||||||
<v-row>
|
<v-row>
|
||||||
@@ -20,8 +20,8 @@
|
|||||||
</v-card-text>
|
</v-card-text>
|
||||||
<v-card-text class="overflow-y-visible">
|
<v-card-text class="overflow-y-visible">
|
||||||
<div class="w-100 d-flex justify-center gap-4">
|
<div class="w-100 d-flex justify-center gap-4">
|
||||||
<v-btn :href="mobileVersionPath">{{$t('Switch to Mobile Version') }}</v-btn>
|
<v-btn :href="mobileVersionPath">{{ tt('Switch to Mobile Version') }}</v-btn>
|
||||||
<v-btn color="secondary" variant="tonal" @click="showState = false">{{ $t('Close') }}</v-btn>
|
<v-btn color="secondary" variant="tonal" @click="showState = false">{{ tt('Close') }}</v-btn>
|
||||||
</div>
|
</div>
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
</v-card>
|
</v-card>
|
||||||
@@ -31,6 +31,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
|
|
||||||
|
import { useI18n } from '@/locales/helpers.ts';
|
||||||
|
|
||||||
import { getMobileUrlQrCodePath } from '@/lib/qrcode.ts';
|
import { getMobileUrlQrCodePath } from '@/lib/qrcode.ts';
|
||||||
import { getMobileVersionPath } from '@/lib/version.ts';
|
import { getMobileVersionPath } from '@/lib/version.ts';
|
||||||
|
|
||||||
@@ -42,6 +44,8 @@ const emit = defineEmits<{
|
|||||||
(e: 'update:show', value: boolean): void
|
(e: 'update:show', value: boolean): void
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
const { tt } = useI18n();
|
||||||
|
|
||||||
const mobileUrlQrCodePath = getMobileUrlQrCodePath();
|
const mobileUrlQrCodePath = getMobileUrlQrCodePath();
|
||||||
const mobileVersionPath = getMobileVersionPath();
|
const mobileVersionPath = getMobileVersionPath();
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<div class="swipe-handler"></div>
|
<div class="swipe-handler"></div>
|
||||||
<div class="left"></div>
|
<div class="left"></div>
|
||||||
<div class="right">
|
<div class="right">
|
||||||
<f7-link sheet-close :text="$t('Done')"></f7-link>
|
<f7-link sheet-close :text="tt('Done')"></f7-link>
|
||||||
</div>
|
</div>
|
||||||
</f7-toolbar>
|
</f7-toolbar>
|
||||||
<f7-page-content>
|
<f7-page-content>
|
||||||
@@ -31,6 +31,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { type Ref, ref, computed } from 'vue';
|
import { type Ref, ref, computed } from 'vue';
|
||||||
|
|
||||||
|
import { useI18n } from '@/locales/helpers.ts';
|
||||||
|
|
||||||
import type { ColorValue, ColorInfo } from '@/core/color.ts';
|
import type { ColorValue, ColorInfo } from '@/core/color.ts';
|
||||||
import { arrayContainsFieldValue } from '@/lib/common.ts';
|
import { arrayContainsFieldValue } from '@/lib/common.ts';
|
||||||
import { getColorsInRows } from '@/lib/color.ts';
|
import { getColorsInRows } from '@/lib/color.ts';
|
||||||
@@ -48,6 +50,8 @@ const emit = defineEmits<{
|
|||||||
(e: 'update:show', value: boolean): void
|
(e: 'update:show', value: boolean): void
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
const { tt } = useI18n();
|
||||||
|
|
||||||
const currentValue: Ref<ColorValue> = ref(props.modelValue);
|
const currentValue: Ref<ColorValue> = ref(props.modelValue);
|
||||||
const itemPerRow: Ref<number> = ref(props.columnCount || 7);
|
const itemPerRow: Ref<number> = ref(props.columnCount || 7);
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<div class="swipe-handler"></div>
|
<div class="swipe-handler"></div>
|
||||||
<div class="left"></div>
|
<div class="left"></div>
|
||||||
<div class="right">
|
<div class="right">
|
||||||
<f7-link sheet-close :text="$t('Done')"></f7-link>
|
<f7-link sheet-close :text="tt('Done')"></f7-link>
|
||||||
</div>
|
</div>
|
||||||
</f7-toolbar>
|
</f7-toolbar>
|
||||||
<f7-page-content>
|
<f7-page-content>
|
||||||
@@ -31,6 +31,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { type Ref, ref, computed } from 'vue';
|
import { type Ref, ref, computed } from 'vue';
|
||||||
|
|
||||||
|
import { useI18n } from '@/locales/helpers.ts';
|
||||||
|
|
||||||
import type { IconInfo, IconInfoWithId } from '@/core/icon.ts';
|
import type { IconInfo, IconInfoWithId } from '@/core/icon.ts';
|
||||||
import { arrayContainsFieldValue } from '@/lib/common.ts';
|
import { arrayContainsFieldValue } from '@/lib/common.ts';
|
||||||
import { getIconsInRows } from '@/lib/icon.ts';
|
import { getIconsInRows } from '@/lib/icon.ts';
|
||||||
@@ -49,6 +51,8 @@ const emit = defineEmits<{
|
|||||||
(e: 'update:show', value: boolean): void
|
(e: 'update:show', value: boolean): void
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
const { tt } = useI18n();
|
||||||
|
|
||||||
const currentValue: Ref<string> = ref(props.modelValue);
|
const currentValue: Ref<string> = ref(props.modelValue);
|
||||||
const itemPerRow: Ref<number> = ref(props.columnCount || 7);
|
const itemPerRow: Ref<number> = ref(props.columnCount || 7);
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
</p>
|
</p>
|
||||||
<textarea class="information-content full-line" readonly="readonly" :rows="rowCount" :value="information"></textarea>
|
<textarea class="information-content full-line" readonly="readonly" :rows="rowCount" :value="information"></textarea>
|
||||||
<div class="margin-top text-align-center">
|
<div class="margin-top text-align-center">
|
||||||
<f7-link @click="close" :text="$t('Close')"></f7-link>
|
<f7-link @click="close" :text="tt('Close')"></f7-link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</f7-page-content>
|
</f7-page-content>
|
||||||
@@ -27,6 +27,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useTemplateRef, watch, onMounted, onUpdated } from 'vue';
|
import { useTemplateRef, watch, onMounted, onUpdated } from 'vue';
|
||||||
|
|
||||||
|
import { useI18n } from '@/locales/helpers.ts';
|
||||||
|
|
||||||
import { ClipboardHolder } from '@/lib/clipboard.ts';
|
import { ClipboardHolder } from '@/lib/clipboard.ts';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
@@ -43,6 +45,8 @@ const emit = defineEmits<{
|
|||||||
(e: 'info:copied'): void
|
(e: 'info:copied'): void
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
const { tt } = useI18n();
|
||||||
|
|
||||||
const iconCopyToClipboard = useTemplateRef('copyToClipboardIcon');
|
const iconCopyToClipboard = useTemplateRef('copyToClipboardIcon');
|
||||||
|
|
||||||
let clipboardHolder: ClipboardHolder | null = null;
|
let clipboardHolder: ClipboardHolder | null = null;
|
||||||
|
|||||||
@@ -5,23 +5,23 @@
|
|||||||
<div class="swipe-handler"></div>
|
<div class="swipe-handler"></div>
|
||||||
<div class="left"></div>
|
<div class="left"></div>
|
||||||
<div class="right">
|
<div class="right">
|
||||||
<f7-link :text="$t('Done')" @click="save"></f7-link>
|
<f7-link :text="tt('Done')" @click="save"></f7-link>
|
||||||
</div>
|
</div>
|
||||||
</f7-toolbar>
|
</f7-toolbar>
|
||||||
<f7-page-content class="no-margin-vertical no-padding-vertical">
|
<f7-page-content class="no-margin-vertical no-padding-vertical">
|
||||||
<map-view ref="map" height="400px" :geo-location="geoLocation">
|
<map-view ref="map" height="400px" :geo-location="geoLocation">
|
||||||
<template #error-title="{ mapSupported, mapDependencyLoaded }">
|
<template #error-title="{ mapSupported, mapDependencyLoaded }">
|
||||||
<div class="display-flex padding justify-content-space-between align-items-center">
|
<div class="display-flex padding justify-content-space-between align-items-center">
|
||||||
<div class="ebk-sheet-title" v-if="!mapSupported"><b>{{ $t('Unsupported Map Provider') }}</b></div>
|
<div class="ebk-sheet-title" v-if="!mapSupported"><b>{{ tt('Unsupported Map Provider') }}</b></div>
|
||||||
<div class="ebk-sheet-title" v-else-if="!mapDependencyLoaded"><b>{{ $t('Cannot Initialize Map') }}</b></div>
|
<div class="ebk-sheet-title" v-else-if="!mapDependencyLoaded"><b>{{ tt('Cannot Initialize Map') }}</b></div>
|
||||||
<div class="ebk-sheet-title" v-else></div>
|
<div class="ebk-sheet-title" v-else></div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #error-content>
|
<template #error-content>
|
||||||
<div class="padding-horizontal padding-bottom">
|
<div class="padding-horizontal padding-bottom">
|
||||||
<p class="no-margin">{{ $t('Please refresh the page and try again. If the error persists, ensure that the server\'s map settings are correctly configured.') }}</p>
|
<p class="no-margin">{{ tt('Please refresh the page and try again. If the error persists, ensure that the server\'s map settings are correctly configured.') }}</p>
|
||||||
<div class="margin-top text-align-center">
|
<div class="margin-top text-align-center">
|
||||||
<f7-link @click="close" :text="$t('Close')"></f7-link>
|
<f7-link @click="close" :text="tt('Close')"></f7-link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -34,6 +34,8 @@
|
|||||||
import { computed, useTemplateRef } from 'vue';
|
import { computed, useTemplateRef } from 'vue';
|
||||||
import MapView from '@/components/common/MapView.vue';
|
import MapView from '@/components/common/MapView.vue';
|
||||||
|
|
||||||
|
import { useI18n } from '@/locales/helpers.ts';
|
||||||
|
|
||||||
import type { MapPosition } from '@/lib/map/base.ts';
|
import type { MapPosition } from '@/lib/map/base.ts';
|
||||||
|
|
||||||
type MapViewType = InstanceType<typeof MapView>;
|
type MapViewType = InstanceType<typeof MapView>;
|
||||||
@@ -48,6 +50,8 @@ const emit = defineEmits<{
|
|||||||
(e: 'update:show', value: boolean): void
|
(e: 'update:show', value: boolean): void
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
const { tt } = useI18n();
|
||||||
|
|
||||||
const map = useTemplateRef<MapViewType>('map');
|
const map = useTemplateRef<MapViewType>('map');
|
||||||
|
|
||||||
const geoLocation = computed<MapPosition | undefined>({
|
const geoLocation = computed<MapPosition | undefined>({
|
||||||
|
|||||||
@@ -17,19 +17,19 @@
|
|||||||
floating-label
|
floating-label
|
||||||
clear-button
|
clear-button
|
||||||
class="no-margin no-padding-bottom"
|
class="no-margin no-padding-bottom"
|
||||||
:label="$t('Passcode')"
|
:label="tt('Passcode')"
|
||||||
:placeholder="$t('Passcode')"
|
:placeholder="tt('Passcode')"
|
||||||
v-model:value="currentPasscode"
|
v-model:value="currentPasscode"
|
||||||
@keyup.enter="confirm()"
|
@keyup.enter="confirm()"
|
||||||
></f7-list-input>
|
></f7-list-input>
|
||||||
</f7-list>
|
</f7-list>
|
||||||
<f7-button large fill
|
<f7-button large fill
|
||||||
:class="{ 'disabled': !currentPasscode || confirmDisabled }"
|
:class="{ 'disabled': !currentPasscode || confirmDisabled }"
|
||||||
:text="$t('Continue')"
|
:text="tt('Continue')"
|
||||||
@click="confirm">
|
@click="confirm">
|
||||||
</f7-button>
|
</f7-button>
|
||||||
<div class="margin-top text-align-center">
|
<div class="margin-top text-align-center">
|
||||||
<f7-link :class="{ 'disabled': cancelDisabled }" @click="cancel" :text="$t('Cancel')"></f7-link>
|
<f7-link :class="{ 'disabled': cancelDisabled }" @click="cancel" :text="tt('Cancel')"></f7-link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</f7-page-content>
|
</f7-page-content>
|
||||||
@@ -39,6 +39,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { type Ref, ref } from 'vue';
|
import { type Ref, ref } from 'vue';
|
||||||
|
|
||||||
|
import { useI18n } from '@/locales/helpers.ts';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
modelValue: string
|
modelValue: string
|
||||||
title?: string
|
title?: string
|
||||||
@@ -54,6 +56,8 @@ const emit = defineEmits<{
|
|||||||
(e: 'passcode:confirm', value: string): void
|
(e: 'passcode:confirm', value: string): void
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
const { tt } = useI18n();
|
||||||
|
|
||||||
const currentPasscode: Ref<string> = ref('');
|
const currentPasscode: Ref<string> = ref('');
|
||||||
|
|
||||||
function confirm() {
|
function confirm() {
|
||||||
|
|||||||
@@ -17,8 +17,8 @@
|
|||||||
clear-button
|
clear-button
|
||||||
class="no-margin no-padding-bottom"
|
class="no-margin no-padding-bottom"
|
||||||
:class="color ? 'color-' + color : ''"
|
:class="color ? 'color-' + color : ''"
|
||||||
:label="$t('Current Password')"
|
:label="tt('Current Password')"
|
||||||
:placeholder="$t('Current Password')"
|
:placeholder="tt('Current Password')"
|
||||||
v-model:value="currentPassword"
|
v-model:value="currentPassword"
|
||||||
@keyup.enter="confirm()"
|
@keyup.enter="confirm()"
|
||||||
></f7-list-input>
|
></f7-list-input>
|
||||||
@@ -26,11 +26,11 @@
|
|||||||
<f7-button large fill
|
<f7-button large fill
|
||||||
:class="{ 'disabled': !currentPassword || confirmDisabled }"
|
:class="{ 'disabled': !currentPassword || confirmDisabled }"
|
||||||
:color="color || 'primary'"
|
:color="color || 'primary'"
|
||||||
:text="$t('Continue')"
|
:text="tt('Continue')"
|
||||||
@click="confirm">
|
@click="confirm">
|
||||||
</f7-button>
|
</f7-button>
|
||||||
<div class="margin-top text-align-center">
|
<div class="margin-top text-align-center">
|
||||||
<f7-link :class="{ 'disabled': cancelDisabled }" @click="cancel" :text="$t('Cancel')"></f7-link>
|
<f7-link :class="{ 'disabled': cancelDisabled }" @click="cancel" :text="tt('Cancel')"></f7-link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</f7-page-content>
|
</f7-page-content>
|
||||||
@@ -40,6 +40,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { type Ref, ref } from 'vue';
|
import { type Ref, ref } from 'vue';
|
||||||
|
|
||||||
|
import { useI18n } from '@/locales/helpers.ts';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
modelValue: string
|
modelValue: string
|
||||||
title?: string
|
title?: string
|
||||||
@@ -56,6 +58,8 @@ const emit = defineEmits<{
|
|||||||
(e: 'password:confirm', value: string): void
|
(e: 'password:confirm', value: string): void
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
const { tt } = useI18n();
|
||||||
|
|
||||||
const currentPassword: Ref<string> = ref('');
|
const currentPassword: Ref<string> = ref('');
|
||||||
|
|
||||||
function confirm() {
|
function confirm() {
|
||||||
|
|||||||
@@ -15,11 +15,11 @@
|
|||||||
</f7-list>
|
</f7-list>
|
||||||
<f7-button large fill
|
<f7-button large fill
|
||||||
:class="{ 'disabled': !currentPinCodeValid || confirmDisabled }"
|
:class="{ 'disabled': !currentPinCodeValid || confirmDisabled }"
|
||||||
:text="$t('Continue')"
|
:text="tt('Continue')"
|
||||||
@click="confirm">
|
@click="confirm">
|
||||||
</f7-button>
|
</f7-button>
|
||||||
<div class="margin-top text-align-center">
|
<div class="margin-top text-align-center">
|
||||||
<f7-link @click="cancel" :text="$t('Cancel')"></f7-link>
|
<f7-link @click="cancel" :text="tt('Cancel')"></f7-link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</f7-page-content>
|
</f7-page-content>
|
||||||
@@ -29,6 +29,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { type Ref, ref, computed } from 'vue';
|
import { type Ref, ref, computed } from 'vue';
|
||||||
|
|
||||||
|
import { useI18n } from '@/locales/helpers.ts';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
modelValue: string
|
modelValue: string
|
||||||
title?: string
|
title?: string
|
||||||
@@ -44,6 +46,8 @@ const emit = defineEmits<{
|
|||||||
(e: 'pincode:confirm', value: string): void
|
(e: 'pincode:confirm', value: string): void
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
const { tt } = useI18n();
|
||||||
|
|
||||||
const currentPinCode: Ref<string> = ref('');
|
const currentPinCode: Ref<string> = ref('');
|
||||||
|
|
||||||
const currentPinCodeValid = computed<boolean>(() => {
|
const currentPinCodeValid = computed<boolean>(() => {
|
||||||
|
|||||||
@@ -62,6 +62,11 @@ export interface LocalizedRecentMonthDateRange {
|
|||||||
readonly displayName: string;
|
readonly displayName: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface LocalizedMeridiemIndicator {
|
||||||
|
readonly values: string[];
|
||||||
|
readonly displayValues: string[];
|
||||||
|
}
|
||||||
|
|
||||||
export class YearUnixTime implements UnixTimeRange {
|
export class YearUnixTime implements UnixTimeRange {
|
||||||
public readonly year: number;
|
public readonly year: number;
|
||||||
public readonly minUnixTime: number;
|
public readonly minUnixTime: number;
|
||||||
|
|||||||
+1
-1
@@ -71,8 +71,8 @@ import draggable from 'vuedraggable';
|
|||||||
import router from '@/router/desktop.js';
|
import router from '@/router/desktop.js';
|
||||||
|
|
||||||
import { getVersion, getBuildTime } from '@/lib/version.ts';
|
import { getVersion, getBuildTime } from '@/lib/version.ts';
|
||||||
|
import { getI18nOptions } from '@/locales/helpers.ts';
|
||||||
import {
|
import {
|
||||||
getI18nOptions,
|
|
||||||
translateIf,
|
translateIf,
|
||||||
translateError,
|
translateError,
|
||||||
i18nFunctions
|
i18nFunctions
|
||||||
|
|||||||
Vendored
+12
@@ -10,3 +10,15 @@ interface Window {
|
|||||||
[key: string]: string | number | boolean | undefined | null;
|
[key: string]: string | number | boolean | undefined | null;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface Navigator {
|
||||||
|
browserLanguage?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module "framework7/components/notification" {
|
||||||
|
export namespace Notification {
|
||||||
|
export interface Notification {
|
||||||
|
destroy(): void;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+1
-1
@@ -19,7 +19,7 @@ export class AmapMapProvider implements MapProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
public asyncLoadAssets(language: string): Promise<unknown> {
|
public asyncLoadAssets(language?: string): Promise<unknown> {
|
||||||
if (AmapMapProvider.AMap) {
|
if (AmapMapProvider.AMap) {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export class BaiduMapProvider implements MapProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
public asyncLoadAssets(language: string): Promise<unknown> {
|
public asyncLoadAssets(language?: string): Promise<unknown> {
|
||||||
if (BaiduMapProvider.BMap) {
|
if (BaiduMapProvider.BMap) {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
export interface MapProvider {
|
export interface MapProvider {
|
||||||
getWebsite(): string;
|
getWebsite(): string;
|
||||||
asyncLoadAssets(language: string): Promise<unknown>;
|
asyncLoadAssets(language?: string): Promise<unknown>;
|
||||||
createMapInstance(): MapInstance | null;
|
createMapInstance(): MapInstance | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export class GoogleMapProvider implements MapProvider {
|
|||||||
return 'https://maps.google.com';
|
return 'https://maps.google.com';
|
||||||
}
|
}
|
||||||
|
|
||||||
public asyncLoadAssets(language: string): Promise<unknown> {
|
public asyncLoadAssets(language?: string): Promise<unknown> {
|
||||||
if (GoogleMapProvider.GoogleMap) {
|
if (GoogleMapProvider.GoogleMap) {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { AmapMapProvider } from './amap.ts';
|
|||||||
|
|
||||||
let mapProvider: MapProvider | null = null;
|
let mapProvider: MapProvider | null = null;
|
||||||
|
|
||||||
export function initMapProvider(language: string): void {
|
export function initMapProvider(language?: string): void {
|
||||||
const mapProviderType = getMapProvider();
|
const mapProviderType = getMapProvider();
|
||||||
|
|
||||||
if (LEAFLET_TILE_SOURCES[mapProviderType] || mapProviderType === 'custom') {
|
if (LEAFLET_TILE_SOURCES[mapProviderType] || mapProviderType === 'custom') {
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ export class LeafletMapProvider implements MapProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
public asyncLoadAssets(language: string): Promise<unknown> {
|
public asyncLoadAssets(language?: string): Promise<unknown> {
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
import('leaflet/dist/leaflet.css'),
|
import('leaflet/dist/leaflet.css'),
|
||||||
import('leaflet/dist/leaflet-src.esm.js').then(leaflet => LeafletMapProvider.Leaflet = leaflet)
|
import('leaflet/dist/leaflet-src.esm.js').then(leaflet => LeafletMapProvider.Leaflet = leaflet)
|
||||||
|
|||||||
+1
-1
@@ -540,7 +540,7 @@ export default {
|
|||||||
|
|
||||||
return url;
|
return url;
|
||||||
},
|
},
|
||||||
generateGoogleMapJavascriptUrl: (language: string, callbackFnName: string): string => {
|
generateGoogleMapJavascriptUrl: (language: string | undefined, callbackFnName: string): string => {
|
||||||
let url = `${GOOGLE_MAP_JAVASCRIPT_URL}?key=${getGoogleMapAPIKey()}&libraries=core,marker&callback=${callbackFnName}`;
|
let url = `${GOOGLE_MAP_JAVASCRIPT_URL}?key=${getGoogleMapAPIKey()}&libraries=core,marker&callback=${callbackFnName}`;
|
||||||
|
|
||||||
if (language) {
|
if (language) {
|
||||||
|
|||||||
+5
-29
@@ -1,7 +1,7 @@
|
|||||||
import { useI18n as useVueI18n } from 'vue-i18n';
|
import { useI18n as useVueI18n } from 'vue-i18n';
|
||||||
import moment from 'moment-timezone';
|
import moment from 'moment-timezone';
|
||||||
|
|
||||||
import { defaultLanguage, allLanguages } from '@/locales/index.ts';
|
import { DEFAULT_LANGUAGE, allLanguages } from '@/locales/index.ts';
|
||||||
|
|
||||||
import { Month, WeekDay, MeridiemIndicator, LongDateFormat, ShortDateFormat, LongTimeFormat, ShortTimeFormat, DateRange, LANGUAGE_DEFAULT_DATE_TIME_FORMAT_VALUE } from '@/core/datetime.ts';
|
import { Month, WeekDay, MeridiemIndicator, LongDateFormat, ShortDateFormat, LongTimeFormat, ShortTimeFormat, DateRange, LANGUAGE_DEFAULT_DATE_TIME_FORMAT_VALUE } from '@/core/datetime.ts';
|
||||||
import { TimezoneTypeForStatistics } from '@/core/timezone.ts';
|
import { TimezoneTypeForStatistics } from '@/core/timezone.ts';
|
||||||
@@ -113,13 +113,13 @@ function getLanguageInfo(locale) {
|
|||||||
|
|
||||||
function getDefaultLanguage() {
|
function getDefaultLanguage() {
|
||||||
if (!window || !window.navigator) {
|
if (!window || !window.navigator) {
|
||||||
return defaultLanguage;
|
return DEFAULT_LANGUAGE;
|
||||||
}
|
}
|
||||||
|
|
||||||
let browserLocale = window.navigator.browserLanguage || window.navigator.language;
|
let browserLocale = window.navigator.browserLanguage || window.navigator.language;
|
||||||
|
|
||||||
if (!browserLocale) {
|
if (!browserLocale) {
|
||||||
return defaultLanguage;
|
return DEFAULT_LANGUAGE;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!allLanguages[browserLocale]) {
|
if (!allLanguages[browserLocale]) {
|
||||||
@@ -153,7 +153,7 @@ function getDefaultLanguage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!allLanguages[browserLocale]) {
|
if (!allLanguages[browserLocale]) {
|
||||||
return defaultLanguage;
|
return DEFAULT_LANGUAGE;
|
||||||
}
|
}
|
||||||
|
|
||||||
return browserLocale;
|
return browserLocale;
|
||||||
@@ -1270,7 +1270,7 @@ function getAllSupportedImportFileTypes(i18nGlobal, translateFn) {
|
|||||||
document.anchor = document.anchor.toLowerCase().replace(/ /g, '-');
|
document.anchor = document.anchor.toLowerCase().replace(/ /g, '-');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (document.language === defaultLanguage) {
|
if (document.language === DEFAULT_LANGUAGE) {
|
||||||
document.language = '';
|
document.language = '';
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -1541,29 +1541,6 @@ function initLocale(i18nGlobal, lastUserLanguage, timezone) {
|
|||||||
return localeDefaultSettings;
|
return localeDefaultSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getI18nOptions() {
|
|
||||||
return {
|
|
||||||
legacy: true,
|
|
||||||
locale: defaultLanguage,
|
|
||||||
fallbackLocale: defaultLanguage,
|
|
||||||
formatFallbackMessages: true,
|
|
||||||
messages: (function () {
|
|
||||||
const messages = {};
|
|
||||||
|
|
||||||
for (let locale in allLanguages) {
|
|
||||||
if (!Object.prototype.hasOwnProperty.call(allLanguages, locale)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const lang = allLanguages[locale];
|
|
||||||
messages[locale] = lang.content;
|
|
||||||
}
|
|
||||||
|
|
||||||
return messages;
|
|
||||||
})()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function translateIf(text, isTranslate, translateFn) {
|
export function translateIf(text, isTranslate, translateFn) {
|
||||||
if (isTranslate) {
|
if (isTranslate) {
|
||||||
return translateFn(text);
|
return translateFn(text);
|
||||||
@@ -1588,7 +1565,6 @@ export function i18nFunctions(i18nGlobal) {
|
|||||||
return {
|
return {
|
||||||
getAllLanguageInfoArray: (includeSystemDefault) => getAllLanguageInfoArray(i18nGlobal.t, includeSystemDefault),
|
getAllLanguageInfoArray: (includeSystemDefault) => getAllLanguageInfoArray(i18nGlobal.t, includeSystemDefault),
|
||||||
getLanguageInfo: getLanguageInfo,
|
getLanguageInfo: getLanguageInfo,
|
||||||
getDefaultLanguage: getDefaultLanguage,
|
|
||||||
getCurrentLanguageTag: () => getCurrentLanguageTag(i18nGlobal),
|
getCurrentLanguageTag: () => getCurrentLanguageTag(i18nGlobal),
|
||||||
getCurrentLanguageInfo: () => getCurrentLanguageInfo(i18nGlobal),
|
getCurrentLanguageInfo: () => getCurrentLanguageInfo(i18nGlobal),
|
||||||
getCurrentLanguageDisplayName: () => getCurrentLanguageDisplayName(i18nGlobal),
|
getCurrentLanguageDisplayName: () => getCurrentLanguageDisplayName(i18nGlobal),
|
||||||
|
|||||||
@@ -0,0 +1,420 @@
|
|||||||
|
import { useI18n as useVueI18n } from 'vue-i18n';
|
||||||
|
import moment from 'moment-timezone';
|
||||||
|
|
||||||
|
import { type LanguageInfo, allLanguages, DEFAULT_LANGUAGE } from '@/locales/index.ts';
|
||||||
|
|
||||||
|
import {
|
||||||
|
type LocalizedMeridiemIndicator,
|
||||||
|
Month,
|
||||||
|
WeekDay,
|
||||||
|
MeridiemIndicator
|
||||||
|
} from '@/core/datetime.ts';
|
||||||
|
|
||||||
|
import type { LocaleDefaultSettings } from '@/core/setting.ts';
|
||||||
|
import type { ErrorResponse } from '@/core/api.ts';
|
||||||
|
|
||||||
|
import { KnownErrorCode, SPECIFIED_API_NOT_FOUND_ERRORS, PARAMETERIZED_ERRORS } from '@/consts/api.ts';
|
||||||
|
|
||||||
|
import {
|
||||||
|
isString,
|
||||||
|
isObject
|
||||||
|
} from '@/lib/common.ts';
|
||||||
|
|
||||||
|
import {
|
||||||
|
isPM,
|
||||||
|
getTimezoneOffset
|
||||||
|
} from '@/lib/datetime.ts';
|
||||||
|
|
||||||
|
import logger from '@/lib/logger.ts';
|
||||||
|
import services from '@/lib/services.ts';
|
||||||
|
|
||||||
|
export interface LocalizedErrorParameter {
|
||||||
|
key: string;
|
||||||
|
localized: boolean;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LocalizedError {
|
||||||
|
message: string;
|
||||||
|
parameters?: LocalizedErrorParameter[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getI18nOptions(): object {
|
||||||
|
return {
|
||||||
|
legacy: true,
|
||||||
|
locale: DEFAULT_LANGUAGE,
|
||||||
|
fallbackLocale: DEFAULT_LANGUAGE,
|
||||||
|
formatFallbackMessages: true,
|
||||||
|
messages: (function () {
|
||||||
|
const messages: Record<string, object> = {};
|
||||||
|
|
||||||
|
for (const languageKey in allLanguages) {
|
||||||
|
if (!Object.prototype.hasOwnProperty.call(allLanguages, languageKey)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const languageInfo = allLanguages[languageKey];
|
||||||
|
messages[languageKey] = languageInfo.content;
|
||||||
|
}
|
||||||
|
|
||||||
|
return messages;
|
||||||
|
})()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useI18n() {
|
||||||
|
const { t, locale } = useVueI18n();
|
||||||
|
|
||||||
|
// private functions
|
||||||
|
function getLanguageInfo(languageKey: string): LanguageInfo | undefined {
|
||||||
|
return allLanguages[languageKey];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDefaultLanguage(): string {
|
||||||
|
if (!window || !window.navigator) {
|
||||||
|
return DEFAULT_LANGUAGE;
|
||||||
|
}
|
||||||
|
|
||||||
|
let browserLanguage = window.navigator.browserLanguage || window.navigator.language;
|
||||||
|
|
||||||
|
if (!browserLanguage) {
|
||||||
|
return DEFAULT_LANGUAGE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!allLanguages[browserLanguage]) {
|
||||||
|
const languageKey = getLanguageKeyFromLanguageAlias(browserLanguage);
|
||||||
|
|
||||||
|
if (languageKey) {
|
||||||
|
browserLanguage = languageKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!allLanguages[browserLanguage] && browserLanguage.split('-').length > 1) { // maybe language-script-region
|
||||||
|
const languageTagParts = browserLanguage.split('-');
|
||||||
|
browserLanguage = languageTagParts[0] + '-' + languageTagParts[1];
|
||||||
|
|
||||||
|
if (!allLanguages[browserLanguage]) {
|
||||||
|
const languageKey = getLanguageKeyFromLanguageAlias(browserLanguage);
|
||||||
|
|
||||||
|
if (languageKey) {
|
||||||
|
browserLanguage = languageKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!allLanguages[browserLanguage]) {
|
||||||
|
browserLanguage = languageTagParts[0];
|
||||||
|
const languageKey = getLanguageKeyFromLanguageAlias(browserLanguage);
|
||||||
|
|
||||||
|
if (languageKey) {
|
||||||
|
browserLanguage = languageKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!allLanguages[browserLanguage]) {
|
||||||
|
return DEFAULT_LANGUAGE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return browserLanguage;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLanguageKeyFromLanguageAlias(alias: string): string | null {
|
||||||
|
for (const languageKey in allLanguages) {
|
||||||
|
if (!Object.prototype.hasOwnProperty.call(allLanguages, languageKey)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (languageKey.toLowerCase() === alias.toLowerCase()) {
|
||||||
|
return languageKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
const langInfo = allLanguages[languageKey];
|
||||||
|
const aliases = langInfo.aliases;
|
||||||
|
|
||||||
|
if (!aliases || aliases.length < 1) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < aliases.length; i++) {
|
||||||
|
if (aliases[i].toLowerCase() === alias.toLowerCase()) {
|
||||||
|
return languageKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLocalizedError(error: ErrorResponse): LocalizedError {
|
||||||
|
if (error.errorCode === KnownErrorCode.ApiNotFound && SPECIFIED_API_NOT_FOUND_ERRORS[error.path]) {
|
||||||
|
return {
|
||||||
|
message: `${SPECIFIED_API_NOT_FOUND_ERRORS[error.path].message}`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error.errorCode !== KnownErrorCode.ValidatorError) {
|
||||||
|
return {
|
||||||
|
message: `error.${error.errorMessage}`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < PARAMETERIZED_ERRORS.length; i++) {
|
||||||
|
const errorInfo = PARAMETERIZED_ERRORS[i];
|
||||||
|
const matches = error.errorMessage.match(errorInfo.regex);
|
||||||
|
|
||||||
|
if (matches && matches.length === errorInfo.parameters.length + 1) {
|
||||||
|
return {
|
||||||
|
message: `parameterizedError.${errorInfo.localeKey}`,
|
||||||
|
parameters: errorInfo.parameters.map((param, index) => {
|
||||||
|
return {
|
||||||
|
key: param.field,
|
||||||
|
localized: param.localized,
|
||||||
|
value: matches[index + 1]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
message: `error.${error.errorMessage}`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLocalizedErrorParameters(parameters?: LocalizedErrorParameter[]): Record<string, string> {
|
||||||
|
const localizedParameters: Record<string, string> = {};
|
||||||
|
|
||||||
|
if (parameters) {
|
||||||
|
for (let i = 0; i < parameters.length; i++) {
|
||||||
|
const parameter = parameters[i];
|
||||||
|
|
||||||
|
if (parameter.localized) {
|
||||||
|
localizedParameters[parameter.key] = t(`parameter.${parameter.value}`);
|
||||||
|
} else {
|
||||||
|
localizedParameters[parameter.key] = parameter.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return localizedParameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAllMonthNames(type: string): string[] {
|
||||||
|
const ret = [];
|
||||||
|
const allMonths = Month.values();
|
||||||
|
|
||||||
|
for (let i = 0; i < allMonths.length; i++) {
|
||||||
|
const month = allMonths[i];
|
||||||
|
ret.push(t(`datetime.${month.name}.${type}`));
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAllWeekdayNames(type: string): string[] {
|
||||||
|
const ret = [];
|
||||||
|
const allWeekDays = WeekDay.values();
|
||||||
|
|
||||||
|
for (let i = 0; i < allWeekDays.length; i++) {
|
||||||
|
const weekDay = allWeekDays[i];
|
||||||
|
ret.push(t(`datetime.${weekDay.name}.${type}`));
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
// public functions
|
||||||
|
function translateIf(text: string, isTranslate: boolean): string {
|
||||||
|
if (isTranslate) {
|
||||||
|
return t(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
function translateError(message: string | { error: ErrorResponse }): string {
|
||||||
|
let finalMessage = '';
|
||||||
|
let parameters = {};
|
||||||
|
|
||||||
|
if (isObject(message) && isObject((message as { error: ErrorResponse }).error)) {
|
||||||
|
const localizedError = getLocalizedError((message as { error: ErrorResponse }).error);
|
||||||
|
finalMessage = localizedError.message;
|
||||||
|
parameters = getLocalizedErrorParameters(localizedError.parameters);
|
||||||
|
} else if (isString(message)) {
|
||||||
|
finalMessage = message as string;
|
||||||
|
} else {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return t(finalMessage, parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCurrentLanguageTag(): string {
|
||||||
|
return locale.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCurrentLanguageInfo(): LanguageInfo {
|
||||||
|
const languageInfo = getLanguageInfo(locale.value);
|
||||||
|
|
||||||
|
if (languageInfo) {
|
||||||
|
return languageInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
return getLanguageInfo(getDefaultLanguage()) as LanguageInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCurrentLanguageDisplayName() {
|
||||||
|
const currentLanguageInfo = getCurrentLanguageInfo();
|
||||||
|
return currentLanguageInfo.displayName;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDefaultCurrency(): string {
|
||||||
|
return t('default.currency');
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDefaultFirstDayOfWeek(): string {
|
||||||
|
return t('default.firstDayOfWeek');
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCurrencyName(currencyCode: string): string {
|
||||||
|
return t(`currency.name.${currencyCode}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAllMeridiemIndicators(): LocalizedMeridiemIndicator {
|
||||||
|
const allMeridiemIndicators = MeridiemIndicator.values();
|
||||||
|
const meridiemIndicatorNames = [];
|
||||||
|
const localizedMeridiemIndicatorNames = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < allMeridiemIndicators.length; i++) {
|
||||||
|
const indicator = allMeridiemIndicators[i];
|
||||||
|
|
||||||
|
meridiemIndicatorNames.push(indicator.name);
|
||||||
|
localizedMeridiemIndicatorNames.push(t(`datetime.${indicator.name}.content`));
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
values: meridiemIndicatorNames,
|
||||||
|
displayValues: localizedMeridiemIndicatorNames
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAllLongMonthNames(): string[] {
|
||||||
|
return getAllMonthNames('long');
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAllShortMonthNames(): string[] {
|
||||||
|
return getAllMonthNames('short');
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAllLongWeekdayNames(): string[] {
|
||||||
|
return getAllWeekdayNames('long');
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAllShortWeekdayNames(): string[] {
|
||||||
|
return getAllWeekdayNames('short');
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAllMinWeekdayNames(): string[] {
|
||||||
|
return getAllWeekdayNames('min');
|
||||||
|
}
|
||||||
|
|
||||||
|
function setLanguage(languageKey: string | null, force?: boolean): LocaleDefaultSettings | null {
|
||||||
|
if (!languageKey) {
|
||||||
|
languageKey = getDefaultLanguage();
|
||||||
|
logger.info(`No specified language, use browser default language ${languageKey}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!getLanguageInfo(languageKey)) {
|
||||||
|
languageKey = getDefaultLanguage();
|
||||||
|
logger.warn(`Not found language ${languageKey}, use browser default language ${languageKey}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!force && locale.value === languageKey) {
|
||||||
|
logger.info(`Current locale is already ${languageKey}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info(`Apply current language to ${languageKey}`);
|
||||||
|
|
||||||
|
locale.value = languageKey;
|
||||||
|
moment.updateLocale(languageKey, {
|
||||||
|
months : getAllLongMonthNames(),
|
||||||
|
monthsShort : getAllShortMonthNames(),
|
||||||
|
weekdays : getAllLongWeekdayNames(),
|
||||||
|
weekdaysShort : getAllShortWeekdayNames(),
|
||||||
|
weekdaysMin : getAllMinWeekdayNames(),
|
||||||
|
meridiem: function (hours) {
|
||||||
|
if (isPM(hours)) {
|
||||||
|
return t(`datetime.${MeridiemIndicator.PM.name}.content`);
|
||||||
|
} else {
|
||||||
|
return t(`datetime.${MeridiemIndicator.AM.name}.content`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
services.setLocale(languageKey);
|
||||||
|
document.querySelector('html')?.setAttribute('lang', languageKey);
|
||||||
|
|
||||||
|
const defaultCurrency = getDefaultCurrency();
|
||||||
|
const defaultFirstDayOfWeekName = getDefaultFirstDayOfWeek();
|
||||||
|
let defaultFirstDayOfWeek = WeekDay.DefaultFirstDay.type;
|
||||||
|
|
||||||
|
if (WeekDay.parse(defaultFirstDayOfWeekName)) {
|
||||||
|
defaultFirstDayOfWeek = WeekDay.parse(defaultFirstDayOfWeekName).type;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
currency: defaultCurrency,
|
||||||
|
firstDayOfWeek: defaultFirstDayOfWeek
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function setTimeZone(timezone: string): void {
|
||||||
|
if (timezone) {
|
||||||
|
moment.tz.setDefault(timezone);
|
||||||
|
} else {
|
||||||
|
moment.tz.setDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function initLocale(lastUserLanguage: string, timezone: string): LocaleDefaultSettings | null {
|
||||||
|
let localeDefaultSettings: LocaleDefaultSettings | null = null;
|
||||||
|
|
||||||
|
if (lastUserLanguage && getLanguageInfo(lastUserLanguage)) {
|
||||||
|
logger.info(`Last user language is ${lastUserLanguage}`);
|
||||||
|
localeDefaultSettings = setLanguage(lastUserLanguage, true);
|
||||||
|
} else {
|
||||||
|
localeDefaultSettings = setLanguage(null, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timezone) {
|
||||||
|
logger.info(`Current timezone is ${timezone}`);
|
||||||
|
setTimeZone(timezone);
|
||||||
|
} else {
|
||||||
|
logger.info(`No timezone is set, use browser default ${getTimezoneOffset()} (maybe ${moment.tz.guess(true)})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return localeDefaultSettings;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
tt: t,
|
||||||
|
ti: translateIf,
|
||||||
|
te: translateError,
|
||||||
|
getCurrentLanguageTag,
|
||||||
|
getCurrentLanguageInfo,
|
||||||
|
getCurrentLanguageDisplayName,
|
||||||
|
getDefaultCurrency,
|
||||||
|
getDefaultFirstDayOfWeek,
|
||||||
|
getCurrencyName,
|
||||||
|
getAllMeridiemIndicators,
|
||||||
|
getAllLongMonthNames,
|
||||||
|
getAllShortMonthNames,
|
||||||
|
getAllLongWeekdayNames,
|
||||||
|
getAllShortWeekdayNames,
|
||||||
|
getAllMinWeekdayNames,
|
||||||
|
setLanguage,
|
||||||
|
setTimeZone,
|
||||||
|
initLocale
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@ import en from './en.json';
|
|||||||
import vi from './vi.json';
|
import vi from './vi.json';
|
||||||
import zhHans from './zh_Hans.json';
|
import zhHans from './zh_Hans.json';
|
||||||
|
|
||||||
interface LanguageInfo {
|
export interface LanguageInfo {
|
||||||
name: string;
|
name: string;
|
||||||
displayName: string;
|
displayName: string;
|
||||||
alternativeLanguageTag: string;
|
alternativeLanguageTag: string;
|
||||||
@@ -10,7 +10,7 @@ interface LanguageInfo {
|
|||||||
content: object;
|
content: object;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const defaultLanguage: string = 'en';
|
export const DEFAULT_LANGUAGE: string = 'en';
|
||||||
|
|
||||||
// To add new languages, please refer to https://ezbookkeeping.mayswind.net/translating
|
// To add new languages, please refer to https://ezbookkeeping.mayswind.net/translating
|
||||||
export const allLanguages: Record<string, LanguageInfo> = {
|
export const allLanguages: Record<string, LanguageInfo> = {
|
||||||
|
|||||||
+1
-1
@@ -80,8 +80,8 @@ import VueDatePicker from '@vuepic/vue-datepicker';
|
|||||||
import '@vuepic/vue-datepicker/dist/main.css';
|
import '@vuepic/vue-datepicker/dist/main.css';
|
||||||
|
|
||||||
import { getVersion, getBuildTime } from '@/lib/version.ts';
|
import { getVersion, getBuildTime } from '@/lib/version.ts';
|
||||||
|
import { getI18nOptions } from '@/locales/helpers.ts';
|
||||||
import {
|
import {
|
||||||
getI18nOptions,
|
|
||||||
translateIf,
|
translateIf,
|
||||||
i18nFunctions
|
i18nFunctions
|
||||||
} from '@/locales/helper.js';
|
} from '@/locales/helper.js';
|
||||||
|
|||||||
@@ -144,7 +144,7 @@ export const useSettingsStore = defineStore('settings', () => {
|
|||||||
appSettings.value = getApplicationSettings();
|
appSettings.value = getApplicationSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateLocalizedDefaultSettings(newLocaleDefaultSettings: LocaleDefaultSettings) {
|
function updateLocalizedDefaultSettings(newLocaleDefaultSettings: LocaleDefaultSettings | null) {
|
||||||
if (!newLocaleDefaultSettings) {
|
if (!newLocaleDefaultSettings) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
+144
-143
@@ -5,9 +5,9 @@
|
|||||||
<div class="nav-header">
|
<div class="nav-header">
|
||||||
<router-link to="/" class="app-logo d-flex align-center gap-x-3 app-title-wrapper">
|
<router-link to="/" class="app-logo d-flex align-center gap-x-3 app-title-wrapper">
|
||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
<img alt="logo" class="main-logo" :src="ezBookkeepingLogoPath" />
|
<img alt="logo" class="main-logo" :src="APPLICATION_LOGO_PATH" />
|
||||||
</div>
|
</div>
|
||||||
<h1 class="font-weight-medium text-xl">{{ $t('global.app.title') }}</h1>
|
<h1 class="font-weight-medium text-xl">{{ tt('global.app.title') }}</h1>
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
<perfect-scrollbar
|
<perfect-scrollbar
|
||||||
@@ -18,82 +18,82 @@
|
|||||||
<li class="nav-link home-link">
|
<li class="nav-link home-link">
|
||||||
<router-link to="/">
|
<router-link to="/">
|
||||||
<v-icon class="nav-item-icon" :icon="icons.overview"/>
|
<v-icon class="nav-item-icon" :icon="icons.overview"/>
|
||||||
<span class="nav-item-title">{{ $t('Overview') }}</span>
|
<span class="nav-item-title">{{ tt('Overview') }}</span>
|
||||||
</router-link>
|
</router-link>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-section-title">
|
<li class="nav-section-title">
|
||||||
<div class="title-wrapper">
|
<div class="title-wrapper">
|
||||||
<span class="title-text">{{ $t('Transaction Data') }}</span>
|
<span class="title-text">{{ tt('Transaction Data') }}</span>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-link">
|
<li class="nav-link">
|
||||||
<router-link to="/transaction/list?dateType=7">
|
<router-link to="/transaction/list?dateType=7">
|
||||||
<v-icon class="nav-item-icon" :icon="icons.transactions"/>
|
<v-icon class="nav-item-icon" :icon="icons.transactions"/>
|
||||||
<span class="nav-item-title">{{ $t('Transaction Details') }}</span>
|
<span class="nav-item-title">{{ tt('Transaction Details') }}</span>
|
||||||
</router-link>
|
</router-link>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-link">
|
<li class="nav-link">
|
||||||
<router-link to="/statistics/transaction">
|
<router-link to="/statistics/transaction">
|
||||||
<v-icon class="nav-item-icon" :icon="icons.statistics"/>
|
<v-icon class="nav-item-icon" :icon="icons.statistics"/>
|
||||||
<span class="nav-item-title">{{ $t('Statistics & Analysis') }}</span>
|
<span class="nav-item-title">{{ tt('Statistics & Analysis') }}</span>
|
||||||
</router-link>
|
</router-link>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-section-title">
|
<li class="nav-section-title">
|
||||||
<div class="title-wrapper">
|
<div class="title-wrapper">
|
||||||
<span class="title-text">{{ $t('Basis Data') }}</span>
|
<span class="title-text">{{ tt('Basis Data') }}</span>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-link">
|
<li class="nav-link">
|
||||||
<router-link to="/account/list">
|
<router-link to="/account/list">
|
||||||
<v-icon class="nav-item-icon" :icon="icons.accounts"/>
|
<v-icon class="nav-item-icon" :icon="icons.accounts"/>
|
||||||
<span class="nav-item-title">{{ $t('Accounts') }}</span>
|
<span class="nav-item-title">{{ tt('Accounts') }}</span>
|
||||||
</router-link>
|
</router-link>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-link">
|
<li class="nav-link">
|
||||||
<router-link to="/category/list">
|
<router-link to="/category/list">
|
||||||
<v-icon class="nav-item-icon" :icon="icons.categories"/>
|
<v-icon class="nav-item-icon" :icon="icons.categories"/>
|
||||||
<span class="nav-item-title">{{ $t('Transaction Categories') }}</span>
|
<span class="nav-item-title">{{ tt('Transaction Categories') }}</span>
|
||||||
</router-link>
|
</router-link>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-link">
|
<li class="nav-link">
|
||||||
<router-link to="/tag/list">
|
<router-link to="/tag/list">
|
||||||
<v-icon class="nav-item-icon" :icon="icons.tags"/>
|
<v-icon class="nav-item-icon" :icon="icons.tags"/>
|
||||||
<span class="nav-item-title">{{ $t('Transaction Tags') }}</span>
|
<span class="nav-item-title">{{ tt('Transaction Tags') }}</span>
|
||||||
</router-link>
|
</router-link>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-link">
|
<li class="nav-link">
|
||||||
<router-link to="/template/list">
|
<router-link to="/template/list">
|
||||||
<v-icon class="nav-item-icon" :icon="icons.templates"/>
|
<v-icon class="nav-item-icon" :icon="icons.templates"/>
|
||||||
<span class="nav-item-title">{{ $t('Transaction Templates') }}</span>
|
<span class="nav-item-title">{{ tt('Transaction Templates') }}</span>
|
||||||
</router-link>
|
</router-link>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-link" v-if="isUserScheduledTransactionEnabled">
|
<li class="nav-link" v-if="isUserScheduledTransactionEnabled()">
|
||||||
<router-link to="/schedule/list">
|
<router-link to="/schedule/list">
|
||||||
<v-icon class="nav-item-icon" :icon="icons.scheduledTransactions"/>
|
<v-icon class="nav-item-icon" :icon="icons.scheduledTransactions"/>
|
||||||
<span class="nav-item-title">{{ $t('Scheduled Transactions') }}</span>
|
<span class="nav-item-title">{{ tt('Scheduled Transactions') }}</span>
|
||||||
</router-link>
|
</router-link>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-section-title">
|
<li class="nav-section-title">
|
||||||
<div class="title-wrapper">
|
<div class="title-wrapper">
|
||||||
<span class="title-text">{{ $t('Miscellaneous') }}</span>
|
<span class="title-text">{{ tt('Miscellaneous') }}</span>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-link">
|
<li class="nav-link">
|
||||||
<router-link to="/exchange_rates">
|
<router-link to="/exchange_rates">
|
||||||
<v-icon class="nav-item-icon" :icon="icons.exchangeRates"/>
|
<v-icon class="nav-item-icon" :icon="icons.exchangeRates"/>
|
||||||
<span class="nav-item-title">{{ $t('Exchange Rates Data') }}</span>
|
<span class="nav-item-title">{{ tt('Exchange Rates Data') }}</span>
|
||||||
</router-link>
|
</router-link>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-link">
|
<li class="nav-link">
|
||||||
<a href="javascript:void(0);" @click="showMobileQrCode = true">
|
<a href="javascript:void(0);" @click="showMobileQrCode = true">
|
||||||
<v-icon class="nav-item-icon" :icon="icons.mobile"/>
|
<v-icon class="nav-item-icon" :icon="icons.mobile"/>
|
||||||
<span class="nav-item-title">{{ $t('Use on Mobile Device') }}</span>
|
<span class="nav-item-title">{{ tt('Use on Mobile Device') }}</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-link">
|
<li class="nav-link">
|
||||||
<router-link to="/about">
|
<router-link to="/about">
|
||||||
<v-icon class="nav-item-icon" :icon="icons.about"/>
|
<v-icon class="nav-item-icon" :icon="icons.about"/>
|
||||||
<span class="nav-item-title">{{ $t('About') }}</span>
|
<span class="nav-item-title">{{ tt('About') }}</span>
|
||||||
</router-link>
|
</router-link>
|
||||||
</li>
|
</li>
|
||||||
</perfect-scrollbar>
|
</perfect-scrollbar>
|
||||||
@@ -109,14 +109,14 @@
|
|||||||
</v-btn>
|
</v-btn>
|
||||||
<div class="app-logo d-flex align-center gap-x-3 app-title-wrapper" v-if="mdAndDown">
|
<div class="app-logo d-flex align-center gap-x-3 app-title-wrapper" v-if="mdAndDown">
|
||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
<img alt="logo" class="main-logo" :src="ezBookkeepingLogoPath" />
|
<img alt="logo" class="main-logo" :src="APPLICATION_LOGO_PATH" />
|
||||||
</div>
|
</div>
|
||||||
<h1 class="font-weight-medium text-xl">{{ $t('global.app.title') }}</h1>
|
<h1 class="font-weight-medium text-xl">{{ tt('global.app.title') }}</h1>
|
||||||
</div>
|
</div>
|
||||||
<v-spacer />
|
<v-spacer />
|
||||||
<v-btn color="primary" variant="text" class="me-2"
|
<v-btn color="primary" variant="text" class="me-2"
|
||||||
:icon="true" @click="(theme === 'light' ? theme = 'dark' : (theme === 'dark' ? theme = 'auto' : theme = 'light'))">
|
:icon="true" @click="(currentTheme === 'light' ? currentTheme = 'dark' : (currentTheme === 'dark' ? currentTheme = 'auto' : currentTheme = 'light'))">
|
||||||
<v-icon :icon="(theme === 'light' ? icons.themeLight : (theme === 'dark' ? icons.themeDark : icons.themeAuto))" size="24" />
|
<v-icon :icon="(currentTheme === 'light' ? icons.themeLight : (currentTheme === 'dark' ? icons.themeDark : icons.themeAuto))" size="24" />
|
||||||
</v-btn>
|
</v-btn>
|
||||||
<v-avatar class="cursor-pointer" variant="tonal"
|
<v-avatar class="cursor-pointer" variant="tonal"
|
||||||
:color="currentUserAvatar ? 'rgba(0,0,0,0)' : 'primary'">
|
:color="currentUserAvatar ? 'rgba(0,0,0,0)' : 'primary'">
|
||||||
@@ -152,19 +152,19 @@
|
|||||||
</v-list-item>
|
</v-list-item>
|
||||||
<v-divider class="my-2"/>
|
<v-divider class="my-2"/>
|
||||||
<v-list-item :prepend-icon="icons.profile"
|
<v-list-item :prepend-icon="icons.profile"
|
||||||
:title="$t('User Settings')"
|
:title="tt('User Settings')"
|
||||||
to="/user/settings"></v-list-item>
|
to="/user/settings"></v-list-item>
|
||||||
<v-list-item :prepend-icon="icons.settings"
|
<v-list-item :prepend-icon="icons.settings"
|
||||||
:title="$t('Application Settings')"
|
:title="tt('Application Settings')"
|
||||||
to="/app/settings"></v-list-item>
|
to="/app/settings"></v-list-item>
|
||||||
<v-divider class="my-2"/>
|
<v-divider class="my-2"/>
|
||||||
<v-list-item :prepend-icon="icons.lock"
|
<v-list-item :prepend-icon="icons.lock"
|
||||||
:title="$t('Lock Application')"
|
:title="tt('Lock Application')"
|
||||||
v-if="isEnableApplicationLock"
|
v-if="isEnableApplicationLock"
|
||||||
@click="lock"></v-list-item>
|
@click="lock"></v-list-item>
|
||||||
<v-list-item :disabled="logouting"
|
<v-list-item :disabled="logouting"
|
||||||
:prepend-icon="icons.logout"
|
:prepend-icon="icons.logout"
|
||||||
:title="$t('Log Out')"
|
:title="tt('Log Out')"
|
||||||
@click="logout"></v-list-item>
|
@click="logout"></v-list-item>
|
||||||
</v-list>
|
</v-list>
|
||||||
</v-menu>
|
</v-menu>
|
||||||
@@ -191,12 +191,17 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup lang="ts">
|
||||||
|
import SnackBar from '@/components/desktop/SnackBar.vue';
|
||||||
|
|
||||||
|
import { type Ref, ref, computed, useTemplateRef } from 'vue';
|
||||||
|
|
||||||
import { useDisplay } from 'vuetify';
|
import { useDisplay } from 'vuetify';
|
||||||
import { useTheme } from 'vuetify';
|
import { useTheme } from 'vuetify';
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
|
|
||||||
|
import { useI18n } from '@/locales/helpers.ts';
|
||||||
|
|
||||||
import { mapStores } from 'pinia';
|
|
||||||
import { useRootStore } from '@/stores/index.js';
|
import { useRootStore } from '@/stores/index.js';
|
||||||
import { useSettingsStore } from '@/stores/setting.ts';
|
import { useSettingsStore } from '@/stores/setting.ts';
|
||||||
import { useUserStore } from '@/stores/user.ts';
|
import { useUserStore } from '@/stores/user.ts';
|
||||||
@@ -206,6 +211,8 @@ import { ThemeType } from '@/core/theme.ts';
|
|||||||
import { isUserScheduledTransactionEnabled } from '@/lib/server_settings.ts';
|
import { isUserScheduledTransactionEnabled } from '@/lib/server_settings.ts';
|
||||||
import { getSystemTheme, setExpenseAndIncomeAmountColor } from '@/lib/ui/common.ts';
|
import { getSystemTheme, setExpenseAndIncomeAmountColor } from '@/lib/ui/common.ts';
|
||||||
|
|
||||||
|
type SnackBarType = InstanceType<typeof SnackBar>;
|
||||||
|
|
||||||
import {
|
import {
|
||||||
mdiMenu,
|
mdiMenu,
|
||||||
mdiHomeOutline,
|
mdiHomeOutline,
|
||||||
@@ -229,124 +236,118 @@ import {
|
|||||||
mdiLogout
|
mdiLogout
|
||||||
} from '@mdi/js';
|
} from '@mdi/js';
|
||||||
|
|
||||||
export default {
|
const display = useDisplay();
|
||||||
data() {
|
const theme = useTheme();
|
||||||
return {
|
const route = useRoute();
|
||||||
logouting: false,
|
const router = useRouter();
|
||||||
isVerticalNavScrolled: false,
|
|
||||||
showVerticalOverlayMenu: false,
|
const { tt, initLocale } = useI18n();
|
||||||
showLoading: false,
|
|
||||||
showMobileQrCode: false,
|
const rootStore = useRootStore();
|
||||||
icons: {
|
const settingsStore = useSettingsStore();
|
||||||
menu: mdiMenu,
|
const userStore = useUserStore();
|
||||||
overview: mdiHomeOutline,
|
|
||||||
transactions: mdiListBoxOutline,
|
const icons = {
|
||||||
accounts: mdiCreditCardOutline,
|
menu: mdiMenu,
|
||||||
categories: mdiViewDashboardOutline,
|
overview: mdiHomeOutline,
|
||||||
tags: mdiTagOutline,
|
transactions: mdiListBoxOutline,
|
||||||
templates: mdiClipboardTextOutline,
|
accounts: mdiCreditCardOutline,
|
||||||
scheduledTransactions: mdiClipboardTextClockOutline,
|
categories: mdiViewDashboardOutline,
|
||||||
statistics: mdiChartPieOutline,
|
tags: mdiTagOutline,
|
||||||
exchangeRates: mdiSwapHorizontal,
|
templates: mdiClipboardTextOutline,
|
||||||
settings: mdiCogOutline,
|
scheduledTransactions: mdiClipboardTextClockOutline,
|
||||||
mobile: mdiCellphone,
|
statistics: mdiChartPieOutline,
|
||||||
about: mdiInformationOutline,
|
exchangeRates: mdiSwapHorizontal,
|
||||||
themeAuto: mdiThemeLightDark,
|
settings: mdiCogOutline,
|
||||||
themeLight: mdiWeatherSunny,
|
mobile: mdiCellphone,
|
||||||
themeDark: mdiWeatherNight,
|
about: mdiInformationOutline,
|
||||||
user: mdiAccount,
|
themeAuto: mdiThemeLightDark,
|
||||||
profile: mdiAccountCogOutline,
|
themeLight: mdiWeatherSunny,
|
||||||
lock: mdiLockOutline,
|
themeDark: mdiWeatherNight,
|
||||||
logout: mdiLogout
|
user: mdiAccount,
|
||||||
|
profile: mdiAccountCogOutline,
|
||||||
|
lock: mdiLockOutline,
|
||||||
|
logout: mdiLogout
|
||||||
|
};
|
||||||
|
|
||||||
|
const snackbar = useTemplateRef<SnackBarType>('snackbar');
|
||||||
|
|
||||||
|
const logouting: Ref<boolean> = ref(false);
|
||||||
|
const isVerticalNavScrolled: Ref<boolean> = ref(false);
|
||||||
|
const showVerticalOverlayMenu: Ref<boolean> = ref(false);
|
||||||
|
const showLoading: Ref<boolean> = ref(false);
|
||||||
|
const showMobileQrCode: Ref<boolean> = ref(false);
|
||||||
|
|
||||||
|
const mdAndDown = computed<boolean>(() => {
|
||||||
|
return display.mdAndDown.value;
|
||||||
|
});
|
||||||
|
|
||||||
|
const currentRoutePath = computed<string>(() => {
|
||||||
|
return route.path;
|
||||||
|
});
|
||||||
|
|
||||||
|
const currentNickName = computed<string>(() => {
|
||||||
|
return userStore.currentUserNickname || tt('User');
|
||||||
|
});
|
||||||
|
|
||||||
|
const currentUserAvatar = computed<string | null>(() => {
|
||||||
|
return userStore.getUserAvatarUrl(userStore.currentUserBasicInfo, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
const currentTheme = computed<string>({
|
||||||
|
get: () => {
|
||||||
|
return settingsStore.appSettings.theme;
|
||||||
|
},
|
||||||
|
set: (value: string) => {
|
||||||
|
if (value !== settingsStore.appSettings.theme) {
|
||||||
|
settingsStore.setTheme(value);
|
||||||
|
|
||||||
|
if (value === ThemeType.Light || value === ThemeType.Dark) {
|
||||||
|
theme.global.name.value = value;
|
||||||
|
} else {
|
||||||
|
theme.global.name.value = getSystemTheme();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
...mapStores(useRootStore, useSettingsStore, useUserStore),
|
|
||||||
ezBookkeepingLogoPath() {
|
|
||||||
return APPLICATION_LOGO_PATH;
|
|
||||||
},
|
|
||||||
mdAndDown() {
|
|
||||||
return this.display.mdAndDown.value;
|
|
||||||
},
|
|
||||||
currentRoutePath() {
|
|
||||||
const route = useRoute();
|
|
||||||
return route.path;
|
|
||||||
},
|
|
||||||
currentNickName() {
|
|
||||||
return this.userStore.currentUserNickname || this.$t('User');
|
|
||||||
},
|
|
||||||
currentUserAvatar() {
|
|
||||||
return this.userStore.getUserAvatarUrl(this.userStore.currentUserBasicInfo, true);
|
|
||||||
},
|
|
||||||
theme: {
|
|
||||||
get: function () {
|
|
||||||
return this.settingsStore.appSettings.theme;
|
|
||||||
},
|
|
||||||
set: function (value) {
|
|
||||||
if (value !== this.settingsStore.appSettings.theme) {
|
|
||||||
this.settingsStore.setTheme(value);
|
|
||||||
|
|
||||||
if (value === ThemeType.Light || value === ThemeType.Dark) {
|
|
||||||
this.globalTheme.global.name.value = value;
|
|
||||||
} else {
|
|
||||||
this.globalTheme.global.name.value = getSystemTheme();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
isUserScheduledTransactionEnabled() {
|
|
||||||
return isUserScheduledTransactionEnabled();
|
|
||||||
},
|
|
||||||
isEnableApplicationLock() {
|
|
||||||
return this.settingsStore.appSettings.applicationLock;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setup() {
|
|
||||||
const display = useDisplay();
|
|
||||||
const theme = useTheme();
|
|
||||||
|
|
||||||
return {
|
|
||||||
display: display,
|
|
||||||
globalTheme: theme
|
|
||||||
};
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
handleNavScroll(e) {
|
|
||||||
this.isVerticalNavScrolled = e.target.scrollTop > 0;
|
|
||||||
},
|
|
||||||
lock() {
|
|
||||||
this.rootStore.lock();
|
|
||||||
this.$router.replace('/unlock');
|
|
||||||
},
|
|
||||||
logout() {
|
|
||||||
const self = this;
|
|
||||||
|
|
||||||
self.logouting = true;
|
|
||||||
self.showLoading = true;
|
|
||||||
|
|
||||||
self.rootStore.logout().then(() => {
|
|
||||||
self.logouting = false;
|
|
||||||
self.showLoading = false;
|
|
||||||
|
|
||||||
self.settingsStore.clearAppSettings();
|
|
||||||
|
|
||||||
const localeDefaultSettings = self.$locale.initLocale(self.userStore.currentUserLanguage, self.settingsStore.appSettings.timeZone);
|
|
||||||
self.settingsStore.updateLocalizedDefaultSettings(localeDefaultSettings);
|
|
||||||
|
|
||||||
setExpenseAndIncomeAmountColor(self.userStore.currentUserExpenseAmountColor, self.userStore.currentUserIncomeAmountColor);
|
|
||||||
|
|
||||||
this.$router.replace('/login');
|
|
||||||
}).catch(error => {
|
|
||||||
self.logouting = false;
|
|
||||||
self.showLoading = false;
|
|
||||||
|
|
||||||
if (!error.processed) {
|
|
||||||
self.$refs.snackbar.showError(error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const isEnableApplicationLock = computed<boolean>(() => {
|
||||||
|
return settingsStore.appSettings.applicationLock;
|
||||||
|
});
|
||||||
|
|
||||||
|
function handleNavScroll(e: Event) {
|
||||||
|
isVerticalNavScrolled.value = (e.target as HTMLElement).scrollTop > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function lock() {
|
||||||
|
rootStore.lock();
|
||||||
|
router.replace('/unlock');
|
||||||
|
}
|
||||||
|
|
||||||
|
function logout() {
|
||||||
|
logouting.value = true;
|
||||||
|
showLoading.value = true;
|
||||||
|
|
||||||
|
rootStore.logout().then(() => {
|
||||||
|
logouting.value = false;
|
||||||
|
showLoading.value = false;
|
||||||
|
|
||||||
|
settingsStore.clearAppSettings();
|
||||||
|
|
||||||
|
const localeDefaultSettings = initLocale(userStore.currentUserLanguage, settingsStore.appSettings.timeZone);
|
||||||
|
settingsStore.updateLocalizedDefaultSettings(localeDefaultSettings);
|
||||||
|
|
||||||
|
setExpenseAndIncomeAmountColor(userStore.currentUserExpenseAmountColor, userStore.currentUserIncomeAmountColor);
|
||||||
|
|
||||||
|
router.replace('/login');
|
||||||
|
}).catch(error => {
|
||||||
|
logouting.value = false;
|
||||||
|
showLoading.value = false;
|
||||||
|
|
||||||
|
if (!error.processed && snackbar.value) {
|
||||||
|
snackbar.value.showError(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -180,7 +180,7 @@ export default defineConfig(() => {
|
|||||||
return 'common';
|
return 'common';
|
||||||
} else if (/[\\/]src[\\/]components[\\/](base|common)[\\/]/i.test(id)) {
|
} else if (/[\\/]src[\\/]components[\\/](base|common)[\\/]/i.test(id)) {
|
||||||
return 'common';
|
return 'common';
|
||||||
} else if (/[\\/]src[\\/]locales[\\/]helper\.(js|ts)/i.test(id)) {
|
} else if (/[\\/]src[\\/]locales[\\/]helpers\.(js|ts)/i.test(id)) {
|
||||||
return 'common';
|
return 'common';
|
||||||
} else if (/[\\/]src[\\/]locales[\\/]/i.test(id)) {
|
} else if (/[\\/]src[\\/]locales[\\/]/i.test(id)) {
|
||||||
return 'locales';
|
return 'locales';
|
||||||
|
|||||||
Reference in New Issue
Block a user