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) {