From bc19ead182dc06678ce51152bb64ccf72d8681d8 Mon Sep 17 00:00:00 2001 From: Bogdan Date: Sun, 30 Jun 2024 07:21:14 +0300 Subject: [PATCH 001/314] Bump version to 2.4.2 --- azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index f17e5374a..8ddae788c 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -9,7 +9,7 @@ variables: testsFolder: './_tests' yarnCacheFolder: $(Pipeline.Workspace)/.yarn nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages - majorVersion: '2.4.1' + majorVersion: '2.4.2' minorVersion: $[counter('minorVersion', 1076)] lidarrVersion: '$(majorVersion).$(minorVersion)' buildName: '$(Build.SourceBranchName).$(lidarrVersion)' From bfcbb67054d0664bb334188ed309ef0bdea7984f Mon Sep 17 00:00:00 2001 From: Bogdan Date: Sun, 30 Jun 2024 20:47:00 +0300 Subject: [PATCH 002/314] Fixed: Already imported downloads appearing in Queue briefly (cherry picked from commit 8099ba10afded446779290de29b1baaf0be932c3) Closes #4877 --- src/NzbDrone.Core.Test/QueueTests/QueueServiceFixture.cs | 1 + src/NzbDrone.Core/Queue/QueueService.cs | 9 ++++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/NzbDrone.Core.Test/QueueTests/QueueServiceFixture.cs b/src/NzbDrone.Core.Test/QueueTests/QueueServiceFixture.cs index 174e0fb1b..ac62bbb37 100644 --- a/src/NzbDrone.Core.Test/QueueTests/QueueServiceFixture.cs +++ b/src/NzbDrone.Core.Test/QueueTests/QueueServiceFixture.cs @@ -46,6 +46,7 @@ namespace NzbDrone.Core.Test.QueueTests _trackedDownloads = Builder.CreateListOfSize(1) .All() + .With(v => v.IsTrackable = true) .With(v => v.DownloadItem = downloadItem) .With(v => v.RemoteAlbum = remoteAlbum) .Build() diff --git a/src/NzbDrone.Core/Queue/QueueService.cs b/src/NzbDrone.Core/Queue/QueueService.cs index 68c4389ba..e23bb47a8 100644 --- a/src/NzbDrone.Core/Queue/QueueService.cs +++ b/src/NzbDrone.Core/Queue/QueueService.cs @@ -20,7 +20,7 @@ namespace NzbDrone.Core.Queue public class QueueService : IQueueService, IHandle { private readonly IEventAggregator _eventAggregator; - private static List _queue = new List(); + private static List _queue = new (); private readonly IHistoryService _historyService; public QueueService(IEventAggregator eventAggregator, @@ -106,8 +106,11 @@ namespace NzbDrone.Core.Queue public void Handle(TrackedDownloadRefreshedEvent message) { - _queue = message.TrackedDownloads.OrderBy(c => c.DownloadItem.RemainingTime).SelectMany(MapQueue) - .ToList(); + _queue = message.TrackedDownloads + .Where(t => t.IsTrackable) + .OrderBy(c => c.DownloadItem.RemainingTime) + .SelectMany(MapQueue) + .ToList(); _eventAggregator.PublishEvent(new QueueUpdatedEvent()); } From bcfabacbd47f7205ce0d5abfaacefeffacfdfa48 Mon Sep 17 00:00:00 2001 From: Bogdan Date: Sun, 7 Jul 2024 14:48:10 +0300 Subject: [PATCH 003/314] Bump version to 2.4.3 --- azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 8ddae788c..bd348fe34 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -9,7 +9,7 @@ variables: testsFolder: './_tests' yarnCacheFolder: $(Pipeline.Workspace)/.yarn nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages - majorVersion: '2.4.2' + majorVersion: '2.4.3' minorVersion: $[counter('minorVersion', 1076)] lidarrVersion: '$(majorVersion).$(minorVersion)' buildName: '$(Build.SourceBranchName).$(lidarrVersion)' From 2ad7396f6db5c2dfcb9e5b584f92650567cabb43 Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Sun, 7 May 2023 18:55:00 -0700 Subject: [PATCH 004/314] New: Added UI for parsing release names (cherry picked from commit 85e285598106346099ceae676599c5cb4b789c92) --- frontend/src/App/AppRoutes.js | 4 +- frontend/src/App/State/AppState.ts | 2 + frontend/src/App/State/ParseAppState.ts | 35 ++++ frontend/src/Helpers/Props/icons.js | 2 + frontend/src/Parse/Parse.css | 45 +++++ frontend/src/Parse/Parse.css.d.ts | 12 ++ frontend/src/Parse/Parse.tsx | 109 ++++++++++++ frontend/src/Parse/ParseModal.tsx | 20 +++ frontend/src/Parse/ParseModalContent.css | 45 +++++ frontend/src/Parse/ParseModalContent.css.d.ts | 12 ++ frontend/src/Parse/ParseModalContent.tsx | 122 +++++++++++++ frontend/src/Parse/ParseResult.css | 8 + frontend/src/Parse/ParseResult.css.d.ts | 8 + frontend/src/Parse/ParseResult.tsx | 160 ++++++++++++++++++ frontend/src/Parse/ParseResultItem.css | 21 +++ frontend/src/Parse/ParseResultItem.css.d.ts | 8 + frontend/src/Parse/ParseResultItem.tsx | 20 +++ frontend/src/Parse/ParseToolbarButton.tsx | 31 ++++ frontend/src/Parse/parseStateSelector.ts | 12 ++ .../CustomFormatSettingsConnector.js | 33 ---- .../CustomFormatSettingsPage.tsx | 42 +++++ .../src/Settings/SettingsToolbarConnector.js | 1 + frontend/src/Store/Actions/index.js | 2 + frontend/src/Store/Actions/parseActions.ts | 111 ++++++++++++ src/Lidarr.Api.V1/openapi.json | 8 +- src/NzbDrone.Core/Localization/Core/en.json | 12 ++ .../Parser/Model/ParsedAlbumInfo.cs | 2 +- 27 files changed, 847 insertions(+), 40 deletions(-) create mode 100644 frontend/src/App/State/ParseAppState.ts create mode 100644 frontend/src/Parse/Parse.css create mode 100644 frontend/src/Parse/Parse.css.d.ts create mode 100644 frontend/src/Parse/Parse.tsx create mode 100644 frontend/src/Parse/ParseModal.tsx create mode 100644 frontend/src/Parse/ParseModalContent.css create mode 100644 frontend/src/Parse/ParseModalContent.css.d.ts create mode 100644 frontend/src/Parse/ParseModalContent.tsx create mode 100644 frontend/src/Parse/ParseResult.css create mode 100644 frontend/src/Parse/ParseResult.css.d.ts create mode 100644 frontend/src/Parse/ParseResult.tsx create mode 100644 frontend/src/Parse/ParseResultItem.css create mode 100644 frontend/src/Parse/ParseResultItem.css.d.ts create mode 100644 frontend/src/Parse/ParseResultItem.tsx create mode 100644 frontend/src/Parse/ParseToolbarButton.tsx create mode 100644 frontend/src/Parse/parseStateSelector.ts delete mode 100644 frontend/src/Settings/CustomFormats/CustomFormatSettingsConnector.js create mode 100644 frontend/src/Settings/CustomFormats/CustomFormatSettingsPage.tsx create mode 100644 frontend/src/Store/Actions/parseActions.ts diff --git a/frontend/src/App/AppRoutes.js b/frontend/src/App/AppRoutes.js index 0af990f43..d12f3c4b0 100644 --- a/frontend/src/App/AppRoutes.js +++ b/frontend/src/App/AppRoutes.js @@ -11,7 +11,7 @@ import CalendarPageConnector from 'Calendar/CalendarPageConnector'; import NotFound from 'Components/NotFound'; import Switch from 'Components/Router/Switch'; import AddNewItemConnector from 'Search/AddNewItemConnector'; -import CustomFormatSettingsConnector from 'Settings/CustomFormats/CustomFormatSettingsConnector'; +import CustomFormatSettingsPage from 'Settings/CustomFormats/CustomFormatSettingsPage'; import DownloadClientSettingsConnector from 'Settings/DownloadClients/DownloadClientSettingsConnector'; import GeneralSettingsConnector from 'Settings/General/GeneralSettingsConnector'; import ImportListSettingsConnector from 'Settings/ImportLists/ImportListSettingsConnector'; @@ -184,7 +184,7 @@ function AppRoutes(props) { ; + +export default ParseAppState; diff --git a/frontend/src/Helpers/Props/icons.js b/frontend/src/Helpers/Props/icons.js index ccb8b90e9..aa9c23145 100644 --- a/frontend/src/Helpers/Props/icons.js +++ b/frontend/src/Helpers/Props/icons.js @@ -32,6 +32,7 @@ import { faBookReader as fasBookReader, faBroadcastTower as fasBroadcastTower, faBug as fasBug, + faCalculator as fasCalculator, faCalendarAlt as fasCalendarAlt, faCaretDown as fasCaretDown, faCheck as fasCheck, @@ -187,6 +188,7 @@ export const PAGE_PREVIOUS = fasBackward; export const PAGE_NEXT = fasForward; export const PAGE_LAST = fasFastForward; export const PARENT = fasLevelUpAlt; +export const PARSE = fasCalculator; export const PAUSED = fasPause; export const PENDING = farClock; export const PROFILE = fasUser; diff --git a/frontend/src/Parse/Parse.css b/frontend/src/Parse/Parse.css new file mode 100644 index 000000000..43536452c --- /dev/null +++ b/frontend/src/Parse/Parse.css @@ -0,0 +1,45 @@ +.inputContainer { + display: flex; + margin-bottom: 10px; +} + +.inputIconContainer { + width: 58px; + height: 46px; + border: 1px solid var(--inputBorderColor); + border-right: none; + border-radius: 4px; + border-top-right-radius: 0; + border-bottom-right-radius: 0; + background-color: var(--inputIconContainerBackgroundColor); + text-align: center; + line-height: 46px; +} + +.input { + composes: input from '~Components/Form/TextInput.css'; + + height: 46px; + border-radius: 0; + font-size: 18px; +} + +.clearButton { + border: 1px solid var(--inputBorderColor); + border-left: none; + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); +} + +.message { + margin-top: 30px; + text-align: center; + font-weight: 300; + font-size: $largeFontSize; +} + +.helpText { + margin-bottom: 10px; + font-size: 24px; +} diff --git a/frontend/src/Parse/Parse.css.d.ts b/frontend/src/Parse/Parse.css.d.ts new file mode 100644 index 000000000..4a4def577 --- /dev/null +++ b/frontend/src/Parse/Parse.css.d.ts @@ -0,0 +1,12 @@ +// This file is automatically generated. +// Please do not change this file! +interface CssExports { + 'clearButton': string; + 'helpText': string; + 'input': string; + 'inputContainer': string; + 'inputIconContainer': string; + 'message': string; +} +export const cssExports: CssExports; +export default cssExports; diff --git a/frontend/src/Parse/Parse.tsx b/frontend/src/Parse/Parse.tsx new file mode 100644 index 000000000..15a0deb47 --- /dev/null +++ b/frontend/src/Parse/Parse.tsx @@ -0,0 +1,109 @@ +import React, { useCallback, useEffect, useState } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import TextInput from 'Components/Form/TextInput'; +import Icon from 'Components/Icon'; +import Button from 'Components/Link/Button'; +import LoadingIndicator from 'Components/Loading/LoadingIndicator'; +import PageContent from 'Components/Page/PageContent'; +import PageContentBody from 'Components/Page/PageContentBody'; +import { icons } from 'Helpers/Props'; +import { clear, fetch } from 'Store/Actions/parseActions'; +import getErrorMessage from 'Utilities/Object/getErrorMessage'; +import translate from 'Utilities/String/translate'; +import ParseResult from './ParseResult'; +import parseStateSelector from './parseStateSelector'; +import styles from './Parse.css'; + +function Parse() { + const { isFetching, error, item } = useSelector(parseStateSelector()); + + const [title, setTitle] = useState(''); + const dispatch = useDispatch(); + + const onInputChange = useCallback( + ({ value }: { value: string }) => { + const trimmedValue = value.trim(); + + setTitle(value); + + if (trimmedValue === '') { + dispatch(clear()); + } else { + dispatch(fetch({ title: trimmedValue })); + } + }, + [setTitle, dispatch] + ); + + const onClearPress = useCallback(() => { + setTitle(''); + dispatch(clear()); + }, [setTitle, dispatch]); + + useEffect( + () => { + return () => { + dispatch(clear()); + }; + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [] + ); + + return ( + + +
+
+ +
+ + + + +
+ + {isFetching ? : null} + + {!isFetching && !!error ? ( +
+
+ {translate('ParseModalErrorParsing')} +
+
{getErrorMessage(error)}
+
+ ) : null} + + {!isFetching && title && !error && !item.parsedAlbumInfo ? ( +
+ {translate('ParseModalUnableToParse')} +
+ ) : null} + + {!isFetching && !error && item.parsedAlbumInfo ? ( + + ) : null} + + {title ? null : ( +
+
+ {translate('ParseModalHelpText')} +
+
{translate('ParseModalHelpTextDetails')}
+
+ )} +
+
+ ); +} + +export default Parse; diff --git a/frontend/src/Parse/ParseModal.tsx b/frontend/src/Parse/ParseModal.tsx new file mode 100644 index 000000000..0ee455bf0 --- /dev/null +++ b/frontend/src/Parse/ParseModal.tsx @@ -0,0 +1,20 @@ +import React from 'react'; +import Modal from 'Components/Modal/Modal'; +import ParseModalContent from './ParseModalContent'; + +interface ParseModalProps { + isOpen: boolean; + onModalClose: () => void; +} + +function ParseModal(props: ParseModalProps) { + const { isOpen, onModalClose } = props; + + return ( + + + + ); +} + +export default ParseModal; diff --git a/frontend/src/Parse/ParseModalContent.css b/frontend/src/Parse/ParseModalContent.css new file mode 100644 index 000000000..43536452c --- /dev/null +++ b/frontend/src/Parse/ParseModalContent.css @@ -0,0 +1,45 @@ +.inputContainer { + display: flex; + margin-bottom: 10px; +} + +.inputIconContainer { + width: 58px; + height: 46px; + border: 1px solid var(--inputBorderColor); + border-right: none; + border-radius: 4px; + border-top-right-radius: 0; + border-bottom-right-radius: 0; + background-color: var(--inputIconContainerBackgroundColor); + text-align: center; + line-height: 46px; +} + +.input { + composes: input from '~Components/Form/TextInput.css'; + + height: 46px; + border-radius: 0; + font-size: 18px; +} + +.clearButton { + border: 1px solid var(--inputBorderColor); + border-left: none; + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); +} + +.message { + margin-top: 30px; + text-align: center; + font-weight: 300; + font-size: $largeFontSize; +} + +.helpText { + margin-bottom: 10px; + font-size: 24px; +} diff --git a/frontend/src/Parse/ParseModalContent.css.d.ts b/frontend/src/Parse/ParseModalContent.css.d.ts new file mode 100644 index 000000000..4a4def577 --- /dev/null +++ b/frontend/src/Parse/ParseModalContent.css.d.ts @@ -0,0 +1,12 @@ +// This file is automatically generated. +// Please do not change this file! +interface CssExports { + 'clearButton': string; + 'helpText': string; + 'input': string; + 'inputContainer': string; + 'inputIconContainer': string; + 'message': string; +} +export const cssExports: CssExports; +export default cssExports; diff --git a/frontend/src/Parse/ParseModalContent.tsx b/frontend/src/Parse/ParseModalContent.tsx new file mode 100644 index 000000000..d5ae93759 --- /dev/null +++ b/frontend/src/Parse/ParseModalContent.tsx @@ -0,0 +1,122 @@ +import React, { useCallback, useEffect, useState } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import TextInput from 'Components/Form/TextInput'; +import Icon from 'Components/Icon'; +import Button from 'Components/Link/Button'; +import LoadingIndicator from 'Components/Loading/LoadingIndicator'; +import ModalBody from 'Components/Modal/ModalBody'; +import ModalContent from 'Components/Modal/ModalContent'; +import ModalFooter from 'Components/Modal/ModalFooter'; +import ModalHeader from 'Components/Modal/ModalHeader'; +import { icons } from 'Helpers/Props'; +import { clear, fetch } from 'Store/Actions/parseActions'; +import getErrorMessage from 'Utilities/Object/getErrorMessage'; +import translate from 'Utilities/String/translate'; +import ParseResult from './ParseResult'; +import parseStateSelector from './parseStateSelector'; +import styles from './ParseModalContent.css'; + +interface ParseModalContentProps { + onModalClose: () => void; +} + +function ParseModalContent(props: ParseModalContentProps) { + const { onModalClose } = props; + const { isFetching, error, item } = useSelector(parseStateSelector()); + + const [title, setTitle] = useState(''); + const dispatch = useDispatch(); + + const onInputChange = useCallback( + ({ value }: { value: string }) => { + const trimmedValue = value.trim(); + + setTitle(value); + + if (trimmedValue === '') { + dispatch(clear()); + } else { + dispatch(fetch({ title: trimmedValue })); + } + }, + [setTitle, dispatch] + ); + + const onClearPress = useCallback(() => { + setTitle(''); + dispatch(clear()); + }, [setTitle, dispatch]); + + useEffect( + () => { + return () => { + dispatch(clear()); + }; + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [] + ); + + return ( + + {translate('TestParsing')} + + +
+
+ +
+ + + + +
+ + {isFetching ? : null} + + {!isFetching && !!error ? ( +
+
+ {translate('ParseModalErrorParsing')} +
+
{getErrorMessage(error)}
+
+ ) : null} + + {!isFetching && title && !error && !item.parsedAlbumInfo ? ( +
+ {translate('ParseModalUnableToParse')} +
+ ) : null} + + {!isFetching && !error && item.parsedAlbumInfo ? ( + + ) : null} + + {title ? null : ( +
+
+ {translate('ParseModalHelpText')} +
+
{translate('ParseModalHelpTextDetails')}
+
+ )} +
+ + + + +
+ ); +} + +export default ParseModalContent; diff --git a/frontend/src/Parse/ParseResult.css b/frontend/src/Parse/ParseResult.css new file mode 100644 index 000000000..c49c4e3fa --- /dev/null +++ b/frontend/src/Parse/ParseResult.css @@ -0,0 +1,8 @@ +.container { + display: flex; + flex-wrap: wrap; +} + +.column { + flex: 0 0 50%; +} diff --git a/frontend/src/Parse/ParseResult.css.d.ts b/frontend/src/Parse/ParseResult.css.d.ts new file mode 100644 index 000000000..653368e06 --- /dev/null +++ b/frontend/src/Parse/ParseResult.css.d.ts @@ -0,0 +1,8 @@ +// This file is automatically generated. +// Please do not change this file! +interface CssExports { + 'column': string; + 'container': string; +} +export const cssExports: CssExports; +export default cssExports; diff --git a/frontend/src/Parse/ParseResult.tsx b/frontend/src/Parse/ParseResult.tsx new file mode 100644 index 000000000..7e6c40d92 --- /dev/null +++ b/frontend/src/Parse/ParseResult.tsx @@ -0,0 +1,160 @@ +import _ from 'lodash'; +import moment from 'moment'; +import React from 'react'; +import AlbumFormats from 'Album/AlbumFormats'; +import AlbumTitleLink from 'Album/AlbumTitleLink'; +import { ParseModel } from 'App/State/ParseAppState'; +import ArtistNameLink from 'Artist/ArtistNameLink'; +import FieldSet from 'Components/FieldSet'; +import translate from 'Utilities/String/translate'; +import ParseResultItem from './ParseResultItem'; +import styles from './ParseResult.css'; + +interface ParseResultProps { + item: ParseModel; +} + +function ParseResult(props: ParseResultProps) { + const { item } = props; + const { customFormats, customFormatScore, albums, parsedAlbumInfo, artist } = + item; + + const { + releaseTitle, + artistName, + albumTitle, + releaseGroup, + discography, + quality, + } = parsedAlbumInfo; + + const sortedAlbums = _.sortBy(albums, (item) => + moment(item.releaseDate).unix() + ); + + return ( +
+
+ + + + + + + +
+ +
+
+
+ +
+
+
+ +
+
+
+ + 1 && !quality.revision.isRepack + ? translate('True') + : '-' + } + /> + + +
+ +
+ 1 ? quality.revision.version : '-' + } + /> + + +
+
+
+ +
+ + ) : ( + '-' + ) + } + /> + + + {sortedAlbums.map((album) => { + return ( +
+ +
+ ); + })} +
+ ) : ( + '-' + ) + } + /> + + + ) : ( + '-' + ) + } + /> + + + + + ); +} + +export default ParseResult; diff --git a/frontend/src/Parse/ParseResultItem.css b/frontend/src/Parse/ParseResultItem.css new file mode 100644 index 000000000..275fe7e1f --- /dev/null +++ b/frontend/src/Parse/ParseResultItem.css @@ -0,0 +1,21 @@ +.item { + display: flex; +} + +.title { + margin-right: 20px; + width: 250px; + text-align: right; + font-weight: bold; +} + +@media (max-width: $breakpointSmall) { + .item { + display: block; + margin-bottom: 10px; + } + + .title { + text-align: left; + } +} diff --git a/frontend/src/Parse/ParseResultItem.css.d.ts b/frontend/src/Parse/ParseResultItem.css.d.ts new file mode 100644 index 000000000..bcf268e50 --- /dev/null +++ b/frontend/src/Parse/ParseResultItem.css.d.ts @@ -0,0 +1,8 @@ +// This file is automatically generated. +// Please do not change this file! +interface CssExports { + 'item': string; + 'title': string; +} +export const cssExports: CssExports; +export default cssExports; diff --git a/frontend/src/Parse/ParseResultItem.tsx b/frontend/src/Parse/ParseResultItem.tsx new file mode 100644 index 000000000..661af448d --- /dev/null +++ b/frontend/src/Parse/ParseResultItem.tsx @@ -0,0 +1,20 @@ +import React, { ReactNode } from 'react'; +import styles from './ParseResultItem.css'; + +interface ParseResultItemProps { + title: string; + data: string | number | ReactNode; +} + +function ParseResultItem(props: ParseResultItemProps) { + const { title, data } = props; + + return ( +
+
{title}
+
{data}
+
+ ); +} + +export default ParseResultItem; diff --git a/frontend/src/Parse/ParseToolbarButton.tsx b/frontend/src/Parse/ParseToolbarButton.tsx new file mode 100644 index 000000000..43b8b959f --- /dev/null +++ b/frontend/src/Parse/ParseToolbarButton.tsx @@ -0,0 +1,31 @@ +import React, { Fragment, useCallback, useState } from 'react'; +import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton'; +import { icons } from 'Helpers/Props'; +import ParseModal from 'Parse/ParseModal'; +import translate from 'Utilities/String/translate'; + +function ParseToolbarButton() { + const [isParseModalOpen, setIsParseModalOpen] = useState(false); + + const onOpenParseModalPress = useCallback(() => { + setIsParseModalOpen(true); + }, [setIsParseModalOpen]); + + const onParseModalClose = useCallback(() => { + setIsParseModalOpen(false); + }, [setIsParseModalOpen]); + + return ( + + + + + + ); +} + +export default ParseToolbarButton; diff --git a/frontend/src/Parse/parseStateSelector.ts b/frontend/src/Parse/parseStateSelector.ts new file mode 100644 index 000000000..7abcfeca1 --- /dev/null +++ b/frontend/src/Parse/parseStateSelector.ts @@ -0,0 +1,12 @@ +import { createSelector } from 'reselect'; +import AppState from 'App/State/AppState'; +import ParseAppState from 'App/State/ParseAppState'; + +export default function parseStateSelector() { + return createSelector( + (state: AppState) => state.parse, + (parse: ParseAppState) => { + return parse; + } + ); +} diff --git a/frontend/src/Settings/CustomFormats/CustomFormatSettingsConnector.js b/frontend/src/Settings/CustomFormats/CustomFormatSettingsConnector.js deleted file mode 100644 index 342df29d2..000000000 --- a/frontend/src/Settings/CustomFormats/CustomFormatSettingsConnector.js +++ /dev/null @@ -1,33 +0,0 @@ -import React, { Component } from 'react'; -import { DndProvider } from 'react-dnd'; -import { HTML5Backend } from 'react-dnd-html5-backend'; -import PageContent from 'Components/Page/PageContent'; -import PageContentBody from 'Components/Page/PageContentBody'; -import SettingsToolbarConnector from 'Settings/SettingsToolbarConnector'; -import translate from 'Utilities/String/translate'; -import CustomFormatsConnector from './CustomFormats/CustomFormatsConnector'; - -class CustomFormatSettingsConnector extends Component { - - // - // Render - - render() { - return ( - - - - - - - - - - ); - } -} - -export default CustomFormatSettingsConnector; - diff --git a/frontend/src/Settings/CustomFormats/CustomFormatSettingsPage.tsx b/frontend/src/Settings/CustomFormats/CustomFormatSettingsPage.tsx new file mode 100644 index 000000000..fee176554 --- /dev/null +++ b/frontend/src/Settings/CustomFormats/CustomFormatSettingsPage.tsx @@ -0,0 +1,42 @@ +import React, { Fragment } from 'react'; +import { DndProvider } from 'react-dnd'; +import { HTML5Backend } from 'react-dnd-html5-backend'; +import PageContent from 'Components/Page/PageContent'; +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 ( + + + + + + + } + /> + + + {/* TODO: Upgrade react-dnd to get typings, we're 2 major versions behind */} + {/* eslint-disable-next-line @typescript-eslint/ban-ts-comment */} + {/* @ts-ignore */} + + {/* eslint-disable-next-line @typescript-eslint/ban-ts-comment */} + {/* @ts-ignore */} + + + + + ); +} + +export default CustomFormatSettingsPage; diff --git a/frontend/src/Settings/SettingsToolbarConnector.js b/frontend/src/Settings/SettingsToolbarConnector.js index 1e6f7a589..65d937ab8 100644 --- a/frontend/src/Settings/SettingsToolbarConnector.js +++ b/frontend/src/Settings/SettingsToolbarConnector.js @@ -134,6 +134,7 @@ const historyShape = { }; SettingsToolbarConnector.propTypes = { + showSave: PropTypes.bool, hasPendingChanges: PropTypes.bool.isRequired, history: PropTypes.shape(historyShape).isRequired, onSavePress: PropTypes.func, diff --git a/frontend/src/Store/Actions/index.js b/frontend/src/Store/Actions/index.js index 95b02d089..85bcc2645 100644 --- a/frontend/src/Store/Actions/index.js +++ b/frontend/src/Store/Actions/index.js @@ -13,6 +13,7 @@ import * as history from './historyActions'; import * as interactiveImportActions from './interactiveImportActions'; import * as oAuth from './oAuthActions'; import * as organizePreview from './organizePreviewActions'; +import * as parse from './parseActions'; import * as paths from './pathActions'; import * as providerOptions from './providerOptionActions'; import * as queue from './queueActions'; @@ -41,6 +42,7 @@ export default [ oAuth, organizePreview, retagPreview, + parse, paths, providerOptions, queue, diff --git a/frontend/src/Store/Actions/parseActions.ts b/frontend/src/Store/Actions/parseActions.ts new file mode 100644 index 000000000..d4b6e9bcb --- /dev/null +++ b/frontend/src/Store/Actions/parseActions.ts @@ -0,0 +1,111 @@ +import { Dispatch } from 'redux'; +import { createAction } from 'redux-actions'; +import { batchActions } from 'redux-batched-actions'; +import AppState from 'App/State/AppState'; +import { createThunk, handleThunks } from 'Store/thunks'; +import createAjaxRequest from 'Utilities/createAjaxRequest'; +import { set, update } from './baseActions'; +import createHandleActions from './Creators/createHandleActions'; +import createClearReducer from './Creators/Reducers/createClearReducer'; + +interface FetchPayload { + title: string; +} + +// +// Variables + +export const section = 'parse'; +let parseTimeout: number | null = null; +let abortCurrentRequest: (() => void) | null = null; + +// +// State + +export const defaultState = { + isFetching: false, + isPopulated: false, + error: null, + item: {}, +}; + +// +// Actions Types + +export const FETCH = 'parse/fetch'; +export const CLEAR = 'parse/clear'; + +// +// Action Creators + +export const fetch = createThunk(FETCH); +export const clear = createAction(CLEAR); + +// +// Action Handlers + +export const actionHandlers = handleThunks({ + [FETCH]: function ( + _getState: () => AppState, + payload: FetchPayload, + dispatch: Dispatch + ) { + if (parseTimeout) { + clearTimeout(parseTimeout); + } + + parseTimeout = window.setTimeout(async () => { + dispatch(set({ section, isFetching: true })); + + if (abortCurrentRequest) { + abortCurrentRequest(); + } + + const { request, abortRequest } = createAjaxRequest({ + url: '/parse', + data: { + title: payload.title, + }, + }); + + try { + const data = await request; + + dispatch( + batchActions([ + update({ section, data }), + + set({ + section, + isFetching: false, + isPopulated: true, + error: null, + }), + ]) + ); + } catch (error) { + dispatch( + set({ + section, + isAdding: false, + isAdded: false, + addError: error, + }) + ); + } + + abortCurrentRequest = abortRequest; + }, 300); + }, +}); + +// +// Reducers + +export const reducers = createHandleActions( + { + [CLEAR]: createClearReducer(section, defaultState), + }, + defaultState, + section +); diff --git a/src/Lidarr.Api.V1/openapi.json b/src/Lidarr.Api.V1/openapi.json index 67f10c69f..d3d5e381c 100644 --- a/src/Lidarr.Api.V1/openapi.json +++ b/src/Lidarr.Api.V1/openapi.json @@ -11263,6 +11263,10 @@ "ParsedAlbumInfo": { "type": "object", "properties": { + "releaseTitle": { + "type": "string", + "nullable": true + }, "albumTitle": { "type": "string", "nullable": true @@ -11307,10 +11311,6 @@ "releaseVersion": { "type": "string", "nullable": true - }, - "releaseTitle": { - "type": "string", - "nullable": true } }, "additionalProperties": false diff --git a/src/NzbDrone.Core/Localization/Core/en.json b/src/NzbDrone.Core/Localization/Core/en.json index 0a2ebd03c..d279bfadd 100644 --- a/src/NzbDrone.Core/Localization/Core/en.json +++ b/src/NzbDrone.Core/Localization/Core/en.json @@ -53,6 +53,7 @@ "AlbumCount": "Album Count", "AlbumDetails": "Album Details", "AlbumHasNotAired": "Album has not aired", + "AlbumInfo": "Album Info", "AlbumIsDownloading": "Album is downloading", "AlbumIsDownloadingInterp": "Album is downloading - {0}% {1}", "AlbumIsNotMonitored": "Album is not monitored", @@ -467,6 +468,7 @@ "FailedDownloadHandling": "Failed Download Handling", "FailedLoadingSearchResults": "Failed to load search results, please try again.", "FailedToLoadQueue": "Failed to load Queue", + "False": "False", "FileDateHelpText": "Change file date on import/rescan", "FileManagement": "File Management", "FileNameTokens": "File Name Tokens", @@ -677,6 +679,8 @@ "MarkAsFailedMessageText": "Are you sure you want to mark '{0}' as failed?", "MassAlbumsCutoffUnmetWarning": "Are you sure you want to search for all '{0}' Cutoff Unmet albums?", "MassSearchCancelWarning": "This cannot be cancelled once started without restarting {appName} or disabling all of your indexers.", + "MatchedToAlbums": "Matched to Albums", + "MatchedToArtist": "Matched to Artist", "MaximumLimits": "Maximum Limits", "MaximumSize": "Maximum Size", "MaximumSizeHelpText": "Maximum size for a release to be grabbed in MB. Set to zero to set to unlimited.", @@ -836,6 +840,11 @@ "PackageVersion": "Package Version", "PageSize": "Page Size", "PageSizeHelpText": "Number of items to show on each page", + "Parse": "Parse", + "ParseModalErrorParsing": "Error parsing, please try again.", + "ParseModalHelpText": "Enter a release title in the input above", + "ParseModalHelpTextDetails": "{appName} will attempt to parse the title and show you details about it", + "ParseModalUnableToParse": "Unable to parse the provided title, please try again.", "Password": "Password", "PasswordConfirmation": "Password Confirmation", "PastDays": "Past Days", @@ -982,6 +991,7 @@ "RenameTracksHelpText": "{appName} will use the existing file name if renaming is disabled", "Renamed": "Renamed", "Reorder": "Reorder", + "Repack": "Repack", "Replace": "Replace", "ReplaceExistingFiles": "Replace Existing Files", "ReplaceIllegalCharacters": "Replace Illegal Characters", @@ -1154,6 +1164,7 @@ "TestAllClients": "Test All Clients", "TestAllIndexers": "Test All Indexers", "TestAllLists": "Test All Lists", + "TestParsing": "Test Parsing", "TheAlbumsFilesWillBeDeleted": "The album's files will be deleted.", "TheArtistFolderStrongpathstrongAndAllOfItsContentWillBeDeleted": "The artist folder '{0}' and all of its content will be deleted.", "Theme": "Theme", @@ -1193,6 +1204,7 @@ "TrackStatus": "Track status", "TrackTitle": "Track Title", "Tracks": "Tracks", + "True": "True", "Type": "Type", "URLBase": "URL Base", "Ui": "UI", diff --git a/src/NzbDrone.Core/Parser/Model/ParsedAlbumInfo.cs b/src/NzbDrone.Core/Parser/Model/ParsedAlbumInfo.cs index be30e257f..1fd80a6f9 100644 --- a/src/NzbDrone.Core/Parser/Model/ParsedAlbumInfo.cs +++ b/src/NzbDrone.Core/Parser/Model/ParsedAlbumInfo.cs @@ -6,6 +6,7 @@ namespace NzbDrone.Core.Parser.Model { public class ParsedAlbumInfo { + public string ReleaseTitle { get; set; } public string AlbumTitle { get; set; } public string ArtistName { get; set; } public string AlbumType { get; set; } @@ -18,7 +19,6 @@ namespace NzbDrone.Core.Parser.Model public string ReleaseGroup { get; set; } public string ReleaseHash { get; set; } public string ReleaseVersion { get; set; } - public string ReleaseTitle { get; set; } [JsonIgnore] public Dictionary ExtraInfo { get; set; } = new Dictionary(); From 9045dea5364ff8a0677adc25e478e908e6b0ee11 Mon Sep 17 00:00:00 2001 From: Qstick Date: Sun, 7 Jul 2024 09:55:24 -0500 Subject: [PATCH 005/314] Update SonarCloud pipeline versions * Update SonarCloud pipeline versions * Update reportgenerator to remove PublishCodeCoverage dep warnings (cherry picked from commit a2a12d245000a0713946cec732d853dd7cdc58c2) (cherry picked from commit 1423ad6aa4094d11efecc1986a3d0571f310bda6) Update SonarCloud pipeline versions for UI (cherry picked from commit 558043f1b2cae371b474a19ba5784df8345d38d2) --- azure-pipelines.yml | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index bd348fe34..b9bb0f4b9 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -1120,7 +1120,7 @@ stages: vmImage: ${{ variables.windowsImage }} steps: - checkout: self # Need history for Sonar analysis - - task: SonarCloudPrepare@1 + - task: SonarCloudPrepare@2 env: SONAR_SCANNER_OPTS: '' inputs: @@ -1132,7 +1132,7 @@ stages: cliProjectName: 'LidarrUI' cliProjectVersion: '$(lidarrVersion)' cliSources: './frontend' - - task: SonarCloudAnalyze@1 + - task: SonarCloudAnalyze@2 - job: Api_Docs displayName: API Docs @@ -1208,7 +1208,7 @@ stages: submodules: true - powershell: Set-Service SCardSvr -StartupType Manual displayName: Enable Windows Test Service - - task: SonarCloudPrepare@1 + - task: SonarCloudPrepare@2 condition: eq(variables['System.PullRequest.IsFork'], 'False') inputs: SonarCloud: 'SonarCloud' @@ -1226,21 +1226,16 @@ stages: ./build.sh --backend -f net6.0 -r win-x64 TEST_DIR=_tests/net6.0/win-x64/publish/ ./test.sh Windows Unit Coverage displayName: Coverage Unit Tests - - task: SonarCloudAnalyze@1 + - task: SonarCloudAnalyze@2 condition: eq(variables['System.PullRequest.IsFork'], 'False') displayName: Publish SonarCloud Results - - task: reportgenerator@4 + - task: reportgenerator@5 displayName: Generate Coverage Report inputs: reports: '$(Build.SourcesDirectory)/CoverageResults/**/coverage.opencover.xml' targetdir: '$(Build.SourcesDirectory)/CoverageResults/combined' reporttypes: 'HtmlInline_AzurePipelines;Cobertura;Badges' - - task: PublishCodeCoverageResults@1 - displayName: Publish Coverage Report - inputs: - codeCoverageTool: 'cobertura' - summaryFileLocation: './CoverageResults/combined/Cobertura.xml' - reportDirectory: './CoverageResults/combined/' + publishCodeCoverageResults: true - stage: Report_Out dependsOn: From 2dec783272409799fced8d7c2260e1bb5781ba51 Mon Sep 17 00:00:00 2001 From: Bogdan Date: Fri, 12 Jul 2024 16:30:40 +0300 Subject: [PATCH 006/314] Fixed: Creating root folders without default tags Fixes #4898 --- src/Lidarr.Api.V1/RootFolders/RootFolderResource.cs | 2 +- src/NzbDrone.Core/RootFolders/RootFolder.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Lidarr.Api.V1/RootFolders/RootFolderResource.cs b/src/Lidarr.Api.V1/RootFolders/RootFolderResource.cs index 6684ce3dd..c9ac64dd4 100644 --- a/src/Lidarr.Api.V1/RootFolders/RootFolderResource.cs +++ b/src/Lidarr.Api.V1/RootFolders/RootFolderResource.cs @@ -67,7 +67,7 @@ namespace Lidarr.Api.V1.RootFolders DefaultQualityProfileId = resource.DefaultQualityProfileId, DefaultMonitorOption = resource.DefaultMonitorOption, DefaultNewItemMonitorOption = resource.DefaultNewItemMonitorOption, - DefaultTags = resource.DefaultTags, + DefaultTags = resource.DefaultTags ?? new HashSet() }; } diff --git a/src/NzbDrone.Core/RootFolders/RootFolder.cs b/src/NzbDrone.Core/RootFolders/RootFolder.cs index e26704c82..138bdf070 100644 --- a/src/NzbDrone.Core/RootFolders/RootFolder.cs +++ b/src/NzbDrone.Core/RootFolders/RootFolder.cs @@ -12,7 +12,7 @@ namespace NzbDrone.Core.RootFolders public int DefaultQualityProfileId { get; set; } public MonitorTypes DefaultMonitorOption { get; set; } public NewItemMonitorTypes DefaultNewItemMonitorOption { get; set; } - public HashSet DefaultTags { get; set; } + public HashSet DefaultTags { get; set; } = new (); public bool Accessible { get; set; } public long? FreeSpace { get; set; } From 4d693f78f3be72a87dfb17bab1d5d5e34061ec0c Mon Sep 17 00:00:00 2001 From: Weblate Date: Sun, 14 Jul 2024 00:25:19 +0000 Subject: [PATCH 007/314] Multiple Translations updated by Weblate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ignore-downstream Co-authored-by: Ano10 Co-authored-by: Anonymous Co-authored-by: Havok Dan Co-authored-by: Kshitij Burman Co-authored-by: Lizandra Candido da Silva Co-authored-by: Oskari Lavinto Co-authored-by: PouleY Co-authored-by: Rauniik Co-authored-by: Serhii Matrunchyk Co-authored-by: Taylan Tatlı Co-authored-by: Weblate Co-authored-by: fordas Co-authored-by: quek76 Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/ca/ Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/cs/ Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/da/ Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/de/ Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/el/ Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/es/ Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/fi/ Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/fr/ Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/he/ Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/hi/ Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/hr/ Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/hu/ Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/is/ Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/it/ Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/nb_NO/ Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/pt_BR/ Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/ro/ Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/ru/ Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/sk/ Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/sv/ Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/th/ Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/tr/ Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/uk/ Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/zh_CN/ Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/zh_TW/ Translation: Servarr/Lidarr --- src/NzbDrone.Core/Localization/Core/ca.json | 6 +- src/NzbDrone.Core/Localization/Core/cs.json | 12 +- src/NzbDrone.Core/Localization/Core/da.json | 3 +- src/NzbDrone.Core/Localization/Core/de.json | 4 +- src/NzbDrone.Core/Localization/Core/el.json | 3 +- src/NzbDrone.Core/Localization/Core/es.json | 12 +- src/NzbDrone.Core/Localization/Core/fi.json | 14 +- src/NzbDrone.Core/Localization/Core/fr.json | 26 ++-- src/NzbDrone.Core/Localization/Core/he.json | 3 +- src/NzbDrone.Core/Localization/Core/hi.json | 10 +- src/NzbDrone.Core/Localization/Core/hr.json | 3 +- src/NzbDrone.Core/Localization/Core/hu.json | 8 +- src/NzbDrone.Core/Localization/Core/is.json | 4 +- src/NzbDrone.Core/Localization/Core/it.json | 94 ++++++++++++- .../Localization/Core/nb_NO.json | 5 +- .../Localization/Core/pt_BR.json | 24 +++- src/NzbDrone.Core/Localization/Core/ro.json | 9 +- src/NzbDrone.Core/Localization/Core/ru.json | 132 +++++++++++++++++- src/NzbDrone.Core/Localization/Core/sk.json | 4 +- src/NzbDrone.Core/Localization/Core/sv.json | 4 +- src/NzbDrone.Core/Localization/Core/th.json | 3 +- src/NzbDrone.Core/Localization/Core/tr.json | 21 ++- src/NzbDrone.Core/Localization/Core/uk.json | 25 +++- .../Localization/Core/zh_CN.json | 11 +- .../Localization/Core/zh_TW.json | 3 +- 25 files changed, 397 insertions(+), 46 deletions(-) diff --git a/src/NzbDrone.Core/Localization/Core/ca.json b/src/NzbDrone.Core/Localization/Core/ca.json index c3e423a88..aeada34be 100644 --- a/src/NzbDrone.Core/Localization/Core/ca.json +++ b/src/NzbDrone.Core/Localization/Core/ca.json @@ -942,5 +942,9 @@ "Logout": "Tanca la sessió", "InteractiveSearchModalHeader": "Cerca interactiva", "Menu": "Menú", - "IndexerPriorityHelpText": "Prioritat de l'indexador d'1 (la més alta) a 50 (la més baixa). Per defecte: 25. S'utilitza quan es capturen llançaments com a desempat per a versions iguals, {appName} encara utilitzarà tots els indexadors habilitats per a la sincronització i la cerca RSS" + "IndexerPriorityHelpText": "Prioritat de l'indexador d'1 (la més alta) a 50 (la més baixa). Per defecte: 25. S'utilitza quan es capturen llançaments com a desempat per a versions iguals, {appName} encara utilitzarà tots els indexadors habilitats per a la sincronització i la cerca RSS", + "False": "Fals", + "Parse": "Analitza", + "True": "Vertader", + "Repack": "Tornat a empaquetar" } diff --git a/src/NzbDrone.Core/Localization/Core/cs.json b/src/NzbDrone.Core/Localization/Core/cs.json index 05ef62900..6c3cd56d6 100644 --- a/src/NzbDrone.Core/Localization/Core/cs.json +++ b/src/NzbDrone.Core/Localization/Core/cs.json @@ -5,7 +5,7 @@ "Columns": "Sloupce", "CopyUsingHardlinksHelpText": "Hardlinks použijte, když se pokoušíte kopírovat soubory z torrentů, které se stále používají", "CopyUsingHardlinksHelpTextWarning": "Zámky souborů mohou občas zabránit přejmenování souborů, které se právě vysazují. Výsev můžete dočasně deaktivovat a použít funkci {appName} pro přejmenování.", - "CutoffUnmet": "Cut-off Unmet", + "CutoffUnmet": "Mezní hodnota nesplněna", "MIA": "MIA", "Actions": "Akce", "ArtistAlbumClickToChangeTrack": "Kliknutím změníte film", @@ -861,5 +861,13 @@ "InteractiveSearchModalHeader": "Interaktivní vyhledávání", "Release": " Uvolněno", "AutoTaggingSpecificationTag": "Značka", - "IndexerPriorityHelpText": "Priorita indexovacího modulu od 1 (nejvyšší) do 50 (nejnižší). Výchozí: 25. Používá se při získávání verzí jako rozhodující prvek pro jinak stejné verze, {appName} bude stále používat všechny povolené indexovací moduly pro Synchronizaci RSS a vyhledávání" + "IndexerPriorityHelpText": "Priorita indexovacího modulu od 1 (nejvyšší) do 50 (nejnižší). Výchozí: 25. Používá se při získávání verzí jako rozhodující prvek pro jinak stejné verze, {appName} bude stále používat všechny povolené indexovací moduly pro Synchronizaci RSS a vyhledávání", + "CustomFormatsSpecificationFlag": "Vlajka", + "CustomFormatsSpecificationRegularExpressionHelpText": "Vlastní formát RegEx nerozlišuje velká a malá písmena", + "BlocklistAndSearch": "Seznam blokovaných a vyhledávání", + "BlocklistMultipleOnlyHint": "Blokovat a nehledat náhradu", + "ChangeCategory": "Změnit kategorii", + "CustomFormatsSettingsTriggerInfo": "Vlastní formát se použije na vydání nebo soubor, pokud odpovídá alespoň jednomu z různých typů zvolených podmínek.", + "ConnectionSettingsUrlBaseHelpText": "Přidá předponu do {connectionName} url, jako např. {url}", + "BlocklistOnlyHint": "Blokovat a nehledat náhradu" } diff --git a/src/NzbDrone.Core/Localization/Core/da.json b/src/NzbDrone.Core/Localization/Core/da.json index 4d0879b91..4d6ba0025 100644 --- a/src/NzbDrone.Core/Localization/Core/da.json +++ b/src/NzbDrone.Core/Localization/Core/da.json @@ -765,5 +765,6 @@ "Release": " udgivelse", "InteractiveSearchModalHeader": "Interaktiv søgning", "FormatAgeHour": "Timer", - "IndexerPriorityHelpText": "Indekseringsprioritet fra 1 (højest) til 50 (lavest). Standard: 25." + "IndexerPriorityHelpText": "Indekseringsprioritet fra 1 (højest) til 50 (lavest). Standard: 25.", + "AutoTaggingNegateHelpText": "Hvis dette er markeret, gælder det tilpassede format ikke, hvis denne {0} betingelse stemmer overens." } diff --git a/src/NzbDrone.Core/Localization/Core/de.json b/src/NzbDrone.Core/Localization/Core/de.json index 94ee8c652..3f22e712d 100644 --- a/src/NzbDrone.Core/Localization/Core/de.json +++ b/src/NzbDrone.Core/Localization/Core/de.json @@ -1176,5 +1176,7 @@ "DeleteSpecificationHelpText": "Sind Sie sicher, dass Sie die Spezifikation „{name}“ löschen möchten?", "DownloadClientDelugeSettingsDirectoryCompletedHelpText": "Optionaler Speicherort für Downloads. Lassen Sie das Feld leer, um den standardmäßigen rTorrent-Speicherort zu verwenden", "DownloadClientDelugeSettingsDirectoryHelpText": "Optionaler Speicherort für Downloads. Lassen Sie das Feld leer, um den standardmäßigen rTorrent-Speicherort zu verwenden", - "WhatsNew": "Was ist neu?" + "WhatsNew": "Was ist neu?", + "TestParsing": "Parsing testen", + "True": "WAHR" } diff --git a/src/NzbDrone.Core/Localization/Core/el.json b/src/NzbDrone.Core/Localization/Core/el.json index 0527f18a3..aab0bdb1c 100644 --- a/src/NzbDrone.Core/Localization/Core/el.json +++ b/src/NzbDrone.Core/Localization/Core/el.json @@ -1105,5 +1105,6 @@ "InteractiveSearchModalHeader": "Διαδραστική αναζήτηση", "AutoTaggingSpecificationTag": "Ετικέτα", "FormatAgeHour": "Ωρες", - "IndexerPriorityHelpText": "Προτεραιότητα δείκτη από 1 (υψηλότερη) έως 50 (χαμηλότερη). Προεπιλογή: 25. Χρησιμοποιείται κατά την κατάκτηση εκδόσεων ως ισοπαλία για ίσες εκδόσεις, το {appName} θα εξακολουθεί να χρησιμοποιεί όλα τα ενεργοποιημένα ευρετήρια για RSS Sync και Search" + "IndexerPriorityHelpText": "Προτεραιότητα δείκτη από 1 (υψηλότερη) έως 50 (χαμηλότερη). Προεπιλογή: 25. Χρησιμοποιείται κατά την κατάκτηση εκδόσεων ως ισοπαλία για ίσες εκδόσεις, το {appName} θα εξακολουθεί να χρησιμοποιεί όλα τα ενεργοποιημένα ευρετήρια για RSS Sync και Search", + "AutoTaggingNegateHelpText": "Εάν επιλεγεί, η προσαρμοσμένη μορφή δεν θα εφαρμοστεί εάν αντιστοιχεί σε αυτήν την {0} συνθήκη." } diff --git a/src/NzbDrone.Core/Localization/Core/es.json b/src/NzbDrone.Core/Localization/Core/es.json index 640555a9e..1c393bd53 100644 --- a/src/NzbDrone.Core/Localization/Core/es.json +++ b/src/NzbDrone.Core/Localization/Core/es.json @@ -1289,5 +1289,15 @@ "IndexerSettingsSeedTime": "Tiempo de sembrado", "IndexerSettingsSeedTimeHelpText": "El tiempo que un torrent debería ser sembrado antes de parar, vacío usa el predeterminado del cliente de descarga", "InteractiveSearchModalHeader": "Búsqueda interactiva", - "InteractiveSearchModalHeaderTitle": "Búsqueda interactiva - {title}" + "InteractiveSearchModalHeaderTitle": "Búsqueda interactiva - {title}", + "Parse": "Analizar", + "ParseModalErrorParsing": "Error analizando, por favor inténtalo de nuevo.", + "False": "Falso", + "ParseModalHelpText": "Introduce un título de lanzamiento en la entrada anterior", + "ParseModalHelpTextDetails": "{appName} intentará analizar el título y te mostrará detalles sobre ello", + "ParseModalUnableToParse": "No se pudo analizar el título proporcionado, por favor inténtalo de nuevo.", + "Repack": "Repack", + "TestParsing": "Probar análisis", + "True": "Verdadero", + "AlbumInfo": "Información del álbum" } diff --git a/src/NzbDrone.Core/Localization/Core/fi.json b/src/NzbDrone.Core/Localization/Core/fi.json index 51a68bab1..cd5ffe77c 100644 --- a/src/NzbDrone.Core/Localization/Core/fi.json +++ b/src/NzbDrone.Core/Localization/Core/fi.json @@ -150,7 +150,7 @@ "GrabSelected": "Kaappaa valitut", "Group": "Ryhmä", "Hostname": "Osoite", - "Importing": "Tuonti", + "Importing": "Tuodaan", "IncludeUnknownArtistItemsHelpText": "Näytä jonossa kohteet, joissa ei ole esittäjää. Tämä voi sisältää poistettuja esittäjiä, kappaleita tai mitä tahansa muuta {appName}ille luokiteltua.", "Interval": "Ajoitus", "IndexerSettings": "Tietolähdeasetukset", @@ -1238,5 +1238,15 @@ "DownloadClientDelugeSettingsDirectoryCompletedHelpText": "Valinnainen latuasten tallennussijainti. Käytä Aria2-oletusta jättämällä tyhjäksi.", "DownloadClientDelugeSettingsDirectoryHelpText": "Valinnainen latuasten tallennussijainti. Käytä Aria2-oletusta jättämällä tyhjäksi.", "IndexerSettingsSeedRatio": "Jakosuhde", - "IndexerSettingsSeedTime": "Jakoaika" + "IndexerSettingsSeedTime": "Jakoaika", + "ArtistIsMonitored": "Kirjailijaa ei valvota", + "False": "Epätosi", + "Parse": "Jäsennä", + "ParseModalErrorParsing": "Virhe jäsennettäessä. Yritä uudelleen.", + "ParseModalHelpText": "Syötä julkaisunimike yllä olevaan kenttään.", + "ParseModalHelpTextDetails": "{appName} pyrkii jäsentämään nimikkeen ja näyttämään sen tiedot.", + "ParseModalUnableToParse": "Annetun nimikkeen jäsennys ei onnistunut. Yritä uudelleen.", + "Repack": "Uudelleenpaketoitu", + "TestParsing": "Testaa jäsennystä", + "True": "Tosi" } diff --git a/src/NzbDrone.Core/Localization/Core/fr.json b/src/NzbDrone.Core/Localization/Core/fr.json index 40f0337c5..5965a30ca 100644 --- a/src/NzbDrone.Core/Localization/Core/fr.json +++ b/src/NzbDrone.Core/Localization/Core/fr.json @@ -66,7 +66,7 @@ "ConnectSettings": "Paramètres de connexion", "CopyUsingHardlinksHelpText": "Les liens fixes permettent à {appName} d'importer des torrents seedés dans le dossier de l'artiste sans prendre d'espace disque supplémentaire ou copier tout le contenu du fichier. Les liens fixes ne fonctionnent que si la source et la destination se trouvent sur le même volume.", "CopyUsingHardlinksHelpTextWarning": "De temps en temps, des verrouillages de fichiers peuvent empêcher de renommer des fichiers qui sont en cours de partage. Vous pouvez temporairement arrêter le partage et utiliser la fonction de renommage de {appName} comme solution de contournement.", - "CreateEmptyArtistFolders": "Créer des dossiers films vides", + "CreateEmptyArtistFolders": "Créer des dossiers d'artistes vides", "CreateEmptyArtistFoldersHelpText": "Créer les dossiers films manquants pendant le scan du disque", "CreateGroup": "Créer un groupe", "CutoffHelpText": "Quand cette qualité est atteinte, {appName} ne téléchargera plus de films", @@ -159,7 +159,7 @@ "IgnoredHelpText": "La version sera rejetée si elle contient au moins l'un de ces termes (insensible à la casse)", "IgnoredPlaceHolder": "Ajouter une nouvelle restriction", "ImportedTo": "Importé vers", - "ImportExtraFilesHelpText": "Importer les fichiers extra correspondants (sous-titres, .nfo etc.) après avoir importé un fichier film", + "ImportExtraFilesHelpText": "Importer les fichiers extra correspondants (sous-titres, .nfo etc.) après avoir importé une piste", "ImportFailedInterp": "Importation a échoué : {0}", "Importing": "Importation", "IncludeUnknownArtistItemsHelpText": "Afficher les éléments sans film dans la file d'attente. Cela peut inclure des films supprimés ou tout autre élément de la catégorie de {appName}", @@ -170,7 +170,7 @@ "IndexerSettings": "Paramètres de l'indexeur", "InteractiveSearch": "Recherche interactive", "Interval": "Intervalle", - "IsCutoffCutoff": "Limite", + "IsCutoffCutoff": "Couper", "IsCutoffUpgradeUntilThisQualityIsMetOrExceeded": "Mettre à niveau jusqu'à ce que cette qualité soit atteinte ou dépassée", "IsTagUsedCannotBeDeletedWhileInUse": "Ne peut pas être supprimé pendant l'utilisation", "Label": "Étiquette", @@ -286,7 +286,7 @@ "RequiresRestartToTakeEffect": "Nécessite un redémarrage pour prendre effet", "RescanAfterRefreshHelpText": "Réanalyser le dossier du film après avoir actualisé le film", "RescanAfterRefreshHelpTextWarning": "{appName} ne détectera pas automatiquement les modifications apportées aux fichiers lorsqu'il n'est pas défini sur «Toujours»", - "RescanArtistFolderAfterRefresh": "Réanalyser le dossier de films après l'actualisation", + "RescanArtistFolderAfterRefresh": "Réanalyser le dossier d'artiste après l'actualisation", "Reset": "Réinitialiser", "ResetAPIKey": "Réinitialiser la clé API", "ResetAPIKeyMessageText": "Êtes-vous sûr de vouloir réinitialiser votre clé API ?", @@ -316,11 +316,11 @@ "SslCertPathHelpTextWarning": "Nécessite un redémarrage pour prendre effet", "SSLPort": "Port SSL", "SslPortHelpTextWarning": "Nécessite un redémarrage pour prendre effet", - "StandardTrackFormat": "Format de film standard", + "StandardTrackFormat": "Format de piste standard", "StartTypingOrSelectAPathBelow": "Commencer à écrire ou sélectionner un chemin ci-dessous", "Status": "État", "Style": "Style", - "SuccessMyWorkIsDoneNoFilesToRename": "Victoire ! Mon travail est terminé, aucun fichier à renommer.", + "SuccessMyWorkIsDoneNoFilesToRename": "C'est fait ! Mon travail est terminé, plus aucun fichier à renommer.", "SuccessMyWorkIsDoneNoFilesToRetag": "Victoire ! Mon travail est terminé, aucun fichier à renommer.", "SupportsRssvalueRSSIsNotSupportedWithThisIndexer": "RSS n'est pas pris en charge avec cet indexeur", "SupportsSearchvalueSearchIsNotSupportedWithThisIndexer": "La recherche n'est pas prise en charge avec cet indexeur", @@ -1129,7 +1129,7 @@ "Unlimited": "Illimité", "ExtraFileExtensionsHelpText": "Liste de fichiers supplémentaires séparés par des virgules à importer (.nfo sera importé en tant que .nfo-orig)", "ExtraFileExtensionsHelpTextsExamples": "Exemples : '.sub, .nfo' ou 'sub,nfo'", - "DownloadClientQbittorrentSettingsContentLayoutHelpText": "Utiliser la disposition du contenu configurée par qBittorrent, la disposition originale du torrent ou toujours créer un sous-dossier (qBittorrent 4.3.2+)", + "DownloadClientQbittorrentSettingsContentLayoutHelpText": "Si il faut utiliser de la présentation du contenu configurée par qBittorrent, la présentation originale du torrent ou la création systématique d'un sous-dossier (qBittorrent 4.3.2+)", "DownloadClientPriorityHelpText": "Priorité du client de téléchargement de 1 (la plus haute) à 50 (la plus faible). Par défaut : 1. Le Round-Robin est utilisé pour les clients ayant la même priorité.", "ArtistMonitoring": "Suivi des artistes", "AddAlbumWithTitle": "Ajouter {albumTitle}", @@ -1288,5 +1288,15 @@ "UiSettingsSummary": "Calendrier, date et les options d'altération des couleurs", "MonitorFutureAlbums": "Futurs albums", "IndexerSettingsSeedRatio": "Ratio d'envoie", - "IndexerSettingsSeedTime": "Temps d'envoie" + "IndexerSettingsSeedTime": "Temps d'envoie", + "NoTracksInThisMedium": "Aucune piste sur ce support", + "False": "Faux", + "Parse": "Analyser", + "ParseModalErrorParsing": "Erreur d'analyse, veuillez réessayer.", + "Repack": "Repack", + "ParseModalHelpText": "Entrez un titre de version dans l'entrée ci-dessus", + "ParseModalHelpTextDetails": "{appName} va tenter d'analyser le titre et de vous afficher les détails à son sujet", + "ParseModalUnableToParse": "Impossible d'analyser le titre fourni, veuillez réessayer.", + "TestParsing": "Tester le parsage", + "True": "Vrai" } diff --git a/src/NzbDrone.Core/Localization/Core/he.json b/src/NzbDrone.Core/Localization/Core/he.json index b398b8936..a65dd7a14 100644 --- a/src/NzbDrone.Core/Localization/Core/he.json +++ b/src/NzbDrone.Core/Localization/Core/he.json @@ -774,5 +774,6 @@ "FormatAgeHour": "שעה (ות", "FormatAgeHours": "שעה (ות", "FormatAgeMinute": "דקות", - "FormatAgeMinutes": "דקות" + "FormatAgeMinutes": "דקות", + "AddImportListExclusionAlbumHelpText": "מנע מלהוסיף סרט לרדאר על ידי רשימות" } diff --git a/src/NzbDrone.Core/Localization/Core/hi.json b/src/NzbDrone.Core/Localization/Core/hi.json index 2e04d4e37..475f1a8ae 100644 --- a/src/NzbDrone.Core/Localization/Core/hi.json +++ b/src/NzbDrone.Core/Localization/Core/hi.json @@ -241,8 +241,8 @@ "RecyclingBinCleanup": "रीसायकल बिन सफाई", "Redownload": "redownload", "Refresh": "ताज़ा करना", - "20MinutesTwenty": "90 मिनट: {0}", - "45MinutesFourtyFive": "90 मिनट: {0}", + "20MinutesTwenty": "20 मिनट: {0}", + "45MinutesFourtyFive": "45 मिनट: {0}", "60MinutesSixty": "60 मिनट: {0}", "AgeWhenGrabbed": "आयु (जब पकड़ा गया)", "AlbumIsDownloadingInterp": "मूवी डाउनलोड हो रही है - {0}% {1}", @@ -281,7 +281,7 @@ "Actions": "कार्रवाई", "AddingTag": "टैग जोड़ना", "AddListExclusion": "सूची बहिष्करण जोड़ें", - "APIKey": "एपीआई कुंजी", + "APIKey": "एपीआई पासवर्ड", "ApiKeyHelpTextWarning": "प्रभावी करने के लिए पुनरारंभ की आवश्यकता है", "AppDataDirectory": "AppData निर्देशिका", "ApplyTags": "टैग लागू करें", @@ -731,5 +731,7 @@ "RemoveQueueItemConfirmation": "क्या आप वाकई {0} आइटम {1} को कतार से हटाना चाहते हैं?", "ArtistIndexFooterDownloading": "डाउनलोड", "AutoRedownloadFailed": "डाउनलोड विफल", - "AutomaticSearch": "स्वचालित खोज" + "AutomaticSearch": "स्वचालित खोज", + "Absolute": "पूर्ण", + "AddImportListExclusionAlbumHelpText": "मूवी को रेडर से सूचियों में जोड़े जाने से रोकें" } diff --git a/src/NzbDrone.Core/Localization/Core/hr.json b/src/NzbDrone.Core/Localization/Core/hr.json index 644fb0820..e4f5b998d 100644 --- a/src/NzbDrone.Core/Localization/Core/hr.json +++ b/src/NzbDrone.Core/Localization/Core/hr.json @@ -252,5 +252,6 @@ "ItsEasyToAddANewArtistJustStartTypingTheNameOfTheArtistYouWantToAdd": "Lako je dodati nove filmove, samo kreni upisivati ime filma kojeg želiš dodati", "AddToDownloadQueue": "Dodano u red za preuzimanje", "AddedToDownloadQueue": "Dodano u red za preuzimanje", - "EditRootFolder": "asdf" + "EditRootFolder": "asdf", + "AddImportListExclusionAlbumHelpText": "Spriječi dodavanje ovog filma na {appName} preko listi" } diff --git a/src/NzbDrone.Core/Localization/Core/hu.json b/src/NzbDrone.Core/Localization/Core/hu.json index 6f3d416fb..174e77b52 100644 --- a/src/NzbDrone.Core/Localization/Core/hu.json +++ b/src/NzbDrone.Core/Localization/Core/hu.json @@ -1202,5 +1202,11 @@ "DownloadClientDelugeSettingsDirectoryHelpText": "Választható hely a letöltések elhelyezéséhez, hagyja üresen az alapértelmezett Aria2 hely használatához", "Enabled": "Engedélyezés", "MonitorFirstAlbum": "Első album", - "Yesterday": "Tegnap" + "Yesterday": "Tegnap", + "Repack": "Újracsomagolás", + "True": "Igaz", + "False": "Hamis", + "ParseModalHelpText": "Adja meg a kiadás címét a fenti bevitelben", + "ParseModalUnableToParse": "A megadott cím nem elemezhető, próbálkozzon újra.", + "TestParsing": "Tesztelemzés" } diff --git a/src/NzbDrone.Core/Localization/Core/is.json b/src/NzbDrone.Core/Localization/Core/is.json index b9496adf0..2ef6dd353 100644 --- a/src/NzbDrone.Core/Localization/Core/is.json +++ b/src/NzbDrone.Core/Localization/Core/is.json @@ -731,5 +731,7 @@ "DefaultCase": "Sjálfgefið mál", "DeleteArtistFolderHelpText": "Eyddu kvikmyndamöppunni og innihaldi hennar", "AutomaticSearch": "Sjálfvirk leit", - "Release": " Sleppt" + "Release": " Sleppt", + "AddImportListExclusionAlbumHelpText": "Koma í veg fyrir að kvikmyndum verði bætt við {appName} af listum", + "DeleteArtistFoldersHelpText": "Eyddu kvikmyndamöppunni og innihaldi hennar" } diff --git a/src/NzbDrone.Core/Localization/Core/it.json b/src/NzbDrone.Core/Localization/Core/it.json index 0ecda209d..0f5c9cc94 100644 --- a/src/NzbDrone.Core/Localization/Core/it.json +++ b/src/NzbDrone.Core/Localization/Core/it.json @@ -904,5 +904,97 @@ "Tomorrow": "Domani", "Yesterday": "Ieri", "GrabReleaseUnknownArtistOrAlbumMessageText": "{appName} non è stato in grado di determinare a quale film si riferisce questa release. {appName} potrebbe non essere in grado di importarla automaticamente. Vuoi catturare '{0}'?", - "RemotePathMappingCheckFilesLocalWrongOSPath": "Stai utilizzando docker; Il client di download {downloadClientName} riporta files in {path} ma questo non è un percorso valido {osName}. Controlla la mappa dei percorsi remoti e le impostazioni del client di download." + "RemotePathMappingCheckFilesLocalWrongOSPath": "Stai utilizzando docker; Il client di download {downloadClientName} riporta files in {path} ma questo non è un percorso valido {osName}. Controlla la mappa dei percorsi remoti e le impostazioni del client di download.", + "FormatShortTimeSpanSeconds": "{seconds} secondo/i", + "NotificationsEmbySettingsSendNotifications": "Invia Notifiche", + "Period": "Periodo", + "SearchMonitored": "Ricerca Monitorate", + "InteractiveSearchModalHeaderTitle": "Ricerca Interattiva - {title}", + "Loading": "Caricamento", + "NotificationsKodiSettingsCleanLibraryHelpText": "Pulisci libreria dopo l'aggiornamento", + "UseSsl": "Usa SSL", + "PastDays": "Giorni Scorsi", + "External": "Esterno", + "IsExpandedHideFileInfo": "Nascondi info file", + "LabelIsRequired": "Etichetta richiesta", + "PreferProtocol": "Preferisci {preferredProtocol}", + "SelectedCountArtistsSelectedInterp": "{selectedCount} autore/i selezionato/i", + "CountDownloadClientsSelected": "{selectedCount} client di download selezionato/i", + "SearchForAllMissingAlbums": "Ricerca tutti i film mancanti", + "NoneData": "Nessun libro sarà monitorato", + "DeleteSelected": "Elimina Selezionati", + "Implementation": "Implementazione", + "SearchForMonitoredAlbums": "Ricerca libri monitorati", + "DeleteFormat": "Elimina Formato", + "ManageClients": "Gestisci Clients", + "EntityName": "Nome Entità", + "ErrorLoadingContent": "Si è verificato un errore caricando questo contenuto", + "InvalidUILanguage": "L'interfaccia è impostata in una lingua non valida, correggi e salva le tue impostazioni", + "IsExpandedShowFileInfo": "Mostra info file", + "IsShowingMonitoredUnmonitorSelected": "Monitora Selezionati", + "ManageIndexers": "Gestisci Indicizzatori", + "ManualDownload": "Download Manuale", + "MassSearchCancelWarning": "Questo non può essere cancellato una volta avviato senza riavviare {appName} o disattivando tutti i tuoi indicizzatori.", + "Menu": "Menu", + "MusicBrainzArtistID": "ID Autore MusicBrainz", + "MusicbrainzId": "ID MusicBrainz", + "NotificationsTelegramSettingsIncludeAppName": "Includi {appName} nel Titolo", + "RemoveCompletedDownloads": "Rimuovi Download Completati", + "RemoveQueueItemRemovalMethod": "Metodo di Rimozione", + "SearchForAllCutoffUnmetAlbumsConfirmationCount": "Sei sicuro di volere cercare tutti i {totalRecords} film mancanti?", + "SearchForAllMissingAlbumsConfirmationCount": "Sei sicuro di volere cercare tutti i {totalRecords} film mancanti?", + "SizeLimit": "Limite Dimensione", + "ThereWasAnErrorLoadingThisItem": "Si è verificato un errore caricando questo elemento", + "Other": "Altri", + "Total": "Totale", + "MusicBrainzTrackID": "ID Libro MusicBrainz", + "FutureDays": "Giorni Futuri", + "Monitoring": "Monitorando", + "InfoUrl": "URL Info", + "BlocklistAndSearch": "Lista dei Blocchi e Ricerca", + "ChangeCategory": "Cambia Categoria", + "CustomFormatsSpecificationRegularExpression": "Espressione Regolare", + "ArtistIsUnmonitored": "Autore non monitorato", + "DeleteArtistFolderCountConfirmation": "Sei sicuro di voler eliminare {count} applicazione(i) selezionata(e)?", + "EditSelectedIndexers": "Modifica Indicizzatori Selezionati", + "ManageLists": "Gestisci Liste", + "NoIndexersFound": "Nessun indicizzatore trovato", + "FormatAgeDays": "giorni", + "FormatAgeDay": "giorno", + "FormatDateTime": "{formattedDate} {formattedTime}", + "FormatRuntimeHours": "{hours}h", + "FormatRuntimeMinutes": "{minutes}m", + "IsShowingMonitoredMonitorSelected": "Monitora Selezionati", + "NotificationsKodiSettingsCleanLibrary": "Pulisci Libreria", + "NotificationsPlexSettingsAuthenticateWithPlexTv": "Autentica con Plex.tv", + "NotificationsSettingsUpdateLibrary": "Aggiorna Libreria", + "AutoTaggingNegateHelpText": "Se selezionato, il formato personalizzato non verrà applicato a questa {0} condizione.", + "RemoveFromDownloadClientHint": "Rimuovi il download e i file dal client di download", + "Donate": "Dona", + "RemoveFailedDownloads": "Rimuovi Download Falliti", + "ThereWasAnErrorLoadingThisPage": "Si è verificato un errore caricando questa pagina", + "IgnoreDownload": "Ignora Download", + "IgnoreDownloads": "Ignora Download", + "IndexerSettingsSeedRatio": "Rapporto Seed", + "Space": "Spazio", + "AlbumStudioTruncated": "Solo le ultime 25 stagione sono mostrate, vai ai dettagli per vedere tutte le stagioni", + "ExpandOtherByDefaultHelpText": "Altri", + "Never": "Mai", + "False": "Falso", + "FormatDateTimeRelative": "{relativeDay}, {formattedDate} {formattedTime}", + "FormatShortTimeSpanHours": "{hours} ora/e", + "FormatShortTimeSpanMinutes": "{minutes} minuto/i", + "FormatTimeSpanDays": "{days}d {time}", + "ManageDownloadClients": "Gestisci Clients di Download", + "MonitorNewItemsHelpText": "Quale libri nuovi dovrebbero essere monitorati", + "NoDownloadClientsFound": "Nessun client di download trovato", + "NoMissingItems": "Nessun elemento mancante", + "NotificationsSettingsUseSslHelpText": "Connetti a {serviceName} tramite HTTPS indece di HTTP", + "Parse": "Analizza", + "ParseModalErrorParsing": "Errore durante l'analisi, per favore prova di nuovo.", + "PasswordConfirmation": "Conferma Password", + "RemoveMultipleFromDownloadClientHint": "Rimuovi i download e i file dal client di download", + "RemoveQueueItem": "Rimuovi - {sourceTitle}", + "ResetTitles": "Reimposta Titoli", + "True": "Vero" } diff --git a/src/NzbDrone.Core/Localization/Core/nb_NO.json b/src/NzbDrone.Core/Localization/Core/nb_NO.json index 47943dbcf..99d919b51 100644 --- a/src/NzbDrone.Core/Localization/Core/nb_NO.json +++ b/src/NzbDrone.Core/Localization/Core/nb_NO.json @@ -266,5 +266,8 @@ "EditConditionImplementation": "Legg til betingelse - {implementationName}", "AddToDownloadQueue": "Legg til i nedlastingskø", "AddedToDownloadQueue": "Lagt til i nedlastingskø", - "AddIndexerImplementation": "Legg til betingelse - {implementationName}" + "AddIndexerImplementation": "Legg til betingelse - {implementationName}", + "UnableToAddANewImportListExclusionPleaseTryAgain": "Ikke mulig å legge til ny betingelse, vennligst prøv igjen", + "UnableToAddANewMetadataProfilePleaseTryAgain": "Ikke mulig å legge til ny betingelse, vennligst prøv igjen", + "UnableToAddANewQualityProfilePleaseTryAgain": "Ikke mulig å legge til ny betingelse, vennligst prøv igjen" } diff --git a/src/NzbDrone.Core/Localization/Core/pt_BR.json b/src/NzbDrone.Core/Localization/Core/pt_BR.json index e38dd937c..3cec9f32b 100644 --- a/src/NzbDrone.Core/Localization/Core/pt_BR.json +++ b/src/NzbDrone.Core/Localization/Core/pt_BR.json @@ -1,13 +1,13 @@ { "Language": "Idioma", "UiLanguage": "Idioma da interface", - "20MinutesTwenty": "20 Minutos: {0}", + "20MinutesTwenty": "20 minutos: {0}", "Blocklist": "Lista de Bloqueio", - "45MinutesFourtyFive": "45 Minutos: {0}", + "45MinutesFourtyFive": "45 minutos: {0}", "YesCancel": "Sim, Cancelar", "Actions": "Ações", "About": "Sobre", - "60MinutesSixty": "60 Minutos: {0}", + "60MinutesSixty": "60 minutos: {0}", "Absolute": "Absoluto", "Time": "Tempo", "AddingTag": "Adicionar tag", @@ -53,7 +53,7 @@ "AnyReleaseOkHelpText": "O {appName} alternará automaticamente para o lançamento que melhor corresponde às faixas baixadas", "ApiKeyHelpTextWarning": "Requer reinício para ter efeito", "ArtistClickToChangeAlbum": "Clique para mudar o álbum", - "APIKey": "Chave API", + "APIKey": "Chave da API", "AutoRedownloadFailedHelpText": "Procurar e tentar baixar automaticamente uma versão diferente", "BackupFolderHelpText": "Os caminhos relativos estarão no diretório AppData do {appName}", "BackupIntervalHelpText": "Intervalo para fazer backup do banco de dados e configurações do {appName}", @@ -703,7 +703,7 @@ "HideAdvanced": "Ocultar opções avançadas", "Ignored": "Ignorado", "IndexerDownloadClientHelpText": "Especifique qual cliente de download é usado para baixar deste indexador", - "IndexerTagHelpText": "Use este indexador apenas para artistas com pelo menos uma tag correspondente. Deixe em branco para usar com todos os artistas.", + "IndexerTagHelpText": "Usar este indexador apenas para artista com pelo menos uma etiqueta correspondente. Deixe em branco para usar com todos os artistas.", "InstanceName": "Nome da instância", "InstanceNameHelpText": "Nome da instância na aba e para o nome do aplicativo Syslog", "InteractiveImport": "Importação interativa", @@ -1289,5 +1289,17 @@ "IndexerSettingsSeedTime": "Tempo de semeação", "IndexerSettingsSeedTimeHelpText": "O tempo que um torrent deve ser semeado antes de parar, vazio usa o padrão do cliente de download", "InteractiveSearchModalHeader": "Pesquisa Interativa", - "InteractiveSearchModalHeaderTitle": "Pesquisa Interativa - {title}" + "InteractiveSearchModalHeaderTitle": "Pesquisa Interativa - {title}", + "False": "Falso", + "Parse": "Analisar", + "ParseModalErrorParsing": "Erro ao analisar, tente novamente.", + "True": "Verdadeiro", + "ParseModalHelpText": "Insira um título de lançamento na entrada acima", + "ParseModalHelpTextDetails": "O {appName} tentará analisar o título e mostrar detalhes sobre ele", + "ParseModalUnableToParse": "Não foi possível analisar o título fornecido, tente novamente.", + "Repack": "Repack", + "TestParsing": "Análise de teste", + "MatchedToArtist": "Correspondido ao artista", + "AlbumInfo": "Informações do álbum", + "MatchedToAlbums": "Correspondido aos álbuns" } diff --git a/src/NzbDrone.Core/Localization/Core/ro.json b/src/NzbDrone.Core/Localization/Core/ro.json index 0ad0176f0..f9ff438cd 100644 --- a/src/NzbDrone.Core/Localization/Core/ro.json +++ b/src/NzbDrone.Core/Localization/Core/ro.json @@ -768,5 +768,12 @@ "InfoUrl": "URL informații", "AutomaticSearch": "Căutare automată", "PosterOptions": "Opțiuni poster", - "ResetDefinitions": "Resetare definitii" + "ResetDefinitions": "Resetare definitii", + "AddImportListExclusionAlbumHelpText": "Împiedicați ca filmele să fie adăugate la {appName} pe liste", + "False": "Fals", + "Parse": "Analiza", + "ParseModalErrorParsing": "Eroare la analizare, încercați din nou.", + "ParseModalHelpTextDetails": "Radarr va încerca să analizeze titlul și să vă arate detalii despre acesta", + "ParseModalUnableToParse": "Nu se poate analiza titlul furnizat, vă rugăm să încercați din nou.", + "True": "Adevărat" } diff --git a/src/NzbDrone.Core/Localization/Core/ru.json b/src/NzbDrone.Core/Localization/Core/ru.json index c4321fae1..462a4f94b 100644 --- a/src/NzbDrone.Core/Localization/Core/ru.json +++ b/src/NzbDrone.Core/Localization/Core/ru.json @@ -760,5 +760,135 @@ "CustomFormatsSettings": "Настройки пользовательских форматов", "CustomFormatsSpecificationRegularExpression": "Регулярное выражение", "AddAutoTag": "Добавить автоматический тег", - "AddAutoTagError": "Не удалось добавить новый авто тег, пожалуйста повторите попытку." + "AddAutoTagError": "Не удалось добавить новый авто тег, пожалуйста повторите попытку.", + "MonitoredStatus": "Отслеживаемые/Статус", + "Albums": "альбом", + "InteractiveSearchModalHeader": "Интерактивный поиск", + "PosterOptions": "Опции постера", + "Posters": "Постеры", + "DefaultCase": "Случай по умолчанию", + "DeleteArtistFoldersHelpText": "Удалить папку и её содержимое", + "ConnectionLostReconnect": "Radarr попытается соединиться автоматически или нажмите кнопку внизу.", + "DeleteSpecificationHelpText": "Вы уверены, что хотите удалить уведомление '{name}'?", + "EditAutoTag": "Редактировать автоматическую маркировку", + "FileNameTokens": "Токены имени файла", + "KeyboardShortcuts": "Горячие клавиши", + "Links": "Ссылки", + "Lowercase": "Нижний регистр", + "NoLimitForAnyDuration": "Нет ограничений для любого времени", + "PreferredSize": "Предпочитаемый размер", + "RemoveQueueItemConfirmation": "Вы уверены, что хотите удалить '{sourceTitle}' из очереди?", + "Small": "Маленький", + "Uppercase": "Верхний регистр", + "WhatsNew": "Что нового?", + "ImportList": "Список", + "Overview": "Обзор", + "Priority": "Приоритет", + "OverviewOptions": "Опции обзора", + "CountDownloadClientsSelected": "{count} выбранных клиентов загрузки", + "CustomFilter": "Настраиваемые фильтры", + "DeleteAutoTag": "Удалить автоматическую маркировку", + "DisabledForLocalAddresses": "Отключено для локальных адресов", + "ExtraFileExtensionsHelpTextsExamples": "Например: '.sub, .nfo' или 'sub,nfo'", + "ImportListRootFolderMultipleMissingRootsHealthCheckMessage": "Для импортируемых списков отсутствуют несколько корневых папок: {0}", + "ImportListsSettingsSummary": "Списки для импорта и исключений", + "Large": "Большой", + "MinimumCustomFormatScoreHelpText": "Минимальная оценка пользовательского формата, необходимая для обхода задержки для предпочитаемого протокола", + "OrganizeSelectedArtists": "Упорядочить выбранные фильмы", + "QualitySettingsSummary": "Качественные размеры и наименования", + "Release": " Выпущен", + "Season": "Сезон", + "Tomorrow": "Завтра", + "RecentChanges": "Последние изменения", + "Enabled": "Включено", + "RemoveTagsAutomatically": "Автоматическое удаление тегов", + "RemoveTagsAutomaticallyHelpText": "Автоматически удалять теги, если условия не выполняются", + "ImportListRootFolderMissingRootHealthCheckMessage": "Отсутствует корневая папка для импортирования списка(ов): {0}", + "ImportLists": "Списки", + "SuggestTranslationChange": "Предложить изменение перевода", + "Unlimited": "Неограниченно", + "NoMinimumForAnyDuration": "Нет минимума для любого времени", + "AutoRedownloadFailed": "Неудачное скачивание", + "AutoTagging": "Автоматическая маркировка", + "AutoTaggingLoadError": "Не удается загрузить автоматическую маркировку", + "Connection": "Подключение", + "ConnectSettingsSummary": "Уведомления, подключения к серверам/проигрывателям и настраиваемые скрипты", + "CustomFormatsSettingsSummary": "Пользовательские форматы и настройки", + "DownloadClientsSettingsSummary": "Программы для скачивания, обработка скаченного и отдалённые ссылки", + "IncludeHealthWarnings": "Включая предупреждения о здоровье", + "SomeResultsAreHiddenByTheAppliedFilter": "Некоторые результаты скрыты примененным фильтром", + "AuthForm": "Формы (Страница авторизации)", + "AuthenticationRequiredPasswordHelpTextWarning": "Введите новый пароль", + "AuthenticationRequiredUsernameHelpTextWarning": "Введите новое имя пользователя", + "AutoTaggingSpecificationTag": "Тэг", + "BypassIfAboveCustomFormatScore": "Пропустить, если значение больше пользовательского формата", + "ConnectionLost": "Соединение прервано", + "DeleteArtistFolderHelpText": "Удалить папку и её содержимое", + "DeleteSpecification": "Удалить уведомление", + "Loading": "Загрузка", + "MonitorNoAlbums": "Ничто", + "Negate": "Отрицать", + "Rejections": "Отказы", + "RemoveQueueItem": "Удалить - {sourceTitle}", + "TagsSettingsSummary": "Посмотрите все теги и способы их использования. Неиспользуемые теги можно удалить", + "UpdateSelected": "Обновление выбрано", + "GrabId": "Захватить ID", + "Absolute": "Абсолютный", + "RecycleBinUnableToWriteHealthCheck": "Не удается выполнить запись в настроенную папку корзины: {path}. Убедитесь, что этот путь существует и доступен для записи пользователем, запускающим {appName}", + "AddListExclusionHelpText": "Запретить добавление серий в {appName} по спискам", + "AddNewArtistRootFolderHelpText": "Подпапка \"{0}\" будет создана автоматически", + "AuthenticationRequiredHelpText": "Отредактируйте, для каких запросов требуется аутентификация. Не меняйте, пока не поймете все риски.", + "DeleteArtistFolderCountConfirmation": "Вы уверены, что хотите удалить {count} выбранных индексатора?", + "RenameFiles": "Файлы переименованы", + "Table": "Таблица", + "FormatAgeHour": "Часы", + "FormatAgeHours": "Часы", + "NoResultsFound": "Нет результатов", + "UiSettingsSummary": "Параметры календаря, даты и опции для слабовидящих", + "Yesterday": "Вчера", + "EditConnectionImplementation": "Добавить соединение - {implementationName}", + "ExpandAlbumByDefaultHelpText": "альбом", + "AddCondition": "Добавить условие", + "AddConditionError": "Не удалось добавить новое условие, попробуйте еще раз.", + "AddImportListExclusionArtistHelpText": "Запретить добавление серий в {appName} по спискам", + "AddNewArtistSearchForMissingAlbums": "Начать поиск пропавшего фильма", + "Album": "альбом", + "AlbumsLoadError": "Невозможно загрузить резервные копии", + "CountArtistsSelected": "{count} выбранных списков импорта", + "AddToDownloadQueue": "Добавить в очередь загрузки", + "AddedToDownloadQueue": "Добавлено в очередь на скачивание", + "AuthenticationRequired": "Требуется авторизация", + "AuthenticationRequiredWarning": "Чтобы предотвратить удаленный доступ без авторизации, {appName} теперь требует, чтобы авторизация была включена. При желании вы можете отключить авторизацию с локальных адресов.", + "Auto": "Авто", + "AutoTaggingNegateHelpText": "Если отмечено, то настроенный формат не будет применён при условии {0}.", + "CloneAutoTag": "Автоматическое клонирование метки", + "CloneCondition": "Условие клонирования", + "ArtistIndexFooterDownloading": "Скачивается", + "AuthBasic": "Базовый (всплывающее окно браузера)", + "AuthenticationMethod": "Способ авторизации", + "UpdateFiltered": "Фильтр обновлений", + "DeleteAutoTagHelpText": "Вы уверены, что хотите удалить автоматическую метку '{name}'?", + "GeneralSettingsSummary": "Порт, SSL, логин/пароль, прокси, аналитика и обновления", + "IndexerFlags": "Флаги индексатора", + "ExtraFileExtensionsHelpText": "Список экстра файлов для импорта разделенных двоеточием(.info будет заменено на .nfo-orig)", + "IndexerPriorityHelpText": "Приоритет индексатора от 1 (самый высокий) до 50 (самый низкий). По умолчанию: 25. Используется при захвате выпусков в качестве средства разрешения конфликтов для равных в остальном выпусков, {appName} по-прежнему будет использовать все включенные индексаторы для синхронизации и поиска RSS", + "AutomaticSearch": "Автоматический поиск", + "GrabReleaseUnknownArtistOrAlbumMessageText": "{appName} не смог определить для какого фильма был релиз. {appName} не сможет автоматически его импортировать. Хотите захватить '{0}'?", + "RemotePathMappingsInfo": "Сопоставление удаленных путей требуется крайне редко, если {appName} и клиент загрузки находятся в одной системе, то лучше согласовать пути. Более подробную информацию можно найти в [wiki]({wikiLink}).", + "ConnectionLostToBackend": "Radarr потерял связь с сервером и его необходимо перезагрузить, чтобы восстановить работоспособность.", + "BypassIfAboveCustomFormatScoreHelpText": "Включите обход, когда оценка релиза выше, чем заданная минимальная оценка пользовательского формата", + "BypassIfHighestQualityHelpText": "Игнорирование задержки, когда выпуск имеет максимальное качество в выбранном профиле качества с предпочитаемым протоколом", + "ErrorLoadingContent": "Произошла ошибка при загрузке этого элемента", + "FormatAgeMinute": "Минуты", + "FormatAgeMinutes": "Минуты", + "ProfilesSettingsArtistSummary": "Профили качества, языка, задержки и выпуска", + "AddImportListExclusionAlbumHelpText": "Запретить добавление серий в {appName} по спискам", + "False": "Неверно", + "Parse": "Анализ", + "ParseModalErrorParsing": "Ошибка при парсинге, попробуйте еще раз.", + "ParseModalHelpText": "Введите название релиза в поле ввода выше", + "ParseModalHelpTextDetails": "{appName} попытается определить название и показать подробную информацию о нем", + "ParseModalUnableToParse": "Невозможно распознать данное название, попробуйте еще раз.", + "TestParsing": "Тест сбора данных", + "True": "Правильно" } diff --git a/src/NzbDrone.Core/Localization/Core/sk.json b/src/NzbDrone.Core/Localization/Core/sk.json index bca9dc1e5..c5dab9f6f 100644 --- a/src/NzbDrone.Core/Localization/Core/sk.json +++ b/src/NzbDrone.Core/Localization/Core/sk.json @@ -286,5 +286,7 @@ "DeleteAutoTagHelpText": "Naozaj chcete zmazať tento profil oneskorenia?", "DisabledForLocalAddresses": "Zakázané pre miestne adresy", "AutomaticSearch": "Automatické vyhľadávanie", - "BlocklistReleases": "Blocklistnúť vydanie" + "BlocklistReleases": "Blocklistnúť vydanie", + "UnableToAddANewMetadataProfilePleaseTryAgain": "Nie je možné pridať novú podmienku, skúste to znova.", + "UnableToAddANewQualityProfilePleaseTryAgain": "Nie je možné pridať novú podmienku, skúste to znova." } diff --git a/src/NzbDrone.Core/Localization/Core/sv.json b/src/NzbDrone.Core/Localization/Core/sv.json index a4ef82a41..f34653b2e 100644 --- a/src/NzbDrone.Core/Localization/Core/sv.json +++ b/src/NzbDrone.Core/Localization/Core/sv.json @@ -902,5 +902,7 @@ "Negate": "Förneka", "NotificationStatusAllClientHealthCheckMessage": "Samtliga listor otillgängliga på grund av fel", "UseSsl": "Använd SSL", - "Links": "Länkar" + "Links": "Länkar", + "AddImportListExclusionAlbumHelpText": "Förhindra TV-serie att läggas till i {appName} från listor", + "ArtistIsMonitored": "Författare är obevakad" } diff --git a/src/NzbDrone.Core/Localization/Core/th.json b/src/NzbDrone.Core/Localization/Core/th.json index 7a05547a6..23cd4a1fc 100644 --- a/src/NzbDrone.Core/Localization/Core/th.json +++ b/src/NzbDrone.Core/Localization/Core/th.json @@ -729,5 +729,6 @@ "Lowercase": "ตัวพิมพ์เล็ก", "OverviewOptions": "ตัวเลือกภาพรวม", "PosterOptions": "ตัวเลือกโปสเตอร์", - "TagsSettingsSummary": "ดูแท็กทั้งหมดและวิธีการใช้งาน แท็กที่ไม่ได้ใช้สามารถลบออกได้" + "TagsSettingsSummary": "ดูแท็กทั้งหมดและวิธีการใช้งาน แท็กที่ไม่ได้ใช้สามารถลบออกได้", + "AddImportListExclusionAlbumHelpText": "ป้องกันไม่ให้เพิ่มภาพยนตร์ไปยัง {appName} ตามรายการ" } diff --git a/src/NzbDrone.Core/Localization/Core/tr.json b/src/NzbDrone.Core/Localization/Core/tr.json index 4188bfdba..d867290d5 100644 --- a/src/NzbDrone.Core/Localization/Core/tr.json +++ b/src/NzbDrone.Core/Localization/Core/tr.json @@ -36,7 +36,7 @@ "AgeWhenGrabbed": "Yıl (yakalandığında)", "AlbumIsDownloadingInterp": "Film indiriliyor - {0}% {1}", "AlreadyInYourLibrary": "Kütüphanenizde mevcut", - "AlternateTitles": "Alternatif Başlık", + "AlternateTitles": "Alternatif Başlıklar", "AlternateTitleslength1Title": "Başlık", "AlternateTitleslength1Titles": "Başlıklar", "Analytics": "Analitik", @@ -647,9 +647,9 @@ "RemoveSelectedItemBlocklistMessageText": "Kara listeden seçili öğeleri kaldırmak istediğinizden emin misiniz?", "RemovingTag": "Etiket kaldırılıyor", "ApplyTagsHelpTextHowToApplyArtists": "Seçilen filmlere etiketler nasıl uygulanır", - "ApplyTagsHelpTextHowToApplyImportLists": "Seçilen içe aktarma listelerine etiketler nasıl uygulanır?", - "ApplyTagsHelpTextHowToApplyIndexers": "Seçilen indeksleyicilere etiketler nasıl uygulanır?", - "ApplyTagsHelpTextHowToApplyDownloadClients": "Seçilen indirme istemcilerine etiketler nasıl uygulanır?", + "ApplyTagsHelpTextHowToApplyImportLists": "Seçilen içe aktarma listelerine etiketler nasıl uygulanır", + "ApplyTagsHelpTextHowToApplyIndexers": "Seçilen indeksleyicilere etiketler nasıl uygulanır", + "ApplyTagsHelpTextHowToApplyDownloadClients": "Seçilen indirme istemcilerine etiketler nasıl uygulanır", "NoResultsFound": "Sonuç bulunamadı", "SuggestTranslationChange": "Çeviri değişikliği önerin", "UpdateSelected": "Seçilmişleri güncelle", @@ -1002,5 +1002,16 @@ "UserAgentProvidedByTheAppThatCalledTheAPI": "API'yi çağıran uygulama tarafından sağlanan Kullanıcı Aracısı", "NoCutoffUnmetItems": "Karşılanmayan son öğe yok", "RemotePathMappingCheckLocalWrongOSPath": "Yerel indirme istemcisi {downloadClientName}, indirmeleri {path} yoluna yerleştiriyor ancak bu geçerli bir {osName} yolu değil. İndirme istemcisi ayarlarınızı gözden geçirin.", - "DeleteArtistFolderCountConfirmation": "Seçilen {count} içe aktarma listesini silmek istediğinizden emin misiniz?" + "DeleteArtistFolderCountConfirmation": "Seçilen {count} içe aktarma listesini silmek istediğinizden emin misiniz?", + "DeleteArtistFolderCountWithFilesConfirmation": "Seçili {count} filmi ve tüm içeriklerini silmek istediğinizden emin misiniz?", + "MassAlbumsCutoffUnmetWarning": "{totalRecords} Karşılanmayan Kesim filmlerinin tamamını aramak istediğinizden emin misiniz?", + "False": "Pasif", + "Parse": "Ayrıştır", + "ParseModalErrorParsing": "Ayrıştırmada hata oluştu. Lütfen tekrar deneyin.", + "ParseModalHelpText": "Yukarıdaki girişe bir yayın başlığı girin", + "ParseModalHelpTextDetails": "{appName}, başlığı ayrıştırmaya ve size konuyla ilgili ayrıntıları göstermeye çalışacak", + "ParseModalUnableToParse": "Sağlanan başlık ayrıştırılamadı, lütfen tekrar deneyin.", + "Repack": "Yeniden paketle", + "TestParsing": "Ayrıştırma Testi", + "True": "Aktif" } diff --git a/src/NzbDrone.Core/Localization/Core/uk.json b/src/NzbDrone.Core/Localization/Core/uk.json index 995b9b4d3..c0b2c13fe 100644 --- a/src/NzbDrone.Core/Localization/Core/uk.json +++ b/src/NzbDrone.Core/Localization/Core/uk.json @@ -871,5 +871,28 @@ "UnableToLoadReleaseProfiles": "Неможливо завантажити профілі затримки", "ChooseImportMethod": "Виберіть режим імпорту", "LaunchBrowserHelpText": " Відкрийте веб-браузер і перейдіть на домашню сторінку {appName} під час запуску програми.", - "LidarrSupportsAnyDownloadClientThatUsesTheNewznabStandardAsWellAsOtherDownloadClientsListedBelow": "{appName} підтримує багато популярних торрент-клієнтів і клієнтів для завантаження через Usenet." + "LidarrSupportsAnyDownloadClientThatUsesTheNewznabStandardAsWellAsOtherDownloadClientsListedBelow": "{appName} підтримує багато популярних торрент-клієнтів і клієнтів для завантаження через Usenet.", + "ClickToChangeIndexerFlags": "Натисніть, щоб змінити прапорці індексатора", + "ChownGroup": "chown Група", + "Clone": "Клонування", + "AutoRedownloadFailedFromInteractiveSearchHelpText": "Автоматично шукати та намагатися завантажити інший реліз, якщо обраний реліз не вдалось завантажити з інтерактивного пошуку.", + "BlocklistAndSearchHint": "Розпочати пошук заміни після додавання до чорного списку", + "BlocklistAndSearchMultipleHint": "Розпочати пошук замін після додавання до чорного списку", + "BlocklistMultipleOnlyHint": "Додати до чорного списку без пошуку замін", + "BlocklistOnly": "Тільки чорний список", + "BlocklistOnlyHint": "Додати до чорного списку без пошуку заміни", + "AutoTaggingLoadError": "Не вдалося завантажити автоматичне маркування", + "AutoTagging": "Автоматичне маркування", + "AutomaticAdd": "Автоматичне додавання", + "CloneCondition": "Клонування умови", + "ChangeCategoryMultipleHint": "Змінює завантаження на «Категорію після імпорту» з клієнта завантажувача", + "ChangeCategoryHint": "Змінює завантаження на «Категорію після імпорту» з клієнта завантажувача", + "CountDownloadClientsSelected": "Вибрано {count} клієнтів завантажувача", + "AddImportListExclusionAlbumHelpText": "Заборонити додавання фільму до {appName} за списками", + "ReleaseProfiles": "профіль релізу", + "BypassIfAboveCustomFormatScoreHelpText": "Увімкнути обхід, якщо реліз має оцінку вищу за встановлений мінімальний бал користувацького формату", + "MinimumCustomFormatScoreHelpText": "Мінімальна оцінка користувацького формату, необхідна для обходу затримки для обраного протоколу", + "ReleaseProfile": "профіль релізу", + "CountArtistsSelected": "Вибрано {count} списків імпорту", + "BypassIfAboveCustomFormatScore": "Пропустити, якщо перевищено оцінку користувацького формату" } diff --git a/src/NzbDrone.Core/Localization/Core/zh_CN.json b/src/NzbDrone.Core/Localization/Core/zh_CN.json index c1e018261..88a0ce058 100644 --- a/src/NzbDrone.Core/Localization/Core/zh_CN.json +++ b/src/NzbDrone.Core/Localization/Core/zh_CN.json @@ -1273,5 +1273,14 @@ "IndexerSettingsSeedTimeHelpText": "停止前应做种的时间,留空使用下载客户端的默认值", "InteractiveSearchModalHeader": "手动搜索", "DownloadClientDelugeSettingsDirectoryCompletedHelpText": "可选的下载位置,留空使用 Aria2 默认位置", - "IndexerSettingsSeedTime": "做种时间" + "IndexerSettingsSeedTime": "做种时间", + "Parse": "解析", + "Repack": "重新打包", + "False": "否", + "ParseModalErrorParsing": "解析错误,请重试。", + "ParseModalHelpText": "在上面的输入框中输入一个发行版标题", + "ParseModalHelpTextDetails": "{appName} 将尝试解析标题并向您显示有关详情", + "ParseModalUnableToParse": "无法解析提供的标题,请重试。", + "TestParsing": "测试解析", + "True": "是" } diff --git a/src/NzbDrone.Core/Localization/Core/zh_TW.json b/src/NzbDrone.Core/Localization/Core/zh_TW.json index 871828b7c..39f6e0314 100644 --- a/src/NzbDrone.Core/Localization/Core/zh_TW.json +++ b/src/NzbDrone.Core/Localization/Core/zh_TW.json @@ -216,5 +216,6 @@ "AutomaticSearch": "自動搜尋", "CatalogNumber": "類別編號", "CustomFormats": "自訂格式", - "ItsEasyToAddANewArtistJustStartTypingTheNameOfTheArtistYouWantToAdd": "加入新的電影很簡單,只需要輸入您想加入的電影名稱" + "ItsEasyToAddANewArtistJustStartTypingTheNameOfTheArtistYouWantToAdd": "加入新的電影很簡單,只需要輸入您想加入的電影名稱", + "EditMetadataProfile": "中繼資料配置" } From 19f824dbd8bce56ba91a96800ef1dd54a5fcc098 Mon Sep 17 00:00:00 2001 From: Bogdan Date: Sun, 14 Jul 2024 14:03:47 +0300 Subject: [PATCH 008/314] Bump version to 2.5.0 --- azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index b9bb0f4b9..4f3e61d15 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -9,7 +9,7 @@ variables: testsFolder: './_tests' yarnCacheFolder: $(Pipeline.Workspace)/.yarn nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages - majorVersion: '2.4.3' + majorVersion: '2.5.0' minorVersion: $[counter('minorVersion', 1076)] lidarrVersion: '$(majorVersion).$(minorVersion)' buildName: '$(Build.SourceBranchName).$(lidarrVersion)' From 30e681e84336a1656ab66586cd82a21c8b340465 Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Wed, 14 Dec 2022 17:45:43 -0800 Subject: [PATCH 009/314] Fixed: Refactor artist statistics (cherry picked from commit 6c53bf30d52b9d10aa0f65c05cb6561cfbf3f8bd) --- .../ArtistStatisticsFixture.cs | 22 +++++++ .../ArtistStats/ArtistStatisticsRepository.cs | 63 ++++++++++++------- 2 files changed, 61 insertions(+), 24 deletions(-) diff --git a/src/NzbDrone.Core.Test/ArtistStatsTests/ArtistStatisticsFixture.cs b/src/NzbDrone.Core.Test/ArtistStatsTests/ArtistStatisticsFixture.cs index 7757ddfc1..e0ad418ba 100644 --- a/src/NzbDrone.Core.Test/ArtistStatsTests/ArtistStatisticsFixture.cs +++ b/src/NzbDrone.Core.Test/ArtistStatsTests/ArtistStatisticsFixture.cs @@ -3,6 +3,7 @@ using System.Linq; using FizzWare.NBuilder; using FluentAssertions; using NUnit.Framework; +using NzbDrone.Common.Extensions; using NzbDrone.Core.ArtistStats; using NzbDrone.Core.MediaFiles; using NzbDrone.Core.Music; @@ -124,5 +125,26 @@ namespace NzbDrone.Core.Test.ArtistStatsTests stats.Should().HaveCount(1); stats.First().SizeOnDisk.Should().Be(_trackFile.Size); } + + [Test] + public void should_not_duplicate_size_for_multi_track_files() + { + GivenTrackWithFile(); + GivenTrack(); + GivenTrackFile(); + + var track2 = _track.JsonClone(); + + track2.Id = 0; + track2.TrackNumber += 1; + track2.ForeignTrackId = "2"; + + Db.Insert(track2); + + var stats = Subject.ArtistStatistics(); + + stats.Should().HaveCount(1); + stats.First().SizeOnDisk.Should().Be(_trackFile.Size); + } } } diff --git a/src/NzbDrone.Core/ArtistStats/ArtistStatisticsRepository.cs b/src/NzbDrone.Core/ArtistStats/ArtistStatisticsRepository.cs index a7e73bb9a..d74863a4f 100644 --- a/src/NzbDrone.Core/ArtistStats/ArtistStatisticsRepository.cs +++ b/src/NzbDrone.Core/ArtistStats/ArtistStatisticsRepository.cs @@ -16,7 +16,8 @@ namespace NzbDrone.Core.ArtistStats public class ArtistStatisticsRepository : IArtistStatisticsRepository { - private const string _selectTemplate = "SELECT /**select**/ FROM \"Tracks\" /**join**/ /**innerjoin**/ /**leftjoin**/ /**where**/ /**groupby**/ /**having**/ /**orderby**/"; + private const string _selectTracksTemplate = "SELECT /**select**/ FROM \"Tracks\" /**join**/ /**innerjoin**/ /**leftjoin**/ /**where**/ /**groupby**/ /**having**/ /**orderby**/"; + private const string _selectTrackFilesTemplate = "SELECT /**select**/ FROM \"TrackFiles\" /**join**/ /**innerjoin**/ /**leftjoin**/ /**where**/ /**groupby**/ /**having**/ /**orderby**/"; private readonly IMainDatabase _database; @@ -28,32 +29,33 @@ namespace NzbDrone.Core.ArtistStats public List ArtistStatistics() { var time = DateTime.UtcNow; - - if (_database.DatabaseType == DatabaseType.PostgreSQL) - { - return Query(Builder().WherePostgres(x => x.ReleaseDate < time)); - } - - return Query(Builder().Where(x => x.ReleaseDate < time)); + return MapResults(Query(TracksBuilder(time), _selectTracksTemplate), + Query(TrackFilesBuilder(), _selectTrackFilesTemplate)); } public List ArtistStatistics(int artistId) { var time = DateTime.UtcNow; - if (_database.DatabaseType == DatabaseType.PostgreSQL) - { - return Query(Builder().WherePostgres(x => x.ReleaseDate < time) - .WherePostgres(x => x.Id == artistId)); - } - - return Query(Builder().Where(x => x.ReleaseDate < time) - .Where(x => x.Id == artistId)); + return MapResults(Query(TracksBuilder(time).Where(x => x.Id == artistId), _selectTracksTemplate), + Query(TrackFilesBuilder().Where(x => x.Id == artistId), _selectTrackFilesTemplate)); } - private List Query(SqlBuilder builder) + private List MapResults(List tracksResult, List filesResult) { - var sql = builder.AddTemplate(_selectTemplate).LogQuery(); + tracksResult.ForEach(e => + { + var file = filesResult.SingleOrDefault(f => f.ArtistId == e.ArtistId & f.AlbumId == e.AlbumId); + + e.SizeOnDisk = file?.SizeOnDisk ?? 0; + }); + + return tracksResult; + } + + private List Query(SqlBuilder builder, string template) + { + var sql = builder.AddTemplate(template).LogQuery(); using (var conn = _database.OpenConnection()) { @@ -61,25 +63,38 @@ namespace NzbDrone.Core.ArtistStats } } - private SqlBuilder Builder() + private SqlBuilder TracksBuilder(DateTime currentDate) { + var parameters = new DynamicParameters(); + parameters.Add("currentDate", currentDate, null); + var trueIndicator = _database.DatabaseType == DatabaseType.PostgreSQL ? "true" : "1"; return new SqlBuilder(_database.DatabaseType) .Select($@"""Artists"".""Id"" AS ""ArtistId"", ""Albums"".""Id"" AS ""AlbumId"", - SUM(COALESCE(""TrackFiles"".""Size"", 0)) AS ""SizeOnDisk"", COUNT(""Tracks"".""Id"") AS ""TotalTrackCount"", - SUM(CASE WHEN ""Tracks"".""TrackFileId"" > 0 THEN 1 ELSE 0 END) AS ""AvailableTrackCount"", - SUM(CASE WHEN ""Albums"".""Monitored"" = {trueIndicator} OR ""Tracks"".""TrackFileId"" > 0 THEN 1 ELSE 0 END) AS ""TrackCount"", - SUM(CASE WHEN ""TrackFiles"".""Id"" IS NULL THEN 0 ELSE 1 END) AS ""TrackFileCount""") + SUM(CASE WHEN ""Albums"".""ReleaseDate"" <= @currentDate OR ""Tracks"".""TrackFileId"" > 0 THEN 1 ELSE 0 END) AS ""AvailableTrackCount"", + SUM(CASE WHEN (""Albums"".""Monitored"" = {trueIndicator} AND ""Albums"".""ReleaseDate"" <= @currentDate) OR ""Tracks"".""TrackFileId"" > 0 THEN 1 ELSE 0 END) AS ""TrackCount"", + SUM(CASE WHEN ""Tracks"".""TrackFileId"" > 0 THEN 1 ELSE 0 END) AS TrackFileCount", parameters) .Join((t, r) => t.AlbumReleaseId == r.Id) .Join((r, a) => r.AlbumId == a.Id) .Join((album, artist) => album.ArtistMetadataId == artist.ArtistMetadataId) - .LeftJoin((t, f) => t.TrackFileId == f.Id) .Where(x => x.Monitored == true) .GroupBy(x => x.Id) .GroupBy(x => x.Id); } + + private SqlBuilder TrackFilesBuilder() + { + return new SqlBuilder(_database.DatabaseType) + .Select(@"""Artists"".""Id"" AS ""ArtistId"", + ""AlbumId"", + SUM(COALESCE(""Size"", 0)) AS SizeOnDisk") + .Join((t, a) => t.AlbumId == a.Id) + .Join((album, artist) => album.ArtistMetadataId == artist.ArtistMetadataId) + .GroupBy(x => x.Id) + .GroupBy(x => x.AlbumId); + } } } From eb04673040e9a320c038f7c5be798b34cba7d1d4 Mon Sep 17 00:00:00 2001 From: Bogdan Date: Sun, 14 Jul 2024 13:25:24 +0300 Subject: [PATCH 010/314] New: Include with files in album group info --- .../src/Album/Details/AlbumDetailsMedium.js | 5 +-- frontend/src/Artist/Details/AlbumGroupInfo.js | 9 +++++ frontend/src/Artist/Details/AlbumRow.js | 2 +- .../src/Artist/Details/ArtistDetailsSeason.js | 37 +++++++++++++------ src/NzbDrone.Core/Localization/Core/en.json | 1 + 5 files changed, 37 insertions(+), 17 deletions(-) diff --git a/frontend/src/Album/Details/AlbumDetailsMedium.js b/frontend/src/Album/Details/AlbumDetailsMedium.js index 09c489b2d..9e80e2c7a 100644 --- a/frontend/src/Album/Details/AlbumDetailsMedium.js +++ b/frontend/src/Album/Details/AlbumDetailsMedium.js @@ -12,16 +12,13 @@ import TrackRowConnector from './TrackRowConnector'; import styles from './AlbumDetailsMedium.css'; function getMediumStatistics(tracks) { - let trackCount = 0; + const trackCount = tracks.length; let trackFileCount = 0; let totalTrackCount = 0; tracks.forEach((track) => { if (track.trackFileId) { - trackCount++; trackFileCount++; - } else { - trackCount++; } totalTrackCount++; diff --git a/frontend/src/Artist/Details/AlbumGroupInfo.js b/frontend/src/Artist/Details/AlbumGroupInfo.js index 0fb62d4a3..139cd7765 100644 --- a/frontend/src/Artist/Details/AlbumGroupInfo.js +++ b/frontend/src/Artist/Details/AlbumGroupInfo.js @@ -10,6 +10,7 @@ function AlbumGroupInfo(props) { const { totalAlbumCount, monitoredAlbumCount, + albumFileCount, trackFileCount, sizeOnDisk } = props; @@ -30,6 +31,13 @@ function AlbumGroupInfo(props) { data={monitoredAlbumCount} /> + + { - statistics.totalTrackCount + totalTrackCount } ); diff --git a/frontend/src/Artist/Details/ArtistDetailsSeason.js b/frontend/src/Artist/Details/ArtistDetailsSeason.js index 37c85aa66..004613e30 100644 --- a/frontend/src/Artist/Details/ArtistDetailsSeason.js +++ b/frontend/src/Artist/Details/ArtistDetailsSeason.js @@ -22,32 +22,43 @@ import styles from './ArtistDetailsSeason.css'; function getAlbumStatistics(albums) { let albumCount = 0; + let albumFileCount = 0; let trackFileCount = 0; let totalAlbumCount = 0; let monitoredAlbumCount = 0; let hasMonitoredAlbums = false; let sizeOnDisk = 0; - albums.forEach((album) => { - if (album.statistics) { - sizeOnDisk = sizeOnDisk + album.statistics.sizeOnDisk; - trackFileCount = trackFileCount + album.statistics.trackFileCount; + albums.forEach(({ monitored, releaseDate, statistics = {} }) => { + const { + trackFileCount: albumTrackFileCount = 0, + totalTrackCount: albumTotalTrackCount = 0, + sizeOnDisk: albumSizeOnDisk = 0 + } = statistics; - if (album.statistics.trackFileCount === album.statistics.totalTrackCount || (album.monitored && isBefore(album.airDateUtc))) { - albumCount++; - } + const hasFiles = albumTrackFileCount > 0 && albumTrackFileCount === albumTotalTrackCount; + + if (hasFiles || (monitored && isBefore(releaseDate))) { + albumCount++; } - if (album.monitored) { + if (hasFiles) { + albumFileCount++; + } + + if (monitored) { monitoredAlbumCount++; hasMonitoredAlbums = true; } totalAlbumCount++; + trackFileCount = trackFileCount + albumTrackFileCount; + sizeOnDisk = sizeOnDisk + albumSizeOnDisk; }); return { albumCount, + albumFileCount, totalAlbumCount, trackFileCount, monitoredAlbumCount, @@ -56,8 +67,8 @@ function getAlbumStatistics(albums) { }; } -function getAlbumCountKind(monitored, albumCount, monitoredAlbumCount) { - if (albumCount === monitoredAlbumCount && monitoredAlbumCount > 0) { +function getAlbumCountKind(monitored, albumCount, albumFileCount) { + if (albumCount === albumFileCount && albumFileCount > 0) { return kinds.SUCCESS; } @@ -192,6 +203,7 @@ class ArtistDetailsSeason extends Component { const { albumCount, + albumFileCount, totalAlbumCount, trackFileCount, monitoredAlbumCount, @@ -226,9 +238,9 @@ class ArtistDetailsSeason extends Component { anchor={ } title={translate('GroupInformation')} @@ -237,6 +249,7 @@ class ArtistDetailsSeason extends Component { diff --git a/src/NzbDrone.Core/Localization/Core/en.json b/src/NzbDrone.Core/Localization/Core/en.json index d279bfadd..642d99a2a 100644 --- a/src/NzbDrone.Core/Localization/Core/en.json +++ b/src/NzbDrone.Core/Localization/Core/en.json @@ -1294,6 +1294,7 @@ "WatchRootFoldersForFileChanges": "Watch Root Folders for file changes", "WeekColumnHeader": "Week Column Header", "WhatsNew": "What's New?", + "WithFiles": "With Files", "WouldYouLikeToRestoreBackup": "Would you like to restore the backup '{name}'?", "WriteAudioTagsHelpTextWarning": "Selecting 'All files' will alter existing files when they are imported.", "WriteMetadataTags": "Write Metadata Tags", From a52c6f6f41bff9b76a34085aca01a1a709bdf62d Mon Sep 17 00:00:00 2001 From: Bogdan Date: Sun, 14 Jul 2024 19:28:28 +0300 Subject: [PATCH 011/314] Fixed: Display last/next monitored albums for artists --- frontend/src/Album/Album.ts | 1 + frontend/src/Album/AlbumTitleLink.js | 5 +++-- frontend/src/Artist/Artist.ts | 1 - .../Store/Selectors/createArtistAlbumsSelector.ts | 2 +- src/Lidarr.Api.V1/Artist/ArtistResource.cs | 6 ++++-- src/Lidarr.Api.V1/openapi.json | 4 ---- .../AlbumRepositoryTests/AlbumRepositoryFixture.cs | 4 ++-- .../Music/Repositories/AlbumRepository.cs | 12 ++++++------ 8 files changed, 17 insertions(+), 18 deletions(-) diff --git a/frontend/src/Album/Album.ts b/frontend/src/Album/Album.ts index c9f10a87c..7a645efee 100644 --- a/frontend/src/Album/Album.ts +++ b/frontend/src/Album/Album.ts @@ -10,6 +10,7 @@ export interface Statistics { } interface Album extends ModelBase { + artistId: number; artist: Artist; foreignAlbumId: string; title: string; diff --git a/frontend/src/Album/AlbumTitleLink.js b/frontend/src/Album/AlbumTitleLink.js index 8b4dfe212..e55fadfc0 100644 --- a/frontend/src/Album/AlbumTitleLink.js +++ b/frontend/src/Album/AlbumTitleLink.js @@ -4,10 +4,11 @@ import Link from 'Components/Link/Link'; function AlbumTitleLink({ foreignAlbumId, title, disambiguation }) { const link = `/album/${foreignAlbumId}`; + const albumTitle = `${title}${disambiguation ? ` (${disambiguation})` : ''}`; return ( - - {title}{disambiguation ? ` (${disambiguation})` : ''} + + {albumTitle} ); } diff --git a/frontend/src/Artist/Artist.ts b/frontend/src/Artist/Artist.ts index d89e32f34..813dbea08 100644 --- a/frontend/src/Artist/Artist.ts +++ b/frontend/src/Artist/Artist.ts @@ -23,7 +23,6 @@ export interface Ratings { interface Artist extends ModelBase { added: string; - artistMetadataId: string; foreignArtistId: string; cleanName: string; ended: boolean; diff --git a/frontend/src/Store/Selectors/createArtistAlbumsSelector.ts b/frontend/src/Store/Selectors/createArtistAlbumsSelector.ts index 2ae54a10c..baffd87ec 100644 --- a/frontend/src/Store/Selectors/createArtistAlbumsSelector.ts +++ b/frontend/src/Store/Selectors/createArtistAlbumsSelector.ts @@ -11,7 +11,7 @@ function createArtistAlbumsSelector(artistId: number) { const { isFetching, isPopulated, error, items } = albums; const filteredAlbums = items.filter( - (album) => album.artist.artistMetadataId === artist.artistMetadataId + (album) => album.artistId === artist.id ); return { diff --git a/src/Lidarr.Api.V1/Artist/ArtistResource.cs b/src/Lidarr.Api.V1/Artist/ArtistResource.cs index 288208fcf..a71791ba0 100644 --- a/src/Lidarr.Api.V1/Artist/ArtistResource.cs +++ b/src/Lidarr.Api.V1/Artist/ArtistResource.cs @@ -1,9 +1,9 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text.Json.Serialization; using Lidarr.Api.V1.Albums; using Lidarr.Http.REST; -using Newtonsoft.Json; using NzbDrone.Common.Extensions; using NzbDrone.Core.MediaCover; using NzbDrone.Core.Music; @@ -32,7 +32,10 @@ namespace Lidarr.Api.V1.Artist public string Disambiguation { get; set; } public List Links { get; set; } + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] public AlbumResource NextAlbum { get; set; } + + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] public AlbumResource LastAlbum { get; set; } public List Images { get; set; } @@ -74,7 +77,6 @@ namespace Lidarr.Api.V1.Artist return new ArtistResource { Id = model.Id, - ArtistMetadataId = model.ArtistMetadataId, ArtistName = model.Name, diff --git a/src/Lidarr.Api.V1/openapi.json b/src/Lidarr.Api.V1/openapi.json index d3d5e381c..b48424105 100644 --- a/src/Lidarr.Api.V1/openapi.json +++ b/src/Lidarr.Api.V1/openapi.json @@ -8670,10 +8670,6 @@ "type": "integer", "format": "int32" }, - "artistMetadataId": { - "type": "integer", - "format": "int32" - }, "status": { "$ref": "#/components/schemas/ArtistStatusType" }, diff --git a/src/NzbDrone.Core.Test/MusicTests/AlbumRepositoryTests/AlbumRepositoryFixture.cs b/src/NzbDrone.Core.Test/MusicTests/AlbumRepositoryTests/AlbumRepositoryFixture.cs index 5d57b2755..4d762c5a0 100644 --- a/src/NzbDrone.Core.Test/MusicTests/AlbumRepositoryTests/AlbumRepositoryFixture.cs +++ b/src/NzbDrone.Core.Test/MusicTests/AlbumRepositoryTests/AlbumRepositoryFixture.cs @@ -193,7 +193,7 @@ namespace NzbDrone.Core.Test.MusicTests.AlbumRepositoryTests GivenMultipleAlbums(); var result = _albumRepo.GetNextAlbums(new[] { _artist.ArtistMetadataId }); - result.Should().BeEquivalentTo(_albums.Take(1), AlbumComparerOptions); + result.Should().BeEquivalentTo(_albums.Skip(1).Take(1), AlbumComparerOptions); } [Test] @@ -202,7 +202,7 @@ namespace NzbDrone.Core.Test.MusicTests.AlbumRepositoryTests GivenMultipleAlbums(); var result = _albumRepo.GetLastAlbums(new[] { _artist.ArtistMetadataId }); - result.Should().BeEquivalentTo(_albums.Skip(2).Take(1), AlbumComparerOptions); + result.Should().BeEquivalentTo(_albums.Skip(3).Take(1), AlbumComparerOptions); } private EquivalencyAssertionOptions AlbumComparerOptions(EquivalencyAssertionOptions opts) => opts.ComparingByMembers() diff --git a/src/NzbDrone.Core/Music/Repositories/AlbumRepository.cs b/src/NzbDrone.Core/Music/Repositories/AlbumRepository.cs index 9d8602f2c..3c69cf42b 100644 --- a/src/NzbDrone.Core/Music/Repositories/AlbumRepository.cs +++ b/src/NzbDrone.Core/Music/Repositories/AlbumRepository.cs @@ -46,13 +46,13 @@ namespace NzbDrone.Core.Music var now = DateTime.UtcNow; var inner = Builder() - .Select("MIN(\"Albums\".\"Id\") as id, MAX(\"Albums\".\"ReleaseDate\") as date") - .Where(x => artistMetadataIds.Contains(x.ArtistMetadataId) && x.ReleaseDate < now) + .Select("\"Albums\".\"ArtistMetadataId\" AS artist_metadata_id, MAX(\"Albums\".\"ReleaseDate\") AS date") + .Where(x => artistMetadataIds.Contains(x.ArtistMetadataId) && x.Monitored == true && x.ReleaseDate < now) .GroupBy(x => x.ArtistMetadataId) .AddSelectTemplate(typeof(Album)); var outer = Builder() - .Join($"({inner.RawSql}) ids on ids.id = \"Albums\".\"Id\" and ids.date = \"Albums\".\"ReleaseDate\"") + .Join($"({inner.RawSql}) ids ON ids.artist_metadata_id = \"Albums\".\"ArtistMetadataId\" AND ids.date = \"Albums\".\"ReleaseDate\"") .AddParameters(inner.Parameters); return Query(outer); @@ -63,13 +63,13 @@ namespace NzbDrone.Core.Music var now = DateTime.UtcNow; var inner = Builder() - .Select("MIN(\"Albums\".\"Id\") as id, MIN(\"Albums\".\"ReleaseDate\") as date") - .Where(x => artistMetadataIds.Contains(x.ArtistMetadataId) && x.ReleaseDate > now) + .Select("\"Albums\".\"ArtistMetadataId\" AS artist_metadata_id, MIN(\"Albums\".\"ReleaseDate\") AS date") + .Where(x => artistMetadataIds.Contains(x.ArtistMetadataId) && x.Monitored == true && x.ReleaseDate > now) .GroupBy(x => x.ArtistMetadataId) .AddSelectTemplate(typeof(Album)); var outer = Builder() - .Join($"({inner.RawSql}) ids on ids.id = \"Albums\".\"Id\" and ids.date = \"Albums\".\"ReleaseDate\"") + .Join($"({inner.RawSql}) ids ON ids.artist_metadata_id = \"Albums\".\"ArtistMetadataId\" AND ids.date = \"Albums\".\"ReleaseDate\"") .AddParameters(inner.Parameters); return Query(outer); From 56679861a01232a5881a81d0a7593c452452b04f Mon Sep 17 00:00:00 2001 From: Bogdan Date: Sun, 14 Jul 2024 21:48:23 +0300 Subject: [PATCH 012/314] Mapping `ArtistMetadataId` is needed for last/next monitored albums --- src/Lidarr.Api.V1/Artist/ArtistController.cs | 6 ++++-- src/Lidarr.Api.V1/Artist/ArtistResource.cs | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Lidarr.Api.V1/Artist/ArtistController.cs b/src/Lidarr.Api.V1/Artist/ArtistController.cs index 24d7855e1..e5829ff96 100644 --- a/src/Lidarr.Api.V1/Artist/ArtistController.cs +++ b/src/Lidarr.Api.V1/Artist/ArtistController.cs @@ -204,8 +204,10 @@ namespace Lidarr.Api.V1.Artist private void LinkNextPreviousAlbums(params ArtistResource[] artists) { - var nextAlbums = _albumService.GetNextAlbumsByArtistMetadataId(artists.Select(x => x.ArtistMetadataId)); - var lastAlbums = _albumService.GetLastAlbumsByArtistMetadataId(artists.Select(x => x.ArtistMetadataId)); + var artistMetadataIds = artists.Select(x => x.ArtistMetadataId).Distinct().ToList(); + + var nextAlbums = _albumService.GetNextAlbumsByArtistMetadataId(artistMetadataIds); + var lastAlbums = _albumService.GetLastAlbumsByArtistMetadataId(artistMetadataIds); foreach (var artistResource in artists) { diff --git a/src/Lidarr.Api.V1/Artist/ArtistResource.cs b/src/Lidarr.Api.V1/Artist/ArtistResource.cs index a71791ba0..73fbe4240 100644 --- a/src/Lidarr.Api.V1/Artist/ArtistResource.cs +++ b/src/Lidarr.Api.V1/Artist/ArtistResource.cs @@ -77,6 +77,7 @@ namespace Lidarr.Api.V1.Artist return new ArtistResource { Id = model.Id, + ArtistMetadataId = model.ArtistMetadataId, ArtistName = model.Name, From 633feaa02342b646277557610e78133c7eb1aeaf Mon Sep 17 00:00:00 2001 From: Bogdan Date: Sun, 14 Jul 2024 23:19:20 +0300 Subject: [PATCH 013/314] Fixed disable options for SelectInput --- frontend/src/Components/Form/MonitorAlbumsSelectInput.js | 2 +- frontend/src/Components/Form/SelectInput.js | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/frontend/src/Components/Form/MonitorAlbumsSelectInput.js b/frontend/src/Components/Form/MonitorAlbumsSelectInput.js index f3cefd29e..d48284c38 100644 --- a/frontend/src/Components/Form/MonitorAlbumsSelectInput.js +++ b/frontend/src/Components/Form/MonitorAlbumsSelectInput.js @@ -25,7 +25,7 @@ function MonitorAlbumsSelectInput(props) { if (includeMixed) { values.unshift({ key: 'mixed', - value: '(Mixed)', + value: `(${translate('Mixed')})`, isDisabled: true }); } diff --git a/frontend/src/Components/Form/SelectInput.js b/frontend/src/Components/Form/SelectInput.js index 553501afc..d43560134 100644 --- a/frontend/src/Components/Form/SelectInput.js +++ b/frontend/src/Components/Form/SelectInput.js @@ -52,6 +52,7 @@ class SelectInput extends Component { const { key, value: optionValue, + isDisabled: optionIsDisabled = false, ...otherOptionProps } = option; @@ -59,6 +60,7 @@ class SelectInput extends Component {