diff --git a/app/(auth)/(tabs)/(home,libraries,search,favorites)/jellyseerr/page.tsx b/app/(auth)/(tabs)/(home,libraries,search,favorites)/jellyseerr/page.tsx index 5cfe68cf..45318462 100644 --- a/app/(auth)/(tabs)/(home,libraries,search,favorites)/jellyseerr/page.tsx +++ b/app/(auth)/(tabs)/(home,libraries,search,favorites)/jellyseerr/page.tsx @@ -42,19 +42,21 @@ const DropdownMenu = !Platform.isTV ? require("zeego/dropdown-menu") : null; import RequestModal from "@/components/jellyseerr/RequestModal"; import { ANIME_KEYWORD_ID } from "@/utils/jellyseerr/server/api/themoviedb/constants"; import { MediaRequestBody } from "@/utils/jellyseerr/server/interfaces/api/requestInterfaces"; +import {MovieDetails} from "@/utils/jellyseerr/server/models/Movie"; const Page: React.FC = () => { const insets = useSafeAreaInsets(); const params = useLocalSearchParams(); const { t } = useTranslation(); - const { mediaTitle, releaseYear, posterSrc, ...result } = + const { mediaTitle, releaseYear, posterSrc, mediaType, ...result } = params as unknown as { mediaTitle: string; releaseYear: number; canRequest: string; posterSrc: string; - } & Partial; + mediaType: MediaType; + } & Partial; const navigation = useNavigation(); const { jellyseerrApi, requestMedia } = useJellyseerr(); @@ -72,7 +74,7 @@ const Page: React.FC = () => { refetch, } = useQuery({ enabled: !!jellyseerrApi && !!result && !!result.id, - queryKey: ["jellyseerr", "detail", result.mediaType, result.id], + queryKey: ["jellyseerr", "detail", mediaType, result.id], staleTime: 0, refetchOnMount: true, refetchOnReconnect: true, @@ -80,7 +82,7 @@ const Page: React.FC = () => { retryOnMount: true, refetchInterval: 0, queryFn: async () => { - return result.mediaType === MediaType.MOVIE + return mediaType === MediaType.MOVIE ? jellyseerrApi?.movieDetails(result.id!!) : jellyseerrApi?.tvDetails(result.id!!); }, @@ -120,7 +122,7 @@ const Page: React.FC = () => { const request = useCallback(async () => { const body: MediaRequestBody = { mediaId: Number(result.id!!), - mediaType: result.mediaType!!, + mediaType: mediaType!!, tvdbId: details?.externalIds?.tvdbId, seasons: (details as TvDetails)?.seasons ?.filter?.((s) => s.seasonNumber !== 0) @@ -138,7 +140,7 @@ const Page: React.FC = () => { const isAnime = useMemo( () => (details?.keywords.some((k) => k.id === ANIME_KEYWORD_ID) || false) && - result.mediaType === MediaType.TV, + mediaType === MediaType.TV, [details] ); @@ -206,7 +208,7 @@ const Page: React.FC = () => { - + { - {result.mediaType === MediaType.TV && ( + {mediaType === MediaType.TV && ( { requestBody={requestBody} title={mediaTitle} id={result.id!!} - type={result.mediaType as MediaType} + type={mediaType} isAnime={isAnime} onRequested={() => { _setRequestBody(undefined) diff --git a/components/GenreTags.tsx b/components/GenreTags.tsx index 35de54a6..ce907d1b 100644 --- a/components/GenreTags.tsx +++ b/components/GenreTags.tsx @@ -21,14 +21,19 @@ export const Tag: React.FC<{ text: string, textClass?: ViewProps["className"], t ); }; -export const Tags: React.FC = ({ tags, textClass = "text-xs", ...props }) => { +export const Tags: React.FC = ({ + tags, + textClass = "text-xs", + tagProps, + ...props +}) => { if (!tags || tags.length === 0) return null; return ( {tags.map((tag, idx) => ( - + ))} diff --git a/components/Ratings.tsx b/components/Ratings.tsx index 64d3d83b..c1c9b060 100644 --- a/components/Ratings.tsx +++ b/components/Ratings.tsx @@ -7,6 +7,9 @@ import { MovieResult, TvResult } from "@/utils/jellyseerr/server/models/Search"; import { useJellyseerr } from "@/hooks/useJellyseerr"; import { useQuery } from "@tanstack/react-query"; import { MediaType } from "@/utils/jellyseerr/server/constants/media"; +import {MovieDetails} from "@/utils/jellyseerr/server/models/Movie"; +import {TvDetails} from "@/utils/jellyseerr/server/models/Tv"; +import {useMemo} from "react"; interface Props extends ViewProps { item?: BaseItemDto | null; @@ -49,14 +52,17 @@ export const Ratings: React.FC = ({ item, ...props }) => { ); }; -export const JellyserrRatings: React.FC<{ result: MovieResult | TvResult }> = ({ +export const JellyserrRatings: React.FC<{ result: MovieResult | TvResult | TvDetails | MovieDetails }> = ({ result, }) => { - const { jellyseerrApi } = useJellyseerr(); + const { jellyseerrApi, getMediaType } = useJellyseerr(); + + const mediaType = useMemo(() => getMediaType(result), [result]); + const { data, isLoading } = useQuery({ - queryKey: ["jellyseerr", result.id, result.mediaType, "ratings"], + queryKey: ["jellyseerr", result.id, mediaType, "ratings"], queryFn: async () => { - return result.mediaType === MediaType.MOVIE + return mediaType === MediaType.MOVIE ? jellyseerrApi?.movieRatings(result.id) : jellyseerrApi?.tvRatings(result.id); }, diff --git a/components/common/JellyseerrItemRouter.tsx b/components/common/JellyseerrItemRouter.tsx index 198d5a45..b132e437 100644 --- a/components/common/JellyseerrItemRouter.tsx +++ b/components/common/JellyseerrItemRouter.tsx @@ -9,13 +9,16 @@ import { Permission, } from "@/utils/jellyseerr/server/lib/permissions"; import { MediaType } from "@/utils/jellyseerr/server/constants/media"; +import {TvDetails} from "@/utils/jellyseerr/server/models/Tv"; +import {MovieDetails} from "@/utils/jellyseerr/server/models/Movie"; interface Props extends TouchableOpacityProps { - result: MovieResult | TvResult; + result: MovieResult | TvResult | MovieDetails | TvDetails; mediaTitle: string; releaseYear: number; canRequest: boolean; posterSrc: string; + mediaType: MediaType; } export const TouchableJellyseerrRouter: React.FC> = ({ @@ -24,6 +27,7 @@ export const TouchableJellyseerrRouter: React.FC> = ({ releaseYear, canRequest, posterSrc, + mediaType, children, ...props }) => { @@ -46,7 +50,7 @@ export const TouchableJellyseerrRouter: React.FC> = ({ () => requestMedia(mediaTitle, { mediaId: result.id, - mediaType: result.mediaType, + mediaType, }), [jellyseerrApi, result] ); @@ -67,6 +71,7 @@ export const TouchableJellyseerrRouter: React.FC> = ({ releaseYear, canRequest, posterSrc, + mediaType }, }); }} @@ -83,7 +88,7 @@ export const TouchableJellyseerrRouter: React.FC> = ({ key={"content"} > Actions - {canRequest && result.mediaType === MediaType.MOVIE && ( + {canRequest && mediaType === MediaType.MOVIE && ( { diff --git a/components/jellyseerr/discover/Discover.tsx b/components/jellyseerr/discover/Discover.tsx index 6270ad2b..e5a84c1a 100644 --- a/components/jellyseerr/discover/Discover.tsx +++ b/components/jellyseerr/discover/Discover.tsx @@ -8,6 +8,7 @@ import {View} from "react-native"; import {networks} from "@/utils/jellyseerr/src/components/Discover/NetworkSlider"; import {studios} from "@/utils/jellyseerr/src/components/Discover/StudioSlider"; import GenreSlide from "@/components/jellyseerr/discover/GenreSlide"; +import RecentRequestsSlide from "@/components/jellyseerr/discover/RecentRequestsSlide"; interface Props { sliders?: DiscoverSlider[]; @@ -25,6 +26,8 @@ const Discover: React.FC = ({ sliders }) => { {sortedSliders.map(slide => { switch (slide.type) { + case DiscoverSliderType.RECENT_REQUESTS: + return case DiscoverSliderType.NETWORKS: return case DiscoverSliderType.STUDIOS: diff --git a/components/jellyseerr/discover/RecentRequestsSlide.tsx b/components/jellyseerr/discover/RecentRequestsSlide.tsx new file mode 100644 index 00000000..5ab234d8 --- /dev/null +++ b/components/jellyseerr/discover/RecentRequestsSlide.tsx @@ -0,0 +1,69 @@ +import React from "react"; +import {useQuery} from "@tanstack/react-query"; +import {useJellyseerr} from "@/hooks/useJellyseerr"; +import Slide, {SlideProps} from "@/components/jellyseerr/discover/Slide"; +import {ViewProps} from "react-native"; +import MediaRequest from "@/utils/jellyseerr/server/entity/MediaRequest"; +import {NonFunctionProperties} from "@/utils/jellyseerr/server/interfaces/api/common"; +import {MediaType} from "@/utils/jellyseerr/server/constants/media"; +import JellyseerrPoster from "@/components/posters/JellyseerrPoster"; + +const RequestCard: React.FC<{request: MediaRequest}> = ({request}) => { + const {jellyseerrApi} = useJellyseerr(); + + const { data: details, isLoading, isError } = useQuery({ + queryKey: ["jellyseerr", "detail", request.media.mediaType, request.media.tmdbId], + queryFn: async () => { + + return request.media.mediaType == MediaType.MOVIE + ? jellyseerrApi?.movieDetails(request.media.tmdbId) + : jellyseerrApi?.tvDetails(request.media.tmdbId); + }, + enabled: !!jellyseerrApi, + refetchOnMount: true, + staleTime: 0, + }); + + const { data: refreshedRequest } = useQuery({ + queryKey: ["jellyseerr", "requests", request.media.mediaType, request.id], + queryFn: async () => jellyseerrApi?.getRequest(request.id), + enabled: !!jellyseerrApi, + refetchOnMount: true, + refetchInterval: 5000, + staleTime: 0, + }); + + return ( + details && + ) +} + +const RecentRequestsSlide: React.FC = ({ slide, ...props }) => { + const {jellyseerrApi} = useJellyseerr(); + + const { data: requests, isLoading, isError } = useQuery({ + queryKey: ["jellyseerr", "recent_requests"], + queryFn: async () => jellyseerrApi?.requests(), + enabled: !!jellyseerrApi, + refetchOnMount: true, + staleTime: 0, + }); + + return ( + requests && + requests.results.length > 0 && + !isError && ( + item.id.toString()} + renderItem={(item: NonFunctionProperties) => ( + + )} + /> + ) + ) +}; + +export default RecentRequestsSlide; \ No newline at end of file diff --git a/components/posters/JellyseerrPoster.tsx b/components/posters/JellyseerrPoster.tsx index 1c3ce45b..a7c019dd 100644 --- a/components/posters/JellyseerrPoster.tsx +++ b/components/posters/JellyseerrPoster.tsx @@ -1,28 +1,42 @@ -import { TouchableJellyseerrRouter } from "@/components/common/JellyseerrItemRouter"; -import { Text } from "@/components/common/Text"; +import {TouchableJellyseerrRouter} from "@/components/common/JellyseerrItemRouter"; +import {Text} from "@/components/common/Text"; import JellyseerrMediaIcon from "@/components/jellyseerr/JellyseerrMediaIcon"; import JellyseerrStatusIcon from "@/components/jellyseerr/JellyseerrStatusIcon"; -import { useJellyseerr } from "@/hooks/useJellyseerr"; -import { useJellyseerrCanRequest } from "@/utils/_jellyseerr/useJellyseerrCanRequest"; -import { MediaType } from "@/utils/jellyseerr/server/constants/media"; -import { MovieResult, TvResult } from "@/utils/jellyseerr/server/models/Search"; -import { Image } from "expo-image"; -import { useMemo } from "react"; -import { View, ViewProps } from "react-native"; -import Animated, { - useAnimatedStyle, - useSharedValue, - withTiming, -} from "react-native-reanimated"; +import {useJellyseerr} from "@/hooks/useJellyseerr"; +import {useJellyseerrCanRequest} from "@/utils/_jellyseerr/useJellyseerrCanRequest"; +import {MovieResult, TvResult} from "@/utils/jellyseerr/server/models/Search"; +import {Image} from "expo-image"; +import {useMemo} from "react"; +import {View, ViewProps} from "react-native"; +import Animated, {useAnimatedStyle, useSharedValue, withTiming,} from "react-native-reanimated"; +import {TvDetails} from "@/utils/jellyseerr/server/models/Tv"; +import {MovieDetails} from "@/utils/jellyseerr/server/models/Movie"; +import type {DownloadingItem} from "@/utils/jellyseerr/server/lib/downloadtracker"; +import MediaRequest from "@/utils/jellyseerr/server/entity/MediaRequest"; +import {useTranslation} from "react-i18next"; +import {MediaStatus} from "@/utils/jellyseerr/server/constants/media"; +import {textShadowStyle} from "@/components/jellyseerr/discover/GenericSlideCard"; +import {Colors} from "@/constants/Colors"; +import {Tags} from "@/components/GenreTags"; interface Props extends ViewProps { - item: MovieResult | TvResult; + item: MovieResult | TvResult | MovieDetails | TvDetails; + horizontal?: boolean; + showDownloadInfo?: boolean; + mediaRequest?: MediaRequest; } -const JellyseerrPoster: React.FC = ({ item, ...props }) => { - const { jellyseerrApi } = useJellyseerr(); +const JellyseerrPoster: React.FC = ({ + item, + horizontal, + showDownloadInfo, + mediaRequest, + ...props +}) => { + const { jellyseerrApi, getTitle, getYear, getMediaType, isJellyseerrResult } = useJellyseerr(); const loadingOpacity = useSharedValue(1); const imageOpacity = useSharedValue(0); + const {t} = useTranslation(); const loadingAnimatedStyle = useAnimatedStyle(() => ({ opacity: loadingOpacity.value, @@ -38,27 +52,64 @@ const JellyseerrPoster: React.FC = ({ item, ...props }) => { }; const imageSrc = useMemo( - () => jellyseerrApi?.imageProxy(item.posterPath, "w300_and_h450_face"), - [item, jellyseerrApi] + () => jellyseerrApi?.imageProxy( + horizontal ? item.backdropPath : item.posterPath, + horizontal ? "w1920_and_h800_multi_faces" : "w300_and_h450_face" + ), + [item, jellyseerrApi, horizontal] ); - const title = useMemo( - () => (item.mediaType === MediaType.MOVIE ? item.title : item.name), - [item] - ); + const title = useMemo(() => getTitle(item), [item]); + const releaseYear = useMemo(() => getYear(item), [item]); + const mediaType = useMemo(() => getMediaType(item), [item]); - const releaseYear = useMemo( - () => - new Date( - item.mediaType === MediaType.MOVIE - ? item.releaseDate - : item.firstAirDate - ).getFullYear(), - [item] - ); + const size = useMemo(() => horizontal ? 'h-28' : 'w-28', [horizontal]) + const ratio = useMemo(() => horizontal ? '15/10' : '10/15', [horizontal]) const [canRequest] = useJellyseerrCanRequest(item); + const is4k = useMemo( + () => mediaRequest?.is4k === true, + [mediaRequest] + ); + + const downloadItems = useMemo( + () => (is4k ? mediaRequest?.media.downloadStatus4k : mediaRequest?.media.downloadStatus) || [], + [mediaRequest, is4k] + ) + + const progress = useMemo(() => { + const [totalSize, sizeLeft] = downloadItems + .reduce((sum: number[], next: DownloadingItem) => + [sum[0] + next.size, sum[1] + next.sizeLeft], + [0, 0] + ); + + return (((totalSize - sizeLeft) / totalSize) * 100); + }, + [downloadItems] + ); + + const requestedSeasons: string[] | undefined = useMemo( + () => { + const seasons = mediaRequest?.seasons?.flatMap(s => s.seasonNumber.toString()) || [] + if (seasons.length > 4) { + const [first, second, third, fourth, ...rest] = seasons; + return [first, second, third, fourth, t("home.settings.plugins.jellyseerr.plus_n_more", {n: rest.length })] + } + return seasons + }, + [mediaRequest] + ); + + const available = useMemo( + () => { + const status = mediaRequest?.media?.[is4k ? 'status4k' : 'status']; + return status === MediaStatus.AVAILABLE + }, + [mediaRequest, is4k] + ); + return ( = ({ item, ...props }) => { releaseYear={releaseYear} canRequest={canRequest} posterSrc={imageSrc!!} + mediaType={mediaType} > - - + + = ({ item, ...props }) => { cachePolicy={"memory-disk"} contentFit="cover" style={{ - aspectRatio: "10/15", - width: "100%", + aspectRatio: ratio, + [horizontal ? 'height' : 'width']: "100%" }} onLoad={handleImageLoad} /> + {mediaRequest && showDownloadInfo && ( + <> + + {!available && !Number.isNaN(progress) && ( + <> + + + + {progress?.toFixed(0)}% + + + + )} + + {mediaRequest?.requestedBy.displayName} + + {requestedSeasons.length > 0 && ( + + )} + + )} - - {title} - {releaseYear} - + + + {title} + {releaseYear} ); diff --git a/components/series/JellyseerrSeasons.tsx b/components/series/JellyseerrSeasons.tsx index a1a1fb7c..0d77ac13 100644 --- a/components/series/JellyseerrSeasons.tsx +++ b/components/series/JellyseerrSeasons.tsx @@ -128,14 +128,12 @@ const RenderItem = ({ item, index }: any) => { const JellyseerrSeasons: React.FC<{ isLoading: boolean; - result?: TvResult; details?: TvDetails; hasAdvancedRequest?: boolean, onAdvancedRequest?: (data: MediaRequestBody) => void; refetch: (options?: (RefetchOptions | undefined)) => Promise>; }> = ({ isLoading, - result, details, refetch, hasAdvancedRequest, @@ -195,7 +193,7 @@ const JellyseerrSeasons: React.FC<{ return onAdvancedRequest?.(body) } - requestMedia(result?.name!!, body, refetch); + requestMedia(details.name, body, refetch); } }, [jellyseerrApi, seasons, details, hasAdvancedRequest, onAdvancedRequest]); @@ -227,7 +225,7 @@ const JellyseerrSeasons: React.FC<{ return onAdvancedRequest?.(body) } - requestMedia(`${result?.name!!}, Season ${seasonNumber}`, body, refetch); + requestMedia(`${details.name}, Season ${seasonNumber}`, body, refetch); } }, [requestMedia, hasAdvancedRequest, onAdvancedRequest]); diff --git a/components/settings/StorageSettings.tsx b/components/settings/StorageSettings.tsx index 8525afd0..aad3b1d3 100644 --- a/components/settings/StorageSettings.tsx +++ b/components/settings/StorageSettings.tsx @@ -8,6 +8,7 @@ import { toast } from "sonner-native"; import { ListGroup } from "../list/ListGroup"; import { ListItem } from "../list/ListItem"; import { useTranslation } from "react-i18next"; +import {Colors} from "@/constants/Colors"; export const StorageSettings = () => { const { deleteAllFiles, appSizeUsage } = useDownload(); @@ -61,7 +62,7 @@ export const StorageSettings = () => { { ((size.total - size.remaining - size.app) / size.total) * 100 }%`, - backgroundColor: "rgb(192 132 252)", + backgroundColor: Colors.primaryLightRGB, }} /> diff --git a/constants/Colors.ts b/constants/Colors.ts index 69734810..8702c547 100644 --- a/constants/Colors.ts +++ b/constants/Colors.ts @@ -1,5 +1,7 @@ export const Colors = { primary: "#9334E9", + primaryRGB: "rgb(147 51 234)", + primaryLightRGB: "rgb(192 132 252)", text: "#ECEDEE", background: "#151718", tint: "#fff", diff --git a/hooks/useJellyseerr.ts b/hooks/useJellyseerr.ts index 9d3b37c7..32e36513 100644 --- a/hooks/useJellyseerr.ts +++ b/hooks/useJellyseerr.ts @@ -1,5 +1,5 @@ import axios, { AxiosError, AxiosInstance } from "axios"; -import { Results } from "@/utils/jellyseerr/server/models/Search"; +import {MovieResult, Results, TvResult} from "@/utils/jellyseerr/server/models/Search"; import { storage } from "@/utils/mmkv"; import { inRange } from "lodash"; import { User as JellyseerrUser } from "@/utils/jellyseerr/server/entity/User"; @@ -14,7 +14,7 @@ import { MediaType, } from "@/utils/jellyseerr/server/constants/media"; import MediaRequest from "@/utils/jellyseerr/server/entity/MediaRequest"; -import { MediaRequestBody } from "@/utils/jellyseerr/server/interfaces/api/requestInterfaces"; +import {MediaRequestBody, RequestResultsResponse} from "@/utils/jellyseerr/server/interfaces/api/requestInterfaces"; import { MovieDetails } from "@/utils/jellyseerr/server/models/Movie"; import { SeasonWithEpisodes, @@ -227,6 +227,23 @@ export class JellyseerrApi { .then(({ data }) => data); } + async getRequest(id: number): Promise { + return this.axios + ?.get(Endpoints.API_V1 + Endpoints.REQUEST + `/${id}`) + .then(({ data }) => data); + } + + async requests(params = { + filter: "all", + take: 10, + sort: "modified", + skip: 0 + }): Promise { + return this.axios + ?.get(Endpoints.API_V1 + Endpoints.REQUEST, {params}) + .then(({data}) => data); + } + async movieDetails(id: number) { return this.axios ?.get(Endpoints.API_V1 + Endpoints.MOVIE + `/${id}`) @@ -439,14 +456,34 @@ export const useJellyseerr = () => { ); const isJellyseerrResult = ( - items: any[] | null | undefined - ): items is Results[] => { + items: any | null | undefined + ): items is Results => { return ( - !items || - (items.length >= 0 && - Object.hasOwn(items[0], "mediaType") && - Object.values(MediaType).includes(items[0]["mediaType"])) - ); + items && + Object.hasOwn(items, "mediaType") && + Object.values(MediaType).includes(items["mediaType"]) + ) + }; + + const getTitle = (item: TvResult | TvDetails | MovieResult | MovieDetails) => { + return isJellyseerrResult(item) + ? (item.mediaType == MediaType.MOVIE ? item?.originalTitle : item?.name) + : (item.mediaInfo.mediaType == MediaType.MOVIE ? (item as MovieDetails)?.title : (item as TvDetails)?.name) + }; + + const getYear = (item: TvResult | TvDetails | MovieResult | MovieDetails) => { + return new Date(( + isJellyseerrResult(item) + ? (item.mediaType == MediaType.MOVIE ? item?.releaseDate : item?.firstAirDate) + : (item.mediaInfo.mediaType == MediaType.MOVIE ? (item as MovieDetails)?.releaseDate : (item as TvDetails)?.firstAirDate)) + || "" + )?.getFullYear?.() + }; + + const getMediaType = (item: TvResult | TvDetails | MovieResult | MovieDetails): MediaType => { + return isJellyseerrResult(item) + ? item.mediaType + : item?.mediaInfo?.mediaType }; const jellyseerrRegion = useMemo( @@ -464,6 +501,9 @@ export const useJellyseerr = () => { setJellyseerrUser, clearAllJellyseerData, isJellyseerrResult, + getTitle, + getYear, + getMediaType, jellyseerrRegion, jellyseerrLocale, requestMedia, diff --git a/translations/de.json b/translations/de.json index 50995e15..962e10d7 100644 --- a/translations/de.json +++ b/translations/de.json @@ -168,7 +168,8 @@ "tv_quota_limit": "TV-Anfragelimit", "tv_quota_days": "TV-Anfragetage", "reset_jellyseerr_config_button": "Setze Jellyseerr-Konfiguration zurück", - "unlimited": "Unlimitiert" + "unlimited": "Unlimitiert", + "plus_n_more": "+{{n}} more" }, "marlin_search": { "enable_marlin_search": "Aktiviere Marlin Search", diff --git a/translations/en.json b/translations/en.json index ca19a136..5ba25ec9 100644 --- a/translations/en.json +++ b/translations/en.json @@ -168,7 +168,8 @@ "tv_quota_limit": "TV quota limit", "tv_quota_days": "TV quota days", "reset_jellyseerr_config_button": "Reset Jellyseerr config", - "unlimited": "Unlimited" + "unlimited": "Unlimited", + "plus_n_more": "+{{n}} more" }, "marlin_search": { "enable_marlin_search": "Enable Marlin Search ", diff --git a/translations/es.json b/translations/es.json index c03c2e69..9004c2f8 100644 --- a/translations/es.json +++ b/translations/es.json @@ -168,7 +168,8 @@ "tv_quota_limit": "Límite de cuota de series", "tv_quota_days": "Días de cuota de series", "reset_jellyseerr_config_button": "Restablecer configuración de Jellyseerr", - "unlimited": "Ilimitado" + "unlimited": "Ilimitado", + "plus_n_more": "+{{n}} more" }, "marlin_search": { "enable_marlin_search": "Habilitar búsqueda de Marlin", diff --git a/translations/fr.json b/translations/fr.json index e9c576e6..7347d274 100644 --- a/translations/fr.json +++ b/translations/fr.json @@ -169,7 +169,8 @@ "tv_quota_limit": "Limite de quota TV", "tv_quota_days": "Jours de quota TV", "reset_jellyseerr_config_button": "Réinitialiser la configuration Jellyseerr", - "unlimited": "Illimité" + "unlimited": "Illimité", + "plus_n_more": "+{{n}} more" }, "marlin_search": { "enable_marlin_search": "Activer Marlin Search ", diff --git a/translations/it.json b/translations/it.json index d88ec71e..60a95bb1 100644 --- a/translations/it.json +++ b/translations/it.json @@ -168,7 +168,8 @@ "tv_quota_limit": "Limite di quota per le serie TV", "tv_quota_days": "Giorni di quota per le serie TV", "reset_jellyseerr_config_button": "Ripristina la configurazione di Jellyseerr", - "unlimited": "Illimitato" + "unlimited": "Illimitato", + "plus_n_more": "+{{n}} more" }, "marlin_search": { "enable_marlin_search": "Abilita la ricerca Marlin ", diff --git a/translations/ja.json b/translations/ja.json index 616067d5..acd99ff2 100644 --- a/translations/ja.json +++ b/translations/ja.json @@ -167,7 +167,8 @@ "tv_quota_limit": "テレビのクオータ制限", "tv_quota_days": "テレビのクオータ日数", "reset_jellyseerr_config_button": "Jellyseerrの設定をリセット", - "unlimited": "無制限" + "unlimited": "無制限", + "plus_n_more": "+{{n}} more" }, "marlin_search": { "enable_marlin_search": "マーリン検索を有効にする ", diff --git a/translations/nl.json b/translations/nl.json index 7912e6b4..d2129263 100644 --- a/translations/nl.json +++ b/translations/nl.json @@ -168,7 +168,8 @@ "tv_quota_limit": "Limiet serie quota", "tv_quota_days": "Serie Quota dagen", "reset_jellyseerr_config_button": "Jellyseerr opnieuw instellen", - "unlimited": "Ongelimiteerd" + "unlimited": "Ongelimiteerd", + "plus_n_more": "+{{n}} more" }, "marlin_search": { "enable_marlin_search": "Marlin Search inschakelen ", diff --git a/translations/tr.json b/translations/tr.json index 7bc2e6d3..bdc140c8 100644 --- a/translations/tr.json +++ b/translations/tr.json @@ -167,7 +167,8 @@ "tv_quota_limit": "TV kota limiti", "tv_quota_days": "TV kota günleri", "reset_jellyseerr_config_button": "Jellyseerr yapılandırmasını sıfırla", - "unlimited": "Sınırsız" + "unlimited": "Sınırsız", + "plus_n_more": "+{{n}} more" }, "marlin_search": { "enable_marlin_search": "Marlin Aramasını Etkinleştir ", diff --git a/translations/zh-CN.json b/translations/zh-CN.json index e3a834a6..94c1dad5 100644 --- a/translations/zh-CN.json +++ b/translations/zh-CN.json @@ -167,7 +167,8 @@ "tv_quota_limit": "剧集配额限制", "tv_quota_days": "剧集配额天数", "reset_jellyseerr_config_button": "重置 Jellyseerr 设置", - "unlimited": "无限制" + "unlimited": "无限制", + "plus_n_more": "+{{n}} more" }, "marlin_search": { "enable_marlin_search": "启用 Marlin 搜索", diff --git a/translations/zh-TW.json b/translations/zh-TW.json index f36174fd..2dbd287e 100644 --- a/translations/zh-TW.json +++ b/translations/zh-TW.json @@ -167,7 +167,8 @@ "tv_quota_limit": "電視配額限制", "tv_quota_days": "電視配額天數", "reset_jellyseerr_config_button": "重置 Jellyseerr 配置", - "unlimited": "無限制" + "unlimited": "無限制", + "plus_n_more": "+{{n}} more" }, "marlin_search": { "enable_marlin_search": "啟用 Marlin 搜索",