Translate Download Clients
Main settings menu and Custom Formats Rebase onto develop
This commit is contained in:
parent
dee8820b1f
commit
bf09f1629c
|
@ -91,7 +91,8 @@ module.exports = (env) => {
|
|||
}),
|
||||
|
||||
new MiniCssExtractPlugin({
|
||||
filename: 'Content/styles.css'
|
||||
filename: 'Content/styles.css',
|
||||
chunkFilename: 'Content/[id]-[chunkhash].css'
|
||||
}),
|
||||
|
||||
new HtmlWebpackPlugin({
|
||||
|
|
|
@ -45,14 +45,14 @@ class QueueRow extends Component {
|
|||
this.setState({ isRemoveQueueItemModalOpen: true });
|
||||
};
|
||||
|
||||
onRemoveQueueItemModalConfirmed = (blocklist) => {
|
||||
onRemoveQueueItemModalConfirmed = (blocklist, skipRedownload) => {
|
||||
const {
|
||||
onRemoveQueueItemPress,
|
||||
onQueueRowModalOpenOrClose
|
||||
} = this.props;
|
||||
|
||||
onQueueRowModalOpenOrClose(false);
|
||||
onRemoveQueueItemPress(blocklist);
|
||||
onRemoveQueueItemPress(blocklist, skipRedownload);
|
||||
|
||||
this.setState({ isRemoveQueueItemModalOpen: false });
|
||||
};
|
||||
|
|
|
@ -10,6 +10,7 @@ import ModalContent from 'Components/Modal/ModalContent';
|
|||
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||
import { inputTypes, kinds, sizes } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
|
||||
class RemoveQueueItemModal extends Component {
|
||||
|
||||
|
@ -21,7 +22,8 @@ class RemoveQueueItemModal extends Component {
|
|||
|
||||
this.state = {
|
||||
remove: true,
|
||||
blocklist: false
|
||||
blocklist: false,
|
||||
skipRedownload: false
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -31,7 +33,8 @@ class RemoveQueueItemModal extends Component {
|
|||
resetState = function() {
|
||||
this.setState({
|
||||
remove: true,
|
||||
blocklist: false
|
||||
blocklist: false,
|
||||
skipRedownload: false
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -46,6 +49,10 @@ class RemoveQueueItemModal extends Component {
|
|||
this.setState({ blocklist: value });
|
||||
};
|
||||
|
||||
onSkipRedownloadChange = ({ value }) => {
|
||||
this.setState({ skipRedownload: value });
|
||||
};
|
||||
|
||||
onRemoveConfirmed = () => {
|
||||
const state = this.state;
|
||||
|
||||
|
@ -69,7 +76,7 @@ class RemoveQueueItemModal extends Component {
|
|||
isPending
|
||||
} = this.props;
|
||||
|
||||
const { remove, blocklist } = this.state;
|
||||
const { remove, blocklist, skipRedownload } = this.state;
|
||||
|
||||
return (
|
||||
<Modal
|
||||
|
@ -118,6 +125,20 @@ class RemoveQueueItemModal extends Component {
|
|||
/>
|
||||
</FormGroup>
|
||||
|
||||
{
|
||||
blocklist ?
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('SkipRedownload')}</FormLabel>
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="skipRedownload"
|
||||
value={skipRedownload}
|
||||
helpText={translate('SkipRedownloadHelpText')}
|
||||
onChange={this.onSkipRedownloadChange}
|
||||
/>
|
||||
</FormGroup> :
|
||||
null
|
||||
}
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
|
|
|
@ -23,7 +23,8 @@ class RemoveQueueItemsModal extends Component {
|
|||
|
||||
this.state = {
|
||||
remove: true,
|
||||
blocklist: false
|
||||
blocklist: false,
|
||||
skipRedownload: false
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -33,7 +34,8 @@ class RemoveQueueItemsModal extends Component {
|
|||
resetState = function() {
|
||||
this.setState({
|
||||
remove: true,
|
||||
blocklist: false
|
||||
blocklist: false,
|
||||
skipRedownload: false
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -48,6 +50,10 @@ class RemoveQueueItemsModal extends Component {
|
|||
this.setState({ blocklist: value });
|
||||
};
|
||||
|
||||
onSkipRedownloadChange = ({ value }) => {
|
||||
this.setState({ skipRedownload: value });
|
||||
};
|
||||
|
||||
onRemoveConfirmed = () => {
|
||||
const state = this.state;
|
||||
|
||||
|
@ -71,7 +77,7 @@ class RemoveQueueItemsModal extends Component {
|
|||
allPending
|
||||
} = this.props;
|
||||
|
||||
const { remove, blocklist } = this.state;
|
||||
const { remove, blocklist, skipRedownload } = this.state;
|
||||
|
||||
return (
|
||||
<Modal
|
||||
|
@ -122,6 +128,20 @@ class RemoveQueueItemsModal extends Component {
|
|||
/>
|
||||
</FormGroup>
|
||||
|
||||
{
|
||||
blocklist ?
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('SkipRedownload')}</FormLabel>
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="skipRedownload"
|
||||
value={skipRedownload}
|
||||
helpText={translate('SkipRedownloadHelpText')}
|
||||
onChange={this.onSkipRedownloadChange}
|
||||
/>
|
||||
</FormGroup> :
|
||||
null
|
||||
}
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
|
|
|
@ -1,3 +1,11 @@
|
|||
.tableScroller {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.row {
|
||||
transition: background-color 500ms;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--tableRowHoverBackgroundColor);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// This file is automatically generated.
|
||||
// Please do not change this file!
|
||||
interface CssExports {
|
||||
'row': string;
|
||||
'tableScroller': string;
|
||||
}
|
||||
export const cssExports: CssExports;
|
||||
|
|
|
@ -65,6 +65,7 @@ const Row: React.FC<ListChildComponentProps<RowItemData>> = ({
|
|||
justifyContent: 'space-between',
|
||||
...style,
|
||||
}}
|
||||
className={styles.row}
|
||||
>
|
||||
<SeriesIndexRow
|
||||
seriesId={series.id}
|
||||
|
|
|
@ -35,7 +35,7 @@ class MonitoringOptionsModalContent extends Component {
|
|||
const {
|
||||
isSaving,
|
||||
saveError
|
||||
} = prevProps;
|
||||
} = this.props;
|
||||
|
||||
if (prevProps.isSaving && !isSaving && !saveError) {
|
||||
this.setState({
|
||||
|
|
|
@ -6,11 +6,12 @@ import PageContentBody from 'Components/Page/PageContentBody';
|
|||
import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator';
|
||||
import ParseToolbarButton from 'Parse/ParseToolbarButton';
|
||||
import SettingsToolbarConnector from 'Settings/SettingsToolbarConnector';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import CustomFormatsConnector from './CustomFormats/CustomFormatsConnector';
|
||||
|
||||
function CustomFormatSettingsPage() {
|
||||
return (
|
||||
<PageContent title="Custom Format Settings">
|
||||
<PageContent title={translate('CustomFormatsSettings')}>
|
||||
<SettingsToolbarConnector
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
|
|
|
@ -5,6 +5,7 @@ import FieldSet from 'Components/FieldSet';
|
|||
import Icon from 'Components/Icon';
|
||||
import PageSectionContent from 'Components/Page/PageSectionContent';
|
||||
import { icons } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import CustomFormat from './CustomFormat';
|
||||
import EditCustomFormatModalConnector from './EditCustomFormatModalConnector';
|
||||
import styles from './CustomFormats.css';
|
||||
|
@ -58,9 +59,9 @@ class CustomFormats extends Component {
|
|||
} = this.props;
|
||||
|
||||
return (
|
||||
<FieldSet legend="Custom Formats">
|
||||
<FieldSet legend={translate('CustomFormats')}>
|
||||
<PageSectionContent
|
||||
errorMessage="Unable to load custom formats"
|
||||
errorMessage={translate('UnableToLoadCustomFormats')}
|
||||
{...otherProps}c={true}
|
||||
>
|
||||
<div className={styles.customFormats}>
|
||||
|
|
|
@ -15,6 +15,7 @@ import ModalContent from 'Components/Modal/ModalContent';
|
|||
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||
import { icons, inputTypes, kinds } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import ImportCustomFormatModal from './ImportCustomFormatModal';
|
||||
import AddSpecificationModal from './Specifications/AddSpecificationModal';
|
||||
import EditSpecificationModalConnector from './Specifications/EditSpecificationModalConnector';
|
||||
|
@ -99,7 +100,7 @@ class EditCustomFormatModalContent extends Component {
|
|||
<ModalContent onModalClose={onModalClose}>
|
||||
|
||||
<ModalHeader>
|
||||
{id ? 'Edit Custom Format' : 'Add Custom Format'}
|
||||
{id ? translate('EditCustomFormat') : translate('AddCustomFormat')}
|
||||
</ModalHeader>
|
||||
|
||||
<ModalBody>
|
||||
|
@ -112,7 +113,7 @@ class EditCustomFormatModalContent extends Component {
|
|||
{
|
||||
!isFetching && !!error &&
|
||||
<div>
|
||||
{'Unable to add a new custom format, please try again.'}
|
||||
{translate('UnableToAddANewCustomFormatPleaseTryAgain')}
|
||||
</div>
|
||||
}
|
||||
|
||||
|
@ -124,7 +125,7 @@ class EditCustomFormatModalContent extends Component {
|
|||
>
|
||||
<FormGroup>
|
||||
<FormLabel>
|
||||
Name
|
||||
{translate('Name')}
|
||||
</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
|
@ -136,19 +137,19 @@ class EditCustomFormatModalContent extends Component {
|
|||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>{'Include Custom Format when Renaming'}</FormLabel>
|
||||
<FormLabel>{translate('IncludeCustomFormatWhenRenaming')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="includeCustomFormatWhenRenaming"
|
||||
helpText={'Include in {Custom Formats} renaming format'}
|
||||
helpText={translate('IncludeCustomFormatWhenRenamingHelpText')}
|
||||
{...includeCustomFormatWhenRenaming}
|
||||
onChange={onInputChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
</Form>
|
||||
|
||||
<FieldSet legend={'Conditions'}>
|
||||
<FieldSet legend={translate('Conditions')}>
|
||||
<div className={styles.customFormats}>
|
||||
{
|
||||
specifications.map((tag) => {
|
||||
|
@ -205,7 +206,7 @@ class EditCustomFormatModalContent extends Component {
|
|||
kind={kinds.DANGER}
|
||||
onPress={onDeleteCustomFormatPress}
|
||||
>
|
||||
Delete
|
||||
{translate('Delete')}
|
||||
</Button>
|
||||
}
|
||||
|
||||
|
@ -213,14 +214,14 @@ class EditCustomFormatModalContent extends Component {
|
|||
className={styles.deleteButton}
|
||||
onPress={this.onImportPress}
|
||||
>
|
||||
Import
|
||||
{translate('Import')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
onPress={onModalClose}
|
||||
>
|
||||
Cancel
|
||||
{translate('Cancel')}
|
||||
</Button>
|
||||
|
||||
<SpinnerErrorButton
|
||||
|
@ -228,7 +229,7 @@ class EditCustomFormatModalContent extends Component {
|
|||
error={saveError}
|
||||
onPress={onSavePress}
|
||||
>
|
||||
Save
|
||||
{translate('Save')}
|
||||
</SpinnerErrorButton>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
|
|
|
@ -8,6 +8,7 @@ import ModalContent from 'Components/Modal/ModalContent';
|
|||
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||
import { kinds } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import styles from './ExportCustomFormatModalContent.css';
|
||||
|
||||
class ExportCustomFormatModalContent extends Component {
|
||||
|
@ -28,7 +29,7 @@ class ExportCustomFormatModalContent extends Component {
|
|||
<ModalContent onModalClose={onModalClose}>
|
||||
|
||||
<ModalHeader>
|
||||
Export Custom Format
|
||||
{translate('ExportCustomFormat')}
|
||||
</ModalHeader>
|
||||
|
||||
<ModalBody>
|
||||
|
@ -41,7 +42,7 @@ class ExportCustomFormatModalContent extends Component {
|
|||
{
|
||||
!isFetching && !!error &&
|
||||
<div>
|
||||
Unable to load custom formats
|
||||
{translate('UnableToLoadCustomFormats')}
|
||||
</div>
|
||||
}
|
||||
|
||||
|
@ -59,13 +60,13 @@ class ExportCustomFormatModalContent extends Component {
|
|||
<ClipboardButton
|
||||
className={styles.button}
|
||||
value={json}
|
||||
title="Copy to clipboard"
|
||||
title={translate('CopyToClipboard')}
|
||||
kind={kinds.DEFAULT}
|
||||
/>
|
||||
<Button
|
||||
onPress={onModalClose}
|
||||
>
|
||||
Close
|
||||
{translate('Close')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
|
|
|
@ -12,6 +12,7 @@ import ModalContent from 'Components/Modal/ModalContent';
|
|||
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||
import { inputTypes, sizes } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import styles from './ImportCustomFormatModalContent.css';
|
||||
|
||||
class ImportCustomFormatModalContent extends Component {
|
||||
|
@ -82,7 +83,7 @@ class ImportCustomFormatModalContent extends Component {
|
|||
<ModalContent onModalClose={onModalClose}>
|
||||
|
||||
<ModalHeader>
|
||||
Import Custom Format
|
||||
{translate('ImportCustomFormat')}
|
||||
</ModalHeader>
|
||||
|
||||
<ModalBody>
|
||||
|
@ -95,7 +96,7 @@ class ImportCustomFormatModalContent extends Component {
|
|||
{
|
||||
!isFetching && !!error &&
|
||||
<div>
|
||||
Unable to load custom formats
|
||||
{translate('UnableToLoadCustomFormats')}
|
||||
</div>
|
||||
}
|
||||
|
||||
|
@ -104,7 +105,7 @@ class ImportCustomFormatModalContent extends Component {
|
|||
<Form>
|
||||
<FormGroup size={sizes.MEDIUM}>
|
||||
<FormLabel>
|
||||
Custom Format JSON
|
||||
{translate('CustomFormatJSON')}
|
||||
</FormLabel>
|
||||
<FormInputGroup
|
||||
key={0}
|
||||
|
@ -125,14 +126,14 @@ class ImportCustomFormatModalContent extends Component {
|
|||
<Button
|
||||
onPress={onModalClose}
|
||||
>
|
||||
Cancel
|
||||
{translate('Cancel')}
|
||||
</Button>
|
||||
<SpinnerErrorButton
|
||||
onPress={this.onImportPress}
|
||||
isSpinning={isSpinning}
|
||||
error={parseError}
|
||||
>
|
||||
Import
|
||||
{translate('Import')}
|
||||
</SpinnerErrorButton>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
|
|
|
@ -6,6 +6,7 @@ import { createSelector } from 'reselect';
|
|||
import { clearPendingChanges } from 'Store/Actions/baseActions';
|
||||
import { clearCustomFormatSpecificationPending, deleteAllCustomFormatSpecification, fetchCustomFormatSpecificationSchema, saveCustomFormatSpecification, selectCustomFormatSpecificationSchema, setCustomFormatSpecificationFieldValue, setCustomFormatSpecificationValue, setCustomFormatValue } from 'Store/Actions/settingsActions';
|
||||
import createProviderSettingsSelector from 'Store/Selectors/createProviderSettingsSelector';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import ImportCustomFormatModalContent from './ImportCustomFormatModalContent';
|
||||
|
||||
function createMapStateToProps() {
|
||||
|
@ -88,7 +89,9 @@ class ImportCustomFormatModalContentConnector extends Component {
|
|||
const selectedImplementation = _.find(this.props.specificationSchema, { implementation: spec.implementation });
|
||||
|
||||
if (!selectedImplementation) {
|
||||
throw new Error(`Unknown Custom Format condition '${spec.implementation}'`);
|
||||
throw new Error(translate('CustomFormatUnknownCondition', {
|
||||
implementation: spec.implementation
|
||||
}));
|
||||
}
|
||||
|
||||
this.props.selectCustomFormatSpecificationSchema({ implementation: spec.implementation });
|
||||
|
@ -108,7 +111,10 @@ class ImportCustomFormatModalContentConnector extends Component {
|
|||
for (const [key, value] of Object.entries(fields)) {
|
||||
const field = _.find(schema.fields, { name: key });
|
||||
if (!field) {
|
||||
throw new Error(`Unknown option '${key}' for condition '${schema.implementationName}'`);
|
||||
throw new Error(translate('CustomFormatUnknownConditionOption', {
|
||||
key,
|
||||
implementation: schema.implementationName
|
||||
}));
|
||||
}
|
||||
|
||||
this.props.setCustomFormatSpecificationFieldValue({ name: key, value });
|
||||
|
|
|
@ -5,6 +5,7 @@ import Link from 'Components/Link/Link';
|
|||
import Menu from 'Components/Menu/Menu';
|
||||
import MenuContent from 'Components/Menu/MenuContent';
|
||||
import { sizes } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import AddSpecificationPresetMenuItem from './AddSpecificationPresetMenuItem';
|
||||
import styles from './AddSpecificationItem.css';
|
||||
|
||||
|
@ -57,7 +58,7 @@ class AddSpecificationItem extends Component {
|
|||
size={sizes.SMALL}
|
||||
onPress={this.onSpecificationSelect}
|
||||
>
|
||||
Custom
|
||||
{translate('Custom')}
|
||||
</Button>
|
||||
|
||||
<Menu className={styles.presetsMenu}>
|
||||
|
@ -65,7 +66,7 @@ class AddSpecificationItem extends Component {
|
|||
className={styles.presetsMenuButton}
|
||||
size={sizes.SMALL}
|
||||
>
|
||||
Presets
|
||||
{translate('Presets')}
|
||||
</Button>
|
||||
|
||||
<MenuContent>
|
||||
|
@ -90,7 +91,7 @@ class AddSpecificationItem extends Component {
|
|||
to={infoLink}
|
||||
size={sizes.SMALL}
|
||||
>
|
||||
More Info
|
||||
{translate('MoreInfo')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -9,6 +9,7 @@ import ModalContent from 'Components/Modal/ModalContent';
|
|||
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||
import { kinds } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import AddSpecificationItem from './AddSpecificationItem';
|
||||
import styles from './AddSpecificationModalContent.css';
|
||||
|
||||
|
@ -42,7 +43,7 @@ class AddSpecificationModalContent extends Component {
|
|||
{
|
||||
!isSchemaFetching && !!schemaError &&
|
||||
<div>
|
||||
{'Unable to add a new condition, please try again.'}
|
||||
{translate('UnableToAddANewConditionPleaseTryAgain')}
|
||||
</div>
|
||||
}
|
||||
|
||||
|
@ -52,11 +53,11 @@ class AddSpecificationModalContent extends Component {
|
|||
|
||||
<Alert kind={kinds.INFO}>
|
||||
<div>
|
||||
{'Sonarr supports custom conditions against the release properties below.'}
|
||||
{translate('SonarrSupportsCustomConditionsAgainstTheReleasePropertiesBelow')}
|
||||
</div>
|
||||
<div>
|
||||
{'Visit the wiki for more details: '}
|
||||
<Link to="https://wiki.servarr.com/sonarr/settings#custom-formats-2">{'Wiki'}</Link>
|
||||
{translate('VisitTheWikiForMoreDetails')}
|
||||
<Link to="https://wiki.servarr.com/sonarr/settings#custom-formats-2">{translate('Wiki')}</Link>
|
||||
</div>
|
||||
</Alert>
|
||||
|
||||
|
@ -81,7 +82,7 @@ class AddSpecificationModalContent extends Component {
|
|||
<Button
|
||||
onPress={onModalClose}
|
||||
>
|
||||
Close
|
||||
{translate('Close')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
|
|
|
@ -14,6 +14,7 @@ import ModalContent from 'Components/Modal/ModalContent';
|
|||
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||
import { inputTypes, kinds } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import styles from './EditSpecificationModalContent.css';
|
||||
|
||||
function EditSpecificationModalContent(props) {
|
||||
|
@ -40,7 +41,7 @@ function EditSpecificationModalContent(props) {
|
|||
return (
|
||||
<ModalContent onModalClose={onCancelPress}>
|
||||
<ModalHeader>
|
||||
{`${id ? 'Edit' : 'Add'} Condition - ${implementationName}`}
|
||||
{`${id ? translate('Edit') : translate('Add')} ${translate('Condition')} - ${implementationName}`}
|
||||
</ModalHeader>
|
||||
|
||||
<ModalBody>
|
||||
|
@ -51,19 +52,25 @@ function EditSpecificationModalContent(props) {
|
|||
fields && fields.some((x) => x.label === 'Regular Expression') &&
|
||||
<Alert kind={kinds.INFO}>
|
||||
<div>
|
||||
<div dangerouslySetInnerHTML={{ __html: 'This condition matches using Regular Expressions. Note that the characters <code>\\^$.|?*+()[{</code> have special meanings and need escaping with a <code>\\</code>' }} />
|
||||
{'More details'} <Link to="https://www.regular-expressions.info/tutorial.html">{'Here'}</Link>
|
||||
<div dangerouslySetInnerHTML={{
|
||||
__html: translate('ThisConditionMatchesUsingRegularExpressions', {
|
||||
specialChars: '<code>\\^$.|?*+()[{</code>',
|
||||
escapeChars: '<code>\\</code>'
|
||||
})
|
||||
}}
|
||||
/>
|
||||
{translate('MoreDetails')} <Link to="https://www.regular-expressions.info/tutorial.html">{translate('Here')}</Link>
|
||||
</div>
|
||||
<div>
|
||||
{'Regular expressions can be tested '}
|
||||
<Link to="http://regexstorm.net/tester">Here</Link>
|
||||
{translate('RegularExpressionsCanBeTested')}
|
||||
<Link to="http://regexstorm.net/tester">{translate('Here')}</Link>
|
||||
</div>
|
||||
</Alert>
|
||||
}
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>
|
||||
Name
|
||||
{translate('Name')}
|
||||
</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
|
@ -91,28 +98,28 @@ function EditSpecificationModalContent(props) {
|
|||
|
||||
<FormGroup>
|
||||
<FormLabel>
|
||||
Negate
|
||||
{translate('Negate')}
|
||||
</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="negate"
|
||||
{...negate}
|
||||
helpText={`If checked, the custom format will not apply if this ${implementationName} condition matches.`}
|
||||
helpText={translate('NegateHelpText', { implementationName })}
|
||||
onChange={onInputChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>
|
||||
Required
|
||||
{translate('Required')}
|
||||
</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="required"
|
||||
{...required}
|
||||
helpText={`This ${implementationName} condition must match for the custom format to apply. Otherwise a single ${implementationName} match is sufficient.`}
|
||||
helpText={translate('RequiredHelpText', { implementationName })}
|
||||
onChange={onInputChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
@ -126,21 +133,21 @@ function EditSpecificationModalContent(props) {
|
|||
kind={kinds.DANGER}
|
||||
onPress={onDeleteSpecificationPress}
|
||||
>
|
||||
Delete
|
||||
{translate('Delete')}
|
||||
</Button>
|
||||
}
|
||||
|
||||
<Button
|
||||
onPress={onCancelPress}
|
||||
>
|
||||
Cancel
|
||||
{translate('Cancel')}
|
||||
</Button>
|
||||
|
||||
<SpinnerErrorButton
|
||||
isSpinning={false}
|
||||
onPress={onSavePress}
|
||||
>
|
||||
Save
|
||||
{translate('Save')}
|
||||
</SpinnerErrorButton>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
|
|
|
@ -115,7 +115,7 @@ class Specification extends Component {
|
|||
isOpen={this.state.isDeleteSpecificationModalOpen}
|
||||
kind={kinds.DANGER}
|
||||
title={translate('DeleteCondition')}
|
||||
message={translate('DeleteConditionMessageText', [name])}
|
||||
message={translate('DeleteConditionMessageText', { name })}
|
||||
confirmLabel={translate('Delete')}
|
||||
onConfirm={this.onConfirmDeleteSpecification}
|
||||
onCancel={this.onDeleteSpecificationModalClose}
|
||||
|
|
|
@ -70,7 +70,7 @@ class DownloadClientSettings extends Component {
|
|||
} = this.state;
|
||||
|
||||
return (
|
||||
<PageContent title="Download Client Settings">
|
||||
<PageContent title={translate('DownloadClientSettings')}>
|
||||
<SettingsToolbarConnector
|
||||
isSaving={isSaving}
|
||||
hasPendingChanges={hasPendingChanges}
|
||||
|
@ -79,7 +79,7 @@ class DownloadClientSettings extends Component {
|
|||
<PageToolbarSeparator />
|
||||
|
||||
<PageToolbarButton
|
||||
label="Test All Clients"
|
||||
label={translate('TestAllClients')}
|
||||
iconName={icons.TEST}
|
||||
isSpinning={isTestingAll}
|
||||
onPress={dispatchTestAllDownloadClients}
|
||||
|
|
|
@ -5,6 +5,7 @@ import Link from 'Components/Link/Link';
|
|||
import Menu from 'Components/Menu/Menu';
|
||||
import MenuContent from 'Components/Menu/MenuContent';
|
||||
import { sizes } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import AddDownloadClientPresetMenuItem from './AddDownloadClientPresetMenuItem';
|
||||
import styles from './AddDownloadClientItem.css';
|
||||
|
||||
|
@ -57,7 +58,7 @@ class AddDownloadClientItem extends Component {
|
|||
size={sizes.SMALL}
|
||||
onPress={this.onDownloadClientSelect}
|
||||
>
|
||||
Custom
|
||||
{translate('Custom')}
|
||||
</Button>
|
||||
|
||||
<Menu className={styles.presetsMenu}>
|
||||
|
@ -65,7 +66,7 @@ class AddDownloadClientItem extends Component {
|
|||
className={styles.presetsMenuButton}
|
||||
size={sizes.SMALL}
|
||||
>
|
||||
Presets
|
||||
{translate('Presets')}
|
||||
</Button>
|
||||
|
||||
<MenuContent>
|
||||
|
@ -90,7 +91,7 @@ class AddDownloadClientItem extends Component {
|
|||
to={infoLink}
|
||||
size={sizes.SMALL}
|
||||
>
|
||||
More info
|
||||
{translate('MoreInfo')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -9,6 +9,7 @@ import ModalContent from 'Components/Modal/ModalContent';
|
|||
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||
import { kinds } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import AddDownloadClientItem from './AddDownloadClientItem';
|
||||
import styles from './AddDownloadClientModalContent.css';
|
||||
|
||||
|
@ -31,7 +32,7 @@ class AddDownloadClientModalContent extends Component {
|
|||
return (
|
||||
<ModalContent onModalClose={onModalClose}>
|
||||
<ModalHeader>
|
||||
Add Download Client
|
||||
{translate('AddDownloadClient')}
|
||||
</ModalHeader>
|
||||
|
||||
<ModalBody>
|
||||
|
@ -42,7 +43,9 @@ class AddDownloadClientModalContent extends Component {
|
|||
|
||||
{
|
||||
!isSchemaFetching && !!schemaError &&
|
||||
<div>Unable to add a new downloadClient, please try again.</div>
|
||||
<div>
|
||||
{translate('UnableToAddANewDownloadClientPleaseTryAgain')}
|
||||
</div>
|
||||
}
|
||||
|
||||
{
|
||||
|
@ -50,11 +53,15 @@ class AddDownloadClientModalContent extends Component {
|
|||
<div>
|
||||
|
||||
<Alert kind={kinds.INFO}>
|
||||
<div>Sonarr supports many popular torrent and usenet download clients.</div>
|
||||
<div>For more information on the individual download clients, click the more info buttons.</div>
|
||||
<div>
|
||||
{translate('SonarrSupportsAnyDownloadClient')}
|
||||
</div>
|
||||
<div>
|
||||
{translate('ForMoreInformationOnTheIndividualDownloadClients')}
|
||||
</div>
|
||||
</Alert>
|
||||
|
||||
<FieldSet legend="Usenet">
|
||||
<FieldSet legend={translate('Usenet')}>
|
||||
<div className={styles.downloadClients}>
|
||||
{
|
||||
usenetDownloadClients.map((downloadClient) => {
|
||||
|
@ -71,7 +78,7 @@ class AddDownloadClientModalContent extends Component {
|
|||
</div>
|
||||
</FieldSet>
|
||||
|
||||
<FieldSet legend="Torrents">
|
||||
<FieldSet legend={translate('Torrents')}>
|
||||
<div className={styles.downloadClients}>
|
||||
{
|
||||
torrentDownloadClients.map((downloadClient) => {
|
||||
|
@ -94,7 +101,7 @@ class AddDownloadClientModalContent extends Component {
|
|||
<Button
|
||||
onPress={onModalClose}
|
||||
>
|
||||
Close
|
||||
{translate('Close')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
|
|
|
@ -5,6 +5,7 @@ import Label from 'Components/Label';
|
|||
import ConfirmModal from 'Components/Modal/ConfirmModal';
|
||||
import TagList from 'Components/TagList';
|
||||
import { kinds } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import EditDownloadClientModalConnector from './EditDownloadClientModalConnector';
|
||||
import styles from './DownloadClient.css';
|
||||
|
||||
|
@ -75,13 +76,13 @@ class DownloadClient extends Component {
|
|||
{
|
||||
enable ?
|
||||
<Label kind={kinds.SUCCESS}>
|
||||
Enabled
|
||||
{translate('Enabled')}
|
||||
</Label> :
|
||||
<Label
|
||||
kind={kinds.DISABLED}
|
||||
outline={true}
|
||||
>
|
||||
Disabled
|
||||
{translate('Disabled')}
|
||||
</Label>
|
||||
}
|
||||
|
||||
|
@ -91,7 +92,7 @@ class DownloadClient extends Component {
|
|||
kind={kinds.DISABLED}
|
||||
outline={true}
|
||||
>
|
||||
Priority: {priority}
|
||||
{translate('PrioritySettings', { priority })}
|
||||
</Label>
|
||||
}
|
||||
</div>
|
||||
|
@ -111,9 +112,9 @@ class DownloadClient extends Component {
|
|||
<ConfirmModal
|
||||
isOpen={this.state.isDeleteDownloadClientModalOpen}
|
||||
kind={kinds.DANGER}
|
||||
title="Delete Download Client"
|
||||
message={`Are you sure you want to delete the download client '${name}'?`}
|
||||
confirmLabel="Delete"
|
||||
title={translate('DeleteDownloadClient')}
|
||||
message={translate('DeleteDownloadClientMessageText', { name })}
|
||||
confirmLabel={translate('Delete')}
|
||||
onConfirm={this.onConfirmDeleteDownloadClient}
|
||||
onCancel={this.onDeleteDownloadClientModalClose}
|
||||
/>
|
||||
|
|
|
@ -5,6 +5,7 @@ import FieldSet from 'Components/FieldSet';
|
|||
import Icon from 'Components/Icon';
|
||||
import PageSectionContent from 'Components/Page/PageSectionContent';
|
||||
import { icons } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import AddDownloadClientModal from './AddDownloadClientModal';
|
||||
import DownloadClient from './DownloadClient';
|
||||
import EditDownloadClientModalConnector from './EditDownloadClientModalConnector';
|
||||
|
@ -59,9 +60,9 @@ class DownloadClients extends Component {
|
|||
} = this.state;
|
||||
|
||||
return (
|
||||
<FieldSet legend="Download Clients">
|
||||
<FieldSet legend={translate('DownloadClients')}>
|
||||
<PageSectionContent
|
||||
errorMessage="Unable to load download clients"
|
||||
errorMessage={translate('UnableToLoadDownloadClients')}
|
||||
{...otherProps}
|
||||
>
|
||||
<div className={styles.downloadClients}>
|
||||
|
|
|
@ -15,6 +15,7 @@ import ModalContent from 'Components/Modal/ModalContent';
|
|||
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||
import { inputTypes, kinds, sizes } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import styles from './EditDownloadClientModalContent.css';
|
||||
|
||||
class EditDownloadClientModalContent extends Component {
|
||||
|
@ -57,7 +58,7 @@ class EditDownloadClientModalContent extends Component {
|
|||
return (
|
||||
<ModalContent onModalClose={onModalClose}>
|
||||
<ModalHeader>
|
||||
{`${id ? 'Edit' : 'Add'} Download Client - ${implementationName}`}
|
||||
{`${id ? translate('Edit') : translate('Add')} ${translate('DownloadClient')} - ${implementationName}`}
|
||||
</ModalHeader>
|
||||
|
||||
<ModalBody>
|
||||
|
@ -68,7 +69,9 @@ class EditDownloadClientModalContent extends Component {
|
|||
|
||||
{
|
||||
!isFetching && !!error &&
|
||||
<div>Unable to add a new download client, please try again.</div>
|
||||
<div>
|
||||
{translate('UnableToAddANewDownloadClientPleaseTryAgain')}
|
||||
</div>
|
||||
}
|
||||
|
||||
{
|
||||
|
@ -85,7 +88,7 @@ class EditDownloadClientModalContent extends Component {
|
|||
}
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>Name</FormLabel>
|
||||
<FormLabel>{translate('Name')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.TEXT}
|
||||
|
@ -96,7 +99,7 @@ class EditDownloadClientModalContent extends Component {
|
|||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>Enable</FormLabel>
|
||||
<FormLabel>{translate('Enable')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
|
@ -125,12 +128,12 @@ class EditDownloadClientModalContent extends Component {
|
|||
advancedSettings={advancedSettings}
|
||||
isAdvanced={true}
|
||||
>
|
||||
<FormLabel>Client Priority</FormLabel>
|
||||
<FormLabel>{translate('ClientPriority')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.NUMBER}
|
||||
name="priority"
|
||||
helpText="Prioritize multiple Download Clients. Round-Robin is used for clients with the same priority."
|
||||
helpText={translate('PriorityHelpText')}
|
||||
min={1}
|
||||
max={50}
|
||||
{...priority}
|
||||
|
@ -139,12 +142,12 @@ class EditDownloadClientModalContent extends Component {
|
|||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>Tags</FormLabel>
|
||||
<FormLabel>{translate('Tags')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.TAG}
|
||||
name="tags"
|
||||
helpText="Only use this download client for series with at least one matching tag. Leave blank to use with all series."
|
||||
helpText={translate('DownloadClientTagHelpText')}
|
||||
{...tags}
|
||||
onChange={onInputChange}
|
||||
/>
|
||||
|
@ -152,15 +155,15 @@ class EditDownloadClientModalContent extends Component {
|
|||
|
||||
<FieldSet
|
||||
size={sizes.SMALL}
|
||||
legend="Completed Download Handling"
|
||||
legend={translate('CompletedDownloadHandling')}
|
||||
>
|
||||
<FormGroup>
|
||||
<FormLabel>Remove Completed</FormLabel>
|
||||
<FormLabel>{translate('RemoveCompleted')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="removeCompletedDownloads"
|
||||
helpText="Remove imported downloads from download client history (when finished seeding for torrents)"
|
||||
helpText={translate('RemoveCompletedDownloadsHelpText')}
|
||||
{...removeCompletedDownloads}
|
||||
onChange={onInputChange}
|
||||
/>
|
||||
|
@ -169,12 +172,12 @@ class EditDownloadClientModalContent extends Component {
|
|||
{
|
||||
protocol.value !== 'torrent' &&
|
||||
<FormGroup>
|
||||
<FormLabel>Remove Failed</FormLabel>
|
||||
<FormLabel>{translate('RemoveFailed')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="removeFailedDownloads"
|
||||
helpText="Remove failed downloads from download client history"
|
||||
helpText={translate('RemoveFailedDownloadsHelpText')}
|
||||
{...removeFailedDownloads}
|
||||
onChange={onInputChange}
|
||||
/>
|
||||
|
@ -192,7 +195,7 @@ class EditDownloadClientModalContent extends Component {
|
|||
kind={kinds.DANGER}
|
||||
onPress={onDeleteDownloadClientPress}
|
||||
>
|
||||
Delete
|
||||
{translate('Delete')}
|
||||
</Button>
|
||||
}
|
||||
|
||||
|
@ -201,13 +204,13 @@ class EditDownloadClientModalContent extends Component {
|
|||
error={saveError}
|
||||
onPress={onTestPress}
|
||||
>
|
||||
Test
|
||||
{translate('Test')}
|
||||
</SpinnerErrorButton>
|
||||
|
||||
<Button
|
||||
onPress={onModalClose}
|
||||
>
|
||||
Cancel
|
||||
{translate('Cancel')}
|
||||
</Button>
|
||||
|
||||
<SpinnerErrorButton
|
||||
|
@ -215,7 +218,7 @@ class EditDownloadClientModalContent extends Component {
|
|||
error={saveError}
|
||||
onPress={onSavePress}
|
||||
>
|
||||
Save
|
||||
{translate('Save')}
|
||||
</SpinnerErrorButton>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
|
|
|
@ -8,6 +8,7 @@ import FormInputGroup from 'Components/Form/FormInputGroup';
|
|||
import FormLabel from 'Components/Form/FormLabel';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import { inputTypes, kinds, sizes } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
|
||||
function DownloadClientOptions(props) {
|
||||
const {
|
||||
|
@ -28,13 +29,15 @@ function DownloadClientOptions(props) {
|
|||
|
||||
{
|
||||
!isFetching && error &&
|
||||
<Alert kind={kinds.DANGER}>Unable to load download client options</Alert>
|
||||
<Alert kind={kinds.DANGER}>
|
||||
{translate('UnableToLoadDownloadClientOptions')}
|
||||
</Alert>
|
||||
}
|
||||
|
||||
{
|
||||
hasSettings && !isFetching && !error && advancedSettings &&
|
||||
<div>
|
||||
<FieldSet legend="Completed Download Handling">
|
||||
<FieldSet legend={translate('CompletedDownloadHandling')}>
|
||||
|
||||
<Form>
|
||||
<FormGroup
|
||||
|
@ -42,12 +45,12 @@ function DownloadClientOptions(props) {
|
|||
isAdvanced={true}
|
||||
size={sizes.MEDIUM}
|
||||
>
|
||||
<FormLabel>Enable</FormLabel>
|
||||
<FormLabel>{translate('Enable')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="enableCompletedDownloadHandling"
|
||||
helpText="Automatically import completed downloads from download client"
|
||||
helpText={translate('EnableCompletedDownloadHandlingHelpText')}
|
||||
onChange={onInputChange}
|
||||
{...settings.enableCompletedDownloadHandling}
|
||||
/>
|
||||
|
@ -58,12 +61,12 @@ function DownloadClientOptions(props) {
|
|||
isAdvanced={true}
|
||||
size={sizes.MEDIUM}
|
||||
>
|
||||
<FormLabel>Redownload Failed</FormLabel>
|
||||
<FormLabel>{translate('RedownloadFailed')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="autoRedownloadFailed"
|
||||
helpText="Automatically search for and attempt to download a different release"
|
||||
helpText={translate('AutoRedownloadFailedHelpText')}
|
||||
onChange={onInputChange}
|
||||
{...settings.autoRedownloadFailed}
|
||||
/>
|
||||
|
@ -71,7 +74,7 @@ function DownloadClientOptions(props) {
|
|||
</Form>
|
||||
|
||||
<Alert kind={kinds.INFO}>
|
||||
The Remove settings were moved to the individual Download Client settings in the table above.
|
||||
{translate('RemoveDownloadsAlert')}
|
||||
</Alert>
|
||||
</FieldSet>
|
||||
</div>
|
||||
|
|
|
@ -13,6 +13,7 @@ import ModalFooter from 'Components/Modal/ModalFooter';
|
|||
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||
import { inputTypes, kinds } from 'Helpers/Props';
|
||||
import { stringSettingShape } from 'Helpers/Props/Shapes/settingShape';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import styles from './EditRemotePathMappingModalContent.css';
|
||||
|
||||
function EditRemotePathMappingModalContent(props) {
|
||||
|
@ -40,7 +41,7 @@ function EditRemotePathMappingModalContent(props) {
|
|||
return (
|
||||
<ModalContent onModalClose={onModalClose}>
|
||||
<ModalHeader>
|
||||
{id ? 'Edit Remote Path Mapping' : 'Add Remote Path Mapping'}
|
||||
{id ? translate('EditRemotePathMapping') : translate('AddRemotePathMapping')}
|
||||
</ModalHeader>
|
||||
|
||||
<ModalBody className={styles.body}>
|
||||
|
@ -51,19 +52,21 @@ function EditRemotePathMappingModalContent(props) {
|
|||
|
||||
{
|
||||
!isFetching && !!error &&
|
||||
<div>Unable to add a new remote path mapping, please try again.</div>
|
||||
<div>
|
||||
{translate('UnableToAddANewRemotePathMappingPleaseTryAgain')}
|
||||
</div>
|
||||
}
|
||||
|
||||
{
|
||||
!isFetching && !error &&
|
||||
<Form {...otherProps}>
|
||||
<FormGroup>
|
||||
<FormLabel>Host</FormLabel>
|
||||
<FormLabel>{translate('Host')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.SELECT}
|
||||
name="host"
|
||||
helpText="The same host you specified for the remote Download Client"
|
||||
helpText={translate('RemotePathMappingHostHelpText')}
|
||||
{...host}
|
||||
values={downloadClientHosts}
|
||||
onChange={onInputChange}
|
||||
|
@ -71,24 +74,24 @@ function EditRemotePathMappingModalContent(props) {
|
|||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>Remote Path</FormLabel>
|
||||
<FormLabel>{translate('RemotePath')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.TEXT}
|
||||
name="remotePath"
|
||||
helpText="Root path to the directory that the Download Client accesses"
|
||||
helpText={translate('RemotePathMappingRemotePathHelpText')}
|
||||
{...remotePath}
|
||||
onChange={onInputChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>Local Path</FormLabel>
|
||||
<FormLabel>{translate('LocalPath')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.PATH}
|
||||
name="localPath"
|
||||
helpText="Path that Sonarr should use to access the remote path locally"
|
||||
helpText={translate('RemotePathMappingLocalPathHelpText')}
|
||||
{...localPath}
|
||||
onChange={onInputChange}
|
||||
/>
|
||||
|
@ -105,14 +108,14 @@ function EditRemotePathMappingModalContent(props) {
|
|||
kind={kinds.DANGER}
|
||||
onPress={onDeleteRemotePathMappingPress}
|
||||
>
|
||||
Delete
|
||||
{translate('Delete')}
|
||||
</Button>
|
||||
}
|
||||
|
||||
<Button
|
||||
onPress={onModalClose}
|
||||
>
|
||||
Cancel
|
||||
{translate('Cancel')}
|
||||
</Button>
|
||||
|
||||
<SpinnerErrorButton
|
||||
|
@ -120,7 +123,7 @@ function EditRemotePathMappingModalContent(props) {
|
|||
error={saveError}
|
||||
onPress={onSavePress}
|
||||
>
|
||||
Save
|
||||
{translate('Save')}
|
||||
</SpinnerErrorButton>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
|
|
|
@ -5,6 +5,7 @@ import Icon from 'Components/Icon';
|
|||
import Link from 'Components/Link/Link';
|
||||
import ConfirmModal from 'Components/Modal/ConfirmModal';
|
||||
import { icons, kinds } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import EditRemotePathMappingModalConnector from './EditRemotePathMappingModalConnector';
|
||||
import styles from './RemotePathMapping.css';
|
||||
|
||||
|
@ -87,9 +88,9 @@ class RemotePathMapping extends Component {
|
|||
<ConfirmModal
|
||||
isOpen={this.state.isDeleteRemotePathMappingModalOpen}
|
||||
kind={kinds.DANGER}
|
||||
title="Delete Remote Path Mapping"
|
||||
message="Are you sure you want to delete this remote path mapping?"
|
||||
confirmLabel="Delete"
|
||||
title={translate('DeleteRemotePathMapping')}
|
||||
message={translate('DeleteRemotePathMappingMessageText')}
|
||||
confirmLabel={translate('Delete')}
|
||||
onConfirm={this.onConfirmDeleteRemotePathMapping}
|
||||
onCancel={this.onDeleteRemotePathMappingModalClose}
|
||||
/>
|
||||
|
|
|
@ -5,6 +5,7 @@ import Icon from 'Components/Icon';
|
|||
import Link from 'Components/Link/Link';
|
||||
import PageSectionContent from 'Components/Page/PageSectionContent';
|
||||
import { icons } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import EditRemotePathMappingModalConnector from './EditRemotePathMappingModalConnector';
|
||||
import RemotePathMapping from './RemotePathMapping';
|
||||
import styles from './RemotePathMappings.css';
|
||||
|
@ -44,15 +45,21 @@ class RemotePathMappings extends Component {
|
|||
} = this.props;
|
||||
|
||||
return (
|
||||
<FieldSet legend="Remote Path Mappings">
|
||||
<FieldSet legend={translate('RemotePathMappings')} >
|
||||
<PageSectionContent
|
||||
errorMessage="Unable to load Remote Path Mappings"
|
||||
errorMessage={translate('UnableToLoadRemotePathMappings')}
|
||||
{...otherProps}
|
||||
>
|
||||
<div className={styles.remotePathMappingsHeader}>
|
||||
<div className={styles.host}>Host</div>
|
||||
<div className={styles.path}>Remote Path</div>
|
||||
<div className={styles.path}>Local Path</div>
|
||||
<div className={styles.host}>
|
||||
{translate('Host')}
|
||||
</div>
|
||||
<div className={styles.path}>
|
||||
{translate('RemotePath')}
|
||||
</div>
|
||||
<div className={styles.path}>
|
||||
{translate('LocalPath')}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
|
|
|
@ -30,28 +30,28 @@ const caseOptions = [
|
|||
const fileNameTokens = [
|
||||
{
|
||||
token: '{Series Title} - S{season:00}E{episode:00} - {Episode Title} {Quality Full}',
|
||||
example: 'Series Title (2010) - S01E01 - Episode Title HDTV-720p Proper'
|
||||
example: 'The Series Title\'s! (2010) - S01E01 - Episode Title HDTV-720p Proper'
|
||||
},
|
||||
{
|
||||
token: '{Series Title} - {season:0}x{episode:00} - {Episode Title} {Quality Full}',
|
||||
example: 'Series Title (2010) - 1x01 - Episode Title HDTV-720p Proper'
|
||||
example: 'The Series Title\'s! (2010) - 1x01 - Episode Title HDTV-720p Proper'
|
||||
},
|
||||
{
|
||||
token: '{Series.Title}.S{season:00}E{episode:00}.{EpisodeClean.Title}.{Quality.Full}',
|
||||
example: 'Series.Title.(2010).S01E01.Episode.Title.HDTV-720p'
|
||||
example: 'The.Series.Title\'s!.(2010).S01E01.Episode.Title.HDTV-720p'
|
||||
}
|
||||
];
|
||||
|
||||
const seriesTokens = [
|
||||
{ token: '{Series Title}', example: 'Series Title\'s' },
|
||||
{ token: '{Series CleanTitle}', example: 'Series Titles' },
|
||||
{ token: '{Series CleanTitleYear}', example: 'Series Titles! 2010' },
|
||||
{ token: '{Series CleanTitleWithoutYear}', example: 'Series Titles!' },
|
||||
{ token: '{Series TitleThe}', example: 'Series Title\'s, The' },
|
||||
{ token: '{Series TitleTheYear}', example: 'Series Title\'s, The (2010)' },
|
||||
{ token: '{Series TitleTheWithoutYear}', example: 'Series Title\'s, The' },
|
||||
{ token: '{Series TitleYear}', example: 'Series Title\'s (2010)' },
|
||||
{ token: '{Series TitleWithoutYear}', example: 'Series Title\'s' },
|
||||
{ token: '{Series Title}', example: 'The Series Title\'s!' },
|
||||
{ token: '{Series CleanTitle}', example: 'The Series Title\'s!' },
|
||||
{ token: '{Series CleanTitleYear}', example: 'The Series Titles! 2010' },
|
||||
{ token: '{Series CleanTitleWithoutYear}', example: 'The Series Title\'s!' },
|
||||
{ token: '{Series TitleThe}', example: 'Series Title\'s!, The' },
|
||||
{ token: '{Series TitleTheYear}', example: 'Series Title\'s!, The (2010)' },
|
||||
{ token: '{Series TitleTheWithoutYear}', example: 'Series Title\'s!, The' },
|
||||
{ token: '{Series TitleYear}', example: 'The Series Title\'s! (2010)' },
|
||||
{ token: '{Series TitleWithoutYear}', example: 'Series Title\'s!' },
|
||||
{ token: '{Series TitleFirstCharacter}', example: 'S' },
|
||||
{ token: '{Series Year}', example: '2010' }
|
||||
];
|
||||
|
@ -89,8 +89,8 @@ const episodeTitleTokens = [
|
|||
];
|
||||
|
||||
const qualityTokens = [
|
||||
{ token: '{Quality Full}', example: 'HDTV-720p Proper' },
|
||||
{ token: '{Quality Title}', example: 'HDTV-720p' }
|
||||
{ token: '{Quality Full}', example: 'WEBDL-1080p Proper' },
|
||||
{ token: '{Quality Title}', example: 'WEBDL-1080p' }
|
||||
];
|
||||
|
||||
const mediaInfoTokens = [
|
||||
|
@ -114,8 +114,8 @@ const otherTokens = [
|
|||
];
|
||||
|
||||
const originalTokens = [
|
||||
{ token: '{Original Title}', example: 'Series.Title.S01E01.HDTV.x264-EVOLVE' },
|
||||
{ token: '{Original Filename}', example: 'series.title.s01e01.hdtv.x264-EVOLVE' }
|
||||
{ token: '{Original Title}', example: 'The.Series.Title\'s!.S01E01.WEBDL.1080p.x264-EVOLVE' },
|
||||
{ token: '{Original Filename}', example: 'the.series.title\'s!.s01e01.webdl.1080p.x264-EVOLVE' }
|
||||
];
|
||||
|
||||
class NamingModal extends Component {
|
||||
|
|
|
@ -8,6 +8,7 @@ import ModalContent from 'Components/Modal/ModalContent';
|
|||
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||
import { kinds } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
|
||||
function PendingChangesModal(props) {
|
||||
const {
|
||||
|
@ -27,10 +28,10 @@ function PendingChangesModal(props) {
|
|||
onModalClose={onCancel}
|
||||
>
|
||||
<ModalContent onModalClose={onCancel}>
|
||||
<ModalHeader>Unsaved Changes</ModalHeader>
|
||||
<ModalHeader>{translate('UnsavedChanges')}</ModalHeader>
|
||||
|
||||
<ModalBody>
|
||||
You have unsaved changes, are you sure you want to leave this page?
|
||||
{translate('PendingChangesMessage')}
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
|
@ -38,7 +39,7 @@ function PendingChangesModal(props) {
|
|||
kind={kinds.DEFAULT}
|
||||
onPress={onCancel}
|
||||
>
|
||||
Stay and review changes
|
||||
{translate('PendingChangesStayReview')}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
|
@ -46,7 +47,7 @@ function PendingChangesModal(props) {
|
|||
kind={kinds.DANGER}
|
||||
onPress={onConfirm}
|
||||
>
|
||||
Discard changes and leave
|
||||
{translate('PendingChangesDiscardChanges')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
|
|
|
@ -2,12 +2,13 @@ import React from 'react';
|
|||
import Link from 'Components/Link/Link';
|
||||
import PageContent from 'Components/Page/PageContent';
|
||||
import PageContentBody from 'Components/Page/PageContentBody';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import SettingsToolbarConnector from './SettingsToolbarConnector';
|
||||
import styles from './Settings.css';
|
||||
|
||||
function Settings() {
|
||||
return (
|
||||
<PageContent title="Settings">
|
||||
<PageContent title={translate('Settings')}>
|
||||
<SettingsToolbarConnector
|
||||
hasPendingChanges={false}
|
||||
/>
|
||||
|
@ -17,143 +18,143 @@ function Settings() {
|
|||
className={styles.link}
|
||||
to="/settings/mediamanagement"
|
||||
>
|
||||
Media Management
|
||||
{translate('MediaManagement')}
|
||||
</Link>
|
||||
|
||||
<div className={styles.summary}>
|
||||
Naming, file management settings and root folders
|
||||
{translate('MediaManagementSettingsSummary')}
|
||||
</div>
|
||||
|
||||
<Link
|
||||
className={styles.link}
|
||||
to="/settings/profiles"
|
||||
>
|
||||
Profiles
|
||||
{translate('Profiles')}
|
||||
</Link>
|
||||
|
||||
<div className={styles.summary}>
|
||||
Quality, Language, Delay and Release profiles
|
||||
{translate('ProfilesSettingsSummary')}
|
||||
</div>
|
||||
|
||||
<Link
|
||||
className={styles.link}
|
||||
to="/settings/quality"
|
||||
>
|
||||
Quality
|
||||
{translate('Quality')}
|
||||
</Link>
|
||||
|
||||
<div className={styles.summary}>
|
||||
Quality sizes and naming
|
||||
{translate('QualitySettingsSummary')}
|
||||
</div>
|
||||
|
||||
<Link
|
||||
className={styles.link}
|
||||
to="/settings/customformats"
|
||||
>
|
||||
Custom Formats
|
||||
{translate('CustomFormats')}
|
||||
</Link>
|
||||
|
||||
<div className={styles.summary}>
|
||||
Custom Formats and Settings
|
||||
{translate('CustomFormatsSettingsSummary')}
|
||||
</div>
|
||||
|
||||
<Link
|
||||
className={styles.link}
|
||||
to="/settings/indexers"
|
||||
>
|
||||
Indexers
|
||||
{translate('Indexers')}
|
||||
</Link>
|
||||
|
||||
<div className={styles.summary}>
|
||||
Indexers and indexer options
|
||||
{translate('IndexersSettingsSummary')}
|
||||
</div>
|
||||
|
||||
<Link
|
||||
className={styles.link}
|
||||
to="/settings/downloadclients"
|
||||
>
|
||||
Download Clients
|
||||
{translate('DownloadClients')}
|
||||
</Link>
|
||||
|
||||
<div className={styles.summary}>
|
||||
Download clients, download handling and remote path mappings
|
||||
{translate('DownloadClientsSettingsSummary')}
|
||||
</div>
|
||||
|
||||
<Link
|
||||
className={styles.link}
|
||||
to="/settings/importlists"
|
||||
>
|
||||
Import Lists
|
||||
{translate('ImportLists')}
|
||||
</Link>
|
||||
|
||||
<div className={styles.summary}>
|
||||
Import from another Sonarr instance or Trakt lists and manage list exclusions
|
||||
{translate('ImportListsSettingsSummary')}
|
||||
</div>
|
||||
|
||||
<Link
|
||||
className={styles.link}
|
||||
to="/settings/connect"
|
||||
>
|
||||
Connect
|
||||
{translate('Connect')}
|
||||
</Link>
|
||||
|
||||
<div className={styles.summary}>
|
||||
Notifications, connections to media servers/players and custom scripts
|
||||
{translate('ConnectSettingsSummary')}
|
||||
</div>
|
||||
|
||||
<Link
|
||||
className={styles.link}
|
||||
to="/settings/metadata"
|
||||
>
|
||||
Metadata
|
||||
{translate('Metadata')}
|
||||
</Link>
|
||||
|
||||
<div className={styles.summary}>
|
||||
Create metadata files when episodes are imported or series are refreshed
|
||||
{translate('MetadataSettingsSummary')}
|
||||
</div>
|
||||
|
||||
<Link
|
||||
className={styles.link}
|
||||
to="/settings/metadatasource"
|
||||
>
|
||||
Metadata Source
|
||||
{translate('MetadataSource')}
|
||||
</Link>
|
||||
|
||||
<div className={styles.summary}>
|
||||
Information on where Sonarr gets Series and Episode information
|
||||
{translate('MetadataSourceSettingsSummary')}
|
||||
</div>
|
||||
|
||||
<Link
|
||||
className={styles.link}
|
||||
to="/settings/tags"
|
||||
>
|
||||
Tags
|
||||
{translate('Tags')}
|
||||
</Link>
|
||||
|
||||
<div className={styles.summary}>
|
||||
See all tags and how they are used. Unused tags can be removed
|
||||
{translate('TagsSettingsSummary')}
|
||||
</div>
|
||||
|
||||
<Link
|
||||
className={styles.link}
|
||||
to="/settings/general"
|
||||
>
|
||||
General
|
||||
{translate('General')}
|
||||
</Link>
|
||||
|
||||
<div className={styles.summary}>
|
||||
Port, SSL, username/password, proxy, analytics and updates
|
||||
{translate('GeneralSettingsSummary')}
|
||||
</div>
|
||||
|
||||
<Link
|
||||
className={styles.link}
|
||||
to="/settings/ui"
|
||||
>
|
||||
UI
|
||||
{translate('UI')}
|
||||
</Link>
|
||||
|
||||
<div className={styles.summary}>
|
||||
Calendar, date and color impaired options
|
||||
{translate('UISettingsSummary')}
|
||||
</div>
|
||||
</PageContentBody>
|
||||
</PageContent>
|
||||
|
|
|
@ -5,6 +5,7 @@ import PageToolbar from 'Components/Page/Toolbar/PageToolbar';
|
|||
import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
|
||||
import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
|
||||
import { icons } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import AdvancedSettingsButton from './AdvancedSettingsButton';
|
||||
import PendingChangesModal from './PendingChangesModal';
|
||||
|
||||
|
@ -61,7 +62,7 @@ class SettingsToolbar extends Component {
|
|||
{
|
||||
showSave &&
|
||||
<PageToolbarButton
|
||||
label={hasPendingChanges ? 'Save Changes' : 'No Changes'}
|
||||
label={hasPendingChanges ? translate('SaveChanges') : translate('NoChanges')}
|
||||
iconName={icons.SAVE}
|
||||
isSpinning={isSaving}
|
||||
isDisabled={!hasPendingChanges}
|
||||
|
|
|
@ -381,13 +381,14 @@ export const actionHandlers = handleThunks({
|
|||
const {
|
||||
id,
|
||||
remove,
|
||||
blocklist
|
||||
blocklist,
|
||||
skipRedownload
|
||||
} = payload;
|
||||
|
||||
dispatch(updateItem({ section: paged, id, isRemoving: true }));
|
||||
|
||||
const promise = createAjaxRequest({
|
||||
url: `/queue/${id}?removeFromClient=${remove}&blocklist=${blocklist}`,
|
||||
url: `/queue/${id}?removeFromClient=${remove}&blocklist=${blocklist}&skipRedownload=${skipRedownload}`,
|
||||
method: 'DELETE'
|
||||
}).request;
|
||||
|
||||
|
@ -404,7 +405,8 @@ export const actionHandlers = handleThunks({
|
|||
const {
|
||||
ids,
|
||||
remove,
|
||||
blocklist
|
||||
blocklist,
|
||||
skipRedownload
|
||||
} = payload;
|
||||
|
||||
dispatch(batchActions([
|
||||
|
@ -420,7 +422,7 @@ export const actionHandlers = handleThunks({
|
|||
]));
|
||||
|
||||
const promise = createAjaxRequest({
|
||||
url: `/queue/bulk?removeFromClient=${remove}&blocklist=${blocklist}`,
|
||||
url: `/queue/bulk?removeFromClient=${remove}&blocklist=${blocklist}&skipRedownload=${skipRedownload}`,
|
||||
method: 'DELETE',
|
||||
dataType: 'json',
|
||||
contentType: 'application/json',
|
||||
|
|
|
@ -54,10 +54,14 @@ class MoreInfo extends Component {
|
|||
{translate('IRC')}
|
||||
</DescriptionListItemTitle>
|
||||
<DescriptionListItemDescription>
|
||||
<Link to="irc://irc.libera.chat/#sonarr">#sonarr on Libera</Link>
|
||||
<Link to="irc://irc.libera.chat/#sonarr">
|
||||
{translate('IRCLinkText')}
|
||||
</Link>
|
||||
</DescriptionListItemDescription>
|
||||
<DescriptionListItemDescription>
|
||||
<Link to="https://web.libera.chat/?channels=#sonarr">Libera webchat</Link>
|
||||
<Link to="https://web.libera.chat/?channels=#sonarr">
|
||||
{translate('LiberaWebchat')}
|
||||
</Link>
|
||||
</DescriptionListItemDescription>
|
||||
|
||||
<DescriptionListItemTitle>
|
||||
|
|
|
@ -70,6 +70,8 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
|
|||
[TestCase("[a] title", "a title")]
|
||||
[TestCase("backslash \\ backlash", "backslash backlash")]
|
||||
[TestCase("I'm the Boss", "Im the Boss")]
|
||||
[TestCase("The Title's", "The Title's")]
|
||||
[TestCase("I'm after I'm", "Im after I'm")]
|
||||
|
||||
// [TestCase("", "")]
|
||||
public void should_get_expected_title_back(string title, string expected)
|
||||
|
|
|
@ -54,6 +54,8 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
|
|||
[TestCase("The Mist", 2018, "The Mist")]
|
||||
[TestCase("The Rat Pack (A&E)", 1999, "The Rat Pack AandE")]
|
||||
[TestCase("The Climax: I (Almost) Got Away With It (2016)", 2016, "The Climax I Almost Got Away With It")]
|
||||
[TestCase("The Series Title's (2016)", 2016, "The Series Titles")]
|
||||
[TestCase("The Series Title's", 2016, "The Series Title's")]
|
||||
public void should_get_expected_title_back(string title, int year, string expected)
|
||||
{
|
||||
_series.Title = title;
|
||||
|
|
|
@ -54,6 +54,7 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
|
|||
[TestCase("The Mist", 2018, "The Mist 2018")]
|
||||
[TestCase("The Rat Pack (A&E)", 1999, "The Rat Pack AandE 1999")]
|
||||
[TestCase("The Climax: I (Almost) Got Away With It (2016)", 2016, "The Climax I Almost Got Away With It 2016")]
|
||||
[TestCase("The Series Title's", 2016, "The Series Titles 2016")]
|
||||
public void should_get_expected_title_back(string title, int year, string expected)
|
||||
{
|
||||
_series.Title = title;
|
||||
|
|
|
@ -23,5 +23,6 @@ namespace NzbDrone.Core.Download
|
|||
public Dictionary<string, string> Data { get; set; }
|
||||
public TrackedDownload TrackedDownload { get; set; }
|
||||
public List<Language> Languages { get; set; }
|
||||
public bool SkipRedownload { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,8 +9,8 @@ namespace NzbDrone.Core.Download
|
|||
{
|
||||
public interface IFailedDownloadService
|
||||
{
|
||||
void MarkAsFailed(int historyId);
|
||||
void MarkAsFailed(string downloadId);
|
||||
void MarkAsFailed(int historyId, bool skipRedownload = false);
|
||||
void MarkAsFailed(string downloadId, bool skipRedownload = false);
|
||||
void Check(TrackedDownload trackedDownload);
|
||||
void ProcessFailed(TrackedDownload trackedDownload);
|
||||
}
|
||||
|
@ -30,14 +30,14 @@ namespace NzbDrone.Core.Download
|
|||
_eventAggregator = eventAggregator;
|
||||
}
|
||||
|
||||
public void MarkAsFailed(int historyId)
|
||||
public void MarkAsFailed(int historyId, bool skipRedownload = false)
|
||||
{
|
||||
var history = _historyService.Get(historyId);
|
||||
|
||||
var downloadId = history.DownloadId;
|
||||
if (downloadId.IsNullOrWhiteSpace())
|
||||
{
|
||||
PublishDownloadFailedEvent(new List<EpisodeHistory> { history }, "Manually marked as failed");
|
||||
PublishDownloadFailedEvent(new List<EpisodeHistory> { history }, "Manually marked as failed", skipRedownload: skipRedownload);
|
||||
|
||||
return;
|
||||
}
|
||||
|
@ -57,7 +57,7 @@ namespace NzbDrone.Core.Download
|
|||
PublishDownloadFailedEvent(grabbedHistory, "Manually marked as failed");
|
||||
}
|
||||
|
||||
public void MarkAsFailed(string downloadId)
|
||||
public void MarkAsFailed(string downloadId, bool skipRedownload = false)
|
||||
{
|
||||
var history = _historyService.Find(downloadId, EpisodeHistoryEventType.Grabbed);
|
||||
|
||||
|
@ -65,7 +65,7 @@ namespace NzbDrone.Core.Download
|
|||
{
|
||||
var trackedDownload = _trackedDownloadService.Find(downloadId);
|
||||
|
||||
PublishDownloadFailedEvent(history, "Manually marked as failed", trackedDownload);
|
||||
PublishDownloadFailedEvent(history, "Manually marked as failed", trackedDownload, skipRedownload: skipRedownload);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -125,7 +125,7 @@ namespace NzbDrone.Core.Download
|
|||
PublishDownloadFailedEvent(grabbedItems, failure, trackedDownload);
|
||||
}
|
||||
|
||||
private void PublishDownloadFailedEvent(List<EpisodeHistory> historyItems, string message, TrackedDownload trackedDownload = null)
|
||||
private void PublishDownloadFailedEvent(List<EpisodeHistory> historyItems, string message, TrackedDownload trackedDownload = null, bool skipRedownload = false)
|
||||
{
|
||||
var historyItem = historyItems.First();
|
||||
|
||||
|
@ -140,7 +140,8 @@ namespace NzbDrone.Core.Download
|
|||
Message = message,
|
||||
Data = historyItem.Data,
|
||||
TrackedDownload = trackedDownload,
|
||||
Languages = historyItem.Languages
|
||||
Languages = historyItem.Languages,
|
||||
SkipRedownload = skipRedownload
|
||||
};
|
||||
|
||||
_eventAggregator.PublishEvent(downloadFailedEvent);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
using System.Linq;
|
||||
using System.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.IndexerSearch;
|
||||
|
@ -30,6 +30,12 @@ namespace NzbDrone.Core.Download
|
|||
[EventHandleOrder(EventHandleOrder.Last)]
|
||||
public void Handle(DownloadFailedEvent message)
|
||||
{
|
||||
if (message.SkipRedownload)
|
||||
{
|
||||
_logger.Debug("Skip redownloading requested by user");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_configService.AutoRedownloadFailed)
|
||||
{
|
||||
_logger.Debug("Auto redownloading failed episodes is disabled");
|
||||
|
|
|
@ -11,6 +11,7 @@ namespace NzbDrone.Core.ImportLists.Simkl
|
|||
public string Imdb { get; set; }
|
||||
public string Tmdb { get; set; }
|
||||
public string Tvdb { get; set; }
|
||||
public string Mal { get; set; }
|
||||
}
|
||||
|
||||
public class SimklSeriesPropsResource
|
||||
|
@ -23,11 +24,15 @@ namespace NzbDrone.Core.ImportLists.Simkl
|
|||
public class SimklSeriesResource
|
||||
{
|
||||
public SimklSeriesPropsResource Show { get; set; }
|
||||
|
||||
[JsonProperty("anime_type")]
|
||||
public SimklAnimeType AnimeType { get; set; }
|
||||
}
|
||||
|
||||
public class SimklResponse
|
||||
{
|
||||
public List<SimklSeriesResource> Shows { get; set; }
|
||||
public List<SimklSeriesResource> Anime { get; set; }
|
||||
}
|
||||
|
||||
public class RefreshRequestResponse
|
||||
|
@ -66,4 +71,10 @@ namespace NzbDrone.Core.ImportLists.Simkl
|
|||
{
|
||||
public DateTime All { get; set; }
|
||||
}
|
||||
|
||||
public enum SimklAnimeType
|
||||
{
|
||||
Tv,
|
||||
Movie
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Instrumentation;
|
||||
using NzbDrone.Common.Serializer;
|
||||
using NzbDrone.Core.ImportLists.Exceptions;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
|
@ -10,6 +12,11 @@ namespace NzbDrone.Core.ImportLists.Simkl
|
|||
public class SimklParser : IParseImportListResponse
|
||||
{
|
||||
private ImportListResponse _importResponse;
|
||||
private static readonly Logger Logger = NzbDroneLogger.GetLogger(typeof(SimklParser));
|
||||
|
||||
public SimklParser()
|
||||
{
|
||||
}
|
||||
|
||||
public virtual IList<ImportListItemInfo> ParseResponse(ImportListResponse importResponse)
|
||||
{
|
||||
|
@ -22,7 +29,7 @@ namespace NzbDrone.Core.ImportLists.Simkl
|
|||
return series;
|
||||
}
|
||||
|
||||
var jsonResponse = STJson.Deserialize<SimklResponse>(_importResponse.Content);
|
||||
var jsonResponse = Json.Deserialize<SimklResponse>(_importResponse.Content);
|
||||
|
||||
// no shows were return
|
||||
if (jsonResponse == null)
|
||||
|
@ -30,14 +37,40 @@ namespace NzbDrone.Core.ImportLists.Simkl
|
|||
return series;
|
||||
}
|
||||
|
||||
foreach (var show in jsonResponse.Shows)
|
||||
if (jsonResponse.Anime != null)
|
||||
{
|
||||
series.AddIfNotNull(new ImportListItemInfo()
|
||||
foreach (var show in jsonResponse.Anime)
|
||||
{
|
||||
Title = show.Show.Title,
|
||||
TvdbId = int.TryParse(show.Show.Ids.Tvdb, out var tvdbId) ? tvdbId : 0,
|
||||
ImdbId = show.Show.Ids.Imdb
|
||||
});
|
||||
var tentativeTvdbId = int.TryParse(show.Show.Ids.Tvdb, out var tvdbId) ? tvdbId : 0;
|
||||
|
||||
if (tentativeTvdbId > 0 && show.AnimeType == SimklAnimeType.Tv)
|
||||
{
|
||||
series.AddIfNotNull(new ImportListItemInfo()
|
||||
{
|
||||
Title = show.Show.Title,
|
||||
ImdbId = show.Show.Ids.Imdb,
|
||||
TvdbId = tvdbId,
|
||||
MalId = int.TryParse(show.Show.Ids.Mal, out var malId) ? malId : 0
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Warn("Skipping info grabbing for '{0}' because it is a movie or it is not the first season of the show", show.Show.Title);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (jsonResponse.Shows != null)
|
||||
{
|
||||
foreach (var show in jsonResponse.Shows)
|
||||
{
|
||||
series.AddIfNotNull(new ImportListItemInfo()
|
||||
{
|
||||
Title = show.Show.Title,
|
||||
TvdbId = int.TryParse(show.Show.Ids.Tvdb, out var tvdbId) ? tvdbId : 0,
|
||||
ImdbId = show.Show.Ids.Imdb
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return series;
|
||||
|
|
|
@ -21,7 +21,7 @@ namespace NzbDrone.Core.ImportLists.Simkl.User
|
|||
|
||||
private IEnumerable<ImportListRequest> GetSeriesRequest()
|
||||
{
|
||||
var link = $"{Settings.BaseUrl.Trim()}/sync/all-items/shows/{((SimklUserListType)Settings.ListType).ToString().ToLowerInvariant()}";
|
||||
var link = $"{Settings.BaseUrl.Trim()}/sync/all-items/{((SimklUserShowType)Settings.ShowType).ToString().ToLowerInvariant()}/{((SimklUserListType)Settings.ListType).ToString().ToLowerInvariant()}";
|
||||
|
||||
var request = new ImportListRequest(link, HttpAccept.Json);
|
||||
|
||||
|
|
|
@ -19,9 +19,13 @@ namespace NzbDrone.Core.ImportLists.Simkl.User
|
|||
public SimklUserSettings()
|
||||
{
|
||||
ListType = (int)SimklUserListType.Watching;
|
||||
ShowType = (int)SimklUserShowType.Shows;
|
||||
}
|
||||
|
||||
[FieldDefinition(1, Label = "List Type", Type = FieldType.Select, SelectOptions = typeof(SimklUserListType), HelpText = "Type of list you're seeking to import from")]
|
||||
public int ListType { get; set; }
|
||||
|
||||
[FieldDefinition(1, Label = "Show Type", Type = FieldType.Select, SelectOptions = typeof(SimklUserShowType), HelpText = "Type of show you're seeking to import from")]
|
||||
public int ShowType { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
namespace NzbDrone.Core.ImportLists.Simkl.User
|
||||
{
|
||||
public enum SimklUserShowType
|
||||
{
|
||||
Shows = 0,
|
||||
Anime = 1
|
||||
}
|
||||
}
|
|
@ -4,9 +4,14 @@
|
|||
"Actions": "Actions",
|
||||
"Activity": "Activity",
|
||||
"Add": "Add",
|
||||
"AddCustomFormat": "Add Custom Format",
|
||||
"AddDelayProfile": "Add Delay Profile",
|
||||
"AddDownloadClient": "Add Download Client",
|
||||
"Added": "Added",
|
||||
"AddExclusion": "Add Exclusion",
|
||||
"AddingTag": "Adding tag",
|
||||
"AddNew": "Add New",
|
||||
"AddRemotePathMapping": "Add Remote Path Mapping",
|
||||
"AirDate": "Air Date",
|
||||
"AllTitles": "All Titles",
|
||||
"ApiKeyValidationHealthCheckMessage": "Please update your API key to be at least {0} characters long. You can do this via settings or the config file",
|
||||
|
@ -26,6 +31,7 @@
|
|||
"AptUpdater": "Use apt to install the update",
|
||||
"AutoAdd": "Auto Add",
|
||||
"AutomaticAdd": "Automatic Add",
|
||||
"AutoRedownloadFailedHelpText": "Automatically search for and attempt to download a different release",
|
||||
"Backup": "Backup",
|
||||
"BackupNow": "Backup Now",
|
||||
"Backups": "Backups",
|
||||
|
@ -39,26 +45,43 @@
|
|||
"Cancel": "Cancel",
|
||||
"CancelPendingTask": "Are you sure you want to cancel this pending task?",
|
||||
"Clear": "Clear",
|
||||
"ClientPriority": "Client Priority",
|
||||
"CloneCondition": "Clone Condition",
|
||||
"CloneCustomFormat": "Clone Custom Format",
|
||||
"Close": "Close",
|
||||
"CompletedDownloadHandling": "Completed Download Handling",
|
||||
"Condition": "Condition",
|
||||
"Conditions": "Conditions",
|
||||
"Connect": "Connect",
|
||||
"ConnectSettingsSummary": "Notifications, connections to media servers/players, and custom scripts",
|
||||
"CopyToClipboard": "Copy to Clipboard",
|
||||
"CountDownloadClientsSelected": "{count} download client(s) selected",
|
||||
"CountImportListsSelected": "{count} import list(s) selected",
|
||||
"CountIndexersSelected": "{count} indexer(s) selected",
|
||||
"CountSeasons": "{count} seasons",
|
||||
"CurrentlyInstalled": "Currently Installed",
|
||||
"Custom": "Custom",
|
||||
"CustomFormats": "Custom Formats",
|
||||
"CustomFormatScore": "Custom Format Score",
|
||||
"CustomFormatsSettings": "Custom Formats Settings",
|
||||
"CustomFormatsSettingsSummary": "Custom Formats and Settings",
|
||||
"CustomFormatUnknownCondition": "Unknown Custom Format condition '{implementation}'",
|
||||
"CustomFormatUnknownConditionOption": "Unknown option '{key}' for condition '{implementation}'",
|
||||
"CutoffUnmet": "Cutoff Unmet",
|
||||
"Daily": "Daily",
|
||||
"Delete": "Delete",
|
||||
"DeleteBackup": "Delete Backup",
|
||||
"DeleteBackupMessageText": "Are you sure you want to delete the backup '{name}'?",
|
||||
"DeleteCondition": "Delete Condition",
|
||||
"DeleteConditionMessageText": "Are you sure you want to delete the condition '{0}'?",
|
||||
"DeleteConditionMessageText": "Are you sure you want to delete the condition '{name}'?",
|
||||
"DeleteCustomFormat": "Delete Custom Format",
|
||||
"DeleteCustomFormatMessageText": "Are you sure you want to delete the custom format '{0}'?",
|
||||
"DeleteDelayProfile": "Delete Delay Profile",
|
||||
"DeleteDelayProfileMessageText": "Are you sure you want to delete this delay profile?",
|
||||
"DeleteDownloadClient": "Delete Download Client",
|
||||
"DeleteDownloadClientMessageText": "Are you sure you want to delete the download client '{name}'?",
|
||||
"DeleteRemotePathMapping": "Delete Remote Path Mapping",
|
||||
"DeleteRemotePathMappingMessageText": "Are you sure you want to delete this remote path mapping?",
|
||||
"DeleteSelectedDownloadClients": "Delete Download Client(s)",
|
||||
"DeleteSelectedDownloadClientsMessageText": "Are you sure you want to delete {count} selected download client(s)?",
|
||||
"DeleteSelectedImportLists": "Delete Import List(s)",
|
||||
|
@ -74,22 +97,46 @@
|
|||
"Donations": "Donations",
|
||||
"DotNetVersion": ".NET",
|
||||
"Download": "Download",
|
||||
"DownloadClient": "Download Client",
|
||||
"DownloadClientCheckNoneAvailableHealthCheckMessage": "No download client is available",
|
||||
"DownloadClientCheckUnableToCommunicateWithHealthCheckMessage": "Unable to communicate with {0}.",
|
||||
"DownloadClientRootFolderHealthCheckMessage": "Download client {0} places downloads in the root folder {1}. You should not download to a root folder.",
|
||||
"DownloadClients": "Download Clients",
|
||||
"DownloadClientSettings": "Download Client Settings",
|
||||
"DownloadClientSortingHealthCheckMessage": "Download client {0} has {1} sorting enabled for Sonarr's category. You should disable sorting in your download client to avoid import issues.",
|
||||
"DownloadClientsSettingsSummary": "Download clients, download handling and remote path mappings",
|
||||
"DownloadClientStatusAllClientHealthCheckMessage": "All download clients are unavailable due to failures",
|
||||
"DownloadClientStatusSingleClientHealthCheckMessage": "Download clients unavailable due to failures: {0}",
|
||||
"DownloadClientTagHelpText": "Only use this download client for series with at least one matching tag. Leave blank to use with all series.",
|
||||
"Duration": "Duration",
|
||||
"Edit": "Edit",
|
||||
"EditCustomFormat": "Edit Custom Format",
|
||||
"EditDelayProfile": "Edit Delay Profile",
|
||||
"EditGroups": "Edit Groups",
|
||||
"EditIndexer": "Edit Indexer",
|
||||
"EditListExclusion": "Edit List Exclusion",
|
||||
"EditQualityProfile": "Edit Quality Profile",
|
||||
"EditRemotePathMapping": "Edit Remote Path Mapping",
|
||||
"EditRestriction": "Edit Restriction",
|
||||
"EditSelectedDownloadClients": "Edit Selected Download Clients",
|
||||
"EditSelectedImportLists": "Edit Selected Import Lists",
|
||||
"EditSelectedIndexers": "Edit Selected Indexers",
|
||||
"EditSeries": "Edit Series",
|
||||
"Enable": "Enable",
|
||||
"EnableAutoHelpText": "If enabled, Series will be automatically added to Sonarr from this list",
|
||||
"EnableAutomaticAdd": "Enable Automatic Add",
|
||||
"EnableAutomaticSearch": "Enable Automatic Search",
|
||||
"EnableAutomaticSearchHelpText": "Will be used when automatic searches are performed via the UI or by Sonarr",
|
||||
"EnableAutomaticSearchHelpTextWarning": "Will be used when interactive search is used",
|
||||
"EnableColorImpairedMode": "Enable Color-Impaired Mode",
|
||||
"EnableColorImpairedModeHelpText": "Altered style to allow color-impaired users to better distinguish color coded information",
|
||||
"EnableCompletedDownloadHandlingHelpText": "Automatically import completed downloads from download client",
|
||||
"Enabled": "Enabled",
|
||||
"EnableHelpText": "Enable metadata file creation for this metadata type",
|
||||
"EnableInteractiveSearch": "Enable Interactive Search",
|
||||
"EnableInteractiveSearchHelpText": "Will be used when interactive search is used",
|
||||
"EnableInteractiveSearchHelpTextWarning": "Search is not supported with this indexer",
|
||||
"EnableMediaInfoHelpText": "Extract video information such as resolution, runtime and codec information from files. This requires Sonarr to read parts of the file which may cause high disk or network activity during scans.",
|
||||
"EnableRSS": "Enable RSS",
|
||||
"Ended": "Ended",
|
||||
"EpisodeInfo": "Episode Info",
|
||||
|
@ -105,26 +152,37 @@
|
|||
"FeatureRequests": "Feature Requests",
|
||||
"Filename": "Filename",
|
||||
"Fixed": "Fixed",
|
||||
"ForMoreInformationOnTheIndividualDownloadClients": "For more information on the individual download clients, click the more info buttons.",
|
||||
"ForMoreInformationOnTheIndividualImportListsClinkOnTheInfoButtons": "For more information on the individual import lists, click on the info buttons.",
|
||||
"ForMoreInformationOnTheIndividualIndexers": "For more information on the individual indexers, click on the info buttons.",
|
||||
"Forums": "Forums",
|
||||
"FreeSpace": "Free Space",
|
||||
"From": "From",
|
||||
"FullSeason": "Full Season",
|
||||
"General": "General",
|
||||
"GeneralSettings": "General Settings",
|
||||
"GeneralSettingsSummary": "Port, SSL, username/password, proxy, analytics and updates",
|
||||
"Health": "Health",
|
||||
"Here": "here",
|
||||
"HiddenClickToShow": "Hidden, click to show",
|
||||
"HideAdvanced": "Hide Advanced",
|
||||
"History": "History",
|
||||
"HomePage": "Home Page",
|
||||
"Host": "Host",
|
||||
"Implementation": "Implementation",
|
||||
"Import": "Import",
|
||||
"ImportCustomFormat": "Import Custom Format",
|
||||
"ImportListRootFolderMissingRootHealthCheckMessage": "Missing root folder for import list(s): {0}",
|
||||
"ImportListRootFolderMultipleMissingRootsHealthCheckMessage": "Multiple root folders are missing for import lists: {0}",
|
||||
"ImportLists": "Import Lists",
|
||||
"ImportListsSettingsSummary": "Import from another Sonarr instance or Trakt lists and manage list exclusions",
|
||||
"ImportListStatusAllUnavailableHealthCheckMessage": "All lists are unavailable due to failures",
|
||||
"ImportListStatusUnavailableHealthCheckMessage": "Lists unavailable due to failures: {0}",
|
||||
"ImportMechanismEnableCompletedDownloadHandlingIfPossibleHealthCheckMessage": "Enable Completed Download Handling if possible",
|
||||
"ImportMechanismEnableCompletedDownloadHandlingIfPossibleMultiComputerHealthCheckMessage": "Enable Completed Download Handling if possible (Multi-Computer unsupported)",
|
||||
"ImportMechanismHandlingDisabledHealthCheckMessage": "Enable Completed Download Handling",
|
||||
"IncludeCustomFormatWhenRenaming": "Include Custom Format when Renaming",
|
||||
"IncludeCustomFormatWhenRenamingHelpText": "Include in {Custom Formats} renaming format",
|
||||
"IndexerJackettAllHealthCheckMessage": "Indexers using the unsupported Jackett 'all' endpoint: {0}",
|
||||
"IndexerLongTermStatusAllUnavailableHealthCheckMessage": "All indexers are unavailable due to failures for more than 6 hours",
|
||||
"IndexerLongTermStatusUnavailableHealthCheckMessage": "Indexers unavailable due to failures for more than 6 hours: {0}",
|
||||
|
@ -134,18 +192,22 @@
|
|||
"IndexerSearchNoAutomaticHealthCheckMessage": "No indexers available with Automatic Search enabled, Sonarr will not provide any automatic search results",
|
||||
"IndexerSearchNoAvailableIndexersHealthCheckMessage": "All search-capable indexers are temporarily unavailable due to recent indexer errors",
|
||||
"IndexerSearchNoInteractiveHealthCheckMessage": "No indexers available with Interactive Search enabled, Sonarr will not provide any interactive search results",
|
||||
"IndexersSettingsSummary": "Indexers and indexer options",
|
||||
"IndexerStatusAllUnavailableHealthCheckMessage": "All indexers are unavailable due to failures",
|
||||
"IndexerStatusUnavailableHealthCheckMessage": "Indexers unavailable due to failures: {0}",
|
||||
"InstallLatest": "Install Latest",
|
||||
"Interval": "Interval",
|
||||
"IRC": "IRC",
|
||||
"IRCLinkText": "#sonarr on Libera",
|
||||
"Language": "Language",
|
||||
"Language that Sonarr will use for UI": "Language that Sonarr will use for UI",
|
||||
"Languages": "Languages",
|
||||
"LastDuration": "Last Duration",
|
||||
"LastExecution": "Last Execution",
|
||||
"LastWriteTime": "Last Write Time",
|
||||
"LiberaWebchat": "Libera Webchat",
|
||||
"LibraryImport": "Library Import",
|
||||
"LocalPath": "Local Path",
|
||||
"Location": "Location",
|
||||
"LogFiles": "Log Files",
|
||||
"LogFilesLocation": "Log files are located in: {location}",
|
||||
|
@ -161,17 +223,25 @@
|
|||
"MatchedToSeason": "Matched to Season",
|
||||
"MatchedToSeries": "Matched to Series",
|
||||
"MediaManagement": "Media Management",
|
||||
"MediaManagementSettings": "Media Management Settings",
|
||||
"MediaManagementSettingsSummary": "Naming, file management settings and root folders",
|
||||
"Message": "Message",
|
||||
"Metadata": "Metadata",
|
||||
"MetadataSettings": "Metadata Settings",
|
||||
"MetadataSettingsSummary": "Create metadata files when episodes are imported or series are refreshed",
|
||||
"MetadataSource": "Metadata Source",
|
||||
"MetadataSourceSettingsSummary": "Information on where Sonarr gets series and episode information",
|
||||
"Missing": "Missing",
|
||||
"Mode": "Mode",
|
||||
"Monitored": "Monitored",
|
||||
"MoreDetails": "More details",
|
||||
"MoreInfo": "More Info",
|
||||
"MountHealthCheckMessage": "Mount containing a series path is mounted read-only: ",
|
||||
"MultiSeason": "Multi-Season",
|
||||
"Name": "Name",
|
||||
"Negate": "Negate",
|
||||
"Negated": "Negated",
|
||||
"NegateHelpText": "If checked, the custom format will not apply if this {implementationName} condition matches.",
|
||||
"Network": "Network",
|
||||
"New": "New",
|
||||
"NextAiring": "Next Airing",
|
||||
|
@ -179,6 +249,7 @@
|
|||
"No": "No",
|
||||
"NoBackupsAreAvailable": "No backups are available",
|
||||
"NoChange": "No Change",
|
||||
"NoChanges": "No Changes",
|
||||
"NoDownloadClientsFound": "No download clients found",
|
||||
"NoEventsFound": "No events found",
|
||||
"NoImportListsFound": "No import lists found",
|
||||
|
@ -189,35 +260,46 @@
|
|||
"NoSeasons": "No seasons",
|
||||
"NoUpdatesAreAvailable": "No updates are available",
|
||||
"OneSeason": "1 season",
|
||||
"OnLatestVersion": "The latest version of Radarr is already installed",
|
||||
"OnLatestVersion": "The latest version of Sonarr is already installed",
|
||||
"Options": "Options",
|
||||
"OriginalLanguage": "Original Language",
|
||||
"PackageVersion": "Package Version",
|
||||
"PackageVersionInfo": "{packageVersion} by {packageAuthor}",
|
||||
"PartialSeason": "Partial Season",
|
||||
"Path": "Path",
|
||||
"PendingChangesDiscardChanges": "Discard changes and leave",
|
||||
"PendingChangesMessage": "You have unsaved changes, are you sure you want to leave this page?",
|
||||
"PendingChangesStayReview": "Stay and review changes",
|
||||
"Presets": "Presets",
|
||||
"PreviousAiring": "Previous Airing",
|
||||
"PreviouslyInstalled": "Previously Installed",
|
||||
"Priority": "Priority",
|
||||
"PriorityHelpText": "Prioritize multiple Download Clients. Round-Robin is used for clients with the same priority.",
|
||||
"PrioritySettings": "Priority: {priority}",
|
||||
"Profiles": "Profiles",
|
||||
"ProfilesSettingsSummary": "Quality, Language Delay and Release profiles",
|
||||
"Proper": "Proper",
|
||||
"ProxyBadRequestHealthCheckMessage": "Failed to test proxy. Status Code: {0}",
|
||||
"ProxyFailedToTestHealthCheckMessage": "Failed to test proxy: {0}",
|
||||
"ProxyResolveIpHealthCheckMessage": "Failed to resolve the IP Address for the Configured Proxy Host {0}",
|
||||
"Quality": "Quality",
|
||||
"QualityProfile": "Quality Profile",
|
||||
"QualitySettingsSummary": "Quality sizes and naming",
|
||||
"Queue": "Queue",
|
||||
"Queued": "Queued",
|
||||
"ReadTheWikiForMoreInformation": "Read the Wiki for more information",
|
||||
"Real": "Real",
|
||||
"RecycleBinUnableToWriteHealthCheckMessage": "Unable to write to configured recycling bin folder: {0}. Ensure this path exists and is writable by the user running Sonarr",
|
||||
"RedownloadFailed": "Redownload Failed",
|
||||
"Refresh": "Refresh",
|
||||
"RefreshSeries": "Refresh Series",
|
||||
"RegularExpressionsCanBeTested": "Regular expressions can be tested ",
|
||||
"Release": "Release",
|
||||
"ReleaseGroup": "Release Group",
|
||||
"ReleaseHash": "Release Hash",
|
||||
"ReleaseTitle": "Release Title",
|
||||
"Reload": "Reload",
|
||||
"RemotePath": "Remote Path",
|
||||
"RemotePathMappingBadDockerPathHealthCheckMessage": "You are using docker; download client {0} places downloads in {1} but this is not a valid {2} path. Review your remote path mappings and download client settings.",
|
||||
"RemotePathMappingDockerFolderMissingHealthCheckMessage": "You are using docker; download client {0} places downloads in {1} but this directory does not appear to exist inside the container. Review your remote path mappings and container volume settings.",
|
||||
"RemotePathMappingDownloadPermissionsHealthCheckMessage": "Sonarr can see but not access downloaded episode {0}. Likely permissions error.",
|
||||
|
@ -228,19 +310,26 @@
|
|||
"RemotePathMappingFilesWrongOSPathHealthCheckMessage": "Remote download client {0} reported files in {1} but this is not a valid {2} path. Review your remote path mappings and download client settings.",
|
||||
"RemotePathMappingFolderPermissionsHealthCheckMessage": "Sonarr can see but not access download directory {0}. Likely permissions error.",
|
||||
"RemotePathMappingGenericPermissionsHealthCheckMessage": "Download client {0} places downloads in {1} but Sonarr cannot see this directory. You may need to adjust the folder's permissions.",
|
||||
"RemotePathMappingHostHelpText": "The same host you specified for the remote Download Client",
|
||||
"RemotePathMappingImportFailedHealthCheckMessage": "Sonarr failed to import (an) episode(s). Check your logs for details.",
|
||||
"RemotePathMappingLocalFolderMissingHealthCheckMessage": "Remote download client {0} places downloads in {1} but this directory does not appear to exist. Likely missing or incorrect remote path mapping.",
|
||||
"RemotePathMappingLocalPathHelpText": "Path that Sonarr should use to access the remote path locally",
|
||||
"RemotePathMappingLocalWrongOSPathHealthCheckMessage": "Local download client {0} places downloads in {1} but this is not a valid {2} path. Review your download client settings.",
|
||||
"RemotePathMappingRemoteDownloadClientHealthCheckMessage": "Remote download client {0} reported files in {1} but this directory does not appear to exist. Likely missing remote path mapping.",
|
||||
"RemotePathMappingRemotePathHelpText": "Root path to the directory that the Download Client accesses",
|
||||
"RemotePathMappings": "Remote Path Mappings",
|
||||
"RemotePathMappingWrongOSPathHealthCheckMessage": "Remote download client {0} places downloads in {1} but this is not a valid {2} path. Review your remote path mappings and download client settings.",
|
||||
"Remove": "Remove",
|
||||
"RemoveCompleted": "Remove Completed",
|
||||
"RemoveCompletedDownloads": "Remove Completed Downloads",
|
||||
"RemoveCompletedDownloadsHelpText": "Remove imported downloads from download client history",
|
||||
"RemovedFromTaskQueue": "Removed from task queue",
|
||||
"RemoveDownloadsAlert": "The Remove settings were moved to the individual Download Client settings in the table above.",
|
||||
"RemovedSeriesMultipleRemovedHealthCheckMessage": "Series {0} were removed from TheTVDB",
|
||||
"RemovedSeriesSingleRemovedHealthCheckMessage": "Series {0} was removed from TheTVDB",
|
||||
"RemoveFailed": "Remove Failed",
|
||||
"RemoveFailedDownloads": "Remove Failed Downloads",
|
||||
"RemoveFailedDownloadsHelpText": "Remove failed downloads from download client history",
|
||||
"RemoveFromDownloadClient": "Remove From Download Client",
|
||||
"RemoveFromDownloadClientHelpTextWarning": "Removing will remove the download and the file(s) from the download client.",
|
||||
"RemoveSelectedItem": "Remove Selected Item",
|
||||
|
@ -251,6 +340,7 @@
|
|||
"Repack": "Repack",
|
||||
"Replace": "Replace",
|
||||
"Required": "Required",
|
||||
"RequiredHelpText": "This {implementationName} condition must match for the custom format to apply. Otherwise a single {implementationName} match is sufficient.",
|
||||
"Reset": "Reset",
|
||||
"ResetDefinitionTitlesHelpText": "Reset definition titles as well as values",
|
||||
"ResetQualityDefinitions": "Reset Quality Definitions",
|
||||
|
@ -264,6 +354,8 @@
|
|||
"RootFolder": "Root Folder",
|
||||
"RootFolderMissingHealthCheckMessage": "Missing root folder: {0}",
|
||||
"RootFolderMultipleMissingHealthCheckMessage": "Multiple root folders are missing: {0}",
|
||||
"SaveChanges": "Save Changes",
|
||||
"SaveSettings": "Save Settings",
|
||||
"Scheduled": "Scheduled",
|
||||
"SearchForMonitoredEpisodes": "Search for monitored episodes",
|
||||
"SeasonNumber": "Season Number",
|
||||
|
@ -276,6 +368,10 @@
|
|||
"ShownClickToHide": "Shown, click to hide",
|
||||
"Size": "Size",
|
||||
"SizeOnDisk": "Size on disk",
|
||||
"SonarrSupportsAnyDownloadClient": "Sonarr supports many popular torrent and usenet download clients.",
|
||||
"SonarrSupportsCustomConditionsAgainstTheReleasePropertiesBelow": "Sonarr supports custom conditions against the release properties below.",
|
||||
"SkipRedownloadHelpText": "Prevents Sonarr from trying to download an alternative release for this item",
|
||||
"SkipRedownload": "Skip Redownload",
|
||||
"Source": "Source",
|
||||
"Special": "Special",
|
||||
"Started": "Started",
|
||||
|
@ -284,19 +380,67 @@
|
|||
"System": "System",
|
||||
"SystemTimeHealthCheckMessage": "System time is off by more than 1 day. Scheduled tasks may not run correctly until the time is corrected",
|
||||
"Tags": "Tags",
|
||||
"TagsSettingsSummary": "See all tags and how they are used. Unused tags can be removed",
|
||||
"Tasks": "Tasks",
|
||||
"TaskUserAgentTooltip": "User-Agent provided by the app that called the API",
|
||||
"TestAll": "Test All",
|
||||
"TestAllClients": "Test All Clients",
|
||||
"TestAllIndexers": "Test All Indexers",
|
||||
"TestAllLists": "Test All Lists",
|
||||
"TestParsing": "Test Parsing",
|
||||
"TheLogLevelDefault": "The log level defaults to 'Info' and can be changed in [General Settings](/settings/general)",
|
||||
"ThisConditionMatchesUsingRegularExpressions": "This condition matches using Regular Expressions. Note that the characters {specialChars} have special meanings and need escaping with a {escapeChars}",
|
||||
"Time": "Time",
|
||||
"Torrents": "Torrents",
|
||||
"TotalSpace": "Total Space",
|
||||
"Twitter": "Twitter",
|
||||
"UI": "UI",
|
||||
"UI Language": "UI Language",
|
||||
"UISettingsSummary": "Calendar, date and color impaired options",
|
||||
"UnableToAddANewConditionPleaseTryAgain": "Unable to add a new condition, please try again.",
|
||||
"UnableToAddANewCustomFormatPleaseTryAgain": "Unable to add a new custom format, please try again.",
|
||||
"UnableToAddANewDownloadClientPleaseTryAgain": "Unable to add a new download client, please try again.",
|
||||
"UnableToAddANewIndexerPleaseTryAgain": "Unable to add a new indexer, please try again.",
|
||||
"UnableToAddANewListExclusionPleaseTryAgain": "Unable to add a new list exclusion, please try again.",
|
||||
"UnableToAddANewListPleaseTryAgain": "Unable to add a new list, please try again.",
|
||||
"UnableToAddANewNotificationPleaseTryAgain": "Unable to add a new notification, 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.",
|
||||
"UnableToAddRootFolder": "Unable to add root folder",
|
||||
"UnableToLoadBackups": "Unable to load backups",
|
||||
"UnableToLoadBlocklist": "Unable to load blocklist",
|
||||
"UnableToLoadCollections": "Unable to load collections",
|
||||
"UnableToLoadCustomFormats": "Unable to load Custom Formats",
|
||||
"UnableToLoadDelayProfiles": "Unable to load Delay Profiles",
|
||||
"UnableToLoadDownloadClientOptions": "Unable to load download client options",
|
||||
"UnableToLoadDownloadClients": "Unable to load download clients",
|
||||
"UnableToLoadGeneralSettings": "Unable to load General settings",
|
||||
"UnableToLoadHistory": "Unable to load history",
|
||||
"UnableToLoadIndexerOptions": "Unable to load indexer options",
|
||||
"UnableToLoadIndexers": "Unable to load Indexers",
|
||||
"UnableToLoadLanguages": "Unable to load languages",
|
||||
"UnableToLoadListExclusions": "Unable to load List Exclusions",
|
||||
"UnableToLoadListOptions": "Unable to load list options",
|
||||
"UnableToLoadLists": "Unable to load Lists",
|
||||
"UnableToLoadManualImportItems": "Unable to load manual import items",
|
||||
"UnableToLoadMediaManagementSettings": "Unable to load Media Management settings",
|
||||
"UnableToLoadMetadata": "Unable to load Metadata",
|
||||
"UnableToLoadMovies": "Unable to load movies",
|
||||
"UnableToLoadNamingSettings": "Unable to load Naming settings",
|
||||
"UnableToLoadNotifications": "Unable to load Notifications",
|
||||
"UnableToLoadQualities": "Unable to load qualities",
|
||||
"UnableToLoadQualityDefinitions": "Unable to load Quality Definitions",
|
||||
"UnableToLoadQualityProfiles": "Unable to load Quality Profiles",
|
||||
"UnableToLoadRemotePathMappings": "Unable to load Remote Path Mappings",
|
||||
"UnableToLoadRestrictions": "Unable to load Restrictions",
|
||||
"UnableToLoadResultsIntSearch": "Unable to load results for this episode search. Try again later",
|
||||
"UnableToLoadRootFolders": "Unable to load root folders",
|
||||
"UnableToLoadTags": "Unable to load Tags",
|
||||
"UnableToLoadTheCalendar": "Unable to load the calendar",
|
||||
"UnableToLoadUISettings": "Unable to load UI settings",
|
||||
"UnableToUpdateSonarrDirectly": "Unable to update Sonarr directly,",
|
||||
"Unmonitored": "Unmonitored",
|
||||
"UnsavedChanges": "Unsaved Changes",
|
||||
"UpdateAvailableHealthCheckMessage": "New update is available",
|
||||
"UpdaterLogFiles": "Updater Log Files",
|
||||
"Updates": "Updates",
|
||||
|
@ -304,7 +448,9 @@
|
|||
"UpdateStartupTranslocationHealthCheckMessage": "Cannot install update because startup folder '{0}' is in an App Translocation folder.",
|
||||
"UpdateUINotWritableHealthCheckMessage": "Cannot install update because UI folder '{0}' is not writable by the user '{1}'.",
|
||||
"Uptime": "Uptime",
|
||||
"Usenet": "Usenet",
|
||||
"Version": "Version",
|
||||
"VisitTheWikiForMoreDetails": "Visit the wiki for more details: ",
|
||||
"Wanted": "Wanted",
|
||||
"Wiki": "Wiki",
|
||||
"WouldYouLikeToRestoreBackup": "Would you like to restore the backup '{name}'?",
|
||||
|
|
|
@ -214,5 +214,100 @@
|
|||
"SeasonNumber": "Número da Temporada",
|
||||
"SeriesTitle": "Título da Série",
|
||||
"Special": "Especial",
|
||||
"TestParsing": "Análise de Teste"
|
||||
"TestParsing": "Análise de Teste",
|
||||
"About": "Sobre",
|
||||
"Actions": "Ações",
|
||||
"AppDataDirectory": "Diretório AppData",
|
||||
"AptUpdater": "Usar apt para instalar atualizações",
|
||||
"BackupNow": "Fazer Backup Agora",
|
||||
"Backups": "Backups",
|
||||
"BeforeUpdate": "Antes de atualizar",
|
||||
"CancelPendingTask": "Tem certeza de que deseja cancelar esta tarefa pendente?",
|
||||
"Clear": "Limpar",
|
||||
"CurrentlyInstalled": "Atualmente instalado",
|
||||
"DeleteBackup": "Excluir Backup",
|
||||
"DeleteBackupMessageText": "Tem certeza de que deseja excluir o backup '{name}'?",
|
||||
"Discord": "Discord",
|
||||
"DiskSpace": "Espaço em Disco",
|
||||
"Docker": "Docker",
|
||||
"DockerUpdater": "Atualize o contêiner docker para receber a atualização",
|
||||
"Donations": "Doações",
|
||||
"DotNetVersion": ".NET",
|
||||
"Download": "Baixar",
|
||||
"Duration": "Duração",
|
||||
"ErrorRestoringBackup": "Erro ao restaurar o backup",
|
||||
"Exception": "Exceção",
|
||||
"ExternalUpdater": "O Sonarr está configurado para usar um mecanismo de atualização externo",
|
||||
"FailedToFetchUpdates": "Falha ao buscar atualizações",
|
||||
"FailedToUpdateSettings": "Falha ao atualizar as configurações",
|
||||
"FeatureRequests": "Solicitações de recursos",
|
||||
"Filename": "Nome do arquivo",
|
||||
"Fixed": "Corrigido",
|
||||
"Forums": "Fóruns",
|
||||
"FreeSpace": "Espaço Livre",
|
||||
"From": "De",
|
||||
"GeneralSettings": "Configurações Gerais",
|
||||
"Health": "Saúde",
|
||||
"HomePage": "Página Inicial",
|
||||
"OnLatestVersion": "A versão mais recente do Sonarr já está instalada",
|
||||
"InstallLatest": "Instalar o mais recente",
|
||||
"Interval": "Intervalo",
|
||||
"IRC": "IRC",
|
||||
"LastDuration": "Última Duração",
|
||||
"LastExecution": "Última Execução",
|
||||
"LastWriteTime": "Hora da Última Gravação",
|
||||
"Location": "Localização",
|
||||
"LogFilesLocation": "Os arquivos de log estão localizados em: {location}",
|
||||
"Logs": "Logs",
|
||||
"MaintenanceRelease": "Versão de manutenção: correções de bugs e outras melhorias. Veja Github Commit History para mais detalhes",
|
||||
"Manual": "Manual",
|
||||
"Message": "Mensagem",
|
||||
"Mode": "Modo",
|
||||
"MoreInfo": "Mais informações",
|
||||
"New": "Novo",
|
||||
"NextExecution": "Próxima Execução",
|
||||
"NoBackupsAreAvailable": "Não há backups disponíveis",
|
||||
"NoEventsFound": "Nenhum evento encontrado",
|
||||
"NoIssuesWithYourConfiguration": "Sem problemas com sua configuração",
|
||||
"NoLeaveIt": "Não, deixe-o",
|
||||
"NoLogFiles": "Nenhum arquivo de log",
|
||||
"NoUpdatesAreAvailable": "Nenhuma atualização está disponível",
|
||||
"Options": "Opções",
|
||||
"PackageVersion": "Versão do pacote",
|
||||
"PackageVersionInfo": "{packageVersion} por {packageAuthor}",
|
||||
"PreviouslyInstalled": "Instalado anteriormente",
|
||||
"Queued": "Enfileirados",
|
||||
"ReadTheWikiForMoreInformation": "Leia o Wiki para mais informações",
|
||||
"Refresh": "Atualizar",
|
||||
"Reload": "Recarregar",
|
||||
"RemovedFromTaskQueue": "Removido da fila de tarefas",
|
||||
"Restart": "Reiniciar",
|
||||
"TaskUserAgentTooltip": "User-Agent fornecido pelo aplicativo que chamou a API",
|
||||
"ResetQualityDefinitions": "Redefinir Definições de Qualidade",
|
||||
"ResetQualityDefinitionsMessageText": "Tem certeza de que deseja redefinir as definições de qualidade?",
|
||||
"ResetTitles": "Redefinir Títulos",
|
||||
"Restore": "Restaurar",
|
||||
"RestoreBackup": "Restaurar backup",
|
||||
"Scheduled": "Agendado",
|
||||
"SeriesEditor": "Editor de séries",
|
||||
"Size": "Tamanho",
|
||||
"Source": "Fonte",
|
||||
"Started": "Iniciado",
|
||||
"StartupDirectory": "Diretório de inicialização",
|
||||
"Status": "Estado",
|
||||
"TestAll": "Testar Tudo",
|
||||
"TheLogLevelDefault": "O padrão do nível de log é 'Info' e pode ser alterado em [Configurações gerais](/configurações /geral)",
|
||||
"Time": "Horário",
|
||||
"TotalSpace": "Espaço Total",
|
||||
"Twitter": "Twitter",
|
||||
"UnableToLoadBackups": "Não foi possível carregar os backups",
|
||||
"UnableToUpdateSonarrDirectly": "Incapaz de atualizar o Sonarr diretamente,",
|
||||
"UpdaterLogFiles": "Arquivos de log do atualizador",
|
||||
"Uptime": "Tempo de atividade",
|
||||
"Wiki": "Wiki",
|
||||
"WouldYouLikeToRestoreBackup": "Gostaria de restaurar o backup '{name}'?",
|
||||
"YesCancel": "Sim, Cancelar",
|
||||
"Reset": "Redefinir",
|
||||
"ResetDefinitionTitlesHelpText": "Redefinir títulos de definição, bem como valores",
|
||||
"RestartReloadNote": "Observação: o Sonarr reiniciará automaticamente e recarregará a IU durante o processo de restauração."
|
||||
}
|
||||
|
|
|
@ -44,7 +44,7 @@ namespace NzbDrone.Core.Organizer
|
|||
_standardSeries = new Series
|
||||
{
|
||||
SeriesType = SeriesTypes.Standard,
|
||||
Title = "The Series Title!",
|
||||
Title = "The Series Title's!",
|
||||
Year = 2010,
|
||||
ImdbId = "tt12345",
|
||||
TvdbId = 12345,
|
||||
|
@ -54,7 +54,7 @@ namespace NzbDrone.Core.Organizer
|
|||
_dailySeries = new Series
|
||||
{
|
||||
SeriesType = SeriesTypes.Daily,
|
||||
Title = "The Series Title!",
|
||||
Title = "The Series Title's!",
|
||||
Year = 2010,
|
||||
ImdbId = "tt12345",
|
||||
TvdbId = 12345,
|
||||
|
@ -64,7 +64,7 @@ namespace NzbDrone.Core.Organizer
|
|||
_animeSeries = new Series
|
||||
{
|
||||
SeriesType = SeriesTypes.Anime,
|
||||
Title = "The Series Title!",
|
||||
Title = "The Series Title's!",
|
||||
Year = 2010,
|
||||
ImdbId = "tt12345",
|
||||
TvdbId = 12345,
|
||||
|
@ -141,45 +141,45 @@ namespace NzbDrone.Core.Organizer
|
|||
|
||||
_singleEpisodeFile = new EpisodeFile
|
||||
{
|
||||
Quality = new QualityModel(Quality.HDTV720p, new Revision(2)),
|
||||
RelativePath = "Series.Title.S01E01.720p.HDTV.x264-EVOLVE.mkv",
|
||||
SceneName = "Series.Title.S01E01.720p.HDTV.x264-EVOLVE",
|
||||
Quality = new QualityModel(Quality.WEBDL1080p, new Revision(2)),
|
||||
RelativePath = "The.Series.Title's!.S01E01.1080p.WEBDL.x264-EVOLVE.mkv",
|
||||
SceneName = "The.Series.Title's!.S01E01.1080p.WEBDL.x264-EVOLVE",
|
||||
ReleaseGroup = "RlsGrp",
|
||||
MediaInfo = mediaInfo
|
||||
};
|
||||
|
||||
_multiEpisodeFile = new EpisodeFile
|
||||
{
|
||||
Quality = new QualityModel(Quality.HDTV720p, new Revision(2)),
|
||||
RelativePath = "Series.Title.S01E01-E03.720p.HDTV.x264-EVOLVE.mkv",
|
||||
SceneName = "Series.Title.S01E01-E03.720p.HDTV.x264-EVOLVE",
|
||||
Quality = new QualityModel(Quality.WEBDL1080p, new Revision(2)),
|
||||
RelativePath = "The.Series.Title's!.S01E01-E03.1080p.WEBDL.x264-EVOLVE.mkv",
|
||||
SceneName = "The.Series.Title's!.S01E01-E03.1080p.WEBDL.x264-EVOLVE",
|
||||
ReleaseGroup = "RlsGrp",
|
||||
MediaInfo = mediaInfo,
|
||||
};
|
||||
|
||||
_dailyEpisodeFile = new EpisodeFile
|
||||
{
|
||||
Quality = new QualityModel(Quality.HDTV720p, new Revision(2)),
|
||||
RelativePath = "Series.Title.2013.10.30.HDTV.x264-EVOLVE.mkv",
|
||||
SceneName = "Series.Title.2013.10.30.HDTV.x264-EVOLVE",
|
||||
Quality = new QualityModel(Quality.WEBDL1080p, new Revision(2)),
|
||||
RelativePath = "The.Series.Title's!.2013.10.30.1080p.WEBDL.x264-EVOLVE.mkv",
|
||||
SceneName = "The.Series.Title's!.2013.10.30.1080p.WEBDL.x264-EVOLVE",
|
||||
ReleaseGroup = "RlsGrp",
|
||||
MediaInfo = mediaInfo
|
||||
};
|
||||
|
||||
_animeEpisodeFile = new EpisodeFile
|
||||
{
|
||||
Quality = new QualityModel(Quality.HDTV720p, new Revision(2)),
|
||||
RelativePath = "[RlsGroup] Series Title - 001 [720p].mkv",
|
||||
SceneName = "[RlsGroup] Series Title - 001 [720p]",
|
||||
Quality = new QualityModel(Quality.WEBDL1080p, new Revision(2)),
|
||||
RelativePath = "[RlsGroup] The Series Title's! - 001 [1080P].mkv",
|
||||
SceneName = "[RlsGroup] The Series Title's! - 001 [1080P]",
|
||||
ReleaseGroup = "RlsGrp",
|
||||
MediaInfo = mediaInfoAnime
|
||||
};
|
||||
|
||||
_animeMultiEpisodeFile = new EpisodeFile
|
||||
{
|
||||
Quality = new QualityModel(Quality.HDTV720p, new Revision(2)),
|
||||
RelativePath = "[RlsGroup] Series Title - 001 - 103 [720p].mkv",
|
||||
SceneName = "[RlsGroup] Series Title - 001 - 103 [720p]",
|
||||
Quality = new QualityModel(Quality.WEBDL1080p, new Revision(2)),
|
||||
RelativePath = "[RlsGroup] The Series Title's! - 001 - 103 [1080p].mkv",
|
||||
SceneName = "[RlsGroup] The Series Title's! - 001 - 103 [1080p]",
|
||||
ReleaseGroup = "RlsGrp",
|
||||
MediaInfo = mediaInfoAnime
|
||||
};
|
||||
|
|
|
@ -11,6 +11,7 @@ namespace NzbDrone.Core.Parser.Model
|
|||
public int TvdbId { get; set; }
|
||||
public int TmdbId { get; set; }
|
||||
public string ImdbId { get; set; }
|
||||
public int MalId { get; set; }
|
||||
public DateTime ReleaseDate { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
|
|
|
@ -70,7 +70,7 @@ namespace Sonarr.Api.V3.Queue
|
|||
}
|
||||
|
||||
[RestDeleteById]
|
||||
public void RemoveAction(int id, bool removeFromClient = true, bool blocklist = false)
|
||||
public void RemoveAction(int id, bool removeFromClient = true, bool blocklist = false, bool skipRedownload = false)
|
||||
{
|
||||
var pendingRelease = _pendingReleaseService.FindPendingQueueItem(id);
|
||||
|
||||
|
@ -88,12 +88,12 @@ namespace Sonarr.Api.V3.Queue
|
|||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
Remove(trackedDownload, removeFromClient, blocklist);
|
||||
Remove(trackedDownload, removeFromClient, blocklist, skipRedownload);
|
||||
_trackedDownloadService.StopTracking(trackedDownload.DownloadItem.DownloadId);
|
||||
}
|
||||
|
||||
[HttpDelete("bulk")]
|
||||
public object RemoveMany([FromBody] QueueBulkResource resource, [FromQuery] bool removeFromClient = true, [FromQuery] bool blocklist = false)
|
||||
public object RemoveMany([FromBody] QueueBulkResource resource, [FromQuery] bool removeFromClient = true, [FromQuery] bool blocklist = false, [FromQuery] bool skipRedownload = false)
|
||||
{
|
||||
var trackedDownloadIds = new List<string>();
|
||||
var pendingToRemove = new List<NzbDrone.Core.Queue.Queue>();
|
||||
|
@ -124,7 +124,7 @@ namespace Sonarr.Api.V3.Queue
|
|||
|
||||
foreach (var trackedDownload in trackedToRemove.DistinctBy(t => t.DownloadItem.DownloadId))
|
||||
{
|
||||
Remove(trackedDownload, removeFromClient, blocklist);
|
||||
Remove(trackedDownload, removeFromClient, blocklist, skipRedownload);
|
||||
trackedDownloadIds.Add(trackedDownload.DownloadItem.DownloadId);
|
||||
}
|
||||
|
||||
|
@ -255,7 +255,7 @@ namespace Sonarr.Api.V3.Queue
|
|||
_pendingReleaseService.RemovePendingQueueItems(pendingRelease.Id);
|
||||
}
|
||||
|
||||
private TrackedDownload Remove(TrackedDownload trackedDownload, bool removeFromClient, bool blocklist)
|
||||
private TrackedDownload Remove(TrackedDownload trackedDownload, bool removeFromClient, bool blocklist, bool skipRedownload)
|
||||
{
|
||||
if (removeFromClient)
|
||||
{
|
||||
|
@ -271,7 +271,7 @@ namespace Sonarr.Api.V3.Queue
|
|||
|
||||
if (blocklist)
|
||||
{
|
||||
_failedDownloadService.MarkAsFailed(trackedDownload.DownloadItem.DownloadId);
|
||||
_failedDownloadService.MarkAsFailed(trackedDownload.DownloadItem.DownloadId, skipRedownload);
|
||||
}
|
||||
|
||||
if (!removeFromClient && !blocklist)
|
||||
|
|
|
@ -5132,6 +5132,14 @@
|
|||
"type": "boolean",
|
||||
"default": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "skipRedownload",
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
|
@ -5162,6 +5170,14 @@
|
|||
"type": "boolean",
|
||||
"default": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "skipRedownload",
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
|
|
|
@ -123,7 +123,7 @@ namespace Sonarr.Http.ClientSchema
|
|||
Placeholder = fieldAttribute.Placeholder
|
||||
};
|
||||
|
||||
if (fieldAttribute.Type == FieldType.Select || fieldAttribute.Type == FieldType.TagSelect)
|
||||
if (fieldAttribute.Type is FieldType.Select or FieldType.TagSelect)
|
||||
{
|
||||
if (fieldAttribute.SelectOptionsProviderAction.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
|
@ -172,31 +172,33 @@ namespace Sonarr.Http.ClientSchema
|
|||
{
|
||||
if (selectOptions.IsEnum)
|
||||
{
|
||||
var options = selectOptions.GetFields().Where(v => v.IsStatic).Select(v =>
|
||||
{
|
||||
var name = v.Name.Replace('_', ' ');
|
||||
var value = Convert.ToInt32(v.GetRawConstantValue());
|
||||
var attrib = v.GetCustomAttribute<FieldOptionAttribute>();
|
||||
if (attrib != null)
|
||||
var options = selectOptions
|
||||
.GetFields()
|
||||
.Where(v => v.IsStatic && !v.GetCustomAttributes(false).OfType<ObsoleteAttribute>().Any())
|
||||
.Select(v =>
|
||||
{
|
||||
return new SelectOption
|
||||
var name = v.Name.Replace('_', ' ');
|
||||
var value = Convert.ToInt32(v.GetRawConstantValue());
|
||||
var attrib = v.GetCustomAttribute<FieldOptionAttribute>();
|
||||
|
||||
if (attrib != null)
|
||||
{
|
||||
Value = value,
|
||||
Name = attrib.Label ?? name,
|
||||
Order = attrib.Order,
|
||||
Hint = attrib.Hint ?? $"({value})"
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
return new SelectOption
|
||||
{
|
||||
Value = value,
|
||||
Name = attrib.Label ?? name,
|
||||
Order = attrib.Order,
|
||||
Hint = attrib.Hint ?? $"({value})"
|
||||
};
|
||||
}
|
||||
|
||||
return new SelectOption
|
||||
{
|
||||
Value = value,
|
||||
Name = name,
|
||||
Order = value
|
||||
};
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return options.OrderBy(o => o.Order).ToList();
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue