From 13d5759e84af56f77e2f919fa96d72b389b2622f Mon Sep 17 00:00:00 2001 From: MaysWind Date: Tue, 19 Aug 2025 23:56:28 +0800 Subject: [PATCH] mobile version supports rtl --- .../mobile/MonthlyTrendsBarChart.vue | 4 +- src/components/mobile/NumberPadSheet.vue | 4 +- src/components/mobile/PieChart.vue | 10 +- src/components/mobile/PinCodeInputSheet.vue | 8 +- .../mobile/ScheduleFrequencySheet.vue | 6 +- .../mobile/TransactionTagSelectionSheet.vue | 8 +- .../TwoColumnListItemSelectionSheet.vue | 6 +- src/lib/settings.ts | 14 +++ src/lib/ui/mobile.ts | 69 ++++++----- src/locales/helpers.ts | 34 +++++- src/mobile-ltr.scss | 33 ++++++ src/mobile-main.ts | 52 ++------- src/mobile-rtl.scss | 33 ++++++ src/mobile.html | 12 ++ .../{amount-color.css => amount-color.scss} | 0 ...ize-default.css => font-size-default.scss} | 0 ...nt-size-large.css => font-size-large.scss} | 0 ...nt-size-small.css => font-size-small.scss} | 0 ...ize-x-large.css => font-size-x-large.scss} | 0 ...e-xx-large.css => font-size-xx-large.scss} | 0 ...xxx-large.css => font-size-xxx-large.scss} | 0 ...xx-large.css => font-size-xxxx-large.scss} | 0 src/styles/mobile/{global.css => global.scss} | 109 ++++++++++++++---- src/views/mobile/HomePage.vue | 8 +- src/views/mobile/SignupPage.vue | 2 +- src/views/mobile/accounts/EditPage.vue | 2 +- src/views/mobile/accounts/ListPage.vue | 26 +++-- .../accounts/ReconciliationStatementPage.vue | 29 +++-- src/views/mobile/categories/ListPage.vue | 17 ++- src/views/mobile/categories/PresetPage.vue | 2 +- src/views/mobile/exchangerates/ListPage.vue | 11 +- .../settings/AccountFilterSettingsPage.vue | 2 +- .../ApplicationCloudSyncSettingsPage.vue | 2 +- .../settings/CategoryFilterSettingsPage.vue | 4 +- .../mobile/settings/TextSizeSettingsPage.vue | 16 ++- .../mobile/statistics/TransactionPage.vue | 23 ++-- src/views/mobile/tags/ListPage.vue | 30 +++-- src/views/mobile/templates/ListPage.vue | 16 ++- src/views/mobile/transactions/EditPage.vue | 16 +-- src/views/mobile/transactions/ListPage.vue | 48 +++++--- src/views/mobile/users/DataManagementPage.vue | 2 +- src/views/mobile/users/SessionListPage.vue | 9 +- src/views/mobile/users/UserProfilePage.vue | 18 ++- vite.config.ts | 68 ++++++++++- 44 files changed, 534 insertions(+), 219 deletions(-) create mode 100644 src/mobile-ltr.scss create mode 100644 src/mobile-rtl.scss rename src/styles/mobile/{amount-color.css => amount-color.scss} (100%) rename src/styles/mobile/{font-size-default.css => font-size-default.scss} (100%) rename src/styles/mobile/{font-size-large.css => font-size-large.scss} (100%) rename src/styles/mobile/{font-size-small.css => font-size-small.scss} (100%) rename src/styles/mobile/{font-size-x-large.css => font-size-x-large.scss} (100%) rename src/styles/mobile/{font-size-xx-large.css => font-size-xx-large.scss} (100%) rename src/styles/mobile/{font-size-xxx-large.css => font-size-xxx-large.scss} (100%) rename src/styles/mobile/{font-size-xxxx-large.css => font-size-xxxx-large.scss} (100%) rename src/styles/mobile/{global.css => global.scss} (86%) diff --git a/src/components/mobile/MonthlyTrendsBarChart.vue b/src/components/mobile/MonthlyTrendsBarChart.vue index a3fe90d1..6a7aaf97 100644 --- a/src/components/mobile/MonthlyTrendsBarChart.vue +++ b/src/components/mobile/MonthlyTrendsBarChart.vue @@ -363,13 +363,13 @@ function toggleLegend(legend: TrendsBarChartLegend): void { diff --git a/src/components/mobile/ScheduleFrequencySheet.vue b/src/components/mobile/ScheduleFrequencySheet.vue index 2e8318ce..08b62060 100644 --- a/src/components/mobile/ScheduleFrequencySheet.vue +++ b/src/components/mobile/ScheduleFrequencySheet.vue @@ -21,7 +21,7 @@ v-for="type in allTransactionScheduledFrequencyTypes" @click="changeFrequencyType(type.type)"> @@ -165,10 +165,10 @@ function onSheetClosed(): void { } .schedule-frequency-type-list.list .item-inner { - padding-right: 6px; + padding-inline-end: 6px; } .schedule-frequency-value-list-list.list .item-content { - padding-left: 0; + padding-inline-start: 0; } diff --git a/src/components/mobile/TransactionTagSelectionSheet.vue b/src/components/mobile/TransactionTagSelectionSheet.vue index 6f8df092..8ec7b8c2 100644 --- a/src/components/mobile/TransactionTagSelectionSheet.vue +++ b/src/components/mobile/TransactionTagSelectionSheet.vue @@ -37,7 +37,7 @@
-
+
{{ tag.name }}
@@ -55,7 +55,7 @@ diff --git a/src/lib/settings.ts b/src/lib/settings.ts index 16564192..6ba4376d 100644 --- a/src/lib/settings.ts +++ b/src/lib/settings.ts @@ -13,6 +13,7 @@ import { } from '@/core/setting.ts'; const settingsLocalStorageKey: string = 'ebk_app_settings'; +const currentLanguageSessionStorageKey: string = 'ebk_current_language'; function getStoredApplicationSettings(): BaseApplicationSetting { try { @@ -101,3 +102,16 @@ export function isEnableAnimate(): boolean { export function clearSettings(): void { localStorage.removeItem(settingsLocalStorageKey); } + +export function getSessionCurrentLanguageKey(): string { + return sessionStorage.getItem(currentLanguageSessionStorageKey) || ''; +} + +export function setSessionCurrentLanguageKey(languageKey: string): void { + if (!languageKey) { + sessionStorage.removeItem(currentLanguageSessionStorageKey); + return; + } + + sessionStorage.setItem(currentLanguageSessionStorageKey, languageKey); +} diff --git a/src/lib/ui/mobile.ts b/src/lib/ui/mobile.ts index bcb7b707..274a2654 100644 --- a/src/lib/ui/mobile.ts +++ b/src/lib/ui/mobile.ts @@ -4,6 +4,7 @@ import type { Dialog, Picker, Router } from 'framework7/types'; import { useI18n } from '@/locales/helpers.ts'; +import { TextDirection } from '@/core/text.ts'; import { FontSize, FONT_SIZE_PREVIEW_CLASSNAME_PREFIX } from '@/core/font.ts'; import { getNumberValue } from '../common.ts'; import { isEnableAnimate } from '../settings.ts'; @@ -195,7 +196,7 @@ export function onInfiniteScrolling(callback: (e: Event) => void): void { } export function useI18nUIComponents() { - const { tt, te } = useI18n(); + const { tt, te, getCurrentLanguageTextDirection } = useI18n(); function routeBackOnError(f7router: Router.Router, errorRef: Ref): void { const unwatch = watch(errorRef, (newValue) => { @@ -230,52 +231,58 @@ export function useI18nUIComponents() { } function showConfirm(message: string, confirmCallback?: (dialog: Dialog.Dialog, e: Event) => void, cancelCallback?: (dialog: Dialog.Dialog, e: Event) => void): void { + const textDirection = getCurrentLanguageTextDirection(); + + const cancelButton: Dialog.DialogButton = { + text: tt('Cancel'), + onClick: cancelCallback + }; + + const confirmButton: Dialog.DialogButton = { + text: tt('OK'), + onClick: confirmCallback + }; + f7ready((f7) => { f7.dialog.create({ title: tt('global.app.title'), text: tt(message), animate: isEnableAnimate(), - buttons: [ - { - text: tt('Cancel'), - onClick: cancelCallback - }, - { - text: tt('OK'), - onClick: confirmCallback - } - ] + buttons: textDirection == TextDirection.RTL ? [confirmButton, cancelButton] : [cancelButton, confirmButton] }).open(); }); } function showPrompt(message: string, currentValue?: string, confirmCallback?: (value: string, dialog: Dialog.Dialog, e: Event) => void, cancelCallback?: (value: string, dialog: Dialog.Dialog, e: Event) => void): void { + const textDirection = getCurrentLanguageTextDirection(); + + const cancelButton: Dialog.DialogButton = { + text: tt('Cancel'), + onClick: (dialog, event) => { + if (cancelCallback) { + const inputValue = dialog.$el.find('.dialog-input').val(); + cancelCallback(inputValue, dialog, event); + } + } + }; + + const confirmButton: Dialog.DialogButton = { + text: tt('OK'), + onClick: (dialog, event) => { + if (confirmCallback) { + const inputValue = dialog.$el.find('.dialog-input').val(); + confirmCallback(inputValue, dialog, event); + } + } + }; + f7ready((f7) => { f7.dialog.create({ title: tt('global.app.title'), text: tt(message), content: `
`, animate: isEnableAnimate(), - buttons: [ - { - text: tt('Cancel'), - onClick: (dialog, event) => { - if (cancelCallback) { - const inputValue = dialog.$el.find('.dialog-input').val(); - cancelCallback(inputValue, dialog, event); - } - } - }, - { - text: tt('OK'), - onClick: (dialog, event) => { - if (confirmCallback) { - const inputValue = dialog.$el.find('.dialog-input').val(); - confirmCallback(inputValue, dialog, event); - } - } - } - ] + buttons: textDirection == TextDirection.RTL ? [confirmButton, cancelButton] : [cancelButton, confirmButton] }).open(); }); } diff --git a/src/locales/helpers.ts b/src/locales/helpers.ts index 8d8ea4cf..d5700918 100644 --- a/src/locales/helpers.ts +++ b/src/locales/helpers.ts @@ -190,6 +190,11 @@ import { getAllFilteredAccountsBalance } from '@/lib/account.ts'; +import { + getSessionCurrentLanguageKey, + setSessionCurrentLanguageKey +} from '@/lib/settings.ts'; + import services from '@/lib/services.ts'; import logger from '@/lib/logger.ts'; @@ -1961,13 +1966,30 @@ export function useI18n() { } }); + setSessionCurrentLanguageKey(languageKey); services.setLocale(languageKey); document.querySelector('html')?.setAttribute('lang', languageKey); - if (languageInfo && languageInfo.textDirection === TextDirection.LTR) { - document.querySelector('html')?.removeAttribute('dir'); - } else if (languageInfo && languageInfo.textDirection === TextDirection.RTL) { - document.querySelector('html')?.setAttribute('dir', 'rtl'); + if (document.querySelector('html')?.getAttribute('data-dir-mode') === 'isolate') { + if (languageInfo && languageInfo.textDirection === TextDirection.LTR) { + if (location.search.includes('rtl')) { + const url = new URL(window.location.href); + url.search = ''; + window.location.replace(url.toString()); + } + } else if (languageInfo && languageInfo.textDirection === TextDirection.RTL) { + if (!location.search.includes('rtl')) { + const url = new URL(window.location.href); + url.searchParams.set('rtl', ''); + window.location.replace(url.toString()); + } + } + } else { + if (languageInfo && languageInfo.textDirection === TextDirection.LTR) { + document.querySelector('html')?.removeAttribute('dir'); + } else if (languageInfo && languageInfo.textDirection === TextDirection.RTL) { + document.querySelector('html')?.setAttribute('dir', 'rtl'); + } } const defaultCurrency = getDefaultCurrency(); @@ -2003,11 +2025,15 @@ export function useI18n() { } function initLocale(lastUserLanguage?: string, timezone?: string): LocaleDefaultSettings | null { + const sessionLanguageKey: string = getSessionCurrentLanguageKey(); let localeDefaultSettings: LocaleDefaultSettings | null = null; if (lastUserLanguage && getLanguageInfo(lastUserLanguage)) { logger.info(`Last user language is ${lastUserLanguage}`); localeDefaultSettings = setLanguage(lastUserLanguage, true); + } else if (sessionLanguageKey && getLanguageInfo(sessionLanguageKey)) { + logger.info(`Session language is ${sessionLanguageKey}`); + localeDefaultSettings = setLanguage(sessionLanguageKey, true); } else { localeDefaultSettings = setLanguage(null, true); } diff --git a/src/mobile-ltr.scss b/src/mobile-ltr.scss new file mode 100644 index 00000000..cc3b2d01 --- /dev/null +++ b/src/mobile-ltr.scss @@ -0,0 +1,33 @@ +@import 'framework7/css'; +@import 'framework7/components/dialog/css'; +@import 'framework7/components/popup/css'; +@import 'framework7/components/login-screen/css'; +@import 'framework7/components/popover/css'; +@import 'framework7/components/actions/css'; +@import 'framework7/components/sheet/css'; +@import 'framework7/components/notification/css'; +@import 'framework7/components/toast/css'; +@import 'framework7/components/preloader/css'; +@import 'framework7/components/progressbar/css'; +@import 'framework7/components/sortable/css'; +@import 'framework7/components/swipeout/css'; +@import 'framework7/components/accordion/css'; +@import 'framework7/components/card/css'; +@import 'framework7/components/chip/css'; +@import 'framework7/components/form/css'; +@import 'framework7/components/input/css'; +@import 'framework7/components/checkbox/css'; +@import 'framework7/components/radio/css'; +@import 'framework7/components/toggle/css'; +@import 'framework7/components/range/css'; +@import 'framework7/components/grid/css'; +@import 'framework7/components/picker/css'; +@import 'framework7/components/infinite-scroll/css'; +@import 'framework7/components/pull-to-refresh/css'; +@import 'framework7/components/searchbar/css'; +@import 'framework7/components/tooltip/css'; +@import 'framework7/components/skeleton/css'; +@import 'framework7/components/treeview/css'; +@import 'framework7/components/typography/css'; +@import 'framework7/components/swiper/css'; +@import 'framework7/components/photo-browser/css'; diff --git a/src/mobile-main.ts b/src/mobile-main.ts index e1cde0cc..119506d0 100644 --- a/src/mobile-main.ts +++ b/src/mobile-main.ts @@ -39,40 +39,6 @@ import Framework7PhotoBrowser from 'framework7/components/photo-browser'; // @ts-expect-error there is a function called "registerComponents" in the framework7-vue package, but it is not declared in the type definition file import Framework7Vue, { registerComponents } from 'framework7-vue/bundle'; -import 'framework7/css'; -import 'framework7/components/dialog/css'; -import 'framework7/components/popup/css'; -import 'framework7/components/login-screen/css'; -import 'framework7/components/popover/css'; -import 'framework7/components/actions/css'; -import 'framework7/components/sheet/css'; -import 'framework7/components/notification/css'; -import 'framework7/components/toast/css'; -import 'framework7/components/preloader/css'; -import 'framework7/components/progressbar/css'; -import 'framework7/components/sortable/css'; -import 'framework7/components/swipeout/css'; -import 'framework7/components/accordion/css'; -import 'framework7/components/card/css'; -import 'framework7/components/chip/css'; -import 'framework7/components/form/css'; -import 'framework7/components/input/css'; -import 'framework7/components/checkbox/css'; -import 'framework7/components/radio/css'; -import 'framework7/components/toggle/css'; -import 'framework7/components/range/css'; -import 'framework7/components/grid/css'; -import 'framework7/components/picker/css'; -import 'framework7/components/infinite-scroll/css'; -import 'framework7/components/pull-to-refresh/css'; -import 'framework7/components/searchbar/css'; -import 'framework7/components/tooltip/css'; -import 'framework7/components/skeleton/css'; -import 'framework7/components/treeview/css'; -import 'framework7/components/typography/css'; -import 'framework7/components/swiper/css'; -import 'framework7/components/photo-browser/css'; - import 'framework7-icons'; import 'line-awesome/dist/line-awesome/css/line-awesome.css'; @@ -113,15 +79,15 @@ import AccountBalanceTrendsBarChart from '@/components/mobile/AccountBalanceTren import TextareaAutoSize from '@/directives/mobile/textareaAutoSize.ts'; -import '@/styles/mobile/global.css'; -import '@/styles/mobile/font-size-default.css'; -import '@/styles/mobile/font-size-small.css'; -import '@/styles/mobile/font-size-large.css'; -import '@/styles/mobile/font-size-x-large.css'; -import '@/styles/mobile/font-size-xx-large.css'; -import '@/styles/mobile/font-size-xxx-large.css'; -import '@/styles/mobile/font-size-xxxx-large.css'; -import '@/styles/mobile/amount-color.css'; +import '@/styles/mobile/global.scss'; +import '@/styles/mobile/font-size-default.scss'; +import '@/styles/mobile/font-size-small.scss'; +import '@/styles/mobile/font-size-large.scss'; +import '@/styles/mobile/font-size-x-large.scss'; +import '@/styles/mobile/font-size-xx-large.scss'; +import '@/styles/mobile/font-size-xxx-large.scss'; +import '@/styles/mobile/font-size-xxxx-large.scss'; +import '@/styles/mobile/amount-color.scss'; import App from '@/MobileApp.vue'; diff --git a/src/mobile-rtl.scss b/src/mobile-rtl.scss new file mode 100644 index 00000000..f8d0e63b --- /dev/null +++ b/src/mobile-rtl.scss @@ -0,0 +1,33 @@ +@import '../node_modules/framework7/framework7-rtl.css'; +@import '../node_modules/framework7/components/dialog/dialog-rtl.css'; +@import '../node_modules/framework7/components/popup/popup-rtl.css'; +@import '../node_modules/framework7/components/login-screen/login-screen-rtl.css'; +@import '../node_modules/framework7/components/popover/popover-rtl.css'; +@import '../node_modules/framework7/components/actions/actions-rtl.css'; +@import '../node_modules/framework7/components/sheet/sheet-rtl.css'; +@import '../node_modules/framework7/components/notification/notification-rtl.css'; +@import '../node_modules/framework7/components/toast/toast-rtl.css'; +@import '../node_modules/framework7/components/preloader/preloader-rtl.css'; +@import '../node_modules/framework7/components/progressbar/progressbar-rtl.css'; +@import '../node_modules/framework7/components/sortable/sortable-rtl.css'; +@import '../node_modules/framework7/components/swipeout/swipeout-rtl.css'; +@import '../node_modules/framework7/components/accordion/accordion-rtl.css'; +@import '../node_modules/framework7/components/card/card-rtl.css'; +@import '../node_modules/framework7/components/chip/chip-rtl.css'; +@import '../node_modules/framework7/components/form/form-rtl.css'; +@import '../node_modules/framework7/components/input/input-rtl.css'; +@import '../node_modules/framework7/components/checkbox/checkbox-rtl.css'; +@import '../node_modules/framework7/components/radio/radio-rtl.css'; +@import '../node_modules/framework7/components/toggle/toggle-rtl.css'; +@import '../node_modules/framework7/components/range/range-rtl.css'; +@import '../node_modules/framework7/components/grid/grid-rtl.css'; +@import '../node_modules/framework7/components/picker/picker-rtl.css'; +@import '../node_modules/framework7/components/infinite-scroll/infinite-scroll-rtl.css'; +@import '../node_modules/framework7/components/pull-to-refresh/pull-to-refresh-rtl.css'; +@import '../node_modules/framework7/components/searchbar/searchbar-rtl.css'; +@import '../node_modules/framework7/components/tooltip/tooltip-rtl.css'; +@import '../node_modules/framework7/components/skeleton/skeleton-rtl.css'; +@import '../node_modules/framework7/components/treeview/treeview-rtl.css'; +@import '../node_modules/framework7/components/typography/typography-rtl.css'; +@import '../node_modules/framework7/components/swiper/swiper-rtl.css'; +@import '../node_modules/framework7/components/photo-browser/photo-browser-rtl.css'; diff --git a/src/mobile.html b/src/mobile.html index 17c5aaca..11ca6984 100644 --- a/src/mobile.html +++ b/src/mobile.html @@ -58,6 +58,18 @@ + + diff --git a/src/styles/mobile/amount-color.css b/src/styles/mobile/amount-color.scss similarity index 100% rename from src/styles/mobile/amount-color.css rename to src/styles/mobile/amount-color.scss diff --git a/src/styles/mobile/font-size-default.css b/src/styles/mobile/font-size-default.scss similarity index 100% rename from src/styles/mobile/font-size-default.css rename to src/styles/mobile/font-size-default.scss diff --git a/src/styles/mobile/font-size-large.css b/src/styles/mobile/font-size-large.scss similarity index 100% rename from src/styles/mobile/font-size-large.css rename to src/styles/mobile/font-size-large.scss diff --git a/src/styles/mobile/font-size-small.css b/src/styles/mobile/font-size-small.scss similarity index 100% rename from src/styles/mobile/font-size-small.css rename to src/styles/mobile/font-size-small.scss diff --git a/src/styles/mobile/font-size-x-large.css b/src/styles/mobile/font-size-x-large.scss similarity index 100% rename from src/styles/mobile/font-size-x-large.css rename to src/styles/mobile/font-size-x-large.scss diff --git a/src/styles/mobile/font-size-xx-large.css b/src/styles/mobile/font-size-xx-large.scss similarity index 100% rename from src/styles/mobile/font-size-xx-large.css rename to src/styles/mobile/font-size-xx-large.scss diff --git a/src/styles/mobile/font-size-xxx-large.css b/src/styles/mobile/font-size-xxx-large.scss similarity index 100% rename from src/styles/mobile/font-size-xxx-large.css rename to src/styles/mobile/font-size-xxx-large.scss diff --git a/src/styles/mobile/font-size-xxxx-large.css b/src/styles/mobile/font-size-xxxx-large.scss similarity index 100% rename from src/styles/mobile/font-size-xxxx-large.css rename to src/styles/mobile/font-size-xxxx-large.scss diff --git a/src/styles/mobile/global.css b/src/styles/mobile/global.scss similarity index 86% rename from src/styles/mobile/global.css rename to src/styles/mobile/global.scss index 91d1266f..854a7639 100644 --- a/src/styles/mobile/global.css +++ b/src/styles/mobile/global.scss @@ -21,6 +21,38 @@ input[type=number] { } /** Common class **/ +.margin-inline-start { + margin-inline-start: var(--f7-typography-margin) !important; +} + +.margin-inline-start-half { + margin-inline-start: calc(var(--f7-typography-margin) / 2) !important; +} + +.margin-inline-end { + margin-inline-end: var(--f7-typography-margin) !important; +} + +.margin-inline-end-half { + margin-inline-end: calc(var(--f7-typography-margin) / 2) !important; +} + +.padding-inline-start { + padding-inline-start: var(--f7-typography-padding) !important; +} + +.padding-inline-start-half { + padding-inline-start: calc(var(--f7-typography-padding) / 2) !important; +} + +.padding-inline-end { + padding-inline-end: var(--f7-typography-padding) !important; +} + +.padding-inline-end-half { + padding-inline-end: calc(var(--f7-typography-padding) / 2) !important; +} + .no-right-border { border-right: 0; } @@ -234,6 +266,10 @@ i.icon.la, i.icon.las, i.icon.lab { } } +html[dir="rtl"] i.icon.icon-with-direction { + transform: scaleX(-1); +} + /** Replacing the default style of @vuepic/vue-datepicker **/ .dp__theme_light { --dp-primary-color: #c67e48; @@ -245,12 +281,12 @@ i.icon.la, i.icon.las, i.icon.lab { /** Common class for replacing the default style of framework7 **/ .navbar .navbar-compact-icons.right a + a { - margin-left: 0; + margin-inline-start: 0; } .toolbar-item-auto-size .toolbar-inner { - padding-left: 16px; - padding-right: 16px; + padding-inline-start: 16px; + padding-inline-end: 16px; gap: 4px; } @@ -402,11 +438,22 @@ i.icon.la, i.icon.las, i.icon.lab { .list .item-content .list-item-checked-icon { font-size: var(--ebk-list-item-checked-icon-font-size); color: var(--f7-radio-active-color, var(--f7-theme-color)); - margin-right: calc(var(--f7-list-item-media-margin) + var(--f7-checkbox-extra-margin)); +} + +html:not([dir="rtl"]) .list .item-content .list-item-checked-icon { + margin-inline-end: calc(var(--f7-list-item-media-margin) + var(--f7-checkbox-extra-margin)); +} + +html[dir="rtl"] .list .item-content .list-item-checked-icon { + margin-inline-start: calc(var(--f7-list-item-media-margin) + var(--f7-checkbox-extra-margin)); } .list .item-content > .item-inner > .item-after .list-item-checked-icon { - margin-right: 0; + margin-inline-end: 0; +} + +html[dir="rtl"] .list .item-content > .item-inner > .item-after .list-item-checked-icon { + margin-inline-start: 0; } .list li.no-margin .item-content.item-input { @@ -435,7 +482,7 @@ i.icon.la, i.icon.las, i.icon.lab { } .icon-after-text { - margin-left: 6px; + margin-inline-start: 6px; } .icon-after-text i.icon { @@ -443,12 +490,22 @@ i.icon.la, i.icon.las, i.icon.lab { } .badge.right-bottom-icon { - margin-left: -12px; + margin-inline-start: -12px; margin-top: 14px; width: 16px; height: 16px; } +html[dir="rtl"] .badge.right-bottom-icon { + left: unset; + right: 50%; +} + +html[dir="rtl"] .icon.las .badge.right-bottom-icon { + right: 100%; + margin-inline-start: -14px; +} + .badge.right-bottom-icon > .icon { font-size: var(--ebk-right-bottom-icon-font-size); width: var(--ebk-right-bottom-icon-font-size); @@ -472,7 +529,7 @@ i.icon.la, i.icon.las, i.icon.lab { position: absolute; left: 50%; top: 50%; - margin-left: -18px; + margin-inline-start: -18px; margin-top: -8px; border-radius: 3px; background: #666 @@ -493,19 +550,19 @@ i.icon.la, i.icon.las, i.icon.lab { } .list-item-with-multi-item .list-item-subitem:first-child .item-content { - padding-left: calc(var(--f7-list-item-padding-horizontal) + var(--f7-safe-area-left)); + padding-inline-start: calc(var(--f7-list-item-padding-horizontal) + var(--f7-safe-area-left)); } .list-item-with-multi-item .list-item-subitem .item-inner { display: block; width: 100%; - padding-left: calc(var(--f7-list-item-padding-horizontal) + var(--f7-safe-area-left)); + padding-inline-start: calc(var(--f7-list-item-padding-horizontal) + var(--f7-safe-area-left)); padding-top: var(--f7-list-item-padding-vertical); padding-bottom: var(--f7-list-item-padding-vertical); } .list-item-with-multi-item .list-item-subitem:first-child .item-inner { - padding-left: 0; + padding-inline-start: 0; } /** Combination list for framework7 **/ @@ -544,8 +601,12 @@ i.icon.la, i.icon.las, i.icon.lab { border-radius: var(--f7-list-inset-border-radius); } -.combination-list-wrapper .list.combination-list-header .combination-list-chevron-icon { - margin-left: auto; +html:not([dir="rtl"]) .combination-list-wrapper .list.combination-list-header .combination-list-chevron-icon { + margin-inline-start: auto; +} + +html[dir="rtl"] .combination-list-wrapper .list.combination-list-header .combination-list-chevron-icon { + margin-inline-end: auto; } .combination-list-wrapper .list.combination-list-content.inset > ul { @@ -576,7 +637,7 @@ i.icon.la, i.icon.las, i.icon.lab { } .nested-list-item.has-child-list-item .item-link .item-inner { - padding-right: 0; + padding-inline-end: 0; } .nested-list-item.has-child-list-item .item-link .item-inner:before { @@ -589,7 +650,7 @@ i.icon.la, i.icon.las, i.icon.lab { .nested-list-item.has-child-list-item .item-link .item-inner .nested-list-item-child .item-link .item-inner, .nested-list-item.has-child-list-item .item-link .item-inner .nested-list-item-inner { - padding-right: calc(var(--f7-list-chevron-icon-area) + var(--f7-list-item-padding-horizontal) + var(--f7-safe-area-right)); + padding-inline-end: calc(var(--f7-list-chevron-icon-area) + var(--f7-list-item-padding-horizontal) + var(--f7-safe-area-right)); } .nested-list-item.has-child-list-item .item-link .item-inner .nested-list-item-child .item-link .item-inner:before { @@ -603,13 +664,13 @@ i.icon.la, i.icon.las, i.icon.lab { .nested-list-item .nested-list-item-title { width: 100%; flex-shrink: 1; - margin-right: var(--f7-list-item-media-margin); + margin-inline-end: var(--f7-list-item-media-margin); overflow: hidden; text-overflow: ellipsis; } .nested-list-item.has-child-list-item .nested-list-item-title { - margin-left: var(--f7-list-item-media-margin); + margin-inline-start: var(--f7-list-item-media-margin); } .nested-list-item .nested-list-item-after { @@ -618,8 +679,8 @@ i.icon.la, i.icon.las, i.icon.lab { display: flex; font-size: var(--f7-list-item-after-font-size); color: var(--f7-list-item-after-text-color); - margin-left: auto; - padding-left: var(--f7-list-item-after-padding); + margin-inline-start: auto; + padding-inline-start: var(--f7-list-item-after-padding); } .nested-list-item.has-child-list-item > .swipeout-content > .item-content > .item-inner:after, @@ -654,7 +715,7 @@ i.icon.la, i.icon.las, i.icon.lab { } .sortable-enabled .nested-list-item .nested-list-item-child .item-inner { - padding-right: var(--f7-safe-area-right) !important; + padding-inline-end: var(--f7-safe-area-right) !important; } /** Fix @vuepic/vue-datepicker style issue **/ @@ -683,6 +744,10 @@ i.icon.la, i.icon.las, i.icon.lab { width: 100%; } +html[dir="rtl"] .dp__main .dp__btn.dp--arrow-btn-nav { + transform: scaleX(-1); +} + /* statistics-list */ .statistics-list-item-overview-amount { margin-top: 2px; @@ -709,7 +774,7 @@ i.icon.la, i.icon.las, i.icon.lab { .statistics-percent { font-size: 0.7em; opacity: 0.6; - margin-left: 6px; + margin-inline-start: 6px; } .statistics-item-end { @@ -719,7 +784,7 @@ i.icon.la, i.icon.las, i.icon.lab { } .statistics-percent-line { - margin-right: calc(var(--f7-list-chevron-icon-area) + var(--f7-list-item-padding-horizontal) + var(--f7-safe-area-right)); + margin-inline-end: calc(var(--f7-list-chevron-icon-area) + var(--f7-list-item-padding-horizontal) + var(--f7-safe-area-right)); } .statistics-percent-line .progressbar { diff --git a/src/views/mobile/HomePage.vue b/src/views/mobile/HomePage.vue index df74b6b9..59a12adc 100644 --- a/src/views/mobile/HomePage.vue +++ b/src/views/mobile/HomePage.vue @@ -21,7 +21,7 @@

0.00 USD {{ transactionOverview && transactionOverview.thisMonth ? getDisplayExpenseAmount(transactionOverview.thisMonth) : '-' }} - +

@@ -314,11 +314,11 @@ init(); } .home-summary-misc > span { - margin-right: 4px; + margin-inline-end: 4px; } .home-summary-misc > span:last-child { - margin-right: 0; + margin-inline-end: 0; } .dark .home-summary-card { @@ -348,7 +348,7 @@ init(); } .overview-transaction-list .overview-transaction-footer > span { - margin-right: 4px; + margin-inline-end: 4px; } .overview-transaction-list .overview-transaction-amount { diff --git a/src/views/mobile/SignupPage.vue b/src/views/mobile/SignupPage.vue index a56b5dda..87296460 100644 --- a/src/views/mobile/SignupPage.vue +++ b/src/views/mobile/SignupPage.vue @@ -156,7 +156,7 @@ - + diff --git a/src/views/mobile/accounts/ListPage.vue b/src/views/mobile/accounts/ListPage.vue index 5d400f3c..a97a3f7f 100644 --- a/src/views/mobile/accounts/ListPage.vue +++ b/src/views/mobile/accounts/ListPage.vue @@ -19,7 +19,7 @@

0.00 USD {{ netAssets }} - +

@@ -44,7 +44,7 @@ Account Category - 0.00 USD + 0.00 USD {{ tt(accountCategory.name) }} - {{ accountCategoryTotalBalance(accountCategory) }} + {{ accountCategoryTotalBalance(accountCategory) }} - - + - + - + @@ -207,6 +211,7 @@ import { useAccountListPageBaseBase } from '@/views/base/accounts/AccountListPag import { useAccountsStore } from '@/stores/account.ts'; +import { TextDirection } from '@/core/text.ts'; import { AccountType, AccountCategory } from '@/core/account.ts'; import type { Account, AccountShowingIds } from '@/models/account.ts'; @@ -216,7 +221,7 @@ const props = defineProps<{ f7router: Router.Router; }>(); -const { tt } = useI18n(); +const { tt, getCurrentLanguageTextDirection } = useI18n(); const { showAlert, showToast, routeBackOnError } = useI18nUIComponents(); const { @@ -244,6 +249,7 @@ const showMoreActionSheet = ref(false); const showDeleteActionSheet = ref(false); const displayOrderSaving = ref(false); +const textDirection = computed(() => getCurrentLanguageTextDirection()); const firstShowingIds = computed(() => accountsStore.getFirstShowingIds(showHidden.value)); const lastShowingIds = computed(() => accountsStore.getLastShowingIds(showHidden.value)); const hasAnyVisibleAccount = computed(() => accountsStore.allVisibleAccountsCount > 0); @@ -487,11 +493,11 @@ init(); } .account-overview-info > span { - margin-right: 4px; + margin-inline-end: 4px; } .account-overview-info > span:last-child { - margin-right: 0; + margin-inline-end: 0; } .account-list { diff --git a/src/views/mobile/accounts/ReconciliationStatementPage.vue b/src/views/mobile/accounts/ReconciliationStatementPage.vue index 6639393c..749c4cea 100644 --- a/src/views/mobile/accounts/ReconciliationStatementPage.vue +++ b/src/views/mobile/accounts/ReconciliationStatementPage.vue @@ -143,7 +143,7 @@
@@ -229,14 +229,16 @@ - + - @@ -260,7 +262,7 @@
- {{ tt('Time Granularity') }} + {{ tt('Time Granularity') }} {{ chartDataDateAggregationTypeDisplayName }}
@@ -352,6 +354,7 @@ import { useAccountsStore } from '@/stores/account.ts'; import { useTransactionCategoriesStore } from '@/stores/transactionCategory.ts'; import { useTransactionsStore } from '@/stores/transaction.ts'; +import { TextDirection } from '@/core/text.ts'; import { type TimeRangeAndDateType, DateRange, DateRangeScene } from '@/core/datetime.ts'; import { AccountType } from '@/core/account.ts'; import { TransactionType } from '@/core/transaction.ts'; @@ -386,7 +389,14 @@ const props = defineProps<{ f7router: Router.Router; }>(); -const { tt, getAllDateRanges, formatUnixTimeToLongDateTime, formatNumberToLocalizedNumerals } = useI18n(); +const { + tt, + getCurrentLanguageTextDirection, + getAllDateRanges, + formatUnixTimeToLongDateTime, + formatNumberToLocalizedNumerals +} = useI18n(); + const { showAlert, showToast, routeBackOnError } = useI18nUIComponents(); const { @@ -440,6 +450,7 @@ const virtualDataItems = ref({ topPosition: 0 }); +const textDirection = computed(() => getCurrentLanguageTextDirection()); const validQuery = computed(() => currentAccount.value && currentAccount.value.type === AccountType.SingleAccount.type); const allAvailableDateRanges = computed(() => getAllDateRanges(DateRangeScene.Normal, true, !!accountsStore.getAccountStatementDate(accountId.value))); const displayStartTime = computed(() => formatUnixTimeToLongDateTime(startTime.value)); @@ -708,11 +719,11 @@ init(); } .list.reconciliation-statement-list li.reconciliation-statement-transaction-date > .item-content { - padding-left: 0 !important; + padding-inline-start: 0 !important; } .list.reconciliation-statement-list li.reconciliation-statement-transaction-date > .item-content > .item-inner { - padding-left: calc(var(--f7-list-item-padding-horizontal) + var(--f7-safe-area-left)); + padding-inline-start: calc(var(--f7-list-item-padding-horizontal) + var(--f7-safe-area-left)); } .list.reconciliation-statement-list li.reconciliation-statement-transaction-date > .item-content > .item-inner:after { @@ -725,7 +736,7 @@ init(); } .list.reconciliation-statement-list li.transaction-info .account-balance { - margin-left: 4px; + margin-inline-start: 4px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; diff --git a/src/views/mobile/categories/ListPage.vue b/src/views/mobile/categories/ListPage.vue index 89a0beae..85633beb 100644 --- a/src/views/mobile/categories/ListPage.vue +++ b/src/views/mobile/categories/ListPage.vue @@ -48,15 +48,19 @@ - - + - + - + @@ -96,6 +100,7 @@ import { useCategoryListPageBase } from '@/views/base/categories/CategoryListPag import { useTransactionCategoriesStore } from '@/stores/transactionCategory.ts'; +import { TextDirection } from '@/core/text.ts'; import { CategoryType } from '@/core/category.ts'; import type { TransactionCategory } from '@/models/transaction_category.ts'; @@ -110,7 +115,7 @@ const props = defineProps<{ f7router: Router.Router; }>(); -const { tt } = useI18n(); +const { tt, getCurrentLanguageTextDirection } = useI18n(); const { showAlert, showToast, routeBackOnError } = useI18nUIComponents(); const { loading, primaryCategoryId, currentPrimaryCategory } = useCategoryListPageBase(); @@ -127,6 +132,8 @@ const showDeleteActionSheet = ref(false); const displayOrderModified = ref(false); const displayOrderSaving = ref(false); +const textDirection = computed(() => getCurrentLanguageTextDirection()); + const categories = computed(() => { if (!primaryCategoryId.value || primaryCategoryId.value === '' || primaryCategoryId.value === '0') { if (!transactionCategoriesStore.allTransactionCategories || !transactionCategoriesStore.allTransactionCategories[categoryType.value]) { diff --git a/src/views/mobile/categories/PresetPage.vue b/src/views/mobile/categories/PresetPage.vue index ce2223c7..a45fd42e 100644 --- a/src/views/mobile/categories/PresetPage.vue +++ b/src/views/mobile/categories/PresetPage.vue @@ -21,7 +21,7 @@ - + - + - @@ -134,6 +136,7 @@ import { useExchangeRatesPageBase } from '@/views/base/ExchangeRatesPageBase.ts' import { useExchangeRatesStore } from '@/stores/exchangeRates.ts'; +import { TextDirection } from '@/core/text.ts'; import { NumeralSystem } from '@/core/numeral.ts'; import { TRANSACTION_MIN_AMOUNT, TRANSACTION_MAX_AMOUNT } from '@/consts/transaction.ts'; @@ -149,6 +152,7 @@ const props = defineProps<{ const { tt, + getCurrentLanguageTextDirection, getCurrentNumeralSystemType, getCurrencyName, formatAmountToLocalizedNumerals, @@ -180,6 +184,7 @@ const showBaseAmountSheet = ref(false); const customExchangeRateToDelete = ref(null); const showDeleteActionSheet = ref(false); +const textDirection = computed(() => getCurrentLanguageTextDirection()); const displayBaseAmount = computed(() => formatAmountToLocalizedNumerals(baseAmount.value, baseCurrency.value)); const baseAmountFontSizeClass = computed(() => { if (baseAmount.value >= 100000000 || baseAmount.value <= -100000000) { diff --git a/src/views/mobile/settings/AccountFilterSettingsPage.vue b/src/views/mobile/settings/AccountFilterSettingsPage.vue index 454cba3d..b811585a 100644 --- a/src/views/mobile/settings/AccountFilterSettingsPage.vue +++ b/src/views/mobile/settings/AccountFilterSettingsPage.vue @@ -83,7 +83,7 @@