From bf09f1629c4347b81bb8135453485f145e77b5ef Mon Sep 17 00:00:00 2001 From: Bogdan Date: Thu, 20 Jul 2023 20:05:05 +0300 Subject: [PATCH] Translate Download Clients Main settings menu and Custom Formats Rebase onto develop --- frontend/build/webpack.config.js | 3 +- frontend/src/Activity/Queue/QueueRow.js | 4 +- .../Activity/Queue/RemoveQueueItemModal.js | 27 +++- .../Activity/Queue/RemoveQueueItemsModal.js | 26 ++- .../Series/Index/Table/SeriesIndexTable.css | 8 + .../Index/Table/SeriesIndexTable.css.d.ts | 1 + .../Series/Index/Table/SeriesIndexTable.tsx | 1 + .../MonitoringOptionsModalContent.js | 2 +- .../CustomFormatSettingsPage.tsx | 3 +- .../CustomFormats/CustomFormats.js | 5 +- .../EditCustomFormatModalContent.js | 21 +-- .../ExportCustomFormatModalContent.js | 9 +- .../ImportCustomFormatModalContent.js | 11 +- ...ImportCustomFormatModalContentConnector.js | 10 +- .../Specifications/AddSpecificationItem.js | 7 +- .../AddSpecificationModalContent.js | 11 +- .../EditSpecificationModalContent.js | 33 ++-- .../Specifications/Specification.js | 2 +- .../DownloadClients/DownloadClientSettings.js | 4 +- .../DownloadClients/AddDownloadClientItem.js | 7 +- .../AddDownloadClientModalContent.js | 21 ++- .../DownloadClients/DownloadClient.js | 13 +- .../DownloadClients/DownloadClients.js | 5 +- .../EditDownloadClientModalContent.js | 37 +++-- .../Options/DownloadClientOptions.js | 17 +- .../EditRemotePathMappingModalContent.js | 25 +-- .../RemotePathMappings/RemotePathMapping.js | 7 +- .../RemotePathMappings/RemotePathMappings.js | 17 +- .../MediaManagement/Naming/NamingModal.js | 32 ++-- frontend/src/Settings/PendingChangesModal.js | 9 +- frontend/src/Settings/Settings.js | 55 +++---- frontend/src/Settings/SettingsToolbar.js | 3 +- frontend/src/Store/Actions/queueActions.js | 10 +- .../src/System/Status/MoreInfo/MoreInfo.js | 8 +- .../FileNameBuilderTests/CleanTitleFixture.cs | 2 + .../CleanTitleWithoutYearFixture.cs | 2 + .../CleanTitleYearFixture.cs | 1 + .../Download/DownloadFailedEvent.cs | 1 + .../Download/FailedDownloadService.cs | 17 +- .../RedownloadFailedDownloadService.cs | 8 +- .../ImportLists/Simkl/SimklAPI.cs | 11 ++ .../ImportLists/Simkl/SimklParser.cs | 47 +++++- .../Simkl/User/SimklUserRequestGenerator.cs | 2 +- .../Simkl/User/SimklUserSettings.cs | 4 + .../Simkl/User/SimklUserShowType.cs | 8 + src/NzbDrone.Core/Localization/Core/en.json | 150 +++++++++++++++++- .../Localization/Core/pt_BR.json | 97 ++++++++++- .../Organizer/FileNameSampleService.cs | 36 ++--- .../Parser/Model/ImportListItemInfo.cs | 1 + src/Sonarr.Api.V3/Queue/QueueController.cs | 12 +- src/Sonarr.Api.V3/openapi.json | 16 ++ src/Sonarr.Http/ClientSchema/SchemaBuilder.cs | 38 ++--- 52 files changed, 672 insertions(+), 235 deletions(-) create mode 100644 src/NzbDrone.Core/ImportLists/Simkl/User/SimklUserShowType.cs diff --git a/frontend/build/webpack.config.js b/frontend/build/webpack.config.js index 733e2bc4d..e0ec27c27 100644 --- a/frontend/build/webpack.config.js +++ b/frontend/build/webpack.config.js @@ -91,7 +91,8 @@ module.exports = (env) => { }), new MiniCssExtractPlugin({ - filename: 'Content/styles.css' + filename: 'Content/styles.css', + chunkFilename: 'Content/[id]-[chunkhash].css' }), new HtmlWebpackPlugin({ diff --git a/frontend/src/Activity/Queue/QueueRow.js b/frontend/src/Activity/Queue/QueueRow.js index 708f960bb..0f66fbfa4 100644 --- a/frontend/src/Activity/Queue/QueueRow.js +++ b/frontend/src/Activity/Queue/QueueRow.js @@ -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 }); }; diff --git a/frontend/src/Activity/Queue/RemoveQueueItemModal.js b/frontend/src/Activity/Queue/RemoveQueueItemModal.js index f1a152734..8260d4362 100644 --- a/frontend/src/Activity/Queue/RemoveQueueItemModal.js +++ b/frontend/src/Activity/Queue/RemoveQueueItemModal.js @@ -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 ( + { + blocklist ? + + {translate('SkipRedownload')} + + : + null + } diff --git a/frontend/src/Activity/Queue/RemoveQueueItemsModal.js b/frontend/src/Activity/Queue/RemoveQueueItemsModal.js index 738559ed5..9ee6fef87 100644 --- a/frontend/src/Activity/Queue/RemoveQueueItemsModal.js +++ b/frontend/src/Activity/Queue/RemoveQueueItemsModal.js @@ -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 ( + { + blocklist ? + + {translate('SkipRedownload')} + + : + null + } diff --git a/frontend/src/Series/Index/Table/SeriesIndexTable.css b/frontend/src/Series/Index/Table/SeriesIndexTable.css index 455f0bc7c..0bfc5fec4 100644 --- a/frontend/src/Series/Index/Table/SeriesIndexTable.css +++ b/frontend/src/Series/Index/Table/SeriesIndexTable.css @@ -1,3 +1,11 @@ .tableScroller { position: relative; } + +.row { + transition: background-color 500ms; + + &:hover { + background-color: var(--tableRowHoverBackgroundColor); + } +} diff --git a/frontend/src/Series/Index/Table/SeriesIndexTable.css.d.ts b/frontend/src/Series/Index/Table/SeriesIndexTable.css.d.ts index 712cb8f72..ff35c263f 100644 --- a/frontend/src/Series/Index/Table/SeriesIndexTable.css.d.ts +++ b/frontend/src/Series/Index/Table/SeriesIndexTable.css.d.ts @@ -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; diff --git a/frontend/src/Series/Index/Table/SeriesIndexTable.tsx b/frontend/src/Series/Index/Table/SeriesIndexTable.tsx index c0d5e169c..c1401f984 100644 --- a/frontend/src/Series/Index/Table/SeriesIndexTable.tsx +++ b/frontend/src/Series/Index/Table/SeriesIndexTable.tsx @@ -65,6 +65,7 @@ const Row: React.FC> = ({ justifyContent: 'space-between', ...style, }} + className={styles.row} > + +
diff --git a/frontend/src/Settings/CustomFormats/CustomFormats/EditCustomFormatModalContent.js b/frontend/src/Settings/CustomFormats/CustomFormats/EditCustomFormatModalContent.js index 8e16ab54c..ecdedecfe 100644 --- a/frontend/src/Settings/CustomFormats/CustomFormats/EditCustomFormatModalContent.js +++ b/frontend/src/Settings/CustomFormats/CustomFormats/EditCustomFormatModalContent.js @@ -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 { - {id ? 'Edit Custom Format' : 'Add Custom Format'} + {id ? translate('EditCustomFormat') : translate('AddCustomFormat')} @@ -112,7 +113,7 @@ class EditCustomFormatModalContent extends Component { { !isFetching && !!error &&
- {'Unable to add a new custom format, please try again.'} + {translate('UnableToAddANewCustomFormatPleaseTryAgain')}
} @@ -124,7 +125,7 @@ class EditCustomFormatModalContent extends Component { > - Name + {translate('Name')} - {'Include Custom Format when Renaming'} + {translate('IncludeCustomFormatWhenRenaming')} -
+
{ specifications.map((tag) => { @@ -205,7 +206,7 @@ class EditCustomFormatModalContent extends Component { kind={kinds.DANGER} onPress={onDeleteCustomFormatPress} > - Delete + {translate('Delete')} } @@ -213,14 +214,14 @@ class EditCustomFormatModalContent extends Component { className={styles.deleteButton} onPress={this.onImportPress} > - Import + {translate('Import')}
- Save + {translate('Save')} diff --git a/frontend/src/Settings/CustomFormats/CustomFormats/ExportCustomFormatModalContent.js b/frontend/src/Settings/CustomFormats/CustomFormats/ExportCustomFormatModalContent.js index 96e93d3b6..41f2a36bb 100644 --- a/frontend/src/Settings/CustomFormats/CustomFormats/ExportCustomFormatModalContent.js +++ b/frontend/src/Settings/CustomFormats/CustomFormats/ExportCustomFormatModalContent.js @@ -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 { - Export Custom Format + {translate('ExportCustomFormat')} @@ -41,7 +42,7 @@ class ExportCustomFormatModalContent extends Component { { !isFetching && !!error &&
- Unable to load custom formats + {translate('UnableToLoadCustomFormats')}
} @@ -59,13 +60,13 @@ class ExportCustomFormatModalContent extends Component {
diff --git a/frontend/src/Settings/CustomFormats/CustomFormats/ImportCustomFormatModalContent.js b/frontend/src/Settings/CustomFormats/CustomFormats/ImportCustomFormatModalContent.js index 5fc796ea6..ba7d77902 100644 --- a/frontend/src/Settings/CustomFormats/CustomFormats/ImportCustomFormatModalContent.js +++ b/frontend/src/Settings/CustomFormats/CustomFormats/ImportCustomFormatModalContent.js @@ -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 { - Import Custom Format + {translate('ImportCustomFormat')} @@ -95,7 +96,7 @@ class ImportCustomFormatModalContent extends Component { { !isFetching && !!error &&
- Unable to load custom formats + {translate('UnableToLoadCustomFormats')}
} @@ -104,7 +105,7 @@ class ImportCustomFormatModalContent extends Component {
- Custom Format JSON + {translate('CustomFormatJSON')} - Cancel + {translate('Cancel')} - Import + {translate('Import')} diff --git a/frontend/src/Settings/CustomFormats/CustomFormats/ImportCustomFormatModalContentConnector.js b/frontend/src/Settings/CustomFormats/CustomFormats/ImportCustomFormatModalContentConnector.js index 3a92bd136..53124e9e9 100644 --- a/frontend/src/Settings/CustomFormats/CustomFormats/ImportCustomFormatModalContentConnector.js +++ b/frontend/src/Settings/CustomFormats/CustomFormats/ImportCustomFormatModalContentConnector.js @@ -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 }); diff --git a/frontend/src/Settings/CustomFormats/CustomFormats/Specifications/AddSpecificationItem.js b/frontend/src/Settings/CustomFormats/CustomFormats/Specifications/AddSpecificationItem.js index 3ca832977..e4c32750c 100644 --- a/frontend/src/Settings/CustomFormats/CustomFormats/Specifications/AddSpecificationItem.js +++ b/frontend/src/Settings/CustomFormats/CustomFormats/Specifications/AddSpecificationItem.js @@ -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')} @@ -65,7 +66,7 @@ class AddSpecificationItem extends Component { className={styles.presetsMenuButton} size={sizes.SMALL} > - Presets + {translate('Presets')} @@ -90,7 +91,7 @@ class AddSpecificationItem extends Component { to={infoLink} size={sizes.SMALL} > - More Info + {translate('MoreInfo')}
diff --git a/frontend/src/Settings/CustomFormats/CustomFormats/Specifications/AddSpecificationModalContent.js b/frontend/src/Settings/CustomFormats/CustomFormats/Specifications/AddSpecificationModalContent.js index e62593301..6ccefa705 100644 --- a/frontend/src/Settings/CustomFormats/CustomFormats/Specifications/AddSpecificationModalContent.js +++ b/frontend/src/Settings/CustomFormats/CustomFormats/Specifications/AddSpecificationModalContent.js @@ -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 &&
- {'Unable to add a new condition, please try again.'} + {translate('UnableToAddANewConditionPleaseTryAgain')}
} @@ -52,11 +53,11 @@ class AddSpecificationModalContent extends Component {
- {'Sonarr supports custom conditions against the release properties below.'} + {translate('SonarrSupportsCustomConditionsAgainstTheReleasePropertiesBelow')}
- {'Visit the wiki for more details: '} - {'Wiki'} + {translate('VisitTheWikiForMoreDetails')} + {translate('Wiki')}
@@ -81,7 +82,7 @@ class AddSpecificationModalContent extends Component { diff --git a/frontend/src/Settings/CustomFormats/CustomFormats/Specifications/EditSpecificationModalContent.js b/frontend/src/Settings/CustomFormats/CustomFormats/Specifications/EditSpecificationModalContent.js index 2364f5746..9f4532281 100644 --- a/frontend/src/Settings/CustomFormats/CustomFormats/Specifications/EditSpecificationModalContent.js +++ b/frontend/src/Settings/CustomFormats/CustomFormats/Specifications/EditSpecificationModalContent.js @@ -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 ( - {`${id ? 'Edit' : 'Add'} Condition - ${implementationName}`} + {`${id ? translate('Edit') : translate('Add')} ${translate('Condition')} - ${implementationName}`} @@ -51,19 +52,25 @@ function EditSpecificationModalContent(props) { fields && fields.some((x) => x.label === 'Regular Expression') &&
-
\\^$.|?*+()[{ have special meanings and need escaping with a \\' }} /> - {'More details'} {'Here'} +
\\^$.|?*+()[{', + escapeChars: '\\' + }) + }} + /> + {translate('MoreDetails')} {translate('Here')}
- {'Regular expressions can be tested '} - Here + {translate('RegularExpressionsCanBeTested')} + {translate('Here')}
} - Name + {translate('Name')} - Negate + {translate('Negate')} - Required + {translate('Required')} @@ -126,21 +133,21 @@ function EditSpecificationModalContent(props) { kind={kinds.DANGER} onPress={onDeleteSpecificationPress} > - Delete + {translate('Delete')} } - Save + {translate('Save')} diff --git a/frontend/src/Settings/CustomFormats/CustomFormats/Specifications/Specification.js b/frontend/src/Settings/CustomFormats/CustomFormats/Specifications/Specification.js index 5d06efcf2..0b59f09d5 100644 --- a/frontend/src/Settings/CustomFormats/CustomFormats/Specifications/Specification.js +++ b/frontend/src/Settings/CustomFormats/CustomFormats/Specifications/Specification.js @@ -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} diff --git a/frontend/src/Settings/DownloadClients/DownloadClientSettings.js b/frontend/src/Settings/DownloadClients/DownloadClientSettings.js index aca9111b3..8eec91d37 100644 --- a/frontend/src/Settings/DownloadClients/DownloadClientSettings.js +++ b/frontend/src/Settings/DownloadClients/DownloadClientSettings.js @@ -70,7 +70,7 @@ class DownloadClientSettings extends Component { } = this.state; return ( - + - Custom + {translate('Custom')} @@ -65,7 +66,7 @@ class AddDownloadClientItem extends Component { className={styles.presetsMenuButton} size={sizes.SMALL} > - Presets + {translate('Presets')} @@ -90,7 +91,7 @@ class AddDownloadClientItem extends Component { to={infoLink} size={sizes.SMALL} > - More info + {translate('MoreInfo')}
diff --git a/frontend/src/Settings/DownloadClients/DownloadClients/AddDownloadClientModalContent.js b/frontend/src/Settings/DownloadClients/DownloadClients/AddDownloadClientModalContent.js index 73d7a3f9e..5054a0640 100644 --- a/frontend/src/Settings/DownloadClients/DownloadClients/AddDownloadClientModalContent.js +++ b/frontend/src/Settings/DownloadClients/DownloadClients/AddDownloadClientModalContent.js @@ -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 ( - Add Download Client + {translate('AddDownloadClient')} @@ -42,7 +43,9 @@ class AddDownloadClientModalContent extends Component { { !isSchemaFetching && !!schemaError && -
Unable to add a new downloadClient, please try again.
+
+ {translate('UnableToAddANewDownloadClientPleaseTryAgain')} +
} { @@ -50,11 +53,15 @@ class AddDownloadClientModalContent extends Component {
-
Sonarr supports many popular torrent and usenet download clients.
-
For more information on the individual download clients, click the more info buttons.
+
+ {translate('SonarrSupportsAnyDownloadClient')} +
+
+ {translate('ForMoreInformationOnTheIndividualDownloadClients')} +
-
+
{ usenetDownloadClients.map((downloadClient) => { @@ -71,7 +78,7 @@ class AddDownloadClientModalContent extends Component {
-
+
{ torrentDownloadClients.map((downloadClient) => { @@ -94,7 +101,7 @@ class AddDownloadClientModalContent extends Component { diff --git a/frontend/src/Settings/DownloadClients/DownloadClients/DownloadClient.js b/frontend/src/Settings/DownloadClients/DownloadClients/DownloadClient.js index 4f0b7e3df..fceaeda65 100644 --- a/frontend/src/Settings/DownloadClients/DownloadClients/DownloadClient.js +++ b/frontend/src/Settings/DownloadClients/DownloadClients/DownloadClient.js @@ -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 ? : } @@ -91,7 +92,7 @@ class DownloadClient extends Component { kind={kinds.DISABLED} outline={true} > - Priority: {priority} + {translate('PrioritySettings', { priority })} }
@@ -111,9 +112,9 @@ class DownloadClient extends Component { diff --git a/frontend/src/Settings/DownloadClients/DownloadClients/DownloadClients.js b/frontend/src/Settings/DownloadClients/DownloadClients/DownloadClients.js index e1e27b37c..a547ab4b1 100644 --- a/frontend/src/Settings/DownloadClients/DownloadClients/DownloadClients.js +++ b/frontend/src/Settings/DownloadClients/DownloadClients/DownloadClients.js @@ -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 ( -
+
diff --git a/frontend/src/Settings/DownloadClients/DownloadClients/EditDownloadClientModalContent.js b/frontend/src/Settings/DownloadClients/DownloadClients/EditDownloadClientModalContent.js index f6ed3c118..ca374ad35 100644 --- a/frontend/src/Settings/DownloadClients/DownloadClients/EditDownloadClientModalContent.js +++ b/frontend/src/Settings/DownloadClients/DownloadClients/EditDownloadClientModalContent.js @@ -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 ( - {`${id ? 'Edit' : 'Add'} Download Client - ${implementationName}`} + {`${id ? translate('Edit') : translate('Add')} ${translate('DownloadClient')} - ${implementationName}`} @@ -68,7 +69,9 @@ class EditDownloadClientModalContent extends Component { { !isFetching && !!error && -
Unable to add a new download client, please try again.
+
+ {translate('UnableToAddANewDownloadClientPleaseTryAgain')} +
} { @@ -85,7 +88,7 @@ class EditDownloadClientModalContent extends Component { } - Name + {translate('Name')} - Enable + {translate('Enable')} - Client Priority + {translate('ClientPriority')} - Tags + {translate('Tags')} @@ -152,15 +155,15 @@ class EditDownloadClientModalContent extends Component {
- Remove Completed + {translate('RemoveCompleted')} @@ -169,12 +172,12 @@ class EditDownloadClientModalContent extends Component { { protocol.value !== 'torrent' && - Remove Failed + {translate('RemoveFailed')} @@ -192,7 +195,7 @@ class EditDownloadClientModalContent extends Component { kind={kinds.DANGER} onPress={onDeleteDownloadClientPress} > - Delete + {translate('Delete')} } @@ -201,13 +204,13 @@ class EditDownloadClientModalContent extends Component { error={saveError} onPress={onTestPress} > - Test + {translate('Test')} - Save + {translate('Save')} diff --git a/frontend/src/Settings/DownloadClients/Options/DownloadClientOptions.js b/frontend/src/Settings/DownloadClients/Options/DownloadClientOptions.js index f3bb181d7..0f77222ad 100644 --- a/frontend/src/Settings/DownloadClients/Options/DownloadClientOptions.js +++ b/frontend/src/Settings/DownloadClients/Options/DownloadClientOptions.js @@ -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 && - Unable to load download client options + + {translate('UnableToLoadDownloadClientOptions')} + } { hasSettings && !isFetching && !error && advancedSettings &&
-
+
- Enable + {translate('Enable')} @@ -58,12 +61,12 @@ function DownloadClientOptions(props) { isAdvanced={true} size={sizes.MEDIUM} > - Redownload Failed + {translate('RedownloadFailed')} @@ -71,7 +74,7 @@ function DownloadClientOptions(props) { - The Remove settings were moved to the individual Download Client settings in the table above. + {translate('RemoveDownloadsAlert')}
diff --git a/frontend/src/Settings/DownloadClients/RemotePathMappings/EditRemotePathMappingModalContent.js b/frontend/src/Settings/DownloadClients/RemotePathMappings/EditRemotePathMappingModalContent.js index 204695623..36c88674a 100644 --- a/frontend/src/Settings/DownloadClients/RemotePathMappings/EditRemotePathMappingModalContent.js +++ b/frontend/src/Settings/DownloadClients/RemotePathMappings/EditRemotePathMappingModalContent.js @@ -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 ( - {id ? 'Edit Remote Path Mapping' : 'Add Remote Path Mapping'} + {id ? translate('EditRemotePathMapping') : translate('AddRemotePathMapping')} @@ -51,19 +52,21 @@ function EditRemotePathMappingModalContent(props) { { !isFetching && !!error && -
Unable to add a new remote path mapping, please try again.
+
+ {translate('UnableToAddANewRemotePathMappingPleaseTryAgain')} +
} { !isFetching && !error &&
- Host + {translate('Host')} - Remote Path + {translate('RemotePath')} - Local Path + {translate('LocalPath')} @@ -105,14 +108,14 @@ function EditRemotePathMappingModalContent(props) { kind={kinds.DANGER} onPress={onDeleteRemotePathMappingPress} > - Delete + {translate('Delete')} } - Save + {translate('Save')} diff --git a/frontend/src/Settings/DownloadClients/RemotePathMappings/RemotePathMapping.js b/frontend/src/Settings/DownloadClients/RemotePathMappings/RemotePathMapping.js index ae2d54a65..ad7daad9e 100644 --- a/frontend/src/Settings/DownloadClients/RemotePathMappings/RemotePathMapping.js +++ b/frontend/src/Settings/DownloadClients/RemotePathMappings/RemotePathMapping.js @@ -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 { diff --git a/frontend/src/Settings/DownloadClients/RemotePathMappings/RemotePathMappings.js b/frontend/src/Settings/DownloadClients/RemotePathMappings/RemotePathMappings.js index 57f215618..e793ef6c6 100644 --- a/frontend/src/Settings/DownloadClients/RemotePathMappings/RemotePathMappings.js +++ b/frontend/src/Settings/DownloadClients/RemotePathMappings/RemotePathMappings.js @@ -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 ( -
+
-
Host
-
Remote Path
-
Local Path
+
+ {translate('Host')} +
+
+ {translate('RemotePath')} +
+
+ {translate('LocalPath')} +
diff --git a/frontend/src/Settings/MediaManagement/Naming/NamingModal.js b/frontend/src/Settings/MediaManagement/Naming/NamingModal.js index 3d2530048..8bbca7dad 100644 --- a/frontend/src/Settings/MediaManagement/Naming/NamingModal.js +++ b/frontend/src/Settings/MediaManagement/Naming/NamingModal.js @@ -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 { diff --git a/frontend/src/Settings/PendingChangesModal.js b/frontend/src/Settings/PendingChangesModal.js index 478c7cafa..4cb83e8f6 100644 --- a/frontend/src/Settings/PendingChangesModal.js +++ b/frontend/src/Settings/PendingChangesModal.js @@ -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} > - Unsaved Changes + {translate('UnsavedChanges')} - You have unsaved changes, are you sure you want to leave this page? + {translate('PendingChangesMessage')} @@ -38,7 +39,7 @@ function PendingChangesModal(props) { kind={kinds.DEFAULT} onPress={onCancel} > - Stay and review changes + {translate('PendingChangesStayReview')} diff --git a/frontend/src/Settings/Settings.js b/frontend/src/Settings/Settings.js index 3ee38d7ef..7906c66d7 100644 --- a/frontend/src/Settings/Settings.js +++ b/frontend/src/Settings/Settings.js @@ -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 ( - + @@ -17,143 +18,143 @@ function Settings() { className={styles.link} to="/settings/mediamanagement" > - Media Management + {translate('MediaManagement')}
- Naming, file management settings and root folders + {translate('MediaManagementSettingsSummary')}
- Profiles + {translate('Profiles')}
- Quality, Language, Delay and Release profiles + {translate('ProfilesSettingsSummary')}
- Quality + {translate('Quality')}
- Quality sizes and naming + {translate('QualitySettingsSummary')}
- Custom Formats + {translate('CustomFormats')}
- Custom Formats and Settings + {translate('CustomFormatsSettingsSummary')}
- Indexers + {translate('Indexers')}
- Indexers and indexer options + {translate('IndexersSettingsSummary')}
- Download Clients + {translate('DownloadClients')}
- Download clients, download handling and remote path mappings + {translate('DownloadClientsSettingsSummary')}
- Import Lists + {translate('ImportLists')}
- Import from another Sonarr instance or Trakt lists and manage list exclusions + {translate('ImportListsSettingsSummary')}
- Connect + {translate('Connect')}
- Notifications, connections to media servers/players and custom scripts + {translate('ConnectSettingsSummary')}
- Metadata + {translate('Metadata')}
- Create metadata files when episodes are imported or series are refreshed + {translate('MetadataSettingsSummary')}
- Metadata Source + {translate('MetadataSource')}
- Information on where Sonarr gets Series and Episode information + {translate('MetadataSourceSettingsSummary')}
- Tags + {translate('Tags')}
- See all tags and how they are used. Unused tags can be removed + {translate('TagsSettingsSummary')}
- General + {translate('General')}
- Port, SSL, username/password, proxy, analytics and updates + {translate('GeneralSettingsSummary')}
- UI + {translate('UI')}
- Calendar, date and color impaired options + {translate('UISettingsSummary')}
diff --git a/frontend/src/Settings/SettingsToolbar.js b/frontend/src/Settings/SettingsToolbar.js index 80ac84563..048dcc66e 100644 --- a/frontend/src/Settings/SettingsToolbar.js +++ b/frontend/src/Settings/SettingsToolbar.js @@ -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 && - #sonarr on Libera + + {translate('IRCLinkText')} + - Libera webchat + + {translate('LiberaWebchat')} + diff --git a/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/CleanTitleFixture.cs b/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/CleanTitleFixture.cs index 3c7a17c8a..674522748 100644 --- a/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/CleanTitleFixture.cs +++ b/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/CleanTitleFixture.cs @@ -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) diff --git a/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/CleanTitleWithoutYearFixture.cs b/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/CleanTitleWithoutYearFixture.cs index 1cf761402..8e3a3f0b7 100644 --- a/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/CleanTitleWithoutYearFixture.cs +++ b/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/CleanTitleWithoutYearFixture.cs @@ -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; diff --git a/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/CleanTitleYearFixture.cs b/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/CleanTitleYearFixture.cs index 8ed607983..92fce8e8d 100644 --- a/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/CleanTitleYearFixture.cs +++ b/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/CleanTitleYearFixture.cs @@ -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; diff --git a/src/NzbDrone.Core/Download/DownloadFailedEvent.cs b/src/NzbDrone.Core/Download/DownloadFailedEvent.cs index 143eba14e..757e5da97 100644 --- a/src/NzbDrone.Core/Download/DownloadFailedEvent.cs +++ b/src/NzbDrone.Core/Download/DownloadFailedEvent.cs @@ -23,5 +23,6 @@ namespace NzbDrone.Core.Download public Dictionary Data { get; set; } public TrackedDownload TrackedDownload { get; set; } public List Languages { get; set; } + public bool SkipRedownload { get; set; } } } diff --git a/src/NzbDrone.Core/Download/FailedDownloadService.cs b/src/NzbDrone.Core/Download/FailedDownloadService.cs index f26741ca8..8e07b23fd 100644 --- a/src/NzbDrone.Core/Download/FailedDownloadService.cs +++ b/src/NzbDrone.Core/Download/FailedDownloadService.cs @@ -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 { history }, "Manually marked as failed"); + PublishDownloadFailedEvent(new List { 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 historyItems, string message, TrackedDownload trackedDownload = null) + private void PublishDownloadFailedEvent(List 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); diff --git a/src/NzbDrone.Core/Download/RedownloadFailedDownloadService.cs b/src/NzbDrone.Core/Download/RedownloadFailedDownloadService.cs index ed23f021b..20c19e15d 100644 --- a/src/NzbDrone.Core/Download/RedownloadFailedDownloadService.cs +++ b/src/NzbDrone.Core/Download/RedownloadFailedDownloadService.cs @@ -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"); diff --git a/src/NzbDrone.Core/ImportLists/Simkl/SimklAPI.cs b/src/NzbDrone.Core/ImportLists/Simkl/SimklAPI.cs index 4472af249..0290dabfb 100644 --- a/src/NzbDrone.Core/ImportLists/Simkl/SimklAPI.cs +++ b/src/NzbDrone.Core/ImportLists/Simkl/SimklAPI.cs @@ -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 Shows { get; set; } + public List 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 + } } diff --git a/src/NzbDrone.Core/ImportLists/Simkl/SimklParser.cs b/src/NzbDrone.Core/ImportLists/Simkl/SimklParser.cs index 6f5758369..1419c6be7 100644 --- a/src/NzbDrone.Core/ImportLists/Simkl/SimklParser.cs +++ b/src/NzbDrone.Core/ImportLists/Simkl/SimklParser.cs @@ -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 ParseResponse(ImportListResponse importResponse) { @@ -22,7 +29,7 @@ namespace NzbDrone.Core.ImportLists.Simkl return series; } - var jsonResponse = STJson.Deserialize(_importResponse.Content); + var jsonResponse = Json.Deserialize(_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; diff --git a/src/NzbDrone.Core/ImportLists/Simkl/User/SimklUserRequestGenerator.cs b/src/NzbDrone.Core/ImportLists/Simkl/User/SimklUserRequestGenerator.cs index 90bdccf1a..74ea05831 100644 --- a/src/NzbDrone.Core/ImportLists/Simkl/User/SimklUserRequestGenerator.cs +++ b/src/NzbDrone.Core/ImportLists/Simkl/User/SimklUserRequestGenerator.cs @@ -21,7 +21,7 @@ namespace NzbDrone.Core.ImportLists.Simkl.User private IEnumerable 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); diff --git a/src/NzbDrone.Core/ImportLists/Simkl/User/SimklUserSettings.cs b/src/NzbDrone.Core/ImportLists/Simkl/User/SimklUserSettings.cs index 61cc48129..65f19aa3f 100644 --- a/src/NzbDrone.Core/ImportLists/Simkl/User/SimklUserSettings.cs +++ b/src/NzbDrone.Core/ImportLists/Simkl/User/SimklUserSettings.cs @@ -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; } } } diff --git a/src/NzbDrone.Core/ImportLists/Simkl/User/SimklUserShowType.cs b/src/NzbDrone.Core/ImportLists/Simkl/User/SimklUserShowType.cs new file mode 100644 index 000000000..8ff2eb16d --- /dev/null +++ b/src/NzbDrone.Core/ImportLists/Simkl/User/SimklUserShowType.cs @@ -0,0 +1,8 @@ +namespace NzbDrone.Core.ImportLists.Simkl.User +{ + public enum SimklUserShowType + { + Shows = 0, + Anime = 1 + } +} diff --git a/src/NzbDrone.Core/Localization/Core/en.json b/src/NzbDrone.Core/Localization/Core/en.json index 31d7cfe44..a77e919fc 100644 --- a/src/NzbDrone.Core/Localization/Core/en.json +++ b/src/NzbDrone.Core/Localization/Core/en.json @@ -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}'?", diff --git a/src/NzbDrone.Core/Localization/Core/pt_BR.json b/src/NzbDrone.Core/Localization/Core/pt_BR.json index 881648a48..fa9e847d3 100644 --- a/src/NzbDrone.Core/Localization/Core/pt_BR.json +++ b/src/NzbDrone.Core/Localization/Core/pt_BR.json @@ -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." } diff --git a/src/NzbDrone.Core/Organizer/FileNameSampleService.cs b/src/NzbDrone.Core/Organizer/FileNameSampleService.cs index 6e66633d3..a8d89364a 100644 --- a/src/NzbDrone.Core/Organizer/FileNameSampleService.cs +++ b/src/NzbDrone.Core/Organizer/FileNameSampleService.cs @@ -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 }; diff --git a/src/NzbDrone.Core/Parser/Model/ImportListItemInfo.cs b/src/NzbDrone.Core/Parser/Model/ImportListItemInfo.cs index df76ac2c2..182c2fca9 100644 --- a/src/NzbDrone.Core/Parser/Model/ImportListItemInfo.cs +++ b/src/NzbDrone.Core/Parser/Model/ImportListItemInfo.cs @@ -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() diff --git a/src/Sonarr.Api.V3/Queue/QueueController.cs b/src/Sonarr.Api.V3/Queue/QueueController.cs index 605bc9db9..6259956c3 100644 --- a/src/Sonarr.Api.V3/Queue/QueueController.cs +++ b/src/Sonarr.Api.V3/Queue/QueueController.cs @@ -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(); var pendingToRemove = new List(); @@ -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) diff --git a/src/Sonarr.Api.V3/openapi.json b/src/Sonarr.Api.V3/openapi.json index f585f2877..60543a7a3 100644 --- a/src/Sonarr.Api.V3/openapi.json +++ b/src/Sonarr.Api.V3/openapi.json @@ -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": { diff --git a/src/Sonarr.Http/ClientSchema/SchemaBuilder.cs b/src/Sonarr.Http/ClientSchema/SchemaBuilder.cs index dcbcb8ed0..4f1bb8383 100644 --- a/src/Sonarr.Http/ClientSchema/SchemaBuilder.cs +++ b/src/Sonarr.Http/ClientSchema/SchemaBuilder.cs @@ -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(); - if (attrib != null) + var options = selectOptions + .GetFields() + .Where(v => v.IsStatic && !v.GetCustomAttributes(false).OfType().Any()) + .Select(v => { - return new SelectOption + var name = v.Name.Replace('_', ' '); + var value = Convert.ToInt32(v.GetRawConstantValue()); + var attrib = v.GetCustomAttribute(); + + 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(); }