Translate System pages

This commit is contained in:
Stevie Robinson 2023-07-20 03:19:43 +02:00 committed by GitHub
parent 360d989cb0
commit 93e8ff0ac7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 319 additions and 126 deletions

View File

@ -9,6 +9,7 @@ import TableRowCell from 'Components/Table/Cells/TableRowCell';
import TableRow from 'Components/Table/TableRow'; import TableRow from 'Components/Table/TableRow';
import { icons, kinds } from 'Helpers/Props'; import { icons, kinds } from 'Helpers/Props';
import formatBytes from 'Utilities/Number/formatBytes'; import formatBytes from 'Utilities/Number/formatBytes';
import translate from 'Utilities/String/translate';
import RestoreBackupModalConnector from './RestoreBackupModalConnector'; import RestoreBackupModalConnector from './RestoreBackupModalConnector';
import styles from './BackupRow.css'; import styles from './BackupRow.css';
@ -75,14 +76,14 @@ class BackupRow extends Component {
} = this.state; } = this.state;
let iconClassName = icons.SCHEDULED; let iconClassName = icons.SCHEDULED;
let iconTooltip = 'Scheduled'; let iconTooltip = translate('Scheduled');
if (type === 'manual') { if (type === 'manual') {
iconClassName = icons.INTERACTIVE; iconClassName = icons.INTERACTIVE;
iconTooltip = 'Manual'; iconTooltip = translate('Manual');
} else if (type === 'update') { } else if (type === 'update') {
iconClassName = icons.UPDATE; iconClassName = icons.UPDATE;
iconTooltip = 'Before update'; iconTooltip = translate('BeforeUpdate');
} }
return ( return (
@ -115,12 +116,13 @@ class BackupRow extends Component {
<TableRowCell className={styles.actions}> <TableRowCell className={styles.actions}>
<IconButton <IconButton
title={translate('RestoreBackup')}
name={icons.RESTORE} name={icons.RESTORE}
onPress={this.onRestorePress} onPress={this.onRestorePress}
/> />
<IconButton <IconButton
title="Delete backup" title={translate('DeleteBackup')}
name={icons.DELETE} name={icons.DELETE}
onPress={this.onDeletePress} onPress={this.onDeletePress}
/> />
@ -136,9 +138,11 @@ class BackupRow extends Component {
<ConfirmModal <ConfirmModal
isOpen={isConfirmDeleteModalOpen} isOpen={isConfirmDeleteModalOpen}
kind={kinds.DANGER} kind={kinds.DANGER}
title="Delete Backup" title={translate('DeleteBackup')}
message={`Are you sure you want to delete the backup '${name}'?`} message={translate('DeleteBackupMessageText', {
confirmLabel="Delete" name
})}
confirmLabel={translate('Delete')}
onConfirm={this.onConfirmDeletePress} onConfirm={this.onConfirmDeletePress}
onCancel={this.onConfirmDeleteModalClose} onCancel={this.onConfirmDeleteModalClose}
/> />

View File

@ -10,6 +10,7 @@ import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
import Table from 'Components/Table/Table'; import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody'; import TableBody from 'Components/Table/TableBody';
import { icons, kinds } from 'Helpers/Props'; import { icons, kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import BackupRow from './BackupRow'; import BackupRow from './BackupRow';
import RestoreBackupModalConnector from './RestoreBackupModalConnector'; import RestoreBackupModalConnector from './RestoreBackupModalConnector';
@ -20,17 +21,23 @@ const columns = [
}, },
{ {
name: 'name', name: 'name',
label: 'Name', get label() {
return translate('Name');
},
isVisible: true isVisible: true
}, },
{ {
name: 'size', name: 'size',
label: 'Size', get label() {
return translate('Size');
},
isVisible: true isVisible: true
}, },
{ {
name: 'time', name: 'time',
label: 'Time', get label() {
return translate('Time');
},
isVisible: true isVisible: true
}, },
{ {
@ -81,18 +88,18 @@ class Backups extends Component {
const noBackups = isPopulated && !items.length; const noBackups = isPopulated && !items.length;
return ( return (
<PageContent title="Backups"> <PageContent title={translate('Backups')}>
<PageToolbar> <PageToolbar>
<PageToolbarSection> <PageToolbarSection>
<PageToolbarButton <PageToolbarButton
label="Backup Now" label={translate('BackupNow')}
iconName={icons.BACKUP} iconName={icons.BACKUP}
isSpinning={backupExecuting} isSpinning={backupExecuting}
onPress={onBackupPress} onPress={onBackupPress}
/> />
<PageToolbarButton <PageToolbarButton
label="Restore Backup" label={translate('RestoreBackup')}
iconName={icons.RESTORE} iconName={icons.RESTORE}
onPress={this.onRestorePress} onPress={this.onRestorePress}
/> />
@ -108,14 +115,14 @@ class Backups extends Component {
{ {
!isFetching && !!error && !isFetching && !!error &&
<Alert kind={kinds.DANGER}> <Alert kind={kinds.DANGER}>
Unable to load backups {translate('UnableToLoadBackups')}
</Alert> </Alert>
} }
{ {
noBackups && noBackups &&
<Alert kind={kinds.INFO}> <Alert kind={kinds.INFO}>
No backups are available {translate('NoBackupsAreAvailable')}
</Alert> </Alert>
} }

View File

@ -9,11 +9,12 @@ 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 { icons, kinds } from 'Helpers/Props'; import { icons, kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import styles from './RestoreBackupModalContent.css'; import styles from './RestoreBackupModalContent.css';
function getErrorMessage(error) { function getErrorMessage(error) {
if (!error || !error.responseJSON || !error.responseJSON.message) { if (!error || !error.responseJSON || !error.responseJSON.message) {
return 'Error restoring backup'; return translate('ErrorRestoringBackup');
} }
return error.responseJSON.message; return error.responseJSON.message;
@ -145,7 +146,9 @@ class RestoreBackupModalContent extends Component {
<ModalBody> <ModalBody>
{ {
!!id && `Would you like to restore the backup '${name}'?` !!id && translate('WouldYouLikeToRestoreBackup', {
name
})
} }
{ {
@ -167,7 +170,9 @@ class RestoreBackupModalContent extends Component {
/> />
</div> </div>
<div>Restore</div> <div>
{translate('Restore')}
</div>
</div> </div>
<div className={styles.step}> <div className={styles.step}>
@ -178,7 +183,9 @@ class RestoreBackupModalContent extends Component {
/> />
</div> </div>
<div>Restart</div> <div>
{translate('Restart')}
</div>
</div> </div>
<div className={styles.step}> <div className={styles.step}>
@ -189,18 +196,20 @@ class RestoreBackupModalContent extends Component {
/> />
</div> </div>
<div>Reload</div> <div>
{translate('Reload')}
</div>
</div> </div>
</div> </div>
</ModalBody> </ModalBody>
<ModalFooter> <ModalFooter>
<div className={styles.additionalInfo}> <div className={styles.additionalInfo}>
Note: Sonarr will automatically restart and reload the UI during the restore process. {translate('RestartReloadNote')}
</div> </div>
<Button onPress={onModalClose}> <Button onPress={onModalClose}>
Cancel {translate('Cancel')}
</Button> </Button>
<SpinnerButton <SpinnerButton
@ -209,7 +218,7 @@ class RestoreBackupModalContent extends Component {
isSpinning={isRestoring} isSpinning={isRestoring}
onPress={this.onRestorePress} onPress={this.onRestorePress}
> >
Restore {translate('Restore')}
</SpinnerButton> </SpinnerButton>
</ModalFooter> </ModalFooter>
</ModalContent> </ModalContent>

View File

@ -13,6 +13,7 @@ import TableBody from 'Components/Table/TableBody';
import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper'; import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper';
import TablePager from 'Components/Table/TablePager'; import TablePager from 'Components/Table/TablePager';
import { align, icons, kinds } from 'Helpers/Props'; import { align, icons, kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import LogsTableRow from './LogsTableRow'; import LogsTableRow from './LogsTableRow';
function LogsTable(props) { function LogsTable(props) {
@ -33,11 +34,11 @@ function LogsTable(props) {
} = props; } = props;
return ( return (
<PageContent title="Logs"> <PageContent title={translate('Logs')}>
<PageToolbar> <PageToolbar>
<PageToolbarSection> <PageToolbarSection>
<PageToolbarButton <PageToolbarButton
label="Refresh" label={translate('Refresh')}
iconName={icons.REFRESH} iconName={icons.REFRESH}
spinningName={icons.REFRESH} spinningName={icons.REFRESH}
isSpinning={isFetching} isSpinning={isFetching}
@ -45,7 +46,7 @@ function LogsTable(props) {
/> />
<PageToolbarButton <PageToolbarButton
label="Clear" label={translate('Clear')}
iconName={icons.CLEAR} iconName={icons.CLEAR}
isSpinning={clearLogExecuting} isSpinning={clearLogExecuting}
onPress={onClearLogsPress} onPress={onClearLogsPress}
@ -59,7 +60,7 @@ function LogsTable(props) {
canModifyColumns={false} canModifyColumns={false}
> >
<PageToolbarButton <PageToolbarButton
label="Options" label={translate('Options')}
iconName={icons.TABLE} iconName={icons.TABLE}
/> />
</TableOptionsModalWrapper> </TableOptionsModalWrapper>
@ -83,7 +84,7 @@ function LogsTable(props) {
{ {
isPopulated && !error && !items.length && isPopulated && !error && !items.length &&
<Alert kind={kinds.INFO}> <Alert kind={kinds.INFO}>
No events found {translate('NoEventsFound')}
</Alert> </Alert>
} }

View File

@ -8,6 +8,7 @@ import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader'; import ModalHeader from 'Components/Modal/ModalHeader';
import Scroller from 'Components/Scroller/Scroller'; import Scroller from 'Components/Scroller/Scroller';
import { scrollDirections } from 'Helpers/Props'; import { scrollDirections } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import styles from './LogsTableDetailsModal.css'; import styles from './LogsTableDetailsModal.css';
function LogsTableDetailsModal(props) { function LogsTableDetailsModal(props) {
@ -27,11 +28,13 @@ function LogsTableDetailsModal(props) {
onModalClose={onModalClose} onModalClose={onModalClose}
> >
<ModalHeader> <ModalHeader>
Details {translate('Details')}
</ModalHeader> </ModalHeader>
<ModalBody> <ModalBody>
<div>Message</div> <div>
{translate('Message')}
</div>
<Scroller <Scroller
className={styles.detailsText} className={styles.detailsText}
@ -43,7 +46,9 @@ function LogsTableDetailsModal(props) {
{ {
!!exception && !!exception &&
<div> <div>
<div>Exception</div> <div>
{translate('Exception')}
</div>
<Scroller <Scroller
className={styles.detailsText} className={styles.detailsText}
scrollDirection={scrollDirections.HORIZONTAL} scrollDirection={scrollDirections.HORIZONTAL}
@ -56,7 +61,7 @@ function LogsTableDetailsModal(props) {
<ModalFooter> <ModalFooter>
<Button onPress={onModalClose}> <Button onPress={onModalClose}>
Close {translate('Close')}
</Button> </Button>
</ModalFooter> </ModalFooter>
</ModalContent> </ModalContent>

View File

@ -1,7 +1,6 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import Alert from 'Components/Alert'; import Alert from 'Components/Alert';
import Link from 'Components/Link/Link';
import LoadingIndicator from 'Components/Loading/LoadingIndicator'; import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import PageContent from 'Components/Page/PageContent'; import PageContent from 'Components/Page/PageContent';
import PageContentBody from 'Components/Page/PageContentBody'; import PageContentBody from 'Components/Page/PageContentBody';
@ -12,18 +11,24 @@ import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator';
import Table from 'Components/Table/Table'; import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody'; import TableBody from 'Components/Table/TableBody';
import { icons, kinds } from 'Helpers/Props'; import { icons, kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import InlineMarkdown from '../../../Components/Markdown/InlineMarkdown';
import LogsNavMenu from '../LogsNavMenu'; import LogsNavMenu from '../LogsNavMenu';
import LogFilesTableRow from './LogFilesTableRow'; import LogFilesTableRow from './LogFilesTableRow';
const columns = [ const columns = [
{ {
name: 'filename', name: 'filename',
label: 'Filename', get label() {
return translate('Filename');
},
isVisible: true isVisible: true
}, },
{ {
name: 'lastWriteTime', name: 'lastWriteTime',
label: 'Last Write Time', get label() {
return translate('LastWriteTime');
},
isVisible: true isVisible: true
}, },
{ {
@ -50,7 +55,7 @@ class LogFiles extends Component {
} = this.props; } = this.props;
return ( return (
<PageContent title="Log Files"> <PageContent title={translate('LogFiles')}>
<PageToolbar> <PageToolbar>
<PageToolbarSection> <PageToolbarSection>
<LogsNavMenu current={currentLogView} /> <LogsNavMenu current={currentLogView} />
@ -58,7 +63,7 @@ class LogFiles extends Component {
<PageToolbarSeparator /> <PageToolbarSeparator />
<PageToolbarButton <PageToolbarButton
label="Refresh" label={translate('Refresh')}
iconName={icons.REFRESH} iconName={icons.REFRESH}
spinningName={icons.REFRESH} spinningName={icons.REFRESH}
isSpinning={isFetching} isSpinning={isFetching}
@ -66,7 +71,7 @@ class LogFiles extends Component {
/> />
<PageToolbarButton <PageToolbarButton
label="Clear" label={translate('Clear')}
iconName={icons.CLEAR} iconName={icons.CLEAR}
isSpinning={deleteFilesExecuting} isSpinning={deleteFilesExecuting}
onPress={onDeleteFilesPress} onPress={onDeleteFilesPress}
@ -76,13 +81,15 @@ class LogFiles extends Component {
<PageContentBody> <PageContentBody>
<Alert> <Alert>
<div> <div>
Log files are located in: {location} {translate('LogFilesLocation', {
location
})}
</div> </div>
{ {
currentLogView === 'Log Files' && currentLogView === 'Log Files' &&
<div> <div>
The log level defaults to 'Info' and can be changed in <Link to="/settings/general">General Settings</Link> <InlineMarkdown data={translate('TheLogLevelDefault')} />
</div> </div>
} }
</Alert> </Alert>
@ -118,7 +125,7 @@ class LogFiles extends Component {
{ {
!isFetching && !items.length && !isFetching && !items.length &&
<Alert kind={kinds.INFO}> <Alert kind={kinds.INFO}>
No log files {translate('NoLogFiles')}
</Alert> </Alert>
} }
</PageContentBody> </PageContentBody>

View File

@ -7,6 +7,7 @@ import { executeCommand } from 'Store/Actions/commandActions';
import { fetchLogFiles } from 'Store/Actions/systemActions'; import { fetchLogFiles } from 'Store/Actions/systemActions';
import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector'; import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector';
import combinePath from 'Utilities/String/combinePath'; import combinePath from 'Utilities/String/combinePath';
import translate from 'Utilities/String/translate';
import LogFiles from './LogFiles'; import LogFiles from './LogFiles';
function createMapStateToProps() { function createMapStateToProps() {
@ -29,7 +30,7 @@ function createMapStateToProps() {
isFetching, isFetching,
items, items,
deleteFilesExecuting, deleteFilesExecuting,
currentLogView: 'Log Files', currentLogView: translate('LogFiles'),
location: combinePath(isWindows, appData, ['logs']) location: combinePath(isWindows, appData, ['logs'])
}; };
} }

View File

@ -4,6 +4,7 @@ import Link from 'Components/Link/Link';
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector'; import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
import TableRowCell from 'Components/Table/Cells/TableRowCell'; import TableRowCell from 'Components/Table/Cells/TableRowCell';
import TableRow from 'Components/Table/TableRow'; import TableRow from 'Components/Table/TableRow';
import translate from 'Utilities/String/translate';
import styles from './LogFilesTableRow.css'; import styles from './LogFilesTableRow.css';
class LogFilesTableRow extends Component { class LogFilesTableRow extends Component {
@ -32,7 +33,7 @@ class LogFilesTableRow extends Component {
target="_blank" target="_blank"
noRouter={true} noRouter={true}
> >
Download {translate('Download')}
</Link> </Link>
</TableRowCell> </TableRowCell>
</TableRow> </TableRow>

View File

@ -4,6 +4,7 @@ import Menu from 'Components/Menu/Menu';
import MenuButton from 'Components/Menu/MenuButton'; import MenuButton from 'Components/Menu/MenuButton';
import MenuContent from 'Components/Menu/MenuContent'; import MenuContent from 'Components/Menu/MenuContent';
import MenuItem from 'Components/Menu/MenuItem'; import MenuItem from 'Components/Menu/MenuItem';
import translate from 'Utilities/String/translate';
class LogsNavMenu extends Component { class LogsNavMenu extends Component {
@ -50,13 +51,13 @@ class LogsNavMenu extends Component {
<MenuItem <MenuItem
to={'/system/logs/files'} to={'/system/logs/files'}
> >
Log Files {translate('LogFiles')}
</MenuItem> </MenuItem>
<MenuItem <MenuItem
to={'/system/logs/files/update'} to={'/system/logs/files/update'}
> >
Updater Log Files {translate('UpdaterLogFiles')}
</MenuItem> </MenuItem>
</MenuContent> </MenuContent>
</Menu> </Menu>

View File

@ -5,6 +5,7 @@ import DescriptionListItem from 'Components/DescriptionList/DescriptionListItem'
import FieldSet from 'Components/FieldSet'; import FieldSet from 'Components/FieldSet';
import InlineMarkdown from 'Components/Markdown/InlineMarkdown'; import InlineMarkdown from 'Components/Markdown/InlineMarkdown';
import titleCase from 'Utilities/String/titleCase'; import titleCase from 'Utilities/String/titleCase';
import translate from 'Utilities/String/translate';
import StartTime from './StartTime'; import StartTime from './StartTime';
import styles from './About.css'; import styles from './About.css';
@ -30,25 +31,32 @@ class About extends Component {
} = this.props; } = this.props;
return ( return (
<FieldSet legend="About"> <FieldSet legend={translate('About')}>
<DescriptionList className={styles.descriptionList}> <DescriptionList className={styles.descriptionList}>
<DescriptionListItem <DescriptionListItem
title="Version" title={translate('Version')}
data={version} data={version}
/> />
{ {
packageVersion && packageVersion &&
<DescriptionListItem <DescriptionListItem
title="Package Version" title={translate('PackageVersion')}
data={(packageAuthor ? <span> {packageVersion} {' by '} <InlineMarkdown data={packageAuthor} /> </span> : packageVersion)} data={(packageAuthor ?
<InlineMarkdown data={translate('PackageVersionInfo', {
packageVersion,
packageAuthor
})}
/> :
packageVersion
)}
/> />
} }
{ {
isNetCore && isNetCore &&
<DescriptionListItem <DescriptionListItem
title=".Net Version" title={translate('DotNetVersion')}
data={`Yes (${runtimeVersion})`} data={`Yes (${runtimeVersion})`}
/> />
} }
@ -56,28 +64,28 @@ class About extends Component {
{ {
isDocker && isDocker &&
<DescriptionListItem <DescriptionListItem
title="Docker" title={translate('Docker')}
data={'Yes'} data={'Yes'}
/> />
} }
<DescriptionListItem <DescriptionListItem
title="AppData directory" title={translate('AppDataDirectory')}
data={appData} data={appData}
/> />
<DescriptionListItem <DescriptionListItem
title="Startup directory" title={translate('StartupDirectory')}
data={startupPath} data={startupPath}
/> />
<DescriptionListItem <DescriptionListItem
title="Mode" title={translate('Mode')}
data={titleCase(mode)} data={titleCase(mode)}
/> />
<DescriptionListItem <DescriptionListItem
title="Uptime" title={translate('Uptime')}
data={ data={
<StartTime <StartTime
startTime={startTime} startTime={startTime}

View File

@ -9,22 +9,29 @@ import TableBody from 'Components/Table/TableBody';
import TableRow from 'Components/Table/TableRow'; import TableRow from 'Components/Table/TableRow';
import { kinds, sizes } from 'Helpers/Props'; import { kinds, sizes } from 'Helpers/Props';
import formatBytes from 'Utilities/Number/formatBytes'; import formatBytes from 'Utilities/Number/formatBytes';
import translate from 'Utilities/String/translate';
import styles from './DiskSpace.css'; import styles from './DiskSpace.css';
const columns = [ const columns = [
{ {
name: 'path', name: 'path',
label: 'Location', get label() {
return translate('Location');
},
isVisible: true isVisible: true
}, },
{ {
name: 'freeSpace', name: 'freeSpace',
label: 'Free Space', get label() {
return translate('FreeSpace');
},
isVisible: true isVisible: true
}, },
{ {
name: 'totalSpace', name: 'totalSpace',
label: 'Total Space', get label() {
return translate('TotalSpace');
},
isVisible: true isVisible: true
}, },
{ {
@ -45,7 +52,7 @@ class DiskSpace extends Component {
} = this.props; } = this.props;
return ( return (
<FieldSet legend="Disk Space"> <FieldSet legend={translate('DiskSpace')}>
{ {
isFetching && isFetching &&
<LoadingIndicator /> <LoadingIndicator />

View File

@ -11,6 +11,7 @@ import TableBody from 'Components/Table/TableBody';
import TableRow from 'Components/Table/TableRow'; import TableRow from 'Components/Table/TableRow';
import { icons, kinds } from 'Helpers/Props'; import { icons, kinds } from 'Helpers/Props';
import titleCase from 'Utilities/String/titleCase'; import titleCase from 'Utilities/String/titleCase';
import translate from 'Utilities/String/translate';
import styles from './Health.css'; import styles from './Health.css';
function getInternalLink(source) { function getInternalLink(source) {
@ -23,7 +24,7 @@ function getInternalLink(source) {
return ( return (
<IconButton <IconButton
name={icons.SETTINGS} name={icons.SETTINGS}
title="Settings" title={translate('Settings')}
to="/settings/indexers" to="/settings/indexers"
/> />
); );
@ -33,7 +34,7 @@ function getInternalLink(source) {
return ( return (
<IconButton <IconButton
name={icons.SETTINGS} name={icons.SETTINGS}
title="Settings" title={translate('Settings')}
to="/settings/downloadclients" to="/settings/downloadclients"
/> />
); );
@ -41,7 +42,7 @@ function getInternalLink(source) {
return ( return (
<IconButton <IconButton
name={icons.SERIES_CONTINUING} name={icons.SERIES_CONTINUING}
title="Series Editor" title={translate('SeriesEditor')}
to="/serieseditor" to="/serieseditor"
/> />
); );
@ -49,7 +50,7 @@ function getInternalLink(source) {
return ( return (
<IconButton <IconButton
name={icons.UPDATE} name={icons.UPDATE}
title="Updates" title={translate('Updates')}
to="/system/updates" to="/system/updates"
/> />
); );
@ -64,7 +65,7 @@ function getTestLink(source, props) {
return ( return (
<SpinnerIconButton <SpinnerIconButton
name={icons.TEST} name={icons.TEST}
title="Test All" title={translate('TestAll')}
isSpinning={props.isTestingAllIndexers} isSpinning={props.isTestingAllIndexers}
onPress={props.dispatchTestAllIndexers} onPress={props.dispatchTestAllIndexers}
/> />
@ -74,7 +75,7 @@ function getTestLink(source, props) {
return ( return (
<SpinnerIconButton <SpinnerIconButton
name={icons.TEST} name={icons.TEST}
title="Test All" title={translate('TestAll')}
isSpinning={props.isTestingAllDownloadClients} isSpinning={props.isTestingAllDownloadClients}
onPress={props.dispatchTestAllDownloadClients} onPress={props.dispatchTestAllDownloadClients}
/> />
@ -93,12 +94,16 @@ const columns = [
}, },
{ {
name: 'message', name: 'message',
label: 'Message', get label() {
return translate('Message');
},
isVisible: true isVisible: true
}, },
{ {
name: 'actions', name: 'actions',
label: 'Actions', get label() {
return translate('Actions');
},
isVisible: true isVisible: true
} }
]; ];
@ -121,7 +126,7 @@ class Health extends Component {
<FieldSet <FieldSet
legend={ legend={
<div className={styles.legend}> <div className={styles.legend}>
Health {translate('Health')}
{ {
isFetching && isPopulated && isFetching && isPopulated &&
@ -141,7 +146,7 @@ class Health extends Component {
{ {
!healthIssues && !healthIssues &&
<div className={styles.healthOk}> <div className={styles.healthOk}>
No issues with your configuration {translate('NoIssuesWithYourConfiguration')}
</div> </div>
} }
@ -186,7 +191,7 @@ class Health extends Component {
<IconButton <IconButton
name={icons.WIKI} name={icons.WIKI}
to={item.wikiUrl} to={item.wikiUrl}
title="Read the Wiki for more information" title={translate('ReadTheWikiForMoreInformation')}
/> />
{ {

View File

@ -4,6 +4,7 @@ import DescriptionListItemDescription from 'Components/DescriptionList/Descripti
import DescriptionListItemTitle from 'Components/DescriptionList/DescriptionListItemTitle'; import DescriptionListItemTitle from 'Components/DescriptionList/DescriptionListItemTitle';
import FieldSet from 'Components/FieldSet'; import FieldSet from 'Components/FieldSet';
import Link from 'Components/Link/Link'; import Link from 'Components/Link/Link';
import translate from 'Utilities/String/translate';
class MoreInfo extends Component { class MoreInfo extends Component {
@ -12,34 +13,46 @@ class MoreInfo extends Component {
render() { render() {
return ( return (
<FieldSet legend="More Info"> <FieldSet legend={translate('MoreInfo')}>
<DescriptionList> <DescriptionList>
<DescriptionListItemTitle>Home page</DescriptionListItemTitle> <DescriptionListItemTitle>
{translate('HomePage')}
</DescriptionListItemTitle>
<DescriptionListItemDescription> <DescriptionListItemDescription>
<Link to="https://sonarr.tv/">sonarr.tv</Link> <Link to="https://sonarr.tv/">sonarr.tv</Link>
</DescriptionListItemDescription> </DescriptionListItemDescription>
<DescriptionListItemTitle>Wiki</DescriptionListItemTitle> <DescriptionListItemTitle>
{translate('Wiki')}
</DescriptionListItemTitle>
<DescriptionListItemDescription> <DescriptionListItemDescription>
<Link to="https://wiki.servarr.com/sonarr">wiki.servarr.com/sonarr</Link> <Link to="https://wiki.servarr.com/sonarr">wiki.servarr.com/sonarr</Link>
</DescriptionListItemDescription> </DescriptionListItemDescription>
<DescriptionListItemTitle>Forums</DescriptionListItemTitle> <DescriptionListItemTitle>
{translate('Forums')}
</DescriptionListItemTitle>
<DescriptionListItemDescription> <DescriptionListItemDescription>
<Link to="https://forums.sonarr.tv/">forums.sonarr.tv</Link> <Link to="https://forums.sonarr.tv/">forums.sonarr.tv</Link>
</DescriptionListItemDescription> </DescriptionListItemDescription>
<DescriptionListItemTitle>Twitter</DescriptionListItemTitle> <DescriptionListItemTitle>
{translate('Twitter')}
</DescriptionListItemTitle>
<DescriptionListItemDescription> <DescriptionListItemDescription>
<Link to="https://twitter.com/sonarrtv">@sonarrtv</Link> <Link to="https://twitter.com/sonarrtv">@sonarrtv</Link>
</DescriptionListItemDescription> </DescriptionListItemDescription>
<DescriptionListItemTitle>Discord</DescriptionListItemTitle> <DescriptionListItemTitle>
{translate('Discord')}
</DescriptionListItemTitle>
<DescriptionListItemDescription> <DescriptionListItemDescription>
<Link to="https://discord.sonarr.tv/">discord.sonarr.tv</Link> <Link to="https://discord.sonarr.tv/">discord.sonarr.tv</Link>
</DescriptionListItemDescription> </DescriptionListItemDescription>
<DescriptionListItemTitle>IRC</DescriptionListItemTitle> <DescriptionListItemTitle>
{translate('IRC')}
</DescriptionListItemTitle>
<DescriptionListItemDescription> <DescriptionListItemDescription>
<Link to="irc://irc.libera.chat/#sonarr">#sonarr on Libera</Link> <Link to="irc://irc.libera.chat/#sonarr">#sonarr on Libera</Link>
</DescriptionListItemDescription> </DescriptionListItemDescription>
@ -47,17 +60,23 @@ class MoreInfo extends Component {
<Link to="https://web.libera.chat/?channels=#sonarr">Libera webchat</Link> <Link to="https://web.libera.chat/?channels=#sonarr">Libera webchat</Link>
</DescriptionListItemDescription> </DescriptionListItemDescription>
<DescriptionListItemTitle>Donations</DescriptionListItemTitle> <DescriptionListItemTitle>
{translate('Donations')}
</DescriptionListItemTitle>
<DescriptionListItemDescription> <DescriptionListItemDescription>
<Link to="https://sonarr.tv/donate">sonarr.tv/donate</Link> <Link to="https://sonarr.tv/donate">sonarr.tv/donate</Link>
</DescriptionListItemDescription> </DescriptionListItemDescription>
<DescriptionListItemTitle>Source</DescriptionListItemTitle> <DescriptionListItemTitle>
{translate('Source')}
</DescriptionListItemTitle>
<DescriptionListItemDescription> <DescriptionListItemDescription>
<Link to="https://github.com/Sonarr/Sonarr/">github.com/Sonarr/Sonarr</Link> <Link to="https://github.com/Sonarr/Sonarr/">github.com/Sonarr/Sonarr</Link>
</DescriptionListItemDescription> </DescriptionListItemDescription>
<DescriptionListItemTitle>Feature Requests</DescriptionListItemTitle> <DescriptionListItemTitle>
{translate('FeatureRequests')}
</DescriptionListItemTitle>
<DescriptionListItemDescription> <DescriptionListItemDescription>
<Link to="https://forums.sonarr.tv/">forums.sonarr.tv</Link> <Link to="https://forums.sonarr.tv/">forums.sonarr.tv</Link>
</DescriptionListItemDescription> </DescriptionListItemDescription>

View File

@ -1,6 +1,7 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import PageContent from 'Components/Page/PageContent'; import PageContent from 'Components/Page/PageContent';
import PageContentBody from 'Components/Page/PageContentBody'; import PageContentBody from 'Components/Page/PageContentBody';
import translate from 'Utilities/String/translate';
import AboutConnector from './About/AboutConnector'; import AboutConnector from './About/AboutConnector';
import DiskSpaceConnector from './DiskSpace/DiskSpaceConnector'; import DiskSpaceConnector from './DiskSpace/DiskSpaceConnector';
import HealthConnector from './Health/HealthConnector'; import HealthConnector from './Health/HealthConnector';
@ -13,7 +14,7 @@ class Status extends Component {
render() { render() {
return ( return (
<PageContent title="Status"> <PageContent title={translate('Status')}>
<PageContentBody> <PageContentBody>
<HealthConnector /> <HealthConnector />
<DiskSpaceConnector /> <DiskSpaceConnector />

View File

@ -11,6 +11,7 @@ import formatDate from 'Utilities/Date/formatDate';
import formatDateTime from 'Utilities/Date/formatDateTime'; import formatDateTime from 'Utilities/Date/formatDateTime';
import formatTimeSpan from 'Utilities/Date/formatTimeSpan'; import formatTimeSpan from 'Utilities/Date/formatTimeSpan';
import titleCase from 'Utilities/String/titleCase'; import titleCase from 'Utilities/String/titleCase';
import translate from 'Utilities/String/translate';
import styles from './QueuedTaskRow.css'; import styles from './QueuedTaskRow.css';
function getStatusIconProps(status, message) { function getStatusIconProps(status, message) {
@ -198,8 +199,8 @@ class QueuedTaskRow extends Component {
</span> </span>
{ {
clientUserAgent ? clientUserAgent ?
<span className={styles.userAgent} title="User-Agent provided by the app that called the API"> <span className={styles.userAgent} title={translate('TaskUserAgentTooltip')}>
from: {clientUserAgent} {translate('From')}: {clientUserAgent}
</span> : </span> :
null null
} }
@ -236,7 +237,7 @@ class QueuedTaskRow extends Component {
{ {
status === 'queued' && status === 'queued' &&
<IconButton <IconButton
title="Removed from task queue" title={translate('RemovedFromTaskQueue')}
name={icons.REMOVE} name={icons.REMOVE}
onPress={this.onCancelPress} onPress={this.onCancelPress}
/> />
@ -246,10 +247,10 @@ class QueuedTaskRow extends Component {
<ConfirmModal <ConfirmModal
isOpen={isCancelConfirmModalOpen} isOpen={isCancelConfirmModalOpen}
kind={kinds.DANGER} kind={kinds.DANGER}
title="Cancel" title={translate('Cancel')}
message={'Are you sure you want to cancel this pending task?'} message={translate('CancelPendingTask')}
confirmLabel="Yes, Cancel" confirmLabel={translate('YesCancel')}
cancelLabel="No, Leave It" cancelLabel={translate('NoLeaveIt')}
onConfirm={onCancelPress} onConfirm={onCancelPress}
onCancel={this.onAbortCancel} onCancel={this.onAbortCancel}
/> />

View File

@ -4,6 +4,7 @@ import FieldSet from 'Components/FieldSet';
import LoadingIndicator from 'Components/Loading/LoadingIndicator'; import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import Table from 'Components/Table/Table'; import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody'; import TableBody from 'Components/Table/TableBody';
import translate from 'Utilities/String/translate';
import QueuedTaskRowConnector from './QueuedTaskRowConnector'; import QueuedTaskRowConnector from './QueuedTaskRowConnector';
const columns = [ const columns = [
@ -14,27 +15,37 @@ const columns = [
}, },
{ {
name: 'commandName', name: 'commandName',
label: 'Name', get label() {
return translate('Name');
},
isVisible: true isVisible: true
}, },
{ {
name: 'queued', name: 'queued',
label: 'Queued', get label() {
return translate('Queued');
},
isVisible: true isVisible: true
}, },
{ {
name: 'started', name: 'started',
label: 'Started', get label() {
return translate('Started');
},
isVisible: true isVisible: true
}, },
{ {
name: 'ended', name: 'ended',
label: 'Ended', get label() {
return translate('Ended');
},
isVisible: true isVisible: true
}, },
{ {
name: 'duration', name: 'duration',
label: 'Duration', get label() {
return translate('Duration');
},
isVisible: true isVisible: true
}, },
{ {
@ -51,7 +62,7 @@ function QueuedTasks(props) {
} = props; } = props;
return ( return (
<FieldSet legend="Queue"> <FieldSet legend={translate('Queue')}>
{ {
isFetching && !isPopulated && isFetching && !isPopulated &&
<LoadingIndicator /> <LoadingIndicator />

View File

@ -4,32 +4,43 @@ import FieldSet from 'Components/FieldSet';
import LoadingIndicator from 'Components/Loading/LoadingIndicator'; import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import Table from 'Components/Table/Table'; import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody'; import TableBody from 'Components/Table/TableBody';
import translate from 'Utilities/String/translate';
import ScheduledTaskRowConnector from './ScheduledTaskRowConnector'; import ScheduledTaskRowConnector from './ScheduledTaskRowConnector';
const columns = [ const columns = [
{ {
name: 'name', name: 'name',
label: 'Name', get label() {
return translate('Name');
},
isVisible: true isVisible: true
}, },
{ {
name: 'interval', name: 'interval',
label: 'Interval', get label() {
return translate('Interval');
},
isVisible: true isVisible: true
}, },
{ {
name: 'lastExecution', name: 'lastExecution',
label: 'Last Execution', get label() {
return translate('LastExecution');
},
isVisible: true isVisible: true
}, },
{ {
name: 'lastDuration', name: 'lastDuration',
label: 'Last Duration', get label() {
return translate('LastDuration');
},
isVisible: true isVisible: true
}, },
{ {
name: 'nextExecution', name: 'nextExecution',
label: 'Next Execution', get label() {
return translate('NextExecution');
},
isVisible: true isVisible: true
}, },
{ {
@ -46,7 +57,7 @@ function ScheduledTasks(props) {
} = props; } = props;
return ( return (
<FieldSet legend="Scheduled"> <FieldSet legend={translate('Scheduled')}>
{ {
isFetching && !isPopulated && isFetching && !isPopulated &&
<LoadingIndicator /> <LoadingIndicator />

View File

@ -1,12 +1,13 @@
import React from 'react'; import React from 'react';
import PageContent from 'Components/Page/PageContent'; import PageContent from 'Components/Page/PageContent';
import PageContentBody from 'Components/Page/PageContentBody'; import PageContentBody from 'Components/Page/PageContentBody';
import translate from 'Utilities/String/translate';
import QueuedTasksConnector from './Queued/QueuedTasksConnector'; import QueuedTasksConnector from './Queued/QueuedTasksConnector';
import ScheduledTasksConnector from './Scheduled/ScheduledTasksConnector'; import ScheduledTasksConnector from './Scheduled/ScheduledTasksConnector';
function Tasks() { function Tasks() {
return ( return (
<PageContent title="Tasks"> <PageContent title={translate('Tasks')}>
<PageContentBody> <PageContentBody>
<ScheduledTasksConnector /> <ScheduledTasksConnector />
<QueuedTasksConnector /> <QueuedTasksConnector />

View File

@ -12,6 +12,7 @@ import PageContentBody from 'Components/Page/PageContentBody';
import { icons, kinds } from 'Helpers/Props'; import { icons, kinds } from 'Helpers/Props';
import formatDate from 'Utilities/Date/formatDate'; import formatDate from 'Utilities/Date/formatDate';
import formatDateTime from 'Utilities/Date/formatDateTime'; import formatDateTime from 'Utilities/Date/formatDateTime';
import translate from 'Utilities/String/translate';
import UpdateChanges from './UpdateChanges'; import UpdateChanges from './UpdateChanges';
import styles from './Updates.css'; import styles from './Updates.css';
@ -43,15 +44,15 @@ class Updates extends Component {
const hasUpdateToInstall = hasUpdates && _.some(items, { installable: true, latest: true }); const hasUpdateToInstall = hasUpdates && _.some(items, { installable: true, latest: true });
const noUpdateToInstall = hasUpdates && !hasUpdateToInstall; const noUpdateToInstall = hasUpdates && !hasUpdateToInstall;
const externalUpdaterPrefix = 'Unable to update Sonarr directly,'; const externalUpdaterPrefix = translate('UnableToUpdateSonarrDirectly');
const externalUpdaterMessages = { const externalUpdaterMessages = {
external: 'Sonarr is configured to use an external update mechanism', external: translate('ExternalUpdater'),
apt: 'use apt to install the update', apt: translate('AptUpdater'),
docker: 'update the docker container to receive the update' docker: translate('DockerUpdater')
}; };
return ( return (
<PageContent title="Updates"> <PageContent title={translate('Updates')}>
<PageContentBody> <PageContentBody>
{ {
!isPopulated && !hasError && !isPopulated && !hasError &&
@ -61,7 +62,7 @@ class Updates extends Component {
{ {
noUpdates && noUpdates &&
<Alert kind={kinds.INFO}> <Alert kind={kinds.INFO}>
No updates are available {translate('NoUpdatesAreAvailable')}
</Alert> </Alert>
} }
@ -76,7 +77,7 @@ class Updates extends Component {
isSpinning={isInstallingUpdate} isSpinning={isInstallingUpdate}
onPress={onInstallLatestPress} onPress={onInstallLatestPress}
> >
Install Latest {translate('InstallLatest')}
</SpinnerButton> : </SpinnerButton> :
<Fragment> <Fragment>
@ -112,7 +113,7 @@ class Updates extends Component {
/> />
<div className={styles.message}> <div className={styles.message}>
The latest version of Sonarr is already installed {translate('OnLatestVersion')}
</div> </div>
{ {
@ -164,7 +165,7 @@ class Updates extends Component {
kind={kinds.SUCCESS} kind={kinds.SUCCESS}
title={formatDateTime(update.installedOn, longDateFormat, timeFormat)} title={formatDateTime(update.installedOn, longDateFormat, timeFormat)}
> >
Currently Installed {translate('CurrentlyInstalled')}
</Label> : </Label> :
null null
} }
@ -176,7 +177,7 @@ class Updates extends Component {
kind={kinds.INVERSE} kind={kinds.INVERSE}
title={formatDateTime(update.installedOn, longDateFormat, timeFormat)} title={formatDateTime(update.installedOn, longDateFormat, timeFormat)}
> >
Previously Installed {translate('PreviouslyInstalled')}
</Label> : </Label> :
null null
} }
@ -184,19 +185,21 @@ class Updates extends Component {
{ {
!hasChanges && !hasChanges &&
<div>Maintenance Release: See GitHub commit history for details.</div> <div>
{translate('MaintenanceRelease')}
</div>
} }
{ {
hasChanges && hasChanges &&
<div className={styles.changes}> <div className={styles.changes}>
<UpdateChanges <UpdateChanges
title="New" title={translate('New')}
changes={update.changes.new} changes={update.changes.new}
/> />
<UpdateChanges <UpdateChanges
title="Fixed" title={translate('Fixed')}
changes={update.changes.fixed} changes={update.changes.fixed}
/> />
</div> </div>
@ -211,14 +214,14 @@ class Updates extends Component {
{ {
!!updatesError && !!updatesError &&
<div> <div>
Failed to fetch updates {translate('FailedToFetchUpdates')}
</div> </div>
} }
{ {
!!generalSettingsError && !!generalSettingsError &&
<div> <div>
Failed to update settings {translate('FailedToUpdateSettings')}
</div> </div>
} }
</PageContentBody> </PageContentBody>

View File

@ -1,13 +1,16 @@
{ {
"About": "About",
"AbsoluteEpisodeNumbers": "Absolute Episode Number(s)", "AbsoluteEpisodeNumbers": "Absolute Episode Number(s)",
"Actions": "Actions",
"Activity": "Activity", "Activity": "Activity",
"Add": "Add", "Add": "Add",
"AddNew": "Add New",
"Added": "Added", "Added": "Added",
"AddingTag": "Adding tag", "AddingTag": "Adding tag",
"AddNew": "Add New",
"AirDate": "Air Date", "AirDate": "Air Date",
"AllTitles": "All Titles", "AllTitles": "All Titles",
"ApiKeyValidationHealthCheckMessage": "Please update your API key to be at least {0} characters long. You can do this via settings or the config file", "ApiKeyValidationHealthCheckMessage": "Please update your API key to be at least {0} characters long. You can do this via settings or the config file",
"AppDataDirectory": "AppData directory",
"AppDataLocationHealthCheckMessage": "Updating will not be possible to prevent deleting AppData on Update", "AppDataLocationHealthCheckMessage": "Updating will not be possible to prevent deleting AppData on Update",
"Apply": "Apply", "Apply": "Apply",
"ApplyChanges": "Apply Changes", "ApplyChanges": "Apply Changes",
@ -20,9 +23,13 @@
"ApplyTagsHelpTextHowToApplySeries": "How to apply tags to the selected series", "ApplyTagsHelpTextHowToApplySeries": "How to apply tags to the selected series",
"ApplyTagsHelpTextRemove": "Remove: Remove the entered tags", "ApplyTagsHelpTextRemove": "Remove: Remove the entered tags",
"ApplyTagsHelpTextReplace": "Replace: Replace the tags with the entered tags (enter no tags to clear all tags)", "ApplyTagsHelpTextReplace": "Replace: Replace the tags with the entered tags (enter no tags to clear all tags)",
"AptUpdater": "Use apt to install the update",
"AutoAdd": "Auto Add", "AutoAdd": "Auto Add",
"AutomaticAdd": "Automatic Add", "AutomaticAdd": "Automatic Add",
"Backup": "Backup", "Backup": "Backup",
"BackupNow": "Backup Now",
"Backups": "Backups",
"BeforeUpdate": "Before update",
"Blocklist": "Blocklist", "Blocklist": "Blocklist",
"BlocklistRelease": "Blocklist Release", "BlocklistRelease": "Blocklist Release",
"BlocklistReleaseHelpText": "Prevents Sonarr from automatically grabbing this release again", "BlocklistReleaseHelpText": "Prevents Sonarr from automatically grabbing this release again",
@ -30,6 +37,8 @@
"Browser Reload Required": "Browser Reload Required", "Browser Reload Required": "Browser Reload Required",
"Calendar": "Calendar", "Calendar": "Calendar",
"Cancel": "Cancel", "Cancel": "Cancel",
"CancelPendingTask": "Are you sure you want to cancel this pending task?",
"Clear": "Clear",
"CloneCondition": "Clone Condition", "CloneCondition": "Clone Condition",
"CloneCustomFormat": "Clone Custom Format", "CloneCustomFormat": "Clone Custom Format",
"Close": "Close", "Close": "Close",
@ -38,11 +47,14 @@
"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",
"CustomFormatScore": "Custom Format Score", "CurrentlyInstalled": "Currently Installed",
"CustomFormats": "Custom Formats", "CustomFormats": "Custom Formats",
"CustomFormatScore": "Custom Format Score",
"CutoffUnmet": "Cutoff Unmet", "CutoffUnmet": "Cutoff Unmet",
"Daily": "Daily", "Daily": "Daily",
"Delete": "Delete", "Delete": "Delete",
"DeleteBackup": "Delete Backup",
"DeleteBackupMessageText": "Are you sure you want to delete the backup '{name}'?",
"DeleteCondition": "Delete Condition", "DeleteCondition": "Delete Condition",
"DeleteConditionMessageText": "Are you sure you want to delete the condition '{0}'?", "DeleteConditionMessageText": "Are you sure you want to delete the condition '{0}'?",
"DeleteCustomFormat": "Delete Custom Format", "DeleteCustomFormat": "Delete Custom Format",
@ -55,39 +67,61 @@
"DeleteSelectedIndexersMessageText": "Are you sure you want to delete {count} selected indexer(s)?", "DeleteSelectedIndexersMessageText": "Are you sure you want to delete {count} selected indexer(s)?",
"Details": "Details", "Details": "Details",
"Disabled": "Disabled", "Disabled": "Disabled",
"Discord": "Discord",
"DiskSpace": "Disk Space",
"Docker": "Docker",
"DockerUpdater": "Update the docker container to receive the update",
"Donations": "Donations",
"DotNetVersion": ".NET",
"Download": "Download",
"DownloadClientCheckNoneAvailableHealthCheckMessage": "No download client is available", "DownloadClientCheckNoneAvailableHealthCheckMessage": "No download client is available",
"DownloadClientCheckUnableToCommunicateWithHealthCheckMessage": "Unable to communicate with {0}.", "DownloadClientCheckUnableToCommunicateWithHealthCheckMessage": "Unable to communicate with {0}.",
"DownloadClientRootFolderHealthCheckMessage": "Download client {0} places downloads in the root folder {1}. You should not download to a root folder.", "DownloadClientRootFolderHealthCheckMessage": "Download client {0} places downloads in the root folder {1}. You should not download to a root folder.",
"DownloadClients": "Download Clients",
"DownloadClientSortingHealthCheckMessage": "Download client {0} has {1} sorting enabled for Sonarr's category. You should disable sorting in your download client to avoid import issues.", "DownloadClientSortingHealthCheckMessage": "Download client {0} has {1} sorting enabled for Sonarr's category. You should disable sorting in your download client to avoid import issues.",
"DownloadClientStatusAllClientHealthCheckMessage": "All download clients are unavailable due to failures", "DownloadClientStatusAllClientHealthCheckMessage": "All download clients are unavailable due to failures",
"DownloadClientStatusSingleClientHealthCheckMessage": "Download clients unavailable due to failures: {0}", "DownloadClientStatusSingleClientHealthCheckMessage": "Download clients unavailable due to failures: {0}",
"DownloadClients": "Download Clients", "Duration": "Duration",
"Edit": "Edit", "Edit": "Edit",
"EditSelectedDownloadClients": "Edit Selected Download Clients", "EditSelectedDownloadClients": "Edit Selected Download Clients",
"EditSelectedImportLists": "Edit Selected Import Lists", "EditSelectedImportLists": "Edit Selected Import Lists",
"EditSelectedIndexers": "Edit Selected Indexers", "EditSelectedIndexers": "Edit Selected Indexers",
"EditSeries": "Edit Series", "EditSeries": "Edit Series",
"EnableAutomaticSearch": "Enable Automatic Search", "EnableAutomaticSearch": "Enable Automatic Search",
"Enabled": "Enabled",
"EnableInteractiveSearch": "Enable Interactive Search", "EnableInteractiveSearch": "Enable Interactive Search",
"EnableRSS": "Enable RSS", "EnableRSS": "Enable RSS",
"Enabled": "Enabled",
"Ended": "Ended", "Ended": "Ended",
"EpisodeInfo": "Episode Info", "EpisodeInfo": "Episode Info",
"EpisodeNumbers": "Episode Number(s)", "EpisodeNumbers": "Episode Number(s)",
"ErrorRestoringBackup": "Error restoring backup",
"Events": "Events", "Events": "Events",
"Exception": "Exception",
"ExistingTag": "Existing tag", "ExistingTag": "Existing tag",
"ExportCustomFormat": "Export Custom Format", "ExportCustomFormat": "Export Custom Format",
"ExternalUpdater": "Sonarr is configured to use an external update mechanism",
"FailedToFetchUpdates": "Failed to fetch updates",
"FailedToUpdateSettings": "Failed to update settings",
"FeatureRequests": "Feature Requests",
"Filename": "Filename",
"Fixed": "Fixed",
"Forums": "Forums",
"FreeSpace": "Free Space",
"From": "From",
"FullSeason": "Full Season", "FullSeason": "Full Season",
"General": "General", "General": "General",
"GeneralSettings": "General Settings",
"Health": "Health",
"HiddenClickToShow": "Hidden, click to show", "HiddenClickToShow": "Hidden, click to show",
"HideAdvanced": "Hide Advanced", "HideAdvanced": "Hide Advanced",
"History": "History", "History": "History",
"HomePage": "Home Page",
"Implementation": "Implementation", "Implementation": "Implementation",
"ImportListRootFolderMissingRootHealthCheckMessage": "Missing root folder for import list(s): {0}", "ImportListRootFolderMissingRootHealthCheckMessage": "Missing root folder for import list(s): {0}",
"ImportListRootFolderMultipleMissingRootsHealthCheckMessage": "Multiple root folders are missing for import lists: {0}", "ImportListRootFolderMultipleMissingRootsHealthCheckMessage": "Multiple root folders are missing for import lists: {0}",
"ImportLists": "Import Lists",
"ImportListStatusAllUnavailableHealthCheckMessage": "All lists are unavailable due to failures", "ImportListStatusAllUnavailableHealthCheckMessage": "All lists are unavailable due to failures",
"ImportListStatusUnavailableHealthCheckMessage": "Lists unavailable due to failures: {0}", "ImportListStatusUnavailableHealthCheckMessage": "Lists unavailable due to failures: {0}",
"ImportLists": "Import Lists",
"ImportMechanismEnableCompletedDownloadHandlingIfPossibleHealthCheckMessage": "Enable Completed Download Handling if possible", "ImportMechanismEnableCompletedDownloadHandlingIfPossibleHealthCheckMessage": "Enable Completed Download Handling if possible",
"ImportMechanismEnableCompletedDownloadHandlingIfPossibleMultiComputerHealthCheckMessage": "Enable Completed Download Handling if possible (Multi-Computer unsupported)", "ImportMechanismEnableCompletedDownloadHandlingIfPossibleMultiComputerHealthCheckMessage": "Enable Completed Download Handling if possible (Multi-Computer unsupported)",
"ImportMechanismHandlingDisabledHealthCheckMessage": "Enable Completed Download Handling", "ImportMechanismHandlingDisabledHealthCheckMessage": "Enable Completed Download Handling",
@ -96,47 +130,74 @@
"IndexerLongTermStatusUnavailableHealthCheckMessage": "Indexers unavailable due to failures for more than 6 hours: {0}", "IndexerLongTermStatusUnavailableHealthCheckMessage": "Indexers unavailable due to failures for more than 6 hours: {0}",
"IndexerRssNoIndexersAvailableHealthCheckMessage": "All rss-capable indexers are temporarily unavailable due to recent indexer errors", "IndexerRssNoIndexersAvailableHealthCheckMessage": "All rss-capable indexers are temporarily unavailable due to recent indexer errors",
"IndexerRssNoIndexersEnabledHealthCheckMessage": "No indexers available with RSS sync enabled, Sonarr will not grab new releases automatically", "IndexerRssNoIndexersEnabledHealthCheckMessage": "No indexers available with RSS sync enabled, Sonarr will not grab new releases automatically",
"Indexers": "Indexers",
"IndexerSearchNoAutomaticHealthCheckMessage": "No indexers available with Automatic Search enabled, Sonarr will not provide any automatic search results", "IndexerSearchNoAutomaticHealthCheckMessage": "No indexers available with Automatic Search enabled, Sonarr will not provide any automatic search results",
"IndexerSearchNoAvailableIndexersHealthCheckMessage": "All search-capable indexers are temporarily unavailable due to recent indexer errors", "IndexerSearchNoAvailableIndexersHealthCheckMessage": "All search-capable indexers are temporarily unavailable due to recent indexer errors",
"IndexerSearchNoInteractiveHealthCheckMessage": "No indexers available with Interactive Search enabled, Sonarr will not provide any interactive search results", "IndexerSearchNoInteractiveHealthCheckMessage": "No indexers available with Interactive Search enabled, Sonarr will not provide any interactive search results",
"IndexerStatusAllUnavailableHealthCheckMessage": "All indexers are unavailable due to failures", "IndexerStatusAllUnavailableHealthCheckMessage": "All indexers are unavailable due to failures",
"IndexerStatusUnavailableHealthCheckMessage": "Indexers unavailable due to failures: {0}", "IndexerStatusUnavailableHealthCheckMessage": "Indexers unavailable due to failures: {0}",
"Indexers": "Indexers", "InstallLatest": "Install Latest",
"Interval": "Interval",
"IRC": "IRC",
"Language": "Language", "Language": "Language",
"Language that Sonarr will use for UI": "Language that Sonarr will use for UI", "Language that Sonarr will use for UI": "Language that Sonarr will use for UI",
"Languages": "Languages", "Languages": "Languages",
"LastDuration": "Last Duration",
"LastExecution": "Last Execution",
"LastWriteTime": "Last Write Time",
"LibraryImport": "Library Import", "LibraryImport": "Library Import",
"Location": "Location",
"LogFiles": "Log Files", "LogFiles": "Log Files",
"LogFilesLocation": "Log files are located in: {location}",
"Logs": "Logs",
"MaintenanceRelease": "Maintenance Release: bug fixes and other improvements. See Github Commit History for more details",
"ManageClients": "Manage Clients", "ManageClients": "Manage Clients",
"ManageDownloadClients": "Manage Download Clients", "ManageDownloadClients": "Manage Download Clients",
"ManageImportLists": "Manage Import Lists", "ManageImportLists": "Manage Import Lists",
"ManageIndexers": "Manage Indexers", "ManageIndexers": "Manage Indexers",
"ManageLists": "Manage Lists", "ManageLists": "Manage Lists",
"Manual": "Manual",
"MatchedToEpisodes": "Matched to Episodes", "MatchedToEpisodes": "Matched to Episodes",
"MatchedToSeason": "Matched to Season", "MatchedToSeason": "Matched to Season",
"MatchedToSeries": "Matched to Series", "MatchedToSeries": "Matched to Series",
"MediaManagement": "Media Management", "MediaManagement": "Media Management",
"Message": "Message",
"Metadata": "Metadata", "Metadata": "Metadata",
"MetadataSource": "Metadata Source", "MetadataSource": "Metadata Source",
"Missing": "Missing", "Missing": "Missing",
"Mode": "Mode",
"Monitored": "Monitored", "Monitored": "Monitored",
"MoreInfo": "More Info",
"MountHealthCheckMessage": "Mount containing a series path is mounted read-only: ", "MountHealthCheckMessage": "Mount containing a series path is mounted read-only: ",
"MultiSeason": "Multi-Season", "MultiSeason": "Multi-Season",
"Name": "Name", "Name": "Name",
"Negated": "Negated", "Negated": "Negated",
"Network": "Network", "Network": "Network",
"New": "New",
"NextAiring": "Next Airing", "NextAiring": "Next Airing",
"NextExecution": "Next Execution",
"No": "No", "No": "No",
"NoBackupsAreAvailable": "No backups are available",
"NoChange": "No Change", "NoChange": "No Change",
"NoDownloadClientsFound": "No download clients found", "NoDownloadClientsFound": "No download clients found",
"NoEventsFound": "No events found",
"NoImportListsFound": "No import lists found", "NoImportListsFound": "No import lists found",
"NoIndexersFound": "No indexers found", "NoIndexersFound": "No indexers found",
"NoIssuesWithYourConfiguration": "No issues with your configuration",
"NoLeaveIt": "No, Leave It",
"NoLogFiles": "No log files",
"NoSeasons": "No seasons", "NoSeasons": "No seasons",
"NoUpdatesAreAvailable": "No updates are available",
"OneSeason": "1 season", "OneSeason": "1 season",
"OnLatestVersion": "The latest version of Radarr is already installed",
"Options": "Options",
"OriginalLanguage": "Original Language", "OriginalLanguage": "Original Language",
"PackageVersion": "Package Version",
"PackageVersionInfo": "{packageVersion} by {packageAuthor}",
"PartialSeason": "Partial Season", "PartialSeason": "Partial Season",
"Path": "Path", "Path": "Path",
"PreviousAiring": "Previous Airing", "PreviousAiring": "Previous Airing",
"PreviouslyInstalled": "Previously Installed",
"Priority": "Priority", "Priority": "Priority",
"Profiles": "Profiles", "Profiles": "Profiles",
"Proper": "Proper", "Proper": "Proper",
@ -146,13 +207,17 @@
"Quality": "Quality", "Quality": "Quality",
"QualityProfile": "Quality Profile", "QualityProfile": "Quality Profile",
"Queue": "Queue", "Queue": "Queue",
"Queued": "Queued",
"ReadTheWikiForMoreInformation": "Read the Wiki for more information",
"Real": "Real", "Real": "Real",
"RecycleBinUnableToWriteHealthCheckMessage": "Unable to write to configured recycling bin folder: {0}. Ensure this path exists and is writable by the user running Sonarr", "RecycleBinUnableToWriteHealthCheckMessage": "Unable to write to configured recycling bin folder: {0}. Ensure this path exists and is writable by the user running Sonarr",
"Refresh": "Refresh",
"RefreshSeries": "Refresh Series", "RefreshSeries": "Refresh Series",
"Release": "Release", "Release": "Release",
"ReleaseGroup": "Release Group", "ReleaseGroup": "Release Group",
"ReleaseHash": "Release Hash", "ReleaseHash": "Release Hash",
"ReleaseTitle": "Release Title", "ReleaseTitle": "Release Title",
"Reload": "Reload",
"RemotePathMappingBadDockerPathHealthCheckMessage": "You are using docker; download client {0} places downloads in {1} but this is not a valid {2} path. Review your remote path mappings and download client settings.", "RemotePathMappingBadDockerPathHealthCheckMessage": "You are using docker; download client {0} places downloads in {1} but this is not a valid {2} path. Review your remote path mappings and download client settings.",
"RemotePathMappingDockerFolderMissingHealthCheckMessage": "You are using docker; download client {0} places downloads in {1} but this directory does not appear to exist inside the container. Review your remote path mappings and container volume settings.", "RemotePathMappingDockerFolderMissingHealthCheckMessage": "You are using docker; download client {0} places downloads in {1} but this directory does not appear to exist inside the container. Review your remote path mappings and container volume settings.",
"RemotePathMappingDownloadPermissionsHealthCheckMessage": "Sonarr can see but not access downloaded episode {0}. Likely permissions error.", "RemotePathMappingDownloadPermissionsHealthCheckMessage": "Sonarr can see but not access downloaded episode {0}. Likely permissions error.",
@ -171,6 +236,9 @@
"Remove": "Remove", "Remove": "Remove",
"RemoveCompleted": "Remove Completed", "RemoveCompleted": "Remove Completed",
"RemoveCompletedDownloads": "Remove Completed Downloads", "RemoveCompletedDownloads": "Remove Completed Downloads",
"RemovedFromTaskQueue": "Removed from task queue",
"RemovedSeriesMultipleRemovedHealthCheckMessage": "Series {0} were removed from TheTVDB",
"RemovedSeriesSingleRemovedHealthCheckMessage": "Series {0} was removed from TheTVDB",
"RemoveFailed": "Remove Failed", "RemoveFailed": "Remove Failed",
"RemoveFailedDownloads": "Remove Failed Downloads", "RemoveFailedDownloads": "Remove Failed Downloads",
"RemoveFromDownloadClient": "Remove From Download Client", "RemoveFromDownloadClient": "Remove From Download Client",
@ -179,40 +247,62 @@
"RemoveSelectedItemQueueMessageText": "Are you sure you want to remove 1 item from the queue?", "RemoveSelectedItemQueueMessageText": "Are you sure you want to remove 1 item from the queue?",
"RemoveSelectedItems": "Remove Selected Items", "RemoveSelectedItems": "Remove Selected Items",
"RemoveSelectedItemsQueueMessageText": "Are you sure you want to remove {0} items from the queue?", "RemoveSelectedItemsQueueMessageText": "Are you sure you want to remove {0} items from the queue?",
"RemovedSeriesMultipleRemovedHealthCheckMessage": "Series {0} were removed from TheTVDB",
"RemovedSeriesSingleRemovedHealthCheckMessage": "Series {0} was removed from TheTVDB",
"RemovingTag": "Removing tag", "RemovingTag": "Removing tag",
"Repack": "Repack", "Repack": "Repack",
"Replace": "Replace", "Replace": "Replace",
"Required": "Required", "Required": "Required",
"Restart": "Restart",
"RestartReloadNote": "Note: Sonarr will automatically restart and reload the UI during the restore process.",
"Restore": "Restore",
"RestoreBackup": "Restore Backup",
"Result": "Result", "Result": "Result",
"RootFolder": "Root Folder", "RootFolder": "Root Folder",
"RootFolderMissingHealthCheckMessage": "Missing root folder: {0}", "RootFolderMissingHealthCheckMessage": "Missing root folder: {0}",
"RootFolderMultipleMissingHealthCheckMessage": "Multiple root folders are missing: {0}", "RootFolderMultipleMissingHealthCheckMessage": "Multiple root folders are missing: {0}",
"Scheduled": "Scheduled",
"SearchForMonitoredEpisodes": "Search for monitored episodes", "SearchForMonitoredEpisodes": "Search for monitored episodes",
"SeasonNumber": "Season Number", "SeasonNumber": "Season Number",
"Series": "Series", "Series": "Series",
"SeriesEditor": "Series Editor",
"SeriesTitle": "Series Title", "SeriesTitle": "Series Title",
"SetTags": "Set Tags", "SetTags": "Set Tags",
"Settings": "Settings", "Settings": "Settings",
"ShowAdvanced": "Show Advanced", "ShowAdvanced": "Show Advanced",
"ShownClickToHide": "Shown, click to hide", "ShownClickToHide": "Shown, click to hide",
"Size": "Size",
"SizeOnDisk": "Size on disk", "SizeOnDisk": "Size on disk",
"Source": "Source",
"Special": "Special", "Special": "Special",
"Started": "Started",
"StartupDirectory": "Startup directory",
"Status": "Status",
"System": "System", "System": "System",
"SystemTimeHealthCheckMessage": "System time is off by more than 1 day. Scheduled tasks may not run correctly until the time is corrected", "SystemTimeHealthCheckMessage": "System time is off by more than 1 day. Scheduled tasks may not run correctly until the time is corrected",
"Tags": "Tags", "Tags": "Tags",
"Tasks": "Tasks", "Tasks": "Tasks",
"TaskUserAgentTooltip": "User-Agent provided by the app that called the API",
"TestAll": "Test All",
"TestParsing": "Test Parsing", "TestParsing": "Test Parsing",
"TheLogLevelDefault": "The log level defaults to 'Info' and can be changed in [General Settings](/settings/general)",
"Time": "Time",
"TotalSpace": "Total Space",
"Twitter": "Twitter",
"UI": "UI", "UI": "UI",
"UI Language": "UI Language", "UI Language": "UI Language",
"UnableToLoadBackups": "Unable to load backups",
"UnableToUpdateSonarrDirectly": "Unable to update Sonarr directly,",
"Unmonitored": "Unmonitored", "Unmonitored": "Unmonitored",
"UpdateAvailableHealthCheckMessage": "New update is available", "UpdateAvailableHealthCheckMessage": "New update is available",
"UpdaterLogFiles": "Updater Log Files",
"Updates": "Updates",
"UpdateStartupNotWritableHealthCheckMessage": "Cannot install update because startup folder '{0}' is not writable by the user '{1}'.", "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.", "UpdateStartupTranslocationHealthCheckMessage": "Cannot install update because startup folder '{0}' is in an App Translocation folder.",
"UpdateUINotWritableHealthCheckMessage": "Cannot install update because UI folder '{0}' is not writable by the user '{1}'.", "UpdateUINotWritableHealthCheckMessage": "Cannot install update because UI folder '{0}' is not writable by the user '{1}'.",
"Updates": "Updates", "Uptime": "Uptime",
"Version": "Version", "Version": "Version",
"Wanted": "Wanted", "Wanted": "Wanted",
"Yes": "Yes" "Wiki": "Wiki",
"WouldYouLikeToRestoreBackup": "Would you like to restore the backup '{name}'?",
"Yes": "Yes",
"YesCancel": "Yes, Cancel"
} }