mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-01-19 01:28:06 +00:00
feat: use l18n for chromecast player
This commit is contained in:
@@ -1,48 +1,27 @@
|
||||
import React, { useMemo, useRef, useState } from "react";
|
||||
import { TouchableOpacity, View } from "react-native";
|
||||
import React, { useMemo } from "react";
|
||||
import { View } from "react-native";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { Loader } from "@/components/Loader";
|
||||
import { Button } from "@/components/Button";
|
||||
import { Feather, Ionicons } from "@expo/vector-icons";
|
||||
import { Feather } from "@expo/vector-icons";
|
||||
import { RoundButton } from "@/components/RoundButton";
|
||||
|
||||
import GoogleCast, {
|
||||
CastButton,
|
||||
CastContext,
|
||||
CastState,
|
||||
MediaInfo,
|
||||
MediaStatus,
|
||||
RemoteMediaClient,
|
||||
useCastDevice,
|
||||
useCastState,
|
||||
useDevices,
|
||||
useMediaStatus,
|
||||
useRemoteMediaClient,
|
||||
useStreamPosition,
|
||||
} from "react-native-google-cast";
|
||||
import { useCallback, useEffect } from "react";
|
||||
import { Platform } from "react-native";
|
||||
import { Image } from "expo-image";
|
||||
import { useRouter } from "expo-router";
|
||||
import { Slider } from "react-native-awesome-slider";
|
||||
import {
|
||||
runOnJS,
|
||||
useAnimatedReaction,
|
||||
useSharedValue,
|
||||
} from "react-native-reanimated";
|
||||
import { debounce } from "lodash";
|
||||
import { useSettings } from "@/utils/atoms/settings";
|
||||
import { useHaptic } from "@/hooks/useHaptic";
|
||||
import { writeToLog } from "@/utils/log";
|
||||
import { formatTimeString } from "@/utils/time";
|
||||
import { BlurView } from "expo-blur";
|
||||
import SkipButton from "@/components/video-player/controls/SkipButton";
|
||||
import NextEpisodeCountDownButton from "@/components/video-player/controls/NextEpisodeCountDownButton";
|
||||
import { useIntroSkipper } from "@/hooks/useIntroSkipper";
|
||||
import { useCreditSkipper } from "@/hooks/useCreditSkipper";
|
||||
import { useAdjacentItems } from "@/hooks/useAdjacentEpisodes";
|
||||
import { secondsToTicks } from "@/utils/secondsToTicks";
|
||||
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client";
|
||||
import ChromecastControls from "@/components/ChromecastControls";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function Player() {
|
||||
const castState = useCastState();
|
||||
@@ -58,6 +37,8 @@ export default function Player() {
|
||||
|
||||
const lightHapticFeedback = useHaptic("light");
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
if (!discoveryManager) {
|
||||
@@ -80,16 +61,24 @@ export default function Player() {
|
||||
[Platform.OS]
|
||||
);
|
||||
|
||||
const GoHomeButton = () => (
|
||||
<Button
|
||||
onPress={() => {
|
||||
router.push("/(auth)/(home)/");
|
||||
}}
|
||||
>
|
||||
Go Home
|
||||
</Button>
|
||||
const GoHomeButton = useCallback(
|
||||
() => (
|
||||
<Button
|
||||
onPress={() => {
|
||||
router.push("/(auth)/(home)/");
|
||||
}}
|
||||
>
|
||||
{t("chromecast.go_home")}
|
||||
</Button>
|
||||
),
|
||||
[router]
|
||||
);
|
||||
|
||||
const ChromecastControlsMemoized = useMemo(() => {
|
||||
if (!mediaStatus || !client) return undefined;
|
||||
return <ChromecastControls mediaStatus={mediaStatus} client={client} />;
|
||||
}, [mediaStatus, client]);
|
||||
|
||||
if (
|
||||
castState === CastState.NO_DEVICES_AVAILABLE ||
|
||||
castState === CastState.NOT_CONNECTED
|
||||
@@ -101,9 +90,11 @@ export default function Player() {
|
||||
<AndroidCastButton />
|
||||
<View className="w-screen h-screen flex flex-col items-center justify-center bg-black">
|
||||
<Text className="text-white text-lg">
|
||||
No Google Cast devices available.
|
||||
{t("chromecast.no_devices_available")}
|
||||
</Text>
|
||||
<Text className="text-gray-400">
|
||||
{t("chromecast.are_you_on_same_network")}
|
||||
</Text>
|
||||
<Text className="text-gray-400">Are you on the same network?</Text>
|
||||
</View>
|
||||
<View className="px-10">
|
||||
<GoHomeButton />
|
||||
@@ -127,8 +118,12 @@ export default function Player() {
|
||||
<AndroidCastButton />
|
||||
<Feather name="cast" size={42} color={"white"} />
|
||||
</RoundButton>
|
||||
<Text className="text-white text-xl mt-2">No device selected</Text>
|
||||
<Text className="text-gray-400">Click icon to connect.</Text>
|
||||
<Text className="text-white text-xl mt-2">
|
||||
{t("chromecast.no_device_selected")}
|
||||
</Text>
|
||||
<Text className="text-gray-400">
|
||||
{t("chromecast.click_icon_to_connect")}
|
||||
</Text>
|
||||
</View>
|
||||
<View className="px-10">
|
||||
<GoHomeButton />
|
||||
@@ -141,7 +136,7 @@ export default function Player() {
|
||||
return (
|
||||
<View className="w-screen h-screen flex flex-col items-center justify-center bg-black">
|
||||
<Text className="text-white font-semibold lg mb-2">
|
||||
Establishing connection...
|
||||
{t("chromecast.establishing_connection")}
|
||||
</Text>
|
||||
<Loader />
|
||||
</View>
|
||||
@@ -153,8 +148,10 @@ export default function Player() {
|
||||
return (
|
||||
<View className="w-screen h-screen flex flex-col ">
|
||||
<View className="w-screen h-screen flex flex-col items-center justify-center bg-black">
|
||||
<Text className="text-white text-lg">No media selected.</Text>
|
||||
<Text className="text-gray-400">Start playing any media</Text>
|
||||
<Text className="text-white text-lg">
|
||||
{t("chromecast.no_media_selected")}
|
||||
</Text>
|
||||
<Text className="text-gray-400">{t("chromecast.start_playing")}</Text>
|
||||
</View>
|
||||
<View className="px-10">
|
||||
<GoHomeButton />
|
||||
@@ -163,415 +160,5 @@ export default function Player() {
|
||||
);
|
||||
}
|
||||
|
||||
return <ChromecastControls mediaStatus={mediaStatus} client={client} />;
|
||||
}
|
||||
|
||||
function ChromecastControls({
|
||||
mediaStatus,
|
||||
client,
|
||||
}: {
|
||||
mediaStatus: MediaStatus;
|
||||
client: RemoteMediaClient | null;
|
||||
}) {
|
||||
const lightHapticFeedback = useHaptic("light");
|
||||
|
||||
const streamPosition = useStreamPosition();
|
||||
|
||||
const [settings] = useSettings();
|
||||
|
||||
const [isSliding, setIsSliding] = useState(false);
|
||||
|
||||
const [currentTime, setCurrentTime] = useState(0);
|
||||
const [remainingTime, setRemainingTime] = useState(Infinity);
|
||||
|
||||
const min = useSharedValue(0);
|
||||
const max = useSharedValue(mediaStatus.mediaInfo?.streamDuration || 0);
|
||||
const progress = useSharedValue(streamPosition || 0);
|
||||
const isSeeking = useSharedValue(false);
|
||||
|
||||
const wasPlayingRef = useRef(false);
|
||||
const lastProgressRef = useRef<number>(0);
|
||||
|
||||
const isPlaying = mediaStatus.playerState === "playing";
|
||||
const isBufferingOrLoading =
|
||||
mediaStatus.playerState === "buffering" ||
|
||||
mediaStatus.playerState === "loading";
|
||||
|
||||
// request update of media status every player state change
|
||||
useEffect(() => {
|
||||
client?.requestStatus();
|
||||
}, [mediaStatus.playerState]);
|
||||
|
||||
// update progess on stream position change
|
||||
useEffect(() => {
|
||||
if (streamPosition) progress.value = streamPosition;
|
||||
}, [streamPosition]);
|
||||
|
||||
// update max progress
|
||||
useEffect(() => {
|
||||
if (mediaStatus.mediaInfo?.streamDuration)
|
||||
max.value = mediaStatus.mediaInfo?.streamDuration;
|
||||
}, [mediaStatus.mediaInfo?.streamDuration]);
|
||||
|
||||
const updateTimes = useCallback(
|
||||
(currentProgress: number, maxValue: number) => {
|
||||
setCurrentTime(progress.value);
|
||||
setRemainingTime(max.value - progress.value);
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
useAnimatedReaction(
|
||||
() => ({
|
||||
progress: progress.value,
|
||||
max: max.value,
|
||||
isSeeking: isSeeking.value,
|
||||
}),
|
||||
(result) => {
|
||||
if (result.isSeeking === false) {
|
||||
runOnJS(updateTimes)(result.progress, result.max);
|
||||
}
|
||||
},
|
||||
[updateTimes]
|
||||
);
|
||||
|
||||
function pause() {
|
||||
client?.pause();
|
||||
}
|
||||
|
||||
function play() {
|
||||
client?.play();
|
||||
}
|
||||
|
||||
function seek(time: number) {
|
||||
client?.seek({
|
||||
position: time,
|
||||
});
|
||||
}
|
||||
|
||||
function togglePlay() {
|
||||
if (isPlaying) pause();
|
||||
else play();
|
||||
}
|
||||
|
||||
const handleSliderStart = useCallback(() => {
|
||||
setIsSliding(true);
|
||||
wasPlayingRef.current = isPlaying;
|
||||
lastProgressRef.current = progress.value;
|
||||
|
||||
pause();
|
||||
isSeeking.value = true;
|
||||
}, [isPlaying]);
|
||||
|
||||
const handleSliderComplete = useCallback(async (value: number) => {
|
||||
isSeeking.value = false;
|
||||
progress.value = value;
|
||||
setIsSliding(false);
|
||||
|
||||
seek(Math.max(0, Math.floor(value)));
|
||||
if (wasPlayingRef.current === true) play();
|
||||
}, []);
|
||||
|
||||
const [time, setTime] = useState({ hours: 0, minutes: 0, seconds: 0 });
|
||||
const handleSliderChange = useCallback(
|
||||
debounce((value: number) => {
|
||||
// TODO check if something must be done here
|
||||
|
||||
const progressInSeconds = Math.floor(value);
|
||||
const hours = Math.floor(progressInSeconds / 3600);
|
||||
const minutes = Math.floor((progressInSeconds % 3600) / 60);
|
||||
const seconds = progressInSeconds % 60;
|
||||
setTime({ hours, minutes, seconds });
|
||||
}, 3),
|
||||
[]
|
||||
);
|
||||
|
||||
const handleSkipBackward = useCallback(async () => {
|
||||
if (!settings?.rewindSkipTime) return;
|
||||
wasPlayingRef.current = isPlaying;
|
||||
lightHapticFeedback();
|
||||
try {
|
||||
const curr = progress.value;
|
||||
if (curr !== undefined) {
|
||||
const newTime = Math.max(0, curr - settings.rewindSkipTime);
|
||||
seek(newTime);
|
||||
if (wasPlayingRef.current === true) play();
|
||||
}
|
||||
} catch (error) {
|
||||
writeToLog("ERROR", "Error seeking video backwards", error);
|
||||
}
|
||||
}, [settings, isPlaying]);
|
||||
|
||||
const handleSkipForward = useCallback(async () => {
|
||||
if (!settings?.forwardSkipTime) return;
|
||||
wasPlayingRef.current = isPlaying;
|
||||
lightHapticFeedback();
|
||||
try {
|
||||
const curr = progress.value;
|
||||
if (curr !== undefined) {
|
||||
const newTime = curr + settings.forwardSkipTime;
|
||||
seek(Math.max(0, newTime));
|
||||
if (wasPlayingRef.current === true) play();
|
||||
}
|
||||
} catch (error) {
|
||||
writeToLog("ERROR", "Error seeking video forwards", error);
|
||||
}
|
||||
}, [settings, isPlaying]);
|
||||
|
||||
const mediaMetadata = mediaStatus.mediaInfo?.metadata;
|
||||
const itemId = mediaStatus.mediaInfo?.contentId;
|
||||
|
||||
const type = mediaMetadata?.type || "generic";
|
||||
const images = mediaMetadata?.images || [];
|
||||
|
||||
const item: BaseItemDto | undefined = mediaStatus.mediaInfo?.customData;
|
||||
|
||||
const { previousItem, nextItem } = useAdjacentItems({
|
||||
item: {
|
||||
Id: itemId,
|
||||
SeriesId: item?.SeriesId,
|
||||
Type: item?.Type,
|
||||
},
|
||||
});
|
||||
|
||||
const goToNextItem = () => {
|
||||
console.warn("go to next item not implemented yet");
|
||||
};
|
||||
const goToPreviousItem = () => {
|
||||
console.warn("go to previous item not implemented yet");
|
||||
};
|
||||
|
||||
const { showSkipButton, skipIntro } = useIntroSkipper(
|
||||
itemId,
|
||||
currentTime,
|
||||
seek,
|
||||
play,
|
||||
false
|
||||
);
|
||||
|
||||
const { showSkipCreditButton, skipCredit } = useCreditSkipper(
|
||||
itemId,
|
||||
currentTime,
|
||||
seek,
|
||||
play,
|
||||
false
|
||||
);
|
||||
|
||||
const blurhash =
|
||||
"|rF?hV%2WCj[ayj[a|j[az_NaeWBj@ayfRayfQfQM{M|azj[azf6fQfQfQIpWXofj[ayj[j[fQayWCoeoeaya}j[ayfQa{oLj?j[WVj[ayayj[fQoff7azayj[ayj[j[ayofayayayj[fQj[ayayj[ayfjj[j[ayjuayj[";
|
||||
|
||||
const ItemInfo = useMemo(() => {
|
||||
switch (type) {
|
||||
case "generic":
|
||||
return <GenericInfo mediaMetadata={mediaMetadata} />;
|
||||
case "movie":
|
||||
return <MovieInfo mediaMetadata={mediaMetadata} />;
|
||||
case "tvShow":
|
||||
return <TvShowInfo mediaMetadata={mediaMetadata} />;
|
||||
default:
|
||||
return <Text>{type} not implemented yet!</Text>;
|
||||
}
|
||||
}, [type]);
|
||||
|
||||
// Android requires the cast button to be present for startDiscovery to work
|
||||
const AndroidCastButton = useCallback(
|
||||
() =>
|
||||
Platform.OS === "android" ? (
|
||||
<CastButton tintColor="transparent" />
|
||||
) : (
|
||||
<></>
|
||||
),
|
||||
[Platform.OS]
|
||||
);
|
||||
|
||||
return (
|
||||
<View className="w-full h-full flex flex-col items-center justify-center bg-black">
|
||||
<View className="w-full h-full flex flex-col justify-between bg-black">
|
||||
<BlurView
|
||||
intensity={60}
|
||||
tint="dark"
|
||||
experimentalBlurMethod="dimezisBlurView"
|
||||
>
|
||||
<View className="flex flex-row items-baseline justify-between w-full">
|
||||
<View className="mt-8 py-2 px-4 w-fit">{ItemInfo}</View>
|
||||
<RoundButton
|
||||
size="large"
|
||||
className="mr-4 mb-4"
|
||||
background={false}
|
||||
onPress={() => {
|
||||
CastContext.showCastDialog();
|
||||
}}
|
||||
>
|
||||
<AndroidCastButton />
|
||||
<Feather name="cast" size={30} color={"white"} />
|
||||
</RoundButton>
|
||||
</View>
|
||||
</BlurView>
|
||||
<Image
|
||||
className="flex h-full w-full bg-[#0553] absolute -z-50"
|
||||
source={images[0].url}
|
||||
placeholder={{ blurhash }}
|
||||
contentFit="cover"
|
||||
transition={1000}
|
||||
/>
|
||||
<View className="flex flex-col w-full">
|
||||
<View className="flex flex-row w-full justify-end px-6 pb-6">
|
||||
<SkipButton
|
||||
showButton={showSkipButton}
|
||||
onPress={skipIntro}
|
||||
buttonText="Skip Intro"
|
||||
/>
|
||||
<SkipButton
|
||||
showButton={showSkipCreditButton}
|
||||
onPress={skipCredit}
|
||||
buttonText="Skip Credits"
|
||||
/>
|
||||
<NextEpisodeCountDownButton
|
||||
show={!nextItem ? false : remainingTime < 10}
|
||||
onFinish={goToNextItem}
|
||||
onPress={goToNextItem}
|
||||
/>
|
||||
</View>
|
||||
<BlurView
|
||||
intensity={5}
|
||||
tint="dark"
|
||||
// blurs buttons too. not wanted
|
||||
experimentalBlurMethod="dimezisBlurView"
|
||||
className="pt-1"
|
||||
>
|
||||
<View className={`flex flex-col w-full shrink px-2`}>
|
||||
<Slider
|
||||
theme={{
|
||||
maximumTrackTintColor: "rgba(255,255,255,0.2)",
|
||||
minimumTrackTintColor: "#fff",
|
||||
cacheTrackTintColor: "rgba(255,255,255,0.3)",
|
||||
bubbleBackgroundColor: "#fff",
|
||||
bubbleTextColor: "#666",
|
||||
heartbeatColor: "#999",
|
||||
}}
|
||||
renderThumb={() => null}
|
||||
onSlidingStart={handleSliderStart}
|
||||
onSlidingComplete={handleSliderComplete}
|
||||
onValueChange={handleSliderChange}
|
||||
containerStyle={{
|
||||
borderRadius: 100,
|
||||
}}
|
||||
renderBubble={() => isSliding}
|
||||
sliderHeight={10}
|
||||
thumbWidth={0}
|
||||
progress={progress}
|
||||
minimumValue={min}
|
||||
maximumValue={max}
|
||||
/>
|
||||
<View className="flex flex-row items-center justify-between mt-2">
|
||||
<Text className="text-[12px] text-neutral-400">
|
||||
{formatTimeString(currentTime, "s")}
|
||||
</Text>
|
||||
<Text className="text-[12px] text-neutral-400">
|
||||
-{formatTimeString(remainingTime, "s")}
|
||||
</Text>
|
||||
</View>
|
||||
<View className="flex flex-row w-full items-center justify-evenly mt-2 mb-10">
|
||||
<TouchableOpacity
|
||||
onPress={goToPreviousItem}
|
||||
disabled={!previousItem}
|
||||
>
|
||||
<Ionicons
|
||||
name="play-skip-back-outline"
|
||||
size={30}
|
||||
color={previousItem ? "white" : "gray"}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity onPress={handleSkipBackward}>
|
||||
<Ionicons name="play-back-outline" size={30} color="white" />
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
onPress={() => togglePlay()}
|
||||
className="flex w-14 h-14 items-center justify-center"
|
||||
>
|
||||
{!isBufferingOrLoading ? (
|
||||
<Ionicons
|
||||
name={isPlaying ? "pause" : "play"}
|
||||
size={50}
|
||||
color="white"
|
||||
/>
|
||||
) : (
|
||||
<Loader size={"large"} />
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity onPress={handleSkipForward}>
|
||||
<Ionicons
|
||||
name="play-forward-outline"
|
||||
size={30}
|
||||
color="white"
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity onPress={goToNextItem} disabled={!nextItem}>
|
||||
<Ionicons
|
||||
name="play-skip-forward-outline"
|
||||
size={30}
|
||||
color={nextItem ? "white" : "gray"}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
</BlurView>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
type MetadataInfoProps = { mediaMetadata: MediaInfo["metadata"] };
|
||||
|
||||
function GenericInfo({ mediaMetadata }: MetadataInfoProps) {
|
||||
const title = mediaMetadata?.title || "Title not found!";
|
||||
|
||||
return (
|
||||
<>
|
||||
<Text className="text-white font-bold text-3xl">{title}</Text>
|
||||
{
|
||||
// @ts-expect-error The metadata type doesn't have subtitle, but the object has
|
||||
mediaMetadata?.subtitle && <Text>{mediaMetadata?.subtitle}</Text>
|
||||
}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function MovieInfo({ mediaMetadata }: MetadataInfoProps) {
|
||||
const title = mediaMetadata?.title || "Title not found!";
|
||||
|
||||
return (
|
||||
<>
|
||||
<Text className="text-white font-bold text-3xl">{title}</Text>
|
||||
{
|
||||
// @ts-expect-error The metadata type doesn't have subtitle, but the object has
|
||||
mediaMetadata?.subtitle && <Text>{mediaMetadata?.subtitle}</Text>
|
||||
}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function TvShowInfo({ mediaMetadata }: MetadataInfoProps) {
|
||||
const itemTitle: string = mediaMetadata?.title || "Title not found!";
|
||||
// @ts-expect-error
|
||||
const seriesTitle: string = mediaMetadata?.seriesTitle || "Title not found!";
|
||||
|
||||
// @ts-expect-error
|
||||
const episodeNumber: number = mediaMetadata?.episodeNumber || 0;
|
||||
// @ts-expect-error
|
||||
const seasonNumber: number = mediaMetadata?.seasonNumber || 0;
|
||||
|
||||
return (
|
||||
<>
|
||||
<View className="flex w-full">
|
||||
<Text className="text-gray-300 font-bold text-2xl">{seriesTitle}</Text>
|
||||
<Text className="text-white font-bold text-3xl">{itemTitle}</Text>
|
||||
</View>
|
||||
<Text>
|
||||
Season {seasonNumber.toLocaleString()} Episode{" "}
|
||||
{episodeNumber.toLocaleString()}
|
||||
</Text>
|
||||
</>
|
||||
);
|
||||
return ChromecastControlsMemoized;
|
||||
}
|
||||
|
||||
@@ -1,156 +1,67 @@
|
||||
{
|
||||
"login": {
|
||||
"username_required": "Username Is Required",
|
||||
"username_required": "Username is required",
|
||||
"error_title": "Error",
|
||||
"login_title": "Log In",
|
||||
"login_title": "Log in",
|
||||
"login_to_title": "Log in to",
|
||||
"username_placeholder": "Username",
|
||||
"password_placeholder": "Password",
|
||||
"login_button": "Log In",
|
||||
"login_button": "Log in",
|
||||
"quick_connect": "Quick Connect",
|
||||
"enter_code_to_login": "Enter code {{code}} to login",
|
||||
"failed_to_initiate_quick_connect": "Failed to initiate Quick Connect",
|
||||
"got_it": "Got It",
|
||||
"connection_failed": "Connection Failed",
|
||||
"got_it": "Got it",
|
||||
"connection_failed": "Connection failed",
|
||||
"could_not_connect_to_server": "Could not connect to the server. Please check the URL and your network connection.",
|
||||
"an_unexpected_error_occured": "An Unexpected Error Occurred",
|
||||
"change_server": "Change Server",
|
||||
"invalid_username_or_password": "Invalid Username or Password",
|
||||
"an_unexpected_error_occured": "An unexpected error occurred",
|
||||
"change_server": "Change server",
|
||||
"invalid_username_or_password": "Invalid username or password",
|
||||
"user_does_not_have_permission_to_log_in": "User does not have permission to log in",
|
||||
"server_is_taking_too_long_to_respond_try_again_later": "Server is taking too long to respond, try again later",
|
||||
"server_received_too_many_requests_try_again_later": "Server received too many requests, try again later.",
|
||||
"there_is_a_server_error": "There is a server error",
|
||||
"an_unexpected_error_occured_did_you_enter_the_correct_url": "An unexpected error occurred. Did you enter the server URL correctly?",
|
||||
"too_old_server_text": "Unsupported Jellyfin Server Discovered",
|
||||
"too_old_server_description": "Please update Jellyfin to the latest version"
|
||||
"an_unexpected_error_occured_did_you_enter_the_correct_url": "An unexpected error occurred. Did you enter the server URL correctly?"
|
||||
},
|
||||
"server": {
|
||||
"enter_url_to_jellyfin_server": "Enter the URL to your Jellyfin server",
|
||||
"server_url_placeholder": "http(s)://your-server.com",
|
||||
"connect_button": "Connect",
|
||||
"previous_servers": "Previous Servers",
|
||||
"clear_button": "Clear all",
|
||||
"swipe_to_remove": "Swipe to remove",
|
||||
"search_for_local_servers": "Search for Local Servers",
|
||||
"previous_servers": "previous servers",
|
||||
"clear_button": "Clear",
|
||||
"search_for_local_servers": "Search for local servers",
|
||||
"searching": "Searching...",
|
||||
"servers": "Servers",
|
||||
"saved": "Saved",
|
||||
"session_expired": "Session Expired",
|
||||
"please_login_again": "Your saved session has expired. Please log in again.",
|
||||
"remove_saved_login": "Remove Saved Login",
|
||||
"remove_saved_login_description": "This will remove your saved credentials for this server. You'll need to enter your username and password again next time.",
|
||||
"accounts_count": "{{count}} accounts",
|
||||
"select_account": "Select Account",
|
||||
"add_account": "Add Account",
|
||||
"remove_account_description": "This will remove the saved credentials for {{username}}."
|
||||
},
|
||||
"save_account": {
|
||||
"title": "Save Account",
|
||||
"save_for_later": "Save this account",
|
||||
"security_option": "Security Option",
|
||||
"no_protection": "No protection",
|
||||
"no_protection_desc": "Quick login without authentication",
|
||||
"pin_code": "PIN code",
|
||||
"pin_code_desc": "4-digit PIN required when switching",
|
||||
"password": "Re-enter password",
|
||||
"password_desc": "Password required when switching",
|
||||
"save_button": "Save",
|
||||
"cancel_button": "Cancel"
|
||||
},
|
||||
"pin": {
|
||||
"enter_pin": "Enter PIN",
|
||||
"enter_pin_for": "Enter PIN for {{username}}",
|
||||
"enter_4_digits": "Enter 4 digits",
|
||||
"invalid_pin": "Invalid PIN",
|
||||
"setup_pin": "Set Up PIN",
|
||||
"confirm_pin": "Confirm PIN",
|
||||
"pins_dont_match": "PINs don't match",
|
||||
"forgot_pin": "Forgot PIN?",
|
||||
"forgot_pin_desc": "Your saved credentials will be removed"
|
||||
},
|
||||
"password": {
|
||||
"enter_password": "Enter Password",
|
||||
"enter_password_for": "Enter password for {{username}}",
|
||||
"invalid_password": "Invalid password"
|
||||
"servers": "Servers"
|
||||
},
|
||||
"home": {
|
||||
"checking_server_connection": "Checking server connection...",
|
||||
"no_internet": "No Internet",
|
||||
"no_items": "No Items",
|
||||
"no_items": "No items",
|
||||
"no_internet_message": "No worries, you can still watch\ndownloaded content.",
|
||||
"checking_server_connection_message": "Checking connection to server",
|
||||
"go_to_downloads": "Go to Downloads",
|
||||
"retry": "Retry",
|
||||
"server_unreachable": "Server Unreachable",
|
||||
"server_unreachable_message": "Could not reach the server.\nPlease check your network connection.",
|
||||
"go_to_downloads": "Go to downloads",
|
||||
"oops": "Oops!",
|
||||
"error_message": "Something went wrong.\nPlease log out and in again.",
|
||||
"continue_watching": "Continue Watching",
|
||||
"next_up": "Next Up",
|
||||
"continue_and_next_up": "Continue & Next Up",
|
||||
"recently_added_in": "Recently Added in {{libraryName}}",
|
||||
"suggested_movies": "Suggested Movies",
|
||||
"suggested_episodes": "Suggested Episodes",
|
||||
"intro": {
|
||||
"welcome_to_streamyfin": "Welcome to Streamyfin",
|
||||
"a_free_and_open_source_client_for_jellyfin": "A Free and Open-Source Client for Jellyfin.",
|
||||
"a_free_and_open_source_client_for_jellyfin": "A free and open-source client for Jellyfin.",
|
||||
"features_title": "Features",
|
||||
"features_description": "Streamyfin has a bunch of features and integrates with a wide array of software which you can find in the settings menu, these include:",
|
||||
"jellyseerr_feature_description": "Connect to your Seerr instance and request movies directly in the app.",
|
||||
"jellyseerr_feature_description": "Connect to your Jellyseerr instance and request movies directly in the app.",
|
||||
"downloads_feature_title": "Downloads",
|
||||
"downloads_feature_description": "Download movies and tv-shows to view offline. Use either the default method or install the optimize server to download files in the background.",
|
||||
"chromecast_feature_description": "Cast movies and tv-shows to your Chromecast devices.",
|
||||
"centralised_settings_plugin_title": "Centralised Settings Plugin",
|
||||
"centralised_settings_plugin_description": "Configure settings from a centralised location on your Jellyfin server. All client settings for all users will be synced automatically.",
|
||||
"done_button": "Done",
|
||||
"go_to_settings_button": "Go to Settings",
|
||||
"read_more": "Read More"
|
||||
"go_to_settings_button": "Go to settings",
|
||||
"read_more": "Read more"
|
||||
},
|
||||
"settings": {
|
||||
"settings_title": "Settings",
|
||||
"log_out_button": "Log Out",
|
||||
"categories": {
|
||||
"title": "Categories"
|
||||
},
|
||||
"playback_controls": {
|
||||
"title": "Playback & Controls"
|
||||
},
|
||||
"audio_subtitles": {
|
||||
"title": "Audio & Subtitles"
|
||||
},
|
||||
"appearance": {
|
||||
"title": "Appearance",
|
||||
"merge_next_up_continue_watching": "Merge Continue Watching & Next Up",
|
||||
"hide_remote_session_button": "Hide Remote Session Button"
|
||||
},
|
||||
"network": {
|
||||
"title": "Network",
|
||||
"local_network": "Local Network",
|
||||
"auto_switch_enabled": "Auto-switch when at home",
|
||||
"auto_switch_description": "Automatically switch to local URL when connected to home WiFi",
|
||||
"local_url": "Local URL",
|
||||
"local_url_hint": "Enter your local server address (e.g., http://192.168.1.100:8096)",
|
||||
"local_url_placeholder": "http://192.168.1.100:8096",
|
||||
"home_wifi_networks": "Home WiFi Networks",
|
||||
"add_current_network": "Add \"{{ssid}}\"",
|
||||
"not_connected_to_wifi": "Not connected to WiFi",
|
||||
"no_networks_configured": "No networks configured",
|
||||
"add_network_hint": "Add your home WiFi network to enable auto-switching",
|
||||
"current_wifi": "Current WiFi",
|
||||
"using_url": "Using",
|
||||
"local": "Local URL",
|
||||
"remote": "Remote URL",
|
||||
"not_connected": "Not connected",
|
||||
"current_server": "Current Server",
|
||||
"remote_url": "Remote URL",
|
||||
"active_url": "Active URL",
|
||||
"not_configured": "Not configured",
|
||||
"network_added": "Network added",
|
||||
"network_already_added": "Network already added",
|
||||
"no_wifi_connected": "Not connected to WiFi",
|
||||
"permission_denied": "Location permission denied",
|
||||
"permission_denied_explanation": "Location permission is required to detect WiFi network for auto-switching. Please enable it in Settings."
|
||||
},
|
||||
"log_out_button": "Log out",
|
||||
"user_info": {
|
||||
"user_info_title": "User Info",
|
||||
"user": "User",
|
||||
@@ -163,53 +74,32 @@
|
||||
"authorize_button": "Authorize Quick Connect",
|
||||
"enter_the_quick_connect_code": "Enter the quick connect code...",
|
||||
"success": "Success",
|
||||
"quick_connect_autorized": "Quick Connect Authorized",
|
||||
"quick_connect_autorized": "Quick Connect authorized",
|
||||
"error": "Error",
|
||||
"invalid_code": "Invalid Code",
|
||||
"invalid_code": "Invalid code",
|
||||
"authorize": "Authorize"
|
||||
},
|
||||
"media_controls": {
|
||||
"media_controls_title": "Media Controls",
|
||||
"forward_skip_length": "Forward Skip Length",
|
||||
"rewind_length": "Rewind Length",
|
||||
"forward_skip_length": "Forward skip length",
|
||||
"rewind_length": "Rewind length",
|
||||
"seconds_unit": "s"
|
||||
},
|
||||
"gesture_controls": {
|
||||
"gesture_controls_title": "Gesture Controls",
|
||||
"horizontal_swipe_skip": "Horizontal Swipe to Skip",
|
||||
"horizontal_swipe_skip_description": "Swipe left/right when controls are hidden to skip",
|
||||
"left_side_brightness": "Left Side Brightness Control",
|
||||
"left_side_brightness_description": "Swipe up/down on left side to adjust brightness",
|
||||
"right_side_volume": "Right Side Volume Control",
|
||||
"right_side_volume_description": "Swipe up/down on right side to adjust volume",
|
||||
"hide_volume_slider": "Hide Volume Slider",
|
||||
"hide_volume_slider_description": "Hide the volume slider in the video player",
|
||||
"hide_brightness_slider": "Hide Brightness Slider",
|
||||
"hide_brightness_slider_description": "Hide the brightness slider in the video player"
|
||||
},
|
||||
"audio": {
|
||||
"audio_title": "Audio",
|
||||
"set_audio_track": "Set Audio Track From Previous Item",
|
||||
"audio_language": "Audio Language",
|
||||
"audio_language": "Audio language",
|
||||
"audio_hint": "Choose a default audio language.",
|
||||
"none": "None",
|
||||
"language": "Language",
|
||||
"transcode_mode": {
|
||||
"title": "Audio Transcoding",
|
||||
"description": "Controls how surround audio (7.1, TrueHD, DTS-HD) is handled",
|
||||
"auto": "Auto",
|
||||
"stereo": "Force Stereo",
|
||||
"5_1": "Allow 5.1",
|
||||
"passthrough": "Passthrough"
|
||||
}
|
||||
"language": "Language"
|
||||
},
|
||||
"subtitles": {
|
||||
"subtitle_title": "Subtitles",
|
||||
"subtitle_hint": "Configure how subtitles look and behave.",
|
||||
"subtitle_language": "Subtitle language",
|
||||
"subtitle_mode": "Subtitle Mode",
|
||||
"set_subtitle_track": "Set Subtitle Track From Previous Item",
|
||||
"subtitle_size": "Subtitle Size",
|
||||
"subtitle_hint": "Configure subtitle preference.",
|
||||
"none": "None",
|
||||
"language": "Language",
|
||||
"loading": "Loading",
|
||||
@@ -219,110 +109,45 @@
|
||||
"Always": "Always",
|
||||
"None": "None",
|
||||
"OnlyForced": "OnlyForced"
|
||||
},
|
||||
"text_color": "Text Color",
|
||||
"background_color": "Background Color",
|
||||
"outline_color": "Outline Color",
|
||||
"outline_thickness": "Outline Thickness",
|
||||
"background_opacity": "Background Opacity",
|
||||
"outline_opacity": "Outline Opacity",
|
||||
"bold_text": "Bold Text",
|
||||
"colors": {
|
||||
"Black": "Black",
|
||||
"Gray": "Gray",
|
||||
"Silver": "Silver",
|
||||
"White": "White",
|
||||
"Maroon": "Maroon",
|
||||
"Red": "Red",
|
||||
"Fuchsia": "Fuchsia",
|
||||
"Yellow": "Yellow",
|
||||
"Olive": "Olive",
|
||||
"Green": "Green",
|
||||
"Teal": "Teal",
|
||||
"Lime": "Lime",
|
||||
"Purple": "Purple",
|
||||
"Navy": "Navy",
|
||||
"Blue": "Blue",
|
||||
"Aqua": "Aqua"
|
||||
},
|
||||
"thickness": {
|
||||
"None": "None",
|
||||
"Thin": "Thin",
|
||||
"Normal": "Normal",
|
||||
"Thick": "Thick"
|
||||
},
|
||||
"subtitle_color": "Subtitle Color",
|
||||
"subtitle_background_color": "Background Color",
|
||||
"subtitle_font": "Subtitle Font",
|
||||
"ksplayer_title": "KSPlayer Settings",
|
||||
"hardware_decode": "Hardware Decoding",
|
||||
"hardware_decode_description": "Use hardware acceleration for video decoding. Disable if you experience playback issues."
|
||||
},
|
||||
"vlc_subtitles": {
|
||||
"title": "VLC Subtitle Settings",
|
||||
"hint": "Customize subtitle appearance for VLC player. Changes take effect on next playback.",
|
||||
"text_color": "Text Color",
|
||||
"background_color": "Background Color",
|
||||
"background_opacity": "Background Opacity",
|
||||
"outline_color": "Outline Color",
|
||||
"outline_opacity": "Outline Opacity",
|
||||
"outline_thickness": "Outline Thickness",
|
||||
"bold": "Bold Text",
|
||||
"margin": "Bottom Margin"
|
||||
},
|
||||
"video_player": {
|
||||
"title": "Video Player",
|
||||
"video_player": "Video Player",
|
||||
"video_player_description": "Choose which video player to use on iOS.",
|
||||
"ksplayer": "KSPlayer",
|
||||
"vlc": "VLC"
|
||||
}
|
||||
},
|
||||
"other": {
|
||||
"other_title": "Other",
|
||||
"video_orientation": "Video Orientation",
|
||||
"auto_rotate": "Auto rotate",
|
||||
"video_orientation": "Video orientation",
|
||||
"orientation": "Orientation",
|
||||
"orientations": {
|
||||
"DEFAULT": "Follow Device Orientation",
|
||||
"DEFAULT": "Default",
|
||||
"ALL": "All",
|
||||
"PORTRAIT": "Portrait Auto",
|
||||
"PORTRAIT": "Portrait",
|
||||
"PORTRAIT_UP": "Portrait Up",
|
||||
"PORTRAIT_DOWN": "Portrait Down",
|
||||
"LANDSCAPE": "Landscape Auto",
|
||||
"LANDSCAPE": "Landscape",
|
||||
"LANDSCAPE_LEFT": "Landscape Left",
|
||||
"LANDSCAPE_RIGHT": "Landscape Right",
|
||||
"OTHER": "Other",
|
||||
"UNKNOWN": "Unknown"
|
||||
},
|
||||
"safe_area_in_controls": "Safe Area in Controls",
|
||||
"video_player": "Video Player",
|
||||
"video_players": {
|
||||
"VLC_3": "VLC 3",
|
||||
"VLC_4": "VLC 4 (Experimental + PiP)"
|
||||
},
|
||||
"safe_area_in_controls": "Safe area in controls",
|
||||
"show_custom_menu_links": "Show Custom Menu Links",
|
||||
"show_large_home_carousel": "Show Large Home Carousel (beta)",
|
||||
"hide_libraries": "Hide Libraries",
|
||||
"select_liraries_you_want_to_hide": "Select the libraries you want to hide from the Library tab and home page sections.",
|
||||
"disable_haptic_feedback": "Disable Haptic Feedback",
|
||||
"default_quality": "Default Quality",
|
||||
"default_playback_speed": "Default Playback Speed",
|
||||
"auto_play_next_episode": "Auto-play Next Episode",
|
||||
"max_auto_play_episode_count": "Max Auto Play Episode Count",
|
||||
"disabled": "Disabled"
|
||||
"disable_haptic_feedback": "Disable Haptic Feedback"
|
||||
},
|
||||
"downloads": {
|
||||
"downloads_title": "Downloads"
|
||||
},
|
||||
"music": {
|
||||
"title": "Music",
|
||||
"playback_title": "Playback",
|
||||
"playback_description": "Configure how music is played.",
|
||||
"prefer_downloaded": "Prefer Downloaded Songs",
|
||||
"caching_title": "Caching",
|
||||
"caching_description": "Automatically cache upcoming tracks for smoother playback.",
|
||||
"lookahead_enabled": "Enable Look-Ahead Caching",
|
||||
"lookahead_count": "Tracks to Pre-cache",
|
||||
"max_cache_size": "Max Cache Size"
|
||||
"downloads_title": "Downloads",
|
||||
"download_method": "Download method",
|
||||
"remux_max_download": "Remux max download",
|
||||
"auto_download": "Auto download",
|
||||
"optimized_versions_server": "Optimized versions server",
|
||||
"save_button": "Save",
|
||||
"optimized_server": "Optimized Server",
|
||||
"optimized": "Optimized",
|
||||
"default": "Default",
|
||||
"optimized_version_hint": "Enter the URL for the optimize server. The URL should include http or https and optionally the port.",
|
||||
"read_more_about_optimized_server": "Read more about the optimize server.",
|
||||
"url": "URL",
|
||||
"server_url_placeholder": "http(s)://domain.org:port"
|
||||
},
|
||||
"plugins": {
|
||||
"plugins_title": "Plugins",
|
||||
@@ -330,201 +155,124 @@
|
||||
"jellyseerr_warning": "This integration is in its early stages. Expect things to change.",
|
||||
"server_url": "Server URL",
|
||||
"server_url_hint": "Example: http(s)://your-host.url\n(add port if required)",
|
||||
"server_url_placeholder": "Seerr URL",
|
||||
"server_url_placeholder": "Jellyseerr URL...",
|
||||
"password": "Password",
|
||||
"password_placeholder": "Enter password for Jellyfin user {{username}}",
|
||||
"save_button": "Save",
|
||||
"clear_button": "Clear",
|
||||
"login_button": "Login",
|
||||
"total_media_requests": "Total Media Requests",
|
||||
"movie_quota_limit": "Movie Quota Limit",
|
||||
"movie_quota_days": "Movie Quota Days",
|
||||
"tv_quota_limit": "TV Quota Limit",
|
||||
"tv_quota_days": "TV Quota Days",
|
||||
"reset_jellyseerr_config_button": "Reset Seerr Config",
|
||||
"unlimited": "Unlimited",
|
||||
"plus_n_more": "+{{n}} More",
|
||||
"order_by": {
|
||||
"DEFAULT": "Default",
|
||||
"VOTE_COUNT_AND_AVERAGE": "Vote count and average",
|
||||
"POPULARITY": "Popularity"
|
||||
}
|
||||
"total_media_requests": "Total media requests",
|
||||
"movie_quota_limit": "Movie quota limit",
|
||||
"movie_quota_days": "Movie quota days",
|
||||
"tv_quota_limit": "TV quota limit",
|
||||
"tv_quota_days": "TV quota days",
|
||||
"reset_jellyseerr_config_button": "Reset Jellyseerr config",
|
||||
"unlimited": "Unlimited"
|
||||
},
|
||||
"marlin_search": {
|
||||
"enable_marlin_search": "Enable Marlin Search",
|
||||
"enable_marlin_search": "Enable Marlin Search ",
|
||||
"url": "URL",
|
||||
"server_url_placeholder": "http(s)://domain.org:port",
|
||||
"marlin_search_hint": "Enter the URL for the Marlin server. The URL should include http or https and optionally the port.",
|
||||
"read_more_about_marlin": "Read More About Marlin.",
|
||||
"read_more_about_marlin": "Read more about Marlin.",
|
||||
"save_button": "Save",
|
||||
"toasts": {
|
||||
"saved": "Saved",
|
||||
"refreshed": "Settings refreshed from server"
|
||||
},
|
||||
"refresh_from_server": "Refresh Settings from Server"
|
||||
},
|
||||
"streamystats": {
|
||||
"enable_streamystats": "Enable Streamystats",
|
||||
"disable_streamystats": "Disable Streamystats",
|
||||
"enable_search": "Use for Search",
|
||||
"url": "URL",
|
||||
"server_url_placeholder": "http(s)://streamystats.example.com",
|
||||
"streamystats_search_hint": "Enter the URL for your Streamystats server. The URL should include http or https and optionally the port.",
|
||||
"read_more_about_streamystats": "Read More About Streamystats.",
|
||||
"save_button": "Save",
|
||||
"save": "Save",
|
||||
"features_title": "Features",
|
||||
"home_sections_title": "Home Sections",
|
||||
"enable_movie_recommendations": "Movie Recommendations",
|
||||
"enable_series_recommendations": "Series Recommendations",
|
||||
"enable_promoted_watchlists": "Promoted Watchlists",
|
||||
"hide_watchlists_tab": "Hide Watchlists Tab",
|
||||
"home_sections_hint": "Show personalized recommendations and promoted watchlists from Streamystats on the home page.",
|
||||
"recommended_movies": "Recommended Movies",
|
||||
"recommended_series": "Recommended Series",
|
||||
"toasts": {
|
||||
"saved": "Saved",
|
||||
"refreshed": "Settings refreshed from server",
|
||||
"disabled": "Streamystats disabled"
|
||||
},
|
||||
"refresh_from_server": "Refresh Settings from Server"
|
||||
},
|
||||
"kefinTweaks": {
|
||||
"watchlist_enabler": "Enable our Watchlist integration",
|
||||
"watchlist_button": "Toggle Watchlist integration"
|
||||
"saved": "Saved"
|
||||
}
|
||||
}
|
||||
},
|
||||
"storage": {
|
||||
"storage_title": "Storage",
|
||||
"app_usage": "App {{usedSpace}}%",
|
||||
"device_usage": "Device {{availableSpace}}%",
|
||||
"size_used": "{{used}} of {{total}} Used",
|
||||
"delete_all_downloaded_files": "Delete All Downloaded Files",
|
||||
"music_cache_title": "Music Cache",
|
||||
"music_cache_description": "Automatically cache songs as you listen for smoother playback and offline support",
|
||||
"enable_music_cache": "Enable Music Cache",
|
||||
"clear_music_cache": "Clear Music Cache",
|
||||
"music_cache_size": "{{size}} cached",
|
||||
"music_cache_cleared": "Music cache cleared",
|
||||
"delete_all_downloaded_songs": "Delete All Downloaded Songs",
|
||||
"downloaded_songs_size": "{{size}} downloaded",
|
||||
"downloaded_songs_deleted": "Downloaded songs deleted"
|
||||
"phone_usage": "Phone {{availableSpace}}%",
|
||||
"size_used": "{{used}} of {{total}} used",
|
||||
"delete_all_downloaded_files": "Delete All Downloaded Files"
|
||||
},
|
||||
"intro": {
|
||||
"title": "Intro",
|
||||
"show_intro": "Show Intro",
|
||||
"reset_intro": "Reset Intro"
|
||||
"show_intro": "Show intro",
|
||||
"reset_intro": "Reset intro"
|
||||
},
|
||||
"logs": {
|
||||
"logs_title": "Logs",
|
||||
"export_logs": "Export Logs",
|
||||
"click_for_more_info": "Click for More Info",
|
||||
"level": "Level",
|
||||
"no_logs_available": "No Logs Available",
|
||||
"delete_all_logs": "Delete All Logs"
|
||||
"no_logs_available": "No logs available",
|
||||
"delete_all_logs": "Delete all logs"
|
||||
},
|
||||
"languages": {
|
||||
"title": "Languages",
|
||||
"app_language": "App Language",
|
||||
"app_language": "App language",
|
||||
"app_language_description": "Select the language for the app.",
|
||||
"system": "System"
|
||||
},
|
||||
"toasts": {
|
||||
"error_deleting_files": "Error Deleting Files",
|
||||
"error_deleting_files": "Error deleting files",
|
||||
"background_downloads_enabled": "Background downloads enabled",
|
||||
"background_downloads_disabled": "Background downloads disabled"
|
||||
"background_downloads_disabled": "Background downloads disabled",
|
||||
"connected": "Connected",
|
||||
"could_not_connect": "Could not connect",
|
||||
"invalid_url": "Invalid URL"
|
||||
}
|
||||
},
|
||||
"sessions": {
|
||||
"title": "Sessions",
|
||||
"no_active_sessions": "No Active Sessions"
|
||||
},
|
||||
"downloads": {
|
||||
"downloads_title": "Downloads",
|
||||
"tvseries": "TV-Series",
|
||||
"movies": "Movies",
|
||||
"queue": "Queue",
|
||||
"other_media": "Other media",
|
||||
"queue_hint": "Queue and downloads will be lost on app restart",
|
||||
"no_items_in_queue": "No Items in Queue",
|
||||
"no_downloaded_items": "No Downloaded Items",
|
||||
"delete_all_movies_button": "Delete All Movies",
|
||||
"delete_all_tvseries_button": "Delete All TV-Series",
|
||||
"delete_all_button": "Delete All",
|
||||
"delete_all_other_media_button": "Delete other media",
|
||||
"active_download": "Active Download",
|
||||
"no_active_downloads": "No Active Downloads",
|
||||
"active_downloads": "Active Downloads",
|
||||
"no_items_in_queue": "No items in queue",
|
||||
"no_downloaded_items": "No downloaded items",
|
||||
"delete_all_movies_button": "Delete all Movies",
|
||||
"delete_all_tvseries_button": "Delete all TV-Series",
|
||||
"delete_all_button": "Delete all",
|
||||
"active_download": "Active download",
|
||||
"no_active_downloads": "No active downloads",
|
||||
"active_downloads": "Active downloads",
|
||||
"new_app_version_requires_re_download": "New app version requires re-download",
|
||||
"new_app_version_requires_re_download_description": "The new update requires content to be downloaded again. Please remove all downloaded content and try again.",
|
||||
"back": "Back",
|
||||
"delete": "Delete",
|
||||
"something_went_wrong": "Something Went Wrong",
|
||||
"something_went_wrong": "Something went wrong",
|
||||
"could_not_get_stream_url_from_jellyfin": "Could not get the stream URL from Jellyfin",
|
||||
"eta": "ETA {{eta}}",
|
||||
"methods": "Methods",
|
||||
"toasts": {
|
||||
"you_are_not_allowed_to_download_files": "You are not allowed to download files.",
|
||||
"deleted_all_movies_successfully": "Deleted All Movies Successfully!",
|
||||
"failed_to_delete_all_movies": "Failed to Delete All Movies",
|
||||
"deleted_all_tvseries_successfully": "Deleted All TV-Series Successfully!",
|
||||
"failed_to_delete_all_tvseries": "Failed to Delete All TV-Series",
|
||||
"deleted_media_successfully": "Deleted other media Successfully!",
|
||||
"failed_to_delete_media": "Failed to Delete other media",
|
||||
"download_deleted": "Download Deleted",
|
||||
"download_cancelled": "Download Cancelled",
|
||||
"could_not_delete_download": "Could Not Delete Download",
|
||||
"download_paused": "Download Paused",
|
||||
"could_not_pause_download": "Could Not Pause Download",
|
||||
"download_resumed": "Download Resumed",
|
||||
"could_not_resume_download": "Could Not Resume Download",
|
||||
"download_completed": "Download Completed",
|
||||
"download_failed": "Download Failed",
|
||||
"deleted_all_movies_successfully": "Deleted all movies successfully!",
|
||||
"failed_to_delete_all_movies": "Failed to delete all movies",
|
||||
"deleted_all_tvseries_successfully": "Deleted all TV-Series successfully!",
|
||||
"failed_to_delete_all_tvseries": "Failed to delete all TV-Series",
|
||||
"download_cancelled": "Download cancelled",
|
||||
"could_not_cancel_download": "Could not cancel download",
|
||||
"download_completed": "Download completed",
|
||||
"download_started_for": "Download started for {{item}}",
|
||||
"item_is_ready_to_be_downloaded": "{{item}} is ready to be downloaded",
|
||||
"download_stated_for_item": "Download started for {{item}}",
|
||||
"download_failed_for_item": "Download failed for {{item}} - {{error}}",
|
||||
"download_completed_for_item": "Download Completed for {{item}}",
|
||||
"download_started_for_item": "Download Started for {{item}}",
|
||||
"failed_to_start_download": "Failed to start download",
|
||||
"item_already_downloading": "{{item}} is already downloading",
|
||||
"all_files_deleted": "All Downloads Deleted Successfully",
|
||||
"files_deleted_by_type": "{{count}} {{type}} deleted",
|
||||
"download_completed_for_item": "Download completed for {{item}}",
|
||||
"queued_item_for_optimization": "Queued {{item}} for optimization",
|
||||
"failed_to_start_download_for_item": "Failed to start downloading for {{item}}: {{message}}",
|
||||
"server_responded_with_status_code": "Server responded with status {{statusCode}}",
|
||||
"no_response_received_from_server": "No response received from the server",
|
||||
"error_setting_up_the_request": "Error setting up the request",
|
||||
"failed_to_start_download_for_item_unexpected_error": "Failed to start downloading for {{item}}: Unexpected error",
|
||||
"all_files_folders_and_jobs_deleted_successfully": "All files, folders, and jobs deleted successfully",
|
||||
"failed_to_clean_cache_directory": "Failed to clean cache directory",
|
||||
"could_not_get_download_url_for_item": "Could not get download URL for {{itemName}}",
|
||||
"go_to_downloads": "Go to Downloads",
|
||||
"file_deleted": "{{item}} deleted"
|
||||
"an_error_occured_while_deleting_files_and_jobs": "An error occurred while deleting files and jobs",
|
||||
"go_to_downloads": "Go to downloads"
|
||||
}
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"select": "Select",
|
||||
"no_trailer_available": "No trailer available",
|
||||
"video": "Video",
|
||||
"audio": "Audio",
|
||||
"subtitle": "Subtitle",
|
||||
"play": "Play",
|
||||
"none": "None",
|
||||
"track": "Track",
|
||||
"cancel": "Cancel",
|
||||
"delete": "Delete",
|
||||
"ok": "OK",
|
||||
"remove": "Remove",
|
||||
"next": "Next",
|
||||
"back": "Back",
|
||||
"continue": "Continue",
|
||||
"verifying": "Verifying..."
|
||||
},
|
||||
"search": {
|
||||
"search_here": "Search here...",
|
||||
"search": "Search...",
|
||||
"x_items": "{{count}} Items",
|
||||
"x_items": "{{count}} items",
|
||||
"library": "Library",
|
||||
"discover": "Discover",
|
||||
"no_results": "No Results",
|
||||
"no_results_found_for": "No Results Found For",
|
||||
"no_results": "No results",
|
||||
"no_results_found_for": "No results found for",
|
||||
"movies": "Movies",
|
||||
"series": "Series",
|
||||
"episodes": "Episodes",
|
||||
"collections": "Collections",
|
||||
"actors": "Actors",
|
||||
"artists": "Artists",
|
||||
"albums": "Albums",
|
||||
"songs": "Songs",
|
||||
"playlists": "Playlists",
|
||||
"request_movies": "Request Movies",
|
||||
"request_series": "Request Series",
|
||||
"recently_added": "Recently Added",
|
||||
@@ -550,29 +298,29 @@
|
||||
"tmdb_tv_streaming_services": "TMDB TV Streaming Services"
|
||||
},
|
||||
"library": {
|
||||
"no_results": "No Results",
|
||||
"no_libraries_found": "No Libraries Found",
|
||||
"no_items_found": "No items found",
|
||||
"no_results": "No results",
|
||||
"no_libraries_found": "No libraries found",
|
||||
"item_types": {
|
||||
"movies": "Movies",
|
||||
"series": "Series",
|
||||
"boxsets": "Box Sets",
|
||||
"items": "Items"
|
||||
"movies": "movies",
|
||||
"series": "series",
|
||||
"boxsets": "box sets",
|
||||
"items": "items"
|
||||
},
|
||||
"options": {
|
||||
"display": "Display",
|
||||
"row": "Row",
|
||||
"list": "List",
|
||||
"image_style": "Image Style",
|
||||
"image_style": "Image style",
|
||||
"poster": "Poster",
|
||||
"cover": "Cover",
|
||||
"show_titles": "Show Titles",
|
||||
"show_stats": "Show Stats"
|
||||
"show_titles": "Show titles",
|
||||
"show_stats": "Show stats"
|
||||
},
|
||||
"filters": {
|
||||
"genres": "Genres",
|
||||
"years": "Years",
|
||||
"sort_by": "Sort By",
|
||||
"filter_by": "Filter By",
|
||||
"sort_order": "Sort Order",
|
||||
"tags": "Tags"
|
||||
}
|
||||
@@ -582,37 +330,32 @@
|
||||
"movies": "Movies",
|
||||
"episodes": "Episodes",
|
||||
"videos": "Videos",
|
||||
"boxsets": "Box Sets",
|
||||
"playlists": "Playlists",
|
||||
"noDataTitle": "No Favorites Yet",
|
||||
"noData": "Mark items as favorites to see them appear here for quick access."
|
||||
"boxsets": "Boxsets",
|
||||
"playlists": "Playlists"
|
||||
},
|
||||
"custom_links": {
|
||||
"no_links": "No Links"
|
||||
"no_links": "No links"
|
||||
},
|
||||
"player": {
|
||||
"error": "Error",
|
||||
"failed_to_get_stream_url": "Failed to get the stream URL",
|
||||
"an_error_occured_while_playing_the_video": "An error occurred while playing the video. Check logs in settings.",
|
||||
"client_error": "Client Error",
|
||||
"client_error": "Client error",
|
||||
"could_not_create_stream_for_chromecast": "Could not create a stream for Chromecast",
|
||||
"message_from_server": "Message from Server: {{message}}",
|
||||
"message_from_server": "Message from server: {{message}}",
|
||||
"video_has_finished_playing": "Video has finished playing!",
|
||||
"no_video_source": "No video source...",
|
||||
"next_episode": "Next Episode",
|
||||
"refresh_tracks": "Refresh Tracks",
|
||||
"subtitle_tracks": "Subtitle Tracks:",
|
||||
"audio_tracks": "Audio Tracks:",
|
||||
"playback_state": "Playback State:",
|
||||
"index": "Index:",
|
||||
"continue_watching": "Continue Watching",
|
||||
"go_back": "Go Back",
|
||||
"downloaded_file_title": "You have this file downloaded",
|
||||
"downloaded_file_message": "Do you want to play the downloaded file?",
|
||||
"downloaded_file_yes": "Yes",
|
||||
"downloaded_file_no": "No",
|
||||
"downloaded_file_cancel": "Cancel"
|
||||
"no_data_available": "No data available",
|
||||
"index": "Index:"
|
||||
},
|
||||
"item_card": {
|
||||
"next_up": "Next Up",
|
||||
"no_items_to_display": "No Items to Display",
|
||||
"next_up": "Next up",
|
||||
"no_items_to_display": "No items to display",
|
||||
"cast_and_crew": "Cast & Crew",
|
||||
"series": "Series",
|
||||
"seasons": "Seasons",
|
||||
@@ -620,34 +363,35 @@
|
||||
"no_episodes_for_this_season": "No episodes for this season",
|
||||
"overview": "Overview",
|
||||
"more_with": "More with {{name}}",
|
||||
"similar_items": "Similar Items",
|
||||
"no_similar_items_found": "No Similar Items Found",
|
||||
"similar_items": "Similar items",
|
||||
"no_similar_items_found": "No similar items found",
|
||||
"video": "Video",
|
||||
"more_details": "More Details",
|
||||
"media_options": "Media Options",
|
||||
"more_details": "More details",
|
||||
"quality": "Quality",
|
||||
"audio": "Audio",
|
||||
"subtitles": "Subtitle",
|
||||
"show_more": "Show More",
|
||||
"show_less": "Show Less",
|
||||
"appeared_in": "Appeared In",
|
||||
"could_not_load_item": "Could Not Load Item",
|
||||
"show_more": "Show more",
|
||||
"show_less": "Show less",
|
||||
"appeared_in": "Appeared in",
|
||||
"could_not_load_item": "Could not load item",
|
||||
"none": "None",
|
||||
"download": {
|
||||
"download_season": "Download Season",
|
||||
"download_series": "Download Series",
|
||||
"download_episode": "Download Episode",
|
||||
"download_movie": "Download Movie",
|
||||
"download_x_item": "Download {{item_count}} Items",
|
||||
"download_unwatched_only": "Unwatched Only",
|
||||
"download_button": "Download"
|
||||
"download_x_item": "Download {{item_count}} items",
|
||||
"download_button": "Download",
|
||||
"using_optimized_server": "Using optimized server",
|
||||
"using_default_method": "Using default method"
|
||||
}
|
||||
},
|
||||
"live_tv": {
|
||||
"next": "Next",
|
||||
"previous": "Previous",
|
||||
"coming_soon": "Coming Soon",
|
||||
"on_now": "On Now",
|
||||
"live_tv": "Live TV",
|
||||
"coming_soon": "Coming soon",
|
||||
"on_now": "On now",
|
||||
"shows": "Shows",
|
||||
"movies": "Movies",
|
||||
"sports": "Sports",
|
||||
@@ -658,16 +402,16 @@
|
||||
"confirm": "Confirm",
|
||||
"cancel": "Cancel",
|
||||
"yes": "Yes",
|
||||
"whats_wrong": "What's Wrong?",
|
||||
"issue_type": "Issue Type",
|
||||
"select_an_issue": "Select an Issue",
|
||||
"whats_wrong": "What's wrong?",
|
||||
"issue_type": "Issue type",
|
||||
"select_an_issue": "Select an issue",
|
||||
"types": "Types",
|
||||
"describe_the_issue": "(Optional) Describe the Issue...",
|
||||
"describe_the_issue": "(optional) Describe the issue...",
|
||||
"submit_button": "Submit",
|
||||
"report_issue_button": "Report Issue",
|
||||
"report_issue_button": "Report issue",
|
||||
"request_button": "Request",
|
||||
"are_you_sure_you_want_to_request_all_seasons": "Are you sure you want to request all seasons?",
|
||||
"failed_to_login": "Failed to Login",
|
||||
"failed_to_login": "Failed to login",
|
||||
"cast": "Cast",
|
||||
"details": "Details",
|
||||
"status": "Status",
|
||||
@@ -682,33 +426,25 @@
|
||||
"production_country": "Production Country",
|
||||
"studios": "Studios",
|
||||
"network": "Network",
|
||||
"currently_streaming_on": "Currently Streaming On",
|
||||
"currently_streaming_on": "Currently Streaming on",
|
||||
"advanced": "Advanced",
|
||||
"request_as": "Request As",
|
||||
"tags": "Tags",
|
||||
"quality_profile": "Quality Profile",
|
||||
"root_folder": "Root Folder",
|
||||
"season_all": "Season (All)",
|
||||
"season_x": "Season {{seasons}}",
|
||||
"season_number": "Season {{season_number}}",
|
||||
"number_episodes": "{{episode_number}} Episodes",
|
||||
"born": "Born",
|
||||
"appearances": "Appearances",
|
||||
"approve": "Approve",
|
||||
"decline": "Decline",
|
||||
"requested_by": "Requested by {{user}}",
|
||||
"unknown_user": "Unknown User",
|
||||
"toasts": {
|
||||
"jellyseer_does_not_meet_requirements": "Seerr server does not meet minimum version requirements! Please update to at least 2.0.0",
|
||||
"jellyseerr_test_failed": "Seerr test failed. Please try again.",
|
||||
"failed_to_test_jellyseerr_server_url": "Failed to test Seerr server url",
|
||||
"issue_submitted": "Issue Submitted!",
|
||||
"jellyseer_does_not_meet_requirements": "Jellyseerr server does not meet minimum version requirements! Please update to at least 2.0.0",
|
||||
"jellyseerr_test_failed": "Jellyseerr test failed. Please try again.",
|
||||
"failed_to_test_jellyseerr_server_url": "Failed to test jellyseerr server url",
|
||||
"issue_submitted": "Issue submitted!",
|
||||
"requested_item": "Requested {{item}}!",
|
||||
"you_dont_have_permission_to_request": "You don't have permission to request!",
|
||||
"something_went_wrong_requesting_media": "Something went wrong requesting media!",
|
||||
"request_approved": "Request Approved!",
|
||||
"request_declined": "Request Declined!",
|
||||
"failed_to_approve_request": "Failed to Approve Request",
|
||||
"failed_to_decline_request": "Failed to Decline Request"
|
||||
"something_went_wrong_requesting_media": "Something went wrong requesting media!"
|
||||
}
|
||||
},
|
||||
"tabs": {
|
||||
@@ -718,128 +454,14 @@
|
||||
"custom_links": "Custom Links",
|
||||
"favorites": "Favorites"
|
||||
},
|
||||
"music": {
|
||||
"title": "Music",
|
||||
"tabs": {
|
||||
"suggestions": "Suggestions",
|
||||
"albums": "Albums",
|
||||
"artists": "Artists",
|
||||
"playlists": "Playlists",
|
||||
"tracks": "tracks"
|
||||
},
|
||||
"filters": {
|
||||
"all": "All"
|
||||
},
|
||||
"recently_added": "Recently Added",
|
||||
"recently_played": "Recently Played",
|
||||
"frequently_played": "Frequently Played",
|
||||
"explore": "Explore",
|
||||
"top_tracks": "Top Tracks",
|
||||
"play": "Play",
|
||||
"shuffle": "Shuffle",
|
||||
"play_top_tracks": "Play Top Tracks",
|
||||
"no_suggestions": "No suggestions available",
|
||||
"no_albums": "No albums found",
|
||||
"no_artists": "No artists found",
|
||||
"no_playlists": "No playlists found",
|
||||
"album_not_found": "Album not found",
|
||||
"artist_not_found": "Artist not found",
|
||||
"playlist_not_found": "Playlist not found",
|
||||
"track_options": {
|
||||
"play_next": "Play Next",
|
||||
"add_to_queue": "Add to Queue",
|
||||
"add_to_playlist": "Add to Playlist",
|
||||
"download": "Download",
|
||||
"downloaded": "Downloaded",
|
||||
"downloading": "Downloading...",
|
||||
"cached": "Cached",
|
||||
"delete_download": "Delete Download",
|
||||
"delete_cache": "Remove from Cache",
|
||||
"go_to_artist": "Go to Artist",
|
||||
"go_to_album": "Go to Album",
|
||||
"add_to_favorites": "Add to Favorites",
|
||||
"remove_from_favorites": "Remove from Favorites",
|
||||
"remove_from_playlist": "Remove from Playlist"
|
||||
},
|
||||
"playlists": {
|
||||
"create_playlist": "Create Playlist",
|
||||
"playlist_name": "Playlist Name",
|
||||
"enter_name": "Enter playlist name",
|
||||
"create": "Create",
|
||||
"search_playlists": "Search playlists...",
|
||||
"added_to": "Added to {{name}}",
|
||||
"added": "Added to playlist",
|
||||
"removed_from": "Removed from {{name}}",
|
||||
"removed": "Removed from playlist",
|
||||
"created": "Playlist created",
|
||||
"create_new": "Create New Playlist",
|
||||
"failed_to_add": "Failed to add to playlist",
|
||||
"failed_to_remove": "Failed to remove from playlist",
|
||||
"failed_to_create": "Failed to create playlist",
|
||||
"delete_playlist": "Delete Playlist",
|
||||
"delete_confirm": "Are you sure you want to delete \"{{name}}\"? This action cannot be undone.",
|
||||
"deleted": "Playlist deleted",
|
||||
"failed_to_delete": "Failed to delete playlist"
|
||||
},
|
||||
"sort": {
|
||||
"title": "Sort By",
|
||||
"alphabetical": "Alphabetical",
|
||||
"date_created": "Date Created"
|
||||
}
|
||||
},
|
||||
"watchlists": {
|
||||
"title": "Watchlists",
|
||||
"my_watchlists": "My Watchlists",
|
||||
"public_watchlists": "Public Watchlists",
|
||||
"create_title": "Create Watchlist",
|
||||
"edit_title": "Edit Watchlist",
|
||||
"create_button": "Create Watchlist",
|
||||
"save_button": "Save Changes",
|
||||
"delete_button": "Delete",
|
||||
"remove_button": "Remove",
|
||||
"cancel_button": "Cancel",
|
||||
"name_label": "Name",
|
||||
"name_placeholder": "Enter watchlist name",
|
||||
"description_label": "Description",
|
||||
"description_placeholder": "Enter description (optional)",
|
||||
"is_public_label": "Public Watchlist",
|
||||
"is_public_description": "Allow others to view this watchlist",
|
||||
"allowed_type_label": "Content Type",
|
||||
"sort_order_label": "Default Sort Order",
|
||||
"empty_title": "No Watchlists",
|
||||
"empty_description": "Create your first watchlist to start organizing your media",
|
||||
"empty_watchlist": "This watchlist is empty",
|
||||
"empty_watchlist_hint": "Add items from your library to this watchlist",
|
||||
"not_configured_title": "Streamystats Not Configured",
|
||||
"not_configured_description": "Configure Streamystats in settings to use watchlists",
|
||||
"go_to_settings": "Go to Settings",
|
||||
"add_to_watchlist": "Add to Watchlist",
|
||||
"remove_from_watchlist": "Remove from Watchlist",
|
||||
"select_watchlist": "Select Watchlist",
|
||||
"create_new": "Create New Watchlist",
|
||||
"item": "item",
|
||||
"items": "items",
|
||||
"public": "Public",
|
||||
"private": "Private",
|
||||
"you": "You",
|
||||
"by_owner": "By another user",
|
||||
"not_found": "Watchlist not found",
|
||||
"delete_confirm_title": "Delete Watchlist",
|
||||
"delete_confirm_message": "Are you sure you want to delete \"{{name}}\"? This action cannot be undone.",
|
||||
"remove_item_title": "Remove from Watchlist",
|
||||
"remove_item_message": "Remove \"{{name}}\" from this watchlist?",
|
||||
"loading": "Loading watchlists...",
|
||||
"no_compatible_watchlists": "No compatible watchlists",
|
||||
"create_one_first": "Create a watchlist that accepts this content type"
|
||||
},
|
||||
"playback_speed": {
|
||||
"title": "Playback Speed",
|
||||
"apply_to": "Apply To",
|
||||
"speed": "Speed",
|
||||
"scope": {
|
||||
"media": "This media only",
|
||||
"show": "This show",
|
||||
"all": "All media (default)"
|
||||
}
|
||||
"chromecast": {
|
||||
"no_devices_available": "No Google Cast devices available.",
|
||||
"are_you_on_same_network": "Are you on the same network?",
|
||||
"no_device_selected": "No device selected",
|
||||
"click_icon_to_connect": "Click icon to connect.",
|
||||
"establishing_connection": "Establishing connection...",
|
||||
"no_media_selected": "No media selected.",
|
||||
"start_playing": "Start playing any media",
|
||||
"go_home": "Go Home"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user