diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 087fdcbc..dca31685 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -5,15 +5,25 @@ diff --git a/frontend/src/css/_shell.css b/frontend/src/css/_shell.css index 5a12bede..77786795 100644 --- a/frontend/src/css/_shell.css +++ b/frontend/src/css/_shell.css @@ -16,10 +16,6 @@ transition: 0.2s ease transform; } -body.rtl .shell { - direction: ltr; -} - .shell__result { display: flex; padding: 0.5em; diff --git a/frontend/src/css/base.css b/frontend/src/css/base.css index bada9ae5..318590de 100644 --- a/frontend/src/css/base.css +++ b/frontend/src/css/base.css @@ -5,10 +5,6 @@ body { color: #333333; } -body.rtl { - direction: rtl; -} - * { box-sizing: border-box; } @@ -62,8 +58,8 @@ nav { left: 0; } -body.rtl nav { - left: unset; +html[dir="rtl"] nav { + left: initial; right: 0; } @@ -78,8 +74,7 @@ nav .action { text-overflow: ellipsis; } -body.rtl .action { - direction: rtl; +html[dir="rtl"] nav .action { text-align: right; } @@ -115,7 +110,7 @@ main { border-radius: 0.125em; } -body.rtl .breadcrumbs a { +html[dir="rtl"] .breadcrumbs a { transform: translateX(-16em); } diff --git a/frontend/src/css/dashboard.css b/frontend/src/css/dashboard.css index 73cac7aa..846d9dff 100644 --- a/frontend/src/css/dashboard.css +++ b/frontend/src/css/dashboard.css @@ -8,7 +8,7 @@ flex-wrap: wrap; } -body.rtl .dashboard .row { +html[dir="rtl"] .dashboard .row { margin-right: 16em; } @@ -64,7 +64,7 @@ p code { border-bottom: 2px solid rgba(0, 0, 0, 0.05); } -body.rtl #nav .wrapper { +html[dir="rtl"] .dashboard #nav .wrapper { margin-right: 16em; } @@ -145,7 +145,7 @@ table tr > *:first-child { padding-left: 1em; } -body.rtl table tr > * { +html[dir="rtl"] table tr > * { padding-left: unset; padding-right: 1em; text-align: right; @@ -197,7 +197,7 @@ table tr > *:last-child { margin-right: auto; } -body.rtl .card .card-title > *:first-child { +html[dir="rtl"] .card .card-title > *:first-child { margin-right: 0; text-align: right; } @@ -486,7 +486,7 @@ body.rtl .card .card-title > *:first-child { } /*** RTL - Fix disk usage information (in english) ***/ -body.rtl .credits { +html[dir="rtl"] .credits { text-align: right; direction: ltr; } diff --git a/frontend/src/css/header.css b/frontend/src/css/header.css index 6fbd5a19..69d81637 100644 --- a/frontend/src/css/header.css +++ b/frontend/src/css/header.css @@ -137,7 +137,7 @@ header .menu-button { z-index: 1; } -body.rtl #search #result { +html[dir="rtl"] #search #result { direction: ltr; } @@ -145,13 +145,13 @@ body.rtl #search #result { margin-top: 0; } -body.rtl #search #result { +html[dir="rtl"] #search #result { direction: rtl; text-align: right; } /*** RTL - Keep search result LTR because it has paths (in english) ***/ -body.rtl #search #result ul > * { +html[dir="rtl"] #search #result ul > * { direction: ltr; text-align: left; } @@ -241,7 +241,7 @@ body.rtl #search #result ul > * { padding: 0.5em; } -body.rtl #search .boxes h3 { +html[dir="rtl"] #search .boxes h3 { text-align: right; } diff --git a/frontend/src/css/listing.css b/frontend/src/css/listing.css index 9c38d9d2..b1c65f16 100644 --- a/frontend/src/css/listing.css +++ b/frontend/src/css/listing.css @@ -2,7 +2,7 @@ --item-selected: white; } -body.rtl #listing { +html[dir="rtl"] #listing { margin-right: 16em; } diff --git a/frontend/src/css/mobile.css b/frontend/src/css/mobile.css index aa948a0e..77dd7ece 100644 --- a/frontend/src/css/mobile.css +++ b/frontend/src/css/mobile.css @@ -45,7 +45,7 @@ z-index: 99999; } - body.rtl #dropdown { + html[dir="rtl"] #dropdown { right: unset; left: 1em; transform-origin: top left; @@ -109,7 +109,7 @@ left: -17em; } - body.rtl nav { + html[dir="rtl"] nav { left: unset; right: -17em; } @@ -117,7 +117,7 @@ left: 0; } - body.rtl nav.active { + html[dir="rtl"] nav.active { left: unset; right: 0; } @@ -133,19 +133,19 @@ margin-bottom: 5em; } - body.rtl #listing { + html[dir="rtl"] #listing { margin-right: unset; } - body.rtl .breadcrumbs { + html[dir="rtl"] .breadcrumbs { transform: translateX(16em); } - body.rtl #nav .wrapper { + html[dir="rtl"] #nav .wrapper { margin-right: unset; } - body.rtl .dashboard .row { + html[dir="rtl"] .dashboard .row { margin-right: unset; } diff --git a/frontend/src/css/styles.css b/frontend/src/css/styles.css index 9308889c..e2fe1c2a 100644 --- a/frontend/src/css/styles.css +++ b/frontend/src/css/styles.css @@ -315,7 +315,7 @@ main .spinner .bounce2 { } /*** RTL - flip and position arrow of path ***/ -body.rtl .breadcrumbs .chevron { +html[dir="rtl"] .breadcrumbs .chevron { transform: scaleX(-1) translateX(16em); } @@ -420,17 +420,17 @@ body.rtl .breadcrumbs .chevron { * RTL overrides * * * * * * * * * * * * * * * * */ -body.rtl .card-content textarea { +html[dir="rtl"] .card-content textarea { direction: ltr; text-align: left; } -body.rtl .card-content .small + input { +html[dir="rtl"] .card-content .small + input { direction: ltr; text-align: left; } -body.rtl .card.floating .card-content .file-list { +html[dir="rtl"] .card.floating .card-content .file-list { direction: ltr; text-align: left; } diff --git a/frontend/src/i18n/index.ts b/frontend/src/i18n/index.ts index 4cd72d40..68221d97 100644 --- a/frontend/src/i18n/index.ts +++ b/frontend/src/i18n/index.ts @@ -1,3 +1,4 @@ +import dayjs from "dayjs"; import { createI18n } from "vue-i18n"; import("dayjs/locale/ar"); @@ -135,4 +136,23 @@ export const i18n = createI18n({ legacy: true, }); +export const isRtl = (locale?: string) => { + return rtlLanguages.includes(locale || i18n.global.locale); +}; + +export function setLocale(locale: string) { + dayjs.locale(locale); + // according to doc u only need .value if legacy: false but they lied + // https://vue-i18n.intlify.dev/guide/essentials/scope.html#local-scope-1 + //@ts-ignore + i18n.global.locale.value = locale; +} + +export function setHtmlLocale(locale: string) { + const html = document.documentElement; + html.lang = locale; + if (isRtl(locale)) html.dir = "rtl"; + else html.dir = "ltr"; +} + export default i18n; diff --git a/frontend/src/main.ts b/frontend/src/main.ts index b508d97e..92545065 100644 --- a/frontend/src/main.ts +++ b/frontend/src/main.ts @@ -8,7 +8,7 @@ import { } from "vue-toastification/dist/types/types"; import createPinia from "@/stores"; import router from "@/router"; -import i18n, { rtlLanguages } from "@/i18n"; +import i18n, { isRtl } from "@/i18n"; import App from "@/App.vue"; import CustomToast from "@/components/CustomToast.vue"; @@ -77,7 +77,7 @@ app.provide("$showSuccess", (message: string) => { message: message, }, }, - { ...toastConfig, rtl: rtlLanguages.includes(i18n.global.locale) } + { ...toastConfig, rtl: isRtl() } ); }); @@ -96,7 +96,7 @@ app.provide("$showError", (error: Error | string, displayReport = true) => { { ...toastConfig, timeout: 0, - rtl: rtlLanguages.includes(i18n.global.locale), + rtl: isRtl(), } ); }); diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts index ea93f3a5..111fa167 100644 --- a/frontend/src/router/index.ts +++ b/frontend/src/router/index.ts @@ -12,7 +12,7 @@ import Shares from "@/views/settings/Shares.vue"; import Errors from "@/views/Errors.vue"; import { useAuthStore } from "@/stores/auth"; import { baseURL, name } from "@/utils/constants"; -import { i18n, rtlLanguages } from "@/i18n"; +import { i18n, isRtl, rtlLanguages } from "@/i18n"; import { recaptcha, loginPage } from "@/utils/constants"; import { login, validateLogin } from "@/utils/auth"; @@ -186,18 +186,6 @@ router.beforeResolve(async (to, from, next) => { // const title = titles[to.name]; document.title = title + " - " + name; - /*** RTL related settings per route ****/ - const rtlSet = document.querySelector("body")?.classList.contains("rtl"); - const shouldSetRtl = rtlLanguages.includes(i18n.global.locale); - switch (true) { - case shouldSetRtl && !rtlSet: - document.querySelector("body")?.classList.add("rtl"); - break; - case !shouldSetRtl && rtlSet: - document.querySelector("body")?.classList.remove("rtl"); - break; - } - const authStore = useAuthStore(); // this will only be null on first route diff --git a/frontend/src/stores/auth.ts b/frontend/src/stores/auth.ts index f157afe7..38b4adcf 100644 --- a/frontend/src/stores/auth.ts +++ b/frontend/src/stores/auth.ts @@ -1,6 +1,6 @@ import { defineStore } from "pinia"; import dayjs from "dayjs"; -import i18n, { detectLocale } from "@/i18n"; +import i18n, { detectLocale, setLocale } from "@/i18n"; import { cloneDeep } from "lodash-es"; export const useAuthStore = defineStore("auth", { @@ -24,20 +24,12 @@ export const useAuthStore = defineStore("auth", { return; } - const locale = user.locale || detectLocale(); - dayjs.locale(locale); - // according to doc u only need .value if legacy: false but they lied - // https://vue-i18n.intlify.dev/guide/essentials/scope.html#local-scope-1 - //@ts-ignore - i18n.global.locale.value = locale; + setLocale(user.locale || detectLocale()); this.user = user; }, updateUser(user: Partial) { if (user.locale) { - dayjs.locale(user.locale); - // see above - //@ts-ignore - i18n.global.locale.value = user.locale; + setLocale(user.locale); } this.user = { ...this.user, ...cloneDeep(user) } as IUser; diff --git a/frontend/src/views/settings/Profile.vue b/frontend/src/views/settings/Profile.vue index 2c4023fe..68e0cf22 100644 --- a/frontend/src/views/settings/Profile.vue +++ b/frontend/src/views/settings/Profile.vue @@ -161,9 +161,7 @@ const updateSettings = async (event: Event) => { singleClick: singleClick.value, dateFormat: dateFormat.value, }; - const shouldReload = - rtlLanguages.includes(data.locale) !== - rtlLanguages.includes(i18n.global.locale); + await api.update(data, [ "locale", "hideDotfiles", @@ -171,9 +169,6 @@ const updateSettings = async (event: Event) => { "dateFormat", ]); authStore.updateUser(data); - if (shouldReload) { - location.reload(); - } $showSuccess(t("settings.settingsUpdated")); } catch (err) { if (err instanceof Error) {