diff --git a/package.json b/package.json index 93a825f9..0c7a20a4 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "hls.js": "^1.0.7", "i18next": "^22.4.5", "immer": "^10.0.2", + "iso-639-1": "^3.1.0", "lodash.isequal": "^4.5.0", "node-forge": "^1.3.1", "ofetch": "^1.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7a140288..77f1e762 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -65,6 +65,9 @@ dependencies: immer: specifier: ^10.0.2 version: 10.0.2 + iso-639-1: + specifier: ^3.1.0 + version: 3.1.0 lodash.isequal: specifier: ^4.5.0 version: 4.5.0 diff --git a/src/setup/index.css b/src/assets/css/index.css similarity index 100% rename from src/setup/index.css rename to src/assets/css/index.css diff --git a/src/assets/languages.ts b/src/assets/languages.ts new file mode 100644 index 00000000..1d6e66b5 --- /dev/null +++ b/src/assets/languages.ts @@ -0,0 +1,5 @@ +import en from "@/assets/locales/en.json"; + +export const locales = { + en, +}; diff --git a/src/assets/locales/en.json b/src/assets/locales/en.json new file mode 100644 index 00000000..5f50f7a2 --- /dev/null +++ b/src/assets/locales/en.json @@ -0,0 +1,59 @@ +{ + "global": { + "name": "movie-web" + }, + "media": { + "types": { + "movie": "Movie", + "show": "Show" + }, + "episodeDisplay": "S{{season}} E{{episode}}" + }, + "home": { + "mediaList": { + "stopEditing": "Stop editing" + } + }, + "overlays": { + "close": "Close" + }, + "screens": { + "loadingUser": "Loading your profile", + "loadingApp": "Loading application", + "loadingUserError": { + "text": "", + "textWithReset": "", + "reset": "Reset custom server" + }, + "migration": { + "failed": "Failed to migrate your data." + } + }, + "navigation": { + "banner": { + "offline": "Check your internet connection" + }, + "menu": { + "register": "Sync to cloud", + "settings": "Settings", + "about": "About us", + "support": "Support", + "logout": "Log out" + } + }, + "actions": { + "copy": "Copy" + }, + "footer": { + "tagline": "Watch your favorite shows and movies with this open source streaming app.", + "links": { + "github": "GitHub", + "dmca": "DMCA", + "discord": "Discord" + }, + "legal": { + "disclaimer": "Disclaimer", + "disclaimerText": "movie-web does not host any files, it merely links to 3rd party services. Legal issues should be taken up with the file hosts and providers. movie-web is not responsible for any media files shown by the video providers." + } + } +} diff --git a/src/assets/opensearch.xml.hbs b/src/assets/templates/opensearch.xml.hbs similarity index 100% rename from src/assets/opensearch.xml.hbs rename to src/assets/templates/opensearch.xml.hbs diff --git a/src/backend/helpers/report.ts b/src/backend/helpers/report.ts index 867ffaa0..f9ac89a6 100644 --- a/src/backend/helpers/report.ts +++ b/src/backend/helpers/report.ts @@ -5,6 +5,8 @@ import { useCallback } from "react"; import { ScrapingItems, ScrapingSegment } from "@/hooks/useProviderScrape"; import { PlayerMeta } from "@/stores/player/slices/source"; +// for anybody who cares - these are anonymous metrics. +// They are just used for figuring out if providers are broken or not const metricsEndpoint = "https://backend.movie-web.app/metrics/providers"; export type ProviderMetric = { diff --git a/src/components/FlagIcon.tsx b/src/components/FlagIcon.tsx index 2a4d2ce5..d512e6e0 100644 --- a/src/components/FlagIcon.tsx +++ b/src/components/FlagIcon.tsx @@ -5,24 +5,24 @@ export interface FlagIconProps { countryCode?: string; } -export function FlagIcon(props: FlagIconProps) { - // Country code overrides - const countryOverrides: Record = { - en: "gb", - cs: "cz", - el: "gr", - fa: "ir", - ko: "kr", - he: "il", - ze: "cn", - ar: "sa", - ja: "jp", - bs: "ba", - vi: "vn", - zh: "cn", - sl: "si", - }; +// Country code overrides +const countryOverrides: Record = { + en: "gb", + cs: "cz", + el: "gr", + fa: "ir", + ko: "kr", + he: "il", + ze: "cn", + ar: "sa", + ja: "jp", + bs: "ba", + vi: "vn", + zh: "cn", + sl: "si", +}; +export function FlagIcon(props: FlagIconProps) { let countryCode = (props.countryCode || "")?.split("-").pop()?.toLowerCase() || ""; if (countryOverrides[countryCode]) diff --git a/src/components/LinksDropdown.tsx b/src/components/LinksDropdown.tsx index 7f3c5b02..b6dfa3a6 100644 --- a/src/components/LinksDropdown.tsx +++ b/src/components/LinksDropdown.tsx @@ -1,11 +1,12 @@ import classNames from "classnames"; import { useCallback, useEffect, useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; import { useHistory } from "react-router-dom"; import { base64ToBuffer, decryptData } from "@/backend/accounts/crypto"; import { UserAvatar } from "@/components/Avatar"; import { Icon, Icons } from "@/components/Icon"; -import { Transition } from "@/components/Transition"; +import { Transition } from "@/components/utils/Transition"; import { useAuth } from "@/hooks/auth/useAuth"; import { conf } from "@/setup/config"; import { useAuthStore } from "@/stores/auth"; @@ -81,6 +82,7 @@ function CircleDropdownLink(props: { icon: Icons; href: string }) { } export function LinksDropdown(props: { children: React.ReactNode }) { + const { t } = useTranslation(); const [open, setOpen] = useState(false); const deviceName = useAuthStore((s) => s.account?.deviceName); const seed = useAuthStore((s) => s.account?.seed); @@ -130,18 +132,18 @@ export function LinksDropdown(props: { children: React.ReactNode }) { ) : ( - Sync to cloud + {t("navigation.menu.register")} )} - Settings + {t("navigation.menu.settings")} - About us + {t("navigation.menu.about")} - HELP MEEE + {t("navigation.menu.support")} {deviceName ? ( - Log out + {t("navigation.menu.logout")} ) : null} diff --git a/src/components/Overlay.tsx b/src/components/Overlay.tsx deleted file mode 100644 index 8f0dc1e8..00000000 --- a/src/components/Overlay.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { Helmet } from "react-helmet-async"; - -import { Transition } from "@/components/Transition"; - -export function Overlay(props: { children: React.ReactNode }) { - return ( - <> - - - -
- - {props.children} -
- - ); -} diff --git a/src/components/Slider.tsx b/src/components/Slider.tsx deleted file mode 100644 index 39b63e6e..00000000 --- a/src/components/Slider.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { ChangeEventHandler, useEffect, useRef } from "react"; - -export type SliderProps = { - label?: string; - min: number; - max: number; - step: number; - value?: number; - valueDisplay?: string; - onChange: ChangeEventHandler; -}; - -export function Slider(props: SliderProps) { - const ref = useRef(null); - useEffect(() => { - const e = ref.current as HTMLInputElement; - e.style.setProperty("--value", e.value); - e.style.setProperty("--min", e.min === "" ? "0" : e.min); - e.style.setProperty("--max", e.max === "" ? "100" : e.max); - e.addEventListener("input", () => e.style.setProperty("--value", e.value)); - }, [ref]); - - return ( -
-
- {props.label ? ( - - ) : null} - -
-
-
- {props.valueDisplay ?? props.value} -
-
-
- ); -} diff --git a/src/components/Button.tsx b/src/components/buttons/Button.tsx similarity index 100% rename from src/components/Button.tsx rename to src/components/buttons/Button.tsx diff --git a/src/components/buttons/ButtonControl.tsx b/src/components/buttons/ButtonControl.tsx deleted file mode 100644 index 41e67f4c..00000000 --- a/src/components/buttons/ButtonControl.tsx +++ /dev/null @@ -1,17 +0,0 @@ -export interface ButtonControlProps { - onClick?: () => void; - children?: React.ReactNode; - className?: string; -} - -export function ButtonControl({ - onClick, - children, - className, -}: ButtonControlProps) { - return ( - - ); -} diff --git a/src/components/buttons/EditButton.tsx b/src/components/buttons/EditButton.tsx index b46504a0..7c806968 100644 --- a/src/components/buttons/EditButton.tsx +++ b/src/components/buttons/EditButton.tsx @@ -4,8 +4,6 @@ import { useTranslation } from "react-i18next"; import { Icon, Icons } from "@/components/Icon"; -import { ButtonControl } from "./ButtonControl"; - export interface EditButtonProps { editing: boolean; onEdit?: (editing: boolean) => void; @@ -20,7 +18,8 @@ export function EditButton(props: EditButtonProps) { }, [props]); return ( - @@ -33,6 +32,6 @@ export function EditButton(props: EditButtonProps) { )} - + ); } diff --git a/src/components/form/ColorPicker.tsx b/src/components/form/ColorPicker.tsx index 4e922b22..5836af81 100644 --- a/src/components/form/ColorPicker.tsx +++ b/src/components/form/ColorPicker.tsx @@ -2,13 +2,14 @@ import classNames from "classnames"; import { Icon, Icons } from "../Icon"; +const colors = ["#2E65CF", "#7652DD", "#CF2E68", "#C2CF2E", "#2ECFA8"]; +export const initialColor = colors[0]; + export function ColorPicker(props: { label: string; value: string; onInput: (v: string) => void; }) { - // Migrate this to another file later - const colors = ["#2E65CF", "#7652DD", "#CF2E68", "#C2CF2E", "#2ECFA8"]; return (
{props.label ? ( diff --git a/src/components/Dropdown.tsx b/src/components/form/Dropdown.tsx similarity index 100% rename from src/components/Dropdown.tsx rename to src/components/form/Dropdown.tsx diff --git a/src/components/form/IconPicker.tsx b/src/components/form/IconPicker.tsx index 17d9f7f5..1310a022 100644 --- a/src/components/form/IconPicker.tsx +++ b/src/components/form/IconPicker.tsx @@ -2,20 +2,20 @@ import classNames from "classnames"; import { UserIcon, UserIcons } from "../UserIcon"; +const icons = [ + UserIcons.USER, + UserIcons.BOOKMARK, + UserIcons.CLOCK, + UserIcons.EYE_SLASH, + UserIcons.SEARCH, +]; +export const initialIcon = icons[0]; + export function IconPicker(props: { label: string; value: UserIcons; onInput: (v: UserIcons) => void; }) { - // Migrate this to another file later - const icons = [ - UserIcons.USER, - UserIcons.BOOKMARK, - UserIcons.CLOCK, - UserIcons.EYE_SLASH, - UserIcons.SEARCH, - ]; - return (
{props.label ? ( diff --git a/src/components/PassphraseDisplay.tsx b/src/components/form/PassphraseDisplay.tsx similarity index 90% rename from src/components/PassphraseDisplay.tsx rename to src/components/form/PassphraseDisplay.tsx index 88338343..b6ea0a0f 100644 --- a/src/components/PassphraseDisplay.tsx +++ b/src/components/form/PassphraseDisplay.tsx @@ -1,9 +1,11 @@ import { useRef, useState } from "react"; +import { useTranslation } from "react-i18next"; import { useCopyToClipboard, useMountedState } from "react-use"; -import { Icon, Icons } from "./Icon"; +import { Icon, Icons } from "../Icon"; export function PassphraseDisplay(props: { mnemonic: string }) { + const { t } = useTranslation(); const individualWords = props.mnemonic.split(" "); const [, copy] = useCopyToClipboard(); @@ -33,7 +35,7 @@ export function PassphraseDisplay(props: { mnemonic: string }) { icon={hasCopied ? Icons.CHECKMARK : Icons.COPY} className={hasCopied ? "text-xs" : ""} /> - Copy + {t("actions.copy")}
diff --git a/src/components/SearchBar.tsx b/src/components/form/SearchBar.tsx similarity index 94% rename from src/components/SearchBar.tsx rename to src/components/form/SearchBar.tsx index 3e5f12bf..26b09c8a 100644 --- a/src/components/SearchBar.tsx +++ b/src/components/form/SearchBar.tsx @@ -3,8 +3,8 @@ import { useState } from "react"; import { Flare } from "@/components/utils/Flare"; -import { Icon, Icons } from "./Icon"; -import { TextInputControl } from "./text-inputs/TextInputControl"; +import { Icon, Icons } from "../Icon"; +import { TextInputControl } from "../text-inputs/TextInputControl"; export interface SearchBarProps { placeholder?: string; diff --git a/src/components/layout/Backdrop.tsx b/src/components/layout/Backdrop.tsx deleted file mode 100644 index 0286880e..00000000 --- a/src/components/layout/Backdrop.tsx +++ /dev/null @@ -1,114 +0,0 @@ -import React, { createRef, useEffect, useState } from "react"; -import { createPortal } from "react-dom"; - -import { useFade } from "@/hooks/useFade"; - -interface BackdropProps { - onClick?: (e: MouseEvent) => void; - onBackdropHide?: () => void; - active?: boolean; -} - -export function useBackdrop(): [ - (state: boolean) => void, - BackdropProps, - { style: any } -] { - const [backdrop, setBackdropState] = useState(false); - const [isHighlighted, setisHighlighted] = useState(false); - - const setBackdrop = (state: boolean) => { - setBackdropState(state); - if (state) setisHighlighted(true); - }; - - const backdropProps: BackdropProps = { - active: backdrop, - onBackdropHide() { - setisHighlighted(false); - }, - }; - - const highlightedProps = { - style: isHighlighted - ? { - zIndex: "1000", - position: "relative", - } - : {}, - }; - - return [setBackdrop, backdropProps, highlightedProps]; -} - -function Backdrop(props: BackdropProps) { - const clickEvent = props.onClick || (() => {}); - const animationEvent = props.onBackdropHide || (() => {}); - const [isVisible, setVisible, fadeProps] = useFade(); - - useEffect(() => { - setVisible(!!props.active); - /* eslint-disable-next-line */ - }, [props.active, setVisible]); - - useEffect(() => { - if (!isVisible) animationEvent(); - /* eslint-disable-next-line */ - }, [isVisible]); - - if (!isVisible) return null; - - return ( -
clickEvent(e.nativeEvent)} - /> - ); -} - -export function BackdropContainer( - props: { - children: React.ReactNode; - } & BackdropProps -) { - const root = createRef(); - const copy = createRef(); - - useEffect(() => { - let frame = -1; - function poll() { - if (root.current && copy.current) { - const rect = root.current.getBoundingClientRect(); - copy.current.style.top = `${rect.top}px`; - copy.current.style.left = `${rect.left}px`; - copy.current.style.width = `${rect.width}px`; - copy.current.style.height = `${rect.height}px`; - } - frame = window.requestAnimationFrame(poll); - } - poll(); - return () => { - window.cancelAnimationFrame(frame); - }; - // we dont want this to run only on mount, dont care about ref updates - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [root, copy]); - - return ( -
- {createPortal( -
- -
- {props.children} -
-
, - document.body - )} -
{props.children}
-
- ); -} diff --git a/src/components/layout/Footer.tsx b/src/components/layout/Footer.tsx index b86dac16..da84527d 100644 --- a/src/components/layout/Footer.tsx +++ b/src/components/layout/Footer.tsx @@ -1,4 +1,5 @@ import { useTranslation } from "react-i18next"; +import { useHistory } from "react-router-dom"; import { Icon, Icons } from "@/components/Icon"; import { BrandPill } from "@/components/layout/BrandPill"; @@ -6,16 +7,18 @@ import { WideContainer } from "@/components/layout/WideContainer"; import { conf } from "@/setup/config"; function FooterLink(props: { - href: string; + href?: string; + onClick?: () => void; children: React.ReactNode; icon: Icons; }) { return ( {props.children} @@ -25,8 +28,10 @@ function FooterLink(props: { function Dmca() { const { t } = useTranslation(); + const history = useHistory(); + return ( - + history.push("/dmca")}> {t("footer.links.dmca")} ); diff --git a/src/components/media/MediaCard.tsx b/src/components/media/MediaCard.tsx index 2664651c..90243acf 100644 --- a/src/components/media/MediaCard.tsx +++ b/src/components/media/MediaCard.tsx @@ -37,7 +37,7 @@ function MediaCardContent({ const canLink = linkable && !closable; - const dotListContent = [t(`media.${media.type}`)]; + const dotListContent = [t(`media.types.${media.type}`)]; if (media.year) dotListContent.push(media.year.toFixed()); return ( @@ -82,7 +82,7 @@ function MediaCardContent({ closable ? "" : "group-hover:text-white", ].join(" ")} > - {t("seasons.seasonAndEpisode", { + {t("media.episodeDisplay", { season: series.season || 1, episode: series.episode, })} diff --git a/src/components/overlays/OverlayDisplay.tsx b/src/components/overlays/OverlayDisplay.tsx index 359ab106..42ce9f61 100644 --- a/src/components/overlays/OverlayDisplay.tsx +++ b/src/components/overlays/OverlayDisplay.tsx @@ -3,7 +3,7 @@ import FocusTrap from "focus-trap-react"; import { ReactNode, useCallback, useEffect, useRef, useState } from "react"; import { createPortal } from "react-dom"; -import { Transition } from "@/components/Transition"; +import { Transition } from "@/components/utils/Transition"; import { useInternalOverlayRouter, useRouterAnchorUpdate, diff --git a/src/components/overlays/OverlayPage.tsx b/src/components/overlays/OverlayPage.tsx index db582395..089b68cc 100644 --- a/src/components/overlays/OverlayPage.tsx +++ b/src/components/overlays/OverlayPage.tsx @@ -1,7 +1,10 @@ import classNames from "classnames"; import { ReactNode, useEffect, useMemo } from "react"; -import { Transition, TransitionAnimations } from "@/components/Transition"; +import { + Transition, + TransitionAnimations, +} from "@/components/utils/Transition"; import { useIsMobile } from "@/hooks/useIsMobile"; import { useInternalOverlayRouter } from "@/hooks/useOverlayRouter"; import { useOverlayStore } from "@/stores/overlay/store"; diff --git a/src/components/overlays/positions/OverlayMobilePosition.tsx b/src/components/overlays/positions/OverlayMobilePosition.tsx index e65cc376..9c452957 100644 --- a/src/components/overlays/positions/OverlayMobilePosition.tsx +++ b/src/components/overlays/positions/OverlayMobilePosition.tsx @@ -1,5 +1,6 @@ import classNames from "classnames"; import { ReactNode } from "react"; +import { useTranslation } from "react-i18next"; import { useInternalOverlayRouter } from "@/hooks/useOverlayRouter"; @@ -10,6 +11,7 @@ interface MobilePositionProps { export function OverlayMobilePosition(props: MobilePositionProps) { const router = useInternalOverlayRouter("hello world :)"); + const { t } = useTranslation(); return (
router.close()} > - Close + {t("overlays.close")} {/* Gradient to hide the progress */}
diff --git a/src/components/player/atoms/NextEpisodeButton.tsx b/src/components/player/atoms/NextEpisodeButton.tsx index e0a5c0d5..2b36d172 100644 --- a/src/components/player/atoms/NextEpisodeButton.tsx +++ b/src/components/player/atoms/NextEpisodeButton.tsx @@ -3,7 +3,7 @@ import { useCallback } from "react"; import { Icon, Icons } from "@/components/Icon"; import { usePlayerMeta } from "@/components/player/hooks/usePlayerMeta"; -import { Transition } from "@/components/Transition"; +import { Transition } from "@/components/utils/Transition"; import { PlayerMeta } from "@/stores/player/slices/source"; import { usePlayerStore } from "@/stores/player/store"; diff --git a/src/components/player/atoms/VolumeChangedPopout.tsx b/src/components/player/atoms/VolumeChangedPopout.tsx index 6551d394..54a0ffb6 100644 --- a/src/components/player/atoms/VolumeChangedPopout.tsx +++ b/src/components/player/atoms/VolumeChangedPopout.tsx @@ -1,6 +1,6 @@ import { Icon, Icons } from "@/components/Icon"; -import { Transition } from "@/components/Transition"; import { Flare } from "@/components/utils/Flare"; +import { Transition } from "@/components/utils/Transition"; import { usePlayerStore } from "@/stores/player/store"; import { useEmpheralVolumeStore } from "@/stores/volume"; diff --git a/src/components/player/atoms/settings/Downloads.tsx b/src/components/player/atoms/settings/Downloads.tsx index a230d87c..bd26e515 100644 --- a/src/components/player/atoms/settings/Downloads.tsx +++ b/src/components/player/atoms/settings/Downloads.tsx @@ -1,6 +1,6 @@ import { useMemo } from "react"; -import { Button } from "@/components/Button"; +import { Button } from "@/components/buttons/Button"; import { Icon, Icons } from "@/components/Icon"; import { OverlayPage } from "@/components/overlays/OverlayPage"; import { Menu } from "@/components/player/internals/ContextMenu"; diff --git a/src/components/player/base/BlackOverlay.tsx b/src/components/player/base/BlackOverlay.tsx index b6231fa3..dbd5f467 100644 --- a/src/components/player/base/BlackOverlay.tsx +++ b/src/components/player/base/BlackOverlay.tsx @@ -1,4 +1,4 @@ -import { Transition } from "@/components/Transition"; +import { Transition } from "@/components/utils/Transition"; export function BlackOverlay(props: { show?: boolean }) { return ( diff --git a/src/components/player/base/BottomControls.tsx b/src/components/player/base/BottomControls.tsx index 68d5e980..4af547f6 100644 --- a/src/components/player/base/BottomControls.tsx +++ b/src/components/player/base/BottomControls.tsx @@ -1,6 +1,6 @@ import { useEffect } from "react"; -import { Transition } from "@/components/Transition"; +import { Transition } from "@/components/utils/Transition"; import { usePlayerStore } from "@/stores/player/store"; export function BottomControls(props: { diff --git a/src/components/player/base/CenterMobileControls.tsx b/src/components/player/base/CenterMobileControls.tsx index 3c4d4bbc..c4e5b5e1 100644 --- a/src/components/player/base/CenterMobileControls.tsx +++ b/src/components/player/base/CenterMobileControls.tsx @@ -1,6 +1,6 @@ import classNames from "classnames"; -import { Transition } from "@/components/Transition"; +import { Transition } from "@/components/utils/Transition"; export function CenterMobileControls(props: { children: React.ReactNode; diff --git a/src/components/player/base/SubtitleView.tsx b/src/components/player/base/SubtitleView.tsx index d32f737e..c3f16a78 100644 --- a/src/components/player/base/SubtitleView.tsx +++ b/src/components/player/base/SubtitleView.tsx @@ -7,7 +7,7 @@ import { parseSubtitles, sanitize, } from "@/components/player/utils/captions"; -import { Transition } from "@/components/Transition"; +import { Transition } from "@/components/utils/Transition"; import { usePlayerStore } from "@/stores/player/store"; import { SubtitleStyling, useSubtitleStore } from "@/stores/subtitles"; diff --git a/src/components/player/base/TopControls.tsx b/src/components/player/base/TopControls.tsx index acb1c7dc..2d01c925 100644 --- a/src/components/player/base/TopControls.tsx +++ b/src/components/player/base/TopControls.tsx @@ -1,6 +1,6 @@ import { useEffect } from "react"; -import { Transition } from "@/components/Transition"; +import { Transition } from "@/components/utils/Transition"; import { useBannerSize } from "@/stores/banner"; import { BannerLocation } from "@/stores/banner/BannerLocation"; import { usePlayerStore } from "@/stores/player/store"; diff --git a/src/components/player/internals/ScrapeCard.tsx b/src/components/player/internals/ScrapeCard.tsx index b1e98b61..bd480758 100644 --- a/src/components/player/internals/ScrapeCard.tsx +++ b/src/components/player/internals/ScrapeCard.tsx @@ -2,7 +2,7 @@ import classNames from "classnames"; import { ReactNode } from "react"; import { StatusCircle } from "@/components/player/internals/StatusCircle"; -import { Transition } from "@/components/Transition"; +import { Transition } from "@/components/utils/Transition"; export interface ScrapeItemProps { status: "failure" | "pending" | "notfound" | "success" | "waiting"; diff --git a/src/components/player/internals/StatusCircle.tsx b/src/components/player/internals/StatusCircle.tsx index 9daf52ef..af57646a 100644 --- a/src/components/player/internals/StatusCircle.tsx +++ b/src/components/player/internals/StatusCircle.tsx @@ -2,7 +2,7 @@ import { a, to, useSpring } from "@react-spring/web"; import classNames from "classnames"; import { Icon, Icons } from "@/components/Icon"; -import { Transition } from "@/components/Transition"; +import { Transition } from "@/components/utils/Transition"; export interface StatusCircle { type: "loading" | "success" | "error" | "noresult" | "waiting"; diff --git a/src/components/text/Link.tsx b/src/components/text/Link.tsx deleted file mode 100644 index a4e6b2e8..00000000 --- a/src/components/text/Link.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { ReactNode } from "react"; -import { Link as LinkRouter } from "react-router-dom"; - -interface ILinkPropsBase { - children?: ReactNode; - className?: string; - onClick?: () => void; -} - -interface ILinkPropsExternal extends ILinkPropsBase { - url: string; - newTab?: boolean; -} - -interface ILinkPropsInternal extends ILinkPropsBase { - to: string; -} - -type LinkProps = ILinkPropsExternal | ILinkPropsInternal | ILinkPropsBase; - -export function Link(props: LinkProps) { - const isExternal = !!(props as ILinkPropsExternal).url; - const isInternal = !!(props as ILinkPropsInternal).to; - const content = ( - - {props.children} - - ); - - if (isExternal) - return ( - - {content} - - ); - if (isInternal) - return ( - {content} - ); - return ( - props.onClick && props.onClick()}>{content} - ); -} diff --git a/src/components/Transition.tsx b/src/components/utils/Transition.tsx similarity index 100% rename from src/components/Transition.tsx rename to src/components/utils/Transition.tsx diff --git a/src/hooks/useChromecastAvailable.ts b/src/hooks/useChromecastAvailable.ts index 15be311b..359a0cad 100644 --- a/src/hooks/useChromecastAvailable.ts +++ b/src/hooks/useChromecastAvailable.ts @@ -1,6 +1,6 @@ /// -import { useEffect, useRef, useState } from "react"; +import { useEffect, useState } from "react"; import { isChromecastAvailable } from "@/setup/chromecast"; @@ -13,93 +13,3 @@ export function useChromecastAvailable() { return available; } - -export function useChromecast() { - const available = useChromecastAvailable(); - const instance = useRef(null); - const remotePlayerController = - useRef(null); - - function startCast() { - const movieMeta = new chrome.cast.media.MovieMediaMetadata(); - movieMeta.title = "Big Buck Bunny"; - - const mediaInfo = new chrome.cast.media.MediaInfo("hello", "video/mp4"); - (mediaInfo as any).contentUrl = - "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4"; - mediaInfo.streamType = chrome.cast.media.StreamType.BUFFERED; - mediaInfo.metadata = movieMeta; - - const request = new chrome.cast.media.LoadRequest(mediaInfo); - request.autoplay = true; - - const session = instance.current?.getCurrentSession(); - if (!session) return; - - session.loadMedia(request).catch((e: any) => { - console.error(e); - }); - } - - function stopCast() { - const session = instance.current?.getCurrentSession(); - if (!session) return; - - const controller = remotePlayerController.current; - if (!controller) return; - controller.stop(); - } - - useEffect(() => { - if (!available) return; - - // setup instance if not already - if (!instance.current) { - const ins = cast.framework.CastContext.getInstance(); - ins.setOptions({ - receiverApplicationId: chrome.cast.media.DEFAULT_MEDIA_RECEIVER_APP_ID, - autoJoinPolicy: chrome.cast.AutoJoinPolicy.ORIGIN_SCOPED, - }); - instance.current = ins; - } - - // setup player if not already - if (!remotePlayerController.current) { - const player = new cast.framework.RemotePlayer(); - const controller = new cast.framework.RemotePlayerController(player); - remotePlayerController.current = controller; - } - - // setup event listener - function listenToEvents(e: cast.framework.RemotePlayerChangedEvent) { - console.debug("chromecast event", e); - } - function connectionChanged(e: cast.framework.RemotePlayerChangedEvent) { - console.info("chromecast event connection changed", e); - } - remotePlayerController.current.addEventListener( - cast.framework.RemotePlayerEventType.PLAYER_STATE_CHANGED, - listenToEvents - ); - remotePlayerController.current.addEventListener( - cast.framework.RemotePlayerEventType.IS_CONNECTED_CHANGED, - connectionChanged - ); - - return () => { - remotePlayerController.current?.removeEventListener( - cast.framework.RemotePlayerEventType.PLAYER_STATE_CHANGED, - listenToEvents - ); - remotePlayerController.current?.removeEventListener( - cast.framework.RemotePlayerEventType.IS_CONNECTED_CHANGED, - connectionChanged - ); - }; - }, [available]); - - return { - startCast, - stopCast, - }; -} diff --git a/src/hooks/useFade.css b/src/hooks/useFade.css deleted file mode 100644 index 9f1deb64..00000000 --- a/src/hooks/useFade.css +++ /dev/null @@ -1,17 +0,0 @@ -@keyframes fadeIn { - 0% { - opacity: 0; - } - 100% { - opacity: 1; - } -} - -@keyframes fadeOut { - 0% { - opacity: 1; - } - 100% { - opacity: 0; - } -} diff --git a/src/hooks/useFade.ts b/src/hooks/useFade.ts deleted file mode 100644 index 58438acf..00000000 --- a/src/hooks/useFade.ts +++ /dev/null @@ -1,29 +0,0 @@ -import React, { useEffect, useState } from "react"; -import "./useFade.css"; - -export const useFade = ( - initial = false -): [boolean, React.Dispatch>, any] => { - const [show, setShow] = useState(initial); - const [isVisible, setVisible] = useState(show); - - // Update visibility when show changes - useEffect(() => { - if (show) setVisible(true); - }, [show]); - - // When the animation finishes, set visibility to false - const onAnimationEnd = () => { - if (!show) setVisible(false); - }; - - const style = { animation: `${show ? "fadeIn" : "fadeOut"} .3s` }; - - // These props go on the fading DOM element - const fadeProps = { - style, - onAnimationEnd, - }; - - return [isVisible, setShow, fadeProps]; -}; diff --git a/src/hooks/useFloatingRouter.ts b/src/hooks/useFloatingRouter.ts deleted file mode 100644 index 0e9db907..00000000 --- a/src/hooks/useFloatingRouter.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { useLayoutEffect, useState } from "react"; - -export function useFloatingRouter(initial = "/") { - const [route, setRoute] = useState( - initial.split("/").filter((v) => v.length > 0) - ); - const [previousRoute, setPreviousRoute] = useState(route); - const currentPage = route[route.length - 1] ?? "/"; - - useLayoutEffect(() => { - if (previousRoute.length === route.length) return; - // when navigating backwards, we delay the updating by a bit so transitions can be applied correctly - setTimeout(() => { - setPreviousRoute(route); - }, 20); - }, [route, previousRoute]); - - function navigate(path: string) { - const newRoute = path.split("/").filter((v) => v.length > 0); - if (newRoute.length > previousRoute.length) setPreviousRoute(newRoute); - setRoute(newRoute); - } - - function isActive(page: string) { - if (page === "/") return true; - const index = previousRoute.indexOf(page); - if (index === -1) return false; // not active - if (index === previousRoute.length - 1) return false; // active but latest route so shouldnt be counted as active - return true; - } - - function isCurrentPage(page: string) { - return page === currentPage; - } - - function isLoaded(page: string) { - if (page === "/") return true; - return route.includes(page); - } - - function pageProps(page: string) { - return { - show: isCurrentPage(page), - active: isActive(page), - }; - } - - function reset() { - navigate("/"); - } - - return { - navigate, - reset, - isLoaded, - isCurrentPage, - pageProps, - isActive, - }; -} diff --git a/src/index.tsx b/src/index.tsx index 50183c68..79daec72 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,17 +1,18 @@ import "core-js/stable"; import "./stores/__old/imports"; import "@/setup/ga"; -import "@/setup/index.css"; +import "@/assets/css/index.css"; import React, { Suspense, useCallback } from "react"; import type { ReactNode } from "react"; import ReactDOM from "react-dom"; import { HelmetProvider } from "react-helmet-async"; +import { useTranslation } from "react-i18next"; import { BrowserRouter, HashRouter } from "react-router-dom"; import { useAsync } from "react-use"; import { registerSW } from "virtual:pwa-register"; -import { Button } from "@/components/Button"; +import { Button } from "@/components/buttons/Button"; import { Icon, Icons } from "@/components/Icon"; import { Loading } from "@/components/layout/Loading"; import { useAuthRestore } from "@/hooks/auth/useAuthRestore"; @@ -44,8 +45,15 @@ registerSW({ }); function LoadingScreen(props: { type: "user" | "lazy" }) { + const mapping = { + user: "screens.loadingUser", + lazy: "screens.loadingApp", + }; + const { t } = useTranslation(); return ( - }>Loading {props.type} + }> + {t(mapping[props.type] ?? "unknown.translation")} + ); } @@ -53,6 +61,7 @@ function ErrorScreen(props: { children: ReactNode; showResetButton?: boolean; }) { + const { t } = useTranslation(); const setBackendUrl = useAuthStore((s) => s.setBackendUrl); const resetBackend = useCallback(() => { setBackendUrl(null); @@ -70,7 +79,7 @@ function ErrorScreen(props: { {props.showResetButton ? (
) : null} @@ -82,14 +91,17 @@ function AuthWrapper() { const status = useAuthRestore(); const backendUrl = conf().BACKEND_URL; const userBackendUrl = useBackendUrl(); + const { t } = useTranslation(); if (status.loading) return ; if (status.error) return ( - {backendUrl !== userBackendUrl - ? "Failed to fetch user data. Try resetting the backend URL" - : "Failed to fetch user data."} + {t( + backendUrl !== userBackendUrl + ? "screens.loadingUserError.textWithReset" + : "screens.loadingUserError.text" + )} ); return ; @@ -100,10 +112,11 @@ function MigrationRunner() { i18n.changeLanguage(useLanguageStore.getState().language); await initializeOldStores(); }, []); + const { t } = useTranslation(); if (status.loading) return ; if (status.error) - return Failed to migrate your data.; + return {t("screens.migration.failed")}; return ; } diff --git a/src/pages/Settings.tsx b/src/pages/Settings.tsx index edc497a2..cb0677f1 100644 --- a/src/pages/Settings.tsx +++ b/src/pages/Settings.tsx @@ -10,7 +10,7 @@ import { import { getSessions, updateSession } from "@/backend/accounts/sessions"; import { updateSettings } from "@/backend/accounts/settings"; import { editUser } from "@/backend/accounts/user"; -import { Button } from "@/components/Button"; +import { Button } from "@/components/buttons/Button"; import { WideContainer } from "@/components/layout/WideContainer"; import { UserIcons } from "@/components/UserIcon"; import { Heading1 } from "@/components/utils/Text"; diff --git a/src/pages/developer/TestView.tsx b/src/pages/developer/TestView.tsx index 96fd2da4..d6f5a053 100644 --- a/src/pages/developer/TestView.tsx +++ b/src/pages/developer/TestView.tsx @@ -1,6 +1,6 @@ import { useState } from "react"; -import { Button } from "@/components/Button"; +import { Button } from "@/components/buttons/Button"; // mostly empty view, add whatever you need export default function TestView() { diff --git a/src/pages/developer/VideoTesterView.tsx b/src/pages/developer/VideoTesterView.tsx index 53a0c6af..10e6a9c0 100644 --- a/src/pages/developer/VideoTesterView.tsx +++ b/src/pages/developer/VideoTesterView.tsx @@ -1,7 +1,7 @@ import { useCallback, useState } from "react"; -import { Button } from "@/components/Button"; -import { Dropdown } from "@/components/Dropdown"; +import { Button } from "@/components/buttons/Button"; +import { Dropdown } from "@/components/form/Dropdown"; import { usePlayer } from "@/components/player/hooks/usePlayer"; import { Title } from "@/components/text/Title"; import { TextInputControl } from "@/components/text-inputs/TextInputControl"; diff --git a/src/pages/parts/admin/BackendTestPart.tsx b/src/pages/parts/admin/BackendTestPart.tsx index 12f11e36..449e9643 100644 --- a/src/pages/parts/admin/BackendTestPart.tsx +++ b/src/pages/parts/admin/BackendTestPart.tsx @@ -2,10 +2,9 @@ import { useState } from "react"; import { useAsyncFn } from "react-use"; import { MetaResponse, getBackendMeta } from "@/backend/accounts/meta"; -import { Button } from "@/components/Button"; +import { Button } from "@/components/buttons/Button"; import { Icon, Icons } from "@/components/Icon"; import { Box } from "@/components/layout/Box"; -import { Spinner } from "@/components/layout/Spinner"; import { Divider } from "@/components/utils/Divider"; import { Heading2 } from "@/components/utils/Text"; import { conf } from "@/setup/config"; diff --git a/src/pages/parts/admin/TMDBTestPart.tsx b/src/pages/parts/admin/TMDBTestPart.tsx index de9b53f4..00198946 100644 --- a/src/pages/parts/admin/TMDBTestPart.tsx +++ b/src/pages/parts/admin/TMDBTestPart.tsx @@ -3,7 +3,7 @@ import { useAsyncFn } from "react-use"; import { getMediaDetails } from "@/backend/metadata/tmdb"; import { TMDBContentTypes } from "@/backend/metadata/types/tmdb"; -import { Button } from "@/components/Button"; +import { Button } from "@/components/buttons/Button"; import { Icon, Icons } from "@/components/Icon"; import { Box } from "@/components/layout/Box"; import { Spinner } from "@/components/layout/Spinner"; diff --git a/src/pages/parts/admin/WorkerTestPart.tsx b/src/pages/parts/admin/WorkerTestPart.tsx index 6ce784f7..e1314e0a 100644 --- a/src/pages/parts/admin/WorkerTestPart.tsx +++ b/src/pages/parts/admin/WorkerTestPart.tsx @@ -3,10 +3,9 @@ import { useMemo, useState } from "react"; import { useAsyncFn } from "react-use"; import { mwFetch } from "@/backend/helpers/fetch"; -import { Button } from "@/components/Button"; +import { Button } from "@/components/buttons/Button"; import { Icon, Icons } from "@/components/Icon"; import { Box } from "@/components/layout/Box"; -import { Spinner } from "@/components/layout/Spinner"; import { Divider } from "@/components/utils/Divider"; import { Heading2 } from "@/components/utils/Text"; import { conf } from "@/setup/config"; diff --git a/src/pages/parts/auth/AccountCreatePart.tsx b/src/pages/parts/auth/AccountCreatePart.tsx index d5210f09..ce7e01c1 100644 --- a/src/pages/parts/auth/AccountCreatePart.tsx +++ b/src/pages/parts/auth/AccountCreatePart.tsx @@ -1,6 +1,6 @@ import { useCallback, useState } from "react"; -import { Button } from "@/components/Button"; +import { Button } from "@/components/buttons/Button"; import { ColorPicker } from "@/components/form/ColorPicker"; import { IconPicker } from "@/components/form/IconPicker"; import { Icon, Icons } from "@/components/Icon"; diff --git a/src/pages/parts/auth/LoginFormPart.tsx b/src/pages/parts/auth/LoginFormPart.tsx index 90898c05..038745cf 100644 --- a/src/pages/parts/auth/LoginFormPart.tsx +++ b/src/pages/parts/auth/LoginFormPart.tsx @@ -2,7 +2,7 @@ import { useState } from "react"; import { useAsyncFn } from "react-use"; import { verifyValidMnemonic } from "@/backend/accounts/crypto"; -import { Button } from "@/components/Button"; +import { Button } from "@/components/buttons/Button"; import { BrandPill } from "@/components/layout/BrandPill"; import { LargeCard, diff --git a/src/pages/parts/auth/PassphraseGeneratePart.tsx b/src/pages/parts/auth/PassphraseGeneratePart.tsx index d3767223..e796e5a1 100644 --- a/src/pages/parts/auth/PassphraseGeneratePart.tsx +++ b/src/pages/parts/auth/PassphraseGeneratePart.tsx @@ -1,14 +1,14 @@ import { useMemo } from "react"; import { genMnemonic } from "@/backend/accounts/crypto"; -import { Button } from "@/components/Button"; +import { Button } from "@/components/buttons/Button"; +import { PassphraseDisplay } from "@/components/form/PassphraseDisplay"; import { Icon, Icons } from "@/components/Icon"; import { LargeCard, LargeCardButtons, LargeCardText, } from "@/components/layout/LargeCard"; -import { PassphraseDisplay } from "@/components/PassphraseDisplay"; interface PassphraseGeneratePartProps { onNext?: (mnemonic: string) => void; diff --git a/src/pages/parts/auth/TrustBackendPart.tsx b/src/pages/parts/auth/TrustBackendPart.tsx index 86f80463..27c11332 100644 --- a/src/pages/parts/auth/TrustBackendPart.tsx +++ b/src/pages/parts/auth/TrustBackendPart.tsx @@ -3,7 +3,7 @@ import { useHistory } from "react-router-dom"; import { useAsync } from "react-use"; import { MetaResponse, getBackendMeta } from "@/backend/accounts/meta"; -import { Button } from "@/components/Button"; +import { Button } from "@/components/buttons/Button"; import { Icon, Icons } from "@/components/Icon"; import { LargeCard, diff --git a/src/pages/parts/auth/VerifyPassphrasePart.tsx b/src/pages/parts/auth/VerifyPassphrasePart.tsx index 3f5e017a..4516a14a 100644 --- a/src/pages/parts/auth/VerifyPassphrasePart.tsx +++ b/src/pages/parts/auth/VerifyPassphrasePart.tsx @@ -3,7 +3,7 @@ import { useGoogleReCaptcha } from "react-google-recaptcha-v3"; import { useAsyncFn } from "react-use"; import { updateSettings } from "@/backend/accounts/settings"; -import { Button } from "@/components/Button"; +import { Button } from "@/components/buttons/Button"; import { Icon, Icons } from "@/components/Icon"; import { LargeCard, diff --git a/src/pages/parts/errors/ErrorCard.tsx b/src/pages/parts/errors/ErrorCard.tsx index 87cb8410..28fa4688 100644 --- a/src/pages/parts/errors/ErrorCard.tsx +++ b/src/pages/parts/errors/ErrorCard.tsx @@ -1,6 +1,6 @@ import { useRef, useState } from "react"; -import { Button } from "@/components/Button"; +import { Button } from "@/components/buttons/Button"; import { Icon, Icons } from "@/components/Icon"; import { DisplayError } from "@/components/player/display/displayInterface"; diff --git a/src/pages/parts/errors/ErrorPart.tsx b/src/pages/parts/errors/ErrorPart.tsx index a547f649..0f7dca96 100644 --- a/src/pages/parts/errors/ErrorPart.tsx +++ b/src/pages/parts/errors/ErrorPart.tsx @@ -1,4 +1,4 @@ -import { ButtonPlain } from "@/components/Button"; +import { ButtonPlain } from "@/components/buttons/Button"; import { Icons } from "@/components/Icon"; import { IconPill } from "@/components/layout/IconPill"; import { Title } from "@/components/text/Title"; diff --git a/src/pages/parts/errors/ErrorWrapperPart.tsx b/src/pages/parts/errors/ErrorWrapperPart.tsx index 64d36459..688858cd 100644 --- a/src/pages/parts/errors/ErrorWrapperPart.tsx +++ b/src/pages/parts/errors/ErrorWrapperPart.tsx @@ -1,7 +1,7 @@ import { Helmet } from "react-helmet-async"; import { useTranslation } from "react-i18next"; -import { Button } from "@/components/Button"; +import { Button } from "@/components/buttons/Button"; import { Icons } from "@/components/Icon"; import { IconPill } from "@/components/layout/IconPill"; import { Navigation } from "@/components/layout/Navigation"; diff --git a/src/pages/parts/home/HeroPart.tsx b/src/pages/parts/home/HeroPart.tsx index 7df46a9c..f3eceef2 100644 --- a/src/pages/parts/home/HeroPart.tsx +++ b/src/pages/parts/home/HeroPart.tsx @@ -1,8 +1,8 @@ import { useCallback, useState } from "react"; import Sticky from "react-sticky-el"; +import { SearchBarInput } from "@/components/form/SearchBar"; import { ThinContainer } from "@/components/layout/ThinContainer"; -import { SearchBarInput } from "@/components/SearchBar"; import { HeroTitle } from "@/components/text/HeroTitle"; import { useRandomTranslation } from "@/hooks/useRandomTranslation"; import { useSearchQuery } from "@/hooks/useSearchQuery"; diff --git a/src/pages/parts/player/MetaPart.tsx b/src/pages/parts/player/MetaPart.tsx index b58a242e..291cba9a 100644 --- a/src/pages/parts/player/MetaPart.tsx +++ b/src/pages/parts/player/MetaPart.tsx @@ -5,7 +5,7 @@ import type { AsyncReturnType } from "type-fest"; import { DetailedMeta, getMetaFromId } from "@/backend/metadata/getmeta"; import { decodeTMDBId } from "@/backend/metadata/tmdb"; import { MWMediaType } from "@/backend/metadata/types/mw"; -import { Button } from "@/components/Button"; +import { Button } from "@/components/buttons/Button"; import { Icons } from "@/components/Icon"; import { IconPill } from "@/components/layout/IconPill"; import { Loading } from "@/components/layout/Loading"; diff --git a/src/pages/parts/player/PlaybackErrorPart.tsx b/src/pages/parts/player/PlaybackErrorPart.tsx index 88ab8af0..b8d2fd76 100644 --- a/src/pages/parts/player/PlaybackErrorPart.tsx +++ b/src/pages/parts/player/PlaybackErrorPart.tsx @@ -1,4 +1,4 @@ -import { Button } from "@/components/Button"; +import { Button } from "@/components/buttons/Button"; import { Icons } from "@/components/Icon"; import { IconPill } from "@/components/layout/IconPill"; import { Paragraph } from "@/components/text/Paragraph"; diff --git a/src/pages/parts/player/ScrapeErrorPart.tsx b/src/pages/parts/player/ScrapeErrorPart.tsx index 16c2afe9..f32f3bd1 100644 --- a/src/pages/parts/player/ScrapeErrorPart.tsx +++ b/src/pages/parts/player/ScrapeErrorPart.tsx @@ -1,6 +1,6 @@ import { useMemo } from "react"; -import { Button } from "@/components/Button"; +import { Button } from "@/components/buttons/Button"; import { Icons } from "@/components/Icon"; import { IconPill } from "@/components/layout/IconPill"; import { Paragraph } from "@/components/text/Paragraph"; diff --git a/src/pages/parts/player/ScrapingPart.tsx b/src/pages/parts/player/ScrapingPart.tsx index 57061f94..54a7afbd 100644 --- a/src/pages/parts/player/ScrapingPart.tsx +++ b/src/pages/parts/player/ScrapingPart.tsx @@ -7,7 +7,6 @@ import { scrapePartsToProviderMetric, useReportProviders, } from "@/backend/helpers/report"; -import { usePlayer } from "@/components/player/hooks/usePlayer"; import { ScrapeCard, ScrapeItem, diff --git a/src/pages/parts/settings/AccountActionsPart.tsx b/src/pages/parts/settings/AccountActionsPart.tsx index 2933a22e..b1a6df65 100644 --- a/src/pages/parts/settings/AccountActionsPart.tsx +++ b/src/pages/parts/settings/AccountActionsPart.tsx @@ -1,7 +1,7 @@ import { useAsyncFn } from "react-use"; import { deleteUser } from "@/backend/accounts/user"; -import { Button } from "@/components/Button"; +import { Button } from "@/components/buttons/Button"; import { SolidSettingsCard } from "@/components/layout/SettingsCard"; import { Modal, ModalCard, useModal } from "@/components/overlays/Modal"; import { Heading2, Heading3, Paragraph } from "@/components/utils/Text"; diff --git a/src/pages/parts/settings/AccountEditPart.tsx b/src/pages/parts/settings/AccountEditPart.tsx index a1c834a7..7c8e4549 100644 --- a/src/pages/parts/settings/AccountEditPart.tsx +++ b/src/pages/parts/settings/AccountEditPart.tsx @@ -1,5 +1,5 @@ import { Avatar } from "@/components/Avatar"; -import { Button } from "@/components/Button"; +import { Button } from "@/components/buttons/Button"; import { Icon, Icons } from "@/components/Icon"; import { SettingsCard } from "@/components/layout/SettingsCard"; import { useModal } from "@/components/overlays/Modal"; diff --git a/src/pages/parts/settings/CaptionsPart.tsx b/src/pages/parts/settings/CaptionsPart.tsx index 5d70443a..19867304 100644 --- a/src/pages/parts/settings/CaptionsPart.tsx +++ b/src/pages/parts/settings/CaptionsPart.tsx @@ -9,8 +9,8 @@ import { } from "@/components/player/atoms/settings/CaptionSettingsView"; import { Menu } from "@/components/player/internals/ContextMenu"; import { CaptionCue } from "@/components/player/Player"; -import { Transition } from "@/components/Transition"; import { Heading1 } from "@/components/utils/Text"; +import { Transition } from "@/components/utils/Transition"; import { SubtitleStyling } from "@/stores/subtitles"; export function CaptionPreview(props: { diff --git a/src/pages/parts/settings/ConnectionsPart.tsx b/src/pages/parts/settings/ConnectionsPart.tsx index 609ad209..0e395ebc 100644 --- a/src/pages/parts/settings/ConnectionsPart.tsx +++ b/src/pages/parts/settings/ConnectionsPart.tsx @@ -1,6 +1,6 @@ import { Dispatch, SetStateAction, useCallback } from "react"; -import { Button } from "@/components/Button"; +import { Button } from "@/components/buttons/Button"; import { Toggle } from "@/components/buttons/Toggle"; import { Icon, Icons } from "@/components/Icon"; import { SettingsCard } from "@/components/layout/SettingsCard"; diff --git a/src/pages/parts/settings/DeviceListPart.tsx b/src/pages/parts/settings/DeviceListPart.tsx index 1fc1ca68..2dfbea45 100644 --- a/src/pages/parts/settings/DeviceListPart.tsx +++ b/src/pages/parts/settings/DeviceListPart.tsx @@ -4,7 +4,7 @@ import { useAsyncFn } from "react-use"; import { SessionResponse } from "@/backend/accounts/auth"; import { base64ToBuffer, decryptData } from "@/backend/accounts/crypto"; import { removeSession } from "@/backend/accounts/sessions"; -import { Button } from "@/components/Button"; +import { Button } from "@/components/buttons/Button"; import { Loading } from "@/components/layout/Loading"; import { SettingsCard } from "@/components/layout/SettingsCard"; import { SecondaryLabel } from "@/components/text/SecondaryLabel"; diff --git a/src/pages/parts/settings/LocalePart.tsx b/src/pages/parts/settings/LocalePart.tsx index 93e9f7e9..9443e802 100644 --- a/src/pages/parts/settings/LocalePart.tsx +++ b/src/pages/parts/settings/LocalePart.tsx @@ -1,5 +1,5 @@ -import { Dropdown } from "@/components/Dropdown"; import { FlagIcon } from "@/components/FlagIcon"; +import { Dropdown } from "@/components/form/Dropdown"; import { Heading1 } from "@/components/utils/Text"; import { appLanguageOptions } from "@/setup/i18n"; import { sortLangCodes } from "@/utils/sortLangCodes"; @@ -8,14 +8,14 @@ export function LocalePart(props: { language: string; setLanguage: (l: string) => void; }) { - const sorted = sortLangCodes(appLanguageOptions.map((t) => t.id)); + const sorted = sortLangCodes(appLanguageOptions.map((t) => t.code)); const options = appLanguageOptions - .sort((a, b) => sorted.indexOf(a.id) - sorted.indexOf(b.id)) + .sort((a, b) => sorted.indexOf(a.code) - sorted.indexOf(b.code)) .map((opt) => ({ - id: opt.id, - name: `${opt.englishName} — ${opt.nativeName}`, - leftIcon: , + id: opt.code, + name: `${opt.name} — ${opt.nativeName}`, + leftIcon: , })); const selected = options.find((t) => t.id === props.language); diff --git a/src/pages/parts/settings/ProfileEditModal.tsx b/src/pages/parts/settings/ProfileEditModal.tsx index d2667c25..de067ec8 100644 --- a/src/pages/parts/settings/ProfileEditModal.tsx +++ b/src/pages/parts/settings/ProfileEditModal.tsx @@ -1,4 +1,4 @@ -import { Button } from "@/components/Button"; +import { Button } from "@/components/buttons/Button"; import { ColorPicker } from "@/components/form/ColorPicker"; import { IconPicker } from "@/components/form/IconPicker"; import { Modal, ModalCard } from "@/components/overlays/Modal"; diff --git a/src/pages/parts/settings/RegisterCalloutPart.tsx b/src/pages/parts/settings/RegisterCalloutPart.tsx index 9744876d..e34951f1 100644 --- a/src/pages/parts/settings/RegisterCalloutPart.tsx +++ b/src/pages/parts/settings/RegisterCalloutPart.tsx @@ -1,6 +1,6 @@ import { useHistory } from "react-router-dom"; -import { Button } from "@/components/Button"; +import { Button } from "@/components/buttons/Button"; import { SolidSettingsCard } from "@/components/layout/SettingsCard"; import { Heading3 } from "@/components/utils/Text"; diff --git a/src/setup/i18n.ts b/src/setup/i18n.ts index ccb77d22..84402df1 100644 --- a/src/setup/i18n.ts +++ b/src/setup/i18n.ts @@ -1,70 +1,27 @@ import i18n from "i18next"; +import ISO6391 from "iso-639-1"; import { initReactI18next } from "react-i18next"; +import { locales } from "@/assets/languages"; + // Languages -import { captionLanguages } from "./iso6391"; -import cs from "./locales/cs/translation.json"; -import de from "./locales/de/translation.json"; -import en from "./locales/en/translation.json"; -import fr from "./locales/fr/translation.json"; -import it from "./locales/it/translation.json"; -import nl from "./locales/nl/translation.json"; -import pirate from "./locales/pirate/translation.json"; -import pl from "./locales/pl/translation.json"; -import tr from "./locales/tr/translation.json"; -import vi from "./locales/vi/translation.json"; -import zh from "./locales/zh/translation.json"; +const langCodes = Object.keys(locales); +const resources = Object.fromEntries( + Object.entries(locales).map((entry) => [entry[0], { translation: entry[1] }]) +); +i18n.use(initReactI18next).init({ + fallbackLng: "en", + resources, + interpolation: { + escapeValue: false, // not needed for react as it escapes by default + }, +}); -const locales = { - en: { - translation: en, - }, - it: { - translation: it, - }, - nl: { - translation: nl, - }, - tr: { - translation: tr, - }, - fr: { - translation: fr, - }, - de: { - translation: de, - }, - zh: { - translation: zh, - }, - cs: { - translation: cs, - }, - pirate: { - translation: pirate, - }, - vi: { - translation: vi, - }, - pl: { - translation: pl, - }, -}; -i18n - // pass the i18n instance to react-i18next. - .use(initReactI18next) - // init i18next - // for all options read: https://www.i18next.com/overview/configuration-options - .init({ - fallbackLng: "en", - resources: locales, - interpolation: { - escapeValue: false, // not needed for react as it escapes by default - }, - }); - -export const appLanguageOptions = captionLanguages.filter((x) => { - return Object.keys(locales).includes(x.id); +export const appLanguageOptions = langCodes.map((lang) => { + const [langObj] = ISO6391.getLanguages([lang]); + if (!langObj) + throw new Error(`Language with code ${lang} cannot be found in database`); + return langObj; }); export default i18n; diff --git a/src/setup/iso6391.ts b/src/setup/iso6391.ts deleted file mode 100644 index 0ea4e817..00000000 --- a/src/setup/iso6391.ts +++ /dev/null @@ -1,1338 +0,0 @@ -export type LangCode = - | "none" - | "pirate" - | "aa" - | "ab" - | "ae" - | "af" - | "ak" - | "am" - | "an" - | "ar" - | "as" - | "av" - | "ay" - | "az" - | "ba" - | "be" - | "bg" - | "bh" - | "bi" - | "bm" - | "bn" - | "bo" - | "br" - | "bs" - | "ca" - | "ce" - | "ch" - | "co" - | "cr" - | "cs" - | "cu" - | "cv" - | "cy" - | "da" - | "de" - | "dv" - | "dz" - | "ee" - | "el" - | "en" - | "eo" - | "es" - | "et" - | "eu" - | "fa" - | "ff" - | "fi" - | "fj" - | "fo" - | "fr" - | "fy" - | "ga" - | "gd" - | "gl" - | "gn" - | "gu" - | "gv" - | "ha" - | "he" - | "hi" - | "ho" - | "hr" - | "ht" - | "hu" - | "hy" - | "hz" - | "ia" - | "id" - | "ie" - | "ig" - | "ii" - | "ik" - | "io" - | "is" - | "it" - | "iu" - | "ja" - | "jv" - | "ka" - | "kg" - | "ki" - | "kj" - | "kk" - | "kl" - | "km" - | "kn" - | "ko" - | "kr" - | "ks" - | "ku" - | "kv" - | "kw" - | "ky" - | "la" - | "lb" - | "lg" - | "li" - | "ln" - | "lo" - | "lt" - | "lu" - | "lv" - | "mg" - | "mh" - | "mi" - | "mk" - | "ml" - | "mn" - | "mr" - | "ms" - | "mt" - | "my" - | "na" - | "nb" - | "nd" - | "ne" - | "ng" - | "nl" - | "nn" - | "no" - | "nr" - | "nv" - | "ny" - | "oc" - | "oj" - | "om" - | "or" - | "os" - | "pa" - | "pi" - | "pl" - | "ps" - | "pt" - | "qu" - | "rm" - | "rn" - | "ro" - | "ru" - | "rw" - | "sa" - | "sc" - | "sd" - | "se" - | "sg" - | "si" - | "sk" - | "sl" - | "sm" - | "sn" - | "so" - | "sq" - | "sr" - | "ss" - | "st" - | "su" - | "sv" - | "sw" - | "ta" - | "te" - | "tg" - | "th" - | "ti" - | "tk" - | "tl" - | "tn" - | "to" - | "tr" - | "ts" - | "tt" - | "tw" - | "ty" - | "ug" - | "uk" - | "ur" - | "uz" - | "ve" - | "vi" - | "vo" - | "wa" - | "wo" - | "xh" - | "yi" - | "yo" - | "za" - | "zh" - | "zu"; -export type CaptionLanguageOption = { - id: LangCode; - name: string; - englishName: string; - nativeName: string; -}; -// https://github.com/emvi/iso-639-1/blob/master/list.go -// MIT License -// -// Copyright (c) 2019 Emvi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -export const captionLanguages: CaptionLanguageOption[] = [ - { - id: "none", - englishName: "None", - name: "None", - nativeName: "Lorem ipsum", - }, - { - id: "pirate", - englishName: "Pirate", - name: "Pirate English", - nativeName: "Pirate English", - }, - { - id: "aa", - englishName: "Afar", - name: "Afar - Afaraf", - nativeName: "Afaraf", - }, - { - id: "ab", - englishName: "Abkhaz", - name: "Abkhaz - Аҧсуа бызшәа", - nativeName: "Аҧсуа бызшәа", - }, - { - id: "ae", - englishName: "Avestan", - name: "Avestan - Avesta", - nativeName: "Avesta", - }, - { - id: "af", - englishName: "Afrikaans", - name: "Afrikaans - Afrikaans", - nativeName: "Afrikaans", - }, - { - id: "ak", - englishName: "Akan", - name: "Akan - Akan", - nativeName: "Akan", - }, - { - id: "am", - englishName: "Amharic", - name: "Amharic - አማርኛ", - nativeName: "አማርኛ", - }, - { - id: "an", - englishName: "Aragonese", - name: "Aragonese - Aragonés", - nativeName: "Aragonés", - }, - { - id: "ar", - englishName: "Arabic", - name: "Arabic - اللغة العربية", - nativeName: "اللغة العربية", - }, - { - id: "as", - englishName: "Assamese", - name: "Assamese - অসমীয়া", - nativeName: "অসমীয়া", - }, - { - id: "av", - englishName: "Avaric", - name: "Avaric - Авар мацӀ", - nativeName: "Авар мацӀ", - }, - { - id: "ay", - englishName: "Aymara", - name: "Aymara - Aymar aru", - nativeName: "Aymar aru", - }, - { - id: "az", - englishName: "Azerbaijani", - name: "Azerbaijani - Azərbaycan dili", - nativeName: "Azərbaycan dili", - }, - { - id: "ba", - englishName: "Bashkir", - name: "Bashkir - Башҡорт теле", - nativeName: "Башҡорт теле", - }, - { - id: "be", - englishName: "Belarusian", - name: "Belarusian - Беларуская мова", - nativeName: "Беларуская мова", - }, - { - id: "bg", - englishName: "Bulgarian", - name: "Bulgarian - Български език", - nativeName: "Български език", - }, - { - id: "bh", - englishName: "Bihari", - name: "Bihari - भोजपुरी", - nativeName: "भोजपुरी", - }, - { - id: "bi", - englishName: "Bislama", - name: "Bislama - Bislama", - nativeName: "Bislama", - }, - { - id: "bm", - englishName: "Bambara", - name: "Bambara - Bamanankan", - nativeName: "Bamanankan", - }, - { - id: "bn", - englishName: "Bengali", - name: "Bengali - বাংলা", - nativeName: "বাংলা", - }, - { - id: "bo", - englishName: "Tibetan Standard", - name: "Tibetan Standard - བོད་ཡིག", - nativeName: "བོད་ཡིག", - }, - { - id: "br", - englishName: "Breton", - name: "Breton - Brezhoneg", - nativeName: "Brezhoneg", - }, - { - id: "bs", - englishName: "Bosnian", - name: "Bosnian - Bosanski jezik", - nativeName: "Bosanski jezik", - }, - { - id: "ca", - englishName: "Catalan", - name: "Catalan - Català", - nativeName: "Català", - }, - { - id: "ce", - englishName: "Chechen", - name: "Chechen - Нохчийн мотт", - nativeName: "Нохчийн мотт", - }, - { - id: "ch", - englishName: "Chamorro", - name: "Chamorro - Chamoru", - nativeName: "Chamoru", - }, - { - id: "co", - englishName: "Corsican", - name: "Corsican - Corsu", - nativeName: "Corsu", - }, - { - id: "cr", - englishName: "Cree", - name: "Cree - ᓀᐦᐃᔭᐍᐏᐣ", - nativeName: "ᓀᐦᐃᔭᐍᐏᐣ", - }, - { - id: "cs", - englishName: "Czech", - name: "Czech - Čeština", - nativeName: "Čeština", - }, - { - id: "cu", - englishName: "Old Church Slavonic", - name: "Old Church Slavonic - Ѩзыкъ словѣньскъ", - nativeName: "Ѩзыкъ словѣньскъ", - }, - { - id: "cv", - englishName: "Chuvash", - name: "Chuvash - Чӑваш чӗлхи", - nativeName: "Чӑваш чӗлхи", - }, - { - id: "cy", - englishName: "Welsh", - name: "Welsh - Cymraeg", - nativeName: "Cymraeg", - }, - { - id: "da", - englishName: "Danish", - name: "Danish - Dansk", - nativeName: "Dansk", - }, - { - id: "de", - englishName: "German", - name: "German - Deutsch", - nativeName: "Deutsch", - }, - { - id: "dv", - englishName: "Divehi", - name: "Divehi - Dhivehi", - nativeName: "Dhivehi", - }, - { - id: "dz", - englishName: "Dzongkha", - name: "Dzongkha - རྫོང་ཁ", - nativeName: "རྫོང་ཁ", - }, - { - id: "ee", - englishName: "Ewe", - name: "Ewe - Eʋegbe", - nativeName: "Eʋegbe", - }, - { - id: "el", - englishName: "Greek", - name: "Greek - Ελληνικά", - nativeName: "Ελληνικά", - }, - { - id: "en", - englishName: "English", - name: "English - English", - nativeName: "English", - }, - { - id: "eo", - englishName: "Esperanto", - name: "Esperanto - Esperanto", - nativeName: "Esperanto", - }, - { - id: "es", - englishName: "Spanish", - name: "Spanish - Español", - nativeName: "Español", - }, - { - id: "et", - englishName: "Estonian", - name: "Estonian - Eesti", - nativeName: "Eesti", - }, - { - id: "eu", - englishName: "Basque", - name: "Basque - Euskara", - nativeName: "Euskara", - }, - { - id: "fa", - englishName: "Persian", - name: "Persian - فارسی", - nativeName: "فارسی", - }, - { - id: "ff", - englishName: "Fula", - name: "Fula - Fulfulde", - nativeName: "Fulfulde", - }, - { - id: "fi", - englishName: "Finnish", - name: "Finnish - Suomi", - nativeName: "Suomi", - }, - { - id: "fj", - englishName: "Fijian", - name: "Fijian - Vakaviti", - nativeName: "Vakaviti", - }, - { - id: "fo", - englishName: "Faroese", - name: "Faroese - Føroyskt", - nativeName: "Føroyskt", - }, - { - id: "fr", - englishName: "French", - name: "French - Français", - nativeName: "Français", - }, - { - id: "fy", - englishName: "Western Frisian", - name: "Western Frisian - Frysk", - nativeName: "Frysk", - }, - { - id: "ga", - englishName: "Irish", - name: "Irish - Gaeilge", - nativeName: "Gaeilge", - }, - { - id: "gd", - englishName: "Scottish Gaelic", - name: "Scottish Gaelic - Gàidhlig", - nativeName: "Gàidhlig", - }, - { - id: "gl", - englishName: "Galician", - name: "Galician - Galego", - nativeName: "Galego", - }, - { - id: "gn", - englishName: "Guaraní", - name: "Guaraní - Avañeẽ", - nativeName: "Avañeẽ", - }, - { - id: "gu", - englishName: "Gujarati", - name: "Gujarati - ગુજરાતી", - nativeName: "ગુજરાતી", - }, - { - id: "gv", - englishName: "Manx", - name: "Manx - Gaelg", - nativeName: "Gaelg", - }, - { - id: "ha", - englishName: "Hausa", - name: "Hausa - هَوُسَ", - nativeName: "هَوُسَ", - }, - { - id: "he", - englishName: "Hebrew", - name: "Hebrew - עברית", - nativeName: "עברית", - }, - { - id: "hi", - englishName: "Hindi", - name: "Hindi - हिन्दी", - nativeName: "हिन्दी", - }, - { - id: "ho", - englishName: "Hiri Motu", - name: "Hiri Motu - Hiri Motu", - nativeName: "Hiri Motu", - }, - { - id: "hr", - englishName: "Croatian", - name: "Croatian - Hrvatski jezik", - nativeName: "Hrvatski jezik", - }, - { - id: "ht", - englishName: "Haitian", - name: "Haitian - Kreyòl ayisyen", - nativeName: "Kreyòl ayisyen", - }, - { - id: "hu", - englishName: "Hungarian", - name: "Hungarian - Magyar", - nativeName: "Magyar", - }, - { - id: "hy", - englishName: "Armenian", - name: "Armenian - Հայերեն", - nativeName: "Հայերեն", - }, - { - id: "hz", - englishName: "Herero", - name: "Herero - Otjiherero", - nativeName: "Otjiherero", - }, - { - id: "ia", - englishName: "Interlingua", - name: "Interlingua - Interlingua", - nativeName: "Interlingua", - }, - { - id: "id", - englishName: "Indonesian", - name: "Indonesian - Indonesian", - nativeName: "Indonesian", - }, - { - id: "ie", - englishName: "Interlingue", - name: "Interlingue - Interlingue", - nativeName: "Interlingue", - }, - { - id: "ig", - englishName: "Igbo", - name: "Igbo - Asụsụ Igbo", - nativeName: "Asụsụ Igbo", - }, - { - id: "ii", - englishName: "Nuosu", - name: "Nuosu - ꆈꌠ꒿ Nuosuhxop", - nativeName: "ꆈꌠ꒿ Nuosuhxop", - }, - { - id: "ik", - englishName: "Inupiaq", - name: "Inupiaq - Iñupiaq", - nativeName: "Iñupiaq", - }, - { - id: "io", - englishName: "Ido", - name: "Ido - Ido", - nativeName: "Ido", - }, - { - id: "is", - englishName: "Icelandic", - name: "Icelandic - Íslenska", - nativeName: "Íslenska", - }, - { - id: "it", - englishName: "Italian", - name: "Italian - Italiano", - nativeName: "Italiano", - }, - { - id: "iu", - englishName: "Inuktitut", - name: "Inuktitut - ᐃᓄᒃᑎᑐᑦ", - nativeName: "ᐃᓄᒃᑎᑐᑦ", - }, - { - id: "ja", - englishName: "Japanese", - name: "Japanese - 日本語", - nativeName: "日本語", - }, - { - id: "jv", - englishName: "Javanese", - name: "Javanese - Basa Jawa", - nativeName: "Basa Jawa", - }, - { - id: "ka", - englishName: "Georgian", - name: "Georgian - Ქართული", - nativeName: "Ქართული", - }, - { - id: "kg", - englishName: "Kongo", - name: "Kongo - Kikongo", - nativeName: "Kikongo", - }, - { - id: "ki", - englishName: "Kikuyu", - name: "Kikuyu - Gĩkũyũ", - nativeName: "Gĩkũyũ", - }, - { - id: "kj", - englishName: "Kwanyama", - name: "Kwanyama - Kuanyama", - nativeName: "Kuanyama", - }, - { - id: "kk", - englishName: "Kazakh", - name: "Kazakh - Қазақ тілі", - nativeName: "Қазақ тілі", - }, - { - id: "kl", - englishName: "Kalaallisut", - name: "Kalaallisut - Kalaallisut", - nativeName: "Kalaallisut", - }, - { - id: "km", - englishName: "Khmer", - name: "Khmer - ខេមរភាសា", - nativeName: "ខេមរភាសា", - }, - { - id: "kn", - englishName: "Kannada", - name: "Kannada - ಕನ್ನಡ", - nativeName: "ಕನ್ನಡ", - }, - { - id: "ko", - englishName: "Korean", - name: "Korean - 한국어", - nativeName: "한국어", - }, - { - id: "kr", - englishName: "Kanuri", - name: "Kanuri - Kanuri", - nativeName: "Kanuri", - }, - { - id: "ks", - englishName: "Kashmiri", - name: "Kashmiri - कश्मीरी", - nativeName: "कश्मीरी", - }, - { - id: "ku", - englishName: "Kurdish", - name: "Kurdish - Kurdî", - nativeName: "Kurdî", - }, - { - id: "kv", - englishName: "Komi", - name: "Komi - Коми кыв", - nativeName: "Коми кыв", - }, - { - id: "kw", - englishName: "Cornish", - name: "Cornish - Kernewek", - nativeName: "Kernewek", - }, - { - id: "ky", - englishName: "Kyrgyz", - name: "Kyrgyz - Кыргызча", - nativeName: "Кыргызча", - }, - { - id: "la", - englishName: "Latin", - name: "Latin - Latine", - nativeName: "Latine", - }, - { - id: "lb", - englishName: "Luxembourgish", - name: "Luxembourgish - Lëtzebuergesch", - nativeName: "Lëtzebuergesch", - }, - { - id: "lg", - englishName: "Ganda", - name: "Ganda - Luganda", - nativeName: "Luganda", - }, - { - id: "li", - englishName: "Limburgish", - name: "Limburgish - Limburgs", - nativeName: "Limburgs", - }, - { - id: "ln", - englishName: "Lingala", - name: "Lingala - Lingála", - nativeName: "Lingála", - }, - { - id: "lo", - englishName: "Lao", - name: "Lao - ພາສາ", - nativeName: "ພາສາ", - }, - { - id: "lt", - englishName: "Lithuanian", - name: "Lithuanian - Lietuvių kalba", - nativeName: "Lietuvių kalba", - }, - { - id: "lu", - englishName: "Luba-Katanga", - name: "Luba-Katanga - Tshiluba", - nativeName: "Tshiluba", - }, - { - id: "lv", - englishName: "Latvian", - name: "Latvian - Latviešu valoda", - nativeName: "Latviešu valoda", - }, - { - id: "mg", - englishName: "Malagasy", - name: "Malagasy - Fiteny malagasy", - nativeName: "Fiteny malagasy", - }, - { - id: "mh", - englishName: "Marshallese", - name: "Marshallese - Kajin M̧ajeļ", - nativeName: "Kajin M̧ajeļ", - }, - { - id: "mi", - englishName: "Māori", - name: "Māori - Te reo Māori", - nativeName: "Te reo Māori", - }, - { - id: "mk", - englishName: "Macedonian", - name: "Macedonian - Македонски јазик", - nativeName: "Македонски јазик", - }, - { - id: "ml", - englishName: "Malayalam", - name: "Malayalam - മലയാളം", - nativeName: "മലയാളം", - }, - { - id: "mn", - englishName: "Mongolian", - name: "Mongolian - Монгол хэл", - nativeName: "Монгол хэл", - }, - { - id: "mr", - englishName: "Marathi", - name: "Marathi - मराठी", - nativeName: "मराठी", - }, - { - id: "ms", - englishName: "Malay", - name: "Malay - هاس ملايو‎", - nativeName: "هاس ملايو‎", - }, - { - id: "mt", - englishName: "Maltese", - name: "Maltese - Malti", - nativeName: "Malti", - }, - { - id: "my", - englishName: "Burmese", - name: "Burmese - ဗမာစာ", - nativeName: "ဗမာစာ", - }, - { - id: "na", - englishName: "Nauru", - name: "Nauru - Ekakairũ Naoero", - nativeName: "Ekakairũ Naoero", - }, - { - id: "nb", - englishName: "Norwegian Bokmål", - name: "Norwegian Bokmål - Norsk bokmål", - nativeName: "Norsk bokmål", - }, - { - id: "nd", - englishName: "Northern Ndebele", - name: "Northern Ndebele - IsiNdebele", - nativeName: "IsiNdebele", - }, - { - id: "ne", - englishName: "Nepali", - name: "Nepali - नेपाली", - nativeName: "नेपाली", - }, - { - id: "ng", - englishName: "Ndonga", - name: "Ndonga - Owambo", - nativeName: "Owambo", - }, - { - id: "nl", - englishName: "Dutch", - name: "Dutch - Nederlands", - nativeName: "Nederlands", - }, - { - id: "nn", - englishName: "Norwegian Nynorsk", - name: "Norwegian Nynorsk - Norsk nynorsk", - nativeName: "Norsk nynorsk", - }, - { - id: "no", - englishName: "Norwegian", - name: "Norwegian - Norsk", - nativeName: "Norsk", - }, - { - id: "nr", - englishName: "Southern Ndebele", - name: "Southern Ndebele - IsiNdebele", - nativeName: "IsiNdebele", - }, - { - id: "nv", - englishName: "Navajo", - name: "Navajo - Diné bizaad", - nativeName: "Diné bizaad", - }, - { - id: "ny", - englishName: "Chichewa", - name: "Chichewa - ChiCheŵa", - nativeName: "ChiCheŵa", - }, - { - id: "oc", - englishName: "Occitan", - name: "Occitan - Occitan", - nativeName: "Occitan", - }, - { - id: "oj", - englishName: "Ojibwe", - name: "Ojibwe - ᐊᓂᔑᓈᐯᒧᐎᓐ", - nativeName: "ᐊᓂᔑᓈᐯᒧᐎᓐ", - }, - { - id: "om", - englishName: "Oromo", - name: "Oromo - Afaan Oromoo", - nativeName: "Afaan Oromoo", - }, - { - id: "or", - englishName: "Oriya", - name: "Oriya - ଓଡ଼ିଆ", - nativeName: "ଓଡ଼ିଆ", - }, - { - id: "os", - englishName: "Ossetian", - name: "Ossetian - Ирон æвзаг", - nativeName: "Ирон æвзаг", - }, - { - id: "pa", - englishName: "Panjabi", - name: "Panjabi - ਪੰਜਾਬੀ", - nativeName: "ਪੰਜਾਬੀ", - }, - { - id: "pi", - englishName: "Pāli", - name: "Pāli - पाऴि", - nativeName: "पाऴि", - }, - { - id: "pl", - englishName: "Polish", - name: "Polish - Język polski", - nativeName: "Język polski", - }, - { - id: "ps", - englishName: "Pashto", - name: "Pashto - پښتو", - nativeName: "پښتو", - }, - { - id: "pt", - englishName: "Portuguese", - name: "Portuguese - Português", - nativeName: "Português", - }, - { - id: "qu", - englishName: "Quechua", - name: "Quechua - Runa Simi", - nativeName: "Runa Simi", - }, - { - id: "rm", - englishName: "Romansh", - name: "Romansh - Rumantsch grischun", - nativeName: "Rumantsch grischun", - }, - { - id: "rn", - englishName: "Kirundi", - name: "Kirundi - Ikirundi", - nativeName: "Ikirundi", - }, - { - id: "ro", - englishName: "Romanian", - name: "Romanian - Română", - nativeName: "Română", - }, - { - id: "ru", - englishName: "Russian", - name: "Russian - Русский", - nativeName: "Русский", - }, - { - id: "rw", - englishName: "Kinyarwanda", - name: "Kinyarwanda - Ikinyarwanda", - nativeName: "Ikinyarwanda", - }, - { - id: "sa", - englishName: "Sanskrit", - name: "Sanskrit - संस्कृतम्", - nativeName: "संस्कृतम्", - }, - { - id: "sc", - englishName: "Sardinian", - name: "Sardinian - Sardu", - nativeName: "Sardu", - }, - { - id: "sd", - englishName: "Sindhi", - name: "Sindhi - सिन्धी", - nativeName: "सिन्धी", - }, - { - id: "se", - englishName: "Northern Sami", - name: "Northern Sami - Davvisámegiella", - nativeName: "Davvisámegiella", - }, - { - id: "sg", - englishName: "Sango", - name: "Sango - Yângâ tî sängö", - nativeName: "Yângâ tî sängö", - }, - { - id: "si", - englishName: "Sinhala", - name: "Sinhala - සිංහල", - nativeName: "සිංහල", - }, - { - id: "sk", - englishName: "Slovak", - name: "Slovak - Slovenčina", - nativeName: "Slovenčina", - }, - { - id: "sl", - englishName: "Slovene", - name: "Slovene - Slovenski jezik", - nativeName: "Slovenski jezik", - }, - { - id: "sm", - englishName: "Samoan", - name: "Samoan - Gagana faa Samoa", - nativeName: "Gagana faa Samoa", - }, - { - id: "sn", - englishName: "Shona", - name: "Shona - ChiShona", - nativeName: "ChiShona", - }, - { - id: "so", - englishName: "Somali", - name: "Somali - Soomaaliga", - nativeName: "Soomaaliga", - }, - { - id: "sq", - englishName: "Albanian", - name: "Albanian - Shqip", - nativeName: "Shqip", - }, - { - id: "sr", - englishName: "Serbian", - name: "Serbian - Српски језик", - nativeName: "Српски језик", - }, - { - id: "ss", - englishName: "Swati", - name: "Swati - SiSwati", - nativeName: "SiSwati", - }, - { - id: "st", - englishName: "Southern Sotho", - name: "Southern Sotho - Sesotho", - nativeName: "Sesotho", - }, - { - id: "su", - englishName: "Sundanese", - name: "Sundanese - Basa Sunda", - nativeName: "Basa Sunda", - }, - { - id: "sv", - englishName: "Swedish", - name: "Swedish - Svenska", - nativeName: "Svenska", - }, - { - id: "sw", - englishName: "Swahili", - name: "Swahili - Kiswahili", - nativeName: "Kiswahili", - }, - { - id: "ta", - englishName: "Tamil", - name: "Tamil - தமிழ்", - nativeName: "தமிழ்", - }, - { - id: "te", - englishName: "Telugu", - name: "Telugu - తెలుగు", - nativeName: "తెలుగు", - }, - { - id: "tg", - englishName: "Tajik", - name: "Tajik - Тоҷикӣ", - nativeName: "Тоҷикӣ", - }, - { - id: "th", - englishName: "Thai", - name: "Thai - ไทย", - nativeName: "ไทย", - }, - { - id: "ti", - englishName: "Tigrinya", - name: "Tigrinya - ትግርኛ", - nativeName: "ትግርኛ", - }, - { - id: "tk", - englishName: "Turkmen", - name: "Turkmen - Türkmen", - nativeName: "Türkmen", - }, - { - id: "tl", - englishName: "Tagalog", - name: "Tagalog - Wikang Tagalog", - nativeName: "Wikang Tagalog", - }, - { - id: "tn", - englishName: "Tswana", - name: "Tswana - Setswana", - nativeName: "Setswana", - }, - { - id: "to", - englishName: "Tonga", - name: "Tonga - Faka Tonga", - nativeName: "Faka Tonga", - }, - { - id: "tr", - englishName: "Turkish", - name: "Turkish - Türkçe", - nativeName: "Türkçe", - }, - { - id: "ts", - englishName: "Tsonga", - name: "Tsonga - Xitsonga", - nativeName: "Xitsonga", - }, - { - id: "tt", - englishName: "Tatar", - name: "Tatar - Татар теле", - nativeName: "Татар теле", - }, - { - id: "tw", - englishName: "Twi", - name: "Twi - Twi", - nativeName: "Twi", - }, - { - id: "ty", - englishName: "Tahitian", - name: "Tahitian - Reo Tahiti", - nativeName: "Reo Tahiti", - }, - { - id: "ug", - englishName: "Uyghur", - name: "Uyghur - ئۇيغۇرچە‎", - nativeName: "ئۇيغۇرچە‎", - }, - { - id: "uk", - englishName: "Ukrainian", - name: "Ukrainian - Українська", - nativeName: "Українська", - }, - { - id: "ur", - englishName: "Urdu", - name: "Urdu - اردو", - nativeName: "اردو", - }, - { - id: "uz", - englishName: "Uzbek", - name: "Uzbek - Ўзбек", - nativeName: "Ўзбек", - }, - { - id: "ve", - englishName: "Venda", - name: "Venda - Tshivenḓa", - nativeName: "Tshivenḓa", - }, - { - id: "vi", - englishName: "Vietnamese", - name: "Vietnamese - Tiếng Việt", - nativeName: "Tiếng Việt", - }, - { - id: "vo", - englishName: "Volapük", - name: "Volapük - Volapük", - nativeName: "Volapük", - }, - { - id: "wa", - englishName: "Walloon", - name: "Walloon - Walon", - nativeName: "Walon", - }, - { - id: "wo", - englishName: "Wolof", - name: "Wolof - Wollof", - nativeName: "Wollof", - }, - { - id: "xh", - englishName: "Xhosa", - name: "Xhosa - IsiXhosa", - nativeName: "IsiXhosa", - }, - { - id: "yi", - englishName: "Yiddish", - name: "Yiddish - ייִדיש", - nativeName: "ייִדיש", - }, - { - id: "yo", - englishName: "Yoruba", - name: "Yoruba - Yorùbá", - nativeName: "Yorùbá", - }, - { - id: "za", - englishName: "Zhuang", - name: "Zhuang - Saɯ cueŋƅ", - nativeName: "Saɯ cueŋƅ", - }, - { - id: "zh", - englishName: "Chinese", - name: "Chinese - 中文", - nativeName: "中文", - }, - { - id: "zu", - englishName: "Zulu", - name: "Zulu - IsiZulu", - nativeName: "IsiZulu", - }, -]; - -export const languageMap: Record = {}; -captionLanguages.forEach((v) => { - languageMap[v.id] = v; -}); diff --git a/src/stores/__old/settings/types.ts b/src/stores/__old/settings/types.ts index c894be9c..d70cc29e 100644 --- a/src/stores/__old/settings/types.ts +++ b/src/stores/__old/settings/types.ts @@ -1,5 +1,3 @@ -import { LangCode } from "@/setup/iso6391"; - export interface CaptionStyleSettings { color: string; /** @@ -18,7 +16,7 @@ export interface CaptionSettingsV1 { } export interface CaptionSettings { - language: LangCode; + language: string; /** * Range is [-10, 10]s */ @@ -26,11 +24,11 @@ export interface CaptionSettings { style: CaptionStyleSettings; } export interface MWSettingsDataV1 { - language: LangCode; + language: string; captionSettings: CaptionSettingsV1; } export interface MWSettingsData { - language: LangCode; + language: string; captionSettings: CaptionSettings; } diff --git a/src/stores/__old/utils.ts b/src/stores/__old/utils.ts new file mode 100644 index 00000000..dfdf3883 --- /dev/null +++ b/src/stores/__old/utils.ts @@ -0,0 +1,11 @@ +function normalizeTitle(title: string): string { + return title + .trim() + .toLowerCase() + .replace(/['":]/g, "") + .replace(/[^a-zA-Z0-9]+/g, "_"); +} + +export function compareTitle(a: string, b: string): boolean { + return normalizeTitle(a) === normalizeTitle(b); +} diff --git a/src/stores/__old/watched/migrations/v2.ts b/src/stores/__old/watched/migrations/v2.ts index 6a11d589..f3f79c71 100644 --- a/src/stores/__old/watched/migrations/v2.ts +++ b/src/stores/__old/watched/migrations/v2.ts @@ -2,7 +2,7 @@ import { DetailedMeta, getMetaFromId } from "@/backend/metadata/getmeta"; import { searchForMedia } from "@/backend/metadata/search"; import { mediaItemTypeToMediaType } from "@/backend/metadata/tmdb"; import { MWMediaMeta, MWMediaType } from "@/backend/metadata/types/mw"; -import { compareTitle } from "@/utils/titleMatch"; +import { compareTitle } from "@/stores/__old/utils"; import { WatchedStoreData, WatchedStoreItem } from "../types"; diff --git a/src/stores/banner/BannerLocation.tsx b/src/stores/banner/BannerLocation.tsx index df2fbbf8..8834f0c2 100644 --- a/src/stores/banner/BannerLocation.tsx +++ b/src/stores/banner/BannerLocation.tsx @@ -55,7 +55,7 @@ export function BannerLocation(props: { location?: string }) {
{!isOnline ? ( - {t("errors.offline")} + {t("navigation.banner.offline")} ) : null}
diff --git a/src/utils/normalizeTitle.ts b/src/utils/normalizeTitle.ts deleted file mode 100644 index f21a7edb..00000000 --- a/src/utils/normalizeTitle.ts +++ /dev/null @@ -1,7 +0,0 @@ -export function normalizeTitle(title: string): string { - return title - .trim() - .toLowerCase() - .replace(/['":]/g, "") - .replace(/[^a-zA-Z0-9]+/g, "_"); -} diff --git a/src/utils/titleMatch.ts b/src/utils/titleMatch.ts deleted file mode 100644 index 5be7be94..00000000 --- a/src/utils/titleMatch.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { normalizeTitle } from "./normalizeTitle"; - -export function compareTitle(a: string, b: string): boolean { - return normalizeTitle(a) === normalizeTitle(b); -}