Compare commits

..

13 Commits

75 changed files with 1158 additions and 4240 deletions

View File

@@ -908,69 +908,6 @@
"contributions": [
"code"
]
},
{
"login": "j0srisk",
"name": "Joseph Risk",
"avatar_url": "https://avatars.githubusercontent.com/u/18372584?v=4",
"profile": "http://josephrisk.com",
"contributions": [
"code"
]
},
{
"login": "Loetwiek",
"name": "Loetwiek",
"avatar_url": "https://avatars.githubusercontent.com/u/79059734?v=4",
"profile": "https://github.com/Loetwiek",
"contributions": [
"code"
]
},
{
"login": "Fuochi",
"name": "Fuochi",
"avatar_url": "https://avatars.githubusercontent.com/u/4720478?v=4",
"profile": "https://github.com/Fuochi",
"contributions": [
"doc"
]
},
{
"login": "demrich",
"name": "David Emrich",
"avatar_url": "https://avatars.githubusercontent.com/u/30092389?v=4",
"profile": "https://github.com/demrich",
"contributions": [
"code"
]
},
{
"login": "maxnatamo",
"name": "Max T. Kristiansen",
"avatar_url": "https://avatars.githubusercontent.com/u/5898152?v=4",
"profile": "https://maxtrier.dk",
"contributions": [
"code"
]
},
{
"login": "DamsDev1",
"name": "Damien Fajole",
"avatar_url": "https://avatars.githubusercontent.com/u/60252259?v=4",
"profile": "https://damsdev.me",
"contributions": [
"code"
]
},
{
"login": "AhmedNSidd",
"name": "Ahmed Siddiqui",
"avatar_url": "https://avatars.githubusercontent.com/u/36286128?v=4",
"profile": "https://github.com/AhmedNSidd",
"contributions": [
"code"
]
}
],
"badgeTemplate": "<a href=\"#contributors-\"><img alt=\"All Contributors\" src=\"https://img.shields.io/badge/all_contributors-<%= contributors.length %>-orange.svg\"/></a>",

View File

@@ -11,7 +11,7 @@
<a href="https://hosted.weblate.org/engage/overseerr/"><img src="https://hosted.weblate.org/widgets/overseerr/-/overseerr-frontend/svg-badge.svg" alt="Translation status" /></a>
<a href="https://github.com/sct/overseerr/blob/develop/LICENSE"><img alt="GitHub" src="https://img.shields.io/github/license/sct/overseerr"></a>
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
<a href="#contributors-"><img alt="All Contributors" src="https://img.shields.io/badge/all_contributors-106-orange.svg"/></a>
<a href="#contributors-"><img alt="All Contributors" src="https://img.shields.io/badge/all_contributors-99-orange.svg"/></a>
<!-- ALL-CONTRIBUTORS-BADGE:END -->
</p>
@@ -202,15 +202,6 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/RemiRigal"><img src="https://avatars.githubusercontent.com/u/19256051?v=4?s=100" width="100px;" alt="RemiRigal"/><br /><sub><b>RemiRigal</b></sub></a><br /><a href="https://github.com/sct/overseerr/commits?author=RemiRigal" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://josephrisk.com"><img src="https://avatars.githubusercontent.com/u/18372584?v=4?s=100" width="100px;" alt="Joseph Risk"/><br /><sub><b>Joseph Risk</b></sub></a><br /><a href="https://github.com/sct/overseerr/commits?author=j0srisk" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Loetwiek"><img src="https://avatars.githubusercontent.com/u/79059734?v=4?s=100" width="100px;" alt="Loetwiek"/><br /><sub><b>Loetwiek</b></sub></a><br /><a href="https://github.com/sct/overseerr/commits?author=Loetwiek" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Fuochi"><img src="https://avatars.githubusercontent.com/u/4720478?v=4?s=100" width="100px;" alt="Fuochi"/><br /><sub><b>Fuochi</b></sub></a><br /><a href="https://github.com/sct/overseerr/commits?author=Fuochi" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/demrich"><img src="https://avatars.githubusercontent.com/u/30092389?v=4?s=100" width="100px;" alt="David Emrich"/><br /><sub><b>David Emrich</b></sub></a><br /><a href="https://github.com/sct/overseerr/commits?author=demrich" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://maxtrier.dk"><img src="https://avatars.githubusercontent.com/u/5898152?v=4?s=100" width="100px;" alt="Max T. Kristiansen"/><br /><sub><b>Max T. Kristiansen</b></sub></a><br /><a href="https://github.com/sct/overseerr/commits?author=maxnatamo" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://damsdev.me"><img src="https://avatars.githubusercontent.com/u/60252259?v=4?s=100" width="100px;" alt="Damien Fajole"/><br /><sub><b>Damien Fajole</b></sub></a><br /><a href="https://github.com/sct/overseerr/commits?author=DamsDev1" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/AhmedNSidd"><img src="https://avatars.githubusercontent.com/u/36286128?v=4?s=100" width="100px;" alt="Ahmed Siddiqui"/><br /><sub><b>Ahmed Siddiqui</b></sub></a><br /><a href="https://github.com/sct/overseerr/commits?author=AhmedNSidd" title="Code">💻</a></td>
</tr>
</tbody>
</table>

View File

@@ -1053,7 +1053,7 @@ components:
status:
type: number
example: 0
description: Availability of the media. 1 = `UNKNOWN`, 2 = `PENDING`, 3 = `PROCESSING`, 4 = `PARTIALLY_AVAILABLE`, 5 = `AVAILABLE`
description: Availability of the media. 1 = `UNKNOWN`, 2 = `PENDING`, 3 = `PROCESSING`, 4 = `PARTIALLY_AVAILABLE`, 5 = `AVAILABLE`, 6 = `DELETED`
requests:
type: array
readOnly: true
@@ -3170,12 +3170,6 @@ paths:
Updates a single slider and return the newly updated slider. Requires the `ADMIN` permission.
tags:
- settings
parameters:
- in: path
name: sliderId
required: true
schema:
type: number
requestBody:
required: true
content:
@@ -3473,7 +3467,7 @@ paths:
results:
type: array
items:
$ref: '#/components/schemas/User'
$ref: '#/components/schemas/User'
post:
summary: Create new user
description: |
@@ -4961,6 +4955,7 @@ paths:
processing,
unavailable,
failed,
deleted,
]
- in: query
name: sort
@@ -5018,7 +5013,7 @@ paths:
- type: array
items:
type: number
minimum: 0
minimum: 1
- type: string
enum: [all]
is4k:
@@ -5124,7 +5119,7 @@ paths:
type: array
items:
type: number
minimum: 0
minimum: 1
is4k:
type: boolean
example: false
@@ -5700,7 +5695,16 @@ paths:
schema:
type: string
nullable: true
enum: [all, available, partial, allavailable, processing, pending]
enum:
[
all,
available,
partial,
allavailable,
processing,
pending,
deleted,
]
- in: query
name: sort
schema:
@@ -5759,7 +5763,7 @@ paths:
example: available
schema:
type: string
enum: [available, partial, processing, pending, unknown]
enum: [available, partial, processing, pending, unknown, deleted]
requestBody:
content:
application/json:

View File

@@ -53,7 +53,7 @@
"dayjs": "1.11.7",
"email-templates": "9.0.0",
"express": "4.18.2",
"express-openapi-validator": "5.3.9",
"express-openapi-validator": "4.13.8",
"express-rate-limit": "6.7.0",
"express-session": "1.17.3",
"formik": "2.2.9",

View File

@@ -2,7 +2,6 @@ import type { PlexDevice } from '@server/interfaces/api/plexInterfaces';
import cacheManager from '@server/lib/cache';
import { getSettings } from '@server/lib/settings';
import logger from '@server/logger';
import { randomUUID } from 'node:crypto';
import xml2js from 'xml2js';
import ExternalAPI from './externalapi';
@@ -128,11 +127,6 @@ export interface PlexWatchlistItem {
title: string;
}
export interface PlexWatchlistCache {
etag: string;
response: WatchlistResponse;
}
class PlexTvAPI extends ExternalAPI {
private authToken: string;
@@ -276,11 +270,6 @@ class PlexTvAPI extends ExternalAPI {
items: PlexWatchlistItem[];
}> {
try {
const watchlistCache = cacheManager.getCache('plexwatchlist');
let cachedWatchlist = watchlistCache.data.get<PlexWatchlistCache>(
this.authToken
);
const response = await this.axios.get<WatchlistResponse>(
'/library/sections/watchlist/all',
{
@@ -288,29 +277,12 @@ class PlexTvAPI extends ExternalAPI {
'X-Plex-Container-Start': offset,
'X-Plex-Container-Size': size,
},
headers: {
'If-None-Match': cachedWatchlist?.etag,
},
baseURL: 'https://metadata.provider.plex.tv',
validateStatus: (status) => status < 400, // Allow HTTP 304 to return without error
}
);
// If we don't recieve HTTP 304, the watchlist has been updated and we need to update the cache.
if (response.status >= 200 && response.status <= 299) {
cachedWatchlist = {
etag: response.headers.etag,
response: response.data,
};
watchlistCache.data.set<PlexWatchlistCache>(
this.authToken,
cachedWatchlist
);
}
const watchlistDetails = await Promise.all(
(cachedWatchlist?.response.MediaContainer.Metadata ?? []).map(
(response.data.MediaContainer.Metadata ?? []).map(
async (watchlistItem) => {
const detailedResponse = await this.getRolling<MetadataResponse>(
`/library/metadata/${watchlistItem.ratingKey}`,
@@ -348,7 +320,7 @@ class PlexTvAPI extends ExternalAPI {
return {
offset,
size,
totalSize: cachedWatchlist?.response.MediaContainer.totalSize ?? 0,
totalSize: response.data.MediaContainer.totalSize,
items: filteredList,
};
} catch (e) {
@@ -364,24 +336,6 @@ class PlexTvAPI extends ExternalAPI {
};
}
}
public async pingToken() {
try {
const response = await this.axios.get('/api/v2/ping', {
headers: {
'X-Plex-Client-Identifier': randomUUID(),
},
});
if (!response?.data?.pong) {
throw new Error('No pong response');
}
} catch (e) {
logger.error('Failed to ping token', {
label: 'Plex Refresh Token',
errorMessage: e.message,
});
}
}
}
export default PlexTvAPI;

View File

@@ -3,6 +3,7 @@ export enum MediaRequestStatus {
APPROVED,
DECLINED,
FAILED,
COMPLETED,
}
export enum MediaType {
@@ -16,4 +17,5 @@ export enum MediaStatus {
PROCESSING,
PARTIALLY_AVAILABLE,
AVAILABLE,
DELETED,
}

View File

@@ -1,6 +1,10 @@
import RadarrAPI from '@server/api/servarr/radarr';
import SonarrAPI from '@server/api/servarr/sonarr';
import { MediaStatus, MediaType } from '@server/constants/media';
import {
MediaRequestStatus,
MediaStatus,
MediaType,
} from '@server/constants/media';
import { getRepository } from '@server/datasource';
import type { DownloadingItem } from '@server/lib/downloadtracker';
import downloadTracker from '@server/lib/downloadtracker';
@@ -8,6 +12,7 @@ import { getSettings } from '@server/lib/settings';
import logger from '@server/logger';
import {
AfterLoad,
AfterUpdate,
Column,
CreateDateColumn,
Entity,
@@ -309,6 +314,37 @@ class Media {
}
}
}
@AfterUpdate()
public async updateRelatedMediaRequest(): Promise<void> {
const requestRepository = getRepository(MediaRequest);
const relatedRequests = await requestRepository.find({
relations: {
media: true,
},
where: {
media: { id: this.id },
status: MediaRequestStatus.APPROVED,
},
});
// Check the media entity status and if
// available or deleted, set the related request
// to completed
if (relatedRequests.length > 0) {
relatedRequests.forEach((request) => {
if (
this[request.is4k ? 'status4k' : 'status'] ===
MediaStatus.AVAILABLE ||
this[request.is4k ? 'status4k' : 'status'] === MediaStatus.DELETED
) {
request.status = MediaRequestStatus.COMPLETED;
}
});
requestRepository.save(relatedRequests);
}
}
}
export default Media;

View File

@@ -167,7 +167,8 @@ export class MediaRequest {
// If there is an existing movie request that isn't declined, don't allow a new one.
if (
requestBody.mediaType === MediaType.MOVIE &&
existing[0].status !== MediaRequestStatus.DECLINED
existing[0].status !== MediaRequestStatus.DECLINED &&
existing[0].status !== MediaRequestStatus.COMPLETED
) {
logger.warn('Duplicate request for media blocked', {
tmdbId: tmdbMedia.id,
@@ -246,7 +247,9 @@ export class MediaRequest {
>;
const requestedSeasons =
requestBody.seasons === 'all'
? tmdbMediaShow.seasons.map((season) => season.season_number)
? tmdbMediaShow.seasons
.map((season) => season.season_number)
.filter((sn) => sn > 0)
: (requestBody.seasons as number[]);
let existingSeasons: number[] = [];
@@ -258,7 +261,8 @@ export class MediaRequest {
.filter(
(request) =>
request.is4k === requestBody.is4k &&
request.status !== MediaRequestStatus.DECLINED
request.status !== MediaRequestStatus.DECLINED &&
request.status !== MediaRequestStatus.COMPLETED
)
.reduce((seasons, request) => {
const combinedSeasons = request.seasons.map(
@@ -277,7 +281,9 @@ export class MediaRequest {
.filter(
(season) =>
season[requestBody.is4k ? 'status4k' : 'status'] !==
MediaStatus.UNKNOWN
MediaStatus.UNKNOWN &&
season[requestBody.is4k ? 'status4k' : 'status'] !==
MediaStatus.DELETED
)
.map((season) => season.seasonNumber),
];
@@ -581,7 +587,8 @@ export class MediaRequest {
if (
media.mediaType === MediaType.MOVIE &&
this.status === MediaRequestStatus.DECLINED
this.status === MediaRequestStatus.DECLINED &&
media[this.is4k ? 'status4k' : 'status'] !== MediaStatus.DELETED
) {
media[this.is4k ? 'status4k' : 'status'] = MediaStatus.UNKNOWN;
mediaRepository.save(media);
@@ -599,7 +606,8 @@ export class MediaRequest {
media.requests.filter(
(request) => request.status === MediaRequestStatus.PENDING
).length === 0 &&
media[this.is4k ? 'status4k' : 'status'] === MediaStatus.PENDING
media[this.is4k ? 'status4k' : 'status'] === MediaStatus.PENDING &&
media[this.is4k ? 'status4k' : 'status'] !== MediaStatus.DELETED
) {
media[this.is4k ? 'status4k' : 'status'] = MediaStatus.UNKNOWN;
mediaRepository.save(media);

View File

@@ -1,5 +1,8 @@
import { MediaStatus } from '@server/constants/media';
import { MediaRequestStatus, MediaStatus } from '@server/constants/media';
import { getRepository } from '@server/datasource';
import SeasonRequest from '@server/entity/SeasonRequest';
import {
AfterUpdate,
Column,
CreateDateColumn,
Entity,
@@ -35,6 +38,37 @@ class Season {
constructor(init?: Partial<Season>) {
Object.assign(this, init);
}
@AfterUpdate()
public async updateSeasonRequests(): Promise<void> {
const seasonRequestRepository = getRepository(SeasonRequest);
const relatedSeasonRequests = await seasonRequestRepository.find({
relations: {
request: true,
},
where: {
request: { media: { id: (await this.media).id } },
seasonNumber: this.seasonNumber,
},
});
// Check seasons when/if they become available or deleted,
// then set the related season request to completed
relatedSeasonRequests.forEach((seasonRequest) => {
if (
this.seasonNumber === seasonRequest.seasonNumber &&
((!seasonRequest.request.is4k &&
(this.status === MediaStatus.AVAILABLE ||
this.status === MediaStatus.DELETED)) ||
(seasonRequest.request.is4k &&
this.status4k === MediaStatus.AVAILABLE) ||
this.status4k === MediaStatus.DELETED)
)
seasonRequest.status = MediaRequestStatus.COMPLETED;
});
seasonRequestRepository.save(relatedSeasonRequests);
}
}
export default Season;

View File

@@ -1,7 +1,7 @@
import { MediaRequestStatus } from '@server/constants/media';
import { getRepository } from '@server/datasource';
import {
AfterRemove,
AfterUpdate,
Column,
CreateDateColumn,
Entity,
@@ -37,15 +37,25 @@ class SeasonRequest {
Object.assign(this, init);
}
@AfterRemove()
public async handleRemoveParent(): Promise<void> {
const mediaRequestRepository = getRepository(MediaRequest);
const requestToBeDeleted = await mediaRequestRepository.findOneOrFail({
@AfterUpdate()
public async updateMediaRequests(): Promise<void> {
const requestRepository = getRepository(MediaRequest);
const relatedRequest = await requestRepository.findOne({
where: { id: this.request.id },
});
if (requestToBeDeleted.seasons.length === 0) {
await mediaRequestRepository.delete({ id: this.request.id });
// Check the parent of the season request and
// if every season request is complete
// set the parent request to complete as well
const isRequestComplete = relatedRequest?.seasons.every(
(seasonRequest) => seasonRequest.status === MediaRequestStatus.COMPLETED
);
if (isRequestComplete && relatedRequest) {
relatedRequest.status = MediaRequestStatus.COMPLETED;
requestRepository.save(relatedRequest);
}
}
}

View File

@@ -1,7 +1,6 @@
import availabilitySync from '@server/lib/availabilitySync';
import downloadTracker from '@server/lib/downloadtracker';
import ImageProxy from '@server/lib/imageproxy';
import refreshToken from '@server/lib/refreshToken';
import { plexFullScanner, plexRecentScanner } from '@server/lib/scanners/plex';
import { radarrScanner } from '@server/lib/scanners/radarr';
import { sonarrScanner } from '@server/lib/scanners/sonarr';
@@ -9,6 +8,7 @@ import type { JobId } from '@server/lib/settings';
import { getSettings } from '@server/lib/settings';
import watchlistSync from '@server/lib/watchlistsync';
import logger from '@server/logger';
import random from 'lodash/random';
import schedule from 'node-schedule';
interface ScheduledJob {
@@ -62,20 +62,30 @@ export const startJobs = (): void => {
});
// Watchlist Sync
scheduledJobs.push({
const watchlistSyncJob: ScheduledJob = {
id: 'plex-watchlist-sync',
name: 'Plex Watchlist Sync',
type: 'process',
interval: 'seconds',
interval: 'fixed',
cronSchedule: jobs['plex-watchlist-sync'].schedule,
job: schedule.scheduleJob(jobs['plex-watchlist-sync'].schedule, () => {
job: schedule.scheduleJob(new Date(Date.now() + 1000 * 60 * 20), () => {
logger.info('Starting scheduled job: Plex Watchlist Sync', {
label: 'Jobs',
});
watchlistSync.syncWatchlist();
}),
};
// To help alleviate load on Plex's servers, we will add some fuzziness to the next schedule
// after each run
watchlistSyncJob.job.on('run', () => {
watchlistSyncJob.job.schedule(
new Date(Math.floor(Date.now() + 1000 * 60 * random(14, 24, true)))
);
});
scheduledJobs.push(watchlistSyncJob);
// Run full radarr scan every 24 hours
scheduledJobs.push({
id: 'radarr-scan',
@@ -169,19 +179,5 @@ export const startJobs = (): void => {
}),
});
scheduledJobs.push({
id: 'plex-refresh-token',
name: 'Plex Refresh Token',
type: 'process',
interval: 'fixed',
cronSchedule: jobs['plex-refresh-token'].schedule,
job: schedule.scheduleJob(jobs['plex-refresh-token'].schedule, () => {
logger.info('Starting scheduled job: Plex Refresh Token', {
label: 'Jobs',
});
refreshToken.run();
}),
});
logger.info('Scheduled jobs loaded', { label: 'Jobs' });
};

View File

@@ -8,7 +8,6 @@ import { getRepository } from '@server/datasource';
import Media from '@server/entity/Media';
import MediaRequest from '@server/entity/MediaRequest';
import type Season from '@server/entity/Season';
import SeasonRequest from '@server/entity/SeasonRequest';
import { User } from '@server/entity/User';
import type { RadarrSettings, SonarrSettings } from '@server/lib/settings';
import { getSettings } from '@server/lib/settings';
@@ -243,105 +242,66 @@ class AvailabilitySync {
} while (mediaPage.length > 0);
}
private findMediaStatus(
requests: MediaRequest[],
is4k: boolean
): MediaStatus {
const filteredRequests = requests.filter(
(request) => request.is4k === is4k
);
let mediaStatus: MediaStatus;
if (
filteredRequests.some(
(request) => request.status === MediaRequestStatus.APPROVED
)
) {
mediaStatus = MediaStatus.PROCESSING;
} else if (
filteredRequests.some(
(request) => request.status === MediaRequestStatus.PENDING
)
) {
mediaStatus = MediaStatus.PENDING;
} else {
mediaStatus = MediaStatus.UNKNOWN;
}
return mediaStatus;
}
private async mediaUpdater(media: Media, is4k: boolean): Promise<void> {
const mediaRepository = getRepository(Media);
const requestRepository = getRepository(MediaRequest);
try {
// Find all related requests only if
// the related media has an available status
const requests = await requestRepository
.createQueryBuilder('request')
.leftJoinAndSelect('request.media', 'media')
.where('(media.id = :id)', {
id: media.id,
})
.andWhere(
`(request.is4k = :is4k AND media.${
is4k ? 'status4k' : 'status'
} IN (:...mediaStatus))`,
{
mediaStatus: [
MediaStatus.AVAILABLE,
MediaStatus.PARTIALLY_AVAILABLE,
],
is4k: is4k,
}
)
.getMany();
// Check if a season is processing or pending to
// make sure we set the media to the correct status
let mediaStatus = MediaStatus.UNKNOWN;
// If media type is tv, check if a season is processing
//to see if we need to keep the external metadata
let isMediaProcessing = false;
if (media.mediaType === 'tv') {
mediaStatus = this.findMediaStatus(requests, is4k);
const requestRepository = getRepository(MediaRequest);
const request = await requestRepository
.createQueryBuilder('request')
.leftJoinAndSelect('request.media', 'media')
.where('(media.id = :id)', {
id: media.id,
})
.andWhere(
'(request.is4k = :is4k AND request.status = :requestStatus)',
{
requestStatus: MediaRequestStatus.APPROVED,
is4k: is4k,
}
)
.getOne();
if (request) {
isMediaProcessing = true;
}
}
media[is4k ? 'status4k' : 'status'] = mediaStatus;
media[is4k ? 'serviceId4k' : 'serviceId'] =
mediaStatus === MediaStatus.PROCESSING
? media[is4k ? 'serviceId4k' : 'serviceId']
: null;
// Set the non-4K or 4K media to deleted
// and change related columns to null if media
// is not processing
media[is4k ? 'status4k' : 'status'] = MediaStatus.DELETED;
media[is4k ? 'serviceId4k' : 'serviceId'] = isMediaProcessing
? media[is4k ? 'serviceId4k' : 'serviceId']
: null;
media[is4k ? 'externalServiceId4k' : 'externalServiceId'] =
mediaStatus === MediaStatus.PROCESSING
isMediaProcessing
? media[is4k ? 'externalServiceId4k' : 'externalServiceId']
: null;
media[is4k ? 'externalServiceSlug4k' : 'externalServiceSlug'] =
mediaStatus === MediaStatus.PROCESSING
isMediaProcessing
? media[is4k ? 'externalServiceSlug4k' : 'externalServiceSlug']
: null;
media[is4k ? 'ratingKey4k' : 'ratingKey'] =
mediaStatus === MediaStatus.PROCESSING
? media[is4k ? 'ratingKey4k' : 'ratingKey']
: null;
media[is4k ? 'ratingKey4k' : 'ratingKey'] = isMediaProcessing
? media[is4k ? 'ratingKey4k' : 'ratingKey']
: null;
logger.info(
`The ${is4k ? '4K' : 'non-4K'} ${
media.mediaType === 'movie' ? 'movie' : 'show'
} [TMDB ID ${media.tmdbId}] was not found in any ${
media.mediaType === 'movie' ? 'Radarr' : 'Sonarr'
} and Plex instance. Status will be changed to unknown.`,
} and Plex instance. Status will be changed to deleted.`,
{ label: 'AvailabilitySync' }
);
await mediaRepository.save({ media, ...media });
// Only delete media request if type is movie.
// Type tv request deletion is handled
// in the season request entity
if (requests.length > 0 && media.mediaType === 'movie') {
await requestRepository.remove(requests);
}
} catch (ex) {
logger.debug(
`Failure updating the ${is4k ? '4K' : 'non-4K'} ${
@@ -361,35 +321,21 @@ class AvailabilitySync {
is4k: boolean
): Promise<void> {
const mediaRepository = getRepository(Media);
const seasonRequestRepository = getRepository(SeasonRequest);
// Filter out only the values that are false
// (media that should be deleted)
const seasonsPendingRemoval = new Map(
// Disabled linter as only the value is needed from the filter
// eslint-disable-next-line @typescript-eslint/no-unused-vars
[...seasons].filter(([_, exists]) => !exists)
);
// Retrieve the season keys to pass into our log
const seasonKeys = [...seasonsPendingRemoval.keys()];
try {
// Need to check and see if there are any related season
// requests. If they are, we will need to delete them.
const seasonRequests = await seasonRequestRepository
.createQueryBuilder('seasonRequest')
.leftJoinAndSelect('seasonRequest.request', 'request')
.leftJoinAndSelect('request.media', 'media')
.where('(media.id = :id)', { id: media.id })
.andWhere(
'(request.is4k = :is4k AND seasonRequest.seasonNumber IN (:...seasonNumbers))',
{
seasonNumbers: seasonKeys,
is4k: is4k,
}
)
.getMany();
for (const mediaSeason of media.seasons) {
if (seasonsPendingRemoval.has(mediaSeason.seasonNumber)) {
mediaSeason[is4k ? 'status4k' : 'status'] = MediaStatus.UNKNOWN;
mediaSeason[is4k ? 'status4k' : 'status'] = MediaStatus.DELETED;
}
}
@@ -411,16 +357,12 @@ class AvailabilitySync {
await mediaRepository.save({ media, ...media });
if (seasonRequests.length > 0) {
await seasonRequestRepository.remove(seasonRequests);
}
logger.info(
`The ${is4k ? '4K' : 'non-4K'} season(s) [${seasonKeys}] [TMDB ID ${
media.tmdbId
}] was not found in any ${
media.mediaType === 'tv' ? 'Sonarr' : 'Radarr'
} and Plex instance. Status will be changed to unknown.`,
} and Plex instance. Status will be changed to deleted.`,
{ label: 'AvailabilitySync' }
);
} catch (ex) {
@@ -445,41 +387,43 @@ class AvailabilitySync {
// Check for availability in all of the available radarr servers
// If any find the media, we will assume the media exists
for (const server of this.radarrServers) {
const radarrAPI = new RadarrAPI({
apiKey: server.apiKey,
url: RadarrAPI.buildUrl(server, '/api/v3'),
});
if (server.is4k === is4k) {
const radarrAPI = new RadarrAPI({
apiKey: server.apiKey,
url: RadarrAPI.buildUrl(server, '/api/v3'),
});
try {
let radarr: RadarrMovie | undefined;
try {
let radarr: RadarrMovie | undefined;
if (!server.is4k && media.externalServiceId && !is4k) {
radarr = await radarrAPI.getMovie({
id: media.externalServiceId,
});
}
if (media.externalServiceId && !is4k) {
radarr = await radarrAPI.getMovie({
id: media.externalServiceId,
});
}
if (server.is4k && media.externalServiceId4k && is4k) {
radarr = await radarrAPI.getMovie({
id: media.externalServiceId4k,
});
}
if (media.externalServiceId4k && is4k) {
radarr = await radarrAPI.getMovie({
id: media.externalServiceId4k,
});
}
if (radarr && radarr.hasFile) {
existsInRadarr = true;
}
} catch (ex) {
if (!ex.message.includes('404')) {
existsInRadarr = true;
logger.debug(
`Failure retrieving the ${is4k ? '4K' : 'non-4K'} movie [TMDB ID ${
media.tmdbId
}] from Radarr.`,
{
errorMessage: ex.message,
label: 'AvailabilitySync',
}
);
if (radarr && radarr.hasFile) {
existsInRadarr = true;
}
} catch (ex) {
if (!ex.message.includes('404')) {
existsInRadarr = true;
logger.debug(
`Failure retrieving the ${
is4k ? '4K' : 'non-4K'
} movie [TMDB ID ${media.tmdbId}] from Radarr.`,
{
errorMessage: ex.message,
label: 'AvailabilitySync',
}
);
}
}
}
}
@@ -497,42 +441,45 @@ class AvailabilitySync {
// Check for availability in all of the available sonarr servers
// If any find the media, we will assume the media exists
for (const server of this.sonarrServers) {
const sonarrAPI = new SonarrAPI({
apiKey: server.apiKey,
url: SonarrAPI.buildUrl(server, '/api/v3'),
});
if (server.is4k === is4k) {
const sonarrAPI = new SonarrAPI({
apiKey: server.apiKey,
url: SonarrAPI.buildUrl(server, '/api/v3'),
});
try {
let sonarr: SonarrSeries | undefined;
try {
let sonarr: SonarrSeries | undefined;
if (!server.is4k && media.externalServiceId && !is4k) {
sonarr = await sonarrAPI.getSeriesById(media.externalServiceId);
this.sonarrSeasonsCache[`${server.id}-${media.externalServiceId}`] =
sonarr.seasons;
}
if (media.externalServiceId && !is4k) {
sonarr = await sonarrAPI.getSeriesById(media.externalServiceId);
this.sonarrSeasonsCache[`${server.id}-${media.externalServiceId}`] =
sonarr.seasons;
}
if (server.is4k && media.externalServiceId4k && is4k) {
sonarr = await sonarrAPI.getSeriesById(media.externalServiceId4k);
this.sonarrSeasonsCache[`${server.id}-${media.externalServiceId4k}`] =
sonarr.seasons;
}
if (media.externalServiceId4k && is4k) {
sonarr = await sonarrAPI.getSeriesById(media.externalServiceId4k);
this.sonarrSeasonsCache[
`${server.id}-${media.externalServiceId4k}`
] = sonarr.seasons;
}
if (sonarr && sonarr.statistics.episodeFileCount > 0) {
existsInSonarr = true;
}
} catch (ex) {
if (!ex.message.includes('404')) {
existsInSonarr = true;
preventSeasonSearch = true;
logger.debug(
`Failure retrieving the ${is4k ? '4K' : 'non-4K'} show [TMDB ID ${
media.tmdbId
}] from Sonarr.`,
{
errorMessage: ex.message,
label: 'AvailabilitySync',
}
);
if (sonarr && sonarr.statistics.episodeFileCount > 0) {
existsInSonarr = true;
}
} catch (ex) {
if (!ex.message.includes('404')) {
existsInSonarr = true;
preventSeasonSearch = true;
logger.debug(
`Failure retrieving the ${is4k ? '4K' : 'non-4K'} show [TMDB ID ${
media.tmdbId
}] from Sonarr.`,
{
errorMessage: ex.message,
label: 'AvailabilitySync',
}
);
}
}
}
}
@@ -722,4 +669,5 @@ class AvailabilitySync {
}
const availabilitySync = new AvailabilitySync();
export default availabilitySync;

View File

@@ -8,8 +8,7 @@ export type AvailableCacheIds =
| 'imdb'
| 'github'
| 'plexguid'
| 'plextv'
| 'plexwatchlist';
| 'plextv';
const DEFAULT_TTL = 300;
const DEFAULT_CHECK_PERIOD = 120;
@@ -69,7 +68,6 @@ class CacheManager {
stdTtl: 86400 * 7, // 1 week cache
checkPeriod: 60,
}),
plexwatchlist: new Cache('plexwatchlist', 'Plex Watchlist'),
};
public getCache(id: AvailableCacheIds): Cache {

View File

@@ -1,37 +0,0 @@
import PlexTvAPI from '@server/api/plextv';
import { getRepository } from '@server/datasource';
import { User } from '@server/entity/User';
import logger from '@server/logger';
class RefreshToken {
public async run() {
const userRepository = getRepository(User);
const users = await userRepository
.createQueryBuilder('user')
.addSelect('user.plexToken')
.where("user.plexToken != ''")
.getMany();
for (const user of users) {
await this.refreshUserToken(user);
}
}
private async refreshUserToken(user: User) {
if (!user.plexToken) {
logger.warn('Skipping user refresh token for user without plex token', {
label: 'Plex Refresh Token',
user: user.displayName,
});
return;
}
const plexTvApi = new PlexTvAPI(user.plexToken);
plexTvApi.pingToken();
}
}
const refreshToken = new RefreshToken();
export default refreshToken;

View File

@@ -281,7 +281,9 @@ class BaseScanner<T> {
? MediaStatus.AVAILABLE
: season.episodes > 0
? MediaStatus.PARTIALLY_AVAILABLE
: !season.is4kOverride && season.processing
: !season.is4kOverride &&
season.processing &&
existingSeason.status !== MediaStatus.DELETED
? MediaStatus.PROCESSING
: existingSeason.status;
@@ -294,7 +296,9 @@ class BaseScanner<T> {
? MediaStatus.AVAILABLE
: this.enable4kShow && season.episodes4k > 0
? MediaStatus.PARTIALLY_AVAILABLE
: season.is4kOverride && season.processing
: season.is4kOverride &&
season.processing &&
existingSeason.status4k !== MediaStatus.DELETED
? MediaStatus.PROCESSING
: existingSeason.status4k;
} else {
@@ -401,13 +405,18 @@ class BaseScanner<T> {
// the status
const shouldStayAvailable =
media.status === MediaStatus.AVAILABLE &&
newSeasons.filter((season) => season.status !== MediaStatus.UNKNOWN)
.length === 0;
newSeasons.filter(
(season) =>
season.status !== MediaStatus.UNKNOWN &&
season.status !== MediaStatus.DELETED
).length === 0;
const shouldStayAvailable4k =
media.status4k === MediaStatus.AVAILABLE &&
newSeasons.filter((season) => season.status4k !== MediaStatus.UNKNOWN)
.length === 0;
newSeasons.filter(
(season) =>
season.status4k !== MediaStatus.UNKNOWN &&
season.status4k !== MediaStatus.DELETED
).length === 0;
media.status =
isAllStandardSeasons || shouldStayAvailable
? MediaStatus.AVAILABLE
@@ -417,11 +426,13 @@ class BaseScanner<T> {
season.status === MediaStatus.AVAILABLE
)
? MediaStatus.PARTIALLY_AVAILABLE
: !seasons.length ||
: (!seasons.length && media.status !== MediaStatus.DELETED) ||
media.seasons.some(
(season) => season.status === MediaStatus.PROCESSING
)
? MediaStatus.PROCESSING
: media.status === MediaStatus.DELETED
? MediaStatus.DELETED
: MediaStatus.UNKNOWN;
media.status4k =
(isAll4kSeasons || shouldStayAvailable4k) && this.enable4kShow
@@ -433,11 +444,13 @@ class BaseScanner<T> {
season.status4k === MediaStatus.AVAILABLE
)
? MediaStatus.PARTIALLY_AVAILABLE
: !seasons.length ||
: (!seasons.length && media.status4k !== MediaStatus.DELETED) ||
media.seasons.some(
(season) => season.status4k === MediaStatus.PROCESSING
)
? MediaStatus.PROCESSING
: media.status4k === MediaStatus.DELETED
? MediaStatus.DELETED
: MediaStatus.UNKNOWN;
await mediaRepository.save(media);
this.log(`Updating existing title: ${title}`);

View File

@@ -278,7 +278,9 @@ class PlexScanner
const seasons = tvShow.seasons;
const processableSeasons: ProcessableSeason[] = [];
for (const season of seasons) {
const filteredSeasons = seasons.filter((sn) => sn.season_number !== 0);
for (const season of filteredSeasons) {
const matchedPlexSeason = metadata.Children?.Metadata.find(
(md) => Number(md.index) === season.season_number
);

View File

@@ -103,8 +103,10 @@ class SonarrScanner
const tmdbId = tvShow.id;
const filteredSeasons = sonarrSeries.seasons.filter((sn) =>
tvShow.seasons.find((s) => s.season_number === sn.seasonNumber)
const filteredSeasons = sonarrSeries.seasons.filter(
(sn) =>
sn.seasonNumber !== 0 &&
tvShow.seasons.find((s) => s.season_number === sn.seasonNumber)
);
for (const season of filteredSeasons) {

View File

@@ -248,7 +248,6 @@ export type JobId =
| 'plex-recently-added-scan'
| 'plex-full-scan'
| 'plex-watchlist-sync'
| 'plex-refresh-token'
| 'radarr-scan'
| 'sonarr-scan'
| 'download-sync'
@@ -408,10 +407,7 @@ class Settings {
schedule: '0 0 3 * * *',
},
'plex-watchlist-sync': {
schedule: '0 */3 * * * *',
},
'plex-refresh-token': {
schedule: '0 0 5 * * *',
schedule: '0 */10 * * * *',
},
'radarr-scan': {
schedule: '0 0 4 * * *',

View File

@@ -62,7 +62,7 @@ class WatchlistSync {
const plexTvApi = new PlexTvAPI(user.plexToken);
const response = await plexTvApi.getWatchlist({ size: 20 });
const response = await plexTvApi.getWatchlist({ size: 200 });
const mediaItems = await Media.getRelatedMedia(
response.items.map((i) => i.tmdbId)

View File

@@ -12,7 +12,7 @@ collectionRoutes.get<{ id: string }>('/:id', async (req, res, next) => {
try {
const collection = await tmdb.getCollection({
collectionId: Number(req.params.id),
language: (req.query.language as string) ?? req.locale,
language: req.locale ?? (req.query.language as string),
});
const media = await Media.getRelatedMedia(

View File

@@ -164,7 +164,7 @@ discoverRoutes.get<{ language: string }>(
const data = await tmdb.getDiscoverMovies({
page: Number(req.query.page),
language: (req.query.language as string) ?? req.locale,
language: req.locale ?? (req.query.language as string),
originalLanguage: req.params.language,
});
@@ -208,7 +208,7 @@ discoverRoutes.get<{ genreId: string }>(
try {
const genres = await tmdb.getMovieGenres({
language: (req.query.language as string) ?? req.locale,
language: req.locale ?? (req.query.language as string),
});
const genre = genres.find(
@@ -221,7 +221,7 @@ discoverRoutes.get<{ genreId: string }>(
const data = await tmdb.getDiscoverMovies({
page: Number(req.query.page),
language: (req.query.language as string) ?? req.locale,
language: req.locale ?? (req.query.language as string),
genre: req.params.genreId as string,
});
@@ -268,7 +268,7 @@ discoverRoutes.get<{ studioId: string }>(
const data = await tmdb.getDiscoverMovies({
page: Number(req.query.page),
language: (req.query.language as string) ?? req.locale,
language: req.locale ?? (req.query.language as string),
studio: req.params.studioId as string,
});
@@ -317,7 +317,7 @@ discoverRoutes.get('/movies/upcoming', async (req, res, next) => {
try {
const data = await tmdb.getDiscoverMovies({
page: Number(req.query.page),
language: (req.query.language as string) ?? req.locale,
language: req.locale ?? (req.query.language as string),
primaryReleaseDateGte: date,
});
@@ -440,7 +440,7 @@ discoverRoutes.get<{ language: string }>(
const data = await tmdb.getDiscoverTv({
page: Number(req.query.page),
language: (req.query.language as string) ?? req.locale,
language: req.locale ?? (req.query.language as string),
originalLanguage: req.params.language,
});
@@ -484,7 +484,7 @@ discoverRoutes.get<{ genreId: string }>(
try {
const genres = await tmdb.getTvGenres({
language: (req.query.language as string) ?? req.locale,
language: req.locale ?? (req.query.language as string),
});
const genre = genres.find(
@@ -497,7 +497,7 @@ discoverRoutes.get<{ genreId: string }>(
const data = await tmdb.getDiscoverTv({
page: Number(req.query.page),
language: (req.query.language as string) ?? req.locale,
language: req.locale ?? (req.query.language as string),
genre: req.params.genreId,
});
@@ -544,7 +544,7 @@ discoverRoutes.get<{ networkId: string }>(
const data = await tmdb.getDiscoverTv({
page: Number(req.query.page),
language: (req.query.language as string) ?? req.locale,
language: req.locale ?? (req.query.language as string),
network: Number(req.params.networkId),
});
@@ -593,7 +593,7 @@ discoverRoutes.get('/tv/upcoming', async (req, res, next) => {
try {
const data = await tmdb.getDiscoverTv({
page: Number(req.query.page),
language: (req.query.language as string) ?? req.locale,
language: req.locale ?? (req.query.language as string),
firstAirDateGte: date,
});
@@ -632,7 +632,7 @@ discoverRoutes.get('/trending', async (req, res, next) => {
try {
const data = await tmdb.getAllTrending({
page: Number(req.query.page),
language: (req.query.language as string) ?? req.locale,
language: req.locale ?? (req.query.language as string),
});
const media = await Media.getRelatedMedia(
@@ -686,7 +686,7 @@ discoverRoutes.get<{ keywordId: string }>(
const data = await tmdb.getMoviesByKeyword({
keywordId: Number(req.params.keywordId),
page: Number(req.query.page),
language: (req.query.language as string) ?? req.locale,
language: req.locale ?? (req.query.language as string),
});
const media = await Media.getRelatedMedia(
@@ -730,7 +730,7 @@ discoverRoutes.get<{ language: string }, GenreSliderItem[]>(
const mappedGenres: GenreSliderItem[] = [];
const genres = await tmdb.getMovieGenres({
language: (req.query.language as string) ?? req.locale,
language: req.locale ?? (req.query.language as string),
});
await Promise.all(
@@ -774,7 +774,7 @@ discoverRoutes.get<{ language: string }, GenreSliderItem[]>(
const mappedGenres: GenreSliderItem[] = [];
const genres = await tmdb.getTvGenres({
language: (req.query.language as string) ?? req.locale,
language: req.locale ?? (req.query.language as string),
});
await Promise.all(

View File

@@ -235,7 +235,7 @@ router.get('/genres/movie', isAuthenticated(), async (req, res, next) => {
try {
const genres = await tmdb.getMovieGenres({
language: (req.query.language as string) ?? req.locale,
language: req.locale ?? (req.query.language as string),
});
return res.status(200).json(genres);
@@ -256,7 +256,7 @@ router.get('/genres/tv', isAuthenticated(), async (req, res, next) => {
try {
const genres = await tmdb.getTvGenres({
language: (req.query.language as string) ?? req.locale,
language: req.locale ?? (req.query.language as string),
});
return res.status(200).json(genres);

View File

@@ -1,7 +1,13 @@
import TautulliAPI from '@server/api/tautulli';
import { MediaStatus, MediaType } from '@server/constants/media';
import {
MediaRequestStatus,
MediaStatus,
MediaType,
} from '@server/constants/media';
import { getRepository } from '@server/datasource';
import Media from '@server/entity/Media';
import MediaRequest from '@server/entity/MediaRequest';
import SeasonRequest from '@server/entity/SeasonRequest';
import { User } from '@server/entity/User';
import type {
MediaResultsResponse,
@@ -98,6 +104,8 @@ mediaRoutes.post<
isAuthenticated(Permission.MANAGE_REQUESTS),
async (req, res, next) => {
const mediaRepository = getRepository(Media);
const requestRepository = getRepository(MediaRequest);
const seasonRequestRepository = getRepository(SeasonRequest);
const media = await mediaRepository.findOne({
where: { id: Number(req.params.id) },
@@ -138,6 +146,35 @@ mediaRoutes.post<
media.status = MediaStatus.UNKNOWN;
}
if (req.params.status === 'available') {
// Here we check all related media requests and
// then set to completed if the media is marked
// as available
const requests = await requestRepository.find({
relations: {
media: true,
},
where: { media: { id: media.id }, is4k: is4k },
});
const requestIds = requests.map((request) => request.id);
if (requestIds.length > 0) {
await requestRepository.update(
{ id: In(requestIds) },
{ status: MediaRequestStatus.COMPLETED }
);
}
requests
.flatMap((request) => request.seasons)
.forEach(async (season) => {
await seasonRequestRepository.update(season.id, {
status: MediaRequestStatus.COMPLETED,
});
});
}
await mediaRepository.save(media);
return res.status(200).json(media);

View File

@@ -17,7 +17,7 @@ movieRoutes.get('/:id', async (req, res, next) => {
try {
const tmdbMovie = await tmdb.getMovie({
movieId: Number(req.params.id),
language: (req.query.language as string) ?? req.locale,
language: req.locale ?? (req.query.language as string),
});
const media = await Media.getMedia(tmdbMovie.id, MediaType.MOVIE);
@@ -43,7 +43,7 @@ movieRoutes.get('/:id/recommendations', async (req, res, next) => {
const results = await tmdb.getMovieRecommendations({
movieId: Number(req.params.id),
page: Number(req.query.page),
language: (req.query.language as string) ?? req.locale,
language: req.locale ?? (req.query.language as string),
});
const media = await Media.getRelatedMedia(
@@ -84,7 +84,7 @@ movieRoutes.get('/:id/similar', async (req, res, next) => {
const results = await tmdb.getMovieSimilar({
movieId: Number(req.params.id),
page: Number(req.query.page),
language: (req.query.language as string) ?? req.locale,
language: req.locale ?? (req.query.language as string),
});
const media = await Media.getRelatedMedia(

View File

@@ -16,7 +16,7 @@ personRoutes.get('/:id', async (req, res, next) => {
try {
const person = await tmdb.getPerson({
personId: Number(req.params.id),
language: (req.query.language as string) ?? req.locale,
language: req.locale ?? (req.query.language as string),
});
return res.status(200).json(mapPersonDetails(person));
} catch (e) {
@@ -38,7 +38,7 @@ personRoutes.get('/:id/combined_credits', async (req, res, next) => {
try {
const combinedCredits = await tmdb.getPersonCombinedCredits({
personId: Number(req.params.id),
language: (req.query.language as string) ?? req.locale,
language: req.locale ?? (req.query.language as string),
});
const castMedia = await Media.getRelatedMedia(

View File

@@ -40,7 +40,6 @@ requestRoutes.get<Record<string, unknown>, RequestResultsResponse>(
switch (req.query.filter) {
case 'approved':
case 'processing':
case 'available':
statusFilter = [MediaRequestStatus.APPROVED];
break;
case 'pending':
@@ -55,12 +54,18 @@ requestRoutes.get<Record<string, unknown>, RequestResultsResponse>(
case 'failed':
statusFilter = [MediaRequestStatus.FAILED];
break;
case 'completed':
case 'available':
case 'deleted':
statusFilter = [MediaRequestStatus.COMPLETED];
break;
default:
statusFilter = [
MediaRequestStatus.PENDING,
MediaRequestStatus.APPROVED,
MediaRequestStatus.DECLINED,
MediaRequestStatus.FAILED,
MediaRequestStatus.COMPLETED,
];
}
@@ -79,6 +84,9 @@ requestRoutes.get<Record<string, unknown>, RequestResultsResponse>(
MediaStatus.PARTIALLY_AVAILABLE,
];
break;
case 'deleted':
mediaStatusFilter = [MediaStatus.DELETED];
break;
default:
mediaStatusFilter = [
MediaStatus.UNKNOWN,
@@ -86,6 +94,7 @@ requestRoutes.get<Record<string, unknown>, RequestResultsResponse>(
MediaStatus.PROCESSING,
MediaStatus.PARTIALLY_AVAILABLE,
MediaStatus.AVAILABLE,
MediaStatus.DELETED,
];
}
@@ -391,7 +400,8 @@ requestRoutes.put<{ requestId: string }>(
(r) =>
r.is4k === request.is4k &&
r.id !== request.id &&
r.status !== MediaRequestStatus.DECLINED
r.status !== MediaRequestStatus.DECLINED &&
r.status !== MediaRequestStatus.COMPLETED
)
.reduce((seasons, r) => {
const combinedSeasons = r.seasons.map(

View File

@@ -20,7 +20,7 @@ searchRoutes.get('/', async (req, res, next) => {
.match(searchProvider.pattern) as RegExpMatchArray;
results = await searchProvider.search({
id,
language: (req.query.language as string) ?? req.locale,
language: req.locale ?? (req.query.language as string),
query: queryString,
});
} else {
@@ -29,7 +29,7 @@ searchRoutes.get('/', async (req, res, next) => {
results = await tmdb.searchMulti({
query: queryString,
page: Number(req.query.page),
language: (req.query.language as string) ?? req.locale,
language: req.locale ?? (req.query.language as string),
});
}

View File

@@ -14,7 +14,7 @@ tvRoutes.get('/:id', async (req, res, next) => {
try {
const tv = await tmdb.getTvShow({
tvId: Number(req.params.id),
language: (req.query.language as string) ?? req.locale,
language: req.locale ?? (req.query.language as string),
});
const media = await Media.getMedia(tv.id, MediaType.TV);
@@ -40,7 +40,7 @@ tvRoutes.get('/:id/season/:seasonNumber', async (req, res, next) => {
const season = await tmdb.getTvSeason({
tvId: Number(req.params.id),
seasonNumber: Number(req.params.seasonNumber),
language: (req.query.language as string) ?? req.locale,
language: req.locale ?? (req.query.language as string),
});
return res.status(200).json(mapSeasonWithEpisodes(season));
@@ -65,7 +65,7 @@ tvRoutes.get('/:id/recommendations', async (req, res, next) => {
const results = await tmdb.getTvRecommendations({
tvId: Number(req.params.id),
page: Number(req.query.page),
language: (req.query.language as string) ?? req.locale,
language: req.locale ?? (req.query.language as string),
});
const media = await Media.getRelatedMedia(
@@ -105,7 +105,7 @@ tvRoutes.get('/:id/similar', async (req, res, next) => {
const results = await tmdb.getTvSimilar({
tvId: Number(req.params.id),
page: Number(req.query.page),
language: (req.query.language as string) ?? req.locale,
language: req.locale ?? (req.query.language as string),
});
const media = await Media.getRelatedMedia(

View File

@@ -0,0 +1,128 @@
import TheMovieDb from '@server/api/themoviedb';
import {
MediaRequestStatus,
MediaStatus,
MediaType,
} from '@server/constants/media';
import { MediaRequest } from '@server/entity/MediaRequest';
import notificationManager, { Notification } from '@server/lib/notifications';
import logger from '@server/logger';
import { truncate } from 'lodash';
import type { EntitySubscriberInterface, UpdateEvent } from 'typeorm';
import { EventSubscriber } from 'typeorm';
@EventSubscriber()
export class MediaRequestSubscriber
implements EntitySubscriberInterface<MediaRequest>
{
private async notifyAvailableMovie(entity: MediaRequest) {
if (entity.media.status === MediaStatus.AVAILABLE) {
const tmdb = new TheMovieDb();
try {
const movie = await tmdb.getMovie({
movieId: entity.media.tmdbId,
});
notificationManager.sendNotification(Notification.MEDIA_AVAILABLE, {
event: `${entity.is4k ? '4K ' : ''}Movie Request Now Available`,
notifyAdmin: false,
notifySystem: true,
notifyUser: entity.requestedBy,
subject: `${movie.title}${
movie.release_date ? ` (${movie.release_date.slice(0, 4)})` : ''
}`,
message: truncate(movie.overview, {
length: 500,
separator: /\s/,
omission: '…',
}),
media: entity.media,
image: `https://image.tmdb.org/t/p/w600_and_h900_bestv2${movie.poster_path}`,
request: entity,
});
} catch (e) {
logger.error('Something went wrong sending media notification(s)', {
label: 'Notifications',
errorMessage: e.message,
mediaId: entity.id,
});
}
}
}
private async notifyAvailableSeries(entity: MediaRequest) {
// Find all seasons in the related media entity
// and see if they are available, then we can check
// if the request contains the same seasons
const isMediaAvailable = entity.media.seasons
.filter(
(season) =>
season[entity.is4k ? 'status4k' : 'status'] === MediaStatus.AVAILABLE
)
.every((seasonRequest) =>
entity.seasons.find(
(season) => season.seasonNumber === seasonRequest.seasonNumber
)
);
if (entity.media.status === MediaStatus.AVAILABLE || isMediaAvailable) {
const tmdb = new TheMovieDb();
try {
const tv = await tmdb.getTvShow({ tvId: entity.media.tmdbId });
notificationManager.sendNotification(Notification.MEDIA_AVAILABLE, {
event: `${entity.is4k ? '4K ' : ''}Series Request Now Available`,
subject: `${tv.name}${
tv.first_air_date ? ` (${tv.first_air_date.slice(0, 4)})` : ''
}`,
message: truncate(tv.overview, {
length: 500,
separator: /\s/,
omission: '…',
}),
notifyAdmin: false,
notifySystem: true,
notifyUser: entity.requestedBy,
image: `https://image.tmdb.org/t/p/w600_and_h900_bestv2${tv.poster_path}`,
media: entity.media,
extra: [
{
name: 'Requested Seasons',
value: entity.seasons
.map((season) => season.seasonNumber)
.join(', '),
},
],
request: entity,
});
} catch (e) {
logger.error('Something went wrong sending media notification(s)', {
label: 'Notifications',
errorMessage: e.message,
mediaId: entity.id,
});
}
}
}
public afterUpdate(event: UpdateEvent<MediaRequest>): void {
if (!event.entity) {
return;
}
if (event.entity.status === MediaRequestStatus.COMPLETED) {
if (event.entity.media.mediaType === MediaType.MOVIE) {
this.notifyAvailableMovie(event.entity as MediaRequest);
}
if (event.entity.media.mediaType === MediaType.TV) {
this.notifyAvailableSeries(event.entity as MediaRequest);
}
}
}
public listenTo(): typeof MediaRequest {
return MediaRequest;
}
}

View File

@@ -1,179 +1,12 @@
import TheMovieDb from '@server/api/themoviedb';
import {
MediaRequestStatus,
MediaStatus,
MediaType,
} from '@server/constants/media';
import { MediaRequestStatus, MediaStatus } from '@server/constants/media';
import { getRepository } from '@server/datasource';
import Media from '@server/entity/Media';
import { MediaRequest } from '@server/entity/MediaRequest';
import Season from '@server/entity/Season';
import notificationManager, { Notification } from '@server/lib/notifications';
import logger from '@server/logger';
import { truncate } from 'lodash';
import type { EntitySubscriberInterface, UpdateEvent } from 'typeorm';
import { EventSubscriber, In, Not } from 'typeorm';
import { EventSubscriber } from 'typeorm';
@EventSubscriber()
export class MediaSubscriber implements EntitySubscriberInterface<Media> {
private async notifyAvailableMovie(
entity: Media,
dbEntity: Media,
is4k: boolean
) {
if (
entity[is4k ? 'status4k' : 'status'] === MediaStatus.AVAILABLE &&
dbEntity[is4k ? 'status4k' : 'status'] !== MediaStatus.AVAILABLE
) {
if (entity.mediaType === MediaType.MOVIE) {
const requestRepository = getRepository(MediaRequest);
const relatedRequests = await requestRepository.find({
where: {
media: {
id: entity.id,
},
is4k,
status: Not(MediaRequestStatus.DECLINED),
},
});
if (relatedRequests.length > 0) {
const tmdb = new TheMovieDb();
try {
const movie = await tmdb.getMovie({ movieId: entity.tmdbId });
relatedRequests.forEach((request) => {
notificationManager.sendNotification(
Notification.MEDIA_AVAILABLE,
{
event: `${is4k ? '4K ' : ''}Movie Request Now Available`,
notifyAdmin: false,
notifySystem: true,
notifyUser: request.requestedBy,
subject: `${movie.title}${
movie.release_date
? ` (${movie.release_date.slice(0, 4)})`
: ''
}`,
message: truncate(movie.overview, {
length: 500,
separator: /\s/,
omission: '…',
}),
media: entity,
image: `https://image.tmdb.org/t/p/w600_and_h900_bestv2${movie.poster_path}`,
request,
}
);
});
} catch (e) {
logger.error('Something went wrong sending media notification(s)', {
label: 'Notifications',
errorMessage: e.message,
mediaId: entity.id,
});
}
}
}
}
}
private async notifyAvailableSeries(
entity: Media,
dbEntity: Media,
is4k: boolean
) {
const seasonRepository = getRepository(Season);
const newAvailableSeasons = entity.seasons
.filter(
(season) =>
season[is4k ? 'status4k' : 'status'] === MediaStatus.AVAILABLE
)
.map((season) => season.seasonNumber);
const oldSeasonIds = dbEntity.seasons.map((season) => season.id);
const oldSeasons = await seasonRepository.findBy({ id: In(oldSeasonIds) });
const oldAvailableSeasons = oldSeasons
.filter(
(season) =>
season[is4k ? 'status4k' : 'status'] === MediaStatus.AVAILABLE
)
.map((season) => season.seasonNumber);
const changedSeasons = newAvailableSeasons.filter(
(seasonNumber) => !oldAvailableSeasons.includes(seasonNumber)
);
if (changedSeasons.length > 0) {
const tmdb = new TheMovieDb();
const requestRepository = getRepository(MediaRequest);
const processedSeasons: number[] = [];
for (const changedSeasonNumber of changedSeasons) {
const requests = await requestRepository.find({
where: {
media: {
id: entity.id,
},
is4k,
status: Not(MediaRequestStatus.DECLINED),
},
});
const request = requests.find(
(request) =>
// Check if the season is complete AND it contains the current season that was just marked available
request.seasons.every((season) =>
newAvailableSeasons.includes(season.seasonNumber)
) &&
request.seasons.some(
(season) => season.seasonNumber === changedSeasonNumber
)
);
if (request && !processedSeasons.includes(changedSeasonNumber)) {
processedSeasons.push(
...request.seasons.map((season) => season.seasonNumber)
);
try {
const tv = await tmdb.getTvShow({ tvId: entity.tmdbId });
notificationManager.sendNotification(Notification.MEDIA_AVAILABLE, {
event: `${is4k ? '4K ' : ''}Series Request Now Available`,
subject: `${tv.name}${
tv.first_air_date ? ` (${tv.first_air_date.slice(0, 4)})` : ''
}`,
message: truncate(tv.overview, {
length: 500,
separator: /\s/,
omission: '…',
}),
notifyAdmin: false,
notifySystem: true,
notifyUser: request.requestedBy,
image: `https://image.tmdb.org/t/p/w600_and_h900_bestv2${tv.poster_path}`,
media: entity,
extra: [
{
name: 'Requested Seasons',
value: request.seasons
.map((season) => season.seasonNumber)
.join(', '),
},
],
request,
});
} catch (e) {
logger.error('Something went wrong sending media notification(s)', {
label: 'Notifications',
errorMessage: e.message,
mediaId: entity.id,
});
}
}
}
}
}
private async updateChildRequestStatus(event: Media, is4k: boolean) {
const requestRepository = getRepository(MediaRequest);
@@ -197,52 +30,6 @@ export class MediaSubscriber implements EntitySubscriberInterface<Media> {
return;
}
if (
event.entity.mediaType === MediaType.MOVIE &&
event.entity.status === MediaStatus.AVAILABLE
) {
this.notifyAvailableMovie(
event.entity as Media,
event.databaseEntity,
false
);
}
if (
event.entity.mediaType === MediaType.MOVIE &&
event.entity.status4k === MediaStatus.AVAILABLE
) {
this.notifyAvailableMovie(
event.entity as Media,
event.databaseEntity,
true
);
}
if (
event.entity.mediaType === MediaType.TV &&
(event.entity.status === MediaStatus.AVAILABLE ||
event.entity.status === MediaStatus.PARTIALLY_AVAILABLE)
) {
this.notifyAvailableSeries(
event.entity as Media,
event.databaseEntity,
false
);
}
if (
event.entity.mediaType === MediaType.TV &&
(event.entity.status4k === MediaStatus.AVAILABLE ||
event.entity.status4k === MediaStatus.PARTIALLY_AVAILABLE)
) {
this.notifyAvailableSeries(
event.entity as Media,
event.databaseEntity,
true
);
}
if (
event.entity.status === MediaStatus.AVAILABLE &&
event.databaseEntity.status === MediaStatus.PENDING

View File

@@ -1,6 +1,11 @@
import Spinner from '@app/assets/spinner.svg';
import { CheckCircleIcon } from '@heroicons/react/20/solid';
import { BellIcon, ClockIcon, MinusSmallIcon } from '@heroicons/react/24/solid';
import {
BellIcon,
ClockIcon,
MinusSmallIcon,
TrashIcon,
} from '@heroicons/react/24/solid';
import { MediaStatus } from '@server/constants/media';
interface StatusBadgeMiniProps {
@@ -50,6 +55,10 @@ const StatusBadgeMini = ({
);
indicatorIcon = <MinusSmallIcon />;
break;
case MediaStatus.DELETED:
badgeStyle.push('bg-red-500 border-red-400 ring-red-400 text-red-100');
indicatorIcon = <TrashIcon />;
break;
}
if (inProgress) {

View File

@@ -73,12 +73,6 @@ const studios: Studio[] = [
'https://image.tmdb.org/t/p/w780_filter(duotone,ffffff,bababa)/2Tc1P3Ac8M479naPp1kYT3izLS5.png',
url: '/discover/movies/studio/9993',
},
{
name: 'A24',
image:
'https://image.tmdb.org/t/p/w780_filter(duotone,ffffff,bababa)/1ZXsGaFPgrgS6ZZGS37AqD5uU12.png',
url: '/discover/movies/studio/41077',
},
];
const StudioSlider = () => {

View File

@@ -4,6 +4,7 @@ import { useEffect, useRef, useState } from 'react';
const PullToRefresh = () => {
const router = useRouter();
const [pullStartPoint, setPullStartPoint] = useState(0);
const [pullChange, setPullChange] = useState(0);
const refreshDiv = useRef<HTMLDivElement>(null);
@@ -18,7 +19,6 @@ const PullToRefresh = () => {
// Reload function that is called when reload threshold has been hit
// Add loading class to determine when to add spin animation
const forceReload = () => {
setPullStartPoint(0);
refreshDiv.current?.classList.add('loading');
setTimeout(() => {
router.reload();
@@ -32,8 +32,6 @@ const PullToRefresh = () => {
const pullStart = (e: TouchEvent) => {
setPullStartPoint(e.targetTouches[0].screenY);
const html = document.querySelector('html');
if (window.scrollY === 0 && window.scrollX === 0) {
refreshDiv.current?.classList.add('block');
refreshDiv.current?.classList.remove('hidden');
@@ -43,7 +41,6 @@ const PullToRefresh = () => {
html.style.overscrollBehaviorY = 'none';
}
} else {
setPullStartPoint(0);
refreshDiv.current?.classList.remove('block');
refreshDiv.current?.classList.add('hidden');
}
@@ -52,6 +49,7 @@ const PullToRefresh = () => {
// Tracks how far we have pulled down the refresh icon
const pullDown = async (e: TouchEvent) => {
const screenY = e.targetTouches[0].screenY;
const pullLength =
pullStartPoint < screenY ? Math.abs(screenY - pullStartPoint) : 0;
@@ -61,11 +59,12 @@ const PullToRefresh = () => {
// Will reload the page if we are past the threshold
// Otherwise, we reset the pull
const pullFinish = () => {
if (pullDownReloadThreshold && pullStartPoint !== 0) {
setPullStartPoint(0);
if (pullDownReloadThreshold) {
forceReload();
} else {
setPullChange(0);
setTimeout(() => setPullStartPoint(0), 200);
}
document.body.style.touchAction = 'auto';
@@ -84,21 +83,7 @@ const PullToRefresh = () => {
window.removeEventListener('touchmove', pullDown);
window.removeEventListener('touchend', pullFinish);
};
}, [
pullDownInitThreshold,
pullDownReloadThreshold,
pullStartPoint,
refreshDiv,
router,
setPullStartPoint,
]);
if (
pullStartPoint === 0 &&
!refreshDiv.current?.classList.contains('loading')
) {
return null;
}
}, [pullDownInitThreshold, pullDownReloadThreshold, pullStartPoint, router]);
return (
<div
@@ -117,7 +102,7 @@ const PullToRefresh = () => {
<div
className={`${
refreshDiv.current?.classList.contains('loading') && 'animate-spin'
} relative -top-28 h-9 w-9 rounded-full border-4 border-gray-800 bg-gray-800 shadow-md shadow-black ring-1 ring-gray-700`}
} relative -top-24 h-9 w-9 rounded-full border-4 border-gray-800 bg-gray-800 shadow-md shadow-black ring-1 ring-gray-700`}
style={{ animationDirection: 'reverse' }}
>
<ArrowPathIcon

View File

@@ -500,7 +500,7 @@ const MovieDetails = ({ movie }: MovieDetailsProps) => {
}}
/>
</div>
<div className="relative z-10 flex h-full items-center justify-between p-4 text-gray-200 transition duration-300 group-hover:text-white">
<div className="relative z-10 flex h-14 items-center justify-between p-4 text-gray-200 transition duration-300 group-hover:text-white">
<div>{data.collection.name}</div>
<Button buttonSize="sm">
{intl.formatMessage(globalMessages.view)}

View File

@@ -204,30 +204,24 @@ const RequestBlock = ({ request, onUpdate }: RequestBlockProps) => {
{intl.formatMessage(globalMessages.failed)}
</Badge>
)}
{request.status === MediaRequestStatus.COMPLETED && (
<Badge badgeType="success">
{intl.formatMessage(globalMessages.completed)}
</Badge>
)}
</div>
</div>
<div className="mt-2 flex items-center text-sm leading-5 sm:mt-0">
<Tooltip content={intl.formatMessage(messages.requestdate)}>
<CalendarIcon className="mr-1.5 h-5 w-5 flex-shrink-0" />
</Tooltip>
<Tooltip
content={intl.formatDate(request.createdAt, {
<span>
{intl.formatDate(request.createdAt, {
year: 'numeric',
month: 'long',
day: 'numeric',
hour: 'numeric',
minute: 'numeric',
second: 'numeric',
})}
>
<span>
{intl.formatDate(request.createdAt, {
year: 'numeric',
month: 'long',
day: 'numeric',
})}
</span>
</Tooltip>
</span>
</div>
</div>
{(request.seasons ?? []).length > 0 && (
@@ -243,11 +237,7 @@ const RequestBlock = ({ request, onUpdate }: RequestBlockProps) => {
key={`season-${season.id}`}
className="mb-1 mr-2 inline-block"
>
<Badge>
{season.seasonNumber === 0
? intl.formatMessage(globalMessages.specials)
: season.seasonNumber}
</Badge>
<Badge>{season.seasonNumber}</Badge>
</span>
))}
</div>

View File

@@ -264,7 +264,9 @@ const RequestButton = ({
// Standard request button
if (
(!media || media.status === MediaStatus.UNKNOWN) &&
(!media ||
media.status === MediaStatus.UNKNOWN ||
(media.status === MediaStatus.DELETED && !activeRequest)) &&
hasPermission(
[
Permission.REQUEST,
@@ -307,7 +309,9 @@ const RequestButton = ({
// 4K request button
if (
(!media || media.status4k === MediaStatus.UNKNOWN) &&
(!media ||
media.status4k === MediaStatus.UNKNOWN ||
(media.status4k === MediaStatus.DELETED && !active4kRequest)) &&
hasPermission(
[
Permission.REQUEST_4K,

View File

@@ -16,7 +16,7 @@ import {
TrashIcon,
XMarkIcon,
} from '@heroicons/react/24/solid';
import { MediaRequestStatus } from '@server/constants/media';
import { MediaRequestStatus, MediaStatus } from '@server/constants/media';
import type { MediaRequest } from '@server/entity/MediaRequest';
import type { MovieDetails } from '@server/models/Movie';
import type { TvDetails } from '@server/models/Tv';
@@ -381,7 +381,8 @@ const RequestCard = ({ request, onTitleData }: RequestCardProps) => {
<span className="mr-2 font-bold ">
{intl.formatMessage(messages.seasons, {
seasonCount:
title.seasons.length === request.seasons.length
title.seasons.filter((season) => season.seasonNumber !== 0)
.length === request.seasons.length
? 0
: request.seasons.length,
})}
@@ -389,11 +390,7 @@ const RequestCard = ({ request, onTitleData }: RequestCardProps) => {
<div className="hide-scrollbar overflow-x-scroll">
{request.seasons.map((season) => (
<span key={`season-${season.id}`} className="mr-2">
<Badge>
{season.seasonNumber === 0
? intl.formatMessage(globalMessages.specials)
: season.seasonNumber}
</Badge>
<Badge>{season.seasonNumber}</Badge>
</span>
))}
</div>
@@ -414,6 +411,15 @@ const RequestCard = ({ request, onTitleData }: RequestCardProps) => {
>
{intl.formatMessage(globalMessages.failed)}
</Badge>
) : requestData.status === MediaRequestStatus.PENDING &&
requestData.media[requestData.is4k ? 'status4k' : 'status'] ===
MediaStatus.DELETED ? (
<Badge
badgeType="warning"
href={`/${requestData.type}/${requestData.media.tmdbId}?manage=1`}
>
{intl.formatMessage(globalMessages.pending)}
</Badge>
) : (
<StatusBadge
status={

View File

@@ -15,7 +15,7 @@ import {
TrashIcon,
XMarkIcon,
} from '@heroicons/react/24/solid';
import { MediaRequestStatus } from '@server/constants/media';
import { MediaRequestStatus, MediaStatus } from '@server/constants/media';
import type { MediaRequest } from '@server/entity/MediaRequest';
import type { MovieDetails } from '@server/models/Movie';
import type { TvDetails } from '@server/models/Tv';
@@ -440,7 +440,9 @@ const RequestItem = ({ request, revalidateList }: RequestItemProps) => {
<span className="card-field-name">
{intl.formatMessage(messages.seasons, {
seasonCount:
title.seasons.length === request.seasons.length
title.seasons.filter(
(season) => season.seasonNumber !== 0
).length === request.seasons.length
? 0
: request.seasons.length,
})}
@@ -448,11 +450,7 @@ const RequestItem = ({ request, revalidateList }: RequestItemProps) => {
<div className="hide-scrollbar flex flex-nowrap overflow-x-scroll">
{request.seasons.map((season) => (
<span key={`season-${season.id}`} className="mr-2">
<Badge>
{season.seasonNumber === 0
? intl.formatMessage(globalMessages.specials)
: season.seasonNumber}
</Badge>
<Badge>{season.seasonNumber}</Badge>
</span>
))}
</div>
@@ -476,6 +474,15 @@ const RequestItem = ({ request, revalidateList }: RequestItemProps) => {
>
{intl.formatMessage(globalMessages.failed)}
</Badge>
) : requestData.status === MediaRequestStatus.PENDING &&
requestData.media[requestData.is4k ? 'status4k' : 'status'] ===
MediaStatus.DELETED ? (
<Badge
badgeType="warning"
href={`/${requestData.type}/${requestData.media.tmdbId}?manage=1`}
>
{intl.formatMessage(globalMessages.pending)}
</Badge>
) : (
<StatusBadge
status={

View File

@@ -34,6 +34,7 @@ enum Filter {
AVAILABLE = 'available',
UNAVAILABLE = 'unavailable',
FAILED = 'failed',
DELETED = 'deleted',
}
type Sort = 'added' | 'modified';
@@ -177,6 +178,9 @@ const RequestList = () => {
<option value="unavailable">
{intl.formatMessage(globalMessages.unavailable)}
</option>
<option value="deleted">
{intl.formatMessage(globalMessages.deleted)}
</option>
</select>
</div>
<div className="mb-2 flex flex-grow sm:mb-0 lg:flex-grow-0">

View File

@@ -78,7 +78,8 @@ const CollectionRequestModal = ({
.filter(
(request) =>
request.is4k === is4k &&
request.status !== MediaRequestStatus.DECLINED
request.status !== MediaRequestStatus.DECLINED &&
request.status !== MediaRequestStatus.COMPLETED
)
.map((part) => part.id),
];
@@ -167,7 +168,9 @@ const CollectionRequestModal = ({
return (part?.mediaInfo?.requests ?? []).find(
(request) =>
request.is4k === is4k && request.status !== MediaRequestStatus.DECLINED
request.is4k === is4k &&
request.status !== MediaRequestStatus.DECLINED &&
request.status !== MediaRequestStatus.COMPLETED
);
};
@@ -342,7 +345,9 @@ const CollectionRequestModal = ({
const partMedia =
part.mediaInfo &&
part.mediaInfo[is4k ? 'status4k' : 'status'] !==
MediaStatus.UNKNOWN
MediaStatus.UNKNOWN &&
part.mediaInfo[is4k ? 'status4k' : 'status'] !==
MediaStatus.DELETED
? part.mediaInfo
: undefined;

View File

@@ -41,6 +41,7 @@ const messages = defineMessages({
season: 'Season',
numberofepisodes: '# of Episodes',
seasonnumber: 'Season {number}',
extras: 'Extras',
errorediting: 'Something went wrong while editing the request.',
requestedited: 'Request for <strong>{title}</strong> edited successfully!',
requestApproved: 'Request for <strong>{title}</strong> approved!',
@@ -231,7 +232,9 @@ const TvRequestModal = ({
const getAllSeasons = (): number[] => {
return (data?.seasons ?? [])
.filter((season) => season.episodeCount !== 0)
.filter(
(season) => season.seasonNumber !== 0 && season.episodeCount !== 0
)
.map((season) => season.seasonNumber);
};
@@ -240,7 +243,8 @@ const TvRequestModal = ({
.filter(
(request) =>
request.is4k === is4k &&
request.status !== MediaRequestStatus.DECLINED
request.status !== MediaRequestStatus.DECLINED &&
request.status !== MediaRequestStatus.COMPLETED
)
.reduce((requestedSeasons, request) => {
return [
@@ -338,7 +342,8 @@ const TvRequestModal = ({
(data.mediaInfo.requests || []).filter(
(request) =>
request.is4k === is4k &&
request.status !== MediaRequestStatus.DECLINED
request.status !== MediaRequestStatus.DECLINED &&
request.status !== MediaRequestStatus.COMPLETED
).length > 0
) {
data.mediaInfo.requests
@@ -346,7 +351,9 @@ const TvRequestModal = ({
.forEach((request) => {
if (!seasonRequest) {
seasonRequest = request.seasons.find(
(season) => season.seasonNumber === seasonNumber
(season) =>
season.seasonNumber === seasonNumber &&
season.status !== MediaRequestStatus.COMPLETED
);
}
});
@@ -554,7 +561,10 @@ const TvRequestModal = ({
</thead>
<tbody className="divide-y divide-gray-700">
{data?.seasons
.filter((season) => season.episodeCount !== 0)
.filter(
(season) =>
season.seasonNumber !== 0 && season.episodeCount !== 0
)
.map((season) => {
const seasonRequest = getSeasonRequest(
season.seasonNumber
@@ -563,7 +573,9 @@ const TvRequestModal = ({
(sn) =>
sn.seasonNumber === season.seasonNumber &&
sn[is4k ? 'status4k' : 'status'] !==
MediaStatus.UNKNOWN
MediaStatus.UNKNOWN &&
sn[is4k ? 'status4k' : 'status'] !==
MediaStatus.DELETED
);
return (
<tr key={`season-${season.id}`}>
@@ -631,7 +643,7 @@ const TvRequestModal = ({
</td>
<td className="whitespace-nowrap px-1 py-4 text-sm font-medium leading-5 text-gray-100 md:px-6">
{season.seasonNumber === 0
? intl.formatMessage(globalMessages.specials)
? intl.formatMessage(messages.extras)
: intl.formatMessage(messages.seasonnumber, {
number: season.seasonNumber,
})}

View File

@@ -53,7 +53,6 @@ const messages: { [messageName: string]: MessageDescriptor } = defineMessages({
'plex-recently-added-scan': 'Plex Recently Added Scan',
'plex-full-scan': 'Plex Full Library Scan',
'plex-watchlist-sync': 'Plex Watchlist Sync',
'plex-refresh-token': 'Plex Refresh Token',
'availability-sync': 'Media Availability Sync',
'radarr-scan': 'Radarr Scan',
'sonarr-scan': 'Sonarr Scan',

View File

@@ -299,6 +299,17 @@ const StatusBadge = ({
</Tooltip>
);
case MediaStatus.DELETED:
return (
<Tooltip content={mediaLinkDescription}>
<Badge badgeType="danger">
{intl.formatMessage(is4k ? messages.status4k : messages.status, {
status: intl.formatMessage(globalMessages.deleted),
})}
</Badge>
</Tooltip>
);
default:
return null;
}

View File

@@ -207,7 +207,9 @@ const TitleCard = ({
<div
className={`px-2 text-white ${
!showRequestButton ||
(currentStatus && currentStatus !== MediaStatus.UNKNOWN)
(currentStatus &&
currentStatus !== MediaStatus.UNKNOWN &&
currentStatus !== MediaStatus.DELETED)
? 'pb-2'
: 'pb-11'
}`}
@@ -235,7 +237,8 @@ const TitleCard = ({
WebkitLineClamp:
!showRequestButton ||
(currentStatus &&
currentStatus !== MediaStatus.UNKNOWN)
currentStatus !== MediaStatus.UNKNOWN &&
currentStatus !== MediaStatus.DELETED)
? 5
: 3,
display: '-webkit-box',
@@ -253,7 +256,9 @@ const TitleCard = ({
<div className="absolute bottom-0 left-0 right-0 flex justify-between px-2 py-2">
{showRequestButton &&
(!currentStatus || currentStatus === MediaStatus.UNKNOWN) && (
(!currentStatus ||
currentStatus === MediaStatus.UNKNOWN ||
currentStatus === MediaStatus.DELETED) && (
<Button
buttonType="primary"
buttonSize="sm"

View File

@@ -200,7 +200,6 @@ const TvDetails = ({ tv }: TvDetailsProps) => {
);
}
// Does NOT include "Specials"
const seasonCount = data.seasons.filter(
(season) => season.seasonNumber !== 0 && season.episodeCount !== 0
).length;
@@ -235,7 +234,8 @@ const TvDetails = ({ tv }: TvDetailsProps) => {
.filter(
(request) =>
request.is4k === is4k &&
request.status !== MediaRequestStatus.DECLINED
request.status !== MediaRequestStatus.DECLINED &&
request.status !== MediaRequestStatus.COMPLETED
)
.reduce((requestedSeasons, request) => {
return [
@@ -258,17 +258,9 @@ const TvDetails = ({ tv }: TvDetailsProps) => {
return [...requestedSeasons, ...availableSeasons];
};
const showHasSpecials = data.seasons.some(
(season) => season.seasonNumber === 0
);
const isComplete = seasonCount <= getAllRequestedSeasons(false).length;
const isComplete =
(showHasSpecials ? seasonCount + 1 : seasonCount) <=
getAllRequestedSeasons(false).length;
const is4kComplete =
(showHasSpecials ? seasonCount + 1 : seasonCount) <=
getAllRequestedSeasons(true).length;
const is4kComplete = seasonCount <= getAllRequestedSeasons(true).length;
const streamingProviders =
data?.watchProviders?.find((provider) => provider.iso_3166_1 === region)
@@ -531,6 +523,7 @@ const TvDetails = ({ tv }: TvDetailsProps) => {
{data.seasons
.slice()
.reverse()
.filter((season) => season.seasonNumber !== 0)
.map((season) => {
const show4k =
settings.currentSettings.series4kEnabled &&
@@ -584,11 +577,9 @@ const TvDetails = ({ tv }: TvDetailsProps) => {
>
<div className="flex flex-1 items-center space-x-2 text-lg">
<span>
{season.seasonNumber === 0
? intl.formatMessage(globalMessages.specials)
: intl.formatMessage(messages.seasonnumber, {
seasonNumber: season.seasonNumber,
})}
{intl.formatMessage(messages.seasonnumber, {
seasonNumber: season.seasonNumber,
})}
</span>
<Badge badgeType="dark">
{intl.formatMessage(messages.episodeCount, {
@@ -657,6 +648,18 @@ const TvDetails = ({ tv }: TvDetailsProps) => {
</div>
</>
)}
{mSeason?.status === MediaStatus.DELETED && (
<>
<div className="hidden md:flex">
<Badge badgeType="danger">
{intl.formatMessage(globalMessages.deleted)}
</Badge>
</div>
<div className="flex md:hidden">
<StatusBadgeMini status={MediaStatus.DELETED} />
</div>
</>
)}
{((!mSeason4k &&
request4k?.status ===
MediaRequestStatus.APPROVED) ||
@@ -743,6 +746,26 @@ const TvDetails = ({ tv }: TvDetailsProps) => {
</div>
</>
)}
{mSeason4k?.status4k === MediaStatus.DELETED &&
show4k && (
<>
<div className="hidden md:flex">
<Badge badgeType="danger">
{intl.formatMessage(messages.status4k, {
status: intl.formatMessage(
globalMessages.deleted
),
})}
</Badge>
</div>
<div className="flex md:hidden">
<StatusBadgeMini
status={MediaStatus.DELETED}
is4k={true}
/>
</div>
</>
)}
<ChevronDownIcon
className={`${
open ? 'rotate-180' : ''

View File

@@ -2,7 +2,6 @@ import React from 'react';
export type AvailableLocale =
| 'ar'
| 'bg'
| 'ca'
| 'cs'
| 'da'
@@ -10,11 +9,8 @@ export type AvailableLocale =
| 'en'
| 'el'
| 'es'
| 'fi'
| 'fr'
| 'hr'
| 'he'
| 'hi'
| 'hu'
| 'it'
| 'ja'
@@ -25,12 +21,10 @@ export type AvailableLocale =
| 'pl'
| 'pt-BR'
| 'pt-PT'
| 'ro'
| 'ru'
| 'sq'
| 'sr'
| 'sv'
| 'uk'
| 'zh-CN'
| 'zh-TW';
@@ -40,10 +34,6 @@ type AvailableLanguageObject = Record<
>;
export const availableLanguages: AvailableLanguageObject = {
bg: {
code: 'bg',
display: 'Bulgarian',
},
ca: {
code: 'ca',
display: 'Català',
@@ -68,22 +58,10 @@ export const availableLanguages: AvailableLanguageObject = {
code: 'es',
display: 'Español',
},
fi: {
code: 'fi',
display: 'Finnish',
},
fr: {
code: 'fr',
display: 'Français',
},
he: {
code: 'he',
display: 'Hebrew',
},
hi: {
code: 'hi',
display: 'Hindi',
},
hr: {
code: 'hr',
display: 'Hrvatski',
@@ -132,10 +110,6 @@ export const availableLanguages: AvailableLanguageObject = {
code: 'el',
display: 'Ελληνικά',
},
ro: {
code: 'ro',
display: 'Romanian',
},
ru: {
code: 'ru',
display: 'pусский',
@@ -156,10 +130,6 @@ export const availableLanguages: AvailableLanguageObject = {
code: 'ko',
display: '한국어',
},
uk: {
code: 'uk',
display: 'українська мова',
},
'zh-TW': {
code: 'zh-TW',
display: '繁體中文',

View File

@@ -19,7 +19,7 @@ const useDeepLinks = ({
useEffect(() => {
if (
/iPad|iPhone|iPod/.test(navigator.userAgent) ||
(navigator.userAgent.includes('Mac') && navigator.maxTouchPoints > 1)
(navigator.userAgent === 'MacIntel' && navigator.maxTouchPoints > 1)
) {
setReturnedPlexUrl(iOSPlexUrl);
setReturnedPlexUrl4k(iOSPlexUrl4k);

View File

@@ -3,6 +3,7 @@ import { defineMessages } from 'react-intl';
const globalMessages = defineMessages({
available: 'Available',
partiallyavailable: 'Partially Available',
deleted: 'Deleted',
processing: 'Processing',
unavailable: 'Unavailable',
notrequested: 'Not Requested',
@@ -14,6 +15,7 @@ const globalMessages = defineMessages({
pending: 'Pending',
declined: 'Declined',
approved: 'Approved',
completed: 'Completed',
movie: 'Movie',
movies: 'Movies',
collection: 'Collection',
@@ -55,7 +57,6 @@ const globalMessages = defineMessages({
noresults: 'No results.',
open: 'Open',
resolved: 'Resolved',
specials: 'Specials',
});
export default globalMessages;

View File

@@ -253,7 +253,7 @@
"components.PermissionEdit.viewrequests": "الإطلاع على الطلبات",
"components.PermissionEdit.viewrequestsDescription": "إعطاء صلاحية بالإطلاع على جميع الطلبات المقدمة من قبل المستخدمين.",
"components.PersonDetails.alsoknownas": "أيضا يُعرف بإسم: {names}",
"components.PersonDetails.appearsin": "الظهور",
"components.PersonDetails.appearsin": "المظهر",
"components.PersonDetails.ascharacter": "{character} شارك بِدوْر",
"components.PersonDetails.birthdate": "ولد في {birthdate}",
"components.PersonDetails.crewmember": "عضو",
@@ -673,7 +673,7 @@
"components.Settings.plexlibraries": "مكتبات بليكس",
"components.Settings.noDefaultServer": "على الأقل {serverType} سيرفر واحد يجب أن يكون معدا كإفتراضي لإتاحة تنفيذ طلبات الـ {mediaType}.",
"components.Settings.plexsettings": "إعدادات بليكس",
"components.Settings.serviceSettingsDescription": "قم بإعداد {serverType} بالإسفل.تستطيع الاتصال بأكثر من سيرفر {serverType} ,ولكن إثنان فقط يمكن إعدادهما كإفتراضيين (واحد لجودة الفور كي والأخر لغير جودة الفور كي). أصحاب الصلاحيات الإدارية بإمكانهم تجاوز السيرفر المستخدم قبل تأكيدهم لأي طلب محتوى جديد.",
"components.Settings.serviceSettingsDescription": "قم بإعداد {serverType} سيرفراتك بالإسفل. تستطيع الاتصال بأكثر من سيرفر {serverType} ولكن إثنان فقط يمكن إعدادهما كإفتراضيين واحد لمحتوى الفور كي والاخر لغير الفور كي. الذين يملكون إذونات مسؤول بإمكانهم تجاوز السيرفر المخصص لتنفيذ الطلبات الجديدة قبل الموافقة.",
"components.Settings.serverpresetManualMessage": "ضبط يدوي",
"components.Settings.sonarrsettings": "إعدادات سونار",
"components.Settings.tautulliSettingsDescription": "بشكل إختياري أضبط إعدادات سيرفرك الخاص بـ Tautulli.أوفرسيرر سيقوم بجلب بيانات سجل المشاهدة لمحتوى بليكس من Tautulli.",
@@ -1232,11 +1232,5 @@
"components.Discover.CreateSlider.validationTitlerequired": "يجب تقديم عنوان.",
"components.Discover.DiscoverMovieKeyword.keywordMovies": "{keywordTitle} الأفلام",
"components.Discover.CreateSlider.providetmdbkeywordid": "قم بتوفير معرّف كلمات بحث رئيسية من (TMDB)",
"components.MovieDetails.imdbuserscore": "تقييم مستخدمين IMDB",
"components.Settings.Notifications.NotificationsPushover.sound": "صوت التنبيه",
"components.UserProfile.UserSettings.UserNotificationSettings.deviceDefault": "الجهاز الإفتراضي",
"components.Settings.SonarrModal.animeSeriesType": "نوع مسلسل الإنمي",
"components.Settings.SonarrModal.seriesType": "نوع المسلسل",
"components.UserProfile.UserSettings.UserNotificationSettings.sound": "صوت التنبيه",
"components.Settings.Notifications.NotificationsPushover.deviceDefault": "الجهاز الإفتراضي"
"components.MovieDetails.imdbuserscore": "تقييم مستخدمين IMDB"
}

File diff suppressed because it is too large Load Diff

View File

@@ -191,7 +191,7 @@
"components.Discover.TvGenreSlider.tvgenres": "Gèneres de Sèries",
"components.Discover.TvGenreList.seriesgenres": "Gèneres de Sèries",
"components.Discover.StudioSlider.studios": "Estudis",
"components.Discover.NetworkSlider.networks": "Plataformes",
"components.Discover.NetworkSlider.networks": "Emissors",
"components.Discover.MovieGenreSlider.moviegenres": "Gèneres de Pel·lícules",
"components.Discover.MovieGenreList.moviegenres": "Gèneres de Pel·lícules",
"components.Discover.DiscoverTvLanguage.languageSeries": "Sèries en {language}",
@@ -397,7 +397,7 @@
"components.TvDetails.originaltitle": "Títol original",
"components.TvDetails.originallanguage": "Idioma original",
"components.TvDetails.nextAirDate": "Pròxima data d'emissió",
"components.TvDetails.network": "{networkCount, plural, one {Plataforma} other {Plataformes}}",
"components.TvDetails.network": "{networkCount, plural, one {Emissor} other {Emissors}}",
"components.TvDetails.firstAirDate": "Primera data d'emissió",
"components.TvDetails.episodeRuntimeMinutes": "{runtime} minuts",
"components.TvDetails.episodeRuntime": "Duració de l'episodi",
@@ -481,7 +481,7 @@
"components.Settings.SonarrModal.validationNameRequired": "Heu de proporcionar un nom de servidor",
"components.Settings.SonarrModal.validationLanguageProfileRequired": "Heu de seleccionar un perfil d'idioma",
"components.Settings.SonarrModal.validationHostnameRequired": "Heu de proporcionar un nom damfitrió o una adreça IP vàlides",
"components.Settings.SonarrModal.validationBaseUrlTrailingSlash": "L'URL base no ha d'acabar amb una barra inclinada final",
"components.Settings.SonarrModal.validationBaseUrlTrailingSlash": "L'URL base no pot acabar amb una barra inclinada final",
"components.Settings.SonarrModal.validationBaseUrlLeadingSlash": "L'URL base ha de tenir una barra inclinada",
"components.Settings.SonarrModal.validationApplicationUrlTrailingSlash": "L'URL no pot acabar amb una barra inclinada final",
"components.Settings.SonarrModal.validationApplicationUrl": "Heu de proporcionar un URL vàlid",
@@ -1119,7 +1119,7 @@
"components.Discover.CreateSlider.needresults": "Cal tenir almenys 1 resultat.",
"components.Discover.CreateSlider.nooptions": "Sense resultats.",
"components.Discover.CreateSlider.providetmdbgenreid": "Proporciona un ID de categoria TMDB",
"components.Discover.CreateSlider.providetmdbnetwork": "Proporciona l'ID de la plataforma TMDB",
"components.Discover.CreateSlider.providetmdbnetwork": "Proporciona l'ID d'emissor TMDB",
"components.Discover.CreateSlider.providetmdbstudio": "Proporciona l'ID d'estudi TMDB",
"components.Discover.CreateSlider.searchGenres": "Cercar per gènere…",
"components.Discover.CreateSlider.searchKeywords": "Cercar per paraules clau…",
@@ -1151,7 +1151,7 @@
"components.Discover.networks": "Emissors",
"components.Discover.resetwarning": "Restablir tots els controls lliscants al valor predeterminat. Això també suprimirà els controls lliscants personalitzats!",
"components.Discover.tmdbmoviekeyword": "Paraula clau de pel·lícula TMDB",
"components.Discover.tmdbnetwork": "Plataformes TMDB",
"components.Discover.tmdbnetwork": "Emissors TMDB",
"components.Discover.FilterSlideover.tmdbuserscore": "Puntuació d'usuaris TMDB",
"components.Discover.tvgenres": "Gèneres de sèries",
"components.Discover.DiscoverTvKeyword.keywordSeries": "Sèries {keywordTitle}",
@@ -1224,19 +1224,5 @@
"components.StatusBadge.seasonepisodenumber": "S{seasonNumber}E{episodeNumber}",
"components.Settings.SettingsJobsCache.availability-sync": "Sincronització de disponibilitat de contingut",
"components.Discover.tmdbmoviestreamingservices": "Serveis de transmissió de pel·lícules TMDB",
"components.Discover.tmdbtvstreamingservices": "Serveis de transmissió de TV TMDB",
"components.Discover.FilterSlideover.tmdbuservotecount": "Recompte de vots dels usuaris de TMDB",
"components.Discover.FilterSlideover.voteCount": "Número de vots entre {minValue} i {maxValue}",
"components.UserProfile.UserSettings.UserNotificationSettings.sound": "So per a les notificacions",
"components.Settings.Notifications.NotificationsPushover.deviceDefault": "Dispositiu per defecte",
"components.Settings.Notifications.NotificationsPushover.sound": "So per a les notificacions",
"components.Settings.SonarrModal.animeSeriesType": "Tipus d'Anime",
"components.Settings.SonarrModal.seriesType": "Tipus de sèrie",
"components.Settings.SonarrModal.tagRequests": "Sol·licituds d'etiquetes",
"components.UserProfile.UserSettings.UserNotificationSettings.deviceDefault": "Dispositiu per defecte",
"i18n.collection": "Col·lecció",
"components.MovieDetails.imdbuserscore": "Puntuació dels usuaris de IMDB",
"components.Settings.RadarrModal.tagRequests": "Sol·licituds d'etiqueta",
"components.Settings.RadarrModal.tagRequestsInfo": "Automàticament afegeix una etiqueta addicional amb el nom d'usuari i nom complet del sol·licitant",
"components.Settings.SonarrModal.tagRequestsInfo": "Automàticament afegeix una etiqueta addicional amb el nom d'usuari i nom complet del sol·licitant"
"components.Discover.tmdbtvstreamingservices": "Serveis de transmissió de TV TMDB"
}

View File

@@ -784,7 +784,7 @@
"components.Settings.externalUrl": "Externí adresa URL",
"components.Settings.hostname": "Název hostitele nebo IP adresa",
"components.Settings.manualscan": "Manuální skenování knihovny",
"components.Settings.plexlibrariesDescription": "Knihovny, ve kterých Overseerr vyhledává tituly. Nastavte a uložte nastavení připojení k systému Plex a poté klikněte na tlačítko níže, pokud nejsou v seznamu uvedeny žádné knihovny.",
"components.Settings.plexlibrariesDescription": "Knihovny Overseerr vyhledává tituly. Nastavte a uložte nastavení připojení k systému Plex a poté klikněte na tlačítko níže, pokud nejsou v seznamu uvedeny žádné knihovny.",
"components.Settings.serverpresetLoad": "Stisknutím tlačítka načtete dostupné servery",
"components.Settings.toastTautulliSettingsFailure": "Při ukládání nastavení Tautulli se něco pokazilo.",
"components.Settings.webAppUrl": "<WebAppLink>Webová aplikace</WebAppLink> Adresa URL",
@@ -836,7 +836,7 @@
"components.Settings.noDefault4kServer": "Server 4K {serverType} musí být označen jako výchozí, aby uživatelé mohli odesílat požadavky 4K {mediaType}.",
"components.Settings.noDefaultNon4kServer": "Pokud máte pouze jeden server {serverType} pro obsah jiný než 4K i 4K (nebo pokud stahujete pouze obsah 4K), váš server {serverType} by <strong>neměl</strong> být označen jako server 4K.",
"components.Settings.noDefaultServer": "Aby mohly být zpracovány požadavky typu {mediaType}, musí být alespoň jeden server typu {serverType} označen jako výchozí.",
"components.Settings.plexsettingsDescription": "Nakonfigurujte nastavení pro váš server Plex. Overseerr prohledá vaše knihovny Plex, aby zjistil dostupnost obsahu.",
"components.Settings.plexsettingsDescription": "Knihovny Overseerr vyhledává tituly. Nastavte a uložte nastavení připojení k systému Plex a poté klikněte na tlačítko níže, pokud nejsou v seznamu uvedeny žádné knihovny.",
"components.Settings.toastPlexRefresh": "Získání seznamu serverů z aplikace Plex…",
"components.Settings.toastPlexRefreshSuccess": "Seznam serverů Plex úspěšně načten!",
"components.UserList.passwordinfodescription": "Nakonfigurujte adresu URL aplikace a povolte e-mailová oznámení, která umožní automatické generování hesla.",
@@ -1194,7 +1194,7 @@
"components.Settings.SettingsMain.applicationurl": "Adresa URL aplikace",
"components.Settings.SettingsMain.cacheImages": "Povolení ukládání obrázků do mezipaměti",
"components.Settings.SettingsMain.cacheImagesTip": "Ukládání obrázků z externích zdrojů do mezipaměti (vyžaduje značné množství místa na disku)",
"components.Settings.SettingsMain.csrfProtection": "Povolit ochranu CSRF",
"components.Settings.SettingsMain.csrfProtection": "Povolení ochrany CSRF",
"components.Settings.SettingsMain.csrfProtectionHoverTip": "Toto nastavení NEPOVOLUJTE, pokud nerozumíte tomu, co děláte!",
"components.Settings.SettingsMain.csrfProtectionTip": "Nastavení externího přístupu k rozhraní API pouze pro čtení (vyžaduje protokol HTTPS)",
"components.Settings.SettingsMain.general": "Obecné",
@@ -1231,12 +1231,5 @@
"components.Discover.FilterSlideover.tmdbuservotecount": "Počet hlasů uživatelů TMDB",
"components.Discover.tmdbmoviestreamingservices": "Filmové streamovací služby TMDB",
"components.Settings.SonarrModal.tagRequestsInfo": "Automatické přidání dodatočné značky s ID uživatele a zobrazovaným jménem žadatele",
"components.Discover.tmdbtvstreamingservices": "Televizní streamovací služby TMDB",
"components.Settings.Notifications.NotificationsPushover.sound": "Zvuk upozornění",
"components.MovieDetails.imdbuserscore": "Uživatelské skóre IMDB",
"components.UserProfile.UserSettings.UserNotificationSettings.sound": "Zvuk upozornění",
"components.Settings.SonarrModal.animeSeriesType": "Typ anime série",
"components.Settings.Notifications.NotificationsPushover.deviceDefault": "Výchozí zařízení",
"components.UserProfile.UserSettings.UserNotificationSettings.deviceDefault": "Výchozí zařízení",
"components.Settings.SonarrModal.seriesType": "Typ série"
"components.Discover.tmdbtvstreamingservices": "Televizní streamovací služby TMDB"
}

View File

@@ -58,7 +58,7 @@
"components.MovieDetails.markavailable": "Markér som Tilgængelig",
"components.MovieDetails.originallanguage": "Originalsprog",
"components.Login.signinwithoverseerr": "Brug din {applicationTitle} konto",
"components.MovieDetails.MovieCast.fullcast": "Fuld rolleliste",
"components.MovieDetails.MovieCast.fullcast": "Medvirkende",
"components.MovieDetails.MovieCrew.fullcrew": "Filmstab",
"components.MovieDetails.budget": "Budget",
"components.MovieDetails.overview": "Overblik",
@@ -539,7 +539,7 @@
"components.Settings.Notifications.validationSmtpPortRequired": "Du skal angive et gyldigt port-nummer",
"components.Settings.Notifications.webhookUrl": "Webhook URL",
"components.Settings.Notifications.webhookUrlTip": "Opret en<DiscordWebhookLink>webhook integration</DiscordWebhookLink> til din server",
"components.Settings.RadarrModal.baseUrl": "URL Kilde",
"components.Settings.RadarrModal.baseUrl": "URL Base",
"components.Settings.RadarrModal.create4kradarr": "Tilføj Ny 4K Radarr Server",
"components.Settings.RadarrModal.createradarr": "Tilføj Ny Radarr Server",
"components.Settings.RadarrModal.default4kserver": "Standard 4K Server",
@@ -625,7 +625,7 @@
"components.Settings.SettingsLogs.label": "Label",
"components.Settings.SettingsLogs.level": "Alvorlighed",
"components.Settings.SettingsLogs.logDetails": "Loginformation",
"components.Settings.SonarrModal.baseUrl": "URL Kilde",
"components.Settings.SonarrModal.baseUrl": "URL Base",
"components.Settings.SonarrModal.create4ksonarr": "Tilføj Ny 4K Sonarr Server",
"components.Settings.SonarrModal.loadingprofiles": "Indlæser kvalitetsprofiler…",
"components.Settings.manualscan": "Manuel Biblioteksskanning",
@@ -794,7 +794,7 @@
"components.Setup.configureplex": "Konfigurér Plex",
"components.Settings.validationPortRequired": "Du skal angive et gyldigt port-nummer",
"components.Setup.configureservices": "Konfigurér Tjenester",
"components.Setup.continue": "Videre",
"components.Setup.continue": "Fortsæt",
"components.Setup.finish": "Fuldfør Opsætning",
"components.Setup.finishing": "Færdiggører…",
"components.Setup.loginwithplex": "Log in med Plex",
@@ -1231,12 +1231,5 @@
"components.Settings.SonarrModal.tagRequests": "Tag forespørgsler",
"components.Settings.SonarrModal.tagRequestsInfo": "Tilføj automatisk et ekstra tag med forespørgerens bruger ID og viste navn",
"i18n.collection": "Samling",
"components.Settings.RadarrModal.tagRequestsInfo": "Tilføj automatisk et ekstra tag med forespørgerens bruger id og viste navn",
"components.MovieDetails.imdbuserscore": "IMDB Bruger Score",
"components.Settings.SonarrModal.animeSeriesType": "Anime Serie Type",
"components.Settings.SonarrModal.seriesType": "Serie Type",
"components.Settings.Notifications.NotificationsPushover.deviceDefault": "Enhedsstandard",
"components.Settings.Notifications.NotificationsPushover.sound": "Notifikationslyd",
"components.UserProfile.UserSettings.UserNotificationSettings.deviceDefault": "Enhedsstandard",
"components.UserProfile.UserSettings.UserNotificationSettings.sound": "Notifikationslyd"
"components.Settings.RadarrModal.tagRequestsInfo": "Tilføj automatisk et ekstra tag med forespørgerens bruger id og viste navn"
}

View File

@@ -1231,12 +1231,5 @@
"components.MovieDetails.imdbuserscore": "IMDB Nutzer Bewertung",
"components.Settings.SonarrModal.tagRequests": "Tag Anforderungen",
"components.Discover.FilterSlideover.voteCount": "Anzahl Abstimmungen zwischen {minValue} und {maxValue}",
"components.Settings.SonarrModal.tagRequestsInfo": "Füge automatisch einen zusätzlichen Tag mit der ID & Namen des anfordernden Nutzers",
"components.Settings.RadarrModal.tagRequests": "Anfragen taggen",
"components.Settings.Notifications.NotificationsPushover.sound": "Benachrichtigungston",
"components.UserProfile.UserSettings.UserNotificationSettings.deviceDefault": "Gerätestandard",
"components.Settings.SonarrModal.animeSeriesType": "Art der Anime-Serie",
"components.Settings.SonarrModal.seriesType": "Serientyp",
"components.UserProfile.UserSettings.UserNotificationSettings.sound": "Benachrichtigungston",
"components.Settings.Notifications.NotificationsPushover.deviceDefault": "Gerätestandard"
"components.Settings.SonarrModal.tagRequestsInfo": "Füge automatisch einen zusätzlichen Tag mit der ID & Namen des anfordernden Nutzers"
}

View File

@@ -1232,11 +1232,5 @@
"components.UserProfile.UserSettings.UserGeneralSettings.discordId": "Αναγνωριστικό χρήστη Discord",
"components.UserProfile.UserSettings.UserGeneralSettings.plexwatchlistsyncseriestip": "Ζητήστε αυτόματα σειρές που βρίσκονται στη <PlexWatchlistSupportLink>λίστα παρακολούθησης του Plex</PlexWatchlistSupportLink>",
"components.UserProfile.plexwatchlist": "Λίστα παρακολούθησης Plex",
"components.MovieDetails.imdbuserscore": "Βαθμολογία χρηστών IMDB",
"components.Settings.SonarrModal.animeSeriesType": "Τύπος σειράς άνιμε",
"components.Settings.SonarrModal.seriesType": "Τύπος σειράς",
"components.Settings.Notifications.NotificationsPushover.sound": "Ήχος ειδοποίησης",
"components.UserProfile.UserSettings.UserNotificationSettings.deviceDefault": "Προεπιλογή συσκευής",
"components.UserProfile.UserSettings.UserNotificationSettings.sound": "Ήχος ειδοποίησης",
"components.Settings.Notifications.NotificationsPushover.deviceDefault": "Προεπιλογή συσκευής"
"components.MovieDetails.imdbuserscore": "Βαθμολογία χρηστών IMDB"
}

View File

@@ -471,6 +471,7 @@
"components.RequestModal.cancel": "Cancel Request",
"components.RequestModal.edit": "Edit Request",
"components.RequestModal.errorediting": "Something went wrong while editing the request.",
"components.RequestModal.extras": "Extras",
"components.RequestModal.numberofepisodes": "# of Episodes",
"components.RequestModal.pending4krequest": "Pending 4K Request",
"components.RequestModal.pendingapproval": "Your request is pending approval.",
@@ -767,7 +768,6 @@
"components.Settings.SettingsJobsCache.nextexecution": "Next Execution",
"components.Settings.SettingsJobsCache.plex-full-scan": "Plex Full Library Scan",
"components.Settings.SettingsJobsCache.plex-recently-added-scan": "Plex Recently Added Scan",
"components.Settings.SettingsJobsCache.plex-refresh-token": "Plex Refresh Token",
"components.Settings.SettingsJobsCache.plex-watchlist-sync": "Plex Watchlist Sync",
"components.Settings.SettingsJobsCache.process": "Process",
"components.Settings.SettingsJobsCache.radarr-scan": "Radarr Scan",
@@ -1190,9 +1190,11 @@
"i18n.canceling": "Canceling…",
"i18n.close": "Close",
"i18n.collection": "Collection",
"i18n.completed": "Completed",
"i18n.decline": "Decline",
"i18n.declined": "Declined",
"i18n.delete": "Delete",
"i18n.deleted": "Deleted",
"i18n.deleting": "Deleting…",
"i18n.delimitedlist": "{a}, {b}",
"i18n.edit": "Edit",

View File

@@ -1232,11 +1232,5 @@
"i18n.collection": "Colección",
"components.Settings.RadarrModal.tagRequestsInfo": "Añadir automáticamente una etiqueta adicional con el nombre de usuario y el nombre para mostrar del solicitante",
"components.Settings.SonarrModal.tagRequestsInfo": "Añadir automáticamente una etiqueta adicional con el nombre de usuario y el nombre para mostrar del solicitante",
"components.MovieDetails.imdbuserscore": "Puntuación de los usuarios de IMDB",
"components.Settings.SonarrModal.animeSeriesType": "Tipo de anime",
"components.Settings.SonarrModal.seriesType": "Tipo de series",
"components.Settings.Notifications.NotificationsPushover.sound": "Sonido para las notificaciones",
"components.UserProfile.UserSettings.UserNotificationSettings.deviceDefault": "Dispositivo predeterminado",
"components.UserProfile.UserSettings.UserNotificationSettings.sound": "Sonido para las notificaciones",
"components.Settings.Notifications.NotificationsPushover.deviceDefault": "Dispositivo predeterminado"
"components.MovieDetails.imdbuserscore": "Puntuación de los usuarios de IMDB"
}

View File

@@ -1,35 +0,0 @@
{
"components.Discover.CreateSlider.editsuccess": "Liukusäädintä muokattu ja löytömuokkausasetukset tallennettu.",
"components.CollectionDetails.numberofmovies": "{count} elokuvaa",
"components.Discover.CreateSlider.slidernameplaceholder": "Liukusäätimen nimi",
"components.AppDataWarning.dockerVolumeMissingDescription": "<code>{appDataPath}</code> -levyn liittäminen ei onnistunut oikein. Kaikki tiedot poistetaan, kun säilö pysäytetään tai käynnistetään uudelleen.",
"components.Discover.DiscoverMovies.sortPopularityDesc": "Suosio laskevassa järjestyksessä",
"components.AirDateBadge.airsrelative": "Esitys {relativeTime}",
"components.CollectionDetails.overview": "Yleiskatsaus",
"components.AirDateBadge.airedrelative": "Esitetty {relativeTime}",
"components.Discover.CreateSlider.searchStudios": "Etsi studioita…",
"components.Discover.CreateSlider.providetmdbnetwork": "Anna TMDB Network ID",
"components.Discover.CreateSlider.addfail": "Uuden liukusäätimen luominen epäonnistui.",
"components.CollectionDetails.requestcollection": "Pyydä kokoelma",
"components.Discover.DiscoverMovieGenre.genreMovies": "Lajityypin {genre} elokuvat",
"components.Discover.DiscoverMovieLanguage.languageMovies": "Elokuvat kielellä {language}",
"components.Discover.DiscoverMovies.sortPopularityAsc": "Suosio nousevassa järjestyksessä",
"components.Discover.CreateSlider.needresults": "Tarvitaan vähintään 1 tulos.",
"components.Discover.CreateSlider.addcustomslider": "Luo mukautettu liukusäädin",
"components.Discover.CreateSlider.editSlider": "Muokkaa liukusäädintä",
"components.Discover.CreateSlider.validationDatarequired": "Sinun täytyy antaa datan arvo.",
"components.Discover.CreateSlider.providetmdbstudio": "Anna TMDB Studio ID",
"components.Discover.CreateSlider.searchGenres": "Etsi tyylilajeja…",
"components.Discover.CreateSlider.editfail": "Liukusäätimen muokkaus epäonnistui.",
"components.Discover.CreateSlider.starttyping": "Aloita kirjoittaminen etsiäksesi.",
"components.Discover.CreateSlider.addSlider": "Lisää liukusäädin",
"components.CollectionDetails.requestcollection4k": "Pyydä kokoelma 4K-laadulla",
"components.Discover.CreateSlider.providetmdbsearch": "Anna hakukysely",
"components.Discover.CreateSlider.providetmdbkeywordid": "Anna TMDB Keyword ID",
"components.Discover.DiscoverMovieKeyword.keywordMovies": "{keywordTitle} elokuvat",
"components.Discover.CreateSlider.validationTitlerequired": "Sinun tulee antaa nimi.",
"components.Discover.CreateSlider.nooptions": "Ei tuloksia.",
"components.Discover.CreateSlider.searchKeywords": "Etsi avainsanoja…",
"components.Discover.DiscoverMovies.discovermovies": "Elokuvat",
"components.Discover.CreateSlider.providetmdbgenreid": "Anna TMDB Genre ID"
}

View File

@@ -61,7 +61,7 @@
"components.Settings.RadarrModal.port": "Port",
"components.Settings.RadarrModal.qualityprofile": "Profil de qualité",
"components.Settings.RadarrModal.rootfolder": "Dossier racine",
"components.Settings.RadarrModal.selectMinimumAvailability": "Sélectionner une disponibilité minimale",
"components.Settings.RadarrModal.selectMinimumAvailability": "Sélectionner une disponibilté minimale",
"components.Settings.RadarrModal.selectQualityProfile": "Sélectionner un profil qualité",
"components.Settings.RadarrModal.selectRootFolder": "Sélectionner un dossier racine",
"components.Settings.RadarrModal.server4k": "Serveur 4K",
@@ -162,7 +162,7 @@
"i18n.tvshows": "Séries",
"i18n.unavailable": "Indisponible",
"pages.oops": "Oups",
"pages.returnHome": "Retourner à l'accueil",
"pages.returnHome": "Retourner à l'acceuil",
"components.TvDetails.TvCast.fullseriescast": "Casting complet de la série",
"components.MovieDetails.MovieCast.fullcast": "Casting complet",
"components.Settings.Notifications.emailsettingssaved": "Paramètres de notification par e-mail enregistrés avec succès !",
@@ -208,7 +208,7 @@
"components.Settings.SettingsAbout.Releases.viewchangelog": "Voir le journal des modifications",
"components.Settings.SettingsAbout.Releases.versionChangelog": "Journal des modifications de la version {version}",
"components.Settings.SettingsAbout.Releases.releases": "Versions",
"components.Settings.SettingsAbout.Releases.releasedataMissing": "Les données de version sont actuellement indisponibles.",
"components.Settings.SettingsAbout.Releases.releasedataMissing": "Les données de version sont actuellement indisponible.",
"components.Settings.SettingsAbout.Releases.latestversion": "Dernière version",
"components.Settings.SettingsAbout.Releases.currentversion": "Actuelle",
"components.UserList.importfromplexerror": "Une erreur s'est produite durant l'importation des utilisateurs de Plex.",
@@ -976,7 +976,7 @@
"components.ManageSlideOver.manageModalMedia4k": "Média(s) 4K",
"components.ManageSlideOver.markallseasons4kavailable": "Marquer toutes les saisons comme disponibles en 4K",
"components.ManageSlideOver.playedby": "Joué par",
"components.Settings.validationUrlTrailingSlash": "L'URL ne doit pas se terminer par un slash",
"components.Settings.validationUrlTrailingSlash": "L'URL ne doit pas ce terminer par un slash",
"components.Settings.externalUrl": "URL externe",
"components.Settings.tautulliApiKey": "Clé API",
"components.Settings.tautulliSettings": "Paramètres Tautulli",
@@ -994,9 +994,9 @@
"components.ManageSlideOver.alltime": "Tout le temps",
"components.ManageSlideOver.manageModalMedia": "Média(s)",
"components.ManageSlideOver.markallseasonsavailable": "Marquer toutes les saisons comme disponibles",
"components.Settings.validationUrlBaseTrailingSlash": "L'URL de base ne doit pas se terminer par un slash",
"components.Settings.validationUrlBaseTrailingSlash": "L'URL de base ne doit pas ce terminer par un slash",
"components.Settings.tautulliSettingsDescription": "Configuration optionnelle pour votre serveur Tautulli. Overseerr va récupérer l'historique de visionnage de votre Plex depuis Tautulli.",
"components.UserList.newplexsigninenabled": "L'option <strong>Autoriser nouvelle connexion Plex</strong> est actuellement activée. Les utilisateurs Plex disposant d'un accès à la librairie n'ont pas besoin d'être importés pour pouvoir se connecter.",
"components.UserList.newplexsigninenabled": "L'option <strong>Autoriser nouvelle connexion Plex</strong> est actuellement activée. Les utilisateurs Plex disposant d'un accès à la librairie n'ont pas besoin d'être importés pour pouvoir ce connecter.",
"components.UserProfile.UserSettings.UserGeneralSettings.discordId": "ID utilisateur Discord",
"components.UserProfile.UserSettings.UserGeneralSettings.discordIdTip": "Le <FindDiscordIdLink>numéro d'identification à plusieurs chiffres</FindDiscordIdLink> est associé avec votre compte Discord",
"components.UserProfile.UserSettings.UserGeneralSettings.validationDiscordId": "Vous devez fournir un ID utilisateur Discord valide",
@@ -1191,7 +1191,7 @@
"components.Settings.SettingsMain.region": "Région à découvrir",
"components.Settings.SettingsMain.regionTip": "Filtrer le contenu par disponibilité régionale",
"components.Settings.SettingsMain.trustProxy": "Activer la prise en charge proxy",
"components.Settings.SettingsMain.trustProxyTip": "Permettre à Overseerr d'enregistrer correctement les adresses IP des clients derrière un proxy",
"components.Settings.SettingsMain.trustProxyTip": "Permettre Overseerr à enregistrer correctement les adresses IP des clients derrière un proxy",
"components.Settings.SettingsMain.applicationTitle": "Nom de l'application",
"components.Settings.SettingsMain.csrfProtectionTip": "Définir l'accès à l'API externe en lecture seule (nécessite HTTPS)",
"components.Settings.SettingsMain.cacheImagesTip": "Met en cache localement et utilise des images optimisées (nécessite une quantité considérable d'espace disque)",
@@ -1232,11 +1232,5 @@
"components.Settings.SonarrModal.tagRequestsInfo": "Ajouter automatiquement un tag supplémentaire avec l'ID utilisateur et le nom d'affichage du demandeur",
"i18n.collection": "Collection",
"components.Settings.RadarrModal.tagRequestsInfo": "Ajouter automatiquement un tag supplémentaire avec l'ID utilisateur et le nom d'affichage du demandeur",
"components.MovieDetails.imdbuserscore": "Note Utilisateurs",
"components.Settings.SonarrModal.seriesType": "Type de série",
"components.Settings.SonarrModal.animeSeriesType": "Types d'anime",
"components.Settings.Notifications.NotificationsPushover.deviceDefault": "Appareil par défaut",
"components.UserProfile.UserSettings.UserNotificationSettings.deviceDefault": "Appareil par défaut",
"components.UserProfile.UserSettings.UserNotificationSettings.sound": "Son de notification",
"components.Settings.Notifications.NotificationsPushover.sound": "Son de notification"
"components.MovieDetails.imdbuserscore": "Note Utilisateurs"
}

View File

@@ -2,7 +2,7 @@
"components.ManageSlideOver.alltime": "כל הזמנים",
"components.Login.validationemailrequired": "חובה לספק כתובת מייל חוקית",
"components.NotificationTypeSelector.userissuereopenedDescription": "קבל התראה כשבעיות שפתחת נפתחות מחדש.",
"components.AppDataWarning.dockerVolumeMissingDescription": "רכיב האחסון <code>{appDataPath}</code> לא הוגדר כראוי. כל המידע יוסר כאשר הקונטיינר יעצור או יותחל מחדש.",
"components.AppDataWarning.dockerVolumeMissingDescription": "ה <code>{appDataPath}</code> אחסון לא הוגדר כראוי. כל המידע יוסר כאשר הקונטיינר יעצור או יותחל מחדש.",
"components.CollectionDetails.overview": "תצוגה כללית",
"components.CollectionDetails.numberofmovies": "{כמות} סרטים",
"components.CollectionDetails.requestcollection": "אוסף בקשות",
@@ -135,41 +135,5 @@
"components.Login.loginerror": "משהו השתבש בזמן ההתחברות.",
"components.Login.password": "סיסמה",
"components.Login.signin": "התחברות",
"components.Login.validationpasswordrequired": "חובה לכתוב סיסמה",
"components.Discover.CreateSlider.editsuccess": "ערוך סליידרהגדרות התאמה אישית שמורות.",
"components.Discover.CreateSlider.slidernameplaceholder": "שם הסליידר",
"components.Discover.DiscoverMovies.sortPopularityDesc": "פופולאריות יורדץ",
"components.Discover.DiscoverMovies.sortTmdbRatingAsc": "דירוג TMDB עולה",
"components.Discover.CreateSlider.searchStudios": "חפש אולפנים…",
"components.Discover.DiscoverMovies.sortReleaseDateDesc": "תאריך שחרור יורד",
"components.Discover.CreateSlider.providetmdbnetwork": "הזמן מזהה רשת TMDB",
"components.Discover.CreateSlider.addfail": "שגיאה ביצירת סליידר.",
"components.Discover.DiscoverMovies.sortPopularityAsc": "פופולאריות עולה",
"components.Discover.CreateSlider.needresults": "חייב שתהיה לפחות תוצאה אחת.",
"components.Discover.CreateSlider.addcustomslider": "צור סליידר מותאם אישית",
"components.Discover.CreateSlider.editSlider": "ערוך סליידר",
"components.Discover.CreateSlider.validationDatarequired": "עליך לספק ערך.",
"components.Discover.DiscoverTv.discovertv": "סדרה",
"components.Discover.DiscoverSliderEdit.deletefail": "שגיאה במחיקת סליידר.",
"components.Discover.CreateSlider.providetmdbstudio": "הזן מזהה אולפן TMDB",
"components.Discover.DiscoverMovies.sortTitleDesc": "כותר (ת-א) יורד",
"components.Discover.CreateSlider.searchGenres": "חפש ז'אנרים…",
"components.Discover.CreateSlider.editfail": "שגיאה בעריכת סליידר.",
"components.Discover.CreateSlider.starttyping": "התחל לכתוב כדי לחפש.",
"components.Discover.DiscoverSliderEdit.enable": "שנה נראות",
"components.Discover.CreateSlider.addSlider": "הוסף סליידר",
"components.Discover.CreateSlider.providetmdbsearch": "הזן מילת חיפוש",
"components.Discover.CreateSlider.providetmdbkeywordid": "הזן מילת חיפוש TMDB",
"components.Discover.DiscoverMovieKeyword.keywordMovies": "סרטי {keywordTitle}",
"components.Discover.CreateSlider.validationTitlerequired": "עליך לספק כותרת.",
"components.Discover.DiscoverMovies.sortReleaseDateAsc": "תאריך שחרור עולה",
"components.Discover.CreateSlider.nooptions": "אין תוצאות.",
"components.Discover.DiscoverMovies.sortTmdbRatingDesc": "דירוג TMDB יורד",
"components.Discover.CreateSlider.searchKeywords": "חפש מילות מפתח…",
"components.Discover.CreateSlider.addsuccess": "צור סליידר חדש והגדרות התאמה אישית שמורות.",
"components.Discover.DiscoverSliderEdit.deletesuccess": "סליידר נמחק בהצלחה.",
"components.Discover.DiscoverMovies.discovermovies": "סרטים",
"components.Discover.DiscoverMovies.sortTitleAsc": "כותר (א-ת) עולה",
"components.Discover.CreateSlider.providetmdbgenreid": "הזן מזהה ז'אנר TMDB",
"components.Discover.DiscoverSliderEdit.remove": "הסר"
"components.Login.validationpasswordrequired": "חובה לכתוב סיסמה"
}

View File

@@ -11,10 +11,10 @@
"components.Discover.DiscoverTvGenre.genreSeries": "{genre} serije",
"components.Discover.DiscoverTvLanguage.languageSeries": "{language} serije",
"components.Discover.DiscoverWatchlist.discoverwatchlist": "Tvoj Plex popis gledanja",
"components.Discover.MovieGenreList.moviegenres": "Žanrovi filmova",
"components.Discover.StudioSlider.studios": "Studiji",
"components.Discover.TvGenreList.seriesgenres": "Žanrovi serija",
"components.Discover.TvGenreSlider.tvgenres": "Žanrovi serija",
"components.Discover.MovieGenreList.moviegenres": "Filmski žanrovi",
"components.Discover.StudioSlider.studios": "Studio",
"components.Discover.TvGenreList.seriesgenres": "Serijski žanrovi",
"components.Discover.TvGenreSlider.tvgenres": "Serijski žanrovi",
"components.Discover.plexwatchlist": "Tvoj Plex popis gledanja",
"components.Discover.popularmovies": "Popularni filmovi",
"components.Discover.recentlyAdded": "Nedavno dodani",
@@ -33,9 +33,9 @@
"components.AirDateBadge.airedrelative": "Emitirano {relativeTime}",
"components.AirDateBadge.airsrelative": "Emitiranje {relativeTime}",
"components.AppDataWarning.dockerVolumeMissingDescription": "Navedeni <code>{appDataPath}</code> mapirani volumen nije ispravno podešen. Svi podaci će biti izbrisani kada se spremnik (container) zaustavi ili ponovno pokrene.",
"components.Discover.DiscoverWatchlist.watchlist": "Plex popis gledanja",
"components.Discover.emptywatchlist": "Mediji koji su dodani u tvoj <PlexWatchlistSupportLink>Plex popis gledanja</PlexWatchlistSupportLink> pojavit će se ovdje.",
"components.Discover.MovieGenreSlider.moviegenres": "Žanrovi filmova",
"components.Discover.DiscoverWatchlist.watchlist": "Plex popis za gledanje",
"components.Discover.emptywatchlist": "Filmovi dodani na Vaš <PlexWatchlistSupportLink> popis za gledanje </PlexWatchlistSupportLink> pojaviti će se ovdje.",
"components.Discover.MovieGenreSlider.moviegenres": "Filmski žanrovi",
"components.Discover.NetworkSlider.networks": "Mreže",
"components.Discover.populartv": "Popularne serije",
"components.Discover.upcomingtv": "Nadolazeće serije",
@@ -67,7 +67,7 @@
"components.IssueModal.issueAudio": "Zvuk",
"components.IssueModal.issueSubtitles": "Podnaslov",
"components.IssueModal.issueVideo": "Video",
"components.IssueList.IssueItem.seasons": "{seasonCount, plural, one {sezona} few {sezone} other {sezone}}",
"components.IssueList.IssueItem.seasons": "{seasonCount, plural, one {sezona} few {sezone} other {sezona}}",
"components.Layout.UserDropdown.myprofile": "Profil",
"components.Layout.UserDropdown.requests": "Zahtjevi",
"components.Layout.VersionStatus.streamstable": "Overseerr stablina verzija",
@@ -78,7 +78,7 @@
"components.ManageSlideOver.movie": "film",
"components.Login.validationemailrequired": "Moraš zadati valjanju email adresu",
"components.ManageSlideOver.manageModalRequests": "Zahtjevi",
"components.ManageSlideOver.manageModalClearMediaWarning": "* Ovo će nepovratno ukloniti sve podatke za {mediaType}, uključujući sve zahtjeve. Ako ova stavka postoji u tvojoj Plex biblioteci, informacije o medijima će se ponovo stvoriti tijekom sljedećeg pretraživanja.",
"components.ManageSlideOver.manageModalClearMediaWarning": "* Ovo će nepovratno ukloniti sve podatke za ovaj {mediaType}, uključujući sve zahtjeve. Ako ova stavka postoji u vašoj Plex biblioteci, informacije o medijima ponovno će se stvoriti tijekom sljedećeg skeniranja.",
"components.ManageSlideOver.manageModalMedia4k": "4K mediji",
"components.ManageSlideOver.manageModalNoRequests": "Nema zahtjeva.",
"components.ManageSlideOver.manageModalMedia": "Mediji",
@@ -93,13 +93,13 @@
"components.MediaSlider.ShowMoreCard.seemore": "Vidi više",
"components.MovieDetails.markavailable": "Označi kao dostupno",
"components.ManageSlideOver.tvshow": "serije",
"components.MovieDetails.productioncountries": "{countryCount, plural, one {Zemlja produkcije} few {Zemlje produkcije} other {Zemlje produkcije}}",
"components.MovieDetails.productioncountries": "{countryCount, plural, one {zemlja produkcije} few {zemlje produkcije} other {zemalja produkcije}}",
"components.MovieDetails.managemovie": "Upravljaj filmom",
"components.MovieDetails.playonplex": "Reproduciraj na Plexu",
"components.MovieDetails.overviewunavailable": "Pregled nedostupan.",
"components.MovieDetails.reportissue": "Prijavi problem",
"components.MovieDetails.revenue": "Prihod",
"components.MovieDetails.rtaudiencescore": "Ocjena Rotten Tomatoes publike",
"components.MovieDetails.rtaudiencescore": "Ocjena publike na Rotten Tomatoes",
"components.MovieDetails.showless": "Prikaži manje",
"components.MovieDetails.showmore": "Prikaži više",
"components.MovieDetails.similar": "Slični naslovi",
@@ -128,7 +128,7 @@
"components.IssueList.IssueItem.opened": "Otvoren",
"components.IssueList.IssueItem.problemepisode": "Zahvaćena epizoda",
"components.IssueList.IssueItem.unknownissuetype": "Nepoznato",
"components.IssueList.IssueItem.episodes": "{episodeCount, plural, one {epizoda} few {epizode} other {epizode}}",
"components.IssueList.IssueItem.episodes": "{episodeCount, plural, one {Epizoda} other {Epizode}}",
"components.IssueList.IssueItem.viewissue": "Prikaz problema",
"components.IssueList.showallissues": "Prikaži sve probleme",
"components.IssueList.sortAdded": "Najnoviji",
@@ -142,7 +142,7 @@
"components.IssueModal.CreateIssueModal.season": "Sezona {seasonNumber}",
"components.IssueModal.CreateIssueModal.submitissue": "Pošalji problem",
"components.IssueModal.CreateIssueModal.toastFailedCreate": "Dogodila se greška prilikom slanja problema.",
"components.IssueModal.CreateIssueModal.toastSuccessCreate": "Izvještaj o problemu za <strong>{title}</strong> je uspješno poslan!",
"components.IssueModal.CreateIssueModal.toastSuccessCreate": "Problem prijavljen za <strong>{title}</strong> je uspješno predan!",
"components.IssueModal.CreateIssueModal.toastviewissue": "Prikaz problema",
"components.IssueModal.CreateIssueModal.validationMessageRequired": "Moraš navesti opis",
"components.IssueModal.CreateIssueModal.whatswrong": "Što nije u redu?",
@@ -166,7 +166,7 @@
"components.Login.loginerror": "Dogodila se greška prilikom pokušaja prijave.",
"components.Login.signin": "Prijavi se",
"components.Login.signingin": "Prijava…",
"components.Layout.VersionStatus.commitsbehind": "{commitsBehind} {commitsBehind, plural, one {commit} few {commita} other {commitova}} iza",
"components.Layout.VersionStatus.commitsbehind": "Verzija ne sadrži {commitsBehind} {commitsBehind, plural, one {promjenu} few {promjene} other {promjena}}",
"components.Login.signinheader": "Prijavi se za nastavak",
"components.Login.signinwithoverseerr": "Koristi tvoj {applicationTitle} račun",
"components.Login.validationpasswordrequired": "Moraš navesti lozinku",
@@ -186,36 +186,36 @@
"components.MovieDetails.originallanguage": "Izvorni jezik",
"components.MovieDetails.MovieCrew.fullcrew": "Svi suradnici",
"components.MovieDetails.physicalrelease": "Fizičko izdanje",
"components.MovieDetails.play4konplex": "Reproduciraj u 4K rezoluciji na Plexu",
"components.MovieDetails.play4konplex": "Reproduciraj u 4K na Plexu",
"components.MovieDetails.recommendations": "Preporuke",
"components.MovieDetails.releasedate": "{releaseCount, plural, one {Datum izdanja} few {Datumi izdanja} other {Datumi izdanja}}",
"components.MovieDetails.rtcriticsscore": "Rotten Tomatoes Tomatometar",
"components.MovieDetails.releasedate": "{releaseCount, plural, one {Datum izlaska} other {Datumi izlaska}}",
"components.MovieDetails.rtcriticsscore": "Tomatometer na Rotten Tomatoes",
"components.MovieDetails.runtime": "{minutes} min",
"components.MovieDetails.studio": "{studioCount, plural, one {studio} few {studija} other {studija}}",
"components.MovieDetails.theatricalrelease": "Izdanje u kinima",
"components.MovieDetails.tmdbuserscore": "TMDB ocjena korisnika",
"components.MovieDetails.viewfullcrew": "Prikaz svih suradnika",
"components.MovieDetails.watchtrailer": "Gledaj najavu",
"components.NotificationTypeSelector.adminissuecommentDescription": "Primi obavijest kada drugi korisnici komentiraju probleme.",
"components.NotificationTypeSelector.adminissuereopenedDescription": "Primi obavijest kada drugi korisnici ponovo otvore probleme.",
"components.NotificationTypeSelector.adminissueresolvedDescription": "Primi obavijest kada drugi korisnici riješe probleme.",
"components.NotificationTypeSelector.adminissuecommentDescription": "Primi obavijest kad drugi korisnici komentiraju probleme.",
"components.NotificationTypeSelector.adminissuereopenedDescription": "Primi obavijest kad drugi korisnici ponovno otvore probleme.",
"components.NotificationTypeSelector.adminissueresolvedDescription": "Primi obavijest kad drugi korisnici riješe probleme.",
"components.NotificationTypeSelector.issuecomment": "Komentar za problem",
"components.NotificationTypeSelector.issuecreated": "Problem prijavljen",
"components.NotificationTypeSelector.issuecreatedDescription": "Pošalji obavijest kada se problem prijavi.",
"components.NotificationTypeSelector.userissueresolvedDescription": "Primi obavijest kad se tvoji prijavljeni problemi riješe.",
"components.NotificationTypeSelector.mediaavailableDescription": "Pošalji obavijesti kada zahtjevi za medije postanu dostupni.",
"components.NotificationTypeSelector.mediadeclinedDescription": "Pošalji obavijesti kada se zahtjevi za medije odbiju.",
"components.NotificationTypeSelector.mediaavailableDescription": "Pošalji obavijesti kada medijski zahtjevi postanu dostupni.",
"components.NotificationTypeSelector.mediadeclinedDescription": "Pošalji obavijesti kada su medijski zahtjevi odbijeni.",
"components.NotificationTypeSelector.mediarequested": "Zahtjev čeka odobrenje",
"components.NotificationTypeSelector.mediarequestedDescription": "Pošalji obavijesti kada korisnici pošalju nove zahtjeve za medije koji zahtijevaju odobrenje.",
"components.NotificationTypeSelector.mediarequestedDescription": "Pošalji obavijesti kada korisnici pošalju nove medijske zahtjeve koji zahtijevaju odobrenje.",
"components.NotificationTypeSelector.mediaautorequested": "Zahtjev je automatski poslan",
"components.NotificationTypeSelector.mediaavailable": "Zahtjev je dostupan",
"components.NotificationTypeSelector.mediafailedDescription": "Pošalji obavijesti kada se zahtjevi za medije ne uspiju dodati u Radarr ili Sonarr.",
"components.NotificationTypeSelector.userissuecommentDescription": "Primi obavijest kada tvoji prijavljeni problemi dobiju nove komentare.",
"components.NotificationTypeSelector.mediaavailable": "Zahtjev dostupan",
"components.NotificationTypeSelector.mediafailedDescription": "Pošalji obavijesti kada se medijski zahtjevi ne uspiju dodati u Radarr ili Sonarr.",
"components.NotificationTypeSelector.userissuecommentDescription": "Primi obavijest kad tvoji prijavljeni problemi dobiju nove komentare.",
"components.PermissionEdit.autoapprove4kSeries": "Automatsko odobravanje 4K serija",
"components.NotificationTypeSelector.usermediafailedDescription": "Primi obavijest kada se zahtjevi za medije ne uspiju dodati u Radarr ili Sonarr.",
"components.NotificationTypeSelector.usermediarequestedDescription": "Primi obavijest kada drugi korisnici pošalju nove zahtjeve za medije koji zahtijevaju odobrenje.",
"components.NotificationTypeSelector.usermediaAutoApprovedDescription": "Primi obavijest kad drugi korisnici pošalju nove zahtjeve za medije koji se automatski odobravaju.",
"components.NotificationTypeSelector.usermediadeclinedDescription": "Primi obavijest kada se tvoji zahtjevi za medije odbiju.",
"components.NotificationTypeSelector.usermediafailedDescription": "Primi obavijest kad se medijski zahtjevi ne uspiju dodati u Radarr ili Sonarr.",
"components.NotificationTypeSelector.usermediarequestedDescription": "Primi obavijest kad drugi korisnici pošalju nove medijske zahtjeve koji zahtijevaju odobrenje.",
"components.NotificationTypeSelector.usermediaAutoApprovedDescription": "Primi obavijest kad drugi korisnici pošalju nove medijske zahtjeve koji se automatski odobravaju.",
"components.NotificationTypeSelector.usermediadeclinedDescription": "Primi obavijest kad se tvoji medijski zahtjevi odbiju.",
"components.PermissionEdit.adminDescription": "Potpun administratorski pristup. Zaobilazi sve druge provjere dozvola.",
"components.PermissionEdit.advancedrequest": "Napredni zahtjevi",
"components.PermissionEdit.autoapprove4k": "Automatsko odobravanje 4K",
@@ -233,24 +233,24 @@
"components.RequestList.RequestItem.seasons": "{seasonCount, plural, one {sezona} few {sezone} other {sezona}}",
"components.RequestBlock.seasons": "{seasonCount, plural, one {sezona} few {sezone} other {sezona}}",
"components.RequestButton.decline4krequests": "Odbij {requestCount, plural, one {4K zahtjev} few {{requestCount} 4K zahtjeva} other {{requestCount} 4K zahtjeva}}",
"components.RequestModal.QuotaDisplay.requiredquotaUser": "Za slanje zahtjeva za ovu seriju, ovaj korisnik mora imati barem <strong>{seasons}</strong> {seasons, plural, one {preostali zahtjev za sezonu} few {preostala zahtjeva za sezone} other {preostalih zahtjeva za sezone}}.",
"components.RequestModal.QuotaDisplay.requiredquota": "Za slanje zahtjeva za ovu seriju moraš imati barem <strong>{seasons}</strong> {seasons, plural, one {preostali zahtjev za sezonu} few {preostala zahtjeva za sezone} other {preostalih zahtjeva za sezone}}.",
"components.RequestModal.QuotaDisplay.requiredquotaUser": "Ovaj korisnik treba imati još barem <strong>{seasons}</strong> {seasons, plural, one {jedan zahtjev za sezonu} other {nekoliko zahtjeva za sezone}} kako bi mogao preadti zahtjev za ovu seriju.",
"components.RequestModal.QuotaDisplay.requiredquota": "Moraš imati još barem <strong>{seasons}</strong> {seasons, plural, one {zahtjev za sezonu} few {zahtjeva za sezone} other {zahtjeva za sezone}} za slanje zahtjeva za ovu seriju.",
"components.RequestModal.QuotaDisplay.seasonlimit": "{limit, plural, one {sezona} few {sezone} other {sezona}}",
"components.RequestModal.requestmovies": "Zatraži {count} {count, plural, one {film} few {filma} other {filmova}}",
"components.RequestModal.requestmovies4k": "Zatraži {count} {count, plural, one {film} few {filma} other {filmova}} u 4K",
"components.NotificationTypeSelector.mediaAutoApprovedDescription": "Pošalji obavijesti kada korisnici pošalju nove zahtjeve za medije koji se automatski odobravaju.",
"components.NotificationTypeSelector.mediaapproved": "Zahtjev je odobren",
"components.NotificationTypeSelector.mediaapprovedDescription": "Pošalji obavijesti kada se zahtjevi za medije ručno odobre.",
"components.NotificationTypeSelector.mediaautorequestedDescription": "Primi obavijest kada se novi zahtjevi za medije automatski pošalju za stavke u tvom Plex popisu gledanja.",
"components.NotificationTypeSelector.mediaAutoApprovedDescription": "Pošalji obavijesti kada korisnici pošalju novi medijski zahtjev koji se automatski odobrava.",
"components.NotificationTypeSelector.mediaapproved": "Zahtjev odobren",
"components.NotificationTypeSelector.mediaapprovedDescription": "Pošalji obavijesti kada se medijski zahtjev ručno odobri.",
"components.NotificationTypeSelector.mediaautorequestedDescription": "Primi obavijest kad se novi medijski zahtjevi automatski podnesu za stavke u tvom Plex popisu gledanja.",
"components.NotificationTypeSelector.mediadeclined": "Zahtjev je odbijen",
"components.NotificationTypeSelector.mediafailed": "Obrada zahtjeva nije uspjela",
"components.NotificationTypeSelector.notificationTypes": "Vrste obavijesti",
"components.NotificationTypeSelector.userissuecreatedDescription": "Primi obavijest kada drugi korisnici prijave probleme.",
"components.NotificationTypeSelector.userissuereopenedDescription": "Primi obavijest kada se tvoji prijavljeni problemi ponovno otvore.",
"components.NotificationTypeSelector.usermediaapprovedDescription": "Primi obavijest kada se tvoji zahtjevi za medije odobre.",
"components.NotificationTypeSelector.usermediaavailableDescription": "Primi obavijest kada tvoji medijski zahtjevi postanu dostupni.",
"components.NotificationTypeSelector.userissuecreatedDescription": "Primi obavijest kad drugi korisnici prijave probleme.",
"components.NotificationTypeSelector.userissuereopenedDescription": "Primi obavijest kad se tvoji prijavljeni problemi ponovno otvore.",
"components.NotificationTypeSelector.usermediaapprovedDescription": "Primi obavijest kad se tvoji zahtjevi za medije odobre.",
"components.NotificationTypeSelector.usermediaavailableDescription": "Primi obavijest kad tvoji medijski zahtjevi postanu dostupni.",
"components.PermissionEdit.admin": "Administrator",
"components.PermissionEdit.advancedrequestDescription": "Dozvoli mijenjanje naprednih opcija zahtjeva za medije.",
"components.PermissionEdit.advancedrequestDescription": "Dozvoli mijenjanje naprednih opcija medijskih zahtjeva.",
"components.PermissionEdit.autoapprove": "Automatsko odobravanje",
"components.PermissionEdit.autoapprove4kMovies": "Automatsko odobravanje 4K filmova",
"components.PermissionEdit.autoapprove4kDescription": "Dozvoli automatsko odobravanje svih zahtjeva za medije u 4K rezoluciji.",
@@ -258,7 +258,7 @@
"components.PermissionEdit.autoapproveMovies": "Automatsko odobravanje filmova",
"components.PermissionEdit.autoapproveSeries": "Automatsko odobravanje serija",
"components.RequestButton.declinerequests": "Odbij {requestCount, plural, one {zahtjev} few {{requestCount} zahtjeva} other {{requestCount} zahtjeva}}",
"components.RequestModal.QuotaDisplay.requestsremaining": "{remaining, plural, =0 {0} other {<strong>#</strong>}} {type} {remaining, plural, one {zahtjev preostao} few {zahtjeva preostala} other {zahtjeva preostalo}}",
"components.RequestModal.QuotaDisplay.requestsremaining": "{remaining, plural, =0 {No} other {<strong>#</strong>}} {type} {remaining, plural, one {zahtjev preostalo} other {zahtjeva preostala}}",
"components.Settings.SettingsJobsCache.editJobScheduleCurrent": "Aktualna učestalost",
"components.Settings.SettingsJobsCache.editJobSchedulePrompt": "Nova učestalost",
"components.Settings.SettingsJobsCache.editJobScheduleSelectorHours": "{jobScheduleHours, plural, one {Svaki sat} few {Svaka {jobScheduleHours} sata} other {Svakih {jobScheduleHours} sati}}",
@@ -364,7 +364,7 @@
"components.Settings.menuServices": "Usluge",
"components.Settings.menuUsers": "Korisnici",
"components.Settings.noDefault4kServer": "4K {serverType} poslužitelj mora biti označen kao zadani kako bi se korisnicima omogućilo slanje zahtjeva za 4K {mediaType}.",
"components.Settings.noDefaultServer": "Barem jedan {serverType} poslužitelj mora biti označen kao zadani kako bi se zahtjevi za {mediaType} mogli obraditi.",
"components.Settings.noDefaultServer": "Najmanje jedan {serverType} poslužitelj mora biti označen kao zadani kako bi se zahtjevi za {mediaType} mogli obraditi.",
"components.Settings.notificationAgentSettingsDescription": "Konfiguriraj i aktiviraj agente obavijesti.",
"components.Settings.notifications": "Obavijesti",
"components.Settings.notificationsettings": "Postavke obavijesti",
@@ -385,7 +385,7 @@
"components.Settings.serverpresetLoad": "Pritisni gumb za učitavanje dostupnih polsužitelja",
"components.Settings.serverpresetManualMessage": "Ručna konfiguracija",
"components.Settings.services": "Usluge",
"components.Settings.settingUpPlexDescription": "Za postavljanje Plexa unesi detalje ručno ili odaberi poslužitelj dohvaćen s <RegisterPlexTVLink>plex.tv</RegisterPlexTVLink>. Pritisni gumb desno od padajućeg izbornika za dohvaćanje popisa dostupnih poslužitelja.",
"components.Settings.settingUpPlexDescription": "Za postavljanje Plexa, unesi detalje ručno ili odaberi poslužitelj preuzet s <RegisterPlexTVLink>plex.tv</RegisterPlexTVLink>. Pritisni gumb desno od padajućeg izbornika za dohvaćanje popisa dostupnih poslužitelja.",
"components.Settings.sonarrsettings": "Sonarr postavke",
"components.Settings.ssl": "SSL",
"components.Settings.tautulliApiKey": "API ključ",
@@ -399,7 +399,7 @@
"components.Settings.urlBase": "Osnovni URL",
"components.Settings.validationApiKey": "Moraš navesti valjani API ključ",
"components.Settings.webpush": "Web Push",
"components.Setup.continue": "Nastaviti",
"components.Setup.continue": "Nastavi",
"components.Setup.finish": "Završi postavljanje",
"components.Setup.tip": "Savjet",
"components.Setup.welcome": "Overseerr dobrodošlica",
@@ -451,8 +451,8 @@
"components.UserProfile.UserSettings.UserGeneralSettings.general": "Opće",
"components.UserProfile.UserSettings.UserGeneralSettings.generalsettings": "Opće postavke",
"components.UserProfile.UserSettings.UserGeneralSettings.languageDefault": "Zadano ({language})",
"components.UserProfile.UserSettings.UserGeneralSettings.originallanguageTip": "Filtriraj sadržaj po izvornom jeziku",
"components.UserProfile.UserSettings.UserGeneralSettings.originallanguage": "Jezik otkrivanja",
"components.UserProfile.UserSettings.UserGeneralSettings.originallanguageTip": "Filtriraj sadržaj prema izvornom jeziku",
"components.UserProfile.UserSettings.UserGeneralSettings.originallanguage": "Otkrij jezik",
"components.UserProfile.UserSettings.UserGeneralSettings.plexuser": "Plex korisnik",
"components.UserProfile.UserSettings.UserGeneralSettings.plexwatchlistsyncmovies": "Automatsko zahtijevanje filmova",
"components.UserProfile.UserSettings.UserGeneralSettings.plexwatchlistsyncmoviestip": "Automatski zatraži filmove na tvom <PlexWatchlistSupportLink>Plex popisu gledanja</PlexWatchlistSupportLink>",
@@ -482,7 +482,7 @@
"components.UserProfile.UserSettings.UserPasswordChange.nopermissionDescription": "Nemaš dozvole za mijenjenje lozinke ovog korisnika.",
"components.UserProfile.UserSettings.UserPasswordChange.password": "Lozinka",
"components.UserProfile.UserSettings.UserPasswordChange.toastSettingsFailure": "Dogodila se greška prilikom spremanja lozinke.",
"components.UserProfile.UserSettings.UserPasswordChange.toastSettingsFailureVerifyCurrent": "Dogodila se greška prilikom spremanja lozinke. Je li tvoja aktualna lozinka ispravno upisana?",
"components.UserProfile.UserSettings.UserPasswordChange.toastSettingsFailureVerifyCurrent": "Dogodila se greška prilikom spremanja lozinke. Je li vaša aktualna lozinka bila ispravno upisana?",
"components.UserProfile.UserSettings.UserPasswordChange.toastSettingsSuccess": "Lozinka je uspješno spremljena!",
"components.UserProfile.UserSettings.UserPasswordChange.validationCurrentPassword": "Moraš navesti tvoju aktualnu lozinku",
"components.UserProfile.UserSettings.UserPasswordChange.validationNewPassword": "Moraš navesti novu lozinku",
@@ -494,10 +494,10 @@
"components.UserProfile.UserSettings.menuNotifications": "Obavijesti",
"components.UserProfile.UserSettings.menuPermissions": "Dozvole",
"components.UserProfile.UserSettings.unauthorizedDescription": "Nemaš dozvole za mijenjenje postavki ovog korisnika.",
"components.UserProfile.emptywatchlist": "Mediji koji su dodani u tvoj <PlexWatchlistSupportLink>Plex popis gledanja</PlexWatchlistSupportLink> pojavit će se ovdje.",
"components.UserProfile.emptywatchlist": "Mediji koji su dodani u tvoj <PlexWatchlistSupportLink>popis gledanja na Plexu</PlexWatchlistSupportLink> pojavit će se ovdje.",
"components.UserProfile.movierequests": "Zahtjevi za filmove",
"components.UserProfile.pastdays": "{type} (zadnjih {days} dana)",
"components.UserProfile.plexwatchlist": "Plex popis gledanja",
"components.UserProfile.plexwatchlist": "Popis gledanja na Plexu",
"components.UserProfile.seriesrequest": "Zahtjevi za serije",
"components.UserProfile.totalrequests": "Ukupno zahtjeva",
"components.UserProfile.unlimited": "Neograničeno",
@@ -538,7 +538,7 @@
"components.PermissionEdit.viewissues": "Prikaz problema",
"components.PermissionEdit.viewrecent": "Prikaz nedavno dodanih",
"components.PermissionEdit.viewrequests": "Prikaz zahtjeva",
"components.PermissionEdit.viewwatchlists": "Prikaži Plex popise gledanja",
"components.PermissionEdit.viewwatchlists": "Prikaz Plex popisa gledanja",
"components.PersonDetails.alsoknownas": "Poznat/a i kao: {names}",
"components.PersonDetails.appearsin": "Nastupanja",
"components.PersonDetails.ascharacter": "kao {character}",
@@ -560,7 +560,7 @@
"components.RequestButton.viewrequest": "Prikaz zahtjeva",
"components.RequestButton.viewrequest4k": "Prikaz 4K zahtjeva",
"components.RequestCard.approverequest": "Odobri zahtjev",
"components.RequestCard.cancelrequest": "Otkaži zahtjev",
"components.RequestCard.cancelrequest": "Prekini zahtjev",
"components.RequestCard.declinerequest": "Odbij zahtjev",
"components.RequestCard.deleterequest": "Izbriši zahtjev",
"components.RequestCard.editrequest": "Uredi zahtjev",
@@ -578,19 +578,19 @@
"components.RequestModal.errorediting": "Dogodila se greška prilikom uređivanja zahtjeva.",
"components.RequestModal.extras": "Dodaci",
"components.RequestModal.numberofepisodes": "Broj epizoda",
"components.RequestModal.pending4krequest": "4K zahtjevi na čekanju",
"components.RequestModal.pending4krequest": "4K zahtjeva na čekanju",
"components.RequestModal.pendingapproval": "Tvoj zahtjev čeka na odobrenje.",
"components.RequestCard.tvdbid": "TVDB ID",
"components.RequestModal.pendingrequest": "Zahtjevai na čekanju",
"components.RequestModal.requestApproved": "Zahtjev za <strong>{title}</strong> je odobren!",
"components.RequestModal.requestcancelled": "Zahtjev za <strong>{title}</strong> je prekinut.",
"components.RequestModal.requestcollection4ktitle": "Zatraži zbirku u 4K",
"components.RequestModal.requestcollectiontitle": "Zatraži zbirku",
"components.RequestModal.requestcollection4ktitle": "Zahtjev za zbirkom u 4K",
"components.RequestModal.requestcollectiontitle": "Zahtjev za zbirkom",
"components.RequestModal.requesterror": "Dogodila se greška prilikom slanja zahtjeva.",
"components.RequestModal.requestfrom": "Zahtjev korisnika {username} čeka na odobrenje.",
"components.RequestModal.requestmovie4ktitle": "Zatraži film u 4K",
"components.RequestModal.requestseries4ktitle": "Zatraži seriju u 4K",
"components.RequestModal.requestseriestitle": "Zatraži seriju",
"components.RequestModal.requestseries4ktitle": "Zatraži serije u 4K",
"components.RequestModal.requestseriestitle": "Zatraži serije",
"components.RequestModal.season": "Sezona",
"components.RequestModal.seasonnumber": "Sezona {number}",
"components.RequestModal.selectmovies": "Odaberi filmove",
@@ -690,7 +690,7 @@
"components.Settings.RadarrModal.validationMinimumAvailabilityRequired": "Moraš odabrati najmanju dostupnost",
"components.Settings.RadarrModal.validationPortRequired": "Moraš navesti valjani broj priključka",
"components.Settings.RadarrModal.validationProfileRequired": "Moraš odabrati profil kvalitete",
"components.Settings.SettingsAbout.Releases.currentversion": "Trenutno",
"components.Settings.SettingsAbout.Releases.currentversion": "Aktualno",
"components.Settings.SettingsAbout.Releases.latestversion": "Najnovije",
"components.Settings.SettingsAbout.Releases.releasedataMissing": "Podaci izdanja trenutačno nisu nedostupni.",
"components.Settings.SettingsAbout.Releases.versionChangelog": "{version} zapisnik promjena",
@@ -718,7 +718,7 @@
"components.StatusChecker.restartRequiredDescription": "Ponovo pokreni poslužitelja kako bi se ažurirane postavke primijenile.",
"components.TvDetails.episodeRuntime": "Trajanje epizode",
"components.TvDetails.nextAirDate": "Datum sljedećeg emitiranja",
"components.TvDetails.rtaudiencescore": "Ocjena Rotten Tomatoes publike",
"components.TvDetails.rtaudiencescore": "Ocjena publike na Rotten Tomatoes",
"components.TvDetails.seasonnumber": "Sezona {seasonNumber}",
"components.UserList.createlocaluser": "Stvori lokalnog korisnika",
"components.UserList.deleteuser": "Izbriši korisnika",
@@ -736,11 +736,11 @@
"components.PermissionEdit.autorequestSeries": "Automatsko zahtijevanje serija",
"components.PermissionEdit.createissues": "Prijavljivanje problema",
"components.PermissionEdit.manageissues": "Upravljanje problemima",
"components.PermissionEdit.managerequests": "Upravljaj zahtjevima",
"components.PermissionEdit.managerequests": "Upravljanje zahtjevima",
"components.PermissionEdit.request": "Zahtjev",
"components.PermissionEdit.request4k": "Zahtjev za 4K rezoluciju",
"components.PermissionEdit.request4kMovies": "Zahtjev za filmove u 4K rezoluciji",
"components.PermissionEdit.request4kTv": "Zahtjev za serije u 4K rezoluciji",
"components.PermissionEdit.request4k": "Zahtjev za 4K",
"components.PermissionEdit.request4kMovies": "Zahtjev za 4K filmove",
"components.PermissionEdit.request4kTv": "Zahtjev za 4K serije",
"components.PermissionEdit.requestMovies": "Zahtjev za filmovima",
"components.PersonDetails.birthdate": "Datum rođenja {birthdate}",
"components.PersonDetails.crewmember": "Suradnik",
@@ -827,7 +827,7 @@
"components.Settings.Notifications.NotificationsWebhook.agentenabled": "Aktiviraj agenta",
"components.Settings.Notifications.NotificationsWebPush.webpushsettingssaved": "Web push postavke obavijesti su uspješno spremljene!",
"components.Settings.Notifications.NotificationsWebhook.authheader": "Zaglavlje autorizacije",
"components.Settings.Notifications.NotificationsWebhook.resetPayload": "Resetiraj na zadane vrijednosti",
"components.Settings.Notifications.NotificationsWebhook.resetPayload": "Vrati na zadane vrijednosti",
"components.Settings.Notifications.NotificationsWebhook.toastWebhookTestFailed": "Neuspjelo slanje Webhook obavijesti provjere.",
"components.Settings.Notifications.NotificationsWebhook.webhooksettingsfailed": "Neuspjelo spremanje Webhook postavki obavijesti.",
"components.Settings.Notifications.NotificationsWebhook.webhooksettingssaved": "Webhook postavke obavijesti su uspješno spremljene!",
@@ -938,7 +938,7 @@
"components.TitleCard.cleardata": "Izbriši podatke",
"components.TitleCard.mediaerror": "{mediaType} nije pronađen",
"components.TitleCard.tmdbid": "TMDB ID",
"components.TvDetails.Season.somethingwentwrong": "Dogodila se greška prilikom dohvaćanja podataka sezona.",
"components.TvDetails.Season.somethingwentwrong": "Dogodila se greška prilikom preuzimanja podataka sezona.",
"components.TvDetails.anime": "Anime",
"components.TvDetails.episodeRuntimeMinutes": "{runtime} min",
"components.TvDetails.firstAirDate": "Datum prvog emitiranja",
@@ -948,12 +948,12 @@
"components.TvDetails.originaltitle": "Izvorni naslov",
"components.TvDetails.overview": "Pregled",
"components.TvDetails.overviewunavailable": "Pregled nedostupan.",
"components.TvDetails.play4konplex": "Reproduciraj u 4K rezoluciji na Plexu",
"components.TvDetails.play4konplex": "Reproduciraj u 4K na Plexu",
"components.TvDetails.playonplex": "Reproduciraj na Plexu",
"components.TvDetails.productioncountries": "{countryCount, plural, one {zemlja produkcije} few {zemlje produkcije} other {zemalja produkcije}}",
"components.TvDetails.recommendations": "Preporuke",
"components.TvDetails.reportissue": "Prijavi problem",
"components.TvDetails.rtcriticsscore": "Rotten Tomatoes Tomatometar",
"components.TvDetails.rtcriticsscore": "Tomatometer na Rotten Tomatoes",
"components.TvDetails.showtype": "Vrste serija",
"components.TvDetails.similar": "Slične serije",
"components.TvDetails.status4k": "4K {status}",
@@ -983,7 +983,7 @@
"components.UserProfile.UserSettings.UserGeneralSettings.movierequestlimit": "Ograničenje zahtjeva za filmove",
"components.UserProfile.UserSettings.UserGeneralSettings.owner": "Vlasnik",
"components.UserProfile.UserSettings.UserGeneralSettings.plexwatchlistsyncseriestip": "Automatski zatraži serije na tvom <PlexWatchlistSupportLink>Plex popisu gledanja</PlexWatchlistSupportLink>",
"components.UserProfile.UserSettings.UserGeneralSettings.region": "Regionalno otkrivanje",
"components.UserProfile.UserSettings.UserGeneralSettings.region": "Otkrij regiju",
"components.UserProfile.UserSettings.UserGeneralSettings.toastSettingsFailure": "Dogodila se greška prilikom spremanja postavki.",
"components.UserProfile.UserSettings.UserGeneralSettings.toastSettingsSuccess": "Postavke su uspješno spremljene!",
"components.UserProfile.UserSettings.UserGeneralSettings.user": "Korisnik",
@@ -1042,7 +1042,7 @@
"i18n.status": "Stanje",
"pages.oops": "Ups",
"components.Settings.advancedTooltip": "Neispravno konfiguriranje ove postavke može pokvariti funkcionalnost",
"components.Settings.noDefaultNon4kServer": "Ako imaš samo jedan {serverType} poslužitelj za ne-4K i za 4K sadržaj (ili ako preuzimaš samo 4K sadržaj), <strong>NEMOJ</strong> odrediti svoj {serverType} poslužitelj kao 4K poslužitelj.",
"components.Settings.noDefaultNon4kServer": "Ako imate samo jedan {serverType} poslužitelj za ne-4K i za 4K sadržaj (ili ako preuzimaš samo 4K sadržaj), tvoj {serverType} poslužitelj <strong>NE </strong> bi trebao biti određen kao 4K poslužitelj.",
"components.PermissionEdit.requestMoviesDescription": "Dozvoli automatsko slanje zahtjeva za filmove koje nisu u 4K rezoluciji.",
"components.PermissionEdit.autorequestDescription": "Dozvoli automatsko slanje zahtjeva za medije koji nisu u 4K rezoluciji putem Plex popisa gledanja.",
"components.PermissionEdit.autorequestMoviesDescription": "Dozvoli automatsko slanje zahtjeva za filmove koji nisu u 4K rezoluciji putem Plex popisa gledanja.",
@@ -1057,16 +1057,16 @@
"components.PermissionEdit.request4kTvDescription": "Dozvoli slanje zahtjeva za serijama u 4K rezoluciji.",
"components.PermissionEdit.usersDescription": "Dozvoli upravljanje korisnicima. Korisnici s ovom dozvolom ne mogu mijenjati korisnike s administratorskom privilegijom ili dozvoliti administratorske privilegije.",
"components.PermissionEdit.viewissuesDescription": "Dozvoli prikaz problema s medijima koje su prijavili drugi korisnici.",
"components.PermissionEdit.viewrequestsDescription": "Dozvoli prikaz zahtjeva za medije koje su poslali drugi korisnici.",
"components.PermissionEdit.viewrequestsDescription": "Dozvoli prikaz medijskih zahtjeva koje su prijavili drugi korisnici.",
"components.PermissionEdit.viewwatchlistsDescription": "Dozvoli prikaz Plex popisa gledanja drugih korisnika.",
"components.Settings.notrunning": "Ne pokreće se",
"components.Settings.serverRemote": "udaljeni",
"components.RequestModal.QuotaDisplay.quotaLink": "Sažetak tvojih ograničenja zahtjeva možeš vidjeti na tvojoj <ProfileLink>stranici profila</ProfileLink>.",
"components.RequestModal.QuotaDisplay.quotaLinkUser": "Sažetak ograničenja zahtjeva korisničkih zahtjeva možeš vidjeti na njihovoj <ProfileLink>stranici profila</ProfileLink>.",
"components.RequestModal.QuotaDisplay.notenoughseasonrequests": "Nema dovoljno preostalih zahtjeva za sezone",
"components.RequestModal.QuotaDisplay.notenoughseasonrequests": "Nema dovoljno preostalih zahtjeva za sezonama",
"components.RequestModal.SearchByNameModal.nomatches": "Nismo uspjeli pronaći seriju koja odgovara ovoj seriji.",
"components.RequestModal.SearchByNameModal.notvdbiddescription": "Nismo uspjeli automatski naći odgovarajuću seriju. Dolje odaberi odgovarajuću seriju.",
"components.RequestModal.requestadmin": "Ovaj će se zahtjev automatski odobriti.",
"components.RequestModal.requestadmin": "Ovaj će se zahtjev automatski prihvatiti.",
"components.ResetPassword.emailresetlink": "E-mail poveznica za obnavljanje lozinke",
"components.Settings.Notifications.NotificationsLunaSea.webhookUrlTip": "Tvoj korisničke ili na osnovi uređaja <LunaSeaLink>webhook URL obavijesti</LunaSeaLink>",
"components.Settings.Notifications.NotificationsWebPush.httpsRequirement": "Za primanje web push obavijesti, Overseerr se mora posluživati putem HTTPS-a.",
@@ -1080,7 +1080,7 @@
"components.Settings.plexlibrariesDescription": "Biblioteke u kojima će Overseerr tražiti naslove. Postavi i spremi tvoje postavke Plex veze, a zatim pritisni gumb ispod ako je popis biblioteka prazan.",
"components.Settings.startscan": "Pokreni pretraživanje",
"components.Setup.scanbackground": "Pretraživanje se pokreće u pozadini. U međuvremenu možeš nastaviti s postupkom postavljanja.",
"components.PermissionEdit.managerequestsDescription": "Dozvoli upravljanje zahtjevima za medije. Svi zahtjevi korisnika s ovom dozvolom će se automatski odobriti.",
"components.PermissionEdit.managerequestsDescription": "Dozvoli upravljanje zahtjevima za medijima. Svi zahtjevi korisnika s ovom dozvolom će se automatski odobriti.",
"components.Settings.Notifications.NotificationsWebhook.customJson": "JSON sadržaj",
"components.Settings.SettingsJobsCache.plex-full-scan": "Pretraživanje cijele Plex biblioteke",
"components.RequestModal.QuotaDisplay.allowedRequests": "Smiješ zatražiti <strong>{limit}</strong> {type} svakih <strong>{days}</strong> dana.",
@@ -1094,149 +1094,12 @@
"components.Settings.SettingsJobsCache.imagecache": "Predmemorija slika",
"components.Settings.SettingsJobsCache.imagecachesize": "Ukupna veličina predmemorije",
"components.Settings.SettingsJobsCache.imagecachecount": "Slike spremljene u predmemoriju",
"components.Settings.SettingsJobsCache.imagecacheDescription": "Kada je u postavkama aktivirano, Overseerr će služiti kao posrednik i predmemorirati slike iz unaprijed konfiguriranih vanjskih izvora. Predmemorirane slike spremaju se u mapu konfiguracije. Datoteke možeš pronaći u <code>{appDataPath}/cache/images</code>.",
"components.Settings.SettingsJobsCache.imagecacheDescription": "Kad je u postavkama aktivirano, Overseerr će služiti kao posrednik i predmemorirati slike iz unaprijed konfiguriranih vanjskih izvora. Predmemorirane slike spremaju se u mapu konfiguracije. Datoteke možeš pronaći u <code>{appDataPath}/cache/images</code>.",
"components.DownloadBlock.formattedTitle": "{title}: Sezona {seasonNumber} Episoda {episodeNumber}",
"components.RequestCard.unknowntitle": "Nepoznat naslov",
"components.RequestList.RequestItem.unknowntitle": "Nepoznat naslov",
"components.StatusBadge.seasonepisodenumber": "S{seasonNumber}E{episodeNumber}",
"components.Discover.DiscoverTv.discovertv": "Serije",
"components.Discover.RecentlyAddedSlider.recentlyAdded": "Nedavno dodani",
"components.Discover.DiscoverMovies.discovermovies": "Filmovi",
"components.Discover.moviegenres": "Žanrovi filmova",
"components.Discover.FilterSlideover.keywords": "Ključne riječi",
"components.Discover.FilterSlideover.activefilters": "{count, plural, one {# aktivni filtar} few {# aktivna filtra} other {# aktivnih filtara}}",
"components.Discover.FilterSlideover.firstAirDate": "Datum prvog emitiranja",
"components.Discover.PlexWatchlistSlider.plexwatchlist": "Tvoj Plex popis gledanja",
"components.Discover.FilterSlideover.genres": "Žanrovi",
"components.Discover.FilterSlideover.originalLanguage": "Izvorni jezik",
"components.Discover.CreateSlider.editsuccess": "Uređen klizač i spremljene postavke prilagodbe otkrivanja.",
"components.Discover.CreateSlider.slidernameplaceholder": "Naziv klizača",
"components.Discover.DiscoverMovies.sortPopularityDesc": "Popularnost (silazno)",
"components.Discover.CreateSlider.searchStudios": "Pretražite studije…",
"components.Discover.CreateSlider.providetmdbnetwork": "Navdeite TMDB ID mreže",
"components.Discover.CreateSlider.addfail": "Izrada novog klizača nije uspjela.",
"components.Discover.DiscoverMovies.sortPopularityAsc": "Popularnost (uzlazno)",
"components.Discover.CreateSlider.needresults": "Morate imati barem 1 rezultat.",
"components.Discover.CreateSlider.addcustomslider": "Stvorite prilagođeni klizač",
"components.Discover.CreateSlider.editSlider": "Uredi klizač",
"components.Discover.CreateSlider.validationDatarequired": "Morate navesti vrijednost.",
"components.Discover.CreateSlider.providetmdbstudio": "Navedite TMDB ID studija",
"components.Discover.CreateSlider.searchGenres": "Traži žanrove…",
"components.Discover.CreateSlider.editfail": "Uređivanje klizača nije uspjelo.",
"components.Discover.CreateSlider.starttyping": "Počinje tipkati za pretraživanje.",
"components.Discover.CreateSlider.addSlider": "Dodaj klizač",
"components.Discover.CreateSlider.providetmdbsearch": "Navedite upit za pretraživanje",
"components.Discover.CreateSlider.providetmdbkeywordid": "Navdeite TMDB ID ključne riječi",
"components.Discover.DiscoverMovieKeyword.keywordMovies": "{keywordTitle} filmovi",
"components.Discover.CreateSlider.validationTitlerequired": "Morate navesti naslov.",
"components.Discover.CreateSlider.nooptions": "Nema rezultata.",
"components.Discover.CreateSlider.searchKeywords": "Pretraži ključne riječi…",
"components.Discover.CreateSlider.addsuccess": "Stvoren je novi klizač i spremljene su postavke prilagodbe otkrivanja.",
"components.Discover.CreateSlider.providetmdbgenreid": "Navedite TMDB ID žanra",
"components.Discover.DiscoverTv.sortTmdbRatingAsc": "TMDB ocjena (uzlazno)",
"components.Discover.FilterSlideover.studio": "Studio",
"components.Discover.FilterSlideover.ratingText": "Ocjena od {minValue} do {maxValue}",
"components.Discover.DiscoverTv.sortFirstAirDateAsc": "Prvo emitiranje (uzlazno)",
"components.Discover.DiscoverTv.sortTmdbRatingDesc": "TMDB ocjena (silazno)",
"components.Discover.DiscoverMovies.activefilters": "{count, plural, one {# aktivni filtar} few {# aktivna filtra} other {# aktivnih filtara}}",
"components.Discover.DiscoverMovies.sortTmdbRatingAsc": "TMDB ocjena (uzlazno)",
"components.Discover.FilterSlideover.clearfilters": "Obriši aktivne filtre",
"components.Discover.DiscoverMovies.sortReleaseDateDesc": "Datum izlaska (silazno)",
"components.Discover.DiscoverTv.activefilters": "{count, plural, one {# aktivni filtar} few {# aktivna filtra} other {# aktivnih filtara}}",
"components.Discover.FilterSlideover.streamingservices": "Streaming usluge",
"components.Discover.FilterSlideover.tmdbuserscore": "TMDB ocjena korisnika",
"components.Discover.DiscoverTv.sortPopularityAsc": "Popularnost (uzlazno)",
"components.Discover.DiscoverTv.sortTitleAsc": "Naslov (A-Z) (uzlazno)",
"components.Discover.DiscoverTv.sortFirstAirDateDesc": "Prvo emitiranje (silazno)",
"components.Discover.FilterSlideover.releaseDate": "Datum izlaska",
"components.Discover.DiscoverSliderEdit.deletefail": "Greška pri uklanjanju klizača.",
"components.Discover.FilterSlideover.runtime": "Trajanje",
"components.Discover.FilterSlideover.from": "Od",
"components.Discover.DiscoverMovies.sortTitleDesc": "Naslov (Z-A) (silazno)",
"components.Discover.DiscoverTv.sortPopularityDesc": "Popularnost (silazno)",
"components.Discover.FilterSlideover.to": "Do",
"components.Discover.FilterSlideover.filters": "Filtri",
"components.Discover.DiscoverSliderEdit.enable": "Vidljivost",
"components.Discover.FilterSlideover.runtimeText": "trajanje {minValue}-{maxValue} minuta",
"components.Discover.DiscoverMovies.sortReleaseDateAsc": "Datum izlaska (uzlazno)",
"components.Discover.DiscoverMovies.sortTmdbRatingDesc": "TMDB ocjena (silazno)",
"components.Discover.FilterSlideover.tmdbuservotecount": "TMDB broj glasova korisnika",
"components.Discover.DiscoverSliderEdit.deletesuccess": "Klizač uspješno uklonjen.",
"components.Discover.DiscoverMovies.sortTitleAsc": "Naslov (A-Z) (uzlazno)",
"components.Discover.DiscoverTv.sortTitleDesc": "Naslov (Z-A) (silazno)",
"components.Discover.DiscoverSliderEdit.remove": "Ukloni",
"components.Discover.tvgenres": "Žanrovi serija",
"components.Discover.DiscoverTvKeyword.keywordSeries": "{keywordTitle} serije",
"components.Selector.showmore": "Pokaži više",
"components.Selector.searchGenres": "Odaberi žanrove …",
"components.Selector.searchStudios": "Traži studija …",
"components.Discover.tmdbtvgenre": "TMDB žanr serije",
"components.Selector.showless": "Pokaži manje",
"components.Selector.starttyping": "Počni tipkati za pretraživanje.",
"components.Selector.searchKeywords": "Traži ključne riječi …",
"components.Selector.nooptions": "Nema rezultata.",
"components.Discover.tmdbmoviegenre": "TMDB žanr filmova",
"components.Discover.FilterSlideover.voteCount": "Broj glasova između {minValue} i {maxValue}",
"components.Settings.SettingsJobsCache.editJobScheduleSelectorSeconds": "{jobScheduleHours, plural, one {Svake sekunde} few {Svake {jobScheduleHours} sekunde} other {Svakih {jobScheduleHours} sekundi}}",
"components.Layout.Sidebar.browsemovies": "Filmovi",
"components.Layout.Sidebar.browsetv": "Serije",
"components.Discover.updatesuccess": "Aktualiziraj postavke za prilagođavanje otkrivanja.",
"components.Discover.updatefailed": "Dogodila se greška tiijekom aktualiziranja postavki za prilagođavanje otkrivanja.",
"components.Settings.SettingsMain.toastApiKeyFailure": "Dogodila se greška prilikom generiranja novog API ključa.",
"components.Settings.SettingsMain.toastSettingsFailure": "Dogodila se greška prilikom spremanja postavki.",
"components.Discover.PlexWatchlistSlider.emptywatchlist": "Mediji koji su dodani u tvoj <PlexWatchlistSupportLink>Plex popis gledanja</PlexWatchlistSupportLink> pojavit će se ovdje.",
"components.Discover.networks": "Mreže",
"components.Discover.studios": "Studiji",
"components.Settings.SettingsMain.hideAvailable": "Sakrij dostupne medije",
"components.Settings.Notifications.NotificationsPushover.sound": "Zvuk obavijesti",
"components.Discover.tmdbmoviekeyword": "TMDB ključna riječ filmova",
"components.Discover.stopediting": "Prekini uređivanje",
"components.Discover.resetsuccess": "Uspješno resetiranje postavki prilagodbe otkrivanja.",
"components.Discover.customizediscover": "Prilagodi otkrivanje",
"components.Settings.SettingsMain.locale": "Jezik prikaza",
"components.Discover.tmdbtvkeyword": "TMDB ključna riječ serija",
"components.Discover.tmdbnetwork": "TMDB mreža",
"components.Settings.SettingsMain.applicationTitle": "Naslov programa",
"components.Settings.SettingsMain.originallanguage": "Jezik otkrivanja",
"components.Settings.SettingsMain.csrfProtection": "Aktiviraj CSRF zaštitu",
"components.Discover.tmdbstudio": "TMDB studio",
"components.Settings.SettingsMain.generalsettings": "Opće postavke",
"components.MovieDetails.imdbuserscore": "IMDB ocjena korisnika",
"components.Discover.tmdbtvstreamingservices": "TMDB streaming usluge za TV",
"components.Settings.SettingsMain.apikey": "API ključ",
"components.Settings.SettingsMain.cacheImages": "Aktiviraj predmemoriranje slika",
"components.Discover.tmdbmoviestreamingservices": "TMDB streaming usluge filmova",
"components.Discover.resetfailed": "Dogodila se greška prilikom resetiranja postavki prilagodbe otkrivanja.",
"components.Discover.tmdbsearch": "TMDB pretraga",
"components.Settings.SettingsMain.applicationurl": "URL programa",
"components.Settings.SettingsMain.general": "Opće",
"components.UserProfile.UserSettings.UserNotificationSettings.sound": "Zvuk obavijesti",
"components.Discover.resettodefault": "Resetiraj na zadane vrijednosti",
"components.Settings.SettingsMain.regionTip": "Filtriraj sadržaj prema regionalnoj dostupnosti",
"components.UserProfile.UserSettings.UserNotificationSettings.deviceDefault": "Zadane postavke uređaja",
"components.Settings.SettingsMain.region": "Regionalno otkrivanje",
"components.Settings.SettingsMain.trustProxy": "Aktiviraj proxy podršku",
"components.Settings.SettingsMain.toastSettingsSuccess": "Postavke su uspješno spremljene!",
"i18n.collection": "Zbirka",
"components.Settings.SettingsMain.originallanguageTip": "Filtriraj sadržaj po izvornom jeziku",
"components.Settings.SettingsMain.cacheImagesTip": "Predmemoriraj slike eksternih izvora (zahtijeva značajnu količinu memorije na disku)",
"components.Settings.SettingsJobsCache.availability-sync": "Sinkronizacija dostupnosti medija",
"components.Settings.SettingsMain.trustProxyTip": "Dopusti Overseerru da ispravno registrira IP adrese klijenata iza proxyja",
"components.Settings.SettingsMain.validationApplicationUrlTrailingSlash": "URL ne smije završiti s kosom crtom",
"components.Settings.SonarrModal.animeSeriesType": "Vrsta anime serije",
"components.Settings.SettingsMain.generalsettingsDescription": "Konfiguriraj globalne i zadane postavke za Overseerr.",
"components.Settings.SettingsMain.toastApiKeySuccess": "Novi API ključ je uspješno generiran!",
"components.Settings.SonarrModal.seriesType": "Vrsta serije",
"components.Settings.SettingsMain.csrfProtectionHoverTip": "NEMOJ aktivirati ovu postavku ako ne razumiješ što radiš!",
"components.Settings.Notifications.NotificationsPushover.deviceDefault": "Zadane postavke uređaja",
"components.Settings.SettingsMain.validationApplicationUrl": "Moraš navesti valjani URL",
"components.Settings.SettingsMain.validationApplicationTitle": "Moraš navesti naslov programa",
"components.Settings.SettingsMain.csrfProtectionTip": "Postavi eksterni API pristup u samo-za-čitanje (zahtijeva HTTPS)",
"components.Discover.resetwarning": "Vrati sve klizače na zadane postavke. To će također izbrisati sve prilagođene klizače!",
"components.Settings.SonarrModal.tagRequestsInfo": "Automatski dodaj dodatnu oznaku s korisničkim ID-om i prikaznim imenom podnositelja zahtjeva",
"components.Settings.RadarrModal.tagRequestsInfo": "Automatski dodaj dodatnu oznaku s korisničkim ID-om i prikaznim imenom podnositelja zahtjeva",
"components.Discover.createnewslider": "Stvori novi klizač",
"components.Settings.SettingsMain.partialRequestsEnabled": "Dopusti djelomične zahtjeve za seriju",
"components.Settings.SonarrModal.tagRequests": "Označi zahtjeve",
"components.Settings.RadarrModal.tagRequests": "Označi zahtjeve"
"components.Discover.RecentlyAddedSlider.recentlyAdded": "Nedavno dodano",
"components.Discover.DiscoverMovies.discovermovies": "Filmovi"
}

View File

@@ -426,7 +426,7 @@
"components.Settings.SettingsJobsCache.jobname": "Feladat neve",
"components.Settings.SettingsJobsCache.jobcancelled": "{jobname} törölve.",
"components.Settings.SettingsJobsCache.flushcache": "Cache kiürítése",
"components.Settings.SettingsJobsCache.download-sync-reset": "Töltse le a Sync Reset",
"components.Settings.SettingsJobsCache.download-sync-reset": "Szinkronizálás \"reset\" letöltése",
"components.Settings.SettingsJobsCache.download-sync": "Szinkronizálás letöltése",
"components.Settings.SettingsJobsCache.command": "Parancs",
"components.Settings.SettingsJobsCache.canceljob": "Feladat megszakítása",
@@ -460,14 +460,14 @@
"components.Settings.SettingsAbout.Releases.releases": "Kiadások",
"components.Settings.SettingsAbout.Releases.releasedataMissing": "Kiadási adatok nem állnak rendelkezésre.",
"components.Settings.SettingsAbout.Releases.latestversion": "Legújabb",
"components.Settings.SettingsAbout.Releases.currentversion": "Jelenlegi",
"components.Settings.SettingsAbout.Releases.currentversion": "Jelenlegi verzió",
"components.Settings.RadarrModal.validationRootFolderRequired": "Ki kell választania egy root mappát",
"components.Settings.RadarrModal.validationProfileRequired": "Ki kell választania egy minőség profilt",
"components.Settings.RadarrModal.validationPortRequired": "Érvényes portszámot kell megadnia",
"components.Settings.RadarrModal.validationNameRequired": "Meg kell adnia a szerver nevét",
"components.Settings.RadarrModal.validationMinimumAvailabilityRequired": "Ki kell választania egy minimális elérhetőséget",
"components.Settings.RadarrModal.validationHostnameRequired": "Érvényes gazdagépnevet vagy IP-címet kell megadnia",
"components.Settings.RadarrModal.validationBaseUrlTrailingSlash": "Az URL alapja nem végződhet perjelre",
"components.Settings.RadarrModal.validationHostnameRequired": "Meg kell adnia egy hostnevet vagy IP-címet",
"components.Settings.RadarrModal.validationBaseUrlTrailingSlash": "Az alap URL-nek nem lehet vége perjel",
"components.Settings.RadarrModal.validationBaseUrlLeadingSlash": "Az alap URL-nek perjellel kell végződnie",
"components.Settings.RadarrModal.validationApplicationUrlTrailingSlash": "Az URL-nek nem lehet vége perjel",
"components.Settings.RadarrModal.validationApplicationUrl": "Érvényes URL-t kell megadnia",
@@ -666,7 +666,7 @@
"components.RequestList.RequestItem.requested": "Kérve",
"components.RequestList.RequestItem.modifieduserdate": "{date} {user} által",
"components.RequestList.RequestItem.modified": "Módosított",
"components.RequestList.RequestItem.mediaerror": "{mediaType} Nem található",
"components.RequestList.RequestItem.mediaerror": "A kéréshez társított címe már nem érhető el.",
"components.RequestList.RequestItem.editrequest": "Kérés szerkesztése",
"components.RequestList.RequestItem.deleterequest": "Kérés törlése",
"components.RequestList.RequestItem.cancelRequest": "Kérés visszavonása",
@@ -958,9 +958,9 @@
"i18n.resolved": "Megoldva",
"components.Settings.RadarrModal.inCinemas": "Mozikban",
"components.Settings.SettingsJobsCache.editJobSchedule": "Feladat Módosítása",
"components.Settings.SettingsJobsCache.editJobSchedulePrompt": "Új frekvencia",
"components.Settings.SettingsJobsCache.editJobSchedulePrompt": "Gyakoriság",
"components.RequestBlock.languageprofile": "Nyelv Profil",
"components.RequestModal.requestApproved": "Kérés <strong>{title}</strong> elfogadva!",
"components.RequestModal.requestApproved": "Kérés elfogadva: <strong>{title}</strong>",
"components.Settings.Notifications.NotificationsGotify.gotifysettingsfailed": "A Gotify értesítési beállításait nem sikerült elmenteni.",
"components.Settings.Notifications.NotificationsGotify.toastGotifyTestSuccess": "Gotify teszt értesítés elküldve!",
"components.Settings.Notifications.NotificationsGotify.token": "Alkalmazás Token",
@@ -1083,77 +1083,5 @@
"components.RequestCard.tvdbid": "TheTVDB azonosító",
"components.RequestList.RequestItem.tmdbid": "TMDB azonosító",
"components.RequestList.RequestItem.tvdbid": "TheTVDB azonosító",
"components.RequestModal.requestcollection4ktitle": "Gyűjtemény kérés 4K-ban",
"components.Discover.CreateSlider.editsuccess": "Szerkesztett csúszka és mentett felfedezés testreszabási beállítások.",
"components.Settings.SettingsJobsCache.imagecache": "Képgyorsítótár",
"components.Settings.SettingsJobsCache.imagecachesize": "Teljes gyorsítótár mérete",
"components.Discover.CreateSlider.slidernameplaceholder": "Csúszka neve",
"components.Discover.DiscoverTv.sortFirstAirDateAsc": "Első adás dátuma növekvő",
"components.Discover.DiscoverMovies.sortPopularityDesc": "Népszerűség csökkenő",
"components.Settings.SettingsJobsCache.image-cache-cleanup": "Képgyorsítótár tisztítása",
"i18n.restartRequired": "Újraindítás szükséges",
"components.Discover.DiscoverMovies.sortTmdbRatingAsc": "TMDB minősítés növekvő",
"components.Discover.FilterSlideover.clearfilters": "Aktív szűrők törlése",
"components.Discover.CreateSlider.searchStudios": "Stúdiók keresése…",
"components.Discover.DiscoverMovies.sortReleaseDateDesc": "Megjelenés dátuma csökkenő sorrendben",
"components.Discover.CreateSlider.providetmdbnetwork": "Adja meg a TMDB hálózati azonosítót",
"components.Settings.SettingsMain.applicationTitle": "Pályázat címe",
"components.Discover.CreateSlider.addfail": "Nem sikerült új csúszkát létrehozni.",
"components.Discover.DiscoverMovies.sortPopularityAsc": "Növekvő népszerűség",
"components.Discover.CreateSlider.needresults": "Legalább 1 eredménynek kell lennie.",
"components.Discover.CreateSlider.addcustomslider": "Egyéni csúszka létrehozása",
"components.Discover.DiscoverTv.sortPopularityAsc": "Növekvő népszerűség",
"components.Discover.CreateSlider.editSlider": "Csúszka szerkesztése",
"components.Discover.CreateSlider.validationDatarequired": "Meg kell adnia egy adatértéket.",
"components.Discover.DiscoverTv.sortFirstAirDateDesc": "Első adási dátum csökkenő",
"components.Discover.DiscoverTv.discovertv": "Sorozat",
"components.Discover.DiscoverSliderEdit.deletefail": "Nem sikerült törölni a csúszkát.",
"components.Discover.CreateSlider.providetmdbstudio": "Adja meg a TMDB Studio azonosítóját",
"components.Settings.SettingsMain.cacheImages": "Képgyorsítótár engedélyezése",
"components.Discover.DiscoverTv.sortPopularityDesc": "Népszerűség csökkenő",
"components.Discover.CreateSlider.searchGenres": "Műfajok keresése…",
"components.Discover.CreateSlider.editfail": "Nem sikerült szerkeszteni a csúszkát.",
"components.Discover.CreateSlider.starttyping": "Gépelés megkezdése a kereséshez.",
"components.Discover.FilterSlideover.filters": "Szűrők",
"components.Discover.DiscoverSliderEdit.enable": "Kapcsolja be a láthatóságot",
"components.Discover.CreateSlider.addSlider": "Csúszka hozzáadása",
"components.Settings.SettingsJobsCache.imagecachecount": "Képek gyorsítótárban",
"components.Discover.CreateSlider.providetmdbsearch": "Adjon meg egy keresési lekérdezést",
"components.Discover.CreateSlider.providetmdbkeywordid": "Adjon meg egy TMDB kulcsszóazonosítót",
"components.Discover.DiscoverMovieKeyword.keywordMovies": "{keywordTitle} Filmek",
"components.Discover.CreateSlider.validationTitlerequired": "Meg kell adnia a címet.",
"components.Discover.DiscoverMovies.sortReleaseDateAsc": "Kiadási dátum Növekvő sorrendben",
"components.UserProfile.UserSettings.UserNotificationSettings.sound": "Értesítési hang",
"components.Discover.CreateSlider.nooptions": "Nincs eredmény.",
"components.Discover.CreateSlider.searchKeywords": "Kulcsszavak keresése…",
"components.Discover.CreateSlider.addsuccess": "Új csúszkát hozott létre, és elmentette a felfedezés testreszabási beállításait.",
"components.Discover.DiscoverSliderEdit.deletesuccess": "A csúszka sikeresen törölve.",
"components.Discover.DiscoverMovies.discovermovies": "Filmek",
"components.Discover.CreateSlider.providetmdbgenreid": "Adjon meg egy TMDB műfajazonosítót",
"components.Discover.DiscoverSliderEdit.remove": "Távolítsa el",
"components.Discover.PlexWatchlistSlider.emptywatchlist": "A hozzáadott média a <PlexWatchlistSupportLink>Plex figyelőlistáján</PlexWatchlistSupportLink> elérhető.",
"components.Discover.DiscoverTvKeyword.keywordSeries": "{keywordTitle} Sorzoat",
"components.Discover.DiscoverMovies.sortTmdbRatingDesc": "TMDB Értékelés Csökkenő",
"components.Discover.DiscoverTv.sortTitleAsc": "Cím (A-Z) Növekvő",
"components.Discover.DiscoverTv.sortTitleDesc": "Cím (Z-A) Csökkenő",
"components.Discover.FilterSlideover.ratingText": "Értékelések {minValue} és {maxValue} között",
"components.Discover.FilterSlideover.tmdbuservotecount": "TMDB Felhasználók szavazat szám",
"components.Discover.FilterSlideover.releaseDate": "Megjelenési dátum",
"components.Discover.FilterSlideover.voteCount": "Értékelések száma {minValue} és {maxValue} között",
"components.Discover.FilterSlideover.originalLanguage": "Eredeti Nyelv",
"components.Discover.PlexWatchlistSlider.plexwatchlist": "A te Plex figyelőlistád",
"components.Discover.RecentlyAddedSlider.recentlyAdded": "Nemrég hozzáadott",
"components.Discover.moviegenres": "Film műfajok",
"components.Discover.DiscoverMovies.sortTitleAsc": "Cím (A-Z) Növekvő",
"components.Discover.DiscoverMovies.sortTitleDesc": "Cím (Z-A) Csökkenő",
"components.Discover.DiscoverTv.sortTmdbRatingAsc": "TMDB Értékelés Növekvő",
"components.Discover.DiscoverTv.sortTmdbRatingDesc": "TMDB Értékelés Csökkenő",
"components.Discover.FilterSlideover.firstAirDate": "Első adási dátum",
"components.Discover.FilterSlideover.genres": "Műfajok",
"components.Discover.FilterSlideover.keywords": "Kulcsszavak",
"components.Discover.FilterSlideover.runtime": "Músoridő",
"components.Discover.FilterSlideover.runtimeText": "{minValue}-{maxValue} percnyi műsoridő",
"components.Discover.FilterSlideover.studio": "Stúdió",
"components.Discover.FilterSlideover.tmdbuserscore": "TMDB Felhasználói Értékelés",
"components.Discover.FilterSlideover.streamingservices": "Streaming Szolgáltatók"
"components.RequestModal.requestcollection4ktitle": "Gyűjtemény kérés 4K-ban"
}

View File

@@ -1136,15 +1136,5 @@
"components.Settings.SettingsMain.toastSettingsSuccess": "Impostazioni salvate con successo!",
"components.UserProfile.plexwatchlist": "Plex Watchlist",
"components.TvDetails.seasonstitle": "Stagioni",
"components.UserProfile.UserSettings.UserGeneralSettings.plexwatchlistsyncseriestip": "Richiedi automaticamente le serie presenti nella tua <PlexWatchlistSupportLink>Plex Watchlist</PlexWatchlistSupportLink>",
"components.Discover.moviegenres": "Film per genere",
"components.RequestModal.requestseries4ktitle": "Richiedi serie in 4K",
"components.RequestModal.requestmovie4ktitle": "Richiedi film in 4K",
"components.Discover.FilterSlideover.tmdbuserscore": "Voto utenti TMDB",
"components.StatusBadge.openinarr": "Apri su {arr}",
"components.Settings.SonarrModal.animeSeriesType": "Tipo serie anime",
"components.Settings.SonarrModal.seriesType": "Tipo serie TV",
"components.RequestModal.requestmovietitle": "Richiedi film",
"components.Discover.FilterSlideover.tmdbuservotecount": "Numero voti utenti TMDB",
"components.RequestModal.requestseriestitle": "Richiedi serie"
"components.UserProfile.UserSettings.UserGeneralSettings.plexwatchlistsyncseriestip": "Richiedi automaticamente le serie presenti nella tua <PlexWatchlistSupportLink>Plex Watchlist</PlexWatchlistSupportLink>"
}

View File

@@ -1231,6 +1231,5 @@
"components.Discover.DiscoverMovies.activefilters": "{count, plural, one {# 선택한 필터} other {# 선택한 필터}}",
"components.QuotaSelector.seasons": "{count, plural, one {시즌} other {시즌}}",
"components.RequestModal.QuotaDisplay.requestsremaining": "{remaining, plural, =0 {없음} other {<strong>#</strong>개의}} {type} {remaining, plural, one {요청} other {요청}} 남음",
"components.Settings.SettingsJobsCache.editJobScheduleSelectorHours": "매 {jobScheduleHours, plural, one {시간} other {{jobScheduleHours} 시간}}",
"components.MovieDetails.imdbuserscore": "IMDB 사용자 점수"
"components.Settings.SettingsJobsCache.editJobScheduleSelectorHours": "매 {jobScheduleHours, plural, one {시간} other {{jobScheduleHours} 시간}}"
}

View File

@@ -1224,19 +1224,5 @@
"components.Settings.SettingsJobsCache.editJobScheduleSelectorSeconds": "Elke {jobScheduleSeconds, plural, one {seconde} other {{jobScheduleSeconds} seconden}}",
"components.Settings.SettingsJobsCache.availability-sync": "Synchronisatie van mediabeschikbaarheid",
"components.Discover.tmdbmoviestreamingservices": "Streamingdiensten voor films TMDB",
"components.Discover.tmdbtvstreamingservices": "Streamingdiensten voor series TMDB",
"components.Discover.FilterSlideover.tmdbuservotecount": "Aantal stemmen TMDB-gebruikers",
"components.Discover.FilterSlideover.voteCount": "Aantal stemmen tussen {minValue} en {maxValue}",
"components.Settings.RadarrModal.tagRequests": "Tagverzoeken",
"components.Settings.RadarrModal.tagRequestsInfo": "Voeg automatisch een extra tag toe met de gebruikers-ID en weergavenaam van de aanvrager",
"components.MovieDetails.imdbuserscore": "Gebruikersscore IMDB",
"components.Settings.SonarrModal.tagRequests": "Tagverzoeken",
"components.Settings.SonarrModal.tagRequestsInfo": "Voeg automatisch een extra tag toe met de gebruikers-ID en weergavenaam van de aanvrager",
"i18n.collection": "Collectie",
"components.Settings.Notifications.NotificationsPushover.sound": "Meldingsgeluid",
"components.UserProfile.UserSettings.UserNotificationSettings.deviceDefault": "Apparaatstandaard",
"components.Settings.SonarrModal.animeSeriesType": "Serietype anime",
"components.Settings.SonarrModal.seriesType": "Serietype",
"components.UserProfile.UserSettings.UserNotificationSettings.sound": "Meldingsgeluid",
"components.Settings.Notifications.NotificationsPushover.deviceDefault": "Apparaatstandaard"
"components.Discover.tmdbtvstreamingservices": "Streamingdiensten voor series TMDB"
}

View File

@@ -1220,8 +1220,5 @@
"components.Discover.updatefailed": "Wystąpił problem podczas aktualizowania ustawień odnajdywania.",
"components.Settings.SettingsMain.apikey": "Klucz API",
"components.Settings.SettingsMain.cacheImagesTip": "Pamięć podręczna dla obrazów pochodzących z zewnętrznych źródeł (wymaga znacznej ilości miejsca na dysku)",
"components.Settings.SettingsMain.toastSettingsFailure": "Coś poszło nie tak podczas zapisywania ustawień.",
"i18n.collection": "Kolekcja",
"components.MovieDetails.imdbuserscore": "Ocena użytkowników IMDB",
"components.Settings.SonarrModal.seriesType": "Typ serialu"
"components.Settings.SettingsMain.toastSettingsFailure": "Coś poszło nie tak podczas zapisywania ustawień."
}

View File

@@ -1227,10 +1227,5 @@
"components.Discover.tmdbmoviestreamingservices": "Serviços de Streaming de Filmes do TMDB",
"components.Discover.FilterSlideover.tmdbuservotecount": "Qtd de Votos de Usuários TMDB",
"components.Discover.FilterSlideover.voteCount": "Qtd the votos entre {minValue} e {maxValue}",
"components.Settings.RadarrModal.tagRequestsInfo": "Adicione automaticamente uma tag extra com o ID de usuário e o nome de exibição do solicitante",
"i18n.collection": "Coleção",
"components.MovieDetails.imdbuserscore": "Pontuação de usuário IMDB",
"components.Settings.SonarrModal.tagRequestsInfo": "Adiciona automaticamente uma tag adicional com o ID de usuário e nome de exibição de quem pediu",
"components.Settings.SonarrModal.tagRequests": "Marcar Pedidos",
"components.Settings.RadarrModal.tagRequests": "Marcar Pedidos"
"components.Settings.RadarrModal.tagRequestsInfo": "Adicione automaticamente uma tag extra com o ID de usuário e o nome de exibição do solicitante"
}

View File

@@ -370,7 +370,7 @@
"components.PlexLoginButton.signinwithplex": "Conectat",
"components.QuotaSelector.movieRequests": "{quotaLimit} <quotaUnits>{movies} per {quotaDays} {days}</quotaUnits>",
"components.PersonDetails.lifespan": "{birthdate} {deathdate}",
"components.RequestBlock.seasons": "{seasonCount, plural, one {Sezon} other {Sezoane}}",
"components.RequestBlock.seasons": "{seasonCount, plural, un {Season} alte {Seasons}}",
"components.PermissionEdit.requestMoviesDescription": "Acordați permisiunea de a trimite solicitări pentru filme non-4K.",
"components.PermissionEdit.viewissuesDescription": "Acordați permisiunea de a vizualiza problemele media raportate de alți utilizatori.",
"components.PermissionEdit.viewwatchlistsDescription": "Acordați permisiunea de a vizualiza listele de urmărire Plex ale altor utilizatori.",
@@ -403,62 +403,8 @@
"components.RequestBlock.approve": "Aprobă Solicitarea",
"components.RequestBlock.decline": "Respinge Solicitarea",
"components.RequestBlock.requestedby": "Solicitat de",
"components.RequestButton.approve4krequests": "Aprobă {requestCount, plural, one {Cerere 4K} other {{requestCount} Cereri 4K}}",
"components.RequestButton.approve4krequests": "Aprobă {requestCount, plural, o {4K Request} alte {{requestCount} 4K Requests}}",
"components.RequestBlock.lastmodifiedby": "Ultima Dată Modificat de",
"components.RequestBlock.profilechanged": "Profil Calitate",
"components.RequestBlock.requestdate": "Dată Solicitare",
"components.RequestButton.approverequests": "Aprobă {requestCount, plural, one {Cerere} other {{requestCount} Cereri}}",
"components.RequestButton.requestmore4k": "Cere mai mult în 4K",
"components.RequestButton.approverequest4k": "Aproba Cereri 4K",
"components.RequestCard.tmdbid": "ID TMDB",
"components.RequestCard.failedretry": "A apărut o eroare la reîncercarea solicitării.",
"components.RequestButton.declinerequest": "Respinge Cerere",
"components.RequestCard.seasons": "{seasonCount, plural, one {Sezon} other {Sezoane}}",
"components.RequestCard.declinerequest": "Respinge Cererea",
"components.RequestButton.viewrequest": "Vezi Cerere",
"components.RequestButton.declinerequests": "Respinge {requestCount, plural, one {Cererea} other {{requestCount} Cererile}}",
"components.RequestCard.mediaerror": "{mediaType} Nu a fost găsit",
"components.RequestCard.editrequest": "Editează Cererea",
"components.RequestButton.viewrequest4k": "Vezi Cerere 4K",
"components.RequestButton.decline4krequests": "Respinge {requestCount, plural, one {Cererea 4K} other {{requestCount} Cererile 4K}}",
"components.RequestButton.declinerequest4k": "Respinge Cerere 4K",
"components.RequestCard.approverequest": "Aprobă Cererea",
"components.RequestButton.approverequest": "Cereri Aprobate",
"components.RequestCard.deleterequest": "Șterge Cererea",
"components.RequestCard.unknowntitle": "Titlu necunoscut",
"components.RequestList.RequestItem.cancelRequest": "Anulează Cerere",
"components.RequestCard.tvdbid": "ID TheTVDB",
"components.RequestButton.requestmore": "Cere mai mult",
"components.RequestList.RequestItem.deleterequest": "Șterge Cerere",
"components.RequestCard.cancelrequest": "Anulează Cererea",
"components.RequestModal.AdvancedRequester.folder": "{path} ({space})",
"components.RequestList.RequestItem.modified": "Modificat",
"components.RequestList.RequestItem.editrequest": "Editează Cererea",
"components.RequestModal.AdvancedRequester.qualityprofile": "Profil de Calitate",
"components.RequestList.requests": "Cereri",
"components.RequestModal.AdvancedRequester.advancedoptions": "Avansat",
"components.RequestModal.AdvancedRequester.notagoptions": "Fără etichete.",
"components.RequestList.RequestItem.modifieduserdate": "{date} de {user}",
"components.RequestModal.AdvancedRequester.requestas": "Cere ca",
"components.RequestList.showallrequests": "Afișează toate cererile",
"components.RequestList.RequestItem.tmdbid": "ID-ul TMDB",
"components.RequestList.RequestItem.requesteddate": "Solicitat",
"components.RequestModal.QuotaDisplay.movie": "film",
"components.RequestList.RequestItem.failedretry": "Ceva a mers greșit în timpul reîncercării cererii.",
"components.RequestList.RequestItem.unknowntitle": "Titlu Necunoscut",
"components.RequestModal.AdvancedRequester.destinationserver": "Server Destinație",
"components.RequestModal.AdvancedRequester.rootfolder": "Folder Rădăcină",
"components.RequestList.sortAdded": "Cele Mai Recente",
"components.RequestModal.AdvancedRequester.tags": "Etichete",
"components.RequestList.RequestItem.mediaerror": "{mediaType} nu a fost găsit",
"components.RequestList.sortModified": "Ultima Modificată",
"components.RequestList.RequestItem.tvdbid": "ID-ul TheTVDB",
"components.RequestModal.AdvancedRequester.selecttags": "Selectați Etichetele",
"components.RequestList.RequestItem.requested": "Solicitat",
"components.RequestModal.QuotaDisplay.notenoughseasonrequests": "Nu sunt suficiente cereri de sezon rămase",
"components.RequestModal.AdvancedRequester.default": "{name} (Implicit)",
"components.RequestModal.AdvancedRequester.languageprofile": "Profil de Limbă",
"components.RequestModal.QuotaDisplay.allowedRequestsUser": "Acest utilizator are voie sa ceara <strong>{limit}</strong> {type} la fiecare <strong>{days}</strong> zile.",
"components.RequestList.RequestItem.seasons": "",
"components.RequestModal.QuotaDisplay.allowedRequests": "Aveți voie să cereți <strong>{limit}</strong> de {type} la fiecare <strong>{days}</strong> zile."
"components.RequestBlock.requestdate": "Dată Solicitare"
}

View File

@@ -164,7 +164,7 @@
"pages.oops": "Упс",
"pages.returnHome": "Вернуться домой",
"components.CollectionDetails.overview": "Обзор",
"components.CollectionDetails.numberofmovies": "{count} {count, plural, one {фильм} few {фильма} other {фильмов}}",
"components.CollectionDetails.numberofmovies": "{count} фильмов",
"components.CollectionDetails.requestcollection": "Запросить Коллекцию",
"components.Login.email": "Адрес электронной почты",
"components.UserList.users": "Пользователи",
@@ -983,7 +983,7 @@
"components.Discover.CreateSlider.addcustomslider": "Создать слайдер",
"components.Discover.CreateSlider.nooptions": "Нет результатов.",
"components.Discover.CreateSlider.providetmdbgenreid": "Введите TMDB ID жанра",
"components.Discover.CreateSlider.needresults": "Должен быть хотя бы 1 результат.",
"components.Discover.CreateSlider.needresults": "Должен быть хотя-бы 1 результат.",
"components.Discover.CreateSlider.providetmdbkeywordid": "Введите TMDB ID ключевого слова",
"components.Discover.CreateSlider.providetmdbnetwork": "Введите TMDB ID сети",
"components.Discover.CreateSlider.providetmdbsearch": "Введите поисковой запрос",
@@ -992,7 +992,7 @@
"components.Discover.CreateSlider.searchKeywords": "Поиск ключевых слов…",
"components.Discover.CreateSlider.searchStudios": "Поиск студий…",
"components.Discover.CreateSlider.slidernameplaceholder": "Название слайдера",
"components.Discover.CreateSlider.starttyping": "Начните писать для поиска.",
"components.Discover.CreateSlider.starttyping": "Начините писать для поиска.",
"components.Discover.CreateSlider.validationDatarequired": "Вы должны ввести дату.",
"components.Discover.CreateSlider.validationTitlerequired": "Вы должны ввести заголовок.",
"components.Discover.DiscoverMovieKeyword.keywordMovies": "Фильмы по ключевому слову \"{keywordTitle}\"",
@@ -1024,7 +1024,7 @@
"components.Discover.DiscoverSliderEdit.deletesuccess": "Слайдер успешно удален.",
"components.Discover.DiscoverSliderEdit.enable": "Изменить видимость",
"components.Discover.DiscoverSliderEdit.remove": "Удалить",
"components.Discover.DiscoverTv.activefilters": "{count, plural, one {# Активен фильтр} other {# Активные фильтры }}",
"components.Discover.DiscoverTv.activefilters": "{count, plural, one {# Активный фильтр} other {# Активные фильтры}}",
"components.Discover.DiscoverTv.discovertv": "Сериалы",
"components.Discover.DiscoverTv.sortPopularityAsc": "Популярность по возрастанию",
"components.Discover.DiscoverTv.sortPopularityDesc": "Популярность по убыванию",
@@ -1221,22 +1221,5 @@
"components.Selector.showless": "Показать меньше",
"components.Selector.showmore": "Показать больше",
"components.Settings.SettingsJobsCache.imagecachesize": "Размер кэша",
"components.Settings.validationUrlBaseLeadingSlash": "Базовый URL должен начинаться с косой черты",
"components.Discover.FilterSlideover.tmdbuservotecount": "Количество голосов пользователей TMDB",
"components.Discover.FilterSlideover.voteCount": "Количество голосов между {minValue} и {maxValue}",
"components.Discover.tmdbmoviestreamingservices": "Стриминговые сервисы фильмов TMDB",
"components.Discover.tmdbtvstreamingservices": "Стриминговые сервисы сериалов TMDB",
"components.Settings.RadarrModal.tagRequestsInfo": "Автоматически добавлять дополнительный тег с ID и именем запросившего пользователя",
"components.Settings.RadarrModal.tagRequests": "Теги запросов",
"components.MovieDetails.imdbuserscore": "Оценка пользователей IMDB",
"components.Settings.SettingsJobsCache.availability-sync": "Синхронизация доступности медиа",
"components.Settings.SonarrModal.tagRequests": "Теги запросов",
"components.Settings.SonarrModal.tagRequestsInfo": "Автоматически добавлять тег с ID и именем запросившего пользователя",
"i18n.collection": "Коллекция",
"components.Settings.Notifications.NotificationsPushover.sound": "Звук уведомления",
"components.UserProfile.UserSettings.UserNotificationSettings.deviceDefault": "Устройство по умолчанию",
"components.Settings.SonarrModal.animeSeriesType": "Тип аниме-сериала",
"components.Settings.SonarrModal.seriesType": "Тип сериала",
"components.UserProfile.UserSettings.UserNotificationSettings.sound": "Звук уведомления",
"components.Settings.Notifications.NotificationsPushover.deviceDefault": "Устройство по умолчанию"
"components.Settings.validationUrlBaseLeadingSlash": "Базовый URL должен начинаться с косой черты"
}

View File

@@ -438,7 +438,7 @@
"components.Settings.Notifications.NotificationsWebhook.webhookUrl": "Webhook adresa",
"components.ManageSlideOver.manageModalAdvanced": "Napredno",
"components.ManageSlideOver.manageModalMedia": "Mediji",
"components.Settings.Notifications.NotificationsWebhook.customJson": "JSON zadržaj",
"components.Settings.Notifications.NotificationsWebhook.customJson": "JSON Payload",
"components.Settings.SettingsLogs.label": "Labela",
"components.Settings.SettingsLogs.level": "Ozbiljnost",
"components.Settings.SettingsAbout.Releases.versionChangelog": "{version} Dnevnik promena",

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -281,7 +281,7 @@
"components.CollectionDetails.requestcollection": "提交系列请求",
"components.CollectionDetails.overview": "简介",
"components.CollectionDetails.numberofmovies": "{count} 部电影",
"components.AppDataWarning.dockerVolumeMissingDescription": "卷(<code>{appDataPath}</code>)挂载配置不正确,当容器终止或重启时所有数据都会被清除。",
"components.AppDataWarning.dockerVolumeMissingDescription": "必须使用链接挂载bind mount指定某个宿主机器的文件夹跟容器内的 <code>{appDataPath}</code> 文件夹连通,才能保存 Overseerr 的配置和数据。",
"components.PersonDetails.ascharacter": "饰演 {character}",
"pages.somethingwentwrong": "出了点问题",
"pages.serviceunavailable": "服务器无法使用",
@@ -1174,7 +1174,7 @@
"components.Discover.PlexWatchlistSlider.emptywatchlist": "您的 <PlexWatchlistSupportLink>Plex 关注列表</PlexWatchlistSupportLink>中的媒体会显示在这里。",
"components.Selector.starttyping": "开始打字以进行搜索。",
"components.Discover.CreateSlider.starttyping": "开始打字以进行搜索。",
"components.Discover.CreateSlider.needresults": "需要至少有 1 个结果。",
"components.Discover.CreateSlider.needresults": "需要至少有 1 个结果。",
"components.Selector.showless": "显示更少",
"components.Discover.resetfailed": "重置探索媒体设置时出了点问题。",
"components.Settings.SettingsMain.validationApplicationTitle": "你必须提供一个应用程序标题",
@@ -1231,12 +1231,5 @@
"components.Settings.SonarrModal.tagRequests": "标记请求",
"i18n.collection": "合集",
"components.Discover.FilterSlideover.tmdbuservotecount": "TMDB 用户评分数",
"components.Settings.SonarrModal.tagRequestsInfo": "自动添加带有请求者的用户 ID 和显示名称的附加标签",
"components.MovieDetails.imdbuserscore": "IMDB 用户评分",
"components.Settings.Notifications.NotificationsPushover.sound": "通知提示音",
"components.UserProfile.UserSettings.UserNotificationSettings.deviceDefault": "默认设备",
"components.Settings.SonarrModal.animeSeriesType": "动漫剧集类型",
"components.Settings.SonarrModal.seriesType": "剧集类型",
"components.UserProfile.UserSettings.UserNotificationSettings.sound": "通知提示音",
"components.Settings.Notifications.NotificationsPushover.deviceDefault": "默认设备"
"components.Settings.SonarrModal.tagRequestsInfo": "自动添加带有请求者的用户 ID 和显示名称的附加标签"
}

View File

@@ -28,8 +28,6 @@ const loadLocaleData = (locale: AvailableLocale): Promise<any> => {
switch (locale) {
case 'ar':
return import('../i18n/locale/ar.json');
case 'bg':
return import('../i18n/locale/bg.json');
case 'ca':
return import('../i18n/locale/ca.json');
case 'cs':
@@ -42,14 +40,8 @@ const loadLocaleData = (locale: AvailableLocale): Promise<any> => {
return import('../i18n/locale/el.json');
case 'es':
return import('../i18n/locale/es.json');
case 'fi':
return import('../i18n/locale/fi.json');
case 'fr':
return import('../i18n/locale/fr.json');
case 'he':
return import('../i18n/locale/he.json');
case 'hi':
return import('../i18n/locale/hi.json');
case 'hr':
return import('../i18n/locale/hr.json');
case 'hu':
@@ -72,8 +64,6 @@ const loadLocaleData = (locale: AvailableLocale): Promise<any> => {
return import('../i18n/locale/pt_BR.json');
case 'pt-PT':
return import('../i18n/locale/pt_PT.json');
case 'ro':
return import('../i18n/locale/ro.json');
case 'ru':
return import('../i18n/locale/ru.json');
case 'sq':
@@ -82,8 +72,6 @@ const loadLocaleData = (locale: AvailableLocale): Promise<any> => {
return import('../i18n/locale/sr.json');
case 'sv':
return import('../i18n/locale/sv.json');
case 'uk':
return import('../i18n/locale/uk.json');
case 'zh-CN':
return import('../i18n/locale/zh_Hans.json');
case 'zh-TW':

103
yarn.lock
View File

@@ -10,13 +10,14 @@
"@jridgewell/gen-mapping" "^0.1.0"
"@jridgewell/trace-mapping" "^0.3.9"
"@apidevtools/json-schema-ref-parser@^11.7.0":
version "11.7.2"
resolved "https://registry.yarnpkg.com/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.7.2.tgz#cdf3e0aded21492364a70e193b45b7cf4177f031"
integrity sha512-4gY54eEGEstClvEkGnwVkTkrx0sqwemEFG5OSRRn3tD91XH0+Q8XIkYIfo7IwEWPpJZwILb9GUXeShtplRc/eA==
"@apidevtools/json-schema-ref-parser@9.0.9":
version "9.0.9"
resolved "https://registry.yarnpkg.com/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.0.9.tgz#d720f9256e3609621280584f2b47ae165359268b"
integrity sha512-GBD2Le9w2+lVFoc4vswGI/TjkNIZSVp7+9xPf+X3uidBfWnAeUWmquteSyt0+VCrhNMWj/FTABISQrD3Z/YA+w==
dependencies:
"@jsdevtools/ono" "^7.1.3"
"@types/json-schema" "^7.0.15"
"@types/json-schema" "^7.0.6"
call-me-maybe "^1.0.1"
js-yaml "^4.1.0"
"@babel/cli@7.21.0":
@@ -3845,16 +3846,11 @@
resolved "https://registry.yarnpkg.com/@types/html-to-text/-/html-to-text-8.1.1.tgz#0c5573207c14f618f24da5a2910c510285573094"
integrity sha512-QFcqfc7TiVbvIX8Fc2kWUxakruI1Ay6uitaGCYHzI5M0WHQROV5D2XeSaVrK0FmvssivXum4yERVnJsiuH61Ww==
"@types/json-schema@*", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9":
"@types/json-schema@*", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.6", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9":
version "7.0.11"
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3"
integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==
"@types/json-schema@^7.0.15":
version "7.0.15"
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841"
integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==
"@types/json-stable-stringify@^1.0.32":
version "1.0.34"
resolved "https://registry.yarnpkg.com/@types/json-stable-stringify/-/json-stable-stringify-1.0.34.tgz#c0fb25e4d957e0ee2e497c1f553d7f8bb668fd75"
@@ -3897,10 +3893,10 @@
resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.31.tgz#31b7ca6407128a3d2bbc27fe2d21b345397f6197"
integrity sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==
"@types/multer@^1.4.12":
version "1.4.12"
resolved "https://registry.yarnpkg.com/@types/multer/-/multer-1.4.12.tgz#da67bd0c809f3a63fe097c458c0d4af1fea50ab7"
integrity sha512-pQ2hoqvXiJt2FP9WQVLPRO+AmiIm/ZYkavPlIQnx282u4ZrVdztx0pkh3jjpQt0Kz+YI0YhSG264y08UJKoUQg==
"@types/multer@^1.4.7":
version "1.4.7"
resolved "https://registry.yarnpkg.com/@types/multer/-/multer-1.4.7.tgz#89cf03547c28c7bbcc726f029e2a76a7232cc79e"
integrity sha512-/SNsDidUFCvqqcWDwxv2feww/yqhNeTRL5CVoL3jU4Goc4kKEL10T7Eye65ZqPNi4HRx8sAEX59pV1aEH7drNA==
dependencies:
"@types/express" "*"
@@ -4323,11 +4319,6 @@ aggregate-error@^3.0.0:
clean-stack "^2.0.0"
indent-string "^4.0.0"
ajv-draft-04@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz#3b64761b268ba0b9e668f0b41ba53fce0ad77fc8"
integrity sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==
ajv-formats@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-2.1.1.tgz#6e669400659eb74973bbf2e33327180a0996b520"
@@ -4347,7 +4338,7 @@ ajv-keywords@^5.0.0:
dependencies:
fast-deep-equal "^3.1.3"
ajv@^6.10.0, ajv@^6.12.3, ajv@^6.12.4, ajv@^6.12.5:
ajv@^6.10.0, ajv@^6.12.3, ajv@^6.12.4, ajv@^6.12.5, ajv@^6.12.6:
version "6.12.6"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
@@ -4367,16 +4358,6 @@ ajv@^8.0.0, ajv@^8.11.0, ajv@^8.8.0:
require-from-string "^2.0.2"
uri-js "^4.2.2"
ajv@^8.17.1:
version "8.17.1"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.17.1.tgz#37d9a5c776af6bc92d7f4f9510eba4c0a60d11a6"
integrity sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==
dependencies:
fast-deep-equal "^3.1.3"
fast-uri "^3.0.1"
json-schema-traverse "^1.0.0"
require-from-string "^2.0.2"
ansi-colors@^4.1.1:
version "4.1.3"
resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.3.tgz#37611340eb2243e70cc604cad35d63270d48781b"
@@ -5091,6 +5072,11 @@ call-bind@^1.0.0, call-bind@^1.0.2:
function-bind "^1.1.1"
get-intrinsic "^1.0.2"
call-me-maybe@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/call-me-maybe/-/call-me-maybe-1.0.2.tgz#03f964f19522ba643b1b0693acb9152fe2074baa"
integrity sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==
callsites@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73"
@@ -5603,12 +5589,7 @@ content-disposition@0.5.4:
dependencies:
safe-buffer "5.2.1"
content-type@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918"
integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==
content-type@~1.0.4:
content-type@^1.0.4, content-type@~1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b"
integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==
@@ -6979,24 +6960,23 @@ expand-tilde@^2.0.0, expand-tilde@^2.0.2:
dependencies:
homedir-polyfill "^1.0.1"
express-openapi-validator@5.3.9:
version "5.3.9"
resolved "https://registry.yarnpkg.com/express-openapi-validator/-/express-openapi-validator-5.3.9.tgz#6f1ced9e4db2709e418c720b19f8023e7a984dea"
integrity sha512-I6MNpbOyJbp8iML7lrmke1cVVLgdH4w9mfSX7qSZRDizqO9CKnjQYXNyu2YqrEqD/3OxFsWuSdzL+1Q8qwAhCQ==
express-openapi-validator@4.13.8:
version "4.13.8"
resolved "https://registry.yarnpkg.com/express-openapi-validator/-/express-openapi-validator-4.13.8.tgz#805e0106b9f4afbb73ea28f42026e844dfa94dae"
integrity sha512-89/sdkq+BKBuIyykaMl/vR9grFc3WFUPTjFo0THHbu+5g+q8rA7fKeoMfz+h84yOQIBcztmJ5ZJdk5uhEls31A==
dependencies:
"@apidevtools/json-schema-ref-parser" "^11.7.0"
"@types/multer" "^1.4.12"
ajv "^8.17.1"
ajv-draft-04 "^1.0.0"
ajv-formats "^2.1.1"
content-type "^1.0.5"
json-schema-traverse "^1.0.0"
"@types/multer" "^1.4.7"
ajv "^6.12.6"
content-type "^1.0.4"
json-schema-ref-parser "^9.0.9"
lodash.clonedeep "^4.5.0"
lodash.get "^4.4.2"
lodash.uniq "^4.5.0"
lodash.zipobject "^4.1.3"
media-typer "^1.1.0"
multer "^1.4.5-lts.1"
ono "^7.1.3"
path-to-regexp "^8.1.0"
path-to-regexp "^6.2.0"
express-rate-limit@6.7.0:
version "6.7.0"
@@ -7148,11 +7128,6 @@ fast-printf@^1.6.9:
dependencies:
boolean "^3.1.4"
fast-uri@^3.0.1:
version "3.0.3"
resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.0.3.tgz#892a1c91802d5d7860de728f18608a0573142241"
integrity sha512-aLrHthzCjH5He4Z2H9YZ+v6Ujb9ocRuW6ZzkJQOrTxleEijANq4v1TsaPaVG1PZcuurEzrLcWRyYBYXD5cEiaw==
fastest-levenshtein@^1.0.12:
version "1.0.16"
resolved "https://registry.yarnpkg.com/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz#210e61b6ff181de91ea9b3d1b84fdedd47e034e5"
@@ -8682,6 +8657,13 @@ json-parse-even-better-errors@^2.3.0, json-parse-even-better-errors@^2.3.1:
resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d"
integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==
json-schema-ref-parser@^9.0.9:
version "9.0.9"
resolved "https://registry.yarnpkg.com/json-schema-ref-parser/-/json-schema-ref-parser-9.0.9.tgz#66ea538e7450b12af342fa3d5b8458bc1e1e013f"
integrity sha512-qcP2lmGy+JUoQJ4DOQeLaZDqH9qSkeGCK3suKWxJXS82dg728Mn3j97azDMaOUmJAN4uCq91LdPx4K7E8F1a7Q==
dependencies:
"@apidevtools/json-schema-ref-parser" "9.0.9"
json-schema-traverse@^0.4.1:
version "0.4.1"
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
@@ -9253,6 +9235,11 @@ lodash.upperfirst@^4.3.1:
resolved "https://registry.yarnpkg.com/lodash.upperfirst/-/lodash.upperfirst-4.3.1.tgz#1365edf431480481ef0d1c68957a5ed99d49f7ce"
integrity sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg==
lodash.zipobject@^4.1.3:
version "4.1.3"
resolved "https://registry.yarnpkg.com/lodash.zipobject/-/lodash.zipobject-4.1.3.tgz#b399f5aba8ff62a746f6979bf20b214f964dbef8"
integrity sha512-A9SzX4hMKWS25MyalwcOnNoplyHbkNVsjidhTp8ru0Sj23wY9GWBKS8gAIGDSAqeWjIjvE4KBEl24XXAs+v4wQ==
lodash@4.17.21, lodash@^4.0.0, lodash@^4.17.11, lodash@^4.17.15, lodash@^4.17.21, lodash@^4.17.4:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
@@ -11025,10 +11012,10 @@ path-to-regexp@0.1.7:
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==
path-to-regexp@^8.1.0:
version "8.2.0"
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-8.2.0.tgz#73990cc29e57a3ff2a0d914095156df5db79e8b4"
integrity sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==
path-to-regexp@^6.2.0:
version "6.2.1"
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-6.2.1.tgz#d54934d6798eb9e5ef14e7af7962c945906918e5"
integrity sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==
path-type@^4.0.0:
version "4.0.0"