Translate frontend series pages

This commit is contained in:
Stevie Robinson 2023-09-01 02:47:17 +02:00 committed by GitHub
parent 33c52a7037
commit c123596c68
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
43 changed files with 543 additions and 279 deletions

View File

@ -1,12 +1,18 @@
import padNumber from 'Utilities/Number/padNumber';
import translate from 'Utilities/String/translate';
export default function formatSeason(seasonNumber: number) {
export default function formatSeason(
seasonNumber: number,
shortFormat?: boolean
) {
if (seasonNumber === 0) {
return translate('Specials');
}
if (seasonNumber > 0) {
return translate('SeasonNumberToken', { seasonNumber });
return shortFormat
? `S${padNumber(seasonNumber, 2)}`
: translate('SeasonNumberToken', { seasonNumber });
}
return null;

View File

@ -6,6 +6,11 @@
margin-right: 8px;
}
.folderPath {
font-weight: bold;
font-family: var(--defaultFontFamily);
}
.deleteFilesMessage {
margin-top: 20px;
color: var(--dangerColor);

View File

@ -2,6 +2,7 @@
// Please do not change this file!
interface CssExports {
'deleteFilesMessage': string;
'folderPath': string;
'pathContainer': string;
'pathIcon': string;
}

View File

@ -5,12 +5,14 @@ import FormInputGroup from 'Components/Form/FormInputGroup';
import FormLabel from 'Components/Form/FormLabel';
import Icon from 'Components/Icon';
import Button from 'Components/Link/Button';
import InlineMarkdown from 'Components/Markdown/InlineMarkdown';
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 { icons, inputTypes, kinds } from 'Helpers/Props';
import formatBytes from 'Utilities/Number/formatBytes';
import translate from 'Utilities/String/translate';
import styles from './DeleteSeriesModalContent.css';
class DeleteSeriesModalContent extends Component {
@ -61,20 +63,13 @@ class DeleteSeriesModalContent extends Component {
const deleteFiles = this.state.deleteFiles;
const addImportListExclusion = deleteOptions.addImportListExclusion;
let deleteFilesLabel = `Delete ${episodeFileCount} Episode Files`;
let deleteFilesHelpText = 'Delete the episode files and series folder';
if (episodeFileCount === 0) {
deleteFilesLabel = 'Delete Series Folder';
deleteFilesHelpText = 'Delete the series folder and its contents';
}
return (
<ModalContent
onModalClose={onModalClose}
>
<ModalHeader>
Delete - {title}
{translate('DeleteSeriesModalHeader', { title })}
</ModalHeader>
<ModalBody>
@ -88,25 +83,25 @@ class DeleteSeriesModalContent extends Component {
</div>
<FormGroup>
<FormLabel>Add List Exclusion</FormLabel>
<FormLabel>{translate('AddListExclusion')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="addImportListExclusion"
value={addImportListExclusion}
helpText="Prevent series from being added to Sonarr by lists"
helpText={translate('AddListExclusionHelpText')}
onChange={onDeleteOptionChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>{deleteFilesLabel}</FormLabel>
<FormLabel>{episodeFileCount === 0 ? translate('DeleteSeriesFolder') : translate('DeleteEpisodesFiles', { episodeFileCount })}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="deleteFiles"
value={deleteFiles}
helpText={deleteFilesHelpText}
helpText={episodeFileCount === 0 ? translate('DeleteSeriesFolderHelpText') : translate('DeleteEpisodesFilesHelpText')}
kind={kinds.DANGER}
onChange={this.onDeleteFilesChange}
/>
@ -115,11 +110,10 @@ class DeleteSeriesModalContent extends Component {
{
deleteFiles &&
<div className={styles.deleteFilesMessage}>
<div>The series folder <strong>{path}</strong> and all of its content will be deleted.</div>
<div><InlineMarkdown data={translate('DeleteSeriesFolderConfirmation', { path })} blockClassName={styles.folderPath} /></div>
{
!!episodeFileCount &&
<div>{episodeFileCount} episode files totaling {formatBytes(sizeOnDisk)}</div>
<div>{translate('DeleteSeriesFolderEpisodeCount', { episodeFileCount, size: formatBytes(sizeOnDisk) })}</div>
}
</div>
}
@ -128,14 +122,14 @@ class DeleteSeriesModalContent extends Component {
<ModalFooter>
<Button onPress={onModalClose}>
Close
{translate('Close')}
</Button>
<Button
kind={kinds.DANGER}
onPress={this.onDeleteSeriesConfirmed}
>
Delete
{translate('Delete')}
</Button>
</ModalFooter>
</ModalContent>

View File

@ -3,6 +3,7 @@ import React from 'react';
import DescriptionList from 'Components/DescriptionList/DescriptionList';
import DescriptionListItem from 'Components/DescriptionList/DescriptionListItem';
import formatBytes from 'Utilities/Number/formatBytes';
import translate from 'Utilities/String/translate';
import styles from './SeasonInfo.css';
function SeasonInfo(props) {
@ -18,28 +19,28 @@ function SeasonInfo(props) {
<DescriptionListItem
titleClassName={styles.title}
descriptionClassName={styles.description}
title="Total"
title={translate('Total')}
data={totalEpisodeCount}
/>
<DescriptionListItem
titleClassName={styles.title}
descriptionClassName={styles.description}
title="Monitored"
title={translate('Monitored')}
data={monitoredEpisodeCount}
/>
<DescriptionListItem
titleClassName={styles.title}
descriptionClassName={styles.description}
title="With Files"
title={translate('WithFiles')}
data={episodeFileCount}
/>
<DescriptionListItem
titleClassName={styles.title}
descriptionClassName={styles.description}
title="Size on Disk"
title={translate('SizeOnDisk')}
data={formatBytes(sizeOnDisk)}
/>
</DescriptionList>

View File

@ -31,6 +31,7 @@ import { getSeriesStatusDetails } from 'Series/SeriesStatus';
import QualityProfileNameConnector from 'Settings/Profiles/Quality/QualityProfileNameConnector';
import fonts from 'Styles/Variables/fonts';
import formatBytes from 'Utilities/Number/formatBytes';
import translate from 'Utilities/String/translate';
import selectAll from 'Utilities/Table/selectAll';
import toggleSelected from 'Utilities/Table/toggleSelected';
import SeriesAlternateTitles from './SeriesAlternateTitles';
@ -230,14 +231,14 @@ class SeriesDetails extends Component {
} = this.state;
const statusDetails = getSeriesStatusDetails(status);
const runningYears = statusDetails.title === 'Ended' ? `${year}-${getDateYear(previousAiring)}` : `${year}-`;
const runningYears = statusDetails.title === translate('Ended') ? `${year}-${getDateYear(previousAiring)}` : `${year}-`;
let episodeFilesCountMessage = 'No episode files';
let episodeFilesCountMessage = translate('SeriesDetailsNoEpisodeFiles');
if (episodeFileCount === 1) {
episodeFilesCountMessage = '1 episode file';
episodeFilesCountMessage = translate('SeriesDetailsOneEpisodeFile');
} else if (episodeFileCount > 1) {
episodeFilesCountMessage = `${episodeFileCount} episode files`;
episodeFilesCountMessage = translate('SeriesDetailsCountEpisodeFiles', { episodeFileCount });
}
let expandIcon = icons.EXPAND_INDETERMINATE;
@ -255,40 +256,40 @@ class SeriesDetails extends Component {
<PageToolbar>
<PageToolbarSection>
<PageToolbarButton
label="Refresh & Scan"
label={translate('RefreshAndScan')}
iconName={icons.REFRESH}
spinningName={icons.REFRESH}
title="Refresh information and scan disk"
title={translate('RefreshAndScanTooltip')}
isSpinning={isRefreshing}
onPress={onRefreshPress}
/>
<PageToolbarButton
label="Search Monitored"
label={translate('SearchMonitored')}
iconName={icons.SEARCH}
isDisabled={!monitored || !hasMonitoredEpisodes || !hasEpisodes}
isSpinning={isSearching}
title={hasMonitoredEpisodes ? undefined : 'No monitored episodes in this series'}
title={hasMonitoredEpisodes ? undefined : translate('NoMonitoredEpisodes')}
onPress={onSearchPress}
/>
<PageToolbarSeparator />
<PageToolbarButton
label="Preview Rename"
label={translate('PreviewRename')}
iconName={icons.ORGANIZE}
isDisabled={!hasEpisodeFiles}
onPress={this.onOrganizePress}
/>
<PageToolbarButton
label="Manage Episodes"
label={translate('ManageEpisodes')}
iconName={icons.EPISODE_FILE}
onPress={this.onManageEpisodesPress}
/>
<PageToolbarButton
label="History"
label={translate('History')}
iconName={icons.HISTORY}
isDisabled={!hasEpisodes}
onPress={this.onSeriesHistoryPress}
@ -297,19 +298,19 @@ class SeriesDetails extends Component {
<PageToolbarSeparator />
<PageToolbarButton
label="Series Monitoring"
label={translate('SeriesMonitoring')}
iconName={icons.MONITORED}
onPress={this.onMonitorOptionsPress}
/>
<PageToolbarButton
label="Edit"
label={translate('Edit')}
iconName={icons.EDIT}
onPress={this.onEditSeriesPress}
/>
<PageToolbarButton
label="Delete"
label={translate('Delete')}
iconName={icons.DELETE}
onPress={this.onDeleteSeriesPress}
/>
@ -318,7 +319,7 @@ class SeriesDetails extends Component {
<PageToolbarSection alignContent={align.RIGHT}>
<PageToolbarButton
label={allExpanded ? 'Collapse All' : 'Expand All'}
label={allExpanded ? translate('CollapseAll') : translate('ExpandAll')}
iconName={expandIcon}
onPress={this.onExpandAllPress}
/>
@ -373,7 +374,7 @@ class SeriesDetails extends Component {
size={20}
/>
}
title="Alternate Titles"
title={translate('AlternateTitles')}
body={<SeriesAlternateTitles alternateTitles={alternateTitles} />}
position={tooltipPositions.BOTTOM}
/>
@ -386,7 +387,7 @@ class SeriesDetails extends Component {
className={styles.seriesNavigationButton}
name={icons.ARROW_LEFT}
size={30}
title={`Go to ${previousSeries.title}`}
title={translate('SeriesDetailsGoTo', { title: previousSeries.title })}
to={`/series/${previousSeries.titleSlug}`}
/>
@ -394,7 +395,7 @@ class SeriesDetails extends Component {
className={styles.seriesNavigationButton}
name={icons.ARROW_RIGHT}
size={30}
title={`Go to ${nextSeries.title}`}
title={translate('SeriesDetailsGoTo', { title: nextSeries.title })}
to={`/series/${nextSeries.titleSlug}`}
/>
</div>
@ -405,7 +406,7 @@ class SeriesDetails extends Component {
{
!!runtime &&
<span className={styles.runtime}>
{runtime} Minutes
{translate('SeriesDetailsRuntime', { runtime })}
</span>
}
@ -466,7 +467,7 @@ class SeriesDetails extends Component {
<Label
className={styles.detailsLabel}
title="Quality Profile"
title={translate('QualityProfile')}
size={sizes.LARGE}
>
<Icon
@ -493,7 +494,7 @@ class SeriesDetails extends Component {
/>
<span className={styles.qualityProfileName}>
{monitored ? 'Monitored' : 'Unmonitored'}
{monitored ? translate('Monitored') : translate('Unmonitored')}
</span>
</Label>
@ -516,7 +517,7 @@ class SeriesDetails extends Component {
!!network &&
<Label
className={styles.detailsLabel}
title="Network"
title={translate('Network')}
size={sizes.LARGE}
>
<Icon
@ -542,7 +543,7 @@ class SeriesDetails extends Component {
/>
<span className={styles.links}>
Links
{translate('Links')}
</span>
</Label>
}
@ -571,7 +572,7 @@ class SeriesDetails extends Component {
/>
<span className={styles.tags}>
Tags
{translate('Tags')}
</span>
</Label>
}
@ -605,12 +606,12 @@ class SeriesDetails extends Component {
{
!isFetching && episodesError &&
<div>Loading episodes failed</div>
<div>{translate('EpisodesLoadError')}</div>
}
{
!isFetching && episodeFilesError &&
<div>Loading episode files failed</div>
<div>{translate('EpisodeFilesLoadError')}</div>
}
{
@ -635,7 +636,7 @@ class SeriesDetails extends Component {
{
isPopulated && !seasons.length &&
<div>
No episode information is available.
{translate('NoEpisodeInformation')}
</div>
}
@ -658,7 +659,7 @@ class SeriesDetails extends Component {
allowSeriesChange={false}
showDelete={true}
showImportMode={false}
modalTitle={'Manage Episodes'}
modalTitle={translate('ManageEpisodes')}
onModalClose={this.onManageEpisodesModalClose}
/>

View File

@ -6,6 +6,7 @@ import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import NotFound from 'Components/NotFound';
import createAllSeriesSelector from 'Store/Selectors/createAllSeriesSelector';
import translate from 'Utilities/String/translate';
import SeriesDetailsConnector from './SeriesDetailsConnector';
function createMapStateToProps() {
@ -54,7 +55,7 @@ class SeriesDetailsPageConnector extends Component {
if (!titleSlug) {
return (
<NotFound
message="Sorry, that series cannot be found."
message={translate('SeriesCannotBeFound')}
/>
);
}

View File

@ -23,6 +23,7 @@ import SeasonInteractiveSearchModalConnector from 'Series/Search/SeasonInteracti
import isAfter from 'Utilities/Date/isAfter';
import isBefore from 'Utilities/Date/isBefore';
import formatBytes from 'Utilities/Number/formatBytes';
import translate from 'Utilities/String/translate';
import getToggledRange from 'Utilities/Table/getToggledRange';
import EpisodeRowConnector from './EpisodeRowConnector';
import SeasonInfo from './SeasonInfo';
@ -239,7 +240,7 @@ class SeriesDetailsSeason extends Component {
isInteractiveSearchModalOpen
} = this.state;
const title = seasonNumber === 0 ? 'Specials' : `Season ${seasonNumber}`;
const title = seasonNumber === 0 ? translate('Specials') : translate('SeasonNumberToken', { seasonNumber });
return (
<div
@ -270,7 +271,7 @@ class SeriesDetailsSeason extends Component {
<span>{episodeFileCount} / {episodeCount}</span>
</Label>
}
title="Season Information"
title={translate('SeasonInformation')}
body={
<div>
<SeasonInfo
@ -300,7 +301,7 @@ class SeriesDetailsSeason extends Component {
<Icon
className={styles.expandButtonIcon}
name={isExpanded ? icons.COLLAPSE : icons.EXPAND}
title={isExpanded ? 'Hide episodes' : 'Show episodes'}
title={isExpanded ? translate('HideEpisodes') : translate('ShowEpisodes')}
size={24}
/>
{
@ -334,7 +335,7 @@ class SeriesDetailsSeason extends Component {
isSpinning={isSearching}
/>
Search
{translate('Search')}
</MenuItem>
<MenuItem
@ -346,7 +347,7 @@ class SeriesDetailsSeason extends Component {
name={icons.INTERACTIVE}
/>
Interactive Search
{translate('InteractiveSearch')}
</MenuItem>
<MenuItem
@ -358,7 +359,7 @@ class SeriesDetailsSeason extends Component {
name={icons.ORGANIZE}
/>
Preview Rename
{translate('PreviewRename')}
</MenuItem>
<MenuItem
@ -370,7 +371,7 @@ class SeriesDetailsSeason extends Component {
name={icons.EPISODE_FILE}
/>
Manage Episodes
{translate('ManageEpisodes')}
</MenuItem>
<MenuItem
@ -382,7 +383,7 @@ class SeriesDetailsSeason extends Component {
name={icons.HISTORY}
/>
History
{translate('History')}
</MenuItem>
</MenuContent>
</Menu> :
@ -391,7 +392,7 @@ class SeriesDetailsSeason extends Component {
<SpinnerIconButton
className={styles.actionButton}
name={icons.SEARCH}
title={hasMonitoredEpisodes && seriesMonitored ? 'Search for monitored episodes in this season' : 'No monitored episodes in this season'}
title={hasMonitoredEpisodes && seriesMonitored ? translate('SearchForMonitoredEpisodesSeason') : translate('NoMonitoredEpisodesSeason')}
size={24}
isSpinning={isSearching}
isDisabled={isSearching || !hasMonitoredEpisodes || !seriesMonitored}
@ -401,7 +402,7 @@ class SeriesDetailsSeason extends Component {
<IconButton
className={styles.actionButton}
name={icons.INTERACTIVE}
title="Interactive search for all episodes in this season"
title={translate('InteractiveSearchSeason')}
size={24}
isDisabled={!totalEpisodeCount}
onPress={this.onInteractiveSearchPress}
@ -410,7 +411,7 @@ class SeriesDetailsSeason extends Component {
<IconButton
className={styles.actionButton}
name={icons.ORGANIZE}
title="Preview rename for this season"
title={translate('PreviewRenameSeason')}
size={24}
isDisabled={!episodeFileCount}
onPress={this.onOrganizePress}
@ -419,7 +420,7 @@ class SeriesDetailsSeason extends Component {
<IconButton
className={styles.actionButton}
name={icons.EPISODE_FILE}
title="Manage episode files in this season"
title={translate('ManageEpisodesSeason')}
size={24}
isDisabled={!episodeFileCount}
onPress={this.onManageEpisodesPress}
@ -428,7 +429,7 @@ class SeriesDetailsSeason extends Component {
<IconButton
className={styles.actionButton}
name={icons.HISTORY}
title="View history for this season"
title={translate('HistorySeason')}
size={24}
isDisabled={!totalEpisodeCount}
onPress={this.onHistoryPress}
@ -465,7 +466,7 @@ class SeriesDetailsSeason extends Component {
</Table> :
<div className={styles.noEpisodes}>
No episodes in this season
{translate('NoEpisodesInThisSeason')}
</div>
}
<div className={styles.collapseButtonContainer}>
@ -473,7 +474,7 @@ class SeriesDetailsSeason extends Component {
iconClassName={styles.collapseButtonIcon}
name={icons.COLLAPSE}
size={20}
title="Hide episodes"
title={translate('HideEpisodes')}
onPress={this.onExpandPress}
/>
</div>
@ -500,7 +501,7 @@ class SeriesDetailsSeason extends Component {
allowSeriesChange={false}
showDelete={true}
showImportMode={false}
modalTitle={'Manage Episodes'}
modalTitle={translate('ManageEpisodes')}
onModalClose={this.onManageEpisodesModalClose}
/>

View File

@ -83,37 +83,37 @@ class EditSeriesModalContent extends Component {
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
Edit - {title}
{translate('EditSeriesModalHeader', { title })}
</ModalHeader>
<ModalBody>
<Form {...otherProps}>
<FormGroup>
<FormLabel>Monitored</FormLabel>
<FormLabel>{translate('Monitored')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="monitored"
helpText="Download monitored episodes in this series"
helpText={translate('MonitoredHelpText')}
{...monitored}
onChange={onInputChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>Use Season Folder</FormLabel>
<FormLabel>{translate('UseSeasonFolder')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="seasonFolder"
helpText="Sort episodes into season folders"
helpText={translate('UseSeasonFolderHelpText')}
{...seasonFolder}
onChange={onInputChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>Quality Profile</FormLabel>
<FormLabel>{translate('QualityProfile')}</FormLabel>
<FormInputGroup
type={inputTypes.QUALITY_PROFILE_SELECT}
@ -124,21 +124,19 @@ class EditSeriesModalContent extends Component {
</FormGroup>
<FormGroup>
<FormLabel>Series Type</FormLabel>
<FormLabel>{translate('SeriesType')}</FormLabel>
<FormInputGroup
type={inputTypes.SERIES_TYPE_SELECT}
name="seriesType"
{...seriesType}
helpText={translate(
'Series type is used for renaming, parsing and searching'
)}
helpText={translate('SeriesTypesHelpText')}
onChange={onInputChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>Path</FormLabel>
<FormLabel>{translate('Path')}</FormLabel>
<FormInputGroup
type={inputTypes.PATH}
@ -149,7 +147,7 @@ class EditSeriesModalContent extends Component {
</FormGroup>
<FormGroup>
<FormLabel>Tags</FormLabel>
<FormLabel>{translate('Tags')}</FormLabel>
<FormInputGroup
type={inputTypes.TAG}
@ -167,20 +165,20 @@ class EditSeriesModalContent extends Component {
kind={kinds.DANGER}
onPress={onDeleteSeriesPress}
>
Delete
{translate('Delete')}
</Button>
<Button
onPress={onModalClose}
>
Cancel
{translate('Cancel')}
</Button>
<SpinnerButton
isSpinning={isSaving}
onPress={this.onSavePress}
>
Save
{translate('Save')}
</SpinnerButton>
</ModalFooter>

View File

@ -88,7 +88,10 @@ class SeriesHistoryModalContent extends Component {
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
History {seasonNumber != null && formatSeason(seasonNumber)}
{seasonNumber == null ?
translate('History') :
translate('HistoryModalHeaderSeason', { season: formatSeason(seasonNumber) })
}
</ModalHeader>
<ModalBody>
@ -99,12 +102,12 @@ class SeriesHistoryModalContent extends Component {
{
!isFetching && !!error &&
<Alert kind={kinds.DANGER}>Unable to load history.</Alert>
<Alert kind={kinds.DANGER}>{translate('HistoryLoadError')}</Alert>
}
{
isPopulated && !hasItems && !error &&
<div>No history.</div>
<div>{translate('NoHistory')}</div>
}
{
@ -130,7 +133,7 @@ class SeriesHistoryModalContent extends Component {
<ModalFooter>
<Button onPress={onModalClose}>
Close
{translate('Close')}
</Button>
</ModalFooter>
</ModalContent>

View File

@ -17,6 +17,7 @@ import EpisodeQuality from 'Episode/EpisodeQuality';
import SeasonEpisodeNumber from 'Episode/SeasonEpisodeNumber';
import { icons, kinds, tooltipPositions } from 'Helpers/Props';
import formatCustomFormatScore from 'Utilities/Number/formatCustomFormatScore';
import translate from 'Utilities/String/translate';
import styles from './SeriesHistoryRow.css';
function getTitle(eventType) {
@ -157,7 +158,7 @@ class SeriesHistoryRow extends Component {
{
eventType === 'grabbed' &&
<IconButton
title="Mark as failed"
title={translate('MarkAsFailed')}
name={icons.REMOVE}
onPress={this.onMarkAsFailedPress}
/>
@ -167,9 +168,9 @@ class SeriesHistoryRow extends Component {
<ConfirmModal
isOpen={isMarkAsFailedModalOpen}
kind={kinds.DANGER}
title="Mark as Failed"
message={`Are you sure you want to mark '${sourceTitle}' as failed?`}
confirmLabel="Mark as Failed"
title={translate('MarkAsFailed')}
message={translate('MarkAsFailedConfirmation', { sourceTitle })}
confirmLabel={translate('MarkAsFailed')}
onConfirm={this.onConfirmMarkAsFailed}
onCancel={this.onMarkAsFailedModalClose}
/>

View File

@ -4,6 +4,7 @@ import SortMenu from 'Components/Menu/SortMenu';
import SortMenuItem from 'Components/Menu/SortMenuItem';
import { align } from 'Helpers/Props';
import SortDirection from 'Helpers/Props/SortDirection';
import translate from 'Utilities/String/translate';
interface SeriesIndexSortMenuProps {
sortKey?: string;
@ -24,7 +25,7 @@ function SeriesIndexSortMenu(props: SeriesIndexSortMenuProps) {
sortDirection={sortDirection}
onPress={onSortSelect}
>
Monitored/Status
{translate('MonitoredStatus')}
</SortMenuItem>
<SortMenuItem
@ -33,7 +34,7 @@ function SeriesIndexSortMenu(props: SeriesIndexSortMenuProps) {
sortDirection={sortDirection}
onPress={onSortSelect}
>
Title
{translate('Title')}
</SortMenuItem>
<SortMenuItem
@ -42,7 +43,7 @@ function SeriesIndexSortMenu(props: SeriesIndexSortMenuProps) {
sortDirection={sortDirection}
onPress={onSortSelect}
>
Network
{translate('Network')}
</SortMenuItem>
<SortMenuItem
@ -51,7 +52,7 @@ function SeriesIndexSortMenu(props: SeriesIndexSortMenuProps) {
sortDirection={sortDirection}
onPress={onSortSelect}
>
Original Language
{translate('OriginalLanguage')}
</SortMenuItem>
<SortMenuItem
@ -60,7 +61,7 @@ function SeriesIndexSortMenu(props: SeriesIndexSortMenuProps) {
sortDirection={sortDirection}
onPress={onSortSelect}
>
Quality Profile
{translate('QualityProfile')}
</SortMenuItem>
<SortMenuItem
@ -69,7 +70,7 @@ function SeriesIndexSortMenu(props: SeriesIndexSortMenuProps) {
sortDirection={sortDirection}
onPress={onSortSelect}
>
Next Airing
{translate('NextAiring')}
</SortMenuItem>
<SortMenuItem
@ -78,7 +79,7 @@ function SeriesIndexSortMenu(props: SeriesIndexSortMenuProps) {
sortDirection={sortDirection}
onPress={onSortSelect}
>
Previous Airing
{translate('PreviousAiring')}
</SortMenuItem>
<SortMenuItem
@ -87,7 +88,7 @@ function SeriesIndexSortMenu(props: SeriesIndexSortMenuProps) {
sortDirection={sortDirection}
onPress={onSortSelect}
>
Added
{translate('Added')}
</SortMenuItem>
<SortMenuItem
@ -96,7 +97,7 @@ function SeriesIndexSortMenu(props: SeriesIndexSortMenuProps) {
sortDirection={sortDirection}
onPress={onSortSelect}
>
Seasons
{translate('Seasons')}
</SortMenuItem>
<SortMenuItem
@ -105,7 +106,7 @@ function SeriesIndexSortMenu(props: SeriesIndexSortMenuProps) {
sortDirection={sortDirection}
onPress={onSortSelect}
>
Episodes
{translate('Episodes')}
</SortMenuItem>
<SortMenuItem
@ -114,7 +115,7 @@ function SeriesIndexSortMenu(props: SeriesIndexSortMenuProps) {
sortDirection={sortDirection}
onPress={onSortSelect}
>
Episode Count
{translate('EpisodeCount')}
</SortMenuItem>
<SortMenuItem
@ -123,7 +124,7 @@ function SeriesIndexSortMenu(props: SeriesIndexSortMenuProps) {
sortDirection={sortDirection}
onPress={onSortSelect}
>
Latest Season
{translate('LatestSeason')}
</SortMenuItem>
<SortMenuItem
@ -132,7 +133,7 @@ function SeriesIndexSortMenu(props: SeriesIndexSortMenuProps) {
sortDirection={sortDirection}
onPress={onSortSelect}
>
Path
{translate('Path')}
</SortMenuItem>
<SortMenuItem
@ -141,7 +142,7 @@ function SeriesIndexSortMenu(props: SeriesIndexSortMenuProps) {
sortDirection={sortDirection}
onPress={onSortSelect}
>
Size on Disk
{translate('SizeOnDisk')}
</SortMenuItem>
<SortMenuItem
@ -150,7 +151,7 @@ function SeriesIndexSortMenu(props: SeriesIndexSortMenuProps) {
sortDirection={sortDirection}
onPress={onSortSelect}
>
Tags
{translate('Tags')}
</SortMenuItem>
</MenuContent>
</SortMenu>

View File

@ -3,6 +3,7 @@ import MenuContent from 'Components/Menu/MenuContent';
import ViewMenu from 'Components/Menu/ViewMenu';
import ViewMenuItem from 'Components/Menu/ViewMenuItem';
import { align } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
interface SeriesIndexViewMenuProps {
view: string;
@ -17,11 +18,11 @@ function SeriesIndexViewMenu(props: SeriesIndexViewMenuProps) {
<ViewMenu isDisabled={isDisabled} alignMenu={align.RIGHT}>
<MenuContent>
<ViewMenuItem name="table" selectedView={view} onPress={onViewSelect}>
Table
{translate('Table')}
</ViewMenuItem>
<ViewMenuItem name="posters" selectedView={view} onPress={onViewSelect}>
Posters
{translate('Posters')}
</ViewMenuItem>
<ViewMenuItem
@ -29,7 +30,7 @@ function SeriesIndexViewMenu(props: SeriesIndexViewMenuProps) {
selectedView={view}
onPress={onViewSelect}
>
Overview
{translate('Overview')}
</ViewMenuItem>
</MenuContent>
</ViewMenu>

View File

@ -11,12 +11,28 @@ import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { inputTypes } from 'Helpers/Props';
import { setSeriesOverviewOption } from 'Store/Actions/seriesIndexActions';
import translate from 'Utilities/String/translate';
import selectOverviewOptions from '../selectOverviewOptions';
const posterSizeOptions = [
{ key: 'small', value: 'Small' },
{ key: 'medium', value: 'Medium' },
{ key: 'large', value: 'Large' },
{
key: 'small',
get value() {
return translate('Small');
},
},
{
key: 'medium',
get value() {
return translate('Medium');
},
},
{
key: 'large',
get value() {
return translate('Large');
},
},
];
interface SeriesIndexOverviewOptionsModalContentProps {
@ -53,12 +69,12 @@ function SeriesIndexOverviewOptionsModalContent(
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>Overview Options</ModalHeader>
<ModalHeader>{translate('OverviewOptions')}</ModalHeader>
<ModalBody>
<Form>
<FormGroup>
<FormLabel>Poster Size</FormLabel>
<FormLabel>{translate('PosterSize')}</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
@ -70,19 +86,19 @@ function SeriesIndexOverviewOptionsModalContent(
</FormGroup>
<FormGroup>
<FormLabel>Detailed Progress Bar</FormLabel>
<FormLabel>{translate('DetailedProgressBar')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="detailedProgressBar"
value={detailedProgressBar}
helpText="Show text on progress bar"
helpText={translate('DetailedProgressBarHelpText')}
onChange={onOverviewOptionChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>Show Monitored</FormLabel>
<FormLabel>{translate('ShowMonitored')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
@ -93,7 +109,7 @@ function SeriesIndexOverviewOptionsModalContent(
</FormGroup>
<FormGroup>
<FormLabel>Show Network</FormLabel>
<FormLabel>{translate('ShowNetwork')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
@ -104,7 +120,7 @@ function SeriesIndexOverviewOptionsModalContent(
</FormGroup>
<FormGroup>
<FormLabel>Show Quality Profile</FormLabel>
<FormLabel>{translate('ShowQualityProfile')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
@ -115,7 +131,7 @@ function SeriesIndexOverviewOptionsModalContent(
</FormGroup>
<FormGroup>
<FormLabel>Show Previous Airing</FormLabel>
<FormLabel>{translate('ShowPreviousAiring')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
@ -126,7 +142,7 @@ function SeriesIndexOverviewOptionsModalContent(
</FormGroup>
<FormGroup>
<FormLabel>Show Date Added</FormLabel>
<FormLabel>{translate('ShowDateAdded')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
@ -137,7 +153,7 @@ function SeriesIndexOverviewOptionsModalContent(
</FormGroup>
<FormGroup>
<FormLabel>Show Season Count</FormLabel>
<FormLabel>{translate('ShowSeasonCount')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
@ -148,7 +164,7 @@ function SeriesIndexOverviewOptionsModalContent(
</FormGroup>
<FormGroup>
<FormLabel>Show Path</FormLabel>
<FormLabel>{translate('ShowPath')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
@ -159,7 +175,7 @@ function SeriesIndexOverviewOptionsModalContent(
</FormGroup>
<FormGroup>
<FormLabel>Show Size on Disk</FormLabel>
<FormLabel>{translate('ShowSizeOnDisk')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
@ -170,13 +186,13 @@ function SeriesIndexOverviewOptionsModalContent(
</FormGroup>
<FormGroup>
<FormLabel>Show Search</FormLabel>
<FormLabel>{translate('ShowSearch')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="showSearchAction"
value={showSearchAction}
helpText="Show search button on hover"
helpText={translate('ShowSearchHelpText')}
onChange={onOverviewOptionChange}
/>
</FormGroup>
@ -184,7 +200,7 @@ function SeriesIndexOverviewOptionsModalContent(
</ModalBody>
<ModalFooter>
<Button onPress={onModalClose}>Close</Button>
<Button onPress={onModalClose}>{translate('Close')}</Button>
</ModalFooter>
</ModalContent>
);

View File

@ -15,6 +15,7 @@ import SeriesPoster from 'Series/SeriesPoster';
import { executeCommand } from 'Store/Actions/commandActions';
import dimensions from 'Styles/Variables/dimensions';
import fonts from 'Styles/Variables/fonts';
import translate from 'Utilities/String/translate';
import createSeriesIndexItemSelector from '../createSeriesIndexItemSelector';
import selectOverviewOptions from './selectOverviewOptions';
import SeriesIndexOverviewInfo from './SeriesIndexOverviewInfo';
@ -27,7 +28,7 @@ const columnPaddingSmallScreen = parseInt(
const defaultFontSize = parseInt(fonts.defaultFontSize);
const lineHeight = parseFloat(fonts.lineHeight);
// Hardcoded height beased on line-height of 32 + bottom margin of 10.
// Hardcoded height based on line-height of 32 + bottom margin of 10.
// Less side-effecty than using react-measure.
const TITLE_HEIGHT = 42;
@ -144,7 +145,7 @@ function SeriesIndexOverview(props: SeriesIndexOverviewProps) {
) : null}
{status === 'ended' && (
<div className={styles.ended} title="Ended" />
<div className={styles.ended} title={translate('Ended')} />
)}
<Link className={styles.link} style={elementStyle} to={link}>
@ -181,7 +182,7 @@ function SeriesIndexOverview(props: SeriesIndexOverviewProps) {
<div className={styles.actions}>
<SpinnerIconButton
name={icons.REFRESH}
title="Refresh series"
title={translate('RefreshSeries')}
isSpinning={isRefreshingSeries}
onPress={onRefreshPress}
/>
@ -189,7 +190,7 @@ function SeriesIndexOverview(props: SeriesIndexOverviewProps) {
{overviewOptions.showSearchAction ? (
<SpinnerIconButton
name={icons.SEARCH}
title="Search for monitored episodes"
title={translate('SearchForMonitoredEpisodes')}
isSpinning={isSearchingSeries}
onPress={onSearchPress}
/>
@ -197,7 +198,7 @@ function SeriesIndexOverview(props: SeriesIndexOverviewProps) {
<IconButton
name={icons.EDIT}
title="Edit Series"
title={translate('EditSeries')}
onPress={onEditSeriesPress}
/>
</div>

View File

@ -9,6 +9,7 @@ import { UiSettings } from 'typings/UiSettings';
import formatDateTime from 'Utilities/Date/formatDateTime';
import getRelativeDate from 'Utilities/Date/getRelativeDate';
import formatBytes from 'Utilities/Number/formatBytes';
import translate from 'Utilities/String/translate';
import SeriesIndexOverviewInfoRow from './SeriesIndexOverviewInfoRow';
import styles from './SeriesIndexOverviewInfo.css';
@ -99,7 +100,9 @@ function getInfoRowProps(
const { name } = row;
if (name === 'monitored') {
const monitoredText = props.monitored ? 'Monitored' : 'Unmonitored';
const monitoredText = props.monitored
? translate('Monitored')
: translate('Unmonitored');
return {
title: monitoredText,
@ -110,7 +113,7 @@ function getInfoRowProps(
if (name === 'network') {
return {
title: 'Network',
title: translate('Network'),
iconName: icons.NETWORK,
label: props.network ?? '',
};
@ -118,7 +121,7 @@ function getInfoRowProps(
if (name === 'qualityProfileId' && !!props.qualityProfile?.name) {
return {
title: 'Quality Profile',
title: translate('QualityProfile'),
iconName: icons.PROFILE,
label: props.qualityProfile.name,
};
@ -130,11 +133,9 @@ function getInfoRowProps(
uiSettings;
return {
title: `Previous Airing: ${formatDateTime(
previousAiring,
longDateFormat,
timeFormat
)}`,
title: translate('PreviousAiringDate', {
date: formatDateTime(previousAiring, longDateFormat, timeFormat),
}),
iconName: icons.CALENDAR,
label:
getRelativeDate(previousAiring, shortDateFormat, showRelativeDates, {
@ -150,7 +151,9 @@ function getInfoRowProps(
uiSettings;
return {
title: `Added: ${formatDateTime(added, longDateFormat, timeFormat)}`,
title: translate('AddedDate', {
date: formatDateTime(added, longDateFormat, timeFormat),
}),
iconName: icons.ADD,
label:
getRelativeDate(added, shortDateFormat, showRelativeDates, {
@ -162,16 +165,16 @@ function getInfoRowProps(
if (name === 'seasonCount') {
const { seasonCount } = props;
let seasons = '1 season';
let seasons = translate('OneSeason');
if (seasonCount === 0) {
seasons = 'No seasons';
seasons = translate('NoSeasons');
} else if (seasonCount > 1) {
seasons = `${seasonCount} seasons`;
seasons = translate('CountSeasons', { count: seasonCount });
}
return {
title: 'Season Count',
title: translate('SeasonCount'),
iconName: icons.CIRCLE,
label: seasons,
};
@ -179,7 +182,7 @@ function getInfoRowProps(
if (name === 'path') {
return {
title: 'Path',
title: translate('Path'),
iconName: icons.FOLDER,
label: props.path,
};
@ -187,7 +190,7 @@ function getInfoRowProps(
if (name === 'sizeOnDisk') {
return {
title: 'Size on Disk',
title: translate('SizeOnDisk'),
iconName: icons.DRIVE,
label: formatBytes(props.sizeOnDisk),
};

View File

@ -12,11 +12,27 @@ import ModalHeader from 'Components/Modal/ModalHeader';
import { inputTypes } from 'Helpers/Props';
import selectPosterOptions from 'Series/Index/Posters/selectPosterOptions';
import { setSeriesPosterOption } from 'Store/Actions/seriesIndexActions';
import translate from 'Utilities/String/translate';
const posterSizeOptions = [
{ key: 'small', value: 'Small' },
{ key: 'medium', value: 'Medium' },
{ key: 'large', value: 'Large' },
{
key: 'small',
get value() {
return translate('Small');
},
},
{
key: 'medium',
get value() {
return translate('Medium');
},
},
{
key: 'large',
get value() {
return translate('Large');
},
},
];
interface SeriesIndexPosterOptionsModalContentProps {
@ -50,12 +66,12 @@ function SeriesIndexPosterOptionsModalContent(
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>Poster Options</ModalHeader>
<ModalHeader>{translate('PosterOptions')}</ModalHeader>
<ModalBody>
<Form>
<FormGroup>
<FormLabel>Poster Size</FormLabel>
<FormLabel>{translate('PosterSize')}</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
@ -67,61 +83,61 @@ function SeriesIndexPosterOptionsModalContent(
</FormGroup>
<FormGroup>
<FormLabel>Detailed Progress Bar</FormLabel>
<FormLabel>{translate('DetailedProgressBar')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="detailedProgressBar"
value={detailedProgressBar}
helpText="Show text on progress bar"
helpText={translate('DetailedProgressBarHelpText')}
onChange={onPosterOptionChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>Show Title</FormLabel>
<FormLabel>{translate('ShowTitle')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="showTitle"
value={showTitle}
helpText="Show series title under poster"
helpText={translate('ShowTitleHelpText')}
onChange={onPosterOptionChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>Show Monitored</FormLabel>
<FormLabel>{translate('ShowMonitored')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="showMonitored"
value={showMonitored}
helpText="Show monitored status under poster"
helpText={translate('ShowMonitoredHelpText')}
onChange={onPosterOptionChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>Show Quality Profile</FormLabel>
<FormLabel>{translate('ShowQualityProfile')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="showQualityProfile"
value={showQualityProfile}
helpText="Show quality profile under poster"
helpText={translate('ShowQualityProfileHelpText')}
onChange={onPosterOptionChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>Show Search</FormLabel>
<FormLabel>{translate('ShowSearch')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="showSearchAction"
value={showSearchAction}
helpText="Show search button on hover"
helpText={translate('ShowSearchHelpText')}
onChange={onPosterOptionChange}
/>
</FormGroup>
@ -129,7 +145,7 @@ function SeriesIndexPosterOptionsModalContent(
</ModalBody>
<ModalFooter>
<Button onPress={onModalClose}>Close</Button>
<Button onPress={onModalClose}>{translate('Close')}</Button>
</ModalFooter>
</ModalContent>
);

View File

@ -6,6 +6,7 @@ import createSeriesQueueItemsDetailsSelector, {
SeriesQueueDetails,
} from 'Series/Index/createSeriesQueueDetailsSelector';
import getProgressBarKind from 'Utilities/Series/getProgressBarKind';
import translate from 'Utilities/String/translate';
import styles from './SeriesIndexProgressBar.css';
interface SeriesIndexProgressBarProps {
@ -59,7 +60,12 @@ function SeriesIndexProgressBar(props: SeriesIndexProgressBarProps) {
size={detailedProgressBar ? sizes.MEDIUM : sizes.SMALL}
showText={detailedProgressBar}
text={text}
title={`${episodeFileCount} / ${episodeCount} (Total: ${totalEpisodeCount}, Downloading: ${queueDetails.count})`}
title={translate('SeriesProgressBarText', {
episodeFileCount,
episodeCount,
totalEpisodeCount,
downloadingCount: queueDetails.count,
})}
width={width}
/>
);

View File

@ -16,6 +16,7 @@ import Series from 'Series/Series';
import { bulkDeleteSeries, setDeleteOption } from 'Store/Actions/seriesActions';
import createAllSeriesSelector from 'Store/Selectors/createAllSeriesSelector';
import { CheckInputChanged } from 'typings/inputs';
import translate from 'Utilities/String/translate';
import styles from './DeleteSeriesModalContent.css';
interface DeleteSeriesModalContentProps {
@ -86,34 +87,38 @@ function DeleteSeriesModalContent(props: DeleteSeriesModalContentProps) {
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>Delete Selected Series</ModalHeader>
<ModalHeader>{translate('DeleteSelectedSeries')}</ModalHeader>
<ModalBody>
<div>
<FormGroup>
<FormLabel>Add List Exclusion</FormLabel>
<FormLabel>{translate('AddListExclusion')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="addImportListExclusion"
value={addImportListExclusion}
helpText="Prevent series from being added to Sonarr by lists"
helpText={translate('AddListExclusionHelpText')}
onChange={onDeleteOptionChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>{`Delete Series Folder${
series.length > 1 ? 's' : ''
}`}</FormLabel>
<FormLabel>
{series.length > 1
? translate('DeleteSeriesFolders')
: translate('DeleteSeriesFolder')}
</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="deleteFiles"
value={deleteFiles}
helpText={`Delete Series Folder${
series.length > 1 ? 's' : ''
} and all contents`}
helpText={
series.length > 1
? translate('DeleteSeriesFoldersHelpText')
: translate('DeleteSeriesFolderHelpText')
}
kind={kinds.DANGER}
onChange={onDeleteFilesChange}
/>
@ -121,9 +126,13 @@ function DeleteSeriesModalContent(props: DeleteSeriesModalContentProps) {
</div>
<div className={styles.message}>
{`Are you sure you want to delete ${series.length} selected series${
deleteFiles ? ' and all contents' : ''
}?`}
{deleteFiles
? translate('DeleteSeriesFolderCountWithFilesConfirmation', {
count: series.length,
})
: translate('DeleteSeriesFolderCountConfirmation', {
count: series.length,
})}
</div>
<ul>
@ -144,10 +153,10 @@ function DeleteSeriesModalContent(props: DeleteSeriesModalContentProps) {
</ModalBody>
<ModalFooter>
<Button onPress={onModalClose}>Cancel</Button>
<Button onPress={onModalClose}>{translate('Cancel')}</Button>
<Button kind={kinds.DANGER} onPress={onDeleteSeriesConfirmed}>
Delete
{translate('Delete')}
</Button>
</ModalFooter>
</ModalContent>

View File

@ -30,15 +30,47 @@ interface EditSeriesModalContentProps {
const NO_CHANGE = 'noChange';
const monitoredOptions = [
{ key: NO_CHANGE, value: 'No Change', disabled: true },
{ key: 'monitored', value: 'Monitored' },
{ key: 'unmonitored', value: 'Unmonitored' },
{
key: NO_CHANGE,
get value() {
return translate('NoChange');
},
disabled: true,
},
{
key: 'monitored',
get value() {
return translate('Monitored');
},
},
{
key: 'unmonitored',
get value() {
return translate('Unmonitored');
},
},
];
const seasonFolderOptions = [
{ key: NO_CHANGE, value: 'No Change', disabled: true },
{ key: 'yes', value: 'Yes' },
{ key: 'no', value: 'No' },
{
key: NO_CHANGE,
get value() {
return translate('NoChange');
},
disabled: true,
},
{
key: 'yes',
get value() {
return translate('Yes');
},
},
{
key: 'no',
get value() {
return translate('No');
},
},
];
function EditSeriesModalContent(props: EditSeriesModalContentProps) {
@ -189,9 +221,7 @@ function EditSeriesModalContent(props: EditSeriesModalContentProps) {
value={seriesType}
includeNoChange={true}
includeNoChangeDisabled={false}
helpText={translate(
'Series type is used for renaming, parsing and searching'
)}
helpText={translate('SeriesTypesHelpText')}
onChange={onInputChange}
/>
</FormGroup>
@ -218,9 +248,7 @@ function EditSeriesModalContent(props: EditSeriesModalContentProps) {
includeNoChange={true}
includeNoChangeDisabled={false}
selectedValueOptions={{ includeFreeSpace: false }}
helpText={translate(
'Moving series to the same root folder can be used to rename series folders to match updated title or naming format'
)}
helpText={translate('SeriesEditRootFolderHelpText')}
onChange={onInputChange}
/>
</FormGroup>
@ -228,7 +256,7 @@ function EditSeriesModalContent(props: EditSeriesModalContentProps) {
<ModalFooter className={styles.modalFooter}>
<div className={styles.selected}>
{translate('{count} series selected', { count: selectedCount })}
{translate('CountSeriesSelected', { count: selectedCount })}
</div>
<div>

View File

@ -13,6 +13,7 @@ import { icons, kinds } from 'Helpers/Props';
import Series from 'Series/Series';
import { executeCommand } from 'Store/Actions/commandActions';
import createAllSeriesSelector from 'Store/Selectors/createAllSeriesSelector';
import translate from 'Utilities/String/translate';
import styles from './OrganizeSeriesModalContent.css';
interface OrganizeSeriesModalContentProps {
@ -55,18 +56,20 @@ function OrganizeSeriesModalContent(props: OrganizeSeriesModalContentProps) {
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>Organize Selected Series</ModalHeader>
<ModalHeader>
{translate('OrganizeSelectedSeriesModalHeader')}
</ModalHeader>
<ModalBody>
<Alert>
Tip: To preview a rename, select "Cancel", then select any series
title and use the
{translate('OrganizeSelectedSeriesModalAlert')}
<Icon className={styles.renameIcon} name={icons.ORGANIZE} />
</Alert>
<div className={styles.message}>
Are you sure you want to organize all files in the{' '}
{seriesTitles.length} selected series?
{translate('OrganizeSelectedSeriesModalConfirmation', {
count: seriesTitles.length,
})}
</div>
<ul>
@ -77,10 +80,10 @@ function OrganizeSeriesModalContent(props: OrganizeSeriesModalContentProps) {
</ModalBody>
<ModalFooter>
<Button onPress={onModalClose}>Cancel</Button>
<Button onPress={onModalClose}>{translate('Cancel')}</Button>
<Button kind={kinds.DANGER} onPress={onOrganizePress}>
Organize
{translate('Organize')}
</Button>
</ModalFooter>
</ModalContent>

View File

@ -75,7 +75,7 @@ function ChangeMonitoringModalContent(
<ModalFooter className={styles.modalFooter}>
<div className={styles.selected}>
{translate('{count} series selected', { count: selectedCount })}
{translate('CountSeriesSelected', { count: selectedCount })}
</div>
<div>

View File

@ -1,5 +1,6 @@
import React, { useMemo } from 'react';
import { Season } from 'Series/Series';
import translate from 'Utilities/String/translate';
import SeasonPassSeason from './SeasonPassSeason';
import styles from './SeasonDetails.css';
@ -39,7 +40,7 @@ function SeasonDetails(props: SeasonDetailsProps) {
{latestSeasons.length < seasons.length ? (
<div className={styles.truncated}>
Only latest 25 seasons are shown, go to details to see all seasons
{translate('SeasonPassTruncated')}
</div>
) : null}
</div>

View File

@ -2,9 +2,10 @@ import classNames from 'classnames';
import React, { useCallback } from 'react';
import { useDispatch } from 'react-redux';
import MonitorToggleButton from 'Components/MonitorToggleButton';
import formatSeason from 'Season/formatSeason';
import { Statistics } from 'Series/Series';
import { toggleSeasonMonitored } from 'Store/Actions/seriesActions';
import padNumber from 'Utilities/Number/padNumber';
import translate from 'Utilities/String/translate';
import styles from './SeasonPassSeason.css';
interface SeasonPassSeasonProps {
@ -46,9 +47,7 @@ function SeasonPassSeason(props: SeasonPassSeasonProps) {
onPress={onSeasonMonitoredPress}
/>
<span>
{seasonNumber === 0 ? 'Specials' : `S${padNumber(seasonNumber, 2)}`}
</span>
<span>{formatSeason(seasonNumber, true)}</span>
</div>
<div
@ -56,7 +55,10 @@ function SeasonPassSeason(props: SeasonPassSeasonProps) {
styles.episodes,
percentOfEpisodes === 100 && styles.allEpisodes
)}
title={`${episodeFileCount}/${totalEpisodeCount} episodes downloaded`}
title={translate('SeasonPassEpisodesDownloaded', {
episodeFileCount,
totalEpisodeCount,
})}
>
{totalEpisodeCount === 0
? '0/0'

View File

@ -2,6 +2,7 @@ import React, { useCallback } from 'react';
import { useSelect } from 'App/SelectContext';
import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
import { icons } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
interface SeriesIndexSelectAllButtonProps {
label: string;
@ -30,7 +31,7 @@ function SeriesIndexSelectAllButton(props: SeriesIndexSelectAllButtonProps) {
return isSelectMode ? (
<PageToolbarButton
label={allSelected ? 'Unselect All' : 'Select All'}
label={allSelected ? translate('UnselectAll') : translate('SelectAll')}
iconName={icon}
onPress={onPress}
/>

View File

@ -2,6 +2,7 @@ import React, { useCallback } from 'react';
import { useSelect } from 'App/SelectContext';
import PageToolbarOverflowMenuItem from 'Components/Page/Toolbar/PageToolbarOverflowMenuItem';
import { icons } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
interface SeriesIndexSelectAllMenuItemProps {
label: string;
@ -31,7 +32,7 @@ function SeriesIndexSelectAllMenuItem(
return isSelectMode ? (
<PageToolbarOverflowMenuItem
label={allSelected ? 'Unselect All' : 'Select All'}
label={allSelected ? translate('UnselectAll') : translate('SelectAll')}
iconName={iconName}
onPress={onPressWrapper}
/>

View File

@ -231,7 +231,7 @@ function SeriesIndexSelectFooter() {
</div>
<div className={styles.selected}>
{translate('{count} series selected', { count: selectedCount })}
{translate('CountSeriesSelected', { count: selectedCount })}
</div>
<EditSeriesModal

View File

@ -67,9 +67,18 @@ function TagsModalContent(props: TagsModalContentProps) {
}, [tags, applyTags, onApplyTagsPress]);
const applyTagsOptions = [
{ key: 'add', value: 'Add' },
{ key: 'remove', value: 'Remove' },
{ key: 'replace', value: 'Replace' },
{
key: 'add',
value: translate('Add'),
},
{
key: 'remove',
value: translate('Remove'),
},
{
key: 'replace',
value: translate('Replace'),
},
];
return (

View File

@ -38,6 +38,7 @@ import scrollPositions from 'Store/scrollPositions';
import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector';
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
import createSeriesClientSideCollectionItemsSelector from 'Store/Selectors/createSeriesClientSideCollectionItemsSelector';
import translate from 'Utilities/String/translate';
import SeriesIndexFilterMenu from './Menus/SeriesIndexFilterMenu';
import SeriesIndexSortMenu from './Menus/SeriesIndexSortMenu';
import SeriesIndexViewMenu from './Menus/SeriesIndexViewMenu';
@ -225,7 +226,7 @@ const SeriesIndex = withScrollPosition((props: SeriesIndexProps) => {
/>
<PageToolbarButton
label="RSS Sync"
label={translate('RssSync')}
iconName={icons.RSS}
isSpinning={isRssSyncExecuting}
isDisabled={hasNoSeries}
@ -235,7 +236,11 @@ const SeriesIndex = withScrollPosition((props: SeriesIndexProps) => {
<PageToolbarSeparator />
<SeriesIndexSelectModeButton
label={isSelectMode ? 'Stop Selecting' : 'Select Series'}
label={
isSelectMode
? translate('StopSelecting')
: translate('SelectSeries')
}
iconName={isSelectMode ? icons.SERIES_ENDED : icons.CHECK}
isSelectMode={isSelectMode}
overflowComponent={SeriesIndexSelectModeMenuItem}
@ -262,11 +267,14 @@ const SeriesIndex = withScrollPosition((props: SeriesIndexProps) => {
optionsComponent={SeriesIndexTableOptions}
onTableOptionChange={onTableOptionChange}
>
<PageToolbarButton label="Options" iconName={icons.TABLE} />
<PageToolbarButton
label={translate('Options')}
iconName={icons.TABLE}
/>
</TableOptionsModalWrapper>
) : (
<PageToolbarButton
label="Options"
label={translate('Options')}
iconName={view === 'posters' ? icons.POSTER : icons.OVERVIEW}
isDisabled={hasNoSeries}
onPress={onOptionsPress}
@ -310,7 +318,7 @@ const SeriesIndex = withScrollPosition((props: SeriesIndexProps) => {
{isFetching && !isPopulated ? <LoadingIndicator /> : null}
{!isFetching && !!error ? (
<Alert kind={kinds.DANGER}>Unable to load series</Alert>
<Alert kind={kinds.DANGER}>{translate('SeriesLoadError')}</Alert>
) : null}
{isLoaded ? (

View File

@ -9,6 +9,7 @@ import DescriptionListItem from 'Components/DescriptionList/DescriptionListItem'
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
import createDeepEqualSelector from 'Store/Selectors/createDeepEqualSelector';
import formatBytes from 'Utilities/Number/formatBytes';
import translate from 'Utilities/String/translate';
import styles from './SeriesIndexFooter.css';
function createUnoptimizedSelector() {
@ -85,7 +86,7 @@ export default function SeriesIndexFooter() {
enableColorImpairedMode && 'colorImpaired'
)}
/>
<div>Continuing (All episodes downloaded)</div>
<div>{translate('SeriesIndexFooterContinuing')}</div>
</div>
<div className={styles.legendItem}>
@ -95,7 +96,7 @@ export default function SeriesIndexFooter() {
enableColorImpairedMode && 'colorImpaired'
)}
/>
<div>Ended (All episodes downloaded)</div>
<div>{translate('SeriesIndexFooterEnded')}</div>
</div>
<div className={styles.legendItem}>
@ -105,7 +106,7 @@ export default function SeriesIndexFooter() {
enableColorImpairedMode && 'colorImpaired'
)}
/>
<div>Missing Episodes (Series monitored)</div>
<div>{translate('SeriesIndexFooterMissingMonitored')}</div>
</div>
<div className={styles.legendItem}>
@ -115,7 +116,7 @@ export default function SeriesIndexFooter() {
enableColorImpairedMode && 'colorImpaired'
)}
/>
<div>Missing Episodes (Series not monitored)</div>
<div>{translate('SeriesIndexFooterMissingUnmonitored')}</div>
</div>
<div className={styles.legendItem}>
@ -125,37 +126,49 @@ export default function SeriesIndexFooter() {
enableColorImpairedMode && 'colorImpaired'
)}
/>
<div>Downloading (One or more episodes)</div>
<div>{translate('SeriesIndexFooterDownloading')}</div>
</div>
</div>
<div className={styles.statistics}>
<DescriptionList>
<DescriptionListItem title="Series" data={count} />
<DescriptionListItem title={translate('Series')} data={count} />
<DescriptionListItem title="Ended" data={ended} />
<DescriptionListItem title={translate('Ended')} data={ended} />
<DescriptionListItem title="Continuing" data={continuing} />
<DescriptionListItem
title={translate('Continuing')}
data={continuing}
/>
</DescriptionList>
<DescriptionList>
<DescriptionListItem title="Monitored" data={monitored} />
<DescriptionListItem
title={translate('Monitored')}
data={monitored}
/>
<DescriptionListItem
title="Unmonitored"
title={translate('Unmonitored')}
data={count - monitored}
/>
</DescriptionList>
<DescriptionList>
<DescriptionListItem title="Episodes" data={episodes} />
<DescriptionListItem
title={translate('Episodes')}
data={episodes}
/>
<DescriptionListItem title="Files" data={episodeFiles} />
<DescriptionListItem
title={translate('Files')}
data={episodeFiles}
/>
</DescriptionList>
<DescriptionList>
<DescriptionListItem
title="Total File Size"
title={translate('TotalFileSize')}
data={formatBytes(totalFileSize)}
/>
</DescriptionList>

View File

@ -9,6 +9,7 @@ import { icons } from 'Helpers/Props';
import { executeCommand } from 'Store/Actions/commandActions';
import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector';
import createSeriesClientSideCollectionItemsSelector from 'Store/Selectors/createSeriesClientSideCollectionItemsSelector';
import translate from 'Utilities/String/translate';
import getSelectedIds from 'Utilities/Table/getSelectedIds';
interface SeriesIndexRefreshSeriesButtonProps {
@ -42,12 +43,12 @@ function SeriesIndexRefreshSeriesButton(
? selectedSeriesIds
: items.map((m) => m.id);
let refreshLabel = 'Update All';
let refreshLabel = translate('UpdateAll');
if (selectedSeriesIds.length > 0) {
refreshLabel = 'Update Selected';
refreshLabel = translate('UpdateSelected');
} else if (selectedFilterKey !== 'all') {
refreshLabel = 'Update Filtered';
refreshLabel = translate('UpdateFiltered');
}
const onPress = useCallback(() => {

View File

@ -24,6 +24,7 @@ import { executeCommand } from 'Store/Actions/commandActions';
import { SelectStateInputProps } from 'typings/props';
import formatBytes from 'Utilities/Number/formatBytes';
import titleCase from 'Utilities/String/titleCase';
import translate from 'Utilities/String/translate';
import SeriesIndexProgressBar from '../ProgressBar/SeriesIndexProgressBar';
import hasGrowableColumns from './hasGrowableColumns';
import SeasonsCell from './SeasonsCell';
@ -454,7 +455,7 @@ function SeriesIndexRow(props: SeriesIndexRowProps) {
<VirtualTableRowCell key={name} className={styles[name]}>
<SpinnerIconButton
name={icons.REFRESH}
title="Refresh series"
title={translate('RefreshSeries')}
isSpinning={isRefreshingSeries}
onPress={onRefreshPress}
/>
@ -462,7 +463,7 @@ function SeriesIndexRow(props: SeriesIndexRowProps) {
{showSearchAction ? (
<SpinnerIconButton
name={icons.SEARCH}
title="Search for monitored episodes"
title={translate('SearchForMonitoredEpisodes')}
isSpinning={isSearchingSeries}
onPress={onSearchPress}
/>
@ -470,7 +471,7 @@ function SeriesIndexRow(props: SeriesIndexRowProps) {
<IconButton
name={icons.EDIT}
title="Edit Series"
title={translate('EditSeries')}
onPress={onEditSeriesPress}
/>
</VirtualTableRowCell>

View File

@ -5,6 +5,7 @@ import FormInputGroup from 'Components/Form/FormInputGroup';
import FormLabel from 'Components/Form/FormLabel';
import { inputTypes } from 'Helpers/Props';
import { CheckInputChanged } from 'typings/inputs';
import translate from 'Utilities/String/translate';
import selectTableOptions from './selectTableOptions';
interface SeriesIndexTableOptionsProps {
@ -33,25 +34,25 @@ function SeriesIndexTableOptions(props: SeriesIndexTableOptionsProps) {
return (
<Fragment>
<FormGroup>
<FormLabel>Show Banners</FormLabel>
<FormLabel>{translate('ShowBanners')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="showBanners"
value={showBanners}
helpText="Show banners instead of titles"
helpText={translate('ShowBannersHelpText')}
onChange={onTableOptionChangeWrapper}
/>
</FormGroup>
<FormGroup>
<FormLabel>Show Search</FormLabel>
<FormLabel>{translate('ShowSearch')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="showSearchAction"
value={showSearchAction}
helpText="Show search button on hover"
helpText={translate('ShowSearchHelpText')}
onChange={onTableOptionChangeWrapper}
/>
</FormGroup>

View File

@ -53,8 +53,8 @@ function SeriesStatusCell(props: SeriesStatusCellProps) {
name={monitored ? icons.MONITORED : icons.UNMONITORED}
title={
monitored
? translate('Series is monitored')
: translate('Series is unmonitored')
? translate('SeriesIsMonitored')
: translate('SeriesIsUnmonitored')
}
/>
)}

View File

@ -2,7 +2,7 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { clearPendingChanges } from 'Store/Actions/baseActions';
import MonitoringOptionsModal from './EditSeriesModal';
import MonitoringOptionsModal from './MonitoringOptionsModal';
const mapDispatchToProps = {
clearPendingChanges

View File

@ -14,6 +14,7 @@ import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import Popover from 'Components/Tooltip/Popover';
import { icons, inputTypes, tooltipPositions } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import styles from './MonitoringOptionsModalContent.css';
const NO_CHANGE = 'noChange';
@ -82,14 +83,14 @@ class MonitoringOptionsModalContent extends Component {
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
Monitor Series
{translate('MonitorSeries')}
</ModalHeader>
<ModalBody>
<Form {...otherProps}>
<FormGroup>
<FormLabel>
Monitoring
{translate('Monitoring')}
<Popover
anchor={
@ -98,7 +99,7 @@ class MonitoringOptionsModalContent extends Component {
name={icons.INFO}
/>
}
title="Monitoring Options"
title={translate('MonitoringOptions')}
body={<SeriesMonitoringOptionsPopoverContent />}
position={tooltipPositions.RIGHT}
/>
@ -119,14 +120,14 @@ class MonitoringOptionsModalContent extends Component {
<Button
onPress={onModalClose}
>
Cancel
{translate('Cancel')}
</Button>
<SpinnerButton
isSpinning={isSaving}
onPress={this.onSavePress}
>
Save
{translate('Save')}
</SpinnerButton>
</ModalFooter>
</ModalContent>

View File

@ -7,6 +7,7 @@ import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { kinds, sizes } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import styles from './MoveSeriesModal.css';
function MoveSeriesModal(props) {
@ -26,7 +27,7 @@ function MoveSeriesModal(props) {
!destinationPath &&
!destinationRootFolder
) {
console.error('orginalPath and destinationPath OR destinationRootFolder must be provided');
console.error('originalPath and destinationPath OR destinationRootFolder must be provided');
}
return (
@ -41,14 +42,14 @@ function MoveSeriesModal(props) {
onModalClose={onModalClose}
>
<ModalHeader>
Move Files
{translate('MoveFiles')}
</ModalHeader>
<ModalBody>
{
destinationRootFolder ?
`Would you like to move the series folders to '${destinationRootFolder}'?` :
`Would you like to move the series files from '${originalPath}' to '${destinationPath}'?`
translate('MoveSeriesFoldersToRootFolder', { destinationRootFolder }) :
translate('MoveSeriesFoldersToNewPath', { originalPath, destinationPath })
}
</ModalBody>
@ -57,14 +58,14 @@ function MoveSeriesModal(props) {
className={styles.doNotMoveButton}
onPress={onSavePress}
>
No, I'll Move the Files Myself
{translate('MoveSeriesFoldersDontMoveFiles')}
</Button>
<Button
kind={kinds.DANGER}
onPress={onMoveSeriesPress}
>
Yes, Move the Files
{translate('MoveSeriesFoldersMoveFiles')}
</Button>
</ModalFooter>
</ModalContent>

View File

@ -2,6 +2,7 @@ import PropTypes from 'prop-types';
import React from 'react';
import Button from 'Components/Link/Button';
import { kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import styles from './NoSeries.css';
function NoSeries(props) {
@ -11,7 +12,7 @@ function NoSeries(props) {
return (
<div>
<div className={styles.message}>
All series are hidden due to the applied filter.
{translate('AllSeriesAreHiddenByTheAppliedFilter')}
</div>
</div>
);
@ -20,7 +21,7 @@ function NoSeries(props) {
return (
<div>
<div className={styles.message}>
No series found, to get started you'll want to import your existing series or add a new series.
{translate('NoSeriesFoundImportOrAdd')}
</div>
<div className={styles.buttonContainer}>
@ -28,7 +29,7 @@ function NoSeries(props) {
to="/add/import"
kind={kinds.PRIMARY}
>
Import Existing Series
{translate('ImportExistingSeries')}
</Button>
</div>
@ -37,7 +38,7 @@ function NoSeries(props) {
to="/add/new"
kind={kinds.PRIMARY}
>
Add New Series
{translate('AddNewSeries')}
</Button>
</div>
</div>

View File

@ -8,6 +8,7 @@ import ModalHeader from 'Components/Modal/ModalHeader';
import { scrollDirections } from 'Helpers/Props';
import InteractiveSearchConnector from 'InteractiveSearch/InteractiveSearchConnector';
import formatSeason from 'Season/formatSeason';
import translate from 'Utilities/String/translate';
function SeasonInteractiveSearchModalContent(props) {
const {
@ -19,7 +20,10 @@ function SeasonInteractiveSearchModalContent(props) {
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
Interactive Search {seasonNumber != null && formatSeason(seasonNumber)}
{seasonNumber === null ?
translate('InteractiveSearchModalHeader') :
translate('InteractiveSearchModalHeaderSeason', { season: formatSeason(seasonNumber) })
}
</ModalHeader>
<ModalBody scrollDirection={scrollDirections.BOTH}>
@ -34,7 +38,7 @@ function SeasonInteractiveSearchModalContent(props) {
<ModalFooter>
<Button onPress={onModalClose}>
Close
{translate('Close')}
</Button>
</ModalFooter>
</ModalContent>

View File

@ -1,31 +1,32 @@
import { icons } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
export function getSeriesStatusDetails(status) {
let statusDetails = {
icon: icons.SERIES_CONTINUING,
title: 'Continuing',
message: 'More episodes/another season is expected'
title: translate('Continuing'),
message: translate('ContinuingSeriesDescription')
};
if (status === 'deleted') {
statusDetails = {
icon: icons.SERIES_DELETED,
title: 'Deleted',
message: 'Series was deleted from TheTVDB'
title: translate('Deleted'),
message: translate('DeletedSeriesDescription')
};
} else if (status === 'ended') {
statusDetails = {
icon: icons.SERIES_ENDED,
title: 'Ended',
message: 'No additional episodes or seasons are expected'
title: translate('Ended'),
message: translate('EndedSeriesDescription')
};
} else if (status === 'upcoming') {
statusDetails = {
icon: icons.SERIES_CONTINUING,
title: 'Upcoming',
message: 'Series has been announced but no exact air date yet'
title: translate('Upcoming'),
message: translate('UpcomingSeriesDescription')
};
}

View File

@ -31,7 +31,9 @@
"AddIndexerImplementation": "Add Indexer - {implementationName}",
"AddList": "Add List",
"AddListError": "Unable to add a new list, please try again.",
"AddListExclusion": "Add List Exclusion",
"AddListExclusionError": "Unable to add a new list exclusion, please try again.",
"AddListExclusionHelpText": "Prevent series from being added to Sonarr by lists",
"AddNew": "Add New",
"AddNewRestriction": "Add new restriction",
"AddNewSeries": "Add New Series",
@ -50,6 +52,7 @@
"AddSeriesWithTitle": "Add {title}",
"AddToDownloadQueue": "Add to download queue",
"Added": "Added",
"AddedDate": "Added: {date}",
"AddedToDownloadQueue": "Added to download queue",
"AddingTag": "Adding tag",
"AfterManualRefresh": "After Manual Refresh",
@ -65,9 +68,11 @@
"All": "All",
"AllFiles": "All Files",
"AllResultsAreHiddenByTheAppliedFilter": "All results are hidden by the applied filter",
"AllSeriesAreHiddenByTheAppliedFilter": "All results are hidden by the applied filter",
"AllSeriesInRootFolderHaveBeenImported": "All series in {path} have been imported",
"AllTitles": "All Titles",
"AlreadyInYourLibrary": "Already in your library",
"AlternateTitles": "Alternate Titles",
"Always": "Always",
"AnEpisodeIsDownloading": "An Episode is downloading",
"AnalyseVideoFiles": "Analyse video files",
@ -192,6 +197,7 @@
"CloneIndexer": "Clone Indexer",
"CloneProfile": "Clone Profile",
"Close": "Close",
"CollapseAll": "Collapse All",
"CollapseMultipleEpisodes": "Collapse Multiple Episodes",
"CollapseMultipleEpisodesHelpText": "Collapse multiple episodes airing on the same day",
"CollectionsLoadError": "Unable to load collections",
@ -212,6 +218,7 @@
"Connections": "Connections",
"Continuing": "Continuing",
"ContinuingOnly": "Continuing Only",
"ContinuingSeriesDescription": "More episodes/another season is expected",
"CopyToClipboard": "Copy to Clipboard",
"CopyUsingHardlinksHelpText": "Hardlinks allow Sonarr to import seeding torrents to the series folder without taking extra disk space or copying the entire contents of the file. Hardlinks will only work if the source and destination are on the same volume",
"CopyUsingHardlinksHelpTextWarning": "Occasionally, file locks may prevent renaming files that are being seeded. You may temporarily disable seeding and use Sonarr's rename function as a work around.",
@ -222,6 +229,7 @@
"CountSeasons": "{count} Seasons",
"CountSelectedFile": "{selectedCount} selected file",
"CountSelectedFiles": "{selectedCount} selected files",
"CountSeriesSelected": "{count} series selected",
"CreateEmptySeriesFolders": "Create Empty Series Folders",
"CreateEmptySeriesFoldersHelpText": "Create missing series folders during disk scan",
"CreateGroup": "Create Group",
@ -279,6 +287,8 @@
"DeleteEpisodeFile": "Delete Episode File",
"DeleteEpisodeFileMessage": "Are you sure you want to delete '{path}'?",
"DeleteEpisodeFromDisk": "Delete episode from disk",
"DeleteEpisodesFiles": "Delete {episodeFileCount} Episode Files",
"DeleteEpisodesFilesHelpText": "Delete the episode files and series folder",
"DeleteImportList": "Delete Import List",
"DeleteImportListExclusion": "Delete Import List Exclusion",
"DeleteImportListExclusionMessageText": "Are you sure you want to delete this import list exclusion?",
@ -303,6 +313,16 @@
"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)?",
"DeleteSelectedSeries": "Delete Selected Series",
"DeleteSeriesFolder": "Delete Series Folder",
"DeleteSeriesFolderConfirmation": "The series folder `{path}` and all of its content will be deleted.",
"DeleteSeriesFolderCountConfirmation": "Are you sure you want to delete {count} selected series?",
"DeleteSeriesFolderCountWithFilesConfirmation": "Are you sure you want to delete {count} selected series and all contents?",
"DeleteSeriesFolderEpisodeCount": "{episodeFileCount} episode files totaling {size}",
"DeleteSeriesFolderHelpText": "Delete the series folder and its contents",
"DeleteSeriesFolders": "Delete Series Folders",
"DeleteSeriesFoldersHelpText": "Delete the series folders and all their contents",
"DeleteSeriesModalHeader": "Delete - {title}",
"DeleteSpecification": "Delete Specification",
"DeleteSpecificationHelpText": "Are you sure you want to delete specification '{name}'?",
"DeleteTag": "DeleteTag",
@ -311,8 +331,11 @@
"DeletedReasonManual": "File was deleted by via UI",
"DeletedReasonMissingFromDisk": "Sonarr was unable to find the file on disk so the file was unlinked from the episode in the database",
"DeletedReasonUpgrade": "File was deleted to import an upgrade",
"DeletedSeriesDescription": "Series was deleted from TheTVDB",
"DestinationPath": "Destination Path",
"DestinationRelativePath": "Destination Relative Path",
"DetailedProgressBar": "Detailed Progress Bar",
"DetailedProgressBarHelpText": "Show text on progress bar",
"Details": "Details",
"Disabled": "Disabled",
"DisabledForLocalAddresses": "Disabled for Local Addresses",
@ -373,7 +396,9 @@
"EditSelectedDownloadClients": "Edit Selected Download Clients",
"EditSelectedImportLists": "Edit Selected Import Lists",
"EditSelectedIndexers": "Edit Selected Indexers",
"EditSelectedSeries": "Edit Selected Series",
"EditSeries": "Edit Series",
"EditSeriesModalHeader": "Edit - {title}",
"Enable": "Enable",
"EnableAutomaticAdd": "Enable Automatic Add",
"EnableAutomaticAddHelpText": "Add series from this list to Sonarr when syncs are performed via the UI or by Sonarr",
@ -398,6 +423,7 @@
"Enabled": "Enabled",
"Ended": "Ended",
"EndedOnly": "Ended Only",
"EndedSeriesDescription": "No additional episodes or seasons are expected",
"Episode": "Episode",
"EpisodeAirDate": "Episode Air Date",
"EpisodeCount": "Episode Count",
@ -406,6 +432,7 @@
"EpisodeFileDeletedTooltip": "Episode file deleted",
"EpisodeFileRenamed": "Episode File Renamed",
"EpisodeFileRenamedTooltip": "Episode file renamed",
"EpisodeFilesLoadError": "Unable to load episode files",
"EpisodeHasNotAired": "Episode has not aired",
"EpisodeHistoryLoadError": "Unable to load episode history",
"EpisodeImported": "Episode Imported",
@ -437,6 +464,7 @@
"Existing": "Existing",
"ExistingSeries": "Existing Series",
"ExistingTag": "Existing tag",
"ExpandAll": "Expand All",
"ExportCustomFormat": "Export Custom Format",
"Extend": "Extend",
"External": "External",
@ -463,6 +491,7 @@
"FileNameTokens": "File Name Tokens",
"FileNames": "File Names",
"Filename": "Filename",
"Files": "Files",
"Filter": "Filter",
"FilterContains": "contains",
"FilterDoesNotContain": "does not contain",
@ -520,8 +549,11 @@
"Here": "here",
"HiddenClickToShow": "Hidden, click to show",
"HideAdvanced": "Hide Advanced",
"HideEpisodes": "Hide episodes",
"History": "History",
"HistoryLoadError": "Unable to load history",
"HistoryModalHeaderSeason": "History {season}",
"HistorySeason": "View history for this season",
"HomePage": "Home Page",
"Host": "Host",
"Hostname": "Hostname",
@ -618,7 +650,10 @@
"InteractiveImportNoSeason": "Season must be chosen for each selected file",
"InteractiveImportNoSeries": "Series must be chosen for each selected file",
"InteractiveSearch": "Interactive Search",
"InteractiveSearchModalHeader": "Interactive Search",
"InteractiveSearchModalHeaderSeason": "Interactive Search - {season}",
"InteractiveSearchResultsFailedErrorMessage": "Search failed because its {message}. Try refreshing the series info and verify the necessary information is present before searching again.",
"InteractiveSearchSeason": "Interactive search for all episodes in this season",
"Interval": "Interval",
"InvalidFormat": "Invalid Format",
"KeyboardShortcuts": "Keyboard Shortcuts",
@ -630,6 +665,7 @@
"Language": "Language",
"Languages": "Languages",
"LanguagesLoadError": "Unable to load languages",
"Large": "Large",
"LastDuration": "Last Duration",
"LastExecution": "Last Execution",
"LastUsed": "Last Used",
@ -643,6 +679,7 @@
"LibraryImportTipsDontUseDownloadsFolder": "Do not use for importing downloads from your download client, this is only for existing organized libraries, not unsorted files.",
"LibraryImportTipsQualityInFilename": "Make sure that your files include the quality in their filenames. eg. `episode.s02e15.bluray.mkv`",
"LibraryImportTipsUseRootFolder": "Point Sonarr to the folder containing all of your tv shows, not a specific one. eg. \"`{goodFolderExample}`\" and not \"`{badFolderExample}`\". Additionally, each series must be in its own folder within the root/library folder.",
"Links": "Links",
"ListExclusionsLoadError": "Unable to load List Exclusions",
"ListOptionsLoadError": "Unable to load list options",
"ListQualityProfileHelpText": "Quality Profile list items will be added with",
@ -667,6 +704,8 @@
"MaintenanceRelease": "Maintenance Release: bug fixes and other improvements. See Github Commit History for more details",
"ManageClients": "Manage Clients",
"ManageDownloadClients": "Manage Download Clients",
"ManageEpisodes": "Manage Episodes",
"ManageEpisodesSeason": "Manage Episodes files in this season",
"ManageImportLists": "Manage Import Lists",
"ManageIndexers": "Manage Indexers",
"ManageLists": "Manage Lists",
@ -694,6 +733,7 @@
"MediaManagementSettings": "Media Management Settings",
"MediaManagementSettingsLoadError": "Unable to load Media Management settings",
"MediaManagementSettingsSummary": "Naming, file management settings and root folders",
"Medium": "Medium",
"MegabytesPerMinute": "Megabytes Per Minute",
"Message": "Message",
"Metadata": "Metadata",
@ -736,10 +776,14 @@
"MonitorMissingEpisodesDescription": "Monitor episodes that do not have files or have not aired yet",
"MonitorNone": "None",
"MonitorNoneDescription": "No episodes will be monitored",
"MonitorSeries": "Monitor Series",
"MonitorSpecials": "Monitor Specials",
"MonitorSpecialsDescription": "Monitor all special episodes without changing the monitored status of other episodes",
"Monitored": "Monitored",
"MonitoredHelpText": "Download monitored episodes in this series",
"MonitoredOnly": "Monitored Only",
"MonitoredStatus": "Monitored/Status",
"Monitoring": "Monitoring",
"MonitoringOptions": "Monitoring Options",
"Month": "Month",
"More": "More",
@ -748,6 +792,10 @@
"MountHealthCheckMessage": "Mount containing a series path is mounted read-only: ",
"MoveAutomatically": "Move Automatically",
"MoveFiles": "Move Files",
"MoveSeriesFoldersDontMoveFiles": "No, I'll Move the Files Myself",
"MoveSeriesFoldersMoveFiles": "Yes, Move the Files",
"MoveSeriesFoldersToNewPath": "Would you like to move the series files from '{originalPath}' to '{destinationPath}'?",
"MoveSeriesFoldersToRootFolder": "Would you like to move the series folders to '{destinationRootFolder}'?",
"MultiEpisode": "Multi Episode",
"MultiEpisodeInvalidFormat": "Multi Episode: Invalid Format",
"MultiEpisodeStyle": "Multi Episode Style",
@ -776,9 +824,12 @@
"NoDelay": "No Delay",
"NoDownloadClientsFound": "No download clients found",
"NoEpisodeHistory": "No episode history",
"NoEpisodeInformation": "No episode information is available.",
"NoEpisodeOverview": "No episode overview",
"NoEpisodesFoundForSelectedSeason": "No episodes were found for the selected season",
"NoEpisodesInThisSeason": "No episodes in this season",
"NoEventsFound": "No events found",
"NoHistory": "No history",
"NoHistoryBlocklist": "No history blocklist",
"NoHistoryFound": "No history found",
"NoImportListsFound": "No import lists found",
@ -790,8 +841,11 @@
"NoLogFiles": "No log files",
"NoMatchFound": "No match found!",
"NoMinimumForAnyRuntime": "No minimum for any runtime",
"NoMonitoredEpisodes": "No monitored episodes in this series",
"NoMonitoredEpisodesSeason": "No monitored episodes in this season",
"NoResultsFound": "No results found",
"NoSeasons": "No seasons",
"NoSeriesFoundImportOrAdd": "No series found, to get started you'll want to import your existing series or add a new series.",
"NoSeriesHaveBeenAdded": "You haven't added any series yet, do you want to import some or all of your series first?",
"NoTagsHaveBeenAddedYet": "No tags have been added yet",
"NoUpdatesAreAvailable": "No updates are available",
@ -836,6 +890,9 @@
"OrganizeNothingToRename": "Success! My work is done, no files to rename.",
"OrganizeRelativePaths": "All paths are relative to: `{path}`",
"OrganizeRenamingDisabled": "Renaming is disabled, nothing to rename",
"OrganizeSelectedSeriesModalAlert": "Tip: To preview a rename, select \"Cancel\", then select any series title and use this icon:",
"OrganizeSelectedSeriesModalConfirmation": "Are you sure you want to organize all files in the {count} selected series?",
"OrganizeSelectedSeriesModalHeader": "Organize Selected Series",
"Original": "Original",
"OriginalLanguage": "Original Language",
"Other": "Other",
@ -846,6 +903,8 @@
"OverrideGrabNoLanguage": "At least one language must be selected",
"OverrideGrabNoQuality": "Quality must be selected",
"OverrideGrabNoSeries": "Series must be selected",
"Overview": "Overview",
"OverviewOptions": "Overview Options",
"PackageVersion": "Package Version",
"PackageVersionInfo": "{packageVersion} by {packageAuthor}",
"Parse": "Parse",
@ -867,6 +926,9 @@
"Permissions": "Permissions",
"Port": "Port",
"PortNumber": "Port Number",
"PosterOptions": "Poster Options",
"PosterSize": "Poster Size",
"Posters": "Posters",
"PreferAndUpgrade": "Prefer and Upgrade",
"PreferProtocol": "Prefer {preferredProtocol}",
"PreferTorrent": "Prefer Torrent",
@ -876,7 +938,10 @@
"PreferredSize": "Preferred Size",
"PrefixedRange": "Prefixed Range",
"Presets": "Presets",
"PreviewRename": "Preview Rename",
"PreviewRenameSeason": "Preview Rename for this season",
"PreviousAiring": "Previous Airing",
"PreviousAiringDate": "Previous Airing: {date}",
"PreviouslyInstalled": "Previously Installed",
"Priority": "Priority",
"PriorityHelpText": "Prioritize multiple Download Clients. Round-Robin is used for clients with the same priority.",
@ -931,6 +996,8 @@
"RecyclingBinHelpText": "Episode files will go here when deleted instead of being permanently deleted",
"RedownloadFailed": "Redownload Failed",
"Refresh": "Refresh",
"RefreshAndScan": "Refresh & Scan",
"RefreshAndScanTooltip": "Refresh information and scan disk",
"RefreshSeries": "Refresh Series",
"RegularExpression": "Regular Expression",
"RegularExpressionsCanBeTested": "Regular expressions can be tested [here](http://regexstorm.net/tester).",
@ -1009,6 +1076,7 @@
"RemovingTag": "Removing tag",
"RenameEpisodes": "Rename Episodes",
"RenameEpisodesHelpText": "Sonarr will use the existing file name if renaming is disabled",
"RenameFiles": "Rename Files",
"Renamed": "Renamed",
"Reorder": "Reorder",
"Repack": "Repack",
@ -1079,21 +1147,28 @@
"SearchFailedError": "Search failed, please try again later.",
"SearchForMissing": "Search for Missing",
"SearchForMonitoredEpisodes": "Search for monitored episodes",
"SearchForMonitoredEpisodesSeason": "Search for monitored episodes in this season",
"SearchForQuery": "Search for {query}",
"SearchIsNotSupportedWithThisIndexer": "Search is not supported with this indexer",
"SearchMonitored": "Search Monitored",
"Season": "Season",
"SeasonCount": "Season Count",
"SeasonDetails": "Season Details",
"SeasonFinale": "Season Finale",
"SeasonFolder": "Season Folder",
"SeasonFolderFormat": "Season Folder Format",
"SeasonInformation": "Season Information",
"SeasonNumber": "Season Number",
"SeasonNumberToken": "Season {seasonNumber}",
"SeasonPack": "Season Pack",
"SeasonPassEpisodesDownloaded": "{episodeFileCount}/{totalEpisodeCount} episodes downloaded",
"SeasonPassTruncated": "Only latest 25 seasons are shown, go to details to see all seasons",
"SeasonPremiere": "Season Premiere",
"SeasonPremieresOnly": "Season Premieres Only",
"Seasons": "Seasons",
"Security": "Security",
"Seeders": "Seeders",
"SelectAll": "Select All",
"SelectDownloadClientModalTitle": "{modalTitle} - Select Download Client",
"SelectDropdown": "Select...",
"SelectEpisodes": "Select Episode(s)",
@ -1111,15 +1186,31 @@
"SendAnonymousUsageData": "Send Anonymous Usage Data",
"Series": "Series",
"SeriesAndEpisodeInformationIsProvidedByTheTVDB": "Series and episode information is provided by TheTVDB.com. [Please consider supporting them](https://www.thetvdb.com/subscribe).",
"SeriesCannotBeFound": "Sorry, that series cannot be found.",
"SeriesDetailsCountEpisodeFiles": "{episodeFileCount} episode files",
"SeriesDetailsGoTo": "Go to {title}",
"SeriesDetailsNoEpisodeFiles": "No episode files",
"SeriesDetailsOneEpisodeFile": "1 episode file",
"SeriesDetailsRuntime": "{runtime} Minutes",
"SeriesEditRootFolderHelpText": "Moving series to the same root folder can be used to rename series folders to match updated title or naming format",
"SeriesEditor": "Series Editor",
"SeriesFinale": "Series Finale",
"SeriesFolderFormat": "Series Folder Format",
"SeriesFolderFormatHelpText": "Used when adding a new series or moving series via the series editor",
"SeriesFolderImportedTooltip": "Episode imported from series folder",
"SeriesID": "Series ID",
"SeriesIndexFooterContinuing": "Continuing (All episodes downloaded)",
"SeriesIndexFooterDownloading": "Downloading (One or more episodes)",
"SeriesIndexFooterEnded": "Ended (All episodes downloaded)",
"SeriesIndexFooterMissingMonitored": "Missing Episodes (Series monitored)",
"SeriesIndexFooterMissingUnmonitored": "Missing Episodes (Series not monitored)",
"SeriesIsMonitored": "Series is monitored",
"SeriesIsUnmonitored": "Series is unmonitored",
"SeriesLoadError": "Unable to load Series",
"SeriesMatchType": "Series Match Type",
"SeriesMonitoring": "Series Monitoring",
"SeriesPremiere": "Series Premiere",
"SeriesProgressBarText": "{episodeFileCount} / {episodeCount} (Total: {totalEpisodeCount}, Downloading: {downloadingCount})",
"SeriesTitle": "Series Title",
"SeriesTitleToExcludeHelpText": "The name of the series to exclude",
"SeriesType": "Series Type",
@ -1134,10 +1225,27 @@
"Settings": "Settings",
"ShortDateFormat": "Short Date Format",
"ShowAdvanced": "Show Advanced",
"ShowBanners": "Show Banners",
"ShowBannersHelpText": "Show banners instead of titles",
"ShowDateAdded": "Show Date Added",
"ShowEpisodeInformation": "Show Episode Information",
"ShowEpisodeInformationHelpText": "Show episode title and number",
"ShowEpisodes": "Show episodes",
"ShowMonitored": "Show Monitored",
"ShowMonitoredHelpText": "Show monitored status under poster",
"ShowNetwork": "Show Network",
"ShowPath": "Show Path",
"ShowPreviousAiring": "Show Previous Airing",
"ShowQualityProfile": "Show Quality Profile",
"ShowQualityProfileHelpText": "Show quality profile under poster",
"ShowRelativeDates": "Show Relative Dates",
"ShowRelativeDatesHelpText": "Show relative (Today/Yesterday/etc) or absolute dates",
"ShowSearch": "Show Search",
"ShowSearchHelpText": "Show search button on hover",
"ShowSeasonCount": "Show Season Count",
"ShowSizeOnDisk": "Show Size on Disk",
"ShowTitle": "Show Title",
"ShowTitleHelpText": "Show series title under poster",
"ShowUnknownSeriesItems": "Show Unknown Series Items",
"ShowUnknownSeriesItemsHelpText": "Show items without a series in the queue, this could include removed series, movies or anything else in Sonarr's category",
"ShownClickToHide": "Shown, click to hide",
@ -1151,6 +1259,7 @@
"SkipFreeSpaceCheckWhenImportingHelpText": "Use when Sonarr is unable to detect free space from your series root folder",
"SkipRedownload": "Skip Redownload",
"SkipRedownloadHelpText": "Prevents Sonarr from trying to download an alternative release for this item",
"Small": "Small",
"SmartReplace": "Smart Replace",
"SmartReplaceHint": "Dash or Space Dash depending on name",
"Socks4": "Socks4",
@ -1181,6 +1290,7 @@
"Started": "Started",
"StartupDirectory": "Startup directory",
"Status": "Status",
"StopSelecting": "Stop Selecting",
"Style": "Style",
"SubtitleLanguages": "Subtitle Languages",
"Sunday": "Sunday",
@ -1195,6 +1305,7 @@
"SupportedListsMoreInfo": "For more information on the individual lists, click on the more info buttons.",
"System": "System",
"SystemTimeHealthCheckMessage": "System time is off by more than 1 day. Scheduled tasks may not run correctly until the time is corrected",
"Table": "Table",
"TableColumns": "Columns",
"TableColumnsHelpText": "Choose which columns are visible and which order they appear in",
"TableOptions": "Table Options",
@ -1236,6 +1347,8 @@
"TorrentDelayTime": "Torrent Delay: {torrentDelay}",
"Torrents": "Torrents",
"TorrentsDisabled": "Torrents Disabled",
"Total": "Total",
"TotalFileSize": "Total File Size",
"TotalRecords": "Total records: {totalRecords}",
"TotalSpace": "Total Space",
"Trace": "Trace",
@ -1276,11 +1389,17 @@
"Unmonitored": "Unmonitored",
"UnmonitoredOnly": "Unmonitored Only",
"UnsavedChanges": "Unsaved Changes",
"UnselectAll": "Unselect All",
"Upcoming": "Upcoming",
"UpcomingSeriesDescription": "Series has been announced but no exact air date yet",
"UpdateAll": "Update All",
"UpdateAutomaticallyHelpText": "Automatically download and install updates. You will still be able to install from System: Updates",
"UpdateAvailableHealthCheckMessage": "New update is available",
"UpdateFiltered": "Update Filtered",
"UpdateMechanismHelpText": "Use Sonarr's built-in updater or a script",
"UpdateMonitoring": "Update Monitoring",
"UpdateScriptPathHelpText": "Path to a custom script that takes an extracted update package and handle the remainder of the update process",
"UpdateSelected": "Update Selected",
"UpdateSonarrDirectlyLoadError": "Unable to update Sonarr directly,",
"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.",
@ -1301,6 +1420,8 @@
"UrlBaseHelpText": "For reverse proxy support, default is empty",
"UseHardlinksInsteadOfCopy": "Use Hardlinks instead of Copy",
"UseProxy": "Use Proxy",
"UseSeasonFolder": "Use Season Folder",
"UseSeasonFolderHelpText": "Sort episodes into season folders",
"Usenet": "Usenet",
"UsenetDelay": "Usenet Delay",
"UsenetDelayHelpText": "Delay in minutes to wait before grabbing a release from Usenet",
@ -1325,6 +1446,7 @@
"WhatsNew": "What's New?",
"WhyCantIFindMyShow": "Why can't I find my show?",
"Wiki": "Wiki",
"WithFiles": "With Files",
"WouldYouLikeToRestoreBackup": "Would you like to restore the backup '{name}'?",
"Year": "Year",
"Yes": "Yes",