mirror of
https://github.com/Radarr/Radarr.git
synced 2026-04-16 21:15:33 -04:00
Compare commits
30 Commits
v5.16.0.94
...
v5.16.2.95
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
23fce4bf2e | ||
|
|
64fd8552f8 | ||
|
|
e016410c10 | ||
|
|
bea943adf8 | ||
|
|
9780d20f8a | ||
|
|
62722d45b0 | ||
|
|
27dd8e8cd5 | ||
|
|
6c47ede76b | ||
|
|
7b9562bb38 | ||
|
|
8b0b7c1cb0 | ||
|
|
7ebd341cd6 | ||
|
|
6c85f166ff | ||
|
|
45aabce107 | ||
|
|
0caa793df4 | ||
|
|
9a107cc8d7 | ||
|
|
a6d727fe2a | ||
|
|
01a53d3624 | ||
|
|
348c29c9d7 | ||
|
|
64739712c6 | ||
|
|
6ac9cca953 | ||
|
|
a2b38c5b7d | ||
|
|
3cc4105d71 | ||
|
|
3449a5d3fe | ||
|
|
5bac157d36 | ||
|
|
114d260f42 | ||
|
|
617b9c5d35 | ||
|
|
ba4ccbb0bd | ||
|
|
b845268b3d | ||
|
|
0fee552074 | ||
|
|
828b994ef4 |
@@ -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)'
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -91,6 +91,10 @@
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.genres {
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
.links {
|
||||
margin-left: 5px;
|
||||
pointer-events: all;
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
6
frontend/src/App/State/MetadataAppState.ts
Normal file
6
frontend/src/App/State/MetadataAppState.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { AppSectionProviderState } from 'App/State/AppSectionState';
|
||||
import Metadata from 'typings/Metadata';
|
||||
|
||||
interface MetadataAppState extends AppSectionProviderState<Metadata> {}
|
||||
|
||||
export default MetadataAppState;
|
||||
@@ -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;
|
||||
|
||||
@@ -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))
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
39
frontend/src/Movie/MovieGenres.tsx
Normal file
39
frontend/src/Movie/MovieGenres.tsx
Normal 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;
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
@@ -0,0 +1,5 @@
|
||||
.message {
|
||||
composes: alert from '~Components/Alert.css';
|
||||
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
7
frontend/src/Settings/Metadata/Metadata/EditMetadataModalContent.css.d.ts
vendored
Normal file
7
frontend/src/Settings/Metadata/Metadata/EditMetadataModalContent.css.d.ts
vendored
Normal 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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
107
frontend/src/Settings/Metadata/Metadata/Metadata.tsx
Normal file
107
frontend/src/Settings/Metadata/Metadata/Metadata.tsx
Normal 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;
|
||||
@@ -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;
|
||||
52
frontend/src/Settings/Metadata/Metadata/Metadatas.tsx
Normal file
52
frontend/src/Settings/Metadata/Metadata/Metadatas.tsx
Normal 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;
|
||||
@@ -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);
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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;
|
||||
168
frontend/src/Store/Selectors/selectSettings.ts
Normal file
168
frontend/src/Store/Selectors/selectSettings.ts
Normal 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;
|
||||
@@ -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';
|
||||
|
||||
@@ -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}>
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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} />);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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[];
|
||||
}
|
||||
|
||||
|
||||
23
frontend/src/typings/Field.ts
Normal file
23
frontend/src/typings/Field.ts
Normal 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;
|
||||
@@ -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[];
|
||||
}
|
||||
|
||||
|
||||
@@ -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[];
|
||||
}
|
||||
|
||||
|
||||
7
frontend/src/typings/Metadata.ts
Normal file
7
frontend/src/typings/Metadata.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import Provider from './Provider';
|
||||
|
||||
interface Metadata extends Provider {
|
||||
enable: boolean;
|
||||
}
|
||||
|
||||
export default Metadata;
|
||||
@@ -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[];
|
||||
}
|
||||
|
||||
|
||||
20
frontend/src/typings/Provider.ts
Normal file
20
frontend/src/typings/Provider.ts
Normal 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;
|
||||
@@ -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>[];
|
||||
};
|
||||
|
||||
1
frontend/typings/Globals.d.ts
vendored
1
frontend/typings/Globals.d.ts
vendored
@@ -7,5 +7,6 @@ interface Window {
|
||||
theme: string;
|
||||
urlBase: string;
|
||||
version: string;
|
||||
isProduction: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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")]
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
16
src/NzbDrone.Core/Datastore/DatabaseVersionParser.cs
Normal file
16
src/NzbDrone.Core/Datastore/DatabaseVersionParser.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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"))
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
34
src/NzbDrone.Core/HealthCheck/Checks/MetadataCheck.cs
Normal file
34
src/NzbDrone.Core/HealthCheck/Checks/MetadataCheck.cs
Normal 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 på 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
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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": "הוסף תנאי"
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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}"
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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ı Açı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"
|
||||
}
|
||||
|
||||
@@ -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}"
|
||||
}
|
||||
|
||||
@@ -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": "弃用"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"About": "关于",
|
||||
"Add": "添加",
|
||||
"About": "关于",
|
||||
"Always": "总是",
|
||||
"Analytics": "分析",
|
||||
"Username": "用户名"
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user