Add Translations to Settings Pages

This commit is contained in:
Stevie Robinson 2023-07-22 21:14:33 +02:00 committed by Mark McDowall
parent 8008610d47
commit f2c31e92ce
102 changed files with 1697 additions and 802 deletions

2
.gitignore vendored
View File

@ -128,8 +128,6 @@ coverage*.json
setup/Output/
*.~is
UI/
#VS outout folders
bin
obj

View File

@ -133,7 +133,7 @@ const links = [
to: '/settings/general'
},
{
title: () => translate('UI'),
title: () => translate('Ui'),
to: '/settings/ui'
}
]

View File

@ -11,7 +11,8 @@ import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { inputTypes, kinds } from 'Helpers/Props';
import { authenticationMethodOptions, authenticationRequiredOptions, authenticationRequiredWarning } from 'Settings/General/SecuritySettings';
import { authenticationMethodOptions, authenticationRequiredOptions } from 'Settings/General/SecuritySettings';
import translate from 'Utilities/String/translate';
import styles from './AuthenticationRequiredModalContent.css';
function onModalClose() {
@ -54,7 +55,7 @@ function AuthenticationRequiredModalContent(props) {
onModalClose={onModalClose}
>
<ModalHeader>
Authentication Required
{translate('AuthenticationRequired')}
</ModalHeader>
<ModalBody>
@ -62,40 +63,40 @@ function AuthenticationRequiredModalContent(props) {
className={styles.authRequiredAlert}
kind={kinds.WARNING}
>
{authenticationRequiredWarning}
{translate('AuthenticationRequiredWarning')}
</Alert>
{
isPopulated && !error ?
<div>
<FormGroup>
<FormLabel>Authentication</FormLabel>
<FormLabel>{translate('Authentication')}</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
name="authenticationMethod"
values={authenticationMethodOptions}
helpText="Require Username and Password to access Sonarr"
helpText={translate('AuthenticationMethodHelpText')}
onChange={onInputChange}
{...authenticationMethod}
/>
</FormGroup>
<FormGroup>
<FormLabel>Authentication Required</FormLabel>
<FormLabel>{translate('AuthenticationRequired')}</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
name="authenticationRequired"
values={authenticationRequiredOptions}
helpText="Change which requests authentication is required for. Do not change unless you understand the risks."
helpText={translate('AuthenticationRequiredHelpText')}
onChange={onInputChange}
{...authenticationRequired}
/>
</FormGroup>
<FormGroup>
<FormLabel>Username</FormLabel>
<FormLabel>{translate('Username')}</FormLabel>
<FormInputGroup
type={inputTypes.TEXT}
@ -106,7 +107,7 @@ function AuthenticationRequiredModalContent(props) {
</FormGroup>
<FormGroup>
<FormLabel>Password</FormLabel>
<FormLabel>{translate('Password')}</FormLabel>
<FormInputGroup
type={inputTypes.PASSWORD}
@ -131,7 +132,7 @@ function AuthenticationRequiredModalContent(props) {
isDisabled={!authenticationEnabled}
onPress={onSavePress}
>
Save
{translate('Save')}
</SpinnerButton>
</ModalFooter>
</ModalContent>

View File

@ -6,11 +6,12 @@ import PageContentBody from 'Components/Page/PageContentBody';
import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator';
import ParseToolbarButton from 'Parse/ParseToolbarButton';
import SettingsToolbarConnector from 'Settings/SettingsToolbarConnector';
import translate from 'Utilities/String/translate';
import CustomFormatsConnector from './CustomFormats/CustomFormatsConnector';
function CustomFormatSettingsPage() {
return (
<PageContent title="Custom Format Settings">
<PageContent title={translate('CustomFormatsSettings')}>
<SettingsToolbarConnector
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore

View File

@ -5,6 +5,7 @@ import FieldSet from 'Components/FieldSet';
import Icon from 'Components/Icon';
import PageSectionContent from 'Components/Page/PageSectionContent';
import { icons } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import CustomFormat from './CustomFormat';
import EditCustomFormatModalConnector from './EditCustomFormatModalConnector';
import styles from './CustomFormats.css';
@ -58,9 +59,9 @@ class CustomFormats extends Component {
} = this.props;
return (
<FieldSet legend="Custom Formats">
<FieldSet legend={translate('CustomFormats')}>
<PageSectionContent
errorMessage="Unable to load custom formats"
errorMessage={translate('CustomFormatsLoadError')}
{...otherProps}c={true}
>
<div className={styles.customFormats}>

View File

@ -15,6 +15,7 @@ import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { icons, inputTypes, kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import ImportCustomFormatModal from './ImportCustomFormatModal';
import AddSpecificationModal from './Specifications/AddSpecificationModal';
import EditSpecificationModalConnector from './Specifications/EditSpecificationModalConnector';
@ -99,7 +100,7 @@ class EditCustomFormatModalContent extends Component {
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
{id ? 'Edit Custom Format' : 'Add Custom Format'}
{id ? translate('EditCustomFormat') : translate('AddCustomFormat')}
</ModalHeader>
<ModalBody>
@ -112,7 +113,7 @@ class EditCustomFormatModalContent extends Component {
{
!isFetching && !!error &&
<div>
{'Unable to add a new custom format, please try again.'}
{translate('AddCustomFormatError')}
</div>
}
@ -124,7 +125,7 @@ class EditCustomFormatModalContent extends Component {
>
<FormGroup>
<FormLabel>
Name
{translate('Name')}
</FormLabel>
<FormInputGroup
@ -136,19 +137,19 @@ class EditCustomFormatModalContent extends Component {
</FormGroup>
<FormGroup>
<FormLabel>{'Include Custom Format when Renaming'}</FormLabel>
<FormLabel>{translate('IncludeCustomFormatWhenRenaming')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="includeCustomFormatWhenRenaming"
helpText={'Include in {Custom Formats} renaming format'}
helpText={translate('IncludeCustomFormatWhenRenamingHelpText')}
{...includeCustomFormatWhenRenaming}
onChange={onInputChange}
/>
</FormGroup>
</Form>
<FieldSet legend={'Conditions'}>
<FieldSet legend={translate('Conditions')}>
<div className={styles.customFormats}>
{
specifications.map((tag) => {
@ -205,7 +206,7 @@ class EditCustomFormatModalContent extends Component {
kind={kinds.DANGER}
onPress={onDeleteCustomFormatPress}
>
Delete
{translate('Delete')}
</Button>
}
@ -213,14 +214,14 @@ class EditCustomFormatModalContent extends Component {
className={styles.deleteButton}
onPress={this.onImportPress}
>
Import
{translate('Import')}
</Button>
</div>
<Button
onPress={onModalClose}
>
Cancel
{translate('Cancel')}
</Button>
<SpinnerErrorButton
@ -228,7 +229,7 @@ class EditCustomFormatModalContent extends Component {
error={saveError}
onPress={onSavePress}
>
Save
{translate('Save')}
</SpinnerErrorButton>
</ModalFooter>
</ModalContent>

View File

@ -8,6 +8,7 @@ import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import styles from './ExportCustomFormatModalContent.css';
class ExportCustomFormatModalContent extends Component {
@ -28,7 +29,7 @@ class ExportCustomFormatModalContent extends Component {
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
Export Custom Format
{translate('ExportCustomFormat')}
</ModalHeader>
<ModalBody>
@ -41,7 +42,7 @@ class ExportCustomFormatModalContent extends Component {
{
!isFetching && !!error &&
<div>
Unable to load custom formats
{translate('CustomFormatsLoadError')}
</div>
}
@ -59,13 +60,13 @@ class ExportCustomFormatModalContent extends Component {
<ClipboardButton
className={styles.button}
value={json}
title="Copy to clipboard"
title={translate('CopyToClipboard')}
kind={kinds.DEFAULT}
/>
<Button
onPress={onModalClose}
>
Close
{translate('Close')}
</Button>
</ModalFooter>
</ModalContent>

View File

@ -12,6 +12,7 @@ import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { inputTypes, sizes } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import styles from './ImportCustomFormatModalContent.css';
class ImportCustomFormatModalContent extends Component {
@ -82,7 +83,7 @@ class ImportCustomFormatModalContent extends Component {
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
Import Custom Format
{translate('ImportCustomFormat')}
</ModalHeader>
<ModalBody>
@ -95,7 +96,7 @@ class ImportCustomFormatModalContent extends Component {
{
!isFetching && !!error &&
<div>
Unable to load custom formats
{translate('CustomFormatsLoadError')}
</div>
}
@ -104,7 +105,7 @@ class ImportCustomFormatModalContent extends Component {
<Form>
<FormGroup size={sizes.MEDIUM}>
<FormLabel>
Custom Format JSON
{translate('CustomFormatJSON')}
</FormLabel>
<FormInputGroup
key={0}
@ -125,14 +126,14 @@ class ImportCustomFormatModalContent extends Component {
<Button
onPress={onModalClose}
>
Cancel
{translate('Cancel')}
</Button>
<SpinnerErrorButton
onPress={this.onImportPress}
isSpinning={isSpinning}
error={parseError}
>
Import
{translate('Import')}
</SpinnerErrorButton>
</ModalFooter>
</ModalContent>

View File

@ -6,6 +6,7 @@ import { createSelector } from 'reselect';
import { clearPendingChanges } from 'Store/Actions/baseActions';
import { clearCustomFormatSpecificationPending, deleteAllCustomFormatSpecification, fetchCustomFormatSpecificationSchema, saveCustomFormatSpecification, selectCustomFormatSpecificationSchema, setCustomFormatSpecificationFieldValue, setCustomFormatSpecificationValue, setCustomFormatValue } from 'Store/Actions/settingsActions';
import createProviderSettingsSelector from 'Store/Selectors/createProviderSettingsSelector';
import translate from 'Utilities/String/translate';
import ImportCustomFormatModalContent from './ImportCustomFormatModalContent';
function createMapStateToProps() {
@ -88,7 +89,9 @@ class ImportCustomFormatModalContentConnector extends Component {
const selectedImplementation = _.find(this.props.specificationSchema, { implementation: spec.implementation });
if (!selectedImplementation) {
throw new Error(`Unknown Custom Format condition '${spec.implementation}'`);
throw new Error(translate('CustomFormatUnknownCondition', {
implementation: spec.implementation
}));
}
this.props.selectCustomFormatSpecificationSchema({ implementation: spec.implementation });
@ -108,7 +111,10 @@ class ImportCustomFormatModalContentConnector extends Component {
for (const [key, value] of Object.entries(fields)) {
const field = _.find(schema.fields, { name: key });
if (!field) {
throw new Error(`Unknown option '${key}' for condition '${schema.implementationName}'`);
throw new Error(translate('CustomFormatUnknownConditionOption', {
key,
implementation: schema.implementationName
}));
}
this.props.setCustomFormatSpecificationFieldValue({ name: key, value });

View File

@ -5,6 +5,7 @@ import Link from 'Components/Link/Link';
import Menu from 'Components/Menu/Menu';
import MenuContent from 'Components/Menu/MenuContent';
import { sizes } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import AddSpecificationPresetMenuItem from './AddSpecificationPresetMenuItem';
import styles from './AddSpecificationItem.css';
@ -57,7 +58,7 @@ class AddSpecificationItem extends Component {
size={sizes.SMALL}
onPress={this.onSpecificationSelect}
>
Custom
{translate('Custom')}
</Button>
<Menu className={styles.presetsMenu}>
@ -65,7 +66,7 @@ class AddSpecificationItem extends Component {
className={styles.presetsMenuButton}
size={sizes.SMALL}
>
Presets
{translate('Presets')}
</Button>
<MenuContent>
@ -90,7 +91,7 @@ class AddSpecificationItem extends Component {
to={infoLink}
size={sizes.SMALL}
>
More Info
{translate('MoreInfo')}
</Button>
</div>
</div>

View File

@ -9,6 +9,7 @@ import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import AddSpecificationItem from './AddSpecificationItem';
import styles from './AddSpecificationModalContent.css';
@ -42,7 +43,7 @@ class AddSpecificationModalContent extends Component {
{
!isSchemaFetching && !!schemaError &&
<div>
{'Unable to add a new condition, please try again.'}
{translate('AddConditionError')}
</div>
}
@ -52,11 +53,11 @@ class AddSpecificationModalContent extends Component {
<Alert kind={kinds.INFO}>
<div>
{'Sonarr supports custom conditions against the release properties below.'}
{translate('SupportedCustomConditions')}
</div>
<div>
{'Visit the wiki for more details: '}
<Link to="https://wiki.servarr.com/sonarr/settings#custom-formats-2">{'Wiki'}</Link>
{translate('VisitTheWikiForMoreDetails')}
<Link to="https://wiki.servarr.com/sonarr/settings#custom-formats-2">{translate('Wiki')}</Link>
</div>
</Alert>
@ -81,7 +82,7 @@ class AddSpecificationModalContent extends Component {
<Button
onPress={onModalClose}
>
Close
{translate('Close')}
</Button>
</ModalFooter>
</ModalContent>

View File

@ -7,13 +7,14 @@ import FormInputGroup from 'Components/Form/FormInputGroup';
import FormLabel from 'Components/Form/FormLabel';
import ProviderFieldFormGroup from 'Components/Form/ProviderFieldFormGroup';
import Button from 'Components/Link/Button';
import Link from 'Components/Link/Link';
import SpinnerErrorButton from 'Components/Link/SpinnerErrorButton';
import ModalBody from 'Components/Modal/ModalBody';
import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { inputTypes, kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import InlineMarkdown from '../../../../Components/Markdown/InlineMarkdown';
import styles from './EditSpecificationModalContent.css';
function EditSpecificationModalContent(props) {
@ -40,7 +41,7 @@ function EditSpecificationModalContent(props) {
return (
<ModalContent onModalClose={onCancelPress}>
<ModalHeader>
{`${id ? 'Edit' : 'Add'} Condition - ${implementationName}`}
{`${id ? translate('EditCondition') : translate('AddCondition')} - ${implementationName}`}
</ModalHeader>
<ModalBody>
@ -48,22 +49,23 @@ function EditSpecificationModalContent(props) {
{...otherProps}
>
{
fields && fields.some((x) => x.label === 'Regular Expression') &&
fields && fields.some((x) => x.label === translate('RegularExpression')) &&
<Alert kind={kinds.INFO}>
<div>
<div dangerouslySetInnerHTML={{ __html: 'This condition matches using Regular Expressions. Note that the characters <code>\\^$.|?*+()[{</code> have special meanings and need escaping with a <code>\\</code>' }} />
{'More details'} <Link to="https://www.regular-expressions.info/tutorial.html">{'Here'}</Link>
<InlineMarkdown data={translate('ConditionUsingRegularExpressions')} />
</div>
<div>
{'Regular expressions can be tested '}
<Link to="http://regexstorm.net/tester">Here</Link>
<InlineMarkdown data={translate('RegularExpressionsTutorialLink')} />
</div>
<div>
<InlineMarkdown data={translate('RegularExpressionsCanBeTested')} />
</div>
</Alert>
}
<FormGroup>
<FormLabel>
Name
{translate('Name')}
</FormLabel>
<FormInputGroup
@ -91,28 +93,28 @@ function EditSpecificationModalContent(props) {
<FormGroup>
<FormLabel>
Negate
{translate('Negate')}
</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="negate"
{...negate}
helpText={`If checked, the custom format will not apply if this ${implementationName} condition matches.`}
helpText={translate('NegateHelpText', { implementationName })}
onChange={onInputChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>
Required
{translate('Required')}
</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="required"
{...required}
helpText={`This ${implementationName} condition must match for the custom format to apply. Otherwise a single ${implementationName} match is sufficient.`}
helpText={translate('RequiredHelpText', { implementationName })}
onChange={onInputChange}
/>
</FormGroup>
@ -126,21 +128,21 @@ function EditSpecificationModalContent(props) {
kind={kinds.DANGER}
onPress={onDeleteSpecificationPress}
>
Delete
{translate('Delete')}
</Button>
}
<Button
onPress={onCancelPress}
>
Cancel
{translate('Cancel')}
</Button>
<SpinnerErrorButton
isSpinning={false}
onPress={onSavePress}
>
Save
{translate('Save')}
</SpinnerErrorButton>
</ModalFooter>
</ModalContent>

View File

@ -115,7 +115,7 @@ class Specification extends Component {
isOpen={this.state.isDeleteSpecificationModalOpen}
kind={kinds.DANGER}
title={translate('DeleteCondition')}
message={translate('DeleteConditionMessageText', [name])}
message={translate('DeleteConditionMessageText', { name })}
confirmLabel={translate('Delete')}
onConfirm={this.onConfirmDeleteSpecification}
onCancel={this.onDeleteSpecificationModalClose}

View File

@ -70,7 +70,7 @@ class DownloadClientSettings extends Component {
} = this.state;
return (
<PageContent title="Download Client Settings">
<PageContent title={translate('DownloadClientSettings')}>
<SettingsToolbarConnector
isSaving={isSaving}
hasPendingChanges={hasPendingChanges}
@ -79,7 +79,7 @@ class DownloadClientSettings extends Component {
<PageToolbarSeparator />
<PageToolbarButton
label="Test All Clients"
label={translate('TestAllClients')}
iconName={icons.TEST}
isSpinning={isTestingAll}
onPress={dispatchTestAllDownloadClients}

View File

@ -5,6 +5,7 @@ import Link from 'Components/Link/Link';
import Menu from 'Components/Menu/Menu';
import MenuContent from 'Components/Menu/MenuContent';
import { sizes } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import AddDownloadClientPresetMenuItem from './AddDownloadClientPresetMenuItem';
import styles from './AddDownloadClientItem.css';
@ -57,7 +58,7 @@ class AddDownloadClientItem extends Component {
size={sizes.SMALL}
onPress={this.onDownloadClientSelect}
>
Custom
{translate('Custom')}
</Button>
<Menu className={styles.presetsMenu}>
@ -65,7 +66,7 @@ class AddDownloadClientItem extends Component {
className={styles.presetsMenuButton}
size={sizes.SMALL}
>
Presets
{translate('Presets')}
</Button>
<MenuContent>
@ -90,7 +91,7 @@ class AddDownloadClientItem extends Component {
to={infoLink}
size={sizes.SMALL}
>
More info
{translate('MoreInfo')}
</Button>
</div>
</div>

View File

@ -9,6 +9,7 @@ import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import AddDownloadClientItem from './AddDownloadClientItem';
import styles from './AddDownloadClientModalContent.css';
@ -31,7 +32,7 @@ class AddDownloadClientModalContent extends Component {
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
Add Download Client
{translate('AddDownloadClient')}
</ModalHeader>
<ModalBody>
@ -42,7 +43,9 @@ class AddDownloadClientModalContent extends Component {
{
!isSchemaFetching && !!schemaError &&
<div>Unable to add a new downloadClient, please try again.</div>
<div>
{translate('AddDownloadClientError')}
</div>
}
{
@ -50,11 +53,15 @@ class AddDownloadClientModalContent extends Component {
<div>
<Alert kind={kinds.INFO}>
<div>Sonarr supports many popular torrent and usenet download clients.</div>
<div>For more information on the individual download clients, click the more info buttons.</div>
<div>
{translate('SupportedDownloadClients')}
</div>
<div>
{translate('SupportedDownloadClientsMoreInfo')}
</div>
</Alert>
<FieldSet legend="Usenet">
<FieldSet legend={translate('Usenet')}>
<div className={styles.downloadClients}>
{
usenetDownloadClients.map((downloadClient) => {
@ -71,7 +78,7 @@ class AddDownloadClientModalContent extends Component {
</div>
</FieldSet>
<FieldSet legend="Torrents">
<FieldSet legend={translate('Torrents')}>
<div className={styles.downloadClients}>
{
torrentDownloadClients.map((downloadClient) => {
@ -94,7 +101,7 @@ class AddDownloadClientModalContent extends Component {
<Button
onPress={onModalClose}
>
Close
{translate('Close')}
</Button>
</ModalFooter>
</ModalContent>

View File

@ -5,6 +5,7 @@ import Label from 'Components/Label';
import ConfirmModal from 'Components/Modal/ConfirmModal';
import TagList from 'Components/TagList';
import { kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import EditDownloadClientModalConnector from './EditDownloadClientModalConnector';
import styles from './DownloadClient.css';
@ -75,13 +76,13 @@ class DownloadClient extends Component {
{
enable ?
<Label kind={kinds.SUCCESS}>
Enabled
{translate('Enabled')}
</Label> :
<Label
kind={kinds.DISABLED}
outline={true}
>
Disabled
{translate('Disabled')}
</Label>
}
@ -91,7 +92,7 @@ class DownloadClient extends Component {
kind={kinds.DISABLED}
outline={true}
>
Priority: {priority}
{translate('PrioritySettings', { priority })}
</Label>
}
</div>
@ -111,9 +112,9 @@ class DownloadClient extends Component {
<ConfirmModal
isOpen={this.state.isDeleteDownloadClientModalOpen}
kind={kinds.DANGER}
title="Delete Download Client"
message={`Are you sure you want to delete the download client '${name}'?`}
confirmLabel="Delete"
title={translate('DeleteDownloadClient')}
message={translate('DeleteDownloadClientMessageText', { name })}
confirmLabel={translate('Delete')}
onConfirm={this.onConfirmDeleteDownloadClient}
onCancel={this.onDeleteDownloadClientModalClose}
/>

View File

@ -5,6 +5,7 @@ import FieldSet from 'Components/FieldSet';
import Icon from 'Components/Icon';
import PageSectionContent from 'Components/Page/PageSectionContent';
import { icons } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import AddDownloadClientModal from './AddDownloadClientModal';
import DownloadClient from './DownloadClient';
import EditDownloadClientModalConnector from './EditDownloadClientModalConnector';
@ -59,9 +60,9 @@ class DownloadClients extends Component {
} = this.state;
return (
<FieldSet legend="Download Clients">
<FieldSet legend={translate('DownloadClients')}>
<PageSectionContent
errorMessage="Unable to load download clients"
errorMessage={translate('DownloadClientsLoadError')}
{...otherProps}
>
<div className={styles.downloadClients}>

View File

@ -15,6 +15,7 @@ import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { inputTypes, kinds, sizes } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import styles from './EditDownloadClientModalContent.css';
class EditDownloadClientModalContent extends Component {
@ -57,7 +58,7 @@ class EditDownloadClientModalContent extends Component {
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
{`${id ? 'Edit' : 'Add'} Download Client - ${implementationName}`}
{`${id ? translate('Edit') : translate('Add')} ${translate('DownloadClient')} - ${implementationName}`}
</ModalHeader>
<ModalBody>
@ -68,7 +69,9 @@ class EditDownloadClientModalContent extends Component {
{
!isFetching && !!error &&
<div>Unable to add a new download client, please try again.</div>
<div>
{translate('AddDownloadClientError')}
</div>
}
{
@ -85,7 +88,7 @@ class EditDownloadClientModalContent extends Component {
}
<FormGroup>
<FormLabel>Name</FormLabel>
<FormLabel>{translate('Name')}</FormLabel>
<FormInputGroup
type={inputTypes.TEXT}
@ -96,7 +99,7 @@ class EditDownloadClientModalContent extends Component {
</FormGroup>
<FormGroup>
<FormLabel>Enable</FormLabel>
<FormLabel>{translate('Enable')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
@ -125,12 +128,12 @@ class EditDownloadClientModalContent extends Component {
advancedSettings={advancedSettings}
isAdvanced={true}
>
<FormLabel>Client Priority</FormLabel>
<FormLabel>{translate('ClientPriority')}</FormLabel>
<FormInputGroup
type={inputTypes.NUMBER}
name="priority"
helpText="Prioritize multiple Download Clients. Round-Robin is used for clients with the same priority."
helpText={translate('PriorityHelpText')}
min={1}
max={50}
{...priority}
@ -139,12 +142,12 @@ class EditDownloadClientModalContent extends Component {
</FormGroup>
<FormGroup>
<FormLabel>Tags</FormLabel>
<FormLabel>{translate('Tags')}</FormLabel>
<FormInputGroup
type={inputTypes.TAG}
name="tags"
helpText="Only use this download client for series with at least one matching tag. Leave blank to use with all series."
helpText={translate('DownloadClientTagHelpText')}
{...tags}
onChange={onInputChange}
/>
@ -152,15 +155,15 @@ class EditDownloadClientModalContent extends Component {
<FieldSet
size={sizes.SMALL}
legend="Completed Download Handling"
legend={translate('CompletedDownloadHandling')}
>
<FormGroup>
<FormLabel>Remove Completed</FormLabel>
<FormLabel>{translate('RemoveCompleted')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="removeCompletedDownloads"
helpText="Remove imported downloads from download client history (when finished seeding for torrents)"
helpText={translate('RemoveCompletedDownloadsHelpText')}
{...removeCompletedDownloads}
onChange={onInputChange}
/>
@ -169,12 +172,12 @@ class EditDownloadClientModalContent extends Component {
{
protocol.value !== 'torrent' &&
<FormGroup>
<FormLabel>Remove Failed</FormLabel>
<FormLabel>{translate('RemoveFailed')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="removeFailedDownloads"
helpText="Remove failed downloads from download client history"
helpText={translate('RemoveFailedDownloadsHelpText')}
{...removeFailedDownloads}
onChange={onInputChange}
/>
@ -192,7 +195,7 @@ class EditDownloadClientModalContent extends Component {
kind={kinds.DANGER}
onPress={onDeleteDownloadClientPress}
>
Delete
{translate('Delete')}
</Button>
}
@ -201,13 +204,13 @@ class EditDownloadClientModalContent extends Component {
error={saveError}
onPress={onTestPress}
>
Test
{translate('Test')}
</SpinnerErrorButton>
<Button
onPress={onModalClose}
>
Cancel
{translate('Cancel')}
</Button>
<SpinnerErrorButton
@ -215,7 +218,7 @@ class EditDownloadClientModalContent extends Component {
error={saveError}
onPress={onSavePress}
>
Save
{translate('Save')}
</SpinnerErrorButton>
</ModalFooter>
</ModalContent>

View File

@ -8,6 +8,7 @@ import FormInputGroup from 'Components/Form/FormInputGroup';
import FormLabel from 'Components/Form/FormLabel';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import { inputTypes, kinds, sizes } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
function DownloadClientOptions(props) {
const {
@ -28,13 +29,15 @@ function DownloadClientOptions(props) {
{
!isFetching && error &&
<Alert kind={kinds.DANGER}>Unable to load download client options</Alert>
<Alert kind={kinds.DANGER}>
{translate('DownloadClientOptionsLoadError')}
</Alert>
}
{
hasSettings && !isFetching && !error && advancedSettings &&
<div>
<FieldSet legend="Completed Download Handling">
<FieldSet legend={translate('CompletedDownloadHandling')}>
<Form>
<FormGroup
@ -42,12 +45,12 @@ function DownloadClientOptions(props) {
isAdvanced={true}
size={sizes.MEDIUM}
>
<FormLabel>Enable</FormLabel>
<FormLabel>{translate('Enable')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="enableCompletedDownloadHandling"
helpText="Automatically import completed downloads from download client"
helpText={translate('EnableCompletedDownloadHandlingHelpText')}
onChange={onInputChange}
{...settings.enableCompletedDownloadHandling}
/>
@ -58,12 +61,12 @@ function DownloadClientOptions(props) {
isAdvanced={true}
size={sizes.MEDIUM}
>
<FormLabel>Redownload Failed</FormLabel>
<FormLabel>{translate('RedownloadFailed')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="autoRedownloadFailed"
helpText="Automatically search for and attempt to download a different release"
helpText={translate('AutoRedownloadFailedHelpText')}
onChange={onInputChange}
{...settings.autoRedownloadFailed}
/>
@ -71,7 +74,7 @@ function DownloadClientOptions(props) {
</Form>
<Alert kind={kinds.INFO}>
The Remove settings were moved to the individual Download Client settings in the table above.
{translate('RemoveDownloadsAlert')}
</Alert>
</FieldSet>
</div>

View File

@ -13,6 +13,7 @@ import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { inputTypes, kinds } from 'Helpers/Props';
import { stringSettingShape } from 'Helpers/Props/Shapes/settingShape';
import translate from 'Utilities/String/translate';
import styles from './EditRemotePathMappingModalContent.css';
function EditRemotePathMappingModalContent(props) {
@ -40,7 +41,7 @@ function EditRemotePathMappingModalContent(props) {
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
{id ? 'Edit Remote Path Mapping' : 'Add Remote Path Mapping'}
{id ? translate('EditRemotePathMapping') : translate('AddRemotePathMapping')}
</ModalHeader>
<ModalBody className={styles.body}>
@ -51,19 +52,21 @@ function EditRemotePathMappingModalContent(props) {
{
!isFetching && !!error &&
<div>Unable to add a new remote path mapping, please try again.</div>
<div>
{translate('AddRemotePathMappingError')}
</div>
}
{
!isFetching && !error &&
<Form {...otherProps}>
<FormGroup>
<FormLabel>Host</FormLabel>
<FormLabel>{translate('Host')}</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
name="host"
helpText="The same host you specified for the remote Download Client"
helpText={translate('RemotePathMappingHostHelpText')}
{...host}
values={downloadClientHosts}
onChange={onInputChange}
@ -71,24 +74,24 @@ function EditRemotePathMappingModalContent(props) {
</FormGroup>
<FormGroup>
<FormLabel>Remote Path</FormLabel>
<FormLabel>{translate('RemotePath')}</FormLabel>
<FormInputGroup
type={inputTypes.TEXT}
name="remotePath"
helpText="Root path to the directory that the Download Client accesses"
helpText={translate('RemotePathMappingRemotePathHelpText')}
{...remotePath}
onChange={onInputChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>Local Path</FormLabel>
<FormLabel>{translate('LocalPath')}</FormLabel>
<FormInputGroup
type={inputTypes.PATH}
name="localPath"
helpText="Path that Sonarr should use to access the remote path locally"
helpText={translate('RemotePathMappingLocalPathHelpText')}
{...localPath}
onChange={onInputChange}
/>
@ -105,14 +108,14 @@ function EditRemotePathMappingModalContent(props) {
kind={kinds.DANGER}
onPress={onDeleteRemotePathMappingPress}
>
Delete
{translate('Delete')}
</Button>
}
<Button
onPress={onModalClose}
>
Cancel
{translate('Cancel')}
</Button>
<SpinnerErrorButton
@ -120,7 +123,7 @@ function EditRemotePathMappingModalContent(props) {
error={saveError}
onPress={onSavePress}
>
Save
{translate('Save')}
</SpinnerErrorButton>
</ModalFooter>
</ModalContent>

View File

@ -5,6 +5,7 @@ import Icon from 'Components/Icon';
import Link from 'Components/Link/Link';
import ConfirmModal from 'Components/Modal/ConfirmModal';
import { icons, kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import EditRemotePathMappingModalConnector from './EditRemotePathMappingModalConnector';
import styles from './RemotePathMapping.css';
@ -87,9 +88,9 @@ class RemotePathMapping extends Component {
<ConfirmModal
isOpen={this.state.isDeleteRemotePathMappingModalOpen}
kind={kinds.DANGER}
title="Delete Remote Path Mapping"
message="Are you sure you want to delete this remote path mapping?"
confirmLabel="Delete"
title={translate('DeleteRemotePathMapping')}
message={translate('DeleteRemotePathMappingMessageText')}
confirmLabel={translate('Delete')}
onConfirm={this.onConfirmDeleteRemotePathMapping}
onCancel={this.onDeleteRemotePathMappingModalClose}
/>

View File

@ -5,6 +5,7 @@ import Icon from 'Components/Icon';
import Link from 'Components/Link/Link';
import PageSectionContent from 'Components/Page/PageSectionContent';
import { icons } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import EditRemotePathMappingModalConnector from './EditRemotePathMappingModalConnector';
import RemotePathMapping from './RemotePathMapping';
import styles from './RemotePathMappings.css';
@ -44,15 +45,21 @@ class RemotePathMappings extends Component {
} = this.props;
return (
<FieldSet legend="Remote Path Mappings">
<FieldSet legend={translate('RemotePathMappings')} >
<PageSectionContent
errorMessage="Unable to load Remote Path Mappings"
errorMessage={translate('RemotePathMappingsLoadError')}
{...otherProps}
>
<div className={styles.remotePathMappingsHeader}>
<div className={styles.host}>Host</div>
<div className={styles.path}>Remote Path</div>
<div className={styles.path}>Local Path</div>
<div className={styles.host}>
{translate('Host')}
</div>
<div className={styles.path}>
{translate('RemotePath')}
</div>
<div className={styles.path}>
{translate('LocalPath')}
</div>
</div>
<div>

View File

@ -5,6 +5,7 @@ import FormGroup from 'Components/Form/FormGroup';
import FormInputGroup from 'Components/Form/FormInputGroup';
import FormLabel from 'Components/Form/FormLabel';
import { inputTypes, sizes } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
function AnalyticSettings(props) {
const {
@ -17,15 +18,15 @@ function AnalyticSettings(props) {
} = settings;
return (
<FieldSet legend="Analytics">
<FieldSet legend={translate('Analytics')}>
<FormGroup size={sizes.MEDIUM}>
<FormLabel>Send Anonymous Usage Data</FormLabel>
<FormLabel>{translate('SendAnonymousUsageData')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="analyticsEnabled"
helpText="Send anonymous usage and error information to Sonarr's servers. This includes information on your browser, which Sonarr WebUI pages you use, error reporting as well as OS and runtime version. We will use this information to prioritize features and bug fixes."
helpTextWarning="Requires restart to take effect"
helpText={translate('AnalyticsEnabledHelpText')}
helpTextWarning={translate('RestartRequiredHelpTextWarning')}
onChange={onInputChange}
{...analyticsEnabled}
/>

View File

@ -5,6 +5,7 @@ import FormGroup from 'Components/Form/FormGroup';
import FormInputGroup from 'Components/Form/FormInputGroup';
import FormLabel from 'Components/Form/FormLabel';
import { inputTypes } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
function BackupSettings(props) {
const {
@ -24,17 +25,17 @@ function BackupSettings(props) {
}
return (
<FieldSet legend="Backups">
<FieldSet legend={translate('Backups')}>
<FormGroup
advancedSettings={advancedSettings}
isAdvanced={true}
>
<FormLabel>Folder</FormLabel>
<FormLabel>{translate('Folder')}</FormLabel>
<FormInputGroup
type={inputTypes.PATH}
name="backupFolder"
helpText="Relative paths will be under Sonarr's AppData directory"
helpText={translate('BackupFolderHelpText')}
onChange={onInputChange}
{...backupFolder}
/>
@ -44,13 +45,13 @@ function BackupSettings(props) {
advancedSettings={advancedSettings}
isAdvanced={true}
>
<FormLabel>Interval</FormLabel>
<FormLabel>{translate('Interval')}</FormLabel>
<FormInputGroup
type={inputTypes.NUMBER}
name="backupInterval"
unit="days"
helpText="Interval between automatic backups"
helpText={translate('BackupIntervalHelpText')}
onChange={onInputChange}
{...backupInterval}
/>
@ -60,13 +61,13 @@ function BackupSettings(props) {
advancedSettings={advancedSettings}
isAdvanced={true}
>
<FormLabel>Retention</FormLabel>
<FormLabel>{translate('Retention')}</FormLabel>
<FormInputGroup
type={inputTypes.NUMBER}
name="backupRetention"
unit="days"
helpText="Automatic backups older than the retention period will be cleaned up automatically"
helpText={translate('BackupRetentionHelpText')}
onChange={onInputChange}
{...backupRetention}
/>

View File

@ -9,6 +9,7 @@ import PageContent from 'Components/Page/PageContent';
import PageContentBody from 'Components/Page/PageContentBody';
import { kinds } from 'Helpers/Props';
import SettingsToolbarConnector from 'Settings/SettingsToolbarConnector';
import translate from 'Utilities/String/translate';
import AnalyticSettings from './AnalyticSettings';
import BackupSettings from './BackupSettings';
import HostSettings from './HostSettings';
@ -111,7 +112,7 @@ class GeneralSettings extends Component {
} = this.props;
return (
<PageContent title="General Settings">
<PageContent title={translate('GeneralSettings')}>
<SettingsToolbarConnector
{...otherProps}
/>
@ -124,7 +125,9 @@ class GeneralSettings extends Component {
{
!isFetching && error &&
<Alert kind={kinds.DANGER}>Unable to load General settings</Alert>
<Alert kind={kinds.DANGER}>
{translate('GeneralSettingsLoadError')}
</Alert>
}
{
@ -183,12 +186,10 @@ class GeneralSettings extends Component {
<ConfirmModal
isOpen={this.state.isRestartRequiredModalOpen}
kind={kinds.DANGER}
title="Restart Sonarr"
message={
`Sonarr requires a restart to apply changes, do you want to restart now? ${isWindowsService ? 'Depending which user is running the Sonarr service you may need to restart Sonarr as admin once before the service will start automatically.' : ''}`
}
cancelLabel="I'll restart later"
confirmLabel="Restart Now"
title={translate('RestartSonarr')}
message={`${translate('RestartRequiredToApplyChanges')} ${isWindowsService ? translate('RestartRequiredWindowsService') : ''}`}
cancelLabel={translate('RestartLater')}
confirmLabel={translate('RestartNow')}
onConfirm={this.onConfirmRestart}
onCancel={this.onCloseRestartRequiredModalOpen}
/>

View File

@ -5,6 +5,7 @@ import FormGroup from 'Components/Form/FormGroup';
import FormInputGroup from 'Components/Form/FormInputGroup';
import FormLabel from 'Components/Form/FormLabel';
import { inputTypes, sizes } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
function HostSettings(props) {
const {
@ -29,25 +30,25 @@ function HostSettings(props) {
} = settings;
return (
<FieldSet legend="Host">
<FieldSet legend={translate('Host')}>
<FormGroup
advancedSettings={advancedSettings}
isAdvanced={true}
>
<FormLabel>Bind Address</FormLabel>
<FormLabel>{translate('BindAddress')}</FormLabel>
<FormInputGroup
type={inputTypes.TEXT}
name="bindAddress"
helpText="Valid IP address, localhost or '*' for all interfaces"
helpTextWarning="Requires restart to take effect"
helpText={translate('BindAddressHelpText')}
helpTextWarning={translate('RestartRequiredHelpTextWarning')}
onChange={onInputChange}
{...bindAddress}
/>
</FormGroup>
<FormGroup>
<FormLabel>Port Number</FormLabel>
<FormLabel>{translate('PortNumber')}</FormLabel>
<FormInputGroup
type={inputTypes.NUMBER}
@ -55,20 +56,20 @@ function HostSettings(props) {
min={1}
max={65535}
autocomplete="off"
helpTextWarning="Requires restart to take effect"
helpTextWarning={translate('RestartRequiredHelpTextWarning')}
onChange={onInputChange}
{...port}
/>
</FormGroup>
<FormGroup>
<FormLabel>URL Base</FormLabel>
<FormLabel>{translate('UrlBase')}</FormLabel>
<FormInputGroup
type={inputTypes.TEXT}
name="urlBase"
helpText="For reverse proxy support, default is empty"
helpTextWarning="Requires restart to take effect"
helpText={translate('UrlBaseHelpText')}
helpTextWarning={translate('RestartRequiredHelpTextWarning')}
onChange={onInputChange}
{...urlBase}
/>
@ -78,13 +79,13 @@ function HostSettings(props) {
advancedSettings={advancedSettings}
isAdvanced={true}
>
<FormLabel>Instance Name</FormLabel>
<FormLabel>{translate('InstanceName')}</FormLabel>
<FormInputGroup
type={inputTypes.TEXT}
name="instanceName"
helpText="Instance name in tab and for Syslog app name"
helpTextWarning="Requires restart to take effect"
helpText={translate('InstanceNameHelpText')}
helpTextWarning={translate('RestartRequiredHelpTextWarning')}
onChange={onInputChange}
{...instanceName}
/>
@ -94,12 +95,12 @@ function HostSettings(props) {
advancedSettings={advancedSettings}
isAdvanced={true}
>
<FormLabel>Application URL</FormLabel>
<FormLabel>{translate('ApplicationURL')}</FormLabel>
<FormInputGroup
type={inputTypes.TEXT}
name="applicationUrl"
helpText="This application's external URL including http(s)://, port and URL base"
helpText={translate('ApplicationUrlHelpText')}
onChange={onInputChange}
{...applicationUrl}
/>
@ -110,12 +111,12 @@ function HostSettings(props) {
isAdvanced={true}
size={sizes.MEDIUM}
>
<FormLabel>Enable SSL</FormLabel>
<FormLabel>{translate('EnableSsl')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="enableSsl"
helpText=" Requires restart running as administrator to take effect"
helpText={translate('EnableSslHelpText')}
onChange={onInputChange}
{...enableSsl}
/>
@ -127,14 +128,14 @@ function HostSettings(props) {
advancedSettings={advancedSettings}
isAdvanced={true}
>
<FormLabel>SSL Port</FormLabel>
<FormLabel>{translate('SslPort')}</FormLabel>
<FormInputGroup
type={inputTypes.NUMBER}
name="sslPort"
min={1}
max={65535}
helpTextWarning="Requires restart to take effect"
helpTextWarning={translate('RestartRequiredHelpTextWarning')}
onChange={onInputChange}
{...sslPort}
/>
@ -148,13 +149,13 @@ function HostSettings(props) {
advancedSettings={advancedSettings}
isAdvanced={true}
>
<FormLabel>SSL Cert Path</FormLabel>
<FormLabel>{translate('SslCertPath')}</FormLabel>
<FormInputGroup
type={inputTypes.TEXT}
name="sslCertPath"
helpText="Path to pfx file"
helpTextWarning="Requires restart to take effect"
helpText={translate('SslCertPathHelpText')}
helpTextWarning={translate('RestartRequiredHelpTextWarning')}
onChange={onInputChange}
{...sslCertPath}
/>
@ -168,13 +169,13 @@ function HostSettings(props) {
advancedSettings={advancedSettings}
isAdvanced={true}
>
<FormLabel>SSL Cert Password</FormLabel>
<FormLabel>{translate('SslCertPassword')}</FormLabel>
<FormInputGroup
type={inputTypes.PASSWORD}
name="sslCertPassword"
helpText="Password for pfx file"
helpTextWarning="Requires restart to take effect"
helpText={translate('SslCertPasswordHelpText')}
helpTextWarning={translate('RestartRequiredHelpTextWarning')}
onChange={onInputChange}
{...sslCertPassword}
/>
@ -185,12 +186,12 @@ function HostSettings(props) {
{
isWindows && mode !== 'service' ?
<FormGroup size={sizes.MEDIUM}>
<FormLabel>Open browser on start</FormLabel>
<FormLabel>{translate('OpenBrowserOnStart')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="launchBrowser"
helpText=" Open a web browser and navigate to Sonarr homepage on app start."
helpText={translate('OpenBrowserOnStartHelpText')}
onChange={onInputChange}
{...launchBrowser}
/>

View File

@ -5,11 +5,27 @@ import FormGroup from 'Components/Form/FormGroup';
import FormInputGroup from 'Components/Form/FormInputGroup';
import FormLabel from 'Components/Form/FormLabel';
import { inputTypes } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
const logLevelOptions = [
{ key: 'info', value: 'Info' },
{ key: 'debug', value: 'Debug' },
{ key: 'trace', value: 'Trace' }
{
key: 'info',
get value() {
return translate('Info');
}
},
{
key: 'debug',
get value() {
return translate('Debug');
}
},
{
key: 'trace',
get value() {
return translate('Trace');
}
}
];
function LoggingSettings(props) {
@ -23,15 +39,15 @@ function LoggingSettings(props) {
} = settings;
return (
<FieldSet legend="Logging">
<FieldSet legend={translate('Logging')}>
<FormGroup>
<FormLabel>Log Level</FormLabel>
<FormLabel>{translate('LogLevel')}</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
name="logLevel"
values={logLevelOptions}
helpTextWarning={logLevel.value === 'trace' ? 'Trace logging should only be enabled temporarily' : undefined}
helpTextWarning={logLevel.value === 'trace' ? translate('LogLevelTraceHelpTextWarning') : undefined}
onChange={onInputChange}
{...logLevel}
/>

View File

@ -5,6 +5,7 @@ import FormGroup from 'Components/Form/FormGroup';
import FormInputGroup from 'Components/Form/FormInputGroup';
import FormLabel from 'Components/Form/FormLabel';
import { inputTypes, sizes } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
function ProxySettings(props) {
const {
@ -24,15 +25,24 @@ function ProxySettings(props) {
} = settings;
const proxyTypeOptions = [
{ key: 'http', value: 'HTTP(S)' },
{ key: 'socks4', value: 'Socks4' },
{ key: 'socks5', value: 'Socks5 (Support TOR)' }
{
key: 'http',
value: translate('HttpHttps')
},
{
key: 'socks4',
value: translate('Socks4')
},
{
key: 'socks5',
value: translate('Socks5')
}
];
return (
<FieldSet legend="Proxy">
<FieldSet legend={translate('Proxy')}>
<FormGroup size={sizes.MEDIUM}>
<FormLabel>Use Proxy</FormLabel>
<FormLabel>{translate('UseProxy')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
@ -46,7 +56,7 @@ function ProxySettings(props) {
proxyEnabled.value &&
<div>
<FormGroup>
<FormLabel>Proxy Type</FormLabel>
<FormLabel>{translate('ProxyType')}</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
@ -58,7 +68,7 @@ function ProxySettings(props) {
</FormGroup>
<FormGroup>
<FormLabel>Hostname</FormLabel>
<FormLabel>{translate('Hostname')}</FormLabel>
<FormInputGroup
type={inputTypes.TEXT}
@ -70,7 +80,7 @@ function ProxySettings(props) {
</FormGroup>
<FormGroup>
<FormLabel>Port</FormLabel>
<FormLabel>{translate('Port')}</FormLabel>
<FormInputGroup
type={inputTypes.NUMBER}
@ -83,43 +93,43 @@ function ProxySettings(props) {
</FormGroup>
<FormGroup>
<FormLabel>Username</FormLabel>
<FormLabel>{translate('Username')}</FormLabel>
<FormInputGroup
type={inputTypes.TEXT}
name="proxyUsername"
helpText="You only need to enter a username and password if one is required. Leave them blank otherwise."
helpText={translate('ProxyUsernameHelpText')}
onChange={onInputChange}
{...proxyUsername}
/>
</FormGroup>
<FormGroup>
<FormLabel>Password</FormLabel>
<FormLabel>{translate('Password')}</FormLabel>
<FormInputGroup
type={inputTypes.PASSWORD}
name="proxyPassword"
helpText="You only need to enter a username and password if one is required. Leave them blank otherwise."
helpText={translate('ProxyPasswordHelpText')}
onChange={onInputChange}
{...proxyPassword}
/>
</FormGroup>
<FormGroup>
<FormLabel>Ignored Addresses</FormLabel>
<FormLabel>{translate('IgnoredAddresses')}</FormLabel>
<FormInputGroup
type={inputTypes.TEXT}
name="proxyBypassFilter"
helpText="Use ',' as a separator, and '*.' as a wildcard for subdomains"
helpText={translate('ProxyBypassFilterHelpText')}
onChange={onInputChange}
{...proxyBypassFilter}
/>
</FormGroup>
<FormGroup size={sizes.MEDIUM}>
<FormLabel>Bypass Proxy for Local Addresses</FormLabel>
<FormLabel>{translate('BypassProxyForLocalAddresses')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}

View File

@ -9,25 +9,71 @@ import Icon from 'Components/Icon';
import ClipboardButton from 'Components/Link/ClipboardButton';
import ConfirmModal from 'Components/Modal/ConfirmModal';
import { icons, inputTypes, kinds } from 'Helpers/Props';
export const authenticationRequiredWarning = 'To prevent remote access without authentication, Sonarr now requires authentication to be enabled. You can optionally disable authentication from local addresses.';
import translate from 'Utilities/String/translate';
export const authenticationMethodOptions = [
{ key: 'none', value: 'None', isDisabled: true },
{ key: 'external', value: 'External', isHidden: true },
{ key: 'basic', value: 'Basic (Browser Popup)' },
{ key: 'forms', value: 'Forms (Login Page)' }
{
key: 'none',
get value() {
return translate('None');
},
isDisabled: true
},
{
key: 'external',
get value() {
return translate('External');
},
isHidden: true
},
{
key: 'basic',
get value() {
return translate('AuthBasic');
}
},
{
key: 'forms',
get value() {
return translate('AuthForm');
}
}
];
export const authenticationRequiredOptions = [
{ key: 'enabled', value: 'Enabled' },
{ key: 'disabledForLocalAddresses', value: 'Disabled for Local Addresses' }
{
key: 'enabled',
get value() {
return translate('Enabled');
}
},
{
key: 'disabledForLocalAddresses',
get value() {
return translate('DisabledForLocalAddresses');
}
}
];
const certificateValidationOptions = [
{ key: 'enabled', value: 'Enabled' },
{ key: 'disabledForLocalAddresses', value: 'Disabled for Local Addresses' },
{ key: 'disabled', value: 'Disabled' }
{
key: 'enabled',
get value() {
return translate('Enabled');
}
},
{
key: 'disabledForLocalAddresses',
get value() {
return translate('DisabledForLocalAddresses');
}
},
{
key: 'disabled',
get value() {
return translate('Disabled');
}
}
];
class SecuritySettings extends Component {
@ -85,16 +131,16 @@ class SecuritySettings extends Component {
const authenticationEnabled = authenticationMethod && authenticationMethod.value !== 'none';
return (
<FieldSet legend="Security">
<FieldSet legend={translate('Security')}>
<FormGroup>
<FormLabel>Authentication</FormLabel>
<FormLabel>{translate('Authentication')}</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
name="authenticationMethod"
values={authenticationMethodOptions}
helpText="Require Username and Password to access Sonarr"
helpTextWarning={authenticationRequiredWarning}
helpText={translate('AuthenticationMethodHelpText')}
helpTextWarning={translate('AuthenticationRequiredWarning')}
onChange={onInputChange}
{...authenticationMethod}
/>
@ -103,13 +149,13 @@ class SecuritySettings extends Component {
{
authenticationEnabled ?
<FormGroup>
<FormLabel>Authentication Required</FormLabel>
<FormLabel>{translate('AuthenticationRequired')}</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
name="authenticationRequired"
values={authenticationRequiredOptions}
helpText="Change which requests authentication is required for. Do not change unless you understand the risks."
helpText={translate('AuthenticationRequiredHelpText')}
onChange={onInputChange}
{...authenticationRequired}
/>
@ -120,7 +166,7 @@ class SecuritySettings extends Component {
{
authenticationEnabled ?
<FormGroup>
<FormLabel>Username</FormLabel>
<FormLabel>{translate('Username')}</FormLabel>
<FormInputGroup
type={inputTypes.TEXT}
@ -135,7 +181,7 @@ class SecuritySettings extends Component {
{
authenticationEnabled ?
<FormGroup>
<FormLabel>Password</FormLabel>
<FormLabel>{translate('Password')}</FormLabel>
<FormInputGroup
type={inputTypes.PASSWORD}
@ -148,13 +194,13 @@ class SecuritySettings extends Component {
}
<FormGroup>
<FormLabel>API Key</FormLabel>
<FormLabel>{translate('ApiKey')}</FormLabel>
<FormInputGroup
type={inputTypes.TEXT}
name="apiKey"
readOnly={true}
helpTextWarning="Requires restart to take effect"
helpTextWarning={translate('RestartRequiredHelpTextWarning')}
buttons={[
<ClipboardButton
key="copy"
@ -180,13 +226,13 @@ class SecuritySettings extends Component {
</FormGroup>
<FormGroup>
<FormLabel>Certificate Validation</FormLabel>
<FormLabel>{translate('CertificateValidation')}</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
name="certificateValidation"
values={certificateValidationOptions}
helpText="Change how strict HTTPS certification validation is. Do not change unless you understand the risks."
helpText={translate('CertificateValidationHelpText')}
onChange={onInputChange}
{...certificateValidation}
/>
@ -195,9 +241,9 @@ class SecuritySettings extends Component {
<ConfirmModal
isOpen={this.state.isConfirmApiKeyResetModalOpen}
kind={kinds.DANGER}
title="Reset API Key"
message="Are you sure you want to reset your API Key?"
confirmLabel="Reset"
title={translate('ResetAPIKey')}
message={translate('ResetAPIKeyMessageText')}
confirmLabel={translate('Reset')}
onConfirm={this.onConfirmResetApiKey}
onCancel={this.onCloseResetApiKeyModal}
/>

View File

@ -6,6 +6,7 @@ import FormInputGroup from 'Components/Form/FormInputGroup';
import FormLabel from 'Components/Form/FormLabel';
import { inputTypes, sizes } from 'Helpers/Props';
import titleCase from 'Utilities/String/titleCase';
import translate from 'Utilities/String/translate';
const branchValues = [
'master',
@ -42,23 +43,23 @@ function UpdateSettings(props) {
value: titleCase(packageUpdateMechanism)
});
} else {
updateOptions.push({ key: 'builtIn', value: 'Built-In' });
updateOptions.push({ key: 'builtIn', value: translate('BuiltIn') });
}
updateOptions.push({ key: 'script', value: 'Script' });
updateOptions.push({ key: 'script', value: translate('Script') });
return (
<FieldSet legend="Updates">
<FieldSet legend={translate('Updates')}>
<FormGroup
advancedSettings={advancedSettings}
isAdvanced={true}
>
<FormLabel>Branch</FormLabel>
<FormLabel>{translate('Branch')}</FormLabel>
<FormInputGroup
type={inputTypes.AUTO_COMPLETE}
name="branch"
helpText={usingExternalUpdateMechanism ? 'Branch used by external update mechanism' : 'Branch to use to update Sonarr'}
helpText={usingExternalUpdateMechanism ? translate('BranchUpdateMechanism') : translate('BranchUpdate')}
helpLink="https://wiki.servarr.com/sonarr/settings#updates"
{...branch}
values={branchValues}
@ -76,12 +77,12 @@ function UpdateSettings(props) {
isAdvanced={true}
size={sizes.MEDIUM}
>
<FormLabel>Automatic</FormLabel>
<FormLabel>{translate('Automatic')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="updateAutomatically"
helpText="Automatically download and install updates. You will still be able to install from System: Updates"
helpText={translate('UpdateAutomaticallyHelpText')}
onChange={onInputChange}
{...updateAutomatically}
/>
@ -91,13 +92,13 @@ function UpdateSettings(props) {
advancedSettings={advancedSettings}
isAdvanced={true}
>
<FormLabel>Mechanism</FormLabel>
<FormLabel>{translate('Mechanism')}</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
name="updateMechanism"
values={updateOptions}
helpText="Use Sonarr's built-in updater or a script"
helpText={translate('UpdateMechanismHelpText')}
helpLink="https://wiki.servarr.com/sonarr/settings#updates"
onChange={onInputChange}
{...updateMechanism}
@ -110,12 +111,12 @@ function UpdateSettings(props) {
advancedSettings={advancedSettings}
isAdvanced={true}
>
<FormLabel>Script Path</FormLabel>
<FormLabel>{translate('ScriptPath')}</FormLabel>
<FormInputGroup
type={inputTypes.TEXT}
name="updateScriptPath"
helpText="Path to a custom script that takes an extracted update package and handle the remainder of the update process"
helpText={translate('UpdateScriptPathHelpText')}
onChange={onInputChange}
{...updateScriptPath}
/>

View File

@ -13,6 +13,7 @@ import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { inputTypes, kinds } from 'Helpers/Props';
import { numberSettingShape, stringSettingShape } from 'Helpers/Props/Shapes/settingShape';
import translate from 'Utilities/String/translate';
import styles from './EditImportListExclusionModalContent.css';
function EditImportListExclusionModalContent(props) {
@ -38,7 +39,7 @@ function EditImportListExclusionModalContent(props) {
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
{id ? 'Edit Import List Exclusion' : 'Add Import List Exclusion'}
{id ? translate('EditImportListExclusion') : translate('AddImportListExclusion')}
</ModalHeader>
<ModalBody className={styles.body}>
@ -49,7 +50,9 @@ function EditImportListExclusionModalContent(props) {
{
!isFetching && !!error &&
<div>Unable to add a new import list exclusion, please try again.</div>
<div>
{translate('AddImportListExclusionError')}
</div>
}
{
@ -58,24 +61,24 @@ function EditImportListExclusionModalContent(props) {
{...otherProps}
>
<FormGroup>
<FormLabel>Title</FormLabel>
<FormLabel>{translate('Title')}</FormLabel>
<FormInputGroup
type={inputTypes.TEXT}
name="title"
helpText="The name of the series to exclude"
helpText={translate('SeriesTitleToExcludeHelpText')}
{...title}
onChange={onInputChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>TVDB ID</FormLabel>
<FormLabel>{translate('TvdbId')}</FormLabel>
<FormInputGroup
type={inputTypes.TEXT}
name="tvdbId"
helpText="The TVDB ID of the series to exclude"
helpText={translate('TvdbIdExcludeHelpText')}
{...tvdbId}
onChange={onInputChange}
/>
@ -92,14 +95,14 @@ function EditImportListExclusionModalContent(props) {
kind={kinds.DANGER}
onPress={onDeleteImportListExclusionPress}
>
Delete
{translate('Delete')}
</Button>
}
<Button
onPress={onModalClose}
>
Cancel
{translate('Cancel')}
</Button>
<SpinnerErrorButton
@ -107,7 +110,7 @@ function EditImportListExclusionModalContent(props) {
error={saveError}
onPress={onSavePress}
>
Save
{translate('Save')}
</SpinnerErrorButton>
</ModalFooter>
</ModalContent>

View File

@ -5,6 +5,7 @@ import Icon from 'Components/Icon';
import Link from 'Components/Link/Link';
import ConfirmModal from 'Components/Modal/ConfirmModal';
import { icons, kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import EditImportListExclusionModalConnector from './EditImportListExclusionModalConnector';
import styles from './ImportListExclusion.css';
@ -85,9 +86,9 @@ class ImportListExclusion extends Component {
<ConfirmModal
isOpen={this.state.isDeleteImportListExclusionModalOpen}
kind={kinds.DANGER}
title="Delete Import List Exclusion"
message="Are you sure you want to delete this import list exclusion?"
confirmLabel="Delete"
title={translate('DeleteImportListExclusion')}
message={translate('DeleteImportListExclusionMessageText')}
confirmLabel={translate('Delete')}
onConfirm={this.onConfirmDeleteImportListExclusion}
onCancel={this.onDeleteImportListExclusionModalClose}
/>

View File

@ -5,6 +5,7 @@ import Icon from 'Components/Icon';
import Link from 'Components/Link/Link';
import PageSectionContent from 'Components/Page/PageSectionContent';
import { icons } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import EditImportListExclusionModalConnector from './EditImportListExclusionModalConnector';
import ImportListExclusion from './ImportListExclusion';
import styles from './ImportListExclusions.css';
@ -44,14 +45,18 @@ class ImportListExclusions extends Component {
} = this.props;
return (
<FieldSet legend="Import List Exclusions">
<FieldSet legend={translate('ImportListExclusions')}>
<PageSectionContent
errorMessage="Unable to load Import List Exclusions"
errorMessage={translate('ImportListExclusionsLoadError')}
{...otherProps}
>
<div className={styles.importListExclusionsHeader}>
<div className={styles.title}>Title</div>
<div className={styles.tvdbId}>TVDB ID</div>
<div className={styles.title}>
{translate('Title')}
</div>
<div className={styles.tvdbId}>
{translate('TvdbId')}
</div>
</div>
<div>

View File

@ -66,7 +66,7 @@ class ImportListSettings extends Component {
} = this.state;
return (
<PageContent title="Import List Settings">
<PageContent title={translate('ImportListSettings')}>
<SettingsToolbarConnector
isSaving={isSaving}
hasPendingChanges={hasPendingChanges}
@ -75,7 +75,7 @@ class ImportListSettings extends Component {
<PageToolbarSeparator />
<PageToolbarButton
label="Test All Lists"
label={translate('TestAllLists')}
iconName={icons.TEST}
isSpinning={isTestingAll}
onPress={dispatchTestAllImportLists}

View File

@ -5,6 +5,7 @@ import Link from 'Components/Link/Link';
import Menu from 'Components/Menu/Menu';
import MenuContent from 'Components/Menu/MenuContent';
import { sizes } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import AddImportListPresetMenuItem from './AddImportListPresetMenuItem';
import styles from './AddImportListItem.css';
@ -57,7 +58,7 @@ class AddImportListItem extends Component {
size={sizes.SMALL}
onPress={this.onListSelect}
>
Custom
{translate('Custom')}
</Button>
<Menu className={styles.presetsMenu}>
@ -65,7 +66,7 @@ class AddImportListItem extends Component {
className={styles.presetsMenuButton}
size={sizes.SMALL}
>
Presets
{translate('Presets')}
</Button>
<MenuContent>
@ -90,7 +91,7 @@ class AddImportListItem extends Component {
to={infoLink}
size={sizes.SMALL}
>
More info
{translate('MoreInfo')}
</Button>
</div>
</div>

View File

@ -10,6 +10,7 @@ import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { kinds } from 'Helpers/Props';
import titleCase from 'Utilities/String/titleCase';
import translate from 'Utilities/String/translate';
import AddImportListItem from './AddImportListItem';
import styles from './AddImportListModalContent.css';
@ -31,7 +32,7 @@ class AddImportListModalContent extends Component {
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
Add List
{translate('AddImportList')}
</ModalHeader>
<ModalBody>
@ -43,7 +44,9 @@ class AddImportListModalContent extends Component {
{
!isSchemaFetching && !!schemaError ?
<div>Unable to add a new list, please try again.</div> :
<div>
{translate('AddListError')}
</div> :
null
}
@ -52,13 +55,20 @@ class AddImportListModalContent extends Component {
<div>
<Alert kind={kinds.INFO}>
<div>Sonarr supports multiple lists for importing Series into the database.</div>
<div>For more information on the individual lists, click on the info buttons.</div>
<div>
{translate('SupportedLists')}
</div>
<div>
{translate('SupportedListsMoreInfo')}
</div>
</Alert>
{
Object.keys(listGroups).map((key) => {
return (
<FieldSet legend={`${titleCase(key)} List`} key={key}>
<FieldSet key={key} legend={translate('TypeOfList', {
typeOfList: titleCase(key)
})} // `${titleCase(key)} ${translate('List')}`}
>
<div className={styles.lists}>
{
listGroups[key].map((list) => {
@ -85,7 +95,7 @@ class AddImportListModalContent extends Component {
<Button
onPress={onModalClose}
>
Close
{translate('Close')}
</Button>
</ModalFooter>
</ModalContent>

View File

@ -19,6 +19,7 @@ import ModalHeader from 'Components/Modal/ModalHeader';
import Popover from 'Components/Tooltip/Popover';
import { icons, inputTypes, kinds, tooltipPositions } from 'Helpers/Props';
import formatShortTimeSpan from 'Utilities/Date/formatShortTimeSpan';
import translate from 'Utilities/String/translate';
import styles from './EditImportListModalContent.css';
function EditImportListModalContent(props) {
@ -57,7 +58,7 @@ function EditImportListModalContent(props) {
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
{id ? 'Edit List' : 'Add List'}
{id ? translate('EditImportList') : translate('AddImportList')}
</ModalHeader>
<ModalBody>
@ -69,7 +70,9 @@ function EditImportListModalContent(props) {
{
!isFetching && !!error ?
<div>Unable to add a new list, please try again.</div> :
<div>
{translate('AddListError')}
</div> :
null
}
@ -81,11 +84,13 @@ function EditImportListModalContent(props) {
kind={kinds.INFO}
className={styles.message}
>
{`List will refresh every ${formatShortTimeSpan(minRefreshInterval.value)}`}
{translate('ListWillRefreshEveryInterval', {
refreshInterval: formatShortTimeSpan(minRefreshInterval.value)
})}
</Alert>
<FormGroup>
<FormLabel>Name</FormLabel>
<FormLabel>{translate('Name')}</FormLabel>
<FormInputGroup
type={inputTypes.TEXT}
@ -96,12 +101,12 @@ function EditImportListModalContent(props) {
</FormGroup>
<FormGroup>
<FormLabel>Enable Automatic Add</FormLabel>
<FormLabel>{translate('EnableAutomaticAdd')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="enableAutomaticAdd"
helpText={'Add series to Sonarr when syncs are performed via the UI or by Sonarr'}
helpText={translate('EnableAutomaticAddHelpText')}
{...enableAutomaticAdd}
onChange={onInputChange}
/>
@ -109,7 +114,7 @@ function EditImportListModalContent(props) {
<FormGroup>
<FormLabel>
Monitor
{translate('Monitor')}
<Popover
anchor={
@ -118,7 +123,7 @@ function EditImportListModalContent(props) {
name={icons.INFO}
/>
}
title="Monitoring Options"
title={translate('MonitoringOptions')}
body={<SeriesMonitoringOptionsPopoverContent />}
position={tooltipPositions.RIGHT}
/>
@ -133,12 +138,12 @@ function EditImportListModalContent(props) {
</FormGroup>
<FormGroup>
<FormLabel>Root Folder</FormLabel>
<FormLabel>{translate('RootFolder')}</FormLabel>
<FormInputGroup
type={inputTypes.ROOT_FOLDER_SELECT}
name="rootFolderPath"
helpText={'Root Folder list items will be added to'}
helpText={translate('ListRootFolderHelpText')}
{...rootFolderPath}
includeMissingValue={true}
onChange={onInputChange}
@ -146,12 +151,12 @@ function EditImportListModalContent(props) {
</FormGroup>
<FormGroup>
<FormLabel>Quality Profile</FormLabel>
<FormLabel>{translate('QualityProfile')}</FormLabel>
<FormInputGroup
type={inputTypes.QUALITY_PROFILE_SELECT}
name="qualityProfileId"
helpText={'Quality Profile list items will be added with'}
helpText={translate('ListQualityProfileHelpText')}
{...qualityProfileId}
onChange={onInputChange}
/>
@ -159,7 +164,7 @@ function EditImportListModalContent(props) {
<FormGroup>
<FormLabel>
Series Type
{translate('SeriesType')}
<Popover
anchor={
@ -168,7 +173,7 @@ function EditImportListModalContent(props) {
name={icons.INFO}
/>
}
title="Series Types"
title={translate('SeriesTypes')}
body={<SeriesTypePopoverContent />}
position={tooltipPositions.RIGHT}
/>
@ -183,7 +188,7 @@ function EditImportListModalContent(props) {
</FormGroup>
<FormGroup>
<FormLabel>Season Folder</FormLabel>
<FormLabel>{translate('SeasonFolder')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
@ -194,12 +199,12 @@ function EditImportListModalContent(props) {
</FormGroup>
<FormGroup>
<FormLabel>Sonarr Tags</FormLabel>
<FormLabel>{translate('SonarrTags')}</FormLabel>
<FormInputGroup
type={inputTypes.TAG}
name="tags"
helpText="Tags list items will be added with"
helpText={translate('ListTagsHelpText')}
{...tags}
onChange={onInputChange}
/>
@ -238,7 +243,7 @@ function EditImportListModalContent(props) {
kind={kinds.DANGER}
onPress={onDeleteImportListPress}
>
Delete
{translate('Delete')}
</Button>
}
@ -247,13 +252,13 @@ function EditImportListModalContent(props) {
error={saveError}
onPress={onTestPress}
>
Test
{translate('Test')}
</SpinnerErrorButton>
<Button
onPress={onModalClose}
>
Cancel
{translate('Cancel')}
</Button>
<SpinnerErrorButton
@ -261,7 +266,7 @@ function EditImportListModalContent(props) {
error={saveError}
onPress={onSavePress}
>
Save
{translate('Save')}
</SpinnerErrorButton>
</ModalFooter>
</ModalContent>

View File

@ -5,6 +5,7 @@ import Label from 'Components/Label';
import ConfirmModal from 'Components/Modal/ConfirmModal';
import { kinds } from 'Helpers/Props';
import formatShortTimeSpan from 'Utilities/Date/formatShortTimeSpan';
import translate from 'Utilities/String/translate';
import EditImportListModalConnector from './EditImportListModalConnector';
import styles from './ImportList.css';
@ -73,7 +74,7 @@ class ImportList extends Component {
{
enableAutomaticAdd &&
<Label kind={kinds.SUCCESS}>
Automatic Add
{translate('AutomaticAdd')}
</Label>
}
@ -81,7 +82,7 @@ class ImportList extends Component {
<div className={styles.enabled}>
<Label kind={kinds.INFO} title='List Refresh Interval'>
{`Refresh: ${formatShortTimeSpan(minRefreshInterval)}`}
{`${translate('Refresh')}: ${formatShortTimeSpan(minRefreshInterval)}`}
</Label>
</div>
@ -95,9 +96,9 @@ class ImportList extends Component {
<ConfirmModal
isOpen={this.state.isDeleteImportListModalOpen}
kind={kinds.DANGER}
title="Delete Import List"
message={`Are you sure you want to delete the list '${name}'?`}
confirmLabel="Delete"
title={translate('DeleteImportList')}
message={translate('DeleteImportListMessageText', { name })}
confirmLabel={translate('Delete')}
onConfirm={this.onConfirmDeleteImportList}
onCancel={this.onDeleteImportListModalClose}
/>

View File

@ -6,6 +6,7 @@ import Icon from 'Components/Icon';
import PageSectionContent from 'Components/Page/PageSectionContent';
import { icons } from 'Helpers/Props';
import sortByName from 'Utilities/Array/sortByName';
import translate from 'Utilities/String/translate';
import AddImportListModal from './AddImportListModal';
import EditImportListModalConnector from './EditImportListModalConnector';
import ImportList from './ImportList';
@ -59,11 +60,9 @@ class ImportLists extends Component {
} = this.state;
return (
<FieldSet
legend="Import Lists"
>
<FieldSet legend={translate('ImportLists')} >
<PageSectionContent
errorMessage="Unable to load Lists"
errorMessage={translate('ImportListsLoadError')}
{...otherProps}
>
<div className={styles.lists}>

View File

@ -7,6 +7,7 @@ import TableRow from 'Components/Table/TableRow';
import TagListConnector from 'Components/TagListConnector';
import { createQualityProfileSelectorForHook } from 'Store/Selectors/createQualityProfileSelector';
import { SelectStateInputProps } from 'typings/props';
import translate from 'Utilities/String/translate';
import styles from './ManageImportListsModalRow.css';
interface ManageImportListsModalRowProps {
@ -63,7 +64,7 @@ function ManageImportListsModalRow(props: ManageImportListsModalRowProps) {
</TableRowCell>
<TableRowCell className={styles.qualityProfileId}>
{qualityProfile?.name ?? 'None'}
{qualityProfile?.name ?? translate('None')}
</TableRowCell>
<TableRowCell className={styles.rootFolderPath}>
@ -71,7 +72,7 @@ function ManageImportListsModalRow(props: ManageImportListsModalRowProps) {
</TableRowCell>
<TableRowCell className={styles.enableAutomaticAdd}>
{enableAutomaticAdd ? 'Yes' : 'No'}
{enableAutomaticAdd ? translate('Yes') : translate('No')}
</TableRowCell>
<TableRowCell className={styles.tags}>

View File

@ -69,7 +69,7 @@ class IndexerSettings extends Component {
} = this.state;
return (
<PageContent title="Indexer Settings">
<PageContent title={translate('IndexerSettings')}>
<SettingsToolbarConnector
isSaving={isSaving}
hasPendingChanges={hasPendingChanges}
@ -78,7 +78,7 @@ class IndexerSettings extends Component {
<PageToolbarSeparator />
<PageToolbarButton
label="Test All Indexers"
label={translate('TestAllIndexers')}
iconName={icons.TEST}
isSpinning={isTestingAll}
onPress={dispatchTestAllIndexers}

View File

@ -5,6 +5,7 @@ import Link from 'Components/Link/Link';
import Menu from 'Components/Menu/Menu';
import MenuContent from 'Components/Menu/MenuContent';
import { sizes } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import AddIndexerPresetMenuItem from './AddIndexerPresetMenuItem';
import styles from './AddIndexerItem.css';
@ -57,7 +58,7 @@ class AddIndexerItem extends Component {
size={sizes.SMALL}
onPress={this.onIndexerSelect}
>
Custom
{translate('Custom')}
</Button>
<Menu className={styles.presetsMenu}>
@ -65,7 +66,7 @@ class AddIndexerItem extends Component {
className={styles.presetsMenuButton}
size={sizes.SMALL}
>
Presets
{translate('Presets')}
</Button>
<MenuContent>
@ -90,7 +91,7 @@ class AddIndexerItem extends Component {
to={infoLink}
size={sizes.SMALL}
>
More info
{translate('MoreInfo')}
</Button>
</div>
</div>

View File

@ -9,6 +9,7 @@ import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import AddIndexerItem from './AddIndexerItem';
import styles from './AddIndexerModalContent.css';
@ -31,7 +32,7 @@ class AddIndexerModalContent extends Component {
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
Add Indexer
{translate('AddIndexer')}
</ModalHeader>
<ModalBody>
@ -42,7 +43,9 @@ class AddIndexerModalContent extends Component {
{
!isSchemaFetching && !!schemaError &&
<div>Unable to add a new indexer, please try again.</div>
<div>
{translate('AddIndexerError')}
</div>
}
{
@ -50,11 +53,15 @@ class AddIndexerModalContent extends Component {
<div>
<Alert kind={kinds.INFO}>
<div>Sonarr supports any indexer that uses the Newznab standard, as well as other indexers listed below.</div>
<div>For more information on the individual indexers, click on the info buttons.</div>
<div>
{translate('SupportedIndexers')}
</div>
<div>
{translate('SupportedIndexersMoreInfo')}
</div>
</Alert>
<FieldSet legend="Usenet">
<FieldSet legend={translate('Usenet')}>
<div className={styles.indexers}>
{
usenetIndexers.map((indexer) => {
@ -71,7 +78,7 @@ class AddIndexerModalContent extends Component {
</div>
</FieldSet>
<FieldSet legend="Torrents">
<FieldSet legend={translate('Torrents')}>
<div className={styles.indexers}>
{
torrentIndexers.map((indexer) => {
@ -94,7 +101,7 @@ class AddIndexerModalContent extends Component {
<Button
onPress={onModalClose}
>
Close
{translate('Close')}
</Button>
</ModalFooter>
</ModalContent>

View File

@ -14,6 +14,7 @@ import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { inputTypes, kinds } from 'Helpers/Props';
import AdvancedSettingsButton from 'Settings/AdvancedSettingsButton';
import translate from 'Utilities/String/translate';
import styles from './EditIndexerModalContent.css';
function EditIndexerModalContent(props) {
@ -55,7 +56,7 @@ function EditIndexerModalContent(props) {
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
{`${id ? 'Edit' : 'Add'} Indexer - ${implementationName}`}
{`${id ? translate('EditIndexer') : translate('AddIndexer')} - ${implementationName}`}
</ModalHeader>
<ModalBody>
@ -66,14 +67,16 @@ function EditIndexerModalContent(props) {
{
!isFetching && !!error &&
<div>Unable to add a new indexer, please try again.</div>
<div>
{translate('AddIndexerError')}
</div>
}
{
!isFetching && !error &&
<Form {...otherProps}>
<FormGroup>
<FormLabel>Name</FormLabel>
<FormLabel>{translate('Name')}</FormLabel>
<FormInputGroup
type={inputTypes.TEXT}
@ -84,13 +87,13 @@ function EditIndexerModalContent(props) {
</FormGroup>
<FormGroup>
<FormLabel>Enable RSS</FormLabel>
<FormLabel>{translate('EnableRss')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="enableRss"
helpText={supportsRss.value ? 'Will be used when Sonarr periodically looks for releases via RSS Sync' : undefined}
helpTextWarning={supportsRss.value ? undefined : 'RSS is not supported with this indexer'}
helpText={supportsRss.value ? translate('EnableRssHelpText') : undefined}
helpTextWarning={supportsRss.value ? undefined : translate('RssIsNotSupportedWithThisIndexer')}
isDisabled={!supportsRss.value}
{...enableRss}
onChange={onInputChange}
@ -98,13 +101,13 @@ function EditIndexerModalContent(props) {
</FormGroup>
<FormGroup>
<FormLabel>Enable Automatic Search</FormLabel>
<FormLabel>{translate('EnableAutomaticSearch')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="enableAutomaticSearch"
helpText={supportsSearch.value ? 'Will be used when automatic searches are performed via the UI or by Sonarr' : undefined}
helpTextWarning={supportsSearch.value ? undefined : 'Search is not supported with this indexer'}
helpText={supportsSearch.value ? translate('EnableAutomaticSearchHelpText') : undefined}
helpTextWarning={supportsSearch.value ? undefined : translate('SearchIsNotSupportedWithThisIndexer')}
isDisabled={!supportsSearch.value}
{...enableAutomaticSearch}
onChange={onInputChange}
@ -112,13 +115,13 @@ function EditIndexerModalContent(props) {
</FormGroup>
<FormGroup>
<FormLabel>Enable Interactive Search</FormLabel>
<FormLabel>{translate('EnableInteractiveSearch')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="enableInteractiveSearch"
helpText={supportsSearch.value ? 'Will be used when interactive search is used' : undefined}
helpTextWarning={supportsSearch.value ? undefined : 'Search is not supported with this indexer'}
helpText={supportsSearch.value ? translate('EnableInteractiveSearchHelpText') : undefined}
helpTextWarning={supportsSearch.value ? undefined : translate('SearchIsNotSupportedWithThisIndexer')}
isDisabled={!supportsSearch.value}
{...enableInteractiveSearch}
onChange={onInputChange}
@ -144,12 +147,12 @@ function EditIndexerModalContent(props) {
advancedSettings={advancedSettings}
isAdvanced={true}
>
<FormLabel>Indexer Priority</FormLabel>
<FormLabel>{translate('IndexerPriority')}</FormLabel>
<FormInputGroup
type={inputTypes.NUMBER}
name="priority"
helpText="Indexer Priority from 1 (Highest) to 50 (Lowest). Default: 25. Used when grabbing releases as a tiebreaker for otherwise equal releases, Sonarr will still use all enabled indexers for RSS Sync and Searching."
helpText={translate('IndexerPriorityHelpText')}
min={1}
max={50}
{...priority}
@ -161,12 +164,12 @@ function EditIndexerModalContent(props) {
advancedSettings={advancedSettings}
isAdvanced={true}
>
<FormLabel>Maximum Single Episode Age</FormLabel>
<FormLabel>{translate('MaximumSingleEpisodeAge')}</FormLabel>
<FormInputGroup
type={inputTypes.NUMBER}
name="seasonSearchMaximumSingleEpisodeAge"
helpText="During a full season search only season packs will be allowed when the season's last episode is older than this setting. Standard series only. Use 0 to disable."
helpText={translate('MaximumSingleEpisodeAgeHelpText')}
min={0}
unit="days"
{...seasonSearchMaximumSingleEpisodeAge}
@ -178,12 +181,12 @@ function EditIndexerModalContent(props) {
advancedSettings={advancedSettings}
isAdvanced={true}
>
<FormLabel>DownloadClient</FormLabel>
<FormLabel>{translate('DownloadClient')}</FormLabel>
<FormInputGroup
type={inputTypes.DOWNLOAD_CLIENT_SELECT}
name="downloadClientId"
helpText={'Specify which download client is used for grabs from this indexer'}
helpText={translate('IndexerDownloadClientHelpText')}
{...downloadClientId}
includeAny={true}
protocol={protocol.value}
@ -192,12 +195,12 @@ function EditIndexerModalContent(props) {
</FormGroup>
<FormGroup>
<FormLabel>Tags</FormLabel>
<FormLabel>{translate('Tags')}</FormLabel>
<FormInputGroup
type={inputTypes.TAG}
name="tags"
helpText="Only use this indexer for series with at least one matching tag. Leave blank to use with all series."
helpText={translate('IndexerTagHelpText')}
{...tags}
onChange={onInputChange}
/>
@ -213,7 +216,7 @@ function EditIndexerModalContent(props) {
kind={kinds.DANGER}
onPress={onDeleteIndexerPress}
>
Delete
{translate('Delete')}
</Button>
}
@ -228,13 +231,13 @@ function EditIndexerModalContent(props) {
error={saveError}
onPress={onTestPress}
>
Test
{translate('Test')}
</SpinnerErrorButton>
<Button
onPress={onModalClose}
>
Cancel
{translate('Cancel')}
</Button>
<SpinnerErrorButton
@ -242,7 +245,7 @@ function EditIndexerModalContent(props) {
error={saveError}
onPress={onSavePress}
>
Save
{translate('Save')}
</SpinnerErrorButton>
</ModalFooter>
</ModalContent>

View File

@ -6,6 +6,7 @@ import IconButton from 'Components/Link/IconButton';
import ConfirmModal from 'Components/Modal/ConfirmModal';
import TagList from 'Components/TagList';
import { icons, kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import EditIndexerModalConnector from './EditIndexerModalConnector';
import styles from './Indexer.css';
@ -89,7 +90,7 @@ class Indexer extends Component {
<IconButton
className={styles.cloneButton}
title="Clone Indexer"
title={translate('CloneIndexer')}
name={icons.CLONE}
onPress={this.onCloneIndexerPress}
/>
@ -100,28 +101,28 @@ class Indexer extends Component {
{
supportsRss && enableRss &&
<Label kind={kinds.SUCCESS}>
RSS
{translate('Rss')}
</Label>
}
{
supportsSearch && enableAutomaticSearch &&
<Label kind={kinds.SUCCESS}>
Automatic Search
{translate('AutomaticSearch')}
</Label>
}
{
supportsSearch && enableInteractiveSearch &&
<Label kind={kinds.SUCCESS}>
Interactive Search
{translate('InteractiveSearch')}
</Label>
}
{
showPriority &&
<Label kind={kinds.DEFAULT}>
Priority: {priority}
{translate('Priority')}: {priority}
</Label>
}
{
@ -130,7 +131,7 @@ class Indexer extends Component {
kind={kinds.DISABLED}
outline={true}
>
Disabled
{translate('Disabled')}
</Label>
}
</div>
@ -150,9 +151,9 @@ class Indexer extends Component {
<ConfirmModal
isOpen={this.state.isDeleteIndexerModalOpen}
kind={kinds.DANGER}
title="Delete Indexer"
message={`Are you sure you want to delete the indexer '${name}'?`}
confirmLabel="Delete"
title={translate('DeleteIndexer')}
message={translate('DeleteIndexerMessageText', { name })}
confirmLabel={translate('Delete')}
onConfirm={this.onConfirmDeleteIndexer}
onCancel={this.onDeleteIndexerModalClose}
/>

View File

@ -5,6 +5,7 @@ import FieldSet from 'Components/FieldSet';
import Icon from 'Components/Icon';
import PageSectionContent from 'Components/Page/PageSectionContent';
import { icons } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import AddIndexerModal from './AddIndexerModal';
import EditIndexerModalConnector from './EditIndexerModalConnector';
import Indexer from './Indexer';
@ -67,9 +68,9 @@ class Indexers extends Component {
const showPriority = items.some((index) => index.priority !== 25);
return (
<FieldSet legend="Indexers">
<FieldSet legend={translate('Indexers')}>
<PageSectionContent
errorMessage="Unable to load Indexers"
errorMessage={translate('IndexersLoadError')}
{...otherProps}
>
<div className={styles.indexers}>

View File

@ -127,7 +127,7 @@ function ManageIndexersEditModalContent(
<ModalBody>
<FormGroup>
<FormLabel>{translate('EnableRSS')}</FormLabel>
<FormLabel>{translate('EnableRss')}</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}

View File

@ -48,7 +48,7 @@ const COLUMNS = [
},
{
name: 'enableRss',
label: () => translate('EnableRSS'),
label: () => translate('EnableRss'),
isSortable: true,
isVisible: true,
},

View File

@ -8,6 +8,7 @@ import FormInputGroup from 'Components/Form/FormInputGroup';
import FormLabel from 'Components/Form/FormLabel';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import { inputTypes, kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
function IndexerOptions(props) {
const {
@ -20,7 +21,7 @@ function IndexerOptions(props) {
} = props;
return (
<FieldSet legend="Options">
<FieldSet legend={translate('Options')}>
{
isFetching &&
<LoadingIndicator />
@ -28,49 +29,51 @@ function IndexerOptions(props) {
{
!isFetching && error &&
<Alert kind={kinds.DANGER}>Unable to load indexer options</Alert>
<Alert kind={kinds.DANGER}>
{translate('IndexerOptionsLoadError')}
</Alert>
}
{
hasSettings && !isFetching && !error &&
<Form>
<FormGroup>
<FormLabel>Minimum Age</FormLabel>
<FormLabel>{translate('MinimumAge')}</FormLabel>
<FormInputGroup
type={inputTypes.NUMBER}
name="minimumAge"
min={0}
unit="minutes"
helpText="Usenet only: Minimum age in minutes of NZBs before they are grabbed. Use this to give new releases time to propagate to your usenet provider."
helpText={translate('MinimumAgeHelpText')}
onChange={onInputChange}
{...settings.minimumAge}
/>
</FormGroup>
<FormGroup>
<FormLabel>Retention</FormLabel>
<FormLabel>{translate('Retention')}</FormLabel>
<FormInputGroup
type={inputTypes.NUMBER}
name="retention"
min={0}
unit="days"
helpText="Usenet only: Set to zero to set for unlimited retention"
helpText={translate('RetentionHelpText')}
onChange={onInputChange}
{...settings.retention}
/>
</FormGroup>
<FormGroup>
<FormLabel>Maximum Size</FormLabel>
<FormLabel>{translate('MaximumSize')}</FormLabel>
<FormInputGroup
type={inputTypes.NUMBER}
name="maximumSize"
min={0}
unit="MB"
helpText="Maximum size for a release to be grabbed in MB. Set to zero to set to unlimited"
helpText={translate('MaximumSizeHelpText')}
onChange={onInputChange}
{...settings.maximumSize}
/>
@ -80,7 +83,7 @@ function IndexerOptions(props) {
advancedSettings={advancedSettings}
isAdvanced={true}
>
<FormLabel>RSS Sync Interval</FormLabel>
<FormLabel>{translate('RssSyncInterval')}</FormLabel>
<FormInputGroup
type={inputTypes.NUMBER}
@ -88,8 +91,8 @@ function IndexerOptions(props) {
min={0}
max={120}
unit="minutes"
helpText="Interval in minutes. Set to zero to disable (this will stop all automatic release grabbing)"
helpTextWarning="This will apply to all indexers, please follow the rules set forth by them"
helpText={translate('RssSyncIntervalHelpText')}
helpTextWarning={translate('RssSyncIntervalHelpTextWarning')}
helpLink="https://wiki.servarr.com/sonarr/faq#how-does-sonarr-find-episodes"
onChange={onInputChange}
{...settings.rssSyncInterval}

View File

@ -17,27 +17,87 @@ import NamingConnector from './Naming/NamingConnector';
import AddRootFolder from './RootFolder/AddRootFolder';
const episodeTitleRequiredOptions = [
{ key: 'always', value: 'Always' },
{ key: 'bulkSeasonReleases', value: 'Only for Bulk Season Releases' },
{ key: 'never', value: 'Never' }
{
key: 'always',
get value() {
return translate('Always');
}
},
{
key: 'bulkSeasonReleases',
get value() {
return translate('OnlyForBulkSeasonReleases');
}
},
{
key: 'never',
get value() {
return translate('Never');
}
}
];
const rescanAfterRefreshOptions = [
{ key: 'always', value: 'Always' },
{ key: 'afterManual', value: 'After Manual Refresh' },
{ key: 'never', value: 'Never' }
{
key: 'always',
get value() {
return translate('Always');
}
},
{
key: 'afterManual',
get value() {
return translate('AfterManualRefresh');
}
},
{
key: 'never',
get value() {
return translate('Never');
}
}
];
const downloadPropersAndRepacksOptions = [
{ key: 'preferAndUpgrade', value: 'Prefer and Upgrade' },
{ key: 'doNotUpgrade', value: 'Do not Upgrade Automatically' },
{ key: 'doNotPrefer', value: 'Do not Prefer' }
{
key: 'preferAndUpgrade',
get value() {
return translate('PreferAndUpgrade');
}
},
{
key: 'doNotUpgrade',
get value() {
return translate('DoNotUpgradeAutomatically');
}
},
{
key: 'doNotPrefer',
get value() {
return translate('DoNotPrefer');
}
}
];
const fileDateOptions = [
{ key: 'none', value: 'None' },
{ key: 'localAirDate', value: 'Local Air Date' },
{ key: 'utcAirDate', value: 'UTC Air Date' }
{
key: 'none',
get value() {
return translate('None');
}
},
{
key: 'localAirDate',
get value() {
return translate('LocalAirDate');
}
},
{
key: 'utcAirDate',
get value() {
return translate('UtcAirDate');
}
}
];
class MediaManagement extends Component {
@ -59,7 +119,7 @@ class MediaManagement extends Component {
} = this.props;
return (
<PageContent title="Media Management Settings">
<PageContent title={translate('MediaManagementSettings')}>
<SettingsToolbarConnector
advancedSettings={advancedSettings}
{...otherProps}
@ -71,15 +131,17 @@ class MediaManagement extends Component {
{
isFetching ?
<FieldSet legend="Naming Settings">
<FieldSet legend={translate('NamingSettings')}>
<LoadingIndicator />
</FieldSet> : null
}
{
!isFetching && error ?
<FieldSet legend="Naming Settings">
<Alert kind={kinds.DANGER}>Unable to load Media Management settings</Alert>
<FieldSet legend={translate('NamingSettings')}>
<Alert kind={kinds.DANGER}>
{translate('MediaManagementSettingsLoadError')}
</Alert>
</FieldSet> : null
}
@ -91,18 +153,18 @@ class MediaManagement extends Component {
>
{
advancedSettings ?
<FieldSet legend="Folders">
<FieldSet legend={translate('Folders')}>
<FormGroup
advancedSettings={advancedSettings}
isAdvanced={true}
size={sizes.MEDIUM}
>
<FormLabel>Create empty series folders</FormLabel>
<FormLabel>{translate('CreateEmptySeriesFolders')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="createEmptySeriesFolders"
helpText="Create missing series folders during disk scan"
helpText={translate('CreateEmptySeriesFoldersHelpText')}
onChange={onInputChange}
{...settings.createEmptySeriesFolders}
/>
@ -113,12 +175,12 @@ class MediaManagement extends Component {
isAdvanced={true}
size={sizes.MEDIUM}
>
<FormLabel>Delete empty folders</FormLabel>
<FormLabel>{translate('DeleteEmptyFolders')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="deleteEmptyFolders"
helpText="Delete empty series and season folders during disk scan and when episode files are deleted"
helpText={translate('DeleteEmptyFoldersHelpText')}
onChange={onInputChange}
{...settings.deleteEmptyFolders}
/>
@ -129,19 +191,19 @@ class MediaManagement extends Component {
{
advancedSettings ?
<FieldSet
legend="Importing"
legend={translate('Importing')}
>
<FormGroup
advancedSettings={advancedSettings}
isAdvanced={true}
size={sizes.SMALL}
>
<FormLabel>Episode Title Required</FormLabel>
<FormLabel>{translate('EpisodeTitleRequired')}</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
name="episodeTitleRequired"
helpText="Prevent importing for up to 48 hours if the episode title is in the naming format and the episode title is TBA"
helpText={translate('EpisodeTitleRequiredHelpText')}
values={episodeTitleRequiredOptions}
onChange={onInputChange}
{...settings.episodeTitleRequired}
@ -156,12 +218,12 @@ class MediaManagement extends Component {
isAdvanced={true}
size={sizes.MEDIUM}
>
<FormLabel>Skip Free Space Check</FormLabel>
<FormLabel>{translate('SkipFreeSpaceCheck')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="skipFreeSpaceCheckWhenImporting"
helpText="Use when Sonarr is unable to detect free space from your series root folder"
helpText={translate('SkipFreeSpaceCheckWhenImportingHelpText')}
onChange={onInputChange}
{...settings.skipFreeSpaceCheckWhenImporting}
/>
@ -173,13 +235,13 @@ class MediaManagement extends Component {
isAdvanced={true}
size={sizes.MEDIUM}
>
<FormLabel>Minimum Free Space</FormLabel>
<FormLabel>{translate('MinimumFreeSpace')}</FormLabel>
<FormInputGroup
type={inputTypes.NUMBER}
unit='MB'
name="minimumFreeSpaceWhenImporting"
helpText="Prevent import if it would leave less than this amount of disk space available"
helpText={translate('MinimumFreeSpaceHelpText')}
onChange={onInputChange}
{...settings.minimumFreeSpaceWhenImporting}
/>
@ -190,13 +252,13 @@ class MediaManagement extends Component {
isAdvanced={true}
size={sizes.MEDIUM}
>
<FormLabel>Use Hardlinks instead of Copy</FormLabel>
<FormLabel>{translate('UseHardlinksInsteadOfCopy')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="copyUsingHardlinks"
helpText="Hardlinks allow Sonarr to import seeding torrents to the the series folder without taking extra disk space or copying the entire contents of the file. Hardlinks will only work if the source and destination are on the same volume"
helpTextWarning="Occasionally, file locks may prevent renaming files that are being seeded. You may temporarily disable seeding and use Sonarr's rename function as a work around."
helpText={translate('CopyUsingHardlinksHelpText')}
helpTextWarning={translate('CopyUsingHardlinksHelpTextWarning')}
onChange={onInputChange}
{...settings.copyUsingHardlinks}
/>
@ -207,12 +269,12 @@ class MediaManagement extends Component {
isAdvanced={true}
size={sizes.MEDIUM}
>
<FormLabel>Import Using Script</FormLabel>
<FormLabel>{translate('ImportUsingScript')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="useScriptImport"
helpText="Copy files for importing using a script (ex. for transcoding)"
helpText={translate('ImportUsingScriptHelpText')}
onChange={onInputChange}
{...settings.useScriptImport}
/>
@ -224,13 +286,13 @@ class MediaManagement extends Component {
advancedSettings={advancedSettings}
isAdvanced={true}
>
<FormLabel>Import Script Path</FormLabel>
<FormLabel>{translate('ImportScriptPath')}</FormLabel>
<FormInputGroup
type={inputTypes.PATH}
includeFiles={true}
name="scriptImportPath"
helpText="The path to the script to use for importing"
helpText={translate('ImportScriptPathHelpText')}
onChange={onInputChange}
{...settings.scriptImportPath}
/>
@ -238,12 +300,12 @@ class MediaManagement extends Component {
}
<FormGroup size={sizes.MEDIUM}>
<FormLabel>Import Extra Files</FormLabel>
<FormLabel>{translate('ImportExtraFiles')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="importExtraFiles"
helpText="Import matching extra files (subtitles, nfo, etc) after importing an episode file"
helpText={translate('ImportExtraFilesHelpText')}
onChange={onInputChange}
{...settings.importExtraFiles}
/>
@ -255,14 +317,14 @@ class MediaManagement extends Component {
advancedSettings={advancedSettings}
isAdvanced={true}
>
<FormLabel>Import Extra Files</FormLabel>
<FormLabel>{translate('ImportExtraFiles')}</FormLabel>
<FormInputGroup
type={inputTypes.TEXT}
name="extraFileExtensions"
helpTexts={[
'Comma separated list of extra files to import (.nfo will be imported as .nfo-orig)',
'Examples: ".sub, .nfo" or "sub,nfo"'
translate('ExtraFileExtensionsHelpText'),
translate('ExtraFileExtensionsHelpTextsExamples')
]}
onChange={onInputChange}
{...settings.extraFileExtensions}
@ -273,15 +335,15 @@ class MediaManagement extends Component {
}
<FieldSet
legend="File Management"
legend={translate('FileManagement')}
>
<FormGroup size={sizes.MEDIUM}>
<FormLabel>Unmonitor Deleted Episodes</FormLabel>
<FormLabel>{translate('UnmonitorDeletedEpisodes')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="autoUnmonitorPreviouslyDownloadedEpisodes"
helpText="Episodes deleted from disk are automatically unmonitored in Sonarr"
helpText={translate('UnmonitorDeletedEpisodesHelpText')}
onChange={onInputChange}
{...settings.autoUnmonitorPreviouslyDownloadedEpisodes}
/>
@ -292,18 +354,18 @@ class MediaManagement extends Component {
isAdvanced={true}
size={sizes.MEDIUM}
>
<FormLabel>Propers and Repacks</FormLabel>
<FormLabel>{translate('DownloadPropersAndRepacks')}</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
name="downloadPropersAndRepacks"
helpTexts={[
'Whether or not to automatically upgrade to Propers/Repacks',
'Use \'Do not Prefer\' to sort by custom format score over propers/repacks'
translate('DownloadPropersAndRepacksHelpText'),
translate('DownloadPropersAndRepacksHelpTextCustomFormat')
]}
helpTextWarning={
settings.downloadPropersAndRepacks.value === 'doNotPrefer' ?
'Use custom formats for automatic upgrades to propers/repacks' :
translate('DownloadPropersAndRepacksHelpTextWarning') :
undefined
}
values={downloadPropersAndRepacksOptions}
@ -317,12 +379,12 @@ class MediaManagement extends Component {
isAdvanced={true}
size={sizes.MEDIUM}
>
<FormLabel>Analyse video files</FormLabel>
<FormLabel>{translate('AnalyseVideoFiles')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="enableMediaInfo"
helpText="Extract video information such as resolution, runtime and codec information from files. This requires Sonarr to read parts of the file which may cause high disk or network activity during scans."
helpText={translate('AnalyseVideoFilesHelpText')}
onChange={onInputChange}
{...settings.enableMediaInfo}
/>
@ -332,13 +394,13 @@ class MediaManagement extends Component {
advancedSettings={advancedSettings}
isAdvanced={true}
>
<FormLabel>Rescan Series Folder after Refresh</FormLabel>
<FormLabel>{translate('RescanSeriesFolderAfterRefresh')}</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
name="rescanAfterRefresh"
helpText="Rescan the series folder after refreshing the series"
helpTextWarning="Sonarr will not automatically detect changes to files when not set to 'Always'"
helpText={translate('RescanAfterRefreshHelpText')}
helpTextWarning={translate('RescanAfterRefreshHelpTextWarning')}
values={rescanAfterRefreshOptions}
onChange={onInputChange}
{...settings.rescanAfterRefresh}
@ -349,12 +411,12 @@ class MediaManagement extends Component {
advancedSettings={advancedSettings}
isAdvanced={true}
>
<FormLabel>Change File Date</FormLabel>
<FormLabel>{translate('ChangeFileDate')}</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
name="fileDate"
helpText="Change file date on import/rescan"
helpText={translate('ChangeFileDateHelpText')}
values={fileDateOptions}
onChange={onInputChange}
{...settings.fileDate}
@ -365,12 +427,12 @@ class MediaManagement extends Component {
advancedSettings={advancedSettings}
isAdvanced={true}
>
<FormLabel>Recycling Bin</FormLabel>
<FormLabel>{translate('RecyclingBin')}</FormLabel>
<FormInputGroup
type={inputTypes.PATH}
name="recycleBin"
helpText="Episode files will go here when deleted instead of being permanently deleted"
helpText={translate('RecyclingBinHelpText')}
onChange={onInputChange}
{...settings.recycleBin}
/>
@ -380,13 +442,13 @@ class MediaManagement extends Component {
advancedSettings={advancedSettings}
isAdvanced={true}
>
<FormLabel>Recycling Bin Cleanup</FormLabel>
<FormLabel>{translate('RecyclingBinCleanup')}</FormLabel>
<FormInputGroup
type={inputTypes.NUMBER}
name="recycleBinCleanupDays"
helpText="Set to 0 to disable automatic cleanup"
helpTextWarning="Files in the recycle bin older than the selected number of days will be cleaned up automatically"
helpText={translate('RecyclingBinCleanupHelpText')}
helpTextWarning={translate('RecyclingBinCleanupHelpTextWarning')}
min={0}
onChange={onInputChange}
{...settings.recycleBinCleanupDays}
@ -397,20 +459,20 @@ class MediaManagement extends Component {
{
advancedSettings && !isWindows ?
<FieldSet
legend="Permissions"
legend={translate('Permissions')}
>
<FormGroup
advancedSettings={advancedSettings}
isAdvanced={true}
size={sizes.MEDIUM}
>
<FormLabel>Set Permissions</FormLabel>
<FormLabel>{translate('SetPermissions')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="setPermissionsLinux"
helpText="Should chmod be run when files are imported/renamed?"
helpTextWarning="If you're unsure what these settings do, do not alter them."
helpText={translate('SetPermissionsLinuxHelpText')}
helpTextWarning={translate('SetPermissionsLinuxHelpTextWarning')}
onChange={onInputChange}
{...settings.setPermissionsLinux}
/>
@ -420,13 +482,13 @@ class MediaManagement extends Component {
advancedSettings={advancedSettings}
isAdvanced={true}
>
<FormLabel>chmod Folder</FormLabel>
<FormLabel>{translate('ChmodFolder')}</FormLabel>
<FormInputGroup
type={inputTypes.UMASK}
name="chmodFolder"
helpText="Octal, applied during import/rename to media folders and files (without execute bits)"
helpTextWarning="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."
helpText={translate('ChmodFolderHelpText')}
helpTextWarning={translate('ChmodFolderHelpTextWarning')}
onChange={onInputChange}
{...settings.chmodFolder}
/>
@ -436,13 +498,13 @@ class MediaManagement extends Component {
advancedSettings={advancedSettings}
isAdvanced={true}
>
<FormLabel>chown Group</FormLabel>
<FormLabel>{translate('ChownGroup')}</FormLabel>
<FormInputGroup
type={inputTypes.TEXT}
name="chownGroup"
helpText="Group name or gid. Use gid for remote file systems."
helpTextWarning="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."
helpText={translate('ChownGroupHelpText')}
helpTextWarning={translate('ChownGroupHelpTextWarning')}
values={fileDateOptions}
onChange={onInputChange}
{...settings.chownGroup}

View File

@ -9,6 +9,7 @@ import FormInputGroup from 'Components/Form/FormInputGroup';
import FormLabel from 'Components/Form/FormLabel';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import { inputTypes, kinds, sizes } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import NamingModal from './NamingModal';
import styles from './Naming.css';
@ -124,20 +125,20 @@ class Naming extends Component {
const replaceIllegalCharacters = hasSettings && settings.replaceIllegalCharacters.value;
const multiEpisodeStyleOptions = [
{ key: 0, value: 'Extend', hint: 'S01E01-02-03' },
{ key: 1, value: 'Duplicate', hint: 'S01E01.S01E02' },
{ key: 2, value: 'Repeat', hint: 'S01E01E02E03' },
{ key: 3, value: 'Scene', hint: 'S01E01-E02-E03' },
{ key: 4, value: 'Range', hint: 'S01E01-03' },
{ key: 5, value: 'Prefixed Range', hint: 'S01E01-E03' }
{ key: 0, value: translate('Extend'), hint: 'S01E01-02-03' },
{ key: 1, value: translate('Duplicate'), hint: 'S01E01.S01E02' },
{ key: 2, value: translate('Repeat'), hint: 'S01E01E02E03' },
{ key: 3, value: translate('Scene'), hint: 'S01E01-E02-E03' },
{ key: 4, value: translate('Range'), hint: 'S01E01-03' },
{ key: 5, value: translate('PrefixedRange'), hint: 'S01E01-E03' }
];
const colonReplacementOptions = [
{ key: 0, value: 'Delete' },
{ key: 1, value: 'Replace with Dash' },
{ key: 2, value: 'Replace with Space Dash' },
{ key: 3, value: 'Replace with Space Dash Space' },
{ key: 4, value: 'Smart Replace', hint: 'Dash or Space Dash depending on name' }
{ key: 0, value: translate('Delete') },
{ key: 1, value: translate('ReplaceWithDash') },
{ key: 2, value: translate('ReplaceWithSpaceDash') },
{ key: 3, value: translate('ReplaceWithSpaceDashSpace') },
{ key: 4, value: translate('SmartReplace'), hint: translate('SmartReplaceHint') }
];
const standardEpisodeFormatHelpTexts = [];
@ -155,56 +156,56 @@ class Naming extends Component {
if (examplesPopulated) {
if (examples.singleEpisodeExample) {
standardEpisodeFormatHelpTexts.push(`Single Episode: ${examples.singleEpisodeExample}`);
standardEpisodeFormatHelpTexts.push(`${translate('SingleEpisode')}: ${examples.singleEpisodeExample}`);
} else {
standardEpisodeFormatErrors.push({ message: 'Single Episode: Invalid Format' });
standardEpisodeFormatErrors.push({ message: translate('SingleEpisodeInvalidFormat') });
}
if (examples.multiEpisodeExample) {
standardEpisodeFormatHelpTexts.push(`Multi Episode: ${examples.multiEpisodeExample}`);
standardEpisodeFormatHelpTexts.push(`${translate('MultiEpisode')}: ${examples.multiEpisodeExample}`);
} else {
standardEpisodeFormatErrors.push({ message: 'Multi Episode: Invalid Format' });
standardEpisodeFormatErrors.push({ message: translate('MultiEpisodeInvalidFormat') });
}
if (examples.dailyEpisodeExample) {
dailyEpisodeFormatHelpTexts.push(`Example: ${examples.dailyEpisodeExample}`);
dailyEpisodeFormatHelpTexts.push(`${translate('Example')}: ${examples.dailyEpisodeExample}`);
} else {
dailyEpisodeFormatErrors.push({ message: 'Invalid Format' });
dailyEpisodeFormatErrors.push({ message: translate('InvalidFormat') });
}
if (examples.animeEpisodeExample) {
animeEpisodeFormatHelpTexts.push(`Single Episode: ${examples.animeEpisodeExample}`);
animeEpisodeFormatHelpTexts.push(`${translate('SingleEpisode')}: ${examples.animeEpisodeExample}`);
} else {
animeEpisodeFormatErrors.push({ message: 'Single Episode: Invalid Format' });
animeEpisodeFormatErrors.push({ message: translate('SingleEpisodeInvalidFormat') });
}
if (examples.animeMultiEpisodeExample) {
animeEpisodeFormatHelpTexts.push(`Multi Episode: ${examples.animeMultiEpisodeExample}`);
animeEpisodeFormatHelpTexts.push(`${translate('MultiEpisode')}: ${examples.animeMultiEpisodeExample}`);
} else {
animeEpisodeFormatErrors.push({ message: 'Multi Episode: Invalid Format' });
animeEpisodeFormatErrors.push({ message: translate('MultiEpisodeInvalidFormat') });
}
if (examples.seriesFolderExample) {
seriesFolderFormatHelpTexts.push(`Example: ${examples.seriesFolderExample}`);
seriesFolderFormatHelpTexts.push(`${translate('Example')}: ${examples.seriesFolderExample}`);
} else {
seriesFolderFormatErrors.push({ message: 'Invalid Format' });
seriesFolderFormatErrors.push({ message: translate('InvalidFormat') });
}
if (examples.seasonFolderExample) {
seasonFolderFormatHelpTexts.push(`Example: ${examples.seasonFolderExample}`);
seasonFolderFormatHelpTexts.push(`${translate('Example')}: ${examples.seasonFolderExample}`);
} else {
seasonFolderFormatErrors.push({ message: 'Invalid Format' });
seasonFolderFormatErrors.push({ message: translate('InvalidFormat') });
}
if (examples.specialsFolderExample) {
specialsFolderFormatHelpTexts.push(`Example: ${examples.specialsFolderExample}`);
specialsFolderFormatHelpTexts.push(`${translate('Example')}: ${examples.specialsFolderExample}`);
} else {
specialsFolderFormatErrors.push({ message: 'Invalid Format' });
specialsFolderFormatErrors.push({ message: translate('InvalidFormat') });
}
}
return (
<FieldSet legend="Episode Naming">
<FieldSet legend={translate('EpisodeNaming')}>
{
isFetching &&
<LoadingIndicator />
@ -212,31 +213,33 @@ class Naming extends Component {
{
!isFetching && error &&
<Alert kind={kinds.DANGER}>Unable to load Naming settings</Alert>
<Alert kind={kinds.DANGER}>
{translate('NamingSettingsLoadError')}
</Alert>
}
{
hasSettings && !isFetching && !error &&
<Form>
<FormGroup size={sizes.MEDIUM}>
<FormLabel>Rename Episodes</FormLabel>
<FormLabel>{translate('RenameEpisodes')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="renameEpisodes"
helpText="Sonarr will use the existing file name if renaming is disabled"
helpText={translate('RenameEpisodesHelpText')}
onChange={onInputChange}
{...settings.renameEpisodes}
/>
</FormGroup>
<FormGroup size={sizes.MEDIUM}>
<FormLabel>Replace Illegal Characters</FormLabel>
<FormLabel>{translate('ReplaceIllegalCharacters')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="replaceIllegalCharacters"
helpText="Replace illegal characters. If unchecked, Sonarr will remove them instead"
helpText={translate('ReplaceIllegalCharactersHelpText')}
onChange={onInputChange}
{...settings.replaceIllegalCharacters}
/>
@ -245,12 +248,13 @@ class Naming extends Component {
{
replaceIllegalCharacters ?
<FormGroup>
<FormLabel>Colon Replacement</FormLabel>
<FormLabel>{translate('ColonReplacement')}</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
name="colonReplacementFormat"
values={colonReplacementOptions}
helpText={translate('ColonReplacementFormatHelpText')}
onChange={onInputChange}
{...settings.colonReplacementFormat}
/>
@ -262,7 +266,7 @@ class Naming extends Component {
renameEpisodes &&
<div>
<FormGroup size={sizes.LARGE}>
<FormLabel>Standard Episode Format</FormLabel>
<FormLabel>{translate('StandardEpisodeFormat')}</FormLabel>
<FormInputGroup
inputClassName={styles.namingInput}
@ -277,7 +281,7 @@ class Naming extends Component {
</FormGroup>
<FormGroup size={sizes.LARGE}>
<FormLabel>Daily Episode Format</FormLabel>
<FormLabel>{translate('DailyEpisodeFormat')}</FormLabel>
<FormInputGroup
inputClassName={styles.namingInput}
@ -292,7 +296,7 @@ class Naming extends Component {
</FormGroup>
<FormGroup size={sizes.LARGE}>
<FormLabel>Anime Episode Format</FormLabel>
<FormLabel>{translate('DailyEpisodeFormat')}</FormLabel>
<FormInputGroup
inputClassName={styles.namingInput}
@ -312,7 +316,7 @@ class Naming extends Component {
advancedSettings={advancedSettings}
isAdvanced={true}
>
<FormLabel>Series Folder Format</FormLabel>
<FormLabel>{translate('SeriesFolderFormat')}</FormLabel>
<FormInputGroup
inputClassName={styles.namingInput}
@ -321,13 +325,13 @@ class Naming extends Component {
buttons={<FormInputButton onPress={this.onSeriesFolderNamingModalOpenClick}>?</FormInputButton>}
onChange={onInputChange}
{...settings.seriesFolderFormat}
helpTexts={['Used when adding a new series or moving series via the series editor', ...seriesFolderFormatHelpTexts]}
helpTexts={[translate('SeriesFolderFormatHelpText'), ...seriesFolderFormatHelpTexts]}
errors={[...seriesFolderFormatErrors, ...settings.seriesFolderFormat.errors]}
/>
</FormGroup>
<FormGroup>
<FormLabel>Season Folder Format</FormLabel>
<FormLabel>{translate('SeasonFolderFormat')}</FormLabel>
<FormInputGroup
inputClassName={styles.namingInput}
@ -345,7 +349,7 @@ class Naming extends Component {
advancedSettings={advancedSettings}
isAdvanced={true}
>
<FormLabel>Specials Folder Format</FormLabel>
<FormLabel>{translate('SpecialsFolderFormat')}</FormLabel>
<FormInputGroup
inputClassName={styles.namingInput}
@ -360,7 +364,7 @@ class Naming extends Component {
</FormGroup>
<FormGroup>
<FormLabel>Multi-Episode Style</FormLabel>
<FormLabel>{translate('MultiEpisodeStyle')}</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}

View File

@ -11,20 +11,57 @@ import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { icons, sizes } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import InlineMarkdown from '../../../Components/Markdown/InlineMarkdown';
import NamingOption from './NamingOption';
import styles from './NamingModal.css';
const separatorOptions = [
{ key: ' ', value: 'Space ( )' },
{ key: '.', value: 'Period (.)' },
{ key: '_', value: 'Underscore (_)' },
{ key: '-', value: 'Dash (-)' }
{
key: ' ',
get value() {
return `${translate('Space')} ( )`;
}
},
{
key: '.',
get value() {
return `${translate('Period')} (.)`;
}
},
{
key: '_',
get value() {
return `${translate('Underscore')} (_)`;
}
},
{
key: '-',
get value() {
return `${translate('Dash')} (-)`;
}
}
];
const caseOptions = [
{ key: 'title', value: 'Default Case' },
{ key: 'lower', value: 'Lowercase' },
{ key: 'upper', value: 'Uppercase' }
{
key: 'title',
get value() {
return translate('DefaultCase');
}
},
{
key: 'lower',
get value() {
return translate('Lowercase');
}
},
{
key: 'upper',
get value() {
return translate('Uppercase');
}
}
];
const fileNameTokens = [
@ -208,7 +245,7 @@ class NamingModal extends Component {
>
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
File Name Tokens
{translate('FileNameTokens')}
</ModalHeader>
<ModalBody>
@ -232,7 +269,7 @@ class NamingModal extends Component {
{
!advancedSettings &&
<FieldSet legend="File Names">
<FieldSet legend={translate('FileNames')}>
<div className={styles.groups}>
{
fileNameTokens.map(({ token, example }) => {
@ -257,7 +294,7 @@ class NamingModal extends Component {
</FieldSet>
}
<FieldSet legend="Series">
<FieldSet legend={translate('Series')}>
<div className={styles.groups}>
{
seriesTokens.map(({ token, example }) => {
@ -279,7 +316,7 @@ class NamingModal extends Component {
</div>
</FieldSet>
<FieldSet legend="Series ID">
<FieldSet legend={translate('SeriesID')}>
<div className={styles.groups}>
{
seriesIdTokens.map(({ token, example }) => {
@ -303,7 +340,7 @@ class NamingModal extends Component {
{
season &&
<FieldSet legend="Season">
<FieldSet legend={translate('Season')}>
<div className={styles.groups}>
{
seasonTokens.map(({ token, example }) => {
@ -329,7 +366,7 @@ class NamingModal extends Component {
{
episode &&
<div>
<FieldSet legend="Episode">
<FieldSet legend={translate('Episode')}>
<div className={styles.groups}>
{
episodeTokens.map(({ token, example }) => {
@ -351,7 +388,7 @@ class NamingModal extends Component {
</div>
</FieldSet>
<FieldSet legend="Air-Date">
<FieldSet legend={translate('AirDate')}>
<div className={styles.groups}>
{
airDateTokens.map(({ token, example }) => {
@ -375,7 +412,7 @@ class NamingModal extends Component {
{
anime &&
<FieldSet legend="Absolute Episode Number">
<FieldSet legend={translate('AbsoluteEpisodeNumber')}>
<div className={styles.groups}>
{
absoluteTokens.map(({ token, example }) => {
@ -403,7 +440,7 @@ class NamingModal extends Component {
{
additional &&
<div>
<FieldSet legend="Episode Title">
<FieldSet legend={translate('EpisodeTitle')}>
<div className={styles.groups}>
{
episodeTitleTokens.map(({ token, example }) => {
@ -425,7 +462,7 @@ class NamingModal extends Component {
</div>
</FieldSet>
<FieldSet legend="Quality">
<FieldSet legend={translate('Quality')}>
<div className={styles.groups}>
{
qualityTokens.map(({ token, example }) => {
@ -447,7 +484,7 @@ class NamingModal extends Component {
</div>
</FieldSet>
<FieldSet legend="Media Info">
<FieldSet legend={translate('MediaInfo')}>
<div className={styles.groups}>
{
mediaInfoTokens.map(({ token, example, footNote }) => {
@ -471,14 +508,11 @@ class NamingModal extends Component {
<div className={styles.footNote}>
<Icon className={styles.icon} name={icons.FOOTNOTE} />
<div>
MediaInfo Full/AudioLanguages/SubtitleLanguages support a <code>:EN+DE</code> suffix allowing you to filter the languages included in the filename. Use <code>-DE</code> to exclude specific languages.
Appending <code>+</code> (eg <code>:EN+</code>) will output <code>[EN]</code>/<code>[EN+--]</code>/<code>[--]</code> depending on excluded languages. For example <code>{'{'}MediaInfo Full:EN+DE{'}'}</code>.
</div>
<InlineMarkdown data={translate('MediaInfoFootNote')} />
</div>
</FieldSet>
<FieldSet legend="Other">
<FieldSet legend={translate('Other')}>
<div className={styles.groups}>
{
otherTokens.map(({ token, example }) => {
@ -500,7 +534,7 @@ class NamingModal extends Component {
</div>
</FieldSet>
<FieldSet legend="Original">
<FieldSet legend={translate('Original')}>
<div className={styles.groups}>
{
originalTokens.map(({ token, example }) => {
@ -534,7 +568,7 @@ class NamingModal extends Component {
onSelectionChange={this.onInputSelectionChange}
/>
<Button onPress={onModalClose}>
Close
{translate('Close')}
</Button>
</ModalFooter>
</ModalContent>

View File

@ -12,6 +12,7 @@ import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { inputTypes } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
function EditMetadataModalContent(props) {
const {
@ -35,18 +36,18 @@ function EditMetadataModalContent(props) {
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
Edit {name.value} Metadata
{translate('EditMetadata', { metadataType: name.value })}
</ModalHeader>
<ModalBody>
<Form {...otherProps}>
<FormGroup>
<FormLabel>Enable</FormLabel>
<FormLabel>{translate('Enable')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="enable"
helpText="Enable metadata file creation for this metadata type"
helpText={translate('EnableMetadataHelpText')}
{...enable}
onChange={onInputChange}
/>
@ -74,7 +75,7 @@ function EditMetadataModalContent(props) {
<Button
onPress={onModalClose}
>
Cancel
{translate('Cancel')}
</Button>
<SpinnerErrorButton
@ -82,7 +83,7 @@ function EditMetadataModalContent(props) {
error={saveError}
onPress={onSavePress}
>
Save
{translate('Save')}
</SpinnerErrorButton>
</ModalFooter>
</ModalContent>

View File

@ -3,6 +3,7 @@ import React, { Component } from 'react';
import Card from 'Components/Card';
import Label from 'Components/Label';
import { kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import EditMetadataModalConnector from './EditMetadataModalConnector';
import styles from './Metadata.css';
@ -66,13 +67,13 @@ class Metadata extends Component {
{
enable ?
<Label kind={kinds.SUCCESS}>
Enabled
{translate('Enabled')}
</Label> :
<Label
kind={kinds.DISABLED}
outline={true}
>
Disabled
{translate('Disabled')}
</Label>
}
</div>
@ -81,7 +82,7 @@ class Metadata extends Component {
enable && !!metadataFields.length &&
<div>
<div className={styles.section}>
Metadata
{translate('Metadata')}
</div>
{
@ -107,7 +108,7 @@ class Metadata extends Component {
enable && !!imageFields.length &&
<div>
<div className={styles.section}>
Images
{translate('Images')}
</div>
{

View File

@ -2,6 +2,7 @@ import PropTypes from 'prop-types';
import React from 'react';
import FieldSet from 'Components/FieldSet';
import PageSectionContent from 'Components/Page/PageSectionContent';
import translate from 'Utilities/String/translate';
import Metadata from './Metadata';
import styles from './Metadatas.css';
@ -12,9 +13,9 @@ function Metadatas(props) {
} = props;
return (
<FieldSet legend="Metadata">
<FieldSet legend={translate('Metadata')}>
<PageSectionContent
errorMessage="Unable to load Metadata"
errorMessage={translate('MetadataLoadError')}
{...otherProps}
>
<div className={styles.metadatas}>

View File

@ -2,11 +2,12 @@ import React from 'react';
import PageContent from 'Components/Page/PageContent';
import PageContentBody from 'Components/Page/PageContentBody';
import SettingsToolbarConnector from 'Settings/SettingsToolbarConnector';
import translate from 'Utilities/String/translate';
import MetadatasConnector from './Metadata/MetadatasConnector';
function MetadataSettings() {
return (
<PageContent title="Metadata Settings">
<PageContent title={translate('MetadataSettings')}>
<SettingsToolbarConnector
showSave={false}
/>

View File

@ -2,11 +2,12 @@ import React from 'react';
import PageContent from 'Components/Page/PageContent';
import PageContentBody from 'Components/Page/PageContentBody';
import SettingsToolbarConnector from 'Settings/SettingsToolbarConnector';
import translate from 'Utilities/String/translate';
import TheTvdb from './TheTvdb';
function MetadataSourceSettings() {
return (
<PageContent title="Metadata Source Settings">
<PageContent title={translate('MetadataSourceSettings')} >
<SettingsToolbarConnector
showSave={false}
/>

View File

@ -1,5 +1,6 @@
import React from 'react';
import Link from 'Components/Link/Link';
import translate from 'Utilities/String/translate';
import InlineMarkdown from '../../Components/Markdown/InlineMarkdown';
import styles from './TheTvdb.css';
function TheTvdb(props) {
@ -12,12 +13,10 @@ function TheTvdb(props) {
<div className={styles.info}>
<div className={styles.title}>
TheTVDB
{translate('TheTvdb')}
</div>
<div>
Series and episode information is provided by TheTVDB.com. <Link to="https://www.thetvdb.com/subscribe">Please consider supporting them.</Link>
</div>
<InlineMarkdown data={translate('SeriesAndEpisodeInformationIsProvidedByTheTVDB')} />
</div>
</div>

View File

@ -2,11 +2,12 @@ import React from 'react';
import PageContent from 'Components/Page/PageContent';
import PageContentBody from 'Components/Page/PageContentBody';
import SettingsToolbarConnector from 'Settings/SettingsToolbarConnector';
import translate from 'Utilities/String/translate';
import NotificationsConnector from './Notifications/NotificationsConnector';
function NotificationSettings() {
return (
<PageContent title="Connect Settings">
<PageContent title={translate('ConnectSettings')}>
<SettingsToolbarConnector
showSave={false}
/>

View File

@ -5,6 +5,7 @@ import Link from 'Components/Link/Link';
import Menu from 'Components/Menu/Menu';
import MenuContent from 'Components/Menu/MenuContent';
import { sizes } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import AddNotificationPresetMenuItem from './AddNotificationPresetMenuItem';
import styles from './AddNotificationItem.css';
@ -57,7 +58,7 @@ class AddNotificationItem extends Component {
size={sizes.SMALL}
onPress={this.onNotificationSelect}
>
Custom
{translate('Custom')}
</Button>
<Menu className={styles.presetsMenu}>
@ -65,7 +66,7 @@ class AddNotificationItem extends Component {
className={styles.presetsMenuButton}
size={sizes.SMALL}
>
Presets
{translate('Presets')}
</Button>
<MenuContent>
@ -90,7 +91,7 @@ class AddNotificationItem extends Component {
to={infoLink}
size={sizes.SMALL}
>
More Info
{translate('MoreInfo')}
</Button>
</div>
</div>

View File

@ -6,6 +6,7 @@ import ModalBody from 'Components/Modal/ModalBody';
import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import translate from 'Utilities/String/translate';
import AddNotificationItem from './AddNotificationItem';
import styles from './AddNotificationModalContent.css';
@ -27,7 +28,7 @@ class AddNotificationModalContent extends Component {
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
Add Notification
{translate('AddNotification')}
</ModalHeader>
<ModalBody>
@ -38,7 +39,9 @@ class AddNotificationModalContent extends Component {
{
!isSchemaFetching && !!schemaError &&
<div>Unable to add a new notification, please try again.</div>
<div>
{translate('AddNotificationError')}
</div>
}
{
@ -65,7 +68,7 @@ class AddNotificationModalContent extends Component {
<Button
onPress={onModalClose}
>
Close
{translate('Close')}
</Button>
</ModalFooter>
</ModalContent>

View File

@ -14,6 +14,7 @@ import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { inputTypes, kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import NotificationEventItems from './NotificationEventItems';
import styles from './EditNotificationModalContent.css';
@ -47,7 +48,7 @@ function EditNotificationModalContent(props) {
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
{`${id ? 'Edit' : 'Add'} Connection - ${implementationName}`}
{`${id ? translate('EditConnection') : translate('AddConnection')} - ${implementationName}`}
</ModalHeader>
<ModalBody>
@ -59,7 +60,7 @@ function EditNotificationModalContent(props) {
{
!isFetching && !!error &&
<div>
Unable to add a new notification, please try again.
{translate('AddNotificationError')}
</div>
}
@ -77,7 +78,7 @@ function EditNotificationModalContent(props) {
}
<FormGroup>
<FormLabel>Name</FormLabel>
<FormLabel>{translate('Name')}</FormLabel>
<FormInputGroup
type={inputTypes.TEXT}
@ -93,12 +94,12 @@ function EditNotificationModalContent(props) {
/>
<FormGroup>
<FormLabel>Tags</FormLabel>
<FormLabel>{translate('Tags')}</FormLabel>
<FormInputGroup
type={inputTypes.TAG}
name="tags"
helpText="Only send notifications for series with at least one matching tag"
helpText={translate('NotificationsTagsHelpText')}
{...tags}
onChange={onInputChange}
/>
@ -131,7 +132,7 @@ function EditNotificationModalContent(props) {
kind={kinds.DANGER}
onPress={onDeleteNotificationPress}
>
Delete
{translate('Delete')}
</Button>
}
@ -140,13 +141,13 @@ function EditNotificationModalContent(props) {
error={saveError}
onPress={onTestPress}
>
Test
{translate('Test')}
</SpinnerErrorButton>
<Button
onPress={onModalClose}
>
Cancel
{translate('Cancel')}
</Button>
<SpinnerErrorButton
@ -154,7 +155,7 @@ function EditNotificationModalContent(props) {
error={saveError}
onPress={onSavePress}
>
Save
{translate('Save')}
</SpinnerErrorButton>
</ModalFooter>
</ModalContent>

View File

@ -5,6 +5,7 @@ import Label from 'Components/Label';
import ConfirmModal from 'Components/Modal/ConfirmModal';
import TagList from 'Components/TagList';
import { kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import EditNotificationModalConnector from './EditNotificationModalConnector';
import styles from './Notification.css';
@ -96,7 +97,7 @@ class Notification extends Component {
{
supportsOnGrab && onGrab ?
<Label kind={kinds.SUCCESS}>
On Grab
{translate('OnGrab')}
</Label> :
null
}
@ -104,7 +105,7 @@ class Notification extends Component {
{
supportsOnDownload && onDownload ?
<Label kind={kinds.SUCCESS}>
On Import
{translate('OnImport')}
</Label> :
null
}
@ -112,7 +113,7 @@ class Notification extends Component {
{
supportsOnUpgrade && onDownload && onUpgrade ?
<Label kind={kinds.SUCCESS}>
On Upgrade
{translate('OnUpgrade')}
</Label> :
null
}
@ -120,7 +121,7 @@ class Notification extends Component {
{
supportsOnRename && onRename ?
<Label kind={kinds.SUCCESS}>
On Rename
{translate('OnRename')}
</Label> :
null
}
@ -128,7 +129,7 @@ class Notification extends Component {
{
supportsOnHealthIssue && onHealthIssue ?
<Label kind={kinds.SUCCESS}>
On Health Issue
{translate('OnHealthIssue')}
</Label> :
null
}
@ -136,7 +137,7 @@ class Notification extends Component {
{
supportsOnHealthRestored && onHealthRestored ?
<Label kind={kinds.SUCCESS}>
On Health Restored
{translate('OnHealthRestored')}
</Label> :
null
}
@ -144,7 +145,7 @@ class Notification extends Component {
{
supportsOnApplicationUpdate && onApplicationUpdate ?
<Label kind={kinds.SUCCESS}>
On Application Update
{translate('OnApplicationUpdate')}
</Label> :
null
}
@ -152,7 +153,7 @@ class Notification extends Component {
{
supportsOnSeriesAdd && onSeriesAdd ?
<Label kind={kinds.SUCCESS}>
On Series Add
{translate('OnSeriesAdd')}
</Label> :
null
}
@ -160,7 +161,7 @@ class Notification extends Component {
{
supportsOnSeriesDelete && onSeriesDelete ?
<Label kind={kinds.SUCCESS}>
On Series Delete
{translate('OnSeriesDelete')}
</Label> :
null
}
@ -168,7 +169,7 @@ class Notification extends Component {
{
supportsOnEpisodeFileDelete && onEpisodeFileDelete ?
<Label kind={kinds.SUCCESS}>
On Episode File Delete
{translate('OnEpisodeFileDelete')}
</Label> :
null
}
@ -176,7 +177,7 @@ class Notification extends Component {
{
supportsOnEpisodeFileDeleteForUpgrade && onEpisodeFileDelete && onEpisodeFileDeleteForUpgrade ?
<Label kind={kinds.SUCCESS}>
On Episode File Delete For Upgrade
{translate('OnEpisodeFileDeleteForUpgrade')}
</Label> :
null
}
@ -184,7 +185,7 @@ class Notification extends Component {
{
supportsOnManualInteractionRequired && onManualInteractionRequired ?
<Label kind={kinds.SUCCESS}>
On Manual Interaction Required
{translate('OnManualInteractionRequired')}
</Label> :
null
}
@ -195,7 +196,7 @@ class Notification extends Component {
kind={kinds.DISABLED}
outline={true}
>
Disabled
{translate('Disabled')}
</Label> :
null
}
@ -215,9 +216,9 @@ class Notification extends Component {
<ConfirmModal
isOpen={this.state.isDeleteNotificationModalOpen}
kind={kinds.DANGER}
title="Delete Notification"
message={`Are you sure you want to delete the notification '${name}'?`}
confirmLabel="Delete"
title={translate('DeleteNotification')}
message={translate('DeleteNotificationMessageText', { name })}
confirmLabel={translate('Delete')}
onConfirm={this.onConfirmDeleteNotification}
onCancel={this.onDeleteNotificationModalClose}
/>

View File

@ -5,6 +5,7 @@ import FormInputGroup from 'Components/Form/FormInputGroup';
import FormInputHelpText from 'Components/Form/FormInputHelpText';
import FormLabel from 'Components/Form/FormLabel';
import { inputTypes } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import styles from './NotificationEventItems.css';
function NotificationEventItems(props) {
@ -43,10 +44,10 @@ function NotificationEventItems(props) {
return (
<FormGroup>
<FormLabel>Notification Triggers</FormLabel>
<FormLabel>{translate('NotificationTriggers')}</FormLabel>
<div>
<FormInputHelpText
text="Select which events should trigger this notification"
text={translate('NotificationTriggersHelpText')}
link="https://wiki.servarr.com/sonarr/settings#connections"
/>
<div className={styles.events}>
@ -54,7 +55,7 @@ function NotificationEventItems(props) {
<FormInputGroup
type={inputTypes.CHECK}
name="onGrab"
helpText="On Grab"
helpText={translate('OnGrab')}
isDisabled={!supportsOnGrab.value}
{...onGrab}
onChange={onInputChange}
@ -65,7 +66,7 @@ function NotificationEventItems(props) {
<FormInputGroup
type={inputTypes.CHECK}
name="onDownload"
helpText="On Import"
helpText={translate('OnImport')}
isDisabled={!supportsOnDownload.value}
{...onDownload}
onChange={onInputChange}
@ -78,7 +79,7 @@ function NotificationEventItems(props) {
<FormInputGroup
type={inputTypes.CHECK}
name="onUpgrade"
helpText="On Upgrade"
helpText={translate('OnUpgrade')}
isDisabled={!supportsOnUpgrade.value}
{...onUpgrade}
onChange={onInputChange}
@ -90,7 +91,7 @@ function NotificationEventItems(props) {
<FormInputGroup
type={inputTypes.CHECK}
name="onRename"
helpText="On Rename"
helpText={translate('OnRename')}
isDisabled={!supportsOnRename.value}
{...onRename}
onChange={onInputChange}
@ -101,7 +102,7 @@ function NotificationEventItems(props) {
<FormInputGroup
type={inputTypes.CHECK}
name="onSeriesAdd"
helpText="On Series Add"
helpText={translate('OnSeriesAdd')}
isDisabled={!supportsOnSeriesAdd.value}
{...onSeriesAdd}
onChange={onInputChange}
@ -112,7 +113,7 @@ function NotificationEventItems(props) {
<FormInputGroup
type={inputTypes.CHECK}
name="onSeriesDelete"
helpText="On Series Delete"
helpText={translate('OnSeriesDelete')}
isDisabled={!supportsOnSeriesDelete.value}
{...onSeriesDelete}
onChange={onInputChange}
@ -123,7 +124,7 @@ function NotificationEventItems(props) {
<FormInputGroup
type={inputTypes.CHECK}
name="onEpisodeFileDelete"
helpText="On Episode File Delete"
helpText={translate('OnEpisodeFileDelete')}
isDisabled={!supportsOnEpisodeFileDelete.value}
{...onEpisodeFileDelete}
onChange={onInputChange}
@ -136,7 +137,7 @@ function NotificationEventItems(props) {
<FormInputGroup
type={inputTypes.CHECK}
name="onEpisodeFileDeleteForUpgrade"
helpText="On Episode File Delete For Upgrade"
helpText={translate('OnEpisodeFileDeleteForUpgrade')}
isDisabled={!supportsOnEpisodeFileDeleteForUpgrade.value}
{...onEpisodeFileDeleteForUpgrade}
onChange={onInputChange}
@ -148,7 +149,7 @@ function NotificationEventItems(props) {
<FormInputGroup
type={inputTypes.CHECK}
name="onHealthIssue"
helpText="On Health Issue"
helpText={translate('OnHealthIssue')}
isDisabled={!supportsOnHealthIssue.value}
{...onHealthIssue}
onChange={onInputChange}
@ -159,7 +160,7 @@ function NotificationEventItems(props) {
<FormInputGroup
type={inputTypes.CHECK}
name="onHealthRestored"
helpText="On Health Restored"
helpText={translate('OnHealthRestored')}
isDisabled={!supportsOnHealthRestored.value}
{...onHealthRestored}
onChange={onInputChange}
@ -172,7 +173,7 @@ function NotificationEventItems(props) {
<FormInputGroup
type={inputTypes.CHECK}
name="includeHealthWarnings"
helpText="Include Health Warnings"
helpText={translate('IncludeHealthWarnings')}
isDisabled={!supportsOnHealthIssue.value}
{...includeHealthWarnings}
onChange={onInputChange}
@ -184,7 +185,7 @@ function NotificationEventItems(props) {
<FormInputGroup
type={inputTypes.CHECK}
name="onApplicationUpdate"
helpText="On Application Update"
helpText={translate('OnApplicationUpdate')}
isDisabled={!supportsOnApplicationUpdate.value}
{...onApplicationUpdate}
onChange={onInputChange}
@ -195,7 +196,7 @@ function NotificationEventItems(props) {
<FormInputGroup
type={inputTypes.CHECK}
name="onManualInteractionRequired"
helpText="On Manual Interaction Required"
helpText={translate('OnManualInteractionRequired')}
isDisabled={!supportsOnManualInteractionRequired.value}
{...onManualInteractionRequired}
onChange={onInputChange}

View File

@ -5,6 +5,7 @@ import FieldSet from 'Components/FieldSet';
import Icon from 'Components/Icon';
import PageSectionContent from 'Components/Page/PageSectionContent';
import { icons } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import AddNotificationModal from './AddNotificationModal';
import EditNotificationModalConnector from './EditNotificationModalConnector';
import Notification from './Notification';
@ -59,9 +60,9 @@ class Notifications extends Component {
} = this.state;
return (
<FieldSet legend="Connections">
<FieldSet legend={translate('Connections')}>
<PageSectionContent
errorMessage="Unable to load Notifications"
errorMessage={translate('NotificationsLoadError')}
{...otherProps}
>
<div className={styles.notifications}>

View File

@ -8,6 +8,7 @@ import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
function PendingChangesModal(props) {
const {
@ -27,10 +28,10 @@ function PendingChangesModal(props) {
onModalClose={onCancel}
>
<ModalContent onModalClose={onCancel}>
<ModalHeader>Unsaved Changes</ModalHeader>
<ModalHeader>{translate('UnsavedChanges')}</ModalHeader>
<ModalBody>
You have unsaved changes, are you sure you want to leave this page?
{translate('PendingChangesMessage')}
</ModalBody>
<ModalFooter>
@ -38,7 +39,7 @@ function PendingChangesModal(props) {
kind={kinds.DEFAULT}
onPress={onCancel}
>
Stay and review changes
{translate('PendingChangesStayReview')}
</Button>
<Button
@ -46,7 +47,7 @@ function PendingChangesModal(props) {
kind={kinds.DANGER}
onPress={onConfirm}
>
Discard changes and leave
{translate('PendingChangesDiscardChanges')}
</Button>
</ModalFooter>
</ModalContent>

View File

@ -7,6 +7,7 @@ import ConfirmModal from 'Components/Modal/ConfirmModal';
import TagList from 'Components/TagList';
import { icons, kinds } from 'Helpers/Props';
import titleCase from 'Utilities/String/titleCase';
import translate from 'Utilities/String/translate';
import EditDelayProfileModalConnector from './EditDelayProfileModalConnector';
import styles from './DelayProfile.css';
@ -16,15 +17,15 @@ function getDelay(enabled, delay) {
}
if (!delay) {
return 'No Delay';
return translate('NoDelay');
}
if (delay === 1) {
return '1 Minute';
return translate('OneMinute');
}
// TODO: use better units of time than just minutes
return `${delay} Minutes`;
return translate('DelayMinutes', { delay });
}
class DelayProfile extends Component {
@ -84,12 +85,12 @@ class DelayProfile extends Component {
connectDragSource
} = this.props;
let preferred = `Prefer ${titleCase(preferredProtocol)}`;
let preferred = titleCase(translate('PreferProtocol', { preferredProtocol }));
if (!enableUsenet) {
preferred = 'Only Torrent';
preferred = translate('OnlyTorrent');
} else if (!enableTorrent) {
preferred = 'Only Usenet';
preferred = translate('OnlyUsenet');
}
return (
@ -139,9 +140,9 @@ class DelayProfile extends Component {
<ConfirmModal
isOpen={this.state.isDeleteDelayProfileModalOpen}
kind={kinds.DANGER}
title="Delete Delay Profile"
message="Are you sure you want to delete this delay profile?"
confirmLabel="Delete"
title={translate('DeleteDelayProfile')}
message={translate('DeleteDelayProfileMessageText')}
confirmLabel={translate('Delete')}
onConfirm={this.onConfirmDeleteDelayProfile}
onCancel={this.onDeleteDelayProfileModalClose}
/>

View File

@ -7,6 +7,7 @@ import Measure from 'Components/Measure';
import PageSectionContent from 'Components/Page/PageSectionContent';
import Scroller from 'Components/Scroller/Scroller';
import { icons, scrollDirections } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import DelayProfile from './DelayProfile';
import DelayProfileDragPreview from './DelayProfileDragPreview';
import DelayProfileDragSource from './DelayProfileDragSource';
@ -67,9 +68,9 @@ class DelayProfiles extends Component {
return (
<Measure onMeasure={this.onMeasure}>
<FieldSet legend="Delay Profiles">
<FieldSet legend={translate('DelayProfiles')}>
<PageSectionContent
errorMessage="Unable to load Delay Profiles"
errorMessage={translate('DelayProfilesLoadError')}
{...otherProps}
>
<Scroller
@ -81,10 +82,18 @@ class DelayProfiles extends Component {
>
<div>
<div className={styles.delayProfilesHeader}>
<div className={styles.column}>Preferred Protocol</div>
<div className={styles.column}>Usenet Delay</div>
<div className={styles.column}>Torrent Delay</div>
<div className={styles.tags}>Tags</div>
<div className={styles.column}>
{translate('PreferredProtocol')}
</div>
<div className={styles.column}>
{translate('UsenetDelay')}
</div>
<div className={styles.column}>
{translate('TorrentDelay')}
</div>
<div className={styles.tags}>
{translate('Tags')}
</div>
</div>
<div className={styles.delayProfiles}>

View File

@ -14,13 +14,34 @@ import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { inputTypes, kinds } from 'Helpers/Props';
import { boolSettingShape, numberSettingShape, tagSettingShape } from 'Helpers/Props/Shapes/settingShape';
import translate from 'Utilities/String/translate';
import styles from './EditDelayProfileModalContent.css';
const protocolOptions = [
{ key: 'preferUsenet', value: 'Prefer Usenet' },
{ key: 'preferTorrent', value: 'Prefer Torrent' },
{ key: 'onlyUsenet', value: 'Only Usenet' },
{ key: 'onlyTorrent', value: 'Only Torrent' }
{
key: 'preferUsenet',
get value() {
return translate('PreferUsenet');
}
},
{
key: 'preferTorrent',
get value() {
return translate('PreferTorrent');
}
},
{
key: 'onlyUsenet',
get value() {
return translate('OnlyUsenet');
}
},
{
key: 'onlyTorrent',
get value() {
return translate('OnlyTorrent');
}
}
];
function EditDelayProfileModalContent(props) {
@ -54,7 +75,7 @@ function EditDelayProfileModalContent(props) {
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
{id ? 'Edit Delay Profile' : 'Add Delay Profile'}
{id ? translate('EditDelayProfile') : translate('AddDelayProfile')}
</ModalHeader>
<ModalBody>
@ -66,7 +87,9 @@ function EditDelayProfileModalContent(props) {
{
!isFetching && !!error ?
<div>Unable to add a new quality profile, please try again.</div> :
<div>
{translate('AddQualityProfileError')}
</div> :
null
}
@ -74,14 +97,14 @@ function EditDelayProfileModalContent(props) {
!isFetching && !error ?
<Form {...otherProps}>
<FormGroup>
<FormLabel>Preferred Protocol</FormLabel>
<FormLabel>{translate('PreferredProtocol')}</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
name="protocol"
value={protocol}
values={protocolOptions}
helpText="Choose which protocol(s) to use and which one is preferred when choosing between otherwise equal releases"
helpText={translate('ProtocolHelpText')}
onChange={onProtocolChange}
/>
</FormGroup>
@ -89,14 +112,14 @@ function EditDelayProfileModalContent(props) {
{
enableUsenet.value ?
<FormGroup>
<FormLabel>Usenet Delay</FormLabel>
<FormLabel>{translate('UsenetDelay')}</FormLabel>
<FormInputGroup
type={inputTypes.NUMBER}
name="usenetDelay"
unit="minutes"
{...usenetDelay}
helpText="Delay in minutes to wait before grabbing a release from Usenet"
helpText={translate('UsenetDelayHelpText')}
onChange={onInputChange}
/>
</FormGroup> :
@ -106,14 +129,14 @@ function EditDelayProfileModalContent(props) {
{
enableTorrent.value ?
<FormGroup>
<FormLabel>Torrent Delay</FormLabel>
<FormLabel>{translate('TorrentDelay')}</FormLabel>
<FormInputGroup
type={inputTypes.NUMBER}
name="torrentDelay"
unit="minutes"
{...torrentDelay}
helpText="Delay in minutes to wait before grabbing a torrent"
helpText={translate('TorrentDelayHelpText')}
onChange={onInputChange}
/>
</FormGroup> :
@ -121,25 +144,25 @@ function EditDelayProfileModalContent(props) {
}
<FormGroup>
<FormLabel>Bypass if Highest Quality</FormLabel>
<FormLabel>{translate('BypassDelayIfHighestQuality')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="bypassIfHighestQuality"
{...bypassIfHighestQuality}
helpText="Bypass delay when release has the highest enabled quality in the quality profile with the preferred protocol"
helpText={translate('BypassDelayIfHighestQualityHelpText')}
onChange={onInputChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>Bypass if Above Custom Format Score</FormLabel>
<FormLabel>{translate('BypassDelayIfAboveCustomFormatScore')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="bypassIfAboveCustomFormatScore"
{...bypassIfAboveCustomFormatScore}
helpText="Enable bypass when release has a score higher than the configured minimum custom format score"
helpText={translate('BypassDelayIfAboveCustomFormatScoreHelpText')}
onChange={onInputChange}
/>
</FormGroup>
@ -147,13 +170,13 @@ function EditDelayProfileModalContent(props) {
{
bypassIfAboveCustomFormatScore.value ?
<FormGroup>
<FormLabel>Minimum Custom Format Score</FormLabel>
<FormLabel>{translate('BypassDelayIfAboveCustomFormatScoreMinimumScore')}</FormLabel>
<FormInputGroup
type={inputTypes.NUMBER}
name="minimumCustomFormatScore"
{...minimumCustomFormatScore}
helpText="Minimum Custom Format Score required to bypass delay for the preferred protocol"
helpText={translate('BypassDelayIfAboveCustomFormatScoreMinimumScoreHelpText')}
onChange={onInputChange}
/>
</FormGroup> :
@ -163,17 +186,17 @@ function EditDelayProfileModalContent(props) {
{
id === 1 ?
<Alert>
This is the default profile. It applies to all series that don't have an explicit profile.
{translate('DefaultDelayProfile')}
</Alert> :
<FormGroup>
<FormLabel>Tags</FormLabel>
<FormLabel>{translate('Tags')}</FormLabel>
<FormInputGroup
type={inputTypes.TAG}
name="tags"
{...tags}
helpText="Applies to series with at least one matching tag"
helpText={translate('DelayProfileTagsHelpText')}
onChange={onInputChange}
/>
</FormGroup>
@ -190,7 +213,7 @@ function EditDelayProfileModalContent(props) {
kind={kinds.DANGER}
onPress={onDeleteDelayProfilePress}
>
Delete
{translate('Delete')}
</Button> :
null
}
@ -198,7 +221,7 @@ function EditDelayProfileModalContent(props) {
<Button
onPress={onModalClose}
>
Cancel
{translate('Cancel')}
</Button>
<SpinnerErrorButton
@ -206,7 +229,7 @@ function EditDelayProfileModalContent(props) {
error={saveError}
onPress={onSavePress}
>
Save
{translate('Save')}
</SpinnerErrorButton>
</ModalFooter>
</ModalContent>

View File

@ -4,6 +4,7 @@ import HTML5toTouch from 'react-dnd-multi-backend/dist/esm/HTML5toTouch';
import PageContent from 'Components/Page/PageContent';
import PageContentBody from 'Components/Page/PageContentBody';
import SettingsToolbarConnector from 'Settings/SettingsToolbarConnector';
import translate from 'Utilities/String/translate';
import DelayProfilesConnector from './Delay/DelayProfilesConnector';
import QualityProfilesConnector from './Quality/QualityProfilesConnector';
import ReleaseProfilesConnector from './Release/ReleaseProfilesConnector';
@ -18,7 +19,7 @@ class Profiles extends Component {
render() {
return (
<PageContent title="Profiles">
<PageContent title={translate('Profiles')}>
<SettingsToolbarConnector showSave={false} />
<PageContentBody>

View File

@ -14,6 +14,7 @@ import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { inputTypes, kinds, sizes } from 'Helpers/Props';
import dimensions from 'Styles/Variables/dimensions';
import translate from 'Utilities/String/translate';
import QualityProfileFormatItems from './QualityProfileFormatItems';
import QualityProfileItems from './QualityProfileItems';
import styles from './EditQualityProfileModalContent.css';
@ -134,7 +135,7 @@ class EditQualityProfileModalContent extends Component {
onMeasure={this.onHeaderMeasure}
>
<ModalHeader>
{id ? 'Edit Quality Profile' : 'Add Quality Profile'}
{id ? translate('EditQualityProfile') : translate('AddQualityProfile')}
</ModalHeader>
</Measure>
@ -151,7 +152,9 @@ class EditQualityProfileModalContent extends Component {
{
!isFetching && !!error &&
<div>Unable to add a new quality profile, please try again.</div>
<div>
{translate('AddQualityProfileError')}
</div>
}
{
@ -163,7 +166,7 @@ class EditQualityProfileModalContent extends Component {
<div className={styles.formGroupWrapper}>
<FormGroup size={sizes.EXTRA_SMALL}>
<FormLabel size={sizes.SMALL}>
Name
{translate('Name')}
</FormLabel>
<FormInputGroup
@ -176,14 +179,14 @@ class EditQualityProfileModalContent extends Component {
<FormGroup size={sizes.EXTRA_SMALL}>
<FormLabel size={sizes.SMALL}>
Upgrades Allowed
{translate('UpgradesAllowed')}
</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="upgradeAllowed"
{...upgradeAllowed}
helpText="If disabled qualities will not be upgraded"
helpText={translate('UpgradesAllowedHelpText')}
onChange={onInputChange}
/>
</FormGroup>
@ -192,7 +195,7 @@ class EditQualityProfileModalContent extends Component {
upgradeAllowed.value &&
<FormGroup size={sizes.EXTRA_SMALL}>
<FormLabel size={sizes.SMALL}>
Upgrade Until
{translate('UpgradeUntil')}
</FormLabel>
<FormInputGroup
@ -200,7 +203,7 @@ class EditQualityProfileModalContent extends Component {
name="cutoff"
{...cutoff}
values={qualities}
helpText="Once this quality is reached Sonarr will no longer download episodes"
helpText={translate('UpgradeUntilHelpText')}
onChange={onCutoffChange}
/>
</FormGroup>
@ -210,14 +213,14 @@ class EditQualityProfileModalContent extends Component {
formatItems.value.length > 0 &&
<FormGroup size={sizes.EXTRA_SMALL}>
<FormLabel size={sizes.SMALL}>
Minimum Custom Format Score
{translate('MinimumCustomFormatScore')}
</FormLabel>
<FormInputGroup
type={inputTypes.NUMBER}
name="minFormatScore"
{...minFormatScore}
helpText="Minimum custom format score allowed to download"
helpText={translate('MinimumCustomFormatScoreHelpText')}
onChange={onInputChange}
/>
</FormGroup>
@ -227,14 +230,14 @@ class EditQualityProfileModalContent extends Component {
upgradeAllowed.value && formatItems.value.length > 0 &&
<FormGroup size={sizes.EXTRA_SMALL}>
<FormLabel size={sizes.SMALL}>
Upgrade Until Custom Format Score
{translate('UpgradeUntilCustomFormatScore')}
</FormLabel>
<FormInputGroup
type={inputTypes.NUMBER}
name="cutoffFormatScore"
{...cutoffFormatScore}
helpText="Once this custom format score is reached Sonarr will no longer grab episode releases"
helpText={translate('UpgradeUntilCustomFormatScoreHelpText')}
onChange={onInputChange}
/>
</FormGroup>
@ -278,7 +281,7 @@ class EditQualityProfileModalContent extends Component {
className={styles.deleteButtonContainer}
title={
isInUse ?
'Can\'t delete a quality profile that is attached to a series' :
translate('QualityProfileInUse') :
undefined
}
>
@ -287,7 +290,7 @@ class EditQualityProfileModalContent extends Component {
isDisabled={isInUse}
onPress={onDeleteQualityProfilePress}
>
Delete
{translate('Delete')}
</Button>
</div> :
null
@ -296,7 +299,7 @@ class EditQualityProfileModalContent extends Component {
<Button
onPress={onModalClose}
>
Cancel
{translate('Cancel')}
</Button>
<SpinnerErrorButton
@ -304,7 +307,7 @@ class EditQualityProfileModalContent extends Component {
error={saveError}
onPress={onSavePress}
>
Save
{translate('Save')}
</SpinnerErrorButton>
</ModalFooter>
</Measure>

View File

@ -6,6 +6,7 @@ import IconButton from 'Components/Link/IconButton';
import ConfirmModal from 'Components/Modal/ConfirmModal';
import Tooltip from 'Components/Tooltip/Tooltip';
import { icons, kinds, tooltipPositions } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import EditQualityProfileModalConnector from './EditQualityProfileModalConnector';
import styles from './QualityProfile.css';
@ -84,7 +85,7 @@ class QualityProfile extends Component {
<IconButton
className={styles.cloneButton}
title="Clone Profile"
title={translate('CloneProfile')}
name={icons.CLONE}
onPress={this.onCloneQualityProfilePress}
/>
@ -104,7 +105,7 @@ class QualityProfile extends Component {
<Label
key={item.quality.id}
kind={isCutoff ? kinds.INFO : kinds.DEFAULT}
title={isCutoff ? 'Upgrade until this quality is met or exceeded' : null}
title={isCutoff ? translate('UpgradeUntilThisQualityIsMetOrExceeded') : null}
>
{item.quality.name}
</Label>
@ -120,7 +121,7 @@ class QualityProfile extends Component {
anchor={
<Label
kind={isCutoff ? kinds.INFO : kinds.DEFAULT}
title={isCutoff ? 'Cutoff' : null}
title={isCutoff ? translate('Cutoff') : null}
>
{item.name}
</Label>
@ -133,7 +134,7 @@ class QualityProfile extends Component {
<Label
key={groupItem.quality.id}
kind={isCutoff ? kinds.INFO : kinds.DEFAULT}
title={isCutoff ? 'Cutoff' : null}
title={isCutoff ? translate('Cutoff') : null}
>
{groupItem.quality.name}
</Label>
@ -160,9 +161,9 @@ class QualityProfile extends Component {
<ConfirmModal
isOpen={this.state.isDeleteQualityProfileModalOpen}
kind={kinds.DANGER}
title="Delete Quality Profile"
message={`Are you sure you want to delete the quality profile '${name}'?`}
confirmLabel="Delete"
title={translate('DeleteQualityProfile')}
message={translate('DeleteQualityProfileMessageText', { name })}
confirmLabel={translate('Delete')}
isSpinning={isDeleting}
onConfirm={this.onConfirmDeleteQualityProfile}
onCancel={this.onDeleteQualityProfileModalClose}

View File

@ -4,8 +4,9 @@ import React, { Component } from 'react';
import FormGroup from 'Components/Form/FormGroup';
import FormInputHelpText from 'Components/Form/FormInputHelpText';
import FormLabel from 'Components/Form/FormLabel';
import Link from 'Components/Link/Link';
import { sizes } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import InlineMarkdown from '../../../Components/Markdown/InlineMarkdown';
import QualityProfileFormatItem from './QualityProfileFormatItem';
import styles from './QualityProfileFormatItems.css';
@ -66,22 +67,19 @@ class QualityProfileFormatItems extends Component {
if (profileFormatItems.length < 1) {
return (
<div className={styles.addCustomFormatMessage}>
{'Want more control over which downloads are preferred? Add a'}
<Link to='/settings/customformats'> Custom Format </Link>
</div>
<InlineMarkdown className={styles.addCustomFormatMessage} data={translate('WantMoreControlAddACustomFormat')} />
);
}
return (
<FormGroup size={sizes.EXTRA_SMALL}>
<FormLabel size={sizes.SMALL}>
Custom Formats
{translate('CustomFormats')}
</FormLabel>
<div>
<FormInputHelpText
text="Sonarr scores each release using the sum of scores for matching custom formats. If a new release would improve the score, at the same or better quality, then Sonarr will grab it."
text={translate('CustomFormatHelpText')}
/>
{
@ -113,10 +111,10 @@ class QualityProfileFormatItems extends Component {
<div className={styles.formats}>
<div className={styles.headerContainer}>
<div className={styles.headerTitle}>
Custom Format
{translate('CustomFormat')}
</div>
<div className={styles.headerScore}>
Score
{translate('Score')}
</div>
</div>
{

View File

@ -5,6 +5,7 @@ import CheckInput from 'Components/Form/CheckInput';
import Icon from 'Components/Icon';
import IconButton from 'Components/Link/IconButton';
import { icons } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import styles from './QualityProfileItem.css';
class QualityProfileItem extends Component {
@ -63,7 +64,7 @@ class QualityProfileItem extends Component {
<IconButton
className={styles.createGroupButton}
name={icons.GROUP}
title="Group"
title={translate('Group')}
onPress={this.onCreateGroupPress}
/>
}
@ -95,7 +96,7 @@ class QualityProfileItem extends Component {
<div className={styles.dragHandle}>
<Icon
className={styles.dragIcon}
title="Create group"
title={translate('CreateGroup')}
name={icons.REORDER}
/>
</div>

View File

@ -7,6 +7,7 @@ import Icon from 'Components/Icon';
import Label from 'Components/Label';
import IconButton from 'Components/Link/IconButton';
import { icons } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import QualityProfileItemDragSource from './QualityProfileItemDragSource';
import styles from './QualityProfileItemGroup.css';
@ -77,7 +78,7 @@ class QualityProfileItemGroup extends Component {
<IconButton
className={styles.deleteGroupButton}
name={icons.UNGROUP}
title="Ungroup"
title={translate('Ungroup')}
onPress={this.onDeleteGroupPress}
/>
@ -133,7 +134,7 @@ class QualityProfileItemGroup extends Component {
<Icon
className={styles.dragIcon}
name={icons.REORDER}
title="Reorder"
title={translate('Reorder')}
/>
</div>
)

View File

@ -7,6 +7,7 @@ import Icon from 'Components/Icon';
import Button from 'Components/Link/Button';
import Measure from 'Components/Measure';
import { icons, kinds, sizes } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import QualityProfileItemDragPreview from './QualityProfileItemDragPreview';
import QualityProfileItemDragSource from './QualityProfileItemDragSource';
import styles from './QualityProfileItems.css';
@ -69,12 +70,12 @@ class QualityProfileItems extends Component {
return (
<FormGroup size={sizes.EXTRA_SMALL}>
<FormLabel size={sizes.SMALL}>
Qualities
{translate('Qualities')}
</FormLabel>
<div>
<FormInputHelpText
text="Qualities higher in the list are more preferred. Qualities within the same group are equal. Only checked qualities are wanted"
text={translate('QualitiesHelpText')}
/>
{
@ -115,7 +116,7 @@ class QualityProfileItems extends Component {
/>
{
editGroups ? 'Done Editing Groups' : 'Edit Groups'
editGroups ? translate('DoneEditingGroups') : translate('EditGroups')
}
</div>
</Button>

View File

@ -5,6 +5,7 @@ import FieldSet from 'Components/FieldSet';
import Icon from 'Components/Icon';
import PageSectionContent from 'Components/Page/PageSectionContent';
import { icons } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import EditQualityProfileModalConnector from './EditQualityProfileModalConnector';
import QualityProfile from './QualityProfile';
import styles from './QualityProfiles.css';
@ -51,9 +52,9 @@ class QualityProfiles extends Component {
} = this.props;
return (
<FieldSet legend="Quality Profiles">
<FieldSet legend={translate('QualityProfiles')}>
<PageSectionContent
errorMessage="Unable to load Quality Profiles"
errorMessage={translate('QualityProfilesLoadError')}
{...otherProps}c={true}
>
<div className={styles.qualityProfiles}>

View File

@ -11,6 +11,7 @@ import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { inputTypes, kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import styles from './EditReleaseProfileModalContent.css';
const tagInputDelimiters = ['Tab', 'Enter'];
@ -40,7 +41,7 @@ function EditReleaseProfileModalContent(props) {
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
{id ? 'Edit Release Profile' : 'Add Release Profile'}
{id ? translate('EditReleaseProfile') : translate('AddReleaseProfile')}
</ModalHeader>
<ModalBody>
@ -53,35 +54,35 @@ function EditReleaseProfileModalContent(props) {
type={inputTypes.TEXT}
name="name"
{...name}
placeholder="Optional name"
placeholder={translate('OptionalName')}
canEdit={true}
onChange={onInputChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>Enable Profile</FormLabel>
<FormLabel>{translate('EnableProfile')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="enabled"
helpText="Check to enable release profile"
helpText={translate('EnableProfileHelpText')}
{...enabled}
onChange={onInputChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>Must Contain</FormLabel>
<FormLabel>{translate('MustContain')}</FormLabel>
<FormInputGroup
{...required}
inputClassName={styles.tagInternalInput}
type={inputTypes.TEXT_TAG}
name="required"
helpText="The release must contain at least one of these terms (case insensitive)"
helpText={translate('MustContainHelpText')}
kind={kinds.SUCCESS}
placeholder="Add new restriction"
placeholder={translate('AddNewRestriction')}
delimiters={tagInputDelimiters}
canEdit={true}
onChange={onInputChange}
@ -89,16 +90,16 @@ function EditReleaseProfileModalContent(props) {
</FormGroup>
<FormGroup>
<FormLabel>Must Not Contain</FormLabel>
<FormLabel>{translate('MustNotContain')}</FormLabel>
<FormInputGroup
{...ignored}
inputClassName={styles.tagInternalInput}
type={inputTypes.TEXT_TAG}
name="ignored"
helpText="The release will be rejected if it contains one or more of terms (case insensitive)"
helpText={translate('MustNotContainHelpText')}
kind={kinds.DANGER}
placeholder="Add new restriction"
placeholder={translate('AddNewRestriction')}
delimiters={tagInputDelimiters}
canEdit={true}
onChange={onInputChange}
@ -106,13 +107,13 @@ function EditReleaseProfileModalContent(props) {
</FormGroup>
<FormGroup>
<FormLabel>Indexer</FormLabel>
<FormLabel>{translate('Indexer')}</FormLabel>
<FormInputGroup
type={inputTypes.INDEXER_SELECT}
name="indexerId"
helpText="Specify what indexer the profile applies to"
helpTextWarning="Using a specific indexer with release profiles can lead to duplicate releases being grabbed"
helpText={translate('ReleaseProfileIndexerHelpText')}
helpTextWarning={translate('ReleaseProfileIndexerHelpTextWarning')}
{...indexerId}
includeAny={true}
onChange={onInputChange}
@ -120,12 +121,12 @@ function EditReleaseProfileModalContent(props) {
</FormGroup>
<FormGroup>
<FormLabel>Tags</FormLabel>
<FormLabel>{translate('Tags')}</FormLabel>
<FormInputGroup
type={inputTypes.TAG}
name="tags"
helpText="Release profiles will apply to series with at least one matching tag. Leave blank to apply to all series"
helpText={translate('ReleaseProfileTagHelpText')}
{...tags}
onChange={onInputChange}
/>
@ -140,14 +141,14 @@ function EditReleaseProfileModalContent(props) {
kind={kinds.DANGER}
onPress={onDeleteReleaseProfilePress}
>
Delete
{translate('Delete')}
</Button>
}
<Button
onPress={onModalClose}
>
Cancel
{translate('Cancel')}
</Button>
<SpinnerErrorButton
@ -155,7 +156,7 @@ function EditReleaseProfileModalContent(props) {
error={saveError}
onPress={onSavePress}
>
Save
{translate('Save')}
</SpinnerErrorButton>
</ModalFooter>
</ModalContent>

View File

@ -7,6 +7,7 @@ import Label from 'Components/Label';
import ConfirmModal from 'Components/Modal/ConfirmModal';
import TagList from 'Components/TagList';
import { kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import EditReleaseProfileModalConnector from './EditReleaseProfileModalConnector';
import styles from './ReleaseProfile.css';
@ -147,7 +148,7 @@ class ReleaseProfile extends Component {
kind={kinds.DISABLED}
outline={true}
>
Disabled
{translate('Disabled')}
</Label>
}
@ -172,9 +173,9 @@ class ReleaseProfile extends Component {
<ConfirmModal
isOpen={isDeleteReleaseProfileModalOpen}
kind={kinds.DANGER}
title="Delete ReleaseProfile"
message={'Are you sure you want to delete this releaseProfile?'}
confirmLabel="Delete"
title={translate('DeleteReleaseProfile')}
message={translate('DeleteReleaseProfileMessageText', { name })}
confirmLabel={translate('Delete')}
onConfirm={this.onConfirmDeleteReleaseProfile}
onCancel={this.onDeleteReleaseProfileModalClose}
/>

View File

@ -5,6 +5,7 @@ import FieldSet from 'Components/FieldSet';
import Icon from 'Components/Icon';
import PageSectionContent from 'Components/Page/PageSectionContent';
import { icons } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import EditReleaseProfileModalConnector from './EditReleaseProfileModalConnector';
import ReleaseProfile from './ReleaseProfile';
import styles from './ReleaseProfiles.css';
@ -46,9 +47,9 @@ class ReleaseProfiles extends Component {
} = this.props;
return (
<FieldSet legend="Release Profiles">
<FieldSet legend={translate('Release Profiles')}>
<PageSectionContent
errorMessage="Unable to load ReleaseProfiles"
errorMessage={translate('ReleaseProfilesLoadError')}
{...otherProps}
>
<div className={styles.releaseProfiles}>

View File

@ -8,6 +8,7 @@ import Popover from 'Components/Tooltip/Popover';
import { kinds, tooltipPositions } from 'Helpers/Props';
import formatBytes from 'Utilities/Number/formatBytes';
import roundNumber from 'Utilities/Number/roundNumber';
import translate from 'Utilities/String/translate';
import QualityDefinitionLimits from './QualityDefinitionLimits';
import styles from './QualityDefinition.css';
@ -186,13 +187,13 @@ class QualityDefinition extends Component {
} = this.state;
const minBytes = minSize * 1024 * 1024;
const minSixty = `${formatBytes(minBytes * 60)}/h`;
const minSixty = `${formatBytes(minBytes * 60)}/${translate('HourShorthand')}`;
const preferredBytes = preferredSize * 1024 * 1024;
const preferredSixty = preferredBytes ? `${formatBytes(preferredBytes * 60)}/h` : 'Unlimited';
const preferredSixty = preferredBytes ? `${formatBytes(preferredBytes * 60)}/${translate('HourShorthand')}` : translate('Unlimited');
const maxBytes = maxSize && maxSize * 1024 * 1024;
const maxSixty = maxBytes ? `${formatBytes(maxBytes * 60)}/h` : 'Unlimited';
const maxSixty = maxBytes ? `${formatBytes(maxBytes * 60)}/${translate('HourShorthand')}` : translate('Unlimited');
return (
<div className={styles.qualityDefinition}>
@ -231,11 +232,11 @@ class QualityDefinition extends Component {
anchor={
<Label kind={kinds.INFO}>{minSixty}</Label>
}
title="Minimum Limits"
title={translate('MinimumLimits')}
body={
<QualityDefinitionLimits
bytes={minBytes}
message="No minimum for any runtime"
message={translate('NoMinimumForAnyRuntime')}
/>
}
position={tooltipPositions.BOTTOM}
@ -247,11 +248,11 @@ class QualityDefinition extends Component {
anchor={
<Label kind={kinds.SUCCESS}>{preferredSixty}</Label>
}
title="Preferred Size"
title={translate('PreferredSize')}
body={
<QualityDefinitionLimits
bytes={preferredBytes}
message="No limit for any runtime"
message={translate('NoLimitForAnyRuntime')}
/>
}
position={tooltipPositions.BOTTOM}
@ -263,11 +264,11 @@ class QualityDefinition extends Component {
anchor={
<Label kind={kinds.WARNING}>{maxSixty}</Label>
}
title="Maximum Limits"
title={translate('MaximumLimits')}
body={
<QualityDefinitionLimits
bytes={maxBytes}
message="No limit for any runtime"
message={translate('NoLimitForAnyRuntime')}
/>
}
position={tooltipPositions.BOTTOM}
@ -280,7 +281,7 @@ class QualityDefinition extends Component {
advancedSettings &&
<div className={styles.megabytesPerMinute}>
<div>
Min
{translate('Min')}
<NumberInput
className={styles.sizeInput}
@ -295,7 +296,7 @@ class QualityDefinition extends Component {
</div>
<div>
Preferred
{translate('Preferred')}
<NumberInput
className={styles.sizeInput}
@ -310,7 +311,7 @@ class QualityDefinition extends Component {
</div>
<div>
Max
{translate('Max')}
<NumberInput
className={styles.sizeInput}

View File

@ -1,6 +1,7 @@
import PropTypes from 'prop-types';
import React from 'react';
import formatBytes from 'Utilities/Number/formatBytes';
import translate from 'Utilities/String/translate';
function QualityDefinitionLimits(props) {
const {
@ -13,14 +14,20 @@ function QualityDefinitionLimits(props) {
}
const thirty = formatBytes(bytes * 30);
const fourtyFive = formatBytes(bytes * 45);
const fortyFive = formatBytes(bytes * 45);
const sixty = formatBytes(bytes * 60);
return (
<div>
<div>30 Minutes: {thirty}</div>
<div>45 Minutes: {fourtyFive}</div>
<div>60 Minutes: {sixty}</div>
<div>
{translate('MinutesThirty', { thirty })}
</div>
<div>
{translate('MinutesFortyFive', { fortyFive })}
</div>
<div>
{translate('MinutesSixty', { sixty })}
</div>
</div>
);
}

View File

@ -2,6 +2,7 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react';
import FieldSet from 'Components/FieldSet';
import PageSectionContent from 'Components/Page/PageSectionContent';
import translate from 'Utilities/String/translate';
import QualityDefinitionConnector from './QualityDefinitionConnector';
import styles from './QualityDefinitions.css';
@ -18,20 +19,26 @@ class QualityDefinitions extends Component {
} = this.props;
return (
<FieldSet legend="Quality Definitions">
<FieldSet legend={translate('QualityDefinitions')}>
<PageSectionContent
errorMessage="Unable to load Quality Definitions"
errorMessage={translate('QualityDefinitionsLoadError')}
{...otherProps}
>
<div className={styles.header}>
<div className={styles.quality}>Quality</div>
<div className={styles.title}>Title</div>
<div className={styles.sizeLimit}>Size Limit</div>
<div className={styles.quality}>
{translate('Quality')}
</div>
<div className={styles.title}>
{translate('Title')}
</div>
<div className={styles.sizeLimit}>
{translate('SizeLimit')}
</div>
{
advancedSettings ?
<div className={styles.megabytesPerMinute}>
Megabytes Per Minute
{translate('MegabytesPerMinute')}
</div> :
null
}
@ -53,7 +60,7 @@ class QualityDefinitions extends Component {
<div className={styles.sizeLimitHelpTextContainer}>
<div className={styles.sizeLimitHelpText}>
Limits are automatically adjusted for the series runtime and number of episodes in the file.
{translate('QualityLimitsHelpText')}
</div>
</div>
</PageSectionContent>

View File

@ -6,6 +6,7 @@ import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator';
import { icons } from 'Helpers/Props';
import SettingsToolbarConnector from 'Settings/SettingsToolbarConnector';
import translate from 'Utilities/String/translate';
import QualityDefinitionsConnector from './Definition/QualityDefinitionsConnector';
import ResetQualityDefinitionsModal from './Reset/ResetQualityDefinitionsModal';
@ -62,7 +63,7 @@ class Quality extends Component {
} = this.state;
return (
<PageContent title="Quality Settings">
<PageContent title={translate('QualitySettings')}>
<SettingsToolbarConnector
isSaving={isSaving}
hasPendingChanges={hasPendingChanges}
@ -71,7 +72,7 @@ class Quality extends Component {
<PageToolbarSeparator />
<PageToolbarButton
label="Reset Definitions"
label={translate('ResetDefinitions')}
iconName={icons.REFRESH}
isSpinning={isResettingQualityDefinitions}
onPress={this.onResetQualityDefinitionsPress}

View File

@ -2,12 +2,13 @@ import React from 'react';
import Link from 'Components/Link/Link';
import PageContent from 'Components/Page/PageContent';
import PageContentBody from 'Components/Page/PageContentBody';
import translate from 'Utilities/String/translate';
import SettingsToolbarConnector from './SettingsToolbarConnector';
import styles from './Settings.css';
function Settings() {
return (
<PageContent title="Settings">
<PageContent title={translate('Settings')}>
<SettingsToolbarConnector
hasPendingChanges={false}
/>
@ -17,143 +18,143 @@ function Settings() {
className={styles.link}
to="/settings/mediamanagement"
>
Media Management
{translate('MediaManagement')}
</Link>
<div className={styles.summary}>
Naming, file management settings and root folders
{translate('MediaManagementSettingsSummary')}
</div>
<Link
className={styles.link}
to="/settings/profiles"
>
Profiles
{translate('Profiles')}
</Link>
<div className={styles.summary}>
Quality, Language, Delay and Release profiles
{translate('ProfilesSettingsSummary')}
</div>
<Link
className={styles.link}
to="/settings/quality"
>
Quality
{translate('Quality')}
</Link>
<div className={styles.summary}>
Quality sizes and naming
{translate('QualitySettingsSummary')}
</div>
<Link
className={styles.link}
to="/settings/customformats"
>
Custom Formats
{translate('CustomFormats')}
</Link>
<div className={styles.summary}>
Custom Formats and Settings
{translate('CustomFormatsSettingsSummary')}
</div>
<Link
className={styles.link}
to="/settings/indexers"
>
Indexers
{translate('Indexers')}
</Link>
<div className={styles.summary}>
Indexers and indexer options
{translate('IndexersSettingsSummary')}
</div>
<Link
className={styles.link}
to="/settings/downloadclients"
>
Download Clients
{translate('DownloadClients')}
</Link>
<div className={styles.summary}>
Download clients, download handling and remote path mappings
{translate('DownloadClientsSettingsSummary')}
</div>
<Link
className={styles.link}
to="/settings/importlists"
>
Import Lists
{translate('ImportLists')}
</Link>
<div className={styles.summary}>
Import from another Sonarr instance or Trakt lists and manage list exclusions
{translate('ImportListsSettingsSummary')}
</div>
<Link
className={styles.link}
to="/settings/connect"
>
Connect
{translate('Connect')}
</Link>
<div className={styles.summary}>
Notifications, connections to media servers/players and custom scripts
{translate('ConnectSettingsSummary')}
</div>
<Link
className={styles.link}
to="/settings/metadata"
>
Metadata
{translate('Metadata')}
</Link>
<div className={styles.summary}>
Create metadata files when episodes are imported or series are refreshed
{translate('MetadataSettingsSummary')}
</div>
<Link
className={styles.link}
to="/settings/metadatasource"
>
Metadata Source
{translate('MetadataSource')}
</Link>
<div className={styles.summary}>
Information on where Sonarr gets Series and Episode information
{translate('MetadataSourceSettingsSummary')}
</div>
<Link
className={styles.link}
to="/settings/tags"
>
Tags
{translate('Tags')}
</Link>
<div className={styles.summary}>
See all tags and how they are used. Unused tags can be removed
{translate('TagsSettingsSummary')}
</div>
<Link
className={styles.link}
to="/settings/general"
>
General
{translate('General')}
</Link>
<div className={styles.summary}>
Port, SSL, username/password, proxy, analytics and updates
{translate('GeneralSettingsSummary')}
</div>
<Link
className={styles.link}
to="/settings/ui"
>
UI
{translate('Ui')}
</Link>
<div className={styles.summary}>
Calendar, date and color impaired options
{translate('UiSettingsSummary')}
</div>
</PageContentBody>
</PageContent>

View File

@ -5,6 +5,7 @@ import PageToolbar from 'Components/Page/Toolbar/PageToolbar';
import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
import { icons } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import AdvancedSettingsButton from './AdvancedSettingsButton';
import PendingChangesModal from './PendingChangesModal';
@ -61,7 +62,7 @@ class SettingsToolbar extends Component {
{
showSave &&
<PageToolbarButton
label={hasPendingChanges ? 'Save Changes' : 'No Changes'}
label={hasPendingChanges ? translate('SaveChanges') : translate('NoChanges')}
iconName={icons.SAVE}
isSpinning={isSaving}
isDisabled={!hasPendingChanges}

View File

@ -58,7 +58,7 @@ export default function AutoTaggings() {
return (
<FieldSet legend={translate('AutoTagging')}>
<PageSectionContent
errorMessage={translate('UnableToLoadAutoTagging')}
errorMessage={translate('AutoTaggingLoadError')}
error={error}
isFetching={isFetching}
isPopulated={isPopulated}

View File

@ -123,7 +123,7 @@ export default function EditAutoTaggingModalContent(props) {
{
!isFetching && !!error ?
<div>
{'Unable to add a new auto tag, please try again.'}
{translate('AddAutoTagError')}
</div> :
null
}

View File

@ -56,7 +56,7 @@ export default function AddSpecificationModalContent(props) {
{
!isSchemaFetching && !!schemaError ?
<div>
{'Unable to add a new condition, please try again.'}
{translate('AddConditionError')}
</div> :
null
}
@ -67,7 +67,7 @@ export default function AddSpecificationModalContent(props) {
<Alert kind={kinds.INFO}>
<div>
{'Sonarr supports the follow properties for auto tagging rules'}
{translate('SupportedAutoTaggingProperties')}
</div>
</Alert>

View File

@ -8,8 +8,8 @@ import FormInputGroup from 'Components/Form/FormInputGroup';
import FormLabel from 'Components/Form/FormLabel';
import ProviderFieldFormGroup from 'Components/Form/ProviderFieldFormGroup';
import Button from 'Components/Link/Button';
import Link from 'Components/Link/Link';
import SpinnerErrorButton from 'Components/Link/SpinnerErrorButton';
import InlineMarkdown from 'Components/Markdown/InlineMarkdown';
import ModalBody from 'Components/Modal/ModalBody';
import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter';
@ -83,12 +83,13 @@ function EditSpecificationModalContent(props) {
fields && fields.some((x) => x.label === 'Regular Expression') &&
<Alert kind={kinds.INFO}>
<div>
<div dangerouslySetInnerHTML={{ __html: 'This condition matches using Regular Expressions. Note that the characters <code>\\^$.|?*+()[{</code> have special meanings and need escaping with a <code>\\</code>' }} />
{'More details'} <Link to="https://www.regular-expressions.info/tutorial.html">{'Here'}</Link>
<InlineMarkdown data={translate('ConditionUsingRegularExpressions')} />
</div>
<div>
{'Regular expressions can be tested '}
<Link to="http://regexstorm.net/tester">Here</Link>
<InlineMarkdown data={translate('RegularExpressionsTutorialLink')} />
</div>
<div>
<InlineMarkdown data={translate('RegularExpressionsCanBeTested')} />
</div>
</Alert>
}
@ -130,7 +131,7 @@ function EditSpecificationModalContent(props) {
type={inputTypes.CHECK}
name="negate"
{...negate}
helpText={translate('AutoTaggingNegateHelpText', { name: implementationName })}
helpText={translate('AutoTaggingNegateHelpText', { implementationName })}
onChange={onInputChange}
/>
</FormGroup>
@ -144,7 +145,7 @@ function EditSpecificationModalContent(props) {
type={inputTypes.CHECK}
name="required"
{...required}
helpText={translate('AutoTaggingRequiredHelpText', { name: implementationName })}
helpText={translate('AutoTaggingRequiredHelpText', { implementationName })}
onChange={onInputChange}
/>
</FormGroup>

View File

@ -5,6 +5,7 @@ import Label from 'Components/Label';
import IconButton from 'Components/Link/IconButton';
import ConfirmModal from 'Components/Modal/ConfirmModal';
import { icons, kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import EditSpecificationModal from './EditSpecificationModal';
import styles from './Specification.css';
@ -60,7 +61,7 @@ export default function Specification(props) {
<IconButton
className={styles.cloneButton}
title="Clone"
title={translate('Clone')}
name={icons.CLONE}
onPress={onClonePress}
/>
@ -74,7 +75,7 @@ export default function Specification(props) {
{
negate ?
<Label kind={kinds.DANGER}>
Negated
{translate('Negated')}
</Label> :
null
}
@ -82,7 +83,7 @@ export default function Specification(props) {
{
required ?
<Label kind={kinds.SUCCESS}>
Required
{translate('Required')}
</Label> :
null
}
@ -98,9 +99,9 @@ export default function Specification(props) {
<ConfirmModal
isOpen={isDeleteModalOpen}
kind={kinds.DANGER}
title="Delete Specification"
message={`Are you sure you want to delete specification ${name} ?`}
confirmLabel="Delete"
title={translate('DeleteSpecification')}
message={translate('DeleteSpecificationHelpText', { name })}
confirmLabel={translate('Delete')}
onConfirm={onConfirmDelete}
onCancel={onDeleteModalClose}
/>

View File

@ -1,6 +1,7 @@
import PropTypes from 'prop-types';
import React from 'react';
import titleCase from 'Utilities/String/titleCase';
import translate from 'Utilities/String/translate';
function TagDetailsDelayProfile(props) {
const {
@ -14,22 +15,22 @@ function TagDetailsDelayProfile(props) {
return (
<div>
<div>
Protocol: {titleCase(preferredProtocol)}
{titleCase(translate('DelayProfileProtocol', { preferredProtocol }))}
</div>
<div>
{
enableUsenet ?
`Usenet Delay: ${usenetDelay}` :
'Usenet disabled'
translate('UsenetDelayTime', { usenetDelay }) :
translate('UsenetDisabled')
}
</div>
<div>
{
enableTorrent ?
`Torrent Delay: ${torrentDelay}` :
'Torrents disabled'
translate('TorrentDelayTime', { torrentDelay }) :
translate('TorrentsDisabled')
}
</div>
</div>

View File

@ -8,6 +8,7 @@ import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import TagDetailsDelayProfile from './TagDetailsDelayProfile';
import styles from './TagDetailsModalContent.css';
@ -30,18 +31,20 @@ function TagDetailsModalContent(props) {
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
Tag Details - {label}
{translate('TagDetails', { label })}
</ModalHeader>
<ModalBody>
{
!isTagUsed &&
<div>Tag is not used and can be deleted</div>
<div>
{translate('TagIsNotUsedAndCanBeDeleted')}
</div>
}
{
series.length ?
<FieldSet legend="Series">
<FieldSet legend={translate('Series')}>
{
series.map((item) => {
return (
@ -57,7 +60,7 @@ function TagDetailsModalContent(props) {
{
delayProfiles.length ?
<FieldSet legend="Delay Profile">
<FieldSet legend={translate('DelayProfile')}>
{
delayProfiles.map((item) => {
const {
@ -87,7 +90,7 @@ function TagDetailsModalContent(props) {
{
notifications.length ?
<FieldSet legend="Connections">
<FieldSet legend={translate('Connections')}>
{
notifications.map((item) => {
return (
@ -103,7 +106,7 @@ function TagDetailsModalContent(props) {
{
importLists.length ?
<FieldSet legend="Import Lists">
<FieldSet legend={translate('ImportLists')}>
{
importLists.map((item) => {
return (
@ -119,7 +122,7 @@ function TagDetailsModalContent(props) {
{
releaseProfiles.length ?
<FieldSet legend="Release Profiles">
<FieldSet legend={translate('ReleaseProfiles')}>
{
releaseProfiles.map((item) => {
return (
@ -166,7 +169,7 @@ function TagDetailsModalContent(props) {
{
indexers.length ?
<FieldSet legend="Indexers">
<FieldSet legend={translate('Indexers')}>
{
indexers.map((item) => {
return (
@ -182,7 +185,7 @@ function TagDetailsModalContent(props) {
{
downloadClients.length ?
<FieldSet legend="Download Clients">
<FieldSet legend={translate('DownloadClients')}>
{
downloadClients.map((item) => {
return (
@ -198,7 +201,7 @@ function TagDetailsModalContent(props) {
{
autoTags.length ?
<FieldSet legend="Auto Tagging">
<FieldSet legend={translate('AutoTagging')}>
{
autoTags.map((item) => {
return (
@ -218,18 +221,18 @@ function TagDetailsModalContent(props) {
<Button
className={styles.deleteButton}
kind={kinds.DANGER}
title={isTagUsed ? 'Cannot be deleted while in use' : undefined}
title={isTagUsed ? translate('TagCannotBeDeletedWhileInUse') : undefined}
isDisabled={isTagUsed}
onPress={onDeleteTagPress}
>
Delete
{translate('Delete')}
</Button>
}
<Button
onPress={onModalClose}
>
Close
{translate('Close')}
</Button>
</ModalFooter>
</ModalContent>

View File

@ -3,6 +3,7 @@ import React, { Component } from 'react';
import Card from 'Components/Card';
import ConfirmModal from 'Components/Modal/ConfirmModal';
import { kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import TagDetailsModal from './Details/TagDetailsModal';
import TagInUse from './TagInUse';
import styles from './Tag.css';
@ -93,45 +94,49 @@ class Tag extends Component {
isTagUsed ?
<div>
<TagInUse
label="series"
label={translate('Series')}
count={seriesIds.length}
shouldPluralize={false}
/>
<TagInUse
label="delay profile"
label={translate('DelayProfile')}
labelPlural={translate('DelayProfiles')}
count={delayProfileIds.length}
/>
<TagInUse
label="import list"
label={translate('ImportList')}
labelPlural={translate('ImportLists')}
count={importListIds.length}
/>
<TagInUse
label="connection"
label={translate('Connection')}
labelPlural={translate('Connections')}
count={notificationIds.length}
/>
<TagInUse
label="release profile"
label={translate('ReleaseProfile')}
labelPlural={translate('ReleaseProfiles')}
count={restrictionIds.length}
/>
<TagInUse
label="indexer"
label={translate('Indexer')}
labelPlural={translate('Indexers')}
count={indexerIds.length}
/>
<TagInUse
label="download client"
label={translate('DownloadClient')}
labelPlural={translate('DownloadClients')}
count={downloadClientIds.length}
/>
<TagInUse
label="auto tagging"
label={translate('AutoTagging')}
count={autoTagIds.length}
shouldPluralize={false}
/>
</div> :
null
@ -140,7 +145,7 @@ class Tag extends Component {
{
!isTagUsed &&
<div>
No links
{translate('NoLinks')}
</div>
}
@ -163,9 +168,9 @@ class Tag extends Component {
<ConfirmModal
isOpen={isDeleteTagModalOpen}
kind={kinds.DANGER}
title="Delete Tag"
message={`Are you sure you want to delete the tag '${label}'?`}
confirmLabel="Delete"
title={translate('DeleteTag')}
message={translate('DeleteTagMessageText', { label })}
confirmLabel={translate('Delete')}
onConfirm={this.onConfirmDeleteTag}
onCancel={this.onDeleteTagModalClose}
/>

View File

@ -4,31 +4,31 @@ import React from 'react';
export default function TagInUse(props) {
const {
label,
count,
shouldPluralize = true
labelPlural,
count
} = props;
if (count === 0) {
return null;
}
if (count > 1 && shouldPluralize) {
if (count > 1 && labelPlural ) {
return (
<div>
{count} {label}s
{count} {labelPlural.toLowerCase()}
</div>
);
}
return (
<div>
{count} {label}
{count} {label.toLowerCase()}
</div>
);
}
TagInUse.propTypes = {
label: PropTypes.string.isRequired,
count: PropTypes.number.isRequired,
shouldPluralize: PropTypes.bool
labelPlural: PropTypes.string,
count: PropTypes.number.isRequired
};

View File

@ -2,12 +2,13 @@ import React from 'react';
import PageContent from 'Components/Page/PageContent';
import PageContentBody from 'Components/Page/PageContentBody';
import SettingsToolbarConnector from 'Settings/SettingsToolbarConnector';
import translate from 'Utilities/String/translate';
import AutoTaggings from './AutoTagging/AutoTaggings';
import TagsConnector from './TagsConnector';
function TagSettings() {
return (
<PageContent title="Tags">
<PageContent title={translate('Tags')}>
<SettingsToolbarConnector
showSave={false}
/>

View File

@ -4,6 +4,7 @@ import Alert from 'Components/Alert';
import FieldSet from 'Components/FieldSet';
import PageSectionContent from 'Components/Page/PageSectionContent';
import { kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import TagConnector from './TagConnector';
import styles from './Tags.css';
@ -16,17 +17,17 @@ function Tags(props) {
if (!items.length) {
return (
<Alert kind={kinds.INFO}>
No tags have been added yet
{translate('NoTagsHaveBeenAddedYet')}
</Alert>
);
}
return (
<FieldSet
legend="Tags"
legend={translate('Tags')}
>
<PageSectionContent
errorMessage="Unable to load Tags"
errorMessage={translate('TagsLoadError')}
{...otherProps}
>
<div className={styles.tags}>

View File

@ -16,8 +16,18 @@ import titleCase from 'Utilities/String/titleCase';
import translate from 'Utilities/String/translate';
export const firstDayOfWeekOptions = [
{ key: 0, value: 'Sunday' },
{ key: 1, value: 'Monday' }
{
key: 0,
get value() {
return translate('Sunday');
}
},
{
key: 1,
get value() {
return translate('Monday');
}
}
];
export const weekColumnOptions = [
@ -67,7 +77,7 @@ class UISettings extends Component {
.map((theme) => ({ key: theme, value: titleCase(theme) }));
return (
<PageContent title="UI Settings">
<PageContent title={translate('UiSettings')}>
<SettingsToolbarConnector
{...otherProps}
onSavePress={onSavePress}
@ -82,7 +92,9 @@ class UISettings extends Component {
{
!isFetching && error ?
<Alert kind={kinds.DANGER}>Unable to load UI settings</Alert> :
<Alert kind={kinds.DANGER}>
{translate('UiSettingsLoadError')}
</Alert> :
null
}
@ -92,9 +104,9 @@ class UISettings extends Component {
id="uiSettings"
{...otherProps}
>
<FieldSet legend="Calendar">
<FieldSet legend={translate('Calendar')}>
<FormGroup>
<FormLabel>First Day of Week</FormLabel>
<FormLabel>{translate('FirstDayOfWeek')}</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
@ -106,24 +118,24 @@ class UISettings extends Component {
</FormGroup>
<FormGroup>
<FormLabel>Week Column Header</FormLabel>
<FormLabel>{translate('WeekColumnHeader')}</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
name="calendarWeekColumnHeader"
values={weekColumnOptions}
onChange={onInputChange}
helpText="Shown above each column when week is the active view"
helpText={translate('WeekColumnHeaderHelpText')}
{...settings.calendarWeekColumnHeader}
/>
</FormGroup>
</FieldSet>
<FieldSet
legend="Dates"
legend={translate('Dates')}
>
<FormGroup>
<FormLabel>Short Date Format</FormLabel>
<FormLabel>{translate('ShortDateFormat')}</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
@ -135,7 +147,7 @@ class UISettings extends Component {
</FormGroup>
<FormGroup>
<FormLabel>Long Date Format</FormLabel>
<FormLabel>{translate('LongDateFormat')}</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
@ -147,7 +159,7 @@ class UISettings extends Component {
</FormGroup>
<FormGroup>
<FormLabel>Time Format</FormLabel>
<FormLabel>{translate('TimeFormat')}</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
@ -159,11 +171,11 @@ class UISettings extends Component {
</FormGroup>
<FormGroup>
<FormLabel>Show Relative Dates</FormLabel>
<FormLabel>{translate('ShowRelativeDates')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="showRelativeDates"
helpText="Show relative (Today/Yesterday/etc) or absolute dates"
helpText={translate('ShowRelativeDatesHelpText')}
onChange={onInputChange}
{...settings.showRelativeDates}
/>
@ -171,14 +183,14 @@ class UISettings extends Component {
</FieldSet>
<FieldSet
legend="Style"
legend={translate('Style')}
>
<FormGroup>
<FormLabel>Theme</FormLabel>
<FormLabel>{translate('Theme')}</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
name="theme"
helpText="Change Application UI Theme, 'Auto' Theme will use your OS Theme to set Light or Dark mode. Inspired by Theme.Park"
helpText={translate('ThemeHelpText')}
values={themeOptions}
onChange={onInputChange}
{...settings.theme}
@ -186,11 +198,11 @@ class UISettings extends Component {
</FormGroup>
<FormGroup>
<FormLabel>Enable Color-Impaired Mode</FormLabel>
<FormLabel>{translate('EnableColorImpairedMode')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="enableColorImpairedMode"
helpText="Altered style to allow color-impaired users to better distinguish color coded information"
helpText={translate('EnableColorImpairedModeHelpText')}
onChange={onInputChange}
{...settings.enableColorImpairedMode}
/>
@ -199,13 +211,13 @@ class UISettings extends Component {
<FieldSet legend={translate('Language')}>
<FormGroup>
<FormLabel>{translate('UI Language')}</FormLabel>
<FormLabel>{translate('UiLanguage')}</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
name="uiLanguage"
values={languages}
helpText={translate('Language that Sonarr will use for UI')}
helpTextWarning={translate('Browser Reload Required')}
helpText={translate('UiLanguageHelpText')}
helpTextWarning={translate('BrowserReloadRequired')}
onChange={onInputChange}
{...settings.uiLanguage}
/>

View File

@ -109,7 +109,7 @@ class Backups extends Component {
{
!isFetching && !!error &&
<Alert kind={kinds.DANGER}>
{translate('UnableToLoadBackups')}
{translate('BackupsLoadError')}
</Alert>
}

Some files were not shown because too many files have changed in this diff Show More