From ddabe66262f8f166cb88025c98f79c0a6058735e Mon Sep 17 00:00:00 2001 From: Qstick Date: Sun, 10 Sep 2023 01:06:46 -0500 Subject: [PATCH 001/136] Use variable for App name in translations --- frontend/src/Utilities/String/translate.ts | 2 +- src/NzbDrone.Core/Localization/Core/cs.json | 22 ++-- src/NzbDrone.Core/Localization/Core/de.json | 2 +- src/NzbDrone.Core/Localization/Core/en.json | 118 ++++++++--------- src/NzbDrone.Core/Localization/Core/es.json | 8 +- src/NzbDrone.Core/Localization/Core/hu.json | 22 ++-- src/NzbDrone.Core/Localization/Core/id.json | 2 +- src/NzbDrone.Core/Localization/Core/nl.json | 2 +- .../Localization/Core/pt_BR.json | 114 ++++++++-------- src/NzbDrone.Core/Localization/Core/ro.json | 4 +- src/NzbDrone.Core/Localization/Core/ru.json | 6 +- .../Localization/Core/zh_CN.json | 124 +++++++++--------- 12 files changed, 213 insertions(+), 213 deletions(-) diff --git a/frontend/src/Utilities/String/translate.ts b/frontend/src/Utilities/String/translate.ts index 8b4f4067a..7d59a22d1 100644 --- a/frontend/src/Utilities/String/translate.ts +++ b/frontend/src/Utilities/String/translate.ts @@ -25,7 +25,7 @@ export async function fetchTranslations(): Promise { export default function translate( key: string, - tokens?: Record + tokens: Record = { appName: 'Sonarr' } ) { const translation = translations[key] || key; diff --git a/src/NzbDrone.Core/Localization/Core/cs.json b/src/NzbDrone.Core/Localization/Core/cs.json index 746bdf439..daac5ecc5 100644 --- a/src/NzbDrone.Core/Localization/Core/cs.json +++ b/src/NzbDrone.Core/Localization/Core/cs.json @@ -5,13 +5,13 @@ "AgeWhenGrabbed": "Stáří (kdy bylo získáno)", "Always": "Vždy", "AnalyseVideoFiles": "Analyzovat video soubory", - "AnalyseVideoFilesHelpText": "Extrahujte ze souborů informace o videu, jako je rozlišení, doba běhu a informace o kodeku. To vyžaduje, aby Sonarr četl části souboru, což může způsobit vysokou aktivitu disku nebo sítě během skenování.", + "AnalyseVideoFilesHelpText": "Extrahujte ze souborů informace o videu, jako je rozlišení, doba běhu a informace o kodeku. To vyžaduje, aby {appName} četl části souboru, což může způsobit vysokou aktivitu disku nebo sítě během skenování.", "ApplicationURL": "URL aplikace", "ApplicationUrlHelpText": "Externí adresa URL této aplikace včetně http(s)://, portu a základní adresy URL", - "AuthenticationMethodHelpText": "Vyžadovat uživatelské jméno a heslo pro přístup k Sonarru", + "AuthenticationMethodHelpText": "Vyžadovat uživatelské jméno a heslo pro přístup k {appName}u", "AuthenticationRequired": "Vyžadované ověření", "AuthenticationRequiredHelpText": "Změnit, pro které požadavky je vyžadováno ověření. Pokud nerozumíte rizikům, neměňte je.", - "AuthenticationRequiredWarning": "Aby se zabránilo vzdálenému přístupu bez ověření, vyžaduje nyní Sonarr povolení ověření. Ověřování z místních adres můžete volitelně zakázat.", + "AuthenticationRequiredWarning": "Aby se zabránilo vzdálenému přístupu bez ověření, vyžaduje nyní {appName} povolení ověření. Ověřování z místních adres můžete volitelně zakázat.", "AutoRedownloadFailedHelpText": "Automatické vyhledání a pokus o stažení jiného vydání", "AutoTaggingLoadError": "Nelze načíst automatické označování", "AutomaticAdd": "Přidat automaticky", @@ -19,7 +19,7 @@ "BackupRetentionHelpText": "Automatické zálohy starší než doba uchovávání budou automaticky vyčištěny", "BackupsLoadError": "Nelze načíst zálohy", "BlocklistReleaseHelpText": "Znovu spustí vyhledávání této epizody a zabrání tomu, aby bylo toto vydání získáno znovu", - "BranchUpdate": "Větev, která se použije k aktualizaci Sonarru", + "BranchUpdate": "Větev, která se použije k aktualizaci {appName}u", "BranchUpdateMechanism": "Větev používaná externím aktualizačním mechanismem", "BrowserReloadRequired": "Vyžaduje se opětovné načtení prohlížeče", "BuiltIn": "Vestavěný", @@ -73,11 +73,11 @@ "Clone": "Klonovat", "CloneIndexer": "Klonovat indexátor", "ColonReplacement": "Nahrazení dvojtečky", - "ColonReplacementFormatHelpText": "Změna způsobu, jakým Sonarr zpracovává náhradu dvojtečky", + "ColonReplacementFormatHelpText": "Změna způsobu, jakým {appName} zpracovává náhradu dvojtečky", "CollectionsLoadError": "Nelze načíst sbírky", "CompletedDownloadHandling": "Zpracování stahování bylo dokončeno", "Condition": "Stav", - "UpdateMechanismHelpText": "Použijte vestavěný nástroj Sonarru pro aktualizaci nebo skript", + "UpdateMechanismHelpText": "Použijte vestavěný nástroj {appName}u pro aktualizaci nebo skript", "AddCondition": "Přidat podmínku", "AutoTagging": "Automatické označování", "AddAutoTag": "Přidat automatické tagy", @@ -130,7 +130,7 @@ "BackupNow": "Ihned zálohovat", "AppDataDirectory": "Adresář AppData", "ApplyTagsHelpTextHowToApplySeries": "Jak použít značky na vybrané seriály", - "BackupFolderHelpText": "Relativní cesty se budou nacházet v adresáři AppData systému Sonarr", + "BackupFolderHelpText": "Relativní cesty se budou nacházet v adresáři AppData systému {appName}", "BlocklistReleases": "Blocklist pro vydání", "Agenda": "Agenda", "AnEpisodeIsDownloading": "Epizoda se stahuje", @@ -171,7 +171,7 @@ "CalendarOptions": "Možnosti kalendáře", "AllFiles": "Všechny soubory", "Analytics": "Analýzy", - "AnalyticsEnabledHelpText": "Odesílání anonymních informací o používání a chybách na servery společnosti Sonarr. To zahrnuje informace o vašem prohlížeči, o tom, které stránky webového rozhraní Sonarr používáte, hlášení chyb a také informace o verzi operačního systému a běhového prostředí. Tyto informace použijeme k určení priorit funkcí a oprav chyb.", + "AnalyticsEnabledHelpText": "Odesílání anonymních informací o používání a chybách na servery společnosti {appName}. To zahrnuje informace o vašem prohlížeči, o tom, které stránky webového rozhraní {appName} používáte, hlášení chyb a také informace o verzi operačního systému a běhového prostředí. Tyto informace použijeme k určení priorit funkcí a oprav chyb.", "Anime": "Anime", "AnimeEpisodeFormat": "Formát epizod pro Anime", "AnimeTypeDescription": "Epizody vydané s použitím absolutního čísla epizody", @@ -214,7 +214,7 @@ "Cutoff": "Odříznout", "DailyEpisodeFormat": "Formát denní epizody", "DailyTypeDescription": "Epizody vydávané denně nebo méně často, které používají rok-měsíc-den (2023-08-04)", - "CopyUsingHardlinksHelpTextWarning": "Příležitostně mohou zámky souborů bránit přejmenování souborů, které jsou odesílány. Můžete dočasně zakázat odesílání a použít funkci přejmenování Sonarr jako řešení.", + "CopyUsingHardlinksHelpTextWarning": "Příležitostně mohou zámky souborů bránit přejmenování souborů, které jsou odesílány. Můžete dočasně zakázat odesílání a použít funkci přejmenování {appName} jako řešení.", "CreateEmptySeriesFoldersHelpText": "Vytvoření chybějících složek pro seriály během skenování disku", "CreateEmptySeriesFolders": "Vytvoření prázdných složek pro seriály", "CreateGroup": "Vytvořit skupinu", @@ -223,8 +223,8 @@ "CustomFormatUnknownConditionOption": "Neznámá možnost '{key}' pro podmínku '{implementation}'", "CustomFormatsLoadError": "Nelze načíst vlastní formáty", "CustomFormatsSettingsSummary": "Vlastní formáty a nastavení", - "CopyUsingHardlinksHelpText": "Pevné odkazy umožňují aplikaci Sonarr importovat odesílané torrenty do složky seriálu, aniž by zabíraly další místo na disku nebo kopírovaly celý obsah souboru. Hardlinky budou fungovat pouze v případě, že zdrojový a cílový soubor jsou na stejném svazku", - "CustomFormatHelpText": "Sonarr hodnotí každé vydání pomocí součtu bodů za odpovídající vlastní formáty. Pokud by nové vydání zlepšilo skóre při stejné nebo lepší kvalitě, Sonarr jej získá.", + "CopyUsingHardlinksHelpText": "Pevné odkazy umožňují aplikaci {appName} importovat odesílané torrenty do složky seriálu, aniž by zabíraly další místo na disku nebo kopírovaly celý obsah souboru. Hardlinky budou fungovat pouze v případě, že zdrojový a cílový soubor jsou na stejném svazku", + "CustomFormatHelpText": "{appName} hodnotí každé vydání pomocí součtu bodů za odpovídající vlastní formáty. Pokud by nové vydání zlepšilo skóre při stejné nebo lepší kvalitě, {appName} jej získá.", "Daily": "Denní", "ContinuingOnly": "Pouze pokračující", "CurrentlyInstalled": "Aktuálně nainstalováno", diff --git a/src/NzbDrone.Core/Localization/Core/de.json b/src/NzbDrone.Core/Localization/Core/de.json index 93591ee5e..f561c98fe 100644 --- a/src/NzbDrone.Core/Localization/Core/de.json +++ b/src/NzbDrone.Core/Localization/Core/de.json @@ -10,7 +10,7 @@ "DownloadClientCheckNoneAvailableHealthCheckMessage": "Es ist kein Download-Client verfügbar", "DownloadClientCheckUnableToCommunicateWithHealthCheckMessage": "Kommunikation mit {0} nicht möglich.", "DownloadClientRootFolderHealthCheckMessage": "Der Download-Client {0} legt Downloads im Stammordner {1} ab. Sie sollten nicht in einen Stammordner herunterladen.", - "DownloadClientSortingHealthCheckMessage": "Im Download-Client {0} ist die Sortierung {1} für die Kategorie von Sonarr aktiviert. Sie sollten die Sortierung in Ihrem Download-Client deaktivieren, um Importprobleme zu vermeiden.", + "DownloadClientSortingHealthCheckMessage": "Im Download-Client {0} ist die Sortierung {1} für die Kategorie von {appName} aktiviert. Sie sollten die Sortierung in Ihrem Download-Client deaktivieren, um Importprobleme zu vermeiden.", "DownloadClientStatusSingleClientHealthCheckMessage": "Download-Clients sind aufgrund von Fehlern nicht verfügbar: {0}", "DownloadClientStatusAllClientHealthCheckMessage": "Alle Download-Clients sind aufgrund von Fehlern nicht verfügbar", "EditSelectedDownloadClients": "Ausgewählte Download Clienten bearbeiten", diff --git a/src/NzbDrone.Core/Localization/Core/en.json b/src/NzbDrone.Core/Localization/Core/en.json index 761324961..8f4171357 100644 --- a/src/NzbDrone.Core/Localization/Core/en.json +++ b/src/NzbDrone.Core/Localization/Core/en.json @@ -33,7 +33,7 @@ "AddListError": "Unable to add a new list, please try again.", "AddListExclusion": "Add List Exclusion", "AddListExclusionError": "Unable to add a new list exclusion, please try again.", - "AddListExclusionHelpText": "Prevent series from being added to Sonarr by lists", + "AddListExclusionHelpText": "Prevent series from being added to {appName} by lists", "AddNew": "Add New", "AddNewRestriction": "Add new restriction", "AddNewSeries": "Add New Series", @@ -76,9 +76,9 @@ "Always": "Always", "AnEpisodeIsDownloading": "An Episode is downloading", "AnalyseVideoFiles": "Analyse video files", - "AnalyseVideoFilesHelpText": "Extract video information such as resolution, runtime and codec information from files. This requires Sonarr to read parts of the file which may cause high disk or network activity during scans.", + "AnalyseVideoFilesHelpText": "Extract video information such as resolution, runtime and codec information from files. This requires {appName} to read parts of the file which may cause high disk or network activity during scans.", "Analytics": "Analytics", - "AnalyticsEnabledHelpText": "Send anonymous usage and error information to Sonarr's servers. This includes information on your browser, which Sonarr WebUI pages you use, error reporting as well as OS and runtime version. We will use this information to prioritize features and bug fixes.", + "AnalyticsEnabledHelpText": "Send anonymous usage and error information to {appName}'s servers. This includes information on your browser, which {appName} WebUI pages you use, error reporting as well as OS and runtime version. We will use this information to prioritize features and bug fixes.", "Anime": "Anime", "AnimeEpisodeFormat": "Anime Episode Format", "AnimeTypeDescription": "Episodes released using an absolute episode number", @@ -127,7 +127,7 @@ "AutomaticSearch": "Automatic Search", "AutomaticUpdatesDisabledDocker": "Automatic updates are not directly supported when using the Docker update mechanism. You will need to update the container image outside of {appName} or use a script", "Backup": "Backup", - "BackupFolderHelpText": "Relative paths will be under Sonarr's AppData directory", + "BackupFolderHelpText": "Relative paths will be under {appName}'s AppData directory", "BackupIntervalHelpText": "Interval between automatic backups", "BackupNow": "Backup Now", "BackupRetentionHelpText": "Automatic backups older than the retention period will be cleaned up automatically", @@ -142,7 +142,7 @@ "BlocklistReleaseHelpText": "Starts a search for this episode again and prevents this release from being grabbed again", "BlocklistReleases": "Blocklist Releases", "Branch": "Branch", - "BranchUpdate": "Branch to use to update Sonarr", + "BranchUpdate": "Branch to use to update {appName}", "BranchUpdateMechanism": "Branch used by external update mechanism", "BrowserReloadRequired": "Browser Reload Required", "BuiltIn": "Built-In", @@ -202,7 +202,7 @@ "CollapseMultipleEpisodesHelpText": "Collapse multiple episodes airing on the same day", "CollectionsLoadError": "Unable to load collections", "ColonReplacement": "Colon Replacement", - "ColonReplacementFormatHelpText": "Change how Sonarr handles colon replacement", + "ColonReplacementFormatHelpText": "Change how {appName} handles colon replacement", "CompletedDownloadHandling": "Completed Download Handling", "Component": "Component", "Condition": "Condition", @@ -220,8 +220,8 @@ "ContinuingOnly": "Continuing Only", "ContinuingSeriesDescription": "More episodes/another season is expected", "CopyToClipboard": "Copy to Clipboard", - "CopyUsingHardlinksHelpText": "Hardlinks allow Sonarr to import seeding torrents to the series folder without taking extra disk space or copying the entire contents of the file. Hardlinks will only work if the source and destination are on the same volume", - "CopyUsingHardlinksHelpTextWarning": "Occasionally, file locks may prevent renaming files that are being seeded. You may temporarily disable seeding and use Sonarr's rename function as a work around.", + "CopyUsingHardlinksHelpText": "Hardlinks allow {appName} to import seeding torrents to the series folder without taking extra disk space or copying the entire contents of the file. Hardlinks will only work if the source and destination are on the same volume", + "CopyUsingHardlinksHelpTextWarning": "Occasionally, file locks may prevent renaming files that are being seeded. You may temporarily disable seeding and use {appName}'s rename function as a work around.", "CouldNotFindResults": "Couldn't find any results for '{term}'", "CountDownloadClientsSelected": "{count} download client(s) selected", "CountImportListsSelected": "{count} import list(s) selected", @@ -237,7 +237,7 @@ "Custom": "Custom", "CustomFilters": "Custom Filters", "CustomFormat": "Custom Format", - "CustomFormatHelpText": "Sonarr scores each release using the sum of scores for matching custom formats. If a new release would improve the score, at the same or better quality, then Sonarr will grab it.", + "CustomFormatHelpText": "{appName} scores each release using the sum of scores for matching custom formats. If a new release would improve the score, at the same or better quality, then {appName} will grab it.", "CustomFormatJson": "Custom Format JSON", "CustomFormatScore": "Custom Format Score", "CustomFormatUnknownCondition": "Unknown Custom Format condition '{implementation}'", @@ -333,7 +333,7 @@ "DeleteTagMessageText": "Are you sure you want to delete the tag '{label}'?", "Deleted": "Deleted", "DeletedReasonManual": "File was deleted by via UI", - "DeletedReasonMissingFromDisk": "Sonarr was unable to find the file on disk so the file was unlinked from the episode in the database", + "DeletedReasonMissingFromDisk": "{appName} was unable to find the file on disk so the file was unlinked from the episode in the database", "DeletedReasonUpgrade": "File was deleted to import an upgrade", "DeletedSeriesDescription": "Series was deleted from TheTVDB", "DestinationPath": "Destination Path", @@ -360,7 +360,7 @@ "DownloadClientOptionsLoadError": "Unable to load download client options", "DownloadClientRootFolderHealthCheckMessage": "Download client {0} places downloads in the root folder {1}. You should not download to a root folder.", "DownloadClientSettings": "Download Client Settings", - "DownloadClientSortingHealthCheckMessage": "Download client {0} has {1} sorting enabled for Sonarr's category. You should disable sorting in your download client to avoid import issues.", + "DownloadClientSortingHealthCheckMessage": "Download client {0} has {1} sorting enabled for {appName}'s category. You should disable sorting in your download client to avoid import issues.", "DownloadClientStatusAllClientHealthCheckMessage": "All download clients are unavailable due to failures", "DownloadClientStatusSingleClientHealthCheckMessage": "Download clients unavailable due to failures: {0}", "DownloadClientTagHelpText": "Only use this download client for series with at least one matching tag. Leave blank to use with all series.", @@ -405,9 +405,9 @@ "EditSeriesModalHeader": "Edit - {title}", "Enable": "Enable", "EnableAutomaticAdd": "Enable Automatic Add", - "EnableAutomaticAddHelpText": "Add series from this list to Sonarr when syncs are performed via the UI or by Sonarr", + "EnableAutomaticAddHelpText": "Add series from this list to {appName} when syncs are performed via the UI or by {appName}", "EnableAutomaticSearch": "Enable Automatic Search", - "EnableAutomaticSearchHelpText": "Will be used when automatic searches are performed via the UI or by Sonarr", + "EnableAutomaticSearchHelpText": "Will be used when automatic searches are performed via the UI or by {appName}", "EnableAutomaticSearchHelpTextWarning": "Will be used when interactive search is used", "EnableColorImpairedMode": "Enable Color-Impaired Mode", "EnableColorImpairedModeHelpText": "Altered style to allow color-impaired users to better distinguish color coded information", @@ -416,12 +416,12 @@ "EnableInteractiveSearch": "Enable Interactive Search", "EnableInteractiveSearchHelpText": "Will be used when interactive search is used", "EnableInteractiveSearchHelpTextWarning": "Search is not supported with this indexer", - "EnableMediaInfoHelpText": "Extract video information such as resolution, runtime and codec information from files. This requires Sonarr to read parts of the file which may cause high disk or network activity during scans.", + "EnableMediaInfoHelpText": "Extract video information such as resolution, runtime and codec information from files. This requires {appName} to read parts of the file which may cause high disk or network activity during scans.", "EnableMetadataHelpText": "Enable metadata file creation for this metadata type", "EnableProfile": "Enable Profile", "EnableProfileHelpText": "Check to enable release profile", "EnableRss": "Enable RSS", - "EnableRssHelpText": "Will be used when Sonarr periodically looks for releases via RSS Sync", + "EnableRssHelpText": "Will be used when {appName} periodically looks for releases via RSS Sync", "EnableSsl": "Enable SSL", "EnableSslHelpText": "Requires restart running as administrator to take effect", "Enabled": "Enabled", @@ -472,7 +472,7 @@ "ExportCustomFormat": "Export Custom Format", "Extend": "Extend", "External": "External", - "ExternalUpdater": "Sonarr is configured to use an external update mechanism", + "ExternalUpdater": "{appName} is configured to use an external update mechanism", "ExtraFileExtensionsHelpText": "Comma separated list of extra files to import (.nfo will be imported as .nfo-orig)", "ExtraFileExtensionsHelpTextsExamples": "Examples: '.sub, .nfo' or 'sub,nfo'", "Failed": "Failed", @@ -480,7 +480,7 @@ "FailedToLoadCustomFiltersFromApi": "Failed to load custom filters from API", "FailedToLoadQualityProfilesFromApi": "Failed to load quality profiles from API", "FailedToLoadSeriesFromApi": "Failed to load series from API", - "FailedToLoadSonarr": "Failed to load Sonarr", + "FailedToLoadSonarr": "Failed to load {appName}", "FailedToLoadSystemStatusFromApi": "Failed to load system status from API", "FailedToLoadTagsFromApi": "Failed to load tags from API", "FailedToLoadTranslationsFromApi": "Failed to load translations from API", @@ -555,7 +555,7 @@ "Grab": "Grab", "GrabId": "Grab ID", "GrabRelease": "Grab Release", - "GrabReleaseMessageText": "Sonarr was unable to determine which series and episode this release was for. Sonarr may be unable to automatically import this release. Do you want to grab '{title}'?", + "GrabReleaseMessageText": "{appName} was unable to determine which series and episode this release was for. {appName} may be unable to automatically import this release. Do you want to grab '{title}'?", "GrabSelected": "Grab Selected", "Grabbed": "Grabbed", "GrabbedHistoryTooltip": "Episode grabbed from {indexer} and sent to {downloadClient}", @@ -616,7 +616,7 @@ "ImportListStatusUnavailableHealthCheckMessage": "Lists unavailable due to failures: {0}", "ImportLists": "Import Lists", "ImportListsLoadError": "Unable to load Import Lists", - "ImportListsSettingsSummary": "Import from another Sonarr instance or Trakt lists and manage list exclusions", + "ImportListsSettingsSummary": "Import from another {appName} instance or Trakt lists and manage list exclusions", "ImportMechanismEnableCompletedDownloadHandlingIfPossibleHealthCheckMessage": "Enable Completed Download Handling if possible", "ImportMechanismEnableCompletedDownloadHandlingIfPossibleMultiComputerHealthCheckMessage": "Enable Completed Download Handling if possible (Multi-Computer unsupported)", "ImportMechanismHandlingDisabledHealthCheckMessage": "Enable Completed Download Handling", @@ -640,12 +640,12 @@ "IndexerLongTermStatusUnavailableHealthCheckMessage": "Indexers unavailable due to failures for more than 6 hours: {0}", "IndexerOptionsLoadError": "Unable to load indexer options", "IndexerPriority": "Indexer Priority", - "IndexerPriorityHelpText": "Indexer Priority from 1 (Highest) to 50 (Lowest). Default: 25. Used when grabbing releases as a tiebreaker for otherwise equal releases, Sonarr will still use all enabled indexers for RSS Sync and Searching", + "IndexerPriorityHelpText": "Indexer Priority from 1 (Highest) to 50 (Lowest). Default: 25. Used when grabbing releases as a tiebreaker for otherwise equal releases, {appName} will still use all enabled indexers for RSS Sync and Searching", "IndexerRssNoIndexersAvailableHealthCheckMessage": "All rss-capable indexers are temporarily unavailable due to recent indexer errors", - "IndexerRssNoIndexersEnabledHealthCheckMessage": "No indexers available with RSS sync enabled, Sonarr will not grab new releases automatically", - "IndexerSearchNoAutomaticHealthCheckMessage": "No indexers available with Automatic Search enabled, Sonarr will not provide any automatic search results", + "IndexerRssNoIndexersEnabledHealthCheckMessage": "No indexers available with RSS sync enabled, {appName} will not grab new releases automatically", + "IndexerSearchNoAutomaticHealthCheckMessage": "No indexers available with Automatic Search enabled, {appName} will not provide any automatic search results", "IndexerSearchNoAvailableIndexersHealthCheckMessage": "All search-capable indexers are temporarily unavailable due to recent indexer errors", - "IndexerSearchNoInteractiveHealthCheckMessage": "No indexers available with Interactive Search enabled, Sonarr will not provide any interactive search results", + "IndexerSearchNoInteractiveHealthCheckMessage": "No indexers available with Interactive Search enabled, {appName} will not provide any interactive search results", "IndexerSettings": "Indexer Settings", "IndexerStatusAllUnavailableHealthCheckMessage": "All indexers are unavailable due to failures", "IndexerStatusUnavailableHealthCheckMessage": "Indexers unavailable due to failures: {0}", @@ -697,7 +697,7 @@ "LibraryImportTips": "Some tips to ensure the import goes smoothly:", "LibraryImportTipsDontUseDownloadsFolder": "Do not use for importing downloads from your download client, this is only for existing organized libraries, not unsorted files.", "LibraryImportTipsQualityInFilename": "Make sure that your files include the quality in their filenames. eg. `episode.s02e15.bluray.mkv`", - "LibraryImportTipsUseRootFolder": "Point Sonarr to the folder containing all of your tv shows, not a specific one. eg. \"`{goodFolderExample}`\" and not \"`{badFolderExample}`\". Additionally, each series must be in its own folder within the root/library folder.", + "LibraryImportTipsUseRootFolder": "Point {appName} to the folder containing all of your tv shows, not a specific one. eg. \"`{goodFolderExample}`\" and not \"`{badFolderExample}`\". Additionally, each series must be in its own folder within the root/library folder.", "Links": "Links", "ListExclusionsLoadError": "Unable to load List Exclusions", "ListOptionsLoadError": "Unable to load list options", @@ -736,7 +736,7 @@ "Mapping": "Mapping", "MarkAsFailed": "Mark as Failed", "MarkAsFailedConfirmation": "Are you sure you want to mark '{sourceTitle}' as failed?", - "MassSearchCancelWarning": "This cannot be cancelled once started without restarting Sonarr or disabling all of your indexers.", + "MassSearchCancelWarning": "This cannot be cancelled once started without restarting {appName} or disabling all of your indexers.", "MatchedToEpisodes": "Matched to Episodes", "MatchedToSeason": "Matched to Season", "MatchedToSeries": "Matched to Series", @@ -763,7 +763,7 @@ "MetadataSettingsSummary": "Create metadata files when episodes are imported or series are refreshed", "MetadataSource": "Metadata Source", "MetadataSourceSettings": "Metadata Source Settings", - "MetadataSourceSettingsSummary": "Information on where Sonarr gets series and episode information", + "MetadataSourceSettingsSummary": "Information on where {appName} gets series and episode information", "MidseasonFinale": "Midseason Finale", "Min": "Min", "MinimumAge": "Minimum Age", @@ -889,7 +889,7 @@ "OnHealthIssue": "On Health Issue", "OnHealthRestored": "On Health Restored", "OnImport": "On Import", - "OnLatestVersion": "The latest version of Sonarr is already installed", + "OnLatestVersion": "The latest version of {appName} is already installed", "OnManualInteractionRequired": "On Manual Interaction Required", "OnRename": "On Rename", "OnSeriesAdd": "On Series Add", @@ -901,7 +901,7 @@ "OnlyTorrent": "Only Torrent", "OnlyUsenet": "Only Usenet", "OpenBrowserOnStart": "Open browser on start", - "OpenBrowserOnStartHelpText": " Open a web browser and navigate to the Sonarr homepage on app start.", + "OpenBrowserOnStartHelpText": " Open a web browser and navigate to the {appName} homepage on app start.", "OpenSeries": "Open Series", "OptionalName": "Optional name", "Options": "Options", @@ -934,7 +934,7 @@ "Parse": "Parse", "ParseModalErrorParsing": "Error parsing, please try again.", "ParseModalHelpText": "Enter a release title in the input above", - "ParseModalHelpTextDetails": "Sonarr will attempt to parse the title and show you details about it", + "ParseModalHelpTextDetails": "{appName} will attempt to parse the title and show you details about it", "ParseModalUnableToParse": "Unable to parse the provided title, please try again.", "PartialSeason": "Partial Season", "Password": "Password", @@ -1012,7 +1012,7 @@ "Real": "Real", "Reason": "Reason", "RecentChanges": "Recent Changes", - "RecycleBinUnableToWriteHealthCheckMessage": "Unable to write to configured recycling bin folder: {0}. Ensure this path exists and is writable by the user running Sonarr", + "RecycleBinUnableToWriteHealthCheckMessage": "Unable to write to configured recycling bin folder: {0}. Ensure this path exists and is writable by the user running {appName}", "RecyclingBin": "Recycling Bin", "RecyclingBinCleanup": "Recycling Bin Cleanup", "RecyclingBinCleanupHelpText": "Set to 0 to disable automatic cleanup", @@ -1051,18 +1051,18 @@ "RemotePath": "Remote Path", "RemotePathMappingBadDockerPathHealthCheckMessage": "You are using docker; download client {0} places downloads in {1} but this is not a valid {2} path. Review your remote path mappings and download client settings.", "RemotePathMappingDockerFolderMissingHealthCheckMessage": "You are using docker; download client {0} places downloads in {1} but this directory does not appear to exist inside the container. Review your remote path mappings and container volume settings.", - "RemotePathMappingDownloadPermissionsHealthCheckMessage": "Sonarr can see but not access downloaded episode {0}. Likely permissions error.", + "RemotePathMappingDownloadPermissionsHealthCheckMessage": "{appName} can see but not access downloaded episode {0}. Likely permissions error.", "RemotePathMappingFileRemovedHealthCheckMessage": "File {0} was removed part way through processing.", "RemotePathMappingFilesBadDockerPathHealthCheckMessage": "You are using docker; download client {0} reported files in {1} but this is not a valid {2} path. Review your remote path mappings and download client settings.", - "RemotePathMappingFilesGenericPermissionsHealthCheckMessage": "Download client {0} reported files in {1} but Sonarr cannot see this directory. You may need to adjust the folder's permissions.", + "RemotePathMappingFilesGenericPermissionsHealthCheckMessage": "Download client {0} reported files in {1} but {appName} cannot see this directory. You may need to adjust the folder's permissions.", "RemotePathMappingFilesLocalWrongOSPathHealthCheckMessage": "Local download client {0} reported files in {1} but this is not a valid {2} path. Review your download client settings.", "RemotePathMappingFilesWrongOSPathHealthCheckMessage": "Remote download client {0} reported files in {1} but this is not a valid {2} path. Review your remote path mappings and download client settings.", - "RemotePathMappingFolderPermissionsHealthCheckMessage": "Sonarr can see but not access download directory {0}. Likely permissions error.", - "RemotePathMappingGenericPermissionsHealthCheckMessage": "Download client {0} places downloads in {1} but Sonarr cannot see this directory. You may need to adjust the folder's permissions.", + "RemotePathMappingFolderPermissionsHealthCheckMessage": "{appName} can see but not access download directory {0}. Likely permissions error.", + "RemotePathMappingGenericPermissionsHealthCheckMessage": "Download client {0} places downloads in {1} but {appName} cannot see this directory. You may need to adjust the folder's permissions.", "RemotePathMappingHostHelpText": "The same host you specified for the remote Download Client", - "RemotePathMappingImportFailedHealthCheckMessage": "Sonarr failed to import (an) episode(s). Check your logs for details.", + "RemotePathMappingImportFailedHealthCheckMessage": "{appName} failed to import (an) episode(s). Check your logs for details.", "RemotePathMappingLocalFolderMissingHealthCheckMessage": "Remote download client {0} places downloads in {1} but this directory does not appear to exist. Likely missing or incorrect remote path mapping.", - "RemotePathMappingLocalPathHelpText": "Path that Sonarr should use to access the remote path locally", + "RemotePathMappingLocalPathHelpText": "Path that {appName} should use to access the remote path locally", "RemotePathMappingLocalWrongOSPathHealthCheckMessage": "Local download client {0} places downloads in {1} but this is not a valid {2} path. Review your download client settings.", "RemotePathMappingRemoteDownloadClientHealthCheckMessage": "Remote download client {0} reported files in {1} but this directory does not appear to exist. Likely missing remote path mapping.", "RemotePathMappingRemotePathHelpText": "Root path to the directory that the Download Client accesses", @@ -1099,7 +1099,7 @@ "RemovedSeriesSingleRemovedHealthCheckMessage": "Series {0} was removed from TheTVDB", "RemovingTag": "Removing tag", "RenameEpisodes": "Rename Episodes", - "RenameEpisodesHelpText": "Sonarr will use the existing file name if renaming is disabled", + "RenameEpisodesHelpText": "{appName} will use the existing file name if renaming is disabled", "RenameFiles": "Rename Files", "Renamed": "Renamed", "Reorder": "Reorder", @@ -1107,14 +1107,14 @@ "Repeat": "Repeat", "Replace": "Replace", "ReplaceIllegalCharacters": "Replace Illegal Characters", - "ReplaceIllegalCharactersHelpText": "Replace illegal characters. If unchecked, Sonarr will remove them instead", + "ReplaceIllegalCharactersHelpText": "Replace illegal characters. If unchecked, {appName} will remove them instead", "ReplaceWithDash": "Replace with Dash", "ReplaceWithSpaceDash": "Replace with Space Dash", "ReplaceWithSpaceDashSpace": "Replace with Space Dash Space", "Required": "Required", "RequiredHelpText": "This {implementationName} condition must match for the custom format to apply. Otherwise a single {implementationName} match is sufficient.", "RescanAfterRefreshHelpText": "Rescan the series folder after refreshing the series", - "RescanAfterRefreshHelpTextWarning": "Sonarr will not automatically detect changes to files when not set to 'Always'", + "RescanAfterRefreshHelpTextWarning": "{appName} will not automatically detect changes to files when not set to 'Always'", "RescanSeriesFolderAfterRefresh": "Rescan Series Folder after Refresh", "Reset": "Reset", "ResetAPIKey": "Reset API Key", @@ -1127,11 +1127,11 @@ "Restart": "Restart", "RestartLater": "I'll restart later", "RestartNow": "Restart Now", - "RestartReloadNote": "Note: Sonarr will automatically restart and reload the UI during the restore process.", + "RestartReloadNote": "Note: {appName} will automatically restart and reload the UI during the restore process.", "RestartRequiredHelpTextWarning": "Requires restart to take effect", - "RestartRequiredToApplyChanges": "Sonarr requires a restart to apply changes, do you want to restart now?", - "RestartRequiredWindowsService": "Depending which user is running the Sonarr service you may need to restart Sonarr as admin once before the service will start automatically.", - "RestartSonarr": "Restart Sonarr", + "RestartRequiredToApplyChanges": "{appName} requires a restart to apply changes, do you want to restart now?", + "RestartRequiredWindowsService": "Depending which user is running the {appName} service you may need to restart {appName} as admin once before the service will start automatically.", + "RestartSonarr": "Restart {appName}", "Restore": "Restore", "RestoreBackup": "Restore Backup", "RestrictionsLoadError": "Unable to load Restrictions", @@ -1277,7 +1277,7 @@ "ShowTitle": "Show Title", "ShowTitleHelpText": "Show series title under poster", "ShowUnknownSeriesItems": "Show Unknown Series Items", - "ShowUnknownSeriesItemsHelpText": "Show items without a series in the queue, this could include removed series, movies or anything else in Sonarr's category", + "ShowUnknownSeriesItemsHelpText": "Show items without a series in the queue, this could include removed series, movies or anything else in {appName}'s category", "ShownClickToHide": "Shown, click to hide", "Shutdown": "Shutdown", "SingleEpisode": "Single Episode", @@ -1286,16 +1286,16 @@ "SizeLimit": "Size Limit", "SizeOnDisk": "Size on disk", "SkipFreeSpaceCheck": "Skip Free Space Check", - "SkipFreeSpaceCheckWhenImportingHelpText": "Use when Sonarr is unable to detect free space from your series root folder", + "SkipFreeSpaceCheckWhenImportingHelpText": "Use when {appName} is unable to detect free space from your series root folder", "SkipRedownload": "Skip Redownload", - "SkipRedownloadHelpText": "Prevents Sonarr from trying to download an alternative release for this item", + "SkipRedownloadHelpText": "Prevents {appName} from trying to download an alternative release for this item", "Small": "Small", "SmartReplace": "Smart Replace", "SmartReplaceHint": "Dash or Space Dash depending on name", "Socks4": "Socks4", "Socks5": "Socks5 (Support TOR)", "SomeResultsAreHiddenByTheAppliedFilter": "Some results are hidden by the applied filter", - "SonarrTags": "Sonarr Tags", + "SonarrTags": "{appName} Tags", "Sort": "Sort", "Source": "Source", "SourcePath": "Source Path", @@ -1324,14 +1324,14 @@ "Style": "Style", "SubtitleLanguages": "Subtitle Languages", "Sunday": "Sunday", - "SupportedAutoTaggingProperties": "Sonarr supports the follow properties for auto tagging rules", - "SupportedCustomConditions": "Sonarr supports custom conditions against the release properties below.", - "SupportedDownloadClients": "Sonarr supports many popular torrent and usenet download clients.", + "SupportedAutoTaggingProperties": "{appName} supports the follow properties for auto tagging rules", + "SupportedCustomConditions": "{appName} supports custom conditions against the release properties below.", + "SupportedDownloadClients": "{appName} supports many popular torrent and usenet download clients.", "SupportedDownloadClientsMoreInfo": "For more information on the individual download clients, click the more info buttons.", "SupportedImportListsMoreInfo": "For more information on the individual import lists, click on the more info buttons.", - "SupportedIndexers": "Sonarr supports any indexer that uses the Newznab standard, as well as other indexers listed below.", + "SupportedIndexers": "{appName} supports any indexer that uses the Newznab standard, as well as other indexers listed below.", "SupportedIndexersMoreInfo": "For more information on the individual indexers, click on the more info buttons.", - "SupportedLists": "Sonarr supports multiple lists for importing Series into the database.", + "SupportedLists": "{appName} supports multiple lists for importing Series into the database.", "SupportedListsMoreInfo": "For more information on the individual lists, click on the more info buttons.", "System": "System", "SystemTimeHealthCheckMessage": "System time is off by more than 1 day. Scheduled tasks may not run correctly until the time is corrected", @@ -1391,7 +1391,7 @@ "TypeOfList": "{typeOfList} List", "Ui": "UI", "UiLanguage": "UI Language", - "UiLanguageHelpText": "Language that Sonarr will use for UI", + "UiLanguageHelpText": "Language that {appName} will use for UI", "UiSettings": "UI Settings", "UiSettingsLoadError": "Unable to load UI settings", "UiSettingsSummary": "Calendar, date and color impaired options", @@ -1404,7 +1404,7 @@ "UnableToLoadAutoTagging": "Unable to load auto tagging", "UnableToLoadBackups": "Unable to load backups", "UnableToLoadRootFolders": "Unable to load root folders", - "UnableToUpdateSonarrDirectly": "Unable to update Sonarr directly,", + "UnableToUpdateSonarrDirectly": "Unable to update {appName} directly,", "Unavailable": "Unavailable", "Underscore": "Underscore", "Ungroup": "Ungroup", @@ -1414,7 +1414,7 @@ "UnmappedFilesOnly": "Unmapped Files Only", "UnmappedFolders": "Unmapped Folders", "UnmonitorDeletedEpisodes": "Unmonitor Deleted Episodes", - "UnmonitorDeletedEpisodesHelpText": "Episodes deleted from disk are automatically unmonitored in Sonarr", + "UnmonitorDeletedEpisodesHelpText": "Episodes deleted from disk are automatically unmonitored in {appName}", "UnmonitorSelected": "Unmonitor Selected", "UnmonitorSpecials": "Unmonitor Specials", "UnmonitorSpecialsDescription": "Unmonitor all special episodes without changing the monitored status of other episodes", @@ -1428,11 +1428,11 @@ "UpdateAutomaticallyHelpText": "Automatically download and install updates. You will still be able to install from System: Updates", "UpdateAvailableHealthCheckMessage": "New update is available", "UpdateFiltered": "Update Filtered", - "UpdateMechanismHelpText": "Use Sonarr's built-in updater or a script", + "UpdateMechanismHelpText": "Use {appName}'s built-in updater or a script", "UpdateMonitoring": "Update Monitoring", "UpdateScriptPathHelpText": "Path to a custom script that takes an extracted update package and handle the remainder of the update process", "UpdateSelected": "Update Selected", - "UpdateSonarrDirectlyLoadError": "Unable to update Sonarr directly,", + "UpdateSonarrDirectlyLoadError": "Unable to update {appName} directly,", "UpdateStartupNotWritableHealthCheckMessage": "Cannot install update because startup folder '{0}' is not writable by the user '{1}'.", "UpdateStartupTranslocationHealthCheckMessage": "Cannot install update because startup folder '{0}' is in an App Translocation folder.", "UpdateUINotWritableHealthCheckMessage": "Cannot install update because UI folder '{0}' is not writable by the user '{1}'.", @@ -1441,8 +1441,8 @@ "Updates": "Updates", "UpgradeUntil": "Upgrade Until", "UpgradeUntilCustomFormatScore": "Upgrade Until Custom Format Score", - "UpgradeUntilCustomFormatScoreHelpText": "Once this custom format score is reached Sonarr will no longer grab episode releases", - "UpgradeUntilHelpText": "Once this quality is reached Sonarr will no longer download episodes", + "UpgradeUntilCustomFormatScoreHelpText": "Once this custom format score is reached {appName} will no longer grab episode releases", + "UpgradeUntilHelpText": "Once this quality is reached {appName} will no longer download episodes", "UpgradeUntilThisQualityIsMetOrExceeded": "Upgrade until this quality is met or exceeded", "UpgradesAllowed": "Upgrades Allowed", "UpgradesAllowedHelpText": "If disabled qualities will not be upgraded", diff --git a/src/NzbDrone.Core/Localization/Core/es.json b/src/NzbDrone.Core/Localization/Core/es.json index 548885bcc..f4a65e235 100644 --- a/src/NzbDrone.Core/Localization/Core/es.json +++ b/src/NzbDrone.Core/Localization/Core/es.json @@ -43,7 +43,7 @@ "ApplicationURL": "URL de la aplicación", "Authentication": "Autenticación", "AuthForm": "Formularios (página de inicio de sesión)", - "AuthenticationMethodHelpText": "Requerir nombre de usuario y contraseña para acceder Sonarr", + "AuthenticationMethodHelpText": "Requerir nombre de usuario y contraseña para acceder {appName}", "AuthenticationRequired": "Autenticación Requerida", "AutoTaggingLoadError": "No se pudo cargar el etiquetado automático", "AutoRedownloadFailedHelpText": "Buscar e intentar descargar automáticamente una versión diferente", @@ -78,7 +78,7 @@ "Torrents": "Torrents", "Ui": "UI", "Underscore": "Guion bajo", - "UpdateMechanismHelpText": "Usar el actualizador de Sonarr o un script", + "UpdateMechanismHelpText": "Usar el actualizador de {appName} o un script", "Warn": "Advertencia", "AutoTagging": "Etiquetado Automático", "AddAutoTag": "Añadir Etiqueta Automática", @@ -165,7 +165,7 @@ "AddRemotePathMappingError": "No se pudo añadir una nueva asignación de ruta remota, inténtelo de nuevo.", "AgeWhenGrabbed": "Antigüedad (cuando se añadió)", "AllResultsAreHiddenByTheAppliedFilter": "Todos los resultados están ocultos por el filtro aplicado", - "AnalyseVideoFilesHelpText": "Extraer información de video como la resolución, el tiempo de ejecución y la información del códec de los archivos. Esto requiere que Sonarr lea partes del archivo lo cual puede causar una alta actividad en el disco o en la red durante los escaneos.", + "AnalyseVideoFilesHelpText": "Extraer información de video como la resolución, el tiempo de ejecución y la información del códec de los archivos. Esto requiere que {appName} lea partes del archivo lo cual puede causar una alta actividad en el disco o en la red durante los escaneos.", "AnimeTypeDescription": "Episodios lanzados usando un número de episodio absoluto", "ApiKeyValidationHealthCheckMessage": "Actualice su clave de API para que tenga al menos {0} carácteres. Puede hacerlo en los ajustes o en el archivo de configuración", "AppDataLocationHealthCheckMessage": "No será posible actualizar para prevenir la eliminación de AppData al Actualizar", @@ -174,7 +174,7 @@ "Clone": "Clonar", "Connections": "Conexiones", "Dash": "Guión", - "AnalyticsEnabledHelpText": "Envíe información anónima de uso y error a los servidores de Sonarr. Esto incluye información sobre su navegador, qué páginas de Sonarr WebUI utiliza, informes de errores, así como el sistema operativo y la versión en tiempo de ejecución. Usaremos esta información para priorizar funciones y correcciones de errores.", + "AnalyticsEnabledHelpText": "Envíe información anónima de uso y error a los servidores de {appName}. Esto incluye información sobre su navegador, qué páginas de {appName} WebUI utiliza, informes de errores, así como el sistema operativo y la versión en tiempo de ejecución. Usaremos esta información para priorizar funciones y correcciones de errores.", "BackupIntervalHelpText": "Intervalo entre copias de seguridad automáticas", "BackupRetentionHelpText": "Las copias de seguridad automáticas anteriores al período de retención serán borradas automáticamente", "AddNewSeries": "Añadir Nueva Serie", diff --git a/src/NzbDrone.Core/Localization/Core/hu.json b/src/NzbDrone.Core/Localization/Core/hu.json index d96b4dfd5..49ff8b7ac 100644 --- a/src/NzbDrone.Core/Localization/Core/hu.json +++ b/src/NzbDrone.Core/Localization/Core/hu.json @@ -1,5 +1,5 @@ { - "BlocklistReleaseHelpText": "Megakadályozza, hogy a Sonarr automatikusan újra letöltse ezt a kiadást", + "BlocklistReleaseHelpText": "Megakadályozza, hogy a {appName} automatikusan újra letöltse ezt a kiadást", "CloneCondition": "Feltétel klónozása", "CloneCustomFormat": "Egyéni formátum klónozása", "Close": "Bezárás", @@ -46,13 +46,13 @@ "ImportMechanismEnableCompletedDownloadHandlingIfPossibleHealthCheckMessage": "Befejezett letöltés kezelésének engedélyezése, ha lehetséges", "ImportMechanismHandlingDisabledHealthCheckMessage": "Befejezett letöltés kezelésének engedélyezése", "ImportMechanismEnableCompletedDownloadHandlingIfPossibleMultiComputerHealthCheckMessage": "Befejezett letöltés kezelésének engedélyezése, ha lehetséges (Több számítógépen nem támogatott)", - "IndexerRssNoIndexersEnabledHealthCheckMessage": "Nincsenek elérhető indexelők RSS szinkronizációval, a Sonarr nem fog automatikusan új kiadásokat letölteni", + "IndexerRssNoIndexersEnabledHealthCheckMessage": "Nincsenek elérhető indexelők RSS szinkronizációval, a {appName} nem fog automatikusan új kiadásokat letölteni", "IndexerRssNoIndexersAvailableHealthCheckMessage": "Az összes RSS-képes indexelő ideiglenesen nem elérhető a legutóbbi indexelő hibák miatt", "IndexerLongTermStatusUnavailableHealthCheckMessage": "Minden indexelő elérhetetlen meghibásodás miatt több, mint 6 órája: {0}", "IndexerLongTermStatusAllUnavailableHealthCheckMessage": "Minden indexelő elérhetetlen meghibásodás miatt több, mint 6 órája", - "IndexerSearchNoInteractiveHealthCheckMessage": "Nincsenek elérhető indexelők az Interaktív Keresés funkcióval, a Sonarr nem fog interaktív keresési eredményeket szolgáltatni", + "IndexerSearchNoInteractiveHealthCheckMessage": "Nincsenek elérhető indexelők az Interaktív Keresés funkcióval, a {appName} nem fog interaktív keresési eredményeket szolgáltatni", "IndexerSearchNoAvailableIndexersHealthCheckMessage": "Az összes keresési képességgel rendelkező indexelő ideiglenesen nem elérhető a legutóbbi indexelő hibák miatt", - "IndexerSearchNoAutomaticHealthCheckMessage": "Nincsenek elérhető indexelők az Automatikus Keresés funkcióval, a Sonarr nem fog automatikus keresési eredményeket szolgáltatni", + "IndexerSearchNoAutomaticHealthCheckMessage": "Nincsenek elérhető indexelők az Automatikus Keresés funkcióval, a {appName} nem fog automatikus keresési eredményeket szolgáltatni", "Language": "Nyelv", "IndexerStatusUnavailableHealthCheckMessage": "Minden indexelő elérhetetlen meghibásodások miatt: {0}", "IndexerStatusAllUnavailableHealthCheckMessage": "Minden indexelő elérhetetlen meghibásodások miatt", @@ -69,15 +69,15 @@ "ProxyFailedToTestHealthCheckMessage": "Sikertelen proxy teszt: {0}", "ProxyResolveIpHealthCheckMessage": "Nem sikerült feloldani a konfigurált proxy kiszolgáló {0} IP-címét", "PreviousAiring": "Előző rész", - "RecycleBinUnableToWriteHealthCheckMessage": "Nem lehet írni a konfigurált lomtár mappába {0}. Győződjön meg arról, hogy ez az elérési útvonal létezik, és az a felhasználó, aki a Sonarr-t futtatja, írási jogosultsággal rendelkezik", + "RecycleBinUnableToWriteHealthCheckMessage": "Nem lehet írni a konfigurált lomtár mappába {0}. Győződjön meg arról, hogy ez az elérési útvonal létezik, és az a felhasználó, aki a {appName}-t futtatja, írási jogosultsággal rendelkezik", "QualityProfile": "Minőségi profil", "RemotePathMappingDockerFolderMissingHealthCheckMessage": "Docker-t használ; a(z) {0} letöltési kliens a letöltéseket a(z) {1} mappába helyezi, de úgy tűnik, hogy ez a könyvtár nem létezik a konténeren belül. Ellenőrizze a távoli útvonal hozzárendeléseket, és a konténer kötet beállításait.", "RefreshSeries": "Sorozat frissítése", "RemotePathMappingFileRemovedHealthCheckMessage": "A(z) {0} fájlt részben feldolgozás közben eltávolították.", - "RemotePathMappingDownloadPermissionsHealthCheckMessage": "A Sonarr látja, de nem tud hozzáférni a letöltött epizódhoz {0}. Valószínűleg jogosultsági hiba.", + "RemotePathMappingDownloadPermissionsHealthCheckMessage": "A {appName} látja, de nem tud hozzáférni a letöltött epizódhoz {0}. Valószínűleg jogosultsági hiba.", "RemotePathMappingFilesLocalWrongOSPathHealthCheckMessage": "A(z) {0} helyi letöltési kliens a fájlokat a(z) {1} mappában jelentette, de ez nem érvényes {2} elérési útvonal. Ellenőrizze a letöltési kliens beállításait.", - "RemotePathMappingGenericPermissionsHealthCheckMessage": "A(z) {0} letöltési kliens a letöltéseket a(z) {1} mappába helyezi, de a Sonarr nem látja ezt a könyvtárat. Lehetséges, hogy be kell állítania a mappa jogosultságait.", - "RemotePathMappingImportFailedHealthCheckMessage": "A Sonarr-nak nem sikerült importálni az epizód(ok)at. Ellenőrizze a naplókat a részletekért.", + "RemotePathMappingGenericPermissionsHealthCheckMessage": "A(z) {0} letöltési kliens a letöltéseket a(z) {1} mappába helyezi, de a {appName} nem látja ezt a könyvtárat. Lehetséges, hogy be kell állítania a mappa jogosultságait.", + "RemotePathMappingImportFailedHealthCheckMessage": "A {appName}-nak nem sikerült importálni az epizód(ok)at. Ellenőrizze a naplókat a részletekért.", "RemotePathMappingRemoteDownloadClientHealthCheckMessage": "A(z) {0} távoli letöltési kliens a fájlokat a(z) {1} mappában jelentette, de úgy tűnik, hogy ez a könyvtár nem létezik. Valószínűleg hiányzik a távoli útvonal hozzárendelés.", "RemotePathMappingLocalFolderMissingHealthCheckMessage": "A(z) {0} távoli letöltési kliens a letöltéseket a(z) {1} mappába helyezi, de úgy tűnik, hogy ez a könyvtár nem létezik. Valószínűleg hiányzik vagy helytelen a távoli útvonal hozzárendelés.", "RemotePathMappingLocalWrongOSPathHealthCheckMessage": "A(z) {0} helyi letöltési kliens a letöltéseket a(z) {1} mappába helyezi, de ez nem érvényes {2} elérési útvonal. Ellenőrizze a letöltési kliens beállításait.", @@ -99,10 +99,10 @@ "UpdateStartupTranslocationHealthCheckMessage": "A frissítés telepítése nem lehetséges, mert a kezdő mappa '{0}' az App Translocation mappában található.", "UpdateAvailableHealthCheckMessage": "Új frissítés elérhető", "UpdateUINotWritableHealthCheckMessage": "A frissítés telepítése nem lehetséges, mert a felhasználó '{1}' nem rendelkezik írási jogosultsággal a(z) '{0}' felhasználói felület mappában.", - "DownloadClientSortingHealthCheckMessage": "A(z) {0} letöltési kliensben engedélyezve van a {1} rendezés a Sonarr kategóriájához. Az import problémák elkerülése érdekében ki kell kapcsolnia a rendezést a letöltési kliensben.", + "DownloadClientSortingHealthCheckMessage": "A(z) {0} letöltési kliensben engedélyezve van a {1} rendezés a {appName} kategóriájához. Az import problémák elkerülése érdekében ki kell kapcsolnia a rendezést a letöltési kliensben.", "RemotePathMappingBadDockerPathHealthCheckMessage": "Docker-t használ; a(z) {0} letöltési kliens a letöltéseket a(z) {1} mappába helyezi, de ez nem érvényes {2} elérési útvonal. Ellenőrizze a távoli útvonal hozzárendeléseket, és a letöltési kliens beállításait.", "RemotePathMappingFilesBadDockerPathHealthCheckMessage": "Docker-t használ; a(z) {0} letöltési kliens a fájlokat a(z) {1} mappában jelentette, de ez nem érvényes {2} elérési útvonal. Ellenőrizze a távoli útvonal hozzárendeléseket, és a letöltési kliens beállításait.", - "RemotePathMappingFilesGenericPermissionsHealthCheckMessage": "A(z) {0} letöltési kliens a fájlokat a(z) {1} mappában jelentette, de a Sonarr nem látja ezt a könyvtárat. Lehetséges, hogy be kell állítania a mappa jogosultságait.", + "RemotePathMappingFilesGenericPermissionsHealthCheckMessage": "A(z) {0} letöltési kliens a fájlokat a(z) {1} mappában jelentette, de a {appName} nem látja ezt a könyvtárat. Lehetséges, hogy be kell állítania a mappa jogosultságait.", "RemotePathMappingFilesWrongOSPathHealthCheckMessage": "A(z) {0} távoli letöltési kliens a fájlokat a(z) {1} mappában jelentette, de ez nem érvényes {2} elérési útvonal. Ellenőrizze a távoli útvonal hozzárendeléseket, és a letöltési kliens beállításait.", - "RemotePathMappingFolderPermissionsHealthCheckMessage": "A Sonarr látja, de nem fér hozzá a letöltési könyvtárhoz {0}. Valószínűleg jogosultsági hiba." + "RemotePathMappingFolderPermissionsHealthCheckMessage": "A {appName} látja, de nem fér hozzá a letöltési könyvtárhoz {0}. Valószínűleg jogosultsági hiba." } diff --git a/src/NzbDrone.Core/Localization/Core/id.json b/src/NzbDrone.Core/Localization/Core/id.json index 5729ecd63..a993eb76e 100644 --- a/src/NzbDrone.Core/Localization/Core/id.json +++ b/src/NzbDrone.Core/Localization/Core/id.json @@ -1,6 +1,6 @@ { "Added": "Ditambahkan", - "BlocklistReleaseHelpText": "Mencegah Sonarr memperoleh rilis ini secara otomatis", + "BlocklistReleaseHelpText": "Mencegah {appName} memperoleh rilis ini secara otomatis", "Delete": "Hapus", "Close": "Tutup", "EnableAutomaticSearch": "Aktifkan Penelusuran Otomatis", diff --git a/src/NzbDrone.Core/Localization/Core/nl.json b/src/NzbDrone.Core/Localization/Core/nl.json index edddcaba9..78a89eebe 100644 --- a/src/NzbDrone.Core/Localization/Core/nl.json +++ b/src/NzbDrone.Core/Localization/Core/nl.json @@ -21,7 +21,7 @@ "ApplyTagsHelpTextHowToApplyIndexers": "Hoe tags toepassen op de geselecteerde indexeerders", "CountDownloadClientsSelected": "{count} download client(s) geselecteerd", "ApplyTagsHelpTextHowToApplySeries": "Hoe tags toepassen op de geselecteerde series", - "BlocklistReleaseHelpText": "Verbied Sonarr om deze release opnieuw automatisch te downloaden", + "BlocklistReleaseHelpText": "Verbied {appName} om deze release opnieuw automatisch te downloaden", "Delete": "Verwijder", "ApplyTagsHelpTextRemove": "Verwijderen: Verwijder de ingevoerde tags", "ApplyTagsHelpTextReplace": "Vervangen: Vervang de tags met de ingevoerde tags (vul geen tags in om alle tags te wissen)", diff --git a/src/NzbDrone.Core/Localization/Core/pt_BR.json b/src/NzbDrone.Core/Localization/Core/pt_BR.json index 1182fb291..6f22cfd97 100644 --- a/src/NzbDrone.Core/Localization/Core/pt_BR.json +++ b/src/NzbDrone.Core/Localization/Core/pt_BR.json @@ -13,9 +13,9 @@ "ImportListStatusAllUnavailableHealthCheckMessage": "Todas as listas estão indisponíveis devido a falhas", "ImportMechanismHandlingDisabledHealthCheckMessage": "Ativar gerenciamento de download concluído", "IndexerLongTermStatusUnavailableHealthCheckMessage": "Indexadores indisponíveis devido a falhas por mais de 6 horas: {0}", - "IndexerRssNoIndexersEnabledHealthCheckMessage": "Nenhum indexador disponível com sincronização de RSS ativada, o Sonarr não obterá novos lançamentos automaticamente", - "IndexerSearchNoAutomaticHealthCheckMessage": "Nenhum indexador disponível com a pesquisa automática ativada, o Sonarr não fornecerá nenhum resultado de pesquisa automática", - "IndexerSearchNoInteractiveHealthCheckMessage": "Nenhum indexador disponível com a Pesquisa interativa habilitada, o Sonarr não fornecerá nenhum resultado de pesquisa interativa", + "IndexerRssNoIndexersEnabledHealthCheckMessage": "Nenhum indexador disponível com sincronização de RSS ativada, o {appName} não obterá novos lançamentos automaticamente", + "IndexerSearchNoAutomaticHealthCheckMessage": "Nenhum indexador disponível com a pesquisa automática ativada, o {appName} não fornecerá nenhum resultado de pesquisa automática", + "IndexerSearchNoInteractiveHealthCheckMessage": "Nenhum indexador disponível com a Pesquisa interativa habilitada, o {appName} não fornecerá nenhum resultado de pesquisa interativa", "IndexerStatusAllUnavailableHealthCheckMessage": "Todos os indexadores estão indisponíveis devido a falhas", "IndexerStatusUnavailableHealthCheckMessage": "Indexadores indisponíveis devido a falhas: {0}", "Language": "Idioma", @@ -31,13 +31,13 @@ "QualityProfile": "Perfil de Qualidade", "RefreshSeries": "Atualizar Séries", "RemotePathMappingDockerFolderMissingHealthCheckMessage": "Você está usando o docker; o cliente de download {0} coloca os downloads em {1}, mas esse diretório parece não existir dentro do contêiner. Revise seus mapeamentos de caminho remoto e configurações de volume do contêiner.", - "RemotePathMappingDownloadPermissionsHealthCheckMessage": "O Sonarr pode ver, mas não acessar o episódio baixado {0}. Provável erro de permissão.", + "RemotePathMappingDownloadPermissionsHealthCheckMessage": "O {appName} pode ver, mas não acessar o episódio baixado {0}. Provável erro de permissão.", "RemotePathMappingFileRemovedHealthCheckMessage": "O arquivo {0} foi removido no meio do processamento.", - "RemotePathMappingFilesGenericPermissionsHealthCheckMessage": "Baixe os arquivos relatados do cliente {0} em {1}, mas o Sonarr não pode ver este diretório. Pode ser necessário ajustar as permissões da pasta.", + "RemotePathMappingFilesGenericPermissionsHealthCheckMessage": "Baixe os arquivos relatados do cliente {0} em {1}, mas o {appName} não pode ver este diretório. Pode ser necessário ajustar as permissões da pasta.", "RemotePathMappingFilesLocalWrongOSPathHealthCheckMessage": "O cliente de download local {0} relatou arquivos em {1}, mas este não é um caminho {2} válido. Revise as configurações do cliente de download.", - "RemotePathMappingFolderPermissionsHealthCheckMessage": "Sonarr pode ver, mas não acessar o diretório de download {0}. Provável erro de permissão.", - "RemotePathMappingGenericPermissionsHealthCheckMessage": "O cliente de download {0} coloca os downloads em {1}, mas o Sonarr não pode ver este diretório. Pode ser necessário ajustar as permissões da pasta.", - "RemotePathMappingImportFailedHealthCheckMessage": "Sonarr falhou ao importar (um) episódio(s). Verifique seus logs para obter detalhes.", + "RemotePathMappingFolderPermissionsHealthCheckMessage": "{appName} pode ver, mas não acessar o diretório de download {0}. Provável erro de permissão.", + "RemotePathMappingGenericPermissionsHealthCheckMessage": "O cliente de download {0} coloca os downloads em {1}, mas o {appName} não pode ver este diretório. Pode ser necessário ajustar as permissões da pasta.", + "RemotePathMappingImportFailedHealthCheckMessage": "{appName} falhou ao importar (um) episódio(s). Verifique seus logs para obter detalhes.", "RemotePathMappingLocalWrongOSPathHealthCheckMessage": "O cliente de download local {0} coloca os downloads em {1}, mas este não é um caminho {2} válido. Revise as configurações do cliente de download.", "RemotePathMappingRemoteDownloadClientHealthCheckMessage": "O cliente de download remoto {0} relatou arquivos em {1}, mas este diretório parece não existir. Provavelmente faltando mapeamento de caminho remoto.", "RemovedSeriesMultipleRemovedHealthCheckMessage": "A série {0} foi removida do TheTVDB", @@ -59,7 +59,7 @@ "AppDataLocationHealthCheckMessage": "A atualização não será possível para evitar a exclusão de AppData na atualização", "DownloadClientCheckUnableToCommunicateWithHealthCheckMessage": "Não é possível se comunicar com {0}.", "DownloadClientRootFolderHealthCheckMessage": "O cliente de download {0} coloca os downloads na pasta raiz {1}. Você não deve baixar para uma pasta raiz.", - "DownloadClientSortingHealthCheckMessage": "O cliente de download {0} tem classificação {1} habilitada para a categoria Sonarr. Você deve desativar a classificação em seu cliente de download para evitar problemas de importação.", + "DownloadClientSortingHealthCheckMessage": "O cliente de download {0} tem classificação {1} habilitada para a categoria {appName}. Você deve desativar a classificação em seu cliente de download para evitar problemas de importação.", "DownloadClientStatusSingleClientHealthCheckMessage": "Clientes de download indisponíveis devido a falhas: {0}", "EditSelectedIndexers": "Editar indexadores selecionados", "EditSeries": "Editar Série", @@ -79,7 +79,7 @@ "ProxyBadRequestHealthCheckMessage": "Falha ao testar o proxy. Código de estado: {0}", "ProxyFailedToTestHealthCheckMessage": "Falha ao testar o proxy: {0}", "ProxyResolveIpHealthCheckMessage": "Falha ao resolver o endereço IP do host de proxy configurado {0}", - "RecycleBinUnableToWriteHealthCheckMessage": "Não é possível gravar na pasta da lixeira configurada: {0}. Certifique-se de que este caminho exista e seja gravável pelo usuário executando o Sonarr", + "RecycleBinUnableToWriteHealthCheckMessage": "Não é possível gravar na pasta da lixeira configurada: {0}. Certifique-se de que este caminho exista e seja gravável pelo usuário executando o {appName}", "RemotePathMappingBadDockerPathHealthCheckMessage": "Você está usando o docker; cliente de download {0} coloca downloads em {1}, mas este não é um caminho {2} válido. Revise seus mapeamentos de caminho remoto e baixe as configurações do cliente.", "RemotePathMappingFilesBadDockerPathHealthCheckMessage": "Você está usando o docker; baixe os arquivos relatados do cliente {0} em {1}, mas este não é um caminho {2} válido. Revise seus mapeamentos de caminho remoto e baixe as configurações do cliente.", "RemotePathMappingFilesWrongOSPathHealthCheckMessage": "O cliente de download remoto {0} relatou arquivos em {1}, mas este não é um caminho {2} válido. Revise seus mapeamentos de caminho remoto e baixe as configurações do cliente.", @@ -231,7 +231,7 @@ "Duration": "Duração", "ErrorRestoringBackup": "Erro ao restaurar o backup", "Exception": "Exceção", - "ExternalUpdater": "O Sonarr está configurado para usar um mecanismo de atualização externo", + "ExternalUpdater": "O {appName} está configurado para usar um mecanismo de atualização externo", "FailedToFetchUpdates": "Falha ao buscar atualizações", "FailedToUpdateSettings": "Falha ao atualizar as configurações", "FeatureRequests": "Solicitações de recursos", @@ -243,7 +243,7 @@ "GeneralSettings": "Configurações Gerais", "Health": "Saúde", "HomePage": "Página Inicial", - "OnLatestVersion": "A versão mais recente do Sonarr já está instalada", + "OnLatestVersion": "A versão mais recente do {appName} já está instalada", "InstallLatest": "Instalar o mais recente", "Interval": "Intervalo", "IRC": "IRC", @@ -295,7 +295,7 @@ "TotalSpace": "Espaço Total", "Twitter": "Twitter", "UnableToLoadBackups": "Não foi possível carregar os backups", - "UnableToUpdateSonarrDirectly": "Incapaz de atualizar o Sonarr diretamente,", + "UnableToUpdateSonarrDirectly": "Incapaz de atualizar o {appName} diretamente,", "UpdaterLogFiles": "Arquivos de log do atualizador", "Uptime": "Tempo de atividade", "Wiki": "Wiki", @@ -303,9 +303,9 @@ "YesCancel": "Sim, Cancelar", "Reset": "Redefinir", "ResetDefinitionTitlesHelpText": "Redefinir títulos de definição, bem como valores", - "RestartReloadNote": "Observação: o Sonarr reiniciará automaticamente e recarregará a IU durante o processo de restauração.", + "RestartReloadNote": "Observação: o {appName} reiniciará automaticamente e recarregará a IU durante o processo de restauração.", "SkipRedownload": "Ignorar o Redownload", - "SkipRedownloadHelpText": "Impede que o Sonarr tente baixar uma versão alternativa para este item", + "SkipRedownloadHelpText": "Impede que o {appName} tente baixar uma versão alternativa para este item", "IRCLinkText": "#sonarr na Libera", "LiberaWebchat": "Libera Webchat", "All": "Todos", @@ -428,7 +428,7 @@ "Always": "Sempre", "AnalyseVideoFiles": "Analisar arquivos de vídeo", "Analytics": "Analítica", - "AnalyticsEnabledHelpText": "Envie informações anônimas de uso e erro para os servidores do Sonarr. Isso inclui informações sobre seu navegador, quais páginas do Sonarr WebUI você usa, relatórios de erros, bem como sistema operacional e versão de tempo de execução. Usaremos essas informações para priorizar recursos e correções de bugs.", + "AnalyticsEnabledHelpText": "Envie informações anônimas de uso e erro para os servidores do {appName}. Isso inclui informações sobre seu navegador, quais páginas do {appName} WebUI você usa, relatórios de erros, bem como sistema operacional e versão de tempo de execução. Usaremos essas informações para priorizar recursos e correções de bugs.", "AnimeEpisodeFormat": "Formato do Episódio de Anime", "ApiKey": "Chave API", "ApplicationURL": "URL do Aplicativo", @@ -442,7 +442,7 @@ "AutoTaggingLoadError": "Não foi possível carregar a marcação automática", "Automatic": "Automático", "AutomaticSearch": "Pesquisa Automática", - "BackupFolderHelpText": "Os caminhos relativos estarão no diretório AppData do Sonarr", + "BackupFolderHelpText": "Os caminhos relativos estarão no diretório AppData do {appName}", "BackupIntervalHelpText": "Intervalo entre backups automáticos", "BackupRetentionHelpText": "Backups automáticos anteriores ao período de retenção serão limpos automaticamente", "BackupsLoadError": "Não foi possível carregar os backups", @@ -450,7 +450,7 @@ "BindAddressHelpText": "Endereço IP válido, localhost ou '*' para todas as interfaces", "BlocklistLoadError": "Não foi possível carregar a lista de bloqueio", "Branch": "Ramificação", - "BranchUpdate": "Ramificação a ser usada para atualizar o Sonarr", + "BranchUpdate": "Ramificação a ser usada para atualizar o {appName}", "BranchUpdateMechanism": "Ramificação usada pelo mecanismo externo de atualização", "BrowserReloadRequired": "Necessário Recarregar o Navegador", "BuiltIn": "Embutido", @@ -478,7 +478,7 @@ "CloneProfile": "Clonar Perfil", "CollectionsLoadError": "Não foi possível carregar as coleções", "ColonReplacement": "Substituto para dois-pontos", - "ColonReplacementFormatHelpText": "Mude como o Sonarr lida com a substituição do dois-pontos", + "ColonReplacementFormatHelpText": "Mude como o {appName} lida com a substituição do dois-pontos", "CompletedDownloadHandling": "Gerenciamento de Downloads Completos", "Condition": "Condição", "ConditionUsingRegularExpressions": "Essa condição corresponde ao uso de expressões regulares. Observe que os caracteres `\\^$.|?*+()[{` têm significados especiais e precisam ser substituídos por um `\\`", @@ -486,7 +486,7 @@ "ConnectSettingsSummary": "Notificações, conexões com servidores/reprodutores de mídia e scripts personalizados", "Connections": "Conexões", "CopyToClipboard": "Copiar para Área de Transferência", - "CopyUsingHardlinksHelpTextWarning": "Ocasionalmente, os bloqueios de arquivo podem impedir a renomeação de arquivos que estão sendo propagados. Você pode desativar temporariamente a propagação e usar a função de renomeação do Sonarr como solução alternativa.", + "CopyUsingHardlinksHelpTextWarning": "Ocasionalmente, os bloqueios de arquivo podem impedir a renomeação de arquivos que estão sendo propagados. Você pode desativar temporariamente a propagação e usar a função de renomeação do {appName} como solução alternativa.", "CreateEmptySeriesFolders": "Criar Pastas de Séries Vazias", "CreateEmptySeriesFoldersHelpText": "Crie pastas de série ausentes durante a verificação de disco", "CreateGroup": "Criar Grupo", @@ -555,20 +555,20 @@ "EditRestriction": "Editar Restrição", "Enable": "Habilitar", "EnableAutomaticAdd": "Habilitar Adição Automática", - "EnableAutomaticAddHelpText": "Adicione séries desta lista ao Sonarr quando as sincronizações forem realizadas por meio da interface do usuário ou pelo Sonarr", - "EnableAutomaticSearchHelpText": "Será usado quando pesquisas automáticas forem realizadas por meio da interface do usuário ou pelo Sonarr", + "EnableAutomaticAddHelpText": "Adicione séries desta lista ao {appName} quando as sincronizações forem realizadas por meio da interface do usuário ou pelo {appName}", + "EnableAutomaticSearchHelpText": "Será usado quando pesquisas automáticas forem realizadas por meio da interface do usuário ou pelo {appName}", "EnableAutomaticSearchHelpTextWarning": "Será usado quando a pesquisa interativa for usada", "EnableCompletedDownloadHandlingHelpText": "Importar automaticamente downloads concluídos do cliente de download", "EnableHelpText": "Habilitar criação de arquivo de metadados para este tipo de metadados", "EnableInteractiveSearchHelpText": "Será usado quando a pesquisa interativa for usada", "EnableColorImpairedMode": "Habilitar Modo para Deficientes Visuais", "EnableInteractiveSearchHelpTextWarning": "A pesquisa não é compatível com este indexador", - "EnableMediaInfoHelpText": "Extraia informações de vídeo, como resolução, tempo de execução e informações de codec de arquivos. Isso requer que o Sonarr leia partes do arquivo que podem causar alta atividade no disco ou na rede durante as varreduras.", + "EnableMediaInfoHelpText": "Extraia informações de vídeo, como resolução, tempo de execução e informações de codec de arquivos. Isso requer que o {appName} leia partes do arquivo que podem causar alta atividade no disco ou na rede durante as varreduras.", "EnableMetadataHelpText": "Habilitar criação de arquivo de metadados para este tipo de metadados", "EnableProfile": "Habilitar Perfil", "EnableProfileHelpText": "Marque para habilitar o perfil de lançamento", "EnableRss": "Habilitar RSS", - "EnableRssHelpText": "Será usado quando o Sonarr procurar periodicamente lançamentos via RSS Sync", + "EnableRssHelpText": "Será usado quando o {appName} procurar periodicamente lançamentos via RSS Sync", "EnableSsl": "Habilitar SSL", "EnableSslHelpText": "Requer reinicialização em execução como administrador para entrar em vigor", "EpisodeNaming": "Nomenclatura do Episódio", @@ -601,7 +601,7 @@ "ImportListExclusionsLoadError": "Não foi possível carregar as exclusões da lista de importação", "ImportListSettings": "Configurações da Importação das Listas", "ImportListsLoadError": "Não foi possível carregar listas de importação", - "ImportListsSettingsSummary": "Importar de outra instância Sonarr ou listas Trakt e gerenciar exclusões de lista", + "ImportListsSettingsSummary": "Importar de outra instância {appName} ou listas Trakt e gerenciar exclusões de lista", "ImportScriptPath": "Importar Caminho do Script", "ImportScriptPathHelpText": "O caminho para o script a ser usado para importar", "ImportUsingScript": "Importar Usando o Script", @@ -613,7 +613,7 @@ "IndexerDownloadClientHelpText": "Especifique qual cliente de download é usado para baixar deste indexador", "IndexerOptionsLoadError": "Não foi possível carregar as opções do indexador", "IndexerPriority": "Prioridade do Indexador", - "IndexerPriorityHelpText": "Prioridade do indexador de 1 (mais alta) a 50 (mais baixa). Padrão: 25. Usado ao obter lançamentos como desempate para lançamentos iguais, o Sonarr ainda usará todos os indexadores habilitados para sincronização e pesquisa de RSS", + "IndexerPriorityHelpText": "Prioridade do indexador de 1 (mais alta) a 50 (mais baixa). Padrão: 25. Usado ao obter lançamentos como desempate para lançamentos iguais, o {appName} ainda usará todos os indexadores habilitados para sincronização e pesquisa de RSS", "IndexerSettings": "Configurações do Indexador", "IndexersLoadError": "Não foi possível carregar os indexadores", "IndexersSettingsSummary": "Indexadores e opções de indexador", @@ -653,7 +653,7 @@ "MetadataSettings": "Configurações de Metadados", "MetadataSettingsSummary": "Crie arquivos de metadados quando os episódios são importados ou as séries são atualizadas", "MetadataSourceSettings": "Configurações de Fontes de Metadados", - "MetadataSourceSettingsSummary": "Informações sobre onde Sonarr obtém informações sobre séries e episódios", + "MetadataSourceSettingsSummary": "Informações sobre onde {appName} obtém informações sobre séries e episódios", "Min": "Mín.", "MinimumAge": "Idade Miníma", "MinimumAgeHelpText": "Somente Usenet: Idade mínima em minutos dos NZBs antes de serem capturados. Use isso para dar aos novos lançamentos tempo para se propagar para seu provedor usenet.", @@ -700,7 +700,7 @@ "OnlyTorrent": "Só Torrent", "OnlyUsenet": "Só Usenet", "OpenBrowserOnStart": "Abrir navegador ao iniciar", - "OpenBrowserOnStartHelpText": " Abra um navegador da Web e navegue até a página inicial do Sonarr no início do aplicativo.", + "OpenBrowserOnStartHelpText": " Abra um navegador da Web e navegue até a página inicial do {appName} no início do aplicativo.", "OptionalName": "Nome opcional", "Original": "Original", "Other": "Outro", @@ -744,11 +744,11 @@ "RedownloadFailed": "Redownload falhou", "AbsoluteEpisodeNumber": "Número Absoluto do Episódio", "AddAutoTagError": "Não foi possível adicionar uma nova tag automática. Tente novamente.", - "AnalyseVideoFilesHelpText": "Extraia informações de vídeo, como resolução, tempo de execução e informações de codec de arquivos. Isso requer que o Sonarr leia partes do arquivo que podem causar alta atividade no disco ou na rede durante as varreduras.", + "AnalyseVideoFilesHelpText": "Extraia informações de vídeo, como resolução, tempo de execução e informações de codec de arquivos. Isso requer que o {appName} leia partes do arquivo que podem causar alta atividade no disco ou na rede durante as varreduras.", "AuthenticationRequiredHelpText": "Altere para quais solicitações a autenticação é necessária. Não mude a menos que você entenda os riscos.", "AuthenticationRequiredWarning": "Para evitar o acesso remoto sem autenticação, {appName} agora exige que a autenticação esteja habilitada. Opcionalmente, você pode desabilitar a autenticação de endereços locais.", - "CopyUsingHardlinksHelpText": "Os links rígidos permitem que o Sonarr importe torrents de propagação para a pasta da série sem ocupar espaço extra em disco ou copiar todo o conteúdo do arquivo. Links rígidos só funcionarão se a origem e o destino estiverem no mesmo volume", - "CustomFormatHelpText": "O Sonarr pontua cada lançamento usando a soma das pontuações para corresponder aos formatos personalizados. Se um novo lançamento melhorar a pontuação, com a mesma, ou melhor, qualidade, o Sonarr o baixará.", + "CopyUsingHardlinksHelpText": "Os links rígidos permitem que o {appName} importe torrents de propagação para a pasta da série sem ocupar espaço extra em disco ou copiar todo o conteúdo do arquivo. Links rígidos só funcionarão se a origem e o destino estiverem no mesmo volume", + "CustomFormatHelpText": "O {appName} pontua cada lançamento usando a soma das pontuações para corresponder aos formatos personalizados. Se um novo lançamento melhorar a pontuação, com a mesma, ou melhor, qualidade, o {appName} o baixará.", "DelayProfileProtocol": "Protocolo: {preferredProtocol}", "DeleteDelayProfileMessageText": "Tem certeza de que deseja excluir este perfil de atraso?", "DeleteImportListMessageText": "Tem certeza de que deseja excluir a lista '{name}'?", @@ -793,11 +793,11 @@ "Reorder": "Reordenar", "Repeat": "Repetir", "ReplaceIllegalCharacters": "Substituir Caracteres Ilegais", - "ReplaceIllegalCharactersHelpText": "Substituir caracteres ilegais. Se desmarcado, o Sonarr irá removê-los", + "ReplaceIllegalCharactersHelpText": "Substituir caracteres ilegais. Se desmarcado, o {appName} irá removê-los", "ReplaceWithDash": "Substituir por Traço", "ReplaceWithSpaceDash": "Substituir por Espaço e Traço", "RescanAfterRefreshHelpText": "Verifique novamente a pasta da série após atualizar a série", - "RescanAfterRefreshHelpTextWarning": "O Sonarr não detectará automaticamente as alterações nos arquivos quando não estiver definido como 'Sempre'", + "RescanAfterRefreshHelpTextWarning": "O {appName} não detectará automaticamente as alterações nos arquivos quando não estiver definido como 'Sempre'", "RescanSeriesFolderAfterRefresh": "Verificar novamente a pasta da série após a atualização", "ResetAPIKey": "Redefinir chave de API", "ResetAPIKeyMessageText": "Tem certeza de que deseja redefinir sua chave de API?", @@ -805,9 +805,9 @@ "RestartLater": "Vou reiniciar mais tarde", "RestartNow": "Reiniciar Agora", "RestartRequiredHelpTextWarning": "Requer reinicialização para entrar em vigor", - "RestartRequiredToApplyChanges": "O Sonarr requer uma reinicialização para aplicar as alterações. Deseja reiniciar agora?", - "RestartRequiredWindowsService": "Dependendo de qual usuário está executando o serviço Sonarr, pode ser necessário reiniciar o Sonarr como administrador uma vez antes que o serviço seja iniciado automaticamente.", - "RestartSonarr": "Reiniciar Sonarr", + "RestartRequiredToApplyChanges": "O {appName} requer uma reinicialização para aplicar as alterações. Deseja reiniciar agora?", + "RestartRequiredWindowsService": "Dependendo de qual usuário está executando o serviço {appName}, pode ser necessário reiniciar o {appName} como administrador uma vez antes que o serviço seja iniciado automaticamente.", + "RestartSonarr": "Reiniciar {appName}", "RestrictionsLoadError": "Não foi possível carregar as Restrições", "Retention": "Retenção", "RetentionHelpText": "Somente Usenet: defina como zero para definir a retenção ilimitada", @@ -844,12 +844,12 @@ "SingleEpisode": "Episódio Único", "SizeLimit": "Limite de Tamanho", "SkipFreeSpaceCheck": "Ignorar verificação de espaço livre", - "SkipFreeSpaceCheckWhenImportingHelpText": "Use quando o Sonarr não conseguir detectar espaço livre na pasta raiz da série", + "SkipFreeSpaceCheckWhenImportingHelpText": "Use quando o {appName} não conseguir detectar espaço livre na pasta raiz da série", "SmartReplace": "Substituição Inteligente", "SmartReplaceHint": "Traço ou Espaço e Traço, dependendo do nome", "Socks4": "Socks4", "Socks5": "Socks5 (Suporte à TOR)", - "SonarrTags": "Tags do Sonarr", + "SonarrTags": "Tags do {appName}", "Space": "Espaço", "SpecialsFolderFormat": "Formato da Pasta para Especiais", "SslCertPassword": "Senha do Certificado SSL", @@ -860,14 +860,14 @@ "StandardEpisodeFormat": "Formato do Episódio Padrão", "Style": "Estilo", "Sunday": "Domingo", - "SupportedAutoTaggingProperties": "O Sonarr suporta as seguintes propriedades para regras de marcação automática", - "SupportedCustomConditions": "O Sonarr oferece suporte a condições personalizadas nas propriedades de lançamento abaixo.", - "SupportedDownloadClients": "O Sonarr suporta muitos clientes populares de download de torrent e usenet.", + "SupportedAutoTaggingProperties": "O {appName} suporta as seguintes propriedades para regras de marcação automática", + "SupportedCustomConditions": "O {appName} oferece suporte a condições personalizadas nas propriedades de lançamento abaixo.", + "SupportedDownloadClients": "O {appName} suporta muitos clientes populares de download de torrent e usenet.", "SupportedDownloadClientsMoreInfo": "Para obter mais informações sobre os clientes de download individuais, clique nos botões de mais informações.", "SupportedImportListsMoreInfo": "Para obter mais informações sobre as listas de importação individuais, clique nos botões de mais informações.", - "SupportedIndexers": "O Sonarr suporta qualquer indexador que use o padrão Newznab, bem como outros indexadores listados abaixo.", + "SupportedIndexers": "O {appName} suporta qualquer indexador que use o padrão Newznab, bem como outros indexadores listados abaixo.", "SupportedIndexersMoreInfo": "Para obter mais informações sobre os indexadores individuais, clique nos botões de mais informações.", - "SupportedLists": "O Sonarr oferece suporte a várias listas para importar séries para o banco de dados.", + "SupportedLists": "O {appName} oferece suporte a várias listas para importar séries para o banco de dados.", "TagCannotBeDeletedWhileInUse": "A tag não pode ser excluída durante o uso", "TagDetails": "Detalhes da Tag - {label}", "TagIsNotUsedAndCanBeDeleted": "A tag não é usada e pode ser excluída", @@ -890,7 +890,7 @@ "TypeOfList": "{typeOfList} Lista", "Ui": "IU", "UiLanguage": "Idioma da UI", - "UiLanguageHelpText": "Idioma que o Sonarr usará para interface do usuário", + "UiLanguageHelpText": "Idioma que o {appName} usará para interface do usuário", "UiSettings": "Configurações da UI", "UiSettingsSummary": "Opções de calendário, data e cores para deficientes visuais", "Underscore": "Sublinhar", @@ -900,11 +900,11 @@ "UnsavedChanges": "Alterações Não Salvas", "UpdateAutomaticallyHelpText": "Baixe e instale atualizações automaticamente. Você ainda poderá instalar a partir do Sistema: Atualizações", "UpdateMechanismHelpText": "Use o atualizador integrado do {appName} ou um script", - "UpdateSonarrDirectlyLoadError": "Incapaz de atualizar o Sonarr diretamente,", + "UpdateSonarrDirectlyLoadError": "Incapaz de atualizar o {appName} diretamente,", "UpdateUiNotWritableHealthCheckMessage": "Não é possível instalar a atualização porque a pasta de IU '{0}' não pode ser salva pelo usuário '{1}'.", "UpgradeUntil": "Atualizar Até", "UpgradeUntilCustomFormatScore": "Atualizar até pontuação de formato personalizado", - "UpgradeUntilHelpText": "Quando essa qualidade for atingida, o Sonarr não fará mais download de episódios", + "UpgradeUntilHelpText": "Quando essa qualidade for atingida, o {appName} não fará mais download de episódios", "UpgradeUntilThisQualityIsMetOrExceeded": "Atualize até que essa qualidade seja atendida ou excedida", "UpgradesAllowed": "Atualizações Permitidas", "UpgradesAllowedHelpText": "se as qualidades desativadas não forem atualizadas", @@ -921,9 +921,9 @@ "UtcAirDate": "Data de Exibição UTC", "WeekColumnHeader": "Cabeçalho da Coluna da Semana", "WeekColumnHeaderHelpText": "Mostrado acima de cada coluna quando a semana é a exibição ativa", - "RemotePathMappingLocalPathHelpText": "Caminho que o Sonarr deve usar para acessar o caminho remoto localmente", + "RemotePathMappingLocalPathHelpText": "Caminho que o {appName} deve usar para acessar o caminho remoto localmente", "RemoveCompletedDownloadsHelpText": "Remover downloads importados do histórico do cliente de download", - "RenameEpisodesHelpText": "O Sonarr usará o nome do arquivo existente se a renomeação estiver desativada", + "RenameEpisodesHelpText": "O {appName} usará o nome do arquivo existente se a renomeação estiver desativada", "ReplaceWithSpaceDashSpace": "Substituir com Espaço, Traço e Espaço", "RequiredHelpText": "Esta condição {implementationName} deve corresponder para que o formato personalizado seja aplicado. Caso contrário, uma única correspondência {implementationName} é suficiente.", "SetPermissionsLinuxHelpText": "O chmod deve ser executado quando os arquivos são importados/renomeados?", @@ -931,9 +931,9 @@ "SupportedListsMoreInfo": "Para obter mais informações sobre as listas individuais, clique nos botões de mais informações.", "ThemeHelpText": "Alterar o tema da interface do usuário do aplicativo, o tema 'Auto' usará o tema do sistema operacional para definir o modo Claro ou Escuro. Inspirado por Theme.Park", "UiSettingsLoadError": "Não foi possível carregar as configurações da UI", - "UnmonitorDeletedEpisodesHelpText": "Os episódios excluídos do disco são deixados de ser monitorados automaticamente no Sonarr", + "UnmonitorDeletedEpisodesHelpText": "Os episódios excluídos do disco são deixados de ser monitorados automaticamente no {appName}", "UpdateScriptPathHelpText": "Caminho para um script personalizado que usa um pacote de atualização extraído e lida com o restante do processo de atualização", - "UpgradeUntilCustomFormatScoreHelpText": "Assim que essa pontuação de formato personalizado for alcançada, o Sonarr não baixará mais lançamentos de episódios", + "UpgradeUntilCustomFormatScoreHelpText": "Assim que essa pontuação de formato personalizado for alcançada, o {appName} não baixará mais lançamentos de episódios", "UseHardlinksInsteadOfCopy": "Usar links rígidos ao invés de Copiar", "VisitTheWikiForMoreDetails": "Visite o wiki para mais detalhes: ", "WantMoreControlAddACustomFormat": "Quer mais controle sobre quais downloads são preferidos? Adicione um [Formato Personalizado](/settings/customformats)", @@ -987,7 +987,7 @@ "AddNewSeriesRootFolderHelpText": "A subpasta '{folder}' será criada automaticamente", "AnimeTypeDescription": "Episódios lançados usando um número de episódio absoluto", "DailyTypeDescription": "Episódios lançados diariamente ou com menos frequência que usam ano-mês-dia (2023-08-04)", - "LibraryImportTipsUseRootFolder": "Aponte o Sonarr para a pasta que contém todos os seus programas de TV, não um específico. Por exemplo. '`{goodFolderExample}`' e não '`{badFolderExample}`'. Além disso, cada série deve estar em sua própria pasta dentro da pasta raiz/biblioteca.", + "LibraryImportTipsUseRootFolder": "Aponte o {appName} para a pasta que contém todos os seus programas de TV, não um específico. Por exemplo. '`{goodFolderExample}`' e não '`{badFolderExample}`'. Além disso, cada série deve estar em sua própria pasta dentro da pasta raiz/biblioteca.", "MonitorExistingEpisodesDescription": "Monitorar os episódios que possuem arquivos ou ainda não foram exibidos", "MonitorLatestSeasonDescription": "Monitora todos os episódios da última temporada que foram ao ar nos últimos 90 dias e todas as temporadas futuras", "NoSeriesHaveBeenAdded": "Você ainda não adicionou nenhuma série. Deseja importar algumas ou todas as suas séries primeiro?", @@ -998,7 +998,7 @@ "Today": "Hoje", "AgeWhenGrabbed": "Idade (quando baixado)", "DelayingDownloadUntil": "Atrasando o download até {date} às {time}", - "DeletedReasonMissingFromDisk": "O Sonarr não conseguiu encontrar o arquivo no disco, então o arquivo foi desvinculado do episódio no banco de dados", + "DeletedReasonMissingFromDisk": "O {appName} não conseguiu encontrar o arquivo no disco, então o arquivo foi desvinculado do episódio no banco de dados", "DeletedReasonManual": "O arquivo foi excluído por meio da UI", "DownloadFailed": "Download Falhou", "DestinationRelativePath": "Caminho Relativo de Destino", @@ -1122,7 +1122,7 @@ "FilterEpisodesPlaceholder": "Filtrar episódios por título ou número", "FilterIsAfter": "está depois", "Grab": "Obter", - "GrabReleaseMessageText": "Sonarr não conseguiu determinar para qual série e episódio era este lançamento. O Sonarr pode não conseguir importar automaticamente esta versão. Você quer pegar '{title}'?", + "GrabReleaseMessageText": "{appName} não conseguiu determinar para qual série e episódio era este lançamento. O {appName} pode não conseguir importar automaticamente esta versão. Você quer pegar '{title}'?", "ICalFeed": "Feed do iCal", "ICalLink": "Link do iCal", "InteractiveImportLoadError": "Não foi possível carregar itens de importação manual", @@ -1171,7 +1171,7 @@ "ExistingSeries": "Série Existente", "FailedToLoadCustomFiltersFromApi": "Falha ao carregar filtros personalizados da API", "FailedToLoadSeriesFromApi": "Falha ao carregar a série da API", - "FailedToLoadSonarr": "Falha ao carregar Sonarr", + "FailedToLoadSonarr": "Falha ao carregar {appName}", "FailedToLoadSystemStatusFromApi": "Falha ao carregar o status do sistema da API", "FailedToLoadTagsFromApi": "Falha ao carregar tags da API", "FailedToLoadTranslationsFromApi": "Falha ao carregar as traduções da API", @@ -1322,11 +1322,11 @@ "ImdbId": "ID do IMDb", "Port": "Porta", "ShowUnknownSeriesItems": "Mostrar Itens de Séries Desconhecidas", - "ShowUnknownSeriesItemsHelpText": "Mostrar itens sem uma série na fila, isso pode incluir séries, filmes removidos ou qualquer outra coisa na categoria do Sonarr", + "ShowUnknownSeriesItemsHelpText": "Mostrar itens sem uma série na fila, isso pode incluir séries, filmes removidos ou qualquer outra coisa na categoria do {appName}", "Test": "Teste", "Level": "Nível", "AddListExclusion": "Adicionar à Lista de Exclusão", - "AddListExclusionHelpText": "Impedir que séries sejam adicionadas ao Sonarr por listas", + "AddListExclusionHelpText": "Impedir que séries sejam adicionadas ao {appName} por listas", "EditSeriesModalHeader": "Editar - {title}", "EditSelectedSeries": "Editar Séries Selecionadas", "HideEpisodes": "Ocultar episódios", @@ -1463,7 +1463,7 @@ "SearchForAllMissingConfirmationCount": "Tem certeza de que deseja pesquisar todos os episódios ausentes de {totalRecords}?", "SearchSelected": "Pesquisar Selecionado", "CutoffUnmetLoadError": "Erro ao carregar itens de corte não atendidos", - "MassSearchCancelWarning": "Isso não pode ser cancelado depois de iniciado sem reiniciar o Sonarr ou desabilitar todos os seus indexadores.", + "MassSearchCancelWarning": "Isso não pode ser cancelado depois de iniciado sem reiniciar o {appName} ou desabilitar todos os seus indexadores.", "SearchForAllMissing": "Pesquisar por todos os episódios ausentes", "SearchForCutoffUnmet": "Pesquise todos os episódios que o corte não foi atingido", "SearchForCutoffUnmetConfirmationCount": "Tem certeza de que deseja pesquisar todos os episódios de {totalRecords} corte não atingido?", diff --git a/src/NzbDrone.Core/Localization/Core/ro.json b/src/NzbDrone.Core/Localization/Core/ro.json index 2d9e25fd2..25fecf030 100644 --- a/src/NzbDrone.Core/Localization/Core/ro.json +++ b/src/NzbDrone.Core/Localization/Core/ro.json @@ -125,12 +125,12 @@ "AddQualityProfileError": "Imposibil de adăugat un nou profil de calitate, încercați din nou.", "AnalyseVideoFiles": "Analizați fișierele video", "Analytics": "Statistici", - "AnalyticsEnabledHelpText": "Trimiteți informații anonime privind utilizarea și erorile către serverele Sonarr. Aceasta include informații despre browserul dvs., ce pagini WebUI Sonarr utilizați, raportarea erorilor, precum și sistemul de operare și versiunea de execuție. Vom folosi aceste informații pentru a acorda prioritate caracteristicilor și remedierilor de erori.", + "AnalyticsEnabledHelpText": "Trimiteți informații anonime privind utilizarea și erorile către serverele {appName}. Aceasta include informații despre browserul dvs., ce pagini WebUI {appName} utilizați, raportarea erorilor, precum și sistemul de operare și versiunea de execuție. Vom folosi aceste informații pentru a acorda prioritate caracteristicilor și remedierilor de erori.", "ApiKey": "Cheie API", "ApplicationURL": "URL aplicație", "AuthBasic": "Basic (fereastră pop-up browser)", "AuthForm": "Formulare (Pagina de autentificare)", - "AuthenticationMethodHelpText": "Solicitați nume utilizator și parola pentru a accesa Sonarr", + "AuthenticationMethodHelpText": "Solicitați nume utilizator și parola pentru a accesa {appName}", "AuthenticationRequired": "Autentificare necesara", "Authentication": "", "AddNewSeriesError": "Nu s-au putut încărca rezultatele căutării, încercați din nou.", diff --git a/src/NzbDrone.Core/Localization/Core/ru.json b/src/NzbDrone.Core/Localization/Core/ru.json index b071eb0b4..a7a0d0c80 100644 --- a/src/NzbDrone.Core/Localization/Core/ru.json +++ b/src/NzbDrone.Core/Localization/Core/ru.json @@ -1,8 +1,8 @@ { "ApiKeyValidationHealthCheckMessage": "Пожалуйста, обновите свой ключ API, чтобы он был длиной не менее {0} символов. Вы можете сделать это через настройки или файл конфигурации", - "DownloadClientSortingHealthCheckMessage": "В клиенте загрузки {0} включена сортировка {1} для категории Sonarr. Вам следует отключить сортировку в вашем клиенте загрузки, чтобы избежать проблем с импортом.", + "DownloadClientSortingHealthCheckMessage": "В клиенте загрузки {0} включена сортировка {1} для категории {appName}. Вам следует отключить сортировку в вашем клиенте загрузки, чтобы избежать проблем с импортом.", "IndexerJackettAllHealthCheckMessage": "Используется не поддерживаемый в Jackett конечный параметр 'all' в индексаторе: {0}", - "IndexerSearchNoAutomaticHealthCheckMessage": "Нет доступных индексаторов с включенным автоматическим поиском, Sonarr не будет предоставлять результаты автоматического поиска", + "IndexerSearchNoAutomaticHealthCheckMessage": "Нет доступных индексаторов с включенным автоматическим поиском, {appName} не будет предоставлять результаты автоматического поиска", "Added": "Добавлено", "AppDataLocationHealthCheckMessage": "Обновление будет не возможно, во избежание удаления данных программы во время обновления", "ApplyChanges": "Применить изменения", @@ -28,7 +28,7 @@ "IndexerLongTermStatusAllUnavailableHealthCheckMessage": "Все индексаторы недоступны из-за ошибок за последние 6 часов", "IndexerLongTermStatusUnavailableHealthCheckMessage": "Все индексаторы недоступны из-за ошибок за последние 6 часов: {0}", "IndexerRssNoIndexersAvailableHealthCheckMessage": "Все RSS индексаторы временно выключены из-за ошибок", - "IndexerRssNoIndexersEnabledHealthCheckMessage": "Нет доступных индексаторов с включенной синхронизацией RSS, Sonarr не будет автоматически получать новые выпуски", + "IndexerRssNoIndexersEnabledHealthCheckMessage": "Нет доступных индексаторов с включенной синхронизацией RSS, {appName} не будет автоматически получать новые выпуски", "IndexerSearchNoAvailableIndexersHealthCheckMessage": "Все индексаторы с возможностью поиска временно выключены из-за ошибок", "DeleteSelectedImportListsMessageText": "Вы уверены, что хотите удалить {count} выбранных списков импорта?", "DeleteSelectedIndexersMessageText": "Вы уверены, что хотите удалить {count} выбранных индексатора?", diff --git a/src/NzbDrone.Core/Localization/Core/zh_CN.json b/src/NzbDrone.Core/Localization/Core/zh_CN.json index b9095865b..54b2811cb 100644 --- a/src/NzbDrone.Core/Localization/Core/zh_CN.json +++ b/src/NzbDrone.Core/Localization/Core/zh_CN.json @@ -65,7 +65,7 @@ "DownloadClientCheckNoneAvailableHealthCheckMessage": "无可用的下载客户端", "DownloadClientCheckUnableToCommunicateWithHealthCheckMessage": "无法与{0}进行通讯。", "DownloadClientRootFolderHealthCheckMessage": "下载客户端{0}将下载内容放在根文件夹{1}中。您不应该下载到根文件夹。", - "DownloadClientSortingHealthCheckMessage": "下载客户端{0}已为Sonarr的类别启用{1}排序。您应该在下载客户端中禁用排序,以避免导入问题。", + "DownloadClientSortingHealthCheckMessage": "下载客户端{0}已为{appName}的类别启用{1}排序。您应该在下载客户端中禁用排序,以避免导入问题。", "DownloadClientStatusAllClientHealthCheckMessage": "所有下载客户端都不可用", "DownloadClientStatusSingleClientHealthCheckMessage": "所有下载客户端都不可用:{0}", "EditSeries": "编辑剧集", @@ -80,8 +80,8 @@ "IndexerJackettAllHealthCheckMessage": "使用 Jackett 不受支持的“全部”终点的索引器:{0}", "IndexerLongTermStatusUnavailableHealthCheckMessage": "由于故障6小时,下列索引器都已不可用:{0}", "IndexerRssNoIndexersAvailableHealthCheckMessage": "由于最近的索引器错误,所有支持rss的索引器暂时不可用", - "IndexerRssNoIndexersEnabledHealthCheckMessage": "没有启用RSS同步的索引器,Sonarr不会自动抓取新版本", - "IndexerSearchNoAutomaticHealthCheckMessage": "没有启用自动搜索的索引器,Sonarr不会提供任何自动搜索结果", + "IndexerRssNoIndexersEnabledHealthCheckMessage": "没有启用RSS同步的索引器,{appName}不会自动抓取新版本", + "IndexerSearchNoAutomaticHealthCheckMessage": "没有启用自动搜索的索引器,{appName}不会提供任何自动搜索结果", "IndexerStatusAllUnavailableHealthCheckMessage": "所有搜刮器都因错误不可用", "MountHealthCheckMessage": "挂载的媒体路径是只读的: ", "Network": "网络", @@ -90,16 +90,16 @@ "OriginalLanguage": "原语言", "Priority": "优先级", "ProxyBadRequestHealthCheckMessage": "测试代理失败。状态代码:{0}", - "RecycleBinUnableToWriteHealthCheckMessage": "配置文件夹:{0}无法写入,检查此路径是否存在,并且是否可由Sonarr写入", + "RecycleBinUnableToWriteHealthCheckMessage": "配置文件夹:{0}无法写入,检查此路径是否存在,并且是否可由{appName}写入", "QualityProfile": "质量配置", "RefreshSeries": "刷新节目", "RemotePathMappingBadDockerPathHealthCheckMessage": "您正在使用docker;下载客户端{0}下载目录为{1},但这不是有效的{2}路径。查看Docker路径映射并为下载客户端重新配置。", - "RemotePathMappingDownloadPermissionsHealthCheckMessage": "Sonarr已存在剧集目录{0}但无法访问。可能是权限错误。", + "RemotePathMappingDownloadPermissionsHealthCheckMessage": "{appName}已存在剧集目录{0}但无法访问。可能是权限错误。", "Mode": "模式", "MoreInfo": "更多信息", "New": "新的", "NoUpdatesAreAvailable": "无可用更新", - "OnLatestVersion": "已安装最新版的Sonarr", + "OnLatestVersion": "已安装最新版的{appName}", "NextExecution": "下一次执行", "NoBackupsAreAvailable": "无备份可用", "NoEventsFound": "无事件", @@ -222,7 +222,7 @@ "DeleteBackupMessageText": "您确定要删除备份 '{name}' 吗?", "EnableAutomaticSearch": "启用自动搜索", "EpisodeAirDate": "剧集播出日期", - "IndexerSearchNoInteractiveHealthCheckMessage": "没有启用交互式搜索的索引器,Sonarr将不提供任何交互式搜索结果", + "IndexerSearchNoInteractiveHealthCheckMessage": "没有启用交互式搜索的索引器,{appName}将不提供任何交互式搜索结果", "ProxyFailedToTestHealthCheckMessage": "测试代理失败: {0}", "About": "关于", "Actions": "动作", @@ -236,7 +236,7 @@ "Clear": "清除", "CountDownloadClientsSelected": "已选择 {count} 个下载客户端", "Exception": "例外", - "ExternalUpdater": "Sonarr配置为使用外部更新机制", + "ExternalUpdater": "{appName}配置为使用外部更新机制", "FailedToFetchUpdates": "无法获取更新", "EnableInteractiveSearch": "启用手动搜索", "EpisodeInfo": "剧集信息", @@ -274,25 +274,25 @@ "Tags": "标签", "Series": "节目", "ImportMechanismEnableCompletedDownloadHandlingIfPossibleHealthCheckMessage": "如果可能,在下载完成后自动处理", - "RemotePathMappingGenericPermissionsHealthCheckMessage": "下载客户端{0}将文件下载在{1}中,但Sonarr无法找到此目录。您可能需要调整文件夹的权限。", + "RemotePathMappingGenericPermissionsHealthCheckMessage": "下载客户端{0}将文件下载在{1}中,但{appName}无法找到此目录。您可能需要调整文件夹的权限。", "RemotePathMappingLocalWrongOSPathHealthCheckMessage": "本地下载客户端{0}将文件下载在{1}中,但这不是有效的{2}路径。查看您的下载客户端设置。", "RemotePathMappingRemoteDownloadClientHealthCheckMessage": "远程下载客户端{0}报告了{1}中的文件,但此目录似乎不存在。可能缺少远程路径映射。", - "IRCLinkText": "#Libera上的Sonarr", + "IRCLinkText": "#Libera上的{appName}", "LiberaWebchat": "Libera聊天", "RemotePathMappingFilesBadDockerPathHealthCheckMessage": "您正在使用Docker;下载客户端{0}报告了{1}中的文件,但这不是有效的{2}中的路径。查看Docker路径映射并更新下载客户端设置。", - "RemotePathMappingFilesGenericPermissionsHealthCheckMessage": "下载客户端{0}报告的文件在{1},但Sonarr无法查看此目录。您可能需要调整文件夹的权限。", + "RemotePathMappingFilesGenericPermissionsHealthCheckMessage": "下载客户端{0}报告的文件在{1},但{appName}无法查看此目录。您可能需要调整文件夹的权限。", "RemovedFromTaskQueue": "从任务队列中删除", "RemotePathMappingFilesLocalWrongOSPathHealthCheckMessage": "本地下载客户端{0}报告了{1}中的文件,但这不是有效的{2}路径。查看您的下载客户端设置。", "RemotePathMappingFilesWrongOSPathHealthCheckMessage": "远程下载客户端{0}报告了{1}中的文件,但这不是有效的{2}路径。查看远程路径映射并更新下载客户端设置。", "Restart": "重启", - "RestartReloadNote": "注意:Sonarr将在恢复过程中自动重启并重新加载UI。", + "RestartReloadNote": "注意:{appName}将在恢复过程中自动重启并重新加载UI。", "Restore": "恢复", "RootFolder": "根目录", "RemoveSelectedItems": "删除所选项目", "RemovedSeriesSingleRemovedHealthCheckMessage": "节目{0}已从TVDB中删除", "RootFolderMissingHealthCheckMessage": "缺少根目录: {0}", "RootFolderMultipleMissingHealthCheckMessage": "多个根目录缺失:{0}", - "SkipRedownloadHelpText": "阻止Sonarr尝试下载此项目的替代版本", + "SkipRedownloadHelpText": "阻止{appName}尝试下载此项目的替代版本", "Tasks": "任务", "Wanted": "想要的", "Yes": "确定", @@ -301,14 +301,14 @@ "RemoveCompleted": "移除已完成", "RemoveFailed": "删除失败", "RemovedSeriesMultipleRemovedHealthCheckMessage": "已从TVDB中删除节目{0}", - "RemotePathMappingFolderPermissionsHealthCheckMessage": "下载目录{0}已存在但Sonarr无法访问。可能是权限错误。", + "RemotePathMappingFolderPermissionsHealthCheckMessage": "下载目录{0}已存在但{appName}无法访问。可能是权限错误。", "RemovingTag": "移除标签", "RemotePathMappingLocalFolderMissingHealthCheckMessage": "远程下载客户端{0}将文件下载在{1}中,但此目录似乎不存在。可能缺少或不正确的远程路径映射。", "Replace": "替换", "Repack": "重新打包", "Version": "版本", "Special": "特色", - "RemotePathMappingImportFailedHealthCheckMessage": "Sonarr无法导入剧集。查看日志以了解详细信息。", + "RemotePathMappingImportFailedHealthCheckMessage": "{appName}无法导入剧集。查看日志以了解详细信息。", "RemotePathMappingWrongOSPathHealthCheckMessage": "远程下载客户端{0}将文件下载在{1}中,但这不是有效的{2}路径。查看远程路径映射并更新下载客户端设置。", "RemoveFromDownloadClientHelpTextWarning": "移除操作会从下载客户端中删除任务和已下载文件。", "Renamed": "已重命名", @@ -357,7 +357,7 @@ "SeriesEditor": "节目编辑", "Twitter": "Twitter", "UnableToLoadBackups": "无法加载备份", - "UnableToUpdateSonarrDirectly": "无法直接更新Sonarr,", + "UnableToUpdateSonarrDirectly": "无法直接更新{appName},", "Unmonitored": "未监控", "TheLogLevelDefault": "默认的日志等级为“信息”,可以在[常规设置](/settings/general)中修改", "Time": "时间", @@ -413,7 +413,7 @@ "AutomaticUpdatesDisabledDocker": "不支持在使用 Docker 容器时直接升级。你需要升级 {appName} 容器镜像或使用脚本(script)", "BackupIntervalHelpText": "自动备份时间间隔", "Branch": "分支", - "BranchUpdate": "更新Sonarr的分支", + "BranchUpdate": "更新{appName}的分支", "BranchUpdateMechanism": "外部更新机制使用的分支", "BrowserReloadRequired": "浏览器需重新加载", "BuiltIn": "内置的", @@ -433,7 +433,7 @@ "CancelProcessing": "取消进行中", "CertificateValidation": "验证证书", "ChooseImportMode": "选择导入模式", - "ChownGroupHelpTextWarning": "这只在Sonarr程序是文件所有者的情况下才有效。最好确保下载客户端使用与Sonarr相同的用户组。", + "ChownGroupHelpTextWarning": "这只在{appName}程序是文件所有者的情况下才有效。最好确保下载客户端使用与{appName}相同的用户组。", "ClickToChangeEpisode": "点击修改集", "ClickToChangeQuality": "点击修改质量", "ClickToChangeLanguage": "点击更换语言", @@ -452,7 +452,7 @@ "Connections": "连接", "Continuing": "仍在继续", "CopyToClipboard": "复制到剪贴板", - "CopyUsingHardlinksHelpTextWarning": "有时候,文件锁可能会阻止对正在做种的文件进行重命名。您可以暂时禁用做种功能,并使用Sonarr的重命名功能作为解决方案。", + "CopyUsingHardlinksHelpTextWarning": "有时候,文件锁可能会阻止对正在做种的文件进行重命名。您可以暂时禁用做种功能,并使用{appName}的重命名功能作为解决方案。", "CouldNotFindResults": "找不到 '{term}' 的任何结果", "CreateEmptySeriesFolders": "创建空剧集文件夹", "CreateEmptySeriesFoldersHelpText": "在扫描硬盘时创建缺失的剧集文件夹", @@ -504,7 +504,7 @@ "FailedToLoadCustomFiltersFromApi": "无法从 API 中加载自定义过滤器", "FailedToLoadQualityProfilesFromApi": "无法从 API 中加载质量配置", "FailedToLoadSeriesFromApi": "无法从 API 中加载剧集", - "FailedToLoadSonarr": "无法加载 Sonarr", + "FailedToLoadSonarr": "无法加载 {appName}", "FailedToLoadSystemStatusFromApi": "无法从 API 中加载系统状态", "FailedToLoadTagsFromApi": "无法从 API 中加载标签", "FailedToLoadTranslationsFromApi": "无法从 API 中加载翻译", @@ -516,12 +516,12 @@ "FileNameTokens": "文件名标记", "FileNames": "文件名", "Filter": "过滤", - "UpdateMechanismHelpText": "使用 Sonarr 内置的更新程序或脚本", + "UpdateMechanismHelpText": "使用 {appName} 内置的更新程序或脚本", "AuthenticationRequiredHelpText": "更改需要身份验证的请求。除非您了解风险,否则请勿更改。", "AnEpisodeIsDownloading": "集正在下载", "AuthenticationRequiredWarning": "为了防止在没有身份验证的情况下进行远程访问,{appName} 需要启用身份验证。请配置您的身份验证方法和凭据。您可以选择从本地地址禁用身份验证。", "AutomaticSearch": "自动搜索", - "BackupFolderHelpText": "相对路径将在Sonarr的AppData目录下", + "BackupFolderHelpText": "相对路径将在{appName}的AppData目录下", "BindAddress": "绑定地址", "BindAddressHelpText": "有效的 IP 地址、localhost、或以'*'代表所有接口", "BlocklistLoadError": "无法加载黑名单", @@ -529,7 +529,7 @@ "AddNewSeriesError": "读取搜索结果失败,请稍后重试。", "CloneProfile": "复制配置", "ColonReplacement": "替换冒号", - "ColonReplacementFormatHelpText": "修改Sonarr如何处理冒号的替换", + "ColonReplacementFormatHelpText": "修改{appName}如何处理冒号的替换", "CollectionsLoadError": "不能加载收藏", "CompletedDownloadHandling": "完成下载处理", "DeleteDelayProfile": "删除延迟配置", @@ -539,8 +539,8 @@ "AllSeriesInRootFolderHaveBeenImported": "{path} 中的所有剧集都已导入", "Analytics": "分析", "Anime": "动漫", - "AnalyseVideoFilesHelpText": "从文件中提取视频信息,如分辨率、运行时间和编解码器信息。这需要Sonarr读取文件,可能导致扫描期间磁盘或网络出现高负载。", - "AnalyticsEnabledHelpText": "将匿名使用情况和错误信息发送到Sonarr的服务器。这包括有关您的浏览器的信息、您使用的Sonarr WebUI页面、错误报告以及操作系统和运行时版本。我们将使用此信息来确定功能和错误修复的优先级。", + "AnalyseVideoFilesHelpText": "从文件中提取视频信息,如分辨率、运行时间和编解码器信息。这需要{appName}读取文件,可能导致扫描期间磁盘或网络出现高负载。", + "AnalyticsEnabledHelpText": "将匿名使用情况和错误信息发送到{appName}的服务器。这包括有关您的浏览器的信息、您使用的{appName} WebUI页面、错误报告以及操作系统和运行时版本。我们将使用此信息来确定功能和错误修复的优先级。", "AnalyseVideoFiles": "分析视频文件", "ApplicationURL": "应用程序 URL", "AnimeTypeDescription": "使用绝对集数发布的集数", @@ -564,7 +564,7 @@ "ChmodFolderHelpText": "八进制,当导入和重命名媒体文件夹和文件时应用(不带执行位)", "CheckDownloadClientForDetails": "查看下载客户端了解更多详细信息", "ChmodFolder": "修改文件夹权限", - "ChmodFolderHelpTextWarning": "这只在Sonarr程序是文件所有者的情况下才有效。最好确保下载客户端正确设置权限。", + "ChmodFolderHelpTextWarning": "这只在{appName}程序是文件所有者的情况下才有效。最好确保下载客户端正确设置权限。", "ChownGroup": "修改组权限", "ChooseAnotherFolder": "选择其他文件夹", "ChownGroupHelpText": "组名称或GID。对于远程文件系统请使用GID。", @@ -605,9 +605,9 @@ "CalendarLoadError": "无法加载日历", "Agenda": "日程表", "CertificateValidationHelpText": "改变HTTPS证书验证的严格程度。不要更改除非您了解风险。", - "CopyUsingHardlinksHelpText": "硬链接 (Hardlinks) 允许 Sonarr 将还在做种中的剧集文件(夹)导入而不占用额外的存储空间或者复制文件(夹)的全部内容。硬链接 (Hardlinks) 仅能在源文件和目标文件在同一磁盘卷中使用", + "CopyUsingHardlinksHelpText": "硬链接 (Hardlinks) 允许 {appName} 将还在做种中的剧集文件(夹)导入而不占用额外的存储空间或者复制文件(夹)的全部内容。硬链接 (Hardlinks) 仅能在源文件和目标文件在同一磁盘卷中使用", "CustomFormat": "自定义命名格式", - "CustomFormatHelpText": "Sonarr会根据满足自定义格式与否给每个发布版本评分,如果一个新的发布版本有更高的分数,有相同或更高的影片质量,则Sonarr会抓取该发布版本。", + "CustomFormatHelpText": "{appName}会根据满足自定义格式与否给每个发布版本评分,如果一个新的发布版本有更高的分数,有相同或更高的影片质量,则{appName}会抓取该发布版本。", "DeleteAutoTagHelpText": "你确定要删除 “{name}” 自动标签吗?", "DownloadClientTagHelpText": "仅将此下载客户端用于至少具有一个匹配标签的剧集。留空可用于所有剧集。", "Absolute": "准确的", @@ -676,7 +676,7 @@ "Folder": "文件夹", "GeneralSettingsLoadError": "无法加载通用设置", "GeneralSettingsSummary": "端口、SSL、用户名/密码、代理、分析、更新", - "GrabReleaseMessageText": "Sonarr无法确定这个发布版本是哪部剧集的哪一集,Sonarr可能无法自动导入此版本,你想要获取“{title}”吗?", + "GrabReleaseMessageText": "{appName}无法确定这个发布版本是哪部剧集的哪一集,{appName}可能无法自动导入此版本,你想要获取“{title}”吗?", "GrabRelease": "抓取版本", "Here": "这里", "Group": "组", @@ -711,13 +711,13 @@ "DeleteSpecification": "删除规范", "DeleteSpecificationHelpText": "您确定要删除规范 '{name}' 吗?", "DeletedReasonManual": "文件已通过 UI 删除", - "DeletedReasonMissingFromDisk": "Sonarr 在磁盘上找不到该文件,因此已取消数据库中和该文件的集关联", + "DeletedReasonMissingFromDisk": "{appName} 在磁盘上找不到该文件,因此已取消数据库中和该文件的集关联", "DownloadPropersAndRepacks": "优化版和重制版", "DownloadPropersAndRepacksHelpText": "是否自动更新至优化版和重制版", "Enable": "启用", "EnableMetadataHelpText": "启用此元数据类型的元数据文件创建", "EnableSsl": "启用SSL", - "EnableRssHelpText": "当Sonarr定期通过RSS同步查找发布时使用", + "EnableRssHelpText": "当{appName}定期通过RSS同步查找发布时使用", "EnableSslHelpText": "需要以管理员身份重新启动才能生效", "HistoryLoadError": "无法加载历史记录", "CustomFormatJson": "自定义格式 JSON", @@ -725,7 +725,7 @@ "EditRemotePathMapping": "编辑远程映射路径", "EditRestriction": "编辑限制", "EnableAutomaticAdd": "启用自动添加", - "EnableAutomaticSearchHelpText": "当自动搜索通过 UI 或 Sonarr 执行时将被使用", + "EnableAutomaticSearchHelpText": "当自动搜索通过 UI 或 {appName} 执行时将被使用", "EnableAutomaticSearchHelpTextWarning": "当手动搜索启用时使用", "EnableColorImpairedMode": "启用色障模式", "EnableInteractiveSearchHelpTextWarning": "该索引器不支持搜索", @@ -757,15 +757,15 @@ "ImportExtraFilesHelpText": "导入集文件后导入匹配的额外文件(字幕/nfo等)", "ImportListSettings": "导入列表设置", "ImportListsLoadError": "无法加载导入列表", - "EnableAutomaticAddHelpText": "当通过 UI 或 Sonarr 执行同步时,将剧集添加到 Sonarr", + "EnableAutomaticAddHelpText": "当通过 UI 或 {appName} 执行同步时,将剧集添加到 {appName}", "EnableInteractiveSearchHelpText": "当手动搜索启用时使用", - "EnableMediaInfoHelpText": "从文件中提取视频信息,如分辨率、运行时间和编解码器信息。这需要Sonarr读取文件,可能导致扫描期间磁盘或网络出现高负载。", + "EnableMediaInfoHelpText": "从文件中提取视频信息,如分辨率、运行时间和编解码器信息。这需要{appName}读取文件,可能导致扫描期间磁盘或网络出现高负载。", "GrabbedHistoryTooltip": "集抓取自 {indexer} 并发送至 {downloadClient}", "HealthMessagesInfoBox": "您可以通过单击行尾的wiki链接(图书图标)或检查您的[日志]({link})来查找有关这些健康检查的更多信息。如果您在解读这些信息时遇到困难,可以通过以下链接联系我们获得支持。", "StandardEpisodeFormat": "标准剧集格式", "DefaultNameCopiedSpecification": "{name} - 复制", "AddListExclusion": "添加排除列表", - "AddListExclusionHelpText": "防止剧集通过列表添加到Sonarr", + "AddListExclusionHelpText": "防止剧集通过列表添加到{appName}", "AddNewSeriesSearchForCutoffUnmetEpisodes": "开始搜索未达截止条件的集", "AddedDate": "新增: {date}", "AllSeriesAreHiddenByTheAppliedFilter": "所有结果都被应用的过滤器隐藏", @@ -784,7 +784,7 @@ "SeriesCannotBeFound": "对不起,这个系列找不到。", "SeriesEditRootFolderHelpText": "将系列移动到相同的根文件夹可用于重命名系列文件夹以匹配已更新的标题或命名格式", "ReplaceWithSpaceDash": "替换为空格破折号", - "RenameEpisodesHelpText": "如果禁用重命名,Sonarr 将使用现有的文件名", + "RenameEpisodesHelpText": "如果禁用重命名,{appName} 将使用现有的文件名", "RenameEpisodes": "重命名剧集", "ReplaceWithDash": "替换为破折号", "ReplaceWithSpaceDashSpace": "替换为空格破折号空格", @@ -797,7 +797,7 @@ "DefaultNameCopiedProfile": "{name} - 复制", "SeriesFinale": "大结局", "SeriesFolderFormat": "集文件夹格式", - "ReplaceIllegalCharactersHelpText": "替换非法字符。如果未选中,Sonarr将删除它们", + "ReplaceIllegalCharactersHelpText": "替换非法字符。如果未选中,{appName}将删除它们", "SeriesAndEpisodeInformationIsProvidedByTheTVDB": "剧集和剧集信息由TheTVDB.com提供。[请考虑支持他们](https://www.thetvdb.com/subscribe)。", "DeleteSelectedSeries": "删除选中的剧集", "ProxyBypassFilterHelpText": "使用“ , ”作为分隔符,和“ *. ”作为二级域名的通配符", @@ -816,19 +816,19 @@ "ImportScriptPathHelpText": "用于导入的脚本的路径", "IncludeCustomFormatWhenRenaming": "重命名时包含自定义格式", "RemotePathMappingsInfo": "很少需要远程路径映射,如果{app}和您的下载客户端在同一系统上,最好匹配您的路径。有关详细信息,请参阅[wiki]({wikiLink})", - "IndexerPriorityHelpText": "索引器优先级从1(最高)到50(最低),默认25。当资源连接中断时寻找同等资源时使用,否则Sonarr将依旧使用已启用的索引器进行RSS同步并搜索", + "IndexerPriorityHelpText": "索引器优先级从1(最高)到50(最低),默认25。当资源连接中断时寻找同等资源时使用,否则{appName}将依旧使用已启用的索引器进行RSS同步并搜索", "IndexerTagHelpText": "仅对至少有一个匹配标记的剧集使用此索引器。留空则适用于所有剧集。", "InteractiveImportNoFilesFound": "在选中文件夹中找不到视频文件", "RemoveSelectedBlocklistMessageText": "您确定要从阻止列表中删除所选项目吗?", "InteractiveImportNoQuality": "必须为每个选中的文件选择质量", - "RestartSonarr": "重启Sonarr", + "RestartSonarr": "重启{appName}", "InteractiveSearchModalHeaderSeason": "手动搜索 - {season}", "SearchMonitored": "搜索已监控", "KeyboardShortcutsOpenModal": "打开该弹窗", "SelectEpisodes": "选择集", "SelectSeason": "选择季", "LibraryImportTipsQualityInFilename": "确保您的文件在其文件名中包含质量。例如:`episode.s02e15.bluray.mkv`", - "LibraryImportTipsUseRootFolder": "将Sonarr指向包含所有电视节目的文件夹,而不是特定的一个。例如“`{goodFolderExample}`”而不是“`{badFolderExamp}`”。此外,每个剧集都必须有单独的文件夹位于根/库文件夹下。", + "LibraryImportTipsUseRootFolder": "将{appName}指向包含所有电视节目的文件夹,而不是特定的一个。例如“`{goodFolderExample}`”而不是“`{badFolderExamp}`”。此外,每个剧集都必须有单独的文件夹位于根/库文件夹下。", "ListQualityProfileHelpText": "质量配置列表项将添加", "SeriesIndexFooterMissingMonitored": "缺失集(剧集被监控)", "SeriesIsMonitored": "剧集被监控", @@ -839,7 +839,7 @@ "SeriesTypesHelpText": "剧集类型用于重命名、解析和搜索", "MediaManagementSettingsLoadError": "无法加载媒体管理设置", "SourceRelativePath": "源相对路径", - "MetadataSourceSettingsSummary": "Sonarr从哪里获得剧集和集信息的总结", + "MetadataSourceSettingsSummary": "{appName}从哪里获得剧集和集信息的总结", "SslCertPassword": "SSL证书密码", "UpdateUiNotWritableHealthCheckMessage": "无法安装更新,因为用户“{1}”无法写入 UI 文件夹“{0}”。", "MinimumCustomFormatScoreHelpText": "允许下载的最小自定义格式分数", @@ -848,10 +848,10 @@ "MonitorExistingEpisodes": "现有集", "StopSelecting": "停止选中", "MonitorFirstSeasonDescription": "监控第一季的所有集。所有其他季都将被忽略", - "SupportedCustomConditions": "Sonarr支持针对以下发布属性的自定义条件。", + "SupportedCustomConditions": "{appName}支持针对以下发布属性的自定义条件。", "MonitorSpecials": "监控特别节目", - "SupportedDownloadClients": "Sonarr支持许多流行的torrent和usenet下载客户端。", - "UpgradeUntilCustomFormatScoreHelpText": "一旦达到此自定义格式分数,Sonarr 将不再抓取集的其他版本", + "SupportedDownloadClients": "{appName}支持许多流行的torrent和usenet下载客户端。", + "UpgradeUntilCustomFormatScoreHelpText": "一旦达到此自定义格式分数,{appName} 将不再抓取集的其他版本", "MoveFiles": "移动文件", "SupportedImportListsMoreInfo": "若需要查看有关导入列表的详细信息,请点击“更多信息”按钮。", "MoveSeriesFoldersDontMoveFiles": "不,我自己移动文件", @@ -1093,7 +1093,7 @@ "ReleaseSceneIndicatorMappedNotRequested": "此搜索中未请求映射的剧集。", "ReleaseSceneIndicatorSourceMessage": "{message} 发布时编号不明确,无法准确地识别集。", "ReleaseSceneIndicatorUnknownSeries": "未知的集或剧集。", - "RemotePathMappingLocalPathHelpText": "Sonarr用于访问远程路径的本地路径", + "RemotePathMappingLocalPathHelpText": "{appName}用于访问远程路径的本地路径", "RemoveFromBlocklist": "从黑名单中移除", "RemoveCompletedDownloadsHelpText": "从下载客户端记录中移除已导入的下载", "RemoveFromQueue": "从队列中移除", @@ -1104,7 +1104,7 @@ "RescanAfterRefreshHelpText": "刷新剧集信息后重新扫描剧集文件夹", "ResetAPIKey": "重置API Key", "RestartRequiredHelpTextWarning": "需重启以生效", - "RestartRequiredWindowsService": "根据运行Sonarr的用户,在服务自动启动之前,您可能需要以管理员身份重新启动Sonarr一次。", + "RestartRequiredWindowsService": "根据运行{appName}的用户,在服务自动启动之前,您可能需要以管理员身份重新启动{appName}一次。", "RootFolderSelectFreeSpace": "{freeSpace} 空闲", "RootFolders": "根目录", "RootFoldersLoadError": "无法加载根目录", @@ -1154,8 +1154,8 @@ "ShowTitle": "显示标题", "ShowTitleHelpText": "在海报下显示剧集标题", "ShowUnknownSeriesItems": "实现未知剧集项目", - "ShowUnknownSeriesItemsHelpText": "显示队列中没有剧集的项目,这可能包括已删除的剧集、电影或 Sonarr 类别中的任何其他内容", - "SkipFreeSpaceCheckWhenImportingHelpText": "当 Sonarr 无法从您的剧集根文件夹中检测到空闲空间时使用", + "ShowUnknownSeriesItemsHelpText": "显示队列中没有剧集的项目,这可能包括已删除的剧集、电影或 {appName} 类别中的任何其他内容", + "SkipFreeSpaceCheckWhenImportingHelpText": "当 {appName} 无法从您的剧集根文件夹中检测到空闲空间时使用", "Small": "小", "SingleEpisodeInvalidFormat": "单集:非法格式", "SkipFreeSpaceCheck": "跳过剩余空间检查", @@ -1180,7 +1180,7 @@ "TvdbIdExcludeHelpText": "要排除的剧集 TVDB ID", "TvdbId": "TVDB ID", "TypeOfList": "{typeOfList} 列表", - "UiLanguageHelpText": "Sonarr将用于UI的语言", + "UiLanguageHelpText": "{appName}将用于UI的语言", "UiSettings": "UI设置", "UiLanguage": "UI界面语言", "Umask770Description": "{octal} - 所有者和组写入", @@ -1194,7 +1194,7 @@ "UnmappedFilesOnly": "仅未映射的文件", "UnmonitorDeletedEpisodes": "取消监控已删除的集", "UnmonitorSpecialsDescription": "取消监控所有特别节目而不改变其他集的监控状态", - "UnmonitorDeletedEpisodesHelpText": "从磁盘删除的集将在 Sonarr 中自动取消监控", + "UnmonitorDeletedEpisodesHelpText": "从磁盘删除的集将在 {appName} 中自动取消监控", "UnmonitorSpecials": "取消监控特别节目", "UpdateAll": "全部更新", "UpdateAutomaticallyHelpText": "自动下载并安装更新。你还可以在“系统:更新”中安装", @@ -1241,18 +1241,18 @@ "UiSettingsLoadError": "无法加载UI设置", "UnknownEventTooltip": "未知事件", "UpdateScriptPathHelpText": "自定义脚本的路径,该脚本处理获取的更新包并处理更新过程的其余部分", - "UpdateSonarrDirectlyLoadError": "无法直接更新Sonarr,", + "UpdateSonarrDirectlyLoadError": "无法直接更新{appName},", "View": "视图", "Negate": "相反的", "ListTagsHelpText": "从此列表导入时将添加标记", "ManageEpisodesSeason": "管理本季的集文件", "ManualImportItemsLoadError": "无法加载手动导入项目", - "MassSearchCancelWarning": "一旦启动,如果不重启Sonarr或禁用所有索引器,就无法取消此操作。", + "MassSearchCancelWarning": "一旦启动,如果不重启{appName}或禁用所有索引器,就无法取消此操作。", "ListsLoadError": "无法加载列表", "LocalAirDate": "当地播出日期", "LocalPath": "本地路径", "LogLevel": "日志等级", - "OpenBrowserOnStartHelpText": " 在应用程序启动时,打开浏览器并导航到Sonarr主页。", + "OpenBrowserOnStartHelpText": " 在应用程序启动时,打开浏览器并导航到{appName}主页。", "OpenBrowserOnStart": "启动时打开浏览器", "Period": "时期", "PrefixedRange": "前缀范围", @@ -1348,10 +1348,10 @@ "SpecialsFolderFormat": "特别节目文件夹格式", "SslCertPath": "SSL证书路径", "SslCertPathHelpText": "pfx文件路径", - "SupportedAutoTaggingProperties": "Sonarr支持自动标记规则的以下属性", + "SupportedAutoTaggingProperties": "{appName}支持自动标记规则的以下属性", "SupportedDownloadClientsMoreInfo": "若需要查看有关下载客户端的详细信息,请点击“更多信息”按钮。", - "SupportedIndexers": "Sonarr支持任何使用Newznab标准的索引器,以及下面列出的其他索引器。", - "SupportedLists": "Sonarr支持将多个列表中的剧集导入数据库。", + "SupportedIndexers": "{appName}支持任何使用Newznab标准的索引器,以及下面列出的其他索引器。", + "SupportedLists": "{appName}支持将多个列表中的剧集导入数据库。", "TableOptionsButton": "表格选项按钮", "TheTvdb": "TheTVDB", "Tomorrow": "明天", @@ -1360,7 +1360,7 @@ "Umask": "掩码", "True": "是", "UpgradeUntil": "升级直至", - "UpgradeUntilHelpText": "一旦达到此质量,Sonarr 将不再下载集的其他版本", + "UpgradeUntilHelpText": "一旦达到此质量,{appName} 将不再下载集的其他版本", "UrlBaseHelpText": "对于反向代理支持,默认为空", "UseHardlinksInsteadOfCopy": "使用硬链接代替复制", "UsenetDelay": "Usenet延时", @@ -1388,8 +1388,8 @@ "RemoveQueueItemConfirmation": "你确定要从队列中移除 '{sourceTitle}' 吗?", "RequiredHelpText": "此 {implementationName} 条件必须匹配才能应用自定义格式。 否则,单个 {implementationName} 匹配就足够了。", "RescanSeriesFolderAfterRefresh": "刷新后重新扫描剧集文件夹", - "RestartRequiredToApplyChanges": "Sonarr需要重新启动才能应用更改,您想现在重新启动吗?", - "RescanAfterRefreshHelpTextWarning": "当没有设置为“总是”时,Sonarr将不会自动检测文件的更改", + "RestartRequiredToApplyChanges": "{appName}需要重新启动才能应用更改,您想现在重新启动吗?", + "RescanAfterRefreshHelpTextWarning": "当没有设置为“总是”时,{appName}将不会自动检测文件的更改", "Retention": "保留", "RetentionHelpText": "仅限Usenet:设置为零以设置无限保留", "RetryingDownloadOn": "于 {date} {time} 重试下载", @@ -1403,7 +1403,7 @@ "SeriesFolderImportedTooltip": "从剧集文件夹导入的集", "Socks4": "Socks4", "Socks5": "Socks5 (支持TOR)", - "SonarrTags": "Sonarr标签", + "SonarrTags": "{appName}标签", "SourcePath": "来源路径", "SmartReplaceHint": "短划线或空格短划线取决于名称", "TagIsNotUsedAndCanBeDeleted": "标签未被使用,可删除", @@ -1441,8 +1441,8 @@ "Monday": "星期一", "Monitor": "是否监控", "NotificationTriggersHelpText": "选择触发此通知的事件", - "ImportListsSettingsSummary": "从另一个Sonarr或Trakt列表导入并管理排除列表", - "ParseModalHelpTextDetails": "Sonarr将尝试解析标题并向您展示有关它的详细信息", + "ImportListsSettingsSummary": "从另一个{appName}或Trakt列表导入并管理排除列表", + "ParseModalHelpTextDetails": "{appName}将尝试解析标题并向您展示有关它的详细信息", "Proxy": "代理", "ImportScriptPath": "导入脚本路径", "IncludeCustomFormatWhenRenamingHelpText": "在 {Custom Formats} 中包含重命名格式", From ad1f185330a30a2a9d27c9d3f18d384e66727c2a Mon Sep 17 00:00:00 2001 From: Bogdan Date: Sat, 16 Sep 2023 15:07:39 +0300 Subject: [PATCH 002/136] Use async requests for media cover proxy --- src/NzbDrone.Core/MediaCover/MediaCoverProxy.cs | 8 +++++--- .../Frontend/Mappers/IMapHttpRequestsToDisk.cs | 5 +++-- .../Frontend/Mappers/MediaCoverProxyMapper.cs | 9 +++++---- .../Mappers/StaticResourceMapperBase.cs | 7 ++++--- .../Frontend/StaticResourceController.cs | 17 +++++++++-------- 5 files changed, 26 insertions(+), 20 deletions(-) diff --git a/src/NzbDrone.Core/MediaCover/MediaCoverProxy.cs b/src/NzbDrone.Core/MediaCover/MediaCoverProxy.cs index 8f6ed8c9a..b8b0cc3a0 100644 --- a/src/NzbDrone.Core/MediaCover/MediaCoverProxy.cs +++ b/src/NzbDrone.Core/MediaCover/MediaCoverProxy.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Threading.Tasks; using NzbDrone.Common.Cache; using NzbDrone.Common.Http; using NzbDrone.Core.Configuration; @@ -12,7 +13,7 @@ namespace NzbDrone.Core.MediaCover string RegisterUrl(string url); string GetUrl(string hash); - byte[] GetImage(string hash); + Task GetImage(string hash); } public class MediaCoverProxy : IMediaCoverProxy @@ -52,13 +53,14 @@ namespace NzbDrone.Core.MediaCover return result; } - public byte[] GetImage(string hash) + public async Task GetImage(string hash) { var url = GetUrl(hash); var request = new HttpRequest(url); + var response = await _httpClient.GetAsync(request); - return _httpClient.Get(request).ResponseData; + return response.ResponseData; } } } diff --git a/src/Sonarr.Http/Frontend/Mappers/IMapHttpRequestsToDisk.cs b/src/Sonarr.Http/Frontend/Mappers/IMapHttpRequestsToDisk.cs index 7b3021f8f..0f8ebe74d 100644 --- a/src/Sonarr.Http/Frontend/Mappers/IMapHttpRequestsToDisk.cs +++ b/src/Sonarr.Http/Frontend/Mappers/IMapHttpRequestsToDisk.cs @@ -1,4 +1,5 @@ -using Microsoft.AspNetCore.Mvc; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; namespace Sonarr.Http.Frontend.Mappers { @@ -6,6 +7,6 @@ namespace Sonarr.Http.Frontend.Mappers { string Map(string resourceUrl); bool CanHandle(string resourceUrl); - IActionResult GetResponse(string resourceUrl); + Task GetResponse(string resourceUrl); } } diff --git a/src/Sonarr.Http/Frontend/Mappers/MediaCoverProxyMapper.cs b/src/Sonarr.Http/Frontend/Mappers/MediaCoverProxyMapper.cs index ece8c0609..9c869c899 100644 --- a/src/Sonarr.Http/Frontend/Mappers/MediaCoverProxyMapper.cs +++ b/src/Sonarr.Http/Frontend/Mappers/MediaCoverProxyMapper.cs @@ -1,6 +1,7 @@ -using System; +using System; using System.Net; using System.Text.RegularExpressions; +using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.StaticFiles; using NzbDrone.Core.MediaCover; @@ -9,7 +10,7 @@ namespace Sonarr.Http.Frontend.Mappers { public class MediaCoverProxyMapper : IMapHttpRequestsToDisk { - private readonly Regex _regex = new Regex(@"/MediaCoverProxy/(?\w+)/(?(.+)\.(jpg|png|gif))"); + private readonly Regex _regex = new (@"/MediaCoverProxy/(?\w+)/(?(.+)\.(jpg|png|gif))"); private readonly IMediaCoverProxy _mediaCoverProxy; private readonly IContentTypeProvider _mimeTypeProvider; @@ -30,7 +31,7 @@ namespace Sonarr.Http.Frontend.Mappers return resourceUrl.StartsWith("/MediaCoverProxy/", StringComparison.InvariantCultureIgnoreCase); } - public IActionResult GetResponse(string resourceUrl) + public async Task GetResponse(string resourceUrl) { var match = _regex.Match(resourceUrl); @@ -42,7 +43,7 @@ namespace Sonarr.Http.Frontend.Mappers var hash = match.Groups["hash"].Value; var filename = match.Groups["filename"].Value; - var imageData = _mediaCoverProxy.GetImage(hash); + var imageData = await _mediaCoverProxy.GetImage(hash); if (!_mimeTypeProvider.TryGetContentType(filename, out var contentType)) { diff --git a/src/Sonarr.Http/Frontend/Mappers/StaticResourceMapperBase.cs b/src/Sonarr.Http/Frontend/Mappers/StaticResourceMapperBase.cs index 277060de1..d7e812af9 100644 --- a/src/Sonarr.Http/Frontend/Mappers/StaticResourceMapperBase.cs +++ b/src/Sonarr.Http/Frontend/Mappers/StaticResourceMapperBase.cs @@ -1,6 +1,7 @@ using System; using System.IO; using System.Text; +using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.StaticFiles; using Microsoft.Net.Http.Headers; @@ -30,7 +31,7 @@ namespace Sonarr.Http.Frontend.Mappers public abstract bool CanHandle(string resourceUrl); - public IActionResult GetResponse(string resourceUrl) + public Task GetResponse(string resourceUrl) { var filePath = Map(resourceUrl); @@ -41,10 +42,10 @@ namespace Sonarr.Http.Frontend.Mappers contentType = "application/octet-stream"; } - return new FileStreamResult(GetContentStream(filePath), new MediaTypeHeaderValue(contentType) + return Task.FromResult(new FileStreamResult(GetContentStream(filePath), new MediaTypeHeaderValue(contentType) { Encoding = contentType == "text/plain" ? Encoding.UTF8 : null - }); + })); } _logger.Warn("File {0} not found", filePath); diff --git a/src/Sonarr.Http/Frontend/StaticResourceController.cs b/src/Sonarr.Http/Frontend/StaticResourceController.cs index b70b5e597..49bc495b7 100644 --- a/src/Sonarr.Http/Frontend/StaticResourceController.cs +++ b/src/Sonarr.Http/Frontend/StaticResourceController.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Cors; using Microsoft.AspNetCore.Mvc; @@ -25,27 +26,27 @@ namespace Sonarr.Http.Frontend [AllowAnonymous] [HttpGet("login")] - public IActionResult LoginPage() + public async Task LoginPage() { - return MapResource("login"); + return await MapResource("login"); } [EnableCors("AllowGet")] [AllowAnonymous] [HttpGet("/content/{**path:regex(^(?!api/).*)}")] - public IActionResult IndexContent([FromRoute] string path) + public async Task IndexContent([FromRoute] string path) { - return MapResource("Content/" + path); + return await MapResource("Content/" + path); } [HttpGet("")] [HttpGet("/{**path:regex(^(?!(api|feed)/).*)}")] - public IActionResult Index([FromRoute] string path) + public async Task Index([FromRoute] string path) { - return MapResource(path); + return await MapResource(path); } - private IActionResult MapResource(string path) + private async Task MapResource(string path) { path = "/" + (path ?? ""); @@ -53,7 +54,7 @@ namespace Sonarr.Http.Frontend if (mapper != null) { - var result = mapper.GetResponse(path); + var result = await mapper.GetResponse(path); if (result != null) { From 82d586e7015d7ea06356ca436024a8af5a4fb677 Mon Sep 17 00:00:00 2001 From: Bogdan Date: Wed, 30 Aug 2023 22:11:27 +0300 Subject: [PATCH 003/136] Use await on reading the response content --- src/NzbDrone.Common/Http/Dispatchers/ManagedHttpDispatcher.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NzbDrone.Common/Http/Dispatchers/ManagedHttpDispatcher.cs b/src/NzbDrone.Common/Http/Dispatchers/ManagedHttpDispatcher.cs index a9449efe7..5281d74fe 100644 --- a/src/NzbDrone.Common/Http/Dispatchers/ManagedHttpDispatcher.cs +++ b/src/NzbDrone.Common/Http/Dispatchers/ManagedHttpDispatcher.cs @@ -114,7 +114,7 @@ namespace NzbDrone.Common.Http.Dispatchers } else { - data = responseMessage.Content.ReadAsByteArrayAsync(cts.Token).GetAwaiter().GetResult(); + data = await responseMessage.Content.ReadAsByteArrayAsync(cts.Token); } } catch (Exception ex) From c1d9187bb66c0524048020613d816918b84b5532 Mon Sep 17 00:00:00 2001 From: Bogdan Date: Fri, 15 Sep 2023 16:26:57 +0300 Subject: [PATCH 004/136] Log exceptions for failed fetches in Custom and Sonarr import lists Co-authored-by: bakerboy448 <55419169+bakerboy448@users.noreply.github.com> --- src/NzbDrone.Core/ImportLists/Custom/CustomImport.cs | 4 +++- src/NzbDrone.Core/ImportLists/Sonarr/SonarrImport.cs | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/NzbDrone.Core/ImportLists/Custom/CustomImport.cs b/src/NzbDrone.Core/ImportLists/Custom/CustomImport.cs index 93f71e6d8..29157be63 100644 --- a/src/NzbDrone.Core/ImportLists/Custom/CustomImport.cs +++ b/src/NzbDrone.Core/ImportLists/Custom/CustomImport.cs @@ -46,8 +46,10 @@ namespace NzbDrone.Core.ImportLists.Custom _importListStatusService.RecordSuccess(Definition.Id); } - catch + catch (Exception ex) { + _logger.Debug(ex, "Failed to fetch data for list {0} ({1})", Definition.Name, Name); + _importListStatusService.RecordFailure(Definition.Id); } diff --git a/src/NzbDrone.Core/ImportLists/Sonarr/SonarrImport.cs b/src/NzbDrone.Core/ImportLists/Sonarr/SonarrImport.cs index 7973eac9a..33d701781 100644 --- a/src/NzbDrone.Core/ImportLists/Sonarr/SonarrImport.cs +++ b/src/NzbDrone.Core/ImportLists/Sonarr/SonarrImport.cs @@ -68,8 +68,10 @@ namespace NzbDrone.Core.ImportLists.Sonarr _importListStatusService.RecordSuccess(Definition.Id); } - catch + catch (Exception ex) { + _logger.Debug(ex, "Failed to fetch data for list {0} ({1})", Definition.Name, Name); + _importListStatusService.RecordFailure(Definition.Id); } From c034d50ff3008a244272b2c312b70be258b2a8b6 Mon Sep 17 00:00:00 2001 From: Havok Dan Date: Tue, 12 Sep 2023 17:45:03 +0000 Subject: [PATCH 005/136] Translated using Weblate (Portuguese (Brazil)) Currently translated at 99.0% (1471 of 1485 strings) Translation: Servarr/Sonarr Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/pt_BR/ (cherry picked from commit 3bf0f8ad5b1b05596f1b04f8963be2c5a9da9493) --- .../Localization/Core/pt_BR.json | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/NzbDrone.Core/Localization/Core/pt_BR.json b/src/NzbDrone.Core/Localization/Core/pt_BR.json index 6f22cfd97..ec91c7a81 100644 --- a/src/NzbDrone.Core/Localization/Core/pt_BR.json +++ b/src/NzbDrone.Core/Localization/Core/pt_BR.json @@ -121,7 +121,7 @@ "DeleteSelectedIndexers": "Excluir indexador(es)", "DeleteSelectedDownloadClientsMessageText": "Tem certeza de que deseja excluir {count} cliente(s) de download selecionado(s)?", "DeleteSelectedImportListsMessageText": "Tem certeza de que deseja excluir {count} lista(s) de importação selecionada(s)?", - "ExistingTag": "Etiqueta existente", + "ExistingTag": "Tag existente", "Implementation": "Implementação", "Disabled": "Desabilitado", "Edit": "Editar", @@ -139,11 +139,11 @@ "RemoveFailed": "Falha na remoção", "Replace": "Substituir", "Result": "Resultado", - "SetTags": "Definir etiquetas", + "SetTags": "Definir tags", "Yes": "Sim", "Tags": "Tags", "AutoAdd": "Adicionar automaticamente", - "RemovingTag": "Removendo etiqueta", + "RemovingTag": "Removendo tag", "DeleteSelectedIndexersMessageText": "Tem certeza de que deseja excluir {count} indexadores selecionados?", "RemoveCompleted": "Remoção Concluída", "LibraryImport": "Importar para biblioteca", @@ -161,9 +161,9 @@ "Tasks": "Tarefas", "Updates": "Atualizações", "Wanted": "Procurado", - "ApplyTagsHelpTextAdd": "Adicionar: adicione as etiquetas à lista existente de etiquetas", - "ApplyTagsHelpTextReplace": "Substituir: Substitua as etiquetas pelas etiquetas inseridas (não digite nenhuma etiqueta para limpar todas as etiquetas)", - "ApplyTagsHelpTextRemove": "Remover: Remove as etiquetas inseridas", + "ApplyTagsHelpTextAdd": "Adicionar: adicione as tags à lista existente de tags", + "ApplyTagsHelpTextReplace": "Substituir: Substitua as tags pelas tags inseridas (não digite nenhuma tag para limpar todas as tags)", + "ApplyTagsHelpTextRemove": "Remover: Remove as tags inseridas", "CustomFormatScore": "Pontuação do formato personalizado", "Activity": "Atividade", "AddNew": "Adicionar Novo", @@ -187,7 +187,7 @@ "Version": "Versão", "ApplyTagsHelpTextHowToApplyDownloadClients": "Como aplicar tags aos clientes de download selecionados", "ApplyTagsHelpTextHowToApplyImportLists": "Como aplicar tags às listas de importação selecionadas", - "ApplyTagsHelpTextHowToApplyIndexers": "Como aplicar etiquetas aos indexadores selecionados", + "ApplyTagsHelpTextHowToApplyIndexers": "Como aplicar tags aos indexadores selecionados", "ApplyTagsHelpTextHowToApplySeries": "Como aplicar etiquetas à série selecionada", "EpisodeInfo": "Info do Episódio", "EpisodeNumbers": "Número(s) do(s) Episódio(s)", @@ -780,8 +780,8 @@ "ReleaseProfileIndexerHelpText": "Especifique a qual indexador o perfil se aplica", "ReleaseProfileIndexerHelpTextWarning": "O uso de um indexador específico com perfis de lançamento pode levar à obtenção de lançamentos duplicados", "ReleaseProfileTagHelpText": "Os perfis de lançamento serão aplicados a séries com pelo menos uma tag correspondente. Deixe em branco para aplicar a todas as séries", - "ReleaseProfiles": "Perfis de Lançamento", - "ReleaseProfilesLoadError": "Não foi possível carregar os perfis de lançamento", + "ReleaseProfiles": "Perfis de Lançamentos", + "ReleaseProfilesLoadError": "Não foi possível carregar perfis de lançamentos", "RemotePath": "Caminho Remoto", "RemotePathMappingHostHelpText": "O mesmo host que você especificou para o Download Client remoto", "RemotePathMappingRemotePathHelpText": "Caminho raiz para o diretório que o Cliente de Download acessa", @@ -999,7 +999,7 @@ "AgeWhenGrabbed": "Idade (quando baixado)", "DelayingDownloadUntil": "Atrasando o download até {date} às {time}", "DeletedReasonMissingFromDisk": "O {appName} não conseguiu encontrar o arquivo no disco, então o arquivo foi desvinculado do episódio no banco de dados", - "DeletedReasonManual": "O arquivo foi excluído por meio da UI", + "DeletedReasonManual": "O arquivo foi excluído por meio da IU", "DownloadFailed": "Download Falhou", "DestinationRelativePath": "Caminho Relativo de Destino", "DownloadIgnoredTooltip": "Download do Episódio Ignorado", @@ -1016,7 +1016,7 @@ "GrabSelected": "Obter Selecionado", "GrabbedHistoryTooltip": "Episódio retirado de {indexer} e enviado para {downloadClient}", "ImportedTo": "Importado Para", - "InfoUrl": "URL de Info", + "InfoUrl": "URL com informações", "MarkAsFailed": "Marcar como Falha", "NoHistoryFound": "Nenhum histórico encontrado", "Ok": "Ok", @@ -1084,12 +1084,12 @@ "IconForSpecialsHelpText": "Mostrar ícone para episódios especiais (temporada 0)", "ImportFailed": "Falha na importação: {sourceTitle}", "EpisodeMissingAbsoluteNumber": "O episódio não tem um número de episódio absoluto", - "FullColorEventsHelpText": "Estilo alterado para colorir todo o evento com a cor de status, em vez de apenas a borda esquerda. Não se aplica à Agenda", + "FullColorEventsHelpText": "Estilo alterado para colorir todo o evento com a cor do status, em vez de apenas a borda esquerda. Não se aplica à Agenda", "ICalTagsHelpText": "O feed conterá apenas séries com pelo menos uma tag correspondente", "IconForFinalesHelpText": "Mostrar ícone para finais de séries/temporadas com base nas informações de episódios disponíveis", "NoHistoryBlocklist": "Sem histórico na lista de bloqueio", "QualityCutoffNotMet": "O corte de qualidade não foi atingido", - "QueueLoadError": "Falha ao carregar a Fila", + "QueueLoadError": "Falha ao carregar a fila", "RemoveQueueItem": "Remover - {sourceTitle}", "RemoveQueueItemConfirmation": "Tem certeza de que deseja remover '{sourceTitle}' da fila?", "Absolute": "Absoluto", @@ -1146,7 +1146,7 @@ "ClickToChangeSeason": "Clique para mudar a temporada", "ClickToChangeSeries": "Clique para mudar de série", "ConnectionLost": "Conexão Perdida", - "ConnectionLostToBackend": "{appName} perdeu sua conexão com o backend e precisará ser recarregado para restaurar a funcionalidade.", + "ConnectionLostToBackend": "{appName} perdeu a conexão com o backend e precisará ser recarregado para restaurar a funcionalidade.", "Continuing": "Continuando", "CountSelectedFile": "{selectedCount} arquivo selecionado", "CustomFilters": "Filtros Personalizados", @@ -1246,7 +1246,7 @@ "SetReleaseGroup": "Definir Grupo do Lançamento", "StandardTypeFormat": "Números da temporada e do episódio ({format})", "TableColumnsHelpText": "Escolha quais colunas são visíveis e em que ordem elas aparecem", - "TablePageSizeMinimum": "O tamanho da página deve ser de pelo menos {minimumValue}", + "TablePageSizeMinimum": "O tamanho da página precisa ser de pelo menos {minimumValue}", "Umask750Description": "{octal} - gravação do proprietário, leitura do grupo", "Umask775Description": "{octal} - gravação do proprietário e do grupo, leitura de outros", "Week": "Semana", @@ -1263,7 +1263,7 @@ "OrganizeLoadError": "Erro ao carregar visualizações", "OrganizeModalHeader": "Organizar & Renomear", "OrganizeNamingPattern": "Padrão de nomenclatura: `{episodeFormat}`", - "OrganizeNothingToRename": "Sucesso! Meu trabalho está feito, nenhum arquivo para renomear.", + "OrganizeNothingToRename": "Sucesso! Meu trabalho está concluído, não há arquivos para renomear.", "OrganizeRelativePaths": "Todos os caminhos são relativos a: `{path}`", "OverrideAndAddToDownloadQueue": "Substituir e adicionar à fila de download", "OverrideGrabModalTitle": "Substituir e Baixar - {title}", @@ -1298,7 +1298,7 @@ "TableOptions": "Opções de Tabela", "TablePageSize": "Tamanho da Página", "TablePageSizeHelpText": "Número de itens a serem exibidos em cada página", - "TablePageSizeMaximum": "O tamanho da página não deve exceder {maximumValue}", + "TablePageSizeMaximum": "O tamanho da página não pode exceder {maximumValue}", "Tba": "A ser anunciado", "Titles": "Título", "ToggleMonitoredSeriesUnmonitored ": "Não é possível alternar o estado monitorado quando a série não é monitorada", @@ -1316,7 +1316,7 @@ "WhatsNew": "O que há de novo?", "Rejections": "Rejeições", "Connection": "Conexão", - "CustomFormatJson": "JSON do Formato Personalizado", + "CustomFormatJson": "JSON do formato personalizado", "Database": "Banco de dados", "HealthMessagesInfoBox": "Você pode encontrar mais informações sobre a causa dessas mensagens de verificação de integridade clicando no link da wiki (ícone do livro) no final da linha ou verificando seus [logs]({link}). Se tiver dificuldade em interpretar essas mensagens, você pode entrar em contato com nosso suporte, nos links abaixo.", "ImdbId": "ID do IMDb", From a4ba3ea244a6a4c4bab56f9d79521ba7674e6a81 Mon Sep 17 00:00:00 2001 From: Herve Lauwerier Date: Thu, 14 Sep 2023 13:02:47 +0000 Subject: [PATCH 006/136] Multiple Translations updated by Weblate Translation: Servarr/Sonarr Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/fr/ (cherry picked from commit aa8339e6c09182b65ed6e0497eb0fcf9a5abbea9) --- src/NzbDrone.Core/Localization/Core/el.json | 6 +- src/NzbDrone.Core/Localization/Core/fr.json | 62 +++++++++- src/NzbDrone.Core/Localization/Core/ko.json | 17 ++- src/NzbDrone.Core/Localization/Core/pl.json | 6 +- src/NzbDrone.Core/Localization/Core/pt.json | 111 +++++++++++++++++- .../Localization/Core/pt_BR.json | 2 +- src/NzbDrone.Core/Localization/Core/ro.json | 34 +++++- src/NzbDrone.Core/Localization/Core/ru.json | 5 +- .../Localization/Core/zh_CN.json | 82 ++++++------- 9 files changed, 276 insertions(+), 49 deletions(-) diff --git a/src/NzbDrone.Core/Localization/Core/el.json b/src/NzbDrone.Core/Localization/Core/el.json index ffadb1f73..7bfb1c9ff 100644 --- a/src/NzbDrone.Core/Localization/Core/el.json +++ b/src/NzbDrone.Core/Localization/Core/el.json @@ -14,5 +14,9 @@ "DeleteCustomFormatMessageText": "Είστε σίγουροι πως θέλετε να διαγράψετε τη προσαρμοσμένη μορφή '{0}';", "RemoveSelectedItemsQueueMessageText": "Είστε σίγουροι πως θέλετε να διαγράψετε {0} αντικείμενα από την ουρά;", "CloneCondition": "Κλωνοποίηση συνθήκης", - "RemoveSelectedItemQueueMessageText": "Είστε σίγουροι πως θέλετε να διαγράψετε 1 αντικείμενο από την ουρά;" + "RemoveSelectedItemQueueMessageText": "Είστε σίγουροι πως θέλετε να διαγράψετε 1 αντικείμενο από την ουρά;", + "AddConditionImplementation": "Προσθήκη", + "AppUpdated": "{appName} Ενημερώθηκε", + "AutoAdd": "Προσθήκη", + "AddConnectionImplementation": "Προσθήκη" } diff --git a/src/NzbDrone.Core/Localization/Core/fr.json b/src/NzbDrone.Core/Localization/Core/fr.json index 3d36f819c..5dce946d5 100644 --- a/src/NzbDrone.Core/Localization/Core/fr.json +++ b/src/NzbDrone.Core/Localization/Core/fr.json @@ -157,5 +157,65 @@ "AuthenticationMethodHelpTextWarning": "Veuillez sélectionner une méthode d'authentification valide", "AuthenticationRequiredHelpText": "Modifier les demandes pour lesquelles l'authentification est requise. Ne changez rien si vous ne comprenez pas les risques.", "AutomaticUpdatesDisabledDocker": "Les mises à jour automatiques ne sont pas directement prises en charge lors de l'utilisation du mécanisme de mise à jour de Docker. Vous devrez mettre à jour l'image du conteneur en dehors de {appName} ou utiliser un script", - "BackupRetentionHelpText": "Les sauvegardes automatiques plus anciennes que la période de rétention seront nettoyées automatiquement" + "BackupRetentionHelpText": "Les sauvegardes automatiques plus anciennes que la période de rétention seront nettoyées automatiquement", + "QualityProfile": "Profil de qualité", + "RemotePathMappingDownloadPermissionsHealthCheckMessage": "Sonarr peut voir mais ne peut pas accéder à l'épisode téléchargé {0}. Probablement une erreur de permissions.", + "RemotePathMappingDockerFolderMissingHealthCheckMessage": "Vous utilisez Docker ; le client de téléchargement {0} place les téléchargements dans {1}, mais ce répertoire ne semble pas exister dans le conteneur. Vérifiez vos mappages de chemins d'accès distants et les paramètres de volume du conteneur.", + "BlocklistReleases": "Publications de la liste de blocage", + "BindAddress": "Adresse de liaison", + "BackupsLoadError": "Impossible de charger les sauvegardes", + "BlocklistReleaseHelpText": "Lance une nouvelle recherche pour cet épisode et empêche que cette version soit à nouveau récupérée.", + "BuiltIn": "Intégré", + "BrowserReloadRequired": "Rechargement du navigateur nécessaire", + "BypassDelayIfAboveCustomFormatScore": "Ignorer si le score est supérieur au format personnalisé", + "CheckDownloadClientForDetails": "Pour plus de détails, consultez le client de téléchargement", + "ChooseAnotherFolder": "Sélectionnez un autre dossier", + "BlocklistLoadError": "Impossible de charger la liste de blocage", + "BranchUpdate": "Branche à utiliser pour mettre à jour Sonarr", + "BypassDelayIfAboveCustomFormatScoreMinimumScore": "Score minimum pour le format personnalisé", + "CalendarLoadError": "Impossible de charger le calendrier", + "BypassDelayIfAboveCustomFormatScoreHelpText": "Ignorer lorsque la version a un score supérieur au score minimum configuré pour le format personnalisé", + "CertificateValidationHelpText": "Modifier le niveau de rigueur de la validation de la certification HTTPS. Ne pas modifier si vous ne maîtrisez pas les risques.", + "Certification": "Certification", + "ChangeFileDateHelpText": "Modifier la date du fichier lors de l'importation/la réanalyse", + "ChmodFolder": "chmod Dossier", + "ChmodFolderHelpText": "Octal, appliqué lors de l'importation/du renommage des dossiers et fichiers multimédias (sans bits d'exécution)", + "ChmodFolderHelpTextWarning": "Cela ne fonctionne que si l'utilisateur qui exécute sonarr est le propriétaire du fichier. Il est préférable de s'assurer que le client de téléchargement définit correctement les permissions.", + "ChownGroup": "chown Groupe", + "ChownGroupHelpText": "Nom du groupe ou gid. Utilisez gid pour les systèmes de fichiers distants.", + "ChownGroupHelpTextWarning": "Cela ne fonctionne que si l'utilisateur qui exécute sonarr est le propriétaire du fichier. Il est préférable de s'assurer que le client de téléchargement utilise le même groupe que sonarr.", + "ClickToChangeQuality": "Cliquez pour changer la qualité", + "RefreshSeries": "Actualiser les séries", + "RecycleBinUnableToWriteHealthCheckMessage": "Impossible d'écrire dans le dossier configuré de la corbeille de recyclage : {0}. Assurez-vous que ce chemin existe et qu'il est accessible en écriture par l'utilisateur qui exécute Sonarr.", + "RemotePathMappingFileRemovedHealthCheckMessage": "Le fichier {0} a été supprimé en cours de traitement.", + "RemotePathMappingFilesGenericPermissionsHealthCheckMessage": "Le client de téléchargement {0} a signalé des fichiers dans {1} mais Sonarr ne peut pas voir ce répertoire. Il se peut que vous deviez ajuster les permissions du dossier.", + "CalendarFeed": "Calendrier {appName}", + "CalendarLegendDownloadedTooltip": "L'épisode a été téléchargé et classé", + "CalendarLegendDownloadingTooltip": "L'épisode est en cours de téléchargement", + "CalendarLegendFinaleTooltip": "Fin de série ou de saison", + "CalendarLegendMissingTooltip": "L'épisode a été diffusé et est absent du disque", + "CalendarLegendOnAirTooltip": "Épisode en cours de diffusion", + "CalendarLegendPremiereTooltip": "Première de la série ou de la saison", + "CalendarLegendUnairedTooltip": "L'épisode n'a pas encore été diffusé", + "CalendarLegendUnmonitoredTooltip": "L'épisode n'est pas surveillé", + "CancelProcessing": "Annuler le traitement", + "ChooseImportMode": "Sélectionnez le mode d'importation", + "ClickToChangeLanguage": "Cliquez pour changer de langue", + "ClickToChangeEpisode": "Cliquez pour changer d'épisode", + "ClickToChangeReleaseGroup": "Cliquez pour changer de groupe de diffusion", + "RemotePathMappingFilesBadDockerPathHealthCheckMessage": "Vous utilisez Docker ; le client de téléchargement {0} a signalé des fichiers dans {1} mais ce n'est pas un chemin {2} valide. Vérifiez vos mappages de chemins d'accès distants et les paramètres du client de téléchargement.", + "BypassDelayIfAboveCustomFormatScoreMinimumScoreHelpText": "Score minimum requis pour le format personnalisé pour ignorer le délai pour le protocole préféré", + "BypassDelayIfHighestQualityHelpText": "Ignorer le délai lorsque la libération a la qualité activée la plus élevée dans le profil de qualité avec le protocole préféré.", + "RemotePathMappingBadDockerPathHealthCheckMessage": "Vous utilisez Docker ; le client de téléchargement {0} place les téléchargements dans {1} mais ce n'est pas un chemin {2} valide. Revoyez vos mappages de chemins d'accès distants et les paramètres du client de téléchargement.", + "RemotePathMappingFilesLocalWrongOSPathHealthCheckMessage": "Le client de téléchargement local {0} a signalé des fichiers dans {1}, mais il ne s'agit pas d'un chemin {2} valide. Vérifiez les paramètres de votre client de téléchargement.", + "RemotePathMappingFilesWrongOSPathHealthCheckMessage": "Le client de téléchargement distant {0} a signalé des fichiers dans {1}, mais il ne s'agit pas d'un chemin {2} valide. Revoyez vos mappages de chemins d'accès distants et les paramètres du client de téléchargement.", + "RemotePathMappingFolderPermissionsHealthCheckMessage": "Sonarr peut voir mais ne peut pas accéder au répertoire de téléchargement {0}. Il s'agit probablement d'une erreur de permissions.", + "Path": "Chemin", + "QueueIsEmpty": "La file d'attente est vide", + "Warn": "Avertissement", + "Week": "Semaine", + "Yesterday": "Hier", + "Password": "Mot de passe", + "Queue": "File d'attente", + "Wanted": "Recherché" } diff --git a/src/NzbDrone.Core/Localization/Core/ko.json b/src/NzbDrone.Core/Localization/Core/ko.json index 0967ef424..a719741c0 100644 --- a/src/NzbDrone.Core/Localization/Core/ko.json +++ b/src/NzbDrone.Core/Localization/Core/ko.json @@ -1 +1,16 @@ -{} +{ + "StopSelecting": "선택 취소", + "Sort": "정렬", + "AddedToDownloadQueue": "다운로드 대기열에 추가됨", + "Calendar": "달력", + "CalendarOptions": "달력 옵션", + "System": "시스템", + "AddToDownloadQueue": "다운로드 대기열에 추가됨", + "NoHistory": "내역 없음", + "SelectAll": "모두 선택", + "View": "표시 변경", + "AuthenticationMethodHelpText": "{appName}에 접근하려면 사용자 이름과 암호가 필요합니다.", + "AddNew": "새로 추가하기", + "History": "내역", + "Sunday": "일요일" +} diff --git a/src/NzbDrone.Core/Localization/Core/pl.json b/src/NzbDrone.Core/Localization/Core/pl.json index 974579375..6717d3c29 100644 --- a/src/NzbDrone.Core/Localization/Core/pl.json +++ b/src/NzbDrone.Core/Localization/Core/pl.json @@ -14,5 +14,9 @@ "Authentication": "Autoryzacja", "BlocklistReleases": "Dodaj wersje do czarnej listy", "ApiKeyValidationHealthCheckMessage": "Zaktualizuj swój klucz API aby był długi na co najmniej {0} znaków. Możesz to zrobić poprzez ustawienia lub plik konfiguracyjny", - "AudioInfo": "Informacje o audio" + "AudioInfo": "Informacje o audio", + "AddAutoTagError": "Nie można dodać nowego tagu automatycznego, spróbuj ponownie.", + "AddConditionError": "Nie można dodać nowego warunku, spróbuj ponownie.", + "AddConnection": "Dodaj połączenie", + "AddCustomFilter": "Dodaj spersonalizowany filtr" } diff --git a/src/NzbDrone.Core/Localization/Core/pt.json b/src/NzbDrone.Core/Localization/Core/pt.json index 32766a87b..5aa1d1182 100644 --- a/src/NzbDrone.Core/Localization/Core/pt.json +++ b/src/NzbDrone.Core/Localization/Core/pt.json @@ -14,5 +14,114 @@ "About": "Sobre", "Actions": "Ações", "Absolute": "Absoluto", - "AddANewPath": "Adicionar novo caminho" + "AddANewPath": "Adicionar novo caminho", + "AddCondition": "Adicionar Condição", + "AirDate": "Data de Transmissão", + "AllTitles": "Todos os Títulos", + "Apply": "Aplicar", + "AddingTag": "Adicionando etiqueta", + "AgeWhenGrabbed": "Idade (quando capturada)", + "ApplyTags": "Aplicar etiquetas", + "Authentication": "Autenticação", + "AuthenticationMethodHelpText": "Solicitar Nome de Usuário e Senha para acessar o {appName}", + "AuthenticationRequired": "Autenticação Necessária", + "AutoAdd": "Adicionar automaticamente", + "AddRootFolder": "Adicionar pasta raiz", + "ApplyTagsHelpTextHowToApplyImportLists": "Como aplicar etiquetas às listas de importação selecionadas", + "ApplyTagsHelpTextHowToApplyIndexers": "Como aplicar etiquetas aos indexadores selecionados", + "AddListExclusion": "Adicionar exclusão de lista", + "AddListExclusionHelpText": "Impedir série de ser adicionada ao Sonarr através de listas", + "AddNewSeriesSearchForCutoffUnmetEpisodes": "Iniciar busca por episódios de corte não atendidos", + "AddSeriesWithTitle": "Adicionar {title}", + "AddedDate": "Adicionado: {date}", + "Airs": "Exibições", + "AirsDateAtTimeOn": "{date} às {time} em {networkLabel}", + "AirsTbaOn": "TBA em {networkLabel}", + "AirsTimeOn": "{time} em {networkLabel}", + "AirsTomorrowOn": "Amanhã às {time} em {networkLabel}", + "AllFiles": "Todos os Arquivos", + "AllSeriesAreHiddenByTheAppliedFilter": "Todos os resultados foram ocultados pelo filtro aplicado", + "AlreadyInYourLibrary": "Já está na sua biblioteca", + "AlternateTitles": "Títulos Alternativos", + "Anime": "Anime", + "AnimeTypeDescription": "Episódios lançados usando um número de episódio absoluto", + "AnimeTypeFormat": "Número absoluto do episódio ({format})", + "Any": "Quaisquer", + "AppUpdated": "{appName} Atualizado", + "AppUpdatedVersion": "{appName} foi atualizado para a versão `{version}`, para obter as alterações mais recentes, você precisará recarregar {appName} ", + "ApplyTagsHelpTextHowToApplySeries": "Como aplicar etiquetas à série selecionada", + "ApplyTagsHelpTextRemove": "Remover: eliminar as etiquetas adicionadas", + "AptUpdater": "Utilize o apt para instalar a atualização", + "AuthenticationMethod": "Método de Autenticação", + "AuthenticationRequiredPasswordHelpTextWarning": "Insira uma nova senha", + "AuthenticationRequiredUsernameHelpTextWarning": "Insira um novo Nome de Usuário", + "AutoTagging": "Etiqueta Automática", + "AutoTaggingLoadError": "Não foi possível carregar a etiqueta automática", + "AutoTaggingNegateHelpText": "Se marcada, a regra de etiqueta automática não será aplicada se esta condição {implementationName} corresponder.", + "AddNew": "Adicionar Novo", + "Age": "Idade", + "AddAutoTagError": "Não foi possível adicionar uma nova etiqueta automática, tente novamente.", + "AddConditionError": "Não foi possível adicionar uma nova condição, tente novamente.", + "AddConnection": "Adicionar Conexão", + "AddCustomFormat": "Adicionar formato personalizado", + "AddDelayProfile": "Adicionar perfil de atraso", + "AddDownloadClientError": "Não foi possível adicionar um novo cliente de downloads, tente novamente.", + "AddExclusion": "Adicionar exclusão", + "AddImportList": "Adicionar Lista de Importação", + "AddIndexer": "Adicionar indexador", + "AddImportListExclusion": "Adicionar exclusão na lista de importação", + "AddNotificationError": "Não foi possível adicionar uma nova notificação, tente novamente.", + "AddQualityProfile": "Adicionar Perfil de Qualidade", + "AddReleaseProfile": "Adicionar Perfil de Lançamento", + "AddRemotePathMappingError": "Não foi possível adicionar um novo mapeamento de caminho remoto, tente novamente.", + "AfterManualRefresh": "Após a atualização manual", + "AllResultsAreHiddenByTheAppliedFilter": "Todos os resultados foram ocultados pelo filtro aplicado", + "Always": "Sempre", + "AnalyseVideoFiles": "Analisar arquivos de vídeo", + "Analytics": "Análise", + "AnimeEpisodeFormat": "Formato do Episódio do Anime", + "ApplicationUrlHelpText": "O URL desta aplicação externa, incluindo http(s)://, porta e URL base", + "AuthBasic": "Básico (pop-up do browser)", + "AppDataDirectory": "Diretório AppData", + "AddCustomFormatError": "Não foi possível adicionar um novo formato personalizado, tente novamente.", + "AddDownloadClient": "Adicionar cliente de transferências", + "AddImportListExclusionError": "Não foi possível adicionar uma nova exclusão na lista de importação, tente novamente.", + "AddNewSeriesRootFolderHelpText": "A subpasta '{folder}' será criada automaticamente", + "AddRemotePathMapping": "Adicionar mapeamento de caminho remoto", + "AnalyseVideoFilesHelpText": "Extraia informações de vídeo, como resolução, tempo de execução e informações de codec de arquivos. Isso requer que o Sonarr leia partes do arquivo que podem causar alta atividade no disco ou na rede durante as varreduras.", + "AnalyticsEnabledHelpText": "Envie informações anônimas de uso e erro para os servidores do Sonarr. Isso inclui informações sobre seu navegador, quais páginas do Sonarr WebUI você usa, relatórios de erros, bem como sistema operacional e versão de tempo de execução. Usaremos essas informações para priorizar recursos e correções de bugs.", + "ApiKey": "Chave da API", + "ApplicationURL": "URL do Aplicativo", + "ApplyTagsHelpTextAdd": "Adicionar: agregar as etiquetas à lista existente de etiquetas", + "AddQualityProfileError": "Não foi possível adicionar um novo perfil de qualidade, tente novamente.", + "ApplyTagsHelpTextHowToApplyDownloadClients": "Como aplicar etiquetas aos clientes de download selecionados", + "ApplyTagsHelpTextReplace": "Substituir: mudar as etiquetas pelas adicionadas (deixe em branco para limpar todas as etiquetas)", + "AuthenticationMethodHelpTextWarning": "Selecione um método de autenticação válido", + "AuthenticationRequiredWarning": "Para evitar o acesso remoto sem autenticação, {appName} agora exige que a autenticação esteja habilitada. Opcionalmente, você pode desabilitar a autenticação de endereços locais.", + "AutoRedownloadFailedHelpText": "Procurar automaticamente e tente baixar uma versão diferente", + "AudioInfo": "Informações do áudio", + "AuthForm": "Formulários (Página de Login)", + "AuthenticationRequiredHelpText": "Altere para quais solicitações a autenticação é necessária. Não mude a menos que você entenda os riscos.", + "Agenda": "Agenda", + "All": "Todos", + "AnEpisodeIsDownloading": "Um episódio está sendo baixado", + "AudioLanguages": "Idiomas do Áudio", + "AddConditionImplementation": "Adicionar Condição - {implementationName}", + "AddConnectionImplementation": "Adicionar Conexão - {implementationName}", + "AddCustomFilter": "Adicionar Filtro Personalizado", + "AddDownloadClientImplementation": "Adicionar Cliente de Download - {implementationName}", + "AddImportListImplementation": "Adicionar Lista de Importação - {implementationName}", + "AddIndexerError": "Não foi possível adicionar um novo indexador, tente novamente.", + "AddIndexerImplementation": "Adicionar Indexador - {implementationName}", + "AddNewRestriction": "Adicionar nova restrição", + "AddNewSeriesSearchForMissingEpisodes": "Iniciar pesquisa por episódios ausentes", + "AddToDownloadQueue": "Adicionar à fila de download", + "AddList": "Adicionar Lista", + "AddListError": "Não foi possível adicionar uma nova lista, tente novamente.", + "AddListExclusionError": "Não foi possível adicionar uma nova exclusão de lista, tente novamente.", + "AddedToDownloadQueue": "Adicionado à fila de download", + "AllSeriesInRootFolderHaveBeenImported": "Todas as séries em {path} foram importadas", + "AddNewSeries": "Adicionar Nova Série", + "AddNewSeriesError": "Erro ao carregar resultados da busca. Por favor, tente novamente.", + "AddNewSeriesHelpText": "É fácil adicionar uma nova série, apenas comece a escrever o nome da série que quer adicionar." } diff --git a/src/NzbDrone.Core/Localization/Core/pt_BR.json b/src/NzbDrone.Core/Localization/Core/pt_BR.json index ec91c7a81..3ba1cd817 100644 --- a/src/NzbDrone.Core/Localization/Core/pt_BR.json +++ b/src/NzbDrone.Core/Localization/Core/pt_BR.json @@ -1272,7 +1272,7 @@ "OverrideGrabNoQuality": "A qualidade deve ser selecionada", "OverrideGrabNoSeries": "A série deve ser selecionada", "Parse": "Analisar", - "ParseModalHelpTextDetails": "{appName} tentará analisar o título e mostrar detalhes sobre ele", + "ParseModalHelpTextDetails": "Sonarr tentará analisar o título e mostrar detalhes sobre ele", "RecentChanges": "Mudanças Recentes", "ReleaseRejected": "Lançamento Rejeitado", "ReleaseSceneIndicatorAssumingScene": "Assumindo a Numeração da Scene.", diff --git a/src/NzbDrone.Core/Localization/Core/ro.json b/src/NzbDrone.Core/Localization/Core/ro.json index 25fecf030..6869a799f 100644 --- a/src/NzbDrone.Core/Localization/Core/ro.json +++ b/src/NzbDrone.Core/Localization/Core/ro.json @@ -159,5 +159,37 @@ "SelectFolderModalTitle": "{modalTitle} - Selectați folder", "SelectDownloadClientModalTitle": "{modalTitle} - Selectați clientul de descărcare", "SelectDropdown": "Selectați...", - "True": "Adevărat" + "True": "Adevărat", + "FormatAgeMinutes": "minute", + "TablePageSize": "Mărimea Paginii", + "QueueLoadError": "Nu s-a putut încărca coada de așteptare", + "DownloadIgnored": "Descărcarea ignorată", + "BlocklistLoadError": "Imposibil de încărcat lista neagră", + "FullColorEvents": "Evenimente pline de culoare", + "InfoUrl": "URL informații", + "AddConnection": "Adăugați conexiune", + "DeletedReasonUpgrade": "Fișierul a fost șters pentru a importa un upgrade", + "CustomFormatJson": "Format JSON personalizat", + "HistoryLoadError": "Istoricul nu poate fi încărcat", + "DefaultNameCopiedSpecification": "{name} - Copie", + "DefaultNameCopiedProfile": "{name} - Copie", + "DeletedReasonManual": "Fișierul a fost șters prin interfața de utilizare", + "NoHistoryFound": "Nu s-a găsit istoric", + "Or": "sau", + "PendingDownloadClientUnavailable": "În așteptare - Clientul de descărcare nu este disponibil", + "ReleaseProfilesLoadError": "Nu se pot încărca profilurile", + "UnknownEventTooltip": "Eveniment necunoscut", + "Unknown": "Necunoscut", + "AddConnectionImplementation": "Adăugați conexiune - {implementationName}", + "AddDownloadClientImplementation": "Adăugați client de descărcare - {implementationName}", + "AddIndexerImplementation": "Adăugați Indexator - {implementationName}", + "Umask": "", + "AuthenticationRequiredPasswordHelpTextWarning": "Introduceți o parolă nouă", + "FormatAgeHours": "ore", + "FormatAgeHour": "oră", + "FormatAgeDays": "zile", + "FormatAgeDay": "zi", + "TablePageSizeHelpText": "Numărul de articole de afișat pe fiecare pagină", + "RemoveSelectedBlocklistMessageText": "Sigur doriți să eliminați elementele selectate din lista neagră?", + "AuthenticationRequiredUsernameHelpTextWarning": "Introduceți un nou nume de utilizator" } diff --git a/src/NzbDrone.Core/Localization/Core/ru.json b/src/NzbDrone.Core/Localization/Core/ru.json index a7a0d0c80..df6c65d17 100644 --- a/src/NzbDrone.Core/Localization/Core/ru.json +++ b/src/NzbDrone.Core/Localization/Core/ru.json @@ -146,5 +146,8 @@ "LanguagesLoadError": "Невозможно загрузить языки", "RemoveQueueItem": "Удалить - {sourceTitle}", "ResetQualityDefinitionsMessageText": "Вы уверены, что хотите сбросить определения качества?", - "RemoveSelectedItemsQueueMessageText": "Вы действительно хотите удалить {selectedCount} элементов из очереди?" + "RemoveSelectedItemsQueueMessageText": "Вы действительно хотите удалить {selectedCount} элементов из очереди?", + "AuthenticationMethod": "Способ авторизации", + "AuthenticationRequiredPasswordHelpTextWarning": "Введите новый пароль", + "AuthenticationRequiredUsernameHelpTextWarning": "Введите новое имя пользователя" } diff --git a/src/NzbDrone.Core/Localization/Core/zh_CN.json b/src/NzbDrone.Core/Localization/Core/zh_CN.json index 54b2811cb..734024ea2 100644 --- a/src/NzbDrone.Core/Localization/Core/zh_CN.json +++ b/src/NzbDrone.Core/Localization/Core/zh_CN.json @@ -5,7 +5,7 @@ "DeleteCustomFormatMessageText": "是否确实要删除条件“{0}”?", "ApiKeyValidationHealthCheckMessage": "请将API密钥更新为至少{0}个字符长。您可以通过设置或配置文件执行此操作", "RemoveSelectedItemQueueMessageText": "您确定要从队列中删除一个项目吗?", - "RemoveSelectedItemsQueueMessageText": "您确定要从队列中删除 {selectedCount} 个项目吗?", + "RemoveSelectedItemsQueueMessageText": "您确定要从队列中删除{selectedCount}项吗?", "ApplyChanges": "应用更改", "AutomaticAdd": "自动添加", "EditSelectedDownloadClients": "编辑选定的下载客户端", @@ -135,8 +135,8 @@ "RemotePathMappingFileRemovedHealthCheckMessage": "文件{0} 在处理的过程中被部分删除。", "AutoAdd": "自动添加", "Cancel": "取消", - "DeleteSelectedDownloadClientsMessageText": "是否确实要删除 {count} 个选定的下载客户端?", - "DeleteSelectedImportListsMessageText": "是否确实要删除 {count} 个选定的导入列表?", + "DeleteSelectedDownloadClientsMessageText": "您确定要删除{count}选定的下载客户端吗?", + "DeleteSelectedImportListsMessageText": "您确定要删除选定的{count}导入列表吗?", "Details": "详情", "Name": "名称", "No": "否", @@ -166,7 +166,7 @@ "Date": "日期", "DeleteBackup": "删除备份", "DeleteCustomFormat": "删除自定义命名格式", - "DeleteSelectedIndexersMessageText": "是否确实要删除 {count} 个选定的索引器?", + "DeleteSelectedIndexersMessageText": "您确定要删除{count}选定的索引器吗?", "Deleted": "已删除", "Disabled": "禁用", "Discord": "Discord", @@ -219,7 +219,7 @@ "RelativePath": "相对路径", "ReleaseGroups": "制作团队", "Reload": "重新加载", - "DeleteBackupMessageText": "您确定要删除备份 '{name}' 吗?", + "DeleteBackupMessageText": "您确定要删除备份“{name}”吗?", "EnableAutomaticSearch": "启用自动搜索", "EpisodeAirDate": "剧集播出日期", "IndexerSearchNoInteractiveHealthCheckMessage": "没有启用交互式搜索的索引器,{appName}将不提供任何交互式搜索结果", @@ -310,7 +310,7 @@ "Special": "特色", "RemotePathMappingImportFailedHealthCheckMessage": "{appName}无法导入剧集。查看日志以了解详细信息。", "RemotePathMappingWrongOSPathHealthCheckMessage": "远程下载客户端{0}将文件下载在{1}中,但这不是有效的{2}路径。查看远程路径映射并更新下载客户端设置。", - "RemoveFromDownloadClientHelpTextWarning": "移除操作会从下载客户端中删除任务和已下载文件。", + "RemoveFromDownloadClientHelpTextWarning": "删除将从下载客户端删除下载和文件。", "Renamed": "已重命名", "RootFolderPath": "根目录路径", "Runtime": "时长", @@ -448,7 +448,7 @@ "ConnectSettingsSummary": "通知、与媒体服务器/播放器的链接、自定义脚本", "ConnectionLost": "连接丢失", "ConnectionLostReconnect": "{appName} 将会尝试自动连接,您也可以点击下方的重新加载。", - "ConnectionLostToBackend": "{appName} 与后端的链接已断开,需要重新加载恢复功能。", + "ConnectionLostToBackend": "{appName}失去了与后端的连接,需要重新加载以恢复功能。", "Connections": "连接", "Continuing": "仍在继续", "CopyToClipboard": "复制到剪贴板", @@ -489,7 +489,7 @@ "DeleteQualityProfileMessageText": "你确定要删除质量配置 “{name}” 吗?", "DeleteReleaseProfile": "删除发布组配置", "DeleteReleaseProfileMessageText": "你确定要删除发布组配置 “{name}” 吗?", - "DeleteRemotePathMapping": "删除远程映射路径", + "DeleteRemotePathMapping": "删除远程路径映射", "DeleteRemotePathMappingMessageText": "你确定要删除此远程路径映射吗?", "DeleteRootFolderMessageText": "你确定要删除根目录 “{path}” 吗?", "DeleteSelectedEpisodeFilesHelpText": "你确定要删除选中的集文件吗?", @@ -519,7 +519,7 @@ "UpdateMechanismHelpText": "使用 {appName} 内置的更新程序或脚本", "AuthenticationRequiredHelpText": "更改需要身份验证的请求。除非您了解风险,否则请勿更改。", "AnEpisodeIsDownloading": "集正在下载", - "AuthenticationRequiredWarning": "为了防止在没有身份验证的情况下进行远程访问,{appName} 需要启用身份验证。请配置您的身份验证方法和凭据。您可以选择从本地地址禁用身份验证。", + "AuthenticationRequiredWarning": "为了防止未经身份验证的远程访问,{appName}现在需要启用身份验证。您可以选择禁用本地地址的身份验证。", "AutomaticSearch": "自动搜索", "BackupFolderHelpText": "相对路径将在{appName}的AppData目录下", "BindAddress": "绑定地址", @@ -546,7 +546,7 @@ "AnimeTypeDescription": "使用绝对集数发布的集数", "ApiKey": "API 密钥", "ApplicationUrlHelpText": "此应用的外部URL,包含 http(s)://、端口和基本URL", - "AuthenticationMethodHelpText": "需要用户名和密码来访问 {appName}", + "AuthenticationMethodHelpText": "需要用户名和密码以访问{appName}", "AuthBasic": "基础(浏览器弹出对话框)", "AuthForm": "表单(登陆页面)", "Authentication": "认证", @@ -588,7 +588,7 @@ "DoneEditingGroups": "完成编辑组", "DoNotPrefer": "不要首选", "DoNotUpgradeAutomatically": "不要自动升级", - "DownloadIgnored": "下载被忽略", + "DownloadIgnored": "忽略下载", "DownloadIgnoredTooltip": "集下载被忽略", "EditAutoTag": "编辑自动标签", "AddAutoTagError": "无法添加一个新自动标签,请重试。", @@ -761,7 +761,7 @@ "EnableInteractiveSearchHelpText": "当手动搜索启用时使用", "EnableMediaInfoHelpText": "从文件中提取视频信息,如分辨率、运行时间和编解码器信息。这需要{appName}读取文件,可能导致扫描期间磁盘或网络出现高负载。", "GrabbedHistoryTooltip": "集抓取自 {indexer} 并发送至 {downloadClient}", - "HealthMessagesInfoBox": "您可以通过单击行尾的wiki链接(图书图标)或检查您的[日志]({link})来查找有关这些健康检查的更多信息。如果您在解读这些信息时遇到困难,可以通过以下链接联系我们获得支持。", + "HealthMessagesInfoBox": "您可以通过单击行尾的wiki链接(图书图标)或检查[logs]({link})来查找有关这些运行状况检查消息原因的更多信息。如果你在理解这些信息方面有困难,你可以通过下面的链接联系我们的支持。", "StandardEpisodeFormat": "标准剧集格式", "DefaultNameCopiedSpecification": "{name} - 复制", "AddListExclusion": "添加排除列表", @@ -772,9 +772,9 @@ "AlternateTitles": "备选标题", "Any": "任何", "AuthenticationMethodHelpTextWarning": "请选择有效的认证方式", - "AuthenticationMethod": "", - "AuthenticationRequiredPasswordHelpTextWarning": "输入一个新的密码", - "AuthenticationRequiredUsernameHelpTextWarning": "输入一个新的用户名", + "AuthenticationMethod": "认证方式", + "AuthenticationRequiredPasswordHelpTextWarning": "输入新密码", + "AuthenticationRequiredUsernameHelpTextWarning": "输入新用户名", "DailyEpisodeFormat": "每日剧集格式", "MediaManagementSettingsSummary": "命名,文件管理和根文件夹设置", "ReplaceIllegalCharacters": "替换非法字符", @@ -818,9 +818,9 @@ "RemotePathMappingsInfo": "很少需要远程路径映射,如果{app}和您的下载客户端在同一系统上,最好匹配您的路径。有关详细信息,请参阅[wiki]({wikiLink})", "IndexerPriorityHelpText": "索引器优先级从1(最高)到50(最低),默认25。当资源连接中断时寻找同等资源时使用,否则{appName}将依旧使用已启用的索引器进行RSS同步并搜索", "IndexerTagHelpText": "仅对至少有一个匹配标记的剧集使用此索引器。留空则适用于所有剧集。", - "InteractiveImportNoFilesFound": "在选中文件夹中找不到视频文件", - "RemoveSelectedBlocklistMessageText": "您确定要从阻止列表中删除所选项目吗?", - "InteractiveImportNoQuality": "必须为每个选中的文件选择质量", + "InteractiveImportNoFilesFound": "在所选文件夹中没有找到视频文件", + "RemoveSelectedBlocklistMessageText": "你确定你想从过滤清单中删除选中的项目吗?", + "InteractiveImportNoQuality": "必须为每个选定的文件选择质量", "RestartSonarr": "重启{appName}", "InteractiveSearchModalHeaderSeason": "手动搜索 - {season}", "SearchMonitored": "搜索已监控", @@ -865,7 +865,7 @@ "VisitTheWikiForMoreDetails": "访问wiki获取更多详细信息: ", "AirsTbaOn": "时间待定,在 {networkLabel} 播出", "AirsTimeOn": "{time} 在 {networkLabel} 播出", - "NotificationStatusAllClientHealthCheckMessage": "由于故障所有通知都不可用", + "NotificationStatusAllClientHealthCheckMessage": "由于故障,所有通知都不可用", "Yesterday": "昨天", "OnGrab": "抓取中", "PendingChangesStayReview": "留下检查更改", @@ -938,7 +938,7 @@ "ExpandAll": "展开所有", "False": "否", "FullColorEvents": "全彩事件", - "FullColorEventsHelpText": "更改样式为将整个事件填充为状态颜色,而不仅仅是左边缘。 不适用于议程", + "FullColorEventsHelpText": "改变样式,用状态颜色为整个事件着色,而不仅仅是左边缘。不适用于议程", "HourShorthand": "时", "ImportedTo": "导入到", "Importing": "导入中", @@ -946,9 +946,9 @@ "IndexersSettingsSummary": "索引器和索引器选项", "InteractiveImport": "手动导入", "InstanceNameHelpText": "选项卡及日志应用名称", - "InteractiveImportLoadError": "无法加载手动导入项目", + "InteractiveImportLoadError": "无法加载手动导入项", "InteractiveImportNoEpisode": "必须为每个选中的文件选择一个或多个集", - "InteractiveImportNoImportMode": "必须选择导入模式", + "InteractiveImportNoImportMode": "必须选择导入方式", "InteractiveImportNoLanguage": "必须为每个选中的文件选择语言", "InteractiveImportNoSeries": "必须为每个选中的文件选择剧集", "InteractiveSearchResultsFailedErrorMessage": "搜索失败,因为 {message}。请尝试刷新剧集信息,并验证是否存在必要信息,再尝试进行搜索。", @@ -1037,22 +1037,22 @@ "OnSeriesAdd": "剧集添加时", "Or": "或", "OrganizeModalHeaderSeason": "整理&重命名 - {season}", - "OrganizeNothingToRename": "成功了!任务已完成,没有文件重命名。", + "OrganizeNothingToRename": "成功!我的工作已完成,没有文件要重命名了。", "OrganizeRelativePaths": "所有路径都相对于: `{path}`", - "OrganizeRenamingDisabled": "重命名已关闭,没有文件重新命名", + "OrganizeRenamingDisabled": "重命名被禁用,没有东西可以重命名", "OrganizeSelectedSeriesModalAlert": "提示:要预览重命名,请点击“取消”,然后点击任何剧集标题并使用此图标:", "OrganizeSelectedSeriesModalConfirmation": "你确定要整理 {count} 个选定剧集中的所有文件?", "OverrideAndAddToDownloadQueue": "覆盖并添加到下载队列", "OverrideGrabModalTitle": "覆盖并抓取 - {title}", "OverrideGrabNoEpisode": "必须至少选择一集", - "OverrideGrabNoLanguage": "必须至少选择一种语言", + "OverrideGrabNoLanguage": "必须选择至少一种语言", "OverrideGrabNoQuality": "必须选择质量", "OverrideGrabNoSeries": "必须选择剧集", "Overview": "概述", "OverviewOptions": "概述选项", - "ParseModalHelpText": "在上面的输入框中输入发布标题", + "ParseModalHelpText": "在上面的输入框中输入一个发行版标题", "ParseModalErrorParsing": "解析错误,请重试。", - "ParseModalUnableToParse": "无法解析提供的标题,请重试。", + "ParseModalUnableToParse": "无法解析提供的标题,请再试一次。", "Paused": "暂停", "Pending": "挂起", "PendingChangesMessage": "您有未保存的修改,确定要退出本页么?", @@ -1097,7 +1097,7 @@ "RemoveFromBlocklist": "从黑名单中移除", "RemoveCompletedDownloadsHelpText": "从下载客户端记录中移除已导入的下载", "RemoveFromQueue": "从队列中移除", - "RemoveQueueItem": "移除 - {sourceTitle}", + "RemoveQueueItem": "删除- {sourceTitle}", "RemoveTagsAutomatically": "自动删除标签", "Repeat": "重复", "ResetDefinitions": "重置定义", @@ -1127,7 +1127,7 @@ "SeasonPremieresOnly": "仅限季首播", "SelectDropdown": "选择...", "SelectEpisodesModalTitle": "{modalTitle} - 选择集", - "SelectFolderModalTitle": "{modalTitle} - 选择文件夹", + "SelectFolderModalTitle": "{modalTitle} -选择文件夹", "SelectQuality": "选择质量", "SelectReleaseGroup": "选择发布组", "SeriesDetailsGoTo": "转到 {title}", @@ -1221,7 +1221,7 @@ "ReleaseSceneIndicatorAssumingScene": "推测场景编号。", "ReleaseSceneIndicatorUnknownMessage": "这一集的编号各不相同,版本与任何已知的映射都不匹配。", "RemoveFailedDownloadsHelpText": "从下载客户端中删除已失败的下载", - "RemoveTagsAutomaticallyHelpText": "如果不满足条件,则自动删除标签", + "RemoveTagsAutomaticallyHelpText": "如果条件不满足,则自动移除标签", "ResetAPIKeyMessageText": "您确定要重置您的 API 密钥吗?", "Rss": "RSS", "SceneInformation": "场景信息", @@ -1234,7 +1234,7 @@ "SizeLimit": "尺寸限制", "ShowRelativeDates": "显示相对日期", "ShowRelativeDatesHelpText": "显示相对日期(今天昨天等)或绝对日期", - "TablePageSizeMaximum": "页面大小不得超过 {maximumValue}", + "TablePageSizeMaximum": "页面大小不得超过{maximumValue}", "UiSettingsSummary": "日历、日期和色弱模式选项", "TagCannotBeDeletedWhileInUse": "无法删除使用中的标签", "UnableToLoadRootFolders": "无法加载根目录", @@ -1284,7 +1284,7 @@ "InteractiveSearch": "手动搜索", "InteractiveSearchModalHeader": "手动搜索", "InvalidFormat": "格式不合法", - "InvalidUILanguage": "您的UI设置为无效语言,请更正并保存您的设置", + "InvalidUILanguage": "您的UI被设置为无效的语言,请纠正它并保存设置", "KeyboardShortcuts": "键盘快捷键", "KeyboardShortcutsSaveSettings": "保存设置", "ListRootFolderHelpText": "根目录文件夹列表项需添加", @@ -1311,7 +1311,7 @@ "MyComputer": "我的电脑", "NamingSettings": "命名设置", "NoEpisodeOverview": "没有集摘要", - "NotificationStatusSingleClientHealthCheckMessage": "由于故障通知不可用: {0}", + "NotificationStatusSingleClientHealthCheckMessage": "由于失败导致通知不可用:{0}", "NotificationTriggers": "通知触发器", "NotificationsLoadError": "无法加载通知连接", "OnApplicationUpdate": "程序更新时", @@ -1320,10 +1320,10 @@ "OnImport": "导入中", "Password": "密码", "PendingDownloadClientUnavailable": "挂起 - 下载客户端不可用", - "QueueLoadError": "读取队列失败", + "QueueLoadError": "加载队列失败", "QuickSearch": "快速搜索", - "ReleaseProfiles": "发行配置文件", - "ReleaseProfilesLoadError": "无法加载发行配置文件", + "ReleaseProfiles": "发行版概要", + "ReleaseProfilesLoadError": "无法加载发行版概要", "RemotePath": "远程路径", "RemoveDownloadsAlert": "移除设置被移至上表中的单个下载客户端设置。", "RemoveRootFolder": "移除根目录", @@ -1372,8 +1372,8 @@ "RssSyncInterval": "RSS同步间隔", "SearchAll": "搜索全部", "AgeWhenGrabbed": "年龄(在被抓取后)", - "BypassDelayIfAboveCustomFormatScoreMinimumScoreHelpText": "跳过首选协议延迟所需的最低自定义格式分数", - "BypassDelayIfAboveCustomFormatScoreHelpText": "当抓取发布版本的分数高于配置的最低自定义格式分数时跳过延时", + "BypassDelayIfAboveCustomFormatScoreMinimumScoreHelpText": "绕过首选协议延迟所需的最小自定义格式分数", + "BypassDelayIfAboveCustomFormatScoreHelpText": "当发布版本的评分高于配置的最小自定义格式评分时,跳过延时", "SearchForAllMissing": "搜索所有缺失的集", "SearchForMissing": "搜索缺少", "SearchForQuery": "搜索 {query}", @@ -1385,7 +1385,7 @@ "OnUpgrade": "升级中", "RemotePathMappings": "远程路径映射", "RemotePathMappingsLoadError": "无法加载远程路径映射", - "RemoveQueueItemConfirmation": "你确定要从队列中移除 '{sourceTitle}' 吗?", + "RemoveQueueItemConfirmation": "您确定要从队列中删除'{sourceTitle}'吗?", "RequiredHelpText": "此 {implementationName} 条件必须匹配才能应用自定义格式。 否则,单个 {implementationName} 匹配就足够了。", "RescanSeriesFolderAfterRefresh": "刷新后重新扫描剧集文件夹", "RestartRequiredToApplyChanges": "{appName}需要重新启动才能应用更改,您想现在重新启动吗?", @@ -1415,7 +1415,7 @@ "TorrentDelayHelpText": "延迟几分钟等待获取torrent", "Underscore": "下划线", "SelectAll": "选择全部", - "SelectDownloadClientModalTitle": "{modalTitle} - 选择下载器", + "SelectDownloadClientModalTitle": "{modalTitle} -选择下载客户端", "WantMoreControlAddACustomFormat": "想要更好地控制首选下载吗?添加[自定义格式](/settings/customformats)", "WeekColumnHeader": "日期格式", "WaitingToImport": "等待导入", @@ -1464,7 +1464,7 @@ "RedownloadFailed": "重新下载失败", "RegularExpressionsCanBeTested": "正则表达式可在[此处](http://regexstorm.net/tester)测试。", "SslPort": "SSL端口", - "TablePageSizeMinimum": "页面大小必须至少为 {minimumValue}", + "TablePageSizeMinimum": "页面大小必须至少为{minimumValue}", "TorrentDelayTime": "Torrent延时:{torrentDelay}", "Torrents": "种子", "TotalFileSize": "总文件体积", From 9a1022386a031c928fc0495d6ab990ebce605ec1 Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Thu, 14 Sep 2023 17:07:32 -0700 Subject: [PATCH 007/136] Fixed: Don't try to create metadata images if source files doesn't exist Closes #6015 --- .../Extras/Metadata/MetadataService.cs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/NzbDrone.Core/Extras/Metadata/MetadataService.cs b/src/NzbDrone.Core/Extras/Metadata/MetadataService.cs index d88c0398d..1cf253e81 100644 --- a/src/NzbDrone.Core/Extras/Metadata/MetadataService.cs +++ b/src/NzbDrone.Core/Extras/Metadata/MetadataService.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -443,6 +443,7 @@ namespace NzbDrone.Core.Extras.Metadata private void DownloadImage(Series series, ImageFileResult image) { var fullPath = Path.Combine(series.Path, image.RelativePath); + var downloaded = true; try { @@ -450,12 +451,19 @@ namespace NzbDrone.Core.Extras.Metadata { _httpClient.DownloadFile(image.Url, fullPath); } - else + else if (_diskProvider.FileExists(image.Url)) { _diskProvider.CopyFile(image.Url, fullPath); } + else + { + downloaded = false; + } - _mediaFileAttributeService.SetFilePermissions(fullPath); + if (downloaded) + { + _mediaFileAttributeService.SetFilePermissions(fullPath); + } } catch (HttpException ex) { From fa5bfc3742c24c5730b77bf8178a423d98fdf50e Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Sun, 17 Sep 2023 23:20:06 -0700 Subject: [PATCH 008/136] New: Optional 'downloadClientId' for pushed releases Closes #6024 --- .../Download/ProcessDownloadDecisions.cs | 122 ++++++++++++++---- .../Download/ProcessedDecisionResult.cs | 11 ++ .../Indexers/ReleasePushController.cs | 15 ++- 3 files changed, 114 insertions(+), 34 deletions(-) create mode 100644 src/NzbDrone.Core/Download/ProcessedDecisionResult.cs diff --git a/src/NzbDrone.Core/Download/ProcessDownloadDecisions.cs b/src/NzbDrone.Core/Download/ProcessDownloadDecisions.cs index 3cab28655..480a33bfa 100644 --- a/src/NzbDrone.Core/Download/ProcessDownloadDecisions.cs +++ b/src/NzbDrone.Core/Download/ProcessDownloadDecisions.cs @@ -14,6 +14,7 @@ namespace NzbDrone.Core.Download public interface IProcessDownloadDecisions { Task ProcessDecisions(List decisions); + Task ProcessDecision(DownloadDecision decision, int? downloadClientId); } public class ProcessDownloadDecisions : IProcessDownloadDecisions @@ -49,7 +50,6 @@ namespace NzbDrone.Core.Download foreach (var report in prioritizedDecisions) { - var remoteEpisode = report.RemoteEpisode; var downloadProtocol = report.RemoteEpisode.Release.DownloadProtocol; // Skip if already grabbed @@ -71,37 +71,48 @@ namespace NzbDrone.Core.Download continue; } - try - { - _logger.Trace("Grabbing from Indexer {0} at priority {1}.", remoteEpisode.Release.Indexer, remoteEpisode.Release.IndexerPriority); - await _downloadService.DownloadReport(remoteEpisode, null); - grabbed.Add(report); - } - catch (ReleaseUnavailableException) - { - _logger.Warn("Failed to download release from indexer, no longer available. " + remoteEpisode); - rejected.Add(report); - } - catch (Exception ex) - { - if (ex is DownloadClientUnavailableException || ex is DownloadClientAuthenticationException) - { - _logger.Debug(ex, "Failed to send release to download client, storing until later. " + remoteEpisode); - PreparePending(pendingAddQueue, grabbed, pending, report, PendingReleaseReason.DownloadClientUnavailable); + var result = await ProcessDecisionInternal(report); - if (downloadProtocol == DownloadProtocol.Usenet) + switch (result) + { + case ProcessedDecisionResult.Grabbed: { - usenetFailed = true; + grabbed.Add(report); + break; } - else if (downloadProtocol == DownloadProtocol.Torrent) + + case ProcessedDecisionResult.Pending: { - torrentFailed = true; + PreparePending(pendingAddQueue, grabbed, pending, report, PendingReleaseReason.Delay); + break; + } + + case ProcessedDecisionResult.Rejected: + { + rejected.Add(report); + break; + } + + case ProcessedDecisionResult.Failed: + { + PreparePending(pendingAddQueue, grabbed, pending, report, PendingReleaseReason.DownloadClientUnavailable); + + if (downloadProtocol == DownloadProtocol.Usenet) + { + usenetFailed = true; + } + else if (downloadProtocol == DownloadProtocol.Torrent) + { + torrentFailed = true; + } + + break; + } + + case ProcessedDecisionResult.Skipped: + { + break; } - } - else - { - _logger.Warn(ex, "Couldn't add report to download queue. " + remoteEpisode); - } } } @@ -113,6 +124,30 @@ namespace NzbDrone.Core.Download return new ProcessedDecisions(grabbed, pending, rejected); } + public async Task ProcessDecision(DownloadDecision decision, int? downloadClientId) + { + if (decision == null) + { + return ProcessedDecisionResult.Skipped; + } + + if (decision.TemporarilyRejected) + { + _pendingReleaseService.Add(decision, PendingReleaseReason.Delay); + + return ProcessedDecisionResult.Pending; + } + + var result = await ProcessDecisionInternal(decision, downloadClientId); + + if (result == ProcessedDecisionResult.Failed) + { + _pendingReleaseService.Add(decision, PendingReleaseReason.DownloadClientUnavailable); + } + + return result; + } + internal List GetQualifiedReports(IEnumerable decisions) { // Process both approved and temporarily rejected @@ -147,5 +182,38 @@ namespace NzbDrone.Core.Download queue.Add(Tuple.Create(report, reason)); pending.Add(report); } + + private async Task ProcessDecisionInternal(DownloadDecision decision, int? downloadClientId = null) + { + var remoteEpisode = decision.RemoteEpisode; + + try + { + _logger.Trace("Grabbing from Indexer {0} at priority {1}.", remoteEpisode.Release.Indexer, remoteEpisode.Release.IndexerPriority); + await _downloadService.DownloadReport(remoteEpisode, downloadClientId); + + return ProcessedDecisionResult.Grabbed; + } + catch (ReleaseUnavailableException) + { + _logger.Warn("Failed to download release from indexer, no longer available. " + remoteEpisode); + return ProcessedDecisionResult.Rejected; + } + catch (Exception ex) + { + if (ex is DownloadClientUnavailableException || ex is DownloadClientAuthenticationException) + { + _logger.Debug(ex, + "Failed to send release to download client, storing until later. " + remoteEpisode); + + return ProcessedDecisionResult.Failed; + } + else + { + _logger.Warn(ex, "Couldn't add report to download queue. " + remoteEpisode); + return ProcessedDecisionResult.Skipped; + } + } + } } } diff --git a/src/NzbDrone.Core/Download/ProcessedDecisionResult.cs b/src/NzbDrone.Core/Download/ProcessedDecisionResult.cs new file mode 100644 index 000000000..d1f27ed49 --- /dev/null +++ b/src/NzbDrone.Core/Download/ProcessedDecisionResult.cs @@ -0,0 +1,11 @@ +namespace NzbDrone.Core.Download +{ + public enum ProcessedDecisionResult + { + Grabbed, + Pending, + Rejected, + Failed, + Skipped + } +} diff --git a/src/Sonarr.Api.V3/Indexers/ReleasePushController.cs b/src/Sonarr.Api.V3/Indexers/ReleasePushController.cs index 17358edd4..a8165af46 100644 --- a/src/Sonarr.Api.V3/Indexers/ReleasePushController.cs +++ b/src/Sonarr.Api.V3/Indexers/ReleasePushController.cs @@ -57,22 +57,23 @@ namespace Sonarr.Api.V3.Indexers ResolveIndexer(info); - List decisions; + DownloadDecision decision; lock (PushLock) { - decisions = _downloadDecisionMaker.GetRssDecision(new List { info }); - _downloadDecisionProcessor.ProcessDecisions(decisions).GetAwaiter().GetResult(); + var decisions = _downloadDecisionMaker.GetRssDecision(new List { info }); + + decision = decisions.FirstOrDefault(); + + _downloadDecisionProcessor.ProcessDecision(decision, release.DownloadClientId).GetAwaiter().GetResult(); } - var firstDecision = decisions.FirstOrDefault(); - - if (firstDecision?.RemoteEpisode.ParsedEpisodeInfo == null) + if (decision?.RemoteEpisode.ParsedEpisodeInfo == null) { throw new ValidationException(new List { new ValidationFailure("Title", "Unable to parse", release.Title) }); } - return MapDecisions(new[] { firstDecision }); + return MapDecisions(new[] { decision }); } private void ResolveIndexer(ReleaseInfo release) From 5ff254b6468fc66a337dc0a13f4be53a997c52fd Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Sun, 17 Sep 2023 23:27:44 -0700 Subject: [PATCH 009/136] Fixed: Skip free space check only applies during import Closes #5951 --- src/NzbDrone.Core/Localization/Core/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NzbDrone.Core/Localization/Core/en.json b/src/NzbDrone.Core/Localization/Core/en.json index 8f4171357..8e03a402a 100644 --- a/src/NzbDrone.Core/Localization/Core/en.json +++ b/src/NzbDrone.Core/Localization/Core/en.json @@ -1286,7 +1286,7 @@ "SizeLimit": "Size Limit", "SizeOnDisk": "Size on disk", "SkipFreeSpaceCheck": "Skip Free Space Check", - "SkipFreeSpaceCheckWhenImportingHelpText": "Use when {appName} is unable to detect free space from your series root folder", + "SkipFreeSpaceCheckWhenImportingHelpText": "Use when {appName} is unable to detect free space of your root folder during file import", "SkipRedownload": "Skip Redownload", "SkipRedownloadHelpText": "Prevents {appName} from trying to download an alternative release for this item", "Small": "Small", From 32e1ae2f64827272d351991838200884876e52b4 Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Sun, 17 Sep 2023 23:45:31 -0700 Subject: [PATCH 010/136] Fixed: Don't allow quality profile to be created without all qualities Closes #6005 --- .../Profiles/Quality/QualityItemsValidator.cs | 46 ++++++++++++++++++- .../Quality/QualityProfileController.cs | 2 + 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/src/Sonarr.Api.V3/Profiles/Quality/QualityItemsValidator.cs b/src/Sonarr.Api.V3/Profiles/Quality/QualityItemsValidator.cs index 3db337213..5046ff2ee 100644 --- a/src/Sonarr.Api.V3/Profiles/Quality/QualityItemsValidator.cs +++ b/src/Sonarr.Api.V3/Profiles/Quality/QualityItemsValidator.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using FluentValidation; using FluentValidation.Validators; @@ -17,6 +17,8 @@ namespace Sonarr.Api.V3.Profiles.Quality ruleBuilder.SetValidator(new ItemGroupIdValidator()); ruleBuilder.SetValidator(new UniqueIdValidator()); ruleBuilder.SetValidator(new UniqueQualityIdValidator()); + ruleBuilder.SetValidator(new AllQualitiesValidator()); + return ruleBuilder.SetValidator(new ItemGroupNameValidator()); } } @@ -151,4 +153,46 @@ namespace Sonarr.Api.V3.Profiles.Quality return true; } } + + public class AllQualitiesValidator : PropertyValidator + { + protected override string GetDefaultMessageTemplate() => "Must contain all qualities"; + + protected override bool IsValid(PropertyValidatorContext context) + { + if (context.PropertyValue is not IList items) + { + return false; + } + + var qualityIds = new HashSet(); + + foreach (var item in items) + { + if (item.Id > 0) + { + foreach (var quality in item.Items) + { + qualityIds.Add(quality.Quality.Id); + } + } + else + { + qualityIds.Add(item.Quality.Id); + } + } + + var allQualityIds = NzbDrone.Core.Qualities.Quality.All; + + foreach (var quality in allQualityIds) + { + if (!qualityIds.Contains(quality.Id)) + { + return false; + } + } + + return true; + } + } } diff --git a/src/Sonarr.Api.V3/Profiles/Quality/QualityProfileController.cs b/src/Sonarr.Api.V3/Profiles/Quality/QualityProfileController.cs index d7c24fcf9..87f39b5ad 100644 --- a/src/Sonarr.Api.V3/Profiles/Quality/QualityProfileController.cs +++ b/src/Sonarr.Api.V3/Profiles/Quality/QualityProfileController.cs @@ -25,6 +25,7 @@ namespace Sonarr.Api.V3.Profiles.Quality SharedValidator.RuleFor(c => c.Cutoff).ValidCutoff(); SharedValidator.RuleFor(c => c.Items).ValidItems(); + SharedValidator.RuleFor(c => c.FormatItems).Must(items => { var all = _formatService.All().Select(f => f.Id).ToList(); @@ -32,6 +33,7 @@ namespace Sonarr.Api.V3.Profiles.Quality return all.Except(ids).Empty(); }).WithMessage("All Custom Formats and no extra ones need to be present inside your Profile! Try refreshing your browser."); + SharedValidator.RuleFor(c => c).Custom((profile, context) => { if (profile.FormatItems.Where(x => x.Score > 0).Sum(x => x.Score) < profile.MinFormatScore && From 5eb420bbe12f59d0a5392abf3d351be28ca210e6 Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Sun, 17 Sep 2023 23:56:17 -0700 Subject: [PATCH 011/136] New: Don't treat 400 responses from Notifiarr as errors Closes #5953 --- src/NzbDrone.Core/Notifications/Notifiarr/NotifiarrProxy.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/NzbDrone.Core/Notifications/Notifiarr/NotifiarrProxy.cs b/src/NzbDrone.Core/Notifications/Notifiarr/NotifiarrProxy.cs index 81d41b862..b308460eb 100644 --- a/src/NzbDrone.Core/Notifications/Notifiarr/NotifiarrProxy.cs +++ b/src/NzbDrone.Core/Notifications/Notifiarr/NotifiarrProxy.cs @@ -53,8 +53,10 @@ namespace NzbDrone.Core.Notifications.Notifiarr _logger.Error("HTTP 401 - API key is invalid"); throw new NotifiarrException("API key is invalid"); case 400: + // 400 responses shouldn't be treated as an actual error because it's a misconfiguration + // between Sonarr and Notifiarr for a specific event, but shouldn't stop all events. _logger.Error("HTTP 400 - Unable to send notification. Ensure Sonarr Integration is enabled & assigned a channel on Notifiarr"); - throw new NotifiarrException("Unable to send notification. Ensure Sonarr Integration is enabled & assigned a channel on Notifiarr"); + break; case 502: case 503: case 504: From 7f2cd8a0e99b537a1c616998514bacdd8468a016 Mon Sep 17 00:00:00 2001 From: Stevie Robinson Date: Tue, 12 Sep 2023 00:19:44 +0200 Subject: [PATCH 012/136] Add health check for dl clients removing completed downloads + enable for sab and qbit --- ...ntRemovesCompletedDownloadsCheckFixture.cs | 77 +++++++++++++++++++ .../Clients/QBittorrent/QBittorrent.cs | 3 +- .../Download/Clients/Sabnzbd/Sabnzbd.cs | 2 + .../Clients/Sabnzbd/SabnzbdCategory.cs | 1 + .../Download/DownloadClientInfo.cs | 1 + ...oadClientRemovesCompletedDownloadsCheck.cs | 64 +++++++++++++++ src/NzbDrone.Core/Localization/Core/en.json | 3 +- 7 files changed, 149 insertions(+), 2 deletions(-) create mode 100644 src/NzbDrone.Core.Test/HealthCheck/Checks/DownloadClientRemovesCompletedDownloadsCheckFixture.cs create mode 100644 src/NzbDrone.Core/HealthCheck/Checks/DownloadClientRemovesCompletedDownloadsCheck.cs diff --git a/src/NzbDrone.Core.Test/HealthCheck/Checks/DownloadClientRemovesCompletedDownloadsCheckFixture.cs b/src/NzbDrone.Core.Test/HealthCheck/Checks/DownloadClientRemovesCompletedDownloadsCheckFixture.cs new file mode 100644 index 000000000..abaad836f --- /dev/null +++ b/src/NzbDrone.Core.Test/HealthCheck/Checks/DownloadClientRemovesCompletedDownloadsCheckFixture.cs @@ -0,0 +1,77 @@ +using System; +using Moq; +using NUnit.Framework; +using NzbDrone.Core.Download; +using NzbDrone.Core.Download.Clients; +using NzbDrone.Core.HealthCheck.Checks; +using NzbDrone.Core.Localization; +using NzbDrone.Core.Test.Framework; +using NzbDrone.Test.Common; + +namespace NzbDrone.Core.Test.HealthCheck.Checks +{ + [TestFixture] + public class DownloadClientRemovesCompletedDownloadsCheckFixture : CoreTest + { + private DownloadClientInfo _clientStatus; + private Mock _downloadClient; + + private static Exception[] DownloadClientExceptions = + { + new DownloadClientUnavailableException("error"), + new DownloadClientAuthenticationException("error"), + new DownloadClientException("error") + }; + + [SetUp] + public void Setup() + { + _clientStatus = new DownloadClientInfo + { + IsLocalhost = true, + SortingMode = null, + RemovesCompletedDownloads = true + }; + + _downloadClient = Mocker.GetMock(); + _downloadClient.Setup(s => s.Definition) + .Returns(new DownloadClientDefinition { Name = "Test" }); + + _downloadClient.Setup(s => s.GetStatus()) + .Returns(_clientStatus); + + Mocker.GetMock() + .Setup(s => s.GetDownloadClients(It.IsAny())) + .Returns(new IDownloadClient[] { _downloadClient.Object }); + + Mocker.GetMock() + .Setup(s => s.GetLocalizedString(It.IsAny())) + .Returns("Some Warning Message"); + } + + [Test] + public void should_return_warning_if_removing_completed_downloads_is_enabled() + { + Subject.Check().ShouldBeWarning(); + } + + [Test] + public void should_return_ok_if_remove_completed_downloads_is_not_enabled() + { + _clientStatus.RemovesCompletedDownloads = false; + Subject.Check().ShouldBeOk(); + } + + [Test] + [TestCaseSource("DownloadClientExceptions")] + public void should_return_ok_if_client_throws_downloadclientexception(Exception ex) + { + _downloadClient.Setup(s => s.GetStatus()) + .Throws(ex); + + Subject.Check().ShouldBeOk(); + + ExceptionVerification.ExpectedErrors(0); + } + } +} diff --git a/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrent.cs b/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrent.cs index 06067c32d..cb8d12e3f 100644 --- a/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrent.cs +++ b/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrent.cs @@ -387,7 +387,8 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent return new DownloadClientInfo { IsLocalhost = Settings.Host == "127.0.0.1" || Settings.Host == "localhost", - OutputRootFolders = new List { _remotePathMappingService.RemapRemoteToLocal(Settings.Host, destDir) } + OutputRootFolders = new List { _remotePathMappingService.RemapRemoteToLocal(Settings.Host, destDir) }, + RemovesCompletedDownloads = (config.MaxRatioEnabled || config.MaxSeedingTimeEnabled) && (config.MaxRatioAction == QBittorrentMaxRatioAction.Remove || config.MaxRatioAction == QBittorrentMaxRatioAction.DeleteFiles) }; } diff --git a/src/NzbDrone.Core/Download/Clients/Sabnzbd/Sabnzbd.cs b/src/NzbDrone.Core/Download/Clients/Sabnzbd/Sabnzbd.cs index e0fea34dc..dba7b20c3 100644 --- a/src/NzbDrone.Core/Download/Clients/Sabnzbd/Sabnzbd.cs +++ b/src/NzbDrone.Core/Download/Clients/Sabnzbd/Sabnzbd.cs @@ -276,6 +276,8 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd status.OutputRootFolders = new List { _remotePathMappingService.RemapRemoteToLocal(Settings.Host, category.FullPath) }; } + status.RemovesCompletedDownloads = config.Misc.history_retention != "0"; + return status; } diff --git a/src/NzbDrone.Core/Download/Clients/Sabnzbd/SabnzbdCategory.cs b/src/NzbDrone.Core/Download/Clients/Sabnzbd/SabnzbdCategory.cs index e25a91701..189b08257 100644 --- a/src/NzbDrone.Core/Download/Clients/Sabnzbd/SabnzbdCategory.cs +++ b/src/NzbDrone.Core/Download/Clients/Sabnzbd/SabnzbdCategory.cs @@ -29,6 +29,7 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd public string[] date_categories { get; set; } public bool enable_date_sorting { get; set; } public bool pre_check { get; set; } + public string history_retention { get; set; } } public class SabnzbdCategory diff --git a/src/NzbDrone.Core/Download/DownloadClientInfo.cs b/src/NzbDrone.Core/Download/DownloadClientInfo.cs index c97481924..2106b895a 100644 --- a/src/NzbDrone.Core/Download/DownloadClientInfo.cs +++ b/src/NzbDrone.Core/Download/DownloadClientInfo.cs @@ -12,6 +12,7 @@ namespace NzbDrone.Core.Download public bool IsLocalhost { get; set; } public string SortingMode { get; set; } + public bool RemovesCompletedDownloads { get; set; } public List OutputRootFolders { get; set; } } } diff --git a/src/NzbDrone.Core/HealthCheck/Checks/DownloadClientRemovesCompletedDownloadsCheck.cs b/src/NzbDrone.Core/HealthCheck/Checks/DownloadClientRemovesCompletedDownloadsCheck.cs new file mode 100644 index 000000000..8683195cb --- /dev/null +++ b/src/NzbDrone.Core/HealthCheck/Checks/DownloadClientRemovesCompletedDownloadsCheck.cs @@ -0,0 +1,64 @@ +using System; +using NLog; +using NzbDrone.Core.Datastore.Events; +using NzbDrone.Core.Download; +using NzbDrone.Core.Download.Clients; +using NzbDrone.Core.Localization; +using NzbDrone.Core.RemotePathMappings; +using NzbDrone.Core.RootFolders; +using NzbDrone.Core.ThingiProvider.Events; + +namespace NzbDrone.Core.HealthCheck.Checks +{ + [CheckOn(typeof(ProviderUpdatedEvent))] + [CheckOn(typeof(ProviderDeletedEvent))] + [CheckOn(typeof(ModelEvent))] + [CheckOn(typeof(ModelEvent))] + + public class DownloadClientRemovesCompletedDownloadsCheck : HealthCheckBase, IProvideHealthCheck + { + private readonly IProvideDownloadClient _downloadClientProvider; + private readonly Logger _logger; + + public DownloadClientRemovesCompletedDownloadsCheck(IProvideDownloadClient downloadClientProvider, + Logger logger, + ILocalizationService localizationService) + : base(localizationService) + { + _downloadClientProvider = downloadClientProvider; + _logger = logger; + } + + public override HealthCheck Check() + { + var clients = _downloadClientProvider.GetDownloadClients(true); + + foreach (var client in clients) + { + try + { + var clientName = client.Definition.Name; + var status = client.GetStatus(); + + if (status.RemovesCompletedDownloads) + { + return new HealthCheck(GetType(), + HealthCheckResult.Warning, + string.Format(_localizationService.GetLocalizedString("DownloadClientRemovesCompletedDownloadsHealthCheckMessage"), clientName, "Sonarr"), + "#download-client-removes-completed-downloads"); + } + } + catch (DownloadClientException ex) + { + _logger.Debug(ex, "Unable to communicate with {0}", client.Definition.Name); + } + catch (Exception ex) + { + _logger.Error(ex, "Unknown error occurred in DownloadClientHistoryRetentionCheck HealthCheck"); + } + } + + return new HealthCheck(GetType()); + } + } +} diff --git a/src/NzbDrone.Core/Localization/Core/en.json b/src/NzbDrone.Core/Localization/Core/en.json index 8e03a402a..e685c1585 100644 --- a/src/NzbDrone.Core/Localization/Core/en.json +++ b/src/NzbDrone.Core/Localization/Core/en.json @@ -358,6 +358,7 @@ "DownloadClientCheckNoneAvailableHealthCheckMessage": "No download client is available", "DownloadClientCheckUnableToCommunicateWithHealthCheckMessage": "Unable to communicate with {0}.", "DownloadClientOptionsLoadError": "Unable to load download client options", + "DownloadClientRemovesCompletedDownloadsHealthCheckMessage": "Download client {0} is set to remove completed downloads. This can result in downloads being removed from your client before {1} can import them.", "DownloadClientRootFolderHealthCheckMessage": "Download client {0} places downloads in the root folder {1}. You should not download to a root folder.", "DownloadClientSettings": "Download Client Settings", "DownloadClientSortingHealthCheckMessage": "Download client {0} has {1} sorting enabled for {appName}'s category. You should disable sorting in your download client to avoid import issues.", @@ -531,8 +532,8 @@ "FormatAgeHours": "hours", "FormatAgeMinute": "minute", "FormatAgeMinutes": "minutes", - "FormatDateTimeRelative": "{relativeDay}, {formattedDate} {formattedTime}", "FormatDateTime": "{formattedDate} {formattedTime}", + "FormatDateTimeRelative": "{relativeDay}, {formattedDate} {formattedTime}", "FormatRuntimeHours": "{hours}h", "FormatRuntimeMinutes": "{minutes}m", "FormatShortTimeSpanHours": "{hours} hour(s)", From f2b0fc946e1fb1b4649f1b46a003bd2add09a461 Mon Sep 17 00:00:00 2001 From: Qstick Date: Mon, 18 Sep 2023 03:09:12 -0400 Subject: [PATCH 013/136] Fixed: Show correct error on unauthorized caps call --- .../Indexers/Newznab/NewznabCapabilitiesProvider.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/NzbDrone.Core/Indexers/Newznab/NewznabCapabilitiesProvider.cs b/src/NzbDrone.Core/Indexers/Newznab/NewznabCapabilitiesProvider.cs index 25599f67c..d84bd49c9 100644 --- a/src/NzbDrone.Core/Indexers/Newznab/NewznabCapabilitiesProvider.cs +++ b/src/NzbDrone.Core/Indexers/Newznab/NewznabCapabilitiesProvider.cs @@ -7,6 +7,7 @@ using NzbDrone.Common.Cache; using NzbDrone.Common.Extensions; using NzbDrone.Common.Http; using NzbDrone.Common.Serializer; +using NzbDrone.Core.Indexers.Exceptions; namespace NzbDrone.Core.Indexers.Newznab { @@ -73,6 +74,13 @@ namespace NzbDrone.Core.Indexers.Newznab _logger.Debug(ex, "Failed to parse newznab api capabilities for {0}", indexerSettings.BaseUrl); throw; } + catch (ApiKeyException ex) + { + ex.WithData(response, 128 * 1024); + _logger.Trace("Unexpected Response content ({0} bytes): {1}", response.ResponseData.Length, response.Content); + _logger.Debug(ex, "Failed to parse newznab api capabilities for {0}, invalid API key", indexerSettings.BaseUrl); + throw; + } catch (Exception ex) { ex.WithData(response, 128 * 1024); From a2f16bddfd7dfba207c5feaaf472913c38dc3e25 Mon Sep 17 00:00:00 2001 From: Bogdan Date: Sun, 10 Sep 2023 18:39:13 +0300 Subject: [PATCH 014/136] Preserve the protocol in Series Image --- frontend/src/Series/SeriesImage.js | 8 +++----- package.json | 1 + yarn.lock | 7 +++++++ 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/frontend/src/Series/SeriesImage.js b/frontend/src/Series/SeriesImage.js index fddc158b8..b1bd738de 100644 --- a/frontend/src/Series/SeriesImage.js +++ b/frontend/src/Series/SeriesImage.js @@ -7,12 +7,10 @@ function findImage(images, coverType) { } function getUrl(image, coverType, size) { - if (image) { - // Remove protocol - let url = image.url.replace(/^https?:/, ''); - url = url.replace(`${coverType}.jpg`, `${coverType}-${size}.jpg`); + const imageUrl = image?.url; - return url; + if (imageUrl) { + return imageUrl.replace(`${coverType}.jpg`, `${coverType}-${size}.jpg`); } } diff --git a/package.json b/package.json index c09c4e1b7..a3ad4b517 100644 --- a/package.json +++ b/package.json @@ -94,6 +94,7 @@ "@babel/preset-typescript": "7.22.11", "@types/classnames": "2.3.1", "@types/lodash": "4.14.194", + "@types/react-lazyload": "3.2.0", "@types/react-router-dom": "5.3.3", "@types/react-text-truncate": "0.14.1", "@types/react-window": "1.8.5", diff --git a/yarn.lock b/yarn.lock index c209a992e..3f761abd6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1443,6 +1443,13 @@ dependencies: "@types/react" "*" +"@types/react-lazyload@3.2.0": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@types/react-lazyload/-/react-lazyload-3.2.0.tgz#b763f8f0c724df2c969d7e0b3a56c6aa2720fa1f" + integrity sha512-4+r+z8Cf7L/mgxA1vl5uHx5GS/8gY2jqq2p5r5WCm+nUsg9KilwQ+8uaJA3EUlLj57AOzOfGGwwRJ5LOVl8fwA== + dependencies: + "@types/react" "*" + "@types/react-redux@^7.1.16": version "7.1.26" resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.26.tgz#84149f5614e40274bb70fcbe8f7cae6267d548b1" From 11deefc51d36b74b30170073289656ff1862162f Mon Sep 17 00:00:00 2001 From: Weblate Date: Mon, 18 Sep 2023 07:07:43 +0000 Subject: [PATCH 015/136] Multiple Translations updated by Weblate ignore-downstream Co-authored-by: Fixer Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/ro/ Translation: Servarr/Sonarr --- src/NzbDrone.Core/Localization/Core/ro.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NzbDrone.Core/Localization/Core/ro.json b/src/NzbDrone.Core/Localization/Core/ro.json index 6869a799f..212c2e146 100644 --- a/src/NzbDrone.Core/Localization/Core/ro.json +++ b/src/NzbDrone.Core/Localization/Core/ro.json @@ -183,7 +183,7 @@ "AddConnectionImplementation": "Adăugați conexiune - {implementationName}", "AddDownloadClientImplementation": "Adăugați client de descărcare - {implementationName}", "AddIndexerImplementation": "Adăugați Indexator - {implementationName}", - "Umask": "", + "Umask": "Umask", "AuthenticationRequiredPasswordHelpTextWarning": "Introduceți o parolă nouă", "FormatAgeHours": "ore", "FormatAgeHour": "oră", From 07f816ffb18ac34090c2f8ba25147737299b361d Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Mon, 18 Sep 2023 20:58:47 -0700 Subject: [PATCH 016/136] Fixed: Pushed releases not being properly rejected --- .../Download/ProcessDownloadDecisions.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/NzbDrone.Core/Download/ProcessDownloadDecisions.cs b/src/NzbDrone.Core/Download/ProcessDownloadDecisions.cs index 480a33bfa..640e67582 100644 --- a/src/NzbDrone.Core/Download/ProcessDownloadDecisions.cs +++ b/src/NzbDrone.Core/Download/ProcessDownloadDecisions.cs @@ -131,6 +131,11 @@ namespace NzbDrone.Core.Download return ProcessedDecisionResult.Skipped; } + if (!IsQualifiedReport(decision)) + { + return ProcessedDecisionResult.Rejected; + } + if (decision.TemporarilyRejected) { _pendingReleaseService.Add(decision, PendingReleaseReason.Delay); @@ -149,9 +154,14 @@ namespace NzbDrone.Core.Download } internal List GetQualifiedReports(IEnumerable decisions) + { + return decisions.Where(IsQualifiedReport).ToList(); + } + + internal bool IsQualifiedReport(DownloadDecision decision) { // Process both approved and temporarily rejected - return decisions.Where(c => (c.Approved || c.TemporarilyRejected) && c.RemoteEpisode.Episodes.Any()).ToList(); + return (decision.Approved || decision.TemporarilyRejected) && decision.RemoteEpisode.Episodes.Any(); } private bool IsEpisodeProcessed(List decisions, DownloadDecision report) From d8633b968830fd20d73612e9f0d0559b0bcb304c Mon Sep 17 00:00:00 2001 From: Bogdan Date: Mon, 18 Sep 2023 17:45:35 +0300 Subject: [PATCH 017/136] Preserve the protocol for fanart images --- frontend/src/Series/Details/SeriesDetails.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/frontend/src/Series/Details/SeriesDetails.js b/frontend/src/Series/Details/SeriesDetails.js index 0686d9edd..6f05d593b 100644 --- a/frontend/src/Series/Details/SeriesDetails.js +++ b/frontend/src/Series/Details/SeriesDetails.js @@ -45,11 +45,7 @@ const defaultFontSize = parseInt(fonts.defaultFontSize); const lineHeight = parseFloat(fonts.lineHeight); function getFanartUrl(images) { - const fanartImage = _.find(images, { coverType: 'fanart' }); - if (fanartImage) { - // Remove protocol - return fanartImage.url.replace(/^https?:/, ''); - } + return _.find(images, { coverType: 'fanart' })?.url; } function getExpandedState(newState) { From c7824bb593291634bf14a5f7aa689666969b03bf Mon Sep 17 00:00:00 2001 From: Bogdan Date: Tue, 19 Sep 2023 07:05:28 +0300 Subject: [PATCH 018/136] Fixed: Skip parsing releases without title Closes #6030 --- src/NzbDrone.Common/Extensions/PathExtensions.cs | 2 +- src/NzbDrone.Core/Indexers/HttpIndexerBase.cs | 10 +++++++++- src/NzbDrone.Core/Parser/QualityParser.cs | 7 ++++++- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/NzbDrone.Common/Extensions/PathExtensions.cs b/src/NzbDrone.Common/Extensions/PathExtensions.cs index 896eb3d91..280083e4c 100644 --- a/src/NzbDrone.Common/Extensions/PathExtensions.cs +++ b/src/NzbDrone.Common/Extensions/PathExtensions.cs @@ -171,7 +171,7 @@ namespace NzbDrone.Common.Extensions { if (text.IsNullOrWhiteSpace()) { - throw new ArgumentNullException("text"); + throw new ArgumentNullException(nameof(text)); } return text.IndexOfAny(Path.GetInvalidPathChars()) >= 0; diff --git a/src/NzbDrone.Core/Indexers/HttpIndexerBase.cs b/src/NzbDrone.Core/Indexers/HttpIndexerBase.cs index f22b1d223..19ff8330e 100644 --- a/src/NzbDrone.Core/Indexers/HttpIndexerBase.cs +++ b/src/NzbDrone.Core/Indexers/HttpIndexerBase.cs @@ -307,9 +307,17 @@ namespace NzbDrone.Core.Indexers protected virtual bool IsValidRelease(ReleaseInfo release) { + if (release.Title.IsNullOrWhiteSpace()) + { + _logger.Trace("Invalid Release: '{0}' from indexer: {1}. No title provided.", release.InfoUrl, Definition.Name); + + return false; + } + if (release.DownloadUrl.IsNullOrWhiteSpace()) { - _logger.Trace("Invalid Release: '{0}' from indexer: {1}. No Download URL provided.", release.Title, release.Indexer); + _logger.Trace("Invalid Release: '{0}' from indexer: {1}. No Download URL provided.", release.Title, Definition.Name); + return false; } diff --git a/src/NzbDrone.Core/Parser/QualityParser.cs b/src/NzbDrone.Core/Parser/QualityParser.cs index 22837f1b5..7631887cd 100644 --- a/src/NzbDrone.Core/Parser/QualityParser.cs +++ b/src/NzbDrone.Core/Parser/QualityParser.cs @@ -67,7 +67,12 @@ namespace NzbDrone.Core.Parser public static QualityModel ParseQuality(string name) { - Logger.Debug("Trying to parse quality for {0}", name); + Logger.Debug("Trying to parse quality for '{0}'", name); + + if (name.IsNullOrWhiteSpace()) + { + return new QualityModel { Quality = Quality.Unknown }; + } name = name.Trim(); From 05440111776c2a408d88bf49ce48d800aad7191f Mon Sep 17 00:00:00 2001 From: Weblate Date: Sun, 24 Sep 2023 13:53:40 +0000 Subject: [PATCH 019/136] 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: Daghriry Co-authored-by: Havok Dan Co-authored-by: Jaspils Co-authored-by: SKAL Co-authored-by: Weblate Co-authored-by: 宿命 <331874545@qq.com> Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/ar/ Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/it/ Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/nl/ Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/pt_BR/ Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/zh_CN/ Translation: Servarr/Sonarr --- src/NzbDrone.Core/Localization/Core/ar.json | 5 ++++- src/NzbDrone.Core/Localization/Core/it.json | 3 ++- src/NzbDrone.Core/Localization/Core/nl.json | 11 ++++++++++- src/NzbDrone.Core/Localization/Core/pt_BR.json | 7 ++++--- src/NzbDrone.Core/Localization/Core/zh_CN.json | 7 ++++--- 5 files changed, 24 insertions(+), 9 deletions(-) diff --git a/src/NzbDrone.Core/Localization/Core/ar.json b/src/NzbDrone.Core/Localization/Core/ar.json index 0967ef424..5e2d116df 100644 --- a/src/NzbDrone.Core/Localization/Core/ar.json +++ b/src/NzbDrone.Core/Localization/Core/ar.json @@ -1 +1,4 @@ -{} +{ + "AddAutoTag": "أضف كلمات دلالية تلقائيا", + "AddCondition": "إضافة شرط" +} diff --git a/src/NzbDrone.Core/Localization/Core/it.json b/src/NzbDrone.Core/Localization/Core/it.json index 5d2bc44a8..72de8677a 100644 --- a/src/NzbDrone.Core/Localization/Core/it.json +++ b/src/NzbDrone.Core/Localization/Core/it.json @@ -27,5 +27,6 @@ "Actions": "Azioni", "AptUpdater": "Usa apt per installare l'aggiornamento", "Backup": "Backup", - "ApplyTagsHelpTextHowToApplySeries": "Come applicare le etichette alle serie selezionate" + "ApplyTagsHelpTextHowToApplySeries": "Come applicare le etichette alle serie selezionate", + "AddImportList": "Aggiungi lista da importare" } diff --git a/src/NzbDrone.Core/Localization/Core/nl.json b/src/NzbDrone.Core/Localization/Core/nl.json index 78a89eebe..b922b1c76 100644 --- a/src/NzbDrone.Core/Localization/Core/nl.json +++ b/src/NzbDrone.Core/Localization/Core/nl.json @@ -48,5 +48,14 @@ "AutoTagging": "Automatisch Taggen", "AddAutoTag": "Voeg Automatische Tag toe", "AddCondition": "Voeg Conditie toe", - "CloneAutoTag": "Kopieer Automatische Tag" + "CloneAutoTag": "Kopieer Automatische Tag", + "AbsoluteEpisodeNumbers": "Absolute Afleveringsnummer(s)", + "AbsoluteEpisodeNumber": "Absoluut Afleveringsnummer", + "AddAutoTagError": "Niet in staat een nieuw automatisch label toe te voegen, probeer het opnieuw.", + "AddConditionError": "Niet in staat een nieuwe voorwaarde toe te voegen, probeer het opnieuw.", + "AddConnection": "Voeg connectie toe", + "Absolute": "Absoluut", + "AddANewPath": "Voeg een nieuw pad toe", + "AddConditionImplementation": "Voeg voorwaarde toe - {implementationName}", + "AddConnectionImplementation": "Voeg connectie toe - {implementationName}" } diff --git a/src/NzbDrone.Core/Localization/Core/pt_BR.json b/src/NzbDrone.Core/Localization/Core/pt_BR.json index 3ba1cd817..0433f4d47 100644 --- a/src/NzbDrone.Core/Localization/Core/pt_BR.json +++ b/src/NzbDrone.Core/Localization/Core/pt_BR.json @@ -844,7 +844,7 @@ "SingleEpisode": "Episódio Único", "SizeLimit": "Limite de Tamanho", "SkipFreeSpaceCheck": "Ignorar verificação de espaço livre", - "SkipFreeSpaceCheckWhenImportingHelpText": "Use quando o {appName} não conseguir detectar espaço livre na pasta raiz da série", + "SkipFreeSpaceCheckWhenImportingHelpText": "Use quando {appName} não consegue detectar espaço livre em sua pasta raiz durante a importação do arquivo", "SmartReplace": "Substituição Inteligente", "SmartReplaceHint": "Traço ou Espaço e Traço, dependendo do nome", "Socks4": "Socks4", @@ -1272,7 +1272,7 @@ "OverrideGrabNoQuality": "A qualidade deve ser selecionada", "OverrideGrabNoSeries": "A série deve ser selecionada", "Parse": "Analisar", - "ParseModalHelpTextDetails": "Sonarr tentará analisar o título e mostrar detalhes sobre ele", + "ParseModalHelpTextDetails": "{appName} tentará analisar o título e mostrar detalhes sobre ele", "RecentChanges": "Mudanças Recentes", "ReleaseRejected": "Lançamento Rejeitado", "ReleaseSceneIndicatorAssumingScene": "Assumindo a Numeração da Scene.", @@ -1483,5 +1483,6 @@ "FormatShortTimeSpanMinutes": "{minutes} minuto(s)", "FormatShortTimeSpanSeconds": "{seconds} segundo(s)", "FormatTimeSpanDays": "{days}d {time}", - "Yesterday": "Ontem" + "Yesterday": "Ontem", + "DownloadClientRemovesCompletedDownloadsHealthCheckMessage": "O cliente de download {0} está configurado para remover downloads concluídos. Isso pode resultar na remoção dos downloads do seu cliente antes que {1} possa importá-los." } diff --git a/src/NzbDrone.Core/Localization/Core/zh_CN.json b/src/NzbDrone.Core/Localization/Core/zh_CN.json index 734024ea2..e18e6f135 100644 --- a/src/NzbDrone.Core/Localization/Core/zh_CN.json +++ b/src/NzbDrone.Core/Localization/Core/zh_CN.json @@ -1155,7 +1155,7 @@ "ShowTitleHelpText": "在海报下显示剧集标题", "ShowUnknownSeriesItems": "实现未知剧集项目", "ShowUnknownSeriesItemsHelpText": "显示队列中没有剧集的项目,这可能包括已删除的剧集、电影或 {appName} 类别中的任何其他内容", - "SkipFreeSpaceCheckWhenImportingHelpText": "当 {appName} 无法从您的剧集根文件夹中检测到空闲空间时使用", + "SkipFreeSpaceCheckWhenImportingHelpText": "在文件导入期间,当{appName}无法检测根文件夹的空闲空间时使用", "Small": "小", "SingleEpisodeInvalidFormat": "单集:非法格式", "SkipFreeSpaceCheck": "跳过剩余空间检查", @@ -1442,7 +1442,7 @@ "Monitor": "是否监控", "NotificationTriggersHelpText": "选择触发此通知的事件", "ImportListsSettingsSummary": "从另一个{appName}或Trakt列表导入并管理排除列表", - "ParseModalHelpTextDetails": "{appName}将尝试解析标题并向您展示有关它的详细信息", + "ParseModalHelpTextDetails": "{appName}将尝试解析标题并显示有关标题的详细信息", "Proxy": "代理", "ImportScriptPath": "导入脚本路径", "IncludeCustomFormatWhenRenamingHelpText": "在 {Custom Formats} 中包含重命名格式", @@ -1483,5 +1483,6 @@ "EpisodeFilesLoadError": "无法加载集文件", "Files": "文件", "SeriesDetailsOneEpisodeFile": "1个集文件", - "UrlBase": "基本URL" + "UrlBase": "基本URL", + "DownloadClientRemovesCompletedDownloadsHealthCheckMessage": "下载客户端{0}设置为删除已完成的下载。这可能导致在{1}可以导入下载之前从您的客户端删除下载。" } From a1ea7accb32bc72f61ed4531d109f76fad843939 Mon Sep 17 00:00:00 2001 From: Bogdan Date: Tue, 19 Sep 2023 20:12:30 +0300 Subject: [PATCH 020/136] Avoid returning null in static resource mapper Task --- src/Sonarr.Http/Frontend/Mappers/StaticResourceMapperBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Sonarr.Http/Frontend/Mappers/StaticResourceMapperBase.cs b/src/Sonarr.Http/Frontend/Mappers/StaticResourceMapperBase.cs index d7e812af9..3f3982ac1 100644 --- a/src/Sonarr.Http/Frontend/Mappers/StaticResourceMapperBase.cs +++ b/src/Sonarr.Http/Frontend/Mappers/StaticResourceMapperBase.cs @@ -50,7 +50,7 @@ namespace Sonarr.Http.Frontend.Mappers _logger.Warn("File {0} not found", filePath); - return null; + return Task.FromResult(null); } protected virtual Stream GetContentStream(string filePath) From 3620ad25170525786acd807cd6a151791116dd64 Mon Sep 17 00:00:00 2001 From: Bogdan Date: Thu, 21 Sep 2023 02:53:24 +0300 Subject: [PATCH 021/136] Fix typo for Romanian regex group ignore-downstream --- src/NzbDrone.Core/Parser/LanguageParser.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/NzbDrone.Core/Parser/LanguageParser.cs b/src/NzbDrone.Core/Parser/LanguageParser.cs index c55e4d07b..e9390a766 100644 --- a/src/NzbDrone.Core/Parser/LanguageParser.cs +++ b/src/NzbDrone.Core/Parser/LanguageParser.cs @@ -19,7 +19,7 @@ namespace NzbDrone.Core.Parser new RegexReplace(@".*?[_. ](S\d{2}(?:E\d{2,4})*[_. ].*)", "$1", RegexOptions.Compiled | RegexOptions.IgnoreCase) }; - private static readonly Regex LanguageRegex = new Regex(@"(?:\W|_)(?\b(?:ing|eng)\b)|(?\b(?:ita|italian)\b)|(?german\b|videomann|ger[. ]dub)|(?flemish)|(?greek)|(?(?:\W|_)(?:FR|VF|VF2|VFF|VFQ|TRUEFRENCH)(?:\W|_))|(?\brus\b)|(?\b(?:HUNDUB|HUN)\b)|(?\bHebDub\b)|(?\b(?:PL\W?DUB|DUB\W?PL|LEK\W?PL|PL\W?LEK)\b)|(?\[(?:CH[ST]|BIG5|GB)\]|简|繁|字幕)|(?\bbgaudio\b)|(?\b(?:español|castellano|esp|spa(?!\(Latino\)))\b)|(?\b(?:ukr)\b)|(?\b(?:THAI)\b)|(?\b(?:RoDubbed|ROMANIAN)\b)|(?[-,. ]cat[. ](?:DD|subs)|\b(?:catalan|catalán)\b)", + private static readonly Regex LanguageRegex = new Regex(@"(?:\W|_)(?\b(?:ing|eng)\b)|(?\b(?:ita|italian)\b)|(?german\b|videomann|ger[. ]dub)|(?flemish)|(?greek)|(?(?:\W|_)(?:FR|VF|VF2|VFF|VFQ|TRUEFRENCH)(?:\W|_))|(?\brus\b)|(?\b(?:HUNDUB|HUN)\b)|(?\bHebDub\b)|(?\b(?:PL\W?DUB|DUB\W?PL|LEK\W?PL|PL\W?LEK)\b)|(?\[(?:CH[ST]|BIG5|GB)\]|简|繁|字幕)|(?\bbgaudio\b)|(?\b(?:español|castellano|esp|spa(?!\(Latino\)))\b)|(?\b(?:ukr)\b)|(?\b(?:THAI)\b)|(?\b(?:RoDubbed|ROMANIAN)\b)|(?[-,. ]cat[. ](?:DD|subs)|\b(?:catalan|catalán)\b)", RegexOptions.IgnoreCase | RegexOptions.Compiled); private static readonly Regex CaseSensitiveLanguageRegex = new Regex(@"(?:(?i)(?\bLT\b)|(?\bCZ\b)|(?\bPL\b)|(?\bBG\b)|(?\bSK\b))(?:(?i)(?![\W|_|^]SUB))", @@ -362,7 +362,7 @@ namespace NzbDrone.Core.Parser languages.Add(Language.Thai); } - if (match.Groups["romainian"].Success) + if (match.Groups["romanian"].Success) { languages.Add(Language.Romanian); } From a3938d8e0264b48b35f4715cbc15329fb489218a Mon Sep 17 00:00:00 2001 From: Stevie Robinson Date: Wed, 27 Sep 2023 16:48:15 +0200 Subject: [PATCH 022/136] Fixed: SABnzbd history retention to allow at least 14 days Closes #6044 --- .../SabnzbdTests/SabnzbdFixture.cs | 24 +++++++++++++++++++ .../Download/Clients/Sabnzbd/Sabnzbd.cs | 11 ++++++++- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/NzbDrone.Core.Test/Download/DownloadClientTests/SabnzbdTests/SabnzbdFixture.cs b/src/NzbDrone.Core.Test/Download/DownloadClientTests/SabnzbdTests/SabnzbdFixture.cs index 2c955bd39..a0c8b656b 100644 --- a/src/NzbDrone.Core.Test/Download/DownloadClientTests/SabnzbdTests/SabnzbdFixture.cs +++ b/src/NzbDrone.Core.Test/Download/DownloadClientTests/SabnzbdTests/SabnzbdFixture.cs @@ -452,6 +452,30 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests result.OutputRootFolders.First().Should().Be(fullCategoryDir); } + [TestCase("0")] + [TestCase("15d")] + public void should_set_history_removes_completed_downloads_false(string historyRetention) + { + _config.Misc.history_retention = historyRetention; + + var downloadClientInfo = Subject.GetStatus(); + + downloadClientInfo.RemovesCompletedDownloads.Should().BeFalse(); + } + + [TestCase("-1")] + [TestCase("15")] + [TestCase("3")] + [TestCase("3d")] + public void should_set_history_removes_completed_downloads_true(string historyRetention) + { + _config.Misc.history_retention = historyRetention; + + var downloadClientInfo = Subject.GetStatus(); + + downloadClientInfo.RemovesCompletedDownloads.Should().BeTrue(); + } + [TestCase(@"Y:\nzbget\root", @"completed\downloads", @"vv", @"Y:\nzbget\root\completed\downloads", @"Y:\nzbget\root\completed\downloads\vv")] [TestCase(@"Y:\nzbget\root", @"completed", @"vv", @"Y:\nzbget\root\completed", @"Y:\nzbget\root\completed\vv")] [TestCase(@"/nzbget/root", @"completed/downloads", @"vv", @"/nzbget/root/completed/downloads", @"/nzbget/root/completed/downloads/vv")] diff --git a/src/NzbDrone.Core/Download/Clients/Sabnzbd/Sabnzbd.cs b/src/NzbDrone.Core/Download/Clients/Sabnzbd/Sabnzbd.cs index dba7b20c3..00bcf5cac 100644 --- a/src/NzbDrone.Core/Download/Clients/Sabnzbd/Sabnzbd.cs +++ b/src/NzbDrone.Core/Download/Clients/Sabnzbd/Sabnzbd.cs @@ -276,7 +276,16 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd status.OutputRootFolders = new List { _remotePathMappingService.RemapRemoteToLocal(Settings.Host, category.FullPath) }; } - status.RemovesCompletedDownloads = config.Misc.history_retention != "0"; + if (config.Misc.history_retention.IsNotNullOrWhiteSpace() && config.Misc.history_retention.EndsWith("d")) + { + int.TryParse(config.Misc.history_retention.AsSpan(0, config.Misc.history_retention.Length - 1), + out var daysRetention); + status.RemovesCompletedDownloads = daysRetention < 14; + } + else + { + status.RemovesCompletedDownloads = config.Misc.history_retention != "0"; + } return status; } From d7ca3490a84ad90dd2b4d4dbe693ad01f62d6cc5 Mon Sep 17 00:00:00 2001 From: Stevie Robinson Date: Tue, 26 Sep 2023 19:09:50 +0200 Subject: [PATCH 023/136] New: Year custom filter in Series Index --- frontend/src/Store/Actions/seriesActions.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/frontend/src/Store/Actions/seriesActions.js b/frontend/src/Store/Actions/seriesActions.js index 971665152..27d581f76 100644 --- a/frontend/src/Store/Actions/seriesActions.js +++ b/frontend/src/Store/Actions/seriesActions.js @@ -348,6 +348,11 @@ export const filterBuilderProps = [ name: 'hasMissingSeason', label: () => translate('HasMissingSeason'), type: filterBuilderTypes.EXACT + }, + { + name: 'year', + label: () => translate('Year'), + type: filterBuilderTypes.NUMBER } ]; From 583eb52ddc01b608ab6cb17e863a8830c17b7b75 Mon Sep 17 00:00:00 2001 From: bakerboy448 <55419169+bakerboy448@users.noreply.github.com> Date: Wed, 27 Sep 2023 09:50:06 -0500 Subject: [PATCH 024/136] Fixed: Only apply remote path mappings for completed items in Qbit --- .../QBittorrentTests/QBittorrentFixture.cs | 4 +- .../Clients/QBittorrent/QBittorrent.cs | 40 ++++++++----------- .../Clients/QBittorrent/QBittorrentTorrent.cs | 3 ++ 3 files changed, 22 insertions(+), 25 deletions(-) diff --git a/src/NzbDrone.Core.Test/Download/DownloadClientTests/QBittorrentTests/QBittorrentFixture.cs b/src/NzbDrone.Core.Test/Download/DownloadClientTests/QBittorrentTests/QBittorrentFixture.cs index 559423f2f..f0dc2ee8e 100644 --- a/src/NzbDrone.Core.Test/Download/DownloadClientTests/QBittorrentTests/QBittorrentFixture.cs +++ b/src/NzbDrone.Core.Test/Download/DownloadClientTests/QBittorrentTests/QBittorrentFixture.cs @@ -137,7 +137,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests { Mocker.GetMock() .Setup(s => s.GetTorrentProperties(torrent.Hash.ToLower(), It.IsAny())) - .Returns(new QBittorrentTorrentProperties { SavePath = torrent.SavePath }); + .Returns(new QBittorrentTorrentProperties { ContentPath = torrent.ContentPath, SavePath = torrent.SavePath }); Mocker.GetMock() .Setup(s => s.GetTorrentFiles(torrent.Hash.ToLower(), It.IsAny())) @@ -426,7 +426,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests Size = 1000, Progress = 0.7, Eta = 8640000, - State = "stalledDL", + State = "pausedUP", Label = "", SavePath = @"C:\Torrents".AsOsAgnostic(), ContentPath = @"C:\Torrents\Droned.S01.12".AsOsAgnostic() diff --git a/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrent.cs b/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrent.cs index cb8d12e3f..039723009 100644 --- a/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrent.cs +++ b/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrent.cs @@ -302,19 +302,6 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent break; } - if (version >= new Version("2.6.1")) - { - if (torrent.ContentPath != torrent.SavePath) - { - item.OutputPath = _remotePathMappingService.RemapRemoteToLocal(Settings.Host, new OsPath(torrent.ContentPath)); - } - else if (item.Status == DownloadItemStatus.Completed) - { - item.Status = DownloadItemStatus.Warning; - item.Message = "Unable to Import. Path matches client base download directory, it's possible 'Keep top-level folder' is disabled for this torrent or 'Torrent Content Layout' is NOT set to 'Original' or 'Create Subfolder'?"; - } - } - queueItems.Add(item); } @@ -328,9 +315,22 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent public override DownloadClientItem GetImportItem(DownloadClientItem item, DownloadClientItem previousImportAttempt) { - // On API version >= 2.6.1 this is already set correctly - if (!item.OutputPath.IsEmpty) + var properties = Proxy.GetTorrentProperties(item.DownloadId.ToLower(), Settings); + var savePath = new OsPath(properties.SavePath); + var version = Proxy.GetApiVersion(Settings); + + if (version >= new Version("2.6.1")) { + if (properties.ContentPath != savePath.ToString()) + { + item.OutputPath = _remotePathMappingService.RemapRemoteToLocal(Settings.Host, new OsPath(properties.ContentPath)); + } + else + { + item.Status = DownloadItemStatus.Warning; + item.Message = "Unable to Import. Path matches client base download directory, it's possible 'Keep top-level folder' is disabled for this torrent or 'Torrent Content Layout' is NOT set to 'Original' or 'Create Subfolder'?"; + } + return item; } @@ -341,11 +341,6 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent return item; } - var properties = Proxy.GetTorrentProperties(item.DownloadId.ToLower(), Settings); - var savePath = new OsPath(properties.SavePath); - - var result = item.Clone(); - // get the first subdirectory - QBittorrent returns `/` path separators even on windows... var relativePath = new OsPath(files[0].Name); while (!relativePath.Directory.IsEmpty) @@ -354,10 +349,9 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent } var outputPath = savePath + relativePath.FileName; + item.OutputPath = _remotePathMappingService.RemapRemoteToLocal(Settings.Host, outputPath); - result.OutputPath = _remotePathMappingService.RemapRemoteToLocal(Settings.Host, outputPath); - - return result; + return item; } public override DownloadClientInfo GetStatus() diff --git a/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrentTorrent.cs b/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrentTorrent.cs index 92e6c7e02..ec5349680 100644 --- a/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrentTorrent.cs +++ b/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrentTorrent.cs @@ -48,6 +48,9 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent [JsonProperty(PropertyName = "seeding_time")] public long SeedingTime { get; set; } // Torrent seeding time (in seconds) + + [JsonProperty(PropertyName = "content_path")] + public string ContentPath { get; set; } // Torrent save path } public class QBittorrentTorrentFile From 35365665cfd436ac276dd9591e23333bd26cf789 Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Wed, 27 Sep 2023 12:06:30 -0700 Subject: [PATCH 025/136] Fixed: Completed downloads in Qbit missing import path --- .../QBittorrentTests/QBittorrentFixture.cs | 2 +- .../Clients/QBittorrent/QBittorrent.cs | 40 +++++++++++-------- .../Clients/QBittorrent/QBittorrentTorrent.cs | 3 -- 3 files changed, 24 insertions(+), 21 deletions(-) diff --git a/src/NzbDrone.Core.Test/Download/DownloadClientTests/QBittorrentTests/QBittorrentFixture.cs b/src/NzbDrone.Core.Test/Download/DownloadClientTests/QBittorrentTests/QBittorrentFixture.cs index f0dc2ee8e..3ae0edd84 100644 --- a/src/NzbDrone.Core.Test/Download/DownloadClientTests/QBittorrentTests/QBittorrentFixture.cs +++ b/src/NzbDrone.Core.Test/Download/DownloadClientTests/QBittorrentTests/QBittorrentFixture.cs @@ -137,7 +137,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests { Mocker.GetMock() .Setup(s => s.GetTorrentProperties(torrent.Hash.ToLower(), It.IsAny())) - .Returns(new QBittorrentTorrentProperties { ContentPath = torrent.ContentPath, SavePath = torrent.SavePath }); + .Returns(new QBittorrentTorrentProperties { SavePath = torrent.SavePath }); Mocker.GetMock() .Setup(s => s.GetTorrentFiles(torrent.Hash.ToLower(), It.IsAny())) diff --git a/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrent.cs b/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrent.cs index 039723009..1be7aa14a 100644 --- a/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrent.cs +++ b/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrent.cs @@ -302,6 +302,19 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent break; } + if (version >= new Version("2.6.1") && item.Status == DownloadItemStatus.Completed) + { + if (torrent.ContentPath != torrent.SavePath) + { + item.OutputPath = _remotePathMappingService.RemapRemoteToLocal(Settings.Host, new OsPath(torrent.ContentPath)); + } + else + { + item.Status = DownloadItemStatus.Warning; + item.Message = "Unable to Import. Path matches client base download directory, it's possible 'Keep top-level folder' is disabled for this torrent or 'Torrent Content Layout' is NOT set to 'Original' or 'Create Subfolder'?"; + } + } + queueItems.Add(item); } @@ -315,22 +328,9 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent public override DownloadClientItem GetImportItem(DownloadClientItem item, DownloadClientItem previousImportAttempt) { - var properties = Proxy.GetTorrentProperties(item.DownloadId.ToLower(), Settings); - var savePath = new OsPath(properties.SavePath); - var version = Proxy.GetApiVersion(Settings); - - if (version >= new Version("2.6.1")) + // On API version >= 2.6.1 this is already set correctly + if (!item.OutputPath.IsEmpty) { - if (properties.ContentPath != savePath.ToString()) - { - item.OutputPath = _remotePathMappingService.RemapRemoteToLocal(Settings.Host, new OsPath(properties.ContentPath)); - } - else - { - item.Status = DownloadItemStatus.Warning; - item.Message = "Unable to Import. Path matches client base download directory, it's possible 'Keep top-level folder' is disabled for this torrent or 'Torrent Content Layout' is NOT set to 'Original' or 'Create Subfolder'?"; - } - return item; } @@ -341,6 +341,11 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent return item; } + var properties = Proxy.GetTorrentProperties(item.DownloadId.ToLower(), Settings); + var savePath = new OsPath(properties.SavePath); + + var result = item.Clone(); + // get the first subdirectory - QBittorrent returns `/` path separators even on windows... var relativePath = new OsPath(files[0].Name); while (!relativePath.Directory.IsEmpty) @@ -349,9 +354,10 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent } var outputPath = savePath + relativePath.FileName; - item.OutputPath = _remotePathMappingService.RemapRemoteToLocal(Settings.Host, outputPath); - return item; + result.OutputPath = _remotePathMappingService.RemapRemoteToLocal(Settings.Host, outputPath); + + return result; } public override DownloadClientInfo GetStatus() diff --git a/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrentTorrent.cs b/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrentTorrent.cs index ec5349680..92e6c7e02 100644 --- a/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrentTorrent.cs +++ b/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrentTorrent.cs @@ -48,9 +48,6 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent [JsonProperty(PropertyName = "seeding_time")] public long SeedingTime { get; set; } // Torrent seeding time (in seconds) - - [JsonProperty(PropertyName = "content_path")] - public string ContentPath { get; set; } // Torrent save path } public class QBittorrentTorrentFile From feee85957f0efa22d7740989e933c2b3418f47f0 Mon Sep 17 00:00:00 2001 From: Weblate Date: Sat, 30 Sep 2023 13:53:42 +0000 Subject: [PATCH 026/136] Multiple Translations updated by Weblate ignore-downstream Co-authored-by: Stevie Robinson Co-authored-by: mr cmuc Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/de/ Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/nl/ Translation: Servarr/Sonarr --- src/NzbDrone.Core/Localization/Core/de.json | 3 ++- src/NzbDrone.Core/Localization/Core/nl.json | 17 ++++++++++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/NzbDrone.Core/Localization/Core/de.json b/src/NzbDrone.Core/Localization/Core/de.json index f561c98fe..3404099d9 100644 --- a/src/NzbDrone.Core/Localization/Core/de.json +++ b/src/NzbDrone.Core/Localization/Core/de.json @@ -37,5 +37,6 @@ "ManageClients": "Verwalte Clienten", "ManageDownloadClients": "Verwalte Download Clienten", "ManageImportLists": "Verwalte Einspiel-Listen", - "NoDownloadClientsFound": "Keine Download Clienten gefunden" + "NoDownloadClientsFound": "Keine Download Clienten gefunden", + "SkipFreeSpaceCheck": "Prüfung des freien Speichers überspringen" } diff --git a/src/NzbDrone.Core/Localization/Core/nl.json b/src/NzbDrone.Core/Localization/Core/nl.json index b922b1c76..1a5318295 100644 --- a/src/NzbDrone.Core/Localization/Core/nl.json +++ b/src/NzbDrone.Core/Localization/Core/nl.json @@ -57,5 +57,20 @@ "Absolute": "Absoluut", "AddANewPath": "Voeg een nieuw pad toe", "AddConditionImplementation": "Voeg voorwaarde toe - {implementationName}", - "AddConnectionImplementation": "Voeg connectie toe - {implementationName}" + "AddConnectionImplementation": "Voeg connectie toe - {implementationName}", + "AddCustomFormat": "Voeg aangepast formaat toe", + "AddDelayProfile": "Voeg vertragingsprofiel toe", + "AddCustomFormatError": "Kan geen nieuw aangepast formaat toevoegen. Probeer het opnieuw.", + "AddDownloadClientError": "Kan geen nieuwe downloadclient toevoegen. Probeer het opnieuw.", + "AddDownloadClient": "Download Client Toevoegen", + "AddIndexerError": "Kon geen nieuwe indexeerder toevoegen, Probeer het opnieuw.", + "AddList": "Lijst Toevoegen", + "AddListError": "Kon geen nieuwe lijst toevoegen, probeer het opnieuw.", + "AddNewRestriction": "Voeg nieuwe restrictie toe", + "AddCustomFilter": "Aangepaste Filter Toevoegen", + "AddDownloadClientImplementation": "Voeg Downloadclient toe - {implementationName}", + "AddIndexer": "Voeg Indexeerder Toe", + "AddIndexerImplementation": "Indexeerder toevoegen - {implementationName}", + "UpdateMechanismHelpText": "Gebruik de ingebouwde updater van {appName} of een script", + "CalendarOptions": "Kalender Opties" } From e611e6c1ede2c1cbfab3efc64dd4e1190f9a7ebc Mon Sep 17 00:00:00 2001 From: Stevie Robinson Date: Fri, 29 Sep 2023 15:12:24 +0200 Subject: [PATCH 027/136] Add Translated + Sponsor Badges to README.md --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 243c4129f..ef3c2ecea 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,10 @@ # Sonarr Sonarr +[![Translated](https://translate.servarr.com/widgets/servarr/-/sonarr/svg-badge.svg)](https://translate.servarr.com/engage/servarr/) +[![Backers on Open Collective](https://opencollective.com/Sonarr/backers/badge.svg)](#backers) +[![Sponsors on Open Collective](https://opencollective.com/Sonarr/sponsors/badge.svg)](#sponsors) +[![Mega Sponsors on Open Collective](https://opencollective.com/Sonarr/megasponsors/badge.svg)](#mega-sponsors) + Sonarr is a PVR for Usenet and BitTorrent users. It can monitor multiple RSS feeds for new episodes of your favorite shows and will grab, sort and rename them. It can also be configured to automatically upgrade the quality of files already downloaded when a better quality format becomes available. ## Getting Started From 33b87acabf2b4c71ee24cda1a466dec6f4f76996 Mon Sep 17 00:00:00 2001 From: Stevie Robinson Date: Wed, 27 Sep 2023 19:21:44 +0200 Subject: [PATCH 028/136] Fixed: qBittorent history retention to allow at least 14 days seeding --- src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrent.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrent.cs b/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrent.cs index 1be7aa14a..c51c99a44 100644 --- a/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrent.cs +++ b/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrent.cs @@ -384,11 +384,13 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent } } + var minimumRetention = 60 * 24 * 14; + return new DownloadClientInfo { IsLocalhost = Settings.Host == "127.0.0.1" || Settings.Host == "localhost", OutputRootFolders = new List { _remotePathMappingService.RemapRemoteToLocal(Settings.Host, destDir) }, - RemovesCompletedDownloads = (config.MaxRatioEnabled || config.MaxSeedingTimeEnabled) && (config.MaxRatioAction == QBittorrentMaxRatioAction.Remove || config.MaxRatioAction == QBittorrentMaxRatioAction.DeleteFiles) + RemovesCompletedDownloads = (config.MaxRatioEnabled || (config.MaxSeedingTimeEnabled && config.MaxSeedingTime < minimumRetention)) && (config.MaxRatioAction == QBittorrentMaxRatioAction.Remove || config.MaxRatioAction == QBittorrentMaxRatioAction.DeleteFiles) }; } From b4ef873cc3386042ba870a4390063dc8add3b748 Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Sat, 30 Sep 2023 11:59:09 -0700 Subject: [PATCH 029/136] Fixed: Treat season packs with volume as partial season packs --- src/NzbDrone.Core.Test/ParserTests/SeasonParserFixture.cs | 1 + src/NzbDrone.Core/Parser/Parser.cs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/NzbDrone.Core.Test/ParserTests/SeasonParserFixture.cs b/src/NzbDrone.Core.Test/ParserTests/SeasonParserFixture.cs index 6f31129cf..9910d612f 100644 --- a/src/NzbDrone.Core.Test/ParserTests/SeasonParserFixture.cs +++ b/src/NzbDrone.Core.Test/ParserTests/SeasonParserFixture.cs @@ -72,6 +72,7 @@ namespace NzbDrone.Core.Test.ParserTests } [TestCase("The.Series.2016.S02.Part.1.1080p.NF.WEBRip.DD5.1.x264-NTb", "The Series 2016", 2, 1)] + [TestCase("The.Series.S07.Vol.1.1080p.NF.WEBRip.DD5.1.x264-NTb", "The Series", 7, 1)] public void should_parse_partial_season_release(string postTitle, string title, int season, int seasonPart) { var result = Parser.Parser.ParseTitle(postTitle); diff --git a/src/NzbDrone.Core/Parser/Parser.cs b/src/NzbDrone.Core/Parser/Parser.cs index 15a8c73c8..789fba4f7 100644 --- a/src/NzbDrone.Core/Parser/Parser.cs +++ b/src/NzbDrone.Core/Parser/Parser.cs @@ -199,7 +199,7 @@ namespace NzbDrone.Core.Parser RegexOptions.IgnoreCase | RegexOptions.Compiled), // Partial season pack - new Regex(@"^(?.+?)(?:\W+S(?<season>(?<!\d+)(?:\d{1,2})(?!\d+))\W+(?:(?:Part\W?|(?<!\d+\W+)e)(?<seasonpart>\d{1,2}(?!\d+)))+)", + new Regex(@"^(?<title>.+?)(?:\W+S(?<season>(?<!\d+)(?:\d{1,2})(?!\d+))\W+(?:(?:(?:Part|Vol)\W?|(?<!\d+\W+)e)(?<seasonpart>\d{1,2}(?!\d+)))+)", RegexOptions.IgnoreCase | RegexOptions.Compiled), // Anime - 4 digit absolute episode number From 9f3915d4adb5e50826a7421fb8db1646127250c3 Mon Sep 17 00:00:00 2001 From: Weblate <noreply@weblate.org> Date: Mon, 9 Oct 2023 00:53:46 +0000 Subject: [PATCH 030/136] 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: Dimitri <dimitridroeck@gmail.com> Co-authored-by: Florian <sephrat.flo@gmail.com> Co-authored-by: Garkus98 <ivan12061998@gmail.com> Co-authored-by: Havok Dan <havokdan@yahoo.com.br> Co-authored-by: He Zhu <zhuhe202@qq.com> Co-authored-by: Oskari Lavinto <olavinto@protonmail.com> Co-authored-by: RicardoVelaC <ricardovelac@gmail.com> Co-authored-by: Stevie Robinson <stevie.robinson@gmail.com> Co-authored-by: Weblate <noreply@weblate.org> Co-authored-by: blankhang <blankhang@gmail.com> Co-authored-by: 宿命 <331874545@qq.com> Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/es/ Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/fi/ Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/fr/ Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/nl/ Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/pt_BR/ Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/ru/ Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/zh_CN/ Translation: Servarr/Sonarr --- src/NzbDrone.Core/Localization/Core/es.json | 17 +- src/NzbDrone.Core/Localization/Core/fi.json | 73 +++- src/NzbDrone.Core/Localization/Core/fr.json | 333 ++++++++++++++++-- src/NzbDrone.Core/Localization/Core/nl.json | 3 +- .../Localization/Core/pt_BR.json | 6 +- src/NzbDrone.Core/Localization/Core/ru.json | 18 +- .../Localization/Core/zh_CN.json | 22 +- 7 files changed, 419 insertions(+), 53 deletions(-) diff --git a/src/NzbDrone.Core/Localization/Core/es.json b/src/NzbDrone.Core/Localization/Core/es.json index f4a65e235..e552fd212 100644 --- a/src/NzbDrone.Core/Localization/Core/es.json +++ b/src/NzbDrone.Core/Localization/Core/es.json @@ -44,7 +44,7 @@ "Authentication": "Autenticación", "AuthForm": "Formularios (página de inicio de sesión)", "AuthenticationMethodHelpText": "Requerir nombre de usuario y contraseña para acceder {appName}", - "AuthenticationRequired": "Autenticación Requerida", + "AuthenticationRequired": "Autenticación requerida", "AutoTaggingLoadError": "No se pudo cargar el etiquetado automático", "AutoRedownloadFailedHelpText": "Buscar e intentar descargar automáticamente una versión diferente", "Backup": "Copia de seguridad", @@ -216,7 +216,16 @@ "ImportScriptPath": "Importar Ruta de Script", "Absolute": "Absoluto", "AddANewPath": "Añadir una nueva ruta", - "AddConditionImplementation": "", - "AppUpdated": "{appName} Actualizado", - "AutomaticUpdatesDisabledDocker": "Las actualizaciones automáticas no son compatibles directamente al usar el mecanismo de actualización de Docker. Deberás actualizar la imagen del contenedor fuera de {appName} o utilizar un script" + "AddConditionImplementation": "Añadir Condición - {implementationName}", + "AppUpdated": "{appName} Actualizada", + "AutomaticUpdatesDisabledDocker": "Las actualizaciones automáticas no están soportadas directamente cuando se utiliza el mecanismo de actualización de Docker. Tendrá que actualizar la imagen del contenedor fuera de {appName} o utilizar un script", + "AuthenticationRequiredHelpText": "Cambiar para que las solicitudes requieran autenticación. No lo cambie a menos que entienda los riesgos.", + "AuthenticationRequiredWarning": "Para evitar el acceso remoto sin autenticación, {appName} ahora requiere que la autenticación esté habilitada. Opcionalmente puede desactivar la autenticación desde una dirección local.", + "AuthenticationRequiredPasswordHelpTextWarning": "Introduzca una nueva contraseña", + "AuthenticationRequiredUsernameHelpTextWarning": "Introduzca un nuevo nombre de usuario", + "AuthenticationMethod": "Método de autenticación", + "AddConnectionImplementation": "Añadir Conexión - {implementationName}", + "AddDownloadClientImplementation": "Añadir Cliente de Descarga - {implementationName}", + "VideoDynamicRange": "Video de Rango Dinámico", + "AuthenticationMethodHelpTextWarning": "Por favor selecciona un método válido de autenticación" } diff --git a/src/NzbDrone.Core/Localization/Core/fi.json b/src/NzbDrone.Core/Localization/Core/fi.json index 505f3f1bd..156195c8f 100644 --- a/src/NzbDrone.Core/Localization/Core/fi.json +++ b/src/NzbDrone.Core/Localization/Core/fi.json @@ -1,15 +1,70 @@ { - "BlocklistReleaseHelpText": "Estää julkaisun automaattisen uudelleensieppauksen.", - "RecycleBinUnableToWriteHealthCheckMessage": "Määritettyyn roskakorikansioon ei voi tallentaa: {0}. Varmista, että sijainti on olemassa ja että käyttäjällä on kirjoitusoikeus kansioon.", - "RemotePathMappingDownloadPermissionsHealthCheckMessage": "Ladattu jakso \"{0}\" näkyy, mutta sitä ei voida käyttää. Todennäköinen syy on sijainnin käyttöoikeusvirhe.", + "BlocklistReleaseHelpText": "Etsii kohdetta uudelleen ja estää {appName}ia sieppaamasta tätä julkaisua automaattisesti uudelleen.", + "RecycleBinUnableToWriteHealthCheckMessage": "Määritettyyn roskakorikansioon ei voida tallentaa: {0}. Varmista että sijainti on olemassa ja että sovelluksen suorittavalla käyttäjällä on siihen kirjoitusoikeus.", + "RemotePathMappingDownloadPermissionsHealthCheckMessage": "{appName} näkee ladatun jakson \"{0}\", muttei voi käyttää sitä. Tämä johtuu todennäköisesti liian rajallisista käyttöoikeuksista.", "Added": "Lisätty", "AppDataLocationHealthCheckMessage": "Päivitystä ei sallita, jotta AppData-kansion poisto päivityksen yhteydessä voidaan estää.", "DownloadClientSortingHealthCheckMessage": "", - "IndexerRssNoIndexersEnabledHealthCheckMessage": "RSS-synkronointia käyttäviä tietolähteitä määritetty, jonka vuoksi uusia julkaisuja ei siepata automaattisesti.", - "IndexerSearchNoInteractiveHealthCheckMessage": "Manuaalista hakua varten ei ole määritetty tietolähteitä, jonka vuoksi haku ei löydä tuloksia.", - "RemotePathMappingFilesGenericPermissionsHealthCheckMessage": "Lataustyökalu \"{0}\" ilmoitti tiedostosijainniksi \"{1}\", mutta kansiota ei nähdä. Saatat joutua muokkaamaan kansion käyttöoikeuksia.", - "RemotePathMappingFolderPermissionsHealthCheckMessage": "Ladatauskansio \"{0}\" näkyy, mutta sitä ei voida käyttää. Todennäköinen syy on sijainnin käyttöoikeusvirhe.", + "IndexerRssNoIndexersEnabledHealthCheckMessage": "RSS-synkronointia käyttäviä tietolähteitä ei ole määritetty, jonka vuoksi uusia julkaisuja ei siepata automaattisesti.", + "IndexerSearchNoInteractiveHealthCheckMessage": "Manuaalista hakua varten ei ole määritetty tietolähteitä, eikä manuaalinen haku sen vuoksi löydä tuloksia.", + "RemotePathMappingFilesGenericPermissionsHealthCheckMessage": "Lataustyökalu \"{0}\" ilmoitti tiedostosijainniksi \"{1}\", mutta {appName} ei näe kansiota. Tämä johtuu todennäköisesti liian rajallisista käyttöoikeuksista.", + "RemotePathMappingFolderPermissionsHealthCheckMessage": "{appName} näkee ladatauskansion \"{0}\" näkyy, muttei voi käyttää sitä. Tämä johtuu todennäköisesti liian rajallisista käyttöoikeuksista.", "RemotePathMappingImportFailedHealthCheckMessage": "Jaksojen tuonti epäonnistui. Katso tarkemmat tiedot lokista.", - "RemotePathMappingGenericPermissionsHealthCheckMessage": "Lataustyökalu \"{0}\" tallentaa latauksen sijaintiin \"{1}\", mutta kansiota ei nähdä. Saatat joutua muokkaamaan kansion käyttöoikeuksia.", - "IndexerSearchNoAutomaticHealthCheckMessage": "Automaattista hakua varten ei ole määritetty tietolähteitä, jonka vuoksi haku ei löydä tuloksia." + "RemotePathMappingGenericPermissionsHealthCheckMessage": "Lataustyökalu \"{0}\" tallentaa latauksen sijaintiin \"{1}\", mutta {appName} ei näe sitä. Tämä johtuu todennäköisesti liian rajallisista käyttöoikeuksista.", + "IndexerSearchNoAutomaticHealthCheckMessage": "Automaattista hakua varten ei ole määritetty tietolähteitä, eikä automaattinen haku sen vuoksi löydä tuloksia.", + "AgeWhenGrabbed": "Ikä (sieppaushetkellä)", + "GrabId": "Sieppaustunniste", + "BindAddressHelpText": "Toimiva IP-osoite, \"localhost\" tai \"*\" (tähti) kaikille verkkoliitännöille.", + "BrowserReloadRequired": "Käyttöönotto vaatii selaimen sivupäivityksen.", + "CustomFormatHelpText": "Julkaisut pisteteytetään täsmäävien mukautettujen muotojen pisteiden summaa. Jos uusi julkaisu parantaisi pisteytystä samalla tai paremmalla laadulla, Radarr sieppaa sen.", + "RemotePathMappingHostHelpText": "Sama osoite, joka on määritty etälataustyökalulle.", + "AudioLanguages": "Äänen kielet", + "Grabbed": "Siepattu", + "GrabSelected": "Sieppaa valitut", + "GrabRelease": "Sieppaa julkaisu", + "Hostname": "Osoite", + "OriginalLanguage": "Alkuperäinen kieli", + "ProxyResolveIpHealthCheckMessage": "Määritetyn välityspalvelimen \"{0}\" IP-osoitteen selvitys epäonnistui.", + "SetPermissionsLinuxHelpText": "Tulisiko chmod suorittaa, kun tiedostoja tuodaan/nimetään uudelleen?", + "UrlBaseHelpText": "Käänteisen välityspalvelimen tuki (esim. \"http://[host]:[port]/[urlBase]\"). Käytä oletusta jättämällä tyhjäksi.", + "SetPermissionsLinuxHelpTextWarning": "Jollet ole varma mitä nämä asetukset tekevät, älä muuta niitä.", + "ClickToChangeLanguage": "Vaihda kieli painamalla tästä", + "EnableColorImpairedModeHelpText": "Vaihtoehtoinen tyyli, joka auttaa erottamaan värikoodatut tiedot paremmin", + "Language": "Kieli", + "Languages": "Kielet", + "MultiLanguages": "Monikielinen", + "Permissions": "Käyttöoikeudet", + "RestartRequiredWindowsService": "Jotta palvelu käynnistyisi automaattisesti, voi suorittavasta käyttäjästä riippuen olla tarpeellista suorittaa sovellus kerran järjestelmänvalvojan oikeuksilla.", + "SelectLanguageModalTitle": "{modalTitle} - Valitse kieli", + "SelectLanguage": "Valitse kieli", + "SelectLanguages": "Valitse kielet", + "ShowRelativeDatesHelpText": "Näytä suhteutetut (tänään/eilen/yms.) absoluuttisten sijaan", + "SetPermissions": "Määritä käyttöoikeudet", + "Style": "Ulkoasu", + "SubtitleLanguages": "Tekstityskielet", + "TimeFormat": "Kellonajan esitys", + "UiLanguage": "Käyttöliittymän kieli", + "UiLanguageHelpText": "Käyttöliittymä näytetään tällä kielellä.", + "AutomaticUpdatesDisabledDocker": "Suoraa automaattista päivitystä ei tueta käytettäessä Dockerin päivitysmekanismia. Joko Docker-säiliö on päivitettävä {appName}in ulkopuolella tai päivitys on suoritettava skriptillä.", + "AddListExclusionHelpText": "Estä sarjan lisääminen {appName}iin listoilta", + "AppUpdated": "{appName} on päivitetty", + "AuthenticationMethodHelpText": "Vaadi käyttäjätunnus ja salasana {appName}in käyttöön.", + "ConnectionLostToBackend": "{appName} kadotti yhteyden taustajärjestelmään ja käytettävyyden palauttamiseksi se on ladattava uudelleen.", + "DeleteTag": "Poista tunniste", + "AppUpdatedVersion": "{appName} on päivitetty versioon {version} ja se on käynnistettävä uudelleen uusia muutoksia varten. ", + "ThemeHelpText": "Vaihda sovelluksen käyttöliittymän ulkoasu. \"Automaattinen\" vaihtaa vaalean ja tumman tilan välillä järjestelmän teeman mukaan. Innoittanut Theme.Park.", + "AnalyticsEnabledHelpText": "Lähetä nimettömiä käyttö- ja virhetietoja palvelimillemme. Tämä sisältää tietoja selaimestasi, käyttöliittymän sivujen käytöstä, virheraportoinnista, käyttöjärjestelmästä ja suoritusalustasta. Käytämme näitä tietoja ominaisuuksien ja vikakorjausten painotukseen.", + "EnableColorImpairedMode": "Heikentyneen värinäön tila", + "EnableAutomaticSearchHelpText": "Profiilia käytetään automaattihauille, jotka suoritetaan käyttöliittymästä tai {appName}in toimesta.", + "InvalidUILanguage": "Käytöliittymän kielivalinta on virheellinen. Korjaa se ja tallenna asetukset.", + "AuthenticationRequiredWarning": "Etäkäytön estämiseksi ilman tunnistautumista {appName} vaatii nyt todennuksen käyttöönoton. Todennus voidaan poistaa käytöstä paikallisille osoitteille.", + "IndexerDownloadClientHelpText": "Määritä tämän tietolähteen kanssa käytettävä lataustyökalu", + "ProfilesSettingsSummary": "Laatu-, kieli-, viive- ja julkaisuprofiilit.", + "ConnectionLostReconnect": "{appName} pyrkii ajoittain muodostamaan yhteyden automaattisesti tai sitä voidaan yrittää manuaalisesti painamalla alta \"Lataa uudelleen\".", + "LanguagesLoadError": "Kielien lataus ei onnistu", + "OverrideGrabNoLanguage": "Ainakin yksi kieli on valittava.", + "ChmodFolderHelpTextWarning": "Tämä toimii vain, jos käyttäjä suorittaa {appName}in tiedoston omistajana. Parempi vaihtoehto on varmistaa, että lataustyökalu asettaa oikeudet oikein.", + "InteractiveImportNoLanguage": "Kielet on valittavat jokaiselle valitulle tiedostolle", + "MediaInfoFootNote": "MediaInfo Full, AudioLanguages ja SubtitleLanguages tukevat \":EN+DE\" jälkiliitettä ja mahdollistaa tiedostonimeen sisältyvien kielten suodatuksen. \"-\" ohittaa tietyt kielet (e.g. \"-EN\") ja \"+\"-pääte (e.g. \":FI+\") tuottaa ohitettavista kielistä riippuen \"[FI]\", \"[FI+--]\" tai \"[--]\". Esimerkiksi \"{MediaInfo Full:FI+EN}\".", + "Host": "Osoite" } diff --git a/src/NzbDrone.Core/Localization/Core/fr.json b/src/NzbDrone.Core/Localization/Core/fr.json index 5dce946d5..7cfbce259 100644 --- a/src/NzbDrone.Core/Localization/Core/fr.json +++ b/src/NzbDrone.Core/Localization/Core/fr.json @@ -9,9 +9,9 @@ "CountSeasons": "{count} saisons", "DownloadClientCheckNoneAvailableHealthCheckMessage": "Aucun client de téléchargement disponible", "Add": "Ajouter", - "AddingTag": "Ajouter un tag", + "AddingTag": "Ajout d'une étiquette", "Apply": "Appliquer", - "ApplyTags": "Appliquer les tags", + "ApplyTags": "Appliquer les étiquettes", "Activity": "Activité", "About": "À propos", "Actions": "Actions", @@ -33,14 +33,14 @@ "Analytics": "Statistiques", "ApiKey": "Clé API", "ApplicationURL": "URL de l'application", - "ApplicationUrlHelpText": "URL externe de cette application, y compris http(s)://, le port ainsi que la base de URL", - "AuthBasic": "Authentification de base (Basic) (popup dans le navigateur)", - "AuthForm": "Authentification par un formulaire (page de connexion)", + "ApplicationUrlHelpText": "L'URL externe de cette application, y compris http(s)://, le port ainsi que la base de URL", + "AuthBasic": "Basique (fenêtre surgissante du navigateur)", + "AuthForm": "Formulaire (page de connexion)", "Authentication": "Authentification", "Automatic": "Automatique", "AutomaticSearch": "Recherche automatique", "BackupIntervalHelpText": "Intervalle entre les sauvegardes automatiques", - "BindAddressHelpText": "Adresse IP valide, localhost ou '*' pour toutes les interfaces", + "BindAddressHelpText": "Adresse IP valide, localhost ou « * » pour toutes les interfaces", "Branch": "Branche", "BranchUpdateMechanism": "Branche utilisée par le mécanisme de mise à jour extérieur", "BypassDelayIfHighestQuality": "Contournement si la qualité est la plus élevée", @@ -51,12 +51,12 @@ "CloneIndexer": "Dupliqué l'indexeur", "CloneProfile": "Dupliqué le profil", "AddRootFolder": "Ajouter un dossier racine", - "ApplyTagsHelpTextHowToApplyIndexers": "Comment appliquer des tags aux indexeurs sélectionnés", + "ApplyTagsHelpTextHowToApplyIndexers": "Comment appliquer des étiquettes aux indexeurs sélectionnés", "AudioInfo": "Info audio", "Cancel": "Annuler", - "ApplyTagsHelpTextAdd": "Ajouter : Ajouter les tags à la liste de tags existantes", - "ApplyTagsHelpTextRemove": "Suprimer : Suprime les étiquettes renseignées", - "AddNew": "Ajouter un nouveau", + "ApplyTagsHelpTextAdd": "Ajouter : ajoute les étiquettes à la liste de étiquettes existantes", + "ApplyTagsHelpTextRemove": "Supprimer : supprime les étiquettes renseignées", + "AddNew": "Ajouter une nouvelle", "Backup": "Sauvegarde", "Blocklist": "Liste noire", "Calendar": "Calendrier", @@ -96,7 +96,7 @@ "ApplyTagsHelpTextHowToApplySeries": "Comment appliquer des balises à la série sélectionnée", "AuthenticationRequired": "Authentification requise", "AudioLanguages": "Langues audio", - "ApplyTagsHelpTextHowToApplyDownloadClients": "Comment appliquer des balises aux clients de téléchargement sélectionnés", + "ApplyTagsHelpTextHowToApplyDownloadClients": "Comment appliquer des étiquettes aux clients de téléchargement sélectionnés", "AddImportList": "Ajouter une liste d'importation", "AddListExclusionError": "Impossible d'ajouter une nouvelle exclusion de liste, veuillez réessayer.", "AddNotificationError": "Impossible d'ajouter une nouvelle notification, veuillez réessayer.", @@ -108,7 +108,7 @@ "AnimeEpisodeFormat": "Format des épisodes d'animé", "AutoRedownloadFailedHelpText": "Recherche automatique et tentative de téléchargement d'une version différente", "AutoTaggingLoadError": "Impossible de charger le balisage automatique", - "AuthenticationRequiredWarning": "Pour empêcher l'accès à distance sans authentification, {appName} exige désormais que l'authentification soit activée. Vous pouvez éventuellement désactiver l'authentification à partir des adresses locales.", + "AuthenticationRequiredWarning": "Pour empêcher l'accès à distance sans authentification, {appName} exige désormais que l'authentification soit activée. Vous pouvez éventuellement désactiver l'authentification pour les adresses locales.", "BackupFolderHelpText": "Les chemins d'accès relatifs se trouvent dans le répertoire AppData de Sonarr", "AirDate": "Date de diffusion", "AllTitles": "Tous les titres", @@ -117,10 +117,10 @@ "AutoTaggingNegateHelpText": "Si cette case est cochée, la règle de marquage automatique ne s'appliquera pas si la condition {implementationName} est remplie.", "AutoTaggingRequiredHelpText": "Cette condition {implementationName} doit être remplie pour que la règle de marquage automatique s'applique. Dans le cas contraire, une seule correspondance {implementationName} suffit.", "AllResultsAreHiddenByTheAppliedFilter": "Tous les résultats sont masqués par le filtre appliqué", - "ApplyTagsHelpTextReplace": "Remplacer : Remplace les balises par les balises saisies (ne pas saisir de balises pour effacer toutes les balises)", + "ApplyTagsHelpTextReplace": "Remplacer : remplace les étiquettes par les étiquettes renseignées (ne pas renseigner d'étiquette pour toutes les effacer)", "Agenda": "Agenda", "AnEpisodeIsDownloading": "Un épisode est en cours de téléchargement", - "AuthenticationMethodHelpText": "Nom d'utilisateur et mot de passe requis pour accéder à {appName}", + "AuthenticationMethodHelpText": "Exiger un nom d'utilisateur et un mot de passe pour accéder à {appName}", "AddedToDownloadQueue": "Ajouté à la file d'attente de téléchargement", "AddImportListImplementation": "Ajouter une liste d'importation - {implementationName}", "Airs": "Diffusions", @@ -132,13 +132,13 @@ "AirsTomorrowOn": "Demain à {time} sur {networkLabel}", "AnimeTypeFormat": "Numéro d'épisode absolu ({format})", "AppUpdatedVersion": "{appName} a été mis à jour à la version `{version}`, afin d'obtenir les derniers changements, vous devrez recharger {appName}. ", - "ApplyTagsHelpTextHowToApplyImportLists": "Comment appliquer des balises aux listes d'importation sélectionnées", + "ApplyTagsHelpTextHowToApplyImportLists": "Comment appliquer des étiquettes aux listes d'importation sélectionnées", "AuthenticationMethod": "Méthode d'authentification", "AuthenticationRequiredPasswordHelpTextWarning": "Saisir un nouveau mot de passe", "AuthenticationRequiredUsernameHelpTextWarning": "Saisir un nouveau nom d'utilisateur", "AddListExclusion": "Ajouter une liste d'exclusion", - "AddNewSeriesHelpText": "Il est facile d'ajouter une nouvelle série, il suffit de taper le nom de la série que vous souhaitez ajouter.", - "AddNewSeriesRootFolderHelpText": "Le sous-dossier '{folder}' sera créé automatiquement", + "AddNewSeriesHelpText": "C'est facile d'ajouter une nouvelle série, il suffit de saisir le nom de la série que vous souhaitez ajouter.", + "AddNewSeriesRootFolderHelpText": "Le sous-dossier « {folder} » sera créé automatiquement", "AddNewSeriesSearchForCutoffUnmetEpisodes": "Lancer la recherche d'épisodes non satisfaits", "AddSeriesWithTitle": "Ajouter {title}", "AddedDate": "Ajouté le : {date}", @@ -149,13 +149,13 @@ "Anime": "Animé", "AnimeTypeDescription": "Episodes diffusés en utilisant un numéro d'épisode absolu", "Any": "Tous", - "AppUpdated": "{appName} Mise à jour", + "AppUpdated": "{appName} mis à jour", "AddListExclusionHelpText": "Empêcher les séries d'être ajoutées à Sonarr par des listes", "AllSeriesAreHiddenByTheAppliedFilter": "Tous les résultats sont masqués par le filtre appliqué", "AnalyseVideoFilesHelpText": "Extraire des fichiers des informations vidéo telles que la résolution, la durée d'exécution et le codec. Pour ce faire, Sonarr doit lire des parties du fichier, ce qui peut entraîner une activité élevée du disque ou du réseau pendant les analyses.", "AnalyticsEnabledHelpText": "Envoyer des informations anonymes sur l'utilisation et les erreurs aux serveurs de Sonarr. Cela inclut des informations sur votre navigateur, les pages de l'interface Web de Sonarr que vous utilisez, les rapports d'erreurs ainsi que le système d'exploitation et la version d'exécution. Nous utiliserons ces informations pour prioriser les fonctionnalités et les corrections de bugs.", - "AuthenticationMethodHelpTextWarning": "Veuillez sélectionner une méthode d'authentification valide", - "AuthenticationRequiredHelpText": "Modifier les demandes pour lesquelles l'authentification est requise. Ne changez rien si vous ne comprenez pas les risques.", + "AuthenticationMethodHelpTextWarning": "Veuillez choisir une méthode d'authentification valide", + "AuthenticationRequiredHelpText": "Modifier les demandes pour lesquelles l'authentification est requise. Ne rien modifier si vous n'en comprenez pas les risques.", "AutomaticUpdatesDisabledDocker": "Les mises à jour automatiques ne sont pas directement prises en charge lors de l'utilisation du mécanisme de mise à jour de Docker. Vous devrez mettre à jour l'image du conteneur en dehors de {appName} ou utiliser un script", "BackupRetentionHelpText": "Les sauvegardes automatiques plus anciennes que la période de rétention seront nettoyées automatiquement", "QualityProfile": "Profil de qualité", @@ -166,7 +166,7 @@ "BackupsLoadError": "Impossible de charger les sauvegardes", "BlocklistReleaseHelpText": "Lance une nouvelle recherche pour cet épisode et empêche que cette version soit à nouveau récupérée.", "BuiltIn": "Intégré", - "BrowserReloadRequired": "Rechargement du navigateur nécessaire", + "BrowserReloadRequired": "Rechargement du navigateur requis", "BypassDelayIfAboveCustomFormatScore": "Ignorer si le score est supérieur au format personnalisé", "CheckDownloadClientForDetails": "Pour plus de détails, consultez le client de téléchargement", "ChooseAnotherFolder": "Sélectionnez un autre dossier", @@ -189,7 +189,7 @@ "RecycleBinUnableToWriteHealthCheckMessage": "Impossible d'écrire dans le dossier configuré de la corbeille de recyclage : {0}. Assurez-vous que ce chemin existe et qu'il est accessible en écriture par l'utilisateur qui exécute Sonarr.", "RemotePathMappingFileRemovedHealthCheckMessage": "Le fichier {0} a été supprimé en cours de traitement.", "RemotePathMappingFilesGenericPermissionsHealthCheckMessage": "Le client de téléchargement {0} a signalé des fichiers dans {1} mais Sonarr ne peut pas voir ce répertoire. Il se peut que vous deviez ajuster les permissions du dossier.", - "CalendarFeed": "Calendrier {appName}", + "CalendarFeed": "Flux de calendrier {appName}", "CalendarLegendDownloadedTooltip": "L'épisode a été téléchargé et classé", "CalendarLegendDownloadingTooltip": "L'épisode est en cours de téléchargement", "CalendarLegendFinaleTooltip": "Fin de série ou de saison", @@ -205,7 +205,7 @@ "ClickToChangeReleaseGroup": "Cliquez pour changer de groupe de diffusion", "RemotePathMappingFilesBadDockerPathHealthCheckMessage": "Vous utilisez Docker ; le client de téléchargement {0} a signalé des fichiers dans {1} mais ce n'est pas un chemin {2} valide. Vérifiez vos mappages de chemins d'accès distants et les paramètres du client de téléchargement.", "BypassDelayIfAboveCustomFormatScoreMinimumScoreHelpText": "Score minimum requis pour le format personnalisé pour ignorer le délai pour le protocole préféré", - "BypassDelayIfHighestQualityHelpText": "Ignorer le délai lorsque la libération a la qualité activée la plus élevée dans le profil de qualité avec le protocole préféré.", + "BypassDelayIfHighestQualityHelpText": "Ignorer le délai lorsque la libération a la qualité activée la plus élevée dans le profil de qualité avec le protocole préféré", "RemotePathMappingBadDockerPathHealthCheckMessage": "Vous utilisez Docker ; le client de téléchargement {0} place les téléchargements dans {1} mais ce n'est pas un chemin {2} valide. Revoyez vos mappages de chemins d'accès distants et les paramètres du client de téléchargement.", "RemotePathMappingFilesLocalWrongOSPathHealthCheckMessage": "Le client de téléchargement local {0} a signalé des fichiers dans {1}, mais il ne s'agit pas d'un chemin {2} valide. Vérifiez les paramètres de votre client de téléchargement.", "RemotePathMappingFilesWrongOSPathHealthCheckMessage": "Le client de téléchargement distant {0} a signalé des fichiers dans {1}, mais il ne s'agit pas d'un chemin {2} valide. Revoyez vos mappages de chemins d'accès distants et les paramètres du client de téléchargement.", @@ -217,5 +217,290 @@ "Yesterday": "Hier", "Password": "Mot de passe", "Queue": "File d'attente", - "Wanted": "Recherché" + "Wanted": "Recherché", + "Remove": "Retirer", + "RemoveSelectedItemQueueMessageText": "Voulez-vous vraiment supprimer 1 élément de la file d'attente ?", + "DeleteNotification": "Supprimer la notification", + "DeleteNotificationMessageText": "Voulez-vous supprimer la notification « {name} » ?", + "MediaManagement": "Gestion des médias", + "MediaManagementSettingsSummary": "Nommage, paramètres de gestion des médias et dossiers racine", + "MinimumFreeSpace": "Espace libre minimum", + "Monitored": "Surveillé", + "NoHistoryFound": "Aucun historique n'a été trouvé", + "NoHistoryBlocklist": "Pas d'historique de liste noire", + "Period": "Point", + "QualityDefinitionsLoadError": "Impossible de charger les définitions de qualité", + "RemoveSelected": "Supprimer la sélection", + "UnknownEventTooltip": "Événement inconnu", + "TablePageSizeHelpText": "Nombre d'éléments à afficher sur chaque page", + "Tags": "Étiquettes", + "Unknown": "Inconnu", + "UnmappedFolders": "Dossiers non mappés", + "UpgradesAllowed": "Mises à niveau autorisées", + "VideoDynamicRange": "Plage de dynamique vidéo", + "WouldYouLikeToRestoreBackup": "Souhaitez-vous restaurer la sauvegarde « {name} » ?", + "YesCancel": "Oui, annuler", + "Deleted": "Supprimé", + "Edit": "Modifier", + "RemoveSelectedItem": "Supprimer l'élément sélectionné", + "SubtitleLanguages": "Langues des sous-titres", + "Clone": "Cloner", + "ColonReplacementFormatHelpText": "Changer la manière dont {appName} remplace les « deux-points »", + "DefaultCase": "Casse par défaut", + "Delete": "Supprimer", + "DelayProfiles": "Profils de retard", + "DelayProfilesLoadError": "Impossible de charger les profils de retard", + "DeleteDownloadClientMessageText": "Voulez-vous supprimer le client de téléchargement « {name} » ?", + "DoneEditingGroups": "Terminer la modification des groupes", + "Duplicate": "Dupliqué", + "ExtraFileExtensionsHelpTextsExamples": "Exemples : '.sub, .nfo' ou 'sub,nfo'", + "None": "Aucun", + "NoTagsHaveBeenAddedYet": "Aucune étiquette n'a encore été ajoutée", + "QualityLimitsHelpText": "Les limites sont automatiquement ajustées en fonction de la durée de la série et du nombre d'épisodes dans le fichier.", + "QualityProfiles": "Profils de qualité", + "Range": "Plage", + "Required": "Obligatoire", + "Unmonitored": "Non surveillé", + "UsenetDelay": "Retard Usenet", + "RemoveTagsAutomatically": "Supprimer automatiquement les étiquettes", + "CountDownloadClientsSelected": "{count} client(s) de téléchargement sélectionné(s)", + "DiskSpace": "Espace disque", + "Save": "Sauvegarder", + "RootFolders": "Dossiers racine", + "TestParsing": "Tester le parsage", + "TotalSpace": "Espace total", + "FullSeason": "Saison complète", + "ManualImport": "Importation manuelle", + "ReplaceWithSpaceDash": "Remplacer par un espace puis un tiret", + "Scene": "Scène", + "SelectDownloadClientModalTitle": "{modalTitle} - Sélectionner le client de téléchargements", + "Shutdown": "Éteindre", + "SonarrTags": "Étiquettes {appName}", + "TagsSettingsSummary": "Voir toutes les étiquettes et leur utilisation. Les étiquettes inutilisées peuvent être supprimées", + "Ui": "UI", + "Uppercase": "Majuscules", + "TorrentDelayTime": "Retard du torrent : {torrentDelay}", + "Underscore": "Tiret du bas", + "UnmonitorSelected": "Arrêter de surveiller la sélection", + "DefaultNameCopiedProfile": "{name} - Copier", + "DeleteSelectedDownloadClientsMessageText": "Voulez-vous vraiment supprimer {count} client(s) de téléchargement sélectionné(s) ?", + "DefaultNameCopiedSpecification": "{name} - Copier", + "IndexerDownloadClientHealthCheckMessage": "Indexeurs avec des clients de téléchargement invalides : {0].", + "Name": "Nom", + "OrganizeNothingToRename": "C'est fait ! Mon travail est terminé, plus aucun fichier à renommer.", + "PortNumber": "Numéro de port", + "ProfilesSettingsSummary": "Profils de qualité, de langue, de retard et de release", + "Qualities": "Qualités", + "QualitiesLoadError": "Impossible de charger les qualités", + "RenameFiles": "Renommer les fichiers", + "Restart": "Redémarrer", + "RestartNow": "Redémarrer maintenant", + "SaveSettings": "Enregistrer les paramètres", + "ShowMonitoredHelpText": "Affiche le statut surveillé sous l'affiche", + "SkipFreeSpaceCheck": "Ignorer la vérification de l'espace libre", + "Sunday": "Dimanche", + "TorrentDelay": "Retard du torrent", + "DownloadClients": "Clients de télécharg.", + "CustomFormats": "Formats perso.", + "NoIndexersFound": "Aucun indexeur n'a été trouvé", + "Profiles": "Profils", + "Dash": "Tiret", + "DelayProfileProtocol": "Protocole : {preferredProtocol}", + "DeleteBackupMessageText": "Voulez-vous supprimer la sauvegarde « {name} » ?", + "DeleteConditionMessageText": "Voulez-vous vraiment supprimer la condition « {name} » ?", + "DeleteCondition": "Supprimer la condition", + "DeleteBackup": "Supprimer la sauvegarde", + "DeleteIndexerMessageText": "Voulez-vous vraiment supprimer l'indexeur « {name} » ?", + "DeleteIndexer": "Supprimer l'indexeur", + "DeleteSelectedIndexersMessageText": "Voulez-vous vraiment supprimer les {count} indexeur(s) sélectionné(s) ?", + "DeleteRootFolder": "Supprimer le dossier racine", + "DisabledForLocalAddresses": "Désactivée pour les adresses IP locales", + "DeleteTagMessageText": "Voulez-vous vraiment supprimer l'étiquette « {label} » ?", + "Filter": "Filtrer", + "FilterContains": "contient", + "FilterIs": "est", + "FreeSpace": "Espace libre", + "Host": "Hôte", + "ICalIncludeUnmonitoredHelpText": "Inclure les épisodes non surveillés dans le flux iCal", + "RenameEpisodesHelpText": "{appName} utilisera le nom de fichier existant si le renommage est désactivé", + "RestartRequiredToApplyChanges": "{appName} exige un redémarrage pour appliquer les modifications, voulez-vous redémarrer maintenant ?", + "OrganizeRenamingDisabled": "Le renommage est désactivé, rien à renommer", + "PendingChangesStayReview": "Rester et vérifier les modifications", + "PendingChangesMessage": "Vous avez des modifications non sauvegardées, voulez-vous vraiment quitter cette page ?", + "RestartRequiredHelpTextWarning": "Nécessite un redémarrage pour prendre effet", + "SelectLanguageModalTitle": "{modalTitle} - Sélectionner une langue", + "SelectFolderModalTitle": "{modalTitle} - Sélectionner un dossier", + "Settings": "Paramètres", + "UsenetDelayTime": "Retard Usenet : {usenetDelay}", + "CountIndexersSelected": "{count} indexeur(s) sélectionné(s)", + "EditGroups": "Modifier les groupes", + "False": "Faux", + "Example": "Exemple", + "FileNameTokens": "Tokens des noms de fichier", + "FileNames": "Noms de fichier", + "Extend": "Étendu", + "FileManagement": "Gestion de fichiers", + "FileBrowser": "Explorateur de fichiers", + "FailedToLoadQualityProfilesFromApi": "Échec du chargement des profils de qualité depuis l'API", + "Filename": "Nom de fichier", + "FailedToLoadTagsFromApi": "Échec du chargement des étiquettes depuis l'API", + "FormatTimeSpanDays": "{days}j {time}", + "FormatShortTimeSpanSeconds": "{seconds} seconde(s)", + "FilterEqual": "égal", + "Implementation": "Implémentation", + "ICalSeasonPremieresOnlyHelpText": "Seul le premier épisode d'une saison sera dans le flux", + "ICalFeed": "Flux iCal", + "History": "Historique", + "HideAdvanced": "Masquer param. av.", + "Large": "Grande", + "LanguagesLoadError": "Impossible de charger les langues", + "IncludeUnmonitored": "Inclure les non surveillés", + "KeyboardShortcutsFocusSearchBox": "Placer le curseur sur la barre de recherche", + "KeyboardShortcutsSaveSettings": "Enregistrer les paramètres", + "ManageClients": "Gérer les clients", + "Logout": "Déconnexion", + "MediaManagementSettings": "Paramètres de gestion des médias", + "Lowercase": "Minuscules", + "MaximumSizeHelpText": "Taille maximale d'une release à récupérer en Mo. Mettre à zéro pour définir sur illimité", + "MissingNoItems": "Aucun élément manquant", + "MoveAutomatically": "Déplacer automatiquement", + "MoreInfo": "Plus d'informations", + "NoHistory": "Aucun historique", + "MonitoredStatus": "Surveillé / État", + "MultiEpisodeStyle": "Style multi-épisodes", + "NoChanges": "Aucune modification", + "NoSeasons": "Aucune saison", + "PosterSize": "Taille des affiches", + "PosterOptions": "Options des affiches", + "Posters": "Affiches", + "Or": "ou", + "ParseModalHelpTextDetails": "{appName} va tenter de parser le titre et de vous afficher les détails à son sujet", + "OrganizeNamingPattern": "Modèle de nommage : `{episodeFormat}`", + "OneSeason": "1 saison", + "Ok": "OK", + "PendingChangesDiscardChanges": "Abandonner les modifications et quitter", + "PreferProtocol": "{preferredProtocol} préféré", + "Refresh": "Actualiser", + "PrefixedRange": "Plage préfixée", + "PreferredProtocol": "Protocole préféré", + "ProtocolHelpText": "Choisissez le(s) protocole(s) à utiliser et celui qui est préféré lors du choix entre des versions par ailleurs égales", + "Quality": "Qualité", + "Presets": "Préconfigurations", + "RemoveSelectedItems": "Supprimer les éléments sélectionnés", + "ReplaceWithSpaceDashSpace": "Remplacer par un espace, un tiret puis un espace", + "RestartLater": "Je redémarrerai plus tard", + "ReplaceIllegalCharacters": "Remplacer les caractères illégaux", + "ReplaceIllegalCharactersHelpText": "Remplacer les caractères illégaux. Si non coché, {appName} les supprimera", + "Repeat": "Répété", + "Renamed": "Renommé", + "ResetQualityDefinitions": "Réinitialiser les définitions de qualité", + "ResetQualityDefinitionsMessageText": "Voulez-vous vraiment réinitialiser les définitions de qualité ?", + "ResetTitles": "Réinitialiser les titres", + "ResetDefinitionTitlesHelpText": "Réinitialiser les titres des définitions ainsi que les valeurs", + "Reset": "Réinitialiser", + "RestartSonarr": "Redémarrer {appName}", + "RootFolderLoadError": "Impossible de charger le dossier racine", + "SelectSeries": "Sélectionner des séries", + "SearchForMissing": "Recherche les manquants", + "SearchAll": "Tout rechercher", + "Series": "Séries", + "ShowSearchHelpText": "Affiche le bouton de recherche au survol du curseur", + "SmartReplace": "Replacement intelligent", + "SmartReplaceHint": "Tiret ou espace puis tiret selon le nom", + "Space": "Espace", + "SizeLimit": "Limite de taille", + "TagIsNotUsedAndCanBeDeleted": "L'étiquette n'est pas utilisée et peut être supprimée", + "TagDetails": "Détails de la balise - {label}", + "Title": "Titre", + "Titles": "Titres", + "True": "Vrai", + "UnmonitorDeletedEpisodes": "Annuler la surveillance des épisodes supprimés", + "UnsavedChanges": "Modifications non sauvegardées", + "TorrentDelayHelpText": "Délai en minutes avant de récupérer un torrent", + "Username": "Nom d'utilisateur", + "UnselectAll": "Tout désélectionner", + "UpgradesAllowedHelpText": "Si désactivé, les qualités ne seront pas améliorées", + "UsenetDelayHelpText": "Délai en minutes avant de récupérer une release de Usenet", + "InteractiveImport": "Importation interactive", + "InteractiveImportNoImportMode": "Un mode d'importation doit être sélectionné", + "Today": "Aujourd'hui", + "QualityDefinitions": "Définitions des qualités", + "ManageDownloadClients": "Gérer les clients de téléchargement", + "NoDownloadClientsFound": "Aucun client de téléchargement n'a été trouvé", + "NotificationStatusAllClientHealthCheckMessage": "Toutes les notifications sont indisponibles en raison de dysfonctionnements", + "NotificationStatusSingleClientHealthCheckMessage": "Notifications indisponibles en raison de dysfonctionnements : {0}", + "RecentChanges": "Changements récents", + "SetTags": "Définir les étiquettes", + "Replace": "Remplacer", + "ResetAPIKeyMessageText": "Voulez-vous réinitialiser votre clé d'API ?", + "StopSelecting": "Effacer la sélection", + "WhatsNew": "Quoi de neuf ?", + "EditDownloadClientImplementation": "Modifier le client de téléchargement - {implementationName}", + "External": "Externe", + "Monday": "Lundi", + "ShowQualityProfileHelpText": "Affiche le profil de qualité sous l'affiche", + "IncludeCustomFormatWhenRenamingHelpText": "Inclut dans {Custom Formats} renommant le format", + "SelectDropdown": "Sélectionner...", + "InteractiveImportNoFilesFound": "Aucun fichier vidéo n'a été trouvé dans le dossier sélectionné", + "Umask": "Umask", + "OrganizeRelativePaths": "Tous les chemins sont relatifs à : `{path}`", + "OverrideGrabNoLanguage": "Au moins une langue doit être sélectionnée", + "OverrideGrabNoQuality": "Une qualité doit être sélectionnée", + "NoSeriesFoundImportOrAdd": "Aucune série n'a été trouvée, pour commencer vous aller devoir importer vos séries existantes ou ajouter une nouvelle série.", + "ICalFeedHelpText": "Copiez cette URL dans votre/vos client(s) ou cliquez pour abonner si votre navigateur est compatible avec webcal", + "SeasonFolderFormat": "Format du dossier des saisons", + "QualitiesHelpText": "Les qualités placées en haut de la liste sont privilégiées même si elles ne sont pas cochées. Les qualités d'un même groupe sont égales. Seules les qualités cochées sont recherchées", + "PrioritySettings": "Priorité : {priority}", + "ImportExistingSeries": "Importer des séries existantes", + "RootFolderSelectFreeSpace": "{freeSpace} disponibles", + "WantMoreControlAddACustomFormat": "Vous voulez plus de contrôle sur les téléchargements préférés ? Ajouter un [Format Personnalisé](/settings/customformats)", + "RemoveSelectedItemsQueueMessageText": "Voulez-vous vraiment supprimer {selectedCount} élément(s) de la file d'attente ?", + "UpdateAll": "Tout actualiser", + "EnableSslHelpText": "Nécessite un redémarrage en tant qu'administrateur pour être effectif", + "UnmonitorDeletedEpisodesHelpText": "Les épisodes effacés du disque dur ne seront plus surveillés dans {appName}", + "RssSync": "Synchro RSS", + "RestartReloadNote": "Remarque : {appName} redémarrera et rechargera automatiquement l'interface utilisateur pendant le processus de restauration.", + "FileBrowserPlaceholderText": "Commencer à écrire ou sélectionner un chemin ci-dessous", + "ICalLink": "Lien iCal", + "KeyboardShortcuts": "Raccourcis clavier", + "QualityProfilesLoadError": "Impossible de charger les profils de qualité", + "KeyboardShortcutsOpenModal": "Ouvrir cette fenêtre modale", + "ICalShowAsAllDayEvents": "Afficher comme événements d'une journée entière", + "KeyboardShortcutsCloseModal": "Fermer cette fenêtre modale", + "ICalShowAsAllDayEventsHelpText": "Les événements apparaîtront comme des événements d'une journée entière dans votre calendrier", + "Reload": "Recharger", + "ICalTagsHelpText": "Le flux ne contiendra que les séries avec au moins une étiquette correspondante", + "MediaManagementSettingsLoadError": "Impossible de charger les paramètres de gestion des médias", + "EpisodeNaming": "Nommage des épisodes", + "ConnectionLostReconnect": "{appName} essaiera de se connecter automatiquement, ou vous pouvez cliquer sur « Recharger » en bas.", + "ConnectionLostToBackend": "{appName} a perdu sa connexion au backend et devra être rechargé pour fonctionner à nouveau.", + "CouldNotFindResults": "Aucun résultat pour « {term} »", + "ConnectionLost": "Connexion perdue", + "HistoryLoadError": "Impossible de charger l'historique", + "Hostname": "Nom d'hôte", + "ReplaceWithDash": "Remplacer par un tiret", + "Status": "État", + "ColonReplacement": "Remplacement pour le « deux-points »", + "CountSeriesSelected": "{count} série(s) sélectionnée(s)", + "Default": "Par défaut", + "ShowAdvanced": "Afficher param. av.", + "SeasonPremieresOnly": "Début de saison uniquement", + "SearchSelected": "Rechercher la sélection", + "Seasons": "Saisons", + "Season": "Saison", + "General": "Général", + "FormatAgeDays": "jours", + "FormatAgeDay": "jour", + "FormatAgeHour": "heure", + "FormatAgeHours": "heures", + "FormatAgeMinute": "minute", + "FormatAgeMinutes": "minutes", + "FormatDateTime": "{formattedDate} {formattedTime}", + "FormatDateTimeRelative": "{relativeDay}, {formattedDate} {formattedTime}", + "FormatRuntimeHours": "{hours} h", + "FormatRuntimeMinutes": "{minutes} m", + "FormatShortTimeSpanHours": "{hours} heure(s)", + "FormatShortTimeSpanMinutes": "{minutes} minute(s)", + "SeriesID": "Identifiant de la série", + "Score": "Score" } diff --git a/src/NzbDrone.Core/Localization/Core/nl.json b/src/NzbDrone.Core/Localization/Core/nl.json index 1a5318295..a7d65f5da 100644 --- a/src/NzbDrone.Core/Localization/Core/nl.json +++ b/src/NzbDrone.Core/Localization/Core/nl.json @@ -72,5 +72,6 @@ "AddIndexer": "Voeg Indexeerder Toe", "AddIndexerImplementation": "Indexeerder toevoegen - {implementationName}", "UpdateMechanismHelpText": "Gebruik de ingebouwde updater van {appName} of een script", - "CalendarOptions": "Kalender Opties" + "CalendarOptions": "Kalender Opties", + "DeleteQualityProfileMessageText": "Bent u zeker dat u het kwaliteitsprofiel {name} wilt verwijderen?" } diff --git a/src/NzbDrone.Core/Localization/Core/pt_BR.json b/src/NzbDrone.Core/Localization/Core/pt_BR.json index 0433f4d47..eb6252099 100644 --- a/src/NzbDrone.Core/Localization/Core/pt_BR.json +++ b/src/NzbDrone.Core/Localization/Core/pt_BR.json @@ -1016,7 +1016,7 @@ "GrabSelected": "Obter Selecionado", "GrabbedHistoryTooltip": "Episódio retirado de {indexer} e enviado para {downloadClient}", "ImportedTo": "Importado Para", - "InfoUrl": "URL com informações", + "InfoUrl": "URL da info", "MarkAsFailed": "Marcar como Falha", "NoHistoryFound": "Nenhum histórico encontrado", "Ok": "Ok", @@ -1306,7 +1306,7 @@ "ToggleUnmonitoredToMonitored": "Não monitorado, clique para monitorar", "TotalRecords": "Total de registros: {totalRecords}", "True": "Verdadeiro", - "Umask": "Máscara de Usuário", + "Umask": "Desmascarar", "Umask755Description": "{octal} - Escrita do proprietário, todos os outros lêem", "Umask770Description": "{octal} - proprietário e gravação do grupo", "Umask777Description": "{octal} - Todo mundo escreve", @@ -1372,7 +1372,7 @@ "InteractiveSearchModalHeader": "Pesquisa Interativa", "InteractiveSearchModalHeaderSeason": "Pesquisa Interativa - {season}", "InteractiveSearchSeason": "Pesquisa interativa para todos os episódios desta temporada", - "InvalidUILanguage": "Sua IU está definida com um idioma inválido, corrija-a e salve suas configurações", + "InvalidUILanguage": "Sua IU está configurada com um idioma inválido, corrija-a e salve suas configurações", "Large": "Grande", "Links": "Links", "ManageEpisodes": "Gerenciar Episódios", diff --git a/src/NzbDrone.Core/Localization/Core/ru.json b/src/NzbDrone.Core/Localization/Core/ru.json index df6c65d17..08eaa7871 100644 --- a/src/NzbDrone.Core/Localization/Core/ru.json +++ b/src/NzbDrone.Core/Localization/Core/ru.json @@ -149,5 +149,21 @@ "RemoveSelectedItemsQueueMessageText": "Вы действительно хотите удалить {selectedCount} элементов из очереди?", "AuthenticationMethod": "Способ авторизации", "AuthenticationRequiredPasswordHelpTextWarning": "Введите новый пароль", - "AuthenticationRequiredUsernameHelpTextWarning": "Введите новое имя пользователя" + "AuthenticationRequiredUsernameHelpTextWarning": "Введите новое имя пользователя", + "Activity": "Активность", + "Add": "Добавить", + "Actions": "Действия", + "About": "Об", + "AddConnection": "Добавить подключение", + "AddConditionError": "Невозможно добавить новое условие, попробуйте еще раз.", + "AddCustomFormat": "Добавить свой формат", + "AddCondition": "Добавить условие", + "AddCustomFormatError": "Невозможно добавить новый пользовательский формат, попробуйте ещё раз.", + "AddDelayProfile": "Добавить профиль задержки", + "AddDownloadClient": "Добавить программу для скачивания", + "AddDownloadClientError": "Не удалось добавить новый клиент загрузки, попробуйте еще раз.", + "AddConnectionImplementation": "Добавить подключение - {implementationName}", + "AddCustomFilter": "Добавить специальный фильтр", + "Absolute": "Абсолютный", + "AddConditionImplementation": "Добавить условие - {implementationName}" } diff --git a/src/NzbDrone.Core/Localization/Core/zh_CN.json b/src/NzbDrone.Core/Localization/Core/zh_CN.json index e18e6f135..0b8833c91 100644 --- a/src/NzbDrone.Core/Localization/Core/zh_CN.json +++ b/src/NzbDrone.Core/Localization/Core/zh_CN.json @@ -405,7 +405,7 @@ "AllFiles": "全部文件", "AlreadyInYourLibrary": "已经在你的库中", "Always": "总是", - "AnimeEpisodeFormat": "动漫集格式", + "AnimeEpisodeFormat": "动漫单集格式", "AnimeTypeFormat": "绝对集数 ({format})", "AppUpdated": "{appName} 升级", "AppUpdatedVersion": "{appName} 已经更新到 {version} 版本,重新加载 {appName} 使更新生效 ", @@ -544,7 +544,7 @@ "AnalyseVideoFiles": "分析视频文件", "ApplicationURL": "应用程序 URL", "AnimeTypeDescription": "使用绝对集数发布的集数", - "ApiKey": "API 密钥", + "ApiKey": "API Key", "ApplicationUrlHelpText": "此应用的外部URL,包含 http(s)://、端口和基本URL", "AuthenticationMethodHelpText": "需要用户名和密码以访问{appName}", "AuthBasic": "基础(浏览器弹出对话框)", @@ -555,7 +555,7 @@ "Automatic": "自动化", "AutoTaggingRequiredHelpText": "此 {implementationName} 条件必须匹配才能应用自动标记规则。否则,一个 {implementationName} 匹配就足够了。", "AutoTaggingNegateHelpText": "如果选中,当 {implementationName} 条件匹配时,自动标记不会应用。", - "BackupRetentionHelpText": "早于保留周期的自动备份将被自动清除", + "BackupRetentionHelpText": "超过保留期限的自动备份将被自动清理", "BackupsLoadError": "无法加载备份", "BypassDelayIfAboveCustomFormatScoreMinimumScore": "最小自定义格式分数", "BypassDelayIfHighestQuality": "如果达到最高质量,则跳过", @@ -619,7 +619,7 @@ "DownloadPropersAndRepacksHelpTextWarning": "使用自定义格式自动升级到优化版/重制版", "EditConditionImplementation": "编辑条件 - {implementationName}", "EditConnectionImplementation": "编辑连接 - {implementationName}", - "Duplicate": "重复", + "Duplicate": "副本", "EditDelayProfile": "编辑延时配置", "EditDownloadClientImplementation": "编辑下载客户端 - {implementationName}", "EditCustomFormat": "编辑自定义格式", @@ -761,8 +761,8 @@ "EnableInteractiveSearchHelpText": "当手动搜索启用时使用", "EnableMediaInfoHelpText": "从文件中提取视频信息,如分辨率、运行时间和编解码器信息。这需要{appName}读取文件,可能导致扫描期间磁盘或网络出现高负载。", "GrabbedHistoryTooltip": "集抓取自 {indexer} 并发送至 {downloadClient}", - "HealthMessagesInfoBox": "您可以通过单击行尾的wiki链接(图书图标)或检查[logs]({link})来查找有关这些运行状况检查消息原因的更多信息。如果你在理解这些信息方面有困难,你可以通过下面的链接联系我们的支持。", - "StandardEpisodeFormat": "标准剧集格式", + "HealthMessagesInfoBox": "您可以通过单击行尾的wiki链接(图书图标)或检查[日志]({link})来查找有关这些运行状况检查消息原因的更多信息。如果你在理解这些信息方面有困难,你可以通过下面的链接联系我们的支持。", + "StandardEpisodeFormat": "标准单集格式", "DefaultNameCopiedSpecification": "{name} - 复制", "AddListExclusion": "添加排除列表", "AddListExclusionHelpText": "防止剧集通过列表添加到{appName}", @@ -775,7 +775,7 @@ "AuthenticationMethod": "认证方式", "AuthenticationRequiredPasswordHelpTextWarning": "输入新密码", "AuthenticationRequiredUsernameHelpTextWarning": "输入新用户名", - "DailyEpisodeFormat": "每日剧集格式", + "DailyEpisodeFormat": "每日单集格式", "MediaManagementSettingsSummary": "命名,文件管理和根文件夹设置", "ReplaceIllegalCharacters": "替换非法字符", "RenameFiles": "重命名文件", @@ -796,7 +796,7 @@ "SelectLanguageModalTitle": "{modalTitle} - 选择语言", "DefaultNameCopiedProfile": "{name} - 复制", "SeriesFinale": "大结局", - "SeriesFolderFormat": "集文件夹格式", + "SeriesFolderFormat": "剧集文件夹格式", "ReplaceIllegalCharactersHelpText": "替换非法字符。如果未选中,{appName}将删除它们", "SeriesAndEpisodeInformationIsProvidedByTheTVDB": "剧集和剧集信息由TheTVDB.com提供。[请考虑支持他们](https://www.thetvdb.com/subscribe)。", "DeleteSelectedSeries": "删除选中的剧集", @@ -1284,7 +1284,7 @@ "InteractiveSearch": "手动搜索", "InteractiveSearchModalHeader": "手动搜索", "InvalidFormat": "格式不合法", - "InvalidUILanguage": "您的UI被设置为无效的语言,请纠正它并保存设置", + "InvalidUILanguage": "您的UI设置的语言无效,请纠正它并保存设置", "KeyboardShortcuts": "键盘快捷键", "KeyboardShortcutsSaveSettings": "保存设置", "ListRootFolderHelpText": "根目录文件夹列表项需添加", @@ -1345,7 +1345,7 @@ "SomeResultsAreHiddenByTheAppliedFilter": "部分结果已被过滤隐藏", "Sort": "排序", "Specials": "特别节目", - "SpecialsFolderFormat": "特别节目文件夹格式", + "SpecialsFolderFormat": "特殊季文件夹格式", "SslCertPath": "SSL证书路径", "SslCertPathHelpText": "pfx文件路径", "SupportedAutoTaggingProperties": "{appName}支持自动标记规则的以下属性", @@ -1435,7 +1435,7 @@ "ProxyType": "代理类型", "QualityLimitsHelpText": "根据剧集运行时间和文件中的集数自动调整限制。", "DeleteSeriesFoldersHelpText": "删除剧集文件夹及其所含文件", - "EpisodeTitleRequiredHelpText": "如果集标题为命名格式,并且集标题为「待定」,则 48 小时内禁用导入", + "EpisodeTitleRequiredHelpText": "如果单集标题为命名格式且单集标题为「待定」,则 在48 小时内禁用导入", "LibraryImportTipsDontUseDownloadsFolder": "不要使用该方法从下载客户端导入影片,本方法只限于导入现有的已整理的库,不能导入未整理的文件。", "MinimumFreeSpace": "最小剩余空间", "Monday": "星期一", From bfaa7291e14a8d3847ef2154a52c363944560803 Mon Sep 17 00:00:00 2001 From: Mark McDowall <mark@mcdowall.ca> Date: Mon, 9 Oct 2023 20:37:31 -0700 Subject: [PATCH 031/136] Paging params in API docs Closes #6003 --- src/NzbDrone.Host/Startup.cs | 2 + .../WantedTests/CutoffUnmetFixture.cs | 2 +- .../ApiTests/WantedTests/MissingFixture.cs | 2 +- .../Client/ClientBase.cs | 5 +- .../Blocklist/BlocklistController.cs | 4 +- .../History/HistoryController.cs | 19 +- src/Sonarr.Api.V3/Logs/LogController.cs | 12 +- src/Sonarr.Api.V3/Queue/QueueController.cs | 4 +- src/Sonarr.Api.V3/Wanted/CutoffController.cs | 16 +- src/Sonarr.Api.V3/Wanted/MissingController.cs | 16 +- src/Sonarr.Api.V3/openapi.json | 345 ++++++++++++++---- .../Extensions/RequestExtensions.cs | 75 ---- src/Sonarr.Http/PagingResource.cs | 24 +- src/Sonarr.Http/PagingResourceFilter.cs | 8 - 14 files changed, 320 insertions(+), 214 deletions(-) delete mode 100644 src/Sonarr.Http/PagingResourceFilter.cs diff --git a/src/NzbDrone.Host/Startup.cs b/src/NzbDrone.Host/Startup.cs index 4bcc75e60..eb18394fc 100644 --- a/src/NzbDrone.Host/Startup.cs +++ b/src/NzbDrone.Host/Startup.cs @@ -158,6 +158,8 @@ namespace NzbDrone.Host { { apikeyQuery, Array.Empty<string>() } }); + + c.DescribeAllParametersInCamelCase(); }); services diff --git a/src/NzbDrone.Integration.Test/ApiTests/WantedTests/CutoffUnmetFixture.cs b/src/NzbDrone.Integration.Test/ApiTests/WantedTests/CutoffUnmetFixture.cs index 1a75068e4..1b4a2b61d 100644 --- a/src/NzbDrone.Integration.Test/ApiTests/WantedTests/CutoffUnmetFixture.cs +++ b/src/NzbDrone.Integration.Test/ApiTests/WantedTests/CutoffUnmetFixture.cs @@ -56,7 +56,7 @@ namespace NzbDrone.Integration.Test.ApiTests.WantedTests var series = EnsureSeries(266189, "The Blacklist", false); EnsureEpisodeFile(series, 1, 1, Quality.SDTV); - var result = WantedCutoffUnmet.GetPaged(0, 15, "airDateUtc", "desc", "monitored", "false"); + var result = WantedCutoffUnmet.GetPaged(0, 15, "airDateUtc", "desc", "monitored", false); result.Records.Should().NotBeEmpty(); } diff --git a/src/NzbDrone.Integration.Test/ApiTests/WantedTests/MissingFixture.cs b/src/NzbDrone.Integration.Test/ApiTests/WantedTests/MissingFixture.cs index a91b954f7..bc6ef9dfa 100644 --- a/src/NzbDrone.Integration.Test/ApiTests/WantedTests/MissingFixture.cs +++ b/src/NzbDrone.Integration.Test/ApiTests/WantedTests/MissingFixture.cs @@ -58,7 +58,7 @@ namespace NzbDrone.Integration.Test.ApiTests.WantedTests { EnsureSeries(266189, "The Blacklist", false); - var result = WantedMissing.GetPaged(0, 15, "airDateUtc", "desc", "monitored", "false"); + var result = WantedMissing.GetPaged(0, 15, "airDateUtc", "desc", "monitored", false); result.Records.Should().NotBeEmpty(); } diff --git a/src/NzbDrone.Integration.Test/Client/ClientBase.cs b/src/NzbDrone.Integration.Test/Client/ClientBase.cs index 8e2091f94..aa45059f6 100644 --- a/src/NzbDrone.Integration.Test/Client/ClientBase.cs +++ b/src/NzbDrone.Integration.Test/Client/ClientBase.cs @@ -102,7 +102,7 @@ namespace NzbDrone.Integration.Test.Client return Get<List<TResource>>(request); } - public PagingResource<TResource> GetPaged(int pageNumber, int pageSize, string sortKey, string sortDir, string filterKey = null, string filterValue = null) + public PagingResource<TResource> GetPaged(int pageNumber, int pageSize, string sortKey, string sortDir, string filterKey = null, object filterValue = null) { var request = BuildRequest(); request.AddParameter("page", pageNumber); @@ -113,8 +113,7 @@ namespace NzbDrone.Integration.Test.Client if (filterKey != null && filterValue != null) { - request.AddParameter("filterKey", filterKey); - request.AddParameter("filterValue", filterValue); + request.AddParameter(filterKey, filterValue); } return Get<PagingResource<TResource>>(request); diff --git a/src/Sonarr.Api.V3/Blocklist/BlocklistController.cs b/src/Sonarr.Api.V3/Blocklist/BlocklistController.cs index b5c9392b8..2091d3cfe 100644 --- a/src/Sonarr.Api.V3/Blocklist/BlocklistController.cs +++ b/src/Sonarr.Api.V3/Blocklist/BlocklistController.cs @@ -23,9 +23,9 @@ namespace Sonarr.Api.V3.Blocklist [HttpGet] [Produces("application/json")] - public PagingResource<BlocklistResource> GetBlocklist() + public PagingResource<BlocklistResource> GetBlocklist([FromQuery] PagingRequestResource paging) { - var pagingResource = Request.ReadPagingResourceFromRequest<BlocklistResource>(); + var pagingResource = new PagingResource<BlocklistResource>(paging); var pagingSpec = pagingResource.MapToPagingSpec<BlocklistResource, NzbDrone.Core.Blocklisting.Blocklist>("date", SortDirection.Descending); return pagingSpec.ApplyToPage(_blocklistService.Paged, model => BlocklistResourceMapper.MapToResource(model, _formatCalculator)); diff --git a/src/Sonarr.Api.V3/History/HistoryController.cs b/src/Sonarr.Api.V3/History/HistoryController.cs index cb6075676..088ed9163 100644 --- a/src/Sonarr.Api.V3/History/HistoryController.cs +++ b/src/Sonarr.Api.V3/History/HistoryController.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; using Microsoft.AspNetCore.Mvc; +using NzbDrone.Common.Extensions; using NzbDrone.Core.CustomFormats; using NzbDrone.Core.Datastore; using NzbDrone.Core.DecisionEngine.Specifications; @@ -61,30 +62,24 @@ namespace Sonarr.Api.V3.History [HttpGet] [Produces("application/json")] - public PagingResource<HistoryResource> GetHistory(bool includeSeries, bool includeEpisode) + public PagingResource<HistoryResource> GetHistory([FromQuery] PagingRequestResource paging, bool includeSeries, bool includeEpisode, int? eventType, int? episodeId, string downloadId) { - var pagingResource = Request.ReadPagingResourceFromRequest<HistoryResource>(); + var pagingResource = new PagingResource<HistoryResource>(paging); var pagingSpec = pagingResource.MapToPagingSpec<HistoryResource, EpisodeHistory>("date", SortDirection.Descending); - var eventTypeFilter = pagingResource.Filters.FirstOrDefault(f => f.Key == "eventType"); - var episodeIdFilter = pagingResource.Filters.FirstOrDefault(f => f.Key == "episodeId"); - var downloadIdFilter = pagingResource.Filters.FirstOrDefault(f => f.Key == "downloadId"); - - if (eventTypeFilter != null) + if (eventType.HasValue) { - var filterValue = (EpisodeHistoryEventType)Convert.ToInt32(eventTypeFilter.Value); + var filterValue = (EpisodeHistoryEventType)eventType.Value; pagingSpec.FilterExpressions.Add(v => v.EventType == filterValue); } - if (episodeIdFilter != null) + if (episodeId.HasValue) { - var episodeId = Convert.ToInt32(episodeIdFilter.Value); pagingSpec.FilterExpressions.Add(h => h.EpisodeId == episodeId); } - if (downloadIdFilter != null) + if (downloadId.IsNotNullOrWhiteSpace()) { - var downloadId = downloadIdFilter.Value; pagingSpec.FilterExpressions.Add(h => h.DownloadId == downloadId); } diff --git a/src/Sonarr.Api.V3/Logs/LogController.cs b/src/Sonarr.Api.V3/Logs/LogController.cs index 3350bcce3..e838bc7e6 100644 --- a/src/Sonarr.Api.V3/Logs/LogController.cs +++ b/src/Sonarr.Api.V3/Logs/LogController.cs @@ -1,5 +1,5 @@ -using System.Linq; using Microsoft.AspNetCore.Mvc; +using NzbDrone.Common.Extensions; using NzbDrone.Core.Instrumentation; using Sonarr.Http; using Sonarr.Http.Extensions; @@ -18,9 +18,9 @@ namespace Sonarr.Api.V3.Logs [HttpGet] [Produces("application/json")] - public PagingResource<LogResource> GetLogs() + public PagingResource<LogResource> GetLogs([FromQuery] PagingRequestResource paging, string level) { - var pagingResource = Request.ReadPagingResourceFromRequest<LogResource>(); + var pagingResource = new PagingResource<LogResource>(paging); var pageSpec = pagingResource.MapToPagingSpec<LogResource, Log>(); if (pageSpec.SortKey == "time") @@ -28,11 +28,9 @@ namespace Sonarr.Api.V3.Logs pageSpec.SortKey = "id"; } - var levelFilter = pagingResource.Filters.FirstOrDefault(f => f.Key == "level"); - - if (levelFilter != null) + if (level.IsNotNullOrWhiteSpace()) { - switch (levelFilter.Value) + switch (level) { case "fatal": pageSpec.FilterExpressions.Add(h => h.Level == "Fatal"); diff --git a/src/Sonarr.Api.V3/Queue/QueueController.cs b/src/Sonarr.Api.V3/Queue/QueueController.cs index 6259956c3..edb631ee4 100644 --- a/src/Sonarr.Api.V3/Queue/QueueController.cs +++ b/src/Sonarr.Api.V3/Queue/QueueController.cs @@ -135,9 +135,9 @@ namespace Sonarr.Api.V3.Queue [HttpGet] [Produces("application/json")] - public PagingResource<QueueResource> GetQueue(bool includeUnknownSeriesItems = false, bool includeSeries = false, bool includeEpisode = false) + public PagingResource<QueueResource> GetQueue([FromQuery] PagingRequestResource paging, bool includeUnknownSeriesItems = false, bool includeSeries = false, bool includeEpisode = false) { - var pagingResource = Request.ReadPagingResourceFromRequest<QueueResource>(); + var pagingResource = new PagingResource<QueueResource>(paging); var pagingSpec = pagingResource.MapToPagingSpec<QueueResource, NzbDrone.Core.Queue.Queue>("timeleft", SortDirection.Ascending); return pagingSpec.ApplyToPage((spec) => GetQueue(spec, includeUnknownSeriesItems), (q) => MapToResource(q, includeSeries, includeEpisode)); diff --git a/src/Sonarr.Api.V3/Wanted/CutoffController.cs b/src/Sonarr.Api.V3/Wanted/CutoffController.cs index b906399b0..479212ec7 100644 --- a/src/Sonarr.Api.V3/Wanted/CutoffController.cs +++ b/src/Sonarr.Api.V3/Wanted/CutoffController.cs @@ -1,4 +1,3 @@ -using System.Linq; using Microsoft.AspNetCore.Mvc; using NzbDrone.Core.CustomFormats; using NzbDrone.Core.Datastore; @@ -29,9 +28,9 @@ namespace Sonarr.Api.V3.Wanted [HttpGet] [Produces("application/json")] - public PagingResource<EpisodeResource> GetCutoffUnmetEpisodes(bool includeSeries = false, bool includeEpisodeFile = false, bool includeImages = false) + public PagingResource<EpisodeResource> GetCutoffUnmetEpisodes([FromQuery] PagingRequestResource paging, bool includeSeries = false, bool includeEpisodeFile = false, bool includeImages = false, bool monitored = true) { - var pagingResource = Request.ReadPagingResourceFromRequest<EpisodeResource>(); + var pagingResource = new PagingResource<EpisodeResource>(paging); var pagingSpec = new PagingSpec<Episode> { Page = pagingResource.Page, @@ -40,16 +39,7 @@ namespace Sonarr.Api.V3.Wanted SortDirection = pagingResource.SortDirection }; - var filter = pagingResource.Filters.FirstOrDefault(f => f.Key == "monitored"); - - if (filter != null && filter.Value == "false") - { - pagingSpec.FilterExpressions.Add(v => v.Monitored == false || v.Series.Monitored == false); - } - else - { - pagingSpec.FilterExpressions.Add(v => v.Monitored == true && v.Series.Monitored == true); - } + pagingSpec.FilterExpressions.Add(v => v.Monitored == monitored || v.Series.Monitored == monitored); var resource = pagingSpec.ApplyToPage(_episodeCutoffService.EpisodesWhereCutoffUnmet, v => MapToResource(v, includeSeries, includeEpisodeFile, includeImages)); diff --git a/src/Sonarr.Api.V3/Wanted/MissingController.cs b/src/Sonarr.Api.V3/Wanted/MissingController.cs index 6ed40dda7..43832c0d2 100644 --- a/src/Sonarr.Api.V3/Wanted/MissingController.cs +++ b/src/Sonarr.Api.V3/Wanted/MissingController.cs @@ -1,4 +1,3 @@ -using System.Linq; using Microsoft.AspNetCore.Mvc; using NzbDrone.Core.CustomFormats; using NzbDrone.Core.Datastore; @@ -25,9 +24,9 @@ namespace Sonarr.Api.V3.Wanted [HttpGet] [Produces("application/json")] - public PagingResource<EpisodeResource> GetMissingEpisodes(bool includeSeries = false, bool includeImages = false) + public PagingResource<EpisodeResource> GetMissingEpisodes([FromQuery] PagingRequestResource paging, bool includeSeries = false, bool includeImages = false, bool monitored = true) { - var pagingResource = Request.ReadPagingResourceFromRequest<EpisodeResource>(); + var pagingResource = new PagingResource<EpisodeResource>(paging); var pagingSpec = new PagingSpec<Episode> { Page = pagingResource.Page, @@ -36,16 +35,7 @@ namespace Sonarr.Api.V3.Wanted SortDirection = pagingResource.SortDirection }; - var monitoredFilter = pagingResource.Filters.FirstOrDefault(f => f.Key == "monitored"); - - if (monitoredFilter != null && monitoredFilter.Value == "false") - { - pagingSpec.FilterExpressions.Add(v => v.Monitored == false || v.Series.Monitored == false); - } - else - { - pagingSpec.FilterExpressions.Add(v => v.Monitored == true && v.Series.Monitored == true); - } + pagingSpec.FilterExpressions.Add(v => v.Monitored == monitored || v.Series.Monitored == monitored); var resource = pagingSpec.ApplyToPage(_episodeService.EpisodesWithoutFiles, v => MapToResource(v, includeSeries, false, includeImages)); diff --git a/src/Sonarr.Api.V3/openapi.json b/src/Sonarr.Api.V3/openapi.json index ef261ba3c..462f08b69 100644 --- a/src/Sonarr.Api.V3/openapi.json +++ b/src/Sonarr.Api.V3/openapi.json @@ -59,25 +59,25 @@ "schema": { "type": "object", "properties": { - "Username": { + "username": { "type": "string" }, - "Password": { + "password": { "type": "string" }, - "RememberMe": { + "rememberMe": { "type": "string" } } }, "encoding": { - "Username": { + "username": { "style": "form" }, - "Password": { + "password": { "style": "form" }, - "RememberMe": { + "rememberMe": { "style": "form" } } @@ -381,6 +381,40 @@ "tags": [ "Blocklist" ], + "parameters": [ + { + "name": "page", + "in": "query", + "schema": { + "type": "integer", + "format": "int32", + "default": 1 + } + }, + { + "name": "pageSize", + "in": "query", + "schema": { + "type": "integer", + "format": "int32", + "default": 10 + } + }, + { + "name": "sortKey", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "sortDirection", + "in": "query", + "schema": { + "$ref": "#/components/schemas/SortDirection" + } + } + ], "responses": { "200": { "description": "Success", @@ -1050,6 +1084,38 @@ "Cutoff" ], "parameters": [ + { + "name": "page", + "in": "query", + "schema": { + "type": "integer", + "format": "int32", + "default": 1 + } + }, + { + "name": "pageSize", + "in": "query", + "schema": { + "type": "integer", + "format": "int32", + "default": 10 + } + }, + { + "name": "sortKey", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "sortDirection", + "in": "query", + "schema": { + "$ref": "#/components/schemas/SortDirection" + } + }, { "name": "includeSeries", "in": "query", @@ -1073,6 +1139,14 @@ "type": "boolean", "default": false } + }, + { + "name": "monitored", + "in": "query", + "schema": { + "type": "boolean", + "default": true + } } ], "responses": { @@ -2220,6 +2294,38 @@ "History" ], "parameters": [ + { + "name": "page", + "in": "query", + "schema": { + "type": "integer", + "format": "int32", + "default": 1 + } + }, + { + "name": "pageSize", + "in": "query", + "schema": { + "type": "integer", + "format": "int32", + "default": 10 + } + }, + { + "name": "sortKey", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "sortDirection", + "in": "query", + "schema": { + "$ref": "#/components/schemas/SortDirection" + } + }, { "name": "includeSeries", "in": "query", @@ -2233,6 +2339,29 @@ "schema": { "type": "boolean" } + }, + { + "name": "eventType", + "in": "query", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "episodeId", + "in": "query", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "downloadId", + "in": "query", + "schema": { + "type": "string" + } } ], "responses": { @@ -3627,6 +3756,47 @@ "tags": [ "Log" ], + "parameters": [ + { + "name": "page", + "in": "query", + "schema": { + "type": "integer", + "format": "int32", + "default": 1 + } + }, + { + "name": "pageSize", + "in": "query", + "schema": { + "type": "integer", + "format": "int32", + "default": 10 + } + }, + { + "name": "sortKey", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "sortDirection", + "in": "query", + "schema": { + "$ref": "#/components/schemas/SortDirection" + } + }, + { + "name": "level", + "in": "query", + "schema": { + "type": "string" + } + } + ], "responses": { "200": { "description": "Success", @@ -4142,6 +4312,38 @@ "Missing" ], "parameters": [ + { + "name": "page", + "in": "query", + "schema": { + "type": "integer", + "format": "int32", + "default": 1 + } + }, + { + "name": "pageSize", + "in": "query", + "schema": { + "type": "integer", + "format": "int32", + "default": 10 + } + }, + { + "name": "sortKey", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "sortDirection", + "in": "query", + "schema": { + "$ref": "#/components/schemas/SortDirection" + } + }, { "name": "includeSeries", "in": "query", @@ -4157,6 +4359,14 @@ "type": "boolean", "default": false } + }, + { + "name": "monitored", + "in": "query", + "schema": { + "type": "boolean", + "default": true + } } ], "responses": { @@ -4325,21 +4535,21 @@ ], "parameters": [ { - "name": "RenameEpisodes", + "name": "renameEpisodes", "in": "query", "schema": { "type": "boolean" } }, { - "name": "ReplaceIllegalCharacters", + "name": "replaceIllegalCharacters", "in": "query", "schema": { "type": "boolean" } }, { - "name": "ColonReplacementFormat", + "name": "colonReplacementFormat", "in": "query", "schema": { "type": "integer", @@ -4347,7 +4557,7 @@ } }, { - "name": "MultiEpisodeStyle", + "name": "multiEpisodeStyle", "in": "query", "schema": { "type": "integer", @@ -4355,91 +4565,91 @@ } }, { - "name": "StandardEpisodeFormat", + "name": "standardEpisodeFormat", "in": "query", "schema": { "type": "string" } }, { - "name": "DailyEpisodeFormat", + "name": "dailyEpisodeFormat", "in": "query", "schema": { "type": "string" } }, { - "name": "AnimeEpisodeFormat", + "name": "animeEpisodeFormat", "in": "query", "schema": { "type": "string" } }, { - "name": "SeriesFolderFormat", + "name": "seriesFolderFormat", "in": "query", "schema": { "type": "string" } }, { - "name": "SeasonFolderFormat", + "name": "seasonFolderFormat", "in": "query", "schema": { "type": "string" } }, { - "name": "SpecialsFolderFormat", + "name": "specialsFolderFormat", "in": "query", "schema": { "type": "string" } }, { - "name": "IncludeSeriesTitle", + "name": "includeSeriesTitle", "in": "query", "schema": { "type": "boolean" } }, { - "name": "IncludeEpisodeTitle", + "name": "includeEpisodeTitle", "in": "query", "schema": { "type": "boolean" } }, { - "name": "IncludeQuality", + "name": "includeQuality", "in": "query", "schema": { "type": "boolean" } }, { - "name": "ReplaceSpaces", + "name": "replaceSpaces", "in": "query", "schema": { "type": "boolean" } }, { - "name": "Separator", + "name": "separator", "in": "query", "schema": { "type": "string" } }, { - "name": "NumberStyle", + "name": "numberStyle", "in": "query", "schema": { "type": "string" } }, { - "name": "Id", + "name": "id", "in": "query", "schema": { "type": "integer", @@ -4447,7 +4657,7 @@ } }, { - "name": "ResourceName", + "name": "resourceName", "in": "query", "schema": { "type": "string" @@ -5212,6 +5422,38 @@ "Queue" ], "parameters": [ + { + "name": "page", + "in": "query", + "schema": { + "type": "integer", + "format": "int32", + "default": 1 + } + }, + { + "name": "pageSize", + "in": "query", + "schema": { + "type": "integer", + "format": "int32", + "default": 10 + } + }, + { + "name": "sortKey", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "sortDirection", + "in": "query", + "schema": { + "$ref": "#/components/schemas/SortDirection" + } + }, { "name": "includeUnknownSeriesItems", "in": "query", @@ -7185,13 +7427,6 @@ "sortDirection": { "$ref": "#/components/schemas/SortDirection" }, - "filters": { - "type": "array", - "items": { - "$ref": "#/components/schemas/PagingResourceFilter" - }, - "nullable": true - }, "totalRecords": { "type": "integer", "format": "int32" @@ -7402,7 +7637,7 @@ "type": "array", "items": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} }, "nullable": true } @@ -7951,13 +8186,6 @@ "sortDirection": { "$ref": "#/components/schemas/SortDirection" }, - "filters": { - "type": "array", - "items": { - "$ref": "#/components/schemas/PagingResourceFilter" - }, - "nullable": true - }, "totalRecords": { "type": "integer", "format": "int32" @@ -8200,13 +8428,6 @@ "sortDirection": { "$ref": "#/components/schemas/SortDirection" }, - "filters": { - "type": "array", - "items": { - "$ref": "#/components/schemas/PagingResourceFilter" - }, - "nullable": true - }, "totalRecords": { "type": "integer", "format": "int32" @@ -8891,13 +9112,6 @@ "sortDirection": { "$ref": "#/components/schemas/SortDirection" }, - "filters": { - "type": "array", - "items": { - "$ref": "#/components/schemas/PagingResourceFilter" - }, - "nullable": true - }, "totalRecords": { "type": "integer", "format": "int32" @@ -9549,20 +9763,6 @@ }, "additionalProperties": false }, - "PagingResourceFilter": { - "type": "object", - "properties": { - "key": { - "type": "string", - "nullable": true - }, - "value": { - "type": "string", - "nullable": true - } - }, - "additionalProperties": false - }, "ParseResource": { "type": "object", "properties": { @@ -10096,13 +10296,6 @@ "sortDirection": { "$ref": "#/components/schemas/SortDirection" }, - "filters": { - "type": "array", - "items": { - "$ref": "#/components/schemas/PagingResourceFilter" - }, - "nullable": true - }, "totalRecords": { "type": "integer", "format": "int32" @@ -11628,10 +11821,10 @@ }, "security": [ { - "X-Api-Key": [ ] + "X-Api-Key": [] }, { - "apikey": [ ] + "apikey": [] } ] } \ No newline at end of file diff --git a/src/Sonarr.Http/Extensions/RequestExtensions.cs b/src/Sonarr.Http/Extensions/RequestExtensions.cs index 8e7ed9590..772264dfb 100644 --- a/src/Sonarr.Http/Extensions/RequestExtensions.cs +++ b/src/Sonarr.Http/Extensions/RequestExtensions.cs @@ -4,7 +4,6 @@ using System.Linq; using Microsoft.AspNetCore.Http; using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Core.Datastore; -using NzbDrone.Core.Exceptions; namespace Sonarr.Http.Extensions { @@ -61,80 +60,6 @@ namespace Sonarr.Http.Extensions return defaultValue; } - public static PagingResource<TResource> ReadPagingResourceFromRequest<TResource>(this HttpRequest request) - { - if (!int.TryParse(request.Query["PageSize"].ToString(), out var pageSize)) - { - pageSize = 10; - } - - if (!int.TryParse(request.Query["Page"].ToString(), out var page)) - { - page = 1; - } - - var pagingResource = new PagingResource<TResource> - { - PageSize = pageSize, - Page = page, - Filters = new List<PagingResourceFilter>() - }; - - if (request.Query["SortKey"].Any()) - { - var sortKey = request.Query["SortKey"].ToString(); - - if (!VALID_SORT_KEYS.Contains(sortKey) && - !TableMapping.Mapper.IsValidSortKey(sortKey)) - { - throw new BadRequestException($"Invalid sort key {sortKey}"); - } - - pagingResource.SortKey = sortKey; - - if (request.Query["SortDirection"].Any()) - { - pagingResource.SortDirection = request.Query["SortDirection"].ToString() - .Equals("ascending", StringComparison.InvariantCultureIgnoreCase) - ? SortDirection.Ascending - : SortDirection.Descending; - } - } - - // For backwards compatibility with v2 - if (request.Query["FilterKey"].Any()) - { - var filter = new PagingResourceFilter - { - Key = request.Query["FilterKey"].ToString() - }; - - if (request.Query["FilterValue"].Any()) - { - filter.Value = request.Query["FilterValue"].ToString(); - } - - pagingResource.Filters.Add(filter); - } - - // v3 uses filters in key=value format - foreach (var pair in request.Query) - { - if (EXCLUDED_KEYS.Contains(pair.Key)) - { - continue; - } - - pagingResource.Filters.Add(new PagingResourceFilter - { - Key = pair.Key, - Value = pair.Value.ToString() - }); - } - - return pagingResource; - } - public static PagingResource<TResource> ApplyToPage<TResource, TModel>(this PagingSpec<TModel> pagingSpec, Func<PagingSpec<TModel>, PagingSpec<TModel>> function, Converter<TModel, TResource> mapper) { pagingSpec = function(pagingSpec); diff --git a/src/Sonarr.Http/PagingResource.cs b/src/Sonarr.Http/PagingResource.cs index d1c0a6eb7..6559d80ab 100644 --- a/src/Sonarr.Http/PagingResource.cs +++ b/src/Sonarr.Http/PagingResource.cs @@ -1,17 +1,39 @@ using System.Collections.Generic; +using System.ComponentModel; using NzbDrone.Core.Datastore; namespace Sonarr.Http { + public class PagingRequestResource + { + [DefaultValue(1)] + public int? Page { get; set; } + [DefaultValue(10)] + public int? PageSize { get; set; } + public string SortKey { get; set; } + public SortDirection? SortDirection { get; set; } + } + public class PagingResource<TResource> { public int Page { get; set; } public int PageSize { get; set; } public string SortKey { get; set; } public SortDirection SortDirection { get; set; } - public List<PagingResourceFilter> Filters { get; set; } public int TotalRecords { get; set; } public List<TResource> Records { get; set; } + + public PagingResource() + { + } + + public PagingResource(PagingRequestResource requestResource) + { + Page = requestResource.Page ?? 1; + PageSize = requestResource.PageSize ?? 10; + SortKey = requestResource.SortKey; + SortDirection = requestResource.SortDirection ?? SortDirection.Descending; + } } public static class PagingResourceMapper diff --git a/src/Sonarr.Http/PagingResourceFilter.cs b/src/Sonarr.Http/PagingResourceFilter.cs deleted file mode 100644 index 303097a9a..000000000 --- a/src/Sonarr.Http/PagingResourceFilter.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Sonarr.Http -{ - public class PagingResourceFilter - { - public string Key { get; set; } - public string Value { get; set; } - } -} From b3c691859a0841cde9bb2655c01c240cc5279d74 Mon Sep 17 00:00:00 2001 From: Stevie Robinson <stevie.robinson@gmail.com> Date: Tue, 10 Oct 2023 05:38:49 +0200 Subject: [PATCH 032/136] Fixed: Cleanup First Character in Title when using 'TitleFirstCharacter' Closes #6055 --- .../SeriesTitleFirstCharacterFixture.cs | 6 ++++++ .../Organizer/FileNameBuilder.cs | 19 ++++++++++++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/SeriesTitleFirstCharacterFixture.cs b/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/SeriesTitleFirstCharacterFixture.cs index 763b948a6..b943cea10 100644 --- a/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/SeriesTitleFirstCharacterFixture.cs +++ b/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/SeriesTitleFirstCharacterFixture.cs @@ -36,6 +36,12 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests [TestCase("The Mist", "M\\The Mist")] [TestCase("A", "A\\A")] [TestCase("30 Rock", "3\\30 Rock")] + [TestCase("The '80s Greatest", "8\\The '80s Greatest")] + [TestCase("좀비버스", "좀\\좀비버스")] + [TestCase("¡Mucha Lucha!", "M\\¡Mucha Lucha!")] + [TestCase(".hack", "H\\hack")] + [TestCase("Ütopya", "U\\Ütopya")] + public void should_get_expected_folder_name_back(string title, string expected) { _series.Title = title; diff --git a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs index 5b638cfbd..9af357cd3 100644 --- a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs +++ b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs @@ -377,6 +377,23 @@ namespace NzbDrone.Core.Organizer return title; } + public static string TitleFirstCharacter(string title) + { + if (char.IsLetterOrDigit(title[0])) + { + return title.Substring(0, 1).ToUpper().RemoveAccent(); + } + + // Try the second character if the first was non alphanumeric + if (char.IsLetterOrDigit(title[1])) + { + return title.Substring(1, 1).ToUpper().RemoveAccent(); + } + + // Default to "_" if no alphanumeric character can be found in the first 2 positions + return "_"; + } + public static string CleanFileName(string name) { return CleanFileName(name, NamingConfig.Default); @@ -452,7 +469,7 @@ namespace NzbDrone.Core.Organizer tokenHandlers["{Series TitleWithoutYear}"] = m => TitleWithoutYear(series.Title); tokenHandlers["{Series TitleTheYear}"] = m => TitleYear(TitleThe(series.Title), series.Year); tokenHandlers["{Series TitleTheWithoutYear}"] = m => TitleWithoutYear(TitleThe(series.Title)); - tokenHandlers["{Series TitleFirstCharacter}"] = m => TitleThe(series.Title).Substring(0, 1).FirstCharToUpper(); + tokenHandlers["{Series TitleFirstCharacter}"] = m => TitleFirstCharacter(TitleThe(series.Title)); tokenHandlers["{Series Year}"] = m => series.Year.ToString(); } From 4ffa1816bd2305550abee20cea27e1296a99ddf6 Mon Sep 17 00:00:00 2001 From: Bogdan <mynameisbogdan@users.noreply.github.com> Date: Mon, 2 Oct 2023 18:20:35 +0300 Subject: [PATCH 033/136] Add status test all button for IndexerLongTermStatusCheck --- frontend/src/System/Status/Health/Health.js | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/System/Status/Health/Health.js b/frontend/src/System/Status/Health/Health.js index 31e1cf053..0a8a2e5a9 100644 --- a/frontend/src/System/Status/Health/Health.js +++ b/frontend/src/System/Status/Health/Health.js @@ -72,6 +72,7 @@ function getInternalLink(source) { function getTestLink(source, props) { switch (source) { case 'IndexerStatusCheck': + case 'IndexerLongTermStatusCheck': return ( <SpinnerIconButton name={icons.TEST} From 732c2fe12f0ac4d1bb46ba888f353121cc3e570f Mon Sep 17 00:00:00 2001 From: Stevie Robinson <stevie.robinson@gmail.com> Date: Tue, 10 Oct 2023 05:39:26 +0200 Subject: [PATCH 034/136] Fixed: Parsing Spanish releases --- .../ParserTests/SingleEpisodeParserFixture.cs | 3 +++ src/NzbDrone.Core/Parser/Parser.cs | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/NzbDrone.Core.Test/ParserTests/SingleEpisodeParserFixture.cs b/src/NzbDrone.Core.Test/ParserTests/SingleEpisodeParserFixture.cs index 4ebd0f1e1..c7f4eb389 100644 --- a/src/NzbDrone.Core.Test/ParserTests/SingleEpisodeParserFixture.cs +++ b/src/NzbDrone.Core.Test/ParserTests/SingleEpisodeParserFixture.cs @@ -161,6 +161,9 @@ namespace NzbDrone.Core.Test.ParserTests [TestCase("Series-S07E12-31st_Century_Fox-[Bluray-1080p].mkv", "Series", 7, 12)] [TestCase("TheTitle-S12E13-3_Acts_of_God.mkv", "TheTitle", 12, 13)] [TestCase("Series Title - Temporada 2 [HDTV 720p][Cap.408]", "Series Title", 4, 8)] + [TestCase("Series Title [HDTV][Cap.104](website.com).avi", "Series Title", 1, 4)] + [TestCase("Series Title [HDTV][Cap.402](website.com).avi", "Series Title", 4, 2)] + [TestCase("Series Title [HDTV 720p][Cap.101](website.com).mkv", "Series Title", 1, 1)] // [TestCase("", "", 0, 0)] public void should_parse_single_episode(string postTitle, string title, int seasonNumber, int episodeNumber) diff --git a/src/NzbDrone.Core/Parser/Parser.cs b/src/NzbDrone.Core/Parser/Parser.cs index 789fba4f7..35880d0e9 100644 --- a/src/NzbDrone.Core/Parser/Parser.cs +++ b/src/NzbDrone.Core/Parser/Parser.cs @@ -363,7 +363,7 @@ namespace NzbDrone.Core.Parser RegexOptions.IgnoreCase | RegexOptions.Compiled), // Spanish tracker releases - new Regex(@"^(?<title>.+?)(?:[-_. ]+?Temporada.+?\[Cap[-_.])(?<season>(?<!\d+)\d{1,2})(?<episode>(?<!e|x)(?:[1-9][0-9]|[0][1-9]))(?:\])", + new Regex(@"^(?<title>.+?)(?:(?:[-_. ]+?Temporada.+?|\[.+?\])\[Cap[-_.])(?<season>(?<!\d+)\d{1,2})(?<episode>(?<!e|x)(?:[1-9][0-9]|[0][1-9]))(?:\])", RegexOptions.IgnoreCase | RegexOptions.Compiled), // Anime Range - Title Absolute Episode Number (ep01-12) From 8c07f0d3d19a48ed96d1ded54399c66bf2977b2a Mon Sep 17 00:00:00 2001 From: Stevie Robinson <stevie.robinson@gmail.com> Date: Tue, 10 Oct 2023 05:40:00 +0200 Subject: [PATCH 035/136] New: Additional tooltips for icon buttons --- frontend/src/Components/Modal/ModalContent.js | 2 ++ frontend/src/Components/Page/Header/PageHeader.js | 1 + frontend/src/Components/Page/Header/PageHeaderActionsMenu.js | 1 + frontend/src/Episode/EpisodeSearchCell.js | 3 +++ 4 files changed, 7 insertions(+) diff --git a/frontend/src/Components/Modal/ModalContent.js b/frontend/src/Components/Modal/ModalContent.js index 8883bf2b9..1d3862a13 100644 --- a/frontend/src/Components/Modal/ModalContent.js +++ b/frontend/src/Components/Modal/ModalContent.js @@ -3,6 +3,7 @@ import React from 'react'; import Icon from 'Components/Icon'; import Link from 'Components/Link/Link'; import { icons } from 'Helpers/Props'; +import translate from 'Utilities/String/translate'; import styles from './ModalContent.css'; function ModalContent(props) { @@ -28,6 +29,7 @@ function ModalContent(props) { <Icon name={icons.CLOSE} size={18} + title={translate('Close')} /> </Link> } diff --git a/frontend/src/Components/Page/Header/PageHeader.js b/frontend/src/Components/Page/Header/PageHeader.js index 23d7d7f99..2af052015 100644 --- a/frontend/src/Components/Page/Header/PageHeader.js +++ b/frontend/src/Components/Page/Header/PageHeader.js @@ -81,6 +81,7 @@ class PageHeader extends Component { aria-label={translate('Donate')} to="https://sonarr.tv/donate.html" size={14} + title={translate('Donate')} /> <PageHeaderActionsMenuConnector onKeyboardShortcutsPress={this.onOpenKeyboardShortcutsModal} diff --git a/frontend/src/Components/Page/Header/PageHeaderActionsMenu.js b/frontend/src/Components/Page/Header/PageHeaderActionsMenu.js index cb5914aaf..88a974f71 100644 --- a/frontend/src/Components/Page/Header/PageHeaderActionsMenu.js +++ b/frontend/src/Components/Page/Header/PageHeaderActionsMenu.js @@ -24,6 +24,7 @@ function PageHeaderActionsMenu(props) { <MenuButton className={styles.menuButton} aria-label="Menu Button"> <Icon name={icons.INTERACTIVE} + title={translate('Menu')} /> </MenuButton> diff --git a/frontend/src/Episode/EpisodeSearchCell.js b/frontend/src/Episode/EpisodeSearchCell.js index c09b3e65a..3ec76d365 100644 --- a/frontend/src/Episode/EpisodeSearchCell.js +++ b/frontend/src/Episode/EpisodeSearchCell.js @@ -4,6 +4,7 @@ import IconButton from 'Components/Link/IconButton'; import SpinnerIconButton from 'Components/Link/SpinnerIconButton'; import TableRowCell from 'Components/Table/Cells/TableRowCell'; import { icons } from 'Helpers/Props'; +import translate from 'Utilities/String/translate'; import EpisodeDetailsModal from './EpisodeDetailsModal'; import styles from './EpisodeSearchCell.css'; @@ -50,11 +51,13 @@ class EpisodeSearchCell extends Component { name={icons.SEARCH} isSpinning={isSearching} onPress={onSearchPress} + title={translate('AutomaticSearch')} /> <IconButton name={icons.INTERACTIVE} onPress={this.onManualSearchPress} + title={translate('InteractiveSearch')} /> <EpisodeDetailsModal From a26df9e9afa8d925c2ad62c126d4edebec7e4e54 Mon Sep 17 00:00:00 2001 From: Bogdan <mynameisbogdan@users.noreply.github.com> Date: Thu, 5 Oct 2023 02:42:18 +0300 Subject: [PATCH 036/136] Prevent NullRef for cases when media covers have nullable urls --- src/NzbDrone.Core/MediaCover/MediaCoverProxy.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/NzbDrone.Core/MediaCover/MediaCoverProxy.cs b/src/NzbDrone.Core/MediaCover/MediaCoverProxy.cs index b8b0cc3a0..fa489171b 100644 --- a/src/NzbDrone.Core/MediaCover/MediaCoverProxy.cs +++ b/src/NzbDrone.Core/MediaCover/MediaCoverProxy.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.IO; using System.Threading.Tasks; using NzbDrone.Common.Cache; +using NzbDrone.Common.Extensions; using NzbDrone.Common.Http; using NzbDrone.Core.Configuration; @@ -31,6 +32,11 @@ namespace NzbDrone.Core.MediaCover public string RegisterUrl(string url) { + if (url.IsNullOrWhiteSpace()) + { + return null; + } + var hash = url.SHA256Hash(); _cache.Set(hash, url, TimeSpan.FromHours(24)); From 6de3e7c950bd939bab96ef2ae74337108ad5a212 Mon Sep 17 00:00:00 2001 From: Bogdan <mynameisbogdan@users.noreply.github.com> Date: Fri, 6 Oct 2023 04:13:22 +0300 Subject: [PATCH 037/136] New: Auto tag based on series quality profile --- .../Components/Form/ProviderFieldFormGroup.js | 2 ++ .../Annotations/FieldDefinitionAttribute.cs | 3 +- .../QualityProfileSpecification.cs | 36 +++++++++++++++++++ 3 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 src/NzbDrone.Core/AutoTagging/Specifications/QualityProfileSpecification.cs diff --git a/frontend/src/Components/Form/ProviderFieldFormGroup.js b/frontend/src/Components/Form/ProviderFieldFormGroup.js index d7486245a..a184aa1ec 100644 --- a/frontend/src/Components/Form/ProviderFieldFormGroup.js +++ b/frontend/src/Components/Form/ProviderFieldFormGroup.js @@ -37,6 +37,8 @@ function getType({ type, selectOptionsProviderAction }) { return inputTypes.OAUTH; case 'rootFolder': return inputTypes.ROOT_FOLDER_SELECT; + case 'qualityProfile': + return inputTypes.QUALITY_PROFILE_SELECT; default: return inputTypes.TEXT; } diff --git a/src/NzbDrone.Core/Annotations/FieldDefinitionAttribute.cs b/src/NzbDrone.Core/Annotations/FieldDefinitionAttribute.cs index 0a30a9f3f..7b711a37d 100644 --- a/src/NzbDrone.Core/Annotations/FieldDefinitionAttribute.cs +++ b/src/NzbDrone.Core/Annotations/FieldDefinitionAttribute.cs @@ -67,7 +67,8 @@ namespace NzbDrone.Core.Annotations OAuth, Device, TagSelect, - RootFolder + RootFolder, + QualityProfile } public enum HiddenType diff --git a/src/NzbDrone.Core/AutoTagging/Specifications/QualityProfileSpecification.cs b/src/NzbDrone.Core/AutoTagging/Specifications/QualityProfileSpecification.cs new file mode 100644 index 000000000..4ac2ef14b --- /dev/null +++ b/src/NzbDrone.Core/AutoTagging/Specifications/QualityProfileSpecification.cs @@ -0,0 +1,36 @@ +using FluentValidation; +using NzbDrone.Core.Annotations; +using NzbDrone.Core.Tv; +using NzbDrone.Core.Validation; + +namespace NzbDrone.Core.AutoTagging.Specifications +{ + public class QualityProfileSpecificationValidator : AbstractValidator<QualityProfileSpecification> + { + public QualityProfileSpecificationValidator() + { + RuleFor(c => c.Value).GreaterThan(0); + } + } + + public class QualityProfileSpecification : AutoTaggingSpecificationBase + { + private static readonly QualityProfileSpecificationValidator Validator = new (); + + public override int Order => 1; + public override string ImplementationName => "Quality Profile"; + + [FieldDefinition(1, Label = "Quality Profile", Type = FieldType.QualityProfile)] + public int Value { get; set; } + + protected override bool IsSatisfiedByWithoutNegate(Series series) + { + return Value == series.QualityProfileId; + } + + public override NzbDroneValidationResult Validate() + { + return new NzbDroneValidationResult(Validator.Validate(this)); + } + } +} From 44eb729ccc13237f4439006159bd616e8bdb5750 Mon Sep 17 00:00:00 2001 From: Bogdan <mynameisbogdan@users.noreply.github.com> Date: Sun, 8 Oct 2023 22:58:42 +0300 Subject: [PATCH 038/136] Fixed: Avoid logging evaluations when not using any Remote Path Mappings --- .../RemotePathMappingService.cs | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/NzbDrone.Core/RemotePathMappings/RemotePathMappingService.cs b/src/NzbDrone.Core/RemotePathMappings/RemotePathMappingService.cs index 5e033b582..b757db3f3 100644 --- a/src/NzbDrone.Core/RemotePathMappings/RemotePathMappingService.cs +++ b/src/NzbDrone.Core/RemotePathMappings/RemotePathMappingService.cs @@ -127,8 +127,16 @@ namespace NzbDrone.Core.RemotePathMappings return remotePath; } + var mappings = All(); + + if (mappings.Empty()) + { + return remotePath; + } + _logger.Trace("Evaluating remote path remote mappings for match to host [{0}] and remote path [{1}]", host, remotePath.FullPath); - foreach (var mapping in All()) + + foreach (var mapping in mappings) { _logger.Trace("Checking configured remote path mapping: {0} - {1}", mapping.Host, mapping.RemotePath); if (host.Equals(mapping.Host, StringComparison.InvariantCultureIgnoreCase) && new OsPath(mapping.RemotePath).Contains(remotePath)) @@ -150,8 +158,16 @@ namespace NzbDrone.Core.RemotePathMappings return localPath; } + var mappings = All(); + + if (mappings.Empty()) + { + return localPath; + } + _logger.Trace("Evaluating remote path local mappings for match to host [{0}] and local path [{1}]", host, localPath.FullPath); - foreach (var mapping in All()) + + foreach (var mapping in mappings) { _logger.Trace("Checking configured remote path mapping {0} - {1}", mapping.Host, mapping.RemotePath); if (host.Equals(mapping.Host, StringComparison.InvariantCultureIgnoreCase) && new OsPath(mapping.LocalPath).Contains(localPath)) From db15a03c1edb98fae568addcec65b8654ce8ec51 Mon Sep 17 00:00:00 2001 From: Mark McDowall <mark@mcdowall.ca> Date: Fri, 6 Oct 2023 12:27:21 -0700 Subject: [PATCH 039/136] New: Import List option to search for missing episodes Close #5882 --- .../ImportLists/EditImportListModalContent.js | 13 +++++++++++++ .../Migration/197_list_add_missing_search.cs | 14 ++++++++++++++ .../ImportLists/ImportListDefinition.cs | 1 + .../ImportLists/ImportListSyncService.cs | 2 +- src/NzbDrone.Core/Localization/Core/en.json | 2 ++ .../ImportLists/ImportListResource.cs | 3 +++ 6 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 src/NzbDrone.Core/Datastore/Migration/197_list_add_missing_search.cs diff --git a/frontend/src/Settings/ImportLists/ImportLists/EditImportListModalContent.js b/frontend/src/Settings/ImportLists/ImportLists/EditImportListModalContent.js index ec0780fff..1b19bc416 100644 --- a/frontend/src/Settings/ImportLists/ImportLists/EditImportListModalContent.js +++ b/frontend/src/Settings/ImportLists/ImportLists/EditImportListModalContent.js @@ -46,6 +46,7 @@ function EditImportListModalContent(props) { implementationName, name, enableAutomaticAdd, + searchForMissingEpisodes, minRefreshInterval, shouldMonitor, rootFolderPath, @@ -113,6 +114,18 @@ function EditImportListModalContent(props) { /> </FormGroup> + <FormGroup> + <FormLabel>{translate('ImportListSearchForMissingEpisodes')}</FormLabel> + + <FormInputGroup + type={inputTypes.CHECK} + name="searchForMissingEpisodes" + helpText={translate('EnableAutomaticAddHelpText')} + {...searchForMissingEpisodes} + onChange={onInputChange} + /> + </FormGroup> + <FormGroup> <FormLabel> {translate('Monitor')} diff --git a/src/NzbDrone.Core/Datastore/Migration/197_list_add_missing_search.cs b/src/NzbDrone.Core/Datastore/Migration/197_list_add_missing_search.cs new file mode 100644 index 000000000..3cd6e6e10 --- /dev/null +++ b/src/NzbDrone.Core/Datastore/Migration/197_list_add_missing_search.cs @@ -0,0 +1,14 @@ +using FluentMigrator; +using NzbDrone.Core.Datastore.Migration.Framework; + +namespace NzbDrone.Core.Datastore.Migration +{ + [Migration(197)] + public class list_add_missing_search : NzbDroneMigrationBase + { + protected override void MainDbUpgrade() + { + Alter.Table("ImportLists").AddColumn("SearchForMissingEpisodes").AsBoolean().NotNullable().WithDefaultValue(true); + } + } +} diff --git a/src/NzbDrone.Core/ImportLists/ImportListDefinition.cs b/src/NzbDrone.Core/ImportLists/ImportListDefinition.cs index 48fb511d9..68600dec0 100644 --- a/src/NzbDrone.Core/ImportLists/ImportListDefinition.cs +++ b/src/NzbDrone.Core/ImportLists/ImportListDefinition.cs @@ -7,6 +7,7 @@ namespace NzbDrone.Core.ImportLists public class ImportListDefinition : ProviderDefinition { public bool EnableAutomaticAdd { get; set; } + public bool SearchForMissingEpisodes { get; set; } public MonitorTypes ShouldMonitor { get; set; } public int QualityProfileId { get; set; } public SeriesTypes SeriesType { get; set; } diff --git a/src/NzbDrone.Core/ImportLists/ImportListSyncService.cs b/src/NzbDrone.Core/ImportLists/ImportListSyncService.cs index e573e10d0..4c10f79d8 100644 --- a/src/NzbDrone.Core/ImportLists/ImportListSyncService.cs +++ b/src/NzbDrone.Core/ImportLists/ImportListSyncService.cs @@ -163,7 +163,7 @@ namespace NzbDrone.Core.ImportLists Tags = importList.Tags, AddOptions = new AddSeriesOptions { - SearchForMissingEpisodes = monitored, + SearchForMissingEpisodes = importList.SearchForMissingEpisodes, Monitor = importList.ShouldMonitor } }); diff --git a/src/NzbDrone.Core/Localization/Core/en.json b/src/NzbDrone.Core/Localization/Core/en.json index e685c1585..1770a2b5a 100644 --- a/src/NzbDrone.Core/Localization/Core/en.json +++ b/src/NzbDrone.Core/Localization/Core/en.json @@ -612,6 +612,8 @@ "ImportListExclusionsLoadError": "Unable to load Import List Exclusions", "ImportListRootFolderMissingRootHealthCheckMessage": "Missing root folder for import list(s): {0}", "ImportListRootFolderMultipleMissingRootsHealthCheckMessage": "Multiple root folders are missing for import lists: {0}", + "ImportListSearchForMissingEpisodes": "Search for Missing Episodes", + "ImportListSearchForMissingEpisodesHelpText": "After series is added to {appName} automatically search for missing episodes", "ImportListSettings": "Import List Settings", "ImportListStatusAllUnavailableHealthCheckMessage": "All lists are unavailable due to failures", "ImportListStatusUnavailableHealthCheckMessage": "Lists unavailable due to failures: {0}", diff --git a/src/Sonarr.Api.V3/ImportLists/ImportListResource.cs b/src/Sonarr.Api.V3/ImportLists/ImportListResource.cs index dad382ca2..c3fa06be7 100644 --- a/src/Sonarr.Api.V3/ImportLists/ImportListResource.cs +++ b/src/Sonarr.Api.V3/ImportLists/ImportListResource.cs @@ -7,6 +7,7 @@ namespace Sonarr.Api.V3.ImportLists public class ImportListResource : ProviderResource<ImportListResource> { public bool EnableAutomaticAdd { get; set; } + public bool SearchForMissingEpisodes { get; set; } public MonitorTypes ShouldMonitor { get; set; } public string RootFolderPath { get; set; } public int QualityProfileId { get; set; } @@ -29,6 +30,7 @@ namespace Sonarr.Api.V3.ImportLists var resource = base.ToResource(definition); resource.EnableAutomaticAdd = definition.EnableAutomaticAdd; + resource.SearchForMissingEpisodes = definition.SearchForMissingEpisodes; resource.ShouldMonitor = definition.ShouldMonitor; resource.RootFolderPath = definition.RootFolderPath; resource.QualityProfileId = definition.QualityProfileId; @@ -51,6 +53,7 @@ namespace Sonarr.Api.V3.ImportLists var definition = base.ToModel(resource, existingDefinition); definition.EnableAutomaticAdd = resource.EnableAutomaticAdd; + definition.SearchForMissingEpisodes = resource.SearchForMissingEpisodes; definition.ShouldMonitor = resource.ShouldMonitor; definition.RootFolderPath = resource.RootFolderPath; definition.QualityProfileId = resource.QualityProfileId; From 113b0864b8e92b7b768acc8341bdf4c9e2e1a47f Mon Sep 17 00:00:00 2001 From: Mark McDowall <mark@mcdowall.ca> Date: Fri, 6 Oct 2023 12:38:10 -0700 Subject: [PATCH 040/136] New: Sort episodes on series details page Closes # 4558 --- .../Episode/SelectEpisodeModalContent.tsx | 9 ++---- .../src/Series/Details/SeriesDetailsSeason.js | 9 ++++++ .../Details/SeriesDetailsSeasonConnector.js | 20 ++++++++++--- frontend/src/Store/Actions/episodeActions.js | 28 +++++++++++++------ 4 files changed, 46 insertions(+), 20 deletions(-) diff --git a/frontend/src/InteractiveImport/Episode/SelectEpisodeModalContent.tsx b/frontend/src/InteractiveImport/Episode/SelectEpisodeModalContent.tsx index 598b64a70..c29e7ac56 100644 --- a/frontend/src/InteractiveImport/Episode/SelectEpisodeModalContent.tsx +++ b/frontend/src/InteractiveImport/Episode/SelectEpisodeModalContent.tsx @@ -69,8 +69,6 @@ interface SelectEpisodeModalContentProps { seasonNumber?: number; selectedDetails?: string; isAnime: boolean; - sortKey?: string; - sortDirection?: string; modalTitle: string; onEpisodesSelect(selectedEpisodes: SelectedEpisode[]): unknown; onModalClose(): unknown; @@ -86,8 +84,6 @@ function SelectEpisodeModalContent(props: SelectEpisodeModalContentProps) { seasonNumber, selectedDetails, isAnime, - sortKey, - sortDirection, modalTitle, onEpisodesSelect, onModalClose, @@ -97,9 +93,8 @@ function SelectEpisodeModalContent(props: SelectEpisodeModalContentProps) { const [selectState, setSelectState] = useSelectState(); const { allSelected, allUnselected, selectedState } = selectState; - const { isFetching, isPopulated, items, error } = useSelector( - episodesSelector() - ); + const { isFetching, isPopulated, items, error, sortKey, sortDirection } = + useSelector(episodesSelector()); const dispatch = useDispatch(); const filterEpisodeNumber = parseInt(filter); diff --git a/frontend/src/Series/Details/SeriesDetailsSeason.js b/frontend/src/Series/Details/SeriesDetailsSeason.js index 27c54a946..5605ad2d0 100644 --- a/frontend/src/Series/Details/SeriesDetailsSeason.js +++ b/frontend/src/Series/Details/SeriesDetailsSeason.js @@ -210,12 +210,15 @@ class SeriesDetailsSeason extends Component { seasonNumber, items, columns, + sortKey, + sortDirection, statistics, isSaving, isExpanded, isSearching, seriesMonitored, isSmallScreen, + onSortPress, onTableOptionChange, onMonitorSeasonPress, onSearchPress @@ -447,6 +450,9 @@ class SeriesDetailsSeason extends Component { items.length ? <Table columns={columns} + sortKey={sortKey} + sortDirection={sortDirection} + onSortPress={onSortPress} onTableOptionChange={onTableOptionChange} > <TableBody> @@ -530,6 +536,8 @@ SeriesDetailsSeason.propTypes = { seasonNumber: PropTypes.number.isRequired, items: PropTypes.arrayOf(PropTypes.object).isRequired, columns: PropTypes.arrayOf(PropTypes.object).isRequired, + sortKey: PropTypes.string.isRequired, + sortDirection: PropTypes.oneOf(sortDirections.all), statistics: PropTypes.object.isRequired, isSaving: PropTypes.bool, isExpanded: PropTypes.bool, @@ -537,6 +545,7 @@ SeriesDetailsSeason.propTypes = { seriesMonitored: PropTypes.bool.isRequired, isSmallScreen: PropTypes.bool.isRequired, onTableOptionChange: PropTypes.func.isRequired, + onSortPress: PropTypes.func.isRequired, onMonitorSeasonPress: PropTypes.func.isRequired, onExpandPress: PropTypes.func.isRequired, onMonitorEpisodePress: PropTypes.func.isRequired, diff --git a/frontend/src/Series/Details/SeriesDetailsSeasonConnector.js b/frontend/src/Series/Details/SeriesDetailsSeasonConnector.js index cdf3b30ea..2fa409765 100644 --- a/frontend/src/Series/Details/SeriesDetailsSeasonConnector.js +++ b/frontend/src/Series/Details/SeriesDetailsSeasonConnector.js @@ -4,8 +4,9 @@ import { connect } from 'react-redux'; import { createSelector } from 'reselect'; import * as commandNames from 'Commands/commandNames'; import { executeCommand } from 'Store/Actions/commandActions'; -import { setEpisodesTableOption, toggleEpisodesMonitored } from 'Store/Actions/episodeActions'; +import { setEpisodesSort, setEpisodesTableOption, toggleEpisodesMonitored } from 'Store/Actions/episodeActions'; import { toggleSeasonMonitored } from 'Store/Actions/seriesActions'; +import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector'; import createCommandsSelector from 'Store/Selectors/createCommandsSelector'; import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector'; import createSeriesSelector from 'Store/Selectors/createSeriesSelector'; @@ -15,7 +16,7 @@ import SeriesDetailsSeason from './SeriesDetailsSeason'; function createMapStateToProps() { return createSelector( (state, { seasonNumber }) => seasonNumber, - (state) => state.episodes, + createClientSideCollectionSelector('episodes'), createSeriesSelector(), createCommandsSelector(), createDimensionsSelector(), @@ -27,11 +28,12 @@ function createMapStateToProps() { })); const episodesInSeason = episodes.items.filter((episode) => episode.seasonNumber === seasonNumber); - const sortedEpisodes = episodesInSeason.sort((a, b) => b.episodeNumber - a.episodeNumber); return { - items: sortedEpisodes, + items: episodesInSeason, columns: episodes.columns, + sortKey: episodes.sortKey, + sortDirection: episodes.sortDirection, isSearching, seriesMonitored: series.monitored, path: series.path, @@ -45,6 +47,7 @@ const mapDispatchToProps = { toggleSeasonMonitored, toggleEpisodesMonitored, setEpisodesTableOption, + setEpisodesSort, executeCommand }; @@ -90,6 +93,13 @@ class SeriesDetailsSeasonConnector extends Component { }); }; + onSortPress = (sortKey, sortDirection) => { + this.props.setEpisodesSort({ + sortKey, + sortDirection + }); + }; + // // Render @@ -98,6 +108,7 @@ class SeriesDetailsSeasonConnector extends Component { <SeriesDetailsSeason {...this.props} onTableOptionChange={this.onTableOptionChange} + onSortPress={this.onSortPress} onMonitorSeasonPress={this.onMonitorSeasonPress} onSearchPress={this.onSearchPress} onMonitorEpisodePress={this.onMonitorEpisodePress} @@ -112,6 +123,7 @@ SeriesDetailsSeasonConnector.propTypes = { toggleSeasonMonitored: PropTypes.func.isRequired, toggleEpisodesMonitored: PropTypes.func.isRequired, setEpisodesTableOption: PropTypes.func.isRequired, + setEpisodesSort: PropTypes.func.isRequired, executeCommand: PropTypes.func.isRequired }; diff --git a/frontend/src/Store/Actions/episodeActions.js b/frontend/src/Store/Actions/episodeActions.js index 628703752..a769913a0 100644 --- a/frontend/src/Store/Actions/episodeActions.js +++ b/frontend/src/Store/Actions/episodeActions.js @@ -40,32 +40,38 @@ export const defaultState = { { name: 'episodeNumber', label: '#', - isVisible: true + isVisible: true, + isSortable: true }, { name: 'title', label: () => translate('Title'), - isVisible: true + isVisible: true, + isSortable: true }, { name: 'path', label: () => translate('Path'), - isVisible: false + isVisible: false, + isSortable: true }, { name: 'relativePath', label: () => translate('RelativePath'), - isVisible: false + isVisible: false, + isSortable: true }, { name: 'airDateUtc', label: () => translate('AirDate'), - isVisible: true + isVisible: true, + isSortable: true }, { name: 'runtime', label: () => translate('Runtime'), - isVisible: false + isVisible: false, + isSortable: true }, { name: 'languages', @@ -100,7 +106,8 @@ export const defaultState = { { name: 'size', label: () => translate('Size'), - isVisible: false + isVisible: false, + isSortable: true }, { name: 'releaseGroup', @@ -119,7 +126,8 @@ export const defaultState = { name: icons.SCORE, title: () => translate('CustomFormatScore') }), - isVisible: false + isVisible: false, + isSortable: true }, { name: 'status', @@ -136,7 +144,9 @@ export const defaultState = { }; export const persistState = [ - 'episodes.columns' + 'episodes.columns', + 'episodes.sortDirection', + 'episodes.sortKey' ]; // From 87e0a7983a437a4d166aa8b9c9eaf78ea5431969 Mon Sep 17 00:00:00 2001 From: Mark McDowall <mark@mcdowall.ca> Date: Fri, 6 Oct 2023 13:10:46 -0700 Subject: [PATCH 041/136] New: Download client option for redownloading failed releases from Interactive Search Closes #5580 --- frontend/src/Components/Form/FormLabel.css | 4 +++- .../Options/DownloadClientOptions.js | 22 ++++++++++++++++++- .../Configuration/ConfigService.cs | 7 ++++++ .../Configuration/IConfigService.cs | 1 + .../Download/DownloadFailedEvent.cs | 2 ++ .../Download/FailedDownloadService.cs | 6 ++++- .../RedownloadFailedDownloadService.cs | 7 ++++++ src/NzbDrone.Core/Localization/Core/en.json | 4 +++- .../Config/DownloadClientConfigResource.cs | 6 +++-- 9 files changed, 53 insertions(+), 6 deletions(-) diff --git a/frontend/src/Components/Form/FormLabel.css b/frontend/src/Components/Form/FormLabel.css index 074b6091d..54a4678e8 100644 --- a/frontend/src/Components/Form/FormLabel.css +++ b/frontend/src/Components/Form/FormLabel.css @@ -2,8 +2,10 @@ display: flex; justify-content: flex-end; margin-right: $formLabelRightMarginWidth; + padding-top: 8px; + min-height: 35px; + text-align: end; font-weight: bold; - line-height: 35px; } .hasError { diff --git a/frontend/src/Settings/DownloadClients/Options/DownloadClientOptions.js b/frontend/src/Settings/DownloadClients/Options/DownloadClientOptions.js index 7de4febfd..5d599a8f4 100644 --- a/frontend/src/Settings/DownloadClients/Options/DownloadClientOptions.js +++ b/frontend/src/Settings/DownloadClients/Options/DownloadClientOptions.js @@ -61,7 +61,7 @@ function DownloadClientOptions(props) { isAdvanced={true} size={sizes.MEDIUM} > - <FormLabel>{translate('RedownloadFailed')}</FormLabel> + <FormLabel>{translate('AutoRedownloadFailed')}</FormLabel> <FormInputGroup type={inputTypes.CHECK} @@ -71,6 +71,26 @@ function DownloadClientOptions(props) { {...settings.autoRedownloadFailed} /> </FormGroup> + + { + settings.autoRedownloadFailed.value ? + <FormGroup + advancedSettings={advancedSettings} + isAdvanced={true} + size={sizes.MEDIUM} + > + <FormLabel>{translate('AutoRedownloadFailedFromInteractiveSearch')}</FormLabel> + + <FormInputGroup + type={inputTypes.CHECK} + name="autoRedownloadFailedFromInteractiveSearch" + helpText={translate('AutoRedownloadFailedFromInteractiveSearchHelpText')} + onChange={onInputChange} + {...settings.autoRedownloadFailedFromInteractiveSearch} + /> + </FormGroup> : + null + } </Form> <Alert kind={kinds.INFO}> diff --git a/src/NzbDrone.Core/Configuration/ConfigService.cs b/src/NzbDrone.Core/Configuration/ConfigService.cs index 85186e91b..2193b182b 100644 --- a/src/NzbDrone.Core/Configuration/ConfigService.cs +++ b/src/NzbDrone.Core/Configuration/ConfigService.cs @@ -144,6 +144,13 @@ namespace NzbDrone.Core.Configuration set { SetValue("AutoRedownloadFailed", value); } } + public bool AutoRedownloadFailedFromInteractiveSearch + { + get { return GetValueBoolean("AutoRedownloadFailedFromInteractiveSearch", true); } + + set { SetValue("AutoRedownloadFailedFromInteractiveSearch", value); } + } + public bool CreateEmptySeriesFolders { get { return GetValueBoolean("CreateEmptySeriesFolders", false); } diff --git a/src/NzbDrone.Core/Configuration/IConfigService.cs b/src/NzbDrone.Core/Configuration/IConfigService.cs index 2ebc262ac..2bcd7b923 100644 --- a/src/NzbDrone.Core/Configuration/IConfigService.cs +++ b/src/NzbDrone.Core/Configuration/IConfigService.cs @@ -20,6 +20,7 @@ namespace NzbDrone.Core.Configuration // Completed/Failed Download Handling (Download client) bool EnableCompletedDownloadHandling { get; set; } bool AutoRedownloadFailed { get; set; } + bool AutoRedownloadFailedFromInteractiveSearch { get; set; } // Media Management bool AutoUnmonitorPreviouslyDownloadedEpisodes { get; set; } diff --git a/src/NzbDrone.Core/Download/DownloadFailedEvent.cs b/src/NzbDrone.Core/Download/DownloadFailedEvent.cs index 757e5da97..7a42b7e18 100644 --- a/src/NzbDrone.Core/Download/DownloadFailedEvent.cs +++ b/src/NzbDrone.Core/Download/DownloadFailedEvent.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using NzbDrone.Common.Messaging; using NzbDrone.Core.Download.TrackedDownloads; using NzbDrone.Core.Languages; +using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Qualities; namespace NzbDrone.Core.Download @@ -24,5 +25,6 @@ namespace NzbDrone.Core.Download public TrackedDownload TrackedDownload { get; set; } public List<Language> Languages { get; set; } public bool SkipRedownload { get; set; } + public ReleaseSourceType ReleaseSource { get; set; } } } diff --git a/src/NzbDrone.Core/Download/FailedDownloadService.cs b/src/NzbDrone.Core/Download/FailedDownloadService.cs index 8e07b23fd..f7687ab41 100644 --- a/src/NzbDrone.Core/Download/FailedDownloadService.cs +++ b/src/NzbDrone.Core/Download/FailedDownloadService.cs @@ -1,9 +1,11 @@ +using System; using System.Collections.Generic; using System.Linq; using NzbDrone.Common.Extensions; using NzbDrone.Core.Download.TrackedDownloads; using NzbDrone.Core.History; using NzbDrone.Core.Messaging.Events; +using NzbDrone.Core.Parser.Model; namespace NzbDrone.Core.Download { @@ -128,6 +130,7 @@ namespace NzbDrone.Core.Download private void PublishDownloadFailedEvent(List<EpisodeHistory> historyItems, string message, TrackedDownload trackedDownload = null, bool skipRedownload = false) { var historyItem = historyItems.First(); + Enum.TryParse(historyItem.Data.GetValueOrDefault(EpisodeHistory.RELEASE_SOURCE, ReleaseSourceType.Unknown.ToString()), out ReleaseSourceType releaseSource); var downloadFailedEvent = new DownloadFailedEvent { @@ -141,7 +144,8 @@ namespace NzbDrone.Core.Download Data = historyItem.Data, TrackedDownload = trackedDownload, Languages = historyItem.Languages, - SkipRedownload = skipRedownload + SkipRedownload = skipRedownload, + ReleaseSource = releaseSource }; _eventAggregator.PublishEvent(downloadFailedEvent); diff --git a/src/NzbDrone.Core/Download/RedownloadFailedDownloadService.cs b/src/NzbDrone.Core/Download/RedownloadFailedDownloadService.cs index 20c19e15d..4a431de6c 100644 --- a/src/NzbDrone.Core/Download/RedownloadFailedDownloadService.cs +++ b/src/NzbDrone.Core/Download/RedownloadFailedDownloadService.cs @@ -5,6 +5,7 @@ using NzbDrone.Core.IndexerSearch; using NzbDrone.Core.Messaging; using NzbDrone.Core.Messaging.Commands; using NzbDrone.Core.Messaging.Events; +using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Tv; namespace NzbDrone.Core.Download @@ -42,6 +43,12 @@ namespace NzbDrone.Core.Download return; } + if (message.ReleaseSource == ReleaseSourceType.InteractiveSearch && !_configService.AutoRedownloadFailedFromInteractiveSearch) + { + _logger.Debug("Auto redownloading failed episodes from interactive search is disabled"); + return; + } + if (message.EpisodeIds.Count == 1) { _logger.Debug("Failed download only contains one episode, searching again"); diff --git a/src/NzbDrone.Core/Localization/Core/en.json b/src/NzbDrone.Core/Localization/Core/en.json index 1770a2b5a..9601f4dd1 100644 --- a/src/NzbDrone.Core/Localization/Core/en.json +++ b/src/NzbDrone.Core/Localization/Core/en.json @@ -117,6 +117,9 @@ "AuthenticationRequiredUsernameHelpTextWarning": "Enter a new username", "AuthenticationRequiredWarning": "To prevent remote access without authentication, {appName} now requires authentication to be enabled. You can optionally disable authentication from local addresses.", "AutoAdd": "Auto Add", + "AutoRedownloadFailed": "Redownload Failed", + "AutoRedownloadFailedFromInteractiveSearch": "Redownload Failed from Interactive Search", + "AutoRedownloadFailedFromInteractiveSearchHelpText": "Automatically search for and attempt to download a different release when failed release was grabbed from interactive search", "AutoRedownloadFailedHelpText": "Automatically search for and attempt to download a different release", "AutoTagging": "Auto Tagging", "AutoTaggingLoadError": "Unable to load auto tagging", @@ -1021,7 +1024,6 @@ "RecyclingBinCleanupHelpText": "Set to 0 to disable automatic cleanup", "RecyclingBinCleanupHelpTextWarning": "Files in the recycle bin older than the selected number of days will be cleaned up automatically", "RecyclingBinHelpText": "Episode files will go here when deleted instead of being permanently deleted", - "RedownloadFailed": "Redownload Failed", "Refresh": "Refresh", "RefreshAndScan": "Refresh & Scan", "RefreshAndScanTooltip": "Refresh information and scan disk", diff --git a/src/Sonarr.Api.V3/Config/DownloadClientConfigResource.cs b/src/Sonarr.Api.V3/Config/DownloadClientConfigResource.cs index 17c98e660..eb7d76152 100644 --- a/src/Sonarr.Api.V3/Config/DownloadClientConfigResource.cs +++ b/src/Sonarr.Api.V3/Config/DownloadClientConfigResource.cs @@ -1,4 +1,4 @@ -using NzbDrone.Core.Configuration; +using NzbDrone.Core.Configuration; using Sonarr.Http.REST; namespace Sonarr.Api.V3.Config @@ -9,6 +9,7 @@ namespace Sonarr.Api.V3.Config public bool EnableCompletedDownloadHandling { get; set; } public bool AutoRedownloadFailed { get; set; } + public bool AutoRedownloadFailedFromInteractiveSearch { get; set; } } public static class DownloadClientConfigResourceMapper @@ -20,7 +21,8 @@ namespace Sonarr.Api.V3.Config DownloadClientWorkingFolders = model.DownloadClientWorkingFolders, EnableCompletedDownloadHandling = model.EnableCompletedDownloadHandling, - AutoRedownloadFailed = model.AutoRedownloadFailed + AutoRedownloadFailed = model.AutoRedownloadFailed, + AutoRedownloadFailedFromInteractiveSearch = model.AutoRedownloadFailedFromInteractiveSearch }; } } From 4b9baddccd9d784ad75de6eb9b1cc7d3910a8a3a Mon Sep 17 00:00:00 2001 From: Mark McDowall <mark@mcdowall.ca> Date: Fri, 6 Oct 2023 13:26:25 -0700 Subject: [PATCH 042/136] Fixed: Only use monitored episodes for previous/next airing Closes #6068 --- src/NzbDrone.Core/SeriesStats/SeriesStatisticsRepository.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/NzbDrone.Core/SeriesStats/SeriesStatisticsRepository.cs b/src/NzbDrone.Core/SeriesStats/SeriesStatisticsRepository.cs index d95828776..ae0dd8fe4 100644 --- a/src/NzbDrone.Core/SeriesStats/SeriesStatisticsRepository.cs +++ b/src/NzbDrone.Core/SeriesStats/SeriesStatisticsRepository.cs @@ -79,8 +79,8 @@ namespace NzbDrone.Core.SeriesStats SUM(CASE WHEN ""AirDateUtc"" <= @currentDate OR ""EpisodeFileId"" > 0 THEN 1 ELSE 0 END) AS AvailableEpisodeCount, SUM(CASE WHEN (""Monitored"" = {trueIndicator} AND ""AirDateUtc"" <= @currentDate) OR ""EpisodeFileId"" > 0 THEN 1 ELSE 0 END) AS EpisodeCount, SUM(CASE WHEN ""EpisodeFileId"" > 0 THEN 1 ELSE 0 END) AS EpisodeFileCount, - MIN(CASE WHEN ""AirDateUtc"" < @currentDate OR ""EpisodeFileId"" > 0 OR ""Monitored"" = {falseIndicator} THEN NULL ELSE ""AirDateUtc"" END) AS NextAiringString, - MAX(CASE WHEN ""AirDateUtc"" >= @currentDate OR ""EpisodeFileId"" = 0 AND ""Monitored"" = {falseIndicator} THEN NULL ELSE ""AirDateUtc"" END) AS PreviousAiringString", parameters) + MIN(CASE WHEN ""AirDateUtc"" < @currentDate OR ""Monitored"" = {falseIndicator} THEN NULL ELSE ""AirDateUtc"" END) AS NextAiringString, + MAX(CASE WHEN ""AirDateUtc"" >= @currentDate OR ""Monitored"" = {falseIndicator} THEN NULL ELSE ""AirDateUtc"" END) AS PreviousAiringString", parameters) .GroupBy<Episode>(x => x.SeriesId) .GroupBy<Episode>(x => x.SeasonNumber); } From 78b39bd2fecda60e04a1fef17ae17f62bd2b6914 Mon Sep 17 00:00:00 2001 From: Mark McDowall <mark@mcdowall.ca> Date: Mon, 9 Oct 2023 21:00:05 -0700 Subject: [PATCH 043/136] Log executing health check Towards #6076 --- .../HealthCheck/HealthCheckService.cs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/NzbDrone.Core/HealthCheck/HealthCheckService.cs b/src/NzbDrone.Core/HealthCheck/HealthCheckService.cs index 31aa9c199..ef906ee99 100644 --- a/src/NzbDrone.Core/HealthCheck/HealthCheckService.cs +++ b/src/NzbDrone.Core/HealthCheck/HealthCheckService.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using NLog; using NzbDrone.Common.Cache; using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.Messaging; @@ -28,6 +29,7 @@ namespace NzbDrone.Core.HealthCheck private readonly IProvideHealthCheck[] _scheduledHealthChecks; private readonly Dictionary<Type, IEventDrivenHealthCheck[]> _eventDrivenHealthChecks; private readonly IEventAggregator _eventAggregator; + private readonly Logger _logger; private readonly ICached<HealthCheck> _healthCheckResults; private readonly HashSet<IProvideHealthCheck> _pendingHealthChecks; @@ -40,10 +42,12 @@ namespace NzbDrone.Core.HealthCheck IEventAggregator eventAggregator, ICacheManager cacheManager, IDebounceManager debounceManager, - IRuntimeInfo runtimeInfo) + IRuntimeInfo runtimeInfo, + Logger logger) { _healthChecks = healthChecks.ToArray(); _eventAggregator = eventAggregator; + _logger = logger; _healthCheckResults = cacheManager.GetCache<HealthCheck>(GetType()); _pendingHealthChecks = new HashSet<IProvideHealthCheck>(); @@ -88,7 +92,14 @@ namespace NzbDrone.Core.HealthCheck try { - var results = healthChecks.Select(c => c.Check()) + var results = healthChecks.Select(c => + { + _logger.Trace("Check health -> {0}", c.GetType().Name); + var result = c.Check(); + _logger.Trace("Check health <- {0}", c.GetType().Name); + + return result; + }) .ToList(); foreach (var result in results) From 95b389a94863d5791d77b20e4018b4a55382ea89 Mon Sep 17 00:00:00 2001 From: Mark McDowall <mark@mcdowall.ca> Date: Mon, 9 Oct 2023 22:13:00 -0700 Subject: [PATCH 044/136] Fix previous airing test --- .../SeriesStatsTests/SeriesStatisticsFixture.cs | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/NzbDrone.Core.Test/SeriesStatsTests/SeriesStatisticsFixture.cs b/src/NzbDrone.Core.Test/SeriesStatsTests/SeriesStatisticsFixture.cs index 772b084ae..f6d386364 100644 --- a/src/NzbDrone.Core.Test/SeriesStatsTests/SeriesStatisticsFixture.cs +++ b/src/NzbDrone.Core.Test/SeriesStatsTests/SeriesStatisticsFixture.cs @@ -94,20 +94,6 @@ namespace NzbDrone.Core.Test.SeriesStatsTests stats.First().NextAiring.Should().NotHaveValue(); } - [Test] - public void should_have_previous_airing_for_old_episode_with_file() - { - GivenEpisodeWithFile(); - GivenOldEpisode(); - GivenEpisode(); - - var stats = Subject.SeriesStatistics(); - - stats.Should().HaveCount(1); - stats.First().NextAiring.Should().NotHaveValue(); - stats.First().PreviousAiring.Should().BeCloseTo(_episode.AirDateUtc.Value, TimeSpan.FromMilliseconds(1000)); - } - [Test] public void should_have_previous_airing_for_old_episode_without_file_monitored() { From 5bceacb30eb7c69e41e296f0637224d52d3f46f3 Mon Sep 17 00:00:00 2001 From: Sonarr <development@sonarr.tv> Date: Tue, 10 Oct 2023 03:44:58 +0000 Subject: [PATCH 045/136] Automated API Docs update ignore-downstream --- src/Sonarr.Api.V3/openapi.json | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/Sonarr.Api.V3/openapi.json b/src/Sonarr.Api.V3/openapi.json index 462f08b69..a154c2b26 100644 --- a/src/Sonarr.Api.V3/openapi.json +++ b/src/Sonarr.Api.V3/openapi.json @@ -7637,7 +7637,7 @@ "type": "array", "items": { "type": "object", - "additionalProperties": {} + "additionalProperties": { } }, "nullable": true } @@ -7856,6 +7856,9 @@ }, "autoRedownloadFailed": { "type": "boolean" + }, + "autoRedownloadFailedFromInteractiveSearch": { + "type": "boolean" } }, "additionalProperties": false @@ -8731,6 +8734,9 @@ "enableAutomaticAdd": { "type": "boolean" }, + "searchForMissingEpisodes": { + "type": "boolean" + }, "shouldMonitor": { "$ref": "#/components/schemas/MonitorTypes" }, @@ -11821,10 +11827,10 @@ }, "security": [ { - "X-Api-Key": [] + "X-Api-Key": [ ] }, { - "apikey": [] + "apikey": [ ] } ] } \ No newline at end of file From 3ade52fc904672e9f934e6a11846f3bf9058267d Mon Sep 17 00:00:00 2001 From: Bogdan <mynameisbogdan@users.noreply.github.com> Date: Tue, 10 Oct 2023 16:56:35 +0300 Subject: [PATCH 046/136] Fixed: Wanted Missing showing Unmonitored episodes Closes #6084 --- src/Sonarr.Api.V3/Wanted/CutoffController.cs | 9 ++++++++- src/Sonarr.Api.V3/Wanted/MissingController.cs | 9 ++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/Sonarr.Api.V3/Wanted/CutoffController.cs b/src/Sonarr.Api.V3/Wanted/CutoffController.cs index 479212ec7..22be80366 100644 --- a/src/Sonarr.Api.V3/Wanted/CutoffController.cs +++ b/src/Sonarr.Api.V3/Wanted/CutoffController.cs @@ -39,7 +39,14 @@ namespace Sonarr.Api.V3.Wanted SortDirection = pagingResource.SortDirection }; - pagingSpec.FilterExpressions.Add(v => v.Monitored == monitored || v.Series.Monitored == monitored); + if (monitored) + { + pagingSpec.FilterExpressions.Add(v => v.Monitored == true && v.Series.Monitored == true); + } + else + { + pagingSpec.FilterExpressions.Add(v => v.Monitored == false || v.Series.Monitored == false); + } var resource = pagingSpec.ApplyToPage(_episodeCutoffService.EpisodesWhereCutoffUnmet, v => MapToResource(v, includeSeries, includeEpisodeFile, includeImages)); diff --git a/src/Sonarr.Api.V3/Wanted/MissingController.cs b/src/Sonarr.Api.V3/Wanted/MissingController.cs index 43832c0d2..f7444f7a3 100644 --- a/src/Sonarr.Api.V3/Wanted/MissingController.cs +++ b/src/Sonarr.Api.V3/Wanted/MissingController.cs @@ -35,7 +35,14 @@ namespace Sonarr.Api.V3.Wanted SortDirection = pagingResource.SortDirection }; - pagingSpec.FilterExpressions.Add(v => v.Monitored == monitored || v.Series.Monitored == monitored); + if (monitored) + { + pagingSpec.FilterExpressions.Add(v => v.Monitored == true && v.Series.Monitored == true); + } + else + { + pagingSpec.FilterExpressions.Add(v => v.Monitored == false || v.Series.Monitored == false); + } var resource = pagingSpec.ApplyToPage(_episodeService.EpisodesWithoutFiles, v => MapToResource(v, includeSeries, false, includeImages)); From 81aaf00a4cd2b3a2f8ddf67226c13bb51ea39dda Mon Sep 17 00:00:00 2001 From: Stevie Robinson <stevie.robinson@gmail.com> Date: Tue, 10 Oct 2023 15:58:03 +0200 Subject: [PATCH 047/136] New: Add additional CleanTitle tokens and re-order options Closes #6066 --- .../MediaManagement/Naming/NamingModal.js | 9 +- .../CleanTitleTheFixture.cs | 86 +++++++++++++++++++ .../CleanTitleTheWithoutYearFixture.cs | 79 +++++++++++++++++ .../CleanTitleTheYearFixture.cs | 81 +++++++++++++++++ .../Organizer/FileNameBuilder.cs | 37 +++++++- 5 files changed, 287 insertions(+), 5 deletions(-) create mode 100644 src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/CleanTitleTheFixture.cs create mode 100644 src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/CleanTitleTheWithoutYearFixture.cs create mode 100644 src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/CleanTitleTheYearFixture.cs diff --git a/frontend/src/Settings/MediaManagement/Naming/NamingModal.js b/frontend/src/Settings/MediaManagement/Naming/NamingModal.js index 60d6a8854..509c5d940 100644 --- a/frontend/src/Settings/MediaManagement/Naming/NamingModal.js +++ b/frontend/src/Settings/MediaManagement/Naming/NamingModal.js @@ -82,13 +82,16 @@ const fileNameTokens = [ const seriesTokens = [ { token: '{Series Title}', example: 'The Series Title\'s!' }, { token: '{Series CleanTitle}', example: 'The Series Title\'s!' }, - { token: '{Series CleanTitleYear}', example: 'The Series Titles! 2010' }, + { token: '{Series TitleYear}', example: 'The Series Title\'s! (2010)' }, + { token: '{Series CleanTitleYear}', example: 'The Series Title\'s! 2010' }, + { token: '{Series TitleWithoutYear}', example: 'The Series Title\'s!' }, { token: '{Series CleanTitleWithoutYear}', example: 'The Series Title\'s!' }, { token: '{Series TitleThe}', example: 'Series Title\'s!, The' }, + { token: '{Series CleanTitleThe}', example: 'Series Title\'s!, The' }, { token: '{Series TitleTheYear}', example: 'Series Title\'s!, The (2010)' }, + { token: '{Series CleanTitleTheYear}', example: 'Series Title\'s!, The 2010' }, { token: '{Series TitleTheWithoutYear}', example: 'Series Title\'s!, The' }, - { token: '{Series TitleYear}', example: 'The Series Title\'s! (2010)' }, - { token: '{Series TitleWithoutYear}', example: 'Series Title\'s!' }, + { token: '{Series CleanTitleTheWithoutYear}', example: 'Series Title\'s!, The' }, { token: '{Series TitleFirstCharacter}', example: 'S' }, { token: '{Series Year}', example: '2010' } ]; diff --git a/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/CleanTitleTheFixture.cs b/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/CleanTitleTheFixture.cs new file mode 100644 index 000000000..6b738f250 --- /dev/null +++ b/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/CleanTitleTheFixture.cs @@ -0,0 +1,86 @@ +using System.Collections.Generic; +using System.Linq; +using FizzWare.NBuilder; +using FluentAssertions; +using NUnit.Framework; +using NzbDrone.Core.CustomFormats; +using NzbDrone.Core.MediaFiles; +using NzbDrone.Core.Organizer; +using NzbDrone.Core.Qualities; +using NzbDrone.Core.Test.Framework; +using NzbDrone.Core.Tv; + +namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests +{ + [TestFixture] + public class CleanTitleTheFixture : CoreTest<FileNameBuilder> + { + private Series _series; + private Episode _episode; + private EpisodeFile _episodeFile; + private NamingConfig _namingConfig; + + [SetUp] + public void Setup() + { + _series = Builder<Series> + .CreateNew() + .Build(); + + _episode = Builder<Episode>.CreateNew() + .With(e => e.Title = "City Sushi") + .With(e => e.SeasonNumber = 15) + .With(e => e.EpisodeNumber = 6) + .With(e => e.AbsoluteEpisodeNumber = 100) + .Build(); + + _episodeFile = new EpisodeFile { Quality = new QualityModel(Quality.HDTV720p), ReleaseGroup = "SonarrTest" }; + + _namingConfig = NamingConfig.Default; + _namingConfig.RenameEpisodes = true; + + Mocker.GetMock<INamingConfigService>() + .Setup(c => c.GetConfig()).Returns(_namingConfig); + + Mocker.GetMock<IQualityDefinitionService>() + .Setup(v => v.Get(Moq.It.IsAny<Quality>())) + .Returns<Quality>(v => Quality.DefaultQualityDefinitions.First(c => c.Quality == v)); + + Mocker.GetMock<ICustomFormatService>() + .Setup(v => v.All()) + .Returns(new List<CustomFormat>()); + } + + [TestCase("The Mist", "Mist, The")] + [TestCase("A Place to Call Home", "Place to Call Home, A")] + [TestCase("An Adventure in Space and Time", "Adventure in Space and Time, An")] + [TestCase("The Flash (2010)", "Flash, The 2010")] + [TestCase("A League Of Their Own (AU)", "League Of Their Own, A AU")] + [TestCase("The Fixer (ZH) (2015)", "Fixer, The ZH 2015")] + [TestCase("The Sixth Sense 2 (Thai)", "Sixth Sense 2, The Thai")] + [TestCase("The Amazing Race (Latin America)", "Amazing Race, The Latin America")] + [TestCase("The Rat Pack (A&E)", "Rat Pack, The AandE")] + [TestCase("The Climax: I (Almost) Got Away With It (2016)", "Climax I Almost Got Away With It, The 2016")] + public void should_get_expected_title_back(string title, string expected) + { + _series.Title = title; + _namingConfig.StandardEpisodeFormat = "{Series CleanTitleThe}"; + + Subject.BuildFileName(new List<Episode> { _episode }, _series, _episodeFile) + .Should().Be(expected); + } + + [TestCase("A")] + [TestCase("Anne")] + [TestCase("Theodore")] + [TestCase("3%")] + public void should_not_change_title(string title) + { + _series.Title = title; + _namingConfig.StandardEpisodeFormat = "{Series CleanTitleThe}"; + + Subject.BuildFileName(new List<Episode> { _episode }, _series, _episodeFile) + .Should().Be(title); + } + } +} diff --git a/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/CleanTitleTheWithoutYearFixture.cs b/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/CleanTitleTheWithoutYearFixture.cs new file mode 100644 index 000000000..104f51867 --- /dev/null +++ b/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/CleanTitleTheWithoutYearFixture.cs @@ -0,0 +1,79 @@ +using System.Collections.Generic; +using System.Linq; +using FizzWare.NBuilder; +using FluentAssertions; +using NUnit.Framework; +using NzbDrone.Core.CustomFormats; +using NzbDrone.Core.MediaFiles; +using NzbDrone.Core.Organizer; +using NzbDrone.Core.Qualities; +using NzbDrone.Core.Test.Framework; +using NzbDrone.Core.Tv; + +namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests +{ + [TestFixture] + public class CleanTitleTheWithoutYearFixture : CoreTest<FileNameBuilder> + { + private Series _series; + private Episode _episode; + private EpisodeFile _episodeFile; + private NamingConfig _namingConfig; + + [SetUp] + public void Setup() + { + _series = Builder<Series> + .CreateNew() + .Build(); + + _episode = Builder<Episode>.CreateNew() + .With(e => e.Title = "City Sushi") + .With(e => e.SeasonNumber = 15) + .With(e => e.EpisodeNumber = 6) + .With(e => e.AbsoluteEpisodeNumber = 100) + .Build(); + + _episodeFile = new EpisodeFile { Quality = new QualityModel(Quality.HDTV720p), ReleaseGroup = "SonarrTest" }; + + _namingConfig = NamingConfig.Default; + _namingConfig.RenameEpisodes = true; + + Mocker.GetMock<INamingConfigService>() + .Setup(c => c.GetConfig()).Returns(_namingConfig); + + Mocker.GetMock<IQualityDefinitionService>() + .Setup(v => v.Get(Moq.It.IsAny<Quality>())) + .Returns<Quality>(v => Quality.DefaultQualityDefinitions.First(c => c.Quality == v)); + + Mocker.GetMock<ICustomFormatService>() + .Setup(v => v.All()) + .Returns(new List<CustomFormat>()); + } + + [TestCase("The Mist", 2018, "Mist, The")] + [TestCase("The Rat Pack (A&E)", 1999, "Rat Pack, The AandE")] + [TestCase("The Climax: I (Almost) Got Away With It (2016)", 2016, "Climax I Almost Got Away With It, The")] + [TestCase("A", 2017, "A")] + public void should_get_expected_title_back(string title, int year, string expected) + { + _series.Title = title; + _series.Year = year; + _namingConfig.StandardEpisodeFormat = "{Series CleanTitleTheWithoutYear}"; + + Subject.BuildFileName(new List<Episode> { _episode }, _series, _episodeFile) + .Should().Be(expected); + } + + [Test] + public void should_not_include_0_for_year() + { + _series.Title = "The Alienist"; + _series.Year = 0; + _namingConfig.StandardEpisodeFormat = "{Series CleanTitleTheWithoutYear}"; + + Subject.BuildFileName(new List<Episode> { _episode }, _series, _episodeFile) + .Should().Be("Alienist, The"); + } + } +} diff --git a/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/CleanTitleTheYearFixture.cs b/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/CleanTitleTheYearFixture.cs new file mode 100644 index 000000000..bc06d4698 --- /dev/null +++ b/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/CleanTitleTheYearFixture.cs @@ -0,0 +1,81 @@ +using System.Collections.Generic; +using System.Linq; +using FizzWare.NBuilder; +using FluentAssertions; +using NUnit.Framework; +using NzbDrone.Core.CustomFormats; +using NzbDrone.Core.MediaFiles; +using NzbDrone.Core.Organizer; +using NzbDrone.Core.Qualities; +using NzbDrone.Core.Test.Framework; +using NzbDrone.Core.Tv; + +namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests +{ + [TestFixture] + public class CleanTitleTheYearFixture : CoreTest<FileNameBuilder> + { + private Series _series; + private Episode _episode; + private EpisodeFile _episodeFile; + private NamingConfig _namingConfig; + + [SetUp] + public void Setup() + { + _series = Builder<Series> + .CreateNew() + .Build(); + + _episode = Builder<Episode>.CreateNew() + .With(e => e.Title = "City Sushi") + .With(e => e.SeasonNumber = 15) + .With(e => e.EpisodeNumber = 6) + .With(e => e.AbsoluteEpisodeNumber = 100) + .Build(); + + _episodeFile = new EpisodeFile { Quality = new QualityModel(Quality.HDTV720p), ReleaseGroup = "SonarrTest" }; + + _namingConfig = NamingConfig.Default; + _namingConfig.RenameEpisodes = true; + + Mocker.GetMock<INamingConfigService>() + .Setup(c => c.GetConfig()).Returns(_namingConfig); + + Mocker.GetMock<IQualityDefinitionService>() + .Setup(v => v.Get(Moq.It.IsAny<Quality>())) + .Returns<Quality>(v => Quality.DefaultQualityDefinitions.First(c => c.Quality == v)); + + Mocker.GetMock<ICustomFormatService>() + .Setup(v => v.All()) + .Returns(new List<CustomFormat>()); + } + + [TestCase("The Mist", 2018, "Mist, The 2018")] + [TestCase("The Rat Pack (A&E)", 1999, "Rat Pack, The AandE 1999")] + [TestCase("The Climax: I (Almost) Got Away With It (2016)", 2016, "Climax I Almost Got Away With It, The 2016")] + [TestCase("The Climax: I (Almost) Got Away With It (2016)", 0, "Climax I Almost Got Away With It, The 2016")] + [TestCase("The Climax: I (Almost) Got Away With It", 0, "Climax I Almost Got Away With It, The")] + [TestCase("A", 2017, "A 2017")] + public void should_get_expected_title_back(string title, int year, string expected) + { + _series.Title = title; + _series.Year = year; + _namingConfig.StandardEpisodeFormat = "{Series CleanTitleTheYear}"; + + Subject.BuildFileName(new List<Episode> { _episode }, _series, _episodeFile) + .Should().Be(expected); + } + + [Test] + public void should_not_include_0_for_year() + { + _series.Title = "The Alienist"; + _series.Year = 0; + _namingConfig.StandardEpisodeFormat = "{Series TitleTheYear}"; + + Subject.BuildFileName(new List<Episode> { _episode }, _series, _episodeFile) + .Should().Be("Alienist, The"); + } + } +} diff --git a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs index 9af357cd3..65522587f 100644 --- a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs +++ b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs @@ -353,6 +353,17 @@ namespace NzbDrone.Core.Organizer return TitlePrefixRegex.Replace(title, "$2, $1$3"); } + public static string CleanTitleThe(string title) + { + if (TitlePrefixRegex.IsMatch(title)) + { + var splitResult = TitlePrefixRegex.Split(title); + return $"{CleanTitle(splitResult[2]).Trim()}, {splitResult[1]}{CleanTitle(splitResult[3])}"; + } + + return CleanTitle(title); + } + public static string TitleYear(string title, int year) { // Don't use 0 for the year. @@ -370,6 +381,25 @@ namespace NzbDrone.Core.Organizer return $"{title} ({year})"; } + public static string CleanTitleTheYear(string title, int year) + { + // Don't use 0 for the year. + if (year == 0) + { + return CleanTitleThe(title); + } + + // Regex match incase the year in the title doesn't match the year, for whatever reason. + if (YearRegex.IsMatch(title)) + { + var splitReturn = YearRegex.Split(title); + var yearMatch = YearRegex.Match(title); + return $"{CleanTitleThe(splitReturn[0].Trim())} {yearMatch.Value[1..5]}"; + } + + return $"{CleanTitleThe(title)} {year}"; + } + public static string TitleWithoutYear(string title) { title = YearRegex.Replace(title, ""); @@ -462,13 +492,16 @@ namespace NzbDrone.Core.Organizer { tokenHandlers["{Series Title}"] = m => series.Title; tokenHandlers["{Series CleanTitle}"] = m => CleanTitle(series.Title); + tokenHandlers["{Series TitleYear}"] = m => TitleYear(series.Title, series.Year); tokenHandlers["{Series CleanTitleYear}"] = m => CleanTitle(TitleYear(series.Title, series.Year)); + tokenHandlers["{Series TitleWithoutYear}"] = m => TitleWithoutYear(series.Title); tokenHandlers["{Series CleanTitleWithoutYear}"] = m => CleanTitle(TitleWithoutYear(series.Title)); tokenHandlers["{Series TitleThe}"] = m => TitleThe(series.Title); - tokenHandlers["{Series TitleYear}"] = m => TitleYear(series.Title, series.Year); - tokenHandlers["{Series TitleWithoutYear}"] = m => TitleWithoutYear(series.Title); + tokenHandlers["{Series CleanTitleThe}"] = m => CleanTitleThe(series.Title); tokenHandlers["{Series TitleTheYear}"] = m => TitleYear(TitleThe(series.Title), series.Year); + tokenHandlers["{Series CleanTitleTheYear}"] = m => CleanTitleTheYear(series.Title, series.Year); tokenHandlers["{Series TitleTheWithoutYear}"] = m => TitleWithoutYear(TitleThe(series.Title)); + tokenHandlers["{Series CleanTitleTheWithoutYear}"] = m => CleanTitleThe(TitleWithoutYear(series.Title)); tokenHandlers["{Series TitleFirstCharacter}"] = m => TitleFirstCharacter(TitleThe(series.Title)); tokenHandlers["{Series Year}"] = m => series.Year.ToString(); } From df2e867528249cf707788d8341c4a26293e179ba Mon Sep 17 00:00:00 2001 From: Mark McDowall <mark@mcdowall.ca> Date: Mon, 9 Oct 2023 22:33:26 -0700 Subject: [PATCH 048/136] Fixed: Reject full DVD disk releases Closes #5975 --- .../RawDiskSpecificationFixture.cs | 16 ++++++++++------ .../Specifications/RawDiskSpecification.cs | 9 +++++---- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/RawDiskSpecificationFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/RawDiskSpecificationFixture.cs index a07f6dca9..1ac77bee8 100644 --- a/src/NzbDrone.Core.Test/DecisionEngineTests/RawDiskSpecificationFixture.cs +++ b/src/NzbDrone.Core.Test/DecisionEngineTests/RawDiskSpecificationFixture.cs @@ -1,4 +1,4 @@ -using FluentAssertions; +using FluentAssertions; using NUnit.Framework; using NzbDrone.Core.DecisionEngine.Specifications; using NzbDrone.Core.Indexers; @@ -72,11 +72,15 @@ namespace NzbDrone.Core.Test.DecisionEngineTests Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeFalse(); } - [TestCase("How the Earth Was Made S02 Disc 1 1080i Blu-ray DTS-HD MA 2.0 AVC-TrollHD")] - [TestCase("The Universe S03 Disc 1 1080p Blu-ray LPCM 2.0 AVC-TrollHD")] - [TestCase("HELL ON WHEELS S02 1080P FULL BLURAY AVC DTS-HD MA 5 1")] - [TestCase("Game.of.Thrones.S06.2016.DISC.3.BluRay.1080p.AVC.Atmos.TrueHD7.1-MTeam")] - [TestCase("Game of Thrones S05 Disc 1 BluRay 1080p AVC Atmos TrueHD 7 1-MTeam")] + [TestCase("Series Title S02 Disc 1 1080i Blu-ray DTS-HD MA 2.0 AVC-TrollHD")] + [TestCase("Series Title S03 Disc 1 1080p Blu-ray LPCM 2.0 AVC-TrollHD")] + [TestCase("SERIES TITLE S02 1080P FULL BLURAY AVC DTS-HD MA 5 1")] + [TestCase("Series.Title.S06.2016.DISC.3.BluRay.1080p.AVC.Atmos.TrueHD7.1-MTeam")] + [TestCase("Series Title S05 Disc 1 BluRay 1080p AVC Atmos TrueHD 7 1-MTeam")] + [TestCase("Series Title S05 Disc 1 BluRay 1080p AVC Atmos TrueHD 7 1-MTeam")] + [TestCase("Someone.the.Entertainer.Presents.S01.NTSC.3xDVD9.MPEG-2.DD2.0")] + [TestCase("Series.Title.S00.The.Christmas.Special.2011.PAL.DVD5.DD2.0")] + [TestCase("Series.of.Desire.2000.S1_D01.NTSC.DVD5")] public void should_return_false_if_matches_disc_format(string title) { _remoteEpisode.Release.Title = title; diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/RawDiskSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/RawDiskSpecification.cs index 23fcaa901..80c40c95e 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/RawDiskSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/RawDiskSpecification.cs @@ -1,4 +1,4 @@ -using System.Linq; +using System.Linq; using System.Text.RegularExpressions; using NLog; using NzbDrone.Common.Extensions; @@ -12,7 +12,8 @@ namespace NzbDrone.Core.DecisionEngine.Specifications private static readonly Regex[] DiscRegex = new[] { new Regex(@"(?:dis[ck])(?:[-_. ]\d+[-_. ])(?:(?:(?:480|720|1080|2160)[ip]|)[-_. ])?(?:Blu\-?ray)", RegexOptions.Compiled | RegexOptions.IgnoreCase), - new Regex(@"(?:(?:480|720|1080|2160)[ip]|)[-_. ](?:full)[-_. ](?:Blu\-?ray)", RegexOptions.Compiled | RegexOptions.IgnoreCase) + new Regex(@"(?:(?:480|720|1080|2160)[ip]|)[-_. ](?:full)[-_. ](?:Blu\-?ray)", RegexOptions.Compiled | RegexOptions.IgnoreCase), + new Regex(@"(?:\d?x?M?DVD-?[R59])", RegexOptions.Compiled | RegexOptions.IgnoreCase) }; private static readonly string[] _dvdContainerTypes = new[] { "vob", "iso" }; @@ -39,8 +40,8 @@ namespace NzbDrone.Core.DecisionEngine.Specifications { if (regex.IsMatch(subject.Release.Title)) { - _logger.Debug("Release contains raw Bluray, rejecting."); - return Decision.Reject("Raw Bluray release"); + _logger.Debug("Release contains raw Bluray/DVD, rejecting."); + return Decision.Reject("Raw Bluray/DVD release"); } } From 076aaba9083900cb6c9cd01459d0e016c66e31d2 Mon Sep 17 00:00:00 2001 From: Mark McDowall <mark@mcdowall.ca> Date: Mon, 9 Oct 2023 23:21:52 -0700 Subject: [PATCH 049/136] Fixed: End year displayed on series details Closes #6067 --- frontend/src/Series/Details/SeriesDetails.js | 5 ++-- .../Migration/199_series_last_aired.cs | 16 ++++++++++++ .../SkyHook/Resource/ShowResource.cs | 1 + .../MetadataSource/SkyHook/SkyHookProxy.cs | 5 ++++ .../SeriesStats/SeasonStatistics.cs | 24 +++++++++++++++++ .../SeriesStats/SeriesStatistics.cs | 26 ++++++++++++++++++- .../SeriesStats/SeriesStatisticsRepository.cs | 3 ++- .../SeriesStats/SeriesStatisticsService.cs | 4 ++- src/NzbDrone.Core/Tv/RefreshSeriesService.cs | 1 + src/NzbDrone.Core/Tv/Series.cs | 1 + src/Sonarr.Api.V3/Series/SeriesController.cs | 3 +++ src/Sonarr.Api.V3/Series/SeriesResource.cs | 2 ++ 12 files changed, 86 insertions(+), 5 deletions(-) create mode 100644 src/NzbDrone.Core/Datastore/Migration/199_series_last_aired.cs diff --git a/frontend/src/Series/Details/SeriesDetails.js b/frontend/src/Series/Details/SeriesDetails.js index 6f05d593b..8f6189cbe 100644 --- a/frontend/src/Series/Details/SeriesDetails.js +++ b/frontend/src/Series/Details/SeriesDetails.js @@ -190,7 +190,7 @@ class SeriesDetails extends Component { genres, tags, year, - previousAiring, + lastAired, isSaving, isRefreshing, isSearching, @@ -227,7 +227,7 @@ class SeriesDetails extends Component { } = this.state; const statusDetails = getSeriesStatusDetails(status); - const runningYears = statusDetails.title === translate('Ended') ? `${year}-${getDateYear(previousAiring)}` : `${year}-`; + const runningYears = statusDetails.title === translate('Ended') ? `${year}-${getDateYear(lastAired)}` : `${year}-`; let episodeFilesCountMessage = translate('SeriesDetailsNoEpisodeFiles'); @@ -711,6 +711,7 @@ SeriesDetails.propTypes = { genres: PropTypes.arrayOf(PropTypes.string).isRequired, tags: PropTypes.arrayOf(PropTypes.number).isRequired, year: PropTypes.number.isRequired, + lastAired: PropTypes.string, previousAiring: PropTypes.string, isSaving: PropTypes.bool.isRequired, saveError: PropTypes.object, diff --git a/src/NzbDrone.Core/Datastore/Migration/199_series_last_aired.cs b/src/NzbDrone.Core/Datastore/Migration/199_series_last_aired.cs new file mode 100644 index 000000000..2264dabd1 --- /dev/null +++ b/src/NzbDrone.Core/Datastore/Migration/199_series_last_aired.cs @@ -0,0 +1,16 @@ +using FluentMigrator; +using NzbDrone.Core.Datastore.Migration.Framework; + +namespace NzbDrone.Core.Datastore.Migration +{ + [Migration(199)] + public class series_last_aired : NzbDroneMigrationBase + { + protected override void MainDbUpgrade() + { + Alter.Table("Series").AddColumn("LastAired").AsDateTimeOffset().Nullable(); + + // Execute.Sql("UPDATE Series SET LastAired = "); + } + } +} diff --git a/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/ShowResource.cs b/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/ShowResource.cs index a44137f5a..c7acd354a 100644 --- a/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/ShowResource.cs +++ b/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/ShowResource.cs @@ -20,6 +20,7 @@ namespace NzbDrone.Core.MetadataSource.SkyHook.Resource // public string Language { get; set; } public string Slug { get; set; } public string FirstAired { get; set; } + public string LastAired { get; set; } public int? TvRageId { get; set; } public int? TvMazeId { get; set; } diff --git a/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs b/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs index 4e5077c02..b067c3dd8 100644 --- a/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs +++ b/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs @@ -189,6 +189,11 @@ namespace NzbDrone.Core.MetadataSource.SkyHook series.Year = series.FirstAired.Value.Year; } + if (show.LastAired != null) + { + series.LastAired = DateTime.ParseExact(show.LastAired, "yyyy-MM-dd", DateTimeFormatInfo.InvariantInfo, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal); + } + series.Overview = show.Overview; if (show.Runtime != null) diff --git a/src/NzbDrone.Core/SeriesStats/SeasonStatistics.cs b/src/NzbDrone.Core/SeriesStats/SeasonStatistics.cs index 8c730bed6..5fe1507c2 100644 --- a/src/NzbDrone.Core/SeriesStats/SeasonStatistics.cs +++ b/src/NzbDrone.Core/SeriesStats/SeasonStatistics.cs @@ -12,6 +12,7 @@ namespace NzbDrone.Core.SeriesStats public int SeasonNumber { get; set; } public string NextAiringString { get; set; } public string PreviousAiringString { get; set; } + public string LastAiredString { get; set; } public int EpisodeFileCount { get; set; } public int EpisodeCount { get; set; } public int AvailableEpisodeCount { get; set; } @@ -65,6 +66,29 @@ namespace NzbDrone.Core.SeriesStats } } + public DateTime? LastAired + { + get + { + DateTime lastAired; + + try + { + if (!DateTime.TryParse(LastAiredString, out lastAired)) + { + return null; + } + } + catch (ArgumentOutOfRangeException) + { + // GHI 3518: Can throw on mono (6.x?) despite being a Try* + return null; + } + + return lastAired; + } + } + public List<string> ReleaseGroups { get diff --git a/src/NzbDrone.Core/SeriesStats/SeriesStatistics.cs b/src/NzbDrone.Core/SeriesStats/SeriesStatistics.cs index fb6215e18..cb054da7c 100644 --- a/src/NzbDrone.Core/SeriesStats/SeriesStatistics.cs +++ b/src/NzbDrone.Core/SeriesStats/SeriesStatistics.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using NzbDrone.Core.Datastore; @@ -9,6 +9,7 @@ namespace NzbDrone.Core.SeriesStats public int SeriesId { get; set; } public string NextAiringString { get; set; } public string PreviousAiringString { get; set; } + public string LastAiredString { get; set; } public int EpisodeFileCount { get; set; } public int EpisodeCount { get; set; } public int TotalEpisodeCount { get; set; } @@ -61,5 +62,28 @@ namespace NzbDrone.Core.SeriesStats return previousAiring; } } + + public DateTime? LastAired + { + get + { + DateTime lastAired; + + try + { + if (!DateTime.TryParse(LastAiredString, out lastAired)) + { + return null; + } + } + catch (ArgumentOutOfRangeException) + { + // GHI 3518: Can throw on mono (6.x?) despite being a Try* + return null; + } + + return lastAired; + } + } } } diff --git a/src/NzbDrone.Core/SeriesStats/SeriesStatisticsRepository.cs b/src/NzbDrone.Core/SeriesStats/SeriesStatisticsRepository.cs index ae0dd8fe4..be64ca1b3 100644 --- a/src/NzbDrone.Core/SeriesStats/SeriesStatisticsRepository.cs +++ b/src/NzbDrone.Core/SeriesStats/SeriesStatisticsRepository.cs @@ -80,7 +80,8 @@ namespace NzbDrone.Core.SeriesStats SUM(CASE WHEN (""Monitored"" = {trueIndicator} AND ""AirDateUtc"" <= @currentDate) OR ""EpisodeFileId"" > 0 THEN 1 ELSE 0 END) AS EpisodeCount, SUM(CASE WHEN ""EpisodeFileId"" > 0 THEN 1 ELSE 0 END) AS EpisodeFileCount, MIN(CASE WHEN ""AirDateUtc"" < @currentDate OR ""Monitored"" = {falseIndicator} THEN NULL ELSE ""AirDateUtc"" END) AS NextAiringString, - MAX(CASE WHEN ""AirDateUtc"" >= @currentDate OR ""Monitored"" = {falseIndicator} THEN NULL ELSE ""AirDateUtc"" END) AS PreviousAiringString", parameters) + MAX(CASE WHEN ""AirDateUtc"" >= @currentDate OR ""Monitored"" = {falseIndicator} THEN NULL ELSE ""AirDateUtc"" END) AS PreviousAiringString, + MAX(""AirDate"") AS LastAiredString", parameters) .GroupBy<Episode>(x => x.SeriesId) .GroupBy<Episode>(x => x.SeasonNumber); } diff --git a/src/NzbDrone.Core/SeriesStats/SeriesStatisticsService.cs b/src/NzbDrone.Core/SeriesStats/SeriesStatisticsService.cs index 44b5ddc16..5baef28f1 100644 --- a/src/NzbDrone.Core/SeriesStats/SeriesStatisticsService.cs +++ b/src/NzbDrone.Core/SeriesStats/SeriesStatisticsService.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; namespace NzbDrone.Core.SeriesStats @@ -52,9 +52,11 @@ namespace NzbDrone.Core.SeriesStats var nextAiring = seasonStatistics.Where(s => s.NextAiring != null).MinBy(s => s.NextAiring); var previousAiring = seasonStatistics.Where(s => s.PreviousAiring != null).MaxBy(s => s.PreviousAiring); + var lastAired = seasonStatistics.Where(s => s.SeasonNumber > 0 && s.LastAired != null).MaxBy(s => s.LastAired); seriesStatistics.NextAiringString = nextAiring?.NextAiringString; seriesStatistics.PreviousAiringString = previousAiring?.PreviousAiringString; + seriesStatistics.LastAiredString = lastAired?.LastAiredString; return seriesStatistics; } diff --git a/src/NzbDrone.Core/Tv/RefreshSeriesService.cs b/src/NzbDrone.Core/Tv/RefreshSeriesService.cs index a3c74b5e5..3cd0ab6b3 100644 --- a/src/NzbDrone.Core/Tv/RefreshSeriesService.cs +++ b/src/NzbDrone.Core/Tv/RefreshSeriesService.cs @@ -104,6 +104,7 @@ namespace NzbDrone.Core.Tv series.Images = seriesInfo.Images; series.Network = seriesInfo.Network; series.FirstAired = seriesInfo.FirstAired; + series.LastAired = seriesInfo.LastAired; series.Ratings = seriesInfo.Ratings; series.Actors = seriesInfo.Actors; series.Genres = seriesInfo.Genres; diff --git a/src/NzbDrone.Core/Tv/Series.cs b/src/NzbDrone.Core/Tv/Series.cs index b10ac3de5..9f557af42 100644 --- a/src/NzbDrone.Core/Tv/Series.cs +++ b/src/NzbDrone.Core/Tv/Series.cs @@ -48,6 +48,7 @@ namespace NzbDrone.Core.Tv public string RootFolderPath { get; set; } public DateTime Added { get; set; } public DateTime? FirstAired { get; set; } + public DateTime? LastAired { get; set; } public LazyLoaded<QualityProfile> QualityProfile { get; set; } public Language OriginalLanguage { get; set; } diff --git a/src/Sonarr.Api.V3/Series/SeriesController.cs b/src/Sonarr.Api.V3/Series/SeriesController.cs index 9f6c6823d..2622aa7ba 100644 --- a/src/Sonarr.Api.V3/Series/SeriesController.cs +++ b/src/Sonarr.Api.V3/Series/SeriesController.cs @@ -245,6 +245,9 @@ namespace Sonarr.Api.V3.Series private void LinkSeriesStatistics(SeriesResource resource, SeriesStatistics seriesStatistics) { + // Only set last aired from statistics if it's missing from the series itself + resource.LastAired ??= seriesStatistics.LastAired; + resource.PreviousAiring = seriesStatistics.PreviousAiring; resource.NextAiring = seriesStatistics.NextAiring; resource.Statistics = seriesStatistics.ToResource(resource.Seasons); diff --git a/src/Sonarr.Api.V3/Series/SeriesResource.cs b/src/Sonarr.Api.V3/Series/SeriesResource.cs index c57e52af5..021518149 100644 --- a/src/Sonarr.Api.V3/Series/SeriesResource.cs +++ b/src/Sonarr.Api.V3/Series/SeriesResource.cs @@ -51,6 +51,7 @@ namespace Sonarr.Api.V3.Series public int TvRageId { get; set; } public int TvMazeId { get; set; } public DateTime? FirstAired { get; set; } + public DateTime? LastAired { get; set; } public SeriesTypes SeriesType { get; set; } public string CleanTitle { get; set; } public string ImdbId { get; set; } @@ -121,6 +122,7 @@ namespace Sonarr.Api.V3.Series TvRageId = model.TvRageId, TvMazeId = model.TvMazeId, FirstAired = model.FirstAired, + LastAired = model.LastAired, SeriesType = model.SeriesType, CleanTitle = model.CleanTitle, ImdbId = model.ImdbId, From ec8da1c7debe80cfaa92f558f811bb671954d430 Mon Sep 17 00:00:00 2001 From: scampower3 <81431263+scampower3@users.noreply.github.com> Date: Tue, 17 Oct 2023 14:49:22 +0800 Subject: [PATCH 050/136] New: Add "enddate" tag to Kodi/Jellyfin series metadata --- .../Extras/Metadata/Consumers/Xbmc/XbmcMetadata.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/NzbDrone.Core/Extras/Metadata/Consumers/Xbmc/XbmcMetadata.cs b/src/NzbDrone.Core/Extras/Metadata/Consumers/Xbmc/XbmcMetadata.cs index 765769be6..38982b579 100644 --- a/src/NzbDrone.Core/Extras/Metadata/Consumers/Xbmc/XbmcMetadata.cs +++ b/src/NzbDrone.Core/Extras/Metadata/Consumers/Xbmc/XbmcMetadata.cs @@ -198,6 +198,12 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Xbmc tvShow.Add(new XElement("premiered", series.FirstAired.Value.ToString("yyyy-MM-dd"))); } + // Add support for Jellyfin's "enddate" tag + if (series.Status == SeriesStatusType.Ended && series.LastAired.HasValue) + { + tvShow.Add(new XElement("enddate", series.LastAired.Value.ToString("yyyy-MM-dd"))); + } + tvShow.Add(new XElement("studio", series.Network)); foreach (var actor in series.Actors) From 7b31287fc4f4bbd709f57c3945ff92f242b747fa Mon Sep 17 00:00:00 2001 From: bakerboy448 <55419169+bakerboy448@users.noreply.github.com> Date: Tue, 17 Oct 2023 01:50:08 -0500 Subject: [PATCH 051/136] Fixed: Re-run Removed Series health check after series is deleted --- src/NzbDrone.Core/HealthCheck/Checks/RemovedSeriesCheck.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NzbDrone.Core/HealthCheck/Checks/RemovedSeriesCheck.cs b/src/NzbDrone.Core/HealthCheck/Checks/RemovedSeriesCheck.cs index 24eb63002..1d0a65a96 100644 --- a/src/NzbDrone.Core/HealthCheck/Checks/RemovedSeriesCheck.cs +++ b/src/NzbDrone.Core/HealthCheck/Checks/RemovedSeriesCheck.cs @@ -7,7 +7,7 @@ using NzbDrone.Core.Tv.Events; namespace NzbDrone.Core.HealthCheck.Checks { [CheckOn(typeof(SeriesUpdatedEvent))] - [CheckOn(typeof(SeriesDeletedEvent), CheckOnCondition.FailedOnly)] + [CheckOn(typeof(SeriesDeletedEvent))] [CheckOn(typeof(SeriesRefreshCompleteEvent))] public class RemovedSeriesCheck : HealthCheckBase, ICheckOnCondition<SeriesUpdatedEvent>, ICheckOnCondition<SeriesDeletedEvent> { From 41ed300899e8d7de82b1113d13ac6f6cf28cec17 Mon Sep 17 00:00:00 2001 From: Bogdan <mynameisbogdan@users.noreply.github.com> Date: Tue, 17 Oct 2023 09:50:23 +0300 Subject: [PATCH 052/136] Fixed: Ignore case when cleansing announce URLs Closes #6047 --- .../CleanseLogMessageFixture.cs | 18 +++++++++--------- .../Instrumentation/CleanseLogMessage.cs | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/NzbDrone.Common.Test/InstrumentationTests/CleanseLogMessageFixture.cs b/src/NzbDrone.Common.Test/InstrumentationTests/CleanseLogMessageFixture.cs index 388a96635..6b1ea4171 100644 --- a/src/NzbDrone.Common.Test/InstrumentationTests/CleanseLogMessageFixture.cs +++ b/src/NzbDrone.Common.Test/InstrumentationTests/CleanseLogMessageFixture.cs @@ -71,15 +71,15 @@ namespace NzbDrone.Common.Test.InstrumentationTests [TestCase(@"[Info] MigrationController: *** Migrating Database=sonarr-main;Host=postgres14;Username=mySecret;Password=mySecret;Port=5432;token=mySecret;Enlist=False&username=mySecret;mypassword=mySecret;mypass=shouldkeep1;test_token=mySecret;password=123%@%_@!#^#@;use_password=mySecret;get_token=shouldkeep2;usetoken=shouldkeep3;passwrd=mySecret;")] // Announce URLs (passkeys) Magnet & Tracker - [TestCase(@"magnet_uri"":""magnet:?xt=urn:btih:9pr04sgkillroyimaveql2tyu8xyui&dn=&tr=https%3a%2f%2fxxx.yyy%2f9pr04sg601233210imaveql2tyu8xyui%2fannounce""}")] - [TestCase(@"magnet_uri"":""magnet:?xt=urn:btih:9pr04sgkillroyimaveql2tyu8xyui&dn=&tr=https%3a%2f%2fxxx.yyy%2ftracker.php%2f9pr04sg601233210imaveql2tyu8xyui%2fannounce""}")] - [TestCase(@"magnet_uri"":""magnet:?xt=urn:btih:9pr04sgkillroyimaveql2tyu8xyui&dn=&tr=https%3a%2f%2fxxx.yyy%2fannounce%2f9pr04sg601233210imaveql2tyu8xyui""}")] - [TestCase(@"magnet_uri"":""magnet:?xt=urn:btih:9pr04sgkillroyimaveql2tyu8xyui&dn=&tr=https%3a%2f%2fxxx.yyy%2fannounce.php%3fpasskey%3d9pr04sg601233210imaveql2tyu8xyui""}")] - [TestCase(@"tracker"":""https://xxx.yyy/9pr04sg601233210imaveql2tyu8xyui/announce""}")] - [TestCase(@"tracker"":""https://xxx.yyy/tracker.php/9pr04sg601233210imaveql2tyu8xyui/announce""}")] - [TestCase(@"tracker"":""https://xxx.yyy/announce/9pr04sg601233210imaveql2tyu8xyui""}")] - [TestCase(@"tracker"":""https://xxx.yyy/announce.php?passkey=9pr04sg601233210imaveql2tyu8xyui""}")] - [TestCase(@"tracker"":""http://xxx.yyy/announce.php?passkey=9pr04sg601233210imaveql2tyu8xyui"",""info"":""http://xxx.yyy/info?a=b""")] + [TestCase(@"magnet_uri"":""magnet:?xt=urn:btih:9pr04sgkillroyimaveql2tyu8xyui&dn=&tr=https%3a%2f%2fxxx.yyy%2f9pr04sg601233210IMAveQL2tyu8xyui%2fannounce""}")] + [TestCase(@"magnet_uri"":""magnet:?xt=urn:btih:9pr04sgkillroyimaveql2tyu8xyui&dn=&tr=https%3a%2f%2fxxx.yyy%2ftracker.php%2f9pr04sg601233210IMAveQL2tyu8xyui%2fannounce""}")] + [TestCase(@"magnet_uri"":""magnet:?xt=urn:btih:9pr04sgkillroyimaveql2tyu8xyui&dn=&tr=https%3a%2f%2fxxx.yyy%2fannounce%2f9pr04sg601233210IMAveQL2tyu8xyui""}")] + [TestCase(@"magnet_uri"":""magnet:?xt=urn:btih:9pr04sgkillroyimaveql2tyu8xyui&dn=&tr=https%3a%2f%2fxxx.yyy%2fannounce.php%3fpasskey%3d9pr04sg601233210IMAveQL2tyu8xyui""}")] + [TestCase(@"tracker"":""https://xxx.yyy/9pr04sg601233210IMAveQL2tyu8xyui/announce""}")] + [TestCase(@"tracker"":""https://xxx.yyy/tracker.php/9pr04sg601233210IMAveQL2tyu8xyui/announce""}")] + [TestCase(@"tracker"":""https://xxx.yyy/announce/9pr04sg601233210IMAveQL2tyu8xyui""}")] + [TestCase(@"tracker"":""https://xxx.yyy/announce.php?passkey=9pr04sg601233210IMAveQL2tyu8xyui""}")] + [TestCase(@"tracker"":""http://xxx.yyy/announce.php?passkey=9pr04sg601233210IMAveQL2tyu8xyui"",""info"":""http://xxx.yyy/info?a=b""")] // Webhooks - Notifiarr [TestCase(@"https://xxx.yyy/api/v1/notification/sonarr/9pr04sg6-0123-3210-imav-eql2tyu8xyui")] diff --git a/src/NzbDrone.Common/Instrumentation/CleanseLogMessage.cs b/src/NzbDrone.Common/Instrumentation/CleanseLogMessage.cs index d0150a7bc..12d027afa 100644 --- a/src/NzbDrone.Common/Instrumentation/CleanseLogMessage.cs +++ b/src/NzbDrone.Common/Instrumentation/CleanseLogMessage.cs @@ -20,7 +20,7 @@ namespace NzbDrone.Common.Instrumentation new (@"\b(\w*)?(_?(?<!use|get_)token|username|passwo?rd)=(?<secret>[^&=]+?)(?= |&|$|;)", RegexOptions.Compiled | RegexOptions.IgnoreCase), // Trackers Announce Keys; Designed for Qbit Json; should work for all in theory - new (@"announce(\.php)?(/|%2f|%3fpasskey%3d)(?<secret>[a-z0-9]{16,})|(?<secret>[a-z0-9]{16,})(/|%2f)announce"), + new (@"announce(\.php)?(/|%2f|%3fpasskey%3d)(?<secret>[a-z0-9]{16,})|(?<secret>[a-z0-9]{16,})(/|%2f)announce", RegexOptions.Compiled | RegexOptions.IgnoreCase), // Path new (@"C:\\Users\\(?<secret>[^\""]+?)(\\|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase), From 11f96c31048c2d1aafca0c91736d439f7f9a95a8 Mon Sep 17 00:00:00 2001 From: Mark McDowall <mark@mcdowall.ca> Date: Mon, 16 Oct 2023 23:51:00 -0700 Subject: [PATCH 053/136] Use named tokens for backend translations Closes #6051 --- .../LocalizationServiceFixture.cs | 23 +-- .../Checks/ApiKeyValidationCheck.cs | 3 +- .../HealthCheck/Checks/DownloadClientCheck.cs | 7 +- ...oadClientRemovesCompletedDownloadsCheck.cs | 6 +- .../Checks/DownloadClientRootFolderCheck.cs | 7 +- .../Checks/DownloadClientSortingCheck.cs | 7 +- .../Checks/DownloadClientStatusCheck.cs | 6 +- .../Checks/ImportListRootFolderCheck.cs | 10 +- .../Checks/ImportListStatusCheck.cs | 6 +- .../Checks/IndexerDownloadClientCheck.cs | 6 +- .../Checks/IndexerJackettAllCheck.cs | 6 +- .../Checks/IndexerLongTermStatusCheck.cs | 6 +- .../HealthCheck/Checks/IndexerStatusCheck.cs | 6 +- .../Checks/NotificationStatusCheck.cs | 6 +- .../HealthCheck/Checks/ProxyCheck.cs | 18 +- .../HealthCheck/Checks/RecyclingBinCheck.cs | 6 +- .../Checks/RemotePathMappingCheck.cs | 187 ++++++++++++++++-- .../HealthCheck/Checks/RemovedSeriesCheck.cs | 11 +- .../HealthCheck/Checks/RootFolderCheck.cs | 15 +- .../HealthCheck/Checks/UpdateCheck.cs | 24 ++- src/NzbDrone.Core/Localization/Core/cs.json | 4 +- src/NzbDrone.Core/Localization/Core/de.json | 16 +- src/NzbDrone.Core/Localization/Core/el.json | 8 +- src/NzbDrone.Core/Localization/Core/en.json | 81 ++++---- src/NzbDrone.Core/Localization/Core/es.json | 4 +- src/NzbDrone.Core/Localization/Core/fi.json | 12 +- src/NzbDrone.Core/Localization/Core/fr.json | 24 +-- src/NzbDrone.Core/Localization/Core/he.json | 2 +- src/NzbDrone.Core/Localization/Core/hu.json | 78 ++++---- src/NzbDrone.Core/Localization/Core/id.json | 4 +- src/NzbDrone.Core/Localization/Core/it.json | 6 +- .../Localization/Core/nb_NO.json | 2 +- src/NzbDrone.Core/Localization/Core/nl.json | 2 +- src/NzbDrone.Core/Localization/Core/pl.json | 2 +- src/NzbDrone.Core/Localization/Core/pt.json | 2 +- .../Localization/Core/pt_BR.json | 81 ++++---- src/NzbDrone.Core/Localization/Core/ro.json | 8 +- src/NzbDrone.Core/Localization/Core/ru.json | 24 +-- src/NzbDrone.Core/Localization/Core/vi.json | 2 +- .../Localization/Core/zh_CN.json | 81 ++++---- .../Localization/Core/zh_TW.json | 2 +- .../Localization/LocalizationService.cs | 35 ++-- 42 files changed, 554 insertions(+), 292 deletions(-) diff --git a/src/NzbDrone.Core.Test/Localization/LocalizationServiceFixture.cs b/src/NzbDrone.Core.Test/Localization/LocalizationServiceFixture.cs index 180292622..3e2f5f430 100644 --- a/src/NzbDrone.Core.Test/Localization/LocalizationServiceFixture.cs +++ b/src/NzbDrone.Core.Test/Localization/LocalizationServiceFixture.cs @@ -30,9 +30,11 @@ namespace NzbDrone.Core.Test.Localization } [Test] - public void should_get_string_in_default_language_dictionary_if_no_lang_country_code_exists_and_string_exists() + public void should_get_string_in_french() { - var localizedString = Subject.GetLocalizedString("UiLanguage", "fr_fr"); + Mocker.GetMock<IConfigService>().Setup(m => m.UILanguage).Returns((int)Language.French); + + var localizedString = Subject.GetLocalizedString("UiLanguage"); localizedString.Should().Be("UI Langue"); @@ -40,19 +42,10 @@ namespace NzbDrone.Core.Test.Localization } [Test] - public void should_get_string_in_default_dictionary_if_no_lang_exists_and_string_exists() + public void should_get_string_in_default_dictionary_if_unknown_language_and_string_exists() { - var localizedString = Subject.GetLocalizedString("UiLanguage", "an"); - - localizedString.Should().Be("UI Language"); - - ExceptionVerification.ExpectedErrors(1); - } - - [Test] - public void should_get_string_in_default_dictionary_if_lang_empty_and_string_exists() - { - var localizedString = Subject.GetLocalizedString("UiLanguage", ""); + Mocker.GetMock<IConfigService>().Setup(m => m.UILanguage).Returns(0); + var localizedString = Subject.GetLocalizedString("UiLanguage"); localizedString.Should().Be("UI Language"); } @@ -60,7 +53,7 @@ namespace NzbDrone.Core.Test.Localization [Test] public void should_return_argument_if_string_doesnt_exists() { - var localizedString = Subject.GetLocalizedString("badString", "en"); + var localizedString = Subject.GetLocalizedString("badString"); localizedString.Should().Be("badString"); } diff --git a/src/NzbDrone.Core/HealthCheck/Checks/ApiKeyValidationCheck.cs b/src/NzbDrone.Core/HealthCheck/Checks/ApiKeyValidationCheck.cs index fa284cb31..ec4ae577a 100644 --- a/src/NzbDrone.Core/HealthCheck/Checks/ApiKeyValidationCheck.cs +++ b/src/NzbDrone.Core/HealthCheck/Checks/ApiKeyValidationCheck.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using NLog; using NzbDrone.Core.Configuration; using NzbDrone.Core.Configuration.Events; @@ -28,7 +29,7 @@ namespace NzbDrone.Core.HealthCheck.Checks { _logger.Warn("Please update your API key to be at least {0} characters long. You can do this via settings or the config file", MinimumLength); - return new HealthCheck(GetType(), HealthCheckResult.Warning, string.Format(_localizationService.GetLocalizedString("ApiKeyValidationHealthCheckMessage"), MinimumLength), "#invalid-api-key"); + return new HealthCheck(GetType(), HealthCheckResult.Warning, _localizationService.GetLocalizedString("ApiKeyValidationHealthCheckMessage", new Dictionary<string, object> { { "MinimumLength", MinimumLength } }), "#invalid-api-key"); } return new HealthCheck(GetType()); diff --git a/src/NzbDrone.Core/HealthCheck/Checks/DownloadClientCheck.cs b/src/NzbDrone.Core/HealthCheck/Checks/DownloadClientCheck.cs index e10ee665f..5d9cd4b7f 100644 --- a/src/NzbDrone.Core/HealthCheck/Checks/DownloadClientCheck.cs +++ b/src/NzbDrone.Core/HealthCheck/Checks/DownloadClientCheck.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using NLog; using NzbDrone.Core.Download; @@ -44,7 +45,11 @@ namespace NzbDrone.Core.HealthCheck.Checks return new HealthCheck(GetType(), HealthCheckResult.Error, - $"{string.Format(_localizationService.GetLocalizedString("DownloadClientCheckUnableToCommunicateWithHealthCheckMessage"), downloadClient.Definition.Name)} {ex.Message}", + _localizationService.GetLocalizedString("DownloadClientCheckUnableToCommunicateWithHealthCheckMessage", new Dictionary<string, object> + { + { "downloadClientName", downloadClient.Definition.Name }, + { "errorMessage", ex.Message } + }), "#unable-to-communicate-with-download-client"); } } diff --git a/src/NzbDrone.Core/HealthCheck/Checks/DownloadClientRemovesCompletedDownloadsCheck.cs b/src/NzbDrone.Core/HealthCheck/Checks/DownloadClientRemovesCompletedDownloadsCheck.cs index 8683195cb..1e1be0a26 100644 --- a/src/NzbDrone.Core/HealthCheck/Checks/DownloadClientRemovesCompletedDownloadsCheck.cs +++ b/src/NzbDrone.Core/HealthCheck/Checks/DownloadClientRemovesCompletedDownloadsCheck.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using NLog; using NzbDrone.Core.Datastore.Events; using NzbDrone.Core.Download; @@ -44,7 +45,10 @@ namespace NzbDrone.Core.HealthCheck.Checks { return new HealthCheck(GetType(), HealthCheckResult.Warning, - string.Format(_localizationService.GetLocalizedString("DownloadClientRemovesCompletedDownloadsHealthCheckMessage"), clientName, "Sonarr"), + _localizationService.GetLocalizedString("DownloadClientRemovesCompletedDownloadsHealthCheckMessage", new Dictionary<string, object> + { + { "downloadClientName", clientName } + }), "#download-client-removes-completed-downloads"); } } diff --git a/src/NzbDrone.Core/HealthCheck/Checks/DownloadClientRootFolderCheck.cs b/src/NzbDrone.Core/HealthCheck/Checks/DownloadClientRootFolderCheck.cs index 4e25fdc70..4f0a6de05 100644 --- a/src/NzbDrone.Core/HealthCheck/Checks/DownloadClientRootFolderCheck.cs +++ b/src/NzbDrone.Core/HealthCheck/Checks/DownloadClientRootFolderCheck.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Net.Http; using NLog; @@ -53,7 +54,11 @@ namespace NzbDrone.Core.HealthCheck.Checks { return new HealthCheck(GetType(), HealthCheckResult.Warning, - string.Format(_localizationService.GetLocalizedString("DownloadClientRootFolderHealthCheckMessage"), client.Definition.Name, folder.FullPath), + _localizationService.GetLocalizedString("DownloadClientRootFolderHealthCheckMessage", new Dictionary<string, object> + { + { "downloadClientName", client.Definition.Name }, + { "path", folder.FullPath } + }), "#downloads-in-root-folder"); } } diff --git a/src/NzbDrone.Core/HealthCheck/Checks/DownloadClientSortingCheck.cs b/src/NzbDrone.Core/HealthCheck/Checks/DownloadClientSortingCheck.cs index 37e7cc5fa..d6474bcb4 100644 --- a/src/NzbDrone.Core/HealthCheck/Checks/DownloadClientSortingCheck.cs +++ b/src/NzbDrone.Core/HealthCheck/Checks/DownloadClientSortingCheck.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using NLog; using NzbDrone.Common.Extensions; using NzbDrone.Core.Datastore.Events; @@ -45,7 +46,11 @@ namespace NzbDrone.Core.HealthCheck.Checks { return new HealthCheck(GetType(), HealthCheckResult.Warning, - string.Format(_localizationService.GetLocalizedString("DownloadClientSortingHealthCheckMessage"), clientName, status.SortingMode), + _localizationService.GetLocalizedString("DownloadClientSortingHealthCheckMessage", new Dictionary<string, object> + { + { "downloadClientName", clientName }, + { "sortingMode", status.SortingMode } + }), "#download-folder-and-library-folder-not-different-folders"); } } diff --git a/src/NzbDrone.Core/HealthCheck/Checks/DownloadClientStatusCheck.cs b/src/NzbDrone.Core/HealthCheck/Checks/DownloadClientStatusCheck.cs index acd0d5f9f..01e190475 100644 --- a/src/NzbDrone.Core/HealthCheck/Checks/DownloadClientStatusCheck.cs +++ b/src/NzbDrone.Core/HealthCheck/Checks/DownloadClientStatusCheck.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.Linq; using NzbDrone.Common.Extensions; using NzbDrone.Core.Download; @@ -45,7 +46,10 @@ namespace NzbDrone.Core.HealthCheck.Checks return new HealthCheck(GetType(), HealthCheckResult.Warning, - string.Format(_localizationService.GetLocalizedString("DownloadClientStatusSingleClientHealthCheckMessage"), string.Join(", ", backOffProviders.Select(v => v.Provider.Definition.Name))), + _localizationService.GetLocalizedString("DownloadClientStatusSingleClientHealthCheckMessage", new Dictionary<string, object> + { + { "downloadClientNames", string.Join(", ", backOffProviders.Select(v => v.Provider.Definition.Name)) } + }), "#download-clients-are-unavailable-due-to-failures"); } } diff --git a/src/NzbDrone.Core/HealthCheck/Checks/ImportListRootFolderCheck.cs b/src/NzbDrone.Core/HealthCheck/Checks/ImportListRootFolderCheck.cs index 15a9675f8..fc4ee7826 100644 --- a/src/NzbDrone.Core/HealthCheck/Checks/ImportListRootFolderCheck.cs +++ b/src/NzbDrone.Core/HealthCheck/Checks/ImportListRootFolderCheck.cs @@ -54,13 +54,19 @@ namespace NzbDrone.Core.HealthCheck.Checks return new HealthCheck(GetType(), HealthCheckResult.Error, - string.Format(_localizationService.GetLocalizedString("ImportListRootFolderMissingRootHealthCheckMessage"), FormatRootFolder(missingRootFolder.Key, missingRootFolder.Value)), + _localizationService.GetLocalizedString("ImportListRootFolderMissingRootHealthCheckMessage", new Dictionary<string, object> + { + { "rootFolderInfo", FormatRootFolder(missingRootFolder.Key, missingRootFolder.Value) } + }), "#import-list-missing-root-folder"); } return new HealthCheck(GetType(), HealthCheckResult.Error, - string.Format(_localizationService.GetLocalizedString("ImportListRootFolderMultipleMissingRootsHealthCheckMessage"), string.Join(" | ", missingRootFolders.Select(m => FormatRootFolder(m.Key, m.Value)))), + _localizationService.GetLocalizedString("ImportListRootFolderMultipleMissingRootsHealthCheckMessage", new Dictionary<string, object> + { + { "rootFoldersInfo", string.Join(" | ", missingRootFolders.Select(m => FormatRootFolder(m.Key, m.Value))) } + }), "#import-list-missing-root-folder"); } diff --git a/src/NzbDrone.Core/HealthCheck/Checks/ImportListStatusCheck.cs b/src/NzbDrone.Core/HealthCheck/Checks/ImportListStatusCheck.cs index 17c958374..e11efc27c 100644 --- a/src/NzbDrone.Core/HealthCheck/Checks/ImportListStatusCheck.cs +++ b/src/NzbDrone.Core/HealthCheck/Checks/ImportListStatusCheck.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.Linq; using NzbDrone.Common.Extensions; using NzbDrone.Core.ImportLists; @@ -45,7 +46,10 @@ namespace NzbDrone.Core.HealthCheck.Checks return new HealthCheck(GetType(), HealthCheckResult.Warning, - string.Format(_localizationService.GetLocalizedString("ImportListStatusUnavailableHealthCheckMessage"), string.Join(", ", backOffProviders.Select(v => v.ImportList.Definition.Name))), + _localizationService.GetLocalizedString("ImportListStatusUnavailableHealthCheckMessage", new Dictionary<string, object> + { + { "importListNames", string.Join(", ", backOffProviders.Select(v => v.ImportList.Definition.Name)) } + }), "#import-lists-are-unavailable-due-to-failures"); } } diff --git a/src/NzbDrone.Core/HealthCheck/Checks/IndexerDownloadClientCheck.cs b/src/NzbDrone.Core/HealthCheck/Checks/IndexerDownloadClientCheck.cs index 94592426e..a7b17dbd8 100644 --- a/src/NzbDrone.Core/HealthCheck/Checks/IndexerDownloadClientCheck.cs +++ b/src/NzbDrone.Core/HealthCheck/Checks/IndexerDownloadClientCheck.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.Linq; using NzbDrone.Core.Download; using NzbDrone.Core.Indexers; @@ -35,7 +36,10 @@ namespace NzbDrone.Core.HealthCheck.Checks { return new HealthCheck(GetType(), HealthCheckResult.Warning, - string.Format(_localizationService.GetLocalizedString("IndexerDownloadClientHealthCheckMessage"), string.Join(", ", invalidIndexers.Select(v => v.Name).ToArray())), + _localizationService.GetLocalizedString("IndexerDownloadClientHealthCheckMessage", new Dictionary<string, object> + { + { "indexerNames", string.Join(", ", invalidIndexers.Select(v => v.Name).ToArray()) } + }), "#invalid-indexer-download-client-setting"); } diff --git a/src/NzbDrone.Core/HealthCheck/Checks/IndexerJackettAllCheck.cs b/src/NzbDrone.Core/HealthCheck/Checks/IndexerJackettAllCheck.cs index 366404892..f01a59437 100644 --- a/src/NzbDrone.Core/HealthCheck/Checks/IndexerJackettAllCheck.cs +++ b/src/NzbDrone.Core/HealthCheck/Checks/IndexerJackettAllCheck.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using NzbDrone.Common.Extensions; using NzbDrone.Core.Indexers; @@ -41,7 +42,10 @@ namespace NzbDrone.Core.HealthCheck.Checks return new HealthCheck(GetType(), HealthCheckResult.Warning, - string.Format(_localizationService.GetLocalizedString("IndexerJackettAllHealthCheckMessage"), string.Join(", ", jackettAllProviders.Select(i => i.Name))), + _localizationService.GetLocalizedString("IndexerJackettAllHealthCheckMessage", new Dictionary<string, object> + { + { "indexerNames", string.Join(", ", jackettAllProviders.Select(i => i.Name)) } + }), "#jackett-all-endpoint-used"); } } diff --git a/src/NzbDrone.Core/HealthCheck/Checks/IndexerLongTermStatusCheck.cs b/src/NzbDrone.Core/HealthCheck/Checks/IndexerLongTermStatusCheck.cs index f0c71ba38..f419cf205 100644 --- a/src/NzbDrone.Core/HealthCheck/Checks/IndexerLongTermStatusCheck.cs +++ b/src/NzbDrone.Core/HealthCheck/Checks/IndexerLongTermStatusCheck.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using NzbDrone.Common.Extensions; using NzbDrone.Core.Indexers; @@ -48,7 +49,10 @@ namespace NzbDrone.Core.HealthCheck.Checks return new HealthCheck(GetType(), HealthCheckResult.Warning, - string.Format(_localizationService.GetLocalizedString("IndexerLongTermStatusUnavailableHealthCheckMessage"), string.Join(", ", backOffProviders.Select(v => v.Provider.Definition.Name))), + _localizationService.GetLocalizedString("IndexerLongTermStatusUnavailableHealthCheckMessage", new Dictionary<string, object> + { + { "indexerNames", string.Join(", ", backOffProviders.Select(v => v.Provider.Definition.Name)) } + }), "#indexers-are-unavailable-due-to-failures"); } } diff --git a/src/NzbDrone.Core/HealthCheck/Checks/IndexerStatusCheck.cs b/src/NzbDrone.Core/HealthCheck/Checks/IndexerStatusCheck.cs index f3afaeba6..fa861fb94 100644 --- a/src/NzbDrone.Core/HealthCheck/Checks/IndexerStatusCheck.cs +++ b/src/NzbDrone.Core/HealthCheck/Checks/IndexerStatusCheck.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using NzbDrone.Common.Extensions; using NzbDrone.Core.Indexers; @@ -48,7 +49,10 @@ namespace NzbDrone.Core.HealthCheck.Checks return new HealthCheck(GetType(), HealthCheckResult.Warning, - string.Format(_localizationService.GetLocalizedString("IndexerStatusUnavailableHealthCheckMessage"), string.Join(", ", backOffProviders.Select(v => v.Provider.Definition.Name))), + _localizationService.GetLocalizedString("IndexerStatusUnavailableHealthCheckMessage", new Dictionary<string, object> + { + { "indexerNames", string.Join(", ", backOffProviders.Select(v => v.Provider.Definition.Name)) } + }), "#indexers-are-unavailable-due-to-failures"); } } diff --git a/src/NzbDrone.Core/HealthCheck/Checks/NotificationStatusCheck.cs b/src/NzbDrone.Core/HealthCheck/Checks/NotificationStatusCheck.cs index c9b5e2561..daf5ee725 100644 --- a/src/NzbDrone.Core/HealthCheck/Checks/NotificationStatusCheck.cs +++ b/src/NzbDrone.Core/HealthCheck/Checks/NotificationStatusCheck.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.Linq; using NzbDrone.Common.Extensions; using NzbDrone.Core.Localization; @@ -45,7 +46,10 @@ namespace NzbDrone.Core.HealthCheck.Checks return new HealthCheck(GetType(), HealthCheckResult.Warning, - string.Format(_localizationService.GetLocalizedString("NotificationStatusSingleClientHealthCheckMessage"), string.Join(", ", backOffProviders.Select(v => v.Provider.Definition.Name))), + _localizationService.GetLocalizedString("NotificationStatusSingleClientHealthCheckMessage", new Dictionary<string, object> + { + { "notificationNames", string.Join(", ", backOffProviders.Select(v => v.Provider.Definition.Name)) } + }), "#notifications-are-unavailable-due-to-failures"); } } diff --git a/src/NzbDrone.Core/HealthCheck/Checks/ProxyCheck.cs b/src/NzbDrone.Core/HealthCheck/Checks/ProxyCheck.cs index 3d05a75ea..f09033370 100644 --- a/src/NzbDrone.Core/HealthCheck/Checks/ProxyCheck.cs +++ b/src/NzbDrone.Core/HealthCheck/Checks/ProxyCheck.cs @@ -1,4 +1,5 @@ -using System; +using System; +using System.Collections.Generic; using System.Linq; using System.Net; using NLog; @@ -42,7 +43,10 @@ namespace NzbDrone.Core.HealthCheck.Checks { return new HealthCheck(GetType(), HealthCheckResult.Error, - string.Format(_localizationService.GetLocalizedString("ProxyResolveIpHealthCheckMessage"), _configService.ProxyHostname), + _localizationService.GetLocalizedString("ProxyResolveIpHealthCheckMessage", new Dictionary<string, object> + { + { "proxyHostName", _configService.ProxyHostname } + }), "#proxy-failed-resolve-ip"); } @@ -61,7 +65,10 @@ namespace NzbDrone.Core.HealthCheck.Checks return new HealthCheck(GetType(), HealthCheckResult.Error, - string.Format(_localizationService.GetLocalizedString("ProxyBadRequestHealthCheckMessage"), response.StatusCode), + _localizationService.GetLocalizedString("ProxyBadRequestHealthCheckMessage", new Dictionary<string, object> + { + { "statusCode", response.StatusCode } + }), "#proxy-failed-test"); } } @@ -71,7 +78,10 @@ namespace NzbDrone.Core.HealthCheck.Checks return new HealthCheck(GetType(), HealthCheckResult.Error, - string.Format(_localizationService.GetLocalizedString("ProxyFailedToTestHealthCheckMessage"), request.Url), + _localizationService.GetLocalizedString("ProxyFailedToTestHealthCheckMessage", new Dictionary<string, object> + { + { "url", request.Url } + }), "#proxy-failed-test"); } diff --git a/src/NzbDrone.Core/HealthCheck/Checks/RecyclingBinCheck.cs b/src/NzbDrone.Core/HealthCheck/Checks/RecyclingBinCheck.cs index 86d3949ac..ad30a74f7 100644 --- a/src/NzbDrone.Core/HealthCheck/Checks/RecyclingBinCheck.cs +++ b/src/NzbDrone.Core/HealthCheck/Checks/RecyclingBinCheck.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using NzbDrone.Common.Disk; using NzbDrone.Common.Extensions; using NzbDrone.Core.Configuration; @@ -33,7 +34,10 @@ namespace NzbDrone.Core.HealthCheck.Checks { return new HealthCheck(GetType(), HealthCheckResult.Error, - string.Format(_localizationService.GetLocalizedString("RecycleBinUnableToWriteHealthCheckMessage"), recycleBin), + _localizationService.GetLocalizedString("RecycleBinUnableToWriteHealthCheckMessage", new Dictionary<string, object> + { + { "path", recycleBin } + }), "#cannot-write-recycle-bin"); } diff --git a/src/NzbDrone.Core/HealthCheck/Checks/RemotePathMappingCheck.cs b/src/NzbDrone.Core/HealthCheck/Checks/RemotePathMappingCheck.cs index 2377272d0..eff41b1f2 100644 --- a/src/NzbDrone.Core/HealthCheck/Checks/RemotePathMappingCheck.cs +++ b/src/NzbDrone.Core/HealthCheck/Checks/RemotePathMappingCheck.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Net.Http; using NLog; @@ -68,30 +69,92 @@ namespace NzbDrone.Core.HealthCheck.Checks { if (!status.IsLocalhost) { - return new HealthCheck(GetType(), HealthCheckResult.Error, string.Format(_localizationService.GetLocalizedString("RemotePathMappingWrongOSPathHealthCheckMessage"), client.Definition.Name, folder.FullPath, _osInfo.Name), "#bad-remote-path-mapping"); + return new HealthCheck( + GetType(), + HealthCheckResult.Error, + _localizationService.GetLocalizedString( + "RemotePathMappingWrongOSPathHealthCheckMessage", new Dictionary<string, object> + { + { "downloadClientName", client.Definition.Name }, + { "path", folder.FullPath }, + { "osName", _osInfo.Name } + }), + "#bad-remote-path-mapping"); } if (_osInfo.IsDocker) { - return new HealthCheck(GetType(), HealthCheckResult.Error, string.Format(_localizationService.GetLocalizedString("RemotePathMappingBadDockerPathHealthCheckMessage"), client.Definition.Name, folder.FullPath, _osInfo.Name), "#docker-bad-remote-path-mapping"); + return new HealthCheck( + GetType(), + HealthCheckResult.Error, + _localizationService.GetLocalizedString( + "RemotePathMappingBadDockerPathHealthCheckMessage", + new Dictionary<string, object> + { + { "downloadClientName", client.Definition.Name }, + { "path", folder.FullPath }, + { "osName", _osInfo.Name } + }), + "#docker-bad-remote-path-mapping"); } - return new HealthCheck(GetType(), HealthCheckResult.Error, string.Format(_localizationService.GetLocalizedString("RemotePathMappingLocalWrongOSPathHealthCheckMessage"), client.Definition.Name, folder.FullPath, _osInfo.Name), "#bad-download-client-settings"); + return new HealthCheck( + GetType(), + HealthCheckResult.Error, + _localizationService.GetLocalizedString( + "RemotePathMappingLocalWrongOSPathHealthCheckMessage", + new Dictionary<string, object> + { + { "downloadClientName", client.Definition.Name }, + { "path", folder.FullPath }, + { "osName", _osInfo.Name } + }), + "#bad-download-client-settings"); } if (!_diskProvider.FolderExists(folder.FullPath)) { if (_osInfo.IsDocker) { - return new HealthCheck(GetType(), HealthCheckResult.Error, string.Format(_localizationService.GetLocalizedString("RemotePathMappingDockerFolderMissingHealthCheckMessage"), client.Definition.Name, folder.FullPath), "#docker-bad-remote-path-mapping"); + return new HealthCheck( + GetType(), + HealthCheckResult.Error, + _localizationService.GetLocalizedString( + "RemotePathMappingDockerFolderMissingHealthCheckMessage", + new Dictionary<string, object> + { + { "downloadClientName", client.Definition.Name }, + { "path", folder.FullPath } + }), + "#docker-bad-remote-path-mapping"); } if (!status.IsLocalhost) { - return new HealthCheck(GetType(), HealthCheckResult.Error, string.Format(_localizationService.GetLocalizedString("RemotePathMappingLocalFolderMissingHealthCheckMessage"), client.Definition.Name, folder.FullPath), "#bad-remote-path-mapping"); + return new HealthCheck( + GetType(), + HealthCheckResult.Error, + _localizationService.GetLocalizedString( + "RemotePathMappingLocalFolderMissingHealthCheckMessage", + new Dictionary<string, object> + { + { "downloadClientName", client.Definition.Name }, + { "path", folder.FullPath } + }), + "#bad-remote-path-mapping"); } - return new HealthCheck(GetType(), HealthCheckResult.Error, string.Format(_localizationService.GetLocalizedString("RemotePathMappingGenericPermissionsHealthCheckMessage"), client.Definition.Name, folder.FullPath), "#permissions-error"); + return new HealthCheck( + GetType(), + HealthCheckResult.Error, + _localizationService.GetLocalizedString( + "RemotePathMappingGenericPermissionsHealthCheckMessage", + new Dictionary<string, object> + { + { "downloadClientName", client.Definition.Name }, + { "path", folder.FullPath } + }), + "#permissions-error"); } } } @@ -129,12 +192,28 @@ namespace NzbDrone.Core.HealthCheck.Checks if (_diskProvider.FileExists(episodePath)) { - return new HealthCheck(GetType(), HealthCheckResult.Error, string.Format(_localizationService.GetLocalizedString("RemotePathMappingDownloadPermissionsHealthCheckMessage"), episodePath), "#permissions-error"); + return new HealthCheck(GetType(), + HealthCheckResult.Error, + _localizationService.GetLocalizedString( + "RemotePathMappingDownloadPermissionsHealthCheckMessage", + new Dictionary<string, object> + { + { "path", episodePath } + }), + "#permissions-error"); } // If the file doesn't exist but EpisodeInfo is not null then the message is coming from // ImportApprovedEpisodes and the file must have been removed part way through processing - return new HealthCheck(GetType(), HealthCheckResult.Error, string.Format(_localizationService.GetLocalizedString("RemotePathMappingFileRemovedHealthCheckMessage"), episodePath), "#remote-path-file-removed"); + return new HealthCheck(GetType(), + HealthCheckResult.Error, + _localizationService.GetLocalizedString( + "RemotePathMappingFileRemovedHealthCheckMessage", + new Dictionary<string, object> + { + { "path", episodePath } + }), + "#remote-path-file-removed"); } // If the previous case did not match then the failure occured in DownloadedEpisodeImportService, @@ -156,42 +235,118 @@ namespace NzbDrone.Core.HealthCheck.Checks // that the user realises something is wrong. if (dlpath.IsNullOrWhiteSpace()) { - return new HealthCheck(GetType(), HealthCheckResult.Error, _localizationService.GetLocalizedString("RemotePathMappingImportFailedHealthCheckMessage"), "#remote-path-import-failed"); + return new HealthCheck( + GetType(), + HealthCheckResult.Error, + _localizationService.GetLocalizedString("RemotePathMappingImportFailedHealthCheckMessage"), + "#remote-path-import-failed"); } if (!dlpath.IsPathValid(PathValidationType.CurrentOs)) { if (!status.IsLocalhost) { - return new HealthCheck(GetType(), HealthCheckResult.Error, string.Format(_localizationService.GetLocalizedString("RemotePathMappingFilesWrongOSPathHealthCheckMessage"), client.Definition.Name, dlpath, _osInfo.Name), "#bad-remote-path-mapping"); + return new HealthCheck( + GetType(), + HealthCheckResult.Error, + _localizationService.GetLocalizedString( + "RemotePathMappingFilesWrongOSPathHealthCheckMessage", + new Dictionary<string, object> + { + { "downloadClientName", client.Definition.Name }, + { "path", dlpath }, + { "osName", _osInfo.Name } + }), + "#bad-remote-path-mapping"); } if (_osInfo.IsDocker) { - return new HealthCheck(GetType(), HealthCheckResult.Error, string.Format(_localizationService.GetLocalizedString("RemotePathMappingFilesBadDockerPathHealthCheckMessage"), client.Definition.Name, dlpath, _osInfo.Name), "#docker-bad-remote-path-mapping"); + return new HealthCheck( + GetType(), + HealthCheckResult.Error, + _localizationService.GetLocalizedString( + "RemotePathMappingFilesBadDockerPathHealthCheckMessage", + new Dictionary<string, object> + { + { "downloadClientName", client.Definition.Name }, + { "path", dlpath }, + { "osName", _osInfo.Name } + }), + "#docker-bad-remote-path-mapping"); } - return new HealthCheck(GetType(), HealthCheckResult.Error, string.Format(_localizationService.GetLocalizedString("RemotePathMappingFilesLocalWrongOSPathHealthCheckMessage"), client.Definition.Name, dlpath, _osInfo.Name), "#bad-download-client-settings"); + return new HealthCheck( + GetType(), + HealthCheckResult.Error, + _localizationService.GetLocalizedString( + "RemotePathMappingFilesLocalWrongOSPathHealthCheckMessage", + new Dictionary<string, object> + { + { "downloadClientName", client.Definition.Name }, + { "path", dlpath }, + { "osName", _osInfo.Name } + }), + "#bad-download-client-settings"); } if (_diskProvider.FolderExists(dlpath)) { - return new HealthCheck(GetType(), HealthCheckResult.Error, string.Format(_localizationService.GetLocalizedString("RemotePathMappingFolderPermissionsHealthCheckMessage"), dlpath), "#permissions-error"); + return new HealthCheck( + GetType(), + HealthCheckResult.Error, + _localizationService.GetLocalizedString( + "RemotePathMappingFolderPermissionsHealthCheckMessage", + new Dictionary<string, object> + { + { "path", dlpath } + }), + "#permissions-error"); } // if it's a remote client/docker, likely missing path mappings if (_osInfo.IsDocker) { - return new HealthCheck(GetType(), HealthCheckResult.Error, string.Format(_localizationService.GetLocalizedString("RemotePathMappingFolderPermissionsHealthCheckMessage"), client.Definition.Name, dlpath), "#docker-bad-remote-path-mapping"); + return new HealthCheck( + GetType(), + HealthCheckResult.Error, + _localizationService.GetLocalizedString( + "RemotePathMappingFolderPermissionsHealthCheckMessage", + new Dictionary<string, object> + { + { "downloadClientName", client.Definition.Name }, + { "path", dlpath } + }), + "#docker-bad-remote-path-mapping"); } if (!status.IsLocalhost) { - return new HealthCheck(GetType(), HealthCheckResult.Error, string.Format(_localizationService.GetLocalizedString("RemotePathMappingRemoteDownloadClientHealthCheckMessage"), client.Definition.Name, dlpath), "#bad-remote-path-mapping"); + return new HealthCheck( + GetType(), + HealthCheckResult.Error, + _localizationService.GetLocalizedString( + "RemotePathMappingRemoteDownloadClientHealthCheckMessage", + new Dictionary<string, object> + { + { "downloadClientName", client.Definition.Name }, + { "path", dlpath }, + { "osName", _osInfo.Name } + }), "#bad-remote-path-mapping"); } // path mappings shouldn't be needed locally so probably a permissions issue - return new HealthCheck(GetType(), HealthCheckResult.Error, string.Format(_localizationService.GetLocalizedString("RemotePathMappingFilesGenericPermissionsHealthCheckMessage"), client.Definition.Name, dlpath), "#permissions-error"); + return new HealthCheck( + GetType(), + HealthCheckResult.Error, + _localizationService.GetLocalizedString( + "RemotePathMappingFilesGenericPermissionsHealthCheckMessage", + new Dictionary<string, object> + { + { "downloadClientName", client.Definition.Name }, + { "path", dlpath } + }), + "#permissions-error"); } catch (DownloadClientException ex) { diff --git a/src/NzbDrone.Core/HealthCheck/Checks/RemovedSeriesCheck.cs b/src/NzbDrone.Core/HealthCheck/Checks/RemovedSeriesCheck.cs index 1d0a65a96..363d3010d 100644 --- a/src/NzbDrone.Core/HealthCheck/Checks/RemovedSeriesCheck.cs +++ b/src/NzbDrone.Core/HealthCheck/Checks/RemovedSeriesCheck.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.Linq; using NzbDrone.Common.Extensions; using NzbDrone.Core.Localization; @@ -34,13 +35,19 @@ namespace NzbDrone.Core.HealthCheck.Checks { return new HealthCheck(GetType(), HealthCheckResult.Error, - string.Format(_localizationService.GetLocalizedString("RemovedSeriesSingleRemovedHealthCheckMessage"), seriesText), + _localizationService.GetLocalizedString("RemovedSeriesSingleRemovedHealthCheckMessage", new Dictionary<string, object> + { + { "series", seriesText } + }), "#series-removed-from-thetvdb"); } return new HealthCheck(GetType(), HealthCheckResult.Error, - string.Format(_localizationService.GetLocalizedString("RemovedSeriesMultipleRemovedHealthCheckMessage"), seriesText), + _localizationService.GetLocalizedString("RemovedSeriesMultipleRemovedHealthCheckMessage", new Dictionary<string, object> + { + { "series", seriesText } + }), "#series-removed-from-thetvdb"); } diff --git a/src/NzbDrone.Core/HealthCheck/Checks/RootFolderCheck.cs b/src/NzbDrone.Core/HealthCheck/Checks/RootFolderCheck.cs index c730be8da..0b0016f52 100644 --- a/src/NzbDrone.Core/HealthCheck/Checks/RootFolderCheck.cs +++ b/src/NzbDrone.Core/HealthCheck/Checks/RootFolderCheck.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.Linq; using NzbDrone.Common.Disk; using NzbDrone.Common.Extensions; @@ -42,13 +43,23 @@ namespace NzbDrone.Core.HealthCheck.Checks { return new HealthCheck(GetType(), HealthCheckResult.Error, - string.Format(_localizationService.GetLocalizedString("RootFolderMissingHealthCheckMessage"), missingRootFolders.First()), + _localizationService.GetLocalizedString( + "RootFolderMissingHealthCheckMessage", + new Dictionary<string, object> + { + { "rootFolderPath", missingRootFolders.First() } + }), "#missing-root-folder"); } return new HealthCheck(GetType(), HealthCheckResult.Error, - string.Format(_localizationService.GetLocalizedString("RootFolderMultipleMissingHealthCheckMessage"), string.Join(" | ", missingRootFolders)), + _localizationService.GetLocalizedString( + "RootFolderMultipleMissingHealthCheckMessage", + new Dictionary<string, object> + { + { "rootFolderPaths", string.Join(" | ", missingRootFolders) } + }), "#missing-root-folder"); } diff --git a/src/NzbDrone.Core/HealthCheck/Checks/UpdateCheck.cs b/src/NzbDrone.Core/HealthCheck/Checks/UpdateCheck.cs index e4e0690a4..23ad009cc 100644 --- a/src/NzbDrone.Core/HealthCheck/Checks/UpdateCheck.cs +++ b/src/NzbDrone.Core/HealthCheck/Checks/UpdateCheck.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using NzbDrone.Common.Disk; using NzbDrone.Common.EnvironmentInfo; @@ -47,7 +48,12 @@ namespace NzbDrone.Core.HealthCheck.Checks { return new HealthCheck(GetType(), HealthCheckResult.Error, - string.Format(_localizationService.GetLocalizedString("UpdateStartupTranslocationHealthCheckMessage"), startupFolder), + _localizationService.GetLocalizedString( + "UpdateStartupTranslocationHealthCheckMessage", + new Dictionary<string, object> + { + { "startupFolder", startupFolder } + }), "#cannot-install-update-because-startup-folder-is-in-an-app-translocation-folder."); } @@ -55,7 +61,13 @@ namespace NzbDrone.Core.HealthCheck.Checks { return new HealthCheck(GetType(), HealthCheckResult.Error, - string.Format(_localizationService.GetLocalizedString("UpdateStartupNotWritableHealthCheckMessage"), startupFolder, Environment.UserName), + _localizationService.GetLocalizedString( + "UpdateStartupNotWritableHealthCheckMessage", + new Dictionary<string, object> + { + { "startupFolder", startupFolder }, + { "userName", Environment.UserName } + }), "#cannot-install-update-because-startup-folder-is-not-writable-by-the-user"); } @@ -63,7 +75,13 @@ namespace NzbDrone.Core.HealthCheck.Checks { return new HealthCheck(GetType(), HealthCheckResult.Error, - string.Format(_localizationService.GetLocalizedString("UpdateUINotWritableHealthCheckMessage"), uiFolder, Environment.UserName), + _localizationService.GetLocalizedString( + "UpdateUiNotWritableHealthCheckMessage", + new Dictionary<string, object> + { + { "startupFolder", startupFolder }, + { "userName", Environment.UserName } + }), "#cannot-install-update-because-ui-folder-is-not-writable-by-the-user"); } } diff --git a/src/NzbDrone.Core/Localization/Core/cs.json b/src/NzbDrone.Core/Localization/Core/cs.json index daac5ecc5..2d676a310 100644 --- a/src/NzbDrone.Core/Localization/Core/cs.json +++ b/src/NzbDrone.Core/Localization/Core/cs.json @@ -1,6 +1,6 @@ { "Added": "Přidáno", - "ApiKeyValidationHealthCheckMessage": "Aktualizujte svůj klíč API tak, aby měl alespoň {0} znaků. Můžete to provést prostřednictvím nastavení nebo konfiguračního souboru", + "ApiKeyValidationHealthCheckMessage": "Aktualizujte svůj klíč API tak, aby měl alespoň {length} znaků. Můžete to provést prostřednictvím nastavení nebo konfiguračního souboru", "BlocklistRelease": "Blocklist pro vydání", "AgeWhenGrabbed": "Stáří (kdy bylo získáno)", "Always": "Vždy", @@ -246,7 +246,7 @@ "CustomFormatJson": "Vlastní JSON formát", "Debug": "Ladit", "Day": "Den", - "DeleteCustomFormatMessageText": "Opravdu chcete odstranit vlastní formát '{0}'?", + "DeleteCustomFormatMessageText": "Opravdu chcete odstranit vlastní formát '{customFormatName}'?", "DefaultNameCopiedProfile": "{name} - Kopírovat", "DefaultNameCopiedSpecification": "{name} - Kopírovat", "DefaultNotFoundMessage": "Asi jsi se ztratil, není tu nic k vidění.", diff --git a/src/NzbDrone.Core/Localization/Core/de.json b/src/NzbDrone.Core/Localization/Core/de.json index 3404099d9..dc241f8b0 100644 --- a/src/NzbDrone.Core/Localization/Core/de.json +++ b/src/NzbDrone.Core/Localization/Core/de.json @@ -1,6 +1,6 @@ { "Added": "Hinzugefügt", - "ApiKeyValidationHealthCheckMessage": "Bitte den API Schlüssel korrigieren, dieser muss mindestens {0} Zeichen lang sein. Die Änderung kann über die Einstellungen oder die Konfigurationsdatei erfolgen", + "ApiKeyValidationHealthCheckMessage": "Bitte den API Schlüssel korrigieren, dieser muss mindestens {length} Zeichen lang sein. Die Änderung kann über die Einstellungen oder die Konfigurationsdatei erfolgen", "AppDataLocationHealthCheckMessage": "Ein Update ist nicht möglich, um das Löschen von AppData beim Update zu verhindern", "RemoveCompletedDownloads": "Entferne abgeschlossene Downloads", "RemoveFailedDownloads": "Entferne fehlgeschlagene Downloads", @@ -8,10 +8,10 @@ "AutomaticAdd": "Automatisch hinzufügen", "CountSeasons": "{Anzahl} Staffeln", "DownloadClientCheckNoneAvailableHealthCheckMessage": "Es ist kein Download-Client verfügbar", - "DownloadClientCheckUnableToCommunicateWithHealthCheckMessage": "Kommunikation mit {0} nicht möglich.", - "DownloadClientRootFolderHealthCheckMessage": "Der Download-Client {0} legt Downloads im Stammordner {1} ab. Sie sollten nicht in einen Stammordner herunterladen.", - "DownloadClientSortingHealthCheckMessage": "Im Download-Client {0} ist die Sortierung {1} für die Kategorie von {appName} aktiviert. Sie sollten die Sortierung in Ihrem Download-Client deaktivieren, um Importprobleme zu vermeiden.", - "DownloadClientStatusSingleClientHealthCheckMessage": "Download-Clients sind aufgrund von Fehlern nicht verfügbar: {0}", + "DownloadClientCheckUnableToCommunicateWithHealthCheckMessage": "Kommunikation mit {downloadClientName} nicht möglich.", + "DownloadClientRootFolderHealthCheckMessage": "Der Download-Client {downloadClientName} legt Downloads im Stammordner {rootFolderPath} ab. Sie sollten nicht in einen Stammordner herunterladen.", + "DownloadClientSortingHealthCheckMessage": "Im Download-Client {downloadClientName} ist die Sortierung {sortingMode} für die Kategorie von {appName} aktiviert. Sie sollten die Sortierung in Ihrem Download-Client deaktivieren, um Importprobleme zu vermeiden.", + "DownloadClientStatusSingleClientHealthCheckMessage": "Download-Clients sind aufgrund von Fehlern nicht verfügbar: {downloadClientNames}", "DownloadClientStatusAllClientHealthCheckMessage": "Alle Download-Clients sind aufgrund von Fehlern nicht verfügbar", "EditSelectedDownloadClients": "Ausgewählte Download Clienten bearbeiten", "EditSelectedImportLists": "Ausgewählte Einspiel-Liste bearbeten", @@ -22,10 +22,10 @@ "Language": "Sprache", "CloneCondition": "Bedingung klonen", "DeleteCondition": "Bedingung löschen", - "DeleteConditionMessageText": "Bist du sicher, dass du die Bedingung '{0}' löschen willst?", - "DeleteCustomFormatMessageText": "Bist du sicher, dass du das eigene Format '{0}' löschen willst?", + "DeleteConditionMessageText": "Bist du sicher, dass du die Bedingung '{name}' löschen willst?", + "DeleteCustomFormatMessageText": "Bist du sicher, dass du das eigene Format '{customFormatName}' löschen willst?", "RemoveSelectedItemQueueMessageText": "Bist du sicher, dass du ein Eintrag aus der Warteschlange entfernen willst?", - "RemoveSelectedItemsQueueMessageText": "Bist du sicher, dass du {0} Einträge aus der Warteschlange entfernen willst?", + "RemoveSelectedItemsQueueMessageText": "Bist du sicher, dass du {selectedCount} Einträge aus der Warteschlange entfernen willst?", "DeleteSelectedDownloadClients": "Lösche Download Client(s)", "DeleteSelectedIndexers": "Lösche Indexer", "DeleteSelectedImportLists": "Lösche Einspiel Liste", diff --git a/src/NzbDrone.Core/Localization/Core/el.json b/src/NzbDrone.Core/Localization/Core/el.json index 7bfb1c9ff..d7f64dbef 100644 --- a/src/NzbDrone.Core/Localization/Core/el.json +++ b/src/NzbDrone.Core/Localization/Core/el.json @@ -1,6 +1,6 @@ { "AppDataLocationHealthCheckMessage": "Η ενημέρωση δεν θα είναι δυνατή για να αποτραπεί η διαγραφή των δεδομένων εφαρμογής κατά την ενημέρωση", - "ApiKeyValidationHealthCheckMessage": "Παρακαλούμε ενημερώστε το κλείδι API ώστε να έχει τουλάχιστον {0} χαρακτήρες. Μπορείτε να το κάνετε αυτό μέσα από τις ρυθμίσεις ή το αρχείο ρυθμίσεων", + "ApiKeyValidationHealthCheckMessage": "Παρακαλούμε ενημερώστε το κλείδι API ώστε να έχει τουλάχιστον {length} χαρακτήρες. Μπορείτε να το κάνετε αυτό μέσα από τις ρυθμίσεις ή το αρχείο ρυθμίσεων", "Added": "Προστέθηκε", "ApplyChanges": "Εφαρμογή Αλλαγών", "AutomaticAdd": "Αυτόματη Προσθήκη", @@ -10,9 +10,9 @@ "RemoveCompletedDownloads": "Αφαίρεση Ολοκληρωμένων Λήψεων", "RemoveFailedDownloads": "Αφαίρεση Αποτυχημένων Λήψεων", "DeleteCondition": "Διαγραφή συνθήκης", - "DeleteConditionMessageText": "Είστε σίγουροι πως θέλετε να διαγράψετε τη συνθήκη '{0}';", - "DeleteCustomFormatMessageText": "Είστε σίγουροι πως θέλετε να διαγράψετε τη προσαρμοσμένη μορφή '{0}';", - "RemoveSelectedItemsQueueMessageText": "Είστε σίγουροι πως θέλετε να διαγράψετε {0} αντικείμενα από την ουρά;", + "DeleteConditionMessageText": "Είστε σίγουροι πως θέλετε να διαγράψετε τη συνθήκη '{name}';", + "DeleteCustomFormatMessageText": "Είστε σίγουροι πως θέλετε να διαγράψετε τη προσαρμοσμένη μορφή '{customFormatName}';", + "RemoveSelectedItemsQueueMessageText": "Είστε σίγουροι πως θέλετε να διαγράψετε {selectedCount} αντικείμενα από την ουρά;", "CloneCondition": "Κλωνοποίηση συνθήκης", "RemoveSelectedItemQueueMessageText": "Είστε σίγουροι πως θέλετε να διαγράψετε 1 αντικείμενο από την ουρά;", "AddConditionImplementation": "Προσθήκη", diff --git a/src/NzbDrone.Core/Localization/Core/en.json b/src/NzbDrone.Core/Localization/Core/en.json index 9601f4dd1..f772d5047 100644 --- a/src/NzbDrone.Core/Localization/Core/en.json +++ b/src/NzbDrone.Core/Localization/Core/en.json @@ -85,7 +85,7 @@ "AnimeTypeFormat": "Absolute episode number ({format})", "Any": "Any", "ApiKey": "API Key", - "ApiKeyValidationHealthCheckMessage": "Please update your API key to be at least {0} characters long. You can do this via settings or the config file", + "ApiKeyValidationHealthCheckMessage": "Please update your API key to be at least {length} characters long. You can do this via settings or the config file", "AppDataDirectory": "AppData directory", "AppDataLocationHealthCheckMessage": "Updating will not be possible to prevent deleting AppData on Update", "AppUpdated": "{appName} Updated", @@ -284,7 +284,7 @@ "DeleteCondition": "Delete Condition", "DeleteConditionMessageText": "Are you sure you want to delete the condition '{name}'?", "DeleteCustomFormat": "Delete Custom Format", - "DeleteCustomFormatMessageText": "Are you sure you want to delete the custom format '{0}'?", + "DeleteCustomFormatMessageText": "Are you sure you want to delete the custom format '{customFormatName}'?", "DeleteDelayProfile": "Delete Delay Profile", "DeleteDelayProfileMessageText": "Are you sure you want to delete this delay profile?", "DeleteDownloadClient": "Delete Download Client", @@ -359,14 +359,14 @@ "Download": "Download", "DownloadClient": "Download Client", "DownloadClientCheckNoneAvailableHealthCheckMessage": "No download client is available", - "DownloadClientCheckUnableToCommunicateWithHealthCheckMessage": "Unable to communicate with {0}.", + "DownloadClientCheckUnableToCommunicateWithHealthCheckMessage": "Unable to communicate with {downloadClientName}. {errorMessage}", "DownloadClientOptionsLoadError": "Unable to load download client options", - "DownloadClientRemovesCompletedDownloadsHealthCheckMessage": "Download client {0} is set to remove completed downloads. This can result in downloads being removed from your client before {1} can import them.", - "DownloadClientRootFolderHealthCheckMessage": "Download client {0} places downloads in the root folder {1}. You should not download to a root folder.", + "DownloadClientRemovesCompletedDownloadsHealthCheckMessage": "Download client {downloadClientName} is set to remove completed downloads. This can result in downloads being removed from your client before {appName} can import them.", + "DownloadClientRootFolderHealthCheckMessage": "Download client {downloadClientName} places downloads in the root folder {rootFolderPath}. You should not download to a root folder.", "DownloadClientSettings": "Download Client Settings", - "DownloadClientSortingHealthCheckMessage": "Download client {0} has {1} sorting enabled for {appName}'s category. You should disable sorting in your download client to avoid import issues.", + "DownloadClientSortingHealthCheckMessage": "Download client {downloadClientName} has {sortingMode} sorting enabled for {appName}'s category. You should disable sorting in your download client to avoid import issues.", "DownloadClientStatusAllClientHealthCheckMessage": "All download clients are unavailable due to failures", - "DownloadClientStatusSingleClientHealthCheckMessage": "Download clients unavailable due to failures: {0}", + "DownloadClientStatusSingleClientHealthCheckMessage": "Download clients unavailable due to failures: {downloadClientNames}", "DownloadClientTagHelpText": "Only use this download client for series with at least one matching tag. Leave blank to use with all series.", "DownloadClients": "Download Clients", "DownloadClientsLoadError": "Unable to load download clients", @@ -613,13 +613,13 @@ "ImportList": "Import List", "ImportListExclusions": "Import List Exclusions", "ImportListExclusionsLoadError": "Unable to load Import List Exclusions", - "ImportListRootFolderMissingRootHealthCheckMessage": "Missing root folder for import list(s): {0}", - "ImportListRootFolderMultipleMissingRootsHealthCheckMessage": "Multiple root folders are missing for import lists: {0}", + "ImportListRootFolderMissingRootHealthCheckMessage": "Missing root folder for import list(s): {rootFolderInfo}", + "ImportListRootFolderMultipleMissingRootsHealthCheckMessage": "Multiple root folders are missing for import lists: {rootFolderInfo}", "ImportListSearchForMissingEpisodes": "Search for Missing Episodes", "ImportListSearchForMissingEpisodesHelpText": "After series is added to {appName} automatically search for missing episodes", "ImportListSettings": "Import List Settings", "ImportListStatusAllUnavailableHealthCheckMessage": "All lists are unavailable due to failures", - "ImportListStatusUnavailableHealthCheckMessage": "Lists unavailable due to failures: {0}", + "ImportListStatusUnavailableHealthCheckMessage": "Lists unavailable due to failures: {importListNames}", "ImportLists": "Import Lists", "ImportListsLoadError": "Unable to load Import Lists", "ImportListsSettingsSummary": "Import from another {appName} instance or Trakt lists and manage list exclusions", @@ -639,11 +639,11 @@ "IncludeHealthWarnings": "Include Health Warnings", "IncludeUnmonitored": "Include Unmonitored", "Indexer": "Indexer", - "IndexerDownloadClientHealthCheckMessage": "Indexers with invalid download clients: {0}.", + "IndexerDownloadClientHealthCheckMessage": "Indexers with invalid download clients: {indexerNames}.", "IndexerDownloadClientHelpText": "Specify which download client is used for grabs from this indexer", - "IndexerJackettAllHealthCheckMessage": "Indexers using the unsupported Jackett 'all' endpoint: {0}", + "IndexerJackettAllHealthCheckMessage": "Indexers using the unsupported Jackett 'all' endpoint: {indexerNames}", "IndexerLongTermStatusAllUnavailableHealthCheckMessage": "All indexers are unavailable due to failures for more than 6 hours", - "IndexerLongTermStatusUnavailableHealthCheckMessage": "Indexers unavailable due to failures for more than 6 hours: {0}", + "IndexerLongTermStatusUnavailableHealthCheckMessage": "Indexers unavailable due to failures for more than 6 hours: {indexerNames}", "IndexerOptionsLoadError": "Unable to load indexer options", "IndexerPriority": "Indexer Priority", "IndexerPriorityHelpText": "Indexer Priority from 1 (Highest) to 50 (Lowest). Default: 25. Used when grabbing releases as a tiebreaker for otherwise equal releases, {appName} will still use all enabled indexers for RSS Sync and Searching", @@ -654,7 +654,7 @@ "IndexerSearchNoInteractiveHealthCheckMessage": "No indexers available with Interactive Search enabled, {appName} will not provide any interactive search results", "IndexerSettings": "Indexer Settings", "IndexerStatusAllUnavailableHealthCheckMessage": "All indexers are unavailable due to failures", - "IndexerStatusUnavailableHealthCheckMessage": "Indexers unavailable due to failures: {0}", + "IndexerStatusUnavailableHealthCheckMessage": "Indexers unavailable due to failures: {indexerNames}", "IndexerTagHelpText": "Only use this indexer for series with at least one matching tag. Leave blank to use with all series.", "Indexers": "Indexers", "IndexersLoadError": "Unable to load Indexers", @@ -882,7 +882,7 @@ "None": "None", "NotSeasonPack": "Not Season Pack", "NotificationStatusAllClientHealthCheckMessage": "All notifications are unavailable due to failures", - "NotificationStatusSingleClientHealthCheckMessage": "Notifications unavailable due to failures: {0}", + "NotificationStatusSingleClientHealthCheckMessage": "Notifications unavailable due to failures: {notificationNames}", "NotificationTriggers": "Notification Triggers", "NotificationTriggersHelpText": "Select which events should trigger this notification", "NotificationsLoadError": "Unable to load Notifications", @@ -985,11 +985,11 @@ "Protocol": "Protocol", "ProtocolHelpText": "Choose which protocol(s) to use and which one is preferred when choosing between otherwise equal releases", "Proxy": "Proxy", - "ProxyBadRequestHealthCheckMessage": "Failed to test proxy. Status Code: {0}", + "ProxyBadRequestHealthCheckMessage": "Failed to test proxy. Status Code: {statusCode}", "ProxyBypassFilterHelpText": "Use ',' as a separator, and '*.' as a wildcard for subdomains", - "ProxyFailedToTestHealthCheckMessage": "Failed to test proxy: {0}", + "ProxyFailedToTestHealthCheckMessage": "Failed to test proxy: {url}", "ProxyPasswordHelpText": "You only need to enter a username and password if one is required. Leave them blank otherwise.", - "ProxyResolveIpHealthCheckMessage": "Failed to resolve the IP Address for the Configured Proxy Host {0}", + "ProxyResolveIpHealthCheckMessage": "Failed to resolve the IP Address for the Configured Proxy Host {proxyHostName}", "ProxyType": "Proxy Type", "ProxyUsernameHelpText": "You only need to enter a username and password if one is required. Leave them blank otherwise.", "PublishedDate": "Published Date", @@ -1018,7 +1018,7 @@ "Real": "Real", "Reason": "Reason", "RecentChanges": "Recent Changes", - "RecycleBinUnableToWriteHealthCheckMessage": "Unable to write to configured recycling bin folder: {0}. Ensure this path exists and is writable by the user running {appName}", + "RecycleBinUnableToWriteHealthCheckMessage": "Unable to write to configured recycling bin folder: {path}. Ensure this path exists and is writable by the user running {appName}", "RecyclingBin": "Recycling Bin", "RecyclingBinCleanup": "Recycling Bin Cleanup", "RecyclingBinCleanupHelpText": "Set to 0 to disable automatic cleanup", @@ -1054,24 +1054,24 @@ "ReleaseTitle": "Release Title", "Reload": "Reload", "RemotePath": "Remote Path", - "RemotePathMappingBadDockerPathHealthCheckMessage": "You are using docker; download client {0} places downloads in {1} but this is not a valid {2} path. Review your remote path mappings and download client settings.", - "RemotePathMappingDockerFolderMissingHealthCheckMessage": "You are using docker; download client {0} places downloads in {1} but this directory does not appear to exist inside the container. Review your remote path mappings and container volume settings.", - "RemotePathMappingDownloadPermissionsHealthCheckMessage": "{appName} can see but not access downloaded episode {0}. Likely permissions error.", - "RemotePathMappingFileRemovedHealthCheckMessage": "File {0} was removed part way through processing.", - "RemotePathMappingFilesBadDockerPathHealthCheckMessage": "You are using docker; download client {0} reported files in {1} but this is not a valid {2} path. Review your remote path mappings and download client settings.", - "RemotePathMappingFilesGenericPermissionsHealthCheckMessage": "Download client {0} reported files in {1} but {appName} cannot see this directory. You may need to adjust the folder's permissions.", - "RemotePathMappingFilesLocalWrongOSPathHealthCheckMessage": "Local download client {0} reported files in {1} but this is not a valid {2} path. Review your download client settings.", - "RemotePathMappingFilesWrongOSPathHealthCheckMessage": "Remote download client {0} reported files in {1} but this is not a valid {2} path. Review your remote path mappings and download client settings.", - "RemotePathMappingFolderPermissionsHealthCheckMessage": "{appName} can see but not access download directory {0}. Likely permissions error.", - "RemotePathMappingGenericPermissionsHealthCheckMessage": "Download client {0} places downloads in {1} but {appName} cannot see this directory. You may need to adjust the folder's permissions.", + "RemotePathMappingBadDockerPathHealthCheckMessage": "You are using docker; download client {downloadClientName} places downloads in {path} but this is not a valid {osName} path. Review your remote path mappings and download client settings.", + "RemotePathMappingDockerFolderMissingHealthCheckMessage": "You are using docker; download client $1{downloadClientName} places downloads in {path} but this directory does not appear to exist inside the container. Review your remote path mappings and container volume settings.", + "RemotePathMappingDownloadPermissionsHealthCheckMessage": "{appName} can see but not access downloaded episode {path}. Likely permissions error.", + "RemotePathMappingFileRemovedHealthCheckMessage": "File {path} was removed part way through processing.", + "RemotePathMappingFilesBadDockerPathHealthCheckMessage": "You are using docker; download client {downloadClientName} reported files in {path} but this is not a valid {osName} path. Review your remote path mappings and download client settings.", + "RemotePathMappingFilesGenericPermissionsHealthCheckMessage": "Download client {downloadClientName} reported files in {path} but {appName} cannot see this directory. You may need to adjust the folder's permissions.", + "RemotePathMappingFilesLocalWrongOSPathHealthCheckMessage": "Local download client {downloadClientName} reported files in {path} but this is not a valid {osName} path. Review your download client settings.", + "RemotePathMappingFilesWrongOSPathHealthCheckMessage": "Remote download client {downloadClientName} reported files in {path} but this is not a valid {osName} path. Review your remote path mappings and download client settings.", + "RemotePathMappingFolderPermissionsHealthCheckMessage": "{appName} can see but not access download directory {downloadPath}. Likely permissions error.", + "RemotePathMappingGenericPermissionsHealthCheckMessage": "Download client {downloadClientName} places downloads in {path} but {appName} cannot see this directory. You may need to adjust the folder's permissions.", "RemotePathMappingHostHelpText": "The same host you specified for the remote Download Client", "RemotePathMappingImportFailedHealthCheckMessage": "{appName} failed to import (an) episode(s). Check your logs for details.", - "RemotePathMappingLocalFolderMissingHealthCheckMessage": "Remote download client {0} places downloads in {1} but this directory does not appear to exist. Likely missing or incorrect remote path mapping.", + "RemotePathMappingLocalFolderMissingHealthCheckMessage": "Remote download client {downloadClientName} places downloads in {path} but this directory does not appear to exist. Likely missing or incorrect remote path mapping.", "RemotePathMappingLocalPathHelpText": "Path that {appName} should use to access the remote path locally", - "RemotePathMappingLocalWrongOSPathHealthCheckMessage": "Local download client {0} places downloads in {1} but this is not a valid {2} path. Review your download client settings.", - "RemotePathMappingRemoteDownloadClientHealthCheckMessage": "Remote download client {0} reported files in {1} but this directory does not appear to exist. Likely missing remote path mapping.", + "RemotePathMappingLocalWrongOSPathHealthCheckMessage": "Local download client {downloadClientName} places downloads in {path} but this is not a valid {osName} path. Review your download client settings.", + "RemotePathMappingRemoteDownloadClientHealthCheckMessage": "Remote download client {downloadClientName} reported files in {path} but this directory does not appear to exist. Likely missing remote path mapping.", "RemotePathMappingRemotePathHelpText": "Root path to the directory that the Download Client accesses", - "RemotePathMappingWrongOSPathHealthCheckMessage": "Remote download client {0} places downloads in {1} but this is not a valid {2} path. Review your remote path mappings and download client settings.", + "RemotePathMappingWrongOSPathHealthCheckMessage": "Remote download client {downloadClientName} places downloads in {path} but this is not a valid {osName} path. Review your remote path mappings and download client settings.", "RemotePathMappings": "Remote Path Mappings", "RemotePathMappingsInfo": "Remote Path Mappings are very rarely required, if {app} and your download client are on the same system it is better to match your paths. For more information see the [wiki]({wikiLink})", "RemotePathMappingsLoadError": "Unable to load Remote Path Mappings", @@ -1100,8 +1100,8 @@ "RemoveTagsAutomatically": "Remove Tags Automatically", "RemoveTagsAutomaticallyHelpText": "Remove tags automatically if conditions are not met", "RemovedFromTaskQueue": "Removed from task queue", - "RemovedSeriesMultipleRemovedHealthCheckMessage": "Series {0} were removed from TheTVDB", - "RemovedSeriesSingleRemovedHealthCheckMessage": "Series {0} was removed from TheTVDB", + "RemovedSeriesMultipleRemovedHealthCheckMessage": "Series {series} were removed from TheTVDB", + "RemovedSeriesSingleRemovedHealthCheckMessage": "Series {series} was removed from TheTVDB", "RemovingTag": "Removing tag", "RenameEpisodes": "Rename Episodes", "RenameEpisodesHelpText": "{appName} will use the existing file name if renaming is disabled", @@ -1146,8 +1146,8 @@ "RetryingDownloadOn": "Retrying download on {date} at {time}", "RootFolder": "Root Folder", "RootFolderLoadError": "Unable to add root folder", - "RootFolderMissingHealthCheckMessage": "Missing root folder: {0}", - "RootFolderMultipleMissingHealthCheckMessage": "Multiple root folders are missing: {0}", + "RootFolderMissingHealthCheckMessage": "Missing root folder: {rootFolderPath}", + "RootFolderMultipleMissingHealthCheckMessage": "Multiple root folders are missing: {rootFolderPaths}", "RootFolderPath": "Root Folder Path", "RootFolderSelectFreeSpace": "{freeSpace} Free", "RootFolders": "Root Folders", @@ -1438,10 +1438,9 @@ "UpdateScriptPathHelpText": "Path to a custom script that takes an extracted update package and handle the remainder of the update process", "UpdateSelected": "Update Selected", "UpdateSonarrDirectlyLoadError": "Unable to update {appName} directly,", - "UpdateStartupNotWritableHealthCheckMessage": "Cannot install update because startup folder '{0}' is not writable by the user '{1}'.", - "UpdateStartupTranslocationHealthCheckMessage": "Cannot install update because startup folder '{0}' is in an App Translocation folder.", - "UpdateUINotWritableHealthCheckMessage": "Cannot install update because UI folder '{0}' is not writable by the user '{1}'.", - "UpdateUiNotWritableHealthCheckMessage": "Cannot install update because UI folder '{0}' is not writable by the user '{1}'.", + "UpdateStartupNotWritableHealthCheckMessage": "Cannot install update because startup folder '{startupFolder}' is not writable by the user '{userName}'.", + "UpdateStartupTranslocationHealthCheckMessage": "Cannot install update because startup folder '{ }' is in an App Translocation folder.", + "UpdateUiNotWritableHealthCheckMessage": "Cannot install update because UI folder '{uiFolder}' is not writable by the user '{userName}'.", "UpdaterLogFiles": "Updater Log Files", "Updates": "Updates", "UpgradeUntil": "Upgrade Until", diff --git a/src/NzbDrone.Core/Localization/Core/es.json b/src/NzbDrone.Core/Localization/Core/es.json index e552fd212..e2ffc7bd9 100644 --- a/src/NzbDrone.Core/Localization/Core/es.json +++ b/src/NzbDrone.Core/Localization/Core/es.json @@ -167,7 +167,7 @@ "AllResultsAreHiddenByTheAppliedFilter": "Todos los resultados están ocultos por el filtro aplicado", "AnalyseVideoFilesHelpText": "Extraer información de video como la resolución, el tiempo de ejecución y la información del códec de los archivos. Esto requiere que {appName} lea partes del archivo lo cual puede causar una alta actividad en el disco o en la red durante los escaneos.", "AnimeTypeDescription": "Episodios lanzados usando un número de episodio absoluto", - "ApiKeyValidationHealthCheckMessage": "Actualice su clave de API para que tenga al menos {0} carácteres. Puede hacerlo en los ajustes o en el archivo de configuración", + "ApiKeyValidationHealthCheckMessage": "Actualice su clave de API para que tenga al menos {length} carácteres. Puede hacerlo en los ajustes o en el archivo de configuración", "AppDataLocationHealthCheckMessage": "No será posible actualizar para prevenir la eliminación de AppData al Actualizar", "Scheduled": "Programado", "Season": "Temporada", @@ -209,7 +209,7 @@ "ManageImportLists": "Gestionar Listas de Importación", "ManageDownloadClients": "Gestionar Clientes de Descarga", "MoveAutomatically": "Mover Automáticamente", - "IndexerDownloadClientHealthCheckMessage": "Indexadores con clientes de descarga inválidos: {0}.", + "IndexerDownloadClientHealthCheckMessage": "Indexadores con clientes de descarga inválidos: {indexerNames}.", "ManageLists": "Gestionar Listas", "DeleteSelectedImportLists": "Eliminar Lista(s) de Importación", "EditSelectedIndexers": "Editar Indexadores Seleccionados", diff --git a/src/NzbDrone.Core/Localization/Core/fi.json b/src/NzbDrone.Core/Localization/Core/fi.json index 156195c8f..1cd0454c8 100644 --- a/src/NzbDrone.Core/Localization/Core/fi.json +++ b/src/NzbDrone.Core/Localization/Core/fi.json @@ -1,16 +1,16 @@ { "BlocklistReleaseHelpText": "Etsii kohdetta uudelleen ja estää {appName}ia sieppaamasta tätä julkaisua automaattisesti uudelleen.", - "RecycleBinUnableToWriteHealthCheckMessage": "Määritettyyn roskakorikansioon ei voida tallentaa: {0}. Varmista että sijainti on olemassa ja että sovelluksen suorittavalla käyttäjällä on siihen kirjoitusoikeus.", - "RemotePathMappingDownloadPermissionsHealthCheckMessage": "{appName} näkee ladatun jakson \"{0}\", muttei voi käyttää sitä. Tämä johtuu todennäköisesti liian rajallisista käyttöoikeuksista.", + "RecycleBinUnableToWriteHealthCheckMessage": "Määritettyyn roskakorikansioon ei voida tallentaa: {path}. Varmista että sijainti on olemassa ja että sovelluksen suorittavalla käyttäjällä on siihen kirjoitusoikeus.", + "RemotePathMappingDownloadPermissionsHealthCheckMessage": "{appName} näkee ladatun jakson \"{path}\", muttei voi käyttää sitä. Tämä johtuu todennäköisesti liian rajallisista käyttöoikeuksista.", "Added": "Lisätty", "AppDataLocationHealthCheckMessage": "Päivitystä ei sallita, jotta AppData-kansion poisto päivityksen yhteydessä voidaan estää.", "DownloadClientSortingHealthCheckMessage": "", "IndexerRssNoIndexersEnabledHealthCheckMessage": "RSS-synkronointia käyttäviä tietolähteitä ei ole määritetty, jonka vuoksi uusia julkaisuja ei siepata automaattisesti.", "IndexerSearchNoInteractiveHealthCheckMessage": "Manuaalista hakua varten ei ole määritetty tietolähteitä, eikä manuaalinen haku sen vuoksi löydä tuloksia.", - "RemotePathMappingFilesGenericPermissionsHealthCheckMessage": "Lataustyökalu \"{0}\" ilmoitti tiedostosijainniksi \"{1}\", mutta {appName} ei näe kansiota. Tämä johtuu todennäköisesti liian rajallisista käyttöoikeuksista.", - "RemotePathMappingFolderPermissionsHealthCheckMessage": "{appName} näkee ladatauskansion \"{0}\" näkyy, muttei voi käyttää sitä. Tämä johtuu todennäköisesti liian rajallisista käyttöoikeuksista.", + "RemotePathMappingFilesGenericPermissionsHealthCheckMessage": "Lataustyökalu \"{downloadClientName}\" ilmoitti tiedostosijainniksi \"{path}\", mutta {appName} ei näe kansiota. Tämä johtuu todennäköisesti liian rajallisista käyttöoikeuksista.", + "RemotePathMappingFolderPermissionsHealthCheckMessage": "{appName} näkee ladatauskansion \"{downloadPath}\" näkyy, muttei voi käyttää sitä. Tämä johtuu todennäköisesti liian rajallisista käyttöoikeuksista.", "RemotePathMappingImportFailedHealthCheckMessage": "Jaksojen tuonti epäonnistui. Katso tarkemmat tiedot lokista.", - "RemotePathMappingGenericPermissionsHealthCheckMessage": "Lataustyökalu \"{0}\" tallentaa latauksen sijaintiin \"{1}\", mutta {appName} ei näe sitä. Tämä johtuu todennäköisesti liian rajallisista käyttöoikeuksista.", + "RemotePathMappingGenericPermissionsHealthCheckMessage": "Lataustyökalu \"{downloadClientName}\" tallentaa latauksen sijaintiin \"{path}\", mutta {appName} ei näe sitä. Tämä johtuu todennäköisesti liian rajallisista käyttöoikeuksista.", "IndexerSearchNoAutomaticHealthCheckMessage": "Automaattista hakua varten ei ole määritetty tietolähteitä, eikä automaattinen haku sen vuoksi löydä tuloksia.", "AgeWhenGrabbed": "Ikä (sieppaushetkellä)", "GrabId": "Sieppaustunniste", @@ -24,7 +24,7 @@ "GrabRelease": "Sieppaa julkaisu", "Hostname": "Osoite", "OriginalLanguage": "Alkuperäinen kieli", - "ProxyResolveIpHealthCheckMessage": "Määritetyn välityspalvelimen \"{0}\" IP-osoitteen selvitys epäonnistui.", + "ProxyResolveIpHealthCheckMessage": "Määritetyn välityspalvelimen \"{proxyHostName}\" IP-osoitteen selvitys epäonnistui.", "SetPermissionsLinuxHelpText": "Tulisiko chmod suorittaa, kun tiedostoja tuodaan/nimetään uudelleen?", "UrlBaseHelpText": "Käänteisen välityspalvelimen tuki (esim. \"http://[host]:[port]/[urlBase]\"). Käytä oletusta jättämällä tyhjäksi.", "SetPermissionsLinuxHelpTextWarning": "Jollet ole varma mitä nämä asetukset tekevät, älä muuta niitä.", diff --git a/src/NzbDrone.Core/Localization/Core/fr.json b/src/NzbDrone.Core/Localization/Core/fr.json index 7cfbce259..223c4d01f 100644 --- a/src/NzbDrone.Core/Localization/Core/fr.json +++ b/src/NzbDrone.Core/Localization/Core/fr.json @@ -2,7 +2,7 @@ "Language": "Langue", "UiLanguage": "UI Langue", "Added": "Ajouté", - "ApiKeyValidationHealthCheckMessage": "Veuillez mettre à jour votre clé API pour qu'elle contienne au moins {0} caractères. Vous pouvez le faire via les paramètres ou le fichier de configuration", + "ApiKeyValidationHealthCheckMessage": "Veuillez mettre à jour votre clé API pour qu'elle contienne au moins {length} caractères. Vous pouvez le faire via les paramètres ou le fichier de configuration", "AppDataLocationHealthCheckMessage": "La mise à jour ne sera pas possible afin empêcher la suppression de AppData lors de la mise à jour", "ApplyChanges": "Appliquer les modifications", "AutomaticAdd": "Ajout automatique", @@ -159,8 +159,8 @@ "AutomaticUpdatesDisabledDocker": "Les mises à jour automatiques ne sont pas directement prises en charge lors de l'utilisation du mécanisme de mise à jour de Docker. Vous devrez mettre à jour l'image du conteneur en dehors de {appName} ou utiliser un script", "BackupRetentionHelpText": "Les sauvegardes automatiques plus anciennes que la période de rétention seront nettoyées automatiquement", "QualityProfile": "Profil de qualité", - "RemotePathMappingDownloadPermissionsHealthCheckMessage": "Sonarr peut voir mais ne peut pas accéder à l'épisode téléchargé {0}. Probablement une erreur de permissions.", - "RemotePathMappingDockerFolderMissingHealthCheckMessage": "Vous utilisez Docker ; le client de téléchargement {0} place les téléchargements dans {1}, mais ce répertoire ne semble pas exister dans le conteneur. Vérifiez vos mappages de chemins d'accès distants et les paramètres de volume du conteneur.", + "RemotePathMappingDownloadPermissionsHealthCheckMessage": "Sonarr peut voir mais ne peut pas accéder à l'épisode téléchargé {path}. Probablement une erreur de permissions.", + "RemotePathMappingDockerFolderMissingHealthCheckMessage": "Vous utilisez Docker ; le client de téléchargement $1{downloadClientName} place les téléchargements dans {path}, mais ce répertoire ne semble pas exister dans le conteneur. Vérifiez vos mappages de chemins d'accès distants et les paramètres de volume du conteneur.", "BlocklistReleases": "Publications de la liste de blocage", "BindAddress": "Adresse de liaison", "BackupsLoadError": "Impossible de charger les sauvegardes", @@ -186,9 +186,9 @@ "ChownGroupHelpTextWarning": "Cela ne fonctionne que si l'utilisateur qui exécute sonarr est le propriétaire du fichier. Il est préférable de s'assurer que le client de téléchargement utilise le même groupe que sonarr.", "ClickToChangeQuality": "Cliquez pour changer la qualité", "RefreshSeries": "Actualiser les séries", - "RecycleBinUnableToWriteHealthCheckMessage": "Impossible d'écrire dans le dossier configuré de la corbeille de recyclage : {0}. Assurez-vous que ce chemin existe et qu'il est accessible en écriture par l'utilisateur qui exécute Sonarr.", - "RemotePathMappingFileRemovedHealthCheckMessage": "Le fichier {0} a été supprimé en cours de traitement.", - "RemotePathMappingFilesGenericPermissionsHealthCheckMessage": "Le client de téléchargement {0} a signalé des fichiers dans {1} mais Sonarr ne peut pas voir ce répertoire. Il se peut que vous deviez ajuster les permissions du dossier.", + "RecycleBinUnableToWriteHealthCheckMessage": "Impossible d'écrire dans le dossier configuré de la corbeille de recyclage : {path}. Assurez-vous que ce chemin existe et qu'il est accessible en écriture par l'utilisateur qui exécute Sonarr.", + "RemotePathMappingFileRemovedHealthCheckMessage": "Le fichier {path} a été supprimé en cours de traitement.", + "RemotePathMappingFilesGenericPermissionsHealthCheckMessage": "Le client de téléchargement {downloadClientName} a signalé des fichiers dans {path} mais Sonarr ne peut pas voir ce répertoire. Il se peut que vous deviez ajuster les permissions du dossier.", "CalendarFeed": "Flux de calendrier {appName}", "CalendarLegendDownloadedTooltip": "L'épisode a été téléchargé et classé", "CalendarLegendDownloadingTooltip": "L'épisode est en cours de téléchargement", @@ -203,13 +203,13 @@ "ClickToChangeLanguage": "Cliquez pour changer de langue", "ClickToChangeEpisode": "Cliquez pour changer d'épisode", "ClickToChangeReleaseGroup": "Cliquez pour changer de groupe de diffusion", - "RemotePathMappingFilesBadDockerPathHealthCheckMessage": "Vous utilisez Docker ; le client de téléchargement {0} a signalé des fichiers dans {1} mais ce n'est pas un chemin {2} valide. Vérifiez vos mappages de chemins d'accès distants et les paramètres du client de téléchargement.", + "RemotePathMappingFilesBadDockerPathHealthCheckMessage": "Vous utilisez Docker ; le client de téléchargement {downloadClientName} a signalé des fichiers dans {path} mais ce n'est pas un chemin {osName} valide. Vérifiez vos mappages de chemins d'accès distants et les paramètres du client de téléchargement.", "BypassDelayIfAboveCustomFormatScoreMinimumScoreHelpText": "Score minimum requis pour le format personnalisé pour ignorer le délai pour le protocole préféré", "BypassDelayIfHighestQualityHelpText": "Ignorer le délai lorsque la libération a la qualité activée la plus élevée dans le profil de qualité avec le protocole préféré", - "RemotePathMappingBadDockerPathHealthCheckMessage": "Vous utilisez Docker ; le client de téléchargement {0} place les téléchargements dans {1} mais ce n'est pas un chemin {2} valide. Revoyez vos mappages de chemins d'accès distants et les paramètres du client de téléchargement.", - "RemotePathMappingFilesLocalWrongOSPathHealthCheckMessage": "Le client de téléchargement local {0} a signalé des fichiers dans {1}, mais il ne s'agit pas d'un chemin {2} valide. Vérifiez les paramètres de votre client de téléchargement.", - "RemotePathMappingFilesWrongOSPathHealthCheckMessage": "Le client de téléchargement distant {0} a signalé des fichiers dans {1}, mais il ne s'agit pas d'un chemin {2} valide. Revoyez vos mappages de chemins d'accès distants et les paramètres du client de téléchargement.", - "RemotePathMappingFolderPermissionsHealthCheckMessage": "Sonarr peut voir mais ne peut pas accéder au répertoire de téléchargement {0}. Il s'agit probablement d'une erreur de permissions.", + "RemotePathMappingBadDockerPathHealthCheckMessage": "Vous utilisez Docker ; le client de téléchargement {downloadClientName} place les téléchargements dans {path} mais ce n'est pas un chemin {osName} valide. Revoyez vos mappages de chemins d'accès distants et les paramètres du client de téléchargement.", + "RemotePathMappingFilesLocalWrongOSPathHealthCheckMessage": "Le client de téléchargement local {downloadClientName} a signalé des fichiers dans {path}, mais il ne s'agit pas d'un chemin {osName} valide. Vérifiez les paramètres de votre client de téléchargement.", + "RemotePathMappingFilesWrongOSPathHealthCheckMessage": "Le client de téléchargement distant {downloadClientName} a signalé des fichiers dans {path}, mais il ne s'agit pas d'un chemin {osName} valide. Revoyez vos mappages de chemins d'accès distants et les paramètres du client de téléchargement.", + "RemotePathMappingFolderPermissionsHealthCheckMessage": "Sonarr peut voir mais ne peut pas accéder au répertoire de téléchargement {downloadPath}. Il s'agit probablement d'une erreur de permissions.", "Path": "Chemin", "QueueIsEmpty": "La file d'attente est vide", "Warn": "Avertissement", @@ -428,7 +428,7 @@ "ManageDownloadClients": "Gérer les clients de téléchargement", "NoDownloadClientsFound": "Aucun client de téléchargement n'a été trouvé", "NotificationStatusAllClientHealthCheckMessage": "Toutes les notifications sont indisponibles en raison de dysfonctionnements", - "NotificationStatusSingleClientHealthCheckMessage": "Notifications indisponibles en raison de dysfonctionnements : {0}", + "NotificationStatusSingleClientHealthCheckMessage": "Notifications indisponibles en raison de dysfonctionnements : {notificationNames}", "RecentChanges": "Changements récents", "SetTags": "Définir les étiquettes", "Replace": "Remplacer", diff --git a/src/NzbDrone.Core/Localization/Core/he.json b/src/NzbDrone.Core/Localization/Core/he.json index 17c5293b8..5cbee5d60 100644 --- a/src/NzbDrone.Core/Localization/Core/he.json +++ b/src/NzbDrone.Core/Localization/Core/he.json @@ -1,6 +1,6 @@ { "Added": "נוסף", - "ApiKeyValidationHealthCheckMessage": "עדכן בבקשה את מפתח ה־API שלך כדי שיהיה באורך של לפחות {0} תווים. תוכל לעשות זאת בהגדרות או דרך קובץ הקונפיגורציה.", + "ApiKeyValidationHealthCheckMessage": "עדכן בבקשה את מפתח ה־API שלך כדי שיהיה באורך של לפחות {length} תווים. תוכל לעשות זאת בהגדרות או דרך קובץ הקונפיגורציה.", "Add": "הוסף", "Activity": "פעילות", "Indexer": "אינדקסר", diff --git a/src/NzbDrone.Core/Localization/Core/hu.json b/src/NzbDrone.Core/Localization/Core/hu.json index 49ff8b7ac..678244844 100644 --- a/src/NzbDrone.Core/Localization/Core/hu.json +++ b/src/NzbDrone.Core/Localization/Core/hu.json @@ -5,56 +5,56 @@ "Close": "Bezárás", "Delete": "Törlés", "DeleteCondition": "Feltétel törlése", - "DeleteConditionMessageText": "Biztosan törölni akarod a '{0}' feltételt?", + "DeleteConditionMessageText": "Biztosan törölni akarod a '{name}' feltételt?", "DeleteCustomFormat": "Egyéni formátum törlése", - "DeleteCustomFormatMessageText": "Biztosan törölni akarod a/az '{0}' egyéni formátumot?", + "DeleteCustomFormatMessageText": "Biztosan törölni akarod a/az '{customFormatName}' egyéni formátumot?", "ExportCustomFormat": "Egyéni formátum exportálása", - "IndexerJackettAllHealthCheckMessage": "A nem támogatott Jackett 'all' végpontot használó indexelők: {0}", + "IndexerJackettAllHealthCheckMessage": "A nem támogatott Jackett 'all' végpontot használó indexelők: {indexerNames}", "Remove": "Eltávolítás", "RemoveFromDownloadClient": "Eltávolítás a letöltési kliensből", "RemoveFromDownloadClientHelpTextWarning": "A törlés eltávolítja a letöltést és a fájl(okat) a letöltési kliensből.", "RemoveSelectedItem": "Kijelölt elem eltávolítása", "RemoveSelectedItemQueueMessageText": "Biztosan el akar távolítani 1 elemet a várólistáról?", "RemoveSelectedItems": "Kijelölt elemek eltávolítása", - "RemoveSelectedItemsQueueMessageText": "Biztosan el akar távolítani {0} elemet a várólistáról?", + "RemoveSelectedItemsQueueMessageText": "Biztosan el akar távolítani {selectedCount} elemet a várólistáról?", "Required": "Kötelező", "Added": "Hozzáadva", - "ApiKeyValidationHealthCheckMessage": "Kérlek frissítsd az API kulcsot, ami legalább {0} karakter hosszú. Ezt megteheted a Beállításokban, vagy a config file-ban", + "ApiKeyValidationHealthCheckMessage": "Kérlek frissítsd az API kulcsot, ami legalább {length} karakter hosszú. Ezt megteheted a Beállításokban, vagy a config file-ban", "ApplyChanges": "Változások alkalmazása", "AppDataLocationHealthCheckMessage": "A frissítés nem lehetséges az alkalmazás adatok törlése nélkül", "AutomaticAdd": "Automatikus hozzáadás", "CountSeasons": "{count} évad", "DownloadClientCheckNoneAvailableHealthCheckMessage": "Nincs elérhető letöltési kliens", - "DownloadClientRootFolderHealthCheckMessage": "A letöltési kliens {0} a letöltéseket a gyökérmappába helyezi. Ne tölts le közvetlenül a gyökérmappába.", - "DownloadClientCheckUnableToCommunicateWithHealthCheckMessage": "Nem lehet kommunikálni a {0} -val.", + "DownloadClientRootFolderHealthCheckMessage": "A letöltési kliens {downloadClientName} a letöltéseket a gyökérmappába helyezi. Ne tölts le közvetlenül a gyökérmappába.", + "DownloadClientCheckUnableToCommunicateWithHealthCheckMessage": "Nem lehet kommunikálni a {downloadClientName} -val.", "DownloadClientStatusAllClientHealthCheckMessage": "Az összes letöltési kliens elérhetetlen meghibásodások miatt", "EditSelectedDownloadClients": "Kiválasztott letöltési kliensek szerkesztése", "EditSelectedImportLists": "Kiválasztott import listák szerkesztése", "EditSelectedIndexers": "Kiválasztott indexelők szerkesztése", - "DownloadClientStatusSingleClientHealthCheckMessage": "Letöltési kliensek elérhetetlenek meghibásodások miatt: {0}", + "DownloadClientStatusSingleClientHealthCheckMessage": "Letöltési kliensek elérhetetlenek meghibásodások miatt: {downloadClientNames}", "EnableAutomaticSearch": "Automatikus keresés engedélyezése", "EditSeries": "Sorozat szerkesztése", "EnableInteractiveSearch": "Interaktív keresés engedélyezése", "Ended": "Vége", "HideAdvanced": "Haladó elrejtése", - "ImportListRootFolderMissingRootHealthCheckMessage": "Hiányzó gyökérmappa a/az {0} importálási listához", - "ImportListRootFolderMultipleMissingRootsHealthCheckMessage": "Több gyökérmappa hiányzik a/az {0} importálási listához", + "ImportListRootFolderMissingRootHealthCheckMessage": "Hiányzó gyökérmappa a/az {rootFolderInfo} importálási listához", + "ImportListRootFolderMultipleMissingRootsHealthCheckMessage": "Több gyökérmappa hiányzik a/az {rootFoldersInfo} importálási listához", "Enabled": "Engedélyezés", "HiddenClickToShow": "Rejtett, kattints a felfedéshez", "ImportListStatusAllUnavailableHealthCheckMessage": "Minden lista elérhetetlen meghibásodások miatt", - "ImportListStatusUnavailableHealthCheckMessage": "Listák elérhetetlenek meghibásodások miatt: {0}", + "ImportListStatusUnavailableHealthCheckMessage": "Listák elérhetetlenek meghibásodások miatt: {importListNames}", "ImportMechanismEnableCompletedDownloadHandlingIfPossibleHealthCheckMessage": "Befejezett letöltés kezelésének engedélyezése, ha lehetséges", "ImportMechanismHandlingDisabledHealthCheckMessage": "Befejezett letöltés kezelésének engedélyezése", "ImportMechanismEnableCompletedDownloadHandlingIfPossibleMultiComputerHealthCheckMessage": "Befejezett letöltés kezelésének engedélyezése, ha lehetséges (Több számítógépen nem támogatott)", "IndexerRssNoIndexersEnabledHealthCheckMessage": "Nincsenek elérhető indexelők RSS szinkronizációval, a {appName} nem fog automatikusan új kiadásokat letölteni", "IndexerRssNoIndexersAvailableHealthCheckMessage": "Az összes RSS-képes indexelő ideiglenesen nem elérhető a legutóbbi indexelő hibák miatt", - "IndexerLongTermStatusUnavailableHealthCheckMessage": "Minden indexelő elérhetetlen meghibásodás miatt több, mint 6 órája: {0}", + "IndexerLongTermStatusUnavailableHealthCheckMessage": "Minden indexelő elérhetetlen meghibásodás miatt több, mint 6 órája: {indexerNames}", "IndexerLongTermStatusAllUnavailableHealthCheckMessage": "Minden indexelő elérhetetlen meghibásodás miatt több, mint 6 órája", "IndexerSearchNoInteractiveHealthCheckMessage": "Nincsenek elérhető indexelők az Interaktív Keresés funkcióval, a {appName} nem fog interaktív keresési eredményeket szolgáltatni", "IndexerSearchNoAvailableIndexersHealthCheckMessage": "Az összes keresési képességgel rendelkező indexelő ideiglenesen nem elérhető a legutóbbi indexelő hibák miatt", "IndexerSearchNoAutomaticHealthCheckMessage": "Nincsenek elérhető indexelők az Automatikus Keresés funkcióval, a {appName} nem fog automatikus keresési eredményeket szolgáltatni", "Language": "Nyelv", - "IndexerStatusUnavailableHealthCheckMessage": "Minden indexelő elérhetetlen meghibásodások miatt: {0}", + "IndexerStatusUnavailableHealthCheckMessage": "Minden indexelő elérhetetlen meghibásodások miatt: {indexerNames}", "IndexerStatusAllUnavailableHealthCheckMessage": "Minden indexelő elérhetetlen meghibásodások miatt", "OneSeason": "1 évad", "OriginalLanguage": "Eredeti nyelv", @@ -64,45 +64,45 @@ "MountHealthCheckMessage": "A sorozat elérési útvonalát tartalmazó kötet csak olvasható módban van csatolva: ", "Network": "Hálózat", "NoSeasons": "Nincsenek évadok", - "ProxyBadRequestHealthCheckMessage": "Sikertelen proxy teszt. Állapotkód: {0}", + "ProxyBadRequestHealthCheckMessage": "Sikertelen proxy teszt. Állapotkód: {statusCode}", "Priority": "Elsőbbség", - "ProxyFailedToTestHealthCheckMessage": "Sikertelen proxy teszt: {0}", - "ProxyResolveIpHealthCheckMessage": "Nem sikerült feloldani a konfigurált proxy kiszolgáló {0} IP-címét", + "ProxyFailedToTestHealthCheckMessage": "Sikertelen proxy teszt: {url}", + "ProxyResolveIpHealthCheckMessage": "Nem sikerült feloldani a konfigurált proxy kiszolgáló {proxyHostName} IP-címét", "PreviousAiring": "Előző rész", - "RecycleBinUnableToWriteHealthCheckMessage": "Nem lehet írni a konfigurált lomtár mappába {0}. Győződjön meg arról, hogy ez az elérési útvonal létezik, és az a felhasználó, aki a {appName}-t futtatja, írási jogosultsággal rendelkezik", + "RecycleBinUnableToWriteHealthCheckMessage": "Nem lehet írni a konfigurált lomtár mappába {path}. Győződjön meg arról, hogy ez az elérési útvonal létezik, és az a felhasználó, aki a {appName}-t futtatja, írási jogosultsággal rendelkezik", "QualityProfile": "Minőségi profil", - "RemotePathMappingDockerFolderMissingHealthCheckMessage": "Docker-t használ; a(z) {0} letöltési kliens a letöltéseket a(z) {1} mappába helyezi, de úgy tűnik, hogy ez a könyvtár nem létezik a konténeren belül. Ellenőrizze a távoli útvonal hozzárendeléseket, és a konténer kötet beállításait.", + "RemotePathMappingDockerFolderMissingHealthCheckMessage": "Docker-t használ; a(z) $1{downloadClientName} letöltési kliens a letöltéseket a(z) {path} mappába helyezi, de úgy tűnik, hogy ez a könyvtár nem létezik a konténeren belül. Ellenőrizze a távoli útvonal hozzárendeléseket, és a konténer kötet beállításait.", "RefreshSeries": "Sorozat frissítése", - "RemotePathMappingFileRemovedHealthCheckMessage": "A(z) {0} fájlt részben feldolgozás közben eltávolították.", - "RemotePathMappingDownloadPermissionsHealthCheckMessage": "A {appName} látja, de nem tud hozzáférni a letöltött epizódhoz {0}. Valószínűleg jogosultsági hiba.", - "RemotePathMappingFilesLocalWrongOSPathHealthCheckMessage": "A(z) {0} helyi letöltési kliens a fájlokat a(z) {1} mappában jelentette, de ez nem érvényes {2} elérési útvonal. Ellenőrizze a letöltési kliens beállításait.", - "RemotePathMappingGenericPermissionsHealthCheckMessage": "A(z) {0} letöltési kliens a letöltéseket a(z) {1} mappába helyezi, de a {appName} nem látja ezt a könyvtárat. Lehetséges, hogy be kell állítania a mappa jogosultságait.", + "RemotePathMappingFileRemovedHealthCheckMessage": "A(z) {path} fájlt részben feldolgozás közben eltávolították.", + "RemotePathMappingDownloadPermissionsHealthCheckMessage": "A {appName} látja, de nem tud hozzáférni a letöltött epizódhoz {path}. Valószínűleg jogosultsági hiba.", + "RemotePathMappingFilesLocalWrongOSPathHealthCheckMessage": "A(z) {downloadClientName} helyi letöltési kliens a fájlokat a(z) {path} mappában jelentette, de ez nem érvényes {osName} elérési útvonal. Ellenőrizze a letöltési kliens beállításait.", + "RemotePathMappingGenericPermissionsHealthCheckMessage": "A(z) {downloadClientName} letöltési kliens a letöltéseket a(z) {path} mappába helyezi, de a {appName} nem látja ezt a könyvtárat. Lehetséges, hogy be kell állítania a mappa jogosultságait.", "RemotePathMappingImportFailedHealthCheckMessage": "A {appName}-nak nem sikerült importálni az epizód(ok)at. Ellenőrizze a naplókat a részletekért.", - "RemotePathMappingRemoteDownloadClientHealthCheckMessage": "A(z) {0} távoli letöltési kliens a fájlokat a(z) {1} mappában jelentette, de úgy tűnik, hogy ez a könyvtár nem létezik. Valószínűleg hiányzik a távoli útvonal hozzárendelés.", - "RemotePathMappingLocalFolderMissingHealthCheckMessage": "A(z) {0} távoli letöltési kliens a letöltéseket a(z) {1} mappába helyezi, de úgy tűnik, hogy ez a könyvtár nem létezik. Valószínűleg hiányzik vagy helytelen a távoli útvonal hozzárendelés.", - "RemotePathMappingLocalWrongOSPathHealthCheckMessage": "A(z) {0} helyi letöltési kliens a letöltéseket a(z) {1} mappába helyezi, de ez nem érvényes {2} elérési útvonal. Ellenőrizze a letöltési kliens beállításait.", + "RemotePathMappingRemoteDownloadClientHealthCheckMessage": "A(z) {downloadClientName} távoli letöltési kliens a fájlokat a(z) {path} mappában jelentette, de úgy tűnik, hogy ez a könyvtár nem létezik. Valószínűleg hiányzik a távoli útvonal hozzárendelés.", + "RemotePathMappingLocalFolderMissingHealthCheckMessage": "A(z) {downloadClientName} távoli letöltési kliens a letöltéseket a(z) {path} mappába helyezi, de úgy tűnik, hogy ez a könyvtár nem létezik. Valószínűleg hiányzik vagy helytelen a távoli útvonal hozzárendelés.", + "RemotePathMappingLocalWrongOSPathHealthCheckMessage": "A(z) {downloadClientName} helyi letöltési kliens a letöltéseket a(z) {path} mappába helyezi, de ez nem érvényes {osName} elérési útvonal. Ellenőrizze a letöltési kliens beállításait.", "RemoveFailedDownloads": "Sikertelen letöltések eltávolítása", - "RemovedSeriesMultipleRemovedHealthCheckMessage": "A(z) {0} sorozatokat eltávolították a TheTVDB-ről", - "RemotePathMappingWrongOSPathHealthCheckMessage": "A(z) {0} távoli letöltési kliens a letöltéseket a(z) {1} mappába helyezi, de ez nem érvényes {2} elérési útvonal. Kérjük, ellenőrizze a távoli útvonal leképezéseket és a letöltési kliens beállításait.", + "RemovedSeriesMultipleRemovedHealthCheckMessage": "A(z) {series} sorozatokat eltávolították a TheTVDB-ről", + "RemotePathMappingWrongOSPathHealthCheckMessage": "A(z) {downloadClientName} távoli letöltési kliens a letöltéseket a(z) {path} mappába helyezi, de ez nem érvényes {osName} elérési útvonal. Kérjük, ellenőrizze a távoli útvonal leképezéseket és a letöltési kliens beállításait.", "RemoveCompletedDownloads": "Befejezett letöltések eltávolítása", - "RemovedSeriesSingleRemovedHealthCheckMessage": "A(z) {0} sorozatot eltávolították a TheTVDB-ről", - "RootFolderMissingHealthCheckMessage": "Hiányzó gyökérmappa: {0}", + "RemovedSeriesSingleRemovedHealthCheckMessage": "A(z) {series} sorozatot eltávolították a TheTVDB-ről", + "RootFolderMissingHealthCheckMessage": "Hiányzó gyökérmappa: {rootFolderPath}", "RootFolder": "Gyökérmappa", "SearchForMonitoredEpisodes": "Megfigyelt epizódok keresése", "ShowAdvanced": "Haladó nézet", - "RootFolderMultipleMissingHealthCheckMessage": "Több gyökérmappa hiányzik: {0}", + "RootFolderMultipleMissingHealthCheckMessage": "Több gyökérmappa hiányzik: {rootFolderPaths}", "SizeOnDisk": "Méret a lemezen", "ShownClickToHide": "Kattints, hogy elrejtsd", "SystemTimeHealthCheckMessage": "A rendszer idő több, mint 1 napot eltér az aktuális időtől. Előfordulhat, hogy az ütemezett feladatok nem futnak megfelelően, amíg az időt nem korrigálják", "Unmonitored": "Nem felügyelt", - "UpdateStartupNotWritableHealthCheckMessage": "A frissítés telepítése nem lehetséges, mert a kezdő mappa '{0}' nem írható a(z) '{1}' felhasználó által.", - "UpdateStartupTranslocationHealthCheckMessage": "A frissítés telepítése nem lehetséges, mert a kezdő mappa '{0}' az App Translocation mappában található.", + "UpdateStartupNotWritableHealthCheckMessage": "A frissítés telepítése nem lehetséges, mert a kezdő mappa '{startupFolder}' nem írható a(z) '{userName}' felhasználó által.", + "UpdateStartupTranslocationHealthCheckMessage": "A frissítés telepítése nem lehetséges, mert a kezdő mappa '{startupFolder}' az App Translocation mappában található.", "UpdateAvailableHealthCheckMessage": "Új frissítés elérhető", - "UpdateUINotWritableHealthCheckMessage": "A frissítés telepítése nem lehetséges, mert a felhasználó '{1}' nem rendelkezik írási jogosultsággal a(z) '{0}' felhasználói felület mappában.", - "DownloadClientSortingHealthCheckMessage": "A(z) {0} letöltési kliensben engedélyezve van a {1} rendezés a {appName} kategóriájához. Az import problémák elkerülése érdekében ki kell kapcsolnia a rendezést a letöltési kliensben.", - "RemotePathMappingBadDockerPathHealthCheckMessage": "Docker-t használ; a(z) {0} letöltési kliens a letöltéseket a(z) {1} mappába helyezi, de ez nem érvényes {2} elérési útvonal. Ellenőrizze a távoli útvonal hozzárendeléseket, és a letöltési kliens beállításait.", - "RemotePathMappingFilesBadDockerPathHealthCheckMessage": "Docker-t használ; a(z) {0} letöltési kliens a fájlokat a(z) {1} mappában jelentette, de ez nem érvényes {2} elérési útvonal. Ellenőrizze a távoli útvonal hozzárendeléseket, és a letöltési kliens beállításait.", - "RemotePathMappingFilesGenericPermissionsHealthCheckMessage": "A(z) {0} letöltési kliens a fájlokat a(z) {1} mappában jelentette, de a {appName} nem látja ezt a könyvtárat. Lehetséges, hogy be kell állítania a mappa jogosultságait.", - "RemotePathMappingFilesWrongOSPathHealthCheckMessage": "A(z) {0} távoli letöltési kliens a fájlokat a(z) {1} mappában jelentette, de ez nem érvényes {2} elérési útvonal. Ellenőrizze a távoli útvonal hozzárendeléseket, és a letöltési kliens beállításait.", - "RemotePathMappingFolderPermissionsHealthCheckMessage": "A {appName} látja, de nem fér hozzá a letöltési könyvtárhoz {0}. Valószínűleg jogosultsági hiba." + "UpdateUiNotWritableHealthCheckMessage": "A frissítés telepítése nem lehetséges, mert a felhasználó '{userName}' nem rendelkezik írási jogosultsággal a(z) '{uiFolder}' felhasználói felület mappában.", + "DownloadClientSortingHealthCheckMessage": "A(z) {downloadClientName} letöltési kliensben engedélyezve van a {sortingMode} rendezés a {appName} kategóriájához. Az import problémák elkerülése érdekében ki kell kapcsolnia a rendezést a letöltési kliensben.", + "RemotePathMappingBadDockerPathHealthCheckMessage": "Docker-t használ; a(z) {downloadClientName} letöltési kliens a letöltéseket a(z) {path} mappába helyezi, de ez nem érvényes {osName} elérési útvonal. Ellenőrizze a távoli útvonal hozzárendeléseket, és a letöltési kliens beállításait.", + "RemotePathMappingFilesBadDockerPathHealthCheckMessage": "Docker-t használ; a(z) {downloadClientName} letöltési kliens a fájlokat a(z) {path} mappában jelentette, de ez nem érvényes {osName} elérési útvonal. Ellenőrizze a távoli útvonal hozzárendeléseket, és a letöltési kliens beállításait.", + "RemotePathMappingFilesGenericPermissionsHealthCheckMessage": "A(z) {downloadClientName} letöltési kliens a fájlokat a(z) {path} mappában jelentette, de a {appName} nem látja ezt a könyvtárat. Lehetséges, hogy be kell állítania a mappa jogosultságait.", + "RemotePathMappingFilesWrongOSPathHealthCheckMessage": "A(z) {downloadClientName} távoli letöltési kliens a fájlokat a(z) {path} mappában jelentette, de ez nem érvényes {osName} elérési útvonal. Ellenőrizze a távoli útvonal hozzárendeléseket, és a letöltési kliens beállításait.", + "RemotePathMappingFolderPermissionsHealthCheckMessage": "A {appName} látja, de nem fér hozzá a letöltési könyvtárhoz {downloadPath}. Valószínűleg jogosultsági hiba." } diff --git a/src/NzbDrone.Core/Localization/Core/id.json b/src/NzbDrone.Core/Localization/Core/id.json index a993eb76e..810ff7cb1 100644 --- a/src/NzbDrone.Core/Localization/Core/id.json +++ b/src/NzbDrone.Core/Localization/Core/id.json @@ -14,8 +14,8 @@ "PreviousAiring": "Sebelumnya Tayang", "OriginalLanguage": "Bahasa Asli", "Priority": "Prioritas", - "ProxyFailedToTestHealthCheckMessage": "Gagal menguji proxy: {0}", - "ProxyBadRequestHealthCheckMessage": "Gagal menguji proxy. Kode Status: {0}", + "ProxyFailedToTestHealthCheckMessage": "Gagal menguji proxy: {url}", + "ProxyBadRequestHealthCheckMessage": "Gagal menguji proxy. Kode Status: {statusCode}", "QualityProfile": "Profil Kualitas", "Add": "Tambah", "Cancel": "Batal", diff --git a/src/NzbDrone.Core/Localization/Core/it.json b/src/NzbDrone.Core/Localization/Core/it.json index 72de8677a..574b32f5c 100644 --- a/src/NzbDrone.Core/Localization/Core/it.json +++ b/src/NzbDrone.Core/Localization/Core/it.json @@ -1,11 +1,11 @@ { - "DeleteConditionMessageText": "Sei sicuro di voler eliminare la condizione '{0}'?", + "DeleteConditionMessageText": "Sei sicuro di voler eliminare la condizione '{name}'?", "ApplyTagsHelpTextReplace": "Sostituire: Sostituisce le etichette con quelle inserite (non inserire nessuna etichette per eliminarle tutte)", "ApplyTagsHelpTextHowToApplyIndexers": "Come applicare etichette agli indicizzatori selezionati", "MoveAutomatically": "Sposta Automaticamente", "ApplyTagsHelpTextRemove": "Rimuovi: Rimuove le etichette inserite", "ApplyTagsHelpTextAdd": "Aggiungi: Aggiunge le etichette alla lista esistente di etichette", - "DeleteCustomFormatMessageText": "Sei sicuro di voler eliminare il formato personalizzato '{0}'?", + "DeleteCustomFormatMessageText": "Sei sicuro di voler eliminare il formato personalizzato '{customFormatName}'?", "DeleteSelectedDownloadClients": "Cancella i Client di Download", "Added": "Aggiunto", "AutomaticAdd": "Aggiungi Automaticamente", @@ -15,7 +15,7 @@ "AutoAdd": "Aggiungi Automaticamente", "AirDate": "Data di Trasmissione", "AllTitles": "Tutti i Titoli", - "ApiKeyValidationHealthCheckMessage": "Aggiorna la tua chiave API in modo che abbia una lunghezza di almeno {0} caratteri. Puoi farlo dalle impostazioni o dal file di configurazione", + "ApiKeyValidationHealthCheckMessage": "Aggiorna la tua chiave API in modo che abbia una lunghezza di almeno {length} caratteri. Puoi farlo dalle impostazioni o dal file di configurazione", "Apply": "Applica", "ApplyChanges": "Applica Cambiamenti", "ApplyTags": "Applica Etichette", diff --git a/src/NzbDrone.Core/Localization/Core/nb_NO.json b/src/NzbDrone.Core/Localization/Core/nb_NO.json index e8082be62..83382909f 100644 --- a/src/NzbDrone.Core/Localization/Core/nb_NO.json +++ b/src/NzbDrone.Core/Localization/Core/nb_NO.json @@ -1,5 +1,5 @@ { - "ApiKeyValidationHealthCheckMessage": "Vennligst oppdater din API-nøkkel til å være minst {0} tegn lang. Du kan gjøre dette via innstillinger eller konfigurasjonsfilen", + "ApiKeyValidationHealthCheckMessage": "Vennligst oppdater din API-nøkkel til å være minst {length} tegn lang. Du kan gjøre dette via innstillinger eller konfigurasjonsfilen", "ApplyChanges": "Bekreft endringer", "AllTitles": "Alle titler", "AddAutoTag": "Legg til automatisk tagg", diff --git a/src/NzbDrone.Core/Localization/Core/nl.json b/src/NzbDrone.Core/Localization/Core/nl.json index a7d65f5da..3ba8d854f 100644 --- a/src/NzbDrone.Core/Localization/Core/nl.json +++ b/src/NzbDrone.Core/Localization/Core/nl.json @@ -1,6 +1,6 @@ { "RemoveFailedDownloads": "Verwijder mislukte downloads", - "ApiKeyValidationHealthCheckMessage": "Maak je API sleutel alsjeblieft minimaal {0} karakters lang. Dit kan gedaan worden via de instellingen of het configuratiebestand", + "ApiKeyValidationHealthCheckMessage": "Maak je API sleutel alsjeblieft minimaal {length} karakters lang. Dit kan gedaan worden via de instellingen of het configuratiebestand", "RemoveCompletedDownloads": "Verwijder voltooide downloads", "AppDataLocationHealthCheckMessage": "Updaten zal niet mogelijk zijn om het verwijderen van AppData te voorkomen", "Added": "Toegevoegd", diff --git a/src/NzbDrone.Core/Localization/Core/pl.json b/src/NzbDrone.Core/Localization/Core/pl.json index 6717d3c29..d45c4d835 100644 --- a/src/NzbDrone.Core/Localization/Core/pl.json +++ b/src/NzbDrone.Core/Localization/Core/pl.json @@ -13,7 +13,7 @@ "ApplyTagsHelpTextHowToApplyIndexers": "Jak zastosować tagi do wybranych indeksatorów", "Authentication": "Autoryzacja", "BlocklistReleases": "Dodaj wersje do czarnej listy", - "ApiKeyValidationHealthCheckMessage": "Zaktualizuj swój klucz API aby był długi na co najmniej {0} znaków. Możesz to zrobić poprzez ustawienia lub plik konfiguracyjny", + "ApiKeyValidationHealthCheckMessage": "Zaktualizuj swój klucz API aby był długi na co najmniej {length} znaków. Możesz to zrobić poprzez ustawienia lub plik konfiguracyjny", "AudioInfo": "Informacje o audio", "AddAutoTagError": "Nie można dodać nowego tagu automatycznego, spróbuj ponownie.", "AddConditionError": "Nie można dodać nowego warunku, spróbuj ponownie.", diff --git a/src/NzbDrone.Core/Localization/Core/pt.json b/src/NzbDrone.Core/Localization/Core/pt.json index 5aa1d1182..1df2c799a 100644 --- a/src/NzbDrone.Core/Localization/Core/pt.json +++ b/src/NzbDrone.Core/Localization/Core/pt.json @@ -4,7 +4,7 @@ "CountSeasons": "{count} temporadas", "Language": "Idioma", "Added": "Adicionado", - "ApiKeyValidationHealthCheckMessage": "Por favor, atualize a sua API Key para ter no mínimo {0} caracteres. Pode fazer através das definições ou do ficheiro de configuração", + "ApiKeyValidationHealthCheckMessage": "Por favor, atualize a sua API Key para ter no mínimo {length} caracteres. Pode fazer através das definições ou do ficheiro de configuração", "AppDataLocationHealthCheckMessage": "Não foi possível atualizar para prevenir apagar a AppData durante a atualização", "AddAutoTag": "Adicionar Etiqueta Automática", "AbsoluteEpisodeNumbers": "Números de Episódios Absolutos", diff --git a/src/NzbDrone.Core/Localization/Core/pt_BR.json b/src/NzbDrone.Core/Localization/Core/pt_BR.json index eb6252099..c5034e996 100644 --- a/src/NzbDrone.Core/Localization/Core/pt_BR.json +++ b/src/NzbDrone.Core/Localization/Core/pt_BR.json @@ -9,15 +9,15 @@ "Enabled": "Habilitado", "Ended": "Terminou", "HideAdvanced": "Ocultar Avançado", - "ImportListRootFolderMultipleMissingRootsHealthCheckMessage": "Múltiplas pastas raiz estão faltando nas listas de importação: {0}", + "ImportListRootFolderMultipleMissingRootsHealthCheckMessage": "Múltiplas pastas raiz estão faltando nas listas de importação: {rootFoldersInfo}", "ImportListStatusAllUnavailableHealthCheckMessage": "Todas as listas estão indisponíveis devido a falhas", "ImportMechanismHandlingDisabledHealthCheckMessage": "Ativar gerenciamento de download concluído", - "IndexerLongTermStatusUnavailableHealthCheckMessage": "Indexadores indisponíveis devido a falhas por mais de 6 horas: {0}", + "IndexerLongTermStatusUnavailableHealthCheckMessage": "Indexadores indisponíveis devido a falhas por mais de 6 horas: {indexerNames}", "IndexerRssNoIndexersEnabledHealthCheckMessage": "Nenhum indexador disponível com sincronização de RSS ativada, o {appName} não obterá novos lançamentos automaticamente", "IndexerSearchNoAutomaticHealthCheckMessage": "Nenhum indexador disponível com a pesquisa automática ativada, o {appName} não fornecerá nenhum resultado de pesquisa automática", "IndexerSearchNoInteractiveHealthCheckMessage": "Nenhum indexador disponível com a Pesquisa interativa habilitada, o {appName} não fornecerá nenhum resultado de pesquisa interativa", "IndexerStatusAllUnavailableHealthCheckMessage": "Todos os indexadores estão indisponíveis devido a falhas", - "IndexerStatusUnavailableHealthCheckMessage": "Indexadores indisponíveis devido a falhas: {0}", + "IndexerStatusUnavailableHealthCheckMessage": "Indexadores indisponíveis devido a falhas: {indexerNames}", "Language": "Idioma", "Monitored": "Monitorado", "MountHealthCheckMessage": "A montagem que contém um caminho de série é montada somente para leitura: ", @@ -30,21 +30,21 @@ "RemoveFailedDownloads": "Remover downloads com falha", "QualityProfile": "Perfil de Qualidade", "RefreshSeries": "Atualizar Séries", - "RemotePathMappingDockerFolderMissingHealthCheckMessage": "Você está usando o docker; o cliente de download {0} coloca os downloads em {1}, mas esse diretório parece não existir dentro do contêiner. Revise seus mapeamentos de caminho remoto e configurações de volume do contêiner.", - "RemotePathMappingDownloadPermissionsHealthCheckMessage": "O {appName} pode ver, mas não acessar o episódio baixado {0}. Provável erro de permissão.", - "RemotePathMappingFileRemovedHealthCheckMessage": "O arquivo {0} foi removido no meio do processamento.", - "RemotePathMappingFilesGenericPermissionsHealthCheckMessage": "Baixe os arquivos relatados do cliente {0} em {1}, mas o {appName} não pode ver este diretório. Pode ser necessário ajustar as permissões da pasta.", - "RemotePathMappingFilesLocalWrongOSPathHealthCheckMessage": "O cliente de download local {0} relatou arquivos em {1}, mas este não é um caminho {2} válido. Revise as configurações do cliente de download.", - "RemotePathMappingFolderPermissionsHealthCheckMessage": "{appName} pode ver, mas não acessar o diretório de download {0}. Provável erro de permissão.", - "RemotePathMappingGenericPermissionsHealthCheckMessage": "O cliente de download {0} coloca os downloads em {1}, mas o {appName} não pode ver este diretório. Pode ser necessário ajustar as permissões da pasta.", + "RemotePathMappingDockerFolderMissingHealthCheckMessage": "Você está usando o docker; o cliente de download $1{downloadClientName} coloca os downloads em {path}, mas esse diretório parece não existir dentro do contêiner. Revise seus mapeamentos de caminho remoto e configurações de volume do contêiner.", + "RemotePathMappingDownloadPermissionsHealthCheckMessage": "O {appName} pode ver, mas não acessar o episódio baixado {path}. Provável erro de permissão.", + "RemotePathMappingFileRemovedHealthCheckMessage": "O arquivo {path} foi removido no meio do processamento.", + "RemotePathMappingFilesGenericPermissionsHealthCheckMessage": "Baixe os arquivos relatados do cliente {downloadClientName} em {path}, mas o {appName} não pode ver este diretório. Pode ser necessário ajustar as permissões da pasta.", + "RemotePathMappingFilesLocalWrongOSPathHealthCheckMessage": "O cliente de download local {downloadClientName} relatou arquivos em {path}, mas este não é um caminho {osName} válido. Revise as configurações do cliente de download.", + "RemotePathMappingFolderPermissionsHealthCheckMessage": "{appName} pode ver, mas não acessar o diretório de download {downloadPath}. Provável erro de permissão.", + "RemotePathMappingGenericPermissionsHealthCheckMessage": "O cliente de download {downloadClientName} coloca os downloads em {path}, mas o {appName} não pode ver este diretório. Pode ser necessário ajustar as permissões da pasta.", "RemotePathMappingImportFailedHealthCheckMessage": "{appName} falhou ao importar (um) episódio(s). Verifique seus logs para obter detalhes.", - "RemotePathMappingLocalWrongOSPathHealthCheckMessage": "O cliente de download local {0} coloca os downloads em {1}, mas este não é um caminho {2} válido. Revise as configurações do cliente de download.", - "RemotePathMappingRemoteDownloadClientHealthCheckMessage": "O cliente de download remoto {0} relatou arquivos em {1}, mas este diretório parece não existir. Provavelmente faltando mapeamento de caminho remoto.", - "RemovedSeriesMultipleRemovedHealthCheckMessage": "A série {0} foi removida do TheTVDB", - "RemovedSeriesSingleRemovedHealthCheckMessage": "As séries {0} foram removidas do TheTVDB", + "RemotePathMappingLocalWrongOSPathHealthCheckMessage": "O cliente de download local {downloadClientName} coloca os downloads em {path}, mas este não é um caminho {osName} válido. Revise as configurações do cliente de download.", + "RemotePathMappingRemoteDownloadClientHealthCheckMessage": "O cliente de download remoto {downloadClientName} relatou arquivos em {path}, mas este diretório parece não existir. Provavelmente faltando mapeamento de caminho remoto.", + "RemovedSeriesMultipleRemovedHealthCheckMessage": "A série {series} foi removida do TheTVDB", + "RemovedSeriesSingleRemovedHealthCheckMessage": "As séries {series} foram removidas do TheTVDB", "RootFolder": "Pasta Raiz", - "RootFolderMissingHealthCheckMessage": "Pasta raiz ausente: {0}", - "RootFolderMultipleMissingHealthCheckMessage": "Faltam várias pastas raiz: {0}", + "RootFolderMissingHealthCheckMessage": "Pasta raiz ausente: {rootFolderPath}", + "RootFolderMultipleMissingHealthCheckMessage": "Faltam várias pastas raiz: {rootFolderPaths}", "SearchForMonitoredEpisodes": "Pesquisar episódios monitorados", "ShowAdvanced": "Mostrar Avançado", "ShownClickToHide": "Mostrado, clique para ocultar", @@ -52,41 +52,40 @@ "SystemTimeHealthCheckMessage": "A hora do sistema está desligada por mais de 1 dia. Tarefas agendadas podem não ser executadas corretamente até que o horário seja corrigido", "Unmonitored": "Não monitorado", "UpdateAvailableHealthCheckMessage": "Nova atualização está disponível", - "UpdateUINotWritableHealthCheckMessage": "Não é possível instalar a atualização porque a pasta de IU '{0}' não pode ser gravada pelo usuário '{1}'.", "Added": "Adicionado", - "ApiKeyValidationHealthCheckMessage": "Atualize sua chave de API para ter pelo menos {0} caracteres. Você pode fazer isso através das configurações ou do arquivo de configuração", + "ApiKeyValidationHealthCheckMessage": "Atualize sua chave de API para ter pelo menos {length} caracteres. Você pode fazer isso através das configurações ou do arquivo de configuração", "RemoveCompletedDownloads": "Remover downloads concluídos", "AppDataLocationHealthCheckMessage": "A atualização não será possível para evitar a exclusão de AppData na atualização", - "DownloadClientCheckUnableToCommunicateWithHealthCheckMessage": "Não é possível se comunicar com {0}.", - "DownloadClientRootFolderHealthCheckMessage": "O cliente de download {0} coloca os downloads na pasta raiz {1}. Você não deve baixar para uma pasta raiz.", - "DownloadClientSortingHealthCheckMessage": "O cliente de download {0} tem classificação {1} habilitada para a categoria {appName}. Você deve desativar a classificação em seu cliente de download para evitar problemas de importação.", - "DownloadClientStatusSingleClientHealthCheckMessage": "Clientes de download indisponíveis devido a falhas: {0}", + "DownloadClientCheckUnableToCommunicateWithHealthCheckMessage": "Não é possível se comunicar com {downloadClientName}.", + "DownloadClientRootFolderHealthCheckMessage": "O cliente de download {downloadClientName} coloca os downloads na pasta raiz {rootFolderPath}. Você não deve baixar para uma pasta raiz.", + "DownloadClientSortingHealthCheckMessage": "O cliente de download {downloadClientName} tem classificação {sortingMode} habilitada para a categoria {appName}. Você deve desativar a classificação em seu cliente de download para evitar problemas de importação.", + "DownloadClientStatusSingleClientHealthCheckMessage": "Clientes de download indisponíveis devido a falhas: {downloadClientNames}", "EditSelectedIndexers": "Editar indexadores selecionados", "EditSeries": "Editar Série", "EnableAutomaticSearch": "Ativar pesquisa automática", "EnableInteractiveSearch": "Ativar pesquisa interativa", "HiddenClickToShow": "Oculto, clique para mostrar", - "ImportListRootFolderMissingRootHealthCheckMessage": "Pasta raiz ausente para lista(s) de importação: {0}", - "ImportListStatusUnavailableHealthCheckMessage": "Listas indisponíveis devido a falhas: {0}", + "ImportListRootFolderMissingRootHealthCheckMessage": "Pasta raiz ausente para lista(s) de importação: {rootFolderInfo}", + "ImportListStatusUnavailableHealthCheckMessage": "Listas indisponíveis devido a falhas: {importListNames}", "ImportMechanismEnableCompletedDownloadHandlingIfPossibleHealthCheckMessage": "Ative o Gerenciamento de download concluído, se possível", "ImportMechanismEnableCompletedDownloadHandlingIfPossibleMultiComputerHealthCheckMessage": "Ative o Gerenciamento de download concluído, se possível (Multi-computador não suportado)", - "IndexerJackettAllHealthCheckMessage": "Indexadores usando o endpont Jackett 'all' sem suporte: {0}", + "IndexerJackettAllHealthCheckMessage": "Indexadores usando o endpont Jackett 'all' sem suporte: {indexerNames}", "IndexerLongTermStatusAllUnavailableHealthCheckMessage": "Todos os indexadores estão indisponíveis devido a falhas por mais de 6 horas", "IndexerRssNoIndexersAvailableHealthCheckMessage": "Todos os indexadores compatíveis com rss estão temporariamente indisponíveis devido a erros recentes do indexador", "IndexerSearchNoAvailableIndexersHealthCheckMessage": "Todos os indexadores com capacidade de pesquisa estão temporariamente indisponíveis devido a erros recentes do indexador", "NextAiring": "Próxima Exibição", "OriginalLanguage": "Idioma Original", - "ProxyBadRequestHealthCheckMessage": "Falha ao testar o proxy. Código de estado: {0}", - "ProxyFailedToTestHealthCheckMessage": "Falha ao testar o proxy: {0}", - "ProxyResolveIpHealthCheckMessage": "Falha ao resolver o endereço IP do host de proxy configurado {0}", - "RecycleBinUnableToWriteHealthCheckMessage": "Não é possível gravar na pasta da lixeira configurada: {0}. Certifique-se de que este caminho exista e seja gravável pelo usuário executando o {appName}", - "RemotePathMappingBadDockerPathHealthCheckMessage": "Você está usando o docker; cliente de download {0} coloca downloads em {1}, mas este não é um caminho {2} válido. Revise seus mapeamentos de caminho remoto e baixe as configurações do cliente.", - "RemotePathMappingFilesBadDockerPathHealthCheckMessage": "Você está usando o docker; baixe os arquivos relatados do cliente {0} em {1}, mas este não é um caminho {2} válido. Revise seus mapeamentos de caminho remoto e baixe as configurações do cliente.", - "RemotePathMappingFilesWrongOSPathHealthCheckMessage": "O cliente de download remoto {0} relatou arquivos em {1}, mas este não é um caminho {2} válido. Revise seus mapeamentos de caminho remoto e baixe as configurações do cliente.", - "RemotePathMappingLocalFolderMissingHealthCheckMessage": "O cliente de download remoto {0} coloca os downloads em {1}, mas este diretório parece não existir. Mapeamento de caminho remoto provavelmente ausente ou incorreto.", - "RemotePathMappingWrongOSPathHealthCheckMessage": "O cliente de download remoto {0} coloca os downloads em {1}, mas este não é um caminho {2} válido. Revise seus mapeamentos de caminho remoto e baixe as configurações do cliente.", - "UpdateStartupNotWritableHealthCheckMessage": "Não é possível instalar a atualização porque a pasta de inicialização '{0}' não pode ser gravada pelo usuário '{1}'.", - "UpdateStartupTranslocationHealthCheckMessage": "Não é possível instalar a atualização porque a pasta de inicialização '{0}' está em uma pasta de translocação de aplicativo.", + "ProxyBadRequestHealthCheckMessage": "Falha ao testar o proxy. Código de estado: {statusCode}", + "ProxyFailedToTestHealthCheckMessage": "Falha ao testar o proxy: {url}", + "ProxyResolveIpHealthCheckMessage": "Falha ao resolver o endereço IP do host de proxy configurado {proxyHostName}", + "RecycleBinUnableToWriteHealthCheckMessage": "Não é possível gravar na pasta da lixeira configurada: {path}. Certifique-se de que este caminho exista e seja gravável pelo usuário executando o {appName}", + "RemotePathMappingBadDockerPathHealthCheckMessage": "Você está usando o docker; cliente de download {downloadClientName} coloca downloads em {path}, mas este não é um caminho {osName} válido. Revise seus mapeamentos de caminho remoto e baixe as configurações do cliente.", + "RemotePathMappingFilesBadDockerPathHealthCheckMessage": "Você está usando o docker; baixe os arquivos relatados do cliente {downloadClientName} em {path}, mas este não é um caminho {osName} válido. Revise seus mapeamentos de caminho remoto e baixe as configurações do cliente.", + "RemotePathMappingFilesWrongOSPathHealthCheckMessage": "O cliente de download remoto {downloadClientName} relatou arquivos em {path}, mas este não é um caminho {osName} válido. Revise seus mapeamentos de caminho remoto e baixe as configurações do cliente.", + "RemotePathMappingLocalFolderMissingHealthCheckMessage": "O cliente de download remoto {downloadClientName} coloca os downloads em {path}, mas este diretório parece não existir. Mapeamento de caminho remoto provavelmente ausente ou incorreto.", + "RemotePathMappingWrongOSPathHealthCheckMessage": "O cliente de download remoto {downloadClientName} coloca os downloads em {path}, mas este não é um caminho {osName} válido. Revise seus mapeamentos de caminho remoto e baixe as configurações do cliente.", + "UpdateStartupNotWritableHealthCheckMessage": "Não é possível instalar a atualização porque a pasta de inicialização '{startupFolder}' não pode ser gravada pelo usuário '{userName}'.", + "UpdateStartupTranslocationHealthCheckMessage": "Não é possível instalar a atualização porque a pasta de inicialização '{startupFolder}' está em uma pasta de translocação de aplicativo.", "BlocklistReleaseHelpText": "Inicia uma busca por este episódio novamente e impede que esta versão seja capturada novamente", "BlocklistReleases": "Lançamentos na lista de bloqueio", "CloneCondition": "Clonar Condição", @@ -96,7 +95,7 @@ "DeleteCondition": "Excluir condição", "DeleteConditionMessageText": "Tem certeza de que deseja excluir a condição '{name}'?", "DeleteCustomFormat": "Excluir formato personalizado", - "DeleteCustomFormatMessageText": "Tem certeza de que deseja excluir o formato personalizado '{0}'?", + "DeleteCustomFormatMessageText": "Tem certeza de que deseja excluir o formato personalizado '{customFormatName}'?", "ExportCustomFormat": "Exportar Formato Personalizado", "Negated": "Negado", "Remove": "Remover", @@ -400,7 +399,7 @@ "SomeResultsAreHiddenByTheAppliedFilter": "Alguns resultados estão ocultos pelo filtro aplicado", "UnableToLoadAutoTagging": "Não foi possível carregar a marcação automática", "UnableToLoadRootFolders": "Não foi possível carregar as pastas raiz", - "IndexerDownloadClientHealthCheckMessage": "Indexadores com clientes de download inválidos: {0}.", + "IndexerDownloadClientHealthCheckMessage": "Indexadores com clientes de download inválidos: {indexerNames}.", "AddConditionError": "Não foi possível adicionar uma nova condição. Tente novamente.", "AddConnection": "Adicionar Conexão", "AddCustomFormat": "Adicionar Formato Personalizado", @@ -901,7 +900,7 @@ "UpdateAutomaticallyHelpText": "Baixe e instale atualizações automaticamente. Você ainda poderá instalar a partir do Sistema: Atualizações", "UpdateMechanismHelpText": "Use o atualizador integrado do {appName} ou um script", "UpdateSonarrDirectlyLoadError": "Incapaz de atualizar o {appName} diretamente,", - "UpdateUiNotWritableHealthCheckMessage": "Não é possível instalar a atualização porque a pasta de IU '{0}' não pode ser salva pelo usuário '{1}'.", + "UpdateUiNotWritableHealthCheckMessage": "Não é possível instalar a atualização porque a pasta de IU '{uiFolder}' não pode ser salva pelo usuário '{userName}'.", "UpgradeUntil": "Atualizar Até", "UpgradeUntilCustomFormatScore": "Atualizar até pontuação de formato personalizado", "UpgradeUntilHelpText": "Quando essa qualidade for atingida, o {appName} não fará mais download de episódios", @@ -1257,7 +1256,7 @@ "MoveFiles": "Mover Arquivos", "MultiLanguages": "Multi-Idiomas", "NoEpisodesFoundForSelectedSeason": "Nenhum episódio foi encontrado para a temporada selecionada", - "NotificationStatusSingleClientHealthCheckMessage": "Notificações indisponíveis devido a falhas: {0}", + "NotificationStatusSingleClientHealthCheckMessage": "Notificações indisponíveis devido a falhas: {notificationNames}", "Or": "ou", "Organize": "Organizar", "OrganizeLoadError": "Erro ao carregar visualizações", @@ -1484,5 +1483,5 @@ "FormatShortTimeSpanSeconds": "{seconds} segundo(s)", "FormatTimeSpanDays": "{days}d {time}", "Yesterday": "Ontem", - "DownloadClientRemovesCompletedDownloadsHealthCheckMessage": "O cliente de download {0} está configurado para remover downloads concluídos. Isso pode resultar na remoção dos downloads do seu cliente antes que {1} possa importá-los." + "DownloadClientRemovesCompletedDownloadsHealthCheckMessage": "O cliente de download {downloadClientName} está configurado para remover downloads concluídos. Isso pode resultar na remoção dos downloads do seu cliente antes que {appName} possa importá-los." } diff --git a/src/NzbDrone.Core/Localization/Core/ro.json b/src/NzbDrone.Core/Localization/Core/ro.json index 212c2e146..44a02672a 100644 --- a/src/NzbDrone.Core/Localization/Core/ro.json +++ b/src/NzbDrone.Core/Localization/Core/ro.json @@ -20,13 +20,13 @@ "ApplyTagsHelpTextAdd": "Adăugare: adăugați etichetele la lista de etichete existentă", "ApplyTagsHelpTextReplace": "Înlocuire: înlocuiți etichetele cu etichetele introduse (nu introduceți etichete pentru a șterge toate etichetele)", "CancelPendingTask": "Sigur doriți să anulați această sarcină în așteptare?", - "DownloadClientCheckUnableToCommunicateWithHealthCheckMessage": "Nu pot comunica cu {0}.", + "DownloadClientCheckUnableToCommunicateWithHealthCheckMessage": "Nu pot comunica cu {downloadClientName}.", "CloneCustomFormat": "Clonați format personalizat", "Close": "Închide", "Delete": "Șterge", "Added": "Adăugat", "CountSeasons": "{count} sezoane", - "DownloadClientStatusSingleClientHealthCheckMessage": "Clienții de descărcare indisponibili datorită erorilor: {0}", + "DownloadClientStatusSingleClientHealthCheckMessage": "Clienții de descărcare indisponibili datorită erorilor: {downloadClientNames}", "EnableAutomaticSearch": "Activați căutarea automată", "EnableInteractiveSearch": "Activați căutarea interactivă", "Enabled": "Activat", @@ -69,7 +69,7 @@ "DotNetVersion": ".NET", "Download": "Descarca", "DownloadClient": "Client de descărcare", - "DownloadClientRootFolderHealthCheckMessage": "Clientul de descărcare {0} plasează descărcările în folderul rădăcină {1}. Nu trebuie să descărcați într-un folder rădăcină.", + "DownloadClientRootFolderHealthCheckMessage": "Clientul de descărcare {downloadClientName} plasează descărcările în folderul rădăcină {rootFolderPath}. Nu trebuie să descărcați într-un folder rădăcină.", "Episode": "Episod", "EpisodeTitle": "Titlu episod", "Episodes": "Episoade", @@ -151,7 +151,7 @@ "InteractiveImportNoImportMode": "Un mod de import trebuie selectat", "InteractiveImportNoFilesFound": "Nu au fost găsite fișiere video în folderul selectat", "InteractiveImportLoadError": "Imposibil de încărcat articole de import manual", - "NotificationStatusSingleClientHealthCheckMessage": "Notificări indisponibile datorită erorilor: {0}", + "NotificationStatusSingleClientHealthCheckMessage": "Notificări indisponibile datorită erorilor: {notificationNames}", "ParseModalUnableToParse": "Nu se poate analiza titlul furnizat, vă rugăm să încercați din nou.", "ParseModalErrorParsing": "Eroare la analizare, încercați din nou.", "Parse": "Analiza", diff --git a/src/NzbDrone.Core/Localization/Core/ru.json b/src/NzbDrone.Core/Localization/Core/ru.json index 08eaa7871..18e1cfe81 100644 --- a/src/NzbDrone.Core/Localization/Core/ru.json +++ b/src/NzbDrone.Core/Localization/Core/ru.json @@ -1,16 +1,16 @@ { - "ApiKeyValidationHealthCheckMessage": "Пожалуйста, обновите свой ключ API, чтобы он был длиной не менее {0} символов. Вы можете сделать это через настройки или файл конфигурации", - "DownloadClientSortingHealthCheckMessage": "В клиенте загрузки {0} включена сортировка {1} для категории {appName}. Вам следует отключить сортировку в вашем клиенте загрузки, чтобы избежать проблем с импортом.", - "IndexerJackettAllHealthCheckMessage": "Используется не поддерживаемый в Jackett конечный параметр 'all' в индексаторе: {0}", + "ApiKeyValidationHealthCheckMessage": "Пожалуйста, обновите свой ключ API, чтобы он был длиной не менее {length} символов. Вы можете сделать это через настройки или файл конфигурации", + "DownloadClientSortingHealthCheckMessage": "В клиенте загрузки {downloadClientName} включена сортировка {sortingMode} для категории {appName}. Вам следует отключить сортировку в вашем клиенте загрузки, чтобы избежать проблем с импортом.", + "IndexerJackettAllHealthCheckMessage": "Используется не поддерживаемый в Jackett конечный параметр 'all' в индексаторе: {indexerNames}", "IndexerSearchNoAutomaticHealthCheckMessage": "Нет доступных индексаторов с включенным автоматическим поиском, {appName} не будет предоставлять результаты автоматического поиска", "Added": "Добавлено", "AppDataLocationHealthCheckMessage": "Обновление будет не возможно, во избежание удаления данных программы во время обновления", "ApplyChanges": "Применить изменения", "DownloadClientCheckNoneAvailableHealthCheckMessage": "Ни один загрузчик не доступен", - "DownloadClientCheckUnableToCommunicateWithHealthCheckMessage": "Невозможно связаться с {0}.", - "DownloadClientRootFolderHealthCheckMessage": "Клиент загрузки {0} помещает загрузки в корневую папку {1}. Вы не должны загружать в корневую папку.", + "DownloadClientCheckUnableToCommunicateWithHealthCheckMessage": "Невозможно связаться с {downloadClientName}.", + "DownloadClientRootFolderHealthCheckMessage": "Клиент загрузки {downloadClientName} помещает загрузки в корневую папку {rootFolderPath}. Вы не должны загружать в корневую папку.", "DownloadClientStatusAllClientHealthCheckMessage": "Все клиенты загрузки недоступны из-за сбоев", - "DownloadClientStatusSingleClientHealthCheckMessage": "Клиенты для скачивания недоступны из-за ошибок: {0}", + "DownloadClientStatusSingleClientHealthCheckMessage": "Клиенты для скачивания недоступны из-за ошибок: {downloadClientNames}", "EditSelectedDownloadClients": "Редактировать выбранные клиенты загрузки", "EditSelectedImportLists": "Редактировать выбранные списки импорта", "EditSeries": "Редактировать серию", @@ -19,14 +19,14 @@ "Enabled": "Включено", "HiddenClickToShow": "Скрыто, нажмите чтобы показать", "HideAdvanced": "Скрыть расширенные", - "ImportListRootFolderMissingRootHealthCheckMessage": "Отсутствует корневая папка для импортирования списка(ов): {0}", - "ImportListRootFolderMultipleMissingRootsHealthCheckMessage": "Для импортируемых списков отсутствуют несколько корневых папок: {0}", + "ImportListRootFolderMissingRootHealthCheckMessage": "Отсутствует корневая папка для импортирования списка(ов): {rootFolderInfo}", + "ImportListRootFolderMultipleMissingRootsHealthCheckMessage": "Для импортируемых списков отсутствуют несколько корневых папок: {rootFoldersInfo}", "ImportListStatusAllUnavailableHealthCheckMessage": "Все листы недоступны из-за ошибок", - "ImportListStatusUnavailableHealthCheckMessage": "Листы недоступны из-за ошибок: {0}", + "ImportListStatusUnavailableHealthCheckMessage": "Листы недоступны из-за ошибок: {importListNames}", "ImportMechanismEnableCompletedDownloadHandlingIfPossibleHealthCheckMessage": "Включить обработку завершенной загрузки, если это возможно", "ImportMechanismHandlingDisabledHealthCheckMessage": "Включить обработку завершенных скачиваний", "IndexerLongTermStatusAllUnavailableHealthCheckMessage": "Все индексаторы недоступны из-за ошибок за последние 6 часов", - "IndexerLongTermStatusUnavailableHealthCheckMessage": "Все индексаторы недоступны из-за ошибок за последние 6 часов: {0}", + "IndexerLongTermStatusUnavailableHealthCheckMessage": "Все индексаторы недоступны из-за ошибок за последние 6 часов: {indexerNames}", "IndexerRssNoIndexersAvailableHealthCheckMessage": "Все RSS индексаторы временно выключены из-за ошибок", "IndexerRssNoIndexersEnabledHealthCheckMessage": "Нет доступных индексаторов с включенной синхронизацией RSS, {appName} не будет автоматически получать новые выпуски", "IndexerSearchNoAvailableIndexersHealthCheckMessage": "Все индексаторы с возможностью поиска временно выключены из-за ошибок", @@ -35,12 +35,12 @@ "EditConditionImplementation": "Редактировать условие - {implementationName}", "EditImportListImplementation": "Редактировать импорт лист - {implementationName}", "Implementation": "Реализация", - "IndexerDownloadClientHealthCheckMessage": "Индексаторы с недопустимыми клиентами загрузки: {0}.", + "IndexerDownloadClientHealthCheckMessage": "Индексаторы с недопустимыми клиентами загрузки: {indexerNames}.", "ManageClients": "Управление клиентами", "ManageIndexers": "Управление индексаторами", "MoveAutomatically": "Перемещать автоматически", "NoDownloadClientsFound": "Клиенты для загрузки не найдены", - "NotificationStatusSingleClientHealthCheckMessage": "Уведомления недоступны из-за сбоев: {0}", + "NotificationStatusSingleClientHealthCheckMessage": "Уведомления недоступны из-за сбоев: {notificationNames}", "CountIndexersSelected": "{count} выбранных индексаторов", "EditAutoTag": "Редактировать автоматическую маркировку", "NoHistoryBlocklist": "Нет истории блокировок", diff --git a/src/NzbDrone.Core/Localization/Core/vi.json b/src/NzbDrone.Core/Localization/Core/vi.json index 0d4b4e5c9..493d34b23 100644 --- a/src/NzbDrone.Core/Localization/Core/vi.json +++ b/src/NzbDrone.Core/Localization/Core/vi.json @@ -2,7 +2,7 @@ "BlocklistRelease": "Chặn bản phát hành", "BlocklistReleases": "Phát hành danh sách đen", "Added": "Đã thêm", - "ApiKeyValidationHealthCheckMessage": "Hãy cập nhật mã API để dài ít nhất {0} kí tự. Bạn có thể làm điều này trong cài đặt hoặc trong tập config", + "ApiKeyValidationHealthCheckMessage": "Hãy cập nhật mã API để dài ít nhất {length} kí tự. Bạn có thể làm điều này trong cài đặt hoặc trong tập config", "AppDataLocationHealthCheckMessage": "Việc cập nhật sẽ không xảy ra để tránh xóa AppData khi cập nhật", "ApplyChanges": "Áp dụng thay đổi", "AutomaticAdd": "Tự động thêm" diff --git a/src/NzbDrone.Core/Localization/Core/zh_CN.json b/src/NzbDrone.Core/Localization/Core/zh_CN.json index 0b8833c91..a7e693399 100644 --- a/src/NzbDrone.Core/Localization/Core/zh_CN.json +++ b/src/NzbDrone.Core/Localization/Core/zh_CN.json @@ -2,8 +2,8 @@ "CloneCondition": "克隆条件", "DeleteCondition": "删除条件", "DeleteConditionMessageText": "你确定要删除条件 “{name}” 吗?", - "DeleteCustomFormatMessageText": "是否确实要删除条件“{0}”?", - "ApiKeyValidationHealthCheckMessage": "请将API密钥更新为至少{0}个字符长。您可以通过设置或配置文件执行此操作", + "DeleteCustomFormatMessageText": "是否确实要删除条件“{customFormatName}”?", + "ApiKeyValidationHealthCheckMessage": "请将API密钥更新为至少{length}个字符长。您可以通过设置或配置文件执行此操作", "RemoveSelectedItemQueueMessageText": "您确定要从队列中删除一个项目吗?", "RemoveSelectedItemsQueueMessageText": "您确定要从队列中删除{selectedCount}项吗?", "ApplyChanges": "应用更改", @@ -63,22 +63,22 @@ "Metadata": "元数据", "CountSeasons": "季{count}", "DownloadClientCheckNoneAvailableHealthCheckMessage": "无可用的下载客户端", - "DownloadClientCheckUnableToCommunicateWithHealthCheckMessage": "无法与{0}进行通讯。", - "DownloadClientRootFolderHealthCheckMessage": "下载客户端{0}将下载内容放在根文件夹{1}中。您不应该下载到根文件夹。", - "DownloadClientSortingHealthCheckMessage": "下载客户端{0}已为{appName}的类别启用{1}排序。您应该在下载客户端中禁用排序,以避免导入问题。", + "DownloadClientCheckUnableToCommunicateWithHealthCheckMessage": "无法与{downloadClientName}进行通讯。", + "DownloadClientRootFolderHealthCheckMessage": "下载客户端{downloadClientName}将下载内容放在根文件夹{rootFolderPath}中。您不应该下载到根文件夹。", + "DownloadClientSortingHealthCheckMessage": "下载客户端{downloadClientName}已为{appName}的类别启用{sortingMode}排序。您应该在下载客户端中禁用排序,以避免导入问题。", "DownloadClientStatusAllClientHealthCheckMessage": "所有下载客户端都不可用", - "DownloadClientStatusSingleClientHealthCheckMessage": "所有下载客户端都不可用:{0}", + "DownloadClientStatusSingleClientHealthCheckMessage": "所有下载客户端都不可用:{downloadClientNames}", "EditSeries": "编辑剧集", "Enabled": "已启用", "Ended": "已完结", - "ImportListRootFolderMissingRootHealthCheckMessage": "在导入列表中缺少根目录文件夹:{0}", - "ImportListRootFolderMultipleMissingRootsHealthCheckMessage": "导入列表中缺失多个根目录文件夹:{0}", + "ImportListRootFolderMissingRootHealthCheckMessage": "在导入列表中缺少根目录文件夹:{rootFolderInfo}", + "ImportListRootFolderMultipleMissingRootsHealthCheckMessage": "导入列表中缺失多个根目录文件夹:{rootFoldersInfo}", "ImportListStatusAllUnavailableHealthCheckMessage": "所有的列表因错误不可用", - "ImportListStatusUnavailableHealthCheckMessage": "列表因错误不可用:{0}", + "ImportListStatusUnavailableHealthCheckMessage": "列表因错误不可用:{importListNames}", "ImportMechanismEnableCompletedDownloadHandlingIfPossibleMultiComputerHealthCheckMessage": "如果可能,启用完整的下载处理(不支持多台计算机)", "ImportMechanismHandlingDisabledHealthCheckMessage": "启用下载完成处理", - "IndexerJackettAllHealthCheckMessage": "使用 Jackett 不受支持的“全部”终点的索引器:{0}", - "IndexerLongTermStatusUnavailableHealthCheckMessage": "由于故障6小时,下列索引器都已不可用:{0}", + "IndexerJackettAllHealthCheckMessage": "使用 Jackett 不受支持的“全部”终点的索引器:{indexerNames}", + "IndexerLongTermStatusUnavailableHealthCheckMessage": "由于故障6小时,下列索引器都已不可用:{indexerNames}", "IndexerRssNoIndexersAvailableHealthCheckMessage": "由于最近的索引器错误,所有支持rss的索引器暂时不可用", "IndexerRssNoIndexersEnabledHealthCheckMessage": "没有启用RSS同步的索引器,{appName}不会自动抓取新版本", "IndexerSearchNoAutomaticHealthCheckMessage": "没有启用自动搜索的索引器,{appName}不会提供任何自动搜索结果", @@ -89,12 +89,12 @@ "OneSeason": "季1", "OriginalLanguage": "原语言", "Priority": "优先级", - "ProxyBadRequestHealthCheckMessage": "测试代理失败。状态代码:{0}", - "RecycleBinUnableToWriteHealthCheckMessage": "配置文件夹:{0}无法写入,检查此路径是否存在,并且是否可由{appName}写入", + "ProxyBadRequestHealthCheckMessage": "测试代理失败。状态代码:{statusCode}", + "RecycleBinUnableToWriteHealthCheckMessage": "配置文件夹:{path}无法写入,检查此路径是否存在,并且是否可由{appName}写入", "QualityProfile": "质量配置", "RefreshSeries": "刷新节目", - "RemotePathMappingBadDockerPathHealthCheckMessage": "您正在使用docker;下载客户端{0}下载目录为{1},但这不是有效的{2}路径。查看Docker路径映射并为下载客户端重新配置。", - "RemotePathMappingDownloadPermissionsHealthCheckMessage": "{appName}已存在剧集目录{0}但无法访问。可能是权限错误。", + "RemotePathMappingBadDockerPathHealthCheckMessage": "您正在使用docker;下载客户端{downloadClientName}下载目录为{path},但这不是有效的{osName}路径。查看Docker路径映射并为下载客户端重新配置。", + "RemotePathMappingDownloadPermissionsHealthCheckMessage": "{appName}已存在剧集目录{path}但无法访问。可能是权限错误。", "Mode": "模式", "MoreInfo": "更多信息", "New": "新的", @@ -107,7 +107,7 @@ "NoLeaveIt": "不,就这样", "NoLogFiles": "没有日志文件", "Options": "选项", - "RemotePathMappingDockerFolderMissingHealthCheckMessage": "您正在使用docker;下载客户端{0}下载目录为{1},但容器中似乎不存在此目录。请检查Docker路径映射。", + "RemotePathMappingDockerFolderMissingHealthCheckMessage": "您正在使用docker;下载客户端$1{downloadClientName}下载目录为{path},但容器中似乎不存在此目录。请检查Docker路径映射。", "AirDate": "播出日期", "Daily": "日常的", "FullSeason": "全部剧集", @@ -115,7 +115,7 @@ "ImportLists": "导入列表", "ExistingTag": "已有标签", "IndexerLongTermStatusAllUnavailableHealthCheckMessage": "由于故障超过6小时,所有索引器均不可用", - "IndexerStatusUnavailableHealthCheckMessage": "搜刮器因错误不可用:{0}", + "IndexerStatusUnavailableHealthCheckMessage": "搜刮器因错误不可用:{indexerNames}", "IndexerSearchNoAvailableIndexersHealthCheckMessage": "由于最近的索引器错误,所有支持搜索的索引器暂时不可用", "LogFiles": "日志文件", "MatchedToEpisodes": "与剧集匹配", @@ -132,7 +132,7 @@ "Proper": "合适的", "ReleaseGroup": "发布组", "ReleaseTitle": "发行版标题", - "RemotePathMappingFileRemovedHealthCheckMessage": "文件{0} 在处理的过程中被部分删除。", + "RemotePathMappingFileRemovedHealthCheckMessage": "文件{path} 在处理的过程中被部分删除。", "AutoAdd": "自动添加", "Cancel": "取消", "DeleteSelectedDownloadClientsMessageText": "您确定要删除{count}选定的下载客户端吗?", @@ -144,7 +144,7 @@ "LibraryImport": "媒体库导入", "PreviousAiring": "上一次播出", "Profiles": "配置", - "ProxyResolveIpHealthCheckMessage": "无法解析已设置的代理服务器主机{0}的IP地址", + "ProxyResolveIpHealthCheckMessage": "无法解析已设置的代理服务器主机{proxyHostName}的IP地址", "Quality": "媒体质量", "Queue": "队列", "Real": "真的", @@ -223,7 +223,7 @@ "EnableAutomaticSearch": "启用自动搜索", "EpisodeAirDate": "剧集播出日期", "IndexerSearchNoInteractiveHealthCheckMessage": "没有启用交互式搜索的索引器,{appName}将不提供任何交互式搜索结果", - "ProxyFailedToTestHealthCheckMessage": "测试代理失败: {0}", + "ProxyFailedToTestHealthCheckMessage": "测试代理失败: {url}", "About": "关于", "Actions": "动作", "AppDataDirectory": "AppData目录", @@ -274,42 +274,41 @@ "Tags": "标签", "Series": "节目", "ImportMechanismEnableCompletedDownloadHandlingIfPossibleHealthCheckMessage": "如果可能,在下载完成后自动处理", - "RemotePathMappingGenericPermissionsHealthCheckMessage": "下载客户端{0}将文件下载在{1}中,但{appName}无法找到此目录。您可能需要调整文件夹的权限。", - "RemotePathMappingLocalWrongOSPathHealthCheckMessage": "本地下载客户端{0}将文件下载在{1}中,但这不是有效的{2}路径。查看您的下载客户端设置。", - "RemotePathMappingRemoteDownloadClientHealthCheckMessage": "远程下载客户端{0}报告了{1}中的文件,但此目录似乎不存在。可能缺少远程路径映射。", + "RemotePathMappingGenericPermissionsHealthCheckMessage": "下载客户端{downloadClientName}将文件下载在{path}中,但{appName}无法找到此目录。您可能需要调整文件夹的权限。", + "RemotePathMappingLocalWrongOSPathHealthCheckMessage": "本地下载客户端{downloadClientName}将文件下载在{path}中,但这不是有效的{osName}路径。查看您的下载客户端设置。", + "RemotePathMappingRemoteDownloadClientHealthCheckMessage": "远程下载客户端{downloadClientName}报告了{path}中的文件,但此目录似乎不存在。可能缺少远程路径映射。", "IRCLinkText": "#Libera上的{appName}", "LiberaWebchat": "Libera聊天", - "RemotePathMappingFilesBadDockerPathHealthCheckMessage": "您正在使用Docker;下载客户端{0}报告了{1}中的文件,但这不是有效的{2}中的路径。查看Docker路径映射并更新下载客户端设置。", - "RemotePathMappingFilesGenericPermissionsHealthCheckMessage": "下载客户端{0}报告的文件在{1},但{appName}无法查看此目录。您可能需要调整文件夹的权限。", + "RemotePathMappingFilesBadDockerPathHealthCheckMessage": "您正在使用Docker;下载客户端{downloadClientName}报告了{path}中的文件,但这不是有效的{osName}中的路径。查看Docker路径映射并更新下载客户端设置。", + "RemotePathMappingFilesGenericPermissionsHealthCheckMessage": "下载客户端{downloadClientName}报告的文件在{path},但{appName}无法查看此目录。您可能需要调整文件夹的权限。", "RemovedFromTaskQueue": "从任务队列中删除", - "RemotePathMappingFilesLocalWrongOSPathHealthCheckMessage": "本地下载客户端{0}报告了{1}中的文件,但这不是有效的{2}路径。查看您的下载客户端设置。", - "RemotePathMappingFilesWrongOSPathHealthCheckMessage": "远程下载客户端{0}报告了{1}中的文件,但这不是有效的{2}路径。查看远程路径映射并更新下载客户端设置。", + "RemotePathMappingFilesLocalWrongOSPathHealthCheckMessage": "本地下载客户端{downloadClientName}报告了{path}中的文件,但这不是有效的{osName}路径。查看您的下载客户端设置。", + "RemotePathMappingFilesWrongOSPathHealthCheckMessage": "远程下载客户端{downloadClientName}报告了{path}中的文件,但这不是有效的{osName}路径。查看远程路径映射并更新下载客户端设置。", "Restart": "重启", "RestartReloadNote": "注意:{appName}将在恢复过程中自动重启并重新加载UI。", "Restore": "恢复", "RootFolder": "根目录", "RemoveSelectedItems": "删除所选项目", - "RemovedSeriesSingleRemovedHealthCheckMessage": "节目{0}已从TVDB中删除", - "RootFolderMissingHealthCheckMessage": "缺少根目录: {0}", - "RootFolderMultipleMissingHealthCheckMessage": "多个根目录缺失:{0}", + "RemovedSeriesSingleRemovedHealthCheckMessage": "节目{series}已从TVDB中删除", + "RootFolderMissingHealthCheckMessage": "缺少根目录: {rootFolderPath}", + "RootFolderMultipleMissingHealthCheckMessage": "多个根目录缺失:{rootFolderPaths}", "SkipRedownloadHelpText": "阻止{appName}尝试下载此项目的替代版本", "Tasks": "任务", "Wanted": "想要的", "Yes": "确定", - "UpdateUINotWritableHealthCheckMessage": "无法安装升级,因为用户“{1}”不可写入界面文件夹“{0}”。", "AbsoluteEpisodeNumbers": "准确的集数", "RemoveCompleted": "移除已完成", "RemoveFailed": "删除失败", - "RemovedSeriesMultipleRemovedHealthCheckMessage": "已从TVDB中删除节目{0}", - "RemotePathMappingFolderPermissionsHealthCheckMessage": "下载目录{0}已存在但{appName}无法访问。可能是权限错误。", + "RemovedSeriesMultipleRemovedHealthCheckMessage": "已从TVDB中删除节目{series}", + "RemotePathMappingFolderPermissionsHealthCheckMessage": "下载目录{downloadPath}已存在但{appName}无法访问。可能是权限错误。", "RemovingTag": "移除标签", - "RemotePathMappingLocalFolderMissingHealthCheckMessage": "远程下载客户端{0}将文件下载在{1}中,但此目录似乎不存在。可能缺少或不正确的远程路径映射。", + "RemotePathMappingLocalFolderMissingHealthCheckMessage": "远程下载客户端{downloadClientName}将文件下载在{path}中,但此目录似乎不存在。可能缺少或不正确的远程路径映射。", "Replace": "替换", "Repack": "重新打包", "Version": "版本", "Special": "特色", "RemotePathMappingImportFailedHealthCheckMessage": "{appName}无法导入剧集。查看日志以了解详细信息。", - "RemotePathMappingWrongOSPathHealthCheckMessage": "远程下载客户端{0}将文件下载在{1}中,但这不是有效的{2}路径。查看远程路径映射并更新下载客户端设置。", + "RemotePathMappingWrongOSPathHealthCheckMessage": "远程下载客户端{downloadClientName}将文件下载在{path}中,但这不是有效的{osName}路径。查看远程路径映射并更新下载客户端设置。", "RemoveFromDownloadClientHelpTextWarning": "删除将从下载客户端删除下载和文件。", "Renamed": "已重命名", "RootFolderPath": "根目录路径", @@ -325,7 +324,7 @@ "Title": "标题", "Type": "类型", "UnmonitoredOnly": "监控中", - "UpdateStartupTranslocationHealthCheckMessage": "无法安装更新,因为启动文件夹“{0}”在一个应用程序迁移文件夹。", + "UpdateStartupTranslocationHealthCheckMessage": "无法安装更新,因为启动文件夹“{startupFolder}”在一个应用程序迁移文件夹。", "Updates": "更新", "VideoCodec": "视频编码", "VideoDynamicRange": "视频动态范围", @@ -334,7 +333,7 @@ "SeasonCount": "季数量", "SystemTimeHealthCheckMessage": "系统时间相差超过1天。在纠正时间之前,计划的任务可能无法正确运行", "TestAll": "测试全部", - "UpdateStartupNotWritableHealthCheckMessage": "无法安装更新,因为用户“{1}”对于启动文件夹“{0}”没有写入权限。", + "UpdateStartupNotWritableHealthCheckMessage": "无法安装更新,因为用户“{userName}”对于启动文件夹“{startupFolder}”没有写入权限。", "SeriesTitle": "节目标题", "SetTags": "设置标签", "Size": "大小", @@ -841,7 +840,7 @@ "SourceRelativePath": "源相对路径", "MetadataSourceSettingsSummary": "{appName}从哪里获得剧集和集信息的总结", "SslCertPassword": "SSL证书密码", - "UpdateUiNotWritableHealthCheckMessage": "无法安装更新,因为用户“{1}”无法写入 UI 文件夹“{0}”。", + "UpdateUiNotWritableHealthCheckMessage": "无法安装更新,因为用户“{userName}”无法写入 UI 文件夹“{uiFolder}”。", "MinimumCustomFormatScoreHelpText": "允许下载的最小自定义格式分数", "SslCertPasswordHelpText": "pfx文件密码", "UpgradeUntilCustomFormatScore": "升级直到自定义格式分数满足", @@ -942,7 +941,7 @@ "HourShorthand": "时", "ImportedTo": "导入到", "Importing": "导入中", - "IndexerDownloadClientHealthCheckMessage": "有无效下载客户端的索引器:{0}。", + "IndexerDownloadClientHealthCheckMessage": "有无效下载客户端的索引器:{indexerNames}。", "IndexersSettingsSummary": "索引器和索引器选项", "InteractiveImport": "手动导入", "InstanceNameHelpText": "选项卡及日志应用名称", @@ -1311,7 +1310,7 @@ "MyComputer": "我的电脑", "NamingSettings": "命名设置", "NoEpisodeOverview": "没有集摘要", - "NotificationStatusSingleClientHealthCheckMessage": "由于失败导致通知不可用:{0}", + "NotificationStatusSingleClientHealthCheckMessage": "由于失败导致通知不可用:{notificationNames}", "NotificationTriggers": "通知触发器", "NotificationsLoadError": "无法加载通知连接", "OnApplicationUpdate": "程序更新时", @@ -1484,5 +1483,5 @@ "Files": "文件", "SeriesDetailsOneEpisodeFile": "1个集文件", "UrlBase": "基本URL", - "DownloadClientRemovesCompletedDownloadsHealthCheckMessage": "下载客户端{0}设置为删除已完成的下载。这可能导致在{1}可以导入下载之前从您的客户端删除下载。" + "DownloadClientRemovesCompletedDownloadsHealthCheckMessage": "下载客户端{downloadClientName}设置为删除已完成的下载。这可能导致在{appName}可以导入下载之前从您的客户端删除下载。" } diff --git a/src/NzbDrone.Core/Localization/Core/zh_TW.json b/src/NzbDrone.Core/Localization/Core/zh_TW.json index e075c19dd..3ec1abed9 100644 --- a/src/NzbDrone.Core/Localization/Core/zh_TW.json +++ b/src/NzbDrone.Core/Localization/Core/zh_TW.json @@ -1,6 +1,6 @@ { "BlocklistRelease": "封鎖清單版本", "BlocklistReleases": "封鎖清單版本", - "ApiKeyValidationHealthCheckMessage": "請將您的API金鑰更新為至少{0}個字元長。您可以通過設定或配置文件進行此操作。", + "ApiKeyValidationHealthCheckMessage": "請將您的API金鑰更新為至少{length}個字元長。您可以通過設定或配置文件進行此操作。", "AppDataLocationHealthCheckMessage": "為了避免在更新過程中刪除AppData,將無法進行更新。" } diff --git a/src/NzbDrone.Core/Localization/LocalizationService.cs b/src/NzbDrone.Core/Localization/LocalizationService.cs index d0c5c8eb7..9e8df41f3 100644 --- a/src/NzbDrone.Core/Localization/LocalizationService.cs +++ b/src/NzbDrone.Core/Localization/LocalizationService.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Text.Json; +using System.Text.RegularExpressions; using System.Threading.Tasks; using NLog; using NzbDrone.Common.Cache; @@ -18,14 +19,17 @@ namespace NzbDrone.Core.Localization public interface ILocalizationService { Dictionary<string, string> GetLocalizationDictionary(); + string GetLocalizedString(string phrase); - string GetLocalizedString(string phrase, string language); + string GetLocalizedString(string phrase, Dictionary<string, object> tokens); string GetLanguageIdentifier(); } public class LocalizationService : ILocalizationService, IHandleAsync<ConfigSavedEvent> { private const string DefaultCulture = "en"; + private static readonly Regex TokenRegex = new Regex(@"(?:\{)(?<token>[a-z0-9]+)(?:\})", + RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); private readonly ICached<Dictionary<string, string>> _cache; @@ -53,23 +57,18 @@ namespace NzbDrone.Core.Localization public string GetLocalizedString(string phrase) { - var language = GetLanguageFileName(); - - return GetLocalizedString(phrase, language); + return GetLocalizedString(phrase, new Dictionary<string, object>()); } - public string GetLocalizedString(string phrase, string language) + public string GetLocalizedString(string phrase, Dictionary<string, object> tokens) { + var language = GetLanguageFileName(); + if (string.IsNullOrEmpty(phrase)) { throw new ArgumentNullException(nameof(phrase)); } - if (language.IsNullOrWhiteSpace()) - { - language = GetLanguageFileName(); - } - if (language == null) { language = DefaultCulture; @@ -79,7 +78,7 @@ namespace NzbDrone.Core.Localization if (dictionary.TryGetValue(phrase, out var value)) { - return value; + return ReplaceTokens(value, tokens); } return phrase; @@ -98,6 +97,20 @@ namespace NzbDrone.Core.Localization return language; } + private string ReplaceTokens(string input, Dictionary<string, object> tokens) + { + tokens.TryAdd("appName", "Sonarr"); + + return TokenRegex.Replace(input, (match) => + { + var tokenName = match.Groups["token"].Value; + + tokens.TryGetValue(tokenName, out var token); + + return token?.ToString() ?? $"{{{tokenName}}}"; + }); + } + private string GetLanguageFileName() { return GetLanguageIdentifier().Replace("-", "_").ToLowerInvariant(); From fd789343b587da252461d84bafba2d72651a11df Mon Sep 17 00:00:00 2001 From: Mark McDowall <mark@mcdowall.ca> Date: Sun, 21 May 2023 17:51:22 -0700 Subject: [PATCH 054/136] Cleanup Calendar custom filters --- frontend/src/App/State/CalendarAppState.ts | 11 ++++++----- frontend/src/Calendar/CalendarFilterModal.tsx | 6 ++---- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/frontend/src/App/State/CalendarAppState.ts b/frontend/src/App/State/CalendarAppState.ts index 304068528..de6a523b3 100644 --- a/frontend/src/App/State/CalendarAppState.ts +++ b/frontend/src/App/State/CalendarAppState.ts @@ -1,9 +1,10 @@ -import AppSectionState from 'App/State/AppSectionState'; +import AppSectionState, { + AppSectionFilterState, +} from 'App/State/AppSectionState'; import Episode from 'Episode/Episode'; -import { FilterBuilderProp } from './AppState'; -interface CalendarAppState extends AppSectionState<Episode> { - filterBuilderProps: FilterBuilderProp<Episode>[]; -} +interface CalendarAppState + extends AppSectionState<Episode>, + AppSectionFilterState<Episode> {} export default CalendarAppState; diff --git a/frontend/src/Calendar/CalendarFilterModal.tsx b/frontend/src/Calendar/CalendarFilterModal.tsx index c09f73743..e26b2928b 100644 --- a/frontend/src/Calendar/CalendarFilterModal.tsx +++ b/frontend/src/Calendar/CalendarFilterModal.tsx @@ -23,13 +23,11 @@ function createFilterBuilderPropsSelector() { ); } -interface SeriesIndexFilterModalProps { +interface CalendarFilterModalProps { isOpen: boolean; } -export default function CalendarFilterModal( - props: SeriesIndexFilterModalProps -) { +export default function CalendarFilterModal(props: CalendarFilterModalProps) { const sectionItems = useSelector(createCalendarSelector()); const filterBuilderProps = useSelector(createFilterBuilderPropsSelector()); const customFilterType = 'calendar'; From e357d17b187378b92377f8acb077b12c1e7ea527 Mon Sep 17 00:00:00 2001 From: Mark McDowall <mark@mcdowall.ca> Date: Sun, 21 May 2023 17:51:36 -0700 Subject: [PATCH 055/136] New: Queue custom filters --- frontend/src/Activity/Queue/Queue.js | 29 +++++++++- frontend/src/Activity/Queue/QueueConnector.js | 13 ++++- .../src/Activity/Queue/QueueFilterModal.tsx | 54 +++++++++++++++++++ frontend/src/App/State/AppSectionState.ts | 5 ++ frontend/src/App/State/QueueAppState.ts | 10 +++- .../Filter/Builder/FilterBuilderRow.js | 8 +++ .../Builder/FilterBuilderRowValueProps.ts | 16 ++++++ .../Builder/LanguageFilterBuilderRowValue.tsx | 13 +++++ .../Builder/SeriesFilterBuilderRowValue.tsx | 21 ++++++++ .../Helpers/Props/filterBuilderValueTypes.js | 2 + .../createFetchServerSideCollectionHandler.js | 12 +++-- frontend/src/Store/Actions/calendarActions.js | 2 - frontend/src/Store/Actions/queueActions.js | 47 ++++++++++++++-- src/NzbDrone.Core/Localization/Core/en.json | 1 + src/Sonarr.Api.V3/Queue/QueueController.cs | 38 +++++++++++-- 15 files changed, 254 insertions(+), 17 deletions(-) create mode 100644 frontend/src/Activity/Queue/QueueFilterModal.tsx create mode 100644 frontend/src/Components/Filter/Builder/FilterBuilderRowValueProps.ts create mode 100644 frontend/src/Components/Filter/Builder/LanguageFilterBuilderRowValue.tsx create mode 100644 frontend/src/Components/Filter/Builder/SeriesFilterBuilderRowValue.tsx diff --git a/frontend/src/Activity/Queue/Queue.js b/frontend/src/Activity/Queue/Queue.js index 0b5827ea9..638fb1f52 100644 --- a/frontend/src/Activity/Queue/Queue.js +++ b/frontend/src/Activity/Queue/Queue.js @@ -3,6 +3,7 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; import Alert from 'Components/Alert'; import LoadingIndicator from 'Components/Loading/LoadingIndicator'; +import FilterMenu from 'Components/Menu/FilterMenu'; import PageContent from 'Components/Page/PageContent'; import PageContentBody from 'Components/Page/PageContentBody'; import PageToolbar from 'Components/Page/Toolbar/PageToolbar'; @@ -21,6 +22,7 @@ import getSelectedIds from 'Utilities/Table/getSelectedIds'; import removeOldSelectedState from 'Utilities/Table/removeOldSelectedState'; import selectAll from 'Utilities/Table/selectAll'; import toggleSelected from 'Utilities/Table/toggleSelected'; +import QueueFilterModal from './QueueFilterModal'; import QueueOptionsConnector from './QueueOptionsConnector'; import QueueRowConnector from './QueueRowConnector'; import RemoveQueueItemsModal from './RemoveQueueItemsModal'; @@ -151,11 +153,16 @@ class Queue extends Component { isEpisodesPopulated, episodesError, columns, + selectedFilterKey, + filters, + customFilters, + count, totalRecords, isGrabbing, isRemoving, isRefreshMonitoredDownloadsExecuting, onRefreshPress, + onFilterSelect, ...otherProps } = this.props; @@ -218,6 +225,15 @@ class Queue extends Component { iconName={icons.TABLE} /> </TableOptionsModalWrapper> + + <FilterMenu + alignMenu={align.RIGHT} + selectedFilterKey={selectedFilterKey} + filters={filters} + customFilters={customFilters} + filterModalConnectorComponent={QueueFilterModal} + onFilterSelect={onFilterSelect} + /> </PageToolbarSection> </PageToolbar> @@ -239,7 +255,11 @@ class Queue extends Component { { isAllPopulated && !hasError && !items.length ? <Alert kind={kinds.INFO}> - {translate('QueueIsEmpty')} + { + selectedFilterKey !== 'all' && count > 0 ? + translate('QueueFilterHasNoItems') : + translate('QueueIsEmpty') + } </Alert> : null } @@ -323,13 +343,18 @@ Queue.propTypes = { isEpisodesPopulated: PropTypes.bool.isRequired, episodesError: PropTypes.object, columns: PropTypes.arrayOf(PropTypes.object).isRequired, + selectedFilterKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, + filters: PropTypes.arrayOf(PropTypes.object).isRequired, + customFilters: PropTypes.arrayOf(PropTypes.object).isRequired, + count: PropTypes.number.isRequired, totalRecords: PropTypes.number, isGrabbing: PropTypes.bool.isRequired, isRemoving: PropTypes.bool.isRequired, isRefreshMonitoredDownloadsExecuting: PropTypes.bool.isRequired, onRefreshPress: PropTypes.func.isRequired, onGrabSelectedPress: PropTypes.func.isRequired, - onRemoveSelectedPress: PropTypes.func.isRequired + onRemoveSelectedPress: PropTypes.func.isRequired, + onFilterSelect: PropTypes.func.isRequired }; export default Queue; diff --git a/frontend/src/Activity/Queue/QueueConnector.js b/frontend/src/Activity/Queue/QueueConnector.js index f7d5d5152..178cb8e5f 100644 --- a/frontend/src/Activity/Queue/QueueConnector.js +++ b/frontend/src/Activity/Queue/QueueConnector.js @@ -7,6 +7,7 @@ import withCurrentPage from 'Components/withCurrentPage'; import { executeCommand } from 'Store/Actions/commandActions'; import { clearEpisodes, fetchEpisodes } from 'Store/Actions/episodeActions'; import * as queueActions from 'Store/Actions/queueActions'; +import { createCustomFiltersSelector } from 'Store/Selectors/createClientSideCollectionSelector'; import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector'; import hasDifferentItems from 'Utilities/Object/hasDifferentItems'; import selectUniqueIds from 'Utilities/Object/selectUniqueIds'; @@ -18,12 +19,16 @@ function createMapStateToProps() { (state) => state.episodes, (state) => state.queue.options, (state) => state.queue.paged, + (state) => state.queue.status.item, + createCustomFiltersSelector('queue'), createCommandExecutingSelector(commandNames.REFRESH_MONITORED_DOWNLOADS), - (episodes, options, queue, isRefreshMonitoredDownloadsExecuting) => { + (episodes, options, queue, status, customFilters, isRefreshMonitoredDownloadsExecuting) => { return { + count: options.includeUnknownSeriesItems ? status.totalCount : status.count, isEpisodesFetching: episodes.isFetching, isEpisodesPopulated: episodes.isPopulated, episodesError: episodes.error, + customFilters, isRefreshMonitoredDownloadsExecuting, ...options, ...queue @@ -122,6 +127,10 @@ class QueueConnector extends Component { this.props.setQueueSort({ sortKey }); }; + onFilterSelect = (selectedFilterKey) => { + this.props.setQueueFilter({ selectedFilterKey }); + }; + onTableOptionChange = (payload) => { this.props.setQueueTableOption(payload); @@ -156,6 +165,7 @@ class QueueConnector extends Component { onLastPagePress={this.onLastPagePress} onPageSelect={this.onPageSelect} onSortPress={this.onSortPress} + onFilterSelect={this.onFilterSelect} onTableOptionChange={this.onTableOptionChange} onRefreshPress={this.onRefreshPress} onGrabSelectedPress={this.onGrabSelectedPress} @@ -178,6 +188,7 @@ QueueConnector.propTypes = { gotoQueueLastPage: PropTypes.func.isRequired, gotoQueuePage: PropTypes.func.isRequired, setQueueSort: PropTypes.func.isRequired, + setQueueFilter: PropTypes.func.isRequired, setQueueTableOption: PropTypes.func.isRequired, clearQueue: PropTypes.func.isRequired, grabQueueItems: PropTypes.func.isRequired, diff --git a/frontend/src/Activity/Queue/QueueFilterModal.tsx b/frontend/src/Activity/Queue/QueueFilterModal.tsx new file mode 100644 index 000000000..3fce6c166 --- /dev/null +++ b/frontend/src/Activity/Queue/QueueFilterModal.tsx @@ -0,0 +1,54 @@ +import React, { useCallback } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { createSelector } from 'reselect'; +import AppState from 'App/State/AppState'; +import FilterModal from 'Components/Filter/FilterModal'; +import { setQueueFilter } from 'Store/Actions/queueActions'; + +function createQueueSelector() { + return createSelector( + (state: AppState) => state.queue.paged.items, + (queueItems) => { + return queueItems; + } + ); +} + +function createFilterBuilderPropsSelector() { + return createSelector( + (state: AppState) => state.queue.paged.filterBuilderProps, + (filterBuilderProps) => { + return filterBuilderProps; + } + ); +} + +interface QueueFilterModalProps { + isOpen: boolean; +} + +export default function QueueFilterModal(props: QueueFilterModalProps) { + const sectionItems = useSelector(createQueueSelector()); + const filterBuilderProps = useSelector(createFilterBuilderPropsSelector()); + const customFilterType = 'queue'; + + const dispatch = useDispatch(); + + const dispatchSetFilter = useCallback( + (payload: unknown) => { + dispatch(setQueueFilter(payload)); + }, + [dispatch] + ); + + return ( + <FilterModal + // TODO: Don't spread all the props + {...props} + sectionItems={sectionItems} + filterBuilderProps={filterBuilderProps} + customFilterType={customFilterType} + dispatchSetFilter={dispatchSetFilter} + /> + ); +} diff --git a/frontend/src/App/State/AppSectionState.ts b/frontend/src/App/State/AppSectionState.ts index d511963fc..cabc39b1c 100644 --- a/frontend/src/App/State/AppSectionState.ts +++ b/frontend/src/App/State/AppSectionState.ts @@ -1,4 +1,5 @@ import SortDirection from 'Helpers/Props/SortDirection'; +import { FilterBuilderProp } from './AppState'; export interface Error { responseJSON: { @@ -20,6 +21,10 @@ export interface PagedAppSectionState { pageSize: number; } +export interface AppSectionFilterState<T> { + filterBuilderProps: FilterBuilderProp<T>[]; +} + export interface AppSectionSchemaState<T> { isSchemaFetching: boolean; isSchemaPopulated: boolean; diff --git a/frontend/src/App/State/QueueAppState.ts b/frontend/src/App/State/QueueAppState.ts index 05fc5a59a..a2936109d 100644 --- a/frontend/src/App/State/QueueAppState.ts +++ b/frontend/src/App/State/QueueAppState.ts @@ -2,7 +2,11 @@ import ModelBase from 'App/ModelBase'; import Language from 'Language/Language'; import { QualityModel } from 'Quality/Quality'; import CustomFormat from 'typings/CustomFormat'; -import AppSectionState, { AppSectionItemState, Error } from './AppSectionState'; +import AppSectionState, { + AppSectionFilterState, + AppSectionItemState, + Error, +} from './AppSectionState'; export interface StatusMessage { title: string; @@ -37,7 +41,9 @@ export interface QueueDetailsAppState extends AppSectionState<Queue> { params: unknown; } -export interface QueuePagedAppState extends AppSectionState<Queue> { +export interface QueuePagedAppState + extends AppSectionState<Queue>, + AppSectionFilterState<Queue> { isGrabbing: boolean; grabError: Error; isRemoving: boolean; diff --git a/frontend/src/Components/Filter/Builder/FilterBuilderRow.js b/frontend/src/Components/Filter/Builder/FilterBuilderRow.js index ee92b395d..6591bc4b7 100644 --- a/frontend/src/Components/Filter/Builder/FilterBuilderRow.js +++ b/frontend/src/Components/Filter/Builder/FilterBuilderRow.js @@ -7,9 +7,11 @@ import BoolFilterBuilderRowValue from './BoolFilterBuilderRowValue'; import DateFilterBuilderRowValue from './DateFilterBuilderRowValue'; import FilterBuilderRowValueConnector from './FilterBuilderRowValueConnector'; import IndexerFilterBuilderRowValueConnector from './IndexerFilterBuilderRowValueConnector'; +import LanguageFilterBuilderRowValue from './LanguageFilterBuilderRowValue'; import ProtocolFilterBuilderRowValue from './ProtocolFilterBuilderRowValue'; import QualityFilterBuilderRowValueConnector from './QualityFilterBuilderRowValueConnector'; import QualityProfileFilterBuilderRowValueConnector from './QualityProfileFilterBuilderRowValueConnector'; +import SeriesFilterBuilderRowValue from './SeriesFilterBuilderRowValue'; import SeriesStatusFilterBuilderRowValue from './SeriesStatusFilterBuilderRowValue'; import SeriesTypeFilterBuilderRowValue from './SeriesTypeFilterBuilderRowValue'; import TagFilterBuilderRowValueConnector from './TagFilterBuilderRowValueConnector'; @@ -60,6 +62,9 @@ function getRowValueConnector(selectedFilterBuilderProp) { case filterBuilderValueTypes.INDEXER: return IndexerFilterBuilderRowValueConnector; + case filterBuilderValueTypes.LANGUAGE: + return LanguageFilterBuilderRowValue; + case filterBuilderValueTypes.PROTOCOL: return ProtocolFilterBuilderRowValue; @@ -69,6 +74,9 @@ function getRowValueConnector(selectedFilterBuilderProp) { case filterBuilderValueTypes.QUALITY_PROFILE: return QualityProfileFilterBuilderRowValueConnector; + case filterBuilderValueTypes.SERIES: + return SeriesFilterBuilderRowValue; + case filterBuilderValueTypes.SERIES_STATUS: return SeriesStatusFilterBuilderRowValue; diff --git a/frontend/src/Components/Filter/Builder/FilterBuilderRowValueProps.ts b/frontend/src/Components/Filter/Builder/FilterBuilderRowValueProps.ts new file mode 100644 index 000000000..5bf9e5785 --- /dev/null +++ b/frontend/src/Components/Filter/Builder/FilterBuilderRowValueProps.ts @@ -0,0 +1,16 @@ +import { FilterBuilderProp } from 'App/State/AppState'; + +interface FilterBuilderRowOnChangeProps { + name: string; + value: unknown[]; +} + +interface FilterBuilderRowValueProps { + filterType?: string; + filterValue: string | number | object | string[] | number[] | object[]; + selectedFilterBuilderProp: FilterBuilderProp<unknown>; + sectionItem: unknown[]; + onChange: (payload: FilterBuilderRowOnChangeProps) => void; +} + +export default FilterBuilderRowValueProps; diff --git a/frontend/src/Components/Filter/Builder/LanguageFilterBuilderRowValue.tsx b/frontend/src/Components/Filter/Builder/LanguageFilterBuilderRowValue.tsx new file mode 100644 index 000000000..e828fd848 --- /dev/null +++ b/frontend/src/Components/Filter/Builder/LanguageFilterBuilderRowValue.tsx @@ -0,0 +1,13 @@ +import React from 'react'; +import { useSelector } from 'react-redux'; +import createLanguagesSelector from 'Store/Selectors/createLanguagesSelector'; +import FilterBuilderRowValue from './FilterBuilderRowValue'; +import FilterBuilderRowValueProps from './FilterBuilderRowValueProps'; + +function LanguageFilterBuilderRowValue(props: FilterBuilderRowValueProps) { + const { items } = useSelector(createLanguagesSelector()); + + return <FilterBuilderRowValue {...props} tagList={items} />; +} + +export default LanguageFilterBuilderRowValue; diff --git a/frontend/src/Components/Filter/Builder/SeriesFilterBuilderRowValue.tsx b/frontend/src/Components/Filter/Builder/SeriesFilterBuilderRowValue.tsx new file mode 100644 index 000000000..418874fc7 --- /dev/null +++ b/frontend/src/Components/Filter/Builder/SeriesFilterBuilderRowValue.tsx @@ -0,0 +1,21 @@ +import React from 'react'; +import { useSelector } from 'react-redux'; +import Series from 'Series/Series'; +import createAllSeriesSelector from 'Store/Selectors/createAllSeriesSelector'; +import FilterBuilderRowValue from './FilterBuilderRowValue'; +import FilterBuilderRowValueProps from './FilterBuilderRowValueProps'; + +function SeriesFilterBuilderRowValue(props: FilterBuilderRowValueProps) { + const allSeries: Series[] = useSelector(createAllSeriesSelector()); + + const tagList = allSeries.map((series) => { + return { + id: series.id, + name: series.title, + }; + }); + + return <FilterBuilderRowValue {...props} tagList={tagList} />; +} + +export default SeriesFilterBuilderRowValue; diff --git a/frontend/src/Helpers/Props/filterBuilderValueTypes.js b/frontend/src/Helpers/Props/filterBuilderValueTypes.js index a1f8f499d..49b6fcbb6 100644 --- a/frontend/src/Helpers/Props/filterBuilderValueTypes.js +++ b/frontend/src/Helpers/Props/filterBuilderValueTypes.js @@ -3,9 +3,11 @@ export const BYTES = 'bytes'; export const DATE = 'date'; export const DEFAULT = 'default'; export const INDEXER = 'indexer'; +export const LANGUAGE = 'language'; export const PROTOCOL = 'protocol'; export const QUALITY = 'quality'; export const QUALITY_PROFILE = 'qualityProfile'; +export const SERIES = 'series'; export const SERIES_STATUS = 'seriesStatus'; export const SERIES_TYPES = 'seriesType'; export const TAG = 'tag'; diff --git a/frontend/src/Store/Actions/Creators/createFetchServerSideCollectionHandler.js b/frontend/src/Store/Actions/Creators/createFetchServerSideCollectionHandler.js index a80ee1e45..f5ef10a4d 100644 --- a/frontend/src/Store/Actions/Creators/createFetchServerSideCollectionHandler.js +++ b/frontend/src/Store/Actions/Creators/createFetchServerSideCollectionHandler.js @@ -6,6 +6,8 @@ import getSectionState from 'Utilities/State/getSectionState'; import { set, updateServerSideCollection } from '../baseActions'; function createFetchServerSideCollectionHandler(section, url, fetchDataAugmenter) { + const [baseSection] = section.split('.'); + return function(getState, payload, dispatch) { dispatch(set({ section, isFetching: true })); @@ -25,10 +27,13 @@ function createFetchServerSideCollectionHandler(section, url, fetchDataAugmenter const { selectedFilterKey, - filters, - customFilters + filters } = sectionState; + const customFilters = getState().customFilters.items.filter((customFilter) => { + return customFilter.type === section || customFilter.type === baseSection; + }); + const selectedFilters = findSelectedFilters(selectedFilterKey, filters, customFilters); selectedFilters.forEach((filter) => { @@ -37,7 +42,8 @@ function createFetchServerSideCollectionHandler(section, url, fetchDataAugmenter const promise = createAjaxRequest({ url, - data + data, + traditional: true }).request; promise.done((response) => { diff --git a/frontend/src/Store/Actions/calendarActions.js b/frontend/src/Store/Actions/calendarActions.js index 0e0febea6..4fece9523 100644 --- a/frontend/src/Store/Actions/calendarActions.js +++ b/frontend/src/Store/Actions/calendarActions.js @@ -52,8 +52,6 @@ export const defaultState = { selectedFilterKey: 'monitored', - customFilters: [], - filters: [ { key: 'all', diff --git a/frontend/src/Store/Actions/queueActions.js b/frontend/src/Store/Actions/queueActions.js index 6c4ee38cc..15ea35561 100644 --- a/frontend/src/Store/Actions/queueActions.js +++ b/frontend/src/Store/Actions/queueActions.js @@ -3,7 +3,7 @@ import React from 'react'; import { createAction } from 'redux-actions'; import { batchActions } from 'redux-batched-actions'; import Icon from 'Components/Icon'; -import { icons, sortDirections } from 'Helpers/Props'; +import { filterBuilderTypes, filterBuilderValueTypes, icons, sortDirections } from 'Helpers/Props'; import { createThunk, handleThunks } from 'Store/thunks'; import createAjaxRequest from 'Utilities/createAjaxRequest'; import serverSideCollectionHandlers from 'Utilities/serverSideCollectionHandlers'; @@ -170,6 +170,43 @@ export const defaultState = { isVisible: true, isModifiable: false } + ], + + selectedFilterKey: 'all', + + filters: [ + { + key: 'all', + label: 'All', + filters: [] + } + ], + + filterBuilderProps: [ + { + name: 'seriesIds', + label: 'Series', + type: filterBuilderTypes.EQUAL, + valueType: filterBuilderValueTypes.SERIES + }, + { + name: 'quality', + label: 'Quality', + type: filterBuilderTypes.EQUAL, + valueType: filterBuilderValueTypes.QUALITY + }, + { + name: 'languages', + label: 'Languages', + type: filterBuilderTypes.CONTAINS, + valueType: filterBuilderValueTypes.LANGUAGE + }, + { + name: 'protocol', + label: 'Protocol', + type: filterBuilderTypes.EQUAL, + valueType: filterBuilderValueTypes.PROTOCOL + } ] } }; @@ -179,7 +216,8 @@ export const persistState = [ 'queue.paged.pageSize', 'queue.paged.sortKey', 'queue.paged.sortDirection', - 'queue.paged.columns' + 'queue.paged.columns', + 'queue.paged.selectedFilterKey' ]; // @@ -204,6 +242,7 @@ export const GOTO_NEXT_QUEUE_PAGE = 'queue/gotoQueueNextPage'; export const GOTO_LAST_QUEUE_PAGE = 'queue/gotoQueueLastPage'; export const GOTO_QUEUE_PAGE = 'queue/gotoQueuePage'; export const SET_QUEUE_SORT = 'queue/setQueueSort'; +export const SET_QUEUE_FILTER = 'queue/setQueueFilter'; export const SET_QUEUE_TABLE_OPTION = 'queue/setQueueTableOption'; export const SET_QUEUE_OPTION = 'queue/setQueueOption'; export const CLEAR_QUEUE = 'queue/clearQueue'; @@ -228,6 +267,7 @@ export const gotoQueueNextPage = createThunk(GOTO_NEXT_QUEUE_PAGE); export const gotoQueueLastPage = createThunk(GOTO_LAST_QUEUE_PAGE); export const gotoQueuePage = createThunk(GOTO_QUEUE_PAGE); export const setQueueSort = createThunk(SET_QUEUE_SORT); +export const setQueueFilter = createThunk(SET_QUEUE_FILTER); export const setQueueTableOption = createAction(SET_QUEUE_TABLE_OPTION); export const setQueueOption = createAction(SET_QUEUE_OPTION); export const clearQueue = createAction(CLEAR_QUEUE); @@ -279,7 +319,8 @@ export const actionHandlers = handleThunks({ [serverSideCollectionHandlers.NEXT_PAGE]: GOTO_NEXT_QUEUE_PAGE, [serverSideCollectionHandlers.LAST_PAGE]: GOTO_LAST_QUEUE_PAGE, [serverSideCollectionHandlers.EXACT_PAGE]: GOTO_QUEUE_PAGE, - [serverSideCollectionHandlers.SORT]: SET_QUEUE_SORT + [serverSideCollectionHandlers.SORT]: SET_QUEUE_SORT, + [serverSideCollectionHandlers.FILTER]: SET_QUEUE_FILTER }, fetchDataAugmenter ), diff --git a/src/NzbDrone.Core/Localization/Core/en.json b/src/NzbDrone.Core/Localization/Core/en.json index f772d5047..47e36054a 100644 --- a/src/NzbDrone.Core/Localization/Core/en.json +++ b/src/NzbDrone.Core/Localization/Core/en.json @@ -1008,6 +1008,7 @@ "QualitySettings": "Quality Settings", "QualitySettingsSummary": "Quality sizes and naming", "Queue": "Queue", + "QueueFilterHasNoItems": "Selected queue filter has no items", "QueueIsEmpty": "Queue is empty", "QueueLoadError": "Failed to load Queue", "Queued": "Queued", diff --git a/src/Sonarr.Api.V3/Queue/QueueController.cs b/src/Sonarr.Api.V3/Queue/QueueController.cs index edb631ee4..c3e4c1d52 100644 --- a/src/Sonarr.Api.V3/Queue/QueueController.cs +++ b/src/Sonarr.Api.V3/Queue/QueueController.cs @@ -9,6 +9,7 @@ using NzbDrone.Core.Datastore.Events; using NzbDrone.Core.Download; using NzbDrone.Core.Download.Pending; using NzbDrone.Core.Download.TrackedDownloads; +using NzbDrone.Core.Indexers; using NzbDrone.Core.Languages; using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Profiles.Qualities; @@ -135,15 +136,15 @@ namespace Sonarr.Api.V3.Queue [HttpGet] [Produces("application/json")] - public PagingResource<QueueResource> GetQueue([FromQuery] PagingRequestResource paging, bool includeUnknownSeriesItems = false, bool includeSeries = false, bool includeEpisode = false) + public PagingResource<QueueResource> GetQueue([FromQuery] PagingRequestResource paging, bool includeUnknownSeriesItems = false, bool includeSeries = false, bool includeEpisode = false, [FromQuery] int[] seriesIds = null, DownloadProtocol? protocol = null, [FromQuery] int[] languages = null, int? quality = null) { var pagingResource = new PagingResource<QueueResource>(paging); var pagingSpec = pagingResource.MapToPagingSpec<QueueResource, NzbDrone.Core.Queue.Queue>("timeleft", SortDirection.Ascending); - return pagingSpec.ApplyToPage((spec) => GetQueue(spec, includeUnknownSeriesItems), (q) => MapToResource(q, includeSeries, includeEpisode)); + return pagingSpec.ApplyToPage((spec) => GetQueue(spec, seriesIds?.ToHashSet(), protocol, languages?.ToHashSet(), quality, includeUnknownSeriesItems), (q) => MapToResource(q, includeSeries, includeEpisode)); } - private PagingSpec<NzbDrone.Core.Queue.Queue> GetQueue(PagingSpec<NzbDrone.Core.Queue.Queue> pagingSpec, bool includeUnknownSeriesItems) + private PagingSpec<NzbDrone.Core.Queue.Queue> GetQueue(PagingSpec<NzbDrone.Core.Queue.Queue> pagingSpec, HashSet<int> seriesIds, DownloadProtocol? protocol, HashSet<int> languages, int? quality, bool includeUnknownSeriesItems) { var ascending = pagingSpec.SortDirection == SortDirection.Ascending; var orderByFunc = GetOrderByFunc(pagingSpec); @@ -151,7 +152,36 @@ namespace Sonarr.Api.V3.Queue var queue = _queueService.GetQueue(); var filteredQueue = includeUnknownSeriesItems ? queue : queue.Where(q => q.Series != null); var pending = _pendingReleaseService.GetPendingQueue(); - var fullQueue = filteredQueue.Concat(pending).ToList(); + + var hasSeriesIdFilter = seriesIds.Any(); + var hasLanguageFilter = languages.Any(); + var fullQueue = filteredQueue.Concat(pending).Where(q => + { + var include = true; + + if (hasSeriesIdFilter) + { + include &= q.Series != null && seriesIds.Contains(q.Series.Id); + } + + if (include && protocol.HasValue) + { + include &= q.Protocol == protocol.Value; + } + + if (include && hasLanguageFilter) + { + include &= q.Languages.Any(l => languages.Contains(l.Id)); + } + + if (include && quality.HasValue) + { + include &= q.Quality.Quality.Id == quality.Value; + } + + return include; + }).ToList(); + IOrderedEnumerable<NzbDrone.Core.Queue.Queue> ordered; if (pagingSpec.SortKey == "timeleft") From 2fe8f3084c90688e6dd01d600796396e74f43ff9 Mon Sep 17 00:00:00 2001 From: Mark McDowall <mark@mcdowall.ca> Date: Mon, 22 May 2023 20:06:32 -0700 Subject: [PATCH 056/136] New: History custom filters Closes #2974 --- frontend/src/Activity/History/History.js | 8 +- .../src/Activity/History/HistoryConnector.js | 5 +- .../Activity/History/HistoryFilterModal.tsx | 54 ++++++++++++ frontend/src/App/State/AppState.ts | 2 + frontend/src/App/State/HistoryAppState.ts | 10 +++ .../Filter/Builder/FilterBuilderRow.js | 4 + .../HistoryEventTypeFilterBuilderRowValue.tsx | 38 +++++++++ .../Helpers/Props/filterBuilderValueTypes.js | 1 + frontend/src/Store/Actions/historyActions.js | 29 ++++++- frontend/src/typings/History.ts | 28 +++++++ .../Analytics/AnalyticsService.cs | 4 +- .../Datastore/BasicRepository.cs | 2 +- .../History/HistoryRepository.cs | 84 ++++++++++++++++--- src/NzbDrone.Core/History/HistoryService.cs | 6 +- .../History/HistoryController.cs | 14 +++- 15 files changed, 265 insertions(+), 24 deletions(-) create mode 100644 frontend/src/Activity/History/HistoryFilterModal.tsx create mode 100644 frontend/src/App/State/HistoryAppState.ts create mode 100644 frontend/src/Components/Filter/Builder/HistoryEventTypeFilterBuilderRowValue.tsx create mode 100644 frontend/src/typings/History.ts diff --git a/frontend/src/Activity/History/History.js b/frontend/src/Activity/History/History.js index 522092725..e5cc31ecd 100644 --- a/frontend/src/Activity/History/History.js +++ b/frontend/src/Activity/History/History.js @@ -15,6 +15,7 @@ import TablePager from 'Components/Table/TablePager'; import { align, icons, kinds } from 'Helpers/Props'; import hasDifferentItems from 'Utilities/Object/hasDifferentItems'; import translate from 'Utilities/String/translate'; +import HistoryFilterModal from './HistoryFilterModal'; import HistoryRowConnector from './HistoryRowConnector'; class History extends Component { @@ -52,6 +53,7 @@ class History extends Component { columns, selectedFilterKey, filters, + customFilters, totalRecords, isEpisodesFetching, isEpisodesPopulated, @@ -92,7 +94,8 @@ class History extends Component { alignMenu={align.RIGHT} selectedFilterKey={selectedFilterKey} filters={filters} - customFilters={[]} + customFilters={customFilters} + filterModalConnectorComponent={HistoryFilterModal} onFilterSelect={onFilterSelect} /> </PageToolbarSection> @@ -163,8 +166,9 @@ History.propTypes = { error: PropTypes.object, items: PropTypes.arrayOf(PropTypes.object).isRequired, columns: PropTypes.arrayOf(PropTypes.object).isRequired, - selectedFilterKey: PropTypes.string.isRequired, + selectedFilterKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, filters: PropTypes.arrayOf(PropTypes.object).isRequired, + customFilters: PropTypes.arrayOf(PropTypes.object).isRequired, totalRecords: PropTypes.number, isEpisodesFetching: PropTypes.bool.isRequired, isEpisodesPopulated: PropTypes.bool.isRequired, diff --git a/frontend/src/Activity/History/HistoryConnector.js b/frontend/src/Activity/History/HistoryConnector.js index 74b7fdfb4..b407960bd 100644 --- a/frontend/src/Activity/History/HistoryConnector.js +++ b/frontend/src/Activity/History/HistoryConnector.js @@ -6,6 +6,7 @@ import withCurrentPage from 'Components/withCurrentPage'; import { clearEpisodes, fetchEpisodes } from 'Store/Actions/episodeActions'; import { clearEpisodeFiles } from 'Store/Actions/episodeFileActions'; import * as historyActions from 'Store/Actions/historyActions'; +import { createCustomFiltersSelector } from 'Store/Selectors/createClientSideCollectionSelector'; import hasDifferentItems from 'Utilities/Object/hasDifferentItems'; import selectUniqueIds from 'Utilities/Object/selectUniqueIds'; import { registerPagePopulator, unregisterPagePopulator } from 'Utilities/pagePopulator'; @@ -15,11 +16,13 @@ function createMapStateToProps() { return createSelector( (state) => state.history, (state) => state.episodes, - (history, episodes) => { + createCustomFiltersSelector('history'), + (history, episodes, customFilters) => { return { isEpisodesFetching: episodes.isFetching, isEpisodesPopulated: episodes.isPopulated, episodesError: episodes.error, + customFilters, ...history }; } diff --git a/frontend/src/Activity/History/HistoryFilterModal.tsx b/frontend/src/Activity/History/HistoryFilterModal.tsx new file mode 100644 index 000000000..f4ad2e57c --- /dev/null +++ b/frontend/src/Activity/History/HistoryFilterModal.tsx @@ -0,0 +1,54 @@ +import React, { useCallback } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { createSelector } from 'reselect'; +import AppState from 'App/State/AppState'; +import FilterModal from 'Components/Filter/FilterModal'; +import { setHistoryFilter } from 'Store/Actions/historyActions'; + +function createHistorySelector() { + return createSelector( + (state: AppState) => state.history.items, + (queueItems) => { + return queueItems; + } + ); +} + +function createFilterBuilderPropsSelector() { + return createSelector( + (state: AppState) => state.history.filterBuilderProps, + (filterBuilderProps) => { + return filterBuilderProps; + } + ); +} + +interface HistoryFilterModalProps { + isOpen: boolean; +} + +export default function HistoryFilterModal(props: HistoryFilterModalProps) { + const sectionItems = useSelector(createHistorySelector()); + const filterBuilderProps = useSelector(createFilterBuilderPropsSelector()); + const customFilterType = 'history'; + + const dispatch = useDispatch(); + + const dispatchSetFilter = useCallback( + (payload: unknown) => { + dispatch(setHistoryFilter(payload)); + }, + [dispatch] + ); + + return ( + <FilterModal + // TODO: Don't spread all the props + {...props} + sectionItems={sectionItems} + filterBuilderProps={filterBuilderProps} + customFilterType={customFilterType} + dispatchSetFilter={dispatchSetFilter} + /> + ); +} diff --git a/frontend/src/App/State/AppState.ts b/frontend/src/App/State/AppState.ts index 572a5a11e..fcf1833ee 100644 --- a/frontend/src/App/State/AppState.ts +++ b/frontend/src/App/State/AppState.ts @@ -3,6 +3,7 @@ import CalendarAppState from './CalendarAppState'; import CommandAppState from './CommandAppState'; import EpisodeFilesAppState from './EpisodeFilesAppState'; import EpisodesAppState from './EpisodesAppState'; +import HistoryAppState from './HistoryAppState'; import ParseAppState from './ParseAppState'; import QueueAppState from './QueueAppState'; import RootFolderAppState from './RootFolderAppState'; @@ -48,6 +49,7 @@ interface AppState { commands: CommandAppState; episodeFiles: EpisodeFilesAppState; episodesSelection: EpisodesAppState; + history: HistoryAppState; interactiveImport: InteractiveImportAppState; parse: ParseAppState; queue: QueueAppState; diff --git a/frontend/src/App/State/HistoryAppState.ts b/frontend/src/App/State/HistoryAppState.ts new file mode 100644 index 000000000..e368ff86e --- /dev/null +++ b/frontend/src/App/State/HistoryAppState.ts @@ -0,0 +1,10 @@ +import AppSectionState, { + AppSectionFilterState, +} from 'App/State/AppSectionState'; +import History from 'typings/History'; + +interface HistoryAppState + extends AppSectionState<History>, + AppSectionFilterState<History> {} + +export default HistoryAppState; diff --git a/frontend/src/Components/Filter/Builder/FilterBuilderRow.js b/frontend/src/Components/Filter/Builder/FilterBuilderRow.js index 6591bc4b7..01c24b460 100644 --- a/frontend/src/Components/Filter/Builder/FilterBuilderRow.js +++ b/frontend/src/Components/Filter/Builder/FilterBuilderRow.js @@ -6,6 +6,7 @@ import { filterBuilderTypes, filterBuilderValueTypes, icons } from 'Helpers/Prop import BoolFilterBuilderRowValue from './BoolFilterBuilderRowValue'; import DateFilterBuilderRowValue from './DateFilterBuilderRowValue'; import FilterBuilderRowValueConnector from './FilterBuilderRowValueConnector'; +import HistoryEventTypeFilterBuilderRowValue from './HistoryEventTypeFilterBuilderRowValue'; import IndexerFilterBuilderRowValueConnector from './IndexerFilterBuilderRowValueConnector'; import LanguageFilterBuilderRowValue from './LanguageFilterBuilderRowValue'; import ProtocolFilterBuilderRowValue from './ProtocolFilterBuilderRowValue'; @@ -59,6 +60,9 @@ function getRowValueConnector(selectedFilterBuilderProp) { case filterBuilderValueTypes.DATE: return DateFilterBuilderRowValue; + case filterBuilderValueTypes.HISTORY_EVENT_TYPE: + return HistoryEventTypeFilterBuilderRowValue; + case filterBuilderValueTypes.INDEXER: return IndexerFilterBuilderRowValueConnector; diff --git a/frontend/src/Components/Filter/Builder/HistoryEventTypeFilterBuilderRowValue.tsx b/frontend/src/Components/Filter/Builder/HistoryEventTypeFilterBuilderRowValue.tsx new file mode 100644 index 000000000..c5f41ebc3 --- /dev/null +++ b/frontend/src/Components/Filter/Builder/HistoryEventTypeFilterBuilderRowValue.tsx @@ -0,0 +1,38 @@ +import React from 'react'; +import FilterBuilderRowValue from './FilterBuilderRowValue'; +import FilterBuilderRowValueProps from './FilterBuilderRowValueProps'; + +const EVENT_TYPE_OPTIONS = [ + { + id: 1, + name: 'Grabbed', + }, + { + id: 3, + name: 'Imported', + }, + { + id: 4, + name: 'Failed', + }, + { + id: 5, + name: 'Deleted', + }, + { + id: 6, + name: 'Renamed', + }, + { + id: 7, + name: 'Ignored', + }, +]; + +function HistoryEventTypeFilterBuilderRowValue( + props: FilterBuilderRowValueProps +) { + return <FilterBuilderRowValue {...props} tagList={EVENT_TYPE_OPTIONS} />; +} + +export default HistoryEventTypeFilterBuilderRowValue; diff --git a/frontend/src/Helpers/Props/filterBuilderValueTypes.js b/frontend/src/Helpers/Props/filterBuilderValueTypes.js index 49b6fcbb6..1f4227779 100644 --- a/frontend/src/Helpers/Props/filterBuilderValueTypes.js +++ b/frontend/src/Helpers/Props/filterBuilderValueTypes.js @@ -2,6 +2,7 @@ export const BOOL = 'bool'; export const BYTES = 'bytes'; export const DATE = 'date'; export const DEFAULT = 'default'; +export const HISTORY_EVENT_TYPE = 'historyEventType'; export const INDEXER = 'indexer'; export const LANGUAGE = 'language'; export const PROTOCOL = 'protocol'; diff --git a/frontend/src/Store/Actions/historyActions.js b/frontend/src/Store/Actions/historyActions.js index 26a2bb8a9..01d0fe66b 100644 --- a/frontend/src/Store/Actions/historyActions.js +++ b/frontend/src/Store/Actions/historyActions.js @@ -1,7 +1,7 @@ import React from 'react'; import { createAction } from 'redux-actions'; import Icon from 'Components/Icon'; -import { filterTypes, icons, sortDirections } from 'Helpers/Props'; +import { filterBuilderTypes, filterBuilderValueTypes, filterTypes, icons, sortDirections } from 'Helpers/Props'; import { createThunk, handleThunks } from 'Store/thunks'; import createAjaxRequest from 'Utilities/createAjaxRequest'; import serverSideCollectionHandlers from 'Utilities/serverSideCollectionHandlers'; @@ -185,6 +185,33 @@ export const defaultState = { } ] } + ], + + filterBuilderProps: [ + { + name: 'eventType', + label: 'Event Type', + type: filterBuilderTypes.EQUAL, + valueType: filterBuilderValueTypes.HISTORY_EVENT_TYPE + }, + { + name: 'seriesIds', + label: 'Series', + type: filterBuilderTypes.EQUAL, + valueType: filterBuilderValueTypes.SERIES + }, + { + name: 'quality', + label: 'Quality', + type: filterBuilderTypes.EQUAL, + valueType: filterBuilderValueTypes.QUALITY + }, + { + name: 'languages', + label: 'Languages', + type: filterBuilderTypes.CONTAINS, + valueType: filterBuilderValueTypes.LANGUAGE + } ] }; diff --git a/frontend/src/typings/History.ts b/frontend/src/typings/History.ts new file mode 100644 index 000000000..99fabe275 --- /dev/null +++ b/frontend/src/typings/History.ts @@ -0,0 +1,28 @@ +import Language from 'Language/Language'; +import { QualityModel } from 'Quality/Quality'; +import CustomFormat from './CustomFormat'; + +export type HistoryEventType = + | 'grabbed' + | 'seriesFolderImported' + | 'downloadFolderImported' + | 'downloadFailed' + | 'episodeFileDeleted' + | 'episodeFileRenamed' + | 'downloadIgnored'; + +export default interface History { + episodeId: number; + seriesId: number; + sourceTitle: string; + languages: Language[]; + quality: QualityModel; + customFormats: CustomFormat[]; + customFormatScore: number; + qualityCutoffNotMet: boolean; + date: string; + downloadId: string; + eventType: HistoryEventType; + data: unknown; + id: number; +} diff --git a/src/NzbDrone.Core/Analytics/AnalyticsService.cs b/src/NzbDrone.Core/Analytics/AnalyticsService.cs index 6c71576a7..a4a043e01 100644 --- a/src/NzbDrone.Core/Analytics/AnalyticsService.cs +++ b/src/NzbDrone.Core/Analytics/AnalyticsService.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Core.Configuration; @@ -30,7 +30,7 @@ namespace NzbDrone.Core.Analytics { get { - var lastRecord = _historyService.Paged(new PagingSpec<EpisodeHistory>() { Page = 0, PageSize = 1, SortKey = "date", SortDirection = SortDirection.Descending }); + var lastRecord = _historyService.Paged(new PagingSpec<EpisodeHistory>() { Page = 0, PageSize = 1, SortKey = "date", SortDirection = SortDirection.Descending }, null, null); var monthAgo = DateTime.UtcNow.AddMonths(-1); return lastRecord.Records.Any(v => v.Date > monthAgo); diff --git a/src/NzbDrone.Core/Datastore/BasicRepository.cs b/src/NzbDrone.Core/Datastore/BasicRepository.cs index b5e57be87..a7da9eb38 100644 --- a/src/NzbDrone.Core/Datastore/BasicRepository.cs +++ b/src/NzbDrone.Core/Datastore/BasicRepository.cs @@ -407,7 +407,7 @@ namespace NzbDrone.Core.Datastore return pagingSpec; } - private void AddFilters(SqlBuilder builder, PagingSpec<TModel> pagingSpec) + protected void AddFilters(SqlBuilder builder, PagingSpec<TModel> pagingSpec) { var filters = pagingSpec.FilterExpressions; diff --git a/src/NzbDrone.Core/History/HistoryRepository.cs b/src/NzbDrone.Core/History/HistoryRepository.cs index c5546147a..7f3da4064 100644 --- a/src/NzbDrone.Core/History/HistoryRepository.cs +++ b/src/NzbDrone.Core/History/HistoryRepository.cs @@ -19,6 +19,7 @@ namespace NzbDrone.Core.History List<EpisodeHistory> FindDownloadHistory(int idSeriesId, QualityModel quality); void DeleteForSeries(List<int> seriesIds); List<EpisodeHistory> Since(DateTime date, EpisodeHistoryEventType? eventType); + PagingSpec<EpisodeHistory> GetPaged(PagingSpec<EpisodeHistory> pagingSpec, int[] languages, int[] qualities); } public class HistoryRepository : BasicRepository<EpisodeHistory>, IHistoryRepository @@ -101,18 +102,6 @@ namespace NzbDrone.Core.History Delete(c => seriesIds.Contains(c.SeriesId)); } - protected override SqlBuilder PagedBuilder() => new SqlBuilder(_database.DatabaseType) - .Join<EpisodeHistory, Series>((h, a) => h.SeriesId == a.Id) - .Join<EpisodeHistory, Episode>((h, a) => h.EpisodeId == a.Id); - - protected override IEnumerable<EpisodeHistory> PagedQuery(SqlBuilder builder) => - _database.QueryJoined<EpisodeHistory, Series, Episode>(builder, (history, series, episode) => - { - history.Series = series; - history.Episode = episode; - return history; - }); - public List<EpisodeHistory> Since(DateTime date, EpisodeHistoryEventType? eventType) { var builder = Builder() @@ -132,5 +121,76 @@ namespace NzbDrone.Core.History return history; }).OrderBy(h => h.Date).ToList(); } + + public PagingSpec<EpisodeHistory> GetPaged(PagingSpec<EpisodeHistory> pagingSpec, int[] languages, int[] qualities) + { + pagingSpec.Records = GetPagedRecords(PagedBuilder(pagingSpec, languages, qualities), pagingSpec, PagedQuery); + + var countTemplate = $"SELECT COUNT(*) FROM (SELECT /**select**/ FROM \"{TableMapping.Mapper.TableNameMapping(typeof(EpisodeHistory))}\" /**join**/ /**innerjoin**/ /**leftjoin**/ /**where**/ /**groupby**/ /**having**/) AS \"Inner\""; + pagingSpec.TotalRecords = GetPagedRecordCount(PagedBuilder(pagingSpec, languages, qualities).Select(typeof(EpisodeHistory)), pagingSpec, countTemplate); + + return pagingSpec; + } + + private SqlBuilder PagedBuilder(PagingSpec<EpisodeHistory> pagingSpec, int[] languages, int[] qualities) + { + var builder = Builder() + .Join<EpisodeHistory, Series>((h, a) => h.SeriesId == a.Id) + .Join<EpisodeHistory, Episode>((h, a) => h.EpisodeId == a.Id); + + AddFilters(builder, pagingSpec); + + if (languages is { Length: > 0 }) + { + builder.Where($"({BuildLanguageWhereClause(languages)})"); + } + + if (qualities is { Length: > 0 }) + { + builder.Where($"({BuildQualityWhereClause(qualities)})"); + } + + return builder; + } + + protected override IEnumerable<EpisodeHistory> PagedQuery(SqlBuilder builder) => + _database.QueryJoined<EpisodeHistory, Series, Episode>(builder, (history, series, episode) => + { + history.Series = series; + history.Episode = episode; + return history; + }); + + private string BuildLanguageWhereClause(int[] languages) + { + var clauses = new List<string>(); + + foreach (var language in languages) + { + // There are 4 different types of values we should see: + // - Not the last value in the array + // - When it's the last value in the array and on different OSes + // - When it was converted from a single language + + clauses.Add($"\"{TableMapping.Mapper.TableNameMapping(typeof(EpisodeHistory))}\".\"Languages\" LIKE '[% {language},%]'"); + clauses.Add($"\"{TableMapping.Mapper.TableNameMapping(typeof(EpisodeHistory))}\".\"Languages\" LIKE '[% {language}' || CHAR(13) || '%]'"); + clauses.Add($"\"{TableMapping.Mapper.TableNameMapping(typeof(EpisodeHistory))}\".\"Languages\" LIKE '[% {language}' || CHAR(10) || '%]'"); + clauses.Add($"\"{TableMapping.Mapper.TableNameMapping(typeof(EpisodeHistory))}\".\"Languages\" LIKE '[{language}]'"); + } + + return $"({string.Join(" OR ", clauses)})"; + } + + private string BuildQualityWhereClause(int[] qualities) + { + var clauses = new List<string>(); + + foreach (var quality in qualities) + { + clauses.Add($"\"{TableMapping.Mapper.TableNameMapping(typeof(EpisodeHistory))}\".\"Quality\" LIKE '%_quality_: {quality},%'"); + } + + return $"({string.Join(" OR ", clauses)})"; + } } } diff --git a/src/NzbDrone.Core/History/HistoryService.cs b/src/NzbDrone.Core/History/HistoryService.cs index 1f1e9a668..86fe74846 100644 --- a/src/NzbDrone.Core/History/HistoryService.cs +++ b/src/NzbDrone.Core/History/HistoryService.cs @@ -16,7 +16,7 @@ namespace NzbDrone.Core.History { public interface IHistoryService { - PagingSpec<EpisodeHistory> Paged(PagingSpec<EpisodeHistory> pagingSpec); + PagingSpec<EpisodeHistory> Paged(PagingSpec<EpisodeHistory> pagingSpec, int[] languages, int[] qualities); EpisodeHistory MostRecentForEpisode(int episodeId); List<EpisodeHistory> FindByEpisodeId(int episodeId); EpisodeHistory MostRecentForDownloadId(string downloadId); @@ -47,9 +47,9 @@ namespace NzbDrone.Core.History _logger = logger; } - public PagingSpec<EpisodeHistory> Paged(PagingSpec<EpisodeHistory> pagingSpec) + public PagingSpec<EpisodeHistory> Paged(PagingSpec<EpisodeHistory> pagingSpec, int[] languages, int[] qualities) { - return _historyRepository.GetPaged(pagingSpec); + return _historyRepository.GetPaged(pagingSpec, languages, qualities); } public EpisodeHistory MostRecentForEpisode(int episodeId) diff --git a/src/Sonarr.Api.V3/History/HistoryController.cs b/src/Sonarr.Api.V3/History/HistoryController.cs index 088ed9163..3fb8b1cf4 100644 --- a/src/Sonarr.Api.V3/History/HistoryController.cs +++ b/src/Sonarr.Api.V3/History/HistoryController.cs @@ -62,7 +62,7 @@ namespace Sonarr.Api.V3.History [HttpGet] [Produces("application/json")] - public PagingResource<HistoryResource> GetHistory([FromQuery] PagingRequestResource paging, bool includeSeries, bool includeEpisode, int? eventType, int? episodeId, string downloadId) + public PagingResource<HistoryResource> GetHistory([FromQuery] PagingRequestResource paging, bool includeSeries, bool includeEpisode, int? eventType, int? episodeId, string downloadId, [FromQuery] int[] seriesIds = null, [FromQuery] int[] languages = null, [FromQuery] int[] quality = null) { var pagingResource = new PagingResource<HistoryResource>(paging); var pagingSpec = pagingResource.MapToPagingSpec<HistoryResource, EpisodeHistory>("date", SortDirection.Descending); @@ -83,7 +83,17 @@ namespace Sonarr.Api.V3.History pagingSpec.FilterExpressions.Add(h => h.DownloadId == downloadId); } - return pagingSpec.ApplyToPage(_historyService.Paged, h => MapToResource(h, includeSeries, includeEpisode)); + if (seriesIds != null && seriesIds.Any()) + { + pagingSpec.FilterExpressions.Add(h => seriesIds.Contains(h.SeriesId)); + } + + if (seriesIds != null && seriesIds.Any()) + { + pagingSpec.FilterExpressions.Add(h => seriesIds.Contains(h.SeriesId)); + } + + return pagingSpec.ApplyToPage(h => _historyService.Paged(pagingSpec, languages, quality), h => MapToResource(h, includeSeries, includeEpisode)); } [HttpGet("since")] From e8a47b4d0b3d8289b29448a7da1004b000446e72 Mon Sep 17 00:00:00 2001 From: Bogdan <mynameisbogdan@users.noreply.github.com> Date: Tue, 17 Oct 2023 09:52:59 +0300 Subject: [PATCH 057/136] New: Support for IMDb ID in Plex Watchlist RSS Closes #6105 --- .../Rss/Plex/PlexRssImportParser.cs | 32 ++++++++++++++++--- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/src/NzbDrone.Core/ImportLists/Rss/Plex/PlexRssImportParser.cs b/src/NzbDrone.Core/ImportLists/Rss/Plex/PlexRssImportParser.cs index cad233559..efd26708e 100644 --- a/src/NzbDrone.Core/ImportLists/Rss/Plex/PlexRssImportParser.cs +++ b/src/NzbDrone.Core/ImportLists/Rss/Plex/PlexRssImportParser.cs @@ -1,5 +1,7 @@ +using System.Text.RegularExpressions; using System.Xml.Linq; using NLog; +using NzbDrone.Common.Extensions; using NzbDrone.Core.Indexers; using NzbDrone.Core.Indexers.Exceptions; using NzbDrone.Core.Parser.Model; @@ -8,6 +10,8 @@ namespace NzbDrone.Core.ImportLists.Rss.Plex { public class PlexRssImportParser : RssImportBaseParser { + private static readonly Regex ImdbIdRegex = new (@"(tt\d{7,8})", RegexOptions.IgnoreCase | RegexOptions.Compiled); + public PlexRssImportParser(Logger logger) : base(logger) { @@ -29,17 +33,37 @@ namespace NzbDrone.Core.ImportLists.Rss.Plex var guid = item.TryGetValue("guid", string.Empty); - if (int.TryParse(guid.Replace("tvdb://", ""), out var tvdbId)) + if (guid.IsNotNullOrWhiteSpace()) { - info.TvdbId = tvdbId; + if (guid.StartsWith("imdb://")) + { + info.ImdbId = ParseImdbId(guid.Replace("imdb://", "")); + } + + if (int.TryParse(guid.Replace("tvdb://", ""), out var tvdbId)) + { + info.TvdbId = tvdbId; + } } - if (info.TvdbId == 0) + if (info.ImdbId.IsNullOrWhiteSpace() && info.TvdbId == 0) { - throw new UnsupportedFeedException("Each item in the RSS feed must have a guid element with a TVDB ID"); + throw new UnsupportedFeedException("Each item in the RSS feed must have a guid element with a IMDB ID or TVDB ID"); } return info; } + + private static string ParseImdbId(string value) + { + if (value.IsNullOrWhiteSpace()) + { + return null; + } + + var match = ImdbIdRegex.Match(value); + + return match.Success ? match.Groups[1].Value : null; + } } } From 0219f8478911558b70c4ebd6e274182528bdf0da Mon Sep 17 00:00:00 2001 From: Stevie Robinson <stevie.robinson@gmail.com> Date: Tue, 17 Oct 2023 08:54:39 +0200 Subject: [PATCH 058/136] Fixed: Has Missing Season not applying false condition --- frontend/src/Store/Actions/seriesActions.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/frontend/src/Store/Actions/seriesActions.js b/frontend/src/Store/Actions/seriesActions.js index 27d581f76..828b95295 100644 --- a/frontend/src/Store/Actions/seriesActions.js +++ b/frontend/src/Store/Actions/seriesActions.js @@ -168,9 +168,10 @@ export const filterPredicates = { }, hasMissingSeason: function(item, filterValue, type) { + const predicate = filterTypePredicates[type]; const { seasons = [] } = item; - return seasons.some((season) => { + const hasMissingSeason = seasons.some((season) => { const { seasonNumber, statistics = {} @@ -189,6 +190,8 @@ export const filterPredicates = { episodeFileCount === 0 ); }); + + return predicate(hasMissingSeason, filterValue); } }; @@ -347,7 +350,8 @@ export const filterBuilderProps = [ { name: 'hasMissingSeason', label: () => translate('HasMissingSeason'), - type: filterBuilderTypes.EXACT + type: filterBuilderTypes.EXACT, + valueType: filterBuilderValueTypes.BOOL }, { name: 'year', From 03f5174a4b2a005aab8d1a1540f4bcb272682f2e Mon Sep 17 00:00:00 2001 From: Stevie Robinson <stevie.robinson@gmail.com> Date: Tue, 17 Oct 2023 08:55:48 +0200 Subject: [PATCH 059/136] Fixed: Reduce font size for series title on series details Closes #6087 --- frontend/src/Series/Details/SeriesDetails.css | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/frontend/src/Series/Details/SeriesDetails.css b/frontend/src/Series/Details/SeriesDetails.css index 61e6b976f..d7a26e4f8 100644 --- a/frontend/src/Series/Details/SeriesDetails.css +++ b/frontend/src/Series/Details/SeriesDetails.css @@ -156,6 +156,12 @@ .headerContent { padding: 15px; } + + .title { + font-weight: 300; + font-size: 30px; + line-height: 30px; + } } @media only screen and (max-width: $breakpointLarge) { From a131c88d5fe04af75bab7b0feadcf7c81aec9f81 Mon Sep 17 00:00:00 2001 From: Daniel Martin Gonzalez <danimart1991@outlook.com> Date: Tue, 17 Oct 2023 08:58:05 +0200 Subject: [PATCH 060/136] New: Add Watched filter type to Trakt User Import List --- .../Trakt/Popular/TraktPopularParser.cs | 12 ++-- .../ImportLists/Trakt/TraktAPI.cs | 27 +++++-- .../ImportLists/Trakt/TraktParser.cs | 12 ++-- .../ImportLists/Trakt/User/TraktUserImport.cs | 5 ++ .../ImportLists/Trakt/User/TraktUserParser.cs | 72 +++++++++++++++++++ .../Trakt/User/TraktUserRequestGenerator.cs | 2 +- .../Trakt/User/TraktUserSettings.cs | 7 +- .../Trakt/User/TraktUserWatchedListType.cs | 14 ++++ 8 files changed, 133 insertions(+), 18 deletions(-) create mode 100644 src/NzbDrone.Core/ImportLists/Trakt/User/TraktUserParser.cs create mode 100644 src/NzbDrone.Core/ImportLists/Trakt/User/TraktUserWatchedListType.cs diff --git a/src/NzbDrone.Core/ImportLists/Trakt/Popular/TraktPopularParser.cs b/src/NzbDrone.Core/ImportLists/Trakt/Popular/TraktPopularParser.cs index 006e1f7ef..ce1e9aac9 100644 --- a/src/NzbDrone.Core/ImportLists/Trakt/Popular/TraktPopularParser.cs +++ b/src/NzbDrone.Core/ImportLists/Trakt/Popular/TraktPopularParser.cs @@ -26,24 +26,24 @@ namespace NzbDrone.Core.ImportLists.Trakt.Popular return listItems; } - var jsonResponse = new List<TraktSeriesResource>(); + var traktSeries = new List<TraktSeriesResource>(); if (_settings.TraktListType == (int)TraktPopularListType.Popular) { - jsonResponse = STJson.Deserialize<List<TraktSeriesResource>>(_importResponse.Content); + traktSeries = STJson.Deserialize<List<TraktSeriesResource>>(_importResponse.Content); } else { - jsonResponse = STJson.Deserialize<List<TraktResponse>>(_importResponse.Content).SelectList(c => c.Show); + traktSeries = STJson.Deserialize<List<TraktResponse>>(_importResponse.Content).SelectList(c => c.Show); } - // no movies were return - if (jsonResponse == null) + // no series were returned + if (traktSeries == null) { return listItems; } - foreach (var series in jsonResponse) + foreach (var series in traktSeries) { listItems.AddIfNotNull(new ImportListItemInfo() { diff --git a/src/NzbDrone.Core/ImportLists/Trakt/TraktAPI.cs b/src/NzbDrone.Core/ImportLists/Trakt/TraktAPI.cs index dcaf98f20..43318c1bc 100644 --- a/src/NzbDrone.Core/ImportLists/Trakt/TraktAPI.cs +++ b/src/NzbDrone.Core/ImportLists/Trakt/TraktAPI.cs @@ -1,4 +1,5 @@ -using Newtonsoft.Json; +using System.Collections.Generic; +using System.Text.Json.Serialization; namespace NzbDrone.Core.ImportLists.Trakt { @@ -16,6 +17,8 @@ namespace NzbDrone.Core.ImportLists.Trakt public string Title { get; set; } public int? Year { get; set; } public TraktSeriesIdsResource Ids { get; set; } + [JsonPropertyName("aired_episodes")] + public int AiredEpisodes { get; set; } } public class TraktResponse @@ -23,13 +26,29 @@ namespace NzbDrone.Core.ImportLists.Trakt public TraktSeriesResource Show { get; set; } } + public class TraktWatchedEpisodeResource + { + public int? Plays { get; set; } + } + + public class TraktWatchedSeasonResource + { + public int? Number { get; set; } + public List<TraktWatchedEpisodeResource> Episodes { get; set; } + } + + public class TraktWatchedResponse : TraktResponse + { + public List<TraktWatchedSeasonResource> Seasons { get; set; } + } + public class RefreshRequestResponse { - [JsonProperty("access_token")] + [JsonPropertyName("access_token")] public string AccessToken { get; set; } - [JsonProperty("expires_in")] + [JsonPropertyName("expires_in")] public int ExpiresIn { get; set; } - [JsonProperty("refresh_token")] + [JsonPropertyName("refresh_token")] public string RefreshToken { get; set; } } diff --git a/src/NzbDrone.Core/ImportLists/Trakt/TraktParser.cs b/src/NzbDrone.Core/ImportLists/Trakt/TraktParser.cs index 313378363..ffde5a462 100644 --- a/src/NzbDrone.Core/ImportLists/Trakt/TraktParser.cs +++ b/src/NzbDrone.Core/ImportLists/Trakt/TraktParser.cs @@ -22,20 +22,20 @@ namespace NzbDrone.Core.ImportLists.Trakt return series; } - var jsonResponse = STJson.Deserialize<List<TraktResponse>>(_importResponse.Content); + var traktResponses = STJson.Deserialize<List<TraktResponse>>(_importResponse.Content); - // no series were return - if (jsonResponse == null) + // no series were returned + if (traktResponses == null) { return series; } - foreach (var show in jsonResponse) + foreach (var traktResponse in traktResponses) { series.AddIfNotNull(new ImportListItemInfo() { - Title = show.Show.Title, - TvdbId = show.Show.Ids.Tvdb.GetValueOrDefault() + Title = traktResponse.Show.Title, + TvdbId = traktResponse.Show.Ids.Tvdb.GetValueOrDefault() }); } diff --git a/src/NzbDrone.Core/ImportLists/Trakt/User/TraktUserImport.cs b/src/NzbDrone.Core/ImportLists/Trakt/User/TraktUserImport.cs index 03fde5d08..76bcf71d7 100644 --- a/src/NzbDrone.Core/ImportLists/Trakt/User/TraktUserImport.cs +++ b/src/NzbDrone.Core/ImportLists/Trakt/User/TraktUserImport.cs @@ -19,6 +19,11 @@ namespace NzbDrone.Core.ImportLists.Trakt.User public override string Name => "Trakt User"; + public override IParseImportListResponse GetParser() + { + return new TraktUserParser(Settings); + } + public override IImportListRequestGenerator GetRequestGenerator() { return new TraktUserRequestGenerator() diff --git a/src/NzbDrone.Core/ImportLists/Trakt/User/TraktUserParser.cs b/src/NzbDrone.Core/ImportLists/Trakt/User/TraktUserParser.cs new file mode 100644 index 000000000..3f3f27283 --- /dev/null +++ b/src/NzbDrone.Core/ImportLists/Trakt/User/TraktUserParser.cs @@ -0,0 +1,72 @@ +using System.Collections.Generic; +using System.Linq; +using NzbDrone.Common.Extensions; +using NzbDrone.Common.Serializer; +using NzbDrone.Core.Parser.Model; + +namespace NzbDrone.Core.ImportLists.Trakt.User +{ + public class TraktUserParser : TraktParser + { + private readonly TraktUserSettings _settings; + private ImportListResponse _importResponse; + + public TraktUserParser(TraktUserSettings settings) + { + _settings = settings; + } + + public override IList<ImportListItemInfo> ParseResponse(ImportListResponse importResponse) + { + _importResponse = importResponse; + + var listItems = new List<ImportListItemInfo>(); + + if (!PreProcess(_importResponse)) + { + return listItems; + } + + var traktSeries = new List<TraktSeriesResource>(); + + if (_settings.TraktListType == (int)TraktUserListType.UserWatchedList) + { + var jsonWatchedResponse = STJson.Deserialize<List<TraktWatchedResponse>>(_importResponse.Content); + + switch (_settings.TraktWatchedListType) + { + case (int)TraktUserWatchedListType.InProgress: + traktSeries = jsonWatchedResponse.Where(c => c.Seasons.Where(s => s.Number > 0).Sum(s => s.Episodes.Count) < c.Show.AiredEpisodes).SelectList(c => c.Show); + break; + case (int)TraktUserWatchedListType.CompletelyWatched: + traktSeries = jsonWatchedResponse.Where(c => c.Seasons.Where(s => s.Number > 0).Sum(s => s.Episodes.Count) == c.Show.AiredEpisodes).SelectList(c => c.Show); + break; + default: + traktSeries = jsonWatchedResponse.SelectList(c => c.Show); + break; + } + } + else + { + traktSeries = STJson.Deserialize<List<TraktResponse>>(_importResponse.Content).SelectList(c => c.Show); + } + + // no series were returned + if (traktSeries == null) + { + return listItems; + } + + foreach (var series in traktSeries) + { + listItems.AddIfNotNull(new ImportListItemInfo() + { + Title = series.Title, + TvdbId = series.Ids.Tvdb.GetValueOrDefault(), + }); + } + + return listItems; + } + } +} diff --git a/src/NzbDrone.Core/ImportLists/Trakt/User/TraktUserRequestGenerator.cs b/src/NzbDrone.Core/ImportLists/Trakt/User/TraktUserRequestGenerator.cs index 3bfd0d629..8f7c0396d 100644 --- a/src/NzbDrone.Core/ImportLists/Trakt/User/TraktUserRequestGenerator.cs +++ b/src/NzbDrone.Core/ImportLists/Trakt/User/TraktUserRequestGenerator.cs @@ -30,7 +30,7 @@ namespace NzbDrone.Core.ImportLists.Trakt.User link += $"/users/{userName}/watchlist/shows?limit={Settings.Limit}"; break; case (int)TraktUserListType.UserWatchedList: - link += $"/users/{userName}/watched/shows?limit={Settings.Limit}"; + link += $"/users/{userName}/watched/shows?extended=full&limit={Settings.Limit}"; break; case (int)TraktUserListType.UserCollectionList: link += $"/users/{userName}/collection/shows?limit={Settings.Limit}"; diff --git a/src/NzbDrone.Core/ImportLists/Trakt/User/TraktUserSettings.cs b/src/NzbDrone.Core/ImportLists/Trakt/User/TraktUserSettings.cs index 9784c858b..057f3edca 100644 --- a/src/NzbDrone.Core/ImportLists/Trakt/User/TraktUserSettings.cs +++ b/src/NzbDrone.Core/ImportLists/Trakt/User/TraktUserSettings.cs @@ -9,6 +9,7 @@ namespace NzbDrone.Core.ImportLists.Trakt.User : base() { RuleFor(c => c.TraktListType).NotNull(); + RuleFor(c => c.TraktWatchedListType).NotNull(); RuleFor(c => c.AuthUser).NotEmpty(); } } @@ -20,12 +21,16 @@ namespace NzbDrone.Core.ImportLists.Trakt.User public TraktUserSettings() { TraktListType = (int)TraktUserListType.UserWatchList; + TraktWatchedListType = (int)TraktUserWatchedListType.All; } [FieldDefinition(1, Label = "List Type", Type = FieldType.Select, SelectOptions = typeof(TraktUserListType), HelpText = "Type of list you're seeking to import from")] public int TraktListType { get; set; } - [FieldDefinition(2, Label = "Username", HelpText = "Username for the List to import from (empty to use Auth User)")] + [FieldDefinition(2, Label = "Watched List Filter", Type = FieldType.Select, SelectOptions = typeof(TraktUserWatchedListType), HelpText = "If List Type is Watched. Series do you want to import from")] + public int TraktWatchedListType { get; set; } + + [FieldDefinition(3, Label = "Username", HelpText = "Username for the List to import from (empty to use Auth User)")] public string Username { get; set; } } } diff --git a/src/NzbDrone.Core/ImportLists/Trakt/User/TraktUserWatchedListType.cs b/src/NzbDrone.Core/ImportLists/Trakt/User/TraktUserWatchedListType.cs new file mode 100644 index 000000000..5b6526e6e --- /dev/null +++ b/src/NzbDrone.Core/ImportLists/Trakt/User/TraktUserWatchedListType.cs @@ -0,0 +1,14 @@ +using System.Runtime.Serialization; + +namespace NzbDrone.Core.ImportLists.Trakt.User +{ + public enum TraktUserWatchedListType + { + [EnumMember(Value = "All")] + All = 0, + [EnumMember(Value = "In Progress")] + InProgress = 1, + [EnumMember(Value = "100% Watched")] + CompletelyWatched = 2 + } +} From b5e2b32915f1560e434fad066627c24e07eb0ba2 Mon Sep 17 00:00:00 2001 From: Weblate <noreply@weblate.org> Date: Tue, 17 Oct 2023 06:49:51 +0000 Subject: [PATCH 061/136] 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: Daniel Suárez Bobes <danielsbobes@gmail.com> Co-authored-by: DavidHenryThoreau <sorau@protonmail.com> Co-authored-by: Dlgeri123 <bornemiszageri@gmail.com> Co-authored-by: Havok Dan <havokdan@yahoo.com.br> Co-authored-by: Ruben Lourenco <ruben.lourenco01@gmail.com> Co-authored-by: Timo <Tclemens@live.com> Co-authored-by: Weblate <noreply@weblate.org> Co-authored-by: bai0012 <baicongrui@gmail.com> Co-authored-by: jianl <jianjianfengyun@126.com> Co-authored-by: 宿命 <331874545@qq.com> Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/ Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/de/ Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/es/ Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/fr/ Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/hu/ Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/pt/ Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/pt_BR/ Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/zh_CN/ Translation: Servarr/Sonarr --- src/NzbDrone.Core/Localization/Core/de.json | 22 +- src/NzbDrone.Core/Localization/Core/es.json | 3 +- src/NzbDrone.Core/Localization/Core/fr.json | 1146 +++++++++++++++-- src/NzbDrone.Core/Localization/Core/hu.json | 33 +- src/NzbDrone.Core/Localization/Core/pt.json | 2 +- .../Localization/Core/pt_BR.json | 8 +- .../Localization/Core/zh_CN.json | 188 +-- 7 files changed, 1224 insertions(+), 178 deletions(-) diff --git a/src/NzbDrone.Core/Localization/Core/de.json b/src/NzbDrone.Core/Localization/Core/de.json index dc241f8b0..76e249266 100644 --- a/src/NzbDrone.Core/Localization/Core/de.json +++ b/src/NzbDrone.Core/Localization/Core/de.json @@ -38,5 +38,25 @@ "ManageDownloadClients": "Verwalte Download Clienten", "ManageImportLists": "Verwalte Einspiel-Listen", "NoDownloadClientsFound": "Keine Download Clienten gefunden", - "SkipFreeSpaceCheck": "Prüfung des freien Speichers überspringen" + "SkipFreeSpaceCheck": "Prüfung des freien Speichers überspringen", + "AbsoluteEpisodeNumber": "Exakte Folgennummer", + "AddConnection": "Verbindung hinzufügen", + "AddAutoTagError": "Neues automatisches Tag kann nicht hinzugefügt werden, bitte versuche es erneut.", + "AddConditionError": "Neue Bedingung kann nicht hinzugefügt werden, bitte versuchen Sie es erneut.", + "AddCustomFormat": "Eigenes Format hinzufügen", + "AddCustomFormatError": "Neues eigenes Format kann nicht hinzugefügt werden, bitte versuchen Sie es erneut.", + "AddDelayProfile": "Verzögerungsprofil hinzufügen", + "AddDownloadClientError": "Neuer Downloadmanager kann nicht hinzugefügt werden, bitte versuchen Sie es erneut.", + "AddExclusion": "Ausschluss hinzufügen", + "AddDownloadClient": "Downloadmanager hinzufügen", + "AddCondition": "Bedingung hinzufügen", + "AddAutoTag": "Automatisches Tag hinzufügen", + "AbsoluteEpisodeNumbers": "Exakte Folgennummer(n)", + "Add": "Hinzufügen", + "Activity": "Aktivität", + "About": "Über", + "Actions": "Aktionen", + "Absolute": "Exakte", + "AddANewPath": "Neuen Pfad hinzufügen", + "AddCustomFilter": "Eigenen Filter hinzufügen" } diff --git a/src/NzbDrone.Core/Localization/Core/es.json b/src/NzbDrone.Core/Localization/Core/es.json index e2ffc7bd9..1cfc8317b 100644 --- a/src/NzbDrone.Core/Localization/Core/es.json +++ b/src/NzbDrone.Core/Localization/Core/es.json @@ -227,5 +227,6 @@ "AddConnectionImplementation": "Añadir Conexión - {implementationName}", "AddDownloadClientImplementation": "Añadir Cliente de Descarga - {implementationName}", "VideoDynamicRange": "Video de Rango Dinámico", - "AuthenticationMethodHelpTextWarning": "Por favor selecciona un método válido de autenticación" + "AuthenticationMethodHelpTextWarning": "Por favor selecciona un método válido de autenticación", + "AddCustomFilter": "Añadir filtro personalizado" } diff --git a/src/NzbDrone.Core/Localization/Core/fr.json b/src/NzbDrone.Core/Localization/Core/fr.json index 223c4d01f..8f377bef5 100644 --- a/src/NzbDrone.Core/Localization/Core/fr.json +++ b/src/NzbDrone.Core/Localization/Core/fr.json @@ -1,6 +1,6 @@ { "Language": "Langue", - "UiLanguage": "UI Langue", + "UiLanguage": "Langue de l'interface utilisateur", "Added": "Ajouté", "ApiKeyValidationHealthCheckMessage": "Veuillez mettre à jour votre clé API pour qu'elle contienne au moins {length} caractères. Vous pouvez le faire via les paramètres ou le fichier de configuration", "AppDataLocationHealthCheckMessage": "La mise à jour ne sera pas possible afin empêcher la suppression de AppData lors de la mise à jour", @@ -74,7 +74,7 @@ "AddCustomFormatError": "Impossible d'ajouter un nouveau format personnalisé, veuillez réessayer.", "AddIndexerError": "Impossible d'ajouter un nouvelle indexeur, veuillez réessayer.", "AddNewRestriction": "Ajouter une nouvelle restriction", - "AddListError": "Impossible d'ajouter une nouvelle liste", + "AddListError": "Impossible d'ajouter une nouvelle liste, veuillez réessayer.", "AddDownloadClientError": "Impossible d'ajouter un nouveau client de téléchargement, veuillez réessayer.", "AddRemotePathMapping": "Ajouter un mappage des chemins d'accès", "AddNewSeries": "Ajouter une nouvelle série", @@ -164,7 +164,7 @@ "BlocklistReleases": "Publications de la liste de blocage", "BindAddress": "Adresse de liaison", "BackupsLoadError": "Impossible de charger les sauvegardes", - "BlocklistReleaseHelpText": "Lance une nouvelle recherche pour cet épisode et empêche que cette version soit à nouveau récupérée.", + "BlocklistReleaseHelpText": "Lance une nouvelle recherche pour cet épisode et empêche que cette version soit à nouveau récupérée", "BuiltIn": "Intégré", "BrowserReloadRequired": "Rechargement du navigateur requis", "BypassDelayIfAboveCustomFormatScore": "Ignorer si le score est supérieur au format personnalisé", @@ -185,10 +185,10 @@ "ChownGroupHelpText": "Nom du groupe ou gid. Utilisez gid pour les systèmes de fichiers distants.", "ChownGroupHelpTextWarning": "Cela ne fonctionne que si l'utilisateur qui exécute sonarr est le propriétaire du fichier. Il est préférable de s'assurer que le client de téléchargement utilise le même groupe que sonarr.", "ClickToChangeQuality": "Cliquez pour changer la qualité", - "RefreshSeries": "Actualiser les séries", - "RecycleBinUnableToWriteHealthCheckMessage": "Impossible d'écrire dans le dossier configuré de la corbeille de recyclage : {path}. Assurez-vous que ce chemin existe et qu'il est accessible en écriture par l'utilisateur qui exécute Sonarr.", + "RefreshSeries": "Actualiser la série", + "RecycleBinUnableToWriteHealthCheckMessage": "Impossible d'écrire dans le dossier de la corbeille configuré : {path}. Assurez-vous que ce chemin existe et qu'il est accessible en écriture par l'utilisateur exécutant {appName}", "RemotePathMappingFileRemovedHealthCheckMessage": "Le fichier {path} a été supprimé en cours de traitement.", - "RemotePathMappingFilesGenericPermissionsHealthCheckMessage": "Le client de téléchargement {downloadClientName} a signalé des fichiers dans {path} mais Sonarr ne peut pas voir ce répertoire. Il se peut que vous deviez ajuster les permissions du dossier.", + "RemotePathMappingFilesGenericPermissionsHealthCheckMessage": "Le client de téléchargement {downloadClientName} a signalé des fichiers dans {path} mais {appName} ne peut pas voir ce répertoire. Il se peut que vous deviez ajuster les permissions du dossier.", "CalendarFeed": "Flux de calendrier {appName}", "CalendarLegendDownloadedTooltip": "L'épisode a été téléchargé et classé", "CalendarLegendDownloadingTooltip": "L'épisode est en cours de téléchargement", @@ -219,21 +219,21 @@ "Queue": "File d'attente", "Wanted": "Recherché", "Remove": "Retirer", - "RemoveSelectedItemQueueMessageText": "Voulez-vous vraiment supprimer 1 élément de la file d'attente ?", + "RemoveSelectedItemQueueMessageText": "Êtes-vous sûr de vouloir supprimer 1 élément de la file d'attente ?", "DeleteNotification": "Supprimer la notification", "DeleteNotificationMessageText": "Voulez-vous supprimer la notification « {name} » ?", "MediaManagement": "Gestion des médias", - "MediaManagementSettingsSummary": "Nommage, paramètres de gestion des médias et dossiers racine", + "MediaManagementSettingsSummary": "Nommage, paramètres de gestion de fichiers et dossiers racine", "MinimumFreeSpace": "Espace libre minimum", "Monitored": "Surveillé", "NoHistoryFound": "Aucun historique n'a été trouvé", "NoHistoryBlocklist": "Pas d'historique de liste noire", - "Period": "Point", + "Period": "Période", "QualityDefinitionsLoadError": "Impossible de charger les définitions de qualité", - "RemoveSelected": "Supprimer la sélection", + "RemoveSelected": "Enlever la sélection", "UnknownEventTooltip": "Événement inconnu", "TablePageSizeHelpText": "Nombre d'éléments à afficher sur chaque page", - "Tags": "Étiquettes", + "Tags": "Tags", "Unknown": "Inconnu", "UnmappedFolders": "Dossiers non mappés", "UpgradesAllowed": "Mises à niveau autorisées", @@ -255,14 +255,14 @@ "Duplicate": "Dupliqué", "ExtraFileExtensionsHelpTextsExamples": "Exemples : '.sub, .nfo' ou 'sub,nfo'", "None": "Aucun", - "NoTagsHaveBeenAddedYet": "Aucune étiquette n'a encore été ajoutée", + "NoTagsHaveBeenAddedYet": "Aucune identification n'a été ajoutée pour l'instant", "QualityLimitsHelpText": "Les limites sont automatiquement ajustées en fonction de la durée de la série et du nombre d'épisodes dans le fichier.", "QualityProfiles": "Profils de qualité", - "Range": "Plage", + "Range": "Gamme", "Required": "Obligatoire", "Unmonitored": "Non surveillé", "UsenetDelay": "Retard Usenet", - "RemoveTagsAutomatically": "Supprimer automatiquement les étiquettes", + "RemoveTagsAutomatically": "Supprimer les balises automatiquement", "CountDownloadClientsSelected": "{count} client(s) de téléchargement sélectionné(s)", "DiskSpace": "Espace disque", "Save": "Sauvegarder", @@ -273,11 +273,11 @@ "ManualImport": "Importation manuelle", "ReplaceWithSpaceDash": "Remplacer par un espace puis un tiret", "Scene": "Scène", - "SelectDownloadClientModalTitle": "{modalTitle} - Sélectionner le client de téléchargements", + "SelectDownloadClientModalTitle": "{modalTitle} – Sélectionnez le client de téléchargement", "Shutdown": "Éteindre", - "SonarrTags": "Étiquettes {appName}", - "TagsSettingsSummary": "Voir toutes les étiquettes et leur utilisation. Les étiquettes inutilisées peuvent être supprimées", - "Ui": "UI", + "SonarrTags": "{appName} Tags", + "TagsSettingsSummary": "Voir toutes les balises et comment elles sont utilisées. Les balises inutilisées peuvent être supprimées", + "Ui": "Interface utilisateur", "Uppercase": "Majuscules", "TorrentDelayTime": "Retard du torrent : {torrentDelay}", "Underscore": "Tiret du bas", @@ -287,23 +287,23 @@ "DefaultNameCopiedSpecification": "{name} - Copier", "IndexerDownloadClientHealthCheckMessage": "Indexeurs avec des clients de téléchargement invalides : {0].", "Name": "Nom", - "OrganizeNothingToRename": "C'est fait ! Mon travail est terminé, plus aucun fichier à renommer.", + "OrganizeNothingToRename": "Succès ! Mon travail est terminé, pas de fichiers à renommer.", "PortNumber": "Numéro de port", - "ProfilesSettingsSummary": "Profils de qualité, de langue, de retard et de release", + "ProfilesSettingsSummary": "Qualité, langue, délais sortie et profils", "Qualities": "Qualités", "QualitiesLoadError": "Impossible de charger les qualités", "RenameFiles": "Renommer les fichiers", "Restart": "Redémarrer", "RestartNow": "Redémarrer maintenant", "SaveSettings": "Enregistrer les paramètres", - "ShowMonitoredHelpText": "Affiche le statut surveillé sous l'affiche", + "ShowMonitoredHelpText": "Afficher l'état de surveillance sous le poster", "SkipFreeSpaceCheck": "Ignorer la vérification de l'espace libre", "Sunday": "Dimanche", "TorrentDelay": "Retard du torrent", - "DownloadClients": "Clients de télécharg.", + "DownloadClients": "Clients de téléchargements", "CustomFormats": "Formats perso.", "NoIndexersFound": "Aucun indexeur n'a été trouvé", - "Profiles": "Profils", + "Profiles": "Profiles", "Dash": "Tiret", "DelayProfileProtocol": "Protocole : {preferredProtocol}", "DeleteBackupMessageText": "Voulez-vous supprimer la sauvegarde « {name} » ?", @@ -322,14 +322,14 @@ "FreeSpace": "Espace libre", "Host": "Hôte", "ICalIncludeUnmonitoredHelpText": "Inclure les épisodes non surveillés dans le flux iCal", - "RenameEpisodesHelpText": "{appName} utilisera le nom de fichier existant si le renommage est désactivé", - "RestartRequiredToApplyChanges": "{appName} exige un redémarrage pour appliquer les modifications, voulez-vous redémarrer maintenant ?", + "RenameEpisodesHelpText": "{appName} utilisera le nom de fichier existant si le changement de nom est désactivé", + "RestartRequiredToApplyChanges": "{appName} nécessite un redémarrage pour appliquer les modifications. Voulez-vous redémarrer maintenant ?", "OrganizeRenamingDisabled": "Le renommage est désactivé, rien à renommer", "PendingChangesStayReview": "Rester et vérifier les modifications", "PendingChangesMessage": "Vous avez des modifications non sauvegardées, voulez-vous vraiment quitter cette page ?", "RestartRequiredHelpTextWarning": "Nécessite un redémarrage pour prendre effet", - "SelectLanguageModalTitle": "{modalTitle} - Sélectionner une langue", - "SelectFolderModalTitle": "{modalTitle} - Sélectionner un dossier", + "SelectLanguageModalTitle": "{modalTitle} – Sélectionner la langue", + "SelectFolderModalTitle": "{modalTitle} – Sélectionner un dossier", "Settings": "Paramètres", "UsenetDelayTime": "Retard Usenet : {usenetDelay}", "CountIndexersSelected": "{count} indexeur(s) sélectionné(s)", @@ -344,69 +344,69 @@ "FailedToLoadQualityProfilesFromApi": "Échec du chargement des profils de qualité depuis l'API", "Filename": "Nom de fichier", "FailedToLoadTagsFromApi": "Échec du chargement des étiquettes depuis l'API", - "FormatTimeSpanDays": "{days}j {time}", + "FormatTimeSpanDays": "{days}d {time}", "FormatShortTimeSpanSeconds": "{seconds} seconde(s)", - "FilterEqual": "égal", - "Implementation": "Implémentation", + "FilterEqual": "égale", + "Implementation": "Mise en œuvre", "ICalSeasonPremieresOnlyHelpText": "Seul le premier épisode d'une saison sera dans le flux", "ICalFeed": "Flux iCal", "History": "Historique", "HideAdvanced": "Masquer param. av.", - "Large": "Grande", + "Large": "Grand", "LanguagesLoadError": "Impossible de charger les langues", "IncludeUnmonitored": "Inclure les non surveillés", "KeyboardShortcutsFocusSearchBox": "Placer le curseur sur la barre de recherche", "KeyboardShortcutsSaveSettings": "Enregistrer les paramètres", "ManageClients": "Gérer les clients", - "Logout": "Déconnexion", + "Logout": "Se déconnecter", "MediaManagementSettings": "Paramètres de gestion des médias", - "Lowercase": "Minuscules", - "MaximumSizeHelpText": "Taille maximale d'une release à récupérer en Mo. Mettre à zéro pour définir sur illimité", + "Lowercase": "Minuscule", + "MaximumSizeHelpText": "Taille maximale d'une version à récupérer en Mo. Régler sur zéro pour définir sur illimité", "MissingNoItems": "Aucun élément manquant", - "MoveAutomatically": "Déplacer automatiquement", + "MoveAutomatically": "Se déplacer automatiquement", "MoreInfo": "Plus d'informations", "NoHistory": "Aucun historique", - "MonitoredStatus": "Surveillé / État", + "MonitoredStatus": "Surveillé/Statut", "MultiEpisodeStyle": "Style multi-épisodes", - "NoChanges": "Aucune modification", - "NoSeasons": "Aucune saison", - "PosterSize": "Taille des affiches", - "PosterOptions": "Options des affiches", - "Posters": "Affiches", + "NoChanges": "Aucuns changements", + "NoSeasons": "Pas de saisons", + "PosterSize": "Poster taille", + "PosterOptions": "Poster options", + "Posters": "Posters", "Or": "ou", - "ParseModalHelpTextDetails": "{appName} va tenter de parser le titre et de vous afficher les détails à son sujet", - "OrganizeNamingPattern": "Modèle de nommage : `{episodeFormat}`", + "ParseModalHelpTextDetails": "{appName} tentera d'analyser le titre et de vous montrer des détails à ce sujet", + "OrganizeNamingPattern": "Modèle de dénomination : `{episodeFormat}`", "OneSeason": "1 saison", - "Ok": "OK", + "Ok": "Ok", "PendingChangesDiscardChanges": "Abandonner les modifications et quitter", - "PreferProtocol": "{preferredProtocol} préféré", - "Refresh": "Actualiser", + "PreferProtocol": "Préféré {preferredProtocol}", + "Refresh": "Rafraîchir", "PrefixedRange": "Plage préfixée", "PreferredProtocol": "Protocole préféré", - "ProtocolHelpText": "Choisissez le(s) protocole(s) à utiliser et celui qui est préféré lors du choix entre des versions par ailleurs égales", + "ProtocolHelpText": "Choisissez quel(s) protocole(s) utiliser et lequel est préféré lorsque vous choisissez entre des versions par ailleurs égales", "Quality": "Qualité", "Presets": "Préconfigurations", "RemoveSelectedItems": "Supprimer les éléments sélectionnés", "ReplaceWithSpaceDashSpace": "Remplacer par un espace, un tiret puis un espace", - "RestartLater": "Je redémarrerai plus tard", + "RestartLater": "Redémarrer plus tard", "ReplaceIllegalCharacters": "Remplacer les caractères illégaux", "ReplaceIllegalCharactersHelpText": "Remplacer les caractères illégaux. Si non coché, {appName} les supprimera", "Repeat": "Répété", - "Renamed": "Renommé", + "Renamed": "Renommer", "ResetQualityDefinitions": "Réinitialiser les définitions de qualité", "ResetQualityDefinitionsMessageText": "Voulez-vous vraiment réinitialiser les définitions de qualité ?", "ResetTitles": "Réinitialiser les titres", - "ResetDefinitionTitlesHelpText": "Réinitialiser les titres des définitions ainsi que les valeurs", + "ResetDefinitionTitlesHelpText": "Réinitialiser les titres de définition ainsi que les valeurs", "Reset": "Réinitialiser", "RestartSonarr": "Redémarrer {appName}", - "RootFolderLoadError": "Impossible de charger le dossier racine", - "SelectSeries": "Sélectionner des séries", - "SearchForMissing": "Recherche les manquants", + "RootFolderLoadError": "Impossible d'ajouter le dossier racine", + "SelectSeries": "Sélectionnez la série", + "SearchForMissing": "Recherche des manquants", "SearchAll": "Tout rechercher", - "Series": "Séries", - "ShowSearchHelpText": "Affiche le bouton de recherche au survol du curseur", - "SmartReplace": "Replacement intelligent", - "SmartReplaceHint": "Tiret ou espace puis tiret selon le nom", + "Series": "Série", + "ShowSearchHelpText": "Afficher le bouton de recherche au survol", + "SmartReplace": "Remplacement intelligent", + "SmartReplaceHint": "Dash ou Space Dash selon le nom", "Space": "Espace", "SizeLimit": "Limite de taille", "TagIsNotUsedAndCanBeDeleted": "L'étiquette n'est pas utilisée et peut être supprimée", @@ -415,7 +415,7 @@ "Titles": "Titres", "True": "Vrai", "UnmonitorDeletedEpisodes": "Annuler la surveillance des épisodes supprimés", - "UnsavedChanges": "Modifications non sauvegardées", + "UnsavedChanges": "Modifications non enregistrées", "TorrentDelayHelpText": "Délai en minutes avant de récupérer un torrent", "Username": "Nom d'utilisateur", "UnselectAll": "Tout désélectionner", @@ -424,41 +424,41 @@ "InteractiveImport": "Importation interactive", "InteractiveImportNoImportMode": "Un mode d'importation doit être sélectionné", "Today": "Aujourd'hui", - "QualityDefinitions": "Définitions des qualités", + "QualityDefinitions": "Définitions de la qualité", "ManageDownloadClients": "Gérer les clients de téléchargement", "NoDownloadClientsFound": "Aucun client de téléchargement n'a été trouvé", - "NotificationStatusAllClientHealthCheckMessage": "Toutes les notifications sont indisponibles en raison de dysfonctionnements", - "NotificationStatusSingleClientHealthCheckMessage": "Notifications indisponibles en raison de dysfonctionnements : {notificationNames}", + "NotificationStatusAllClientHealthCheckMessage": "Toutes les notifications ne sont pas disponibles en raison d'échecs", + "NotificationStatusSingleClientHealthCheckMessage": "Notifications indisponibles en raison d'échecs : {notificationNames}", "RecentChanges": "Changements récents", - "SetTags": "Définir les étiquettes", + "SetTags": "Définir des balises", "Replace": "Remplacer", - "ResetAPIKeyMessageText": "Voulez-vous réinitialiser votre clé d'API ?", + "ResetAPIKeyMessageText": "Êtes-vous sûr de vouloir réinitialiser votre clé API ?", "StopSelecting": "Effacer la sélection", "WhatsNew": "Quoi de neuf ?", "EditDownloadClientImplementation": "Modifier le client de téléchargement - {implementationName}", "External": "Externe", "Monday": "Lundi", - "ShowQualityProfileHelpText": "Affiche le profil de qualité sous l'affiche", - "IncludeCustomFormatWhenRenamingHelpText": "Inclut dans {Custom Formats} renommant le format", + "ShowQualityProfileHelpText": "Afficher le profil de qualité sous l'affiche", + "IncludeCustomFormatWhenRenamingHelpText": "Inclure dans le format de renommage {Formats personnalisés}", "SelectDropdown": "Sélectionner...", "InteractiveImportNoFilesFound": "Aucun fichier vidéo n'a été trouvé dans le dossier sélectionné", "Umask": "Umask", - "OrganizeRelativePaths": "Tous les chemins sont relatifs à : `{path}`", + "OrganizeRelativePaths": "Tous les chemins sont relatifs à : `{chemin}`", "OverrideGrabNoLanguage": "Au moins une langue doit être sélectionnée", - "OverrideGrabNoQuality": "Une qualité doit être sélectionnée", - "NoSeriesFoundImportOrAdd": "Aucune série n'a été trouvée, pour commencer vous aller devoir importer vos séries existantes ou ajouter une nouvelle série.", + "OverrideGrabNoQuality": "La qualité doit être sélectionnée", + "NoSeriesFoundImportOrAdd": "Aucune série trouvée. Pour commencer, vous souhaiterez importer votre série existante ou ajouter une nouvelle série.", "ICalFeedHelpText": "Copiez cette URL dans votre/vos client(s) ou cliquez pour abonner si votre navigateur est compatible avec webcal", - "SeasonFolderFormat": "Format du dossier des saisons", - "QualitiesHelpText": "Les qualités placées en haut de la liste sont privilégiées même si elles ne sont pas cochées. Les qualités d'un même groupe sont égales. Seules les qualités cochées sont recherchées", - "PrioritySettings": "Priorité : {priority}", - "ImportExistingSeries": "Importer des séries existantes", - "RootFolderSelectFreeSpace": "{freeSpace} disponibles", + "SeasonFolderFormat": "Format du dossier de saison", + "QualitiesHelpText": "Les qualités plus élevées dans la liste sont plus préférées. Les qualités au sein d’un même groupe sont égales. Seules les qualités vérifiées sont recherchées", + "PrioritySettings": "Priorité : {priority}", + "ImportExistingSeries": "Importer une série existante", + "RootFolderSelectFreeSpace": "{freeSpace} Libre", "WantMoreControlAddACustomFormat": "Vous voulez plus de contrôle sur les téléchargements préférés ? Ajouter un [Format Personnalisé](/settings/customformats)", - "RemoveSelectedItemsQueueMessageText": "Voulez-vous vraiment supprimer {selectedCount} élément(s) de la file d'attente ?", + "RemoveSelectedItemsQueueMessageText": "Voulez-vous vraiment supprimer {selectedCount} éléments de la file d'attente ?", "UpdateAll": "Tout actualiser", "EnableSslHelpText": "Nécessite un redémarrage en tant qu'administrateur pour être effectif", "UnmonitorDeletedEpisodesHelpText": "Les épisodes effacés du disque dur ne seront plus surveillés dans {appName}", - "RssSync": "Synchro RSS", + "RssSync": "Synchronisation RSS", "RestartReloadNote": "Remarque : {appName} redémarrera et rechargera automatiquement l'interface utilisateur pendant le processus de restauration.", "FileBrowserPlaceholderText": "Commencer à écrire ou sélectionner un chemin ci-dessous", "ICalLink": "Lien iCal", @@ -477,14 +477,14 @@ "CouldNotFindResults": "Aucun résultat pour « {term} »", "ConnectionLost": "Connexion perdue", "HistoryLoadError": "Impossible de charger l'historique", - "Hostname": "Nom d'hôte", + "Hostname": "Hostname", "ReplaceWithDash": "Remplacer par un tiret", "Status": "État", "ColonReplacement": "Remplacement pour le « deux-points »", "CountSeriesSelected": "{count} série(s) sélectionnée(s)", "Default": "Par défaut", - "ShowAdvanced": "Afficher param. av.", - "SeasonPremieresOnly": "Début de saison uniquement", + "ShowAdvanced": "Afficher les paramètres avancés", + "SeasonPremieresOnly": "Premières saisons uniquement", "SearchSelected": "Rechercher la sélection", "Seasons": "Saisons", "Season": "Saison", @@ -501,6 +501,992 @@ "FormatRuntimeMinutes": "{minutes} m", "FormatShortTimeSpanHours": "{hours} heure(s)", "FormatShortTimeSpanMinutes": "{minutes} minute(s)", - "SeriesID": "Identifiant de la série", - "Score": "Score" + "SeriesID": "ID de série", + "Score": "Score", + "IndexerStatusAllUnavailableHealthCheckMessage": "Tous les indexeurs sont indisponibles en raison de pannes", + "RemoveFailedDownloads": "Supprimer les téléchargements ayant échoué", + "RemovedSeriesMultipleRemovedHealthCheckMessage": "La série {0} a été supprimée de TheTVDB", + "FilterGreaterThanOrEqual": "supérieur ou égal à", + "FilterInLast": "à la fin", + "FilterInNext": "ensuite", + "FilterIsNot": "n'est pas", + "FilterLessThan": "moins que", + "FilterLessThanOrEqual": "inférieur ou égal", + "FilterNotEqual": "inégal", + "FilterNotInLast": "pas dans le dernier", + "FilterNotInNext": "pas dans le prochain", + "FilterSeriesPlaceholder": "Série de filtres", + "FilterStartsWith": "commence avec", + "FirstDayOfWeek": "Premier jour de la semaine", + "IconForFinalesHelpText": "Afficher l'icône pour les finales de séries/saisons en fonction des informations disponibles sur les épisodes", + "ImportListExclusionsLoadError": "Impossible de charger les exclusions de la liste d'importation", + "ImportListRootFolderMultipleMissingRootsHealthCheckMessage": "Plusieurs dossiers racine sont manquants pour les listes d'importation : {0}", + "ImportListSearchForMissingEpisodes": "Rechercher les épisodes manquants", + "ImportListSearchForMissingEpisodesHelpText": "Une fois la série ajoutée à {appName}, recherchez automatiquement les épisodes manquants", + "ImportListStatusAllUnavailableHealthCheckMessage": "Toutes les listes sont indisponibles en raison d'échecs", + "ImportListStatusUnavailableHealthCheckMessage": "Listes indisponibles en raison d'échecs : {0}", + "ImportMechanismEnableCompletedDownloadHandlingIfPossibleHealthCheckMessage": "Activer la gestion des téléchargements terminés si possible", + "Imported": "Importé", + "IndexerDownloadClientHelpText": "Spécifiez quel client de téléchargement est utilisé pour les récupérations à partir de cet indexeur", + "IndexerLongTermStatusAllUnavailableHealthCheckMessage": "Tous les indexeurs sont indisponibles en raison de pannes pendant plus de 6 heures", + "IndexerLongTermStatusUnavailableHealthCheckMessage": "Indexeurs indisponibles en raison d'échecs pendant plus de six heures : {0}", + "IndexerPriorityHelpText": "Priorité de l'indexeur de 1 (la plus élevée) à 50 (la plus basse). Valeur par défaut : 25. Utilisé lors de la récupération des versions comme départage pour des versions par ailleurs égales, {appName} utilisera toujours tous les indexeurs activés pour la synchronisation RSS et la recherche", + "IndexerRssNoIndexersAvailableHealthCheckMessage": "Tous les indexeurs compatibles RSS sont temporairement indisponibles en raison d'erreurs récentes de l'indexeur", + "IndexerSearchNoAutomaticHealthCheckMessage": "Aucun indexeur disponible avec la recherche automatique activée, {appName} ne fournira aucun résultat de recherche automatique", + "IndexerSearchNoInteractiveHealthCheckMessage": "Aucun indexeur n'est disponible avec la recherche interactive activée. {appName} ne fournira aucun résultat de recherche interactif", + "IndexerStatusUnavailableHealthCheckMessage": "Indexeurs indisponibles en raison d'échecs : {0}", + "Info": "Information", + "InstallLatest": "Installer le dernier", + "InteractiveImportNoLanguage": "La ou les langues doivent être choisies pour chaque fichier sélectionné", + "InteractiveImportNoQuality": "La qualité doit être choisie pour chaque fichier sélectionné", + "InteractiveSearchModalHeader": "Recherche interactive", + "InteractiveSearchModalHeaderSeason": "Recherche interactive - {season}", + "InteractiveSearchResultsFailedErrorMessage": "La recherche a échoué car il s'agit d'un {message}. Essayez d'actualiser les informations sur la série et vérifiez que les informations nécessaires sont présentes avant de lancer une nouvelle recherche.", + "InteractiveSearchSeason": "Recherche interactive de tous les épisodes de cette saison", + "Interval": "Intervalle", + "InvalidFormat": "Format invalide", + "InvalidUILanguage": "Votre interface utilisateur est définie sur une langue non valide, corrigez-la et enregistrez vos paramètres", + "Languages": "Langues", + "LastDuration": "Dernière durée", + "LastExecution": "Dernière exécution", + "LastWriteTime": "Heure de la dernière écriture", + "LatestSeason": "Dernière saison", + "LibraryImportTipsDontUseDownloadsFolder": "Ne l'utilisez pas pour importer des téléchargements à partir de votre client de téléchargement, cela concerne uniquement les bibliothèques organisées existantes, pas les fichiers non triés.", + "ListWillRefreshEveryInterval": "La liste sera actualisée tous les {refreshInterval}", + "ListsLoadError": "Impossible de charger les listes", + "Local": "Locale", + "LocalPath": "Chemin local", + "LocalStorageIsNotSupported": "Le stockage local n'est pas pris en charge ou désactivé. Un plugin ou une navigation privée l'ont peut-être désactivé.", + "LogLevel": "Niveau de journalisation", + "LogLevelTraceHelpTextWarning": "La journalisation des traces ne doit être activée que temporairement", + "Logging": "Enregistrement", + "Logs": "Journaux", + "MaintenanceRelease": "Version de maintenance : corrections de bugs et autres améliorations. Voir l'historique des validations Github pour plus de détails", + "ManageEpisodes": "Gérer les épisodes", + "ManageEpisodesSeason": "Gérer les fichiers d'épisodes de cette saison", + "ManageImportLists": "Gérer les listes d'importation", + "ManageIndexers": "Gérer les indexeurs", + "Manual": "Manuel", + "ManualGrab": "Saisie manuelle", + "ManualImportItemsLoadError": "Impossible de charger les éléments d'importation manuelle", + "Mapping": "Cartographie", + "MarkAsFailedConfirmation": "Êtes-vous sûr de vouloir marquer « {sourceTitle} » comme ayant échoué ?", + "MassSearchCancelWarning": "Cette opération ne peut pas être annulée une fois démarrée sans redémarrer {appName} ou désactiver tous vos indexeurs.", + "MaximumLimits": "Limites maximales", + "MaximumSingleEpisodeAge": "Âge maximum d'un seul épisode", + "MaximumSingleEpisodeAgeHelpText": "Lors d'une recherche de saison complète, seuls les packs de saisons seront autorisés lorsque le dernier épisode de la saison est plus ancien que ce paramètre. Série standard uniquement. Utilisez 0 pour désactiver.", + "MaximumSize": "Taille maximum", + "Mechanism": "Mécanisme", + "MediaInfo": "Informations médias", + "MediaInfoFootNote": "MediaInfo Full/AudioLanguages/SubtitleLanguages supporte un suffixe `:EN+DE` vous permettant de filtrer les langues incluses dans le nom de fichier. Utilisez `-DE` pour exclure des langues spécifiques. L'ajout de `+` (par exemple `:EN+`) affichera `[EN]`/`[EN+--]`/`[--]` en fonction des langues exclues. Par exemple `{MediaInfo Full:EN+DE}`.", + "MetadataProvidedBy": "Les métadonnées sont fournies par {provider}", + "MetadataSettings": "Paramètres des métadonnées", + "MetadataSettingsSummary": "Créez des fichiers de métadonnées lorsque les épisodes sont importés ou que les séries sont actualisées", + "MetadataSourceSettings": "Paramètres de source de métadonnées", + "MetadataSourceSettingsSummary": "Informations sur l'endroit où {appName} obtient des informations sur les séries et les épisodes", + "MidseasonFinale": "Finale de la mi-saison", + "MinimumCustomFormatScore": "Score minimum de format personnalisé", + "MinimumCustomFormatScoreHelpText": "Score de format personnalisé minimum autorisé à télécharger", + "Mixed": "Mixte", + "Mode": "Mode", + "MonitorAllEpisodes": "Tous les épisodes", + "MonitorFutureEpisodesDescription": "Surveiller les épisodes qui n'ont pas encore été diffusés", + "MonitorNoneDescription": "Aucun épisode ne sera surveillé", + "MonitorPilotEpisode": "Épisode pilote", + "MonitorSelected": "Surveiller les séries sélectionnées", + "MonitorSeries": "Surveiller les séries", + "MonitorSpecials": "Surveiller les épisodes spéciaux", + "MountHealthCheckMessage": "Le montage contenant un chemin de série est monté en lecture seule : ", + "MultiLanguages": "Multi langues", + "MultiSeason": "Multi saison", + "MustContain": "Doit contenir", + "MustNotContainHelpText": "La version sera rejetée si elle contient un ou plusieurs termes (insensible à la casse)", + "NamingSettings": "Paramètres de dénomination", + "Negate": "Nier", + "NegateHelpText": "Si cette case est cochée, le format personnalisé ne s'appliquera pas si cette condition {implementationName} correspond.", + "Negated": "Nier", + "Network": "Réseau", + "Never": "Jamais", + "New": "Nouveau", + "NextExecution": "Prochaine exécution", + "NoChange": "Pas de changement", + "NoDelay": "Sans délais", + "NoEpisodeHistory": "Pas d'historique des épisodes", + "NoEpisodesInThisSeason": "Aucun épisode dans cette saison", + "NoEventsFound": "Aucun événement trouvé", + "OnSeriesAdd": "À l'ajout de séries", + "OnSeriesDelete": "Lors de la suppression de la série", + "OnlyTorrent": "Uniquement Torrent", + "OpenBrowserOnStart": "Ouvrir le navigateur au démarrage", + "OpenSeries": "Série ouverte", + "Options": "Options", + "Organize": "Organiser", + "OrganizeLoadError": "Erreur lors du chargement des aperçus", + "OrganizeModalHeader": "Organiser et renommer", + "OrganizeModalHeaderSeason": "Organiser et renommer – {saison}", + "OrganizeSelectedSeriesModalAlert": "Astuce : Pour prévisualiser un changement de nom, sélectionnez \"Annuler\", puis sélectionnez n'importe quel titre de série et utilisez cette icône :", + "OrganizeSelectedSeriesModalConfirmation": "Voulez-vous vraiment organiser tous les fichiers des {count} séries sélectionnées ?", + "OrganizeSelectedSeriesModalHeader": "Organiser les séries sélectionnées", + "Original": "Original", + "OverrideAndAddToDownloadQueue": "Remplacer et ajouter à la file d'attente de téléchargement", + "OverrideGrabModalTitle": "Remplacer et récupérer - {title}", + "OverviewOptions": "Options de présentation", + "Parse": "Analyser", + "ParseModalUnableToParse": "Impossible d'analyser le titre fourni, veuillez réessayer.", + "PartialSeason": "Saison partielle", + "PreferUsenet": "Préférer Usenet", + "Preferred": "Préféré", + "ProxyBadRequestHealthCheckMessage": "Échec du test du proxy. Code d'état : {0}", + "ProxyBypassFilterHelpText": "Utilisez ',' comme séparateur et '*.' comme caractère générique pour les sous-domaines", + "ProxyPasswordHelpText": "Il vous suffit de saisir un nom d'utilisateur et un mot de passe si nécessaire. Sinon, laissez-les vides.", + "ProxyResolveIpHealthCheckMessage": "Échec de la résolution de l'adresse IP de l'hôte proxy configuré {0}", + "Rating": "Notation", + "ReadTheWikiForMoreInformation": "Lisez le wiki pour plus d'informations", + "Real": "Réel", + "RecyclingBin": "Poubelle de recyclage", + "RecyclingBinCleanup": "Nettoyage du bac de recyclage", + "RecyclingBinCleanupHelpTextWarning": "Les fichiers dans la corbeille plus anciens que le nombre de jours sélectionné seront nettoyés automatiquement", + "RefreshAndScan": "Actualiser et analyser", + "RefreshAndScanTooltip": "Actualiser les informations et analyser le disque", + "RegularExpression": "Expression régulière", + "RegularExpressionsTutorialLink": "Plus de détails sur les expressions régulières peuvent être trouvés [ici](https://www.regular-expressions.info/tutorial.html).", + "RelativePath": "Chemin relatif", + "Release": "Version", + "ReleaseGroup": "Groupe de versions", + "ReleaseGroups": "Groupes de versions", + "ReleaseHash": "Somme de contrôle de la version", + "ReleaseProfile": "Profil de version", + "ReleaseProfileIndexerHelpTextWarning": "L'utilisation d'un indexeur spécifique avec des profils de version peut conduire à la saisie de versions en double", + "ReleaseProfiles": "Profils de version", + "ReleaseProfilesLoadError": "Impossible de charger les profils de version", + "RemotePathMappingGenericPermissionsHealthCheckMessage": "Le client de téléchargement {0} place les téléchargements dans {1} mais {appName} ne peut pas voir ce répertoire. Vous devrez peut-être ajuster les autorisations du dossier.", + "RemotePathMappingHostHelpText": "Le même hôte que vous avez spécifié pour le client de téléchargement distant", + "ImportListRootFolderMissingRootHealthCheckMessage": "Dossier racine manquant pour la ou les listes d'importation : {0}", + "RemotePathMappingImportFailedHealthCheckMessage": "{appName} n'a pas réussi à importer un ou plusieurs épisodes. Vérifiez vos journaux pour plus de détails.", + "IndexerJackettAllHealthCheckMessage": "Indexeurs utilisant le point de terminaison Jackett « all » non pris en charge : {0}", + "IndexerRssNoIndexersEnabledHealthCheckMessage": "Aucun indexeur disponible avec la synchronisation RSS activée, {appName} ne récupérera pas automatiquement les nouvelles versions", + "ProxyFailedToTestHealthCheckMessage": "Échec du test du proxy : {0}", + "RemotePathMappingLocalPathHelpText": "Chemin que {appName} doit utiliser pour accéder localement au chemin distant", + "RemotePathMappingLocalWrongOSPathHealthCheckMessage": "Le client de téléchargement local {0} place les téléchargements dans {1}, mais ce n'est pas un chemin {2} valide. Vérifiez les paramètres de votre client de téléchargement.", + "RemotePathMappingRemoteDownloadClientHealthCheckMessage": "Le client de téléchargement à distance {0} a signalé des fichiers dans {1} mais ce répertoire ne semble pas exister. Il manque probablement un mappage de chemin distant.", + "RemotePathMappingRemotePathHelpText": "Chemin racine du répertoire auquel accède le client de téléchargement", + "RemotePathMappingWrongOSPathHealthCheckMessage": "Le client de téléchargement à distance {0} place les téléchargements dans {1} mais ce n'est pas un chemin {2} valide. Vérifiez vos mappages de chemins distants et téléchargez les paramètres client.", + "RemotePathMappingsInfo": "Les mappages de chemins distants sont très rarement requis. Si {app} et votre client de téléchargement sont sur le même système, il est préférable de faire correspondre vos chemins. Pour plus d'informations, consultez le [wiki]({wikiLink})", + "RemotePathMappingsLoadError": "Impossible de charger les mappages de chemin distant", + "RemoveCompleted": "Supprimer terminé", + "RemoveCompletedDownloadsHelpText": "Supprimer les téléchargements importés de l'historique du client de téléchargement", + "RemoveDownloadsAlert": "Les paramètres de suppression ont été déplacés vers les paramètres individuels du client de téléchargement dans le tableau ci-dessus.", + "RemoveFailed": "Échec de la suppression", + "RemoveFilter": "Supprimer le filtre", + "RemoveFromBlocklist": "Supprimer de la liste de blocage", + "RemoveRootFolder": "Supprimer le dossier racine", + "RootFolderMissingHealthCheckMessage": "Dossier racine manquant : {0}", + "RootFolderMultipleMissingHealthCheckMessage": "Plusieurs dossiers racine sont manquants : {0}", + "RssIsNotSupportedWithThisIndexer": "RSS n'est pas pris en charge avec cet indexeur", + "RssSyncInterval": "Intervalle de synchronisation RSS", + "RssSyncIntervalHelpText": "Intervalle en minutes. Réglez sur zéro pour désactiver (cela arrêtera toute capture de libération automatique)", + "RssSyncIntervalHelpTextWarning": "Cela s'appliquera à tous les indexeurs, veuillez suivre les règles énoncées par eux", + "SaveChanges": "Sauvegarder les modifications", + "SceneNumbering": "Numérotation des scènes", + "SearchFailedError": "La recherche a échoué, veuillez réessayer plus tard.", + "SearchForAllMissing": "Rechercher tous les épisodes manquants", + "SearchForAllMissingConfirmationCount": "Êtes-vous sûr de vouloir rechercher tous les {totalRecords} épisodes manquants ?", + "SearchForCutoffUnmet": "Rechercher tous les épisodes Cutoff Unmet", + "SearchForCutoffUnmetConfirmationCount": "Êtes-vous sûr de vouloir rechercher tous les épisodes {totalRecords} Cutoff Unmet ?", + "SearchForMonitoredEpisodes": "Rechercher des épisodes surveillés", + "SearchForMonitoredEpisodesSeason": "Rechercher des épisodes surveillés dans cette saison", + "SearchIsNotSupportedWithThisIndexer": "La recherche n'est pas prise en charge avec cet indexeur", + "SeasonFinale": "Saison finale", + "SeasonInformation": "Informations sur la saison", + "SeasonNumber": "Numéro de saison", + "SeasonPassTruncated": "Seules les 25 dernières saisons sont affichées, allez aux détails pour voir toutes les saisons", + "SelectAll": "Tout sélectionner", + "SelectEpisodes": "Sélectionnez les épisodes", + "SelectFolder": "Sélectionner le dossier", + "SelectLanguage": "Choisir la langue", + "SendAnonymousUsageData": "Envoyer des données d'utilisation anonymes", + "SeriesAndEpisodeInformationIsProvidedByTheTVDB": "Les informations sur les séries et les épisodes sont fournies par TheTVDB.com. [Veuillez envisager de les soutenir](https://www.thetvdb.com/subscribe).", + "SeriesCannotBeFound": "Désolé, cette série est introuvable.", + "SeriesDetailsCountEpisodeFiles": "{episodeFileCount} fichiers d'épisode", + "SeriesDetailsRuntime": "{runtime} Minutes", + "SeriesEditor": "Éditeur de la série", + "SeriesFolderFormat": "Format du dossier de série", + "SeriesFolderFormatHelpText": "Utilisé lors de l'ajout d'une nouvelle série ou du déplacement d'une série via l'éditeur de séries", + "SeriesIndexFooterContinuing": "Suite (Tous les épisodes téléchargés)", + "SeriesIndexFooterDownloading": "Téléchargement (Un ou plusieurs épisodes)", + "SeriesIndexFooterEnded": "Terminé (Tous les épisodes téléchargés)", + "SeriesIndexFooterMissingMonitored": "Épisodes manquants (série surveillée)", + "SeriesIndexFooterMissingUnmonitored": "Épisodes manquants (série non surveillée)", + "SeriesIsMonitored": "La série est surveillée", + "SeriesIsUnmonitored": "La série n'est pas surveillée", + "SeriesLoadError": "Impossible de charger la série", + "SeriesMatchType": "Type de correspondance de série", + "SeriesMonitoring": "Surveillance des séries", + "SeriesPremiere": "Première de la série", + "SeriesProgressBarText": "{episodeFileCount} / {episodeCount} (Total : {totalEpisodeCount}, Téléchargement : {downloadingCount})", + "SeriesTitle": "Titre de la série", + "SetReleaseGroupModalTitle": "{modalTitle} – Définir le groupe de versions", + "ShortDateFormat": "Format de date courte", + "ShowBannersHelpText": "Afficher des bannières au lieu de titres", + "ShowDateAdded": "Afficher la date d'ajout", + "ShowEpisodeInformation": "Afficher les informations sur l'épisode", + "ShowEpisodeInformationHelpText": "Afficher le titre et le numéro de l'épisode", + "ShowEpisodes": "Afficher les épisodes", + "ShowMonitored": "Afficher le chemin", + "ShowNetwork": "Afficher le réseau", + "ShowPath": "Afficher le chemin", + "ShowPreviousAiring": "Afficher la diffusion précédente", + "ShowQualityProfile": "Afficher le profil de qualité", + "ShowRelativeDatesHelpText": "Afficher les dates relatives (Aujourd'hui/Hier/etc) ou absolues", + "ShowSearch": "Afficher la recherche", + "ShowSeasonCount": "Afficher le nombre de saisons", + "ShowSizeOnDisk": "Afficher la taille sur le disque", + "ShowTitle": "Montrer le titre", + "ShowTitleHelpText": "Afficher le titre de la série sous l'affiche", + "ShowUnknownSeriesItems": "Afficher les éléments de série inconnus", + "ShowUnknownSeriesItemsHelpText": "Afficher les éléments sans série dans la file d'attente. Cela peut inclure des séries, des films ou tout autre élément supprimé dans la catégorie de {appName}", + "ShownClickToHide": "Affiché, cliquez pour masquer", + "SkipFreeSpaceCheckWhenImportingHelpText": "À utiliser lorsque {appName} ne parvient pas à détecter l'espace libre de votre dossier racine lors de l'importation de fichiers", + "SkipRedownloadHelpText": "Empêche {appName} d'essayer de télécharger une version alternative pour cet élément", + "Small": "Petit", + "Socks5": "Socks5 (Support TOR)", + "SomeResultsAreHiddenByTheAppliedFilter": "Certains résultats sont masqués par le filtre appliqué", + "Source": "Source", + "SourcePath": "Chemin source", + "SourceRelativePath": "Chemin relatif de la source", + "Special": "spécial", + "SpecialEpisode": "Épisode spécial", + "Specials": "Épisodes spéciaux", + "SpecialsFolderFormat": "Format du dossier des épisodes spéciaux", + "SslCertPassword": "Mot de passe du certificat SSL/TLS", + "SslCertPasswordHelpText": "Mot de passe pour le fichier pfx", + "SslCertPath": "Chemin de certificat SSL/TLS", + "SslCertPathHelpText": "Chemin d'accès au fichier pfx", + "SslPort": "Port SSL/TLS", + "StandardEpisodeFormat": "Format d'épisode standard", + "SupportedCustomConditions": "{appName} prend en charge les conditions personnalisées pour les propriétés de version ci-dessous.", + "SupportedDownloadClients": "{appName} prend en charge de nombreux clients de téléchargement torrent et Usenet populaires.", + "SupportedDownloadClientsMoreInfo": "Pour plus d'informations sur les clients de téléchargement individuels, cliquez sur les boutons Plus d'informations.", + "SupportedIndexers": "{appName} prend en charge tout indexeur utilisant la norme Newznab, ainsi que les autres indexeurs répertoriés ci-dessous.", + "System": "Système", + "TableOptionsButton": "Bouton Options du tableau", + "TablePageSize": "Taille de la page", + "TablePageSizeMinimum": "La taille de la page doit être d'au moins {minimumValue}", + "TagCannotBeDeletedWhileInUse": "La balise ne peut pas être supprimée pendant son utilisation", + "Tasks": "Tâches", + "Test": "Tester", + "TestAll": "Tout tester", + "TestAllIndexers": "Testez tous les indexeurs", + "TheLogLevelDefault": "Le niveau de journalisation est par défaut « Info » et peut être modifié dans [Paramètres généraux] (/settings/general)", + "TheTvdb": "TheTVDB", + "Theme": "Thème", + "Time": "Heure", + "TimeFormat": "Format de l'heure", + "TimeLeft": "Temps restant", + "ToggleMonitoredSeriesUnmonitored ": "Impossible de basculer entre l'état surveillé lorsque la série n'est pas surveillée", + "ToggleMonitoredToUnmonitored": "Surveillé, cliquez pour annuler la surveillance", + "TotalFileSize": "Taille totale des fichiers", + "TotalRecords": "Enregistrements totaux : {totalRecords}", + "Trace": "Tracer", + "Type": "Type", + "UiLanguageHelpText": "Langue que {appName} utilisera pour l'interface utilisateur", + "UiSettingsLoadError": "Impossible de charger les paramètres de l'interface utilisateur", + "UiSettingsSummary": "Options de calendrier, de date et de couleurs dégradées", + "Umask750Description": "Écriture du propriétaire, lecture pour le groupe - {octal}", + "Umask755Description": "Le propriétaire écrit, tous les autres lisent - {octal}", + "Umask770Description": "Propriétaire et groupe écrivent - {octal}", + "Umask775Description": "Propriétaire et groupe écrivent, autre lecture - {octal}", + "UnableToLoadAutoTagging": "Impossible de charger le marquage automatique", + "UnableToLoadBackups": "Impossible de charger les sauvegardes", + "UnableToUpdateSonarrDirectly": "Impossible de mettre à jour directement {appName},", + "UnmonitoredOnly": "Non surveillé uniquement", + "UpdateMechanismHelpText": "Utilisez le programme de mise à jour intégré de {appName} ou un script", + "UpdateSelected": "Mise à jour sélectionnée", + "UpdateSonarrDirectlyLoadError": "Impossible de mettre à jour directement {appName},", + "UpdateStartupNotWritableHealthCheckMessage": "Impossible d'installer la mise à jour car le dossier de démarrage « {0} » n'est pas accessible en écriture par l'utilisateur '{1}'.", + "UpdateStartupTranslocationHealthCheckMessage": "Impossible d'installer la mise à jour, car le dossier de démarrage '{0}' se trouve dans un dossier App Translocation.", + "UpdaterLogFiles": "Journaux du programme de mise à jour", + "UpgradeUntil": "Mise à niveau jusqu'à", + "UpgradeUntilCustomFormatScore": "Mise à niveau jusqu'au score de format personnalisé", + "UpgradeUntilCustomFormatScoreHelpText": "Une fois ce score de format personnalisé atteint, {appName} ne récupérera plus les sorties d'épisodes", + "UrlBase": "URL de base", + "UseHardlinksInsteadOfCopy": "Utiliser les liens durs au lieu de copier", + "UseSeasonFolder": "Utiliser le dossier de la saison", + "UseSeasonFolderHelpText": "Trier les épisodes dans les dossiers des saisons", + "Usenet": "Usenet", + "UsenetDisabled": "Usenet Désactivé", + "UtcAirDate": "Date de diffusion UTC", + "VersionNumber": "Version {version}", + "VideoCodec": "Video Codec", + "VisitTheWikiForMoreDetails": "Visitez le wiki pour plus de details : ", + "WaitingToProcess": "En attente de traitement", + "WeekColumnHeader": "En-tête de colonne de la semaine", + "WhyCantIFindMyShow": "Pourquoi je ne peux pas trouver l'épisode ?", + "Wiki": "Wiki", + "IRCLinkText": "#sonarr sur Libera", + "NoBackupsAreAvailable": "Aucune sauvegarde n'est disponible", + "RemoveFromDownloadClient": "Supprimer du client de téléchargement", + "Formats": "Formats", + "GeneralSettings": "Réglages généraux", + "Genres": "Genres", + "GrabId": "Saisir ID", + "GrabSelected": "Saisir la sélection", + "GrabbedHistoryTooltip": "Épisode récupéré de {indexer} et envoyé à {downloadClient}", + "HourShorthand": "h", + "ImportCountSeries": "Importer {selectedCount} Séries", + "ImportErrors": "Erreurs d'importation", + "ImportFailed": "Échec de l'importation : {sourceTitle}", + "ImportSeries": "Série d'importation", + "ImportedTo": "Importé vers", + "IndexerSearchNoAvailableIndexersHealthCheckMessage": "Tous les indexeurs compatibles avec la recherche sont temporairement indisponibles en raison d'erreurs récentes de l'indexeur", + "LastUsed": "Dernière utilisation", + "LiberaWebchat": "Libera Webchat", + "LibraryImportHeader": "Importez des séries que vous possédez déjà", + "LibraryImportTips": "Quelques conseils pour garantir le bon déroulement de l’importation :", + "LibraryImportTipsQualityInFilename": "Assurez-vous que vos fichiers incluent la qualité dans leurs noms de fichiers. par exemple. `épisode.s02e15.bluray.mkv`", + "ManageLists": "Gérer les listes", + "MarkAsFailed": "Marquer comme échec", + "MegabytesPerMinute": "Mégaoctets par minute", + "Message": "Message", + "Metadata": "Metadonnées", + "MinimumFreeSpaceHelpText": "Empêcher l'importation si elle laisse moins d'espace disque disponible", + "MinimumLimits": "Limites minimales", + "MinutesFortyFive": "45 Minutes : {fortyFive}", + "Monitor": "Surveillé", + "MonitorAllEpisodesDescription": "Surveillez tous les épisodes sauf les spéciaux", + "MonitorExistingEpisodes": "Épisodes existants", + "MonitorExistingEpisodesDescription": "Surveiller les épisodes contenant des fichiers ou qui n'ont pas encore été diffusés", + "MonitorFirstSeason": "Première saison", + "MonitorFirstSeasonDescription": "Surveillez tous les épisodes de la première saison. Toutes les autres saisons seront ignorées", + "MonitorFutureEpisodes": "Épisodes futurs", + "MonitorLatestSeason": "Dernière saison", + "MonitorLatestSeasonDescription": "Surveillez tous les épisodes de la dernière saison diffusés au cours des 90 derniers jours et toutes les saisons futures", + "MonitorMissingEpisodes": "Épisodes manquants", + "MonitorMissingEpisodesDescription": "Surveiller les épisodes qui n'ont pas de fichiers ou qui n'ont pas encore été diffusés", + "MonitorNone": "Aucun", + "MonitorSpecialsDescription": "Surveillez tous les épisodes spéciaux sans modifier le statut surveillé des autres épisodes", + "MonitoringOptions": "Options de surveillance", + "NextAiring": "Prochaine diffusion", + "OnlyUsenet": "Uniquement Usenet", + "OpenBrowserOnStartHelpText": " Ouvrez un navigateur Web et accédez à la page d'accueil de {appName} au démarrage de l'application.", + "OptionalName": "Nom facultatif", + "Paused": "En pause", + "Pending": "En attente", + "Permissions": "Permissions", + "PreviouslyInstalled": "Précédemment installé", + "Priority": "Priorité", + "PublishedDate": "Date de publication", + "QualitySettings": "Paramètres de qualité", + "QualitySettingsSummary": "Tailles et dénomination de qualité", + "QueueLoadError": "Échec du chargement de la file d'attente", + "Reason": "Raison", + "RecyclingBinCleanupHelpText": "Réglez sur 0 pour désactiver le nettoyage automatique", + "RecyclingBinHelpText": "Les fichiers des épisodes seront placés ici une fois supprimés au lieu d'être définitivement supprimés", + "ReleaseProfileTagHelpText": "Les profils de version s'appliqueront aux séries avec au moins une balise correspondante. Laisser vide pour appliquer à toutes les séries", + "RemotePathMappingLocalFolderMissingHealthCheckMessage": "Le client de téléchargement à distance {0} place les téléchargements dans {1} mais ce répertoire ne semble pas exister. Mappage de chemin distant probablement manquant ou incorrect.", + "RemoveCompletedDownloads": "Supprimer les téléchargements terminés", + "RemoveQueueItemConfirmation": "Êtes-vous sûr de vouloir supprimer « {sourceTitle} » de la file d'attente ?", + "RemoveSelectedBlocklistMessageText": "Êtes-vous sûr de vouloir supprimer les éléments sélectionnés de la liste de blocage ?", + "RescanAfterRefreshHelpText": "Analysez à nouveau le dossier de la série après avoir actualisé la série", + "RetryingDownloadOn": "Nouvelle tentative de téléchargement le {date} à {time}", + "RootFoldersLoadError": "Impossible de charger les dossiers racine", + "SeriesFolderImportedTooltip": "Épisode importé du dossier de la série", + "StandardTypeDescription": "Épisodes publiés avec le modèle SxxEyy", + "Rejections": "Rejets", + "RemoveFromQueue": "Supprimer de la file d'attente", + "RemoveQueueItem": "Supprimer – {sourceTitle}", + "Search": "Rechercher", + "Seeders": "Seeders", + "SelectEpisodesModalTitle": "{modalTitle} – Sélectionnez un ou plusieurs épisodes", + "SetReleaseGroup": "Définir le groupe de versions", + "ShowRelativeDates": "Afficher les dates relatives", + "SizeOnDisk": "Taille sur le disque", + "SkipRedownload": "Ignorer le nouveau téléchargement", + "Standard": "Standard", + "StandardTypeFormat": "Numéros de saison et d'épisode ({format})", + "StartProcessing": "Démarrer le traitement", + "TableColumnsHelpText": "Choisissez quelles colonnes sont visibles et dans quel ordre elles apparaissent", + "TablePageSizeMaximum": "La taille de la page ne doit pas dépasser {maximumValue}", + "TaskUserAgentTooltip": "Agent utilisateur fourni par l'application qui a appelé l'API", + "Tba": "À déterminer", + "TorrentsDisabled": "Torrents désactivés", + "UiSettings": "Paramètres de l'interface utilisateur", + "Umask777Description": "Tout le monde écrit - {octal}", + "UnmappedFilesOnly": "Fichiers non mappés uniquement", + "UnmonitorSpecialsDescription": "Annulez la surveillance de tous les épisodes spéciaux sans modifier le statut surveillé des autres épisodes", + "UpdateUiNotWritableHealthCheckMessage": "Impossible d'installer la mise à jour, car le dossier de l'interface utilisateur '{0}' n'est pas accessible en écriture par l'utilisateur '{1}'.", + "UpgradeUntilHelpText": "Une fois cette qualité atteinte, {appName} ne téléchargera plus d'épisodes", + "UpgradeUntilThisQualityIsMetOrExceeded": "Mise à niveau jusqu'à ce que cette qualité soit atteinte ou dépassée", + "UseProxy": "Utiliser le proxy", + "WaitingToImport": "En attente d'import", + "Year": "Année", + "Grabbed": "Saisie", + "HasMissingSeason": "A une saison manquante", + "Here": "ici", + "HiddenClickToShow": "Masqué, cliquez pour afficher", + "HttpHttps": "HTTP(S)", + "ImportListSettings": "Paramètres de liste d'importation", + "ImportListsLoadError": "Impossible de charger les listes d'importation", + "ImportListsSettingsSummary": "Importer depuis une autre instance {appName} ou des listes Trakt et gérer les exclusions de listes", + "ImportScriptPathHelpText": "Le chemin d'accès au script à utiliser pour l'importation", + "Indexers": "Indexeurs", + "InstanceNameHelpText": "Nom de l'instance dans l'onglet et pour le nom de l'application Syslog", + "Max": "Max", + "NoLeaveIt": "Non, laisse tomber", + "NotificationsLoadError": "Impossible de charger les notifications", + "OnEpisodeFileDeleteForUpgrade": "Lors de la suppression du fichier de l'épisode pour la mise à niveau", + "OnGrab": "À saisir", + "OnlyForBulkSeasonReleases": "Uniquement pour les versions de saison en masse", + "RegularExpressionsCanBeTested": "Les expressions régulières peuvent être testées [ici](http://regexstorm.net/tester).", + "ReleaseProfileIndexerHelpText": "Spécifiez à quel indexeur le profil s'applique", + "RemotePathMappings": "Mappages de chemins distants", + "RescanAfterRefreshHelpTextWarning": "{appName} ne détectera pas automatiquement les modifications apportées aux fichiers lorsqu'il n'est pas défini sur 'Toujours'", + "SingleEpisode": "Épisode unique", + "SingleEpisodeInvalidFormat": "Épisode unique : format invalide", + "TestAllLists": "Tester toutes les listes", + "Updates": "Mises à jour", + "IRC": "IRC", + "Runtime": "Durée", + "Twitter": "Twitter", + "MatchedToEpisodes": "Adapté aux épisodes", + "NoEpisodesFoundForSelectedSeason": "Aucun épisode n'a été trouvé pour la saison sélectionnée", + "OnHealthRestored": "Sur la santé restaurée", + "OnImport": "À l'importation", + "OnLatestVersion": "La dernière version de {appName} est déjà installée", + "PreferAndUpgrade": "Préférer et mettre à niveau", + "RejectionCount": "Nombre de rejets", + "Script": "Script", + "SeasonFolder": "Dossier de saison", + "Security": "Sécurité", + "SupportedListsMoreInfo": "Pour plus d'informations sur les listes individuelles, cliquez sur les boutons Plus d'informations.", + "SystemTimeHealthCheckMessage": "L’heure du système est décalée de plus d’un jour. Les tâches planifiées peuvent ne pas s'exécuter correctement tant que l'heure n'est pas corrigée", + "TvdbId": "TVDB ID", + "TvdbIdExcludeHelpText": "L'ID TVDB de la série à exclure", + "TypeOfList": "{typeOfList} Liste", + "Version": "Version", + "SelectSeason": "Sélectionnez la saison", + "SelectSeasonModalTitle": "{modalTitle} – Sélectionnez la saison", + "SeriesDetailsGoTo": "Accédez à {title}", + "SeriesDetailsNoEpisodeFiles": "Aucun fichier d'épisode", + "SeriesDetailsOneEpisodeFile": "1 fichier épisode", + "Unavailable": "Indisponible", + "Ungroup": "Dissocier", + "Folder": "Dossier", + "FullColorEvents": "Événements en couleur", + "GeneralSettingsSummary": "Port, SSL/TLS, nom d'utilisateur/mot de passe, proxy, analyses et mises à jour", + "HistoryModalHeaderSeason": "Historique {season}", + "HistorySeason": "Afficher l'historique de cette saison", + "Images": "Images", + "ImdbId": "IMDb ID", + "ImportUsingScriptHelpText": "Copier des fichiers pour les importer à l'aide d'un script (ex. pour le transcodage)", + "IndexerOptionsLoadError": "Impossible de charger les options de l'indexeur", + "IndexerPriority": "Priorité de l'indexeur", + "IndexersLoadError": "Impossible de charger les indexeurs", + "IndexersSettingsSummary": "Indexeurs et options d'indexeur", + "InteractiveImportNoSeason": "La saison doit être choisie pour chaque fichier sélectionné", + "InteractiveSearch": "Recherche interactive", + "KeyboardShortcutsConfirmModal": "Accepter le mode de confirmation", + "MatchedToSeries": "Adapté à la série", + "Medium": "Moyen", + "MetadataLoadError": "Impossible de charger les métadonnées", + "Min": "Min", + "MinimumAge": "Âge minimum", + "MinimumAgeHelpText": "Usenet uniquement : âge minimum en minutes des NZB avant leur saisie. Utilisez-le pour donner aux nouvelles versions le temps de se propager à votre fournisseur Usenet.", + "MinutesSixty": "60 Minutes : {sixty}", + "MonitoredOnly": "Surveillé uniquement", + "MoveSeriesFoldersDontMoveFiles": "Non, je déplacerai les fichiers moi-même", + "MoveSeriesFoldersMoveFiles": "Oui, déplacez les fichiers", + "MoveSeriesFoldersToNewPath": "Souhaitez-vous déplacer les fichiers de la série de « {originalPath} » vers « {destinationPath} » ?", + "MoveSeriesFoldersToRootFolder": "Souhaitez-vous déplacer les dossiers de la série vers « {DestinationRootFolder} » ?", + "MustContainHelpText": "Le communiqué doit contenir au moins un de ces termes (insensible à la casse)", + "MustNotContain": "Ne doit pas contenir", + "NamingSettingsLoadError": "Impossible de charger les paramètres de dénomination", + "NoEpisodeInformation": "Aucune information sur l'épisode n'est disponible.", + "NoResultsFound": "Aucun résultat trouvé", + "NotificationTriggers": "Déclencheurs de notifications", + "OnUpgrade": "Lors de la mise à niveau", + "Other": "Autre", + "OutputPath": "Chemin de sortie", + "OverrideGrabNoEpisode": "Au moins un épisode doit être sélectionné", + "Overview": "Aperçu", + "ParseModalErrorParsing": "Erreur d'analyse, veuillez réessayer.", + "ParseModalHelpText": "Entrez un titre de version dans l'entrée ci-dessus", + "Peers": "Peers", + "ProgressBarProgress": "Barre de progression à {progress} %", + "Progress": "Progression", + "Proper": "Approprié", + "Proxy": "Proxy", + "ProxyUsernameHelpText": "Il vous suffit de saisir un nom d'utilisateur et un mot de passe si nécessaire. Sinon, laissez-les vides.", + "RemotePath": "Chemin distant", + "RemoveFailedDownloadsHelpText": "Supprimer les téléchargements ayant échoué de l'historique du client de téléchargement", + "RenameEpisodes": "Renommer les épisodes", + "ResetDefinitions": "Réinitialiser les définitions", + "SceneInfo": "Informations sur la scène", + "SceneInformation": "Informations sur la scène", + "SceneNumberNotVerified": "Le numéro de scène n'a pas encore été vérifié", + "SeasonDetails": "Détails de la saison", + "SelectQuality": "Sélectionnez la qualité", + "Size": "Taille", + "SourceTitle": "Titre source", + "Style": "Style", + "SupportedImportListsMoreInfo": "Pour plus d'informations sur les listes d'importation individuelles, cliquez sur les boutons Plus d'informations.", + "SupportedIndexersMoreInfo": "Pour plus d'informations sur les indexeurs individuels, cliquez sur les boutons Plus d'informations.", + "TestAllClients": "Tester tous les clients", + "UnableToLoadRootFolders": "Impossible de charger les dossiers racine", + "Unlimited": "Illimité", + "UpcomingSeriesDescription": "La série a été annoncée mais pas encore de date de diffusion exacte", + "UpdateMonitoring": "Surveillance des mises à jour", + "UpdateScriptPathHelpText": "Chemin d'accès à un script personnalisé qui prend un package de mise à jour extrait et gère le reste du processus de mise à jour", + "UpdateUINotWritableHealthCheckMessage": "Impossible d'installer la mise à jour, car le dossier de l'interface utilisateur '{0}' n'est pas accessible en écriture par l'utilisateur '{1}'.", + "UrlBaseHelpText": "Pour la prise en charge du proxy inverse, la valeur par défaut est vide", + "WeekColumnHeaderHelpText": "Affiché au-dessus de chaque colonne lorsque la semaine est la vue active", + "WithFiles": "Avec les fichiers", + "Yes": "Oui", + "FilterDoesNotEndWith": "ne se termine pas par", + "FilterIsAfter": "est après", + "FilterIsBefore": "est avant", + "FinaleTooltip": "Finale de la série ou de la saison", + "Fixed": "Fixé", + "Forecast": "Prévision", + "Forums": "Forums", + "From": "De", + "GeneralSettingsLoadError": "Impossible de charger les paramètres généraux", + "HomePage": "Page d'accueil", + "IconForFinales": "Icône pour les épisodes de fin", + "IconForSpecials": "Icône pour les épisodes spéciaux", + "ImportScriptPath": "Chemin du script d'importation", + "IncludeCustomFormatWhenRenaming": "Inclure un format personnalisé lors du changement de nom", + "InteractiveImportNoSeries": "Les séries doivent être choisies pour chaque fichier sélectionné", + "Level": "Niveau", + "LibraryImport": "Importation de bibliothèque", + "ListExclusionsLoadError": "Impossible de charger les exclusions de liste", + "ListQualityProfileHelpText": "Les éléments de la liste des profils de qualité seront ajoutés avec", + "ListTagsHelpText": "Balises qui seront ajoutées lors de l'importation à partir de cette liste", + "LocalAirDate": "Date de diffusion locale", + "Location": "Emplacement", + "LogFiles": "Fichiers journaux", + "LogFilesLocation": "Les fichiers journaux se trouvent dans : {location}", + "SupportedLists": "{appName} prend en charge plusieurs listes pour importer des séries dans la base de données.", + "MatchedToSeason": "Adapté à la saison", + "MetadataSource": "Source des métadonnées", + "MoveFiles": "Déplacer des fichiers", + "MultiEpisode": "Multi-épisode", + "MultiEpisodeInvalidFormat": "Épisode multiple : format invalide", + "NoEpisodeOverview": "Aucun aperçu des épisodes", + "OneMinute": "1 Minute", + "OriginalLanguage": "Langue originale", + "Port": "Port", + "PreferTorrent": "Préféré Torrent", + "QualityProfileInUse": "Impossible de supprimer un profil de qualité associé à une série, une liste ou une collection", + "ReleaseTitle": "Titre de la version", + "RemovingTag": "Supprimer la balise", + "Result": "Résultat", + "SelectLanguages": "Sélectionnez les langues", + "SeriesTypesHelpText": "Le type de série est utilisé pour renommer, analyser et rechercher", + "SetPermissionsLinuxHelpText": "Chmod doit-il être exécuté lorsque les fichiers sont importés/renommés ?", + "Socks4": "Socks4", + "TableColumns": "Colonnes", + "TableOptions": "Options des tableaux", + "TagsLoadError": "Impossible de charger les balises", + "ThemeHelpText": "Modifiez le thème de l'interface utilisateur de l'application, le thème « Auto » utilisera le thème de votre système d'exploitation pour définir le mode clair ou sombre. Inspiré par Theme.Park", + "UnmonitorSpecials": "Ne plus surveiller les épisodes spéciaux", + "Queued": "En file d'attente", + "IconForCutoffUnmet": "Icône pour la date limite non respectée", + "IconForCutoffUnmetHelpText": "Afficher l'icône pour les fichiers lorsque la limite n'a pas été respectée", + "SeriesFinale": "Le final de la série", + "FilterDoesNotStartWith": "ne commence pas par", + "FilterEndsWith": "se termine par", + "FilterEpisodesPlaceholder": "Filtrer les épisodes par titre ou numéro", + "Grab": "Saisir", + "GrabRelease": "Saisir Release", + "GrabReleaseMessageText": "{appName} n'a pas pu déterminer à quelle série et à quel épisode cette version était destinée. Il est possible que {appName} ne parvienne pas à importer automatiquement cette version. Voulez-vous récupérer « {title} » ?", + "Group": "Groupe", + "HideEpisodes": "Masquer les épisodes", + "ImportExtraFilesHelpText": "Importez les fichiers supplémentaires correspondants (sous-titres, informations, etc.) après avoir importé un fichier d'épisode", + "ImportList": "Liste d'importation", + "ImportListExclusions": "Exclusions de la liste d'importation", + "ImportLists": "Importer des listes", + "ImportMechanismEnableCompletedDownloadHandlingIfPossibleMultiComputerHealthCheckMessage": "Activer la gestion des téléchargements terminés si possible (multi-ordinateur non pris en charge)", + "ImportMechanismHandlingDisabledHealthCheckMessage": "Activer la gestion des téléchargements terminés", + "ImportUsingScript": "Importer à l'aide d'un script", + "IncludeHealthWarnings": "Inclure des avertissements de santé", + "Indexer": "Indexeur", + "LibraryImportTipsUseRootFolder": "Pointez {appName} vers le dossier contenant toutes vos émissions de télévision, pas une en particulier. par exemple. \"`{goodFolderExample}`\" et non \"`{badFolderExample}`\". De plus, chaque série doit se trouver dans son propre dossier dans le dossier racine/bibliothèque.", + "Links": "Liens", + "ListOptionsLoadError": "Impossible de charger les options de la liste", + "ListRootFolderHelpText": "Les éléments de la liste du dossier racine seront ajoutés à", + "MinutesThirty": "30 Minutes : {thirty}", + "Missing": "Manquant", + "MissingEpisodes": "Épisodes manquants", + "MissingLoadError": "Erreur lors du chargement des éléments manquants", + "MonitoredHelpText": "Téléchargez les épisodes surveillés de cette série", + "Monitoring": "Surveillance", + "Month": "Mois", + "More": "Plus", + "MoreDetails": "Plus de détails", + "No": "Non", + "NoIssuesWithYourConfiguration": "Aucun problème avec votre configuration", + "NoLimitForAnyRuntime": "Aucune limite pour aucune durée d'exécution", + "NoLinks": "Aucun lien", + "NoLogFiles": "Aucun fichier journal", + "NoMatchFound": "Pas de résultat trouvé !", + "NoMinimumForAnyRuntime": "Aucun minimum pour aucune durée d'exécution", + "NoMonitoredEpisodes": "Aucun épisode surveillé dans cette série", + "NoMonitoredEpisodesSeason": "Aucun épisode surveillé dans cette saison", + "NoSeriesHaveBeenAdded": "Vous n'avez pas encore ajouté de séries, souhaitez-vous d'abord importer tout ou partie de vos séries ?", + "NoUpdatesAreAvailable": "Aucune mise à jour n'est disponible", + "NotSeasonPack": "Pas de pack saisonnier", + "NotificationTriggersHelpText": "Sélectionnez les événements qui doivent déclencher cette notification", + "NotificationsTagsHelpText": "N'envoyer des notifications que pour les séries avec au moins une balise correspondante", + "OnApplicationUpdate": "Sur la mise à jour de l'application", + "OnEpisodeFileDelete": "Lors de la suppression du fichier de l'épisode", + "OnHealthIssue": "Sur la question de la santé", + "OnManualInteractionRequired": "Sur l'interaction manuelle requise", + "OnRename": "Au renommage", + "PreferredSize": "Taille préférée", + "PreviewRename": "Aperçu Renommer", + "PreviewRenameSeason": "Aperçu Renommer pour cette saison", + "PreviousAiring": "Diffusion précédente", + "PreviousAiringDate": "Diffusion précédente : {date}", + "PriorityHelpText": "Donnez la priorité à plusieurs clients de téléchargement. Round-Robin est utilisé pour les clients ayant la même priorité.", + "ProcessingFolders": "Dossiers de traitement", + "Protocol": "Protocole", + "ProxyType": "Type de mandataire", + "ReleaseSceneIndicatorSourceMessage": "Les versions {message} existent avec une numérotation ambiguë, incapable d'identifier l'épisode de manière fiable.", + "ReleaseSceneIndicatorUnknownMessage": "La numérotation varie pour cet épisode et la version ne correspond à aucun mappage connu.", + "ReleaseSceneIndicatorUnknownSeries": "Épisode ou série inconnu.", + "RemoveFromDownloadClientHelpTextWarning": "La suppression supprimera le téléchargement et le(s) fichier(s) du client de téléchargement.", + "RemoveTagsAutomaticallyHelpText": "Supprimez automatiquement les balises si les conditions ne sont pas remplies", + "RemovedFromTaskQueue": "Supprimé de la file d'attente des tâches", + "RemovedSeriesSingleRemovedHealthCheckMessage": "La série {0} a été supprimée de TheTVDB", + "Reorder": "Réorganiser", + "Repack": "Remballer", + "RequiredHelpText": "Cette condition {implementationName} doit correspondre pour que le format personnalisé s'applique. Sinon, une seule correspondance {implementationName} suffit.", + "RescanSeriesFolderAfterRefresh": "Réanalyser le dossier de la série après l'actualisation", + "ResetAPIKey": "Réinitialiser la clé API", + "RestartRequiredWindowsService": "Selon l'utilisateur qui exécute le service {appName}, vous devrez peut-être redémarrer {appName} en tant qu'administrateur une fois avant que le service ne démarre automatiquement.", + "Restore": "Restaurer", + "RestoreBackup": "Restaurer la sauvegarde", + "RestrictionsLoadError": "Impossible de charger les restrictions", + "Retention": "Rétention", + "RetentionHelpText": "Usenet uniquement : définissez-le sur zéro pour définir une rétention illimitée", + "RootFolder": "Dossier racine", + "Scheduled": "Programmé", + "ScriptPath": "Chemin du script", + "SearchByTvdbId": "Vous pouvez également effectuer une recherche en utilisant l'ID TVDB d'une émission. par exemple. base de données tv:71663", + "SeriesTitleToExcludeHelpText": "Le nom de la série à exclure", + "SeriesType": "Type de série", + "SeriesTypes": "Types de séries", + "SetPermissions": "Définir les autorisations", + "SetPermissionsLinuxHelpTextWarning": "Si vous n'êtes pas sûr de l'utilité de ces paramètres, ne les modifiez pas.", + "StartImport": "Démarrer l'importation", + "Started": "Démarré", + "StartupDirectory": "Répertoire de démarrage", + "SupportedAutoTaggingProperties": "{appName} prend en charge les propriétés suivantes pour les règles de marquage automatique", + "ToggleUnmonitoredToMonitored": "Non surveillé, cliquez pour surveiller", + "Torrents": "Torrents", + "Total": "Total", + "Upcoming": "À venir", + "UpdateAutomaticallyHelpText": "Téléchargez et installez automatiquement les mises à jour. Vous pourrez toujours installer à partir du système : mises à jour", + "UpdateAvailableHealthCheckMessage": "Une nouvelle mise à jour est disponible", + "UpdateFiltered": "Mise à jour filtrée", + "IconForSpecialsHelpText": "Afficher l'icône pour les épisodes spéciaux (saison 0)", + "Ignored": "Ignoré", + "IgnoredAddresses": "Adresses ignorées", + "Import": "Importer", + "ImportCustomFormat": "Importer un format personnalisé", + "ImportExtraFiles": "Importer des fichiers supplémentaires", + "Importing": "Importation", + "IndexerSettings": "Paramètres de l'indexeur", + "IndexerTagHelpText": "Utilisez cet indexeur uniquement pour les séries avec au moins une balise correspondante. Laissez vide pour utiliser toutes les séries.", + "InfoUrl": "URL d'informations", + "InstanceName": "Nom de l'instance", + "InteractiveImportLoadError": "Impossible de charger les éléments d'importation manuelle", + "InteractiveImportNoEpisode": "Un ou plusieurs épisodes doivent être choisis pour chaque fichier sélectionné", + "MappedNetworkDrivesWindowsService": "Les lecteurs réseau mappés ne sont pas disponibles lors de l'exécution en tant que service Windows, consultez la [FAQ](https://wiki.servarr.com/sonarr/faq#why-cant-sonarr-see-my-files-on-a-remote -serveur) pour plus d'informations.", + "SelectReleaseGroup": "Sélectionnez un groupe de versions", + "Tomorrow": "Demain", + "OverrideGrabNoSeries": "La série doit être sélectionnée", + "PackageVersion": "Version du paquet", + "PackageVersionInfo": "{packageVersion} de {packageAuthor}", + "QuickSearch": "Recherche rapide", + "ReleaseRejected": "Libération rejetée", + "ReleaseSceneIndicatorAssumingScene": "En supposant la numérotation des scènes.", + "ReleaseSceneIndicatorAssumingTvdb": "En supposant la numérotation TVDB.", + "ReleaseSceneIndicatorMappedNotRequested": "L'épisode mappé n'a pas été demandé dans cette recherche.", + "SearchForQuery": "Rechercher {requête}", + "View": "Vues", + "HardlinkCopyFiles": "Lien physique/Copie de fichiers", + "Health": "Santé", + "QualityCutoffNotMet": "Le seuil de qualité n'a pas été atteint", + "RootFolderPath": "Chemin du dossier racine", + "ShowBanners": "Afficher les bannières", + "SearchMonitored": "Recherche surveillée", + "SeasonCount": "Nombre de saisons", + "SeasonNumberToken": "Saison {seasonNumber}", + "SeasonPack": "Pack Saison", + "SeasonPassEpisodesDownloaded": "{episodeFileCount}/{totalEpisodeCount} épisodes téléchargés", + "SeasonPremiere": "Première de la saison", + "SeriesEditRootFolderHelpText": "Le déplacement d'une série vers le même dossier racine peut être utilisé pour renommer les dossiers de séries afin qu'ils correspondent au titre ou au format de dénomination mis à jour", + "FilterGreaterThan": "plus grand que", + "Folders": "Dossiers", + "FullColorEventsHelpText": "Style modifié pour colorer l'intégralité de l'événement avec la couleur d'état, au lieu de simplement le bord gauche. Ne s'applique pas à l'ordre du jour", + "Global": "Global", + "HealthMessagesInfoBox": "Vous pouvez trouver plus d'informations sur la cause de ces messages de contrôle de santé en cliquant sur le lien wiki (icône de livre) à la fin de la ligne, ou en vérifiant vos [journaux]({link}). Si vous rencontrez des difficultés pour interpréter ces messages, vous pouvez contacter notre support, via les liens ci-dessous.", + "LongDateFormat": "Format de date longue", + "PendingDownloadClientUnavailable": "En attente – Le client de téléchargement n'est pas disponible", + "Rss": "RSS", + "Sort": "Trier", + "Uptime": "Disponibilité", + "EnableInteractiveSearch": "Activer la recherche interactive", + "CloneCondition": "État du clone", + "DeleteCustomFormat": "Supprimer le format personnalisé", + "DeleteCustomFormatMessageText": "Êtes-vous sûr de vouloir supprimer le format personnalisé « {0} » ?", + "Conditions": "Conditions", + "CountImportListsSelected": "{count} liste(s) d'importation sélectionnée(s)", + "DeleteSeriesFolderConfirmation": "Le dossier de la série `{path}` et tout son contenu seront supprimés.", + "DeleteSelectedImportLists": "Supprimer la ou les listes d'importation", + "DeletedReasonMissingFromDisk": "{appName} n'a pas pu trouver le fichier sur le disque. Le fichier a donc été dissocié de l'épisode dans la base de données", + "Docker": "Docker", + "DockerUpdater": "Mettez à jour le conteneur Docker pour recevoir la mise à jour", + "DownloadClientCheckUnableToCommunicateWithHealthCheckMessage": "Impossible de communiquer avec {0}.", + "EnableAutomaticSearch": "Activer la recherche automatique", + "EpisodeAirDate": "Date de diffusion de l'épisode", + "ErrorLoadingPage": "Une erreur s'est produite lors du chargement de cette page", + "ExternalUpdater": "{appName} est configuré pour utiliser un mécanisme de mise à jour externe", + "DailyTypeDescription": "Épisodes diffusés quotidiennement ou moins fréquemment qui utilisent année-mois-jour (2023-08-04)", + "Debug": "Déboguer", + "DelayProfileTagsHelpText": "S'applique aux séries avec au moins une balise correspondante", + "DelayingDownloadUntil": "Retarder le téléchargement jusqu'au {date} à {time}", + "DeletedReasonManual": "Le fichier a été supprimé via l'interface utilisateur", + "DeleteRemotePathMapping": "Supprimer le mappage de chemin distant", + "DestinationPath": "Chemin de destination", + "DestinationRelativePath": "Chemin relatif de destination", + "DownloadClientRootFolderHealthCheckMessage": "Le client de téléchargement {0} place les téléchargements dans le dossier racine {1}. Vous ne devez pas télécharger vers un dossier racine.", + "DownloadFailedTooltip": "Échec du téléchargement de l'épisode", + "DownloadIgnored": "Téléchargement ignoré", + "DownloadWarning": "Avertissement de téléchargement : {warningMessage}", + "DownloadIgnoredTooltip": "Téléchargement de l'épisode ignoré", + "Downloaded": "Téléchargé", + "EditCustomFormat": "Modifier le format personnalisé", + "Downloading": "Téléchargement", + "EditListExclusion": "Modifier l'exclusion de liste", + "EditMetadata": "Modifier les métadonnées {metadataType}", + "EnableAutomaticAdd": "Activer l'ajout automatique", + "EpisodeFileDeleted": "Fichier de l'épisode supprimé", + "EpisodeFileDeletedTooltip": "Fichier de l'épisode supprimé", + "EpisodeFileRenamed": "Fichier de l'épisode renommé", + "EpisodeFileRenamedTooltip": "Fichier de l'épisode renommé", + "EpisodeImported": "Épisode importé", + "EpisodeImportedTooltip": "Épisode téléchargé avec succès et récupéré à partir du client de téléchargement", + "Existing": "Existant", + "Enabled": "Activé", + "CompletedDownloadHandling": "Traitement du téléchargement terminé", + "Component": "Composant", + "Condition": "Condition", + "Connections": "Connexions", + "ConnectSettingsSummary": "Notifications, connexions aux serveurs/lecteurs multimédias et scripts personnalisés", + "CopyToClipboard": "Copier dans le presse-papier", + "CreateEmptySeriesFolders": "Créer des dossiers de séries vides", + "Custom": "Customisé", + "CopyUsingHardlinksHelpText": "Les liens physiques permettent à {appName} d'importer des torrents dans le dossier de la série sans prendre d'espace disque supplémentaire ni copier l'intégralité du contenu du fichier. Les liens physiques ne fonctionneront que si la source et la destination sont sur le même volume", + "CustomFormatsSettingsSummary": "Paramètres de formats personnalisés", + "CustomFormatsSettings": "Paramètres de formats personnalisés", + "DefaultDelayProfile": "Il s'agit du profil par défaut. Cela s'applique à toutes les séries qui n'ont pas de profil explicite.", + "DeleteDownloadClient": "Supprimer le client de téléchargement", + "DeleteEmptyFolders": "Supprimer les dossiers vides", + "DeleteImportList": "Supprimer la liste d'importation", + "DeleteImportListExclusion": "Supprimer l'exclusion de la liste d'importation", + "DeleteImportListExclusionMessageText": "Êtes-vous sûr de vouloir supprimer cette exclusion de la liste d'importation ?", + "DeleteQualityProfile": "Supprimer le profil de qualité", + "DeleteReleaseProfile": "Supprimer le profil de version", + "DeleteRemotePathMappingMessageText": "Êtes-vous sûr de vouloir supprimer ce mappage de chemin distant ?", + "DoNotPrefer": "Ne préfère pas", + "DoNotUpgradeAutomatically": "Ne pas mettre à niveau automatiquement", + "DownloadClient": "Client de téléchargement", + "DownloadClientTagHelpText": "Utilisez uniquement ce client de téléchargement pour les séries avec au moins une balise correspondante. Laissez vide pour utiliser toutes les séries.", + "EditDelayProfile": "Modifier le profil de retard", + "EditQualityProfile": "Modifier le profil de qualité", + "EditReleaseProfile": "Modifier le profil de version", + "EnableAutomaticAddHelpText": "Ajoutez des séries de cette liste à {appName} lorsque les synchronisations sont effectuées via l'interface utilisateur ou par {appName}", + "EnableCompletedDownloadHandlingHelpText": "Importer automatiquement les téléchargements terminés à partir du client de téléchargement", + "EnableColorImpairedModeHelpText": "Style modifié pour permettre aux utilisateurs ayant des difficultés de couleur de mieux distinguer les informations codées par couleur", + "ExtraFileExtensionsHelpText": "Liste de fichiers supplémentaires séparés par des virgules à importer (.nfo sera importé en tant que .nfo-orig)", + "CloneAutoTag": "Cloner la balise automatique", + "Failed": "Échoué", + "Daily": "Tous les jours", + "ContinuingOnly": "Continuer seulement", + "CurrentlyInstalled": "Actuellement installé", + "Donations": "Dons", + "EpisodeCount": "Nombre d'épisodes", + "DeleteSelectedDownloadClients": "Supprimer le(s) client(s) de téléchargement", + "EpisodeNumbers": "Numéro(s) d'épisode", + "DeleteRootFolderMessageText": "Êtes-vous sûr de vouloir supprimer le dossier racine « {path} » ?", + "DeleteSeriesFolderHelpText": "Supprimer le dossier de la série et son contenu", + "DeleteSeriesFolders": "Supprimer les dossiers de séries", + "DeleteSpecification": "Supprimer la spécification", + "Details": "Détails", + "EnableMetadataHelpText": "Activer la création de fichiers de métadonnées pour ce type de métadonnées", + "DownloadClientSortingHealthCheckMessage": "Le client de téléchargement {0} a le tri {1} activé pour la catégorie de {appName}. Vous devez désactiver le tri dans votre client de téléchargement pour éviter les problèmes d'importation.", + "EnableSsl": "Activer SSL/TLS", + "EnableProfile": "Activer le profil", + "EpisodeProgress": "Progression de l'épisode", + "FailedToLoadSeriesFromApi": "Échec du chargement de la série depuis l'API", + "FailedToLoadTranslationsFromApi": "Échec du chargement des traductions depuis l'API", + "Table": "Tableau", + "CustomFormatScore": "Partition au format personnalisé", + "DeleteSelectedImportListsMessageText": "Êtes-vous sûr de vouloir supprimer {count} liste(s) d'importation sélectionnée(s) ?", + "DeleteSelectedIndexers": "Supprimer un ou plusieurs indexeurs", + "Connect": "Connecter", + "Connection": "Connexion", + "CustomFormatsLoadError": "Impossible de charger les formats personnalisés", + "Cutoff": "Couper", + "DailyEpisodeFormat": "Format d'épisode quotidien", + "DailyTypeFormat": "Date ({format})", + "CreateEmptySeriesFoldersHelpText": "Créer des dossiers de séries manquants lors de l'analyse du disque", + "CreateGroup": "Créer un groupe", + "Database": "Base de données", + "Dates": "Dates", + "CustomFormatJson": "Format personnalisé JSON", + "DelayMinutes": "{delay} Minutes", + "DelayProfile": "Profil de retard", + "DeleteDelayProfile": "Supprimer le profil de retard", + "DeleteDelayProfileMessageText": "Êtes-vous sûr de vouloir supprimer ce profil de retard ?", + "DeleteEpisodeFile": "Supprimer le fichier de l'épisode", + "DeleteEpisodeFileMessage": "Supprimer le fichier de l'épisode ?", + "DeleteEpisodeFromDisk": "Supprimer l'épisode du disque", + "DeleteImportListMessageText": "Êtes-vous sûr de vouloir supprimer cette exclusion de la liste d'importation ?", + "DeleteSelectedEpisodeFiles": "Supprimer les fichiers d'épisode sélectionnés", + "DeleteSelectedEpisodeFilesHelpText": "Êtes-vous sûr de vouloir supprimer les fichiers d'épisode sélectionnés ?", + "DeleteSpecificationHelpText": "Êtes-vous sûr de vouloir supprimer la spécification « {name} » ?", + "DeleteTag": "Supprimer la balise", + "DownloadClientStatusSingleClientHealthCheckMessage": "Clients de téléchargement indisponibles en raison d'échecs : {0}", + "DownloadClientStatusAllClientHealthCheckMessage": "Tous les clients de téléchargement sont indisponibles en raison d'échecs", + "DownloadClientsLoadError": "Impossible de charger les clients de téléchargement", + "DownloadPropersAndRepacks": "Propriétés et reconditionnements", + "DownloadClientsSettingsSummary": "Clients de téléchargement, gestion des téléchargements et mappages de chemins distants", + "DownloadPropersAndRepacksHelpText": "S'il faut ou non mettre à niveau automatiquement vers Propers/Repacks", + "DownloadPropersAndRepacksHelpTextWarning": "Utilisez des formats personnalisés pour les mises à niveau automatiques vers Propers/Repacks", + "DownloadPropersAndRepacksHelpTextCustomFormat": "Utilisez « Ne pas préférer » pour trier par score de format personnalisé sur Propers/Repacks", + "EditAutoTag": "Modifier la balise automatique", + "EditSelectedImportLists": "Modifier les listes d'importation sélectionnées", + "Duration": "Durée", + "EditRemotePathMapping": "Modifier le mappage de chemin distant", + "EnableAutomaticSearchHelpTextWarning": "Sera utilisé lorsque la recherche interactive est utilisée", + "EpisodeSearchResultsLoadError": "Impossible de charger les résultats pour cette recherche d'épisode. Réessayez plus tard", + "EpisodeHistoryLoadError": "Impossible de charger l'historique de l'épisode", + "EpisodeIsDownloading": "L'épisode est en cours de téléchargement", + "EpisodeIsNotMonitored": "L'épisode n'est pas surveillé", + "EpisodeMissingAbsoluteNumber": "L'épisode n'a pas de numéro d'épisode absolu", + "EpisodeMissingFromDisk": "Épisode manquant sur le disque", + "EpisodeTitleRequired": "Titre de l'épisode requis", + "ErrorLoadingContent": "Une erreur s'est produite lors du chargement de ce contenu", + "Exception": "Exception", + "ExistingSeries": "Série existante", + "ExistingTag": "Balise existante", + "ExportCustomFormat": "Exporter un format personnalisé", + "CopyUsingHardlinksHelpTextWarning": "Parfois, les verrous de fichiers peuvent empêcher de renommer les fichiers en cours de création. Vous pouvez temporairement désactiver l'amorçage et utiliser la fonction de renommage de {appName} pour contourner le problème.", + "FailedToFetchUpdates": "Échec de la récupération des mises à jour", + "FilterDoesNotContain": "ne contient pas", + "EnableMediaInfoHelpText": "Extrayez les informations vidéo telles que la résolution, la durée d'exécution et les informations sur le codec à partir de fichiers. Cela nécessite que {appName} lise des parties du fichier, ce qui peut entraîner une activité élevée du disque ou du réseau pendant les analyses.", + "EnableRssHelpText": "Sera utilisé lorsque {appName} recherche périodiquement des versions via RSS Sync", + "MyComputer": "Mon ordinateur", + "Disabled": "Désactivé", + "Day": "Jour", + "ClickToChangeSeries": "Cliquez pour changer de série", + "CountSelectedFiles": "{selectedCount} fichiers sélectionnés", + "CustomFilters": "Filtres personnalisés", + "CustomFormat": "Format personnalisé", + "CustomFormatHelpText": "{appName} note chaque version en utilisant la somme des scores pour la correspondance des formats personnalisés. Si une nouvelle version devait améliorer le score, avec une qualité identique ou supérieure, alors {appName} la récupérerait.", + "CustomFormatUnknownCondition": "Condition de format personnalisé inconnue '{implémentation}'", + "CustomFormatUnknownConditionOption": "Option inconnue '{key}' pour la condition '{implementation}'", + "DownloadClientRemovesCompletedDownloadsHealthCheckMessage": "Le client de téléchargement {0} est configuré pour supprimer les téléchargements terminés. Cela peut entraîner la suppression des téléchargements de votre client avant que {1} puisse les importer.", + "DownloadFailed": "Échec du téléchargement", + "EditImportListExclusion": "Modifier l'exclusion de la liste d'importation", + "EditSeries": "Modifier les séries", + "EnableAutomaticSearchHelpText": "Sera utilisé lorsque des recherches automatiques sont effectuées via l'interface utilisateur ou par {appName}", + "EnableColorImpairedMode": "Activer le mode de couleurs altérées", + "EnableHelpText": "Activer la création de fichiers de métadonnées pour ce type de métadonnées", + "EnableInteractiveSearchHelpText": "Sera utilisé lorsque la recherche interactive est utilisée", + "EnableInteractiveSearchHelpTextWarning": "La recherche n'est pas prise en charge avec cet indexeur", + "EnableProfileHelpText": "Cochez pour activer le profil de version", + "EnableRss": "Activer RSS", + "Ended": "Terminé", + "EndedOnly": "Terminé seulement", + "EndedSeriesDescription": "Aucun épisode ou saison supplémentaire n'est attendu", + "Episode": "Épisode", + "EpisodeFilesLoadError": "Impossible de charger les fichiers d'épisode", + "EpisodeHasNotAired": "L'épisode n'a pas été diffusé", + "EpisodeInfo": "Informations sur l'épisode", + "EpisodeTitle": "Titre de l'épisode", + "EpisodeTitleRequiredHelpText": "Empêcher l'importation pendant 48 heures maximum si le titre de l'épisode est au format de nom et que le titre de l'épisode est à déterminer", + "Episodes": "Épisodes", + "EpisodesLoadError": "Impossible de charger les épisodes", + "Files": "Fichiers", + "Continuing": "Continuer", + "Donate": "Faire un don", + "EditConditionImplementation": "Modifier la condition – {implementationName}", + "EditConnectionImplementation": "Modifier la connexion - {implementationName}", + "EditImportListImplementation": "Modifier la liste d'importation - {implementationName}", + "EditIndexerImplementation": "Modifier l'indexeur - {implementationName}", + "CollapseMultipleEpisodesHelpText": "Réduire plusieurs épisodes diffusés le même jour", + "ClickToChangeSeason": "Cliquez pour changer de saison", + "CollapseAll": "Réduire tout", + "CollapseMultipleEpisodes": "Réduire plusieurs épisodes", + "CollectionsLoadError": "Impossible de charger les collections", + "ConnectSettings": "Paramètres de connexion", + "ContinuingSeriesDescription": "Plus d'épisodes/une autre saison sont attendus", + "EpisodeDownloaded": "Épisode téléchargé", + "CutoffUnmetLoadError": "Erreur lors du chargement des éléments non satisfaits", + "CountSelectedFile": "{selectedCount} fichier sélectionné", + "AutoRedownloadFailed": "Retélécharger les fichiers ayant échoué", + "AutoRedownloadFailedFromInteractiveSearch": "Retélécharger les fichiers ayant échoué depuis la recherche interactive", + "AutoRedownloadFailedFromInteractiveSearchHelpText": "Rechercher et tenter automatiquement de télécharger une version différente lorsque la version ayant échoué a été récupérée à partir de la recherche interactive", + "ConditionUsingRegularExpressions": "Cette condition correspond à l'aide d'expressions régulières. Notez que les caractères `\\^$.|?*+()[{` ont des significations particulières et doivent être échappés par un `\\`", + "CutoffUnmet": "Seuil non atteint", + "CutoffUnmetNoItems": "Aucun élément non satisfait", + "Date": "Date", + "DefaultNotFoundMessage": "Vous devez être perdu, rien à voir ici.", + "DeleteAutoTag": "Supprimer la balise automatique", + "DeleteAutoTagHelpText": "Voulez-vous vraiment supprimer la balise automatique « {name} » ?", + "DeleteEmptyFoldersHelpText": "Supprimez les dossiers de séries et de saisons vides lors de l'analyse du disque et lorsque les fichiers d'épisode sont supprimés", + "DeleteEpisodesFiles": "Supprimer {episodeFileCount} fichiers d'épisode", + "DeleteEpisodesFilesHelpText": "Supprimer les fichiers d'épisode et le dossier de série", + "DeleteQualityProfileMessageText": "Êtes-vous sûr de vouloir supprimer le profil de qualité « {name} » ?", + "DeleteReleaseProfileMessageText": "Êtes-vous sûr de vouloir supprimer ce profil de version « {name} » ?", + "DeleteSelectedSeries": "Supprimer la série sélectionnée", + "DeleteSeriesFolder": "Supprimer le dossier de série", + "DeleteSeriesFolderCountConfirmation": "Voulez-vous vraiment supprimer {count} séries sélectionnées ?", + "DeleteSeriesFolderCountWithFilesConfirmation": "Voulez-vous vraiment supprimer {count} séries sélectionnées et tous les contenus ?", + "DeleteSeriesFolderEpisodeCount": "{episodeFileCount} fichiers d'épisode totalisant {taille}", + "DeleteSeriesFoldersHelpText": "Supprimez les dossiers de séries et tout leur contenu", + "DeleteSeriesModalHeader": "Supprimer - {titre}", + "DeletedReasonUpgrade": "Le fichier a été supprimé pour importer une mise à niveau", + "DeletedSeriesDescription": "La série a été supprimée de TheTVDB", + "DetailedProgressBar": "Barre de progression détaillée", + "DetailedProgressBarHelpText": "Afficher le texte sur la barre de progression", + "Discord": "Discord", + "DotNetVersion": ".NET", + "Download": "Téléchargement", + "DownloadClientOptionsLoadError": "Impossible de charger les options du client de téléchargement", + "DownloadClientSettings": "Télécharger les paramètres client", + "EditRestriction": "Modifier la restriction", + "EditSelectedSeries": "Modifier la série sélectionnée", + "EditSeriesModalHeader": "Modifier - {titre}", + "Enable": "Activer", + "Error": "Erreur", + "ErrorLoadingContents": "Erreur lors du chargement du contenu", + "ErrorLoadingItem": "Une erreur s'est produite lors du chargement de cet élément", + "ErrorRestoringBackup": "Erreur lors de la restauration de la sauvegarde", + "EventType": "Type d'événement", + "Events": "Événements", + "ExpandAll": "Développer tout", + "FailedToLoadCustomFiltersFromApi": "Échec du chargement des filtres personnalisés à partir de l'API", + "FailedToLoadSonarr": "Échec du chargement de {appName}", + "FailedToLoadSystemStatusFromApi": "Échec du chargement de l'état du système à partir de l'API", + "FailedToLoadUiSettingsFromApi": "Échec du chargement des paramètres de l'interface utilisateur à partir de l'API", + "FailedToUpdateSettings": "Échec de la mise à jour des paramètres", + "FeatureRequests": "Requêtes de nouvelles fonctionnalités", + "File": "Fichier", + "NoImportListsFound": "Aucune liste d'importation trouvée" } diff --git a/src/NzbDrone.Core/Localization/Core/hu.json b/src/NzbDrone.Core/Localization/Core/hu.json index 678244844..fe523fcf3 100644 --- a/src/NzbDrone.Core/Localization/Core/hu.json +++ b/src/NzbDrone.Core/Localization/Core/hu.json @@ -104,5 +104,36 @@ "RemotePathMappingFilesBadDockerPathHealthCheckMessage": "Docker-t használ; a(z) {downloadClientName} letöltési kliens a fájlokat a(z) {path} mappában jelentette, de ez nem érvényes {osName} elérési útvonal. Ellenőrizze a távoli útvonal hozzárendeléseket, és a letöltési kliens beállításait.", "RemotePathMappingFilesGenericPermissionsHealthCheckMessage": "A(z) {downloadClientName} letöltési kliens a fájlokat a(z) {path} mappában jelentette, de a {appName} nem látja ezt a könyvtárat. Lehetséges, hogy be kell állítania a mappa jogosultságait.", "RemotePathMappingFilesWrongOSPathHealthCheckMessage": "A(z) {downloadClientName} távoli letöltési kliens a fájlokat a(z) {path} mappában jelentette, de ez nem érvényes {osName} elérési útvonal. Ellenőrizze a távoli útvonal hozzárendeléseket, és a letöltési kliens beállításait.", - "RemotePathMappingFolderPermissionsHealthCheckMessage": "A {appName} látja, de nem fér hozzá a letöltési könyvtárhoz {downloadPath}. Valószínűleg jogosultsági hiba." + "RemotePathMappingFolderPermissionsHealthCheckMessage": "A {appName} látja, de nem fér hozzá a letöltési könyvtárhoz {downloadPath}. Valószínűleg jogosultsági hiba.", + "All": "Összes", + "Updates": "Frissítések", + "UnknownEventTooltip": "Ismeretlen Esemény", + "AddList": "Lista hozzáadása", + "AddQualityProfile": "Minőségi Profil hozzáadása", + "Automatic": "Automatikus", + "AutomaticSearch": "Automatikus keresés", + "Backups": "Biztonsági mentések", + "Cancel": "Mégse", + "CalendarLoadError": "Naptár betöltése sikertelen", + "Age": "Kor", + "Add": "Hozzáadás", + "Calendar": "Naptár", + "Activity": "Aktivitás", + "Absolute": "Abszolút", + "ApplicationURL": "Alkalmazás URL", + "AutoAdd": "Automatikus hozzáadás", + "CalendarLegendDownloadingTooltip": "Epizód letöltés alatt", + "BuiltIn": "Beépített", + "CalendarLegendFinaleTooltip": "Sorozat vagy évad finálé", + "CancelProcessing": "Folyamat leállítása", + "CalendarOptions": "Naptár beállítások", + "About": "Névjegy", + "Actions": "Teendők", + "Anime": "Anime", + "AuthenticationRequiredUsernameHelpTextWarning": "Adjon meg új felhasználónevet", + "Unavailable": "Nem elérhető", + "Unknown": "Ismeretlen", + "UnmonitoredOnly": "Csak a nem felügyelt", + "UpdateAll": "Összes frissítése", + "AuthenticationRequiredPasswordHelpTextWarning": "Adjon meg új jelszót" } diff --git a/src/NzbDrone.Core/Localization/Core/pt.json b/src/NzbDrone.Core/Localization/Core/pt.json index 1df2c799a..8bf8b79a4 100644 --- a/src/NzbDrone.Core/Localization/Core/pt.json +++ b/src/NzbDrone.Core/Localization/Core/pt.json @@ -14,7 +14,7 @@ "About": "Sobre", "Actions": "Ações", "Absolute": "Absoluto", - "AddANewPath": "Adicionar novo caminho", + "AddANewPath": "Adicionar um novo caminho", "AddCondition": "Adicionar Condição", "AirDate": "Data de Transmissão", "AllTitles": "Todos os Títulos", diff --git a/src/NzbDrone.Core/Localization/Core/pt_BR.json b/src/NzbDrone.Core/Localization/Core/pt_BR.json index c5034e996..7840cf781 100644 --- a/src/NzbDrone.Core/Localization/Core/pt_BR.json +++ b/src/NzbDrone.Core/Localization/Core/pt_BR.json @@ -740,7 +740,6 @@ "RecyclingBinCleanupHelpText": "Defina como 0 para desativar a limpeza automática", "RecyclingBinCleanupHelpTextWarning": "Os arquivos na lixeira mais antigos do que o número de dias selecionado serão limpos automaticamente", "RecyclingBinHelpText": "Os arquivos do episódio irão para cá quando excluídos, em vez de serem excluídos permanentemente", - "RedownloadFailed": "Redownload falhou", "AbsoluteEpisodeNumber": "Número Absoluto do Episódio", "AddAutoTagError": "Não foi possível adicionar uma nova tag automática. Tente novamente.", "AnalyseVideoFilesHelpText": "Extraia informações de vídeo, como resolução, tempo de execução e informações de codec de arquivos. Isso requer que o {appName} leia partes do arquivo que podem causar alta atividade no disco ou na rede durante as varreduras.", @@ -1483,5 +1482,10 @@ "FormatShortTimeSpanSeconds": "{seconds} segundo(s)", "FormatTimeSpanDays": "{days}d {time}", "Yesterday": "Ontem", - "DownloadClientRemovesCompletedDownloadsHealthCheckMessage": "O cliente de download {downloadClientName} está configurado para remover downloads concluídos. Isso pode resultar na remoção dos downloads do seu cliente antes que {appName} possa importá-los." + "DownloadClientRemovesCompletedDownloadsHealthCheckMessage": "O cliente de download {downloadClientName} está configurado para remover downloads concluídos. Isso pode resultar na remoção dos downloads do seu cliente antes que {appName} possa importá-los.", + "AutoRedownloadFailedFromInteractiveSearchHelpText": "Procure e tente baixar automaticamente uma versão diferente quando a versão com falha for obtida pela pesquisa interativa", + "AutoRedownloadFailed": "Falha no Novo Download", + "AutoRedownloadFailedFromInteractiveSearch": "Falha no Novo Download pela Pesquisa Interativa", + "ImportListSearchForMissingEpisodes": "Pesquisar Episódios Ausentes", + "ImportListSearchForMissingEpisodesHelpText": "Depois que a série for adicionada ao {appName}, procure automaticamente episódios ausentes" } diff --git a/src/NzbDrone.Core/Localization/Core/zh_CN.json b/src/NzbDrone.Core/Localization/Core/zh_CN.json index a7e693399..974a9bff8 100644 --- a/src/NzbDrone.Core/Localization/Core/zh_CN.json +++ b/src/NzbDrone.Core/Localization/Core/zh_CN.json @@ -169,7 +169,7 @@ "DeleteSelectedIndexersMessageText": "您确定要删除{count}选定的索引器吗?", "Deleted": "已删除", "Disabled": "禁用", - "Discord": "Discord", + "Discord": "分歧", "DiskSpace": "硬盘空间", "Docker": "Docker", "DockerUpdater": "更新Docker容器以更新应用", @@ -383,10 +383,10 @@ "AddDownloadClient": "添加下载客户端", "AddDownloadClientError": "无法添加下载客户端,请稍后重试。", "AddConditionImplementation": "添加条件 - {implementationName}", - "AddConnectionImplementation": "添加连接 - {implementationName}", - "AddCustomFilter": "添加自定义过滤", - "AddDownloadClientImplementation": "添加下载客户端 - {implementationName}", - "AddImportListImplementation": "添加导入列表 - {implementationName}", + "AddConnectionImplementation": "添加连接- {implementationName}", + "AddCustomFilter": "添加自定义过滤器", + "AddDownloadClientImplementation": "添加下载客户端- {implementationName}", + "AddImportListImplementation": "添加导入列表- {implementationName}", "AddIndexerImplementation": "添加索引器 - {implementationName}", "AddNewRestriction": "添加新限制", "AddNewSeriesRootFolderHelpText": "将自动创建 '{folder}' 子文件夹", @@ -431,12 +431,12 @@ "CalendarOptions": "日历选项", "CancelProcessing": "取消进行中", "CertificateValidation": "验证证书", - "ChooseImportMode": "选择导入模式", + "ChooseImportMode": "选择导入方式", "ChownGroupHelpTextWarning": "这只在{appName}程序是文件所有者的情况下才有效。最好确保下载客户端使用与{appName}相同的用户组。", "ClickToChangeEpisode": "点击修改集", - "ClickToChangeQuality": "点击修改质量", + "ClickToChangeQuality": "点击更改质量", "ClickToChangeLanguage": "点击更换语言", - "ClickToChangeReleaseGroup": "点击修改发布组", + "ClickToChangeReleaseGroup": "单击更改发布组", "ClickToChangeSeason": "点击修改季", "ClickToChangeSeries": "点击修改剧集", "CloneAutoTag": "复制自动标签", @@ -457,7 +457,7 @@ "CreateEmptySeriesFoldersHelpText": "在扫描硬盘时创建缺失的剧集文件夹", "Custom": "自定义", "CreateGroup": "创建组", - "CustomFilters": "自定义过滤", + "CustomFilters": "自定义过滤器", "CustomFormatUnknownCondition": "未知自定义格式条件 '{implementation}'", "CustomFormatUnknownConditionOption": "未知的条件“{key}”的选项“{implementation}”", "CustomFormatsLoadError": "无法加载自定义格式", @@ -468,7 +468,7 @@ "Day": "天", "Default": "默认", "DefaultDelayProfile": "这是默认配置,它适用于所有没有明确配置的剧集。", - "DefaultNotFoundMessage": "你一定迷路了,这里什么都没有。", + "DefaultNotFoundMessage": "你一定是迷路了,这里没什么可看的。", "DelayMinutes": "{delay} 分钟", "DelayProfile": "延时配置", "DelayProfileTagsHelpText": "至少有一个匹配的标签应用于剧集", @@ -481,17 +481,17 @@ "DeleteEmptyFolders": "删除空目录", "DeleteEmptyFoldersHelpText": "如果集文件被删除,当磁盘扫描时删除剧集或季的空目录", "DeleteEpisodeFile": "删除集文件", - "DeleteEpisodeFileMessage": "你确定要删除 “{path}” 吗?", - "DeleteEpisodeFromDisk": "从磁盘中删除集", + "DeleteEpisodeFileMessage": "您确定要删除“{path}”吗?", + "DeleteEpisodeFromDisk": "从磁盘中删除剧集", "DeleteImportList": "删除导入的列表", "DeleteQualityProfile": "删除质量配置", - "DeleteQualityProfileMessageText": "你确定要删除质量配置 “{name}” 吗?", + "DeleteQualityProfileMessageText": "您确定要删除质量配置“{name}”吗?", "DeleteReleaseProfile": "删除发布组配置", - "DeleteReleaseProfileMessageText": "你确定要删除发布组配置 “{name}” 吗?", + "DeleteReleaseProfileMessageText": "您确定要删除此发布配置文件“{name}”吗?", "DeleteRemotePathMapping": "删除远程路径映射", "DeleteRemotePathMappingMessageText": "你确定要删除此远程路径映射吗?", "DeleteRootFolderMessageText": "你确定要删除根目录 “{path}” 吗?", - "DeleteSelectedEpisodeFilesHelpText": "你确定要删除选中的集文件吗?", + "DeleteSelectedEpisodeFilesHelpText": "您确定要删除选定的剧集文件吗?", "DeleteTag": "删除标签", "DownloadClientOptionsLoadError": "无法加载下载客户端选项", "DownloadClientSettings": "下载客户端设置", @@ -500,17 +500,17 @@ "DownloadWarning": "下载警告:{warningMessage}", "Downloaded": "已下载", "Downloading": "下载中", - "FailedToLoadCustomFiltersFromApi": "无法从 API 中加载自定义过滤器", - "FailedToLoadQualityProfilesFromApi": "无法从 API 中加载质量配置", - "FailedToLoadSeriesFromApi": "无法从 API 中加载剧集", + "FailedToLoadCustomFiltersFromApi": "未能从API加载自定义过滤器", + "FailedToLoadQualityProfilesFromApi": "未能从API加载质量配置文件", + "FailedToLoadSeriesFromApi": "未能从API加载系列", "FailedToLoadSonarr": "无法加载 {appName}", - "FailedToLoadSystemStatusFromApi": "无法从 API 中加载系统状态", - "FailedToLoadTagsFromApi": "无法从 API 中加载标签", - "FailedToLoadTranslationsFromApi": "无法从 API 中加载翻译", - "FailedToLoadUiSettingsFromApi": "无法从 API 中加载 UI 设置", + "FailedToLoadSystemStatusFromApi": "未能从API加载系统状态", + "FailedToLoadTagsFromApi": "未能从API加载标签", + "FailedToLoadTranslationsFromApi": "未能从API加载翻译", + "FailedToLoadUiSettingsFromApi": "未能从API加载UI设置", "File": "文件", "FileBrowser": "文件浏览器", - "FileBrowserPlaceholderText": "输入路径或者从下面选择", + "FileBrowserPlaceholderText": "开始输入或选择下面的路径", "FileManagement": "文件管理", "FileNameTokens": "文件名标记", "FileNames": "文件名", @@ -573,11 +573,11 @@ "Debug": "调试", "DelayProfilesLoadError": "无法加载延时配置", "DeleteImportListExclusion": "删除导入排除列表", - "DeleteIndexerMessageText": "您确定要删除索引器 “{name}” 吗?", + "DeleteIndexerMessageText": "您确定要删除索引器“{name}”吗?", "DeleteImportListExclusionMessageText": "你确定要删除这个导入排除列表吗?", "DeleteImportListMessageText": "您确定要删除列表 “{name}” 吗?", "DeleteNotification": "删除消息推送", - "DeleteNotificationMessageText": "您确定要删除消息推送 “{name}” 吗?", + "DeleteNotificationMessageText": "您确定要删除通知“{name}”吗?", "DeleteIndexer": "删除索引器", "DeleteTagMessageText": "您确定要删除标签 '{label}' 吗?", "DeletedReasonUpgrade": "升级时删除原文件", @@ -609,22 +609,22 @@ "CustomFormatHelpText": "{appName}会根据满足自定义格式与否给每个发布版本评分,如果一个新的发布版本有更高的分数,有相同或更高的影片质量,则{appName}会抓取该发布版本。", "DeleteAutoTagHelpText": "你确定要删除 “{name}” 自动标签吗?", "DownloadClientTagHelpText": "仅将此下载客户端用于至少具有一个匹配标签的剧集。留空可用于所有剧集。", - "Absolute": "准确的", + "Absolute": "绝对", "AddANewPath": "添加一个新的目录", "AbsoluteEpisodeNumber": "准确的集数", - "DeleteSelectedEpisodeFiles": "删除选中的集文件", + "DeleteSelectedEpisodeFiles": "删除选定的剧集文件", "Donate": "捐赠", "DownloadPropersAndRepacksHelpTextCustomFormat": "使用“不要首选”按 优化版(Propers) / 重制版(Repacks) 中的自定义格式分数排序", "DownloadPropersAndRepacksHelpTextWarning": "使用自定义格式自动升级到优化版/重制版", - "EditConditionImplementation": "编辑条件 - {implementationName}", - "EditConnectionImplementation": "编辑连接 - {implementationName}", + "EditConditionImplementation": "编辑条件- {implementationName}", + "EditConnectionImplementation": "编辑连接- {implementationName}", "Duplicate": "副本", "EditDelayProfile": "编辑延时配置", - "EditDownloadClientImplementation": "编辑下载客户端 - {implementationName}", + "EditDownloadClientImplementation": "编辑下载客户端- {implementationName}", "EditCustomFormat": "编辑自定义格式", "EditGroups": "编辑组", - "EditImportListImplementation": "编辑导入列表 - {implementationName}", - "EditIndexerImplementation": "编辑索引器 - {implementationName}", + "EditImportListImplementation": "编辑导入列表- {implementationName}", + "EditIndexerImplementation": "编辑索引器- {implementationName}", "EditListExclusion": "编辑排除列表", "EditImportListExclusion": "编辑导入排除列表", "EditMetadata": "编辑 {metadataType} 元数据", @@ -638,19 +638,19 @@ "EnableHelpText": "启用此元数据类型的元数据文件创建", "EpisodeFileRenamed": "集文件已被重命名", "EpisodeFileRenamedTooltip": "集文件已被重命名", - "EpisodeHistoryLoadError": "无法加载集的历史记录", + "EpisodeHistoryLoadError": "无法载入剧集历史记录", "EpisodeImported": "集已被导入", "EpisodeImportedTooltip": "集下载成功并从下载客户端获取", - "EpisodeHasNotAired": "集尚未播出", + "EpisodeHasNotAired": "这一集还没有播出", "EpisodeIsNotMonitored": "集未被监控", "EpisodeMissingAbsoluteNumber": "集没有准确的集数", "EpisodeIsDownloading": "集正在下载", "EpisodeSearchResultsLoadError": "无法加载此集的搜索结果。稍后再试", "EpisodeNaming": "集命名", "EpisodeTitleRequired": "需要集标题", - "EpisodesLoadError": "无法加载集", - "ErrorLoadingContents": "读取内容错误", - "ErrorLoadingContent": "加载此内容时出错", + "EpisodesLoadError": "无法加载剧集", + "ErrorLoadingContents": "加载内容出错", + "ErrorLoadingContent": "加载此内容时出现错误", "Existing": "已存在", "ExistingSeries": "已存在剧集", "External": "外部的", @@ -659,10 +659,10 @@ "ExtraFileExtensionsHelpText": "导入逗号分隔其他文件(.nfo将做为.nfo-orig被导入)", "FilterContains": "包含", "FilterDoesNotContain": "不包含", - "FilterEndsWith": "以…结尾", + "FilterEndsWith": "以…完结", "FilterEqual": "相等", - "FilterDoesNotStartWith": "不以…开头", - "FilterEpisodesPlaceholder": "通过标题或数字过滤集", + "FilterDoesNotStartWith": "不以...开始", + "FilterEpisodesPlaceholder": "按标题或编号过滤剧集", "FilterInLast": "在最后", "FilterInNext": "在下一个", "FilterNotInLast": "不在最后", @@ -732,9 +732,9 @@ "EpisodeFileDeleted": "集文件已删除", "EpisodeFileDeletedTooltip": "集文件已删除", "EpisodeMissingFromDisk": "磁盘中缺少集", - "ErrorLoadingItem": "加载此项目时出错", - "ErrorLoadingPage": "加载此页时出错", - "FilterDoesNotEndWith": "不以…结尾", + "ErrorLoadingItem": "加载此项目时出现错误", + "ErrorLoadingPage": "加载此页时出现错误", + "FilterDoesNotEndWith": "不以...结尾", "FilterGreaterThan": "大于", "FilterGreaterThanOrEqual": "大于等于", "FilterIs": "是", @@ -742,8 +742,8 @@ "FilterIsBefore": "在之前", "FilterIsNot": "不是", "FilterLessThan": "小于", - "FilterLessThanOrEqual": "小于等于", - "FilterStartsWith": "以…开头", + "FilterLessThanOrEqual": "小于或等于", + "FilterStartsWith": "以...开头", "FinaleTooltip": "剧集或季完结", "Global": "全局", "Grab": "抓取", @@ -790,7 +790,7 @@ "StandardTypeFormat": "季数和集数({format})", "DailyTypeDescription": "使用年-月-日的每天或更少频率发布的剧集(2023-08-04)", "Dash": "破折号", - "DelayingDownloadUntil": "将下载延迟到 {date} 的 {time}", + "DelayingDownloadUntil": "将下载推迟到 {date} 的 {time}", "SelectLanguage": "选择语言", "SelectLanguageModalTitle": "{modalTitle} - 选择语言", "DefaultNameCopiedProfile": "{name} - 复制", @@ -817,14 +817,14 @@ "RemotePathMappingsInfo": "很少需要远程路径映射,如果{app}和您的下载客户端在同一系统上,最好匹配您的路径。有关详细信息,请参阅[wiki]({wikiLink})", "IndexerPriorityHelpText": "索引器优先级从1(最高)到50(最低),默认25。当资源连接中断时寻找同等资源时使用,否则{appName}将依旧使用已启用的索引器进行RSS同步并搜索", "IndexerTagHelpText": "仅对至少有一个匹配标记的剧集使用此索引器。留空则适用于所有剧集。", - "InteractiveImportNoFilesFound": "在所选文件夹中没有找到视频文件", + "InteractiveImportNoFilesFound": "在所选文件夹中找不到视频文件", "RemoveSelectedBlocklistMessageText": "你确定你想从过滤清单中删除选中的项目吗?", "InteractiveImportNoQuality": "必须为每个选定的文件选择质量", "RestartSonarr": "重启{appName}", "InteractiveSearchModalHeaderSeason": "手动搜索 - {season}", "SearchMonitored": "搜索已监控", - "KeyboardShortcutsOpenModal": "打开该弹窗", - "SelectEpisodes": "选择集", + "KeyboardShortcutsOpenModal": "打开此模式", + "SelectEpisodes": "选择剧集", "SelectSeason": "选择季", "LibraryImportTipsQualityInFilename": "确保您的文件在其文件名中包含质量。例如:`episode.s02e15.bluray.mkv`", "LibraryImportTipsUseRootFolder": "将{appName}指向包含所有电视节目的文件夹,而不是特定的一个。例如“`{goodFolderExample}`”而不是“`{badFolderExamp}`”。此外,每个剧集都必须有单独的文件夹位于根/库文件夹下。", @@ -856,7 +856,7 @@ "MoveSeriesFoldersDontMoveFiles": "不,我自己移动文件", "SupportedIndexersMoreInfo": "若需要查看有关索引器的详细信息,请点击“更多信息”按钮。", "NoEpisodeInformation": "没有可用的集信息。", - "TableColumnsHelpText": "选择显示哪些列并排序", + "TableColumnsHelpText": "选择哪些列可见以及它们的显示顺序", "NoMinimumForAnyRuntime": "运行环境没有最小限制", "Theme": "主题", "UseSeasonFolderHelpText": "将集排序整理到季文件夹中", @@ -877,7 +877,7 @@ "MediaManagementSettings": "媒体管理设置", "MetadataSettingsSummary": "导入或刷新剧集时创建元数据文件", "MetadataSourceSettings": "元数据源设置", - "Mixed": "混合的", + "Mixed": "混合", "MonitorLatestSeason": "最新季", "MonitorFutureEpisodesDescription": "监控尚未播出的集", "MonitorSpecialsDescription": "监控所有特别节目,而不更改其他集的监控状态", @@ -898,10 +898,10 @@ "QualitiesHelpText": "列表中的质量排序越高优先级也越高。同组内的质量优先级相同。质量只有选中才标记为需要", "QualityDefinitionsLoadError": "无法加载质量定义", "RemotePathMappingRemotePathHelpText": "下载客户端访问的目录的根路径", - "RemoveFilter": "移除过滤条件", + "RemoveFilter": "移除过滤器", "RestartNow": "马上重启", - "SetReleaseGroupModalTitle": "{modalTitle} - 设定发布组", - "Shutdown": "关闭", + "SetReleaseGroupModalTitle": "{modalTitle} - 设置发布组", + "Shutdown": "关机", "Style": "类型", "TableColumns": "列", "SupportedListsMoreInfo": "若需要查看有关列表的详细信息,请点击“更多信息”按钮。", @@ -909,23 +909,23 @@ "Today": "今天", "Titles": "标题", "TorrentDelay": "Torrent延时", - "ToggleUnmonitoredToMonitored": "未监控,单击可监控", + "ToggleUnmonitoredToMonitored": "未监控,单击进行监控", "Upcoming": "即将播出", - "ProgressBarProgress": "进度条位于 {progress}%", + "ProgressBarProgress": "进度栏位于{Progress}%", "Usenet": "Usenet", "Week": "周", "Standard": "标准", "Sunday": "星期日", "AirsDateAtTimeOn": "{date} {time} 在 {networkLabel} 播出", "AirsTomorrowOn": "明天 {time} 在 {networkLabel} 播出", - "Airs": "放送", + "Airs": "播出", "CollapseAll": "收缩所有", "CollapseMultipleEpisodes": "收缩多集", "CollapseMultipleEpisodesHelpText": "收缩同一天播出的多集", "Connection": "连接", "ContinuingSeriesDescription": "预计会有更多集/下一季", - "CountSelectedFile": "所中 {selectedCount} 个文件", - "CountSelectedFiles": "所中 {selectedCount} 个文件", + "CountSelectedFile": "{selectedCount} 选中的文件", + "CountSelectedFiles": "{selectedCount}选中的文件", "CountSeriesSelected": "所中 {count} 个剧集", "DeleteEpisodesFiles": "删除 {episodeFileCount} 个集文件", "DeleteEpisodesFilesHelpText": "删除集文件和剧集文件夹", @@ -945,16 +945,16 @@ "IndexersSettingsSummary": "索引器和索引器选项", "InteractiveImport": "手动导入", "InstanceNameHelpText": "选项卡及日志应用名称", - "InteractiveImportLoadError": "无法加载手动导入项", - "InteractiveImportNoEpisode": "必须为每个选中的文件选择一个或多个集", - "InteractiveImportNoImportMode": "必须选择导入方式", - "InteractiveImportNoLanguage": "必须为每个选中的文件选择语言", + "InteractiveImportLoadError": "无法加载手动导入项目", + "InteractiveImportNoEpisode": "必须为每个选定的文件选择一个或多个剧集", + "InteractiveImportNoImportMode": "必须选择导入模式", + "InteractiveImportNoLanguage": "必须为每个选定的文件选择语言", "InteractiveImportNoSeries": "必须为每个选中的文件选择剧集", "InteractiveSearchResultsFailedErrorMessage": "搜索失败,因为 {message}。请尝试刷新剧集信息,并验证是否存在必要信息,再尝试进行搜索。", "InteractiveSearchSeason": "手动搜索本季所有集", - "KeyboardShortcutsConfirmModal": "接受确认弹窗", - "KeyboardShortcutsCloseModal": "关闭当前弹窗", - "KeyboardShortcutsFocusSearchBox": "聚焦搜索框", + "KeyboardShortcutsConfirmModal": "接受确认模式", + "KeyboardShortcutsCloseModal": "关闭当前模式", + "KeyboardShortcutsFocusSearchBox": "焦点搜索框", "Large": "大", "Level": "等级", "LibraryImportHeader": "导入您已有的剧集", @@ -964,13 +964,13 @@ "ListOptionsLoadError": "无法加载列表选项", "ListWillRefreshEveryInterval": "列表将每隔 {refreshInterval} 刷新一次", "Local": "本地", - "LocalStorageIsNotSupported": "不支持或禁用本地存储。插件或无痕浏览可能已将其禁用。", + "LocalStorageIsNotSupported": "不支持或禁用本地存储。插件或私人浏览可能已将其禁用。", "ManualGrab": "手动抓取", "ManualImport": "手动导入", "MappedNetworkDrivesWindowsService": "映射网络驱动器在作为Windows服务运行时不可用,请参阅[常见问题解答](https://wiki.servarr.com/sonarr/faq#why-cant-sonarr-see-my-files-on-a-remote-server)获取更多信息。", "Mapping": "映射", "MaximumLimits": "最大限制", - "MarkAsFailedConfirmation": "你确定要将 '{sourceTitle}' 标记为失败吗?", + "MarkAsFailedConfirmation": "是否确实要将“{sourceTitle}”标记为失败?", "Max": "最大的", "MaximumSingleEpisodeAgeHelpText": "在整季搜索期间,当该季的最后一集比此设置旧时,只允许获取整季包。仅限标准剧集。填写 0 可禁用此设置。", "MaximumSize": "最大文件体积", @@ -996,13 +996,13 @@ "Monitoring": "监控中", "MonitoringOptions": "监控选项", "MoreDetails": "更多详细信息", - "More": "更多的", + "More": "更多", "MoveAutomatically": "自动移动", "MoveSeriesFoldersToNewPath": "是否将剧集文件从 '{originalPath}' 移动到 '{originalPath}' ?", "MoveSeriesFoldersMoveFiles": "是,移动文件", "MultiEpisode": "多集", "MultiEpisodeStyle": "多集风格", - "MultiLanguages": "多语言", + "MultiLanguages": "多种语言", "MultiEpisodeInvalidFormat": "多集:无效格式", "MustNotContain": "必须不包含", "MustContain": "必须包含", @@ -1012,7 +1012,7 @@ "Never": "永不", "NoChanges": "无修改", "NoDelay": "无延迟", - "NoEpisodeHistory": "没有集的历史记录", + "NoEpisodeHistory": "无片段历史记录", "NoEpisodesFoundForSelectedSeason": "未找到所选季的集", "NoEpisodesInThisSeason": "本季没有集", "NoHistory": "无历史记录", @@ -1087,7 +1087,7 @@ "RefreshAndScanTooltip": "刷新信息并扫描磁盘", "ReleaseProfileIndexerHelpText": "指定配置文件应用于哪个索引器", "ReleaseProfileIndexerHelpTextWarning": "使用有发布配置的特定索引器可能会导致重复获取发布", - "ReleaseRejected": "版本被拒绝", + "ReleaseRejected": "发布被拒绝", "ReleaseSceneIndicatorAssumingTvdb": "推测TVDB编号。", "ReleaseSceneIndicatorMappedNotRequested": "此搜索中未请求映射的剧集。", "ReleaseSceneIndicatorSourceMessage": "{message} 发布时编号不明确,无法准确地识别集。", @@ -1125,9 +1125,9 @@ "SeasonPassTruncated": "只显示最新的25季,查看详情获取所有季信息", "SeasonPremieresOnly": "仅限季首播", "SelectDropdown": "选择...", - "SelectEpisodesModalTitle": "{modalTitle} - 选择集", - "SelectFolderModalTitle": "{modalTitle} -选择文件夹", - "SelectQuality": "选择质量", + "SelectEpisodesModalTitle": "{modalTitle} - 选择剧集", + "SelectFolderModalTitle": "{modalTitle} - 选择文件夹", + "SelectQuality": "选择品质", "SelectReleaseGroup": "选择发布组", "SeriesDetailsGoTo": "转到 {title}", "SeriesDetailsNoEpisodeFiles": "没有集文件", @@ -1168,12 +1168,12 @@ "TablePageSizeHelpText": "每页显示的项目数", "TagDetails": "标签详情 - {label}", "TagsSettingsSummary": "显示全部标签和标签使用情况,可删除未使用的标签", - "Tba": "待定", + "Tba": "待宣布", "Test": "测试", "ThemeHelpText": "改变应用界面主题,选择“自动”主题会通过操作系统主题来自适应白天黑夜模式。(受Theme.Park启发)", "TimeFormat": "时间格式", "ToggleMonitoredSeriesUnmonitored ": "当系列不受监控时,无法切换监控状态", - "ToggleMonitoredToUnmonitored": "已监控,单击可取消监控", + "ToggleMonitoredToUnmonitored": "已监视,单击可取消监视", "Total": "全部的", "TorrentsDisabled": "Torrents关闭", "TvdbIdExcludeHelpText": "要排除的剧集 TVDB ID", @@ -1184,13 +1184,13 @@ "UiLanguage": "UI界面语言", "Umask770Description": "{octal} - 所有者和组写入", "Umask775Description": "{octal} - 所有者和组写入,其他读取", - "Umask777Description": "{octal} - 全部可以写入", + "Umask777Description": "{octal} - 每个人都写", "UnableToLoadAutoTagging": "无法加载自动标记", "Unavailable": "不可用", "Ungroup": "未分组", "Unknown": "未知", "Unlimited": "无限制", - "UnmappedFilesOnly": "仅未映射的文件", + "UnmappedFilesOnly": "仅限未映射的文件", "UnmonitorDeletedEpisodes": "取消监控已删除的集", "UnmonitorSpecialsDescription": "取消监控所有特别节目而不改变其他集的监控状态", "UnmonitorDeletedEpisodesHelpText": "从磁盘删除的集将在 {appName} 中自动取消监控", @@ -1233,7 +1233,7 @@ "SizeLimit": "尺寸限制", "ShowRelativeDates": "显示相对日期", "ShowRelativeDatesHelpText": "显示相对日期(今天昨天等)或绝对日期", - "TablePageSizeMaximum": "页面大小不得超过{maximumValue}", + "TablePageSizeMaximum": "页面大小不得超过 {maximumValue}", "UiSettingsSummary": "日历、日期和色弱模式选项", "TagCannotBeDeletedWhileInUse": "无法删除使用中的标签", "UnableToLoadRootFolders": "无法加载根目录", @@ -1241,7 +1241,7 @@ "UnknownEventTooltip": "未知事件", "UpdateScriptPathHelpText": "自定义脚本的路径,该脚本处理获取的更新包并处理更新过程的其余部分", "UpdateSonarrDirectlyLoadError": "无法直接更新{appName},", - "View": "视图", + "View": "查看", "Negate": "相反的", "ListTagsHelpText": "从此列表导入时将添加标记", "ManageEpisodesSeason": "管理本季的集文件", @@ -1284,13 +1284,13 @@ "InteractiveSearchModalHeader": "手动搜索", "InvalidFormat": "格式不合法", "InvalidUILanguage": "您的UI设置的语言无效,请纠正它并保存设置", - "KeyboardShortcuts": "键盘快捷键", + "KeyboardShortcuts": "快捷键", "KeyboardShortcutsSaveSettings": "保存设置", "ListRootFolderHelpText": "根目录文件夹列表项需添加", - "Logout": "登出", + "Logout": "注销", "ManageEpisodes": "管理集", "MaximumSizeHelpText": "抓取影片最大多少MB,设置为0则不限制", - "MetadataProvidedBy": "元数据由 {provider} 提供", + "MetadataProvidedBy": "元数据由{provider}提供", "MidseasonFinale": "季中完结", "MinimumFreeSpaceHelpText": "如果导入的磁盘空间不足,则禁止导入", "MinimumLimits": "最小限制", @@ -1309,7 +1309,7 @@ "MustNotContainHelpText": "如版本包含一个或多个条件则丢弃(无视大小写)", "MyComputer": "我的电脑", "NamingSettings": "命名设置", - "NoEpisodeOverview": "没有集摘要", + "NoEpisodeOverview": "无片段概述", "NotificationStatusSingleClientHealthCheckMessage": "由于失败导致通知不可用:{notificationNames}", "NotificationTriggers": "通知触发器", "NotificationsLoadError": "无法加载通知连接", @@ -1340,7 +1340,7 @@ "SeriesType": "剧集类型", "SeriesTypes": "剧集类型", "SetPermissions": "设定权限", - "SetReleaseGroup": "设定发布组", + "SetReleaseGroup": "设置发布组", "SomeResultsAreHiddenByTheAppliedFilter": "部分结果已被过滤隐藏", "Sort": "排序", "Specials": "特别节目", @@ -1355,7 +1355,7 @@ "TheTvdb": "TheTVDB", "Tomorrow": "明天", "Ui": "UI", - "Umask755Description": "{octal} - 所有者写入,其他人读取", + "Umask755Description": "{octal} - 所有者写,其他人读", "Umask": "掩码", "True": "是", "UpgradeUntil": "升级直至", @@ -1375,7 +1375,7 @@ "BypassDelayIfAboveCustomFormatScoreHelpText": "当发布版本的评分高于配置的最小自定义格式评分时,跳过延时", "SearchForAllMissing": "搜索所有缺失的集", "SearchForMissing": "搜索缺少", - "SearchForQuery": "搜索 {query}", + "SearchForQuery": "搜索{query}", "ImportUsingScriptHelpText": "使用脚本复制文件以进行导入(例如用于转码)", "LanguagesLoadError": "无法加载语言", "MonitorLatestSeasonDescription": "监控过去90天内播出的最新一季的所有集以及未来的所有集", @@ -1460,14 +1460,13 @@ "RecentChanges": "最近修改", "RecyclingBinCleanupHelpText": "设置为0关闭自动清理", "RecyclingBinHelpText": "集文件将在删除时移动到此处,而不是永久删除", - "RedownloadFailed": "重新下载失败", "RegularExpressionsCanBeTested": "正则表达式可在[此处](http://regexstorm.net/tester)测试。", "SslPort": "SSL端口", - "TablePageSizeMinimum": "页面大小必须至少为{minimumValue}", + "TablePageSizeMinimum": "页面大小必须至少为 {minimumValue}", "TorrentDelayTime": "Torrent延时:{torrentDelay}", "Torrents": "种子", "TotalFileSize": "总文件体积", - "TotalRecords": "总记录数:{totalRecords}", + "TotalRecords": "记录总数: {totalRecords}", "Trace": "追踪", "CutoffUnmetLoadError": "加载未达截止条件项目错误", "CutoffUnmetNoItems": "没有未达截止条件的项目", @@ -1483,5 +1482,10 @@ "Files": "文件", "SeriesDetailsOneEpisodeFile": "1个集文件", "UrlBase": "基本URL", - "DownloadClientRemovesCompletedDownloadsHealthCheckMessage": "下载客户端{downloadClientName}设置为删除已完成的下载。这可能导致在{appName}可以导入下载之前从您的客户端删除下载。" + "DownloadClientRemovesCompletedDownloadsHealthCheckMessage": "下载客户端{downloadClientName}设置为删除已完成的下载。这可能导致在{appName}可以导入下载之前从您的客户端删除下载。", + "ImportListSearchForMissingEpisodesHelpText": "将系列添加到{appName}后,自动搜索缺失的剧集", + "AutoRedownloadFailed": "重新下载失败", + "AutoRedownloadFailedFromInteractiveSearch": "从交互式搜索中重新下载失败", + "AutoRedownloadFailedFromInteractiveSearchHelpText": "当从交互式搜索中获取失败的版本时,自动搜索并尝试下载其他版本", + "ImportListSearchForMissingEpisodes": "搜索缺失集" } From 5d90d68bf704bce7b0a0c23d3bb4721678939b32 Mon Sep 17 00:00:00 2001 From: Qstick <qstick@gmail.com> Date: Fri, 20 Oct 2023 22:14:04 -0500 Subject: [PATCH 062/136] Fix translation for Search for Missing option --- .../ImportLists/ImportLists/EditImportListModalContent.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/Settings/ImportLists/ImportLists/EditImportListModalContent.js b/frontend/src/Settings/ImportLists/ImportLists/EditImportListModalContent.js index 1b19bc416..b1bccdd84 100644 --- a/frontend/src/Settings/ImportLists/ImportLists/EditImportListModalContent.js +++ b/frontend/src/Settings/ImportLists/ImportLists/EditImportListModalContent.js @@ -120,7 +120,7 @@ function EditImportListModalContent(props) { <FormInputGroup type={inputTypes.CHECK} name="searchForMissingEpisodes" - helpText={translate('EnableAutomaticAddHelpText')} + helpText={translate('ImportListSearchForMissingEpisodesHelpText')} {...searchForMissingEpisodes} onChange={onInputChange} /> From 99b34c20657972a856c31ee1e8814ca2203d26a1 Mon Sep 17 00:00:00 2001 From: Stevie Robinson <stevie.robinson@gmail.com> Date: Fri, 27 Oct 2023 15:53:05 +0200 Subject: [PATCH 063/136] Fix Localization test after translation changes --- .../Localization/LocalizationServiceFixture.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NzbDrone.Core.Test/Localization/LocalizationServiceFixture.cs b/src/NzbDrone.Core.Test/Localization/LocalizationServiceFixture.cs index 3e2f5f430..d43657c7f 100644 --- a/src/NzbDrone.Core.Test/Localization/LocalizationServiceFixture.cs +++ b/src/NzbDrone.Core.Test/Localization/LocalizationServiceFixture.cs @@ -36,7 +36,7 @@ namespace NzbDrone.Core.Test.Localization var localizedString = Subject.GetLocalizedString("UiLanguage"); - localizedString.Should().Be("UI Langue"); + localizedString.Should().Be("Langue de l'interface utilisateur"); ExceptionVerification.ExpectedErrors(1); } From 59ea524e0ce85333779f430b867e93aae366289f Mon Sep 17 00:00:00 2001 From: Stevie Robinson <stevie.robinson@gmail.com> Date: Fri, 20 Oct 2023 20:48:42 +0200 Subject: [PATCH 064/136] Use Diacritical.Net library for TitleFirstCharacter token --- .../SeriesTitleFirstCharacterFixture.cs | 1 + src/NzbDrone.Core/Organizer/FileNameBuilder.cs | 6 ++++-- src/NzbDrone.Core/Sonarr.Core.csproj | 1 + 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/SeriesTitleFirstCharacterFixture.cs b/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/SeriesTitleFirstCharacterFixture.cs index b943cea10..5b3d09d6a 100644 --- a/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/SeriesTitleFirstCharacterFixture.cs +++ b/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/SeriesTitleFirstCharacterFixture.cs @@ -41,6 +41,7 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests [TestCase("¡Mucha Lucha!", "M\\¡Mucha Lucha!")] [TestCase(".hack", "H\\hack")] [TestCase("Ütopya", "U\\Ütopya")] + [TestCase("Æon Flux", "A\\Æon Flux")] public void should_get_expected_folder_name_back(string title, string expected) { diff --git a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs index 65522587f..112232521 100644 --- a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs +++ b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs @@ -5,6 +5,8 @@ using System.Globalization; using System.IO; using System.Linq; using System.Text.RegularExpressions; +using Diacritical; +using DryIoc.ImTools; using NLog; using NzbDrone.Common.Cache; using NzbDrone.Common.Disk; @@ -411,13 +413,13 @@ namespace NzbDrone.Core.Organizer { if (char.IsLetterOrDigit(title[0])) { - return title.Substring(0, 1).ToUpper().RemoveAccent(); + return title.Substring(0, 1).ToUpper().RemoveDiacritics()[0].ToString(); } // Try the second character if the first was non alphanumeric if (char.IsLetterOrDigit(title[1])) { - return title.Substring(1, 1).ToUpper().RemoveAccent(); + return title.Substring(1, 1).ToUpper().RemoveDiacritics()[0].ToString(); } // Default to "_" if no alphanumeric character can be found in the first 2 positions diff --git a/src/NzbDrone.Core/Sonarr.Core.csproj b/src/NzbDrone.Core/Sonarr.Core.csproj index 5c4cec3dd..b4e214628 100644 --- a/src/NzbDrone.Core/Sonarr.Core.csproj +++ b/src/NzbDrone.Core/Sonarr.Core.csproj @@ -4,6 +4,7 @@ </PropertyGroup> <ItemGroup> <PackageReference Include="Dapper" Version="2.0.123" /> + <PackageReference Include="Diacritical.Net" Version="1.0.4" /> <PackageReference Include="MailKit" Version="3.6.0" /> <PackageReference Include="Microsoft.AspNetCore.Cryptography.KeyDerivation" Version="6.0.21" /> <PackageReference Include="Servarr.FFMpegCore" Version="4.7.0-26" /> From b183743d9f0a0b15e4a9db0a9d3d2d1c238b0d9c Mon Sep 17 00:00:00 2001 From: Bogdan <mynameisbogdan@users.noreply.github.com> Date: Sun, 29 Oct 2023 00:45:47 +0300 Subject: [PATCH 065/136] Fixed: Avoid import loop for already imported episodes part of season packs Closes #6075 --- .../Specifications/AlreadyImportedSpecificationFixture.cs | 2 +- .../Specifications/AlreadyImportedSpecification.cs | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/NzbDrone.Core.Test/MediaFiles/EpisodeImport/Specifications/AlreadyImportedSpecificationFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/EpisodeImport/Specifications/AlreadyImportedSpecificationFixture.cs index 9e276d909..3898118ef 100644 --- a/src/NzbDrone.Core.Test/MediaFiles/EpisodeImport/Specifications/AlreadyImportedSpecificationFixture.cs +++ b/src/NzbDrone.Core.Test/MediaFiles/EpisodeImport/Specifications/AlreadyImportedSpecificationFixture.cs @@ -123,7 +123,7 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Specifications GivenHistory(history); - Subject.IsSatisfiedBy(_localEpisode, _downloadClientItem).Accepted.Should().BeTrue(); + Subject.IsSatisfiedBy(_localEpisode, _downloadClientItem).Accepted.Should().BeFalse(); } } } diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/AlreadyImportedSpecification.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/AlreadyImportedSpecification.cs index 891cef89a..e7a62650b 100644 --- a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/AlreadyImportedSpecification.cs +++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/AlreadyImportedSpecification.cs @@ -67,6 +67,11 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications return Decision.Reject("Episode file already imported at {0}", lastImported.Date.ToLocalTime()); } } + else + { + _logger.Debug("Episode file previously imported at {0}", lastImported.Date); + return Decision.Reject("Episode file already imported at {0}", lastImported.Date.ToLocalTime()); + } } return Decision.Accept(); From e9bb1d52a72b20a58d1a672ecfa3797eda6f081a Mon Sep 17 00:00:00 2001 From: Bogdan <mynameisbogdan@users.noreply.github.com> Date: Sat, 21 Oct 2023 10:06:22 +0300 Subject: [PATCH 066/136] Sort Custom Formats by name --- .../CustomFormats/CustomFormatCalculationService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NzbDrone.Core/CustomFormats/CustomFormatCalculationService.cs b/src/NzbDrone.Core/CustomFormats/CustomFormatCalculationService.cs index 18adc70cd..e344c355f 100644 --- a/src/NzbDrone.Core/CustomFormats/CustomFormatCalculationService.cs +++ b/src/NzbDrone.Core/CustomFormats/CustomFormatCalculationService.cs @@ -150,7 +150,7 @@ namespace NzbDrone.Core.CustomFormats } } - return matches; + return matches.OrderBy(x => x.Name).ToList(); } private static List<CustomFormat> ParseCustomFormat(EpisodeFile episodeFile, Series series, List<CustomFormat> allCustomFormats) From 6027041a4f7ca91d201def25341c4ceae0b7a1f6 Mon Sep 17 00:00:00 2001 From: Bogdan <mynameisbogdan@users.noreply.github.com> Date: Tue, 17 Oct 2023 18:43:49 +0300 Subject: [PATCH 067/136] Sort series by name in filter builder --- .../Filter/Builder/SeriesFilterBuilderRowValue.tsx | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/frontend/src/Components/Filter/Builder/SeriesFilterBuilderRowValue.tsx b/frontend/src/Components/Filter/Builder/SeriesFilterBuilderRowValue.tsx index 418874fc7..2eae79c80 100644 --- a/frontend/src/Components/Filter/Builder/SeriesFilterBuilderRowValue.tsx +++ b/frontend/src/Components/Filter/Builder/SeriesFilterBuilderRowValue.tsx @@ -2,18 +2,16 @@ import React from 'react'; import { useSelector } from 'react-redux'; import Series from 'Series/Series'; import createAllSeriesSelector from 'Store/Selectors/createAllSeriesSelector'; +import sortByName from 'Utilities/Array/sortByName'; import FilterBuilderRowValue from './FilterBuilderRowValue'; import FilterBuilderRowValueProps from './FilterBuilderRowValueProps'; function SeriesFilterBuilderRowValue(props: FilterBuilderRowValueProps) { const allSeries: Series[] = useSelector(createAllSeriesSelector()); - const tagList = allSeries.map((series) => { - return { - id: series.id, - name: series.title, - }; - }); + const tagList = allSeries + .map((series) => ({ id: series.id, name: series.title })) + .sort(sortByName); return <FilterBuilderRowValue {...props} tagList={tagList} />; } From 910c403d841ab46b0ec7b68c4bbb76445d340484 Mon Sep 17 00:00:00 2001 From: Bogdan <mynameisbogdan@users.noreply.github.com> Date: Tue, 17 Oct 2023 18:44:55 +0300 Subject: [PATCH 068/136] Add translations to history and queue custom filters --- .../HistoryEventTypeFilterBuilderRowValue.tsx | 25 ++++++++++++++----- frontend/src/Store/Actions/historyActions.js | 8 +++--- frontend/src/Store/Actions/queueActions.js | 8 +++--- 3 files changed, 27 insertions(+), 14 deletions(-) diff --git a/frontend/src/Components/Filter/Builder/HistoryEventTypeFilterBuilderRowValue.tsx b/frontend/src/Components/Filter/Builder/HistoryEventTypeFilterBuilderRowValue.tsx index c5f41ebc3..4ecddf646 100644 --- a/frontend/src/Components/Filter/Builder/HistoryEventTypeFilterBuilderRowValue.tsx +++ b/frontend/src/Components/Filter/Builder/HistoryEventTypeFilterBuilderRowValue.tsx @@ -1,31 +1,44 @@ import React from 'react'; +import translate from 'Utilities/String/translate'; import FilterBuilderRowValue from './FilterBuilderRowValue'; import FilterBuilderRowValueProps from './FilterBuilderRowValueProps'; const EVENT_TYPE_OPTIONS = [ { id: 1, - name: 'Grabbed', + get name() { + return translate('Grabbed'); + }, }, { id: 3, - name: 'Imported', + get name() { + return translate('Imported'); + }, }, { id: 4, - name: 'Failed', + get name() { + return translate('Failed'); + }, }, { id: 5, - name: 'Deleted', + get name() { + return translate('Deleted'); + }, }, { id: 6, - name: 'Renamed', + get name() { + return translate('Renamed'); + }, }, { id: 7, - name: 'Ignored', + get name() { + return translate('Ignored'); + }, }, ]; diff --git a/frontend/src/Store/Actions/historyActions.js b/frontend/src/Store/Actions/historyActions.js index 01d0fe66b..3e773eca8 100644 --- a/frontend/src/Store/Actions/historyActions.js +++ b/frontend/src/Store/Actions/historyActions.js @@ -190,25 +190,25 @@ export const defaultState = { filterBuilderProps: [ { name: 'eventType', - label: 'Event Type', + label: () => translate('EventType'), type: filterBuilderTypes.EQUAL, valueType: filterBuilderValueTypes.HISTORY_EVENT_TYPE }, { name: 'seriesIds', - label: 'Series', + label: () => translate('Series'), type: filterBuilderTypes.EQUAL, valueType: filterBuilderValueTypes.SERIES }, { name: 'quality', - label: 'Quality', + label: () => translate('Quality'), type: filterBuilderTypes.EQUAL, valueType: filterBuilderValueTypes.QUALITY }, { name: 'languages', - label: 'Languages', + label: () => translate('Languages'), type: filterBuilderTypes.CONTAINS, valueType: filterBuilderValueTypes.LANGUAGE } diff --git a/frontend/src/Store/Actions/queueActions.js b/frontend/src/Store/Actions/queueActions.js index 15ea35561..150c1ac00 100644 --- a/frontend/src/Store/Actions/queueActions.js +++ b/frontend/src/Store/Actions/queueActions.js @@ -185,25 +185,25 @@ export const defaultState = { filterBuilderProps: [ { name: 'seriesIds', - label: 'Series', + label: () => translate('Series'), type: filterBuilderTypes.EQUAL, valueType: filterBuilderValueTypes.SERIES }, { name: 'quality', - label: 'Quality', + label: () => translate('Quality'), type: filterBuilderTypes.EQUAL, valueType: filterBuilderValueTypes.QUALITY }, { name: 'languages', - label: 'Languages', + label: () => translate('Languages'), type: filterBuilderTypes.CONTAINS, valueType: filterBuilderValueTypes.LANGUAGE }, { name: 'protocol', - label: 'Protocol', + label: () => translate('Protocol'), type: filterBuilderTypes.EQUAL, valueType: filterBuilderValueTypes.PROTOCOL } From 3a99c2781b5db3e8495ab177bc0eecfbbcc8cda9 Mon Sep 17 00:00:00 2001 From: Bogdan <mynameisbogdan@users.noreply.github.com> Date: Tue, 17 Oct 2023 18:45:36 +0300 Subject: [PATCH 069/136] Remove duplicated condition in history controller --- src/Sonarr.Api.V3/History/HistoryController.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/Sonarr.Api.V3/History/HistoryController.cs b/src/Sonarr.Api.V3/History/HistoryController.cs index 3fb8b1cf4..991b2bb21 100644 --- a/src/Sonarr.Api.V3/History/HistoryController.cs +++ b/src/Sonarr.Api.V3/History/HistoryController.cs @@ -88,11 +88,6 @@ namespace Sonarr.Api.V3.History pagingSpec.FilterExpressions.Add(h => seriesIds.Contains(h.SeriesId)); } - if (seriesIds != null && seriesIds.Any()) - { - pagingSpec.FilterExpressions.Add(h => seriesIds.Contains(h.SeriesId)); - } - return pagingSpec.ApplyToPage(h => _historyService.Paged(pagingSpec, languages, quality), h => MapToResource(h, includeSeries, includeEpisode)); } From 43ed7730f08de7baddbdafcccd99370258593221 Mon Sep 17 00:00:00 2001 From: Bogdan <mynameisbogdan@users.noreply.github.com> Date: Tue, 17 Oct 2023 18:47:12 +0300 Subject: [PATCH 070/136] Add default value for Queue count to avoid failed prop type --- frontend/src/Activity/Queue/Queue.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/frontend/src/Activity/Queue/Queue.js b/frontend/src/Activity/Queue/Queue.js index 638fb1f52..633357b7e 100644 --- a/frontend/src/Activity/Queue/Queue.js +++ b/frontend/src/Activity/Queue/Queue.js @@ -357,4 +357,8 @@ Queue.propTypes = { onFilterSelect: PropTypes.func.isRequired }; +Queue.defaultProps = { + count: 0 +}; + export default Queue; From a171132f989437e3cd85a9689d42e862c6335d1b Mon Sep 17 00:00:00 2001 From: Bogdan <mynameisbogdan@users.noreply.github.com> Date: Thu, 19 Oct 2023 13:34:43 +0300 Subject: [PATCH 071/136] Fix typo in visible for Queue size --- frontend/src/Store/Actions/queueActions.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/Store/Actions/queueActions.js b/frontend/src/Store/Actions/queueActions.js index 150c1ac00..6af498685 100644 --- a/frontend/src/Store/Actions/queueActions.js +++ b/frontend/src/Store/Actions/queueActions.js @@ -144,7 +144,7 @@ export const defaultState = { name: 'size', label: () => translate('Size'), isSortable: true, - isVisibile: false + isVisible: false }, { name: 'outputPath', From dc099a77ca9e6993d386a2b70f1aff1fed34a32b Mon Sep 17 00:00:00 2001 From: Bogdan <mynameisbogdan@users.noreply.github.com> Date: Thu, 19 Oct 2023 14:39:23 +0300 Subject: [PATCH 072/136] Set proper default props for Queue details and status --- frontend/src/Activity/Queue/QueueDetails.js | 5 +++++ frontend/src/Activity/Queue/QueueStatusCell.js | 5 ++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/frontend/src/Activity/Queue/QueueDetails.js b/frontend/src/Activity/Queue/QueueDetails.js index b9b610176..abc97b75c 100644 --- a/frontend/src/Activity/Queue/QueueDetails.js +++ b/frontend/src/Activity/Queue/QueueDetails.js @@ -81,4 +81,9 @@ QueueDetails.propTypes = { progressBar: PropTypes.node.isRequired }; +QueueDetails.defaultProps = { + trackedDownloadStatus: 'ok', + trackedDownloadState: 'downloading' +}; + export default QueueDetails; diff --git a/frontend/src/Activity/Queue/QueueStatusCell.js b/frontend/src/Activity/Queue/QueueStatusCell.js index e5ebb2bf6..4e8b9658c 100644 --- a/frontend/src/Activity/Queue/QueueStatusCell.js +++ b/frontend/src/Activity/Queue/QueueStatusCell.js @@ -2,7 +2,6 @@ import PropTypes from 'prop-types'; import React from 'react'; import TableRowCell from 'Components/Table/Cells/TableRowCell'; import { tooltipPositions } from 'Helpers/Props'; -import translate from 'Utilities/String/translate'; import QueueStatus from './QueueStatus'; import styles from './QueueStatusCell.css'; @@ -41,8 +40,8 @@ QueueStatusCell.propTypes = { }; QueueStatusCell.defaultProps = { - trackedDownloadStatus: translate('Ok'), - trackedDownloadState: translate('Downloading') + trackedDownloadStatus: 'ok', + trackedDownloadState: 'downloading' }; export default QueueStatusCell; From f2cae4e2b2f87924973a00431be7216296648dac Mon Sep 17 00:00:00 2001 From: Bogdan <mynameisbogdan@users.noreply.github.com> Date: Sat, 21 Oct 2023 10:39:41 +0300 Subject: [PATCH 073/136] Improve UI notice for delayed queue items --- frontend/src/Activity/Queue/TimeleftCell.js | 27 +++++++++++++-------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/frontend/src/Activity/Queue/TimeleftCell.js b/frontend/src/Activity/Queue/TimeleftCell.js index ab1134b8c..b280b5a06 100644 --- a/frontend/src/Activity/Queue/TimeleftCell.js +++ b/frontend/src/Activity/Queue/TimeleftCell.js @@ -1,6 +1,9 @@ import PropTypes from 'prop-types'; import React from 'react'; +import Icon from 'Components/Icon'; import TableRowCell from 'Components/Table/Cells/TableRowCell'; +import Tooltip from 'Components/Tooltip/Tooltip'; +import { icons, kinds, tooltipPositions } from 'Helpers/Props'; import formatTime from 'Utilities/Date/formatTime'; import formatTimeSpan from 'Utilities/Date/formatTimeSpan'; import getRelativeDate from 'Utilities/Date/getRelativeDate'; @@ -25,11 +28,13 @@ function TimeleftCell(props) { const time = formatTime(estimatedCompletionTime, timeFormat, { includeMinuteZero: true }); return ( - <TableRowCell - className={styles.timeleft} - title={translate('DelayingDownloadUntil', { date, time })} - > - - + <TableRowCell className={styles.timeleft}> + <Tooltip + anchor={<Icon name={icons.INFO} />} + tooltip={translate('DelayingDownloadUntil', { date, time })} + kind={kinds.INVERSE} + position={tooltipPositions.TOP} + /> </TableRowCell> ); } @@ -39,11 +44,13 @@ function TimeleftCell(props) { const time = formatTime(estimatedCompletionTime, timeFormat, { includeMinuteZero: true }); return ( - <TableRowCell - className={styles.timeleft} - title={translate('RetryingDownloadOn', { date, time })} - > - - + <TableRowCell className={styles.timeleft}> + <Tooltip + anchor={<Icon name={icons.INFO} />} + tooltip={translate('RetryingDownloadOn', { date, time })} + kind={kinds.INVERSE} + position={tooltipPositions.TOP} + /> </TableRowCell> ); } From b06d5fb07bc7ab73ff3705410d8993ef7e040852 Mon Sep 17 00:00:00 2001 From: Bogdan <mynameisbogdan@users.noreply.github.com> Date: Fri, 27 Oct 2023 20:23:34 +0300 Subject: [PATCH 074/136] Use the correct property for quality profile on overview --- frontend/src/Series/Index/Overview/SeriesIndexOverviewInfo.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/Series/Index/Overview/SeriesIndexOverviewInfo.tsx b/frontend/src/Series/Index/Overview/SeriesIndexOverviewInfo.tsx index 486b8f038..4c3c85555 100644 --- a/frontend/src/Series/Index/Overview/SeriesIndexOverviewInfo.tsx +++ b/frontend/src/Series/Index/Overview/SeriesIndexOverviewInfo.tsx @@ -63,7 +63,7 @@ const rows = [ { name: 'qualityProfileId', showProp: 'showQualityProfile', - valueProp: 'qualityProfileId', + valueProp: 'qualityProfile', }, { name: 'previousAiring', From 653aede0b7376420d02f45677c7f1591dbb4fd8e Mon Sep 17 00:00:00 2001 From: Bogdan <mynameisbogdan@users.noreply.github.com> Date: Wed, 25 Oct 2023 13:31:58 +0300 Subject: [PATCH 075/136] Rename instances of Profile to QualityProfile --- .../Profiles/QualityProfileServiceFixture.cs | 16 +++++++-------- .../Qualities/QualityProfileRepository.cs | 4 ++-- .../Qualities/QualityProfileService.cs | 20 +++++++++---------- ...or.cs => QualityProfileExistsValidator.cs} | 6 +++--- .../ApiTests/SeriesEditorFixture.cs | 2 +- .../WantedTests/CutoffUnmetFixture.cs | 8 ++++---- .../IntegrationTestBase.cs | 10 +++++----- .../ImportLists/ImportListController.cs | 4 ++-- src/Sonarr.Api.V3/Series/SeriesController.cs | 4 ++-- 9 files changed, 37 insertions(+), 37 deletions(-) rename src/NzbDrone.Core/Validation/{ProfileExistsValidator.cs => QualityProfileExistsValidator.cs} (76%) diff --git a/src/NzbDrone.Core.Test/Profiles/QualityProfileServiceFixture.cs b/src/NzbDrone.Core.Test/Profiles/QualityProfileServiceFixture.cs index 4c776af57..fae7da081 100644 --- a/src/NzbDrone.Core.Test/Profiles/QualityProfileServiceFixture.cs +++ b/src/NzbDrone.Core.Test/Profiles/QualityProfileServiceFixture.cs @@ -32,7 +32,7 @@ namespace NzbDrone.Core.Test.Profiles Subject.Handle(new ApplicationStartedEvent()); - Mocker.GetMock<IProfileRepository>() + Mocker.GetMock<IQualityProfileRepository>() .Verify(v => v.Insert(It.IsAny<QualityProfile>()), Times.Exactly(6)); } @@ -42,13 +42,13 @@ namespace NzbDrone.Core.Test.Profiles // We don't want to keep adding them back if a user deleted them on purpose. public void Init_should_skip_if_any_profiles_already_exist() { - Mocker.GetMock<IProfileRepository>() + Mocker.GetMock<IQualityProfileRepository>() .Setup(s => s.All()) .Returns(Builder<QualityProfile>.CreateListOfSize(2).Build().ToList()); Subject.Handle(new ApplicationStartedEvent()); - Mocker.GetMock<IProfileRepository>() + Mocker.GetMock<IQualityProfileRepository>() .Verify(v => v.Insert(It.IsAny<QualityProfile>()), Times.Never()); } @@ -65,11 +65,11 @@ namespace NzbDrone.Core.Test.Profiles .Build().ToList(); Mocker.GetMock<ISeriesService>().Setup(c => c.GetAllSeries()).Returns(seriesList); - Mocker.GetMock<IProfileRepository>().Setup(c => c.Get(profile.Id)).Returns(profile); + Mocker.GetMock<IQualityProfileRepository>().Setup(c => c.Get(profile.Id)).Returns(profile); Assert.Throws<QualityProfileInUseException>(() => Subject.Delete(profile.Id)); - Mocker.GetMock<IProfileRepository>().Verify(c => c.Delete(It.IsAny<int>()), Times.Never()); + Mocker.GetMock<IQualityProfileRepository>().Verify(c => c.Delete(It.IsAny<int>()), Times.Never()); } [Test] @@ -84,7 +84,7 @@ namespace NzbDrone.Core.Test.Profiles Subject.Delete(1); - Mocker.GetMock<IProfileRepository>().Verify(c => c.Delete(1), Times.Once()); + Mocker.GetMock<IQualityProfileRepository>().Verify(c => c.Delete(1), Times.Once()); } [Test] @@ -103,7 +103,7 @@ namespace NzbDrone.Core.Test.Profiles .Random(1) .Build().ToList(); - Mocker.GetMock<IProfileRepository>().Setup(c => c.Get(profile.Id)).Returns(profile); + Mocker.GetMock<IQualityProfileRepository>().Setup(c => c.Get(profile.Id)).Returns(profile); Mocker.GetMock<ISeriesService>().Setup(c => c.GetAllSeries()).Returns(seriesList); Mocker.GetMock<IImportListFactory>() @@ -112,7 +112,7 @@ namespace NzbDrone.Core.Test.Profiles Assert.Throws<QualityProfileInUseException>(() => Subject.Delete(1)); - Mocker.GetMock<IProfileRepository>().Verify(c => c.Delete(It.IsAny<int>()), Times.Never()); + Mocker.GetMock<IQualityProfileRepository>().Verify(c => c.Delete(It.IsAny<int>()), Times.Never()); } } } diff --git a/src/NzbDrone.Core/Profiles/Qualities/QualityProfileRepository.cs b/src/NzbDrone.Core/Profiles/Qualities/QualityProfileRepository.cs index 65b82eb75..c824d30e5 100644 --- a/src/NzbDrone.Core/Profiles/Qualities/QualityProfileRepository.cs +++ b/src/NzbDrone.Core/Profiles/Qualities/QualityProfileRepository.cs @@ -6,12 +6,12 @@ using NzbDrone.Core.Messaging.Events; namespace NzbDrone.Core.Profiles.Qualities { - public interface IProfileRepository : IBasicRepository<QualityProfile> + public interface IQualityProfileRepository : IBasicRepository<QualityProfile> { bool Exists(int id); } - public class QualityProfileRepository : BasicRepository<QualityProfile>, IProfileRepository + public class QualityProfileRepository : BasicRepository<QualityProfile>, IQualityProfileRepository { private readonly ICustomFormatService _customFormatService; diff --git a/src/NzbDrone.Core/Profiles/Qualities/QualityProfileService.cs b/src/NzbDrone.Core/Profiles/Qualities/QualityProfileService.cs index ae7446511..67b7dc18f 100644 --- a/src/NzbDrone.Core/Profiles/Qualities/QualityProfileService.cs +++ b/src/NzbDrone.Core/Profiles/Qualities/QualityProfileService.cs @@ -28,19 +28,19 @@ namespace NzbDrone.Core.Profiles.Qualities IHandle<CustomFormatAddedEvent>, IHandle<CustomFormatDeletedEvent> { - private readonly IProfileRepository _profileRepository; + private readonly IQualityProfileRepository _qualityProfileRepository; private readonly IImportListFactory _importListFactory; private readonly ICustomFormatService _formatService; private readonly ISeriesService _seriesService; private readonly Logger _logger; - public QualityProfileService(IProfileRepository profileRepository, + public QualityProfileService(IQualityProfileRepository qualityProfileRepository, IImportListFactory importListFactory, ICustomFormatService formatService, ISeriesService seriesService, Logger logger) { - _profileRepository = profileRepository; + _qualityProfileRepository = qualityProfileRepository; _importListFactory = importListFactory; _formatService = formatService; _seriesService = seriesService; @@ -49,38 +49,38 @@ namespace NzbDrone.Core.Profiles.Qualities public QualityProfile Add(QualityProfile profile) { - return _profileRepository.Insert(profile); + return _qualityProfileRepository.Insert(profile); } public void Update(QualityProfile profile) { - _profileRepository.Update(profile); + _qualityProfileRepository.Update(profile); } public void Delete(int id) { if (_seriesService.GetAllSeries().Any(c => c.QualityProfileId == id) || _importListFactory.All().Any(c => c.QualityProfileId == id)) { - var profile = _profileRepository.Get(id); + var profile = _qualityProfileRepository.Get(id); throw new QualityProfileInUseException(profile.Name); } - _profileRepository.Delete(id); + _qualityProfileRepository.Delete(id); } public List<QualityProfile> All() { - return _profileRepository.All().ToList(); + return _qualityProfileRepository.All().ToList(); } public QualityProfile Get(int id) { - return _profileRepository.Get(id); + return _qualityProfileRepository.Get(id); } public bool Exists(int id) { - return _profileRepository.Exists(id); + return _qualityProfileRepository.Exists(id); } public void Handle(ApplicationStartedEvent message) diff --git a/src/NzbDrone.Core/Validation/ProfileExistsValidator.cs b/src/NzbDrone.Core/Validation/QualityProfileExistsValidator.cs similarity index 76% rename from src/NzbDrone.Core/Validation/ProfileExistsValidator.cs rename to src/NzbDrone.Core/Validation/QualityProfileExistsValidator.cs index b8dceb294..81d458b0b 100644 --- a/src/NzbDrone.Core/Validation/ProfileExistsValidator.cs +++ b/src/NzbDrone.Core/Validation/QualityProfileExistsValidator.cs @@ -3,16 +3,16 @@ using NzbDrone.Core.Profiles.Qualities; namespace NzbDrone.Core.Validation { - public class ProfileExistsValidator : PropertyValidator + public class QualityProfileExistsValidator : PropertyValidator { private readonly IQualityProfileService _qualityProfileService; - public ProfileExistsValidator(IQualityProfileService qualityProfileService) + public QualityProfileExistsValidator(IQualityProfileService qualityProfileService) { _qualityProfileService = qualityProfileService; } - protected override string GetDefaultMessageTemplate() => "QualityProfile does not exist"; + protected override string GetDefaultMessageTemplate() => "Quality Profile does not exist"; protected override bool IsValid(PropertyValidatorContext context) { diff --git a/src/NzbDrone.Integration.Test/ApiTests/SeriesEditorFixture.cs b/src/NzbDrone.Integration.Test/ApiTests/SeriesEditorFixture.cs index 7293ce97e..0faaa429a 100644 --- a/src/NzbDrone.Integration.Test/ApiTests/SeriesEditorFixture.cs +++ b/src/NzbDrone.Integration.Test/ApiTests/SeriesEditorFixture.cs @@ -11,7 +11,7 @@ namespace NzbDrone.Integration.Test.ApiTests { private void GivenExistingSeries() { - WaitForCompletion(() => Profiles.All().Count > 0); + WaitForCompletion(() => QualityProfiles.All().Count > 0); foreach (var title in new[] { "90210", "Dexter" }) { diff --git a/src/NzbDrone.Integration.Test/ApiTests/WantedTests/CutoffUnmetFixture.cs b/src/NzbDrone.Integration.Test/ApiTests/WantedTests/CutoffUnmetFixture.cs index 1b4a2b61d..97dff6948 100644 --- a/src/NzbDrone.Integration.Test/ApiTests/WantedTests/CutoffUnmetFixture.cs +++ b/src/NzbDrone.Integration.Test/ApiTests/WantedTests/CutoffUnmetFixture.cs @@ -12,7 +12,7 @@ namespace NzbDrone.Integration.Test.ApiTests.WantedTests [Order(1)] public void cutoff_should_have_monitored_items() { - EnsureProfileCutoff(1, Quality.HDTV720p, true); + EnsureQualityProfileCutoff(1, Quality.HDTV720p, true); var series = EnsureSeries(266189, "The Blacklist", true); EnsureEpisodeFile(series, 1, 1, Quality.SDTV); @@ -25,7 +25,7 @@ namespace NzbDrone.Integration.Test.ApiTests.WantedTests [Order(1)] public void cutoff_should_not_have_unmonitored_items() { - EnsureProfileCutoff(1, Quality.HDTV720p, true); + EnsureQualityProfileCutoff(1, Quality.HDTV720p, true); var series = EnsureSeries(266189, "The Blacklist", false); EnsureEpisodeFile(series, 1, 1, Quality.SDTV); @@ -38,7 +38,7 @@ namespace NzbDrone.Integration.Test.ApiTests.WantedTests [Order(1)] public void cutoff_should_have_series() { - EnsureProfileCutoff(1, Quality.HDTV720p, true); + EnsureQualityProfileCutoff(1, Quality.HDTV720p, true); var series = EnsureSeries(266189, "The Blacklist", true); EnsureEpisodeFile(series, 1, 1, Quality.SDTV); @@ -52,7 +52,7 @@ namespace NzbDrone.Integration.Test.ApiTests.WantedTests [Order(2)] public void cutoff_should_have_unmonitored_items() { - EnsureProfileCutoff(1, Quality.HDTV720p, true); + EnsureQualityProfileCutoff(1, Quality.HDTV720p, true); var series = EnsureSeries(266189, "The Blacklist", false); EnsureEpisodeFile(series, 1, 1, Quality.SDTV); diff --git a/src/NzbDrone.Integration.Test/IntegrationTestBase.cs b/src/NzbDrone.Integration.Test/IntegrationTestBase.cs index a1c086790..3e8a8a2b0 100644 --- a/src/NzbDrone.Integration.Test/IntegrationTestBase.cs +++ b/src/NzbDrone.Integration.Test/IntegrationTestBase.cs @@ -49,7 +49,7 @@ namespace NzbDrone.Integration.Test public LogsClient Logs; public ClientBase<NamingConfigResource> NamingConfig; public NotificationClient Notifications; - public ClientBase<QualityProfileResource> Profiles; + public ClientBase<QualityProfileResource> QualityProfiles; public ReleaseClient Releases; public ReleasePushClient ReleasePush; public ClientBase<RootFolderResource> RootFolders; @@ -113,7 +113,7 @@ namespace NzbDrone.Integration.Test Logs = new LogsClient(RestClient, ApiKey); NamingConfig = new ClientBase<NamingConfigResource>(RestClient, ApiKey, "config/naming"); Notifications = new NotificationClient(RestClient, ApiKey); - Profiles = new ClientBase<QualityProfileResource>(RestClient, ApiKey); + QualityProfiles = new ClientBase<QualityProfileResource>(RestClient, ApiKey); Releases = new ReleaseClient(RestClient, ApiKey); ReleasePush = new ReleasePushClient(RestClient, ApiKey); RootFolders = new ClientBase<RootFolderResource>(RestClient, ApiKey); @@ -317,10 +317,10 @@ namespace NzbDrone.Integration.Test return result.EpisodeFile; } - public QualityProfileResource EnsureProfileCutoff(int profileId, Quality cutoff, bool upgradeAllowed) + public QualityProfileResource EnsureQualityProfileCutoff(int profileId, Quality cutoff, bool upgradeAllowed) { var needsUpdate = false; - var profile = Profiles.Get(profileId); + var profile = QualityProfiles.Get(profileId); if (profile.Cutoff != cutoff.Id) { @@ -336,7 +336,7 @@ namespace NzbDrone.Integration.Test if (needsUpdate) { - profile = Profiles.Put(profile); + profile = QualityProfiles.Put(profile); } return profile; diff --git a/src/Sonarr.Api.V3/ImportLists/ImportListController.cs b/src/Sonarr.Api.V3/ImportLists/ImportListController.cs index 8ea3887dd..542158d6a 100644 --- a/src/Sonarr.Api.V3/ImportLists/ImportListController.cs +++ b/src/Sonarr.Api.V3/ImportLists/ImportListController.cs @@ -11,13 +11,13 @@ namespace Sonarr.Api.V3.ImportLists public static readonly ImportListResourceMapper ResourceMapper = new (); public static readonly ImportListBulkResourceMapper BulkResourceMapper = new (); - public ImportListController(IImportListFactory importListFactory, ProfileExistsValidator profileExistsValidator) + public ImportListController(IImportListFactory importListFactory, QualityProfileExistsValidator qualityProfileExistsValidator) : base(importListFactory, "importlist", ResourceMapper, BulkResourceMapper) { Http.Validation.RuleBuilderExtensions.ValidId(SharedValidator.RuleFor(s => s.QualityProfileId)); SharedValidator.RuleFor(c => c.RootFolderPath).IsValidPath(); - SharedValidator.RuleFor(c => c.QualityProfileId).SetValidator(profileExistsValidator); + SharedValidator.RuleFor(c => c.QualityProfileId).SetValidator(qualityProfileExistsValidator); } } } diff --git a/src/Sonarr.Api.V3/Series/SeriesController.cs b/src/Sonarr.Api.V3/Series/SeriesController.cs index 2622aa7ba..1a920a6e0 100644 --- a/src/Sonarr.Api.V3/Series/SeriesController.cs +++ b/src/Sonarr.Api.V3/Series/SeriesController.cs @@ -58,7 +58,7 @@ namespace Sonarr.Api.V3.Series SeriesExistsValidator seriesExistsValidator, SeriesAncestorValidator seriesAncestorValidator, SystemFolderValidator systemFolderValidator, - ProfileExistsValidator profileExistsValidator, + QualityProfileExistsValidator qualityProfileExistsValidator, SeriesFolderAsRootFolderValidator seriesFolderAsRootFolderValidator) : base(signalRBroadcaster) { @@ -83,7 +83,7 @@ namespace Sonarr.Api.V3.Series .SetValidator(systemFolderValidator) .When(s => !s.Path.IsNullOrWhiteSpace()); - SharedValidator.RuleFor(s => s.QualityProfileId).SetValidator(profileExistsValidator); + SharedValidator.RuleFor(s => s.QualityProfileId).SetValidator(qualityProfileExistsValidator); PostValidator.RuleFor(s => s.Path).IsValidPath().When(s => s.RootFolderPath.IsNullOrWhiteSpace()); PostValidator.RuleFor(s => s.RootFolderPath) From e53b7f8c945e3597ca1719961e82540f1f01f0e9 Mon Sep 17 00:00:00 2001 From: Bogdan <mynameisbogdan@users.noreply.github.com> Date: Wed, 25 Oct 2023 15:11:41 +0300 Subject: [PATCH 076/136] New: Add Download Client validation for indexers --- .../DownloadClientExistsValidator.cs | 27 +++++++++++++++++++ .../Indexers/IndexerController.cs | 4 ++- 2 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 src/NzbDrone.Core/Validation/DownloadClientExistsValidator.cs diff --git a/src/NzbDrone.Core/Validation/DownloadClientExistsValidator.cs b/src/NzbDrone.Core/Validation/DownloadClientExistsValidator.cs new file mode 100644 index 000000000..cf021f464 --- /dev/null +++ b/src/NzbDrone.Core/Validation/DownloadClientExistsValidator.cs @@ -0,0 +1,27 @@ +using FluentValidation.Validators; +using NzbDrone.Core.Download; + +namespace NzbDrone.Core.Validation +{ + public class DownloadClientExistsValidator : PropertyValidator + { + private readonly IDownloadClientFactory _downloadClientFactory; + + public DownloadClientExistsValidator(IDownloadClientFactory downloadClientFactory) + { + _downloadClientFactory = downloadClientFactory; + } + + protected override string GetDefaultMessageTemplate() => "Download Client does not exist"; + + protected override bool IsValid(PropertyValidatorContext context) + { + if (context?.PropertyValue == null || (int)context.PropertyValue == 0) + { + return true; + } + + return _downloadClientFactory.Exists((int)context.PropertyValue); + } + } +} diff --git a/src/Sonarr.Api.V3/Indexers/IndexerController.cs b/src/Sonarr.Api.V3/Indexers/IndexerController.cs index 444993c2f..fffbc4d96 100644 --- a/src/Sonarr.Api.V3/Indexers/IndexerController.cs +++ b/src/Sonarr.Api.V3/Indexers/IndexerController.cs @@ -1,4 +1,5 @@ using NzbDrone.Core.Indexers; +using NzbDrone.Core.Validation; using Sonarr.Http; namespace Sonarr.Api.V3.Indexers @@ -9,9 +10,10 @@ namespace Sonarr.Api.V3.Indexers public static readonly IndexerResourceMapper ResourceMapper = new (); public static readonly IndexerBulkResourceMapper BulkResourceMapper = new (); - public IndexerController(IndexerFactory indexerFactory) + public IndexerController(IndexerFactory indexerFactory, DownloadClientExistsValidator downloadClientExistsValidator) : base(indexerFactory, "indexer", ResourceMapper, BulkResourceMapper) { + SharedValidator.RuleFor(c => c.DownloadClientId).SetValidator(downloadClientExistsValidator); } } } From 36ca24e55a5eda859047d82855f65c401cc0b30f Mon Sep 17 00:00:00 2001 From: Bogdan <mynameisbogdan@users.noreply.github.com> Date: Wed, 25 Oct 2023 18:28:57 +0300 Subject: [PATCH 077/136] Allow 0 as valid value in QualityProfileExistsValidator --- src/NzbDrone.Core/Validation/QualityProfileExistsValidator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NzbDrone.Core/Validation/QualityProfileExistsValidator.cs b/src/NzbDrone.Core/Validation/QualityProfileExistsValidator.cs index 81d458b0b..0bc853a56 100644 --- a/src/NzbDrone.Core/Validation/QualityProfileExistsValidator.cs +++ b/src/NzbDrone.Core/Validation/QualityProfileExistsValidator.cs @@ -16,7 +16,7 @@ namespace NzbDrone.Core.Validation protected override bool IsValid(PropertyValidatorContext context) { - if (context.PropertyValue == null) + if (context?.PropertyValue == null || (int)context.PropertyValue == 0) { return true; } From cd1d8a3ff04b0d06f6fa7dfeb7a7ab6dd88b36b3 Mon Sep 17 00:00:00 2001 From: Stevie Robinson <stevie.robinson@gmail.com> Date: Fri, 27 Oct 2023 16:10:03 +0200 Subject: [PATCH 078/136] fix translation token --- src/NzbDrone.Core/HealthCheck/Checks/UpdateCheck.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NzbDrone.Core/HealthCheck/Checks/UpdateCheck.cs b/src/NzbDrone.Core/HealthCheck/Checks/UpdateCheck.cs index 23ad009cc..09b3eea1f 100644 --- a/src/NzbDrone.Core/HealthCheck/Checks/UpdateCheck.cs +++ b/src/NzbDrone.Core/HealthCheck/Checks/UpdateCheck.cs @@ -79,7 +79,7 @@ namespace NzbDrone.Core.HealthCheck.Checks "UpdateUiNotWritableHealthCheckMessage", new Dictionary<string, object> { - { "startupFolder", startupFolder }, + { "uiFolder", uiFolder }, { "userName", Environment.UserName } }), "#cannot-install-update-because-ui-folder-is-not-writable-by-the-user"); From a5506b09d278c21a02a5f243617280de97073c4f Mon Sep 17 00:00:00 2001 From: Stevie Robinson <stevie.robinson@gmail.com> Date: Sat, 28 Oct 2023 23:49:52 +0200 Subject: [PATCH 079/136] New: Add [SEV] to release group exceptions Closes #6118 --- .../ParserTests/ReleaseGroupParserFixture.cs | 3 +++ src/NzbDrone.Core/Parser/Parser.cs | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/NzbDrone.Core.Test/ParserTests/ReleaseGroupParserFixture.cs b/src/NzbDrone.Core.Test/ParserTests/ReleaseGroupParserFixture.cs index 003fb50b5..ebdf82704 100644 --- a/src/NzbDrone.Core.Test/ParserTests/ReleaseGroupParserFixture.cs +++ b/src/NzbDrone.Core.Test/ParserTests/ReleaseGroupParserFixture.cs @@ -70,6 +70,9 @@ namespace NzbDrone.Core.Test.ParserTests [TestCase("Show Title (2021) S01 (2160p ATVP WEB-DL Hybrid H265 DV HDR10+ DDP Atmos 5.1 English - HONE)", "HONE")] [TestCase("Series.Title.S01E09.1080p.DSNP.WEB-DL.DDP2.0.H.264-VARYG (Blue Lock, Multi-Subs)", "VARYG")] [TestCase("Series.Title (2014) S09E10 (1080p AMZN WEB-DL x265 HEVC 10bit DDP 5.1 Vyndros)", "Vyndros")] + [TestCase("Series Title S02E03 Title 4k to 1080p DSNP WEBrip x265 DDP 5 1 Releaser[SEV]", "SEV")] + [TestCase("Series Title Season 01 S01 1080p AMZN UHD WebRip x265 DDP 5.1 Atmos Releaser-SEV", "SEV")] + [TestCase("Series Title - S01.E06 - Title 1080p AMZN WebRip x265 DDP 5.1 Atmos Releaser [SEV]", "SEV")] public void should_parse_exception_release_group(string title, string expected) { Parser.Parser.ParseReleaseGroup(title).Should().Be(expected); diff --git a/src/NzbDrone.Core/Parser/Parser.cs b/src/NzbDrone.Core/Parser/Parser.cs index 35880d0e9..27f98b91c 100644 --- a/src/NzbDrone.Core/Parser/Parser.cs +++ b/src/NzbDrone.Core/Parser/Parser.cs @@ -514,7 +514,7 @@ namespace NzbDrone.Core.Parser private static readonly Regex ExceptionReleaseGroupRegexExact = new Regex(@"(?<releasegroup>(?:D\-Z0N3|Fight-BB|VARYG)\b)", RegexOptions.IgnoreCase | RegexOptions.Compiled); // groups whose releases end with RlsGroup) or RlsGroup] - private static readonly Regex ExceptionReleaseGroupRegex = new Regex(@"(?<releasegroup>(Silence|afm72|Panda|Ghost|MONOLITH|Tigole|Joy|ImE|UTR|t3nzin|Anime Time|Project Angel|Hakata Ramen|HONE|Vyndros)(?=\]|\)))", RegexOptions.IgnoreCase | RegexOptions.Compiled); + private static readonly Regex ExceptionReleaseGroupRegex = new Regex(@"(?<releasegroup>(Silence|afm72|Panda|Ghost|MONOLITH|Tigole|Joy|ImE|UTR|t3nzin|Anime Time|Project Angel|Hakata Ramen|HONE|Vyndros|SEV)(?=\]|\)))", RegexOptions.IgnoreCase | RegexOptions.Compiled); private static readonly Regex YearInTitleRegex = new Regex(@"^(?<title>.+?)[-_. ]+?[\(\[]?(?<year>\d{4})[\]\)]?", RegexOptions.IgnoreCase | RegexOptions.Compiled); From ba447c93e363475dc90ceea3c0a274f7d0845f96 Mon Sep 17 00:00:00 2001 From: Bogdan <mynameisbogdan@users.noreply.github.com> Date: Sun, 29 Oct 2023 00:52:13 +0300 Subject: [PATCH 080/136] Fixed: DVDRip/DVDRemux being treated as full disk releases Closes #6134 --- .../DecisionEngineTests/RawDiskSpecificationFixture.cs | 8 ++++++++ .../DecisionEngine/Specifications/RawDiskSpecification.cs | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/RawDiskSpecificationFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/RawDiskSpecificationFixture.cs index 1ac77bee8..9831d1fea 100644 --- a/src/NzbDrone.Core.Test/DecisionEngineTests/RawDiskSpecificationFixture.cs +++ b/src/NzbDrone.Core.Test/DecisionEngineTests/RawDiskSpecificationFixture.cs @@ -86,5 +86,13 @@ namespace NzbDrone.Core.Test.DecisionEngineTests _remoteEpisode.Release.Title = title; Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeFalse(); } + + [TestCase("Series Title EP50 USLT NTSC DVDRemux DD2.0")] + [TestCase("Series.Title.S01.NTSC.DVDRip.DD2.0.x264-PLAiD")] + public void should_return_true_if_dvdrip(string title) + { + _remoteEpisode.Release.Title = title; + Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeTrue(); + } } } diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/RawDiskSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/RawDiskSpecification.cs index 80c40c95e..e62e68edc 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/RawDiskSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/RawDiskSpecification.cs @@ -13,7 +13,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications { new Regex(@"(?:dis[ck])(?:[-_. ]\d+[-_. ])(?:(?:(?:480|720|1080|2160)[ip]|)[-_. ])?(?:Blu\-?ray)", RegexOptions.Compiled | RegexOptions.IgnoreCase), new Regex(@"(?:(?:480|720|1080|2160)[ip]|)[-_. ](?:full)[-_. ](?:Blu\-?ray)", RegexOptions.Compiled | RegexOptions.IgnoreCase), - new Regex(@"(?:\d?x?M?DVD-?[R59])", RegexOptions.Compiled | RegexOptions.IgnoreCase) + new Regex(@"(?:\d?x?M?DVD-?[R59])[ ._]", RegexOptions.Compiled | RegexOptions.IgnoreCase) }; private static readonly string[] _dvdContainerTypes = new[] { "vob", "iso" }; From 192eb7b62ae60f300a9371ce3ed2e0056b5a1f4d Mon Sep 17 00:00:00 2001 From: Bogdan <mynameisbogdan@users.noreply.github.com> Date: Sun, 29 Oct 2023 00:52:30 +0300 Subject: [PATCH 081/136] New: Set busy timeout for SQLite --- src/NzbDrone.Core/Datastore/ConnectionStringFactory.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/NzbDrone.Core/Datastore/ConnectionStringFactory.cs b/src/NzbDrone.Core/Datastore/ConnectionStringFactory.cs index 069b944fb..19c938737 100644 --- a/src/NzbDrone.Core/Datastore/ConnectionStringFactory.cs +++ b/src/NzbDrone.Core/Datastore/ConnectionStringFactory.cs @@ -44,11 +44,12 @@ namespace NzbDrone.Core.Datastore var connectionBuilder = new SQLiteConnectionStringBuilder { DataSource = dbPath, - CacheSize = (int)-10000, + CacheSize = (int)-20000, DateTimeKind = DateTimeKind.Utc, JournalMode = OsInfo.IsOsx ? SQLiteJournalModeEnum.Truncate : SQLiteJournalModeEnum.Wal, Pooling = true, - Version = 3 + Version = 3, + BusyTimeout = 100 }; if (OsInfo.IsOsx) From b0cf36a431e38ea6962b634228cfa2b27aed730d Mon Sep 17 00:00:00 2001 From: Sonarr <development@sonarr.tv> Date: Sat, 28 Oct 2023 21:51:41 +0000 Subject: [PATCH 082/136] Automated API Docs update ignore-downstream --- src/Sonarr.Api.V3/openapi.json | 75 ++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/src/Sonarr.Api.V3/openapi.json b/src/Sonarr.Api.V3/openapi.json index a154c2b26..1dd49cdbb 100644 --- a/src/Sonarr.Api.V3/openapi.json +++ b/src/Sonarr.Api.V3/openapi.json @@ -2362,6 +2362,39 @@ "schema": { "type": "string" } + }, + { + "name": "seriesIds", + "in": "query", + "schema": { + "type": "array", + "items": { + "type": "integer", + "format": "int32" + } + } + }, + { + "name": "languages", + "in": "query", + "schema": { + "type": "array", + "items": { + "type": "integer", + "format": "int32" + } + } + }, + { + "name": "quality", + "in": "query", + "schema": { + "type": "array", + "items": { + "type": "integer", + "format": "int32" + } + } } ], "responses": { @@ -5477,6 +5510,43 @@ "type": "boolean", "default": false } + }, + { + "name": "seriesIds", + "in": "query", + "schema": { + "type": "array", + "items": { + "type": "integer", + "format": "int32" + } + } + }, + { + "name": "protocol", + "in": "query", + "schema": { + "$ref": "#/components/schemas/DownloadProtocol" + } + }, + { + "name": "languages", + "in": "query", + "schema": { + "type": "array", + "items": { + "type": "integer", + "format": "int32" + } + } + }, + { + "name": "quality", + "in": "query", + "schema": { + "type": "integer", + "format": "int32" + } } ], "responses": { @@ -11130,6 +11200,11 @@ "format": "date-time", "nullable": true }, + "lastAired": { + "type": "string", + "format": "date-time", + "nullable": true + }, "seriesType": { "$ref": "#/components/schemas/SeriesTypes" }, From fc0b42efd5f39ea76bea68de4dcd6d93eba8f71e Mon Sep 17 00:00:00 2001 From: Weblate <noreply@weblate.org> Date: Thu, 26 Oct 2023 23:57:59 +0000 Subject: [PATCH 083/136] 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: Fixer <ygj59783@zslsz.com> Co-authored-by: Havok Dan <havokdan@yahoo.com.br> Co-authored-by: ID-86 <id86dev@gmail.com> Co-authored-by: Jordy <prive@jordyhoebergen.nl> Co-authored-by: LeDaFeEs <leonardo.fonseca85300@gmail.com> Co-authored-by: Lizandra Candido da Silva <lizandra.c.s@gmail.com> Co-authored-by: Weblate <noreply@weblate.org> Co-authored-by: bai0012 <baicongrui@gmail.com> Co-authored-by: 無情天 <kofzhanganguo@126.com> Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/ Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/cs/ Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/fr/ Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/nl/ Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/pt/ Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/pt_BR/ Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/ro/ Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/zh_CN/ Translation: Servarr/Sonarr --- src/NzbDrone.Core/Localization/Core/cs.json | 3 +- src/NzbDrone.Core/Localization/Core/fr.json | 14 +- src/NzbDrone.Core/Localization/Core/nl.json | 3 +- src/NzbDrone.Core/Localization/Core/pt.json | 15 +- .../Localization/Core/pt_BR.json | 307 +++++++++--------- src/NzbDrone.Core/Localization/Core/ro.json | 10 +- .../Localization/Core/zh_CN.json | 123 +++---- 7 files changed, 248 insertions(+), 227 deletions(-) diff --git a/src/NzbDrone.Core/Localization/Core/cs.json b/src/NzbDrone.Core/Localization/Core/cs.json index 2d676a310..3cef163d4 100644 --- a/src/NzbDrone.Core/Localization/Core/cs.json +++ b/src/NzbDrone.Core/Localization/Core/cs.json @@ -270,5 +270,6 @@ "Dates": "Termíny", "DefaultCase": "Výchozí případ", "DailyTypeFormat": "Datum ({format})", - "Default": "Výchozí" + "Default": "Výchozí", + "IndexerDownloadClientHelpText": "Zvolte, který klient pro stahování bude použit pro zachytávání z toho indexeru" } diff --git a/src/NzbDrone.Core/Localization/Core/fr.json b/src/NzbDrone.Core/Localization/Core/fr.json index 8f377bef5..60653e070 100644 --- a/src/NzbDrone.Core/Localization/Core/fr.json +++ b/src/NzbDrone.Core/Localization/Core/fr.json @@ -563,7 +563,7 @@ "Logs": "Journaux", "MaintenanceRelease": "Version de maintenance : corrections de bugs et autres améliorations. Voir l'historique des validations Github pour plus de détails", "ManageEpisodes": "Gérer les épisodes", - "ManageEpisodesSeason": "Gérer les fichiers d'épisodes de cette saison", + "ManageEpisodesSeason": "Gérer les épisodes de cette saison", "ManageImportLists": "Gérer les listes d'importation", "ManageIndexers": "Gérer les indexeurs", "Manual": "Manuel", @@ -1037,7 +1037,6 @@ "UpcomingSeriesDescription": "La série a été annoncée mais pas encore de date de diffusion exacte", "UpdateMonitoring": "Surveillance des mises à jour", "UpdateScriptPathHelpText": "Chemin d'accès à un script personnalisé qui prend un package de mise à jour extrait et gère le reste du processus de mise à jour", - "UpdateUINotWritableHealthCheckMessage": "Impossible d'installer la mise à jour, car le dossier de l'interface utilisateur '{0}' n'est pas accessible en écriture par l'utilisateur '{1}'.", "UrlBaseHelpText": "Pour la prise en charge du proxy inverse, la valeur par défaut est vide", "WeekColumnHeaderHelpText": "Affiché au-dessus de chaque colonne lorsque la semaine est la vue active", "WithFiles": "Avec les fichiers", @@ -1241,7 +1240,7 @@ "EnableInteractiveSearch": "Activer la recherche interactive", "CloneCondition": "État du clone", "DeleteCustomFormat": "Supprimer le format personnalisé", - "DeleteCustomFormatMessageText": "Êtes-vous sûr de vouloir supprimer le format personnalisé « {0} » ?", + "DeleteCustomFormatMessageText": "Êtes-vous sûr de vouloir supprimer le format personnalisé '{customFormatName}' ?", "Conditions": "Conditions", "CountImportListsSelected": "{count} liste(s) d'importation sélectionnée(s)", "DeleteSeriesFolderConfirmation": "Le dossier de la série `{path}` et tout son contenu seront supprimés.", @@ -1249,7 +1248,7 @@ "DeletedReasonMissingFromDisk": "{appName} n'a pas pu trouver le fichier sur le disque. Le fichier a donc été dissocié de l'épisode dans la base de données", "Docker": "Docker", "DockerUpdater": "Mettez à jour le conteneur Docker pour recevoir la mise à jour", - "DownloadClientCheckUnableToCommunicateWithHealthCheckMessage": "Impossible de communiquer avec {0}.", + "DownloadClientCheckUnableToCommunicateWithHealthCheckMessage": "Impossible de communiquer avec {downloadClientName}. {errorMessage}", "EnableAutomaticSearch": "Activer la recherche automatique", "EpisodeAirDate": "Date de diffusion de l'épisode", "ErrorLoadingPage": "Une erreur s'est produite lors du chargement de cette page", @@ -1262,7 +1261,7 @@ "DeleteRemotePathMapping": "Supprimer le mappage de chemin distant", "DestinationPath": "Chemin de destination", "DestinationRelativePath": "Chemin relatif de destination", - "DownloadClientRootFolderHealthCheckMessage": "Le client de téléchargement {0} place les téléchargements dans le dossier racine {1}. Vous ne devez pas télécharger vers un dossier racine.", + "DownloadClientRootFolderHealthCheckMessage": "Le client de téléchargement {downloadClientName} place les téléchargements dans le dossier racine {rootFolderPath}. Vous ne devez pas télécharger vers un dossier racine.", "DownloadFailedTooltip": "Échec du téléchargement de l'épisode", "DownloadIgnored": "Téléchargement ignoré", "DownloadWarning": "Avertissement de téléchargement : {warningMessage}", @@ -1400,7 +1399,7 @@ "CustomFormatHelpText": "{appName} note chaque version en utilisant la somme des scores pour la correspondance des formats personnalisés. Si une nouvelle version devait améliorer le score, avec une qualité identique ou supérieure, alors {appName} la récupérerait.", "CustomFormatUnknownCondition": "Condition de format personnalisé inconnue '{implémentation}'", "CustomFormatUnknownConditionOption": "Option inconnue '{key}' pour la condition '{implementation}'", - "DownloadClientRemovesCompletedDownloadsHealthCheckMessage": "Le client de téléchargement {0} est configuré pour supprimer les téléchargements terminés. Cela peut entraîner la suppression des téléchargements de votre client avant que {1} puisse les importer.", + "DownloadClientRemovesCompletedDownloadsHealthCheckMessage": "Le client de téléchargement {downloadClientName} est configuré pour supprimer les téléchargements terminés. Cela peut entraîner la suppression des téléchargements de votre client avant que {appName} puisse les importer.", "DownloadFailed": "Échec du téléchargement", "EditImportListExclusion": "Modifier l'exclusion de la liste d'importation", "EditSeries": "Modifier les séries", @@ -1488,5 +1487,6 @@ "FailedToUpdateSettings": "Échec de la mise à jour des paramètres", "FeatureRequests": "Requêtes de nouvelles fonctionnalités", "File": "Fichier", - "NoImportListsFound": "Aucune liste d'importation trouvée" + "NoImportListsFound": "Aucune liste d'importation trouvée", + "QueueFilterHasNoItems": "Le filtre de file d'attente sélectionné ne contient aucun élément" } diff --git a/src/NzbDrone.Core/Localization/Core/nl.json b/src/NzbDrone.Core/Localization/Core/nl.json index 3ba8d854f..7e9f60b05 100644 --- a/src/NzbDrone.Core/Localization/Core/nl.json +++ b/src/NzbDrone.Core/Localization/Core/nl.json @@ -73,5 +73,6 @@ "AddIndexerImplementation": "Indexeerder toevoegen - {implementationName}", "UpdateMechanismHelpText": "Gebruik de ingebouwde updater van {appName} of een script", "CalendarOptions": "Kalender Opties", - "DeleteQualityProfileMessageText": "Bent u zeker dat u het kwaliteitsprofiel {name} wilt verwijderen?" + "DeleteQualityProfileMessageText": "Bent u zeker dat u het kwaliteitsprofiel {name} wilt verwijderen?", + "AppUpdated": "{appName} is geüpdatet" } diff --git a/src/NzbDrone.Core/Localization/Core/pt.json b/src/NzbDrone.Core/Localization/Core/pt.json index 8bf8b79a4..efc7c2a49 100644 --- a/src/NzbDrone.Core/Localization/Core/pt.json +++ b/src/NzbDrone.Core/Localization/Core/pt.json @@ -5,7 +5,7 @@ "Language": "Idioma", "Added": "Adicionado", "ApiKeyValidationHealthCheckMessage": "Por favor, atualize a sua API Key para ter no mínimo {length} caracteres. Pode fazer através das definições ou do ficheiro de configuração", - "AppDataLocationHealthCheckMessage": "Não foi possível atualizar para prevenir apagar a AppData durante a atualização", + "AppDataLocationHealthCheckMessage": "Não foi possível actualizar para prevenir apagar a AppData durante a actualização", "AddAutoTag": "Adicionar Etiqueta Automática", "AbsoluteEpisodeNumbers": "Números de Episódios Absolutos", "Add": "Adicionar", @@ -23,7 +23,7 @@ "AgeWhenGrabbed": "Idade (quando capturada)", "ApplyTags": "Aplicar etiquetas", "Authentication": "Autenticação", - "AuthenticationMethodHelpText": "Solicitar Nome de Usuário e Senha para acessar o {appName}", + "AuthenticationMethodHelpText": "Solicitar nome de utilizador e palavra-passe para acessar ao {appName}", "AuthenticationRequired": "Autenticação Necessária", "AutoAdd": "Adicionar automaticamente", "AddRootFolder": "Adicionar pasta raiz", @@ -123,5 +123,14 @@ "AllSeriesInRootFolderHaveBeenImported": "Todas as séries em {path} foram importadas", "AddNewSeries": "Adicionar Nova Série", "AddNewSeriesError": "Erro ao carregar resultados da busca. Por favor, tente novamente.", - "AddNewSeriesHelpText": "É fácil adicionar uma nova série, apenas comece a escrever o nome da série que quer adicionar." + "AddNewSeriesHelpText": "É fácil adicionar uma nova série, apenas comece a escrever o nome da série que quer adicionar.", + "DeleteTagMessageText": "Tem a certeza que quer eliminar a etiqueta \"{label}\"?", + "DeleteSelectedIndexersMessageText": "Tem a certeza que quer eliminar os {count} indexadores seleccionados?", + "DeleteDownloadClientMessageText": "Tem a certeza que quer eliminar o cliente de transferências \"{name}\"?", + "DeleteNotificationMessageText": "Tem a certeza que quer eliminar a notificação \"{name}\"?", + "EnableRss": "Activar RSS", + "DeleteSelectedDownloadClientsMessageText": "Tem a certeza que quer eliminar os {count} clientes de transferência seleccionados?", + "MaintenanceRelease": "Versão de manutenção: reparações de erros e outras melhorias. Consulte o Histórico de Commits do Github para saber mais", + "DeleteBackupMessageText": "Tem a certeza que quer eliminar a cópia de segurança \"{name}\"?", + "Exception": "Excepção" } diff --git a/src/NzbDrone.Core/Localization/Core/pt_BR.json b/src/NzbDrone.Core/Localization/Core/pt_BR.json index 7840cf781..3770297b3 100644 --- a/src/NzbDrone.Core/Localization/Core/pt_BR.json +++ b/src/NzbDrone.Core/Localization/Core/pt_BR.json @@ -8,7 +8,7 @@ "EditSelectedImportLists": "Editar listas de importação selecionadas", "Enabled": "Habilitado", "Ended": "Terminou", - "HideAdvanced": "Ocultar Avançado", + "HideAdvanced": "Ocultar opções avançadas", "ImportListRootFolderMultipleMissingRootsHealthCheckMessage": "Múltiplas pastas raiz estão faltando nas listas de importação: {rootFoldersInfo}", "ImportListStatusAllUnavailableHealthCheckMessage": "Todas as listas estão indisponíveis devido a falhas", "ImportMechanismHandlingDisabledHealthCheckMessage": "Ativar gerenciamento de download concluído", @@ -46,7 +46,7 @@ "RootFolderMissingHealthCheckMessage": "Pasta raiz ausente: {rootFolderPath}", "RootFolderMultipleMissingHealthCheckMessage": "Faltam várias pastas raiz: {rootFolderPaths}", "SearchForMonitoredEpisodes": "Pesquisar episódios monitorados", - "ShowAdvanced": "Mostrar Avançado", + "ShowAdvanced": "Mostrar opções avançadas", "ShownClickToHide": "Mostrado, clique para ocultar", "SizeOnDisk": "Tamanho no disco", "SystemTimeHealthCheckMessage": "A hora do sistema está desligada por mais de 1 dia. Tarefas agendadas podem não ser executadas corretamente até que o horário seja corrigido", @@ -56,7 +56,7 @@ "ApiKeyValidationHealthCheckMessage": "Atualize sua chave de API para ter pelo menos {length} caracteres. Você pode fazer isso através das configurações ou do arquivo de configuração", "RemoveCompletedDownloads": "Remover downloads concluídos", "AppDataLocationHealthCheckMessage": "A atualização não será possível para evitar a exclusão de AppData na atualização", - "DownloadClientCheckUnableToCommunicateWithHealthCheckMessage": "Não é possível se comunicar com {downloadClientName}.", + "DownloadClientCheckUnableToCommunicateWithHealthCheckMessage": "Não é possível se comunicar com {downloadClientName}. {errorMessage}", "DownloadClientRootFolderHealthCheckMessage": "O cliente de download {downloadClientName} coloca os downloads na pasta raiz {rootFolderPath}. Você não deve baixar para uma pasta raiz.", "DownloadClientSortingHealthCheckMessage": "O cliente de download {downloadClientName} tem classificação {sortingMode} habilitada para a categoria {appName}. Você deve desativar a classificação em seu cliente de download para evitar problemas de importação.", "DownloadClientStatusSingleClientHealthCheckMessage": "Clientes de download indisponíveis devido a falhas: {downloadClientNames}", @@ -96,7 +96,7 @@ "DeleteConditionMessageText": "Tem certeza de que deseja excluir a condição '{name}'?", "DeleteCustomFormat": "Excluir formato personalizado", "DeleteCustomFormatMessageText": "Tem certeza de que deseja excluir o formato personalizado '{customFormatName}'?", - "ExportCustomFormat": "Exportar Formato Personalizado", + "ExportCustomFormat": "Exportar formato personalizado", "Negated": "Negado", "Remove": "Remover", "RemoveFromDownloadClient": "Remover Do Cliente de Download", @@ -179,7 +179,7 @@ "ImportLists": "Listas de importação", "Indexers": "Indexadores", "AbsoluteEpisodeNumbers": "Número(s) absoluto(s) do episódio", - "AirDate": "Data de Exibição", + "AirDate": "Data de exibição", "Daily": "Diário", "Details": "Detalhes", "AllTitles": "Todos os Títulos", @@ -195,7 +195,7 @@ "MatchedToEpisodes": "Correspondente aos Episódios", "MatchedToSeason": "Correspondente a Temporada", "MatchedToSeries": "Correspondente à Série", - "MultiSeason": "Multi-Temporada", + "MultiSeason": "Várias temporadas", "PartialSeason": "Temporada Parcial", "Proper": "Proper", "Real": "Real", @@ -278,7 +278,7 @@ "TaskUserAgentTooltip": "User-Agent fornecido pelo aplicativo que chamou a API", "ResetQualityDefinitions": "Redefinir Definições de Qualidade", "ResetQualityDefinitionsMessageText": "Tem certeza de que deseja redefinir as definições de qualidade?", - "ResetTitles": "Redefinir Títulos", + "ResetTitles": "Redefinir títulos", "Restore": "Restaurar", "RestoreBackup": "Restaurar backup", "Scheduled": "Agendado", @@ -301,7 +301,7 @@ "WouldYouLikeToRestoreBackup": "Gostaria de restaurar o backup '{name}'?", "YesCancel": "Sim, Cancelar", "Reset": "Redefinir", - "ResetDefinitionTitlesHelpText": "Redefinir títulos de definição, bem como valores", + "ResetDefinitionTitlesHelpText": "Redefinir títulos de definição e valores", "RestartReloadNote": "Observação: o {appName} reiniciará automaticamente e recarregará a IU durante o processo de restauração.", "SkipRedownload": "Ignorar o Redownload", "SkipRedownloadHelpText": "Impede que o {appName} tente baixar uma versão alternativa para este item", @@ -329,11 +329,11 @@ "Grabbed": "Obtido", "Ignored": "Ignorado", "Imported": "Importado", - "IncludeUnmonitored": "Incluir não monitorado", + "IncludeUnmonitored": "Incluir não monitorados", "Indexer": "Indexador", "LatestSeason": "Última temporada", "MissingEpisodes": "Episódios ausentes", - "MonitoredOnly": "Somente monitorado", + "MonitoredOnly": "Somente monitorados", "OutputPath": "Caminho de saída", "Progress": "Progresso", "Rating": "Avaliação", @@ -356,11 +356,11 @@ "VideoDynamicRange": "Faixa Dinâmica de Vídeo", "Warn": "Alerta", "Year": "Ano", - "Age": "Idade", + "Age": "Tempo de vida", "Episodes": "Episódios", "Failed": "Falhou", "HasMissingSeason": "Está faltando temporada", - "Info": "Info", + "Info": "Informações", "NotSeasonPack": "Não Pacote de Temporada", "Peers": "Pares", "Protocol": "Protocolo", @@ -384,9 +384,9 @@ "RootFolders": "Pastas Raiz", "AllResultsAreHiddenByTheAppliedFilter": "Todos os resultados são ocultados pelo filtro aplicado", "Folder": "Pasta", - "InteractiveImport": "Importação Interativa", + "InteractiveImport": "Importação interativa", "LastUsed": "Usado por último", - "MoveAutomatically": "Mover Automaticamente", + "MoveAutomatically": "Mover automaticamente", "NoResultsFound": "Nenhum resultado encontrado", "RemoveRootFolder": "Remover pasta raiz", "RemoveTagsAutomatically": "Remover tags automaticamente", @@ -418,7 +418,7 @@ "AddListExclusionError": "Não foi possível adicionar uma nova exclusão de lista, tente novamente.", "AddNewRestriction": "Adicionar nova restrição", "AddNotificationError": "Não foi possível adicionar uma nova notificação, tente novamente.", - "AddQualityProfile": "Adicionar Perfil de Qualidade", + "AddQualityProfile": "Adicionar perfil de qualidade", "AddQualityProfileError": "Não foi possível adicionar uma nova notificação, tente novamente.", "AddReleaseProfile": "Adicionar Perfil de Lançamento", "AddRemotePathMapping": "Adicionar Mapeamento de Caminho Remoto", @@ -432,11 +432,11 @@ "ApiKey": "Chave API", "ApplicationURL": "URL do Aplicativo", "ApplicationUrlHelpText": "A URL externa deste aplicativo, incluindo http(s)://, porta e base da URL", - "AuthBasic": "Básico (Balão do Navegador)", - "AuthForm": "Formulário (Página de login)", + "AuthBasic": "Básico (pop-up do navegador)", + "AuthForm": "Formulário (página de login)", "Authentication": "Autenticação", - "AuthenticationMethodHelpText": "Exigir nome de usuário e senha para acessar {appName}", - "AuthenticationRequired": "Autenticação Requerida", + "AuthenticationMethodHelpText": "Exigir nome de usuário e senha para acessar o {appName}", + "AuthenticationRequired": "Autenticação exigida", "AutoRedownloadFailedHelpText": "Procurar automaticamente e tente baixar uma versão diferente", "AutoTaggingLoadError": "Não foi possível carregar a marcação automática", "Automatic": "Automático", @@ -455,7 +455,7 @@ "BuiltIn": "Embutido", "BypassDelayIfAboveCustomFormatScore": "Ignorar se estiver acima da pontuação do formato personalizado", "BypassDelayIfAboveCustomFormatScoreHelpText": "Ativar a opção de ignorar quando a versão tiver uma pontuação maior que a pontuação mínima configurada do formato personalizado", - "BypassDelayIfAboveCustomFormatScoreMinimumScore": "Pontuação Mínima de Formato Personalizado", + "BypassDelayIfAboveCustomFormatScoreMinimumScore": "Pontuação mínima de formato personalizado", "BypassDelayIfAboveCustomFormatScoreMinimumScoreHelpText": "Pontuação mínima de formato personalizado necessária para ignorar o atraso do protocolo preferido", "BypassDelayIfHighestQuality": "Ignorar se a qualidade é mais alta", "BypassDelayIfHighestQualityHelpText": "Ignorar o atraso quando o lançamento tiver a qualidade mais alta habilitada no perfil de qualidade com o protocolo preferencial", @@ -490,7 +490,7 @@ "CreateEmptySeriesFoldersHelpText": "Crie pastas de série ausentes durante a verificação de disco", "CreateGroup": "Criar Grupo", "Custom": "Personalizar", - "CustomFormat": "Formato Personalizado", + "CustomFormat": "Formato personalizado", "CustomFormatUnknownCondition": "Condição de formato personalizado desconhecido '{implementation}'", "CustomFormatUnknownConditionOption": "Opção desconhecida '{key}' para a condição '{implementation}'", "CustomFormatsLoadError": "Não foi possível carregar Formatos Personalizados", @@ -529,7 +529,7 @@ "DeleteSpecificationHelpText": "Tem certeza de que deseja excluir a especificação '{name}'?", "DeleteTag": "Excluir Tag", "DeleteTagMessageText": "Tem certeza de que deseja excluir a tag '{label}'?", - "DisabledForLocalAddresses": "Desabilitar para Endereços Locais", + "DisabledForLocalAddresses": "Desabilitado para endereços locais", "DoNotPrefer": "Não Preferir", "DoNotUpgradeAutomatically": "Não Atualizar Automaticamente", "DoneEditingGroups": "Concluir Edição de Grupos", @@ -540,7 +540,7 @@ "DownloadPropersAndRepacks": "Propers e Repacks", "DownloadPropersAndRepacksHelpText": "Se deve ou não atualizar automaticamente para Propers/Repacks", "DownloadPropersAndRepacksHelpTextCustomFormat": "Use 'Não Preferir' para classificar por pontuação de formato personalizado em Propers/Repacks", - "DownloadPropersAndRepacksHelpTextWarning": "Use formatos personalizados para atualizações automáticas para Propers/Repacks", + "DownloadPropersAndRepacksHelpTextWarning": "Usar formatos personalizados para atualizações automáticas para propers/repacks", "Duplicate": "Duplicado", "EditCustomFormat": "Editar Formato Personalizado", "EditDelayProfile": "Editar Perfil de Atraso", @@ -598,88 +598,88 @@ "ImportList": "Importar Lista", "ImportListExclusions": "Importar Lista de Exclusões", "ImportListExclusionsLoadError": "Não foi possível carregar as exclusões da lista de importação", - "ImportListSettings": "Configurações da Importação das Listas", - "ImportListsLoadError": "Não foi possível carregar listas de importação", - "ImportListsSettingsSummary": "Importar de outra instância {appName} ou listas Trakt e gerenciar exclusões de lista", - "ImportScriptPath": "Importar Caminho do Script", + "ImportListSettings": "Configurações de Importar listas", + "ImportListsLoadError": "Não foi possível carregar Importar listas", + "ImportListsSettingsSummary": "Importar de outra instância do {appName} ou de listas do Trakt, e gerenciar as exclusões de lista", + "ImportScriptPath": "Caminho para importar script", "ImportScriptPathHelpText": "O caminho para o script a ser usado para importar", - "ImportUsingScript": "Importar Usando o Script", - "ImportUsingScriptHelpText": "Copiar arquivos para importar usando um script (ex. para transcodificação)", + "ImportUsingScript": "Importar usando script", + "ImportUsingScriptHelpText": "Copiar arquivos para importar usando um script (p. ex. para transcodificação)", "Importing": "Importando", - "IncludeCustomFormatWhenRenaming": "Incluir Formato Personalizado ao Renomear", + "IncludeCustomFormatWhenRenaming": "Incluir formato personalizado ao renomear", "IncludeCustomFormatWhenRenamingHelpText": "Incluir no formato de renomeação {Custom Formats}", - "IncludeHealthWarnings": "Incluir Avisos de Saúde", + "IncludeHealthWarnings": "Incluir avisos de integridade", "IndexerDownloadClientHelpText": "Especifique qual cliente de download é usado para baixar deste indexador", "IndexerOptionsLoadError": "Não foi possível carregar as opções do indexador", - "IndexerPriority": "Prioridade do Indexador", - "IndexerPriorityHelpText": "Prioridade do indexador de 1 (mais alta) a 50 (mais baixa). Padrão: 25. Usado ao obter lançamentos como desempate para lançamentos iguais, o {appName} ainda usará todos os indexadores habilitados para sincronização e pesquisa de RSS", - "IndexerSettings": "Configurações do Indexador", + "IndexerPriority": "Prioridade do indexador", + "IndexerPriorityHelpText": "Prioridade do indexador de 1 (mais alta) a 50 (mais baixa). Padrão: 25. Usado como desempate para lançamentos iguais ao obter lançamentos, o {appName} ainda usará todos os indexadores habilitados para sincronização e pesquisa de RSS", + "IndexerSettings": "Configurações do indexador", "IndexersLoadError": "Não foi possível carregar os indexadores", "IndexersSettingsSummary": "Indexadores e opções de indexador", - "InstanceName": "Nome da Instância", + "InstanceName": "Nome da instância", "InstanceNameHelpText": "Nome da instância na aba e para o nome do aplicativo Syslog", - "InteractiveSearch": "Pesquisa Interativa", - "InvalidFormat": "Formato Inválido", + "InteractiveSearch": "Pesquisa interativa", + "InvalidFormat": "Formato inválido", "LanguagesLoadError": "Não foi possível carregar os idiomas", - "ListExclusionsLoadError": "Não foi possível carregar as exclusões da lista", - "ListOptionsLoadError": "Não foi possível carregar as opções da lista", + "ListExclusionsLoadError": "Não foi possível carregar as exclusões de lista", + "ListOptionsLoadError": "Não foi possível carregar as opções de lista", "ListQualityProfileHelpText": "Os itens da lista de perfil de qualidade serão adicionados com", "ListRootFolderHelpText": "Os itens da lista da pasta raiz serão adicionados a", - "ListTagsHelpText": "Tags que serão adicionadas na importação desta lista", + "ListTagsHelpText": "Tags que serão adicionadas ao importar esta lista", "ListWillRefreshEveryInterval": "A lista será atualizada a cada {refreshInterval}", "ListsLoadError": "Não foi possível carregar as listas", "LocalAirDate": "Data de exibição local", - "LocalPath": "Caminho Local", - "LogLevel": "Nível de Registro", - "LogLevelTraceHelpTextWarning": "O registro de rastreamento deve ser ativado apenas temporariamente", - "Logging": "Registrando", - "LongDateFormat": "Formato de Data Longa", + "LocalPath": "Caminho local", + "LogLevel": "Nível de registro", + "LogLevelTraceHelpTextWarning": "O registro em log deve ser habilitado apenas temporariamente", + "Logging": "Registro em log", + "LongDateFormat": "Formato longo de data", "Lowercase": "Minúsculas", - "ManualImportItemsLoadError": "Não é possível carregar itens de importação manual", + "ManualImportItemsLoadError": "Não foi possível carregar itens de importação manual", "Max": "Máx.", - "MaximumLimits": "Limites Máximos", - "MaximumSingleEpisodeAge": "Idade Máxima de Episódio Único", + "MaximumLimits": "Limites máximos", + "MaximumSingleEpisodeAge": "Idade máxima de episódio único", "MaximumSingleEpisodeAgeHelpText": "Durante uma pesquisa de temporada completa, apenas os pacotes de temporada serão permitidos quando o último episódio da temporada for mais antigo do que esta configuração. Somente série padrão. Use 0 para desabilitar.", - "MaximumSize": "Tamanho Máximo", - "MaximumSizeHelpText": "Tamanho máximo para uma liberação a ser capturada em MB. Defina como zero para definir como ilimitado", + "MaximumSize": "Tamanho máximo", + "MaximumSizeHelpText": "Tamanho máximo, em MB, para obter um lançamento. Zero significa ilimitado", "Mechanism": "Mecanismo", "MediaInfo": "Informações da mídia", - "MediaManagementSettings": "Configurações de Gerenciamento de Mídia", + "MediaManagementSettings": "Configurações de gerenciamento de mídia", "MediaManagementSettingsLoadError": "Não foi possível carregar as configurações de gerenciamento de mídia", "MediaManagementSettingsSummary": "Nomenclatura, configurações de gerenciamento de arquivos e pastas raiz", "MegabytesPerMinute": "Megabytes por minuto", - "MetadataLoadError": "Não é possível carregar metadados", - "MetadataSettings": "Configurações de Metadados", - "MetadataSettingsSummary": "Crie arquivos de metadados quando os episódios são importados ou as séries são atualizadas", - "MetadataSourceSettings": "Configurações de Fontes de Metadados", - "MetadataSourceSettingsSummary": "Informações sobre onde {appName} obtém informações sobre séries e episódios", + "MetadataLoadError": "Não foi possível carregar os metadados", + "MetadataSettings": "Configurações de metadados", + "MetadataSettingsSummary": "Criar arquivos de metadados ao importar episódios ou atualizar a série", + "MetadataSourceSettings": "Configurações da fonte de metadados", + "MetadataSourceSettingsSummary": "Informações sobre onde o {appName} obtém informações sobre séries e episódios", "Min": "Mín.", - "MinimumAge": "Idade Miníma", - "MinimumAgeHelpText": "Somente Usenet: Idade mínima em minutos dos NZBs antes de serem capturados. Use isso para dar aos novos lançamentos tempo para se propagar para seu provedor usenet.", - "MinimumCustomFormatScore": "Pontuação Mínima de Formato Personalizado", + "MinimumAge": "Idade miníma", + "MinimumAgeHelpText": "Somente Usenet: idade mínima, em minutos, dos NZBs antes de serem capturados. Use isso para dar aos novos lançamentos tempo para se propagar para seu provedor de Usenet.", + "MinimumCustomFormatScore": "Pontuação mínima de formato personalizado", "MinimumCustomFormatScoreHelpText": "Pontuação mínima de formato personalizado permitida para download", - "MinimumFreeSpace": "Espaço Livre Mínimo", - "MinimumLimits": "Limites Mínimos", - "MinutesFortyFive": "45 Minutos: {fortyFive}", - "MinutesSixty": "60 Minutos: {sixty}", - "MinutesThirty": "30 Minutos: {thirty}", + "MinimumFreeSpace": "Mínimo de espaço livre", + "MinimumLimits": "Limites mínimos", + "MinutesFortyFive": "45 minutos: {fortyFive}", + "MinutesSixty": "60 minutos: {sixty}", + "MinutesThirty": "30 minutos: {thirty}", "Monday": "Segunda-feira", - "MonitoringOptions": "Opções de Monitoramento", + "MonitoringOptions": "Opções de monitoramento", "MoreDetails": "Mais detalhes", - "MultiEpisode": "Multi Episódio", - "MultiEpisodeInvalidFormat": "Multi Episódio: Formato Inválido", - "MultiEpisodeStyle": "Estilo de Multi Episódios", - "MustContain": "Deve Conter", - "MustNotContain": "Não Deve Conter", - "MustNotContainHelpText": "O lançamento será rejeitado se contiver um ou mais termos (sem distinção entre maiúsculas e minúsculas)", - "NamingSettings": "Configurações de Nomes", - "NamingSettingsLoadError": "Não foi possível carregar as configurações de nomeação", + "MultiEpisode": "Multiepisódio", + "MultiEpisodeInvalidFormat": "Multiepisódio: formato inválido", + "MultiEpisodeStyle": "Estilo de multiepisódio", + "MustContain": "Deve conter", + "MustNotContain": "Não deve conter", + "MustNotContainHelpText": "O lançamento será rejeitado se contiver um ou mais destes termos (sem distinção entre maiúsculas e minúsculas)", + "NamingSettings": "Configurações de nomenclatura", + "NamingSettingsLoadError": "Não foi possível carregar as configurações de nomenclatura", "Never": "Nunca", - "NoChanges": "Sem Alterações", - "NoDelay": "Sem Atraso", + "NoChanges": "Sem alterações", + "NoDelay": "Sem atraso", "NoLinks": "Sem links", "NoTagsHaveBeenAddedYet": "Nenhuma tag foi adicionada ainda", - "None": "Vazio", + "None": "Nenhum", "NotificationTriggers": "Gatilhos de Notificação", "NotificationTriggersHelpText": "Selecione quais eventos devem acionar esta notificação", "NotificationsLoadError": "Não foi possível carregar as notificações", @@ -711,7 +711,7 @@ "PortNumber": "Número da Porta", "PreferAndUpgrade": "Preferir e Atualizar", "PreferProtocol": "Preferir {preferredProtocol}", - "PreferTorrent": "Preferir Torrent", + "PreferTorrent": "Preferir torrent", "PreferUsenet": "Preferir Usenet", "Preferred": "Preferido", "PreferredProtocol": "Protocolo Preferido", @@ -756,13 +756,13 @@ "External": "Externo", "ExtraFileExtensionsHelpText": "Lista separada por vírgulas de arquivos extras para importar (.nfo será importado como .nfo-orig)", "HistoryLoadError": "Não foi possível carregar o histórico", - "IndexerTagHelpText": "Use este indexador apenas para séries com pelo menos uma tag correspondente. Deixe em branco para usar com todas as séries.", - "MediaInfoFootNote": "MediaInfo Full/AudioLanguages/SubtitleLanguages oferece suporte a um sufixo `:EN+DE`, permitindo que você filtre os idiomas incluídos no nome do arquivo. Use `-DE` para excluir idiomas específicos. Acrescentar `+` (por exemplo `:EN+`) resultará em `[EN]`/`[EN+--]`/`[--]` dependendo dos idiomas excluídos. Por exemplo `{MediaInfo Full:EN+DE}`.", + "IndexerTagHelpText": "Usar este indexador apenas para séries com pelo menos uma tag correspondente. Deixe em branco para usar com todas as séries.", + "MediaInfoFootNote": "MediaInfo Full/AudioLanguages/SubtitleLanguages oferece suporte a um sufixo \":EN+DE\", permitindo que você filtre os idiomas inclusos no nome do arquivo. Use \"-DE\" para excluir idiomas específicos. Usar \"+\" (p. ex.: \":EN+\") resultará em \"[EN]\"/\"[EN+--]\"/\"[--]\" dependendo dos idiomas excluídos. P. ex.: \"{MediaInfo Full:EN+DE}\".", "MinimumFreeSpaceHelpText": "Impedir a importação se deixar menos do que esta quantidade de espaço em disco disponível", "MustContainHelpText": "O lançamento deve conter pelo menos um desses termos (sem distinção entre maiúsculas e minúsculas)", "NegateHelpText": "Se marcado, o formato personalizado não será aplicado se esta condição {implementationName} corresponder.", - "NoLimitForAnyRuntime": "Sem limite para qualquer tempo de execução", - "NoMinimumForAnyRuntime": "Sem mínimo para qualquer tempo de execução", + "NoLimitForAnyRuntime": "Sem limite para qualquer duração", + "NoMinimumForAnyRuntime": "Sem mínimo para qualquer duração", "NotificationsTagsHelpText": "Envie notificações apenas para séries com pelo menos uma tag correspondente", "OnManualInteractionRequired": "Na Interação Manual Necessária", "PendingChangesMessage": "Você tem alterações não salvas. Tem certeza de que deseja sair desta página?", @@ -799,7 +799,7 @@ "RescanSeriesFolderAfterRefresh": "Verificar novamente a pasta da série após a atualização", "ResetAPIKey": "Redefinir chave de API", "ResetAPIKeyMessageText": "Tem certeza de que deseja redefinir sua chave de API?", - "ResetDefinitions": "Redefinir Definições", + "ResetDefinitions": "Redefinir definições", "RestartLater": "Vou reiniciar mais tarde", "RestartNow": "Reiniciar Agora", "RestartRequiredHelpTextWarning": "Requer reinicialização para entrar em vigor", @@ -817,7 +817,7 @@ "RssSyncIntervalHelpText": "Intervalo em minutos. Defina como zero para desativar (isso interromperá todas as capturas de lançamentos automáticas)", "RssSyncIntervalHelpTextWarning": "Isso se aplica a todos os indexadores, siga as regras estabelecidas por eles", "SaveChanges": "Salvar Mudanças", - "SaveSettings": "Salvar Configurações", + "SaveSettings": "Salvar configurações", "Score": "Pontuação", "Script": "Script", "ScriptPath": "Caminho do Script", @@ -843,7 +843,7 @@ "SizeLimit": "Limite de Tamanho", "SkipFreeSpaceCheck": "Ignorar verificação de espaço livre", "SkipFreeSpaceCheckWhenImportingHelpText": "Use quando {appName} não consegue detectar espaço livre em sua pasta raiz durante a importação do arquivo", - "SmartReplace": "Substituição Inteligente", + "SmartReplace": "Substituição inteligente", "SmartReplaceHint": "Traço ou Espaço e Traço, dependendo do nome", "Socks4": "Socks4", "Socks5": "Socks5 (Suporte à TOR)", @@ -897,7 +897,7 @@ "UnmonitorDeletedEpisodes": "Cancelar Monitoramento de Episódios Excluídos", "UnsavedChanges": "Alterações Não Salvas", "UpdateAutomaticallyHelpText": "Baixe e instale atualizações automaticamente. Você ainda poderá instalar a partir do Sistema: Atualizações", - "UpdateMechanismHelpText": "Use o atualizador integrado do {appName} ou um script", + "UpdateMechanismHelpText": "Usar o atualizador integrado do {appName} ou um script", "UpdateSonarrDirectlyLoadError": "Incapaz de atualizar o {appName} diretamente,", "UpdateUiNotWritableHealthCheckMessage": "Não é possível instalar a atualização porque a pasta de IU '{uiFolder}' não pode ser salva pelo usuário '{userName}'.", "UpgradeUntil": "Atualizar Até", @@ -907,7 +907,7 @@ "UpgradesAllowed": "Atualizações Permitidas", "UpgradesAllowedHelpText": "se as qualidades desativadas não forem atualizadas", "Uppercase": "Maiúsculo", - "UrlBase": "URL Base", + "UrlBase": "URL base", "UrlBaseHelpText": "Para suporte a proxy reverso, o padrão é vazio", "UseProxy": "Usar Proxy", "Usenet": "Usenet", @@ -953,10 +953,10 @@ "ImportErrors": "Erros de Importação", "ImportExistingSeries": "Importar Série Existente", "ImportSeries": "Importar Séries", - "LibraryImportHeader": "Importa séries que você já possui", + "LibraryImportHeader": "Importar as séries que você já possui", "LibraryImportTips": "Algumas dicas para garantir que a importação ocorra sem problemas:", - "LibraryImportTipsDontUseDownloadsFolder": "Não use para importar downloads de seu cliente de download, isso é apenas para bibliotecas organizadas existentes, não para arquivos não organizados.", - "LibraryImportTipsQualityInFilename": "Certifique-se de que seus arquivos incluam a qualidade em seus nomes de arquivo. Por exemplo: `episode.s02e15.bluray.mkv`", + "LibraryImportTipsDontUseDownloadsFolder": "Não use para importar downloads de seu cliente. Isso se aplica apenas a bibliotecas organizadas existentes, e não a arquivos desorganizados.", + "LibraryImportTipsQualityInFilename": "Certifique-se de que seus arquivos incluam a qualidade nos nomes de arquivo. Por exemplo: \"episódio.s02e15.bluray.mkv\"", "Monitor": "Monitorar", "MonitorAllEpisodesDescription": "Monitorar todos os episódios, exceto os especiais", "MonitorExistingEpisodes": "Episódios Existentes", @@ -965,12 +965,12 @@ "MonitorFutureEpisodes": "Futuros Episódios", "MonitorFutureEpisodesDescription": "Monitorar episódios que não foram exibidos", "MonitorLatestSeason": "Última Temporada", - "MonitorMissingEpisodes": "Episódios Ausentes", + "MonitorMissingEpisodes": "Episódios ausentes", "MonitorMissingEpisodesDescription": "Monitora os episódios que não possuem arquivos ou ainda não foram ao ar", - "MonitorNone": "Nada", + "MonitorNone": "Nenhum", "MonitorNoneDescription": "Nenhum episódio será monitorado", - "MonitorSpecials": "Monitorar Especiais", - "MonitorSpecialsDescription": "Monitore todos os episódios especiais sem alterar o status monitorado de outros episódios", + "MonitorSpecials": "Monitorar especiais", + "MonitorSpecialsDescription": "Monitorar todos os episódios especiais sem alterar o status de monitoramento de outros episódios", "NoMatchFound": "Nenhum resultado encontrado!", "ProcessingFolders": "Processando Pastas", "SearchByTvdbId": "Você também pode pesquisar usando o ID TVDB de um programa. Por exemplo: tvdb:71663", @@ -981,11 +981,11 @@ "StartImport": "Iniciar Importação", "StartProcessing": "Iniciar Processamento", "Upcoming": "Por vir", - "AddNewSeriesError": "Falha ao carregar os resultados da pesquisa, tente novamente.", + "AddNewSeriesError": "Falha ao carregar os resultados da pesquisa. Tente novamente.", "AddNewSeriesRootFolderHelpText": "A subpasta '{folder}' será criada automaticamente", "AnimeTypeDescription": "Episódios lançados usando um número de episódio absoluto", "DailyTypeDescription": "Episódios lançados diariamente ou com menos frequência que usam ano-mês-dia (2023-08-04)", - "LibraryImportTipsUseRootFolder": "Aponte o {appName} para a pasta que contém todos os seus programas de TV, não um específico. Por exemplo. '`{goodFolderExample}`' e não '`{badFolderExample}`'. Além disso, cada série deve estar em sua própria pasta dentro da pasta raiz/biblioteca.", + "LibraryImportTipsUseRootFolder": "Aponte o {appName} para a pasta que contém todas as suas séries, não uma específica. Por exemplo. \"`{goodFolderExample}`\" e não \"`{badFolderExample}`\". Além disso, cada série deve estar em sua própria pasta dentro da pasta raiz/biblioteca.", "MonitorExistingEpisodesDescription": "Monitorar os episódios que possuem arquivos ou ainda não foram exibidos", "MonitorLatestSeasonDescription": "Monitora todos os episódios da última temporada que foram ao ar nos últimos 90 dias e todas as temporadas futuras", "NoSeriesHaveBeenAdded": "Você ainda não adicionou nenhuma série. Deseja importar algumas ou todas as suas séries primeiro?", @@ -994,7 +994,7 @@ "EpisodeImported": "Episódio importado", "Month": "Mês", "Today": "Hoje", - "AgeWhenGrabbed": "Idade (quando baixado)", + "AgeWhenGrabbed": "Tempo de vida (quando obtido)", "DelayingDownloadUntil": "Atrasando o download até {date} às {time}", "DeletedReasonMissingFromDisk": "O {appName} não conseguiu encontrar o arquivo no disco, então o arquivo foi desvinculado do episódio no banco de dados", "DeletedReasonManual": "O arquivo foi excluído por meio da IU", @@ -1013,9 +1013,9 @@ "GrabId": "Obter ID", "GrabSelected": "Obter Selecionado", "GrabbedHistoryTooltip": "Episódio retirado de {indexer} e enviado para {downloadClient}", - "ImportedTo": "Importado Para", + "ImportedTo": "Importado para", "InfoUrl": "URL da info", - "MarkAsFailed": "Marcar como Falha", + "MarkAsFailed": "Marcar como falha", "NoHistoryFound": "Nenhum histórico encontrado", "Ok": "Ok", "Paused": "Pausado", @@ -1045,7 +1045,7 @@ "ShowEpisodeInformationHelpText": "Mostrar título e número do episódio", "SourcePath": "Caminho da Fonte", "SpecialEpisode": "Episódio Especial", - "Agenda": "Agenda", + "Agenda": "Programação", "AnEpisodeIsDownloading": "Um episódio está baixando", "CalendarLegendMissingTooltip": "O episódio foi ao ar e está faltando no disco", "CalendarFeed": "{appName} Feed do Calendário", @@ -1085,7 +1085,7 @@ "FullColorEventsHelpText": "Estilo alterado para colorir todo o evento com a cor do status, em vez de apenas a borda esquerda. Não se aplica à Agenda", "ICalTagsHelpText": "O feed conterá apenas séries com pelo menos uma tag correspondente", "IconForFinalesHelpText": "Mostrar ícone para finais de séries/temporadas com base nas informações de episódios disponíveis", - "NoHistoryBlocklist": "Sem histórico na lista de bloqueio", + "NoHistoryBlocklist": "Não há lista de bloqueio no histórico", "QualityCutoffNotMet": "O corte de qualidade não foi atingido", "QueueLoadError": "Falha ao carregar a fila", "RemoveQueueItem": "Remover - {sourceTitle}", @@ -1101,7 +1101,7 @@ "AddIndexerImplementation": "Adicionar Indexador - {implementationName}", "AddToDownloadQueue": "Adicionar à fila de download", "AddedToDownloadQueue": "Adicionado à fila de download", - "Airs": "Exibições", + "Airs": "Vai ao ar em", "AirsDateAtTimeOn": "{date} às {time} em {networkLabel}", "AnimeTypeFormat": "Número absoluto do episódio ({format})", "AppUpdatedVersion": "{appName} foi atualizado para a versão `{version}`, para obter as alterações mais recentes, você precisará recarregar {appName} ", @@ -1120,18 +1120,18 @@ "FilterEpisodesPlaceholder": "Filtrar episódios por título ou número", "FilterIsAfter": "está depois", "Grab": "Obter", - "GrabReleaseMessageText": "{appName} não conseguiu determinar para qual série e episódio era este lançamento. O {appName} pode não conseguir importar automaticamente esta versão. Você quer pegar '{title}'?", + "GrabReleaseMessageText": "O {appName} não conseguiu determinar para qual série e episódio é este lançamento. O {appName} pode não conseguir importar automaticamente este lançamento. Deseja obter \"{title}\"?", "ICalFeed": "Feed do iCal", "ICalLink": "Link do iCal", "InteractiveImportLoadError": "Não foi possível carregar itens de importação manual", "InteractiveImportNoFilesFound": "Nenhum arquivo de vídeo foi encontrado na pasta selecionada", "InteractiveImportNoSeason": "A temporada deve ser escolhida para cada arquivo selecionado", - "InteractiveSearchResultsFailedErrorMessage": "A pesquisa falhou porque é {message}. Tente atualizar as informações da série e verifique se as informações necessárias estão presentes antes de pesquisar novamente.", - "KeyboardShortcutsFocusSearchBox": "Foco na Caixa de Pesquisa", - "KeyboardShortcutsSaveSettings": "Salvar Configurações", - "LocalStorageIsNotSupported": "O armazenamento local não é compatível ou está desativado. Um plugin ou navegação privada pode tê-lo desativado.", - "MappedNetworkDrivesWindowsService": "As unidades de rede mapeadas não estão disponíveis ao executar como um serviço do Windows, consulte as [FAQ](https://wiki.servarr.com/sonarr/faq#why-cant-sonarr-see-my-files-on-a-remote -server) para obter mais informações.", - "AirsTbaOn": "TBA em {networkLabel}", + "InteractiveSearchResultsFailedErrorMessage": "A pesquisa falhou porque {message}. Tente atualizar as informações da série e verifique se as informações necessárias estão presentes antes de pesquisar novamente.", + "KeyboardShortcutsFocusSearchBox": "Selecionar a caixa de pesquisa", + "KeyboardShortcutsSaveSettings": "Salvar configurações", + "LocalStorageIsNotSupported": "O armazenamento local não é compatível ou está desabilitado. Um plugin ou a navegação privada pode tê-lo desativado.", + "MappedNetworkDrivesWindowsService": "As unidades de rede mapeadas não estão disponíveis ao executar como um serviço do Windows, consulte as [Perguntas frequentes](https://wiki.servarr.com/sonarr/faq#why-cant-sonarr-see-my-files-on-a-remote -server) para saber mais.", + "AirsTbaOn": "A ser anunciado em {networkLabel}", "AirsTimeOn": "{time} em {networkLabel}", "AirsTomorrowOn": "Amanhã às {time} em {networkLabel}", "AllFiles": "Todos os Arquivos", @@ -1198,23 +1198,23 @@ "FilterNotInNext": "não no próximo", "FilterSeriesPlaceholder": "Filtrar séries", "FilterStartsWith": "começa com", - "GrabRelease": "Obter Lançamento", + "GrabRelease": "Obter lançamento", "HardlinkCopyFiles": "Hardlink/Copiar Arquivos", - "InteractiveImportNoEpisode": "Um ou mais episódios devem ser escolhidos para cada arquivo selecionado", - "InteractiveImportNoImportMode": "Um modo de importação deve ser selecionado", - "InteractiveImportNoLanguage": "O(s) idioma(s) deve(m) ser escolhido(s) para cada arquivo selecionado", - "InteractiveImportNoQuality": "A qualidade deve ser escolhida para cada arquivo selecionado", + "InteractiveImportNoEpisode": "Escolha um ou mais episódios para cada arquivo selecionado", + "InteractiveImportNoImportMode": "Defina um modo de importação", + "InteractiveImportNoLanguage": "Defina um idioma para cada arquivo selecionado", + "InteractiveImportNoQuality": "Defina a qualidade para cada arquivo selecionado", "InteractiveImportNoSeries": "A série deve ser escolhida para cada arquivo selecionado", - "KeyboardShortcuts": "Atalhos do Teclado", - "KeyboardShortcutsCloseModal": "Fechar Modal Atual", - "KeyboardShortcutsConfirmModal": "Aceitar Modal de Confirmação", - "KeyboardShortcutsOpenModal": "Abrir Este Modal", + "KeyboardShortcuts": "Atalhos de teclado", + "KeyboardShortcutsCloseModal": "Fechar pop-up atual", + "KeyboardShortcutsConfirmModal": "Aceitar o pop-up de confirmação", + "KeyboardShortcutsOpenModal": "Abrir este pop-up", "Local": "Local", "Logout": "Sair", - "ManualGrab": "Baixar Manualmente", - "ManualImport": "Importação Manual", - "Mapping": "Mapeando", - "MarkAsFailedConfirmation": "Tem certeza de que deseja marcar '{sourceTitle}' como reprovado?", + "ManualGrab": "Obter manualmente", + "ManualImport": "Importação manual", + "Mapping": "Mapeamento", + "MarkAsFailedConfirmation": "Tem certeza de que deseja marcar \"{sourceTitle}\" como em falha?", "MidseasonFinale": "Final da Meia Temporada", "More": "Mais", "MyComputer": "Meu Computador", @@ -1252,8 +1252,8 @@ "SceneNumberNotVerified": "O número da Scene ainda não foi verificado", "MetadataProvidedBy": "Os metadados são fornecidos por {provider}", "Mixed": "Misturado", - "MoveFiles": "Mover Arquivos", - "MultiLanguages": "Multi-Idiomas", + "MoveFiles": "Mover arquivos", + "MultiLanguages": "Vários idiomas", "NoEpisodesFoundForSelectedSeason": "Nenhum episódio foi encontrado para a temporada selecionada", "NotificationStatusSingleClientHealthCheckMessage": "Notificações indisponíveis devido a falhas: {notificationNames}", "Or": "ou", @@ -1270,7 +1270,7 @@ "OverrideGrabNoQuality": "A qualidade deve ser selecionada", "OverrideGrabNoSeries": "A série deve ser selecionada", "Parse": "Analisar", - "ParseModalHelpTextDetails": "{appName} tentará analisar o título e mostrar detalhes sobre ele", + "ParseModalHelpTextDetails": "O {appName} tentará analisar o título e mostrar detalhes sobre ele", "RecentChanges": "Mudanças Recentes", "ReleaseRejected": "Lançamento Rejeitado", "ReleaseSceneIndicatorAssumingScene": "Assumindo a Numeração da Scene.", @@ -1316,15 +1316,15 @@ "Connection": "Conexão", "CustomFormatJson": "JSON do formato personalizado", "Database": "Banco de dados", - "HealthMessagesInfoBox": "Você pode encontrar mais informações sobre a causa dessas mensagens de verificação de integridade clicando no link da wiki (ícone do livro) no final da linha ou verificando seus [logs]({link}). Se tiver dificuldade em interpretar essas mensagens, você pode entrar em contato com nosso suporte, nos links abaixo.", + "HealthMessagesInfoBox": "Para saber mais sobre a causa dessas mensagens de verificação de integridade, clique no link da wiki (ícone de livro) no final da linha ou verifique os [logs]({link}). Se tiver dificuldade em interpretar essas mensagens, entre em contato com nosso suporte nos links abaixo.", "ImdbId": "ID do IMDb", "Port": "Porta", "ShowUnknownSeriesItems": "Mostrar Itens de Séries Desconhecidas", "ShowUnknownSeriesItemsHelpText": "Mostrar itens sem uma série na fila, isso pode incluir séries, filmes removidos ou qualquer outra coisa na categoria do {appName}", "Test": "Teste", "Level": "Nível", - "AddListExclusion": "Adicionar à Lista de Exclusão", - "AddListExclusionHelpText": "Impedir que séries sejam adicionadas ao {appName} por listas", + "AddListExclusion": "Adicionar exclusão à lista", + "AddListExclusionHelpText": "Impedir que o {appName} adicione séries por listas", "EditSeriesModalHeader": "Editar - {title}", "EditSelectedSeries": "Editar Séries Selecionadas", "HideEpisodes": "Ocultar episódios", @@ -1353,7 +1353,7 @@ "SeriesIndexFooterMissingUnmonitored": "Episódios Ausentes (Série não monitorada)", "DefaultNameCopiedProfile": "{name} - Cópia", "DefaultNameCopiedSpecification": "{name} - Cópia", - "DeleteEpisodesFilesHelpText": "Exclua os arquivos do episódio e a pasta da série", + "DeleteEpisodesFilesHelpText": "Excluir os arquivos do episódio e a pasta da série", "DeleteSelectedSeries": "Excluir Séries Selecionadas", "DeleteSeriesFolder": "Excluir Pasta da Série", "DeleteSeriesFolderConfirmation": "A pasta da série `{path}` e todo o seu conteúdo serão excluídos.", @@ -1363,32 +1363,32 @@ "DeleteSeriesModalHeader": "Excluir - {title}", "DeletedSeriesDescription": "A série foi excluída do TheTVDB", "DetailedProgressBarHelpText": "Mostrar texto na barra de progresso", - "ExpandAll": "Expandir Todos", + "ExpandAll": "Expandir tudo", "Files": "Arquivos", "HistorySeason": "Exibir histórico para esta temporada", - "HistoryModalHeaderSeason": "Histórico {season}", - "InteractiveSearchModalHeader": "Pesquisa Interativa", - "InteractiveSearchModalHeaderSeason": "Pesquisa Interativa - {season}", + "HistoryModalHeaderSeason": "Histórico - {season}", + "InteractiveSearchModalHeader": "Pesquisa interativa", + "InteractiveSearchModalHeaderSeason": "Pesquisa interativa - {season}", "InteractiveSearchSeason": "Pesquisa interativa para todos os episódios desta temporada", - "InvalidUILanguage": "Sua IU está configurada com um idioma inválido, corrija-a e salve suas configurações", + "InvalidUILanguage": "A interface está configurada com um idioma inválido, corrija-o e salve as configurações", "Large": "Grande", "Links": "Links", - "ManageEpisodes": "Gerenciar Episódios", - "ManageEpisodesSeason": "Gerenciar Arquivos de Episódios nesta temporada", + "ManageEpisodes": "Gerenciar episódios", + "ManageEpisodesSeason": "Gerenciar arquivos de episódios nesta temporada", "Medium": "Médio", "MonitorSeries": "Monitorar Série", - "MonitoredHelpText": "Baixa episódios monitorados desta série", + "MonitoredHelpText": "Baixar episódios monitorados desta série", "MonitoredStatus": "Monitorado/Status", "Monitoring": "Monitorando", "NoEpisodeInformation": "Nenhuma informação do episódio está disponível.", "NoEpisodesInThisSeason": "Não há episódios nesta temporada", - "NoHistory": "Sem histórico", + "NoHistory": "Não há histórico", "NoMonitoredEpisodesSeason": "Nenhum episódio monitorado nesta temporada", "OrganizeSelectedSeriesModalConfirmation": "Tem certeza de que deseja organizar todos os arquivos da {count} série selecionada?", "OrganizeSelectedSeriesModalHeader": "Organizar Séries Selecionadas", - "Overview": "Visão Geral", - "OverviewOptions": "Opções de Visão Geral", - "PosterOptions": "Opções de Pôster", + "Overview": "Visão geral", + "OverviewOptions": "Opções da visão geral", + "PosterOptions": "Opções do pôster", "PosterSize": "Tamanho do Pôster", "Posters": "Pôsteres", "RefreshAndScan": "Atualizar & Escanear", @@ -1428,7 +1428,7 @@ "UseSeasonFolder": "Usar Pasta de Temporada", "UseSeasonFolderHelpText": "Organizar episódios em pastas de temporadas", "WithFiles": "Com Arquivos", - "DeleteEpisodesFiles": "Excluir arquivos de episódios {episodeFileCount}", + "DeleteEpisodesFiles": "Excluir {episodeFileCount} arquivos de episódios", "NoMonitoredEpisodes": "Nenhum episódio monitorado nesta série", "ShowBanners": "Mostrar Banners", "DeleteSeriesFolderCountWithFilesConfirmation": "Tem certeza de que deseja excluir {count} séries selecionadas e todos os conteúdos?", @@ -1440,28 +1440,28 @@ "DeleteSeriesFoldersHelpText": "Excluir as pastas da série e todo o seu conteúdo", "Total": "Total", "DetailedProgressBar": "Barra de progresso detalhada", - "EndedSeriesDescription": "Não são esperados episódios ou temporadas adicionais", + "EndedSeriesDescription": "Não se espera mais episódios ou temporadas", "EpisodeFilesLoadError": "Não foi possível carregar os arquivos do episódio", "AddedDate": "Adicionado: {date}", "AllSeriesAreHiddenByTheAppliedFilter": "Todos os resultados são ocultados pelo filtro aplicado", - "AlternateTitles": "Títulos Alternativos", - "AuthenticationMethod": "Método de Autenticação", + "AlternateTitles": "Títulos alternativos", + "AuthenticationMethod": "Método de autenticação", "AuthenticationMethodHelpTextWarning": "Selecione um método de autenticação válido", - "AuthenticationRequiredPasswordHelpTextWarning": "Insira uma nova senha", + "AuthenticationRequiredPasswordHelpTextWarning": "Digite uma nova senha", "AuthenticationRequiredUsernameHelpTextWarning": "Digite um novo nome de usuário", - "CollapseAll": "Agrupar Todos", - "ContinuingSeriesDescription": "Mais episódios/outra temporada é esperada", + "CollapseAll": "Recolher tudo", + "ContinuingSeriesDescription": "Espera-se mais episódio ou outra temporada", "CountSeriesSelected": "{count} séries selecionadas", "MissingLoadError": "Erro ao carregar itens ausentes", "MissingNoItems": "Nenhum item ausente", "SearchAll": "Pesquisar Todos", "UnmonitorSelected": "Não Monitorar Selecionado", - "CutoffUnmetNoItems": "Nenhum item de corte não atendido", - "MonitorSelected": "Monitorar Selecionado", + "CutoffUnmetNoItems": "Nenhum item com limite não atendido", + "MonitorSelected": "Monitorar selecionados", "SearchForAllMissingConfirmationCount": "Tem certeza de que deseja pesquisar todos os episódios ausentes de {totalRecords}?", "SearchSelected": "Pesquisar Selecionado", - "CutoffUnmetLoadError": "Erro ao carregar itens de corte não atendidos", - "MassSearchCancelWarning": "Isso não pode ser cancelado depois de iniciado sem reiniciar o {appName} ou desabilitar todos os seus indexadores.", + "CutoffUnmetLoadError": "Erro ao carregar itens de limite não atendido", + "MassSearchCancelWarning": "Após começar, não é possível cancelar sem reiniciar o {appName} ou desabilitar todos os seus indexadores.", "SearchForAllMissing": "Pesquisar por todos os episódios ausentes", "SearchForCutoffUnmet": "Pesquise todos os episódios que o corte não foi atingido", "SearchForCutoffUnmetConfirmationCount": "Tem certeza de que deseja pesquisar todos os episódios de {totalRecords} corte não atingido?", @@ -1476,7 +1476,7 @@ "FormatAgeMinutes": "minutos", "FormatDateTimeRelative": "{relativeDay}, {formattedDate} {formattedTime}", "FormatRuntimeHours": "{hours}h", - "FormatRuntimeMinutes": "{minutes}m", + "FormatRuntimeMinutes": "{minutes} m", "FormatShortTimeSpanHours": "{hours} hora(s)", "FormatShortTimeSpanMinutes": "{minutes} minuto(s)", "FormatShortTimeSpanSeconds": "{seconds} segundo(s)", @@ -1487,5 +1487,6 @@ "AutoRedownloadFailed": "Falha no Novo Download", "AutoRedownloadFailedFromInteractiveSearch": "Falha no Novo Download pela Pesquisa Interativa", "ImportListSearchForMissingEpisodes": "Pesquisar Episódios Ausentes", - "ImportListSearchForMissingEpisodesHelpText": "Depois que a série for adicionada ao {appName}, procure automaticamente episódios ausentes" + "ImportListSearchForMissingEpisodesHelpText": "Depois que a série for adicionada ao {appName}, procure automaticamente episódios ausentes", + "QueueFilterHasNoItems": "O filtro de fila selecionado não tem itens" } diff --git a/src/NzbDrone.Core/Localization/Core/ro.json b/src/NzbDrone.Core/Localization/Core/ro.json index 44a02672a..68252092b 100644 --- a/src/NzbDrone.Core/Localization/Core/ro.json +++ b/src/NzbDrone.Core/Localization/Core/ro.json @@ -191,5 +191,13 @@ "FormatAgeDay": "zi", "TablePageSizeHelpText": "Numărul de articole de afișat pe fiecare pagină", "RemoveSelectedBlocklistMessageText": "Sigur doriți să eliminați elementele selectate din lista neagră?", - "AuthenticationRequiredUsernameHelpTextWarning": "Introduceți un nou nume de utilizator" + "AuthenticationRequiredUsernameHelpTextWarning": "Introduceți un nou nume de utilizator", + "FirstDayOfWeek": "Prima zi a săptămânii", + "ShortDateFormat": "Format scurt de dată", + "ShowRelativeDates": "Afișați datele relative", + "LongDateFormat": "Format de dată lungă", + "AppUpdated": "{appName} actualizat", + "ShowRelativeDatesHelpText": "Afișați datele relative (Azi / Ieri / etc) sau absolute", + "WeekColumnHeader": "Antetul coloanei săptămânii", + "TimeFormat": "Format ora" } diff --git a/src/NzbDrone.Core/Localization/Core/zh_CN.json b/src/NzbDrone.Core/Localization/Core/zh_CN.json index 974a9bff8..9f64f20e8 100644 --- a/src/NzbDrone.Core/Localization/Core/zh_CN.json +++ b/src/NzbDrone.Core/Localization/Core/zh_CN.json @@ -63,7 +63,7 @@ "Metadata": "元数据", "CountSeasons": "季{count}", "DownloadClientCheckNoneAvailableHealthCheckMessage": "无可用的下载客户端", - "DownloadClientCheckUnableToCommunicateWithHealthCheckMessage": "无法与{downloadClientName}进行通讯。", + "DownloadClientCheckUnableToCommunicateWithHealthCheckMessage": "无法与{downloadClientName}进行通讯", "DownloadClientRootFolderHealthCheckMessage": "下载客户端{downloadClientName}将下载内容放在根文件夹{rootFolderPath}中。您不应该下载到根文件夹。", "DownloadClientSortingHealthCheckMessage": "下载客户端{downloadClientName}已为{appName}的类别启用{sortingMode}排序。您应该在下载客户端中禁用排序,以避免导入问题。", "DownloadClientStatusAllClientHealthCheckMessage": "所有下载客户端都不可用", @@ -400,7 +400,7 @@ "AddRemotePathMappingError": "无法添加远程路径映射,请稍后重试。", "AddSeriesWithTitle": "添加 {title}", "AddToDownloadQueue": "添加到下载队列", - "AddedToDownloadQueue": "已添加到下载队列", + "AddedToDownloadQueue": "已加入下载队列", "AllFiles": "全部文件", "AlreadyInYourLibrary": "已经在你的库中", "Always": "总是", @@ -408,7 +408,7 @@ "AnimeTypeFormat": "绝对集数 ({format})", "AppUpdated": "{appName} 升级", "AppUpdatedVersion": "{appName} 已经更新到 {version} 版本,重新加载 {appName} 使更新生效 ", - "AuthenticationRequired": "需要认证", + "AuthenticationRequired": "需要身份验证", "AutomaticUpdatesDisabledDocker": "不支持在使用 Docker 容器时直接升级。你需要升级 {appName} 容器镜像或使用脚本(script)", "BackupIntervalHelpText": "自动备份时间间隔", "Branch": "分支", @@ -516,9 +516,9 @@ "FileNames": "文件名", "Filter": "过滤", "UpdateMechanismHelpText": "使用 {appName} 内置的更新程序或脚本", - "AuthenticationRequiredHelpText": "更改需要身份验证的请求。除非您了解风险,否则请勿更改。", + "AuthenticationRequiredHelpText": "更改身份验证的请求。除非您了解风险,否则请勿更改。", "AnEpisodeIsDownloading": "集正在下载", - "AuthenticationRequiredWarning": "为了防止未经身份验证的远程访问,{appName}现在需要启用身份验证。您可以选择禁用本地地址的身份验证。", + "AuthenticationRequiredWarning": "为了防止未经身份验证的远程访问,{appName} 现在需要启用身份验证。您可以禁用本地地址的身份验证。", "AutomaticSearch": "自动搜索", "BackupFolderHelpText": "相对路径将在{appName}的AppData目录下", "BindAddress": "绑定地址", @@ -545,7 +545,7 @@ "AnimeTypeDescription": "使用绝对集数发布的集数", "ApiKey": "API Key", "ApplicationUrlHelpText": "此应用的外部URL,包含 http(s)://、端口和基本URL", - "AuthenticationMethodHelpText": "需要用户名和密码以访问{appName}", + "AuthenticationMethodHelpText": "需要用户名和密码以访问 {appName}", "AuthBasic": "基础(浏览器弹出对话框)", "AuthForm": "表单(登陆页面)", "Authentication": "认证", @@ -582,7 +582,7 @@ "DeleteTagMessageText": "您确定要删除标签 '{label}' 吗?", "DeletedReasonUpgrade": "升级时删除原文件", "DestinationRelativePath": "目的相对路径", - "DisabledForLocalAddresses": "对局域网内禁用", + "DisabledForLocalAddresses": "在本地地址上禁用", "DestinationPath": "目的路径", "DoneEditingGroups": "完成编辑组", "DoNotPrefer": "不要首选", @@ -719,7 +719,7 @@ "EnableRssHelpText": "当{appName}定期通过RSS同步查找发布时使用", "EnableSslHelpText": "需要以管理员身份重新启动才能生效", "HistoryLoadError": "无法加载历史记录", - "CustomFormatJson": "自定义格式 JSON", + "CustomFormatJson": "自定义格式JSON", "Database": "数据库", "EditRemotePathMapping": "编辑远程映射路径", "EditRestriction": "编辑限制", @@ -763,17 +763,17 @@ "HealthMessagesInfoBox": "您可以通过单击行尾的wiki链接(图书图标)或检查[日志]({link})来查找有关这些运行状况检查消息原因的更多信息。如果你在理解这些信息方面有困难,你可以通过下面的链接联系我们的支持。", "StandardEpisodeFormat": "标准单集格式", "DefaultNameCopiedSpecification": "{name} - 复制", - "AddListExclusion": "添加排除列表", + "AddListExclusion": "添加列表例外", "AddListExclusionHelpText": "防止剧集通过列表添加到{appName}", "AddNewSeriesSearchForCutoffUnmetEpisodes": "开始搜索未达截止条件的集", - "AddedDate": "新增: {date}", + "AddedDate": "加入于:{date}", "AllSeriesAreHiddenByTheAppliedFilter": "所有结果都被应用的过滤器隐藏", "AlternateTitles": "备选标题", "Any": "任何", - "AuthenticationMethodHelpTextWarning": "请选择有效的认证方式", + "AuthenticationMethodHelpTextWarning": "请选择一个有效的身份验证方式", "AuthenticationMethod": "认证方式", - "AuthenticationRequiredPasswordHelpTextWarning": "输入新密码", - "AuthenticationRequiredUsernameHelpTextWarning": "输入新用户名", + "AuthenticationRequiredPasswordHelpTextWarning": "请输入新密码", + "AuthenticationRequiredUsernameHelpTextWarning": "请输入新用户名", "DailyEpisodeFormat": "每日单集格式", "MediaManagementSettingsSummary": "命名,文件管理和根文件夹设置", "ReplaceIllegalCharacters": "替换非法字符", @@ -814,7 +814,7 @@ "HistorySeason": "查看本季历史记录", "ImportScriptPathHelpText": "用于导入的脚本的路径", "IncludeCustomFormatWhenRenaming": "重命名时包含自定义格式", - "RemotePathMappingsInfo": "很少需要远程路径映射,如果{app}和您的下载客户端在同一系统上,最好匹配您的路径。有关详细信息,请参阅[wiki]({wikiLink})", + "RemotePathMappingsInfo": "很少需要远程路径映射,如果{app}和您的下载客户端在同一系统上,则最好匹配您的路径。更多信息,请参阅[wiki]({wikiLink})", "IndexerPriorityHelpText": "索引器优先级从1(最高)到50(最低),默认25。当资源连接中断时寻找同等资源时使用,否则{appName}将依旧使用已启用的索引器进行RSS同步并搜索", "IndexerTagHelpText": "仅对至少有一个匹配标记的剧集使用此索引器。留空则适用于所有剧集。", "InteractiveImportNoFilesFound": "在所选文件夹中找不到视频文件", @@ -831,7 +831,7 @@ "ListQualityProfileHelpText": "质量配置列表项将添加", "SeriesIndexFooterMissingMonitored": "缺失集(剧集被监控)", "SeriesIsMonitored": "剧集被监控", - "SearchForCutoffUnmet": "搜索所有未达截止条件的集", + "SearchForCutoffUnmet": "搜索所有Cutoff Unmet的剧集", "SeriesProgressBarText": "{episodeFileCount} / {episodeCount} (总计: {totalEpisodeCount}, 下载中: {downloadingCount})", "SeriesTitleToExcludeHelpText": "要排除的剧集名称", "Umask750Description": "{octal} - 所有者写入,组读取", @@ -845,7 +845,7 @@ "SslCertPasswordHelpText": "pfx文件密码", "UpgradeUntilCustomFormatScore": "升级直到自定义格式分数满足", "MonitorExistingEpisodes": "现有集", - "StopSelecting": "停止选中", + "StopSelecting": "停止选择", "MonitorFirstSeasonDescription": "监控第一季的所有集。所有其他季都将被忽略", "SupportedCustomConditions": "{appName}支持针对以下发布属性的自定义条件。", "MonitorSpecials": "监控特别节目", @@ -868,7 +868,7 @@ "Yesterday": "昨天", "OnGrab": "抓取中", "PendingChangesStayReview": "留下检查更改", - "SearchForCutoffUnmetConfirmationCount": "你确定要搜索所有共 {totalRecords} 个未达截止条件的集?", + "SearchForCutoffUnmetConfirmationCount": "您确定要搜索所有{totalRecords}Cutoff Unmet的剧集吗?", "IncludeHealthWarnings": "包含健康度警告", "IndexerPriority": "索引器优先级", "IndexerOptionsLoadError": "无法加载索引器选项", @@ -885,7 +885,7 @@ "OnlyForBulkSeasonReleases": "仅适用于批量季发布", "OnlyTorrent": "只有torrent", "OnlyUsenet": "只有usenet", - "OrganizeNamingPattern": "命名规则: `{episodeFormat}`", + "OrganizeNamingPattern": "命名格式: \"{episodeFormat}\"", "Parse": "解析", "OrganizeSelectedSeriesModalHeader": "整理选定的剧集", "Original": "原始的", @@ -919,7 +919,7 @@ "AirsDateAtTimeOn": "{date} {time} 在 {networkLabel} 播出", "AirsTomorrowOn": "明天 {time} 在 {networkLabel} 播出", "Airs": "播出", - "CollapseAll": "收缩所有", + "CollapseAll": "全部收起", "CollapseMultipleEpisodes": "收缩多集", "CollapseMultipleEpisodesHelpText": "收缩同一天播出的多集", "Connection": "连接", @@ -927,14 +927,14 @@ "CountSelectedFile": "{selectedCount} 选中的文件", "CountSelectedFiles": "{selectedCount}选中的文件", "CountSeriesSelected": "所中 {count} 个剧集", - "DeleteEpisodesFiles": "删除 {episodeFileCount} 个集文件", + "DeleteEpisodesFiles": "删除{episodeFileCount}个剧集文件", "DeleteEpisodesFilesHelpText": "删除集文件和剧集文件夹", "DeleteSeriesFolder": "删除剧集文件夹", "DeleteSeriesFolderConfirmation": "剧集文件夹 `{path}` 及所含内容将会被删除。", "DeleteSeriesFolderCountWithFilesConfirmation": "你确定要删除选中的 {count} 个剧集及其所含的所有文件吗?", "DeleteSeriesFolderEpisodeCount": "{episodeFileCount} 个集文件,总计 {size}", "DeleteSeriesFolders": "删除剧集文件夹", - "ExpandAll": "展开所有", + "ExpandAll": "展开全部", "False": "否", "FullColorEvents": "全彩事件", "FullColorEventsHelpText": "改变样式,用状态颜色为整个事件着色,而不仅仅是左边缘。不适用于议程", @@ -1035,23 +1035,23 @@ "OnRename": "重命名中", "OnSeriesAdd": "剧集添加时", "Or": "或", - "OrganizeModalHeaderSeason": "整理&重命名 - {season}", - "OrganizeNothingToRename": "成功!我的工作已完成,没有文件要重命名了。", + "OrganizeModalHeaderSeason": "整理并重命名 - {season}", + "OrganizeNothingToRename": "重命名成功!已没有需要重命名的文件。", "OrganizeRelativePaths": "所有路径都相对于: `{path}`", - "OrganizeRenamingDisabled": "重命名被禁用,没有东西可以重命名", + "OrganizeRenamingDisabled": "重命名已禁用,无需重命名", "OrganizeSelectedSeriesModalAlert": "提示:要预览重命名,请点击“取消”,然后点击任何剧集标题并使用此图标:", "OrganizeSelectedSeriesModalConfirmation": "你确定要整理 {count} 个选定剧集中的所有文件?", "OverrideAndAddToDownloadQueue": "覆盖并添加到下载队列", "OverrideGrabModalTitle": "覆盖并抓取 - {title}", - "OverrideGrabNoEpisode": "必须至少选择一集", - "OverrideGrabNoLanguage": "必须选择至少一种语言", + "OverrideGrabNoEpisode": "请选择至少一集", + "OverrideGrabNoLanguage": "请选择至少一种语言", "OverrideGrabNoQuality": "必须选择质量", "OverrideGrabNoSeries": "必须选择剧集", - "Overview": "概述", - "OverviewOptions": "概述选项", + "Overview": "概览", + "OverviewOptions": "概览选项", "ParseModalHelpText": "在上面的输入框中输入一个发行版标题", "ParseModalErrorParsing": "解析错误,请重试。", - "ParseModalUnableToParse": "无法解析提供的标题,请再试一次。", + "ParseModalUnableToParse": "无法解析提供的标题,请重试。", "Paused": "暂停", "Pending": "挂起", "PendingChangesMessage": "您有未保存的修改,确定要退出本页么?", @@ -1060,7 +1060,7 @@ "Port": "端口", "PortNumber": "端口号", "PosterOptions": "海报选项", - "PosterSize": "海报尺寸", + "PosterSize": "海报大小", "PreferProtocol": "首选 {preferredProtocol}", "PreferAndUpgrade": "首选并升级", "PreferTorrent": "首选Torrent", @@ -1068,8 +1068,8 @@ "Preferred": "首选的", "Presets": "预设", "PreferredSize": "首选影片大小", - "PreviewRename": "预览重命名", - "PreviewRenameSeason": "预览此季重命名", + "PreviewRename": "重命名预览", + "PreviewRenameSeason": "重命名此季的预览", "PreviousAiringDate": "上一次播出: {date}", "PrioritySettings": "优先级: {priority}", "ProfilesSettingsSummary": "质量、元数据、延迟、发行配置", @@ -1083,15 +1083,15 @@ "Reason": "原因", "RecyclingBin": "回收站", "RecyclingBinCleanupHelpTextWarning": "回收站中的文件在超出选择的天数后会被自动清理", - "RefreshAndScan": "刷新&扫描", + "RefreshAndScan": "刷新并扫描", "RefreshAndScanTooltip": "刷新信息并扫描磁盘", "ReleaseProfileIndexerHelpText": "指定配置文件应用于哪个索引器", "ReleaseProfileIndexerHelpTextWarning": "使用有发布配置的特定索引器可能会导致重复获取发布", "ReleaseRejected": "发布被拒绝", "ReleaseSceneIndicatorAssumingTvdb": "推测TVDB编号。", "ReleaseSceneIndicatorMappedNotRequested": "此搜索中未请求映射的剧集。", - "ReleaseSceneIndicatorSourceMessage": "{message} 发布时编号不明确,无法准确地识别集。", - "ReleaseSceneIndicatorUnknownSeries": "未知的集或剧集。", + "ReleaseSceneIndicatorSourceMessage": "{message}的版本中存在模糊的编号,无法正确地识别剧集。", + "ReleaseSceneIndicatorUnknownSeries": "未知的剧集或系列。", "RemotePathMappingLocalPathHelpText": "{appName}用于访问远程路径的本地路径", "RemoveFromBlocklist": "从黑名单中移除", "RemoveCompletedDownloadsHelpText": "从下载客户端记录中移除已导入的下载", @@ -1115,14 +1115,14 @@ "SceneInfo": "场景信息", "Search": "搜索", "SearchFailedError": "搜索失败,请稍后重试。", - "SearchForMonitoredEpisodesSeason": "搜索本季监控的集", + "SearchForMonitoredEpisodesSeason": "搜索所有监控的剧集", "Season": "季", "SeasonDetails": "季详情", "SeasonFinale": "季完结", "SeasonInformation": "季信息", "SeasonNumberToken": "季 {seasonNumber}", - "SeasonPassEpisodesDownloaded": "{episodeFileCount}/{totalEpisodeCount} 集已下载", - "SeasonPassTruncated": "只显示最新的25季,查看详情获取所有季信息", + "SeasonPassEpisodesDownloaded": "{episodeFileCount}/{totalEpisodeCount} 的剧集被下载", + "SeasonPassTruncated": "只显示最新的25季,点击详情查看所有的季", "SeasonPremieresOnly": "仅限季首播", "SelectDropdown": "选择...", "SelectEpisodesModalTitle": "{modalTitle} - 选择剧集", @@ -1140,16 +1140,16 @@ "SeriesMonitoring": "剧集监控中", "SeriesPremiere": "剧集首播", "ShortDateFormat": "短日期格式", - "ShowEpisodes": "显示集", + "ShowEpisodes": "显示剧集", "ShowMonitored": "显示监控中的", "ShowMonitoredHelpText": "在海报下显示监控状态", - "ShowNetwork": "显示电视网", + "ShowNetwork": "显示网络", "ShowPreviousAiring": "显示上一次播出", "ShowQualityProfileHelpText": "在海报下方显示媒体质量配置", - "ShowQualityProfile": "显示媒体质量配置", - "ShowSearch": "显示搜索按钮", - "ShowSearchHelpText": "在选项中显示搜索框", - "ShowSeasonCount": "显示季计数", + "ShowQualityProfile": "显示质量配置文件", + "ShowSearch": "显示搜索", + "ShowSearchHelpText": "悬停时显示搜索按钮", + "ShowSeasonCount": "显示季数", "ShowTitle": "显示标题", "ShowTitleHelpText": "在海报下显示剧集标题", "ShowUnknownSeriesItems": "实现未知剧集项目", @@ -1195,9 +1195,9 @@ "UnmonitorSpecialsDescription": "取消监控所有特别节目而不改变其他集的监控状态", "UnmonitorDeletedEpisodesHelpText": "从磁盘删除的集将在 {appName} 中自动取消监控", "UnmonitorSpecials": "取消监控特别节目", - "UpdateAll": "全部更新", + "UpdateAll": "更新全部", "UpdateAutomaticallyHelpText": "自动下载并安装更新。你还可以在“系统:更新”中安装", - "UpdateSelected": "更新已选", + "UpdateSelected": "更新选择的内容", "UpgradeUntilThisQualityIsMetOrExceeded": "升级直到影片质量超出或者满足", "UpgradesAllowed": "允许升级", "UpgradesAllowedHelpText": "如关闭,则质量不做升级", @@ -1261,7 +1261,7 @@ "OpenSeries": "打开剧集", "OptionalName": "可选名称", "Organize": "整理", - "OrganizeLoadError": "读取预告片错误", + "OrganizeLoadError": "载入预览时出错", "FormatAgeHour": "小时", "FormatAgeHours": "小时", "FormatAgeMinute": "分钟", @@ -1331,7 +1331,7 @@ "RestartLater": "稍后重启", "RestrictionsLoadError": "无法加载限制条件", "RssSync": "RSS同步", - "SearchForAllMissingConfirmationCount": "你确定要搜索所有共 {totalRecords} 缺失的集?", + "SearchForAllMissingConfirmationCount": "您确定要搜索所有{totalRecords}缺失的剧集吗?", "SearchIsNotSupportedWithThisIndexer": "该索引器不支持搜索", "Security": "安全", "SelectFolder": "选择文件夹", @@ -1364,7 +1364,7 @@ "UseHardlinksInsteadOfCopy": "使用硬链接代替复制", "UsenetDelay": "Usenet延时", "UsenetDelayHelpText": "延迟几分钟才能等待从Usenet获取发布", - "OrganizeModalHeader": "整理&重命名", + "OrganizeModalHeader": "整理并重命名", "RssSyncIntervalHelpText": "间隔时间以分钟为单位,设置为0则关闭该功能(会停止所有剧集的自动抓取下载)", "RssSyncIntervalHelpTextWarning": "这将适用于所有索引器,请遵循他们所制定的规则", "SceneNumberNotVerified": "场景编号未确认", @@ -1373,7 +1373,7 @@ "AgeWhenGrabbed": "年龄(在被抓取后)", "BypassDelayIfAboveCustomFormatScoreMinimumScoreHelpText": "绕过首选协议延迟所需的最小自定义格式分数", "BypassDelayIfAboveCustomFormatScoreHelpText": "当发布版本的评分高于配置的最小自定义格式评分时,跳过延时", - "SearchForAllMissing": "搜索所有缺失的集", + "SearchForAllMissing": "搜索所有缺失的剧集", "SearchForMissing": "搜索缺少", "SearchForQuery": "搜索{query}", "ImportUsingScriptHelpText": "使用脚本复制文件以进行导入(例如用于转码)", @@ -1414,7 +1414,7 @@ "TorrentDelayHelpText": "延迟几分钟等待获取torrent", "Underscore": "下划线", "SelectAll": "选择全部", - "SelectDownloadClientModalTitle": "{modalTitle} -选择下载客户端", + "SelectDownloadClientModalTitle": "{modalTitle} - 选择下载客户端", "WantMoreControlAddACustomFormat": "想要更好地控制首选下载吗?添加[自定义格式](/settings/customformats)", "WeekColumnHeader": "日期格式", "WaitingToImport": "等待导入", @@ -1422,10 +1422,10 @@ "SendAnonymousUsageData": "发送匿名使用数据", "UnmonitorSelected": "取消监控选中的", "UnsavedChanges": "未保存更改", - "UnselectAll": "全不选", + "UnselectAll": "取消选择全部", "UpcomingSeriesDescription": "剧集已宣布,但尚未确定具体的播出日期", - "UpdateFiltered": "更新过滤", - "UpdateMonitoring": "更新监控中的", + "UpdateFiltered": "更新已过滤的内容", + "UpdateMonitoring": "更新监控的内容", "IndexerSettings": "索引器设置", "LogLevelTraceHelpTextWarning": "追踪日志只应该暂时启用", "Logging": "日志记录中", @@ -1441,16 +1441,16 @@ "Monitor": "是否监控", "NotificationTriggersHelpText": "选择触发此通知的事件", "ImportListsSettingsSummary": "从另一个{appName}或Trakt列表导入并管理排除列表", - "ParseModalHelpTextDetails": "{appName}将尝试解析标题并显示有关标题的详细信息", + "ParseModalHelpTextDetails": "{appName}将尝试解析标题并向显示详细信息", "Proxy": "代理", "ImportScriptPath": "导入脚本路径", "IncludeCustomFormatWhenRenamingHelpText": "在 {Custom Formats} 中包含重命名格式", - "ShowSizeOnDisk": "显示占用磁盘体积", + "ShowSizeOnDisk": "显示已用空间", "SingleEpisode": "单集", "SmartReplace": "智能替换", "ShowBanners": "显示横幅", - "ShowBannersHelpText": "显示横幅而不是名称", - "ShowDateAdded": "显示添加日期", + "ShowBannersHelpText": "显示横幅而不是标题", + "ShowDateAdded": "显示加入时间", "ShowEpisodeInformation": "显示集信息", "ShowEpisodeInformationHelpText": "显示集号和标题", "ShowPath": "显示路径", @@ -1465,7 +1465,7 @@ "TablePageSizeMinimum": "页面大小必须至少为 {minimumValue}", "TorrentDelayTime": "Torrent延时:{torrentDelay}", "Torrents": "种子", - "TotalFileSize": "总文件体积", + "TotalFileSize": "文件总大小", "TotalRecords": "记录总数: {totalRecords}", "Trace": "追踪", "CutoffUnmetLoadError": "加载未达截止条件项目错误", @@ -1478,7 +1478,7 @@ "EditSeriesModalHeader": "编辑 - {title}", "DetailedProgressBar": "详细的进度条", "EndedSeriesDescription": "剧集已完结,预计不会有其他集或季", - "EpisodeFilesLoadError": "无法加载集文件", + "EpisodeFilesLoadError": "无法加载剧集文件", "Files": "文件", "SeriesDetailsOneEpisodeFile": "1个集文件", "UrlBase": "基本URL", @@ -1487,5 +1487,6 @@ "AutoRedownloadFailed": "重新下载失败", "AutoRedownloadFailedFromInteractiveSearch": "从交互式搜索中重新下载失败", "AutoRedownloadFailedFromInteractiveSearchHelpText": "当从交互式搜索中获取失败的版本时,自动搜索并尝试下载其他版本", - "ImportListSearchForMissingEpisodes": "搜索缺失集" + "ImportListSearchForMissingEpisodes": "搜索缺失集", + "QueueFilterHasNoItems": "选定的队列过滤器没有项目" } From 6e2162ebf4b3366df540bd35ad91bd5527976728 Mon Sep 17 00:00:00 2001 From: Stevie Robinson <stevie.robinson@gmail.com> Date: Sat, 28 Oct 2023 23:56:18 +0200 Subject: [PATCH 084/136] New: Relative path as default Sort order on Manual Import --- frontend/src/Store/Actions/interactiveImportActions.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/Store/Actions/interactiveImportActions.js b/frontend/src/Store/Actions/interactiveImportActions.js index 28ad0f220..789fa7464 100644 --- a/frontend/src/Store/Actions/interactiveImportActions.js +++ b/frontend/src/Store/Actions/interactiveImportActions.js @@ -28,8 +28,8 @@ export const defaultState = { error: null, items: [], originalItems: [], - sortKey: 'quality', - sortDirection: sortDirections.DESCENDING, + sortKey: 'relativePath', + sortDirection: sortDirections.ASCENDING, recentFolders: [], importMode: 'chooseImportMode', sortPredicates: { From aa8659eecd0a0fedd56eb1066730d1d1ec32df0d Mon Sep 17 00:00:00 2001 From: Bogdan <mynameisbogdan@users.noreply.github.com> Date: Sun, 29 Oct 2023 18:44:42 +0200 Subject: [PATCH 085/136] Fix full disk releases regex for DVDs at the end of the title --- .../DecisionEngine/Specifications/RawDiskSpecification.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/RawDiskSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/RawDiskSpecification.cs index e62e68edc..ed4fb1545 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/RawDiskSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/RawDiskSpecification.cs @@ -13,7 +13,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications { new Regex(@"(?:dis[ck])(?:[-_. ]\d+[-_. ])(?:(?:(?:480|720|1080|2160)[ip]|)[-_. ])?(?:Blu\-?ray)", RegexOptions.Compiled | RegexOptions.IgnoreCase), new Regex(@"(?:(?:480|720|1080|2160)[ip]|)[-_. ](?:full)[-_. ](?:Blu\-?ray)", RegexOptions.Compiled | RegexOptions.IgnoreCase), - new Regex(@"(?:\d?x?M?DVD-?[R59])[ ._]", RegexOptions.Compiled | RegexOptions.IgnoreCase) + new Regex(@"(?:\d?x?M?DVD-?[R59])(?:[ ._]|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase) }; private static readonly string[] _dvdContainerTypes = new[] { "vob", "iso" }; From 58d841c463854cc46d8feb6dfb1b784f4610b1a5 Mon Sep 17 00:00:00 2001 From: Mark McDowall <mark@mcdowall.ca> Date: Sat, 28 Oct 2023 15:31:17 -0700 Subject: [PATCH 086/136] Fixed: Searching for series with XEM and TVDB mapping overrides --- .../ReleaseSearchServiceFixture.cs | 40 +++++++++++++++++++ .../IndexerSearch/ReleaseSearchService.cs | 2 +- 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/src/NzbDrone.Core.Test/IndexerSearchTests/ReleaseSearchServiceFixture.cs b/src/NzbDrone.Core.Test/IndexerSearchTests/ReleaseSearchServiceFixture.cs index a2b77ab7d..522d92f9d 100644 --- a/src/NzbDrone.Core.Test/IndexerSearchTests/ReleaseSearchServiceFixture.cs +++ b/src/NzbDrone.Core.Test/IndexerSearchTests/ReleaseSearchServiceFixture.cs @@ -568,5 +568,45 @@ namespace NzbDrone.Core.Test.IndexerSearchTests allCriteria.First().Should().BeOfType<SeasonSearchCriteria>(); allCriteria.First().As<SeasonSearchCriteria>().SeasonNumber.Should().Be(7); } + + [Test] + public async Task episode_search_should_use_all_available_numbering_from_services_and_xem() + { + WithEpisode(1, 12, 2, 3); + + Mocker.GetMock<ISceneMappingService>() + .Setup(s => s.FindByTvdbId(It.IsAny<int>())) + .Returns(new List<SceneMapping> + { + new SceneMapping + { + TvdbId = _xemSeries.TvdbId, + SearchTerm = _xemSeries.Title, + ParseTerm = _xemSeries.Title, + FilterRegex = "(?i)-(BTN)$", + SeasonNumber = 1, + SceneSeasonNumber = 1, + SceneOrigin = "tvdb", + Type = "ServicesProvider" + } + }); + + var allCriteria = WatchForSearchCriteria(); + + await Subject.EpisodeSearch(_xemEpisodes.First(), false, false); + + Mocker.GetMock<ISceneMappingService>() + .Verify(v => v.FindByTvdbId(_xemSeries.Id), Times.Once()); + + allCriteria.Should().HaveCount(2); + + allCriteria.First().Should().BeOfType<SingleEpisodeSearchCriteria>(); + allCriteria.First().As<SingleEpisodeSearchCriteria>().SeasonNumber.Should().Be(1); + allCriteria.First().As<SingleEpisodeSearchCriteria>().EpisodeNumber.Should().Be(12); + + allCriteria.Last().Should().BeOfType<SingleEpisodeSearchCriteria>(); + allCriteria.Last().As<SingleEpisodeSearchCriteria>().SeasonNumber.Should().Be(2); + allCriteria.Last().As<SingleEpisodeSearchCriteria>().EpisodeNumber.Should().Be(3); + } } } diff --git a/src/NzbDrone.Core/IndexerSearch/ReleaseSearchService.cs b/src/NzbDrone.Core/IndexerSearch/ReleaseSearchService.cs index de905efed..97420fb65 100644 --- a/src/NzbDrone.Core/IndexerSearch/ReleaseSearchService.cs +++ b/src/NzbDrone.Core/IndexerSearch/ReleaseSearchService.cs @@ -265,7 +265,7 @@ namespace NzbDrone.Core.IndexerSearch } } - if (sceneMapping.ParseTerm == series.CleanTitle && sceneMapping.FilterRegex.IsNotNullOrWhiteSpace()) + if (sceneMapping.ParseTerm == series.CleanTitle && sceneMapping.FilterRegex.IsNullOrWhiteSpace()) { // Disable the implied mapping if we have an explicit mapping by the same name includeGlobal = false; From 44d8dbaac81706691124ae5f8317289f0a3e5d73 Mon Sep 17 00:00:00 2001 From: Mark McDowall <mark@mcdowall.ca> Date: Sun, 29 Oct 2023 12:14:02 -0700 Subject: [PATCH 087/136] Fixed: Blocking unknown indexers from pushed releases Closes #5900 --- .../IndexerTests/IndexerStatusServiceFixture.cs | 11 +++++++++++ .../Status/ProviderStatusServiceBase.cs | 5 +++++ 2 files changed, 16 insertions(+) diff --git a/src/NzbDrone.Core.Test/IndexerTests/IndexerStatusServiceFixture.cs b/src/NzbDrone.Core.Test/IndexerTests/IndexerStatusServiceFixture.cs index 55ed8abfb..f836852e1 100644 --- a/src/NzbDrone.Core.Test/IndexerTests/IndexerStatusServiceFixture.cs +++ b/src/NzbDrone.Core.Test/IndexerTests/IndexerStatusServiceFixture.cs @@ -68,5 +68,16 @@ namespace NzbDrone.Core.Test.IndexerTests VerifyNoUpdate(); } + + [Test] + public void should_not_record_failure_for_unknown_provider() + { + Subject.RecordFailure(0); + + Mocker.GetMock<IIndexerStatusRepository>() + .Verify(v => v.FindByProviderId(1), Times.Never); + + VerifyNoUpdate(); + } } } diff --git a/src/NzbDrone.Core/ThingiProvider/Status/ProviderStatusServiceBase.cs b/src/NzbDrone.Core/ThingiProvider/Status/ProviderStatusServiceBase.cs index 15cf84d3b..40c353b24 100644 --- a/src/NzbDrone.Core/ThingiProvider/Status/ProviderStatusServiceBase.cs +++ b/src/NzbDrone.Core/ThingiProvider/Status/ProviderStatusServiceBase.cs @@ -79,6 +79,11 @@ namespace NzbDrone.Core.ThingiProvider.Status protected virtual void RecordFailure(int providerId, TimeSpan minimumBackOff, bool escalate) { + if (providerId <= 0) + { + return; + } + lock (_syncRoot) { var status = GetProviderStatus(providerId); From d484553b310af4257c841c37a503ef54650c1426 Mon Sep 17 00:00:00 2001 From: Mark McDowall <mark@mcdowall.ca> Date: Sun, 29 Oct 2023 14:19:47 -0700 Subject: [PATCH 088/136] New: Support SABnzb's new format for sorters Closes #6125 --- .../SabnzbdTests/SabnzbdFixture.cs | 46 +++++++++++++++++++ .../Download/Clients/Sabnzbd/Sabnzbd.cs | 10 ++++ .../Clients/Sabnzbd/SabnzbdCategory.cs | 20 ++++++++ 3 files changed, 76 insertions(+) diff --git a/src/NzbDrone.Core.Test/Download/DownloadClientTests/SabnzbdTests/SabnzbdFixture.cs b/src/NzbDrone.Core.Test/Download/DownloadClientTests/SabnzbdTests/SabnzbdFixture.cs index a0c8b656b..b81633b51 100644 --- a/src/NzbDrone.Core.Test/Download/DownloadClientTests/SabnzbdTests/SabnzbdFixture.cs +++ b/src/NzbDrone.Core.Test/Download/DownloadClientTests/SabnzbdTests/SabnzbdFixture.cs @@ -543,6 +543,52 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests result.HasWarnings.Should().BeTrue(); } + [Test] + public void should_test_success_if_sorters_are_empty() + { + _config.Misc.enable_tv_sorting = false; + _config.Misc.tv_categories = null; + _config.Sorters = new List<SabnzbdSorter>(); + + var result = new NzbDroneValidationResult(Subject.Test()); + + result.IsValid.Should().BeTrue(); + } + + [Test] + public void should_test_failed_if_sorter_is_enabled_for_non_tv_category() + { + _config.Misc.enable_tv_sorting = false; + _config.Misc.tv_categories = null; + _config.Sorters = Builder<SabnzbdSorter>.CreateListOfSize(1) + .All() + .With(s => s.is_active = true) + .With(s => s.sort_cats = new List<string> { "tv-custom" }) + .Build() + .ToList(); + + var result = new NzbDroneValidationResult(Subject.Test()); + + result.IsValid.Should().BeTrue(); + } + + [Test] + public void should_test_failed_if_sorter_is_enabled_for_tv_category() + { + _config.Misc.enable_tv_sorting = false; + _config.Misc.tv_categories = null; + _config.Sorters = Builder<SabnzbdSorter>.CreateListOfSize(1) + .All() + .With(s => s.is_active = true) + .With(s => s.sort_cats = new List<string> { "tv" }) + .Build() + .ToList(); + + var result = new NzbDroneValidationResult(Subject.Test()); + + result.IsValid.Should().BeFalse(); + } + [Test] public void should_test_success_if_tv_sorting_disabled() { diff --git a/src/NzbDrone.Core/Download/Clients/Sabnzbd/Sabnzbd.cs b/src/NzbDrone.Core/Download/Clients/Sabnzbd/Sabnzbd.cs index 00bcf5cac..95f8942aa 100644 --- a/src/NzbDrone.Core/Download/Clients/Sabnzbd/Sabnzbd.cs +++ b/src/NzbDrone.Core/Download/Clients/Sabnzbd/Sabnzbd.cs @@ -482,6 +482,16 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd } } + // New in SABnzbd 4.1, but on older versions this will be empty and not apply + if (config.Sorters.Any(s => s.is_active && ContainsCategory(s.sort_cats, Settings.TvCategory))) + { + return new NzbDroneValidationFailure("TvCategory", "Disable TV Sorting") + { + InfoLink = _proxy.GetBaseUrl(Settings, "config/sorting/"), + DetailedDescription = "You must disable sorting for the category Sonarr uses to prevent import issues. Go to Sabnzbd to fix it." + }; + } + if (config.Misc.enable_tv_sorting && ContainsCategory(config.Misc.tv_categories, Settings.TvCategory)) { return new NzbDroneValidationFailure("TvCategory", "Disable TV Sorting") diff --git a/src/NzbDrone.Core/Download/Clients/Sabnzbd/SabnzbdCategory.cs b/src/NzbDrone.Core/Download/Clients/Sabnzbd/SabnzbdCategory.cs index 189b08257..aa04edc5d 100644 --- a/src/NzbDrone.Core/Download/Clients/Sabnzbd/SabnzbdCategory.cs +++ b/src/NzbDrone.Core/Download/Clients/Sabnzbd/SabnzbdCategory.cs @@ -11,11 +11,13 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd { Categories = new List<SabnzbdCategory>(); Servers = new List<object>(); + Sorters = new List<SabnzbdSorter>(); } public SabnzbdConfigMisc Misc { get; set; } public List<SabnzbdCategory> Categories { get; set; } public List<object> Servers { get; set; } + public List<SabnzbdSorter> Sorters { get; set; } } public class SabnzbdConfigMisc @@ -42,4 +44,22 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd public OsPath FullPath { get; set; } } + + public class SabnzbdSorter + { + public SabnzbdSorter() + { + sort_cats = new List<string>(); + sort_type = new List<int>(); + } + + public string name { get; set; } + public int order { get; set; } + public string min_size { get; set; } + public string multipart_label { get; set; } + public string sort_string { get; set; } + public List<string> sort_cats { get; set; } + public List<int> sort_type { get; set; } + public bool is_active { get; set; } + } } From 165e3dbae8c9ba478d4aac4bd1b8514f54f0dfec Mon Sep 17 00:00:00 2001 From: Mark McDowall <mark@mcdowall.ca> Date: Sun, 29 Oct 2023 16:27:30 -0700 Subject: [PATCH 089/136] New: Parsing for titles with multiple translated titles separated by '/' Closes #6137 --- .../ParserTests/MultiEpisodeParserFixture.cs | 1 + .../ParserTests/ParserFixture.cs | 7 +++++++ .../ParserTests/SingleEpisodeParserFixture.cs | 1 + src/NzbDrone.Core/Parser/Parser.cs | 15 ++++++++++++--- 4 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/NzbDrone.Core.Test/ParserTests/MultiEpisodeParserFixture.cs b/src/NzbDrone.Core.Test/ParserTests/MultiEpisodeParserFixture.cs index 3fbd06f34..6af9a1733 100644 --- a/src/NzbDrone.Core.Test/ParserTests/MultiEpisodeParserFixture.cs +++ b/src/NzbDrone.Core.Test/ParserTests/MultiEpisodeParserFixture.cs @@ -75,6 +75,7 @@ namespace NzbDrone.Core.Test.ParserTests [TestCase("13 Series Se.1 afl.2-3-4 [VTM]", "13 Series", 1, new[] { 2, 3, 4 })] [TestCase("Series T Se.3 afl.3 en 4", "Series T", 3, new[] { 3, 4 })] [TestCase("Series Title (S15E06-08) City Sushi", "Series Title", 15, new[] { 6, 7, 8 })] + [TestCase("Босх: Спадок (S2E1-4) / Series: Legacy (S2E1-4) (2023) WEB-DL 1080p Ukr/Eng | sub Eng", "Series: Legacy", 2, new[] { 1, 2, 3, 4 })] // [TestCase("", "", , new [] { })] public void should_parse_multiple_episodes(string postTitle, string title, int season, int[] episodes) diff --git a/src/NzbDrone.Core.Test/ParserTests/ParserFixture.cs b/src/NzbDrone.Core.Test/ParserTests/ParserFixture.cs index 91cf0e477..db1a61d3f 100644 --- a/src/NzbDrone.Core.Test/ParserTests/ParserFixture.cs +++ b/src/NzbDrone.Core.Test/ParserTests/ParserFixture.cs @@ -92,5 +92,12 @@ namespace NzbDrone.Core.Test.ParserTests var result = Parser.Parser.ParseTitle(path); result.ReleaseTitle.Should().Be(releaseTitle); } + + [TestCase("Босх: Спадок (S2E1) / Series: Legacy (S2E1) (2023) WEB-DL 1080p Ukr/Eng | sub Eng", "Босх: Спадок", "Series: Legacy")] + public void should_parse_multiple_series_titles(string postTitle, params string[] titles) + { + var seriesTitleInfo = Parser.Parser.ParseTitle(postTitle).SeriesTitleInfo; + seriesTitleInfo.AllTitles.Should().BeEquivalentTo(titles); + } } } diff --git a/src/NzbDrone.Core.Test/ParserTests/SingleEpisodeParserFixture.cs b/src/NzbDrone.Core.Test/ParserTests/SingleEpisodeParserFixture.cs index c7f4eb389..245fee1a6 100644 --- a/src/NzbDrone.Core.Test/ParserTests/SingleEpisodeParserFixture.cs +++ b/src/NzbDrone.Core.Test/ParserTests/SingleEpisodeParserFixture.cs @@ -164,6 +164,7 @@ namespace NzbDrone.Core.Test.ParserTests [TestCase("Series Title [HDTV][Cap.104](website.com).avi", "Series Title", 1, 4)] [TestCase("Series Title [HDTV][Cap.402](website.com).avi", "Series Title", 4, 2)] [TestCase("Series Title [HDTV 720p][Cap.101](website.com).mkv", "Series Title", 1, 1)] + [TestCase("Босх: Спадок (S2E1) / Series: Legacy (S2E1) (2023) WEB-DL 1080p Ukr/Eng | sub Eng", "Series: Legacy", 2, 1)] // [TestCase("", "", 0, 0)] public void should_parse_single_episode(string postTitle, string title, int seasonNumber, int episodeNumber) diff --git a/src/NzbDrone.Core/Parser/Parser.cs b/src/NzbDrone.Core/Parser/Parser.cs index 27f98b91c..8136f0f89 100644 --- a/src/NzbDrone.Core/Parser/Parser.cs +++ b/src/NzbDrone.Core/Parser/Parser.cs @@ -202,7 +202,7 @@ namespace NzbDrone.Core.Parser new Regex(@"^(?<title>.+?)(?:\W+S(?<season>(?<!\d+)(?:\d{1,2})(?!\d+))\W+(?:(?:(?:Part|Vol)\W?|(?<!\d+\W+)e)(?<seasonpart>\d{1,2}(?!\d+)))+)", RegexOptions.IgnoreCase | RegexOptions.Compiled), - // Anime - 4 digit absolute episode number + // Anime - 4 digit absolute episode number new Regex(@"^(?:\[(?<subgroup>.+?)\][-_. ]?)(?<title>.+?)[-_. ]+?(?<absoluteepisode>\d{4}(\.\d{1,2})?(?!\d+))", RegexOptions.IgnoreCase | RegexOptions.Compiled), @@ -254,6 +254,11 @@ namespace NzbDrone.Core.Parser new Regex(@"^(?<title>.+?)[-_. ]S(?<season>(?<!\d+)(?:\d{1,2}|\d{4})(?!\d+))(?:[-_. ]?[ex]?(?<episode>(?<!\d+)\d{1,2}(?!\d+)))+", RegexOptions.IgnoreCase | RegexOptions.Compiled), + // TODO: Before this + // Single or multi episode releases with multiple titles, each followed by season and episode numbers in brackets + new Regex(@"^(?<title>.*?)[ ._]\(S(?<season>(?<!\d+)\d{1,2}(?!\d+))(?:\W|_)?E?[ ._]?(?<episode>(?<!\d+)\d{1,2}(?!\d+))(?:-(?<episode>(?<!\d+)\d{1,2}(?!\d+)))?\)(?:[ ._]\/[ ._])(?<title>.*?)[ ._]\(", + RegexOptions.IgnoreCase | RegexOptions.Compiled), + // Single episode season or episode S1E1 or S1-E1 or S1.Ep1 or S01.Ep.01 new Regex(@"(?:.*(?:\""|^))(?<title>.*?)(?:\W?|_)S(?<season>(?<!\d+)\d{1,2}(?!\d+))(?:\W|_)?Ep?[ ._]?(?<episode>(?<!\d+)\d{1,2}(?!\d+))", RegexOptions.IgnoreCase | RegexOptions.Compiled), @@ -883,7 +888,7 @@ namespace NzbDrone.Core.Parser return title; } - private static SeriesTitleInfo GetSeriesTitleInfo(string title) + private static SeriesTitleInfo GetSeriesTitleInfo(string title, MatchCollection matchCollection) { var seriesTitleInfo = new SeriesTitleInfo(); seriesTitleInfo.Title = title; @@ -906,6 +911,10 @@ namespace NzbDrone.Core.Parser { seriesTitleInfo.AllTitles = matchComponents.Groups["title"].Captures.OfType<Capture>().Select(v => v.Value).ToArray(); } + else if (matchCollection[0].Groups["title"].Captures.Count > 1) + { + seriesTitleInfo.AllTitles = matchCollection[0].Groups["title"].Captures.Select(c => c.Value.Replace('.', ' ').Replace('_', ' ')).ToArray(); + } return seriesTitleInfo; } @@ -1112,7 +1121,7 @@ namespace NzbDrone.Core.Parser } result.SeriesTitle = seriesName; - result.SeriesTitleInfo = GetSeriesTitleInfo(result.SeriesTitle); + result.SeriesTitleInfo = GetSeriesTitleInfo(result.SeriesTitle, matchCollection); Logger.Debug("Episode Parsed. {0}", result); From 0a2090237a727231defdcea9f94a778570fc7044 Mon Sep 17 00:00:00 2001 From: Zach Nedwich <zach@znedw.com> Date: Fri, 10 Nov 2023 09:56:21 +1000 Subject: [PATCH 090/136] Remove extraneous $1 from translation --- src/NzbDrone.Core/Localization/Core/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NzbDrone.Core/Localization/Core/en.json b/src/NzbDrone.Core/Localization/Core/en.json index 47e36054a..fc59a7b89 100644 --- a/src/NzbDrone.Core/Localization/Core/en.json +++ b/src/NzbDrone.Core/Localization/Core/en.json @@ -1056,7 +1056,7 @@ "Reload": "Reload", "RemotePath": "Remote Path", "RemotePathMappingBadDockerPathHealthCheckMessage": "You are using docker; download client {downloadClientName} places downloads in {path} but this is not a valid {osName} path. Review your remote path mappings and download client settings.", - "RemotePathMappingDockerFolderMissingHealthCheckMessage": "You are using docker; download client $1{downloadClientName} places downloads in {path} but this directory does not appear to exist inside the container. Review your remote path mappings and container volume settings.", + "RemotePathMappingDockerFolderMissingHealthCheckMessage": "You are using docker; download client {downloadClientName} places downloads in {path} but this directory does not appear to exist inside the container. Review your remote path mappings and container volume settings.", "RemotePathMappingDownloadPermissionsHealthCheckMessage": "{appName} can see but not access downloaded episode {path}. Likely permissions error.", "RemotePathMappingFileRemovedHealthCheckMessage": "File {path} was removed part way through processing.", "RemotePathMappingFilesBadDockerPathHealthCheckMessage": "You are using docker; download client {downloadClientName} reported files in {path} but this is not a valid {osName} path. Review your remote path mappings and download client settings.", From 0496e728dc1e21b16b9ee2fee010de9707bdff85 Mon Sep 17 00:00:00 2001 From: Bogdan <mynameisbogdan@users.noreply.github.com> Date: Mon, 30 Oct 2023 23:53:48 +0200 Subject: [PATCH 091/136] New: Resolve download client by name using 'downloadClient' for pushed releases --- .../Indexers/ReleasePushController.cs | 30 +++++++++++++++++-- src/Sonarr.Api.V3/Indexers/ReleaseResource.cs | 3 ++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/Sonarr.Api.V3/Indexers/ReleasePushController.cs b/src/Sonarr.Api.V3/Indexers/ReleasePushController.cs index a8165af46..efd553793 100644 --- a/src/Sonarr.Api.V3/Indexers/ReleasePushController.cs +++ b/src/Sonarr.Api.V3/Indexers/ReleasePushController.cs @@ -21,6 +21,7 @@ namespace Sonarr.Api.V3.Indexers private readonly IMakeDownloadDecision _downloadDecisionMaker; private readonly IProcessDownloadDecisions _downloadDecisionProcessor; private readonly IIndexerFactory _indexerFactory; + private readonly IDownloadClientFactory _downloadClientFactory; private readonly Logger _logger; private static readonly object PushLock = new object(); @@ -28,6 +29,7 @@ namespace Sonarr.Api.V3.Indexers public ReleasePushController(IMakeDownloadDecision downloadDecisionMaker, IProcessDownloadDecisions downloadDecisionProcessor, IIndexerFactory indexerFactory, + IDownloadClientFactory downloadClientFactory, IQualityProfileService qualityProfileService, Logger logger) : base(qualityProfileService) @@ -35,6 +37,7 @@ namespace Sonarr.Api.V3.Indexers _downloadDecisionMaker = downloadDecisionMaker; _downloadDecisionProcessor = downloadDecisionProcessor; _indexerFactory = indexerFactory; + _downloadClientFactory = downloadClientFactory; _logger = logger; PostValidator.RuleFor(s => s.Title).NotEmpty(); @@ -57,6 +60,8 @@ namespace Sonarr.Api.V3.Indexers ResolveIndexer(info); + var downloadClientId = ResolveDownloadClientId(release); + DownloadDecision decision; lock (PushLock) @@ -65,12 +70,12 @@ namespace Sonarr.Api.V3.Indexers decision = decisions.FirstOrDefault(); - _downloadDecisionProcessor.ProcessDecision(decision, release.DownloadClientId).GetAwaiter().GetResult(); + _downloadDecisionProcessor.ProcessDecision(decision, downloadClientId).GetAwaiter().GetResult(); } if (decision?.RemoteEpisode.ParsedEpisodeInfo == null) { - throw new ValidationException(new List<ValidationFailure> { new ValidationFailure("Title", "Unable to parse", release.Title) }); + throw new ValidationException(new List<ValidationFailure> { new ("Title", "Unable to parse", release.Title) }); } return MapDecisions(new[] { decision }); @@ -110,5 +115,26 @@ namespace Sonarr.Api.V3.Indexers _logger.Debug("Push Release {0} not associated with an indexer.", release.Title); } } + + private int? ResolveDownloadClientId(ReleaseResource release) + { + var downloadClientId = release.DownloadClientId.GetValueOrDefault(); + + if (downloadClientId == 0 && release.DownloadClient.IsNotNullOrWhiteSpace()) + { + var downloadClient = _downloadClientFactory.All().FirstOrDefault(v => v.Name.EqualsIgnoreCase(release.DownloadClient)); + + if (downloadClient != null) + { + _logger.Debug("Push Release {0} associated with download client {1} - {2}.", release.Title, downloadClientId, release.DownloadClient); + + return downloadClient.Id; + } + + _logger.Debug("Push Release {0} not associated with known download client {1}.", release.Title, release.DownloadClient); + } + + return release.DownloadClientId; + } } } diff --git a/src/Sonarr.Api.V3/Indexers/ReleaseResource.cs b/src/Sonarr.Api.V3/Indexers/ReleaseResource.cs index 578e652f2..a15a1b234 100644 --- a/src/Sonarr.Api.V3/Indexers/ReleaseResource.cs +++ b/src/Sonarr.Api.V3/Indexers/ReleaseResource.cs @@ -85,6 +85,9 @@ namespace Sonarr.Api.V3.Indexers [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public int? DownloadClientId { get; set; } + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public string DownloadClient { get; set; } + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public bool? ShouldOverride { get; set; } } From e4b9086a7a55817ef83f093b444302bbdccee643 Mon Sep 17 00:00:00 2001 From: Bogdan <mynameisbogdan@users.noreply.github.com> Date: Fri, 10 Nov 2023 01:57:04 +0200 Subject: [PATCH 092/136] New: Sort root folders by path --- .../SelectFolder/ImportSeriesSelectFolderConnector.js | 3 ++- frontend/src/Components/Form/RootFolderSelectInputConnector.js | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/frontend/src/AddSeries/ImportSeries/SelectFolder/ImportSeriesSelectFolderConnector.js b/frontend/src/AddSeries/ImportSeries/SelectFolder/ImportSeriesSelectFolderConnector.js index 76bb2f340..1df231f4e 100644 --- a/frontend/src/AddSeries/ImportSeries/SelectFolder/ImportSeriesSelectFolderConnector.js +++ b/frontend/src/AddSeries/ImportSeries/SelectFolder/ImportSeriesSelectFolderConnector.js @@ -5,12 +5,13 @@ import React, { Component } from 'react'; import { connect } from 'react-redux'; import { createSelector } from 'reselect'; import { addRootFolder, fetchRootFolders } from 'Store/Actions/rootFolderActions'; +import createRootFoldersSelector from 'Store/Selectors/createRootFoldersSelector'; import createSystemStatusSelector from 'Store/Selectors/createSystemStatusSelector'; import ImportSeriesSelectFolder from './ImportSeriesSelectFolder'; function createMapStateToProps() { return createSelector( - (state) => state.rootFolders, + createRootFoldersSelector(), createSystemStatusSelector(), (rootFolders, systemStatus) => { return { diff --git a/frontend/src/Components/Form/RootFolderSelectInputConnector.js b/frontend/src/Components/Form/RootFolderSelectInputConnector.js index dc5930417..43581835f 100644 --- a/frontend/src/Components/Form/RootFolderSelectInputConnector.js +++ b/frontend/src/Components/Form/RootFolderSelectInputConnector.js @@ -3,6 +3,7 @@ import React, { Component } from 'react'; import { connect } from 'react-redux'; import { createSelector } from 'reselect'; import { addRootFolder } from 'Store/Actions/rootFolderActions'; +import createRootFoldersSelector from 'Store/Selectors/createRootFoldersSelector'; import translate from 'Utilities/String/translate'; import RootFolderSelectInput from './RootFolderSelectInput'; @@ -10,7 +11,7 @@ const ADD_NEW_KEY = 'addNew'; function createMapStateToProps() { return createSelector( - (state) => state.rootFolders, + createRootFoldersSelector(), (state, { value }) => value, (state, { includeMissingValue }) => includeMissingValue, (state, { includeNoChange }) => includeNoChange, From 6432f310e7bad911adafe8e44de5980d724debb5 Mon Sep 17 00:00:00 2001 From: Collin Heist <collinheist@gmail.com> Date: Thu, 9 Nov 2023 16:57:20 -0700 Subject: [PATCH 093/136] New: Add TvdbId to Webhook Episode payload --- src/NzbDrone.Core/Notifications/Webhook/WebhookEpisode.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/NzbDrone.Core/Notifications/Webhook/WebhookEpisode.cs b/src/NzbDrone.Core/Notifications/Webhook/WebhookEpisode.cs index 29fa466aa..ba247ce8f 100644 --- a/src/NzbDrone.Core/Notifications/Webhook/WebhookEpisode.cs +++ b/src/NzbDrone.Core/Notifications/Webhook/WebhookEpisode.cs @@ -19,6 +19,7 @@ namespace NzbDrone.Core.Notifications.Webhook AirDate = episode.AirDate; AirDateUtc = episode.AirDateUtc; SeriesId = episode.SeriesId; + TvdbId = episode.TvdbId; } public int Id { get; set; } @@ -29,5 +30,6 @@ namespace NzbDrone.Core.Notifications.Webhook public string AirDate { get; set; } public DateTime? AirDateUtc { get; set; } public int SeriesId { get; set; } + public int TvdbId { get; set; } } } From 10fbc4e25ad41abd30901254399b1a515d454bf4 Mon Sep 17 00:00:00 2001 From: Xetera <contact@xetera.dev> Date: Fri, 10 Nov 2023 02:58:38 +0300 Subject: [PATCH 094/136] New: Parsing of Turkish release names --- .../ParserTests/AbsoluteEpisodeNumberParserFixture.cs | 6 ++++++ src/NzbDrone.Core/Parser/Parser.cs | 3 +++ 2 files changed, 9 insertions(+) diff --git a/src/NzbDrone.Core.Test/ParserTests/AbsoluteEpisodeNumberParserFixture.cs b/src/NzbDrone.Core.Test/ParserTests/AbsoluteEpisodeNumberParserFixture.cs index e0cae8bf2..5dccae231 100644 --- a/src/NzbDrone.Core.Test/ParserTests/AbsoluteEpisodeNumberParserFixture.cs +++ b/src/NzbDrone.Core.Test/ParserTests/AbsoluteEpisodeNumberParserFixture.cs @@ -122,6 +122,12 @@ namespace NzbDrone.Core.Test.ParserTests [TestCase("[SubsPlease] Anime Title 100 S3 - 01 (1080p) [5A493522]", "Anime Title 100 S3", 1, 0, 0)] [TestCase("[CameEsp] Another Anime 100 - Another 100 Anime - 01 [720p][ESP-ENG][mkv]", "Another Anime 100 - Another 100 Anime", 1, 0, 0)] [TestCase("[SubsPlease] Another Anime 100 - Another 100 Anime - 01 (1080p) [4E6B4518].mkv", "Another Anime 100 - Another 100 Anime", 1, 0, 0)] + [TestCase("Some show 69. Blm (29.10.2023) 1080p WebDL #turkseed", "Some show", 69, 0, 0)] + [TestCase("Soap opera 01 BLM(01.11.2023) 1080p HDTV AC3 x264 TURG", "Soap opera", 1, 0, 0)] + [TestCase("Turkish show 60.Bolum (31.01.2023) 720p WebDL AAC H.264 - TURG", "Turkish show", 60, 0, 0)] + [TestCase("Different show 1. Bölüm (23.10.2023) 720p WebDL AAC H.264 - TURG", "Different show", 1, 0, 0)] + [TestCase("Dubbed show 79.BLM Sezon Finali(25.06.2023) 720p WEB-DL AAC2.0 H.264-TURG", "Dubbed show", 79, 0, 0)] + [TestCase("Exclusive BLM Documentary with no false positives EP03.1080p.AAC.x264", "Exclusive BLM Documentary with no false positives", 3, 0, 0)] // [TestCase("", "", 0, 0, 0)] public void should_parse_absolute_numbers(string postTitle, string title, int absoluteEpisodeNumber, int seasonNumber, int episodeNumber) diff --git a/src/NzbDrone.Core/Parser/Parser.cs b/src/NzbDrone.Core/Parser/Parser.cs index 8136f0f89..52c042e45 100644 --- a/src/NzbDrone.Core/Parser/Parser.cs +++ b/src/NzbDrone.Core/Parser/Parser.cs @@ -335,6 +335,9 @@ namespace NzbDrone.Core.Parser new Regex(@"^(?<title>.+?)?\W*(?<airyear>\d{4})[-_. ]+(?<airmonth>[0-1][0-9])[-_. ]+(?<airday>[0-3][0-9])(?![-_. ]+[0-3][0-9])", RegexOptions.IgnoreCase | RegexOptions.Compiled), + // Turkish tracker releases (01 BLM, 3. Blm, 04.Bolum, etc) + new Regex(@"^(?<title>.+?)[_. ](?<absoluteepisode>\d{1,4})(?:[_. ]+)(?:BLM|B[oö]l[uü]m)", RegexOptions.IgnoreCase | RegexOptions.Compiled), + // Episodes with airdate (04.28.2018) new Regex(@"^(?<title>.+?)?\W*(?<airmonth>[0-1][0-9])[-_. ]+(?<airday>[0-3][0-9])[-_. ]+(?<airyear>\d{4})(?!\d+)", RegexOptions.IgnoreCase | RegexOptions.Compiled), From 8c3a0ebabae163274b7587afe1f998edb39c5184 Mon Sep 17 00:00:00 2001 From: Bogdan <mynameisbogdan@users.noreply.github.com> Date: Mon, 6 Nov 2023 17:39:42 +0200 Subject: [PATCH 095/136] Fixed: Showing already imported episodes as downloading in Series index --- frontend/src/App/State/QueueAppState.ts | 34 +------------- .../Index/createSeriesQueueDetailsSelector.ts | 5 +- frontend/src/typings/Queue.ts | 46 +++++++++++++++++++ 3 files changed, 51 insertions(+), 34 deletions(-) create mode 100644 frontend/src/typings/Queue.ts diff --git a/frontend/src/App/State/QueueAppState.ts b/frontend/src/App/State/QueueAppState.ts index a2936109d..05d74acac 100644 --- a/frontend/src/App/State/QueueAppState.ts +++ b/frontend/src/App/State/QueueAppState.ts @@ -1,42 +1,10 @@ -import ModelBase from 'App/ModelBase'; -import Language from 'Language/Language'; -import { QualityModel } from 'Quality/Quality'; -import CustomFormat from 'typings/CustomFormat'; +import Queue from 'typings/Queue'; import AppSectionState, { AppSectionFilterState, AppSectionItemState, Error, } from './AppSectionState'; -export interface StatusMessage { - title: string; - messages: string[]; -} - -export interface Queue extends ModelBase { - languages: Language[]; - quality: QualityModel; - customFormats: CustomFormat[]; - size: number; - title: string; - sizeleft: number; - timeleft: string; - estimatedCompletionTime: string; - status: string; - trackedDownloadStatus: string; - trackedDownloadState: string; - statusMessages: StatusMessage[]; - errorMessage: string; - downloadId: string; - protocol: string; - downloadClient: string; - outputPath: string; - episodeHasFile: boolean; - seriesId?: number; - episodeId?: number; - seasonNumber?: number; -} - export interface QueueDetailsAppState extends AppSectionState<Queue> { params: unknown; } diff --git a/frontend/src/Series/Index/createSeriesQueueDetailsSelector.ts b/frontend/src/Series/Index/createSeriesQueueDetailsSelector.ts index 66143ad2c..0b194161a 100644 --- a/frontend/src/Series/Index/createSeriesQueueDetailsSelector.ts +++ b/frontend/src/Series/Index/createSeriesQueueDetailsSelector.ts @@ -15,7 +15,10 @@ function createSeriesQueueDetailsSelector( (queueItems) => { return queueItems.reduce( (acc: SeriesQueueDetails, item) => { - if (item.seriesId !== seriesId) { + if ( + item.trackedDownloadState === 'imported' || + item.seriesId !== seriesId + ) { return acc; } diff --git a/frontend/src/typings/Queue.ts b/frontend/src/typings/Queue.ts new file mode 100644 index 000000000..855173306 --- /dev/null +++ b/frontend/src/typings/Queue.ts @@ -0,0 +1,46 @@ +import ModelBase from 'App/ModelBase'; +import Language from 'Language/Language'; +import { QualityModel } from 'Quality/Quality'; +import CustomFormat from 'typings/CustomFormat'; + +export type QueueTrackedDownloadStatus = 'ok' | 'warning' | 'error'; + +export type QueueTrackedDownloadState = + | 'downloading' + | 'importPending' + | 'importing' + | 'imported' + | 'failedPending' + | 'failed' + | 'ignored'; + +export interface StatusMessage { + title: string; + messages: string[]; +} + +interface Queue extends ModelBase { + languages: Language[]; + quality: QualityModel; + customFormats: CustomFormat[]; + size: number; + title: string; + sizeleft: number; + timeleft: string; + estimatedCompletionTime: string; + status: string; + trackedDownloadStatus: QueueTrackedDownloadStatus; + trackedDownloadState: QueueTrackedDownloadState; + statusMessages: StatusMessage[]; + errorMessage: string; + downloadId: string; + protocol: string; + downloadClient: string; + outputPath: string; + episodeHasFile: boolean; + seriesId?: number; + episodeId?: number; + seasonNumber?: number; +} + +export default Queue; From 2e51b8792db0d3ec402672dc92c95f3cb886ef44 Mon Sep 17 00:00:00 2001 From: Stevie Robinson <stevie.robinson@gmail.com> Date: Fri, 10 Nov 2023 01:00:54 +0100 Subject: [PATCH 096/136] Fixed: Replacing 'appName' translation token --- frontend/src/Utilities/String/translate.ts | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/frontend/src/Utilities/String/translate.ts b/frontend/src/Utilities/String/translate.ts index 7d59a22d1..76ab770ea 100644 --- a/frontend/src/Utilities/String/translate.ts +++ b/frontend/src/Utilities/String/translate.ts @@ -25,15 +25,13 @@ export async function fetchTranslations(): Promise<boolean> { export default function translate( key: string, - tokens: Record<string, string | number | boolean> = { appName: 'Sonarr' } + tokens: Record<string, string | number | boolean> = {} ) { const translation = translations[key] || key; - if (tokens) { - return translation.replace(/\{([a-z0-9]+?)\}/gi, (match, tokenMatch) => - String(tokens[tokenMatch] ?? match) - ); - } + tokens.appName = 'Sonarr'; - return translation; + return translation.replace(/\{([a-z0-9]+?)\}/gi, (match, tokenMatch) => + String(tokens[tokenMatch] ?? match) + ); } From eec862d3ff023f670c027d2a2a39181e16e3cb14 Mon Sep 17 00:00:00 2001 From: Weblate <noreply@weblate.org> Date: Tue, 7 Nov 2023 22:58:02 +0000 Subject: [PATCH 097/136] Multiple Translations updated by Weblate ignore-downstream Co-authored-by: Baptiste Mongin <baptiste.mongin@gmail.com> Co-authored-by: Francisco Cachado <franciscomcachado@gmail.com> Co-authored-by: Havok Dan <havokdan@yahoo.com.br> Co-authored-by: Javier Parada <jparada@gmail.com> Co-authored-by: Nesego <nesego@gmail.com> Co-authored-by: RicardoVelaC <ricardovelac@gmail.com> Co-authored-by: Ruben Lourenco <ruben.lourenco01@gmail.com> Co-authored-by: Weblate <noreply@weblate.org> Co-authored-by: bowsefather <husseinali39@gmail.com> Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/es/ Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/fr/ Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/pt/ Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/pt_BR/ Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/tr/ Translation: Servarr/Sonarr --- src/NzbDrone.Core/Localization/Core/es.json | 45 +++++++++++++- src/NzbDrone.Core/Localization/Core/fr.json | 2 +- src/NzbDrone.Core/Localization/Core/pt.json | 60 +++++++++++++++++-- .../Localization/Core/pt_BR.json | 24 ++++---- src/NzbDrone.Core/Localization/Core/tr.json | 6 +- 5 files changed, 114 insertions(+), 23 deletions(-) diff --git a/src/NzbDrone.Core/Localization/Core/es.json b/src/NzbDrone.Core/Localization/Core/es.json index 1cfc8317b..bfa448698 100644 --- a/src/NzbDrone.Core/Localization/Core/es.json +++ b/src/NzbDrone.Core/Localization/Core/es.json @@ -73,7 +73,7 @@ "Style": "Estilo", "System": "Sistema", "Tasks": "Tareas", - "Trace": "Trace", + "Trace": "Rastro", "TvdbId": "TVDB ID", "Torrents": "Torrents", "Ui": "UI", @@ -129,7 +129,7 @@ "Episode": "Episodio", "Activity": "Actividad", "AddNew": "Añadir Nuevo", - "ApplyTagsHelpTextAdd": "Añadir: Añadir a las etiquetas la lista existente de etiquetas", + "ApplyTagsHelpTextAdd": "Añadir: Añadir las etiquetas la lista existente de etiquetas", "ApplyTagsHelpTextRemove": "Eliminar: Eliminar las etiquetas introducidas", "Blocklist": "Bloqueadas", "Grabbed": "Añadido", @@ -228,5 +228,44 @@ "AddDownloadClientImplementation": "Añadir Cliente de Descarga - {implementationName}", "VideoDynamicRange": "Video de Rango Dinámico", "AuthenticationMethodHelpTextWarning": "Por favor selecciona un método válido de autenticación", - "AddCustomFilter": "Añadir filtro personalizado" + "AddCustomFilter": "Añadir filtro personalizado", + "BypassDelayIfAboveCustomFormatScoreMinimumScore": "Puntuación mínima de formato personalizado", + "CountIndexersSelected": "{count} indexador(es) seleccionado(s)", + "CouldNotFindResults": "No se pudieron encontrar resultados para '{term}'", + "CountImportListsSelected": "{count} lista(s) de importación seleccionada(s)", + "DelayingDownloadUntil": "Retrasar la descarga hasta {date} a {time}", + "DeleteIndexerMessageText": "Seguro que quieres eliminar el indexer '{name}'?", + "BlocklistLoadError": "No se han podido cargar las bloqueadas", + "BypassDelayIfAboveCustomFormatScore": "Omitir si está por encima de la puntuación del formato personalizado", + "BypassDelayIfAboveCustomFormatScoreMinimumScoreHelpText": "Puntuación mínima de formato personalizado necesaria para evitar el retraso del protocolo preferido", + "DeleteDownloadClientMessageText": "¿Seguro que quieres eliminar el cliente de descargas '{name}'?", + "DeleteNotificationMessageText": "¿Seguro que quieres eliminiar la notificación '{name}'?", + "DefaultNameCopiedSpecification": "{name} - Copia", + "DefaultNameCopiedProfile": "{name} - Copia", + "DeleteConditionMessageText": "Seguro que quieres eliminar la etiqueta '{name}'?", + "DeleteQualityProfileMessageText": "¿Seguro que quieres eliminar el perfil de calidad {name}?", + "DeleteRootFolderMessageText": "¿Está seguro de querer eliminar la carpeta raíz '{path}'?", + "DeleteSelectedDownloadClientsMessageText": "¿Estas seguro que quieres eliminar {count} cliente(s) de descarga seleccionado(s)?", + "ConnectionLostToBackend": "{appName} ha perdido su conexión con el backend y necesitará ser recargada para restaurar su funcionalidad.", + "CalendarOptions": "Opciones de Calendario", + "BypassDelayIfAboveCustomFormatScoreHelpText": "Habilitar ignorar cuando la versión tenga una puntuación superior a la puntuación mínima configurada para el formato personalizado", + "Default": "Por defecto", + "DeleteBackupMessageText": "Seguro que quieres eliminar la copia de seguridad '{name}'?", + "DeleteAutoTagHelpText": "¿Está seguro de querer eliminar el etiquetado automático '{name}'?", + "AddImportListImplementation": "Añadir lista de importación - {implementationName}", + "AddIndexerImplementation": "Añadir Indexador - {implementationName}", + "AutoRedownloadFailed": "La descarga ha fallado", + "ConnectionLostReconnect": "Radarr intentará conectarse automáticamente, o haz clic en el botón de recarga abajo.", + "CustomFormatJson": "Formato JSON personalizado", + "CountDownloadClientsSelected": "{count} cliente(s) de descarga seleccionado(s)", + "DeleteImportList": "Eliminar Lista(s) de Importación", + "DeleteImportListMessageText": "Seguro que quieres eliminar la lista '{name}'?", + "AutoRedownloadFailedFromInteractiveSearchHelpText": "La búsqueda automática para intentar descargar una versión diferente cuando en la búsqueda interactiva se obtiene una versión fallida", + "AutoRedownloadFailedFromInteractiveSearch": "Fallo al volver a descargar desde la búsqueda interactiva", + "DeleteSelectedIndexersMessageText": "Seguro que quieres eliminar {count} indexer seleccionado(s)?", + "DeleteSelectedImportListsMessageText": "Seguro que quieres eliminar {count} lista(s) de importación seleccionada(s)?", + "DeletedReasonUpgrade": "Se ha borrado el archivo para importar una versión mejorada", + "DeleteTagMessageText": "¿Seguro que quieres eliminar la etiqueta '{label}'?", + "DisabledForLocalAddresses": "Desactivado para direcciones locales", + "DeletedReasonManual": "El archivo fue borrado por vía UI" } diff --git a/src/NzbDrone.Core/Localization/Core/fr.json b/src/NzbDrone.Core/Localization/Core/fr.json index 60653e070..0c55d6ee2 100644 --- a/src/NzbDrone.Core/Localization/Core/fr.json +++ b/src/NzbDrone.Core/Localization/Core/fr.json @@ -1328,7 +1328,7 @@ "EnableMetadataHelpText": "Activer la création de fichiers de métadonnées pour ce type de métadonnées", "DownloadClientSortingHealthCheckMessage": "Le client de téléchargement {0} a le tri {1} activé pour la catégorie de {appName}. Vous devez désactiver le tri dans votre client de téléchargement pour éviter les problèmes d'importation.", "EnableSsl": "Activer SSL/TLS", - "EnableProfile": "Activer le profil", + "EnableProfile": "Activer profil", "EpisodeProgress": "Progression de l'épisode", "FailedToLoadSeriesFromApi": "Échec du chargement de la série depuis l'API", "FailedToLoadTranslationsFromApi": "Échec du chargement des traductions depuis l'API", diff --git a/src/NzbDrone.Core/Localization/Core/pt.json b/src/NzbDrone.Core/Localization/Core/pt.json index efc7c2a49..f443e05b7 100644 --- a/src/NzbDrone.Core/Localization/Core/pt.json +++ b/src/NzbDrone.Core/Localization/Core/pt.json @@ -30,7 +30,7 @@ "ApplyTagsHelpTextHowToApplyImportLists": "Como aplicar etiquetas às listas de importação selecionadas", "ApplyTagsHelpTextHowToApplyIndexers": "Como aplicar etiquetas aos indexadores selecionados", "AddListExclusion": "Adicionar exclusão de lista", - "AddListExclusionHelpText": "Impedir série de ser adicionada ao Sonarr através de listas", + "AddListExclusionHelpText": "Impedir série de ser adicionada ao {appName} através de listas", "AddNewSeriesSearchForCutoffUnmetEpisodes": "Iniciar busca por episódios de corte não atendidos", "AddSeriesWithTitle": "Adicionar {title}", "AddedDate": "Adicionado: {date}", @@ -88,8 +88,8 @@ "AddImportListExclusionError": "Não foi possível adicionar uma nova exclusão na lista de importação, tente novamente.", "AddNewSeriesRootFolderHelpText": "A subpasta '{folder}' será criada automaticamente", "AddRemotePathMapping": "Adicionar mapeamento de caminho remoto", - "AnalyseVideoFilesHelpText": "Extraia informações de vídeo, como resolução, tempo de execução e informações de codec de arquivos. Isso requer que o Sonarr leia partes do arquivo que podem causar alta atividade no disco ou na rede durante as varreduras.", - "AnalyticsEnabledHelpText": "Envie informações anônimas de uso e erro para os servidores do Sonarr. Isso inclui informações sobre seu navegador, quais páginas do Sonarr WebUI você usa, relatórios de erros, bem como sistema operacional e versão de tempo de execução. Usaremos essas informações para priorizar recursos e correções de bugs.", + "AnalyseVideoFilesHelpText": "Extraia informações de vídeo como resolução, tempo de execução e informações de codec dos ficheiros. Isso requer que {appName} leia partes do ficheiro, o que pode causar alta atividade do disco ou da rede durante as análises.", + "AnalyticsEnabledHelpText": "Envie informações anónimas de uso e erro para os servidores do {appName}. Isso inclui informações sobre seu navegador, quais páginas do {appName} WebUI você usa, relatórios de erros, bem como sistema operacional e versão de tempo de execução. Usaremos essas informações para priorizar funcionalidades e correções de bugs.", "ApiKey": "Chave da API", "ApplicationURL": "URL do Aplicativo", "ApplyTagsHelpTextAdd": "Adicionar: agregar as etiquetas à lista existente de etiquetas", @@ -125,12 +125,60 @@ "AddNewSeriesError": "Erro ao carregar resultados da busca. Por favor, tente novamente.", "AddNewSeriesHelpText": "É fácil adicionar uma nova série, apenas comece a escrever o nome da série que quer adicionar.", "DeleteTagMessageText": "Tem a certeza que quer eliminar a etiqueta \"{label}\"?", - "DeleteSelectedIndexersMessageText": "Tem a certeza que quer eliminar os {count} indexadores seleccionados?", + "DeleteSelectedIndexersMessageText": "Tem a certeza de que pretende eliminar {count} indexador(es) selecionado(s)?", "DeleteDownloadClientMessageText": "Tem a certeza que quer eliminar o cliente de transferências \"{name}\"?", "DeleteNotificationMessageText": "Tem a certeza que quer eliminar a notificação \"{name}\"?", "EnableRss": "Activar RSS", - "DeleteSelectedDownloadClientsMessageText": "Tem a certeza que quer eliminar os {count} clientes de transferência seleccionados?", + "DeleteSelectedDownloadClientsMessageText": "Tem a certeza de que pretende eliminar o(s) cliente(s) de transferência selecionado(s)?", "MaintenanceRelease": "Versão de manutenção: reparações de erros e outras melhorias. Consulte o Histórico de Commits do Github para saber mais", "DeleteBackupMessageText": "Tem a certeza que quer eliminar a cópia de segurança \"{name}\"?", - "Exception": "Excepção" + "Exception": "Exceção", + "BypassDelayIfAboveCustomFormatScoreMinimumScoreHelpText": "Pontuação Mínima do Formato Personalizado necessária para contornar o atraso do protocolo preferido", + "ConnectionLostReconnect": "O Radarr tentará ligar-se automaticamente, ou você pode clicar em Recarregar abaixo.", + "ConnectionLostToBackend": "O Radarr perdeu a ligação com o back-end e precisará ser recarregado para restaurar a funcionalidade.", + "CountIndexersSelected": "{count} indexador(es) selecionado(s)", + "DeleteImportListMessageText": "Tem a certeza de que pretende eliminar a lista '{name}'?", + "DeleteRootFolder": "Eliminar a Pasta Raiz", + "DeleteRootFolderMessageText": "Tem a certeza de que pretende eliminar a pasta de raiz '{path}'?", + "EditSelectedDownloadClients": "Editar Clientes de Transferência Selecionados", + "EditSelectedImportLists": "Editar Listas de Importação Selecionadas", + "CloneAutoTag": "Clonar Etiqueta Automática", + "DeleteSelectedImportListsMessageText": "Tem a certeza de que pretende eliminar a(s) lista(s) de importação selecionada(s)?", + "BypassDelayIfAboveCustomFormatScore": "Ignorar se estiver acima da pontuação de formato personalizado", + "CouldNotFindResults": "Nenhum resultado encontrado para \"{term}\"", + "CountImportListsSelected": "{count} importar lista(s) selecionada(s)", + "DeleteCondition": "Eliminar Condição", + "DeleteQualityProfileMessageText": "Tem a certeza de que pretende eliminar o perfil de qualidade '{name}'?", + "DeletedReasonManual": "O ficheiro foi eliminado através da IU", + "DeletedReasonUpgrade": "O ficheiro foi eliminado para importar uma atualização", + "DownloadIgnored": "Transferência Ignorada", + "DownloadWarning": "Alerta de transferência: {warningMessage}", + "BypassDelayIfAboveCustomFormatScoreMinimumScore": "Pontuação mínima de formato personalizado", + "BlocklistLoadError": "Não foi possível carregar a lista de bloqueio", + "DeleteImportList": "Eliminar Lista de Importação", + "DownloadClientsLoadError": "Não foi possível carregar os clientes de transferências", + "DefaultNameCopiedProfile": "{name} - Copiar", + "CustomFormatJson": "Formato personalizado JSON", + "DeleteAutoTag": "Eliminar Etiqueta Automática", + "DeleteAutoTagHelpText": "Tem a certeza de que pretende eliminar a etiqueta automática \"{name}\"?", + "EditAutoTag": "Editar Etiqueta Automática", + "CloneCondition": "Clonar Condição", + "EditImportListImplementation": "Editar Lista de Importação - {implementationName}", + "EditIndexerImplementation": "Editar Indexador - {implementationName}", + "AutoRedownloadFailedFromInteractiveSearch": "Falha na transferência a partir da Pesquisa interactiva", + "AutoRedownloadFailedFromInteractiveSearchHelpText": "Procurar automaticamente e tentar transferir uma versão diferente quando a versão falhada foi obtida a partir da pesquisa interactiva", + "AutomaticUpdatesDisabledDocker": "As actualizações automáticas não são diretamente suportadas quando se utiliza o mecanismo de atualização do Docker. Terá de atualizar a imagem do contentor fora de {appName} ou utilizar um script", + "BypassDelayIfAboveCustomFormatScoreHelpText": "Ativar o desvio quando a versão tem uma pontuação superior à pontuação mínima configurada para o formato personalizado", + "DeleteConditionMessageText": "Tem a certeza de que pretende eliminar a condição '{name}'?", + "EditConditionImplementation": "Editar Condição - {implementationName}", + "EditDownloadClientImplementation": "Editar Cliente de Transferência - {implementationName}", + "AutoRedownloadFailed": "Falha na transferência", + "CountDownloadClientsSelected": "{count} cliente(s) de transferência selecionado(s)", + "Default": "Predefinição", + "DefaultNameCopiedSpecification": "{name} - Copiar", + "DelayingDownloadUntil": "Atraso da transferência até {date} a {time}", + "DeleteIndexerMessageText": "Tem a certeza de que pretende eliminar o indexador '{name}'?", + "DisabledForLocalAddresses": "Desativado para Endereços Locais", + "CalendarOptions": "Opções do calendário", + "UpdateMechanismHelpText": "Use o atualizador do {appName} ou um script" } diff --git a/src/NzbDrone.Core/Localization/Core/pt_BR.json b/src/NzbDrone.Core/Localization/Core/pt_BR.json index 3770297b3..b7aafe6e0 100644 --- a/src/NzbDrone.Core/Localization/Core/pt_BR.json +++ b/src/NzbDrone.Core/Localization/Core/pt_BR.json @@ -377,10 +377,10 @@ "Negate": "Negar", "Save": "Salvar", "AddRootFolder": "Adicionar Pasta Raiz", - "AutoTagging": "Marcação Automática", + "AutoTagging": "Tagging Automática", "DeleteRootFolder": "Excluir Pasta Raiz", "DeleteRootFolderMessageText": "Tem certeza de que deseja excluir a pasta raiz '{path}'?", - "RemoveTagsAutomaticallyHelpText": "Remova tags automaticamente se as condições não forem atendidas", + "RemoveTagsAutomaticallyHelpText": "Remover tags automaticamente se as condições não forem encontradas", "RootFolders": "Pastas Raiz", "AllResultsAreHiddenByTheAppliedFilter": "Todos os resultados são ocultados pelo filtro aplicado", "Folder": "Pasta", @@ -389,18 +389,18 @@ "MoveAutomatically": "Mover automaticamente", "NoResultsFound": "Nenhum resultado encontrado", "RemoveRootFolder": "Remover pasta raiz", - "RemoveTagsAutomatically": "Remover tags automaticamente", + "RemoveTagsAutomatically": "Remover Tags Automaticamente", "Season": "Temporada", "SelectFolder": "Selecionar Pasta", "Unavailable": "Indisponível", "UnmappedFolders": "Pastas não mapeadas", - "AutoTaggingNegateHelpText": "se marcada, a regra de marcação automática não será aplicada se esta condição {implementationName} corresponder.", - "AutoTaggingRequiredHelpText": "Esta condição {implementationName} deve corresponder para que a regra de etiqueta automática seja aplicada. Caso contrário, uma única correspondência {implementationName} é suficiente.", + "AutoTaggingNegateHelpText": "Se marcada, a regra de tagging automática não será aplicada se esta condição {implementationName} corresponder.", + "AutoTaggingRequiredHelpText": "Esta condição {implementationName} deve corresponder para que a regra de tagging automática seja aplicada. Caso contrário, uma única correspondência de {implementationName} será suficiente.", "SomeResultsAreHiddenByTheAppliedFilter": "Alguns resultados estão ocultos pelo filtro aplicado", "UnableToLoadAutoTagging": "Não foi possível carregar a marcação automática", "UnableToLoadRootFolders": "Não foi possível carregar as pastas raiz", "IndexerDownloadClientHealthCheckMessage": "Indexadores com clientes de download inválidos: {indexerNames}.", - "AddConditionError": "Não foi possível adicionar uma nova condição. Tente novamente.", + "AddConditionError": "Não foi possível adicionar uma nova condição, por favor, tente novamente.", "AddConnection": "Adicionar Conexão", "AddCustomFormat": "Adicionar Formato Personalizado", "AddCustomFormatError": "Não foi possível adicionar um novo formato personalizado. Tente novamente.", @@ -438,7 +438,7 @@ "AuthenticationMethodHelpText": "Exigir nome de usuário e senha para acessar o {appName}", "AuthenticationRequired": "Autenticação exigida", "AutoRedownloadFailedHelpText": "Procurar automaticamente e tente baixar uma versão diferente", - "AutoTaggingLoadError": "Não foi possível carregar a marcação automática", + "AutoTaggingLoadError": "Não foi possível carregar tagging automática", "Automatic": "Automático", "AutomaticSearch": "Pesquisa Automática", "BackupFolderHelpText": "Os caminhos relativos estarão no diretório AppData do {appName}", @@ -480,7 +480,7 @@ "ColonReplacementFormatHelpText": "Mude como o {appName} lida com a substituição do dois-pontos", "CompletedDownloadHandling": "Gerenciamento de Downloads Completos", "Condition": "Condição", - "ConditionUsingRegularExpressions": "Essa condição corresponde ao uso de expressões regulares. Observe que os caracteres `\\^$.|?*+()[{` têm significados especiais e precisam ser substituídos por um `\\`", + "ConditionUsingRegularExpressions": "Esta condição corresponde ao uso de expressões regulares. Observe que os caracteres `\\^$.|?*+()[{` têm significados especiais e precisam ser escapados com um `\\`", "ConnectSettings": "Configurações de Conexão", "ConnectSettingsSummary": "Notificações, conexões com servidores/reprodutores de mídia e scripts personalizados", "Connections": "Conexões", @@ -741,7 +741,7 @@ "RecyclingBinCleanupHelpTextWarning": "Os arquivos na lixeira mais antigos do que o número de dias selecionado serão limpos automaticamente", "RecyclingBinHelpText": "Os arquivos do episódio irão para cá quando excluídos, em vez de serem excluídos permanentemente", "AbsoluteEpisodeNumber": "Número Absoluto do Episódio", - "AddAutoTagError": "Não foi possível adicionar uma nova tag automática. Tente novamente.", + "AddAutoTagError": "Não foi possível adicionar uma nova tag automática, por favor, tente novamente.", "AnalyseVideoFilesHelpText": "Extraia informações de vídeo, como resolução, tempo de execução e informações de codec de arquivos. Isso requer que o {appName} leia partes do arquivo que podem causar alta atividade no disco ou na rede durante as varreduras.", "AuthenticationRequiredHelpText": "Altere para quais solicitações a autenticação é necessária. Não mude a menos que você entenda os riscos.", "AuthenticationRequiredWarning": "Para evitar o acesso remoto sem autenticação, {appName} agora exige que a autenticação esteja habilitada. Opcionalmente, você pode desabilitar a autenticação de endereços locais.", @@ -858,7 +858,7 @@ "StandardEpisodeFormat": "Formato do Episódio Padrão", "Style": "Estilo", "Sunday": "Domingo", - "SupportedAutoTaggingProperties": "O {appName} suporta as seguintes propriedades para regras de marcação automática", + "SupportedAutoTaggingProperties": "{appName} oferece suporte às propriedades a seguir para regras de codificação automática", "SupportedCustomConditions": "O {appName} oferece suporte a condições personalizadas nas propriedades de lançamento abaixo.", "SupportedDownloadClients": "O {appName} suporta muitos clientes populares de download de torrent e usenet.", "SupportedDownloadClientsMoreInfo": "Para obter mais informações sobre os clientes de download individuais, clique nos botões de mais informações.", @@ -1422,7 +1422,7 @@ "UnselectAll": "Desmarcar Todos", "UpcomingSeriesDescription": "A série foi anunciada, mas ainda não há data exata para ir ao ar", "UpdateAll": "Atualizar Tudo", - "UpdateFiltered": "Atualizar Filtrado", + "UpdateFiltered": "Atualização Filtrada", "UpdateMonitoring": "Atualizar Monitoramento", "UpdateSelected": "Atualizar Selecionado", "UseSeasonFolder": "Usar Pasta de Temporada", @@ -1488,5 +1488,5 @@ "AutoRedownloadFailedFromInteractiveSearch": "Falha no Novo Download pela Pesquisa Interativa", "ImportListSearchForMissingEpisodes": "Pesquisar Episódios Ausentes", "ImportListSearchForMissingEpisodesHelpText": "Depois que a série for adicionada ao {appName}, procure automaticamente episódios ausentes", - "QueueFilterHasNoItems": "O filtro de fila selecionado não tem itens" + "QueueFilterHasNoItems": "O filtro de fila selecionado não possui itens" } diff --git a/src/NzbDrone.Core/Localization/Core/tr.json b/src/NzbDrone.Core/Localization/Core/tr.json index 6db0abcbe..73d3828cd 100644 --- a/src/NzbDrone.Core/Localization/Core/tr.json +++ b/src/NzbDrone.Core/Localization/Core/tr.json @@ -1,4 +1,8 @@ { "About": "Hakkında", - "Absolute": "Mutlak" + "Absolute": "Mutlak", + "AddCondition": "Koşul Ekle", + "AddAutoTag": "Otomatik Etiket Ekle", + "AddConnection": "Bağlantı Ekle", + "AddConditionImplementation": "Koşul Ekle - {uygulama Adı}" } From 24759b9060df80f9cfc0f99f03e937fac9b05967 Mon Sep 17 00:00:00 2001 From: Sonarr <development@sonarr.tv> Date: Thu, 9 Nov 2023 23:58:52 +0000 Subject: [PATCH 098/136] Automated API Docs update ignore-downstream --- src/Sonarr.Api.V3/openapi.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Sonarr.Api.V3/openapi.json b/src/Sonarr.Api.V3/openapi.json index 1dd49cdbb..70271b11d 100644 --- a/src/Sonarr.Api.V3/openapi.json +++ b/src/Sonarr.Api.V3/openapi.json @@ -10773,6 +10773,10 @@ "format": "int32", "nullable": true }, + "downloadClient": { + "type": "string", + "nullable": true + }, "shouldOverride": { "type": "boolean", "nullable": true From 0fe05fb3a9e00d129dd84bb06f75acdfeb67409b Mon Sep 17 00:00:00 2001 From: Mark McDowall <mark@mcdowall.ca> Date: Thu, 9 Nov 2023 15:31:11 -0800 Subject: [PATCH 099/136] Remove extraneous comments/code from migrations --- ...e_language_tags_from_existing_subtitle_files.cs | 14 -------------- .../Datastore/Migration/199_series_last_aired.cs | 2 -- 2 files changed, 16 deletions(-) diff --git a/src/NzbDrone.Core/Datastore/Migration/195_parse_language_tags_from_existing_subtitle_files.cs b/src/NzbDrone.Core/Datastore/Migration/195_parse_language_tags_from_existing_subtitle_files.cs index b49bfaae5..934b2b6da 100644 --- a/src/NzbDrone.Core/Datastore/Migration/195_parse_language_tags_from_existing_subtitle_files.cs +++ b/src/NzbDrone.Core/Datastore/Migration/195_parse_language_tags_from_existing_subtitle_files.cs @@ -1,8 +1,5 @@ -using System; using System.Collections.Generic; using System.Data; -using System.Text.Json; -using System.Text.Json.Serialization; using Dapper; using FluentMigrator; using NzbDrone.Core.Datastore.Migration.Framework; @@ -21,7 +18,6 @@ namespace NzbDrone.Core.Datastore.Migration private void UpdateLanguageTags(IDbConnection conn, IDbTransaction tran) { var updatedLanguageTags = new List<object>(); - var now = DateTime.Now; using (var cmd = conn.CreateCommand()) { @@ -43,16 +39,6 @@ namespace NzbDrone.Core.Datastore.Migration } } - var serializerSettings = new JsonSerializerOptions - { - AllowTrailingCommas = true, - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, - PropertyNameCaseInsensitive = true, - DictionaryKeyPolicy = JsonNamingPolicy.CamelCase, - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - WriteIndented = true - }; - var updateSubtitleFilesSql = "UPDATE \"SubtitleFiles\" SET \"LanguageTags\" = @LanguageTags, \"LastUpdated\" = CURRENT_TIMESTAMP WHERE \"Id\" = @Id"; conn.Execute(updateSubtitleFilesSql, updatedLanguageTags, transaction: tran); } diff --git a/src/NzbDrone.Core/Datastore/Migration/199_series_last_aired.cs b/src/NzbDrone.Core/Datastore/Migration/199_series_last_aired.cs index 2264dabd1..c048dcc69 100644 --- a/src/NzbDrone.Core/Datastore/Migration/199_series_last_aired.cs +++ b/src/NzbDrone.Core/Datastore/Migration/199_series_last_aired.cs @@ -9,8 +9,6 @@ namespace NzbDrone.Core.Datastore.Migration protected override void MainDbUpgrade() { Alter.Table("Series").AddColumn("LastAired").AsDateTimeOffset().Nullable(); - - // Execute.Sql("UPDATE Series SET LastAired = "); } } } From 1e295d81f2583b7cb738944acc5393f2c67f16e9 Mon Sep 17 00:00:00 2001 From: Mark McDowall <mark@mcdowall.ca> Date: Thu, 9 Nov 2023 15:32:40 -0800 Subject: [PATCH 100/136] Fixed: Don't do title search when adding series from AniList import list Closes #6140 --- src/NzbDrone.Core/ImportLists/ImportListSyncService.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/NzbDrone.Core/ImportLists/ImportListSyncService.cs b/src/NzbDrone.Core/ImportLists/ImportListSyncService.cs index 4c10f79d8..e85e95730 100644 --- a/src/NzbDrone.Core/ImportLists/ImportListSyncService.cs +++ b/src/NzbDrone.Core/ImportLists/ImportListSyncService.cs @@ -109,11 +109,15 @@ namespace NzbDrone.Core.ImportLists var mappedSeries = _seriesSearchService.SearchForNewSeriesByAniListId(item.AniListId) .FirstOrDefault(); - if (mappedSeries != null) + if (mappedSeries == null) { - item.TvdbId = mappedSeries.TvdbId; - item.Title = mappedSeries.Title; + _logger.Debug("Rejected, unable to find matching TVDB ID for Anilist ID: {0} [{1}]", item.AniListId, item.Title); + + continue; } + + item.TvdbId = mappedSeries.TvdbId; + item.Title = mappedSeries.Title; } // Map TVDb if we only have a series name From de23182d593e2284972103d505e66dd8d812dfdb Mon Sep 17 00:00:00 2001 From: Mark McDowall <mark@mcdowall.ca> Date: Thu, 9 Nov 2023 15:33:03 -0800 Subject: [PATCH 101/136] Don't store successful results for invalid providers --- .../ThingiProvider/Status/ProviderStatusServiceBase.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/NzbDrone.Core/ThingiProvider/Status/ProviderStatusServiceBase.cs b/src/NzbDrone.Core/ThingiProvider/Status/ProviderStatusServiceBase.cs index 40c353b24..6279c6e35 100644 --- a/src/NzbDrone.Core/ThingiProvider/Status/ProviderStatusServiceBase.cs +++ b/src/NzbDrone.Core/ThingiProvider/Status/ProviderStatusServiceBase.cs @@ -59,6 +59,11 @@ namespace NzbDrone.Core.ThingiProvider.Status public virtual void RecordSuccess(int providerId) { + if (providerId <= 0) + { + return; + } + lock (_syncRoot) { var status = GetProviderStatus(providerId); From 3d05913534e40e1b9ff217798d806d0b7c170d2d Mon Sep 17 00:00:00 2001 From: Bogdan <mynameisbogdan@users.noreply.github.com> Date: Fri, 10 Nov 2023 04:55:01 +0200 Subject: [PATCH 102/136] Fixed: Record status for notifications on tests --- .../Notifications/NotificationFactory.cs | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/NzbDrone.Core/Notifications/NotificationFactory.cs b/src/NzbDrone.Core/Notifications/NotificationFactory.cs index ca7fa6215..35823feca 100644 --- a/src/NzbDrone.Core/Notifications/NotificationFactory.cs +++ b/src/NzbDrone.Core/Notifications/NotificationFactory.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using FluentValidation.Results; using NLog; using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.ThingiProvider; @@ -193,5 +194,26 @@ namespace NzbDrone.Core.Notifications definition.SupportsOnApplicationUpdate = provider.SupportsOnApplicationUpdate; definition.SupportsOnManualInteractionRequired = provider.SupportsOnManualInteractionRequired; } + + public override ValidationResult Test(NotificationDefinition definition) + { + var result = base.Test(definition); + + if (definition.Id == 0) + { + return result; + } + + if (result == null || result.IsValid) + { + _notificationStatusService.RecordSuccess(definition.Id); + } + else + { + _notificationStatusService.RecordFailure(definition.Id); + } + + return result; + } } } From 48b12f5b00429a7cd218d23f0544641b0da62a06 Mon Sep 17 00:00:00 2001 From: Stevie Robinson <stevie.robinson@gmail.com> Date: Fri, 10 Nov 2023 17:44:04 +0100 Subject: [PATCH 103/136] Translate Download Clients on the backend --- .../ClientSchemaTests/SchemaBuilderFixture.cs | 13 ++ .../HadoukenTests/HadoukenFixture.cs | 2 +- .../Annotations/FieldDefinitionAttribute.cs | 24 +++ .../Download/Clients/Aria2/Aria2.cs | 15 +- .../Download/Clients/Aria2/Aria2Settings.cs | 6 +- .../Clients/Blackhole/TorrentBlackhole.cs | 8 +- .../Blackhole/TorrentBlackholeSettings.cs | 11 +- .../Clients/Blackhole/UsenetBlackhole.cs | 8 +- .../Blackhole/UsenetBlackholeSettings.cs | 5 +- .../Download/Clients/Deluge/Deluge.cs | 40 ++--- .../Download/Clients/Deluge/DelugeSettings.cs | 16 +- .../DownloadStationSettings.cs | 7 +- .../DownloadStation/TorrentDownloadStation.cs | 47 +++--- .../DownloadStation/UsenetDownloadStation.cs | 47 +++--- .../Download/Clients/Flood/Flood.cs | 8 +- .../Download/Clients/Flood/FloodSettings.cs | 16 +- .../FreeboxDownloadSettings.cs | 27 ++-- .../FreeboxDownload/TorrentFreeboxDownload.cs | 11 +- .../Download/Clients/Hadouken/Hadouken.cs | 16 +- .../Clients/Hadouken/HadoukenSettings.cs | 9 +- .../Download/Clients/NzbVortex/NzbVortex.cs | 23 +-- .../Clients/NzbVortex/NzbVortexSettings.cs | 12 +- .../Download/Clients/Nzbget/Nzbget.cs | 37 +++-- .../Download/Clients/Nzbget/NzbgetSettings.cs | 15 +- .../Download/Clients/Pneumatic/Pneumatic.cs | 6 +- .../Clients/Pneumatic/PneumaticSettings.cs | 4 +- .../Clients/QBittorrent/QBittorrent.cs | 77 ++++++---- .../QBittorrent/QBittorrentSettings.cs | 20 +-- .../Download/Clients/Sabnzbd/Sabnzbd.cs | 53 ++++--- .../Clients/Sabnzbd/SabnzbdSettings.cs | 15 +- .../Clients/Transmission/Transmission.cs | 9 +- .../Clients/Transmission/TransmissionBase.cs | 16 +- .../Transmission/TransmissionSettings.cs | 19 ++- .../Download/Clients/Vuze/Vuze.cs | 8 +- .../Download/Clients/rTorrent/RTorrent.cs | 19 ++- .../Clients/rTorrent/RTorrentSettings.cs | 19 ++- .../Download/Clients/uTorrent/UTorrent.cs | 29 ++-- .../Clients/uTorrent/UTorrentSettings.cs | 18 ++- .../Download/DownloadClientBase.cs | 6 +- .../Download/TorrentClientBase.cs | 14 +- .../Download/UsenetClientBase.cs | 6 +- src/NzbDrone.Core/Localization/Core/en.json | 145 ++++++++++++++++++ src/NzbDrone.Host/Bootstrap.cs | 3 + .../IntegrationTest.cs | 26 ++-- src/NzbDrone.Test.Common/AutoMoq/AutoMoqer.cs | 2 + src/Sonarr.Http/ClientSchema/SchemaBuilder.cs | 46 +++++- 46 files changed, 678 insertions(+), 305 deletions(-) diff --git a/src/NzbDrone.Api.Test/ClientSchemaTests/SchemaBuilderFixture.cs b/src/NzbDrone.Api.Test/ClientSchemaTests/SchemaBuilderFixture.cs index d3c036327..6af8abb7b 100644 --- a/src/NzbDrone.Api.Test/ClientSchemaTests/SchemaBuilderFixture.cs +++ b/src/NzbDrone.Api.Test/ClientSchemaTests/SchemaBuilderFixture.cs @@ -1,6 +1,9 @@ +using System.Collections.Generic; using FluentAssertions; +using Moq; using NUnit.Framework; using NzbDrone.Core.Annotations; +using NzbDrone.Core.Localization; using NzbDrone.Test.Common; using Sonarr.Http.ClientSchema; @@ -9,6 +12,16 @@ namespace NzbDrone.Api.Test.ClientSchemaTests [TestFixture] public class SchemaBuilderFixture : TestBase { + [SetUp] + public void Setup() + { + Mocker.GetMock<ILocalizationService>() + .Setup(s => s.GetLocalizedString(It.IsAny<string>(), It.IsAny<Dictionary<string, object>>())) + .Returns<string, Dictionary<string, object>>((s, d) => s); + + SchemaBuilder.Initialize(Mocker.Container); + } + [Test] public void should_return_field_for_every_property() { diff --git a/src/NzbDrone.Core.Test/Download/DownloadClientTests/HadoukenTests/HadoukenFixture.cs b/src/NzbDrone.Core.Test/Download/DownloadClientTests/HadoukenTests/HadoukenFixture.cs index 9cc35560f..bf609e48c 100644 --- a/src/NzbDrone.Core.Test/Download/DownloadClientTests/HadoukenTests/HadoukenFixture.cs +++ b/src/NzbDrone.Core.Test/Download/DownloadClientTests/HadoukenTests/HadoukenFixture.cs @@ -320,7 +320,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.HadoukenTests var result = Subject.Test(); - result.Errors.First().ErrorMessage.Should().Be("Old Hadouken client with unsupported API, need 5.1 or higher"); + result.Errors.Count.Should().Be(1); } } } diff --git a/src/NzbDrone.Core/Annotations/FieldDefinitionAttribute.cs b/src/NzbDrone.Core/Annotations/FieldDefinitionAttribute.cs index 7b711a37d..a0a1896e0 100644 --- a/src/NzbDrone.Core/Annotations/FieldDefinitionAttribute.cs +++ b/src/NzbDrone.Core/Annotations/FieldDefinitionAttribute.cs @@ -42,6 +42,23 @@ namespace NzbDrone.Core.Annotations public string RequestAction { get; set; } } + [AttributeUsage(AttributeTargets.Property, AllowMultiple = true)] + public class FieldTokenAttribute : Attribute + { + public FieldTokenAttribute(TokenField field, string label = "", string token = "", object value = null) + { + Label = label; + Field = field; + Token = token; + Value = value?.ToString(); + } + + public string Label { get; set; } + public TokenField Field { get; set; } + public string Token { get; set; } + public string Value { get; set; } + } + public class FieldSelectOption { public int Value { get; set; } @@ -85,4 +102,11 @@ namespace NzbDrone.Core.Annotations ApiKey, UserName } + + public enum TokenField + { + Label, + HelpText, + HelpTextWarning + } } diff --git a/src/NzbDrone.Core/Download/Clients/Aria2/Aria2.cs b/src/NzbDrone.Core/Download/Clients/Aria2/Aria2.cs index d8ba934a0..223af1286 100644 --- a/src/NzbDrone.Core/Download/Clients/Aria2/Aria2.cs +++ b/src/NzbDrone.Core/Download/Clients/Aria2/Aria2.cs @@ -8,6 +8,7 @@ using NzbDrone.Common.Disk; using NzbDrone.Common.Extensions; using NzbDrone.Common.Http; using NzbDrone.Core.Configuration; +using NzbDrone.Core.Localization; using NzbDrone.Core.MediaFiles.TorrentInfo; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.RemotePathMappings; @@ -27,8 +28,9 @@ namespace NzbDrone.Core.Download.Clients.Aria2 IConfigService configService, IDiskProvider diskProvider, IRemotePathMappingService remotePathMappingService, - Logger logger) - : base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, logger) + Logger logger, + ILocalizationService localizationService) + : base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, logger, localizationService) { _proxy = proxy; } @@ -233,14 +235,19 @@ namespace NzbDrone.Core.Download.Clients.Aria2 if (new Version(version) < new Version("1.34.0")) { - return new ValidationFailure(string.Empty, "Aria2 version should be at least 1.34.0. Version reported is {0}", version); + return new ValidationFailure(string.Empty, + _localizationService.GetLocalizedString("DownloadClientValidationErrorVersion", + new Dictionary<string, object> + { + { "clientName", "Aria2" }, { "requiredVersion", "1.34.0" }, { "reportedVersion", version } + })); } } catch (Exception ex) { _logger.Error(ex, "Failed to test Aria2"); - return new NzbDroneValidationFailure("Host", "Unable to connect to Aria2") + return new NzbDroneValidationFailure("Host", _localizationService.GetLocalizedString("DownloadClientValidationUnableToConnect", new Dictionary<string, object> { { "clientName", "Aria2" } })) { DetailedDescription = ex.Message }; diff --git a/src/NzbDrone.Core/Download/Clients/Aria2/Aria2Settings.cs b/src/NzbDrone.Core/Download/Clients/Aria2/Aria2Settings.cs index e88bc4cc1..088f06f9e 100644 --- a/src/NzbDrone.Core/Download/Clients/Aria2/Aria2Settings.cs +++ b/src/NzbDrone.Core/Download/Clients/Aria2/Aria2Settings.cs @@ -32,13 +32,13 @@ namespace NzbDrone.Core.Download.Clients.Aria2 [FieldDefinition(1, Label = "Port", Type = FieldType.Number)] public int Port { get; set; } - [FieldDefinition(2, Label = "XML RPC Path", Type = FieldType.Textbox)] + [FieldDefinition(2, Label = "XmlRpcPath", Type = FieldType.Textbox)] public string RpcPath { get; set; } - [FieldDefinition(3, Label = "Use SSL", Type = FieldType.Checkbox)] + [FieldDefinition(3, Label = "UseSsl", Type = FieldType.Checkbox)] public bool UseSsl { get; set; } - [FieldDefinition(4, Label = "Secret token", Type = FieldType.Password, Privacy = PrivacyLevel.Password)] + [FieldDefinition(4, Label = "SecretToken", Type = FieldType.Password, Privacy = PrivacyLevel.Password)] public string SecretToken { get; set; } public NzbDroneValidationResult Validate() diff --git a/src/NzbDrone.Core/Download/Clients/Blackhole/TorrentBlackhole.cs b/src/NzbDrone.Core/Download/Clients/Blackhole/TorrentBlackhole.cs index c9b45369e..62a9a4255 100644 --- a/src/NzbDrone.Core/Download/Clients/Blackhole/TorrentBlackhole.cs +++ b/src/NzbDrone.Core/Download/Clients/Blackhole/TorrentBlackhole.cs @@ -8,6 +8,7 @@ using NzbDrone.Common.Disk; using NzbDrone.Common.Extensions; using NzbDrone.Common.Http; using NzbDrone.Core.Configuration; +using NzbDrone.Core.Localization; using NzbDrone.Core.MediaFiles.TorrentInfo; using NzbDrone.Core.Organizer; using NzbDrone.Core.Parser.Model; @@ -29,8 +30,9 @@ namespace NzbDrone.Core.Download.Clients.Blackhole IConfigService configService, IDiskProvider diskProvider, IRemotePathMappingService remotePathMappingService, - Logger logger) - : base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, logger) + Logger logger, + ILocalizationService localizationService) + : base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, logger, localizationService) { _scanWatchFolder = scanWatchFolder; @@ -79,7 +81,7 @@ namespace NzbDrone.Core.Download.Clients.Blackhole return null; } - public override string Name => "Torrent Blackhole"; + public override string Name => _localizationService.GetLocalizedString("TorrentBlackhole"); public override IEnumerable<DownloadClientItem> GetItems() { diff --git a/src/NzbDrone.Core/Download/Clients/Blackhole/TorrentBlackholeSettings.cs b/src/NzbDrone.Core/Download/Clients/Blackhole/TorrentBlackholeSettings.cs index 49673a562..ad3010a60 100644 --- a/src/NzbDrone.Core/Download/Clients/Blackhole/TorrentBlackholeSettings.cs +++ b/src/NzbDrone.Core/Download/Clients/Blackhole/TorrentBlackholeSettings.cs @@ -28,23 +28,24 @@ namespace NzbDrone.Core.Download.Clients.Blackhole private static readonly TorrentBlackholeSettingsValidator Validator = new TorrentBlackholeSettingsValidator(); - [FieldDefinition(0, Label = "Torrent Folder", Type = FieldType.Path, HelpText = "Folder in which Sonarr will store the .torrent file")] + [FieldDefinition(0, Label = "TorrentBlackholeTorrentFolder", Type = FieldType.Path, HelpText = "BlackholeFolderHelpText")] + [FieldToken(TokenField.HelpText, "TorrentBlackholeTorrentFolder", "extension", ".torrent")] public string TorrentFolder { get; set; } - [FieldDefinition(1, Label = "Watch Folder", Type = FieldType.Path, HelpText = "Folder from which Sonarr should import completed downloads")] + [FieldDefinition(1, Label = "BlackholeWatchFolder", Type = FieldType.Path, HelpText = "BlackholeWatchFolderHelpText")] public string WatchFolder { get; set; } [DefaultValue(false)] [JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)] - [FieldDefinition(2, Label = "Save Magnet Files", Type = FieldType.Checkbox, HelpText = "Save the magnet link if no .torrent file is available (only useful if the download client supports magnets saved to a file)")] + [FieldDefinition(2, Label = "TorrentBlackholeSaveMagnetFiles", Type = FieldType.Checkbox, HelpText = "TorrentBlackholeSaveMagnetFilesHelpText")] public bool SaveMagnetFiles { get; set; } - [FieldDefinition(3, Label = "Save Magnet Files", Type = FieldType.Textbox, HelpText = "Extension to use for magnet links, defaults to '.magnet'")] + [FieldDefinition(3, Label = "TorrentBlackholeSaveMagnetFilesExtension", Type = FieldType.Textbox, HelpText = "TorrentBlackholeSaveMagnetFilesExtensionHelpText")] public string MagnetFileExtension { get; set; } [DefaultValue(false)] [JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)] - [FieldDefinition(4, Label = "Read Only", Type = FieldType.Checkbox, HelpText = "Instead of moving files this will instruct Sonarr to Copy or Hardlink (depending on settings/system configuration)")] + [FieldDefinition(4, Label = "TorrentBlackholeSaveMagnetFilesReadOnly", Type = FieldType.Checkbox, HelpText = "TorrentBlackholeSaveMagnetFilesReadOnlyHelpText")] public bool ReadOnly { get; set; } public NzbDroneValidationResult Validate() diff --git a/src/NzbDrone.Core/Download/Clients/Blackhole/UsenetBlackhole.cs b/src/NzbDrone.Core/Download/Clients/Blackhole/UsenetBlackhole.cs index 5fbc74a74..3a7105ba9 100644 --- a/src/NzbDrone.Core/Download/Clients/Blackhole/UsenetBlackhole.cs +++ b/src/NzbDrone.Core/Download/Clients/Blackhole/UsenetBlackhole.cs @@ -7,6 +7,7 @@ using NzbDrone.Common.Disk; using NzbDrone.Common.Extensions; using NzbDrone.Common.Http; using NzbDrone.Core.Configuration; +using NzbDrone.Core.Localization; using NzbDrone.Core.Organizer; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.RemotePathMappings; @@ -25,8 +26,9 @@ namespace NzbDrone.Core.Download.Clients.Blackhole IDiskProvider diskProvider, IRemotePathMappingService remotePathMappingService, IValidateNzbs nzbValidationService, - Logger logger) - : base(httpClient, configService, diskProvider, remotePathMappingService, nzbValidationService, logger) + Logger logger, + ILocalizationService localizationService) + : base(httpClient, configService, diskProvider, remotePathMappingService, nzbValidationService, logger, localizationService) { _scanWatchFolder = scanWatchFolder; @@ -51,7 +53,7 @@ namespace NzbDrone.Core.Download.Clients.Blackhole return null; } - public override string Name => "Usenet Blackhole"; + public override string Name => _localizationService.GetLocalizedString("UsenetBlackhole"); public override IEnumerable<DownloadClientItem> GetItems() { diff --git a/src/NzbDrone.Core/Download/Clients/Blackhole/UsenetBlackholeSettings.cs b/src/NzbDrone.Core/Download/Clients/Blackhole/UsenetBlackholeSettings.cs index b2ff88149..ae9603b61 100644 --- a/src/NzbDrone.Core/Download/Clients/Blackhole/UsenetBlackholeSettings.cs +++ b/src/NzbDrone.Core/Download/Clients/Blackhole/UsenetBlackholeSettings.cs @@ -19,10 +19,11 @@ namespace NzbDrone.Core.Download.Clients.Blackhole { private static readonly UsenetBlackholeSettingsValidator Validator = new UsenetBlackholeSettingsValidator(); - [FieldDefinition(0, Label = "Nzb Folder", Type = FieldType.Path, HelpText = "Folder in which Sonarr will store the .nzb file")] + [FieldDefinition(0, Label = "UsenetBlackholeNzbFolder", Type = FieldType.Path, HelpText = "BlackholeFolderHelpText")] + [FieldToken(TokenField.HelpText, "UsenetBlackholeNzbFolder", "extension", ".nzb")] public string NzbFolder { get; set; } - [FieldDefinition(1, Label = "Watch Folder", Type = FieldType.Path, HelpText = "Folder from which Sonarr should import completed downloads")] + [FieldDefinition(1, Label = "BlackholeWatchFolder", Type = FieldType.Path, HelpText = "BlackholeWatchFolderHelpText")] public string WatchFolder { get; set; } public NzbDroneValidationResult Validate() diff --git a/src/NzbDrone.Core/Download/Clients/Deluge/Deluge.cs b/src/NzbDrone.Core/Download/Clients/Deluge/Deluge.cs index 39a191089..725c86521 100644 --- a/src/NzbDrone.Core/Download/Clients/Deluge/Deluge.cs +++ b/src/NzbDrone.Core/Download/Clients/Deluge/Deluge.cs @@ -8,6 +8,7 @@ using NzbDrone.Common.Disk; using NzbDrone.Common.Extensions; using NzbDrone.Common.Http; using NzbDrone.Core.Configuration; +using NzbDrone.Core.Localization; using NzbDrone.Core.MediaFiles.TorrentInfo; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.RemotePathMappings; @@ -25,8 +26,9 @@ namespace NzbDrone.Core.Download.Clients.Deluge IConfigService configService, IDiskProvider diskProvider, IRemotePathMappingService remotePathMappingService, - Logger logger) - : base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, logger) + Logger logger, + ILocalizationService localizationService) + : base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, logger, localizationService) { _proxy = proxy; } @@ -155,7 +157,7 @@ namespace NzbDrone.Core.Download.Clients.Deluge if (torrent.State == DelugeTorrentStatus.Error) { item.Status = DownloadItemStatus.Warning; - item.Message = "Deluge is reporting an error"; + item.Message = _localizationService.GetLocalizedString("DownloadClientDelugeTorrentStateError"); } else if (torrent.IsFinished && torrent.State != DelugeTorrentStatus.Checking) { @@ -248,7 +250,7 @@ namespace NzbDrone.Core.Download.Clients.Deluge { _logger.Error(ex, ex.Message); - return new NzbDroneValidationFailure("Password", "Authentication failed"); + return new NzbDroneValidationFailure("Password", _localizationService.GetLocalizedString("DownloadClientValidationAuthenticationFailure")); } catch (WebException ex) { @@ -256,29 +258,29 @@ namespace NzbDrone.Core.Download.Clients.Deluge switch (ex.Status) { case WebExceptionStatus.ConnectFailure: - return new NzbDroneValidationFailure("Host", "Unable to connect") + return new NzbDroneValidationFailure("Host", _localizationService.GetLocalizedString("DownloadClientValidationUnableToConnect", new Dictionary<string, object> { { "clientName", Name } })) { - DetailedDescription = "Please verify the hostname and port." + DetailedDescription = _localizationService.GetLocalizedString("DownloadClientValidationUnableToConnectDetail") }; case WebExceptionStatus.ConnectionClosed: - return new NzbDroneValidationFailure("UseSsl", "Verify SSL settings") + return new NzbDroneValidationFailure("UseSsl", _localizationService.GetLocalizedString("DownloadClientValidationVerifySsl")) { - DetailedDescription = "Please verify your SSL configuration on both Deluge and Sonarr." + DetailedDescription = _localizationService.GetLocalizedString("DownloadClientValidationVerifySslDetail", new Dictionary<string, object> { { "clientName", Name } }) }; case WebExceptionStatus.SecureChannelFailure: - return new NzbDroneValidationFailure("UseSsl", "Unable to connect through SSL") + return new NzbDroneValidationFailure("UseSsl", _localizationService.GetLocalizedString("DownloadClientValidationSslConnectFailure")) { - DetailedDescription = "Sonarr is unable to connect to Deluge using SSL. This problem could be computer related. Please try to configure both Sonarr and Deluge to not use SSL." + DetailedDescription = _localizationService.GetLocalizedString("DownloadClientValidationSslConnectFailureDetail", new Dictionary<string, object> { { "clientName", Name } }) }; default: - return new NzbDroneValidationFailure(string.Empty, "Unknown exception: " + ex.Message); + return new NzbDroneValidationFailure(string.Empty, _localizationService.GetLocalizedString("DownloadClientValidationUnknownException", new Dictionary<string, object> { { "exception", ex.Message } })); } } catch (Exception ex) { _logger.Error(ex, "Failed to test connection"); - return new NzbDroneValidationFailure("Host", "Unable to connect to Deluge") + return new NzbDroneValidationFailure("Host", _localizationService.GetLocalizedString("DownloadClientValidationUnableToConnect", new Dictionary<string, object> { { "clientName", Name } })) { DetailedDescription = ex.Message }; @@ -298,9 +300,9 @@ namespace NzbDrone.Core.Download.Clients.Deluge if (!enabledPlugins.Contains("Label")) { - return new NzbDroneValidationFailure("TvCategory", "Label plugin not activated") + return new NzbDroneValidationFailure("TvCategory", _localizationService.GetLocalizedString("DownloadClientDelugeValidationLabelPluginInactive")) { - DetailedDescription = "You must have the Label plugin enabled in Deluge to use categories." + DetailedDescription = _localizationService.GetLocalizedString("DownloadClientDelugeValidationLabelPluginInactiveDetail", new Dictionary<string, object> { { "clientName", Name } }) }; } @@ -313,9 +315,9 @@ namespace NzbDrone.Core.Download.Clients.Deluge if (!labels.Contains(Settings.TvCategory)) { - return new NzbDroneValidationFailure("TvCategory", "Configuration of label failed") + return new NzbDroneValidationFailure("TvCategory", _localizationService.GetLocalizedString("DownloadClientDelugeValidationLabelPluginFailure")) { - DetailedDescription = "Sonarr was unable to add the label to Deluge." + DetailedDescription = _localizationService.GetLocalizedString("DownloadClientDelugeValidationLabelPluginFailureDetail", new Dictionary<string, object> { { "clientName", Name } }) }; } } @@ -327,9 +329,9 @@ namespace NzbDrone.Core.Download.Clients.Deluge if (!labels.Contains(Settings.TvImportedCategory)) { - return new NzbDroneValidationFailure("TvImportedCategory", "Configuration of label failed") + return new NzbDroneValidationFailure("TvImportedCategory", _localizationService.GetLocalizedString("DownloadClientDelugeValidationLabelPluginFailure")) { - DetailedDescription = "Sonarr was unable to add the label to Deluge." + DetailedDescription = _localizationService.GetLocalizedString("DownloadClientDelugeValidationLabelPluginFailureDetail", new Dictionary<string, object> { { "clientName", Name } }) }; } } @@ -346,7 +348,7 @@ namespace NzbDrone.Core.Download.Clients.Deluge catch (Exception ex) { _logger.Error(ex, "Unable to get torrents"); - return new NzbDroneValidationFailure(string.Empty, "Failed to get the list of torrents: " + ex.Message); + return new NzbDroneValidationFailure(string.Empty, _localizationService.GetLocalizedString("DownloadClientValidationTestTorrents", new Dictionary<string, object> { { "exceptionMessage", ex.Message } })); } return null; diff --git a/src/NzbDrone.Core/Download/Clients/Deluge/DelugeSettings.cs b/src/NzbDrone.Core/Download/Clients/Deluge/DelugeSettings.cs index 97aab6a6e..66192d3f8 100644 --- a/src/NzbDrone.Core/Download/Clients/Deluge/DelugeSettings.cs +++ b/src/NzbDrone.Core/Download/Clients/Deluge/DelugeSettings.cs @@ -35,28 +35,30 @@ namespace NzbDrone.Core.Download.Clients.Deluge [FieldDefinition(1, Label = "Port", Type = FieldType.Textbox)] public int Port { get; set; } - [FieldDefinition(2, Label = "Use SSL", Type = FieldType.Checkbox, HelpText = "Use secure connection when connecting to Deluge")] + [FieldDefinition(2, Label = "UseSsl", Type = FieldType.Checkbox, HelpText = "DownloadClientSettingsUseSslHelpText")] + [FieldToken(TokenField.HelpText, "UseSsl", "clientName", "Deluge")] public bool UseSsl { get; set; } - [FieldDefinition(3, Label = "Url Base", Type = FieldType.Textbox, Advanced = true, HelpText = "Adds a prefix to the deluge json url, see http://[host]:[port]/[urlBase]/json")] + [FieldDefinition(3, Label = "UrlBase", Type = FieldType.Textbox, Advanced = true, HelpText = "DownloadClientDelugeSettingsUrlBaseHelpText")] + [FieldToken(TokenField.HelpText, "UrlBase", "url", "http://[host]:[port]/[urlBase]/json")] public string UrlBase { get; set; } [FieldDefinition(4, Label = "Password", Type = FieldType.Password, Privacy = PrivacyLevel.Password)] public string Password { get; set; } - [FieldDefinition(5, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Sonarr avoids conflicts with unrelated non-Sonarr downloads. Using a category is optional, but strongly recommended.")] + [FieldDefinition(5, Label = "Category", Type = FieldType.Textbox, HelpText = "DownloadClientSettingsCategoryHelpText")] public string TvCategory { get; set; } - [FieldDefinition(6, Label = "Post-Import Category", Type = FieldType.Textbox, Advanced = true, HelpText = "Category for Sonarr to set after it has imported the download. Sonarr will not remove torrents in that category even if seeding finished. Leave blank to keep same category.")] + [FieldDefinition(6, Label = "PostImportCategory", Type = FieldType.Textbox, Advanced = true, HelpText = "DownloadClientSettingsPostImportCategoryHelpText")] public string TvImportedCategory { get; set; } - [FieldDefinition(7, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(DelugePriority), HelpText = "Priority to use when grabbing episodes that aired within the last 14 days")] + [FieldDefinition(7, Label = "DownloadClientSettingsRecentPriority", Type = FieldType.Select, SelectOptions = typeof(DelugePriority), HelpText = "DownloadClientSettingsRecentPriorityHelpText")] public int RecentTvPriority { get; set; } - [FieldDefinition(8, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(DelugePriority), HelpText = "Priority to use when grabbing episodes that aired over 14 days ago")] + [FieldDefinition(8, Label = "DownloadClientSettingsOlderPriority", Type = FieldType.Select, SelectOptions = typeof(DelugePriority), HelpText = "DownloadClientSettingsOlderPriorityHelpText")] public int OlderTvPriority { get; set; } - [FieldDefinition(9, Label = "Add Paused", Type = FieldType.Checkbox)] + [FieldDefinition(9, Label = "DownloadClientSettingsAddPaused", Type = FieldType.Checkbox)] public bool AddPaused { get; set; } public NzbDroneValidationResult Validate() diff --git a/src/NzbDrone.Core/Download/Clients/DownloadStation/DownloadStationSettings.cs b/src/NzbDrone.Core/Download/Clients/DownloadStation/DownloadStationSettings.cs index 1d964377b..6bb2d5a5a 100644 --- a/src/NzbDrone.Core/Download/Clients/DownloadStation/DownloadStationSettings.cs +++ b/src/NzbDrone.Core/Download/Clients/DownloadStation/DownloadStationSettings.cs @@ -36,7 +36,8 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation [FieldDefinition(1, Label = "Port", Type = FieldType.Textbox)] public int Port { get; set; } - [FieldDefinition(2, Label = "Use SSL", Type = FieldType.Checkbox, HelpText = "Use secure connection when connecting to Download Station")] + [FieldDefinition(2, Label = "UseSsl", Type = FieldType.Checkbox, HelpText = "DownloadClientSettingsUseSslHelpText")] + [FieldToken(TokenField.HelpText, "UseSsl", "clientName", "Download Station")] public bool UseSsl { get; set; } [FieldDefinition(3, Label = "Username", Type = FieldType.Textbox, Privacy = PrivacyLevel.UserName)] @@ -45,10 +46,10 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation [FieldDefinition(4, Label = "Password", Type = FieldType.Password, Privacy = PrivacyLevel.Password)] public string Password { get; set; } - [FieldDefinition(5, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Sonarr avoids conflicts with unrelated non-Sonarr downloads. Using a category is optional, but strongly recommended.. Creates a [category] subdirectory in the output directory.")] + [FieldDefinition(5, Label = "Category", Type = FieldType.Textbox, HelpText = "DownloadClientSettingsCategorySubFolderHelpText")] public string TvCategory { get; set; } - [FieldDefinition(6, Label = "Directory", Type = FieldType.Textbox, HelpText = "Optional shared folder to put downloads into, leave blank to use the default Download Station location")] + [FieldDefinition(6, Label = "Directory", Type = FieldType.Textbox, HelpText = "DownloadClientDownloadStationSettingsDirectory")] public string TvDirectory { get; set; } public DownloadStationSettings() diff --git a/src/NzbDrone.Core/Download/Clients/DownloadStation/TorrentDownloadStation.cs b/src/NzbDrone.Core/Download/Clients/DownloadStation/TorrentDownloadStation.cs index 55fed1410..72b3ebbcb 100644 --- a/src/NzbDrone.Core/Download/Clients/DownloadStation/TorrentDownloadStation.cs +++ b/src/NzbDrone.Core/Download/Clients/DownloadStation/TorrentDownloadStation.cs @@ -10,6 +10,7 @@ using NzbDrone.Common.Extensions; using NzbDrone.Common.Http; using NzbDrone.Core.Configuration; using NzbDrone.Core.Download.Clients.DownloadStation.Proxies; +using NzbDrone.Core.Localization; using NzbDrone.Core.MediaFiles.TorrentInfo; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.RemotePathMappings; @@ -36,8 +37,9 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation IConfigService configService, IDiskProvider diskProvider, IRemotePathMappingService remotePathMappingService, - Logger logger) - : base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, logger) + Logger logger, + ILocalizationService localizationService) + : base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, logger, localizationService) { _dsInfoProxy = dsInfoProxy; _dsTaskProxySelector = dsTaskProxySelector; @@ -48,7 +50,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation public override string Name => "Download Station"; - public override ProviderMessage Message => new ProviderMessage("Sonarr is unable to connect to Download Station if 2-Factor Authentication is enabled on your DSM account", ProviderMessageType.Warning); + public override ProviderMessage Message => new ProviderMessage(_localizationService.GetLocalizedString("DownloadClientDownloadStationProviderMessage"), ProviderMessageType.Warning); private IDownloadStationTaskProxy DsTaskProxy => _dsTaskProxySelector.GetProxy(Settings); @@ -222,7 +224,9 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation { if (torrent.Status == DownloadStationTaskStatus.Extracting) { - return $"Extracting: {int.Parse(torrent.StatusExtra["unzip_progress"])}%"; + return _localizationService.GetLocalizedString("DownloadStationStatusExtracting", + new Dictionary<string, object> + { { "progress", int.Parse(torrent.StatusExtra["unzip_progress"]) } }); } if (torrent.Status == DownloadStationTaskStatus.Error) @@ -308,9 +312,9 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation if (downloadDir == null) { - return new NzbDroneValidationFailure(nameof(Settings.TvDirectory), "No default destination") + return new NzbDroneValidationFailure(nameof(Settings.TvDirectory), "DownloadClientDownloadStationValidationNoDefaultDestination") { - DetailedDescription = $"You must login into your Diskstation as {Settings.Username} and manually set it up into DownloadStation settings under BT/HTTP/FTP/NZB -> Location." + DetailedDescription = _localizationService.GetLocalizedString("DownloadClientDownloadStationValidationNoDefaultDestinationDetail", new Dictionary<string, object> { { "username", Settings.Username } }) }; } @@ -325,17 +329,17 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation if (folderInfo.Additional == null) { - return new NzbDroneValidationFailure(fieldName, $"Shared folder does not exist") + return new NzbDroneValidationFailure(fieldName, _localizationService.GetLocalizedString("DownloadClientDownloadStationValidationSharedFolderMissing")) { - DetailedDescription = $"The Diskstation does not have a Shared Folder with the name '{sharedFolder}', are you sure you specified it correctly?" + DetailedDescription = _localizationService.GetLocalizedString("DownloadClientDownloadStationValidationSharedFolderMissingDetail", new Dictionary<string, object> { { "sharedFolder", sharedFolder } }) }; } if (!folderInfo.IsDir) { - return new NzbDroneValidationFailure(fieldName, $"Folder does not exist") + return new NzbDroneValidationFailure(fieldName, _localizationService.GetLocalizedString("DownloadClientDownloadStationValidationFolderMissing")) { - DetailedDescription = $"The folder '{downloadDir}' does not exist, it must be created manually inside the Shared Folder '{sharedFolder}'." + DetailedDescription = _localizationService.GetLocalizedString("DownloadClientDownloadStationValidationFolderMissingDetail", new Dictionary<string, object> { { "downloadDir", downloadDir }, { "sharedFolder", sharedFolder } }) }; } } @@ -351,7 +355,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation catch (Exception ex) { _logger.Error(ex, "Error testing Torrent Download Station"); - return new NzbDroneValidationFailure(string.Empty, $"Unknown exception: {ex.Message}"); + return new NzbDroneValidationFailure(string.Empty, _localizationService.GetLocalizedString("DownloadClientValidationUnknownException", new Dictionary<string, object> { { "exception", ex.Message } })); } } @@ -364,9 +368,9 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation catch (DownloadClientAuthenticationException ex) { _logger.Error(ex, ex.Message); - return new NzbDroneValidationFailure("Username", "Authentication failure") + return new NzbDroneValidationFailure("Username", _localizationService.GetLocalizedString("DownloadClientValidationAuthenticationFailure")) { - DetailedDescription = $"Please verify your username and password. Also verify if the host running Sonarr isn't blocked from accessing {Name} by WhiteList limitations in the {Name} configuration." + DetailedDescription = _localizationService.GetLocalizedString("DownloadClientValidationAuthenticationFailureDetail", new Dictionary<string, object> { { "clientName", Name } }) }; } catch (WebException ex) @@ -375,19 +379,19 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation if (ex.Status == WebExceptionStatus.ConnectFailure) { - return new NzbDroneValidationFailure("Host", "Unable to connect") + return new NzbDroneValidationFailure("Host", _localizationService.GetLocalizedString("DownloadClientValidationUnableToConnect", new Dictionary<string, object> { { "clientName", Name } })) { - DetailedDescription = "Please verify the hostname and port." + DetailedDescription = _localizationService.GetLocalizedString("DownloadClientValidationUnableToConnectDetail") }; } - return new NzbDroneValidationFailure(string.Empty, $"Unknown exception: {ex.Message}"); + return new NzbDroneValidationFailure(string.Empty, _localizationService.GetLocalizedString("DownloadClientValidationUnknownException", new Dictionary<string, object> { { "exception", ex.Message } })); } catch (Exception ex) { _logger.Error(ex, "Error testing Torrent Download Station"); - return new NzbDroneValidationFailure("Host", "Unable to connect to Torrent Download Station") + return new NzbDroneValidationFailure("Host", _localizationService.GetLocalizedString("DownloadClientValidationUnableToConnect", new Dictionary<string, object> { { "clientName", Name } })) { DetailedDescription = ex.Message }; @@ -402,7 +406,12 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation if (info.MinVersion > 2 || info.MaxVersion < 2) { - return new ValidationFailure(string.Empty, $"Download Station API version not supported, should be at least 2. It supports from {info.MinVersion} to {info.MaxVersion}"); + return new ValidationFailure(string.Empty, + _localizationService.GetLocalizedString("DownloadClientDownloadStationValidationApiVersion", + new Dictionary<string, object> + { + { "requiredVersion", 2 }, { "minVersion", info.MinVersion }, { "maxVersion", info.MaxVersion } + })); } return null; @@ -417,7 +426,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation } catch (Exception ex) { - return new NzbDroneValidationFailure(string.Empty, $"Failed to get the list of torrents: {ex.Message}"); + return new NzbDroneValidationFailure(string.Empty, _localizationService.GetLocalizedString("DownloadClientValidationTestTorrents", new Dictionary<string, object> { { "exceptionMessage", ex.Message } })); } } diff --git a/src/NzbDrone.Core/Download/Clients/DownloadStation/UsenetDownloadStation.cs b/src/NzbDrone.Core/Download/Clients/DownloadStation/UsenetDownloadStation.cs index 27bc2bbdf..6f89845a9 100644 --- a/src/NzbDrone.Core/Download/Clients/DownloadStation/UsenetDownloadStation.cs +++ b/src/NzbDrone.Core/Download/Clients/DownloadStation/UsenetDownloadStation.cs @@ -9,6 +9,7 @@ using NzbDrone.Common.Extensions; using NzbDrone.Common.Http; using NzbDrone.Core.Configuration; using NzbDrone.Core.Download.Clients.DownloadStation.Proxies; +using NzbDrone.Core.Localization; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.RemotePathMappings; using NzbDrone.Core.ThingiProvider; @@ -34,8 +35,9 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation IDiskProvider diskProvider, IRemotePathMappingService remotePathMappingService, IValidateNzbs nzbValidationService, - Logger logger) - : base(httpClient, configService, diskProvider, remotePathMappingService, nzbValidationService, logger) + Logger logger, + ILocalizationService localizationService) + : base(httpClient, configService, diskProvider, remotePathMappingService, nzbValidationService, logger, localizationService) { _dsInfoProxy = dsInfoProxy; _dsTaskProxySelector = dsTaskProxySelector; @@ -46,7 +48,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation public override string Name => "Download Station"; - public override ProviderMessage Message => new ProviderMessage("Sonarr is unable to connect to Download Station if 2-Factor Authentication is enabled on your DSM account", ProviderMessageType.Warning); + public override ProviderMessage Message => new ProviderMessage(_localizationService.GetLocalizedString("DownloadClientDownloadStationProviderMessage"), ProviderMessageType.Warning); private IDownloadStationTaskProxy DsTaskProxy => _dsTaskProxySelector.GetProxy(Settings); @@ -213,9 +215,9 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation if (downloadDir == null) { - return new NzbDroneValidationFailure(nameof(Settings.TvDirectory), "No default destination") + return new NzbDroneValidationFailure(nameof(Settings.TvDirectory), "DownloadClientDownloadStationValidationNoDefaultDestination") { - DetailedDescription = $"You must login into your Diskstation as {Settings.Username} and manually set it up into DownloadStation settings under BT/HTTP/FTP/NZB -> Location." + DetailedDescription = _localizationService.GetLocalizedString("DownloadClientDownloadStationValidationNoDefaultDestinationDetail", new Dictionary<string, object> { { "username", Settings.Username } }) }; } @@ -230,17 +232,17 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation if (folderInfo.Additional == null) { - return new NzbDroneValidationFailure(fieldName, $"Shared folder does not exist") + return new NzbDroneValidationFailure(fieldName, _localizationService.GetLocalizedString("DownloadClientDownloadStationValidationSharedFolderMissing")) { - DetailedDescription = $"The Diskstation does not have a Shared Folder with the name '{sharedFolder}', are you sure you specified it correctly?" + DetailedDescription = _localizationService.GetLocalizedString("DownloadClientDownloadStationValidationSharedFolderMissingDetail", new Dictionary<string, object> { { "sharedFolder", sharedFolder } }) }; } if (!folderInfo.IsDir) { - return new NzbDroneValidationFailure(fieldName, $"Folder does not exist") + return new NzbDroneValidationFailure(fieldName, _localizationService.GetLocalizedString("DownloadClientDownloadStationValidationFolderMissing")) { - DetailedDescription = $"The folder '{downloadDir}' does not exist, it must be created manually inside the Shared Folder '{sharedFolder}'." + DetailedDescription = _localizationService.GetLocalizedString("DownloadClientDownloadStationValidationFolderMissingDetail", new Dictionary<string, object> { { "downloadDir", downloadDir }, { "sharedFolder", sharedFolder } }) }; } } @@ -256,7 +258,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation catch (Exception ex) { _logger.Error(ex, "Error testing Usenet Download Station"); - return new NzbDroneValidationFailure(string.Empty, $"Unknown exception: {ex.Message}"); + return new NzbDroneValidationFailure(string.Empty, _localizationService.GetLocalizedString("DownloadClientValidationUnknownException", new Dictionary<string, object> { { "exception", ex.Message } })); } } @@ -269,9 +271,9 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation catch (DownloadClientAuthenticationException ex) { _logger.Error(ex, ex.Message); - return new NzbDroneValidationFailure("Username", "Authentication failure") + return new NzbDroneValidationFailure("Username", _localizationService.GetLocalizedString("DownloadClientValidationAuthenticationFailure")) { - DetailedDescription = $"Please verify your username and password. Also verify if the host running Sonarr isn't blocked from accessing {Name} by WhiteList limitations in the {Name} configuration." + DetailedDescription = _localizationService.GetLocalizedString("DownloadClientValidationAuthenticationFailureDetail", new Dictionary<string, object> { { "clientName", Name } }) }; } catch (WebException ex) @@ -280,19 +282,19 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation if (ex.Status == WebExceptionStatus.ConnectFailure) { - return new NzbDroneValidationFailure("Host", "Unable to connect") + return new NzbDroneValidationFailure("Host", _localizationService.GetLocalizedString("DownloadClientValidationUnableToConnect", new Dictionary<string, object> { { "clientName", Name } })) { - DetailedDescription = "Please verify the hostname and port." + DetailedDescription = _localizationService.GetLocalizedString("DownloadClientValidationUnableToConnectDetail") }; } - return new NzbDroneValidationFailure(string.Empty, "Unknown exception: " + ex.Message); + return new NzbDroneValidationFailure(string.Empty, _localizationService.GetLocalizedString("DownloadClientValidationUnknownException", new Dictionary<string, object> { { "exception", ex.Message } })); } catch (Exception ex) { _logger.Error(ex, "Error testing Torrent Download Station"); - return new NzbDroneValidationFailure("Host", "Unable to connect to Usenet Download Station") + return new NzbDroneValidationFailure("Host", _localizationService.GetLocalizedString("DownloadClientValidationUnableToConnect", new Dictionary<string, object> { { "clientName", Name } })) { DetailedDescription = ex.Message }; @@ -307,7 +309,12 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation if (info.MinVersion > 2 || info.MaxVersion < 2) { - return new ValidationFailure(string.Empty, $"Download Station API version not supported, should be at least 2. It supports from {info.MinVersion} to {info.MaxVersion}"); + return new ValidationFailure(string.Empty, + _localizationService.GetLocalizedString("DownloadClientDownloadStationValidationApiVersion", + new Dictionary<string, object> + { + { "requiredVersion", 2 }, { "minVersion", info.MinVersion }, { "maxVersion", info.MaxVersion } + })); } return null; @@ -319,7 +326,9 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation { if (task.Status == DownloadStationTaskStatus.Extracting) { - return $"Extracting: {int.Parse(task.StatusExtra["unzip_progress"])}%"; + return _localizationService.GetLocalizedString("DownloadStationStatusExtracting", + new Dictionary<string, object> + { { "progress", int.Parse(task.StatusExtra["unzip_progress"]) } }); } if (task.Status == DownloadStationTaskStatus.Error) @@ -398,7 +407,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation } catch (Exception ex) { - return new NzbDroneValidationFailure(string.Empty, "Failed to get the list of NZBs: " + ex.Message); + return new NzbDroneValidationFailure(string.Empty, _localizationService.GetLocalizedString("DownloadClientValidationTestNzbs", new Dictionary<string, object> { { "exceptionMessage", ex.Message } })); } } diff --git a/src/NzbDrone.Core/Download/Clients/Flood/Flood.cs b/src/NzbDrone.Core/Download/Clients/Flood/Flood.cs index abbc62e6f..f101f715d 100644 --- a/src/NzbDrone.Core/Download/Clients/Flood/Flood.cs +++ b/src/NzbDrone.Core/Download/Clients/Flood/Flood.cs @@ -9,6 +9,7 @@ using NzbDrone.Common.Extensions; using NzbDrone.Common.Http; using NzbDrone.Core.Configuration; using NzbDrone.Core.Download.Clients.Flood.Models; +using NzbDrone.Core.Localization; using NzbDrone.Core.MediaFiles.TorrentInfo; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.RemotePathMappings; @@ -28,8 +29,9 @@ namespace NzbDrone.Core.Download.Clients.Flood IConfigService configService, IDiskProvider diskProvider, IRemotePathMappingService remotePathMappingService, - Logger logger) - : base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, logger) + Logger logger, + ILocalizationService localizationService) + : base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, logger, localizationService) { _proxy = proxy; _downloadSeedConfigProvider = downloadSeedConfigProvider; @@ -81,7 +83,7 @@ namespace NzbDrone.Core.Download.Clients.Flood } public override string Name => "Flood"; - public override ProviderMessage Message => new ProviderMessage("Sonarr will handle automatic removal of torrents based on the current seed criteria in Settings -> Indexers", ProviderMessageType.Info); + public override ProviderMessage Message => new ProviderMessage(_localizationService.GetLocalizedString("DownloadClientFloodSettingsRemovalInfo"), ProviderMessageType.Info); protected override string AddFromTorrentFile(RemoteEpisode remoteEpisode, string hash, string filename, byte[] fileContent) { diff --git a/src/NzbDrone.Core/Download/Clients/Flood/FloodSettings.cs b/src/NzbDrone.Core/Download/Clients/Flood/FloodSettings.cs index 9877b9707..f26e19512 100644 --- a/src/NzbDrone.Core/Download/Clients/Flood/FloodSettings.cs +++ b/src/NzbDrone.Core/Download/Clients/Flood/FloodSettings.cs @@ -40,10 +40,12 @@ namespace NzbDrone.Core.Download.Clients.Flood [FieldDefinition(1, Label = "Port", Type = FieldType.Textbox)] public int Port { get; set; } - [FieldDefinition(2, Label = "Use SSL", Type = FieldType.Checkbox, HelpText = "Use secure connection when connecting to Flood")] + [FieldDefinition(2, Label = "UseSsl", Type = FieldType.Checkbox, HelpText = "DownloadClientSettingsUseSslHelpText")] + [FieldToken(TokenField.HelpText, "UseSsl", "clientName", "Flood")] public bool UseSsl { get; set; } - [FieldDefinition(3, Label = "Url Base", Type = FieldType.Textbox, HelpText = "Optionally adds a prefix to Flood API, such as [protocol]://[host]:[port]/[urlBase]api")] + [FieldDefinition(3, Label = "UrlBase", Type = FieldType.Textbox, HelpText = "DownloadClientFloodSettingsUrlBaseHelpText")] + [FieldToken(TokenField.HelpText, "UrlBase", "url", "[protocol]://[host]:[port]/[urlBase]/api")] public string UrlBase { get; set; } [FieldDefinition(4, Label = "Username", Type = FieldType.Textbox, Privacy = PrivacyLevel.UserName)] @@ -52,19 +54,19 @@ namespace NzbDrone.Core.Download.Clients.Flood [FieldDefinition(5, Label = "Password", Type = FieldType.Password, Privacy = PrivacyLevel.Password)] public string Password { get; set; } - [FieldDefinition(6, Label = "Destination", Type = FieldType.Textbox, HelpText = "Manually specifies download destination")] + [FieldDefinition(6, Label = "Destination", Type = FieldType.Textbox, HelpText = "DownloadClientSettingsDestinationHelpText")] public string Destination { get; set; } - [FieldDefinition(7, Label = "Tags", Type = FieldType.Tag, HelpText = "Initial tags of a download. To be recognized, a download must have all initial tags. This avoids conflicts with unrelated downloads.")] + [FieldDefinition(7, Label = "Tags", Type = FieldType.Tag, HelpText = "DownloadClientFloodSettingsTagsHelpText")] public IEnumerable<string> Tags { get; set; } - [FieldDefinition(8, Label = "Post-Import Tags", Type = FieldType.Tag, HelpText = "Appends tags after a download is imported.", Advanced = true)] + [FieldDefinition(8, Label = "DownloadClientFloodSettingsPostImportTags", Type = FieldType.Tag, HelpText = "DownloadClientFloodSettingsPostImportTagsHelpText", Advanced = true)] public IEnumerable<string> PostImportTags { get; set; } - [FieldDefinition(9, Label = "Additional Tags", Type = FieldType.Select, SelectOptions = typeof(AdditionalTags), HelpText = "Adds properties of media as tags. Hints are examples.", Advanced = true)] + [FieldDefinition(9, Label = "DownloadClientFloodSettingsAdditionalTags", Type = FieldType.Select, SelectOptions = typeof(AdditionalTags), HelpText = "DownloadClientFloodSettingsAdditionalTagsHelpText", Advanced = true)] public IEnumerable<int> AdditionalTags { get; set; } - [FieldDefinition(10, Label = "Start on Add", Type = FieldType.Checkbox)] + [FieldDefinition(10, Label = "DownloadClientFloodSettingsStartOnAdd", Type = FieldType.Checkbox)] public bool StartOnAdd { get; set; } public NzbDroneValidationResult Validate() diff --git a/src/NzbDrone.Core/Download/Clients/FreeboxDownload/FreeboxDownloadSettings.cs b/src/NzbDrone.Core/Download/Clients/FreeboxDownload/FreeboxDownloadSettings.cs index 45de36ef4..62de8f46c 100644 --- a/src/NzbDrone.Core/Download/Clients/FreeboxDownload/FreeboxDownloadSettings.cs +++ b/src/NzbDrone.Core/Download/Clients/FreeboxDownload/FreeboxDownloadSettings.cs @@ -46,37 +46,42 @@ namespace NzbDrone.Core.Download.Clients.FreeboxDownload ApiUrl = "/api/v1/"; } - [FieldDefinition(0, Label = "Host", Type = FieldType.Textbox, HelpText = "Hostname or host IP address of the Freebox, defaults to 'mafreebox.freebox.fr' (will only work if on same network)")] + [FieldDefinition(0, Label = "Host", Type = FieldType.Textbox, HelpText = "DownloadClientFreeboxSettingsHostHelpText")] + [FieldToken(TokenField.HelpText, "Host", "url", "mafreebox.freebox.fr")] public string Host { get; set; } - [FieldDefinition(1, Label = "Port", Type = FieldType.Textbox, HelpText = "Port used to access Freebox interface, defaults to '443'")] + [FieldDefinition(1, Label = "Port", Type = FieldType.Textbox, HelpText = "DownloadClientFreeboxSettingsPortHelpText")] + [FieldToken(TokenField.HelpText, "Port", "port", 443)] public int Port { get; set; } - [FieldDefinition(2, Label = "Use SSL", Type = FieldType.Checkbox, HelpText = "Use secured connection when connecting to Freebox API")] + [FieldDefinition(2, Label = "UseSsl", Type = FieldType.Checkbox, HelpText = "DownloadClientSettingsUseSslHelpText")] + [FieldToken(TokenField.HelpText, "UseSsl", "clientName", "Freebox API")] public bool UseSsl { get; set; } - [FieldDefinition(3, Label = "API URL", Type = FieldType.Textbox, Advanced = true, HelpText = "Define Freebox API base URL with API version, eg http://[host]:[port]/[api_base_url]/[api_version]/, defaults to '/api/v1/'")] + [FieldDefinition(3, Label = "DownloadClientFreeboxSettingsApiUrl", Type = FieldType.Textbox, Advanced = true, HelpText = "DownloadClientFreeboxSettingsApiUrlHelpText")] + [FieldToken(TokenField.HelpText, "DownloadClientFreeboxSettingsApiUrl", "url", "http://[host]:[port]/[api_base_url]/[api_version]/")] + [FieldToken(TokenField.HelpText, "DownloadClientFreeboxSettingsApiUrl", "defaultApiUrl", "/api/v1/")] public string ApiUrl { get; set; } - [FieldDefinition(4, Label = "App ID", Type = FieldType.Textbox, HelpText = "App ID given when creating access to Freebox API (ie 'app_id')")] + [FieldDefinition(4, Label = "DownloadClientFreeboxSettingsAppId", Type = FieldType.Textbox, HelpText = "DownloadClientFreeboxSettingsAppIdHelpText")] public string AppId { get; set; } - [FieldDefinition(5, Label = "App Token", Type = FieldType.Password, Privacy = PrivacyLevel.Password, HelpText = "App token retrieved when creating access to Freebox API (ie 'app_token')")] + [FieldDefinition(5, Label = "DownloadClientFreeboxSettingsAppToken", Type = FieldType.Password, Privacy = PrivacyLevel.Password, HelpText = "DownloadClientFreeboxSettingsAppTokenHelpText")] public string AppToken { get; set; } - [FieldDefinition(6, Label = "Destination Directory", Type = FieldType.Textbox, Advanced = true, HelpText = "Optional location to put downloads in, leave blank to use the default Freebox download location")] + [FieldDefinition(6, Label = "Destination", Type = FieldType.Textbox, Advanced = true, HelpText = "DownloadClientSettingsDestinationHelpText")] public string DestinationDirectory { get; set; } - [FieldDefinition(7, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Sonarr avoids conflicts with unrelated non-Sonarr downloads (will create a [category] subdirectory in the output directory)")] + [FieldDefinition(7, Label = "Category", Type = FieldType.Textbox, HelpText = "DownloadClientSettingsCategorySubFolderHelpText")] public string Category { get; set; } - [FieldDefinition(8, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(FreeboxDownloadPriority), HelpText = "Priority to use when grabbing episodes that aired within the last 14 days")] + [FieldDefinition(8, Label = "DownloadClientSettingsRecentPriority", Type = FieldType.Select, SelectOptions = typeof(FreeboxDownloadPriority), HelpText = "DownloadClientSettingsRecentPriorityHelpText")] public int RecentPriority { get; set; } - [FieldDefinition(9, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(FreeboxDownloadPriority), HelpText = "Priority to use when grabbing episodes that aired over 14 days ago")] + [FieldDefinition(9, Label = "DownloadClientSettingsOlderPriority", Type = FieldType.Select, SelectOptions = typeof(FreeboxDownloadPriority), HelpText = "DownloadClientSettingsOlderPriorityHelpText")] public int OlderPriority { get; set; } - [FieldDefinition(10, Label = "Add Paused", Type = FieldType.Checkbox)] + [FieldDefinition(10, Label = "DownloadClientSettingsAddPaused", Type = FieldType.Checkbox)] public bool AddPaused { get; set; } public NzbDroneValidationResult Validate() diff --git a/src/NzbDrone.Core/Download/Clients/FreeboxDownload/TorrentFreeboxDownload.cs b/src/NzbDrone.Core/Download/Clients/FreeboxDownload/TorrentFreeboxDownload.cs index 449afa7b3..6f020b57b 100644 --- a/src/NzbDrone.Core/Download/Clients/FreeboxDownload/TorrentFreeboxDownload.cs +++ b/src/NzbDrone.Core/Download/Clients/FreeboxDownload/TorrentFreeboxDownload.cs @@ -8,6 +8,7 @@ using NzbDrone.Common.Extensions; using NzbDrone.Common.Http; using NzbDrone.Core.Configuration; using NzbDrone.Core.Download.Clients.FreeboxDownload.Responses; +using NzbDrone.Core.Localization; using NzbDrone.Core.MediaFiles.TorrentInfo; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.RemotePathMappings; @@ -24,8 +25,9 @@ namespace NzbDrone.Core.Download.Clients.FreeboxDownload IConfigService configService, IDiskProvider diskProvider, IRemotePathMappingService remotePathMappingService, - Logger logger) - : base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, logger) + Logger logger, + ILocalizationService localizationService) + : base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, logger, localizationService) { _proxy = proxy; } @@ -108,8 +110,9 @@ namespace NzbDrone.Core.Download.Clients.FreeboxDownload case FreeboxDownloadTaskStatus.Unknown: default: // new status in API? default to downloading - item.Message = "Unknown download state: " + torrent.Status; - _logger.Info(item.Message); + item.Message = _localizationService.GetLocalizedString("UnknownDownloadState", + new Dictionary<string, object> { { "state", torrent.Status } }); + _logger.Info($"Unknown download state: {torrent.Status}"); item.Status = DownloadItemStatus.Downloading; break; } diff --git a/src/NzbDrone.Core/Download/Clients/Hadouken/Hadouken.cs b/src/NzbDrone.Core/Download/Clients/Hadouken/Hadouken.cs index 9773065aa..2d50030e7 100644 --- a/src/NzbDrone.Core/Download/Clients/Hadouken/Hadouken.cs +++ b/src/NzbDrone.Core/Download/Clients/Hadouken/Hadouken.cs @@ -7,6 +7,7 @@ using NzbDrone.Common.Extensions; using NzbDrone.Common.Http; using NzbDrone.Core.Configuration; using NzbDrone.Core.Download.Clients.Hadouken.Models; +using NzbDrone.Core.Localization; using NzbDrone.Core.MediaFiles.TorrentInfo; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.RemotePathMappings; @@ -24,8 +25,9 @@ namespace NzbDrone.Core.Download.Clients.Hadouken IConfigService configService, IDiskProvider diskProvider, IRemotePathMappingService remotePathMappingService, - Logger logger) - : base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, logger) + Logger logger, + ILocalizationService localizationService) + : base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, logger, localizationService) { _proxy = proxy; } @@ -159,18 +161,20 @@ namespace NzbDrone.Core.Download.Clients.Hadouken if (version < new Version("5.1")) { return new ValidationFailure(string.Empty, - "Old Hadouken client with unsupported API, need 5.1 or higher"); + _localizationService.GetLocalizedString("DownloadClientValidationErrorVersion", + new Dictionary<string, object> + { { "clientName", Name }, { "requiredVersion", "5.1" }, { "reportedVersion", version } })); } } catch (DownloadClientAuthenticationException ex) { _logger.Error(ex, ex.Message); - return new NzbDroneValidationFailure("Password", "Authentication failed"); + return new NzbDroneValidationFailure("Password", _localizationService.GetLocalizedString("DownloadClientValidationAuthenticationFailure")); } catch (Exception ex) { - return new NzbDroneValidationFailure("Host", "Unable to connect to Hadouken") + return new NzbDroneValidationFailure("Host", _localizationService.GetLocalizedString("DownloadClientValidationUnableToConnect")) { DetailedDescription = ex.Message }; @@ -188,7 +192,7 @@ namespace NzbDrone.Core.Download.Clients.Hadouken catch (Exception ex) { _logger.Error(ex, ex.Message); - return new NzbDroneValidationFailure(string.Empty, "Failed to get the list of torrents: " + ex.Message); + return new NzbDroneValidationFailure(string.Empty, _localizationService.GetLocalizedString("DownloadClientValidationTestTorrents", new Dictionary<string, object> { { "exceptionMessage", ex.Message } })); } return null; diff --git a/src/NzbDrone.Core/Download/Clients/Hadouken/HadoukenSettings.cs b/src/NzbDrone.Core/Download/Clients/Hadouken/HadoukenSettings.cs index 5b0603dd7..8e560720e 100644 --- a/src/NzbDrone.Core/Download/Clients/Hadouken/HadoukenSettings.cs +++ b/src/NzbDrone.Core/Download/Clients/Hadouken/HadoukenSettings.cs @@ -39,10 +39,13 @@ namespace NzbDrone.Core.Download.Clients.Hadouken [FieldDefinition(1, Label = "Port", Type = FieldType.Textbox)] public int Port { get; set; } - [FieldDefinition(2, Label = "Use SSL", Type = FieldType.Checkbox, HelpText = "Use secure connection when connecting to Hadouken")] + [FieldDefinition(2, Label = "UseSsl", Type = FieldType.Checkbox, HelpText = "DownloadClientSettingsUseSslHelpText")] + [FieldToken(TokenField.HelpText, "UseSsl", "clientName", "Hadouken")] public bool UseSsl { get; set; } - [FieldDefinition(3, Label = "Url Base", Type = FieldType.Textbox, Advanced = true, HelpText = "Adds a prefix to the Hadouken url, e.g. http://[host]:[port]/[urlBase]/api")] + [FieldDefinition(3, Label = "UrlBase", Type = FieldType.Textbox, Advanced = true, HelpText = "DownloadClientSettingsUrlBaseHelpText")] + [FieldToken(TokenField.HelpText, "UrlBase", "clientName", "Hadouken")] + [FieldToken(TokenField.HelpText, "UrlBase", "url", "http://[host]:[port]/[urlBase]/api")] public string UrlBase { get; set; } [FieldDefinition(4, Label = "Username", Type = FieldType.Textbox, Privacy = PrivacyLevel.UserName)] @@ -51,7 +54,7 @@ namespace NzbDrone.Core.Download.Clients.Hadouken [FieldDefinition(5, Label = "Password", Type = FieldType.Password, Privacy = PrivacyLevel.Password)] public string Password { get; set; } - [FieldDefinition(6, Label = "Category", Type = FieldType.Textbox)] + [FieldDefinition(6, Label = "Category", Type = FieldType.Textbox, HelpText = "DownloadClientSettingsCategoryHelpText")] public string Category { get; set; } public NzbDroneValidationResult Validate() diff --git a/src/NzbDrone.Core/Download/Clients/NzbVortex/NzbVortex.cs b/src/NzbDrone.Core/Download/Clients/NzbVortex/NzbVortex.cs index 5e5fb3acd..dbdfdb7c4 100644 --- a/src/NzbDrone.Core/Download/Clients/NzbVortex/NzbVortex.cs +++ b/src/NzbDrone.Core/Download/Clients/NzbVortex/NzbVortex.cs @@ -8,6 +8,7 @@ using NzbDrone.Common.Disk; using NzbDrone.Common.Extensions; using NzbDrone.Common.Http; using NzbDrone.Core.Configuration; +using NzbDrone.Core.Localization; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.RemotePathMappings; using NzbDrone.Core.Validation; @@ -24,8 +25,9 @@ namespace NzbDrone.Core.Download.Clients.NzbVortex IDiskProvider diskProvider, IRemotePathMappingService remotePathMappingService, IValidateNzbs nzbValidationService, - Logger logger) - : base(httpClient, configService, diskProvider, remotePathMappingService, nzbValidationService, logger) + Logger logger, + ILocalizationService localizationService) + : base(httpClient, configService, diskProvider, remotePathMappingService, nzbValidationService, logger, localizationService) { _proxy = proxy; } @@ -162,7 +164,7 @@ namespace NzbDrone.Core.Download.Clients.NzbVortex { _logger.Error(ex, "Unable to connect to NZBVortex"); - return new NzbDroneValidationFailure("Host", "Unable to connect to NZBVortex") + return new NzbDroneValidationFailure("Host", _localizationService.GetLocalizedString("DownloadClientValidationUnableToConnect", new Dictionary<string, object> { { "clientName", Name } })) { DetailedDescription = ex.Message }; @@ -180,13 +182,16 @@ namespace NzbDrone.Core.Download.Clients.NzbVortex if (version.Major < 2 || (version.Major == 2 && version.Minor < 3)) { - return new ValidationFailure("Host", "NZBVortex needs to be updated"); + return new ValidationFailure("Host", + _localizationService.GetLocalizedString("DownloadClientValidationErrorVersion", + new Dictionary<string, object> + { { "clientName", Name }, { "requiredVersion", "2.3" }, { "reportedVersion", version } })); } } catch (Exception ex) { _logger.Error(ex, "Unable to connect to NZBVortex"); - return new ValidationFailure("Host", "Unable to connect to NZBVortex"); + return new NzbDroneValidationFailure("Host", _localizationService.GetLocalizedString("DownloadClientValidationUnableToConnect", new Dictionary<string, object> { { "clientName", Name } })); } return null; @@ -200,7 +205,7 @@ namespace NzbDrone.Core.Download.Clients.NzbVortex } catch (NzbVortexAuthenticationException) { - return new ValidationFailure("ApiKey", "API Key Incorrect"); + return new ValidationFailure("ApiKey", _localizationService.GetLocalizedString("DownloadClientValidationApiKeyIncorrect")); } return null; @@ -214,9 +219,9 @@ namespace NzbDrone.Core.Download.Clients.NzbVortex { if (Settings.TvCategory.IsNotNullOrWhiteSpace()) { - return new NzbDroneValidationFailure("TvCategory", "Group does not exist") + return new NzbDroneValidationFailure("TvCategory", _localizationService.GetLocalizedString("DownloadClientValidationGroupMissing")) { - DetailedDescription = "The Group you entered doesn't exist in NzbVortex. Go to NzbVortex to create it." + DetailedDescription = _localizationService.GetLocalizedString("DownloadClientValidationGroupMissingDetail", new Dictionary<string, object> { { "clientName", Name } }) }; } } @@ -243,7 +248,7 @@ namespace NzbDrone.Core.Download.Clients.NzbVortex if (filesResponse.Count > 1) { - var message = string.Format("Download contains multiple files and is not in a job folder: {0}", outputPath); + var message = _localizationService.GetLocalizedString("DownloadClientNzbVortexMultipleFilesMessage", new Dictionary<string, object> { { "outputPath", outputPath } }); queueItem.Status = DownloadItemStatus.Warning; queueItem.Message = message; diff --git a/src/NzbDrone.Core/Download/Clients/NzbVortex/NzbVortexSettings.cs b/src/NzbDrone.Core/Download/Clients/NzbVortex/NzbVortexSettings.cs index d73c1853e..31169f74d 100644 --- a/src/NzbDrone.Core/Download/Clients/NzbVortex/NzbVortexSettings.cs +++ b/src/NzbDrone.Core/Download/Clients/NzbVortex/NzbVortexSettings.cs @@ -42,19 +42,21 @@ namespace NzbDrone.Core.Download.Clients.NzbVortex [FieldDefinition(1, Label = "Port", Type = FieldType.Textbox)] public int Port { get; set; } - [FieldDefinition(2, Label = "Url Base", Type = FieldType.Textbox, Advanced = true, HelpText = "Adds a prefix to the NZBVortex url, e.g. http://[host]:[port]/[urlBase]/api")] + [FieldDefinition(2, Label = "UrlBase", Type = FieldType.Textbox, Advanced = true, HelpText = "DownloadClientSettingsUrlBaseHelpText")] + [FieldToken(TokenField.HelpText, "UrlBase", "clientName", "NZBVortex")] + [FieldToken(TokenField.HelpText, "UrlBase", "url", "http://[host]:[port]/[urlBase]/api")] public string UrlBase { get; set; } - [FieldDefinition(3, Label = "API Key", Type = FieldType.Textbox, Privacy = PrivacyLevel.ApiKey)] + [FieldDefinition(3, Label = "ApiKey", Type = FieldType.Textbox, Privacy = PrivacyLevel.ApiKey)] public string ApiKey { get; set; } - [FieldDefinition(4, Label = "Group", Type = FieldType.Textbox, HelpText = "Adding a category specific to Sonarr avoids conflicts with unrelated non-Sonarr downloads. Using a category is optional, but strongly recommended.")] + [FieldDefinition(4, Label = "Group", Type = FieldType.Textbox, HelpText = "DownloadClientSettingsCategoryHelpText")] public string TvCategory { get; set; } - [FieldDefinition(5, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(NzbVortexPriority), HelpText = "Priority to use when grabbing episodes that aired within the last 14 days")] + [FieldDefinition(5, Label = "DownloadClientSettingsRecentPriority", Type = FieldType.Select, SelectOptions = typeof(NzbVortexPriority), HelpText = "DownloadClientSettingsRecentPriorityHelpText")] public int RecentTvPriority { get; set; } - [FieldDefinition(6, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(NzbVortexPriority), HelpText = "Priority to use when grabbing episodes that aired over 14 days ago")] + [FieldDefinition(6, Label = "DownloadClientSettingsOlderPriority", Type = FieldType.Select, SelectOptions = typeof(NzbVortexPriority), HelpText = "DownloadClientSettingsOlderPriorityHelpText")] public int OlderTvPriority { get; set; } public NzbDroneValidationResult Validate() diff --git a/src/NzbDrone.Core/Download/Clients/Nzbget/Nzbget.cs b/src/NzbDrone.Core/Download/Clients/Nzbget/Nzbget.cs index 54974815c..d7956318e 100644 --- a/src/NzbDrone.Core/Download/Clients/Nzbget/Nzbget.cs +++ b/src/NzbDrone.Core/Download/Clients/Nzbget/Nzbget.cs @@ -10,6 +10,7 @@ using NzbDrone.Common.Extensions; using NzbDrone.Common.Http; using NzbDrone.Core.Configuration; using NzbDrone.Core.Exceptions; +using NzbDrone.Core.Localization; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.RemotePathMappings; using NzbDrone.Core.Validation; @@ -28,8 +29,9 @@ namespace NzbDrone.Core.Download.Clients.Nzbget IDiskProvider diskProvider, IRemotePathMappingService remotePathMappingService, IValidateNzbs nzbValidationService, - Logger logger) - : base(httpClient, configService, diskProvider, remotePathMappingService, nzbValidationService, logger) + Logger logger, + ILocalizationService localizationService) + : base(httpClient, configService, diskProvider, remotePathMappingService, nzbValidationService, logger, localizationService) { _proxy = proxy; } @@ -124,7 +126,13 @@ namespace NzbDrone.Core.Download.Clients.Nzbget historyItem.TotalSize = MakeInt64(item.FileSizeHi, item.FileSizeLo); historyItem.OutputPath = _remotePathMappingService.RemapRemoteToLocal(Settings.Host, new OsPath(itemDir)); historyItem.Category = item.Category; - historyItem.Message = $"PAR Status: {item.ParStatus} - Unpack Status: {item.UnpackStatus} - Move Status: {item.MoveStatus} - Script Status: {item.ScriptStatus} - Delete Status: {item.DeleteStatus} - Mark Status: {item.MarkStatus}"; + historyItem.Message = _localizationService.GetLocalizedString("NzbgetHistoryItemMessage", + new Dictionary<string, object> + { + { "parStatus", item.ParStatus }, { "unpackStatus", item.UnpackStatus }, + { "moveStatus", item.MoveStatus }, { "scriptStaus", item.ScriptStatus }, + { "deleteStatus", item.DeleteStatus }, { "markStatus", item.MarkStatus } + }); historyItem.Status = DownloadItemStatus.Completed; historyItem.RemainingTime = TimeSpan.Zero; historyItem.CanMoveFiles = true; @@ -270,18 +278,23 @@ namespace NzbDrone.Core.Download.Clients.Nzbget if (Version.Parse(version) < Version.Parse("12.0")) { - return new ValidationFailure(string.Empty, "Nzbget version too low, need 12.0 or higher"); + return new ValidationFailure(string.Empty, + _localizationService.GetLocalizedString("DownloadClientValidationErrorVersion", + new Dictionary<string, object> + { { "clientName", Name }, { "requiredVersion", "12.0" }, { "reportedVersion", version } })); } } catch (Exception ex) { if (ex.Message.ContainsIgnoreCase("Authentication failed")) { - return new ValidationFailure("Username", "Authentication failed"); + return new ValidationFailure("Username", _localizationService.GetLocalizedString("DownloadClientValidationAuthenticationFailure")); } _logger.Error(ex, "Unable to connect to NZBGet"); - return new ValidationFailure("Host", "Unable to connect to NZBGet"); + return new ValidationFailure("Host", + _localizationService.GetLocalizedString("DownloadClientValidationUnableToConnect", + new Dictionary<string, object> { { "clientName", Name } })); } return null; @@ -294,10 +307,10 @@ namespace NzbDrone.Core.Download.Clients.Nzbget if (!Settings.TvCategory.IsNullOrWhiteSpace() && !categories.Any(v => v.Name == Settings.TvCategory)) { - return new NzbDroneValidationFailure("TvCategory", "Category does not exist") + return new NzbDroneValidationFailure("TvCategory", _localizationService.GetLocalizedString("DownloadClientValidationCategoryMissing")) { InfoLink = _proxy.GetBaseUrl(Settings), - DetailedDescription = "The Category your entered doesn't exist in NzbGet. Go to NzbGet to create it." + DetailedDescription = _localizationService.GetLocalizedString("DownloadClientValidationCategoryMissingDetail", new Dictionary<string, object> { { "clientName", Name } }) }; } @@ -311,18 +324,18 @@ namespace NzbDrone.Core.Download.Clients.Nzbget var keepHistory = config.GetValueOrDefault("KeepHistory", "7"); if (!int.TryParse(keepHistory, NumberStyles.None, CultureInfo.InvariantCulture, out var value) || value == 0) { - return new NzbDroneValidationFailure(string.Empty, "NzbGet setting KeepHistory should be greater than 0") + return new NzbDroneValidationFailure(string.Empty, _localizationService.GetLocalizedString("DownloadClientNzbgetValidationKeepHistoryZero")) { InfoLink = _proxy.GetBaseUrl(Settings), - DetailedDescription = "NzbGet setting KeepHistory is set to 0. Which prevents Sonarr from seeing completed downloads." + DetailedDescription = _localizationService.GetLocalizedString("DownloadClientNzbgetValidationKeepHistoryZeroDetail") }; } else if (value > 25000) { - return new NzbDroneValidationFailure(string.Empty, "NzbGet setting KeepHistory should be less than 25000") + return new NzbDroneValidationFailure(string.Empty, _localizationService.GetLocalizedString("DownloadClientNzbgetValidationKeepHistoryOverMax")) { InfoLink = _proxy.GetBaseUrl(Settings), - DetailedDescription = "NzbGet setting KeepHistory is set too high." + DetailedDescription = _localizationService.GetLocalizedString("DownloadClientNzbgetValidationKeepHistoryOverMaxDetail") }; } diff --git a/src/NzbDrone.Core/Download/Clients/Nzbget/NzbgetSettings.cs b/src/NzbDrone.Core/Download/Clients/Nzbget/NzbgetSettings.cs index 3fe98ef9c..adc8891e5 100644 --- a/src/NzbDrone.Core/Download/Clients/Nzbget/NzbgetSettings.cs +++ b/src/NzbDrone.Core/Download/Clients/Nzbget/NzbgetSettings.cs @@ -40,10 +40,13 @@ namespace NzbDrone.Core.Download.Clients.Nzbget [FieldDefinition(1, Label = "Port", Type = FieldType.Textbox)] public int Port { get; set; } - [FieldDefinition(2, Label = "Use SSL", Type = FieldType.Checkbox, HelpText = "Use secure connection when connecting to NZBGet")] + [FieldDefinition(2, Label = "UseSsl", Type = FieldType.Checkbox, HelpText = "DownloadClientSettingsUseSslHelpText")] + [FieldToken(TokenField.HelpText, "UseSsl", "clientName", "NZBGet")] public bool UseSsl { get; set; } - [FieldDefinition(3, Label = "Url Base", Type = FieldType.Textbox, Advanced = true, HelpText = "Adds a prefix to the nzbget url, e.g. http://[host]:[port]/[urlBase]/jsonrpc")] + [FieldDefinition(3, Label = "UrlBase", Type = FieldType.Textbox, Advanced = true, HelpText = "DownloadClientSettingsUrlBaseHelpText")] + [FieldToken(TokenField.HelpText, "UrlBase", "clientName", "NZBGet")] + [FieldToken(TokenField.HelpText, "UrlBase", "url", "http://[host]:[port]/[urlBase]/jsonrpc")] public string UrlBase { get; set; } [FieldDefinition(4, Label = "Username", Type = FieldType.Textbox, Privacy = PrivacyLevel.UserName)] @@ -52,16 +55,16 @@ namespace NzbDrone.Core.Download.Clients.Nzbget [FieldDefinition(5, Label = "Password", Type = FieldType.Password, Privacy = PrivacyLevel.Password)] public string Password { get; set; } - [FieldDefinition(6, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Sonarr avoids conflicts with unrelated non-Sonarr downloads. Using a category is optional, but strongly recommended.")] + [FieldDefinition(6, Label = "Category", Type = FieldType.Textbox, HelpText = "DownloadClientSettingsCategoryHelpText")] public string TvCategory { get; set; } - [FieldDefinition(7, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(NzbgetPriority), HelpText = "Priority to use when grabbing episodes that aired within the last 14 days")] + [FieldDefinition(7, Label = "DownloadClientSettingsRecentPriority", Type = FieldType.Select, SelectOptions = typeof(NzbgetPriority), HelpText = "DownloadClientSettingsRecentPriorityHelpText")] public int RecentTvPriority { get; set; } - [FieldDefinition(8, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(NzbgetPriority), HelpText = "Priority to use when grabbing episodes that aired over 14 days ago")] + [FieldDefinition(8, Label = "DownloadClientSettingsOlderPriority", Type = FieldType.Select, SelectOptions = typeof(NzbgetPriority), HelpText = "DownloadClientSettingsOlderPriorityHelpText")] public int OlderTvPriority { get; set; } - [FieldDefinition(9, Label = "Add Paused", Type = FieldType.Checkbox, HelpText = "This option requires at least NzbGet version 16.0")] + [FieldDefinition(9, Label = "DownloadClientSettingsAddPaused", Type = FieldType.Checkbox, HelpText = "DownloadClientNzbgetSettingsAddPausedHelpText")] public bool AddPaused { get; set; } public NzbDroneValidationResult Validate() diff --git a/src/NzbDrone.Core/Download/Clients/Pneumatic/Pneumatic.cs b/src/NzbDrone.Core/Download/Clients/Pneumatic/Pneumatic.cs index f531778d1..920279263 100644 --- a/src/NzbDrone.Core/Download/Clients/Pneumatic/Pneumatic.cs +++ b/src/NzbDrone.Core/Download/Clients/Pneumatic/Pneumatic.cs @@ -9,6 +9,7 @@ using NzbDrone.Common.Extensions; using NzbDrone.Common.Http; using NzbDrone.Core.Configuration; using NzbDrone.Core.Indexers; +using NzbDrone.Core.Localization; using NzbDrone.Core.Organizer; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.RemotePathMappings; @@ -23,8 +24,9 @@ namespace NzbDrone.Core.Download.Clients.Pneumatic IConfigService configService, IDiskProvider diskProvider, IRemotePathMappingService remotePathMappingService, - Logger logger) - : base(configService, diskProvider, remotePathMappingService, logger) + Logger logger, + ILocalizationService localizationService) + : base(configService, diskProvider, remotePathMappingService, logger, localizationService) { _httpClient = httpClient; } diff --git a/src/NzbDrone.Core/Download/Clients/Pneumatic/PneumaticSettings.cs b/src/NzbDrone.Core/Download/Clients/Pneumatic/PneumaticSettings.cs index 741021a3f..6cd8a2b89 100644 --- a/src/NzbDrone.Core/Download/Clients/Pneumatic/PneumaticSettings.cs +++ b/src/NzbDrone.Core/Download/Clients/Pneumatic/PneumaticSettings.cs @@ -19,10 +19,10 @@ namespace NzbDrone.Core.Download.Clients.Pneumatic { private static readonly PneumaticSettingsValidator Validator = new PneumaticSettingsValidator(); - [FieldDefinition(0, Label = "Nzb Folder", Type = FieldType.Path, HelpText = "This folder will need to be reachable from XBMC")] + [FieldDefinition(0, Label = "DownloadClientPneumaticSettingsNzbFolder", Type = FieldType.Path, HelpText = "DownloadClientPneumaticSettingsNzbFolderHelpText")] public string NzbFolder { get; set; } - [FieldDefinition(1, Label = "Strm Folder", Type = FieldType.Path, HelpText = ".strm files in this folder will be import by drone")] + [FieldDefinition(1, Label = "DownloadClientPneumaticSettingsStrmFolder", Type = FieldType.Path, HelpText = "DownloadClientPneumaticSettingsStrmFolderHelpText")] public string StrmFolder { get; set; } public NzbDroneValidationResult Validate() diff --git a/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrent.cs b/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrent.cs index c51c99a44..77cbeef17 100644 --- a/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrent.cs +++ b/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrent.cs @@ -9,6 +9,7 @@ using NzbDrone.Common.Disk; using NzbDrone.Common.Extensions; using NzbDrone.Common.Http; using NzbDrone.Core.Configuration; +using NzbDrone.Core.Localization; using NzbDrone.Core.MediaFiles.TorrentInfo; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.RemotePathMappings; @@ -34,8 +35,9 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent IDiskProvider diskProvider, IRemotePathMappingService remotePathMappingService, ICacheManager cacheManager, - Logger logger) - : base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, logger) + Logger logger, + ILocalizationService localizationService) + : base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, logger, localizationService) { _proxySelector = proxySelector; @@ -241,7 +243,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent { case "error": // some error occurred, applies to paused torrents, warning so failed download handling isn't triggered item.Status = DownloadItemStatus.Warning; - item.Message = "qBittorrent is reporting an error"; + item.Message = _localizationService.GetLocalizedString("DownloadClientQbittorrentTorrentStateError"); break; case "pausedDL": // torrent is paused and has NOT finished downloading @@ -266,24 +268,24 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent case "stalledDL": // torrent is being downloaded, but no connection were made item.Status = DownloadItemStatus.Warning; - item.Message = "The download is stalled with no connections"; + item.Message = _localizationService.GetLocalizedString("DownloadClientQbittorrentTorrentStateStalled"); break; case "missingFiles": // torrent is missing files item.Status = DownloadItemStatus.Warning; - item.Message = "The download is missing files"; + item.Message = _localizationService.GetLocalizedString("DownloadClientQbittorrentTorrentStateMissingFiles"); break; case "metaDL": // torrent magnet is being downloaded if (config.DhtEnabled) { item.Status = DownloadItemStatus.Queued; - item.Message = "qBittorrent is downloading metadata"; + item.Message = _localizationService.GetLocalizedString("DownloadClientQbittorrentTorrentStateMetadata"); } else { item.Status = DownloadItemStatus.Warning; - item.Message = "qBittorrent cannot resolve magnet link with DHT disabled"; + item.Message = _localizationService.GetLocalizedString("DownloadClientQbittorrentTorrentStateDhtDisabled"); } break; @@ -296,8 +298,8 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent break; default: // new status in API? default to downloading - item.Message = "Unknown download state: " + torrent.State; - _logger.Info(item.Message); + item.Message = _localizationService.GetLocalizedString("DownloadClientQbittorrentTorrentStateUnknown", new Dictionary<string, object> { { "state", torrent.State } }); + _logger.Info($"Unknown download state: {torrent.State}"); item.Status = DownloadItemStatus.Downloading; break; } @@ -311,7 +313,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent else { item.Status = DownloadItemStatus.Warning; - item.Message = "Unable to Import. Path matches client base download directory, it's possible 'Keep top-level folder' is disabled for this torrent or 'Torrent Content Layout' is NOT set to 'Original' or 'Create Subfolder'?"; + item.Message = _localizationService.GetLocalizedString("DownloadClientQbittorrentTorrentStatePathError"); } } @@ -415,29 +417,30 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent if (version < Version.Parse("1.5")) { // API version 5 introduced the "save_path" property in /query/torrents - return new NzbDroneValidationFailure("Host", "Unsupported client version") - { - DetailedDescription = "Please upgrade to qBittorrent version 3.2.4 or higher." - }; + return new NzbDroneValidationFailure("Host", _localizationService.GetLocalizedString("DownloadClientValidationErrorVersion", + new Dictionary<string, object> + { + { "clientName", Name }, { "requiredVersion", "3.2.4" }, { "reportedVersion", version } + })); } else if (version < Version.Parse("1.6")) { // API version 6 introduced support for labels if (Settings.TvCategory.IsNotNullOrWhiteSpace()) { - return new NzbDroneValidationFailure("Category", "Category is not supported") + return new NzbDroneValidationFailure("Category", _localizationService.GetLocalizedString("DownloadClientQbittorrentValidationCategoryUnsupported")) { - DetailedDescription = "Labels are not supported until qBittorrent version 3.3.0. Please upgrade or try again with an empty Category." + DetailedDescription = _localizationService.GetLocalizedString("DownloadClientQbittorrentValidationCategoryUnsupportedDetail") }; } } else if (Settings.TvCategory.IsNullOrWhiteSpace()) { // warn if labels are supported, but category is not provided - return new NzbDroneValidationFailure("TvCategory", "Category is recommended") + return new NzbDroneValidationFailure("TvCategory", _localizationService.GetLocalizedString("DownloadClientQbittorrentValidationCategoryRecommended")) { IsWarning = true, - DetailedDescription = "Sonarr will not attempt to import completed downloads without a category." + DetailedDescription = _localizationService.GetLocalizedString("DownloadClientQbittorrentValidationCategoryRecommendedDetail") }; } @@ -445,18 +448,18 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent var config = Proxy.GetConfig(Settings); if ((config.MaxRatioEnabled || config.MaxSeedingTimeEnabled) && (config.MaxRatioAction == QBittorrentMaxRatioAction.Remove || config.MaxRatioAction == QBittorrentMaxRatioAction.DeleteFiles)) { - return new NzbDroneValidationFailure(string.Empty, "qBittorrent is configured to remove torrents when they reach their Share Ratio Limit") + return new NzbDroneValidationFailure(string.Empty, _localizationService.GetLocalizedString("DownloadClientQbittorrentValidationRemovesAtRatioLimit")) { - DetailedDescription = "Sonarr will be unable to perform Completed Download Handling as configured. You can fix this in qBittorrent ('Tools -> Options...' in the menu) by changing 'Options -> BitTorrent -> Share Ratio Limiting' from 'Remove them' to 'Pause them'." + DetailedDescription = _localizationService.GetLocalizedString("DownloadClientQbittorrentValidationRemovesAtRatioLimitDetail") }; } } catch (DownloadClientAuthenticationException ex) { _logger.Error(ex, ex.Message); - return new NzbDroneValidationFailure("Username", "Authentication failure") + return new NzbDroneValidationFailure("Username", _localizationService.GetLocalizedString("DownloadClientValidationAuthenticationFailure")) { - DetailedDescription = "Please verify your username and password." + DetailedDescription = _localizationService.GetLocalizedString("DownloadClientValidationAuthenticationFailureDetail", new Dictionary<string, object> { { "clientName", Name } }) }; } catch (WebException ex) @@ -464,19 +467,19 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent _logger.Error(ex, "Unable to connect to qBittorrent"); if (ex.Status == WebExceptionStatus.ConnectFailure) { - return new NzbDroneValidationFailure("Host", "Unable to connect") + return new NzbDroneValidationFailure("Host", _localizationService.GetLocalizedString("DownloadClientValidationUnableToConnect", new Dictionary<string, object> { { "clientName", Name } })) { - DetailedDescription = "Please verify the hostname and port." + DetailedDescription = _localizationService.GetLocalizedString("DownloadClientValidationUnableToConnectDetail") }; } - return new NzbDroneValidationFailure(string.Empty, "Unknown exception: " + ex.Message); + return new NzbDroneValidationFailure(string.Empty, _localizationService.GetLocalizedString("DownloadClientValidationUnknownException", new Dictionary<string, object> { { "exception", ex.Message } })); } catch (Exception ex) { _logger.Error(ex, "Unable to test qBittorrent"); - return new NzbDroneValidationFailure("Host", "Unable to connect to qBittorrent") + return new NzbDroneValidationFailure("Host", _localizationService.GetLocalizedString("DownloadClientValidationUnableToConnect", new Dictionary<string, object> { { "clientName", Name } })) { DetailedDescription = ex.Message }; @@ -508,9 +511,9 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent if (!labels.ContainsKey(Settings.TvCategory)) { - return new NzbDroneValidationFailure("TvCategory", "Configuration of label failed") + return new NzbDroneValidationFailure("TvCategory", _localizationService.GetLocalizedString("DownloadClientQbittorrentValidationCategoryAddFailure")) { - DetailedDescription = "Sonarr was unable to add the label to qBittorrent." + DetailedDescription = _localizationService.GetLocalizedString("DownloadClientQbittorrentValidationCategoryAddFailureDetail") }; } } @@ -522,9 +525,9 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent if (!labels.ContainsKey(Settings.TvImportedCategory)) { - return new NzbDroneValidationFailure("TvImportedCategory", "Configuration of label failed") + return new NzbDroneValidationFailure("TvImportedCategory", _localizationService.GetLocalizedString("DownloadClientQbittorrentValidationCategoryAddFailure")) { - DetailedDescription = "Sonarr was unable to add the label to qBittorrent." + DetailedDescription = _localizationService.GetLocalizedString("DownloadClientQbittorrentValidationCategoryAddFailureDetail") }; } } @@ -550,18 +553,24 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent { if (!recentPriorityDefault) { - return new NzbDroneValidationFailure(nameof(Settings.RecentTvPriority), "Queueing not enabled") { DetailedDescription = "Torrent Queueing is not enabled in your qBittorrent settings. Enable it in qBittorrent or select 'Last' as priority." }; + return new NzbDroneValidationFailure(nameof(Settings.RecentTvPriority), _localizationService.GetLocalizedString("DownloadClientQbittorrentValidationQueueingNotEnabled")) + { + DetailedDescription = _localizationService.GetLocalizedString("DownloadClientQbittorrentValidationQueueingNotEnabledDetail") + }; } else if (!olderPriorityDefault) { - return new NzbDroneValidationFailure(nameof(Settings.OlderTvPriority), "Queueing not enabled") { DetailedDescription = "Torrent Queueing is not enabled in your qBittorrent settings. Enable it in qBittorrent or select 'Last' as priority." }; + return new NzbDroneValidationFailure(nameof(Settings.OlderTvPriority), _localizationService.GetLocalizedString("DownloadClientQbittorrentValidationQueueingNotEnabled")) + { + DetailedDescription = _localizationService.GetLocalizedString("DownloadClientQbittorrentValidationQueueingNotEnabledDetail") + }; } } } catch (Exception ex) { _logger.Error(ex, "Failed to test qBittorrent"); - return new NzbDroneValidationFailure(string.Empty, "Unknown exception: " + ex.Message); + return new NzbDroneValidationFailure(string.Empty, _localizationService.GetLocalizedString("DownloadClientValidationUnknownException", new Dictionary<string, object> { { "exception", ex.Message } })); } return null; @@ -576,7 +585,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent catch (Exception ex) { _logger.Error(ex, "Failed to get torrents"); - return new NzbDroneValidationFailure(string.Empty, "Failed to get the list of torrents: " + ex.Message); + return new NzbDroneValidationFailure(string.Empty, _localizationService.GetLocalizedString("DownloadClientValidationTestTorrents", new Dictionary<string, object> { { "exceptionMessage", ex.Message } })); } return null; diff --git a/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrentSettings.cs b/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrentSettings.cs index 8d83e9156..fd12a7e04 100644 --- a/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrentSettings.cs +++ b/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrentSettings.cs @@ -36,10 +36,12 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent [FieldDefinition(1, Label = "Port", Type = FieldType.Textbox)] public int Port { get; set; } - [FieldDefinition(2, Label = "Use SSL", Type = FieldType.Checkbox, HelpText = "Use a secure connection. See Options -> Web UI -> 'Use HTTPS instead of HTTP' in qBittorrent.")] + [FieldDefinition(2, Label = "UseSsl", Type = FieldType.Checkbox, HelpText = "DownloadClientQbittorrentSettingsUseSslHelpText")] public bool UseSsl { get; set; } - [FieldDefinition(3, Label = "Url Base", Type = FieldType.Textbox, Advanced = true, HelpText = "Adds a prefix to the qBittorrent url, e.g. http://[host]:[port]/[urlBase]/api")] + [FieldDefinition(3, Label = "UrlBase", Type = FieldType.Textbox, Advanced = true, HelpText = "DownloadClientSettingsUrlBaseHelpText")] + [FieldToken(TokenField.HelpText, "UrlBase", "clientName", "qBittorrent")] + [FieldToken(TokenField.HelpText, "UrlBase", "url", "http://[host]:[port]/[urlBase]/api")] public string UrlBase { get; set; } [FieldDefinition(4, Label = "Username", Type = FieldType.Textbox, Privacy = PrivacyLevel.UserName)] @@ -48,25 +50,25 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent [FieldDefinition(5, Label = "Password", Type = FieldType.Password, Privacy = PrivacyLevel.Password)] public string Password { get; set; } - [FieldDefinition(6, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Sonarr avoids conflicts with unrelated non-Sonarr downloads. Using a category is optional, but strongly recommended.")] + [FieldDefinition(6, Label = "Category", Type = FieldType.Textbox, HelpText = "DownloadClientSettingsCategoryHelpText")] public string TvCategory { get; set; } - [FieldDefinition(7, Label = "Post-Import Category", Type = FieldType.Textbox, Advanced = true, HelpText = "Category for Sonarr to set after it has imported the download. Sonarr will not remove the torrent if seeding has finished. Leave blank to keep same category.")] + [FieldDefinition(7, Label = "PostImportCategory", Type = FieldType.Textbox, Advanced = true, HelpText = "DownloadClientSettingsPostImportCategoryHelpText")] public string TvImportedCategory { get; set; } - [FieldDefinition(8, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(QBittorrentPriority), HelpText = "Priority to use when grabbing episodes that aired within the last 14 days")] + [FieldDefinition(8, Label = "DownloadClientSettingsRecentPriority", Type = FieldType.Select, SelectOptions = typeof(QBittorrentPriority), HelpText = "DownloadClientSettingsRecentPriorityHelpText")] public int RecentTvPriority { get; set; } - [FieldDefinition(9, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(QBittorrentPriority), HelpText = "Priority to use when grabbing episodes that aired over 14 days ago")] + [FieldDefinition(9, Label = "DownloadClientSettingsOlderPriority", Type = FieldType.Select, SelectOptions = typeof(QBittorrentPriority), HelpText = "DownloadClientSettingsOlderPriorityHelpText")] public int OlderTvPriority { get; set; } - [FieldDefinition(10, Label = "Initial State", Type = FieldType.Select, SelectOptions = typeof(QBittorrentState), HelpText = "Initial state for torrents added to qBittorrent. Note that Forced Torrents do not abide by seed restrictions")] + [FieldDefinition(10, Label = "DownloadClientSettingsInitialState", Type = FieldType.Select, SelectOptions = typeof(QBittorrentState), HelpText = "DownloadClientQbittorrentSettingsInitialStateHelpText")] public int InitialState { get; set; } - [FieldDefinition(11, Label = "Sequential Order", Type = FieldType.Checkbox, HelpText = "Download in sequential order (qBittorrent 4.1.0+)")] + [FieldDefinition(11, Label = "DownloadClientQbittorrentSettingsSequentialOrder", Type = FieldType.Checkbox, HelpText = "DownloadClientQbittorrentSettingsSequentialOrderHelpText")] public bool SequentialOrder { get; set; } - [FieldDefinition(12, Label = "First and Last First", Type = FieldType.Checkbox, HelpText = "Download first and last pieces first (qBittorrent 4.1.0+)")] + [FieldDefinition(12, Label = "DownloadClientQbittorrentSettingsFirstAndLastFirst", Type = FieldType.Checkbox, HelpText = "DownloadClientQbittorrentSettingsFirstAndLastFirstHelpText")] public bool FirstAndLast { get; set; } public NzbDroneValidationResult Validate() diff --git a/src/NzbDrone.Core/Download/Clients/Sabnzbd/Sabnzbd.cs b/src/NzbDrone.Core/Download/Clients/Sabnzbd/Sabnzbd.cs index 95f8942aa..5d9003849 100644 --- a/src/NzbDrone.Core/Download/Clients/Sabnzbd/Sabnzbd.cs +++ b/src/NzbDrone.Core/Download/Clients/Sabnzbd/Sabnzbd.cs @@ -10,6 +10,7 @@ using NzbDrone.Common.Extensions; using NzbDrone.Common.Http; using NzbDrone.Core.Configuration; using NzbDrone.Core.Exceptions; +using NzbDrone.Core.Localization; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.RemotePathMappings; using NzbDrone.Core.Validation; @@ -26,8 +27,9 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd IDiskProvider diskProvider, IRemotePathMappingService remotePathMappingService, IValidateNzbs nzbValidationService, - Logger logger) - : base(httpClient, configService, diskProvider, remotePathMappingService, nzbValidationService, logger) + Logger logger, + ILocalizationService localizationService) + : base(httpClient, configService, diskProvider, remotePathMappingService, nzbValidationService, logger, localizationService) { _proxy = proxy; } @@ -381,15 +383,15 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd if (version == null) { - return new ValidationFailure("Version", "Unknown Version: " + rawVersion); + return new ValidationFailure("Version", _localizationService.GetLocalizedString("DownloadClientSabnzbdValidationUnknownVersion", new Dictionary<string, object> { { "rawVersion", rawVersion ?? "" } })); } if (rawVersion.Equals("develop", StringComparison.InvariantCultureIgnoreCase)) { - return new NzbDroneValidationFailure("Version", "Sabnzbd develop version, assuming version 3.0.0 or higher.") + return new NzbDroneValidationFailure("Version", _localizationService.GetLocalizedString("DownloadClientSabnzbdValidationDevelopVersion")) { IsWarning = true, - DetailedDescription = "Sonarr may not be able to support new features added to SABnzbd when running develop versions." + DetailedDescription = _localizationService.GetLocalizedString("DownloadClientSabnzbdValidationDevelopVersionDetail") }; } @@ -403,12 +405,17 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd return null; } - return new ValidationFailure("Version", "Version 0.7.0+ is required, but found: " + version); + return new ValidationFailure("Version", + _localizationService.GetLocalizedString("DownloadClientValidationErrorVersion", + new Dictionary<string, object> + { + { "clientName", Name }, { "requiredVersion", "0.7.0" }, { "reportedVersion", version } + })); } catch (Exception ex) { _logger.Error(ex, ex.Message); - return new NzbDroneValidationFailure("Host", "Unable to connect to SABnzbd") + return new NzbDroneValidationFailure("Host", _localizationService.GetLocalizedString("DownloadClientValidationUnableToConnect", new Dictionary<string, object> { { "clientName", Name } })) { DetailedDescription = ex.Message }; @@ -425,12 +432,12 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd { if (ex.Message.ContainsIgnoreCase("API Key Incorrect")) { - return new ValidationFailure("APIKey", "API Key Incorrect"); + return new ValidationFailure("APIKey", _localizationService.GetLocalizedString("DownloadClientValidationApiKeyIncorrect")); } if (ex.Message.ContainsIgnoreCase("API Key Required")) { - return new ValidationFailure("APIKey", "API Key Required"); + return new ValidationFailure("APIKey", _localizationService.GetLocalizedString("DownloadClientValidationApiKeyRequired")); } throw; @@ -444,10 +451,10 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd var config = _proxy.GetConfig(Settings); if (config.Misc.pre_check && !HasVersion(1, 1)) { - return new NzbDroneValidationFailure("", "Disable 'Check before download' option in Sabnbzd") + return new NzbDroneValidationFailure("", _localizationService.GetLocalizedString("DownloadClientSabnzbdValidationCheckBeforeDownload")) { InfoLink = _proxy.GetBaseUrl(Settings, "config/switches/"), - DetailedDescription = "Using Check before download affects Sonarr ability to track new downloads. Also Sabnzbd recommends 'Abort jobs that cannot be completed' instead since it's more effective." + DetailedDescription = _localizationService.GetLocalizedString("DownloadClientSabnzbdValidationCheckBeforeDownloadDetail") }; } @@ -463,10 +470,10 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd { if (category.Dir.EndsWith("*")) { - return new NzbDroneValidationFailure("TvCategory", "Enable Job folders") + return new NzbDroneValidationFailure("TvCategory", _localizationService.GetLocalizedString("DownloadClientSabnzbdValidationEnableJobFolders")) { InfoLink = _proxy.GetBaseUrl(Settings, "config/categories/"), - DetailedDescription = "Sonarr prefers each download to have a separate folder. With * appended to the Folder/Path Sabnzbd will not create these job folders. Go to Sabnzbd to fix it." + DetailedDescription = _localizationService.GetLocalizedString("DownloadClientSabnzbdValidationEnableJobFoldersDetail") }; } } @@ -474,10 +481,10 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd { if (!Settings.TvCategory.IsNullOrWhiteSpace()) { - return new NzbDroneValidationFailure("TvCategory", "Category does not exist") + return new NzbDroneValidationFailure("TvCategory", _localizationService.GetLocalizedString("DownloadClientValidationCategoryMissing")) { InfoLink = _proxy.GetBaseUrl(Settings, "config/categories/"), - DetailedDescription = "The Category your entered doesn't exist in Sabnzbd. Go to Sabnzbd to create it." + DetailedDescription = _localizationService.GetLocalizedString("DownloadClientValidationCategoryMissingDetail", new Dictionary<string, object> { { "clientName", Name } }) }; } } @@ -485,37 +492,37 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd // New in SABnzbd 4.1, but on older versions this will be empty and not apply if (config.Sorters.Any(s => s.is_active && ContainsCategory(s.sort_cats, Settings.TvCategory))) { - return new NzbDroneValidationFailure("TvCategory", "Disable TV Sorting") + return new NzbDroneValidationFailure("TvCategory", _localizationService.GetLocalizedString("DownloadClientSabnzbdValidationEnableDisableTvSorting")) { InfoLink = _proxy.GetBaseUrl(Settings, "config/sorting/"), - DetailedDescription = "You must disable sorting for the category Sonarr uses to prevent import issues. Go to Sabnzbd to fix it." + DetailedDescription = _localizationService.GetLocalizedString("DownloadClientSabnzbdValidationEnableDisableTvSortingDetail") }; } if (config.Misc.enable_tv_sorting && ContainsCategory(config.Misc.tv_categories, Settings.TvCategory)) { - return new NzbDroneValidationFailure("TvCategory", "Disable TV Sorting") + return new NzbDroneValidationFailure("TvCategory", _localizationService.GetLocalizedString("DownloadClientSabnzbdValidationEnableDisableTvSorting")) { InfoLink = _proxy.GetBaseUrl(Settings, "config/sorting/"), - DetailedDescription = "You must disable Sabnzbd TV Sorting for the category Sonarr uses to prevent import issues. Go to Sabnzbd to fix it." + DetailedDescription = _localizationService.GetLocalizedString("DownloadClientSabnzbdValidationEnableDisableTvSortingDetail") }; } if (config.Misc.enable_movie_sorting && ContainsCategory(config.Misc.movie_categories, Settings.TvCategory)) { - return new NzbDroneValidationFailure("TvCategory", "Disable Movie Sorting") + return new NzbDroneValidationFailure("TvCategory", _localizationService.GetLocalizedString("DownloadClientSabnzbdValidationEnableDisableMovieSorting")) { InfoLink = _proxy.GetBaseUrl(Settings, "config/sorting/"), - DetailedDescription = "You must disable Sabnzbd Movie Sorting for the category Sonarr uses to prevent import issues. Go to Sabnzbd to fix it." + DetailedDescription = _localizationService.GetLocalizedString("DownloadClientSabnzbdValidationEnableDisableMovieSortingDetail") }; } if (config.Misc.enable_date_sorting && ContainsCategory(config.Misc.date_categories, Settings.TvCategory)) { - return new NzbDroneValidationFailure("TvCategory", "Disable Date Sorting") + return new NzbDroneValidationFailure("TvCategory", _localizationService.GetLocalizedString("DownloadClientSabnzbdValidationEnableDisableDateSorting")) { InfoLink = _proxy.GetBaseUrl(Settings, "config/sorting/"), - DetailedDescription = "You must disable Sabnzbd Date Sorting for the category Sonarr uses to prevent import issues. Go to Sabnzbd to fix it." + DetailedDescription = _localizationService.GetLocalizedString("DownloadClientSabnzbdValidationEnableDisableDateSortingDetail") }; } diff --git a/src/NzbDrone.Core/Download/Clients/Sabnzbd/SabnzbdSettings.cs b/src/NzbDrone.Core/Download/Clients/Sabnzbd/SabnzbdSettings.cs index 13e6b660c..baa6b3553 100644 --- a/src/NzbDrone.Core/Download/Clients/Sabnzbd/SabnzbdSettings.cs +++ b/src/NzbDrone.Core/Download/Clients/Sabnzbd/SabnzbdSettings.cs @@ -51,13 +51,16 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd [FieldDefinition(1, Label = "Port", Type = FieldType.Textbox)] public int Port { get; set; } - [FieldDefinition(2, Label = "Use SSL", Type = FieldType.Checkbox, HelpText = "Use secure connection when connecting to Sabnzbd")] + [FieldDefinition(2, Label = "UseSsl", Type = FieldType.Checkbox, HelpText = "DownloadClientSettingsUseSslHelpText")] + [FieldToken(TokenField.HelpText, "UseSsl", "clientName", "Sabnzbd")] public bool UseSsl { get; set; } - [FieldDefinition(3, Label = "Url Base", Type = FieldType.Textbox, Advanced = true, HelpText = "Adds a prefix to the Sabnzbd url, e.g. http://[host]:[port]/[urlBase]/api")] + [FieldDefinition(3, Label = "UrlBase", Type = FieldType.Textbox, Advanced = true, HelpText = "DownloadClientSettingsUrlBaseHelpText")] + [FieldToken(TokenField.HelpText, "UrlBase", "clientName", "Sabnzbd")] + [FieldToken(TokenField.HelpText, "UrlBase", "url", "http://[host]:[port]/[urlBase]/api")] public string UrlBase { get; set; } - [FieldDefinition(4, Label = "API Key", Type = FieldType.Textbox, Privacy = PrivacyLevel.ApiKey)] + [FieldDefinition(4, Label = "ApiKey", Type = FieldType.Textbox, Privacy = PrivacyLevel.ApiKey)] public string ApiKey { get; set; } [FieldDefinition(5, Label = "Username", Type = FieldType.Textbox, Privacy = PrivacyLevel.UserName)] @@ -66,13 +69,13 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd [FieldDefinition(6, Label = "Password", Type = FieldType.Password, Privacy = PrivacyLevel.Password)] public string Password { get; set; } - [FieldDefinition(7, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Sonarr avoids conflicts with unrelated non-Sonarr downloads. Using a category is optional, but strongly recommended.")] + [FieldDefinition(7, Label = "Category", Type = FieldType.Textbox, HelpText = "DownloadClientSettingsCategoryHelpText")] public string TvCategory { get; set; } - [FieldDefinition(8, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(SabnzbdPriority), HelpText = "Priority to use when grabbing episodes that aired within the last 14 days")] + [FieldDefinition(8, Label = "DownloadClientSettingsRecentPriority", Type = FieldType.Select, SelectOptions = typeof(SabnzbdPriority), HelpText = "DownloadClientSettingsRecentPriorityHelpText")] public int RecentTvPriority { get; set; } - [FieldDefinition(9, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(SabnzbdPriority), HelpText = "Priority to use when grabbing episodes that aired over 14 days ago")] + [FieldDefinition(9, Label = "DownloadClientSettingsOlderPriority", Type = FieldType.Select, SelectOptions = typeof(SabnzbdPriority), HelpText = "DownloadClientSettingsOlderPriorityHelpText")] public int OlderTvPriority { get; set; } public NzbDroneValidationResult Validate() diff --git a/src/NzbDrone.Core/Download/Clients/Transmission/Transmission.cs b/src/NzbDrone.Core/Download/Clients/Transmission/Transmission.cs index 369b9f961..8c9f858bc 100644 --- a/src/NzbDrone.Core/Download/Clients/Transmission/Transmission.cs +++ b/src/NzbDrone.Core/Download/Clients/Transmission/Transmission.cs @@ -1,10 +1,12 @@ using System; +using System.Collections.Generic; using System.Text.RegularExpressions; using FluentValidation.Results; using NLog; using NzbDrone.Common.Disk; using NzbDrone.Common.Http; using NzbDrone.Core.Configuration; +using NzbDrone.Core.Localization; using NzbDrone.Core.MediaFiles.TorrentInfo; using NzbDrone.Core.RemotePathMappings; @@ -18,8 +20,9 @@ namespace NzbDrone.Core.Download.Clients.Transmission IConfigService configService, IDiskProvider diskProvider, IRemotePathMappingService remotePathMappingService, - Logger logger) - : base(proxy, torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, logger) + Logger logger, + ILocalizationService localizationService) + : base(proxy, torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, logger, localizationService) { } @@ -34,7 +37,7 @@ namespace NzbDrone.Core.Download.Clients.Transmission if (version < new Version(2, 40)) { - return new ValidationFailure(string.Empty, "Transmission version not supported, should be 2.40 or higher."); + return new ValidationFailure(string.Empty, _localizationService.GetLocalizedString("DownloadClientValidationErrorVersion", new Dictionary<string, object> { { "clientName", Name }, { "requiredVersion", "2.40" }, { "reportedVersion", version } })); } return null; diff --git a/src/NzbDrone.Core/Download/Clients/Transmission/TransmissionBase.cs b/src/NzbDrone.Core/Download/Clients/Transmission/TransmissionBase.cs index d0f5a892b..f08ab547c 100644 --- a/src/NzbDrone.Core/Download/Clients/Transmission/TransmissionBase.cs +++ b/src/NzbDrone.Core/Download/Clients/Transmission/TransmissionBase.cs @@ -7,6 +7,7 @@ using NzbDrone.Common.Disk; using NzbDrone.Common.Extensions; using NzbDrone.Common.Http; using NzbDrone.Core.Configuration; +using NzbDrone.Core.Localization; using NzbDrone.Core.MediaFiles.TorrentInfo; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.RemotePathMappings; @@ -24,8 +25,9 @@ namespace NzbDrone.Core.Download.Clients.Transmission IConfigService configService, IDiskProvider diskProvider, IRemotePathMappingService remotePathMappingService, - Logger logger) - : base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, logger) + Logger logger, + ILocalizationService localizationService) + : base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, logger, localizationService) { _proxy = proxy; } @@ -270,16 +272,16 @@ namespace NzbDrone.Core.Download.Clients.Transmission catch (DownloadClientAuthenticationException ex) { _logger.Error(ex, ex.Message); - return new NzbDroneValidationFailure("Username", "Authentication failure") + return new NzbDroneValidationFailure("Username", _localizationService.GetLocalizedString("DownloadClientValidationAuthenticationFailure")) { - DetailedDescription = string.Format("Please verify your username and password. Also verify if the host running Sonarr isn't blocked from accessing {0} by WhiteList limitations in the {0} configuration.", Name) + DetailedDescription = _localizationService.GetLocalizedString("DownloadClientValidationAuthenticationFailureDetail", new Dictionary<string, object> { { "clientName", Name } }) }; } catch (DownloadClientUnavailableException ex) { _logger.Error(ex, ex.Message); - return new NzbDroneValidationFailure("Host", "Unable to connect to Transmission") + return new NzbDroneValidationFailure("Host", _localizationService.GetLocalizedString("DownloadClientValidationUnableToConnect", new Dictionary<string, object> { { "clientName", Name } })) { DetailedDescription = ex.Message }; @@ -288,7 +290,7 @@ namespace NzbDrone.Core.Download.Clients.Transmission { _logger.Error(ex, "Failed to test"); - return new NzbDroneValidationFailure(string.Empty, "Unknown exception: " + ex.Message); + return new NzbDroneValidationFailure(string.Empty, _localizationService.GetLocalizedString("DownloadClientValidationUnknownException", new Dictionary<string, object> { { "exception", ex.Message } })); } } @@ -303,7 +305,7 @@ namespace NzbDrone.Core.Download.Clients.Transmission catch (Exception ex) { _logger.Error(ex, "Failed to get torrents"); - return new NzbDroneValidationFailure(string.Empty, "Failed to get the list of torrents: " + ex.Message); + return new NzbDroneValidationFailure(string.Empty, _localizationService.GetLocalizedString("DownloadClientValidationTestTorrents", new Dictionary<string, object> { { "exceptionMessage", ex.Message } })); } return null; diff --git a/src/NzbDrone.Core/Download/Clients/Transmission/TransmissionSettings.cs b/src/NzbDrone.Core/Download/Clients/Transmission/TransmissionSettings.cs index 40e2528ac..7038ad7af 100644 --- a/src/NzbDrone.Core/Download/Clients/Transmission/TransmissionSettings.cs +++ b/src/NzbDrone.Core/Download/Clients/Transmission/TransmissionSettings.cs @@ -41,10 +41,15 @@ namespace NzbDrone.Core.Download.Clients.Transmission [FieldDefinition(1, Label = "Port", Type = FieldType.Textbox)] public int Port { get; set; } - [FieldDefinition(2, Label = "Use SSL", Type = FieldType.Checkbox, HelpText = "Use secure connection when connecting to Transmission")] + [FieldDefinition(2, Label = "UseSsl", Type = FieldType.Checkbox, HelpText = "DownloadClientSettingsUseSslHelpText")] + [FieldToken(TokenField.HelpText, "UseSsl", "clientName", "Transmission")] public bool UseSsl { get; set; } - [FieldDefinition(3, Label = "Url Base", Type = FieldType.Textbox, Advanced = true, HelpText = "Adds a prefix to the transmission rpc url, eg http://[host]:[port]/[urlBase]/rpc, defaults to '/transmission/'")] + [FieldDefinition(3, Label = "UrlBase", Type = FieldType.Textbox, Advanced = true, HelpText = "Adds a prefix to the transmission rpc url, eg http://[host]:[port]/[urlBase]/rpc, defaults to '/transmission/'")] + [FieldToken(TokenField.HelpText, "UrlBase", "clientName", "Transmission")] + [FieldToken(TokenField.HelpText, "UrlBase", "url", "http://[host]:[port]/[urlBase]/rpc")] + [FieldToken(TokenField.HelpText, "UrlBase", "defaultUrl", "/transmission/")] + public string UrlBase { get; set; } [FieldDefinition(4, Label = "Username", Type = FieldType.Textbox, Privacy = PrivacyLevel.UserName)] @@ -53,19 +58,19 @@ namespace NzbDrone.Core.Download.Clients.Transmission [FieldDefinition(5, Label = "Password", Type = FieldType.Password, Privacy = PrivacyLevel.Password)] public string Password { get; set; } - [FieldDefinition(6, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Sonarr avoids conflicts with unrelated non-Sonarr downloads. Using a category is optional, but strongly recommended.. Creates a [category] subdirectory in the output directory.")] + [FieldDefinition(6, Label = "Category", Type = FieldType.Textbox, HelpText = "DownloadClientSettingsCategorySubFolderHelpText")] public string TvCategory { get; set; } - [FieldDefinition(7, Label = "Directory", Type = FieldType.Textbox, Advanced = true, HelpText = "Optional location to put downloads in, leave blank to use the default Transmission location")] + [FieldDefinition(7, Label = "Directory", Type = FieldType.Textbox, Advanced = true, HelpText = "DownloadClientTransmissionSettingsDirectoryHelpText")] public string TvDirectory { get; set; } - [FieldDefinition(8, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(TransmissionPriority), HelpText = "Priority to use when grabbing episodes that aired within the last 14 days")] + [FieldDefinition(8, Label = "DownloadClientSettingsRecentPriority", Type = FieldType.Select, SelectOptions = typeof(TransmissionPriority), HelpText = "DownloadClientSettingsRecentPriorityHelpText")] public int RecentTvPriority { get; set; } - [FieldDefinition(9, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(TransmissionPriority), HelpText = "Priority to use when grabbing episodes that aired over 14 days ago")] + [FieldDefinition(9, Label = "DownloadClientSettingsOlderPriority", Type = FieldType.Select, SelectOptions = typeof(TransmissionPriority), HelpText = "DownloadClientSettingsOlderPriorityHelpText")] public int OlderTvPriority { get; set; } - [FieldDefinition(10, Label = "Add Paused", Type = FieldType.Checkbox)] + [FieldDefinition(10, Label = "DownloadClientSettingsAddPaused", Type = FieldType.Checkbox)] public bool AddPaused { get; set; } public NzbDroneValidationResult Validate() diff --git a/src/NzbDrone.Core/Download/Clients/Vuze/Vuze.cs b/src/NzbDrone.Core/Download/Clients/Vuze/Vuze.cs index c52a8de88..14ab2534f 100644 --- a/src/NzbDrone.Core/Download/Clients/Vuze/Vuze.cs +++ b/src/NzbDrone.Core/Download/Clients/Vuze/Vuze.cs @@ -4,6 +4,7 @@ using NzbDrone.Common.Disk; using NzbDrone.Common.Http; using NzbDrone.Core.Configuration; using NzbDrone.Core.Download.Clients.Transmission; +using NzbDrone.Core.Localization; using NzbDrone.Core.MediaFiles.TorrentInfo; using NzbDrone.Core.RemotePathMappings; @@ -19,8 +20,9 @@ namespace NzbDrone.Core.Download.Clients.Vuze IConfigService configService, IDiskProvider diskProvider, IRemotePathMappingService remotePathMappingService, - Logger logger) - : base(proxy, torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, logger) + Logger logger, + ILocalizationService localizationService) + : base(proxy, torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, logger, localizationService) { } @@ -57,7 +59,7 @@ namespace NzbDrone.Core.Download.Clients.Vuze if (!int.TryParse(versionString, out var version) || version < MINIMUM_SUPPORTED_PROTOCOL_VERSION) { { - return new ValidationFailure(string.Empty, "Protocol version not supported, use Vuze 5.0.0.0 or higher with Vuze Web Remote plugin."); + return new ValidationFailure(string.Empty, _localizationService.GetLocalizedString("DownloadClientVuzeValidationErrorVersion")); } } diff --git a/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrent.cs b/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrent.cs index ebfbe8559..bbfb2fe8e 100644 --- a/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrent.cs +++ b/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrent.cs @@ -11,6 +11,7 @@ using NzbDrone.Common.Http; using NzbDrone.Core.Configuration; using NzbDrone.Core.Download.Clients.rTorrent; using NzbDrone.Core.Exceptions; +using NzbDrone.Core.Localization; using NzbDrone.Core.MediaFiles.TorrentInfo; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.RemotePathMappings; @@ -34,8 +35,9 @@ namespace NzbDrone.Core.Download.Clients.RTorrent IRemotePathMappingService remotePathMappingService, IDownloadSeedConfigProvider downloadSeedConfigProvider, IRTorrentDirectoryValidator rTorrentDirectoryValidator, - Logger logger) - : base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, logger) + Logger logger, + ILocalizationService localizationService) + : base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, logger, localizationService) { _proxy = proxy; _rTorrentDirectoryValidator = rTorrentDirectoryValidator; @@ -115,7 +117,7 @@ namespace NzbDrone.Core.Download.Clients.RTorrent public override string Name => "rTorrent"; - public override ProviderMessage Message => new ProviderMessage($"rTorrent will not pause torrents when they meet the seed criteria. Sonarr will handle automatic removal of torrents based on the current seed criteria in Settings->Indexers only when Remove Completed is enabled. After importing it will also set \"{_imported_view}\" as an rTorrent view, which can be used in rTorrent scripts to customize behavior.", ProviderMessageType.Info); + public override ProviderMessage Message => new ProviderMessage(_localizationService.GetLocalizedString("DownloadClientRTorrentProviderMessage", new Dictionary<string, object> { { "importedView", _imported_view } }), ProviderMessageType.Info); public override IEnumerable<DownloadClientItem> GetItems() { @@ -250,14 +252,19 @@ namespace NzbDrone.Core.Download.Clients.RTorrent if (new Version(version) < new Version("0.9.0")) { - return new ValidationFailure(string.Empty, "rTorrent version should be at least 0.9.0. Version reported is {0}", version); + return new ValidationFailure(string.Empty, + _localizationService.GetLocalizedString("DownloadClientValidationErrorVersion", + new Dictionary<string, object> + { + { "clientName", Name }, { "requiredVersion", "0.9.0" }, { "reportedVersion", version } + })); } } catch (Exception ex) { _logger.Error(ex, "Failed to test rTorrent"); - return new NzbDroneValidationFailure("Host", "Unable to connect to rTorrent") + return new NzbDroneValidationFailure("Host", _localizationService.GetLocalizedString("DownloadClientValidationUnableToConnect", new Dictionary<string, object> { { "clientName", Name } })) { DetailedDescription = ex.Message }; @@ -275,7 +282,7 @@ namespace NzbDrone.Core.Download.Clients.RTorrent catch (Exception ex) { _logger.Error(ex, "Failed to get torrents"); - return new NzbDroneValidationFailure(string.Empty, "Failed to get the list of torrents: " + ex.Message); + return new NzbDroneValidationFailure(string.Empty, _localizationService.GetLocalizedString("DownloadClientValidationTestTorrents", new Dictionary<string, object> { { "exceptionMessage", ex.Message } })); } return null; diff --git a/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrentSettings.cs b/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrentSettings.cs index 820932cec..90252c585 100644 --- a/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrentSettings.cs +++ b/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrentSettings.cs @@ -37,10 +37,13 @@ namespace NzbDrone.Core.Download.Clients.RTorrent [FieldDefinition(1, Label = "Port", Type = FieldType.Textbox)] public int Port { get; set; } - [FieldDefinition(2, Label = "Use SSL", Type = FieldType.Checkbox, HelpText = "Use secure connection when connecting to rTorrent")] + [FieldDefinition(2, Label = "UseSsl", Type = FieldType.Checkbox, HelpText = "DownloadClientSettingsUseSslHelpText")] + [FieldToken(TokenField.HelpText, "UseSsl", "clientName", "rTorrent")] public bool UseSsl { get; set; } - [FieldDefinition(3, Label = "Url Path", Type = FieldType.Textbox, HelpText = "Path to the XMLRPC endpoint, see http(s)://[host]:[port]/[urlPath]. This is usually RPC2 or [path to ruTorrent]/plugins/rpc/rpc.php when using ruTorrent.")] + [FieldDefinition(3, Label = "DownloadClientRTorrentSettingsUrlPath", Type = FieldType.Textbox, HelpText = "DownloadClientRTorrentSettingsUrlPathHelpText")] + [FieldToken(TokenField.HelpText, "DownloadClientRTorrentSettingsUrlPath", "url", "http(s)://[host]:[port]/[urlPath]")] + [FieldToken(TokenField.HelpText, "DownloadClientRTorrentSettingsUrlPath", "url2", "/plugins/rpc/rpc.php")] public string UrlBase { get; set; } [FieldDefinition(4, Label = "Username", Type = FieldType.Textbox, Privacy = PrivacyLevel.UserName)] @@ -49,22 +52,22 @@ namespace NzbDrone.Core.Download.Clients.RTorrent [FieldDefinition(5, Label = "Password", Type = FieldType.Password, Privacy = PrivacyLevel.Password)] public string Password { get; set; } - [FieldDefinition(6, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Sonarr avoids conflicts with unrelated non-Sonarr downloads. Using a category is optional, but strongly recommended.")] + [FieldDefinition(6, Label = "Category", Type = FieldType.Textbox, HelpText = "DownloadClientSettingsCategoryHelpText")] public string TvCategory { get; set; } - [FieldDefinition(7, Label = "Post-Import Category", Type = FieldType.Textbox, Advanced = true, HelpText = "Category for Sonarr to set after it has imported the download. Sonarr will not remove the torrent if seeding has finished. Leave blank to keep same category.")] + [FieldDefinition(7, Label = "PostImportCategory", Type = FieldType.Textbox, Advanced = true, HelpText = "DownloadClientSettingsPostImportCategoryHelpText")] public string TvImportedCategory { get; set; } - [FieldDefinition(8, Label = "Directory", Type = FieldType.Textbox, Advanced = true, HelpText = "Optional location to put downloads in, leave blank to use the default rTorrent location")] + [FieldDefinition(8, Label = "Directory", Type = FieldType.Textbox, Advanced = true, HelpText = "DownloadClientRTorrentSettingsDirectoryHelpText")] public string TvDirectory { get; set; } - [FieldDefinition(9, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(RTorrentPriority), HelpText = "Priority to use when grabbing episodes that aired within the last 14 days")] + [FieldDefinition(9, Label = "DownloadClientSettingsRecentPriority", Type = FieldType.Select, SelectOptions = typeof(RTorrentPriority), HelpText = "DownloadClientSettingsRecentPriorityHelpText")] public int RecentTvPriority { get; set; } - [FieldDefinition(10, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(RTorrentPriority), HelpText = "Priority to use when grabbing episodes that aired over 14 days ago")] + [FieldDefinition(10, Label = "DownloadClientSettingsOlderPriority", Type = FieldType.Select, SelectOptions = typeof(RTorrentPriority), HelpText = "DownloadClientSettingsOlderPriorityHelpText")] public int OlderTvPriority { get; set; } - [FieldDefinition(11, Label = "Add Stopped", Type = FieldType.Checkbox, HelpText = "Enabling will add torrents and magnets to rTorrent in a stopped state. This may break magnet files.")] + [FieldDefinition(11, Label = "DownloadClientRTorrentSettingsAddStopped", Type = FieldType.Checkbox, HelpText = "DownloadClientRTorrentSettingsAddStoppedHelpText")] public bool AddStopped { get; set; } public NzbDroneValidationResult Validate() diff --git a/src/NzbDrone.Core/Download/Clients/uTorrent/UTorrent.cs b/src/NzbDrone.Core/Download/Clients/uTorrent/UTorrent.cs index 6a7b372e4..0c2c30e31 100644 --- a/src/NzbDrone.Core/Download/Clients/uTorrent/UTorrent.cs +++ b/src/NzbDrone.Core/Download/Clients/uTorrent/UTorrent.cs @@ -9,6 +9,7 @@ using NzbDrone.Common.Disk; using NzbDrone.Common.Extensions; using NzbDrone.Common.Http; using NzbDrone.Core.Configuration; +using NzbDrone.Core.Localization; using NzbDrone.Core.MediaFiles.TorrentInfo; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.RemotePathMappings; @@ -28,8 +29,9 @@ namespace NzbDrone.Core.Download.Clients.UTorrent IConfigService configService, IDiskProvider diskProvider, IRemotePathMappingService remotePathMappingService, - Logger logger) - : base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, logger) + Logger logger, + ILocalizationService localizationService) + : base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, logger, localizationService) { _proxy = proxy; @@ -141,7 +143,7 @@ namespace NzbDrone.Core.Download.Clients.UTorrent if (torrent.Status.HasFlag(UTorrentTorrentStatus.Error)) { item.Status = DownloadItemStatus.Warning; - item.Message = "uTorrent is reporting an error"; + item.Message = _localizationService.GetLocalizedString("DownloadClientUTorrentTorrentStateError"); } else if (torrent.Status.HasFlag(UTorrentTorrentStatus.Loaded) && torrent.Status.HasFlag(UTorrentTorrentStatus.Checked) && torrent.Remaining == 0 && torrent.Progress == 1.0) @@ -264,15 +266,20 @@ namespace NzbDrone.Core.Download.Clients.UTorrent if (version < 25406) { - return new ValidationFailure(string.Empty, "Old uTorrent client with unsupported API, need 3.0 or higher"); + return new ValidationFailure(string.Empty, + _localizationService.GetLocalizedString("DownloadClientValidationErrorVersion", + new Dictionary<string, object> + { + { "clientName", Name }, { "requiredVersion", "3.0" }, { "reportedVersion", version } + })); } } catch (DownloadClientAuthenticationException ex) { _logger.Error(ex, ex.Message); - return new NzbDroneValidationFailure("Username", "Authentication failure") + return new NzbDroneValidationFailure("Username", _localizationService.GetLocalizedString("DownloadClientValidationAuthenticationFailure")) { - DetailedDescription = "Please verify your username and password." + DetailedDescription = _localizationService.GetLocalizedString("DownloadClientValidationAuthenticationFailureDetail", new Dictionary<string, object> { { "clientName", Name } }) }; } catch (WebException ex) @@ -280,19 +287,19 @@ namespace NzbDrone.Core.Download.Clients.UTorrent _logger.Error(ex, "Unable to connect to uTorrent"); if (ex.Status == WebExceptionStatus.ConnectFailure) { - return new NzbDroneValidationFailure("Host", "Unable to connect") + return new NzbDroneValidationFailure("Host", _localizationService.GetLocalizedString("DownloadClientValidationUnableToConnect", new Dictionary<string, object> { { "clientName", Name } })) { - DetailedDescription = "Please verify the hostname and port." + DetailedDescription = _localizationService.GetLocalizedString("DownloadClientValidationUnableToConnectDetail") }; } - return new NzbDroneValidationFailure(string.Empty, "Unknown exception: " + ex.Message); + return new NzbDroneValidationFailure(string.Empty, _localizationService.GetLocalizedString("DownloadClientValidationUnknownException", new Dictionary<string, object> { { "exception", ex.Message } })); } catch (Exception ex) { _logger.Error(ex, "Failed to test uTorrent"); - return new NzbDroneValidationFailure("Host", "Unable to connect to uTorrent") + return new NzbDroneValidationFailure("Host", _localizationService.GetLocalizedString("DownloadClientValidationUnableToConnect", new Dictionary<string, object> { { "clientName", Name } })) { DetailedDescription = ex.Message }; @@ -310,7 +317,7 @@ namespace NzbDrone.Core.Download.Clients.UTorrent catch (Exception ex) { _logger.Error(ex, "Failed to get torrents"); - return new NzbDroneValidationFailure(string.Empty, "Failed to get the list of torrents: " + ex.Message); + return new NzbDroneValidationFailure(string.Empty, _localizationService.GetLocalizedString("DownloadClientValidationTestTorrents", new Dictionary<string, object> { { "exceptionMessage", ex.Message } })); } return null; diff --git a/src/NzbDrone.Core/Download/Clients/uTorrent/UTorrentSettings.cs b/src/NzbDrone.Core/Download/Clients/uTorrent/UTorrentSettings.cs index e6ae23df5..711815bc2 100644 --- a/src/NzbDrone.Core/Download/Clients/uTorrent/UTorrentSettings.cs +++ b/src/NzbDrone.Core/Download/Clients/uTorrent/UTorrentSettings.cs @@ -34,10 +34,13 @@ namespace NzbDrone.Core.Download.Clients.UTorrent [FieldDefinition(1, Label = "Port", Type = FieldType.Textbox)] public int Port { get; set; } - [FieldDefinition(2, Label = "Use SSL", Type = FieldType.Checkbox, HelpText = "Use secure connection when connecting to uTorrent")] + [FieldDefinition(2, Label = "UseSsl", Type = FieldType.Checkbox, HelpText = "DownloadClientSettingsUseSslHelpText")] + [FieldToken(TokenField.HelpText, "UseSsl", "clientName", "uTorrent")] public bool UseSsl { get; set; } - [FieldDefinition(3, Label = "Url Base", Type = FieldType.Textbox, Advanced = true, HelpText = "Adds a prefix to the uTorrent url, e.g. http://[host]:[port]/[urlBase]/api")] + [FieldDefinition(3, Label = "UrlBase", Type = FieldType.Textbox, Advanced = true, HelpText = "DownloadClientSettingsUrlBaseHelpText")] + [FieldToken(TokenField.HelpText, "UrlBase", "clientName", "uTorrent")] + [FieldToken(TokenField.HelpText, "UrlBase", "url", "http://[host]:[port]/[urlBase]/api")] public string UrlBase { get; set; } [FieldDefinition(4, Label = "Username", Type = FieldType.Textbox, Privacy = PrivacyLevel.UserName)] @@ -46,19 +49,20 @@ namespace NzbDrone.Core.Download.Clients.UTorrent [FieldDefinition(5, Label = "Password", Type = FieldType.Password, Privacy = PrivacyLevel.Password)] public string Password { get; set; } - [FieldDefinition(6, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Sonarr avoids conflicts with unrelated non-Sonarr downloads. Using a category is optional, but strongly recommended.")] + [FieldDefinition(6, Label = "Category", Type = FieldType.Textbox, HelpText = "DownloadClientSettingsCategoryHelpText")] public string TvCategory { get; set; } - [FieldDefinition(7, Label = "Post-Import Category", Type = FieldType.Textbox, Advanced = true, HelpText = "Category for Sonarr to set after it has imported the download. Sonarr will not remove the torrent if seeding has finished. Leave blank to keep same category.")] + [FieldDefinition(7, Label = "PostImportCategory", Type = FieldType.Textbox, Advanced = true, HelpText = "DownloadClientSettingsPostImportCategoryHelpText")] public string TvImportedCategory { get; set; } - [FieldDefinition(8, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(UTorrentPriority), HelpText = "Priority to use when grabbing episodes that aired within the last 14 days")] + [FieldDefinition(8, Label = "DownloadClientSettingsRecentPriority", Type = FieldType.Select, SelectOptions = typeof(UTorrentPriority), HelpText = "DownloadClientSettingsRecentPriorityHelpText")] public int RecentTvPriority { get; set; } - [FieldDefinition(9, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(UTorrentPriority), HelpText = "Priority to use when grabbing episodes that aired over 14 days ago")] + [FieldDefinition(9, Label = "DownloadClientSettingsOlderPriority", Type = FieldType.Select, SelectOptions = typeof(UTorrentPriority), HelpText = "DownloadClientSettingsOlderPriorityHelpText")] public int OlderTvPriority { get; set; } - [FieldDefinition(10, Label = "Initial State", Type = FieldType.Select, SelectOptions = typeof(UTorrentState), HelpText = "Initial state for torrents added to uTorrent")] + [FieldDefinition(10, Label = "DownloadClientSettingsInitialState", Type = FieldType.Select, SelectOptions = typeof(UTorrentState), HelpText = "DownloadClientSettingsInitialStateHelpText")] + [FieldToken(TokenField.HelpText, "DownloadClientSettingsInitialState", "clientName", "uTorrent")] public int IntialState { get; set; } public NzbDroneValidationResult Validate() diff --git a/src/NzbDrone.Core/Download/DownloadClientBase.cs b/src/NzbDrone.Core/Download/DownloadClientBase.cs index c87e30fc0..887091891 100644 --- a/src/NzbDrone.Core/Download/DownloadClientBase.cs +++ b/src/NzbDrone.Core/Download/DownloadClientBase.cs @@ -6,6 +6,7 @@ using NLog; using NzbDrone.Common.Disk; using NzbDrone.Core.Configuration; using NzbDrone.Core.Indexers; +using NzbDrone.Core.Localization; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.RemotePathMappings; using NzbDrone.Core.ThingiProvider; @@ -20,6 +21,7 @@ namespace NzbDrone.Core.Download protected readonly IDiskProvider _diskProvider; protected readonly IRemotePathMappingService _remotePathMappingService; protected readonly Logger _logger; + protected readonly ILocalizationService _localizationService; public abstract string Name { get; } @@ -41,12 +43,14 @@ namespace NzbDrone.Core.Download protected DownloadClientBase(IConfigService configService, IDiskProvider diskProvider, IRemotePathMappingService remotePathMappingService, - Logger logger) + Logger logger, + ILocalizationService localizationService) { _configService = configService; _diskProvider = diskProvider; _remotePathMappingService = remotePathMappingService; _logger = logger; + _localizationService = localizationService; } public override string ToString() diff --git a/src/NzbDrone.Core/Download/TorrentClientBase.cs b/src/NzbDrone.Core/Download/TorrentClientBase.cs index a1e921872..872b2e9ba 100644 --- a/src/NzbDrone.Core/Download/TorrentClientBase.cs +++ b/src/NzbDrone.Core/Download/TorrentClientBase.cs @@ -9,6 +9,7 @@ using NzbDrone.Common.Http; using NzbDrone.Core.Configuration; using NzbDrone.Core.Exceptions; using NzbDrone.Core.Indexers; +using NzbDrone.Core.Localization; using NzbDrone.Core.MediaFiles.TorrentInfo; using NzbDrone.Core.Organizer; using NzbDrone.Core.Parser.Model; @@ -24,12 +25,13 @@ namespace NzbDrone.Core.Download protected readonly ITorrentFileInfoReader _torrentFileInfoReader; protected TorrentClientBase(ITorrentFileInfoReader torrentFileInfoReader, - IHttpClient httpClient, - IConfigService configService, - IDiskProvider diskProvider, - IRemotePathMappingService remotePathMappingService, - Logger logger) - : base(configService, diskProvider, remotePathMappingService, logger) + IHttpClient httpClient, + IConfigService configService, + IDiskProvider diskProvider, + IRemotePathMappingService remotePathMappingService, + Logger logger, + ILocalizationService localizationService) + : base(configService, diskProvider, remotePathMappingService, logger, localizationService) { _httpClient = httpClient; _torrentFileInfoReader = torrentFileInfoReader; diff --git a/src/NzbDrone.Core/Download/UsenetClientBase.cs b/src/NzbDrone.Core/Download/UsenetClientBase.cs index 319f3ad5a..14d87ef09 100644 --- a/src/NzbDrone.Core/Download/UsenetClientBase.cs +++ b/src/NzbDrone.Core/Download/UsenetClientBase.cs @@ -6,6 +6,7 @@ using NzbDrone.Common.Http; using NzbDrone.Core.Configuration; using NzbDrone.Core.Exceptions; using NzbDrone.Core.Indexers; +using NzbDrone.Core.Localization; using NzbDrone.Core.Organizer; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.RemotePathMappings; @@ -24,8 +25,9 @@ namespace NzbDrone.Core.Download IDiskProvider diskProvider, IRemotePathMappingService remotePathMappingService, IValidateNzbs nzbValidationService, - Logger logger) - : base(configService, diskProvider, remotePathMappingService, logger) + Logger logger, + ILocalizationService localizationService) + : base(configService, diskProvider, remotePathMappingService, logger, localizationService) { _httpClient = httpClient; _nzbValidationService = nzbValidationService; diff --git a/src/NzbDrone.Core/Localization/Core/en.json b/src/NzbDrone.Core/Localization/Core/en.json index fc59a7b89..13b227326 100644 --- a/src/NzbDrone.Core/Localization/Core/en.json +++ b/src/NzbDrone.Core/Localization/Core/en.json @@ -139,6 +139,9 @@ "BeforeUpdate": "Before update", "BindAddress": "Bind Address", "BindAddressHelpText": "Valid IP address, localhost or '*' for all interfaces", + "BlackholeFolderHelpText": "Folder in which {appName} will store the {extension} file", + "BlackholeWatchFolder": "Watch Folder", + "BlackholeWatchFolderHelpText": "Folder from which {appName} should import completed downloads", "Blocklist": "Blocklist", "BlocklistLoadError": "Unable to load blocklist", "BlocklistRelease": "Blocklist Release", @@ -171,6 +174,7 @@ "Cancel": "Cancel", "CancelPendingTask": "Are you sure you want to cancel this pending task?", "CancelProcessing": "Cancel Processing", + "Category": "Category", "CertificateValidation": "Certificate Validation", "CertificateValidationHelpText": "Change how strict HTTPS certification validation is. Do not change unless you understand the risks.", "Certification": "Certification", @@ -339,11 +343,13 @@ "DeletedReasonMissingFromDisk": "{appName} was unable to find the file on disk so the file was unlinked from the episode in the database", "DeletedReasonUpgrade": "File was deleted to import an upgrade", "DeletedSeriesDescription": "Series was deleted from TheTVDB", + "Destination": "Destination", "DestinationPath": "Destination Path", "DestinationRelativePath": "Destination Relative Path", "DetailedProgressBar": "Detailed Progress Bar", "DetailedProgressBarHelpText": "Show text on progress bar", "Details": "Details", + "Directory": "Directory", "Disabled": "Disabled", "DisabledForLocalAddresses": "Disabled for Local Addresses", "Discord": "Discord", @@ -360,14 +366,136 @@ "DownloadClient": "Download Client", "DownloadClientCheckNoneAvailableHealthCheckMessage": "No download client is available", "DownloadClientCheckUnableToCommunicateWithHealthCheckMessage": "Unable to communicate with {downloadClientName}. {errorMessage}", + "DownloadClientDelugeSettingsUrlBaseHelpText": "Adds a prefix to the deluge json url, see {url}", + "DownloadClientDelugeTorrentStateError": "Deluge is reporting an error", + "DownloadClientDelugeValidationLabelPluginFailure": "Configuration of label failed", + "DownloadClientDelugeValidationLabelPluginFailureDetail": "{appName} was unable to add the label to {clientName}.", + "DownloadClientDelugeValidationLabelPluginInactive": "Label plugin not activated", + "DownloadClientDelugeValidationLabelPluginInactiveDetail": "You must have the Label plugin enabled in {clientName} to use categories.", + "DownloadClientDownloadStationProviderMessage": "{appName} is unable to connect to Download Station if 2-Factor Authentication is enabled on your DSM account", + "DownloadClientDownloadStationSettingsDirectory": "Optional shared folder to put downloads into, leave blank to use the default Download Station location", + "DownloadClientDownloadStationValidationApiVersion": "Download Station API version not supported, should be at least {requiredVersion}. It supports from {minVersion} to {maxVersion}", + "DownloadClientDownloadStationValidationFolderMissing": "Folder does not exist", + "DownloadClientDownloadStationValidationFolderMissingDetail": "The folder '{downloadDir}' does not exist, it must be created manually inside the Shared Folder '{sharedFolder}'.", + "DownloadClientDownloadStationValidationNoDefaultDestination": "No default destination", + "DownloadClientDownloadStationValidationNoDefaultDestinationDetail": "You must login into your Diskstation as {username} and manually set it up into DownloadStation settings under BT/HTTP/FTP/NZB -> Location.", + "DownloadClientDownloadStationValidationSharedFolderMissing": "Shared folder does not exist", + "DownloadClientDownloadStationValidationSharedFolderMissingDetail": "The Diskstation does not have a Shared Folder with the name '{sharedFolder}', are you sure you specified it correctly?", + "DownloadClientFloodSettingsAdditionalTags": "Additional Tags", + "DownloadClientFloodSettingsAdditionalTagsHelpText": "Adds properties of media as tags. Hints are examples.", + "DownloadClientFloodSettingsPostImportTags": "Post-Import Tags", + "DownloadClientFloodSettingsPostImportTagsHelpText": "Appends tags after a download is imported.", + "DownloadClientFloodSettingsRemovalInfo": "{appName} will handle automatic removal of torrents based on the current seed criteria in Settings -> Indexers", + "DownloadClientFloodSettingsStartOnAdd": "Start on Add", + "DownloadClientFloodSettingsTagsHelpText": "Initial tags of a download. To be recognized, a download must have all initial tags. This avoids conflicts with unrelated downloads.", + "DownloadClientFloodSettingsUrlBaseHelpText": "Adds a prefix to the Flood API, such as {url}", + "DownloadClientFreeboxApiError": "Freebox API returned error: {errorDescription}", + "DownloadClientFreeboxAuthenticationError": "Authentication to Freebox API failed. Reason: {errorDescription}", + "DownloadClientFreeboxNotLoggedIn": "Not logged in", + "DownloadClientFreeboxSettingsApiUrl": "API URL", + "DownloadClientFreeboxSettingsApiUrlHelpText": "Define Freebox API base URL with API version, eg '{url}', defaults to '{defaultApiUrl}'", + "DownloadClientFreeboxSettingsAppId": "App ID", + "DownloadClientFreeboxSettingsAppIdHelpText": "App ID given when creating access to Freebox API (ie 'app_id')", + "DownloadClientFreeboxSettingsAppToken": "App Token", + "DownloadClientFreeboxSettingsAppTokenHelpText": "App token retrieved when creating access to Freebox API (ie 'app_token')", + "DownloadClientFreeboxSettingsHostHelpText": "Hostname or host IP address of the Freebox, defaults to '{url}' (will only work if on same network)", + "DownloadClientFreeboxSettingsPortHelpText": "Port used to access Freebox interface, defaults to '{port}'", + "DownloadClientFreeboxUnableToReachFreebox": "Unable to reach Freebox API. Verify 'Host', 'Port' or 'Use SSL' settings. (Error: {exceptionMessage})", + "DownloadClientFreeboxUnableToReachFreeboxApi": "Unable to reach Freebox API. Verify 'API URL' setting for base URL and version.", + "DownloadClientNzbVortexMultipleFilesMessage": "Download contains multiple files and is not in a job folder: {outputPath}", + "DownloadClientNzbgetSettingsAddPausedHelpText": "This option requires at least NzbGet version 16.0", + "DownloadClientNzbgetValidationKeepHistoryOverMax": "NzbGet setting KeepHistory should be less than 25000", + "DownloadClientNzbgetValidationKeepHistoryOverMaxDetail": "NzbGet setting KeepHistory is set too high.", + "DownloadClientNzbgetValidationKeepHistoryZero": "NzbGet setting KeepHistory should be greater than 0", + "DownloadClientNzbgetValidationKeepHistoryZeroDetail": "NzbGet setting KeepHistory is set to 0. Which prevents {appName} from seeing completed downloads.", "DownloadClientOptionsLoadError": "Unable to load download client options", + "DownloadClientPneumaticSettingsNzbFolder": "Nzb Folder", + "DownloadClientPneumaticSettingsNzbFolderHelpText": "This folder will need to be reachable from XBMC", + "DownloadClientPneumaticSettingsStrmFolder": "Strm Folder", + "DownloadClientPneumaticSettingsStrmFolderHelpText": ".strm files in this folder will be import by drone", + "DownloadClientQbittorrentSettingsFirstAndLastFirst": "First and Last First", + "DownloadClientQbittorrentSettingsFirstAndLastFirstHelpText": "Download first and last pieces first (qBittorrent 4.1.0+)", + "DownloadClientQbittorrentSettingsInitialStateHelpText": "Initial state for torrents added to qBittorrent. Note that Forced Torrents do not abide by seed restrictions", + "DownloadClientQbittorrentSettingsSequentialOrder": "Sequential Order", + "DownloadClientQbittorrentSettingsSequentialOrderHelpText": "Download in sequential order (qBittorrent 4.1.0+)", + "DownloadClientQbittorrentSettingsUseSslHelpText": "Use a secure connection. See Options -> Web UI -> 'Use HTTPS instead of HTTP' in qBittorrent.", + "DownloadClientQbittorrentTorrentStateDhtDisabled": "qBittorrent cannot resolve magnet link with DHT disabled", + "DownloadClientQbittorrentTorrentStateError": "qBittorrent is reporting an error", + "DownloadClientQbittorrentTorrentStateMetadata": "qBittorrent is downloading metadata", + "DownloadClientQbittorrentTorrentStatePathError": "Unable to Import. Path matches client base download directory, it's possible 'Keep top-level folder' is disabled for this torrent or 'Torrent Content Layout' is NOT set to 'Original' or 'Create Subfolder'?", + "DownloadClientQbittorrentTorrentStateStalled": "The download is stalled with no connections", + "DownloadClientQbittorrentTorrentStateUnknown": "Unknown download state: {state}", + "DownloadClientQbittorrentValidationCategoryAddFailure": "Configuration of category failed", + "DownloadClientQbittorrentValidationCategoryAddFailureDetail": "{appName} was unable to add the label to qBittorrent.", + "DownloadClientQbittorrentValidationCategoryRecommended": "Category is recommended", + "DownloadClientQbittorrentValidationCategoryRecommendedDetail": "{appName} will not attempt to import completed downloads without a category.", + "DownloadClientQbittorrentValidationCategoryUnsupported": "Category is not supported", + "DownloadClientQbittorrentValidationCategoryUnsupportedDetail": "Categories are not supported until qBittorrent version 3.3.0. Please upgrade or try again with an empty Category.", + "DownloadClientQbittorrentValidationQueueingNotEnabled": "Queueing Not Enabled", + "DownloadClientQbittorrentValidationQueueingNotEnabledDetail": "Torrent Queueing is not enabled in your qBittorrent settings. Enable it in qBittorrent or select 'Last' as priority.", + "DownloadClientQbittorrentValidationRemovesAtRatioLimit": "qBittorrent is configured to remove torrents when they reach their Share Ratio Limit", + "DownloadClientQbittorrentValidationRemovesAtRatioLimitDetail": "{appName} will be unable to perform Completed Download Handling as configured. You can fix this in qBittorrent ('Tools -> Options...' in the menu) by changing 'Options -> BitTorrent -> Share Ratio Limiting' from 'Remove them' to 'Pause them'", + "DownloadClientRTorrentProviderMessage": "rTorrent will not pause torrents when they meet the seed criteria. {appName} will handle automatic removal of torrents based on the current seed criteria in Settings->Indexers only when Remove Completed is enabled. After importing it will also set {importedView} as an rTorrent view, which can be used in rTorrent scripts to customize behavior.", + "DownloadClientRTorrentSettingsAddStopped": "Add Stopped", + "DownloadClientRTorrentSettingsAddStoppedHelpText": "Enabling will add torrents and magnets to rTorrent in a stopped state. This may break magnet files.", + "DownloadClientRTorrentSettingsDirectoryHelpText": "Optional location to put downloads in, leave blank to use the default rTorrent location", + "DownloadClientRTorrentSettingsUrlPath": "Url Path", + "DownloadClientRTorrentSettingsUrlPathHelpText": "Path to the XMLRPC endpoint, see {url}. This is usually RPC2 or [path to ruTorrent]{url2} when using ruTorrent.", "DownloadClientRemovesCompletedDownloadsHealthCheckMessage": "Download client {downloadClientName} is set to remove completed downloads. This can result in downloads being removed from your client before {appName} can import them.", "DownloadClientRootFolderHealthCheckMessage": "Download client {downloadClientName} places downloads in the root folder {rootFolderPath}. You should not download to a root folder.", + "DownloadClientSabnzbdValidationCheckBeforeDownload": "Disable 'Check before download' option in Sabnbzd", + "DownloadClientSabnzbdValidationCheckBeforeDownloadDetail": "Using 'Check before download' affects {appName} ability to track new downloads. Also Sabnzbd recommends 'Abort jobs that cannot be completed' instead since it's more effective.", + "DownloadClientSabnzbdValidationDevelopVersion": "Sabnzbd develop version, assuming version 3.0.0 or higher.", + "DownloadClientSabnzbdValidationDevelopVersionDetail": "{appName} may not be able to support new features added to SABnzbd when running develop versions.", + "DownloadClientSabnzbdValidationEnableDisableDateSorting": "Disable Date Sorting", + "DownloadClientSabnzbdValidationEnableDisableDateSortingDetail": "You must disable Date sorting for the category {appName} uses to prevent import issues. Go to Sabnzbd to fix it.", + "DownloadClientSabnzbdValidationEnableDisableMovieSorting": "Disable Movie Sorting", + "DownloadClientSabnzbdValidationEnableDisableMovieSortingDetail": "You must disable Movie sorting for the category {appName} uses to prevent import issues. Go to Sabnzbd to fix it.", + "DownloadClientSabnzbdValidationEnableDisableTvSorting": "Disable TV Sorting", + "DownloadClientSabnzbdValidationEnableDisableTvSortingDetail": "You must disable TV sorting for the category {appName} uses to prevent import issues. Go to Sabnzbd to fix it.", + "DownloadClientSabnzbdValidationEnableJobFolders": "Enable Job folders", + "DownloadClientSabnzbdValidationEnableJobFoldersDetail": "{appName} prefers each download to have a separate folder. With * appended to the Folder/Path Sabnzbd will not create these job folders. Go to Sabnzbd to fix it.", + "DownloadClientSabnzbdValidationUnknownVersion": "Unknown Version: {rawVersion}", "DownloadClientSettings": "Download Client Settings", + "DownloadClientSettingsAddPaused": "Add Paused", + "DownloadClientSettingsCategoryHelpText": "Adding a category specific to {appName} avoids conflicts with unrelated non-{appName} downloads. Using a category is optional, but strongly recommended.", + "DownloadClientSettingsCategorySubFolderHelpText": "Adding a category specific to {appName} avoids conflicts with unrelated non-{appName} downloads. Using a category is optional, but strongly recommended. Creates a [category] subdirectory in the output directory.", + "DownloadClientSettingsDestinationHelpText": "Manually specifies download destination, leave blank to use the default", + "DownloadClientSettingsInitialState": "Initial State", + "DownloadClientSettingsInitialStateHelpText": "Initial state for torrents added to {clientName}", + "DownloadClientSettingsOlderPriority": "Older Priority", + "DownloadClientSettingsOlderPriorityHelpText": "Priority to use when grabbing episodes that aired over 14 days ago", + "DownloadClientSettingsPostImportCategoryHelpText": "Category for {appName} to set after it has imported the download. {appName} will not remove torrents in that category even if seeding finished. Leave blank to keep same category.", + "DownloadClientSettingsRecentPriority": "Recent Priority", + "DownloadClientSettingsRecentPriorityHelpText": "Priority to use when grabbing episodes that aired within the last 14 days", + "DownloadClientSettingsUrlBaseHelpText": "Adds a prefix to the {clientName} url, such as {url}", + "DownloadClientSettingsUseSslHelpText": "Use secure connection when connection to {clientName}", "DownloadClientSortingHealthCheckMessage": "Download client {downloadClientName} has {sortingMode} sorting enabled for {appName}'s category. You should disable sorting in your download client to avoid import issues.", "DownloadClientStatusAllClientHealthCheckMessage": "All download clients are unavailable due to failures", "DownloadClientStatusSingleClientHealthCheckMessage": "Download clients unavailable due to failures: {downloadClientNames}", "DownloadClientTagHelpText": "Only use this download client for series with at least one matching tag. Leave blank to use with all series.", + "DownloadClientTransmissionSettingsDirectoryHelpText": "Optional location to put downloads in, leave blank to use the default Transmission location", + "DownloadClientTransmissionSettingsUrlBaseHelpText": "Adds a prefix to the {clientName} rpc url, eg {url}, defaults to '{defaultUrl}'", + "DownloadClientUTorrentTorrentStateError": "uTorrent is reporting an error", + "DownloadClientValidationApiKeyIncorrect": "API Key Incorrect", + "DownloadClientValidationApiKeyRequired": "API Key Required", + "DownloadClientValidationAuthenticationFailure": "Authentication Failure", + "DownloadClientValidationAuthenticationFailureDetail": "Please verify your username and password. Also verify if the host running {appName} isn't blocked from accessing {clientName} by WhiteList limitations in the {clientName} configuration.", + "DownloadClientValidationCategoryMissing": "Category does not exist", + "DownloadClientValidationCategoryMissingDetail": "The category you entered doesn't exist in {clientName}. Create it in {clientName} first.", + "DownloadClientValidationErrorVersion": "{clientName} version should be at least {requiredVersion}. Version reported is {reportedVersion}", + "DownloadClientValidationGroupMissing": "Group does not exist", + "DownloadClientValidationGroupMissingDetail": "The group you entered doesn't exist in {clientName}. Create it in {clientName} first.", + "DownloadClientValidationSslConnectFailure": "Unable to connect through SSL", + "DownloadClientValidationSslConnectFailureDetail": "{appName} is unable to connect to {clientName} using SSL. This problem could be computer related. Please try to configure both {appName} and {clientName} to not use SSL.", + "DownloadClientValidationTestNzbs": "Failed to get the list of NZBs: {exceptionMessage}", + "DownloadClientValidationTestTorrents": "Failed to get the list of torrents: {exceptionMessage}", + "DownloadClientValidationUnableToConnect": "Unable to connect to {clientName}", + "DownloadClientValidationUnableToConnectDetail": "Please verify the hostname and port.", + "DownloadClientValidationUnknownException": "Unknown exception: {exception}", + "DownloadClientValidationVerifySsl": "Verify SSL settings", + "DownloadClientValidationVerifySslDetail": "Please verify your SSL configuration on both {clientName} and {appName}", + "DownloadClientVuzeValidationErrorVersion": "Protocol version not supported, use Vuze 5.0.0.0 or higher with Vuze Web Remote plugin.", "DownloadClients": "Download Clients", "DownloadClientsLoadError": "Unable to load download clients", "DownloadClientsSettingsSummary": "Download clients, download handling and remote path mappings", @@ -379,6 +507,7 @@ "DownloadPropersAndRepacksHelpText": "Whether or not to automatically upgrade to Propers/Repacks", "DownloadPropersAndRepacksHelpTextCustomFormat": "Use 'Do not Prefer' to sort by custom format score over Propers/Repacks", "DownloadPropersAndRepacksHelpTextWarning": "Use custom formats for automatic upgrades to Propers/Repacks", + "DownloadStationStatusExtracting": "Extracting: {progress}%", "DownloadWarning": "Download warning: {warningMessage}", "Downloaded": "Downloaded", "Downloading": "Downloading", @@ -887,6 +1016,7 @@ "NotificationTriggersHelpText": "Select which events should trigger this notification", "NotificationsLoadError": "Unable to load Notifications", "NotificationsTagsHelpText": "Only send notifications for series with at least one matching tag", + "NzbgetHistoryItemMessage": "PAR Status: {parStatus} - Unpack Status: {unpackStatus} - Move Status: {moveStatus} - Script Status: {scriptStatus} - Delete Status: {deleteStatus} - Mark Status: {markStatus}", "Ok": "Ok", "OnApplicationUpdate": "On Application Update", "OnEpisodeFileDelete": "On Episode File Delete", @@ -956,6 +1086,7 @@ "Permissions": "Permissions", "Port": "Port", "PortNumber": "Port Number", + "PostImportCategory": "Post-Import Category", "PosterOptions": "Poster Options", "PosterSize": "Poster Size", "Posters": "Posters", @@ -1202,6 +1333,7 @@ "SeasonPremiere": "Season Premiere", "SeasonPremieresOnly": "Season Premieres Only", "Seasons": "Seasons", + "SecretToken": "Secret Token", "Security": "Security", "Seeders": "Seeders", "SelectAll": "Select All", @@ -1379,6 +1511,14 @@ "ToggleMonitoredToUnmonitored": "Monitored, click to unmonitor", "ToggleUnmonitoredToMonitored": "Unmonitored, click to monitor", "Tomorrow": "Tomorrow", + "TorrentBlackhole": "Torrent Blackhole", + "TorrentBlackholeSaveMagnetFiles": "Save Magnet Files", + "TorrentBlackholeSaveMagnetFilesExtension": "Save Magnet Files Extension", + "TorrentBlackholeSaveMagnetFilesExtensionHelpText": "Extension to use for magnet links, defaults to '.magnet'", + "TorrentBlackholeSaveMagnetFilesHelpText": "Save the magnet link if no .torrent file is available (only useful if the download client supports magnets saved to a file)", + "TorrentBlackholeSaveMagnetFilesReadOnly": "Read Only", + "TorrentBlackholeSaveMagnetFilesReadOnlyHelpText": "Instead of moving files this will instruct {appName} to Copy or Hardlink (depending on settings/system configuration)", + "TorrentBlackholeTorrentFolder": "Torrent Folder", "TorrentDelay": "Torrent Delay", "TorrentDelayHelpText": "Delay in minutes to wait before grabbing a torrent", "TorrentDelayTime": "Torrent Delay: {torrentDelay}", @@ -1415,6 +1555,7 @@ "Underscore": "Underscore", "Ungroup": "Ungroup", "Unknown": "Unknown", + "UnknownDownloadState": "Unknown download state: {state}", "UnknownEventTooltip": "Unknown event", "Unlimited": "Unlimited", "UnmappedFilesOnly": "Unmapped Files Only", @@ -1459,7 +1600,10 @@ "UseProxy": "Use Proxy", "UseSeasonFolder": "Use Season Folder", "UseSeasonFolderHelpText": "Sort episodes into season folders", + "UseSsl": "Use SSL", "Usenet": "Usenet", + "UsenetBlackhole": "Usenet Blackhole", + "UsenetBlackholeNzbFolder": "Nzb Folder", "UsenetDelay": "Usenet Delay", "UsenetDelayHelpText": "Delay in minutes to wait before grabbing a release from Usenet", "UsenetDelayTime": "Usenet Delay: {usenetDelay}", @@ -1485,6 +1629,7 @@ "Wiki": "Wiki", "WithFiles": "With Files", "WouldYouLikeToRestoreBackup": "Would you like to restore the backup '{name}'?", + "XmlRpcPath": "XML RPC Path", "Year": "Year", "Yes": "Yes", "YesCancel": "Yes, Cancel", diff --git a/src/NzbDrone.Host/Bootstrap.cs b/src/NzbDrone.Host/Bootstrap.cs index a14e9f2ff..0e9ff30a4 100644 --- a/src/NzbDrone.Host/Bootstrap.cs +++ b/src/NzbDrone.Host/Bootstrap.cs @@ -22,6 +22,7 @@ using NzbDrone.Common.Instrumentation; using NzbDrone.Common.Instrumentation.Extensions; using NzbDrone.Core.Configuration; using NzbDrone.Core.Datastore.Extensions; +using Sonarr.Http.ClientSchema; using LogLevel = Microsoft.Extensions.Logging.LogLevel; using PostgresOptions = NzbDrone.Core.Datastore.PostgresOptions; @@ -145,6 +146,8 @@ namespace NzbDrone.Host .AddNzbDroneLogger() .AddDatabase() .AddStartupContext(context); + + SchemaBuilder.Initialize(c); }) .ConfigureServices(services => { diff --git a/src/NzbDrone.Integration.Test/IntegrationTest.cs b/src/NzbDrone.Integration.Test/IntegrationTest.cs index a6721fdcb..9524d39d3 100644 --- a/src/NzbDrone.Integration.Test/IntegrationTest.cs +++ b/src/NzbDrone.Integration.Test/IntegrationTest.cs @@ -1,3 +1,5 @@ +using System; +using System.Linq; using System.Threading; using NLog; using NUnit.Framework; @@ -7,7 +9,6 @@ using NzbDrone.Core.Datastore.Migration.Framework; using NzbDrone.Core.Indexers.Newznab; using NzbDrone.Test.Common; using NzbDrone.Test.Common.Datastore; -using Sonarr.Http.ClientSchema; namespace NzbDrone.Integration.Test { @@ -50,17 +51,20 @@ namespace NzbDrone.Integration.Test // Make sure tasks have been initialized so the config put below doesn't cause errors WaitForCompletion(() => Tasks.All().SelectList(x => x.TaskName).Contains("RssSync")); - Indexers.Post(new Sonarr.Api.V3.Indexers.IndexerResource + var indexer = Indexers.Schema().FirstOrDefault(i => i.Implementation == nameof(Newznab)); + + if (indexer == null) { - EnableRss = false, - EnableInteractiveSearch = false, - EnableAutomaticSearch = false, - ConfigContract = nameof(NewznabSettings), - Implementation = nameof(Newznab), - Name = "NewznabTest", - Protocol = Core.Indexers.DownloadProtocol.Usenet, - Fields = SchemaBuilder.ToSchema(new NewznabSettings()) - }); + throw new NullReferenceException("Expected valid indexer schema, found null"); + } + + indexer.EnableRss = false; + indexer.EnableInteractiveSearch = false; + indexer.EnableAutomaticSearch = false; + indexer.ConfigContract = nameof(NewznabSettings); + indexer.Implementation = nameof(Newznab); + indexer.Name = "NewznabTest"; + indexer.Protocol = Core.Indexers.DownloadProtocol.Usenet; // Change Console Log Level to Debug so we get more details. var config = HostConfig.Get(1); diff --git a/src/NzbDrone.Test.Common/AutoMoq/AutoMoqer.cs b/src/NzbDrone.Test.Common/AutoMoq/AutoMoqer.cs index 6e6d84666..948994c51 100644 --- a/src/NzbDrone.Test.Common/AutoMoq/AutoMoqer.cs +++ b/src/NzbDrone.Test.Common/AutoMoq/AutoMoqer.cs @@ -23,6 +23,8 @@ namespace NzbDrone.Test.Common.AutoMoq SetupAutoMoqer(CreateTestContainer(new Container())); } + public IContainer Container => _container; + public virtual T Resolve<T>() { var result = _container.Resolve<T>(); diff --git a/src/Sonarr.Http/ClientSchema/SchemaBuilder.cs b/src/Sonarr.Http/ClientSchema/SchemaBuilder.cs index 8d2392f2e..cd088677e 100644 --- a/src/Sonarr.Http/ClientSchema/SchemaBuilder.cs +++ b/src/Sonarr.Http/ClientSchema/SchemaBuilder.cs @@ -3,11 +3,13 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text.Json; +using DryIoc; using NzbDrone.Common.EnsureThat; using NzbDrone.Common.Extensions; using NzbDrone.Common.Reflection; using NzbDrone.Common.Serializer; using NzbDrone.Core.Annotations; +using NzbDrone.Core.Localization; namespace Sonarr.Http.ClientSchema { @@ -15,6 +17,12 @@ namespace Sonarr.Http.ClientSchema { private const string PRIVATE_VALUE = "********"; private static Dictionary<Type, FieldMapping[]> _mappings = new Dictionary<Type, FieldMapping[]>(); + private static ILocalizationService _localizationService; + + public static void Initialize(IContainer container) + { + _localizationService = container.Resolve<ILocalizationService>(); + } public static List<Field> ToSchema(object model) { @@ -107,13 +115,27 @@ namespace Sonarr.Http.ClientSchema if (propertyInfo.PropertyType.IsSimpleType()) { var fieldAttribute = property.Item2; + + var label = fieldAttribute.Label.IsNotNullOrWhiteSpace() + ? _localizationService.GetLocalizedString(fieldAttribute.Label, + GetTokens(type, fieldAttribute.Label, TokenField.Label)) + : fieldAttribute.Label; + var helpText = fieldAttribute.HelpText.IsNotNullOrWhiteSpace() + ? _localizationService.GetLocalizedString(fieldAttribute.HelpText, + GetTokens(type, fieldAttribute.Label, TokenField.HelpText)) + : fieldAttribute.HelpText; + var helpTextWarning = fieldAttribute.HelpTextWarning.IsNotNullOrWhiteSpace() + ? _localizationService.GetLocalizedString(fieldAttribute.HelpTextWarning, + GetTokens(type, fieldAttribute.Label, TokenField.HelpTextWarning)) + : fieldAttribute.HelpTextWarning; + var field = new Field { Name = prefix + GetCamelCaseName(propertyInfo.Name), - Label = fieldAttribute.Label, + Label = label, Unit = fieldAttribute.Unit, - HelpText = fieldAttribute.HelpText, - HelpTextWarning = fieldAttribute.HelpTextWarning, + HelpText = helpText, + HelpTextWarning = helpTextWarning, HelpLink = fieldAttribute.HelpLink, Order = fieldAttribute.Order, Advanced = fieldAttribute.Advanced, @@ -173,6 +195,24 @@ namespace Sonarr.Http.ClientSchema .ToArray(); } + private static Dictionary<string, object> GetTokens(Type type, string label, TokenField field) + { + var tokens = new Dictionary<string, object>(); + + foreach (var propertyInfo in type.GetProperties()) + { + foreach (var attribute in propertyInfo.GetCustomAttributes(true)) + { + if (attribute is FieldTokenAttribute fieldTokenAttribute && fieldTokenAttribute.Field == field && fieldTokenAttribute.Label == label) + { + tokens.Add(fieldTokenAttribute.Token, fieldTokenAttribute.Value); + } + } + } + + return tokens; + } + private static List<SelectOption> GetSelectOptions(Type selectOptions) { if (selectOptions.IsEnum) From e68b13940d4dd4c5fd008884d909e614521c8cee Mon Sep 17 00:00:00 2001 From: Qstick <qstick@gmail.com> Date: Sat, 11 Nov 2023 10:36:06 -0600 Subject: [PATCH 104/136] Bump ffprobe to 5.1.4 --- src/NzbDrone.Core/Sonarr.Core.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NzbDrone.Core/Sonarr.Core.csproj b/src/NzbDrone.Core/Sonarr.Core.csproj index b4e214628..94c3a44fe 100644 --- a/src/NzbDrone.Core/Sonarr.Core.csproj +++ b/src/NzbDrone.Core/Sonarr.Core.csproj @@ -8,7 +8,7 @@ <PackageReference Include="MailKit" Version="3.6.0" /> <PackageReference Include="Microsoft.AspNetCore.Cryptography.KeyDerivation" Version="6.0.21" /> <PackageReference Include="Servarr.FFMpegCore" Version="4.7.0-26" /> - <PackageReference Include="Servarr.FFprobe" Version="5.1.2.106" /> + <PackageReference Include="Servarr.FFprobe" Version="5.1.4.112" /> <PackageReference Include="System.Memory" Version="4.5.5" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.1" /> <PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" /> From f205cfababdcabe2213654d88556981b9128af19 Mon Sep 17 00:00:00 2001 From: Stevie Robinson <stevie.robinson@gmail.com> Date: Fri, 17 Nov 2023 01:30:22 +0100 Subject: [PATCH 105/136] Translate Indexers on the backend --- .../DownloadClientFixtureBase.cs | 4 +- .../PneumaticProviderFixture.cs | 4 +- .../IndexerTests/TestIndexer.cs | 5 +- .../TestTorrentRssIndexer.cs | 5 +- .../Indexers/BroadcastheNet/BroadcastheNet.cs | 5 +- .../BroadcastheNet/BroadcastheNetSettings.cs | 6 +-- src/NzbDrone.Core/Indexers/Fanzub/Fanzub.cs | 5 +- .../Indexers/Fanzub/FanzubSettings.cs | 5 +- .../Indexers/FileList/FileList.cs | 5 +- .../Indexers/FileList/FileListSettings.cs | 10 ++-- src/NzbDrone.Core/Indexers/HDBits/HDBits.cs | 5 +- .../Indexers/HDBits/HDBitsSettings.cs | 6 +-- src/NzbDrone.Core/Indexers/HttpIndexerBase.cs | 39 ++++++++------- .../Indexers/IPTorrents/IPTorrents.cs | 5 +- .../Indexers/IPTorrents/IPTorrentsSettings.cs | 4 +- src/NzbDrone.Core/Indexers/IndexerBase.cs | 7 ++- src/NzbDrone.Core/Indexers/Newznab/Newznab.cs | 9 ++-- .../Indexers/Newznab/NewznabSettings.cs | 13 ++--- src/NzbDrone.Core/Indexers/Nyaa/Nyaa.cs | 5 +- .../Indexers/Nyaa/NyaaSettings.cs | 8 +-- .../Indexers/SeedCriteriaSettings.cs | 6 +-- .../Indexers/TorrentRss/TorrentRssIndexer.cs | 5 +- .../TorrentRss/TorrentRssIndexerSettings.cs | 8 +-- .../Indexers/Torrentleech/Torrentleech.cs | 5 +- .../Torrentleech/TorrentleechSettings.cs | 6 +-- src/NzbDrone.Core/Indexers/Torznab/Torznab.cs | 13 ++--- .../Indexers/Torznab/TorznabSettings.cs | 2 +- src/NzbDrone.Core/Localization/Core/en.json | 49 +++++++++++++++++++ 28 files changed, 160 insertions(+), 89 deletions(-) diff --git a/src/NzbDrone.Core.Test/Download/DownloadClientTests/DownloadClientFixtureBase.cs b/src/NzbDrone.Core.Test/Download/DownloadClientTests/DownloadClientFixtureBase.cs index 32e4a6335..804099e4a 100644 --- a/src/NzbDrone.Core.Test/Download/DownloadClientTests/DownloadClientFixtureBase.cs +++ b/src/NzbDrone.Core.Test/Download/DownloadClientTests/DownloadClientFixtureBase.cs @@ -11,6 +11,7 @@ using NzbDrone.Core.Configuration; using NzbDrone.Core.Download; using NzbDrone.Core.Indexers; using NzbDrone.Core.IndexerSearch.Definitions; +using NzbDrone.Core.Localization; using NzbDrone.Core.Parser; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.RemotePathMappings; @@ -70,7 +71,8 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests Mocker.Resolve<IIndexerStatusService>(), Mocker.Resolve<IConfigService>(), Mocker.Resolve<IParsingService>(), - Mocker.Resolve<Logger>()); + Mocker.Resolve<Logger>(), + Mocker.Resolve<ILocalizationService>()); } protected void VerifyIdentifiable(DownloadClientItem downloadClientItem) diff --git a/src/NzbDrone.Core.Test/Download/DownloadClientTests/PneumaticProviderFixture.cs b/src/NzbDrone.Core.Test/Download/DownloadClientTests/PneumaticProviderFixture.cs index d2d206bd6..c11b1317a 100644 --- a/src/NzbDrone.Core.Test/Download/DownloadClientTests/PneumaticProviderFixture.cs +++ b/src/NzbDrone.Core.Test/Download/DownloadClientTests/PneumaticProviderFixture.cs @@ -11,6 +11,7 @@ using NzbDrone.Core.Configuration; using NzbDrone.Core.Download; using NzbDrone.Core.Download.Clients.Pneumatic; using NzbDrone.Core.Indexers; +using NzbDrone.Core.Localization; using NzbDrone.Core.Parser; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Test.Framework; @@ -51,7 +52,8 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests Mocker.Resolve<IIndexerStatusService>(), Mocker.Resolve<IConfigService>(), Mocker.Resolve<IParsingService>(), - Mocker.Resolve<Logger>()); + Mocker.Resolve<Logger>(), + Mocker.Resolve<ILocalizationService>()); _downloadClientItem = Builder<DownloadClientItem> .CreateNew().With(d => d.DownloadId = "_Droned.S01E01.Pilot.1080p.WEB-DL-DRONE_0") diff --git a/src/NzbDrone.Core.Test/IndexerTests/TestIndexer.cs b/src/NzbDrone.Core.Test/IndexerTests/TestIndexer.cs index 2b73b3b73..24fe564dd 100644 --- a/src/NzbDrone.Core.Test/IndexerTests/TestIndexer.cs +++ b/src/NzbDrone.Core.Test/IndexerTests/TestIndexer.cs @@ -2,6 +2,7 @@ using NzbDrone.Common.Http; using NzbDrone.Core.Configuration; using NzbDrone.Core.Indexers; +using NzbDrone.Core.Localization; using NzbDrone.Core.Parser; namespace NzbDrone.Core.Test.IndexerTests @@ -15,8 +16,8 @@ namespace NzbDrone.Core.Test.IndexerTests public int _supportedPageSize; public override int PageSize => _supportedPageSize; - public TestIndexer(IHttpClient httpClient, IIndexerStatusService indexerStatusService, IConfigService configService, IParsingService parsingService, Logger logger) - : base(httpClient, indexerStatusService, configService, parsingService, logger) + public TestIndexer(IHttpClient httpClient, IIndexerStatusService indexerStatusService, IConfigService configService, IParsingService parsingService, Logger logger, ILocalizationService localizationService) + : base(httpClient, indexerStatusService, configService, parsingService, logger, localizationService) { } diff --git a/src/NzbDrone.Core.Test/IndexerTests/TorrentRssIndexerTests/TestTorrentRssIndexer.cs b/src/NzbDrone.Core.Test/IndexerTests/TorrentRssIndexerTests/TestTorrentRssIndexer.cs index d969c5653..301da2da6 100644 --- a/src/NzbDrone.Core.Test/IndexerTests/TorrentRssIndexerTests/TestTorrentRssIndexer.cs +++ b/src/NzbDrone.Core.Test/IndexerTests/TorrentRssIndexerTests/TestTorrentRssIndexer.cs @@ -7,14 +7,15 @@ using NzbDrone.Common.Http; using NzbDrone.Core.Configuration; using NzbDrone.Core.Indexers; using NzbDrone.Core.Indexers.TorrentRss; +using NzbDrone.Core.Localization; using NzbDrone.Core.Parser; namespace NzbDrone.Core.Test.IndexerTests.TorrentRssIndexerTests { public class TestTorrentRssIndexer : TorrentRssIndexer { - public TestTorrentRssIndexer(ITorrentRssParserFactory torrentRssParserFactory, IHttpClient httpClient, IIndexerStatusService indexerStatusService, IConfigService configService, IParsingService parsingService, Logger logger) - : base(torrentRssParserFactory, httpClient, indexerStatusService, configService, parsingService, logger) + public TestTorrentRssIndexer(ITorrentRssParserFactory torrentRssParserFactory, IHttpClient httpClient, IIndexerStatusService indexerStatusService, IConfigService configService, IParsingService parsingService, Logger logger, ILocalizationService localizationService) + : base(torrentRssParserFactory, httpClient, indexerStatusService, configService, parsingService, logger, localizationService) { } diff --git a/src/NzbDrone.Core/Indexers/BroadcastheNet/BroadcastheNet.cs b/src/NzbDrone.Core/Indexers/BroadcastheNet/BroadcastheNet.cs index 2f01995ae..61b994c4e 100644 --- a/src/NzbDrone.Core/Indexers/BroadcastheNet/BroadcastheNet.cs +++ b/src/NzbDrone.Core/Indexers/BroadcastheNet/BroadcastheNet.cs @@ -1,6 +1,7 @@ using NLog; using NzbDrone.Common.Http; using NzbDrone.Core.Configuration; +using NzbDrone.Core.Localization; using NzbDrone.Core.Parser; namespace NzbDrone.Core.Indexers.BroadcastheNet @@ -14,8 +15,8 @@ namespace NzbDrone.Core.Indexers.BroadcastheNet public override bool SupportsSearch => true; public override int PageSize => 100; - public BroadcastheNet(IHttpClient httpClient, IIndexerStatusService indexerStatusService, IConfigService configService, IParsingService parsingService, Logger logger) - : base(httpClient, indexerStatusService, configService, parsingService, logger) + public BroadcastheNet(IHttpClient httpClient, IIndexerStatusService indexerStatusService, IConfigService configService, IParsingService parsingService, Logger logger, ILocalizationService localizationService) + : base(httpClient, indexerStatusService, configService, parsingService, logger, localizationService) { } diff --git a/src/NzbDrone.Core/Indexers/BroadcastheNet/BroadcastheNetSettings.cs b/src/NzbDrone.Core/Indexers/BroadcastheNet/BroadcastheNetSettings.cs index 35d48c94a..4d3a688de 100644 --- a/src/NzbDrone.Core/Indexers/BroadcastheNet/BroadcastheNetSettings.cs +++ b/src/NzbDrone.Core/Indexers/BroadcastheNet/BroadcastheNetSettings.cs @@ -25,13 +25,13 @@ namespace NzbDrone.Core.Indexers.BroadcastheNet MinimumSeeders = IndexerDefaults.MINIMUM_SEEDERS; } - [FieldDefinition(0, Label = "API URL", Advanced = true, HelpText = "Do not change this unless you know what you're doing. Since your API key will be sent to that host.")] + [FieldDefinition(0, Label = "IndexerSettingsApiUrl", Advanced = true, HelpText = "IndexerSettingsApiUrlHelpText")] public string BaseUrl { get; set; } - [FieldDefinition(1, Label = "API Key", Privacy = PrivacyLevel.ApiKey)] + [FieldDefinition(1, Label = "ApiKey", Privacy = PrivacyLevel.ApiKey)] public string ApiKey { get; set; } - [FieldDefinition(2, Type = FieldType.Number, Label = "Minimum Seeders", HelpText = "Minimum number of seeders required.", Advanced = true)] + [FieldDefinition(2, Type = FieldType.Number, Label = "IndexerSettingsMinimumSeeders", HelpText = "IndexerSettingsMinimumSeedersHelpText", Advanced = true)] public int MinimumSeeders { get; set; } [FieldDefinition(3)] diff --git a/src/NzbDrone.Core/Indexers/Fanzub/Fanzub.cs b/src/NzbDrone.Core/Indexers/Fanzub/Fanzub.cs index f6b00e10a..5216a1ab1 100644 --- a/src/NzbDrone.Core/Indexers/Fanzub/Fanzub.cs +++ b/src/NzbDrone.Core/Indexers/Fanzub/Fanzub.cs @@ -1,6 +1,7 @@ using NLog; using NzbDrone.Common.Http; using NzbDrone.Core.Configuration; +using NzbDrone.Core.Localization; using NzbDrone.Core.Parser; namespace NzbDrone.Core.Indexers.Fanzub @@ -11,8 +12,8 @@ namespace NzbDrone.Core.Indexers.Fanzub public override DownloadProtocol Protocol => DownloadProtocol.Usenet; - public Fanzub(IHttpClient httpClient, IIndexerStatusService indexerStatusService, IConfigService configService, IParsingService parsingService, Logger logger) - : base(httpClient, indexerStatusService, configService, parsingService, logger) + public Fanzub(IHttpClient httpClient, IIndexerStatusService indexerStatusService, IConfigService configService, IParsingService parsingService, Logger logger, ILocalizationService localizationService) + : base(httpClient, indexerStatusService, configService, parsingService, logger, localizationService) { } diff --git a/src/NzbDrone.Core/Indexers/Fanzub/FanzubSettings.cs b/src/NzbDrone.Core/Indexers/Fanzub/FanzubSettings.cs index 48e4eff20..51ff19fa1 100644 --- a/src/NzbDrone.Core/Indexers/Fanzub/FanzubSettings.cs +++ b/src/NzbDrone.Core/Indexers/Fanzub/FanzubSettings.cs @@ -21,10 +21,11 @@ namespace NzbDrone.Core.Indexers.Fanzub BaseUrl = "http://fanzub.com/rss/"; } - [FieldDefinition(0, Label = "Rss URL", HelpText = "Enter to URL to an Fanzub compatible RSS feed")] + [FieldDefinition(0, Label = "IndexerSettingsRssUrl", HelpText = "IndexerSettingsRssUrlHelpText")] + [FieldToken(TokenField.HelpText, "IndexerSettingsRssUrl", "indexer", "Fanzub")] public string BaseUrl { get; set; } - [FieldDefinition(1, Label = "Anime Standard Format Search", Type = FieldType.Checkbox, HelpText = "Also search for anime using the standard numbering")] + [FieldDefinition(1, Label = "IndexerSettingsAnimeStandardFormatSearch", Type = FieldType.Checkbox, HelpText = "IndexerSettingsAnimeStandardFormatSearchHelpText")] public bool AnimeStandardFormatSearch { get; set; } public NzbDroneValidationResult Validate() diff --git a/src/NzbDrone.Core/Indexers/FileList/FileList.cs b/src/NzbDrone.Core/Indexers/FileList/FileList.cs index bb0da183d..7e74749de 100644 --- a/src/NzbDrone.Core/Indexers/FileList/FileList.cs +++ b/src/NzbDrone.Core/Indexers/FileList/FileList.cs @@ -1,6 +1,7 @@ using NLog; using NzbDrone.Common.Http; using NzbDrone.Core.Configuration; +using NzbDrone.Core.Localization; using NzbDrone.Core.Parser; namespace NzbDrone.Core.Indexers.FileList @@ -12,8 +13,8 @@ namespace NzbDrone.Core.Indexers.FileList public override bool SupportsRss => true; public override bool SupportsSearch => true; - public FileList(IHttpClient httpClient, IIndexerStatusService indexerStatusService, IConfigService configService, IParsingService parsingService, Logger logger) - : base(httpClient, indexerStatusService, configService, parsingService, logger) + public FileList(IHttpClient httpClient, IIndexerStatusService indexerStatusService, IConfigService configService, IParsingService parsingService, Logger logger, ILocalizationService localizationService) + : base(httpClient, indexerStatusService, configService, parsingService, logger, localizationService) { } diff --git a/src/NzbDrone.Core/Indexers/FileList/FileListSettings.cs b/src/NzbDrone.Core/Indexers/FileList/FileListSettings.cs index eb22bf666..f149ef8f4 100644 --- a/src/NzbDrone.Core/Indexers/FileList/FileListSettings.cs +++ b/src/NzbDrone.Core/Indexers/FileList/FileListSettings.cs @@ -40,19 +40,19 @@ namespace NzbDrone.Core.Indexers.FileList [FieldDefinition(0, Label = "Username", Privacy = PrivacyLevel.UserName)] public string Username { get; set; } - [FieldDefinition(1, Label = "Passkey", Privacy = PrivacyLevel.ApiKey)] + [FieldDefinition(1, Label = "IndexerSettingsPasskey", Privacy = PrivacyLevel.ApiKey)] public string Passkey { get; set; } - [FieldDefinition(3, Label = "API URL", Advanced = true, HelpText = "Do not change this unless you know what you're doing. Since your API key will be sent to that host.")] + [FieldDefinition(3, Label = "IndexerSettingsApiUrl", Advanced = true, HelpText = "IndexerSettingsApiUrlHelpText")] public string BaseUrl { get; set; } - [FieldDefinition(4, Label = "Categories", Type = FieldType.Select, SelectOptions = typeof(FileListCategories), HelpText = "Categories for use in search and feeds, leave blank to disable standard/daily shows")] + [FieldDefinition(4, Label = "IndexerSettingsCategories", Type = FieldType.Select, SelectOptions = typeof(FileListCategories), HelpText = "IndexerSettingsCategoriesHelpText")] public IEnumerable<int> Categories { get; set; } - [FieldDefinition(5, Label = "Anime Categories", Type = FieldType.Select, SelectOptions = typeof(FileListCategories), HelpText = "Categories for use in search and feeds, leave blank to disable anime")] + [FieldDefinition(5, Label = "IndexerSettingsAnimeCategories", Type = FieldType.Select, SelectOptions = typeof(FileListCategories), HelpText = "IndexerSettingsAnimeCategoriesHelpText")] public IEnumerable<int> AnimeCategories { get; set; } - [FieldDefinition(6, Type = FieldType.Number, Label = "Minimum Seeders", HelpText = "Minimum number of seeders required.", Advanced = true)] + [FieldDefinition(6, Type = FieldType.Number, Label = "IndexerSettingsMinimumSeeders", HelpText = "IndexerSettingsMinimumSeedersHelpText", Advanced = true)] public int MinimumSeeders { get; set; } [FieldDefinition(7)] diff --git a/src/NzbDrone.Core/Indexers/HDBits/HDBits.cs b/src/NzbDrone.Core/Indexers/HDBits/HDBits.cs index c52a2f496..e7a200bf9 100644 --- a/src/NzbDrone.Core/Indexers/HDBits/HDBits.cs +++ b/src/NzbDrone.Core/Indexers/HDBits/HDBits.cs @@ -1,6 +1,7 @@ using NLog; using NzbDrone.Common.Http; using NzbDrone.Core.Configuration; +using NzbDrone.Core.Localization; using NzbDrone.Core.Parser; namespace NzbDrone.Core.Indexers.HDBits @@ -13,8 +14,8 @@ namespace NzbDrone.Core.Indexers.HDBits public override bool SupportsSearch => true; public override int PageSize => 30; - public HDBits(IHttpClient httpClient, IIndexerStatusService indexerStatusService, IConfigService configService, IParsingService parsingService, Logger logger) - : base(httpClient, indexerStatusService, configService, parsingService, logger) + public HDBits(IHttpClient httpClient, IIndexerStatusService indexerStatusService, IConfigService configService, IParsingService parsingService, Logger logger, ILocalizationService localizationService) + : base(httpClient, indexerStatusService, configService, parsingService, logger, localizationService) { } diff --git a/src/NzbDrone.Core/Indexers/HDBits/HDBitsSettings.cs b/src/NzbDrone.Core/Indexers/HDBits/HDBitsSettings.cs index f0d2667d4..0b573850c 100644 --- a/src/NzbDrone.Core/Indexers/HDBits/HDBitsSettings.cs +++ b/src/NzbDrone.Core/Indexers/HDBits/HDBitsSettings.cs @@ -28,13 +28,13 @@ namespace NzbDrone.Core.Indexers.HDBits [FieldDefinition(0, Label = "Username", Privacy = PrivacyLevel.UserName)] public string Username { get; set; } - [FieldDefinition(1, Label = "API Key", Privacy = PrivacyLevel.ApiKey)] + [FieldDefinition(1, Label = "ApiKey", Privacy = PrivacyLevel.ApiKey)] public string ApiKey { get; set; } - [FieldDefinition(2, Label = "API URL", Advanced = true, HelpText = "Do not change this unless you know what you're doing. Since your API key will be sent to that host.")] + [FieldDefinition(2, Label = "IndexerSettingsApiUrl", Advanced = true, HelpText = "IndexerSettingsApiUrlHelpText")] public string BaseUrl { get; set; } - [FieldDefinition(3, Type = FieldType.Number, Label = "Minimum Seeders", HelpText = "Minimum number of seeders required.", Advanced = true)] + [FieldDefinition(3, Type = FieldType.Number, Label = "IndexerSettingsMinimumSeeders", HelpText = "IndexerSettingsMinimumSeedersHelpText", Advanced = true)] public int MinimumSeeders { get; set; } [FieldDefinition(4)] diff --git a/src/NzbDrone.Core/Indexers/HttpIndexerBase.cs b/src/NzbDrone.Core/Indexers/HttpIndexerBase.cs index 19ff8330e..35e8c629f 100644 --- a/src/NzbDrone.Core/Indexers/HttpIndexerBase.cs +++ b/src/NzbDrone.Core/Indexers/HttpIndexerBase.cs @@ -12,6 +12,7 @@ using NzbDrone.Core.Configuration; using NzbDrone.Core.Http.CloudFlare; using NzbDrone.Core.Indexers.Exceptions; using NzbDrone.Core.IndexerSearch.Definitions; +using NzbDrone.Core.Localization; using NzbDrone.Core.Parser; using NzbDrone.Core.Parser.Model; @@ -34,8 +35,8 @@ namespace NzbDrone.Core.Indexers public abstract IIndexerRequestGenerator GetRequestGenerator(); public abstract IParseIndexerResponse GetParser(); - public HttpIndexerBase(IHttpClient httpClient, IIndexerStatusService indexerStatusService, IConfigService configService, IParsingService parsingService, Logger logger) - : base(indexerStatusService, configService, parsingService, logger) + public HttpIndexerBase(IHttpClient httpClient, IIndexerStatusService indexerStatusService, IConfigService configService, IParsingService parsingService, Logger logger, ILocalizationService localizationService) + : base(indexerStatusService, configService, parsingService, logger, localizationService) { _httpClient = httpClient; } @@ -376,50 +377,50 @@ namespace NzbDrone.Core.Indexers if (firstRequest == null) { - return new ValidationFailure(string.Empty, "No rss feed query available. This may be an issue with the indexer or your indexer category settings."); + return new ValidationFailure(string.Empty, _localizationService.GetLocalizedString("IndexerValidationJackettNoRssFeedQueryAvailable")); } var releases = await FetchPage(firstRequest, parser); if (releases.Empty()) { - return new ValidationFailure(string.Empty, "Query successful, but no results in the configured categories were returned from your indexer. This may be an issue with the indexer or your indexer category settings."); + return new ValidationFailure(string.Empty, _localizationService.GetLocalizedString("IndexerValidationJackettNoResultsInConfiguredCategories")); } } catch (ApiKeyException ex) { _logger.Warn("Indexer returned result for RSS URL, API Key appears to be invalid: " + ex.Message); - return new ValidationFailure("ApiKey", "Invalid API Key"); + return new ValidationFailure("ApiKey", _localizationService.GetLocalizedString("IndexerValidationInvalidApiKey")); } catch (RequestLimitReachedException ex) { _logger.Warn("Request limit reached: " + ex.Message); - return new ValidationFailure(string.Empty, "Request limit reached: " + ex.Message); + return new ValidationFailure(string.Empty, _localizationService.GetLocalizedString("IndexerValidationRequestLimitReached", new Dictionary<string, object> { { "exceptionMessage", ex.Message } })); } catch (CloudFlareCaptchaException ex) { if (ex.IsExpired) { - return new ValidationFailure("CaptchaToken", "CloudFlare CAPTCHA token expired, please Refresh."); + return new ValidationFailure("CaptchaToken", _localizationService.GetLocalizedString("IndexerValidationCloudFlareCaptchaExpired")); } else { - return new ValidationFailure("CaptchaToken", "Site protected by CloudFlare CAPTCHA. Valid CAPTCHA token required."); + return new ValidationFailure("CaptchaToken", _localizationService.GetLocalizedString("IndexerValidationCloudFlareCaptchaRequired")); } } catch (UnsupportedFeedException ex) { _logger.Warn(ex, "Indexer feed is not supported"); - return new ValidationFailure(string.Empty, "Indexer feed is not supported: " + ex.Message); + return new ValidationFailure(string.Empty, _localizationService.GetLocalizedString("IndexerValidationFeedNotSupported", new Dictionary<string, object> { { "exceptionMessage", ex.Message } })); } catch (IndexerException ex) { _logger.Warn(ex, "Unable to connect to indexer"); - return new ValidationFailure(string.Empty, "Unable to connect to indexer. " + ex.Message); + return new ValidationFailure(string.Empty, _localizationService.GetLocalizedString("IndexerValidationUnableToConnect", new Dictionary<string, object> { { "exceptionMessage", ex.Message } })); } catch (HttpException ex) { @@ -427,33 +428,33 @@ namespace NzbDrone.Core.Indexers ex.Response.Content.Contains("not support the requested query")) { _logger.Warn(ex, "Indexer does not support the query"); - return new ValidationFailure(string.Empty, "Indexer does not support the current query. Check if the categories and or searching for seasons/episodes are supported. Check the log for more details."); + return new ValidationFailure(string.Empty, _localizationService.GetLocalizedString("IndexerValidationQueryNotSupported")); } _logger.Warn(ex, "Unable to connect to indexer"); if (ex.Response.HasHttpServerError) { - return new ValidationFailure(string.Empty, "Unable to connect to indexer, indexer's server is unavailable. Try again later. " + ex.Message); + return new ValidationFailure(string.Empty, _localizationService.GetLocalizedString("IndexerValidationUnableToConnectServerUnavailable", new Dictionary<string, object> { { "exceptionMessage", ex.Message } })); } if (ex.Response.StatusCode is HttpStatusCode.Forbidden or HttpStatusCode.Unauthorized) { - return new ValidationFailure(string.Empty, "Unable to connect to indexer, invalid credentials. " + ex.Message); + return new ValidationFailure(string.Empty, _localizationService.GetLocalizedString("IndexerValidationUnableToConnectInvalidCredentials", new Dictionary<string, object> { { "exceptionMessage", ex.Message } })); } - return new ValidationFailure(string.Empty, "Unable to connect to indexer, check the log above the ValidationFailure for more details. " + ex.Message); + return new ValidationFailure(string.Empty, _localizationService.GetLocalizedString("IndexerValidationUnableToConnect", new Dictionary<string, object> { { "exceptionMessage", ex.Message } })); } catch (HttpRequestException ex) { _logger.Warn(ex, "Unable to connect to indexer"); - return new ValidationFailure(string.Empty, "Unable to connect to indexer, please check your DNS settings and ensure IPv6 is working or disabled. " + ex.Message); + return new ValidationFailure(string.Empty, _localizationService.GetLocalizedString("IndexerValidationUnableToConnectHttpError", new Dictionary<string, object> { { "exceptionMessage", ex.Message } })); } catch (TaskCanceledException ex) { _logger.Warn(ex, "Unable to connect to indexer"); - return new ValidationFailure(string.Empty, "Unable to connect to indexer, possibly due to a timeout. Try again or check your network settings. " + ex.Message); + return new ValidationFailure(string.Empty, _localizationService.GetLocalizedString("IndexerValidationUnableToConnectTimeout", new Dictionary<string, object> { { "exceptionMessage", ex.Message } })); } catch (WebException webException) { @@ -461,20 +462,20 @@ namespace NzbDrone.Core.Indexers if (webException.Status is WebExceptionStatus.NameResolutionFailure or WebExceptionStatus.ConnectFailure) { - return new ValidationFailure(string.Empty, "Unable to connect to indexer connection failure. Check your connection to the indexer's server and DNS." + webException.Message); + return new ValidationFailure(string.Empty, _localizationService.GetLocalizedString("IndexerValidationUnableToConnectResolutionFailure", new Dictionary<string, object> { { "exceptionMessage", webException.Message } })); } if (webException.Message.Contains("502") || webException.Message.Contains("503") || webException.Message.Contains("504") || webException.Message.Contains("timed out")) { - return new ValidationFailure(string.Empty, "Unable to connect to indexer, indexer's server is unavailable. Try again later. " + webException.Message); + return new ValidationFailure(string.Empty, _localizationService.GetLocalizedString("IndexerValidationUnableToConnectServerUnavailable", new Dictionary<string, object> { { "exceptionMessage", webException.Message } })); } } catch (Exception ex) { _logger.Warn(ex, "Unable to connect to indexer"); - return new ValidationFailure(string.Empty, $"Unable to connect to indexer: {ex.Message}. Check the log surrounding this error for details"); + return new ValidationFailure(string.Empty, _localizationService.GetLocalizedString("IndexerValidationUnableToConnect", new Dictionary<string, object> { { "exceptionMessage", ex.Message } })); } return null; diff --git a/src/NzbDrone.Core/Indexers/IPTorrents/IPTorrents.cs b/src/NzbDrone.Core/Indexers/IPTorrents/IPTorrents.cs index 6b74949ba..ab7f9ee7d 100644 --- a/src/NzbDrone.Core/Indexers/IPTorrents/IPTorrents.cs +++ b/src/NzbDrone.Core/Indexers/IPTorrents/IPTorrents.cs @@ -1,6 +1,7 @@ using NLog; using NzbDrone.Common.Http; using NzbDrone.Core.Configuration; +using NzbDrone.Core.Localization; using NzbDrone.Core.Parser; namespace NzbDrone.Core.Indexers.IPTorrents @@ -13,8 +14,8 @@ namespace NzbDrone.Core.Indexers.IPTorrents public override bool SupportsSearch => false; public override int PageSize => 0; - public IPTorrents(IHttpClient httpClient, IIndexerStatusService indexerStatusService, IConfigService configService, IParsingService parsingService, Logger logger) - : base(httpClient, indexerStatusService, configService, parsingService, logger) + public IPTorrents(IHttpClient httpClient, IIndexerStatusService indexerStatusService, IConfigService configService, IParsingService parsingService, Logger logger, ILocalizationService localizationService) + : base(httpClient, indexerStatusService, configService, parsingService, logger, localizationService) { } diff --git a/src/NzbDrone.Core/Indexers/IPTorrents/IPTorrentsSettings.cs b/src/NzbDrone.Core/Indexers/IPTorrents/IPTorrentsSettings.cs index e2b433539..473bc9f8e 100644 --- a/src/NzbDrone.Core/Indexers/IPTorrents/IPTorrentsSettings.cs +++ b/src/NzbDrone.Core/Indexers/IPTorrents/IPTorrentsSettings.cs @@ -31,10 +31,10 @@ namespace NzbDrone.Core.Indexers.IPTorrents MinimumSeeders = IndexerDefaults.MINIMUM_SEEDERS; } - [FieldDefinition(0, Label = "Feed URL", HelpText = "The full RSS feed url generated by IPTorrents, using only the categories you selected (HD, SD, x264, etc ...)")] + [FieldDefinition(0, Label = "IndexerIPTorrentsSettingsFeedUrl", HelpText = "IndexerIPTorrentsSettingsFeedUrlHelpText")] public string BaseUrl { get; set; } - [FieldDefinition(1, Type = FieldType.Number, Label = "Minimum Seeders", HelpText = "Minimum number of seeders required.", Advanced = true)] + [FieldDefinition(1, Type = FieldType.Number, Label = "IndexerSettingsMinimumSeeders", HelpText = "IndexerSettingsMinimumSeedersHelpText", Advanced = true)] public int MinimumSeeders { get; set; } [FieldDefinition(2)] diff --git a/src/NzbDrone.Core/Indexers/IndexerBase.cs b/src/NzbDrone.Core/Indexers/IndexerBase.cs index 07680e0c3..dbb9916c0 100644 --- a/src/NzbDrone.Core/Indexers/IndexerBase.cs +++ b/src/NzbDrone.Core/Indexers/IndexerBase.cs @@ -7,6 +7,7 @@ using NLog; using NzbDrone.Common.Http; using NzbDrone.Core.Configuration; using NzbDrone.Core.IndexerSearch.Definitions; +using NzbDrone.Core.Localization; using NzbDrone.Core.Parser; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.ThingiProvider; @@ -20,6 +21,7 @@ namespace NzbDrone.Core.Indexers protected readonly IConfigService _configService; protected readonly IParsingService _parsingService; protected readonly Logger _logger; + protected readonly ILocalizationService _localizationService; public abstract string Name { get; } public abstract DownloadProtocol Protocol { get; } @@ -29,12 +31,13 @@ namespace NzbDrone.Core.Indexers public abstract bool SupportsRss { get; } public abstract bool SupportsSearch { get; } - public IndexerBase(IIndexerStatusService indexerStatusService, IConfigService configService, IParsingService parsingService, Logger logger) + public IndexerBase(IIndexerStatusService indexerStatusService, IConfigService configService, IParsingService parsingService, Logger logger, ILocalizationService localizationService) { _indexerStatusService = indexerStatusService; _configService = configService; _parsingService = parsingService; _logger = logger; + _localizationService = localizationService; } public Type ConfigContract => typeof(TSettings); @@ -105,7 +108,7 @@ namespace NzbDrone.Core.Indexers catch (Exception ex) { _logger.Error(ex, "Test aborted due to exception"); - failures.Add(new ValidationFailure(string.Empty, "Test was aborted due to an error: " + ex.Message)); + failures.Add(new ValidationFailure(string.Empty, _localizationService.GetLocalizedString("IndexerValidationTestAbortedDueToError", new Dictionary<string, object> { { "exceptionMessage", ex.Message } }))); } return new ValidationResult(failures); diff --git a/src/NzbDrone.Core/Indexers/Newznab/Newznab.cs b/src/NzbDrone.Core/Indexers/Newznab/Newznab.cs index 16bbe72c6..344932210 100644 --- a/src/NzbDrone.Core/Indexers/Newznab/Newznab.cs +++ b/src/NzbDrone.Core/Indexers/Newznab/Newznab.cs @@ -7,6 +7,7 @@ using NLog; using NzbDrone.Common.Extensions; using NzbDrone.Common.Http; using NzbDrone.Core.Configuration; +using NzbDrone.Core.Localization; using NzbDrone.Core.Parser; using NzbDrone.Core.ThingiProvider; using NzbDrone.Core.Validation; @@ -22,8 +23,8 @@ namespace NzbDrone.Core.Indexers.Newznab public override DownloadProtocol Protocol => DownloadProtocol.Usenet; public override int PageSize => GetProviderPageSize(); - public Newznab(INewznabCapabilitiesProvider capabilitiesProvider, IHttpClient httpClient, IIndexerStatusService indexerStatusService, IConfigService configService, IParsingService parsingService, Logger logger) - : base(httpClient, indexerStatusService, configService, parsingService, logger) + public Newznab(INewznabCapabilitiesProvider capabilitiesProvider, IHttpClient httpClient, IIndexerStatusService indexerStatusService, IConfigService configService, IParsingService parsingService, Logger logger, ILocalizationService localizationService) + : base(httpClient, indexerStatusService, configService, parsingService, logger, localizationService) { _capabilitiesProvider = capabilitiesProvider; } @@ -129,13 +130,13 @@ namespace NzbDrone.Core.Indexers.Newznab return null; } - return new ValidationFailure(string.Empty, "Indexer does not support required search parameters"); + return new ValidationFailure(string.Empty, _localizationService.GetLocalizedString("IndexerValidationSearchParametersNotSupported")); } catch (Exception ex) { _logger.Warn(ex, "Unable to connect to indexer: " + ex.Message); - return new ValidationFailure(string.Empty, $"Unable to connect to indexer: {ex.Message}. Check the log surrounding this error for details"); + return new ValidationFailure(string.Empty, _localizationService.GetLocalizedString("IndexerValidationUnableToConnect", new Dictionary<string, object> { { "exceptionMessage", ex.Message } })); } } diff --git a/src/NzbDrone.Core/Indexers/Newznab/NewznabSettings.cs b/src/NzbDrone.Core/Indexers/Newznab/NewznabSettings.cs index 688be0d1f..a38229560 100644 --- a/src/NzbDrone.Core/Indexers/Newznab/NewznabSettings.cs +++ b/src/NzbDrone.Core/Indexers/Newznab/NewznabSettings.cs @@ -60,22 +60,23 @@ namespace NzbDrone.Core.Indexers.Newznab [FieldDefinition(0, Label = "URL")] public string BaseUrl { get; set; } - [FieldDefinition(1, Label = "API Path", HelpText = "Path to the api, usually /api", Advanced = true)] + [FieldDefinition(1, Label = "IndexerSettingsApiPath", HelpText = "IndexerSettingsApiPathHelpText", Advanced = true)] + [FieldToken(TokenField.HelpText, "IndexerSettingsApiPath", "url", "/api")] public string ApiPath { get; set; } - [FieldDefinition(2, Label = "API Key", Privacy = PrivacyLevel.ApiKey)] + [FieldDefinition(2, Label = "ApiKey", Privacy = PrivacyLevel.ApiKey)] public string ApiKey { get; set; } - [FieldDefinition(3, Label = "Categories", Type = FieldType.Select, SelectOptionsProviderAction = "newznabCategories", HelpText = "Drop down list, leave blank to disable standard/daily shows")] + [FieldDefinition(3, Label = "IndexerSettingsCategories", Type = FieldType.Select, SelectOptionsProviderAction = "newznabCategories", HelpText = "IndexerSettingsCategoriesHelpText")] public IEnumerable<int> Categories { get; set; } - [FieldDefinition(4, Label = "Anime Categories", Type = FieldType.Select, SelectOptionsProviderAction = "newznabCategories", HelpText = "Drop down list, leave blank to disable anime")] + [FieldDefinition(4, Label = "IndexerSettingsAnimeCategories", Type = FieldType.Select, SelectOptionsProviderAction = "newznabCategories", HelpText = "IndexerSettingsAnimeCategoriesHelpText")] public IEnumerable<int> AnimeCategories { get; set; } - [FieldDefinition(5, Label = "Anime Standard Format Search", Type = FieldType.Checkbox, HelpText = "Also search for anime using the standard numbering")] + [FieldDefinition(5, Label = "IndexerSettingsAnimeStandardFormatSearch", Type = FieldType.Checkbox, HelpText = "IndexerSettingsAnimeStandardFormatSearchHelpText")] public bool AnimeStandardFormatSearch { get; set; } - [FieldDefinition(6, Label = "Additional Parameters", HelpText = "Additional Newznab parameters", Advanced = true)] + [FieldDefinition(6, Label = "IndexerSettingsAdditionalParameters", HelpText = "IndexerSettingsAdditionalNewznabParametersHelpText", Advanced = true)] public string AdditionalParameters { get; set; } // Field 7 is used by TorznabSettings MinimumSeeders diff --git a/src/NzbDrone.Core/Indexers/Nyaa/Nyaa.cs b/src/NzbDrone.Core/Indexers/Nyaa/Nyaa.cs index af169ecf5..391f2ed0b 100644 --- a/src/NzbDrone.Core/Indexers/Nyaa/Nyaa.cs +++ b/src/NzbDrone.Core/Indexers/Nyaa/Nyaa.cs @@ -1,6 +1,7 @@ using NLog; using NzbDrone.Common.Http; using NzbDrone.Core.Configuration; +using NzbDrone.Core.Localization; using NzbDrone.Core.Parser; namespace NzbDrone.Core.Indexers.Nyaa @@ -12,8 +13,8 @@ namespace NzbDrone.Core.Indexers.Nyaa public override DownloadProtocol Protocol => DownloadProtocol.Torrent; public override int PageSize => 100; - public Nyaa(IHttpClient httpClient, IIndexerStatusService indexerStatusService, IConfigService configService, IParsingService parsingService, Logger logger) - : base(httpClient, indexerStatusService, configService, parsingService, logger) + public Nyaa(IHttpClient httpClient, IIndexerStatusService indexerStatusService, IConfigService configService, IParsingService parsingService, Logger logger, ILocalizationService localizationService) + : base(httpClient, indexerStatusService, configService, parsingService, logger, localizationService) { } diff --git a/src/NzbDrone.Core/Indexers/Nyaa/NyaaSettings.cs b/src/NzbDrone.Core/Indexers/Nyaa/NyaaSettings.cs index e13a73c50..25741b13b 100644 --- a/src/NzbDrone.Core/Indexers/Nyaa/NyaaSettings.cs +++ b/src/NzbDrone.Core/Indexers/Nyaa/NyaaSettings.cs @@ -27,16 +27,16 @@ namespace NzbDrone.Core.Indexers.Nyaa MinimumSeeders = IndexerDefaults.MINIMUM_SEEDERS; } - [FieldDefinition(0, Label = "Website URL")] + [FieldDefinition(0, Label = "IndexerSettingsWebsiteUrl")] public string BaseUrl { get; set; } - [FieldDefinition(1, Label = "Anime Standard Format Search", Type = FieldType.Checkbox, HelpText = "Also search for anime using the standard numbering")] + [FieldDefinition(1, Label = "IndexerSettingsAnimeStandardFormatSearch", Type = FieldType.Checkbox, HelpText = "IndexerSettingsAnimeStandardFormatSearchHelpText")] public bool AnimeStandardFormatSearch { get; set; } - [FieldDefinition(2, Label = "Additional Parameters", Advanced = true, HelpText = "Please note if you change the category you will have to add required/restricted rules about the subgroups to avoid foreign language releases.")] + [FieldDefinition(2, Label = "IndexerSettingsAdditionalParameters", Advanced = true, HelpText = "IndexerSettingsAdditionalNewznabParametersHelpText")] public string AdditionalParameters { get; set; } - [FieldDefinition(3, Type = FieldType.Number, Label = "Minimum Seeders", HelpText = "Minimum number of seeders required.", Advanced = true)] + [FieldDefinition(3, Type = FieldType.Number, Label = "IndexerSettingsMinimumSeeders", HelpText = "IndexerSettingsMinimumSeedersHelpText", Advanced = true)] public int MinimumSeeders { get; set; } [FieldDefinition(4)] diff --git a/src/NzbDrone.Core/Indexers/SeedCriteriaSettings.cs b/src/NzbDrone.Core/Indexers/SeedCriteriaSettings.cs index aa13f6b1a..af9fad86f 100644 --- a/src/NzbDrone.Core/Indexers/SeedCriteriaSettings.cs +++ b/src/NzbDrone.Core/Indexers/SeedCriteriaSettings.cs @@ -48,13 +48,13 @@ namespace NzbDrone.Core.Indexers public class SeedCriteriaSettings { - [FieldDefinition(0, Type = FieldType.Textbox, Label = "Seed Ratio", HelpText = "The ratio a torrent should reach before stopping, empty is download client's default. Ratio should be at least 1.0 and follow the indexers rules")] + [FieldDefinition(0, Type = FieldType.Textbox, Label = "IndexerSettingsSeedRatio", HelpText = "IndexerSettingsSeedRatioHelpText")] public double? SeedRatio { get; set; } - [FieldDefinition(1, Type = FieldType.Number, Label = "Seed Time", Unit = "minutes", HelpText = "The time a torrent should be seeded before stopping, empty is download client's default", Advanced = true)] + [FieldDefinition(1, Type = FieldType.Number, Label = "IndexerSettingsSeedTime", Unit = "minutes", HelpText = "IndexerSettingsSeedTimeHelpText", Advanced = true)] public int? SeedTime { get; set; } - [FieldDefinition(2, Type = FieldType.Number, Label = "Season-Pack Seed Time", Unit = "minutes", HelpText = "The time a torrent should be seeded before stopping, empty is download client's default", Advanced = true)] + [FieldDefinition(2, Type = FieldType.Number, Label = "Season-Pack Seed Time", Unit = "minutes", HelpText = "IndexerSettingsSeasonPackSeedTimeHelpText", Advanced = true)] public int? SeasonPackSeedTime { get; set; } } } diff --git a/src/NzbDrone.Core/Indexers/TorrentRss/TorrentRssIndexer.cs b/src/NzbDrone.Core/Indexers/TorrentRss/TorrentRssIndexer.cs index 5f467807a..9f69a02b8 100644 --- a/src/NzbDrone.Core/Indexers/TorrentRss/TorrentRssIndexer.cs +++ b/src/NzbDrone.Core/Indexers/TorrentRss/TorrentRssIndexer.cs @@ -1,6 +1,7 @@ using NLog; using NzbDrone.Common.Http; using NzbDrone.Core.Configuration; +using NzbDrone.Core.Localization; using NzbDrone.Core.Parser; namespace NzbDrone.Core.Indexers.TorrentRss @@ -15,8 +16,8 @@ namespace NzbDrone.Core.Indexers.TorrentRss private readonly ITorrentRssParserFactory _torrentRssParserFactory; - public TorrentRssIndexer(ITorrentRssParserFactory torrentRssParserFactory, IHttpClient httpClient, IIndexerStatusService indexerStatusService, IConfigService configService, IParsingService parsingService, Logger logger) - : base(httpClient, indexerStatusService, configService, parsingService, logger) + public TorrentRssIndexer(ITorrentRssParserFactory torrentRssParserFactory, IHttpClient httpClient, IIndexerStatusService indexerStatusService, IConfigService configService, IParsingService parsingService, Logger logger, ILocalizationService localizationService) + : base(httpClient, indexerStatusService, configService, parsingService, logger, localizationService) { _torrentRssParserFactory = torrentRssParserFactory; } diff --git a/src/NzbDrone.Core/Indexers/TorrentRss/TorrentRssIndexerSettings.cs b/src/NzbDrone.Core/Indexers/TorrentRss/TorrentRssIndexerSettings.cs index 1c9ace66e..298fce7ce 100644 --- a/src/NzbDrone.Core/Indexers/TorrentRss/TorrentRssIndexerSettings.cs +++ b/src/NzbDrone.Core/Indexers/TorrentRss/TorrentRssIndexerSettings.cs @@ -25,16 +25,16 @@ namespace NzbDrone.Core.Indexers.TorrentRss MinimumSeeders = IndexerDefaults.MINIMUM_SEEDERS; } - [FieldDefinition(0, Label = "Full RSS Feed URL")] + [FieldDefinition(0, Label = "IndexerSettingsRssUrl")] public string BaseUrl { get; set; } - [FieldDefinition(1, Label = "Cookie", HelpText = "If you site requires a login cookie to access the rss, you'll have to retrieve it via a browser.")] + [FieldDefinition(1, Label = "IndexerSettingsCookie", HelpText = "IndexerSettingsCookieHelpText")] public string Cookie { get; set; } - [FieldDefinition(2, Type = FieldType.Checkbox, Label = "Allow Zero Size", HelpText="Enabling this will allow you to use feeds that don't specify release size, but be careful, size related checks will not be performed.")] + [FieldDefinition(2, Type = FieldType.Checkbox, Label = "Allow Zero Size", HelpText="IndexerSettingsAllowZeroSizeHelpText")] public bool AllowZeroSize { get; set; } - [FieldDefinition(3, Type = FieldType.Number, Label = "Minimum Seeders", HelpText = "Minimum number of seeders required.", Advanced = true)] + [FieldDefinition(3, Type = FieldType.Number, Label = "IndexerSettingsMinimumSeeders", HelpText = "IndexerSettingsMinimumSeedersHelpText", Advanced = true)] public int MinimumSeeders { get; set; } [FieldDefinition(4)] diff --git a/src/NzbDrone.Core/Indexers/Torrentleech/Torrentleech.cs b/src/NzbDrone.Core/Indexers/Torrentleech/Torrentleech.cs index fc3a46bc2..4cad693e7 100644 --- a/src/NzbDrone.Core/Indexers/Torrentleech/Torrentleech.cs +++ b/src/NzbDrone.Core/Indexers/Torrentleech/Torrentleech.cs @@ -1,6 +1,7 @@ using NLog; using NzbDrone.Common.Http; using NzbDrone.Core.Configuration; +using NzbDrone.Core.Localization; using NzbDrone.Core.Parser; namespace NzbDrone.Core.Indexers.Torrentleech @@ -13,8 +14,8 @@ namespace NzbDrone.Core.Indexers.Torrentleech public override bool SupportsSearch => false; public override int PageSize => 0; - public Torrentleech(IHttpClient httpClient, IIndexerStatusService indexerStatusService, IConfigService configService, IParsingService parsingService, Logger logger) - : base(httpClient, indexerStatusService, configService, parsingService, logger) + public Torrentleech(IHttpClient httpClient, IIndexerStatusService indexerStatusService, IConfigService configService, IParsingService parsingService, Logger logger, ILocalizationService localizationService) + : base(httpClient, indexerStatusService, configService, parsingService, logger, localizationService) { } diff --git a/src/NzbDrone.Core/Indexers/Torrentleech/TorrentleechSettings.cs b/src/NzbDrone.Core/Indexers/Torrentleech/TorrentleechSettings.cs index d621b818c..09ad61bff 100644 --- a/src/NzbDrone.Core/Indexers/Torrentleech/TorrentleechSettings.cs +++ b/src/NzbDrone.Core/Indexers/Torrentleech/TorrentleechSettings.cs @@ -25,13 +25,13 @@ namespace NzbDrone.Core.Indexers.Torrentleech MinimumSeeders = IndexerDefaults.MINIMUM_SEEDERS; } - [FieldDefinition(0, Label = "Website URL")] + [FieldDefinition(0, Label = "IndexerSettingsWebsiteUrl")] public string BaseUrl { get; set; } - [FieldDefinition(1, Label = "API Key", Privacy = PrivacyLevel.ApiKey)] + [FieldDefinition(1, Label = "ApiKey", Privacy = PrivacyLevel.ApiKey)] public string ApiKey { get; set; } - [FieldDefinition(2, Type = FieldType.Number, Label = "Minimum Seeders", HelpText = "Minimum number of seeders required.", Advanced = true)] + [FieldDefinition(2, Type = FieldType.Number, Label = "IndexerSettingsMinimumSeeders", HelpText = "IndexerSettingsMinimumSeedersHelpText", Advanced = true)] public int MinimumSeeders { get; set; } [FieldDefinition(3)] diff --git a/src/NzbDrone.Core/Indexers/Torznab/Torznab.cs b/src/NzbDrone.Core/Indexers/Torznab/Torznab.cs index 978d4b186..e77d669d3 100644 --- a/src/NzbDrone.Core/Indexers/Torznab/Torznab.cs +++ b/src/NzbDrone.Core/Indexers/Torznab/Torznab.cs @@ -8,6 +8,7 @@ using NzbDrone.Common.Extensions; using NzbDrone.Common.Http; using NzbDrone.Core.Configuration; using NzbDrone.Core.Indexers.Newznab; +using NzbDrone.Core.Localization; using NzbDrone.Core.Parser; using NzbDrone.Core.ThingiProvider; using NzbDrone.Core.Validation; @@ -23,8 +24,8 @@ namespace NzbDrone.Core.Indexers.Torznab public override DownloadProtocol Protocol => DownloadProtocol.Torrent; public override int PageSize => GetProviderPageSize(); - public Torznab(INewznabCapabilitiesProvider capabilitiesProvider, IHttpClient httpClient, IIndexerStatusService indexerStatusService, IConfigService configService, IParsingService parsingService, Logger logger) - : base(httpClient, indexerStatusService, configService, parsingService, logger) + public Torznab(INewznabCapabilitiesProvider capabilitiesProvider, IHttpClient httpClient, IIndexerStatusService indexerStatusService, IConfigService configService, IParsingService parsingService, Logger logger, ILocalizationService localizationService) + : base(httpClient, indexerStatusService, configService, parsingService, logger, localizationService) { _capabilitiesProvider = capabilitiesProvider; } @@ -123,13 +124,13 @@ namespace NzbDrone.Core.Indexers.Torznab return null; } - return new ValidationFailure(string.Empty, "Indexer does not support required search parameters"); + return new ValidationFailure(string.Empty, _localizationService.GetLocalizedString("IndexerValidationSearchParametersNotSupported")); } catch (Exception ex) { _logger.Warn(ex, "Unable to connect to indexer: " + ex.Message); - return new ValidationFailure(string.Empty, $"Unable to connect to indexer: {ex.Message}. Check the log surrounding this error for details"); + return new ValidationFailure(string.Empty, _localizationService.GetLocalizedString("IndexerValidationUnableToConnect", new Dictionary<string, object> { { "exceptionMessage", ex.Message } })); } } @@ -140,10 +141,10 @@ namespace NzbDrone.Core.Indexers.Torznab Settings.BaseUrl.Contains("/torznab/all") || Settings.BaseUrl.Contains("/api/v2.0/indexers/all/results/torznab")) { - return new NzbDroneValidationFailure("ApiPath", "Jackett's all endpoint is not supported, please add indexers individually") + return new NzbDroneValidationFailure("ApiPath", _localizationService.GetLocalizedString("IndexerValidationJackettAllNotSupported")) { IsWarning = true, - DetailedDescription = "Jackett's all endpoint is not supported, please add indexers individually" + DetailedDescription = _localizationService.GetLocalizedString("IndexerValidationJackettAllNotSupportedHelpText") }; } diff --git a/src/NzbDrone.Core/Indexers/Torznab/TorznabSettings.cs b/src/NzbDrone.Core/Indexers/Torznab/TorznabSettings.cs index ff5f59f65..983c3d594 100644 --- a/src/NzbDrone.Core/Indexers/Torznab/TorznabSettings.cs +++ b/src/NzbDrone.Core/Indexers/Torznab/TorznabSettings.cs @@ -51,7 +51,7 @@ namespace NzbDrone.Core.Indexers.Torznab MinimumSeeders = IndexerDefaults.MINIMUM_SEEDERS; } - [FieldDefinition(7, Type = FieldType.Number, Label = "Minimum Seeders", HelpText = "Minimum number of seeders required.", Advanced = true)] + [FieldDefinition(7, Type = FieldType.Number, Label = "IndexerSettingsMinimumSeeders", HelpText = "IndexerSettingsMinimumSeedersHelpText", Advanced = true)] public int MinimumSeeders { get; set; } [FieldDefinition(8)] diff --git a/src/NzbDrone.Core/Localization/Core/en.json b/src/NzbDrone.Core/Localization/Core/en.json index 13b227326..df12a7431 100644 --- a/src/NzbDrone.Core/Localization/Core/en.json +++ b/src/NzbDrone.Core/Localization/Core/en.json @@ -770,6 +770,8 @@ "Indexer": "Indexer", "IndexerDownloadClientHealthCheckMessage": "Indexers with invalid download clients: {indexerNames}.", "IndexerDownloadClientHelpText": "Specify which download client is used for grabs from this indexer", + "IndexerIPTorrentsSettingsFeedUrl": "Feed URL", + "IndexerIPTorrentsSettingsFeedUrlHelpText": "The full RSS feed url generated by IPTorrents, using only the categories you selected (HD, SD, x264, etc ...)", "IndexerJackettAllHealthCheckMessage": "Indexers using the unsupported Jackett 'all' endpoint: {indexerNames}", "IndexerLongTermStatusAllUnavailableHealthCheckMessage": "All indexers are unavailable due to failures for more than 6 hours", "IndexerLongTermStatusUnavailableHealthCheckMessage": "Indexers unavailable due to failures for more than 6 hours: {indexerNames}", @@ -782,9 +784,56 @@ "IndexerSearchNoAvailableIndexersHealthCheckMessage": "All search-capable indexers are temporarily unavailable due to recent indexer errors", "IndexerSearchNoInteractiveHealthCheckMessage": "No indexers available with Interactive Search enabled, {appName} will not provide any interactive search results", "IndexerSettings": "Indexer Settings", + "IndexerSettingsAdditionalNewznabParametersHelpText": "Please note if you change the category you will have to add required/restricted rules about the subgroups to avoid foreign language releases.", + "IndexerSettingsAdditionalParameters": "Additional Parameters", + "IndexerSettingsAdditionalParametersNyaa": "Additional Parameters", + "IndexerSettingsAllowZeroSize": "Allow Zero Size", + "IndexerSettingsAllowZeroSizeHelpText": "Enabling this will allow you to use feeds that don't specify release size, but be careful, size related checks will not be performed.", + "IndexerSettingsAnimeCategories": "Anime Categories", + "IndexerSettingsAnimeCategoriesHelpText": "Drop down list, leave blank to disable anime", + "IndexerSettingsAnimeStandardFormatSearch": "Anime Standard Format Search", + "IndexerSettingsAnimeStandardFormatSearchHelpText": "Also search for anime using the standard numbering", + "IndexerSettingsApiPath": "API Path", + "IndexerSettingsApiPathHelpText": "Path to the api, usually {url}", + "IndexerSettingsApiUrl": "API URL", + "IndexerSettingsApiUrlHelpText": "Do not change this unless you know what you're doing. Since your API key will be sent to that host.", + "IndexerSettingsCategories": "Categories", + "IndexerSettingsCategoriesHelpText": "Drop down list, leave blank to disable standard/daily shows", + "IndexerSettingsCookie": "Cookie", + "IndexerSettingsCookieHelpText": "If your site requires a login cookie to access the rss, you'll have to retrieve it via a browser.", + "IndexerSettingsMinimumSeeders": "Minimum Seeders", + "IndexerSettingsMinimumSeedersHelpText": "Minimum number of seeders required.", + "IndexerSettingsPasskey": "Passkey", + "IndexerSettingsRssUrl": "RSS URL", + "IndexerSettingsRssUrlHelpText": "Enter to URL to an {indexer} compatible RSS feed", + "IndexerSettingsSeasonPackSeedTime": "Season-Pack Seed Time", + "IndexerSettingsSeasonPackSeedTimeHelpText": "The time a season-pack torrent should be seeded before stopping, empty uses the download client's default", + "IndexerSettingsSeedRatio": "Seed Ratio", + "IndexerSettingsSeedRatioHelpText": "The ratio a torrent should reach before stopping, empty uses the download client's default. Ratio should be at least 1.0 and follow the indexers rules", + "IndexerSettingsSeedTime": "Seed Time", + "IndexerSettingsSeedTimeHelpText": "The time a torrent should be seeded before stopping, empty uses the download client's default", + "IndexerSettingsWebsiteUrl": "Website URL", "IndexerStatusAllUnavailableHealthCheckMessage": "All indexers are unavailable due to failures", "IndexerStatusUnavailableHealthCheckMessage": "Indexers unavailable due to failures: {indexerNames}", "IndexerTagHelpText": "Only use this indexer for series with at least one matching tag. Leave blank to use with all series.", + "IndexerValidationCloudFlareCaptchaExpired": "CloudFlare CAPTCHA token expired, please refresh it.", + "IndexerValidationCloudFlareCaptchaRequired": "Site protected by CloudFlare CAPTCHA. Valid CAPTCHA token required.", + "IndexerValidationFeedNotSupported": "Indexer feed is not supported: {exceptionMessage}", + "IndexerValidationInvalidApiKey": "Invalid API Key", + "IndexerValidationJackettAllNotSupported": "Jackett's all endpoint is not supported, please add indexers individually", + "IndexerValidationJackettAllNotSupportedHelpText": "Jackett's all endpoint is not supported, please add indexers individually", + "IndexerValidationJackettNoResultsInConfiguredCategories": "Query successful, but no results in the configured categories were returned from your indexer. This may be an issue with the indexer or your indexer category settings.", + "IndexerValidationJackettNoRssFeedQueryAvailable": "No RSS feed query available. This may be an issue with the indexer or your indexer category settings.", + "IndexerValidationQueryNotSupported": "Indexer does not support the current query. Check if the categories and or searching for seasons/episodes are supported. Check the log for more details.", + "IndexerValidationRequestLimitReached": "Request limit reached: {exceptionMessage}", + "IndexerValidationSearchParametersNotSupported": "Indexer does not support required search parameters", + "IndexerValidationTestAbortedDueToError": "Test was aborted due to an error: {exceptionMessage}", + "IndexerValidationUnableToConnect": "Unable to connect to indexer: {exceptionMessage}. Check the log surrounding this error for details", + "IndexerValidationUnableToConnectHttpError": "Unable to connect to indexer, please check your DNS settings and ensure that IPv6 is working or disabled. {exceptionMessage}.", + "IndexerValidationUnableToConnectInvalidCredentials": "Unable to connect to indexer, invalid credentials. {exceptionMessage}.", + "IndexerValidationUnableToConnectResolutionFailure": "Unable to connect to indexer connection failure. Check your connection to the indexer's server and DNS. {exceptionMessage}.", + "IndexerValidationUnableToConnectServerUnavailable": "Unable to connect to indexer, indexer's server is unavailable. Try again later. {exceptionMessage}.", + "IndexerValidationUnableToConnectTimeout": "Unable to connect to indexer, possibly due to a timeout. Try again or check your network settings. {exceptionMessage}.", "Indexers": "Indexers", "IndexersLoadError": "Unable to load Indexers", "IndexersSettingsSummary": "Indexers and indexer options", From c3b4126d0c4f449a41e2cf7ea438b20e25370995 Mon Sep 17 00:00:00 2001 From: Bogdan <mynameisbogdan@users.noreply.github.com> Date: Sun, 12 Nov 2023 18:01:39 +0200 Subject: [PATCH 106/136] Fixed: Enforce validation warnings when testing providers --- src/Sonarr.Api.V3/ProviderControllerBase.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Sonarr.Api.V3/ProviderControllerBase.cs b/src/Sonarr.Api.V3/ProviderControllerBase.cs index c5f6a5bcb..b35d52760 100644 --- a/src/Sonarr.Api.V3/ProviderControllerBase.cs +++ b/src/Sonarr.Api.V3/ProviderControllerBase.cs @@ -74,7 +74,7 @@ namespace Sonarr.Api.V3 if (providerDefinition.Enable) { - Test(providerDefinition, false); + Test(providerDefinition, !forceSave); } providerDefinition = _providerFactory.Create(providerDefinition); @@ -92,7 +92,7 @@ namespace Sonarr.Api.V3 // Only test existing definitions if it is enabled and forceSave isn't set. if (providerDefinition.Enable && !forceSave) { - Test(providerDefinition, false); + Test(providerDefinition, true); } _providerFactory.Update(providerDefinition); From b76bf373717edff8e475fde31fbaec86c65903fe Mon Sep 17 00:00:00 2001 From: Stevie Robinson <stevie.robinson@gmail.com> Date: Fri, 17 Nov 2023 01:31:44 +0100 Subject: [PATCH 107/136] Fixed: Custom Format Deletion confirmation message Closes #6172 --- .../src/Settings/CustomFormats/CustomFormats/CustomFormat.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/Settings/CustomFormats/CustomFormats/CustomFormat.js b/frontend/src/Settings/CustomFormats/CustomFormats/CustomFormat.js index d18abcc32..efe105b20 100644 --- a/frontend/src/Settings/CustomFormats/CustomFormats/CustomFormat.js +++ b/frontend/src/Settings/CustomFormats/CustomFormats/CustomFormat.js @@ -152,7 +152,7 @@ class CustomFormat extends Component { isOpen={this.state.isDeleteCustomFormatModalOpen} kind={kinds.DANGER} title={translate('DeleteCustomFormat')} - message={translate('DeleteCustomFormatMessageText', [name])} + message={translate('DeleteCustomFormatMessageText', { customFormatName: name })} confirmLabel={translate('Delete')} isSpinning={isDeleting} onConfirm={this.onConfirmDeleteCustomFormat} From c7d12066bf05f6b33063c6ac24b9b2bdf2f41fd4 Mon Sep 17 00:00:00 2001 From: bakerboy448 <55419169+bakerboy448@users.noreply.github.com> Date: Thu, 16 Nov 2023 18:32:51 -0600 Subject: [PATCH 108/136] Fixed: Logging length of sample file Closes #6180 --- src/NzbDrone.Core/MediaFiles/EpisodeImport/DetectSample.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/DetectSample.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/DetectSample.cs index e2981c748..b7c225eb2 100644 --- a/src/NzbDrone.Core/MediaFiles/EpisodeImport/DetectSample.cs +++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/DetectSample.cs @@ -67,7 +67,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport return DetectSampleResult.Sample; } - _logger.Debug("Runtime is over 90 seconds"); + _logger.Debug("[{0}] does not appear to be a sample. Runtime {1} seconds is more than minimum of {2} seconds", path, runTime, minimumRuntime); return DetectSampleResult.NotSample; } From 71fd09f162b2880c461e03cba4317c34ee3203dc Mon Sep 17 00:00:00 2001 From: Mark McDowall <mark@mcdowall.ca> Date: Sun, 12 Nov 2023 21:31:00 -0800 Subject: [PATCH 109/136] Don't retest unchanged providers New: Don't retest connections, indexers, download clients, etc if re-saved with the exact same settings Closes #6169 --- .../ThingiProvider/ProviderDefinition.cs | 6 +++- src/Sonarr.Api.V3/ProviderControllerBase.cs | 28 ++++++++++++------- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/src/NzbDrone.Core/ThingiProvider/ProviderDefinition.cs b/src/NzbDrone.Core/ThingiProvider/ProviderDefinition.cs index d83c7dfda..292bc4bc5 100644 --- a/src/NzbDrone.Core/ThingiProvider/ProviderDefinition.cs +++ b/src/NzbDrone.Core/ThingiProvider/ProviderDefinition.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System.Collections.Generic; +using System.Text.Json.Serialization; using NzbDrone.Core.Datastore; namespace NzbDrone.Core.ThingiProvider @@ -13,7 +14,10 @@ namespace NzbDrone.Core.ThingiProvider private IProviderConfig _settings; public string Name { get; set; } + + [JsonIgnore] public string ImplementationName { get; set; } + public string Implementation { get; set; } public string ConfigContract { get; set; } public virtual bool Enable { get; set; } diff --git a/src/Sonarr.Api.V3/ProviderControllerBase.cs b/src/Sonarr.Api.V3/ProviderControllerBase.cs index b35d52760..71230d36a 100644 --- a/src/Sonarr.Api.V3/ProviderControllerBase.cs +++ b/src/Sonarr.Api.V3/ProviderControllerBase.cs @@ -70,7 +70,7 @@ namespace Sonarr.Api.V3 [Produces("application/json")] public ActionResult<TProviderResource> CreateProvider([FromBody] TProviderResource providerResource, [FromQuery] bool forceSave = false) { - var providerDefinition = GetDefinition(providerResource, true, !forceSave, false); + var providerDefinition = GetDefinition(providerResource, null, true, !forceSave, false); if (providerDefinition.Enable) { @@ -87,15 +87,22 @@ namespace Sonarr.Api.V3 [Produces("application/json")] public ActionResult<TProviderResource> UpdateProvider([FromBody] TProviderResource providerResource, [FromQuery] bool forceSave = false) { - var providerDefinition = GetDefinition(providerResource, true, !forceSave, false); + var existingDefinition = _providerFactory.Find(providerResource.Id); + var providerDefinition = GetDefinition(providerResource, existingDefinition, true, !forceSave, false); - // Only test existing definitions if it is enabled and forceSave isn't set. - if (providerDefinition.Enable && !forceSave) + // Comparing via JSON string to eliminate the need for every provider implementation to implement equality checks. + var hasDefinitionChanged = STJson.ToJson(existingDefinition) != STJson.ToJson(providerDefinition); + + // Only test existing definitions if it is enabled and forceSave isn't set or the definition has changed. + if (providerDefinition.Enable && (!forceSave || hasDefinitionChanged)) { Test(providerDefinition, true); } - _providerFactory.Update(providerDefinition); + if (hasDefinitionChanged) + { + _providerFactory.Update(providerDefinition); + } return Accepted(providerResource.Id); } @@ -141,9 +148,8 @@ namespace Sonarr.Api.V3 return Accepted(_providerFactory.Update(definitionsToUpdate).Select(x => _resourceMapper.ToResource(x))); } - private TProviderDefinition GetDefinition(TProviderResource providerResource, bool validate, bool includeWarnings, bool forceValidate) + private TProviderDefinition GetDefinition(TProviderResource providerResource, TProviderDefinition existingDefinition, bool validate, bool includeWarnings, bool forceValidate) { - var existingDefinition = providerResource.Id > 0 ? _providerFactory.Find(providerResource.Id) : null; var definition = _resourceMapper.ToModel(providerResource, existingDefinition); if (validate && (definition.Enable || forceValidate)) @@ -199,7 +205,8 @@ namespace Sonarr.Api.V3 [Consumes("application/json")] public object Test([FromBody] TProviderResource providerResource) { - var providerDefinition = GetDefinition(providerResource, true, true, true); + var existingDefinition = providerResource.Id > 0 ? _providerFactory.Find(providerResource.Id) : null; + var providerDefinition = GetDefinition(providerResource, existingDefinition, true, true, true); Test(providerDefinition, true); @@ -236,9 +243,10 @@ namespace Sonarr.Api.V3 [HttpPost("action/{name}")] [Consumes("application/json")] [Produces("application/json")] - public IActionResult RequestAction(string name, [FromBody] TProviderResource resource) + public IActionResult RequestAction(string name, [FromBody] TProviderResource providerResource) { - var providerDefinition = GetDefinition(resource, false, false, false); + var existingDefinition = providerResource.Id > 0 ? _providerFactory.Find(providerResource.Id) : null; + var providerDefinition = GetDefinition(providerResource, existingDefinition, false, false, false); var query = Request.Query.ToDictionary(x => x.Key, x => x.Value.ToString()); From c028222617c5771d3a4776ad3ad9efa1a0293d1f Mon Sep 17 00:00:00 2001 From: Mark McDowall <mark@mcdowall.ca> Date: Thu, 16 Nov 2023 16:34:44 -0800 Subject: [PATCH 110/136] New: Support import list lookup by TMDb ID --- .../ImportLists/ImportListSyncService.cs | 17 +++++++++++++++-- .../ImportLists/Rss/Plex/PlexRssImportParser.cs | 5 +++++ .../MetadataSource/ISearchForNewSeries.cs | 1 + .../MetadataSource/SkyHook/SkyHookProxy.cs | 7 +++++++ 4 files changed, 28 insertions(+), 2 deletions(-) diff --git a/src/NzbDrone.Core/ImportLists/ImportListSyncService.cs b/src/NzbDrone.Core/ImportLists/ImportListSyncService.cs index e85e95730..ef735e75f 100644 --- a/src/NzbDrone.Core/ImportLists/ImportListSyncService.cs +++ b/src/NzbDrone.Core/ImportLists/ImportListSyncService.cs @@ -90,7 +90,7 @@ namespace NzbDrone.Core.ImportLists var importList = importLists.Single(x => x.Id == item.ImportListId); - // Map by IMDbId if we have it + // Map by IMDb ID if we have it if (item.TvdbId <= 0 && item.ImdbId.IsNotNullOrWhiteSpace()) { var mappedSeries = _seriesSearchService.SearchForNewSeriesByImdbId(item.ImdbId) @@ -103,7 +103,20 @@ namespace NzbDrone.Core.ImportLists } } - // Map by AniListId if we have it + // Map by TMDb ID if we have it + if (item.TvdbId <= 0 && item.TmdbId > 0) + { + var mappedSeries = _seriesSearchService.SearchForNewSeriesByTmdbId(item.TmdbId) + .FirstOrDefault(); + + if (mappedSeries != null) + { + item.TvdbId = mappedSeries.TvdbId; + item.Title = mappedSeries?.Title; + } + } + + // Map by AniList ID if we have it if (item.TvdbId <= 0 && item.AniListId > 0) { var mappedSeries = _seriesSearchService.SearchForNewSeriesByAniListId(item.AniListId) diff --git a/src/NzbDrone.Core/ImportLists/Rss/Plex/PlexRssImportParser.cs b/src/NzbDrone.Core/ImportLists/Rss/Plex/PlexRssImportParser.cs index efd26708e..c1b7307fe 100644 --- a/src/NzbDrone.Core/ImportLists/Rss/Plex/PlexRssImportParser.cs +++ b/src/NzbDrone.Core/ImportLists/Rss/Plex/PlexRssImportParser.cs @@ -44,6 +44,11 @@ namespace NzbDrone.Core.ImportLists.Rss.Plex { info.TvdbId = tvdbId; } + + if (int.TryParse(guid.Replace("tmdb://", ""), out var tmdbId)) + { + info.TmdbId = tvdbId; + } } if (info.ImdbId.IsNullOrWhiteSpace() && info.TvdbId == 0) diff --git a/src/NzbDrone.Core/MetadataSource/ISearchForNewSeries.cs b/src/NzbDrone.Core/MetadataSource/ISearchForNewSeries.cs index 94d30f166..f8aef8654 100644 --- a/src/NzbDrone.Core/MetadataSource/ISearchForNewSeries.cs +++ b/src/NzbDrone.Core/MetadataSource/ISearchForNewSeries.cs @@ -8,5 +8,6 @@ namespace NzbDrone.Core.MetadataSource List<Series> SearchForNewSeries(string title); List<Series> SearchForNewSeriesByImdbId(string imdbId); List<Series> SearchForNewSeriesByAniListId(int aniListId); + List<Series> SearchForNewSeriesByTmdbId(int tmdbId); } } diff --git a/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs b/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs index b067c3dd8..76efea07d 100644 --- a/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs +++ b/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs @@ -90,6 +90,13 @@ namespace NzbDrone.Core.MetadataSource.SkyHook return results; } + public List<Series> SearchForNewSeriesByTmdbId(int tmdbId) + { + var results = SearchForNewSeries($"tmdb:{tmdbId}"); + + return results; + } + public List<Series> SearchForNewSeries(string title) { try From 812712e2843a738054c065a6d5c1b7c81c5f8e7b Mon Sep 17 00:00:00 2001 From: Mark McDowall <mark@mcdowall.ca> Date: Thu, 16 Nov 2023 16:35:12 -0800 Subject: [PATCH 111/136] Rename 'ReturnUrl' to 'returnUrl' for forms auth redirection --- .../Authentication/AuthenticationBuilderExtensions.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Sonarr.Http/Authentication/AuthenticationBuilderExtensions.cs b/src/Sonarr.Http/Authentication/AuthenticationBuilderExtensions.cs index e6e0d6cd2..21d5b7009 100644 --- a/src/Sonarr.Http/Authentication/AuthenticationBuilderExtensions.cs +++ b/src/Sonarr.Http/Authentication/AuthenticationBuilderExtensions.cs @@ -40,6 +40,7 @@ namespace Sonarr.Http.Authentication options.LoginPath = "/login"; options.ExpireTimeSpan = TimeSpan.FromDays(7); options.SlidingExpiration = true; + options.ReturnUrlParameter = "returnUrl"; }) .AddApiKey("API", options => { From 6ddfd996199ef2d8cc44bec2ec064fb15d627542 Mon Sep 17 00:00:00 2001 From: Weblate <noreply@weblate.org> Date: Tue, 14 Nov 2023 14:58:06 +0000 Subject: [PATCH 112/136] Multiple Translations updated by Weblate ignore-downstream Co-authored-by: Havok Dan <havokdan@yahoo.com.br> Co-authored-by: Weblate <noreply@weblate.org> Co-authored-by: kevin8717 <871728422@qq.com> Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/pt_BR/ Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/zh_CN/ Translation: Servarr/Sonarr --- .../Localization/Core/pt_BR.json | 159 +++++++++++++++++- .../Localization/Core/zh_CN.json | 2 +- 2 files changed, 153 insertions(+), 8 deletions(-) diff --git a/src/NzbDrone.Core/Localization/Core/pt_BR.json b/src/NzbDrone.Core/Localization/Core/pt_BR.json index b7aafe6e0..f4acff10f 100644 --- a/src/NzbDrone.Core/Localization/Core/pt_BR.json +++ b/src/NzbDrone.Core/Localization/Core/pt_BR.json @@ -30,7 +30,7 @@ "RemoveFailedDownloads": "Remover downloads com falha", "QualityProfile": "Perfil de Qualidade", "RefreshSeries": "Atualizar Séries", - "RemotePathMappingDockerFolderMissingHealthCheckMessage": "Você está usando o docker; o cliente de download $1{downloadClientName} coloca os downloads em {path}, mas esse diretório parece não existir dentro do contêiner. Revise seus mapeamentos de caminho remoto e configurações de volume do contêiner.", + "RemotePathMappingDockerFolderMissingHealthCheckMessage": "Você está usando o docker; o cliente de download {downloadClientName} coloca os downloads em {path}, mas este diretório parece não existir dentro do contêiner. Revise seus mapeamentos de caminho remoto e configurações de volume de contêiner.", "RemotePathMappingDownloadPermissionsHealthCheckMessage": "O {appName} pode ver, mas não acessar o episódio baixado {path}. Provável erro de permissão.", "RemotePathMappingFileRemovedHealthCheckMessage": "O arquivo {path} foi removido no meio do processamento.", "RemotePathMappingFilesGenericPermissionsHealthCheckMessage": "Baixe os arquivos relatados do cliente {downloadClientName} em {path}, mas o {appName} não pode ver este diretório. Pode ser necessário ajustar as permissões da pasta.", @@ -142,7 +142,7 @@ "Yes": "Sim", "Tags": "Tags", "AutoAdd": "Adicionar automaticamente", - "RemovingTag": "Removendo tag", + "RemovingTag": "Removendo a tag", "DeleteSelectedIndexersMessageText": "Tem certeza de que deseja excluir {count} indexadores selecionados?", "RemoveCompleted": "Remoção Concluída", "LibraryImport": "Importar para biblioteca", @@ -160,7 +160,7 @@ "Tasks": "Tarefas", "Updates": "Atualizações", "Wanted": "Procurado", - "ApplyTagsHelpTextAdd": "Adicionar: adicione as tags à lista existente de tags", + "ApplyTagsHelpTextAdd": "Adicionar: Adicione as tags à lista existente de tags", "ApplyTagsHelpTextReplace": "Substituir: Substitua as tags pelas tags inseridas (não digite nenhuma tag para limpar todas as tags)", "ApplyTagsHelpTextRemove": "Remover: Remove as tags inseridas", "CustomFormatScore": "Pontuação do formato personalizado", @@ -187,7 +187,7 @@ "ApplyTagsHelpTextHowToApplyDownloadClients": "Como aplicar tags aos clientes de download selecionados", "ApplyTagsHelpTextHowToApplyImportLists": "Como aplicar tags às listas de importação selecionadas", "ApplyTagsHelpTextHowToApplyIndexers": "Como aplicar tags aos indexadores selecionados", - "ApplyTagsHelpTextHowToApplySeries": "Como aplicar etiquetas à série selecionada", + "ApplyTagsHelpTextHowToApplySeries": "Como aplicar tags à série selecionada", "EpisodeInfo": "Info do Episódio", "EpisodeNumbers": "Número(s) do(s) Episódio(s)", "FullSeason": "Temporada Completa", @@ -897,7 +897,7 @@ "UnmonitorDeletedEpisodes": "Cancelar Monitoramento de Episódios Excluídos", "UnsavedChanges": "Alterações Não Salvas", "UpdateAutomaticallyHelpText": "Baixe e instale atualizações automaticamente. Você ainda poderá instalar a partir do Sistema: Atualizações", - "UpdateMechanismHelpText": "Usar o atualizador integrado do {appName} ou um script", + "UpdateMechanismHelpText": "Use o atualizador integrado do {appName} ou um script", "UpdateSonarrDirectlyLoadError": "Incapaz de atualizar o {appName} diretamente,", "UpdateUiNotWritableHealthCheckMessage": "Não é possível instalar a atualização porque a pasta de IU '{uiFolder}' não pode ser salva pelo usuário '{userName}'.", "UpgradeUntil": "Atualizar Até", @@ -1270,7 +1270,7 @@ "OverrideGrabNoQuality": "A qualidade deve ser selecionada", "OverrideGrabNoSeries": "A série deve ser selecionada", "Parse": "Analisar", - "ParseModalHelpTextDetails": "O {appName} tentará analisar o título e mostrar detalhes sobre ele", + "ParseModalHelpTextDetails": "{appName} tentará analisar o título e mostrar detalhes sobre ele", "RecentChanges": "Mudanças Recentes", "ReleaseRejected": "Lançamento Rejeitado", "ReleaseSceneIndicatorAssumingScene": "Assumindo a Numeração da Scene.", @@ -1488,5 +1488,150 @@ "AutoRedownloadFailedFromInteractiveSearch": "Falha no Novo Download pela Pesquisa Interativa", "ImportListSearchForMissingEpisodes": "Pesquisar Episódios Ausentes", "ImportListSearchForMissingEpisodesHelpText": "Depois que a série for adicionada ao {appName}, procure automaticamente episódios ausentes", - "QueueFilterHasNoItems": "O filtro de fila selecionado não possui itens" + "QueueFilterHasNoItems": "O filtro de fila selecionado não possui itens", + "BlackholeFolderHelpText": "Pasta na qual {appName} armazenará o arquivo {extension}", + "Destination": "Destinação", + "DownloadClientDelugeSettingsUrlBaseHelpText": "Adiciona um prefixo ao URL json do deluge, consulte {url}", + "DownloadClientDelugeValidationLabelPluginFailure": "Falha na configuração do rótulo", + "DownloadClientDelugeValidationLabelPluginFailureDetail": "{appName} não conseguiu adicionar o rótulo ao {clientName}.", + "DownloadClientDownloadStationProviderMessage": "{appName} não consegue se conectar ao Download Station se a autenticação de dois fatores estiver habilitada em sua conta DSM", + "DownloadClientDownloadStationValidationFolderMissingDetail": "A pasta '{downloadDir}' não existe, ela deve ser criada manualmente dentro da Pasta Compartilhada '{sharedFolder}'.", + "DownloadClientDownloadStationValidationSharedFolderMissingDetail": "O Diskstation não possui uma pasta compartilhada com o nome '{sharedFolder}', tem certeza de que a especificou corretamente?", + "DownloadClientFreeboxSettingsAppIdHelpText": "ID do aplicativo fornecido ao criar acesso à API Freebox (ou seja, 'app_id')", + "DownloadClientFreeboxSettingsPortHelpText": "Porta usada para acessar a interface do Freebox, o padrão é '{port}'", + "DownloadClientFreeboxUnableToReachFreeboxApi": "Não foi possível acessar a API Freebox. Verifique a configuração de 'URL da API' para URL base e versão.", + "DownloadClientNzbgetValidationKeepHistoryOverMax": "A configuração NzbGet KeepHistory deve ser menor que 25.000", + "DownloadClientNzbgetValidationKeepHistoryZeroDetail": "A configuração KeepHistory do NzbGet está definida como 0. O que impede que {appName} veja os downloads concluídos.", + "DownloadClientSettingsUrlBaseHelpText": "Adiciona um prefixo ao URL {clientName}, como {url}", + "DownloadClientSettingsUseSslHelpText": "Use conexão segura ao conectar-se a {clientName}", + "DownloadClientTransmissionSettingsDirectoryHelpText": "Local opcional para colocar downloads, deixe em branco para usar o local padrão do Transmission", + "DownloadClientTransmissionSettingsUrlBaseHelpText": "Adiciona um prefixo ao URL rpc {clientName}, por exemplo, {url}, o padrão é '{defaultUrl}'", + "DownloadClientValidationAuthenticationFailureDetail": "Por favor, verifique seu nome de usuário e senha. Verifique também se o host que executa {appName} não está impedido de acessar {clientName} pelas limitações da WhiteList na configuração de {clientName}.", + "DownloadClientValidationSslConnectFailureDetail": "{appName} não consegue se conectar a {clientName} usando SSL. Este problema pode estar relacionado ao computador. Tente configurar {appName} e {clientName} para não usar SSL.", + "NzbgetHistoryItemMessage": "Status PAR: {parStatus} - Status de descompactação: {unpackStatus} - Status de movimentação: {moveStatus} - Status do script: {scriptStatus} - Status de exclusão: {deleteStatus} - Status de marcação: {markStatus}", + "PostImportCategory": "Categoria Pós-Importação", + "SecretToken": "Token Secreto", + "TorrentBlackhole": "Torrent Blackhole", + "TorrentBlackholeSaveMagnetFiles": "Salvar arquivos magnéticos", + "TorrentBlackholeSaveMagnetFilesHelpText": "Salve o link magnético se nenhum arquivo .torrent estiver disponível (útil apenas se o cliente de download suportar magnets salvos em um arquivo)", + "UnknownDownloadState": "Estado de download desconhecido: {state}", + "UsenetBlackhole": "Usenet Blackhole", + "DownloadClientQbittorrentSettingsInitialStateHelpText": "Estado inicial para torrents adicionados ao qBittorrent. Observe que os Torrents Forçados não obedecem às restrições de sementes", + "DownloadClientQbittorrentTorrentStatePathError": "Não foi possível importar. O caminho corresponde ao diretório de download da base do cliente, é possível que 'Manter pasta de nível superior' esteja desativado para este torrent ou 'Layout de conteúdo do torrent' NÃO esteja definido como 'Original' ou 'Criar subpasta'?", + "DownloadClientQbittorrentValidationCategoryUnsupportedDetail": "As categorias não são suportadas até a versão 3.3.0 do qBittorrent. Atualize ou tente novamente com uma categoria vazia.", + "DownloadClientQbittorrentValidationRemovesAtRatioLimit": "qBittorrent está configurado para remover torrents quando eles atingem seu limite de proporção de compartilhamento", + "DownloadClientQbittorrentValidationRemovesAtRatioLimitDetail": "{appName} não poderá realizar o tratamento de download concluído conforme configurado. Você pode corrigir isso no qBittorrent ('Ferramentas - Opções...' no menu) alterando 'Opções - BitTorrent - Limitação da proporção de compartilhamento' de 'Removê-los' para 'Pausá-los'", + "DownloadClientRTorrentSettingsUrlPathHelpText": "Caminho para o endpoint XMLRPC, consulte {url}. Geralmente é RPC2 ou [caminho para ruTorrent]{url2} ao usar o ruTorrent.", + "DownloadClientSabnzbdValidationCheckBeforeDownloadDetail": "Usar 'Verificar antes do download' afeta a capacidade do {appName} de rastrear novos downloads. Além disso, o Sabnzbd recomenda 'Abortar trabalhos que não podem ser concluídos', pois é mais eficaz.", + "DownloadClientSabnzbdValidationEnableDisableMovieSortingDetail": "Você deve desativar a ordenação de filmes para a categoria usada por {appName} para evitar problemas de importação. Vá para Sabnzbd para consertar.", + "DownloadClientSabnzbdValidationEnableJobFoldersDetail": "{appName} prefere que cada download tenha uma pasta separada. Com * anexado à pasta/caminho, o Sabnzbd não criará essas pastas de trabalho. Vá para Sabnzbd para consertar.", + "DownloadClientSettingsCategorySubFolderHelpText": "Adicionar uma categoria específica para {appName} evita conflitos com downloads não relacionados que não sejam de {appName}. Usar uma categoria é opcional, mas altamente recomendado. Cria um subdiretório [categoria] no diretório de saída.", + "XmlRpcPath": "Caminho RPC XML", + "DownloadClientFloodSettingsTagsHelpText": "Etiquetas iniciais de um download. Para ser reconhecido, um download deve ter todas as tags iniciais. Isso evita conflitos com downloads não relacionados.", + "DownloadClientFloodSettingsAdditionalTagsHelpText": "Adiciona propriedades de mídia como tags. As dicas são exemplos.", + "DownloadClientFloodSettingsPostImportTagsHelpText": "Acrescenta tags após a importação de um download.", + "BlackholeWatchFolder": "Pasta Monitorada", + "BlackholeWatchFolderHelpText": "Pasta da qual {appName} deve importar downloads concluídos", + "Category": "Categoria", + "Directory": "Diretório", + "DownloadClientDelugeTorrentStateError": "Deluge está relatando um erro", + "DownloadClientDelugeValidationLabelPluginInactiveDetail": "Você deve ter o plugin Rotulo habilitado em {clientName} para usar categorias.", + "DownloadClientDownloadStationSettingsDirectory": "Pasta compartilhada opcional para colocar downloads, deixe em branco para usar o local padrão do Download Station", + "DownloadClientDownloadStationValidationApiVersion": "A versão da API do Download Station não é suportada; deve ser pelo menos {requiredVersion}. Suporta de {minVersion} a {maxVersion}", + "DownloadClientDownloadStationValidationFolderMissing": "A pasta não existe", + "DownloadClientDownloadStationValidationNoDefaultDestination": "Nenhum destino padrão", + "DownloadClientDownloadStationValidationNoDefaultDestinationDetail": "Você deve fazer login em seu Diskstation como {username} e configurá-lo manualmente nas configurações do DownloadStation em BT/HTTP/FTP/NZB - Localização.", + "DownloadClientDownloadStationValidationSharedFolderMissing": "A pasta compartilhada não existe", + "DownloadClientFloodSettingsAdditionalTags": "Etiquetas Adicionais", + "DownloadClientFloodSettingsPostImportTags": "Etiquetas Pós-Importação", + "DownloadClientFloodSettingsRemovalInfo": "{appName} cuidará da remoção automática de torrents com base nos critérios de propagação atuais em Configurações - Indexadores", + "DownloadClientFloodSettingsStartOnAdd": "Comece em Adicionar", + "DownloadClientFloodSettingsUrlBaseHelpText": "Adiciona um prefixo à API Flood, como {url}", + "DownloadClientFreeboxApiError": "A API Freebox retornou um erro: {errorDescription}", + "DownloadClientFreeboxAuthenticationError": "A autenticação na API Freebox falhou. Motivo: {errorDescription}", + "DownloadClientFreeboxNotLoggedIn": "Não logado", + "DownloadClientFreeboxSettingsApiUrl": "URL da API", + "DownloadClientFreeboxSettingsApiUrlHelpText": "Defina o URL base da API Freebox com a versão da API, por exemplo, '{url}', o padrão é '{defaultApiUrl}'", + "DownloadClientFreeboxSettingsAppId": "ID do App", + "DownloadClientFreeboxSettingsAppToken": "Token do App", + "DownloadClientFreeboxSettingsAppTokenHelpText": "Token do aplicativo recuperado ao criar acesso à API Freebox (ou seja, 'app_token')", + "DownloadClientFreeboxSettingsHostHelpText": "Nome do host ou endereço IP do host do Freebox, o padrão é '{url}' (só funcionará se estiver na mesma rede)", + "DownloadClientFreeboxUnableToReachFreebox": "Não foi possível acessar a API Freebox. Verifique as configurações de 'Host', 'Porta' ou 'Usar SSL'. (Erro: {exceptionMessage})", + "DownloadClientNzbVortexMultipleFilesMessage": "O download contém vários arquivos e não está em uma pasta de trabalho: {outputPath}", + "DownloadClientNzbgetSettingsAddPausedHelpText": "Esta opção requer pelo menos NzbGet versão 16.0", + "DownloadClientDelugeValidationLabelPluginInactive": "Plugin de tag não está ativado", + "DownloadClientNzbgetValidationKeepHistoryOverMaxDetail": "A configuração KeepHistory do NzbGet está muito alta.", + "DownloadClientNzbgetValidationKeepHistoryZero": "A configuração KeepHistory do NzbGet deve ser maior que 0", + "DownloadClientPneumaticSettingsNzbFolder": "Pasta Nzb", + "DownloadClientPneumaticSettingsNzbFolderHelpText": "Esta pasta precisará estar acessível no XBMC", + "DownloadClientPneumaticSettingsStrmFolder": "Pasta Strm", + "DownloadClientPneumaticSettingsStrmFolderHelpText": "Os arquivos .strm nesta pasta serão importados pelo drone", + "DownloadClientQbittorrentSettingsFirstAndLastFirst": "Primeiro e último primeiro", + "DownloadClientQbittorrentSettingsFirstAndLastFirstHelpText": "Baixe a primeira e a última peças primeiro (qBittorrent 4.1.0+)", + "DownloadClientQbittorrentSettingsSequentialOrder": "Ordem sequencial", + "DownloadClientQbittorrentSettingsSequentialOrderHelpText": "Baixe em ordem sequencial (qBittorrent 4.1.0+)", + "DownloadClientQbittorrentSettingsUseSslHelpText": "Use uma conexão segura. Consulte Opções - UI da Web - 'Usar HTTPS em vez de HTTP' no qBittorrent.", + "DownloadClientQbittorrentTorrentStateDhtDisabled": "qBittorrent não pode resolver o link magnético com DHT desativado", + "DownloadClientQbittorrentTorrentStateError": "qBittorrent está relatando um erro", + "DownloadClientQbittorrentTorrentStateMetadata": "qBittorrent está baixando metadados", + "DownloadClientQbittorrentTorrentStateStalled": "O download está parado sem conexões", + "DownloadClientQbittorrentTorrentStateUnknown": "Estado de download desconhecido: {state}", + "DownloadClientQbittorrentValidationCategoryAddFailure": "Falha na configuração da categoria", + "DownloadClientQbittorrentValidationCategoryAddFailureDetail": "{appName} não conseguiu adicionar o rótulo ao qBittorrent.", + "DownloadClientQbittorrentValidationCategoryRecommended": "Categoria é recomendada", + "DownloadClientQbittorrentValidationCategoryRecommendedDetail": "{appName} não tentará importar downloads concluídos sem uma categoria.", + "DownloadClientQbittorrentValidationCategoryUnsupported": "A categoria não é suportada", + "DownloadClientQbittorrentValidationQueueingNotEnabled": "Fila não habilitada", + "DownloadClientQbittorrentValidationQueueingNotEnabledDetail": "O Filas de Torrent não está habilitado nas configurações do qBittorrent. Habilite-o no qBittorrent ou selecione ‘Último’ como prioridade.", + "DownloadClientRTorrentProviderMessage": "O rTorrent não pausará os torrents quando eles atenderem aos critérios de seed. {appName} lidará com a remoção automática de torrents com base nos critérios de propagação atuais em Configurações - Indexadores somente quando a remoção concluída estiver ativada.", + "DownloadClientRTorrentSettingsAddStopped": "Adicionar parado", + "DownloadClientRTorrentSettingsAddStoppedHelpText": "A ativação adicionará torrents e magnets ao rTorrent em um estado parado. Isso pode quebrar os arquivos magnéticos.", + "DownloadClientRTorrentSettingsDirectoryHelpText": "Local opcional para colocar downloads, deixe em branco para usar o local padrão do rTorrent", + "DownloadClientRTorrentSettingsUrlPath": "Caminho da URL", + "DownloadClientSabnzbdValidationCheckBeforeDownload": "Desative a opção ‘Verificar antes do download’ no Sabnbzd", + "DownloadClientSabnzbdValidationDevelopVersion": "Versão de desenvolvimento do Sabnzbd, assumindo a versão 3.0.0 ou superior.", + "DownloadClientSabnzbdValidationDevelopVersionDetail": "{appName} pode não ser compatível com novos recursos adicionados ao SABnzbd ao executar versões de desenvolvimento.", + "DownloadClientSabnzbdValidationEnableDisableDateSorting": "Desabilitar ordenação por data", + "DownloadClientSabnzbdValidationEnableDisableDateSortingDetail": "Você deve desativar a ordenação por data para a categoria usada por {appName} para evitar problemas de importação. Vá para Sabnzbd para consertar.", + "DownloadClientSabnzbdValidationEnableDisableMovieSorting": "Desabilitar Ordenação de Filmes", + "DownloadClientSabnzbdValidationEnableDisableTvSorting": "Desabilitar Ordenação para TV", + "DownloadClientSabnzbdValidationEnableDisableTvSortingDetail": "Você deve desativar a ordenação de TV para a categoria usada por {appName} para evitar problemas de importação. Vá para Sabnzbd para consertar.", + "DownloadClientSabnzbdValidationEnableJobFolders": "Habilitar pastas de trabalho", + "DownloadClientSabnzbdValidationUnknownVersion": "Versão desconhecida: {rawVersion}", + "DownloadClientSettingsAddPaused": "Adicionar Pausado", + "DownloadClientSettingsCategoryHelpText": "Adicionar uma categoria específica para {appName} evita conflitos com downloads não relacionados que não sejam de {appName}. Usar uma categoria é opcional, mas altamente recomendado.", + "DownloadClientSettingsDestinationHelpText": "Especifica manualmente o destino do download, deixe em branco para usar o padrão", + "DownloadClientSettingsInitialState": "Estado Inicial", + "DownloadClientSettingsInitialStateHelpText": "Estado inicial dos torrents adicionados ao {clientName}", + "DownloadClientSettingsOlderPriorityHelpText": "Prioridade de uso ao baixar episódios que foram ao ar há mais de 14 dias", + "DownloadClientSettingsPostImportCategoryHelpText": "Categoria para {appName} definir após importar o download. {appName} não removerá torrents nessa categoria mesmo que a propagação seja concluída. Deixe em branco para manter a mesma categoria.", + "DownloadClientSettingsRecentPriorityHelpText": "Prioridade de uso ao baixar episódios que foram ao ar nos últimos 14 dias", + "DownloadClientSettingsOlderPriority": "Prioridade para os mais antigos", + "DownloadClientSettingsRecentPriority": "Prioridade para os mais recentes", + "DownloadClientUTorrentTorrentStateError": "uTorrent está relatando um erro", + "DownloadClientValidationApiKeyIncorrect": "Chave de API incorreta", + "DownloadClientValidationApiKeyRequired": "Chave de API necessária", + "DownloadClientValidationAuthenticationFailure": "Falha de autenticação", + "DownloadClientValidationCategoryMissing": "A categoria não existe", + "DownloadClientValidationCategoryMissingDetail": "A categoria inserida não existe em {clientName}. Crie-a primeiro em {clientName}.", + "DownloadClientValidationErrorVersion": "A versão de {clientName} deve ser pelo menos {requiredVersion}. A versão informada é {reportedVersion}", + "DownloadClientValidationGroupMissing": "O grupo não existe", + "DownloadClientValidationGroupMissingDetail": "O grupo inserido não existe em {clientName}. Crie-a primeiro em {clientName}.", + "DownloadClientValidationSslConnectFailure": "Não é possível conectar através de SSL", + "DownloadClientValidationTestNzbs": "Falha ao obter a lista de NZBs: {exceptionMessage}", + "DownloadClientValidationTestTorrents": "Falha ao obter a lista de torrents: {exceptionMessage}", + "DownloadClientValidationUnableToConnect": "Não foi possível conectar-se a {clientName}", + "DownloadClientValidationUnableToConnectDetail": "Verifique o nome do host e a porta.", + "DownloadClientValidationUnknownException": "Exceção desconhecida: {exception}", + "DownloadClientValidationVerifySsl": "Verifique as configurações de SSL", + "DownloadClientValidationVerifySslDetail": "Verifique sua configuração SSL em {clientName} e {appName}", + "DownloadClientVuzeValidationErrorVersion": "Versão do protocolo não suportada, use Vuze 5.0.0.0 ou superior com o plugin Vuze Web Remote.", + "DownloadStationStatusExtracting": "Extraindo: {progress}%", + "TorrentBlackholeSaveMagnetFilesExtension": "Salvar extensão de arquivos magnéticos", + "TorrentBlackholeSaveMagnetFilesExtensionHelpText": "Extensão a ser usada para links magnéticos, o padrão é '.magnet'", + "TorrentBlackholeSaveMagnetFilesReadOnly": "Somente para Leitura", + "TorrentBlackholeSaveMagnetFilesReadOnlyHelpText": "Em vez de mover arquivos, isso instruirá {appName} a copiar ou vincular (dependendo das configurações/configuração do sistema)", + "TorrentBlackholeTorrentFolder": "Pasta do Torrent", + "UseSsl": "Usar SSL", + "UsenetBlackholeNzbFolder": "Pasta Nzb" } diff --git a/src/NzbDrone.Core/Localization/Core/zh_CN.json b/src/NzbDrone.Core/Localization/Core/zh_CN.json index 9f64f20e8..79e85f711 100644 --- a/src/NzbDrone.Core/Localization/Core/zh_CN.json +++ b/src/NzbDrone.Core/Localization/Core/zh_CN.json @@ -590,7 +590,7 @@ "DownloadIgnored": "忽略下载", "DownloadIgnoredTooltip": "集下载被忽略", "EditAutoTag": "编辑自动标签", - "AddAutoTagError": "无法添加一个新自动标签,请重试。", + "AddAutoTagError": "无法添加新的自动标签,请重试。", "AddImportListExclusionError": "无法添加新排除列表,请再试一次。", "AddIndexer": "添加索引器", "AddImportList": "添加导入列表", From 5328fb4ab31b643449a38adb4abcd2ea57371df4 Mon Sep 17 00:00:00 2001 From: Bogdan <mynameisbogdan@users.noreply.github.com> Date: Fri, 17 Nov 2023 17:25:38 +0200 Subject: [PATCH 113/136] New: Additional options for HDBits --- src/NzbDrone.Core/Indexers/HDBits/HDBits.cs | 4 +- .../Indexers/HDBits/HDBitsApi.cs | 16 +++--- .../Indexers/HDBits/HDBitsParser.cs | 7 +-- .../Indexers/HDBits/HDBitsRequestGenerator.cs | 13 ++++- .../Indexers/HDBits/HDBitsSettings.cs | 55 +++++++++++++++---- src/NzbDrone.Core/Localization/Core/en.json | 6 ++ 6 files changed, 72 insertions(+), 29 deletions(-) diff --git a/src/NzbDrone.Core/Indexers/HDBits/HDBits.cs b/src/NzbDrone.Core/Indexers/HDBits/HDBits.cs index e7a200bf9..2ac2cfb4b 100644 --- a/src/NzbDrone.Core/Indexers/HDBits/HDBits.cs +++ b/src/NzbDrone.Core/Indexers/HDBits/HDBits.cs @@ -12,7 +12,7 @@ namespace NzbDrone.Core.Indexers.HDBits public override DownloadProtocol Protocol => DownloadProtocol.Torrent; public override bool SupportsRss => true; public override bool SupportsSearch => true; - public override int PageSize => 30; + public override int PageSize => 100; public HDBits(IHttpClient httpClient, IIndexerStatusService indexerStatusService, IConfigService configService, IParsingService parsingService, Logger logger, ILocalizationService localizationService) : base(httpClient, indexerStatusService, configService, parsingService, logger, localizationService) @@ -21,7 +21,7 @@ namespace NzbDrone.Core.Indexers.HDBits public override IIndexerRequestGenerator GetRequestGenerator() { - return new HDBitsRequestGenerator() { Settings = Settings }; + return new HDBitsRequestGenerator { Settings = Settings }; } public override IParseIndexerResponse GetParser() diff --git a/src/NzbDrone.Core/Indexers/HDBits/HDBitsApi.cs b/src/NzbDrone.Core/Indexers/HDBits/HDBitsApi.cs index 9bb6d624b..75c360599 100644 --- a/src/NzbDrone.Core/Indexers/HDBits/HDBitsApi.cs +++ b/src/NzbDrone.Core/Indexers/HDBits/HDBitsApi.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using Newtonsoft.Json; namespace NzbDrone.Core.Indexers.HDBits @@ -7,20 +8,16 @@ namespace NzbDrone.Core.Indexers.HDBits { [JsonProperty(Required = Required.Always)] public string Username { get; set; } + [JsonProperty(Required = Required.Always)] public string Passkey { get; set; } public string Hash { get; set; } - public string Search { get; set; } - - public int[] Category { get; set; } - - public int[] Codec { get; set; } - - public int[] Medium { get; set; } - - public int[] Origin { get; set; } + public IEnumerable<int> Category { get; set; } + public IEnumerable<int> Codec { get; set; } + public IEnumerable<int> Medium { get; set; } + public int? Origin { get; set; } [JsonProperty(PropertyName = "imdb")] public ImdbInfo ImdbInfo { get; set; } @@ -33,6 +30,7 @@ namespace NzbDrone.Core.Indexers.HDBits [JsonProperty(PropertyName = "snatched_only")] public bool? SnatchedOnly { get; set; } + public int? Limit { get; set; } public int? Page { get; set; } diff --git a/src/NzbDrone.Core/Indexers/HDBits/HDBitsParser.cs b/src/NzbDrone.Core/Indexers/HDBits/HDBitsParser.cs index 06f986b60..4bff86c7c 100644 --- a/src/NzbDrone.Core/Indexers/HDBits/HDBitsParser.cs +++ b/src/NzbDrone.Core/Indexers/HDBits/HDBitsParser.cs @@ -38,8 +38,7 @@ namespace NzbDrone.Core.Indexers.HDBits jsonResponse.Message ?? string.Empty); } - var responseData = jsonResponse.Data as JArray; - if (responseData == null) + if (jsonResponse.Data is not JArray responseData) { throw new IndexerException(indexerResponse, "Indexer API call response missing result data"); @@ -50,9 +49,9 @@ namespace NzbDrone.Core.Indexers.HDBits foreach (var result in queryResults) { var id = result.Id; - torrentInfos.Add(new TorrentInfo() + torrentInfos.Add(new TorrentInfo { - Guid = string.Format("HDBits-{0}", id), + Guid = $"HDBits-{id}", Title = result.Name, Size = result.Size, InfoHash = result.Hash, diff --git a/src/NzbDrone.Core/Indexers/HDBits/HDBitsRequestGenerator.cs b/src/NzbDrone.Core/Indexers/HDBits/HDBitsRequestGenerator.cs index 315376b72..413404d7a 100644 --- a/src/NzbDrone.Core/Indexers/HDBits/HDBitsRequestGenerator.cs +++ b/src/NzbDrone.Core/Indexers/HDBits/HDBitsRequestGenerator.cs @@ -72,7 +72,7 @@ namespace NzbDrone.Core.Indexers.HDBits if (TryAddSearchParameters(query, searchCriteria)) { - query.Search = string.Format("{0:yyyy}-{0:MM}-{0:dd}", searchCriteria.AirDate); + query.Search = searchCriteria.AirDate.ToString("yyyy-MM-dd"); pageableRequests.Add(GetRequest(query)); } @@ -87,7 +87,7 @@ namespace NzbDrone.Core.Indexers.HDBits if (TryAddSearchParameters(query, searchCriteria)) { - query.Search = string.Format("{0}-", searchCriteria.Year); + query.Search = $"{searchCriteria.Year}-"; pageableRequests.Add(GetRequest(query)); } @@ -140,8 +140,9 @@ namespace NzbDrone.Core.Indexers.HDBits { if (searchCriteria.Series.TvdbId != 0) { - query.TvdbInfo = query.TvdbInfo ?? new TvdbInfo(); + query.TvdbInfo ??= new TvdbInfo(); query.TvdbInfo.Id = searchCriteria.Series.TvdbId; + return true; } @@ -162,6 +163,12 @@ namespace NzbDrone.Core.Indexers.HDBits query.Username = Settings.Username; query.Passkey = Settings.ApiKey; + query.Category = Settings.Categories.ToArray(); + query.Codec = Settings.Codecs.ToArray(); + query.Medium = Settings.Mediums.ToArray(); + + query.Limit = 100; + request.SetContent(query.ToJson()); request.ContentSummary = query.ToJson(Formatting.None); diff --git a/src/NzbDrone.Core/Indexers/HDBits/HDBitsSettings.cs b/src/NzbDrone.Core/Indexers/HDBits/HDBitsSettings.cs index 0b573850c..c6525771d 100644 --- a/src/NzbDrone.Core/Indexers/HDBits/HDBitsSettings.cs +++ b/src/NzbDrone.Core/Indexers/HDBits/HDBitsSettings.cs @@ -1,3 +1,5 @@ +using System; +using System.Collections.Generic; using FluentValidation; using NzbDrone.Core.Annotations; using NzbDrone.Core.Validation; @@ -17,28 +19,41 @@ namespace NzbDrone.Core.Indexers.HDBits public class HDBitsSettings : ITorrentIndexerSettings { - private static readonly HDBitsSettingsValidator Validator = new HDBitsSettingsValidator(); + private static readonly HDBitsSettingsValidator Validator = new (); public HDBitsSettings() { BaseUrl = "https://hdbits.org"; MinimumSeeders = IndexerDefaults.MINIMUM_SEEDERS; + + Categories = new[] { (int)HdBitsCategory.Tv, (int)HdBitsCategory.Documentary }; + Codecs = Array.Empty<int>(); + Mediums = Array.Empty<int>(); } - [FieldDefinition(0, Label = "Username", Privacy = PrivacyLevel.UserName)] - public string Username { get; set; } - - [FieldDefinition(1, Label = "ApiKey", Privacy = PrivacyLevel.ApiKey)] - public string ApiKey { get; set; } - - [FieldDefinition(2, Label = "IndexerSettingsApiUrl", Advanced = true, HelpText = "IndexerSettingsApiUrlHelpText")] + [FieldDefinition(0, Label = "IndexerSettingsApiUrl", Advanced = true, HelpText = "IndexerSettingsApiUrlHelpText")] public string BaseUrl { get; set; } - [FieldDefinition(3, Type = FieldType.Number, Label = "IndexerSettingsMinimumSeeders", HelpText = "IndexerSettingsMinimumSeedersHelpText", Advanced = true)] + [FieldDefinition(1, Label = "Username", Privacy = PrivacyLevel.UserName)] + public string Username { get; set; } + + [FieldDefinition(2, Label = "ApiKey", Privacy = PrivacyLevel.ApiKey)] + public string ApiKey { get; set; } + + [FieldDefinition(3, Label = "IndexerHDBitsSettingsCategories", Type = FieldType.Select, SelectOptions = typeof(HdBitsCategory), HelpText = "IndexerHDBitsSettingsCategoriesHelpText")] + public IEnumerable<int> Categories { get; set; } + + [FieldDefinition(4, Label = "IndexerHDBitsSettingsCodecs", Type = FieldType.Select, SelectOptions = typeof(HdBitsCodec), Advanced = true, HelpText = "IndexerHDBitsSettingsCodecsHelpText")] + public IEnumerable<int> Codecs { get; set; } + + [FieldDefinition(5, Label = "IndexerHDBitsSettingsMediums", Type = FieldType.Select, SelectOptions = typeof(HdBitsMedium), Advanced = true, HelpText = "IndexerHDBitsSettingsMediumsHelpText")] + public IEnumerable<int> Mediums { get; set; } + + [FieldDefinition(6, Type = FieldType.Number, Label = "IndexerSettingsMinimumSeeders", HelpText = "IndexerSettingsMinimumSeedersHelpText", Advanced = true)] public int MinimumSeeders { get; set; } - [FieldDefinition(4)] - public SeedCriteriaSettings SeedCriteria { get; set; } = new SeedCriteriaSettings(); + [FieldDefinition(7)] + public SeedCriteriaSettings SeedCriteria { get; set; } = new (); public NzbDroneValidationResult Validate() { @@ -48,31 +63,49 @@ namespace NzbDrone.Core.Indexers.HDBits public enum HdBitsCategory { + [FieldOption(label: "Movie")] Movie = 1, + [FieldOption(label: "TV")] Tv = 2, + [FieldOption(label: "Documentary")] Documentary = 3, + [FieldOption(label: "Music")] Music = 4, + [FieldOption(label: "Sport")] Sport = 5, + [FieldOption(label: "Audio Track")] Audio = 6, + [FieldOption(label: "XXX")] Xxx = 7, + [FieldOption(label: "Misc/Demo")] MiscDemo = 8 } public enum HdBitsCodec { + [FieldOption(label: "H.264")] H264 = 1, + [FieldOption(label: "MPEG-2")] Mpeg2 = 2, + [FieldOption(label: "VC-1")] Vc1 = 3, + [FieldOption(label: "XviD")] Xvid = 4, + [FieldOption(label: "HEVC")] Hevc = 5 } public enum HdBitsMedium { + [FieldOption(label: "Blu-ray/HD DVD")] Bluray = 1, + [FieldOption(label: "Encode")] Encode = 3, + [FieldOption(label: "Capture")] Capture = 4, + [FieldOption(label: "Remux")] Remux = 5, + [FieldOption(label: "WEB-DL")] WebDl = 6 } } diff --git a/src/NzbDrone.Core/Localization/Core/en.json b/src/NzbDrone.Core/Localization/Core/en.json index df12a7431..de3b64ce2 100644 --- a/src/NzbDrone.Core/Localization/Core/en.json +++ b/src/NzbDrone.Core/Localization/Core/en.json @@ -770,6 +770,12 @@ "Indexer": "Indexer", "IndexerDownloadClientHealthCheckMessage": "Indexers with invalid download clients: {indexerNames}.", "IndexerDownloadClientHelpText": "Specify which download client is used for grabs from this indexer", + "IndexerHDBitsSettingsCategories": "Categories", + "IndexerHDBitsSettingsCategoriesHelpText": "If unspecified, all options are used.", + "IndexerHDBitsSettingsCodecs": "Codecs", + "IndexerHDBitsSettingsCodecsHelpText": "If unspecified, all options are used.", + "IndexerHDBitsSettingsMediums": "Mediums", + "IndexerHDBitsSettingsMediumsHelpText": "If unspecified, all options are used.", "IndexerIPTorrentsSettingsFeedUrl": "Feed URL", "IndexerIPTorrentsSettingsFeedUrlHelpText": "The full RSS feed url generated by IPTorrents, using only the categories you selected (HD, SD, x264, etc ...)", "IndexerJackettAllHealthCheckMessage": "Indexers using the unsupported Jackett 'all' endpoint: {indexerNames}", From ade40b72bc02d47845a719f2ae85edb2d41b6eaf Mon Sep 17 00:00:00 2001 From: Qstick <qstick@gmail.com> Date: Sat, 21 Oct 2023 21:57:59 -0500 Subject: [PATCH 114/136] New: Option to control whether new seasons get monitored automatically (cherry picked from commit b95a84f6612333d96fcdca083f9c39d96956f3f4) Closes #5083 --- ...iesMonitorNewItemsOptionsPopoverContent.js | 22 ++++++++ .../src/Components/Form/FormInputGroup.js | 4 ++ .../Form/MonitorNewItemsSelectInput.js | 50 +++++++++++++++++++ frontend/src/Helpers/Props/inputTypes.js | 2 + .../Series/Edit/EditSeriesModalContent.css | 4 ++ .../Edit/EditSeriesModalContent.css.d.ts | 1 + .../src/Series/Edit/EditSeriesModalContent.js | 31 +++++++++++- .../Edit/EditSeriesModalContentConnector.js | 1 + .../Select/Edit/EditSeriesModalContent.tsx | 24 +++++++++ .../ImportLists/EditImportListModalContent.js | 27 ++++++++++ frontend/src/Utilities/Series/getNewSeries.js | 2 + .../Series/monitorNewItemsOptions.js | 18 +++++++ .../TvTests/RefreshSeriesServiceFixture.cs | 10 ++-- .../Migration/200_monitor_new_items.cs | 15 ++++++ .../ImportLists/ImportListDefinition.cs | 1 + .../ImportLists/ImportListSyncService.cs | 1 + src/NzbDrone.Core/Localization/Core/en.json | 5 ++ src/NzbDrone.Core/Tv/MonitoringOptions.cs | 6 +++ src/NzbDrone.Core/Tv/RefreshEpisodeService.cs | 6 +-- src/NzbDrone.Core/Tv/Series.cs | 2 + .../ImportLists/ImportListResource.cs | 3 ++ .../Series/SeriesEditorController.cs | 5 ++ .../Series/SeriesEditorResource.cs | 1 + src/Sonarr.Api.V3/Series/SeriesResource.cs | 3 ++ 24 files changed, 236 insertions(+), 8 deletions(-) create mode 100644 frontend/src/AddSeries/SeriesMonitorNewItemsOptionsPopoverContent.js create mode 100644 frontend/src/Components/Form/MonitorNewItemsSelectInput.js create mode 100644 frontend/src/Utilities/Series/monitorNewItemsOptions.js create mode 100644 src/NzbDrone.Core/Datastore/Migration/200_monitor_new_items.cs diff --git a/frontend/src/AddSeries/SeriesMonitorNewItemsOptionsPopoverContent.js b/frontend/src/AddSeries/SeriesMonitorNewItemsOptionsPopoverContent.js new file mode 100644 index 000000000..164b75e5c --- /dev/null +++ b/frontend/src/AddSeries/SeriesMonitorNewItemsOptionsPopoverContent.js @@ -0,0 +1,22 @@ +import React from 'react'; +import DescriptionList from 'Components/DescriptionList/DescriptionList'; +import DescriptionListItem from 'Components/DescriptionList/DescriptionListItem'; +import translate from 'Utilities/String/translate'; + +function SeriesMonitorNewItemsOptionsPopoverContent() { + return ( + <DescriptionList> + <DescriptionListItem + title={translate('MonitorAllSeasons')} + data={translate('MonitorAllSeasonsDescription')} + /> + + <DescriptionListItem + title={translate('MonitorNone')} + data={translate('MonitorNoNewSeasonsDescription')} + /> + </DescriptionList> + ); +} + +export default SeriesMonitorNewItemsOptionsPopoverContent; diff --git a/frontend/src/Components/Form/FormInputGroup.js b/frontend/src/Components/Form/FormInputGroup.js index 6f3155f5b..49f08c90b 100644 --- a/frontend/src/Components/Form/FormInputGroup.js +++ b/frontend/src/Components/Form/FormInputGroup.js @@ -14,6 +14,7 @@ import FormInputHelpText from './FormInputHelpText'; import IndexerSelectInputConnector from './IndexerSelectInputConnector'; import KeyValueListInput from './KeyValueListInput'; import MonitorEpisodesSelectInput from './MonitorEpisodesSelectInput'; +import MonitorNewItemsSelectInput from './MonitorNewItemsSelectInput'; import NumberInput from './NumberInput'; import OAuthInputConnector from './OAuthInputConnector'; import PasswordInput from './PasswordInput'; @@ -49,6 +50,9 @@ function getComponent(type) { case inputTypes.MONITOR_EPISODES_SELECT: return MonitorEpisodesSelectInput; + case inputTypes.MONITOR_NEW_ITEMS_SELECT: + return MonitorNewItemsSelectInput; + case inputTypes.NUMBER: return NumberInput; diff --git a/frontend/src/Components/Form/MonitorNewItemsSelectInput.js b/frontend/src/Components/Form/MonitorNewItemsSelectInput.js new file mode 100644 index 000000000..c704e5c1f --- /dev/null +++ b/frontend/src/Components/Form/MonitorNewItemsSelectInput.js @@ -0,0 +1,50 @@ +import PropTypes from 'prop-types'; +import React from 'react'; +import monitorNewItemsOptions from 'Utilities/Series/monitorNewItemsOptions'; +import SelectInput from './SelectInput'; + +function MonitorNewItemsSelectInput(props) { + const { + includeNoChange, + includeMixed, + ...otherProps + } = props; + + const values = [...monitorNewItemsOptions]; + + if (includeNoChange) { + values.unshift({ + key: 'noChange', + value: 'No Change', + disabled: true + }); + } + + if (includeMixed) { + values.unshift({ + key: 'mixed', + value: '(Mixed)', + disabled: true + }); + } + + return ( + <SelectInput + values={values} + {...otherProps} + /> + ); +} + +MonitorNewItemsSelectInput.propTypes = { + includeNoChange: PropTypes.bool.isRequired, + includeMixed: PropTypes.bool.isRequired, + onChange: PropTypes.func.isRequired +}; + +MonitorNewItemsSelectInput.defaultProps = { + includeNoChange: false, + includeMixed: false +}; + +export default MonitorNewItemsSelectInput; diff --git a/frontend/src/Helpers/Props/inputTypes.js b/frontend/src/Helpers/Props/inputTypes.js index 575dc698a..126b45954 100644 --- a/frontend/src/Helpers/Props/inputTypes.js +++ b/frontend/src/Helpers/Props/inputTypes.js @@ -4,6 +4,7 @@ export const CHECK = 'check'; export const DEVICE = 'device'; export const KEY_VALUE_LIST = 'keyValueList'; export const MONITOR_EPISODES_SELECT = 'monitorEpisodesSelect'; +export const MONITOR_NEW_ITEMS_SELECT = 'monitorNewItemsSelect'; export const FLOAT = 'float'; export const NUMBER = 'number'; export const OAUTH = 'oauth'; @@ -31,6 +32,7 @@ export const all = [ DEVICE, KEY_VALUE_LIST, MONITOR_EPISODES_SELECT, + MONITOR_NEW_ITEMS_SELECT, FLOAT, NUMBER, OAUTH, diff --git a/frontend/src/Series/Edit/EditSeriesModalContent.css b/frontend/src/Series/Edit/EditSeriesModalContent.css index a2b6014df..fd7ddf093 100644 --- a/frontend/src/Series/Edit/EditSeriesModalContent.css +++ b/frontend/src/Series/Edit/EditSeriesModalContent.css @@ -3,3 +3,7 @@ margin-right: auto; } + +.labelIcon { + margin-left: 8px; +} diff --git a/frontend/src/Series/Edit/EditSeriesModalContent.css.d.ts b/frontend/src/Series/Edit/EditSeriesModalContent.css.d.ts index c5f0ef8a7..238343ae5 100644 --- a/frontend/src/Series/Edit/EditSeriesModalContent.css.d.ts +++ b/frontend/src/Series/Edit/EditSeriesModalContent.css.d.ts @@ -2,6 +2,7 @@ // Please do not change this file! interface CssExports { 'deleteButton': string; + 'labelIcon': string; } export const cssExports: CssExports; export default cssExports; diff --git a/frontend/src/Series/Edit/EditSeriesModalContent.js b/frontend/src/Series/Edit/EditSeriesModalContent.js index 2bc6ba202..6824b0f21 100644 --- a/frontend/src/Series/Edit/EditSeriesModalContent.js +++ b/frontend/src/Series/Edit/EditSeriesModalContent.js @@ -1,16 +1,19 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; +import SeriesMonitorNewItemsOptionsPopoverContent from 'AddSeries/SeriesMonitorNewItemsOptionsPopoverContent'; import Form from 'Components/Form/Form'; import FormGroup from 'Components/Form/FormGroup'; import FormInputGroup from 'Components/Form/FormInputGroup'; import FormLabel from 'Components/Form/FormLabel'; +import Icon from 'Components/Icon'; import Button from 'Components/Link/Button'; import SpinnerButton from 'Components/Link/SpinnerButton'; 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 { inputTypes, kinds } from 'Helpers/Props'; +import Popover from 'Components/Tooltip/Popover'; +import { icons, inputTypes, kinds, tooltipPositions } from 'Helpers/Props'; import MoveSeriesModal from 'Series/MoveSeries/MoveSeriesModal'; import translate from 'Utilities/String/translate'; import styles from './EditSeriesModalContent.css'; @@ -73,6 +76,7 @@ class EditSeriesModalContent extends Component { const { monitored, + monitorNewItems, seasonFolder, qualityProfileId, seriesType, @@ -100,6 +104,31 @@ class EditSeriesModalContent extends Component { /> </FormGroup> + <FormGroup> + <FormLabel> + {translate('MonitorNewSeasons')} + <Popover + anchor={ + <Icon + className={styles.labelIcon} + name={icons.INFO} + /> + } + title={translate('MonitorNewSeasons')} + body={<SeriesMonitorNewItemsOptionsPopoverContent />} + position={tooltipPositions.RIGHT} + /> + </FormLabel> + + <FormInputGroup + type={inputTypes.MONITOR_NEW_ITEMS_SELECT} + name="monitorNewItems" + helpText={translate('MonitorNewSeasonsHelpText')} + {...monitorNewItems} + onChange={onInputChange} + /> + </FormGroup> + <FormGroup> <FormLabel>{translate('UseSeasonFolder')}</FormLabel> diff --git a/frontend/src/Series/Edit/EditSeriesModalContentConnector.js b/frontend/src/Series/Edit/EditSeriesModalContentConnector.js index 0521f92df..b4a53685a 100644 --- a/frontend/src/Series/Edit/EditSeriesModalContentConnector.js +++ b/frontend/src/Series/Edit/EditSeriesModalContentConnector.js @@ -38,6 +38,7 @@ function createMapStateToProps() { const seriesSettings = _.pick(series, [ 'monitored', + 'monitorNewItems', 'seasonFolder', 'qualityProfileId', 'seriesType', diff --git a/frontend/src/Series/Index/Select/Edit/EditSeriesModalContent.tsx b/frontend/src/Series/Index/Select/Edit/EditSeriesModalContent.tsx index 522d3906d..27b54f95b 100644 --- a/frontend/src/Series/Index/Select/Edit/EditSeriesModalContent.tsx +++ b/frontend/src/Series/Index/Select/Edit/EditSeriesModalContent.tsx @@ -14,6 +14,7 @@ import styles from './EditSeriesModalContent.css'; interface SavePayload { monitored?: boolean; + monitorNewItems?: string; qualityProfileId?: number; seriesType?: string; seasonFolder?: boolean; @@ -77,6 +78,7 @@ function EditSeriesModalContent(props: EditSeriesModalContentProps) { const { seriesIds, onSavePress, onModalClose } = props; const [monitored, setMonitored] = useState(NO_CHANGE); + const [monitorNewItems, setMonitorNewItems] = useState(NO_CHANGE); const [qualityProfileId, setQualityProfileId] = useState<string | number>( NO_CHANGE ); @@ -95,6 +97,11 @@ function EditSeriesModalContent(props: EditSeriesModalContentProps) { payload.monitored = monitored === 'monitored'; } + if (monitorNewItems !== NO_CHANGE) { + hasChanges = true; + payload.monitorNewItems = monitorNewItems; + } + if (qualityProfileId !== NO_CHANGE) { hasChanges = true; payload.qualityProfileId = qualityProfileId as number; @@ -124,6 +131,7 @@ function EditSeriesModalContent(props: EditSeriesModalContentProps) { }, [ monitored, + monitorNewItems, qualityProfileId, seriesType, seasonFolder, @@ -139,6 +147,9 @@ function EditSeriesModalContent(props: EditSeriesModalContentProps) { case 'monitored': setMonitored(value); break; + case 'monitorNewItems': + setMonitorNewItems(value); + break; case 'qualityProfileId': setQualityProfileId(value); break; @@ -199,6 +210,19 @@ function EditSeriesModalContent(props: EditSeriesModalContentProps) { /> </FormGroup> + <FormGroup> + <FormLabel>{translate('MonitorNewItems')}</FormLabel> + + <FormInputGroup + type={inputTypes.MONITOR_NEW_ITEMS_SELECT} + name="monitorNewItems" + value={monitorNewItems} + includeNoChange={true} + includeNoChangeDisabled={false} + onChange={onInputChange} + /> + </FormGroup> + <FormGroup> <FormLabel>{translate('QualityProfile')}</FormLabel> diff --git a/frontend/src/Settings/ImportLists/ImportLists/EditImportListModalContent.js b/frontend/src/Settings/ImportLists/ImportLists/EditImportListModalContent.js index b1bccdd84..1dc9c8def 100644 --- a/frontend/src/Settings/ImportLists/ImportLists/EditImportListModalContent.js +++ b/frontend/src/Settings/ImportLists/ImportLists/EditImportListModalContent.js @@ -1,6 +1,7 @@ import PropTypes from 'prop-types'; import React from 'react'; import SeriesMonitoringOptionsPopoverContent from 'AddSeries/SeriesMonitoringOptionsPopoverContent'; +import SeriesMonitorNewItemsOptionsPopoverContent from 'AddSeries/SeriesMonitorNewItemsOptionsPopoverContent'; import SeriesTypePopoverContent from 'AddSeries/SeriesTypePopoverContent'; import Alert from 'Components/Alert'; import Form from 'Components/Form/Form'; @@ -50,6 +51,7 @@ function EditImportListModalContent(props) { minRefreshInterval, shouldMonitor, rootFolderPath, + monitorNewItems, qualityProfileId, seriesType, seasonFolder, @@ -151,6 +153,31 @@ function EditImportListModalContent(props) { /> </FormGroup> + <FormGroup> + <FormLabel> + {translate('MonitorNewSeasons')} + <Popover + anchor={ + <Icon + className={styles.labelIcon} + name={icons.INFO} + /> + } + title={translate('MonitorNewSeasons')} + body={<SeriesMonitorNewItemsOptionsPopoverContent />} + position={tooltipPositions.RIGHT} + /> + </FormLabel> + + <FormInputGroup + type={inputTypes.MONITOR_NEW_ITEMS_SELECT} + name="monitorNewItems" + helpText={translate('MonitorNewSeasonsHelpText')} + {...monitorNewItems} + onChange={onInputChange} + /> + </FormGroup> + <FormGroup> <FormLabel>{translate('RootFolder')}</FormLabel> diff --git a/frontend/src/Utilities/Series/getNewSeries.js b/frontend/src/Utilities/Series/getNewSeries.js index 6e4de0c74..0acbe93d7 100644 --- a/frontend/src/Utilities/Series/getNewSeries.js +++ b/frontend/src/Utilities/Series/getNewSeries.js @@ -3,6 +3,7 @@ function getNewSeries(series, payload) { const { rootFolderPath, monitor, + monitorNewItems, qualityProfileId, seriesType, seasonFolder, @@ -19,6 +20,7 @@ function getNewSeries(series, payload) { series.addOptions = addOptions; series.monitored = true; + series.monitorNewItems = monitorNewItems; series.qualityProfileId = qualityProfileId; series.rootFolderPath = rootFolderPath; series.seriesType = seriesType; diff --git a/frontend/src/Utilities/Series/monitorNewItemsOptions.js b/frontend/src/Utilities/Series/monitorNewItemsOptions.js new file mode 100644 index 000000000..184d60397 --- /dev/null +++ b/frontend/src/Utilities/Series/monitorNewItemsOptions.js @@ -0,0 +1,18 @@ +import translate from 'Utilities/String/translate'; + +const monitorNewItemsOptions = [ + { + key: 'all', + get value() { + return translate('MonitorAllSeasons'); + } + }, + { + key: 'none', + get value() { + return translate('MonitorNone'); + } + } +]; + +export default monitorNewItemsOptions; diff --git a/src/NzbDrone.Core.Test/TvTests/RefreshSeriesServiceFixture.cs b/src/NzbDrone.Core.Test/TvTests/RefreshSeriesServiceFixture.cs index 7a154d5fa..429eef2d2 100644 --- a/src/NzbDrone.Core.Test/TvTests/RefreshSeriesServiceFixture.cs +++ b/src/NzbDrone.Core.Test/TvTests/RefreshSeriesServiceFixture.cs @@ -58,9 +58,10 @@ namespace NzbDrone.Core.Test.TvTests } [Test] - public void should_monitor_new_seasons_automatically_if_series_is_monitored() + public void should_monitor_new_seasons_automatically_if_monitor_new_items_is_all() { - _series.Monitored = true; + _series.MonitorNewItems = NewItemMonitorTypes.All; + var newSeriesInfo = _series.JsonClone(); newSeriesInfo.Seasons.Add(Builder<Season>.CreateNew() .With(s => s.SeasonNumber = 2) @@ -75,9 +76,10 @@ namespace NzbDrone.Core.Test.TvTests } [Test] - public void should_not_monitor_new_seasons_automatically_if_series_is_not_monitored() + public void should_not_monitor_new_seasons_automatically_if_monitor_new_items_is_none() { - _series.Monitored = false; + _series.MonitorNewItems = NewItemMonitorTypes.None; + var newSeriesInfo = _series.JsonClone(); newSeriesInfo.Seasons.Add(Builder<Season>.CreateNew() .With(s => s.SeasonNumber = 2) diff --git a/src/NzbDrone.Core/Datastore/Migration/200_monitor_new_items.cs b/src/NzbDrone.Core/Datastore/Migration/200_monitor_new_items.cs new file mode 100644 index 000000000..d2961c06c --- /dev/null +++ b/src/NzbDrone.Core/Datastore/Migration/200_monitor_new_items.cs @@ -0,0 +1,15 @@ +using FluentMigrator; +using NzbDrone.Core.Datastore.Migration.Framework; + +namespace NzbDrone.Core.Datastore.Migration +{ + [Migration(200)] + public class AddNewItemMonitorType : NzbDroneMigrationBase + { + protected override void MainDbUpgrade() + { + Alter.Table("Series").AddColumn("MonitorNewItems").AsInt32().WithDefaultValue(0); + Alter.Table("ImportLists").AddColumn("MonitorNewItems").AsInt32().WithDefaultValue(0); + } + } +} diff --git a/src/NzbDrone.Core/ImportLists/ImportListDefinition.cs b/src/NzbDrone.Core/ImportLists/ImportListDefinition.cs index 68600dec0..31b99c23c 100644 --- a/src/NzbDrone.Core/ImportLists/ImportListDefinition.cs +++ b/src/NzbDrone.Core/ImportLists/ImportListDefinition.cs @@ -9,6 +9,7 @@ namespace NzbDrone.Core.ImportLists public bool EnableAutomaticAdd { get; set; } public bool SearchForMissingEpisodes { get; set; } public MonitorTypes ShouldMonitor { get; set; } + public NewItemMonitorTypes MonitorNewItems { get; set; } public int QualityProfileId { get; set; } public SeriesTypes SeriesType { get; set; } public bool SeasonFolder { get; set; } diff --git a/src/NzbDrone.Core/ImportLists/ImportListSyncService.cs b/src/NzbDrone.Core/ImportLists/ImportListSyncService.cs index ef735e75f..12109a94a 100644 --- a/src/NzbDrone.Core/ImportLists/ImportListSyncService.cs +++ b/src/NzbDrone.Core/ImportLists/ImportListSyncService.cs @@ -173,6 +173,7 @@ namespace NzbDrone.Core.ImportLists Title = item.Title, Year = item.Year, Monitored = monitored, + MonitorNewItems = importList.MonitorNewItems, RootFolderPath = importList.RootFolderPath, QualityProfileId = importList.QualityProfileId, SeriesType = importList.SeriesType, diff --git a/src/NzbDrone.Core/Localization/Core/en.json b/src/NzbDrone.Core/Localization/Core/en.json index de3b64ce2..5623983fd 100644 --- a/src/NzbDrone.Core/Localization/Core/en.json +++ b/src/NzbDrone.Core/Localization/Core/en.json @@ -976,6 +976,8 @@ "Monitor": "Monitor", "MonitorAllEpisodes": "All Episodes", "MonitorAllEpisodesDescription": "Monitor all episodes except specials", + "MonitorAllSeasons": "All Seasons", + "MonitorAllSeasonsDescription": "Monitor all new seasons automatically", "MonitorExistingEpisodes": "Existing Episodes", "MonitorExistingEpisodesDescription": "Monitor episodes that have files or have not aired yet", "MonitorFirstSeason": "First Season", @@ -986,6 +988,9 @@ "MonitorLatestSeasonDescription": "Monitor all episodes of the latest season that aired within the last 90 days and all future seasons", "MonitorMissingEpisodes": "Missing Episodes", "MonitorMissingEpisodesDescription": "Monitor episodes that do not have files or have not aired yet", + "MonitorNewSeasons": "Monitor New Seasons", + "MonitorNewSeasonsHelpText": "Which new seasons should be monitored automatically", + "MonitorNoNewSeasonsDescription": "Do not monitor any new seasons automatically", "MonitorNone": "None", "MonitorNoneDescription": "No episodes will be monitored", "MonitorPilotEpisode": "Pilot Episode", diff --git a/src/NzbDrone.Core/Tv/MonitoringOptions.cs b/src/NzbDrone.Core/Tv/MonitoringOptions.cs index 878a9f8e1..0fb4baeed 100644 --- a/src/NzbDrone.Core/Tv/MonitoringOptions.cs +++ b/src/NzbDrone.Core/Tv/MonitoringOptions.cs @@ -23,4 +23,10 @@ namespace NzbDrone.Core.Tv UnmonitorSpecials, None } + + public enum NewItemMonitorTypes + { + All, + None + } } diff --git a/src/NzbDrone.Core/Tv/RefreshEpisodeService.cs b/src/NzbDrone.Core/Tv/RefreshEpisodeService.cs index e2a3d41f1..893e326e1 100644 --- a/src/NzbDrone.Core/Tv/RefreshEpisodeService.cs +++ b/src/NzbDrone.Core/Tv/RefreshEpisodeService.cs @@ -63,7 +63,7 @@ namespace NzbDrone.Core.Tv else { episodeToUpdate = new Episode(); - episodeToUpdate.Monitored = GetMonitoredStatus(episode, seasons); + episodeToUpdate.Monitored = GetMonitoredStatus(episode, seasons, series); newList.Add(episodeToUpdate); } @@ -135,9 +135,9 @@ namespace NzbDrone.Core.Tv } } - private bool GetMonitoredStatus(Episode episode, IEnumerable<Season> seasons) + private bool GetMonitoredStatus(Episode episode, IEnumerable<Season> seasons, Series series) { - if (episode.EpisodeNumber == 0 && episode.SeasonNumber != 1) + if ((episode.EpisodeNumber == 0 && episode.SeasonNumber != 1) || series.MonitorNewItems == NewItemMonitorTypes.None) { return false; } diff --git a/src/NzbDrone.Core/Tv/Series.cs b/src/NzbDrone.Core/Tv/Series.cs index 9f557af42..6296d8500 100644 --- a/src/NzbDrone.Core/Tv/Series.cs +++ b/src/NzbDrone.Core/Tv/Series.cs @@ -30,6 +30,7 @@ namespace NzbDrone.Core.Tv public string Overview { get; set; } public string AirTime { get; set; } public bool Monitored { get; set; } + public NewItemMonitorTypes MonitorNewItems { get; set; } public int QualityProfileId { get; set; } public bool SeasonFolder { get; set; } public DateTime? LastInfoSync { get; set; } @@ -71,6 +72,7 @@ namespace NzbDrone.Core.Tv SeasonFolder = otherSeries.SeasonFolder; Monitored = otherSeries.Monitored; + MonitorNewItems = otherSeries.MonitorNewItems; SeriesType = otherSeries.SeriesType; RootFolderPath = otherSeries.RootFolderPath; diff --git a/src/Sonarr.Api.V3/ImportLists/ImportListResource.cs b/src/Sonarr.Api.V3/ImportLists/ImportListResource.cs index c3fa06be7..4e4fcac11 100644 --- a/src/Sonarr.Api.V3/ImportLists/ImportListResource.cs +++ b/src/Sonarr.Api.V3/ImportLists/ImportListResource.cs @@ -9,6 +9,7 @@ namespace Sonarr.Api.V3.ImportLists public bool EnableAutomaticAdd { get; set; } public bool SearchForMissingEpisodes { get; set; } public MonitorTypes ShouldMonitor { get; set; } + public NewItemMonitorTypes MonitorNewItems { get; set; } public string RootFolderPath { get; set; } public int QualityProfileId { get; set; } public SeriesTypes SeriesType { get; set; } @@ -32,6 +33,7 @@ namespace Sonarr.Api.V3.ImportLists resource.EnableAutomaticAdd = definition.EnableAutomaticAdd; resource.SearchForMissingEpisodes = definition.SearchForMissingEpisodes; resource.ShouldMonitor = definition.ShouldMonitor; + resource.MonitorNewItems = definition.MonitorNewItems; resource.RootFolderPath = definition.RootFolderPath; resource.QualityProfileId = definition.QualityProfileId; resource.SeriesType = definition.SeriesType; @@ -55,6 +57,7 @@ namespace Sonarr.Api.V3.ImportLists definition.EnableAutomaticAdd = resource.EnableAutomaticAdd; definition.SearchForMissingEpisodes = resource.SearchForMissingEpisodes; definition.ShouldMonitor = resource.ShouldMonitor; + definition.MonitorNewItems = resource.MonitorNewItems; definition.RootFolderPath = resource.RootFolderPath; definition.QualityProfileId = resource.QualityProfileId; definition.SeriesType = resource.SeriesType; diff --git a/src/Sonarr.Api.V3/Series/SeriesEditorController.cs b/src/Sonarr.Api.V3/Series/SeriesEditorController.cs index 99927887b..cea1220e1 100644 --- a/src/Sonarr.Api.V3/Series/SeriesEditorController.cs +++ b/src/Sonarr.Api.V3/Series/SeriesEditorController.cs @@ -34,6 +34,11 @@ namespace Sonarr.Api.V3.Series series.Monitored = resource.Monitored.Value; } + if (resource.MonitorNewItems.HasValue) + { + series.MonitorNewItems = resource.MonitorNewItems.Value; + } + if (resource.QualityProfileId.HasValue) { series.QualityProfileId = resource.QualityProfileId.Value; diff --git a/src/Sonarr.Api.V3/Series/SeriesEditorResource.cs b/src/Sonarr.Api.V3/Series/SeriesEditorResource.cs index 368251a93..c1d8a53fb 100644 --- a/src/Sonarr.Api.V3/Series/SeriesEditorResource.cs +++ b/src/Sonarr.Api.V3/Series/SeriesEditorResource.cs @@ -7,6 +7,7 @@ namespace Sonarr.Api.V3.Series { public List<int> SeriesIds { get; set; } public bool? Monitored { get; set; } + public NewItemMonitorTypes? MonitorNewItems { get; set; } public int? QualityProfileId { get; set; } public SeriesTypes? SeriesType { get; set; } public bool? SeasonFolder { get; set; } diff --git a/src/Sonarr.Api.V3/Series/SeriesResource.cs b/src/Sonarr.Api.V3/Series/SeriesResource.cs index 021518149..e1c87a434 100644 --- a/src/Sonarr.Api.V3/Series/SeriesResource.cs +++ b/src/Sonarr.Api.V3/Series/SeriesResource.cs @@ -44,6 +44,7 @@ namespace Sonarr.Api.V3.Series // Editing Only public bool SeasonFolder { get; set; } public bool Monitored { get; set; } + public NewItemMonitorTypes MonitorNewItems { get; set; } public bool UseSceneNumbering { get; set; } public int Runtime { get; set; } @@ -115,6 +116,7 @@ namespace Sonarr.Api.V3.Series SeasonFolder = model.SeasonFolder, Monitored = model.Monitored, + MonitorNewItems = model.MonitorNewItems, UseSceneNumbering = model.UseSceneNumbering, Runtime = model.Runtime, @@ -178,6 +180,7 @@ namespace Sonarr.Api.V3.Series SeasonFolder = resource.SeasonFolder, Monitored = resource.Monitored, + MonitorNewItems = resource.MonitorNewItems, UseSceneNumbering = resource.UseSceneNumbering, Runtime = resource.Runtime, From 5d86329c182343de3103a5d6cf941553da0f8246 Mon Sep 17 00:00:00 2001 From: Mark McDowall <mark@mcdowall.ca> Date: Sat, 18 Nov 2023 14:35:03 -0800 Subject: [PATCH 115/136] Last Season and Recent Episodes New: Added option to only monitor recent episodes Fixed: Last Season always monitors the whole season Closes #6175 --- .../SeriesMonitoringOptionsPopoverContent.js | 14 +++- .../src/Utilities/Series/monitorOptions.js | 10 ++- .../SetEpisodeMontitoredFixture.cs | 81 +++++++++++++++++-- src/NzbDrone.Core/Localization/Core/en.json | 7 +- .../Tv/EpisodeMonitoredService.cs | 33 ++++---- src/NzbDrone.Core/Tv/MonitoringOptions.cs | 6 ++ src/NzbDrone.Core/Tv/RefreshSeriesService.cs | 7 +- 7 files changed, 129 insertions(+), 29 deletions(-) diff --git a/frontend/src/AddSeries/SeriesMonitoringOptionsPopoverContent.js b/frontend/src/AddSeries/SeriesMonitoringOptionsPopoverContent.js index c1fa934d5..fab8ec3bb 100644 --- a/frontend/src/AddSeries/SeriesMonitoringOptionsPopoverContent.js +++ b/frontend/src/AddSeries/SeriesMonitoringOptionsPopoverContent.js @@ -26,14 +26,24 @@ function SeriesMonitoringOptionsPopoverContent() { data={translate('MonitorExistingEpisodesDescription')} /> + <DescriptionListItem + title={translate('MonitorRecentEpisodes')} + data={translate('MonitorRecentEpisodesDescription')} + /> + + <DescriptionListItem + title={translate('MonitorPilotEpisode')} + data={translate('MonitorPilotEpisodeDescription')} + /> + <DescriptionListItem title={translate('MonitorFirstSeason')} data={translate('MonitorFirstSeasonDescription')} /> <DescriptionListItem - title={translate('MonitorLatestSeason')} - data={translate('MonitorLatestSeasonDescription')} + title={translate('MonitorLastSeason')} + data={translate('MonitorLastSeasonDescription')} /> <DescriptionListItem diff --git a/frontend/src/Utilities/Series/monitorOptions.js b/frontend/src/Utilities/Series/monitorOptions.js index dbb715061..1d0543042 100644 --- a/frontend/src/Utilities/Series/monitorOptions.js +++ b/frontend/src/Utilities/Series/monitorOptions.js @@ -25,6 +25,12 @@ const monitorOptions = [ return translate('MonitorExistingEpisodes'); } }, + { + key: 'recent', + get value() { + return translate('MonitorRecentEpisodes'); + } + }, { key: 'pilot', get value() { @@ -38,9 +44,9 @@ const monitorOptions = [ } }, { - key: 'latestSeason', + key: 'lastSeason', get value() { - return translate('MonitorLatestSeason'); + return translate('MonitorLastSeason'); } }, { diff --git a/src/NzbDrone.Core.Test/TvTests/EpisodeMonitoredServiceTests/SetEpisodeMontitoredFixture.cs b/src/NzbDrone.Core.Test/TvTests/EpisodeMonitoredServiceTests/SetEpisodeMontitoredFixture.cs index 0ad93ce7e..23d43095a 100644 --- a/src/NzbDrone.Core.Test/TvTests/EpisodeMonitoredServiceTests/SetEpisodeMontitoredFixture.cs +++ b/src/NzbDrone.Core.Test/TvTests/EpisodeMonitoredServiceTests/SetEpisodeMontitoredFixture.cs @@ -202,7 +202,7 @@ namespace NzbDrone.Core.Test.TvTests.EpisodeMonitoredServiceTests } [Test] - public void should_not_monitor_season_when_all_episodes_are_monitored_except_latest_season() + public void should_not_monitor_season_when_all_episodes_are_monitored_except_last_season() { _series.Seasons = Builder<Season>.CreateListOfSize(2) .All() @@ -226,7 +226,7 @@ namespace NzbDrone.Core.Test.TvTests.EpisodeMonitoredServiceTests var monitoringOptions = new MonitoringOptions { - Monitor = MonitorTypes.LatestSeason + Monitor = MonitorTypes.LastSeason }; Subject.SetEpisodeMonitoredStatus(_series, monitoringOptions); @@ -264,13 +264,47 @@ namespace NzbDrone.Core.Test.TvTests.EpisodeMonitoredServiceTests } [Test] - public void should_not_monitor_latest_season_if_all_episodes_aired_more_than_90_days_ago() + public void should_monitor_last_season_if_all_episodes_aired_more_than_90_days_ago() + { + _series.Seasons = Builder<Season>.CreateListOfSize(2) + .All() + .With(n => n.Monitored = true) + .Build() + .ToList(); + + _episodes = Builder<Episode>.CreateListOfSize(5) + .All() + .With(e => e.SeasonNumber = 1) + .With(e => e.EpisodeFileId = 0) + .With(e => e.AirDateUtc = DateTime.UtcNow.AddDays(-200)) + .TheLast(2) + .With(e => e.SeasonNumber = 2) + .With(e => e.AirDateUtc = DateTime.UtcNow.AddDays(-100)) + .Build() + .ToList(); + + var monitoringOptions = new MonitoringOptions + { + Monitor = MonitorTypes.LastSeason + }; + + Subject.SetEpisodeMonitoredStatus(_series, monitoringOptions); + + VerifySeasonMonitored(n => n.SeasonNumber == 2); + VerifyMonitored(n => n.SeasonNumber == 2); + + VerifySeasonNotMonitored(n => n.SeasonNumber == 1); + VerifyNotMonitored(n => n.SeasonNumber == 1); + } + + [Test] + public void should_not_monitor_any_recent_episodes_if_all_episodes_aired_more_than_90_days_ago() { _episodes.ForEach(e => e.AirDateUtc = DateTime.UtcNow.AddDays(-100)); var monitoringOptions = new MonitoringOptions { - Monitor = MonitorTypes.LatestSeason + Monitor = MonitorTypes.Recent }; Subject.SetEpisodeMonitoredStatus(_series, monitoringOptions); @@ -279,6 +313,43 @@ namespace NzbDrone.Core.Test.TvTests.EpisodeMonitoredServiceTests .Verify(v => v.UpdateEpisodes(It.Is<List<Episode>>(l => l.All(e => !e.Monitored)))); } + [Test] + public void should_monitor_any_recent_and_future_episodes_if_all_episodes_aired_within_90_days() + { + _series.Seasons = Builder<Season>.CreateListOfSize(1) + .All() + .With(n => n.Monitored = true) + .Build() + .ToList(); + + _episodes = Builder<Episode>.CreateListOfSize(5) + .All() + .With(e => e.SeasonNumber = 1) + .With(e => e.EpisodeFileId = 0) + .With(e => e.AirDateUtc = DateTime.UtcNow.AddDays(-200)) + .TheLast(3) + .With(e => e.AirDateUtc = DateTime.UtcNow.AddDays(-5)) + .TheLast(1) + .With(e => e.AirDateUtc = DateTime.UtcNow.AddDays(30)) + .Build() + .ToList(); + + Mocker.GetMock<IEpisodeService>() + .Setup(s => s.GetEpisodeBySeries(It.IsAny<int>())) + .Returns(_episodes); + + var monitoringOptions = new MonitoringOptions + { + Monitor = MonitorTypes.Recent + }; + + Subject.SetEpisodeMonitoredStatus(_series, monitoringOptions); + + VerifySeasonMonitored(n => n.SeasonNumber == 1); + VerifyNotMonitored(n => n.AirDateUtc.HasValue && n.AirDateUtc.Value.Before(DateTime.UtcNow.AddDays(-90))); + VerifyMonitored(n => n.AirDateUtc.HasValue && n.AirDateUtc.Value.After(DateTime.UtcNow.AddDays(-90))); + } + [Test] public void should_monitor_latest_season_if_some_episodes_have_aired() { @@ -302,7 +373,7 @@ namespace NzbDrone.Core.Test.TvTests.EpisodeMonitoredServiceTests var monitoringOptions = new MonitoringOptions { - Monitor = MonitorTypes.LatestSeason + Monitor = MonitorTypes.LastSeason }; Subject.SetEpisodeMonitoredStatus(_series, monitoringOptions); diff --git a/src/NzbDrone.Core/Localization/Core/en.json b/src/NzbDrone.Core/Localization/Core/en.json index 5623983fd..b61b045c3 100644 --- a/src/NzbDrone.Core/Localization/Core/en.json +++ b/src/NzbDrone.Core/Localization/Core/en.json @@ -984,8 +984,8 @@ "MonitorFirstSeasonDescription": "Monitor all episodes of the first season. All other seasons will be ignored", "MonitorFutureEpisodes": "Future Episodes", "MonitorFutureEpisodesDescription": "Monitor episodes that have not aired yet", - "MonitorLatestSeason": "Latest Season", - "MonitorLatestSeasonDescription": "Monitor all episodes of the latest season that aired within the last 90 days and all future seasons", + "MonitorLastSeason": "Last Season", + "MonitorLastSeasonDescription": "Monitor all episodes of the last season", "MonitorMissingEpisodes": "Missing Episodes", "MonitorMissingEpisodesDescription": "Monitor episodes that do not have files or have not aired yet", "MonitorNewSeasons": "Monitor New Seasons", @@ -994,6 +994,9 @@ "MonitorNone": "None", "MonitorNoneDescription": "No episodes will be monitored", "MonitorPilotEpisode": "Pilot Episode", + "MonitorPilotEpisodeDescription": "Only monitor the first episode of the first season", + "MonitorRecentEpisodes": "Recent Episodes", + "MonitorRecentEpisodesDescription": "Monitor episodes aired within the last 90 days and future episodes", "MonitorSelected": "Monitor Selected", "MonitorSeries": "Monitor Series", "MonitorSpecials": "Monitor Specials", diff --git a/src/NzbDrone.Core/Tv/EpisodeMonitoredService.cs b/src/NzbDrone.Core/Tv/EpisodeMonitoredService.cs index c0351457a..41cdbcfc2 100644 --- a/src/NzbDrone.Core/Tv/EpisodeMonitoredService.cs +++ b/src/NzbDrone.Core/Tv/EpisodeMonitoredService.cs @@ -83,23 +83,27 @@ namespace NzbDrone.Core.Tv break; + case MonitorTypes.LastSeason: + #pragma warning disable CS0612 case MonitorTypes.LatestSeason: - if (episodes.Where(e => e.SeasonNumber == lastSeason) - .All(e => e.AirDateUtc.HasValue && - e.AirDateUtc.Value.Before(DateTime.UtcNow) && - !e.AirDateUtc.Value.InLastDays(90))) - { - _logger.Debug("[{0}] Unmonitoring all episodes because latest season aired more than 90 days ago", series.Title); - ToggleEpisodesMonitoredState(episodes, e => false); - break; - } - + #pragma warning restore CS0612 _logger.Debug("[{0}] Monitoring latest season episodes", series.Title); ToggleEpisodesMonitoredState(episodes, e => e.SeasonNumber > 0 && e.SeasonNumber == lastSeason); break; + case MonitorTypes.Recent: + _logger.Debug("[{0}] Monitoring recent and future episodes", series.Title); + + ToggleEpisodesMonitoredState(episodes, e => e.SeasonNumber > 0 && + (!e.AirDateUtc.HasValue || ( + e.AirDateUtc.Value.Before(DateTime.UtcNow) && + e.AirDateUtc.Value.InLastDays(90)) + || e.AirDateUtc.Value.After(DateTime.UtcNow))); + + break; + case MonitorTypes.MonitorSpecials: _logger.Debug("[{0}] Monitoring special episodes", series.Title); ToggleEpisodesMonitoredState(episodes.Where(e => e.SeasonNumber == 0), true); @@ -128,15 +132,14 @@ namespace NzbDrone.Core.Tv { var seasonNumber = season.SeasonNumber; - // Monitor the season when: + // Monitor the last season when: // - Not specials // - The latest season - // - Not only supposed to monitor the first season + // - Set to monitor all or future episodes if (seasonNumber > 0 && seasonNumber == lastSeason && - monitoringOptions.Monitor != MonitorTypes.FirstSeason && - monitoringOptions.Monitor != MonitorTypes.Pilot && - monitoringOptions.Monitor != MonitorTypes.None) + (monitoringOptions.Monitor == MonitorTypes.All || + monitoringOptions.Monitor == MonitorTypes.Future)) { season.Monitored = true; } diff --git a/src/NzbDrone.Core/Tv/MonitoringOptions.cs b/src/NzbDrone.Core/Tv/MonitoringOptions.cs index 0fb4baeed..b49a5ee6a 100644 --- a/src/NzbDrone.Core/Tv/MonitoringOptions.cs +++ b/src/NzbDrone.Core/Tv/MonitoringOptions.cs @@ -1,3 +1,4 @@ +using System; using NzbDrone.Core.Datastore; namespace NzbDrone.Core.Tv @@ -17,8 +18,13 @@ namespace NzbDrone.Core.Tv Missing, Existing, FirstSeason, + LastSeason, + + [Obsolete] LatestSeason, + Pilot, + Recent, MonitorSpecials, UnmonitorSpecials, None diff --git a/src/NzbDrone.Core/Tv/RefreshSeriesService.cs b/src/NzbDrone.Core/Tv/RefreshSeriesService.cs index 3cd0ab6b3..41a3b5cae 100644 --- a/src/NzbDrone.Core/Tv/RefreshSeriesService.cs +++ b/src/NzbDrone.Core/Tv/RefreshSeriesService.cs @@ -139,7 +139,6 @@ namespace NzbDrone.Core.Tv { var existingSeason = series.Seasons.FirstOrDefault(s => s.SeasonNumber == season.SeasonNumber); - // Todo: Should this should use the previous season's monitored state? if (existingSeason == null) { if (season.SeasonNumber == 0) @@ -149,8 +148,10 @@ namespace NzbDrone.Core.Tv continue; } - _logger.Debug("New season ({0}) for series: [{1}] {2}, setting monitored to {3}", season.SeasonNumber, series.TvdbId, series.Title, series.Monitored.ToString().ToLowerInvariant()); - season.Monitored = series.Monitored; + var monitorNewSeasons = series.MonitorNewItems == NewItemMonitorTypes.All; + + _logger.Debug("New season ({0}) for series: [{1}] {2}, setting monitored to {3}", season.SeasonNumber, series.TvdbId, series.Title, monitorNewSeasons.ToString().ToLowerInvariant()); + season.Monitored = monitorNewSeasons; } else { From 3541cd7ba877fb785c7f97123745abf51162eb8e Mon Sep 17 00:00:00 2001 From: Mark McDowall <mark@mcdowall.ca> Date: Sun, 29 Oct 2023 11:23:23 -0700 Subject: [PATCH 116/136] Fixed: Blocklisting torrents from indexers that do not provide torrent hash Closes #6108 --- .../Blocklisting/BlocklistService.cs | 7 +++ .../Download/Clients/Aria2/Aria2.cs | 8 ++-- .../Clients/Blackhole/TorrentBlackhole.cs | 8 ++-- .../Download/Clients/Deluge/Deluge.cs | 8 ++-- .../DownloadStation/TorrentDownloadStation.cs | 8 ++-- .../Download/Clients/Flood/Flood.cs | 8 ++-- .../FreeboxDownload/TorrentFreeboxDownload.cs | 10 ++-- .../Download/Clients/Hadouken/Hadouken.cs | 8 ++-- .../Clients/NzbVortex/NzbVortexProxy.cs | 2 +- .../Clients/QBittorrent/QBittorrent.cs | 8 ++-- .../Clients/Transmission/Transmission.cs | 10 ++-- .../Clients/Transmission/TransmissionBase.cs | 8 ++-- .../Download/Clients/Vuze/Vuze.cs | 8 ++-- .../Download/Clients/rTorrent/RTorrent.cs | 8 ++-- .../Download/Clients/uTorrent/UTorrent.cs | 8 ++-- src/NzbDrone.Core/Download/DownloadService.cs | 7 ++- .../Download/FailedDownloadService.cs | 2 +- .../Download/TorrentClientBase.cs | 46 ++++++++++++++++--- .../Exceptions/ReleaseBlockedException.cs | 28 +++++++++++ src/NzbDrone.Core/History/HistoryService.cs | 4 +- .../BroadcastheNet/BroadcastheNetSettings.cs | 3 ++ .../Indexers/FileList/FileListSettings.cs | 3 ++ .../Indexers/HDBits/HDBitsSettings.cs | 3 ++ .../Indexers/IPTorrents/IPTorrentsSettings.cs | 3 ++ .../Indexers/ITorrentIndexerSettings.cs | 1 + .../Indexers/Nyaa/NyaaSettings.cs | 3 ++ .../TorrentRss/TorrentRssIndexerSettings.cs | 3 ++ .../Torrentleech/TorrentleechSettings.cs | 3 ++ .../Indexers/Torznab/TorznabSettings.cs | 3 ++ 29 files changed, 176 insertions(+), 53 deletions(-) create mode 100644 src/NzbDrone.Core/Exceptions/ReleaseBlockedException.cs diff --git a/src/NzbDrone.Core/Blocklisting/BlocklistService.cs b/src/NzbDrone.Core/Blocklisting/BlocklistService.cs index 93203cc81..808e3dff5 100644 --- a/src/NzbDrone.Core/Blocklisting/BlocklistService.cs +++ b/src/NzbDrone.Core/Blocklisting/BlocklistService.cs @@ -15,6 +15,7 @@ namespace NzbDrone.Core.Blocklisting public interface IBlocklistService { bool Blocklisted(int seriesId, ReleaseInfo release); + bool BlocklistedTorrentHash(int seriesId, string hash); PagingSpec<Blocklist> Paged(PagingSpec<Blocklist> pagingSpec); void Block(RemoteEpisode remoteEpisode, string message); void Delete(int id); @@ -61,6 +62,12 @@ namespace NzbDrone.Core.Blocklisting .Any(b => SameNzb(b, release)); } + public bool BlocklistedTorrentHash(int seriesId, string hash) + { + return _blocklistRepository.BlocklistedByTorrentInfoHash(seriesId, hash).Any(b => + b.TorrentInfoHash.Equals(hash, StringComparison.InvariantCultureIgnoreCase)); + } + public PagingSpec<Blocklist> Paged(PagingSpec<Blocklist> pagingSpec) { return _blocklistRepository.GetPaged(pagingSpec); diff --git a/src/NzbDrone.Core/Download/Clients/Aria2/Aria2.cs b/src/NzbDrone.Core/Download/Clients/Aria2/Aria2.cs index 223af1286..621b8937e 100644 --- a/src/NzbDrone.Core/Download/Clients/Aria2/Aria2.cs +++ b/src/NzbDrone.Core/Download/Clients/Aria2/Aria2.cs @@ -7,6 +7,7 @@ using NLog; using NzbDrone.Common.Disk; using NzbDrone.Common.Extensions; using NzbDrone.Common.Http; +using NzbDrone.Core.Blocklisting; using NzbDrone.Core.Configuration; using NzbDrone.Core.Localization; using NzbDrone.Core.MediaFiles.TorrentInfo; @@ -28,9 +29,10 @@ namespace NzbDrone.Core.Download.Clients.Aria2 IConfigService configService, IDiskProvider diskProvider, IRemotePathMappingService remotePathMappingService, - Logger logger, - ILocalizationService localizationService) - : base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, logger, localizationService) + ILocalizationService localizationService, + IBlocklistService blocklistService, + Logger logger) + : base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, localizationService, blocklistService, logger) { _proxy = proxy; } diff --git a/src/NzbDrone.Core/Download/Clients/Blackhole/TorrentBlackhole.cs b/src/NzbDrone.Core/Download/Clients/Blackhole/TorrentBlackhole.cs index 62a9a4255..282ededa1 100644 --- a/src/NzbDrone.Core/Download/Clients/Blackhole/TorrentBlackhole.cs +++ b/src/NzbDrone.Core/Download/Clients/Blackhole/TorrentBlackhole.cs @@ -7,6 +7,7 @@ using NLog; using NzbDrone.Common.Disk; using NzbDrone.Common.Extensions; using NzbDrone.Common.Http; +using NzbDrone.Core.Blocklisting; using NzbDrone.Core.Configuration; using NzbDrone.Core.Localization; using NzbDrone.Core.MediaFiles.TorrentInfo; @@ -30,9 +31,10 @@ namespace NzbDrone.Core.Download.Clients.Blackhole IConfigService configService, IDiskProvider diskProvider, IRemotePathMappingService remotePathMappingService, - Logger logger, - ILocalizationService localizationService) - : base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, logger, localizationService) + ILocalizationService localizationService, + IBlocklistService blocklistService, + Logger logger) + : base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, localizationService, blocklistService, logger) { _scanWatchFolder = scanWatchFolder; diff --git a/src/NzbDrone.Core/Download/Clients/Deluge/Deluge.cs b/src/NzbDrone.Core/Download/Clients/Deluge/Deluge.cs index 725c86521..10716c699 100644 --- a/src/NzbDrone.Core/Download/Clients/Deluge/Deluge.cs +++ b/src/NzbDrone.Core/Download/Clients/Deluge/Deluge.cs @@ -7,6 +7,7 @@ using NLog; using NzbDrone.Common.Disk; using NzbDrone.Common.Extensions; using NzbDrone.Common.Http; +using NzbDrone.Core.Blocklisting; using NzbDrone.Core.Configuration; using NzbDrone.Core.Localization; using NzbDrone.Core.MediaFiles.TorrentInfo; @@ -26,9 +27,10 @@ namespace NzbDrone.Core.Download.Clients.Deluge IConfigService configService, IDiskProvider diskProvider, IRemotePathMappingService remotePathMappingService, - Logger logger, - ILocalizationService localizationService) - : base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, logger, localizationService) + ILocalizationService localizationService, + IBlocklistService blocklistService, + Logger logger) + : base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, localizationService, blocklistService, logger) { _proxy = proxy; } diff --git a/src/NzbDrone.Core/Download/Clients/DownloadStation/TorrentDownloadStation.cs b/src/NzbDrone.Core/Download/Clients/DownloadStation/TorrentDownloadStation.cs index 72b3ebbcb..612be692d 100644 --- a/src/NzbDrone.Core/Download/Clients/DownloadStation/TorrentDownloadStation.cs +++ b/src/NzbDrone.Core/Download/Clients/DownloadStation/TorrentDownloadStation.cs @@ -8,6 +8,7 @@ using NLog; using NzbDrone.Common.Disk; using NzbDrone.Common.Extensions; using NzbDrone.Common.Http; +using NzbDrone.Core.Blocklisting; using NzbDrone.Core.Configuration; using NzbDrone.Core.Download.Clients.DownloadStation.Proxies; using NzbDrone.Core.Localization; @@ -37,9 +38,10 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation IConfigService configService, IDiskProvider diskProvider, IRemotePathMappingService remotePathMappingService, - Logger logger, - ILocalizationService localizationService) - : base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, logger, localizationService) + ILocalizationService localizationService, + IBlocklistService blocklistService, + Logger logger) + : base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, localizationService, blocklistService, logger) { _dsInfoProxy = dsInfoProxy; _dsTaskProxySelector = dsTaskProxySelector; diff --git a/src/NzbDrone.Core/Download/Clients/Flood/Flood.cs b/src/NzbDrone.Core/Download/Clients/Flood/Flood.cs index f101f715d..6e091e4e6 100644 --- a/src/NzbDrone.Core/Download/Clients/Flood/Flood.cs +++ b/src/NzbDrone.Core/Download/Clients/Flood/Flood.cs @@ -7,6 +7,7 @@ using NLog; using NzbDrone.Common.Disk; using NzbDrone.Common.Extensions; using NzbDrone.Common.Http; +using NzbDrone.Core.Blocklisting; using NzbDrone.Core.Configuration; using NzbDrone.Core.Download.Clients.Flood.Models; using NzbDrone.Core.Localization; @@ -29,9 +30,10 @@ namespace NzbDrone.Core.Download.Clients.Flood IConfigService configService, IDiskProvider diskProvider, IRemotePathMappingService remotePathMappingService, - Logger logger, - ILocalizationService localizationService) - : base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, logger, localizationService) + ILocalizationService localizationService, + IBlocklistService blocklistService, + Logger logger) + : base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, localizationService, blocklistService, logger) { _proxy = proxy; _downloadSeedConfigProvider = downloadSeedConfigProvider; diff --git a/src/NzbDrone.Core/Download/Clients/FreeboxDownload/TorrentFreeboxDownload.cs b/src/NzbDrone.Core/Download/Clients/FreeboxDownload/TorrentFreeboxDownload.cs index 6f020b57b..07f435a34 100644 --- a/src/NzbDrone.Core/Download/Clients/FreeboxDownload/TorrentFreeboxDownload.cs +++ b/src/NzbDrone.Core/Download/Clients/FreeboxDownload/TorrentFreeboxDownload.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using FluentValidation.Results; @@ -6,6 +6,7 @@ using NLog; using NzbDrone.Common.Disk; using NzbDrone.Common.Extensions; using NzbDrone.Common.Http; +using NzbDrone.Core.Blocklisting; using NzbDrone.Core.Configuration; using NzbDrone.Core.Download.Clients.FreeboxDownload.Responses; using NzbDrone.Core.Localization; @@ -25,9 +26,10 @@ namespace NzbDrone.Core.Download.Clients.FreeboxDownload IConfigService configService, IDiskProvider diskProvider, IRemotePathMappingService remotePathMappingService, - Logger logger, - ILocalizationService localizationService) - : base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, logger, localizationService) + ILocalizationService localizationService, + IBlocklistService blocklistService, + Logger logger) + : base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, localizationService, blocklistService, logger) { _proxy = proxy; } diff --git a/src/NzbDrone.Core/Download/Clients/Hadouken/Hadouken.cs b/src/NzbDrone.Core/Download/Clients/Hadouken/Hadouken.cs index 2d50030e7..a29be7f4c 100644 --- a/src/NzbDrone.Core/Download/Clients/Hadouken/Hadouken.cs +++ b/src/NzbDrone.Core/Download/Clients/Hadouken/Hadouken.cs @@ -5,6 +5,7 @@ using NLog; using NzbDrone.Common.Disk; using NzbDrone.Common.Extensions; using NzbDrone.Common.Http; +using NzbDrone.Core.Blocklisting; using NzbDrone.Core.Configuration; using NzbDrone.Core.Download.Clients.Hadouken.Models; using NzbDrone.Core.Localization; @@ -25,9 +26,10 @@ namespace NzbDrone.Core.Download.Clients.Hadouken IConfigService configService, IDiskProvider diskProvider, IRemotePathMappingService remotePathMappingService, - Logger logger, - ILocalizationService localizationService) - : base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, logger, localizationService) + ILocalizationService localizationService, + IBlocklistService blocklistService, + Logger logger) + : base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, localizationService, blocklistService, logger) { _proxy = proxy; } diff --git a/src/NzbDrone.Core/Download/Clients/NzbVortex/NzbVortexProxy.cs b/src/NzbDrone.Core/Download/Clients/NzbVortex/NzbVortexProxy.cs index 3b7187a63..cec43775a 100644 --- a/src/NzbDrone.Core/Download/Clients/NzbVortex/NzbVortexProxy.cs +++ b/src/NzbDrone.Core/Download/Clients/NzbVortex/NzbVortexProxy.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Net; using Newtonsoft.Json; diff --git a/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrent.cs b/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrent.cs index 77cbeef17..c1d6f9f4f 100644 --- a/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrent.cs +++ b/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrent.cs @@ -8,6 +8,7 @@ using NzbDrone.Common.Cache; using NzbDrone.Common.Disk; using NzbDrone.Common.Extensions; using NzbDrone.Common.Http; +using NzbDrone.Core.Blocklisting; using NzbDrone.Core.Configuration; using NzbDrone.Core.Localization; using NzbDrone.Core.MediaFiles.TorrentInfo; @@ -35,9 +36,10 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent IDiskProvider diskProvider, IRemotePathMappingService remotePathMappingService, ICacheManager cacheManager, - Logger logger, - ILocalizationService localizationService) - : base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, logger, localizationService) + ILocalizationService localizationService, + IBlocklistService blocklistService, + Logger logger) + : base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, localizationService, blocklistService, logger) { _proxySelector = proxySelector; diff --git a/src/NzbDrone.Core/Download/Clients/Transmission/Transmission.cs b/src/NzbDrone.Core/Download/Clients/Transmission/Transmission.cs index 8c9f858bc..1cfc134c5 100644 --- a/src/NzbDrone.Core/Download/Clients/Transmission/Transmission.cs +++ b/src/NzbDrone.Core/Download/Clients/Transmission/Transmission.cs @@ -1,10 +1,11 @@ -using System; +using System; using System.Collections.Generic; using System.Text.RegularExpressions; using FluentValidation.Results; using NLog; using NzbDrone.Common.Disk; using NzbDrone.Common.Http; +using NzbDrone.Core.Blocklisting; using NzbDrone.Core.Configuration; using NzbDrone.Core.Localization; using NzbDrone.Core.MediaFiles.TorrentInfo; @@ -20,9 +21,10 @@ namespace NzbDrone.Core.Download.Clients.Transmission IConfigService configService, IDiskProvider diskProvider, IRemotePathMappingService remotePathMappingService, - Logger logger, - ILocalizationService localizationService) - : base(proxy, torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, logger, localizationService) + ILocalizationService localizationService, + IBlocklistService blocklistService, + Logger logger) + : base(proxy, torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, localizationService, blocklistService, logger) { } diff --git a/src/NzbDrone.Core/Download/Clients/Transmission/TransmissionBase.cs b/src/NzbDrone.Core/Download/Clients/Transmission/TransmissionBase.cs index f08ab547c..48a268275 100644 --- a/src/NzbDrone.Core/Download/Clients/Transmission/TransmissionBase.cs +++ b/src/NzbDrone.Core/Download/Clients/Transmission/TransmissionBase.cs @@ -6,6 +6,7 @@ using NLog; using NzbDrone.Common.Disk; using NzbDrone.Common.Extensions; using NzbDrone.Common.Http; +using NzbDrone.Core.Blocklisting; using NzbDrone.Core.Configuration; using NzbDrone.Core.Localization; using NzbDrone.Core.MediaFiles.TorrentInfo; @@ -25,9 +26,10 @@ namespace NzbDrone.Core.Download.Clients.Transmission IConfigService configService, IDiskProvider diskProvider, IRemotePathMappingService remotePathMappingService, - Logger logger, - ILocalizationService localizationService) - : base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, logger, localizationService) + ILocalizationService localizationService, + IBlocklistService blocklistService, + Logger logger) + : base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, localizationService, blocklistService, logger) { _proxy = proxy; } diff --git a/src/NzbDrone.Core/Download/Clients/Vuze/Vuze.cs b/src/NzbDrone.Core/Download/Clients/Vuze/Vuze.cs index 14ab2534f..db12ea64f 100644 --- a/src/NzbDrone.Core/Download/Clients/Vuze/Vuze.cs +++ b/src/NzbDrone.Core/Download/Clients/Vuze/Vuze.cs @@ -2,6 +2,7 @@ using FluentValidation.Results; using NLog; using NzbDrone.Common.Disk; using NzbDrone.Common.Http; +using NzbDrone.Core.Blocklisting; using NzbDrone.Core.Configuration; using NzbDrone.Core.Download.Clients.Transmission; using NzbDrone.Core.Localization; @@ -20,9 +21,10 @@ namespace NzbDrone.Core.Download.Clients.Vuze IConfigService configService, IDiskProvider diskProvider, IRemotePathMappingService remotePathMappingService, - Logger logger, - ILocalizationService localizationService) - : base(proxy, torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, logger, localizationService) + ILocalizationService localizationService, + IBlocklistService blocklistService, + Logger logger) + : base(proxy, torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, localizationService, blocklistService, logger) { } diff --git a/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrent.cs b/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrent.cs index bbfb2fe8e..d1e129949 100644 --- a/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrent.cs +++ b/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrent.cs @@ -8,6 +8,7 @@ using NzbDrone.Common.Disk; using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.Extensions; using NzbDrone.Common.Http; +using NzbDrone.Core.Blocklisting; using NzbDrone.Core.Configuration; using NzbDrone.Core.Download.Clients.rTorrent; using NzbDrone.Core.Exceptions; @@ -35,9 +36,10 @@ namespace NzbDrone.Core.Download.Clients.RTorrent IRemotePathMappingService remotePathMappingService, IDownloadSeedConfigProvider downloadSeedConfigProvider, IRTorrentDirectoryValidator rTorrentDirectoryValidator, - Logger logger, - ILocalizationService localizationService) - : base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, logger, localizationService) + ILocalizationService localizationService, + IBlocklistService blocklistService, + Logger logger) + : base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, localizationService, blocklistService, logger) { _proxy = proxy; _rTorrentDirectoryValidator = rTorrentDirectoryValidator; diff --git a/src/NzbDrone.Core/Download/Clients/uTorrent/UTorrent.cs b/src/NzbDrone.Core/Download/Clients/uTorrent/UTorrent.cs index 0c2c30e31..cecc76dd7 100644 --- a/src/NzbDrone.Core/Download/Clients/uTorrent/UTorrent.cs +++ b/src/NzbDrone.Core/Download/Clients/uTorrent/UTorrent.cs @@ -8,6 +8,7 @@ using NzbDrone.Common.Cache; using NzbDrone.Common.Disk; using NzbDrone.Common.Extensions; using NzbDrone.Common.Http; +using NzbDrone.Core.Blocklisting; using NzbDrone.Core.Configuration; using NzbDrone.Core.Localization; using NzbDrone.Core.MediaFiles.TorrentInfo; @@ -29,9 +30,10 @@ namespace NzbDrone.Core.Download.Clients.UTorrent IConfigService configService, IDiskProvider diskProvider, IRemotePathMappingService remotePathMappingService, - Logger logger, - ILocalizationService localizationService) - : base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, logger, localizationService) + ILocalizationService localizationService, + IBlocklistService blocklistService, + Logger logger) + : base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, localizationService, blocklistService, logger) { _proxy = proxy; diff --git a/src/NzbDrone.Core/Download/DownloadService.cs b/src/NzbDrone.Core/Download/DownloadService.cs index 3032f4394..9f07145ca 100644 --- a/src/NzbDrone.Core/Download/DownloadService.cs +++ b/src/NzbDrone.Core/Download/DownloadService.cs @@ -104,6 +104,11 @@ namespace NzbDrone.Core.Download _logger.Trace("Release {0} no longer available on indexer.", remoteEpisode); throw; } + catch (ReleaseBlockedException) + { + _logger.Trace("Release {0} previously added to blocklist, not sending to download client again.", remoteEpisode); + throw; + } catch (DownloadClientRejectedReleaseException) { _logger.Trace("Release {0} rejected by download client, possible duplicate.", remoteEpisode); @@ -128,7 +133,7 @@ namespace NzbDrone.Core.Download episodeGrabbedEvent.DownloadClientId = downloadClient.Definition.Id; episodeGrabbedEvent.DownloadClientName = downloadClient.Definition.Name; - if (!string.IsNullOrWhiteSpace(downloadClientId)) + if (downloadClientId.IsNotNullOrWhiteSpace()) { episodeGrabbedEvent.DownloadId = downloadClientId; } diff --git a/src/NzbDrone.Core/Download/FailedDownloadService.cs b/src/NzbDrone.Core/Download/FailedDownloadService.cs index f7687ab41..3e540a256 100644 --- a/src/NzbDrone.Core/Download/FailedDownloadService.cs +++ b/src/NzbDrone.Core/Download/FailedDownloadService.cs @@ -129,7 +129,7 @@ namespace NzbDrone.Core.Download private void PublishDownloadFailedEvent(List<EpisodeHistory> historyItems, string message, TrackedDownload trackedDownload = null, bool skipRedownload = false) { - var historyItem = historyItems.First(); + var historyItem = historyItems.Last(); Enum.TryParse(historyItem.Data.GetValueOrDefault(EpisodeHistory.RELEASE_SOURCE, ReleaseSourceType.Unknown.ToString()), out ReleaseSourceType releaseSource); var downloadFailedEvent = new DownloadFailedEvent diff --git a/src/NzbDrone.Core/Download/TorrentClientBase.cs b/src/NzbDrone.Core/Download/TorrentClientBase.cs index 872b2e9ba..7f93b3395 100644 --- a/src/NzbDrone.Core/Download/TorrentClientBase.cs +++ b/src/NzbDrone.Core/Download/TorrentClientBase.cs @@ -6,6 +6,7 @@ using NLog; using NzbDrone.Common.Disk; using NzbDrone.Common.Extensions; using NzbDrone.Common.Http; +using NzbDrone.Core.Blocklisting; using NzbDrone.Core.Configuration; using NzbDrone.Core.Exceptions; using NzbDrone.Core.Indexers; @@ -22,6 +23,7 @@ namespace NzbDrone.Core.Download where TSettings : IProviderConfig, new() { protected readonly IHttpClient _httpClient; + private readonly IBlocklistService _blocklistService; protected readonly ITorrentFileInfoReader _torrentFileInfoReader; protected TorrentClientBase(ITorrentFileInfoReader torrentFileInfoReader, @@ -29,11 +31,13 @@ namespace NzbDrone.Core.Download IConfigService configService, IDiskProvider diskProvider, IRemotePathMappingService remotePathMappingService, - Logger logger, - ILocalizationService localizationService) + ILocalizationService localizationService, + IBlocklistService blocklistService, + Logger logger) : base(configService, diskProvider, remotePathMappingService, logger, localizationService) { _httpClient = httpClient; + _blocklistService = blocklistService; _torrentFileInfoReader = torrentFileInfoReader; } @@ -88,7 +92,7 @@ namespace NzbDrone.Core.Download { try { - return DownloadFromMagnetUrl(remoteEpisode, magnetUrl); + return DownloadFromMagnetUrl(remoteEpisode, indexer, magnetUrl); } catch (NotSupportedException ex) { @@ -102,7 +106,7 @@ namespace NzbDrone.Core.Download { try { - return DownloadFromMagnetUrl(remoteEpisode, magnetUrl); + return DownloadFromMagnetUrl(remoteEpisode, indexer, magnetUrl); } catch (NotSupportedException ex) { @@ -149,7 +153,7 @@ namespace NzbDrone.Core.Download { if (locationHeader.StartsWith("magnet:")) { - return DownloadFromMagnetUrl(remoteEpisode, locationHeader); + return DownloadFromMagnetUrl(remoteEpisode, indexer, locationHeader); } request.Url += new HttpUri(locationHeader); @@ -192,6 +196,9 @@ namespace NzbDrone.Core.Download var filename = string.Format("{0}.torrent", FileNameBuilder.CleanFileName(remoteEpisode.Release.Title)); var hash = _torrentFileInfoReader.GetHashFromTorrentFile(torrentFile); + + EnsureReleaseIsNotBlocklisted(remoteEpisode, indexer, hash); + var actualHash = AddFromTorrentFile(remoteEpisode, hash, filename, torrentFile); if (actualHash.IsNotNullOrWhiteSpace() && hash != actualHash) @@ -205,7 +212,7 @@ namespace NzbDrone.Core.Download return actualHash; } - private string DownloadFromMagnetUrl(RemoteEpisode remoteEpisode, string magnetUrl) + private string DownloadFromMagnetUrl(RemoteEpisode remoteEpisode, IIndexer indexer, string magnetUrl) { string hash = null; string actualHash = null; @@ -223,6 +230,8 @@ namespace NzbDrone.Core.Download if (hash != null) { + EnsureReleaseIsNotBlocklisted(remoteEpisode, indexer, hash); + actualHash = AddFromMagnetLink(remoteEpisode, hash, magnetUrl); } @@ -236,5 +245,30 @@ namespace NzbDrone.Core.Download return actualHash; } + + private void EnsureReleaseIsNotBlocklisted(RemoteEpisode remoteEpisode, IIndexer indexer, string hash) + { + var indexerSettings = indexer?.Definition?.Settings as ITorrentIndexerSettings; + var torrentInfo = remoteEpisode.Release as TorrentInfo; + var torrentInfoHash = torrentInfo?.InfoHash; + + // If the release didn't come from an interactive search, + // the hash wasn't known during processing and the + // indexer is configured to reject blocklisted releases + // during grab check if it's already been blocklisted. + + if (torrentInfo != null && torrentInfoHash.IsNullOrWhiteSpace()) + { + // If the hash isn't known from parsing we set it here so it can be used for blocklisting. + torrentInfo.InfoHash = hash; + + if (remoteEpisode.ReleaseSource != ReleaseSourceType.InteractiveSearch && + indexerSettings?.RejectBlocklistedTorrentHashesWhileGrabbing == true && + _blocklistService.BlocklistedTorrentHash(remoteEpisode.Series.Id, hash)) + { + throw new ReleaseBlockedException(remoteEpisode.Release, "Release previously added to blocklist"); + } + } + } } } diff --git a/src/NzbDrone.Core/Exceptions/ReleaseBlockedException.cs b/src/NzbDrone.Core/Exceptions/ReleaseBlockedException.cs new file mode 100644 index 000000000..73d8b8f8d --- /dev/null +++ b/src/NzbDrone.Core/Exceptions/ReleaseBlockedException.cs @@ -0,0 +1,28 @@ +using System; +using NzbDrone.Core.Parser.Model; + +namespace NzbDrone.Core.Exceptions +{ + public class ReleaseBlockedException : ReleaseDownloadException + { + public ReleaseBlockedException(ReleaseInfo release, string message, params object[] args) + : base(release, message, args) + { + } + + public ReleaseBlockedException(ReleaseInfo release, string message) + : base(release, message) + { + } + + public ReleaseBlockedException(ReleaseInfo release, string message, Exception innerException, params object[] args) + : base(release, message, innerException, args) + { + } + + public ReleaseBlockedException(ReleaseInfo release, string message, Exception innerException) + : base(release, message, innerException) + { + } + } +} diff --git a/src/NzbDrone.Core/History/HistoryService.cs b/src/NzbDrone.Core/History/HistoryService.cs index 86fe74846..813329908 100644 --- a/src/NzbDrone.Core/History/HistoryService.cs +++ b/src/NzbDrone.Core/History/HistoryService.cs @@ -175,9 +175,7 @@ namespace NzbDrone.Core.History history.Data.Add("ReleaseHash", message.Episode.ParsedEpisodeInfo.ReleaseHash); } - var torrentRelease = message.Episode.Release as TorrentInfo; - - if (torrentRelease != null) + if (message.Episode.Release is TorrentInfo torrentRelease) { history.Data.Add("TorrentInfoHash", torrentRelease.InfoHash); } diff --git a/src/NzbDrone.Core/Indexers/BroadcastheNet/BroadcastheNetSettings.cs b/src/NzbDrone.Core/Indexers/BroadcastheNet/BroadcastheNetSettings.cs index 4d3a688de..4adf2745b 100644 --- a/src/NzbDrone.Core/Indexers/BroadcastheNet/BroadcastheNetSettings.cs +++ b/src/NzbDrone.Core/Indexers/BroadcastheNet/BroadcastheNetSettings.cs @@ -37,6 +37,9 @@ namespace NzbDrone.Core.Indexers.BroadcastheNet [FieldDefinition(3)] public SeedCriteriaSettings SeedCriteria { get; set; } = new SeedCriteriaSettings(); + [FieldDefinition(4, Type = FieldType.Checkbox, Label = "Reject Blocklisted Torrent Hashes While Grabbing", HelpText = "If a torrent is blocked by hash it may not properly be rejected during RSS/Search for some indexers, enabling this will allow it to be rejected after the torrent is grabbed, but before it is sent to the client.", Advanced = true)] + public bool RejectBlocklistedTorrentHashesWhileGrabbing { get; set; } + public NzbDroneValidationResult Validate() { return new NzbDroneValidationResult(Validator.Validate(this)); diff --git a/src/NzbDrone.Core/Indexers/FileList/FileListSettings.cs b/src/NzbDrone.Core/Indexers/FileList/FileListSettings.cs index f149ef8f4..59eb7c13d 100644 --- a/src/NzbDrone.Core/Indexers/FileList/FileListSettings.cs +++ b/src/NzbDrone.Core/Indexers/FileList/FileListSettings.cs @@ -58,6 +58,9 @@ namespace NzbDrone.Core.Indexers.FileList [FieldDefinition(7)] public SeedCriteriaSettings SeedCriteria { get; set; } = new SeedCriteriaSettings(); + [FieldDefinition(8, Type = FieldType.Checkbox, Label = "Reject Blocklisted Torrent Hashes While Grabbing", HelpText = "If a torrent is blocked by hash it may not properly be rejected during RSS/Search for some indexers, enabling this will allow it to be rejected after the torrent is grabbed, but before it is sent to the client.", Advanced = true)] + public bool RejectBlocklistedTorrentHashesWhileGrabbing { get; set; } + public NzbDroneValidationResult Validate() { return new NzbDroneValidationResult(Validator.Validate(this)); diff --git a/src/NzbDrone.Core/Indexers/HDBits/HDBitsSettings.cs b/src/NzbDrone.Core/Indexers/HDBits/HDBitsSettings.cs index c6525771d..1e35ca310 100644 --- a/src/NzbDrone.Core/Indexers/HDBits/HDBitsSettings.cs +++ b/src/NzbDrone.Core/Indexers/HDBits/HDBitsSettings.cs @@ -55,6 +55,9 @@ namespace NzbDrone.Core.Indexers.HDBits [FieldDefinition(7)] public SeedCriteriaSettings SeedCriteria { get; set; } = new (); + [FieldDefinition(8, Type = FieldType.Checkbox, Label = "Reject Blocklisted Torrent Hashes While Grabbing", HelpText = "If a torrent is blocked by hash it may not properly be rejected during RSS/Search for some indexers, enabling this will allow it to be rejected after the torrent is grabbed, but before it is sent to the client.", Advanced = true)] + public bool RejectBlocklistedTorrentHashesWhileGrabbing { get; set; } + public NzbDroneValidationResult Validate() { return new NzbDroneValidationResult(Validator.Validate(this)); diff --git a/src/NzbDrone.Core/Indexers/IPTorrents/IPTorrentsSettings.cs b/src/NzbDrone.Core/Indexers/IPTorrents/IPTorrentsSettings.cs index 473bc9f8e..f1c3bb490 100644 --- a/src/NzbDrone.Core/Indexers/IPTorrents/IPTorrentsSettings.cs +++ b/src/NzbDrone.Core/Indexers/IPTorrents/IPTorrentsSettings.cs @@ -40,6 +40,9 @@ namespace NzbDrone.Core.Indexers.IPTorrents [FieldDefinition(2)] public SeedCriteriaSettings SeedCriteria { get; set; } = new SeedCriteriaSettings(); + [FieldDefinition(3, Type = FieldType.Checkbox, Label = "Reject Blocklisted Torrent Hashes While Grabbing", HelpText = "If a torrent is blocked by hash it may not properly be rejected during RSS/Search for some indexers, enabling this will allow it to be rejected after the torrent is grabbed, but before it is sent to the client.", Advanced = true)] + public bool RejectBlocklistedTorrentHashesWhileGrabbing { get; set; } + public NzbDroneValidationResult Validate() { return new NzbDroneValidationResult(Validator.Validate(this)); diff --git a/src/NzbDrone.Core/Indexers/ITorrentIndexerSettings.cs b/src/NzbDrone.Core/Indexers/ITorrentIndexerSettings.cs index 6a9070475..eb507c0c9 100644 --- a/src/NzbDrone.Core/Indexers/ITorrentIndexerSettings.cs +++ b/src/NzbDrone.Core/Indexers/ITorrentIndexerSettings.cs @@ -6,5 +6,6 @@ namespace NzbDrone.Core.Indexers // TODO: System.Text.Json requires setter be public for sub-object deserialization in 3.0. https://github.com/dotnet/corefx/issues/42515 SeedCriteriaSettings SeedCriteria { get; set; } + bool RejectBlocklistedTorrentHashesWhileGrabbing { get; set; } } } diff --git a/src/NzbDrone.Core/Indexers/Nyaa/NyaaSettings.cs b/src/NzbDrone.Core/Indexers/Nyaa/NyaaSettings.cs index 25741b13b..f6c40e713 100644 --- a/src/NzbDrone.Core/Indexers/Nyaa/NyaaSettings.cs +++ b/src/NzbDrone.Core/Indexers/Nyaa/NyaaSettings.cs @@ -42,6 +42,9 @@ namespace NzbDrone.Core.Indexers.Nyaa [FieldDefinition(4)] public SeedCriteriaSettings SeedCriteria { get; set; } = new SeedCriteriaSettings(); + [FieldDefinition(5, Type = FieldType.Checkbox, Label = "Reject Blocklisted Torrent Hashes While Grabbing", HelpText = "If a torrent is blocked by hash it may not properly be rejected during RSS/Search for some indexers, enabling this will allow it to be rejected after the torrent is grabbed, but before it is sent to the client.", Advanced = true)] + public bool RejectBlocklistedTorrentHashesWhileGrabbing { get; set; } + public NzbDroneValidationResult Validate() { return new NzbDroneValidationResult(Validator.Validate(this)); diff --git a/src/NzbDrone.Core/Indexers/TorrentRss/TorrentRssIndexerSettings.cs b/src/NzbDrone.Core/Indexers/TorrentRss/TorrentRssIndexerSettings.cs index 298fce7ce..6543b5a7f 100644 --- a/src/NzbDrone.Core/Indexers/TorrentRss/TorrentRssIndexerSettings.cs +++ b/src/NzbDrone.Core/Indexers/TorrentRss/TorrentRssIndexerSettings.cs @@ -40,6 +40,9 @@ namespace NzbDrone.Core.Indexers.TorrentRss [FieldDefinition(4)] public SeedCriteriaSettings SeedCriteria { get; set; } = new SeedCriteriaSettings(); + [FieldDefinition(5, Type = FieldType.Checkbox, Label = "Reject Blocklisted Torrent Hashes While Grabbing", HelpText = "If a torrent is blocked by hash it may not properly be rejected during RSS/Search for some indexers, enabling this will allow it to be rejected after the torrent is grabbed, but before it is sent to the client.", Advanced = true)] + public bool RejectBlocklistedTorrentHashesWhileGrabbing { get; set; } + public NzbDroneValidationResult Validate() { return new NzbDroneValidationResult(Validator.Validate(this)); diff --git a/src/NzbDrone.Core/Indexers/Torrentleech/TorrentleechSettings.cs b/src/NzbDrone.Core/Indexers/Torrentleech/TorrentleechSettings.cs index 09ad61bff..51e21b31c 100644 --- a/src/NzbDrone.Core/Indexers/Torrentleech/TorrentleechSettings.cs +++ b/src/NzbDrone.Core/Indexers/Torrentleech/TorrentleechSettings.cs @@ -37,6 +37,9 @@ namespace NzbDrone.Core.Indexers.Torrentleech [FieldDefinition(3)] public SeedCriteriaSettings SeedCriteria { get; set; } = new SeedCriteriaSettings(); + [FieldDefinition(4, Type = FieldType.Checkbox, Label = "Reject Blocklisted Torrent Hashes While Grabbing", HelpText = "If a torrent is blocked by hash it may not properly be rejected during RSS/Search for some indexers, enabling this will allow it to be rejected after the torrent is grabbed, but before it is sent to the client.", Advanced = true)] + public bool RejectBlocklistedTorrentHashesWhileGrabbing { get; set; } + public NzbDroneValidationResult Validate() { return new NzbDroneValidationResult(Validator.Validate(this)); diff --git a/src/NzbDrone.Core/Indexers/Torznab/TorznabSettings.cs b/src/NzbDrone.Core/Indexers/Torznab/TorznabSettings.cs index 983c3d594..7e3ce1295 100644 --- a/src/NzbDrone.Core/Indexers/Torznab/TorznabSettings.cs +++ b/src/NzbDrone.Core/Indexers/Torznab/TorznabSettings.cs @@ -57,6 +57,9 @@ namespace NzbDrone.Core.Indexers.Torznab [FieldDefinition(8)] public SeedCriteriaSettings SeedCriteria { get; set; } = new SeedCriteriaSettings(); + [FieldDefinition(9, Type = FieldType.Checkbox, Label = "Reject Blocklisted Torrent Hashes While Grabbing", HelpText = "If a torrent is blocked by hash it may not properly be rejected during RSS/Search for some indexers, enabling this will allow it to be rejected after the torrent is grabbed, but before it is sent to the client.", Advanced = true)] + public bool RejectBlocklistedTorrentHashesWhileGrabbing { get; set; } + public override NzbDroneValidationResult Validate() { return new NzbDroneValidationResult(Validator.Validate(this)); From b4ac495983d61819d9ab84f49c880957ba57418b Mon Sep 17 00:00:00 2001 From: Jendrik Weise <jewe37@gmail.com> Date: Sun, 19 Nov 2023 19:52:37 +0100 Subject: [PATCH 117/136] New: Custom import scripts can communicate information back --- .../DiskScanServiceTests/ScanFixture.cs | 2 +- .../UpdateMediaInfoServiceFixture.cs | 13 ++- .../Extras/ExistingExtraFileService.cs | 39 +++---- src/NzbDrone.Core/Extras/ExtraService.cs | 11 ++ .../MediaFiles/DiskScanService.cs | 11 +- .../MediaFiles/EpisodeFileMovingService.cs | 1 + .../EpisodeImport/ImportApprovedEpisodes.cs | 18 ++- .../MediaFiles/Events/SeriesScannedEvent.cs | 7 +- .../MediaFiles/ScriptImportDecider.cs | 107 ++++++++++++++++-- .../MediaFiles/ScriptImportInfo.cs | 20 ++++ .../Parser/Model/LocalEpisode.cs | 4 + 11 files changed, 186 insertions(+), 47 deletions(-) create mode 100644 src/NzbDrone.Core/MediaFiles/ScriptImportInfo.cs diff --git a/src/NzbDrone.Core.Test/MediaFiles/DiskScanServiceTests/ScanFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/DiskScanServiceTests/ScanFixture.cs index cd9da2e05..d3b8c000f 100644 --- a/src/NzbDrone.Core.Test/MediaFiles/DiskScanServiceTests/ScanFixture.cs +++ b/src/NzbDrone.Core.Test/MediaFiles/DiskScanServiceTests/ScanFixture.cs @@ -267,7 +267,7 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests Subject.Scan(_series); Mocker.GetMock<IDiskProvider>() - .Verify(v => v.GetFiles(It.IsAny<string>(), It.IsAny<bool>()), Times.Once()); + .Verify(v => v.GetFiles(It.IsAny<string>(), It.IsAny<bool>()), Times.Exactly(2)); Mocker.GetMock<IMakeImportDecision>() .Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _series, false), Times.Once()); diff --git a/src/NzbDrone.Core.Test/MediaFiles/MediaInfo/UpdateMediaInfoServiceFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/MediaInfo/UpdateMediaInfoServiceFixture.cs index de3621fcd..72af3a7ca 100644 --- a/src/NzbDrone.Core.Test/MediaFiles/MediaInfo/UpdateMediaInfoServiceFixture.cs +++ b/src/NzbDrone.Core.Test/MediaFiles/MediaInfo/UpdateMediaInfoServiceFixture.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.IO; using FizzWare.NBuilder; using FluentAssertions; @@ -71,7 +72,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo GivenFileExists(); GivenSuccessfulScan(); - Subject.Handle(new SeriesScannedEvent(_series)); + Subject.Handle(new SeriesScannedEvent(_series, new List<string>())); Mocker.GetMock<IVideoFileInfoReader>() .Verify(v => v.GetMediaInfo(Path.Combine(_series.Path, "media.mkv")), Times.Exactly(2)); @@ -97,7 +98,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo GivenFileExists(); GivenSuccessfulScan(); - Subject.Handle(new SeriesScannedEvent(_series)); + Subject.Handle(new SeriesScannedEvent(_series, new List<string>())); Mocker.GetMock<IVideoFileInfoReader>() .Verify(v => v.GetMediaInfo(Path.Combine(_series.Path, "media.mkv")), Times.Exactly(2)); @@ -123,7 +124,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo GivenFileExists(); GivenSuccessfulScan(); - Subject.Handle(new SeriesScannedEvent(_series)); + Subject.Handle(new SeriesScannedEvent(_series, new List<string>())); Mocker.GetMock<IVideoFileInfoReader>() .Verify(v => v.GetMediaInfo(Path.Combine(_series.Path, "media.mkv")), Times.Exactly(3)); @@ -146,7 +147,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo GivenSuccessfulScan(); - Subject.Handle(new SeriesScannedEvent(_series)); + Subject.Handle(new SeriesScannedEvent(_series, new List<string>())); Mocker.GetMock<IVideoFileInfoReader>() .Verify(v => v.GetMediaInfo("media.mkv"), Times.Never()); @@ -173,7 +174,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo GivenSuccessfulScan(); GivenFailedScan(Path.Combine(_series.Path, "media2.mkv")); - Subject.Handle(new SeriesScannedEvent(_series)); + Subject.Handle(new SeriesScannedEvent(_series, new List<string>())); Mocker.GetMock<IVideoFileInfoReader>() .Verify(v => v.GetMediaInfo(Path.Combine(_series.Path, "media.mkv")), Times.Exactly(1)); @@ -203,7 +204,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo GivenFileExists(); GivenSuccessfulScan(); - Subject.Handle(new SeriesScannedEvent(_series)); + Subject.Handle(new SeriesScannedEvent(_series, new List<string>())); Mocker.GetMock<IVideoFileInfoReader>() .Verify(v => v.GetMediaInfo(It.IsAny<string>()), Times.Never()); diff --git a/src/NzbDrone.Core/Extras/ExistingExtraFileService.cs b/src/NzbDrone.Core/Extras/ExistingExtraFileService.cs index 5cfe4944f..ae17b44c6 100644 --- a/src/NzbDrone.Core/Extras/ExistingExtraFileService.cs +++ b/src/NzbDrone.Core/Extras/ExistingExtraFileService.cs @@ -2,45 +2,33 @@ using System.Collections.Generic; using System.IO; using System.Linq; using NLog; -using NzbDrone.Common.Disk; -using NzbDrone.Core.MediaFiles; using NzbDrone.Core.MediaFiles.Events; using NzbDrone.Core.Messaging.Events; +using NzbDrone.Core.Tv; namespace NzbDrone.Core.Extras { - public class ExistingExtraFileService : IHandle<SeriesScannedEvent> + public interface IExistingExtraFiles + { + List<string> ImportExtraFiles(Series series, List<string> possibleExtraFiles); + } + + public class ExistingExtraFileService : IExistingExtraFiles, IHandle<SeriesScannedEvent> { - private readonly IDiskProvider _diskProvider; - private readonly IDiskScanService _diskScanService; private readonly List<IImportExistingExtraFiles> _existingExtraFileImporters; private readonly Logger _logger; - public ExistingExtraFileService(IDiskProvider diskProvider, - IDiskScanService diskScanService, - IEnumerable<IImportExistingExtraFiles> existingExtraFileImporters, + public ExistingExtraFileService(IEnumerable<IImportExistingExtraFiles> existingExtraFileImporters, Logger logger) { - _diskProvider = diskProvider; - _diskScanService = diskScanService; _existingExtraFileImporters = existingExtraFileImporters.OrderBy(e => e.Order).ToList(); _logger = logger; } - public void Handle(SeriesScannedEvent message) + public List<string> ImportExtraFiles(Series series, List<string> possibleExtraFiles) { - var series = message.Series; - - if (!_diskProvider.FolderExists(series.Path)) - { - return; - } - _logger.Debug("Looking for existing extra files in {0}", series.Path); - var filesOnDisk = _diskScanService.GetNonVideoFiles(series.Path); - var possibleExtraFiles = _diskScanService.FilterPaths(series.Path, filesOnDisk); - var importedFiles = new List<string>(); foreach (var existingExtraFileImporter in _existingExtraFileImporters) @@ -50,6 +38,15 @@ namespace NzbDrone.Core.Extras importedFiles.AddRange(imported.Select(f => Path.Combine(series.Path, f.RelativePath))); } + return importedFiles; + } + + public void Handle(SeriesScannedEvent message) + { + var series = message.Series; + var possibleExtraFiles = message.PossibleExtraFiles; + var importedFiles = ImportExtraFiles(series, possibleExtraFiles); + _logger.Info("Found {0} possible extra files, imported {1} files.", possibleExtraFiles.Count, importedFiles.Count); } } diff --git a/src/NzbDrone.Core/Extras/ExtraService.cs b/src/NzbDrone.Core/Extras/ExtraService.cs index eca970898..b29fa6d17 100644 --- a/src/NzbDrone.Core/Extras/ExtraService.cs +++ b/src/NzbDrone.Core/Extras/ExtraService.cs @@ -17,6 +17,7 @@ namespace NzbDrone.Core.Extras { public interface IExtraService { + void MoveFilesAfterRename(Series series, EpisodeFile episodeFile); void ImportEpisode(LocalEpisode localEpisode, EpisodeFile episodeFile, bool isReadOnly); } @@ -139,6 +140,16 @@ namespace NzbDrone.Core.Extras } } + public void MoveFilesAfterRename(Series series, EpisodeFile episodeFile) + { + var episodeFiles = new List<EpisodeFile> { episodeFile }; + + foreach (var extraFileManager in _extraFileManagers) + { + extraFileManager.MoveFilesAfterRename(series, episodeFiles); + } + } + public void Handle(SeriesRenamedEvent message) { var series = message.Series; diff --git a/src/NzbDrone.Core/MediaFiles/DiskScanService.cs b/src/NzbDrone.Core/MediaFiles/DiskScanService.cs index 589a5c1c4..4c92b0d93 100644 --- a/src/NzbDrone.Core/MediaFiles/DiskScanService.cs +++ b/src/NzbDrone.Core/MediaFiles/DiskScanService.cs @@ -121,7 +121,7 @@ namespace NzbDrone.Core.MediaFiles } CleanMediaFiles(series, new List<string>()); - CompletedScanning(series); + CompletedScanning(series, new List<string>()); return; } @@ -174,8 +174,11 @@ namespace NzbDrone.Core.MediaFiles fileInfoStopwatch.Stop(); _logger.Trace("Reprocessing existing files complete for: {0} [{1}]", series, decisionsStopwatch.Elapsed); + var filesOnDisk = GetNonVideoFiles(series.Path); + var possibleExtraFiles = FilterPaths(series.Path, filesOnDisk); + RemoveEmptySeriesFolder(series.Path); - CompletedScanning(series); + CompletedScanning(series, possibleExtraFiles); } private void CleanMediaFiles(Series series, List<string> mediaFileList) @@ -184,10 +187,10 @@ namespace NzbDrone.Core.MediaFiles _mediaFileTableCleanupService.Clean(series, mediaFileList); } - private void CompletedScanning(Series series) + private void CompletedScanning(Series series, List<string> possibleExtraFiles) { _logger.Info("Completed scanning disk for {0}", series.Title); - _eventAggregator.PublishEvent(new SeriesScannedEvent(series)); + _eventAggregator.PublishEvent(new SeriesScannedEvent(series, possibleExtraFiles)); } public string[] GetVideoFiles(string path, bool allDirectories = true) diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeFileMovingService.cs b/src/NzbDrone.Core/MediaFiles/EpisodeFileMovingService.cs index 3ea8a9fc1..eac485671 100644 --- a/src/NzbDrone.Core/MediaFiles/EpisodeFileMovingService.cs +++ b/src/NzbDrone.Core/MediaFiles/EpisodeFileMovingService.cs @@ -130,6 +130,7 @@ namespace NzbDrone.Core.MediaFiles try { MoveEpisodeFile(episodeFile, series, episodeFile.Episodes); + localEpisode.FileRenamedAfterScriptImport = true; } catch (SameFilenameException) { diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedEpisodes.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedEpisodes.cs index a77cc8957..5d4bb77f6 100644 --- a/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedEpisodes.cs +++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedEpisodes.cs @@ -26,6 +26,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport private readonly IUpgradeMediaFiles _episodeFileUpgrader; private readonly IMediaFileService _mediaFileService; private readonly IExtraService _extraService; + private readonly IExistingExtraFiles _existingExtraFiles; private readonly IDiskProvider _diskProvider; private readonly IEventAggregator _eventAggregator; private readonly IManageCommandQueue _commandQueueManager; @@ -34,6 +35,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport public ImportApprovedEpisodes(IUpgradeMediaFiles episodeFileUpgrader, IMediaFileService mediaFileService, IExtraService extraService, + IExistingExtraFiles existingExtraFiles, IDiskProvider diskProvider, IEventAggregator eventAggregator, IManageCommandQueue commandQueueManager, @@ -42,6 +44,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport _episodeFileUpgrader = episodeFileUpgrader; _mediaFileService = mediaFileService; _extraService = extraService; + _existingExtraFiles = existingExtraFiles; _diskProvider = diskProvider; _eventAggregator = eventAggregator; _commandQueueManager = commandQueueManager; @@ -130,7 +133,20 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport if (newDownload) { - _extraService.ImportEpisode(localEpisode, episodeFile, copyOnly); + if (localEpisode.ScriptImported) + { + _existingExtraFiles.ImportExtraFiles(localEpisode.Series, localEpisode.PossibleExtraFiles); + + if (localEpisode.FileRenamedAfterScriptImport) + { + _extraService.MoveFilesAfterRename(localEpisode.Series, episodeFile); + } + } + + if (!localEpisode.ScriptImported || localEpisode.ShouldImportExtras) + { + _extraService.ImportEpisode(localEpisode, episodeFile, copyOnly); + } } _eventAggregator.PublishEvent(new EpisodeImportedEvent(localEpisode, episodeFile, oldFiles, newDownload, downloadClientItem)); diff --git a/src/NzbDrone.Core/MediaFiles/Events/SeriesScannedEvent.cs b/src/NzbDrone.Core/MediaFiles/Events/SeriesScannedEvent.cs index e07bbd75f..a76348b39 100644 --- a/src/NzbDrone.Core/MediaFiles/Events/SeriesScannedEvent.cs +++ b/src/NzbDrone.Core/MediaFiles/Events/SeriesScannedEvent.cs @@ -1,4 +1,5 @@ -using NzbDrone.Common.Messaging; +using System.Collections.Generic; +using NzbDrone.Common.Messaging; using NzbDrone.Core.Tv; namespace NzbDrone.Core.MediaFiles.Events @@ -6,10 +7,12 @@ namespace NzbDrone.Core.MediaFiles.Events public class SeriesScannedEvent : IEvent { public Series Series { get; private set; } + public List<string> PossibleExtraFiles { get; set; } - public SeriesScannedEvent(Series series) + public SeriesScannedEvent(Series series, List<string> possibleExtraFiles) { Series = series; + PossibleExtraFiles = possibleExtraFiles; } } } diff --git a/src/NzbDrone.Core/MediaFiles/ScriptImportDecider.cs b/src/NzbDrone.Core/MediaFiles/ScriptImportDecider.cs index c626d5b44..45ab383d7 100644 --- a/src/NzbDrone.Core/MediaFiles/ScriptImportDecider.cs +++ b/src/NzbDrone.Core/MediaFiles/ScriptImportDecider.cs @@ -1,6 +1,8 @@ +using System.Collections.Generic; using System.Collections.Specialized; using System.IO; using System.Linq; +using System.Text.RegularExpressions; using NLog; using NzbDrone.Common.Disk; using NzbDrone.Common.Extensions; @@ -25,6 +27,7 @@ namespace NzbDrone.Core.MediaFiles private readonly IProcessProvider _processProvider; private readonly IConfigService _configService; private readonly ITagRepository _tagRepository; + private readonly IDiskProvider _diskProvider; private readonly Logger _logger; public ImportScriptService(IProcessProvider processProvider, @@ -32,6 +35,7 @@ namespace NzbDrone.Core.MediaFiles IConfigService configService, IConfigFileProvider configFileProvider, ITagRepository tagRepository, + IDiskProvider diskProvider, Logger logger) { _processProvider = processProvider; @@ -39,9 +43,73 @@ namespace NzbDrone.Core.MediaFiles _configService = configService; _configFileProvider = configFileProvider; _tagRepository = tagRepository; + _diskProvider = diskProvider; _logger = logger; } + private static readonly Regex OutputRegex = new Regex(@"^(?:\[(?:(?<mediaFile>MediaFile)|(?<extraFile>ExtraFile))\]\s?(?<fileName>.+)|(?<preventExtraImport>\[PreventExtraImport\])|\[MoveStatus\]\s?(?:(?<deferMove>DeferMove)|(?<moveComplete>MoveComplete)|(?<renameRequested>RenameRequested)))$", RegexOptions.Compiled); + + private ScriptImportInfo ProcessOutput(List<ProcessOutputLine> processOutputLines) + { + var possibleExtraFiles = new List<string>(); + string mediaFile = null; + var decision = ScriptImportDecision.MoveComplete; + var importExtraFiles = true; + + foreach (var line in processOutputLines) + { + var match = OutputRegex.Match(line.Content); + + if (match.Groups["mediaFile"].Success) + { + if (mediaFile is not null) + { + throw new ScriptImportException("Script output contains multiple media files. Only one media file can be returned."); + } + + mediaFile = match.Groups["fileName"].Value; + + if (!MediaFileExtensions.Extensions.Contains(Path.GetExtension(mediaFile))) + { + throw new ScriptImportException("Script output contains invalid media file: {0}", mediaFile); + } + else if (!_diskProvider.FileExists(mediaFile)) + { + throw new ScriptImportException("Script output contains non-existent media file: {0}", mediaFile); + } + } + else if (match.Groups["extraFile"].Success) + { + var fileName = match.Groups["fileName"].Value; + + if (!_diskProvider.FileExists(fileName)) + { + _logger.Warn("Script output contains non-existent possible extra file: {0}", fileName); + } + + possibleExtraFiles.Add(fileName); + } + else if (match.Groups["moveComplete"].Success) + { + decision = ScriptImportDecision.MoveComplete; + } + else if (match.Groups["renameRequested"].Success) + { + decision = ScriptImportDecision.RenameRequested; + } + else if (match.Groups["deferMove"].Success) + { + decision = ScriptImportDecision.DeferMove; + } + else if (match.Groups["preventExtraImport"].Success) + { + importExtraFiles = false; + } + } + + return new ScriptImportInfo(possibleExtraFiles, mediaFile, decision, importExtraFiles); + } + public ScriptImportDecision TryImport(string sourcePath, string destinationFilePath, LocalEpisode localEpisode, EpisodeFile episodeFile, TransferMode mode) { var series = localEpisode.Series; @@ -115,22 +183,37 @@ namespace NzbDrone.Core.MediaFiles var processOutput = _processProvider.StartAndCapture(_configService.ScriptImportPath, $"\"{sourcePath}\" \"{destinationFilePath}\"", environmentVariables); - _logger.Debug("Executed external script: {0} - Status: {1}", _configService.ScriptImportPath, processOutput.ExitCode); _logger.Debug("Script Output: \r\n{0}", string.Join("\r\n", processOutput.Lines)); - switch (processOutput.ExitCode) + if (processOutput.ExitCode != 0) { - case 0: // Copy complete - return ScriptImportDecision.MoveComplete; - case 2: // Copy complete, file potentially changed, should try renaming again - episodeFile.MediaInfo = _videoFileInfoReader.GetMediaInfo(destinationFilePath); - episodeFile.Path = null; - return ScriptImportDecision.RenameRequested; - case 3: // Let Sonarr handle it - return ScriptImportDecision.DeferMove; - default: // Error, fail to import - throw new ScriptImportException("Moving with script failed! Exit code {0}", processOutput.ExitCode); + throw new ScriptImportException("Script exited with non-zero exit code: {0}", processOutput.ExitCode); } + + var scriptImportInfo = ProcessOutput(processOutput.Lines); + + var mediaFile = scriptImportInfo.MediaFile ?? destinationFilePath; + localEpisode.PossibleExtraFiles = scriptImportInfo.PossibleExtraFiles; + + episodeFile.RelativePath = series.Path.GetRelativePath(mediaFile); + episodeFile.Path = mediaFile; + + var exitCode = processOutput.ExitCode; + + localEpisode.ShouldImportExtras = scriptImportInfo.ImportExtraFiles; + + if (scriptImportInfo.Decision != ScriptImportDecision.DeferMove) + { + localEpisode.ScriptImported = true; + } + + if (scriptImportInfo.Decision == ScriptImportDecision.RenameRequested) + { + episodeFile.MediaInfo = _videoFileInfoReader.GetMediaInfo(mediaFile); + episodeFile.Path = null; + } + + return scriptImportInfo.Decision; } } } diff --git a/src/NzbDrone.Core/MediaFiles/ScriptImportInfo.cs b/src/NzbDrone.Core/MediaFiles/ScriptImportInfo.cs new file mode 100644 index 000000000..6c861fa8d --- /dev/null +++ b/src/NzbDrone.Core/MediaFiles/ScriptImportInfo.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; + +namespace NzbDrone.Core.MediaFiles +{ + public struct ScriptImportInfo + { + public List<string> PossibleExtraFiles { get; set; } + public string MediaFile { get; set; } + public ScriptImportDecision Decision { get; set; } + public bool ImportExtraFiles { get; set; } + + public ScriptImportInfo(List<string> possibleExtraFiles, string mediaFile, ScriptImportDecision decision, bool importExtraFiles) + { + PossibleExtraFiles = possibleExtraFiles; + MediaFile = mediaFile; + Decision = decision; + ImportExtraFiles = importExtraFiles; + } + } +} diff --git a/src/NzbDrone.Core/Parser/Model/LocalEpisode.cs b/src/NzbDrone.Core/Parser/Model/LocalEpisode.cs index bcb9a5132..470310b7c 100644 --- a/src/NzbDrone.Core/Parser/Model/LocalEpisode.cs +++ b/src/NzbDrone.Core/Parser/Model/LocalEpisode.cs @@ -40,6 +40,10 @@ namespace NzbDrone.Core.Parser.Model public List<CustomFormat> CustomFormats { get; set; } public int CustomFormatScore { get; set; } public GrabbedReleaseInfo Release { get; set; } + public bool ScriptImported { get; set; } + public bool FileRenamedAfterScriptImport { get; set; } + public bool ShouldImportExtras { get; set; } + public List<string> PossibleExtraFiles { get; set; } public int SeasonNumber { From c3dcc542da9a06ffe4f10fbe7f9c51cd9ee92d1a Mon Sep 17 00:00:00 2001 From: Stevie Robinson <stevie.robinson@gmail.com> Date: Sun, 19 Nov 2023 19:53:30 +0100 Subject: [PATCH 118/136] Fix translation tokens --- src/NzbDrone.Core/HealthCheck/Checks/ApiKeyValidationCheck.cs | 2 +- src/NzbDrone.Core/Localization/Core/en.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/NzbDrone.Core/HealthCheck/Checks/ApiKeyValidationCheck.cs b/src/NzbDrone.Core/HealthCheck/Checks/ApiKeyValidationCheck.cs index ec4ae577a..d3198b111 100644 --- a/src/NzbDrone.Core/HealthCheck/Checks/ApiKeyValidationCheck.cs +++ b/src/NzbDrone.Core/HealthCheck/Checks/ApiKeyValidationCheck.cs @@ -29,7 +29,7 @@ namespace NzbDrone.Core.HealthCheck.Checks { _logger.Warn("Please update your API key to be at least {0} characters long. You can do this via settings or the config file", MinimumLength); - return new HealthCheck(GetType(), HealthCheckResult.Warning, _localizationService.GetLocalizedString("ApiKeyValidationHealthCheckMessage", new Dictionary<string, object> { { "MinimumLength", MinimumLength } }), "#invalid-api-key"); + return new HealthCheck(GetType(), HealthCheckResult.Warning, _localizationService.GetLocalizedString("ApiKeyValidationHealthCheckMessage", new Dictionary<string, object> { { "length", MinimumLength } }), "#invalid-api-key"); } return new HealthCheck(GetType()); diff --git a/src/NzbDrone.Core/Localization/Core/en.json b/src/NzbDrone.Core/Localization/Core/en.json index b61b045c3..fbde87fe8 100644 --- a/src/NzbDrone.Core/Localization/Core/en.json +++ b/src/NzbDrone.Core/Localization/Core/en.json @@ -1644,7 +1644,7 @@ "UpdateSelected": "Update Selected", "UpdateSonarrDirectlyLoadError": "Unable to update {appName} directly,", "UpdateStartupNotWritableHealthCheckMessage": "Cannot install update because startup folder '{startupFolder}' is not writable by the user '{userName}'.", - "UpdateStartupTranslocationHealthCheckMessage": "Cannot install update because startup folder '{ }' is in an App Translocation folder.", + "UpdateStartupTranslocationHealthCheckMessage": "Cannot install update because startup folder '{startupFolder}' is in an App Translocation folder.", "UpdateUiNotWritableHealthCheckMessage": "Cannot install update because UI folder '{uiFolder}' is not writable by the user '{userName}'.", "UpdaterLogFiles": "Updater Log Files", "Updates": "Updates", From c922cc5dc617dd776d4523cbf62376821c5a4ad9 Mon Sep 17 00:00:00 2001 From: Mark McDowall <mark@mcdowall.ca> Date: Sun, 19 Nov 2023 00:17:32 -0800 Subject: [PATCH 119/136] Always validate Custom Script path --- .../Notifications/CustomScript/CustomScript.cs | 8 -------- .../Notifications/CustomScript/CustomScriptSettings.cs | 1 + 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/src/NzbDrone.Core/Notifications/CustomScript/CustomScript.cs b/src/NzbDrone.Core/Notifications/CustomScript/CustomScript.cs index 1976904d7..e085b4165 100644 --- a/src/NzbDrone.Core/Notifications/CustomScript/CustomScript.cs +++ b/src/NzbDrone.Core/Notifications/CustomScript/CustomScript.cs @@ -363,14 +363,6 @@ namespace NzbDrone.Core.Notifications.CustomScript failures.Add(new NzbDroneValidationFailure("Path", "File does not exist")); } - foreach (var systemFolder in SystemFolders.GetSystemFolders()) - { - if (systemFolder.IsParentPath(Settings.Path)) - { - failures.Add(new NzbDroneValidationFailure("Path", $"Must not be a descendant of '{systemFolder}'")); - } - } - if (failures.Empty()) { try diff --git a/src/NzbDrone.Core/Notifications/CustomScript/CustomScriptSettings.cs b/src/NzbDrone.Core/Notifications/CustomScript/CustomScriptSettings.cs index f4d4d7803..ce5a398ed 100644 --- a/src/NzbDrone.Core/Notifications/CustomScript/CustomScriptSettings.cs +++ b/src/NzbDrone.Core/Notifications/CustomScript/CustomScriptSettings.cs @@ -11,6 +11,7 @@ namespace NzbDrone.Core.Notifications.CustomScript public CustomScriptSettingsValidator() { RuleFor(c => c.Path).IsValidPath(); + RuleFor(c => c.Path).SetValidator(new SystemFolderValidator()).WithMessage("Must not be a descendant of '{systemFolder}'"); RuleFor(c => c.Arguments).Empty().WithMessage("Arguments are no longer supported for custom scripts"); } } From d95660d3c78d1ee11a7966d58e78a82a8df01393 Mon Sep 17 00:00:00 2001 From: Mark McDowall <mark@mcdowall.ca> Date: Sat, 18 Nov 2023 17:47:02 -0800 Subject: [PATCH 120/136] Fixed: Disable SSL when migrating from v3 Closes #5979 --- .../Configuration/ConfigFileProvider.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/NzbDrone.Core/Configuration/ConfigFileProvider.cs b/src/NzbDrone.Core/Configuration/ConfigFileProvider.cs index e48f9c245..d2cdd6fed 100644 --- a/src/NzbDrone.Core/Configuration/ConfigFileProvider.cs +++ b/src/NzbDrone.Core/Configuration/ConfigFileProvider.cs @@ -339,6 +339,20 @@ namespace NzbDrone.Core.Configuration } } + public void MigrateConfigFile() + { + if (!File.Exists(_configFile)) + { + return; + } + + // If SSL is enabled and a cert hash is still in the config file disable SSL + if (EnableSsl && GetValue("SslCertHash", null).IsNotNullOrWhiteSpace()) + { + SetValue("EnableSsl", false); + } + } + private void DeleteOldValues() { var xDoc = LoadConfigFile(); @@ -410,6 +424,7 @@ namespace NzbDrone.Core.Configuration public void HandleAsync(ApplicationStartedEvent message) { + MigrateConfigFile(); EnsureDefaultConfigFile(); DeleteOldValues(); } From b248163df598dc611ee919d525eb7357256d73d5 Mon Sep 17 00:00:00 2001 From: Mark McDowall <mark@mcdowall.ca> Date: Sat, 18 Nov 2023 18:51:23 -0800 Subject: [PATCH 121/136] New: Require password confirmation when setting or changing password --- .../AuthenticationRequiredModalContent.js | 15 +++++++++- .../src/Settings/General/SecuritySettings.js | 16 ++++++++++ src/NzbDrone.Core/Localization/Core/en.json | 2 ++ .../Validation/Paths/FileExistsValidator.cs | 2 +- .../Config/HostConfigController.cs | 29 +++++++++++++++---- .../Config/HostConfigResource.cs | 1 + 6 files changed, 58 insertions(+), 7 deletions(-) diff --git a/frontend/src/FirstRun/AuthenticationRequiredModalContent.js b/frontend/src/FirstRun/AuthenticationRequiredModalContent.js index a94730b16..8a2a1e62d 100644 --- a/frontend/src/FirstRun/AuthenticationRequiredModalContent.js +++ b/frontend/src/FirstRun/AuthenticationRequiredModalContent.js @@ -34,7 +34,8 @@ function AuthenticationRequiredModalContent(props) { authenticationMethod, authenticationRequired, username, - password + password, + passwordConfirmation } = settings; const authenticationEnabled = authenticationMethod && authenticationMethod.value !== 'none'; @@ -120,6 +121,18 @@ function AuthenticationRequiredModalContent(props) { {...password} /> </FormGroup> + + <FormGroup> + <FormLabel>{translate('PasswordConfirmation')}</FormLabel> + + <FormInputGroup + type={inputTypes.PASSWORD} + name="passwordConfirmation" + onChange={onInputChange} + helpTextWarning={passwordConfirmation?.value ? undefined : translate('AuthenticationRequiredPasswordConfirmationHelpTextWarning')} + {...passwordConfirmation} + /> + </FormGroup> </div> : null } diff --git a/frontend/src/Settings/General/SecuritySettings.js b/frontend/src/Settings/General/SecuritySettings.js index 011b308d0..e849ca03e 100644 --- a/frontend/src/Settings/General/SecuritySettings.js +++ b/frontend/src/Settings/General/SecuritySettings.js @@ -124,6 +124,7 @@ class SecuritySettings extends Component { authenticationRequired, username, password, + passwordConfirmation, apiKey, certificateValidation } = settings; @@ -193,6 +194,21 @@ class SecuritySettings extends Component { null } + { + authenticationEnabled ? + <FormGroup> + <FormLabel>{translate('PasswordConfirmation')}</FormLabel> + + <FormInputGroup + type={inputTypes.PASSWORD} + name="passwordConfirmation" + onChange={onInputChange} + {...passwordConfirmation} + /> + </FormGroup> : + null + } + <FormGroup> <FormLabel>{translate('ApiKey')}</FormLabel> diff --git a/src/NzbDrone.Core/Localization/Core/en.json b/src/NzbDrone.Core/Localization/Core/en.json index fbde87fe8..009a8cdf8 100644 --- a/src/NzbDrone.Core/Localization/Core/en.json +++ b/src/NzbDrone.Core/Localization/Core/en.json @@ -114,6 +114,7 @@ "AuthenticationRequired": "Authentication Required", "AuthenticationRequiredHelpText": "Change which requests authentication is required for. Do not change unless you understand the risks.", "AuthenticationRequiredPasswordHelpTextWarning": "Enter a new password", + "AuthenticationRequiredPasswordConfirmationHelpTextWarning": "Confirm new password", "AuthenticationRequiredUsernameHelpTextWarning": "Enter a new username", "AuthenticationRequiredWarning": "To prevent remote access without authentication, {appName} now requires authentication to be enabled. You can optionally disable authentication from local addresses.", "AutoAdd": "Auto Add", @@ -1137,6 +1138,7 @@ "ParseModalUnableToParse": "Unable to parse the provided title, please try again.", "PartialSeason": "Partial Season", "Password": "Password", + "PasswordConfirmation": "Password Confirmation", "Path": "Path", "Paused": "Paused", "Peers": "Peers", diff --git a/src/NzbDrone.Core/Validation/Paths/FileExistsValidator.cs b/src/NzbDrone.Core/Validation/Paths/FileExistsValidator.cs index 5393ce57b..327765537 100644 --- a/src/NzbDrone.Core/Validation/Paths/FileExistsValidator.cs +++ b/src/NzbDrone.Core/Validation/Paths/FileExistsValidator.cs @@ -1,4 +1,4 @@ -using FluentValidation.Validators; +using FluentValidation.Validators; using NzbDrone.Common.Disk; namespace NzbDrone.Core.Validation.Paths diff --git a/src/Sonarr.Api.V3/Config/HostConfigController.cs b/src/Sonarr.Api.V3/Config/HostConfigController.cs index 333212aff..50ef5c900 100644 --- a/src/Sonarr.Api.V3/Config/HostConfigController.cs +++ b/src/Sonarr.Api.V3/Config/HostConfigController.cs @@ -47,6 +47,9 @@ namespace Sonarr.Api.V3.Config SharedValidator.RuleFor(c => c.Password).NotEmpty().When(c => c.AuthenticationMethod == AuthenticationType.Basic || c.AuthenticationMethod == AuthenticationType.Forms); + SharedValidator.RuleFor(c => c.PasswordConfirmation) + .Must((resource, p) => IsMatchingPassword(resource)).WithMessage("Must match Password"); + SharedValidator.RuleFor(c => c.SslPort).ValidPort().When(c => c.EnableSsl); SharedValidator.RuleFor(c => c.SslPort).NotEqual(c => c.Port).When(c => c.EnableSsl); @@ -81,6 +84,23 @@ namespace Sonarr.Api.V3.Config return cert != null; } + private bool IsMatchingPassword(HostConfigResource resource) + { + var user = _userService.FindUser(); + + if (user != null && user.Password == resource.Password) + { + return true; + } + + if (resource.Password == resource.PasswordConfirmation) + { + return true; + } + + return false; + } + protected override HostConfigResource GetResourceById(int id) { return GetHostConfig(); @@ -93,11 +113,10 @@ namespace Sonarr.Api.V3.Config resource.Id = 1; var user = _userService.FindUser(); - if (user != null) - { - resource.Username = user.Username; - resource.Password = user.Password; - } + + resource.Username = user?.Username ?? string.Empty; + resource.Password = user?.Password ?? string.Empty; + resource.PasswordConfirmation = string.Empty; return resource; } diff --git a/src/Sonarr.Api.V3/Config/HostConfigResource.cs b/src/Sonarr.Api.V3/Config/HostConfigResource.cs index 3e25ae477..1b400800b 100644 --- a/src/Sonarr.Api.V3/Config/HostConfigResource.cs +++ b/src/Sonarr.Api.V3/Config/HostConfigResource.cs @@ -19,6 +19,7 @@ namespace Sonarr.Api.V3.Config public bool AnalyticsEnabled { get; set; } public string Username { get; set; } public string Password { get; set; } + public string PasswordConfirmation { get; set; } public string LogLevel { get; set; } public string ConsoleLogLevel { get; set; } public string Branch { get; set; } From 366d8a4e78e024bc045e4791d3a51af391f4e95a Mon Sep 17 00:00:00 2001 From: Bogdan <mynameisbogdan@users.noreply.github.com> Date: Sun, 19 Nov 2023 06:06:18 +0200 Subject: [PATCH 122/136] New: Confirmation before clearing blocklist --- frontend/src/Activity/Blocklist/Blocklist.js | 31 ++++++++++++++++++-- src/NzbDrone.Core/Localization/Core/en.json | 2 ++ 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/frontend/src/Activity/Blocklist/Blocklist.js b/frontend/src/Activity/Blocklist/Blocklist.js index b2af5a035..797aa5175 100644 --- a/frontend/src/Activity/Blocklist/Blocklist.js +++ b/frontend/src/Activity/Blocklist/Blocklist.js @@ -36,6 +36,7 @@ class Blocklist extends Component { lastToggled: null, selectedState: {}, isConfirmRemoveModalOpen: false, + isConfirmClearModalOpen: false, items: props.items }; } @@ -90,6 +91,19 @@ class Blocklist extends Component { this.setState({ isConfirmRemoveModalOpen: false }); }; + onClearBlocklistPress = () => { + this.setState({ isConfirmClearModalOpen: true }); + }; + + onClearBlocklistConfirmed = () => { + this.props.onClearBlocklistPress(); + this.setState({ isConfirmClearModalOpen: false }); + }; + + onConfirmClearModalClose = () => { + this.setState({ isConfirmClearModalOpen: false }); + }; + // // Render @@ -103,7 +117,6 @@ class Blocklist extends Component { totalRecords, isRemoving, isClearingBlocklistExecuting, - onClearBlocklistPress, ...otherProps } = this.props; @@ -111,7 +124,8 @@ class Blocklist extends Component { allSelected, allUnselected, selectedState, - isConfirmRemoveModalOpen + isConfirmRemoveModalOpen, + isConfirmClearModalOpen } = this.state; const selectedIds = this.getSelectedIds(); @@ -131,8 +145,9 @@ class Blocklist extends Component { <PageToolbarButton label={translate('Clear')} iconName={icons.CLEAR} + isDisabled={!items.length} isSpinning={isClearingBlocklistExecuting} - onPress={onClearBlocklistPress} + onPress={this.onClearBlocklistPress} /> </PageToolbarSection> @@ -215,6 +230,16 @@ class Blocklist extends Component { onConfirm={this.onRemoveSelectedConfirmed} onCancel={this.onConfirmRemoveModalClose} /> + + <ConfirmModal + isOpen={isConfirmClearModalOpen} + kind={kinds.DANGER} + title={translate('ClearBlocklist')} + message={translate('ClearBlocklistMessageText')} + confirmLabel={translate('Clear')} + onConfirm={this.onClearBlocklistConfirmed} + onCancel={this.onConfirmClearModalClose} + /> </PageContent> ); } diff --git a/src/NzbDrone.Core/Localization/Core/en.json b/src/NzbDrone.Core/Localization/Core/en.json index 009a8cdf8..39e6deff9 100644 --- a/src/NzbDrone.Core/Localization/Core/en.json +++ b/src/NzbDrone.Core/Localization/Core/en.json @@ -191,6 +191,8 @@ "ChownGroupHelpText": "Group name or gid. Use gid for remote file systems.", "ChownGroupHelpTextWarning": "This only works if the user running sonarr is the owner of the file. It's better to ensure the download client uses the same group as sonarr.", "Clear": "Clear", + "ClearBlocklist": "Clear blocklist", + "ClearBlocklistMessageText": "Are you sure you want to clear all items from the blocklist?", "ClickToChangeEpisode": "Click to change episode", "ClickToChangeLanguage": "Click to change language", "ClickToChangeQuality": "Click to change quality", From 81f3c4bd55324eeea08eeb579104decb2a061007 Mon Sep 17 00:00:00 2001 From: Stevie Robinson <stevie.robinson@gmail.com> Date: Sun, 19 Nov 2023 19:56:28 +0100 Subject: [PATCH 123/136] Cleanup appName tokens --- frontend/src/App/AppUpdatedModalContent.js | 4 ++-- frontend/src/App/ConnectionLostModal.js | 4 ++-- frontend/src/Calendar/iCal/CalendarLinkModalContent.js | 2 +- frontend/src/FirstRun/AuthenticationRequiredModalContent.js | 4 ++-- .../RemotePathMappings/RemotePathMappings.js | 2 +- frontend/src/Settings/General/SecuritySettings.js | 4 ++-- frontend/src/Settings/General/UpdateSettings.js | 2 +- src/NzbDrone.Core/Localization/Core/en.json | 6 +++--- src/NzbDrone.Core/Localization/Core/fr.json | 2 +- src/NzbDrone.Core/Localization/Core/pt_BR.json | 2 +- src/NzbDrone.Core/Localization/Core/zh_CN.json | 2 +- 11 files changed, 17 insertions(+), 17 deletions(-) diff --git a/frontend/src/App/AppUpdatedModalContent.js b/frontend/src/App/AppUpdatedModalContent.js index 1f81e1660..8cce1bc16 100644 --- a/frontend/src/App/AppUpdatedModalContent.js +++ b/frontend/src/App/AppUpdatedModalContent.js @@ -65,12 +65,12 @@ function AppUpdatedModalContent(props) { return ( <ModalContent onModalClose={onModalClose}> <ModalHeader> - {translate('AppUpdated', { appName: 'Sonarr' })} + {translate('AppUpdated')} </ModalHeader> <ModalBody> <div> - <InlineMarkdown data={translate('AppUpdatedVersion', { appName: 'Sonarr', version })} blockClassName={styles.version} /> + <InlineMarkdown data={translate('AppUpdatedVersion', { version })} blockClassName={styles.version} /> </div> { diff --git a/frontend/src/App/ConnectionLostModal.js b/frontend/src/App/ConnectionLostModal.js index 3497d8345..5c08f491f 100644 --- a/frontend/src/App/ConnectionLostModal.js +++ b/frontend/src/App/ConnectionLostModal.js @@ -28,11 +28,11 @@ function ConnectionLostModal(props) { <ModalBody> <div> - {translate('ConnectionLostToBackend', { appName: 'Sonarr' })} + {translate('ConnectionLostToBackend')} </div> <div className={styles.automatic}> - {translate('ConnectionLostReconnect', { appName: 'Sonarr' })} + {translate('ConnectionLostReconnect')} </div> </ModalBody> <ModalFooter> diff --git a/frontend/src/Calendar/iCal/CalendarLinkModalContent.js b/frontend/src/Calendar/iCal/CalendarLinkModalContent.js index f7a904e21..a35c46a79 100644 --- a/frontend/src/Calendar/iCal/CalendarLinkModalContent.js +++ b/frontend/src/Calendar/iCal/CalendarLinkModalContent.js @@ -116,7 +116,7 @@ class CalendarLinkModalContent extends Component { return ( <ModalContent onModalClose={onModalClose}> <ModalHeader> - {translate('CalendarFeed', { appName: 'Sonarr' })} + {translate('CalendarFeed')} </ModalHeader> <ModalBody> diff --git a/frontend/src/FirstRun/AuthenticationRequiredModalContent.js b/frontend/src/FirstRun/AuthenticationRequiredModalContent.js index 8a2a1e62d..f3646bc96 100644 --- a/frontend/src/FirstRun/AuthenticationRequiredModalContent.js +++ b/frontend/src/FirstRun/AuthenticationRequiredModalContent.js @@ -64,7 +64,7 @@ function AuthenticationRequiredModalContent(props) { className={styles.authRequiredAlert} kind={kinds.WARNING} > - {translate('AuthenticationRequiredWarning', { appName: 'Sonarr' })} + {translate('AuthenticationRequiredWarning')} </Alert> { @@ -77,7 +77,7 @@ function AuthenticationRequiredModalContent(props) { type={inputTypes.SELECT} name="authenticationMethod" values={authenticationMethodOptions} - helpText={translate('AuthenticationMethodHelpText', { appName: 'Sonarr' })} + helpText={translate('AuthenticationMethodHelpText')} helpTextWarning={authenticationMethod.value === 'none' ? translate('AuthenticationMethodHelpTextWarning') : undefined} helpLink="https://wiki.servarr.com/sonarr/faq#forced-authentication" onChange={onInputChange} diff --git a/frontend/src/Settings/DownloadClients/RemotePathMappings/RemotePathMappings.js b/frontend/src/Settings/DownloadClients/RemotePathMappings/RemotePathMappings.js index 78c3c7e02..1a942a19e 100644 --- a/frontend/src/Settings/DownloadClients/RemotePathMappings/RemotePathMappings.js +++ b/frontend/src/Settings/DownloadClients/RemotePathMappings/RemotePathMappings.js @@ -54,7 +54,7 @@ class RemotePathMappings extends Component { > <Alert kind={kinds.INFO}> - <InlineMarkdown data={translate('RemotePathMappingsInfo', { app: 'Sonarr', wikiLink: 'https://wiki.servarr.com/sonarr/settings#remote-path-mappings' })} /> + <InlineMarkdown data={translate('RemotePathMappingsInfo', { wikiLink: 'https://wiki.servarr.com/sonarr/settings#remote-path-mappings' })} /> </Alert> <div className={styles.remotePathMappingsHeader}> diff --git a/frontend/src/Settings/General/SecuritySettings.js b/frontend/src/Settings/General/SecuritySettings.js index e849ca03e..8e2597741 100644 --- a/frontend/src/Settings/General/SecuritySettings.js +++ b/frontend/src/Settings/General/SecuritySettings.js @@ -140,8 +140,8 @@ class SecuritySettings extends Component { type={inputTypes.SELECT} name="authenticationMethod" values={authenticationMethodOptions} - helpText={translate('AuthenticationMethodHelpText', { appName: 'Sonarr' })} - helpTextWarning={translate('AuthenticationRequiredWarning', { appName: 'Sonarr' })} + helpText={translate('AuthenticationMethodHelpText')} + helpTextWarning={translate('AuthenticationRequiredWarning')} onChange={onInputChange} {...authenticationMethod} /> diff --git a/frontend/src/Settings/General/UpdateSettings.js b/frontend/src/Settings/General/UpdateSettings.js index 9e5cf0a6b..4558650c0 100644 --- a/frontend/src/Settings/General/UpdateSettings.js +++ b/frontend/src/Settings/General/UpdateSettings.js @@ -83,7 +83,7 @@ function UpdateSettings(props) { type={inputTypes.CHECK} name="updateAutomatically" helpText={translate('UpdateAutomaticallyHelpText')} - helpTextWarning={updateMechanism.value === 'docker' ? translate('AutomaticUpdatesDisabledDocker', { appName: 'Sonarr' }) : undefined} + helpTextWarning={updateMechanism.value === 'docker' ? translate('AutomaticUpdatesDisabledDocker') : undefined} onChange={onInputChange} {...updateAutomatically} /> diff --git a/src/NzbDrone.Core/Localization/Core/en.json b/src/NzbDrone.Core/Localization/Core/en.json index 39e6deff9..331e4da45 100644 --- a/src/NzbDrone.Core/Localization/Core/en.json +++ b/src/NzbDrone.Core/Localization/Core/en.json @@ -184,12 +184,12 @@ "CheckDownloadClientForDetails": "check download client for more details", "ChmodFolder": "chmod Folder", "ChmodFolderHelpText": "Octal, applied during import/rename to media folders and files (without execute bits)", - "ChmodFolderHelpTextWarning": "This only works if the user running sonarr is the owner of the file. It's better to ensure the download client sets the permissions properly.", + "ChmodFolderHelpTextWarning": "This only works if the user running {appName} is the owner of the file. It's better to ensure the download client sets the permissions properly.", "ChooseAnotherFolder": "Choose another folder", "ChooseImportMode": "Choose Import Mode", "ChownGroup": "chown Group", "ChownGroupHelpText": "Group name or gid. Use gid for remote file systems.", - "ChownGroupHelpTextWarning": "This only works if the user running sonarr is the owner of the file. It's better to ensure the download client uses the same group as sonarr.", + "ChownGroupHelpTextWarning": "This only works if the user running {appName} is the owner of the file. It's better to ensure the download client uses the same group as {appName}.", "Clear": "Clear", "ClearBlocklist": "Clear blocklist", "ClearBlocklistMessageText": "Are you sure you want to clear all items from the blocklist?", @@ -1272,7 +1272,7 @@ "RemotePathMappingRemotePathHelpText": "Root path to the directory that the Download Client accesses", "RemotePathMappingWrongOSPathHealthCheckMessage": "Remote download client {downloadClientName} places downloads in {path} but this is not a valid {osName} path. Review your remote path mappings and download client settings.", "RemotePathMappings": "Remote Path Mappings", - "RemotePathMappingsInfo": "Remote Path Mappings are very rarely required, if {app} and your download client are on the same system it is better to match your paths. For more information see the [wiki]({wikiLink})", + "RemotePathMappingsInfo": "Remote Path Mappings are very rarely required, if {appName} and your download client are on the same system it is better to match your paths. For more information see the [wiki]({wikiLink})", "RemotePathMappingsLoadError": "Unable to load Remote Path Mappings", "Remove": "Remove", "RemoveCompleted": "Remove Completed", diff --git a/src/NzbDrone.Core/Localization/Core/fr.json b/src/NzbDrone.Core/Localization/Core/fr.json index 0c55d6ee2..1681123a6 100644 --- a/src/NzbDrone.Core/Localization/Core/fr.json +++ b/src/NzbDrone.Core/Localization/Core/fr.json @@ -671,7 +671,7 @@ "RemotePathMappingRemoteDownloadClientHealthCheckMessage": "Le client de téléchargement à distance {0} a signalé des fichiers dans {1} mais ce répertoire ne semble pas exister. Il manque probablement un mappage de chemin distant.", "RemotePathMappingRemotePathHelpText": "Chemin racine du répertoire auquel accède le client de téléchargement", "RemotePathMappingWrongOSPathHealthCheckMessage": "Le client de téléchargement à distance {0} place les téléchargements dans {1} mais ce n'est pas un chemin {2} valide. Vérifiez vos mappages de chemins distants et téléchargez les paramètres client.", - "RemotePathMappingsInfo": "Les mappages de chemins distants sont très rarement requis. Si {app} et votre client de téléchargement sont sur le même système, il est préférable de faire correspondre vos chemins. Pour plus d'informations, consultez le [wiki]({wikiLink})", + "RemotePathMappingsInfo": "Les mappages de chemins distants sont très rarement requis. Si {appName} et votre client de téléchargement sont sur le même système, il est préférable de faire correspondre vos chemins. Pour plus d'informations, consultez le [wiki]({wikiLink})", "RemotePathMappingsLoadError": "Impossible de charger les mappages de chemin distant", "RemoveCompleted": "Supprimer terminé", "RemoveCompletedDownloadsHelpText": "Supprimer les téléchargements importés de l'historique du client de téléchargement", diff --git a/src/NzbDrone.Core/Localization/Core/pt_BR.json b/src/NzbDrone.Core/Localization/Core/pt_BR.json index f4acff10f..42b682a06 100644 --- a/src/NzbDrone.Core/Localization/Core/pt_BR.json +++ b/src/NzbDrone.Core/Localization/Core/pt_BR.json @@ -1233,7 +1233,7 @@ "ReleaseSceneIndicatorSourceMessage": "Existem lançamentos de {message} com numeração ambígua, incapaz de identificar o episódio de forma confiável.", "ReleaseSceneIndicatorUnknownMessage": "A numeração varia para este episódio e o lançamento não corresponde a nenhum mapeamento conhecido.", "ReleaseSceneIndicatorUnknownSeries": "Episódio ou série desconhecida.", - "RemotePathMappingsInfo": "Raramente são necessários mapeamentos de caminho remoto, se {app} e seu cliente de download estiverem no mesmo sistema, é melhor combinar seus caminhos. Para mais informações, consulte o [wiki]({wikiLink})", + "RemotePathMappingsInfo": "Raramente são necessários mapeamentos de caminho remoto, se {appName} e seu cliente de download estiverem no mesmo sistema, é melhor combinar seus caminhos. Para mais informações, consulte o [wiki]({wikiLink})", "RemoveFilter": "Remover filtro", "RootFolderSelectFreeSpace": "{freeSpace} Livre", "Search": "Pesquisar", diff --git a/src/NzbDrone.Core/Localization/Core/zh_CN.json b/src/NzbDrone.Core/Localization/Core/zh_CN.json index 79e85f711..9848c5834 100644 --- a/src/NzbDrone.Core/Localization/Core/zh_CN.json +++ b/src/NzbDrone.Core/Localization/Core/zh_CN.json @@ -814,7 +814,7 @@ "HistorySeason": "查看本季历史记录", "ImportScriptPathHelpText": "用于导入的脚本的路径", "IncludeCustomFormatWhenRenaming": "重命名时包含自定义格式", - "RemotePathMappingsInfo": "很少需要远程路径映射,如果{app}和您的下载客户端在同一系统上,则最好匹配您的路径。更多信息,请参阅[wiki]({wikiLink})", + "RemotePathMappingsInfo": "很少需要远程路径映射,如果{appName}和您的下载客户端在同一系统上,则最好匹配您的路径。更多信息,请参阅[wiki]({wikiLink})", "IndexerPriorityHelpText": "索引器优先级从1(最高)到50(最低),默认25。当资源连接中断时寻找同等资源时使用,否则{appName}将依旧使用已启用的索引器进行RSS同步并搜索", "IndexerTagHelpText": "仅对至少有一个匹配标记的剧集使用此索引器。留空则适用于所有剧集。", "InteractiveImportNoFilesFound": "在所选文件夹中找不到视频文件", From 4f57c088a1ecdd11d0cf4d8d8d15b10e4b20a4e6 Mon Sep 17 00:00:00 2001 From: nexus671 <acurrymuir@gmail.com> Date: Sun, 19 Nov 2023 14:27:31 -0500 Subject: [PATCH 124/136] Fixed: Plex RSS TMDb ID Lookup --- .../ImportLists/Rss/Plex/PlexRssImportParser.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/NzbDrone.Core/ImportLists/Rss/Plex/PlexRssImportParser.cs b/src/NzbDrone.Core/ImportLists/Rss/Plex/PlexRssImportParser.cs index c1b7307fe..2dda602cb 100644 --- a/src/NzbDrone.Core/ImportLists/Rss/Plex/PlexRssImportParser.cs +++ b/src/NzbDrone.Core/ImportLists/Rss/Plex/PlexRssImportParser.cs @@ -47,13 +47,13 @@ namespace NzbDrone.Core.ImportLists.Rss.Plex if (int.TryParse(guid.Replace("tmdb://", ""), out var tmdbId)) { - info.TmdbId = tvdbId; + info.TmdbId = tmdbId; } } - if (info.ImdbId.IsNullOrWhiteSpace() && info.TvdbId == 0) + if (info.ImdbId.IsNullOrWhiteSpace() && info.TvdbId == 0 && info.TmdbId == 0) { - throw new UnsupportedFeedException("Each item in the RSS feed must have a guid element with a IMDB ID or TVDB ID"); + throw new UnsupportedFeedException("Each item in the RSS feed must have a guid element with a IMDB ID, TVDB ID or TMDB ID"); } return info; From dd5a4ad0dcafd70abd951b2942c825a238160145 Mon Sep 17 00:00:00 2001 From: Weblate <noreply@weblate.org> Date: Sun, 19 Nov 2023 18:51:37 +0000 Subject: [PATCH 125/136] 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: Havok Dan <havokdan@yahoo.com.br> Co-authored-by: Weblate <noreply@weblate.org> Co-authored-by: Андрей <andryfly7@gmail.com> Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/ Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/pt_BR/ Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/ru/ Translation: Servarr/Sonarr --- src/NzbDrone.Core/Localization/Core/fr.json | 2 - .../Localization/Core/pt_BR.json | 61 +++++++++++++++++-- src/NzbDrone.Core/Localization/Core/ru.json | 35 ++++++++++- .../Localization/Core/zh_CN.json | 2 - 4 files changed, 90 insertions(+), 10 deletions(-) diff --git a/src/NzbDrone.Core/Localization/Core/fr.json b/src/NzbDrone.Core/Localization/Core/fr.json index 1681123a6..536bcf5b7 100644 --- a/src/NzbDrone.Core/Localization/Core/fr.json +++ b/src/NzbDrone.Core/Localization/Core/fr.json @@ -859,8 +859,6 @@ "MonitorFirstSeason": "Première saison", "MonitorFirstSeasonDescription": "Surveillez tous les épisodes de la première saison. Toutes les autres saisons seront ignorées", "MonitorFutureEpisodes": "Épisodes futurs", - "MonitorLatestSeason": "Dernière saison", - "MonitorLatestSeasonDescription": "Surveillez tous les épisodes de la dernière saison diffusés au cours des 90 derniers jours et toutes les saisons futures", "MonitorMissingEpisodes": "Épisodes manquants", "MonitorMissingEpisodesDescription": "Surveiller les épisodes qui n'ont pas de fichiers ou qui n'ont pas encore été diffusés", "MonitorNone": "Aucun", diff --git a/src/NzbDrone.Core/Localization/Core/pt_BR.json b/src/NzbDrone.Core/Localization/Core/pt_BR.json index 42b682a06..15fe666fe 100644 --- a/src/NzbDrone.Core/Localization/Core/pt_BR.json +++ b/src/NzbDrone.Core/Localization/Core/pt_BR.json @@ -964,7 +964,6 @@ "MonitorFirstSeasonDescription": "Monitorar todos os episódios da primeira temporada. As demais temporadas serão ignoradas", "MonitorFutureEpisodes": "Futuros Episódios", "MonitorFutureEpisodesDescription": "Monitorar episódios que não foram exibidos", - "MonitorLatestSeason": "Última Temporada", "MonitorMissingEpisodes": "Episódios ausentes", "MonitorMissingEpisodesDescription": "Monitora os episódios que não possuem arquivos ou ainda não foram ao ar", "MonitorNone": "Nenhum", @@ -987,7 +986,6 @@ "DailyTypeDescription": "Episódios lançados diariamente ou com menos frequência que usam ano-mês-dia (2023-08-04)", "LibraryImportTipsUseRootFolder": "Aponte o {appName} para a pasta que contém todas as suas séries, não uma específica. Por exemplo. \"`{goodFolderExample}`\" e não \"`{badFolderExample}`\". Além disso, cada série deve estar em sua própria pasta dentro da pasta raiz/biblioteca.", "MonitorExistingEpisodesDescription": "Monitorar os episódios que possuem arquivos ou ainda não foram exibidos", - "MonitorLatestSeasonDescription": "Monitora todos os episódios da última temporada que foram ao ar nos últimos 90 dias e todas as temporadas futuras", "NoSeriesHaveBeenAdded": "Você ainda não adicionou nenhuma série. Deseja importar algumas ou todas as suas séries primeiro?", "UnmonitorSpecialsDescription": "Cancela o monitoramento de todos os episódios especiais sem alterar o status monitorado de outros episódios", "WhyCantIFindMyShow": "Por que não consigo encontrar meu programa?", @@ -1270,7 +1268,7 @@ "OverrideGrabNoQuality": "A qualidade deve ser selecionada", "OverrideGrabNoSeries": "A série deve ser selecionada", "Parse": "Analisar", - "ParseModalHelpTextDetails": "{appName} tentará analisar o título e mostrar detalhes sobre ele", + "ParseModalHelpTextDetails": "{appName} irá tentar analisar o título e mostrará para você os detalhes sobre isso", "RecentChanges": "Mudanças Recentes", "ReleaseRejected": "Lançamento Rejeitado", "ReleaseSceneIndicatorAssumingScene": "Assumindo a Numeração da Scene.", @@ -1633,5 +1631,60 @@ "TorrentBlackholeSaveMagnetFilesReadOnlyHelpText": "Em vez de mover arquivos, isso instruirá {appName} a copiar ou vincular (dependendo das configurações/configuração do sistema)", "TorrentBlackholeTorrentFolder": "Pasta do Torrent", "UseSsl": "Usar SSL", - "UsenetBlackholeNzbFolder": "Pasta Nzb" + "UsenetBlackholeNzbFolder": "Pasta Nzb", + "IndexerIPTorrentsSettingsFeedUrl": "URL do Feed", + "IndexerIPTorrentsSettingsFeedUrlHelpText": "A URL completa do feed RSS gerado pelo IPTorrents, usando apenas as categorias que você selecionou (HD, SD, x264, etc...)", + "IndexerSettingsAdditionalParameters": "Parâmetros Adicionais", + "IndexerSettingsAdditionalParametersNyaa": "Parâmetros Adicionais", + "IndexerSettingsAllowZeroSize": "Permitir tamanho zero", + "IndexerSettingsAnimeCategories": "Categorias de anime", + "IndexerSettingsAnimeStandardFormatSearch": "Formato de Pesquisa Padrão para Anime", + "IndexerSettingsApiPath": "Caminho da API", + "IndexerSettingsApiPathHelpText": "Caminho para a API, geralmente {url}", + "IndexerSettingsApiUrl": "URL da API", + "IndexerSettingsApiUrlHelpText": "Não mude isso a menos que você saiba o que está fazendo. Já que sua chave API será enviada para esse host.", + "IndexerSettingsCategories": "Categorias", + "IndexerSettingsCategoriesHelpText": "Lista suspensa, deixe em branco para desativar programas padrão/diários", + "IndexerSettingsCookie": "Cookie", + "IndexerSettingsMinimumSeeders": "Mínimo de Semeadores", + "IndexerSettingsMinimumSeedersHelpText": "Número mínimo de semeadores necessário.", + "IndexerSettingsPasskey": "Passkey", + "IndexerSettingsRssUrl": "URL do RSS", + "IndexerSettingsSeasonPackSeedTime": "Tempo de Seed de Pack de Temporada", + "IndexerSettingsSeedRatio": "Taxa de Semeação", + "IndexerSettingsSeedTime": "Tempo de Semeação", + "IndexerSettingsSeedTimeHelpText": "O tempo que um torrent deve ser propagado antes de parar, vazio usa o padrão do cliente de download", + "IndexerSettingsWebsiteUrl": "URL do Website", + "IndexerValidationCloudFlareCaptchaExpired": "O token CloudFlare CAPTCHA expirou, atualize-o.", + "IndexerValidationFeedNotSupported": "O feed do indexador não é compatível: {exceptionMessage}", + "IndexerValidationJackettAllNotSupportedHelpText": "Todos os endpoints de Jackett não são suportados. Adicione indexadores individualmente", + "IndexerValidationJackettNoResultsInConfiguredCategories": "Consulta bem-sucedida, mas nenhum resultado nas categorias configuradas foi retornado do seu indexador. Isso pode ser um problema com o indexador ou com as configurações de categoria do indexador.", + "IndexerValidationJackettNoRssFeedQueryAvailable": "Nenhuma consulta de feed RSS disponível. Isso pode ser um problema com o indexador ou com as configurações de categoria do indexador.", + "IndexerValidationRequestLimitReached": "Limite de solicitações atingido: {exceptionMessage}", + "IndexerValidationSearchParametersNotSupported": "O indexador não oferece suporte aos parâmetros de pesquisa obrigatórios", + "IndexerValidationUnableToConnectResolutionFailure": "Não é possível conectar-se à falha de conexão do indexador. Verifique sua conexão com o servidor e DNS do indexador. {exceptionMessage}.", + "IndexerValidationUnableToConnectServerUnavailable": "Não foi possível conectar-se ao indexador, o servidor do indexador não está disponível. Tente mais tarde. {exceptionMessage}.", + "IndexerSettingsAdditionalNewznabParametersHelpText": "Observe que se você alterar a categoria, terá que adicionar regras obrigatórias/restritas sobre os subgrupos para evitar lançamentos em idiomas estrangeiros.", + "IndexerSettingsAllowZeroSizeHelpText": "Ativar isso permitirá que você use feeds que não especificam o tamanho do lançamento, mas tenha cuidado, pois verificações relacionadas ao tamanho não serão realizadas.", + "IndexerSettingsAnimeCategoriesHelpText": "Lista suspensa, deixe em branco para desativar o anime", + "IndexerSettingsAnimeStandardFormatSearchHelpText": "Pesquise também por animes usando a numeração padrão", + "IndexerSettingsCookieHelpText": "se o seu site exigir um cookie de login para acessar o RSS, você terá que recuperá-lo por meio de um navegador.", + "IndexerSettingsRssUrlHelpText": "Insira a URL de um feed RSS compatível com {indexer}", + "IndexerSettingsSeasonPackSeedTimeHelpText": "O tempo que um torrent de pack de temporada deve ser semeado antes de parar, vazio usa o padrão do cliente de download", + "IndexerSettingsSeedRatioHelpText": "A proporção que um torrent deve atingir antes de parar, vazio usa o padrão do cliente de download. A proporção deve ser de pelo menos 1,0 e seguir as regras dos indexadores", + "IndexerValidationCloudFlareCaptchaRequired": "Site protegido por CloudFlare CAPTCHA. É necessário um token CAPTCHA válido.", + "IndexerValidationInvalidApiKey": "Chave de API inválida", + "IndexerValidationJackettAllNotSupported": "Todos os endpoints de Jackett não são suportados. Adicione indexadores individualmente", + "IndexerValidationQueryNotSupported": "O indexador não oferece suporte à consulta atual. Verifique se as categorias e/ou busca por temporadas/episódios são suportadas. Verifique o registro para mais detalhes.", + "IndexerValidationTestAbortedDueToError": "O teste foi abortado devido a um erro: {exceptionMessage}", + "IndexerValidationUnableToConnect": "Não foi possível conectar-se ao indexador: {exceptionMessage}. Verifique o registro em torno deste erro para obter detalhes", + "IndexerValidationUnableToConnectHttpError": "Não foi possível conectar-se ao indexador. Verifique suas configurações de DNS e certifique-se de que o IPv6 esteja funcionando ou desativado. {exceptionMessage}.", + "IndexerValidationUnableToConnectInvalidCredentials": "Não foi possível conectar-se ao indexador, credenciais inválidas. {exceptionMessage}.", + "IndexerValidationUnableToConnectTimeout": "Não foi possível conectar-se ao indexador, possivelmente devido ao tempo limite. Tente novamente ou verifique as configurações de rede. {exceptionMessage}.", + "IndexerHDBitsSettingsCategories": "Categorias", + "IndexerHDBitsSettingsCategoriesHelpText": "se não for especificado, todas as opções serão usadas.", + "IndexerHDBitsSettingsCodecs": "Codecs", + "IndexerHDBitsSettingsCodecsHelpText": "Se não for especificado, todas as opções serão usadas.", + "IndexerHDBitsSettingsMediums": "Mediums", + "IndexerHDBitsSettingsMediumsHelpText": "se não for especificado, todas as opções serão usadas." } diff --git a/src/NzbDrone.Core/Localization/Core/ru.json b/src/NzbDrone.Core/Localization/Core/ru.json index 18e1cfe81..f78efce54 100644 --- a/src/NzbDrone.Core/Localization/Core/ru.json +++ b/src/NzbDrone.Core/Localization/Core/ru.json @@ -7,7 +7,7 @@ "AppDataLocationHealthCheckMessage": "Обновление будет не возможно, во избежание удаления данных программы во время обновления", "ApplyChanges": "Применить изменения", "DownloadClientCheckNoneAvailableHealthCheckMessage": "Ни один загрузчик не доступен", - "DownloadClientCheckUnableToCommunicateWithHealthCheckMessage": "Невозможно связаться с {downloadClientName}.", + "DownloadClientCheckUnableToCommunicateWithHealthCheckMessage": "Невозможно связаться с {downloadClientName}. {errorMessage}", "DownloadClientRootFolderHealthCheckMessage": "Клиент загрузки {downloadClientName} помещает загрузки в корневую папку {rootFolderPath}. Вы не должны загружать в корневую папку.", "DownloadClientStatusAllClientHealthCheckMessage": "Все клиенты загрузки недоступны из-за сбоев", "DownloadClientStatusSingleClientHealthCheckMessage": "Клиенты для скачивания недоступны из-за ошибок: {downloadClientNames}", @@ -165,5 +165,36 @@ "AddConnectionImplementation": "Добавить подключение - {implementationName}", "AddCustomFilter": "Добавить специальный фильтр", "Absolute": "Абсолютный", - "AddConditionImplementation": "Добавить условие - {implementationName}" + "AddConditionImplementation": "Добавить условие - {implementationName}", + "AddNew": "Добавить", + "AddRootFolder": "Добавить корневой каталог", + "AgeWhenGrabbed": "Возраст (когда захвачен)", + "Age": "Возраст", + "All": "Все", + "AddList": "Добавить список", + "AddListError": "Не удалось добавить новый список, попробуйте еще раз.", + "AddIndexerError": "Не удалось добавить новый индексатор, повторите попытку.", + "AddImportList": "Добавить список импорта", + "AddQualityProfile": "Добавить профиль качества", + "AddQualityProfileError": "Не удалось добавить новый профиль качества. Повторите попытку.", + "AfterManualRefresh": "После обновления вручную", + "AddExclusion": "Добавить исключение", + "AddIndexer": "Добавить индексатор", + "AddImportListExclusion": "Добавить исключение из списка импорта", + "AddNewRestriction": "Добавить новое ограничение", + "AddNotificationError": "Невозможно добавить новое уведомление, попробуйте еще раз.", + "AllResultsAreHiddenByTheAppliedFilter": "Все результаты скрыты фильтром", + "ParseModalHelpTextDetails": "{appName} попытается определить название и показать подробную информацию о нем", + "AddDownloadClientImplementation": "Добавить клиент загрузки - {implementationName}", + "AddImportListImplementation": "Добавить список импорта - {implementationName}", + "AddIndexerImplementation": "Добавить индексатор - {implementationName}", + "AddNewSeriesError": "Не удалось загрузить результат поиска, попробуйте еще раз.", + "AddListExclusionHelpText": "Запретить добавление серий в {appName} по спискам", + "AddToDownloadQueue": "Добавить в очередь загрузки", + "AddedDate": "Добавлено: {date}", + "AddedToDownloadQueue": "Добавлено в очередь на скачивание", + "AllFiles": "Все файлы", + "AllSeriesAreHiddenByTheAppliedFilter": "Все результаты скрыты фильтром", + "AlreadyInYourLibrary": "Уже в вашей библиотеке", + "Always": "Всегда" } diff --git a/src/NzbDrone.Core/Localization/Core/zh_CN.json b/src/NzbDrone.Core/Localization/Core/zh_CN.json index 9848c5834..dbea0aeb4 100644 --- a/src/NzbDrone.Core/Localization/Core/zh_CN.json +++ b/src/NzbDrone.Core/Localization/Core/zh_CN.json @@ -878,7 +878,6 @@ "MetadataSettingsSummary": "导入或刷新剧集时创建元数据文件", "MetadataSourceSettings": "元数据源设置", "Mixed": "混合", - "MonitorLatestSeason": "最新季", "MonitorFutureEpisodesDescription": "监控尚未播出的集", "MonitorSpecialsDescription": "监控所有特别节目,而不更改其他集的监控状态", "OnSeriesDelete": "剧集删除时", @@ -1378,7 +1377,6 @@ "SearchForQuery": "搜索{query}", "ImportUsingScriptHelpText": "使用脚本复制文件以进行导入(例如用于转码)", "LanguagesLoadError": "无法加载语言", - "MonitorLatestSeasonDescription": "监控过去90天内播出的最新一季的所有集以及未来的所有集", "MonitorMissingEpisodes": "缺少集", "OneMinute": "1分钟", "OnUpgrade": "升级中", From 0bfa7aed83287b765ee80d7b15f16c412fe03c76 Mon Sep 17 00:00:00 2001 From: Sonarr <development@sonarr.tv> Date: Sun, 19 Nov 2023 18:56:28 +0000 Subject: [PATCH 126/136] Automated API Docs update ignore-downstream --- src/Sonarr.Api.V3/openapi.json | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/Sonarr.Api.V3/openapi.json b/src/Sonarr.Api.V3/openapi.json index 70271b11d..4c4f2e5f5 100644 --- a/src/Sonarr.Api.V3/openapi.json +++ b/src/Sonarr.Api.V3/openapi.json @@ -8557,6 +8557,10 @@ "type": "string", "nullable": true }, + "passwordConfirmation": { + "type": "string", + "nullable": true + }, "logLevel": { "type": "string", "nullable": true @@ -8810,6 +8814,9 @@ "shouldMonitor": { "$ref": "#/components/schemas/MonitorTypes" }, + "monitorNewItems": { + "$ref": "#/components/schemas/NewItemMonitorTypes" + }, "rootFolderPath": { "type": "string", "nullable": true @@ -9609,8 +9616,10 @@ "missing", "existing", "firstSeason", + "lastSeason", "latestSeason", "pilot", + "recent", "monitorSpecials", "unmonitorSpecials", "none" @@ -9700,6 +9709,13 @@ }, "additionalProperties": false }, + "NewItemMonitorTypes": { + "enum": [ + "all", + "none" + ], + "type": "string" + }, "NotificationResource": { "type": "object", "properties": { @@ -11047,6 +11063,9 @@ "type": "boolean", "nullable": true }, + "monitorNewItems": { + "$ref": "#/components/schemas/NewItemMonitorTypes" + }, "qualityProfileId": { "type": "integer", "format": "int32", @@ -11180,6 +11199,9 @@ "monitored": { "type": "boolean" }, + "monitorNewItems": { + "$ref": "#/components/schemas/NewItemMonitorTypes" + }, "useSceneNumbering": { "type": "boolean" }, From 7464c09a46797d8d448f463f7dbacfddf8edcc74 Mon Sep 17 00:00:00 2001 From: Stevie Robinson <stevie.robinson@gmail.com> Date: Sun, 19 Nov 2023 20:34:32 +0100 Subject: [PATCH 127/136] Update translation keys to be Sonarr specific --- .../History/Details/HistoryDetails.js | 2 +- .../Activity/History/HistoryEventTypeCell.js | 6 +- .../Activity/Queue/RemoveQueueItemModal.js | 2 +- .../Activity/Queue/RemoveQueueItemsModal.js | 2 +- .../SelectFolder/ImportSeriesSelectFolder.js | 6 +- .../SeriesMonitoringOptionsPopoverContent.js | 12 +- .../src/AddSeries/SeriesTypePopoverContent.js | 6 +- frontend/src/Calendar/Legend/Legend.js | 16 +-- .../Calendar/iCal/CalendarLinkModalContent.js | 4 +- .../Components/Form/SeriesTypeSelectInput.tsx | 6 +- .../InteractiveSearch/InteractiveSearch.js | 2 +- .../InteractiveSearchRow.tsx | 4 +- .../Series/Delete/DeleteSeriesModalContent.js | 2 +- .../src/Series/Edit/EditSeriesModalContent.js | 2 +- .../SeriesIndexPosterOptionsModalContent.tsx | 2 +- .../Delete/DeleteSeriesModalContent.tsx | 2 +- .../EditDownloadClientModalContent.js | 2 +- .../ImportLists/AddImportListModalContent.js | 2 +- .../ImportLists/EditImportListModalContent.js | 2 +- .../Indexers/EditIndexerModalContent.js | 2 +- .../MediaManagement/MediaManagement.js | 8 +- .../EditNotificationModalContent.js | 2 +- .../Delay/EditDelayProfileModalContent.js | 4 +- .../Quality/EditQualityProfileModalContent.js | 6 +- .../Release/EditReleaseProfileModalContent.js | 2 +- .../Quality/Definition/QualityDefinitions.js | 2 +- frontend/src/Settings/Settings.js | 4 +- .../src/Utilities/Series/monitorOptions.js | 6 +- .../src/Wanted/CutoffUnmet/CutoffUnmet.js | 4 +- frontend/src/Wanted/Missing/Missing.js | 4 +- .../Download/Clients/Deluge/DelugeSettings.cs | 4 +- .../FreeboxDownloadSettings.cs | 4 +- .../Clients/NzbVortex/NzbVortexSettings.cs | 4 +- .../Download/Clients/Nzbget/NzbgetSettings.cs | 4 +- .../QBittorrent/QBittorrentSettings.cs | 4 +- .../Clients/Sabnzbd/SabnzbdSettings.cs | 4 +- .../Transmission/TransmissionSettings.cs | 4 +- .../Clients/rTorrent/RTorrentSettings.cs | 4 +- .../Clients/uTorrent/UTorrentSettings.cs | 4 +- .../HealthCheck/Checks/MountCheck.cs | 2 +- .../Checks/RemotePathMappingCheck.cs | 4 +- src/NzbDrone.Core/Indexers/HttpIndexerBase.cs | 2 +- src/NzbDrone.Core/Localization/Core/cs.json | 32 ++--- src/NzbDrone.Core/Localization/Core/en.json | 128 +++++++++--------- src/NzbDrone.Core/Localization/Core/es.json | 4 +- src/NzbDrone.Core/Localization/Core/fi.json | 8 +- src/NzbDrone.Core/Localization/Core/fr.json | 118 ++++++++-------- src/NzbDrone.Core/Localization/Core/hu.json | 12 +- src/NzbDrone.Core/Localization/Core/id.json | 2 +- src/NzbDrone.Core/Localization/Core/nl.json | 2 +- src/NzbDrone.Core/Localization/Core/pt.json | 6 +- .../Localization/Core/pt_BR.json | 124 ++++++++--------- .../Localization/Core/zh_CN.json | 120 ++++++++-------- 53 files changed, 364 insertions(+), 362 deletions(-) diff --git a/frontend/src/Activity/History/Details/HistoryDetails.js b/frontend/src/Activity/History/Details/HistoryDetails.js index 874ec52c9..862d8707e 100644 --- a/frontend/src/Activity/History/Details/HistoryDetails.js +++ b/frontend/src/Activity/History/Details/HistoryDetails.js @@ -231,7 +231,7 @@ function HistoryDetails(props) { reasonMessage = translate('DeletedReasonManual'); break; case 'MissingFromDisk': - reasonMessage = translate('DeletedReasonMissingFromDisk'); + reasonMessage = translate('DeletedReasonEpisodeMissingFromDisk'); break; case 'Upgrade': reasonMessage = translate('DeletedReasonUpgrade'); diff --git a/frontend/src/Activity/History/HistoryEventTypeCell.js b/frontend/src/Activity/History/HistoryEventTypeCell.js index 55b1351f1..cce30c6e5 100644 --- a/frontend/src/Activity/History/HistoryEventTypeCell.js +++ b/frontend/src/Activity/History/HistoryEventTypeCell.js @@ -39,19 +39,19 @@ function getIconKind(eventType) { function getTooltip(eventType, data) { switch (eventType) { case 'grabbed': - return translate('GrabbedHistoryTooltip', { indexer: data.indexer, downloadClient: data.downloadClient }); + return translate('EpisodeGrabbedTooltip', { indexer: data.indexer, downloadClient: data.downloadClient }); case 'seriesFolderImported': return translate('SeriesFolderImportedTooltip'); case 'downloadFolderImported': return translate('EpisodeImportedTooltip'); case 'downloadFailed': - return translate('DownloadFailedTooltip'); + return translate('DownloadFailedEpisodeTooltip'); case 'episodeFileDeleted': return translate('EpisodeFileDeletedTooltip'); case 'episodeFileRenamed': return translate('EpisodeFileRenamedTooltip'); case 'downloadIgnored': - return translate('DownloadIgnoredTooltip'); + return translate('DownloadIgnoredEpisodeTooltip'); default: return translate('UnknownEventTooltip'); } diff --git a/frontend/src/Activity/Queue/RemoveQueueItemModal.js b/frontend/src/Activity/Queue/RemoveQueueItemModal.js index d79ef665c..0cf7af855 100644 --- a/frontend/src/Activity/Queue/RemoveQueueItemModal.js +++ b/frontend/src/Activity/Queue/RemoveQueueItemModal.js @@ -120,7 +120,7 @@ class RemoveQueueItemModal extends Component { type={inputTypes.CHECK} name="blocklist" value={blocklist} - helpText={translate('BlocklistReleaseHelpText')} + helpText={translate('BlocklistReleaseSearchEpisodeAgainHelpText')} onChange={this.onBlocklistChange} /> </FormGroup> diff --git a/frontend/src/Activity/Queue/RemoveQueueItemsModal.js b/frontend/src/Activity/Queue/RemoveQueueItemsModal.js index 6a212868c..18ea39aea 100644 --- a/frontend/src/Activity/Queue/RemoveQueueItemsModal.js +++ b/frontend/src/Activity/Queue/RemoveQueueItemsModal.js @@ -123,7 +123,7 @@ class RemoveQueueItemsModal extends Component { type={inputTypes.CHECK} name="blocklist" value={blocklist} - helpText={translate('BlocklistReleaseHelpText')} + helpText={translate('BlocklistReleaseSearchEpisodeAgainHelpText')} onChange={this.onBlocklistChange} /> </FormGroup> diff --git a/frontend/src/AddSeries/ImportSeries/SelectFolder/ImportSeriesSelectFolder.js b/frontend/src/AddSeries/ImportSeries/SelectFolder/ImportSeriesSelectFolder.js index 8c8dda91e..939da79d2 100644 --- a/frontend/src/AddSeries/ImportSeries/SelectFolder/ImportSeriesSelectFolder.js +++ b/frontend/src/AddSeries/ImportSeries/SelectFolder/ImportSeriesSelectFolder.js @@ -79,17 +79,17 @@ class ImportSeriesSelectFolder extends Component { !error && isPopulated && <div> <div className={styles.header}> - {translate('LibraryImportHeader')} + {translate('LibraryImportSeriesHeader')} </div> <div className={styles.tips}> {translate('LibraryImportTips')} <ul> <li className={styles.tip}> - <InlineMarkdown data={translate('LibraryImportTipsQualityInFilename')} /> + <InlineMarkdown data={translate('LibraryImportTipsQualityInEpisodeFilename')} /> </li> <li className={styles.tip}> - <InlineMarkdown data={translate('LibraryImportTipsUseRootFolder', { goodFolderExample, badFolderExample })} /> + <InlineMarkdown data={translate('LibraryImportTipsSeriesUseRootFolder', { goodFolderExample, badFolderExample })} /> </li> <li className={styles.tip}> {translate('LibraryImportTipsDontUseDownloadsFolder')} diff --git a/frontend/src/AddSeries/SeriesMonitoringOptionsPopoverContent.js b/frontend/src/AddSeries/SeriesMonitoringOptionsPopoverContent.js index fab8ec3bb..21289fcb8 100644 --- a/frontend/src/AddSeries/SeriesMonitoringOptionsPopoverContent.js +++ b/frontend/src/AddSeries/SeriesMonitoringOptionsPopoverContent.js @@ -47,18 +47,18 @@ function SeriesMonitoringOptionsPopoverContent() { /> <DescriptionListItem - title={translate('MonitorSpecials')} - data={translate('MonitorSpecialsDescription')} + title={translate('MonitorSpecialEpisodes')} + data={translate('MonitorSpecialEpisodesDescription')} /> <DescriptionListItem - title={translate('UnmonitorSpecials')} - data={translate('UnmonitorSpecialsDescription')} + title={translate('UnmonitorSpecialEpisodes')} + data={translate('UnmonitorSpecialsEpisodesDescription')} /> <DescriptionListItem - title={translate('MonitorNone')} - data={translate('MonitorNoneDescription')} + title={translate('MonitorNoEpisodes')} + data={translate('MonitorNoEpisodesDescription')} /> </DescriptionList> ); diff --git a/frontend/src/AddSeries/SeriesTypePopoverContent.js b/frontend/src/AddSeries/SeriesTypePopoverContent.js index c71dcbb4d..9771bd8db 100644 --- a/frontend/src/AddSeries/SeriesTypePopoverContent.js +++ b/frontend/src/AddSeries/SeriesTypePopoverContent.js @@ -8,17 +8,17 @@ function SeriesTypePopoverContent() { <DescriptionList> <DescriptionListItem title={translate('Anime')} - data={translate('AnimeTypeDescription')} + data={translate('AnimeEpisodeTypeDescription')} /> <DescriptionListItem title={translate('Daily')} - data={translate('DailyTypeDescription')} + data={translate('DailyEpisodeTypeDescription')} /> <DescriptionListItem title={translate('Standard')} - data={translate('StandardTypeDescription')} + data={translate('StandardEpisodeTypeDescription')} /> </DescriptionList> ); diff --git a/frontend/src/Calendar/Legend/Legend.js b/frontend/src/Calendar/Legend/Legend.js index 546917b4a..5fc84234f 100644 --- a/frontend/src/Calendar/Legend/Legend.js +++ b/frontend/src/Calendar/Legend/Legend.js @@ -25,7 +25,7 @@ function Legend(props) { name="Finale" icon={icons.INFO} kind={fullColorEvents ? kinds.DEFAULT : kinds.WARNING} - tooltip={translate('CalendarLegendFinaleTooltip')} + tooltip={translate('CalendarLegendSeriesFinaleTooltip')} /> ); } @@ -58,7 +58,7 @@ function Legend(props) { <div> <LegendItem status="unaired" - tooltip={translate('CalendarLegendUnairedTooltip')} + tooltip={translate('CalendarLegendEpisodeUnairedTooltip')} isAgendaView={isAgendaView} fullColorEvents={fullColorEvents} colorImpairedMode={colorImpairedMode} @@ -66,7 +66,7 @@ function Legend(props) { <LegendItem status="unmonitored" - tooltip={translate('CalendarLegendUnmonitoredTooltip')} + tooltip={translate('CalendarLegendEpisodeUnmonitoredTooltip')} isAgendaView={isAgendaView} fullColorEvents={fullColorEvents} colorImpairedMode={colorImpairedMode} @@ -77,7 +77,7 @@ function Legend(props) { <LegendItem status="onAir" name="On Air" - tooltip={translate('CalendarLegendOnAirTooltip')} + tooltip={translate('CalendarLegendEpisodeOnAirTooltip')} isAgendaView={isAgendaView} fullColorEvents={fullColorEvents} colorImpairedMode={colorImpairedMode} @@ -85,7 +85,7 @@ function Legend(props) { <LegendItem status="missing" - tooltip={translate('CalendarLegendMissingTooltip')} + tooltip={translate('CalendarLegendEpisodeMissingTooltip')} isAgendaView={isAgendaView} fullColorEvents={fullColorEvents} colorImpairedMode={colorImpairedMode} @@ -95,7 +95,7 @@ function Legend(props) { <div> <LegendItem status="downloading" - tooltip={translate('CalendarLegendDownloadingTooltip')} + tooltip={translate('CalendarLegendEpisodeDownloadingTooltip')} isAgendaView={isAgendaView} fullColorEvents={fullColorEvents} colorImpairedMode={colorImpairedMode} @@ -103,7 +103,7 @@ function Legend(props) { <LegendItem status="downloaded" - tooltip={translate('CalendarLegendDownloadedTooltip')} + tooltip={translate('CalendarLegendEpisodeDownloadedTooltip')} isAgendaView={isAgendaView} fullColorEvents={fullColorEvents} colorImpairedMode={colorImpairedMode} @@ -116,7 +116,7 @@ function Legend(props) { icon={icons.INFO} kind={kinds.INFO} darken={true} - tooltip={translate('CalendarLegendPremiereTooltip')} + tooltip={translate('CalendarLegendSeriesPremiereTooltip')} /> {iconsToShow[0]} diff --git a/frontend/src/Calendar/iCal/CalendarLinkModalContent.js b/frontend/src/Calendar/iCal/CalendarLinkModalContent.js index a35c46a79..eb64cb207 100644 --- a/frontend/src/Calendar/iCal/CalendarLinkModalContent.js +++ b/frontend/src/Calendar/iCal/CalendarLinkModalContent.js @@ -128,7 +128,7 @@ class CalendarLinkModalContent extends Component { type={inputTypes.CHECK} name="unmonitored" value={unmonitored} - helpText={translate('ICalIncludeUnmonitoredHelpText')} + helpText={translate('ICalIncludeUnmonitoredEpisodesHelpText')} onChange={this.onInputChange} /> </FormGroup> @@ -164,7 +164,7 @@ class CalendarLinkModalContent extends Component { type={inputTypes.TAG} name="tags" value={tags} - helpText={translate('ICalTagsHelpText')} + helpText={translate('ICalTagsSeriesHelpText')} onChange={this.onInputChange} /> </FormGroup> diff --git a/frontend/src/Components/Form/SeriesTypeSelectInput.tsx b/frontend/src/Components/Form/SeriesTypeSelectInput.tsx index 72f0ed9ac..471d6592b 100644 --- a/frontend/src/Components/Form/SeriesTypeSelectInput.tsx +++ b/frontend/src/Components/Form/SeriesTypeSelectInput.tsx @@ -23,21 +23,21 @@ const seriesTypeOptions: ISeriesTypeOption[] = [ key: seriesTypes.STANDARD, value: 'Standard', get format() { - return translate('StandardTypeFormat', { format: 'S01E05' }); + return translate('StandardEpisodeTypeFormat', { format: 'S01E05' }); }, }, { key: seriesTypes.DAILY, value: 'Daily / Date', get format() { - return translate('DailyTypeFormat', { format: '2020-05-25' }); + return translate('DailyEpisodeTypeFormat', { format: '2020-05-25' }); }, }, { key: seriesTypes.ANIME, value: 'Anime / Absolute', get format() { - return translate('AnimeTypeFormat', { format: '005' }); + return translate('AnimeEpisodeTypeFormat', { format: '005' }); }, }, ]; diff --git a/frontend/src/InteractiveSearch/InteractiveSearch.js b/frontend/src/InteractiveSearch/InteractiveSearch.js index b852f82c7..1961de02c 100644 --- a/frontend/src/InteractiveSearch/InteractiveSearch.js +++ b/frontend/src/InteractiveSearch/InteractiveSearch.js @@ -139,7 +139,7 @@ function InteractiveSearch(props) { { errorMessage ? <Fragment> - {translate('InteractiveSearchResultsFailedErrorMessage', { message: errorMessage.charAt(0).toLowerCase() + errorMessage.slice(1) })} + {translate('InteractiveSearchResultsSeriesFailedErrorMessage', { message: errorMessage.charAt(0).toLowerCase() + errorMessage.slice(1) })} </Fragment> : translate('EpisodeSearchResultsLoadError') } diff --git a/frontend/src/InteractiveSearch/InteractiveSearchRow.tsx b/frontend/src/InteractiveSearch/InteractiveSearchRow.tsx index 4d3e700e5..d95c383c0 100644 --- a/frontend/src/InteractiveSearch/InteractiveSearchRow.tsx +++ b/frontend/src/InteractiveSearch/InteractiveSearchRow.tsx @@ -309,7 +309,9 @@ function InteractiveSearchRow(props: InteractiveSearchRowProps) { isOpen={isConfirmGrabModalOpen} kind={kinds.WARNING} title={translate('GrabRelease')} - message={translate('GrabReleaseMessageText', { title })} + message={translate('GrabReleaseUnknownSeriesOrEpisodeMessageText', { + title, + })} confirmLabel={translate('Grab')} onConfirm={onGrabConfirm} onCancel={onGrabCancel} diff --git a/frontend/src/Series/Delete/DeleteSeriesModalContent.js b/frontend/src/Series/Delete/DeleteSeriesModalContent.js index 6a1e1effa..2af242499 100644 --- a/frontend/src/Series/Delete/DeleteSeriesModalContent.js +++ b/frontend/src/Series/Delete/DeleteSeriesModalContent.js @@ -89,7 +89,7 @@ class DeleteSeriesModalContent extends Component { type={inputTypes.CHECK} name="addImportListExclusion" value={addImportListExclusion} - helpText={translate('AddListExclusionHelpText')} + helpText={translate('AddListExclusionSeriesHelpText')} onChange={onDeleteOptionChange} /> </FormGroup> diff --git a/frontend/src/Series/Edit/EditSeriesModalContent.js b/frontend/src/Series/Edit/EditSeriesModalContent.js index 6824b0f21..537d8990b 100644 --- a/frontend/src/Series/Edit/EditSeriesModalContent.js +++ b/frontend/src/Series/Edit/EditSeriesModalContent.js @@ -98,7 +98,7 @@ class EditSeriesModalContent extends Component { <FormInputGroup type={inputTypes.CHECK} name="monitored" - helpText={translate('MonitoredHelpText')} + helpText={translate('MonitoredEpisodesHelpText')} {...monitored} onChange={onInputChange} /> diff --git a/frontend/src/Series/Index/Posters/Options/SeriesIndexPosterOptionsModalContent.tsx b/frontend/src/Series/Index/Posters/Options/SeriesIndexPosterOptionsModalContent.tsx index b7f72116a..81e0f942d 100644 --- a/frontend/src/Series/Index/Posters/Options/SeriesIndexPosterOptionsModalContent.tsx +++ b/frontend/src/Series/Index/Posters/Options/SeriesIndexPosterOptionsModalContent.tsx @@ -101,7 +101,7 @@ function SeriesIndexPosterOptionsModalContent( type={inputTypes.CHECK} name="showTitle" value={showTitle} - helpText={translate('ShowTitleHelpText')} + helpText={translate('ShowSeriesTitleHelpText')} onChange={onPosterOptionChange} /> </FormGroup> diff --git a/frontend/src/Series/Index/Select/Delete/DeleteSeriesModalContent.tsx b/frontend/src/Series/Index/Select/Delete/DeleteSeriesModalContent.tsx index f66b28346..8904a24ce 100644 --- a/frontend/src/Series/Index/Select/Delete/DeleteSeriesModalContent.tsx +++ b/frontend/src/Series/Index/Select/Delete/DeleteSeriesModalContent.tsx @@ -98,7 +98,7 @@ function DeleteSeriesModalContent(props: DeleteSeriesModalContentProps) { type={inputTypes.CHECK} name="addImportListExclusion" value={addImportListExclusion} - helpText={translate('AddListExclusionHelpText')} + helpText={translate('AddListExclusionSeriesHelpText')} onChange={onDeleteOptionChange} /> </FormGroup> diff --git a/frontend/src/Settings/DownloadClients/DownloadClients/EditDownloadClientModalContent.js b/frontend/src/Settings/DownloadClients/DownloadClients/EditDownloadClientModalContent.js index 114061b05..52493adf5 100644 --- a/frontend/src/Settings/DownloadClients/DownloadClients/EditDownloadClientModalContent.js +++ b/frontend/src/Settings/DownloadClients/DownloadClients/EditDownloadClientModalContent.js @@ -147,7 +147,7 @@ class EditDownloadClientModalContent extends Component { <FormInputGroup type={inputTypes.TAG} name="tags" - helpText={translate('DownloadClientTagHelpText')} + helpText={translate('DownloadClientSeriesTagHelpText')} {...tags} onChange={onInputChange} /> diff --git a/frontend/src/Settings/ImportLists/ImportLists/AddImportListModalContent.js b/frontend/src/Settings/ImportLists/ImportLists/AddImportListModalContent.js index 8a691b0bd..b5132db42 100644 --- a/frontend/src/Settings/ImportLists/ImportLists/AddImportListModalContent.js +++ b/frontend/src/Settings/ImportLists/ImportLists/AddImportListModalContent.js @@ -56,7 +56,7 @@ class AddImportListModalContent extends Component { <Alert kind={kinds.INFO}> <div> - {translate('SupportedLists')} + {translate('SupportedListsSeries')} </div> <div> {translate('SupportedListsMoreInfo')} diff --git a/frontend/src/Settings/ImportLists/ImportLists/EditImportListModalContent.js b/frontend/src/Settings/ImportLists/ImportLists/EditImportListModalContent.js index 1dc9c8def..c8020d975 100644 --- a/frontend/src/Settings/ImportLists/ImportLists/EditImportListModalContent.js +++ b/frontend/src/Settings/ImportLists/ImportLists/EditImportListModalContent.js @@ -110,7 +110,7 @@ function EditImportListModalContent(props) { <FormInputGroup type={inputTypes.CHECK} name="enableAutomaticAdd" - helpText={translate('EnableAutomaticAddHelpText')} + helpText={translate('EnableAutomaticAddSeriesHelpText')} {...enableAutomaticAdd} onChange={onInputChange} /> diff --git a/frontend/src/Settings/Indexers/Indexers/EditIndexerModalContent.js b/frontend/src/Settings/Indexers/Indexers/EditIndexerModalContent.js index d2eba8e48..4306aa2d9 100644 --- a/frontend/src/Settings/Indexers/Indexers/EditIndexerModalContent.js +++ b/frontend/src/Settings/Indexers/Indexers/EditIndexerModalContent.js @@ -200,7 +200,7 @@ function EditIndexerModalContent(props) { <FormInputGroup type={inputTypes.TAG} name="tags" - helpText={translate('IndexerTagHelpText')} + helpText={translate('IndexerTagSeriesHelpText')} {...tags} onChange={onInputChange} /> diff --git a/frontend/src/Settings/MediaManagement/MediaManagement.js b/frontend/src/Settings/MediaManagement/MediaManagement.js index 1ceba5a69..e39ed837d 100644 --- a/frontend/src/Settings/MediaManagement/MediaManagement.js +++ b/frontend/src/Settings/MediaManagement/MediaManagement.js @@ -180,7 +180,7 @@ class MediaManagement extends Component { <FormInputGroup type={inputTypes.CHECK} name="deleteEmptyFolders" - helpText={translate('DeleteEmptyFoldersHelpText')} + helpText={translate('DeleteEmptySeriesFoldersHelpText')} onChange={onInputChange} {...settings.deleteEmptyFolders} /> @@ -257,7 +257,7 @@ class MediaManagement extends Component { <FormInputGroup type={inputTypes.CHECK} name="copyUsingHardlinks" - helpText={translate('CopyUsingHardlinksHelpText')} + helpText={translate('CopyUsingHardlinksSeriesHelpText')} helpTextWarning={translate('CopyUsingHardlinksHelpTextWarning')} onChange={onInputChange} {...settings.copyUsingHardlinks} @@ -305,7 +305,7 @@ class MediaManagement extends Component { <FormInputGroup type={inputTypes.CHECK} name="importExtraFiles" - helpText={translate('ImportExtraFilesHelpText')} + helpText={translate('ImportExtraFilesEpisodeHelpText')} onChange={onInputChange} {...settings.importExtraFiles} /> @@ -399,7 +399,7 @@ class MediaManagement extends Component { <FormInputGroup type={inputTypes.SELECT} name="rescanAfterRefresh" - helpText={translate('RescanAfterRefreshHelpText')} + helpText={translate('RescanAfterRefreshSeriesHelpText')} helpTextWarning={translate('RescanAfterRefreshHelpTextWarning')} values={rescanAfterRefreshOptions} onChange={onInputChange} diff --git a/frontend/src/Settings/Notifications/Notifications/EditNotificationModalContent.js b/frontend/src/Settings/Notifications/Notifications/EditNotificationModalContent.js index 9c3585b11..5bfc9b5ae 100644 --- a/frontend/src/Settings/Notifications/Notifications/EditNotificationModalContent.js +++ b/frontend/src/Settings/Notifications/Notifications/EditNotificationModalContent.js @@ -99,7 +99,7 @@ function EditNotificationModalContent(props) { <FormInputGroup type={inputTypes.TAG} name="tags" - helpText={translate('NotificationsTagsHelpText')} + helpText={translate('NotificationsTagsSeriesHelpText')} {...tags} onChange={onInputChange} /> diff --git a/frontend/src/Settings/Profiles/Delay/EditDelayProfileModalContent.js b/frontend/src/Settings/Profiles/Delay/EditDelayProfileModalContent.js index f99f42038..38cc33559 100644 --- a/frontend/src/Settings/Profiles/Delay/EditDelayProfileModalContent.js +++ b/frontend/src/Settings/Profiles/Delay/EditDelayProfileModalContent.js @@ -186,7 +186,7 @@ function EditDelayProfileModalContent(props) { { id === 1 ? <Alert> - {translate('DefaultDelayProfile')} + {translate('DefaultDelayProfileSeries')} </Alert> : <FormGroup> @@ -196,7 +196,7 @@ function EditDelayProfileModalContent(props) { type={inputTypes.TAG} name="tags" {...tags} - helpText={translate('DelayProfileTagsHelpText')} + helpText={translate('DelayProfileSeriesTagsHelpText')} onChange={onInputChange} /> </FormGroup> diff --git a/frontend/src/Settings/Profiles/Quality/EditQualityProfileModalContent.js b/frontend/src/Settings/Profiles/Quality/EditQualityProfileModalContent.js index 486aeec2f..1c129a9b3 100644 --- a/frontend/src/Settings/Profiles/Quality/EditQualityProfileModalContent.js +++ b/frontend/src/Settings/Profiles/Quality/EditQualityProfileModalContent.js @@ -203,7 +203,7 @@ class EditQualityProfileModalContent extends Component { name="cutoff" {...cutoff} values={qualities} - helpText={translate('UpgradeUntilHelpText')} + helpText={translate('UpgradeUntilEpisodeHelpText')} onChange={onCutoffChange} /> </FormGroup> @@ -237,7 +237,7 @@ class EditQualityProfileModalContent extends Component { type={inputTypes.NUMBER} name="cutoffFormatScore" {...cutoffFormatScore} - helpText={translate('UpgradeUntilCustomFormatScoreHelpText')} + helpText={translate('UpgradeUntilCustomFormatScoreEpisodeHelpText')} onChange={onInputChange} /> </FormGroup> @@ -281,7 +281,7 @@ class EditQualityProfileModalContent extends Component { className={styles.deleteButtonContainer} title={ isInUse ? - translate('QualityProfileInUse') : + translate('QualityProfileInUseSeriesListCollection') : undefined } > diff --git a/frontend/src/Settings/Profiles/Release/EditReleaseProfileModalContent.js b/frontend/src/Settings/Profiles/Release/EditReleaseProfileModalContent.js index 025c8e48e..64d707b5f 100644 --- a/frontend/src/Settings/Profiles/Release/EditReleaseProfileModalContent.js +++ b/frontend/src/Settings/Profiles/Release/EditReleaseProfileModalContent.js @@ -126,7 +126,7 @@ function EditReleaseProfileModalContent(props) { <FormInputGroup type={inputTypes.TAG} name="tags" - helpText={translate('ReleaseProfileTagHelpText')} + helpText={translate('ReleaseProfileTagSeriesHelpText')} {...tags} onChange={onInputChange} /> diff --git a/frontend/src/Settings/Quality/Definition/QualityDefinitions.js b/frontend/src/Settings/Quality/Definition/QualityDefinitions.js index d7e9ed40c..76b7ca383 100644 --- a/frontend/src/Settings/Quality/Definition/QualityDefinitions.js +++ b/frontend/src/Settings/Quality/Definition/QualityDefinitions.js @@ -60,7 +60,7 @@ class QualityDefinitions extends Component { <div className={styles.sizeLimitHelpTextContainer}> <div className={styles.sizeLimitHelpText}> - {translate('QualityLimitsHelpText')} + {translate('QualityLimitsSeriesRuntimeHelpText')} </div> </div> </PageSectionContent> diff --git a/frontend/src/Settings/Settings.js b/frontend/src/Settings/Settings.js index abc6d2fcf..2afbae783 100644 --- a/frontend/src/Settings/Settings.js +++ b/frontend/src/Settings/Settings.js @@ -110,7 +110,7 @@ function Settings() { </Link> <div className={styles.summary}> - {translate('MetadataSettingsSummary')} + {translate('MetadataSettingsSeriesSummary')} </div> <Link @@ -121,7 +121,7 @@ function Settings() { </Link> <div className={styles.summary}> - {translate('MetadataSourceSettingsSummary')} + {translate('MetadataSourceSettingsSeriesSummary')} </div> <Link diff --git a/frontend/src/Utilities/Series/monitorOptions.js b/frontend/src/Utilities/Series/monitorOptions.js index 1d0543042..6616cf7c4 100644 --- a/frontend/src/Utilities/Series/monitorOptions.js +++ b/frontend/src/Utilities/Series/monitorOptions.js @@ -52,19 +52,19 @@ const monitorOptions = [ { key: 'monitorSpecials', get value() { - return translate('MonitorSpecials'); + return translate('MonitorSpecialEpisodes'); } }, { key: 'unmonitorSpecials', get value() { - return translate('UnmonitorSpecials'); + return translate('UnmonitorSpecialEpisodes'); } }, { key: 'none', get value() { - return translate('MonitorNone'); + return translate('MonitorNoEpisodes'); } } ]; diff --git a/frontend/src/Wanted/CutoffUnmet/CutoffUnmet.js b/frontend/src/Wanted/CutoffUnmet/CutoffUnmet.js index a03ea8b2e..29c925702 100644 --- a/frontend/src/Wanted/CutoffUnmet/CutoffUnmet.js +++ b/frontend/src/Wanted/CutoffUnmet/CutoffUnmet.js @@ -247,11 +247,11 @@ class CutoffUnmet extends Component { <ConfirmModal isOpen={isConfirmSearchAllCutoffUnmetModalOpen} kind={kinds.DANGER} - title={translate('SearchForCutoffUnmet')} + title={translate('SearchForCutoffUnmetEpisodes')} message={ <div> <div> - {translate('SearchForCutoffUnmetConfirmationCount', { totalRecords })} + {translate('SearchForCutoffUnmetEpisodesConfirmationCount', { totalRecords })} </div> <div> {translate('MassSearchCancelWarning')} diff --git a/frontend/src/Wanted/Missing/Missing.js b/frontend/src/Wanted/Missing/Missing.js index 4185e54e3..70054a73b 100644 --- a/frontend/src/Wanted/Missing/Missing.js +++ b/frontend/src/Wanted/Missing/Missing.js @@ -260,11 +260,11 @@ class Missing extends Component { <ConfirmModal isOpen={isConfirmSearchAllMissingModalOpen} kind={kinds.DANGER} - title={translate('SearchForAllMissing')} + title={translate('SearchForAllMissingEpisodes')} message={ <div> <div> - {translate('SearchForAllMissingConfirmationCount', { totalRecords })} + {translate('SearchForAllMissingEpisodesConfirmationCount', { totalRecords })} </div> <div> {translate('MassSearchCancelWarning')} diff --git a/src/NzbDrone.Core/Download/Clients/Deluge/DelugeSettings.cs b/src/NzbDrone.Core/Download/Clients/Deluge/DelugeSettings.cs index 66192d3f8..03f266189 100644 --- a/src/NzbDrone.Core/Download/Clients/Deluge/DelugeSettings.cs +++ b/src/NzbDrone.Core/Download/Clients/Deluge/DelugeSettings.cs @@ -52,10 +52,10 @@ namespace NzbDrone.Core.Download.Clients.Deluge [FieldDefinition(6, Label = "PostImportCategory", Type = FieldType.Textbox, Advanced = true, HelpText = "DownloadClientSettingsPostImportCategoryHelpText")] public string TvImportedCategory { get; set; } - [FieldDefinition(7, Label = "DownloadClientSettingsRecentPriority", Type = FieldType.Select, SelectOptions = typeof(DelugePriority), HelpText = "DownloadClientSettingsRecentPriorityHelpText")] + [FieldDefinition(7, Label = "DownloadClientSettingsRecentPriority", Type = FieldType.Select, SelectOptions = typeof(DelugePriority), HelpText = "DownloadClientSettingsRecentPriorityEpisodeHelpText")] public int RecentTvPriority { get; set; } - [FieldDefinition(8, Label = "DownloadClientSettingsOlderPriority", Type = FieldType.Select, SelectOptions = typeof(DelugePriority), HelpText = "DownloadClientSettingsOlderPriorityHelpText")] + [FieldDefinition(8, Label = "DownloadClientSettingsOlderPriority", Type = FieldType.Select, SelectOptions = typeof(DelugePriority), HelpText = "DownloadClientSettingsOlderPriorityEpisodeHelpText")] public int OlderTvPriority { get; set; } [FieldDefinition(9, Label = "DownloadClientSettingsAddPaused", Type = FieldType.Checkbox)] diff --git a/src/NzbDrone.Core/Download/Clients/FreeboxDownload/FreeboxDownloadSettings.cs b/src/NzbDrone.Core/Download/Clients/FreeboxDownload/FreeboxDownloadSettings.cs index 62de8f46c..42b645848 100644 --- a/src/NzbDrone.Core/Download/Clients/FreeboxDownload/FreeboxDownloadSettings.cs +++ b/src/NzbDrone.Core/Download/Clients/FreeboxDownload/FreeboxDownloadSettings.cs @@ -75,10 +75,10 @@ namespace NzbDrone.Core.Download.Clients.FreeboxDownload [FieldDefinition(7, Label = "Category", Type = FieldType.Textbox, HelpText = "DownloadClientSettingsCategorySubFolderHelpText")] public string Category { get; set; } - [FieldDefinition(8, Label = "DownloadClientSettingsRecentPriority", Type = FieldType.Select, SelectOptions = typeof(FreeboxDownloadPriority), HelpText = "DownloadClientSettingsRecentPriorityHelpText")] + [FieldDefinition(8, Label = "DownloadClientSettingsRecentPriority", Type = FieldType.Select, SelectOptions = typeof(FreeboxDownloadPriority), HelpText = "DownloadClientSettingsRecentPriorityEpisodeHelpText")] public int RecentPriority { get; set; } - [FieldDefinition(9, Label = "DownloadClientSettingsOlderPriority", Type = FieldType.Select, SelectOptions = typeof(FreeboxDownloadPriority), HelpText = "DownloadClientSettingsOlderPriorityHelpText")] + [FieldDefinition(9, Label = "DownloadClientSettingsOlderPriority", Type = FieldType.Select, SelectOptions = typeof(FreeboxDownloadPriority), HelpText = "DownloadClientSettingsOlderPriorityEpisodeHelpText")] public int OlderPriority { get; set; } [FieldDefinition(10, Label = "DownloadClientSettingsAddPaused", Type = FieldType.Checkbox)] diff --git a/src/NzbDrone.Core/Download/Clients/NzbVortex/NzbVortexSettings.cs b/src/NzbDrone.Core/Download/Clients/NzbVortex/NzbVortexSettings.cs index 31169f74d..81a72004e 100644 --- a/src/NzbDrone.Core/Download/Clients/NzbVortex/NzbVortexSettings.cs +++ b/src/NzbDrone.Core/Download/Clients/NzbVortex/NzbVortexSettings.cs @@ -53,10 +53,10 @@ namespace NzbDrone.Core.Download.Clients.NzbVortex [FieldDefinition(4, Label = "Group", Type = FieldType.Textbox, HelpText = "DownloadClientSettingsCategoryHelpText")] public string TvCategory { get; set; } - [FieldDefinition(5, Label = "DownloadClientSettingsRecentPriority", Type = FieldType.Select, SelectOptions = typeof(NzbVortexPriority), HelpText = "DownloadClientSettingsRecentPriorityHelpText")] + [FieldDefinition(5, Label = "DownloadClientSettingsRecentPriority", Type = FieldType.Select, SelectOptions = typeof(NzbVortexPriority), HelpText = "DownloadClientSettingsRecentPriorityEpisodeHelpText")] public int RecentTvPriority { get; set; } - [FieldDefinition(6, Label = "DownloadClientSettingsOlderPriority", Type = FieldType.Select, SelectOptions = typeof(NzbVortexPriority), HelpText = "DownloadClientSettingsOlderPriorityHelpText")] + [FieldDefinition(6, Label = "DownloadClientSettingsOlderPriority", Type = FieldType.Select, SelectOptions = typeof(NzbVortexPriority), HelpText = "DownloadClientSettingsOlderPriorityEpisodeHelpText")] public int OlderTvPriority { get; set; } public NzbDroneValidationResult Validate() diff --git a/src/NzbDrone.Core/Download/Clients/Nzbget/NzbgetSettings.cs b/src/NzbDrone.Core/Download/Clients/Nzbget/NzbgetSettings.cs index adc8891e5..067867b08 100644 --- a/src/NzbDrone.Core/Download/Clients/Nzbget/NzbgetSettings.cs +++ b/src/NzbDrone.Core/Download/Clients/Nzbget/NzbgetSettings.cs @@ -58,10 +58,10 @@ namespace NzbDrone.Core.Download.Clients.Nzbget [FieldDefinition(6, Label = "Category", Type = FieldType.Textbox, HelpText = "DownloadClientSettingsCategoryHelpText")] public string TvCategory { get; set; } - [FieldDefinition(7, Label = "DownloadClientSettingsRecentPriority", Type = FieldType.Select, SelectOptions = typeof(NzbgetPriority), HelpText = "DownloadClientSettingsRecentPriorityHelpText")] + [FieldDefinition(7, Label = "DownloadClientSettingsRecentPriority", Type = FieldType.Select, SelectOptions = typeof(NzbgetPriority), HelpText = "DownloadClientSettingsRecentPriorityEpisodeHelpText")] public int RecentTvPriority { get; set; } - [FieldDefinition(8, Label = "DownloadClientSettingsOlderPriority", Type = FieldType.Select, SelectOptions = typeof(NzbgetPriority), HelpText = "DownloadClientSettingsOlderPriorityHelpText")] + [FieldDefinition(8, Label = "DownloadClientSettingsOlderPriority", Type = FieldType.Select, SelectOptions = typeof(NzbgetPriority), HelpText = "DownloadClientSettingsOlderPriorityEpisodeHelpText")] public int OlderTvPriority { get; set; } [FieldDefinition(9, Label = "DownloadClientSettingsAddPaused", Type = FieldType.Checkbox, HelpText = "DownloadClientNzbgetSettingsAddPausedHelpText")] diff --git a/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrentSettings.cs b/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrentSettings.cs index fd12a7e04..d092703ad 100644 --- a/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrentSettings.cs +++ b/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrentSettings.cs @@ -56,10 +56,10 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent [FieldDefinition(7, Label = "PostImportCategory", Type = FieldType.Textbox, Advanced = true, HelpText = "DownloadClientSettingsPostImportCategoryHelpText")] public string TvImportedCategory { get; set; } - [FieldDefinition(8, Label = "DownloadClientSettingsRecentPriority", Type = FieldType.Select, SelectOptions = typeof(QBittorrentPriority), HelpText = "DownloadClientSettingsRecentPriorityHelpText")] + [FieldDefinition(8, Label = "DownloadClientSettingsRecentPriority", Type = FieldType.Select, SelectOptions = typeof(QBittorrentPriority), HelpText = "DownloadClientSettingsRecentPriorityEpisodeHelpText")] public int RecentTvPriority { get; set; } - [FieldDefinition(9, Label = "DownloadClientSettingsOlderPriority", Type = FieldType.Select, SelectOptions = typeof(QBittorrentPriority), HelpText = "DownloadClientSettingsOlderPriorityHelpText")] + [FieldDefinition(9, Label = "DownloadClientSettingsOlderPriority", Type = FieldType.Select, SelectOptions = typeof(QBittorrentPriority), HelpText = "DownloadClientSettingsOlderPriorityEpisodeHelpText")] public int OlderTvPriority { get; set; } [FieldDefinition(10, Label = "DownloadClientSettingsInitialState", Type = FieldType.Select, SelectOptions = typeof(QBittorrentState), HelpText = "DownloadClientQbittorrentSettingsInitialStateHelpText")] diff --git a/src/NzbDrone.Core/Download/Clients/Sabnzbd/SabnzbdSettings.cs b/src/NzbDrone.Core/Download/Clients/Sabnzbd/SabnzbdSettings.cs index baa6b3553..33d2e3266 100644 --- a/src/NzbDrone.Core/Download/Clients/Sabnzbd/SabnzbdSettings.cs +++ b/src/NzbDrone.Core/Download/Clients/Sabnzbd/SabnzbdSettings.cs @@ -72,10 +72,10 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd [FieldDefinition(7, Label = "Category", Type = FieldType.Textbox, HelpText = "DownloadClientSettingsCategoryHelpText")] public string TvCategory { get; set; } - [FieldDefinition(8, Label = "DownloadClientSettingsRecentPriority", Type = FieldType.Select, SelectOptions = typeof(SabnzbdPriority), HelpText = "DownloadClientSettingsRecentPriorityHelpText")] + [FieldDefinition(8, Label = "DownloadClientSettingsRecentPriority", Type = FieldType.Select, SelectOptions = typeof(SabnzbdPriority), HelpText = "DownloadClientSettingsRecentPriorityEpisodeHelpText")] public int RecentTvPriority { get; set; } - [FieldDefinition(9, Label = "DownloadClientSettingsOlderPriority", Type = FieldType.Select, SelectOptions = typeof(SabnzbdPriority), HelpText = "DownloadClientSettingsOlderPriorityHelpText")] + [FieldDefinition(9, Label = "DownloadClientSettingsOlderPriority", Type = FieldType.Select, SelectOptions = typeof(SabnzbdPriority), HelpText = "DownloadClientSettingsOlderPriorityEpisodeHelpText")] public int OlderTvPriority { get; set; } public NzbDroneValidationResult Validate() diff --git a/src/NzbDrone.Core/Download/Clients/Transmission/TransmissionSettings.cs b/src/NzbDrone.Core/Download/Clients/Transmission/TransmissionSettings.cs index 7038ad7af..7140c0f4c 100644 --- a/src/NzbDrone.Core/Download/Clients/Transmission/TransmissionSettings.cs +++ b/src/NzbDrone.Core/Download/Clients/Transmission/TransmissionSettings.cs @@ -64,10 +64,10 @@ namespace NzbDrone.Core.Download.Clients.Transmission [FieldDefinition(7, Label = "Directory", Type = FieldType.Textbox, Advanced = true, HelpText = "DownloadClientTransmissionSettingsDirectoryHelpText")] public string TvDirectory { get; set; } - [FieldDefinition(8, Label = "DownloadClientSettingsRecentPriority", Type = FieldType.Select, SelectOptions = typeof(TransmissionPriority), HelpText = "DownloadClientSettingsRecentPriorityHelpText")] + [FieldDefinition(8, Label = "DownloadClientSettingsRecentPriority", Type = FieldType.Select, SelectOptions = typeof(TransmissionPriority), HelpText = "DownloadClientSettingsRecentPriorityEpisodeHelpText")] public int RecentTvPriority { get; set; } - [FieldDefinition(9, Label = "DownloadClientSettingsOlderPriority", Type = FieldType.Select, SelectOptions = typeof(TransmissionPriority), HelpText = "DownloadClientSettingsOlderPriorityHelpText")] + [FieldDefinition(9, Label = "DownloadClientSettingsOlderPriority", Type = FieldType.Select, SelectOptions = typeof(TransmissionPriority), HelpText = "DownloadClientSettingsOlderPriorityEpisodeHelpText")] public int OlderTvPriority { get; set; } [FieldDefinition(10, Label = "DownloadClientSettingsAddPaused", Type = FieldType.Checkbox)] diff --git a/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrentSettings.cs b/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrentSettings.cs index 90252c585..45822a9f0 100644 --- a/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrentSettings.cs +++ b/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrentSettings.cs @@ -61,10 +61,10 @@ namespace NzbDrone.Core.Download.Clients.RTorrent [FieldDefinition(8, Label = "Directory", Type = FieldType.Textbox, Advanced = true, HelpText = "DownloadClientRTorrentSettingsDirectoryHelpText")] public string TvDirectory { get; set; } - [FieldDefinition(9, Label = "DownloadClientSettingsRecentPriority", Type = FieldType.Select, SelectOptions = typeof(RTorrentPriority), HelpText = "DownloadClientSettingsRecentPriorityHelpText")] + [FieldDefinition(9, Label = "DownloadClientSettingsRecentPriority", Type = FieldType.Select, SelectOptions = typeof(RTorrentPriority), HelpText = "DownloadClientSettingsRecentPriorityEpisodeHelpText")] public int RecentTvPriority { get; set; } - [FieldDefinition(10, Label = "DownloadClientSettingsOlderPriority", Type = FieldType.Select, SelectOptions = typeof(RTorrentPriority), HelpText = "DownloadClientSettingsOlderPriorityHelpText")] + [FieldDefinition(10, Label = "DownloadClientSettingsOlderPriority", Type = FieldType.Select, SelectOptions = typeof(RTorrentPriority), HelpText = "DownloadClientSettingsOlderPriorityEpisodeHelpText")] public int OlderTvPriority { get; set; } [FieldDefinition(11, Label = "DownloadClientRTorrentSettingsAddStopped", Type = FieldType.Checkbox, HelpText = "DownloadClientRTorrentSettingsAddStoppedHelpText")] diff --git a/src/NzbDrone.Core/Download/Clients/uTorrent/UTorrentSettings.cs b/src/NzbDrone.Core/Download/Clients/uTorrent/UTorrentSettings.cs index 711815bc2..6c77dd873 100644 --- a/src/NzbDrone.Core/Download/Clients/uTorrent/UTorrentSettings.cs +++ b/src/NzbDrone.Core/Download/Clients/uTorrent/UTorrentSettings.cs @@ -55,10 +55,10 @@ namespace NzbDrone.Core.Download.Clients.UTorrent [FieldDefinition(7, Label = "PostImportCategory", Type = FieldType.Textbox, Advanced = true, HelpText = "DownloadClientSettingsPostImportCategoryHelpText")] public string TvImportedCategory { get; set; } - [FieldDefinition(8, Label = "DownloadClientSettingsRecentPriority", Type = FieldType.Select, SelectOptions = typeof(UTorrentPriority), HelpText = "DownloadClientSettingsRecentPriorityHelpText")] + [FieldDefinition(8, Label = "DownloadClientSettingsRecentPriority", Type = FieldType.Select, SelectOptions = typeof(UTorrentPriority), HelpText = "DownloadClientSettingsRecentPriorityEpisodeHelpText")] public int RecentTvPriority { get; set; } - [FieldDefinition(9, Label = "DownloadClientSettingsOlderPriority", Type = FieldType.Select, SelectOptions = typeof(UTorrentPriority), HelpText = "DownloadClientSettingsOlderPriorityHelpText")] + [FieldDefinition(9, Label = "DownloadClientSettingsOlderPriority", Type = FieldType.Select, SelectOptions = typeof(UTorrentPriority), HelpText = "DownloadClientSettingsOlderPriorityEpisodeHelpText")] public int OlderTvPriority { get; set; } [FieldDefinition(10, Label = "DownloadClientSettingsInitialState", Type = FieldType.Select, SelectOptions = typeof(UTorrentState), HelpText = "DownloadClientSettingsInitialStateHelpText")] diff --git a/src/NzbDrone.Core/HealthCheck/Checks/MountCheck.cs b/src/NzbDrone.Core/HealthCheck/Checks/MountCheck.cs index e3f3395c3..46f1ea103 100644 --- a/src/NzbDrone.Core/HealthCheck/Checks/MountCheck.cs +++ b/src/NzbDrone.Core/HealthCheck/Checks/MountCheck.cs @@ -30,7 +30,7 @@ namespace NzbDrone.Core.HealthCheck.Checks { return new HealthCheck(GetType(), HealthCheckResult.Error, - $"{_localizationService.GetLocalizedString("MountHealthCheckMessage")}{string.Join(", ", mounts.Select(m => m.Name))}", + $"{_localizationService.GetLocalizedString("MountSeriesHealthCheckMessage")}{string.Join(", ", mounts.Select(m => m.Name))}", "#series-mount-ro"); } diff --git a/src/NzbDrone.Core/HealthCheck/Checks/RemotePathMappingCheck.cs b/src/NzbDrone.Core/HealthCheck/Checks/RemotePathMappingCheck.cs index eff41b1f2..82a7f1d35 100644 --- a/src/NzbDrone.Core/HealthCheck/Checks/RemotePathMappingCheck.cs +++ b/src/NzbDrone.Core/HealthCheck/Checks/RemotePathMappingCheck.cs @@ -195,7 +195,7 @@ namespace NzbDrone.Core.HealthCheck.Checks return new HealthCheck(GetType(), HealthCheckResult.Error, _localizationService.GetLocalizedString( - "RemotePathMappingDownloadPermissionsHealthCheckMessage", + "RemotePathMappingDownloadPermissionsEpisodeHealthCheckMessage", new Dictionary<string, object> { { "path", episodePath } @@ -238,7 +238,7 @@ namespace NzbDrone.Core.HealthCheck.Checks return new HealthCheck( GetType(), HealthCheckResult.Error, - _localizationService.GetLocalizedString("RemotePathMappingImportFailedHealthCheckMessage"), + _localizationService.GetLocalizedString("RemotePathMappingImportEpisodeFailedHealthCheckMessage"), "#remote-path-import-failed"); } diff --git a/src/NzbDrone.Core/Indexers/HttpIndexerBase.cs b/src/NzbDrone.Core/Indexers/HttpIndexerBase.cs index 35e8c629f..42d790bf5 100644 --- a/src/NzbDrone.Core/Indexers/HttpIndexerBase.cs +++ b/src/NzbDrone.Core/Indexers/HttpIndexerBase.cs @@ -428,7 +428,7 @@ namespace NzbDrone.Core.Indexers ex.Response.Content.Contains("not support the requested query")) { _logger.Warn(ex, "Indexer does not support the query"); - return new ValidationFailure(string.Empty, _localizationService.GetLocalizedString("IndexerValidationQueryNotSupported")); + return new ValidationFailure(string.Empty, _localizationService.GetLocalizedString("IndexerValidationQuerySeasonEpisodesNotSupported")); } _logger.Warn(ex, "Unable to connect to indexer"); diff --git a/src/NzbDrone.Core/Localization/Core/cs.json b/src/NzbDrone.Core/Localization/Core/cs.json index 3cef163d4..d11d3f779 100644 --- a/src/NzbDrone.Core/Localization/Core/cs.json +++ b/src/NzbDrone.Core/Localization/Core/cs.json @@ -18,7 +18,7 @@ "BackupIntervalHelpText": "Interval mezi automatickými zálohami", "BackupRetentionHelpText": "Automatické zálohy starší než doba uchovávání budou automaticky vyčištěny", "BackupsLoadError": "Nelze načíst zálohy", - "BlocklistReleaseHelpText": "Znovu spustí vyhledávání této epizody a zabrání tomu, aby bylo toto vydání získáno znovu", + "BlocklistReleaseSearchEpisodeAgainHelpText": "Znovu spustí vyhledávání této epizody a zabrání tomu, aby bylo toto vydání získáno znovu", "BranchUpdate": "Větev, která se použije k aktualizaci {appName}u", "BranchUpdateMechanism": "Větev používaná externím aktualizačním mechanismem", "BrowserReloadRequired": "Vyžaduje se opětovné načtení prohlížeče", @@ -174,8 +174,8 @@ "AnalyticsEnabledHelpText": "Odesílání anonymních informací o používání a chybách na servery společnosti {appName}. To zahrnuje informace o vašem prohlížeči, o tom, které stránky webového rozhraní {appName} používáte, hlášení chyb a také informace o verzi operačního systému a běhového prostředí. Tyto informace použijeme k určení priorit funkcí a oprav chyb.", "Anime": "Anime", "AnimeEpisodeFormat": "Formát epizod pro Anime", - "AnimeTypeDescription": "Epizody vydané s použitím absolutního čísla epizody", - "AnimeTypeFormat": "Absolutní číslo epizody ({format})", + "AnimeEpisodeTypeDescription": "Epizody vydané s použitím absolutního čísla epizody", + "AnimeEpisodeTypeFormat": "Absolutní číslo epizody ({format})", "Any": "Jakákoliv", "ApiKey": "Klíč API", "AppUpdated": "{appName} aktualizován", @@ -189,14 +189,14 @@ "BypassDelayIfAboveCustomFormatScore": "Obejít, pokud je vyšší než skóre vlastního formátu", "Calendar": "Kalendář", "CalendarFeed": "{appName} zdroj kalendáře", - "CalendarLegendDownloadedTooltip": "Epizoda byla stažena a roztříděna", - "CalendarLegendDownloadingTooltip": "Epizoda se právě stahuje", - "CalendarLegendFinaleTooltip": "Finále seriálu nebo řady", - "CalendarLegendMissingTooltip": "Epizoda byla odvysílána a na disku chybí", - "CalendarLegendOnAirTooltip": "Epizoda se právě vysílá", - "CalendarLegendPremiereTooltip": "Premiéra seriálu nebo řady", - "CalendarLegendUnairedTooltip": "Epizoda ještě nebyla odvysílána", - "CalendarLegendUnmonitoredTooltip": "Epizoda je nemonitorovaná", + "CalendarLegendEpisodeDownloadedTooltip": "Epizoda byla stažena a roztříděna", + "CalendarLegendEpisodeDownloadingTooltip": "Epizoda se právě stahuje", + "CalendarLegendSeriesFinaleTooltip": "Finále seriálu nebo řady", + "CalendarLegendEpisodeMissingTooltip": "Epizoda byla odvysílána a na disku chybí", + "CalendarLegendEpisodeOnAirTooltip": "Epizoda se právě vysílá", + "CalendarLegendSeriesPremiereTooltip": "Premiéra seriálu nebo řady", + "CalendarLegendEpisodeUnairedTooltip": "Epizoda ještě nebyla odvysílána", + "CalendarLegendEpisodeUnmonitoredTooltip": "Epizoda je nemonitorovaná", "ChmodFolder": "Složka chmod", "ClickToChangeReleaseGroup": "Kliknutím změníte skupinu vydání", "ClickToChangeSeason": "Kliknutím změníte řadu", @@ -213,7 +213,7 @@ "CutoffUnmet": "Vynechat nevyhovující", "Cutoff": "Odříznout", "DailyEpisodeFormat": "Formát denní epizody", - "DailyTypeDescription": "Epizody vydávané denně nebo méně často, které používají rok-měsíc-den (2023-08-04)", + "DailyEpisodeTypeDescription": "Epizody vydávané denně nebo méně často, které používají rok-měsíc-den (2023-08-04)", "CopyUsingHardlinksHelpTextWarning": "Příležitostně mohou zámky souborů bránit přejmenování souborů, které jsou odesílány. Můžete dočasně zakázat odesílání a použít funkci přejmenování {appName} jako řešení.", "CreateEmptySeriesFoldersHelpText": "Vytvoření chybějících složek pro seriály během skenování disku", "CreateEmptySeriesFolders": "Vytvoření prázdných složek pro seriály", @@ -223,7 +223,7 @@ "CustomFormatUnknownConditionOption": "Neznámá možnost '{key}' pro podmínku '{implementation}'", "CustomFormatsLoadError": "Nelze načíst vlastní formáty", "CustomFormatsSettingsSummary": "Vlastní formáty a nastavení", - "CopyUsingHardlinksHelpText": "Pevné odkazy umožňují aplikaci {appName} importovat odesílané torrenty do složky seriálu, aniž by zabíraly další místo na disku nebo kopírovaly celý obsah souboru. Hardlinky budou fungovat pouze v případě, že zdrojový a cílový soubor jsou na stejném svazku", + "CopyUsingHardlinksSeriesHelpText": "Pevné odkazy umožňují aplikaci {appName} importovat odesílané torrenty do složky seriálu, aniž by zabíraly další místo na disku nebo kopírovaly celý obsah souboru. Hardlinky budou fungovat pouze v případě, že zdrojový a cílový soubor jsou na stejném svazku", "CustomFormatHelpText": "{appName} hodnotí každé vydání pomocí součtu bodů za odpovídající vlastní formáty. Pokud by nové vydání zlepšilo skóre při stejné nebo lepší kvalitě, {appName} jej získá.", "Daily": "Denní", "ContinuingOnly": "Pouze pokračující", @@ -240,9 +240,9 @@ "CustomFilters": "Vlastní filtry", "DelayProfiles": "Profily zpoždění", "DelayProfilesLoadError": "Nelze načíst profily zpoždění", - "DelayProfileTagsHelpText": "Platí pro seriály s alespoň jednou odpovídající značkou", + "DelayProfileSeriesTagsHelpText": "Platí pro seriály s alespoň jednou odpovídající značkou", "DelayMinutes": "{delay} Minuty", - "DefaultDelayProfile": "Toto je výchozí profil. Platí pro všechny seriály, které nemají explicitní profil.", + "DefaultDelayProfileSeries": "Toto je výchozí profil. Platí pro všechny seriály, které nemají explicitní profil.", "CustomFormatJson": "Vlastní JSON formát", "Debug": "Ladit", "Day": "Den", @@ -269,7 +269,7 @@ "Date": "Datum", "Dates": "Termíny", "DefaultCase": "Výchozí případ", - "DailyTypeFormat": "Datum ({format})", + "DailyEpisodeTypeFormat": "Datum ({format})", "Default": "Výchozí", "IndexerDownloadClientHelpText": "Zvolte, který klient pro stahování bude použit pro zachytávání z toho indexeru" } diff --git a/src/NzbDrone.Core/Localization/Core/en.json b/src/NzbDrone.Core/Localization/Core/en.json index 331e4da45..918ba4cd1 100644 --- a/src/NzbDrone.Core/Localization/Core/en.json +++ b/src/NzbDrone.Core/Localization/Core/en.json @@ -33,7 +33,7 @@ "AddListError": "Unable to add a new list, please try again.", "AddListExclusion": "Add List Exclusion", "AddListExclusionError": "Unable to add a new list exclusion, please try again.", - "AddListExclusionHelpText": "Prevent series from being added to {appName} by lists", + "AddListExclusionSeriesHelpText": "Prevent series from being added to {appName} by lists", "AddNew": "Add New", "AddNewRestriction": "Add new restriction", "AddNewSeries": "Add New Series", @@ -81,8 +81,8 @@ "AnalyticsEnabledHelpText": "Send anonymous usage and error information to {appName}'s servers. This includes information on your browser, which {appName} WebUI pages you use, error reporting as well as OS and runtime version. We will use this information to prioritize features and bug fixes.", "Anime": "Anime", "AnimeEpisodeFormat": "Anime Episode Format", - "AnimeTypeDescription": "Episodes released using an absolute episode number", - "AnimeTypeFormat": "Absolute episode number ({format})", + "AnimeEpisodeTypeDescription": "Episodes released using an absolute episode number", + "AnimeEpisodeTypeFormat": "Absolute episode number ({format})", "Any": "Any", "ApiKey": "API Key", "ApiKeyValidationHealthCheckMessage": "Please update your API key to be at least {length} characters long. You can do this via settings or the config file", @@ -146,7 +146,7 @@ "Blocklist": "Blocklist", "BlocklistLoadError": "Unable to load blocklist", "BlocklistRelease": "Blocklist Release", - "BlocklistReleaseHelpText": "Starts a search for this episode again and prevents this release from being grabbed again", + "BlocklistReleaseSearchEpisodeAgainHelpText": "Starts a search for this episode again and prevents this release from being grabbed again", "BlocklistReleases": "Blocklist Releases", "Branch": "Branch", "BranchUpdate": "Branch to use to update {appName}", @@ -162,14 +162,14 @@ "BypassProxyForLocalAddresses": "Bypass Proxy for Local Addresses", "Calendar": "Calendar", "CalendarFeed": "{appName} Calendar Feed", - "CalendarLegendDownloadedTooltip": "Episode was downloaded and sorted", - "CalendarLegendDownloadingTooltip": "Episode is currently downloading", - "CalendarLegendFinaleTooltip": "Series or season finale", - "CalendarLegendMissingTooltip": "Episode has aired and is missing from disk", - "CalendarLegendOnAirTooltip": "Episode is currently airing", - "CalendarLegendPremiereTooltip": "Series or season premiere", - "CalendarLegendUnairedTooltip": "Episode hasn't aired yet", - "CalendarLegendUnmonitoredTooltip": "Episode is unmonitored", + "CalendarLegendEpisodeDownloadedTooltip": "Episode was downloaded and sorted", + "CalendarLegendEpisodeDownloadingTooltip": "Episode is currently downloading", + "CalendarLegendEpisodeMissingTooltip": "Episode has aired and is missing from disk", + "CalendarLegendEpisodeOnAirTooltip": "Episode is currently airing", + "CalendarLegendEpisodeUnairedTooltip": "Episode hasn't aired yet", + "CalendarLegendEpisodeUnmonitoredTooltip": "Episode is unmonitored", + "CalendarLegendSeriesFinaleTooltip": "Series or season finale", + "CalendarLegendSeriesPremiereTooltip": "Series or season premiere", "CalendarLoadError": "Unable to load the calendar", "CalendarOptions": "Calendar Options", "Cancel": "Cancel", @@ -230,8 +230,8 @@ "ContinuingOnly": "Continuing Only", "ContinuingSeriesDescription": "More episodes/another season is expected", "CopyToClipboard": "Copy to Clipboard", - "CopyUsingHardlinksHelpText": "Hardlinks allow {appName} to import seeding torrents to the series folder without taking extra disk space or copying the entire contents of the file. Hardlinks will only work if the source and destination are on the same volume", "CopyUsingHardlinksHelpTextWarning": "Occasionally, file locks may prevent renaming files that are being seeded. You may temporarily disable seeding and use {appName}'s rename function as a work around.", + "CopyUsingHardlinksSeriesHelpText": "Hardlinks allow {appName} to import seeding torrents to the series folder without taking extra disk space or copying the entire contents of the file. Hardlinks will only work if the source and destination are on the same volume", "CouldNotFindResults": "Couldn't find any results for '{term}'", "CountDownloadClientsSelected": "{count} download client(s) selected", "CountImportListsSelected": "{count} import list(s) selected", @@ -262,8 +262,8 @@ "CutoffUnmetNoItems": "No cutoff unmet items", "Daily": "Daily", "DailyEpisodeFormat": "Daily Episode Format", - "DailyTypeDescription": "Episodes released daily or less frequently that use year-month-day (2023-08-04)", - "DailyTypeFormat": "Date ({format})", + "DailyEpisodeTypeDescription": "Episodes released daily or less frequently that use year-month-day (2023-08-04)", + "DailyEpisodeTypeFormat": "Date ({format})", "Dash": "Dash", "Database": "Database", "Date": "Date", @@ -272,14 +272,14 @@ "Debug": "Debug", "Default": "Default", "DefaultCase": "Default Case", - "DefaultDelayProfile": "This is the default profile. It applies to all series that don't have an explicit profile.", + "DefaultDelayProfileSeries": "This is the default profile. It applies to all series that don't have an explicit profile.", "DefaultNameCopiedProfile": "{name} - Copy", "DefaultNameCopiedSpecification": "{name} - Copy", "DefaultNotFoundMessage": "You must be lost, nothing to see here.", "DelayMinutes": "{delay} Minutes", "DelayProfile": "Delay Profile", "DelayProfileProtocol": "Protocol: {preferredProtocol}", - "DelayProfileTagsHelpText": "Applies to series with at least one matching tag", + "DelayProfileSeriesTagsHelpText": "Applies to series with at least one matching tag", "DelayProfiles": "Delay Profiles", "DelayProfilesLoadError": "Unable to load Delay Profiles", "DelayingDownloadUntil": "Delaying download until {date} at {time}", @@ -297,7 +297,7 @@ "DeleteDownloadClient": "Delete Download Client", "DeleteDownloadClientMessageText": "Are you sure you want to delete the download client '{name}'?", "DeleteEmptyFolders": "Delete Empty Folders", - "DeleteEmptyFoldersHelpText": "Delete empty series and season folders during disk scan and when episode files are deleted", + "DeleteEmptySeriesFoldersHelpText": "Delete empty series and season folders during disk scan and when episode files are deleted", "DeleteEpisodeFile": "Delete Episode File", "DeleteEpisodeFileMessage": "Are you sure you want to delete '{path}'?", "DeleteEpisodeFromDisk": "Delete episode from disk", @@ -342,8 +342,8 @@ "DeleteTag": "DeleteTag", "DeleteTagMessageText": "Are you sure you want to delete the tag '{label}'?", "Deleted": "Deleted", + "DeletedReasonEpisodeMissingFromDisk": "{appName} was unable to find the file on disk so the file was unlinked from the episode in the database", "DeletedReasonManual": "File was deleted by via UI", - "DeletedReasonMissingFromDisk": "{appName} was unable to find the file on disk so the file was unlinked from the episode in the database", "DeletedReasonUpgrade": "File was deleted to import an upgrade", "DeletedSeriesDescription": "Series was deleted from TheTVDB", "Destination": "Destination", @@ -459,6 +459,7 @@ "DownloadClientSabnzbdValidationEnableJobFolders": "Enable Job folders", "DownloadClientSabnzbdValidationEnableJobFoldersDetail": "{appName} prefers each download to have a separate folder. With * appended to the Folder/Path Sabnzbd will not create these job folders. Go to Sabnzbd to fix it.", "DownloadClientSabnzbdValidationUnknownVersion": "Unknown Version: {rawVersion}", + "DownloadClientSeriesTagHelpText": "Only use this download client for series with at least one matching tag. Leave blank to use with all series.", "DownloadClientSettings": "Download Client Settings", "DownloadClientSettingsAddPaused": "Add Paused", "DownloadClientSettingsCategoryHelpText": "Adding a category specific to {appName} avoids conflicts with unrelated non-{appName} downloads. Using a category is optional, but strongly recommended.", @@ -467,16 +468,15 @@ "DownloadClientSettingsInitialState": "Initial State", "DownloadClientSettingsInitialStateHelpText": "Initial state for torrents added to {clientName}", "DownloadClientSettingsOlderPriority": "Older Priority", - "DownloadClientSettingsOlderPriorityHelpText": "Priority to use when grabbing episodes that aired over 14 days ago", + "DownloadClientSettingsOlderPriorityEpisodeHelpText": "Priority to use when grabbing episodes that aired over 14 days ago", "DownloadClientSettingsPostImportCategoryHelpText": "Category for {appName} to set after it has imported the download. {appName} will not remove torrents in that category even if seeding finished. Leave blank to keep same category.", "DownloadClientSettingsRecentPriority": "Recent Priority", - "DownloadClientSettingsRecentPriorityHelpText": "Priority to use when grabbing episodes that aired within the last 14 days", + "DownloadClientSettingsRecentPriorityEpisodeHelpText": "Priority to use when grabbing episodes that aired within the last 14 days", "DownloadClientSettingsUrlBaseHelpText": "Adds a prefix to the {clientName} url, such as {url}", "DownloadClientSettingsUseSslHelpText": "Use secure connection when connection to {clientName}", "DownloadClientSortingHealthCheckMessage": "Download client {downloadClientName} has {sortingMode} sorting enabled for {appName}'s category. You should disable sorting in your download client to avoid import issues.", "DownloadClientStatusAllClientHealthCheckMessage": "All download clients are unavailable due to failures", "DownloadClientStatusSingleClientHealthCheckMessage": "Download clients unavailable due to failures: {downloadClientNames}", - "DownloadClientTagHelpText": "Only use this download client for series with at least one matching tag. Leave blank to use with all series.", "DownloadClientTransmissionSettingsDirectoryHelpText": "Optional location to put downloads in, leave blank to use the default Transmission location", "DownloadClientTransmissionSettingsUrlBaseHelpText": "Adds a prefix to the {clientName} rpc url, eg {url}, defaults to '{defaultUrl}'", "DownloadClientUTorrentTorrentStateError": "uTorrent is reporting an error", @@ -503,9 +503,9 @@ "DownloadClientsLoadError": "Unable to load download clients", "DownloadClientsSettingsSummary": "Download clients, download handling and remote path mappings", "DownloadFailed": "Download Failed", - "DownloadFailedTooltip": "Episode download failed", + "DownloadFailedEpisodeTooltip": "Episode download failed", "DownloadIgnored": "Download Ignored", - "DownloadIgnoredTooltip": "Episode Download Ignored", + "DownloadIgnoredEpisodeTooltip": "Episode Download Ignored", "DownloadPropersAndRepacks": "Propers and Repacks", "DownloadPropersAndRepacksHelpText": "Whether or not to automatically upgrade to Propers/Repacks", "DownloadPropersAndRepacksHelpTextCustomFormat": "Use 'Do not Prefer' to sort by custom format score over Propers/Repacks", @@ -541,7 +541,7 @@ "EditSeriesModalHeader": "Edit - {title}", "Enable": "Enable", "EnableAutomaticAdd": "Enable Automatic Add", - "EnableAutomaticAddHelpText": "Add series from this list to {appName} when syncs are performed via the UI or by {appName}", + "EnableAutomaticAddSeriesHelpText": "Add series from this list to {appName} when syncs are performed via the UI or by {appName}", "EnableAutomaticSearch": "Enable Automatic Search", "EnableAutomaticSearchHelpText": "Will be used when automatic searches are performed via the UI or by {appName}", "EnableAutomaticSearchHelpTextWarning": "Will be used when interactive search is used", @@ -573,6 +573,7 @@ "EpisodeFileRenamed": "Episode File Renamed", "EpisodeFileRenamedTooltip": "Episode file renamed", "EpisodeFilesLoadError": "Unable to load episode files", + "EpisodeGrabbedTooltip": "Episode grabbed from {indexer} and sent to {downloadClient}", "EpisodeHasNotAired": "Episode has not aired", "EpisodeHistoryLoadError": "Unable to load episode history", "EpisodeImported": "Episode Imported", @@ -691,10 +692,9 @@ "Grab": "Grab", "GrabId": "Grab ID", "GrabRelease": "Grab Release", - "GrabReleaseMessageText": "{appName} was unable to determine which series and episode this release was for. {appName} may be unable to automatically import this release. Do you want to grab '{title}'?", + "GrabReleaseUnknownSeriesOrEpisodeMessageText": "{appName} was unable to determine which series and episode this release was for. {appName} may be unable to automatically import this release. Do you want to grab '{title}'?", "GrabSelected": "Grab Selected", "Grabbed": "Grabbed", - "GrabbedHistoryTooltip": "Episode grabbed from {indexer} and sent to {downloadClient}", "Group": "Group", "HardlinkCopyFiles": "Hardlink/Copy Files", "HasMissingSeason": "Has Missing Season", @@ -715,12 +715,12 @@ "HttpHttps": "HTTP(S)", "ICalFeed": "iCal Feed", "ICalFeedHelpText": "Copy this URL to your client(s) or click to subscribe if your browser supports webcal", - "ICalIncludeUnmonitoredHelpText": "Include unmonitored episodes in the iCal feed", + "ICalIncludeUnmonitoredEpisodesHelpText": "Include unmonitored episodes in the iCal feed", "ICalLink": "iCal Link", "ICalSeasonPremieresOnlyHelpText": "Only the first episode in a season will be in the feed", "ICalShowAsAllDayEvents": "Show as All-Day Events", "ICalShowAsAllDayEventsHelpText": "Events will appear as all-day events in your calendar", - "ICalTagsHelpText": "Feed will only contain series with at least one matching tag", + "ICalTagsSeriesHelpText": "Feed will only contain series with at least one matching tag", "IRC": "IRC", "IRCLinkText": "#sonarr on Libera", "IconForCutoffUnmet": "Icon for Cutoff Unmet", @@ -740,7 +740,7 @@ "ImportErrors": "Import Errors", "ImportExistingSeries": "Import Existing Series", "ImportExtraFiles": "Import Extra Files", - "ImportExtraFilesHelpText": "Import matching extra files (subtitles, nfo, etc) after importing an episode file", + "ImportExtraFilesEpisodeHelpText": "Import matching extra files (subtitles, nfo, etc) after importing an episode file", "ImportFailed": "Import Failed: {sourceTitle}", "ImportList": "Import List", "ImportListExclusions": "Import List Exclusions", @@ -824,7 +824,7 @@ "IndexerSettingsWebsiteUrl": "Website URL", "IndexerStatusAllUnavailableHealthCheckMessage": "All indexers are unavailable due to failures", "IndexerStatusUnavailableHealthCheckMessage": "Indexers unavailable due to failures: {indexerNames}", - "IndexerTagHelpText": "Only use this indexer for series with at least one matching tag. Leave blank to use with all series.", + "IndexerTagSeriesHelpText": "Only use this indexer for series with at least one matching tag. Leave blank to use with all series.", "IndexerValidationCloudFlareCaptchaExpired": "CloudFlare CAPTCHA token expired, please refresh it.", "IndexerValidationCloudFlareCaptchaRequired": "Site protected by CloudFlare CAPTCHA. Valid CAPTCHA token required.", "IndexerValidationFeedNotSupported": "Indexer feed is not supported: {exceptionMessage}", @@ -833,7 +833,7 @@ "IndexerValidationJackettAllNotSupportedHelpText": "Jackett's all endpoint is not supported, please add indexers individually", "IndexerValidationJackettNoResultsInConfiguredCategories": "Query successful, but no results in the configured categories were returned from your indexer. This may be an issue with the indexer or your indexer category settings.", "IndexerValidationJackettNoRssFeedQueryAvailable": "No RSS feed query available. This may be an issue with the indexer or your indexer category settings.", - "IndexerValidationQueryNotSupported": "Indexer does not support the current query. Check if the categories and or searching for seasons/episodes are supported. Check the log for more details.", + "IndexerValidationQuerySeasonEpisodesNotSupported": "Indexer does not support the current query. Check if the categories and or searching for seasons/episodes are supported. Check the log for more details.", "IndexerValidationRequestLimitReached": "Request limit reached: {exceptionMessage}", "IndexerValidationSearchParametersNotSupported": "Indexer does not support required search parameters", "IndexerValidationTestAbortedDueToError": "Test was aborted due to an error: {exceptionMessage}", @@ -863,7 +863,7 @@ "InteractiveSearch": "Interactive Search", "InteractiveSearchModalHeader": "Interactive Search", "InteractiveSearchModalHeaderSeason": "Interactive Search - {season}", - "InteractiveSearchResultsFailedErrorMessage": "Search failed because its {message}. Try refreshing the series info and verify the necessary information is present before searching again.", + "InteractiveSearchResultsSeriesFailedErrorMessage": "Search failed because its {message}. Try refreshing the series info and verify the necessary information is present before searching again.", "InteractiveSearchSeason": "Interactive search for all episodes in this season", "Interval": "Interval", "InvalidFormat": "Invalid Format", @@ -886,11 +886,11 @@ "Level": "Level", "LiberaWebchat": "Libera Webchat", "LibraryImport": "Library Import", - "LibraryImportHeader": "Import series you already have", + "LibraryImportSeriesHeader": "Import series you already have", "LibraryImportTips": "Some tips to ensure the import goes smoothly:", "LibraryImportTipsDontUseDownloadsFolder": "Do not use for importing downloads from your download client, this is only for existing organized libraries, not unsorted files.", - "LibraryImportTipsQualityInFilename": "Make sure that your files include the quality in their filenames. eg. `episode.s02e15.bluray.mkv`", - "LibraryImportTipsUseRootFolder": "Point {appName} to the folder containing all of your tv shows, not a specific one. eg. \"`{goodFolderExample}`\" and not \"`{badFolderExample}`\". Additionally, each series must be in its own folder within the root/library folder.", + "LibraryImportTipsQualityInEpisodeFilename": "Make sure that your files include the quality in their filenames. eg. `episode.s02e15.bluray.mkv`", + "LibraryImportTipsSeriesUseRootFolder": "Point {appName} to the folder containing all of your tv shows, not a specific one. eg. \"`{goodFolderExample}`\" and not \"`{badFolderExample}`\". Additionally, each series must be in its own folder within the root/library folder.", "Links": "Links", "ListExclusionsLoadError": "Unable to load List Exclusions", "ListOptionsLoadError": "Unable to load list options", @@ -953,10 +953,10 @@ "MetadataLoadError": "Unable to load Metadata", "MetadataProvidedBy": "Metadata is provided by {provider}", "MetadataSettings": "Metadata Settings", - "MetadataSettingsSummary": "Create metadata files when episodes are imported or series are refreshed", + "MetadataSettingsSeriesSummary": "Create metadata files when episodes are imported or series are refreshed", "MetadataSource": "Metadata Source", "MetadataSourceSettings": "Metadata Source Settings", - "MetadataSourceSettingsSummary": "Information on where {appName} gets series and episode information", + "MetadataSourceSettingsSeriesSummary": "Information on where {appName} gets series and episode information", "MidseasonFinale": "Midseason Finale", "Min": "Min", "MinimumAge": "Minimum Age", @@ -991,21 +991,21 @@ "MonitorLastSeasonDescription": "Monitor all episodes of the last season", "MonitorMissingEpisodes": "Missing Episodes", "MonitorMissingEpisodesDescription": "Monitor episodes that do not have files or have not aired yet", + "MonitorNoEpisodes": "None", + "MonitorNoEpisodesDescription": "No episodes will be monitored", "MonitorNewSeasons": "Monitor New Seasons", "MonitorNewSeasonsHelpText": "Which new seasons should be monitored automatically", "MonitorNoNewSeasonsDescription": "Do not monitor any new seasons automatically", - "MonitorNone": "None", - "MonitorNoneDescription": "No episodes will be monitored", "MonitorPilotEpisode": "Pilot Episode", "MonitorPilotEpisodeDescription": "Only monitor the first episode of the first season", "MonitorRecentEpisodes": "Recent Episodes", "MonitorRecentEpisodesDescription": "Monitor episodes aired within the last 90 days and future episodes", "MonitorSelected": "Monitor Selected", "MonitorSeries": "Monitor Series", - "MonitorSpecials": "Monitor Specials", - "MonitorSpecialsDescription": "Monitor all special episodes without changing the monitored status of other episodes", + "MonitorSpecialEpisodes": "Monitor Specials", + "MonitorSpecialEpisodesDescription": "Monitor all special episodes without changing the monitored status of other episodes", "Monitored": "Monitored", - "MonitoredHelpText": "Download monitored episodes in this series", + "MonitoredEpisodesHelpText": "Download monitored episodes in this series", "MonitoredOnly": "Monitored Only", "MonitoredStatus": "Monitored/Status", "Monitoring": "Monitoring", @@ -1014,7 +1014,7 @@ "More": "More", "MoreDetails": "More details", "MoreInfo": "More Info", - "MountHealthCheckMessage": "Mount containing a series path is mounted read-only: ", + "MountSeriesHealthCheckMessage": "Mount containing a series path is mounted read-only: ", "MoveAutomatically": "Move Automatically", "MoveFiles": "Move Files", "MoveSeriesFoldersDontMoveFiles": "No, I'll Move the Files Myself", @@ -1081,7 +1081,7 @@ "NotificationTriggers": "Notification Triggers", "NotificationTriggersHelpText": "Select which events should trigger this notification", "NotificationsLoadError": "Unable to load Notifications", - "NotificationsTagsHelpText": "Only send notifications for series with at least one matching tag", + "NotificationsTagsSeriesHelpText": "Only send notifications for series with at least one matching tag", "NzbgetHistoryItemMessage": "PAR Status: {parStatus} - Unpack Status: {unpackStatus} - Move Status: {moveStatus} - Script Status: {scriptStatus} - Delete Status: {deleteStatus} - Mark Status: {markStatus}", "Ok": "Ok", "OnApplicationUpdate": "On Application Update", @@ -1198,9 +1198,9 @@ "QualityCutoffNotMet": "Quality cutoff has not been met", "QualityDefinitions": "Quality Definitions", "QualityDefinitionsLoadError": "Unable to load Quality Definitions", - "QualityLimitsHelpText": "Limits are automatically adjusted for the series runtime and number of episodes in the file.", + "QualityLimitsSeriesRuntimeHelpText": "Limits are automatically adjusted for the series runtime and number of episodes in the file.", "QualityProfile": "Quality Profile", - "QualityProfileInUse": "Can't delete a quality profile that is attached to a series, list, or collection", + "QualityProfileInUseSeriesListCollection": "Can't delete a quality profile that is attached to a series, list, or collection", "QualityProfiles": "Quality Profiles", "QualityProfilesLoadError": "Unable to load Quality Profiles", "QualitySettings": "Quality Settings", @@ -1222,7 +1222,7 @@ "RecyclingBinCleanup": "Recycling Bin Cleanup", "RecyclingBinCleanupHelpText": "Set to 0 to disable automatic cleanup", "RecyclingBinCleanupHelpTextWarning": "Files in the recycle bin older than the selected number of days will be cleaned up automatically", - "RecyclingBinHelpText": "Episode files will go here when deleted instead of being permanently deleted", + "RecyclingBinHelpText": "Files will go here when deleted instead of being permanently deleted", "Refresh": "Refresh", "RefreshAndScan": "Refresh & Scan", "RefreshAndScanTooltip": "Refresh information and scan disk", @@ -1240,7 +1240,7 @@ "ReleaseProfile": "Release Profile", "ReleaseProfileIndexerHelpText": "Specify what indexer the profile applies to", "ReleaseProfileIndexerHelpTextWarning": "Using a specific indexer with release profiles can lead to duplicate releases being grabbed", - "ReleaseProfileTagHelpText": "Release profiles will apply to series with at least one matching tag. Leave blank to apply to all series", + "ReleaseProfileTagSeriesHelpText": "Release profiles will apply to series with at least one matching tag. Leave blank to apply to all series", "ReleaseProfiles": "Release Profiles", "ReleaseProfilesLoadError": "Unable to load Release Profiles", "ReleaseRejected": "Release Rejected", @@ -1255,7 +1255,7 @@ "RemotePath": "Remote Path", "RemotePathMappingBadDockerPathHealthCheckMessage": "You are using docker; download client {downloadClientName} places downloads in {path} but this is not a valid {osName} path. Review your remote path mappings and download client settings.", "RemotePathMappingDockerFolderMissingHealthCheckMessage": "You are using docker; download client {downloadClientName} places downloads in {path} but this directory does not appear to exist inside the container. Review your remote path mappings and container volume settings.", - "RemotePathMappingDownloadPermissionsHealthCheckMessage": "{appName} can see but not access downloaded episode {path}. Likely permissions error.", + "RemotePathMappingDownloadPermissionsEpisodeHealthCheckMessage": "{appName} can see but not access downloaded episode {path}. Likely permissions error.", "RemotePathMappingFileRemovedHealthCheckMessage": "File {path} was removed part way through processing.", "RemotePathMappingFilesBadDockerPathHealthCheckMessage": "You are using docker; download client {downloadClientName} reported files in {path} but this is not a valid {osName} path. Review your remote path mappings and download client settings.", "RemotePathMappingFilesGenericPermissionsHealthCheckMessage": "Download client {downloadClientName} reported files in {path} but {appName} cannot see this directory. You may need to adjust the folder's permissions.", @@ -1264,7 +1264,7 @@ "RemotePathMappingFolderPermissionsHealthCheckMessage": "{appName} can see but not access download directory {downloadPath}. Likely permissions error.", "RemotePathMappingGenericPermissionsHealthCheckMessage": "Download client {downloadClientName} places downloads in {path} but {appName} cannot see this directory. You may need to adjust the folder's permissions.", "RemotePathMappingHostHelpText": "The same host you specified for the remote Download Client", - "RemotePathMappingImportFailedHealthCheckMessage": "{appName} failed to import (an) episode(s). Check your logs for details.", + "RemotePathMappingImportEpisodeFailedHealthCheckMessage": "{appName} failed to import (an) episode(s). Check your logs for details.", "RemotePathMappingLocalFolderMissingHealthCheckMessage": "Remote download client {downloadClientName} places downloads in {path} but this directory does not appear to exist. Likely missing or incorrect remote path mapping.", "RemotePathMappingLocalPathHelpText": "Path that {appName} should use to access the remote path locally", "RemotePathMappingLocalWrongOSPathHealthCheckMessage": "Local download client {downloadClientName} places downloads in {path} but this is not a valid {osName} path. Review your download client settings.", @@ -1317,8 +1317,8 @@ "ReplaceWithSpaceDashSpace": "Replace with Space Dash Space", "Required": "Required", "RequiredHelpText": "This {implementationName} condition must match for the custom format to apply. Otherwise a single {implementationName} match is sufficient.", - "RescanAfterRefreshHelpText": "Rescan the series folder after refreshing the series", "RescanAfterRefreshHelpTextWarning": "{appName} will not automatically detect changes to files when not set to 'Always'", + "RescanAfterRefreshSeriesHelpText": "Rescan the series folder after refreshing the series", "RescanSeriesFolderAfterRefresh": "Rescan Series Folder after Refresh", "Reset": "Reset", "ResetAPIKey": "Reset API Key", @@ -1374,10 +1374,10 @@ "SearchAll": "Search All", "SearchByTvdbId": "You can also search using TVDB ID of a show. eg. tvdb:71663", "SearchFailedError": "Search failed, please try again later.", - "SearchForAllMissing": "Search for all missing episodes", - "SearchForAllMissingConfirmationCount": "Are you sure you want to search for all {totalRecords} missing episodes?", - "SearchForCutoffUnmet": "Search for all Cutoff Unmet episodes", - "SearchForCutoffUnmetConfirmationCount": "Are you sure you want to search for all {totalRecords} Cutoff Unmet episodes?", + "SearchForAllMissingEpisodes": "Search for all missing episodes", + "SearchForAllMissingEpisodesConfirmationCount": "Are you sure you want to search for all {totalRecords} missing episodes?", + "SearchForCutoffUnmetEpisodes": "Search for all Cutoff Unmet episodes", + "SearchForCutoffUnmetEpisodesConfirmationCount": "Are you sure you want to search for all {totalRecords} Cutoff Unmet episodes?", "SearchForMissing": "Search for Missing", "SearchForMonitoredEpisodes": "Search for monitored episodes", "SearchForMonitoredEpisodesSeason": "Search for monitored episodes in this season", @@ -1478,9 +1478,9 @@ "ShowSearch": "Show Search", "ShowSearchHelpText": "Show search button on hover", "ShowSeasonCount": "Show Season Count", + "ShowSeriesTitleHelpText": "Show series title under poster", "ShowSizeOnDisk": "Show Size on Disk", "ShowTitle": "Show Title", - "ShowTitleHelpText": "Show series title under poster", "ShowUnknownSeriesItems": "Show Unknown Series Items", "ShowUnknownSeriesItemsHelpText": "Show items without a series in the queue, this could include removed series, movies or anything else in {appName}'s category", "ShownClickToHide": "Shown, click to hide", @@ -1518,8 +1518,8 @@ "SslPort": "SSL Port", "Standard": "Standard", "StandardEpisodeFormat": "Standard Episode Format", - "StandardTypeDescription": "Episodes released with SxxEyy pattern", - "StandardTypeFormat": "Season and episode numbers ({format})", + "StandardEpisodeTypeDescription": "Episodes released with SxxEyy pattern", + "StandardEpisodeTypeFormat": "Season and episode numbers ({format})", "StartImport": "Start Import", "StartProcessing": "Start Processing", "Started": "Started", @@ -1536,8 +1536,8 @@ "SupportedImportListsMoreInfo": "For more information on the individual import lists, click on the more info buttons.", "SupportedIndexers": "{appName} supports any indexer that uses the Newznab standard, as well as other indexers listed below.", "SupportedIndexersMoreInfo": "For more information on the individual indexers, click on the more info buttons.", - "SupportedLists": "{appName} supports multiple lists for importing Series into the database.", "SupportedListsMoreInfo": "For more information on the individual lists, click on the more info buttons.", + "SupportedListsSeries": "{appName} supports multiple lists for importing Series into the database.", "System": "System", "SystemTimeHealthCheckMessage": "System time is off by more than 1 day. Scheduled tasks may not run correctly until the time is corrected", "Table": "Table", @@ -1630,8 +1630,8 @@ "UnmonitorDeletedEpisodes": "Unmonitor Deleted Episodes", "UnmonitorDeletedEpisodesHelpText": "Episodes deleted from disk are automatically unmonitored in {appName}", "UnmonitorSelected": "Unmonitor Selected", - "UnmonitorSpecials": "Unmonitor Specials", - "UnmonitorSpecialsDescription": "Unmonitor all special episodes without changing the monitored status of other episodes", + "UnmonitorSpecialEpisodes": "Unmonitor Specials", + "UnmonitorSpecialsEpisodesDescription": "Unmonitor all special episodes without changing the monitored status of other episodes", "Unmonitored": "Unmonitored", "UnmonitoredOnly": "Unmonitored Only", "UnsavedChanges": "Unsaved Changes", @@ -1654,8 +1654,8 @@ "Updates": "Updates", "UpgradeUntil": "Upgrade Until", "UpgradeUntilCustomFormatScore": "Upgrade Until Custom Format Score", - "UpgradeUntilCustomFormatScoreHelpText": "Once this custom format score is reached {appName} will no longer grab episode releases", - "UpgradeUntilHelpText": "Once this quality is reached {appName} will no longer download episodes", + "UpgradeUntilCustomFormatScoreEpisodeHelpText": "Once this custom format score is reached {appName} will no longer grab episode releases", + "UpgradeUntilEpisodeHelpText": "Once this quality is reached {appName} will no longer download episodes", "UpgradeUntilThisQualityIsMetOrExceeded": "Upgrade until this quality is met or exceeded", "UpgradesAllowed": "Upgrades Allowed", "UpgradesAllowedHelpText": "If disabled qualities will not be upgraded", diff --git a/src/NzbDrone.Core/Localization/Core/es.json b/src/NzbDrone.Core/Localization/Core/es.json index bfa448698..cd266a6ca 100644 --- a/src/NzbDrone.Core/Localization/Core/es.json +++ b/src/NzbDrone.Core/Localization/Core/es.json @@ -8,7 +8,7 @@ "Failed": "Fallido", "Example": "Ejemplo", "Ignored": "Ignorado", - "IndexerTagHelpText": "Solo utilizar este indexador para series que coincidan con al menos una etiqueta. Déjelo en blanco para utilizarlo con todas las series.", + "IndexerTagSeriesHelpText": "Solo utilizar este indexador para series que coincidan con al menos una etiqueta. Déjelo en blanco para utilizarlo con todas las series.", "Options": "Opciones", "Paused": "Pausado", "Runtime": "Tiempo de duración", @@ -166,7 +166,7 @@ "AgeWhenGrabbed": "Antigüedad (cuando se añadió)", "AllResultsAreHiddenByTheAppliedFilter": "Todos los resultados están ocultos por el filtro aplicado", "AnalyseVideoFilesHelpText": "Extraer información de video como la resolución, el tiempo de ejecución y la información del códec de los archivos. Esto requiere que {appName} lea partes del archivo lo cual puede causar una alta actividad en el disco o en la red durante los escaneos.", - "AnimeTypeDescription": "Episodios lanzados usando un número de episodio absoluto", + "AnimeEpisodeTypeDescription": "Episodios lanzados usando un número de episodio absoluto", "ApiKeyValidationHealthCheckMessage": "Actualice su clave de API para que tenga al menos {length} carácteres. Puede hacerlo en los ajustes o en el archivo de configuración", "AppDataLocationHealthCheckMessage": "No será posible actualizar para prevenir la eliminación de AppData al Actualizar", "Scheduled": "Programado", diff --git a/src/NzbDrone.Core/Localization/Core/fi.json b/src/NzbDrone.Core/Localization/Core/fi.json index 1cd0454c8..3806de293 100644 --- a/src/NzbDrone.Core/Localization/Core/fi.json +++ b/src/NzbDrone.Core/Localization/Core/fi.json @@ -1,7 +1,7 @@ { - "BlocklistReleaseHelpText": "Etsii kohdetta uudelleen ja estää {appName}ia sieppaamasta tätä julkaisua automaattisesti uudelleen.", + "BlocklistReleaseSearchEpisodeAgainHelpText": "Etsii kohdetta uudelleen ja estää {appName}ia sieppaamasta tätä julkaisua automaattisesti uudelleen.", "RecycleBinUnableToWriteHealthCheckMessage": "Määritettyyn roskakorikansioon ei voida tallentaa: {path}. Varmista että sijainti on olemassa ja että sovelluksen suorittavalla käyttäjällä on siihen kirjoitusoikeus.", - "RemotePathMappingDownloadPermissionsHealthCheckMessage": "{appName} näkee ladatun jakson \"{path}\", muttei voi käyttää sitä. Tämä johtuu todennäköisesti liian rajallisista käyttöoikeuksista.", + "RemotePathMappingDownloadPermissionsEpisodeHealthCheckMessage": "{appName} näkee ladatun jakson \"{path}\", muttei voi käyttää sitä. Tämä johtuu todennäköisesti liian rajallisista käyttöoikeuksista.", "Added": "Lisätty", "AppDataLocationHealthCheckMessage": "Päivitystä ei sallita, jotta AppData-kansion poisto päivityksen yhteydessä voidaan estää.", "DownloadClientSortingHealthCheckMessage": "", @@ -9,7 +9,7 @@ "IndexerSearchNoInteractiveHealthCheckMessage": "Manuaalista hakua varten ei ole määritetty tietolähteitä, eikä manuaalinen haku sen vuoksi löydä tuloksia.", "RemotePathMappingFilesGenericPermissionsHealthCheckMessage": "Lataustyökalu \"{downloadClientName}\" ilmoitti tiedostosijainniksi \"{path}\", mutta {appName} ei näe kansiota. Tämä johtuu todennäköisesti liian rajallisista käyttöoikeuksista.", "RemotePathMappingFolderPermissionsHealthCheckMessage": "{appName} näkee ladatauskansion \"{downloadPath}\" näkyy, muttei voi käyttää sitä. Tämä johtuu todennäköisesti liian rajallisista käyttöoikeuksista.", - "RemotePathMappingImportFailedHealthCheckMessage": "Jaksojen tuonti epäonnistui. Katso tarkemmat tiedot lokista.", + "RemotePathMappingImportEpisodeFailedHealthCheckMessage": "Jaksojen tuonti epäonnistui. Katso tarkemmat tiedot lokista.", "RemotePathMappingGenericPermissionsHealthCheckMessage": "Lataustyökalu \"{downloadClientName}\" tallentaa latauksen sijaintiin \"{path}\", mutta {appName} ei näe sitä. Tämä johtuu todennäköisesti liian rajallisista käyttöoikeuksista.", "IndexerSearchNoAutomaticHealthCheckMessage": "Automaattista hakua varten ei ole määritetty tietolähteitä, eikä automaattinen haku sen vuoksi löydä tuloksia.", "AgeWhenGrabbed": "Ikä (sieppaushetkellä)", @@ -46,7 +46,7 @@ "UiLanguage": "Käyttöliittymän kieli", "UiLanguageHelpText": "Käyttöliittymä näytetään tällä kielellä.", "AutomaticUpdatesDisabledDocker": "Suoraa automaattista päivitystä ei tueta käytettäessä Dockerin päivitysmekanismia. Joko Docker-säiliö on päivitettävä {appName}in ulkopuolella tai päivitys on suoritettava skriptillä.", - "AddListExclusionHelpText": "Estä sarjan lisääminen {appName}iin listoilta", + "AddListExclusionSeriesHelpText": "Estä sarjan lisääminen {appName}iin listoilta", "AppUpdated": "{appName} on päivitetty", "AuthenticationMethodHelpText": "Vaadi käyttäjätunnus ja salasana {appName}in käyttöön.", "ConnectionLostToBackend": "{appName} kadotti yhteyden taustajärjestelmään ja käytettävyyden palauttamiseksi se on ladattava uudelleen.", diff --git a/src/NzbDrone.Core/Localization/Core/fr.json b/src/NzbDrone.Core/Localization/Core/fr.json index 536bcf5b7..8f058e188 100644 --- a/src/NzbDrone.Core/Localization/Core/fr.json +++ b/src/NzbDrone.Core/Localization/Core/fr.json @@ -130,7 +130,7 @@ "AirsTbaOn": "À confirmer sur {networkLabel}", "AirsTimeOn": "{time} sur {networkLabel}", "AirsTomorrowOn": "Demain à {time} sur {networkLabel}", - "AnimeTypeFormat": "Numéro d'épisode absolu ({format})", + "AnimeEpisodeTypeFormat": "Numéro d'épisode absolu ({format})", "AppUpdatedVersion": "{appName} a été mis à jour à la version `{version}`, afin d'obtenir les derniers changements, vous devrez recharger {appName}. ", "ApplyTagsHelpTextHowToApplyImportLists": "Comment appliquer des étiquettes aux listes d'importation sélectionnées", "AuthenticationMethod": "Méthode d'authentification", @@ -147,10 +147,10 @@ "AlreadyInYourLibrary": "Déjà dans la bibliothèque", "AlternateTitles": "Titres alternatifs", "Anime": "Animé", - "AnimeTypeDescription": "Episodes diffusés en utilisant un numéro d'épisode absolu", + "AnimeEpisodeTypeDescription": "Episodes diffusés en utilisant un numéro d'épisode absolu", "Any": "Tous", "AppUpdated": "{appName} mis à jour", - "AddListExclusionHelpText": "Empêcher les séries d'être ajoutées à Sonarr par des listes", + "AddListExclusionSeriesHelpText": "Empêcher les séries d'être ajoutées à Sonarr par des listes", "AllSeriesAreHiddenByTheAppliedFilter": "Tous les résultats sont masqués par le filtre appliqué", "AnalyseVideoFilesHelpText": "Extraire des fichiers des informations vidéo telles que la résolution, la durée d'exécution et le codec. Pour ce faire, Sonarr doit lire des parties du fichier, ce qui peut entraîner une activité élevée du disque ou du réseau pendant les analyses.", "AnalyticsEnabledHelpText": "Envoyer des informations anonymes sur l'utilisation et les erreurs aux serveurs de Sonarr. Cela inclut des informations sur votre navigateur, les pages de l'interface Web de Sonarr que vous utilisez, les rapports d'erreurs ainsi que le système d'exploitation et la version d'exécution. Nous utiliserons ces informations pour prioriser les fonctionnalités et les corrections de bugs.", @@ -159,12 +159,12 @@ "AutomaticUpdatesDisabledDocker": "Les mises à jour automatiques ne sont pas directement prises en charge lors de l'utilisation du mécanisme de mise à jour de Docker. Vous devrez mettre à jour l'image du conteneur en dehors de {appName} ou utiliser un script", "BackupRetentionHelpText": "Les sauvegardes automatiques plus anciennes que la période de rétention seront nettoyées automatiquement", "QualityProfile": "Profil de qualité", - "RemotePathMappingDownloadPermissionsHealthCheckMessage": "Sonarr peut voir mais ne peut pas accéder à l'épisode téléchargé {path}. Probablement une erreur de permissions.", + "RemotePathMappingDownloadPermissionsEpisodeHealthCheckMessage": "Sonarr peut voir mais ne peut pas accéder à l'épisode téléchargé {path}. Probablement une erreur de permissions.", "RemotePathMappingDockerFolderMissingHealthCheckMessage": "Vous utilisez Docker ; le client de téléchargement $1{downloadClientName} place les téléchargements dans {path}, mais ce répertoire ne semble pas exister dans le conteneur. Vérifiez vos mappages de chemins d'accès distants et les paramètres de volume du conteneur.", "BlocklistReleases": "Publications de la liste de blocage", "BindAddress": "Adresse de liaison", "BackupsLoadError": "Impossible de charger les sauvegardes", - "BlocklistReleaseHelpText": "Lance une nouvelle recherche pour cet épisode et empêche que cette version soit à nouveau récupérée", + "BlocklistReleaseSearchEpisodeAgainHelpText": "Lance une nouvelle recherche pour cet épisode et empêche que cette version soit à nouveau récupérée", "BuiltIn": "Intégré", "BrowserReloadRequired": "Rechargement du navigateur requis", "BypassDelayIfAboveCustomFormatScore": "Ignorer si le score est supérieur au format personnalisé", @@ -190,14 +190,14 @@ "RemotePathMappingFileRemovedHealthCheckMessage": "Le fichier {path} a été supprimé en cours de traitement.", "RemotePathMappingFilesGenericPermissionsHealthCheckMessage": "Le client de téléchargement {downloadClientName} a signalé des fichiers dans {path} mais {appName} ne peut pas voir ce répertoire. Il se peut que vous deviez ajuster les permissions du dossier.", "CalendarFeed": "Flux de calendrier {appName}", - "CalendarLegendDownloadedTooltip": "L'épisode a été téléchargé et classé", - "CalendarLegendDownloadingTooltip": "L'épisode est en cours de téléchargement", - "CalendarLegendFinaleTooltip": "Fin de série ou de saison", - "CalendarLegendMissingTooltip": "L'épisode a été diffusé et est absent du disque", - "CalendarLegendOnAirTooltip": "Épisode en cours de diffusion", - "CalendarLegendPremiereTooltip": "Première de la série ou de la saison", - "CalendarLegendUnairedTooltip": "L'épisode n'a pas encore été diffusé", - "CalendarLegendUnmonitoredTooltip": "L'épisode n'est pas surveillé", + "CalendarLegendEpisodeDownloadedTooltip": "L'épisode a été téléchargé et classé", + "CalendarLegendEpisodeDownloadingTooltip": "L'épisode est en cours de téléchargement", + "CalendarLegendSeriesFinaleTooltip": "Fin de série ou de saison", + "CalendarLegendEpisodeMissingTooltip": "L'épisode a été diffusé et est absent du disque", + "CalendarLegendEpisodeOnAirTooltip": "Épisode en cours de diffusion", + "CalendarLegendSeriesPremiereTooltip": "Première de la série ou de la saison", + "CalendarLegendEpisodeUnairedTooltip": "L'épisode n'a pas encore été diffusé", + "CalendarLegendEpisodeUnmonitoredTooltip": "L'épisode n'est pas surveillé", "CancelProcessing": "Annuler le traitement", "ChooseImportMode": "Sélectionnez le mode d'importation", "ClickToChangeLanguage": "Cliquez pour changer de langue", @@ -256,7 +256,7 @@ "ExtraFileExtensionsHelpTextsExamples": "Exemples : '.sub, .nfo' ou 'sub,nfo'", "None": "Aucun", "NoTagsHaveBeenAddedYet": "Aucune identification n'a été ajoutée pour l'instant", - "QualityLimitsHelpText": "Les limites sont automatiquement ajustées en fonction de la durée de la série et du nombre d'épisodes dans le fichier.", + "QualityLimitsSeriesRuntimeHelpText": "Les limites sont automatiquement ajustées en fonction de la durée de la série et du nombre d'épisodes dans le fichier.", "QualityProfiles": "Profils de qualité", "Range": "Gamme", "Required": "Obligatoire", @@ -321,7 +321,7 @@ "FilterIs": "est", "FreeSpace": "Espace libre", "Host": "Hôte", - "ICalIncludeUnmonitoredHelpText": "Inclure les épisodes non surveillés dans le flux iCal", + "ICalIncludeUnmonitoredEpisodesHelpText": "Inclure les épisodes non surveillés dans le flux iCal", "RenameEpisodesHelpText": "{appName} utilisera le nom de fichier existant si le changement de nom est désactivé", "RestartRequiredToApplyChanges": "{appName} nécessite un redémarrage pour appliquer les modifications. Voulez-vous redémarrer maintenant ?", "OrganizeRenamingDisabled": "Le renommage est désactivé, rien à renommer", @@ -469,7 +469,7 @@ "KeyboardShortcutsCloseModal": "Fermer cette fenêtre modale", "ICalShowAsAllDayEventsHelpText": "Les événements apparaîtront comme des événements d'une journée entière dans votre calendrier", "Reload": "Recharger", - "ICalTagsHelpText": "Le flux ne contiendra que les séries avec au moins une étiquette correspondante", + "ICalTagsSeriesHelpText": "Le flux ne contiendra que les séries avec au moins une étiquette correspondante", "MediaManagementSettingsLoadError": "Impossible de charger les paramètres de gestion des médias", "EpisodeNaming": "Nommage des épisodes", "ConnectionLostReconnect": "{appName} essaiera de se connecter automatiquement, ou vous pouvez cliquer sur « Recharger » en bas.", @@ -541,7 +541,7 @@ "InteractiveImportNoQuality": "La qualité doit être choisie pour chaque fichier sélectionné", "InteractiveSearchModalHeader": "Recherche interactive", "InteractiveSearchModalHeaderSeason": "Recherche interactive - {season}", - "InteractiveSearchResultsFailedErrorMessage": "La recherche a échoué car il s'agit d'un {message}. Essayez d'actualiser les informations sur la série et vérifiez que les informations nécessaires sont présentes avant de lancer une nouvelle recherche.", + "InteractiveSearchResultsSeriesFailedErrorMessage": "La recherche a échoué car il s'agit d'un {message}. Essayez d'actualiser les informations sur la série et vérifiez que les informations nécessaires sont présentes avant de lancer une nouvelle recherche.", "InteractiveSearchSeason": "Recherche interactive de tous les épisodes de cette saison", "Interval": "Intervalle", "InvalidFormat": "Format invalide", @@ -581,9 +581,9 @@ "MediaInfoFootNote": "MediaInfo Full/AudioLanguages/SubtitleLanguages supporte un suffixe `:EN+DE` vous permettant de filtrer les langues incluses dans le nom de fichier. Utilisez `-DE` pour exclure des langues spécifiques. L'ajout de `+` (par exemple `:EN+`) affichera `[EN]`/`[EN+--]`/`[--]` en fonction des langues exclues. Par exemple `{MediaInfo Full:EN+DE}`.", "MetadataProvidedBy": "Les métadonnées sont fournies par {provider}", "MetadataSettings": "Paramètres des métadonnées", - "MetadataSettingsSummary": "Créez des fichiers de métadonnées lorsque les épisodes sont importés ou que les séries sont actualisées", + "MetadataSettingsSeriesSummary": "Créez des fichiers de métadonnées lorsque les épisodes sont importés ou que les séries sont actualisées", "MetadataSourceSettings": "Paramètres de source de métadonnées", - "MetadataSourceSettingsSummary": "Informations sur l'endroit où {appName} obtient des informations sur les séries et les épisodes", + "MetadataSourceSettingsSeriesSummary": "Informations sur l'endroit où {appName} obtient des informations sur les séries et les épisodes", "MidseasonFinale": "Finale de la mi-saison", "MinimumCustomFormatScore": "Score minimum de format personnalisé", "MinimumCustomFormatScoreHelpText": "Score de format personnalisé minimum autorisé à télécharger", @@ -591,12 +591,12 @@ "Mode": "Mode", "MonitorAllEpisodes": "Tous les épisodes", "MonitorFutureEpisodesDescription": "Surveiller les épisodes qui n'ont pas encore été diffusés", - "MonitorNoneDescription": "Aucun épisode ne sera surveillé", + "MonitorNoEpisodesDescription": "Aucun épisode ne sera surveillé", "MonitorPilotEpisode": "Épisode pilote", "MonitorSelected": "Surveiller les séries sélectionnées", "MonitorSeries": "Surveiller les séries", - "MonitorSpecials": "Surveiller les épisodes spéciaux", - "MountHealthCheckMessage": "Le montage contenant un chemin de série est monté en lecture seule : ", + "MonitorSpecialEpisodes": "Surveiller les épisodes spéciaux", + "MountSeriesHealthCheckMessage": "Le montage contenant un chemin de série est monté en lecture seule : ", "MultiLanguages": "Multi langues", "MultiSeason": "Multi saison", "MustContain": "Doit contenir", @@ -662,7 +662,7 @@ "RemotePathMappingGenericPermissionsHealthCheckMessage": "Le client de téléchargement {0} place les téléchargements dans {1} mais {appName} ne peut pas voir ce répertoire. Vous devrez peut-être ajuster les autorisations du dossier.", "RemotePathMappingHostHelpText": "Le même hôte que vous avez spécifié pour le client de téléchargement distant", "ImportListRootFolderMissingRootHealthCheckMessage": "Dossier racine manquant pour la ou les listes d'importation : {0}", - "RemotePathMappingImportFailedHealthCheckMessage": "{appName} n'a pas réussi à importer un ou plusieurs épisodes. Vérifiez vos journaux pour plus de détails.", + "RemotePathMappingImportEpisodeFailedHealthCheckMessage": "{appName} n'a pas réussi à importer un ou plusieurs épisodes. Vérifiez vos journaux pour plus de détails.", "IndexerJackettAllHealthCheckMessage": "Indexeurs utilisant le point de terminaison Jackett « all » non pris en charge : {0}", "IndexerRssNoIndexersEnabledHealthCheckMessage": "Aucun indexeur disponible avec la synchronisation RSS activée, {appName} ne récupérera pas automatiquement les nouvelles versions", "ProxyFailedToTestHealthCheckMessage": "Échec du test du proxy : {0}", @@ -689,10 +689,10 @@ "SaveChanges": "Sauvegarder les modifications", "SceneNumbering": "Numérotation des scènes", "SearchFailedError": "La recherche a échoué, veuillez réessayer plus tard.", - "SearchForAllMissing": "Rechercher tous les épisodes manquants", - "SearchForAllMissingConfirmationCount": "Êtes-vous sûr de vouloir rechercher tous les {totalRecords} épisodes manquants ?", - "SearchForCutoffUnmet": "Rechercher tous les épisodes Cutoff Unmet", - "SearchForCutoffUnmetConfirmationCount": "Êtes-vous sûr de vouloir rechercher tous les épisodes {totalRecords} Cutoff Unmet ?", + "SearchForAllMissingEpisodes": "Rechercher tous les épisodes manquants", + "SearchForAllMissingEpisodesConfirmationCount": "Êtes-vous sûr de vouloir rechercher tous les {totalRecords} épisodes manquants ?", + "SearchForCutoffUnmetEpisodes": "Rechercher tous les épisodes Cutoff Unmet", + "SearchForCutoffUnmetEpisodesConfirmationCount": "Êtes-vous sûr de vouloir rechercher tous les épisodes {totalRecords} Cutoff Unmet ?", "SearchForMonitoredEpisodes": "Rechercher des épisodes surveillés", "SearchForMonitoredEpisodesSeason": "Rechercher des épisodes surveillés dans cette saison", "SearchIsNotSupportedWithThisIndexer": "La recherche n'est pas prise en charge avec cet indexeur", @@ -742,7 +742,7 @@ "ShowSeasonCount": "Afficher le nombre de saisons", "ShowSizeOnDisk": "Afficher la taille sur le disque", "ShowTitle": "Montrer le titre", - "ShowTitleHelpText": "Afficher le titre de la série sous l'affiche", + "ShowSeriesTitleHelpText": "Afficher le titre de la série sous l'affiche", "ShowUnknownSeriesItems": "Afficher les éléments de série inconnus", "ShowUnknownSeriesItemsHelpText": "Afficher les éléments sans série dans la file d'attente. Cela peut inclure des séries, des films ou tout autre élément supprimé dans la catégorie de {appName}", "ShownClickToHide": "Affiché, cliquez pour masquer", @@ -808,7 +808,7 @@ "UpdaterLogFiles": "Journaux du programme de mise à jour", "UpgradeUntil": "Mise à niveau jusqu'à", "UpgradeUntilCustomFormatScore": "Mise à niveau jusqu'au score de format personnalisé", - "UpgradeUntilCustomFormatScoreHelpText": "Une fois ce score de format personnalisé atteint, {appName} ne récupérera plus les sorties d'épisodes", + "UpgradeUntilCustomFormatScoreEpisodeHelpText": "Une fois ce score de format personnalisé atteint, {appName} ne récupérera plus les sorties d'épisodes", "UrlBase": "URL de base", "UseHardlinksInsteadOfCopy": "Utiliser les liens durs au lieu de copier", "UseSeasonFolder": "Utiliser le dossier de la saison", @@ -831,7 +831,7 @@ "Genres": "Genres", "GrabId": "Saisir ID", "GrabSelected": "Saisir la sélection", - "GrabbedHistoryTooltip": "Épisode récupéré de {indexer} et envoyé à {downloadClient}", + "EpisodeGrabbedTooltip": "Épisode récupéré de {indexer} et envoyé à {downloadClient}", "HourShorthand": "h", "ImportCountSeries": "Importer {selectedCount} Séries", "ImportErrors": "Erreurs d'importation", @@ -841,9 +841,9 @@ "IndexerSearchNoAvailableIndexersHealthCheckMessage": "Tous les indexeurs compatibles avec la recherche sont temporairement indisponibles en raison d'erreurs récentes de l'indexeur", "LastUsed": "Dernière utilisation", "LiberaWebchat": "Libera Webchat", - "LibraryImportHeader": "Importez des séries que vous possédez déjà", + "LibraryImportSeriesHeader": "Importez des séries que vous possédez déjà", "LibraryImportTips": "Quelques conseils pour garantir le bon déroulement de l’importation :", - "LibraryImportTipsQualityInFilename": "Assurez-vous que vos fichiers incluent la qualité dans leurs noms de fichiers. par exemple. `épisode.s02e15.bluray.mkv`", + "LibraryImportTipsQualityInEpisodeFilename": "Assurez-vous que vos fichiers incluent la qualité dans leurs noms de fichiers. par exemple. `épisode.s02e15.bluray.mkv`", "ManageLists": "Gérer les listes", "MarkAsFailed": "Marquer comme échec", "MegabytesPerMinute": "Mégaoctets par minute", @@ -861,8 +861,8 @@ "MonitorFutureEpisodes": "Épisodes futurs", "MonitorMissingEpisodes": "Épisodes manquants", "MonitorMissingEpisodesDescription": "Surveiller les épisodes qui n'ont pas de fichiers ou qui n'ont pas encore été diffusés", - "MonitorNone": "Aucun", - "MonitorSpecialsDescription": "Surveillez tous les épisodes spéciaux sans modifier le statut surveillé des autres épisodes", + "MonitorNoEpisodes": "Aucun", + "MonitorSpecialEpisodesDescription": "Surveillez tous les épisodes spéciaux sans modifier le statut surveillé des autres épisodes", "MonitoringOptions": "Options de surveillance", "NextAiring": "Prochaine diffusion", "OnlyUsenet": "Uniquement Usenet", @@ -880,16 +880,16 @@ "Reason": "Raison", "RecyclingBinCleanupHelpText": "Réglez sur 0 pour désactiver le nettoyage automatique", "RecyclingBinHelpText": "Les fichiers des épisodes seront placés ici une fois supprimés au lieu d'être définitivement supprimés", - "ReleaseProfileTagHelpText": "Les profils de version s'appliqueront aux séries avec au moins une balise correspondante. Laisser vide pour appliquer à toutes les séries", + "ReleaseProfileTagSeriesHelpText": "Les profils de version s'appliqueront aux séries avec au moins une balise correspondante. Laisser vide pour appliquer à toutes les séries", "RemotePathMappingLocalFolderMissingHealthCheckMessage": "Le client de téléchargement à distance {0} place les téléchargements dans {1} mais ce répertoire ne semble pas exister. Mappage de chemin distant probablement manquant ou incorrect.", "RemoveCompletedDownloads": "Supprimer les téléchargements terminés", "RemoveQueueItemConfirmation": "Êtes-vous sûr de vouloir supprimer « {sourceTitle} » de la file d'attente ?", "RemoveSelectedBlocklistMessageText": "Êtes-vous sûr de vouloir supprimer les éléments sélectionnés de la liste de blocage ?", - "RescanAfterRefreshHelpText": "Analysez à nouveau le dossier de la série après avoir actualisé la série", + "RescanAfterRefreshSeriesHelpText": "Analysez à nouveau le dossier de la série après avoir actualisé la série", "RetryingDownloadOn": "Nouvelle tentative de téléchargement le {date} à {time}", "RootFoldersLoadError": "Impossible de charger les dossiers racine", "SeriesFolderImportedTooltip": "Épisode importé du dossier de la série", - "StandardTypeDescription": "Épisodes publiés avec le modèle SxxEyy", + "StandardEpisodeTypeDescription": "Épisodes publiés avec le modèle SxxEyy", "Rejections": "Rejets", "RemoveFromQueue": "Supprimer de la file d'attente", "RemoveQueueItem": "Supprimer – {sourceTitle}", @@ -901,7 +901,7 @@ "SizeOnDisk": "Taille sur le disque", "SkipRedownload": "Ignorer le nouveau téléchargement", "Standard": "Standard", - "StandardTypeFormat": "Numéros de saison et d'épisode ({format})", + "StandardEpisodeTypeFormat": "Numéros de saison et d'épisode ({format})", "StartProcessing": "Démarrer le traitement", "TableColumnsHelpText": "Choisissez quelles colonnes sont visibles et dans quel ordre elles apparaissent", "TablePageSizeMaximum": "La taille de la page ne doit pas dépasser {maximumValue}", @@ -911,9 +911,9 @@ "UiSettings": "Paramètres de l'interface utilisateur", "Umask777Description": "Tout le monde écrit - {octal}", "UnmappedFilesOnly": "Fichiers non mappés uniquement", - "UnmonitorSpecialsDescription": "Annulez la surveillance de tous les épisodes spéciaux sans modifier le statut surveillé des autres épisodes", + "UnmonitorSpecialsEpisodesDescription": "Annulez la surveillance de tous les épisodes spéciaux sans modifier le statut surveillé des autres épisodes", "UpdateUiNotWritableHealthCheckMessage": "Impossible d'installer la mise à jour, car le dossier de l'interface utilisateur '{0}' n'est pas accessible en écriture par l'utilisateur '{1}'.", - "UpgradeUntilHelpText": "Une fois cette qualité atteinte, {appName} ne téléchargera plus d'épisodes", + "UpgradeUntilEpisodeHelpText": "Une fois cette qualité atteinte, {appName} ne téléchargera plus d'épisodes", "UpgradeUntilThisQualityIsMetOrExceeded": "Mise à niveau jusqu'à ce que cette qualité soit atteinte ou dépassée", "UseProxy": "Utiliser le proxy", "WaitingToImport": "En attente d'import", @@ -1074,7 +1074,7 @@ "OriginalLanguage": "Langue originale", "Port": "Port", "PreferTorrent": "Préféré Torrent", - "QualityProfileInUse": "Impossible de supprimer un profil de qualité associé à une série, une liste ou une collection", + "QualityProfileInUseSeriesListCollection": "Impossible de supprimer un profil de qualité associé à une série, une liste ou une collection", "ReleaseTitle": "Titre de la version", "RemovingTag": "Supprimer la balise", "Result": "Résultat", @@ -1086,7 +1086,7 @@ "TableOptions": "Options des tableaux", "TagsLoadError": "Impossible de charger les balises", "ThemeHelpText": "Modifiez le thème de l'interface utilisateur de l'application, le thème « Auto » utilisera le thème de votre système d'exploitation pour définir le mode clair ou sombre. Inspiré par Theme.Park", - "UnmonitorSpecials": "Ne plus surveiller les épisodes spéciaux", + "UnmonitorSpecialEpisodes": "Ne plus surveiller les épisodes spéciaux", "Queued": "En file d'attente", "IconForCutoffUnmet": "Icône pour la date limite non respectée", "IconForCutoffUnmetHelpText": "Afficher l'icône pour les fichiers lorsque la limite n'a pas été respectée", @@ -1096,10 +1096,10 @@ "FilterEpisodesPlaceholder": "Filtrer les épisodes par titre ou numéro", "Grab": "Saisir", "GrabRelease": "Saisir Release", - "GrabReleaseMessageText": "{appName} n'a pas pu déterminer à quelle série et à quel épisode cette version était destinée. Il est possible que {appName} ne parvienne pas à importer automatiquement cette version. Voulez-vous récupérer « {title} » ?", + "GrabReleaseUnknownSeriesOrEpisodeMessageText": "{appName} n'a pas pu déterminer à quelle série et à quel épisode cette version était destinée. Il est possible que {appName} ne parvienne pas à importer automatiquement cette version. Voulez-vous récupérer « {title} » ?", "Group": "Groupe", "HideEpisodes": "Masquer les épisodes", - "ImportExtraFilesHelpText": "Importez les fichiers supplémentaires correspondants (sous-titres, informations, etc.) après avoir importé un fichier d'épisode", + "ImportExtraFilesEpisodeHelpText": "Importez les fichiers supplémentaires correspondants (sous-titres, informations, etc.) après avoir importé un fichier d'épisode", "ImportList": "Liste d'importation", "ImportListExclusions": "Exclusions de la liste d'importation", "ImportLists": "Importer des listes", @@ -1108,7 +1108,7 @@ "ImportUsingScript": "Importer à l'aide d'un script", "IncludeHealthWarnings": "Inclure des avertissements de santé", "Indexer": "Indexeur", - "LibraryImportTipsUseRootFolder": "Pointez {appName} vers le dossier contenant toutes vos émissions de télévision, pas une en particulier. par exemple. \"`{goodFolderExample}`\" et non \"`{badFolderExample}`\". De plus, chaque série doit se trouver dans son propre dossier dans le dossier racine/bibliothèque.", + "LibraryImportTipsSeriesUseRootFolder": "Pointez {appName} vers le dossier contenant toutes vos émissions de télévision, pas une en particulier. par exemple. \"`{goodFolderExample}`\" et non \"`{badFolderExample}`\". De plus, chaque série doit se trouver dans son propre dossier dans le dossier racine/bibliothèque.", "Links": "Liens", "ListOptionsLoadError": "Impossible de charger les options de la liste", "ListRootFolderHelpText": "Les éléments de la liste du dossier racine seront ajoutés à", @@ -1116,7 +1116,7 @@ "Missing": "Manquant", "MissingEpisodes": "Épisodes manquants", "MissingLoadError": "Erreur lors du chargement des éléments manquants", - "MonitoredHelpText": "Téléchargez les épisodes surveillés de cette série", + "MonitoredEpisodesHelpText": "Téléchargez les épisodes surveillés de cette série", "Monitoring": "Surveillance", "Month": "Mois", "More": "Plus", @@ -1134,7 +1134,7 @@ "NoUpdatesAreAvailable": "Aucune mise à jour n'est disponible", "NotSeasonPack": "Pas de pack saisonnier", "NotificationTriggersHelpText": "Sélectionnez les événements qui doivent déclencher cette notification", - "NotificationsTagsHelpText": "N'envoyer des notifications que pour les séries avec au moins une balise correspondante", + "NotificationsTagsSeriesHelpText": "N'envoyer des notifications que pour les séries avec au moins une balise correspondante", "OnApplicationUpdate": "Sur la mise à jour de l'application", "OnEpisodeFileDelete": "Lors de la suppression du fichier de l'épisode", "OnHealthIssue": "Sur la question de la santé", @@ -1195,7 +1195,7 @@ "ImportExtraFiles": "Importer des fichiers supplémentaires", "Importing": "Importation", "IndexerSettings": "Paramètres de l'indexeur", - "IndexerTagHelpText": "Utilisez cet indexeur uniquement pour les séries avec au moins une balise correspondante. Laissez vide pour utiliser toutes les séries.", + "IndexerTagSeriesHelpText": "Utilisez cet indexeur uniquement pour les séries avec au moins une balise correspondante. Laissez vide pour utiliser toutes les séries.", "InfoUrl": "URL d'informations", "InstanceName": "Nom de l'instance", "InteractiveImportLoadError": "Impossible de charger les éléments d'importation manuelle", @@ -1243,7 +1243,7 @@ "CountImportListsSelected": "{count} liste(s) d'importation sélectionnée(s)", "DeleteSeriesFolderConfirmation": "Le dossier de la série `{path}` et tout son contenu seront supprimés.", "DeleteSelectedImportLists": "Supprimer la ou les listes d'importation", - "DeletedReasonMissingFromDisk": "{appName} n'a pas pu trouver le fichier sur le disque. Le fichier a donc été dissocié de l'épisode dans la base de données", + "DeletedReasonEpisodeMissingFromDisk": "{appName} n'a pas pu trouver le fichier sur le disque. Le fichier a donc été dissocié de l'épisode dans la base de données", "Docker": "Docker", "DockerUpdater": "Mettez à jour le conteneur Docker pour recevoir la mise à jour", "DownloadClientCheckUnableToCommunicateWithHealthCheckMessage": "Impossible de communiquer avec {downloadClientName}. {errorMessage}", @@ -1251,19 +1251,19 @@ "EpisodeAirDate": "Date de diffusion de l'épisode", "ErrorLoadingPage": "Une erreur s'est produite lors du chargement de cette page", "ExternalUpdater": "{appName} est configuré pour utiliser un mécanisme de mise à jour externe", - "DailyTypeDescription": "Épisodes diffusés quotidiennement ou moins fréquemment qui utilisent année-mois-jour (2023-08-04)", + "DailyEpisodeTypeDescription": "Épisodes diffusés quotidiennement ou moins fréquemment qui utilisent année-mois-jour (2023-08-04)", "Debug": "Déboguer", - "DelayProfileTagsHelpText": "S'applique aux séries avec au moins une balise correspondante", + "DelayProfileSeriesTagsHelpText": "S'applique aux séries avec au moins une balise correspondante", "DelayingDownloadUntil": "Retarder le téléchargement jusqu'au {date} à {time}", "DeletedReasonManual": "Le fichier a été supprimé via l'interface utilisateur", "DeleteRemotePathMapping": "Supprimer le mappage de chemin distant", "DestinationPath": "Chemin de destination", "DestinationRelativePath": "Chemin relatif de destination", "DownloadClientRootFolderHealthCheckMessage": "Le client de téléchargement {downloadClientName} place les téléchargements dans le dossier racine {rootFolderPath}. Vous ne devez pas télécharger vers un dossier racine.", - "DownloadFailedTooltip": "Échec du téléchargement de l'épisode", + "DownloadFailedEpisodeTooltip": "Échec du téléchargement de l'épisode", "DownloadIgnored": "Téléchargement ignoré", "DownloadWarning": "Avertissement de téléchargement : {warningMessage}", - "DownloadIgnoredTooltip": "Téléchargement de l'épisode ignoré", + "DownloadIgnoredEpisodeTooltip": "Téléchargement de l'épisode ignoré", "Downloaded": "Téléchargé", "EditCustomFormat": "Modifier le format personnalisé", "Downloading": "Téléchargement", @@ -1286,10 +1286,10 @@ "CopyToClipboard": "Copier dans le presse-papier", "CreateEmptySeriesFolders": "Créer des dossiers de séries vides", "Custom": "Customisé", - "CopyUsingHardlinksHelpText": "Les liens physiques permettent à {appName} d'importer des torrents dans le dossier de la série sans prendre d'espace disque supplémentaire ni copier l'intégralité du contenu du fichier. Les liens physiques ne fonctionneront que si la source et la destination sont sur le même volume", + "CopyUsingHardlinksSeriesHelpText": "Les liens physiques permettent à {appName} d'importer des torrents dans le dossier de la série sans prendre d'espace disque supplémentaire ni copier l'intégralité du contenu du fichier. Les liens physiques ne fonctionneront que si la source et la destination sont sur le même volume", "CustomFormatsSettingsSummary": "Paramètres de formats personnalisés", "CustomFormatsSettings": "Paramètres de formats personnalisés", - "DefaultDelayProfile": "Il s'agit du profil par défaut. Cela s'applique à toutes les séries qui n'ont pas de profil explicite.", + "DefaultDelayProfileSeries": "Il s'agit du profil par défaut. Cela s'applique à toutes les séries qui n'ont pas de profil explicite.", "DeleteDownloadClient": "Supprimer le client de téléchargement", "DeleteEmptyFolders": "Supprimer les dossiers vides", "DeleteImportList": "Supprimer la liste d'importation", @@ -1301,11 +1301,11 @@ "DoNotPrefer": "Ne préfère pas", "DoNotUpgradeAutomatically": "Ne pas mettre à niveau automatiquement", "DownloadClient": "Client de téléchargement", - "DownloadClientTagHelpText": "Utilisez uniquement ce client de téléchargement pour les séries avec au moins une balise correspondante. Laissez vide pour utiliser toutes les séries.", + "DownloadClientSeriesTagHelpText": "Utilisez uniquement ce client de téléchargement pour les séries avec au moins une balise correspondante. Laissez vide pour utiliser toutes les séries.", "EditDelayProfile": "Modifier le profil de retard", "EditQualityProfile": "Modifier le profil de qualité", "EditReleaseProfile": "Modifier le profil de version", - "EnableAutomaticAddHelpText": "Ajoutez des séries de cette liste à {appName} lorsque les synchronisations sont effectuées via l'interface utilisateur ou par {appName}", + "EnableAutomaticAddSeriesHelpText": "Ajoutez des séries de cette liste à {appName} lorsque les synchronisations sont effectuées via l'interface utilisateur ou par {appName}", "EnableCompletedDownloadHandlingHelpText": "Importer automatiquement les téléchargements terminés à partir du client de téléchargement", "EnableColorImpairedModeHelpText": "Style modifié pour permettre aux utilisateurs ayant des difficultés de couleur de mieux distinguer les informations codées par couleur", "ExtraFileExtensionsHelpText": "Liste de fichiers supplémentaires séparés par des virgules à importer (.nfo sera importé en tant que .nfo-orig)", @@ -1339,7 +1339,7 @@ "CustomFormatsLoadError": "Impossible de charger les formats personnalisés", "Cutoff": "Couper", "DailyEpisodeFormat": "Format d'épisode quotidien", - "DailyTypeFormat": "Date ({format})", + "DailyEpisodeTypeFormat": "Date ({format})", "CreateEmptySeriesFoldersHelpText": "Créer des dossiers de séries manquants lors de l'analyse du disque", "CreateGroup": "Créer un groupe", "Database": "Base de données", @@ -1446,7 +1446,7 @@ "DefaultNotFoundMessage": "Vous devez être perdu, rien à voir ici.", "DeleteAutoTag": "Supprimer la balise automatique", "DeleteAutoTagHelpText": "Voulez-vous vraiment supprimer la balise automatique « {name} » ?", - "DeleteEmptyFoldersHelpText": "Supprimez les dossiers de séries et de saisons vides lors de l'analyse du disque et lorsque les fichiers d'épisode sont supprimés", + "DeleteEmptySeriesFoldersHelpText": "Supprimez les dossiers de séries et de saisons vides lors de l'analyse du disque et lorsque les fichiers d'épisode sont supprimés", "DeleteEpisodesFiles": "Supprimer {episodeFileCount} fichiers d'épisode", "DeleteEpisodesFilesHelpText": "Supprimer les fichiers d'épisode et le dossier de série", "DeleteQualityProfileMessageText": "Êtes-vous sûr de vouloir supprimer le profil de qualité « {name} » ?", diff --git a/src/NzbDrone.Core/Localization/Core/hu.json b/src/NzbDrone.Core/Localization/Core/hu.json index fe523fcf3..55db72695 100644 --- a/src/NzbDrone.Core/Localization/Core/hu.json +++ b/src/NzbDrone.Core/Localization/Core/hu.json @@ -1,5 +1,5 @@ { - "BlocklistReleaseHelpText": "Megakadályozza, hogy a {appName} automatikusan újra letöltse ezt a kiadást", + "BlocklistReleaseSearchEpisodeAgainHelpText": "Megakadályozza, hogy a {appName} automatikusan újra letöltse ezt a kiadást", "CloneCondition": "Feltétel klónozása", "CloneCustomFormat": "Egyéni formátum klónozása", "Close": "Bezárás", @@ -61,7 +61,7 @@ "Path": "Útvonal", "NextAiring": "Következő rész", "Monitored": "Felügyelt", - "MountHealthCheckMessage": "A sorozat elérési útvonalát tartalmazó kötet csak olvasható módban van csatolva: ", + "MountSeriesHealthCheckMessage": "A sorozat elérési útvonalát tartalmazó kötet csak olvasható módban van csatolva: ", "Network": "Hálózat", "NoSeasons": "Nincsenek évadok", "ProxyBadRequestHealthCheckMessage": "Sikertelen proxy teszt. Állapotkód: {statusCode}", @@ -74,10 +74,10 @@ "RemotePathMappingDockerFolderMissingHealthCheckMessage": "Docker-t használ; a(z) $1{downloadClientName} letöltési kliens a letöltéseket a(z) {path} mappába helyezi, de úgy tűnik, hogy ez a könyvtár nem létezik a konténeren belül. Ellenőrizze a távoli útvonal hozzárendeléseket, és a konténer kötet beállításait.", "RefreshSeries": "Sorozat frissítése", "RemotePathMappingFileRemovedHealthCheckMessage": "A(z) {path} fájlt részben feldolgozás közben eltávolították.", - "RemotePathMappingDownloadPermissionsHealthCheckMessage": "A {appName} látja, de nem tud hozzáférni a letöltött epizódhoz {path}. Valószínűleg jogosultsági hiba.", + "RemotePathMappingDownloadPermissionsEpisodeHealthCheckMessage": "A {appName} látja, de nem tud hozzáférni a letöltött epizódhoz {path}. Valószínűleg jogosultsági hiba.", "RemotePathMappingFilesLocalWrongOSPathHealthCheckMessage": "A(z) {downloadClientName} helyi letöltési kliens a fájlokat a(z) {path} mappában jelentette, de ez nem érvényes {osName} elérési útvonal. Ellenőrizze a letöltési kliens beállításait.", "RemotePathMappingGenericPermissionsHealthCheckMessage": "A(z) {downloadClientName} letöltési kliens a letöltéseket a(z) {path} mappába helyezi, de a {appName} nem látja ezt a könyvtárat. Lehetséges, hogy be kell állítania a mappa jogosultságait.", - "RemotePathMappingImportFailedHealthCheckMessage": "A {appName}-nak nem sikerült importálni az epizód(ok)at. Ellenőrizze a naplókat a részletekért.", + "RemotePathMappingImportEpisodeFailedHealthCheckMessage": "A {appName}-nak nem sikerült importálni az epizód(ok)at. Ellenőrizze a naplókat a részletekért.", "RemotePathMappingRemoteDownloadClientHealthCheckMessage": "A(z) {downloadClientName} távoli letöltési kliens a fájlokat a(z) {path} mappában jelentette, de úgy tűnik, hogy ez a könyvtár nem létezik. Valószínűleg hiányzik a távoli útvonal hozzárendelés.", "RemotePathMappingLocalFolderMissingHealthCheckMessage": "A(z) {downloadClientName} távoli letöltési kliens a letöltéseket a(z) {path} mappába helyezi, de úgy tűnik, hogy ez a könyvtár nem létezik. Valószínűleg hiányzik vagy helytelen a távoli útvonal hozzárendelés.", "RemotePathMappingLocalWrongOSPathHealthCheckMessage": "A(z) {downloadClientName} helyi letöltési kliens a letöltéseket a(z) {path} mappába helyezi, de ez nem érvényes {osName} elérési útvonal. Ellenőrizze a letöltési kliens beállításait.", @@ -122,9 +122,9 @@ "Absolute": "Abszolút", "ApplicationURL": "Alkalmazás URL", "AutoAdd": "Automatikus hozzáadás", - "CalendarLegendDownloadingTooltip": "Epizód letöltés alatt", + "CalendarLegendEpisodeDownloadingTooltip": "Epizód letöltés alatt", "BuiltIn": "Beépített", - "CalendarLegendFinaleTooltip": "Sorozat vagy évad finálé", + "CalendarLegendSeriesFinaleTooltip": "Sorozat vagy évad finálé", "CancelProcessing": "Folyamat leállítása", "CalendarOptions": "Naptár beállítások", "About": "Névjegy", diff --git a/src/NzbDrone.Core/Localization/Core/id.json b/src/NzbDrone.Core/Localization/Core/id.json index 810ff7cb1..720303511 100644 --- a/src/NzbDrone.Core/Localization/Core/id.json +++ b/src/NzbDrone.Core/Localization/Core/id.json @@ -1,6 +1,6 @@ { "Added": "Ditambahkan", - "BlocklistReleaseHelpText": "Mencegah {appName} memperoleh rilis ini secara otomatis", + "BlocklistReleaseSearchEpisodeAgainHelpText": "Mencegah {appName} memperoleh rilis ini secara otomatis", "Delete": "Hapus", "Close": "Tutup", "EnableAutomaticSearch": "Aktifkan Penelusuran Otomatis", diff --git a/src/NzbDrone.Core/Localization/Core/nl.json b/src/NzbDrone.Core/Localization/Core/nl.json index 7e9f60b05..7e9b7742d 100644 --- a/src/NzbDrone.Core/Localization/Core/nl.json +++ b/src/NzbDrone.Core/Localization/Core/nl.json @@ -21,7 +21,7 @@ "ApplyTagsHelpTextHowToApplyIndexers": "Hoe tags toepassen op de geselecteerde indexeerders", "CountDownloadClientsSelected": "{count} download client(s) geselecteerd", "ApplyTagsHelpTextHowToApplySeries": "Hoe tags toepassen op de geselecteerde series", - "BlocklistReleaseHelpText": "Verbied {appName} om deze release opnieuw automatisch te downloaden", + "BlocklistReleaseSearchEpisodeAgainHelpText": "Verbied {appName} om deze release opnieuw automatisch te downloaden", "Delete": "Verwijder", "ApplyTagsHelpTextRemove": "Verwijderen: Verwijder de ingevoerde tags", "ApplyTagsHelpTextReplace": "Vervangen: Vervang de tags met de ingevoerde tags (vul geen tags in om alle tags te wissen)", diff --git a/src/NzbDrone.Core/Localization/Core/pt.json b/src/NzbDrone.Core/Localization/Core/pt.json index f443e05b7..833f03964 100644 --- a/src/NzbDrone.Core/Localization/Core/pt.json +++ b/src/NzbDrone.Core/Localization/Core/pt.json @@ -30,7 +30,7 @@ "ApplyTagsHelpTextHowToApplyImportLists": "Como aplicar etiquetas às listas de importação selecionadas", "ApplyTagsHelpTextHowToApplyIndexers": "Como aplicar etiquetas aos indexadores selecionados", "AddListExclusion": "Adicionar exclusão de lista", - "AddListExclusionHelpText": "Impedir série de ser adicionada ao {appName} através de listas", + "AddListExclusionSeriesHelpText": "Impedir série de ser adicionada ao {appName} através de listas", "AddNewSeriesSearchForCutoffUnmetEpisodes": "Iniciar busca por episódios de corte não atendidos", "AddSeriesWithTitle": "Adicionar {title}", "AddedDate": "Adicionado: {date}", @@ -44,8 +44,8 @@ "AlreadyInYourLibrary": "Já está na sua biblioteca", "AlternateTitles": "Títulos Alternativos", "Anime": "Anime", - "AnimeTypeDescription": "Episódios lançados usando um número de episódio absoluto", - "AnimeTypeFormat": "Número absoluto do episódio ({format})", + "AnimeEpisodeTypeDescription": "Episódios lançados usando um número de episódio absoluto", + "AnimeEpisodeTypeFormat": "Número absoluto do episódio ({format})", "Any": "Quaisquer", "AppUpdated": "{appName} Atualizado", "AppUpdatedVersion": "{appName} foi atualizado para a versão `{version}`, para obter as alterações mais recentes, você precisará recarregar {appName} ", diff --git a/src/NzbDrone.Core/Localization/Core/pt_BR.json b/src/NzbDrone.Core/Localization/Core/pt_BR.json index 15fe666fe..9466b47ef 100644 --- a/src/NzbDrone.Core/Localization/Core/pt_BR.json +++ b/src/NzbDrone.Core/Localization/Core/pt_BR.json @@ -20,7 +20,7 @@ "IndexerStatusUnavailableHealthCheckMessage": "Indexadores indisponíveis devido a falhas: {indexerNames}", "Language": "Idioma", "Monitored": "Monitorado", - "MountHealthCheckMessage": "A montagem que contém um caminho de série é montada somente para leitura: ", + "MountSeriesHealthCheckMessage": "A montagem que contém um caminho de série é montada somente para leitura: ", "Network": "Rede", "NoSeasons": "Sem temporadas", "OneSeason": "1 Temporada", @@ -31,13 +31,13 @@ "QualityProfile": "Perfil de Qualidade", "RefreshSeries": "Atualizar Séries", "RemotePathMappingDockerFolderMissingHealthCheckMessage": "Você está usando o docker; o cliente de download {downloadClientName} coloca os downloads em {path}, mas este diretório parece não existir dentro do contêiner. Revise seus mapeamentos de caminho remoto e configurações de volume de contêiner.", - "RemotePathMappingDownloadPermissionsHealthCheckMessage": "O {appName} pode ver, mas não acessar o episódio baixado {path}. Provável erro de permissão.", + "RemotePathMappingDownloadPermissionsEpisodeHealthCheckMessage": "O {appName} pode ver, mas não acessar o episódio baixado {path}. Provável erro de permissão.", "RemotePathMappingFileRemovedHealthCheckMessage": "O arquivo {path} foi removido no meio do processamento.", "RemotePathMappingFilesGenericPermissionsHealthCheckMessage": "Baixe os arquivos relatados do cliente {downloadClientName} em {path}, mas o {appName} não pode ver este diretório. Pode ser necessário ajustar as permissões da pasta.", "RemotePathMappingFilesLocalWrongOSPathHealthCheckMessage": "O cliente de download local {downloadClientName} relatou arquivos em {path}, mas este não é um caminho {osName} válido. Revise as configurações do cliente de download.", "RemotePathMappingFolderPermissionsHealthCheckMessage": "{appName} pode ver, mas não acessar o diretório de download {downloadPath}. Provável erro de permissão.", "RemotePathMappingGenericPermissionsHealthCheckMessage": "O cliente de download {downloadClientName} coloca os downloads em {path}, mas o {appName} não pode ver este diretório. Pode ser necessário ajustar as permissões da pasta.", - "RemotePathMappingImportFailedHealthCheckMessage": "{appName} falhou ao importar (um) episódio(s). Verifique seus logs para obter detalhes.", + "RemotePathMappingImportEpisodeFailedHealthCheckMessage": "{appName} falhou ao importar (um) episódio(s). Verifique seus logs para obter detalhes.", "RemotePathMappingLocalWrongOSPathHealthCheckMessage": "O cliente de download local {downloadClientName} coloca os downloads em {path}, mas este não é um caminho {osName} válido. Revise as configurações do cliente de download.", "RemotePathMappingRemoteDownloadClientHealthCheckMessage": "O cliente de download remoto {downloadClientName} relatou arquivos em {path}, mas este diretório parece não existir. Provavelmente faltando mapeamento de caminho remoto.", "RemovedSeriesMultipleRemovedHealthCheckMessage": "A série {series} foi removida do TheTVDB", @@ -86,7 +86,7 @@ "RemotePathMappingWrongOSPathHealthCheckMessage": "O cliente de download remoto {downloadClientName} coloca os downloads em {path}, mas este não é um caminho {osName} válido. Revise seus mapeamentos de caminho remoto e baixe as configurações do cliente.", "UpdateStartupNotWritableHealthCheckMessage": "Não é possível instalar a atualização porque a pasta de inicialização '{startupFolder}' não pode ser gravada pelo usuário '{userName}'.", "UpdateStartupTranslocationHealthCheckMessage": "Não é possível instalar a atualização porque a pasta de inicialização '{startupFolder}' está em uma pasta de translocação de aplicativo.", - "BlocklistReleaseHelpText": "Inicia uma busca por este episódio novamente e impede que esta versão seja capturada novamente", + "BlocklistReleaseSearchEpisodeAgainHelpText": "Inicia uma busca por este episódio novamente e impede que esta versão seja capturada novamente", "BlocklistReleases": "Lançamentos na lista de bloqueio", "CloneCondition": "Clonar Condição", "CloneCustomFormat": "Clonar formato personalizado", @@ -501,18 +501,18 @@ "Dash": "Traço", "Dates": "Datas", "Debug": "Depuração", - "DefaultDelayProfile": "Este é o perfil padrão. Aplica-se a todas as séries que não possuem um perfil explícito.", + "DefaultDelayProfileSeries": "Este é o perfil padrão. Aplica-se a todas as séries que não possuem um perfil explícito.", "DelayMinutes": "{delay} Minutos", "DelayProfile": "Perfil de Atraso", "DefaultCase": "Minúscula ou maiúscula", - "DelayProfileTagsHelpText": "Aplica-se a séries com pelo menos uma tag correspondente", + "DelayProfileSeriesTagsHelpText": "Aplica-se a séries com pelo menos uma tag correspondente", "DelayProfiles": "Perfis de Atraso", "DelayProfilesLoadError": "Não foi possível carregar perfis de atraso", "DeleteDelayProfile": "Excluir Perfil de Atraso", "DeleteDownloadClient": "Excluir Cliente de Download", "DeleteDownloadClientMessageText": "Tem certeza de que deseja excluir o cliente de download '{name}'?", "DeleteEmptyFolders": "Excluir Pastas Vazias", - "DeleteEmptyFoldersHelpText": "Excluir pastas vazias de séries e temporadas durante a verificação de disco e quando os arquivos de episódios são excluídos", + "DeleteEmptySeriesFoldersHelpText": "Excluir pastas vazias de séries e temporadas durante a verificação de disco e quando os arquivos de episódios são excluídos", "DeleteImportList": "Excluir Lista de Importação", "DeleteImportListExclusion": "Excluir Exclusão da Lista de Importação", "DeleteImportListExclusionMessageText": "Tem certeza de que deseja excluir esta exclusão da lista de importação?", @@ -554,7 +554,7 @@ "EditRestriction": "Editar Restrição", "Enable": "Habilitar", "EnableAutomaticAdd": "Habilitar Adição Automática", - "EnableAutomaticAddHelpText": "Adicione séries desta lista ao {appName} quando as sincronizações forem realizadas por meio da interface do usuário ou pelo {appName}", + "EnableAutomaticAddSeriesHelpText": "Adicione séries desta lista ao {appName} quando as sincronizações forem realizadas por meio da interface do usuário ou pelo {appName}", "EnableAutomaticSearchHelpText": "Será usado quando pesquisas automáticas forem realizadas por meio da interface do usuário ou pelo {appName}", "EnableAutomaticSearchHelpTextWarning": "Será usado quando a pesquisa interativa for usada", "EnableCompletedDownloadHandlingHelpText": "Importar automaticamente downloads concluídos do cliente de download", @@ -594,7 +594,7 @@ "Host": "Host", "Hostname": "Hostname", "ImportExtraFiles": "Importar Arquivos Extras", - "ImportExtraFilesHelpText": "Importar arquivos extras correspondentes (legendas, nfo, etc) após importar um arquivo de episódio", + "ImportExtraFilesEpisodeHelpText": "Importar arquivos extras correspondentes (legendas, nfo, etc) após importar um arquivo de episódio", "ImportList": "Importar Lista", "ImportListExclusions": "Importar Lista de Exclusões", "ImportListExclusionsLoadError": "Não foi possível carregar as exclusões da lista de importação", @@ -650,9 +650,9 @@ "MegabytesPerMinute": "Megabytes por minuto", "MetadataLoadError": "Não foi possível carregar os metadados", "MetadataSettings": "Configurações de metadados", - "MetadataSettingsSummary": "Criar arquivos de metadados ao importar episódios ou atualizar a série", + "MetadataSettingsSeriesSummary": "Criar arquivos de metadados ao importar episódios ou atualizar a série", "MetadataSourceSettings": "Configurações da fonte de metadados", - "MetadataSourceSettingsSummary": "Informações sobre onde o {appName} obtém informações sobre séries e episódios", + "MetadataSourceSettingsSeriesSummary": "Informações sobre onde o {appName} obtém informações sobre séries e episódios", "Min": "Mín.", "MinimumAge": "Idade miníma", "MinimumAgeHelpText": "Somente Usenet: idade mínima, em minutos, dos NZBs antes de serem capturados. Use isso para dar aos novos lançamentos tempo para se propagar para seu provedor de Usenet.", @@ -729,7 +729,7 @@ "QualitiesHelpText": "Qualidades mais altas na lista são mais preferidas. As qualidades dentro do mesmo grupo são iguais. Somente qualidades verificadas são desejadas", "QualitiesLoadError": "Não foi possível carregar qualidades", "QualityDefinitions": "Definições de Qualidade", - "QualityLimitsHelpText": "Os limites são ajustados automaticamente para o tempo de execução da série e o número de episódios no arquivo.", + "QualityLimitsSeriesRuntimeHelpText": "Os limites são ajustados automaticamente para o tempo de execução da série e o número de episódios no arquivo.", "QualityProfiles": "Perfis de Qualidade", "QualityProfilesLoadError": "Não é possível carregar perfis de qualidade", "QualitySettings": "Configurações de Qualidade", @@ -745,31 +745,31 @@ "AnalyseVideoFilesHelpText": "Extraia informações de vídeo, como resolução, tempo de execução e informações de codec de arquivos. Isso requer que o {appName} leia partes do arquivo que podem causar alta atividade no disco ou na rede durante as varreduras.", "AuthenticationRequiredHelpText": "Altere para quais solicitações a autenticação é necessária. Não mude a menos que você entenda os riscos.", "AuthenticationRequiredWarning": "Para evitar o acesso remoto sem autenticação, {appName} agora exige que a autenticação esteja habilitada. Opcionalmente, você pode desabilitar a autenticação de endereços locais.", - "CopyUsingHardlinksHelpText": "Os links rígidos permitem que o {appName} importe torrents de propagação para a pasta da série sem ocupar espaço extra em disco ou copiar todo o conteúdo do arquivo. Links rígidos só funcionarão se a origem e o destino estiverem no mesmo volume", + "CopyUsingHardlinksSeriesHelpText": "Os links rígidos permitem que o {appName} importe torrents de propagação para a pasta da série sem ocupar espaço extra em disco ou copiar todo o conteúdo do arquivo. Links rígidos só funcionarão se a origem e o destino estiverem no mesmo volume", "CustomFormatHelpText": "O {appName} pontua cada lançamento usando a soma das pontuações para corresponder aos formatos personalizados. Se um novo lançamento melhorar a pontuação, com a mesma, ou melhor, qualidade, o {appName} o baixará.", "DelayProfileProtocol": "Protocolo: {preferredProtocol}", "DeleteDelayProfileMessageText": "Tem certeza de que deseja excluir este perfil de atraso?", "DeleteImportListMessageText": "Tem certeza de que deseja excluir a lista '{name}'?", "DeleteReleaseProfileMessageText": "Tem certeza de que deseja excluir este perfil de lançamento '{name}'?", - "DownloadClientTagHelpText": "Use este cliente de download apenas para séries com pelo menos uma tag correspondente. Deixe em branco para usar com todas as séries.", + "DownloadClientSeriesTagHelpText": "Use este cliente de download apenas para séries com pelo menos uma tag correspondente. Deixe em branco para usar com todas as séries.", "EpisodeTitleRequiredHelpText": "Impeça a importação por até 48 horas se o título do episódio estiver no formato de nomenclatura e o título do episódio for TBA", "External": "Externo", "ExtraFileExtensionsHelpText": "Lista separada por vírgulas de arquivos extras para importar (.nfo será importado como .nfo-orig)", "HistoryLoadError": "Não foi possível carregar o histórico", - "IndexerTagHelpText": "Usar este indexador apenas para séries com pelo menos uma tag correspondente. Deixe em branco para usar com todas as séries.", + "IndexerTagSeriesHelpText": "Usar este indexador apenas para séries com pelo menos uma tag correspondente. Deixe em branco para usar com todas as séries.", "MediaInfoFootNote": "MediaInfo Full/AudioLanguages/SubtitleLanguages oferece suporte a um sufixo \":EN+DE\", permitindo que você filtre os idiomas inclusos no nome do arquivo. Use \"-DE\" para excluir idiomas específicos. Usar \"+\" (p. ex.: \":EN+\") resultará em \"[EN]\"/\"[EN+--]\"/\"[--]\" dependendo dos idiomas excluídos. P. ex.: \"{MediaInfo Full:EN+DE}\".", "MinimumFreeSpaceHelpText": "Impedir a importação se deixar menos do que esta quantidade de espaço em disco disponível", "MustContainHelpText": "O lançamento deve conter pelo menos um desses termos (sem distinção entre maiúsculas e minúsculas)", "NegateHelpText": "Se marcado, o formato personalizado não será aplicado se esta condição {implementationName} corresponder.", "NoLimitForAnyRuntime": "Sem limite para qualquer duração", "NoMinimumForAnyRuntime": "Sem mínimo para qualquer duração", - "NotificationsTagsHelpText": "Envie notificações apenas para séries com pelo menos uma tag correspondente", + "NotificationsTagsSeriesHelpText": "Envie notificações apenas para séries com pelo menos uma tag correspondente", "OnManualInteractionRequired": "Na Interação Manual Necessária", "PendingChangesMessage": "Você tem alterações não salvas. Tem certeza de que deseja sair desta página?", "ProtocolHelpText": "Escolha qual(is) protocolo(s) usar e qual é o preferido ao escolher entre laçamentos iguais", "ProxyUsernameHelpText": "Você só precisa digitar um nome de usuário e senha se for necessário. Caso contrário, deixe-os em branco.", "QualityDefinitionsLoadError": "Não foi possível carregar as definições de qualidade", - "QualityProfileInUse": "Não é possível excluir um perfil de qualidade anexado a uma série, lista ou coleção", + "QualityProfileInUseSeriesListCollection": "Não é possível excluir um perfil de qualidade anexado a uma série, lista ou coleção", "EnableColorImpairedModeHelpText": "Estilo alterado para permitir que usuários com deficiência de cor distingam melhor as informações codificadas por cores", "RegularExpression": "Expressão Regular", "RegularExpressionsCanBeTested": "Expressões regulares podem ser testadas [aqui](http://regexstorm.net/tester).", @@ -777,7 +777,7 @@ "ReleaseProfile": "Perfil de Lançamento", "ReleaseProfileIndexerHelpText": "Especifique a qual indexador o perfil se aplica", "ReleaseProfileIndexerHelpTextWarning": "O uso de um indexador específico com perfis de lançamento pode levar à obtenção de lançamentos duplicados", - "ReleaseProfileTagHelpText": "Os perfis de lançamento serão aplicados a séries com pelo menos uma tag correspondente. Deixe em branco para aplicar a todas as séries", + "ReleaseProfileTagSeriesHelpText": "Os perfis de lançamento serão aplicados a séries com pelo menos uma tag correspondente. Deixe em branco para aplicar a todas as séries", "ReleaseProfiles": "Perfis de Lançamentos", "ReleaseProfilesLoadError": "Não foi possível carregar perfis de lançamentos", "RemotePath": "Caminho Remoto", @@ -794,7 +794,7 @@ "ReplaceIllegalCharactersHelpText": "Substituir caracteres ilegais. Se desmarcado, o {appName} irá removê-los", "ReplaceWithDash": "Substituir por Traço", "ReplaceWithSpaceDash": "Substituir por Espaço e Traço", - "RescanAfterRefreshHelpText": "Verifique novamente a pasta da série após atualizar a série", + "RescanAfterRefreshSeriesHelpText": "Verifique novamente a pasta da série após atualizar a série", "RescanAfterRefreshHelpTextWarning": "O {appName} não detectará automaticamente as alterações nos arquivos quando não estiver definido como 'Sempre'", "RescanSeriesFolderAfterRefresh": "Verificar novamente a pasta da série após a atualização", "ResetAPIKey": "Redefinir chave de API", @@ -865,7 +865,7 @@ "SupportedImportListsMoreInfo": "Para obter mais informações sobre as listas de importação individuais, clique nos botões de mais informações.", "SupportedIndexers": "O {appName} suporta qualquer indexador que use o padrão Newznab, bem como outros indexadores listados abaixo.", "SupportedIndexersMoreInfo": "Para obter mais informações sobre os indexadores individuais, clique nos botões de mais informações.", - "SupportedLists": "O {appName} oferece suporte a várias listas para importar séries para o banco de dados.", + "SupportedListsSeries": "O {appName} oferece suporte a várias listas para importar séries para o banco de dados.", "TagCannotBeDeletedWhileInUse": "A tag não pode ser excluída durante o uso", "TagDetails": "Detalhes da Tag - {label}", "TagIsNotUsedAndCanBeDeleted": "A tag não é usada e pode ser excluída", @@ -902,7 +902,7 @@ "UpdateUiNotWritableHealthCheckMessage": "Não é possível instalar a atualização porque a pasta de IU '{uiFolder}' não pode ser salva pelo usuário '{userName}'.", "UpgradeUntil": "Atualizar Até", "UpgradeUntilCustomFormatScore": "Atualizar até pontuação de formato personalizado", - "UpgradeUntilHelpText": "Quando essa qualidade for atingida, o {appName} não fará mais download de episódios", + "UpgradeUntilEpisodeHelpText": "Quando essa qualidade for atingida, o {appName} não fará mais download de episódios", "UpgradeUntilThisQualityIsMetOrExceeded": "Atualize até que essa qualidade seja atendida ou excedida", "UpgradesAllowed": "Atualizações Permitidas", "UpgradesAllowedHelpText": "se as qualidades desativadas não forem atualizadas", @@ -931,11 +931,11 @@ "UiSettingsLoadError": "Não foi possível carregar as configurações da UI", "UnmonitorDeletedEpisodesHelpText": "Os episódios excluídos do disco são deixados de ser monitorados automaticamente no {appName}", "UpdateScriptPathHelpText": "Caminho para um script personalizado que usa um pacote de atualização extraído e lida com o restante do processo de atualização", - "UpgradeUntilCustomFormatScoreHelpText": "Assim que essa pontuação de formato personalizado for alcançada, o {appName} não baixará mais lançamentos de episódios", + "UpgradeUntilCustomFormatScoreEpisodeHelpText": "Assim que essa pontuação de formato personalizado for alcançada, o {appName} não baixará mais lançamentos de episódios", "UseHardlinksInsteadOfCopy": "Usar links rígidos ao invés de Copiar", "VisitTheWikiForMoreDetails": "Visite o wiki para mais detalhes: ", "WantMoreControlAddACustomFormat": "Quer mais controle sobre quais downloads são preferidos? Adicione um [Formato Personalizado](/settings/customformats)", - "UnmonitorSpecials": "Não Monitorar Especiais", + "UnmonitorSpecialEpisodes": "Não Monitorar Especiais", "MonitorAllEpisodes": "Todos os Episódios", "AddNewSeries": "Adicionar Novas Séries", "AddNewSeriesHelpText": "É fácil adicionar uma nova série, basta começar a digitar o nome da série que deseja adicionar.", @@ -953,10 +953,10 @@ "ImportErrors": "Erros de Importação", "ImportExistingSeries": "Importar Série Existente", "ImportSeries": "Importar Séries", - "LibraryImportHeader": "Importar as séries que você já possui", + "LibraryImportSeriesHeader": "Importar as séries que você já possui", "LibraryImportTips": "Algumas dicas para garantir que a importação ocorra sem problemas:", "LibraryImportTipsDontUseDownloadsFolder": "Não use para importar downloads de seu cliente. Isso se aplica apenas a bibliotecas organizadas existentes, e não a arquivos desorganizados.", - "LibraryImportTipsQualityInFilename": "Certifique-se de que seus arquivos incluam a qualidade nos nomes de arquivo. Por exemplo: \"episódio.s02e15.bluray.mkv\"", + "LibraryImportTipsQualityInEpisodeFilename": "Certifique-se de que seus arquivos incluam a qualidade nos nomes de arquivo. Por exemplo: \"episódio.s02e15.bluray.mkv\"", "Monitor": "Monitorar", "MonitorAllEpisodesDescription": "Monitorar todos os episódios, exceto os especiais", "MonitorExistingEpisodes": "Episódios Existentes", @@ -966,40 +966,40 @@ "MonitorFutureEpisodesDescription": "Monitorar episódios que não foram exibidos", "MonitorMissingEpisodes": "Episódios ausentes", "MonitorMissingEpisodesDescription": "Monitora os episódios que não possuem arquivos ou ainda não foram ao ar", - "MonitorNone": "Nenhum", - "MonitorNoneDescription": "Nenhum episódio será monitorado", - "MonitorSpecials": "Monitorar especiais", - "MonitorSpecialsDescription": "Monitorar todos os episódios especiais sem alterar o status de monitoramento de outros episódios", + "MonitorNoEpisodes": "Nenhum", + "MonitorNoEpisodesDescription": "Nenhum episódio será monitorado", + "MonitorSpecialEpisodes": "Monitorar especiais", + "MonitorSpecialEpisodesDescription": "Monitorar todos os episódios especiais sem alterar o status de monitoramento de outros episódios", "NoMatchFound": "Nenhum resultado encontrado!", "ProcessingFolders": "Processando Pastas", "SearchByTvdbId": "Você também pode pesquisar usando o ID TVDB de um programa. Por exemplo: tvdb:71663", "SearchFailedError": "Falha na pesquisa, tente novamente mais tarde.", "SeriesTypesHelpText": "O tipo de série é usado para renomear, analisar e pesquisar", "Standard": "Padrão", - "StandardTypeDescription": "Episódios lançados com o padrão SxxEyy", + "StandardEpisodeTypeDescription": "Episódios lançados com o padrão SxxEyy", "StartImport": "Iniciar Importação", "StartProcessing": "Iniciar Processamento", "Upcoming": "Por vir", "AddNewSeriesError": "Falha ao carregar os resultados da pesquisa. Tente novamente.", "AddNewSeriesRootFolderHelpText": "A subpasta '{folder}' será criada automaticamente", - "AnimeTypeDescription": "Episódios lançados usando um número de episódio absoluto", - "DailyTypeDescription": "Episódios lançados diariamente ou com menos frequência que usam ano-mês-dia (2023-08-04)", - "LibraryImportTipsUseRootFolder": "Aponte o {appName} para a pasta que contém todas as suas séries, não uma específica. Por exemplo. \"`{goodFolderExample}`\" e não \"`{badFolderExample}`\". Além disso, cada série deve estar em sua própria pasta dentro da pasta raiz/biblioteca.", + "AnimeEpisodeTypeDescription": "Episódios lançados usando um número de episódio absoluto", + "DailyEpisodeTypeDescription": "Episódios lançados diariamente ou com menos frequência que usam ano-mês-dia (2023-08-04)", + "LibraryImportTipsSeriesUseRootFolder": "Aponte o {appName} para a pasta que contém todas as suas séries, não uma específica. Por exemplo. \"`{goodFolderExample}`\" e não \"`{badFolderExample}`\". Além disso, cada série deve estar em sua própria pasta dentro da pasta raiz/biblioteca.", "MonitorExistingEpisodesDescription": "Monitorar os episódios que possuem arquivos ou ainda não foram exibidos", "NoSeriesHaveBeenAdded": "Você ainda não adicionou nenhuma série. Deseja importar algumas ou todas as suas séries primeiro?", - "UnmonitorSpecialsDescription": "Cancela o monitoramento de todos os episódios especiais sem alterar o status monitorado de outros episódios", + "UnmonitorSpecialsEpisodesDescription": "Cancela o monitoramento de todos os episódios especiais sem alterar o status monitorado de outros episódios", "WhyCantIFindMyShow": "Por que não consigo encontrar meu programa?", "EpisodeImported": "Episódio importado", "Month": "Mês", "Today": "Hoje", "AgeWhenGrabbed": "Tempo de vida (quando obtido)", "DelayingDownloadUntil": "Atrasando o download até {date} às {time}", - "DeletedReasonMissingFromDisk": "O {appName} não conseguiu encontrar o arquivo no disco, então o arquivo foi desvinculado do episódio no banco de dados", + "DeletedReasonEpisodeMissingFromDisk": "O {appName} não conseguiu encontrar o arquivo no disco, então o arquivo foi desvinculado do episódio no banco de dados", "DeletedReasonManual": "O arquivo foi excluído por meio da IU", "DownloadFailed": "Download Falhou", "DestinationRelativePath": "Caminho Relativo de Destino", - "DownloadIgnoredTooltip": "Download do Episódio Ignorado", - "DownloadFailedTooltip": "O download do episódio falhou", + "DownloadIgnoredEpisodeTooltip": "Download do Episódio Ignorado", + "DownloadFailedEpisodeTooltip": "O download do episódio falhou", "DownloadIgnored": "Download ignorado", "DownloadWarning": "Aviso de download: {warningMessage}", "Downloading": "Baixando", @@ -1010,7 +1010,7 @@ "EpisodeFileRenamed": "Arquivo do Episódio Renomeado", "GrabId": "Obter ID", "GrabSelected": "Obter Selecionado", - "GrabbedHistoryTooltip": "Episódio retirado de {indexer} e enviado para {downloadClient}", + "EpisodeGrabbedTooltip": "Episódio retirado de {indexer} e enviado para {downloadClient}", "ImportedTo": "Importado para", "InfoUrl": "URL da info", "MarkAsFailed": "Marcar como falha", @@ -1045,15 +1045,15 @@ "SpecialEpisode": "Episódio Especial", "Agenda": "Programação", "AnEpisodeIsDownloading": "Um episódio está baixando", - "CalendarLegendMissingTooltip": "O episódio foi ao ar e está faltando no disco", + "CalendarLegendEpisodeMissingTooltip": "O episódio foi ao ar e está faltando no disco", "CalendarFeed": "{appName} Feed do Calendário", - "CalendarLegendDownloadedTooltip": "O episódio foi baixado e classificado", - "CalendarLegendDownloadingTooltip": "O episódio está sendo baixado no momento", - "CalendarLegendFinaleTooltip": "Final de série ou temporada", - "CalendarLegendOnAirTooltip": "Episódio está sendo exibido no momento", - "CalendarLegendPremiereTooltip": "Estreia de série ou temporada", - "CalendarLegendUnairedTooltip": "Episódio ainda não foi ao ar", - "CalendarLegendUnmonitoredTooltip": "Episódio não monitorado", + "CalendarLegendEpisodeDownloadedTooltip": "O episódio foi baixado e classificado", + "CalendarLegendEpisodeDownloadingTooltip": "O episódio está sendo baixado no momento", + "CalendarLegendSeriesFinaleTooltip": "Final de série ou temporada", + "CalendarLegendEpisodeOnAirTooltip": "Episódio está sendo exibido no momento", + "CalendarLegendSeriesPremiereTooltip": "Estreia de série ou temporada", + "CalendarLegendEpisodeUnairedTooltip": "Episódio ainda não foi ao ar", + "CalendarLegendEpisodeUnmonitoredTooltip": "Episódio não monitorado", "CalendarOptions": "Opções de Calendário", "CheckDownloadClientForDetails": "verifique o cliente de download para mais detalhes", "CollapseMultipleEpisodes": "Agrupar Múltiplos Episódios", @@ -1070,7 +1070,7 @@ "FullColorEvents": "Eventos em Cores", "Global": "Global", "ICalFeedHelpText": "Copie esta URL para seu(s) cliente(s) ou clique para se inscrever se seu navegador for compatível com webcal", - "ICalIncludeUnmonitoredHelpText": "Incluir episódios não monitorados no feed iCal", + "ICalIncludeUnmonitoredEpisodesHelpText": "Incluir episódios não monitorados no feed iCal", "ICalSeasonPremieresOnlyHelpText": "Apenas o primeiro episódio de uma temporada estará no feed", "ICalShowAsAllDayEventsHelpText": "Os eventos aparecerão como eventos de dia inteiro em seu calendário", "IconForCutoffUnmet": "Ícone para Corte Não Atendido", @@ -1081,7 +1081,7 @@ "ImportFailed": "Falha na importação: {sourceTitle}", "EpisodeMissingAbsoluteNumber": "O episódio não tem um número de episódio absoluto", "FullColorEventsHelpText": "Estilo alterado para colorir todo o evento com a cor do status, em vez de apenas a borda esquerda. Não se aplica à Agenda", - "ICalTagsHelpText": "O feed conterá apenas séries com pelo menos uma tag correspondente", + "ICalTagsSeriesHelpText": "O feed conterá apenas séries com pelo menos uma tag correspondente", "IconForFinalesHelpText": "Mostrar ícone para finais de séries/temporadas com base nas informações de episódios disponíveis", "NoHistoryBlocklist": "Não há lista de bloqueio no histórico", "QualityCutoffNotMet": "O corte de qualidade não foi atingido", @@ -1101,7 +1101,7 @@ "AddedToDownloadQueue": "Adicionado à fila de download", "Airs": "Vai ao ar em", "AirsDateAtTimeOn": "{date} às {time} em {networkLabel}", - "AnimeTypeFormat": "Número absoluto do episódio ({format})", + "AnimeEpisodeTypeFormat": "Número absoluto do episódio ({format})", "AppUpdatedVersion": "{appName} foi atualizado para a versão `{version}`, para obter as alterações mais recentes, você precisará recarregar {appName} ", "ChooseImportMode": "Escolha o Modo de Importação", "ClickToChangeEpisode": "Clique para alterar o episódio", @@ -1118,13 +1118,13 @@ "FilterEpisodesPlaceholder": "Filtrar episódios por título ou número", "FilterIsAfter": "está depois", "Grab": "Obter", - "GrabReleaseMessageText": "O {appName} não conseguiu determinar para qual série e episódio é este lançamento. O {appName} pode não conseguir importar automaticamente este lançamento. Deseja obter \"{title}\"?", + "GrabReleaseUnknownSeriesOrEpisodeMessageText": "O {appName} não conseguiu determinar para qual série e episódio é este lançamento. O {appName} pode não conseguir importar automaticamente este lançamento. Deseja obter \"{title}\"?", "ICalFeed": "Feed do iCal", "ICalLink": "Link do iCal", "InteractiveImportLoadError": "Não foi possível carregar itens de importação manual", "InteractiveImportNoFilesFound": "Nenhum arquivo de vídeo foi encontrado na pasta selecionada", "InteractiveImportNoSeason": "A temporada deve ser escolhida para cada arquivo selecionado", - "InteractiveSearchResultsFailedErrorMessage": "A pesquisa falhou porque {message}. Tente atualizar as informações da série e verifique se as informações necessárias estão presentes antes de pesquisar novamente.", + "InteractiveSearchResultsSeriesFailedErrorMessage": "A pesquisa falhou porque {message}. Tente atualizar as informações da série e verifique se as informações necessárias estão presentes antes de pesquisar novamente.", "KeyboardShortcutsFocusSearchBox": "Selecionar a caixa de pesquisa", "KeyboardShortcutsSaveSettings": "Salvar configurações", "LocalStorageIsNotSupported": "O armazenamento local não é compatível ou está desabilitado. Um plugin ou a navegação privada pode tê-lo desativado.", @@ -1146,7 +1146,7 @@ "Continuing": "Continuando", "CountSelectedFile": "{selectedCount} arquivo selecionado", "CustomFilters": "Filtros Personalizados", - "DailyTypeFormat": "Data ({format})", + "DailyEpisodeTypeFormat": "Data ({format})", "Default": "Padrão", "DeleteEpisodeFileMessage": "Tem certeza de que deseja excluir '{path}'?", "DeleteEpisodeFromDisk": "Excluir episódio do disco", @@ -1240,7 +1240,7 @@ "SelectSeason": "Selecionar Temporada", "SelectSeasonModalTitle": "{modalTitle} - Selecione a Temporada", "SetReleaseGroup": "Definir Grupo do Lançamento", - "StandardTypeFormat": "Números da temporada e do episódio ({format})", + "StandardEpisodeTypeFormat": "Números da temporada e do episódio ({format})", "TableColumnsHelpText": "Escolha quais colunas são visíveis e em que ordem elas aparecem", "TablePageSizeMinimum": "O tamanho da página precisa ser de pelo menos {minimumValue}", "Umask750Description": "{octal} - gravação do proprietário, leitura do grupo", @@ -1322,7 +1322,7 @@ "Test": "Teste", "Level": "Nível", "AddListExclusion": "Adicionar exclusão à lista", - "AddListExclusionHelpText": "Impedir que o {appName} adicione séries por listas", + "AddListExclusionSeriesHelpText": "Impedir que o {appName} adicione séries por listas", "EditSeriesModalHeader": "Editar - {title}", "EditSelectedSeries": "Editar Séries Selecionadas", "HideEpisodes": "Ocultar episódios", @@ -1375,7 +1375,7 @@ "ManageEpisodesSeason": "Gerenciar arquivos de episódios nesta temporada", "Medium": "Médio", "MonitorSeries": "Monitorar Série", - "MonitoredHelpText": "Baixar episódios monitorados desta série", + "MonitoredEpisodesHelpText": "Baixar episódios monitorados desta série", "MonitoredStatus": "Monitorado/Status", "Monitoring": "Monitorando", "NoEpisodeInformation": "Nenhuma informação do episódio está disponível.", @@ -1412,7 +1412,7 @@ "ShowSeasonCount": "Mostrar Número da Temporada", "ShowSizeOnDisk": "Mostrar Tamanho no Disco", "ShowTitle": "Mostrar Título", - "ShowTitleHelpText": "Mostrar o título da série abaixo do pôster", + "ShowSeriesTitleHelpText": "Mostrar o título da série abaixo do pôster", "Small": "Pequeno", "StopSelecting": "Pare de Selecionar", "Table": "Tabela", @@ -1456,13 +1456,13 @@ "UnmonitorSelected": "Não Monitorar Selecionado", "CutoffUnmetNoItems": "Nenhum item com limite não atendido", "MonitorSelected": "Monitorar selecionados", - "SearchForAllMissingConfirmationCount": "Tem certeza de que deseja pesquisar todos os episódios ausentes de {totalRecords}?", + "SearchForAllMissingEpisodesConfirmationCount": "Tem certeza de que deseja pesquisar todos os episódios ausentes de {totalRecords}?", "SearchSelected": "Pesquisar Selecionado", "CutoffUnmetLoadError": "Erro ao carregar itens de limite não atendido", "MassSearchCancelWarning": "Após começar, não é possível cancelar sem reiniciar o {appName} ou desabilitar todos os seus indexadores.", - "SearchForAllMissing": "Pesquisar por todos os episódios ausentes", - "SearchForCutoffUnmet": "Pesquise todos os episódios que o corte não foi atingido", - "SearchForCutoffUnmetConfirmationCount": "Tem certeza de que deseja pesquisar todos os episódios de {totalRecords} corte não atingido?", + "SearchForAllMissingEpisodes": "Pesquisar por todos os episódios ausentes", + "SearchForCutoffUnmetEpisodes": "Pesquise todos os episódios que o corte não foi atingido", + "SearchForCutoffUnmetEpisodesConfirmationCount": "Tem certeza de que deseja pesquisar todos os episódios de {totalRecords} corte não atingido?", "FormatAgeDay": "dia", "FormatAgeHours": "horas", "FormatDateTime": "{formattedDate} {formattedTime}", @@ -1601,9 +1601,9 @@ "DownloadClientSettingsDestinationHelpText": "Especifica manualmente o destino do download, deixe em branco para usar o padrão", "DownloadClientSettingsInitialState": "Estado Inicial", "DownloadClientSettingsInitialStateHelpText": "Estado inicial dos torrents adicionados ao {clientName}", - "DownloadClientSettingsOlderPriorityHelpText": "Prioridade de uso ao baixar episódios que foram ao ar há mais de 14 dias", + "DownloadClientSettingsOlderPriorityEpisodeHelpText": "Prioridade de uso ao baixar episódios que foram ao ar há mais de 14 dias", "DownloadClientSettingsPostImportCategoryHelpText": "Categoria para {appName} definir após importar o download. {appName} não removerá torrents nessa categoria mesmo que a propagação seja concluída. Deixe em branco para manter a mesma categoria.", - "DownloadClientSettingsRecentPriorityHelpText": "Prioridade de uso ao baixar episódios que foram ao ar nos últimos 14 dias", + "DownloadClientSettingsRecentPriorityEpisodeHelpText": "Prioridade de uso ao baixar episódios que foram ao ar nos últimos 14 dias", "DownloadClientSettingsOlderPriority": "Prioridade para os mais antigos", "DownloadClientSettingsRecentPriority": "Prioridade para os mais recentes", "DownloadClientUTorrentTorrentStateError": "uTorrent está relatando um erro", diff --git a/src/NzbDrone.Core/Localization/Core/zh_CN.json b/src/NzbDrone.Core/Localization/Core/zh_CN.json index dbea0aeb4..676afd879 100644 --- a/src/NzbDrone.Core/Localization/Core/zh_CN.json +++ b/src/NzbDrone.Core/Localization/Core/zh_CN.json @@ -47,7 +47,7 @@ "ApplyTagsHelpTextHowToApplyIndexers": "如何将标签应用到已选择的索引器", "AppDataLocationHealthCheckMessage": "正在更新期间的 AppData 不会被更新删除", "BlocklistRelease": "黑名单版本", - "BlocklistReleaseHelpText": "再次启动对此集的搜索并阻止再次获取此版本", + "BlocklistReleaseSearchEpisodeAgainHelpText": "再次启动对此集的搜索并阻止再次获取此版本", "BlocklistReleases": "黑名单版本", "CloneCustomFormat": "复制自定义命名格式", "Close": "关闭", @@ -83,7 +83,7 @@ "IndexerRssNoIndexersEnabledHealthCheckMessage": "没有启用RSS同步的索引器,{appName}不会自动抓取新版本", "IndexerSearchNoAutomaticHealthCheckMessage": "没有启用自动搜索的索引器,{appName}不会提供任何自动搜索结果", "IndexerStatusAllUnavailableHealthCheckMessage": "所有搜刮器都因错误不可用", - "MountHealthCheckMessage": "挂载的媒体路径是只读的: ", + "MountSeriesHealthCheckMessage": "挂载的媒体路径是只读的: ", "Network": "网络", "NextAiring": "下一次上映", "OneSeason": "季1", @@ -94,7 +94,7 @@ "QualityProfile": "质量配置", "RefreshSeries": "刷新节目", "RemotePathMappingBadDockerPathHealthCheckMessage": "您正在使用docker;下载客户端{downloadClientName}下载目录为{path},但这不是有效的{osName}路径。查看Docker路径映射并为下载客户端重新配置。", - "RemotePathMappingDownloadPermissionsHealthCheckMessage": "{appName}已存在剧集目录{path}但无法访问。可能是权限错误。", + "RemotePathMappingDownloadPermissionsEpisodeHealthCheckMessage": "{appName}已存在剧集目录{path}但无法访问。可能是权限错误。", "Mode": "模式", "MoreInfo": "更多信息", "New": "新的", @@ -307,7 +307,7 @@ "Repack": "重新打包", "Version": "版本", "Special": "特色", - "RemotePathMappingImportFailedHealthCheckMessage": "{appName}无法导入剧集。查看日志以了解详细信息。", + "RemotePathMappingImportEpisodeFailedHealthCheckMessage": "{appName}无法导入剧集。查看日志以了解详细信息。", "RemotePathMappingWrongOSPathHealthCheckMessage": "远程下载客户端{downloadClientName}将文件下载在{path}中,但这不是有效的{osName}路径。查看远程路径映射并更新下载客户端设置。", "RemoveFromDownloadClientHelpTextWarning": "删除将从下载客户端删除下载和文件。", "Renamed": "已重命名", @@ -405,7 +405,7 @@ "AlreadyInYourLibrary": "已经在你的库中", "Always": "总是", "AnimeEpisodeFormat": "动漫单集格式", - "AnimeTypeFormat": "绝对集数 ({format})", + "AnimeEpisodeTypeFormat": "绝对集数 ({format})", "AppUpdated": "{appName} 升级", "AppUpdatedVersion": "{appName} 已经更新到 {version} 版本,重新加载 {appName} 使更新生效 ", "AuthenticationRequired": "需要身份验证", @@ -420,14 +420,14 @@ "BypassDelayIfHighestQualityHelpText": "当发布版本的质量中含有已启用的最高质量首选协议时跳过延迟", "BypassProxyForLocalAddresses": "对局域网地址不使用代理", "CalendarFeed": "{appName} 日历订阅", - "CalendarLegendDownloadedTooltip": "集已下载完成并完成排序", - "CalendarLegendDownloadingTooltip": "集正在下载中", - "CalendarLegendFinaleTooltip": "剧集或季完结", - "CalendarLegendMissingTooltip": "集已播出,但硬盘中缺失", - "CalendarLegendOnAirTooltip": "集正在播出", - "CalendarLegendPremiereTooltip": "剧集或季首播", - "CalendarLegendUnairedTooltip": "集尚未播出", - "CalendarLegendUnmonitoredTooltip": "集未被监控", + "CalendarLegendEpisodeDownloadedTooltip": "集已下载完成并完成排序", + "CalendarLegendEpisodeDownloadingTooltip": "集正在下载中", + "CalendarLegendSeriesFinaleTooltip": "剧集或季完结", + "CalendarLegendEpisodeMissingTooltip": "集已播出,但硬盘中缺失", + "CalendarLegendEpisodeOnAirTooltip": "集正在播出", + "CalendarLegendSeriesPremiereTooltip": "剧集或季首播", + "CalendarLegendEpisodeUnairedTooltip": "集尚未播出", + "CalendarLegendEpisodeUnmonitoredTooltip": "集未被监控", "CalendarOptions": "日历选项", "CancelProcessing": "取消进行中", "CertificateValidation": "验证证书", @@ -464,14 +464,14 @@ "CustomFormatsSettings": "自定义格式设置", "CustomFormatsSettingsSummary": "自定义格式和设置", "Cutoff": "截止", - "DailyTypeFormat": "日期 ({format})", + "DailyEpisodeTypeFormat": "日期 ({format})", "Day": "天", "Default": "默认", - "DefaultDelayProfile": "这是默认配置,它适用于所有没有明确配置的剧集。", + "DefaultDelayProfileSeries": "这是默认配置,它适用于所有没有明确配置的剧集。", "DefaultNotFoundMessage": "你一定是迷路了,这里没什么可看的。", "DelayMinutes": "{delay} 分钟", "DelayProfile": "延时配置", - "DelayProfileTagsHelpText": "至少有一个匹配的标签应用于剧集", + "DelayProfileSeriesTagsHelpText": "至少有一个匹配的标签应用于剧集", "DelayProfiles": "延迟配置", "DelayProfileProtocol": "协议:{preferredProtocol}", "DeleteAutoTag": "删除自动标签", @@ -479,7 +479,7 @@ "DeleteDownloadClient": "删除下载客户端", "DeleteDownloadClientMessageText": "你确定要删除下载客户端 “{name}” 吗?", "DeleteEmptyFolders": "删除空目录", - "DeleteEmptyFoldersHelpText": "如果集文件被删除,当磁盘扫描时删除剧集或季的空目录", + "DeleteEmptySeriesFoldersHelpText": "如果集文件被删除,当磁盘扫描时删除剧集或季的空目录", "DeleteEpisodeFile": "删除集文件", "DeleteEpisodeFileMessage": "您确定要删除“{path}”吗?", "DeleteEpisodeFromDisk": "从磁盘中删除剧集", @@ -496,7 +496,7 @@ "DownloadClientOptionsLoadError": "无法加载下载客户端选项", "DownloadClientSettings": "下载客户端设置", "DownloadFailed": "下载失败", - "DownloadFailedTooltip": "集下载失败", + "DownloadFailedEpisodeTooltip": "集下载失败", "DownloadWarning": "下载警告:{warningMessage}", "Downloaded": "已下载", "Downloading": "下载中", @@ -542,7 +542,7 @@ "AnalyticsEnabledHelpText": "将匿名使用情况和错误信息发送到{appName}的服务器。这包括有关您的浏览器的信息、您使用的{appName} WebUI页面、错误报告以及操作系统和运行时版本。我们将使用此信息来确定功能和错误修复的优先级。", "AnalyseVideoFiles": "分析视频文件", "ApplicationURL": "应用程序 URL", - "AnimeTypeDescription": "使用绝对集数发布的集数", + "AnimeEpisodeTypeDescription": "使用绝对集数发布的集数", "ApiKey": "API Key", "ApplicationUrlHelpText": "此应用的外部URL,包含 http(s)://、端口和基本URL", "AuthenticationMethodHelpText": "需要用户名和密码以访问 {appName}", @@ -588,7 +588,7 @@ "DoNotPrefer": "不要首选", "DoNotUpgradeAutomatically": "不要自动升级", "DownloadIgnored": "忽略下载", - "DownloadIgnoredTooltip": "集下载被忽略", + "DownloadIgnoredEpisodeTooltip": "集下载被忽略", "EditAutoTag": "编辑自动标签", "AddAutoTagError": "无法添加新的自动标签,请重试。", "AddImportListExclusionError": "无法添加新排除列表,请再试一次。", @@ -604,11 +604,11 @@ "CalendarLoadError": "无法加载日历", "Agenda": "日程表", "CertificateValidationHelpText": "改变HTTPS证书验证的严格程度。不要更改除非您了解风险。", - "CopyUsingHardlinksHelpText": "硬链接 (Hardlinks) 允许 {appName} 将还在做种中的剧集文件(夹)导入而不占用额外的存储空间或者复制文件(夹)的全部内容。硬链接 (Hardlinks) 仅能在源文件和目标文件在同一磁盘卷中使用", + "CopyUsingHardlinksSeriesHelpText": "硬链接 (Hardlinks) 允许 {appName} 将还在做种中的剧集文件(夹)导入而不占用额外的存储空间或者复制文件(夹)的全部内容。硬链接 (Hardlinks) 仅能在源文件和目标文件在同一磁盘卷中使用", "CustomFormat": "自定义命名格式", "CustomFormatHelpText": "{appName}会根据满足自定义格式与否给每个发布版本评分,如果一个新的发布版本有更高的分数,有相同或更高的影片质量,则{appName}会抓取该发布版本。", "DeleteAutoTagHelpText": "你确定要删除 “{name}” 自动标签吗?", - "DownloadClientTagHelpText": "仅将此下载客户端用于至少具有一个匹配标签的剧集。留空可用于所有剧集。", + "DownloadClientSeriesTagHelpText": "仅将此下载客户端用于至少具有一个匹配标签的剧集。留空可用于所有剧集。", "Absolute": "绝对", "AddANewPath": "添加一个新的目录", "AbsoluteEpisodeNumber": "准确的集数", @@ -675,7 +675,7 @@ "Folder": "文件夹", "GeneralSettingsLoadError": "无法加载通用设置", "GeneralSettingsSummary": "端口、SSL、用户名/密码、代理、分析、更新", - "GrabReleaseMessageText": "{appName}无法确定这个发布版本是哪部剧集的哪一集,{appName}可能无法自动导入此版本,你想要获取“{title}”吗?", + "GrabReleaseUnknownSeriesOrEpisodeMessageText": "{appName}无法确定这个发布版本是哪部剧集的哪一集,{appName}可能无法自动导入此版本,你想要获取“{title}”吗?", "GrabRelease": "抓取版本", "Here": "这里", "Group": "组", @@ -685,7 +685,7 @@ "ICalFeed": "iCal订阅地址", "Host": "主机", "ICalFeedHelpText": "将此URL复制到您的客户端,如果您的浏览器支持webcal,请直接点击订阅按钮", - "ICalIncludeUnmonitoredHelpText": "在iCal订阅中包含未监控的集", + "ICalIncludeUnmonitoredEpisodesHelpText": "在iCal订阅中包含未监控的集", "ICalLink": "iCal链接", "ICalShowAsAllDayEvents": "作为全天事件显示", "ICalShowAsAllDayEventsHelpText": "事件将以全天事件的形式显示在日历中", @@ -710,7 +710,7 @@ "DeleteSpecification": "删除规范", "DeleteSpecificationHelpText": "您确定要删除规范 '{name}' 吗?", "DeletedReasonManual": "文件已通过 UI 删除", - "DeletedReasonMissingFromDisk": "{appName} 在磁盘上找不到该文件,因此已取消数据库中和该文件的集关联", + "DeletedReasonEpisodeMissingFromDisk": "{appName} 在磁盘上找不到该文件,因此已取消数据库中和该文件的集关联", "DownloadPropersAndRepacks": "优化版和重制版", "DownloadPropersAndRepacksHelpText": "是否自动更新至优化版和重制版", "Enable": "启用", @@ -749,22 +749,22 @@ "Grab": "抓取", "GrabId": "抓取ID", "GrabSelected": "抓取已选", - "ICalTagsHelpText": "剧集至少要匹配一个标签才会出现在订阅中", + "ICalTagsSeriesHelpText": "剧集至少要匹配一个标签才会出现在订阅中", "IconForSpecials": "特别节目的图标", "IconForSpecialsHelpText": "为特别节目(季0)显示图标", "ImdbId": "IMDb ID", - "ImportExtraFilesHelpText": "导入集文件后导入匹配的额外文件(字幕/nfo等)", + "ImportExtraFilesEpisodeHelpText": "导入集文件后导入匹配的额外文件(字幕/nfo等)", "ImportListSettings": "导入列表设置", "ImportListsLoadError": "无法加载导入列表", - "EnableAutomaticAddHelpText": "当通过 UI 或 {appName} 执行同步时,将剧集添加到 {appName}", + "EnableAutomaticAddSeriesHelpText": "当通过 UI 或 {appName} 执行同步时,将剧集添加到 {appName}", "EnableInteractiveSearchHelpText": "当手动搜索启用时使用", "EnableMediaInfoHelpText": "从文件中提取视频信息,如分辨率、运行时间和编解码器信息。这需要{appName}读取文件,可能导致扫描期间磁盘或网络出现高负载。", - "GrabbedHistoryTooltip": "集抓取自 {indexer} 并发送至 {downloadClient}", + "EpisodeGrabbedTooltip": "集抓取自 {indexer} 并发送至 {downloadClient}", "HealthMessagesInfoBox": "您可以通过单击行尾的wiki链接(图书图标)或检查[日志]({link})来查找有关这些运行状况检查消息原因的更多信息。如果你在理解这些信息方面有困难,你可以通过下面的链接联系我们的支持。", "StandardEpisodeFormat": "标准单集格式", "DefaultNameCopiedSpecification": "{name} - 复制", "AddListExclusion": "添加列表例外", - "AddListExclusionHelpText": "防止剧集通过列表添加到{appName}", + "AddListExclusionSeriesHelpText": "防止剧集通过列表添加到{appName}", "AddNewSeriesSearchForCutoffUnmetEpisodes": "开始搜索未达截止条件的集", "AddedDate": "加入于:{date}", "AllSeriesAreHiddenByTheAppliedFilter": "所有结果都被应用的过滤器隐藏", @@ -787,8 +787,8 @@ "RenameEpisodes": "重命名剧集", "ReplaceWithDash": "替换为破折号", "ReplaceWithSpaceDashSpace": "替换为空格破折号空格", - "StandardTypeFormat": "季数和集数({format})", - "DailyTypeDescription": "使用年-月-日的每天或更少频率发布的剧集(2023-08-04)", + "StandardEpisodeTypeFormat": "季数和集数({format})", + "DailyEpisodeTypeDescription": "使用年-月-日的每天或更少频率发布的剧集(2023-08-04)", "Dash": "破折号", "DelayingDownloadUntil": "将下载推迟到 {date} 的 {time}", "SelectLanguage": "选择语言", @@ -807,7 +807,7 @@ "FormatAgeDays": "天", "RegularExpressionsTutorialLink": "有关正则表达式的更多详细信息,请参阅[此处](https://www.regular-expressions.info/tutorial.html)。", "FormatDateTime": "{formattedDate} {formattedTime}", - "ReleaseProfileTagHelpText": "发布配置将应用于至少有一个匹配标记的剧集。留空适用于所有剧集", + "ReleaseProfileTagSeriesHelpText": "发布配置将应用于至少有一个匹配标记的剧集。留空适用于所有剧集", "FormatShortTimeSpanHours": "{hours} 时", "RemotePathMappingHostHelpText": "与您为远程下载客户端指定的主机相同", "HideEpisodes": "隐藏集", @@ -816,7 +816,7 @@ "IncludeCustomFormatWhenRenaming": "重命名时包含自定义格式", "RemotePathMappingsInfo": "很少需要远程路径映射,如果{appName}和您的下载客户端在同一系统上,则最好匹配您的路径。更多信息,请参阅[wiki]({wikiLink})", "IndexerPriorityHelpText": "索引器优先级从1(最高)到50(最低),默认25。当资源连接中断时寻找同等资源时使用,否则{appName}将依旧使用已启用的索引器进行RSS同步并搜索", - "IndexerTagHelpText": "仅对至少有一个匹配标记的剧集使用此索引器。留空则适用于所有剧集。", + "IndexerTagSeriesHelpText": "仅对至少有一个匹配标记的剧集使用此索引器。留空则适用于所有剧集。", "InteractiveImportNoFilesFound": "在所选文件夹中找不到视频文件", "RemoveSelectedBlocklistMessageText": "你确定你想从过滤清单中删除选中的项目吗?", "InteractiveImportNoQuality": "必须为每个选定的文件选择质量", @@ -826,19 +826,19 @@ "KeyboardShortcutsOpenModal": "打开此模式", "SelectEpisodes": "选择剧集", "SelectSeason": "选择季", - "LibraryImportTipsQualityInFilename": "确保您的文件在其文件名中包含质量。例如:`episode.s02e15.bluray.mkv`", - "LibraryImportTipsUseRootFolder": "将{appName}指向包含所有电视节目的文件夹,而不是特定的一个。例如“`{goodFolderExample}`”而不是“`{badFolderExamp}`”。此外,每个剧集都必须有单独的文件夹位于根/库文件夹下。", + "LibraryImportTipsQualityInEpisodeFilename": "确保您的文件在其文件名中包含质量。例如:`episode.s02e15.bluray.mkv`", + "LibraryImportTipsSeriesUseRootFolder": "将{appName}指向包含所有电视节目的文件夹,而不是特定的一个。例如“`{goodFolderExample}`”而不是“`{badFolderExamp}`”。此外,每个剧集都必须有单独的文件夹位于根/库文件夹下。", "ListQualityProfileHelpText": "质量配置列表项将添加", "SeriesIndexFooterMissingMonitored": "缺失集(剧集被监控)", "SeriesIsMonitored": "剧集被监控", - "SearchForCutoffUnmet": "搜索所有Cutoff Unmet的剧集", + "SearchForCutoffUnmetEpisodes": "搜索所有Cutoff Unmet的剧集", "SeriesProgressBarText": "{episodeFileCount} / {episodeCount} (总计: {totalEpisodeCount}, 下载中: {downloadingCount})", "SeriesTitleToExcludeHelpText": "要排除的剧集名称", "Umask750Description": "{octal} - 所有者写入,组读取", "SeriesTypesHelpText": "剧集类型用于重命名、解析和搜索", "MediaManagementSettingsLoadError": "无法加载媒体管理设置", "SourceRelativePath": "源相对路径", - "MetadataSourceSettingsSummary": "{appName}从哪里获得剧集和集信息的总结", + "MetadataSourceSettingsSeriesSummary": "{appName}从哪里获得剧集和集信息的总结", "SslCertPassword": "SSL证书密码", "UpdateUiNotWritableHealthCheckMessage": "无法安装更新,因为用户“{userName}”无法写入 UI 文件夹“{uiFolder}”。", "MinimumCustomFormatScoreHelpText": "允许下载的最小自定义格式分数", @@ -848,9 +848,9 @@ "StopSelecting": "停止选择", "MonitorFirstSeasonDescription": "监控第一季的所有集。所有其他季都将被忽略", "SupportedCustomConditions": "{appName}支持针对以下发布属性的自定义条件。", - "MonitorSpecials": "监控特别节目", + "MonitorSpecialEpisodes": "监控特别节目", "SupportedDownloadClients": "{appName}支持许多流行的torrent和usenet下载客户端。", - "UpgradeUntilCustomFormatScoreHelpText": "一旦达到此自定义格式分数,{appName} 将不再抓取集的其他版本", + "UpgradeUntilCustomFormatScoreEpisodeHelpText": "一旦达到此自定义格式分数,{appName} 将不再抓取集的其他版本", "MoveFiles": "移动文件", "SupportedImportListsMoreInfo": "若需要查看有关导入列表的详细信息,请点击“更多信息”按钮。", "MoveSeriesFoldersDontMoveFiles": "不,我自己移动文件", @@ -868,18 +868,18 @@ "Yesterday": "昨天", "OnGrab": "抓取中", "PendingChangesStayReview": "留下检查更改", - "SearchForCutoffUnmetConfirmationCount": "您确定要搜索所有{totalRecords}Cutoff Unmet的剧集吗?", + "SearchForCutoffUnmetEpisodesConfirmationCount": "您确定要搜索所有{totalRecords}Cutoff Unmet的剧集吗?", "IncludeHealthWarnings": "包含健康度警告", "IndexerPriority": "索引器优先级", "IndexerOptionsLoadError": "无法加载索引器选项", "LastUsed": "上次使用", "MarkAsFailed": "标记为失败", "MediaManagementSettings": "媒体管理设置", - "MetadataSettingsSummary": "导入或刷新剧集时创建元数据文件", + "MetadataSettingsSeriesSummary": "导入或刷新剧集时创建元数据文件", "MetadataSourceSettings": "元数据源设置", "Mixed": "混合", "MonitorFutureEpisodesDescription": "监控尚未播出的集", - "MonitorSpecialsDescription": "监控所有特别节目,而不更改其他集的监控状态", + "MonitorSpecialEpisodesDescription": "监控所有特别节目,而不更改其他集的监控状态", "OnSeriesDelete": "剧集删除时", "OnlyForBulkSeasonReleases": "仅适用于批量季发布", "OnlyTorrent": "只有torrent", @@ -949,14 +949,14 @@ "InteractiveImportNoImportMode": "必须选择导入模式", "InteractiveImportNoLanguage": "必须为每个选定的文件选择语言", "InteractiveImportNoSeries": "必须为每个选中的文件选择剧集", - "InteractiveSearchResultsFailedErrorMessage": "搜索失败,因为 {message}。请尝试刷新剧集信息,并验证是否存在必要信息,再尝试进行搜索。", + "InteractiveSearchResultsSeriesFailedErrorMessage": "搜索失败,因为 {message}。请尝试刷新剧集信息,并验证是否存在必要信息,再尝试进行搜索。", "InteractiveSearchSeason": "手动搜索本季所有集", "KeyboardShortcutsConfirmModal": "接受确认模式", "KeyboardShortcutsCloseModal": "关闭当前模式", "KeyboardShortcutsFocusSearchBox": "焦点搜索框", "Large": "大", "Level": "等级", - "LibraryImportHeader": "导入您已有的剧集", + "LibraryImportSeriesHeader": "导入您已有的剧集", "LibraryImportTips": "一些小提示以确保导入顺利进行:", "Links": "链接", "ListExclusionsLoadError": "无法加载排除列表", @@ -1026,7 +1026,7 @@ "NoSeriesFoundImportOrAdd": "找不到剧集,您需要导入现有剧集或添加新剧集以开始使用。", "NoTagsHaveBeenAddedYet": "未添加标签", "NoSeriesHaveBeenAdded": "您尚未添加任何剧集,是否要先导入部分或全部剧集?", - "NotificationsTagsHelpText": "剧集至少有一个标签匹配时发送通知", + "NotificationsTagsSeriesHelpText": "剧集至少有一个标签匹配时发送通知", "Ok": "完成", "OnEpisodeFileDelete": "集文件删除时", "OnEpisodeFileDeleteForUpgrade": "集文件因升级删除时", @@ -1099,7 +1099,7 @@ "RemoveTagsAutomatically": "自动删除标签", "Repeat": "重复", "ResetDefinitions": "重置定义", - "RescanAfterRefreshHelpText": "刷新剧集信息后重新扫描剧集文件夹", + "RescanAfterRefreshSeriesHelpText": "刷新剧集信息后重新扫描剧集文件夹", "ResetAPIKey": "重置API Key", "RestartRequiredHelpTextWarning": "需重启以生效", "RestartRequiredWindowsService": "根据运行{appName}的用户,在服务自动启动之前,您可能需要以管理员身份重新启动{appName}一次。", @@ -1150,7 +1150,7 @@ "ShowSearchHelpText": "悬停时显示搜索按钮", "ShowSeasonCount": "显示季数", "ShowTitle": "显示标题", - "ShowTitleHelpText": "在海报下显示剧集标题", + "ShowSeriesTitleHelpText": "在海报下显示剧集标题", "ShowUnknownSeriesItems": "实现未知剧集项目", "ShowUnknownSeriesItemsHelpText": "显示队列中没有剧集的项目,这可能包括已删除的剧集、电影或 {appName} 类别中的任何其他内容", "SkipFreeSpaceCheckWhenImportingHelpText": "在文件导入期间,当{appName}无法检测根文件夹的空闲空间时使用", @@ -1159,7 +1159,7 @@ "SkipFreeSpaceCheck": "跳过剩余空间检查", "Space": "空间", "SpecialEpisode": "特别集", - "StandardTypeDescription": "以SxxEyy模式发布的集", + "StandardEpisodeTypeDescription": "以SxxEyy模式发布的集", "StartImport": "开始导入", "StartProcessing": "开始处理", "Table": "表格", @@ -1191,9 +1191,9 @@ "Unlimited": "无限制", "UnmappedFilesOnly": "仅限未映射的文件", "UnmonitorDeletedEpisodes": "取消监控已删除的集", - "UnmonitorSpecialsDescription": "取消监控所有特别节目而不改变其他集的监控状态", + "UnmonitorSpecialsEpisodesDescription": "取消监控所有特别节目而不改变其他集的监控状态", "UnmonitorDeletedEpisodesHelpText": "从磁盘删除的集将在 {appName} 中自动取消监控", - "UnmonitorSpecials": "取消监控特别节目", + "UnmonitorSpecialEpisodes": "取消监控特别节目", "UpdateAll": "更新全部", "UpdateAutomaticallyHelpText": "自动下载并安装更新。你还可以在“系统:更新”中安装", "UpdateSelected": "更新选择的内容", @@ -1297,12 +1297,12 @@ "MissingNoItems": "没有缺失项目", "MonitorExistingEpisodesDescription": "监控有文件或尚未播出的集", "MonitorFirstSeason": "第一季", - "MonitorNone": "无", - "MonitorNoneDescription": "没有集被监控", + "MonitorNoEpisodes": "无", + "MonitorNoEpisodesDescription": "没有集被监控", "MonitorPilotEpisode": "试播集", "MonitorSelected": "监控选中的", "MonitorSeries": "监控剧集", - "MonitoredHelpText": "下载本剧集中监控的集", + "MonitoredEpisodesHelpText": "下载本剧集中监控的集", "Month": "月", "MoveSeriesFoldersToRootFolder": "是否将剧集文件夹移动到 '{destinationRootFolder}' ?", "MustNotContainHelpText": "如版本包含一个或多个条件则丢弃(无视大小写)", @@ -1330,7 +1330,7 @@ "RestartLater": "稍后重启", "RestrictionsLoadError": "无法加载限制条件", "RssSync": "RSS同步", - "SearchForAllMissingConfirmationCount": "您确定要搜索所有{totalRecords}缺失的剧集吗?", + "SearchForAllMissingEpisodesConfirmationCount": "您确定要搜索所有{totalRecords}缺失的剧集吗?", "SearchIsNotSupportedWithThisIndexer": "该索引器不支持搜索", "Security": "安全", "SelectFolder": "选择文件夹", @@ -1349,7 +1349,7 @@ "SupportedAutoTaggingProperties": "{appName}支持自动标记规则的以下属性", "SupportedDownloadClientsMoreInfo": "若需要查看有关下载客户端的详细信息,请点击“更多信息”按钮。", "SupportedIndexers": "{appName}支持任何使用Newznab标准的索引器,以及下面列出的其他索引器。", - "SupportedLists": "{appName}支持将多个列表中的剧集导入数据库。", + "SupportedListsSeries": "{appName}支持将多个列表中的剧集导入数据库。", "TableOptionsButton": "表格选项按钮", "TheTvdb": "TheTVDB", "Tomorrow": "明天", @@ -1358,7 +1358,7 @@ "Umask": "掩码", "True": "是", "UpgradeUntil": "升级直至", - "UpgradeUntilHelpText": "一旦达到此质量,{appName} 将不再下载集的其他版本", + "UpgradeUntilEpisodeHelpText": "一旦达到此质量,{appName} 将不再下载集的其他版本", "UrlBaseHelpText": "对于反向代理支持,默认为空", "UseHardlinksInsteadOfCopy": "使用硬链接代替复制", "UsenetDelay": "Usenet延时", @@ -1372,7 +1372,7 @@ "AgeWhenGrabbed": "年龄(在被抓取后)", "BypassDelayIfAboveCustomFormatScoreMinimumScoreHelpText": "绕过首选协议延迟所需的最小自定义格式分数", "BypassDelayIfAboveCustomFormatScoreHelpText": "当发布版本的评分高于配置的最小自定义格式评分时,跳过延时", - "SearchForAllMissing": "搜索所有缺失的剧集", + "SearchForAllMissingEpisodes": "搜索所有缺失的剧集", "SearchForMissing": "搜索缺少", "SearchForQuery": "搜索{query}", "ImportUsingScriptHelpText": "使用脚本复制文件以进行导入(例如用于转码)", @@ -1430,7 +1430,7 @@ "LongDateFormat": "长时间格式", "Lowercase": "小写字母", "ProxyType": "代理类型", - "QualityLimitsHelpText": "根据剧集运行时间和文件中的集数自动调整限制。", + "QualityLimitsSeriesRuntimeHelpText": "根据剧集运行时间和文件中的集数自动调整限制。", "DeleteSeriesFoldersHelpText": "删除剧集文件夹及其所含文件", "EpisodeTitleRequiredHelpText": "如果单集标题为命名格式且单集标题为「待定」,则 在48 小时内禁用导入", "LibraryImportTipsDontUseDownloadsFolder": "不要使用该方法从下载客户端导入影片,本方法只限于导入现有的已整理的库,不能导入未整理的文件。", @@ -1452,7 +1452,7 @@ "ShowEpisodeInformation": "显示集信息", "ShowEpisodeInformationHelpText": "显示集号和标题", "ShowPath": "显示路径", - "QualityProfileInUse": "无法删除已指定给剧集、列表、收藏的质量配置", + "QualityProfileInUseSeriesListCollection": "无法删除已指定给剧集、列表、收藏的质量配置", "QualityProfiles": "媒体质量配置", "QualityProfilesLoadError": "无法加载质量配置文件", "RecentChanges": "最近修改", From 174deb2509b14047750110eadd24618817c91a20 Mon Sep 17 00:00:00 2001 From: Stevie Robinson <stevie.robinson@gmail.com> Date: Sun, 19 Nov 2023 20:54:18 +0100 Subject: [PATCH 128/136] Fix missing translation renames --- src/NzbDrone.Core/Localization/Core/fr.json | 2 +- src/NzbDrone.Core/Localization/Core/pt_BR.json | 2 +- src/NzbDrone.Core/Localization/Core/ru.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/NzbDrone.Core/Localization/Core/fr.json b/src/NzbDrone.Core/Localization/Core/fr.json index 8f058e188..cdc550840 100644 --- a/src/NzbDrone.Core/Localization/Core/fr.json +++ b/src/NzbDrone.Core/Localization/Core/fr.json @@ -1063,7 +1063,7 @@ "Location": "Emplacement", "LogFiles": "Fichiers journaux", "LogFilesLocation": "Les fichiers journaux se trouvent dans : {location}", - "SupportedLists": "{appName} prend en charge plusieurs listes pour importer des séries dans la base de données.", + "SupportedListsSeries": "{appName} prend en charge plusieurs listes pour importer des séries dans la base de données.", "MatchedToSeason": "Adapté à la saison", "MetadataSource": "Source des métadonnées", "MoveFiles": "Déplacer des fichiers", diff --git a/src/NzbDrone.Core/Localization/Core/pt_BR.json b/src/NzbDrone.Core/Localization/Core/pt_BR.json index 9466b47ef..2e3a8e705 100644 --- a/src/NzbDrone.Core/Localization/Core/pt_BR.json +++ b/src/NzbDrone.Core/Localization/Core/pt_BR.json @@ -1675,7 +1675,7 @@ "IndexerValidationCloudFlareCaptchaRequired": "Site protegido por CloudFlare CAPTCHA. É necessário um token CAPTCHA válido.", "IndexerValidationInvalidApiKey": "Chave de API inválida", "IndexerValidationJackettAllNotSupported": "Todos os endpoints de Jackett não são suportados. Adicione indexadores individualmente", - "IndexerValidationQueryNotSupported": "O indexador não oferece suporte à consulta atual. Verifique se as categorias e/ou busca por temporadas/episódios são suportadas. Verifique o registro para mais detalhes.", + "IndexerValidationQuerySeasonEpisodesNotSupported": "O indexador não oferece suporte à consulta atual. Verifique se as categorias e/ou busca por temporadas/episódios são suportadas. Verifique o registro para mais detalhes.", "IndexerValidationTestAbortedDueToError": "O teste foi abortado devido a um erro: {exceptionMessage}", "IndexerValidationUnableToConnect": "Não foi possível conectar-se ao indexador: {exceptionMessage}. Verifique o registro em torno deste erro para obter detalhes", "IndexerValidationUnableToConnectHttpError": "Não foi possível conectar-se ao indexador. Verifique suas configurações de DNS e certifique-se de que o IPv6 esteja funcionando ou desativado. {exceptionMessage}.", diff --git a/src/NzbDrone.Core/Localization/Core/ru.json b/src/NzbDrone.Core/Localization/Core/ru.json index f78efce54..f37e97d20 100644 --- a/src/NzbDrone.Core/Localization/Core/ru.json +++ b/src/NzbDrone.Core/Localization/Core/ru.json @@ -189,7 +189,7 @@ "AddImportListImplementation": "Добавить список импорта - {implementationName}", "AddIndexerImplementation": "Добавить индексатор - {implementationName}", "AddNewSeriesError": "Не удалось загрузить результат поиска, попробуйте еще раз.", - "AddListExclusionHelpText": "Запретить добавление серий в {appName} по спискам", + "AddListExclusionSeriesHelpText": "Запретить добавление серий в {appName} по спискам", "AddToDownloadQueue": "Добавить в очередь загрузки", "AddedDate": "Добавлено: {date}", "AddedToDownloadQueue": "Добавлено в очередь на скачивание", From 87ecbf39c1c0cc8a3a3f4ee1d1878b34ea49f6b8 Mon Sep 17 00:00:00 2001 From: Bogdan <mynameisbogdan@users.noreply.github.com> Date: Mon, 20 Nov 2023 00:47:27 +0200 Subject: [PATCH 129/136] Fixed: Autotagging Genres are case insensitive --- .../AutoTagging/Specifications/GenreSpecification.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/NzbDrone.Core/AutoTagging/Specifications/GenreSpecification.cs b/src/NzbDrone.Core/AutoTagging/Specifications/GenreSpecification.cs index 59173f312..032813f20 100644 --- a/src/NzbDrone.Core/AutoTagging/Specifications/GenreSpecification.cs +++ b/src/NzbDrone.Core/AutoTagging/Specifications/GenreSpecification.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Linq; using FluentValidation; +using NzbDrone.Common.Extensions; using NzbDrone.Core.Annotations; using NzbDrone.Core.Tv; using NzbDrone.Core.Validation; @@ -17,7 +18,7 @@ namespace NzbDrone.Core.AutoTagging.Specifications public class GenreSpecification : AutoTaggingSpecificationBase { - private static readonly GenreSpecificationValidator Validator = new GenreSpecificationValidator(); + private static readonly GenreSpecificationValidator Validator = new (); public override int Order => 1; public override string ImplementationName => "Genre"; @@ -27,7 +28,7 @@ namespace NzbDrone.Core.AutoTagging.Specifications protected override bool IsSatisfiedByWithoutNegate(Series series) { - return series.Genres.Any(genre => Value.Contains(genre)); + return series.Genres.Any(genre => Value.ContainsIgnoreCase(genre)); } public override NzbDroneValidationResult Validate() From 804a5921b3b620e2407d5d6a7fd69fb1fd9b0cbf Mon Sep 17 00:00:00 2001 From: Mark McDowall <mark@mcdowall.ca> Date: Sun, 19 Nov 2023 11:13:11 -0800 Subject: [PATCH 130/136] Fixed: Saving indexer, download client, etc settings --- src/Sonarr.Api.V3/ProviderControllerBase.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Sonarr.Api.V3/ProviderControllerBase.cs b/src/Sonarr.Api.V3/ProviderControllerBase.cs index 71230d36a..bc92070b1 100644 --- a/src/Sonarr.Api.V3/ProviderControllerBase.cs +++ b/src/Sonarr.Api.V3/ProviderControllerBase.cs @@ -91,7 +91,9 @@ namespace Sonarr.Api.V3 var providerDefinition = GetDefinition(providerResource, existingDefinition, true, !forceSave, false); // Comparing via JSON string to eliminate the need for every provider implementation to implement equality checks. - var hasDefinitionChanged = STJson.ToJson(existingDefinition) != STJson.ToJson(providerDefinition); + // Compare settings separately because they are not serialized with the definition. + var hasDefinitionChanged = STJson.ToJson(existingDefinition) != STJson.ToJson(providerDefinition) || + STJson.ToJson(existingDefinition.Settings) != STJson.ToJson(providerDefinition.Settings); // Only test existing definitions if it is enabled and forceSave isn't set or the definition has changed. if (providerDefinition.Enable && (!forceSave || hasDefinitionChanged)) From d2118870501fd3497b1a420e555c04aec2f1e6ee Mon Sep 17 00:00:00 2001 From: Mark McDowall <mark@mcdowall.ca> Date: Sun, 19 Nov 2023 20:45:37 -0800 Subject: [PATCH 131/136] New: Add more exception release groups Closes #6146 --- .../ParserTests/ReleaseGroupParserFixture.cs | 9 +++++++++ src/NzbDrone.Core/Parser/Parser.cs | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/NzbDrone.Core.Test/ParserTests/ReleaseGroupParserFixture.cs b/src/NzbDrone.Core.Test/ParserTests/ReleaseGroupParserFixture.cs index ebdf82704..dc7c38ab5 100644 --- a/src/NzbDrone.Core.Test/ParserTests/ReleaseGroupParserFixture.cs +++ b/src/NzbDrone.Core.Test/ParserTests/ReleaseGroupParserFixture.cs @@ -73,6 +73,15 @@ namespace NzbDrone.Core.Test.ParserTests [TestCase("Series Title S02E03 Title 4k to 1080p DSNP WEBrip x265 DDP 5 1 Releaser[SEV]", "SEV")] [TestCase("Series Title Season 01 S01 1080p AMZN UHD WebRip x265 DDP 5.1 Atmos Releaser-SEV", "SEV")] [TestCase("Series Title - S01.E06 - Title 1080p AMZN WebRip x265 DDP 5.1 Atmos Releaser [SEV]", "SEV")] + [TestCase("Grey's Anatomy (2005) - S01E01 - A Hard Day's Night (1080p DSNP WEB-DL x265 Garshasp).mkv", "Garshasp")] + [TestCase("Marvel's Agent Carter (2015) - S02E04 - Smoke & Mirrors (1080p BluRay x265 Kappa).mkv", "Kappa")] + [TestCase("Snowpiercer (2020) - S02E03 - A Great Odyssey (1080p BluRay x265 Kappa).mkv", "Kappa")] + [TestCase("Enaaya (2019) - S01E01 - Episode 1 (1080p WEB-DL x265 Natty).mkv", "Natty")] + [TestCase("SpongeBob SquarePants (1999) - S03E01-E02 - Mermaid Man and Barnacle Boy IV & Doing Time (1080p AMZN WEB-DL x265 RCVR).mkv", "RCVR")] + [TestCase("Invincible (2021) - S01E02 - Here Goes Nothing (1080p WEB-DL x265 SAMPA).mkv", "SAMPA")] + [TestCase("The Bad Batch (2021) - S01E01 - Aftermath (1080p DSNP WEB-DL x265 YOGI).mkv", "YOGI")] + [TestCase("Line of Duty (2012) - S01E01 - Episode 1 (1080p BluRay x265 r00t).mkv", "r00t")] + [TestCase("Rich & Shameless - S01E01 - Girls Gone Wild Exposed (720p x265 EDGE2020).mkv", "EDGE2020")] public void should_parse_exception_release_group(string title, string expected) { Parser.Parser.ParseReleaseGroup(title).Should().Be(expected); diff --git a/src/NzbDrone.Core/Parser/Parser.cs b/src/NzbDrone.Core/Parser/Parser.cs index 52c042e45..df0cb00a7 100644 --- a/src/NzbDrone.Core/Parser/Parser.cs +++ b/src/NzbDrone.Core/Parser/Parser.cs @@ -522,7 +522,7 @@ namespace NzbDrone.Core.Parser private static readonly Regex ExceptionReleaseGroupRegexExact = new Regex(@"(?<releasegroup>(?:D\-Z0N3|Fight-BB|VARYG)\b)", RegexOptions.IgnoreCase | RegexOptions.Compiled); // groups whose releases end with RlsGroup) or RlsGroup] - private static readonly Regex ExceptionReleaseGroupRegex = new Regex(@"(?<releasegroup>(Silence|afm72|Panda|Ghost|MONOLITH|Tigole|Joy|ImE|UTR|t3nzin|Anime Time|Project Angel|Hakata Ramen|HONE|Vyndros|SEV)(?=\]|\)))", RegexOptions.IgnoreCase | RegexOptions.Compiled); + private static readonly Regex ExceptionReleaseGroupRegex = new Regex(@"(?<releasegroup>(Silence|afm72|Panda|Ghost|MONOLITH|Tigole|Joy|ImE|UTR|t3nzin|Anime Time|Project Angel|Hakata Ramen|HONE|Vyndros|SEV|Garshasp|Kappa|Natty|RCVR|SAMPA|YOGI|r00t|EDGE2020)(?=\]|\)))", RegexOptions.IgnoreCase | RegexOptions.Compiled); private static readonly Regex YearInTitleRegex = new Regex(@"^(?<title>.+?)[-_. ]+?[\(\[]?(?<year>\d{4})[\]\)]?", RegexOptions.IgnoreCase | RegexOptions.Compiled); From 65cb1ccafd54479fa3fca1f1eaa4b96222b0176b Mon Sep 17 00:00:00 2001 From: Mark McDowall <mark@mcdowall.ca> Date: Sun, 19 Nov 2023 21:02:28 -0800 Subject: [PATCH 132/136] Fixed force saving provider triggering testing --- src/Sonarr.Api.V3/ProviderControllerBase.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Sonarr.Api.V3/ProviderControllerBase.cs b/src/Sonarr.Api.V3/ProviderControllerBase.cs index bc92070b1..ca1082609 100644 --- a/src/Sonarr.Api.V3/ProviderControllerBase.cs +++ b/src/Sonarr.Api.V3/ProviderControllerBase.cs @@ -95,8 +95,8 @@ namespace Sonarr.Api.V3 var hasDefinitionChanged = STJson.ToJson(existingDefinition) != STJson.ToJson(providerDefinition) || STJson.ToJson(existingDefinition.Settings) != STJson.ToJson(providerDefinition.Settings); - // Only test existing definitions if it is enabled and forceSave isn't set or the definition has changed. - if (providerDefinition.Enable && (!forceSave || hasDefinitionChanged)) + // Only test existing definitions if it is enabled and forceSave isn't set and the definition has changed. + if (providerDefinition.Enable && !forceSave && hasDefinitionChanged) { Test(providerDefinition, true); } From 4e048bf4999230f9c75d98ef2d8a1201d5ed68ed Mon Sep 17 00:00:00 2001 From: Bogdan <mynameisbogdan@users.noreply.github.com> Date: Mon, 20 Nov 2023 07:08:17 +0200 Subject: [PATCH 133/136] Wrap long lines in description lists --- .../DescriptionList/DescriptionListItemDescription.css | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/frontend/src/Components/DescriptionList/DescriptionListItemDescription.css b/frontend/src/Components/DescriptionList/DescriptionListItemDescription.css index b23415a76..786123fb7 100644 --- a/frontend/src/Components/DescriptionList/DescriptionListItemDescription.css +++ b/frontend/src/Components/DescriptionList/DescriptionListItemDescription.css @@ -1,9 +1,7 @@ -.description { - line-height: $lineHeight; -} - .description { margin-left: 0; + line-height: $lineHeight; + overflow-wrap: break-word; } @media (min-width: 768px) { From 749d841d3a335ae4bdf8deafcb8573e74888a181 Mon Sep 17 00:00:00 2001 From: Weblate <noreply@weblate.org> Date: Tue, 21 Nov 2023 10:58:09 +0000 Subject: [PATCH 134/136] Multiple Translations updated by Weblate ignore-downstream Co-authored-by: Havok Dan <havokdan@yahoo.com.br> Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/pt_BR/ Translation: Servarr/Sonarr --- .../Localization/Core/pt_BR.json | 32 +++++++++++++------ 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/src/NzbDrone.Core/Localization/Core/pt_BR.json b/src/NzbDrone.Core/Localization/Core/pt_BR.json index 2e3a8e705..670169662 100644 --- a/src/NzbDrone.Core/Localization/Core/pt_BR.json +++ b/src/NzbDrone.Core/Localization/Core/pt_BR.json @@ -85,7 +85,7 @@ "RemotePathMappingLocalFolderMissingHealthCheckMessage": "O cliente de download remoto {downloadClientName} coloca os downloads em {path}, mas este diretório parece não existir. Mapeamento de caminho remoto provavelmente ausente ou incorreto.", "RemotePathMappingWrongOSPathHealthCheckMessage": "O cliente de download remoto {downloadClientName} coloca os downloads em {path}, mas este não é um caminho {osName} válido. Revise seus mapeamentos de caminho remoto e baixe as configurações do cliente.", "UpdateStartupNotWritableHealthCheckMessage": "Não é possível instalar a atualização porque a pasta de inicialização '{startupFolder}' não pode ser gravada pelo usuário '{userName}'.", - "UpdateStartupTranslocationHealthCheckMessage": "Não é possível instalar a atualização porque a pasta de inicialização '{startupFolder}' está em uma pasta de translocação de aplicativo.", + "UpdateStartupTranslocationHealthCheckMessage": "Não é possível instalar a atualização porque a pasta de inicialização '{startupFolder}' está em uma pasta de translocação do App.", "BlocklistReleaseSearchEpisodeAgainHelpText": "Inicia uma busca por este episódio novamente e impede que esta versão seja capturada novamente", "BlocklistReleases": "Lançamentos na lista de bloqueio", "CloneCondition": "Clonar Condição", @@ -289,7 +289,7 @@ "StartupDirectory": "Diretório de inicialização", "Status": "Estado", "TestAll": "Testar Tudo", - "TheLogLevelDefault": "O padrão do nível de log é 'Info' e pode ser alterado em [Configurações gerais](/configurações /geral)", + "TheLogLevelDefault": "O nível de registro é padronizado como 'Info' e pode ser alterado em [Configurações Gerais](/settings/general)", "Time": "Horário", "TotalSpace": "Espaço Total", "Twitter": "Twitter", @@ -467,10 +467,10 @@ "ChangeFileDateHelpText": "Alterar a data do arquivo na importação/rescan", "ChmodFolder": "chmod Pasta", "ChmodFolderHelpText": "Octal, aplicado durante a importação/renomeação de pastas e arquivos de mídia (sem bits de execução)", - "ChmodFolderHelpTextWarning": "Isso só funciona se o usuário que estiver executando o sonarr for o proprietário do arquivo. É melhor garantir que o cliente de download defina as permissões corretamente.", + "ChmodFolderHelpTextWarning": "Isso só funciona se o usuário que executa {appName} for o proprietário do arquivo. É melhor garantir que o cliente de download defina as permissões corretamente.", "ChownGroup": "chown Grupo", "ChownGroupHelpText": "Nome do grupo ou gid. Use gid para sistemas de arquivos remotos.", - "ChownGroupHelpTextWarning": "Isso só funciona se o usuário que estiver executando o sonarr for o proprietário do arquivo. É melhor garantir que o cliente de download use o mesmo grupo do sonarr.", + "ChownGroupHelpTextWarning": "Isso só funciona se o usuário que executa {appName} for o proprietário do arquivo. É melhor garantir que o cliente de download use o mesmo grupo que {appName}.", "ClientPriority": "Prioridade do Cliente", "Clone": "Clonar", "CloneIndexer": "Clonar Indexador", @@ -739,7 +739,7 @@ "RecyclingBinCleanup": "Esvaziar Lixeira", "RecyclingBinCleanupHelpText": "Defina como 0 para desativar a limpeza automática", "RecyclingBinCleanupHelpTextWarning": "Os arquivos na lixeira mais antigos do que o número de dias selecionado serão limpos automaticamente", - "RecyclingBinHelpText": "Os arquivos do episódio irão para cá quando excluídos, em vez de serem excluídos permanentemente", + "RecyclingBinHelpText": "Os arquivos irão para cá quando excluídos, em vez de serem excluídos permanentemente", "AbsoluteEpisodeNumber": "Número Absoluto do Episódio", "AddAutoTagError": "Não foi possível adicionar uma nova tag automática, por favor, tente novamente.", "AnalyseVideoFilesHelpText": "Extraia informações de vídeo, como resolução, tempo de execução e informações de codec de arquivos. Isso requer que o {appName} leia partes do arquivo que podem causar alta atividade no disco ou na rede durante as varreduras.", @@ -1268,7 +1268,7 @@ "OverrideGrabNoQuality": "A qualidade deve ser selecionada", "OverrideGrabNoSeries": "A série deve ser selecionada", "Parse": "Analisar", - "ParseModalHelpTextDetails": "{appName} irá tentar analisar o título e mostrará para você os detalhes sobre isso", + "ParseModalHelpTextDetails": "{appName} tentará analisar o título e mostrar detalhes sobre ele", "RecentChanges": "Mudanças Recentes", "ReleaseRejected": "Lançamento Rejeitado", "ReleaseSceneIndicatorAssumingScene": "Assumindo a Numeração da Scene.", @@ -1365,7 +1365,7 @@ "Files": "Arquivos", "HistorySeason": "Exibir histórico para esta temporada", "HistoryModalHeaderSeason": "Histórico - {season}", - "InteractiveSearchModalHeader": "Pesquisa interativa", + "InteractiveSearchModalHeader": "Pesquisa Interativa", "InteractiveSearchModalHeaderSeason": "Pesquisa interativa - {season}", "InteractiveSearchSeason": "Pesquisa interativa para todos os episódios desta temporada", "InvalidUILanguage": "A interface está configurada com um idioma inválido, corrija-o e salve as configurações", @@ -1685,6 +1685,20 @@ "IndexerHDBitsSettingsCategoriesHelpText": "se não for especificado, todas as opções serão usadas.", "IndexerHDBitsSettingsCodecs": "Codecs", "IndexerHDBitsSettingsCodecsHelpText": "Se não for especificado, todas as opções serão usadas.", - "IndexerHDBitsSettingsMediums": "Mediums", - "IndexerHDBitsSettingsMediumsHelpText": "se não for especificado, todas as opções serão usadas." + "IndexerHDBitsSettingsMediums": "Meio", + "IndexerHDBitsSettingsMediumsHelpText": "se não for especificado, todas as opções serão usadas.", + "ClearBlocklist": "Limpar lista de bloqueio", + "MonitorRecentEpisodesDescription": "Monitore episódios exibidos nos últimos 90 dias e episódios futuros", + "ClearBlocklistMessageText": "Tem certeza de que deseja limpar todos os itens da lista de bloqueio?", + "PasswordConfirmation": "Confirmação Da Senha", + "MonitorPilotEpisodeDescription": "Monitore apenas o primeiro episódio da primeira temporada", + "MonitorNoNewSeasonsDescription": "Não monitore nenhuma nova temporada automaticamente", + "MonitorAllSeasons": "Todas as Temporadas", + "MonitorAllSeasonsDescription": "Monitorar todas as novas temporadas automaticamente", + "MonitorLastSeason": "Última Temporada", + "MonitorLastSeasonDescription": "Monitorar todos os episódios da última temporada", + "MonitorNewSeasons": "Monitorar Novas Temporadas", + "MonitorNewSeasonsHelpText": "Quais novas temporadas devem ser monitoradas automaticamente", + "MonitorRecentEpisodes": "Episódios Recentes", + "AuthenticationRequiredPasswordConfirmationHelpTextWarning": "Confirme a nova senha" } From 75420d4a893d016c6d569604d09d9e84159d0ddb Mon Sep 17 00:00:00 2001 From: Stevie Robinson <stevie.robinson@gmail.com> Date: Sat, 25 Nov 2023 21:22:37 +0100 Subject: [PATCH 135/136] Fix missing translation key MonitorNone --- .../SeriesMonitorNewItemsOptionsPopoverContent.js | 2 +- frontend/src/Utilities/Series/monitorNewItemsOptions.js | 2 +- src/NzbDrone.Core/Localization/Core/en.json | 7 ++++--- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/frontend/src/AddSeries/SeriesMonitorNewItemsOptionsPopoverContent.js b/frontend/src/AddSeries/SeriesMonitorNewItemsOptionsPopoverContent.js index 164b75e5c..c70ec0dec 100644 --- a/frontend/src/AddSeries/SeriesMonitorNewItemsOptionsPopoverContent.js +++ b/frontend/src/AddSeries/SeriesMonitorNewItemsOptionsPopoverContent.js @@ -12,7 +12,7 @@ function SeriesMonitorNewItemsOptionsPopoverContent() { /> <DescriptionListItem - title={translate('MonitorNone')} + title={translate('MonitorNoNewSeasons')} data={translate('MonitorNoNewSeasonsDescription')} /> </DescriptionList> diff --git a/frontend/src/Utilities/Series/monitorNewItemsOptions.js b/frontend/src/Utilities/Series/monitorNewItemsOptions.js index 184d60397..49c948c7f 100644 --- a/frontend/src/Utilities/Series/monitorNewItemsOptions.js +++ b/frontend/src/Utilities/Series/monitorNewItemsOptions.js @@ -10,7 +10,7 @@ const monitorNewItemsOptions = [ { key: 'none', get value() { - return translate('MonitorNone'); + return translate('MonitorNoNewSeasons'); } } ]; diff --git a/src/NzbDrone.Core/Localization/Core/en.json b/src/NzbDrone.Core/Localization/Core/en.json index 918ba4cd1..2ad0187ad 100644 --- a/src/NzbDrone.Core/Localization/Core/en.json +++ b/src/NzbDrone.Core/Localization/Core/en.json @@ -113,8 +113,8 @@ "AuthenticationMethodHelpTextWarning": "Please select a valid authentication method", "AuthenticationRequired": "Authentication Required", "AuthenticationRequiredHelpText": "Change which requests authentication is required for. Do not change unless you understand the risks.", - "AuthenticationRequiredPasswordHelpTextWarning": "Enter a new password", "AuthenticationRequiredPasswordConfirmationHelpTextWarning": "Confirm new password", + "AuthenticationRequiredPasswordHelpTextWarning": "Enter a new password", "AuthenticationRequiredUsernameHelpTextWarning": "Enter a new username", "AuthenticationRequiredWarning": "To prevent remote access without authentication, {appName} now requires authentication to be enabled. You can optionally disable authentication from local addresses.", "AutoAdd": "Auto Add", @@ -991,10 +991,11 @@ "MonitorLastSeasonDescription": "Monitor all episodes of the last season", "MonitorMissingEpisodes": "Missing Episodes", "MonitorMissingEpisodesDescription": "Monitor episodes that do not have files or have not aired yet", - "MonitorNoEpisodes": "None", - "MonitorNoEpisodesDescription": "No episodes will be monitored", "MonitorNewSeasons": "Monitor New Seasons", "MonitorNewSeasonsHelpText": "Which new seasons should be monitored automatically", + "MonitorNoEpisodes": "None", + "MonitorNoEpisodesDescription": "No episodes will be monitored", + "MonitorNoNewSeasons": "No New Seasons", "MonitorNoNewSeasonsDescription": "Do not monitor any new seasons automatically", "MonitorPilotEpisode": "Pilot Episode", "MonitorPilotEpisodeDescription": "Only monitor the first episode of the first season", From c6ad2396bb98dc8eb1ad47bf5d066b227a47f8b5 Mon Sep 17 00:00:00 2001 From: Stevie Robinson <stevie.robinson@gmail.com> Date: Sat, 25 Nov 2023 21:23:00 +0100 Subject: [PATCH 136/136] New: Remove Defunct Boxcar notifications --- .../Notifications/Boxcar/Boxcar.cs | 73 -------------- .../Notifications/Boxcar/BoxcarException.cs | 18 ---- .../Notifications/Boxcar/BoxcarProxy.cs | 97 ------------------- .../Notifications/Boxcar/BoxcarSettings.cs | 28 ------ 4 files changed, 216 deletions(-) delete mode 100644 src/NzbDrone.Core/Notifications/Boxcar/Boxcar.cs delete mode 100644 src/NzbDrone.Core/Notifications/Boxcar/BoxcarException.cs delete mode 100644 src/NzbDrone.Core/Notifications/Boxcar/BoxcarProxy.cs delete mode 100644 src/NzbDrone.Core/Notifications/Boxcar/BoxcarSettings.cs diff --git a/src/NzbDrone.Core/Notifications/Boxcar/Boxcar.cs b/src/NzbDrone.Core/Notifications/Boxcar/Boxcar.cs deleted file mode 100644 index ef5850637..000000000 --- a/src/NzbDrone.Core/Notifications/Boxcar/Boxcar.cs +++ /dev/null @@ -1,73 +0,0 @@ -using System.Collections.Generic; -using FluentValidation.Results; -using NzbDrone.Common.Extensions; - -namespace NzbDrone.Core.Notifications.Boxcar -{ - public class Boxcar : NotificationBase<BoxcarSettings> - { - private readonly IBoxcarProxy _proxy; - - public Boxcar(IBoxcarProxy proxy) - { - _proxy = proxy; - } - - public override string Link => "https://boxcar.io/client"; - public override string Name => "Boxcar"; - - public override void OnGrab(GrabMessage grabMessage) - { - _proxy.SendNotification(EPISODE_GRABBED_TITLE, grabMessage.Message, Settings); - } - - public override void OnDownload(DownloadMessage message) - { - _proxy.SendNotification(EPISODE_DOWNLOADED_TITLE, message.Message, Settings); - } - - public override void OnEpisodeFileDelete(EpisodeDeleteMessage deleteMessage) - { - _proxy.SendNotification(EPISODE_DELETED_TITLE, deleteMessage.Message, Settings); - } - - public override void OnSeriesAdd(SeriesAddMessage message) - { - _proxy.SendNotification(SERIES_ADDED_TITLE, message.Message, Settings); - } - - public override void OnSeriesDelete(SeriesDeleteMessage deleteMessage) - { - _proxy.SendNotification(SERIES_DELETED_TITLE, deleteMessage.Message, Settings); - } - - public override void OnHealthIssue(HealthCheck.HealthCheck message) - { - _proxy.SendNotification(HEALTH_ISSUE_TITLE, message.Message, Settings); - } - - public override void OnHealthRestored(HealthCheck.HealthCheck previousCheck) - { - _proxy.SendNotification(HEALTH_RESTORED_TITLE, $"The following issue is now resolved: {previousCheck.Message}", Settings); - } - - public override void OnApplicationUpdate(ApplicationUpdateMessage message) - { - _proxy.SendNotification(APPLICATION_UPDATE_TITLE, message.Message, Settings); - } - - public override void OnManualInteractionRequired(ManualInteractionRequiredMessage message) - { - _proxy.SendNotification(MANUAL_INTERACTION_REQUIRED_TITLE, message.Message, Settings); - } - - public override ValidationResult Test() - { - var failures = new List<ValidationFailure>(); - - failures.AddIfNotNull(_proxy.Test(Settings)); - - return new ValidationResult(failures); - } - } -} diff --git a/src/NzbDrone.Core/Notifications/Boxcar/BoxcarException.cs b/src/NzbDrone.Core/Notifications/Boxcar/BoxcarException.cs deleted file mode 100644 index 6108d4aab..000000000 --- a/src/NzbDrone.Core/Notifications/Boxcar/BoxcarException.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; -using NzbDrone.Common.Exceptions; - -namespace NzbDrone.Core.Notifications.Boxcar -{ - public class BoxcarException : NzbDroneException - { - public BoxcarException(string message) - : base(message) - { - } - - public BoxcarException(string message, Exception innerException, params object[] args) - : base(message, innerException, args) - { - } - } -} diff --git a/src/NzbDrone.Core/Notifications/Boxcar/BoxcarProxy.cs b/src/NzbDrone.Core/Notifications/Boxcar/BoxcarProxy.cs deleted file mode 100644 index fda8dc71e..000000000 --- a/src/NzbDrone.Core/Notifications/Boxcar/BoxcarProxy.cs +++ /dev/null @@ -1,97 +0,0 @@ -using System; -using System.Net; -using FluentValidation.Results; -using NLog; -using NzbDrone.Common.EnvironmentInfo; -using NzbDrone.Common.Http; - -namespace NzbDrone.Core.Notifications.Boxcar -{ - public interface IBoxcarProxy - { - void SendNotification(string title, string message, BoxcarSettings settings); - ValidationFailure Test(BoxcarSettings settings); - } - - public class BoxcarProxy : IBoxcarProxy - { - private const string URL = "https://new.boxcar.io/api/notifications"; - - private readonly IHttpClient _httpClient; - private readonly Logger _logger; - - public BoxcarProxy(IHttpClient httpClient, Logger logger) - { - _httpClient = httpClient; - _logger = logger; - } - - public void SendNotification(string title, string message, BoxcarSettings settings) - { - try - { - ProcessNotification(title, message, settings); - } - catch (BoxcarException ex) - { - _logger.Error(ex, "Unable to send message"); - throw new BoxcarException("Unable to send Boxcar notifications"); - } - } - - public ValidationFailure Test(BoxcarSettings settings) - { - try - { - const string title = "Test Notification"; - const string body = "This is a test message from Sonarr"; - - SendNotification(title, body, settings); - return null; - } - catch (HttpException ex) - { - if (ex.Response.StatusCode == HttpStatusCode.Unauthorized) - { - _logger.Error(ex, "Access Token is invalid"); - return new ValidationFailure("Token", "Access Token is invalid"); - } - - _logger.Error(ex, "Unable to send test message"); - return new ValidationFailure("Token", "Unable to send test message"); - } - catch (Exception ex) - { - _logger.Error(ex, "Unable to send test message"); - return new ValidationFailure("", "Unable to send test message"); - } - } - - private void ProcessNotification(string title, string message, BoxcarSettings settings) - { - try - { - var requestBuilder = new HttpRequestBuilder(URL).Post(); - - var request = requestBuilder.AddFormParameter("user_credentials", settings.Token) - .AddFormParameter("notification[title]", title) - .AddFormParameter("notification[long_message]", message) - .AddFormParameter("notification[source_name]", BuildInfo.AppName) - .AddFormParameter("notification[icon_url]", "https://raw.githubusercontent.com/Sonarr/Sonarr/develop/Logo/64.png") - .Build(); - - _httpClient.Post(request); - } - catch (HttpException ex) - { - if (ex.Response.StatusCode == HttpStatusCode.Unauthorized) - { - _logger.Error(ex, "Access Token is invalid"); - throw; - } - - throw new BoxcarException("Unable to send text message: " + ex.Message, ex); - } - } - } -} diff --git a/src/NzbDrone.Core/Notifications/Boxcar/BoxcarSettings.cs b/src/NzbDrone.Core/Notifications/Boxcar/BoxcarSettings.cs deleted file mode 100644 index b6b9fd96d..000000000 --- a/src/NzbDrone.Core/Notifications/Boxcar/BoxcarSettings.cs +++ /dev/null @@ -1,28 +0,0 @@ -using FluentValidation; -using NzbDrone.Core.Annotations; -using NzbDrone.Core.ThingiProvider; -using NzbDrone.Core.Validation; - -namespace NzbDrone.Core.Notifications.Boxcar -{ - public class BoxcarSettingsValidator : AbstractValidator<BoxcarSettings> - { - public BoxcarSettingsValidator() - { - RuleFor(c => c.Token).NotEmpty(); - } - } - - public class BoxcarSettings : IProviderConfig - { - private static readonly BoxcarSettingsValidator Validator = new BoxcarSettingsValidator(); - - [FieldDefinition(0, Label = "Access Token", Privacy = PrivacyLevel.ApiKey, HelpText = "Your Access Token, from your Boxcar account settings: https://new.boxcar.io/account/edit", HelpLink = "https://new.boxcar.io/account/edit")] - public string Token { get; set; } - - public NzbDroneValidationResult Validate() - { - return new NzbDroneValidationResult(Validator.Validate(this)); - } - } -}