diff --git a/index.html b/index.html index 4bfec5d1..3335369e 100644 --- a/index.html +++ b/index.html @@ -1,45 +1,41 @@ - - - - - - + - - - - - - + + + + + - - - + + + + + + - - + + + - - + + - - + + - movie-web + + - {{#if opensearchEnabled }} - - + movie-web - - - {{/if}} - - - -
- - - + {{/if}} + + + + +
+ + + + \ No newline at end of file diff --git a/package.json b/package.json index b2bbd592..92016b6f 100644 --- a/package.json +++ b/package.json @@ -98,6 +98,8 @@ "handlebars": "^4.7.7", "jsdom": "^21.1.0", "postcss": "^8.4.20", + "postcss-rtl": "^2.0.0", + "postcss-rtlcss": "^4.0.9", "prettier": "^2.5.1", "prettier-plugin-tailwindcss": "^0.1.7", "tailwind-scrollbar": "^2.0.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1b318e25..729e7e19 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -223,6 +223,12 @@ devDependencies: postcss: specifier: '>=8.4.31' version: 8.4.31 + postcss-rtl: + specifier: ^2.0.0 + version: 2.0.0(postcss@8.4.31) + postcss-rtlcss: + specifier: ^4.0.9 + version: 4.0.9(postcss@8.4.31) prettier: specifier: ^2.5.1 version: 2.8.8 @@ -5070,6 +5076,26 @@ packages: postcss-selector-parser: 6.0.13 dev: true + /postcss-rtl@2.0.0(postcss@8.4.31): + resolution: {integrity: sha512-vFu78CvaGY9BafWRHNgDm6OjUxzRCWWCrp+KtnyXdgwibLwb/j5ls8Z/ubvOsk9B/Q2NLwSPrXRARKMaa9RBmA==} + engines: {node: '>=14.0.0'} + peerDependencies: + postcss: '>=8.4.31' + dependencies: + postcss: 8.4.31 + rtlcss: 4.0.0 + dev: true + + /postcss-rtlcss@4.0.9(postcss@8.4.31): + resolution: {integrity: sha512-dCNKEf+FgTv+EA3XI8ysg2RnpS5s3/iZmU+9qpCNFxHU/BhK+4hz7jyCsCAfo0CLnDrMPtaQENhwb+EGm1wh7Q==} + engines: {node: '>=18.0.0'} + peerDependencies: + postcss: '>=8.4.31' + dependencies: + postcss: 8.4.31 + rtlcss: 4.1.1 + dev: true + /postcss-selector-parser@6.0.13: resolution: {integrity: sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==} engines: {node: '>=4'} @@ -5480,6 +5506,28 @@ packages: '@babel/runtime': 7.22.11 dev: false + /rtlcss@4.0.0: + resolution: {integrity: sha512-j6oypPP+mgFwDXL1JkLCtm6U/DQntMUqlv5SOhpgHhdIE+PmBcjrtAHIpXfbIup47kD5Sgja9JDsDF1NNOsBwQ==} + engines: {node: '>=12.0.0'} + hasBin: true + dependencies: + escalade: 3.1.1 + picocolors: 1.0.0 + postcss: 8.4.31 + strip-json-comments: 3.1.1 + dev: true + + /rtlcss@4.1.1: + resolution: {integrity: sha512-/oVHgBtnPNcggP2aVXQjSy6N1mMAfHg4GSag0QtZBlD5bdDgAHwr4pydqJGd+SUCu9260+Pjqbjwtvu7EMH1KQ==} + engines: {node: '>=12.0.0'} + hasBin: true + dependencies: + escalade: 3.1.1 + picocolors: 1.0.0 + postcss: 8.4.31 + strip-json-comments: 3.1.1 + dev: true + /run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} dependencies: diff --git a/src/assets/languages.ts b/src/assets/languages.ts index 13b06541..71c21fe2 100644 --- a/src/assets/languages.ts +++ b/src/assets/languages.ts @@ -25,3 +25,6 @@ export const locales = { pirate, minion, }; +export type Locales = keyof typeof locales; + +export const rtlLocales: Locales[] = ["nl"]; diff --git a/src/components/player/atoms/EpisodeTitle.tsx b/src/components/player/atoms/EpisodeTitle.tsx index b36909b1..1187d71a 100644 --- a/src/components/player/atoms/EpisodeTitle.tsx +++ b/src/components/player/atoms/EpisodeTitle.tsx @@ -9,8 +9,8 @@ export function EpisodeTitle() { if (meta?.type !== "show") return null; return ( -
- +
+ {t("media.episodeDisplay", { season: meta?.season?.number, episode: meta?.episode?.number, diff --git a/src/setup/App.tsx b/src/setup/App.tsx index d532d824..10d9cd14 100644 --- a/src/setup/App.tsx +++ b/src/setup/App.tsx @@ -23,7 +23,7 @@ import { RegisterPage } from "@/pages/Register"; import { SettingsPage } from "@/pages/Settings"; import { Layout } from "@/setup/Layout"; import { useHistoryListener } from "@/stores/history"; -import { useLanguageListener } from "@/stores/language"; +import { LanguageProvider } from "@/stores/language"; function LegacyUrlView({ children }: { children: ReactElement }) { const location = useLocation(); @@ -61,10 +61,10 @@ function QuickSearch() { function App() { useHistoryListener(); useOnlineListener(); - useLanguageListener(); return ( + {/* functional routes */} diff --git a/src/stores/language/index.ts b/src/stores/language/index.ts index 052127bc..1bf632ed 100644 --- a/src/stores/language/index.ts +++ b/src/stores/language/index.ts @@ -4,6 +4,7 @@ import { persist } from "zustand/middleware"; import { immer } from "zustand/middleware/immer"; import i18n from "@/setup/i18n"; +import { rtlLocales } from "@/assets/languages"; export interface LanguageStore { language: string; @@ -24,10 +25,18 @@ export const useLanguageStore = create( ) ); -export function useLanguageListener() { +export function LanguageProvider() { const language = useLanguageStore((s) => s.language); useEffect(() => { i18n.changeLanguage(language); }, [language]); + + const isRtl = rtlLocales.includes(language); + + return ( + + + + ); } diff --git a/src/stores/language/index.tsx b/src/stores/language/index.tsx new file mode 100644 index 00000000..ba33d59a --- /dev/null +++ b/src/stores/language/index.tsx @@ -0,0 +1,43 @@ +import { useEffect } from "react"; +import { Helmet } from "react-helmet-async"; +import { create } from "zustand"; +import { persist } from "zustand/middleware"; +import { immer } from "zustand/middleware/immer"; + +import { rtlLocales } from "@/assets/languages"; +import i18n from "@/setup/i18n"; + +export interface LanguageStore { + language: string; + setLanguage(v: string): void; +} + +export const useLanguageStore = create( + persist( + immer((set) => ({ + language: "en", + setLanguage(v) { + set((s) => { + s.language = v; + }); + }, + })), + { name: "__MW::locale" } + ) +); + +export function LanguageProvider() { + const language = useLanguageStore((s) => s.language); + + useEffect(() => { + i18n.changeLanguage(language); + }, [language]); + + const isRtl = rtlLocales.includes(language as any); + + return ( + + + + ); +} diff --git a/vite.config.ts b/vite.config.ts index 9a0018b0..8ebf8198 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -7,6 +7,9 @@ import path from "path"; import { handlebars } from "./plugins/handlebars"; import { loadEnv } from "vite"; +import tailwind from "tailwindcss"; +import rtl from "postcss-rtlcss"; + export default defineConfig(({ mode }) => { const env = loadEnv(mode, process.cwd()); return { @@ -18,8 +21,8 @@ export default defineConfig(({ mode }) => { env.VITE_APP_DOMAIN + (env.VITE_NORMAL_ROUTER !== "true" ? "/#" : ""), domain: env.VITE_APP_DOMAIN, - env - } + env, + }, }), react({ babel: { @@ -31,24 +34,24 @@ export default defineConfig(({ mode }) => { modules: false, useBuiltIns: "entry", corejs: { - version: "3.29" - } - } - ] - ] - } + version: "3.29", + }, + }, + ], + ], + }, }), VitePWA({ disable: env.VITE_PWA_ENABLED !== "true", registerType: "autoUpdate", workbox: { maximumFileSizeToCacheInBytes: 4000000, // 4mb - globIgnores: ["**ping.txt**"] + globIgnores: ["**ping.txt**"], }, includeAssets: [ "favicon.ico", "apple-touch-icon.png", - "safari-pinned-tab.svg" + "safari-pinned-tab.svg", ], manifest: { name: "movie-web", @@ -63,48 +66,53 @@ export default defineConfig(({ mode }) => { src: "android-chrome-192x192.png", sizes: "192x192", type: "image/png", - purpose: "any" + purpose: "any", }, { src: "android-chrome-512x512.png", sizes: "512x512", type: "image/png", - purpose: "any" + purpose: "any", }, { src: "android-chrome-192x192.png", sizes: "192x192", type: "image/png", - purpose: "maskable" + purpose: "maskable", }, { src: "android-chrome-512x512.png", sizes: "512x512", type: "image/png", - purpose: "maskable" - } - ] - } + purpose: "maskable", + }, + ], + }, }), loadVersion(), checker({ overlay: { - position: "tr" + position: "tr", }, typescript: true, // check typescript build errors in dev server eslint: { // check lint errors in dev server lintCommand: "eslint --ext .tsx,.ts src", dev: { - logLevel: ["error"] - } - } - }) + logLevel: ["error"], + }, + }, + }), ], build: { sourcemap: true, }, + css: { + postcss: { + plugins: [tailwind(), rtl()], + }, + }, resolve: { alias: { @@ -112,12 +120,12 @@ export default defineConfig(({ mode }) => { "@sozialhelden/ietf-language-tags": path.resolve( __dirname, "./node_modules/@sozialhelden/ietf-language-tags/dist/cjs" - ) - } + ), + }, }, test: { - environment: "jsdom" - } + environment: "jsdom", + }, }; });