New: Show detailed queue status on Calendar

(cherry picked from commit 8fff59ff107d9a9fcfc0de1acb6aa635565e5d9b)
This commit is contained in:
Mark McDowall 2022-12-29 19:08:14 -08:00 committed by Bogdan
parent 69253a4ac4
commit a798556d32
15 changed files with 308 additions and 266 deletions

View file

@ -0,0 +1,5 @@
.progressBarContainer {
display: flex;
justify-content: center;
width: 100%;
}

View file

@ -0,0 +1,7 @@
// This file is automatically generated.
// Please do not change this file!
interface CssExports {
'progressBarContainer': string;
}
export const cssExports: CssExports;
export default cssExports;

View file

@ -1,116 +1,71 @@
import moment from 'moment';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React from 'react'; import React from 'react';
import Icon from 'Components/Icon'; import Icon from 'Components/Icon';
import { icons, kinds } from 'Helpers/Props'; import Popover from 'Components/Tooltip/Popover';
import { icons, tooltipPositions } from 'Helpers/Props';
import translate from 'Utilities/String/translate'; import translate from 'Utilities/String/translate';
import QueueStatus from './QueueStatus';
import styles from './QueueDetails.css';
function QueueDetails(props) { function QueueDetails(props) {
const { const {
title, title,
size, size,
sizeleft, sizeleft,
estimatedCompletionTime,
status, status,
trackedDownloadState, trackedDownloadState,
trackedDownloadStatus, trackedDownloadStatus,
statusMessages,
errorMessage, errorMessage,
progressBar progressBar
} = props; } = props;
const progress = size ? (100 - sizeleft / size * 100) : 0; const progress = size ? (100 - sizeleft / size * 100) : 0;
const isDownloading = status === 'downloading';
const isPaused = status === 'paused';
const hasWarning = trackedDownloadStatus === 'warning';
const hasError = trackedDownloadStatus === 'error';
if (status === 'pending') { if (
return ( (isDownloading || isPaused) &&
<Icon !hasWarning &&
name={icons.PENDING} !hasError
title={translate('ReleaseWillBeProcessedInterp', [moment(estimatedCompletionTime).fromNow()])} ) {
/> const state = isPaused ? translate('Paused') : translate('Downloading');
);
}
if (status === 'completed') { if (progress < 5) {
if (errorMessage) {
return ( return (
<Icon <Icon
name={icons.DOWNLOAD} name={icons.DOWNLOADING}
kind={kinds.DANGER} title={`${state} - ${progress.toFixed(1)}% ${title}`}
title={translate('ImportFailedInterp', { errorMessage })}
/> />
); );
} }
if (trackedDownloadStatus === 'warning') {
return (
<Icon
name={icons.DOWNLOAD}
kind={kinds.WARNING}
title={translate('UnableToImportCheckLogs')}
/>
);
}
if (trackedDownloadState === 'importPending') {
return (
<Icon
name={icons.DOWNLOAD}
kind={kinds.PURPLE}
title={`${translate('Downloaded')} - ${translate('WaitingToImport')}`}
/>
);
}
if (trackedDownloadState === 'importing') {
return (
<Icon
name={icons.DOWNLOAD}
kind={kinds.PURPLE}
title={`${translate('Downloaded')} - ${translate('Importing')}`}
/>
);
}
}
if (errorMessage) {
return ( return (
<Icon <Popover
name={icons.DOWNLOADING} className={styles.progressBarContainer}
kind={kinds.DANGER} anchor={progressBar}
title={translate('DownloadFailedInterp', { errorMessage })} title={`${state} - ${progress.toFixed(1)}%`}
body={
<div>{title}</div>
}
position={tooltipPositions.LEFT}
/> />
); );
} }
if (status === 'failed') { return (
return ( <QueueStatus
<Icon sourceTitle={title}
name={icons.DOWNLOADING} status={status}
kind={kinds.DANGER} trackedDownloadStatus={trackedDownloadStatus}
title={translate('DownloadFailedCheckDownloadClientForMoreDetails')} trackedDownloadState={trackedDownloadState}
/> statusMessages={statusMessages}
); errorMessage={errorMessage}
} position={tooltipPositions.LEFT}
/>
if (status === 'warning') { );
return (
<Icon
name={icons.DOWNLOADING}
kind={kinds.WARNING}
title={translate('DownloadWarningCheckDownloadClientForMoreDetails')}
/>
);
}
if (progress < 5) {
return (
<Icon
name={icons.DOWNLOADING}
title={translate('MovieIsDownloadingInterp', [progress.toFixed(1), title])}
/>
);
}
return progressBar;
} }
QueueDetails.propTypes = { QueueDetails.propTypes = {
@ -121,6 +76,7 @@ QueueDetails.propTypes = {
status: PropTypes.string.isRequired, status: PropTypes.string.isRequired,
trackedDownloadState: PropTypes.string.isRequired, trackedDownloadState: PropTypes.string.isRequired,
trackedDownloadStatus: PropTypes.string.isRequired, trackedDownloadStatus: PropTypes.string.isRequired,
statusMessages: PropTypes.arrayOf(PropTypes.object),
errorMessage: PropTypes.string, errorMessage: PropTypes.string,
progressBar: PropTypes.node.isRequired progressBar: PropTypes.node.isRequired
}; };

View file

@ -0,0 +1,3 @@
.noMessages {
margin-bottom: 10px;
}

View file

@ -0,0 +1,7 @@
// This file is automatically generated.
// Please do not change this file!
interface CssExports {
'noMessages': string;
}
export const cssExports: CssExports;
export default cssExports;

View file

@ -0,0 +1,162 @@
import PropTypes from 'prop-types';
import React from 'react';
import Icon from 'Components/Icon';
import Popover from 'Components/Tooltip/Popover';
import { icons, kinds, tooltipPositions } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import styles from './QueueStatus.css';
function getDetailedPopoverBody(statusMessages) {
return (
<div>
{
statusMessages.map(({ title, messages }) => {
return (
<div
key={title}
className={messages.length ? undefined: styles.noMessages}
>
{title}
<ul>
{
messages.map((message) => {
return (
<li key={message}>
{message}
</li>
);
})
}
</ul>
</div>
);
})
}
</div>
);
}
function QueueStatus(props) {
const {
sourceTitle,
status,
trackedDownloadStatus,
trackedDownloadState,
statusMessages,
errorMessage,
position,
canFlip
} = props;
const hasWarning = trackedDownloadStatus === 'warning';
const hasError = trackedDownloadStatus === 'error';
// status === 'downloading'
let iconName = icons.DOWNLOADING;
let iconKind = kinds.DEFAULT;
let title = translate('Downloading');
if (status === 'paused') {
iconName = icons.PAUSED;
title = translate('Paused');
}
if (status === 'queued') {
iconName = icons.QUEUED;
title = translate('Queued');
}
if (status === 'completed') {
iconName = icons.DOWNLOADED;
title = translate('Downloaded');
if (trackedDownloadState === 'importPending') {
title += ` - ${translate('WaitingToImport')}`;
iconKind = kinds.PURPLE;
}
if (trackedDownloadState === 'importing') {
title += ` - ${translate('Importing')}`;
iconKind = kinds.PURPLE;
}
if (trackedDownloadState === 'failedPending') {
title += ` - ${translate('WaitingToProcess')}`;
iconKind = kinds.DANGER;
}
}
if (hasWarning) {
iconKind = kinds.WARNING;
}
if (status === 'delay') {
iconName = icons.PENDING;
title = translate('Pending');
}
if (status === 'downloadClientUnavailable') {
iconName = icons.PENDING;
iconKind = kinds.WARNING;
title = translate('PendingDownloadClientUnavailable');
}
if (status === 'failed') {
iconName = icons.DOWNLOADING;
iconKind = kinds.DANGER;
title = translate('DownloadFailed');
}
if (status === 'warning') {
iconName = icons.DOWNLOADING;
iconKind = kinds.WARNING;
const warningMessage = errorMessage || translate('CheckDownloadClientForDetails');
title = translate('DownloadWarning', { warningMessage });
}
if (hasError) {
if (status === 'completed') {
iconName = icons.DOWNLOAD;
iconKind = kinds.DANGER;
title = translate('ImportFailed', { sourceTitle });
} else {
iconName = icons.DOWNLOADING;
iconKind = kinds.DANGER;
title = translate('DownloadFailed');
}
}
return (
<Popover
anchor={
<Icon
name={iconName}
kind={iconKind}
/>
}
title={title}
body={hasWarning || hasError ? getDetailedPopoverBody(statusMessages) : sourceTitle}
position={position}
canFlip={canFlip}
/>
);
}
QueueStatus.propTypes = {
sourceTitle: PropTypes.string.isRequired,
status: PropTypes.string.isRequired,
trackedDownloadStatus: PropTypes.string.isRequired,
trackedDownloadState: PropTypes.string.isRequired,
statusMessages: PropTypes.arrayOf(PropTypes.object),
errorMessage: PropTypes.string,
position: PropTypes.oneOf(tooltipPositions.all).isRequired,
canFlip: PropTypes.bool.isRequired
};
QueueStatus.defaultProps = {
trackedDownloadStatus: 'ok',
trackedDownloadState: 'downloading',
canFlip: false
};
export default QueueStatus;

View file

@ -1,39 +1,11 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React from 'react'; import React from 'react';
import Icon from 'Components/Icon';
import TableRowCell from 'Components/Table/Cells/TableRowCell'; import TableRowCell from 'Components/Table/Cells/TableRowCell';
import Popover from 'Components/Tooltip/Popover'; import { tooltipPositions } from 'Helpers/Props';
import { icons, kinds, tooltipPositions } from 'Helpers/Props';
import translate from 'Utilities/String/translate'; import translate from 'Utilities/String/translate';
import QueueStatus from './QueueStatus';
import styles from './QueueStatusCell.css'; import styles from './QueueStatusCell.css';
function getDetailedPopoverBody(statusMessages) {
return (
<div>
{
statusMessages.map(({ title, messages }) => {
return (
<div key={title}>
{title}
<ul>
{
messages.map((message) => {
return (
<li key={message}>
{message}
</li>
);
})
}
</ul>
</div>
);
})
}
</div>
);
}
function QueueStatusCell(props) { function QueueStatusCell(props) {
const { const {
sourceTitle, sourceTitle,
@ -44,97 +16,16 @@ function QueueStatusCell(props) {
errorMessage errorMessage
} = props; } = props;
const hasWarning = trackedDownloadStatus === 'warning';
const hasError = trackedDownloadStatus === 'error';
// status === 'downloading'
let iconName = icons.DOWNLOADING;
let iconKind = kinds.DEFAULT;
let title = translate('Downloading');
if (status === 'paused') {
iconName = icons.PAUSED;
title = translate('Paused');
}
if (status === 'queued') {
iconName = icons.QUEUED;
title = translate('Queued');
}
if (status === 'completed') {
iconName = icons.DOWNLOADED;
title = translate('Downloaded');
if (trackedDownloadState === 'importPending') {
title += ` - ${translate('WaitingToImport')}`;
iconKind = kinds.PURPLE;
}
if (trackedDownloadState === 'importing') {
title += ` - ${translate('Importing')}`;
iconKind = kinds.PURPLE;
}
if (trackedDownloadState === 'failedPending') {
title += ` - ${translate('WaitingToProcess')}`;
iconKind = kinds.DANGER;
}
}
if (hasWarning) {
iconKind = kinds.WARNING;
}
if (status === 'delay') {
iconName = icons.PENDING;
title = translate('Pending');
}
if (status === 'DownloadClientUnavailable') {
iconName = icons.PENDING;
iconKind = kinds.WARNING;
title = `${translate('Pending')} - ${translate('DownloadClientUnavailable')}`;
}
if (status === 'failed') {
iconName = icons.DOWNLOADING;
iconKind = kinds.DANGER;
title = translate('DownloadFailed');
}
if (status === 'warning') {
iconName = icons.DOWNLOADING;
iconKind = kinds.WARNING;
const warningMessage = errorMessage || translate('CheckDownloadClientForDetails');
title = translate('DownloadWarning', { warningMessage });
}
if (hasError) {
if (status === 'completed') {
iconName = icons.DOWNLOAD;
iconKind = kinds.DANGER;
title = translate('ImportFailed', { sourceTitle });
} else {
iconName = icons.DOWNLOADING;
iconKind = kinds.DANGER;
title = translate('DownloadFailed');
}
}
return ( return (
<TableRowCell className={styles.status}> <TableRowCell className={styles.status}>
<Popover <QueueStatus
anchor={ sourceTitle={sourceTitle}
<Icon status={status}
name={iconName} trackedDownloadStatus={trackedDownloadStatus}
kind={iconKind} trackedDownloadState={trackedDownloadState}
/> statusMessages={statusMessages}
} errorMessage={errorMessage}
title={title}
body={hasWarning || hasError ? getDetailedPopoverBody(statusMessages) : sourceTitle}
position={tooltipPositions.RIGHT} position={tooltipPositions.RIGHT}
canFlip={false}
/> />
</TableRowCell> </TableRowCell>
); );

View file

@ -1,17 +1,28 @@
.event { .event {
display: flex; position: relative;
overflow-x: hidden;
padding: 5px; padding: 5px;
border-bottom: 1px solid var(--borderColor); border-bottom: 1px solid var(--borderColor);
font-size: $defaultFontSize; }
.underlay {
@add-mixin cover;
&:hover { &:hover {
background-color: var(--tableRowHoverBackgroundColor); background-color: var(--tableRowHoverBackgroundColor);
} }
} }
.link { .overlay {
composes: link from '~Calendar/Events/CalendarEvent.css'; @add-mixin linkOverlay;
position: relative;
display: flex;
overflow-x: hidden;
font-size: $defaultFontSize;
&:global(.colorImpaired) {
border-left-width: 5px;
}
} }
.eventWrapper { .eventWrapper {
@ -44,6 +55,8 @@
.statusIcon { .statusIcon {
margin-left: 3px; margin-left: 3px;
cursor: default;
pointer-events: all;
} }
/* /*
@ -95,6 +108,8 @@
} }
} }
.dateIcon { .releaseIcon {
margin-right: 20px;
width: 25px; width: 25px;
text-align: right;
} }

View file

@ -3,18 +3,19 @@
interface CssExports { interface CssExports {
'continuing': string; 'continuing': string;
'date': string; 'date': string;
'dateIcon': string;
'downloaded': string; 'downloaded': string;
'event': string; 'event': string;
'eventWrapper': string; 'eventWrapper': string;
'genres': string; 'genres': string;
'link': string;
'missingMonitored': string; 'missingMonitored': string;
'missingUnmonitored': string; 'missingUnmonitored': string;
'movieTitle': string; 'movieTitle': string;
'overlay': string;
'queue': string; 'queue': string;
'releaseIcon': string;
'statusIcon': string; 'statusIcon': string;
'time': string; 'time': string;
'underlay': string;
'unmonitored': string; 'unmonitored': string;
} }
export const cssExports: CssExports; export const cssExports: CssExports;

View file

@ -87,25 +87,24 @@ class AgendaEvent extends Component {
const link = `/movie/${titleSlug}`; const link = `/movie/${titleSlug}`;
return ( return (
<div> <div className={styles.event}>
<Link <Link
className={classNames( className={styles.underlay}
styles.event,
styles.link
)}
to={link} to={link}
> />
<div className={styles.dateIcon}>
<div className={styles.overlay}>
<div className={styles.date}>
{(showDate) ? startTime.format(longDateFormat) : null}
</div>
<div className={styles.releaseIcon}>
<Icon <Icon
name={releaseIcon} name={releaseIcon}
kind={kinds.DEFAULT} kind={kinds.DEFAULT}
/> />
</div> </div>
<div className={styles.date}>
{(showDate) ? startTime.format(longDateFormat) : null}
</div>
<div <div
className={classNames( className={classNames(
styles.eventWrapper, styles.eventWrapper,
@ -143,9 +142,7 @@ class AgendaEvent extends Component {
} }
{ {
showCutoffUnmetIcon && showCutoffUnmetIcon && !!movieFile && movieFile.qualityCutoffNotMet &&
!!movieFile &&
movieFile.qualityCutoffNotMet &&
<Icon <Icon
className={styles.statusIcon} className={styles.statusIcon}
name={icons.MOVIE_FILE} name={icons.MOVIE_FILE}
@ -154,7 +151,7 @@ class AgendaEvent extends Component {
/> />
} }
</div> </div>
</Link> </div>
</div> </div>
); );
} }

View file

@ -1,11 +1,22 @@
$fullColorGradient: rgba(244, 245, 246, 0.2); $fullColorGradient: rgba(244, 245, 246, 0.2);
.event { .event {
overflow-x: hidden; position: relative;
margin: 4px 2px; margin: 4px 2px;
padding: 5px; padding: 5px;
border-bottom: 1px solid var(--calendarBorderColor); border-bottom: 1px solid var(--calendarBorderColor);
border-left: 4px solid var(--calendarBorderColor); border-left: 4px solid var(--calendarBorderColor);
}
.underlay {
@add-mixin cover;
}
.overlay {
@add-mixin linkOverlay;
position: relative;
overflow-x: hidden;
font-size: 12px; font-size: 12px;
&:global(.colorImpaired) { &:global(.colorImpaired) {
@ -13,18 +24,6 @@ $fullColorGradient: rgba(244, 245, 246, 0.2);
} }
} }
.link {
composes: link from '~Components/Link/Link.css';
display: block;
color: var(--defaultColor);
&:hover {
color: var(--defaultColor);
text-decoration: none;
}
}
.info, .info,
.movieInfo { .movieInfo {
display: flex; display: flex;
@ -53,6 +52,8 @@ $fullColorGradient: rgba(244, 245, 246, 0.2);
.statusIcon { .statusIcon {
margin-left: 3px; margin-left: 3px;
cursor: default;
pointer-events: all;
} }
/* /*

View file

@ -6,14 +6,15 @@ interface CssExports {
'event': string; 'event': string;
'genres': string; 'genres': string;
'info': string; 'info': string;
'link': string;
'missingMonitored': string; 'missingMonitored': string;
'missingUnmonitored': string; 'missingUnmonitored': string;
'movieInfo': string; 'movieInfo': string;
'movieTitle': string; 'movieTitle': string;
'overlay': string;
'queue': string; 'queue': string;
'statusContainer': string; 'statusContainer': string;
'statusIcon': string; 'statusIcon': string;
'underlay': string;
'unmonitored': string; 'unmonitored': string;
} }
export const cssExports: CssExports; export const cssExports: CssExports;

View file

@ -1,7 +1,7 @@
import classNames from 'classnames'; import classNames from 'classnames';
import moment from 'moment'; import moment from 'moment';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, { Component, Fragment } from 'react'; import React, { Component } from 'react';
import Icon from 'Components/Icon'; import Icon from 'Components/Icon';
import Link from 'Components/Link/Link'; import Link from 'Components/Link/Link';
import { icons, kinds } from 'Helpers/Props'; import { icons, kinds } from 'Helpers/Props';
@ -57,18 +57,20 @@ class CalendarEvent extends Component {
} }
return ( return (
<Fragment> <div
className={classNames(
styles.event,
styles[statusStyle],
colorImpairedMode && 'colorImpaired',
fullColorEvents && 'fullColor'
)}
>
<Link <Link
className={classNames( className={styles.underlay}
styles.event,
styles.link,
styles[statusStyle],
colorImpairedMode && 'colorImpaired',
fullColorEvents && 'fullColor'
)}
// component="div"
to={link} to={link}
> />
<div className={styles.overlay} >
<div className={styles.info}> <div className={styles.info}>
<div className={styles.movieTitle}> <div className={styles.movieTitle}>
{title} {title}
@ -130,8 +132,8 @@ class CalendarEvent extends Component {
</div> : </div> :
null null
} }
</Link> </div>
</Fragment> </div>
); );
} }
} }

View file

@ -2,7 +2,6 @@ import PropTypes from 'prop-types';
import React from 'react'; import React from 'react';
import QueueDetails from 'Activity/Queue/QueueDetails'; import QueueDetails from 'Activity/Queue/QueueDetails';
import CircularProgressBar from 'Components/CircularProgressBar'; import CircularProgressBar from 'Components/CircularProgressBar';
import translate from 'Utilities/String/translate';
function CalendarEventQueueDetails(props) { function CalendarEventQueueDetails(props) {
const { const {
@ -13,6 +12,7 @@ function CalendarEventQueueDetails(props) {
status, status,
trackedDownloadState, trackedDownloadState,
trackedDownloadStatus, trackedDownloadStatus,
statusMessages,
errorMessage errorMessage
} = props; } = props;
@ -27,16 +27,15 @@ function CalendarEventQueueDetails(props) {
status={status} status={status}
trackedDownloadState={trackedDownloadState} trackedDownloadState={trackedDownloadState}
trackedDownloadStatus={trackedDownloadStatus} trackedDownloadStatus={trackedDownloadStatus}
statusMessages={statusMessages}
errorMessage={errorMessage} errorMessage={errorMessage}
progressBar={ progressBar={
<div title={translate('MovieIsDownloadingInterp', [progress.toFixed(1), title])}> <CircularProgressBar
<CircularProgressBar progress={progress}
progress={progress} size={20}
size={20} strokeWidth={2}
strokeWidth={2} strokeColor={'#7a43b6'}
strokeColor={'#7a43b6'} />
/>
</div>
} }
/> />
); );
@ -50,6 +49,7 @@ CalendarEventQueueDetails.propTypes = {
status: PropTypes.string.isRequired, status: PropTypes.string.isRequired,
trackedDownloadState: PropTypes.string.isRequired, trackedDownloadState: PropTypes.string.isRequired,
trackedDownloadStatus: PropTypes.string.isRequired, trackedDownloadStatus: PropTypes.string.isRequired,
statusMessages: PropTypes.arrayOf(PropTypes.object),
errorMessage: PropTypes.string errorMessage: PropTypes.string
}; };

View file

@ -314,14 +314,11 @@
"DownloadClientsLoadError": "Unable to load download clients", "DownloadClientsLoadError": "Unable to load download clients",
"DownloadClientsSettingsSummary": "Download clients, download handling and remote path mappings", "DownloadClientsSettingsSummary": "Download clients, download handling and remote path mappings",
"DownloadFailed": "Download failed", "DownloadFailed": "Download failed",
"DownloadFailedCheckDownloadClientForMoreDetails": "Download failed: check download client for more details",
"DownloadFailedInterp": "Download failed: {errorMessage}",
"DownloadPropersAndRepacks": "Propers and Repacks", "DownloadPropersAndRepacks": "Propers and Repacks",
"DownloadPropersAndRepacksHelpText1": "Whether or not to automatically upgrade to Propers/Repacks", "DownloadPropersAndRepacksHelpText1": "Whether or not to automatically upgrade to Propers/Repacks",
"DownloadPropersAndRepacksHelpText2": "Use 'Do not Prefer' to sort by custom format score over Propers/Repacks", "DownloadPropersAndRepacksHelpText2": "Use 'Do not Prefer' to sort by custom format score over Propers/Repacks",
"DownloadPropersAndRepacksHelpTextWarning": "Use custom formats for automatic upgrades to Propers/Repacks", "DownloadPropersAndRepacksHelpTextWarning": "Use custom formats for automatic upgrades to Propers/Repacks",
"DownloadWarning": "Download warning: {warningMessage}", "DownloadWarning": "Download warning: {warningMessage}",
"DownloadWarningCheckDownloadClientForMoreDetails": "Download warning: check download client for more details",
"Downloaded": "Downloaded", "Downloaded": "Downloaded",
"DownloadedAndMonitored": "Downloaded (Monitored)", "DownloadedAndMonitored": "Downloaded (Monitored)",
"DownloadedButNotMonitored": "Downloaded (Unmonitored)", "DownloadedButNotMonitored": "Downloaded (Unmonitored)",
@ -472,7 +469,6 @@
"ImportExtraFiles": "Import Extra Files", "ImportExtraFiles": "Import Extra Files",
"ImportExtraFilesHelpText": "Import matching extra files (subtitles, nfo, etc) after importing an movie file", "ImportExtraFilesHelpText": "Import matching extra files (subtitles, nfo, etc) after importing an movie file",
"ImportFailed": "Import failed: {sourceTitle}", "ImportFailed": "Import failed: {sourceTitle}",
"ImportFailedInterp": "Import failed: {errorMessage}",
"ImportHeader": "Import an existing organized library to add movies to Radarr", "ImportHeader": "Import an existing organized library to add movies to Radarr",
"ImportIncludeQuality": "Make sure that your files include the quality in their filenames. e.g. {0}", "ImportIncludeQuality": "Make sure that your files include the quality in their filenames. e.g. {0}",
"ImportLibrary": "Library Import", "ImportLibrary": "Library Import",
@ -674,7 +670,6 @@
"MovieInfoLanguageHelpTextWarning": "Browser Reload Required", "MovieInfoLanguageHelpTextWarning": "Browser Reload Required",
"MovieInvalidFormat": "Movie: Invalid Format", "MovieInvalidFormat": "Movie: Invalid Format",
"MovieIsDownloading": "Movie is downloading", "MovieIsDownloading": "Movie is downloading",
"MovieIsDownloadingInterp": "Movie is downloading - {0}% {1}",
"MovieIsMonitored": "Movie is monitored", "MovieIsMonitored": "Movie is monitored",
"MovieIsOnImportExclusionList": "Movie is on Import Exclusion List", "MovieIsOnImportExclusionList": "Movie is on Import Exclusion List",
"MovieIsRecommend": "Movie is recommended based on recent addition", "MovieIsRecommend": "Movie is recommended based on recent addition",
@ -798,6 +793,7 @@
"PendingChangesDiscardChanges": "Discard changes and leave", "PendingChangesDiscardChanges": "Discard changes and leave",
"PendingChangesMessage": "You have unsaved changes, are you sure you want to leave this page?", "PendingChangesMessage": "You have unsaved changes, are you sure you want to leave this page?",
"PendingChangesStayReview": "Stay and review changes", "PendingChangesStayReview": "Stay and review changes",
"PendingDownloadClientUnavailable": "Pending - Download client is unavailable",
"Permissions": "Permissions", "Permissions": "Permissions",
"PhysicalRelease": "Physical Release", "PhysicalRelease": "Physical Release",
"PhysicalReleaseDate": "Physical Release Date", "PhysicalReleaseDate": "Physical Release Date",
@ -902,7 +898,6 @@
"ReleaseRejected": "Release Rejected", "ReleaseRejected": "Release Rejected",
"ReleaseStatus": "Release Status", "ReleaseStatus": "Release Status",
"ReleaseTitle": "Release Title", "ReleaseTitle": "Release Title",
"ReleaseWillBeProcessedInterp": "Release will be processed {0}",
"Released": "Released", "Released": "Released",
"ReleasedMsg": "Movie is released", "ReleasedMsg": "Movie is released",
"Reload": "Reload", "Reload": "Reload",
@ -1208,7 +1203,6 @@
"UnableToAddANewQualityProfilePleaseTryAgain": "Unable to add a new quality profile, please try again.", "UnableToAddANewQualityProfilePleaseTryAgain": "Unable to add a new quality profile, please try again.",
"UnableToAddANewRemotePathMappingPleaseTryAgain": "Unable to add a new remote path mapping, please try again.", "UnableToAddANewRemotePathMappingPleaseTryAgain": "Unable to add a new remote path mapping, please try again.",
"UnableToAddRootFolder": "Unable to add root folder", "UnableToAddRootFolder": "Unable to add root folder",
"UnableToImportCheckLogs": "Downloaded - Unable to Import: check logs for details",
"UnableToLoadAltTitle": "Unable to load alternative titles.", "UnableToLoadAltTitle": "Unable to load alternative titles.",
"UnableToLoadAutoTagging": "Unable to load auto tagging", "UnableToLoadAutoTagging": "Unable to load auto tagging",
"UnableToLoadBackups": "Unable to load backups", "UnableToLoadBackups": "Unable to load backups",