Add translations to frontend/InteractiveImport

This commit is contained in:
Stevie Robinson 2023-08-14 01:28:16 +02:00 committed by GitHub
parent 16d95ea6bf
commit 060b66aa39
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 161 additions and 63 deletions

View File

@ -71,7 +71,7 @@ interface SelectEpisodeModalContentProps {
isAnime: boolean; isAnime: boolean;
sortKey?: string; sortKey?: string;
sortDirection?: string; sortDirection?: string;
modalTitle?: string; modalTitle: string;
onEpisodesSelect(selectedEpisodes: SelectedEpisode[]): unknown; onEpisodesSelect(selectedEpisodes: SelectedEpisode[]): unknown;
onModalClose(): unknown; onModalClose(): unknown;
} }
@ -103,7 +103,7 @@ function SelectEpisodeModalContent(props: SelectEpisodeModalContentProps) {
const dispatch = useDispatch(); const dispatch = useDispatch();
const filterEpisodeNumber = parseInt(filter); const filterEpisodeNumber = parseInt(filter);
const errorMessage = getErrorMessage(error, 'Unable to load episodes'); const errorMessage = getErrorMessage(error, translate('EpisodesLoadError'));
const selectedCount = selectedIds.length; const selectedCount = selectedIds.length;
const selectedEpisodesCount = getSelectedIds(selectedState).length; const selectedEpisodesCount = getSelectedIds(selectedState).length;
const selectionIsValid = const selectionIsValid =
@ -197,13 +197,15 @@ function SelectEpisodeModalContent(props: SelectEpisodeModalContentProps) {
if (!details) { if (!details) {
details = details =
selectedCount > 1 selectedCount > 1
? `${selectedCount} selected files` ? translate('CountSelectedFiles', { selectedCount })
: `${selectedCount} selected file`; : translate('CountSelectedFile', { selectedCount });
} }
return ( return (
<ModalContent onModalClose={onModalClose}> <ModalContent onModalClose={onModalClose}>
<ModalHeader>{modalTitle} - Select Episode(s)</ModalHeader> <ModalHeader>
{translate('SelectEpisodesModalTitle', { modalTitle })}
</ModalHeader>
<ModalBody <ModalBody
className={styles.modalBody} className={styles.modalBody}
@ -211,7 +213,7 @@ function SelectEpisodeModalContent(props: SelectEpisodeModalContentProps) {
> >
<TextInput <TextInput
className={styles.filterInput} className={styles.filterInput}
placeholder="Filter episodes by title or number" placeholder={translate('FilterEpisodesPlaceholder')}
name="filter" name="filter"
value={filter} value={filter}
autoFocus={true} autoFocus={true}
@ -256,7 +258,7 @@ function SelectEpisodeModalContent(props: SelectEpisodeModalContentProps) {
) : null} ) : null}
{isPopulated && !items.length {isPopulated && !items.length
? 'No episodes were found for the selected season' ? translate('NoEpisodesFoundForSelectedSeason')
: null} : null}
</Scroller> </Scroller>
</ModalBody> </ModalBody>
@ -265,14 +267,14 @@ function SelectEpisodeModalContent(props: SelectEpisodeModalContentProps) {
<div className={styles.details}>{details}</div> <div className={styles.details}>{details}</div>
<div className={styles.buttons}> <div className={styles.buttons}>
<Button onPress={onModalClose}>Cancel</Button> <Button onPress={onModalClose}>{translate('Cancel')}</Button>
<Button <Button
kind={kinds.SUCCESS} kind={kinds.SUCCESS}
isDisabled={!selectionIsValid} isDisabled={!selectionIsValid}
onPress={onEpisodesSelectWrapper} onPress={onEpisodesSelectWrapper}
> >
Select Episodes {translate('SelectEpisodes')}
</Button> </Button>
</div> </div>
</ModalFooter> </ModalFooter>

View File

@ -100,7 +100,7 @@ function InteractiveImportSelectFolderModalContent(
return ( return (
<ModalContent onModalClose={onModalClose}> <ModalContent onModalClose={onModalClose}>
<ModalHeader> <ModalHeader>
{modalTitle} - {translate('SelectFolder')} {translate('SelectFolderModalTitle', { modalTitle })}
</ModalHeader> </ModalHeader>
<ModalBody> <ModalBody>

View File

@ -5,6 +5,7 @@ import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellCo
import TableRowCell from 'Components/Table/Cells/TableRowCell'; import TableRowCell from 'Components/Table/Cells/TableRowCell';
import TableRowButton from 'Components/Table/TableRowButton'; import TableRowButton from 'Components/Table/TableRowButton';
import { icons } from 'Helpers/Props'; import { icons } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import styles from './RecentFolderRow.css'; import styles from './RecentFolderRow.css';
class RecentFolderRow extends Component { class RecentFolderRow extends Component {
@ -44,7 +45,7 @@ class RecentFolderRow extends Component {
<TableRowCell className={styles.actions}> <TableRowCell className={styles.actions}>
<IconButton <IconButton
title="Remove" title={translate('Remove')}
name={icons.REMOVE} name={icons.REMOVE}
onPress={this.onRemovePress} onPress={this.onRemovePress}
/> />

View File

@ -147,9 +147,19 @@ const COLUMNS = [
]; ];
const importModeOptions = [ const importModeOptions = [
{ key: 'chooseImportMode', value: 'Choose Import Mode', disabled: true }, {
{ key: 'move', value: 'Move Files' }, key: 'chooseImportMode',
{ key: 'copy', value: 'Hardlink/Copy Files' }, value: () => translate('ChooseImportMode'),
disabled: true,
},
{
key: 'move',
value: () => translate('MoveFiles'),
},
{
key: 'copy',
value: () => translate('HardlinkCopyFiles'),
},
]; ];
function isSameEpisodeFile( function isSameEpisodeFile(
@ -260,12 +270,31 @@ function InteractiveImportModalContent(
useState<string | null>(null); useState<string | null>(null);
const [selectState, setSelectState] = useSelectState(); const [selectState, setSelectState] = useSelectState();
const [bulkSelectOptions, setBulkSelectOptions] = useState([ const [bulkSelectOptions, setBulkSelectOptions] = useState([
{ key: 'select', value: 'Select...', disabled: true }, {
{ key: 'season', value: 'Select Season' }, key: 'select',
{ key: 'episode', value: 'Select Episode(s)' }, value: translate('SelectDropdown'),
{ key: 'quality', value: 'Select Quality' }, disabled: true,
{ key: 'releaseGroup', value: 'Select Release Group' }, },
{ key: 'language', value: 'Select Language' }, {
key: 'season',
value: translate('SelectSeason'),
},
{
key: 'episode',
value: translate('SelectEpisodes'),
},
{
key: 'quality',
value: translate('SelectQuality'),
},
{
key: 'releaseGroup',
value: translate('SelectReleaseGroup'),
},
{
key: 'language',
value: translate('SelectLanguage'),
},
]); ]);
const { allSelected, allUnselected, selectedState } = selectState; const { allSelected, allUnselected, selectedState } = selectState;
const previousIsDeleting = usePrevious(isDeleting); const previousIsDeleting = usePrevious(isDeleting);
@ -296,7 +325,7 @@ function InteractiveImportModalContent(
newBulkSelectOptions.splice(1, 0, { newBulkSelectOptions.splice(1, 0, {
key: 'series', key: 'series',
value: 'Select Series', value: translate('SelectSeries'),
}); });
setBulkSelectOptions(newBulkSelectOptions); setBulkSelectOptions(newBulkSelectOptions);
@ -410,7 +439,9 @@ function InteractiveImportModalContent(
const files: InteractiveImportCommandOptions[] = []; const files: InteractiveImportCommandOptions[] = [];
if (finalImportMode === 'chooseImportMode') { if (finalImportMode === 'chooseImportMode') {
setInteractiveImportErrorMessage('An import mode must be selected'); setInteractiveImportErrorMessage(
translate('InteractiveImportNoImportMode')
);
return; return;
} }
@ -431,35 +462,35 @@ function InteractiveImportModalContent(
if (!series) { if (!series) {
setInteractiveImportErrorMessage( setInteractiveImportErrorMessage(
'Series must be chosen for each selected file' translate('InteractiveImportNoSeries')
); );
return; return;
} }
if (isNaN(seasonNumber)) { if (isNaN(seasonNumber)) {
setInteractiveImportErrorMessage( setInteractiveImportErrorMessage(
'Season must be chosen for each selected file' translate('InteractiveImportNoSeason')
); );
return; return;
} }
if (!episodes || !episodes.length) { if (!episodes || !episodes.length) {
setInteractiveImportErrorMessage( setInteractiveImportErrorMessage(
'One or more episodes must be chosen for each selected file' translate('InteractiveImportNoEpisode')
); );
return; return;
} }
if (!quality) { if (!quality) {
setInteractiveImportErrorMessage( setInteractiveImportErrorMessage(
'Quality must be chosen for each selected file' translate('InteractiveImportNoQuality')
); );
return; return;
} }
if (!languages) { if (!languages) {
setInteractiveImportErrorMessage( setInteractiveImportErrorMessage(
'Language(s) must be chosen for each selected file' translate('InteractiveImportNoLanguage')
); );
return; return;
} }
@ -699,7 +730,7 @@ function InteractiveImportModalContent(
const errorMessage = getErrorMessage( const errorMessage = getErrorMessage(
error, error,
'Unable to load manual import items' translate('InteractiveImportLoadError')
); );
return ( return (
@ -716,7 +747,9 @@ function InteractiveImportModalContent(
<Icon name={icons.FILTER} size={22} /> <Icon name={icons.FILTER} size={22} />
<div className={styles.filterText}> <div className={styles.filterText}>
{filterExistingFiles ? 'Unmapped Files Only' : 'All Files'} {filterExistingFiles
? translate('UnmappedFilesOnly')
: translate('AllFiles')}
</div> </div>
</MenuButton> </MenuButton>
@ -726,7 +759,7 @@ function InteractiveImportModalContent(
isSelected={!filterExistingFiles} isSelected={!filterExistingFiles}
onPress={onFilterExistingFilesChange} onPress={onFilterExistingFilesChange}
> >
All Files {translate('AllFiles')}
</SelectedMenuItem> </SelectedMenuItem>
<SelectedMenuItem <SelectedMenuItem
@ -734,7 +767,7 @@ function InteractiveImportModalContent(
isSelected={filterExistingFiles} isSelected={filterExistingFiles}
onPress={onFilterExistingFilesChange} onPress={onFilterExistingFilesChange}
> >
Unmapped Files Only {translate('UnmappedFilesOnly')}
</SelectedMenuItem> </SelectedMenuItem>
</MenuContent> </MenuContent>
</Menu> </Menu>
@ -777,7 +810,7 @@ function InteractiveImportModalContent(
) : null} ) : null}
{isPopulated && !items.length && !isFetching {isPopulated && !items.length && !isFetching
? 'No video files were found in the selected folder' ? translate('InteractiveImportNoFilesFound')
: null} : null}
</ModalBody> </ModalBody>
@ -793,7 +826,7 @@ function InteractiveImportModalContent(
} }
onPress={onDeleteSelectedPress} onPress={onDeleteSelectedPress}
> >
Delete {translate('Delete')}
</SpinnerButton> </SpinnerButton>
) : null} ) : null}
@ -831,7 +864,7 @@ function InteractiveImportModalContent(
isDisabled={!selectedIds.length || !!invalidRowsSelected.length} isDisabled={!selectedIds.length || !!invalidRowsSelected.length}
onPress={onImportSelectedPress} onPress={onImportSelectedPress}
> >
Import {translate('Import')}
</Button> </Button>
</div> </div>
</ModalFooter> </ModalFooter>
@ -891,9 +924,9 @@ function InteractiveImportModalContent(
<ConfirmModal <ConfirmModal
isOpen={isConfirmDeleteModalOpen} isOpen={isConfirmDeleteModalOpen}
kind={kinds.DANGER} kind={kinds.DANGER}
title="Delete Selected Episode Files" title={translate('DeleteSelectedEpisodeFiles')}
message={'Are you sure you want to delete the selected episode files?'} message={translate('DeleteSelectedEpisodeFilesHelpText')}
confirmLabel="Delete" confirmLabel={translate('Delete')}
onConfirm={onConfirmDelete} onConfirm={onConfirmDelete}
onCancel={onConfirmDeleteModalClose} onCancel={onConfirmDeleteModalClose}
/> />

View File

@ -348,7 +348,9 @@ function InteractiveImportRow(props: InteractiveImportRowProps) {
{isSeriesColumnVisible ? ( {isSeriesColumnVisible ? (
<TableRowCellButton <TableRowCellButton
isDisabled={!allowSeriesChange} isDisabled={!allowSeriesChange}
title={allowSeriesChange ? 'Click to change series' : undefined} title={
allowSeriesChange ? translate('ClickToChangeSeries') : undefined
}
onPress={onSelectSeriesPress} onPress={onSelectSeriesPress}
> >
{showSeriesPlaceholder ? ( {showSeriesPlaceholder ? (
@ -361,7 +363,7 @@ function InteractiveImportRow(props: InteractiveImportRowProps) {
<TableRowCellButton <TableRowCellButton
isDisabled={!series} isDisabled={!series}
title={series ? 'Click to change season' : undefined} title={series ? translate('ClickToChangeSeason') : undefined}
onPress={onSelectSeasonPress} onPress={onSelectSeasonPress}
> >
{showSeasonNumberPlaceholder ? ( {showSeasonNumberPlaceholder ? (
@ -379,7 +381,7 @@ function InteractiveImportRow(props: InteractiveImportRowProps) {
isDisabled={!series || requiresSeasonNumber} isDisabled={!series || requiresSeasonNumber}
title={ title={
series && !requiresSeasonNumber series && !requiresSeasonNumber
? 'Click to change episode' ? translate('ClickToChangeEpisode')
: undefined : undefined
} }
onPress={onSelectEpisodePress} onPress={onSelectEpisodePress}
@ -392,7 +394,7 @@ function InteractiveImportRow(props: InteractiveImportRowProps) {
</TableRowCellButton> </TableRowCellButton>
<TableRowCellButton <TableRowCellButton
title="Click to change release group" title={translate('ClickToChangeReleaseGroup')}
onPress={onSelectReleaseGroupPress} onPress={onSelectReleaseGroupPress}
> >
{showReleaseGroupPlaceholder ? ( {showReleaseGroupPlaceholder ? (
@ -404,7 +406,7 @@ function InteractiveImportRow(props: InteractiveImportRowProps) {
<TableRowCellButton <TableRowCellButton
className={styles.quality} className={styles.quality}
title="Click to change quality" title={translate('ClickToChangeQuality')}
onPress={onSelectQualityPress} onPress={onSelectQualityPress}
> >
{showQualityPlaceholder && <InteractiveImportRowCellPlaceholder />} {showQualityPlaceholder && <InteractiveImportRowCellPlaceholder />}
@ -416,7 +418,7 @@ function InteractiveImportRow(props: InteractiveImportRowProps) {
<TableRowCellButton <TableRowCellButton
className={styles.languages} className={styles.languages}
title="Click to change language" title={translate('ClickToChangeLanguage')}
onPress={onSelectLanguagePress} onPress={onSelectLanguagePress}
> >
{showLanguagePlaceholder && <InteractiveImportRowCellPlaceholder />} {showLanguagePlaceholder && <InteractiveImportRowCellPlaceholder />}
@ -450,7 +452,7 @@ function InteractiveImportRow(props: InteractiveImportRowProps) {
{rejections.length ? ( {rejections.length ? (
<Popover <Popover
anchor={<Icon name={icons.DANGER} kind={kinds.DANGER} />} anchor={<Icon name={icons.DANGER} kind={kinds.DANGER} />}
title="Release Rejected" title={translate('ReleaseRejected')}
body={ body={
<ul> <ul>
{rejections.map((rejection, index) => { {rejections.map((rejection, index) => {

View File

@ -2,6 +2,7 @@ import React, { useCallback, useEffect, useState } from 'react';
import Modal from 'Components/Modal/Modal'; import Modal from 'Components/Modal/Modal';
import usePrevious from 'Helpers/Hooks/usePrevious'; import usePrevious from 'Helpers/Hooks/usePrevious';
import { sizes } from 'Helpers/Props'; import { sizes } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import InteractiveImportSelectFolderModalContent from './Folder/InteractiveImportSelectFolderModalContent'; import InteractiveImportSelectFolderModalContent from './Folder/InteractiveImportSelectFolderModalContent';
import InteractiveImportModalContent from './Interactive/InteractiveImportModalContent'; import InteractiveImportModalContent from './Interactive/InteractiveImportModalContent';
@ -18,7 +19,7 @@ function InteractiveImportModal(props: InteractiveImportModalProps) {
isOpen, isOpen,
folder, folder,
downloadId, downloadId,
modalTitle = 'Manual Import', modalTitle = translate('ManualImport'),
onModalClose, onModalClose,
...otherProps ...otherProps
} = props; } = props;

View File

@ -16,6 +16,7 @@ import ModalHeader from 'Components/Modal/ModalHeader';
import { inputTypes, kinds, sizes } from 'Helpers/Props'; import { inputTypes, kinds, sizes } from 'Helpers/Props';
import Language from 'Language/Language'; import Language from 'Language/Language';
import createLanguagesSelector from 'Store/Selectors/createLanguagesSelector'; import createLanguagesSelector from 'Store/Selectors/createLanguagesSelector';
import translate from 'Utilities/String/translate';
import styles from './SelectLanguageModalContent.css'; import styles from './SelectLanguageModalContent.css';
interface SelectLanguageModalContentProps { interface SelectLanguageModalContentProps {
@ -78,13 +79,15 @@ function SelectLanguageModalContent(props: SelectLanguageModalContentProps) {
return ( return (
<ModalContent onModalClose={onModalClose}> <ModalContent onModalClose={onModalClose}>
<ModalHeader>{modalTitle} - Select Language</ModalHeader> <ModalHeader>
{translate('SelectLanguageModalTitle', { modalTitle })}
</ModalHeader>
<ModalBody> <ModalBody>
{isFetching ? <LoadingIndicator /> : null} {isFetching ? <LoadingIndicator /> : null}
{!isFetching && error ? ( {!isFetching && error ? (
<Alert kind={kinds.DANGER}>Unable to load Languages</Alert> <Alert kind={kinds.DANGER}>{translate('LanguagesLoadError')}</Alert>
) : null} ) : null}
{isPopulated && !error ? ( {isPopulated && !error ? (
@ -111,10 +114,10 @@ function SelectLanguageModalContent(props: SelectLanguageModalContentProps) {
</ModalBody> </ModalBody>
<ModalFooter> <ModalFooter>
<Button onPress={onModalClose}>Cancel</Button> <Button onPress={onModalClose}>{translate('Cancel')}</Button>
<Button kind={kinds.SUCCESS} onPress={onLanguagesSelectWrapper}> <Button kind={kinds.SUCCESS} onPress={onLanguagesSelectWrapper}>
Select Languages {translate('SelectLanguages')}
</Button> </Button>
</ModalFooter> </ModalFooter>
</ModalContent> </ModalContent>

View File

@ -19,6 +19,7 @@ import Quality, { QualityModel } from 'Quality/Quality';
import { fetchQualityProfileSchema } from 'Store/Actions/settingsActions'; import { fetchQualityProfileSchema } from 'Store/Actions/settingsActions';
import { CheckInputChanged } from 'typings/inputs'; import { CheckInputChanged } from 'typings/inputs';
import getQualities from 'Utilities/Quality/getQualities'; import getQualities from 'Utilities/Quality/getQualities';
import translate from 'Utilities/String/translate';
interface QualitySchemaState { interface QualitySchemaState {
isFetching: boolean; isFetching: boolean;
@ -128,13 +129,13 @@ function SelectQualityModalContent(props: SelectQualityModalContentProps) {
{isFetching && <LoadingIndicator />} {isFetching && <LoadingIndicator />}
{!isFetching && error ? ( {!isFetching && error ? (
<Alert kind={kinds.DANGER}>Unable to load qualities</Alert> <Alert kind={kinds.DANGER}>{translate('QualitiesLoadError')}</Alert>
) : null} ) : null}
{isPopulated && !error ? ( {isPopulated && !error ? (
<Form> <Form>
<FormGroup> <FormGroup>
<FormLabel>Quality</FormLabel> <FormLabel>{translate('Quality')}</FormLabel>
<FormInputGroup <FormInputGroup
type={inputTypes.SELECT} type={inputTypes.SELECT}
@ -146,7 +147,7 @@ function SelectQualityModalContent(props: SelectQualityModalContentProps) {
</FormGroup> </FormGroup>
<FormGroup> <FormGroup>
<FormLabel>Proper</FormLabel> <FormLabel>{translate('Proper')}</FormLabel>
<FormInputGroup <FormInputGroup
type={inputTypes.CHECK} type={inputTypes.CHECK}
@ -157,7 +158,7 @@ function SelectQualityModalContent(props: SelectQualityModalContentProps) {
</FormGroup> </FormGroup>
<FormGroup> <FormGroup>
<FormLabel>Real</FormLabel> <FormLabel>{translate('Real')}</FormLabel>
<FormInputGroup <FormInputGroup
type={inputTypes.CHECK} type={inputTypes.CHECK}
@ -174,7 +175,7 @@ function SelectQualityModalContent(props: SelectQualityModalContentProps) {
<Button onPress={onModalClose}>Cancel</Button> <Button onPress={onModalClose}>Cancel</Button>
<Button kind={kinds.SUCCESS} onPress={onQualitySelectWrapper}> <Button kind={kinds.SUCCESS} onPress={onQualitySelectWrapper}>
Select Quality {translate('SelectQuality')}
</Button> </Button>
</ModalFooter> </ModalFooter>
</ModalContent> </ModalContent>

View File

@ -9,6 +9,7 @@ import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter'; import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader'; import ModalHeader from 'Components/Modal/ModalHeader';
import { inputTypes, kinds, scrollDirections } from 'Helpers/Props'; import { inputTypes, kinds, scrollDirections } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import styles from './SelectReleaseGroupModalContent.css'; import styles from './SelectReleaseGroupModalContent.css';
interface SelectReleaseGroupModalContentProps { interface SelectReleaseGroupModalContentProps {
@ -37,7 +38,9 @@ function SelectReleaseGroupModalContent(
return ( return (
<ModalContent onModalClose={onModalClose}> <ModalContent onModalClose={onModalClose}>
<ModalHeader>{modalTitle} - Set Release Group</ModalHeader> <ModalHeader>
{translate('SetReleaseGroupModalTitle', { modalTitle })}
</ModalHeader>
<ModalBody <ModalBody
className={styles.modalBody} className={styles.modalBody}
@ -45,7 +48,7 @@ function SelectReleaseGroupModalContent(
> >
<Form> <Form>
<FormGroup> <FormGroup>
<FormLabel>Release Group</FormLabel> <FormLabel>{translate('ReleaseGroup')}</FormLabel>
<FormInputGroup <FormInputGroup
type={inputTypes.TEXT} type={inputTypes.TEXT}
@ -59,10 +62,10 @@ function SelectReleaseGroupModalContent(
</ModalBody> </ModalBody>
<ModalFooter> <ModalFooter>
<Button onPress={onModalClose}>Cancel</Button> <Button onPress={onModalClose}>{translate('Cancel')}</Button>
<Button kind={kinds.SUCCESS} onPress={onReleaseGroupSelectWrapper}> <Button kind={kinds.SUCCESS} onPress={onReleaseGroupSelectWrapper}>
Set Release Group {translate('SetReleaseGroup')}
</Button> </Button>
</ModalFooter> </ModalFooter>
</ModalContent> </ModalContent>

View File

@ -7,6 +7,7 @@ import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader'; import ModalHeader from 'Components/Modal/ModalHeader';
import { Season } from 'Series/Series'; import { Season } from 'Series/Series';
import { createSeriesSelectorForHook } from 'Store/Selectors/createSeriesSelector'; import { createSeriesSelectorForHook } from 'Store/Selectors/createSeriesSelector';
import translate from 'Utilities/String/translate';
import SelectSeasonRow from './SelectSeasonRow'; import SelectSeasonRow from './SelectSeasonRow';
interface SelectSeasonModalContentProps { interface SelectSeasonModalContentProps {
@ -25,7 +26,9 @@ function SelectSeasonModalContent(props: SelectSeasonModalContentProps) {
return ( return (
<ModalContent onModalClose={onModalClose}> <ModalContent onModalClose={onModalClose}>
<ModalHeader>{modalTitle} - Select Season</ModalHeader> <ModalHeader>
{translate('SelectSeasonModalTitle', { modalTitle })}
</ModalHeader>
<ModalBody> <ModalBody>
{seasons.map((item) => { {seasons.map((item) => {
@ -40,7 +43,7 @@ function SelectSeasonModalContent(props: SelectSeasonModalContentProps) {
</ModalBody> </ModalBody>
<ModalFooter> <ModalFooter>
<Button onPress={onModalClose}>Cancel</Button> <Button onPress={onModalClose}>{translate('Cancel')}</Button>
</ModalFooter> </ModalFooter>
</ModalContent> </ModalContent>
); );

View File

@ -1,5 +1,6 @@
import React, { useCallback } from 'react'; import React, { useCallback } from 'react';
import Link from 'Components/Link/Link'; import Link from 'Components/Link/Link';
import translate from 'Utilities/String/translate';
import styles from './SelectSeasonRow.css'; import styles from './SelectSeasonRow.css';
interface SelectSeasonRowProps { interface SelectSeasonRowProps {
@ -20,7 +21,9 @@ function SelectSeasonRow(props: SelectSeasonRowProps) {
component="div" component="div"
onPress={onSeasonSelectWrapper} onPress={onSeasonSelectWrapper}
> >
{seasonNumber === 0 ? 'Specials' : `Season ${seasonNumber}`} {seasonNumber === 0
? translate('Specials')
: translate('SeasonNumberToken', { seasonNumber })}
</Link> </Link>
); );
} }

View File

@ -10,6 +10,7 @@ import Scroller from 'Components/Scroller/Scroller';
import { scrollDirections } from 'Helpers/Props'; import { scrollDirections } from 'Helpers/Props';
import Series from 'Series/Series'; import Series from 'Series/Series';
import createAllSeriesSelector from 'Store/Selectors/createAllSeriesSelector'; import createAllSeriesSelector from 'Store/Selectors/createAllSeriesSelector';
import translate from 'Utilities/String/translate';
import SelectSeriesRow from './SelectSeriesRow'; import SelectSeriesRow from './SelectSeriesRow';
import styles from './SelectSeriesModalContent.css'; import styles from './SelectSeriesModalContent.css';
@ -61,7 +62,7 @@ function SelectSeriesModalContent(props: SelectSeriesModalContentProps) {
> >
<TextInput <TextInput
className={styles.filterInput} className={styles.filterInput}
placeholder="Filter series" placeholder={translate('FilterSeriesPlaceholder')}
name="filter" name="filter"
value={filter} value={filter}
autoFocus={true} autoFocus={true}
@ -83,7 +84,7 @@ function SelectSeriesModalContent(props: SelectSeriesModalContentProps) {
</ModalBody> </ModalBody>
<ModalFooter> <ModalFooter>
<Button onPress={onModalClose}>Cancel</Button> <Button onPress={onModalClose}>{translate('Cancel')}</Button>
</ModalFooter> </ModalFooter>
</ModalContent> </ModalContent>
); );

View File

@ -61,6 +61,7 @@
"AirsTimeOn": "{time} on {networkLabel}", "AirsTimeOn": "{time} on {networkLabel}",
"AirsTomorrowOn": "Tomorrow at {time} on {networkLabel}", "AirsTomorrowOn": "Tomorrow at {time} on {networkLabel}",
"All": "All", "All": "All",
"AllFiles": "All Files",
"AllResultsAreHiddenByTheAppliedFilter": "All results are hidden by the applied filter", "AllResultsAreHiddenByTheAppliedFilter": "All results are hidden by the applied filter",
"AllSeriesInRootFolderHaveBeenImported": "All series in {path} have been imported", "AllSeriesInRootFolderHaveBeenImported": "All series in {path} have been imported",
"AllTitles": "All Titles", "AllTitles": "All Titles",
@ -166,10 +167,17 @@
"ChmodFolderHelpText": "Octal, applied during import/rename to media folders and files (without execute bits)", "ChmodFolderHelpText": "Octal, applied during import/rename to media folders and files (without execute bits)",
"ChmodFolderHelpTextWarning": "This only works if the user running sonarr is the owner of the file. It's better to ensure the download client sets the permissions properly.", "ChmodFolderHelpTextWarning": "This only works if the user running sonarr is the owner of the file. It's better to ensure the download client sets the permissions properly.",
"ChooseAnotherFolder": "Choose another folder", "ChooseAnotherFolder": "Choose another folder",
"ChooseImportMode": "Choose Import Mode",
"ChownGroup": "chown Group", "ChownGroup": "chown Group",
"ChownGroupHelpText": "Group name or gid. Use gid for remote file systems.", "ChownGroupHelpText": "Group name or gid. Use gid for remote file systems.",
"ChownGroupHelpTextWarning": "This only works if the user running sonarr is the owner of the file. It's better to ensure the download client uses the same group as sonarr.", "ChownGroupHelpTextWarning": "This only works if the user running sonarr is the owner of the file. It's better to ensure the download client uses the same group as sonarr.",
"Clear": "Clear", "Clear": "Clear",
"ClickToChangeEpisode": "Click to change episode",
"ClickToChangeLanguage": "Click to change language",
"ClickToChangeQuality": "Click to change quality",
"ClickToChangeReleaseGroup": "Click to change release group",
"ClickToChangeSeason": "Click to change season",
"ClickToChangeSeries": "Click to change series",
"ClientPriority": "Client Priority", "ClientPriority": "Client Priority",
"Clone": "Clone", "Clone": "Clone",
"CloneAutoTag": "Clone Auto Tag", "CloneAutoTag": "Clone Auto Tag",
@ -205,6 +213,8 @@
"CountImportListsSelected": "{count} import list(s) selected", "CountImportListsSelected": "{count} import list(s) selected",
"CountIndexersSelected": "{count} indexer(s) selected", "CountIndexersSelected": "{count} indexer(s) selected",
"CountSeasons": "{count} Seasons", "CountSeasons": "{count} Seasons",
"CountSelectedFile": "{selectedCount} selected file",
"CountSelectedFiles": "{selectedCount} selected files",
"CreateEmptySeriesFolders": "Create Empty Series Folders", "CreateEmptySeriesFolders": "Create Empty Series Folders",
"CreateEmptySeriesFoldersHelpText": "Create missing series folders during disk scan", "CreateEmptySeriesFoldersHelpText": "Create missing series folders during disk scan",
"CreateGroup": "Create Group", "CreateGroup": "Create Group",
@ -277,6 +287,8 @@
"DeleteRootFolderMessageText": "Are you sure you want to delete the root folder '{path}'?", "DeleteRootFolderMessageText": "Are you sure you want to delete the root folder '{path}'?",
"DeleteSelectedDownloadClients": "Delete Download Client(s)", "DeleteSelectedDownloadClients": "Delete Download Client(s)",
"DeleteSelectedDownloadClientsMessageText": "Are you sure you want to delete {count} selected download client(s)?", "DeleteSelectedDownloadClientsMessageText": "Are you sure you want to delete {count} selected download client(s)?",
"DeleteSelectedEpisodeFiles": "Delete Selected Episode Files",
"DeleteSelectedEpisodeFilesHelpText": "Are you sure you want to delete the selected episode files?",
"DeleteSelectedImportLists": "Delete Import List(s)", "DeleteSelectedImportLists": "Delete Import List(s)",
"DeleteSelectedImportListsMessageText": "Are you sure you want to delete {count} selected import list(s)?", "DeleteSelectedImportListsMessageText": "Are you sure you want to delete {count} selected import list(s)?",
"DeleteSelectedIndexers": "Delete Indexer(s)", "DeleteSelectedIndexers": "Delete Indexer(s)",
@ -401,6 +413,7 @@
"EpisodeTitleRequired": "Episode Title Required", "EpisodeTitleRequired": "Episode Title Required",
"EpisodeTitleRequiredHelpText": "Prevent importing for up to 48 hours if the episode title is in the naming format and the episode title is TBA", "EpisodeTitleRequiredHelpText": "Prevent importing for up to 48 hours if the episode title is in the naming format and the episode title is TBA",
"Episodes": "Episodes", "Episodes": "Episodes",
"EpisodesLoadError": "Unable to load episodes",
"Error": "Error", "Error": "Error",
"ErrorLoadingContent": "There was an error loading this content", "ErrorLoadingContent": "There was an error loading this content",
"ErrorLoadingContents": "Error loading contents", "ErrorLoadingContents": "Error loading contents",
@ -445,6 +458,7 @@
"FilterDoesNotEndWith": "does not end with", "FilterDoesNotEndWith": "does not end with",
"FilterDoesNotStartWith": "does not start with", "FilterDoesNotStartWith": "does not start with",
"FilterEndsWith": "ends with", "FilterEndsWith": "ends with",
"FilterEpisodesPlaceholder": "Filter episodes by title or number",
"FilterEqual": "equal", "FilterEqual": "equal",
"FilterGreaterThan": "greater than", "FilterGreaterThan": "greater than",
"FilterGreaterThanOrEqual": "greater than or equal", "FilterGreaterThanOrEqual": "greater than or equal",
@ -459,6 +473,7 @@
"FilterNotEqual": "not equal", "FilterNotEqual": "not equal",
"FilterNotInLast": "not in the last", "FilterNotInLast": "not in the last",
"FilterNotInNext": "not in the next", "FilterNotInNext": "not in the next",
"FilterSeriesPlaceholder": "Filter series",
"FilterStartsWith": "starts with", "FilterStartsWith": "starts with",
"FinaleTooltip": "Series or season finale", "FinaleTooltip": "Series or season finale",
"FirstDayOfWeek": "First Day of Week", "FirstDayOfWeek": "First Day of Week",
@ -484,6 +499,7 @@
"Grabbed": "Grabbed", "Grabbed": "Grabbed",
"GrabbedHistoryTooltip": "Episode grabbed from {indexer} and sent to {downloadClient}", "GrabbedHistoryTooltip": "Episode grabbed from {indexer} and sent to {downloadClient}",
"Group": "Group", "Group": "Group",
"HardlinkCopyFiles": "Hardlink/Copy Files",
"HasMissingSeason": "Has Missing Season", "HasMissingSeason": "Has Missing Season",
"Health": "Health", "Health": "Health",
"Here": "here", "Here": "here",
@ -577,6 +593,14 @@
"InstanceName": "Instance Name", "InstanceName": "Instance Name",
"InstanceNameHelpText": "Instance name in tab and for Syslog app name", "InstanceNameHelpText": "Instance name in tab and for Syslog app name",
"InteractiveImport": "Interactive Import", "InteractiveImport": "Interactive Import",
"InteractiveImportLoadError": "Unable to load manual import items",
"InteractiveImportNoEpisode": "One or more episodes must be chosen for each selected file",
"InteractiveImportNoFilesFound": "No video files were found in the selected folder",
"InteractiveImportNoImportMode": "An import mode must be selected",
"InteractiveImportNoLanguage": "Language(s) must be chosen for each selected file",
"InteractiveImportNoQuality": "Quality must be chosen for each selected file",
"InteractiveImportNoSeason": "Season must be chosen for each selected file",
"InteractiveImportNoSeries": "Series must be chosen for each selected file",
"InteractiveSearch": "Interactive Search", "InteractiveSearch": "Interactive Search",
"Interval": "Interval", "Interval": "Interval",
"InvalidFormat": "Invalid Format", "InvalidFormat": "Invalid Format",
@ -629,6 +653,7 @@
"ManageIndexers": "Manage Indexers", "ManageIndexers": "Manage Indexers",
"ManageLists": "Manage Lists", "ManageLists": "Manage Lists",
"Manual": "Manual", "Manual": "Manual",
"ManualImport": "Manual Import",
"ManualImportItemsLoadError": "Unable to load manual import items", "ManualImportItemsLoadError": "Unable to load manual import items",
"MappedNetworkDrivesWindowsService": "Mapped network drives are not available when running as a Windows Service, see the [FAQ](https://wiki.servarr.com/sonarr/faq#why-cant-sonarr-see-my-files-on-a-remote-server) for more information.", "MappedNetworkDrivesWindowsService": "Mapped network drives are not available when running as a Windows Service, see the [FAQ](https://wiki.servarr.com/sonarr/faq#why-cant-sonarr-see-my-files-on-a-remote-server) for more information.",
"MarkAsFailed": "Mark as Failed", "MarkAsFailed": "Mark as Failed",
@ -701,6 +726,7 @@
"MoreInfo": "More Info", "MoreInfo": "More Info",
"MountHealthCheckMessage": "Mount containing a series path is mounted read-only: ", "MountHealthCheckMessage": "Mount containing a series path is mounted read-only: ",
"MoveAutomatically": "Move Automatically", "MoveAutomatically": "Move Automatically",
"MoveFiles": "Move Files",
"MultiEpisode": "Multi Episode", "MultiEpisode": "Multi Episode",
"MultiEpisodeInvalidFormat": "Multi Episode: Invalid Format", "MultiEpisodeInvalidFormat": "Multi Episode: Invalid Format",
"MultiEpisodeStyle": "Multi Episode Style", "MultiEpisodeStyle": "Multi Episode Style",
@ -730,6 +756,7 @@
"NoDownloadClientsFound": "No download clients found", "NoDownloadClientsFound": "No download clients found",
"NoEpisodeHistory": "No episode history", "NoEpisodeHistory": "No episode history",
"NoEpisodeOverview": "No episode overview", "NoEpisodeOverview": "No episode overview",
"NoEpisodesFoundForSelectedSeason": "No episodes were found for the selected season",
"NoEventsFound": "No events found", "NoEventsFound": "No events found",
"NoHistoryBlocklist": "No history blocklist", "NoHistoryBlocklist": "No history blocklist",
"NoHistoryFound": "No history found", "NoHistoryFound": "No history found",
@ -877,6 +904,7 @@
"ReleaseProfileTagHelpText": "Release profiles will apply to series with at least one matching tag. Leave blank to apply to all series", "ReleaseProfileTagHelpText": "Release profiles will apply to series with at least one matching tag. Leave blank to apply to all series",
"ReleaseProfiles": "Release Profiles", "ReleaseProfiles": "Release Profiles",
"ReleaseProfilesLoadError": "Unable to load Release Profiles", "ReleaseProfilesLoadError": "Unable to load Release Profiles",
"ReleaseRejected": "Release Rejected",
"ReleaseTitle": "Release Title", "ReleaseTitle": "Release Title",
"Reload": "Reload", "Reload": "Reload",
"RemotePath": "Remote Path", "RemotePath": "Remote Path",
@ -1007,13 +1035,26 @@
"SeasonFolder": "Season Folder", "SeasonFolder": "Season Folder",
"SeasonFolderFormat": "Season Folder Format", "SeasonFolderFormat": "Season Folder Format",
"SeasonNumber": "Season Number", "SeasonNumber": "Season Number",
"SeasonNumberToken": "Season {seasonNumber}",
"SeasonPack": "Season Pack", "SeasonPack": "Season Pack",
"SeasonPremiere": "Season Premiere", "SeasonPremiere": "Season Premiere",
"SeasonPremieresOnly": "Season Premieres Only", "SeasonPremieresOnly": "Season Premieres Only",
"Seasons": "Seasons", "Seasons": "Seasons",
"Security": "Security", "Security": "Security",
"Seeders": "Seeders", "Seeders": "Seeders",
"SelectDropdown": "Select...",
"SelectEpisodes": "Select Episode(s)",
"SelectEpisodesModalTitle": "{modalTitle} - Select Episode(s)",
"SelectFolder": "Select Folder", "SelectFolder": "Select Folder",
"SelectFolderModalTitle": "{modalTitle} - Select Folder",
"SelectLanguage": "Select Language",
"SelectLanguageModalTitle": "{modalTitle} - Select Language",
"SelectLanguages": "Select Languages",
"SelectQuality": "Select Quality",
"SelectReleaseGroup": "Select Release Group",
"SelectSeason": "Select Season",
"SelectSeasonModalTitle": "{modalTitle} - Select Season",
"SelectSeries": "Select Series",
"SendAnonymousUsageData": "Send Anonymous Usage Data", "SendAnonymousUsageData": "Send Anonymous Usage Data",
"Series": "Series", "Series": "Series",
"SeriesAndEpisodeInformationIsProvidedByTheTVDB": "Series and episode information is provided by TheTVDB.com. [Please consider supporting them](https://www.thetvdb.com/subscribe).", "SeriesAndEpisodeInformationIsProvidedByTheTVDB": "Series and episode information is provided by TheTVDB.com. [Please consider supporting them](https://www.thetvdb.com/subscribe).",
@ -1034,6 +1075,8 @@
"SetPermissions": "Set Permissions", "SetPermissions": "Set Permissions",
"SetPermissionsLinuxHelpText": "Should chmod be run when files are imported/renamed?", "SetPermissionsLinuxHelpText": "Should chmod be run when files are imported/renamed?",
"SetPermissionsLinuxHelpTextWarning": "If you're unsure what these settings do, do not alter them.", "SetPermissionsLinuxHelpTextWarning": "If you're unsure what these settings do, do not alter them.",
"SetReleaseGroup": "Set Release Group",
"SetReleaseGroupModalTitle": "{modalTitle} - Set Release Group",
"SetTags": "Set Tags", "SetTags": "Set Tags",
"Settings": "Settings", "Settings": "Settings",
"ShortDateFormat": "Short Date Format", "ShortDateFormat": "Short Date Format",
@ -1069,6 +1112,7 @@
"Space": "Space", "Space": "Space",
"Special": "Special", "Special": "Special",
"SpecialEpisode": "Special Episode", "SpecialEpisode": "Special Episode",
"Specials": "Specials",
"SpecialsFolderFormat": "Specials Folder Format", "SpecialsFolderFormat": "Specials Folder Format",
"SslCertPassword": "SSL Cert Password", "SslCertPassword": "SSL Cert Password",
"SslCertPasswordHelpText": "Password for pfx file", "SslCertPasswordHelpText": "Password for pfx file",
@ -1168,6 +1212,7 @@
"Unknown": "Unknown", "Unknown": "Unknown",
"UnknownEventTooltip": "Unknown event", "UnknownEventTooltip": "Unknown event",
"Unlimited": "Unlimited", "Unlimited": "Unlimited",
"UnmappedFilesOnly": "Unmapped Files Only",
"UnmappedFolders": "Unmapped Folders", "UnmappedFolders": "Unmapped Folders",
"UnmonitorDeletedEpisodes": "Unmonitor Deleted Episodes", "UnmonitorDeletedEpisodes": "Unmonitor Deleted Episodes",
"UnmonitorDeletedEpisodesHelpText": "Episodes deleted from disk are automatically unmonitored in Sonarr", "UnmonitorDeletedEpisodesHelpText": "Episodes deleted from disk are automatically unmonitored in Sonarr",