1
0
mirror of https://github.com/Sonarr/Sonarr.git synced 2026-04-17 21:26:13 -04:00

Compare commits

...

61 Commits

Author SHA1 Message Date
Weblate
dac69445e4 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Fixer <ygj59783@zslsz.com>
Co-authored-by: Jason54 <jason54700.jg@gmail.com>
Co-authored-by: Michael5564445 <michaelvelosk@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/ro/
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/uk/
Translation: Servarr/Sonarr
2024-04-05 23:13:02 -07:00
Qstick
aca10f6f4f Fixed: Skip move when source and destination are the same
ignore-downstream

Co-Authored-By: Colin Hebert <makkhdyn@gmail.com>
(cherry picked from commit 7a5ae56a96700f401726ac80b3031a25207d8f75)
2024-04-05 23:11:37 -07:00
Mark McDowall
74cdf01e49 New: Set 'Release Type' during Manual Import
Closes #6681
2024-04-06 02:11:17 -04:00
Mark McDowall
a169ebff2a Fixed: Sending ntfy.sh notifications with unicode characters
Closes #6679
2024-04-06 02:11:03 -04:00
fireph
7fc3bebc91 New: Footnote to indicate some renaming tokens support truncation 2024-04-06 02:10:42 -04:00
Till Krüss
e672996dbb Improve text for file deleted through UI/API 2024-04-06 02:09:55 -04:00
Stevie Robinson
238ba85f0a New: Informational text on Custom Formats modal 2024-04-06 02:08:57 -04:00
Cuki
1562d3bae3 Fixed: Use widely supported display mode for PWA 2024-04-06 02:08:08 -04:00
Jendrik Weise
7776ec9955 Reimport files imported prematurely during script import 2024-04-05 23:07:38 -07:00
Jendrik Weise
af5a681ab7 Fix ignoring title based on pre-rename episodefile 2024-04-05 23:07:38 -07:00
Jendrik Weise
0a7f3a12c2 Do not remove all extras when script importing 2024-04-05 23:07:38 -07:00
Jendrik Weise
2ef46e5b90 Fix incorrect subtitle copy regex 2024-04-05 23:07:38 -07:00
Mark McDowall
6003ca1696 Fixed: Deleted episodes not being unmonitored when series folder has been deleted
Closes #6678
2024-04-05 23:07:07 -07:00
Mark McDowall
0937ee6fef Fixed: Path parsing incorrectly treating series title as episode number 2024-04-05 23:06:56 -07:00
Mark McDowall
60ee7cc716 Fixed: Cleanse BHD RSS key in log files
Closes #6666
2024-04-06 02:06:35 -04:00
Mark McDowall
4e83820511 Bump version to 4.0.3 2024-03-31 21:52:19 -07:00
Sonarr
5a66b949cf Automated API Docs update
ignore-downstream
2024-03-31 21:43:33 -07:00
Weblate
f010f56290 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Fixer <ygj59783@zslsz.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: fordas <fordas15@gmail.com>
Co-authored-by: 王锋 <17611382361@163.com>
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/es/
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
2024-03-31 21:43:09 -07:00
Louis R
060b789bc6 Fixed: Exceptions when checking for routable IPv4 addresses 2024-03-28 01:31:28 -04:00
Bogdan
7353fe479d New: Allow HEAD requests to ping endpoint
Closes #6656
2024-03-28 01:30:45 -04:00
Alex Cortelyou
1ec1ce58e9 New: Add additional fields to Webhook Manual Interaction Required events 2024-03-28 01:30:21 -04:00
Stevie Robinson
35d0e6a6f8 Fixed: Handling torrents with relative path in rTorrent 2024-03-28 01:29:15 -04:00
Carlos Gustavo Sarmiento
588372fd95 Fixed: qBittorrent not correctly handling retention during testing 2024-03-28 01:28:41 -04:00
Bogdan
13c925b341 New: Advanced settings toggle in import list, notification and download client modals 2024-03-27 22:27:51 -07:00
iceypotato
1335efd487 New: My Anime List import list
Closes #5148
2024-03-27 22:27:34 -07:00
Mark McDowall
d338425951 Fixed: Use custom formats from import during rename 2024-03-27 22:27:25 -07:00
Mark McDowall
fc6494c569 Fixed: Task with removed series causing error 2024-03-27 22:27:14 -07:00
Weblate
c403b2cdd5 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Altair <villagermd@outlook.com>
Co-authored-by: Dani Talens <databio@gmail.com>
Co-authored-by: Fixer <ygj59783@zslsz.com>
Co-authored-by: Stanislav <prekop3@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: fordas <fordas15@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/ca/
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/sk/
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/tr/
Translation: Servarr/Sonarr
2024-03-27 22:27:07 -07:00
Weblate
cf3d51bab2 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Casselluu <jack10193@163.com>
Co-authored-by: Gianmarco Novelli <rinogaetano94@live.it>
Co-authored-by: Jason54 <jason54700.jg@gmail.com>
Co-authored-by: MadaxDeLuXe <madaxdeluxe@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: infoaitek24 <info@aitekph.com>
Co-authored-by: reloxx <reloxx@interia.pl>
Co-authored-by: shimmyx <shimmygodx@gmail.com>
Co-authored-by: vfaergestad <vgf@hotmail.no>
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/ca/
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/it/
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/nb_NO/
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/zh_CN/
Translation: Servarr/Sonarr
2024-03-21 21:21:30 -07:00
Mark McDowall
dec3fc6889 Fixed: Don't add series from import list with no matched TVDB ID 2024-03-22 00:21:04 -04:00
Mark McDowall
40bac23698 New: Support parsing season number from season folder when importing
Closes #903
2024-03-21 21:20:49 -07:00
Mark McDowall
88de927435 Fixed: Plex Watchlist import list 2024-03-21 21:20:27 -07:00
Mark McDowall
29204c93a3 New: Parsing multi-episode file with two and three digit episode numbers
Closes #6631
2024-03-21 21:20:13 -07:00
Mark McDowall
c641733781 Fixed: Task progress messages in the UI
Closes #6632
2024-03-21 21:20:08 -07:00
Weblate
58de0310fd Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Gianmarco Novelli <rinogaetano94@live.it>
Co-authored-by: Jason54 <jason54700.jg@gmail.com>
Co-authored-by: MadaxDeLuXe <madaxdeluxe@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: infoaitek24 <info@aitekph.com>
Co-authored-by: reloxx <reloxx@interia.pl>
Co-authored-by: vfaergestad <vgf@hotmail.no>
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/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/it/
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/nb_NO/
Translation: Servarr/Sonarr
2024-03-21 21:20:01 -07:00
Bogdan
172b1a82d1 Sort series by title in task name 2024-03-21 21:19:23 -07:00
Bogdan
e14568adef Ensure not allowed cursor is shown for disabled select inputs 2024-03-21 21:19:23 -07:00
Weblate
381ce61aef Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Dennis Langthjem <dennis@langthjem.dk>
Co-authored-by: DimitriDR <dimitridroeck@gmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Ihor Mudryi <mudryy33@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: fordas <fordas15@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/da/
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_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/uk/
Translation: Servarr/Sonarr
2024-03-13 21:49:22 -07:00
Mark McDowall
9f705e4161 Fixed: Release push with only Magnet URL
Closes #6622
2024-03-13 21:47:50 -07:00
Mark McDowall
063dba22a8 Fixed: Disabled select option still selectable 2024-03-13 21:47:33 -07:00
Mark McDowall
6d552f2a60 New: Show Series title and season number after task name when applicable
Closes #6601
2024-03-13 21:47:22 -07:00
Mark McDowall
4d4d63921b Add notification for build success/failures 2024-03-14 00:47:01 -04:00
Alan Collins
6584d95331 New: Update Custom Format renaming token to allow excluding specific formats
Closes #6615
2024-03-14 00:46:33 -04:00
Bogdan
86034beccd Bump ImageSharp, Polly, DryIoc, STJson, WindowsServices 2024-03-13 21:44:23 -07:00
Mark McDowall
4aa56e3f91 Fixed: Parsing of some French and Spanish anime releases 2024-03-13 21:44:07 -07:00
Stevie Robinson
2ec071a5ec Update release profile download client warning 2024-03-09 23:54:21 -05:00
Alan Collins
d86aeb7472 New: Release Hash renaming token
Closes #6570
2024-03-09 23:54:06 -05:00
Alan Collins
48cb5d2271 New: 'Custom Format: Format Name' rename token 2024-03-09 23:53:02 -05:00
bakerboy448
a0329adeba Improve single file detected as full season messaging 2024-03-09 23:51:29 -05:00
Bogdan
89bef4af99 New: Wider modal for Interactive Search and Manual Import 2024-03-09 23:50:45 -05:00
Mark McDowall
a12cdb34bc Fixed: Error sending Manual Interaction Required notification 2024-03-07 18:11:36 -08:00
Bogdan
13e29bd257 Prevent NullRef in naming when truncating a null Release Group 2024-03-07 18:11:28 -08:00
Sonarr
61a7515041 Automated API Docs update
ignore-downstream
2024-03-07 17:34:04 -08:00
Weblate
2c25245860 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Jason54 <jason54700.jg@gmail.com>
Co-authored-by: Mark Martines <mark-martines@hotmail.com>
Co-authored-by: Maxence Winandy <maxence.winandy@gmail.com>
Co-authored-by: Stevie Robinson <stevie.robinson@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: fordas <fordas15@gmail.com>
Co-authored-by: linkin931 <931linkin@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/ca/
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/cs/
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/el/
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/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/it/
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/ko/
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/ru/
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/tr/
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/zh_CN/
Translation: Servarr/Sonarr
2024-03-07 17:30:56 -08:00
Bogdan
18aadb544e Fixed: Maintain release type for items in Manual Import 2024-03-07 20:30:20 -05:00
Helvio Pedreschi
c7dd7abf89 Fixed: WebApp functionality on Apple devices 2024-03-07 20:29:50 -05:00
CheAle14
d0e9504af0 Fix import list exclusion props 2024-03-07 17:26:29 -08:00
Bogdan
e81bb3b993 Persist page size for Import List Exclusions 2024-03-07 17:25:27 -08:00
Bogdan
f211433b77 Remove debugger from metadata source and rearrange some imports 2024-03-07 17:25:19 -08:00
Bogdan
2068c5393e Fixed: URL Base setting for Kodi connections 2024-03-07 17:25:19 -08:00
Mark McDowall
0183812cc5 Fixed: Overly aggressive exception release group parsing
Closes #6591
2024-03-07 17:25:10 -08:00
154 changed files with 3464 additions and 1013 deletions

View File

@@ -22,7 +22,7 @@ env:
FRAMEWORK: net6.0
RAW_BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
SONARR_MAJOR_VERSION: 4
VERSION: 4.0.2
VERSION: 4.0.3
jobs:
backend:
@@ -121,7 +121,7 @@ jobs:
run: yarn lint
- name: Stylelint
run: yarn stylelint
run: yarn stylelint -f github
- name: Build
run: yarn build --env production
@@ -225,3 +225,25 @@ jobs:
branch: ${{ github.ref_name }}
major_version: ${{ needs.backend.outputs.major_version }}
version: ${{ needs.backend.outputs.version }}
notify:
name: Discord Notification
needs: [backend, unit_test, unit_test_postgres, integration_test]
if: ${{ !cancelled() && (github.ref_name == 'develop' || github.ref_name == 'main') }}
env:
STATUS: ${{ contains(needs.*.result, 'failure') && 'failure' || 'success' }}
runs-on: ubuntu-latest
steps:
- name: Notify
uses: tsickert/discord-webhook@v5.3.0
with:
webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }}
username: 'GitHub Actions'
avatar-url: 'https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png'
embed-title: "${{ github.workflow }}: ${{ env.STATUS == 'success' && 'Success' || 'Failure' }}"
embed-url: 'https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}'
embed-description: |
**Branch** ${{ github.ref }}
**Build** ${{ needs.backend.outputs.version }}
embed-color: ${{ env.STATUS == 'success' && '3066993' || '15158332' }}

View File

@@ -19,6 +19,7 @@ export interface AppSectionSaveState {
export interface PagedAppSectionState {
pageSize: number;
totalRecords?: number;
}
export interface AppSectionFilterState<T> {

View File

@@ -13,6 +13,8 @@ export interface CommandBody {
trigger: string;
suppressMessages: boolean;
seriesId?: number;
seriesIds?: number[];
seasonNumber?: number;
}
interface Command extends ModelBase {

View File

@@ -19,7 +19,7 @@
.isDisabled {
opacity: 0.7;
cursor: not-allowed;
cursor: not-allowed !important;
}
.dropdownArrowContainer {

View File

@@ -2,7 +2,7 @@ import PropTypes from 'prop-types';
import React from 'react';
import monitorOptions from 'Utilities/Series/monitorOptions';
import translate from 'Utilities/String/translate';
import SelectInput from './SelectInput';
import EnhancedSelectInput from './EnhancedSelectInput';
function MonitorEpisodesSelectInput(props) {
const {
@@ -19,7 +19,7 @@ function MonitorEpisodesSelectInput(props) {
get value() {
return translate('NoChange');
},
disabled: true
isDisabled: true
});
}
@@ -29,12 +29,12 @@ function MonitorEpisodesSelectInput(props) {
get value() {
return `(${translate('Mixed')})`;
},
disabled: true
isDisabled: true
});
}
return (
<SelectInput
<EnhancedSelectInput
values={values}
{...otherProps}
/>

View File

@@ -1,7 +1,7 @@
import PropTypes from 'prop-types';
import React from 'react';
import monitorNewItemsOptions from 'Utilities/Series/monitorNewItemsOptions';
import SelectInput from './SelectInput';
import EnhancedSelectInput from './EnhancedSelectInput';
function MonitorNewItemsSelectInput(props) {
const {
@@ -16,7 +16,7 @@ function MonitorNewItemsSelectInput(props) {
values.unshift({
key: 'noChange',
value: 'No Change',
disabled: true
isDisabled: true
});
}
@@ -24,12 +24,12 @@ function MonitorNewItemsSelectInput(props) {
values.unshift({
key: 'mixed',
value: '(Mixed)',
disabled: true
isDisabled: true
});
}
return (
<SelectInput
<EnhancedSelectInput
values={values}
{...otherProps}
/>

View File

@@ -28,7 +28,7 @@ function createMapStateToProps() {
get value() {
return translate('NoChange');
},
disabled: includeNoChangeDisabled
isDisabled: includeNoChangeDisabled
});
}
@@ -38,7 +38,7 @@ function createMapStateToProps() {
get value() {
return `(${translate('Mixed')})`;
},
disabled: true
isDisabled: true
});
}

View File

@@ -15,7 +15,7 @@ interface ISeriesTypeOption {
key: string;
value: string;
format?: string;
disabled?: boolean;
isDisabled?: boolean;
}
const seriesTypeOptions: ISeriesTypeOption[] = [
@@ -55,7 +55,7 @@ function SeriesTypeSelectInput(props: SeriesTypeSelectInputProps) {
values.unshift({
key: 'noChange',
value: translate('NoChange'),
disabled: includeNoChangeDisabled,
isDisabled: includeNoChangeDisabled,
});
}
@@ -63,7 +63,7 @@ function SeriesTypeSelectInput(props: SeriesTypeSelectInputProps) {
values.unshift({
key: 'mixed',
value: `(${translate('Mixed')})`,
disabled: true,
isDisabled: true,
});
}

View File

@@ -63,6 +63,13 @@
width: 1280px;
}
.extraExtraLarge {
composes: modal;
width: 1600px;
}
@media only screen and (max-width: $breakpointExtraLarge) {
.modal.extraLarge {
width: 90%;
@@ -90,7 +97,8 @@
.modal.small,
.modal.medium,
.modal.large,
.modal.extraLarge {
.modal.extraLarge,
.modal.extraExtraLarge {
max-height: 100%;
width: 100%;
height: 100% !important;

View File

@@ -1,6 +1,7 @@
// This file is automatically generated.
// Please do not change this file!
interface CssExports {
'extraExtraLarge': string;
'extraLarge': string;
'large': string;
'medium': string;

View File

@@ -15,5 +15,5 @@
"start_url": "../../../../",
"theme_color": "#3a3f51",
"background_color": "#3a3f51",
"display": "minimal-ui"
"display": "standalone"
}

View File

@@ -37,7 +37,7 @@ class EpisodeDetailsModal extends Component {
return (
<Modal
isOpen={isOpen}
size={sizes.EXTRA_LARGE}
size={sizes.EXTRA_EXTRA_LARGE}
closeOnBackgroundClick={this.state.closeOnBackgroundClick}
onModalClose={onModalClose}
>

View File

@@ -0,0 +1,17 @@
import ReleaseType from 'InteractiveImport/ReleaseType';
import translate from 'Utilities/String/translate';
export default function getReleaseTypeName(
releaseType?: ReleaseType
): string | null {
switch (releaseType) {
case 'singleEpisode':
return translate('SingleEpisode');
case 'multiEpisode':
return translate('MultiEpisode');
case 'seasonPack':
return translate('SeasonPack');
default:
return translate('Unknown');
}
}

View File

@@ -1,4 +1,5 @@
import ModelBase from 'App/ModelBase';
import ReleaseType from 'InteractiveImport/ReleaseType';
import Language from 'Language/Language';
import { QualityModel } from 'Quality/Quality';
import CustomFormat from 'typings/CustomFormat';
@@ -17,6 +18,7 @@ export interface EpisodeFile extends ModelBase {
quality: QualityModel;
customFormats: CustomFormat[];
indexerFlags: number;
releaseType: ReleaseType;
mediaInfo: MediaInfo;
qualityCutoffNotMet: boolean;
}

View File

@@ -3,5 +3,5 @@ export const SMALL = 'small';
export const MEDIUM = 'medium';
export const LARGE = 'large';
export const EXTRA_LARGE = 'extraLarge';
export const all = [EXTRA_SMALL, SMALL, MEDIUM, LARGE, EXTRA_LARGE];
export const EXTRA_EXTRA_LARGE = 'extraExtraLarge';
export const all = [EXTRA_SMALL, SMALL, MEDIUM, LARGE, EXTRA_LARGE, EXTRA_EXTRA_LARGE];

View File

@@ -36,6 +36,7 @@ import InteractiveImport, {
import SelectLanguageModal from 'InteractiveImport/Language/SelectLanguageModal';
import SelectQualityModal from 'InteractiveImport/Quality/SelectQualityModal';
import SelectReleaseGroupModal from 'InteractiveImport/ReleaseGroup/SelectReleaseGroupModal';
import SelectReleaseTypeModal from 'InteractiveImport/ReleaseType/SelectReleaseTypeModal';
import SelectSeasonModal from 'InteractiveImport/Season/SelectSeasonModal';
import SelectSeriesModal from 'InteractiveImport/Series/SelectSeriesModal';
import Language from 'Language/Language';
@@ -73,7 +74,8 @@ type SelectType =
| 'releaseGroup'
| 'quality'
| 'language'
| 'indexerFlags';
| 'indexerFlags'
| 'releaseType';
type FilterExistingFiles = 'all' | 'new';
@@ -128,6 +130,12 @@ const COLUMNS = [
isSortable: true,
isVisible: true,
},
{
name: 'releaseType',
label: () => translate('ReleaseType'),
isSortable: true,
isVisible: true,
},
{
name: 'customFormats',
label: React.createElement(Icon, {
@@ -369,6 +377,10 @@ function InteractiveImportModalContent(
key: 'indexerFlags',
value: translate('SelectIndexerFlags'),
},
{
key: 'releaseType',
value: translate('SelectReleaseType'),
},
];
if (allowSeriesChange) {
@@ -511,6 +523,7 @@ function InteractiveImportModalContent(
languages,
indexerFlags,
episodeFileId,
releaseType,
} = item;
if (!series) {
@@ -560,6 +573,7 @@ function InteractiveImportModalContent(
quality,
languages,
indexerFlags,
releaseType,
});
return;
@@ -575,6 +589,7 @@ function InteractiveImportModalContent(
quality,
languages,
indexerFlags,
releaseType,
downloadId,
episodeFileId,
});
@@ -787,6 +802,22 @@ function InteractiveImportModalContent(
[selectedIds, dispatch]
);
const onReleaseTypeSelect = useCallback(
(releaseType: string) => {
dispatch(
updateInteractiveImportItems({
ids: selectedIds,
releaseType,
})
);
dispatch(reprocessInteractiveImportItems({ ids: selectedIds }));
setSelectModalOpen(null);
},
[selectedIds, dispatch]
);
const orderedSelectedIds = items.reduce((acc: number[], file) => {
if (selectedIds.includes(file.id)) {
acc.push(file.id);
@@ -1000,6 +1031,14 @@ function InteractiveImportModalContent(
onModalClose={onSelectModalClose}
/>
<SelectReleaseTypeModal
isOpen={selectModalOpen === 'releaseType'}
releaseType="unknown"
modalTitle={modalTitle}
onReleaseTypeSelect={onReleaseTypeSelect}
onModalClose={onSelectModalClose}
/>
<ConfirmModal
isOpen={isConfirmDeleteModalOpen}
kind={kinds.DANGER}

View File

@@ -12,6 +12,7 @@ import Episode from 'Episode/Episode';
import EpisodeFormats from 'Episode/EpisodeFormats';
import EpisodeLanguages from 'Episode/EpisodeLanguages';
import EpisodeQuality from 'Episode/EpisodeQuality';
import getReleaseTypeName from 'Episode/getReleaseTypeName';
import IndexerFlags from 'Episode/IndexerFlags';
import { icons, kinds, tooltipPositions } from 'Helpers/Props';
import SelectEpisodeModal from 'InteractiveImport/Episode/SelectEpisodeModal';
@@ -20,6 +21,8 @@ import SelectIndexerFlagsModal from 'InteractiveImport/IndexerFlags/SelectIndexe
import SelectLanguageModal from 'InteractiveImport/Language/SelectLanguageModal';
import SelectQualityModal from 'InteractiveImport/Quality/SelectQualityModal';
import SelectReleaseGroupModal from 'InteractiveImport/ReleaseGroup/SelectReleaseGroupModal';
import ReleaseType from 'InteractiveImport/ReleaseType';
import SelectReleaseTypeModal from 'InteractiveImport/ReleaseType/SelectReleaseTypeModal';
import SelectSeasonModal from 'InteractiveImport/Season/SelectSeasonModal';
import SelectSeriesModal from 'InteractiveImport/Series/SelectSeriesModal';
import Language from 'Language/Language';
@@ -44,7 +47,8 @@ type SelectType =
| 'releaseGroup'
| 'quality'
| 'language'
| 'indexerFlags';
| 'indexerFlags'
| 'releaseType';
type SelectedChangeProps = SelectStateInputProps & {
hasEpisodeFileId: boolean;
@@ -61,6 +65,7 @@ interface InteractiveImportRowProps {
quality?: QualityModel;
languages?: Language[];
size: number;
releaseType: ReleaseType;
customFormats?: object[];
customFormatScore?: number;
indexerFlags: number;
@@ -86,6 +91,7 @@ function InteractiveImportRow(props: InteractiveImportRowProps) {
languages,
releaseGroup,
size,
releaseType,
customFormats,
customFormatScore,
indexerFlags,
@@ -315,6 +321,27 @@ function InteractiveImportRow(props: InteractiveImportRowProps) {
[id, dispatch, setSelectModalOpen, selectRowAfterChange]
);
const onSelectReleaseTypePress = useCallback(() => {
setSelectModalOpen('releaseType');
}, [setSelectModalOpen]);
const onReleaseTypeSelect = useCallback(
(releaseType: ReleaseType) => {
dispatch(
updateInteractiveImportItem({
id,
releaseType,
})
);
dispatch(reprocessInteractiveImportItems({ ids: [id] }));
setSelectModalOpen(null);
selectRowAfterChange();
},
[id, dispatch, setSelectModalOpen, selectRowAfterChange]
);
const onSelectIndexerFlagsPress = useCallback(() => {
setSelectModalOpen('indexerFlags');
}, [setSelectModalOpen]);
@@ -461,6 +488,13 @@ function InteractiveImportRow(props: InteractiveImportRowProps) {
<TableRowCell>{formatBytes(size)}</TableRowCell>
<TableRowCellButton
title={translate('ClickToChangeReleaseType')}
onPress={onSelectReleaseTypePress}
>
{getReleaseTypeName(releaseType)}
</TableRowCellButton>
<TableRowCell>
{customFormats?.length ? (
<Popover
@@ -572,6 +606,14 @@ function InteractiveImportRow(props: InteractiveImportRowProps) {
onModalClose={onSelectModalClose}
/>
<SelectReleaseTypeModal
isOpen={selectModalOpen === 'releaseType'}
releaseType={releaseType ?? 'unknown'}
modalTitle={modalTitle}
onReleaseTypeSelect={onReleaseTypeSelect}
onModalClose={onSelectModalClose}
/>
<SelectIndexerFlagsModal
isOpen={selectModalOpen === 'indexerFlags'}
indexerFlags={indexerFlags ?? 0}

View File

@@ -1,5 +1,6 @@
import ModelBase from 'App/ModelBase';
import Episode from 'Episode/Episode';
import ReleaseType from 'InteractiveImport/ReleaseType';
import Language from 'Language/Language';
import { QualityModel } from 'Quality/Quality';
import Series from 'Series/Series';
@@ -14,6 +15,7 @@ export interface InteractiveImportCommandOptions {
quality: QualityModel;
languages: Language[];
indexerFlags: number;
releaseType: ReleaseType;
downloadId?: string;
episodeFileId?: number;
}
@@ -33,6 +35,7 @@ interface InteractiveImport extends ModelBase {
qualityWeight: number;
customFormats: object[];
indexerFlags: number;
releaseType: ReleaseType;
rejections: Rejection[];
episodeFileId?: number;
}

View File

@@ -47,7 +47,7 @@ function InteractiveImportModal(props: InteractiveImportModalProps) {
return (
<Modal
isOpen={isOpen}
size={sizes.EXTRA_LARGE}
size={sizes.EXTRA_EXTRA_LARGE}
closeOnBackgroundClick={false}
onModalClose={onModalClose}
>

View File

@@ -0,0 +1,3 @@
type ReleaseType = 'unknown' | 'singleEpisode' | 'multiEpisode' | 'seasonPack';
export default ReleaseType;

View File

@@ -0,0 +1,30 @@
import React from 'react';
import Modal from 'Components/Modal/Modal';
import ReleaseType from 'InteractiveImport/ReleaseType';
import SelectReleaseTypeModalContent from './SelectReleaseTypeModalContent';
interface SelectQualityModalProps {
isOpen: boolean;
releaseType: ReleaseType;
modalTitle: string;
onReleaseTypeSelect(releaseType: ReleaseType): void;
onModalClose(): void;
}
function SelectReleaseTypeModal(props: SelectQualityModalProps) {
const { isOpen, releaseType, modalTitle, onReleaseTypeSelect, onModalClose } =
props;
return (
<Modal isOpen={isOpen} onModalClose={onModalClose}>
<SelectReleaseTypeModalContent
releaseType={releaseType}
modalTitle={modalTitle}
onReleaseTypeSelect={onReleaseTypeSelect}
onModalClose={onModalClose}
/>
</Modal>
);
}
export default SelectReleaseTypeModal;

View File

@@ -0,0 +1,99 @@
import React, { useCallback, useState } from 'react';
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 Button from 'Components/Link/Button';
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 ReleaseType from 'InteractiveImport/ReleaseType';
import translate from 'Utilities/String/translate';
const options = [
{
key: 'unknown',
get value() {
return translate('Unknown');
},
},
{
key: 'singleEpisode',
get value() {
return translate('SingleEpisode');
},
},
{
key: 'multiEpisode',
get value() {
return translate('MultiEpisode');
},
},
{
key: 'seasonPack',
get value() {
return translate('SeasonPack');
},
},
];
interface SelectReleaseTypeModalContentProps {
releaseType: ReleaseType;
modalTitle: string;
onReleaseTypeSelect(releaseType: ReleaseType): void;
onModalClose(): void;
}
function SelectReleaseTypeModalContent(
props: SelectReleaseTypeModalContentProps
) {
const { modalTitle, onReleaseTypeSelect, onModalClose } = props;
const [releaseType, setReleaseType] = useState(props.releaseType);
const handleReleaseTypeChange = useCallback(
({ value }: { value: string }) => {
setReleaseType(value as ReleaseType);
},
[setReleaseType]
);
const handleReleaseTypeSelect = useCallback(() => {
onReleaseTypeSelect(releaseType);
}, [releaseType, onReleaseTypeSelect]);
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
{modalTitle} - {translate('SelectReleaseType')}
</ModalHeader>
<ModalBody>
<Form>
<FormGroup>
<FormLabel>{translate('ReleaseType')}</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
name="releaseType"
value={releaseType}
values={options}
onChange={handleReleaseTypeChange}
/>
</FormGroup>
</Form>
</ModalBody>
<ModalFooter>
<Button onPress={onModalClose}>{translate('Cancel')}</Button>
<Button kind={kinds.SUCCESS} onPress={handleReleaseTypeSelect}>
{translate('SelectReleaseType')}
</Button>
</ModalFooter>
</ModalContent>
);
}
export default SelectReleaseTypeModalContent;

View File

@@ -36,7 +36,7 @@ const monitoredOptions = [
get value() {
return translate('NoChange');
},
disabled: true,
isDisabled: true,
},
{
key: 'monitored',
@@ -58,7 +58,7 @@ const seasonFolderOptions = [
get value() {
return translate('NoChange');
},
disabled: true,
isDisabled: true,
},
{
key: 'yes',

View File

@@ -15,7 +15,7 @@ function SeasonInteractiveSearchModal(props) {
return (
<Modal
isOpen={isOpen}
size={sizes.EXTRA_LARGE}
size={sizes.EXTRA_EXTRA_LARGE}
closeOnBackgroundClick={false}
onModalClose={onModalClose}
>

View File

@@ -151,6 +151,11 @@ class EditCustomFormatModalContent extends Component {
</Form>
<FieldSet legend={translate('Conditions')}>
<Alert kind={kinds.INFO}>
<div>
{translate('CustomFormatsSettingsTriggerInfo')}
</div>
</Alert>
<div className={styles.customFormats}>
{
specifications.map((tag) => {

View File

@@ -15,6 +15,7 @@ import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { inputTypes, kinds, sizes } from 'Helpers/Props';
import AdvancedSettingsButton from 'Settings/AdvancedSettingsButton';
import translate from 'Utilities/String/translate';
import styles from './EditDownloadClientModalContent.css';
@@ -37,6 +38,7 @@ class EditDownloadClientModalContent extends Component {
onModalClose,
onSavePress,
onTestPress,
onAdvancedSettingsPress,
onDeleteDownloadClientPress,
...otherProps
} = this.props;
@@ -199,6 +201,12 @@ class EditDownloadClientModalContent extends Component {
</Button>
}
<AdvancedSettingsButton
advancedSettings={advancedSettings}
onAdvancedSettingsPress={onAdvancedSettingsPress}
showLabel={false}
/>
<SpinnerErrorButton
isSpinning={isTesting}
error={saveError}
@@ -239,6 +247,7 @@ EditDownloadClientModalContent.propTypes = {
onModalClose: PropTypes.func.isRequired,
onSavePress: PropTypes.func.isRequired,
onTestPress: PropTypes.func.isRequired,
onAdvancedSettingsPress: PropTypes.func.isRequired,
onDeleteDownloadClientPress: PropTypes.func
};

View File

@@ -2,7 +2,13 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { saveDownloadClient, setDownloadClientFieldValue, setDownloadClientValue, testDownloadClient } from 'Store/Actions/settingsActions';
import {
saveDownloadClient,
setDownloadClientFieldValue,
setDownloadClientValue,
testDownloadClient,
toggleAdvancedSettings
} from 'Store/Actions/settingsActions';
import createProviderSettingsSelector from 'Store/Selectors/createProviderSettingsSelector';
import EditDownloadClientModalContent from './EditDownloadClientModalContent';
@@ -23,7 +29,8 @@ const mapDispatchToProps = {
setDownloadClientValue,
setDownloadClientFieldValue,
saveDownloadClient,
testDownloadClient
testDownloadClient,
toggleAdvancedSettings
};
class EditDownloadClientModalContentConnector extends Component {
@@ -56,6 +63,10 @@ class EditDownloadClientModalContentConnector extends Component {
this.props.testDownloadClient({ id: this.props.id });
};
onAdvancedSettingsPress = () => {
this.props.toggleAdvancedSettings();
};
//
// Render
@@ -65,6 +76,7 @@ class EditDownloadClientModalContentConnector extends Component {
{...this.props}
onSavePress={this.onSavePress}
onTestPress={this.onTestPress}
onAdvancedSettingsPress={this.onAdvancedSettingsPress}
onInputChange={this.onInputChange}
onFieldChange={this.onFieldChange}
/>
@@ -82,6 +94,7 @@ EditDownloadClientModalContentConnector.propTypes = {
setDownloadClientFieldValue: PropTypes.func.isRequired,
saveDownloadClient: PropTypes.func.isRequired,
testDownloadClient: PropTypes.func.isRequired,
toggleAdvancedSettings: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired
};

View File

@@ -32,7 +32,7 @@ const enableOptions = [
get value() {
return translate('NoChange');
},
disabled: true,
isDisabled: true,
},
{
key: 'enabled',

View File

@@ -1,5 +1,6 @@
import React, { useCallback, useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useHistory } from 'react-router';
import { createSelector } from 'reselect';
import AppState from 'App/State/AppState';
import FieldSet from 'Components/FieldSet';
@@ -41,11 +42,6 @@ const COLUMNS = [
},
];
interface ImportListExclusionsProps {
useCurrentPage: number;
totalRecords: number;
}
function createImportListExlucionsSelector() {
return createSelector(
(state: AppState) => state.settings.importListExclusions,
@@ -57,8 +53,9 @@ function createImportListExlucionsSelector() {
);
}
function ImportListExclusions(props: ImportListExclusionsProps) {
const { useCurrentPage, totalRecords } = props;
function ImportListExclusions() {
const history = useHistory();
const useCurrentPage = history.action === 'POP';
const dispatch = useDispatch();
@@ -155,6 +152,7 @@ function ImportListExclusions(props: ImportListExclusionsProps) {
sortKey,
error,
sortDirection,
totalRecords,
...otherProps
} = selected;

View File

@@ -19,6 +19,7 @@ import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import Popover from 'Components/Tooltip/Popover';
import { icons, inputTypes, kinds, tooltipPositions } from 'Helpers/Props';
import AdvancedSettingsButton from 'Settings/AdvancedSettingsButton';
import formatShortTimeSpan from 'Utilities/Date/formatShortTimeSpan';
import translate from 'Utilities/String/translate';
import styles from './EditImportListModalContent.css';
@@ -38,6 +39,7 @@ function EditImportListModalContent(props) {
onModalClose,
onSavePress,
onTestPress,
onAdvancedSettingsPress,
onDeleteImportListPress,
...otherProps
} = props;
@@ -288,6 +290,12 @@ function EditImportListModalContent(props) {
</Button>
}
<AdvancedSettingsButton
advancedSettings={advancedSettings}
onAdvancedSettingsPress={onAdvancedSettingsPress}
showLabel={false}
/>
<SpinnerErrorButton
isSpinning={isTesting}
error={saveError}
@@ -327,6 +335,7 @@ EditImportListModalContent.propTypes = {
onModalClose: PropTypes.func.isRequired,
onSavePress: PropTypes.func.isRequired,
onTestPress: PropTypes.func.isRequired,
onAdvancedSettingsPress: PropTypes.func.isRequired,
onDeleteImportListPress: PropTypes.func
};

View File

@@ -2,7 +2,13 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { saveImportList, setImportListFieldValue, setImportListValue, testImportList } from 'Store/Actions/settingsActions';
import {
saveImportList,
setImportListFieldValue,
setImportListValue,
testImportList,
toggleAdvancedSettings
} from 'Store/Actions/settingsActions';
import createProviderSettingsSelector from 'Store/Selectors/createProviderSettingsSelector';
import EditImportListModalContent from './EditImportListModalContent';
@@ -23,7 +29,8 @@ const mapDispatchToProps = {
setImportListValue,
setImportListFieldValue,
saveImportList,
testImportList
testImportList,
toggleAdvancedSettings
};
class EditImportListModalContentConnector extends Component {
@@ -56,6 +63,10 @@ class EditImportListModalContentConnector extends Component {
this.props.testImportList({ id: this.props.id });
};
onAdvancedSettingsPress = () => {
this.props.toggleAdvancedSettings();
};
//
// Render
@@ -65,6 +76,7 @@ class EditImportListModalContentConnector extends Component {
{...this.props}
onSavePress={this.onSavePress}
onTestPress={this.onTestPress}
onAdvancedSettingsPress={this.onAdvancedSettingsPress}
onInputChange={this.onInputChange}
onFieldChange={this.onFieldChange}
/>
@@ -82,6 +94,7 @@ EditImportListModalContentConnector.propTypes = {
setImportListFieldValue: PropTypes.func.isRequired,
saveImportList: PropTypes.func.isRequired,
testImportList: PropTypes.func.isRequired,
toggleAdvancedSettings: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired
};

View File

@@ -31,7 +31,7 @@ const autoAddOptions = [
get value() {
return translate('NoChange');
},
disabled: true,
isDisabled: true,
},
{
key: 'enabled',

View File

@@ -32,7 +32,7 @@ const enableOptions = [
get value() {
return translate('NoChange');
},
disabled: true,
isDisabled: true,
},
{
key: 'enabled',

View File

@@ -80,19 +80,19 @@ const fileNameTokens = [
];
const seriesTokens = [
{ token: '{Series Title}', example: 'The Series Title\'s!' },
{ token: '{Series CleanTitle}', example: 'The Series Title\'s!' },
{ 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 CleanTitleTheWithoutYear}', example: 'Series Title\'s!, The' },
{ token: '{Series TitleFirstCharacter}', example: 'S' },
{ token: '{Series Title}', example: 'The Series Title\'s!', footNote: 1 },
{ token: '{Series CleanTitle}', example: 'The Series Title\'s!', footNote: 1 },
{ token: '{Series TitleYear}', example: 'The Series Title\'s! (2010)', footNote: 1 },
{ token: '{Series CleanTitleYear}', example: 'The Series Title\'s! 2010', footNote: 1 },
{ token: '{Series TitleWithoutYear}', example: 'The Series Title\'s!', footNote: 1 },
{ token: '{Series CleanTitleWithoutYear}', example: 'The Series Title\'s!', footNote: 1 },
{ token: '{Series TitleThe}', example: 'Series Title\'s!, The', footNote: 1 },
{ token: '{Series CleanTitleThe}', example: 'Series Title\'s!, The', footNote: 1 },
{ token: '{Series TitleTheYear}', example: 'Series Title\'s!, The (2010)', footNote: 1 },
{ token: '{Series CleanTitleTheYear}', example: 'Series Title\'s!, The 2010', footNote: 1 },
{ token: '{Series TitleTheWithoutYear}', example: 'Series Title\'s!, The', footNote: 1 },
{ token: '{Series CleanTitleTheWithoutYear}', example: 'Series Title\'s!, The', footNote: 1 },
{ token: '{Series TitleFirstCharacter}', example: 'S', footNote: 1 },
{ token: '{Series Year}', example: '2010' }
];
@@ -124,8 +124,8 @@ const absoluteTokens = [
];
const episodeTitleTokens = [
{ token: '{Episode Title}', example: 'Episode\'s Title' },
{ token: '{Episode CleanTitle}', example: 'Episodes Title' }
{ token: '{Episode Title}', example: 'Episode\'s Title', footNote: 1 },
{ token: '{Episode CleanTitle}', example: 'Episodes Title', footNote: 1 }
];
const qualityTokens = [
@@ -149,8 +149,13 @@ const mediaInfoTokens = [
];
const otherTokens = [
{ token: '{Release Group}', example: 'Rls Grp' },
{ token: '{Custom Formats}', example: 'iNTERNAL' }
{ token: '{Release Group}', example: 'Rls Grp', footNote: 1 },
{ token: '{Custom Formats}', example: 'iNTERNAL' },
{ token: '{Custom Format:FormatName}', example: 'AMZN' }
];
const otherAnimeTokens = [
{ token: '{Release Hash}', example: 'ABCDEFGH' }
];
const originalTokens = [
@@ -300,7 +305,7 @@ class NamingModal extends Component {
<FieldSet legend={translate('Series')}>
<div className={styles.groups}>
{
seriesTokens.map(({ token, example }) => {
seriesTokens.map(({ token, example, footNote }) => {
return (
<NamingOption
key={token}
@@ -308,6 +313,7 @@ class NamingModal extends Component {
value={value}
token={token}
example={example}
footNote={footNote}
tokenSeparator={tokenSeparator}
tokenCase={tokenCase}
onPress={this.onOptionPress}
@@ -317,6 +323,11 @@ class NamingModal extends Component {
)
}
</div>
<div className={styles.footNote}>
<Icon className={styles.icon} name={icons.FOOTNOTE} />
<InlineMarkdown data={translate('SeriesFootNote')} />
</div>
</FieldSet>
<FieldSet legend={translate('SeriesID')}>
@@ -446,7 +457,7 @@ class NamingModal extends Component {
<FieldSet legend={translate('EpisodeTitle')}>
<div className={styles.groups}>
{
episodeTitleTokens.map(({ token, example }) => {
episodeTitleTokens.map(({ token, example, footNote }) => {
return (
<NamingOption
key={token}
@@ -454,6 +465,7 @@ class NamingModal extends Component {
value={value}
token={token}
example={example}
footNote={footNote}
tokenSeparator={tokenSeparator}
tokenCase={tokenCase}
onPress={this.onOptionPress}
@@ -463,6 +475,10 @@ class NamingModal extends Component {
)
}
</div>
<div className={styles.footNote}>
<Icon className={styles.icon} name={icons.FOOTNOTE} />
<InlineMarkdown data={translate('EpisodeTitleFootNote')} />
</div>
</FieldSet>
<FieldSet legend={translate('Quality')}>
@@ -518,7 +534,26 @@ class NamingModal extends Component {
<FieldSet legend={translate('Other')}>
<div className={styles.groups}>
{
otherTokens.map(({ token, example }) => {
otherTokens.map(({ token, example, footNote }) => {
return (
<NamingOption
key={token}
name={name}
value={value}
token={token}
example={example}
footNote={footNote}
tokenSeparator={tokenSeparator}
tokenCase={tokenCase}
onPress={this.onOptionPress}
/>
);
}
)
}
{
anime && otherAnimeTokens.map(({ token, example }) => {
return (
<NamingOption
key={token}
@@ -535,6 +570,11 @@ class NamingModal extends Component {
)
}
</div>
<div className={styles.footNote}>
<Icon className={styles.icon} name={icons.FOOTNOTE} />
<InlineMarkdown data={translate('ReleaseGroupFootNote')} />
</div>
</FieldSet>
<FieldSet legend={translate('Original')}>

View File

@@ -26,7 +26,7 @@
.token {
flex: 0 0 50%;
padding: 6px 6px;
padding: 6px;
background-color: var(--popoverTitleBackgroundColor);
font-family: $monoSpaceFontFamily;
}
@@ -36,7 +36,7 @@
align-items: center;
justify-content: space-between;
flex: 0 0 50%;
padding: 6px 6px;
padding: 6px;
background-color: var(--popoverBodyBackgroundColor);
.footNote {

View File

@@ -4,7 +4,6 @@ import translate from 'Utilities/String/translate';
import styles from './TheTvdb.css';
function TheTvdb(props) {
debugger;
return (
<div className={styles.container}>
<img

View File

@@ -14,6 +14,7 @@ import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { inputTypes, kinds } from 'Helpers/Props';
import AdvancedSettingsButton from 'Settings/AdvancedSettingsButton';
import translate from 'Utilities/String/translate';
import NotificationEventItems from './NotificationEventItems';
import styles from './EditNotificationModalContent.css';
@@ -32,6 +33,7 @@ function EditNotificationModalContent(props) {
onModalClose,
onSavePress,
onTestPress,
onAdvancedSettingsPress,
onDeleteNotificationPress,
...otherProps
} = props;
@@ -136,6 +138,12 @@ function EditNotificationModalContent(props) {
</Button>
}
<AdvancedSettingsButton
advancedSettings={advancedSettings}
onAdvancedSettingsPress={onAdvancedSettingsPress}
showLabel={false}
/>
<SpinnerErrorButton
isSpinning={isTesting}
error={saveError}
@@ -175,6 +183,7 @@ EditNotificationModalContent.propTypes = {
onModalClose: PropTypes.func.isRequired,
onSavePress: PropTypes.func.isRequired,
onTestPress: PropTypes.func.isRequired,
onAdvancedSettingsPress: PropTypes.func.isRequired,
onDeleteNotificationPress: PropTypes.func
};

View File

@@ -2,7 +2,13 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { saveNotification, setNotificationFieldValue, setNotificationValue, testNotification } from 'Store/Actions/settingsActions';
import {
saveNotification,
setNotificationFieldValue,
setNotificationValue,
testNotification,
toggleAdvancedSettings
} from 'Store/Actions/settingsActions';
import createProviderSettingsSelector from 'Store/Selectors/createProviderSettingsSelector';
import EditNotificationModalContent from './EditNotificationModalContent';
@@ -23,7 +29,8 @@ const mapDispatchToProps = {
setNotificationValue,
setNotificationFieldValue,
saveNotification,
testNotification
testNotification,
toggleAdvancedSettings
};
class EditNotificationModalContentConnector extends Component {
@@ -56,6 +63,10 @@ class EditNotificationModalContentConnector extends Component {
this.props.testNotification({ id: this.props.id });
};
onAdvancedSettingsPress = () => {
this.props.toggleAdvancedSettings();
};
//
// Render
@@ -65,6 +76,7 @@ class EditNotificationModalContentConnector extends Component {
{...this.props}
onSavePress={this.onSavePress}
onTestPress={this.onTestPress}
onAdvancedSettingsPress={this.onAdvancedSettingsPress}
onInputChange={this.onInputChange}
onFieldChange={this.onFieldChange}
/>
@@ -82,6 +94,7 @@ EditNotificationModalContentConnector.propTypes = {
setNotificationFieldValue: PropTypes.func.isRequired,
saveNotification: PropTypes.func.isRequired,
testNotification: PropTypes.func.isRequired,
toggleAdvancedSettings: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired
};

View File

@@ -1,11 +1,11 @@
import { createAction } from 'redux-actions';
import createRemoveItemHandler from 'Store/Actions/Creators/createRemoveItemHandler';
import createSaveProviderHandler from 'Store/Actions/Creators/createSaveProviderHandler';
import createServerSideCollectionHandlers from 'Store/Actions/Creators/createServerSideCollectionHandlers';
import createSetSettingValueReducer from 'Store/Actions/Creators/Reducers/createSetSettingValueReducer';
import createSetTableOptionReducer from 'Store/Actions/Creators/Reducers/createSetTableOptionReducer';
import { createThunk, handleThunks } from 'Store/thunks';
import serverSideCollectionHandlers from 'Utilities/serverSideCollectionHandlers';
import createServerSideCollectionHandlers from '../Creators/createServerSideCollectionHandlers';
import createSetTableOptionReducer from '../Creators/Reducers/createSetTableOptionReducer';
//
// Variables

View File

@@ -163,6 +163,7 @@ export const actionHandlers = handleThunks({
languages: item.languages,
releaseGroup: item.releaseGroup,
indexerFlags: item.indexerFlags,
releaseType: item.releaseType,
downloadId: item.downloadId
};
});

View File

@@ -1,5 +1,4 @@
import { createAction } from 'redux-actions';
import indexerFlags from 'Store/Actions/Settings/indexerFlags';
import { handleThunks } from 'Store/thunks';
import createHandleActions from './Creators/createHandleActions';
import autoTaggings from './Settings/autoTaggings';
@@ -13,6 +12,7 @@ import general from './Settings/general';
import importListExclusions from './Settings/importListExclusions';
import importListOptions from './Settings/importListOptions';
import importLists from './Settings/importLists';
import indexerFlags from './Settings/indexerFlags';
import indexerOptions from './Settings/indexerOptions';
import indexers from './Settings/indexers';
import languages from './Settings/languages';
@@ -91,7 +91,8 @@ export const defaultState = {
};
export const persistState = [
'settings.advancedSettings'
'settings.advancedSettings',
'settings.importListExclusions.pageSize'
];
//

View File

@@ -0,0 +1,23 @@
import { createSelector } from 'reselect';
import AppState from 'App/State/AppState';
import Series from 'Series/Series';
function createMultiSeriesSelector(seriesIds: number[]) {
return createSelector(
(state: AppState) => state.series.itemMap,
(state: AppState) => state.series.items,
(itemMap, allSeries) => {
return seriesIds.reduce((acc: Series[], seriesId) => {
const series = allSeries[itemMap[seriesId]];
if (series) {
acc.push(series);
}
return acc;
}, []);
}
);
}
export default createMultiSeriesSelector;

View File

@@ -10,15 +10,6 @@
width: 100%;
}
.commandName {
display: inline-block;
min-width: 220px;
}
.userAgent {
color: #b0b0b0;
}
.queued,
.started,
.ended {

View File

@@ -2,14 +2,12 @@
// Please do not change this file!
interface CssExports {
'actions': string;
'commandName': string;
'duration': string;
'ended': string;
'queued': string;
'started': string;
'trigger': string;
'triggerContent': string;
'userAgent': string;
}
export const cssExports: CssExports;
export default cssExports;

View File

@@ -1,279 +0,0 @@
import moment from 'moment';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Icon from 'Components/Icon';
import IconButton from 'Components/Link/IconButton';
import ConfirmModal from 'Components/Modal/ConfirmModal';
import TableRowCell from 'Components/Table/Cells/TableRowCell';
import TableRow from 'Components/Table/TableRow';
import { icons, kinds } from 'Helpers/Props';
import formatDate from 'Utilities/Date/formatDate';
import formatDateTime from 'Utilities/Date/formatDateTime';
import formatTimeSpan from 'Utilities/Date/formatTimeSpan';
import titleCase from 'Utilities/String/titleCase';
import translate from 'Utilities/String/translate';
import styles from './QueuedTaskRow.css';
function getStatusIconProps(status, message) {
const title = titleCase(status);
switch (status) {
case 'queued':
return {
name: icons.PENDING,
title
};
case 'started':
return {
name: icons.REFRESH,
isSpinning: true,
title
};
case 'completed':
return {
name: icons.CHECK,
kind: kinds.SUCCESS,
title: message === 'Completed' ? title : `${title}: ${message}`
};
case 'failed':
return {
name: icons.FATAL,
kind: kinds.DANGER,
title: `${title}: ${message}`
};
default:
return {
name: icons.UNKNOWN,
title
};
}
}
function getFormattedDates(props) {
const {
queued,
started,
ended,
showRelativeDates,
shortDateFormat
} = props;
if (showRelativeDates) {
return {
queuedAt: moment(queued).fromNow(),
startedAt: started ? moment(started).fromNow() : '-',
endedAt: ended ? moment(ended).fromNow() : '-'
};
}
return {
queuedAt: formatDate(queued, shortDateFormat),
startedAt: started ? formatDate(started, shortDateFormat) : '-',
endedAt: ended ? formatDate(ended, shortDateFormat) : '-'
};
}
class QueuedTaskRow extends Component {
//
// Lifecycle
constructor(props, context) {
super(props, context);
this.state = {
...getFormattedDates(props),
isCancelConfirmModalOpen: false
};
this._updateTimeoutId = null;
}
componentDidMount() {
this.setUpdateTimer();
}
componentDidUpdate(prevProps) {
const {
queued,
started,
ended
} = this.props;
if (
queued !== prevProps.queued ||
started !== prevProps.started ||
ended !== prevProps.ended
) {
this.setState(getFormattedDates(this.props));
}
}
componentWillUnmount() {
if (this._updateTimeoutId) {
this._updateTimeoutId = clearTimeout(this._updateTimeoutId);
}
}
//
// Control
setUpdateTimer() {
this._updateTimeoutId = setTimeout(() => {
this.setState(getFormattedDates(this.props));
this.setUpdateTimer();
}, 30000);
}
//
// Listeners
onCancelPress = () => {
this.setState({
isCancelConfirmModalOpen: true
});
};
onAbortCancel = () => {
this.setState({
isCancelConfirmModalOpen: false
});
};
//
// Render
render() {
const {
trigger,
commandName,
queued,
started,
ended,
status,
duration,
message,
clientUserAgent,
longDateFormat,
timeFormat,
onCancelPress
} = this.props;
const {
queuedAt,
startedAt,
endedAt,
isCancelConfirmModalOpen
} = this.state;
let triggerIcon = icons.QUICK;
if (trigger === 'manual') {
triggerIcon = icons.INTERACTIVE;
} else if (trigger === 'scheduled') {
triggerIcon = icons.SCHEDULED;
}
return (
<TableRow>
<TableRowCell className={styles.trigger}>
<span className={styles.triggerContent}>
<Icon
name={triggerIcon}
title={titleCase(trigger)}
/>
<Icon
{...getStatusIconProps(status, message)}
/>
</span>
</TableRowCell>
<TableRowCell>
<span className={styles.commandName}>
{commandName}
</span>
{
clientUserAgent ?
<span className={styles.userAgent} title={translate('TaskUserAgentTooltip')}>
{translate('From')}: {clientUserAgent}
</span> :
null
}
</TableRowCell>
<TableRowCell
className={styles.queued}
title={formatDateTime(queued, longDateFormat, timeFormat)}
>
{queuedAt}
</TableRowCell>
<TableRowCell
className={styles.started}
title={formatDateTime(started, longDateFormat, timeFormat)}
>
{startedAt}
</TableRowCell>
<TableRowCell
className={styles.ended}
title={formatDateTime(ended, longDateFormat, timeFormat)}
>
{endedAt}
</TableRowCell>
<TableRowCell className={styles.duration}>
{formatTimeSpan(duration)}
</TableRowCell>
<TableRowCell
className={styles.actions}
>
{
status === 'queued' &&
<IconButton
title={translate('RemovedFromTaskQueue')}
name={icons.REMOVE}
onPress={this.onCancelPress}
/>
}
</TableRowCell>
<ConfirmModal
isOpen={isCancelConfirmModalOpen}
kind={kinds.DANGER}
title={translate('Cancel')}
message={translate('CancelPendingTask')}
confirmLabel={translate('YesCancel')}
cancelLabel={translate('NoLeaveIt')}
onConfirm={onCancelPress}
onCancel={this.onAbortCancel}
/>
</TableRow>
);
}
}
QueuedTaskRow.propTypes = {
trigger: PropTypes.string.isRequired,
commandName: PropTypes.string.isRequired,
queued: PropTypes.string.isRequired,
started: PropTypes.string,
ended: PropTypes.string,
status: PropTypes.string.isRequired,
duration: PropTypes.string,
message: PropTypes.string,
clientUserAgent: PropTypes.string,
showRelativeDates: PropTypes.bool.isRequired,
shortDateFormat: PropTypes.string.isRequired,
longDateFormat: PropTypes.string.isRequired,
timeFormat: PropTypes.string.isRequired,
onCancelPress: PropTypes.func.isRequired
};
export default QueuedTaskRow;

View File

@@ -0,0 +1,238 @@
import moment from 'moment';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { CommandBody } from 'Commands/Command';
import Icon from 'Components/Icon';
import IconButton from 'Components/Link/IconButton';
import ConfirmModal from 'Components/Modal/ConfirmModal';
import TableRowCell from 'Components/Table/Cells/TableRowCell';
import TableRow from 'Components/Table/TableRow';
import useModalOpenState from 'Helpers/Hooks/useModalOpenState';
import { icons, kinds } from 'Helpers/Props';
import { cancelCommand } from 'Store/Actions/commandActions';
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
import formatDate from 'Utilities/Date/formatDate';
import formatDateTime from 'Utilities/Date/formatDateTime';
import formatTimeSpan from 'Utilities/Date/formatTimeSpan';
import titleCase from 'Utilities/String/titleCase';
import translate from 'Utilities/String/translate';
import QueuedTaskRowNameCell from './QueuedTaskRowNameCell';
import styles from './QueuedTaskRow.css';
function getStatusIconProps(status: string, message: string | undefined) {
const title = titleCase(status);
switch (status) {
case 'queued':
return {
name: icons.PENDING,
title,
};
case 'started':
return {
name: icons.REFRESH,
isSpinning: true,
title,
};
case 'completed':
return {
name: icons.CHECK,
kind: kinds.SUCCESS,
title: message === 'Completed' ? title : `${title}: ${message}`,
};
case 'failed':
return {
name: icons.FATAL,
kind: kinds.DANGER,
title: `${title}: ${message}`,
};
default:
return {
name: icons.UNKNOWN,
title,
};
}
}
function getFormattedDates(
queued: string,
started: string | undefined,
ended: string | undefined,
showRelativeDates: boolean,
shortDateFormat: string
) {
if (showRelativeDates) {
return {
queuedAt: moment(queued).fromNow(),
startedAt: started ? moment(started).fromNow() : '-',
endedAt: ended ? moment(ended).fromNow() : '-',
};
}
return {
queuedAt: formatDate(queued, shortDateFormat),
startedAt: started ? formatDate(started, shortDateFormat) : '-',
endedAt: ended ? formatDate(ended, shortDateFormat) : '-',
};
}
interface QueuedTimes {
queuedAt: string;
startedAt: string;
endedAt: string;
}
export interface QueuedTaskRowProps {
id: number;
trigger: string;
commandName: string;
queued: string;
started?: string;
ended?: string;
status: string;
duration?: string;
message?: string;
body: CommandBody;
clientUserAgent?: string;
}
export default function QueuedTaskRow(props: QueuedTaskRowProps) {
const {
id,
trigger,
commandName,
queued,
started,
ended,
status,
duration,
message,
body,
clientUserAgent,
} = props;
const dispatch = useDispatch();
const { longDateFormat, shortDateFormat, showRelativeDates, timeFormat } =
useSelector(createUISettingsSelector());
const updateTimeTimeoutId = useRef<ReturnType<typeof setTimeout> | null>(
null
);
const [times, setTimes] = useState<QueuedTimes>(
getFormattedDates(
queued,
started,
ended,
showRelativeDates,
shortDateFormat
)
);
const [
isCancelConfirmModalOpen,
openCancelConfirmModal,
closeCancelConfirmModal,
] = useModalOpenState(false);
const handleCancelPress = useCallback(() => {
dispatch(cancelCommand({ id }));
}, [id, dispatch]);
useEffect(() => {
updateTimeTimeoutId.current = setTimeout(() => {
setTimes(
getFormattedDates(
queued,
started,
ended,
showRelativeDates,
shortDateFormat
)
);
}, 30000);
return () => {
if (updateTimeTimeoutId.current) {
clearTimeout(updateTimeTimeoutId.current);
}
};
}, [queued, started, ended, showRelativeDates, shortDateFormat, setTimes]);
const { queuedAt, startedAt, endedAt } = times;
let triggerIcon = icons.QUICK;
if (trigger === 'manual') {
triggerIcon = icons.INTERACTIVE;
} else if (trigger === 'scheduled') {
triggerIcon = icons.SCHEDULED;
}
return (
<TableRow>
<TableRowCell className={styles.trigger}>
<span className={styles.triggerContent}>
<Icon name={triggerIcon} title={titleCase(trigger)} />
<Icon {...getStatusIconProps(status, message)} />
</span>
</TableRowCell>
<QueuedTaskRowNameCell
commandName={commandName}
body={body}
clientUserAgent={clientUserAgent}
/>
<TableRowCell
className={styles.queued}
title={formatDateTime(queued, longDateFormat, timeFormat)}
>
{queuedAt}
</TableRowCell>
<TableRowCell
className={styles.started}
title={formatDateTime(started, longDateFormat, timeFormat)}
>
{startedAt}
</TableRowCell>
<TableRowCell
className={styles.ended}
title={formatDateTime(ended, longDateFormat, timeFormat)}
>
{endedAt}
</TableRowCell>
<TableRowCell className={styles.duration}>
{formatTimeSpan(duration)}
</TableRowCell>
<TableRowCell className={styles.actions}>
{status === 'queued' && (
<IconButton
title={translate('RemovedFromTaskQueue')}
name={icons.REMOVE}
onPress={openCancelConfirmModal}
/>
)}
</TableRowCell>
<ConfirmModal
isOpen={isCancelConfirmModalOpen}
kind={kinds.DANGER}
title={translate('Cancel')}
message={translate('CancelPendingTask')}
confirmLabel={translate('YesCancel')}
cancelLabel={translate('NoLeaveIt')}
onConfirm={handleCancelPress}
onCancel={closeCancelConfirmModal}
/>
</TableRow>
);
}

View File

@@ -1,31 +0,0 @@
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { cancelCommand } from 'Store/Actions/commandActions';
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
import QueuedTaskRow from './QueuedTaskRow';
function createMapStateToProps() {
return createSelector(
createUISettingsSelector(),
(uiSettings) => {
return {
showRelativeDates: uiSettings.showRelativeDates,
shortDateFormat: uiSettings.shortDateFormat,
longDateFormat: uiSettings.longDateFormat,
timeFormat: uiSettings.timeFormat
};
}
);
}
function createMapDispatchToProps(dispatch, props) {
return {
onCancelPress() {
dispatch(cancelCommand({
id: props.id
}));
}
};
}
export default connect(createMapStateToProps, createMapDispatchToProps)(QueuedTaskRow);

View File

@@ -0,0 +1,8 @@
.commandName {
display: inline-block;
min-width: 220px;
}
.userAgent {
color: #b0b0b0;
}

View File

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

View File

@@ -0,0 +1,57 @@
import React from 'react';
import { useSelector } from 'react-redux';
import { CommandBody } from 'Commands/Command';
import TableRowCell from 'Components/Table/Cells/TableRowCell';
import createMultiSeriesSelector from 'Store/Selectors/createMultiSeriesSelector';
import translate from 'Utilities/String/translate';
import styles from './QueuedTaskRowNameCell.css';
export interface QueuedTaskRowNameCellProps {
commandName: string;
body: CommandBody;
clientUserAgent?: string;
}
export default function QueuedTaskRowNameCell(
props: QueuedTaskRowNameCellProps
) {
const { commandName, body, clientUserAgent } = props;
const seriesIds = [...(body.seriesIds ?? [])];
if (body.seriesId) {
seriesIds.push(body.seriesId);
}
const series = useSelector(createMultiSeriesSelector(seriesIds));
const sortedSeries = series.sort((a, b) =>
a.sortTitle.localeCompare(b.sortTitle)
);
return (
<TableRowCell>
<span className={styles.commandName}>
{commandName}
{sortedSeries.length ? (
<span> - {sortedSeries.map((s) => s.title).join(', ')}</span>
) : null}
{body.seasonNumber ? (
<span>
{' '}
{translate('SeasonNumberToken', {
seasonNumber: body.seasonNumber,
})}
</span>
) : null}
</span>
{clientUserAgent ? (
<span
className={styles.userAgent}
title={translate('TaskUserAgentTooltip')}
>
{translate('From')}: {clientUserAgent}
</span>
) : null}
</TableRowCell>
);
}

View File

@@ -1,90 +0,0 @@
import PropTypes from 'prop-types';
import React from 'react';
import FieldSet from 'Components/FieldSet';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody';
import translate from 'Utilities/String/translate';
import QueuedTaskRowConnector from './QueuedTaskRowConnector';
const columns = [
{
name: 'trigger',
label: '',
isVisible: true
},
{
name: 'commandName',
label: () => translate('Name'),
isVisible: true
},
{
name: 'queued',
label: () => translate('Queued'),
isVisible: true
},
{
name: 'started',
label: () => translate('Started'),
isVisible: true
},
{
name: 'ended',
label: () => translate('Ended'),
isVisible: true
},
{
name: 'duration',
label: () => translate('Duration'),
isVisible: true
},
{
name: 'actions',
isVisible: true
}
];
function QueuedTasks(props) {
const {
isFetching,
isPopulated,
items
} = props;
return (
<FieldSet legend={translate('Queue')}>
{
isFetching && !isPopulated &&
<LoadingIndicator />
}
{
isPopulated &&
<Table
columns={columns}
>
<TableBody>
{
items.map((item) => {
return (
<QueuedTaskRowConnector
key={item.id}
{...item}
/>
);
})
}
</TableBody>
</Table>
}
</FieldSet>
);
}
QueuedTasks.propTypes = {
isFetching: PropTypes.bool.isRequired,
isPopulated: PropTypes.bool.isRequired,
items: PropTypes.array.isRequired
};
export default QueuedTasks;

View File

@@ -0,0 +1,74 @@
import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import AppState from 'App/State/AppState';
import FieldSet from 'Components/FieldSet';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody';
import { fetchCommands } from 'Store/Actions/commandActions';
import translate from 'Utilities/String/translate';
import QueuedTaskRow from './QueuedTaskRow';
const columns = [
{
name: 'trigger',
label: '',
isVisible: true,
},
{
name: 'commandName',
label: () => translate('Name'),
isVisible: true,
},
{
name: 'queued',
label: () => translate('Queued'),
isVisible: true,
},
{
name: 'started',
label: () => translate('Started'),
isVisible: true,
},
{
name: 'ended',
label: () => translate('Ended'),
isVisible: true,
},
{
name: 'duration',
label: () => translate('Duration'),
isVisible: true,
},
{
name: 'actions',
isVisible: true,
},
];
export default function QueuedTasks() {
const dispatch = useDispatch();
const { isFetching, isPopulated, items } = useSelector(
(state: AppState) => state.commands
);
useEffect(() => {
dispatch(fetchCommands());
}, [dispatch]);
return (
<FieldSet legend={translate('Queue')}>
{isFetching && !isPopulated && <LoadingIndicator />}
{isPopulated && (
<Table columns={columns}>
<TableBody>
{items.map((item) => {
return <QueuedTaskRow key={item.id} {...item} />;
})}
</TableBody>
</Table>
)}
</FieldSet>
);
}

View File

@@ -1,46 +0,0 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { fetchCommands } from 'Store/Actions/commandActions';
import QueuedTasks from './QueuedTasks';
function createMapStateToProps() {
return createSelector(
(state) => state.commands,
(commands) => {
return commands;
}
);
}
const mapDispatchToProps = {
dispatchFetchCommands: fetchCommands
};
class QueuedTasksConnector extends Component {
//
// Lifecycle
componentDidMount() {
this.props.dispatchFetchCommands();
}
//
// Render
render() {
return (
<QueuedTasks
{...this.props}
/>
);
}
}
QueuedTasksConnector.propTypes = {
dispatchFetchCommands: PropTypes.func.isRequired
};
export default connect(createMapStateToProps, mapDispatchToProps)(QueuedTasksConnector);

View File

@@ -2,7 +2,7 @@ import React from 'react';
import PageContent from 'Components/Page/PageContent';
import PageContentBody from 'Components/Page/PageContentBody';
import translate from 'Utilities/String/translate';
import QueuedTasksConnector from './Queued/QueuedTasksConnector';
import QueuedTasks from './Queued/QueuedTasks';
import ScheduledTasksConnector from './Scheduled/ScheduledTasksConnector';
function Tasks() {
@@ -10,7 +10,7 @@ function Tasks() {
<PageContent title={translate('Tasks')}>
<PageContentBody>
<ScheduledTasksConnector />
<QueuedTasksConnector />
<QueuedTasks />
</PageContentBody>
</PageContent>
);

View File

@@ -3,13 +3,16 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<!-- Chrome, Opera, and Firefox OS -->
<meta name="theme-color" content="#3a3f51" />
<!-- Windows Phone -->
<meta name="msapplication-navbutton-color" content="#3a3f51" />
<!-- Android/Apple Phone -->
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="format-detection" content="telephone=no">
<meta name="description" content="Sonarr" />

View File

@@ -3,13 +3,16 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<!-- Chrome, Opera, and Firefox OS -->
<meta name="theme-color" content="#3a3f51" />
<!-- Windows Phone -->
<meta name="msapplication-navbutton-color" content="#3a3f51" />
<!-- Android/Apple Phone -->
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="format-detection" content="telephone=no">
<meta name="description" content="Sonarr" />

View File

@@ -10,7 +10,7 @@
"watch": "webpack --watch --config ./frontend/build/webpack.config.js",
"lint": "eslint --config frontend/.eslintrc.js --ignore-path frontend/.eslintignore frontend/",
"lint-fix": "yarn lint --fix",
"stylelint": "stylelint frontend/**/*.css --config frontend/.stylelintrc"
"stylelint": "stylelint \"frontend/**/*.css\" --config frontend/.stylelintrc"
},
"repository": "https://github.com/Sonarr/Sonarr",
"author": "Team Sonarr",

View File

@@ -18,6 +18,8 @@ namespace NzbDrone.Common.Test.InstrumentationTests
[TestCase(@"https://baconbits.org/feeds.php?feed=torrents_tv&user=12345&auth=2b51db35e1910123321025a12b9933d2&passkey=mySecret&authkey=2b51db35e1910123321025a12b9933d2")]
[TestCase(@"http://127.0.0.1:9117/dl/indexername?jackett_apikey=flwjiefewklfjacketmySecretsdfldskjfsdlk&path=we0re9f0sdfbase64sfdkfjsdlfjk&file=The+Torrent+File+Name.torrent")]
[TestCase(@"http://nzb.su/getnzb/2b51db35e1912ffc138825a12b9933d2.nzb&i=37292&r=2b51db35e1910123321025a12b9933d2")]
[TestCase(@"https://b-hd.me/torrent/download/auto.343756.is1t1pl127p1sfwur8h4kgyhg1wcsn05")]
[TestCase(@"https://b-hd.me/torrent/download/a-slug-in-the-url.343756.is1t1pl127p1sfwur8h4kgyhg1wcsn05")]
// NzbGet
[TestCase(@"{ ""Name"" : ""ControlUsername"", ""Value"" : ""mySecret"" }, { ""Name"" : ""ControlPassword"", ""Value"" : ""mySecret"" }, ")]

View File

@@ -9,6 +9,7 @@ using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using NLog;
using NzbDrone.Common.Cache;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http.Proxy;
@@ -30,11 +31,14 @@ namespace NzbDrone.Common.Http.Dispatchers
private readonly ICached<System.Net.Http.HttpClient> _httpClientCache;
private readonly ICached<CredentialCache> _credentialCache;
private readonly Logger _logger;
public ManagedHttpDispatcher(IHttpProxySettingsProvider proxySettingsProvider,
ICreateManagedWebProxy createManagedWebProxy,
ICertificateValidationService certificateValidationService,
IUserAgentBuilder userAgentBuilder,
ICacheManager cacheManager)
ICacheManager cacheManager,
Logger logger)
{
_proxySettingsProvider = proxySettingsProvider;
_createManagedWebProxy = createManagedWebProxy;
@@ -43,6 +47,8 @@ namespace NzbDrone.Common.Http.Dispatchers
_httpClientCache = cacheManager.GetCache<System.Net.Http.HttpClient>(typeof(ManagedHttpDispatcher));
_credentialCache = cacheManager.GetCache<CredentialCache>(typeof(ManagedHttpDispatcher), "credentialcache");
_logger = logger;
}
public async Task<HttpResponse> GetResponseAsync(HttpRequest request, CookieContainer cookies)
@@ -249,19 +255,27 @@ namespace NzbDrone.Common.Http.Dispatchers
return _credentialCache.Get("credentialCache", () => new CredentialCache());
}
private static bool HasRoutableIPv4Address()
private bool HasRoutableIPv4Address()
{
// Get all IPv4 addresses from all interfaces and return true if there are any with non-loopback addresses
var networkInterfaces = NetworkInterface.GetAllNetworkInterfaces();
try
{
var networkInterfaces = NetworkInterface.GetAllNetworkInterfaces();
return networkInterfaces.Any(ni =>
ni.OperationalStatus == OperationalStatus.Up &&
ni.GetIPProperties().UnicastAddresses.Any(ip =>
ip.Address.AddressFamily == AddressFamily.InterNetwork &&
!IPAddress.IsLoopback(ip.Address)));
return networkInterfaces.Any(ni =>
ni.OperationalStatus == OperationalStatus.Up &&
ni.GetIPProperties().UnicastAddresses.Any(ip =>
ip.Address.AddressFamily == AddressFamily.InterNetwork &&
!IPAddress.IsLoopback(ip.Address)));
}
catch (Exception e)
{
_logger.Debug(e, "Caught exception while GetAllNetworkInterfaces assuming IPv4 connectivity: {0}", e.Message);
return true;
}
}
private static async ValueTask<Stream> onConnect(SocketsHttpConnectionContext context, CancellationToken cancellationToken)
private async ValueTask<Stream> onConnect(SocketsHttpConnectionContext context, CancellationToken cancellationToken)
{
// Until .NET supports an implementation of Happy Eyeballs (https://tools.ietf.org/html/rfc8305#section-2), let's make IPv4 fallback work in a simple way.
// This issue is being tracked at https://github.com/dotnet/runtime/issues/26177 and expected to be fixed in .NET 6.
@@ -285,7 +299,9 @@ namespace NzbDrone.Common.Http.Dispatchers
catch
{
// Do not retry IPv6 if a routable IPv4 address is available, otherwise continue to attempt IPv6 connections.
useIPv6 = !HasRoutableIPv4Address();
var routableIPv4 = HasRoutableIPv4Address();
_logger.Info("IPv4 is available: {0}, IPv6 will be {1}", routableIPv4, routableIPv4 ? "disabled" : "left enabled");
useIPv6 = !routableIPv4;
}
finally
{

View File

@@ -18,6 +18,7 @@ namespace NzbDrone.Common.Instrumentation
new (@"/fetch/[a-z0-9]{32}/(?<secret>[a-z0-9]{32})", RegexOptions.Compiled),
new (@"getnzb.*?(?<=\?|&)(r)=(?<secret>[^&=]+?)(?= |&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new (@"\b(\w*)?(_?(?<!use|get_)token|username|passwo?rd)=(?<secret>[^&=]+?)(?= |&|$|;)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new (@"-hd.me/torrent/[a-z0-9-]\.[0-9]+\.(?<secret>[0-9a-z]+)", 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", RegexOptions.Compiled | RegexOptions.IgnoreCase),

View File

@@ -4,16 +4,16 @@
<DefineConstants Condition="'$(RuntimeIdentifier)' == 'linux-musl-x64' or '$(RuntimeIdentifier)' == 'linux-musl-arm64'">ISMUSL</DefineConstants>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="DryIoc.dll" Version="5.4.1" />
<PackageReference Include="DryIoc.dll" Version="5.4.3" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.1" />
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="6.0.1" />
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="6.0.2" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="NLog" Version="4.7.14" />
<PackageReference Include="NLog.Targets.Syslog" Version="6.0.3" />
<PackageReference Include="NLog.Extensions.Logging" Version="1.7.4" />
<PackageReference Include="Sentry" Version="4.0.2" />
<PackageReference Include="SharpZipLib" Version="1.4.2" />
<PackageReference Include="System.Text.Json" Version="6.0.8" />
<PackageReference Include="System.Text.Json" Version="6.0.9" />
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
<PackageReference Include="System.Data.SQLite.Core.Servarr" Version="1.0.115.5-18" />
<PackageReference Include="System.Configuration.ConfigurationManager" Version="6.0.1" />

View File

@@ -25,7 +25,7 @@ namespace NzbDrone.Core.Test.Framework
Mocker.SetConstant<IHttpProxySettingsProvider>(new HttpProxySettingsProvider(Mocker.Resolve<ConfigService>()));
Mocker.SetConstant<ICreateManagedWebProxy>(new ManagedWebProxyFactory(Mocker.Resolve<CacheManager>()));
Mocker.SetConstant<ICertificateValidationService>(new X509CertificateValidationService(Mocker.Resolve<ConfigService>(), TestLogger));
Mocker.SetConstant<IHttpDispatcher>(new ManagedHttpDispatcher(Mocker.Resolve<IHttpProxySettingsProvider>(), Mocker.Resolve<ICreateManagedWebProxy>(), Mocker.Resolve<ICertificateValidationService>(), Mocker.Resolve<UserAgentBuilder>(), Mocker.Resolve<CacheManager>()));
Mocker.SetConstant<IHttpDispatcher>(new ManagedHttpDispatcher(Mocker.Resolve<IHttpProxySettingsProvider>(), Mocker.Resolve<ICreateManagedWebProxy>(), Mocker.Resolve<ICertificateValidationService>(), Mocker.Resolve<UserAgentBuilder>(), Mocker.Resolve<CacheManager>(), TestLogger));
Mocker.SetConstant<IHttpClient>(new HttpClient(Array.Empty<IHttpRequestInterceptor>(), Mocker.Resolve<CacheManager>(), Mocker.Resolve<RateLimitService>(), Mocker.Resolve<IHttpDispatcher>(), TestLogger));
Mocker.SetConstant<ISonarrCloudRequestBuilder>(new SonarrCloudRequestBuilder());
}

View File

@@ -120,6 +120,17 @@ namespace NzbDrone.Core.Test.ImportListTests
private void WithImdbId()
{
_list1Series.First().ImdbId = "tt0496424";
Mocker.GetMock<ISearchForNewSeries>()
.Setup(s => s.SearchForNewSeriesByImdbId(_list1Series.First().ImdbId))
.Returns(
Builder<Series>
.CreateListOfSize(1)
.All()
.With(s => s.Title = "Breaking Bad")
.With(s => s.TvdbId = 81189)
.Build()
.ToList());
}
private void WithExistingSeries()
@@ -342,6 +353,7 @@ namespace NzbDrone.Core.Test.ImportListTests
public void should_add_new_series_from_single_list_to_library()
{
_importListFetch.Series.ForEach(m => m.ImportListId = 1);
WithTvdbId();
WithList(1, true);
WithCleanLevel(ListSyncLevelType.Disabled);
@@ -358,6 +370,7 @@ namespace NzbDrone.Core.Test.ImportListTests
_importListFetch.Series.ForEach(m => m.ImportListId = 1);
_importListFetch.Series.AddRange(_list2Series);
WithTvdbId();
WithList(1, true);
WithList(2, true);
@@ -376,6 +389,7 @@ namespace NzbDrone.Core.Test.ImportListTests
_importListFetch.Series.ForEach(m => m.ImportListId = 1);
_importListFetch.Series.AddRange(_list2Series);
WithTvdbId();
WithList(1, true);
WithList(2, false);
@@ -422,12 +436,17 @@ namespace NzbDrone.Core.Test.ImportListTests
public void should_search_by_imdb_if_series_title_and_series_imdb()
{
_importListFetch.Series.ForEach(m => m.ImportListId = 1);
WithList(1, true);
WithImdbId();
Subject.Execute(_commandAll);
Mocker.GetMock<ISearchForNewSeries>()
.Verify(v => v.SearchForNewSeriesByImdbId(It.IsAny<string>()), Times.Once());
Mocker.GetMock<IAddSeriesService>()
.Verify(v => v.AddSeries(It.Is<List<Series>>(t => t.Count == 1), It.IsAny<bool>()));
}
[Test]
@@ -498,5 +517,18 @@ namespace NzbDrone.Core.Test.ImportListTests
Mocker.GetMock<IImportListExclusionService>()
.Verify(v => v.All(), Times.Never);
}
[Test]
public void should_not_add_if_tvdbid_is_0()
{
_importListFetch.Series.ForEach(m => m.ImportListId = 1);
WithList(1, true);
WithExcludedSeries();
Subject.Execute(_commandAll);
Mocker.GetMock<IAddSeriesService>()
.Verify(v => v.AddSeries(It.Is<List<Series>>(t => t.Count == 0), It.IsAny<bool>()));
}
}
}

View File

@@ -0,0 +1,83 @@
using FizzWare.NBuilder;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.MediaFiles.EpisodeImport.Aggregation.Aggregators;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv;
using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Aggregation.Aggregators
{
[TestFixture]
public class AggregateReleaseHashFixture : CoreTest<AggregateReleaseHash>
{
private Series _series;
[SetUp]
public void Setup()
{
_series = Builder<Series>.CreateNew().Build();
}
[Test]
public void should_prefer_file()
{
var fileEpisodeInfo = Parser.Parser.ParseTitle("[DHD] Series Title! - 08 (1280x720 10bit AAC) [ABCDEFGH]");
var folderEpisodeInfo = Parser.Parser.ParseTitle("[DHD] Series Title! - 08 [12345678]");
var downloadClientEpisodeInfo = Parser.Parser.ParseTitle("[DHD] Series Title! - 08 (1280x720 10bit AAC) [ABCD1234]");
var localEpisode = new LocalEpisode
{
FileEpisodeInfo = fileEpisodeInfo,
FolderEpisodeInfo = folderEpisodeInfo,
DownloadClientEpisodeInfo = downloadClientEpisodeInfo,
Path = @"C:\Test\Unsorted TV\Series.Title.S01\Series.Title.S01E01.mkv".AsOsAgnostic(),
Series = _series
};
Subject.Aggregate(localEpisode, null);
localEpisode.ReleaseHash.Should().Be("ABCDEFGH");
}
[Test]
public void should_fallback_to_downloadclient()
{
var fileEpisodeInfo = Parser.Parser.ParseTitle("[DHD] Series Title! - 08 (1280x720 10bit AAC)");
var downloadClientEpisodeInfo = Parser.Parser.ParseTitle("[DHD] Series Title! - 08 (1280x720 10bit AAC) [ABCD1234]");
var folderEpisodeInfo = Parser.Parser.ParseTitle("[DHD] Series Title! - 08 [12345678]");
var localEpisode = new LocalEpisode
{
FileEpisodeInfo = fileEpisodeInfo,
FolderEpisodeInfo = folderEpisodeInfo,
DownloadClientEpisodeInfo = downloadClientEpisodeInfo,
Path = @"C:\Test\Unsorted TV\Series.Title.S01\Series.Title.S01E01.WEB-DL.mkv".AsOsAgnostic(),
Series = _series
};
Subject.Aggregate(localEpisode, null);
localEpisode.ReleaseHash.Should().Be("ABCD1234");
}
[Test]
public void should_fallback_to_folder()
{
var fileEpisodeInfo = Parser.Parser.ParseTitle("[DHD] Series Title! - 08 (1280x720 10bit AAC)");
var downloadClientEpisodeInfo = Parser.Parser.ParseTitle("[DHD] Series Title! - 08 (1280x720 10bit AAC)");
var folderEpisodeInfo = Parser.Parser.ParseTitle("[DHD] Series Title! - 08 [12345678]");
var localEpisode = new LocalEpisode
{
FileEpisodeInfo = fileEpisodeInfo,
FolderEpisodeInfo = folderEpisodeInfo,
DownloadClientEpisodeInfo = downloadClientEpisodeInfo,
Path = @"C:\Test\Unsorted TV\Series.Title.S01\Series.Title.S01E01.WEB-DL.mkv".AsOsAgnostic(),
Series = _series
};
Subject.Aggregate(localEpisode, null);
localEpisode.ReleaseHash.Should().Be("12345678");
}
}
}

View File

@@ -9,13 +9,14 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Aggregation.Aggregators
[TestFixture]
public class AggregateSubtitleInfoFixture : CoreTest<AggregateSubtitleInfo>
{
[TestCase("Name (2020)/Season 1/Name (2020) - S01E20 - [AAC 2.0].mkv", "", "Name (2020) - S01E20 - [AAC 2.0].default.eng.forced.ass")]
[TestCase("Name (2020)/Season 1/Name (2020) - S01E20 - [AAC 2.0].mkv", "", "Name (2020) - S01E20 - [AAC 2.0].eng.default.ass")]
[TestCase("Name (2020)/Season 1/Name (2020) - S01E20 - [AAC 2.0].mkv", "", "Name (2020) - S01E20 - [AAC 2.0].fra.ass")]
[TestCase("", "Name (2020)/Season 1/Name (2020) - S01E20 - [AAC 2.0].mkv", "Name (2020) - S01E20 - [AAC 2.0].default.eng.forced.ass")]
[TestCase("", "Name (2020)/Season 1/Name (2020) - S01E20 - [AAC 2.0].mkv", "Name (2020) - S01E20 - [AAC 2.0].eng.default.ass")]
[TestCase("", "Name (2020)/Season 1/Name (2020) - S01E20 - [AAC 2.0].mkv", "Name (2020) - S01E20 - [AAC 2.0].fra.ass")]
public void should_do_basic_parse(string relativePath, string originalFilePath, string path)
[TestCase("Name (2020)/Season 1/Name (2020) - S01E20 - [AAC 2.0].mkv", "", "Name (2020) - S01E20 - [AAC 2.0].default.eng.forced.ass", null)]
[TestCase("Name (2020)/Season 1/Name (2020) - S01E20 - [AAC 2.0].mkv", "", "Name (2020) - S01E20 - [AAC 2.0].eng.default.ass", null)]
[TestCase("Name (2020)/Season 1/Name (2020) - S01E20 - [AAC 2.0].mkv", "", "Name (2020) - S01E20 - [AAC 2.0].fra.ass", null)]
[TestCase("Name (2020)/Season 1/Name (2020) - S01E20 - [AAC 5.1].mkv", "", "Name (2020) - S01E20 - [FLAC 2.0].fra.ass", "Name (2020)/Season 1/Name (2020) - S01E20 - [FLAC 2.0].mkv")]
[TestCase("", "Name (2020)/Season 1/Name (2020) - S01E20 - [AAC 2.0].mkv", "Name (2020) - S01E20 - [AAC 2.0].default.eng.forced.ass", null)]
[TestCase("", "Name (2020)/Season 1/Name (2020) - S01E20 - [AAC 2.0].mkv", "Name (2020) - S01E20 - [AAC 2.0].eng.default.ass", null)]
[TestCase("", "Name (2020)/Season 1/Name (2020) - S01E20 - [AAC 2.0].mkv", "Name (2020) - S01E20 - [AAC 2.0].fra.ass", null)]
public void should_do_basic_parse(string relativePath, string originalFilePath, string path, string fileNameBeforeRename)
{
var episodeFile = new EpisodeFile
{
@@ -23,7 +24,7 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Aggregation.Aggregators
OriginalFilePath = originalFilePath
};
var subtitleTitleInfo = Subject.CleanSubtitleTitleInfo(episodeFile, path);
var subtitleTitleInfo = Subject.CleanSubtitleTitleInfo(episodeFile, path, fileNameBeforeRename);
subtitleTitleInfo.Title.Should().BeNull();
subtitleTitleInfo.Copy.Should().Be(0);
@@ -40,7 +41,7 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Aggregation.Aggregators
RelativePath = relativePath
};
var subtitleTitleInfo = Subject.CleanSubtitleTitleInfo(episodeFile, path);
var subtitleTitleInfo = Subject.CleanSubtitleTitleInfo(episodeFile, path, null);
subtitleTitleInfo.LanguageTags.Should().NotContain("default");
}

View File

@@ -0,0 +1,146 @@
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 CustomFormatsFixture : CoreTest<FileNameBuilder>
{
private Series _series;
private Episode _episode1;
private EpisodeFile _episodeFile;
private NamingConfig _namingConfig;
private List<CustomFormat> _customFormats;
[SetUp]
public void Setup()
{
_series = Builder<Series>
.CreateNew()
.With(s => s.Title = "South Park")
.Build();
_namingConfig = NamingConfig.Default;
_namingConfig.RenameEpisodes = true;
Mocker.GetMock<INamingConfigService>()
.Setup(c => c.GetConfig()).Returns(_namingConfig);
_episode1 = 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" };
_customFormats = new List<CustomFormat>()
{
new CustomFormat()
{
Name = "INTERNAL",
IncludeCustomFormatWhenRenaming = true
},
new CustomFormat()
{
Name = "AMZN",
IncludeCustomFormatWhenRenaming = true
},
new CustomFormat()
{
Name = "NAME WITH SPACES",
IncludeCustomFormatWhenRenaming = true
},
new CustomFormat()
{
Name = "NotIncludedFormat",
IncludeCustomFormatWhenRenaming = false
}
};
Mocker.GetMock<IQualityDefinitionService>()
.Setup(v => v.Get(Moq.It.IsAny<Quality>()))
.Returns<Quality>(v => Quality.DefaultQualityDefinitions.First(c => c.Quality == v));
}
[TestCase("{Custom Formats}", "INTERNAL AMZN NAME WITH SPACES")]
public void should_replace_custom_formats(string format, string expected)
{
_namingConfig.StandardEpisodeFormat = format;
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile, customFormats: _customFormats)
.Should().Be(expected);
}
[TestCase("{Custom Formats}", "")]
public void should_replace_custom_formats_with_no_custom_formats(string format, string expected)
{
_namingConfig.StandardEpisodeFormat = format;
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile, customFormats: new List<CustomFormat>())
.Should().Be(expected);
}
[TestCase("{Custom Formats:-INTERNAL}", "AMZN NAME WITH SPACES")]
[TestCase("{Custom Formats:-NAME WITH SPACES}", "INTERNAL AMZN")]
[TestCase("{Custom Formats:-INTERNAL,NAME WITH SPACES}", "AMZN")]
[TestCase("{Custom Formats:INTERNAL}", "INTERNAL")]
[TestCase("{Custom Formats:NAME WITH SPACES}", "NAME WITH SPACES")]
[TestCase("{Custom Formats:INTERNAL,NAME WITH SPACES}", "INTERNAL NAME WITH SPACES")]
public void should_replace_custom_formats_with_filtered_names(string format, string expected)
{
_namingConfig.StandardEpisodeFormat = format;
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile, customFormats: _customFormats)
.Should().Be(expected);
}
[TestCase("{Custom Formats:-}", "{Custom Formats:-}")]
[TestCase("{Custom Formats:}", "{Custom Formats:}")]
public void should_not_replace_custom_formats_due_to_invalid_token(string format, string expected)
{
_namingConfig.StandardEpisodeFormat = format;
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile, customFormats: _customFormats)
.Should().Be(expected);
}
[TestCase("{Custom Format}", "")]
[TestCase("{Custom Format:INTERNAL}", "INTERNAL")]
[TestCase("{Custom Format:AMZN}", "AMZN")]
[TestCase("{Custom Format:NAME WITH SPACES}", "NAME WITH SPACES")]
[TestCase("{Custom Format:DOESNOTEXIST}", "")]
[TestCase("{Custom Format:INTERNAL} - {Custom Format:AMZN}", "INTERNAL - AMZN")]
[TestCase("{Custom Format:AMZN} - {Custom Format:INTERNAL}", "AMZN - INTERNAL")]
public void should_replace_custom_format(string format, string expected)
{
_namingConfig.StandardEpisodeFormat = format;
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile, customFormats: _customFormats)
.Should().Be(expected);
}
[TestCase("{Custom Format}", "")]
[TestCase("{Custom Format:INTERNAL}", "")]
[TestCase("{Custom Format:AMZN}", "")]
public void should_replace_custom_format_with_no_custom_formats(string format, string expected)
{
_namingConfig.StandardEpisodeFormat = format;
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile, customFormats: new List<CustomFormat>())
.Should().Be(expected);
}
}
}

View File

@@ -991,6 +991,28 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
result.Should().EndWith("HDR");
}
[Test]
public void should_replace_release_hash_with_stored_hash()
{
_namingConfig.StandardEpisodeFormat = "{Release Hash}";
_episodeFile.ReleaseHash = "ABCDEFGH";
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be("ABCDEFGH");
}
[Test]
public void should_replace_null_release_hash_with_empty_string()
{
_namingConfig.StandardEpisodeFormat = "{Release Hash}";
_episodeFile.ReleaseHash = null;
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be(string.Empty);
}
private void GivenMediaInfoModel(string videoCodec = "h264",
string audioCodec = "dts",
int audioChannels = 6,

View File

@@ -132,6 +132,8 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("[Naruto-Kun.Hu] Dr Series S3 - 21 [1080p]", "Dr Series S3", 21, 0, 0)]
[TestCase("[Naruto-Kun.Hu] Series Title - 12 [1080p].mkv", "Series Title", 12, 0, 0)]
[TestCase("[Naruto-Kun.Hu] Anime Triangle - 08 [1080p].mkv", "Anime Triangle", 8, 0, 0)]
[TestCase("[Mystic Z-Team] Series Title Super - Episode 013 VF - Non-censuré [720p].mp4", "Series Title Super", 13, 0, 0)]
[TestCase("Series Title Kai Episodio 13 Audio Latino", "Series Title Kai", 13, 0, 0)]
// [TestCase("", "", 0, 0, 0)]
public void should_parse_absolute_numbers(string postTitle, string title, int absoluteEpisodeNumber, int seasonNumber, int episodeNumber)

View File

@@ -22,12 +22,42 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("Series Title - 031 - The Resolution to Kill [Lunar].avi", "Lunar", "")]
[TestCase("[ACX]Series Title 01 Episode Name [Kosaka] [9C57891E].mkv", "ACX", "9C57891E")]
[TestCase("[S-T-D] Series Title! - 06 (1280x720 10bit AAC) [59B3F2EA].mkv", "S-T-D", "59B3F2EA")]
public void should_parse_absolute_numbers(string postTitle, string subGroup, string hash)
// These tests are dupes of the above, except with parenthesized hashes instead of square bracket
[TestCase("[SubDESU]_Show_Title_DxD_07_(1280x720_x264-AAC)_(6B7FD717)", "SubDESU", "6B7FD717")]
[TestCase("[Chihiro]_Show_Title!!_-_06_[848x480_H.264_AAC](859EEAFA)", "Chihiro", "859EEAFA")]
[TestCase("[Underwater]_Show_Title_-_12_(720p)_(5C7BC4F9)", "Underwater", "5C7BC4F9")]
[TestCase("[HorribleSubs]_Show_Title_-_33_[720p]", "HorribleSubs", "")]
[TestCase("[HorribleSubs] Show-Title - 13 [1080p].mkv", "HorribleSubs", "")]
[TestCase("[Doremi].Show.Title.5.Go.Go!.31.[1280x720].(C65D4B1F).mkv", "Doremi", "C65D4B1F")]
[TestCase("[Doremi].Show.Title.5.Go.Go!.31[1280x720].(C65D4B1F)", "Doremi", "C65D4B1F")]
[TestCase("[Doremi].Show.Title.5.Go.Go!.31.[1280x720].mkv", "Doremi", "")]
[TestCase("[K-F] Series Title 214", "K-F", "")]
[TestCase("[K-F] Series Title S10E14 214", "K-F", "")]
[TestCase("[K-F] Series Title 10x14 214", "K-F", "")]
[TestCase("[K-F] Series Title 214 10x14", "K-F", "")]
[TestCase("Series Title - 031 - The Resolution to Kill [Lunar].avi", "Lunar", "")]
[TestCase("[ACX]Series Title 01 Episode Name [Kosaka] (9C57891E).mkv", "ACX", "9C57891E")]
[TestCase("[S-T-D] Series Title! - 06 (1280x720 10bit AAC) (59B3F2EA).mkv", "S-T-D", "59B3F2EA")]
public void should_parse_releasegroup_and_hash(string postTitle, string subGroup, string hash)
{
var result = Parser.Parser.ParseTitle(postTitle);
result.Should().NotBeNull();
result.ReleaseGroup.Should().Be(subGroup);
result.ReleaseHash.Should().Be(hash);
}
[TestCase("[DHD] Series Title! - 08 (1280x720 10bit AAC) [8B00F2EA].mkv", "8B00F2EA")]
[TestCase("[DHD] Series Title! - 10 (1280x720 10bit AAC) [10BBF2EA].mkv", "10BBF2EA")]
[TestCase("[DHD] Series Title! - 08 (1280x720 10bit AAC) [008BF28B].mkv", "008BF28B")]
[TestCase("[DHD] Series Title! - 10 (1280x720 10bit AAC) [000BF10B].mkv", "000BF10B")]
[TestCase("[DHD] Series Title! - 08 (1280x720 8bit AAC) [8B8BF2EA].mkv", "8B8BF2EA")]
[TestCase("[DHD] Series Title! - 10 (1280x720 8bit AAC) [10B10BEA].mkv", "10B10BEA")]
public void should_parse_release_hashes_with_10b_or_8b(string postTitle, string hash)
{
var result = Parser.Parser.ParseTitle(postTitle);
result.Should().NotBeNull();
result.ReleaseHash.Should().Be(hash);
}
}
}

View File

@@ -444,6 +444,7 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("Name (2020) - S01E20 - [AAC 2.0].ru-something-else.srt", new string[0], "something-else", "Russian")]
[TestCase("Name (2020) - S01E20 - [AAC 2.0].Full Subtitles.eng.ass", new string[0], "Full Subtitles", "English")]
[TestCase("Name (2020) - S01E20 - [AAC 2.0].mytitle - 1.en.ass", new string[0], "mytitle", "English")]
[TestCase("Name (2020) - S01E20 - [AAC 2.0].mytitle 1.en.ass", new string[0], "mytitle 1", "English")]
[TestCase("Name (2020) - S01E20 - [AAC 2.0].mytitle.en.ass", new string[0], "mytitle", "English")]
public void should_parse_title_and_tags(string postTitle, string[] expectedTags, string expectedTitle, string expectedLanguage)
{

View File

@@ -77,6 +77,7 @@ namespace NzbDrone.Core.Test.ParserTests
[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("Босх: Спадок / Series: Legacy / S2E1-4 of 10 (2023) WEB-DL 1080p Ukr/Eng | sub Eng", "Series: Legacy", 2, new[] { 1, 2, 3, 4 })]
[TestCase("Series Title - S26E96-97-98-99-100 - Episode 5931 + Episode 5932 + Episode 5933 + Episode 5934 + Episode 5935", "Series Title", 26, new[] { 96, 97, 98, 99, 100 })]
// [TestCase("", "", , new [] { })]
public void should_parse_multiple_episodes(string postTitle, string title, int season, int[] episodes)

View File

@@ -29,6 +29,10 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase(@"C:\Test\Series\Season 01\1 Pilot (1080p HD).mkv", 1, 1)]
[TestCase(@"C:\Test\Series\Season 1\02 Honor Thy Father (1080p HD).m4v", 1, 2)]
[TestCase(@"C:\Test\Series\Season 1\2 Honor Thy Developer (1080p HD).m4v", 1, 2)]
[TestCase(@"C:\Test\Series\Season 2 - Total Series Action\01. Total Series Action - Episode 1 - Monster Cash.mkv", 2, 1)]
[TestCase(@"C:\Test\Series\Season 2\01. Total Series Action - Episode 1 - Monster Cash.mkv", 2, 1)]
[TestCase(@"C:\Test\Series\Season 1\02.04.24 - S01E01 - The Rabbit Hole", 1, 1)]
[TestCase(@"C:\Test\Series\Season 1\8 Series Rules - S01E01 - Pilot", 1, 1)]
// [TestCase(@"C:\series.state.S02E04.720p.WEB-DL.DD5.1.H.264\73696S02-04.mkv", 2, 4)] //Gets treated as S01E04 (because it gets parsed as anime); 2020-01 broken test case: Expected result.EpisodeNumbers to contain 1 item(s), but found 0
public void should_parse_from_path(string path, int season, int episode)
@@ -45,6 +49,7 @@ namespace NzbDrone.Core.Test.ParserTests
}
[TestCase("01-03\\The Series Title (2010) - 1x01-02-03 - Episode Title HDTV-720p Proper", "The Series Title (2010)", 1, new[] { 1, 2, 3 })]
[TestCase("Season 2\\E05-06 - Episode Title HDTV-720p Proper", "", 2, new[] { 5, 6 })]
public void should_parse_multi_episode_from_path(string path, string title, int season, int[] episodes)
{
var result = Parser.Parser.ParsePath(path.AsOsAgnostic());

View File

@@ -44,6 +44,7 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("[Erai-raws] Series - 0955 ~ 1005 [1080p]", "Erai-raws")]
[TestCase("[Exiled-Destiny] Series Title", "Exiled-Destiny")]
[TestCase("Series.Title.S01E09.1080p.DSNP.WEB-DL.DDP2.0.H.264-VARYG", "VARYG")]
[TestCase("Stargate SG-1 (1997) - S01E01-02 - Children of the Gods (Showtime) (1080p.BD.DD5.1.x265-TheSickle[TAoE])", "TheSickle")]
// [TestCase("", "")]
public void should_parse_release_group(string title, string expected)

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Core.Datastore;

View File

@@ -0,0 +1,76 @@
using System.Collections.Generic;
using System.Data;
using System.IO;
using Dapper;
using FluentMigrator;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Datastore.Migration.Framework;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(204)]
public class add_add_release_hash : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Alter.Table("EpisodeFiles").AddColumn("ReleaseHash").AsString().Nullable();
Execute.WithConnection(UpdateEpisodeFiles);
}
private void UpdateEpisodeFiles(IDbConnection conn, IDbTransaction tran)
{
var updates = new List<object>();
using (var cmd = conn.CreateCommand())
{
cmd.Transaction = tran;
cmd.CommandText = "SELECT \"Id\", \"SceneName\", \"RelativePath\", \"OriginalFilePath\" FROM \"EpisodeFiles\"";
using var reader = cmd.ExecuteReader();
while (reader.Read())
{
var id = reader.GetInt32(0);
var sceneName = reader[1] as string;
var relativePath = reader[2] as string;
var originalFilePath = reader[3] as string;
ParsedEpisodeInfo parsedEpisodeInfo = null;
var originalTitle = sceneName;
if (originalTitle.IsNullOrWhiteSpace() && originalFilePath.IsNotNullOrWhiteSpace())
{
originalTitle = Path.GetFileNameWithoutExtension(originalFilePath);
}
if (originalTitle.IsNotNullOrWhiteSpace())
{
parsedEpisodeInfo = Parser.Parser.ParseTitle(originalTitle);
}
if (parsedEpisodeInfo == null || parsedEpisodeInfo.ReleaseHash.IsNullOrWhiteSpace())
{
parsedEpisodeInfo = Parser.Parser.ParseTitle(Path.GetFileNameWithoutExtension(relativePath));
}
if (parsedEpisodeInfo != null && parsedEpisodeInfo.ReleaseHash.IsNotNullOrWhiteSpace())
{
updates.Add(new
{
Id = id,
ReleaseHash = parsedEpisodeInfo.ReleaseHash
});
}
}
}
if (updates.Count > 0)
{
var updateEpisodeFilesSql = "UPDATE \"EpisodeFiles\" SET \"ReleaseHash\" = @ReleaseHash WHERE \"Id\" = @Id";
conn.Execute(updateEpisodeFilesSql, updates, transaction: tran);
}
}
}
}

View File

@@ -388,16 +388,20 @@ 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<OsPath> { _remotePathMappingService.RemapRemoteToLocal(Settings.Host, destDir) },
RemovesCompletedDownloads = (config.MaxRatioEnabled || (config.MaxSeedingTimeEnabled && config.MaxSeedingTime < minimumRetention)) && (config.MaxRatioAction == QBittorrentMaxRatioAction.Remove || config.MaxRatioAction == QBittorrentMaxRatioAction.DeleteFiles)
RemovesCompletedDownloads = RemovesCompletedDownloads(config)
};
}
private bool RemovesCompletedDownloads(QBittorrentPreferences config)
{
var minimumRetention = 60 * 24 * 14; // 14 days in minutes
return (config.MaxRatioEnabled || (config.MaxSeedingTimeEnabled && config.MaxSeedingTime < minimumRetention)) && (config.MaxRatioAction == QBittorrentMaxRatioAction.Remove || config.MaxRatioAction == QBittorrentMaxRatioAction.DeleteFiles);
}
protected override void Test(List<ValidationFailure> failures)
{
failures.AddIfNotNull(TestConnection());
@@ -448,7 +452,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
// Complain if qBittorrent is configured to remove torrents on max ratio
var config = Proxy.GetConfig(Settings);
if ((config.MaxRatioEnabled || config.MaxSeedingTimeEnabled) && (config.MaxRatioAction == QBittorrentMaxRatioAction.Remove || config.MaxRatioAction == QBittorrentMaxRatioAction.DeleteFiles))
if (RemovesCompletedDownloads(config))
{
return new NzbDroneValidationFailure(string.Empty, _localizationService.GetLocalizedString("DownloadClientQbittorrentValidationRemovesAtRatioLimit"))
{

View File

@@ -139,12 +139,14 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
// Ignore torrents with an empty path
if (torrent.Path.IsNullOrWhiteSpace())
{
_logger.Warn("Torrent '{0}' has an empty download path and will not be processed. Adjust this to an absolute path in rTorrent", torrent.Name);
continue;
}
if (torrent.Path.StartsWith("."))
{
throw new DownloadClientException("Download paths must be absolute. Please specify variable \"directory\" in rTorrent.");
_logger.Warn("Torrent '{0}' has a download path starting with '.' and will not be processed. Adjust this to an absolute path in rTorrent", torrent.Name);
continue;
}
var item = new DownloadClientItem();

View File

@@ -10,7 +10,7 @@ namespace NzbDrone.Core.Extras
{
public interface IExistingExtraFiles
{
List<string> ImportExtraFiles(Series series, List<string> possibleExtraFiles);
List<string> ImportExtraFiles(Series series, List<string> possibleExtraFiles, string fileNameBeforeRename);
}
public class ExistingExtraFileService : IExistingExtraFiles, IHandle<SeriesScannedEvent>
@@ -25,7 +25,7 @@ namespace NzbDrone.Core.Extras
_logger = logger;
}
public List<string> ImportExtraFiles(Series series, List<string> possibleExtraFiles)
public List<string> ImportExtraFiles(Series series, List<string> possibleExtraFiles, string fileNameBeforeRename)
{
_logger.Debug("Looking for existing extra files in {0}", series.Path);
@@ -33,7 +33,7 @@ namespace NzbDrone.Core.Extras
foreach (var existingExtraFileImporter in _existingExtraFileImporters)
{
var imported = existingExtraFileImporter.ProcessFiles(series, possibleExtraFiles, importedFiles);
var imported = existingExtraFileImporter.ProcessFiles(series, possibleExtraFiles, importedFiles, fileNameBeforeRename);
importedFiles.AddRange(imported.Select(f => Path.Combine(series.Path, f.RelativePath)));
}
@@ -45,7 +45,7 @@ namespace NzbDrone.Core.Extras
{
var series = message.Series;
var possibleExtraFiles = message.PossibleExtraFiles;
var importedFiles = ImportExtraFiles(series, possibleExtraFiles);
var importedFiles = ImportExtraFiles(series, possibleExtraFiles, null);
_logger.Info("Found {0} possible extra files, imported {1} files.", possibleExtraFiles.Count, importedFiles.Count);
}

View File

@@ -7,6 +7,6 @@ namespace NzbDrone.Core.Extras
public interface IImportExistingExtraFiles
{
int Order { get; }
IEnumerable<ExtraFile> ProcessFiles(Series series, List<string> filesOnDisk, List<string> importedFiles);
IEnumerable<ExtraFile> ProcessFiles(Series series, List<string> filesOnDisk, List<string> importedFiles, string fileNameBeforeRename);
}
}

View File

@@ -19,12 +19,21 @@ namespace NzbDrone.Core.Extras
}
public abstract int Order { get; }
public abstract IEnumerable<ExtraFile> ProcessFiles(Series series, List<string> filesOnDisk, List<string> importedFiles);
public abstract IEnumerable<ExtraFile> ProcessFiles(Series series, List<string> filesOnDisk, List<string> importedFiles, string fileNameBeforeRename);
public virtual ImportExistingExtraFileFilterResult<TExtraFile> FilterAndClean(Series series, List<string> filesOnDisk, List<string> importedFiles)
public virtual ImportExistingExtraFileFilterResult<TExtraFile> FilterAndClean(Series series, List<string> filesOnDisk, List<string> importedFiles, bool keepExistingEntries)
{
var seriesFiles = _extraFileService.GetFilesBySeries(series.Id);
if (keepExistingEntries)
{
var incompleteImports = seriesFiles.IntersectBy(f => Path.Combine(series.Path, f.RelativePath), filesOnDisk, i => i, PathEqualityComparer.Instance).Select(f => f.Id);
_extraFileService.DeleteMany(incompleteImports);
return Filter(series, filesOnDisk, importedFiles, new List<TExtraFile>());
}
Clean(series, filesOnDisk, importedFiles, seriesFiles);
return Filter(series, filesOnDisk, importedFiles, seriesFiles);

View File

@@ -33,12 +33,12 @@ namespace NzbDrone.Core.Extras.Metadata
public override int Order => 0;
public override IEnumerable<ExtraFile> ProcessFiles(Series series, List<string> filesOnDisk, List<string> importedFiles)
public override IEnumerable<ExtraFile> ProcessFiles(Series series, List<string> filesOnDisk, List<string> importedFiles, string fileNameBeforeRename)
{
_logger.Debug("Looking for existing metadata in {0}", series.Path);
var metadataFiles = new List<MetadataFile>();
var filterResult = FilterAndClean(series, filesOnDisk, importedFiles);
var filterResult = FilterAndClean(series, filesOnDisk, importedFiles, fileNameBeforeRename is not null);
foreach (var possibleMetadataFile in filterResult.FilesOnDisk)
{

View File

@@ -28,12 +28,12 @@ namespace NzbDrone.Core.Extras.Others
public override int Order => 2;
public override IEnumerable<ExtraFile> ProcessFiles(Series series, List<string> filesOnDisk, List<string> importedFiles)
public override IEnumerable<ExtraFile> ProcessFiles(Series series, List<string> filesOnDisk, List<string> importedFiles, string fileNameBeforeRename)
{
_logger.Debug("Looking for existing extra files in {0}", series.Path);
var extraFiles = new List<OtherExtraFile>();
var filterResult = FilterAndClean(series, filesOnDisk, importedFiles);
var filterResult = FilterAndClean(series, filesOnDisk, importedFiles, fileNameBeforeRename is not null);
foreach (var possibleExtraFile in filterResult.FilesOnDisk)
{

View File

@@ -29,12 +29,12 @@ namespace NzbDrone.Core.Extras.Subtitles
public override int Order => 1;
public override IEnumerable<ExtraFile> ProcessFiles(Series series, List<string> filesOnDisk, List<string> importedFiles)
public override IEnumerable<ExtraFile> ProcessFiles(Series series, List<string> filesOnDisk, List<string> importedFiles, string fileNameBeforeRename)
{
_logger.Debug("Looking for existing subtitle files in {0}", series.Path);
var subtitleFiles = new List<SubtitleFile>();
var filterResult = FilterAndClean(series, filesOnDisk, importedFiles);
var filterResult = FilterAndClean(series, filesOnDisk, importedFiles, fileNameBeforeRename is not null);
foreach (var possibleSubtitleFile in filterResult.FilesOnDisk)
{
@@ -46,7 +46,8 @@ namespace NzbDrone.Core.Extras.Subtitles
{
FileEpisodeInfo = Parser.Parser.ParsePath(possibleSubtitleFile),
Series = series,
Path = possibleSubtitleFile
Path = possibleSubtitleFile,
FileNameBeforeRename = fileNameBeforeRename
};
try

View File

@@ -57,7 +57,7 @@ namespace NzbDrone.Core.HealthCheck.Checks
_localizationService.GetLocalizedString("DownloadClientRootFolderHealthCheckMessage", new Dictionary<string, object>
{
{ "downloadClientName", client.Definition.Name },
{ "path", folder.FullPath }
{ "rootFolderPath", folder.FullPath }
}),
"#downloads-in-root-folder");
}

View File

@@ -190,6 +190,29 @@ namespace NzbDrone.Core.ImportLists
item.Title = mappedSeries.Title;
}
// Map by MyAniList ID if we have it
if (item.TvdbId <= 0 && item.MalId > 0)
{
var mappedSeries = _seriesSearchService.SearchForNewSeriesByMyAnimeListId(item.MalId)
.FirstOrDefault();
if (mappedSeries == null)
{
_logger.Debug("Rejected, unable to find matching TVDB ID for MAL ID: {0} [{1}]", item.MalId, item.Title);
continue;
}
item.TvdbId = mappedSeries.TvdbId;
item.Title = mappedSeries.Title;
}
if (item.TvdbId == 0)
{
_logger.Debug("[{0}] Rejected, unable to find TVDB ID", item.Title);
continue;
}
// Check to see if series excluded
var excludedSeries = listExclusions.Where(s => s.TvdbId == item.TvdbId).SingleOrDefault();
@@ -202,7 +225,7 @@ namespace NzbDrone.Core.ImportLists
// Break if Series Exists in DB
if (existingTvdbIds.Any(x => x == item.TvdbId))
{
_logger.Debug("{0} [{1}] Rejected, Series Exists in DB", item.TvdbId, item.Title);
_logger.Debug("{0} [{1}] Rejected, series exists in database", item.TvdbId, item.Title);
continue;
}

View File

@@ -0,0 +1,121 @@
using System;
using System.Collections.Generic;
using System.Net.Http;
using NLog;
using NzbDrone.Common.Cloud;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Localization;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.ImportLists.MyAnimeList
{
public class MyAnimeListImport : HttpImportListBase<MyAnimeListSettings>
{
public const string OAuthPath = "oauth/myanimelist/authorize";
public const string RedirectUriPath = "oauth/myanimelist/auth";
public const string RenewUriPath = "oauth/myanimelist/renew";
public override string Name => "MyAnimeList";
public override ImportListType ListType => ImportListType.Other;
public override TimeSpan MinRefreshInterval => TimeSpan.FromHours(6);
private readonly IImportListRepository _importListRepository;
private readonly IHttpRequestBuilderFactory _requestBuilder;
// This constructor the first thing that is called when sonarr creates a button
public MyAnimeListImport(IImportListRepository netImportRepository, IHttpClient httpClient, IImportListStatusService importListStatusService, IConfigService configService, IParsingService parsingService, ILocalizationService localizationService, ISonarrCloudRequestBuilder requestBuilder, Logger logger)
: base(httpClient, importListStatusService, configService, parsingService, localizationService, logger)
{
_importListRepository = netImportRepository;
_requestBuilder = requestBuilder.Services;
}
public override ImportListFetchResult Fetch()
{
if (Settings.Expires < DateTime.UtcNow.AddMinutes(5))
{
RefreshToken();
}
return FetchItems(g => g.GetListItems());
}
// MAL OAuth info: https://myanimelist.net/blog.php?eid=835707
// The whole process is handled through Sonarr's services.
public override object RequestAction(string action, IDictionary<string, string> query)
{
if (action == "startOAuth")
{
var request = _requestBuilder.Create()
.Resource(OAuthPath)
.AddQueryParam("state", query["callbackUrl"])
.AddQueryParam("redirect_uri", _requestBuilder.Create().Resource(RedirectUriPath).Build().Url.ToString())
.Build();
return new
{
OauthUrl = request.Url.ToString()
};
}
else if (action == "getOAuthToken")
{
return new
{
accessToken = query["access_token"],
expires = DateTime.UtcNow.AddSeconds(int.Parse(query["expires_in"])),
refreshToken = query["refresh_token"]
};
}
return new { };
}
public override IParseImportListResponse GetParser()
{
return new MyAnimeListParser();
}
public override IImportListRequestGenerator GetRequestGenerator()
{
return new MyAnimeListRequestGenerator()
{
Settings = Settings,
};
}
private void RefreshToken()
{
_logger.Trace("Refreshing Token");
Settings.Validate().Filter("RefreshToken").ThrowOnError();
var httpReq = _requestBuilder.Create()
.Resource(RenewUriPath)
.AddQueryParam("refresh_token", Settings.RefreshToken)
.Build();
try
{
var httpResp = _httpClient.Get<MyAnimeListAuthToken>(httpReq);
if (httpResp?.Resource != null)
{
var token = httpResp.Resource;
Settings.AccessToken = token.AccessToken;
Settings.Expires = DateTime.UtcNow.AddSeconds(token.ExpiresIn);
Settings.RefreshToken = token.RefreshToken ?? Settings.RefreshToken;
if (Definition.Id > 0)
{
_importListRepository.UpdateSettings((ImportListDefinition)Definition);
}
}
}
catch (HttpRequestException)
{
_logger.Error("Error trying to refresh MAL access token.");
}
}
}
}

View File

@@ -0,0 +1,31 @@
using System.Collections.Generic;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Instrumentation;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.ImportLists.MyAnimeList
{
public class MyAnimeListParser : IParseImportListResponse
{
private static readonly Logger Logger = NzbDroneLogger.GetLogger(typeof(MyAnimeListParser));
public IList<ImportListItemInfo> ParseResponse(ImportListResponse importListResponse)
{
var jsonResponse = Json.Deserialize<MyAnimeListResponse>(importListResponse.Content);
var series = new List<ImportListItemInfo>();
foreach (var show in jsonResponse.Animes)
{
series.AddIfNotNull(new ImportListItemInfo
{
Title = show.AnimeListInfo.Title,
MalId = show.AnimeListInfo.Id
});
}
return series;
}
}
}

View File

@@ -0,0 +1,50 @@
using System.Collections.Generic;
using NzbDrone.Common.Http;
namespace NzbDrone.Core.ImportLists.MyAnimeList
{
public class MyAnimeListRequestGenerator : IImportListRequestGenerator
{
public MyAnimeListSettings Settings { get; set; }
private static readonly Dictionary<MyAnimeListStatus, string> StatusMapping = new Dictionary<MyAnimeListStatus, string>
{
{ MyAnimeListStatus.Watching, "watching" },
{ MyAnimeListStatus.Completed, "completed" },
{ MyAnimeListStatus.OnHold, "on_hold" },
{ MyAnimeListStatus.Dropped, "dropped" },
{ MyAnimeListStatus.PlanToWatch, "plan_to_watch" },
};
public virtual ImportListPageableRequestChain GetListItems()
{
var pageableReq = new ImportListPageableRequestChain();
pageableReq.Add(GetSeriesRequest());
return pageableReq;
}
private IEnumerable<ImportListRequest> GetSeriesRequest()
{
var status = (MyAnimeListStatus)Settings.ListStatus;
var requestBuilder = new HttpRequestBuilder(Settings.BaseUrl.Trim());
requestBuilder.Resource("users/@me/animelist");
requestBuilder.AddQueryParam("fields", "list_status");
requestBuilder.AddQueryParam("limit", "1000");
requestBuilder.Accept(HttpAccept.Json);
if (status != MyAnimeListStatus.All && StatusMapping.TryGetValue(status, out var statusName))
{
requestBuilder.AddQueryParam("status", statusName);
}
var httpReq = new ImportListRequest(requestBuilder.Build());
httpReq.HttpRequest.Headers.Add("Authorization", $"Bearer {Settings.AccessToken}");
yield return httpReq;
}
}
}

View File

@@ -0,0 +1,55 @@
using System.Collections.Generic;
using Newtonsoft.Json;
namespace NzbDrone.Core.ImportLists.MyAnimeList
{
public class MyAnimeListResponse
{
[JsonProperty("data")]
public List<MyAnimeListItem> Animes { get; set; }
}
public class MyAnimeListItem
{
[JsonProperty("node")]
public MyAnimeListItemInfo AnimeListInfo { get; set; }
[JsonProperty("list_status")]
public MyAnimeListStatusResult ListStatus { get; set; }
}
public class MyAnimeListStatusResult
{
public string Status { get; set; }
}
public class MyAnimeListItemInfo
{
public int Id { get; set; }
public string Title { get; set; }
}
public class MyAnimeListIds
{
[JsonProperty("mal_id")]
public int MalId { get; set; }
[JsonProperty("thetvdb_id")]
public int TvdbId { get; set; }
}
public class MyAnimeListAuthToken
{
[JsonProperty("token_type")]
public string TokenType { get; set; }
[JsonProperty("expires_in")]
public int ExpiresIn { get; set; }
[JsonProperty("access_token")]
public string AccessToken { get; set; }
[JsonProperty("refresh_token")]
public string RefreshToken { get; set; }
}
}

View File

@@ -0,0 +1,58 @@
using System;
using FluentValidation;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.ImportLists.MyAnimeList
{
public class MalSettingsValidator : AbstractValidator<MyAnimeListSettings>
{
public MalSettingsValidator()
{
RuleFor(c => c.BaseUrl).ValidRootUrl();
RuleFor(c => c.AccessToken).NotEmpty()
.OverridePropertyName("SignIn")
.WithMessage("Must authenticate with MyAnimeList");
RuleFor(c => c.ListStatus).Custom((status, context) =>
{
if (!Enum.IsDefined(typeof(MyAnimeListStatus), status))
{
context.AddFailure($"Invalid status: {status}");
}
});
}
}
public class MyAnimeListSettings : IImportListSettings
{
public string BaseUrl { get; set; }
protected AbstractValidator<MyAnimeListSettings> Validator => new MalSettingsValidator();
public MyAnimeListSettings()
{
BaseUrl = "https://api.myanimelist.net/v2";
}
[FieldDefinition(0, Label = "ImportListsMyAnimeListSettingsListStatus", Type = FieldType.Select, SelectOptions = typeof(MyAnimeListStatus), HelpText = "ImportListsMyAnimeListSettingsListStatusHelpText")]
public int ListStatus { get; set; }
[FieldDefinition(0, Label = "ImportListsSettingsAccessToken", Type = FieldType.Textbox, Hidden = HiddenType.Hidden)]
public string AccessToken { get; set; }
[FieldDefinition(0, Label = "ImportListsSettingsRefreshToken", Type = FieldType.Textbox, Hidden = HiddenType.Hidden)]
public string RefreshToken { get; set; }
[FieldDefinition(0, Label = "ImportListsSettingsExpires", Type = FieldType.Textbox, Hidden = HiddenType.Hidden)]
public DateTime Expires { get; set; }
[FieldDefinition(99, Label = "ImportListsMyAnimeListSettingsAuthenticateWithMyAnimeList", Type = FieldType.OAuth)]
public string SignIn { get; set; }
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));
}
}
}

View File

@@ -0,0 +1,25 @@
using NzbDrone.Core.Annotations;
namespace NzbDrone.Core.ImportLists.MyAnimeList
{
public enum MyAnimeListStatus
{
[FieldOption(label: "All")]
All = 0,
[FieldOption(label: "Watching")]
Watching = 1,
[FieldOption(label: "Completed")]
Completed = 2,
[FieldOption(label: "On Hold")]
OnHold = 3,
[FieldOption(label: "Dropped")]
Dropped = 4,
[FieldOption(label: "Plan to Watch")]
PlanToWatch = 5
}
}

View File

@@ -522,7 +522,7 @@ namespace NzbDrone.Core.IndexerSearch
var reports = batch.SelectMany(x => x).ToList();
_logger.Debug("Total of {0} reports were found for {1} from {2} indexers", reports.Count, criteriaBase, indexers.Count);
_logger.ProgressDebug("Total of {0} reports were found for {1} from {2} indexers", reports.Count, criteriaBase, indexers.Count);
// Update the last search time for all episodes if at least 1 indexer was searched.
if (indexers.Any())

View File

@@ -5,7 +5,6 @@ namespace NzbDrone.Core.Indexers
public class RssSyncCommand : Command
{
public override bool SendUpdatesToClient => true;
public override bool IsLongRunning => true;
}
}

View File

@@ -164,7 +164,7 @@
"AirsTbaOn": "Pendent el seu anunci a {networkLabel}",
"AllFiles": "Tots els fitxers",
"AllSeriesAreHiddenByTheAppliedFilter": "Tots els resultats estan ocults pel filtre aplicat",
"AllSeriesInRootFolderHaveBeenImported": "S'han importat totes les sèries de {0}",
"AllSeriesInRootFolderHaveBeenImported": "S'han importat totes les sèries de {path}",
"AlreadyInYourLibrary": "Ja a la vostra biblioteca",
"AlternateTitles": "Títols alternatius",
"AnalyseVideoFilesHelpText": "Extraieu informació de vídeo com ara la resolució, el temps d'execució i la informació del còdec dels fitxers. Això requereix que {appName} llegeixi parts del fitxer que poden provocar una activitat elevada al disc o a la xarxa durant les exploracions.",
@@ -183,7 +183,7 @@
"DeleteRootFolder": "Suprimeix la carpeta arrel",
"DownloadClientCheckUnableToCommunicateWithHealthCheckMessage": "No es pot comunicar amb {downloadClientName}. {errorMessage}",
"DownloadClientStatusSingleClientHealthCheckMessage": "Clients de baixada no disponibles a causa d'errors: {downloadClientNames}",
"DownloadClientRootFolderHealthCheckMessage": "El client de baixada {downloadClientName} col·loca les baixades a la carpeta arrel {path}. No s'hauria de baixar a una carpeta arrel.",
"DownloadClientRootFolderHealthCheckMessage": "El client de baixada {downloadClientName} col·loca les baixades a la carpeta arrel {rootFolderPath}. No s'hauria de baixar a una carpeta arrel.",
"DownloadClientSortingHealthCheckMessage": "El client de baixada {downloadClientName} té l'ordenació {sortingMode} activada per a la categoria de {appName}. Hauríeu de desactivar l'ordenació al vostre client de descàrrega per a evitar problemes d'importació.",
"HiddenClickToShow": "Amagat, feu clic per a mostrar",
"ImportUsingScript": "Importa amb script",
@@ -213,7 +213,7 @@
"FailedToFetchUpdates": "No s'han pogut obtenir les actualitzacions",
"False": "Fals",
"Implementation": "Implementació",
"ImportListRootFolderMultipleMissingRootsHealthCheckMessage": "Falten diverses carpetes arrel per a les llistes d'importació: {rootFoldersInfo}",
"ImportListRootFolderMultipleMissingRootsHealthCheckMessage": "Falten diverses carpetes arrel per a les llistes d'importació: {rootFolderInfo}",
"ImportListRootFolderMissingRootHealthCheckMessage": "Falta la carpeta arrel per a les llistes d'importació: {rootFolderInfo}",
"IndexerRssNoIndexersEnabledHealthCheckMessage": "No hi ha indexadors disponibles amb la sincronització RSS activada, {appName} no capturarà els nous llançaments automàticament",
"ImportListStatusAllUnavailableHealthCheckMessage": "Totes les llistes no estan disponibles a causa d'errors",
@@ -261,7 +261,7 @@
"AuthenticationMethodHelpText": "Es requereix nom d'usuari i contrasenya per a accedir a {appName}",
"AutoRedownloadFailedHelpText": "Cerca i intenta baixar automàticament una versió diferent",
"AutoTaggingNegateHelpText": "Si està marcat, el format personalitzat no s'aplicarà si la condició {implementationName} coincideix.",
"AutoTaggingRequiredHelpText": "Aquesta condició {implementationName} ha de coincidir perquè s'apliqui la regla d'etiquetatge automàtic. En cas contrari, una única coincidència {implementationName} és suficient.",
"AutoTaggingRequiredHelpText": "La condició {implementationName} ha de coincidir perquè s'apliqui el format personalitzat. En cas contrari, n'hi ha prou amb una única coincidència de {implementationName}.",
"BlocklistLoadError": "No es pot carregar la llista de bloqueig",
"BlocklistRelease": "Publicació de la llista de bloqueig",
"BranchUpdateMechanism": "Branca utilitzada pel mecanisme d'actualització extern",
@@ -301,7 +301,7 @@
"Clone": "Clona",
"CloneProfile": "Clona el perfil",
"CompletedDownloadHandling": "Gestió de descàrregues completades",
"CountSeriesSelected": "{selectedCount} sèries seleccionades",
"CountSeriesSelected": "{count} sèries seleccionades",
"InteractiveImportLoadError": "No es poden carregar els elements d'importació manual",
"ChownGroupHelpTextWarning": "Això només funciona si l'usuari que executa {appName} és el propietari del fitxer. És millor assegurar-se que el client de descàrrega utilitza el mateix grup que {appName}.",
"ChmodFolderHelpTextWarning": "Això només funciona si l'usuari que executa {appName} és el propietari del fitxer. És millor assegurar-se que el client de descàrrega estableixi correctament els permisos.",
@@ -451,7 +451,7 @@
"DeleteEpisodesFilesHelpText": "Suprimeix els fitxers de l'episodi i la carpeta de la sèrie",
"DeleteRemotePathMapping": "Editeu el mapa de camins remots",
"DefaultNotFoundMessage": "Deu estar perdut, no hi ha res a veure aquí.",
"DelayMinutes": "{delay} minuts",
"DelayMinutes": "{delay} Minuts",
"DelayProfile": "Perfil de retard",
"DeleteImportListExclusionMessageText": "Esteu segur que voleu suprimir aquesta exclusió de la llista d'importació?",
"DeleteReleaseProfile": "Suprimeix el perfil de llançament",
@@ -665,5 +665,51 @@
"NotificationsPushoverSettingsRetry": "Torna-ho a provar",
"NotificationsSettingsWebhookMethod": "Mètode",
"Other": "Altres",
"Monitor": "Monitora"
"Monitor": "Monitora",
"AutoTaggingSpecificationOriginalLanguage": "Llenguatge",
"AutoTaggingSpecificationQualityProfile": "Perfil de Qualitat",
"AutoTaggingSpecificationRootFolder": "Carpeta arrel",
"AddDelayProfileError": "No s'ha pogut afegir un perfil realentit, torna-ho a probar",
"AutoTaggingSpecificationSeriesType": "Tipus de Sèries",
"AutoTaggingSpecificationStatus": "Estat",
"BlocklistAndSearch": "Llista de bloqueig i cerca",
"BlocklistAndSearchHint": "Comença una cerca per reemplaçar després d'haver bloquejat",
"DownloadClientAriaSettingsDirectoryHelpText": "Ubicació opcional per a les baixades, deixeu-lo en blanc per utilitzar la ubicació predeterminada d'Aria2",
"Directory": "Directori",
"DownloadClientDelugeSettingsUrlBaseHelpText": "Afegeix un prefix a l'url json del Deluge, vegeu {url}",
"Destination": "Destinació",
"Umask": "UMask",
"ConnectionSettingsUrlBaseHelpText": "Afegeix un prefix a l'URL {connectionName}, com ara {url}",
"DoNotBlocklist": "No afegiu a la llista de bloqueig",
"DoNotBlocklistHint": "Elimina sense afegir a la llista de bloqueig",
"Donate": "Dona",
"DownloadClientDelugeTorrentStateError": "Deluge està informant d'un error",
"NegateHelpText": "Si està marcat, el format personalitzat no s'aplicarà si la condició {implementationName} coincideix.",
"TorrentDelayTime": "Retard del torrent: {torrentDelay}",
"CustomFormatsSpecificationRegularExpression": "Expressió regular",
"RemoveFromDownloadClient": "Elimina del client de baixada",
"StartupDirectory": "Directori d'inici",
"ClickToChangeIndexerFlags": "Feu clic per canviar els indicadors de l'indexador",
"ImportListsSettingsSummary": "Importa des d'una altra instància {appName} o llistes de Trakt i gestiona les exclusions de llistes",
"DeleteSpecificationHelpText": "Esteu segur que voleu suprimir l'especificació '{name}'?",
"DeleteSpecification": "Esborra especificació",
"UsenetDelayTime": "Retard d'Usenet: {usenetDelay}",
"DownloadClientDelugeSettingsDirectory": "Directori de baixada",
"DownloadClientDelugeSettingsDirectoryCompleted": "Directori al qual es mou quan s'hagi completat",
"DownloadClientDelugeSettingsDirectoryCompletedHelpText": "Ubicació opcional de les baixades completades, deixeu-lo en blanc per utilitzar la ubicació predeterminada de Deluge",
"DownloadClientDelugeSettingsDirectoryHelpText": "Ubicació opcional de les baixades completades, deixeu-lo en blanc per utilitzar la ubicació predeterminada de Deluge",
"RetryingDownloadOn": "S'està retardant la baixada fins al {date} a les {time}",
"ListWillRefreshEveryInterval": "La llista s'actualitzarà cada {refreshInterval}",
"BlocklistAndSearchMultipleHint": "Comença una cerca per reemplaçar després d'haver bloquejat",
"BlocklistMultipleOnlyHint": "Afegeix a la llista de bloqueig sense cercar substituts",
"BlocklistOnly": "Sols afegir a la llista de bloqueig",
"BlocklistOnlyHint": "Afegir a la llista de bloqueig sense cercar substituts",
"ChangeCategory": "Canvia categoria",
"ChangeCategoryHint": "Canvia la baixada a la \"Categoria post-importació\" des del client de descàrrega",
"ChangeCategoryMultipleHint": "Canvia les baixades a la \"Categoria post-importació\" des del client de descàrrega",
"BlocklistReleaseHelpText": "Impedeix que {appName} baixi aquesta versió mitjançant RSS o cerca automàtica",
"MinutesSixty": "60 minuts: {sixty}",
"CustomFilter": "Filtres personalitzats",
"CustomFormatsSpecificationRegularExpressionHelpText": "El format personalitzat RegEx no distingeix entre majúscules i minúscules",
"CustomFormatsSpecificationFlag": "Bandera"
}

View File

@@ -30,7 +30,7 @@
"CancelProcessing": "Zrušit zpracování",
"CheckDownloadClientForDetails": "zkontrolujte klienta pro stahování pro více informací",
"ChmodFolderHelpText": "Octal, aplikováno během importu / přejmenování na mediální složky a soubory (bez provádění bitů)",
"ChmodFolderHelpTextWarning": "Toto funguje pouze v případě, že uživatel, který spustil sonarr, je vlastníkem souboru. Je lepší zajistit, aby klient pro stahování správně nastavil oprávnění.",
"ChmodFolderHelpTextWarning": "Toto funguje pouze v případě, že uživatel, který spustil {appName}, je vlastníkem souboru. Je lepší zajistit, aby klient pro stahování správně nastavil oprávnění.",
"ChooseAnotherFolder": "Vyberte jinou složku",
"ChownGroup": "Skupina chown",
"ConnectSettings": "Nastavení připojení",
@@ -67,7 +67,7 @@
"CalendarLoadError": "Nelze načíst kalendář",
"CertificateValidationHelpText": "Změňte přísnost ověřování certifikátů HTTPS. Neměňte, pokud nerozumíte rizikům.",
"ChownGroupHelpText": "Název skupiny nebo gid. Použijte gid pro vzdálené systémy souborů.",
"ChownGroupHelpTextWarning": "Toto funguje pouze v případě, že uživatel, který spustil sonarr, je vlastníkem souboru. Je lepší zajistit, aby klient pro stahování správně nastavil oprávnění.",
"ChownGroupHelpTextWarning": "Toto funguje pouze v případě, že uživatel, který spustil {appName}, je vlastníkem souboru. Je lepší zajistit, aby klient stahování používal stejnou skupinu jako {appName}.",
"ClientPriority": "Priorita klienta",
"Clone": "Klonovat",
"CloneIndexer": "Klonovat indexátor",
@@ -319,5 +319,5 @@
"EditSelectedImportLists": "Upravit vybrané seznamy k importu",
"FormatDateTime": "{formattedDate} {formattedTime}",
"AddRootFolderError": "Nepodařilo se přidat kořenový adresář",
"DownloadClientRemovesCompletedDownloadsHealthCheckMessage": "Klient stahování {downloadClientName} je nastaven na odstranění dokončených stahování. To může vést k tomu, že stahování budou z klienta odstraněna dříve, než je bude moci importovat {1}."
"DownloadClientRemovesCompletedDownloadsHealthCheckMessage": "Klient stahování {downloadClientName} je nastaven na odstranění dokončených stahování. To může vést k tomu, že stahování budou z klienta odstraněna dříve, než je bude moci importovat {appName}."
}

View File

@@ -17,5 +17,22 @@
"AddANewPath": "Tilføj en ny sti",
"AddConditionImplementation": "Tilføj betingelse - {implementationName}",
"AddConnectionImplementation": "Tilføj forbindelse - {implementationName}",
"AddCustomFilter": "Tilføj tilpasset filter"
"AddCustomFilter": "Tilføj tilpasset filter",
"ApplyChanges": "Anvend ændringer",
"Test": "Afprøv",
"AddImportList": "Tilføj importliste",
"AddExclusion": "Tilføj undtagelse",
"TestAll": "Afprøv alle",
"TestAllClients": "Afprøv alle klienter",
"TestAllLists": "Afprøv alle lister",
"Unknown": "Ukendt",
"AllTitles": "All titler",
"TablePageSize": "Sidestørrelse",
"TestAllIndexers": "Afprøv alle indeks",
"AddDownloadClientImplementation": "Tilføj downloadklient - {implementationName}",
"AddIndexerError": "Kunne ikke tilføje en ny indekser. Prøv igen.",
"AddImportListImplementation": "Tilføj importliste - {implementationName}",
"AddRootFolderError": "Kunne ikke tilføje rodmappe",
"Table": "Tabel",
"AddIndexer": "Tilføj indekser"
}

View File

@@ -8,7 +8,7 @@
"AutomaticAdd": "Automatisch hinzufügen",
"CountSeasons": "{count} Staffeln",
"DownloadClientCheckNoneAvailableHealthCheckMessage": "Es ist kein Download-Client verfügbar",
"DownloadClientCheckUnableToCommunicateWithHealthCheckMessage": "Kommunikation mit {downloadClientName} nicht möglich.",
"DownloadClientCheckUnableToCommunicateWithHealthCheckMessage": "Kommunikation mit {downloadClientName} nicht möglich. {errorMessage}",
"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}",
@@ -41,7 +41,7 @@
"SkipFreeSpaceCheck": "Prüfung des freien Speichers überspringen",
"AbsoluteEpisodeNumber": "Exakte Folgennummer",
"AddConnection": "Verbindung hinzufügen",
"AddAutoTagError": "Der neue automatische Tag konnte nicht hinzugefügt werden, bitte versuche es erneut.",
"AddAutoTagError": "Auto-Tag konnte nicht hinzugefügt werden. Bitte erneut versuchen.",
"AddConditionError": "Neue Bedingung konnte nicht hinzugefügt werden, bitte erneut versuchen.",
"AddCustomFormat": "Eigenes Format hinzufügen",
"AddCustomFormatError": "Neues eigenes Format kann nicht hinzugefügt werden, bitte versuchen Sie es erneut.",
@@ -86,7 +86,7 @@
"QuickSearch": "Schnelle Suche",
"ReadTheWikiForMoreInformation": "Lesen Sie das Wiki für weitere Informationen",
"Real": "Real",
"RecycleBinUnableToWriteHealthCheckMessage": "Es kann nicht in den konfigurierten Papierkorb-Ordner geschrieben werden: {Pfad}. Stellen Sie sicher, dass dieser Pfad vorhanden ist und vom Benutzer, der {appName} ausführt, beschreibbar ist.",
"RecycleBinUnableToWriteHealthCheckMessage": "Es kann nicht in den konfigurierten Papierkorb-Ordner geschrieben werden: {path}. Stellen Sie sicher, dass dieser Pfad vorhanden ist und vom Benutzer, der {appName} ausführt, beschreibbar ist.",
"RecyclingBin": "Papierkorb",
"RecyclingBinCleanup": "Papierkorb leeren",
"RefreshSeries": "Serie aktualisieren",
@@ -146,7 +146,7 @@
"AuthenticationRequiredHelpText": "Ändern, welche anfragen Authentifizierung benötigen. Ändere nichts wenn du dir nicht des Risikos bewusst bist.",
"AnalyseVideoFilesHelpText": "Videoinformationen wie Auflösung, Laufzeit und Codec-Informationen aus Dateien extrahieren. Dies erfordert, dass {appName} Teile der Datei liest, was bei Scans zu hoher Festplatten- oder Netzwerkaktivität führen kann.",
"AnalyticsEnabledHelpText": "Senden Sie anonyme Nutzungs- und Fehlerinformationen an die Server von {appName}. Dazu gehören Informationen zu Ihrem Browser, welche {appName}-WebUI-Seiten Sie verwenden, Fehlerberichte sowie Betriebssystem- und Laufzeitversion. Wir werden diese Informationen verwenden, um Funktionen und Fehlerbehebungen zu priorisieren.",
"AutoTaggingNegateHelpText": "Wenn diese Option aktiviert ist, wird die automatische Tagging-Regel nicht angewendet, wenn diese {implementationName}-Bedingung zutrifft.",
"AutoTaggingNegateHelpText": "Falls aktiviert wird das eigene Format nicht angewendet solange diese {0} Bedingung zutrifft.",
"CopyUsingHardlinksSeriesHelpText": "Mithilfe von Hardlinks kann {appName} Seeding-Torrents in den Serienordner importieren, ohne zusätzlichen Speicherplatz zu beanspruchen oder den gesamten Inhalt der Datei zu kopieren. Hardlinks funktionieren nur, wenn sich Quelle und Ziel auf demselben Volume befinden",
"DailyEpisodeTypeFormat": "Datum ({format})",
"DefaultDelayProfileSeries": "Dies ist das Standardprofil. Es gilt für alle Serien, die kein explizites Profil haben.",
@@ -171,7 +171,7 @@
"BackupIntervalHelpText": "Intervall zwischen automatischen Sicherungen",
"BuiltIn": "Eingebaut",
"ChangeFileDate": "Ändern Sie das Dateidatum",
"CustomFormatsLoadError": "Benutzerdefinierte Formate können nicht geladen werden",
"CustomFormatsLoadError": "Eigene Formate konnten nicht geladen werden",
"DeleteQualityProfileMessageText": "Sind Sie sicher, dass Sie das Qualitätsprofil „{name}“ löschen möchten?",
"DeletedReasonUpgrade": "Die Datei wurde gelöscht, um ein Upgrade zu importieren",
"DeleteEpisodesFiles": "{episodeFileCount} Episodendateien löschen",
@@ -202,10 +202,10 @@
"AuthBasic": "Basis (Browser-Popup)",
"AuthForm": "Formulare (Anmeldeseite)",
"Authentication": "Authentifizierung",
"AuthenticationMethodHelpText": "Für den Zugriff auf {appName} sind Benutzername und Passwort erforderlich.",
"AuthenticationMethodHelpText": "Für den Zugriff auf {appName} sind Benutzername und Passwort erforderlich",
"Automatic": "Automatisch",
"AutomaticSearch": "Automatische Suche",
"AutoTaggingRequiredHelpText": "Diese {implementationName}-Bedingung muss zutreffen, damit die automatische Tagging-Regel angewendet wird. Andernfalls reicht eine einzelne {implementationName}-Übereinstimmung aus.",
"AutoTaggingRequiredHelpText": "Diese {0} Bedingungen müssen erfüllt sein, damit das eigene Format zutrifft. Ansonsten reicht ein einzelner {1} Treffer.",
"BackupRetentionHelpText": "Automatische Backups, die älter als der Aufbewahrungszeitraum sind, werden automatisch bereinigt",
"BindAddressHelpText": "Gültige IP-Adresse, localhost oder „*“ für alle Schnittstellen",
"BackupsLoadError": "Sicherrungen können nicht geladen werden",
@@ -280,23 +280,23 @@
"Custom": "Benutzerdefiniert",
"CustomFilters": "Benutzerdefinierte Filter",
"CustomFormat": "Benutzerdefiniertes Format",
"CustomFormats": "Benutzerdefinierte Formate",
"CustomFormatsSettingsSummary": "Benutzerdefinierte Formate und Einstellungen",
"CustomFormats": "Eigene Formate",
"CustomFormatsSettingsSummary": "Eigene Formate und Einstellungen",
"DailyEpisodeFormat": "Tägliches Episodenformat",
"Database": "Datenbank",
"Dates": "Termine",
"Day": "Tag",
"Default": "Standard",
"DefaultCase": "Standardfall",
"DefaultNameCopiedProfile": "{Name} Kopieren",
"DefaultNameCopiedSpecification": "{Name} Kopieren",
"DefaultNameCopiedProfile": "{name} Kopieren",
"DefaultNameCopiedSpecification": "{name} Kopieren",
"DefaultNotFoundMessage": "Sie müssen verloren sein, hier gibt es nichts zu sehen.",
"DelayMinutes": "{delay} Minuten",
"DelayProfile": "Verzögerungsprofil",
"DelayProfileProtocol": "Protokoll: {preferredProtocol}",
"DelayProfiles": "Verzögerungsprofile",
"DelayProfilesLoadError": "Verzögerungsprofile können nicht geladen werden",
"DelayingDownloadUntil": "Download wird bis zum {Datum} um {Uhrzeit} verzögert",
"DelayingDownloadUntil": "Download wird bis zum {date} um {time} verzögert",
"DeleteAutoTag": "Auto-Tag löschen",
"DeleteAutoTagHelpText": "Sind Sie sicher, dass Sie das automatische Tag „{name}“ löschen möchten?",
"DeleteBackup": "Sicherung löschen",
@@ -350,7 +350,7 @@
"DownloadClientDownloadStationValidationFolderMissing": "Ordner existiert nicht",
"DownloadClientDownloadStationValidationFolderMissingDetail": "Der Ordner „{downloadDir}“ existiert nicht, er muss manuell im freigegebenen Ordner „{sharedFolder}“ erstellt werden.",
"DownloadClientDownloadStationValidationNoDefaultDestination": "Kein Standardziel",
"DownloadClientDownloadStationValidationNoDefaultDestinationDetail": "Sie müssen sich bei Ihrer Diskstation als {Benutzername} anmelden und sie manuell in den DownloadStation-Einstellungen unter BT/HTTP/FTP/NZB -> Standort einrichten.",
"DownloadClientDownloadStationValidationNoDefaultDestinationDetail": "Sie müssen sich bei Ihrer Diskstation als {username} anmelden und sie manuell in den DownloadStation-Einstellungen unter BT/HTTP/FTP/NZB -> Standort einrichten.",
"DownloadClientDownloadStationValidationSharedFolderMissing": "Der freigegebene Ordner existiert nicht",
"DownloadClientDownloadStationValidationSharedFolderMissingDetail": "Die Diskstation verfügt nicht über einen freigegebenen Ordner mit dem Namen „{sharedFolder}“. Sind Sie sicher, dass Sie ihn richtig angegeben haben?",
"DownloadClientFloodSettingsAdditionalTags": "Zusätzliche Tags",
@@ -372,7 +372,7 @@
"DownloadClientFreeboxSettingsAppTokenHelpText": "App-Token, das beim Erstellen des Zugriffs auf die Freebox-API abgerufen wird (z. B. „app_token“)",
"DownloadClientFreeboxSettingsHostHelpText": "Hostname oder Host-IP-Adresse der Freebox, standardmäßig „{url}“ (funktioniert nur im selben Netzwerk)",
"DownloadClientFreeboxSettingsPortHelpText": "Port, der für den Zugriff auf die Freebox-Schnittstelle verwendet wird, standardmäßig ist „{port}“",
"DownloadClientFreeboxUnableToReachFreebox": "Die Freebox-API kann nicht erreicht werden. Überprüfen Sie die Einstellungen „Host“, „Port“ oder „SSL verwenden“. (Fehler: {ExceptionMessage})",
"DownloadClientFreeboxUnableToReachFreebox": "Die Freebox-API kann nicht erreicht werden. Überprüfen Sie die Einstellungen „Host“, „Port“ oder „SSL verwenden“. (Fehler: {exceptionMessage})",
"DownloadClientFreeboxUnableToReachFreeboxApi": "Die Freebox-API kann nicht erreicht werden. Überprüfen Sie die Einstellung „API-URL“ für Basis-URL und Version.",
"DownloadClientNzbVortexMultipleFilesMessage": "Der Download enthält mehrere Dateien und befindet sich nicht in einem Jobordner: {outputPath}",
"DownloadClientNzbgetSettingsAddPausedHelpText": "Diese Option erfordert mindestens NzbGet Version 16.0",
@@ -446,7 +446,7 @@
"Restore": "Wiederherstellen",
"RestartRequiredWindowsService": "Je nachdem, welcher Benutzer den {appName}-Dienst ausführt, müssen Sie {appName} möglicherweise einmal als Administrator neu starten, bevor der Dienst automatisch gestartet wird.",
"RestartSonarr": "{appName} neu starten",
"RetryingDownloadOn": "Erneuter Downloadversuch am {Datum} um {Uhrzeit}",
"RetryingDownloadOn": "Erneuter Downloadversuch am {date} um {time}",
"SceneInfo": "Szeneninfo",
"Scene": "Szene",
"SaveSettings": "Einstellungen speichern",
@@ -525,7 +525,7 @@
"UsenetDelayTime": "Usenet-Verzögerung: {usenetDelay}",
"UsenetDelayHelpText": "Verzögerung in Minuten, bevor Sie eine Veröffentlichung aus dem Usenet erhalten",
"VideoCodec": "Video-Codec",
"VersionNumber": "Version {Version}",
"VersionNumber": "Version {version}",
"Version": "Version",
"WantMoreControlAddACustomFormat": "Möchten Sie mehr Kontrolle darüber haben, welche Downloads bevorzugt werden? Fügen Sie ein [benutzerdefiniertes Format] hinzu (/settings/customformats)",
"WaitingToProcess": "Warten auf Bearbeitung",
@@ -540,7 +540,7 @@
"ApplyTagsHelpTextAdd": "Hinzufügen: Fügen Sie die Tags der vorhandenen Tag-Liste hinzu",
"ApplyTagsHelpTextRemove": "Entfernen: Die eingegebenen Tags entfernen",
"ApplyTagsHelpTextReplace": "Ersetzen: Ersetzen Sie die Tags durch die eingegebenen Tags (geben Sie keine Tags ein, um alle Tags zu löschen).",
"Wanted": " Gesucht",
"Wanted": "Gesucht",
"ConnectionLostToBackend": "{appName} hat die Verbindung zum Backend verloren und muss neu geladen werden, um die Funktionalität wiederherzustellen.",
"Continuing": "Fortsetzung",
"CopyUsingHardlinksHelpTextWarning": "Gelegentlich können Dateisperren das Umbenennen von Dateien verhindern, die geseedet werden. Sie können das Seeding vorübergehend deaktivieren und als Workaround die Umbenennungsfunktion von {appName} verwenden.",
@@ -549,9 +549,8 @@
"CountImportListsSelected": "{count} Importliste(n) ausgewählt",
"CountIndexersSelected": "{count} Indexer ausgewählt",
"CountSelectedFiles": "{selectedCount} ausgewählte Dateien",
"CustomFormatUnknownCondition": "Unknown Custom Format condition '{implementation}'",
"CustomFormatUnknownConditionOption": "Unbekannte Option „{key}“ für Bedingung „{implementation}“",
"CustomFormatsSettings": "Benutzerdefinierte Formateinstellungen",
"CustomFormatsSettings": "Einstellungen für eigene Formate",
"Daily": "Täglich",
"Dash": "Bindestrich",
"Debug": "Debuggen",
@@ -710,7 +709,7 @@
"ClickToChangeSeason": "Klicken Sie hier, um die Staffel zu ändern",
"BlackholeFolderHelpText": "Ordner, in dem {appName} die Datei {extension} speichert",
"BlackholeWatchFolder": "Überwachter Ordner",
"BlackholeWatchFolderHelpText": "Ordner, aus dem {appName} abgeschlossene Downloads importieren soll",
"BlackholeWatchFolderHelpText": "Der Ordner, aus dem {appName} fertige Downloads importieren soll",
"BrowserReloadRequired": "Neuladen des Browsers erforderlich",
"CalendarOptions": "Kalenderoptionen",
"CancelPendingTask": "Möchten Sie diese ausstehende Aufgabe wirklich abbrechen?",
@@ -777,5 +776,16 @@
"Airs": "Wird ausgestrahlt",
"AddRootFolderError": "Stammverzeichnis kann nicht hinzugefügt werden",
"IconForCutoffUnmet": "Symbol für Schwelle nicht erreicht",
"DownloadClientSettingsAddPaused": "Pausiert hinzufügen"
"DownloadClientSettingsAddPaused": "Pausiert hinzufügen",
"ClickToChangeIndexerFlags": "Klicken, um Indexer-Flags zu ändern",
"BranchUpdate": "Branch, der verwendet werden soll, um {appName} zu updaten",
"BlocklistAndSearch": "Sperrliste und Suche",
"AddDelayProfileError": "Verzögerungsprofil konnte nicht hinzugefügt werden. Bitte erneut versuchen.",
"BlocklistAndSearchHint": "Starte Suche nach einer Alternative, falls es der Sperrliste hinzugefügt wurde",
"BlocklistAndSearchMultipleHint": "Starte Suchen nach einer Alternative, falls es der Sperrliste hinzugefügt wurde",
"BlocklistMultipleOnlyHint": "Der Sperrliste hinzufügen, ohne nach Alternativen zu suchen",
"BlocklistOnly": "Nur der Sperrliste hinzufügen",
"BlocklistOnlyHint": "Der Sperrliste hinzufügen, ohne nach Alternative zu suchen",
"BlocklistReleaseHelpText": "Dieses Release für erneuten Download durch {appName} via RSS oder automatische Suche sperren",
"ChangeCategory": "Kategorie wechseln"
}

View File

@@ -15,8 +15,8 @@
"RemoveSelectedItemsQueueMessageText": "Είστε σίγουροι πως θέλετε να διαγράψετε {selectedCount} αντικείμενα από την ουρά;",
"CloneCondition": "Κλωνοποίηση συνθήκης",
"RemoveSelectedItemQueueMessageText": "Είστε σίγουροι πως θέλετε να διαγράψετε 1 αντικείμενο από την ουρά;",
"AddConditionImplementation": "Προσθήκη",
"AddConditionImplementation": "Προσθήκη - {implementationName}",
"AppUpdated": "{appName} Ενημερώθηκε",
"AutoAdd": "Προσθήκη",
"AddConnectionImplementation": "Προσθήκη"
"AddConnectionImplementation": "Προσθήκη - {implementationName}"
}

View File

@@ -218,6 +218,7 @@
"ClickToChangeLanguage": "Click to change language",
"ClickToChangeQuality": "Click to change quality",
"ClickToChangeReleaseGroup": "Click to change release group",
"ClickToChangeReleaseType": "Click to change release type",
"ClickToChangeSeason": "Click to change season",
"ClickToChangeSeries": "Click to change series",
"ClientPriority": "Client Priority",
@@ -278,6 +279,7 @@
"CustomFormats": "Custom Formats",
"CustomFormatsLoadError": "Unable to load Custom Formats",
"CustomFormatsSettings": "Custom Formats Settings",
"CustomFormatsSettingsTriggerInfo": "A Custom Format will be applied to a release or file when it matches at least one of each of the different condition types chosen.",
"CustomFormatsSettingsSummary": "Custom Formats and Settings",
"CustomFormatsSpecificationFlag": "Flag",
"CustomFormatsSpecificationLanguage": "Language",
@@ -378,7 +380,7 @@
"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",
"DeletedReasonManual": "File was deleted using {appName}, either manually or by another tool through the API",
"DeletedReasonUpgrade": "File was deleted to import an upgrade",
"DeletedSeriesDescription": "Series was deleted from TheTVDB",
"Destination": "Destination",
@@ -635,6 +637,7 @@
"EpisodeRequested": "Episode Requested",
"EpisodeSearchResultsLoadError": "Unable to load results for this episode search. Try again later",
"EpisodeTitle": "Episode Title",
"EpisodeTitleFootNote": "Optionally control truncation to a maximum number of bytes including ellipsis (`...`). Truncating from the end (e.g. `{Episode Title:30}`) or the beginning (e.g. `{Episode Title:-30}`) are both supported. Episode titles will be automatically truncated to file system limitations if necessary.",
"EpisodeTitleRequired": "Episode Title Required",
"EpisodeTitleRequiredHelpText": "Prevent importing for up to 48 hours if the episode title is in the naming format and the episode title is TBA",
"Episodes": "Episodes",
@@ -838,6 +841,9 @@
"ImportListsImdbSettingsListId": "List ID",
"ImportListsImdbSettingsListIdHelpText": "IMDb list ID (e.g ls12345678)",
"ImportListsLoadError": "Unable to load Import Lists",
"ImportListsMyAnimeListSettingsAuthenticateWithMyAnimeList": "Authenticate with MyAnimeList",
"ImportListsMyAnimeListSettingsListStatus": "List Status",
"ImportListsMyAnimeListSettingsListStatusHelpText": "Type of list you want to import from, set to 'All' for all lists",
"ImportListsPlexSettingsAuthenticateWithPlex": "Authenticate with Plex.tv",
"ImportListsPlexSettingsWatchlistName": "Plex Watchlist",
"ImportListsPlexSettingsWatchlistRSSName": "Plex Watchlist RSS",
@@ -1586,11 +1592,12 @@
"RelativePath": "Relative Path",
"Release": "Release",
"ReleaseGroup": "Release Group",
"ReleaseGroupFootNote": "Optionally control truncation to a maximum number of bytes including ellipsis (`...`). Truncating from the end (e.g. `{Release Group:30}`) or the beginning (e.g. `{Release Group:-30}`) are both supported.`).",
"ReleaseGroups": "Release Groups",
"ReleaseHash": "Release Hash",
"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",
"ReleaseProfileIndexerHelpTextWarning": "Setting a specific indexer on a release profile will cause this profile to only apply to releases from that indexer.",
"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",
@@ -1771,11 +1778,13 @@
"SelectLanguages": "Select Languages",
"SelectQuality": "Select Quality",
"SelectReleaseGroup": "Select Release Group",
"SelectReleaseType": "Select Release Type",
"SelectSeason": "Select Season",
"SelectSeasonModalTitle": "{modalTitle} - Select Season",
"SelectSeries": "Select Series",
"SendAnonymousUsageData": "Send Anonymous Usage Data",
"Series": "Series",
"SeriesFootNote": "Optionally control truncation to a maximum number of bytes including ellipsis (`...`). Truncating from the end (e.g. `{Series Title:30}`) or the beginning (e.g. `{Series Title:-30}`) are both supported.",
"SeriesAndEpisodeInformationIsProvidedByTheTVDB": "Series and episode information is provided by TheTVDB.com. [Please consider supporting them]({url}) .",
"SeriesCannotBeFound": "Sorry, that series cannot be found.",
"SeriesDetailsCountEpisodeFiles": "{episodeFileCount} episode files",

View File

@@ -167,8 +167,8 @@
"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.",
"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",
"ApiKeyValidationHealthCheckMessage": "Por favor actualiza tu clave API para que tenga de longitud al menos {length} caracteres. Puedes hacerlo en los ajustes o en el archivo de configuración",
"AppDataLocationHealthCheckMessage": "No será posible actualizar para evitar la eliminación de AppData al actualizar",
"Scheduled": "Programado",
"Season": "Temporada",
"Clone": "Clonar",
@@ -308,7 +308,7 @@
"CountSeasons": "{count} Temporadas",
"BranchUpdate": "Rama a usar para actualizar {appName}",
"ChmodFolder": "Carpeta chmod",
"CheckDownloadClientForDetails": "Revisar cliente de descarpa para mas detalles",
"CheckDownloadClientForDetails": "Revisar el cliente de descarga para más detalles",
"ChooseAnotherFolder": "Elige otra Carpeta",
"ClientPriority": "Prioridad del Cliente",
"CloneIndexer": "Clonar Indexer",
@@ -324,11 +324,11 @@
"ConnectSettingsSummary": "Notificaciones, conexiones a servidores/reproductores y scripts personalizados",
"ConnectSettings": "Conectar Ajustes",
"CustomFormatUnknownCondition": "Condición de Formato Personalizado Desconocida '{implementation}'",
"XmlRpcPath": "Ruta XML RPC",
"AutoTaggingNegateHelpText": "Si está marcado, la regla de etiquetado automático no aplicará si la condición {implementationName} coincide.",
"XmlRpcPath": "Ruta RPC de XML",
"AutoTaggingNegateHelpText": "Si está marcado, la regla de etiquetado automático no se aplicará si esta condición {implementationName} coincide.",
"CloneCustomFormat": "Clonar formato personalizado",
"Close": "Cerrar",
"AutoTaggingRequiredHelpText": "Esta condición {implementationName} debe coincidir para que la regla de etiquetado automático se aplique. De lo contrario una sola coincidencia de {0} será suficiente.",
"AutoTaggingRequiredHelpText": "Esta condición {implementationName} debe coincidir para que la regla de etiquetado automático se aplique. De lo contrario una sola coincidencia de {implementationName} será suficiente.",
"WeekColumnHeaderHelpText": "Mostrado sobre cada columna cuando la vista activa es semana",
"WhyCantIFindMyShow": "Por que no puedo encontrar mi serie?",
"WouldYouLikeToRestoreBackup": "Te gustaria restaurar la copia de seguridad '{name}'?",
@@ -357,7 +357,7 @@
"ChangeFileDate": "Cambiar fecha de archivo",
"CertificateValidationHelpText": "Cambiar como es la validacion de la certificacion estricta de HTTPS. No cambiar a menos que entiendas las consecuencias.",
"AddListExclusion": "Agregar Lista de Exclusión",
"AddedDate": "Agregado: {fecha}",
"AddedDate": "Agregado: {date}",
"AllSeriesAreHiddenByTheAppliedFilter": "Todos los resultados estan ocultos por el filtro aplicado",
"AlternateTitles": "Titulos alternativos",
"ChmodFolderHelpText": "Octal, aplicado durante la importación / cambio de nombre a carpetas y archivos multimedia (sin bits de ejecución)",
@@ -369,7 +369,7 @@
"AirsTbaOn": "A anunciar en {networkLabel}",
"AllFiles": "Todos los archivos",
"Any": "Cualquiera",
"AirsTomorrowOn": "Mañana a las {hora} en {networkLabel}",
"AirsTomorrowOn": "Mañana a las {time} en {networkLabel}",
"AppUpdatedVersion": "{appName} ha sido actualizado a la versión `{version}`, para obtener los cambios más recientes, necesitará recargar {appName} ",
"AddListExclusionSeriesHelpText": "Evitar que las series sean agregadas a {appName} por las listas",
"CalendarLegendEpisodeDownloadedTooltip": "El episodio fue descargado y ordenado",
@@ -530,20 +530,20 @@
"DeleteEpisodesFilesHelpText": "Eliminar archivos de episodios y directorio de series",
"DoNotPrefer": "No preferir",
"DoNotUpgradeAutomatically": "No actualizar automáticamente",
"IndexerSettingsSeedRatioHelpText": "El ratio que un torrent debería alcanzar antes de detenerse, en blanco usa el defecto por el cliente de descarga. El ratio debería ser al menos 1.0 y seguir las reglas de los indexadores",
"IndexerSettingsSeedRatioHelpText": "El ratio que un torrent debería alcanzar antes de detenerse, vacío usa el predeterminado del cliente de descarga. El ratio debería ser al menos 1.0 y seguir las reglas de los indexadores",
"Download": "Descargar",
"Donate": "Donar",
"DownloadClientDelugeValidationLabelPluginFailure": "Falló la configuración de la etiqueta",
"DownloadClientDelugeTorrentStateError": "Deluge está informando de un error",
"DownloadClientDownloadStationValidationFolderMissing": "No existe la carpeta",
"DownloadClientDownloadStationValidationNoDefaultDestinationDetail": "Debes iniciar sesión en tu Diskstation como {username} y configurarlo manualmente en los ajustes de DownloadStation en BT/HTTP/FTP/NZB -> Ubicación.",
"DownloadClientFreeboxSettingsAppIdHelpText": "ID de la aplicación cuando se crea acceso a la API de Freebox (i.e. 'app_id')",
"DownloadClientFreeboxSettingsAppToken": "Token de la aplicación",
"DownloadClientFreeboxSettingsAppIdHelpText": "ID de la app dada cuando se crea acceso a la API de Freebox (esto es 'app_id')",
"DownloadClientFreeboxSettingsAppToken": "Token de la app",
"DownloadClientFreeboxUnableToReachFreebox": "No es posible acceder a la API de Freebox. Verifica las opciones 'Host', 'Puerto' o 'Usar SSL'. (Error: {exceptionMessage})",
"DownloadClientNzbVortexMultipleFilesMessage": "La descarga contiene varios archivos y no está en una carpeta de trabajo: {outputPath}",
"DownloadClientNzbgetSettingsAddPausedHelpText": "Esta opción requiere al menos NzbGet versión 16.0",
"DownloadClientOptionsLoadError": "No es posible cargar las opciones del cliente de descarga",
"DownloadClientPneumaticSettingsNzbFolderHelpText": "Esta carpeta tendrá que ser accesible desde XBMC",
"DownloadClientPneumaticSettingsNzbFolderHelpText": "Esta carpeta necesitará ser alcanzable desde XBMC",
"DownloadClientPneumaticSettingsNzbFolder": "Carpeta de Nzb",
"Docker": "Docker",
"DockerUpdater": "Actualiza el contenedor docker para recibir la actualización",
@@ -570,17 +570,17 @@
"DotNetVersion": ".NET",
"DownloadClientCheckNoneAvailableHealthCheckMessage": "Ningún cliente de descarga disponible",
"DownloadClientCheckUnableToCommunicateWithHealthCheckMessage": "No es posible comunicarse con {downloadClientName}. {errorMessage}",
"DownloadClientDelugeSettingsUrlBaseHelpText": "Añade un prefijo a la url json de deluge, ver {url}",
"DownloadClientDelugeSettingsUrlBaseHelpText": "Añade un prefijo al url del json de deluge, vea {url}",
"DownloadClientDownloadStationValidationApiVersion": "Versión de la API de la Estación de Descarga no soportada, debería ser al menos {requiredVersion}. Soporte desde {minVersion} hasta {maxVersion}",
"DownloadClientDownloadStationValidationFolderMissingDetail": "No existe la carpeta '{downloadDir}', debe ser creada manualmente dentro de la Carpeta Compartida '{sharedFolder}'.",
"DownloadClientDownloadStationValidationNoDefaultDestination": "Sin destino predeterminado",
"DownloadClientFreeboxNotLoggedIn": "No ha iniciado sesión",
"DownloadClientFreeboxSettingsApiUrl": "URL de la API",
"DownloadClientFreeboxSettingsApiUrlHelpText": "Define la URL base de la API de Freebox con la versión de la API, p.ej. '{url}', por defecto es '{defaultApiUrl}'",
"DownloadClientFreeboxSettingsAppId": "ID de la aplicación",
"DownloadClientFreeboxSettingsAppTokenHelpText": "App token recuperado cuando se crea el acceso a la API de Freebox (i.e. 'app_token')",
"DownloadClientFreeboxSettingsPortHelpText": "Puerto usado para acceder a la interfaz de Freebox, por defecto es '{port}'",
"DownloadClientFreeboxSettingsHostHelpText": "Nombre de host o dirección IP del Freebox, por defecto es '{url}' (solo funcionará en la misma red)",
"DownloadClientFreeboxSettingsApiUrl": "URL de API",
"DownloadClientFreeboxSettingsApiUrlHelpText": "Define la URL base de la API Freebox con la versión de la API, p. ej. '{url}', por defecto a '{defaultApiUrl}'",
"DownloadClientFreeboxSettingsAppId": "ID de la app",
"DownloadClientFreeboxSettingsAppTokenHelpText": "Token de la app recuperado cuando se crea acceso a la API de Freebox (esto es 'app_token')",
"DownloadClientFreeboxSettingsPortHelpText": "Puerto usado para acceder a la interfaz de Freebox, predeterminado a '{port}'",
"DownloadClientFreeboxSettingsHostHelpText": "Nombre de host o dirección IP de host del Freebox, predeterminado a '{url}' (solo funcionará en la misma red)",
"DownloadClientFreeboxUnableToReachFreeboxApi": "No es posible acceder a la API de Freebox. Verifica la configuración 'URL de la API' para la URL base y la versión.",
"DownloadClientNzbgetValidationKeepHistoryOverMax": "La opción KeepHistory de NZBGet debería ser menor de 25000",
"DownloadClientNzbgetValidationKeepHistoryOverMaxDetail": "La opción KeepHistory de NzbGet está establecida demasiado alta.",
@@ -605,7 +605,7 @@
"EnableHelpText": "Habilitar la creación de un fichero de metadatos para este tipo de metadato",
"EnableMediaInfoHelpText": "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.",
"TheLogLevelDefault": "El nivel de registro por defecto es 'Info' y puede ser cambiado en [Opciones generales](opciones/general)",
"TorrentBlackholeSaveMagnetFilesExtensionHelpText": "Extensión a usar para enlaces magnet, por defecto es '.magnet'",
"TorrentBlackholeSaveMagnetFilesExtensionHelpText": "Extensión a usar para enlaces magnet, predeterminado a '.magnet'",
"DownloadIgnoredEpisodeTooltip": "Descarga de episodio ignorada",
"EditDelayProfile": "Editar perfil de retraso",
"DownloadClientFloodSettingsUrlBaseHelpText": "Añade un prefijo a la API de Flood, como {url}",
@@ -613,13 +613,13 @@
"EditReleaseProfile": "Editar perfil de lanzamiento",
"DownloadClientPneumaticSettingsStrmFolder": "Carpeta de Strm",
"DownloadClientQbittorrentValidationCategoryAddFailure": "Falló la configuración de categoría",
"DownloadClientRTorrentSettingsUrlPath": "Ruta de la url",
"DownloadClientRTorrentSettingsUrlPath": "Ruta de url",
"DownloadClientSabnzbdValidationDevelopVersion": "Versión de desarrollo de Sabnzbd, asumiendo versión 3.0.0 o superior.",
"DownloadClientSabnzbdValidationCheckBeforeDownloadDetail": "Usar 'Verificar antes de descargar' afecta a la habilidad de {appName} de rastrear nuevas descargas. Sabnzbd también recomienda 'Abortar trabajos que no pueden ser completados' en su lugar ya que resulta más efectivo.",
"DownloadClientSabnzbdValidationDevelopVersionDetail": "{appName] puede no ser capaz de soportar nuevas características añadidas a SABnzbd cuando se ejecutan versiones de desarrollo.",
"DownloadClientSabnzbdValidationDevelopVersionDetail": "{appName} puede no ser capaz de soportar nuevas características añadidas a SABnzbd cuando se ejecutan versiones de desarrollo.",
"DownloadClientSabnzbdValidationEnableDisableDateSortingDetail": "Debe deshabilitar la ordenación por fechas para la categoría que {appName} usa para evitar problemas al importar. Vaya a Sabnzbd para arreglarlo.",
"DownloadClientSabnzbdValidationEnableJobFolders": "Habilitar carpetas de trabajo",
"DownloadClientSettingsUrlBaseHelpText": "Añade un prefijo a la url de {clientName}, como {url}",
"DownloadClientSettingsUrlBaseHelpText": "Añade un prefijo a la url {clientName}, como {url}",
"DownloadClientStatusAllClientHealthCheckMessage": "Ningún cliente de descarga está disponible debido a fallos",
"DownloadClientValidationGroupMissing": "El grupo no existe",
"DownloadClientValidationSslConnectFailure": "No es posible conectarse a través de SSL",
@@ -641,7 +641,7 @@
"EnableInteractiveSearchHelpText": "Se usará cuando se utilice la búsqueda interactiva",
"DoneEditingGroups": "Terminado de editar grupos",
"DownloadClientFloodSettingsAdditionalTags": "Etiquetas adicionales",
"DownloadClientFloodSettingsAdditionalTagsHelpText": "Añade propiedades multimedia como etiquetas. Sugerencias a modo de ejemplo.",
"DownloadClientFloodSettingsAdditionalTagsHelpText": "Añade propiedades de medios como etiquetas. Los consejos son ejemplos.",
"DownloadClientFloodSettingsPostImportTagsHelpText": "Añade etiquetas después de que se importe una descarga.",
"DownloadClientFloodSettingsRemovalInfo": "{appName} manejará la eliminación automática de torrents basada en el criterio de sembrado actual en Ajustes -> Indexadores",
"DownloadClientFloodSettingsPostImportTags": "Etiquetas tras importación",
@@ -649,7 +649,7 @@
"DownloadClientFloodSettingsStartOnAdd": "Inicial al añadir",
"DownloadClientFreeboxApiError": "La API de Freebox devolvió el error: {errorDescription}",
"DownloadClientFreeboxAuthenticationError": "La autenticación a la API de Freebox falló. El motivo: {errorDescription}",
"DownloadClientPneumaticSettingsStrmFolderHelpText": "Se importarán los archivos .strm en esta carpeta por drone",
"DownloadClientPneumaticSettingsStrmFolderHelpText": "Los archivos .strm en esta carpeta será importados por drone",
"DownloadClientQbittorrentTorrentStateError": "qBittorrent está informando de un error",
"DownloadClientQbittorrentSettingsSequentialOrder": "Orden secuencial",
"DownloadClientQbittorrentTorrentStateDhtDisabled": "qBittorrent no puede resolver enlaces magnet con DHT deshabilitado",
@@ -658,26 +658,26 @@
"DownloadClientQbittorrentValidationCategoryRecommendedDetail": "{appName} no intentará importar descargas completadas sin una categoría.",
"DownloadClientQbittorrentValidationQueueingNotEnabledDetail": "Poner en cola el torrent no está habilitado en los ajustes de su qBittorrent. Habilítelo en qBittorrent o seleccione 'Último' como prioridad.",
"DownloadClientQbittorrentValidationRemovesAtRatioLimitDetail": "{appName} no podrá efectuar el Manejo de Descargas Completadas según lo configurado. Puede arreglar esto en qBittorrent ('Herramientas -> Opciones...' en el menú) cambiando 'Opciones -> BitTorrent -> Límite de ratio ' de 'Eliminarlas' a 'Pausarlas'",
"DownloadClientRTorrentSettingsAddStopped": "Añadir parados",
"DownloadClientRTorrentSettingsAddStoppedHelpText": "Habilitarlo añadirá los torrents y magnets a rTorrent en un estado parado. Esto puede romper los archivos magnet.",
"DownloadClientRTorrentSettingsDirectoryHelpText": "Ubicación opcional en la que poner las descargas, déjelo en blanco para usar la ubicación predeterminada de rTorrent",
"DownloadClientRTorrentSettingsAddStopped": "Añadir detenido",
"DownloadClientRTorrentSettingsAddStoppedHelpText": "Permite añadir torrents y magnets a rTorrent en estado detenido. Esto puede romper los archivos magnet.",
"DownloadClientRTorrentSettingsDirectoryHelpText": "Ubicación opcional en la que poner las descargas, dejar en blanco para usar la ubicación predeterminada de rTorrent",
"DownloadClientRemovesCompletedDownloadsHealthCheckMessage": "El cliente de descarga {downloadClientName} se establece para eliminar las descargas completadas. Esto puede resultar en descargas siendo eliminadas de tu cliente antes de que {appName} pueda importarlas.",
"DownloadClientRootFolderHealthCheckMessage": "El cliente de descarga {downloadClientName} ubica las descargas en la carpeta raíz {rootFolderPath}. No debería descargar a una carpeta raíz.",
"DownloadClientSabnzbdValidationEnableDisableDateSorting": "Deshabilitar la ordenación por fechas",
"DownloadClientSabnzbdValidationEnableDisableMovieSortingDetail": "Debe deshabilitar la ordenación de películas para la categoría que {appName} usa para evitar problemas al importar. Vaya a Sabnzbd para arreglarlo.",
"DownloadClientSettingsCategorySubFolderHelpText": "Añadir una categoría específica a {appName} evita conflictos con descargas no-{appName} no relacionadas. Usar una categoría es opcional, pero bastante recomendado. Crea un subdirectorio [categoría] en el directorio de salida.",
"DownloadClientSettingsDestinationHelpText": "Especifica manualmente el destino de descarga, déjelo en blanco para usar el predeterminado",
"DownloadClientSettingsDestinationHelpText": "Especifica manualmente el destino de descarga, dejar en blanco para usar el predeterminado",
"DownloadClientSettingsInitialState": "Estado inicial",
"DownloadClientSettingsInitialStateHelpText": "Estado inicial para los torrents añadidos a {clientName}",
"DownloadClientSettingsInitialStateHelpText": "Estado inicial para torrents añadidos a {clientName}",
"DownloadClientSettingsOlderPriorityEpisodeHelpText": "Prioridad a usar cuando se tomen episodios que llevan en emisión hace más de 14 días",
"DownloadClientQbittorrentTorrentStateMetadata": "qBittorrent está descargando metadatos",
"DownloadClientSettingsPostImportCategoryHelpText": "Categoría para {appName} que se establece después de que se haya importado la descarga. {appName} no eliminará los torrents en esa categoría incluso si finalizó la siembra. Déjelo en blanco para mantener la misma categoría.",
"DownloadClientSettingsRecentPriorityEpisodeHelpText": "Prioridad a usar cuando se tomen episodios que llevan en emisión dentro de los últimos 14 días",
"DownloadClientSettingsUseSslHelpText": "Usa conexión segura cuando haya una conexión a {clientName}",
"DownloadClientSettingsUseSslHelpText": "Usa una conexión segura cuando haya una conexión a {clientName}",
"DownloadClientSortingHealthCheckMessage": "El cliente de descarga {downloadClientName} tiene habilitada la ordenación {sortingMode} para la categoría de {appName}. Debería deshabilitar la ordenación en su cliente de descarga para evitar problemas al importar.",
"DownloadClientStatusSingleClientHealthCheckMessage": "Clientes de descarga no disponibles debido a fallos: {downloadClientNames}",
"DownloadClientTransmissionSettingsDirectoryHelpText": "Ubicación opcional en la que poner las descargas, déjelo en blanco para usar la ubicación predeterminada de Transmission",
"DownloadClientTransmissionSettingsUrlBaseHelpText": "Añade un prefijo a la url rpc de {clientName}, p.ej. {url}, por defecto es '{defaultUrl}'",
"DownloadClientTransmissionSettingsDirectoryHelpText": "Ubicación opcional en la que poner las descargas, dejar en blanco para usar la ubicación predeterminada de Transmission",
"DownloadClientTransmissionSettingsUrlBaseHelpText": "Añade un prefijo a la url rpc de {clientName}, p. ej. {url}, predeterminado a '{defaultUrl}'",
"DownloadClientUTorrentTorrentStateError": "uTorrent está informando de un error",
"DownloadClientValidationApiKeyIncorrect": "Clave API incorrecta",
"DownloadClientValidationApiKeyRequired": "Clave API requerida",
@@ -706,11 +706,11 @@
"DownloadClientSabnzbdValidationUnknownVersion": "Versión desconocida: {rawVersion}",
"DownloadClientSettingsAddPaused": "Añadir pausado",
"DownloadClientSeriesTagHelpText": "Solo use este cliente de descarga para series con al menos una etiqueta coincidente. Déjelo en blanco para usarlo con todas las series.",
"DownloadClientQbittorrentSettingsUseSslHelpText": "Usa una conexión segura. Consulte Opciones -> Interfaz Web -> 'Usar HTTPS en lugar de HTTP' en qBittorrent.",
"DownloadClientQbittorrentSettingsUseSslHelpText": "Usa una conexión segura. Ver en Opciones -> Interfaz web -> 'Usar HTTPS en lugar de HTTP' en qbittorrent.",
"DownloadClientQbittorrentValidationCategoryAddFailureDetail": "{appName} no pudo añadir la etiqueta a qBittorrent.",
"DownloadClientQbittorrentValidationCategoryUnsupported": "La categoría no está soportada",
"DownloadClientQbittorrentValidationQueueingNotEnabled": "Poner en cola no está habilitado",
"DownloadClientRTorrentSettingsUrlPathHelpText": "Ruta al endpoint de XMLRPC, vea {url}. Esto es usualmente RPC2 o [ruta a rTorrent]{url2} cuando se usa rTorrent.",
"DownloadClientRTorrentSettingsUrlPathHelpText": "Ruta al endpoint de XMLRPC, ver {url}. Esto es usualmente RPC2 o [ruta a ruTorrent]{url2} cuando se usa ruTorrent.",
"DownloadClientQbittorrentTorrentStatePathError": "No es posible importar. La ruta coincide con el directorio de descarga base del cliente, ¿es posible que 'Mantener carpeta de nivel superior' esté deshabilitado para este torrent o que 'Diseño de contenido de torrent' NO se haya establecido en 'Original' o 'Crear subcarpeta'?",
"DownloadClientSabnzbdValidationEnableDisableMovieSorting": "Deshabilitar la ordenación de películas",
"DownloadClientSabnzbdValidationEnableDisableTvSorting": "Deshabilitar ordenación de TV",
@@ -733,8 +733,8 @@
"DownloadClientValidationSslConnectFailureDetail": "{appName} no se puede conectar a {clientName} usando SSL. Este problema puede estar relacionado con el ordenador. Por favor, intente configurar tanto {appName} como {clientName} para no usar SSL.",
"DownloadFailedEpisodeTooltip": "La descarga del episodio falló",
"DownloadClientQbittorrentSettingsFirstAndLastFirstHelpText": "Descarga primero las primeras y últimas piezas (qBittorrent 4.1.0+)",
"DownloadClientQbittorrentSettingsFirstAndLastFirst": "Primero las primeras y últimas",
"DownloadClientQbittorrentSettingsInitialStateHelpText": "Estado inicial para los torrents añadidos a qBittorrent. Tenga en cuenta que Torrents forzados no se atiene a las restricciones de sembrado",
"DownloadClientQbittorrentSettingsFirstAndLastFirst": "Primeras y últimas primero",
"DownloadClientQbittorrentSettingsInitialStateHelpText": "Estado inicial para los torrents añadidos a qBittorrent. Ten en cuenta que Forzar torrents no cumple las restricciones de semilla",
"DownloadClientQbittorrentSettingsSequentialOrderHelpText": "Descarga en orden secuencial (qBittorrent 4.1.0+)",
"DownloadClientDownloadStationValidationSharedFolderMissingDetail": "El Diskstation no tiene una Carpeta Compartida con el nombre '{sharedFolder}', ¿estás seguro que lo has especificado correctamente?",
"EnableCompletedDownloadHandlingHelpText": "Importar automáticamente las descargas completas del gestor de descargas",
@@ -825,7 +825,7 @@
"Existing": "Existentes",
"ExportCustomFormat": "Exportar formato personalizado",
"EpisodeFilesLoadError": "No se puede cargar los archivos de episodios",
"EpisodeGrabbedTooltip": "Episodio capturado desde {indexer} y enviado a {downloadCliente}",
"EpisodeGrabbedTooltip": "Episodio capturado desde {indexer} y enviado a {downloadClient}",
"EpisodeInfo": "Información del episodio",
"EpisodeMissingAbsoluteNumber": "El episodio no tiene un número de episodio absoluto",
"EpisodeTitleRequired": "Título del episodio requerido",
@@ -873,7 +873,7 @@
"FilterNotInLast": "no en el último",
"Group": "Grupo",
"ImportListSearchForMissingEpisodes": "Buscar episodios faltantes",
"EnableProfileHelpText": "Señalar para habilitar el perfil de lanzamiento",
"EnableProfileHelpText": "Marcar para habilitar el perfil de lanzamiento",
"EnableRssHelpText": "Se usará cuando {appName} busque periódicamente lanzamientos vía Sincronización RSS",
"EndedSeriesDescription": "No se esperan episodios o temporadas adicionales",
"EpisodeFileDeleted": "Archivo de episodio eliminado",
@@ -1005,8 +1005,8 @@
"Forecast": "Previsión",
"IndexerDownloadClientHelpText": "Especifica qué cliente de descarga es usado para capturas desde este indexador",
"IndexerHDBitsSettingsCodecs": "Códecs",
"IndexerHDBitsSettingsCodecsHelpText": "Si no se especifica, se usan todas las opciones.",
"IndexerHDBitsSettingsMediumsHelpText": "Si no se especifica, se usan todas las opciones.",
"IndexerHDBitsSettingsCodecsHelpText": "Si no se especifica, se usan todas las opciones.",
"IndexerHDBitsSettingsMediumsHelpText": "Si no se especifica, se usan todas las opciones.",
"IndexerPriority": "Prioridad del indexador",
"IconForFinales": "Icono para Finales",
"IgnoreDownload": "Ignorar descarga",
@@ -1059,7 +1059,7 @@
"ICalTagsSeriesHelpText": "El feed solo contendrá series con al menos una etiqueta coincidente",
"IconForCutoffUnmet": "Icono para Umbrales no alcanzados",
"IconForCutoffUnmetHelpText": "Mostrar icono para archivos cuando el umbral no haya sido alcanzado",
"EpisodeCount": "Número de episodios",
"EpisodeCount": "Recuento de episodios",
"IndexerSettings": "Ajustes de Indexador",
"AddDelayProfileError": "No se pudo añadir un nuevo perfil de retraso, inténtelo de nuevo.",
"IndexerRssNoIndexersAvailableHealthCheckMessage": "Todos los indexers capaces de RSS están temporalmente desactivados debido a errores recientes con el indexer",
@@ -1068,23 +1068,23 @@
"IndexerSearchNoAvailableIndexersHealthCheckMessage": "Todos los indexadores con capacidad de búsqueda no están disponibles temporalmente debido a errores recientes del indexadores",
"IndexerSearchNoInteractiveHealthCheckMessage": "No hay indexadores disponibles con Búsqueda Interactiva activada, {appName} no proporcionará ningún resultado de búsquedas interactivas",
"PasswordConfirmation": "Confirmación de Contraseña",
"IndexerSettingsAdditionalParameters": "Parámetros Adicionales",
"IndexerSettingsAdditionalParameters": "Parámetros adicionales",
"IndexerSettingsAllowZeroSizeHelpText": "Activar esta opción le permitirá utilizar fuentes que no especifiquen el tamaño del lanzamiento, pero tenga cuidado, no se realizarán comprobaciones relacionadas con el tamaño.",
"IndexerSettingsAllowZeroSize": "Permitir Tamaño Cero",
"StopSelecting": "Detener la Selección",
"IndexerSettingsCookie": "Cookie",
"IndexerSettingsCategories": "Categorías",
"IndexerSettingsMinimumSeedersHelpText": "Número mínimo de semillas necesario.",
"IndexerSettingsSeedRatio": "Proporción de Semillado",
"IndexerSettingsSeedRatio": "Ratio de sembrado",
"StartupDirectory": "Directorio de Arranque",
"IndexerSettingsAdditionalParametersNyaa": "Parámetros Adicionales",
"IndexerSettingsPasskey": "Clave de acceso",
"IndexerSettingsSeasonPackSeedTime": "Tiempo de Semillado de los Pack de Temporada",
"IndexerSettingsAnimeStandardFormatSearch": "Formato Estándar de Búsqueda de Anime",
"IndexerSettingsAnimeStandardFormatSearchHelpText": "Buscar también anime utilizando la numeración estándar",
"IndexerSettingsApiPathHelpText": "Ruta a la api, normalmente {url}",
"IndexerSettingsApiPathHelpText": "Ruta a la API, usualmente {url}",
"IndexerSettingsSeasonPackSeedTimeHelpText": "La cantidad de tiempo que un torrent de pack de temporada debe ser compartido antes de que se detenga, dejar vacío utiliza el valor por defecto del cliente de descarga",
"IndexerSettingsSeedTime": "Tiempo de Semillado",
"IndexerSettingsSeedTime": "Tiempo de sembrado",
"IndexerStatusAllUnavailableHealthCheckMessage": "Todos los indexadores no están disponibles debido a errores",
"IndexerValidationCloudFlareCaptchaExpired": "El token CAPTCHA de CloudFlare ha caducado, actualícelo.",
"NotificationsDiscordSettingsAuthor": "Autor",
@@ -1097,12 +1097,12 @@
"IndexerSettingsMinimumSeeders": "Semillas mínimas",
"IndexerSettingsRssUrl": "URL de RSS",
"IndexerSettingsAnimeCategoriesHelpText": "Lista desplegable, dejar en blanco para desactivar anime",
"IndexerSettingsApiPath": "Ruta de la API",
"IndexerSettingsApiPath": "Ruta de API",
"IndexerSettingsCookieHelpText": "Si su sitio requiere una cookie de inicio de sesión para acceder al RSS, tendrá que conseguirla a través de un navegador.",
"IndexerSettingsRssUrlHelpText": "Introduzca la URL de un canal RSS compatible con {indexer}",
"IndexerStatusUnavailableHealthCheckMessage": "Indexadores no disponibles debido a errores: {indexerNames}",
"IndexerHDBitsSettingsMediums": "Medios",
"IndexerSettingsSeedTimeHelpText": "La cantidad de tiempo que un torrent debe ser compartido antes de que se detenga, dejar vacío utiliza el valor por defecto del cliente de descarga",
"IndexerSettingsSeedTimeHelpText": "El tiempo que un torrent debería ser compartido antes de detenerse, vació usa el predeterminado del cliente de descarga",
"IndexerValidationCloudFlareCaptchaRequired": "Sitio protegido por CloudFlare CAPTCHA. Se requiere un token CAPTCHA válido.",
"NotificationsEmailSettingsUseEncryption": "Usar Cifrado",
"LastDuration": "Última Duración",
@@ -1177,7 +1177,7 @@
"LogFiles": "Archivos de Registro",
"LogLevel": "Nivel de Registro",
"LogLevelTraceHelpTextWarning": "El registro de seguimiento sólo debe activarse temporalmente",
"LibraryImportTipsQualityInEpisodeFilename": "Asegúrate de que tus archivos incluyen la calidad en sus nombres de archivo. ej. 'episodio.s02e15.bluray.mkv'.",
"LibraryImportTipsQualityInEpisodeFilename": "Asegúrate de que tus archivos incluyen la calidad en sus nombres de archivo. P. ej. `episodio.s02e15.bluray.mkv`",
"ListSyncLevelHelpText": "Las series de la biblioteca se gestionarán en función de su selección si se caen o no aparecen en su(s) lista(s)",
"LogOnly": "Sólo Registro",
"LongDateFormat": "Formato de Fecha Larga",
@@ -1335,7 +1335,7 @@
"Monitor": "Monitorizar",
"MonitorAllEpisodes": "Todos los episodios",
"MonitorAllSeasons": "Todas las temporadas",
"NotificationsCustomScriptSettingsProviderMessage": "El test ejecutará el script con el EventType establecido en {eventTypeSet}, asegúrate de que tu script maneja esto correctamente",
"NotificationsCustomScriptSettingsProviderMessage": "El test ejecutará el script con el EventType establecido en {eventTypeTest}, asegúrate de que tu script maneja esto correctamente",
"NotificationsDiscordSettingsAvatar": "Avatar",
"NotificationsDiscordSettingsAvatarHelpText": "Cambia el avatar que es usado para mensajes desde esta integración",
"NotificationsAppriseSettingsNotificationType": "Tipo de notificación de Apprise",
@@ -1351,7 +1351,7 @@
"NotificationsDiscordSettingsAuthorHelpText": "Sobrescribe el autor incrustado que se muestra para esta notificación. En blanco es el nombre de la instancia",
"MonitorNewItems": "Monitorizar nuevos elementos",
"MonitoredEpisodesHelpText": "Descargar episodios monitorizados en estas series",
"NegateHelpText": "Si se elige, el formato personalizado no se aplica si coincide la condición {implementationName}.",
"NegateHelpText": "Si se marca, el formato personalizado no se aplica si coincide la condición {implementationName}.",
"NotificationsCustomScriptSettingsName": "Script personalizado",
"ImportListsSonarrSettingsSyncSeasonMonitoring": "Sincronizar la monitorización de temporada",
"ImportListsSonarrSettingsSyncSeasonMonitoringHelpText": "Sincroniza la monitorización de temporada de la instancia de {appName}, si se habilita 'Monitorizar' será ignorado",
@@ -1361,5 +1361,704 @@
"MoreDetails": "Más detalles",
"MoreInfo": "Más información",
"NoEpisodesInThisSeason": "No hay episodios en esta temporada",
"NoLinks": "No hay enlaces"
"NoLinks": "No hay enlaces",
"OrganizeSelectedSeriesModalAlert": "Consejo: Para previsualizar un renombrado, selecciona \"Cancelar\", entonces selecciona cualquier título de serie y usa este icono:",
"OrganizeSelectedSeriesModalConfirmation": "¿Estás seguro que quieres organizar todos los archivos en las {count} series seleccionadas?",
"Password": "Contraseña",
"Permissions": "Permisos",
"Port": "Puerto",
"RecyclingBinCleanup": "Limpieza de la papelera de reciclaje",
"ReleaseSceneIndicatorSourceMessage": "Los lanzamientos {message} existen con numeración ambigua, no se pudo identificar de forma fiable el episodio.",
"SeriesTitle": "Título de serie",
"ShowEpisodes": "Mostrar episodios",
"ShowBanners": "Mostrar banners",
"ShowSeriesTitleHelpText": "Muestra el título de serie bajo el póster",
"SkipFreeSpaceCheck": "Saltar comprobación de espacio libre",
"OneSeason": "1 temporada",
"OnlyTorrent": "Solo torrent",
"OpenBrowserOnStart": "Abrir navegador al inicio",
"OnlyUsenet": "Solo Usenet",
"OverrideAndAddToDownloadQueue": "Sobrescribe y añade a la cola de descarga",
"Table": "Tabla",
"TagsLoadError": "No se pudo cargar Etiquetas",
"OverviewOptions": "Opciones de vista general",
"Umask775Description": "{octal} - Usuario y grupo escriben, Otros leen",
"PendingChangesStayReview": "Quedarse y revisar cambios",
"PendingDownloadClientUnavailable": "Pendiente - El cliente de descarga no está disponible",
"PostImportCategory": "Categoría de post-importación",
"PreferUsenet": "Preferir usenet",
"PreviousAiringDate": "Emisiones anteriores: {date}",
"Profiles": "Perfiles",
"PrioritySettings": "Prioridad: {priority}",
"Ok": "Ok",
"PrefixedRange": "Rango prefijado",
"Qualities": "Calidades",
"PublishedDate": "Fecha de publicación",
"QualitySettings": "Opciones de calidad",
"QualitySettingsSummary": "Tamaños de calidad y nombrado",
"RecentChanges": "Cambios recientes",
"MountSeriesHealthCheckMessage": "El montaje que contiene una ruta de series se monta en solo lectura: ",
"NotificationsEmailSettingsBccAddress": "Dirección(es) BCC",
"NotificationsEmailSettingsBccAddressHelpText": "Lista separada por coma de destinatarios de e-mail bcc",
"NotificationsEmailSettingsName": "E-mail",
"NotificationsEmailSettingsRecipientAddress": "Dirección(es) de destinatario",
"NotificationsEmbySettingsSendNotificationsHelpText": "Hacer que MediaBrowser envíe notificaciones a los proveedores configurados",
"NotificationsGotifySettingsAppToken": "Token de app",
"NotificationsGotifySettingIncludeSeriesPosterHelpText": "Incluye poster de serie en mensaje",
"NotificationsJoinSettingsDeviceNames": "Nombres de dispositivo",
"NotificationsJoinSettingsDeviceNamesHelpText": "Lista separada por coma de nombres de dispositivo completos o parciales a los que te gustaría enviar notificaciones. Si no se establece, todos los dispositivos recibirán notificaciones.",
"NotificationsJoinSettingsNotificationPriority": "Prioridad de notificación",
"NotificationsNtfySettingsClickUrlHelpText": "Enlace opcional cuando el usuario hace clic en la notificación",
"NotificationsNtfySettingsPasswordHelpText": "Contraseña opcional",
"NotificationsNtfySettingsTagsEmojis": "Etiquetas y emojis de Ntfy",
"NotificationsNtfySettingsServerUrlHelpText": "Deja en blanco para usar el servidor público ({url})",
"NotificationsNtfySettingsTopicsHelpText": "Lista de temas a la que enviar notificaciones",
"NotificationsPushBulletSettingSenderIdHelpText": "La ID del dispositivo desde la que enviar notificaciones, usa device_iden en la URL del dispositivo en pushbullet.com (deja en blanco para enviarla por ti mismo)",
"NotificationsPushBulletSettingsChannelTagsHelpText": "Lista de etiquetas de canal a las que enviar notificaciones",
"NotificationsSettingsUpdateMapPathsFromHelpText": "Ruta de {appName}, usado para modificar rutas de series cuando {serviceName} ve la ubicación de ruta de biblioteca de forma distinta a {appName} (Requiere 'Actualizar biblioteca')",
"NotificationsSettingsUpdateMapPathsFrom": "Mapear rutas desde",
"NotificationsTagsSeriesHelpText": "Envía notificaciones solo para series con al menos una etiqueta coincidente",
"NotificationsTraktSettingsRefreshToken": "Refrescar token",
"OnEpisodeFileDelete": "Al borrar un archivo de episodio",
"OnGrab": "Al capturar",
"OneMinute": "1 minuto",
"Or": "o",
"OrganizeSelectedSeriesModalHeader": "Organizar series seleccionadas",
"Original": "Original",
"OriginalLanguage": "Idioma original",
"OverrideGrabNoLanguage": "Al menos un idioma debe ser seleccionado",
"ParseModalHelpTextDetails": "{appName} intentará analizar el título y te mostrará detalles sobre ello",
"Parse": "Analizar",
"Path": "Ruta",
"PortNumber": "Número de puerto",
"PosterSize": "Tamaño de póster",
"Posters": "Pósteres",
"PreviewRename": "Previsualizar renombrado",
"PreferredProtocol": "Protocolo preferido",
"ProcessingFolders": "Procesando carpetas",
"Proper": "Proper",
"ProxyFailedToTestHealthCheckMessage": "Fallo al probar el proxy: {url}",
"ProxyBadRequestHealthCheckMessage": "Fallo al probar el proxy. Código de estado: {statusCode}",
"ProxyType": "Tipo de proxy",
"QualityLimitsSeriesRuntimeHelpText": "Los límites son automáticamente ajustados para las series en tiempo de ejecución y el número de episodios en el archivo.",
"Range": "Rango",
"RecycleBinUnableToWriteHealthCheckMessage": "No se pudo escribir en la carpeta configurada de la papelera de reciclaje: {path}. Asegúrate de que la ruta existe y es modificable por el usuario que ejecuta {appName}",
"RecyclingBinHelpText": "Los archivos irán aquí cuando se borren en lugar de ser borrados permanentemente",
"RelativePath": "Ruta relativa",
"RegularExpressionsCanBeTested": "Las expresiones regulares pueden ser probadas [aquí]({url}).",
"ReleaseGroup": "Grupo de lanzamiento",
"ReleaseGroups": "Grupos de lanzamiento",
"ReleaseProfilesLoadError": "No se pudo cargar los perfiles de lanzamiento",
"RemotePathMappingImportEpisodeFailedHealthCheckMessage": "{appName} falló al importar (un) episodio(s). Comprueba tus registros para más detalles.",
"RemotePathMappingRemoteDownloadClientHealthCheckMessage": "El cliente de descarga remoto {downloadClientName} reportó archivos en {path} pero este directorio no parece existir. Posiblemente mapeo de ruta remota perdido.",
"RemoveQueueItem": "Eliminar - {sourceTitle}",
"RemoveFailed": "Fallo al eliminar",
"ResetQualityDefinitions": "Restablecer definiciones de calidad",
"Scene": "Escena",
"RssSyncIntervalHelpText": "Intervalo en minutos. Configurar a cero para deshabilitar (esto detendrá todas las capturas automáticas de lanzamientos)",
"SceneNumberNotVerified": "El número de escena no ha sido verificado aún",
"SearchForAllMissingEpisodes": "Buscar todos los episodios perdidos",
"SeasonInformation": "Información de temporada",
"SeasonNumber": "Número de temporada",
"SeasonCount": "Recuento de temporada",
"SelectDownloadClientModalTitle": "{modalTitle} - Seleccionar cliente de descarga",
"SelectEpisodes": "Seleccionar episodio(s)",
"SeriesDetailsGoTo": "Ir a {title}",
"SeriesTypes": "Tipos de serie",
"SeriesTypesHelpText": "El tipo de serie es usado para renombrar, analizar y buscar",
"SingleEpisodeInvalidFormat": "Episodio individual: Formato inválido",
"SslCertPasswordHelpText": "Contraseña para el archivo pfx",
"SslPort": "Puerto SSL",
"StandardEpisodeFormat": "Formato de episodio estándar",
"StartProcessing": "Iniciar procesamiento",
"SupportedListsMoreInfo": "Para más información en las listas individuales, haz clic en los botones de más información.",
"TagDetails": "Detalles de etiqueta - {label}",
"Total": "Total",
"True": "Verdadero",
"Umask770Description": "{octal} - Usuario y grupo escriben",
"UsenetBlackholeNzbFolder": "Carpeta Nzb",
"UsenetDelay": "Retraso de usenet",
"UsenetBlackhole": "Blackhole de usenet",
"RemotePathMappingFilesGenericPermissionsHealthCheckMessage": "El cliente de descarga {downloadClientName} reportó archivos en {path} pero {appName} no puede ver este directorio. Puede que necesites ajustar los permisos de la carpeta.",
"RemotePathMappingFilesLocalWrongOSPathHealthCheckMessage": "El cliente de descarga local {downloadClientName} reportó archivos en {path} pero esta no es una ruta {osName} válida. Revisa las opciones de tu cliente de descarga.",
"RemotePathMappingFilesWrongOSPathHealthCheckMessage": "El cliente de descarga remoto {downloadClientName} reportó archivos en {path} pero esta no es una ruta {osName} válida. Revisa tus mapeos de ruta remota y las opciones de tu cliente de descarga.",
"RemoveFilter": "Eliminar filtro",
"RemoveQueueItemConfirmation": "¿Estás seguro que quieres eliminar '{sourceTitle}' de la cola?",
"RemoveRootFolder": "Eliminar la carpeta raíz",
"RemoveSelectedItem": "Eliminar elemento seleccionado",
"RemoveTagsAutomaticallyHelpText": "Eliminar etiquetas automáticamente si las condiciones no se cumplen",
"RemovedFromTaskQueue": "Eliminar de la cola de tareas",
"RemovedSeriesMultipleRemovedHealthCheckMessage": "Las series {series} fueron eliminadas de TheTVDB",
"RenameFiles": "Renombrar archivos",
"ResetAPIKeyMessageText": "¿Estás seguro que quieres restablecer tu clave API?",
"ResetDefinitions": "Restablecer definiciones",
"ResetDefinitionTitlesHelpText": "Restablecer títulos de definición también como valores",
"ResetQualityDefinitionsMessageText": "¿Estás seguro que quieres restablecer las definiciones de calidad?",
"RestartNow": "Reiniciar ahora",
"RestartRequiredToApplyChanges": "{appName} requiere reiniciar para aplicar cambios. ¿Quieres reiniciar ahora?",
"RestartSonarr": "Reiniciar {appName}",
"RestoreBackup": "Restaurar copia de seguridad",
"Result": "Resultado",
"RetryingDownloadOn": "Reintentar descarga en {date} a las {time}",
"Rss": "RSS",
"SaveChanges": "Guardar cambios",
"SceneNumbering": "Numeración de escena",
"Script": "Script",
"Search": "Buscar",
"SearchForMonitoredEpisodesSeason": "Buscar episodios monitorizados en esta temporada",
"SearchForQuery": "Buscar {query}",
"SeasonFolder": "Carpeta de temporada",
"SeasonPassEpisodesDownloaded": "{episodeFileCount}/{totalEpisodeCount} episodios descargados",
"SelectDropdown": "Seleccionar...",
"SelectLanguageModalTitle": "{modalTitle} - Seleccionar idioma",
"SelectLanguages": "Seleccionar idiomas",
"SelectReleaseGroup": "Seleccionar grupo de lanzamiento",
"SeriesDetailsNoEpisodeFiles": "Sin archivos de episodio",
"SeriesFolderImportedTooltip": "Episodio importado de la carpeta de serie",
"SeriesIsMonitored": "La serie está monitorizada",
"SeriesLoadError": "No se pudo cargar la serie",
"SeriesIsUnmonitored": "La serie no está monitorizada",
"SetPermissionsLinuxHelpTextWarning": "Si no estás seguro qué configuraciones hacer, no las cambies.",
"SetPermissionsLinuxHelpText": "¿Debería ejecutarse chmod cuando los archivos son importados/renombrados?",
"SetReleaseGroup": "Establecer grupo de lanzamiento",
"ShowEpisodeInformationHelpText": "Muestra el título y número de episodio",
"ShowMonitoredHelpText": "Muestra el estado monitorizado bajo el póster",
"ShowQualityProfile": "Mostrar perfil de calidad",
"ShowQualityProfileHelpText": "Muestra el perfil de calidad bajo el póster",
"ShowRelativeDates": "Mostrar fechas relativas",
"OnImport": "Al importar",
"Other": "Otro",
"ShowRelativeDatesHelpText": "Muestra fechas absolutas o relativas (Hoy/Ayer/etc)",
"Proxy": "Proxy",
"ShowSearch": "Mostrar búsqueda",
"ShowSearchHelpText": "Muestra el botón de búsqueda al pasar por encima",
"ShowSeasonCount": "Muestra el recuento de temporada",
"ShowAdvanced": "Mostrar avanzado",
"Socks4": "Socks4",
"Socks5": "Socks5 (Soporta TOR)",
"ShowTitle": "Mostrar título",
"Unknown": "Desconocido",
"Sort": "Ordenar",
"SourcePath": "Ruta de la fuente",
"SourceRelativePath": "Ruta relativa de la fuente",
"Special": "Especial",
"SourceTitle": "Título de la fuente",
"SpecialEpisode": "Episodio especial",
"Specials": "Especiales",
"SpecialsFolderFormat": "Formato de carpeta de los especiales",
"SslCertPassword": "Contraseña de certificado SSL",
"SupportedCustomConditions": "{appName} soporta condiciones personalizadas para las siguientes propiedades de lanzamiento.",
"SupportedDownloadClients": "{appName} soporta muchos torrent populares y clientes de descarga de usenet.",
"SupportedIndexers": "{appName} soporta cualquier indexador que use el estándar Newznab, así como otros indexadores listados a continuación.",
"OnSeriesDelete": "Al borrar series",
"OnRename": "Al renombrar",
"OutputPath": "Ruta de salida",
"PreferAndUpgrade": "Preferir y actualizar",
"Presets": "Preajustes",
"ProxyPasswordHelpText": "Solo necesitas introducir un usuario y contraseña si se requiere alguno. De otra forma déjalos en blanco.",
"QueueLoadError": "Fallo al cargar la cola",
"ReadTheWikiForMoreInformation": "Lee la Wiki para más información",
"RecyclingBinCleanupHelpText": "Establece a 0 para deshabilitar la limpieza automática",
"RegularExpressionsTutorialLink": "Más detalles de las expresiones regulares pueden ser encontradas [aquí]({url}).",
"RejectionCount": "Recuento de rechazos",
"RemotePathMappingFileRemovedHealthCheckMessage": "El fichero {path} ha sido eliminado durante el proceso.",
"RemotePathMappings": "Mapeos de ruta remota",
"RemovedSeriesSingleRemovedHealthCheckMessage": "La serie {series} fue eliminada de TheTVDB",
"ReplaceWithDash": "Reemplazar con guion",
"ReplaceWithSpaceDash": "Reemplazar por barra espaciadora",
"ReplaceWithSpaceDashSpace": "Reemplazar por espacio en la barra espaciadora",
"ScriptPath": "Ruta del script",
"SeasonDetails": "Detalles de temporada",
"SecretToken": "Token secreto",
"SelectQuality": "Seleccionar calidad",
"SelectLanguage": "Seleccionar idioma",
"SelectSeason": "Seleccionar temporada",
"SeriesIndexFooterMissingUnmonitored": "Episodios perdidos (Serie no monitorizada)",
"ShowEpisodeInformation": "Mostrar información de episodio",
"ShowPath": "Mostrar ruta",
"ShowNetwork": "Mostrar red",
"TvdbIdExcludeHelpText": "La ID de TVDB de la serie a excluir",
"UpdateSonarrDirectlyLoadError": "No se pudo actualizar {appName} directamente,",
"UpgradesAllowedHelpText": "Si se deshabilita las calidades no serán actualizadas",
"WithFiles": "Con archivos",
"SystemTimeHealthCheckMessage": "La hora del sistema está desfasada más de 1 día. Las tareas programadas pueden no ejecutarse correctamente hasta que la hora sea corregida",
"TableColumns": "Columnas",
"TableColumnsHelpText": "Elige qué columnas son visibles en qué orden aparecen",
"TablePageSize": "Tamaño de página",
"TablePageSizeHelpText": "Número de elementos a mostrar en cada página",
"TablePageSizeMinimum": "El tamaño de página debe ser al menos {minimumValue}",
"TablePageSizeMaximum": "El tamaño de página no debe exceder {maximumValue}",
"TagIsNotUsedAndCanBeDeleted": "La etiqueta no se usa y puede ser borrada",
"TagsSettingsSummary": "Vea todas las etiquetas y cómo se usan. Las etiquetas sin usar pueden ser eliminadas",
"TaskUserAgentTooltip": "User-Agent proporcionado por la aplicación que llamó a la API",
"Test": "Prueba",
"TestAllIndexers": "Probar todos los indexadores",
"TestAllLists": "Probar todas las listas",
"TestParsing": "Probar análisis",
"ThemeHelpText": "Cambiar el tema de la interfaz de la aplicación, el tema 'Auto' usará el tema de tu sistema para establecer el modo luminoso u oscuro. Inspirado por Theme.Park",
"TimeLeft": "Tiempo restante",
"ToggleMonitoredSeriesUnmonitored ": "No se puede conmutar el estado monitorizado cuando la serie no está monitorizada",
"Tomorrow": "Mañana",
"TorrentBlackhole": "Blackhole de torrent",
"TorrentBlackholeSaveMagnetFiles": "Guardar archivos magnet",
"TorrentBlackholeSaveMagnetFilesExtension": "Guardar extensión de archivos magnet",
"TorrentBlackholeSaveMagnetFilesReadOnly": "Solo lectura",
"TorrentBlackholeTorrentFolder": "Carpeta de torrent",
"TorrentDelayHelpText": "Retraso en minutos a esperar antes de capturar un torrent",
"TorrentDelayTime": "Retraso torrent: {torrentDelay}",
"Umask755Description": "{octal} - Usuario escribe, Todos los demás leen",
"Umask777Description": "{octal} - Todos escriben",
"UnableToLoadAutoTagging": "No se pudo cargar el etiquetado automático",
"UnableToLoadBackups": "No se pudo cargar las copias de seguridad",
"Ungroup": "Sin agrupar",
"UnknownDownloadState": "Estado de descarga desconocido: {state}",
"Unlimited": "Ilimitado",
"UnmappedFilesOnly": "Solo archivos sin mapear",
"UnmonitorDeletedEpisodes": "Dejar de monitorizar episodios borrados",
"UnmonitoredOnly": "Solo sin monitorizar",
"UnsavedChanges": "Cambios sin guardar",
"UnselectAll": "Desmarcar todo",
"Upcoming": "Próximamente",
"UpcomingSeriesDescription": "Series que han sido anunciadas pero aún no hay fecha de emisión exacta",
"ReleaseSceneIndicatorUnknownSeries": "Episodio o serie desconocido.",
"RemoveDownloadsAlert": "Las opciones de Eliminar fueron movidas a las opciones del cliente de descarga individual en la table anterior.",
"RestartRequiredHelpTextWarning": "Requiere reiniciar para que tenga efecto",
"SelectFolder": "Seleccionar carpeta",
"TestAllClients": "Probar todos los clientes",
"UpdateFiltered": "Actualizar filtrados",
"SeriesEditor": "Editor de serie",
"Updates": "Actualizaciones",
"NotificationsKodiSettingsDisplayTimeHelpText": "Durante cuánto tiempo serán mostradas las notificaciones (en segundos)",
"NotificationsNtfySettingsUsernameHelpText": "Usuario opcional",
"NotificationsSimplepushSettingsEvent": "Evento",
"NotificationsSimplepushSettingsEventHelpText": "Personaliza el comportamiento de las notificaciones push",
"NotificationsTwitterSettingsConsumerSecret": "Secreto de consumidor",
"NotificationsTelegramSettingsSendSilently": "Enviar de forma silenciosa",
"NotificationsValidationInvalidHttpCredentials": "Credenciales de autenticación HTTP inválidas: {exceptionMessage}",
"OnEpisodeFileDeleteForUpgrade": "Al borrar un archivo de episodio para actualización",
"OnHealthIssue": "Al haber un problema de salud",
"Organize": "Organizar",
"OrganizeRenamingDisabled": "El renombrado está deshabilitado, nada que renombrar",
"OrganizeNothingToRename": "¡Éxito! Mi trabajo está hecho, no hay archivos que renombrar.",
"OrganizeRelativePaths": "Todas las rutas son relativas a: `{path}`",
"Pending": "Pendiente",
"QualityDefinitions": "Definiciones de calidad",
"RecyclingBin": "Papelera de reciclaje",
"ReleaseTitle": "Título de lanzamiento",
"RemotePathMappingLocalPathHelpText": "Ruta que {appName} debería usar para acceder a la ruta remota localmente",
"Remove": "Eliminar",
"RetentionHelpText": "Solo usenet: Establece a cero para establecer una retención ilimitada",
"SelectIndexerFlags": "Seleccionar banderas del indexador",
"SelectSeasonModalTitle": "{modalTitle} - Seleccionar temporada",
"SeriesFinale": "Final de serie",
"SeriesAndEpisodeInformationIsProvidedByTheTVDB": "La información de serie y episodio es proporcionada por TheTVDB.com. [Por favor considera apoyarlos]({url}).",
"SetIndexerFlags": "Establecer banderas del indexador",
"SkipRedownload": "Saltar redescarga",
"ShowMonitored": "Mostrar monitorizado",
"Space": "Espacio",
"TimeFormat": "Formato de hora",
"UiSettings": "Opciones de interfaz",
"Umask": "UMask",
"UpdateStartupNotWritableHealthCheckMessage": "No se puede instalar la actualización porque la carpeta de inicio '{startupFolder}' no es modificable por el usuario '{userName}'.",
"UsenetDelayHelpText": "Retraso en minutos a esperar antes de capturar un lanzamiento desde usenet",
"PartialSeason": "Temporada parcial",
"RemoveSelectedItemQueueMessageText": "¿Estás seguro que quieres eliminar 1 elemento de la cola?",
"SceneInformation": "Información de escena",
"UpgradeUntilThisQualityIsMetOrExceeded": "Actualizar hasta que esta calidad sea alcanzada o excedida",
"Uppercase": "Mayúsculas",
"SeriesDetailsRuntime": "{runtime} minutos",
"ShowBannersHelpText": "Muestra banners en lugar de títulos",
"SslCertPathHelpText": "Ruta al archivo pfx",
"Umask750Description": "{octal} - Usuario escribe, Grupo lee",
"UrlBaseHelpText": "Para soporte de proxy inverso, por defecto está vacío",
"UpdateAll": "Actualizar todo",
"ConnectionSettingsUrlBaseHelpText": "Añade un prefijo a la url {connectionName}, como {url}",
"UsenetDelayTime": "Retraso de usenet: {usenetDelay}",
"UsenetDisabled": "Usenet deshabilitado",
"Username": "Usuario",
"UtcAirDate": "Fecha de emisión UTC",
"Version": "Versión",
"WaitingToImport": "Esperar para importar",
"NotificationsDiscordSettingsOnGrabFieldsHelpText": "Cambia los campos que se pasan para esta notificación 'al capturar'",
"NotificationsNtfyValidationAuthorizationRequired": "Se requiere autorización",
"NotificationsNtfySettingsClickUrl": "URL al hacer clic",
"NotificationsNotifiarrSettingsApiKeyHelpText": "Tu clave API de tu perfil",
"NotificationsPlexSettingsAuthenticateWithPlexTv": "Autenticar con Plex.tv",
"NotificationsPushcutSettingsNotificationNameHelpText": "Nombre de notificación de la pestaña Notificaciones de la aplicación Pushcut",
"NotificationsPlexValidationNoTvLibraryFound": "Al menos se requiere una biblioteca de TV",
"NotificationsPushBulletSettingSenderId": "ID del remitente",
"NotificationsSignalSettingsGroupIdPhoneNumberHelpText": "ID de grupo / Número de teléfono del receptor",
"NotificationsSettingsWebhookMethod": "Método",
"NotificationsSettingsUseSslHelpText": "Conectar a {serviceName} sobre HTTPS en vez de HTTP",
"NotificationsTwitterSettingsConsumerSecretHelpText": "Secreto de consumidor de una aplicación de Twitter",
"NotificationsValidationInvalidUsernamePassword": "Usuario o contraseña inválido",
"NotificationsSlackSettingsWebhookUrlHelpText": "URL de canal webhook de Slack",
"PackageVersion": "Versión del paquete",
"NotificationsValidationUnableToConnectToApi": "No se pudo conectar a la API de {service}. La conexión al servidor falló: ({responseCode}) {exceptionMessage}",
"PosterOptions": "Opciones de póster",
"PreferTorrent": "Preferir torrent",
"PreviewRenameSeason": "Previsualizar renombrado para esta temporada",
"PreviousAiring": "Emisiones anteriores",
"RemoveFromDownloadClient": "Eliminar del cliente de descarga",
"RemovingTag": "Eliminando etiqueta",
"Required": "Solicitado",
"Reorder": "Reordenar",
"SceneInfo": "Información de escena",
"RootFolderMissingHealthCheckMessage": "Carpeta raíz perdida: {rootFolderPath}",
"SearchAll": "Buscar todo",
"SelectAll": "Seleccionar todo",
"SeriesIndexFooterEnded": "FInalizado (Todos los episodios descargados)",
"ShowDateAdded": "Mostrar fecha de adición",
"UnmonitorDeletedEpisodesHelpText": "Los episodios borrados del disco son dejados de monitorizar automáticamente en {appName}",
"UnmonitorSelected": "Dejar de monitorizar seleccionados",
"UpdateSelected": "Actualizar seleccionados",
"UpdateUiNotWritableHealthCheckMessage": "No se puede instalar la actualización porque la carpeta de interfaz '{uiFolder}' no es modificable por el usuario '{userName}'.",
"UpgradeUntil": "Actualizar hasta",
"UpdaterLogFiles": "Actualizador de archivos de registro",
"UseSeasonFolder": "Usar carpeta de temporada",
"UseHardlinksInsteadOfCopy": "Utilizar enlaces directos en lugar de copiar",
"View": "Vista",
"VisitTheWikiForMoreDetails": "Visita la wiki para más detalles: ",
"WaitingToProcess": "Esperar al proceso",
"Week": "Semana",
"WeekColumnHeader": "Cabecera de columna de semana",
"Release": "Lanzamiento",
"RemoveSelectedItems": "Eliminar elementos seleccionados",
"RemoveSelectedItemsQueueMessageText": "¿Estás seguro que quieres eliminar {selectedCount} elementos de la cola?",
"RootFolderSelectFreeSpace": "{freeSpace} libres",
"RootFolderPath": "Ruta de carpeta raíz",
"RssSyncInterval": "Intervalo de sincronización RSS",
"SingleEpisode": "Episodio individual",
"ShowUnknownSeriesItems": "Mostrar elementos de serie desconocidos",
"NotificationsGotifySettingIncludeSeriesPoster": "Incluir poster de serie",
"NotificationsKodiSettingsCleanLibraryHelpText": "Limpia la biblioteca después de actualizar",
"NotificationsKodiSettingsCleanLibrary": "Limpiar biblioteca",
"NotificationsKodiSettingsGuiNotification": "Notificación de interfaz gráfica",
"NotificationsKodiSettingsUpdateLibraryHelpText": "¿Actualiza la biblioteca durante Importar y renombrar?",
"NotificationsMailgunSettingsUseEuEndpointHelpText": "Habilitar el uso del endpoint de UE de MailGun",
"NotificationsMailgunSettingsUseEuEndpoint": "Usar el endpoint de la UE",
"NotificationsNtfySettingsAccessToken": "Token de acceso",
"NotificationsNtfySettingsAccessTokenHelpText": "Autorización opcional basada en token. Tiene prioridad sobre usuario/contraseña",
"NotificationsNtfySettingsTagsEmojisHelpText": "Lista opcional de etiquetas o emojis para usar",
"NotificationsNtfySettingsTopics": "Temas",
"NotificationsPushBulletSettingsDeviceIds": "IDs de dispositivo",
"NotificationsPushBulletSettingsAccessToken": "Token de acceso",
"NotificationsPushBulletSettingsChannelTags": "Etiquetas de canal",
"NotificationsPushcutSettingsTimeSensitive": "Sensible al tiempo",
"NotificationsPushcutSettingsTimeSensitiveHelpText": "Habilitar para marcas la notificación como \"Sensible al tiempo\"",
"NotificationsPushoverSettingsDevices": "Dispositivos",
"NotificationsPushoverSettingsDevicesHelpText": "Lista de nombres de dispositivo (deja en blanco para enviar a todos los dispositivos)",
"NotificationsPushoverSettingsExpireHelpText": "Tiempo máximo para reintentar las alertas de emergencia, máximo 86400 segundos",
"NotificationsPushoverSettingsRetry": "Reintentar",
"NotificationsPushoverSettingsSound": "Sonido",
"NotificationsPushoverSettingsUserKey": "Clave de usuario",
"NotificationsPushoverSettingsSoundHelpText": "Sonido de notificación, deja en blanco para usar el predeterminado",
"NotificationsSettingsWebhookMethodHelpText": "Qué método HTTP utilizar para enviar al servicio web",
"NotificationsSettingsWebhookUrl": "URL del webhook",
"NotificationsSignalSettingsGroupIdPhoneNumber": "ID de grupo / Número de teléfono",
"NotificationsSignalSettingsPasswordHelpText": "Contraseña usada para autenticar solicitudes hacia signal-api",
"NotificationsSignalSettingsSenderNumber": "Número del emisor",
"NotificationsSignalSettingsUsernameHelpText": "Usuario usado para autenticar solicitudes hacia signal-api",
"NotificationsSignalValidationSslRequired": "Se requiere SSL",
"NotificationsSimplepushSettingsKey": "Clave",
"NotificationsSlackSettingsChannel": "Canal",
"NotificationsSlackSettingsIconHelpText": "Cambia el icono usado para mensajes publicados a Slack (emoji o URL)",
"NotificationsSlackSettingsUsernameHelpText": "Usuario para publicar a Slack",
"NotificationsTelegramSettingsSendSilentlyHelpText": "Envía el mensaje de forma silenciosa. Los usuarios recibirán una notificación sin sonido",
"NotificationsTelegramSettingsTopicId": "ID de tema",
"NotificationsTraktSettingsAuthenticateWithTrakt": "Autenticar con Trakt",
"NotificationsTraktSettingsExpires": "Caduca",
"NotificationsValidationUnableToConnectToService": "No se pudo conectar a {serviceName}",
"NotificationsValidationUnableToSendTestMessage": "No se pudo enviar un mensaje de prueba: {exceptionMessage}",
"NzbgetHistoryItemMessage": "Estado de PAR: {parStatus} - Estado de desempaquetado: {unpackStatus} - Estado de movido: {moveStatus} - Estado de script: {scriptStatus} - Estado de borrado: {deleteStatus} - Estado de marcado: {markStatus}",
"OpenSeries": "Abrir serie",
"OrganizeLoadError": "Error cargando vistas previas",
"OrganizeNamingPattern": "Patrón de nombrado: `{episodeFormat}`",
"OverrideGrabNoSeries": "La serie debe ser seleccionada",
"PackageVersionInfo": "{packageVersion} por {packageAuthor}",
"PendingChangesDiscardChanges": "Descartar cambios y salir",
"Period": "Periodo",
"PendingChangesMessage": "Tienes cambios sin guardar. ¿Estás seguro que quieres salir de esta página?",
"PreviouslyInstalled": "Previamente instalado",
"ProtocolHelpText": "Elige qué protocolo(s) usar y cuál se prefiere cuando se elige entre lanzamientos equivalentes",
"ProgressBarProgress": "Barra de progreso al {progress}%",
"ProxyBypassFilterHelpText": "Usa ',' como separador, y '*.' como comodín para subdominios",
"ProxyResolveIpHealthCheckMessage": "Fallo al resolver la dirección IP para el host proxy configurado {proxyHostName}",
"ProxyUsernameHelpText": "Solo necesitas introducir un usuario y contraseña si se requiere alguno. De otra forma déjalos en blanco.",
"QualityProfile": "Perfil de calidad",
"QualityDefinitionsLoadError": "No se pudo cargar las definiciones de calidad",
"QualityProfiles": "Perfiles de calidad",
"QualityProfilesLoadError": "No se pudo cargar los perfiles de calidad",
"QueueFilterHasNoItems": "Seleccionado filtro de cola que no tiene elementos",
"QuickSearch": "Búsqueda rápida",
"Real": "Real",
"Reason": "Razón",
"RegularExpression": "Expresión regular",
"ReleaseHash": "Hash de lanzamiento",
"Rejections": "Rechazos",
"RecyclingBinCleanupHelpTextWarning": "Los archivos en la papelera de reciclaje anteriores al número de días seleccionado serán limpiados automáticamente",
"ReleaseProfiles": "Perfiles de lanzamiento",
"ReleaseRejected": "Lanzamiento rechazado",
"ReleaseSceneIndicatorAssumingScene": "Asumiendo numeración de escena.",
"ReleaseSceneIndicatorAssumingTvdb": "Asumiendo numeración de TVDB.",
"ReleaseSceneIndicatorUnknownMessage": "La numeración varía para este episodio y el lanzamiento no coincide con ningún mapeo conocido.",
"RemotePathMappingDownloadPermissionsEpisodeHealthCheckMessage": "{appName} puede ver pero no acceder al episodio descargado {path}. Probablemente error de permisos.",
"RemotePathMappingRemotePathHelpText": "Ruta raíz al directorio al que accede el cliente de descarga",
"RemoveFailedDownloads": "Eliminar descargas fallidas",
"RemoveSelected": "Eliminar seleccionado",
"RenameEpisodesHelpText": "{appName} usará el nombre de archivo existente si el renombrado está deshabilitado",
"RenameEpisodes": "Renombrar episodios",
"RestrictionsLoadError": "No se pudo cargar Restricciones",
"SearchForMissing": "Buscar perdidos",
"SeasonFinale": "Final de temporada",
"SearchSelected": "Buscar seleccionados",
"SeasonFolderFormat": "Formato de carpeta de temporada",
"SendAnonymousUsageData": "Enviar datos de uso anónimos",
"SeriesDetailsOneEpisodeFile": "1 archivo de episodio",
"SeriesFolderFormatHelpText": "Usado cuando se añade una nueva serie o se mueve la serie a través del editor de serie",
"SeriesID": "ID de serie",
"SetPermissions": "Establecer permisos",
"SetReleaseGroupModalTitle": "{modalTitle} - Establecer grupo de lanzamiento",
"SetTags": "Establecer etiquetas",
"ShowPreviousAiring": "Mostrar emisión anterior",
"ShowSizeOnDisk": "Mostrar tamaño en disco",
"SizeOnDisk": "Tamaño en disco",
"SizeLimit": "Límite de tamaño",
"SkipRedownloadHelpText": "Evita que {appName} intente descargar un lanzamiento alternativo para este elemento",
"Small": "Pequeño",
"SomeResultsAreHiddenByTheAppliedFilter": "Algunos resultados están ocultos por el filtro aplicado",
"SonarrTags": "Etiquetas de {appName}",
"Standard": "Estándar",
"StandardEpisodeTypeFormat": "Temporada y número de episodios ({format})",
"StandardEpisodeTypeDescription": "Episodios lanzados con patrón SxxEyy",
"SubtitleLanguages": "Idiomas de subtítulo",
"SupportedAutoTaggingProperties": "{appName} soporta las siguientes propiedades para reglas de etiquetado automáticas",
"SupportedIndexersMoreInfo": "Para más información en los indexadores individuales, haz clic en los botones de más información.",
"SupportedListsSeries": "{appName} soporta múltiples listas para importar series en la base de datos.",
"TableOptions": "Opciones de tabla",
"TableOptionsButton": "Botón de opciones de tabla",
"Today": "Hoy",
"Titles": "Títulos",
"ToggleUnmonitoredToMonitored": "Sin monitorizar, haz clic para monitorizar",
"TotalFileSize": "Tamaño total de archivo",
"UpdateAvailableHealthCheckMessage": "Hay disponible una nueva actualización",
"UpgradeUntilCustomFormatScore": "Actualizar hasta la puntuación de formato personalizado",
"UrlBase": "URL base",
"UseSsl": "Usar SSL",
"Usenet": "Usenet",
"VersionNumber": "Versión {version}",
"OnManualInteractionRequired": "Cuando se requiera interacción manual",
"OnLatestVersion": "La última versión de {appName} ya está instalada",
"OnUpgrade": "Al actualizar",
"RootFolders": "Carpetas raíz",
"SeasonPremiere": "Estreno de temporada",
"UnableToUpdateSonarrDirectly": "No se pudo actualizar {appName} directamente,",
"UnmappedFolders": "Carpetas sin mapear",
"QualitiesLoadError": "No se pudo cargar las calidades",
"SeasonNumberToken": "Temporada {seasonNumber}",
"PreferredSize": "Tamaño preferido",
"TypeOfList": "Lista {typeOfList}",
"UiSettingsLoadError": "No se pudo cargar las opciones de interfaz",
"UpdateMonitoring": "Actualizar monitorizando",
"ReleaseType": "Tipo de lanzamiento",
"RemotePathMappingLocalWrongOSPathHealthCheckMessage": "El cliente de descarga local {downloadClientName} ubica las descargas en {path} pero esta no es una ruta {osName} válida. Revisa las opciones de tu cliente de descarga.",
"RemotePathMappingWrongOSPathHealthCheckMessage": "El cliente de descarga remoto {downloadClientName} ubica las descargas en {path} pero esta no es una ruta {osName} válida. Revisa tus mapeos de ruta remota y las opciones del cliente de descarga.",
"RemoveFailedDownloadsHelpText": "Eliminar descargas fallidas desde el historial del cliente de descarga",
"RemoveFromQueue": "Eliminar de la cola",
"RemoveMultipleFromDownloadClientHint": "Elimina descargas y archivos del cliente de descarga",
"RemoveQueueItemsRemovalMethodHelpTextWarning": "'Eliminar del cliente de descarga' eliminará las descargas y los archivos del cliente de descarga.",
"RemoveTagsAutomatically": "Eliminar etiquetas automáticamente",
"ReplaceIllegalCharactersHelpText": "Reemplaza los caracteres ilegales. Si no está marcado, {appName} los eliminará en su lugar",
"ResetAPIKey": "Restablecer clave API",
"RootFolder": "Carpeta raíz",
"RootFolderMultipleMissingHealthCheckMessage": "Múltiples carpetas raíz están perdidas: {rootFolderPaths}",
"RestartReloadNote": "Nota: {appName} se reiniciará automáticamente y recargará la interfaz durante el proceso de restauración.",
"RestartRequiredWindowsService": "Dependiendo de qué usuario esté ejecutando el servicio {appName}, puede ser necesario reiniciar {appName} como administrador antes de que el servicio se inicie automáticamente.",
"SeasonPremieresOnly": "Solo estrenos de temporada",
"SeasonPassTruncated": "Solo se muestran las últimas 25 temporadas, ve a detalles para ver todas las temporadas",
"SelectFolderModalTitle": "{modalTitle} - Seleccionar carpeta",
"SeriesDetailsCountEpisodeFiles": "{episodeFileCount} archivos de episodio",
"SeriesIndexFooterContinuing": "Continuando (Todos los episodios descargados)",
"SetIndexerFlagsModalTitle": "{modalTitle} - Establecer banderas del indexador",
"ShortDateFormat": "Formato de fecha breve",
"ShowUnknownSeriesItemsHelpText": "Muestra elementos sin una serie en la cola, esto incluiría series eliminadas, películas o cualquier cosa más en la categoría de {appName}",
"ShownClickToHide": "Mostrado, haz clic para ocultar",
"SkipFreeSpaceCheckWhenImportingHelpText": "Se usa cuando {appName} no puede detectar el espacio libre de tu carpeta raíz durante la importación de archivo",
"SmartReplace": "Reemplazo inteligente",
"SupportedDownloadClientsMoreInfo": "Para más información en los clientes de descarga individuales, haz clic en los botones de más información.",
"SupportedImportListsMoreInfo": "Para más información de los listas de importación individuales, haz clic en los botones de más información.",
"TorrentBlackholeSaveMagnetFilesReadOnlyHelpText": "En lugar de mover archivos esto indicará a {appName} que copie o enlace (dependiendo de los ajustes/configuración del sistema)",
"TorrentDelay": "Retraso de torrent",
"ToggleMonitoredToUnmonitored": "Monitorizado, haz clic para dejar de monitorizar",
"TorrentBlackholeSaveMagnetFilesHelpText": "Guarda el enlace magnet si no hay ningún archivo .torrent disponible (útil solo si el cliente de descarga soporta magnets guardados en un archivo)",
"UiLanguage": "Idioma de interfaz",
"UiLanguageHelpText": "Idioma que {appName} usará en la interfaz",
"UiSettingsSummary": "Opciones de calendario, fecha y color alterado",
"UpdateAutomaticallyHelpText": "Descargar e instalar actualizaciones automáticamente. Todavía puedes instalar desde Sistema: Actualizaciones",
"TotalRecords": "Total de registros: {totalRecords}",
"WantMoreControlAddACustomFormat": "¿Quieres más control sobre qué descargas son preferidas? Añade un [formato personalizado](/opciones/formatospersonalizados)",
"OrganizeModalHeader": "Organizar y renombrar",
"RemoveCompleted": "Eliminar completado",
"OpenBrowserOnStartHelpText": " Abre un navegador web y navega a la página de inicio de {appName} al iniciar la aplicación.",
"SslCertPath": "Ruta del certificado SSL",
"StartImport": "Iniciar importación",
"OptionalName": "Nombre opcional",
"RemotePath": "Ruta remota",
"SeriesPremiere": "Estreno de serie",
"SeriesMatchType": "Tipo de emparejamiento de series",
"SeriesMonitoring": "Monitorización de serie",
"Tba": "TBA",
"TorrentsDisabled": "Torrents deshabilitados",
"RemotePathMappingGenericPermissionsHealthCheckMessage": "El cliente de descarga {downloadClientName} ubica las descargas en {path} pero {appName} no puede ver este directorio. Puede que necesites ajustar los permisos de la carpeta.",
"ReplaceIllegalCharacters": "Reemplazar caracteres ilegales",
"ResetTitles": "Restablecer títulos",
"SmartReplaceHint": "Raya o barra espaciadora según el nombre",
"SelectEpisodesModalTitle": "{modalTitle} - Seleccionar episodio(s)",
"DownloadClientDelugeSettingsDirectory": "Directorio de descarga",
"DownloadClientDelugeSettingsDirectoryHelpText": "Ubicación opcional en la que poner las descargas, dejar en blanco para usar la ubicación predeterminada de Deluge",
"UnmonitorSpecialsEpisodesDescription": "Dejar de monitorizar todos los episodios especiales sin cambiar el estado monitorizado de otros episodios",
"DownloadClientDelugeSettingsDirectoryCompletedHelpText": "Ubicación opcional a la que mover las descargas completadas, dejar en blanco para usar la ubicación predeterminada de Deluge",
"DownloadClientDelugeSettingsDirectoryCompleted": "Directorio al que mover cuando se complete",
"NotificationsDiscordSettingsWebhookUrlHelpText": "URL de canal webhook de Discord",
"NotificationsEmailSettingsCcAddress": "Dirección(es) CC",
"NotificationsEmbySettingsSendNotifications": "Enviar notificaciones",
"NotificationsEmbySettingsUpdateLibraryHelpText": "¿Actualiza biblioteca en importar, renombrar o borrar?",
"NotificationsJoinSettingsDeviceIdsHelpText": "En desuso, usar Nombres de dispositivo en su lugar. Lista separada por coma de los IDs de dispositivo a los que te gustaría enviar notificaciones. Si no se establece, todos los dispositivos recibirán notificaciones.",
"NotificationsPushoverSettingsExpire": "Caduca",
"NotificationsMailgunSettingsSenderDomain": "Dominio del remitente",
"NotificationsNtfySettingsServerUrl": "URL del servidor",
"PreferProtocol": "Preferir {preferredProtocol}",
"ProfilesSettingsSummary": "Perfiles de calidad, de retraso de idioma y de lanzamiento",
"QualitiesHelpText": "Calidades superiores en la lista son más preferibles. Calidades dentro del mismo grupo son iguales. Comprobar solo calidades que se busquen",
"RssIsNotSupportedWithThisIndexer": "RSS no está soportado con este indexador",
"Repack": "Reempaquetar",
"NotificationsGotifySettingsPriorityHelpText": "Prioridad de la notificación",
"NotificationsGotifySettingsServer": "Servidor Gotify",
"NotificationsPlexSettingsAuthToken": "Token de autenticación",
"NotificationsSynologySettingsUpdateLibraryHelpText": "Llamada synoindex en localhost para actualizar un archivo de biblioteca",
"Overview": "Vista general",
"UseSeasonFolderHelpText": "Ordenar episodios en carpetas de temporada",
"RemotePathMappingDockerFolderMissingHealthCheckMessage": "Estás usando docker; el cliente de descarga {downloadClientName} ubica las descargas en {path} pero este directorio no parece existir dentro del contenedor. Revisa tus mapeos de ruta remotos y opciones de volumen del contenedor.",
"Retention": "Retención",
"NotificationsDiscordSettingsOnManualInteractionFields": "Campos durante la interacción manual",
"NotificationsDiscordSettingsOnGrabFields": "Campos al capturar",
"NotificationsDiscordSettingsOnImportFields": "Campos al importar",
"NotificationsDiscordSettingsOnImportFieldsHelpText": "Cambia los campos que se pasan para esta notificación 'al importar'",
"NotificationsDiscordSettingsOnManualInteractionFieldsHelpText": "Cambia los campos que se pasan para esta notificación 'durante la interacción manual'",
"NotificationsEmailSettingsCcAddressHelpText": "Lista separada por coma de destinatarios de e-mail cc",
"NotificationsEmailSettingsFromAddress": "De dirección",
"NotificationsKodiSettingAlwaysUpdateHelpText": "¿Actualiza la biblioteca incluso cuando un video se esté reproduciendo?",
"NotificationsKodiSettingsDisplayTime": "Tiempo de visualización",
"NotificationsLoadError": "No se pudo cargar las notificaciones",
"NotificationsMailgunSettingsApiKeyHelpText": "La clave API generada desde MailGun",
"NotificationsSendGridSettingsApiKeyHelpText": "La clave API generada por SendGrid",
"NotificationsTwitterSettingsConsumerKeyHelpText": "Clave de consumidor de una aplicación de Twitter",
"NotificationsTwitterSettingsDirectMessage": "Mensaje directo",
"NotificationsTwitterSettingsDirectMessageHelpText": "Envía un mensaje directo en lugar de un mensaje público",
"OnApplicationUpdate": "Al actualizar la aplicación",
"OnSeriesAdd": "Al añadir series",
"OnlyForBulkSeasonReleases": "Solo para lanzamientos de temporada a granel",
"OrganizeModalHeaderSeason": "Organizar y renombrar - {season}",
"OverrideGrabNoEpisode": "Al menos un episodio debe ser seleccionado",
"OverrideGrabNoQuality": "La calidad debe ser seleccionada",
"NotificationsValidationInvalidAuthenticationToken": "Token de autenticación inválido",
"NotificationsValidationUnableToConnect": "No se pudo conectar: {exceptionMessage}",
"NotificationsValidationUnableToSendTestMessageApiResponse": "No se pudo enviar un mensaje de prueba. Respuesta de la API: {error}",
"OverrideGrabModalTitle": "Sobrescribe y captura - {title}",
"ReleaseProfileTagSeriesHelpText": "Los perfiles de lanzamientos se aplicarán a series con al menos una etiqueta coincidente. Deja en blanco para aplicar a todas las series",
"ReleaseSceneIndicatorMappedNotRequested": "El episodio mapeado no fue solicitado en esta búsqueda.",
"RemotePathMappingBadDockerPathHealthCheckMessage": "Estás usando docker; el cliente de descarga {downloadClientName} ubica las descargas en {path} pero esta no es una ruta {osName} válida. Revisa tus mapeos de ruta remotos y opciones del cliente de descarga.",
"RemotePathMappingFolderPermissionsHealthCheckMessage": "{appName} puede ver pero no acceder al directorio de descarga {downloadPath}. Probablemente error de permisos.",
"RemotePathMappingHostHelpText": "El mismo host que especificaste para el cliente de descarga remoto",
"ParseModalUnableToParse": "No se pudo analizar el título proporcionado, por favor inténtalo de nuevo.",
"Preferred": "Preferido",
"Priority": "Prioridad",
"QualityProfileInUseSeriesListCollection": "No se puede borrar un perfil de calidad que está asignado a una serie, lista o colección",
"ReleaseProfile": "Perfil de lanzamiento",
"ReleaseProfileIndexerHelpText": "Especifica a qué indexador se aplica el perfil",
"RequiredHelpText": "Esta condición {implementationName} debe coincidir para el formato personalizado para aplicar. De otro modo una coincidencia sencilla {implementationName} es suficiente.",
"RemotePathMappingsLoadError": "No se pudo cargar los mapeos de ruta remota",
"RestartLater": "Reiniciaré más tarde",
"RootFoldersLoadError": "No se pudo cargar las carpetas raíz",
"RssSync": "Sincronización RSS",
"RssSyncIntervalHelpTextWarning": "Esto se aplicará a todos los indexadores, por favor sigue las reglas establecidas por ellos",
"Score": "Puntuación",
"SearchFailedError": "La búsqueda falló, por favor inténtalo de nuevo más tarde.",
"SearchForMonitoredEpisodes": "Buscar episodios monitorizados",
"SearchIsNotSupportedWithThisIndexer": "La búsqueda no está soportada con este indexador",
"SearchMonitored": "Buscar monitorizados",
"SeasonPack": "Pack de temporada",
"SeriesCannotBeFound": "Lo siento, esta serie no puede ser encontrada.",
"SeriesEditRootFolderHelpText": "Mover series a la misma carpeta raíz se puede usar para renombrar carpetas de series para coincidir el título actualizado o el formato de nombrado",
"SeriesFolderFormat": "Formato de carpeta de serie",
"SeriesIndexFooterDownloading": "Descargando (Uno o más episodios)",
"SeriesIndexFooterMissingMonitored": "Episodios perdidos (Serie monitorizada)",
"SeriesProgressBarText": "{episodeFileCount} / {episodeCount} (Total: {totalEpisodeCount}, Descargando: {downloadingCount})",
"UpgradesAllowed": "Actualizaciones permitidas",
"VideoCodec": "Códec de vídeo",
"SeriesTitleToExcludeHelpText": "El nombre de la serie a excluir",
"Shutdown": "Apagar",
"TestAll": "Probar todo",
"UseProxy": "Usar proxy",
"Repeat": "Repetir",
"Replace": "Reemplazar",
"RemoveCompletedDownloadsHelpText": "Elimina las descargas importadas desde el historial del cliente de descarga",
"RemoveQueueItemRemovalMethod": "Método de eliminación",
"RemotePathMappingFilesBadDockerPathHealthCheckMessage": "Estás usando docker; el cliente de descarga {downloadClientName} reportó archivos en {path} pero esta no es una ruta {osName} válida. Revisa tus mapeos de ruta remotos y opciones del cliente de descarga.",
"RemoveCompletedDownloads": "Eliminar descargas completadas",
"RemoveFromDownloadClientHint": "Elimina la descarga y archivo(s) del cliente de descarga",
"EpisodeRequested": "Episodio requerido",
"NotificationsEmailSettingsServer": "Servidor",
"NotificationsEmailSettingsServerHelpText": "Nombre de host o IP del servidor de e-mail",
"NotificationsGotifySettingsAppTokenHelpText": "El token de aplicación generado por Gotify",
"NotificationsGotifySettingsServerHelpText": "URL de servidor de Gotify, incluyendo http(s):// y puerto si es necesario",
"NotificationsJoinSettingsDeviceIds": "IDs de dispositivo",
"NotificationsJoinValidationInvalidDeviceId": "Los IDs de dispositivo parecen inválidos.",
"NotificationsKodiSettingAlwaysUpdate": "Actualizar siempre",
"NotificationsPushcutSettingsApiKeyHelpText": "Las claves API pueden ser gestionadas en la vista Cuenta de la aplicación Pushcut",
"NotificationsPushcutSettingsNotificationName": "Nombre de notificación",
"NotificationsPushoverSettingsRetryHelpText": "Intervalo para reintentar las alertas de emergencia, mínimo 30 segundos",
"NotificationsSettingsUpdateLibrary": "Actualizar biblioteca",
"NotificationsSettingsUpdateMapPathsTo": "Mapear rutas a",
"NotificationsSignalSettingsSenderNumberHelpText": "Número de teléfono del emisor registrado en signal-api",
"NotificationsSlackSettingsChannelHelpText": "Sobrescribe el canal predeterminado para el webhook entrante (#otro-canal)",
"NotificationsSlackSettingsIcon": "Icono",
"NotificationsSynologyValidationInvalidOs": "Debe ser un Synology",
"NotificationsSynologyValidationTestFailed": "No es Synology o synoindex no está disponible",
"NotificationsTelegramSettingsBotToken": "Token de bot",
"NotificationsTelegramSettingsChatId": "ID de chat",
"NotificationsTelegramSettingsTopicIdHelpText": "Especifica una ID de tema para enviar notificaciones a ese tema. Deja en blanco para usar el tema general (solo supergrupos)",
"NotificationsTraktSettingsAccessToken": "Token de acceso",
"NotificationsTraktSettingsAuthUser": "Autenticar usuario",
"NotificationsTwitterSettingsAccessToken": "Token de acceso",
"NotificationsTwitterSettingsAccessTokenSecret": "Token secreto de acceso",
"NotificationsTwitterSettingsConsumerKey": "Clave de consumidor",
"NotificationsTwitterSettingsMention": "Mención",
"NotificationsTwitterSettingsMentionHelpText": "Menciona este usuario en tweets enviados",
"NotificationsValidationInvalidAccessToken": "Token de acceso inválido",
"NotificationsValidationInvalidApiKey": "Clave API inválida",
"ParseModalErrorParsing": "Error analizando, por favor inténtalo de nuevo.",
"ParseModalHelpText": "Introduce un título de lanzamiento en la entrada anterior",
"SearchByTvdbId": "También puedes buscar usando la ID de TVDB de un show. P. ej. tvdb:71663",
"SearchForAllMissingEpisodesConfirmationCount": "¿Estás seguro que quieres buscar los {totalRecords} episodios perdidos?",
"SeriesType": "Tipo de serie",
"TagCannotBeDeletedWhileInUse": "La etiqueta no puede ser borrada mientras esté en uso",
"UnmonitorSpecialEpisodes": "Dejar de monitorizar especiales",
"UpdateStartupTranslocationHealthCheckMessage": "No se puede instalar la actualización porque la carpeta de inicio '{startupFolder}' está en una carpeta de translocalización de la aplicación.",
"Yesterday": "Ayer",
"RemoveQueueItemRemovalMethodHelpTextWarning": "'Eliminar del cliente de descarga' eliminará la descarga y el archivo(s) del cliente de descarga.",
"RemotePathMappingLocalFolderMissingHealthCheckMessage": "El cliente de descarga remoto {downloadClientName} ubica las descargas en {path} pero este directorio no parece existir. Probablemente mapeo de ruta remota perdido o incorrecto.",
"RemotePathMappingsInfo": "Los mapeos de ruta remota son muy raramente solicitados, si {appName} y tu cliente de descarga están en el mismo sistema es mejor coincidir sus rutas. Para más información mira la [wiki]({wikiLink})",
"UpdateScriptPathHelpText": "Ruta a un script personalizado que toma un paquete de actualización extraído y gestiona el resto del proceso de actualización",
"NotificationsTelegramSettingsChatIdHelpText": "Debes comenzar una conversación con el bot o añádelo a tu grupo para recibir mensajes",
"NotificationsEmailSettingsRecipientAddressHelpText": "Lista separada por coma de destinatarios de e-mail",
"NotificationsTwitterSettingsConnectToTwitter": "Conectar a Twitter / X",
"NotificationsValidationInvalidApiKeyExceptionMessage": "Clave API inválida: {exceptionMessage}",
"NotificationsJoinSettingsApiKeyHelpText": "La clave API de tus ajustes de Añadir cuenta (haz clic en el botón Añadir API).",
"NotificationsPushBulletSettingsDeviceIdsHelpText": "Lista de IDs de dispositivo (deja en blanco para enviar a todos los dispositivos)",
"NotificationsSettingsUpdateMapPathsToHelpText": "Ruta de {appName}, usado para modificar rutas de series cuando {serviceName} ve la ubicación de ruta de biblioteca de forma distinta a {appName} (Requiere 'Actualizar biblioteca')",
"ReleaseProfileIndexerHelpTextWarning": "Establecer un indexador específico en un perfil de lanzamiento provocará que este perfil solo se aplique a lanzamientos desde ese indexador.",
"ImportListsMyAnimeListSettingsAuthenticateWithMyAnimeList": "Autenticar con MyAnimeList",
"ImportListsMyAnimeListSettingsListStatus": "Estado de lista",
"ImportListsMyAnimeListSettingsListStatusHelpText": "Tipo de lista desde la que quieres importar, establecer a 'Todo' para todas las listas"
}

Some files were not shown because too many files have changed in this diff Show More