forked from Ninjalama/streamyfin_mirror
More Translations
This commit is contained in:
@@ -7,6 +7,7 @@ import { ListItem } from "@/components/list/ListItem";
|
||||
import * as WebBrowser from "expo-web-browser";
|
||||
import Ionicons from "@expo/vector-icons/Ionicons";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export interface MenuLink {
|
||||
name: string;
|
||||
@@ -18,6 +19,7 @@ export default function menuLinks() {
|
||||
const [api] = useAtom(apiAtom);
|
||||
const insets = useSafeAreaInsets();
|
||||
const [menuLinks, setMenuLinks] = useState<MenuLink[]>([]);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const getMenuLinks = useCallback(async () => {
|
||||
try {
|
||||
@@ -67,7 +69,7 @@ export default function menuLinks() {
|
||||
)}
|
||||
ListEmptyComponent={
|
||||
<View className="flex flex-col items-center justify-center h-full">
|
||||
<Text className="font-bold text-xl text-neutral-500">No links</Text>
|
||||
<Text className="font-bold text-xl text-neutral-500">{t("custom_links.no_links")}</Text>
|
||||
</View>
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -11,10 +11,13 @@ import { useAtom } from "jotai";
|
||||
import { useEffect, useState } from "react";
|
||||
import { ActivityIndicator, TouchableOpacity, View } from "react-native";
|
||||
import { toast } from "sonner-native";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function page() {
|
||||
const navigation = useNavigation();
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [api] = useAtom(apiAtom);
|
||||
const [settings, updateSettings] = useSettings();
|
||||
|
||||
@@ -24,7 +27,7 @@ export default function page() {
|
||||
const saveMutation = useMutation({
|
||||
mutationFn: async (newVal: string) => {
|
||||
if (newVal.length === 0 || !newVal.startsWith("http")) {
|
||||
toast.error("Invalid URL");
|
||||
toast.error(t("home.settings.toasts.invalid_url"));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -42,13 +45,13 @@ export default function page() {
|
||||
},
|
||||
onSuccess: (data) => {
|
||||
if (data) {
|
||||
toast.success("Connected");
|
||||
toast.success(t("home.settings.toasts.connected"));
|
||||
} else {
|
||||
toast.error("Could not connect");
|
||||
toast.error(t("home.settings.toasts.could_not_connect"));
|
||||
}
|
||||
},
|
||||
onError: () => {
|
||||
toast.error("Could not connect");
|
||||
toast.error(t("home.settings.toasts.could_not_connect"));
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -10,10 +10,13 @@ import { useAtom } from "jotai";
|
||||
import { useEffect, useState } from "react";
|
||||
import { ActivityIndicator, TouchableOpacity, View } from "react-native";
|
||||
import { toast } from "sonner-native";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function page() {
|
||||
const navigation = useNavigation();
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [api] = useAtom(apiAtom);
|
||||
const [settings, updateSettings] = useSettings();
|
||||
|
||||
@@ -23,7 +26,7 @@ export default function page() {
|
||||
const saveMutation = useMutation({
|
||||
mutationFn: async (newVal: string) => {
|
||||
if (newVal.length === 0 || !newVal.startsWith("http")) {
|
||||
toast.error("Invalid URL");
|
||||
toast.error(t("home.settings.toasts.invalid_url"));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -41,13 +44,13 @@ export default function page() {
|
||||
},
|
||||
onSuccess: (data) => {
|
||||
if (data) {
|
||||
toast.success("Connected");
|
||||
toast.success(t("home.settings.toasts.connected"));
|
||||
} else {
|
||||
toast.error("Could not connect");
|
||||
toast.error(t("home.settings.toasts.could_not_connect"));
|
||||
}
|
||||
},
|
||||
onError: () => {
|
||||
toast.error("Could not connect");
|
||||
toast.error(t("home.settings.toasts.could_not_connect"));
|
||||
},
|
||||
});
|
||||
|
||||
@@ -57,13 +60,13 @@ export default function page() {
|
||||
|
||||
useEffect(() => {
|
||||
navigation.setOptions({
|
||||
title: "Optimized Server",
|
||||
title: t("home.settings.downloads.optimized_server"),
|
||||
headerRight: () =>
|
||||
saveMutation.isPending ? (
|
||||
<ActivityIndicator size={"small"} color={"white"} />
|
||||
) : (
|
||||
<TouchableOpacity onPress={() => onSave(optimizedVersionsServerUrl)}>
|
||||
<Text className="text-blue-500">Save</Text>
|
||||
<Text className="text-blue-500">{t("home.settings.downloads.save_button")}</Text>
|
||||
</TouchableOpacity>
|
||||
),
|
||||
});
|
||||
|
||||
@@ -18,10 +18,12 @@ import { useLocalSearchParams } from "expo-router";
|
||||
import { useAtom } from "jotai";
|
||||
import { useCallback, useMemo } from "react";
|
||||
import { View } from "react-native";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const page: React.FC = () => {
|
||||
const local = useLocalSearchParams();
|
||||
const { actorId } = local as { actorId: string };
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [api] = useAtom(apiAtom);
|
||||
const [user] = useAtom(userAtom);
|
||||
@@ -110,7 +112,7 @@ const page: React.FC = () => {
|
||||
</View>
|
||||
|
||||
<Text className="px-4 text-2xl font-bold mb-2 text-neutral-100">
|
||||
Appeared In
|
||||
{t("item_card.appeared_in")}
|
||||
</Text>
|
||||
<InfiniteHorizontalScroll
|
||||
height={247}
|
||||
|
||||
@@ -10,6 +10,7 @@ import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
||||
import { getItemsApi } from "@jellyfin/sdk/lib/utils/api";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { router, useLocalSearchParams, useNavigation } from "expo-router";
|
||||
import { t } from "i18next";
|
||||
import { useAtom } from "jotai";
|
||||
import { useEffect, useState } from "react";
|
||||
import { ScrollView, TouchableOpacity, View } from "react-native";
|
||||
@@ -112,7 +113,7 @@ export default function page() {
|
||||
<View className="px-4 mb-8">
|
||||
<Text className="font-bold text-2xl mb-2">{album?.Name}</Text>
|
||||
<Text className="text-neutral-500">
|
||||
{songs?.TotalRecordCount} songs
|
||||
{t("item_card.x_songs", { count: songs?.TotalRecordCount })}
|
||||
</Text>
|
||||
</View>
|
||||
<View className="px-4">
|
||||
|
||||
@@ -5,6 +5,7 @@ import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
||||
import { getItemsApi } from "@jellyfin/sdk/lib/utils/api";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { router, useLocalSearchParams, useNavigation } from "expo-router";
|
||||
import { t } from "i18next";
|
||||
import { useAtom } from "jotai";
|
||||
import { useEffect, useState } from "react";
|
||||
import { FlatList, ScrollView, TouchableOpacity, View } from "react-native";
|
||||
@@ -107,7 +108,7 @@ export default function page() {
|
||||
<View className="px-4 mb-8">
|
||||
<Text className="font-bold text-2xl mb-2">{artist?.Name}</Text>
|
||||
<Text className="text-neutral-500">
|
||||
{albums.TotalRecordCount} albums
|
||||
{t("item_card.x_albums", { count: albums.TotalRecordCount })}
|
||||
</Text>
|
||||
</View>
|
||||
<View className="flex flex-row flex-wrap justify-between px-4">
|
||||
|
||||
@@ -7,6 +7,7 @@ import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
||||
import { getArtistsApi, getItemsApi } from "@jellyfin/sdk/lib/utils/api";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { router, useLocalSearchParams } from "expo-router";
|
||||
import { t } from "i18next";
|
||||
import { useAtom } from "jotai";
|
||||
import { useMemo, useState } from "react";
|
||||
import { FlatList, TouchableOpacity, View } from "react-native";
|
||||
@@ -81,7 +82,7 @@ export default function page() {
|
||||
}}
|
||||
ListHeaderComponent={
|
||||
<View className="mb-4">
|
||||
<Text className="font-bold text-3xl mb-2">Artists</Text>
|
||||
<Text className="font-bold text-3xl mb-2">{t("item_card.artists")}</Text>
|
||||
</View>
|
||||
}
|
||||
nestedScrollEnabled
|
||||
|
||||
@@ -377,7 +377,7 @@ const page: React.FC = () => {
|
||||
<FlashList
|
||||
ListEmptyComponent={
|
||||
<View className="flex flex-col items-center justify-center h-full">
|
||||
<Text className="font-bold text-xl text-neutral-500">No results</Text>
|
||||
<Text className="font-bold text-xl text-neutral-500">{t("search.no_results")}</Text>
|
||||
</View>
|
||||
}
|
||||
extraData={[
|
||||
|
||||
@@ -13,11 +13,13 @@ import Animated, {
|
||||
useSharedValue,
|
||||
withTiming,
|
||||
} from "react-native-reanimated";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const Page: React.FC = () => {
|
||||
const [api] = useAtom(apiAtom);
|
||||
const [user] = useAtom(userAtom);
|
||||
const { id } = useLocalSearchParams() as { id: string };
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { data: item, isError } = useQuery({
|
||||
queryKey: ["item", id],
|
||||
@@ -74,7 +76,7 @@ const Page: React.FC = () => {
|
||||
if (isError)
|
||||
return (
|
||||
<View className="flex flex-col items-center justify-center h-screen w-screen">
|
||||
<Text>Could not load item</Text>
|
||||
<Text>{t("item_card.could_not_load_item")}</Text>
|
||||
</View>
|
||||
);
|
||||
|
||||
|
||||
@@ -27,10 +27,12 @@ import * as DropdownMenu from "zeego/dropdown-menu";
|
||||
import { TvDetails } from "@/utils/jellyseerr/server/models/Tv";
|
||||
import JellyseerrSeasons from "@/components/series/JellyseerrSeasons";
|
||||
import { JellyserrRatings } from "@/components/Ratings";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const Page: React.FC = () => {
|
||||
const insets = useSafeAreaInsets();
|
||||
const params = useLocalSearchParams();
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
mediaTitle,
|
||||
releaseYear,
|
||||
@@ -184,7 +186,7 @@ const Page: React.FC = () => {
|
||||
</View>
|
||||
{canRequest ? (
|
||||
<Button color="purple" onPress={request}>
|
||||
Request
|
||||
{t("jellyseerr.request_button")}
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
@@ -199,7 +201,7 @@ const Page: React.FC = () => {
|
||||
borderStyle: "solid",
|
||||
}}
|
||||
>
|
||||
Report issue
|
||||
{t("jellyseerr.report_issue_button")}
|
||||
</Button>
|
||||
)}
|
||||
<OverviewText text={result.overview} className="mt-4" />
|
||||
@@ -231,7 +233,7 @@ const Page: React.FC = () => {
|
||||
<View className="flex flex-col space-y-4 px-4 pb-8 pt-2">
|
||||
<View>
|
||||
<Text className="font-bold text-2xl text-neutral-100">
|
||||
Whats wrong?
|
||||
{t("jellyseerr.whats_wrong")}
|
||||
</Text>
|
||||
</View>
|
||||
<View className="flex flex-col space-y-2 items-start">
|
||||
@@ -240,13 +242,13 @@ const Page: React.FC = () => {
|
||||
<DropdownMenu.Trigger>
|
||||
<View className="flex flex-col">
|
||||
<Text className="opacity-50 mb-1 text-xs">
|
||||
Issue Type
|
||||
{t("jellyseerr.issue_type")}
|
||||
</Text>
|
||||
<TouchableOpacity className="bg-neutral-900 h-10 rounded-xl border-neutral-800 border px-3 py-2 flex flex-row items-center justify-between">
|
||||
<Text style={{}} className="" numberOfLines={1}>
|
||||
{issueType
|
||||
? IssueTypeName[issueType]
|
||||
: "Select an issue"}
|
||||
: t("jellyseerr.select_an_issue")}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
@@ -260,7 +262,7 @@ const Page: React.FC = () => {
|
||||
collisionPadding={0}
|
||||
sideOffset={0}
|
||||
>
|
||||
<DropdownMenu.Label>Types</DropdownMenu.Label>
|
||||
<DropdownMenu.Label>{t("jellyseerr.types")}</DropdownMenu.Label>
|
||||
{Object.entries(IssueTypeName)
|
||||
.reverse()
|
||||
.map(([key, value], idx) => (
|
||||
@@ -287,7 +289,7 @@ const Page: React.FC = () => {
|
||||
maxLength={254}
|
||||
style={{color: "white"}}
|
||||
clearButtonMode="always"
|
||||
placeholder="(optional) Describe the issue..."
|
||||
placeholder={t("jellyseerr.describe_the_issue")}
|
||||
placeholderTextColor="#9CA3AF"
|
||||
// Issue with multiline + Textinput inside a portal
|
||||
// https://github.com/callstack/react-native-paper/issues/1668
|
||||
@@ -297,7 +299,7 @@ const Page: React.FC = () => {
|
||||
</View>
|
||||
</View>
|
||||
<Button className="mt-auto" onPress={submitIssue} color="purple">
|
||||
Submit
|
||||
{t("jellyseerr.submit_button")}
|
||||
</Button>
|
||||
</View>
|
||||
</BottomSheetView>
|
||||
|
||||
@@ -320,7 +320,7 @@ export default function search() {
|
||||
<View className="flex flex-row flex-wrap space-x-2 px-4 mb-2">
|
||||
<TouchableOpacity onPress={() => setSearchType("Library")}>
|
||||
<Tag
|
||||
text="Library"
|
||||
text={t("search.library")}
|
||||
textClass="p-1"
|
||||
className={
|
||||
searchType === "Library" ? "bg-purple-600" : undefined
|
||||
@@ -329,7 +329,7 @@ export default function search() {
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity onPress={() => setSearchType("Discover")}>
|
||||
<Tag
|
||||
text="Discover"
|
||||
text={t("search.discover")}
|
||||
textClass="p-1"
|
||||
className={
|
||||
searchType === "Discover" ? "bg-purple-600" : undefined
|
||||
@@ -341,7 +341,7 @@ export default function search() {
|
||||
{!!q && (
|
||||
<View className="px-4 flex flex-col space-y-2">
|
||||
<Text className="text-neutral-500 ">
|
||||
Results for <Text className="text-purple-600">{q}</Text>
|
||||
{t("search.results_for_x")} <Text className="text-purple-600">{q}</Text>
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
|
||||
@@ -423,7 +423,7 @@ export default function page() {
|
||||
if (isErrorItem || isErrorStreamUrl)
|
||||
return (
|
||||
<View className="w-screen h-screen flex flex-col items-center justify-center bg-black">
|
||||
<Text className="text-white">Error</Text>
|
||||
<Text className="text-white">{t("player.error")}</Text>
|
||||
</View>
|
||||
);
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ import React, { useCallback, useMemo, useRef, useState } from "react";
|
||||
import { Pressable, useWindowDimensions, View } from "react-native";
|
||||
import { useSharedValue } from "react-native-reanimated";
|
||||
import Video, { OnProgressData, VideoRef } from "react-native-video";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function page() {
|
||||
const api = useAtomValue(apiAtom);
|
||||
@@ -32,6 +33,7 @@ export default function page() {
|
||||
const [settings] = useSettings();
|
||||
const videoRef = useRef<VideoRef | null>(null);
|
||||
const windowDimensions = useWindowDimensions();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const firstTime = useRef(true);
|
||||
|
||||
@@ -278,14 +280,14 @@ export default function page() {
|
||||
if (isErrorItem || isErrorStreamUrl)
|
||||
return (
|
||||
<View className="w-screen h-screen flex flex-col items-center justify-center bg-black">
|
||||
<Text className="text-white">Error</Text>
|
||||
<Text className="text-white">{t("player.error")}</Text>
|
||||
</View>
|
||||
);
|
||||
|
||||
if (!item || !stream)
|
||||
return (
|
||||
<View className="w-screen h-screen flex flex-col items-center justify-center bg-black">
|
||||
<Text className="text-white">Error</Text>
|
||||
<Text className="text-white">{t("player.error")}</Text>
|
||||
</View>
|
||||
);
|
||||
|
||||
|
||||
@@ -39,12 +39,14 @@ import Video, {
|
||||
VideoRef,
|
||||
} from "react-native-video";
|
||||
import { SubtitleHelper } from "@/utils/SubtitleHelper";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const Player = () => {
|
||||
const api = useAtomValue(apiAtom);
|
||||
const user = useAtomValue(userAtom);
|
||||
const [settings] = useSettings();
|
||||
const videoRef = useRef<VideoRef | null>(null);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const firstTime = useRef(true);
|
||||
const revalidateProgressCache = useInvalidatePlaybackProgressCache();
|
||||
@@ -373,7 +375,7 @@ const Player = () => {
|
||||
if (isErrorItem || isErrorStreamUrl)
|
||||
return (
|
||||
<View className="w-screen h-screen flex flex-col items-center justify-center bg-black">
|
||||
<Text className="text-white">Error</Text>
|
||||
<Text className="text-white">{t("player.error")}</Text>
|
||||
</View>
|
||||
);
|
||||
|
||||
@@ -440,7 +442,7 @@ const Player = () => {
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<Text>No video source...</Text>
|
||||
<Text>{t("player.no_video_source")}</Text>
|
||||
)}
|
||||
</View>
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import { TouchableOpacity, View } from "react-native";
|
||||
import * as DropdownMenu from "zeego/dropdown-menu";
|
||||
import { Text } from "./common/Text";
|
||||
import { convertBitsToMegabitsOrGigabits } from "@/utils/bToMb";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
interface Props extends React.ComponentProps<typeof View> {
|
||||
item: BaseItemDto;
|
||||
@@ -29,6 +30,8 @@ export const MediaSourceSelector: React.FC<Props> = ({
|
||||
[item, selected]
|
||||
);
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<View
|
||||
className="flex shrink"
|
||||
@@ -39,7 +42,7 @@ export const MediaSourceSelector: React.FC<Props> = ({
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger>
|
||||
<View className="flex flex-col" {...props}>
|
||||
<Text className="opacity-50 mb-1 text-xs">Video</Text>
|
||||
<Text className="opacity-50 mb-1 text-xs">{t("item_card.video")}</Text>
|
||||
<TouchableOpacity className="bg-neutral-900 h-10 rounded-xl border-neutral-800 border px-3 py-2 flex flex-row items-center">
|
||||
<Text numberOfLines={1}>{selectedName}</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
@@ -15,6 +15,7 @@ import Animated, {
|
||||
} from "react-native-reanimated";
|
||||
import { Loader } from "../Loader";
|
||||
import { Text } from "./Text";
|
||||
import { t } from "i18next";
|
||||
|
||||
interface HorizontalScrollProps
|
||||
extends Omit<FlashListProps<BaseItemDto>, "renderItem" | "data" | "style"> {
|
||||
@@ -136,7 +137,7 @@ export function InfiniteHorizontalScroll({
|
||||
showsHorizontalScrollIndicator={false}
|
||||
ListEmptyComponent={
|
||||
<View className="flex-1 justify-center items-center">
|
||||
<Text className="text-center text-gray-500">No data available</Text>
|
||||
<Text className="text-center text-gray-500">{t("item_card.no_data_available")}</Text>
|
||||
</View>
|
||||
}
|
||||
{...props}
|
||||
|
||||
@@ -154,7 +154,7 @@ const DownloadCard = ({ process, ...props }: DownloadCardProps) => {
|
||||
<Text className="text-xs">{process.speed?.toFixed(2)}x</Text>
|
||||
)}
|
||||
{eta(process) && (
|
||||
<Text className="text-xs">ETA {eta(process)}</Text>
|
||||
<Text className="text-xs">{t("home.downloads.eta", {eta: eta(process)})}</Text>
|
||||
)}
|
||||
</View>
|
||||
|
||||
|
||||
@@ -155,7 +155,7 @@ export const FilterSheet = <T,>({
|
||||
>
|
||||
<View className="px-4 mt-2 mb-8">
|
||||
<Text className="font-bold text-2xl">{title}</Text>
|
||||
<Text className="mb-2 text-neutral-500">{_data?.length} items</Text>
|
||||
<Text className="mb-2 text-neutral-500">{t("search.items", {count: _data?.length})}</Text>
|
||||
{showSearch && (
|
||||
<Input
|
||||
placeholder={t("search.search")}
|
||||
|
||||
@@ -11,6 +11,7 @@ import ContinueWatchingPoster from "../ContinueWatchingPoster";
|
||||
import { ItemCardText } from "../ItemCardText";
|
||||
import { TouchableItemRouter } from "../common/TouchableItemRouter";
|
||||
import SeriesPoster from "../posters/SeriesPoster";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
interface Props extends ViewProps {
|
||||
title?: string | null;
|
||||
@@ -43,6 +44,8 @@ export const ScrollingCollectionList: React.FC<Props> = ({
|
||||
|
||||
if (hideIfEmpty === true && data?.length === 0) return null;
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<View {...props}>
|
||||
<Text className="px-4 text-lg font-bold mb-2 text-neutral-100">
|
||||
@@ -50,7 +53,7 @@ export const ScrollingCollectionList: React.FC<Props> = ({
|
||||
</Text>
|
||||
{isLoading === false && data?.length === 0 && (
|
||||
<View className="px-4">
|
||||
<Text className="text-neutral-500">No items</Text>
|
||||
<Text className="text-neutral-500">{t("home.no_items")}</Text>
|
||||
</View>
|
||||
)}
|
||||
{isLoading ? (
|
||||
|
||||
@@ -211,7 +211,7 @@ export const SeasonPicker: React.FC<Props> = ({ item, initialSeasonIndex }) => {
|
||||
{(episodes?.length || 0) === 0 ? (
|
||||
<View className="flex flex-col">
|
||||
<Text className="text-neutral-500">
|
||||
{t("item_card.no_episodes_for_this_seasonz")}
|
||||
{t("item_card.no_episodes_for_this_season")}
|
||||
</Text>
|
||||
</View>
|
||||
) : null}
|
||||
|
||||
@@ -30,8 +30,8 @@ export const DownloadSettings: React.FC = ({ ...props }) => {
|
||||
<TouchableOpacity className="flex flex-row items-center justify-between py-3 pl-3">
|
||||
<Text className="mr-1 text-[#8E8D91]">
|
||||
{settings.downloadMethod === "remux"
|
||||
? "Default"
|
||||
: "Optimized"}
|
||||
? t("home.settings.downloads.default")
|
||||
: t("home.settings.downloads.optimized")}
|
||||
</Text>
|
||||
<Ionicons
|
||||
name="chevron-expand-sharp"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { TextInput, View, Linking } from "react-native";
|
||||
import { Text } from "../common/Text";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
interface Props {
|
||||
value: string;
|
||||
@@ -14,14 +15,16 @@ export const OptimizedServerForm: React.FC<Props> = ({
|
||||
Linking.openURL("https://github.com/streamyfin/optimized-versions-server");
|
||||
};
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<View>
|
||||
<View className="flex flex-col rounded-xl overflow-hidden pl-4 bg-neutral-900 px-4">
|
||||
<View className={`flex flex-row items-center bg-neutral-900 h-11 pr-4`}>
|
||||
<Text className="mr-4">URL</Text>
|
||||
<Text className="mr-4">{t("home.settings.downloads.url")}</Text>
|
||||
<TextInput
|
||||
className="text-white"
|
||||
placeholder="http(s)://domain.org:port"
|
||||
placeholder={t("home.settings.downloads.server_url_placeholder")}
|
||||
value={value}
|
||||
keyboardType="url"
|
||||
returnKeyType="done"
|
||||
@@ -32,10 +35,9 @@ export const OptimizedServerForm: React.FC<Props> = ({
|
||||
</View>
|
||||
</View>
|
||||
<Text className="px-4 text-xs text-neutral-500 mt-1">
|
||||
Enter the URL for the optimize server. The URL should include http or
|
||||
https and optionally the port.{" "}
|
||||
{t("home.settings.downloads.optimized_version_hint")}{" "}
|
||||
<Text className="text-blue-500" onPress={handleOpenLink}>
|
||||
Read more about the optimize server.
|
||||
{t("home.settings.downloads.read_more_about_optimized_server")}
|
||||
</Text>
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
@@ -86,7 +86,7 @@ export const QuickConnect: React.FC<Props> = ({ ...props }) => {
|
||||
<View className="flex flex-col space-y-4 px-4 pb-8 pt-2">
|
||||
<View>
|
||||
<Text className="font-bold text-2xl text-neutral-100">
|
||||
Quick Connect
|
||||
{t("home.settings.quick_connect.quick_connect_title")}
|
||||
</Text>
|
||||
</View>
|
||||
<View className="flex flex-col space-y-2">
|
||||
|
||||
@@ -9,6 +9,7 @@ import Animated, {
|
||||
runOnJS,
|
||||
} from "react-native-reanimated";
|
||||
import { Colors } from "@/constants/Colors";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
interface NextEpisodeCountDownButtonProps extends TouchableOpacityProps {
|
||||
onFinish?: () => void;
|
||||
@@ -63,6 +64,8 @@ const NextEpisodeCountDownButton: React.FC<NextEpisodeCountDownButtonProps> = ({
|
||||
return null;
|
||||
}
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
className="w-32 overflow-hidden rounded-md bg-black/60 border border-neutral-900"
|
||||
@@ -71,7 +74,7 @@ const NextEpisodeCountDownButton: React.FC<NextEpisodeCountDownButtonProps> = ({
|
||||
>
|
||||
<Animated.View style={animatedStyle} />
|
||||
<View className="px-3 py-3">
|
||||
<Text className="text-center font-bold">Next Episode</Text>
|
||||
<Text className="text-center font-bold">{t("player.next_episode")}</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
|
||||
@@ -6,6 +6,7 @@ import React, { useEffect, useState } from "react";
|
||||
import { TouchableOpacity, View, ViewProps } from "react-native";
|
||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||
import { Text } from "../common/Text";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
interface Props extends ViewProps {
|
||||
playerRef: React.RefObject<VlcPlayerViewRef>;
|
||||
@@ -32,6 +33,8 @@ export const VideoDebugInfo: React.FC<Props> = ({ playerRef, ...props }) => {
|
||||
|
||||
const insets = useSafeAreaInsets();
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
@@ -42,19 +45,19 @@ export const VideoDebugInfo: React.FC<Props> = ({ playerRef, ...props }) => {
|
||||
}}
|
||||
{...props}
|
||||
>
|
||||
<Text className="font-bold">Playback State:</Text>
|
||||
<Text className="font-bold mt-2.5">Audio Tracks:</Text>
|
||||
<Text className="font-bold">{t("item_card.playback_state")}</Text>
|
||||
<Text className="font-bold mt-2.5">{t("item_card.audio_tracks")}</Text>
|
||||
{audioTracks &&
|
||||
audioTracks.map((track, index) => (
|
||||
<Text key={index}>
|
||||
{track.name} (Index: {track.index})
|
||||
{track.name} ({t("item_card.index")} {track.index})
|
||||
</Text>
|
||||
))}
|
||||
<Text className="font-bold mt-2.5">Subtitle Tracks:</Text>
|
||||
<Text className="font-bold mt-2.5">{t("item_card.subtitles_tracks")}</Text>
|
||||
{subtitleTracks &&
|
||||
subtitleTracks.map((track, index) => (
|
||||
<Text key={index}>
|
||||
{track.name} (Index: {track.index})
|
||||
{track.name} ({t("item_card.index")} {track.index})
|
||||
</Text>
|
||||
))}
|
||||
<TouchableOpacity
|
||||
@@ -66,7 +69,7 @@ export const VideoDebugInfo: React.FC<Props> = ({ playerRef, ...props }) => {
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Text className="text-white text-center">Refresh Tracks</Text>
|
||||
<Text className="text-white text-center">{t("item_card.refresh_tracks")}</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
);
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
"home": {
|
||||
"home": "Home",
|
||||
"no_internet": "No Internet",
|
||||
"no_items": "No items",
|
||||
"no_internet_message": "No worries, you can still watch\ndownloaded content.",
|
||||
"go_to_downloads": "Go to downloads",
|
||||
"oops": "Oops!",
|
||||
@@ -46,7 +47,7 @@
|
||||
"app_version": "App Version"
|
||||
},
|
||||
"quick_connect": {
|
||||
"quick_connect_title": "Quick connect",
|
||||
"quick_connect_title": "Quick Connect",
|
||||
"authorize_button": "Authorize Quick Connect",
|
||||
"enter_the_quick_connect_code": "Enter the Quick Connect code",
|
||||
"success": "Success",
|
||||
@@ -87,7 +88,15 @@
|
||||
"download_method": "Download method",
|
||||
"remux_max_download": "Remux max download",
|
||||
"auto_download": "Auto download",
|
||||
"optimized_versions_server": "Optimized versions server"
|
||||
"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",
|
||||
@@ -149,7 +158,8 @@
|
||||
"background_downloads_enabled": "Background downloads enabled",
|
||||
"background_downloads_disabled": "Background downloads disabled",
|
||||
"connected": "Connected",
|
||||
"could_not_connect": "Could not connect"
|
||||
"could_not_connect": "Could not connect",
|
||||
"invalid_url": "Invalid URL"
|
||||
}
|
||||
},
|
||||
"downloads": {
|
||||
@@ -172,6 +182,7 @@
|
||||
"delete": "Delete",
|
||||
"something_went_wrong": "Something went wrong",
|
||||
"could_not_get_stream_url_from_jellyfin": "Could not get stream URL from Jellyfin",
|
||||
"eta": "ETA {{eta}}",
|
||||
"toasts": {
|
||||
"you_are_not_allowed_to_download_files": "You are not allowed to download files.",
|
||||
"deleted_all_movies_successfully": "Deleted all movies successfully!",
|
||||
@@ -199,9 +210,14 @@
|
||||
}
|
||||
},
|
||||
"search": {
|
||||
"results_for_x": "Results for ",
|
||||
"search_title": "Search",
|
||||
"search_here": "Search here...",
|
||||
"search": "Search...",
|
||||
"x_items": "{{count}} items",
|
||||
"library": "Library",
|
||||
"discover": "Discover",
|
||||
"no_results": "No results",
|
||||
"no_results_found_for": "No results found for",
|
||||
"movies": "Movies",
|
||||
"series": "Series",
|
||||
@@ -248,6 +264,9 @@
|
||||
"music_albums": "Music Albums",
|
||||
"audio": "Audio"
|
||||
},
|
||||
"custom_links": {
|
||||
"no_links": "No links"
|
||||
},
|
||||
"player": {
|
||||
"error": "Error",
|
||||
"failed_to_get_stream_url": "Failed to get stream URL",
|
||||
@@ -255,7 +274,9 @@
|
||||
"client_error": "Client error",
|
||||
"could_not_create_stream_for_chromecast": "Could not create stream for Chromecast",
|
||||
"message_from_server": "Message from server: {{message}}",
|
||||
"video_has_finished_playing": "Video has finished playing!"
|
||||
"video_has_finished_playing": "Video has finished playing!",
|
||||
"no_video_source": "No video source...",
|
||||
"next_episode": "Next Episode"
|
||||
},
|
||||
"item_card": {
|
||||
"next_up": "Next up",
|
||||
@@ -264,7 +285,6 @@
|
||||
"series": "Series",
|
||||
"seasons": "Seasons",
|
||||
"season": "Season",
|
||||
"episodes": "Episodes",
|
||||
"no_episodes_for_this_season": "No episodes for this season",
|
||||
"overview": "Overview",
|
||||
"more_with": "More with {{name}}",
|
||||
@@ -277,18 +297,37 @@
|
||||
"subtitles": "Subtitle",
|
||||
"show_more": "Show more",
|
||||
"show_less": "Show less",
|
||||
"appeared_in": "Appeared in",
|
||||
"x_songs": "{{count}} songs",
|
||||
"x_albums": "{{count}} albums",
|
||||
"artists": "Artists",
|
||||
"could_not_load_item": "Could not load item",
|
||||
"refresh_tracks": "Refresh Tracks",
|
||||
"subtitle_tracks": "Subtitle Tracks:",
|
||||
"audio_tracks": "Audio Tracks:",
|
||||
"playback_state": "Playback State:",
|
||||
"no_data_available": "No data available",
|
||||
"index": "Index:",
|
||||
"download": {
|
||||
"download_season": "Download Season",
|
||||
"download_x_item": "Download {{item_count}} items",
|
||||
"download_button": "Download",
|
||||
"using_optimized_server": "Using optimized server",
|
||||
"using_default_method": "Using default method",
|
||||
"using_default_method": "Using default method"
|
||||
}
|
||||
},
|
||||
"jellyseerr":{
|
||||
"confirm": "Confirm",
|
||||
"cancel": "Cancel",
|
||||
"yes": "Yes",
|
||||
"whats_wrong": "What's wrong?",
|
||||
"issue_type": "Issue type",
|
||||
"select_an_issue": "Select an issue",
|
||||
"types": "Types",
|
||||
"describe_the_issue": "(optional) Describe the issue...",
|
||||
"submit_button": "Submit",
|
||||
"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",
|
||||
"toasts": {
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
"login_to_title": "Se connecter à",
|
||||
"username_placeholder": "Nom d'utilisateur",
|
||||
"password_placeholder": "Mot de passe",
|
||||
"use_quick_connect": "Utiliser Quick Connect",
|
||||
"use_quick_connect": "Utiliser Connexion Rapide",
|
||||
"login_button": "Se connecter",
|
||||
"quick_connect": "Quick Connect",
|
||||
"quick_connect": "Connexion Rapide",
|
||||
"enter_code_to_login": "Entrez le code {{code}} pour vous connecter",
|
||||
"failed_to_initiate_quick_connect": "Échec de l'initialisation de Quick Connect",
|
||||
"failed_to_initiate_quick_connect": "Échec de l'initialisation de Connexion Rapide",
|
||||
"got_it": "D'accord",
|
||||
"connection_failed": "La connection a échouée",
|
||||
"could_not_connect_to_server": "Impossible de se connecter au serveur. Veuillez vérifier l'URL et votre connection réseau."
|
||||
@@ -26,6 +26,7 @@
|
||||
"home": {
|
||||
"home": "Accueil",
|
||||
"no_internet": "Pas d'Internet",
|
||||
"no_items": "Aucun item",
|
||||
"no_internet_message": "Aucun problème, vous pouvez toujours regarder\nle contenu téléchargé.",
|
||||
"go_to_downloads": "Aller aux téléchargements",
|
||||
"oops": "Oups!",
|
||||
@@ -46,11 +47,11 @@
|
||||
"app_version": "Version de l'application"
|
||||
},
|
||||
"quick_connect": {
|
||||
"quick_connect_title": "Quick connect",
|
||||
"authorize_button": "Autoriser Quick Connect",
|
||||
"enter_the_quick_connect_code": "Entrez le code Quick Connect",
|
||||
"quick_connect_title": "Connexion Rapide",
|
||||
"authorize_button": "Autoriser Connexion Rapide",
|
||||
"enter_the_quick_connect_code": "Entrez le code Connexion Rapide",
|
||||
"success": "Succès",
|
||||
"quick_connect_autorized": "Quick Connect autorisé",
|
||||
"quick_connect_autorized": "Connexion Rapide autorisé",
|
||||
"error": "Errur",
|
||||
"invalid_code": "Code invalide"
|
||||
},
|
||||
@@ -63,7 +64,8 @@
|
||||
"audio_title": "Audio",
|
||||
"set_audio_track": "Configurer la piste audio à partir de l'élément précédent",
|
||||
"audio_language": "Langue audio",
|
||||
"audio_hint": "Chosissez une langue audio par défaut."
|
||||
"audio_hint": "Chosissez une langue audio par défaut.",
|
||||
"none": "Aucun"
|
||||
},
|
||||
"subtitles": {
|
||||
"subtitle_title": "Sous-titres",
|
||||
@@ -71,7 +73,8 @@
|
||||
"subtitle_mode": "Mode des sous-titres",
|
||||
"set_subtitle_track": "Configurer la piste de sous-titres à partir de l'élément précédent",
|
||||
"subtitle_size": "Taille des sous-titres",
|
||||
"subtitle_hint": "Configurez les préférences des sous-titres."
|
||||
"subtitle_hint": "Configurez les préférences des sous-titres.",
|
||||
"none": "Aucun"
|
||||
},
|
||||
"other": {
|
||||
"other_title": "Autres",
|
||||
@@ -85,7 +88,15 @@
|
||||
"download_method": "Méthode de téléchargement",
|
||||
"remux_max_download": "Téléchargement max remux",
|
||||
"auto_download": "Téléchargement automatique",
|
||||
"optimized_versions_server": "Serveur de versions optimisées"
|
||||
"optimized_versions_server": "Serveur de versions optimisées",
|
||||
"save_button": "Enregistrer",
|
||||
"optimized_server": "Serveur optimisé",
|
||||
"optimized": "Optimisé",
|
||||
"default": "Défaut",
|
||||
"optimized_version_hint": "Entrez l'URL du serveur de versions optimisées. L'URL devrait inclure http ou https et optionnellement le port.",
|
||||
"read_more_about_optimized_server": "Lisez-en plus sur le serveur de versions optimisées.",
|
||||
"url": "URL",
|
||||
"server_url_placeholder": "http(s)://domaine.org:port"
|
||||
},
|
||||
"plugins": {
|
||||
"plugins_title": "Plugiciels",
|
||||
@@ -147,7 +158,8 @@
|
||||
"background_downloads_enabled": "Téléchargements en arrière-plan activés",
|
||||
"background_downloads_disabled": "Téléchargements en arrière-plan désactivés",
|
||||
"connected": "Connecté",
|
||||
"could_not_connect": "Impossible de se connecter"
|
||||
"could_not_connect": "Impossible de se connecter",
|
||||
"invalid_url": "URL invalide"
|
||||
}
|
||||
},
|
||||
"downloads": {
|
||||
@@ -170,6 +182,7 @@
|
||||
"delete": "Supprimer",
|
||||
"something_went_wrong": "Quelque chose s'est mal passé",
|
||||
"could_not_get_stream_url_from_jellyfin": "Impossible d'obtenir l'URL du flux depuis Jellyfin",
|
||||
"eta": "ETA {{eta}}",
|
||||
"toasts": {
|
||||
"you_are_not_allowed_to_download_files": "Vous n'êtes pas autorisé à télécharger des fichiers",
|
||||
"deleted_all_movies_successfully": "Tous les films ont été supprimés avec succès!",
|
||||
@@ -197,9 +210,14 @@
|
||||
}
|
||||
},
|
||||
"search": {
|
||||
"results_for_x": "Résultats pour ",
|
||||
"search_title": "Recherche",
|
||||
"search_here": "Rechercher ici...",
|
||||
"search": "Rechercher...",
|
||||
"x_items": "{{count}} items",
|
||||
"library": "Bibliothèque",
|
||||
"discover": "Découvrir",
|
||||
"no_results": "Aucun résultat",
|
||||
"no_results_found_for": "Aucun résultat trouvé pour",
|
||||
"movies": "Films",
|
||||
"series": "Séries",
|
||||
@@ -245,6 +263,9 @@
|
||||
"playlists": "Listes de lecture",
|
||||
"music_albums": "Albums de musique",
|
||||
"audio": "Audio"
|
||||
},
|
||||
"custom_links": {
|
||||
"no_links": "Aucun lien"
|
||||
},
|
||||
"player": {
|
||||
"error": "Erreur",
|
||||
@@ -253,7 +274,9 @@
|
||||
"client_error": "Erreur client",
|
||||
"could_not_create_stream_for_chromecast": "Impossible de créer un flux pour Chromecast",
|
||||
"message_from_server": "Message du serveur: {{message}}",
|
||||
"video_has_finished_playing": "La vidéo a fini de jouer!"
|
||||
"video_has_finished_playing": "La vidéo a fini de jouer!",
|
||||
"no_video_source": "Aucune source vidéo...",
|
||||
"next_episode": "Épisode suivant"
|
||||
},
|
||||
"item_card": {
|
||||
"next_up": "À suivre",
|
||||
@@ -262,7 +285,6 @@
|
||||
"series": "Séries",
|
||||
"seasons": "Saisons",
|
||||
"season": "Saison",
|
||||
"episodes": "Épisodes",
|
||||
"no_episodes_for_this_season": "Aucun épisode pour cette saison",
|
||||
"overview": "Aperçu",
|
||||
"more_with": "Plus avec {{name}}",
|
||||
@@ -275,6 +297,17 @@
|
||||
"subtitles": "Sous-titres",
|
||||
"show_more": "Afficher plus",
|
||||
"show_less": "Afficher moins",
|
||||
"appeared_in": "Apparu dans",
|
||||
"x_songs": "{{count}} chansons",
|
||||
"x_albums": "{{count}} albums",
|
||||
"artists": "Artistes",
|
||||
"could_not_load_item": "Impossible de charger l'item",
|
||||
"refresh_tracks": "Rafraîchir les pistes",
|
||||
"subtitle_tracks": "Pistes de sous-titres:",
|
||||
"audio_tracks": "Pistes audio:",
|
||||
"playback_state": "État de lecture:",
|
||||
"no_data_available": "Aucune donnée disponible",
|
||||
"index": "Index:",
|
||||
"download": {
|
||||
"download_season": "Télécharger la saison",
|
||||
"download_x_item": "Télécharger {{item_count}} items",
|
||||
@@ -287,6 +320,14 @@
|
||||
"confirm": "Confirmer",
|
||||
"cancel": "Annuler",
|
||||
"yes": "Oui",
|
||||
"whats_wrong": "Qu'est-ce qui ne va pas?",
|
||||
"issue_type": "Type de problème",
|
||||
"select_an_issue": "Sélectionnez un problème",
|
||||
"types": "Types",
|
||||
"describe_the_issue": "(optionnel) Décrivez le problème...",
|
||||
"submit_button": "Soumettre",
|
||||
"report_issue_button": "Signaler un problème",
|
||||
"request_button": "Demander",
|
||||
"are_you_sure_you_want_to_request_all_seasons": "Êtes-vous sûr de vouloir demander toutes les saisons?",
|
||||
"failed_to_login": "Échec de la connexion",
|
||||
"toasts": {
|
||||
|
||||
Reference in New Issue
Block a user