1
0
mirror of https://github.com/Radarr/Radarr.git synced 2026-04-16 21:15:33 -04:00

Compare commits

..

30 Commits

Author SHA1 Message Date
Bogdan
23fce4bf2e Fixed: Refresh backup list on deletion
(cherry picked from commit 3b00112447361b19c04851a510e63f812597a043)
2024-12-15 05:29:12 +02:00
Mark McDowall
64fd8552f8 Fixed: Error getting processes in some cases
(cherry picked from commit b552d4e9f7ca7388404aa0d52566010a54cb0244)
2024-12-15 05:28:39 +02:00
Weblate
e016410c10 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Fixer <ygj59783@zslsz.com>
Co-authored-by: GkhnGRBZ <gkhn.gurbuz@hotmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Rodion <rodyon009@gmail.com>
Co-authored-by: Tomer Horowitz <tomerh2001@gmail.com>
Co-authored-by: Weblate <noreply-mt-weblate@weblate.org>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: farebyting <farelbyting@gmail.com>
Co-authored-by: fordas <fordas15@gmail.com>
Co-authored-by: hhjuhl <hans@kopula.dk>
Co-authored-by: kaisernet <afimark7@gmail.com>
Co-authored-by: keysuck <joshkkim@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/da/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/he/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/id/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ko/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/tr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/uk/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/zh_CN/
Translation: Servarr/Radarr
2024-12-14 02:33:47 +02:00
Bogdan
bea943adf8 New: Tooltip with extra genres on search and collections 2024-12-13 19:17:59 +02:00
Bogdan
9780d20f8a Improve is visible property check for discover movies 2024-12-13 19:14:41 +02:00
Bogdan
62722d45b0 Fixed: Using all movie genres for collection filters 2024-12-13 19:13:55 +02:00
Bogdan
27dd8e8cd5 New: Tooltip with extra genres on movie details page 2024-12-12 21:59:19 +02:00
Bogdan
6c47ede76b Fixed: Refreshing movie genres 2024-12-12 21:58:54 +02:00
Mark McDowall
7b9562bb38 Update React
(cherry picked from commit 4491df3ae7530f2167beebc3548dd01fd2cc1a12)

Towards #10703
2024-12-12 21:01:19 +02:00
Stevie Robinson
8b0b7c1cb0 New: Reactive search button on Wanted pages
(cherry picked from commit e8c3aa20bd92701a16dcd97c5e103b79b3683105)

Closes #10750
2024-12-12 20:42:55 +02:00
Mark McDowall
7ebd341cd6 Sync TimeSpanConverter with upstream
(cherry picked from commit 1374240321f08d1400faf95e84217e4b7a2d116b)

Closes #10756
2024-12-09 14:03:12 +02:00
Bogdan
6c85f166ff Bump version to 5.16.2 2024-12-08 11:22:50 +02:00
Servarr
45aabce107 Automated API Docs update 2024-12-04 14:09:02 +02:00
soup
0caa793df4 New: Add config file setting for CGNAT authentication bypass
(cherry picked from commit 4c41a4f368046f73f82306bbd73bec992392938b)
2024-12-04 13:30:56 +02:00
Stevie Robinson
9a107cc8d7 New: Add Languages to Webhook Notifications
(cherry picked from commit e039dc45e267cf717e00c0a49ba637012f37e3d7)

Closes #10733
2024-12-02 16:59:45 +02:00
Mark McDowall
a6d727fe2a New: Kometa metadata file creation disabled
(cherry picked from commit c62fc9d05bb9e1fe51b454d78e80bd9250e31f89)

Closes #10738
2024-12-02 16:56:12 +02:00
Servarr
01a53d3624 Automated API Docs update 2024-12-02 16:03:25 +02:00
Weblate
348c29c9d7 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Ardenet <1213193613@qq.com>
Co-authored-by: Languages add-on <noreply-addon-languages@weblate.org>
Co-authored-by: Robin Dadswell <robin@robindadswell.tech>
Co-authored-by: Weblate <noreply-mt-weblate@weblate.org>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: mryx007 <mryx@mail.de>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/zh_CN/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/zh_Hans/
Translation: Servarr/Radarr
2024-12-02 15:34:43 +02:00
Bogdan
64739712c6 Add return type for movie lookup and import endpoints
Closes #10737
2024-12-02 15:29:50 +02:00
Bogdan
6ac9cca953 Avoid default category on existing Transmission configurations
Co-authored-by: Mark McDowall <mark@mcdowall.ca>
(cherry picked from commit bd656ae7f66fc9224ef2a57857152ee5d54d54f8)
2024-12-02 15:12:07 +02:00
Bogdan
a2b38c5b7d New: Labels support for Transmission 4.0
(cherry picked from commit 675e3cd38a14ea33c27f2d66a4be2bf802e17d88)
2024-12-02 15:12:07 +02:00
Bogdan
3cc4105d71 Bump NLog, Npgsql, Ical.Net, IPAddressRange, ImageSharp and Polly 2024-12-02 14:30:38 +02:00
Mark McDowall
3449a5d3fe Fixed: Don't fail import if symlink target can't be resolved
(cherry picked from commit 8cb58a63d8ec1b290bc57ad2cf1e90809ceebce9)
2024-12-02 02:32:21 +02:00
Gylesie
5bac157d36 Remove unnecessary heap allocations in local IP check
(cherry picked from commit ed536a85ad5f2062bf6f01f80efddb19fa935f63)
2024-12-02 02:31:28 +02:00
Mark McDowall
114d260f42 Deprecate Sizeleft and Timeleft queue item properties
Rename SizeLeft and TimeLeft queue item properties

(cherry picked from commit b51a49097941e5f306cae5785c63985b319784fd)

Fixed: Error loading queue

(cherry picked from commit f9606518eef78117f1e06a8bcc34af57ab0d2454)
2024-12-01 19:25:45 +02:00
Bogdan
617b9c5d35 Console warnings for missing translations on development builds
(cherry picked from commit 67a1ecb0fea4e6c7dfdb68fbe3ef30d4c22398d8)

Closes #10669
2024-12-01 18:54:53 +02:00
Mark McDowall
ba4ccbb0bd Deluge communication improvements
(cherry picked from commit 183b8b574a4dd948b5fada94d0e645b87710f223)
2024-12-01 14:01:41 +02:00
Mark McDowall
b845268b3d New: Support for new SABnzbd history retention values
Closes #10699

(cherry picked from commit e361f18837d98c089f7dc9c0190221ca8e2cf225)
2024-12-01 14:01:09 +02:00
Bogdan
0fee552074 Bump version to 5.16.1 2024-12-01 14:00:15 +02:00
Mark McDowall
828b994ef4 Support Postgres with non-standard version string
(cherry picked from commit 40f4ef27b22113c1dae0d0cbdee8205132bed68a)
2024-12-01 11:47:19 +00:00
118 changed files with 2401 additions and 1568 deletions

View File

@@ -9,7 +9,7 @@ variables:
testsFolder: './_tests'
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
majorVersion: '5.16.0'
majorVersion: '5.16.2'
minorVersion: $[counter('minorVersion', 2000)]
radarrVersion: '$(majorVersion).$(minorVersion)'
buildName: '$(Build.SourceBranchName).$(radarrVersion)'

View File

@@ -210,7 +210,6 @@ module.exports = {
'no-undef-init': 'off',
'no-undefined': 'off',
'no-unused-vars': ['error', { args: 'none', ignoreRestSiblings: true }],
'no-use-before-define': 'error',
// Node.js and CommonJS

View File

@@ -91,6 +91,10 @@
margin-left: 5px;
}
.genres {
pointer-events: all;
}
.links {
margin-left: 5px;
pointer-events: all;

View File

@@ -10,6 +10,7 @@ import { icons, kinds, sizes, tooltipPositions } from 'Helpers/Props';
import MovieDetailsLinks from 'Movie/Details/MovieDetailsLinks';
import MovieStatusLabel from 'Movie/Details/MovieStatusLabel';
import MovieIndexProgressBar from 'Movie/Index/ProgressBar/MovieIndexProgressBar';
import MovieGenres from 'Movie/MovieGenres';
import MoviePoster from 'Movie/MoviePoster';
import formatRuntime from 'Utilities/Date/formatRuntime';
import translate from 'Utilities/String/translate';
@@ -249,9 +250,7 @@ class AddNewMovieSearchResult extends Component {
name={icons.GENRE}
size={13}
/>
<span className={styles.genres}>
{genres.slice(0, 3).join(', ')}
</span>
<MovieGenres className={styles.genres} genres={genres} />
</Label> :
null
}
@@ -280,7 +279,7 @@ class AddNewMovieSearchResult extends Component {
}
canFlip={true}
kind={kinds.INVERSE}
position={tooltipPositions.BOTTOM}
position={tooltipPositions.TOP}
/>
{

View File

@@ -1,11 +1,16 @@
import Column from 'Components/Table/Column';
import { SortDirection } from 'Helpers/Props/sortDirections';
import { ValidationFailure } from 'typings/pending';
import { FilterBuilderProp, PropertyFilter } from './AppState';
export interface Error {
responseJSON: {
message: string;
};
status?: number;
responseJSON:
| {
message: string | undefined;
}
| ValidationFailure[]
| undefined;
}
export interface AppSectionDeleteState {
@@ -51,6 +56,16 @@ export interface AppSectionItemState<T> {
item: T;
}
export interface AppSectionProviderState<T>
extends AppSectionDeleteState,
AppSectionSaveState {
isFetching: boolean;
isPopulated: boolean;
error: Error;
items: T[];
pendingChanges: Partial<T>;
}
interface AppSectionState<T> {
isFetching: boolean;
isPopulated: boolean;

View File

@@ -0,0 +1,6 @@
import { AppSectionProviderState } from 'App/State/AppSectionState';
import Metadata from 'typings/Metadata';
interface MetadataAppState extends AppSectionProviderState<Metadata> {}
export default MetadataAppState;

View File

@@ -20,6 +20,7 @@ import NamingConfig from 'typings/Settings/NamingConfig';
import NamingExample from 'typings/Settings/NamingExample';
import ReleaseProfile from 'typings/Settings/ReleaseProfile';
import UiSettings from 'typings/Settings/UiSettings';
import MetadataAppState from './MetadataAppState';
export interface DownloadClientAppState
extends AppSectionState<DownloadClient>,
@@ -97,6 +98,7 @@ interface SettingsAppState {
indexerFlags: IndexerFlagSettingsAppState;
indexers: IndexerAppState;
languages: LanguageSettingsAppState;
metadata: MetadataAppState;
naming: NamingAppState;
namingExamples: NamingExamplesAppState;
notifications: NotificationAppState;

View File

@@ -22,7 +22,7 @@ function createMapStateToProps() {
return {
...collection,
movies: [...collection.movies].sort((a, b) => b.year - a.year),
genres: Array.from(new Set(allGenres)).slice(0, 3)
genres: Array.from(new Set(allGenres))
};
}
);

View File

@@ -10,6 +10,7 @@ import Label from 'Components/Label';
import IconButton from 'Components/Link/IconButton';
import MonitorToggleButton from 'Components/MonitorToggleButton';
import { icons, sizes } from 'Helpers/Props';
import MovieGenres from 'Movie/MovieGenres';
import QualityProfileNameConnector from 'Settings/Profiles/Quality/QualityProfileNameConnector';
import dimensions from 'Styles/Variables/dimensions';
import fonts from 'Styles/Variables/fonts';
@@ -242,12 +243,10 @@ class CollectionOverview extends Component {
size={sizes.MEDIUM}
>
<Icon
name={icons.PROFILE}
name={icons.GENRE}
size={13}
/>
<span className={styles.genres}>
{genres.join(', ')}
</span>
<MovieGenres className={styles.genres} genres={genres} />
</Label>
}

View File

@@ -139,6 +139,8 @@ ProviderFieldFormGroup.propTypes = {
type: PropTypes.string.isRequired,
advanced: PropTypes.bool.isRequired,
hidden: PropTypes.string,
isDisabled: PropTypes.bool,
provider: PropTypes.string,
pending: PropTypes.bool.isRequired,
errors: PropTypes.arrayOf(PropTypes.object).isRequired,
warnings: PropTypes.arrayOf(PropTypes.object).isRequired,

View File

@@ -49,7 +49,7 @@ function isVisible(row, props) {
valueProp
} = row;
return _.has(props, valueProp) && (_.get(props, showProp) || props.sortKey === name);
return _.has(props, valueProp) && _.get(props, valueProp) !== null && (props[showProp] || props.sortKey === name);
}
function getInfoRowProps(row, props) {

View File

@@ -28,6 +28,7 @@ import EditMovieModalConnector from 'Movie/Edit/EditMovieModalConnector';
import getMovieStatusDetails from 'Movie/getMovieStatusDetails';
import MovieHistoryModal from 'Movie/History/MovieHistoryModal';
import MovieCollectionLabelConnector from 'Movie/MovieCollectionLabelConnector';
import MovieGenres from 'Movie/MovieGenres';
import MoviePoster from 'Movie/MoviePoster';
import MovieInteractiveSearchModal from 'Movie/Search/MovieInteractiveSearchModal';
import MovieFileEditorTable from 'MovieFile/Editor/MovieFileEditorTable';
@@ -651,9 +652,7 @@ class MovieDetails extends Component {
name={translate('Genres')}
size={sizes.LARGE}
>
<span className={styles.genres}>
{genres.join(', ')}
</span>
<MovieGenres className={styles.genres} genres={genres} />
</InfoLabel> :
null
}

View File

@@ -0,0 +1,39 @@
import React from 'react';
import Label from 'Components/Label';
import Tooltip from 'Components/Tooltip/Tooltip';
import { kinds, sizes, tooltipPositions } from 'Helpers/Props';
interface MovieGenresProps {
className?: string;
genres: string[];
}
function MovieGenres({ className, genres }: MovieGenresProps) {
const firstGenres = genres.slice(0, 3);
const otherGenres = genres.slice(3);
if (otherGenres.length) {
return (
<Tooltip
anchor={<span className={className}>{firstGenres.join(', ')}</span>}
tooltip={
<div>
{otherGenres.map((tag) => {
return (
<Label key={tag} kind={kinds.INFO} size={sizes.LARGE}>
{tag}
</Label>
);
})}
</div>
}
kind={kinds.INVERSE}
position={tooltipPositions.TOP}
/>
);
}
return <span className={className}>{firstGenres.join(', ')}</span>;
}
export default MovieGenres;

View File

@@ -40,7 +40,7 @@ function createImportListExclusionSelector(id?: number) {
importListExclusions;
const mapping = id
? items.find((i) => i.id === id)
? items.find((i) => i.id === id)!
: newImportListExclusion;
const settings = selectSettings(mapping, pendingChanges, saveError);

View File

@@ -1,27 +0,0 @@
import PropTypes from 'prop-types';
import React from 'react';
import Modal from 'Components/Modal/Modal';
import { sizes } from 'Helpers/Props';
import EditMetadataModalContentConnector from './EditMetadataModalContentConnector';
function EditMetadataModal({ isOpen, onModalClose, ...otherProps }) {
return (
<Modal
size={sizes.MEDIUM}
isOpen={isOpen}
onModalClose={onModalClose}
>
<EditMetadataModalContentConnector
{...otherProps}
onModalClose={onModalClose}
/>
</Modal>
);
}
EditMetadataModal.propTypes = {
isOpen: PropTypes.bool.isRequired,
onModalClose: PropTypes.func.isRequired
};
export default EditMetadataModal;

View File

@@ -0,0 +1,36 @@
import React, { useCallback } from 'react';
import { useDispatch } from 'react-redux';
import Modal from 'Components/Modal/Modal';
import { sizes } from 'Helpers/Props';
import { clearPendingChanges } from 'Store/Actions/baseActions';
import EditMetadataModalContent, {
EditMetadataModalContentProps,
} from './EditMetadataModalContent';
interface EditMetadataModalProps extends EditMetadataModalContentProps {
isOpen: boolean;
}
function EditMetadataModal({
isOpen,
onModalClose,
...otherProps
}: EditMetadataModalProps) {
const dispatch = useDispatch();
const handleModalClose = useCallback(() => {
dispatch(clearPendingChanges({ section: 'metadata' }));
onModalClose();
}, [dispatch, onModalClose]);
return (
<Modal size={sizes.MEDIUM} isOpen={isOpen} onModalClose={handleModalClose}>
<EditMetadataModalContent
{...otherProps}
onModalClose={handleModalClose}
/>
</Modal>
);
}
export default EditMetadataModal;

View File

@@ -1,44 +0,0 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { clearPendingChanges } from 'Store/Actions/baseActions';
import EditMetadataModal from './EditMetadataModal';
function createMapDispatchToProps(dispatch, props) {
const section = 'settings.metadata';
return {
dispatchClearPendingChanges() {
dispatch(clearPendingChanges({ section }));
}
};
}
class EditMetadataModalConnector extends Component {
//
// Listeners
onModalClose = () => {
this.props.dispatchClearPendingChanges({ section: 'metadata' });
this.props.onModalClose();
};
//
// Render
render() {
return (
<EditMetadataModal
{...this.props}
onModalClose={this.onModalClose}
/>
);
}
}
EditMetadataModalConnector.propTypes = {
onModalClose: PropTypes.func.isRequired,
dispatchClearPendingChanges: PropTypes.func.isRequired
};
export default connect(null, createMapDispatchToProps)(EditMetadataModalConnector);

View File

@@ -0,0 +1,5 @@
.message {
composes: alert from '~Components/Alert.css';
margin-bottom: 30px;
}

View File

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

View File

@@ -1,105 +0,0 @@
import PropTypes from 'prop-types';
import React 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 ProviderFieldFormGroup from 'Components/Form/ProviderFieldFormGroup';
import Button from 'Components/Link/Button';
import SpinnerErrorButton from 'Components/Link/SpinnerErrorButton';
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 } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
function EditMetadataModalContent(props) {
const {
advancedSettings,
isSaving,
saveError,
item,
onInputChange,
onFieldChange,
onModalClose,
onSavePress,
...otherProps
} = props;
const {
name,
enable,
fields
} = item;
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
{translate('EditMetadata', { metadataType: name.value })}
</ModalHeader>
<ModalBody>
<Form {...otherProps}>
<FormGroup>
<FormLabel>{translate('Enable')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="enable"
helpText={translate('EnableMetadataHelpText')}
{...enable}
onChange={onInputChange}
/>
</FormGroup>
{
fields.map((field) => {
return (
<ProviderFieldFormGroup
key={field.name}
advancedSettings={advancedSettings}
provider="metadata"
{...field}
isDisabled={!enable.value}
onChange={onFieldChange}
/>
);
})
}
</Form>
</ModalBody>
<ModalFooter>
<Button
onPress={onModalClose}
>
{translate('Cancel')}
</Button>
<SpinnerErrorButton
isSpinning={isSaving}
error={saveError}
onPress={onSavePress}
>
{translate('Save')}
</SpinnerErrorButton>
</ModalFooter>
</ModalContent>
);
}
EditMetadataModalContent.propTypes = {
advancedSettings: PropTypes.bool.isRequired,
isSaving: PropTypes.bool.isRequired,
saveError: PropTypes.object,
item: PropTypes.object.isRequired,
onInputChange: PropTypes.func.isRequired,
onFieldChange: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired,
onSavePress: PropTypes.func.isRequired,
onDeleteMetadataPress: PropTypes.func
};
export default EditMetadataModalContent;

View File

@@ -0,0 +1,128 @@
import React, { useCallback, useMemo } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import AppState from 'App/State/AppState';
import Alert from 'Components/Alert';
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 ProviderFieldFormGroup from 'Components/Form/ProviderFieldFormGroup';
import Button from 'Components/Link/Button';
import SpinnerErrorButton from 'Components/Link/SpinnerErrorButton';
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 } from 'Helpers/Props';
import {
saveMetadata,
setMetadataFieldValue,
setMetadataValue,
} from 'Store/Actions/settingsActions';
import selectSettings from 'Store/Selectors/selectSettings';
import { InputChanged } from 'typings/inputs';
import translate from 'Utilities/String/translate';
import styles from './EditMetadataModalContent.css';
export interface EditMetadataModalContentProps {
id: number;
advancedSettings: boolean;
onModalClose: () => void;
}
function EditMetadataModalContent({
id,
advancedSettings,
onModalClose,
}: EditMetadataModalContentProps) {
const dispatch = useDispatch();
const { isSaving, saveError, pendingChanges, items } = useSelector(
(state: AppState) => state.settings.metadata
);
const { settings, ...otherSettings } = useMemo(() => {
const item = items.find((item) => item.id === id)!;
return selectSettings(item, pendingChanges, saveError);
}, [id, items, pendingChanges, saveError]);
const { name, enable, fields, message } = settings;
const handleInputChange = useCallback(
({ name, value }: InputChanged) => {
// @ts-expect-error not typed
dispatch(setMetadataValue({ name, value }));
},
[dispatch]
);
const handleFieldChange = useCallback(
({ name, value }: InputChanged) => {
// @ts-expect-error not typed
dispatch(setMetadataFieldValue({ name, value }));
},
[dispatch]
);
const handleSavePress = useCallback(() => {
dispatch(saveMetadata({ id }));
}, [id, dispatch]);
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
{translate('EditMetadata', { metadataType: name.value })}
</ModalHeader>
<ModalBody>
<Form {...otherSettings}>
{message ? (
<Alert className={styles.message} kind={message.value.type}>
{message.value.message}
</Alert>
) : null}
<FormGroup>
<FormLabel>{translate('Enable')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="enable"
helpText={translate('EnableMetadataHelpText')}
{...enable}
onChange={handleInputChange}
/>
</FormGroup>
{fields.map((field) => {
return (
<ProviderFieldFormGroup
key={field.name}
advancedSettings={advancedSettings}
provider="metadata"
{...field}
isDisabled={!enable.value}
onChange={handleFieldChange}
/>
);
})}
</Form>
</ModalBody>
<ModalFooter>
<Button onPress={onModalClose}>{translate('Cancel')}</Button>
<SpinnerErrorButton
isSpinning={isSaving}
error={saveError}
onPress={handleSavePress}
>
{translate('Save')}
</SpinnerErrorButton>
</ModalFooter>
</ModalContent>
);
}
export default EditMetadataModalContent;

View File

@@ -1,95 +0,0 @@
import _ from 'lodash';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { saveMetadata, setMetadataFieldValue, setMetadataValue } from 'Store/Actions/settingsActions';
import selectSettings from 'Store/Selectors/selectSettings';
import EditMetadataModalContent from './EditMetadataModalContent';
function createMapStateToProps() {
return createSelector(
(state) => state.settings.advancedSettings,
(state, { id }) => id,
(state) => state.settings.metadata,
(advancedSettings, id, metadata) => {
const {
isSaving,
saveError,
pendingChanges,
items
} = metadata;
const settings = selectSettings(_.find(items, { id }), pendingChanges, saveError);
return {
advancedSettings,
id,
isSaving,
saveError,
item: settings.settings,
...settings
};
}
);
}
const mapDispatchToProps = {
setMetadataValue,
setMetadataFieldValue,
saveMetadata
};
class EditMetadataModalContentConnector extends Component {
//
// Lifecycle
componentDidUpdate(prevProps, prevState) {
if (prevProps.isSaving && !this.props.isSaving && !this.props.saveError) {
this.props.onModalClose();
}
}
//
// Listeners
onInputChange = ({ name, value }) => {
this.props.setMetadataValue({ name, value });
};
onFieldChange = ({ name, value }) => {
this.props.setMetadataFieldValue({ name, value });
};
onSavePress = () => {
this.props.saveMetadata({ id: this.props.id });
};
//
// Render
render() {
return (
<EditMetadataModalContent
{...this.props}
onSavePress={this.onSavePress}
onInputChange={this.onInputChange}
onFieldChange={this.onFieldChange}
/>
);
}
}
EditMetadataModalContentConnector.propTypes = {
id: PropTypes.number,
isSaving: PropTypes.bool.isRequired,
saveError: PropTypes.object,
item: PropTypes.object.isRequired,
setMetadataValue: PropTypes.func.isRequired,
setMetadataFieldValue: PropTypes.func.isRequired,
saveMetadata: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired
};
export default connect(createMapStateToProps, mapDispatchToProps)(EditMetadataModalContentConnector);

View File

@@ -1,150 +0,0 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Card from 'Components/Card';
import Label from 'Components/Label';
import { kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import EditMetadataModalConnector from './EditMetadataModalConnector';
import styles from './Metadata.css';
class Metadata extends Component {
//
// Lifecycle
constructor(props, context) {
super(props, context);
this.state = {
isEditMetadataModalOpen: false
};
}
//
// Listeners
onEditMetadataPress = () => {
this.setState({ isEditMetadataModalOpen: true });
};
onEditMetadataModalClose = () => {
this.setState({ isEditMetadataModalOpen: false });
};
//
// Render
render() {
const {
id,
name,
enable,
fields
} = this.props;
const metadataFields = [];
const imageFields = [];
fields.forEach((field) => {
if (field.section === 'metadata') {
metadataFields.push(field);
} else {
imageFields.push(field);
}
});
return (
<Card
className={styles.metadata}
overlayContent={true}
onPress={this.onEditMetadataPress}
>
<div className={styles.name}>
{name}
</div>
<div>
{
enable ?
<Label kind={kinds.SUCCESS}>
{translate('Enabled')}
</Label> :
<Label
kind={kinds.DISABLED}
outline={true}
>
{translate('Disabled')}
</Label>
}
</div>
{
enable && !!metadataFields.length &&
<div>
<div className={styles.section}>
{translate('Metadata')}
</div>
{
metadataFields.map((field) => {
if (!field.value) {
return null;
}
return (
<Label
key={field.label}
kind={kinds.SUCCESS}
>
{field.label}
</Label>
);
})
}
</div>
}
{
enable && !!imageFields.length &&
<div>
<div className={styles.section}>
{translate('Images')}
</div>
{
imageFields.map((field) => {
if (!field.value) {
return null;
}
return (
<Label
key={field.label}
kind={kinds.SUCCESS}
>
{field.label}
</Label>
);
})
}
</div>
}
<EditMetadataModalConnector
id={id}
isOpen={this.state.isEditMetadataModalOpen}
onModalClose={this.onEditMetadataModalClose}
/>
</Card>
);
}
}
Metadata.propTypes = {
id: PropTypes.number.isRequired,
name: PropTypes.string.isRequired,
enable: PropTypes.bool.isRequired,
fields: PropTypes.arrayOf(PropTypes.object).isRequired
};
export default Metadata;

View File

@@ -0,0 +1,107 @@
import React, { useCallback, useMemo, useState } from 'react';
import Card from 'Components/Card';
import Label from 'Components/Label';
import { kinds } from 'Helpers/Props';
import Field from 'typings/Field';
import translate from 'Utilities/String/translate';
import EditMetadataModal from './EditMetadataModal';
import styles from './Metadata.css';
interface MetadataProps {
id: number;
name: string;
enable: boolean;
fields: Field[];
}
function Metadata({ id, name, enable, fields }: MetadataProps) {
const [isEditMetadataModalOpen, setIsEditMetadataModalOpen] = useState(false);
const { metadataFields, imageFields } = useMemo(() => {
return fields.reduce<{ metadataFields: Field[]; imageFields: Field[] }>(
(acc, field) => {
if (field.section === 'metadata') {
acc.metadataFields.push(field);
} else {
acc.imageFields.push(field);
}
return acc;
},
{ metadataFields: [], imageFields: [] }
);
}, [fields]);
const handleOpenPress = useCallback(() => {
setIsEditMetadataModalOpen(true);
}, []);
const handleModalClose = useCallback(() => {
setIsEditMetadataModalOpen(false);
}, []);
return (
<Card
className={styles.metadata}
overlayContent={true}
onPress={handleOpenPress}
>
<div className={styles.name}>{name}</div>
<div>
{enable ? (
<Label kind={kinds.SUCCESS}>{translate('Enabled')}</Label>
) : (
<Label kind={kinds.DISABLED} outline={true}>
{translate('Disabled')}
</Label>
)}
</div>
{enable && metadataFields.length ? (
<div>
<div className={styles.section}>{translate('Metadata')}</div>
{metadataFields.map((field) => {
if (!field.value) {
return null;
}
return (
<Label key={field.label} kind={kinds.SUCCESS}>
{field.label}
</Label>
);
})}
</div>
) : null}
{enable && imageFields.length ? (
<div>
<div className={styles.section}>{translate('Images')}</div>
{imageFields.map((field) => {
if (!field.value) {
return null;
}
return (
<Label key={field.label} kind={kinds.SUCCESS}>
{field.label}
</Label>
);
})}
</div>
) : null}
<EditMetadataModal
advancedSettings={false}
id={id}
isOpen={isEditMetadataModalOpen}
onModalClose={handleModalClose}
/>
</Card>
);
}
export default Metadata;

View File

@@ -1,44 +0,0 @@
import PropTypes from 'prop-types';
import React from 'react';
import FieldSet from 'Components/FieldSet';
import PageSectionContent from 'Components/Page/PageSectionContent';
import translate from 'Utilities/String/translate';
import Metadata from './Metadata';
import styles from './Metadatas.css';
function Metadatas(props) {
const {
items,
...otherProps
} = props;
return (
<FieldSet legend={translate('Metadata')}>
<PageSectionContent
errorMessage={translate('MetadataLoadError')}
{...otherProps}
>
<div className={styles.metadatas}>
{
items.map((item) => {
return (
<Metadata
key={item.id}
{...item}
/>
);
})
}
</div>
</PageSectionContent>
</FieldSet>
);
}
Metadatas.propTypes = {
isFetching: PropTypes.bool.isRequired,
error: PropTypes.object,
items: PropTypes.arrayOf(PropTypes.object).isRequired
};
export default Metadatas;

View File

@@ -0,0 +1,52 @@
import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { createSelector } from 'reselect';
import MetadataAppState from 'App/State/MetadataAppState';
import FieldSet from 'Components/FieldSet';
import PageSectionContent from 'Components/Page/PageSectionContent';
import { fetchMetadata } from 'Store/Actions/settingsActions';
import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector';
import MetadataType from 'typings/Metadata';
import sortByProp from 'Utilities/Array/sortByProp';
import translate from 'Utilities/String/translate';
import Metadata from './Metadata';
import styles from './Metadatas.css';
function createMetadatasSelector() {
return createSelector(
createSortedSectionSelector<MetadataType>(
'settings.metadata',
sortByProp('name')
),
(metadata: MetadataAppState) => metadata
);
}
function Metadatas() {
const dispatch = useDispatch();
const { isFetching, error, items, ...otherProps } = useSelector(
createMetadatasSelector()
);
useEffect(() => {
dispatch(fetchMetadata());
}, [dispatch]);
return (
<FieldSet legend={translate('Metadata')}>
<PageSectionContent
isFetching={isFetching}
errorMessage={translate('MetadataLoadError')}
{...otherProps}
>
<div className={styles.metadatas}>
{items.map((item) => {
return <Metadata key={item.id} {...item} />;
})}
</div>
</PageSectionContent>
</FieldSet>
);
}
export default Metadatas;

View File

@@ -1,47 +0,0 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { fetchMetadata } from 'Store/Actions/settingsActions';
import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector';
import sortByProp from 'Utilities/Array/sortByProp';
import Metadatas from './Metadatas';
function createMapStateToProps() {
return createSelector(
createSortedSectionSelector('settings.metadata', sortByProp('name')),
(metadata) => metadata
);
}
const mapDispatchToProps = {
fetchMetadata
};
class MetadatasConnector extends Component {
//
// Lifecycle
componentDidMount() {
this.props.fetchMetadata();
}
//
// Render
render() {
return (
<Metadatas
{...this.props}
onConfirmDeleteMetadata={this.onConfirmDeleteMetadata}
/>
);
}
}
MetadatasConnector.propTypes = {
fetchMetadata: PropTypes.func.isRequired
};
export default connect(createMapStateToProps, mapDispatchToProps)(MetadatasConnector);

View File

@@ -3,7 +3,7 @@ import PageContent from 'Components/Page/PageContent';
import PageContentBody from 'Components/Page/PageContentBody';
import SettingsToolbarConnector from 'Settings/SettingsToolbarConnector';
import translate from 'Utilities/String/translate';
import MetadatasConnector from './Metadata/MetadatasConnector';
import Metadatas from './Metadata/Metadatas';
import MetadataOptionsConnector from './Options/MetadataOptionsConnector';
class MetadataSettings extends Component {
@@ -62,7 +62,7 @@ class MetadataSettings extends Component {
onChildStateChange={this.onChildStateChange}
/>
<MetadatasConnector />
<Metadatas />
</PageContentBody>
</PageContent>
);

View File

@@ -19,14 +19,15 @@ import {
setReleaseProfileValue,
} from 'Store/Actions/Settings/releaseProfiles';
import selectSettings from 'Store/Selectors/selectSettings';
import { PendingSection } from 'typings/pending';
import ReleaseProfile from 'typings/Settings/ReleaseProfile';
import translate from 'Utilities/String/translate';
import styles from './EditReleaseProfileModalContent.css';
const tagInputDelimiters = ['Tab', 'Enter'];
const newReleaseProfile = {
const newReleaseProfile: ReleaseProfile = {
id: 0,
name: '',
enabled: true,
required: [],
ignored: [],
@@ -41,8 +42,12 @@ function createReleaseProfileSelector(id?: number) {
const { items, isFetching, error, isSaving, saveError, pendingChanges } =
releaseProfiles;
const mapping = id ? items.find((i) => i.id === id) : newReleaseProfile;
const settings = selectSettings(mapping, pendingChanges, saveError);
const mapping = id ? items.find((i) => i.id === id)! : newReleaseProfile;
const settings = selectSettings<ReleaseProfile>(
mapping,
pendingChanges,
saveError
);
return {
id,
@@ -50,7 +55,7 @@ function createReleaseProfileSelector(id?: number) {
error,
isSaving,
saveError,
item: settings.settings as PendingSection<ReleaseProfile>,
item: settings.settings,
...settings,
};
}

View File

@@ -91,12 +91,8 @@ export const defaultState = {
genres: function(item, filterValue, type) {
const predicate = filterTypePredicates[type];
let allGenres = [];
item.movies.forEach((movie) => {
allGenres = allGenres.concat(movie.genres);
});
const genres = Array.from(new Set(allGenres)).slice(0, 3);
const allGenres = item.movies.flatMap(({ genres }) => genres);
const genres = Array.from(new Set(allGenres));
return predicate(genres, filterValue);
},
@@ -138,12 +134,8 @@ export const defaultState = {
type: filterBuilderTypes.ARRAY,
optionsSelector: function(items) {
const genreList = items.reduce((acc, collection) => {
let collectionGenres = [];
collection.movies.forEach((movie) => {
collectionGenres = collectionGenres.concat(movie.genres);
});
const genres = Array.from(new Set(collectionGenres)).slice(0, 3);
const collectionGenres = collection.movies.flatMap(({ genres }) => genres);
const genres = Array.from(new Set(collectionGenres));
genres.forEach((genre) => {
acc.push({

View File

@@ -1,104 +0,0 @@
import _ from 'lodash';
function getValidationFailures(saveError) {
if (!saveError || saveError.status !== 400) {
return [];
}
return _.cloneDeep(saveError.responseJSON);
}
function mapFailure(failure) {
return {
message: failure.errorMessage,
link: failure.infoLink,
detailedMessage: failure.detailedDescription
};
}
function selectSettings(item, pendingChanges, saveError) {
const validationFailures = getValidationFailures(saveError);
// Merge all settings from the item along with pending
// changes to ensure any settings that were not included
// with the item are included.
const allSettings = Object.assign({}, item, pendingChanges);
const settings = _.reduce(allSettings, (result, value, key) => {
if (key === 'fields') {
return result;
}
// Return a flattened value
if (key === 'implementationName') {
result.implementationName = item[key];
return result;
}
const setting = {
value: item[key],
errors: _.map(_.remove(validationFailures, (failure) => {
return failure.propertyName.toLowerCase() === key.toLowerCase() && !failure.isWarning;
}), mapFailure),
warnings: _.map(_.remove(validationFailures, (failure) => {
return failure.propertyName.toLowerCase() === key.toLowerCase() && failure.isWarning;
}), mapFailure)
};
if (pendingChanges.hasOwnProperty(key)) {
setting.previousValue = setting.value;
setting.value = pendingChanges[key];
setting.pending = true;
}
result[key] = setting;
return result;
}, {});
const fields = _.reduce(item.fields, (result, f) => {
const field = Object.assign({ pending: false }, f);
const hasPendingFieldChange = pendingChanges.fields && pendingChanges.fields.hasOwnProperty(field.name);
if (hasPendingFieldChange) {
field.previousValue = field.value;
field.value = pendingChanges.fields[field.name];
field.pending = true;
}
field.errors = _.map(_.remove(validationFailures, (failure) => {
return failure.propertyName.toLowerCase() === field.name.toLowerCase() && !failure.isWarning;
}), mapFailure);
field.warnings = _.map(_.remove(validationFailures, (failure) => {
return failure.propertyName.toLowerCase() === field.name.toLowerCase() && failure.isWarning;
}), mapFailure);
result.push(field);
return result;
}, []);
if (fields.length) {
settings.fields = fields;
}
const validationErrors = _.filter(validationFailures, (failure) => {
return !failure.isWarning;
});
const validationWarnings = _.filter(validationFailures, (failure) => {
return failure.isWarning;
});
return {
settings,
validationErrors,
validationWarnings,
hasPendingChanges: !_.isEmpty(pendingChanges),
hasSettings: !_.isEmpty(settings),
pendingChanges
};
}
export default selectSettings;

View File

@@ -0,0 +1,168 @@
import { cloneDeep, isEmpty } from 'lodash';
import { Error } from 'App/State/AppSectionState';
import Field from 'typings/Field';
import {
Failure,
Pending,
PendingField,
PendingSection,
ValidationError,
ValidationFailure,
ValidationWarning,
} from 'typings/pending';
interface ValidationFailures {
errors: ValidationError[];
warnings: ValidationWarning[];
}
function getValidationFailures(saveError?: Error): ValidationFailures {
if (!saveError || saveError.status !== 400) {
return {
errors: [],
warnings: [],
};
}
return cloneDeep(saveError.responseJSON as ValidationFailure[]).reduce(
(acc: ValidationFailures, failure: ValidationFailure) => {
if (failure.isWarning) {
acc.warnings.push(failure as ValidationWarning);
} else {
acc.errors.push(failure as ValidationError);
}
return acc;
},
{
errors: [],
warnings: [],
}
);
}
function getFailures(failures: ValidationFailure[], key: string) {
const result = [];
for (let i = failures.length - 1; i >= 0; i--) {
if (failures[i].propertyName.toLowerCase() === key.toLowerCase()) {
result.unshift(mapFailure(failures[i]));
failures.splice(i, 1);
}
}
return result;
}
function mapFailure(failure: ValidationFailure): Failure {
return {
errorMessage: failure.errorMessage,
infoLink: failure.infoLink,
detailedDescription: failure.detailedDescription,
// TODO: Remove these renamed properties
message: failure.errorMessage,
link: failure.infoLink,
detailedMessage: failure.detailedDescription,
};
}
interface ModelBaseSetting {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
[id: string]: any;
}
function selectSettings<T extends ModelBaseSetting>(
item: T,
pendingChanges: Partial<ModelBaseSetting>,
saveError?: Error
) {
const { errors, warnings } = getValidationFailures(saveError);
// Merge all settings from the item along with pending
// changes to ensure any settings that were not included
// with the item are included.
const allSettings = Object.assign({}, item, pendingChanges);
const settings = Object.keys(allSettings).reduce(
(acc: PendingSection<T>, key) => {
if (key === 'fields') {
return acc;
}
// Return a flattened value
if (key === 'implementationName') {
acc.implementationName = item[key];
return acc;
}
const setting: Pending<T> = {
value: item[key],
pending: false,
errors: getFailures(errors, key),
warnings: getFailures(warnings, key),
};
if (pendingChanges.hasOwnProperty(key)) {
setting.previousValue = setting.value;
setting.value = pendingChanges[key];
setting.pending = true;
}
// @ts-expect-error - This is a valid key
acc[key] = setting;
return acc;
},
{} as PendingSection<T>
);
if ('fields' in item) {
const fields =
(item.fields as Field[]).reduce((acc: PendingField<T>[], f) => {
const field: PendingField<T> = Object.assign(
{ pending: false, errors: [], warnings: [] },
f
);
if ('fields' in pendingChanges) {
const pendingChangesFields = pendingChanges.fields as Record<
string,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
any
>;
if (pendingChangesFields.hasOwnProperty(field.name)) {
field.previousValue = field.value;
field.value = pendingChangesFields[field.name];
field.pending = true;
}
}
field.errors = getFailures(errors, field.name);
field.warnings = getFailures(warnings, field.name);
acc.push(field);
return acc;
}, []) ?? [];
if (fields.length) {
settings.fields = fields;
}
}
const validationErrors = errors;
const validationWarnings = warnings;
return {
settings,
validationErrors,
validationWarnings,
hasPendingChanges: !isEmpty(pendingChanges),
hasSettings: !isEmpty(settings),
pendingChanges,
};
}
export default selectSettings;

View File

@@ -27,6 +27,12 @@ export default function translate(
key: string,
tokens: Record<string, string | number | boolean> = {}
) {
const { isProduction = true } = window.Radarr;
if (!isProduction && !(key in translations)) {
console.warn(`Missing translation for key: ${key}`);
}
const translation = translations[key] || key;
tokens.appName = 'Radarr';

View File

@@ -153,12 +153,15 @@ class CutoffUnmet extends Component {
<PageToolbar>
<PageToolbarSection>
<PageToolbarButton
label={translate('SearchSelected')}
label={itemsSelected ? translate('SearchSelected') : translate('SearchAll')}
iconName={icons.SEARCH}
isDisabled={!itemsSelected || isSearchingForCutoffUnmetMovies}
onPress={this.onSearchSelectedPress}
isDisabled={isSearchingForCutoffUnmetMovies}
isSpinning={isSearchingForCutoffUnmetMovies}
onPress={itemsSelected ? this.onSearchSelectedPress : this.onSearchAllCutoffUnmetPress}
/>
<PageToolbarSeparator />
<PageToolbarButton
label={isShowingMonitored ? translate('UnmonitorSelected') : translate('MonitorSelected')}
iconName={icons.MONITORED}
@@ -166,18 +169,6 @@ class CutoffUnmet extends Component {
isSpinning={isSaving}
onPress={this.onToggleSelectedPress}
/>
<PageToolbarSeparator />
<PageToolbarButton
label={translate('SearchAll')}
iconName={icons.SEARCH}
isDisabled={!items.length}
isSpinning={isSearchingForCutoffUnmetMovies}
onPress={this.onSearchAllCutoffUnmetPress}
/>
<PageToolbarSeparator />
</PageToolbarSection>
<PageToolbarSection alignContent={align.RIGHT}>

View File

@@ -18,9 +18,10 @@ function createMapStateToProps() {
return createSelector(
(state) => state.wanted.cutoffUnmet,
createCommandExecutingSelector(commandNames.CUTOFF_UNMET_MOVIES_SEARCH),
(cutoffUnmet, isSearchingForCutoffUnmetMovies) => {
createCommandExecutingSelector(commandNames.MOVIE_SEARCH),
(cutoffUnmet, isSearchingForCutoffUnmetMovies, isSearchingForSelectedCutoffUnmetMovies) => {
return {
isSearchingForCutoffUnmetMovies,
isSearchingForCutoffUnmetMovies: isSearchingForCutoffUnmetMovies || isSearchingForSelectedCutoffUnmetMovies,
isSaving: cutoffUnmet.items.filter((m) => m.isSaving).length > 1,
...cutoffUnmet
};

View File

@@ -159,12 +159,15 @@ class Missing extends Component {
<PageToolbar>
<PageToolbarSection>
<PageToolbarButton
label={translate('SearchSelected')}
label={itemsSelected ? translate('SearchSelected') : translate('SearchAll')}
iconName={icons.SEARCH}
isDisabled={!itemsSelected || isSearchingForMissingMovies}
onPress={this.onSearchSelectedPress}
isSpinning={isSearchingForMissingMovies}
isDisabled={isSearchingForMissingMovies}
onPress={itemsSelected ? this.onSearchSelectedPress : this.onSearchAllMissingPress}
/>
<PageToolbarSeparator />
<PageToolbarButton
label={isShowingMonitored ? translate('UnmonitorSelected') : translate('MonitorSelected')}
iconName={icons.MONITORED}
@@ -175,16 +178,6 @@ class Missing extends Component {
<PageToolbarSeparator />
<PageToolbarButton
label={translate('SearchAll')}
iconName={icons.SEARCH}
isDisabled={!items.length}
isSpinning={isSearchingForMissingMovies}
onPress={this.onSearchAllMissingPress}
/>
<PageToolbarSeparator />
<PageToolbarButton
label={translate('ManualImport')}
iconName={icons.INTERACTIVE}

View File

@@ -17,9 +17,10 @@ function createMapStateToProps() {
return createSelector(
(state) => state.wanted.missing,
createCommandExecutingSelector(commandNames.MISSING_MOVIES_SEARCH),
(missing, isSearchingForMissingMovies) => {
createCommandExecutingSelector(commandNames.MOVIE_SEARCH),
(missing, isSearchingForMissingMovies, isSearchingForSelectedMissingMovies) => {
return {
isSearchingForMissingMovies,
isSearchingForMissingMovies: isSearchingForMissingMovies || isSearchingForSelectedMissingMovies,
isSaving: missing.items.filter((m) => m.isSaving).length > 1,
...missing
};

View File

@@ -1,6 +1,6 @@
import { createBrowserHistory } from 'history';
import React from 'react';
import { render } from 'react-dom';
import { createRoot } from 'react-dom/client';
import createAppStore from 'Store/createAppStore';
import App from './App/App';
@@ -9,9 +9,8 @@ import 'Diag/ConsoleApi';
export async function bootstrap() {
const history = createBrowserHistory();
const store = createAppStore(history);
const container = document.getElementById('root');
render(
<App store={store} history={history} />,
document.getElementById('root')
);
const root = createRoot(container!); // createRoot(container!) if you use TypeScript
root.render(<App store={store} history={history} />);
}

View File

@@ -14,6 +14,31 @@ window.Radarr = await response.json();
__webpack_public_path__ = `${window.Radarr.urlBase}/`;
/* eslint-enable no-undef, @typescript-eslint/ban-ts-comment */
const error = console.error;
// Monkey patch console.error to filter out some warnings from React
// TODO: Remove this after the great TypeScript migration
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function logError(...parameters: any[]) {
const filter = parameters.find((parameter) => {
return (
parameter.includes(
'Support for defaultProps will be removed from function components in a future major release'
) ||
parameter.includes(
'findDOMNode is deprecated and will be removed in the next major release'
)
);
});
if (!filter) {
error(...parameters);
}
}
console.error = logError;
const { bootstrap } = await import('./bootstrap');
await bootstrap();

View File

@@ -1,27 +1,13 @@
import ModelBase from 'App/ModelBase';
import Provider from './Provider';
export interface Field {
order: number;
name: string;
label: string;
value: boolean | number | string;
type: string;
advanced: boolean;
privacy: string;
}
export type Protocol = 'torrent' | 'usenet' | 'unknown';
interface DownloadClient extends ModelBase {
interface DownloadClient extends Provider {
enable: boolean;
protocol: string;
protocol: Protocol;
priority: number;
removeCompletedDownloads: boolean;
removeFailedDownloads: boolean;
name: string;
fields: Field[];
implementationName: string;
implementation: string;
configContract: string;
infoLink: string;
tags: number[];
}

View File

@@ -0,0 +1,23 @@
export interface FieldSelectOption<T> {
value: T;
name: string;
order: number;
hint?: string;
parentValue?: T;
isDisabled?: boolean;
additionalProperties?: Record<string, unknown>;
}
interface Field {
order: number;
name: string;
label: string;
value: boolean | number | string | number[];
section: string;
hidden: 'hidden' | 'hiddenIfNotSet' | 'visible';
type: string;
advanced: boolean;
privacy: string;
}
export default Field;

View File

@@ -1,28 +1,12 @@
import ModelBase from 'App/ModelBase';
import Provider from './Provider';
export interface Field {
order: number;
name: string;
label: string;
value: boolean | number | string;
type: string;
advanced: boolean;
privacy: string;
}
interface ImportList extends ModelBase {
interface ImportList extends Provider {
enable: boolean;
enabled: boolean;
enableAuto: boolean;
qualityProfileId: number;
minimumAvailability: string;
rootFolderPath: string;
name: string;
fields: Field[];
implementationName: string;
implementation: string;
configContract: string;
infoLink: string;
tags: number[];
}

View File

@@ -1,27 +1,11 @@
import ModelBase from 'App/ModelBase';
import Provider from './Provider';
export interface Field {
order: number;
name: string;
label: string;
value: boolean | number | string;
type: string;
advanced: boolean;
privacy: string;
}
interface Indexer extends ModelBase {
interface Indexer extends Provider {
enableRss: boolean;
enableAutomaticSearch: boolean;
enableInteractiveSearch: boolean;
protocol: string;
priority: number;
name: string;
fields: Field[];
implementationName: string;
implementation: string;
configContract: string;
infoLink: string;
tags: number[];
}

View File

@@ -0,0 +1,7 @@
import Provider from './Provider';
interface Metadata extends Provider {
enable: boolean;
}
export default Metadata;

View File

@@ -1,23 +1,7 @@
import ModelBase from 'App/ModelBase';
import Provider from './Provider';
export interface Field {
order: number;
name: string;
label: string;
value: boolean | number | string;
type: string;
advanced: boolean;
privacy: string;
}
interface Notification extends ModelBase {
interface Notification extends Provider {
enable: boolean;
name: string;
fields: Field[];
implementationName: string;
implementation: string;
configContract: string;
infoLink: string;
tags: number[];
}

View File

@@ -0,0 +1,20 @@
import ModelBase from 'App/ModelBase';
import { Kind } from 'Helpers/Props/kinds';
import Field from './Field';
export interface ProviderMessage {
message: string;
type: Extract<Kind, 'info' | 'error' | 'warning'>;
}
interface Provider extends ModelBase {
name: string;
fields: Field[];
implementationName: string;
implementation: string;
configContract: string;
infoLink: string;
message: ProviderMessage;
}
export default Provider;

View File

@@ -1,6 +1,11 @@
import Field from './Field';
export interface ValidationFailure {
isWarning: boolean;
propertyName: string;
errorMessage: string;
infoLink?: string;
detailedDescription?: string;
severity: 'error' | 'warning';
}
@@ -12,12 +17,47 @@ export interface ValidationWarning extends ValidationFailure {
isWarning: true;
}
export interface Pending<T> {
value: T;
errors: ValidationError[];
warnings: ValidationWarning[];
export interface Failure {
errorMessage: ValidationFailure['errorMessage'];
infoLink: ValidationFailure['infoLink'];
detailedDescription: ValidationFailure['detailedDescription'];
// TODO: Remove these renamed properties
message: ValidationFailure['errorMessage'];
link: ValidationFailure['infoLink'];
detailedMessage: ValidationFailure['detailedDescription'];
}
export type PendingSection<T> = {
[K in keyof T]: Pending<T[K]>;
export interface Pending<T> {
value: T;
errors: Failure[];
warnings: Failure[];
pending: boolean;
previousValue?: T;
}
export interface PendingField<T>
extends Field,
Omit<Pending<T>, 'previousValue' | 'value'> {
previousValue?: Field['value'];
}
// export type PendingSection<T> = {
// [K in keyof T]: Pending<T[K]>;
// };
type Mapped<T> = {
[Prop in keyof T]: {
value: T[Prop];
errors: Failure[];
warnings: Failure[];
pending?: boolean;
previousValue?: T[Prop];
};
};
export type PendingSection<T> = Mapped<T> & {
implementationName?: string;
fields?: PendingField<T>[];
};

View File

@@ -7,5 +7,6 @@ interface Window {
theme: string;
urlBase: string;
version: string;
isProduction: boolean;
};
}

View File

@@ -33,8 +33,8 @@
"@sentry/browser": "7.119.1",
"@sentry/integrations": "7.119.1",
"@types/node": "20.16.11",
"@types/react": "18.2.79",
"@types/react-dom": "18.2.25",
"@types/react": "18.3.12",
"@types/react-dom": "18.3.1",
"classnames": "2.5.1",
"connected-react-router": "6.9.3",
"copy-to-clipboard": "3.3.3",
@@ -51,7 +51,7 @@
"normalize.css": "8.0.1",
"prop-types": "15.8.1",
"qs": "6.13.0",
"react": "17.0.2",
"react": "18.3.1",
"react-addons-shallow-compare": "15.6.3",
"react-async-script": "1.2.0",
"react-autosuggest": "10.1.0",
@@ -61,7 +61,7 @@
"react-dnd-multi-backend": "6.0.2",
"react-dnd-touch-backend": "14.1.1",
"react-document-title": "2.0.3",
"react-dom": "17.0.2",
"react-dom": "18.3.1",
"react-focus-lock": "2.9.4",
"react-google-recaptcha": "2.1.0",
"react-lazyload": "3.2.0",

View File

@@ -21,9 +21,28 @@ namespace NzbDrone.Common.Test.ExtensionTests
[TestCase("1.2.3.4")]
[TestCase("172.55.0.1")]
[TestCase("192.55.0.1")]
[TestCase("100.64.0.1")]
[TestCase("100.127.255.254")]
public void should_return_false_for_public_ip_address(string ipAddress)
{
IPAddress.Parse(ipAddress).IsLocalAddress().Should().BeFalse();
}
[TestCase("100.64.0.1")]
[TestCase("100.127.255.254")]
[TestCase("100.100.100.100")]
public void should_return_true_for_cgnat_ip_address(string ipAddress)
{
IPAddress.Parse(ipAddress).IsCgnatIpAddress().Should().BeTrue();
}
[TestCase("1.2.3.4")]
[TestCase("192.168.5.1")]
[TestCase("100.63.255.255")]
[TestCase("100.128.0.0")]
public void should_return_false_for_non_cgnat_ip_address(string ipAddress)
{
IPAddress.Parse(ipAddress).IsCgnatIpAddress().Should().BeFalse();
}
}
}

View File

@@ -190,16 +190,23 @@ namespace NzbDrone.Common.Disk
var fi = new FileInfo(path);
// If the file is a symlink, resolve the target path and get the size of the target file.
if (fi.Attributes.HasFlag(FileAttributes.ReparsePoint))
try
{
var targetPath = fi.ResolveLinkTarget(true)?.FullName;
if (targetPath != null)
// If the file is a symlink, resolve the target path and get the size of the target file.
if (fi.Attributes.HasFlag(FileAttributes.ReparsePoint))
{
fi = new FileInfo(targetPath);
var targetPath = fi.ResolveLinkTarget(true)?.FullName;
if (targetPath != null)
{
fi = new FileInfo(targetPath);
}
}
}
catch (IOException ex)
{
Logger.Trace(ex, "Unable to resolve symlink target for {0}", path);
}
return fi.Length;
}

View File

@@ -148,10 +148,5 @@ namespace NzbDrone.Common.Extensions
{
return string.Join(separator, source.Select(predicate));
}
public static HashSet<T> ToHashSet<T>(this IEnumerable<T> source, IEqualityComparer<T> comparer = null)
{
return new HashSet<T>(source, comparer);
}
}
}

View File

@@ -39,18 +39,24 @@ namespace NzbDrone.Common.Extensions
private static bool IsLocalIPv4(byte[] ipv4Bytes)
{
// Link local (no IP assigned by DHCP): 169.254.0.0 to 169.254.255.255 (169.254.0.0/16)
bool IsLinkLocal() => ipv4Bytes[0] == 169 && ipv4Bytes[1] == 254;
var isLinkLocal = ipv4Bytes[0] == 169 && ipv4Bytes[1] == 254;
// Class A private range: 10.0.0.0 10.255.255.255 (10.0.0.0/8)
bool IsClassA() => ipv4Bytes[0] == 10;
var isClassA = ipv4Bytes[0] == 10;
// Class B private range: 172.16.0.0 172.31.255.255 (172.16.0.0/12)
bool IsClassB() => ipv4Bytes[0] == 172 && ipv4Bytes[1] >= 16 && ipv4Bytes[1] <= 31;
var isClassB = ipv4Bytes[0] == 172 && ipv4Bytes[1] >= 16 && ipv4Bytes[1] <= 31;
// Class C private range: 192.168.0.0 192.168.255.255 (192.168.0.0/16)
bool IsClassC() => ipv4Bytes[0] == 192 && ipv4Bytes[1] == 168;
var isClassC = ipv4Bytes[0] == 192 && ipv4Bytes[1] == 168;
return IsLinkLocal() || IsClassA() || IsClassC() || IsClassB();
return isLinkLocal || isClassA || isClassC || isClassB;
}
public static bool IsCgnatIpAddress(this IPAddress ipAddress)
{
var bytes = ipAddress.GetAddressBytes();
return bytes.Length == 4 && bytes[0] == 100 && bytes[1] >= 64 && bytes[1] <= 127;
}
}
}

View File

@@ -6,4 +6,5 @@ public class AuthOptions
public bool? Enabled { get; set; }
public string Method { get; set; }
public string Required { get; set; }
public bool? TrustCgnatIpAddresses { get; set; }
}

View File

@@ -313,7 +313,7 @@ namespace NzbDrone.Common.Processes
processInfo = new ProcessInfo();
processInfo.Id = process.Id;
processInfo.Name = process.ProcessName;
processInfo.StartPath = process.MainModule.FileName;
processInfo.StartPath = process.MainModule?.FileName;
if (process.Id != GetCurrentProcessId() && process.HasExited)
{

View File

@@ -5,14 +5,14 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="DryIoc.dll" Version="5.4.3" />
<PackageReference Include="IPAddressRange" Version="6.0.0" />
<PackageReference Include="IPAddressRange" Version="6.1.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" 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="5.3.3" />
<PackageReference Include="NLog.Layouts.ClefJsonLayout" Version="1.0.0" />
<PackageReference Include="NLog.Extensions.Logging" Version="5.3.12" />
<PackageReference Include="Npgsql" Version="7.0.8" />
<PackageReference Include="NLog" Version="5.3.4" />
<PackageReference Include="NLog.Layouts.ClefJsonLayout" Version="1.0.2" />
<PackageReference Include="NLog.Extensions.Logging" Version="5.3.15" />
<PackageReference Include="Npgsql" Version="7.0.9" />
<PackageReference Include="Sentry" Version="4.0.2" />
<PackageReference Include="NLog.Targets.Syslog" Version="7.0.0" />
<PackageReference Include="SharpZipLib" Version="1.4.2" />

View File

@@ -0,0 +1,43 @@
using System;
using System.Data.SQLite;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.Datastore.Converters;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.Datastore.Converters;
[TestFixture]
public class TimeSpanConverterFixture : CoreTest<TimeSpanConverter>
{
private SQLiteParameter _param;
[SetUp]
public void Setup()
{
_param = new SQLiteParameter();
}
[Test]
public void should_return_string_when_saving_timespan_to_db()
{
var span = TimeSpan.FromMilliseconds(10);
Subject.SetValue(_param, span);
_param.Value.Should().Be(span.ToString());
}
[Test]
public void should_return_timespan_when_getting_string_from_db()
{
var span = TimeSpan.FromMilliseconds(10);
Subject.Parse(span.ToString()).Should().Be(span);
}
[Test]
public void should_return_zero_timespan_for_db_null_value_when_getting_from_db()
{
Subject.Parse(null).Should().Be(TimeSpan.Zero);
}
}

View File

@@ -0,0 +1,38 @@
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.Datastore;
namespace NzbDrone.Core.Test.Datastore;
[TestFixture]
public class DatabaseVersionParserFixture
{
[TestCase("3.44.2", 3, 44, 2)]
public void should_parse_sqlite_database_version(string serverVersion, int majorVersion, int minorVersion, int buildVersion)
{
var version = DatabaseVersionParser.ParseServerVersion(serverVersion);
version.Should().NotBeNull();
version.Major.Should().Be(majorVersion);
version.Minor.Should().Be(minorVersion);
version.Build.Should().Be(buildVersion);
}
[TestCase("14.8 (Debian 14.8-1.pgdg110+1)", 14, 8, null)]
[TestCase("16.3 (Debian 16.3-1.pgdg110+1)", 16, 3, null)]
[TestCase("16.3 - Percona Distribution", 16, 3, null)]
[TestCase("17.0 - Percona Server", 17, 0, null)]
public void should_parse_postgres_database_version(string serverVersion, int majorVersion, int minorVersion, int? buildVersion)
{
var version = DatabaseVersionParser.ParseServerVersion(serverVersion);
version.Should().NotBeNull();
version.Major.Should().Be(majorVersion);
version.Minor.Should().Be(minorVersion);
if (buildVersion.HasValue)
{
version.Build.Should().Be(buildVersion.Value);
}
}
}

View File

@@ -478,6 +478,37 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests
downloadClientInfo.RemovesCompletedDownloads.Should().BeTrue();
}
[TestCase("all", 0)]
[TestCase("days-archive", 15)]
[TestCase("days-delete", 15)]
public void should_set_history_removes_completed_downloads_false_for_separate_properties(string option, int number)
{
_config.Misc.history_retention_option = option;
_config.Misc.history_retention_number = number;
var downloadClientInfo = Subject.GetStatus();
downloadClientInfo.RemovesCompletedDownloads.Should().BeFalse();
}
[TestCase("number-archive", 10)]
[TestCase("number-delete", 10)]
[TestCase("number-archive", 0)]
[TestCase("number-delete", 0)]
[TestCase("days-archive", 3)]
[TestCase("days-delete", 3)]
[TestCase("all-archive", 0)]
[TestCase("all-delete", 0)]
public void should_set_history_removes_completed_downloads_true_for_separate_properties(string option, int number)
{
_config.Misc.history_retention_option = option;
_config.Misc.history_retention_number = number;
var downloadClientInfo = Subject.GetStatus();
downloadClientInfo.RemovesCompletedDownloads.Should().BeTrue();
}
[TestCase(@"Y:\sabnzbd\root", @"completed\downloads", @"vv", @"Y:\sabnzbd\root\completed\downloads", @"Y:\sabnzbd\root\completed\downloads\vv")]
[TestCase(@"Y:\sabnzbd\root", @"completed", @"vv", @"Y:\sabnzbd\root\completed", @"Y:\sabnzbd\root\completed\vv")]
[TestCase(@"/sabnzbd/root", @"completed/downloads", @"vv", @"/sabnzbd/root/completed/downloads", @"/sabnzbd/root/completed/downloads/vv")]

View File

@@ -13,6 +13,14 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.TransmissionTests
[TestFixture]
public class TransmissionFixture : TransmissionFixtureBase<Transmission>
{
[SetUp]
public void Setup_Transmission()
{
Mocker.GetMock<ITransmissionProxy>()
.Setup(v => v.GetClientVersion(It.IsAny<TransmissionSettings>(), It.IsAny<bool>()))
.Returns("4.0.6");
}
[Test]
public void queued_item_should_have_required_properties()
{
@@ -272,7 +280,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.TransmissionTests
public void should_only_check_version_number(string version)
{
Mocker.GetMock<ITransmissionProxy>()
.Setup(s => s.GetClientVersion(It.IsAny<TransmissionSettings>()))
.Setup(s => s.GetClientVersion(It.IsAny<TransmissionSettings>(), true))
.Returns(version);
Subject.Test().IsValid.Should().BeTrue();

View File

@@ -29,7 +29,8 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.TransmissionTests
Host = "127.0.0.1",
Port = 2222,
Username = "admin",
Password = "pass"
Password = "pass",
MovieCategory = ""
};
Subject.Definition = new DownloadClientDefinition();
@@ -152,7 +153,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.TransmissionTests
}
Mocker.GetMock<ITransmissionProxy>()
.Setup(s => s.GetTorrents(It.IsAny<TransmissionSettings>()))
.Setup(s => s.GetTorrents(null, It.IsAny<TransmissionSettings>()))
.Returns(torrents);
}

View File

@@ -66,6 +66,7 @@ namespace NzbDrone.Core.Configuration
string PostgresMainDb { get; }
string PostgresLogDb { get; }
string Theme { get; }
bool TrustCgnatIpAddresses { get; }
}
public class ConfigFileProvider : IConfigFileProvider
@@ -461,5 +462,7 @@ namespace NzbDrone.Core.Configuration
{
SetValue("ApiKey", GenerateApiKey());
}
public bool TrustCgnatIpAddresses => _authOptions.TrustCgnatIpAddresses ?? GetValueBoolean("TrustCgnatIpAddresses", false, persist: false);
}
}

View File

@@ -444,6 +444,12 @@ namespace NzbDrone.Core.Configuration
public string ApplicationUrl => GetValue("ApplicationUrl", string.Empty);
public bool TrustCgnatIpAddresses
{
get { return GetValueBoolean("TrustCgnatIpAddresses", false); }
set { SetValue("TrustCgnatIpAddresses", value); }
}
private string GetValue(string key)
{
return GetValue(key, string.Empty);

View File

@@ -2,18 +2,17 @@ using System;
using System.Data;
using Dapper;
namespace NzbDrone.Core.Datastore.Converters
{
public class DapperTimeSpanConverter : SqlMapper.TypeHandler<TimeSpan>
{
public override void SetValue(IDbDataParameter parameter, TimeSpan value)
{
parameter.Value = value.ToString();
}
namespace NzbDrone.Core.Datastore.Converters;
public override TimeSpan Parse(object value)
{
return TimeSpan.Parse((string)value);
}
public class TimeSpanConverter : SqlMapper.TypeHandler<TimeSpan>
{
public override void SetValue(IDbDataParameter parameter, TimeSpan value)
{
parameter.Value = value.ToString();
}
public override TimeSpan Parse(object value)
{
return value is string str ? TimeSpan.Parse(str) : TimeSpan.Zero;
}
}

View File

@@ -2,7 +2,6 @@
using System.Data;
using System.Data.Common;
using System.Data.SQLite;
using System.Text.RegularExpressions;
using Dapper;
using NLog;
using NzbDrone.Common.Instrumentation;
@@ -52,9 +51,8 @@ namespace NzbDrone.Core.Datastore
{
using var db = _datamapperFactory();
var dbConnection = db as DbConnection;
var version = Regex.Replace(dbConnection.ServerVersion, @"\(.*?\)", "");
return new Version(version);
return DatabaseVersionParser.ParseServerVersion(dbConnection.ServerVersion);
}
}

View File

@@ -0,0 +1,16 @@
using System;
using System.Text.RegularExpressions;
namespace NzbDrone.Core.Datastore;
public static class DatabaseVersionParser
{
private static readonly Regex VersionRegex = new (@"^[^ ]+", RegexOptions.Compiled);
public static Version ParseServerVersion(string serverVersion)
{
var match = VersionRegex.Match(serverVersion);
return match.Success ? new Version(match.Value) : null;
}
}

View File

@@ -188,7 +188,6 @@ namespace NzbDrone.Core.Datastore
SqlMapper.RemoveTypeMap(typeof(DateTime));
SqlMapper.AddTypeHandler(new DapperUtcConverter());
SqlMapper.AddTypeHandler(new DapperTimeSpanConverter());
SqlMapper.AddTypeHandler(new DapperQualityIntConverter());
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<List<QualityProfileQualityItem>>(new QualityIntConverter()));
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<List<ProfileFormatItem>>(new CustomFormatIntConverter()));
@@ -213,6 +212,9 @@ namespace NzbDrone.Core.Datastore
SqlMapper.RemoveTypeMap(typeof(Guid));
SqlMapper.RemoveTypeMap(typeof(Guid?));
SqlMapper.AddTypeHandler(new GuidConverter());
SqlMapper.RemoveTypeMap(typeof(TimeSpan));
SqlMapper.RemoveTypeMap(typeof(TimeSpan?));
SqlMapper.AddTypeHandler(new TimeSpanConverter());
SqlMapper.AddTypeHandler(new CommandConverter());
SqlMapper.AddTypeHandler(new SystemVersionConverter());
}

View File

@@ -20,6 +20,7 @@ namespace NzbDrone.Core.Download.Clients.Deluge
public class Deluge : TorrentClientBase<DelugeSettings>
{
private readonly IDelugeProxy _proxy;
private bool _hasAttemptedReconnecting;
public Deluge(IDelugeProxy proxy,
ITorrentFileInfoReader torrentFileInfoReader,
@@ -128,14 +129,9 @@ namespace NzbDrone.Core.Download.Clients.Deluge
foreach (var torrent in torrents)
{
// Silently ignore torrents with no hash
if (torrent.Hash.IsNullOrWhiteSpace())
{
continue;
}
// Ignore torrents without a name, but track to log a single warning for all invalid torrents.
if (torrent.Name.IsNullOrWhiteSpace())
// Ignore torrents without a hash or name, but track to log a single warning
// for all invalid torrents as well as reconnect to the Daemon.
if (torrent.Hash.IsNullOrWhiteSpace() || torrent.Name.IsNullOrWhiteSpace())
{
ignoredCount++;
continue;
@@ -199,9 +195,20 @@ namespace NzbDrone.Core.Download.Clients.Deluge
items.Add(item);
}
if (ignoredCount > 0)
if (ignoredCount > 0 && _hasAttemptedReconnecting)
{
_logger.Warn("{0} torrent(s) were ignored because they did not have a title. Check Deluge and remove any invalid torrents");
if (_hasAttemptedReconnecting)
{
_logger.Warn("{0} torrent(s) were ignored because they did not have a hash or title. Deluge may have disconnected from it's daemon. If you continue to see this error, check Deluge for invalid torrents.", ignoredCount);
}
else
{
_proxy.ReconnectToDaemon(Settings);
}
}
else
{
_hasAttemptedReconnecting = false;
}
return items;
@@ -322,9 +329,9 @@ namespace NzbDrone.Core.Download.Clients.Deluge
return null;
}
var enabledPlugins = _proxy.GetEnabledPlugins(Settings);
var methods = _proxy.GetMethods(Settings);
if (!enabledPlugins.Contains("Label"))
if (!methods.Any(m => m.StartsWith("label.")))
{
return new NzbDroneValidationFailure("MovieCategory", _localizationService.GetLocalizedString("DownloadClientDelugeValidationLabelPluginInactive"))
{

View File

@@ -18,8 +18,7 @@ namespace NzbDrone.Core.Download.Clients.Deluge
Dictionary<string, object> GetConfig(DelugeSettings settings);
DelugeTorrent[] GetTorrents(DelugeSettings settings);
DelugeTorrent[] GetTorrentsByLabel(string label, DelugeSettings settings);
string[] GetAvailablePlugins(DelugeSettings settings);
string[] GetEnabledPlugins(DelugeSettings settings);
string[] GetMethods(DelugeSettings settings);
string[] GetAvailableLabels(DelugeSettings settings);
DelugeLabel GetLabelOptions(DelugeSettings settings);
void SetTorrentLabel(string hash, string label, DelugeSettings settings);
@@ -30,6 +29,7 @@ namespace NzbDrone.Core.Download.Clients.Deluge
string AddTorrentFromFile(string filename, byte[] fileContent, DelugeSettings settings);
bool RemoveTorrent(string hash, bool removeData, DelugeSettings settings);
void MoveTorrentToTopInQueue(string hash, DelugeSettings settings);
void ReconnectToDaemon(DelugeSettings settings);
}
public class DelugeProxy : IDelugeProxy
@@ -51,25 +51,14 @@ namespace NzbDrone.Core.Download.Clients.Deluge
public string GetVersion(DelugeSettings settings)
{
try
var methods = GetMethods(settings);
if (methods.Contains("daemon.get_version"))
{
var response = ProcessRequest<string>(settings, "daemon.info");
return response;
return ProcessRequest<string>(settings, "daemon.get_version");
}
catch (DownloadClientException ex)
{
if (ex.Message.Contains("Unknown method"))
{
// Deluge v2 beta replaced 'daemon.info' with 'daemon.get_version'.
// It may return or become official, for now we just retry with the get_version api.
var response = ProcessRequest<string>(settings, "daemon.get_version");
return response;
}
throw;
}
return ProcessRequest<string>(settings, "daemon.info");
}
public Dictionary<string, object> GetConfig(DelugeSettings settings)
@@ -101,6 +90,13 @@ namespace NzbDrone.Core.Download.Clients.Deluge
return GetTorrents(response);
}
public string[] GetMethods(DelugeSettings settings)
{
var response = ProcessRequest<string[]>(settings, "system.listMethods");
return response;
}
public string AddTorrentFromMagnet(string magnetLink, DelugeSettings settings)
{
dynamic options = new ExpandoObject();
@@ -159,20 +155,6 @@ namespace NzbDrone.Core.Download.Clients.Deluge
ProcessRequest<object>(settings, "core.queue_top", (object)new string[] { hash });
}
public string[] GetAvailablePlugins(DelugeSettings settings)
{
var response = ProcessRequest<string[]>(settings, "core.get_available_plugins");
return response;
}
public string[] GetEnabledPlugins(DelugeSettings settings)
{
var response = ProcessRequest<string[]>(settings, "core.get_enabled_plugins");
return response;
}
public string[] GetAvailableLabels(DelugeSettings settings)
{
var response = ProcessRequest<string[]>(settings, "label.get_labels");
@@ -223,6 +205,12 @@ namespace NzbDrone.Core.Download.Clients.Deluge
ProcessRequest<object>(settings, "label.set_torrent", hash, label);
}
public void ReconnectToDaemon(DelugeSettings settings)
{
ProcessRequest<string>(settings, "web.disconnect");
ConnectDaemon(BuildRequest(settings));
}
private JsonRpcRequestBuilder BuildRequest(DelugeSettings settings)
{
var url = HttpRequestBuilder.BuildBaseUrl(settings.UseSsl, settings.Host, settings.Port, settings.UrlBase);

View File

@@ -278,20 +278,7 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
status.OutputRootFolders = new List<OsPath> { _remotePathMappingService.RemapRemoteToLocal(Settings.Host, category.FullPath) };
}
if (config.Misc.history_retention.IsNullOrWhiteSpace())
{
status.RemovesCompletedDownloads = false;
}
else if (config.Misc.history_retention.EndsWith("d"))
{
int.TryParse(config.Misc.history_retention.AsSpan(0, config.Misc.history_retention.Length - 1),
out var daysRetention);
status.RemovesCompletedDownloads = daysRetention < 14;
}
else
{
status.RemovesCompletedDownloads = config.Misc.history_retention != "0";
}
status.RemovesCompletedDownloads = RemovesCompletedDownloads(config);
return status;
}
@@ -548,6 +535,44 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
return categories.Contains(category);
}
private bool RemovesCompletedDownloads(SabnzbdConfig config)
{
var retention = config.Misc.history_retention;
var option = config.Misc.history_retention_option;
var number = config.Misc.history_retention_number;
switch (option)
{
case "all":
return false;
case "number-archive":
case "number-delete":
return true;
case "days-archive":
case "days-delete":
return number < 14;
case "all-archive":
case "all-delete":
return true;
}
// TODO: Remove these checks once support for SABnzbd < 4.3 is removed
if (retention.IsNullOrWhiteSpace())
{
return false;
}
if (retention.EndsWith("d"))
{
int.TryParse(config.Misc.history_retention.AsSpan(0, config.Misc.history_retention.Length - 1),
out var daysRetention);
return daysRetention < 14;
}
return retention != "0";
}
private bool ValidatePath(DownloadClientItem downloadClientItem)
{
var downloadItemOutputPath = downloadClientItem.OutputPath;

View File

@@ -32,6 +32,8 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
public bool enable_date_sorting { get; set; }
public bool pre_check { get; set; }
public string history_retention { get; set; }
public string history_retention_option { get; set; }
public int history_retention_number { get; set; }
}
public class SabnzbdCategory

View File

@@ -1,9 +1,11 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using FluentValidation.Results;
using NLog;
using NzbDrone.Common.Disk;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Blocklisting;
using NzbDrone.Core.Configuration;
@@ -15,6 +17,9 @@ namespace NzbDrone.Core.Download.Clients.Transmission
{
public class Transmission : TransmissionBase
{
public override string Name => "Transmission";
public override bool SupportsLabels => HasClientVersion(4, 0);
public Transmission(ITransmissionProxy proxy,
ITorrentFileInfoReader torrentFileInfoReader,
IHttpClient httpClient,
@@ -28,9 +33,48 @@ namespace NzbDrone.Core.Download.Clients.Transmission
{
}
public override void MarkItemAsImported(DownloadClientItem downloadClientItem)
{
if (!SupportsLabels)
{
throw new NotSupportedException($"{Name} does not support marking items as imported");
}
// set post-import category
if (Settings.MovieImportedCategory.IsNotNullOrWhiteSpace() &&
Settings.MovieImportedCategory != Settings.MovieCategory)
{
var hash = downloadClientItem.DownloadId.ToLowerInvariant();
var torrent = _proxy.GetTorrents(new[] { hash }, Settings).FirstOrDefault();
if (torrent == null)
{
_logger.Warn("Could not find torrent with hash \"{0}\" in Transmission.", hash);
return;
}
try
{
var labels = torrent.Labels.ToHashSet(StringComparer.InvariantCultureIgnoreCase);
labels.Add(Settings.MovieImportedCategory);
if (Settings.MovieCategory.IsNotNullOrWhiteSpace())
{
labels.Remove(Settings.MovieCategory);
}
_proxy.SetTorrentLabels(hash, labels, Settings);
}
catch (DownloadClientException ex)
{
_logger.Warn(ex, "Failed to set post-import torrent label \"{0}\" for {1} in Transmission.", Settings.MovieImportedCategory, downloadClientItem.Title);
}
}
}
protected override ValidationFailure ValidateVersion()
{
var versionString = _proxy.GetClientVersion(Settings);
var versionString = _proxy.GetClientVersion(Settings, true);
_logger.Debug("Transmission version information: {0}", versionString);
@@ -44,7 +88,5 @@ namespace NzbDrone.Core.Download.Clients.Transmission
return null;
}
public override string Name => "Transmission";
}
}

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using FluentValidation.Results;
using NLog;
using NzbDrone.Common.Disk;
@@ -18,6 +19,8 @@ namespace NzbDrone.Core.Download.Clients.Transmission
{
public abstract class TransmissionBase : TorrentClientBase<TransmissionSettings>
{
public abstract bool SupportsLabels { get; }
protected readonly ITransmissionProxy _proxy;
public TransmissionBase(ITransmissionProxy proxy,
@@ -37,7 +40,7 @@ namespace NzbDrone.Core.Download.Clients.Transmission
public override IEnumerable<DownloadClientItem> GetItems()
{
var configFunc = new Lazy<TransmissionConfig>(() => _proxy.GetConfig(Settings));
var torrents = _proxy.GetTorrents(Settings);
var torrents = _proxy.GetTorrents(null, Settings);
var items = new List<DownloadClientItem>();
@@ -45,36 +48,45 @@ namespace NzbDrone.Core.Download.Clients.Transmission
{
var outputPath = new OsPath(torrent.DownloadDir);
if (Settings.MovieDirectory.IsNotNullOrWhiteSpace())
if (Settings.MovieCategory.IsNotNullOrWhiteSpace() && SupportsLabels && torrent.Labels is { Count: > 0 })
{
if (!new OsPath(Settings.MovieDirectory).Contains(outputPath))
if (!torrent.Labels.Contains(Settings.MovieCategory, StringComparer.InvariantCultureIgnoreCase))
{
continue;
}
}
else if (Settings.MovieCategory.IsNotNullOrWhiteSpace())
else
{
var directories = outputPath.FullPath.Split('\\', '/');
if (!directories.Contains(Settings.MovieCategory))
if (Settings.MovieDirectory.IsNotNullOrWhiteSpace())
{
continue;
if (!new OsPath(Settings.MovieDirectory).Contains(outputPath))
{
continue;
}
}
else if (Settings.MovieCategory.IsNotNullOrWhiteSpace())
{
var directories = outputPath.FullPath.Split('\\', '/');
if (!directories.Contains(Settings.MovieCategory))
{
continue;
}
}
}
outputPath = _remotePathMappingService.RemapRemoteToLocal(Settings.Host, outputPath);
var item = new DownloadClientItem();
item.DownloadId = torrent.HashString.ToUpper();
item.Category = Settings.MovieCategory;
item.Title = torrent.Name;
item.DownloadClientInfo = DownloadClientItemClientInfo.FromDownloadClient(this, false);
item.OutputPath = GetOutputPath(outputPath, torrent);
item.TotalSize = torrent.TotalSize;
item.RemainingSize = torrent.LeftUntilDone;
item.SeedRatio = torrent.DownloadedEver <= 0 ? 0 :
(double)torrent.UploadedEver / torrent.DownloadedEver;
var item = new DownloadClientItem
{
DownloadId = torrent.HashString.ToUpper(),
Category = Settings.MovieCategory,
Title = torrent.Name,
DownloadClientInfo = DownloadClientItemClientInfo.FromDownloadClient(this, Settings.MovieImportedCategory.IsNotNullOrWhiteSpace() && SupportsLabels),
OutputPath = GetOutputPath(outputPath, torrent),
TotalSize = torrent.TotalSize,
RemainingSize = torrent.LeftUntilDone,
SeedRatio = torrent.DownloadedEver <= 0 ? 0 : (double)torrent.UploadedEver / torrent.DownloadedEver
};
if (torrent.Eta >= 0)
{
@@ -300,7 +312,7 @@ namespace NzbDrone.Core.Download.Clients.Transmission
{
try
{
_proxy.GetTorrents(Settings);
_proxy.GetTorrents(null, Settings);
}
catch (Exception ex)
{
@@ -310,5 +322,15 @@ namespace NzbDrone.Core.Download.Clients.Transmission
return null;
}
protected bool HasClientVersion(int major, int minor)
{
var rawVersion = _proxy.GetClientVersion(Settings);
var versionResult = Regex.Match(rawVersion, @"(?<!\(|(\d|\.)+)(\d|\.)+(?!\)|(\d|\.)+)").Value;
var clientVersion = Version.Parse(versionResult);
return clientVersion >= new Version(major, minor);
}
}
}

View File

@@ -1,5 +1,8 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Collections.ObjectModel;
using System.Linq;
using System.Net;
using Newtonsoft.Json.Linq;
using NLog;
@@ -12,15 +15,16 @@ namespace NzbDrone.Core.Download.Clients.Transmission
{
public interface ITransmissionProxy
{
List<TransmissionTorrent> GetTorrents(TransmissionSettings settings);
IReadOnlyCollection<TransmissionTorrent> GetTorrents(IReadOnlyCollection<string> hashStrings, TransmissionSettings settings);
void AddTorrentFromUrl(string torrentUrl, string downloadDirectory, TransmissionSettings settings);
void AddTorrentFromData(byte[] torrentData, string downloadDirectory, TransmissionSettings settings);
void SetTorrentSeedingConfiguration(string hash, TorrentSeedConfiguration seedConfiguration, TransmissionSettings settings);
TransmissionConfig GetConfig(TransmissionSettings settings);
string GetProtocolVersion(TransmissionSettings settings);
string GetClientVersion(TransmissionSettings settings);
string GetClientVersion(TransmissionSettings settings, bool force = false);
void RemoveTorrent(string hash, bool removeData, TransmissionSettings settings);
void MoveTorrentToTopInQueue(string hashString, TransmissionSettings settings);
void SetTorrentLabels(string hash, IEnumerable<string> labels, TransmissionSettings settings);
}
public class TransmissionProxy : ITransmissionProxy
@@ -28,50 +32,66 @@ namespace NzbDrone.Core.Download.Clients.Transmission
private readonly IHttpClient _httpClient;
private readonly Logger _logger;
private ICached<string> _authSessionIDCache;
private readonly ICached<string> _authSessionIdCache;
private readonly ICached<string> _versionCache;
public TransmissionProxy(ICacheManager cacheManager, IHttpClient httpClient, Logger logger)
{
_httpClient = httpClient;
_logger = logger;
_authSessionIDCache = cacheManager.GetCache<string>(GetType(), "authSessionID");
_authSessionIdCache = cacheManager.GetCache<string>(GetType(), "authSessionID");
_versionCache = cacheManager.GetCache<string>(GetType(), "versions");
}
public List<TransmissionTorrent> GetTorrents(TransmissionSettings settings)
public IReadOnlyCollection<TransmissionTorrent> GetTorrents(IReadOnlyCollection<string> hashStrings, TransmissionSettings settings)
{
var result = GetTorrentStatus(settings);
var result = GetTorrentStatus(hashStrings, settings);
var torrents = ((JArray)result.Arguments["torrents"]).ToObject<List<TransmissionTorrent>>();
var torrents = ((JArray)result.Arguments["torrents"]).ToObject<ReadOnlyCollection<TransmissionTorrent>>();
return torrents;
}
public void AddTorrentFromUrl(string torrentUrl, string downloadDirectory, TransmissionSettings settings)
{
var arguments = new Dictionary<string, object>();
arguments.Add("filename", torrentUrl);
arguments.Add("paused", settings.AddPaused);
var arguments = new Dictionary<string, object>
{
{ "filename", torrentUrl },
{ "paused", settings.AddPaused }
};
if (!downloadDirectory.IsNullOrWhiteSpace())
{
arguments.Add("download-dir", downloadDirectory);
}
if (settings.MovieCategory.IsNotNullOrWhiteSpace())
{
arguments.Add("labels", new List<string> { settings.MovieCategory });
}
ProcessRequest("torrent-add", arguments, settings);
}
public void AddTorrentFromData(byte[] torrentData, string downloadDirectory, TransmissionSettings settings)
{
var arguments = new Dictionary<string, object>();
arguments.Add("metainfo", Convert.ToBase64String(torrentData));
arguments.Add("paused", settings.AddPaused);
var arguments = new Dictionary<string, object>
{
{ "metainfo", Convert.ToBase64String(torrentData) },
{ "paused", settings.AddPaused }
};
if (!downloadDirectory.IsNullOrWhiteSpace())
{
arguments.Add("download-dir", downloadDirectory);
}
if (settings.MovieCategory.IsNotNullOrWhiteSpace())
{
arguments.Add("labels", new List<string> { settings.MovieCategory });
}
ProcessRequest("torrent-add", arguments, settings);
}
@@ -82,8 +102,10 @@ namespace NzbDrone.Core.Download.Clients.Transmission
return;
}
var arguments = new Dictionary<string, object>();
arguments.Add("ids", new[] { hash });
var arguments = new Dictionary<string, object>
{
{ "ids", new List<string> { hash } }
};
if (seedConfiguration.Ratio != null)
{
@@ -97,6 +119,12 @@ namespace NzbDrone.Core.Download.Clients.Transmission
arguments.Add("seedIdleMode", 1);
}
// Avoid extraneous request if no limits are to be set
if (arguments.All(arg => arg.Key == "ids"))
{
return;
}
ProcessRequest("torrent-set", arguments, settings);
}
@@ -107,11 +135,16 @@ namespace NzbDrone.Core.Download.Clients.Transmission
return config.RpcVersion;
}
public string GetClientVersion(TransmissionSettings settings)
public string GetClientVersion(TransmissionSettings settings, bool force = false)
{
var config = GetConfig(settings);
var cacheKey = $"version:{$"{GetBaseUrl(settings)}:{settings.Password}".SHA256Hash()}";
return config.Version;
if (force)
{
_versionCache.Remove(cacheKey);
}
return _versionCache.Get(cacheKey, () => GetConfig(settings).Version, TimeSpan.FromHours(6));
}
public TransmissionConfig GetConfig(TransmissionSettings settings)
@@ -124,21 +157,36 @@ namespace NzbDrone.Core.Download.Clients.Transmission
public void RemoveTorrent(string hashString, bool removeData, TransmissionSettings settings)
{
var arguments = new Dictionary<string, object>();
arguments.Add("ids", new string[] { hashString });
arguments.Add("delete-local-data", removeData);
var arguments = new Dictionary<string, object>
{
{ "ids", new List<string> { hashString } },
{ "delete-local-data", removeData }
};
ProcessRequest("torrent-remove", arguments, settings);
}
public void MoveTorrentToTopInQueue(string hashString, TransmissionSettings settings)
{
var arguments = new Dictionary<string, object>();
arguments.Add("ids", new string[] { hashString });
var arguments = new Dictionary<string, object>
{
{ "ids", new List<string> { hashString } }
};
ProcessRequest("queue-move-top", arguments, settings);
}
public void SetTorrentLabels(string hash, IEnumerable<string> labels, TransmissionSettings settings)
{
var arguments = new Dictionary<string, object>
{
{ "ids", new List<string> { hash } },
{ "labels", labels.ToImmutableHashSet() }
};
ProcessRequest("torrent-set", arguments, settings);
}
private TransmissionResponse GetSessionVariables(TransmissionSettings settings)
{
// Retrieve transmission information such as the default download directory, bandwidth throttling and seed ratio.
@@ -151,14 +199,9 @@ namespace NzbDrone.Core.Download.Clients.Transmission
return ProcessRequest("session-stats", null, settings);
}
private TransmissionResponse GetTorrentStatus(TransmissionSettings settings)
{
return GetTorrentStatus(null, settings);
}
private TransmissionResponse GetTorrentStatus(IEnumerable<string> hashStrings, TransmissionSettings settings)
{
var fields = new string[]
var fields = new List<string>
{
"id",
"hashString", // Unique torrent ID. Use this instead of the client id?
@@ -179,11 +222,14 @@ namespace NzbDrone.Core.Download.Clients.Transmission
"seedIdleLimit",
"seedIdleMode",
"fileCount",
"file-count"
"file-count",
"labels"
};
var arguments = new Dictionary<string, object>();
arguments.Add("fields", fields);
var arguments = new Dictionary<string, object>
{
{ "fields", fields }
};
if (hashStrings != null)
{
@@ -195,9 +241,14 @@ namespace NzbDrone.Core.Download.Clients.Transmission
return result;
}
private string GetBaseUrl(TransmissionSettings settings)
{
return HttpRequestBuilder.BuildBaseUrl(settings.UseSsl, settings.Host, settings.Port, settings.UrlBase);
}
private HttpRequestBuilder BuildRequest(TransmissionSettings settings)
{
var requestBuilder = new HttpRequestBuilder(settings.UseSsl, settings.Host, settings.Port, settings.UrlBase)
var requestBuilder = new HttpRequestBuilder(GetBaseUrl(settings))
.Resource("rpc")
.Accept(HttpAccept.Json);
@@ -212,11 +263,11 @@ namespace NzbDrone.Core.Download.Clients.Transmission
{
var authKey = string.Format("{0}:{1}", requestBuilder.BaseUrl, settings.Password);
var sessionId = _authSessionIDCache.Find(authKey);
var sessionId = _authSessionIdCache.Find(authKey);
if (sessionId == null || reauthenticate)
{
_authSessionIDCache.Remove(authKey);
_authSessionIdCache.Remove(authKey);
var authLoginRequest = BuildRequest(settings).Build();
authLoginRequest.SuppressHttpError = true;
@@ -244,7 +295,7 @@ namespace NzbDrone.Core.Download.Clients.Transmission
_logger.Debug("Transmission authentication succeeded.");
_authSessionIDCache.Set(authKey, sessionId);
_authSessionIdCache.Set(authKey, sessionId);
}
requestBuilder.SetHeader("X-Transmission-Session-Id", sessionId);

View File

@@ -1,3 +1,4 @@
using System.Text.Json.Serialization;
using System.Text.RegularExpressions;
using FluentValidation;
using NzbDrone.Common.Extensions;
@@ -27,11 +28,25 @@ namespace NzbDrone.Core.Download.Clients.Transmission
{
private static readonly TransmissionSettingsValidator Validator = new ();
// This constructor is used when creating a new instance, such as the user adding a new Transmission client.
public TransmissionSettings()
{
Host = "localhost";
Port = 9091;
UrlBase = "/transmission/";
MovieCategory = "radarr";
}
// TODO: Remove this in v6
// This constructor is used when deserializing from JSON, it will set the
// category to the deserialized value, defaulting to null.
[JsonConstructor]
public TransmissionSettings(string movieCategory = null)
{
Host = "localhost";
Port = 9091;
UrlBase = "/transmission/";
MovieCategory = movieCategory;
}
[FieldDefinition(0, Label = "Host", Type = FieldType.Textbox)]
@@ -59,16 +74,19 @@ namespace NzbDrone.Core.Download.Clients.Transmission
[FieldDefinition(6, Label = "Category", Type = FieldType.Textbox, HelpText = "DownloadClientSettingsCategorySubFolderHelpText")]
public string MovieCategory { get; set; }
[FieldDefinition(7, Label = "Directory", Type = FieldType.Textbox, Advanced = true, HelpText = "DownloadClientTransmissionSettingsDirectoryHelpText")]
[FieldDefinition(7, Label = "PostImportCategory", Type = FieldType.Textbox, Advanced = true, HelpText = "DownloadClientSettingsPostImportCategoryHelpText")]
public string MovieImportedCategory { get; set; }
[FieldDefinition(8, Label = "Directory", Type = FieldType.Textbox, Advanced = true, HelpText = "DownloadClientTransmissionSettingsDirectoryHelpText")]
public string MovieDirectory { get; set; }
[FieldDefinition(8, Label = "DownloadClientSettingsRecentPriority", Type = FieldType.Select, SelectOptions = typeof(TransmissionPriority), HelpText = "DownloadClientSettingsRecentPriorityMovieHelpText")]
[FieldDefinition(9, Label = "DownloadClientSettingsRecentPriority", Type = FieldType.Select, SelectOptions = typeof(TransmissionPriority), HelpText = "DownloadClientSettingsRecentPriorityMovieHelpText")]
public int RecentMoviePriority { get; set; }
[FieldDefinition(9, Label = "DownloadClientSettingsOlderPriority", Type = FieldType.Select, SelectOptions = typeof(TransmissionPriority), HelpText = "DownloadClientSettingsOlderPriorityMovieHelpText")]
[FieldDefinition(10, Label = "DownloadClientSettingsOlderPriority", Type = FieldType.Select, SelectOptions = typeof(TransmissionPriority), HelpText = "DownloadClientSettingsOlderPriorityMovieHelpText")]
public int OlderMoviePriority { get; set; }
[FieldDefinition(10, Label = "DownloadClientSettingsAddPaused", Type = FieldType.Checkbox)]
[FieldDefinition(11, Label = "DownloadClientSettingsAddPaused", Type = FieldType.Checkbox)]
public bool AddPaused { get; set; }
public override NzbDroneValidationResult Validate()

View File

@@ -1,3 +1,5 @@
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
namespace NzbDrone.Core.Download.Clients.Transmission
@@ -11,6 +13,7 @@ namespace NzbDrone.Core.Download.Clients.Transmission
public long TotalSize { get; set; }
public long LeftUntilDone { get; set; }
public bool IsFinished { get; set; }
public IReadOnlyCollection<string> Labels { get; set; } = Array.Empty<string>();
public long Eta { get; set; }
public TransmissionTorrentStatus Status { get; set; }
public long SecondsDownloading { get; set; }

View File

@@ -15,6 +15,9 @@ namespace NzbDrone.Core.Download.Clients.Vuze
{
private const int MINIMUM_SUPPORTED_PROTOCOL_VERSION = 14;
public override string Name => "Vuze";
public override bool SupportsLabels => false;
public Vuze(ITransmissionProxy proxy,
ITorrentFileInfoReader torrentFileInfoReader,
IHttpClient httpClient,
@@ -67,7 +70,5 @@ namespace NzbDrone.Core.Download.Clients.Vuze
return null;
}
public override string Name => "Vuze";
}
}

View File

@@ -311,11 +311,11 @@ namespace NzbDrone.Core.Download.Pending
ect = ect.AddMinutes(_configService.RssSyncInterval);
}
var timeleft = ect.Subtract(DateTime.UtcNow);
var timeLeft = ect.Subtract(DateTime.UtcNow);
if (timeleft.TotalSeconds < 0)
if (timeLeft.TotalSeconds < 0)
{
timeleft = TimeSpan.Zero;
timeLeft = TimeSpan.Zero;
}
string downloadClientName = null;
@@ -336,9 +336,9 @@ namespace NzbDrone.Core.Download.Pending
Languages = pendingRelease.RemoteMovie.Languages,
Title = pendingRelease.Title,
Size = pendingRelease.RemoteMovie.Release.Size,
Sizeleft = pendingRelease.RemoteMovie.Release.Size,
SizeLeft = pendingRelease.RemoteMovie.Release.Size,
RemoteMovie = pendingRelease.RemoteMovie,
Timeleft = timeleft,
TimeLeft = timeLeft,
EstimatedCompletionTime = ect,
Added = pendingRelease.Added,
Status = Enum.TryParse(pendingRelease.Reason.ToString(), out QueueStatus outValue) ? outValue : QueueStatus.Unknown,

View File

@@ -1,13 +1,12 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Extras.Metadata.Files;
using NzbDrone.Core.MediaCover;
using NzbDrone.Core.Localization;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Movies;
using NzbDrone.Core.ThingiProvider;
namespace NzbDrone.Core.Extras.Metadata.Consumers.Kometa
{
@@ -15,13 +14,15 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Kometa
{
private static readonly Regex MovieImagesRegex = new (@"^(?:poster|background)\.(?:png|jpe?g)$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private readonly IMapCoversToLocal _mediaCoverService;
private readonly ILocalizationService _localizationService;
public override string Name => "Kometa";
public KometaMetadata(IMapCoversToLocal mediaCoverService)
public override ProviderMessage Message => new (_localizationService.GetLocalizedString("MetadataKometaDeprecated"), ProviderMessageType.Warning);
public KometaMetadata(ILocalizationService localizationService)
{
_mediaCoverService = mediaCoverService;
_localizationService = localizationService;
}
public override MetadataFile FindMetadataFile(Movie movie, string path)
@@ -56,31 +57,7 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Kometa
public override List<ImageFileResult> MovieImages(Movie movie)
{
if (!Settings.MovieImages)
{
return new List<ImageFileResult>();
}
return ProcessMovieImages(movie).ToList();
}
private IEnumerable<ImageFileResult> ProcessMovieImages(Movie movie)
{
foreach (var image in movie.MovieMetadata.Value.Images.Where(i => i.CoverType is MediaCoverTypes.Poster or MediaCoverTypes.Fanart))
{
var source = _mediaCoverService.GetCoverPath(movie.Id, image.CoverType);
var filename = image.CoverType switch
{
MediaCoverTypes.Poster => "poster",
MediaCoverTypes.Fanart => "background",
_ => throw new ArgumentOutOfRangeException($"{image.CoverType} is not supported")
};
var destination = filename + Path.GetExtension(source);
yield return new ImageFileResult(destination, source);
}
return new List<ImageFileResult>();
}
}
}

View File

@@ -15,11 +15,11 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Kometa
public KometaMetadataSettings()
{
MovieImages = true;
Deprecated = true;
}
[FieldDefinition(0, Label = "MetadataSettingsMovieImages", Type = FieldType.Checkbox, Section = MetadataSectionType.Image, HelpText = "poster.jpg, background.jpg")]
public bool MovieImages { get; set; }
[FieldDefinition(0, Label = "MetadataKometaDeprecatedSetting", Type = FieldType.Checkbox, Section = MetadataSectionType.Image, Hidden = HiddenType.Hidden)]
public bool Deprecated { get; set; }
public NzbDroneValidationResult Validate()
{

View File

@@ -0,0 +1,34 @@
using System.Linq;
using NzbDrone.Core.Extras.Metadata;
using NzbDrone.Core.Extras.Metadata.Consumers.Kometa;
using NzbDrone.Core.Localization;
using NzbDrone.Core.ThingiProvider.Events;
namespace NzbDrone.Core.HealthCheck.Checks
{
[CheckOn(typeof(ProviderUpdatedEvent<IMetadata>))]
public class MetadataCheck : HealthCheckBase
{
private readonly IMetadataFactory _metadataFactory;
public MetadataCheck(IMetadataFactory metadataFactory, ILocalizationService localizationService)
: base(localizationService)
{
_metadataFactory = metadataFactory;
}
public override HealthCheck Check()
{
var enabled = _metadataFactory.Enabled();
if (enabled.Any(m => m.Definition.Implementation == nameof(KometaMetadata)))
{
return new HealthCheck(GetType(),
HealthCheckResult.Warning,
$"{_localizationService.GetLocalizedString("MetadataKometaDeprecated")}");
}
return new HealthCheck(GetType());
}
}
}

View File

@@ -16,7 +16,7 @@
"InCinemas": "I Biografen",
"ImportTipsMessage": "Nogle tips for at sikre importeringen går glat:",
"ImportMechanismHealthCheckMessage": "Aktiver Fuldendt Download Håndtering",
"ImportHeader": "Importer film du allerede har",
"ImportHeader": "Importer et allerede organiseret filmbibliotek for at føje film til {appName}",
"ImportExistingMovies": "Importer Eksisterende Film",
"Imported": "Importeret",
"Import": "Importer",
@@ -49,7 +49,7 @@
"DownloadClientStatusCheckAllClientMessage": "Alle download klienter er utilgængelige på grund af fejl",
"DownloadClientsSettingsSummary": "Download klienter, download håndtering og remote path mappings",
"DownloadClients": "Download Klienter",
"DownloadClientCheckUnableToCommunicateMessage": "Ude af stand til at kommunikere med {downloadClientName}.",
"DownloadClientCheckUnableToCommunicateMessage": "Ude af stand til at kommunikere med {downloadClientName}. »{errorMessage}«",
"DownloadClientCheckNoneAvailableMessage": "Ingen download klient tilgængelig",
"DownloadClient": "Download Klient",
"DiskSpace": "Disk Plads",
@@ -63,13 +63,13 @@
"Dates": "Datoer",
"Date": "Dato",
"CustomFormatsSettingsSummary": "Bruger Tilpassede Formater og Indstillinger",
"CustomFormatScore": "Bruger Tilpasset Format score",
"CustomFormatScore": "Brugerdefineret formats resultat",
"CustomFormats": "Bruger Tilpasset Formater",
"CustomFilters": "Bruger Tilpassede Filtere",
"Crew": "Besætning",
"ConnectSettingsSummary": "Notifikationer, forbindelser til media servere/afspillere og custom scripts",
"ConnectSettingsSummary": "Notifikationer, forbindelser til medieservere/-afspillere og brugerdefinerede scripts",
"Connections": "Forbindelser",
"ConnectionLost": "Forbindelse Mistet",
"ConnectionLost": "Forbindelse mistet",
"Connect": "Tilslut",
"Component": "Komponent",
"CompletedDownloadHandling": "Færdig Download Håndtering",
@@ -130,9 +130,9 @@
"EnableInteractiveSearch": "Aktivér interaktiv søgning",
"IgnoreDeletedMovies": "Fjern overvågning af slettede film",
"Images": "Billeder",
"IndexerPriorityHelpText": "Indekseringsprioritet fra 1 (højest) til 50 (lavest). Standard: 25.",
"IndexerPriorityHelpText": "Indeksatorprioritet fra 1 (højest) til 50 (lavest). Standard: 25. Anvendes til at vælge mellem udgivelser med ellers lige mange point. {appName} vil stadig bruge alle aktiverede indeksatorer til RSS-synkronisering og søgning",
"LogLevelTraceHelpTextWarning": "Sporlogning bør kun aktiveres midlertidigt",
"MappedNetworkDrivesWindowsService": "Tilsluttede netværksdrev er ikke tilgængelige, når programmet kører som en Windows-tjeneste. Se FAQ'en for mere information",
"MappedNetworkDrivesWindowsService": "Tilsluttede netværksdrev er ikke tilgængelige, når programmet kører som en Windows-tjeneste. Se FAQ'en ({url}) for mere information.",
"MassMovieSearch": "Massefilmsøgning",
"MIA": "MIA",
"MonitoredOnly": "Kun overvåget",
@@ -170,15 +170,15 @@
"AddRemotePathMapping": "Tilføj kortlægning af fjernsti",
"AddDelayProfile": "Tilføj forsinkelsesprofil",
"AddDownloadClient": "Tilføj downloadklient",
"AddedToDownloadQueue": "Tilføjet til download kø",
"AddedToDownloadQueue": "Føjet til downloadkø",
"AddQualityProfile": "Tilføj kvalitetsprofil",
"AddRootFolder": "Tilføj rodmappe",
"AllFiles": "Alle filer",
"AllMoviesInPathHaveBeenImported": "Alle film i {0} er blevet importeret",
"AllMoviesInPathHaveBeenImported": "Alle film i {path} er blevet importeret",
"AllResultsHiddenFilter": "Alle resultater skjules af det anvendte filter",
"Always": "Altid",
"AnalyticsEnabledHelpText": "Send anonym brugs- og fejlinformation til {appName}s servere. Dette inkluderer information i din browser, hvilke {appName} WebUI-sider du bruger, fejlrapportering samt OS og runtime-version. Vi bruger disse oplysninger til at prioritere funktioner og fejlrettelser.",
"AppDataDirectory": "AppData-bibliotek",
"AppDataDirectory": "AppData-mappe",
"AuthBasic": "Grundlæggende (pop op-browser)",
"Authentication": "Godkendelse",
"ApplyTags": "Anvend tags",
@@ -206,7 +206,7 @@
"ChownGroupHelpText": "Gruppens navn eller gid. Brug gid til eksterne filsystemer.",
"Add": "Tilføj",
"AddCustomFormat": "Tilføj tilpasset format",
"AddToDownloadQueue": "Tilføjet til downloadkø",
"AddToDownloadQueue": "Føj til downloadkø",
"AfterManualRefresh": "Efter manuel opdatering",
"ApiKey": "API-nøgle",
"AptUpdater": "Brug apt til at installere opdateringen",
@@ -219,10 +219,10 @@
"CancelProcessing": "Annuller behandling",
"CantFindMovie": "Hvorfor kan jeg ikke finde min film?",
"CertificateValidation": "Validering af certifikat",
"CertificateValidationHelpText": "Skift, hvor streng HTTPS-certificering er",
"CertificateValidationHelpText": "Skift, hvor streng HTTPS-certificering er. Ændr kun dette hvis du forstå risiciene.",
"CertValidationNoLocal": "Deaktiveret for lokale adresser",
"ChangeFileDate": "Skift fildato",
"CustomFormatUnknownCondition": "Ukendt tilstand for tilpasset format '{0}'",
"CustomFormatUnknownCondition": "Ukendt betingelse for tilpasset format »{implementation}«",
"DatabaseMigration": "DB Migration",
"ChownGroupHelpTextWarning": "Dette fungerer kun, hvis den bruger, der kører {appName}, er ejeren af filen. Det er bedre at sikre, at downloadklienten bruger den samme gruppe som {appName}.",
"ExcludeMovie": "Ekskluder film",
@@ -232,8 +232,8 @@
"CertificationCountryHelpText": "Vælg land for filmcertificeringer",
"ImportErrors": "Importfejl",
"ImportExtraFiles": "Importer ekstra filer",
"ImportExtraFilesMovieHelpText": "Importer matchende ekstra filer (undertekster, nfo osv.) Efter import af en filmfil",
"ImportFailed": "Import mislykkedes: {0}",
"ImportExtraFilesMovieHelpText": "Importer matchende ekstra filer (undertekster, nfo osv.) efter import af en filmfil",
"ImportFailed": "Import mislykkedes: »{sourceTitle}«",
"ImportLibrary": "Biblioteksimport",
"ImportListStatusCheckAllClientMessage": "Alle lister er utilgængelige på grund af fejl",
"DeleteBackup": "Slet sikkerhedskopi",
@@ -310,7 +310,7 @@
"RecyclingBinCleanup": "Oprydning af papirkurven",
"Refresh": "Opdater",
"RefreshMovie": "Opdater film",
"RegularExpressionsCanBeTested": "Regulære udtryk kan testes ",
"RegularExpressionsCanBeTested": "Regulære udtryk kan testes [her](http://regexstorm.net/tester).",
"RejectionCount": "Afvisningstal",
"RelativePath": "Relativ sti",
"Released": "Udgivet",
@@ -358,7 +358,7 @@
"MediaManagementSettings": "Indstillinger for mediestyring",
"MediaManagementSettingsSummary": "Navngivning og filhåndteringsindstillinger",
"Hostname": "Værtsnavn",
"MinutesSixty": "60 minutter: {0}",
"MinutesSixty": "60 minutter: {sixty}",
"Missing": "Mangler",
"Month": "Måned",
"MoreDetails": "Flere detaljer",
@@ -399,7 +399,7 @@
"AllowHardcodedSubs": "Tillad hardcodede subs",
"PhysicalRelease": "Fysisk frigivelse",
"Port": "Havn",
"ProfilesSettingsSummary": "Kvalitets-, sprog- og forsinkelsesprofiler",
"ProfilesSettingsSummary": "Kvalitets-, sprog-, forsinkelses-, og udgivelsesprofiler",
"Progress": "Fremskridt",
"Proper": "Passende",
"Protocol": "Protokol",
@@ -408,7 +408,7 @@
"QualityProfile": "Kvalitetsprofil",
"QualityProfiles": "Kvalitetsprofiler",
"PreferredSize": "Foretrukken størrelse",
"QuickImport": "Hurtig import",
"QuickImport": "Flyt automatisk",
"SupportedListsMovie": "{appName} understøtter alle RSS-filmlister såvel som nedenstående.",
"AlreadyInYourLibrary": "Allerede i dit bibliotek",
"RecyclingBinCleanupHelpTextWarning": "Filer i papirkurven, der er ældre end det valgte antal dage, renses automatisk",
@@ -416,7 +416,7 @@
"AddingTag": "Tilføjer tag",
"AgeWhenGrabbed": "Alder (når grebet)",
"RefreshAndScan": "Opdater & Scan",
"RequiredHelpText": "Denne {0} betingelse skal matche for at det tilpassede format kan anvendes. Ellers er en enkelt {1} match tilstrækkelig.",
"RequiredHelpText": "Denne {implementationName}-betingelse skal matche for at det tilpassede format kan anvendes. Ellers er et enkelt {implementationName}-match tilstrækkeligt.",
"AddNewRestriction": "Tilføj ny begrænsning",
"RestartRequiredHelpTextWarning": "Kræver genstart for at træde i kraft",
"RestoreBackup": "Gendan sikkerhedskopi",
@@ -425,7 +425,7 @@
"Level": "Niveau",
"FileBrowserPlaceholderText": "Start med at skrive, eller vælg en sti nedenfor",
"StartupDirectory": "Startmappe",
"MoviesSelectedInterp": "{0} Film (er) valgt",
"MoviesSelectedInterp": "{count} film er valgt",
"MovieTitle": "Filmtitel",
"EditDelayProfile": "Rediger forsinkelsesprofil",
"Name": "Navn",
@@ -435,7 +435,7 @@
"MustContain": "Skal indeholde",
"MustNotContain": "Må ikke indeholde",
"NamingSettings": "Navngivningsindstillinger",
"NegateHelpText": "Hvis dette er markeret, gælder det tilpassede format ikke, hvis denne {0} betingelse stemmer overens.",
"NegateHelpText": "Hvis dette er markeret, gælder det tilpassede format ikke, hvis denne {implementationName}-betingelse stemmer overens.",
"SystemTimeHealthCheckMessage": "Systemtiden er slukket mere end 1 dag. Planlagte opgaver kører muligvis ikke korrekt, før tiden er rettet",
"Posters": "Plakater",
"PosterSize": "Plakatstørrelse",
@@ -466,53 +466,53 @@
"CloneIndexer": "Klonindekser",
"CloneProfile": "Klonprofil",
"CloseCurrentModal": "Luk Nuværende Modal",
"ColonReplacement": "Udskiftning af tyktarm",
"ColonReplacement": "Udskiftning af kolon",
"ColonReplacementFormatHelpText": "Skift hvordan {appName} håndterer kolonudskiftning",
"Conditions": "Betingelser",
"Connection": "Forbindelser",
"ConnectSettings": "Forbind indstillinger",
"ConsideredAvailable": "Anses for tilgængelig",
"CopyToClipboard": "Kopier til udklipsholder",
"CopyUsingHardlinksMovieHelpText": "Brug hardlinks, når du prøver at kopiere filer fra torrents, der stadig udsås",
"CopyUsingHardlinksMovieHelpText": "Hardlinks tillader {appName} at importere torrent-filer der stadig seedes, uden at optage ekstra diskplads eller at kopiere the fulde fil. Hardlinks virker kun, hvis kildefilen og destinationsfilen er på samme volumen",
"CopyUsingHardlinksHelpTextWarning": "Lejlighedsvis kan fillåse forhindre omdøbning af filer, der bliver seedet. Du kan midlertidigt deaktivere såning og bruge {appName}s omdøbningsfunktion som et arbejde rundt.",
"CouldNotFindResults": "Kunne ikke finde nogen resultater for '{0}'",
"CouldNotFindResults": "Kunne ikke finde nogen resultater for »{term}«",
"CreateEmptyMovieFolders": "Opret tomme filmmapper",
"CreateGroup": "Opret gruppe",
"CurrentlyInstalled": "Aktuelt installeret",
"CustomFormat": "Bruger Tilpasset Formater",
"CustomFormatHelpText": "{appName} scorer hver udgivelse ved hjælp af summen af scores for matchende tilpassede formater. Hvis en ny udgivelse ville forbedre scoren i samme eller bedre kvalitet, vil {appName} gribe den.",
"CustomFormatsSettings": "Indstillinger for brugerdefinerede formater",
"CustomFormatUnknownConditionOption": "Ukendt valgmulighed '{0}' for betingelse '{1}'",
"CustomFormatUnknownConditionOption": "Ukendt valgmulighed »{key}« for betingelsen »{implementation}«",
"Cutoff": "Skære af",
"UpgradeUntilCustomFormatScoreMovieHelpText": "Når denne score til brugerdefineret format er nået, downloader {appName} ikke længere film",
"UpgradeUntilMovieHelpText": "Når denne kvalitet er nået, downloader {appName} ikke længere film",
"CutoffUnmet": "Afskåret ude",
"CutoffUnmet": "Grænse ikke opnået",
"Days": "Dage",
"Debug": "Fejlfinde",
"EditCustomFormat": "Rediger brugerdefineret format",
"DefaultCase": "Standard sag",
"DefaultDelayProfileMovie": "Dette er standardprofilen. Det gælder for alle film, der ikke har en eksplicit profil.",
"DelayProfile": "Udskyd Profiler",
"DeleteBackupMessageText": "Er du sikker på, at du vil slette sikkerhedskopien '{0}'?",
"DeleteBackupMessageText": "Er du sikker på, at du vil slette sikkerhedskopien »{name}«?",
"DeleteDelayProfile": "Slet forsinkelsesprofil",
"DeleteDownloadClientMessageText": "Er du sikker på, at du vil slette downloadklienten '{0}'?",
"DeleteDownloadClientMessageText": "Er du sikker på, at du vil fjerne downloadklienten »{name}«?",
"DeleteEmptyFolders": "Slet tomme mapper",
"DeleteEmptyFoldersHelpText": "Slet tomme filmmapper under diskscanning, og når filmfiler slettes",
"DeleteMovieFilesHelpText": "Slet filmfilerne og filmmappen",
"DeleteMovieFiles": "Slet {0} filmfiler",
"DeleteMovieFiles": "Fjern {movieFileCount} filmfiler",
"DeleteHeader": "Slet - {0}",
"DeleteImportListExclusion": "Slet udelukkelse af importliste",
"DeleteIndexer": "Slet Indexer",
"DeleteIndexerMessageText": "Er du sikker på, at du vil slette indeksøren '{0}'?",
"DeleteIndexerMessageText": "Er du sikker på, at du vil slette indeksøren »{name}«?",
"DeleteNotification": "Slet underretning",
"DeleteNotificationMessageText": "Er du sikker på, at du vil slette underretningen '{0}'?",
"DeleteNotificationMessageText": "Er du sikker på, at du vil slette notifikationen »{name}«?",
"DeleteQualityProfile": "Slet kvalitetsprofil",
"DeleteRestriction": "Slet begrænsning",
"DeleteRestrictionHelpText": "Er du sikker på, at du vil slette denne begrænsning?",
"DeleteSelectedMovie": "Slet valgte film",
"DeleteSelectedMovieFiles": "Slet valgte filmfiler",
"DeleteTagMessageText": "Er du sikker på, at du vil slette tagget '{0}'?",
"DeleteMovieFolderConfirmation": "Filmmappen '{0}' og alt dens indhold slettes.",
"DeleteTagMessageText": "Er du sikker på, at du vil slette etiketten »{label}«?",
"DeleteMovieFolderConfirmation": "Filmmappen »{path}« og alt dens indhold slettes.",
"DestinationPath": "Destinationssti",
"DestinationRelativePath": "Destinationsrelateret sti",
"DetailedProgressBar": "Detaljeret statuslinje",
@@ -527,7 +527,7 @@
"Downloading": "Downloader",
"DownloadPropersAndRepacks": "Propers og Repacks",
"DownloadPropersAndRepacksHelpTextCustomFormat": "Brug 'Foretrækkes ikke' til at sortere efter brugerdefineret format score over Propers / Repacks",
"DownloadWarning": "Downloadadvarsel: {0}",
"DownloadWarning": "Downloadadvarsel: »{warningMessage}«",
"Edition": "Udgave",
"EditImportListExclusion": "Rediger ekskludering af lister",
"EditMovieFile": "Rediger filmfil",
@@ -580,12 +580,12 @@
"Global": "Global",
"GoToInterp": "Gå til {0}",
"Grab": "Tag fat",
"GrabRelease": "Grab Release",
"GrabRelease": "Hent udgivelse",
"GrabReleaseMessageText": "{appName} var ikke i stand til at bestemme, hvilken film denne udgivelse var til. {appName} kan muligvis ikke automatisk importere denne udgivelse. Vil du hente '{0}'?",
"Group": "Gruppe",
"HiddenClickToShow": "Skjult, klik for at vise",
"Host": "Vært",
"ICalLink": "iCal Link",
"ICalLink": "iCal-link",
"IconForCutoffUnmet": "Ikon til Cutoff Unmet",
"IgnoredAddresses": "Ignorerede adresser",
"IgnoredHelpText": "Frigivelsen afvises, hvis den indeholder et eller flere af vilkårene (store og små bogstaver)",
@@ -614,7 +614,7 @@
"Links": "Links",
"ImportLists": "Lister",
"ImportListSettings": "Listeindstillinger",
"ImportListsSettingsSummary": "Importlister, listeekskluderinger",
"ImportListsSettingsSummary": "Importér fra en anden {appName}-instans eller fra Trakt-lister og håndter listeekskluderinger",
"ListSyncLevelHelpText": "Film i biblioteket fjernes eller overvåges, hvis de ikke er på din liste",
"LogFiles": "Logfiler",
"Logging": "Logning",
@@ -640,8 +640,8 @@
"MinimumAvailability": "Minimum tilgængelighed",
"MinimumCustomFormatScore": "Minimum tilpasset format score",
"MinimumFreeSpaceHelpText": "Forhindre import, hvis den efterlader mindre end denne mængde diskplads tilgængelig",
"MinutesHundredTwenty": "120 minutter: {0}",
"MinutesNinety": "90 minutter: {0}",
"MinutesHundredTwenty": "120 minutter: {hundredTwenty}",
"MinutesNinety": "90 minutter: {ninety}",
"Mode": "Mode",
"Monitor": "Overvåge",
"Monitored": "Overvåget",
@@ -666,7 +666,7 @@
"NoTagsHaveBeenAddedYet": "Der er ikke tilføjet nogen tags endnu",
"Options": "Muligheder",
"Organize": "Organisere",
"OrganizeConfirm": "Er du sikker på, at du vil organisere alle filer i de {0} valgte film?",
"OrganizeConfirm": "Er du sikker på, at du vil organisere alle filer i de {count} valgte film?",
"OrganizeSelectedMovies": "Organiser valgte film",
"Original": "Original",
"OutputPath": "Outputsti",
@@ -678,7 +678,7 @@
"Preferred": "Foretrukket",
"PreviewRename": "Vis eksempel Omdøb",
"PreviewRenameHelpText": "Tip: For at få vist et omdøbning ... vælg 'Annuller', klik derefter på en filmtitel og brug",
"PrioritySettings": "Prioritet: {0}",
"PrioritySettings": "Prioritet: {priority}",
"ProxyCheckBadRequestMessage": "Kunne ikke teste proxy. Statuskode: {statusCode}",
"ProxyCheckFailedToTestMessage": "Kunne ikke teste proxy: {url}",
"ProxyCheckResolveIpMessage": "Mislykkedes at løse IP-adressen til den konfigurerede proxyhost {proxyHostName}",
@@ -702,7 +702,7 @@
"ReleaseStatus": "Frigør status",
"ReleaseTitle": "Udgiv titel",
"Reload": "Genindlæs",
"RemotePathMappings": "Remote Path Mappings",
"RemotePathMappings": "Sammenkædning med fjernsti",
"Remove": "Fjerne",
"RemovedFromTaskQueue": "Fjernet fra opgavekøen",
"RemovedMovieCheckMultipleMessage": "Film {movies} blev fjernet fra TMDb",
@@ -756,7 +756,7 @@
"SearchForMovie": "Søg efter film",
"SearchMissing": "Søgning mangler",
"SearchOnAdd": "Søg på Tilføj",
"ListSearchOnAddMovieHelpText": "Søg efter film på denne liste, når du føjes til {appName}",
"ListSearchOnAddMovieHelpText": "Søg efter film på denne liste, når de føjes til biblioteket",
"SearchSelected": "Søgning valgt",
"Seconds": "Sekunder",
"Security": "Sikkerhed",
@@ -805,7 +805,7 @@
"SkipFreeSpaceCheck": "Spring fri pladscheck over",
"Small": "Lille",
"Socks4": "Strømper 4",
"Socks5": "Socks5 (Support TOR)",
"Socks5": "Socks5 (Understøtter TOR)",
"SomeResultsHiddenFilter": "Nogle resultater skjules af det anvendte filter",
"SorryThatMovieCannotBeFound": "Beklager, den film kan ikke findes.",
"Sort": "Sortere",
@@ -830,23 +830,23 @@
"Table": "Tabel",
"TableOptions": "Tabelindstillinger",
"TableOptionsColumnsMessage": "Vælg hvilke kolonner der er synlige og hvilken rækkefølge de vises i",
"TagDetails": "Tagdetaljer - {0}",
"TagDetails": "Etiketdetaljer - {label}",
"TagIsNotUsedAndCanBeDeleted": "Tag bruges ikke og kan slettes",
"Tags": "Mærker",
"ICalTagsMoviesHelpText": "Gælder film med mindst et matchende tag",
"Tasks": "Opgaver",
"Test": "Afprøv",
"TestAll": "Afprøv alle",
"TheLogLevelDefault": "Logniveauet er som standard 'Info' og kan ændres i",
"ThisCannotBeCancelled": "Dette kan ikke annulleres en gang startet uden genstart af {appName}.",
"TheLogLevelDefault": "Logniveauet er som standard 'Info' og kan ændres under [Generelle indstillinger](/settings/general)",
"ThisCannotBeCancelled": "Dette kan ikke annulleres når først det er startet uden at du deaktiverer alle dine indeksører.",
"Title": "Titel",
"Titles": "Titler",
"TMDBId": "TMDb Id",
"TMDBId": "TMDb-ID",
"TmdbIdExcludeHelpText": "TMDb-id for filmen, der skal ekskluderes",
"Today": "I dag",
"TorrentDelay": "Torrentforsinkelse",
"TorrentDelayHelpText": "Forsink i minutter, før du tager fat i en torrent",
"TorrentDelayTime": "Torrentforsinkelse: {0}",
"TorrentDelayTime": "Torrentforsinkelse: {torrentDelay}",
"Torrents": "Torrenter",
"TorrentsDisabled": "Torrenter deaktiveret",
"Trace": "Spor",
@@ -904,7 +904,7 @@
"UpdateAutomaticallyHelpText": "Download og installer opdateringer automatisk. Du kan stadig installere fra System: Updates",
"UpdateCheckStartupTranslocationMessage": "Kan ikke installere opdatering, fordi startmappen '{startupFolder}' er i en App Translocation-mappe.",
"UpdateCheckUINotWritableMessage": "Kan ikke installere opdatering, fordi '{userName}' ikke kan skrive til mappen for brugergrænseflade '{uiFolder}'.",
"UpdateMechanismHelpText": "Brug {appName}s indbyggede opdatering eller et script",
"UpdateMechanismHelpText": "Brug {appName}s indbyggede opdateringsfunktion eller et script",
"UpdateScriptPathHelpText": "Sti til et brugerdefineret script, der tager en udpakket opdateringspakke og håndterer resten af opdateringsprocessen",
"UpdateSelected": "Opdatering valgt",
"UpgradeUntilCustomFormatScore": "Opgrader indtil brugerdefineret format score",
@@ -917,7 +917,7 @@
"Usenet": "Usenet",
"UsenetDelay": "Usenet-forsinkelse",
"UsenetDelayHelpText": "Forsink i minutter, før du tager fat i en frigivelse fra Usenet",
"UsenetDelayTime": "Usenetforsinkelse: {0}",
"UsenetDelayTime": "Usenet-forsinkelse: {usenetDelay}",
"UsenetDisabled": "Usenet deaktiveret",
"UseProxy": "Brug proxy",
"Version": "Version",
@@ -930,7 +930,7 @@
"WhitelistedHardcodedSubsHelpText": "Undertekstmærker, der er angivet her, betragtes ikke som hardkodede",
"WhitelistedSubtitleTags": "Hvidlistede undertekstmærker",
"Wiki": "Wiki",
"WouldYouLikeToRestoreBackup": "Vil du gendanne sikkerhedskopien {0}?",
"WouldYouLikeToRestoreBackup": "Vil du gendanne sikkerhedskopien »{name}«?",
"Year": "År",
"YesCancel": "Ja, Annuller",
"YesMoveFiles": "Ja, flyt filerne",
@@ -966,7 +966,7 @@
"Rating": "Bedømmelser",
"RssSyncIntervalHelpText": "Interval på få minutter. Sæt til nul for at deaktivere (dette stopper al automatisk frigivelse)",
"MonitorMovies": "Overvåg film",
"NoCollections": "Ingen film fundet. For at komme i gang vil du tilføje en ny film eller importere nogle eksisterende.",
"NoCollections": "Ingen samlinger fundet. For at komme i gang skal du tilføje en ny film eller importere nogle eksisterende",
"AllCollectionsHiddenDueToFilter": "Alle film er gemt på grund af aktivt filter.",
"Collections": "Samling",
"File": "Filer",
@@ -979,10 +979,10 @@
"DeleteDelayProfileMessageText": "Er du sikker på, at du vil slette denne forsinkelsesprofil?",
"DeleteImportListExclusionMessageText": "Er du sikker på, at du vil slette denne undtagelse fra importlisten?",
"DeleteFormatMessageText": "Er du sikker på, at du vil slette formattag {0}?",
"DeleteConditionMessageText": "Er du sikker på, at du vil slette listen '{0}'?",
"DeleteConditionMessageText": "Er du sikker på, at du vil slette betingelsen »{name}«?",
"DeleteCustomFormatMessageText": "Er du sikker på, at du vil slette indeksøren '{name}'?",
"RemoveSelectedItemQueueMessageText": "Er du sikker på, at du vil fjerne {0} element {1} fra køen?",
"RemoveSelectedItemsQueueMessageText": "Er du sikker på, at du vil fjerne {0} element {1} fra køen?",
"RemoveSelectedItemQueueMessageText": "Er du sikker på, at du vil fjerne 1 element fra køen?",
"RemoveSelectedItemsQueueMessageText": "Er du sikker på, at du vil fjerne {selectedCount} elementer fra køen?",
"ResetAPIKeyMessageText": "Er du sikker på, at du vil nulstille din API-nøgle?",
"ApplyTagsHelpTextAdd": "Tilføj: Føj tags til den eksisterende liste over tags",
"ApplyTagsHelpTextHowToApplyIndexers": "Sådan anvendes tags på de valgte film",
@@ -1006,16 +1006,16 @@
"DownloadClientsLoadError": "Kunne ikke indlæse downloadklienter",
"NotificationStatusSingleClientHealthCheckMessage": "Lister utilgængelige på grund af fejl: {notificationNames}",
"NotificationsSimplepushSettingsEvent": "Begivenhed",
"RemoveQueueItemConfirmation": "Er du sikker på, at du vil fjerne {0} element {1} fra køen?",
"RemoveQueueItemConfirmation": "Er du sikker på, at du vil fjerne »{sourceTitle}« fra køen?",
"AutoRedownloadFailed": "Download fejlede",
"BypassDelayIfAboveCustomFormatScoreMinimumScore": "Minimum tilpasset format score",
"EditImportListImplementation": "Tilføj importliste - {implementationName}",
"FormatAgeMinute": "Protokoller",
"InteractiveImportLoadError": "Kunne ikke indlæse manuelle importvarer",
"ConditionUsingRegularExpressions": "Denne betingelse stemmer overens med brug af regulære udtryk. Bemærk, at tegnene {0} har en særlig betydning og skal undslippe med en {1}",
"ConnectionLostReconnect": "Radarr vil prøve at tilslutte automatisk, eller du kan klikke genindlæs forneden.",
"ConditionUsingRegularExpressions": "Denne betingelse stemmer overens ved brug af regulære udtryk. Bemærk, at tegnene »\\^$.|?*+()[{« har en særlig betydning og skal indledes med indkodningstegnet »\\«",
"ConnectionLostReconnect": "{appName} vil prøve at tilslutte automatisk. Ellers du kan klikke genindlæs forneden.",
"DeleteSpecification": "Slet underretning",
"DeleteSpecificationHelpText": "Er du sikker på, at du vil slette kvalitetsprofilen {0}",
"DeleteSpecificationHelpText": "Er du sikker på, at du vil slette specifikationen »{name}«?",
"DeletedReasonUpgrade": "Filen blev slettet for at importere en opgradering",
"Directory": "Mappe",
"Lists": "Lister",
@@ -1023,15 +1023,15 @@
"PreferredProtocol": "Foretrukken protokol",
"RestartLater": "Jeg genstarter senere",
"SelectDropdown": "'Vælg...",
"AddIndexerImplementation": "Tilføj betingelse - {implementationName}",
"AddIndexerImplementation": "Tilføj indeksør - {implementationName}",
"EditDownloadClientImplementation": "Tilføj downloadklient - {implementationName}",
"DelayingDownloadUntil": "Forsinker download indtil {0} kl. {1}",
"AddAutoTag": "Tilføj automatisk Tag",
"DeleteAutoTagHelpText": "Er du sikker på, at du vil slette kvalitetsprofilen {0}",
"DelayingDownloadUntil": "Forsinker download indtil {date} kl. {time}",
"AddAutoTag": "Tilføj automatisk etiket",
"DeleteAutoTagHelpText": "Er du sikker på, at du vil slette den automatiske etiket »{name}«?",
"BlocklistLoadError": "Kunne ikke indlæse sortliste",
"NotificationStatusAllClientHealthCheckMessage": "Alle lister er utilgængelige på grund af fejl",
"InteractiveSearchModalHeader": "Interaktiv søgning",
"RetryingDownloadOn": "Prøver igen at downloade {0} kl. {1}",
"RetryingDownloadOn": "Prøver igen at downloade d. {date} kl. {time}",
"OrganizeLoadError": "Fejl ved indlæsning af forhåndsvisning",
"QueueLoadError": "Kunne kunne ikke indlæses",
"EditIndexerImplementation": "Tilføj betingelse - {implementationName}",
@@ -1050,39 +1050,39 @@
"CustomFilter": "Bruger Tilpassede Filtere",
"ReleaseProfiles": "udgivelsesprofil",
"EditConditionImplementation": "Tilføj forbindelse - {implementationName}",
"GrabId": "Grab ID",
"GrabId": "Hent ID",
"FormatAgeMinutes": "Protokoller",
"Label": "Etiket",
"RemoveSelectedBlocklistMessageText": "Er du sikker på, at du vil fjerne de valgte emner fra sortlisten?",
"AddAutoTagError": "Kan ikke tilføje en ny liste, prøv igen.",
"AddAutoTagError": "Kan ikke tilføje en ny automatisk etiket. Prøv igen.",
"Release": "udgivelse",
"DelayProfileMovieTagsHelpText": "Gælder film med mindst et matchende tag",
"OrganizeNothingToRename": "Succes! Mit arbejde er udført, ingen filer at omdøbe.",
"MovieFileDeleted": "Slet på filmfil",
"ApplyTagsHelpTextHowToApplyMovies": "Sådan anvendes tags på de valgte film",
"DownloadClientSettingsRecentPriority": "Kundens prioritet",
"DeleteQualityProfileMessageText": "Er du sikker på, at du vil slette kvalitetsprofilen {0}",
"DeleteQualityProfileMessageText": "Er du sikker på, at du vil slette kvalitetsprofilen »{name}«?",
"DeleteSelectedMovieFilesHelpText": "Er du sikker på, at du vil slette de valgte filmfiler?",
"MovieIsNotMonitored": "Film overvåges",
"DeleteReleaseProfile": "Slet forsinkelsesprofil",
"DeleteReleaseProfileMessageText": "Er du sikker på, at du vil slette kvalitetsprofilen {0}",
"DeleteReleaseProfile": "Slet udgivelsesprofil",
"DeleteReleaseProfileMessageText": "Er du sikker på, at du vil slette udgivelsesprofilen »{name}«?",
"DeletedReasonMovieMissingFromDisk": "{appName} kunne ikke finde filen på disken, så den blev fjernet",
"ReleaseProfilesLoadError": "Kunne ikke indlæse forsinkelsesprofiler",
"SearchOnAddCollectionHelpText": "Søg efter film denne liste, når du føjes til {appName}",
"SearchOnAddCollectionHelpText": "Søg efter film i denne samling, når de føjes til biblioteket",
"EditConnectionImplementation": "Tilføj forbindelse - {implementationName}",
"MovieSearchResultsLoadError": "Kunne ikke indlæse resultater for denne filmsøgning. Prøv igen senere",
"FormatAgeHour": "Timer",
"IndexerSettingsMultiLanguageRelease": "Multi-sprog",
"DeleteImportListMessageText": "Er du sikker på, at du vil slette kvalitetsprofilen {0}",
"DeleteImportListMessageText": "Er du sikker på, at du vil slette listen »{name}«?",
"ReleaseGroups": "Slip gruppe",
"IMDbId": "TMDb Id",
"AddDelayProfileError": "Kan ikke tilføje en ny forsinkelsesprofil. Prøv venligst igen.",
"AddDelayProfileError": "Kan ikke tilføje en ny forsinkelsesprofil. Prøv igen.",
"ShowUnknownMovieItemsHelpText": "Vis emner uden en film i køen. Dette kan omfatte fjernede film eller andet i {appName}s kategori",
"MovieFileDeletedTooltip": "Slet på filmfil",
"DeleteMovieFolders": "Slet filmmappe",
"DeleteMovieFoldersHelpText": "Slet filmmappen og dens indhold",
"DeleteSelectedMovies": "Slet valgte filmfiler",
"AutoTaggingNegateHelpText": "Hvis dette er markeret, gælder det tilpassede format ikke, hvis denne {0} betingelse stemmer overens.",
"AutoTaggingNegateHelpText": "Hvis dette er markeret, vil reglen for automatisk etiket ikke blive anvendt, hvis denne {implementationName}-betingelse stemmer overens.",
"DeleteSelectedImportListExclusionsMessageText": "Er du sikker på, at du vil slette denne undtagelse fra importlisten?",
"DeleteSelectedCustomFormats": "Slet brugerdefineret format",
"ReleaseDate": "Slip datoer",
@@ -1095,5 +1095,6 @@
"Delay": "Forsinkelse",
"EditReleaseProfile": "Rediger forsinkelsesprofil",
"DownloadClientUnavailable": "Downloadklienten er ikke tilgængelig",
"AddReleaseProfile": "Rediger forsinkelsesprofil"
"AddReleaseProfile": "Rediger forsinkelsesprofil",
"ApiKeyValidationHealthCheckMessage": "Opdater din API-nøgle til at være på mindste {length} karakterer. Dette kan gøres i indstillingerne eller i konfigurationsfilen"
}

File diff suppressed because it is too large Load Diff

View File

@@ -919,6 +919,8 @@
"Menu": "Menu",
"Message": "Message",
"Metadata": "Metadata",
"MetadataKometaDeprecated": "Kometa files will no longer be created, support will be removed completely in v6",
"MetadataKometaDeprecatedSetting": "Deprecated",
"MetadataLoadError": "Unable to load Metadata",
"MetadataSettings": "Metadata Settings",
"MetadataSettingsMovieImages": "Movie Images",

View File

@@ -1863,5 +1863,8 @@
"Warning": "Aviso",
"FavoriteFolderRemove": "Eliminar carpeta favorita",
"DownloadClientUnavailable": "Cliente de descarga no disponible",
"NotificationsSettingsWebhookHeaders": "Cabeceras"
"NotificationsSettingsWebhookHeaders": "Cabeceras",
"MetadataKometaDeprecated": "Los archivos de Kometa no seguirán siendo creados, el soporte se eliminará completamente en la v6",
"MetadataKometaDeprecatedSetting": "Obsoleto",
"Fallback": "Retirada"
}

View File

@@ -865,7 +865,7 @@
"BrowserReloadRequired": "חובה לטעון דפדפן",
"UiSettings": "הגדרות ממשק המשתמש",
"UiSettingsSummary": "אפשרויות לקויות לוח שנה, תאריך וצבע",
"AddConditionError": "לא ניתן להוסיף תנאי חדש, נסה שוב.",
"AddConditionError": "לא ניתן להוסיף תנאי חדש, נסה שנית.",
"AddCustomFormatError": "לא ניתן להוסיף פורמט מותאם אישית חדש, נסה שוב.",
"AddDownloadClientError": "לא ניתן להוסיף לקוח הורדות חדש, נסה שוב.",
"AddIndexerError": "לא ניתן להוסיף אינדקס חדש, נסה שוב.",
@@ -1048,7 +1048,7 @@
"ReleaseProfilesLoadError": "לא ניתן לטעון פרופילי עיכוב",
"RemoveQueueItemConfirmation": "האם אתה בטוח שברצונך להסיר את {0} פריט {1} מהתור?",
"Yes": "כן",
"AddAutoTag": "הוסף טגית אוטומטית",
"AddAutoTag": "הוסף תג אוטומטית",
"DeleteQualityProfileMessageText": "האם אתה בטוח שברצונך למחוק את פרופיל האיכות {0}",
"ApplyTagsHelpTextHowToApplyDownloadClients": "כיצד להחיל תגים על הסרטים שנבחרו",
"DisabledForLocalAddresses": "מושבת לכתובות מקומיות",
@@ -1084,7 +1084,7 @@
"DeleteReleaseProfile": "מחק פרופיל עיכוב",
"InteractiveSearchModalHeader": "חיפוש אינטראקטיבי",
"InteractiveImportLoadError": "לא ניתן לטעון פריטי ייבוא ידניים",
"AddAutoTagError": "לא ניתן להוסיף רשימה חדשה, אנא נסה שוב.",
"AddAutoTagError": "לא ניתן להוסיף תג חדש, נסה שנית.",
"No": "לא",
"MustNotContainHelpText": "המהדורה תידחה אם היא מכילה אחד או יותר מהתנאים (חסר רישיות)",
"ApplyTagsHelpTextHowToApplyMovies": "כיצד להחיל תגים על הסרטים שנבחרו",
@@ -1118,5 +1118,6 @@
"Clone": "סגור",
"EditReleaseProfile": "ערוך פרופיל עיכוב",
"AddReleaseProfile": "ערוך פרופיל עיכוב",
"DownloadClientUnavailable": "לקוח ההורדות אינו זמין"
"DownloadClientUnavailable": "לקוח ההורדות אינו זמין",
"AddCondition": "הוסף תנאי"
}

View File

@@ -7,7 +7,7 @@
"Activity": "Aktivitas",
"Add": "Tambah",
"AddCustomFormat": "Tambahkan Format Khusus",
"AddDownloadClient": "Tambahkan Klien Pengunduhan",
"AddDownloadClient": "Tambahkan Download Client",
"AddIndexer": "Tambahkan Pengindeks",
"Age": "Usia",
"All": "Semua",
@@ -174,5 +174,7 @@
"ToggleMonitoredToUnmonitored": "Dimonitor, klik untuk berhenti monitor",
"ToggleUnmonitoredToMonitored": "Tidak dimonitor, klik untuk monitor",
"Clone": "Tutup",
"EnableSsl": "Aktifkan RSS"
"EnableSsl": "Aktifkan RSS",
"AddCustomFormatError": "Tidak dapat menambahkan format khusus baru, coba lagi.",
"AddDelayProfile": "Tambah Delay Profile"
}

View File

@@ -85,7 +85,7 @@
"UpdateScriptPathHelpText": "추출 된 업데이트 패키지를 사용하고 나머지 업데이트 프로세스를 처리하는 사용자 지정 스크립트에 대한 경로",
"UpgradeUntil": "품질까지 업그레이드",
"Usenet": "유즈넷",
"VisitTheWikiForMoreDetails": "자세한 내용은 위키를 방문하십시오. ",
"VisitTheWikiForMoreDetails": "자세한 내용은 위키를 방문하세요: ",
"YouCanAlsoSearch": "영화의 TMDb ID 또는 IMDb ID를 사용하여 검색 할 수도 있습니다. 예 : `tmdb : 71663`",
"OverviewOptions": "개요 옵션",
"PackageVersion": "패키지 버전",
@@ -404,7 +404,7 @@
"ImportListStatusCheckAllClientMessage": "실패로 인해 모든 목록을 사용할 수 없습니다.",
"ImportListStatusCheckSingleClientMessage": "실패로 인해 사용할 수없는 목록 : {importListNames}",
"TotalSpace": "총 공간",
"UpdateCheckStartupNotWritableMessage": "'{1}'사용자가 '{0}'시작 폴더에 쓸 수 없기 때문에 업데이트를 설치할 수 없습니다.",
"UpdateCheckStartupNotWritableMessage": "'{userName}' 사용자가 시작 폴더 '{startupFolder}'에 쓸 수 없기 때문에 업데이트를 설치할 수 없습니다.",
"UpgradesAllowedHelpText": "비활성화 된 자질은 업그레이드되지 않습니다.",
"Backup": "백업",
"Username": "사용자 이름",
@@ -413,9 +413,9 @@
"Disabled": "비활성화됨",
"Peers": "동료",
"UiSettingsLoadError": "UI 설정을 불러올 수 없습니다.",
"Unavailable": "없는",
"Unavailable": "사용 불가능",
"VideoCodec": "비디오 코덱",
"View": "표시 변경",
"View": "화면",
"Importing": "가져오기",
"NoLeaveIt": "아니, 놔둬",
"NoLimitForAnyRuntime": "런타임 제한 없음",
@@ -853,10 +853,10 @@
"TMDBId": "TMDb ID",
"TmdbIdExcludeHelpText": "제외 할 영화의 TMDb ID",
"Today": "오늘",
"TorrentDelay": "급류 지연",
"TorrentDelayHelpText": "급류를 잡기 전에 대기하는 데 몇 분이 걸립니다.",
"TorrentDelayTime": "급류 지연 : {0}",
"Torrents": "급류",
"TorrentDelay": "토렌트 지연",
"TorrentDelayHelpText": "토렌트를 잡기 전에 대기까지 소요되는 지연 (분)",
"TorrentDelayTime": "토렌트 지연: {0torrentDelay}",
"Torrents": "토렌트",
"TorrentsDisabled": "토렌트 비활성화",
"Trace": "자취",
"Trailer": "트레일러",
@@ -869,10 +869,10 @@
"BrowserReloadRequired": "브라우저 새로 고침 필요",
"UiSettings": "UI 설정",
"UiSettingsSummary": "달력, 날짜 및 색상 장애 옵션",
"AddConditionError": "새 조건을 추가 할 수 없습니다. 다시 시도하십시오.",
"AddConditionError": "새 조건을 추가 할 수 없습니다. 다시 시도해주세요.",
"AddCustomFormatError": "새 사용자 정의 형식을 추가 할 수 없습니다. 다시 시도하십시오.",
"AddDownloadClientError": "새 다운로드 클라이언트를 추가 할 수 없습니다. 다시 시도하십시오.",
"AddIndexerError": "새 인덱서를 추가 할 수 없습니다. 다시 시도하십시오.",
"AddIndexerError": "새 인덱서를 추가 할 수 없습니다. 다시 시도해주세요.",
"AddImportListExclusionError": "새 목록 제외를 추가 할 수 없습니다. 다시 시도하십시오.",
"AddListError": "새 목록을 추가 할 수 없습니다. 다시 시도하십시오.",
"AddQualityProfileError": "새 품질 프로필을 추가 할 수 없습니다. 다시 시도하십시오.",
@@ -908,12 +908,12 @@
"UpdateAll": "모두 업데이트",
"UpdateAutomaticallyHelpText": "업데이트를 자동으로 다운로드하고 설치합니다. 시스템 : 업데이트에서 계속 설치할 수 있습니다.",
"UpdateCheckStartupTranslocationMessage": "시작 폴더 '{startupFolder}'이 (가) App Translocation 폴더에 있으므로 업데이트를 설치할 수 없습니다.",
"UpdateMechanismHelpText": "{appName}의 내장 업데이트 프로그램 또는 스크립트 사용",
"UpdateMechanismHelpText": "{appName}의 내장 업데이트 도구 또는 스크립트 사용",
"UpdateSelected": "선택한 항목 업데이트",
"UpgradeUntilCustomFormatScore": "사용자 지정 형식 점수까지 업그레이드",
"UpgradeUntilThisQualityIsMetOrExceeded": "이 품질이 충족되거나 초과 될 때까지 업그레이드",
"Uppercase": "대문자",
"UrlBase": "URL베이스",
"UrlBase": "URL 기반",
"UrlBaseHelpText": "역방향 프록시 지원의 경우 기본값은 비어 있습니다.",
"UseHardlinksInsteadOfCopy": "복사 대신 하드 링크 사용",
"UsenetDelay": "유즈넷 지연",
@@ -928,12 +928,12 @@
"Week": "주",
"Weeks": "주",
"WhitelistedHardcodedSubsHelpText": "여기에 설정된 자막 태그는 하드 코딩 된 것으로 간주되지 않습니다.",
"WhitelistedSubtitleTags": "허용 된 자막 태그",
"WhitelistedSubtitleTags": "허용된 자막 태그",
"Wiki": "위키",
"WouldYouLikeToRestoreBackup": "{0} 백업을 복원 하시겠습니까?",
"WouldYouLikeToRestoreBackup": "'{name}' 백업을 복원하시겠습니까?",
"Year": "년",
"YesCancel": "예, 취소합니다",
"YesMoveFiles": "예, 파일 이동",
"YesMoveFiles": "예, 파일 이동합니다",
"Yesterday": "어제",
"MaintenanceRelease": "유지 관리 출시 : 버그 수정 및 기타 개선. 자세한 내용은 Github 커밋 내역을 참조하십시오.",
"MissingMonitoredAndConsideredAvailable": "누락 (모니터링 됨)",
@@ -1024,7 +1024,7 @@
"ReleaseProfilesLoadError": "지연 프로필을로드 할 수 없습니다.",
"QualitiesLoadError": "품질을로드 할 수 없습니다.",
"TablePageSize": "페이지 크기",
"AddAutoTagError": "새 목록을 추가 할 수 없습니다. 다시 시도하십시오.",
"AddAutoTagError": "새 자동 태그을 추가 할 수 없습니다. 다시 시도해주세요.",
"AddRootFolderError": "루트 폴더를로드 할 수 없습니다.",
"ApplyTagsHelpTextHowToApplyImportLists": "선택한 동영상에 태그를 적용하는 방법",
"MustNotContainHelpText": "하나 이상의 용어가 포함 된 경우 릴리스가 거부됩니다 (대소 문자 구분 안 함).",
@@ -1045,7 +1045,7 @@
"MovieSearchResultsLoadError": "이 영화 검색 결과를 불러올 수 없습니다. 나중에 다시 시도",
"NotificationsSimplepushSettingsEvent": "이벤트",
"SearchOnAddCollectionHelpText": "{appName}에 추가되면이 목록에서 영화 검색",
"AddDelayProfileError": "새 품질 프로필을 추가 할 수 없습니다. 다시 시도하십시오.",
"AddDelayProfileError": "새 지연 프로필을 추가할 수 없습니다. 다시 시도해주세요.",
"DeleteMovieFolders": "영화 폴더 삭제",
"DeleteMovieFoldersHelpText": "동영상 폴더 및 내용 삭제",
"DeleteSelectedMovies": "선택한 동영상 파일 삭제",
@@ -1073,5 +1073,23 @@
"EditConditionImplementation": "연결 추가 - {implementationName}",
"EditConnectionImplementation": "애플리케이션 추가 - {implementationName}",
"EditDownloadClientImplementation": "다운로드 클라이언트 추가 - {implementationName}",
"AddConditionImplementation": "연결 추가 - {implementationName}"
"AddConditionImplementation": "조건 추가 - {implementationName}",
"UnknownEventTooltip": "알 수 없는 이벤트",
"Waiting": "기다리는 중",
"TraktRating": "Trakt 평점",
"AddAutoTag": "자동 태그 추가",
"AddCondition": "조건 추가",
"TotalMovies": "총 영화",
"UseSsl": "SSL 사용",
"UsenetBlackhole": "유즈넷 블랙홀",
"TypeOfList": "{typeOfList} 목록",
"AddImportList": "가져오기 목록 추가",
"AddImportListImplementation": "가져오기 목록 추가 - {implementationName}",
"TorrentBlackholeTorrentFolder": "토렌트 폴더",
"UsenetBlackholeNzbFolder": "Nzb 폴더",
"UpdateAvailableHealthCheckMessage": "새 업데이트 사용 가능: {version}",
"Warning": "경고",
"VideoDynamicRange": "동영상 다이나믹 레인지",
"XmlRpcPath": "XML RPC 경로",
"YesterdayAt": "어제 {time}"
}

View File

@@ -1864,5 +1864,7 @@
"FavoriteFolders": "Pastas Favoritas",
"Warning": "Cuidado",
"DownloadClientUnavailable": "Cliente de download indisponível",
"NotificationsSettingsWebhookHeaders": "Cabeçalhos"
"NotificationsSettingsWebhookHeaders": "Cabeçalhos",
"MetadataKometaDeprecatedSetting": "Deprecado",
"MetadataKometaDeprecated": "Os arquivos Kometa não serão mais criados, o suporte será completamente removido na v6"
}

View File

@@ -19,7 +19,7 @@
"Sort": "Sınıflandır",
"SetTags": "Etiketleri Ayarla",
"Scheduled": "Planlı",
"RootFolders": "Kök klasörler",
"RootFolders": "Kök Klasörler",
"RootFolderCheckSingleMessage": "Eksik kök klasör: {rootFolderPath}",
"RootFolderCheckMultipleMessage": "Birden fazla kök klasörler eksik: {rootFolderPaths}",
"RootFolder": "Kök Klasör",
@@ -29,20 +29,20 @@
"ProxyCheckBadRequestMessage": "Proxy ile test edilemedi. DurumKodu: {statusCode}",
"Proxy": "Proxy",
"PreviewRename": "Yeniden Adlandır ve Önizle",
"ImportListStatusCheckSingleClientMessage": "Hatalar nedeniyle kullanılamayan listeler: {importListNames}",
"ImportListStatusCheckAllClientMessage": "Hatalar nedeniyle tüm listeler kullanılamıyor",
"ImportListStatusCheckSingleClientMessage": "Hatalar nedeniyle kullanılamayan dizinleyiciler: {importListNames}",
"ImportListStatusCheckAllClientMessage": "Hatalar nedeniyle tüm dizinleyiciler kullanılamıyor",
"MonitoredOnly": "Sadece Takip Edilen",
"MetadataSettingsMovieSummary": "Filmler içe aktarıldığında veya yenilenince meta veri dosyaları oluştur",
"Metadata": "Meta veri",
"MediaManagement": "Medya işletme",
"MediaManagement": "Medya Yönetimi",
"Logging": "Loglama",
"LogFiles": "Log dosyaları",
"LogFiles": "Log Kayıtları",
"ImportListsSettingsSummary": "Başka bir {appName} örneğinden veya Trakt listelerinden içe aktarın ve liste hariç tutma işlemlerini yönetin",
"ImportTipsMessage": "İçe aktarmanın sorunsuz geçmesini sağlamak için bazı ipuçları:",
"ImportHeader": "{appName} uygulamasına film eklemek için mevcut kitaplığı içe aktarın",
"Host": "Ana bilgisayar",
"GrabSelected": "Seçilenleri Kap",
"GeneralSettingsSummary": "Port, SSL, kullanıcı adı/şifre, proxy, analitikler ve güncellemeler",
"Host": "Sunucu",
"GrabSelected": "Seçilenleri Al",
"GeneralSettingsSummary": "Port, SSL, kullanıcı adı/şifre, proxy, analizler ve güncellemeler",
"Folder": "Klasör",
"Files": "Dosyalar",
"Filename": "Dosya adı",
@@ -51,7 +51,7 @@
"CustomFormats": "Özel Formatlar",
"Crew": "Ekip",
"AppDataLocationHealthCheckMessage": "Güncelleme sırasında AppData'nın silinmesini önlemek için güncelleme yapılmayacaktır",
"AddNewTmdbIdMessage": "Ayrıca bir filmin TMDb kimliğini kullanarak da arama yapabilirsiniz. Örneğin. tmdb:71663",
"AddNewTmdbIdMessage": "Ayrıca bir filmin TMDb kimliğini kullanarak da arama yapabilirsiniz. Örneğin. tmdb:50737",
"AddNewMessage": "Yeni film eklemek çok kolay, eklemek istediğiniz filmin adını yazmanız yeterli",
"AddExclusion": "Dışlananlara Ekle",
"Actions": "Eylemler",
@@ -68,7 +68,7 @@
"UpdateAll": "Tümünü Güncelle",
"UnselectAll": "Tüm Seçimleri Kaldır",
"UnmappedFolders": "Eşlenmemiş Klasörler",
"UiSettingsSummary": "Takvim, tarih ve renk engelli seçenekler",
"UiSettingsSummary": "Takvim, tarih ve renk körlüğü seçenekleri",
"Ui": "Arayüz",
"Titles": "Başlıklar",
"Timeleft": "Kalan Zaman",
@@ -123,7 +123,7 @@
"MovieNaming": "Film Adlandırma",
"MovieEditor": "Film Editörü",
"Movie": "Film",
"MoreInfo": "Daha fazla bilgi",
"MoreInfo": "Daha Fazla Bilgi",
"Month": "Ay",
"Monitor": "Takip",
"Missing": "Eksik",
@@ -145,7 +145,7 @@
"Formats": "Formatlar",
"Forecast": "Tahmin",
"Filter": "Filtre",
"FileManagement": "Dosya idare",
"FileManagement": "Dosya Yönetimi",
"Failed": "Başarısız oldu",
"Edit": "Düzenle",
"Downloaded": "İndirildi",
@@ -156,19 +156,19 @@
"ConnectSettingsSummary": "Bildirimler, medya sunucularına/oynatıcılara bağlantılar ve özel komut dosyaları",
"CompletedDownloadHandling": "Tamamlanan İndirme İşlemleri",
"ChooseAnotherFolder": "Başka bir dosya seç",
"Analytics": "Analitik",
"Analytics": "Analiz",
"All": "Hepsi",
"Agenda": "Ajanda",
"Added": "Eklendi",
"Added": "Eklenme",
"Activity": "Etkinlik",
"MinimumCustomFormatScoreHelpText": "İndirmeye izin verilen minimum özel biçim puanı",
"MinimumCustomFormatScoreHelpText": "İndirmeye izin verilen minimum özel format puanı",
"MovieIsRecommend": "Son eklenenlere göre film önerilir",
"NoEventsFound": "Etkinlik bulunamadı",
"NoMinimumForAnyRuntime": "Herhangi bir çalışma süresi için minimum değer yok",
"NoMoviesExist": "Film bulunamadı, başlamak için yeni bir film eklemek veya mevcut filmlerden bazılarını içe aktarmak isteyeceksiniz.",
"CancelPendingTask": "Bu bekleyen görevi iptal etmek istediğinizden emin misiniz?",
"CancelPendingTask": "Bekleyen görevi iptal etmek istediğinizden emin misiniz?",
"OnHealthIssue": "Sağlık Sorunu Hakkında",
"OnLatestVersion": "{appName}'ın en son sürümü zaten kurulu",
"OnLatestVersion": "{appName}'ın en son sürümü kurulu",
"OnlyTorrent": "Sadece Torrent",
"OnRename": "Yeniden Adlandırıldığında",
"OpenBrowserOnStart": "Başlangıçta tarayıcıyı aç",
@@ -190,7 +190,7 @@
"RemotePath": "Uzak Yol",
"ShowCertification": "Sertifikayı Göster",
"TestAllClients": "Tüm İstemcileri Test Et",
"TestAllIndexers": "Tüm Dizinleyicileri Test Et",
"TestAllIndexers": "Dizinleyicileri Test Et",
"TestAllLists": "Tüm Listeleri Test Et",
"TMDb": "TMDb",
"Name": "İsim",
@@ -204,13 +204,13 @@
"LoadingMovieCreditsFailed": "Film jeneriği yüklenemedi",
"LoadingMovieFilesFailed": "Film dosyaları yüklenemedi",
"Local": "Yerel",
"Location": "yer",
"Location": "Klasör Yolu",
"MarkAsFailed": "Başarısız olarak işaretle",
"MaximumSizeHelpText": "MB cinsinden alınacak bir sürüm için maksimum boyut. Sınırsız olarak ayarlamak için sıfıra ayarlayın",
"MediaManagementSettings": "Medya Yönetimi Ayarları",
"MountMovieHealthCheckMessage": "Bir film yolu içeren bağlama, salt okunur olarak bağlanır: ",
"RadarrTags": "{appName} Etiketleri",
"VideoCodec": "Video Codec",
"VideoCodec": "Video Kodek",
"NoLimitForAnyRuntime": "Herhangi bir çalışma zamanı için sınır yok",
"CloneCustomFormat": "Özel Formatı Klonla",
"Columns": "Sütunlar",
@@ -219,19 +219,19 @@
"DeleteIndexerMessageText": "'{name}' dizinleyicisini silmek istediğinizden emin misiniz?",
"DeleteRestrictionHelpText": "Bu kısıtlamayı silmek istediğinizden emin misiniz?",
"DoneEditingGroups": "Grupları Düzenleme Bitti",
"EditCustomFormat": "Özel Biçimi Düzenle",
"EditCustomFormat": "Özel Formatı Düzenle",
"EditPerson": "Kişiyi Düzenle",
"EditQualityProfile": "Kalite Profilini Düzenle",
"EnableAutomaticSearchHelpText": "Kullanıcı arayüzü veya {appName} tarafından otomatik aramalar yapıldığında kullanılacaktır",
"EnableColorImpairedModeHelpText": "Renk bozukluğu olan kullanıcıların renk kodlu bilgileri daha iyi ayırt etmesine olanak tanıyan değiştirilmiş stil",
"EnableColorImpairedModeHelpText": "Renk engelli kullanıcıların renkleri daha iyi ayırt edebilmelerini sağlamak için değiştirilmiş stil",
"EnableInteractiveSearch": "Etkileşimli Aramayı Etkinleştir",
"ExtraFileExtensionsHelpTextsExamples": "Örnekler: \".sub, .nfo\" veya \"sub, nfo\"",
"FocusSearchBox": "Arama Kutusuna Odaklan",
"Folders": "Klasörler",
"FollowPerson": "Kişiyi Takip Et",
"GrabReleaseMessageText": "{appName}, bu yayının hangi film için olduğunu belirleyemedi. {appName} bu yayını otomatik olarak içe aktaramayabilir. '{0}'ı yakalamak istiyor musunuz?",
"GrabReleaseMessageText": "{appName}, bu yayının hangi film için olduğunu belirleyemedi. {appName} bu yayını otomatik olarak içe aktaramayabilir. '{0}'ı almak istiyor musunuz?",
"IndexerPriority": "Dizinleyici Önceliği",
"IndexerSearchCheckNoAvailableIndexersMessage": "Son indeksleyici hataları nedeniyle arama özellikli indeksleyicilerin tümü geçici olarak kullanılamıyor",
"IndexerSearchCheckNoAvailableIndexersMessage": "Son zamanlardaki dizinleyici hataları nedeniyle tüm arama yeteneğine sahip dizinleyiciler geçici olarak kullanılamıyor",
"InstallLatest": "En Sonu Yükle",
"InteractiveSearch": "Etkileşimli Arama",
"MovieInvalidFormat": "Film: Geçersiz Format",
@@ -290,11 +290,11 @@
"SubfolderWillBeCreatedAutomaticallyInterp": "'{0}' alt klasörü otomatik olarak oluşturulacak",
"Sunday": "Pazar",
"Table": "Tablo",
"TableOptions": "Masa Seçenekleri",
"TableOptions": "Tablo Seçenekleri",
"TableOptionsColumnsMessage": "Hangi sütunların görünür olduğunu ve hangi sırada görüneceklerini seçin",
"TagIsNotUsedAndCanBeDeleted": "Etiket kullanılmaz ve silinebilir",
"TestAll": "Tümünü Test Et",
"TheLogLevelDefault": "Günlük düzeyi varsayılan olarak 'Bilgi' şeklindedir ve [Genel Ayarlar](/settings/general) bölümünden değiştirilebilir",
"TheLogLevelDefault": "Log seviyesi varsayılan olarak 'Bilgi' şeklindedir ve [Genel Ayarlar](/ayarlar/genel) bölümünden değiştirilebilir",
"ThisCannotBeCancelled": "Bu, {appName} yeniden başlatılmadan başlatıldıktan sonra iptal edilemez.",
"Title": "Başlık",
"TMDBId": "TMDb Kimliği",
@@ -313,7 +313,7 @@
"BrowserReloadRequired": "Tarayıcının Yeniden Yüklenmesi Gerekiyor",
"UiSettings": "Arayüz Ayarları",
"AddConditionError": "Yeni bir koşul eklenemiyor, lütfen tekrar deneyin.",
"AddCustomFormatError": "Yeni bir özel biçim eklenemiyor, lütfen tekrar deneyin.",
"AddCustomFormatError": "Yeni bir özel format eklenemiyor, lütfen tekrar deneyin.",
"AddDownloadClientError": "Yeni bir indirme istemcisi eklenemiyor, lütfen tekrar deneyin.",
"AddIndexerError": "Yeni dizinleyici eklenemiyor, lütfen tekrar deneyin.",
"AddListError": "Yeni bir liste eklenemiyor, lütfen tekrar deneyin.",
@@ -347,7 +347,7 @@
"UnsavedChanges": "Kaydedilmemiş Değişiklikler",
"UpdateMechanismHelpText": "{appName}'ın yerleşik güncelleyicisini veya bir komut dosyasını kullanın",
"UpdateScriptPathHelpText": ıkarılan bir güncelleme paketini alan ve güncelleme işleminin geri kalanını işleyen özel bir komut dosyasına giden yol",
"UpgradeUntilCustomFormatScore": "Özel Biçim Puanına Kadar Yükseltme",
"UpgradeUntilCustomFormatScore": "Özel Format Puanına Kadar Yükseltme",
"UpgradeUntil": "Kaliteye Kadar Yükseltme",
"UpgradeUntilThisQualityIsMetOrExceeded": "Bu kalite karşılanana veya aşılana kadar yükseltin",
"UrlBaseHelpText": "Ters proxy desteği için varsayılan boştur",
@@ -367,7 +367,7 @@
"YesCancel": "Evet İptal",
"YesMoveFiles": "Evet, Dosyaları Taşı",
"Yesterday": "Dün",
"YouCanAlsoSearch": "Filmin TMDb kimliğini veya IMDb kimliğini kullanarak da arama yapabilirsiniz. Örneğin. \"tmdb: 71663\"",
"YouCanAlsoSearch": "Filmin TMDb kimliğini veya IMDb kimliğini kullanarak da arama yapabilirsiniz. Örneğin. \"tmdb: 50737\"",
"MaintenanceRelease": "Bakım Sürümü: hata düzeltmeleri ve diğer iyileştirmeler. Daha fazla ayrıntı için Github İşlem Geçmişine bakın",
"MovieDetailsPreviousMovie": "Film Detayları: Önceki Film",
"MovieExcludedFromAutomaticAdd": "Otomatik Eklemeden Hariç Tutulan Film",
@@ -382,12 +382,12 @@
"AlternativeTitle": "Alternatif Başlık",
"Always": "Her zaman",
"AptUpdater": "Güncellemeyi yüklemek için apt'ı kullanın",
"CancelProcessing": "İşlemeyi İptal Et",
"CancelProcessing": "İşlemi İptal Et",
"CantFindMovie": "Filmimi neden bulamıyorum?",
"CertValidationNoLocal": "Yerel Adresler için Devre Dışı Bırak",
"ChmodFolderHelpTextWarning": "Bu, yalnızca {appName}'ı çalıştıran kullanıcı dosyanın sahibi ise çalışır. İndirme istemcisinin izinleri doğru şekilde ayarladığından emin olmak daha iyidir.",
"ChmodFolderHelpTextWarning": "Bu yalnızca {appName} uygulamasını çalıştıran kullanıcı, dosyanın sahibiyse işe yarar. İndirme istemci izinlerinin doğruluğundan emin olmak önerilir.",
"ChownGroupHelpText": "Grup adı veya gid. Uzak dosya sistemleri için gid kullanın.",
"ChownGroupHelpTextWarning": "Bu, yalnızca {appName}'ı çalıştıran kullanıcı dosyanın sahibi ise çalışır. İndirme istemcisinin {appName} ile aynı grubu kullanmasını sağlamak daha iyidir.",
"ChownGroupHelpTextWarning": "Bu yalnızca {appName} uygulamasını çalıştıran kullanıcı, dosyanın sahibiyse işe yarar. İndirme istemcisinin {appName} ile aynı grubu kullandığından emin olmak önerilir.",
"DeleteMovieFilesHelpText": "Film dosyalarını ve film klasörünü silin",
"DeleteMovieFiles": "{movieFileCount} Film Dosyasını Sil",
"DeleteHeader": "Sil - {0}",
@@ -407,10 +407,10 @@
"QualityProfileInUseMovieListCollection": "Bir filme eklenmiş kaliteli bir profili silemezsiniz",
"QueueIsEmpty": "Kuyruk boş",
"CalendarFeed": "{appName} Takvim Beslemesi",
"DownloadPropersAndRepacksHelpTextCustomFormat": "Propers / Repacks üzerinden özel format puanına göre sıralamak için \"Tercih Etme\" seçeneğini kullanın",
"DownloadPropersAndRepacksHelpTextCustomFormat": "Uygunluk ve Yeniden Paketlemeler üzerinden özel format puanına göre sıralamak için \"Tercih Etme\" seçeneğini kullanın",
"Tomorrow": "Yarın",
"DotNetVersion": ".NET",
"NoHistory": "Geçmiş yok",
"NoHistory": "Geçmiş bulunamadı",
"AddQualityProfile": "Kalite Profili Ekle",
"NoBackupsAreAvailable": "Kullanılabilir yedek yok",
"AddRootFolder": "Kök Klasör Ekle",
@@ -420,11 +420,11 @@
"AcceptConfirmationModal": "Onay Modunu Kabul Et",
"AddIndexer": "Dizinleyici Ekle",
"AllResultsHiddenFilter": "Tüm sonuçlar, uygulanan filtre tarafından gizlenir",
"AnalyseVideoFiles": "Video dosyalarını analiz edin",
"AnalyseVideoFiles": "Video Dosyalarını Analiz Et",
"AppDataDirectory": "Uygulama Veri Dizini",
"CustomFormatUnknownCondition": "Bilinmeyen Özel Biçim koşulu '{implementation}'",
"CustomFormatUnknownCondition": "Bilinmeyen Özel Format koşulu '{implementation}'",
"AuthBasic": "Temel (Tarayıcıılır Penceresi)",
"AuthForm": "Formlar (Giriş Sayfası)",
"AuthForm": "Form (Giriş Sayfası)",
"Branch": "Şube",
"BuiltIn": "Dahili",
"CalendarOptions": "Takvim Seçenekleri",
@@ -474,13 +474,13 @@
"None": "Yok",
"NoResultsFound": "Sonuç bulunamadı",
"MovieFilesTotaling": "Film Dosyaları Toplamı",
"OnGrab": "Yakalandığında",
"OnGrab": "Alındığında",
"OnlyUsenet": "Sadece Usenet",
"PendingChangesMessage": "Kaydedilmemiş değişiklikleriniz var, bu sayfadan ayrılmak istediğinizden emin misiniz?",
"ProxyType": "Proxy Türü",
"RegularExpressionsCanBeTested": "Normal ifadeler [burada](http://regexstorm.net/tester) test edilebilir.",
"RegularExpressionsCanBeTested": "Düzenli ifadeler [burada]({url}) test edilebilir.",
"ShowGenres": "Türleri Göster",
"UpgradesAllowed": "Yükseltmelere İzin Verildi",
"UpgradesAllowed": "Yükseltmelere İzin Ver",
"Pending": "Bekliyor",
"EnableSsl": "SSL'yi etkinleştir",
"Indexer": "Dizinleyici",
@@ -489,7 +489,7 @@
"LoadingMovieExtraFilesFailed": "İlave film dosyaları yüklenemedi",
"Manual": "Manuel",
"ManualImport": "Manuel İçe Aktar",
"Mechanism": "İşleyiş",
"Mechanism": "Teknik",
"MediaInfo": "Medya bilgisi",
"Hostname": "Hostname",
"MinutesSixty": "60 Dakika: {sixty}",
@@ -505,43 +505,43 @@
"AddImportListExclusion": "İçe Aktarma Listesi Hariç Tutma Ekle",
"ApiKey": "API Anahtarı",
"NamingSettings": "Adlandırma Ayarları",
"NoLogFiles": "Günlük dosyası yok",
"NoLogFiles": "Log kayıt dosyası henüz yok",
"NoMatchFound": "Eşleşme bulunamadı!",
"NotMonitored": "Takip Edilmeyen",
"MinimumAge": "Asgari yaş",
"MinimumAge": "Minimum Geçen Süre",
"NoUpdatesAreAvailable": "Güncelleme yok",
"AddingTag": "Etiket ekleniyor",
"Age": "Yıl",
"AgeWhenGrabbed": "Yıl (yakalandığında)",
"AgeWhenGrabbed": "Yıl (alındığında)",
"AnalyticsEnabledHelpText": "Anonim kullanım ve hata bilgilerini {appName} sunucularına gönderin. Buna, tarayıcınız, hangi {appName} WebUI sayfalarını kullandığınız, hata raporlamanın yanı sıra işletim sistemi ve çalışma zamanı sürümü hakkındaki bilgiler de dahildir. Bu bilgiyi özelliklere ve hata düzeltmelerine öncelik vermek için kullanacağız.",
"Apply": "Uygula",
"ICalShowAsAllDayEventsHelpText": "Etkinlikler, takviminizde tüm gün süren etkinlikler olarak görünecek",
"Authentication": "Doğrulama",
"ApplyTags": "Etiketleri Uygula",
"AuthenticationMethodHelpText": "{appName}'a erişmek için Kullanıcı Adı ve Şifre gerektir",
"AuthenticationMethodHelpText": "{appName}'e erişmek için Kullanıcı Adı ve Parola gereklidir",
"Automatic": "Otomatik",
"AutoRedownloadFailedHelpText": "Otomatik olarak farklı bir Yayın arayın ve indirmeye çalışın",
"AutoUnmonitorPreviouslyDownloadedMoviesHelpText": "Diskten silinen filmler otomatik olarak {appName}'da takip edilmez",
"AvailabilityDelay": "Kullanılabilirlik Gecikmesi",
"AvailabilityDelayHelpText": "Film aramak için mevcut tarihten önceki veya sonraki zaman miktarı",
"BackupIntervalHelpText": "Otomatik yedeklemeler arasındaki zaman aralığı",
"BackupNow": "Şimdi yedekle",
"BackupRetentionHelpText": "Saklama süresinden daha eski olan otomatik yedeklemeler otomatik olarak temizlenecektir",
"BackupNow": "Şimdi Yedekle",
"BackupRetentionHelpText": "Saklama süresinden daha eski otomatik yedeklemeler otomatik olarak temizlenecektir",
"Backups": "Yedeklemeler",
"BeforeUpdate": "Güncellemeden önce",
"BindAddress": "Bind Adresi",
"BindAddressHelpText": "Tüm arayüzler için geçerli IP adresi, localhost veya '*'",
"CertificateValidation": "Sertifika Doğrulama",
"CertificateValidation": "Sertifika Doğrulaması",
"CertificateValidationHelpText": "HTTPS sertifika doğrulamasının sıkılığını değiştirin. Riskleri anlamadığınız sürece değişmeyin.",
"Certification": "Sertifikasyon",
"BranchUpdate": "{appName}'ı güncellemek için kullanılacak dal",
"BranchUpdateMechanism": "Harici güncelleme mekanizması tarafından kullanılan dal",
"BypassProxyForLocalAddresses": "Yerel Adresler için Proxy'yi Atla",
"Certification": "Sertifika",
"BranchUpdate": "{appName} uygulamasını güncellemek için kullanılacak şube",
"BranchUpdateMechanism": "Harici güncelleme mekanizması tarafından kullanılan şube",
"BypassProxyForLocalAddresses": "Yerel Adresler için Proxy'yi Kullanma",
"ChangeFileDate": "Dosya Tarihini Değiştir",
"ChangeHasNotBeenSavedYet": "Değişiklik henüz kaydedilmedi",
"CheckDownloadClientForDetails": "daha fazla ayrıntı için indirme istemcisini kontrol edin",
"CheckForFinishedDownloadsInterval": "Tamamlanan İndirmeler Aralığını Kontrol Et",
"CleanLibraryLevel": "Kitaplık Düzeyini Temizle",
"CleanLibraryLevel": "Kütüphane Seviyesini Temizle",
"ClientPriority": "Müşteri Önceliği",
"CertificationCountry": "Sertifikasyon Ülkesi",
"CertificationCountryHelpText": "Film Sertifikaları için Ülke Seçin",
@@ -555,9 +555,9 @@
"Edition": "Baskı",
"EnableCompletedDownloadHandlingHelpText": "Tamamlanan indirmeleri indirme istemcisinden otomatik olarak içe aktarın",
"ListEnabledHelpText": "Bu listeyi {appName}'da kullanmak üzere etkinleştirin",
"Ended": "Bitti",
"Events": "Etkinlikler",
"IncludeCustomFormatWhenRenamingHelpText": "{Custom Formats} yeniden adlandırma formatına dahil et",
"Ended": "Biten",
"Events": "Olaylar",
"IncludeCustomFormatWhenRenamingHelpText": "Özel formatları yeniden adlandırma formatına dahil et",
"Overview": "Genel Bakış",
"OverviewOptions": "Genel Bakış Seçenekler",
"MinimumFreeSpace": "Minimum Boş Alan",
@@ -572,12 +572,12 @@
"MovieTitleToExcludeHelpText": "Hariç tutulacak filmin başlığı (anlamlı herhangi bir şey olabilir)",
"MovieYear": "Film Yılı",
"MovieYearToExcludeHelpText": "Hariç tutulacak film yılı",
"PendingChangesDiscardChanges": "Değişiklikleri atın ve ayrıl",
"PendingChangesDiscardChanges": "Değişiklikleri at ve ayrıl",
"PreferredSize": "Tercih Edilen Boyut",
"Proper": "Uygun",
"PtpOldSettingsCheckMessage": "Aşağıdaki PassThePopcorn dizinleyicilerinin ayarları kullanımdan kaldırıldı ve güncellenmeleri gerekiyor: {0}",
"Queued": "Kuyruğa alındı",
"RecyclingBinCleanup": "Geri Dönüşüm Kutusu Temizleme",
"RecyclingBinCleanup": "Geri Dönüşüm Kutusu Temizle",
"RefreshMovie": "Filmi yenile",
"RejectionCount": "Reddetme Sayısı",
"RelativePath": "Göreceli yol",
@@ -595,7 +595,7 @@
"AudioInfo": "Ses Bilgisi",
"Cancel": "Vazgeç",
"PhysicalRelease": "Fiziksel Salınım",
"Port": "Liman",
"Port": "Port No",
"Close": "Kapat",
"PortNumber": "Port numarası",
"ProtocolHelpText": "Hangi protokol (ler) in kullanılacağını ve başka türlü eşit sürümler arasında seçim yaparken hangisinin tercih edileceğini seçin",
@@ -607,7 +607,7 @@
"SupportedListsMovie": "{appName}, aşağıda belirtilenlerin yanı sıra tüm RSS film listelerini destekler.",
"AlreadyInYourLibrary": "Kütüphanenizde mevcut",
"RecyclingBinHelpText": "Film dosyaları, kalıcı olarak silinmek yerine silindiğinde buraya gider",
"RequiredHelpText": "Özel biçimin uygulanabilmesi için bu {implementationName} koşulunun eşleşmesi gerekir. Aksi takdirde tek bir {implementationName} eşleşmesi yeterlidir.",
"RequiredHelpText": "Özel formatın uygulanabilmesi için bu {implementationName} koşulunun eşleşmesi gerekir. Aksi takdirde tek bir {implementationName} eşleşmesi yeterlidir.",
"AddNewRestriction": "Yeni kısıtlama ekle",
"RestartRequiredHelpTextWarning": "Etkili olması için yeniden başlatma gerektirir",
"Restore": "Onarmak",
@@ -625,12 +625,12 @@
"Backup": "Yedekler",
"Username": "Kullanıcı adı",
"WaitingToImport": "İçe Aktarma Bekleniyor",
"BackupFolderHelpText": "Göreli yollar {appName}'ın AppData dizini altında olacaktır",
"BackupFolderHelpText": "Bağıl yollar {appName}'ın AppData dizini altında olacak",
"Disabled": "Devre dışı",
"Peers": "Akranlar",
"Peers": "Eşler",
"UiSettingsLoadError": "Arayüz ayarları yüklenemiyor",
"Unavailable": "Kullanım dışı",
"Importing": "İçe aktarılıyor",
"Importing": "İçe Aktarma",
"NoLeaveIt": "Hayır, Bırak",
"NotAvailable": "Müsait değil",
"NotificationTriggers": "Bildirim Tetikleyicileri",
@@ -652,11 +652,11 @@
"CouldNotFindResults": "'{term}' için herhangi bir sonuç bulunamadı",
"CreateEmptyMovieFolders": "Boş film klasörleri oluşturun",
"CreateGroup": "Grup oluştur",
"CurrentlyInstalled": "Şu anda Yüklü",
"CurrentlyInstalled": "Şuan Kurulu",
"Custom": "Özel",
"CustomFormat": "Özel Format",
"CustomFormatHelpText": "{appName}, özel formatlarla eşleşen puanların toplamını kullanarak her yayını puanlar. Yeni bir yayının, puanı aynı veya daha iyi kalitede iyileştirecekse, {appName} onu alacaktır.",
"CustomFormatScore": "Özel Biçim Puanı",
"CustomFormatScore": "Özel Format Puanı",
"CustomFormatsSettings": "Özel Format Ayarları",
"CustomFormatUnknownConditionOption": "'{implementation}' koşulu için bilinmeyen seçenek '{key}'",
"Cutoff": "Kesinti",
@@ -684,21 +684,21 @@
"DeleteTagMessageText": "'{label}' etiketini silmek istediğinizden emin misiniz?",
"DeleteMovieFolderConfirmation": "'{path}' film klasörü ve tüm içeriği silinecek.",
"Discord": "Uyuşmazlık",
"Docker": "Liman işçisi",
"Donations": "Bağışlar",
"Docker": "Docker",
"Donations": "Bağış",
"DoNotPrefer": "Tercih etmeme",
"DoNotUpgradeAutomatically": "Otomatik Olarak Yükseltme",
"DownloadClient": stemciyi İndir",
"DownloadClient": ndirme İstemcisi",
"DownloadClientCheckNoneAvailableMessage": "İndirme istemcisi yok",
"DownloadClients": "İndirme İstemcileri",
"DownloadClientSettings": "İstemci Ayarlarını İndir",
"DownloadClientSettings": ndirme İstemcisi Ayarlarını",
"DownloadClientsSettingsSummary": "İndirme İstemcileri, indirme işlemleri ve uzaktan yol eşlemeleri",
"DownloadClientStatusCheckSingleClientMessage": "Hatalar nedeniyle indirilemeyen istemciler: {downloadClientNames}",
"DownloadedAndMonitored": "İndirildi (Takip Ediliyor)",
"DownloadedButNotMonitored": "İndirildi (Takip Edilmiyor)",
"DownloadFailed": "Yükleme başarısız",
"Downloading": "İndiriliyor",
"DownloadPropersAndRepacks": "Propers ve Repacks",
"DownloadPropersAndRepacks": "Uygunluj ve Yeniden Paketlemeler",
"DownloadPropersAndRepacksHelpText": "Propers / Repacks'e otomatik olarak yükseltme yapılıp yapılmayacağı",
"DownloadPropersAndRepacksHelpTextWarning": "Propers / Repacks'e otomatik yükseltmeler için özel formatlar kullanın",
"DownloadWarning": "Uyarıyı indir: {warningMessage}",
@@ -734,11 +734,11 @@
"FailedLoadingSearchResults": "Arama sonuçları yüklenemedi, lütfen tekrar deneyin.",
"FailedToLoadMovieFromAPI": "API'den film yüklenemedi",
"FeatureRequests": "Özellik talepleri",
"ChangeFileDateHelpText": "İçe aktarmada / yeniden taramada dosya tarihini değiştirin",
"ChangeFileDateHelpText": "İçe aktarma/yeniden tarama sırasında dosya tarihini değiştir",
"FileNames": "Dosya Adları",
"FilterPlaceHolder": "Film ara",
"FirstDayOfWeek": "Haftanın ilk günü",
"Fixed": "Sabit",
"Fixed": "Düzeltilen",
"FolderMoveRenameWarning": "Bu aynı zamanda ayarlarda film klasörü formatına göre film klasörünü yeniden adlandıracaktır.",
"SupportedDownloadClientsMoreInfo": "Bireysel indirme istemcileri hakkında daha fazla bilgi için bilgi düğmelerine tıklayın.",
"SupportedListsMoreInfo": "Ayrı ayrı içe aktarma listeleri hakkında daha fazla bilgi için bilgi düğmelerine tıklayın.",
@@ -747,8 +747,8 @@
"Genres": "Türler",
"Global": "Küresel",
"GoToInterp": "{0} adresine gidin",
"Grabbed": "Yakalandı",
"GrabRelease": "Yayın Yakalama",
"Grabbed": "Alındı",
"GrabRelease": "Yayın Alma",
"Group": "Grup",
"HardlinkCopyFiles": "Hardlink / Dosyaları Kopyala",
"NoIssuesWithYourConfiguration": "Yapılandırmanızla ilgili sorun yok",
@@ -761,7 +761,7 @@
"Images": "Görüntüler",
"IMDb": "IMDb",
"Import": "İçe aktar",
"ImportCustomFormat": "Özel Biçimi İçe Aktar",
"ImportCustomFormat": "Özel Formatı İçe Aktar",
"ImportedTo": "İçeri Aktarıldı",
"ImportRootPath": "{appName}'ı belirli bir filmi değil, tüm filmlerinizi içeren klasöre yöneltin. Örneğin. {1} değil {0}. Ek olarak, her film kök / kitaplık klasöründe kendi klasöründe olmalıdır.",
"InCinemasDate": "Vizyon Tarihi",
@@ -771,11 +771,11 @@
"IncludeUnmonitored": "Takip Edilmeyenleri Dahil Et",
"ImportMovies": "Filmleri İçe Aktar",
"IndexerPriorityHelpText": "Dizinleyici Önceliği (En Yüksek) 1'den (En Düşük) 50'ye kadar. Varsayılan: 25'dir. Eşit olmayan yayınlar için eşitlik bozucu olarak yayınlar alınırken kullanılan {appName}, RSS Senkronizasyonu ve Arama için etkinleştirilmiş tüm dizin oluşturucuları kullanmaya devam edecek",
"IndexerRssHealthCheckNoAvailableIndexers": "Son indeksleyici hataları nedeniyle tüm rss özellikli indeksleyiciler geçici olarak kullanılamıyor",
"IndexerRssHealthCheckNoAvailableIndexers": "Son zamanlardaki dizinleyici hataları nedeniyle tüm rss uyumlu dizinleyiciler geçici olarak kullanılamıyor",
"IndexerRssHealthCheckNoIndexers": "RSS senkronizasyonunun etkin olduğu dizinleyici yok, {appName} yeni yayınlar otomatik olarak almayacak",
"Indexers": "Dizinleyiciler",
"IndexerSearchCheckNoAutomaticMessage": "Otomatik Arama etkinken indeksleyici yok, {appName} herhangi bir otomatik arama sonucu sağlamayacak",
"IndexerSearchCheckNoInteractiveMessage": "Etkileşimli Arama etkinleştirilmiş fakat dizinleyici mevcut değil; {appName} herhangi bir etkileşimli arama sonucu sağlayamayacaktır",
"IndexerSearchCheckNoAutomaticMessage": "Otomatik Arama etkinleştirildiğinde hiçbir dizinleyici kullanılamaz, {appName} herhangi bir otomatik arama sonucu sağlamayacaktır",
"IndexerSearchCheckNoInteractiveMessage": "Etkileşimli Arama etkinleştirildiğinde hiçbir dizinleyici kullanılamaz, {appName} herhangi bir etkileşimli arama sonucu sağlamayacaktır",
"IndexerSettings": "Dizinleyici Ayarları",
"IndexerStatusCheckSingleClientMessage": "Hatalar nedeniyle dizinleyiciler kullanılamıyor: {indexerNames}",
"InteractiveImport": "Etkileşimli İçe Aktarma",
@@ -790,26 +790,26 @@
"Links": "Bağlantılar",
"ImportListSettings": "Liste Ayarları",
"ListSyncLevelHelpText": "Kitaplıktaki filmlerin listenizde/listelerinizde görünmemesi durumunda seçiminize göre işlem yapılacaktır",
"LogLevel": "Günlük Düzeyi",
"LogLevelTraceHelpTextWarning": "İzleme günlük kaydı yalnızca geçici olarak etkinleştirilmelidir",
"LogLevel": "Log Seviyesi",
"LogLevelTraceHelpTextWarning": "İzleme kaydı yalnızca geçici olarak etkinleştirilmelidir",
"LogOnly": "Sadece hesap aç",
"Logs": "Kütükler",
"Logs": "Kayıtlar",
"LookingForReleaseProfiles1": "Yayımlama Profilleri mi arıyorsunuz? Deneyin",
"LookingForReleaseProfiles2": "yerine.",
"Lowercase": "Küçük Harf",
"ManualImportSelectLanguage": "Manuel İçe Aktar - Dil Seçin",
"ManualImportSelectMovie": "Manuel İçe Aktar - Film Seçin",
"ManualImportSelectQuality": " Manuel İçe Aktar - Kaliteyi Seçin",
"MappedNetworkDrivesWindowsService": "Eşlenen ağ sürücüleri, bir Windows Hizmeti olarak çalışırken kullanılamaz. Daha fazla bilgi için lütfen SSS bölümüne bakın",
"MappedNetworkDrivesWindowsService": "Windows Hizmeti olarak çalıştırıldığında eşlenen ağ sürücüleri kullanılamaz, daha fazla bilgi için [SSS]({url}) bölümüne bakın.",
"MarkAsFailedMessageText": "'{0}' başarısız olarak işaretlemek istediğinizden emin misiniz?",
"MaximumLimits": "Maksimum Sınırlar",
"MaximumSize": "En büyük boy",
"MaximumSize": "Maksimum Boyut",
"MegabytesPerMinute": "Dakika Başına Megabayt",
"Message": "İleti",
"Message": "Mesaj",
"MetadataSettings": "Meta Veri Ayarları",
"MIA": "MIA",
"Min": "Min",
"MinimumAgeHelpText": "Yalnızca Usenet: NZB'lerin alınmadan önceki dakika cinsinden minimum yaşı. Yeni yayınların usenet sağlayıcınıza yayılması için zaman tanımak için bunu kullanın.",
"MinimumAgeHelpText": "Yalnızca Usenet: NZB'lerin almadan önceki minimum geçen süre (dakika cinsinden). Bunu, yeni sürümlerin usenet sağlayıcınıza yayılması için zaman vermek amacıyla kullanın.",
"MinimumAvailability": "Minimum Kullanılabilirlik",
"MinimumCustomFormatScore": "Minimum Özel Format Puanı",
"MinimumFreeSpaceHelpText": "Bu miktardan daha az kullanılabilir disk alanı bırakacaksa içe aktarmayı önleyin",
@@ -874,7 +874,7 @@
"RemovingTag": "Etiket kaldırılıyor",
"RenameMovies": "Filmleri Yeniden Adlandır",
"RenameMoviesHelpText": "Yeniden adlandırma devre dışı bırakılırsa, {appName} mevcut dosya adını kullanacaktır",
"ReplaceIllegalCharacters": "Yasadışı Karakterleri Değiştirin",
"ReplaceIllegalCharacters": "Geçersiz Karakterleri Değiştirin",
"ReplaceIllegalCharactersHelpText": "Geçersiz karakterleri değiştirin. İşaretlenmezse bunun yerine {appName} bunları kaldıracak",
"ReplaceWithSpaceDash": "Space Dash ile değiştirin",
"Required": "Gerekli",
@@ -894,7 +894,7 @@
"Save": "Kaydet",
"SaveSettings": "Ayarları kaydet",
"Score": "Puan",
"Script": "Hazır Metin",
"Script": "Komut Dosyası",
"ScriptPath": "Komut Dosyası Yolu",
"SearchCutoffUnmet": "Arama Kesintisi Karşılanmadı",
"SearchForMovie": "Film ara",
@@ -925,7 +925,7 @@
"StartProcessing": "İşlemeye Başla",
"TagDetails": "Etiket Ayrıntıları - {label}",
"ICalTagsMoviesHelpText": "En az bir eşleşen etikete sahip filmler için geçerlidir",
"Test": "Sına",
"Test": "Test Et",
"Time": "Zaman",
"Trigger": "Tetik",
"UiLanguage": "Arayüz Dili",
@@ -955,7 +955,7 @@
"More": "Daha",
"Download": "İndir",
"DownloadClientRootFolderHealthCheckMessage": "İndirme istemcisi {downloadClientName}, indirmeleri kök klasöre yerleştirir {rootFolderPath}. Bir kök klasöre indirmemelisiniz.",
"Blocklist": "Kara liste",
"Blocklist": "Engellenenler listesi",
"BlocklistRelease": "Kara Liste Sürümü",
"RemoveFromBlocklist": "Kara listeden kaldır",
"Blocklisted": "Kara liste",
@@ -964,7 +964,7 @@
"Filters": "Filtreler",
"Rating": "Puan",
"SelectLanguages": "Dil Seçin",
"RssSyncIntervalHelpText": "Dakika cinsinden periyot. Devre dışı bırakmak için sıfıra ayarlayın (tüm otomatik yayın yakalamayı durduracaktır)",
"RssSyncIntervalHelpText": "Dakika cinsinden aralık. Devre dışı bırakmak için sıfıra ayarlayın (tüm otomatik yayın almayı durduracaktır)",
"AllCollectionsHiddenDueToFilter": "Uygulanan filtre nedeniyle tüm koleksiyonlar gizlendi.",
"Collections": "Koleksiyon",
"MonitorMovies": "Film Takip Edilebilirliği",
@@ -978,7 +978,7 @@
"AddCondition": "Koşul Ekle",
"AddConditionImplementation": "Koşul Ekle - {implementationName}",
"AddConnection": "Bağlantı Ekle",
"AddIndexerImplementation": "Yeni Dizin Ekle - {implementationName}",
"AddIndexerImplementation": "Yeni Dizinleyici Ekle - {implementationName}",
"AddConnectionImplementation": "Bağlantı Ekle - {implementationName}",
"EditIndexerImplementation": "Koşul Ekle - {implementationName}",
"AllTitles": "Tüm Başlıklar",
@@ -995,7 +995,7 @@
"AutomaticUpdatesDisabledDocker": "Docker güncelleme mekanizması kullanıldığında otomatik güncellemeler doğrudan desteklenmez. Konteyner görüntüsünü {appName} dışında güncellemeniz veya bir komut dosyası kullanmanız gerekecek",
"BypassDelayIfHighestQuality": "En Yüksek Kalitedeyse Atla",
"AppUpdated": "{appName} Güncellendi",
"AppUpdatedVersion": "{appName}, `{version}` sürümüne güncellendi; en son değişikliklerin etkin olabilmesi için {appName} uygulamasını yeniden başlatmanız gerekli",
"AppUpdatedVersion": "{appName}, `{version}` sürümüne güncellendi; değişikliklerin etkin olabilmesi için {appName} uygulamasını yeniden başlatmanız gerekli",
"ApplyTagsHelpTextHowToApplyIndexers": "Seçilen indeksleyicilere etiketler nasıl uygulanır",
"ApplyTagsHelpTextHowToApplyMovies": "Seçilen filmlere etiketler nasıl uygulanır",
"ApplyTagsHelpTextRemove": "Kaldır: Girilen etiketleri kaldırın",
@@ -1081,13 +1081,13 @@
"DeletedReasonUpgrade": "Bir yükseltmeyi içe aktarmak için dosya silindi",
"AutoTaggingSpecificationTag": "Etiket",
"CustomFormatsSettingsTriggerInfo": "Bir yayına veya dosyaya, seçilen farklı koşul türlerinden en az biriyle eşleştiğinde Özel Format uygulanacaktır.",
"CutoffNotMet": "Kesinti Noktasına Ulaşılmadı",
"CutoffNotMet": "Kesinti Karşılanmadı",
"Default": "Varsayılan",
"Dash": "Çizgi",
"DefaultNameCopiedSpecification": "{name} - Kopyala",
"DeleteAutoTag": "Etiketi Otomatik Sil",
"DeleteCondition": "Koşulu Sil",
"Directory": "Rehber",
"Directory": "Dizin",
"Donate": "Bağış yap",
"DeleteImportListExclusionMessageText": "Bu içe aktarma listesi hariç tutma işlemini silmek istediğinizden emin misiniz?",
"DoNotBlocklist": "Engelleme Listesine Eklemeyin",
@@ -1105,7 +1105,7 @@
"DownloadClientFreeboxApiError": "Freebox API'si şu hatayı döndürdü: {errorDescription}",
"DownloadClientFreeboxSettingsPortHelpText": "Freebox arayüzüne erişim için kullanılan bağlantı noktası, varsayılan olarak '{port}' şeklindedir",
"DeleteAutoTagHelpText": "'{name}' etiketini otomatik silmek istediğinizden emin misiniz?",
"DeleteCustomFormatMessageText": "'{name}' özel biçimini silmek istediğinizden emin misiniz?",
"DeleteCustomFormatMessageText": "'{name}' özel formatı silmek istediğinizden emin misiniz?",
"DeleteImportList": "İçe Aktarma Listesini Sil",
"DeleteRootFolder": "Kök Klasörü Sil",
"DeleteSelectedIndexers": "Dizinleyicileri Sil",
@@ -1152,7 +1152,7 @@
"DownloadClientFreeboxSettingsAppId": "Uygulama kimliği",
"DownloadClientFreeboxSettingsApiUrl": "API URL'si",
"DownloadClientFreeboxSettingsAppToken": "Uygulama Jetonu",
"DownloadClientFreeboxSettingsHostHelpText": "Freebox'un ana bilgisayar adı veya ana bilgisayar IP adresi, varsayılan olarak '{url}' şeklindedir (yalnızca aynı ağdaysa çalışır)",
"DownloadClientFreeboxSettingsHostHelpText": "Freebox'un istemci adı veya istemci IP adresi, varsayılan olarak '{url}' şeklindedir (yalnızca aynı ağda çalışır)",
"DownloadClientFreeboxAuthenticationError": "Freebox API'sinde kimlik doğrulama başarısız oldu. Sebep: {errorDescription}",
"DownloadClientFreeboxNotLoggedIn": "Giriş yapmadınız",
"DownloadClientFreeboxSettingsApiUrlHelpText": "Freebox API temel URL'sini API sürümüyle tanımlayın, örneğin '{url}', varsayılan olarak '{defaultApiUrl}' olur",
@@ -1170,7 +1170,7 @@
"DownloadClientSettingsCategoryHelpText": "{appName}'e özel bir kategori eklemek, {appName} dışındaki ilgisiz indirmelerle çakışmaları önler. Kategori kullanmak isteğe bağlıdır ancak önemle tavsiye edilir.",
"DownloadClientSettingsCategorySubFolderHelpText": "{appName}'e özel bir kategori eklemek, {appName} dışındaki ilgisiz indirmelerle çakışmaları önler. Kategori kullanmak isteğe bağlıdır ancak önemle tavsiye edilir. Çıkış dizininde bir [kategori] alt dizini oluşturur.",
"DownloadClientSettingsDestinationHelpText": "İndirme hedefini manuel olarak belirtir, varsayılanı kullanmak için boş bırakın",
"DownloadClientSettingsRecentPriority": "Yeni Öncelik",
"DownloadClientSettingsRecentPriority": "Yeni Önceliği",
"DownloadClientSettingsUseSslHelpText": "{clientName} ile bağlantı kurulurken güvenli bağlantıyı kullan",
"DownloadClientUTorrentTorrentStateError": "uTorrent bir hata bildirdi",
"DownloadClientValidationVerifySsl": "SSL Doğrulama ayarı",
@@ -1181,7 +1181,7 @@
"DownloadClientNzbgetValidationKeepHistoryOverMaxDetail": "NzbGet KeepHistory ayarı çok yüksek ayarlanmış.",
"DownloadClientPneumaticSettingsNzbFolder": "Nzb Klasörü",
"DownloadClientPneumaticSettingsStrmFolderHelpText": "Bu klasördeki .strm dosyaları drone ile içe aktarılacak",
"DownloadClientQbittorrentSettingsFirstAndLastFirst": "İlk ve Son İlk",
"DownloadClientQbittorrentSettingsFirstAndLastFirst": "İlk ve Son",
"DownloadClientQbittorrentSettingsInitialStateHelpText": "Torrentlerin başlangıç durumu qBittorrent'e eklendi. Zorunlu Torrentlerin seed kısıtlamalarına uymadığını unutmayın",
"DownloadClientQbittorrentSettingsSequentialOrder": "Sıralı Sıra",
"DownloadClientQbittorrentSettingsSequentialOrderHelpText": "Sıralı olarak indirin (qBittorrent 4.1.0+)",
@@ -1194,7 +1194,7 @@
"DownloadClientQbittorrentValidationQueueingNotEnabledDetail": "Torrent Kuyruğa Alma, qBittorrent ayarlarınızda etkin değil. qBittorrent'te etkinleştirin veya öncelik olarak 'Son'u seçin.",
"DownloadClientQbittorrentValidationQueueingNotEnabled": "kuyruğa Alma Etkin Değil",
"DownloadClientQbittorrentValidationRemovesAtRatioLimit": "qBittorrent, Torrentleri Paylaşım Oranı Sınırına ulaştıklarında kaldıracak şekilde yapılandırılmıştır",
"DownloadClientRTorrentSettingsAddStopped": "Ekleme Durduruldu",
"DownloadClientRTorrentSettingsAddStopped": "Durdurulana Ekle",
"DownloadClientRTorrentSettingsUrlPathHelpText": "XMLRPC uç noktasının yolu, bkz. {url}. RuTorrent kullanılırken bu genellikle RPC2 veya [ruTorrent yolu]{url2} olur.",
"DownloadClientRTorrentSettingsUrlPath": "URL Yolu",
"DownloadClientSabnzbdValidationCheckBeforeDownload": "Sabnbzd'de 'İndirmeden önce kontrol et' seçeneğini devre dışı bırakın",
@@ -1203,7 +1203,7 @@
"DownloadClientSabnzbdValidationEnableDisableTvSorting": "TV Sıralamasını Devre Dışı Bırak",
"DownloadClientSabnzbdValidationEnableJobFolders": "İş klasörlerini etkinleştir",
"DownloadClientSabnzbdValidationUnknownVersion": "Bilinmeyen Sürüm: {rawVersion}",
"DownloadClientSettingsAddPaused": "Ekleme Durduruldu",
"DownloadClientSettingsAddPaused": "Duraklatılana Ekle",
"DownloadClientSettingsInitialState": "Başlangıç Durumu",
"DownloadClientSettingsInitialStateHelpText": "{clientName} dosyasına eklenen torrentler için başlangıç durumu",
"DownloadClientSettingsOlderPriority": "Eski Önceliği",
@@ -1301,7 +1301,7 @@
"ManageClients": "İstemcileri Yönet",
"FormatRuntimeHours": "{hours}s",
"IgnoreDownloadHint": "{appName}'in bu indirmeyi daha fazla işlemesini durdurur",
"LogFilesLocation": "Günlük dosyaları şu konumda bulunur: {location}",
"LogFilesLocation": "Log kayıtlarının bulunduğu konum: {location}",
"ManageIndexers": "Dizinleyicileri Yönet",
"MovieFileDeletedTooltip": "Film dosyası silindi",
"NotificationsCustomScriptSettingsArgumentsHelpText": "Komut dosyasına aktarılacak argümanlar",
@@ -1330,20 +1330,20 @@
"FullColorEventsHelpText": "Etkinliğin tamamını yalnızca sol kenar yerine durum rengiyle renklendirecek şekilde stil değiştirildi. Gündem için geçerli değildir",
"IMDbId": "IMDb Id",
"EnableProfile": "Profili Etkinleştir",
"IndexerDownloadClientHelpText": "Bu dizinleyiciden yakalamak için hangi indirme istemcisinin kullanılacağını belirtin",
"IndexerDownloadClientHelpText": "Bu dizinleyiciden almak için hangi indirme istemcisinin kullanılacağını belirtin",
"InstanceNameHelpText": "Sekmedeki örnek adı ve Syslog uygulaması adı için",
"InstanceName": "Örnek isim",
"InteractiveImportNoFilesFound": "Seçilen klasörde video dosyası bulunamadı",
"InteractiveImportNoQuality": "Seçilen her dosya için kalite seçilmelidir",
"InvalidUILanguage": "Kullanıcı arayüzünüz geçersiz bir dile ayarlanmış, düzeltin ve ayarlarınızı kaydedin",
"ManualGrab": "Manuel Yakalama",
"ManualGrab": "Manuel Alımlarda",
"IndexerDownloadClientHealthCheckMessage": "Geçersiz indirme istemcilerine sahip dizinleyiciler: {indexerNames}.",
"MovieCollectionRootFolderMissingRootHealthCheckMessage": "Film koleksiyonu için eksik kök klasör: {rootFolderInfo}",
"NoImportListsFound": "İçe aktarma listesi bulunamadı",
"MovieGrabbedTooltip": "Film {indexer}'dan alındı ve {downloadClient}'a gönderildi",
"NotificationStatusAllClientHealthCheckMessage": "Arızalar nedeniyle tüm bildirimler kullanılamıyor",
"FormatAgeHour": "saat",
"GrabId": "ID'den Yakala",
"GrabId": "ID'den Al",
"ImportListMissingRoot": "İçe aktarma listeleri için kök klasör eksik: {rootFolderInfo}",
"MonitoredCollectionHelpText": "Bu koleksiyondaki filmlerin otomatik olarak kitaplığa eklenmesini sağlamak için takip edin",
"MovieCollectionFolderMultipleMissingRootsHealthCheckMessage": "Film koleksiyonları için birden fazla kök klasör eksik: {rootFoldersInfo}",
@@ -1379,7 +1379,7 @@
"NotificationsAppriseSettingsNotificationType": "Apprise Bildirim Türü",
"NotificationsAppriseSettingsTagsHelpText": "İsteğe bağlı olarak yalnızca uygun şekilde etiketlenenleri bilgilendirin.",
"NotificationsDiscordSettingsAvatarHelpText": "Bu entegrasyondaki mesajlar için kullanılan avatarı değiştirin",
"NotificationsDiscordSettingsOnGrabFields": "Yakalamalarda",
"NotificationsDiscordSettingsOnGrabFields": "Alımlarda",
"ImportScriptPath": "Komut Dosyası Yolunu İçe Aktar",
"MovieIsPopular": "TMDb'de Popüler Film",
"NotificationStatusSingleClientHealthCheckMessage": "Arızalar nedeniyle bildirimler kullanılamıyor: {notificationNames}",
@@ -1392,7 +1392,7 @@
"IncludeHealthWarnings": "Sağlık Uyarılarını Dahil Et",
"IgnoreDownload": "İndirmeyi Yoksay",
"IgnoreDownloads": "İndirilenleri Yoksay",
"ListQualityProfileHelpText": "Kalite Profili listesi öğeleri şu şekilde eklenecektir:",
"ListQualityProfileHelpText": "Kalite Profili liste öğeleri eklenecektir",
"ListRootFolderHelpText": "Kök Klasör listesi öğeleri eklenecek",
"MustContainHelpText": "Yayın, bu terimlerden en az birini içermelidir (büyük / küçük harfe duyarsız)",
"NewNonExcluded": "Yeni Hariç Tutulmayanlar",
@@ -1405,8 +1405,8 @@
"NotificationsKodiSettingsCleanLibraryHelpText": "Güncellemeden sonra kitaplığı temizle",
"NotificationsMailgunSettingsUseEuEndpointHelpText": "AB MailGun uç noktasını kullanmayı etkinleştirin",
"IndexerJackettAll": "Desteklenmeyen Jackett 'hepsi' uç noktasını kullanan dizinleyiciler: {indexerNames}",
"IndexerSettingsRejectBlocklistedTorrentHashes": "Yakalarken Engellenen Torrent Karmalarını Reddet",
"IndexerSettingsRejectBlocklistedTorrentHashesHelpText": "Bir torrent hash tarafından engellenirse, bazı dizinleyiciler için RSS / Arama sırasında düzgün bir şekilde reddedilmeyebilir, bunun etkinleştirilmesi, torrent yakalandıktan sonra, ancak istemciye gönderilmeden önce reddedilmesine izin verecektir.",
"IndexerSettingsRejectBlocklistedTorrentHashes": "Alırken Engellenen Torrent Karmalarını Reddet",
"IndexerSettingsRejectBlocklistedTorrentHashesHelpText": "Bir torrent hash tarafından engellenirse, bazı dizinleyiciler için RSS / Arama sırasında düzgün bir şekilde reddedilmeyebilir, bunun etkinleştirilmesi, torrent alındıktan sonra, ancak istemciye gönderilmeden önce reddedilmesine izin verecektir.",
"MovieFileRenamedTooltip": "Film dosyası yeniden adlandırıldı",
"MovieFileRenamed": "Film Dosyası Yeniden Adlandırıldı",
"NotificationsCustomScriptValidationFileDoesNotExist": "Dosya bulunmuyor",
@@ -1421,8 +1421,8 @@
"NotificationsKodiSettingsDisplayTime": "Gösterim Süresi",
"NotificationsMailgunSettingsSenderDomain": "Gönderen Alanı",
"IndexerSettingsMultiLanguageReleaseHelpText": "Bu indeksleyicideki çoklu sürümde normalde hangi diller bulunur?",
"IndexerSettingsMultiLanguageRelease": "Çok dil",
"ListWillRefreshEveryInterval": "Liste her {refreshInterval} yenilenecektir",
"IndexerSettingsMultiLanguageRelease": "Çoklu Dil",
"ListWillRefreshEveryInterval": "Liste yenileme periyodu {refreshInterval}dır",
"NotificationsNotifiarrSettingsApiKeyHelpText": "Profilinizdeki API anahtarınız",
"Menu": "Menü",
"MovieSearchResultsLoadError": "Bu film aramasına ilişkin sonuçlar yüklenemiyor. Daha sonra tekrar deneyin",
@@ -1461,7 +1461,7 @@
"FailedToUpdateSettings": "Ayarlar güncellenemedi",
"FormatShortTimeSpanSeconds": "{seconds} saniye",
"ExistsInLibrary": "Kütüphanede Mevcut",
"HealthMessagesInfoBox": "Satırın sonundaki wiki bağlantısını (kitap simgesi) tıklayarak veya [günlüklerinizi]({link}) kontrol ederek bu durum kontrolü mesajlarının nedeni hakkında daha fazla bilgi bulabilirsiniz. Bu mesajları yorumlamakta zorluk yaşıyorsanız aşağıdaki bağlantılardan destek ekibimize ulaşabilirsiniz.",
"HealthMessagesInfoBox": "Satırın sonundaki wiki bağlantısını (kitap simgesi) tıklayarak veya [log kayıtlarınızı]({link}) kontrol ederek bu durum kontrolü mesajlarının nedeni hakkında daha fazla bilgi bulabilirsiniz. Bu mesajları yorumlamakta zorluk yaşıyorsanız aşağıdaki bağlantılardan destek ekibimize ulaşabilirsiniz.",
"HourShorthand": "s",
"ImportScriptPathHelpText": "İçe aktarma için kullanılacak komut dosyasının yolu",
"ImportUsingScriptHelpText": "Bir komut dosyası kullanarak içe aktarmak için dosyaları kopyalayın (ör. kod dönüştürme için)",
@@ -1473,17 +1473,17 @@
"Letterboxd": "Letterboxd",
"MediaInfoFootNote": "Full/AudioLanguages/SubtitleLanguages, dosya adında yer alan dilleri filtrelemenize olanak tanıyan bir `:EN+DE` son ekini destekler. Belirli dilleri hariç tutmak için '-DE'yi kullanın. `+` (örneğin `:EN+`) eklenmesi, hariç tutulan dillere bağlı olarak `[EN]`/`[EN+--]`/`[--]` sonucunu verecektir. Örneğin `{MediaInfo Full:EN+DE}`.",
"NoIndexersFound": "Dizinleyici bulunamadı",
"NotificationsDiscordSettingsOnGrabFieldsHelpText": "Bu 'yakalandı' bildirimi için iletilen alanları değiştirin",
"NotificationsDiscordSettingsOnGrabFieldsHelpText": "Bu 'alındı' bildirimi için iletilen alanları değiştirin",
"NotificationsDiscordSettingsOnManualInteractionFieldsHelpText": "'Manuel Etkileşimlerde' bildirimi için iletilen alanları değiştirin",
"Popularity": "Popülerlik",
"PopularityIndex": "Güncel Popülerlik Endeksi",
"ReleaseProfilesLoadError": "Yayımlama Profilleri yüklenemiyor",
"RemotePathMappingCheckImportFailed": "{appName} filmi içe aktaramadı. Ayrıntılar için günlüklerinizi kontrol edin.",
"RemotePathMappingCheckImportFailed": "{appName} filmi içe aktaramadı. Ayrıntılar için log kayıtlarınızı kontrol edin.",
"OnManualInteractionRequired": "Manuel Etkileşim Gerektiğinde",
"OnHealthRestored": "Sağlığın İyileştirilmesi Hakkında",
"OrganizeNamingPattern": "Adlandırma düzeni: `{standardMovieFormat}`",
"OrganizeNamingPattern": "Adlandırma düzeni: `{episodeFormat}`",
"OrganizeNothingToRename": "Başarılı! İşim bitti, yeniden adlandırılacak dosya yok.",
"OverrideGrabModalTitle": "Geçersiz Kıl ve Yakala - {title}",
"OverrideGrabModalTitle": "Geçersiz Kıl ve Al - {title}",
"QueueLoadError": "Kuyruk yüklenemedi",
"RefreshMonitoredIntervalHelpText": "İndirme istemcilerinden takip edilen indirmelerin ne sıklıkta yenileneceği, minimum 1 dakika",
"Rejections": "Reddedilenler",
@@ -1605,7 +1605,7 @@
"NotificationsValidationUnableToConnectToApi": "{service} API'sine bağlanılamıyor. Sunucu bağlantısı başarısız oldu: ({responseCode}) {exceptionMessage}",
"Recommendation": "Öneri",
"Recommended": "Önerilen",
"RegularExpressionsTutorialLink": "Normal ifadelerle ilgili daha fazla ayrıntıyı [burada](https://www.regular-expressions.info/tutorial.html) bulabilirsiniz.",
"RegularExpressionsTutorialLink": "Düzenli ifadeler hakkında daha fazla ayrıntı [burada]({url}) bulunabilir.",
"NotificationsTwitterSettingsDirectMessageHelpText": "Herkese açık mesaj yerine doğrudan mesaj gönderin",
"NzbgetHistoryItemMessage": "PAR Durumu: {parStatus} - Paketten Çıkarma Durumu: {unpackStatus} - Taşıma Durumu: {moveStatus} - Komut Dosyası Durumu: {scriptStatus} - Silme Durumu: {deleteStatus} - İşaretleme Durumu: {markStatus}",
"OneMinute": "1 dakika",
@@ -1637,7 +1637,7 @@
"Popular": "Popüler",
"PostImportCategory": "İçe Aktarma Sonrası Kategorisi",
"PreferProtocol": "{preferredProtocol}'u tercih edin",
"PreviouslyInstalled": "Önceden Yüklenmiş",
"PreviouslyInstalled": "Daha Önce Kurulmuş",
"Release": "Yayın",
"ReleaseProfileTagMovieHelpText": "Yayımlama profilleri, en az bir eşleşen etikete sahip filmlere uygulanacaktır. Tüm filmlere uygulamak için boş bırakın",
"RestartLater": "Daha sonra yeniden başlayacağım",
@@ -1664,7 +1664,7 @@
"RemoveSelectedItemsQueueMessageText": "{selectedCount} öğeyi kuyruktan kaldırmak istediğinizden emin misiniz?",
"RemoveSelectedBlocklistMessageText": "Seçilen öğeleri engellenenler listesinden kaldırmak istediğinizden emin misiniz?",
"RemoveTagsAutomatically": "Otomatik Etiketlemeyi Kaldır",
"RemotePathMappingsInfo": "Uzak Yol Eşlemeleri çok nadiren gereklidir; {appName} ve indirme istemciniz aynı sistemdeyse yollarınızı eşleştirmek daha iyidir. Daha fazla bilgi için [wiki]({wikiLink}) sayfasına bakın.",
"RemotePathMappingsInfo": "Uzak Yol Eşlemeleri çok nadiren gereklidir, {appName} ve indirme istemciniz aynı sistemdeyse yollarınızı eşleştirmeniz daha iyidir. Daha fazla bilgi için [wiki]({wikiLink}) adresini ziyaret edin",
"ResetDefinitions": "Tanımları Sıfırla",
"UpdateFiltered": "Filtrelenenleri Güncelle",
"RemoveFailedDownloads": "Başarısız İndirmeleri Kaldır",
@@ -1709,7 +1709,7 @@
"ThereWasAnErrorLoadingThisItem": "Bu öğe yüklenirken bir hata oluştu",
"ResetTitles": "Başlıkları Sıfırla",
"SkipRedownloadHelpText": "{appName} uygulamasının bu öğe için alternatif bir yayın indirmeye çalışmasını engeller",
"UpdaterLogFiles": "Güncelleme Günlük Dosyaları",
"UpdaterLogFiles": "Log Kayıt Güncelleyici",
"Unknown": "Bilinmeyen",
"RottenTomatoesRating": "Tomato Derecelendirmesi",
"RootFolderPath": "Kök Klasör Yolu",
@@ -1758,19 +1758,19 @@
"BlocklistFilterHasNoItems": "Seçilen engelleme listesi filtresi hiçbir öğe içermiyor",
"IndexerSettingsSeedTime": "Seed Süresi",
"IndexerSettingsSeedRatio": "Seed Oranı",
"IndexerSettingsSeedTimeHelpText": "Bir torrentin durmadan önce seed edilmesi gereken süre. Boş bırakılırsa indirme istemcisinin varsayılan ayarını kullanır",
"IndexerSettingsSeedRatioHelpText": "Bir torrentin durmadan önce ulaşması gereken oran. Boş bırakılırsa indirme istemcisinin varsayılan değerini kullanır. Oran en az 1,0 olmalı ve indeksleyici kurallarına uygun olmalıdır",
"IndexerSettingsSeedTimeHelpText": "Bir torrentin durdurulmadan önce ulaşması gereken oran, boş bırakıldığında uygulamanın varsayılanı kullanılır",
"IndexerSettingsSeedRatioHelpText": "Bir torrentin durdurulmadan önce ulaşması gereken oran. Boş bırakılırsa indirme istemcisinin varsayılan değerini kullanır. Oran en az 1,0 olmalı ve indeksleyici kurallarına uygun olmalıdır",
"MissingLoadError": "Eksik öğeler yüklenirken hata oluştu",
"MissingNoItems": "Eksik öğe yok",
"MovieDownloaded": "Film İndirildi",
"MovieIsNotAvailable": "Film kullanılamıyor",
"MovieMissingFromDisk": "Film diskte eksik",
"CutoffUnmetLoadError": "Karşılanmamış kesinti öğeleri yüklenirken hata oluştu",
"CutoffUnmetLoadError": "Kesinti karşılanmayan öğeleri yükleme hatası",
"CutoffUnmetNoItems": "Karşılanmayan son öğe yok",
"External": "Harici",
"InteractiveSearchModalHeaderTitle": "İnteraktif Arama - {title}",
"MassSearchCancelWarning": "Bu işlem, {appName} yeniden başlatılmadan veya tüm dizin oluşturucularınız devre dışı bırakılmadan başlatılır ise iptal edilemez.",
"MonitorSelected": "Takip Edilen Seçildi",
"MonitorSelected": "Seçilenleri Bırak",
"MovieIsNotMonitored": "Film takip edilemiyor",
"SearchForAllMissingMovies": "Eksik tüm filmleri arayın",
"SearchForAllMissingMoviesConfirmationCount": "{totalRecords} eksik filmin tamamını aramak istediğinizden emin misiniz?",
@@ -1795,7 +1795,7 @@
"ReleaseDate": "Yayın tarihleri",
"EditSelectedCustomFormats": "Seçilen Özel Formatları Düzenle",
"DeleteSelected": "Seçileni Sil",
"CountCustomFormatsSelected": "{count} özel biçim seçildi",
"CountCustomFormatsSelected": "{count} özel format seçildi",
"NoCustomFormatsFound": "Özel format bulunamadı",
"ProgressBarProgress": "İlerleme Çubuğu %{progress} seviyesinde",
"InvalidMovieInfoLanguageLanguage": "Film Bilgi Diliniz geçersiz bir değere ayarlanmış, düzeltin ve ayarlarınızı kaydedin",
@@ -1816,7 +1816,7 @@
"TodayAt": "Bugün {time}'da",
"YesterdayAt": "Dün saat {time}'da",
"MinimumCustomFormatScoreIncrement": "Minimum Özel Format Puanı Artışı",
"MinimumCustomFormatScoreIncrementHelpText": "{appName}'in bunu bir yükseltme olarak değerlendirmesi için mevcut ve yeni sürümler arasında özel biçim puanında gereken minimum iyileştirme",
"MinimumCustomFormatScoreIncrementHelpText": "{appName}'in bunu bir yükseltme olarak değerlendirmesi için mevcut ve yeni sürümler arasında özel format puanında gereken minimum iyileştirme",
"DayOfWeekAt": "{day}, {time} saatinde",
"Disposition": "Düzen",
"CustomFormatsSpecificationExceptLanguageHelpText": "Seçilen dil dışında herhangi bir dil mevcutsa eşleşir",
@@ -1863,5 +1863,8 @@
"FavoriteFolderRemove": "Favori Klasörü Kaldır",
"FavoriteFolders": "Favori Klasörler",
"Warning": "Uyarı",
"ManageFormats": "Biçimleri Yönet"
"ManageFormats": "Formatları Yönet",
"MetadataKometaDeprecatedSetting": "Kullanım Dışı",
"MetadataKometaDeprecated": "Kometa dosyaları artık oluşturulmayacak, destek v6'da tamamen kaldırılacak",
"NotificationsSettingsWebhookHeaders": "Başlıklar"
}

View File

@@ -1120,7 +1120,7 @@
"RetryingDownloadOn": "Завантаження відкладається до {0} о {1}",
"TablePageSize": "Розмір сторінки",
"BlocklistLoadError": "Не вдалося завантажити список блокувань",
"BlocklistReleaseHelpText": "Забороняє {appName} знову автоматично захопити цей випуск",
"BlocklistReleaseHelpText": "Блокує завантаження цього випуску {appName} через RSS або Автоматичний пошук",
"DelayingDownloadUntil": "Завантаження відкладається до {date} о {time}",
"DeletedReasonUpgrade": "Файл видалено, щоб імпортувати оновлення",
"AutoRedownloadFailed": "Помилка повторного завантаження",
@@ -1289,5 +1289,8 @@
"DefaultNotFoundMessage": "Ви, мабуть, заблукали, тут нічого не видно.",
"Completed": "Завершено",
"Delay": "Затримка",
"DownloadClientUnavailable": "Клієнт завантажувача недоступний"
"DownloadClientUnavailable": "Клієнт завантажувача недоступний",
"CountIndexersSelected": "{count} індексер(-и) обрано",
"AnnouncedMovieAvailabilityDescription": "Фільми вважаються доступними, щойно їх додають у {appName}.",
"CountCustomFormatsSelected": "Користувацькі формати обрано {count}"
}

View File

@@ -518,7 +518,7 @@
"AutoRedownloadFailedHelpText": "自动搜索并尝试下载不同的发布资源",
"AutomaticSearch": "自动搜索",
"ICalShowAsAllDayEventsHelpText": "事件将以全天事件的形式显示在日历中",
"AppDataLocationHealthCheckMessage": "无法更新,以防止在更新时删除 AppData",
"AppDataLocationHealthCheckMessage": "防止在更新时删除 AppData,更新将无法进行",
"Announced": "已公布",
"Analytics": "分析",
"AllMoviesInPathHaveBeenImported": "在 {path} 中的所有电影已被导入",
@@ -584,7 +584,7 @@
"InstallLatest": "安装最新版",
"IndexerStatusCheckSingleClientMessage": "下列索引器因错误不可用:{indexerNames}",
"IndexerStatusCheckAllClientMessage": "所有索引器都因错误不可用",
"IndexerSearchCheckNoInteractiveMessage": "没有索引器开启手动搜索{appName} 将不提供任何手动搜索结果",
"IndexerSearchCheckNoInteractiveMessage": "没有启用交互式搜索的索引器,{appName}将不提供任何交互式搜索结果",
"IndexerRssHealthCheckNoIndexers": "没有索引器开启 RSS 同步,{appName} 将不会自动抓取新版本",
"IndexerLongTermStatusCheckSingleClientMessage": "由于故障超过6小时下列索引器已不可用{indexerNames}",
"IndexerLongTermStatusCheckAllClientMessage": "由于故障超过6小时所有索引器均不可用",
@@ -914,7 +914,7 @@
"MIA": "MIA",
"MegabytesPerMinute": "每分钟MB",
"Mechanism": "机制",
"MappedNetworkDrivesWindowsService": "映射的网络驱动器在作为Windows服务运行时不可用请参阅常见问题解答了解更多信息",
"MappedNetworkDrivesWindowsService": "作为 Windows 服务运行时,映射的网络驱动器不可用请参阅 [FAQ]({url}) 获取更多信息",
"MaintenanceRelease": "维护版本修复错误及其他改进参见Github提交 查看更多详情",
"LogOnly": "只有日志",
"LogLevelTraceHelpTextWarning": "追踪日志只应该暂时启用",
@@ -1712,7 +1712,7 @@
"Logout": "注销",
"Recommendation": "推荐",
"NotificationsSynologyValidationInvalidOs": "必须是 Synology群辉设备",
"NoMovieReleaseDatesAvailable": "TMDb 上未找到此电影的发布日期。",
"NoMovieReleaseDatesAvailable": "[TMDb]({url}) 上未找到此电影的发布日期。",
"NotificationsSlackSettingsChannelHelpText": "覆盖传入 Webhook 的默认渠道(#other-channel",
"NotificationsTelegramSettingsIncludeAppName": "标题中包含 {appName}",
"UnableToImportAutomatically": "无法自动导入",
@@ -1864,5 +1864,6 @@
"OnFileImport": "关于文件导入",
"ManageFormats": "管理格式",
"NotificationsSettingsWebhookHeaders": "标头",
"Warning": "警告"
"Warning": "警告",
"MetadataKometaDeprecatedSetting": "弃用"
}

View File

@@ -1,6 +1,6 @@
{
"About": "关于",
"Add": "添加",
"About": "关于",
"Always": "总是",
"Analytics": "分析",
"Username": "用户名"

View File

@@ -275,7 +275,6 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
movie.Genres = resource.Genres;
movie.Images = resource.Images.Select(MapImage).ToList();
// movie.Genres = resource.Genres;
movie.Recommendations = resource.Recommendations?.Select(r => r.TmdbId).ToList() ?? new List<int>();
// Workaround due to metadata change until cache cleans up

View File

@@ -117,8 +117,7 @@ namespace NzbDrone.Core.Movies
movieMetadata.LastInfoSync = DateTime.UtcNow;
movieMetadata.Runtime = movieInfo.Runtime;
movieMetadata.Ratings = movieInfo.Ratings;
// movie.Genres = movieInfo.Genres;
movieMetadata.Genres = movieInfo.Genres;
movieMetadata.Certification = movieInfo.Certification;
movieMetadata.InCinemas = movieInfo.InCinemas;
movieMetadata.Website = movieInfo.Website;

View File

@@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using NzbDrone.Core.Languages;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Movies;
@@ -20,6 +21,7 @@ namespace NzbDrone.Core.Notifications.Webhook
public List<string> Genres { get; set; }
public List<WebhookImage> Images { get; set; }
public List<string> Tags { get; set; }
public Language OriginalLanguage { get; set; }
public WebhookMovie()
{
@@ -38,6 +40,7 @@ namespace NzbDrone.Core.Notifications.Webhook
Genres = movie.MovieMetadata.Value.Genres;
Images = movie.MovieMetadata.Value.Images.Select(i => new WebhookImage(i)).ToList();
Tags = tags;
OriginalLanguage = movie.MovieMetadata.Value.OriginalLanguage;
}
public WebhookMovie(Movie movie, MovieFile movieFile, List<string> tags)

View File

@@ -1,4 +1,6 @@
using System;
using System.Collections.Generic;
using NzbDrone.Core.Languages;
using NzbDrone.Core.MediaFiles;
namespace NzbDrone.Core.Notifications.Webhook
@@ -21,6 +23,7 @@ namespace NzbDrone.Core.Notifications.Webhook
IndexerFlags = movieFile.IndexerFlags.ToString();
Size = movieFile.Size;
DateAdded = movieFile.DateAdded;
Languages = movieFile.Languages;
if (movieFile.MediaInfo != null)
{
@@ -38,6 +41,7 @@ namespace NzbDrone.Core.Notifications.Webhook
public string IndexerFlags { get; set; }
public long Size { get; set; }
public DateTime DateAdded { get; set; }
public List<Language> Languages { get; set; }
public WebhookMovieFileMediaInfo MediaInfo { get; set; }
public string SourcePath { get; set; }
public string RecycleBinPath { get; set; }

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Core.Languages;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Qualities;
@@ -22,6 +23,7 @@ namespace NzbDrone.Core.Notifications.Webhook
Size = remoteMovie.Release.Size;
CustomFormats = remoteMovie.CustomFormats?.Select(x => x.Name).ToList();
CustomFormatScore = remoteMovie.CustomFormatScore;
Languages = remoteMovie.Languages;
IndexerFlags = Enum.GetValues(typeof(IndexerFlags)).Cast<IndexerFlags>().Where(r => (remoteMovie.Release.IndexerFlags & r) == r).Select(r => r.ToString()).ToList();
}
@@ -33,6 +35,7 @@ namespace NzbDrone.Core.Notifications.Webhook
public long Size { get; set; }
public int CustomFormatScore { get; set; }
public List<string> CustomFormats { get; set; }
public List<Language> Languages { get; set; }
public List<string> IndexerFlags { get; set; }
}
}

View File

@@ -17,8 +17,8 @@ namespace NzbDrone.Core.Queue
public QualityModel Quality { get; set; }
public decimal Size { get; set; }
public string Title { get; set; }
public decimal Sizeleft { get; set; }
public TimeSpan? Timeleft { get; set; }
public decimal SizeLeft { get; set; }
public TimeSpan? TimeLeft { get; set; }
public DateTime? EstimatedCompletionTime { get; set; }
public DateTime? Added { get; set; }
public QueueStatus Status { get; set; }

View File

@@ -62,8 +62,8 @@ namespace NzbDrone.Core.Queue
Quality = trackedDownload.RemoteMovie?.ParsedMovieInfo.Quality ?? new QualityModel(Quality.Unknown),
Title = trackedDownload.DownloadItem.Title,
Size = trackedDownload.DownloadItem.TotalSize,
Sizeleft = trackedDownload.DownloadItem.RemainingSize,
Timeleft = trackedDownload.DownloadItem.RemainingTime,
SizeLeft = trackedDownload.DownloadItem.RemainingSize,
TimeLeft = trackedDownload.DownloadItem.RemainingTime,
Status = Enum.TryParse(trackedDownload.DownloadItem.Status.ToString(), out QueueStatus outValue) ? outValue : QueueStatus.Unknown,
TrackedDownloadStatus = trackedDownload.Status,
TrackedDownloadState = trackedDownload.State,
@@ -82,9 +82,9 @@ namespace NzbDrone.Core.Queue
queue.Id = HashConverter.GetHashInt31($"trackedDownload-{trackedDownload.DownloadClient}-{trackedDownload.DownloadItem.DownloadId}");
if (queue.Timeleft.HasValue)
if (queue.TimeLeft.HasValue)
{
queue.EstimatedCompletionTime = DateTime.UtcNow.Add(queue.Timeleft.Value);
queue.EstimatedCompletionTime = DateTime.UtcNow.Add(queue.TimeLeft.Value);
}
return queue;

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