Merge remote-tracking branch 'overseerr/develop' into develop

This commit is contained in:
notfakie
2022-09-12 19:17:01 +12:00
19 changed files with 624 additions and 192 deletions

View File

@@ -0,0 +1,25 @@
describe('Pull To Refresh', () => {
beforeEach(() => {
cy.login(Cypress.env('ADMIN_EMAIL'), Cypress.env('ADMIN_PASSWORD'));
cy.viewport(390, 844);
cy.visitMobile('/');
});
it('reloads the current page', () => {
cy.wait(500);
cy.intercept({
method: 'GET',
url: '/api/v1/*',
}).as('apiCall');
cy.get('.searchbar').swipe('bottom', [190, 400]);
cy.wait('@apiCall').then((interception) => {
assert.isNotNull(
interception.response.body,
'API was called and received data'
);
});
});
});

View File

@@ -1,4 +1,5 @@
/// <reference types="cypress" /> /// <reference types="cypress" />
import 'cy-mobile-commands';
Cypress.Commands.add('login', (email, password) => { Cypress.Commands.add('login', (email, password) => {
cy.session( cy.session(

View File

@@ -2731,6 +2731,12 @@ paths:
nullable: true nullable: true
enum: [debug, info, warn, error] enum: [debug, info, warn, error]
default: debug default: debug
- in: query
name: search
schema:
type: string
nullable: true
example: plex
responses: responses:
'200': '200':
description: Server log returned description: Server log returned

View File

@@ -47,6 +47,7 @@
"cookie-parser": "1.4.6", "cookie-parser": "1.4.6",
"copy-to-clipboard": "3.3.2", "copy-to-clipboard": "3.3.2",
"country-flag-icons": "1.5.5", "country-flag-icons": "1.5.5",
"cronstrue": "2.11.0",
"csurf": "1.11.0", "csurf": "1.11.0",
"date-fns": "2.29.1", "date-fns": "2.29.1",
"email-templates": "9.0.0", "email-templates": "9.0.0",
@@ -67,6 +68,7 @@
"openpgp": "5.4.0", "openpgp": "5.4.0",
"plex-api": "5.3.2", "plex-api": "5.3.2",
"pug": "3.0.2", "pug": "3.0.2",
"pulltorefreshjs": "0.1.22",
"react": "18.2.0", "react": "18.2.0",
"react-ace": "10.1.0", "react-ace": "10.1.0",
"react-animate-height": "2.1.2", "react-animate-height": "2.1.2",
@@ -116,6 +118,7 @@
"@types/node": "17.0.36", "@types/node": "17.0.36",
"@types/node-schedule": "2.1.0", "@types/node-schedule": "2.1.0",
"@types/nodemailer": "6.4.5", "@types/nodemailer": "6.4.5",
"@types/pulltorefreshjs": "0.1.5",
"@types/react": "18.0.17", "@types/react": "18.0.17",
"@types/react-dom": "18.0.6", "@types/react-dom": "18.0.6",
"@types/react-transition-group": "4.4.5", "@types/react-transition-group": "4.4.5",
@@ -133,6 +136,7 @@
"babel-plugin-react-intl-auto": "3.3.0", "babel-plugin-react-intl-auto": "3.3.0",
"commitizen": "4.2.5", "commitizen": "4.2.5",
"copyfiles": "2.4.1", "copyfiles": "2.4.1",
"cy-mobile-commands": "0.3.0",
"cypress": "10.6.0", "cypress": "10.6.0",
"cz-conventional-changelog": "3.3.0", "cz-conventional-changelog": "3.3.0",
"eslint": "8.22.0", "eslint": "8.22.0",

View File

@@ -16,6 +16,7 @@ interface ScheduledJob {
name: string; name: string;
type: 'process' | 'command'; type: 'process' | 'command';
interval: 'short' | 'long' | 'fixed'; interval: 'short' | 'long' | 'fixed';
cronSchedule: string;
running?: () => boolean; running?: () => boolean;
cancelFn?: () => void; cancelFn?: () => void;
} }
@@ -33,6 +34,7 @@ export const startJobs = (): void => {
name: 'Plex Recently Added Scan', name: 'Plex Recently Added Scan',
type: 'process', type: 'process',
interval: 'short', interval: 'short',
cronSchedule: jobs['plex-recently-added-scan'].schedule,
job: schedule.scheduleJob( job: schedule.scheduleJob(
jobs['plex-recently-added-scan'].schedule, jobs['plex-recently-added-scan'].schedule,
() => { () => {
@@ -52,6 +54,7 @@ export const startJobs = (): void => {
name: 'Plex Full Library Scan', name: 'Plex Full Library Scan',
type: 'process', type: 'process',
interval: 'long', interval: 'long',
cronSchedule: jobs['plex-full-scan'].schedule,
job: schedule.scheduleJob(jobs['plex-full-scan'].schedule, () => { job: schedule.scheduleJob(jobs['plex-full-scan'].schedule, () => {
logger.info('Starting scheduled job: Plex Full Library Scan', { logger.info('Starting scheduled job: Plex Full Library Scan', {
label: 'Jobs', label: 'Jobs',
@@ -71,6 +74,7 @@ export const startJobs = (): void => {
name: 'Jellyfin Recently Added Sync', name: 'Jellyfin Recently Added Sync',
type: 'process', type: 'process',
interval: 'long', interval: 'long',
cronSchedule: jobs['jellyfin-recently-added-sync'].schedule,
job: schedule.scheduleJob( job: schedule.scheduleJob(
jobs['jellyfin-recently-added-sync'].schedule, jobs['jellyfin-recently-added-sync'].schedule,
() => { () => {
@@ -90,6 +94,7 @@ export const startJobs = (): void => {
name: 'Jellyfin Full Library Sync', name: 'Jellyfin Full Library Sync',
type: 'process', type: 'process',
interval: 'long', interval: 'long',
cronSchedule: jobs['jellyfin-full-sync'].schedule,
job: schedule.scheduleJob(jobs['jellyfin-full-sync'].schedule, () => { job: schedule.scheduleJob(jobs['jellyfin-full-sync'].schedule, () => {
logger.info('Starting scheduled job: Jellyfin Full Sync', { logger.info('Starting scheduled job: Jellyfin Full Sync', {
label: 'Jobs', label: 'Jobs',
@@ -107,6 +112,7 @@ export const startJobs = (): void => {
name: 'Plex Watchlist Sync', name: 'Plex Watchlist Sync',
type: 'process', type: 'process',
interval: 'long', interval: 'long',
cronSchedule: jobs['plex-watchlist-sync'].schedule,
job: schedule.scheduleJob(jobs['plex-watchlist-sync'].schedule, () => { job: schedule.scheduleJob(jobs['plex-watchlist-sync'].schedule, () => {
logger.info('Starting scheduled job: Plex Watchlist Sync', { logger.info('Starting scheduled job: Plex Watchlist Sync', {
label: 'Jobs', label: 'Jobs',
@@ -121,6 +127,7 @@ export const startJobs = (): void => {
name: 'Radarr Scan', name: 'Radarr Scan',
type: 'process', type: 'process',
interval: 'long', interval: 'long',
cronSchedule: jobs['radarr-scan'].schedule,
job: schedule.scheduleJob(jobs['radarr-scan'].schedule, () => { job: schedule.scheduleJob(jobs['radarr-scan'].schedule, () => {
logger.info('Starting scheduled job: Radarr Scan', { label: 'Jobs' }); logger.info('Starting scheduled job: Radarr Scan', { label: 'Jobs' });
radarrScanner.run(); radarrScanner.run();
@@ -135,6 +142,7 @@ export const startJobs = (): void => {
name: 'Sonarr Scan', name: 'Sonarr Scan',
type: 'process', type: 'process',
interval: 'long', interval: 'long',
cronSchedule: jobs['sonarr-scan'].schedule,
job: schedule.scheduleJob(jobs['sonarr-scan'].schedule, () => { job: schedule.scheduleJob(jobs['sonarr-scan'].schedule, () => {
logger.info('Starting scheduled job: Sonarr Scan', { label: 'Jobs' }); logger.info('Starting scheduled job: Sonarr Scan', { label: 'Jobs' });
sonarrScanner.run(); sonarrScanner.run();
@@ -149,6 +157,7 @@ export const startJobs = (): void => {
name: 'Download Sync', name: 'Download Sync',
type: 'command', type: 'command',
interval: 'fixed', interval: 'fixed',
cronSchedule: jobs['download-sync'].schedule,
job: schedule.scheduleJob(jobs['download-sync'].schedule, () => { job: schedule.scheduleJob(jobs['download-sync'].schedule, () => {
logger.debug('Starting scheduled job: Download Sync', { logger.debug('Starting scheduled job: Download Sync', {
label: 'Jobs', label: 'Jobs',
@@ -163,6 +172,7 @@ export const startJobs = (): void => {
name: 'Download Sync Reset', name: 'Download Sync Reset',
type: 'command', type: 'command',
interval: 'long', interval: 'long',
cronSchedule: jobs['download-sync-reset'].schedule,
job: schedule.scheduleJob(jobs['download-sync-reset'].schedule, () => { job: schedule.scheduleJob(jobs['download-sync-reset'].schedule, () => {
logger.info('Starting scheduled job: Download Sync Reset', { logger.info('Starting scheduled job: Download Sync Reset', {
label: 'Jobs', label: 'Jobs',

View File

@@ -27,7 +27,7 @@ import { getAppVersion } from '@server/utils/appVersion';
import { Router } from 'express'; import { Router } from 'express';
import rateLimit from 'express-rate-limit'; import rateLimit from 'express-rate-limit';
import fs from 'fs'; import fs from 'fs';
import { merge, omit, set, sortBy } from 'lodash'; import { escapeRegExp, merge, omit, set, sortBy } from 'lodash';
import { rescheduleJob } from 'node-schedule'; import { rescheduleJob } from 'node-schedule';
import path from 'path'; import path from 'path';
import semver from 'semver'; import semver from 'semver';
@@ -454,6 +454,8 @@ settingsRoutes.get(
(req, res, next) => { (req, res, next) => {
const pageSize = req.query.take ? Number(req.query.take) : 25; const pageSize = req.query.take ? Number(req.query.take) : 25;
const skip = req.query.skip ? Number(req.query.skip) : 0; const skip = req.query.skip ? Number(req.query.skip) : 0;
const search = (req.query.search as string) ?? '';
const searchRegexp = new RegExp(escapeRegExp(search), 'i');
let filter: string[] = []; let filter: string[] = [];
switch (req.query.filter) { switch (req.query.filter) {
@@ -485,6 +487,22 @@ settingsRoutes.get(
'data', 'data',
]; ];
const deepValueStrings = (obj: Record<string, unknown>): string[] => {
const values = [];
for (const val of Object.values(obj)) {
if (typeof val === 'string') {
values.push(val);
} else if (typeof val === 'number') {
values.push(val.toString());
} else if (val !== null && typeof val === 'object') {
values.push(...deepValueStrings(val as Record<string, unknown>));
}
}
return values;
};
try { try {
fs.readFileSync(logFile, 'utf-8') fs.readFileSync(logFile, 'utf-8')
.split('\n') .split('\n')
@@ -509,6 +527,19 @@ settingsRoutes.get(
}); });
} }
if (req.query.search) {
if (
// label and data are sometimes undefined
!searchRegexp.test(logMessage.label ?? '') &&
!searchRegexp.test(logMessage.message) &&
!deepValueStrings(logMessage.data ?? {}).some((val) =>
searchRegexp.test(val)
)
) {
return;
}
}
logs.push(logMessage); logs.push(logMessage);
}); });
@@ -543,6 +574,7 @@ settingsRoutes.get('/jobs', (_req, res) => {
name: job.name, name: job.name,
type: job.type, type: job.type,
interval: job.interval, interval: job.interval,
cronSchedule: job.cronSchedule,
nextExecutionTime: job.job.nextInvocation(), nextExecutionTime: job.job.nextInvocation(),
running: job.running ? job.running() : false, running: job.running ? job.running() : false,
})) }))
@@ -563,6 +595,7 @@ settingsRoutes.post<{ jobId: string }>('/jobs/:jobId/run', (req, res, next) => {
name: scheduledJob.name, name: scheduledJob.name,
type: scheduledJob.type, type: scheduledJob.type,
interval: scheduledJob.interval, interval: scheduledJob.interval,
cronSchedule: scheduledJob.cronSchedule,
nextExecutionTime: scheduledJob.job.nextInvocation(), nextExecutionTime: scheduledJob.job.nextInvocation(),
running: scheduledJob.running ? scheduledJob.running() : false, running: scheduledJob.running ? scheduledJob.running() : false,
}); });
@@ -588,6 +621,7 @@ settingsRoutes.post<{ jobId: string }>(
name: scheduledJob.name, name: scheduledJob.name,
type: scheduledJob.type, type: scheduledJob.type,
interval: scheduledJob.interval, interval: scheduledJob.interval,
cronSchedule: scheduledJob.cronSchedule,
nextExecutionTime: scheduledJob.job.nextInvocation(), nextExecutionTime: scheduledJob.job.nextInvocation(),
running: scheduledJob.running ? scheduledJob.running() : false, running: scheduledJob.running ? scheduledJob.running() : false,
}); });
@@ -612,11 +646,14 @@ settingsRoutes.post<{ jobId: string }>(
settings.jobs[scheduledJob.id].schedule = req.body.schedule; settings.jobs[scheduledJob.id].schedule = req.body.schedule;
settings.save(); settings.save();
scheduledJob.cronSchedule = req.body.schedule;
return res.status(200).json({ return res.status(200).json({
id: scheduledJob.id, id: scheduledJob.id,
name: scheduledJob.name, name: scheduledJob.name,
type: scheduledJob.type, type: scheduledJob.type,
interval: scheduledJob.interval, interval: scheduledJob.interval,
cronSchedule: scheduledJob.cronSchedule,
nextExecutionTime: scheduledJob.job.nextInvocation(), nextExecutionTime: scheduledJob.job.nextInvocation(),
running: scheduledJob.running ? scheduledJob.running() : false, running: scheduledJob.running ? scheduledJob.running() : false,
}); });

View File

@@ -1,6 +1,7 @@
import SearchInput from '@app/components/Layout/SearchInput'; import SearchInput from '@app/components/Layout/SearchInput';
import Sidebar from '@app/components/Layout/Sidebar'; import Sidebar from '@app/components/Layout/Sidebar';
import UserDropdown from '@app/components/Layout/UserDropdown'; import UserDropdown from '@app/components/Layout/UserDropdown';
import PullToRefresh from '@app/components/PullToRefresh';
import type { AvailableLocale } from '@app/context/LanguageContext'; import type { AvailableLocale } from '@app/context/LanguageContext';
import useLocale from '@app/hooks/useLocale'; import useLocale from '@app/hooks/useLocale';
import useSettings from '@app/hooks/useSettings'; import useSettings from '@app/hooks/useSettings';
@@ -58,6 +59,7 @@ const Layout = ({ children }: LayoutProps) => {
<Sidebar open={isSidebarOpen} setClosed={() => setSidebarOpen(false)} /> <Sidebar open={isSidebarOpen} setClosed={() => setSidebarOpen(false)} />
<div className="relative mb-16 flex w-0 min-w-0 flex-1 flex-col lg:ml-64"> <div className="relative mb-16 flex w-0 min-w-0 flex-1 flex-col lg:ml-64">
<PullToRefresh />
<div <div
className={`searchbar fixed left-0 right-0 top-0 z-10 flex flex-shrink-0 bg-opacity-80 transition duration-300 ${ className={`searchbar fixed left-0 right-0 top-0 z-10 flex flex-shrink-0 bg-opacity-80 transition duration-300 ${
isScrolled ? 'bg-gray-700' : 'bg-transparent' isScrolled ? 'bg-gray-700' : 'bg-transparent'

View File

@@ -11,6 +11,7 @@ import { ServerIcon, ViewListIcon } from '@heroicons/react/outline';
import { CheckCircleIcon, DocumentRemoveIcon } from '@heroicons/react/solid'; import { CheckCircleIcon, DocumentRemoveIcon } from '@heroicons/react/solid';
import { IssueStatus } from '@server/constants/issue'; import { IssueStatus } from '@server/constants/issue';
import { MediaRequestStatus, MediaStatus } from '@server/constants/media'; import { MediaRequestStatus, MediaStatus } from '@server/constants/media';
import { MediaServerType } from '@server/constants/server';
import type { MediaWatchDataResponse } from '@server/interfaces/api/mediaInterfaces'; import type { MediaWatchDataResponse } from '@server/interfaces/api/mediaInterfaces';
import type { MovieDetails } from '@server/models/Movie'; import type { MovieDetails } from '@server/models/Movie';
import type { TvDetails } from '@server/models/Tv'; import type { TvDetails } from '@server/models/Tv';

View File

@@ -0,0 +1,36 @@
import { RefreshIcon } from '@heroicons/react/outline';
import Router from 'next/router';
import PR from 'pulltorefreshjs';
import { useEffect } from 'react';
import ReactDOMServer from 'react-dom/server';
const PullToRefresh: React.FC = () => {
useEffect(() => {
PR.init({
mainElement: '#pull-to-refresh',
onRefresh() {
Router.reload();
},
iconArrow: ReactDOMServer.renderToString(
<RefreshIcon className="z-50 m-auto h-9 w-9 rounded-full border-4 border-gray-800 bg-gray-800 text-indigo-500 ring-1 ring-gray-700" />
),
iconRefreshing: ReactDOMServer.renderToString(
<RefreshIcon
className="z-50 m-auto h-9 w-9 animate-spin rounded-full border-4 border-gray-800 bg-gray-800 text-indigo-500 ring-1 ring-gray-700"
style={{ animationDirection: 'reverse' }}
/>
),
instructionsPullToRefresh: ReactDOMServer.renderToString(<div />),
instructionsReleaseToRefresh: ReactDOMServer.renderToString(<div />),
instructionsRefreshing: ReactDOMServer.renderToString(<div />),
distReload: 55,
});
return () => {
PR.destroyAll();
};
}, []);
return <div id="pull-to-refresh"></div>;
};
export default PullToRefresh;

View File

@@ -5,6 +5,7 @@ import LoadingSpinner from '@app/components/Common/LoadingSpinner';
import Modal from '@app/components/Common/Modal'; import Modal from '@app/components/Common/Modal';
import PageTitle from '@app/components/Common/PageTitle'; import PageTitle from '@app/components/Common/PageTitle';
import Table from '@app/components/Common/Table'; import Table from '@app/components/Common/Table';
import useLocale from '@app/hooks/useLocale';
import useSettings from '@app/hooks/useSettings'; import useSettings from '@app/hooks/useSettings';
import globalMessages from '@app/i18n/globalMessages'; import globalMessages from '@app/i18n/globalMessages';
import { formatBytes } from '@app/utils/numberHelpers'; import { formatBytes } from '@app/utils/numberHelpers';
@@ -15,7 +16,8 @@ import { MediaServerType } from '@server/constants/server';
import type { CacheItem } from '@server/interfaces/api/settingsInterfaces'; import type { CacheItem } from '@server/interfaces/api/settingsInterfaces';
import type { JobId } from '@server/lib/settings'; import type { JobId } from '@server/lib/settings';
import axios from 'axios'; import axios from 'axios';
import { Fragment, useState } from 'react'; import cronstrue from 'cronstrue/i18n';
import { Fragment, useReducer, useState } from 'react';
import type { MessageDescriptor } from 'react-intl'; import type { MessageDescriptor } from 'react-intl';
import { defineMessages, FormattedRelativeTime, useIntl } from 'react-intl'; import { defineMessages, FormattedRelativeTime, useIntl } from 'react-intl';
import { useToasts } from 'react-toast-notifications'; import { useToasts } from 'react-toast-notifications';
@@ -59,7 +61,8 @@ const messages: { [messageName: string]: MessageDescriptor } = defineMessages({
editJobSchedule: 'Modify Job', editJobSchedule: 'Modify Job',
jobScheduleEditSaved: 'Job edited successfully!', jobScheduleEditSaved: 'Job edited successfully!',
jobScheduleEditFailed: 'Something went wrong while saving the job.', jobScheduleEditFailed: 'Something went wrong while saving the job.',
editJobSchedulePrompt: 'Frequency', editJobScheduleCurrent: 'Current Frequency',
editJobSchedulePrompt: 'New Frequency',
editJobScheduleSelectorHours: editJobScheduleSelectorHours:
'Every {jobScheduleHours, plural, one {hour} other {{jobScheduleHours} hours}}', 'Every {jobScheduleHours, plural, one {hour} other {{jobScheduleHours} hours}}',
editJobScheduleSelectorMinutes: editJobScheduleSelectorMinutes:
@@ -71,12 +74,56 @@ interface Job {
name: string; name: string;
type: 'process' | 'command'; type: 'process' | 'command';
interval: 'short' | 'long' | 'fixed'; interval: 'short' | 'long' | 'fixed';
cronSchedule: string;
nextExecutionTime: string; nextExecutionTime: string;
running: boolean; running: boolean;
} }
type JobModalState = {
isOpen?: boolean;
job?: Job;
scheduleHours: number;
scheduleMinutes: number;
};
type JobModalAction =
| { type: 'set'; hours?: number; minutes?: number }
| {
type: 'close';
}
| { type: 'open'; job?: Job };
const jobModalReducer = (
state: JobModalState,
action: JobModalAction
): JobModalState => {
switch (action.type) {
case 'close':
return {
...state,
isOpen: false,
};
case 'open':
return {
isOpen: true,
job: action.job,
scheduleHours: 1,
scheduleMinutes: 5,
};
case 'set':
return {
...state,
scheduleHours: action.hours ?? state.scheduleHours,
scheduleMinutes: action.minutes ?? state.scheduleMinutes,
};
}
};
const SettingsJobs = () => { const SettingsJobs = () => {
const intl = useIntl(); const intl = useIntl();
const { locale } = useLocale();
const { addToast } = useToasts(); const { addToast } = useToasts();
const { const {
data, data,
@@ -92,15 +139,12 @@ const SettingsJobs = () => {
} }
); );
const [jobEditModal, setJobEditModal] = useState<{ const [jobModalState, dispatch] = useReducer(jobModalReducer, {
isOpen: boolean;
job?: Job;
}>({
isOpen: false, isOpen: false,
scheduleHours: 1,
scheduleMinutes: 5,
}); });
const [isSaving, setIsSaving] = useState(false); const [isSaving, setIsSaving] = useState(false);
const [jobScheduleMinutes, setJobScheduleMinutes] = useState(5);
const [jobScheduleHours, setJobScheduleHours] = useState(1);
const settings = useSettings(); const settings = useSettings();
if (!data && !error) { if (!data && !error) {
@@ -151,10 +195,10 @@ const SettingsJobs = () => {
const jobScheduleCron = ['0', '0', '*', '*', '*', '*']; const jobScheduleCron = ['0', '0', '*', '*', '*', '*'];
try { try {
if (jobEditModal.job?.interval === 'short') { if (jobModalState.job?.interval === 'short') {
jobScheduleCron[1] = `*/${jobScheduleMinutes}`; jobScheduleCron[1] = `*/${jobModalState.scheduleMinutes}`;
} else if (jobEditModal.job?.interval === 'long') { } else if (jobModalState.job?.interval === 'long') {
jobScheduleCron[2] = `*/${jobScheduleHours}`; jobScheduleCron[2] = `*/${jobModalState.scheduleHours}`;
} else { } else {
// jobs with interval: fixed should not be editable // jobs with interval: fixed should not be editable
throw new Error(); throw new Error();
@@ -162,16 +206,18 @@ const SettingsJobs = () => {
setIsSaving(true); setIsSaving(true);
await axios.post( await axios.post(
`/api/v1/settings/jobs/${jobEditModal.job?.id}/schedule`, `/api/v1/settings/jobs/${jobModalState.job.id}/schedule`,
{ {
schedule: jobScheduleCron.join(' '), schedule: jobScheduleCron.join(' '),
} }
); );
addToast(intl.formatMessage(messages.jobScheduleEditSaved), { addToast(intl.formatMessage(messages.jobScheduleEditSaved), {
appearance: 'success', appearance: 'success',
autoDismiss: true, autoDismiss: true,
}); });
setJobEditModal({ isOpen: false });
dispatch({ type: 'close' });
revalidate(); revalidate();
} catch (e) { } catch (e) {
addToast(intl.formatMessage(messages.jobScheduleEditFailed), { addToast(intl.formatMessage(messages.jobScheduleEditFailed), {
@@ -199,7 +245,7 @@ const SettingsJobs = () => {
leave="opacity-100 transition duration-300" leave="opacity-100 transition duration-300"
leaveFrom="opacity-100" leaveFrom="opacity-100"
leaveTo="opacity-0" leaveTo="opacity-0"
show={jobEditModal.isOpen} show={jobModalState.isOpen}
> >
<Modal <Modal
title={intl.formatMessage(messages.editJobSchedule)} title={intl.formatMessage(messages.editJobSchedule)}
@@ -208,24 +254,43 @@ const SettingsJobs = () => {
? intl.formatMessage(globalMessages.saving) ? intl.formatMessage(globalMessages.saving)
: intl.formatMessage(globalMessages.save) : intl.formatMessage(globalMessages.save)
} }
onCancel={() => setJobEditModal({ isOpen: false })} onCancel={() => dispatch({ type: 'close' })}
okDisabled={isSaving} okDisabled={isSaving}
onOk={() => scheduleJob()} onOk={() => scheduleJob()}
> >
<div className="section"> <div className="section">
<form> <form className="mb-6">
<div className="form-row pb-6"> <div className="form-row">
<label className="text-label">
{intl.formatMessage(messages.editJobScheduleCurrent)}
</label>
<div className="form-input-area mt-2 mb-1">
<div>
{jobModalState.job &&
cronstrue.toString(jobModalState.job.cronSchedule, {
locale,
})}
</div>
<div className="text-sm text-gray-500">
{jobModalState.job?.cronSchedule}
</div>
</div>
</div>
<div className="form-row">
<label htmlFor="jobSchedule" className="text-label"> <label htmlFor="jobSchedule" className="text-label">
{intl.formatMessage(messages.editJobSchedulePrompt)} {intl.formatMessage(messages.editJobSchedulePrompt)}
</label> </label>
<div className="form-input-area"> <div className="form-input-area">
{jobEditModal.job?.interval === 'short' ? ( {jobModalState.job?.interval === 'short' ? (
<select <select
name="jobScheduleMinutes" name="jobScheduleMinutes"
className="inline" className="inline"
value={jobScheduleMinutes} value={jobModalState.scheduleMinutes}
onChange={(e) => onChange={(e) =>
setJobScheduleMinutes(Number(e.target.value)) dispatch({
type: 'set',
minutes: Number(e.target.value),
})
} }
> >
{[5, 10, 15, 20, 30, 60].map((v) => ( {[5, 10, 15, 20, 30, 60].map((v) => (
@@ -243,9 +308,12 @@ const SettingsJobs = () => {
<select <select
name="jobScheduleHours" name="jobScheduleHours"
className="inline" className="inline"
value={jobScheduleHours} value={jobModalState.scheduleHours}
onChange={(e) => onChange={(e) =>
setJobScheduleHours(Number(e.target.value)) dispatch({
type: 'set',
hours: Number(e.target.value),
})
} }
> >
{[1, 2, 3, 4, 6, 8, 12, 24, 48, 72].map((v) => ( {[1, 2, 3, 4, 6, 8, 12, 24, 48, 72].map((v) => (
@@ -324,9 +392,7 @@ const SettingsJobs = () => {
<Button <Button
className="mr-2" className="mr-2"
buttonType="warning" buttonType="warning"
onClick={() => onClick={() => dispatch({ type: 'open', job })}
setJobEditModal({ isOpen: true, job: job })
}
> >
<PencilIcon /> <PencilIcon />
<span>{intl.formatMessage(globalMessages.edit)}</span> <span>{intl.formatMessage(globalMessages.edit)}</span>

View File

@@ -5,6 +5,7 @@ import Modal from '@app/components/Common/Modal';
import PageTitle from '@app/components/Common/PageTitle'; import PageTitle from '@app/components/Common/PageTitle';
import Table from '@app/components/Common/Table'; import Table from '@app/components/Common/Table';
import Tooltip from '@app/components/Common/Tooltip'; import Tooltip from '@app/components/Common/Tooltip';
import useDebouncedState from '@app/hooks/useDebouncedState';
import { useUpdateQueryParams } from '@app/hooks/useUpdateQueryParams'; import { useUpdateQueryParams } from '@app/hooks/useUpdateQueryParams';
import globalMessages from '@app/i18n/globalMessages'; import globalMessages from '@app/i18n/globalMessages';
import Error from '@app/pages/_error'; import Error from '@app/pages/_error';
@@ -17,6 +18,7 @@ import {
FilterIcon, FilterIcon,
PauseIcon, PauseIcon,
PlayIcon, PlayIcon,
SearchIcon,
} from '@heroicons/react/solid'; } from '@heroicons/react/solid';
import type { import type {
LogMessage, LogMessage,
@@ -59,6 +61,8 @@ const SettingsLogs = () => {
const { addToast } = useToasts(); const { addToast } = useToasts();
const [currentFilter, setCurrentFilter] = useState<Filter>('debug'); const [currentFilter, setCurrentFilter] = useState<Filter>('debug');
const [currentPageSize, setCurrentPageSize] = useState(25); const [currentPageSize, setCurrentPageSize] = useState(25);
const [searchFilter, debouncedSearchFilter, setSearchFilter] =
useDebouncedState('');
const [refreshInterval, setRefreshInterval] = useState(5000); const [refreshInterval, setRefreshInterval] = useState(5000);
const [activeLog, setActiveLog] = useState<{ const [activeLog, setActiveLog] = useState<{
isOpen: boolean; isOpen: boolean;
@@ -76,7 +80,9 @@ const SettingsLogs = () => {
const { data, error } = useSWR<LogsResultsResponse>( const { data, error } = useSWR<LogsResultsResponse>(
`/api/v1/settings/logs?take=${currentPageSize}&skip=${ `/api/v1/settings/logs?take=${currentPageSize}&skip=${
pageIndex * currentPageSize pageIndex * currentPageSize
}&filter=${currentFilter}`, }&filter=${currentFilter}${
debouncedSearchFilter ? `&search=${debouncedSearchFilter}` : ''
}`,
{ {
refreshInterval: refreshInterval, refreshInterval: refreshInterval,
revalidateOnFocus: false, revalidateOnFocus: false,
@@ -118,15 +124,13 @@ const SettingsLogs = () => {
}); });
}; };
if (!data && !error) { // check if there's no data and no errors in the table
return <LoadingSpinner />; // so as to show a spinner inside the table and not refresh the whole component
} if (!data && error) {
if (!data) {
return <Error statusCode={500} />; return <Error statusCode={500} />;
} }
const hasNextPage = data.pageInfo.pages > pageIndex + 1; const hasNextPage = data?.pageInfo.pages ?? 0 > pageIndex + 1;
const hasPrevPage = pageIndex > 0; const hasPrevPage = pageIndex > 0;
return ( return (
@@ -245,10 +249,21 @@ const SettingsLogs = () => {
appDataPath: appData ? appData.appDataPath : '/app/config', appDataPath: appData ? appData.appDataPath : '/app/config',
})} })}
</p> </p>
<div className="mt-2 flex flex-grow flex-row sm:flex-grow-0 sm:justify-end"> <div className="mt-2 flex flex-grow flex-col sm:flex-grow-0 sm:flex-row sm:justify-end">
<div className="mb-2 flex flex-grow sm:mb-0 sm:mr-2 md:flex-grow-0">
<span className="inline-flex cursor-default items-center rounded-l-md border border-r-0 border-gray-500 bg-gray-800 px-3 text-sm text-gray-100">
<SearchIcon className="h-6 w-6" />
</span>
<input
type="text"
className="rounded-r-only"
value={searchFilter}
onChange={(e) => setSearchFilter(e.target.value as string)}
/>
</div>
<div className="mb-2 flex flex-1 flex-row justify-between sm:mb-0 sm:flex-none"> <div className="mb-2 flex flex-1 flex-row justify-between sm:mb-0 sm:flex-none">
<Button <Button
className="mr-2 w-full flex-grow" className="mr-2 flex flex-grow"
buttonType={refreshInterval ? 'default' : 'primary'} buttonType={refreshInterval ? 'default' : 'primary'}
onClick={() => toggleLogs()} onClick={() => toggleLogs()}
> >
@@ -259,34 +274,34 @@ const SettingsLogs = () => {
)} )}
</span> </span>
</Button> </Button>
</div> <div className="flex flex-grow">
<div className="mb-2 flex flex-1 sm:mb-0 sm:flex-none"> <span className="inline-flex cursor-default items-center rounded-l-md border border-r-0 border-gray-500 bg-gray-800 px-3 text-sm text-gray-100">
<span className="inline-flex cursor-default items-center rounded-l-md border border-r-0 border-gray-500 bg-gray-800 px-3 text-sm text-gray-100"> <FilterIcon className="h-6 w-6" />
<FilterIcon className="h-6 w-6" /> </span>
</span> <select
<select id="filter"
id="filter" name="filter"
name="filter" onChange={(e) => {
onChange={(e) => { setCurrentFilter(e.target.value as Filter);
setCurrentFilter(e.target.value as Filter); router.push(router.pathname);
router.push(router.pathname); }}
}} value={currentFilter}
value={currentFilter} className="rounded-r-only"
className="rounded-r-only" >
> <option value="debug">
<option value="debug"> {intl.formatMessage(messages.filterDebug)}
{intl.formatMessage(messages.filterDebug)} </option>
</option> <option value="info">
<option value="info"> {intl.formatMessage(messages.filterInfo)}
{intl.formatMessage(messages.filterInfo)} </option>
</option> <option value="warn">
<option value="warn"> {intl.formatMessage(messages.filterWarn)}
{intl.formatMessage(messages.filterWarn)} </option>
</option> <option value="error">
<option value="error"> {intl.formatMessage(messages.filterError)}
{intl.formatMessage(messages.filterError)} </option>
</option> </select>
</select> </div>
</div> </div>
</div> </div>
<Table> <Table>
@@ -300,73 +315,81 @@ const SettingsLogs = () => {
</tr> </tr>
</thead> </thead>
<Table.TBody> <Table.TBody>
{data.results.map((row: LogMessage, index: number) => { {!data ? (
return ( <tr>
<tr key={`log-list-${index}`}> <Table.TD colSpan={5} noPadding>
<Table.TD className="text-gray-300"> <LoadingSpinner />
{intl.formatDate(row.timestamp, { </Table.TD>
year: 'numeric', </tr>
month: 'short', ) : (
day: '2-digit', data.results.map((row: LogMessage, index: number) => {
hour: 'numeric', return (
minute: 'numeric', <tr key={`log-list-${index}`}>
second: 'numeric', <Table.TD className="text-gray-300">
})} {intl.formatDate(row.timestamp, {
</Table.TD> year: 'numeric',
<Table.TD className="text-gray-300"> month: 'short',
<Badge day: '2-digit',
badgeType={ hour: 'numeric',
row.level === 'error' minute: 'numeric',
? 'danger' second: 'numeric',
: row.level === 'warn' })}
? 'warning' </Table.TD>
: row.level === 'info' <Table.TD className="text-gray-300">
? 'success' <Badge
: 'default' badgeType={
} row.level === 'error'
> ? 'danger'
{row.level.toUpperCase()} : row.level === 'warn'
</Badge> ? 'warning'
</Table.TD> : row.level === 'info'
<Table.TD className="text-gray-300"> ? 'success'
{row.label ?? ''} : 'default'
</Table.TD> }
<Table.TD className="text-gray-300">{row.message}</Table.TD> >
<Table.TD className="-m-1 flex flex-wrap items-center justify-end"> {row.level.toUpperCase()}
{row.data && ( </Badge>
</Table.TD>
<Table.TD className="text-gray-300">
{row.label ?? ''}
</Table.TD>
<Table.TD className="text-gray-300">{row.message}</Table.TD>
<Table.TD className="-m-1 flex flex-wrap items-center justify-end">
{row.data && (
<Tooltip
content={intl.formatMessage(messages.viewdetails)}
>
<Button
buttonSize="sm"
buttonType="primary"
onClick={() =>
setActiveLog({ log: row, isOpen: true })
}
className="m-1"
>
<DocumentSearchIcon className="icon-md" />
</Button>
</Tooltip>
)}
<Tooltip <Tooltip
content={intl.formatMessage(messages.viewdetails)} content={intl.formatMessage(messages.copyToClipboard)}
> >
<Button <Button
buttonType="primary" buttonType="primary"
buttonSize="sm" buttonSize="sm"
onClick={() => onClick={() => copyLogString(row)}
setActiveLog({ log: row, isOpen: true })
}
className="m-1" className="m-1"
> >
<DocumentSearchIcon className="icon-md" /> <ClipboardCopyIcon className="icon-md" />
</Button> </Button>
</Tooltip> </Tooltip>
)} </Table.TD>
<Tooltip </tr>
content={intl.formatMessage(messages.copyToClipboard)} );
> })
<Button )}
buttonType="primary"
buttonSize="sm"
onClick={() => copyLogString(row)}
className="m-1"
>
<ClipboardCopyIcon className="icon-md" />
</Button>
</Tooltip>
</Table.TD>
</tr>
);
})}
{data.results.length === 0 && ( {data?.results.length === 0 && (
<tr className="relative h-24 p-2 text-white"> <tr className="relative h-24 p-2 text-white">
<Table.TD colSpan={5} noPadding> <Table.TD colSpan={5} noPadding>
<div className="flex w-screen flex-col items-center justify-center p-6 md:w-full"> <div className="flex w-screen flex-col items-center justify-center p-6 md:w-full">
@@ -396,15 +419,15 @@ const SettingsLogs = () => {
> >
<div className="hidden lg:flex lg:flex-1"> <div className="hidden lg:flex lg:flex-1">
<p className="text-sm"> <p className="text-sm">
{data.results.length > 0 && {(data?.results.length ?? 0) > 0 &&
intl.formatMessage(globalMessages.showingresults, { intl.formatMessage(globalMessages.showingresults, {
from: pageIndex * currentPageSize + 1, from: pageIndex * currentPageSize + 1,
to: to:
data.results.length < currentPageSize data?.results.length ?? 0 < currentPageSize
? pageIndex * currentPageSize + ? pageIndex * currentPageSize +
data.results.length (data?.results.length ?? 0)
: (pageIndex + 1) * currentPageSize, : (pageIndex + 1) * currentPageSize,
total: data.pageInfo.results, total: data?.pageInfo.results ?? 0,
strong: (msg: React.ReactNode) => ( strong: (msg: React.ReactNode) => (
<span className="font-medium">{msg}</span> <span className="font-medium">{msg}</span>
), ),

View File

@@ -26,9 +26,9 @@
"components.Settings.SettingsUsers.userSettings": "Uživatelské nastavení", "components.Settings.SettingsUsers.userSettings": "Uživatelské nastavení",
"components.Settings.SettingsUsers.defaultPermissions": "Výchozí oprávnění", "components.Settings.SettingsUsers.defaultPermissions": "Výchozí oprávnění",
"components.Settings.SettingsJobsCache.unknownJob": "Neznámá úloha", "components.Settings.SettingsJobsCache.unknownJob": "Neznámá úloha",
"components.Settings.SettingsJobsCache.sonarr-scan": "Sonarr Sken", "components.Settings.SettingsJobsCache.sonarr-scan": "Sonarr sken",
"components.Settings.SettingsJobsCache.runnow": "Spustit nyní", "components.Settings.SettingsJobsCache.runnow": "Spustit nyní",
"components.Settings.SettingsJobsCache.radarr-scan": "Radarr Sken", "components.Settings.SettingsJobsCache.radarr-scan": "Radarr sken",
"components.Settings.SettingsJobsCache.jobstarted": "{jobname} zahájeno.", "components.Settings.SettingsJobsCache.jobstarted": "{jobname} zahájeno.",
"components.Settings.SettingsJobsCache.jobname": "Název úlohy", "components.Settings.SettingsJobsCache.jobname": "Název úlohy",
"components.Settings.SettingsJobsCache.jobcancelled": "{jobname} zrušeno.", "components.Settings.SettingsJobsCache.jobcancelled": "{jobname} zrušeno.",
@@ -39,12 +39,12 @@
"components.Settings.SettingsAbout.totalrequests": "Celkový počet žádostí", "components.Settings.SettingsAbout.totalrequests": "Celkový počet žádostí",
"components.Settings.SettingsAbout.totalmedia": "Celkový počet médií", "components.Settings.SettingsAbout.totalmedia": "Celkový počet médií",
"components.Settings.SettingsAbout.timezone": "Časové pásmo", "components.Settings.SettingsAbout.timezone": "Časové pásmo",
"components.Settings.SettingsAbout.supportoverseerr": "Podpořte Jellyseerr", "components.Settings.SettingsAbout.supportoverseerr": "Podpořte Overseerr",
"components.Settings.SettingsAbout.overseerrinformation": "Jellyseerr Informace", "components.Settings.SettingsAbout.overseerrinformation": "O Jellyseerr",
"components.Settings.SettingsAbout.githubdiscussions": "Diskuze na GitHubu", "components.Settings.SettingsAbout.githubdiscussions": "Diskuze na GitHubu",
"components.Settings.SettingsAbout.Releases.viewchangelog": "Zobrazit seznam změn", "components.Settings.SettingsAbout.Releases.viewchangelog": "Zobrazit seznam změn",
"components.Settings.SettingsAbout.Releases.versionChangelog": "Seznam změn", "components.Settings.SettingsAbout.Releases.versionChangelog": "{version} Seznam změn",
"components.Settings.SettingsAbout.Releases.currentversion": "Aktuální verze", "components.Settings.SettingsAbout.Releases.currentversion": "Aktuální",
"components.Settings.RadarrModal.syncEnabled": "Povolit skenování", "components.Settings.RadarrModal.syncEnabled": "Povolit skenování",
"components.Settings.RadarrModal.ssl": "Použít SSL", "components.Settings.RadarrModal.ssl": "Použít SSL",
"components.Settings.RadarrModal.servername": "Název serveru", "components.Settings.RadarrModal.servername": "Název serveru",
@@ -85,7 +85,7 @@
"components.Settings.Notifications.NotificationsLunaSea.webhookUrl": "Webhook URL", "components.Settings.Notifications.NotificationsLunaSea.webhookUrl": "Webhook URL",
"components.Settings.Notifications.NotificationsLunaSea.profileName": "Jméno profilu", "components.Settings.Notifications.NotificationsLunaSea.profileName": "Jméno profilu",
"components.Settings.Notifications.NotificationsLunaSea.agentenabled": "Povolit agenta", "components.Settings.Notifications.NotificationsLunaSea.agentenabled": "Povolit agenta",
"components.Search.searchresults": "Výsledek vyhledávání", "components.Search.searchresults": "Výsledky vyhledávání",
"components.ResetPassword.passwordreset": "Obnovení hesla", "components.ResetPassword.passwordreset": "Obnovení hesla",
"components.ResetPassword.email": "E-mailová adresa", "components.ResetPassword.email": "E-mailová adresa",
"components.ResetPassword.confirmpassword": "Potvrďte heslo", "components.ResetPassword.confirmpassword": "Potvrďte heslo",
@@ -105,7 +105,7 @@
"components.RequestModal.AdvancedRequester.destinationserver": "Cílový server", "components.RequestModal.AdvancedRequester.destinationserver": "Cílový server",
"components.RequestModal.AdvancedRequester.default": "{name} (výchozí)", "components.RequestModal.AdvancedRequester.default": "{name} (výchozí)",
"components.RequestList.sortModified": "Naposledy změněno", "components.RequestList.sortModified": "Naposledy změněno",
"components.RequestList.sortAdded": "Datum žádosti", "components.RequestList.sortAdded": "Nejnovější",
"components.RequestList.RequestItem.editrequest": "Upravit žádost", "components.RequestList.RequestItem.editrequest": "Upravit žádost",
"components.RequestList.RequestItem.deleterequest": "Odstranit žádost", "components.RequestList.RequestItem.deleterequest": "Odstranit žádost",
"components.RequestList.RequestItem.cancelRequest": "Zrušit žádost", "components.RequestList.RequestItem.cancelRequest": "Zrušit žádost",
@@ -128,8 +128,8 @@
"components.PermissionEdit.users": "Spravovat uživatele", "components.PermissionEdit.users": "Spravovat uživatele",
"components.PermissionEdit.settings": "Spravovat nastavení", "components.PermissionEdit.settings": "Spravovat nastavení",
"components.PermissionEdit.requestTv": "Žádat seriály", "components.PermissionEdit.requestTv": "Žádat seriály",
"components.PermissionEdit.requestMovies": "Žádat filmy", "components.PermissionEdit.requestMovies": "Vyžádat filmy",
"components.PermissionEdit.request4k": "Žádosti ve 4K", "components.PermissionEdit.request4k": "Vyžádat ve 4K",
"components.PermissionEdit.managerequests": "Spravovat žádosti", "components.PermissionEdit.managerequests": "Spravovat žádosti",
"components.PermissionEdit.autoapproveSeries": "Automaticky schvalovat seriály", "components.PermissionEdit.autoapproveSeries": "Automaticky schvalovat seriály",
"components.PermissionEdit.autoapproveMovies": "Automaticky schvalovat filmy", "components.PermissionEdit.autoapproveMovies": "Automaticky schvalovat filmy",
@@ -234,7 +234,7 @@
"components.NotificationTypeSelector.usermediaapprovedDescription": "Získat upozornění na schválení vašich žádostí o média.", "components.NotificationTypeSelector.usermediaapprovedDescription": "Získat upozornění na schválení vašich žádostí o média.",
"components.NotificationTypeSelector.usermediaAutoApprovedDescription": "Získat upozornění, když ostatní uživatelé zadají nové požadavky na média, která jsou automaticky schválena.", "components.NotificationTypeSelector.usermediaAutoApprovedDescription": "Získat upozornění, když ostatní uživatelé zadají nové požadavky na média, která jsou automaticky schválena.",
"components.NotificationTypeSelector.mediarequestedDescription": "Odeslat oznámení, když uživatelé zažádají o média vyžadující schválení.", "components.NotificationTypeSelector.mediarequestedDescription": "Odeslat oznámení, když uživatelé zažádají o média vyžadující schválení.",
"components.MovieDetails.streamingproviders": "Aktuálně streamovatelné zde", "components.MovieDetails.streamingproviders": "Aktuálně streamovatelné na",
"pages.serviceunavailable": "Služba není k dispozici", "pages.serviceunavailable": "Služba není k dispozici",
"pages.returnHome": "Vrátit se domů", "pages.returnHome": "Vrátit se domů",
"pages.pagenotfound": "Stránka nebyla nalezena", "pages.pagenotfound": "Stránka nebyla nalezena",
@@ -469,7 +469,7 @@
"components.IssueDetails.IssueComment.edit": "Upravit komentář", "components.IssueDetails.IssueComment.edit": "Upravit komentář",
"components.IssueDetails.allseasons": "Všechny série", "components.IssueDetails.allseasons": "Všechny série",
"components.IssueDetails.closeissue": "Uzavřít problém", "components.IssueDetails.closeissue": "Uzavřít problém",
"components.ManageSlideOver.downloadstatus": "Stav stahování", "components.ManageSlideOver.downloadstatus": "Stahování",
"components.NotificationTypeSelector.issueresolved": "Problém vyřešen", "components.NotificationTypeSelector.issueresolved": "Problém vyřešen",
"components.RequestList.RequestItem.modifieduserdate": "{date} od {user}", "components.RequestList.RequestItem.modifieduserdate": "{date} od {user}",
"components.RequestList.showallrequests": "Zobrazit všechny žádosti", "components.RequestList.showallrequests": "Zobrazit všechny žádosti",
@@ -710,7 +710,7 @@
"components.Settings.SonarrModal.toastSonarrTestSuccess": "Připojení Sonarr úspěšně navázáno!", "components.Settings.SonarrModal.toastSonarrTestSuccess": "Připojení Sonarr úspěšně navázáno!",
"components.Settings.SonarrModal.validationBaseUrlLeadingSlash": "Základní adresa URL musí mít na začátku lomítko", "components.Settings.SonarrModal.validationBaseUrlLeadingSlash": "Základní adresa URL musí mít na začátku lomítko",
"components.Settings.cacheImages": "Povolení ukládání obrázků do mezipaměti", "components.Settings.cacheImages": "Povolení ukládání obrázků do mezipaměti",
"components.Settings.cacheImagesTip": "Optimalizovat a ukládat všechny obrázky lokálně (spotřebovává značné množství místa na disku)", "components.Settings.cacheImagesTip": "Ukládat do mezipaměti a poskytovat optimalizované obrazy (vyžaduje značné množství místa na disku)",
"components.Settings.manualscanDescription": "Obvykle se provádí pouze jednou za 24 hodin. Jellyseerr bude kontrolovat nedávno přidané položky vašeho serveru Plex agresivněji. Pokud Plex konfigurujete poprvé, doporučujeme provést jednorázovou úplnou ruční kontrolu knihovny!", "components.Settings.manualscanDescription": "Obvykle se provádí pouze jednou za 24 hodin. Jellyseerr bude kontrolovat nedávno přidané položky vašeho serveru Plex agresivněji. Pokud Plex konfigurujete poprvé, doporučujeme provést jednorázovou úplnou ruční kontrolu knihovny!",
"components.Settings.originallanguageTip": "Filtrování obsahu podle původního jazyka", "components.Settings.originallanguageTip": "Filtrování obsahu podle původního jazyka",
"components.Settings.urlBase": "Základní adresa URL", "components.Settings.urlBase": "Základní adresa URL",
@@ -894,11 +894,11 @@
"components.Settings.Notifications.NotificationsPushover.userToken": "Klíč uživatele nebo skupiny", "components.Settings.Notifications.NotificationsPushover.userToken": "Klíč uživatele nebo skupiny",
"components.RequestCard.failedretry": "Při opakovaném pokusu o zadání požadavku se něco pokazilo.", "components.RequestCard.failedretry": "Při opakovaném pokusu o zadání požadavku se něco pokazilo.",
"components.Settings.Notifications.NotificationsLunaSea.profileNameTip": "Vyžaduje se pouze v případě, že nepoužíváte profil <code>default</code>", "components.Settings.Notifications.NotificationsLunaSea.profileNameTip": "Vyžaduje se pouze v případě, že nepoužíváte profil <code>default</code>",
"components.RequestCard.mediaerror": "Související titul pro tuto žádost již není k dispozici.", "components.RequestCard.mediaerror": "{mediaType} Nenalezeno",
"components.Settings.trustProxyTip": "Povolit Jellyseerr správně registrovat IP adresy klientů za proxy serverem (aby se změny projevily, musí být Jellyseerr znovu načten)", "components.Settings.trustProxyTip": "Povolit Jellyseerr správně registrovat IP adresy klientů za proxy serverem",
"components.RequestList.RequestItem.mediaerror": "Související titul pro tuto žádost již není k dispozici.", "components.RequestList.RequestItem.mediaerror": "{mediaType} Nenalezeno",
"components.RequestModal.QuotaDisplay.allowedRequests": "Můžete požádat o <strong>{limit}</strong> {type} každé <strong>{days}</strong> dny.", "components.RequestModal.QuotaDisplay.allowedRequests": "Můžete požádat o <strong>{limit}</strong> {type} každé <strong>{days}</strong> dny.",
"components.RequestModal.SearchByNameModal.notvdbiddescription": "Váš požadavek jsme nemohli automaticky porovnat. Vyberte prosím správnou shodu ze seznamu níže.", "components.RequestModal.SearchByNameModal.notvdbiddescription": "Tuto sérii jsme nemohli automaticky spárovat. Níže prosím vyberte správnou shodu.",
"components.Settings.RadarrModal.validationBaseUrlLeadingSlash": "Základ URL musí mít na začátku lomítko", "components.Settings.RadarrModal.validationBaseUrlLeadingSlash": "Základ URL musí mít na začátku lomítko",
"components.Settings.SettingsJobsCache.plex-recently-added-scan": "Plex Nedávno přidané skenování", "components.Settings.SettingsJobsCache.plex-recently-added-scan": "Plex Nedávno přidané skenování",
"components.Settings.SettingsLogs.logsDescription": "Tyto protokoly můžete také zobrazit přímo prostřednictvím <code>stdout</code> nebo v <code>{appDataPath}/logs/overseerr.log</code>.", "components.Settings.SettingsLogs.logsDescription": "Tyto protokoly můžete také zobrazit přímo prostřednictvím <code>stdout</code> nebo v <code>{appDataPath}/logs/overseerr.log</code>.",
@@ -1036,5 +1036,91 @@
"components.RequestButton.decline4krequests": "Odmítnout {requestCount, plural, one {4K žádost} other {{requestCount} 4K žádosti}}", "components.RequestButton.decline4krequests": "Odmítnout {requestCount, plural, one {4K žádost} other {{requestCount} 4K žádosti}}",
"components.RequestButton.declinerequests": "Odmítnout {requestCount, plural, one {Žádost} other {{requestCount} Žádosti}}", "components.RequestButton.declinerequests": "Odmítnout {requestCount, plural, one {Žádost} other {{requestCount} Žádosti}}",
"components.RequestModal.requestmovies": "Požádat {count} {count, plural, one {film} other {filmy}}", "components.RequestModal.requestmovies": "Požádat {count} {count, plural, one {film} other {filmy}}",
"components.Settings.SettingsJobsCache.editJobScheduleSelectorMinutes": "{jobScheduleMinutes, plural, one {Každou minutu} few {Každé {jobScheduleMinutes} minuty} other {Každých {jobScheduleMinutes} minut}}" "components.Settings.SettingsJobsCache.editJobScheduleSelectorMinutes": "{jobScheduleMinutes, plural, one {Každou minutu} few {Každé {jobScheduleMinutes} minuty} other {Každých {jobScheduleMinutes} minut}}",
"components.MovieDetails.digitalrelease": "Digitální vydání",
"components.Discover.DiscoverWatchlist.discoverwatchlist": "Váš Plex Watchlist",
"components.MovieDetails.physicalrelease": "Fyzické vydání",
"components.MovieDetails.managemovie": "Spravovat film",
"components.AirDateBadge.airsrelative": "Vysíla se {relativeTime}",
"components.Layout.UserDropdown.MiniQuotaDisplay.movierequests": "Žádosti o filmy",
"components.MovieDetails.rtaudiencescore": "Skóre publika Rotten Tomatoes",
"components.MovieDetails.rtcriticsscore": "Rotten Tomatoes Tomatometer",
"components.Layout.UserDropdown.MiniQuotaDisplay.seriesrequests": "Žádosti o seriály",
"components.AirDateBadge.airedrelative": "Odvysílano {relativeTime}",
"components.Layout.UserDropdown.requests": "Žádosti",
"components.MovieDetails.reportissue": "Nahlásit problém",
"components.StatusChecker.restartRequired": "Je vyžadován restart serveru",
"components.PermissionEdit.viewrecent": "Zobrazit naposledy přidané",
"components.StatusChecker.appUpdated": "{applicationTitle} Aktualizováno",
"components.StatusChecker.appUpdatedDescription": "Chcete-li aplikaci znovu načíst, klikněte na tlačítko níže.",
"components.StatusChecker.restartRequiredDescription": "Chcete-li použít aktualizovaná nastavení, restartujte server.",
"components.RequestCard.tmdbid": "TMDB ID",
"components.RequestList.RequestItem.tmdbid": "TMDB ID",
"components.TitleCard.tmdbid": "TMDB ID",
"components.TitleCard.tvdbid": "TheTVDB ID",
"components.RequestCard.tvdbid": "TheTVDB ID",
"components.RequestList.RequestItem.tvdbid": "TheTVDB ID",
"components.PermissionEdit.autorequestMovies": "Automatické vyžádání filmů",
"components.PermissionEdit.autorequestMoviesDescription": "Udělte oprávnění k automatickému odesílání žádostí o filmy v jiném rozlišení než 4K prostřednictvím Plex Watchlistu.",
"components.PermissionEdit.viewrecentDescription": "Udělte oprávnění k zobrazení seznamu nedávno přidaných médií.",
"components.Settings.restartrequiredTooltip": "Aby se změny tohoto nastavení projevily, musí být Overserr restartován",
"components.StatusChecker.reloadApp": "Znovu načíst {applicationTitle}",
"components.TitleCard.cleardata": "Vyčistit data",
"components.TitleCard.mediaerror": "{mediaType} Nenalezeno",
"components.PermissionEdit.autorequestDescription": "Udělte oprávnění k automatickému odesílání žádostí o média jiná než 4K prostřednictvím seznamu Plex Watchlist.",
"components.PermissionEdit.autorequest": "Automatická žádost",
"components.PermissionEdit.autorequestSeries": "Automaticky vyžádat seriál",
"components.PermissionEdit.viewwatchlists": "Zobrazit Plex Watchilisty",
"components.Settings.advancedTooltip": "Nesprávná konfigurace tohoto nastavení může vést k narušení funkčnosti",
"components.Settings.deleteServer": "Smazat server {serverType}",
"components.Settings.experimentalTooltip": "Povolení tohoto nastavení může vést k neočekávanému chování aplikace",
"components.Settings.SettingsLogs.viewdetails": "Zobrazit podrobnosti",
"components.PermissionEdit.autorequestSeriesDescription": "Udělte oprávnění k automatickému odesílání žádostí o seriály jiné než 4K prostřednictvím Plex Watchlist.",
"components.RequestBlock.approve": "Schválit žádost",
"components.RequestBlock.decline": "Odmítnout požadavek",
"components.RequestBlock.lastmodifiedby": "Naposledy změněno od",
"components.RequestBlock.requestdate": "Datum požadavku",
"components.RequestBlock.requestedby": "Vyžádáno od",
"components.RequestCard.approverequest": "Schválit žádost",
"components.RequestCard.cancelrequest": "Zrušit žádost",
"components.RequestCard.declinerequest": "Odmítnout požadavek",
"components.Settings.SettingsJobsCache.plex-watchlist-sync": "Plex Watchlist synchronizace",
"components.StatusBadge.managemedia": "Spravovat {mediaType}",
"components.StatusBadge.openinarr": "Otevřít v {arr}",
"components.StatusBadge.playonplex": "Přehrávání cez Plex",
"components.TvDetails.manageseries": "Spravovat sérii",
"components.RequestBlock.delete": "Smazat požadavek",
"components.RequestBlock.edit": "Upravit požadavek",
"components.RequestCard.editrequest": "Upravit požadavek",
"components.RequestModal.SearchByNameModal.nomatches": "Pro tuto sérii jsme nenašli shodu.",
"components.RequestModal.requestcollection4ktitle": "Vyžádejte si kolekci ve 4K",
"components.RequestModal.requestcollectiontitle": "Vyžádat kolekci",
"components.RequestModal.requestmovie4ktitle": "Vyžádejte si film ve 4K",
"components.RequestModal.requestmovietitle": "Vyžádat si film",
"components.RequestModal.requestseries4ktitle": "Žádost o sérii ve 4K",
"components.RequestModal.requestseriestitle": "Vyžádat sérii",
"components.TvDetails.episodeCount": "{episodeCount, plural, one {# Epizoda} other {# Epizody}}",
"components.NotificationTypeSelector.mediaautorequested": "Žádost byla odeslána automaticky",
"components.NotificationTypeSelector.mediaautorequestedDescription": "Získejte upozornění, když jsou automaticky odeslány nové požadavky na média pro položky na vašem Plex Watchlistu.",
"components.PermissionEdit.viewwatchlistsDescription": "Udělte oprávnění k zobrazení Plex Watchlistu ostatních uživatelů.",
"components.TvDetails.Season.somethingwentwrong": "Při načítání údajů o sezóně se něco pokazilo.",
"i18n.restartRequired": "Je vyžadován restart",
"components.Discover.plexwatchlist": "Váš Plex Watchlist",
"components.MovieDetails.theatricalrelease": "Dostupné v kinách",
"components.Discover.DiscoverWatchlist.watchlist": "Plex Watchlist",
"components.TvDetails.reportissue": "Nahlásit problém",
"components.MovieDetails.tmdbuserscore": "Uživatelské skóre TMDB",
"components.TvDetails.seasonstitle": "Sezóny",
"components.TvDetails.seasonnumber": "Sezóna {seasonNumber}",
"components.TvDetails.rtcriticsscore": "Rotten Tomatoes Tomatometer",
"components.TvDetails.rtaudiencescore": "Rotten Tomatoes Audience Skóre",
"components.TvDetails.tmdbuserscore": "Uživatelské skóre TMDB",
"components.TvDetails.status4k": "4K {status}",
"components.Discover.emptywatchlist": "Média přidaná do vašeho <PlexWatchlistSupportLink>Plex Watchlistu</PlexWatchlistSupportLink> se zobrazí zde.",
"components.UserProfile.UserSettings.UserGeneralSettings.plexwatchlistsyncmoviestip": "Automaticky vyžádat filmy na vašem <PlexWatchlistSupportLink>Plex Watchlistu</PlexWatchlistSupportLink>",
"components.UserProfile.UserSettings.UserGeneralSettings.plexwatchlistsyncseriestip": "Automaticky vyžádejte sérii na vašem <PlexWatchlistSupportLink>Plex Watchlistu</PlexWatchlistSupportLink>",
"components.UserProfile.UserSettings.UserGeneralSettings.plexwatchlistsyncseries": "Automaticky vyžádat sérii",
"components.UserProfile.UserSettings.UserGeneralSettings.plexwatchlistsyncmovies": "Automaticky vyžádat filmy",
"components.UserProfile.plexwatchlist": "Plex Watchlist",
"components.UserProfile.emptywatchlist": "Zde se zobrazí média přidaná do vašeho <PlexWatchlistSupportLink>Plex Watchlistu</PlexWatchlistSupportLink>."
} }

View File

@@ -642,7 +642,8 @@
"components.Settings.SettingsJobsCache.download-sync": "Download Sync", "components.Settings.SettingsJobsCache.download-sync": "Download Sync",
"components.Settings.SettingsJobsCache.download-sync-reset": "Download Sync Reset", "components.Settings.SettingsJobsCache.download-sync-reset": "Download Sync Reset",
"components.Settings.SettingsJobsCache.editJobSchedule": "Modify Job", "components.Settings.SettingsJobsCache.editJobSchedule": "Modify Job",
"components.Settings.SettingsJobsCache.editJobSchedulePrompt": "Frequency", "components.Settings.SettingsJobsCache.editJobScheduleCurrent": "Current Frequency",
"components.Settings.SettingsJobsCache.editJobSchedulePrompt": "New Frequency",
"components.Settings.SettingsJobsCache.editJobScheduleSelectorHours": "Every {jobScheduleHours, plural, one {hour} other {{jobScheduleHours} hours}}", "components.Settings.SettingsJobsCache.editJobScheduleSelectorHours": "Every {jobScheduleHours, plural, one {hour} other {{jobScheduleHours} hours}}",
"components.Settings.SettingsJobsCache.editJobScheduleSelectorMinutes": "Every {jobScheduleMinutes, plural, one {minute} other {{jobScheduleMinutes} minutes}}", "components.Settings.SettingsJobsCache.editJobScheduleSelectorMinutes": "Every {jobScheduleMinutes, plural, one {minute} other {{jobScheduleMinutes} minutes}}",
"components.Settings.SettingsJobsCache.flushcache": "Flush Cache", "components.Settings.SettingsJobsCache.flushcache": "Flush Cache",

55
src/i18n/locale/hr.json Normal file
View File

@@ -0,0 +1,55 @@
{
"components.CollectionDetails.numberofmovies": "{count} Filmovi",
"components.CollectionDetails.overview": "Pregled",
"components.CollectionDetails.requestcollection4k": "Zahtjev za serijalom u 4K",
"components.CollectionDetails.requestcollection": "Zahtjev za serijalom",
"components.Discover.DiscoverMovieGenre.genreMovies": "{genre} Filmovi",
"components.Discover.DiscoverMovieLanguage.languageMovies": "{language} Filmovi",
"components.Discover.DiscoverNetwork.networkSeries": "{network} Serije",
"components.Discover.DiscoverStudio.studioMovies": "{studio} Filmovi",
"components.Discover.discover": "Otkrivanje",
"components.Discover.discovermovies": "Popularni Filmovi",
"components.Discover.discovertv": "Popularne Serije",
"components.Discover.DiscoverTvGenre.genreSeries": "{genre} Serije",
"components.Discover.DiscoverTvLanguage.languageSeries": "{language} Serije",
"components.Discover.DiscoverWatchlist.discoverwatchlist": "Vaš Plex popis za gledanje",
"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": "Vaš Plex popis za gledanje",
"components.Discover.popularmovies": "Popularni Filmovi",
"components.Discover.recentlyAdded": "Nedavno Dodano",
"components.Discover.recentrequests": "Nedavni Zahtjevi",
"components.Discover.trending": "Popularno",
"components.IssueDetails.IssueComment.edit": "Uredi Komentar",
"components.IssueDetails.IssueDescription.deleteissue": "Izbriši problem",
"components.IssueDetails.IssueDescription.description": "Opis",
"components.IssueDetails.IssueDescription.edit": "Uredi opis",
"components.IssueDetails.allepisodes": "Sve Epizode",
"components.IssueDetails.closeissue": "Zatvori Problem",
"components.IssueDetails.closeissueandcomment": "Zatvori s komentarom",
"components.IssueDetails.commentplaceholder": "Dodaj komentar…",
"components.IssueDetails.comments": "Komentari",
"components.IssueDetails.deleteissue": "Izbriši problem",
"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 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": "Striming servisi",
"components.Discover.populartv": "Popularne Serije",
"components.Discover.upcomingtv": "Nadolazeće Serije",
"components.Discover.upcoming": "Nadolazeći Filmovi",
"components.Discover.upcomingmovies": "Nadolazeći Filmovi",
"components.DownloadBlock.estimatedtime": "Procijenjeno {time}",
"components.IssueDetails.IssueComment.areyousuredelete": "Jeste li sigurni da želite izbrisati ovaj komentar?",
"components.IssueDetails.IssueComment.delete": "Izbriši Komentar",
"components.IssueDetails.IssueComment.postedby": "Objavljeno u {relativeTime} od korisnika {username}",
"components.IssueDetails.IssueComment.validationComment": "Morate unijeti poruku",
"components.IssueDetails.IssueComment.postedbyedited": "Objavljeno u {relativeTime} od korisnika {username} (Uređeno)",
"components.IssueDetails.allseasons": "Sve Sezone",
"components.IssueDetails.episode": "Epizode {episodeNumber}",
"components.IssueDetails.deleteissueconfirm": "Jeste li sigurni da želite izbrisati ovaj problem?"
}

View File

@@ -337,7 +337,7 @@
"i18n.experimental": "Experimenteel", "i18n.experimental": "Experimenteel",
"components.Settings.hideAvailable": "Beschikbare media verbergen", "components.Settings.hideAvailable": "Beschikbare media verbergen",
"components.RequestModal.requesterror": "Er ging iets mis bij het aanvragen.", "components.RequestModal.requesterror": "Er ging iets mis bij het aanvragen.",
"components.RequestModal.SearchByNameModal.notvdbiddescription": "We konden je verzoek niet automatisch matchen. Selecteer de juiste match uit de onderstaande lijst.", "components.RequestModal.SearchByNameModal.notvdbiddescription": "We kunnen deze serie niet automatisch matchen. Selecteer hieronder de juiste match.",
"components.Login.signinwithplex": "Plex-account gebruiken", "components.Login.signinwithplex": "Plex-account gebruiken",
"components.Login.signinheader": "Log in om verder te gaan", "components.Login.signinheader": "Log in om verder te gaan",
"components.Login.signingin": "Bezig met inloggen…", "components.Login.signingin": "Bezig met inloggen…",
@@ -632,7 +632,7 @@
"components.Settings.SettingsJobsCache.jobsandcache": "Taken en cache", "components.Settings.SettingsJobsCache.jobsandcache": "Taken en cache",
"components.Settings.SettingsAbout.about": "Over", "components.Settings.SettingsAbout.about": "Over",
"components.ResetPassword.passwordreset": "Wachtwoord opnieuw instellen", "components.ResetPassword.passwordreset": "Wachtwoord opnieuw instellen",
"components.Settings.cacheImagesTip": "Alle afbeeldingen optimaliseren en lokaal opslaan (gebruikt een aanzienlijke hoeveelheid schijfruimte)", "components.Settings.cacheImagesTip": "Cache en serveer geoptimaliseerde afbeeldingen (een aanzienlijke hoeveelheid schijfruimte is nodig)",
"components.Settings.cacheImages": "Afbeeldingscaching inschakelen", "components.Settings.cacheImages": "Afbeeldingscaching inschakelen",
"components.Settings.SettingsLogs.logDetails": "Loggegevens", "components.Settings.SettingsLogs.logDetails": "Loggegevens",
"components.Settings.SettingsLogs.extraData": "Aanvullende gegevens", "components.Settings.SettingsLogs.extraData": "Aanvullende gegevens",
@@ -713,9 +713,9 @@
"components.RequestModal.AdvancedRequester.selecttags": "Labels selecteren", "components.RequestModal.AdvancedRequester.selecttags": "Labels selecteren",
"components.RequestModal.AdvancedRequester.notagoptions": "Geen labels.", "components.RequestModal.AdvancedRequester.notagoptions": "Geen labels.",
"components.Settings.RadarrModal.loadingTags": "Labels laden…", "components.Settings.RadarrModal.loadingTags": "Labels laden…",
"components.RequestList.RequestItem.mediaerror": "De gekoppelde titel voor dit verzoek is niet meer beschikbaar.", "components.RequestList.RequestItem.mediaerror": "{mediaType} Niet Gevonden",
"components.RequestList.RequestItem.deleterequest": "Verzoek verwijderen", "components.RequestList.RequestItem.deleterequest": "Verzoek verwijderen",
"components.RequestCard.mediaerror": "De gekoppelde titel voor dit verzoek is niet meer beschikbaar.", "components.RequestCard.mediaerror": "{mediaType} Niet Gevonden",
"components.RequestCard.deleterequest": "Verzoek verwijderen", "components.RequestCard.deleterequest": "Verzoek verwijderen",
"components.UserProfile.UserSettings.UserNotificationSettings.validationPgpPublicKey": "Je moet een geldige openbare PGP-sleutel opgeven", "components.UserProfile.UserSettings.UserNotificationSettings.validationPgpPublicKey": "Je moet een geldige openbare PGP-sleutel opgeven",
"components.UserProfile.UserSettings.UserNotificationSettings.telegramsettingssaved": "Instellingen Telegrammeldingen succesvol opgeslagen!", "components.UserProfile.UserSettings.UserNotificationSettings.telegramsettingssaved": "Instellingen Telegrammeldingen succesvol opgeslagen!",

View File

@@ -1034,5 +1034,44 @@
"i18n.next": "Tjetra", "i18n.next": "Tjetra",
"i18n.experimental": "Eksperimentale", "i18n.experimental": "Eksperimentale",
"i18n.noresults": "S'ka rezultate.", "i18n.noresults": "S'ka rezultate.",
"i18n.notrequested": "Nuk është Kerkuar" "i18n.notrequested": "Nuk është Kerkuar",
"components.Settings.SettingsAbout.appDataPath": "Direktoria e të dhënave",
"components.MovieDetails.digitalrelease": "Publikimi Dixhital",
"components.MovieDetails.physicalrelease": "Lëshimi Fizik",
"components.MovieDetails.theatricalrelease": "Publikimi Teatror",
"components.PermissionEdit.viewrecent": "Shiko Shtuar së fundi",
"components.PermissionEdit.viewrecentDescription": "Jep leje për të parë listën e mediave të shtuara kohët e fundit.",
"components.Discover.DiscoverWatchlist.discoverwatchlist": "Lista juaj e shikimit në Plex",
"components.PermissionEdit.autorequestMoviesDescription": "Jepni lejen për të paraqitur automatikisht kërkesat për mediat jo-4K nëpërmjet Plex Watchlist.",
"components.PermissionEdit.autorequestSeries": "Kërkesa Automatike Serialesh",
"components.PermissionEdit.autorequestSeriesDescription": "Jep leje për të paraqitur automatikisht kërkesat për seri jo-4K nëpërmjet Plex Watchlist.",
"components.Discover.DiscoverWatchlist.watchlist": "Lista e Shikimit Plex",
"components.Discover.plexwatchlist": "Lista juaj e shikimit në Plex",
"components.MovieDetails.reportissue": "Raportoni një problem",
"components.NotificationTypeSelector.mediaautorequested": "Kërkesa u dorëzua automatikisht",
"components.PermissionEdit.autorequest": "Auto-Kërkesë",
"components.PermissionEdit.autorequestDescription": "Jepni lejen për të paraqitur automatikisht kërkesat për mediat jo-4K nëpërmjet Plex Watchlist.",
"components.PermissionEdit.autorequestMovies": "Kërkesë automatike për filma",
"components.PermissionEdit.viewwatchlists": "Shiko listat e shikimit të Plex",
"components.PermissionEdit.viewwatchlistsDescription": "Jep leje për të parë listat e Plex Watchlist të përdoruesve të tjerë.",
"components.MovieDetails.managemovie": "Menaxho filmin",
"components.AirDateBadge.airedrelative": "Transmetuar {relativeTime}",
"components.Layout.UserDropdown.MiniQuotaDisplay.movierequests": "Kërkesat e Filmave",
"components.Layout.UserDropdown.MiniQuotaDisplay.seriesrequests": "Kërkesat e Serialeve",
"components.Layout.UserDropdown.requests": "Kërkesat",
"components.MovieDetails.rtaudiencescore": "Rezultati i audiencës së Rotten Tomatoes",
"components.MovieDetails.rtcriticsscore": "Tomatometeri i Rotten Tomatoes",
"components.NotificationTypeSelector.mediaautorequestedDescription": "Njoftohuni kur kërkesat e reja të medias dorëzohen automatikisht për artikujt në Listën tuaj të Plex Watchlist.",
"components.RequestBlock.languageprofile": "Profili i gjuhës",
"components.AirDateBadge.airsrelative": "Duke u Transmetuar {relativeTime}",
"components.Discover.emptywatchlist": "Këtu do të shfaqet media e shtuar në listën tuaj <PlexWatchlistSupportLink>Plex</PlexWatchlistSupportLink>.",
"components.RequestBlock.decline": "Refuzo kërkesën",
"components.RequestBlock.delete": "Fshije Kërkesën",
"components.RequestBlock.edit": "Ndrysho kërkesën",
"components.MovieDetails.tmdbuserscore": "Nota e përdoruesve TMDB",
"components.RequestBlock.approve": "Mirato Kërkesën",
"components.RequestBlock.lastmodifiedby": "Ndryshuar së fundi nga",
"components.RequestBlock.requestdate": "Data e Kërkesës",
"components.RequestBlock.requestedby": "Kërkuar nga",
"components.RequestCard.approverequest": "Mirato Kërkesën"
} }

View File

@@ -57,7 +57,7 @@
"components.UserList.creating": "創建中…", "components.UserList.creating": "創建中…",
"components.UserList.createlocaluser": "建立本地使用者", "components.UserList.createlocaluser": "建立本地使用者",
"components.UserList.autogeneratepassword": "自動生成密碼", "components.UserList.autogeneratepassword": "自動生成密碼",
"i18n.tvshows": "電視節目", "i18n.tvshows": "影集",
"pages.oops": "哎呀", "pages.oops": "哎呀",
"components.TvDetails.firstAirDate": "原始播出日期", "components.TvDetails.firstAirDate": "原始播出日期",
"i18n.delete": "刪除", "i18n.delete": "刪除",
@@ -133,15 +133,15 @@
"components.Layout.Sidebar.users": "使用者", "components.Layout.Sidebar.users": "使用者",
"components.Layout.Sidebar.settings": "設定", "components.Layout.Sidebar.settings": "設定",
"components.Layout.Sidebar.requests": "請求", "components.Layout.Sidebar.requests": "請求",
"components.Layout.SearchInput.searchPlaceholder": "搜尋電影、電視節目、人物…", "components.Layout.SearchInput.searchPlaceholder": "搜尋電影、影集、人物…",
"components.Discover.upcomingmovies": "即將上映的電影", "components.Discover.upcomingmovies": "即將上映的電影",
"components.Discover.upcoming": "即將上映的電影", "components.Discover.upcoming": "即將上映的電影",
"components.Discover.trending": "趨勢", "components.Discover.trending": "趨勢",
"components.Discover.recentlyAdded": "最近新增", "components.Discover.recentlyAdded": "最近新增",
"components.Discover.recentrequests": "最新請求", "components.Discover.recentrequests": "最新請求",
"components.Discover.populartv": "熱門電視節目", "components.Discover.populartv": "熱門影集",
"components.Discover.popularmovies": "熱門電影", "components.Discover.popularmovies": "熱門電影",
"components.Discover.discovertv": "熱門電視節目", "components.Discover.discovertv": "熱門影集",
"components.Discover.discovermovies": "熱門電影", "components.Discover.discovermovies": "熱門電影",
"components.CollectionDetails.requestcollection": "提出電影系列請求", "components.CollectionDetails.requestcollection": "提出電影系列請求",
"components.CollectionDetails.overview": "概要", "components.CollectionDetails.overview": "概要",
@@ -262,7 +262,7 @@
"components.TvDetails.overviewunavailable": "沒有概要。", "components.TvDetails.overviewunavailable": "沒有概要。",
"components.MovieDetails.overviewunavailable": "沒有概要。", "components.MovieDetails.overviewunavailable": "沒有概要。",
"components.TvDetails.watchtrailer": "觀看預告片", "components.TvDetails.watchtrailer": "觀看預告片",
"components.TvDetails.showtype": "節目類型", "components.TvDetails.showtype": "影集類型",
"components.TvDetails.similar": "類似", "components.TvDetails.similar": "類似",
"components.RequestModal.requestfrom": "{username} 的請求待處理。", "components.RequestModal.requestfrom": "{username} 的請求待處理。",
"components.Settings.toastApiKeySuccess": "生成新應用程式密鑰成功!", "components.Settings.toastApiKeySuccess": "生成新應用程式密鑰成功!",
@@ -306,7 +306,7 @@
"components.Settings.RadarrModal.add": "新增伺服器", "components.Settings.RadarrModal.add": "新增伺服器",
"components.RequestModal.requestcancelled": "<strong>{title}</strong> 的請求已被取消。", "components.RequestModal.requestcancelled": "<strong>{title}</strong> 的請求已被取消。",
"components.RequestModal.AdvancedRequester.qualityprofile": "品質設定", "components.RequestModal.AdvancedRequester.qualityprofile": "品質設定",
"components.RequestModal.AdvancedRequester.animenote": "※這是個動漫節目。", "components.RequestModal.AdvancedRequester.animenote": "※這是個動漫影集。",
"components.RequestModal.AdvancedRequester.advancedoptions": "進階選項", "components.RequestModal.AdvancedRequester.advancedoptions": "進階選項",
"components.RequestModal.AdvancedRequester.default": "{name}(預設)", "components.RequestModal.AdvancedRequester.default": "{name}(預設)",
"components.RequestModal.AdvancedRequester.destinationserver": "目標伺服器", "components.RequestModal.AdvancedRequester.destinationserver": "目標伺服器",
@@ -321,7 +321,7 @@
"components.Settings.SonarrModal.toastSonarrTestFailure": "Sonarr 伺服器連線失敗。", "components.Settings.SonarrModal.toastSonarrTestFailure": "Sonarr 伺服器連線失敗。",
"components.Settings.serverpreset": "伺服器", "components.Settings.serverpreset": "伺服器",
"components.RequestModal.autoapproval": "自動批准", "components.RequestModal.autoapproval": "自動批准",
"components.PermissionEdit.autoapproveSeries": "電視節目自動批准", "components.PermissionEdit.autoapproveSeries": "影集自動批准",
"components.PermissionEdit.autoapproveMovies": "電影自動批准", "components.PermissionEdit.autoapproveMovies": "電影自動批准",
"components.PermissionEdit.autoapprove": "自動批准", "components.PermissionEdit.autoapprove": "自動批准",
"components.PermissionEdit.admin": "管理員", "components.PermissionEdit.admin": "管理員",
@@ -330,7 +330,7 @@
"components.Settings.serverpresetRefreshing": "載入中…", "components.Settings.serverpresetRefreshing": "載入中…",
"components.Settings.SonarrModal.syncEnabled": "啟用掃描", "components.Settings.SonarrModal.syncEnabled": "啟用掃描",
"components.UserList.userssaved": "使用者權限儲存成功!", "components.UserList.userssaved": "使用者權限儲存成功!",
"components.Settings.hideAvailable": "隱藏可觀看的電影和電視節目", "components.Settings.hideAvailable": "隱藏可觀看的電影和影集",
"components.Settings.SonarrModal.externalUrl": "外部網址", "components.Settings.SonarrModal.externalUrl": "外部網址",
"components.Settings.RadarrModal.externalUrl": "外部網址", "components.Settings.RadarrModal.externalUrl": "外部網址",
"components.Settings.csrfProtection": "防止跨站請求偽造CSRF攻擊", "components.Settings.csrfProtection": "防止跨站請求偽造CSRF攻擊",
@@ -353,7 +353,7 @@
"components.PlexLoginButton.signinwithplex": "登入", "components.PlexLoginButton.signinwithplex": "登入",
"components.PlexLoginButton.signingin": "登入中…", "components.PlexLoginButton.signingin": "登入中…",
"components.PermissionEdit.users": "管理使用者", "components.PermissionEdit.users": "管理使用者",
"components.PermissionEdit.request4kTv": "提出 4K 電視節目請求", "components.PermissionEdit.request4kTv": "提出 4K 影集請求",
"components.PermissionEdit.request4kMovies": "提出 4K 電影請求", "components.PermissionEdit.request4kMovies": "提出 4K 電影請求",
"components.PermissionEdit.request4k": "提出 4K 請求", "components.PermissionEdit.request4k": "提出 4K 請求",
"components.PermissionEdit.request": "提出請求", "components.PermissionEdit.request": "提出請求",
@@ -432,7 +432,7 @@
"components.NotificationTypeSelector.mediadeclinedDescription": "當請求拒被絕時發送通知。", "components.NotificationTypeSelector.mediadeclinedDescription": "當請求拒被絕時發送通知。",
"components.PermissionEdit.request4kDescription": "授予提出 4K 媒體請求的權限。", "components.PermissionEdit.request4kDescription": "授予提出 4K 媒體請求的權限。",
"components.PermissionEdit.request4kMoviesDescription": "授予提出 4K 電影請求的權限。", "components.PermissionEdit.request4kMoviesDescription": "授予提出 4K 電影請求的權限。",
"components.PermissionEdit.request4kTvDescription": "授予提出 4K 電視節目請求的權限。", "components.PermissionEdit.request4kTvDescription": "授予提出 4K 影集請求的權限。",
"components.PermissionEdit.requestDescription": "授予提出非 4K 媒體請求的權限。", "components.PermissionEdit.requestDescription": "授予提出非 4K 媒體請求的權限。",
"components.PermissionEdit.viewrequestsDescription": "授予查看其他使用者提出的媒體請求的權限。", "components.PermissionEdit.viewrequestsDescription": "授予查看其他使用者提出的媒體請求的權限。",
"components.Settings.SonarrModal.validationLanguageProfileRequired": "請設定語言", "components.Settings.SonarrModal.validationLanguageProfileRequired": "請設定語言",
@@ -443,7 +443,7 @@
"components.Settings.SonarrModal.animelanguageprofile": "動漫語言設定", "components.Settings.SonarrModal.animelanguageprofile": "動漫語言設定",
"components.RequestModal.AdvancedRequester.languageprofile": "語言設定", "components.RequestModal.AdvancedRequester.languageprofile": "語言設定",
"components.PermissionEdit.usersDescription": "授予管理使用者的權限。有此權限的使用者不能編輯管理員或授予管理員權限。", "components.PermissionEdit.usersDescription": "授予管理使用者的權限。有此權限的使用者不能編輯管理員或授予管理員權限。",
"components.PermissionEdit.autoapproveSeriesDescription": "自動批准非 4K 電視節目請求。", "components.PermissionEdit.autoapproveSeriesDescription": "自動批准非 4K 影集請求。",
"components.PermissionEdit.autoapproveMoviesDescription": "自動批准非 4K 電影請求。", "components.PermissionEdit.autoapproveMoviesDescription": "自動批准非 4K 電影請求。",
"components.PermissionEdit.autoapproveDescription": "自動批准所有非 4K 媒體請求。", "components.PermissionEdit.autoapproveDescription": "自動批准所有非 4K 媒體請求。",
"components.PermissionEdit.advancedrequestDescription": "授予使用進階媒體請求選項的權限。", "components.PermissionEdit.advancedrequestDescription": "授予使用進階媒體請求選項的權限。",
@@ -456,8 +456,8 @@
"components.UserList.sortCreated": "加入日期", "components.UserList.sortCreated": "加入日期",
"components.UserList.sortDisplayName": "顯示名稱", "components.UserList.sortDisplayName": "顯示名稱",
"components.UserList.sortRequests": "請求數", "components.UserList.sortRequests": "請求數",
"components.PermissionEdit.autoapprove4kSeriesDescription": "自動批准 4K 電視節目請求。", "components.PermissionEdit.autoapprove4kSeriesDescription": "自動批准 4K 影集請求。",
"components.PermissionEdit.autoapprove4kSeries": "4K 電視節目自動批准", "components.PermissionEdit.autoapprove4kSeries": "4K 影集自動批准",
"components.PermissionEdit.autoapprove4kMoviesDescription": "自動批准 4K 電影請求。", "components.PermissionEdit.autoapprove4kMoviesDescription": "自動批准 4K 電影請求。",
"components.PermissionEdit.autoapprove4kMovies": "4K 電影自動批准", "components.PermissionEdit.autoapprove4kMovies": "4K 電影自動批准",
"components.PermissionEdit.autoapprove4kDescription": "自動批准所有 4K 媒體請求。", "components.PermissionEdit.autoapprove4kDescription": "自動批准所有 4K 媒體請求。",
@@ -502,7 +502,7 @@
"components.UserProfile.UserSettings.UserNotificationSettings.validationDiscordId": "請輸入有效的使用者 ID", "components.UserProfile.UserSettings.UserNotificationSettings.validationDiscordId": "請輸入有效的使用者 ID",
"components.UserProfile.UserSettings.UserNotificationSettings.discordIdTip": "您的<FindDiscordIdLink>使用者 ID 號碼</FindDiscordIdLink>", "components.UserProfile.UserSettings.UserNotificationSettings.discordIdTip": "您的<FindDiscordIdLink>使用者 ID 號碼</FindDiscordIdLink>",
"components.UserProfile.UserSettings.UserPasswordChange.toastSettingsFailure": "重設密碼時出了點問題。", "components.UserProfile.UserSettings.UserPasswordChange.toastSettingsFailure": "重設密碼時出了點問題。",
"components.RequestModal.SearchByNameModal.notvdbiddescription": "無法自動配對此電視節目的數據。 請在以下選擇正確的媒體項。", "components.RequestModal.SearchByNameModal.notvdbiddescription": "無法自動配對此影集的數據。 請在以下選擇正確的媒體項。",
"components.CollectionDetails.requestcollection4k": "提出 4K 電影系列請求", "components.CollectionDetails.requestcollection4k": "提出 4K 電影系列請求",
"components.Settings.trustProxyTip": "使用代理伺服器時,允許 Jellyseerr 註冊客戶端的正確 IP 位址", "components.Settings.trustProxyTip": "使用代理伺服器時,允許 Jellyseerr 註冊客戶端的正確 IP 位址",
"components.Settings.csrfProtectionTip": "設定外部訪問權限為只讀(需要 HTTPS", "components.Settings.csrfProtectionTip": "設定外部訪問權限為只讀(需要 HTTPS",
@@ -513,7 +513,7 @@
"components.Settings.region": "「探索」地區", "components.Settings.region": "「探索」地區",
"components.UserProfile.UserSettings.UserGeneralSettings.originallanguage": "「探索」語言", "components.UserProfile.UserSettings.UserGeneralSettings.originallanguage": "「探索」語言",
"components.Settings.originallanguage": "「探索」語言", "components.Settings.originallanguage": "「探索」語言",
"components.Discover.upcomingtv": "即將上映的電視節目", "components.Discover.upcomingtv": "即將上映的影集",
"components.Settings.webhook": "Webhook", "components.Settings.webhook": "Webhook",
"components.Settings.email": "電子郵件", "components.Settings.email": "電子郵件",
"components.Settings.generalsettingsDescription": "Jellyseerr 的全域和預設設定。", "components.Settings.generalsettingsDescription": "Jellyseerr 的全域和預設設定。",
@@ -540,8 +540,8 @@
"components.Settings.SettingsJobsCache.download-sync": "下載狀態同步", "components.Settings.SettingsJobsCache.download-sync": "下載狀態同步",
"components.Settings.SettingsJobsCache.unknownJob": "未知作業", "components.Settings.SettingsJobsCache.unknownJob": "未知作業",
"components.TvDetails.seasons": "{seasonCount} 季", "components.TvDetails.seasons": "{seasonCount} 季",
"components.Discover.DiscoverTvGenre.genreSeries": "{genre}電視節目", "components.Discover.DiscoverTvGenre.genreSeries": "{genre}影集",
"components.Discover.DiscoverNetwork.networkSeries": "{network} 電視節目", "components.Discover.DiscoverNetwork.networkSeries": "{network} 影集",
"components.Discover.DiscoverStudio.studioMovies": "{studio} 電影", "components.Discover.DiscoverStudio.studioMovies": "{studio} 電影",
"components.Discover.DiscoverMovieGenre.genreMovies": "{genre}電影", "components.Discover.DiscoverMovieGenre.genreMovies": "{genre}電影",
"i18n.loading": "載入中…", "i18n.loading": "載入中…",
@@ -565,7 +565,7 @@
"components.Settings.SettingsJobsCache.radarr-scan": "Radarr 掃描", "components.Settings.SettingsJobsCache.radarr-scan": "Radarr 掃描",
"components.Settings.SettingsJobsCache.plex-recently-added-scan": "Plex 最近新增掃描", "components.Settings.SettingsJobsCache.plex-recently-added-scan": "Plex 最近新增掃描",
"components.Settings.SettingsJobsCache.plex-full-scan": "Plex 媒體庫掃描", "components.Settings.SettingsJobsCache.plex-full-scan": "Plex 媒體庫掃描",
"components.Discover.DiscoverTvLanguage.languageSeries": "{language}電視節目", "components.Discover.DiscoverTvLanguage.languageSeries": "{language}影集",
"components.Discover.DiscoverMovieLanguage.languageMovies": "{language}電影", "components.Discover.DiscoverMovieLanguage.languageMovies": "{language}電影",
"components.UserProfile.ProfileHeader.userid": "使用者 ID{userid}", "components.UserProfile.ProfileHeader.userid": "使用者 ID{userid}",
"components.UserProfile.ProfileHeader.joindate": "加入日期:{joindate}", "components.UserProfile.ProfileHeader.joindate": "加入日期:{joindate}",
@@ -587,11 +587,11 @@
"components.TvDetails.episodeRuntime": "劇集片長", "components.TvDetails.episodeRuntime": "劇集片長",
"components.TvDetails.episodeRuntimeMinutes": "{runtime} 分鐘", "components.TvDetails.episodeRuntimeMinutes": "{runtime} 分鐘",
"components.RequestModal.AdvancedRequester.folder": "{path}{space}", "components.RequestModal.AdvancedRequester.folder": "{path}{space}",
"components.Discover.TvGenreSlider.tvgenres": "電視節目類型", "components.Discover.TvGenreSlider.tvgenres": "影集類型",
"components.Discover.MovieGenreSlider.moviegenres": "電影類型", "components.Discover.MovieGenreSlider.moviegenres": "電影類型",
"components.Discover.MovieGenreList.moviegenres": "電影類型", "components.Discover.MovieGenreList.moviegenres": "電影類型",
"components.Discover.TvGenreList.seriesgenres": "電視節目類型", "components.Discover.TvGenreList.seriesgenres": "影集類型",
"components.Settings.partialRequestsEnabled": "允許不完整的電視節目請求", "components.Settings.partialRequestsEnabled": "允許不完整的影集請求",
"components.RequestModal.alreadyrequested": "已經有請求", "components.RequestModal.alreadyrequested": "已經有請求",
"components.Settings.SettingsLogs.time": "時間戳", "components.Settings.SettingsLogs.time": "時間戳",
"components.Settings.SettingsLogs.resumeLogs": "恢復", "components.Settings.SettingsLogs.resumeLogs": "恢復",
@@ -636,16 +636,16 @@
"components.PersonDetails.lifespan": "{birthdate}{deathdate}", "components.PersonDetails.lifespan": "{birthdate}{deathdate}",
"components.PersonDetails.alsoknownas": "別名:{names}", "components.PersonDetails.alsoknownas": "別名:{names}",
"i18n.delimitedlist": "{a}、{b}", "i18n.delimitedlist": "{a}、{b}",
"components.Settings.SettingsUsers.tvRequestLimitLabel": "電視節目請求全域限制", "components.Settings.SettingsUsers.tvRequestLimitLabel": "影集請求全域限制",
"components.Settings.SettingsUsers.movieRequestLimitLabel": "電影請求全域限制", "components.Settings.SettingsUsers.movieRequestLimitLabel": "電影請求全域限制",
"components.RequestModal.QuotaDisplay.seasonlimit": "季數", "components.RequestModal.QuotaDisplay.seasonlimit": "季數",
"components.RequestModal.QuotaDisplay.season": "電視節目季數", "components.RequestModal.QuotaDisplay.season": "影集季數",
"components.RequestModal.QuotaDisplay.requestsremaining": "{remaining, plural, =0 {電影請求剩餘數不足} other {剩餘 <strong>#</strong> 個{type}請求}}", "components.RequestModal.QuotaDisplay.requestsremaining": "{remaining, plural, =0 {電影請求剩餘數不足} other {剩餘 <strong>#</strong> 個{type}請求}}",
"components.RequestModal.QuotaDisplay.notenoughseasonrequests": "請求剩餘數不足", "components.RequestModal.QuotaDisplay.notenoughseasonrequests": "請求剩餘數不足",
"components.RequestModal.QuotaDisplay.movielimit": "電影", "components.RequestModal.QuotaDisplay.movielimit": "電影",
"components.RequestModal.QuotaDisplay.allowedRequestsUser": "此使用者每 <strong>{days}</strong> 天能提出 <strong>{limit}</strong> 個{type}請求。", "components.RequestModal.QuotaDisplay.allowedRequestsUser": "此使用者每 <strong>{days}</strong> 天能提出 <strong>{limit}</strong> 個{type}請求。",
"components.RequestModal.QuotaDisplay.allowedRequests": "您每 <strong>{days}</strong> 天能提出 <strong>{limit}</strong> 個{type}請求。", "components.RequestModal.QuotaDisplay.allowedRequests": "您每 <strong>{days}</strong> 天能提出 <strong>{limit}</strong> 個{type}請求。",
"components.UserProfile.UserSettings.UserGeneralSettings.seriesrequestlimit": "電視節目請求限制", "components.UserProfile.UserSettings.UserGeneralSettings.seriesrequestlimit": "影集請求限制",
"components.UserProfile.UserSettings.UserGeneralSettings.movierequestlimit": "電影請求限制", "components.UserProfile.UserSettings.UserGeneralSettings.movierequestlimit": "電影請求限制",
"components.UserProfile.movierequests": "電影請求", "components.UserProfile.movierequests": "電影請求",
"components.UserProfile.limit": "{limit} 之 {remaining}", "components.UserProfile.limit": "{limit} 之 {remaining}",
@@ -656,9 +656,9 @@
"components.UserProfile.UserSettings.UserGeneralSettings.enableOverride": "覆寫全域限制", "components.UserProfile.UserSettings.UserGeneralSettings.enableOverride": "覆寫全域限制",
"components.UserProfile.unlimited": "無限", "components.UserProfile.unlimited": "無限",
"components.UserProfile.totalrequests": "請求總數", "components.UserProfile.totalrequests": "請求總數",
"components.UserProfile.seriesrequest": "電視節目請求", "components.UserProfile.seriesrequest": "影集請求",
"i18n.view": "檢視", "i18n.view": "檢視",
"i18n.tvshow": "電視節目", "i18n.tvshow": "影集",
"i18n.testing": "測試中…", "i18n.testing": "測試中…",
"i18n.test": "測試", "i18n.test": "測試",
"i18n.status": "狀態", "i18n.status": "狀態",
@@ -677,8 +677,8 @@
"i18n.back": "返回", "i18n.back": "返回",
"i18n.all": "所有", "i18n.all": "所有",
"i18n.areyousure": "確定嗎?", "i18n.areyousure": "確定嗎?",
"components.RequestModal.QuotaDisplay.requiredquotaUser": "此使用者的電視節目請求數量必須至少剩餘 <strong>{seasons}</strong> 個季數才能提出此節目請求。", "components.RequestModal.QuotaDisplay.requiredquotaUser": "此使用者的影集請求數量必須至少剩餘 <strong>{seasons}</strong> 個季數才能提出此影集請求。",
"components.RequestModal.QuotaDisplay.requiredquota": "您的電視節目請求數量必須至少剩餘 <strong>{seasons}</strong> 個季數才能提出此節目請求。", "components.RequestModal.QuotaDisplay.requiredquota": "您的影集請求數量必須至少剩餘 <strong>{seasons}</strong> 個季數才能提出此影集請求。",
"components.TvDetails.originaltitle": "原始標題", "components.TvDetails.originaltitle": "原始標題",
"components.MovieDetails.originaltitle": "原始標題", "components.MovieDetails.originaltitle": "原始標題",
"components.RequestModal.QuotaDisplay.quotaLinkUser": "訪問此使用者的<ProfileLink>個人資料頁面</ProfileLink>以查看使用者的請求限制 。", "components.RequestModal.QuotaDisplay.quotaLinkUser": "訪問此使用者的<ProfileLink>個人資料頁面</ProfileLink>以查看使用者的請求限制 。",
@@ -734,7 +734,7 @@
"components.Settings.noDefaultNon4kServer": "如果您只有一個 {serverType} 伺服器,請勿把它設定為 4K 伺服器。", "components.Settings.noDefaultNon4kServer": "如果您只有一個 {serverType} 伺服器,請勿把它設定為 4K 伺服器。",
"components.Settings.noDefaultServer": "您必須至少指定一個預設 {serverType} 伺服器,才能處理{mediaType}請求。", "components.Settings.noDefaultServer": "您必須至少指定一個預設 {serverType} 伺服器,才能處理{mediaType}請求。",
"components.Settings.serviceSettingsDescription": "關於 {serverType} 伺服器的設定。{serverType} 伺服器數沒有最大值限制,但您只能指定兩個預設伺服器(一個非 4K、一個 4K。", "components.Settings.serviceSettingsDescription": "關於 {serverType} 伺服器的設定。{serverType} 伺服器數沒有最大值限制,但您只能指定兩個預設伺服器(一個非 4K、一個 4K。",
"components.Settings.mediaTypeSeries": "電視節目", "components.Settings.mediaTypeSeries": "影集",
"components.Settings.mediaTypeMovie": "電影", "components.Settings.mediaTypeMovie": "電影",
"components.UserProfile.UserSettings.UserPasswordChange.noPasswordSet": "此使用者的帳戶目前沒有設密碼。若在以下設定密碼,此使用者就能使用「本地登入」。", "components.UserProfile.UserSettings.UserPasswordChange.noPasswordSet": "此使用者的帳戶目前沒有設密碼。若在以下設定密碼,此使用者就能使用「本地登入」。",
"components.UserProfile.UserSettings.UserPasswordChange.noPasswordSetOwnAccount": "您的帳戶目前沒有設密碼。若在以下設定密碼,您就能使用「本地登入」。", "components.UserProfile.UserSettings.UserPasswordChange.noPasswordSetOwnAccount": "您的帳戶目前沒有設密碼。若在以下設定密碼,您就能使用「本地登入」。",
@@ -792,10 +792,10 @@
"components.Settings.Notifications.NotificationsWebhook.toastWebhookTestSuccess": "Webhook 測試通知已發送!", "components.Settings.Notifications.NotificationsWebhook.toastWebhookTestSuccess": "Webhook 測試通知已發送!",
"components.Settings.SettingsUsers.newPlexLoginTip": "讓還沒匯入的 Plex 使用者登入", "components.Settings.SettingsUsers.newPlexLoginTip": "讓還沒匯入的 Plex 使用者登入",
"components.Settings.SettingsUsers.newPlexLogin": "允許新的 Plex 登入", "components.Settings.SettingsUsers.newPlexLogin": "允許新的 Plex 登入",
"components.PermissionEdit.requestTv": "提出電視節目請求", "components.PermissionEdit.requestTv": "提出影集請求",
"components.PermissionEdit.requestMovies": "提出電影請求", "components.PermissionEdit.requestMovies": "提出電影請求",
"components.PermissionEdit.requestMoviesDescription": "授予提出非 4K 電影請求的權限。", "components.PermissionEdit.requestMoviesDescription": "授予提出非 4K 電影請求的權限。",
"components.PermissionEdit.requestTvDescription": "授予提出非 4K 電視節目請求的權限。", "components.PermissionEdit.requestTvDescription": "授予提出非 4K 影集請求的權限。",
"components.UserProfile.UserSettings.UserGeneralSettings.languageDefault": "預設設定({language}", "components.UserProfile.UserSettings.UserGeneralSettings.languageDefault": "預設設定({language}",
"components.Settings.locale": "顯示語言", "components.Settings.locale": "顯示語言",
"components.DownloadBlock.estimatedtime": "預計:{time}", "components.DownloadBlock.estimatedtime": "預計:{time}",
@@ -879,16 +879,16 @@
"components.ManageSlideOver.manageModalNoRequests": "沒有請求。", "components.ManageSlideOver.manageModalNoRequests": "沒有請求。",
"components.ManageSlideOver.openarr": "開啟 {arr} 伺服器", "components.ManageSlideOver.openarr": "開啟 {arr} 伺服器",
"components.ManageSlideOver.openarr4k": "開啟 4K {arr} 伺服器", "components.ManageSlideOver.openarr4k": "開啟 4K {arr} 伺服器",
"components.ManageSlideOver.movie": "電影", "components.ManageSlideOver.movie": "電影",
"components.ManageSlideOver.markavailable": "標記為可觀看", "components.ManageSlideOver.markavailable": "標記為可觀看",
"components.IssueModal.issueAudio": "音訊", "components.IssueModal.issueAudio": "音訊",
"components.ManageSlideOver.downloadstatus": "下載狀態", "components.ManageSlideOver.downloadstatus": "下載狀態",
"components.IssueModal.CreateIssueModal.allepisodes": "所有集數", "components.IssueModal.CreateIssueModal.allepisodes": "所有集數",
"components.ManageSlideOver.manageModalClearMediaWarning": "※這將會刪除包括使用者請求在內所有有關{mediaType}的資料。如果這{mediaType}存在於您的 Plex 伺服器,資料將會在媒體庫掃描時重新建立。", "components.ManageSlideOver.manageModalClearMediaWarning": "※這將會刪除包括使用者請求在內所有有關{mediaType}的資料。如果這{mediaType}存在於您的 Plex 伺服器,資料將會在媒體庫掃描時重新建立。",
"components.ManageSlideOver.mark4kavailable": "標記 4K 版為可觀看", "components.ManageSlideOver.mark4kavailable": "標記 4K 版為可觀看",
"components.IssueModal.issueSubtitles": "字幕", "components.IssueModal.issueSubtitles": "字幕",
"components.IssueModal.issueOther": "其他", "components.IssueModal.issueOther": "其他",
"components.ManageSlideOver.tvshow": "個電視節目", "components.ManageSlideOver.tvshow": "影集",
"components.ManageSlideOver.manageModalTitle": "管理{mediaType}", "components.ManageSlideOver.manageModalTitle": "管理{mediaType}",
"components.IssueModal.issueVideo": "影片", "components.IssueModal.issueVideo": "影片",
"components.IssueList.IssueItem.issuetype": "類型", "components.IssueList.IssueItem.issuetype": "類型",
@@ -1053,19 +1053,19 @@
"components.RequestList.RequestItem.tvdbid": "TheTVDB ID", "components.RequestList.RequestItem.tvdbid": "TheTVDB ID",
"components.Discover.plexwatchlist": "您的 Plex Watchlist", "components.Discover.plexwatchlist": "您的 Plex Watchlist",
"components.PermissionEdit.autorequestMovies": "自動提出電影請求", "components.PermissionEdit.autorequestMovies": "自動提出電影請求",
"components.PermissionEdit.autorequestSeries": "自動提出電視節目請求", "components.PermissionEdit.autorequestSeries": "自動提出影集請求",
"components.Settings.SettingsJobsCache.plex-watchlist-sync": "Plex Watchlist 同步", "components.Settings.SettingsJobsCache.plex-watchlist-sync": "Plex Watchlist 同步",
"components.PermissionEdit.autorequest": "自動提出請求", "components.PermissionEdit.autorequest": "自動提出請求",
"components.Discover.DiscoverWatchlist.discoverwatchlist": "您的 Plex Watchlist", "components.Discover.DiscoverWatchlist.discoverwatchlist": "您的 Plex Watchlist",
"components.UserProfile.UserSettings.UserGeneralSettings.plexwatchlistsyncmovies": "自動提出電影請求", "components.UserProfile.UserSettings.UserGeneralSettings.plexwatchlistsyncmovies": "自動提出電影請求",
"components.UserProfile.UserSettings.UserGeneralSettings.plexwatchlistsyncseries": "自動提出電視節目請求", "components.UserProfile.UserSettings.UserGeneralSettings.plexwatchlistsyncseries": "自動提出影集請求",
"components.NotificationTypeSelector.mediaautorequested": "請求自動提出", "components.NotificationTypeSelector.mediaautorequested": "請求自動提出",
"components.PermissionEdit.autorequestMoviesDescription": "授予從 Plex Watchlist 中自動提出非 4K 電影請求的權限。", "components.PermissionEdit.autorequestMoviesDescription": "授予從 Plex Watchlist 中自動提出非 4K 電影請求的權限。",
"components.UserProfile.UserSettings.UserGeneralSettings.plexwatchlistsyncmoviestip": "從您的 <PlexWatchlistSupportLink>Plex Watchlist</PlexWatchlistSupportLink> 中自動提出電影請求", "components.UserProfile.UserSettings.UserGeneralSettings.plexwatchlistsyncmoviestip": "從您的 <PlexWatchlistSupportLink>Plex Watchlist</PlexWatchlistSupportLink> 中自動提出電影請求",
"components.NotificationTypeSelector.mediaautorequestedDescription": "當您的 Plex Watchlist 中的媒體自動提出請求時取得通知。", "components.NotificationTypeSelector.mediaautorequestedDescription": "當您的 Plex Watchlist 中的媒體自動提出請求時取得通知。",
"components.PermissionEdit.autorequestDescription": "授予從 Plex Watchlist 中自動提出非 4K 媒體請求的權限。", "components.PermissionEdit.autorequestDescription": "授予從 Plex Watchlist 中自動提出非 4K 媒體請求的權限。",
"components.PermissionEdit.autorequestSeriesDescription": "授予從 Plex Watchlist 中自動提出非 4K 電視節目請求的權限。", "components.PermissionEdit.autorequestSeriesDescription": "授予從 Plex Watchlist 中自動提出非 4K 影集請求的權限。",
"components.UserProfile.UserSettings.UserGeneralSettings.plexwatchlistsyncseriestip": "從您的 <PlexWatchlistSupportLink>Plex Watchlist</PlexWatchlistSupportLink> 中自動提出電視節目請求", "components.UserProfile.UserSettings.UserGeneralSettings.plexwatchlistsyncseriestip": "從您的 <PlexWatchlistSupportLink>Plex Watchlist</PlexWatchlistSupportLink> 中自動提出影集請求",
"components.Settings.SettingsLogs.viewdetails": "查看詳細信息", "components.Settings.SettingsLogs.viewdetails": "查看詳細信息",
"components.TvDetails.reportissue": "報告問題", "components.TvDetails.reportissue": "報告問題",
"components.MovieDetails.managemovie": "管理電影", "components.MovieDetails.managemovie": "管理電影",
@@ -1074,10 +1074,10 @@
"components.MovieDetails.reportissue": "報告問題", "components.MovieDetails.reportissue": "報告問題",
"components.PermissionEdit.viewwatchlists": "查看 Plex Watchlists", "components.PermissionEdit.viewwatchlists": "查看 Plex Watchlists",
"components.PermissionEdit.viewwatchlistsDescription": "授予查看其他使用者的 Plex Watchlists 的權限。", "components.PermissionEdit.viewwatchlistsDescription": "授予查看其他使用者的 Plex Watchlists 的權限。",
"components.TvDetails.manageseries": "管理電視節目", "components.TvDetails.manageseries": "管理影集",
"components.Settings.restartrequiredTooltip": "Jellyseerr 必須重新啟動才能應用設定的變更", "components.Settings.restartrequiredTooltip": "Jellyseerr 必須重新啟動才能應用設定的變更",
"components.Layout.UserDropdown.MiniQuotaDisplay.movierequests": "電影請求", "components.Layout.UserDropdown.MiniQuotaDisplay.movierequests": "電影請求",
"components.Layout.UserDropdown.MiniQuotaDisplay.seriesrequests": "電視節目請求", "components.Layout.UserDropdown.MiniQuotaDisplay.seriesrequests": "影集請求",
"components.Layout.UserDropdown.requests": "請求", "components.Layout.UserDropdown.requests": "請求",
"components.TvDetails.seasonstitle": "季數", "components.TvDetails.seasonstitle": "季數",
"components.RequestBlock.decline": "拒絕請求", "components.RequestBlock.decline": "拒絕請求",
@@ -1109,10 +1109,10 @@
"components.RequestModal.requestmovietitle": "提出電影請求", "components.RequestModal.requestmovietitle": "提出電影請求",
"components.RequestModal.requestmovie4ktitle": "提出 4K 電影請求", "components.RequestModal.requestmovie4ktitle": "提出 4K 電影請求",
"components.RequestModal.requestcollection4ktitle": "提出 4K 電影系列請求", "components.RequestModal.requestcollection4ktitle": "提出 4K 電影系列請求",
"components.RequestModal.requestseriestitle": "提出電視節目請求", "components.RequestModal.requestseriestitle": "提出影集請求",
"components.RequestModal.requestseries4ktitle": "提出 4K 電視節目請求", "components.RequestModal.requestseries4ktitle": "提出 4K 影集請求",
"components.RequestModal.requestcollectiontitle": "提出電影系列請求", "components.RequestModal.requestcollectiontitle": "提出電影系列請求",
"components.RequestModal.SearchByNameModal.nomatches": "找不到此電視節目的數據。", "components.RequestModal.SearchByNameModal.nomatches": "找不到此影集的數據。",
"components.UserProfile.emptywatchlist": "您的 <PlexWatchlistSupportLink>Plex Watchlist</PlexWatchlistSupportLink> 中的媒體會顯示在這裡。", "components.UserProfile.emptywatchlist": "您的 <PlexWatchlistSupportLink>Plex Watchlist</PlexWatchlistSupportLink> 中的媒體會顯示在這裡。",
"components.Discover.emptywatchlist": "您的 <PlexWatchlistSupportLink>Plex Watchlist</PlexWatchlistSupportLink> 中的媒體會顯示在這裡。", "components.Discover.emptywatchlist": "您的 <PlexWatchlistSupportLink>Plex Watchlist</PlexWatchlistSupportLink> 中的媒體會顯示在這裡。",
"components.Settings.advancedTooltip": "錯誤的設定可能會破壞應用程式功能", "components.Settings.advancedTooltip": "錯誤的設定可能會破壞應用程式功能",

View File

@@ -11,6 +11,7 @@
body { body {
@apply bg-gray-900; @apply bg-gray-900;
overscroll-behavior-y: contain;
} }
code { code {
@@ -453,3 +454,22 @@
} }
} }
} }
.ptr--ptr {
box-shadow: initial !important;
position: absolute !important;
z-index: 30 !important;
}
.ptr--refresh {
overflow: visible !important;
z-index: 30 !important;
}
.ptr--pull {
z-index: 30 !important;
}
.ptr--ptr .ptr--box {
margin-bottom: -13px !important;
}

View File

@@ -3095,6 +3095,11 @@
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf" resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf"
integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w== integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==
"@types/pulltorefreshjs@0.1.5":
version "0.1.5"
resolved "https://registry.yarnpkg.com/@types/pulltorefreshjs/-/pulltorefreshjs-0.1.5.tgz#f15c9dbc91b8fdd8135093d81ece9e9d4d2324d7"
integrity sha512-/VRTgBettvBg1KI8mGnA9oeWs359tTXQ7qsxLuXnksL88jvK6ZNMStG5T9x9vUO9O7jLsgREB0cElz/BWFfdew==
"@types/qs@*": "@types/qs@*":
version "6.9.7" version "6.9.7"
resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb"
@@ -4949,6 +4954,11 @@ cron-parser@^3.5.0:
is-nan "^1.3.2" is-nan "^1.3.2"
luxon "^1.26.0" luxon "^1.26.0"
cronstrue@2.11.0:
version "2.11.0"
resolved "https://registry.yarnpkg.com/cronstrue/-/cronstrue-2.11.0.tgz#18ff1b95a836b9b4e06854f796db2dc8fa98ce41"
integrity sha512-iIBCSis5yqtFYWtJAmNOiwDveFWWIn+8uV5UYuPHYu/Aeu5CSSJepSbaHMyfc+pPFgnsCcGzfPQEo7LSGmWbTg==
cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3: cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3:
version "7.0.3" version "7.0.3"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
@@ -5028,6 +5038,11 @@ csurf@1.11.0:
csrf "3.1.0" csrf "3.1.0"
http-errors "~1.7.3" http-errors "~1.7.3"
cy-mobile-commands@0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/cy-mobile-commands/-/cy-mobile-commands-0.3.0.tgz#2bf242093149154d846b755977da197b4730429e"
integrity sha512-Bj5P2ylw88hPqolLu68xWB6euVH5uNt8zyh+Ju8sBukGv39mWZxpjp6LtnUX/LK/YMthwvILYHhvr9SG1TP+4w==
cypress@10.6.0: cypress@10.6.0:
version "10.6.0" version "10.6.0"
resolved "https://registry.yarnpkg.com/cypress/-/cypress-10.6.0.tgz#13f46867febf2c3715874ed5dce9c2e946b175fe" resolved "https://registry.yarnpkg.com/cypress/-/cypress-10.6.0.tgz#13f46867febf2c3715874ed5dce9c2e946b175fe"
@@ -10302,6 +10317,11 @@ pug@3.0.2, pug@^3.0.2:
pug-runtime "^3.0.1" pug-runtime "^3.0.1"
pug-strip-comments "^2.0.0" pug-strip-comments "^2.0.0"
pulltorefreshjs@0.1.22:
version "0.1.22"
resolved "https://registry.yarnpkg.com/pulltorefreshjs/-/pulltorefreshjs-0.1.22.tgz#ddb5e3feee0b2a49fd46e1b18e84fffef2c47ac0"
integrity sha512-haxNVEHnS4NCQA7NeG7TSV69z4uqy/N7nfPRuc4dPWe8H6ygUrMjdNeohE+6v0lVVX/ukSjbLYwPUGUYtFKfvQ==
pump@^3.0.0: pump@^3.0.0:
version "3.0.0" version "3.0.0"
resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64"