diff --git a/src/assets/locales/en.json b/src/assets/locales/en.json
index 8aabe71b..c8be940e 100644
--- a/src/assets/locales/en.json
+++ b/src/assets/locales/en.json
@@ -72,13 +72,25 @@
"title": "Goo goo gaa gaa",
"text": "Oh, my apowogies, sweetie! The itty-bitty movie-web did its utmost bestest, but alas, no wucky videos to be spotted anywhere (ยดโฯโ`) Please don't be angwy, wittle movie-web ish twying so hard. Can you find it in your heart to forgive? UwU ๐",
"homeButton": "Go home"
+ },
+ "items": {
+ "pending": "Checking for videos...",
+ "notFound": "Doesn't have the video",
+ "failure": "Error occured"
}
},
"playbackError": {
"badge": "Not found",
"title": "Goo goo gaa gaa",
"text": "Oh, my apowogies, sweetie! The itty-bitty movie-web did its utmost bestest, but alas, no wucky videos to be spotted anywhere (ยดโฯโ`) Please don't be angwy, wittle movie-web ish twying so hard. Can you find it in your heart to forgive? UwU ๐",
- "homeButton": "Go home"
+ "homeButton": "Go home",
+ "errors": {
+ "errorAborted": "The fetching of the associated resource was aborted by the user's request.",
+ "errorNetwork": "Some kind of network error occurred which prevented the media from being successfully fetched, despite having previously been available.",
+ "errorDecode": "Despite having previously been determined to be usable, an error occurred while trying to decode the media resource, resulting in an error.",
+ "errorNotSupported": "The associated resource or media provider object has been found to be unsuitable.",
+ "errorGenericMedia": "Unknown media error occured"
+ }
},
"metadata": {
"notFound": {
@@ -93,6 +105,97 @@
"text": "Oh, my apowogies, sweetie! The itty-bitty movie-web did its utmost bestest, but alas, no wucky videos to be spotted anywhere (ยดโฯโ`) Please don't be angwy, wittle movie-web ish twying so hard. Can you find it in your heart to forgive? UwU ๐",
"homeButton": "Go home"
}
+ },
+ "back": {
+ "default": "Back to home",
+ "short": "Back"
+ },
+ "time": {
+ "short": "-{{timeLeft}}",
+ "regular": "{{timeWatched}} / {{duration}}",
+ "remaining": "{{timeLeft}} left โข Finish at {{timeFinished, datetime}}"
+ },
+ "nextEpisode": {
+ "next": "Next episode",
+ "cancel": "Cancel"
+ },
+ "menus": {
+ "settings": {
+ "videoSection": "Video settings",
+ "experienceSection": "Viewing Experience",
+ "enableCaptions": "Enable Captions",
+ "captionItem": "Caption settings",
+ "sourceItem": "Video sources",
+ "playbackItem": "Playback settings",
+ "downloadItem": "Download",
+ "qualityItem": "Quality"
+ },
+ "episodes": {
+ "button": "Episodes",
+ "loadingTitle": "Loading...",
+ "loadingList": "Loading...",
+ "loadingError": "Error loading season",
+ "emptyState": "There are no episodes in this season, check back later!",
+ "episodeBadge": "E{{episode}}"
+ },
+ "sources": {
+ "title": "Sources",
+ "unknownOption": "Unknown",
+ "noStream": {
+ "title": "No stream",
+ "text": "This source has no streams for this movie or show."
+ },
+ "noEmbeds": {
+ "title": "No embeds found",
+ "text": "We were unable to find any embeds for this source, please try another."
+ },
+ "failed": {
+ "title": "Failed to scrape",
+ "text": "We were unable to find any videos for this source. Don't come bitchin' to us about it, just try another source."
+ }
+ },
+ "captions": {
+ "title": "Captions",
+ "customizeLabel": "Customize",
+ "settings": {
+ "fixCapitals": "Fix capitalization",
+ "delay": "Caption delay"
+ },
+ "customChoice": "Upload captions",
+ "offChoice": "Off",
+ "unknownLanguage": "Unknown"
+ },
+ "downloads": {
+ "title": "Download",
+ "disclaimer": "Downloads are taken directly from the provider. movie-web does not have control over how the downloads are provided.",
+ "hlsExplanation": "Insert explanation for why you can't download HLS here",
+ "downloadVideo": "Download video",
+ "downloadCaption": "Download current caption",
+ "onPc": {
+ "title": "Downloading on PC",
+ "shortTitle": "Download / PC",
+ "1": "On PC, right click the video and select Save video as"
+ },
+ "onAndroid": {
+ "title": "Downloading on Android",
+ "shortTitle": "Download / Android",
+ "1": "To download on Android, tap and hold on the video, then select save."
+ },
+ "onIos": {
+ "title": "Downloading on iOS",
+ "shortTitle": "Download / iOS",
+ "1": "To download on iOS, click , then Save to Files . All that's left to do now is to pick a nice and cozy folder for your video!"
+ }
+ },
+ "playback": {
+ "title": "Playback settings",
+ "speedLabel": "Playback speed"
+ },
+ "quality": {
+ "title": "Quality",
+ "automaticLabel": "Automatic quality",
+ "hint": "You can try <0>switching source0> to get different quality options."
+ }
}
},
"home": {
diff --git a/src/components/player/atoms/EpisodeTitle.tsx b/src/components/player/atoms/EpisodeTitle.tsx
index f3cefcef..b36909b1 100644
--- a/src/components/player/atoms/EpisodeTitle.tsx
+++ b/src/components/player/atoms/EpisodeTitle.tsx
@@ -11,7 +11,7 @@ export function EpisodeTitle() {
return (
router.navigate("/download/pc")}>
- Downloading on PC
+ {t("player.menus.downloads.onPc.title")}
router.navigate("/download/ios")}>
- Downloading on iOS
+ {t("player.menus.downloads.onIos.title")}
router.navigate("/download/android")}
>
- Downloading on Android
+ {t("player.menus.downloads.onAndroid.title")}
- Downloads are taken directly from the provider. movie-web does not
- have control over how the downloads are provided.
+
@@ -80,15 +98,16 @@ export function DownloadView({ id }: { id: string }) {
export function CantDownloadView({ id }: { id: string }) {
const router = useOverlayRouter(id);
+ const { t } = useTranslation();
return (
<>
router.navigate("/")}>
- Download
+ {t("player.menus.downloads.title")}
- Insert explanation for why you can't download HLS here
+
>
@@ -97,16 +116,16 @@ export function CantDownloadView({ id }: { id: string }) {
function AndroidExplanationView({ id }: { id: string }) {
const router = useOverlayRouter(id);
+ const { t } = useTranslation();
return (
<>
router.navigate("/download")}>
- Download / Android
+ {t("player.menus.downloads.onAndroid.shortTitle")}
- To download on Android, tap and hold{" "}
- on the video, then select save.
+
>
@@ -115,16 +134,16 @@ function AndroidExplanationView({ id }: { id: string }) {
function PCExplanationView({ id }: { id: string }) {
const router = useOverlayRouter(id);
+ const { t } = useTranslation();
return (
<>
router.navigate("/download")}>
- Download / PC
+ {t("player.menus.downloads.onPc.shortTitle")}
- On PC, right click the video and select{" "}
- Save video as
+
>
@@ -137,27 +156,11 @@ function IOSExplanationView({ id }: { id: string }) {
return (
<>
router.navigate("/download")}>
- Download / iOS
+
- To download on iOS, click{" "}
-
-
-
- , then{" "}
-
- Save to Files
-
- {" "}
- . All that's left to do now is to pick a nice and cozy folder for
- your video!
+
>
diff --git a/src/components/player/atoms/settings/PlaybackSettingsView.tsx b/src/components/player/atoms/settings/PlaybackSettingsView.tsx
index c71c5627..23bec683 100644
--- a/src/components/player/atoms/settings/PlaybackSettingsView.tsx
+++ b/src/components/player/atoms/settings/PlaybackSettingsView.tsx
@@ -1,5 +1,6 @@
import classNames from "classnames";
import { useCallback } from "react";
+import { useTranslation } from "react-i18next";
import { Menu } from "@/components/player/internals/ContextMenu";
import { useOverlayRouter } from "@/hooks/useOverlayRouter";
@@ -34,6 +35,7 @@ function ButtonList(props: {
}
export function PlaybackSettingsView({ id }: { id: string }) {
+ const { t } = useTranslation();
const router = useOverlayRouter(id);
const playbackRate = usePlayerStore((s) => s.mediaPlaying.playbackRate);
const display = usePlayerStore((s) => s.display);
@@ -50,11 +52,13 @@ export function PlaybackSettingsView({ id }: { id: string }) {
return (
<>
router.navigate("/")}>
- Playback settings
+ {t("player.menus.playback.title")}
- Playback speed
+
+ {t("player.menus.playback.speedLabel")}
+ router.navigate("/")}>
- Quality
+ {t("player.menus.quality.title")}
{visibleQualities.map((v) => (
@@ -76,14 +78,14 @@ export function QualityView({ id }: { id: string }) {
}
>
- Automatic quality
+ {t("player.menus.quality.automaticLabel")}
- You can try{" "}
- router.navigate("/source")}>
- switching source
- {" "}
- to get different quality options.
+
+ router.navigate("/source")}>
+ text
+
+
>
diff --git a/src/components/player/atoms/settings/SettingsMenu.tsx b/src/components/player/atoms/settings/SettingsMenu.tsx
index 5ecd8aec..4ab971ff 100644
--- a/src/components/player/atoms/settings/SettingsMenu.tsx
+++ b/src/components/player/atoms/settings/SettingsMenu.tsx
@@ -1,4 +1,5 @@
import { useMemo } from "react";
+import { useTranslation } from "react-i18next";
import { Toggle } from "@/components/buttons/Toggle";
import { Icon, Icons } from "@/components/Icon";
@@ -12,6 +13,7 @@ import { useSubtitleStore } from "@/stores/subtitles";
import { providers } from "@/utils/providers";
export function SettingsMenu({ id }: { id: string }) {
+ const { t } = useTranslation();
const router = useOverlayRouter(id);
const currentQuality = usePlayerStore((s) => s.currentQuality);
const selectedCaptionLanguage = usePlayerStore(
@@ -26,26 +28,29 @@ export function SettingsMenu({ id }: { id: string }) {
const { toggleLastUsed } = useCaptions();
const selectedLanguagePretty = selectedCaptionLanguage
- ? getLanguageFromIETF(selectedCaptionLanguage) ?? "unknown"
+ ? getLanguageFromIETF(selectedCaptionLanguage) ??
+ t("player.menus.captions.unknownLanguage")
: undefined;
const source = usePlayerStore((s) => s.source);
return (
- Video settings
+
+ {t("player.menus.settings.videoSection")}
+ router.navigate("/quality")}
rightText={currentQuality ? qualityToString(currentQuality) : ""}
>
- Quality
+ {t("player.menus.settings.qualityItem")}
router.navigate("/source")}
rightText={sourceName}
>
- Video source
+ {t("player.menus.settings.sourceItem")}
}
className={source?.type === "file" ? "opacity-100" : "opacity-50"}
>
- Download
+ {t("player.menus.settings.downloadItem")}
- Viewing Experience
+
+ {t("player.menus.settings.experienceSection")}
+
}
>
- Enable Captions
+ {t("player.menus.settings.enableCaptions")}
router.navigate("/captions")}
- rightText={selectedLanguagePretty}
+ rightText={selectedLanguagePretty ?? undefined}
>
- Caption settings
+ {t("player.menus.settings.captionItem")}
router.navigate("/playback")}>
- Playback settings
+ {t("player.menus.settings.playbackItem")}
diff --git a/src/components/player/atoms/settings/SourceSelectingView.tsx b/src/components/player/atoms/settings/SourceSelectingView.tsx
index 644c573c..83c08914 100644
--- a/src/components/player/atoms/settings/SourceSelectingView.tsx
+++ b/src/components/player/atoms/settings/SourceSelectingView.tsx
@@ -1,4 +1,5 @@
import { ReactNode, useEffect, useMemo, useRef } from "react";
+import { useTranslation } from "react-i18next";
import { Loading } from "@/components/layout/Loading";
import {
@@ -27,13 +28,14 @@ export function EmbedOption(props: {
sourceId: string;
routerId: string;
}) {
- const unknownEmbedName = "Unknown";
+ const { t } = useTranslation();
+ const unknownEmbedName = t("player.menus.sources.unknownOption");
const embedName = useMemo(() => {
if (!props.embedId) return unknownEmbedName;
const sourceMeta = providers.getMetadata(props.embedId);
return sourceMeta?.name ?? unknownEmbedName;
- }, [props.embedId]);
+ }, [props.embedId, unknownEmbedName]);
const { run, errored, loading } = useEmbedScraping(
props.routerId,
@@ -52,6 +54,7 @@ export function EmbedOption(props: {
}
export function EmbedSelectionView({ sourceId, id }: EmbedSelectionViewProps) {
+ const { t } = useTranslation();
const router = useOverlayRouter(id);
const { run, watching, notfound, loading, items, errored } =
useSourceScraping(sourceId, id);
@@ -79,21 +82,26 @@ export function EmbedSelectionView({ sourceId, id }: EmbedSelectionViewProps) {
);
else if (notfound)
content = (
-
- This source has no streams for this movie or show.
+
+ {t("player.menus.sources.noStream.text")}
);
else if (items?.length === 0)
content = (
-
- We were unable to find any embeds for this source, please try another.
+
+ {t("player.menus.sources.noEmbeds.text")}
);
else if (errored)
content = (
-
- We were unable to find any videos for this source. Don't come
- bitchin' to us about it, just try another source.
+
+ {t("player.menus.sources.failed.text")}
);
else if (watching)
@@ -123,6 +131,7 @@ export function SourceSelectionView({
id,
onChoose,
}: SourceSelectionViewProps) {
+ const { t } = useTranslation();
const router = useOverlayRouter(id);
const metaType = usePlayerStore((s) => s.meta?.type);
const currentSourceId = usePlayerStore((s) => s.sourceId);
@@ -136,7 +145,7 @@ export function SourceSelectionView({
return (
<>
router.navigate("/")}>
- Sources
+ {t("player.menus.sources.title")}
{sources.map((v) => (
diff --git a/src/components/player/base/BackLink.tsx b/src/components/player/base/BackLink.tsx
index 6e42f1f4..714520ee 100644
--- a/src/components/player/base/BackLink.tsx
+++ b/src/components/player/base/BackLink.tsx
@@ -15,8 +15,8 @@ export function BackLink(props: { url: string }) {
className="py-1 -my-1 px-2 -mx-2 tabbable rounded-lg flex items-center cursor-pointer text-type-secondary hover:text-white transition-colors duration-200 font-medium"
>
- {t("videoPlayer.backToHomeShort")}
- {t("videoPlayer.backToHome")}
+ {t("player.back.short")}
+ {t("player.back.default")}