diff --git a/frontend/src/Components/Filter/Builder/FilterBuilderRow.js b/frontend/src/Components/Filter/Builder/FilterBuilderRow.js index 491829434..ee92b395d 100644 --- a/frontend/src/Components/Filter/Builder/FilterBuilderRow.js +++ b/frontend/src/Components/Filter/Builder/FilterBuilderRow.js @@ -206,9 +206,11 @@ class FilterBuilderRow extends Component { const selectedFilterBuilderProp = this.selectedFilterBuilderProp; const keyOptions = filterBuilderProps.map((availablePropFilter) => { + const { name, label } = availablePropFilter; + return { - key: availablePropFilter.name, - value: availablePropFilter.label + key: name, + value: typeof label === 'function' ? label() : label }; }).sort((a, b) => a.value.localeCompare(b.value)); diff --git a/frontend/src/Components/Form/SelectInput.js b/frontend/src/Components/Form/SelectInput.js index 0a60ffe1e..553501afc 100644 --- a/frontend/src/Components/Form/SelectInput.js +++ b/frontend/src/Components/Form/SelectInput.js @@ -61,7 +61,7 @@ class SelectInput extends Component { value={key} {...otherOptionProps} > - {optionValue} + {typeof optionValue === 'function' ? optionValue() : optionValue} ); }) @@ -75,7 +75,7 @@ SelectInput.propTypes = { className: PropTypes.string, disabledClassName: PropTypes.string, name: PropTypes.string.isRequired, - value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, + value: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.func]).isRequired, values: PropTypes.arrayOf(PropTypes.object).isRequired, isDisabled: PropTypes.bool, hasError: PropTypes.bool, diff --git a/frontend/src/Components/Menu/FilterMenuContent.js b/frontend/src/Components/Menu/FilterMenuContent.js index 09313cc55..5d978c9ca 100644 --- a/frontend/src/Components/Menu/FilterMenuContent.js +++ b/frontend/src/Components/Menu/FilterMenuContent.js @@ -32,7 +32,7 @@ class FilterMenuContent extends Component { selectedFilterKey={selectedFilterKey} onPress={onFilterSelect} > - {filter.label} + {typeof filter.label === 'function' ? filter.label() : filter.label} ); }) diff --git a/frontend/src/Components/Table/Column.ts b/frontend/src/Components/Table/Column.ts index 8c2122c65..31a696df7 100644 --- a/frontend/src/Components/Table/Column.ts +++ b/frontend/src/Components/Table/Column.ts @@ -1,8 +1,10 @@ import React from 'react'; +type PropertyFunction = () => T; + interface Column { name: string; - label: string | React.ReactNode; + label: string | PropertyFunction | React.ReactNode; columnLabel?: string; isSortable?: boolean; isVisible: boolean; diff --git a/frontend/src/Components/Table/Table.js b/frontend/src/Components/Table/Table.js index befc8219a..8afbf9ea0 100644 --- a/frontend/src/Components/Table/Table.js +++ b/frontend/src/Components/Table/Table.js @@ -107,7 +107,7 @@ function Table(props) { {...getTableHeaderCellProps(otherProps)} {...column} > - {column.label} + {typeof column.label === 'function' ? column.label() : column.label} ); }) diff --git a/frontend/src/Components/Table/TableHeaderCell.js b/frontend/src/Components/Table/TableHeaderCell.js index 21766978b..b0ed5c571 100644 --- a/frontend/src/Components/Table/TableHeaderCell.js +++ b/frontend/src/Components/Table/TableHeaderCell.js @@ -30,6 +30,7 @@ class TableHeaderCell extends Component { const { className, name, + label, columnLabel, isSortable, isVisible, @@ -53,7 +54,8 @@ class TableHeaderCell extends Component { {...otherProps} component="th" className={className} - title={columnLabel} + label={typeof label === 'function' ? label() : label} + title={typeof columnLabel === 'function' ? columnLabel() : columnLabel} onPress={this.onPress} > {children} @@ -77,7 +79,8 @@ class TableHeaderCell extends Component { TableHeaderCell.propTypes = { className: PropTypes.string, name: PropTypes.string.isRequired, - columnLabel: PropTypes.string, + label: PropTypes.oneOfType([PropTypes.string, PropTypes.func, PropTypes.node]), + columnLabel: PropTypes.oneOfType([PropTypes.string, PropTypes.func]), isSortable: PropTypes.bool, isVisible: PropTypes.bool, isModifiable: PropTypes.bool, diff --git a/frontend/src/Components/Table/TableOptions/TableOptionsColumn.js b/frontend/src/Components/Table/TableOptions/TableOptionsColumn.js index 2d91c7c63..402ef5ae1 100644 --- a/frontend/src/Components/Table/TableOptions/TableOptionsColumn.js +++ b/frontend/src/Components/Table/TableOptions/TableOptionsColumn.js @@ -35,7 +35,7 @@ function TableOptionsColumn(props) { isDisabled={isModifiable === false} onChange={onVisibleChange} /> - {label} + {typeof label === 'function' ? label() : label} { @@ -56,7 +56,7 @@ function TableOptionsColumn(props) { TableOptionsColumn.propTypes = { name: PropTypes.string.isRequired, - label: PropTypes.string.isRequired, + label: PropTypes.oneOfType([PropTypes.string, PropTypes.func]).isRequired, isVisible: PropTypes.bool.isRequired, isModifiable: PropTypes.bool.isRequired, index: PropTypes.number.isRequired, diff --git a/frontend/src/Components/Table/TableOptions/TableOptionsColumnDragSource.js b/frontend/src/Components/Table/TableOptions/TableOptionsColumnDragSource.js index 100559660..77d18463f 100644 --- a/frontend/src/Components/Table/TableOptions/TableOptionsColumnDragSource.js +++ b/frontend/src/Components/Table/TableOptions/TableOptionsColumnDragSource.js @@ -112,7 +112,7 @@ class TableOptionsColumnDragSource extends Component { - {label} + {typeof label === 'function' ? label() : label} ); })} diff --git a/frontend/src/Store/Actions/blocklistActions.js b/frontend/src/Store/Actions/blocklistActions.js index 95c30d712..f341b72aa 100644 --- a/frontend/src/Store/Actions/blocklistActions.js +++ b/frontend/src/Store/Actions/blocklistActions.js @@ -4,6 +4,7 @@ import { sortDirections } from 'Helpers/Props'; import { createThunk, handleThunks } from 'Store/thunks'; import createAjaxRequest from 'Utilities/createAjaxRequest'; import serverSideCollectionHandlers from 'Utilities/serverSideCollectionHandlers'; +import translate from 'Utilities/String/translate'; import { set, updateItem } from './baseActions'; import createHandleActions from './Creators/createHandleActions'; import createRemoveItemHandler from './Creators/createRemoveItemHandler'; @@ -32,47 +33,47 @@ export const defaultState = { columns: [ { name: 'series.sortTitle', - label: 'Series Title', + label: () => translate('SeriesTitle'), isSortable: true, isVisible: true }, { name: 'sourceTitle', - label: 'Source Title', + label: () => translate('SourceTitle'), isSortable: true, isVisible: true }, { name: 'languages', - label: 'Languages', + label: () => translate('Languages'), isVisible: false }, { name: 'quality', - label: 'Quality', + label: () => translate('Quality'), isVisible: true }, { name: 'customFormats', - label: 'Formats', + label: () => translate('Formats'), isSortable: false, isVisible: true }, { name: 'date', - label: 'Date', + label: () => translate('Date'), isSortable: true, isVisible: true }, { name: 'indexer', - label: 'Indexer', + label: () => translate('Indexer'), isSortable: true, isVisible: false }, { name: 'actions', - columnLabel: 'Actions', + columnLabel: () => translate('Actions'), isVisible: true, isModifiable: false } diff --git a/frontend/src/Store/Actions/calendarActions.js b/frontend/src/Store/Actions/calendarActions.js index 106de58bf..0e0febea6 100644 --- a/frontend/src/Store/Actions/calendarActions.js +++ b/frontend/src/Store/Actions/calendarActions.js @@ -8,6 +8,7 @@ import { filterBuilderTypes, filterBuilderValueTypes, filterTypes } from 'Helper import { createThunk, handleThunks } from 'Store/thunks'; import createAjaxRequest from 'Utilities/createAjaxRequest'; import findSelectedFilters from 'Utilities/Filter/findSelectedFilters'; +import translate from 'Utilities/String/translate'; import { set, update } from './baseActions'; import { executeCommandHelper } from './commandActions'; import createHandleActions from './Creators/createHandleActions'; @@ -56,7 +57,7 @@ export const defaultState = { filters: [ { key: 'all', - label: 'All', + label: () => translate('All'), filters: [ { key: 'unmonitored', @@ -67,7 +68,7 @@ export const defaultState = { }, { key: 'monitored', - label: 'Monitored Only', + label: () => translate('MonitoredOnly'), filters: [ { key: 'unmonitored', @@ -81,13 +82,13 @@ export const defaultState = { filterBuilderProps: [ { name: 'unmonitored', - label: 'Include Unmonitored', + label: () => translate('IncludeUnmonitored'), type: filterBuilderTypes.EQUAL, valueType: filterBuilderValueTypes.BOOL }, { name: 'tags', - label: 'Tags', + label: () => translate('Tags'), type: filterBuilderTypes.CONTAINS, valueType: filterBuilderValueTypes.TAG } diff --git a/frontend/src/Store/Actions/episodeActions.js b/frontend/src/Store/Actions/episodeActions.js index b84dfe168..628703752 100644 --- a/frontend/src/Store/Actions/episodeActions.js +++ b/frontend/src/Store/Actions/episodeActions.js @@ -33,7 +33,7 @@ export const defaultState = { columns: [ { name: 'monitored', - columnLabel: 'Monitored', + columnLabel: () => translate('Monitored'), isVisible: true, isModifiable: false }, @@ -44,94 +44,91 @@ export const defaultState = { }, { name: 'title', - label: 'Title', + label: () => translate('Title'), isVisible: true }, { name: 'path', - label: 'Path', + label: () => translate('Path'), isVisible: false }, { name: 'relativePath', - label: 'Relative Path', + label: () => translate('RelativePath'), isVisible: false }, { name: 'airDateUtc', - label: 'Air Date', + label: () => translate('AirDate'), isVisible: true }, { name: 'runtime', - label: 'Runtime', + label: () => translate('Runtime'), isVisible: false }, { name: 'languages', - label: 'Languages', + label: () => translate('Languages'), isVisible: false }, { name: 'audioInfo', - label: 'Audio Info', + label: () => translate('AudioInfo'), isVisible: false }, { name: 'videoCodec', - label: 'Video Codec', + label: () => translate('VideoCodec'), isVisible: false }, { name: 'videoDynamicRangeType', - label: 'Video Dynamic Range', + label: () => translate('VideoDynamicRange'), isVisible: false }, { name: 'audioLanguages', - label: 'Audio Languages', + label: () => translate('AudioLanguages'), isVisible: false }, { name: 'subtitleLanguages', - label: 'Subtitle Languages', + label: () => translate('SubtitleLanguages'), isVisible: false }, { name: 'size', - label: 'Size', + label: () => translate('Size'), isVisible: false }, { name: 'releaseGroup', - label: 'Release Group', + label: () => translate('ReleaseGroup'), isVisible: false }, { name: 'customFormats', - label: 'Formats', + label: () => translate('Formats'), isVisible: false }, { name: 'customFormatScore', - get columnLabel() { - return translate('CustomFormatScore'); - }, + columnLabel: () => translate('CustomFormatScore'), label: React.createElement(Icon, { name: icons.SCORE, title: () => translate('CustomFormatScore') - }), isVisible: false }, { name: 'status', - label: 'Status', + label: () => translate('Status'), isVisible: true }, { name: 'actions', - columnLabel: 'Actions', + columnLabel: () => translate('Actions'), isVisible: true, isModifiable: false } diff --git a/frontend/src/Store/Actions/historyActions.js b/frontend/src/Store/Actions/historyActions.js index 5b6ef8851..26a2bb8a9 100644 --- a/frontend/src/Store/Actions/historyActions.js +++ b/frontend/src/Store/Actions/historyActions.js @@ -5,6 +5,7 @@ import { filterTypes, icons, sortDirections } from 'Helpers/Props'; import { createThunk, handleThunks } from 'Store/thunks'; import createAjaxRequest from 'Utilities/createAjaxRequest'; import serverSideCollectionHandlers from 'Utilities/serverSideCollectionHandlers'; +import translate from 'Utilities/String/translate'; import { updateItem } from './baseActions'; import createHandleActions from './Creators/createHandleActions'; import createServerSideCollectionHandlers from './Creators/createServerSideCollectionHandlers'; @@ -31,80 +32,80 @@ export const defaultState = { columns: [ { name: 'eventType', - columnLabel: 'Event Type', + columnLabel: () => translate('EventType'), isVisible: true, isModifiable: false }, { name: 'series.sortTitle', - label: 'Series', + label: () => translate('Series'), isSortable: true, isVisible: true }, { name: 'episode', - label: 'Episode', + label: () => translate('Episode'), isVisible: true }, { name: 'episodes.title', - label: 'Episode Title', + label: () => translate('EpisodeTitle'), isVisible: true }, { name: 'languages', - label: 'Languages', + label: () => translate('Languages'), isVisible: false }, { name: 'quality', - label: 'Quality', + label: () => translate('Quality'), isVisible: true }, { name: 'customFormats', - label: 'Formats', + label: () => translate('Formats'), isSortable: false, isVisible: true }, { name: 'date', - label: 'Date', + label: () => translate('Date'), isSortable: true, isVisible: true }, { name: 'downloadClient', - label: 'Download Client', + label: () => translate('DownloadClient'), isVisible: false }, { name: 'indexer', - label: 'Indexer', + label: () => translate('Indexer'), isVisible: false }, { name: 'releaseGroup', - label: 'Release Group', + label: () => translate('ReleaseGroup'), isVisible: false }, { name: 'sourceTitle', - label: 'Source Title', + label: () => translate('SourceTitle'), isVisible: false }, { name: 'customFormatScore', - columnLabel: 'Custom Format Score', + columnLabel: () => translate('CustomFormatScore'), label: React.createElement(Icon, { name: icons.SCORE, - title: 'Custom format score' + title: () => translate('CustomFormatScore') }), isVisible: false }, { name: 'details', - columnLabel: 'Details', + columnLabel: () => translate('Details'), isVisible: true, isModifiable: false } @@ -115,12 +116,12 @@ export const defaultState = { filters: [ { key: 'all', - label: 'All', + label: () => translate('All'), filters: [] }, { key: 'grabbed', - label: 'Grabbed', + label: () => translate('Grabbed'), filters: [ { key: 'eventType', @@ -131,7 +132,7 @@ export const defaultState = { }, { key: 'imported', - label: 'Imported', + label: () => translate('Imported'), filters: [ { key: 'eventType', @@ -142,7 +143,7 @@ export const defaultState = { }, { key: 'failed', - label: 'Failed', + label: () => translate('Failed'), filters: [ { key: 'eventType', @@ -153,7 +154,7 @@ export const defaultState = { }, { key: 'deleted', - label: 'Deleted', + label: () => translate('Deleted'), filters: [ { key: 'eventType', @@ -164,7 +165,7 @@ export const defaultState = { }, { key: 'renamed', - label: 'Renamed', + label: () => translate('Renamed'), filters: [ { key: 'eventType', @@ -175,7 +176,7 @@ export const defaultState = { }, { key: 'ignored', - label: 'Ignored', + label: () => translate('Ignored'), filters: [ { key: 'eventType', diff --git a/frontend/src/Store/Actions/queueActions.js b/frontend/src/Store/Actions/queueActions.js index e4fbb3d90..6c4ee38cc 100644 --- a/frontend/src/Store/Actions/queueActions.js +++ b/frontend/src/Store/Actions/queueActions.js @@ -7,6 +7,7 @@ import { icons, sortDirections } from 'Helpers/Props'; import { createThunk, handleThunks } from 'Store/thunks'; import createAjaxRequest from 'Utilities/createAjaxRequest'; import serverSideCollectionHandlers from 'Utilities/serverSideCollectionHandlers'; +import translate from 'Utilities/String/translate'; import { set, updateItem } from './baseActions'; import createFetchHandler from './Creators/createFetchHandler'; import createHandleActions from './Creators/createHandleActions'; @@ -59,113 +60,113 @@ export const defaultState = { columns: [ { name: 'status', - columnLabel: 'Status', + columnLabel: () => translate('Status'), isSortable: true, isVisible: true, isModifiable: false }, { name: 'series.sortTitle', - label: 'Series', + label: () => translate('Series'), isSortable: true, isVisible: true }, { name: 'episode', - label: 'Episode', + label: () => translate('Episode'), isSortable: true, isVisible: true }, { name: 'episodes.title', - label: 'Episode Title', + label: () => translate('EpisodeTitle'), isSortable: true, isVisible: true }, { name: 'episodes.airDateUtc', - label: 'Episode Air Date', + label: () => translate('EpisodeAirDate'), isSortable: true, isVisible: false }, { name: 'languages', - label: 'Languages', + label: () => translate('Languages'), isSortable: true, isVisible: false }, { name: 'quality', - label: 'Quality', + label: () => translate('Quality'), isSortable: true, isVisible: true }, { name: 'customFormats', - label: 'Formats', + label: () => translate('Formats'), isSortable: false, isVisible: true }, { name: 'customFormatScore', - columnLabel: 'Custom Format Score', + columnLabel: () => translate('CustomFormatScore'), label: React.createElement(Icon, { name: icons.SCORE, - title: 'Custom format score' + title: () => translate('CustomFormatScore') }), isVisible: false }, { name: 'protocol', - label: 'Protocol', + label: () => translate('Protocol'), isSortable: true, isVisible: false }, { name: 'indexer', - label: 'Indexer', + label: () => translate('Indexer'), isSortable: true, isVisible: false }, { name: 'downloadClient', - label: 'Download Client', + label: () => translate('DownloadClient'), isSortable: true, isVisible: false }, { name: 'title', - label: 'Release Title', + label: () => translate('ReleaseTitle'), isSortable: true, isVisible: false }, { name: 'size', - label: 'Size', + label: () => translate('Size'), isSortable: true, isVisibile: false }, { name: 'outputPath', - label: 'Output Path', + label: () => translate('OutputPath'), isSortable: false, isVisible: false }, { name: 'estimatedCompletionTime', - label: 'Time Left', + label: () => translate('TimeLeft'), isSortable: true, isVisible: true }, { name: 'progress', - label: 'Progress', + label: () => translate('Progress'), isSortable: true, isVisible: true }, { name: 'actions', - columnLabel: 'Actions', + columnLabel: () => translate('Actions'), isVisible: true, isModifiable: false } diff --git a/frontend/src/Store/Actions/releaseActions.js b/frontend/src/Store/Actions/releaseActions.js index 7163f93c6..b14bc19e4 100644 --- a/frontend/src/Store/Actions/releaseActions.js +++ b/frontend/src/Store/Actions/releaseActions.js @@ -3,6 +3,7 @@ import { filterBuilderTypes, filterBuilderValueTypes, filterTypePredicates, filt import { createThunk, handleThunks } from 'Store/thunks'; import sortByName from 'Utilities/Array/sortByName'; import createAjaxRequest from 'Utilities/createAjaxRequest'; +import translate from 'Utilities/String/translate'; import createFetchHandler from './Creators/createFetchHandler'; import createHandleActions from './Creators/createHandleActions'; import createSetClientSideCollectionFilterReducer from './Creators/Reducers/createSetClientSideCollectionFilterReducer'; @@ -62,12 +63,12 @@ export const defaultState = { filters: [ { key: 'all', - label: 'All', + label: () => translate('All'), filters: [] }, { key: 'season-pack', - label: 'Season Pack', + label: () => translate('SeasonPack'), filters: [ { key: 'fullSeason', @@ -78,7 +79,7 @@ export const defaultState = { }, { key: 'not-season-pack', - label: 'Not Season Pack', + label: () => translate('NotSeasonPack'), filters: [ { key: 'fullSeason', @@ -173,51 +174,51 @@ export const defaultState = { filterBuilderProps: [ { name: 'title', - label: 'Title', + label: () => translate('Title'), type: filterBuilderTypes.STRING }, { name: 'age', - label: 'Age', + label: () => translate('Age'), type: filterBuilderTypes.NUMBER }, { name: 'protocol', - label: 'Protocol', + label: () => translate('Protocol'), type: filterBuilderTypes.EXACT, valueType: filterBuilderValueTypes.PROTOCOL }, { name: 'indexerId', - label: 'Indexer', + label: () => translate('Indexer'), type: filterBuilderTypes.EXACT, valueType: filterBuilderValueTypes.INDEXER }, { name: 'size', - label: 'Size', + label: () => translate('Size'), type: filterBuilderTypes.NUMBER, valueType: filterBuilderValueTypes.BYTES }, { name: 'seeders', - label: 'Seeders', + label: () => translate('Seeders'), type: filterBuilderTypes.NUMBER }, { name: 'peers', - label: 'Peers', + label: () => translate('Peers'), type: filterBuilderTypes.NUMBER }, { name: 'quality', - label: 'Quality', + label: () => translate('Quality'), type: filterBuilderTypes.EXACT, valueType: filterBuilderValueTypes.QUALITY }, { name: 'languages', - label: 'Languages', + label: () => translate('Languages'), type: filterBuilderTypes.ARRAY, optionsSelector: function(items) { const genreList = items.reduce((acc, release) => { @@ -236,17 +237,17 @@ export const defaultState = { }, { name: 'customFormatScore', - label: 'Custom Format Score', + label: () => translate('CustomFormatScore'), type: filterBuilderTypes.NUMBER }, { name: 'rejectionCount', - label: 'Rejection Count', + label: () => translate('RejectionCount'), type: filterBuilderTypes.NUMBER }, { name: 'fullSeason', - label: 'Season Pack', + label: () => translate('SeasonPack'), type: filterBuilderTypes.EXACT, valueType: filterBuilderValueTypes.BOOL } diff --git a/frontend/src/Store/Actions/seriesActions.js b/frontend/src/Store/Actions/seriesActions.js index 5d8ff2995..971665152 100644 --- a/frontend/src/Store/Actions/seriesActions.js +++ b/frontend/src/Store/Actions/seriesActions.js @@ -6,6 +6,7 @@ import { createThunk, handleThunks } from 'Store/thunks'; import sortByName from 'Utilities/Array/sortByName'; import createAjaxRequest from 'Utilities/createAjaxRequest'; import dateFilterPredicate from 'Utilities/Date/dateFilterPredicate'; +import translate from 'Utilities/String/translate'; import { set, updateItem } from './baseActions'; import createFetchHandler from './Creators/createFetchHandler'; import createHandleActions from './Creators/createHandleActions'; @@ -29,12 +30,12 @@ export const section = 'series'; export const filters = [ { key: 'all', - label: 'All', + label: () => translate('All'), filters: [] }, { key: 'monitored', - label: 'Monitored Only', + label: () => translate('MonitoredOnly'), filters: [ { key: 'monitored', @@ -45,7 +46,7 @@ export const filters = [ }, { key: 'unmonitored', - label: 'Unmonitored Only', + label: () => translate('UnmonitoredOnly'), filters: [ { key: 'monitored', @@ -56,7 +57,7 @@ export const filters = [ }, { key: 'continuing', - label: 'Continuing Only', + label: () => translate('ContinuingOnly'), filters: [ { key: 'status', @@ -67,7 +68,7 @@ export const filters = [ }, { key: 'ended', - label: 'Ended Only', + label: () => translate('EndedOnly'), filters: [ { key: 'status', @@ -78,7 +79,7 @@ export const filters = [ }, { key: 'missing', - label: 'Missing Episodes', + label: () => translate('MissingEpisodes'), filters: [ { key: 'missing', @@ -194,25 +195,25 @@ export const filterPredicates = { export const filterBuilderProps = [ { name: 'monitored', - label: 'Monitored', + label: () => translate('Monitored'), type: filterBuilderTypes.EXACT, valueType: filterBuilderValueTypes.BOOL }, { name: 'status', - label: 'Status', + label: () => translate('Status'), type: filterBuilderTypes.EXACT, valueType: filterBuilderValueTypes.SERIES_STATUS }, { name: 'seriesType', - label: 'Type', + label: () => translate('Type'), type: filterBuilderTypes.EXACT, valueType: filterBuilderValueTypes.SERIES_TYPES }, { name: 'network', - label: 'Network', + label: () => translate('Network'), type: filterBuilderTypes.STRING, optionsSelector: function(items) { const tagList = items.reduce((acc, series) => { @@ -231,57 +232,57 @@ export const filterBuilderProps = [ }, { name: 'qualityProfileId', - label: 'Quality Profile', + label: () => translate('QualityProfile'), type: filterBuilderTypes.EXACT, valueType: filterBuilderValueTypes.QUALITY_PROFILE }, { name: 'nextAiring', - label: 'Next Airing', + label: () => translate('NextAiring'), type: filterBuilderTypes.DATE, valueType: filterBuilderValueTypes.DATE }, { name: 'previousAiring', - label: 'Previous Airing', + label: () => translate('PreviousAiring'), type: filterBuilderTypes.DATE, valueType: filterBuilderValueTypes.DATE }, { name: 'added', - label: 'Added', + label: () => translate('Added'), type: filterBuilderTypes.DATE, valueType: filterBuilderValueTypes.DATE }, { name: 'seasonCount', - label: 'Season Count', + label: () => translate('SeasonCount'), type: filterBuilderTypes.NUMBER }, { name: 'episodeProgress', - label: 'Episode Progress', + label: () => translate('EpisodeProgress'), type: filterBuilderTypes.NUMBER }, { name: 'path', - label: 'Path', + label: () => translate('Path'), type: filterBuilderTypes.STRING }, { name: 'rootFolderPath', - label: 'Root Folder Path', + label: () => translate('RootFolderPath'), type: filterBuilderTypes.EXACT }, { name: 'sizeOnDisk', - label: 'Size on Disk', + label: () => translate('SizeOnDisk'), type: filterBuilderTypes.NUMBER, valueType: filterBuilderValueTypes.BYTES }, { name: 'genres', - label: 'Genres', + label: () => translate('Genres'), type: filterBuilderTypes.ARRAY, optionsSelector: function(items) { const tagList = items.reduce((acc, series) => { @@ -300,7 +301,7 @@ export const filterBuilderProps = [ }, { name: 'originalLanguage', - label: 'Original Language', + label: () => translate('OriginalLanguage'), type: filterBuilderTypes.EXACT, optionsSelector: function(items) { const languageList = items.reduce((acc, series) => { @@ -319,33 +320,33 @@ export const filterBuilderProps = [ }, { name: 'releaseGroups', - label: 'Release Groups', + label: () => translate('ReleaseGroups'), type: filterBuilderTypes.ARRAY }, { name: 'ratings', - label: 'Rating', + label: () => translate('Rating'), type: filterBuilderTypes.NUMBER }, { name: 'certification', - label: 'Certification', + label: () => translate('Certification'), type: filterBuilderTypes.EXACT }, { name: 'tags', - label: 'Tags', + label: () => translate('Tags'), type: filterBuilderTypes.ARRAY, valueType: filterBuilderValueTypes.TAG }, { name: 'useSceneNumbering', - label: 'Scene Numbering', + label: () => translate('SceneNumbering'), type: filterBuilderTypes.EXACT }, { name: 'hasMissingSeason', - label: 'Has Missing Season', + label: () => translate('HasMissingSeason'), type: filterBuilderTypes.EXACT } ]; diff --git a/frontend/src/Store/Actions/seriesIndexActions.js b/frontend/src/Store/Actions/seriesIndexActions.js index a8e60199f..65c20870f 100644 --- a/frontend/src/Store/Actions/seriesIndexActions.js +++ b/frontend/src/Store/Actions/seriesIndexActions.js @@ -1,6 +1,7 @@ import moment from 'moment'; import { createAction } from 'redux-actions'; import { sortDirections } from 'Helpers/Props'; +import translate from 'Utilities/String/translate'; import createHandleActions from './Creators/createHandleActions'; import createSetClientSideCollectionFilterReducer from './Creators/Reducers/createSetClientSideCollectionFilterReducer'; import createSetClientSideCollectionSortReducer from './Creators/Reducers/createSetClientSideCollectionSortReducer'; @@ -53,147 +54,147 @@ export const defaultState = { columns: [ { name: 'status', - columnLabel: 'Status', + columnLabel: () => translate('Status'), isSortable: true, isVisible: true, isModifiable: false }, { name: 'sortTitle', - label: 'Series Title', + label: () => translate('SeriesTitle'), isSortable: true, isVisible: true, isModifiable: false }, { name: 'seriesType', - label: 'Type', + label: () => translate('Type'), isSortable: true, isVisible: false }, { name: 'network', - label: 'Network', + label: () => translate('Network'), isSortable: true, isVisible: true }, { name: 'qualityProfileId', - label: 'Quality Profile', + label: () => translate('QualityProfile'), isSortable: true, isVisible: true }, { name: 'nextAiring', - label: 'Next Airing', + label: () => translate('NextAiring'), isSortable: true, isVisible: true }, { name: 'previousAiring', - label: 'Previous Airing', + label: () => translate('PreviousAiring'), isSortable: true, isVisible: false }, { name: 'originalLanguage', - label: 'Original Language', + label: () => translate('OriginalLanguage'), isSortable: true, isVisible: false }, { name: 'added', - label: 'Added', + label: () => translate('Added'), isSortable: true, isVisible: false }, { name: 'seasonCount', - label: 'Seasons', + label: () => translate('Seasons'), isSortable: true, isVisible: true }, { name: 'seasonFolder', - label: 'Season Folder', + label: () => translate('SeasonFolder'), isSortable: true, isVisible: false }, { name: 'episodeProgress', - label: 'Episodes', + label: () => translate('Episodes'), isSortable: true, isVisible: true }, { name: 'episodeCount', - label: 'Episode Count', + label: () => translate('EpisodeCount'), isSortable: true, isVisible: false }, { name: 'latestSeason', - label: 'Latest Season', + label: () => translate('LatestSeason'), isSortable: true, isVisible: false }, { name: 'year', - label: 'Year', + label: () => translate('Year'), isSortable: true, isVisible: false }, { name: 'path', - label: 'Path', + label: () => translate('Path'), isSortable: true, isVisible: false }, { name: 'sizeOnDisk', - label: 'Size on Disk', + label: () => translate('SizeOnDisk'), isSortable: true, isVisible: false }, { name: 'genres', - label: 'Genres', + label: () => translate('Genres'), isSortable: false, isVisible: false }, { name: 'ratings', - label: 'Rating', + label: () => translate('Rating'), isSortable: true, isVisible: false }, { name: 'certification', - label: 'Certification', + label: () => translate('Certification'), isSortable: false, isVisible: false }, { name: 'releaseGroups', - label: 'Release Groups', + label: () => translate('ReleaseGroups'), isSortable: false, isVisible: false }, { name: 'tags', - label: 'Tags', + label: () => translate('Tags'), isSortable: true, isVisible: false }, { name: 'useSceneNumbering', - label: 'Scene Numbering', + label: () => translate('SceneNumbering'), isSortable: true, isVisible: false }, { name: 'actions', - columnLabel: 'Actions', + columnLabel: () => translate('Actions'), isVisible: true, isModifiable: false } diff --git a/frontend/src/Store/Actions/systemActions.js b/frontend/src/Store/Actions/systemActions.js index ab197f648..92360b589 100644 --- a/frontend/src/Store/Actions/systemActions.js +++ b/frontend/src/Store/Actions/systemActions.js @@ -4,6 +4,7 @@ import { setAppValue } from 'Store/Actions/appActions'; import { createThunk, handleThunks } from 'Store/thunks'; import createAjaxRequest from 'Utilities/createAjaxRequest'; import serverSideCollectionHandlers from 'Utilities/serverSideCollectionHandlers'; +import translate from 'Utilities/String/translate'; import { pingServer } from './appActions'; import { set } from './baseActions'; import createFetchHandler from './Creators/createFetchHandler'; @@ -81,34 +82,34 @@ export const defaultState = { columns: [ { name: 'level', - columnLabel: 'Level', + columnLabel: () => translate('Level'), isSortable: false, isVisible: true, isModifiable: false }, { name: 'time', - label: 'Time', + label: () => translate('Time'), isSortable: true, isVisible: true, isModifiable: false }, { name: 'logger', - label: 'Component', + label: () => translate('Component'), isSortable: false, isVisible: true, isModifiable: false }, { name: 'message', - label: 'Message', + label: () => translate('Message'), isVisible: true, isModifiable: false }, { name: 'actions', - columnLabel: 'Actions', + columnLabel: () => translate('Actions'), isSortable: true, isVisible: true, isModifiable: false @@ -120,12 +121,12 @@ export const defaultState = { filters: [ { key: 'all', - label: 'All', + label: () => translate('All'), filters: [] }, { key: 'info', - label: 'Info', + label: () => translate('Info'), filters: [ { key: 'level', @@ -136,7 +137,7 @@ export const defaultState = { }, { key: 'warn', - label: 'Warn', + label: () => translate('Warn'), filters: [ { key: 'level', @@ -147,7 +148,7 @@ export const defaultState = { }, { key: 'error', - label: 'Error', + label: () => translate('Error'), filters: [ { key: 'level', diff --git a/frontend/src/Store/Actions/wantedActions.js b/frontend/src/Store/Actions/wantedActions.js index 178080434..21bfcd3c0 100644 --- a/frontend/src/Store/Actions/wantedActions.js +++ b/frontend/src/Store/Actions/wantedActions.js @@ -2,6 +2,7 @@ import { createAction } from 'redux-actions'; import { filterTypes, sortDirections } from 'Helpers/Props'; import { createThunk, handleThunks } from 'Store/thunks'; import serverSideCollectionHandlers from 'Utilities/serverSideCollectionHandlers'; +import translate from 'Utilities/String/translate'; import createBatchToggleEpisodeMonitoredHandler from './Creators/createBatchToggleEpisodeMonitoredHandler'; import createHandleActions from './Creators/createHandleActions'; import createServerSideCollectionHandlers from './Creators/createServerSideCollectionHandlers'; @@ -29,34 +30,34 @@ export const defaultState = { columns: [ { name: 'series.sortTitle', - label: 'Series Title', + label: () => translate('SeriesTitle'), isSortable: true, isVisible: true }, { name: 'episode', - label: 'Episode', + label: () => translate('Episode'), isVisible: true }, { name: 'episodes.title', - label: 'Episode Title', + label: () => translate('EpisodeTitle'), isVisible: true }, { name: 'episodes.airDateUtc', - label: 'Air Date', + label: () => translate('AirDate'), isSortable: true, isVisible: true }, { name: 'status', - label: 'Status', + label: () => translate('Status'), isVisible: true }, { name: 'actions', - columnLabel: 'Actions', + columnLabel: () => translate('Actions'), isVisible: true, isModifiable: false } @@ -67,7 +68,7 @@ export const defaultState = { filters: [ { key: 'monitored', - label: 'Monitored', + label: () => translate('Monitored'), filters: [ { key: 'monitored', @@ -78,7 +79,7 @@ export const defaultState = { }, { key: 'unmonitored', - label: 'Unmonitored', + label: () => translate('Unmonitored'), filters: [ { key: 'monitored', @@ -101,39 +102,39 @@ export const defaultState = { columns: [ { name: 'series.sortTitle', - label: 'Series Title', + label: () => translate('SeriesTitle'), isSortable: true, isVisible: true }, { name: 'episode', - label: 'Episode', + label: () => translate('Episode'), isVisible: true }, { name: 'episodes.title', - label: 'Episode Title', + label: () => translate('EpisodeTitle'), isVisible: true }, { name: 'episodes.airDateUtc', - label: 'Air Date', + label: () => translate('AirDate'), isSortable: true, isVisible: true }, { name: 'languages', - label: 'Languages', + label: () => translate('Languages'), isVisible: false }, { name: 'status', - label: 'Status', + label: () => translate('Status'), isVisible: true }, { name: 'actions', - columnLabel: 'Actions', + columnLabel: () => translate('Actions'), isVisible: true, isModifiable: false } @@ -144,7 +145,7 @@ export const defaultState = { filters: [ { key: 'monitored', - label: 'Monitored', + label: () => translate('Monitored'), filters: [ { key: 'monitored', @@ -155,7 +156,7 @@ export const defaultState = { }, { key: 'unmonitored', - label: 'Unmonitored', + label: () => translate('Unmonitored'), filters: [ { key: 'monitored', diff --git a/src/NzbDrone.Core/Localization/Core/en.json b/src/NzbDrone.Core/Localization/Core/en.json index 5a01aea69..74b50039c 100644 --- a/src/NzbDrone.Core/Localization/Core/en.json +++ b/src/NzbDrone.Core/Localization/Core/en.json @@ -4,10 +4,12 @@ "Actions": "Actions", "Activity": "Activity", "Add": "Add", + "AddNew": "Add New", "Added": "Added", "AddingTag": "Adding tag", - "AddNew": "Add New", + "Age": "Age", "AirDate": "Air Date", + "All": "All", "AllTitles": "All Titles", "ApiKeyValidationHealthCheckMessage": "Please update your API key to be at least {0} characters long. You can do this via settings or the config file", "AppDataDirectory": "AppData directory", @@ -24,6 +26,8 @@ "ApplyTagsHelpTextRemove": "Remove: Remove the entered tags", "ApplyTagsHelpTextReplace": "Replace: Replace the tags with the entered tags (enter no tags to clear all tags)", "AptUpdater": "Use apt to install the update", + "AudioInfo": "Audio Info", + "AudioLanguages": "Audio Languages", "AutoAdd": "Auto Add", "AutomaticAdd": "Automatic Add", "Backup": "Backup", @@ -38,20 +42,24 @@ "Calendar": "Calendar", "Cancel": "Cancel", "CancelPendingTask": "Are you sure you want to cancel this pending task?", + "Certification": "Certification", "Clear": "Clear", "CloneCondition": "Clone Condition", "CloneCustomFormat": "Clone Custom Format", "Close": "Close", + "Component": "Component", "Connect": "Connect", + "ContinuingOnly": "Continuing Only", "CountDownloadClientsSelected": "{count} download client(s) selected", "CountImportListsSelected": "{count} import list(s) selected", "CountIndexersSelected": "{count} indexer(s) selected", "CountSeasons": "{count} seasons", "CurrentlyInstalled": "Currently Installed", - "CustomFormats": "Custom Formats", "CustomFormatScore": "Custom Format Score", + "CustomFormats": "Custom Formats", "CutoffUnmet": "Cutoff Unmet", "Daily": "Daily", + "Date": "Date", "Delete": "Delete", "DeleteBackup": "Delete Backup", "DeleteBackupMessageText": "Are you sure you want to delete the backup '{name}'?", @@ -65,6 +73,7 @@ "DeleteSelectedImportListsMessageText": "Are you sure you want to delete {count} selected import list(s)?", "DeleteSelectedIndexers": "Delete Indexer(s)", "DeleteSelectedIndexersMessageText": "Are you sure you want to delete {count} selected indexer(s)?", + "Deleted": "Deleted", "Details": "Details", "Disabled": "Disabled", "Discord": "Discord", @@ -74,13 +83,14 @@ "Donations": "Donations", "DotNetVersion": ".NET", "Download": "Download", + "DownloadClient": "Download Client", "DownloadClientCheckNoneAvailableHealthCheckMessage": "No download client is available", "DownloadClientCheckUnableToCommunicateWithHealthCheckMessage": "Unable to communicate with {0}.", "DownloadClientRootFolderHealthCheckMessage": "Download client {0} places downloads in the root folder {1}. You should not download to a root folder.", - "DownloadClients": "Download Clients", "DownloadClientSortingHealthCheckMessage": "Download client {0} has {1} sorting enabled for Sonarr's category. You should disable sorting in your download client to avoid import issues.", "DownloadClientStatusAllClientHealthCheckMessage": "All download clients are unavailable due to failures", "DownloadClientStatusSingleClientHealthCheckMessage": "Download clients unavailable due to failures: {0}", + "DownloadClients": "Download Clients", "Duration": "Duration", "Edit": "Edit", "EditSelectedDownloadClients": "Edit Selected Download Clients", @@ -88,64 +98,84 @@ "EditSelectedIndexers": "Edit Selected Indexers", "EditSeries": "Edit Series", "EnableAutomaticSearch": "Enable Automatic Search", - "Enabled": "Enabled", "EnableInteractiveSearch": "Enable Interactive Search", "EnableRSS": "Enable RSS", + "Enabled": "Enabled", "Ended": "Ended", + "EndedOnly": "Ended Only", + "Episode": "Episode", + "EpisodeAirDate": "Episode Air Date", + "EpisodeCount": "Episode Count", "EpisodeInfo": "Episode Info", "EpisodeNumbers": "Episode Number(s)", + "EpisodeProgress": "Episode Progress", + "EpisodeTitle": "Episode Title", + "Episodes": "Episodes", + "Error": "Error", "ErrorRestoringBackup": "Error restoring backup", + "EventType": "Event Type", "Events": "Events", "Exception": "Exception", "ExistingTag": "Existing tag", "ExportCustomFormat": "Export Custom Format", "ExternalUpdater": "Sonarr is configured to use an external update mechanism", + "Failed": "Failed", "FailedToFetchUpdates": "Failed to fetch updates", "FailedToUpdateSettings": "Failed to update settings", "FeatureRequests": "Feature Requests", "Filename": "Filename", "Fixed": "Fixed", + "Formats": "Formats", "Forums": "Forums", "FreeSpace": "Free Space", "From": "From", "FullSeason": "Full Season", "General": "General", "GeneralSettings": "General Settings", + "Genres": "Genres", + "Grabbed": "Grabbed", + "HasMissingSeason": "Has Missing Season", "Health": "Health", "HiddenClickToShow": "Hidden, click to show", "HideAdvanced": "Hide Advanced", "History": "History", "HomePage": "Home Page", + "IRC": "IRC", + "IRCLinkText": "#sonarr on Libera", + "Ignored": "Ignored", "Implementation": "Implementation", "ImportListRootFolderMissingRootHealthCheckMessage": "Missing root folder for import list(s): {0}", "ImportListRootFolderMultipleMissingRootsHealthCheckMessage": "Multiple root folders are missing for import lists: {0}", - "ImportLists": "Import Lists", "ImportListStatusAllUnavailableHealthCheckMessage": "All lists are unavailable due to failures", "ImportListStatusUnavailableHealthCheckMessage": "Lists unavailable due to failures: {0}", + "ImportLists": "Import Lists", "ImportMechanismEnableCompletedDownloadHandlingIfPossibleHealthCheckMessage": "Enable Completed Download Handling if possible", "ImportMechanismEnableCompletedDownloadHandlingIfPossibleMultiComputerHealthCheckMessage": "Enable Completed Download Handling if possible (Multi-Computer unsupported)", "ImportMechanismHandlingDisabledHealthCheckMessage": "Enable Completed Download Handling", + "Imported": "Imported", + "IncludeUnmonitored": "Include Unmonitored", + "Indexer": "Indexer", "IndexerJackettAllHealthCheckMessage": "Indexers using the unsupported Jackett 'all' endpoint: {0}", "IndexerLongTermStatusAllUnavailableHealthCheckMessage": "All indexers are unavailable due to failures for more than 6 hours", "IndexerLongTermStatusUnavailableHealthCheckMessage": "Indexers unavailable due to failures for more than 6 hours: {0}", "IndexerRssNoIndexersAvailableHealthCheckMessage": "All rss-capable indexers are temporarily unavailable due to recent indexer errors", "IndexerRssNoIndexersEnabledHealthCheckMessage": "No indexers available with RSS sync enabled, Sonarr will not grab new releases automatically", - "Indexers": "Indexers", "IndexerSearchNoAutomaticHealthCheckMessage": "No indexers available with Automatic Search enabled, Sonarr will not provide any automatic search results", "IndexerSearchNoAvailableIndexersHealthCheckMessage": "All search-capable indexers are temporarily unavailable due to recent indexer errors", "IndexerSearchNoInteractiveHealthCheckMessage": "No indexers available with Interactive Search enabled, Sonarr will not provide any interactive search results", "IndexerStatusAllUnavailableHealthCheckMessage": "All indexers are unavailable due to failures", "IndexerStatusUnavailableHealthCheckMessage": "Indexers unavailable due to failures: {0}", + "Indexers": "Indexers", + "Info": "Info", "InstallLatest": "Install Latest", "Interval": "Interval", - "IRC": "IRC", - "IRCLinkText": "#sonarr on Libera", "Language": "Language", "Language that Sonarr will use for UI": "Language that Sonarr will use for UI", "Languages": "Languages", "LastDuration": "Last Duration", "LastExecution": "Last Execution", "LastWriteTime": "Last Write Time", + "LatestSeason": "Latest Season", "LiberaWebchat": "Libera Webchat", "LibraryImport": "Library Import", "Location": "Location", @@ -167,8 +197,10 @@ "Metadata": "Metadata", "MetadataSource": "Metadata Source", "Missing": "Missing", + "MissingEpisodes": "Missing Episodes", "Mode": "Mode", "Monitored": "Monitored", + "MonitoredOnly": "Monitored Only", "MoreInfo": "More Info", "MountHealthCheckMessage": "Mount containing a series path is mounted read-only: ", "MultiSeason": "Multi-Season", @@ -190,19 +222,24 @@ "NoLogFiles": "No log files", "NoSeasons": "No seasons", "NoUpdatesAreAvailable": "No updates are available", - "OneSeason": "1 season", + "NotSeasonPack": "Not Season Pack", "OnLatestVersion": "The latest version of Sonarr is already installed", + "OneSeason": "1 season", "Options": "Options", "OriginalLanguage": "Original Language", + "OutputPath": "Output Path", "PackageVersion": "Package Version", "PackageVersionInfo": "{packageVersion} by {packageAuthor}", "PartialSeason": "Partial Season", "Path": "Path", + "Peers": "Peers", "PreviousAiring": "Previous Airing", "PreviouslyInstalled": "Previously Installed", "Priority": "Priority", "Profiles": "Profiles", + "Progress": "Progress", "Proper": "Proper", + "Protocol": "Protocol", "ProxyBadRequestHealthCheckMessage": "Failed to test proxy. Status Code: {0}", "ProxyFailedToTestHealthCheckMessage": "Failed to test proxy: {0}", "ProxyResolveIpHealthCheckMessage": "Failed to resolve the IP Address for the Configured Proxy Host {0}", @@ -210,13 +247,17 @@ "QualityProfile": "Quality Profile", "Queue": "Queue", "Queued": "Queued", + "Rating": "Rating", "ReadTheWikiForMoreInformation": "Read the Wiki for more information", "Real": "Real", "RecycleBinUnableToWriteHealthCheckMessage": "Unable to write to configured recycling bin folder: {0}. Ensure this path exists and is writable by the user running Sonarr", "Refresh": "Refresh", "RefreshSeries": "Refresh Series", + "RejectionCount": "Rejection Count", + "RelativePath": "Relative Path", "Release": "Release", "ReleaseGroup": "Release Group", + "ReleaseGroups": "Release Groups", "ReleaseHash": "Release Hash", "ReleaseTitle": "Release Title", "Reload": "Reload", @@ -238,9 +279,6 @@ "Remove": "Remove", "RemoveCompleted": "Remove Completed", "RemoveCompletedDownloads": "Remove Completed Downloads", - "RemovedFromTaskQueue": "Removed from task queue", - "RemovedSeriesMultipleRemovedHealthCheckMessage": "Series {0} were removed from TheTVDB", - "RemovedSeriesSingleRemovedHealthCheckMessage": "Series {0} was removed from TheTVDB", "RemoveFailed": "Remove Failed", "RemoveFailedDownloads": "Remove Failed Downloads", "RemoveFromDownloadClient": "Remove From Download Client", @@ -249,7 +287,11 @@ "RemoveSelectedItemQueueMessageText": "Are you sure you want to remove 1 item from the queue?", "RemoveSelectedItems": "Remove Selected Items", "RemoveSelectedItemsQueueMessageText": "Are you sure you want to remove {0} items from the queue?", + "RemovedFromTaskQueue": "Removed from task queue", + "RemovedSeriesMultipleRemovedHealthCheckMessage": "Series {0} were removed from TheTVDB", + "RemovedSeriesSingleRemovedHealthCheckMessage": "Series {0} was removed from TheTVDB", "RemovingTag": "Removing tag", + "Renamed": "Renamed", "Repack": "Repack", "Replace": "Replace", "Required": "Required", @@ -266,9 +308,17 @@ "RootFolder": "Root Folder", "RootFolderMissingHealthCheckMessage": "Missing root folder: {0}", "RootFolderMultipleMissingHealthCheckMessage": "Multiple root folders are missing: {0}", + "RootFolderPath": "Root Folder Path", + "Runtime": "Runtime", + "SceneNumbering": "Scene Numbering", "Scheduled": "Scheduled", "SearchForMonitoredEpisodes": "Search for monitored episodes", + "SeasonCount": "Season Count", + "SeasonFolder": "Season Folder", "SeasonNumber": "Season Number", + "SeasonPack": "Season Pack", + "Seasons": "Seasons", + "Seeders": "Seeders", "Series": "Series", "SeriesEditor": "Series Editor", "SeriesTitle": "Series Title", @@ -278,40 +328,50 @@ "ShownClickToHide": "Shown, click to hide", "Size": "Size", "SizeOnDisk": "Size on disk", - "SkipRedownloadHelpText": "Prevents Sonarr from trying to download an alternative release for this item", "SkipRedownload": "Skip Redownload", + "SkipRedownloadHelpText": "Prevents Sonarr from trying to download an alternative release for this item", "Source": "Source", + "SourceTitle": "Source Title", "Special": "Special", "Started": "Started", "StartupDirectory": "Startup directory", "Status": "Status", + "SubtitleLanguages": "Subtitle Languages", "System": "System", "SystemTimeHealthCheckMessage": "System time is off by more than 1 day. Scheduled tasks may not run correctly until the time is corrected", "Tags": "Tags", - "Tasks": "Tasks", "TaskUserAgentTooltip": "User-Agent provided by the app that called the API", + "Tasks": "Tasks", "TestAll": "Test All", "TestParsing": "Test Parsing", "TheLogLevelDefault": "The log level defaults to 'Info' and can be changed in [General Settings](/settings/general)", "Time": "Time", + "TimeLeft": "Time Left", + "Title": "Title", "TotalSpace": "Total Space", "Twitter": "Twitter", + "Type": "Type", "UI": "UI", "UI Language": "UI Language", "UnableToLoadBackups": "Unable to load backups", "UnableToUpdateSonarrDirectly": "Unable to update Sonarr directly,", "Unmonitored": "Unmonitored", + "UnmonitoredOnly": "Unmonitored Only", "UpdateAvailableHealthCheckMessage": "New update is available", - "UpdaterLogFiles": "Updater Log Files", - "Updates": "Updates", "UpdateStartupNotWritableHealthCheckMessage": "Cannot install update because startup folder '{0}' is not writable by the user '{1}'.", "UpdateStartupTranslocationHealthCheckMessage": "Cannot install update because startup folder '{0}' is in an App Translocation folder.", "UpdateUINotWritableHealthCheckMessage": "Cannot install update because UI folder '{0}' is not writable by the user '{1}'.", + "UpdaterLogFiles": "Updater Log Files", + "Updates": "Updates", "Uptime": "Uptime", "Version": "Version", + "VideoCodec": "Video Codec", + "VideoDynamicRange": "Video Dynamic Range", "Wanted": "Wanted", + "Warn": "Warn", "Wiki": "Wiki", "WouldYouLikeToRestoreBackup": "Would you like to restore the backup '{name}'?", + "Year": "Year", "Yes": "Yes", "YesCancel": "Yes, Cancel" }