client: migrate to lingui.js

This commit is contained in:
Jesse Chan
2021-01-29 11:53:13 +08:00
parent 6bc71c5e26
commit c4bf66fbf6
142 changed files with 5469 additions and 82242 deletions

View File

@@ -16,7 +16,7 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
node: [15] node: [15]
check: [check-compiled-i18n, check-source-formatting, check-types, lint] check: [check-source-formatting, check-types, lint]
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2

51
.linguirc Normal file
View File

@@ -0,0 +1,51 @@
{
"locales": [
"af",
"ar",
"ca",
"cs",
"da",
"de",
"el",
"en",
"es",
"fi",
"fr",
"he",
"hu",
"it",
"ja",
"ko",
"nl",
"no",
"pl",
"pt",
"ro",
"ru",
"sr",
"sv",
"tr",
"uk",
"vi",
"zh-Hans",
"zh-Hant"
],
"sourceLocale": "en",
"fallbackLocales": {
"zh-Hans": "zh-Hant",
"zh-Hant": "zh-Hans",
"default": "en"
},
"catalogs": [
{
"path": "client/src/javascript/i18n/strings/{locale}",
"include": [
"<rootDir>/client/src/javascript"
],
"exclude": [
"**/node_modules/**"
]
}
],
"format": "minimal"
}

View File

@@ -111,7 +111,6 @@ module.exports = {
resolve: { resolve: {
extensions: ['.cjs', '.mjs', '.js', '.jsx', '.ts', '.tsx', '.json'], extensions: ['.cjs', '.mjs', '.js', '.jsx', '.ts', '.tsx', '.json'],
alias: { alias: {
'react-intl': 'react-intl/react-intl-no-parser.umd.min.js',
'@client': path.resolve('./client/src/javascript'), '@client': path.resolve('./client/src/javascript'),
'@shared': path.resolve('./shared'), '@shared': path.resolve('./shared'),
}, },

View File

@@ -11,7 +11,6 @@ import AppWrapper from './components/AppWrapper';
import LoadingOverlay from './components/general/LoadingOverlay'; import LoadingOverlay from './components/general/LoadingOverlay';
import AsyncIntlProvider from './i18n/languages'; import AsyncIntlProvider from './i18n/languages';
import ConfigStore from './stores/ConfigStore'; import ConfigStore from './stores/ConfigStore';
import SettingStore from './stores/SettingStore';
import UIStore from './stores/UIStore'; import UIStore from './stores/UIStore';
import history from './util/history'; import history from './util/history';
@@ -76,7 +75,7 @@ const FloodApp: FC = observer(() => {
return ( return (
<Suspense fallback={<LoadingOverlay />}> <Suspense fallback={<LoadingOverlay />}>
<AsyncIntlProvider language={SettingStore.floodSettings.language}> <AsyncIntlProvider>
<Router history={history}> <Router history={history}>
<QueryParamProvider ReactRouterRoute={Route}> <QueryParamProvider ReactRouterRoute={Route}>
<AppWrapper className={ConfigStore.preferDark ? 'dark' : undefined}> <AppWrapper className={ConfigStore.preferDark ? 'dark' : undefined}>

View File

@@ -1,7 +1,7 @@
import {FC} from 'react'; import {FC} from 'react';
import {FormattedMessage} from 'react-intl';
import classnames from 'classnames'; import classnames from 'classnames';
import {observer} from 'mobx-react'; import {observer} from 'mobx-react';
import {Trans} from '@lingui/react';
import AlertStore from '@client/stores/AlertStore'; import AlertStore from '@client/stores/AlertStore';
import {CircleCheckmark, CircleExclamation} from '@client/ui/icons'; import {CircleCheckmark, CircleExclamation} from '@client/ui/icons';
@@ -32,7 +32,7 @@ const Alert: FC<AlertProps> = observer((props: AlertProps) => {
<li className={alertClasses}> <li className={alertClasses}>
{icon} {icon}
<span className="alert__content"> <span className="alert__content">
<FormattedMessage <Trans
id={id} id={id}
values={{ values={{
count, count,

View File

@@ -1,5 +1,5 @@
import {FC, useRef, useState} from 'react'; import {FC, useRef, useState} from 'react';
import {useIntl} from 'react-intl'; import {useLingui} from '@lingui/react';
import {Button, Form, FormError, FormRow, Panel, PanelContent, PanelHeader, PanelFooter, Textbox} from '@client/ui'; import {Button, Form, FormError, FormRow, Panel, PanelContent, PanelHeader, PanelFooter, Textbox} from '@client/ui';
import AuthActions from '@client/actions/AuthActions'; import AuthActions from '@client/actions/AuthActions';
@@ -20,7 +20,7 @@ interface AuthFormProps {
} }
const AuthForm: FC<AuthFormProps> = ({mode}: AuthFormProps) => { const AuthForm: FC<AuthFormProps> = ({mode}: AuthFormProps) => {
const intl = useIntl(); const {i18n} = useLingui();
const formRef = useRef<Form>(null); const formRef = useRef<Form>(null);
const clientConnectionSettingsRef = useRef<ClientConnectionSettings | null>(null); const clientConnectionSettingsRef = useRef<ClientConnectionSettings | null>(null);
const [errorMessage, setErrorMessage] = useState<string | {id: string} | undefined>(undefined); const [errorMessage, setErrorMessage] = useState<string | {id: string} | undefined>(undefined);
@@ -102,30 +102,16 @@ const AuthForm: FC<AuthFormProps> = ({mode}: AuthFormProps) => {
}} }}
ref={formRef}> ref={formRef}>
<PanelHeader> <PanelHeader>
<h1> <h1>{isLoginMode ? i18n._('auth.login') : i18n._('auth.create.an.account')}</h1>
{isLoginMode
? intl.formatMessage({
id: 'auth.login',
})
: intl.formatMessage({
id: 'auth.create.an.account',
})}
</h1>
</PanelHeader> </PanelHeader>
<PanelContent> <PanelContent>
<p className="copy--lead"> <p className="copy--lead">
{isLoginMode {isLoginMode ? i18n._('auth.login.intro') : i18n._('auth.create.an.account.intro')}
? intl.formatMessage({
id: 'auth.login.intro',
})
: intl.formatMessage({
id: 'auth.create.an.account.intro',
})}
</p> </p>
{errorMessage != null ? ( {errorMessage != null ? (
<FormRow> <FormRow>
<FormError isLoading={isSubmitting}> <FormError isLoading={isSubmitting}>
{typeof errorMessage === 'string' ? errorMessage : intl.formatMessage(errorMessage)} {typeof errorMessage === 'string' ? errorMessage : i18n._(errorMessage)}
</FormError> </FormError>
</FormRow> </FormRow>
) : null} ) : null}
@@ -159,18 +145,10 @@ const AuthForm: FC<AuthFormProps> = ({mode}: AuthFormProps) => {
formRef.current.resetForm(); formRef.current.resetForm();
} }
}}> }}>
{intl.formatMessage({ {i18n._('auth.input.clear')}
id: 'auth.input.clear',
})}
</Button> </Button>
<Button isLoading={isSubmitting} type="submit"> <Button isLoading={isSubmitting} type="submit">
{isLoginMode {isLoginMode ? i18n._('auth.log.in') : i18n._('auth.create.account')}
? intl.formatMessage({
id: 'auth.log.in',
})
: intl.formatMessage({
id: 'auth.create.account',
})}
</Button> </Button>
</FormRow> </FormRow>
</PanelFooter> </PanelFooter>

View File

@@ -1,6 +1,6 @@
import {FC, ReactText, useRef, useState} from 'react'; import {FC, ReactText, useRef, useState} from 'react';
import {FormattedMessage} from 'react-intl';
import {observer} from 'mobx-react'; import {observer} from 'mobx-react';
import {Trans} from '@lingui/react';
import { import {
Button, Button,
@@ -74,14 +74,14 @@ const ClientConnectionInterruption: FC = observer(() => {
}}> }}>
<PanelHeader> <PanelHeader>
<h1> <h1>
<FormattedMessage id="connection-interruption.heading" /> <Trans id="connection-interruption.heading" />
</h1> </h1>
</PanelHeader> </PanelHeader>
<PanelContent> <PanelContent>
{error && ( {error && (
<FormRow> <FormRow>
<FormError> <FormError>
<FormattedMessage id={error} /> <Trans id={error} />
</FormError> </FormError>
</FormRow> </FormRow>
)} )}
@@ -89,16 +89,16 @@ const ClientConnectionInterruption: FC = observer(() => {
<FormRow> <FormRow>
<Select id="action" onSelect={setSelection} defaultID="retry"> <Select id="action" onSelect={setSelection} defaultID="retry">
<SelectItem key="retry" id="retry"> <SelectItem key="retry" id="retry">
<FormattedMessage id="connection-interruption.action.selection.retry" /> <Trans id="connection-interruption.action.selection.retry" />
</SelectItem> </SelectItem>
<SelectItem key="config" id="config"> <SelectItem key="config" id="config">
<FormattedMessage id="connection-interruption.action.selection.config" /> <Trans id="connection-interruption.action.selection.config" />
</SelectItem> </SelectItem>
</Select> </Select>
</FormRow> </FormRow>
) : ( ) : (
<p className="copy--lead"> <p className="copy--lead">
<FormattedMessage id="connection-interruption.not.admin" /> <Trans id="connection-interruption.not.admin" />
</p> </p>
)} )}
{selection === 'config' && ( {selection === 'config' && (
@@ -113,12 +113,12 @@ const ClientConnectionInterruption: FC = observer(() => {
<FormRow justify="end"> <FormRow justify="end">
{selection === 'retry' && ( {selection === 'retry' && (
<Button type="submit" isLoading={isSubmitting}> <Button type="submit" isLoading={isSubmitting}>
<FormattedMessage id="button.retry" /> <Trans id="button.retry" />
</Button> </Button>
)} )}
{selection === 'config' && ( {selection === 'config' && (
<Button type="submit" isLoading={isSubmitting}> <Button type="submit" isLoading={isSubmitting}>
<FormattedMessage id="button.save" /> <Trans id="button.save" />
</Button> </Button>
)} )}
</FormRow> </FormRow>

View File

@@ -1,7 +1,7 @@
import classnames from 'classnames'; import classnames from 'classnames';
import {FC} from 'react'; import {FC} from 'react';
import {observer} from 'mobx-react'; import {observer} from 'mobx-react';
import {useIntl} from 'react-intl'; import {useLingui} from '@lingui/react';
import {useKeyPressEvent} from 'react-use'; import {useKeyPressEvent} from 'react-use';
import {ContextMenu} from '@client/ui'; import {ContextMenu} from '@client/ui';
@@ -22,7 +22,7 @@ const ContextMenuMountPoint: FC<ContextMenuMountPointProps> = observer(({id}: Co
y: 0, y: 0,
}; };
const intl = useIntl(); const {i18n} = useLingui();
useKeyPressEvent('Escape', () => UIActions.dismissContextMenu(id)); useKeyPressEvent('Escape', () => UIActions.dismissContextMenu(id));
@@ -51,7 +51,7 @@ const ContextMenuMountPoint: FC<ContextMenuMountPointProps> = observer(({id}: Co
className={classnames('menu__item__label--primary', { className={classnames('menu__item__label--primary', {
'has-action': item.labelAction, 'has-action': item.labelAction,
})}> })}>
<span className="menu__item__label">{intl.formatMessage({id: item.label})}</span> <span className="menu__item__label">{i18n._(item.label)}</span>
{item.labelAction ? ( {item.labelAction ? (
<span className="menu__item__label__action"> <span className="menu__item__label__action">
<item.labelAction /> <item.labelAction />

View File

@@ -1,5 +1,5 @@
import {FC, ReactNode} from 'react'; import {FC, ReactNode} from 'react';
import {FormattedMessage} from 'react-intl'; import {Trans} from '@lingui/react';
const secondsToDuration = ( const secondsToDuration = (
cumSeconds: number, cumSeconds: number,
@@ -59,19 +59,19 @@ const Duration: FC<DurationProps> = (props: DurationProps) => {
const duration = value === -1 ? -1 : secondsToDuration(value); const duration = value === -1 ? -1 : secondsToDuration(value);
if (duration === -1) { if (duration === -1) {
content = <FormattedMessage id="unit.time.infinity" />; content = <Trans id="unit.time.infinity" />;
} else if (duration.years != null && duration.years > 0) { } else if (duration.years != null && duration.years > 0) {
content = [ content = [
<span className="duration--segment" key="years"> <span className="duration--segment" key="years">
{duration.years} {duration.years}
<em className="unit"> <em className="unit">
<FormattedMessage id="unit.time.year" /> <Trans id="unit.time.year" />
</em> </em>
</span>, </span>,
<span className="duration--segment" key="weeks"> <span className="duration--segment" key="weeks">
{duration.weeks} {duration.weeks}
<em className="unit"> <em className="unit">
<FormattedMessage id="unit.time.week" /> <Trans id="unit.time.week" />
</em> </em>
</span>, </span>,
]; ];
@@ -80,13 +80,13 @@ const Duration: FC<DurationProps> = (props: DurationProps) => {
<span className="duration--segment" key="weeks"> <span className="duration--segment" key="weeks">
{duration.weeks} {duration.weeks}
<em className="unit"> <em className="unit">
<FormattedMessage id="unit.time.week" /> <Trans id="unit.time.week" />
</em> </em>
</span>, </span>,
<span className="duration--segment" key="days"> <span className="duration--segment" key="days">
{duration.days} {duration.days}
<em className="unit"> <em className="unit">
<FormattedMessage id="unit.time.day" /> <Trans id="unit.time.day" />
</em> </em>
</span>, </span>,
]; ];
@@ -95,13 +95,13 @@ const Duration: FC<DurationProps> = (props: DurationProps) => {
<span className="duration--segment" key="days"> <span className="duration--segment" key="days">
{duration.days} {duration.days}
<em className="unit"> <em className="unit">
<FormattedMessage id="unit.time.day" /> <Trans id="unit.time.day" />
</em> </em>
</span>, </span>,
<span className="duration--segment" key="hours"> <span className="duration--segment" key="hours">
{duration.hours} {duration.hours}
<em className="unit"> <em className="unit">
<FormattedMessage id="unit.time.hour" /> <Trans id="unit.time.hour" />
</em> </em>
</span>, </span>,
]; ];
@@ -110,13 +110,13 @@ const Duration: FC<DurationProps> = (props: DurationProps) => {
<span className="duration--segment" key="hours"> <span className="duration--segment" key="hours">
{duration.hours} {duration.hours}
<em className="unit"> <em className="unit">
<FormattedMessage id="unit.time.hour" /> <Trans id="unit.time.hour" />
</em> </em>
</span>, </span>,
<span className="duration--segment" key="minutes"> <span className="duration--segment" key="minutes">
{duration.minutes} {duration.minutes}
<em className="unit"> <em className="unit">
<FormattedMessage id="unit.time.minute" /> <Trans id="unit.time.minute" />
</em> </em>
</span>, </span>,
]; ];
@@ -125,13 +125,13 @@ const Duration: FC<DurationProps> = (props: DurationProps) => {
<span className="duration--segment" key="minutes"> <span className="duration--segment" key="minutes">
{duration.minutes} {duration.minutes}
<em className="unit"> <em className="unit">
<FormattedMessage id="unit.time.minute" /> <Trans id="unit.time.minute" />
</em> </em>
</span>, </span>,
<span className="duration--segment" key="seconds"> <span className="duration--segment" key="seconds">
{duration.seconds} {duration.seconds}
<em className="unit"> <em className="unit">
<FormattedMessage id="unit.time.second" /> <Trans id="unit.time.second" />
</em> </em>
</span>, </span>,
]; ];
@@ -140,7 +140,7 @@ const Duration: FC<DurationProps> = (props: DurationProps) => {
<span className="duration--segment"> <span className="duration--segment">
{duration.seconds} {duration.seconds}
<em className="unit"> <em className="unit">
<FormattedMessage id="unit.time.second" /> <Trans id="unit.time.second" />
</em> </em>
</span> </span>
); );

View File

@@ -1,6 +1,6 @@
import classnames from 'classnames'; import classnames from 'classnames';
import {FC} from 'react'; import {FC} from 'react';
import {useIntl} from 'react-intl'; import {useLingui} from '@lingui/react';
import {CheckmarkThick} from '@client/ui/icons'; import {CheckmarkThick} from '@client/ui/icons';
@@ -13,7 +13,7 @@ const ICONS = {
}; };
const LoadingDependencyList: FC<{dependencies: Dependencies}> = ({dependencies}: {dependencies: Dependencies}) => { const LoadingDependencyList: FC<{dependencies: Dependencies}> = ({dependencies}: {dependencies: Dependencies}) => {
const intl = useIntl(); const {i18n} = useLingui();
return ( return (
<ul className="dependency-list"> <ul className="dependency-list">
@@ -28,7 +28,7 @@ const LoadingDependencyList: FC<{dependencies: Dependencies}> = ({dependencies}:
<li className={classes} key={id}> <li className={classes} key={id}>
{satisfied != null ? <span className="dependency-list__dependency__icon">{statusIcon}</span> : null} {satisfied != null ? <span className="dependency-list__dependency__icon">{statusIcon}</span> : null}
<span className="dependency-list__dependency__message"> <span className="dependency-list__dependency__message">
{typeof message === 'string' ? message : intl.formatMessage(message)} {typeof message === 'string' ? message : i18n._(message)}
</span> </span>
</li> </li>
); );

View File

@@ -1,5 +1,5 @@
import {FC, ReactNode, useState} from 'react'; import {FC, ReactNode, useState} from 'react';
import {useIntl} from 'react-intl'; import {useLingui} from '@lingui/react';
import PriorityLevels from '../../constants/PriorityLevels'; import PriorityLevels from '../../constants/PriorityLevels';
@@ -24,7 +24,7 @@ const PriorityMeter: FC<PriorityMeterProps> = ({
changePriorityFuncRef, changePriorityFuncRef,
onChange, onChange,
}: PriorityMeterProps) => { }: PriorityMeterProps) => {
const intl = useIntl(); const {i18n} = useLingui();
const [priorityLevel, setPriorityLevel] = useState<number>(level); const [priorityLevel, setPriorityLevel] = useState<number>(level);
const changePriority = () => { const changePriority = () => {
@@ -54,24 +54,16 @@ const PriorityMeter: FC<PriorityMeterProps> = ({
let priorityLevelElement: ReactNode; let priorityLevelElement: ReactNode;
switch (priorityLevels[priorityLevel as keyof typeof priorityLevels]) { switch (priorityLevels[priorityLevel as keyof typeof priorityLevels]) {
case 'DONT_DOWNLOAD': case 'DONT_DOWNLOAD':
priorityLevelElement = intl.formatMessage({ priorityLevelElement = i18n._('priority.dont.download');
id: 'priority.dont.download',
});
break; break;
case 'HIGH': case 'HIGH':
priorityLevelElement = intl.formatMessage({ priorityLevelElement = i18n._('priority.high');
id: 'priority.high',
});
break; break;
case 'LOW': case 'LOW':
priorityLevelElement = intl.formatMessage({ priorityLevelElement = i18n._('priority.low');
id: 'priority.low',
});
break; break;
default: default:
priorityLevelElement = intl.formatMessage({ priorityLevelElement = i18n._('priority.normal');
id: 'priority.normal',
});
break; break;
} }

View File

@@ -1,16 +1,8 @@
import {FC} from 'react'; import {FC} from 'react';
import {FormattedNumber, useIntl} from 'react-intl'; import {useLingui} from '@lingui/react';
import {compute, getTranslationString} from '../../util/size'; import {compute, getTranslationString} from '../../util/size';
const renderNumber = (computedNumber: ReturnType<typeof compute>) => {
if (Number.isNaN(computedNumber.value)) {
return '—';
}
return <FormattedNumber value={computedNumber.value} />;
};
interface SizeProps { interface SizeProps {
value: number; value: number;
precision?: number; precision?: number;
@@ -20,26 +12,19 @@ interface SizeProps {
const Size: FC<SizeProps> = ({value, isSpeed, className, precision}: SizeProps) => { const Size: FC<SizeProps> = ({value, isSpeed, className, precision}: SizeProps) => {
const computed = compute(value, precision); const computed = compute(value, precision);
const intl = useIntl(); const {i18n} = useLingui();
let translatedUnit = intl.formatMessage({ let translatedUnit = i18n._(getTranslationString(computed.unit));
id: getTranslationString(computed.unit),
});
if (isSpeed) { if (isSpeed) {
translatedUnit = intl.formatMessage( translatedUnit = i18n._('unit.speed', {
{ baseUnit: translatedUnit,
id: 'unit.speed', });
},
{
baseUnit: translatedUnit,
},
);
} }
return ( return (
<span className={className}> <span className={className}>
{renderNumber(computed)} {Number.isNaN(computed.value) ? '—' : i18n.number(computed.value)}
<em className="unit">{translatedUnit}</em> <em className="unit">{translatedUnit}</em>
</span> </span>
); );

View File

@@ -1,13 +1,13 @@
import {FC} from 'react'; import {FC} from 'react';
import {useIntl} from 'react-intl';
import {observer} from 'mobx-react'; import {observer} from 'mobx-react';
import {useLingui} from '@lingui/react';
import {compute, getTranslationString} from '../../util/size'; import {compute, getTranslationString} from '../../util/size';
import TransferDataStore from '../../stores/TransferDataStore'; import TransferDataStore from '../../stores/TransferDataStore';
const WindowTitle: FC = observer(() => { const WindowTitle: FC = observer(() => {
const {transferSummary: summary} = TransferDataStore; const {transferSummary: summary} = TransferDataStore;
const intl = useIntl(); const {i18n} = useLingui();
let title = 'Flood'; let title = 'Flood';
@@ -15,25 +15,15 @@ const WindowTitle: FC = observer(() => {
const down = compute(summary.downRate); const down = compute(summary.downRate);
const up = compute(summary.upRate); const up = compute(summary.upRate);
const formattedDownSpeed = intl.formatNumber(down.value); const formattedDownSpeed = i18n.number(down.value);
const formattedUpSpeed = intl.formatNumber(up.value); const formattedUpSpeed = i18n.number(up.value);
const translatedDownUnit = intl.formatMessage( const translatedDownUnit = i18n._('unit.speed', {
{ baseUnit: i18n._(getTranslationString(down.unit)),
id: 'unit.speed', });
}, const translatedUpUnit = i18n._('unit.speed', {
{ baseUnit: i18n._(getTranslationString(up.unit)),
baseUnit: intl.formatMessage({id: getTranslationString(down.unit)}), });
},
);
const translatedUpUnit = intl.formatMessage(
{
id: 'unit.speed',
},
{
baseUnit: intl.formatMessage({id: getTranslationString(up.unit)}),
},
);
title = `${formattedDownSpeed} ${translatedDownUnit}${formattedUpSpeed} ${translatedUpUnit} - Flood`; title = `${formattedDownSpeed} ${translatedDownUnit}${formattedUpSpeed} ${translatedUpUnit} - Flood`;
} }

View File

@@ -1,5 +1,5 @@
import {FC, ReactNode, useEffect, useState} from 'react'; import {FC, ReactNode, useEffect, useState} from 'react';
import {FormattedMessage, useIntl} from 'react-intl'; import {Trans, useLingui} from '@lingui/react';
import {FormRow, Select, SelectItem} from '@client/ui'; import {FormRow, Select, SelectItem} from '@client/ui';
@@ -20,7 +20,7 @@ interface ClientConnectionSettingsFormProps {
const ClientConnectionSettingsForm: FC<ClientConnectionSettingsFormProps> = ({ const ClientConnectionSettingsForm: FC<ClientConnectionSettingsFormProps> = ({
onSettingsChange, onSettingsChange,
}: ClientConnectionSettingsFormProps) => { }: ClientConnectionSettingsFormProps) => {
const intl = useIntl(); const {i18n} = useLingui();
const [selectedClient, setSelectedClient] = useState<ClientConnectionSettings['client']>(DEFAULT_SELECTION); const [selectedClient, setSelectedClient] = useState<ClientConnectionSettings['client']>(DEFAULT_SELECTION);
useEffect(() => { useEffect(() => {
@@ -47,16 +47,14 @@ const ClientConnectionSettingsForm: FC<ClientConnectionSettingsFormProps> = ({
<FormRow> <FormRow>
<Select <Select
id="client" id="client"
label={intl.formatMessage({ label={i18n._('connection.settings.client.select')}
id: 'connection.settings.client.select',
})}
onSelect={(newSelectedClient) => { onSelect={(newSelectedClient) => {
setSelectedClient(newSelectedClient as ClientConnectionSettings['client']); setSelectedClient(newSelectedClient as ClientConnectionSettings['client']);
}} }}
defaultID={DEFAULT_SELECTION}> defaultID={DEFAULT_SELECTION}>
{SUPPORTED_CLIENTS.map((client) => ( {SUPPORTED_CLIENTS.map((client) => (
<SelectItem key={client} id={client}> <SelectItem key={client} id={client}>
<FormattedMessage id={`connection.settings.${client.toLowerCase()}`} /> <Trans id={`connection.settings.${client.toLowerCase()}`} />
</SelectItem> </SelectItem>
))} ))}
</Select> </Select>

View File

@@ -1,5 +1,5 @@
import {FC, useState} from 'react'; import {FC, useState} from 'react';
import {FormattedMessage, useIntl} from 'react-intl'; import {Trans, useLingui} from '@lingui/react';
import {FormGroup, FormRow, Textbox} from '@client/ui'; import {FormGroup, FormRow, Textbox} from '@client/ui';
@@ -12,7 +12,7 @@ export interface QBittorrentConnectionSettingsProps {
const QBittorrentConnectionSettingsForm: FC<QBittorrentConnectionSettingsProps> = ({ const QBittorrentConnectionSettingsForm: FC<QBittorrentConnectionSettingsProps> = ({
onSettingsChange, onSettingsChange,
}: QBittorrentConnectionSettingsProps) => { }: QBittorrentConnectionSettingsProps) => {
const intl = useIntl(); const {i18n} = useLingui();
const [settings, setSettings] = useState<QBittorrentConnectionSettings>({ const [settings, setSettings] = useState<QBittorrentConnectionSettings>({
client: 'qBittorrent', client: 'qBittorrent',
type: 'web', type: 'web',
@@ -44,29 +44,23 @@ const QBittorrentConnectionSettingsForm: FC<QBittorrentConnectionSettingsProps>
<Textbox <Textbox
onChange={(e) => handleFormChange('url', e.target.value)} onChange={(e) => handleFormChange('url', e.target.value)}
id="url" id="url"
label={<FormattedMessage id="connection.settings.qbittorrent.url" />} label={<Trans id="connection.settings.qbittorrent.url" />}
placeholder={intl.formatMessage({ placeholder={i18n._('connection.settings.qbittorrent.url.input.placeholder')}
id: 'connection.settings.qbittorrent.url.input.placeholder',
})}
/> />
</FormRow> </FormRow>
<FormRow> <FormRow>
<Textbox <Textbox
onChange={(e) => handleFormChange('username', e.target.value)} onChange={(e) => handleFormChange('username', e.target.value)}
id="qbt-username" id="qbt-username"
label={<FormattedMessage id="connection.settings.qbittorrent.username" />} label={<Trans id="connection.settings.qbittorrent.username" />}
placeholder={intl.formatMessage({ placeholder={i18n._('connection.settings.qbittorrent.username.input.placeholder')}
id: 'connection.settings.qbittorrent.username.input.placeholder',
})}
autoComplete="off" autoComplete="off"
/> />
<Textbox <Textbox
onChange={(e) => handleFormChange('password', e.target.value)} onChange={(e) => handleFormChange('password', e.target.value)}
id="qbt-password" id="qbt-password"
label={<FormattedMessage id="connection.settings.qbittorrent.password" />} label={<Trans id="connection.settings.qbittorrent.password" />}
placeholder={intl.formatMessage({ placeholder={i18n._('connection.settings.qbittorrent.password.input.placeholder')}
id: 'connection.settings.qbittorrent.password.input.placeholder',
})}
autoComplete="off" autoComplete="off"
type="password" type="password"
/> />

View File

@@ -1,5 +1,5 @@
import {FC, useState} from 'react'; import {FC, useState} from 'react';
import {FormattedMessage, useIntl} from 'react-intl'; import {Trans, useLingui} from '@lingui/react';
import {FormError, FormGroup, FormRow, FormRowGroup, Radio, Textbox} from '@client/ui'; import {FormError, FormGroup, FormRow, FormRowGroup, Radio, Textbox} from '@client/ui';
@@ -12,7 +12,7 @@ export interface RTorrentConnectionSettingsProps {
const RTorrentConnectionSettingsForm: FC<RTorrentConnectionSettingsProps> = ({ const RTorrentConnectionSettingsForm: FC<RTorrentConnectionSettingsProps> = ({
onSettingsChange, onSettingsChange,
}: RTorrentConnectionSettingsProps) => { }: RTorrentConnectionSettingsProps) => {
const intl = useIntl(); const {i18n} = useLingui();
const [type, setType] = useState<'tcp' | 'socket'>('socket'); const [type, setType] = useState<'tcp' | 'socket'>('socket');
const [settings, setSettings] = useState<RTorrentConnectionSettings | null>(null); const [settings, setSettings] = useState<RTorrentConnectionSettings | null>(null);
@@ -47,10 +47,7 @@ const RTorrentConnectionSettingsForm: FC<RTorrentConnectionSettingsProps> = ({
<FormRow> <FormRow>
<FormGroup> <FormGroup>
<FormRow> <FormRow>
<FormGroup <FormGroup label={i18n._('connection.settings.rtorrent.type')}>
label={intl.formatMessage({
id: 'connection.settings.rtorrent.type',
})}>
<FormRow> <FormRow>
<Radio <Radio
onClick={() => { onClick={() => {
@@ -60,7 +57,7 @@ const RTorrentConnectionSettingsForm: FC<RTorrentConnectionSettingsProps> = ({
id="socket" id="socket"
grow={false} grow={false}
defaultChecked={type === 'socket'}> defaultChecked={type === 'socket'}>
<FormattedMessage id="connection.settings.rtorrent.type.socket" /> <Trans id="connection.settings.rtorrent.type.socket" />
</Radio> </Radio>
<Radio <Radio
onClick={() => { onClick={() => {
@@ -70,7 +67,7 @@ const RTorrentConnectionSettingsForm: FC<RTorrentConnectionSettingsProps> = ({
id="tcp" id="tcp"
grow={false} grow={false}
defaultChecked={type === 'tcp'}> defaultChecked={type === 'tcp'}>
<FormattedMessage id="connection.settings.rtorrent.type.tcp" /> <Trans id="connection.settings.rtorrent.type.tcp" />
</Radio> </Radio>
</FormRow> </FormRow>
</FormGroup> </FormGroup>
@@ -78,28 +75,20 @@ const RTorrentConnectionSettingsForm: FC<RTorrentConnectionSettingsProps> = ({
{type === 'tcp' ? ( {type === 'tcp' ? (
<FormRowGroup> <FormRowGroup>
<FormRow> <FormRow>
<FormError> <FormError>{i18n._('connection.settings.rtorrent.type.tcp.warning')}</FormError>
{intl.formatMessage({
id: 'connection.settings.rtorrent.type.tcp.warning',
})}
</FormError>
</FormRow> </FormRow>
<FormRow> <FormRow>
<Textbox <Textbox
onChange={(e) => handleFormChange('host', e.target.value)} onChange={(e) => handleFormChange('host', e.target.value)}
id="host" id="host"
label={<FormattedMessage id="connection.settings.rtorrent.host" />} label={<Trans id="connection.settings.rtorrent.host" />}
placeholder={intl.formatMessage({ placeholder={i18n._('connection.settings.rtorrent.host.input.placeholder')}
id: 'connection.settings.rtorrent.host.input.placeholder',
})}
/> />
<Textbox <Textbox
onChange={(e) => handleFormChange('port', Number(e.target.value))} onChange={(e) => handleFormChange('port', Number(e.target.value))}
id="port" id="port"
label={<FormattedMessage id="connection.settings.rtorrent.port" />} label={<Trans id="connection.settings.rtorrent.port" />}
placeholder={intl.formatMessage({ placeholder={i18n._('connection.settings.rtorrent.port.input.placeholder')}
id: 'connection.settings.rtorrent.port.input.placeholder',
})}
/> />
</FormRow> </FormRow>
</FormRowGroup> </FormRowGroup>
@@ -108,10 +97,8 @@ const RTorrentConnectionSettingsForm: FC<RTorrentConnectionSettingsProps> = ({
<Textbox <Textbox
onChange={(e) => handleFormChange('socket', e.target.value)} onChange={(e) => handleFormChange('socket', e.target.value)}
id="socket" id="socket"
label={<FormattedMessage id="connection.settings.rtorrent.socket" />} label={<Trans id="connection.settings.rtorrent.socket" />}
placeholder={intl.formatMessage({ placeholder={i18n._('connection.settings.rtorrent.socket.input.placeholder')}
id: 'connection.settings.rtorrent.socket.input.placeholder',
})}
/> />
</FormRow> </FormRow>
)} )}

View File

@@ -1,5 +1,5 @@
import {FC, useState} from 'react'; import {FC, useState} from 'react';
import {FormattedMessage, useIntl} from 'react-intl'; import {Trans, useLingui} from '@lingui/react';
import {FormGroup, FormRow, Textbox} from '@client/ui'; import {FormGroup, FormRow, Textbox} from '@client/ui';
@@ -12,7 +12,7 @@ export interface TransmissionConnectionSettingsProps {
const TransmissionConnectionSettingsForm: FC<TransmissionConnectionSettingsProps> = ({ const TransmissionConnectionSettingsForm: FC<TransmissionConnectionSettingsProps> = ({
onSettingsChange, onSettingsChange,
}: TransmissionConnectionSettingsProps) => { }: TransmissionConnectionSettingsProps) => {
const intl = useIntl(); const {i18n} = useLingui();
const [settings, setSettings] = useState<TransmissionConnectionSettings>({ const [settings, setSettings] = useState<TransmissionConnectionSettings>({
client: 'Transmission', client: 'Transmission',
type: 'rpc', type: 'rpc',
@@ -44,29 +44,23 @@ const TransmissionConnectionSettingsForm: FC<TransmissionConnectionSettingsProps
<Textbox <Textbox
onChange={(e) => handleFormChange('url', e.target.value)} onChange={(e) => handleFormChange('url', e.target.value)}
id="url" id="url"
label={<FormattedMessage id="connection.settings.transmission.url" />} label={<Trans id="connection.settings.transmission.url" />}
placeholder={intl.formatMessage({ placeholder={i18n._('connection.settings.transmission.url.input.placeholder')}
id: 'connection.settings.transmission.url.input.placeholder',
})}
/> />
</FormRow> </FormRow>
<FormRow> <FormRow>
<Textbox <Textbox
onChange={(e) => handleFormChange('username', e.target.value)} onChange={(e) => handleFormChange('username', e.target.value)}
id="transmission-username" id="transmission-username"
label={<FormattedMessage id="connection.settings.transmission.username" />} label={<Trans id="connection.settings.transmission.username" />}
placeholder={intl.formatMessage({ placeholder={i18n._('connection.settings.transmission.username.input.placeholder')}
id: 'connection.settings.transmission.username.input.placeholder',
})}
autoComplete="off" autoComplete="off"
/> />
<Textbox <Textbox
onChange={(e) => handleFormChange('password', e.target.value)} onChange={(e) => handleFormChange('password', e.target.value)}
id="transmission-password" id="transmission-password"
label={<FormattedMessage id="connection.settings.transmission.password" />} label={<Trans id="connection.settings.transmission.password" />}
placeholder={intl.formatMessage({ placeholder={i18n._('connection.settings.transmission.password.input.placeholder')}
id: 'connection.settings.transmission.password.input.placeholder',
})}
autoComplete="off" autoComplete="off"
type="password" type="password"
/> />

View File

@@ -1,28 +1,15 @@
import {defineMessages, WrappedComponentProps} from 'react-intl';
import {PureComponent, ReactNodeArray} from 'react'; import {PureComponent, ReactNodeArray} from 'react';
import {Trans} from '@lingui/react';
import {Arrow, File, FolderClosedSolid} from '@client/ui/icons'; import {Arrow, File, FolderClosedSolid} from '@client/ui/icons';
import FloodActions from '@client/actions/FloodActions'; import FloodActions from '@client/actions/FloodActions';
const MESSAGES = defineMessages({ const MESSAGES = {
EACCES: { EACCES: 'filesystem.error.eacces',
id: 'filesystem.error.eacces', ENOENT: 'filesystem.error.enoent',
}, };
ENOENT: {
id: 'filesystem.error.enoent',
},
emptyDirectory: {
id: 'filesystem.empty.directory',
},
fetching: {
id: 'filesystem.fetching',
},
unknownError: {
id: 'filesystem.error.unknown',
},
});
interface FilesystemBrowserProps extends WrappedComponentProps { interface FilesystemBrowserProps {
selectable?: 'files' | 'directories'; selectable?: 'files' | 'directories';
directory: string; directory: string;
onItemSelection?: (newDestination: string, isDirectory?: boolean) => void; onItemSelection?: (newDestination: string, isDirectory?: boolean) => void;
@@ -108,7 +95,7 @@ class FilesystemBrowser extends PureComponent<FilesystemBrowserProps, Filesystem
}; };
render() { render() {
const {intl, selectable} = this.props; const {selectable} = this.props;
const {directories, errorResponse, files} = this.state; const {directories, errorResponse, files} = this.state;
let errorMessage = null; let errorMessage = null;
let listItems = null; let listItems = null;
@@ -119,7 +106,9 @@ class FilesystemBrowser extends PureComponent<FilesystemBrowserProps, Filesystem
shouldShowDirectoryList = false; shouldShowDirectoryList = false;
errorMessage = ( errorMessage = (
<div className="filesystem__directory-list__item filesystem__directory-list__item--message"> <div className="filesystem__directory-list__item filesystem__directory-list__item--message">
<em>{intl.formatMessage(MESSAGES.fetching)}</em> <em>
<Trans id="filesystem.fetching" />
</em>
</div> </div>
); );
} }
@@ -127,11 +116,11 @@ class FilesystemBrowser extends PureComponent<FilesystemBrowserProps, Filesystem
if (errorResponse && errorResponse.data && errorResponse.data.code) { if (errorResponse && errorResponse.data && errorResponse.data.code) {
shouldShowDirectoryList = false; shouldShowDirectoryList = false;
const messageConfig = MESSAGES[errorResponse.data.code as keyof typeof MESSAGES] || MESSAGES.unknownError;
errorMessage = ( errorMessage = (
<div className="filesystem__directory-list__item filesystem__directory-list__item--message"> <div className="filesystem__directory-list__item filesystem__directory-list__item--message">
<em>{intl.formatMessage(messageConfig)}</em> <em>
<Trans id={MESSAGES[errorResponse.data.code as keyof typeof MESSAGES] || 'filesystem.error.unknown'} />
</em>
</div> </div>
); );
} }
@@ -141,9 +130,7 @@ class FilesystemBrowser extends PureComponent<FilesystemBrowserProps, Filesystem
className="filesystem__directory-list__item filesystem__directory-list__item--parent" className="filesystem__directory-list__item filesystem__directory-list__item--parent"
onClick={this.handleParentDirectoryClick}> onClick={this.handleParentDirectoryClick}>
<Arrow /> <Arrow />
{intl.formatMessage({ <Trans id="filesystem.parent.directory" />
id: 'filesystem.parent.directory',
})}
</li> </li>
); );
@@ -188,7 +175,9 @@ class FilesystemBrowser extends PureComponent<FilesystemBrowserProps, Filesystem
if ((!listItems || listItems.length === 0) && !errorMessage) { if ((!listItems || listItems.length === 0) && !errorMessage) {
errorMessage = ( errorMessage = (
<div className="filesystem__directory-list__item filesystem__directory-list__item--message"> <div className="filesystem__directory-list__item filesystem__directory-list__item--message">
<em>{intl.formatMessage(MESSAGES.emptyDirectory)}</em> <em>
<Trans id="filesystem.empty.directory" />
</em>
</div> </div>
); );
} }

View File

@@ -1,6 +1,6 @@
import Dropzone from 'react-dropzone'; import Dropzone from 'react-dropzone';
import {FormattedMessage} from 'react-intl';
import {FC, useEffect, useState} from 'react'; import {FC, useEffect, useState} from 'react';
import {Trans} from '@lingui/react';
import {Close, File, Files} from '@client/ui/icons'; import {Close, File, Files} from '@client/ui/icons';
import {FormRowItem} from '@client/ui'; import {FormRowItem} from '@client/ui';
@@ -21,7 +21,7 @@ const FileDropzone: FC<FileDropzoneProps> = ({onFilesChanged}: FileDropzoneProps
return ( return (
<FormRowItem> <FormRowItem>
<label className="form__element__label"> <label className="form__element__label">
<FormattedMessage id="torrents.add.torrents.label" /> <Trans id="torrents.add.torrents.label" />
</label> </label>
{files.length > 0 ? ( {files.length > 0 ? (
<ul <ul
@@ -76,9 +76,9 @@ const FileDropzone: FC<FileDropzoneProps> = ({onFilesChanged}: FileDropzoneProps
<div className="dropzone__icon"> <div className="dropzone__icon">
<Files /> <Files />
</div> </div>
<FormattedMessage id="torrents.add.tab.file.drop" />{' '} <Trans id="torrents.add.tab.file.drop" />{' '}
<span className="dropzone__browse-button"> <span className="dropzone__browse-button">
<FormattedMessage id="torrents.add.tab.file.browse" /> <Trans id="torrents.add.tab.file.browse" />
</span> </span>
. .
</div> </div>

View File

@@ -1,6 +1,6 @@
import debounce from 'lodash/debounce'; import debounce from 'lodash/debounce';
import {FormattedMessage, useIntl} from 'react-intl';
import {forwardRef, MutableRefObject, ReactNode, useEffect, useRef, useState} from 'react'; import {forwardRef, MutableRefObject, ReactNode, useEffect, useRef, useState} from 'react';
import {Trans, useLingui} from '@lingui/react';
import {useEnsuredForwardedRef} from 'react-use'; import {useEnsuredForwardedRef} from 'react-use';
import {Checkbox, ContextMenu, FormElementAddon, FormRow, FormRowGroup, Portal, Textbox} from '@client/ui'; import {Checkbox, ContextMenu, FormElementAddon, FormRow, FormRowGroup, Portal, Textbox} from '@client/ui';
@@ -45,7 +45,7 @@ const FilesystemBrowserTextbox = forwardRef<HTMLInputElement, FilesystemBrowserT
const formRowRef = useRef<HTMLDivElement>(null); const formRowRef = useRef<HTMLDivElement>(null);
const textboxRef = useEnsuredForwardedRef(ref as MutableRefObject<HTMLInputElement>); const textboxRef = useEnsuredForwardedRef(ref as MutableRefObject<HTMLInputElement>);
const intl = useIntl(); const {i18n} = useLingui();
useEffect(() => { useEffect(() => {
const closeDirectoryList = (): void => { const closeDirectoryList = (): void => {
@@ -71,14 +71,14 @@ const FilesystemBrowserTextbox = forwardRef<HTMLInputElement, FilesystemBrowserT
if (showBasePathToggle) { if (showBasePathToggle) {
toggles.push( toggles.push(
<Checkbox grow={false} id="isBasePath" key="isBasePath"> <Checkbox grow={false} id="isBasePath" key="isBasePath">
<FormattedMessage id="torrents.destination.base_path" /> <Trans id="torrents.destination.base_path" />
</Checkbox>, </Checkbox>,
); );
} }
if (showCompletedToggle) { if (showCompletedToggle) {
toggles.push( toggles.push(
<Checkbox grow={false} id="isCompleted" key="isCompleted"> <Checkbox grow={false} id="isCompleted" key="isCompleted">
<FormattedMessage id="torrents.destination.completed" /> <Trans id="torrents.destination.completed" />
</Checkbox>, </Checkbox>,
); );
} }
@@ -86,7 +86,7 @@ const FilesystemBrowserTextbox = forwardRef<HTMLInputElement, FilesystemBrowserT
// TODO: this is getting bloated. toggles can be moved to their own elements... // TODO: this is getting bloated. toggles can be moved to their own elements...
toggles.push( toggles.push(
<Checkbox grow={false} id="isSequential" key="isSequential"> <Checkbox grow={false} id="isSequential" key="isSequential">
<FormattedMessage id="torrents.destination.sequential" /> <Trans id="torrents.destination.sequential" />
</Checkbox>, </Checkbox>,
); );
} }
@@ -118,9 +118,7 @@ const FilesystemBrowserTextbox = forwardRef<HTMLInputElement, FilesystemBrowserT
{leading: true}, {leading: true},
)} )}
onClick={(event) => event.nativeEvent.stopImmediatePropagation()} onClick={(event) => event.nativeEvent.stopImmediatePropagation()}
placeholder={intl.formatMessage({ placeholder={i18n._('torrents.add.destination.placeholder')}
id: 'torrents.add.destination.placeholder',
})}
ref={textboxRef}> ref={textboxRef}>
<FormElementAddon <FormElementAddon
onClick={() => { onClick={() => {
@@ -140,7 +138,6 @@ const FilesystemBrowserTextbox = forwardRef<HTMLInputElement, FilesystemBrowserT
triggerRef={textboxRef}> triggerRef={textboxRef}>
<FilesystemBrowser <FilesystemBrowser
directory={destination} directory={destination}
intl={intl}
selectable={selectable} selectable={selectable}
onItemSelection={(newDestination: string, isDirectory = true) => { onItemSelection={(newDestination: string, isDirectory = true) => {
if (textboxRef.current != null) { if (textboxRef.current != null) {

View File

@@ -1,7 +1,7 @@
import classnames from 'classnames'; import classnames from 'classnames';
import {FC, ReactNode, ReactNodeArray, useEffect, useRef, useState} from 'react'; import {FC, ReactNode, ReactNodeArray, useEffect, useRef, useState} from 'react';
import {FormattedMessage} from 'react-intl';
import sort from 'fast-sort'; import sort from 'fast-sort';
import {Trans} from '@lingui/react';
import {useKeyPressEvent} from 'react-use'; import {useKeyPressEvent} from 'react-use';
import {ContextMenu, FormElementAddon, FormRowItem, Portal, SelectItem, Textbox} from '@client/ui'; import {ContextMenu, FormElementAddon, FormRowItem, Portal, SelectItem, Textbox} from '@client/ui';
@@ -130,7 +130,7 @@ const TagSelect: FC<TagSelectProps> = ({defaultValue, placeholder, id, label, on
setSelectedTags([...selectedTags.filter((key) => key !== ''), tag]); setSelectedTags([...selectedTags.filter((key) => key !== ''), tag]);
} }
}}> }}>
{tag === 'untagged' ? <FormattedMessage id="filter.untagged" /> : tag} {tag === 'untagged' ? <Trans id="filter.untagged" /> : tag}
</SelectItem>, </SelectItem>,
); );
return accumulator; return accumulator;

View File

@@ -1,5 +1,5 @@
import {FC} from 'react'; import {FC} from 'react';
import {useIntl} from 'react-intl'; import {useLingui} from '@lingui/react';
import ModalActions from '../ModalActions'; import ModalActions from '../ModalActions';
import SettingStore from '../../../stores/SettingStore'; import SettingStore from '../../../stores/SettingStore';
@@ -13,33 +13,27 @@ const AddTorrentsActions: FC<AddTorrentsActionsProps> = ({
isAddingTorrents, isAddingTorrents,
onAddTorrentsClick, onAddTorrentsClick,
}: AddTorrentsActionsProps) => { }: AddTorrentsActionsProps) => {
const intl = useIntl(); const {i18n} = useLingui();
return ( return (
<ModalActions <ModalActions
actions={[ actions={[
{ {
checked: Boolean(SettingStore.floodSettings.startTorrentsOnLoad), checked: Boolean(SettingStore.floodSettings.startTorrentsOnLoad),
clickHandler: null, clickHandler: null,
content: intl.formatMessage({ content: i18n._('torrents.add.start.label'),
id: 'torrents.add.start.label',
}),
id: 'start', id: 'start',
triggerDismiss: false, triggerDismiss: false,
type: 'checkbox', type: 'checkbox',
}, },
{ {
clickHandler: null, clickHandler: null,
content: intl.formatMessage({ content: i18n._('button.cancel'),
id: 'button.cancel',
}),
triggerDismiss: true, triggerDismiss: true,
type: 'tertiary', type: 'tertiary',
}, },
{ {
clickHandler: onAddTorrentsClick, clickHandler: onAddTorrentsClick,
content: intl.formatMessage({ content: i18n._('torrents.add.button.add'),
id: 'torrents.add.button.add',
}),
isLoading: isAddingTorrents, isLoading: isAddingTorrents,
submit: true, submit: true,
triggerDismiss: false, triggerDismiss: false,

View File

@@ -1,5 +1,5 @@
import {FC, useRef, useState} from 'react'; import {FC, useRef, useState} from 'react';
import {useIntl} from 'react-intl'; import {useLingui} from '@lingui/react';
import {Checkbox, Form, FormRow, Textbox} from '@client/ui'; import {Checkbox, Form, FormRow, Textbox} from '@client/ui';
import {saveAddTorrentsUserPreferences} from '@client/util/userPreferences'; import {saveAddTorrentsUserPreferences} from '@client/util/userPreferences';
@@ -25,74 +25,49 @@ type AddTorrentsByCreationFormData = {
const AddTorrentsByCreation: FC = () => { const AddTorrentsByCreation: FC = () => {
const formRef = useRef<Form>(null); const formRef = useRef<Form>(null);
const intl = useIntl(); const {i18n} = useLingui();
const [isCreatingTorrents, setIsCreatingTorrents] = useState<boolean>(false); const [isCreatingTorrents, setIsCreatingTorrents] = useState<boolean>(false);
return ( return (
<Form className="inverse" ref={formRef}> <Form className="inverse" ref={formRef}>
<FilesystemBrowserTextbox <FilesystemBrowserTextbox id="sourcePath" label={i18n._('torrents.create.source.path.label')} />
id="sourcePath"
label={intl.formatMessage({
id: 'torrents.create.source.path.label',
})}
/>
<TextboxRepeater <TextboxRepeater
id="trackers" id="trackers"
label={intl.formatMessage({ label={i18n._('torrents.create.trackers.label')}
id: 'torrents.create.trackers.label', placeholder={i18n._('torrents.create.tracker.input.placeholder')}
})}
placeholder={intl.formatMessage({
id: 'torrents.create.tracker.input.placeholder',
})}
defaultValues={[{id: 0, value: ''}]} defaultValues={[{id: 0, value: ''}]}
/> />
<FormRow> <FormRow>
<Textbox <Textbox
id="name" id="name"
label={intl.formatMessage({ label={i18n._('torrents.create.base.name.label')}
id: 'torrents.create.base.name.label', placeholder={i18n._('torrents.create.base.name.input.placeholder')}
})}
placeholder={intl.formatMessage({
id: 'torrents.create.base.name.input.placeholder',
})}
/> />
</FormRow> </FormRow>
<FormRow> <FormRow>
<Textbox <Textbox
id="comment" id="comment"
label={intl.formatMessage({ label={i18n._('torrents.create.comment.label')}
id: 'torrents.create.comment.label', placeholder={i18n._('torrents.create.comment.input.placeholder')}
})}
placeholder={intl.formatMessage({
id: 'torrents.create.comment.input.placeholder',
})}
/> />
</FormRow> </FormRow>
<FormRow> <FormRow>
<Textbox <Textbox
id="infoSource" id="infoSource"
label={intl.formatMessage({ label={i18n._('torrents.create.info.source.label')}
id: 'torrents.create.info.source.label', placeholder={i18n._('torrents.create.info.source.input.placeholder')}
})}
placeholder={intl.formatMessage({
id: 'torrents.create.info.source.input.placeholder',
})}
/> />
</FormRow> </FormRow>
<FormRow> <FormRow>
<Checkbox grow={false} id="isPrivate"> <Checkbox grow={false} id="isPrivate">
{intl.formatMessage({id: 'torrents.create.is.private.label'})} {i18n._('torrents.create.is.private.label')}
</Checkbox> </Checkbox>
</FormRow> </FormRow>
<FormRow> <FormRow>
<TagSelect <TagSelect
id="tags" id="tags"
label={intl.formatMessage({ label={i18n._('torrents.add.tags')}
id: 'torrents.add.tags', placeholder={i18n._('torrents.create.tags.input.placeholder')}
})}
placeholder={intl.formatMessage({
id: 'torrents.create.tags.input.placeholder',
})}
/> />
</FormRow> </FormRow>
<AddTorrentsActions <AddTorrentsActions

View File

@@ -1,5 +1,5 @@
import {FC, useRef, useState} from 'react'; import {FC, useRef, useState} from 'react';
import {useIntl} from 'react-intl'; import {useLingui} from '@lingui/react';
import {Form, FormRow} from '@client/ui'; import {Form, FormRow} from '@client/ui';
import {saveAddTorrentsUserPreferences} from '@client/util/userPreferences'; import {saveAddTorrentsUserPreferences} from '@client/util/userPreferences';
@@ -29,7 +29,7 @@ const AddTorrentsByFile: FC = () => {
const textboxRef = useRef<HTMLInputElement>(null); const textboxRef = useRef<HTMLInputElement>(null);
const [isAddingTorrents, setIsAddingTorrents] = useState<boolean>(false); const [isAddingTorrents, setIsAddingTorrents] = useState<boolean>(false);
const intl = useIntl(); const {i18n} = useLingui();
return ( return (
<Form className="inverse" ref={formRef}> <Form className="inverse" ref={formRef}>
@@ -42,9 +42,7 @@ const AddTorrentsByFile: FC = () => {
</FormRow> </FormRow>
<FormRow> <FormRow>
<TagSelect <TagSelect
label={intl.formatMessage({ label={i18n._('torrents.add.tags')}
id: 'torrents.add.tags',
})}
id="tags" id="tags"
onTagSelected={(tags) => { onTagSelected={(tags) => {
if (textboxRef.current != null) { if (textboxRef.current != null) {
@@ -59,9 +57,7 @@ const AddTorrentsByFile: FC = () => {
</FormRow> </FormRow>
<FilesystemBrowserTextbox <FilesystemBrowserTextbox
id="destination" id="destination"
label={intl.formatMessage({ label={i18n._('torrents.add.destination.label')}
id: 'torrents.add.destination.label',
})}
ref={textboxRef} ref={textboxRef}
selectable="directories" selectable="directories"
showBasePathToggle showBasePathToggle

View File

@@ -1,5 +1,5 @@
import {FC, useRef, useState} from 'react'; import {FC, useRef, useState} from 'react';
import {useIntl} from 'react-intl'; import {useLingui} from '@lingui/react';
import ConfigStore from '@client/stores/ConfigStore'; import ConfigStore from '@client/stores/ConfigStore';
import {Form, FormRow} from '@client/ui'; import {Form, FormRow} from '@client/ui';
@@ -31,7 +31,7 @@ const AddTorrentsByURL: FC = () => {
const textboxRef = useRef<HTMLInputElement>(null); const textboxRef = useRef<HTMLInputElement>(null);
const [isAddingTorrents, setIsAddingTorrents] = useState<boolean>(false); const [isAddingTorrents, setIsAddingTorrents] = useState<boolean>(false);
const intl = useIntl(); const {i18n} = useLingui();
return ( return (
<Form className="inverse" ref={formRef}> <Form className="inverse" ref={formRef}>
@@ -39,9 +39,7 @@ const AddTorrentsByURL: FC = () => {
id="urls" id="urls"
label={ label={
<div> <div>
{intl.formatMessage({ {i18n._('torrents.add.torrents.label')}
id: 'torrents.add.torrents.label',
})}
{typeof navigator.registerProtocolHandler === 'function' && ( {typeof navigator.registerProtocolHandler === 'function' && (
<em <em
style={{cursor: 'pointer', fontSize: '0.8em', float: 'right'}} style={{cursor: 'pointer', fontSize: '0.8em', float: 'right'}}
@@ -54,35 +52,25 @@ const AddTorrentsByURL: FC = () => {
); );
} }
}}> }}>
{intl.formatMessage({ {i18n._('torrents.add.tab.url.register.magnet.handler')}
id: 'torrents.add.tab.url.register.magnet.handler',
})}
</em> </em>
)} )}
</div> </div>
} }
placeholder={intl.formatMessage({ placeholder={i18n._('torrents.add.tab.url.input.placeholder')}
id: 'torrents.add.tab.url.input.placeholder',
})}
defaultValues={ defaultValues={
(UIStore.activeModal?.id === 'add-torrents' && UIStore.activeModal?.initialURLs) || [{id: 0, value: ''}] (UIStore.activeModal?.id === 'add-torrents' && UIStore.activeModal?.initialURLs) || [{id: 0, value: ''}]
} }
/> />
<TextboxRepeater <TextboxRepeater
id="cookies" id="cookies"
label={intl.formatMessage({ label={i18n._('torrents.add.cookies.label')}
id: 'torrents.add.cookies.label', placeholder={i18n._('torrents.add.cookies.input.placeholder')}
})}
placeholder={intl.formatMessage({
id: 'torrents.add.cookies.input.placeholder',
})}
/> />
<FormRow> <FormRow>
<TagSelect <TagSelect
id="tags" id="tags"
label={intl.formatMessage({ label={i18n._('torrents.add.tags')}
id: 'torrents.add.tags',
})}
onTagSelected={(tags) => { onTagSelected={(tags) => {
if (textboxRef.current != null) { if (textboxRef.current != null) {
const suggestedPath = SettingStore.floodSettings.torrentDestinations?.[tags[0]]; const suggestedPath = SettingStore.floodSettings.torrentDestinations?.[tags[0]];
@@ -96,9 +84,7 @@ const AddTorrentsByURL: FC = () => {
</FormRow> </FormRow>
<FilesystemBrowserTextbox <FilesystemBrowserTextbox
id="destination" id="destination"
label={intl.formatMessage({ label={i18n._('torrents.add.destination.label')}
id: 'torrents.add.destination.label',
})}
ref={textboxRef} ref={textboxRef}
selectable="directories" selectable="directories"
showBasePathToggle showBasePathToggle

View File

@@ -1,5 +1,5 @@
import {FC} from 'react'; import {FC} from 'react';
import {useIntl} from 'react-intl'; import {useLingui} from '@lingui/react';
import AddTorrentsByCreation from './AddTorrentsByCreation'; import AddTorrentsByCreation from './AddTorrentsByCreation';
import AddTorrentsByFile from './AddTorrentsByFile'; import AddTorrentsByFile from './AddTorrentsByFile';
@@ -9,26 +9,20 @@ import SettingStore from '../../../stores/SettingStore';
import UIStore from '../../../stores/UIStore'; import UIStore from '../../../stores/UIStore';
const AddTorrentsModal: FC = () => { const AddTorrentsModal: FC = () => {
const intl = useIntl(); const {i18n} = useLingui();
const tabs = { const tabs = {
'by-url': { 'by-url': {
content: AddTorrentsByURL, content: AddTorrentsByURL,
label: intl.formatMessage({ label: i18n._('torrents.add.tab.url.title'),
id: 'torrents.add.tab.url.title',
}),
}, },
'by-file': { 'by-file': {
content: AddTorrentsByFile, content: AddTorrentsByFile,
label: intl.formatMessage({ label: i18n._('torrents.add.tab.file.title'),
id: 'torrents.add.tab.file.title',
}),
}, },
'by-creation': { 'by-creation': {
content: AddTorrentsByCreation, content: AddTorrentsByCreation,
label: intl.formatMessage({ label: i18n._('torrents.add.tab.create.title'),
id: 'torrents.add.tab.create.title',
}),
}, },
}; };
@@ -41,15 +35,7 @@ const AddTorrentsModal: FC = () => {
initialTabId = SettingStore.floodSettings.UITorrentsAddTab ?? initialTabId; initialTabId = SettingStore.floodSettings.UITorrentsAddTab ?? initialTabId;
} }
return ( return <Modal heading={i18n._('torrents.add.heading')} tabs={tabs} initialTabId={initialTabId} />;
<Modal
heading={intl.formatMessage({
id: 'torrents.add.heading',
})}
tabs={tabs}
initialTabId={initialTabId}
/>
);
}; };
export default AddTorrentsModal; export default AddTorrentsModal;

View File

@@ -1,5 +1,5 @@
import {FC} from 'react'; import {FC} from 'react';
import {FormattedMessage, useIntl} from 'react-intl'; import {Trans, useLingui} from '@lingui/react';
import { import {
Button, Button,
@@ -34,30 +34,22 @@ const DownloadRuleForm: FC<DownloadRuleFormProps> = ({
onCancel, onCancel,
}: DownloadRuleFormProps) => { }: DownloadRuleFormProps) => {
const {feeds} = FeedStore; const {feeds} = FeedStore;
const intl = useIntl(); const {i18n} = useLingui();
return ( return (
<FormRowGroup> <FormRowGroup>
<FormRow> <FormRow>
<Textbox <Textbox id="label" label={i18n._('feeds.label')} defaultValue={rule.label} />
id="label"
label={intl.formatMessage({
id: 'feeds.label',
})}
defaultValue={rule.label}
/>
<Select <Select
disabled={!feeds.length} disabled={!feeds.length}
id="feedID" id="feedID"
label={intl.formatMessage({ label={i18n._('feeds.applicable.feed')}
id: 'feeds.applicable.feed',
})}
defaultID={rule.feedIDs?.[0]}> defaultID={rule.feedIDs?.[0]}>
{feeds.length === 0 {feeds.length === 0
? [ ? [
<SelectItem key="empty" id="placeholder" isPlaceholder> <SelectItem key="empty" id="placeholder" isPlaceholder>
<em> <em>
<FormattedMessage id="feeds.no.feeds.available" /> <Trans id="feeds.no.feeds.available" />
</em> </em>
</SelectItem>, </SelectItem>,
] ]
@@ -71,7 +63,7 @@ const DownloadRuleForm: FC<DownloadRuleFormProps> = ({
[ [
<SelectItem key="select-feed" id="placeholder" isPlaceholder> <SelectItem key="select-feed" id="placeholder" isPlaceholder>
<em> <em>
<FormattedMessage id="feeds.select.feed" /> <Trans id="feeds.select.feed" />
</em> </em>
</SelectItem>, </SelectItem>,
], ],
@@ -81,23 +73,15 @@ const DownloadRuleForm: FC<DownloadRuleFormProps> = ({
<FormRow> <FormRow>
<Textbox <Textbox
id="match" id="match"
label={intl.formatMessage({ label={i18n._('feeds.match.pattern')}
id: 'feeds.match.pattern', placeholder={i18n._('feeds.regEx')}
})}
placeholder={intl.formatMessage({
id: 'feeds.regEx',
})}
defaultValue={rule.match} defaultValue={rule.match}
width="three-eighths" width="three-eighths"
/> />
<Textbox <Textbox
id="exclude" id="exclude"
label={intl.formatMessage({ label={i18n._('feeds.exclude.pattern')}
id: 'feeds.exclude.pattern', placeholder={i18n._('feeds.regEx')}
})}
placeholder={intl.formatMessage({
id: 'feeds.regEx',
})}
defaultValue={rule.exclude} defaultValue={rule.exclude}
width="three-eighths" width="three-eighths"
/> />
@@ -106,12 +90,8 @@ const DownloadRuleForm: FC<DownloadRuleFormProps> = ({
<Textbox <Textbox
addonPlacement="after" addonPlacement="after"
id="check" id="check"
label={intl.formatMessage({ label={i18n._('feeds.test.match')}
id: 'feeds.test.match', placeholder={i18n._('feeds.check')}>
})}
placeholder={intl.formatMessage({
id: 'feeds.check',
})}>
{isPatternMatched && ( {isPatternMatched && (
<FormElementAddon> <FormElementAddon>
<CheckmarkThick /> <CheckmarkThick />
@@ -123,9 +103,7 @@ const DownloadRuleForm: FC<DownloadRuleFormProps> = ({
<FormRowItem> <FormRowItem>
<FilesystemBrowserTextbox <FilesystemBrowserTextbox
id="destination" id="destination"
label={intl.formatMessage({ label={i18n._('feeds.torrent.destination')}
id: 'feeds.torrent.destination',
})}
selectable="directories" selectable="directories"
suggested={rule.destination} suggested={rule.destination}
showBasePathToggle showBasePathToggle
@@ -133,25 +111,21 @@ const DownloadRuleForm: FC<DownloadRuleFormProps> = ({
</FormRowItem> </FormRowItem>
<TagSelect <TagSelect
id="tags" id="tags"
label={intl.formatMessage({ label={i18n._('feeds.apply.tags')}
id: 'feeds.apply.tags', placeholder={i18n._('feeds.tags')}
})}
placeholder={intl.formatMessage({
id: 'feeds.tags',
})}
defaultValue={rule.tags} defaultValue={rule.tags}
/> />
</FormRow> </FormRow>
<FormRow> <FormRow>
<br /> <br />
<Checkbox id="startOnLoad" defaultChecked={rule.startOnLoad} matchTextboxHeight> <Checkbox id="startOnLoad" defaultChecked={rule.startOnLoad} matchTextboxHeight>
<FormattedMessage id="feeds.start.on.load" /> <Trans id="feeds.start.on.load" />
</Checkbox> </Checkbox>
<Button onClick={onCancel}> <Button onClick={onCancel}>
<FormattedMessage id="button.cancel" /> <Trans id="button.cancel" />
</Button> </Button>
<Button type="submit" isLoading={isSubmitting}> <Button type="submit" isLoading={isSubmitting}>
<FormattedMessage id="button.save.feed" /> <Trans id="button.save.feed" />
</Button> </Button>
</FormRow> </FormRow>
</FormRowGroup> </FormRowGroup>

View File

@@ -1,6 +1,6 @@
import {FC} from 'react'; import {FC} from 'react';
import {FormattedMessage} from 'react-intl';
import {observer} from 'mobx-react'; import {observer} from 'mobx-react';
import {Trans} from '@lingui/react';
import {Close, Edit} from '@client/ui/icons'; import {Close, Edit} from '@client/ui/icons';
@@ -22,7 +22,7 @@ const DownloadRuleList: FC<DownloadRuleListProps> = observer(
return ( return (
<ul className="interactive-list"> <ul className="interactive-list">
<li className="interactive-list__item"> <li className="interactive-list__item">
<FormattedMessage id="feeds.no.rules.defined" /> <Trans id="feeds.no.rules.defined" />
</li> </li>
</ul> </ul>
); );
@@ -40,7 +40,7 @@ const DownloadRuleList: FC<DownloadRuleListProps> = observer(
<li <li
className="interactive-list__detail-list__item className="interactive-list__detail-list__item
interactive-list__detail interactive-list__detail--tertiary"> interactive-list__detail interactive-list__detail--tertiary">
<FormattedMessage id="feeds.exclude" /> <Trans id="feeds.exclude" />
{': '} {': '}
{rule.exclude} {rule.exclude}
</li> </li>
@@ -56,7 +56,7 @@ const DownloadRuleList: FC<DownloadRuleListProps> = observer(
tags = ( tags = (
<li className="interactive-list__detail-list__item interactive-list__detail interactive-list__detail--tertiary"> <li className="interactive-list__detail-list__item interactive-list__detail interactive-list__detail--tertiary">
<FormattedMessage id="feeds.tags" /> <Trans id="feeds.tags" />
{': '} {': '}
{tagNodes} {tagNodes}
</li> </li>
@@ -76,7 +76,7 @@ const DownloadRuleList: FC<DownloadRuleListProps> = observer(
className="interactive-list__detail-list__item className="interactive-list__detail-list__item
interactive-list__detail-list__item--overflow interactive-list__detail-list__item--overflow
interactive-list__detail interactive-list__detail--secondary"> interactive-list__detail interactive-list__detail--secondary">
<FormattedMessage id="feeds.match.count" values={{count: matchedCount}} /> <Trans id="feeds.match.count" values={{count: matchedCount}} />
</li> </li>
{rule === currentRule && ( {rule === currentRule && (
<li <li
@@ -95,7 +95,7 @@ const DownloadRuleList: FC<DownloadRuleListProps> = observer(
overflow: 'hidden', overflow: 'hidden',
textOverflow: 'ellipsis', textOverflow: 'ellipsis',
}}> }}>
<FormattedMessage id="feeds.match" /> <Trans id="feeds.match" />
{': '} {': '}
{rule.match} {rule.match}
</li> </li>

View File

@@ -1,5 +1,5 @@
import {FC, ReactNodeArray, useRef, useState} from 'react'; import {FC, ReactNodeArray, useRef, useState} from 'react';
import {FormattedMessage, useIntl} from 'react-intl'; import {Trans, useLingui} from '@lingui/react';
import {Button, Form, FormError, FormRow, FormRowItem} from '@client/ui'; import {Button, Form, FormError, FormRow, FormRowItem} from '@client/ui';
import FeedActions from '@client/actions/FeedActions'; import FeedActions from '@client/actions/FeedActions';
@@ -71,7 +71,7 @@ interface RuleFormData {
const DownloadRulesTab: FC = () => { const DownloadRulesTab: FC = () => {
const formRef = useRef<Form>(null); const formRef = useRef<Form>(null);
const intl = useIntl(); const {i18n} = useLingui();
const [currentRule, setCurrentRule] = useState<Rule | null>(null); const [currentRule, setCurrentRule] = useState<Rule | null>(null);
const [errors, setErrors] = useState<Record<string, string | undefined>>({}); const [errors, setErrors] = useState<Record<string, string | undefined>>({});
@@ -161,13 +161,13 @@ const DownloadRulesTab: FC = () => {
}} }}
ref={formRef}> ref={formRef}>
<ModalFormSectionHeader> <ModalFormSectionHeader>
<FormattedMessage id="feeds.existing.rules" /> <Trans id="feeds.existing.rules" />
</ModalFormSectionHeader> </ModalFormSectionHeader>
{Object.keys(errors).reduce((memo: ReactNodeArray, key) => { {Object.keys(errors).reduce((memo: ReactNodeArray, key) => {
if (errors[key as ValidatedField] != null) { if (errors[key as ValidatedField] != null) {
memo.push( memo.push(
<FormRow key={`error-${key}`}> <FormRow key={`error-${key}`}>
<FormError>{intl.formatMessage({id: errors?.[key as ValidatedField]})}</FormError> <FormError>{i18n._(errors?.[key as ValidatedField] as string)}</FormError>
</FormRow>, </FormRow>,
); );
} }
@@ -212,7 +212,7 @@ const DownloadRulesTab: FC = () => {
onClick={() => { onClick={() => {
setIsEditing(true); setIsEditing(true);
}}> }}>
<FormattedMessage id="button.new" /> <Trans id="button.new" />
</Button> </Button>
</FormRow> </FormRow>
)} )}

View File

@@ -1,5 +1,5 @@
import {FC} from 'react'; import {FC} from 'react';
import {FormattedMessage, useIntl} from 'react-intl'; import {Trans, useLingui} from '@lingui/react';
import {Button, FormRow, FormRowGroup, Select, SelectItem, Textbox} from '@client/ui'; import {Button, FormRow, FormRowGroup, Select, SelectItem, Textbox} from '@client/ui';
@@ -20,7 +20,7 @@ const FeedForm: FC<FeedFormProps> = ({
isSubmitting, isSubmitting,
onCancel, onCancel,
}: FeedFormProps) => { }: FeedFormProps) => {
const intl = useIntl(); const {i18n} = useLingui();
const feedInterval = currentFeed?.interval ?? defaultFeed.interval; const feedInterval = currentFeed?.interval ?? defaultFeed.interval;
let defaultIntervalTextValue = feedInterval; let defaultIntervalTextValue = feedInterval;
@@ -40,29 +40,21 @@ const FeedForm: FC<FeedFormProps> = ({
<FormRow> <FormRow>
<Textbox <Textbox
id="label" id="label"
label={intl.formatMessage({ label={i18n._('feeds.label')}
id: 'feeds.label', placeholder={i18n._('feeds.label')}
})}
placeholder={intl.formatMessage({
id: 'feeds.label',
})}
defaultValue={currentFeed?.label ?? defaultFeed.label} defaultValue={currentFeed?.label ?? defaultFeed.label}
/> />
<Textbox <Textbox
id="interval" id="interval"
label={intl.formatMessage({ label={i18n._('feeds.select.interval')}
id: 'feeds.select.interval', placeholder={i18n._('feeds.interval')}
})}
placeholder={intl.formatMessage({
id: 'feeds.interval',
})}
defaultValue={defaultIntervalTextValue} defaultValue={defaultIntervalTextValue}
width="one-eighth" width="one-eighth"
/> />
<Select labelOffset defaultID={defaultIntervalMultiplier} id="intervalMultiplier" width="one-eighth"> <Select labelOffset defaultID={defaultIntervalMultiplier} id="intervalMultiplier" width="one-eighth">
{intervalMultipliers.map((interval) => ( {intervalMultipliers.map((interval) => (
<SelectItem key={interval.value} id={interval.value}> <SelectItem key={interval.value} id={interval.value}>
{intl.formatMessage({id: interval.message})} {i18n._(interval.message)}
</SelectItem> </SelectItem>
))} ))}
</Select> </Select>
@@ -70,17 +62,15 @@ const FeedForm: FC<FeedFormProps> = ({
<FormRow> <FormRow>
<Textbox <Textbox
id="url" id="url"
label={intl.formatMessage({ label={i18n._('feeds.url')}
id: 'feeds.url', placeholder={i18n._('feeds.url')}
})}
placeholder={intl.formatMessage({id: 'feeds.url'})}
defaultValue={currentFeed?.url ?? defaultFeed?.url} defaultValue={currentFeed?.url ?? defaultFeed?.url}
/> />
<Button labelOffset onClick={onCancel}> <Button labelOffset onClick={onCancel}>
<FormattedMessage id="button.cancel" /> <Trans id="button.cancel" />
</Button> </Button>
<Button labelOffset type="submit" isLoading={isSubmitting}> <Button labelOffset type="submit" isLoading={isSubmitting}>
<FormattedMessage id="button.save.feed" /> <Trans id="button.save.feed" />
</Button> </Button>
</FormRow> </FormRow>
</FormRowGroup> </FormRowGroup>

View File

@@ -1,6 +1,6 @@
import {FC, ReactNodeArray} from 'react'; import {FC, ReactNodeArray} from 'react';
import {FormattedMessage} from 'react-intl';
import {observer} from 'mobx-react'; import {observer} from 'mobx-react';
import {Trans} from '@lingui/react';
import {Checkbox, FormRow} from '@client/ui'; import {Checkbox, FormRow} from '@client/ui';
@@ -42,7 +42,7 @@ const FeedItems: FC<FeedItemsProps> = observer(({selectedFeedID}: FeedItemsProps
<ul className="interactive-list"> <ul className="interactive-list">
<li className="interactive-list__item"> <li className="interactive-list__item">
<div className="interactive-list__label"> <div className="interactive-list__label">
<FormattedMessage id="feeds.no.items.matching" /> <Trans id="feeds.no.items.matching" />
</div> </div>
</li> </li>
</ul> </ul>

View File

@@ -1,6 +1,6 @@
import {FC, useRef, useState} from 'react'; import {FC, useRef, useState} from 'react';
import {FormattedMessage, useIntl} from 'react-intl';
import {observer} from 'mobx-react'; import {observer} from 'mobx-react';
import {Trans, useLingui} from '@lingui/react';
import {Button, Form, FormRow, Select, SelectItem, Textbox} from '@client/ui'; import {Button, Form, FormRow, Select, SelectItem, Textbox} from '@client/ui';
import FeedActions from '@client/actions/FeedActions'; import FeedActions from '@client/actions/FeedActions';
@@ -11,7 +11,7 @@ import FeedItems from './FeedItems';
import ModalFormSectionHeader from '../ModalFormSectionHeader'; import ModalFormSectionHeader from '../ModalFormSectionHeader';
const FeedItemsForm: FC = observer(() => { const FeedItemsForm: FC = observer(() => {
const intl = useIntl(); const {i18n} = useLingui();
const manualAddingFormRef = useRef<Form>(null); const manualAddingFormRef = useRef<Form>(null);
const [selectedFeedID, setSelectedFeedID] = useState<string | null>(null); const [selectedFeedID, setSelectedFeedID] = useState<string | null>(null);
@@ -55,22 +55,20 @@ const FeedItemsForm: FC = observer(() => {
}} }}
ref={manualAddingFormRef}> ref={manualAddingFormRef}>
<ModalFormSectionHeader> <ModalFormSectionHeader>
<FormattedMessage id="feeds.browse.feeds" /> <Trans id="feeds.browse.feeds" />
</ModalFormSectionHeader> </ModalFormSectionHeader>
<FormRow> <FormRow>
<Select <Select
disabled={!feeds.length} disabled={!feeds.length}
grow={false} grow={false}
id="feedID" id="feedID"
label={intl.formatMessage({ label={i18n._('feeds.select.feed')}
id: 'feeds.select.feed',
})}
width="three-eighths"> width="three-eighths">
{!feeds.length {!feeds.length
? [ ? [
<SelectItem key="empty" id="placeholder" isPlaceholder> <SelectItem key="empty" id="placeholder" isPlaceholder>
<em> <em>
<FormattedMessage id="feeds.no.feeds.available" /> <Trans id="feeds.no.feeds.available" />
</em> </em>
</SelectItem>, </SelectItem>,
] ]
@@ -89,26 +87,18 @@ const FeedItemsForm: FC = observer(() => {
[ [
<SelectItem key="select-feed" id="placeholder" isPlaceholder> <SelectItem key="select-feed" id="placeholder" isPlaceholder>
<em> <em>
<FormattedMessage id="feeds.select.feed" /> <Trans id="feeds.select.feed" />
</em> </em>
</SelectItem>, </SelectItem>,
], ],
)} )}
</Select> </Select>
{selectedFeedID && ( {selectedFeedID && (
<Textbox <Textbox id="search" label={i18n._('feeds.search.term')} placeholder={i18n._('feeds.search')} />
id="search"
label={intl.formatMessage({
id: 'feeds.search.term',
})}
placeholder={intl.formatMessage({
id: 'feeds.search',
})}
/>
)} )}
{selectedFeedID && ( {selectedFeedID && (
<Button key="button" type="submit" labelOffset> <Button key="button" type="submit" labelOffset>
<FormattedMessage id="button.download" /> <Trans id="button.download" />
</Button> </Button>
)} )}
</FormRow> </FormRow>

View File

@@ -1,6 +1,6 @@
import {FC} from 'react'; import {FC} from 'react';
import {observer} from 'mobx-react'; import {observer} from 'mobx-react';
import {FormattedMessage, useIntl} from 'react-intl'; import {Trans, useLingui} from '@lingui/react';
import {Close, Edit} from '@client/ui/icons'; import {Close, Edit} from '@client/ui/icons';
@@ -18,13 +18,13 @@ interface FeedListProps {
const FeedList: FC<FeedListProps> = observer( const FeedList: FC<FeedListProps> = observer(
({currentFeed, intervalMultipliers, onSelect, onRemove}: FeedListProps) => { ({currentFeed, intervalMultipliers, onSelect, onRemove}: FeedListProps) => {
const {feeds} = FeedStore; const {feeds} = FeedStore;
const intl = useIntl(); const {i18n} = useLingui();
if (feeds.length === 0) { if (feeds.length === 0) {
return ( return (
<ul className="interactive-list"> <ul className="interactive-list">
<li className="interactive-list__item"> <li className="interactive-list__item">
<FormattedMessage id="feeds.no.feeds.defined" /> <Trans id="feeds.no.feeds.defined" />
</li> </li>
</ul> </ul>
); );
@@ -60,7 +60,7 @@ const FeedList: FC<FeedListProps> = observer(
className="interactive-list__detail-list__item className="interactive-list__detail-list__item
interactive-list__detail-list__item--overflow interactive-list__detail-list__item--overflow
interactive-list__detail interactive-list__detail--secondary"> interactive-list__detail interactive-list__detail--secondary">
<FormattedMessage id="feeds.match.count" values={{count: matchedCount}} /> <Trans id="feeds.match.count" values={{count: matchedCount}} />
</li> </li>
{feed === currentFeed && ( {feed === currentFeed && (
<li <li
@@ -74,7 +74,7 @@ const FeedList: FC<FeedListProps> = observer(
<li <li
className="interactive-list__detail-list__item className="interactive-list__detail-list__item
interactive-list__detail interactive-list__detail--tertiary"> interactive-list__detail interactive-list__detail--tertiary">
{`${intervalText} ${intl.formatMessage({id: intervalMultiplierMessage})}`} {`${intervalText} ${i18n._(intervalMultiplierMessage)}`}
</li> </li>
<li <li
className="interactive-list__detail-list__item className="interactive-list__detail-list__item

View File

@@ -1,5 +1,5 @@
import {FC, useEffect} from 'react'; import {FC, useEffect} from 'react';
import {useIntl} from 'react-intl'; import {useLingui} from '@lingui/react';
import DownloadRulesTab from './DownloadRulesTab'; import DownloadRulesTab from './DownloadRulesTab';
import FeedActions from '../../../actions/FeedActions'; import FeedActions from '../../../actions/FeedActions';
@@ -7,7 +7,7 @@ import FeedsTab from './FeedsTab';
import Modal from '../Modal'; import Modal from '../Modal';
const FeedsModal: FC = () => { const FeedsModal: FC = () => {
const intl = useIntl(); const {i18n} = useLingui();
useEffect(() => { useEffect(() => {
FeedActions.fetchFeedMonitors(); FeedActions.fetchFeedMonitors();
@@ -16,28 +16,15 @@ const FeedsModal: FC = () => {
const tabs = { const tabs = {
feeds: { feeds: {
content: FeedsTab, content: FeedsTab,
label: intl.formatMessage({ label: i18n._('feeds.tabs.feeds'),
id: 'feeds.tabs.feeds',
}),
}, },
downloadRules: { downloadRules: {
content: DownloadRulesTab, content: DownloadRulesTab,
label: intl.formatMessage({ label: i18n._('feeds.tabs.download.rules'),
id: 'feeds.tabs.download.rules',
}),
}, },
}; };
return ( return <Modal heading={i18n._('feeds.tabs.heading')} orientation="horizontal" size="large" tabs={tabs} />;
<Modal
heading={intl.formatMessage({
id: 'feeds.tabs.heading',
})}
orientation="horizontal"
size="large"
tabs={tabs}
/>
);
}; };
export default FeedsModal; export default FeedsModal;

View File

@@ -1,5 +1,5 @@
import {FC, ReactNodeArray, useRef, useState} from 'react'; import {FC, ReactNodeArray, useRef, useState} from 'react';
import {FormattedMessage, useIntl} from 'react-intl'; import {Trans, useLingui} from '@lingui/react';
import {Button, Form, FormError, FormRow, FormRowItem} from '@client/ui'; import {Button, Form, FormError, FormRow, FormRowItem} from '@client/ui';
import FeedActions from '@client/actions/FeedActions'; import FeedActions from '@client/actions/FeedActions';
@@ -63,7 +63,7 @@ const defaultFeed: AddFeedOptions = {
const FeedsTab: FC = () => { const FeedsTab: FC = () => {
const formRef = useRef<Form>(null); const formRef = useRef<Form>(null);
const intl = useIntl(); const {i18n} = useLingui();
const [currentFeed, setCurrentFeed] = useState<Feed | null>(null); const [currentFeed, setCurrentFeed] = useState<Feed | null>(null);
const [errors, setErrors] = useState<Record<string, string | undefined>>({}); const [errors, setErrors] = useState<Record<string, string | undefined>>({});
const [isEditing, setIsEditing] = useState<boolean>(false); const [isEditing, setIsEditing] = useState<boolean>(false);
@@ -134,13 +134,13 @@ const FeedsTab: FC = () => {
}} }}
ref={formRef}> ref={formRef}>
<ModalFormSectionHeader> <ModalFormSectionHeader>
<FormattedMessage id="feeds.existing.feeds" /> <Trans id="feeds.existing.feeds" />
</ModalFormSectionHeader> </ModalFormSectionHeader>
{Object.keys(errors).reduce((memo: ReactNodeArray, key) => { {Object.keys(errors).reduce((memo: ReactNodeArray, key) => {
if (errors[key as ValidatedField] != null) { if (errors[key as ValidatedField] != null) {
memo.push( memo.push(
<FormRow key={`error-${key}`}> <FormRow key={`error-${key}`}>
<FormError>{intl.formatMessage({id: errors?.[key as ValidatedField]})}</FormError> <FormError>{i18n._(errors?.[key as ValidatedField] as string)}</FormError>
</FormRow>, </FormRow>,
); );
} }
@@ -192,7 +192,7 @@ const FeedsTab: FC = () => {
onClick={() => { onClick={() => {
setIsEditing(true); setIsEditing(true);
}}> }}>
<FormattedMessage id="button.new" /> <Trans id="button.new" />
</Button> </Button>
</FormRow> </FormRow>
)} )}

View File

@@ -1,5 +1,5 @@
import {FC, useEffect, useRef, useState} from 'react'; import {FC, useEffect, useRef, useState} from 'react';
import {useIntl} from 'react-intl'; import {useLingui} from '@lingui/react';
import {CheckmarkThick, Clipboard} from '@client/ui/icons'; import {CheckmarkThick, Clipboard} from '@client/ui/icons';
import {Form, FormElementAddon, FormError, FormRow, Textbox} from '@client/ui'; import {Form, FormElementAddon, FormError, FormRow, Textbox} from '@client/ui';
@@ -25,7 +25,7 @@ const generateMagnet = (hash: string, trackers?: Array<string>): string => {
const GenerateMagnetModal: FC = () => { const GenerateMagnetModal: FC = () => {
const magnetTextboxRef = useRef<HTMLInputElement>(null); const magnetTextboxRef = useRef<HTMLInputElement>(null);
const magnetTrackersTextboxRef = useRef<HTMLInputElement>(null); const magnetTrackersTextboxRef = useRef<HTMLInputElement>(null);
const intl = useIntl(); const {i18n} = useLingui();
const [isMagnetCopied, setIsMagnetCopied] = useState<boolean>(false); const [isMagnetCopied, setIsMagnetCopied] = useState<boolean>(false);
const [isMagnetTrackersCopied, setIsMagnetTrackersCopied] = useState<boolean>(false); const [isMagnetTrackersCopied, setIsMagnetTrackersCopied] = useState<boolean>(false);
@@ -55,15 +55,13 @@ const GenerateMagnetModal: FC = () => {
return ( return (
<Modal <Modal
heading={intl.formatMessage({ heading={i18n._('torrents.generate.magnet.heading')}
id: 'torrents.generate.magnet.heading',
})}
content={ content={
<div className="modal__content inverse"> <div className="modal__content inverse">
<Form> <Form>
{TorrentStore.torrents[TorrentStore.selectedTorrents[0]]?.isPrivate ? ( {TorrentStore.torrents[TorrentStore.selectedTorrents[0]]?.isPrivate ? (
<FormRow> <FormRow>
<FormError>{intl.formatMessage({id: 'torrents.generate.magnet.private.torrent'})}</FormError> <FormError>{i18n._('torrents.generate.magnet.private.torrent')}</FormError>
</FormRow> </FormRow>
) : null} ) : null}
<FormRow> <FormRow>
@@ -71,7 +69,7 @@ const GenerateMagnetModal: FC = () => {
id="magnet" id="magnet"
ref={magnetTextboxRef} ref={magnetTextboxRef}
addonPlacement="after" addonPlacement="after"
label={intl.formatMessage({id: 'torrents.generate.magnet.magnet'})} label={i18n._('torrents.generate.magnet.magnet')}
defaultValue={magnetLink} defaultValue={magnetLink}
readOnly> readOnly>
<FormElementAddon <FormElementAddon
@@ -94,10 +92,8 @@ const GenerateMagnetModal: FC = () => {
{trackerState.isLoadingTrackers ? ( {trackerState.isLoadingTrackers ? (
<Textbox <Textbox
id="loading" id="loading"
label={intl.formatMessage({id: 'torrents.generate.magnet.magnet.with.trackers'})} label={i18n._('torrents.generate.magnet.magnet.with.trackers')}
placeholder={intl.formatMessage({ placeholder={i18n._('torrents.generate.magnet.loading.trackers')}
id: 'torrents.generate.magnet.loading.trackers',
})}
disabled disabled
/> />
) : ( ) : (
@@ -105,7 +101,7 @@ const GenerateMagnetModal: FC = () => {
id="magnet-trackers" id="magnet-trackers"
ref={magnetTrackersTextboxRef} ref={magnetTrackersTextboxRef}
addonPlacement="after" addonPlacement="after"
label={intl.formatMessage({id: 'torrents.generate.magnet.magnet.with.trackers'})} label={i18n._('torrents.generate.magnet.magnet.with.trackers')}
defaultValue={trackerState.magnetTrackersLink} defaultValue={trackerState.magnetTrackersLink}
readOnly> readOnly>
<FormElementAddon <FormElementAddon
@@ -131,9 +127,7 @@ const GenerateMagnetModal: FC = () => {
actions={[ actions={[
{ {
clickHandler: null, clickHandler: null,
content: intl.formatMessage({ content: i18n._('button.close'),
id: 'button.close',
}),
triggerDismiss: true, triggerDismiss: true,
type: 'tertiary', type: 'tertiary',
}, },

View File

@@ -1,5 +1,5 @@
import {FC, useState} from 'react'; import {FC, useState} from 'react';
import {useIntl} from 'react-intl'; import {useLingui} from '@lingui/react';
import {Form} from '@client/ui'; import {Form} from '@client/ui';
import TorrentActions from '@client/actions/TorrentActions'; import TorrentActions from '@client/actions/TorrentActions';
@@ -31,14 +31,12 @@ const getSuggestedPath = (sources: Array<string>): string | undefined => {
}; };
const MoveTorrents: FC = () => { const MoveTorrents: FC = () => {
const intl = useIntl(); const {i18n} = useLingui();
const [isSettingDownloadPath, setIsSettingDownloadPath] = useState<boolean>(false); const [isSettingDownloadPath, setIsSettingDownloadPath] = useState<boolean>(false);
return ( return (
<Modal <Modal
heading={intl.formatMessage({ heading={i18n._('torrents.move.heading')}
id: 'torrents.move.heading',
})}
content={ content={
<div className="modal__content"> <div className="modal__content">
<Form <Form
@@ -71,31 +69,23 @@ const MoveTorrents: FC = () => {
actions={[ actions={[
{ {
checked: true, checked: true,
content: intl.formatMessage({ content: i18n._('torrents.move.data.label'),
id: 'torrents.move.data.label',
}),
id: 'moveFiles', id: 'moveFiles',
type: 'checkbox', type: 'checkbox',
}, },
{ {
checked: false, checked: false,
content: intl.formatMessage({ content: i18n._('torrents.move.check_hash.label'),
id: 'torrents.move.check_hash.label',
}),
id: 'isCheckHash', id: 'isCheckHash',
type: 'checkbox', type: 'checkbox',
}, },
{ {
content: intl.formatMessage({ content: i18n._('button.cancel'),
id: 'button.cancel',
}),
triggerDismiss: true, triggerDismiss: true,
type: 'tertiary', type: 'tertiary',
}, },
{ {
content: intl.formatMessage({ content: i18n._('torrents.move.button.set.location'),
id: 'torrents.move.button.set.location',
}),
isLoading: isSettingDownloadPath, isLoading: isSettingDownloadPath,
submit: true, submit: true,
type: 'primary', type: 'primary',

View File

@@ -1,5 +1,5 @@
import {FC, useState} from 'react'; import {FC, useState} from 'react';
import {FormattedMessage, useIntl} from 'react-intl'; import {Trans, useLingui} from '@lingui/react';
import {Form, FormRow} from '@client/ui'; import {Form, FormRow} from '@client/ui';
import {saveDeleteTorrentsUserPreferences} from '@client/util/userPreferences'; import {saveDeleteTorrentsUserPreferences} from '@client/util/userPreferences';
@@ -12,21 +12,19 @@ import Modal from '../Modal';
import ModalActions from '../ModalActions'; import ModalActions from '../ModalActions';
const RemoveTorrentsModal: FC = () => { const RemoveTorrentsModal: FC = () => {
const intl = useIntl(); const {i18n} = useLingui();
const [isRemoving, setIsRemoving] = useState<boolean>(false); const [isRemoving, setIsRemoving] = useState<boolean>(false);
const {selectedTorrents} = TorrentStore; const {selectedTorrents} = TorrentStore;
if (selectedTorrents.length === 0) { if (selectedTorrents.length === 0) {
return ( return (
<Modal <Modal
heading={intl.formatMessage({ heading={i18n._('torrents.remove')}
id: 'torrents.remove',
})}
content={ content={
<div className="modal__content inverse"> <div className="modal__content inverse">
<Form> <Form>
<FormRow> <FormRow>
<FormattedMessage id="torrents.remove.error.no.torrents.selected" /> <Trans id="torrents.remove.error.no.torrents.selected" />
</FormRow> </FormRow>
</Form> </Form>
</div> </div>
@@ -34,9 +32,7 @@ const RemoveTorrentsModal: FC = () => {
actions={[ actions={[
{ {
clickHandler: null, clickHandler: null,
content: intl.formatMessage({ content: i18n._('button.ok'),
id: 'button.ok',
}),
triggerDismiss: true, triggerDismiss: true,
type: 'primary', type: 'primary',
}, },
@@ -47,9 +43,7 @@ const RemoveTorrentsModal: FC = () => {
return ( return (
<Modal <Modal
heading={intl.formatMessage({ heading={i18n._('torrents.remove')}
id: 'torrents.remove',
})}
content={ content={
<div className="modal__content"> <div className="modal__content">
<Form <Form
@@ -69,29 +63,23 @@ const RemoveTorrentsModal: FC = () => {
}); });
}}> }}>
<FormRow> <FormRow>
<FormattedMessage id="torrents.remove.are.you.sure" values={{count: selectedTorrents.length}} /> <Trans id="torrents.remove.are.you.sure" values={{count: selectedTorrents.length}} />
</FormRow> </FormRow>
<ModalActions <ModalActions
actions={[ actions={[
{ {
checked: SettingStore.floodSettings.deleteTorrentData, checked: SettingStore.floodSettings.deleteTorrentData,
content: intl.formatMessage({ content: i18n._('torrents.remove.delete.data'),
id: 'torrents.remove.delete.data',
}),
id: 'deleteData', id: 'deleteData',
type: 'checkbox', type: 'checkbox',
}, },
{ {
content: intl.formatMessage({ content: i18n._('button.no'),
id: 'button.no',
}),
triggerDismiss: true, triggerDismiss: true,
type: 'tertiary', type: 'tertiary',
}, },
{ {
content: intl.formatMessage({ content: i18n._('button.yes'),
id: 'button.yes',
}),
isLoading: isRemoving, isLoading: isRemoving,
submit: true, submit: true,
type: 'primary', type: 'primary',

View File

@@ -1,5 +1,5 @@
import {FC, useRef, useState} from 'react'; import {FC, useRef, useState} from 'react';
import {useIntl} from 'react-intl'; import {useLingui} from '@lingui/react';
import {Form, FormRow} from '@client/ui'; import {Form, FormRow} from '@client/ui';
import TorrentActions from '@client/actions/TorrentActions'; import TorrentActions from '@client/actions/TorrentActions';
@@ -10,14 +10,12 @@ import TagSelect from '../../general/form-elements/TagSelect';
const SetTagsModal: FC = () => { const SetTagsModal: FC = () => {
const formRef = useRef<Form>(null); const formRef = useRef<Form>(null);
const intl = useIntl(); const {i18n} = useLingui();
const [isSettingTags, setIsSettingTags] = useState<boolean>(false); const [isSettingTags, setIsSettingTags] = useState<boolean>(false);
return ( return (
<Modal <Modal
heading={intl.formatMessage({ heading={i18n._('torrents.set.tags.heading')}
id: 'torrents.set.tags.heading',
})}
content={ content={
<div className="modal__content inverse"> <div className="modal__content inverse">
<Form ref={formRef}> <Form ref={formRef}>
@@ -27,9 +25,7 @@ const SetTagsModal: FC = () => {
.map((hash: string) => TorrentStore.torrents[hash].tags)[0] .map((hash: string) => TorrentStore.torrents[hash].tags)[0]
.slice()} .slice()}
id="tags" id="tags"
placeholder={intl.formatMessage({ placeholder={i18n._('torrents.set.tags.enter.tags')}
id: 'torrents.set.tags.enter.tags',
})}
/> />
</FormRow> </FormRow>
</Form> </Form>
@@ -37,17 +33,13 @@ const SetTagsModal: FC = () => {
} }
actions={[ actions={[
{ {
content: intl.formatMessage({ content: i18n._('button.cancel'),
id: 'button.cancel',
}),
clickHandler: null, clickHandler: null,
triggerDismiss: true, triggerDismiss: true,
type: 'tertiary', type: 'tertiary',
}, },
{ {
content: intl.formatMessage({ content: i18n._('torrents.set.tags.button.set'),
id: 'torrents.set.tags.button.set',
}),
clickHandler: () => { clickHandler: () => {
if (formRef.current == null) { if (formRef.current == null) {
return; return;

View File

@@ -1,5 +1,5 @@
import {FC, useEffect, useRef, useState} from 'react'; import {FC, useEffect, useRef, useState} from 'react';
import {useIntl} from 'react-intl'; import {useLingui} from '@lingui/react';
import {Form, FormRow, Textbox} from '@client/ui'; import {Form, FormRow, Textbox} from '@client/ui';
import TorrentActions from '@client/actions/TorrentActions'; import TorrentActions from '@client/actions/TorrentActions';
@@ -13,7 +13,7 @@ import TextboxRepeater, {getTextArray} from '../../general/form-elements/Textbox
const SetTrackersModal: FC = () => { const SetTrackersModal: FC = () => {
const formRef = useRef<Form>(null); const formRef = useRef<Form>(null);
const intl = useIntl(); const {i18n} = useLingui();
const [isSettingTrackers, setIsSettingTrackers] = useState<boolean>(false); const [isSettingTrackers, setIsSettingTrackers] = useState<boolean>(false);
const [trackerState, setTrackerState] = useState<{ const [trackerState, setTrackerState] = useState<{
isLoadingTrackers: boolean; isLoadingTrackers: boolean;
@@ -38,28 +38,18 @@ const SetTrackersModal: FC = () => {
return ( return (
<Modal <Modal
heading={intl.formatMessage({ heading={i18n._('torrents.set.trackers.heading')}
id: 'torrents.set.trackers.heading',
})}
content={ content={
<div className="modal__content inverse"> <div className="modal__content inverse">
<Form ref={formRef}> <Form ref={formRef}>
{trackerState.isLoadingTrackers ? ( {trackerState.isLoadingTrackers ? (
<FormRow> <FormRow>
<Textbox <Textbox id="loading" placeholder={i18n._('torrents.set.trackers.loading.trackers')} disabled />
id="loading"
placeholder={intl.formatMessage({
id: 'torrents.set.trackers.loading.trackers',
})}
disabled
/>
</FormRow> </FormRow>
) : ( ) : (
<TextboxRepeater <TextboxRepeater
id="trackers" id="trackers"
placeholder={intl.formatMessage({ placeholder={i18n._('torrents.set.trackers.enter.tracker')}
id: 'torrents.set.trackers.enter.tracker',
})}
defaultValues={ defaultValues={
trackerState.trackerURLs.length === 0 trackerState.trackerURLs.length === 0
? undefined ? undefined
@@ -76,9 +66,7 @@ const SetTrackersModal: FC = () => {
actions={[ actions={[
{ {
clickHandler: null, clickHandler: null,
content: intl.formatMessage({ content: i18n._('button.cancel'),
id: 'button.cancel',
}),
triggerDismiss: true, triggerDismiss: true,
type: 'tertiary', type: 'tertiary',
}, },
@@ -101,9 +89,7 @@ const SetTrackersModal: FC = () => {
UIStore.dismissModal(); UIStore.dismissModal();
}); });
}, },
content: intl.formatMessage({ content: i18n._('torrents.set.trackers.button.set'),
id: 'torrents.set.trackers.button.set',
}),
isLoading: isSettingTrackers || trackerState.isLoadingTrackers, isLoading: isSettingTrackers || trackerState.isLoadingTrackers,
triggerDismiss: false, triggerDismiss: false,
type: 'primary', type: 'primary',

View File

@@ -1,8 +1,8 @@
import classnames from 'classnames'; import classnames from 'classnames';
import {CSSTransition, TransitionGroup} from 'react-transition-group'; import {CSSTransition, TransitionGroup} from 'react-transition-group';
import {FC, useEffect, useRef, useState} from 'react'; import {FC, useEffect, useRef, useState} from 'react';
import {FormattedMessage, useIntl} from 'react-intl';
import {observer} from 'mobx-react'; import {observer} from 'mobx-react';
import {Trans, useLingui} from '@lingui/react';
import {Button, Checkbox, Form, FormError, FormRowItem, FormRow, LoadingRing, Textbox} from '@client/ui'; import {Button, Checkbox, Form, FormError, FormRowItem, FormRow, LoadingRing, Textbox} from '@client/ui';
import {Close} from '@client/ui/icons'; import {Close} from '@client/ui/icons';
@@ -29,7 +29,7 @@ const AuthTab: FC = observer(() => {
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const [isUserListFetched, setIsUserListFetched] = useState<boolean>(false); const [isUserListFetched, setIsUserListFetched] = useState<boolean>(false);
const [isSubmitting, setIsSubmitting] = useState<boolean>(false); const [isSubmitting, setIsSubmitting] = useState<boolean>(false);
const intl = useIntl(); const {i18n} = useLingui();
useEffect(() => { useEffect(() => {
if (AuthStore.currentUser.isAdmin) { if (AuthStore.currentUser.isAdmin) {
@@ -43,11 +43,11 @@ const AuthTab: FC = observer(() => {
return ( return (
<Form> <Form>
<ModalFormSectionHeader> <ModalFormSectionHeader>
<FormattedMessage id="auth.user.accounts" /> <Trans id="auth.user.accounts" />
</ModalFormSectionHeader> </ModalFormSectionHeader>
<FormRow> <FormRow>
<FormError> <FormError>
<FormattedMessage id="auth.message.not.admin" /> <Trans id="auth.message.not.admin" />
</FormError> </FormError>
</FormRow> </FormRow>
</Form> </Form>
@@ -106,7 +106,7 @@ const AuthTab: FC = observer(() => {
}} }}
ref={formRef}> ref={formRef}>
<ModalFormSectionHeader> <ModalFormSectionHeader>
<FormattedMessage id="auth.user.accounts" /> <Trans id="auth.user.accounts" />
</ModalFormSectionHeader> </ModalFormSectionHeader>
<FormRow> <FormRow>
<FormRowItem> <FormRowItem>
@@ -139,7 +139,7 @@ const AuthTab: FC = observer(() => {
} else { } else {
badge = ( badge = (
<span className="interactive-list__label__tag tag"> <span className="interactive-list__label__tag tag">
<FormattedMessage id="auth.current.user" /> <Trans id="auth.current.user" />
</span> </span>
); );
} }
@@ -162,32 +162,28 @@ const AuthTab: FC = observer(() => {
</FormRowItem> </FormRowItem>
</FormRow> </FormRow>
<ModalFormSectionHeader> <ModalFormSectionHeader>
<FormattedMessage id="auth.add.user" /> <Trans id="auth.add.user" />
</ModalFormSectionHeader> </ModalFormSectionHeader>
{error && ( {error && (
<FormRow> <FormRow>
<FormError>{intl.formatMessage({id: error})}</FormError> <FormError>{i18n._(error)}</FormError>
</FormRow> </FormRow>
)} )}
<FormRow> <FormRow>
<Textbox <Textbox
id="username" id="username"
label={<FormattedMessage id="auth.username" />} label={<Trans id="auth.username" />}
placeholder={intl.formatMessage({ placeholder={i18n._('auth.username')}
id: 'auth.username',
})}
autoComplete="username" autoComplete="username"
/> />
<Textbox <Textbox
id="password" id="password"
label={<FormattedMessage id="auth.password" />} label={<Trans id="auth.password" />}
placeholder={intl.formatMessage({ placeholder={i18n._('auth.password')}
id: 'auth.password',
})}
autoComplete="new-password" autoComplete="new-password"
/> />
<Checkbox grow={false} id="isAdmin" labelOffset matchTextboxHeight> <Checkbox grow={false} id="isAdmin" labelOffset matchTextboxHeight>
<FormattedMessage id="auth.admin" /> <Trans id="auth.admin" />
</Checkbox> </Checkbox>
</FormRow> </FormRow>
<ClientConnectionSettingsForm <ClientConnectionSettingsForm
@@ -198,7 +194,7 @@ const AuthTab: FC = observer(() => {
<p /> <p />
<FormRow justify="end"> <FormRow justify="end">
<Button isLoading={isSubmitting} priority="primary" type="submit"> <Button isLoading={isSubmitting} priority="primary" type="submit">
<FormattedMessage id="button.add" /> <Trans id="button.add" />
</Button> </Button>
</FormRow> </FormRow>
</Form> </Form>

View File

@@ -1,5 +1,5 @@
import {FormattedMessage} from 'react-intl';
import {FC, FormEvent, useState} from 'react'; import {FC, FormEvent, useState} from 'react';
import {Trans} from '@lingui/react';
import {Form, FormRow, Textbox} from '@client/ui'; import {Form, FormRow, Textbox} from '@client/ui';
import SettingStore from '@client/stores/SettingStore'; import SettingStore from '@client/stores/SettingStore';
@@ -62,7 +62,7 @@ const BandwidthTab: FC<BandwidthTabProps> = ({onSettingsChange, onClientSettings
onClientSettingsChange(newChangedClientSettings); onClientSettingsChange(newChangedClientSettings);
}}> }}>
<ModalFormSectionHeader> <ModalFormSectionHeader>
<FormattedMessage id="settings.bandwidth.transferrate.heading" /> <Trans id="settings.bandwidth.transferrate.heading" />
</ModalFormSectionHeader> </ModalFormSectionHeader>
<FormRow> <FormRow>
<Textbox <Textbox
@@ -71,7 +71,7 @@ const BandwidthTab: FC<BandwidthTabProps> = ({onSettingsChange, onClientSettings
? processSpeedsForDisplay(SettingStore.floodSettings.speedLimits.download) ? processSpeedsForDisplay(SettingStore.floodSettings.speedLimits.download)
: 0 : 0
} }
label={<FormattedMessage id="settings.bandwidth.transferrate.dropdown.preset.download.label" />} label={<Trans id="settings.bandwidth.transferrate.dropdown.preset.download.label" />}
id="dropdownPresetDownload" id="dropdownPresetDownload"
/> />
</FormRow> </FormRow>
@@ -82,46 +82,46 @@ const BandwidthTab: FC<BandwidthTabProps> = ({onSettingsChange, onClientSettings
? processSpeedsForDisplay(SettingStore.floodSettings.speedLimits.upload) ? processSpeedsForDisplay(SettingStore.floodSettings.speedLimits.upload)
: 0 : 0
} }
label={<FormattedMessage id="settings.bandwidth.transferrate.dropdown.preset.upload.label" />} label={<Trans id="settings.bandwidth.transferrate.dropdown.preset.upload.label" />}
id="dropdownPresetUpload" id="dropdownPresetUpload"
/> />
</FormRow> </FormRow>
<FormRow> <FormRow>
<Textbox <Textbox
defaultValue={getChangedClientSetting(changedClientSettings, 'throttleGlobalDownSpeed')} defaultValue={getChangedClientSetting(changedClientSettings, 'throttleGlobalDownSpeed')}
label={<FormattedMessage id="settings.bandwidth.transferrate.global.throttle.download" />} label={<Trans id="settings.bandwidth.transferrate.global.throttle.download" />}
id="throttleGlobalDownSpeed" id="throttleGlobalDownSpeed"
/> />
<Textbox <Textbox
defaultValue={getChangedClientSetting(changedClientSettings, 'throttleGlobalUpSpeed')} defaultValue={getChangedClientSetting(changedClientSettings, 'throttleGlobalUpSpeed')}
label={<FormattedMessage id="settings.bandwidth.transferrate.global.throttle.upload" />} label={<Trans id="settings.bandwidth.transferrate.global.throttle.upload" />}
id="throttleGlobalUpSpeed" id="throttleGlobalUpSpeed"
/> />
</FormRow> </FormRow>
<ModalFormSectionHeader> <ModalFormSectionHeader>
<FormattedMessage id="settings.bandwidth.slots.heading" /> <Trans id="settings.bandwidth.slots.heading" />
</ModalFormSectionHeader> </ModalFormSectionHeader>
<FormRow> <FormRow>
<Textbox <Textbox
defaultValue={getChangedClientSetting(changedClientSettings, 'throttleMaxUploads')} defaultValue={getChangedClientSetting(changedClientSettings, 'throttleMaxUploads')}
label={<FormattedMessage id="settings.bandwidth.slots.upload.label" />} label={<Trans id="settings.bandwidth.slots.upload.label" />}
id="throttleMaxUploads" id="throttleMaxUploads"
/> />
<Textbox <Textbox
defaultValue={getChangedClientSetting(changedClientSettings, 'throttleMaxUploadsGlobal')} defaultValue={getChangedClientSetting(changedClientSettings, 'throttleMaxUploadsGlobal')}
label={<FormattedMessage id="settings.bandwidth.slots.upload.global.label" />} label={<Trans id="settings.bandwidth.slots.upload.global.label" />}
id="throttleMaxUploadsGlobal" id="throttleMaxUploadsGlobal"
/> />
</FormRow> </FormRow>
<FormRow> <FormRow>
<Textbox <Textbox
defaultValue={getChangedClientSetting(changedClientSettings, 'throttleMaxDownloads')} defaultValue={getChangedClientSetting(changedClientSettings, 'throttleMaxDownloads')}
label={<FormattedMessage id="settings.bandwidth.slots.download.label" />} label={<Trans id="settings.bandwidth.slots.download.label" />}
id="throttleMaxDownloads" id="throttleMaxDownloads"
/> />
<Textbox <Textbox
defaultValue={getChangedClientSetting(changedClientSettings, 'throttleMaxDownloadsGlobal')} defaultValue={getChangedClientSetting(changedClientSettings, 'throttleMaxDownloadsGlobal')}
label={<FormattedMessage id="settings.bandwidth.slots.download.global.label" />} label={<Trans id="settings.bandwidth.slots.download.global.label" />}
id="throttleMaxDownloadsGlobal" id="throttleMaxDownloadsGlobal"
/> />
</FormRow> </FormRow>

View File

@@ -1,5 +1,5 @@
import {FC, useState} from 'react'; import {FC, useState} from 'react';
import {FormattedMessage} from 'react-intl'; import {Trans} from '@lingui/react';
import {Checkbox, Form, FormRow, Textbox} from '@client/ui'; import {Checkbox, Form, FormRow, Textbox} from '@client/ui';
@@ -27,13 +27,13 @@ const ConnectivityTab: FC<ConnectivityTabProps> = ({onClientSettingsChange}: Con
onClientSettingsChange(newChangedClientSettings); onClientSettingsChange(newChangedClientSettings);
}}> }}>
<ModalFormSectionHeader> <ModalFormSectionHeader>
<FormattedMessage id="settings.connectivity.incoming.heading" /> <Trans id="settings.connectivity.incoming.heading" />
</ModalFormSectionHeader> </ModalFormSectionHeader>
<FormRow> <FormRow>
<Textbox <Textbox
defaultValue={getChangedClientSetting(changedClientSettings, 'networkPortRange')} defaultValue={getChangedClientSetting(changedClientSettings, 'networkPortRange')}
id="networkPortRange" id="networkPortRange"
label={<FormattedMessage id="settings.connectivity.port.range.label" />} label={<Trans id="settings.connectivity.port.range.label" />}
width="one-quarter" width="one-quarter"
/> />
<Checkbox <Checkbox
@@ -42,7 +42,7 @@ const ConnectivityTab: FC<ConnectivityTabProps> = ({onClientSettingsChange}: Con
id="networkPortRandom" id="networkPortRandom"
labelOffset labelOffset
matchTextboxHeight> matchTextboxHeight>
<FormattedMessage id="settings.connectivity.port.randomize.label" /> <Trans id="settings.connectivity.port.randomize.label" />
</Checkbox> </Checkbox>
<Checkbox <Checkbox
defaultChecked={getChangedClientSetting(changedClientSettings, 'networkPortOpen')} defaultChecked={getChangedClientSetting(changedClientSettings, 'networkPortOpen')}
@@ -50,29 +50,29 @@ const ConnectivityTab: FC<ConnectivityTabProps> = ({onClientSettingsChange}: Con
id="networkPortOpen" id="networkPortOpen"
labelOffset labelOffset
matchTextboxHeight> matchTextboxHeight>
<FormattedMessage id="settings.connectivity.port.open.label" /> <Trans id="settings.connectivity.port.open.label" />
</Checkbox> </Checkbox>
</FormRow> </FormRow>
<FormRow> <FormRow>
<Textbox <Textbox
defaultValue={getChangedClientSetting(changedClientSettings, 'networkLocalAddress')} defaultValue={getChangedClientSetting(changedClientSettings, 'networkLocalAddress')}
id="networkLocalAddress" id="networkLocalAddress"
label={<FormattedMessage id="settings.connectivity.ip.hostname.label" />} label={<Trans id="settings.connectivity.ip.hostname.label" />}
/> />
<Textbox <Textbox
defaultValue={getChangedClientSetting(changedClientSettings, 'networkHttpMaxOpen')} defaultValue={getChangedClientSetting(changedClientSettings, 'networkHttpMaxOpen')}
id="networkHttpMaxOpen" id="networkHttpMaxOpen"
label={<FormattedMessage id="settings.connectivity.max.http.connections" />} label={<Trans id="settings.connectivity.max.http.connections" />}
/> />
</FormRow> </FormRow>
<ModalFormSectionHeader> <ModalFormSectionHeader>
<FormattedMessage id="settings.connectivity.dpd.heading" /> <Trans id="settings.connectivity.dpd.heading" />
</ModalFormSectionHeader> </ModalFormSectionHeader>
<FormRow> <FormRow>
<Textbox <Textbox
defaultValue={getChangedClientSetting(changedClientSettings, 'dhtPort')} defaultValue={getChangedClientSetting(changedClientSettings, 'dhtPort')}
id="dhtPort" id="dhtPort"
label={<FormattedMessage id="settings.connectivity.dht.port.label" />} label={<Trans id="settings.connectivity.dht.port.label" />}
width="one-quarter" width="one-quarter"
/> />
<Checkbox <Checkbox
@@ -81,7 +81,7 @@ const ConnectivityTab: FC<ConnectivityTabProps> = ({onClientSettingsChange}: Con
id="dht" id="dht"
labelOffset labelOffset
matchTextboxHeight> matchTextboxHeight>
<FormattedMessage id="settings.connectivity.dht.label" /> <Trans id="settings.connectivity.dht.label" />
</Checkbox> </Checkbox>
<Checkbox <Checkbox
defaultChecked={getChangedClientSetting(changedClientSettings, 'protocolPex')} defaultChecked={getChangedClientSetting(changedClientSettings, 'protocolPex')}
@@ -89,41 +89,41 @@ const ConnectivityTab: FC<ConnectivityTabProps> = ({onClientSettingsChange}: Con
id="protocolPex" id="protocolPex"
labelOffset labelOffset
matchTextboxHeight> matchTextboxHeight>
<FormattedMessage id="settings.connectivity.peer.exchange.label" /> <Trans id="settings.connectivity.peer.exchange.label" />
</Checkbox> </Checkbox>
</FormRow> </FormRow>
<ModalFormSectionHeader> <ModalFormSectionHeader>
<FormattedMessage id="settings.connectivity.peers.heading" /> <Trans id="settings.connectivity.peers.heading" />
</ModalFormSectionHeader> </ModalFormSectionHeader>
<FormRow> <FormRow>
<Textbox <Textbox
defaultValue={getChangedClientSetting(changedClientSettings, 'throttleMinPeersNormal')} defaultValue={getChangedClientSetting(changedClientSettings, 'throttleMinPeersNormal')}
id="throttleMinPeersNormal" id="throttleMinPeersNormal"
label={<FormattedMessage id="settings.connectivity.peers.min.label" />} label={<Trans id="settings.connectivity.peers.min.label" />}
/> />
<Textbox <Textbox
defaultValue={getChangedClientSetting(changedClientSettings, 'throttleMaxPeersNormal')} defaultValue={getChangedClientSetting(changedClientSettings, 'throttleMaxPeersNormal')}
id="throttleMaxPeersNormal" id="throttleMaxPeersNormal"
label={<FormattedMessage id="settings.connectivity.peers.max.label" />} label={<Trans id="settings.connectivity.peers.max.label" />}
/> />
</FormRow> </FormRow>
<FormRow> <FormRow>
<Textbox <Textbox
defaultValue={getChangedClientSetting(changedClientSettings, 'throttleMinPeersSeed')} defaultValue={getChangedClientSetting(changedClientSettings, 'throttleMinPeersSeed')}
id="throttleMinPeersSeed" id="throttleMinPeersSeed"
label={<FormattedMessage id="settings.connectivity.peers.seeding.min.label" />} label={<Trans id="settings.connectivity.peers.seeding.min.label" />}
/> />
<Textbox <Textbox
defaultValue={getChangedClientSetting(changedClientSettings, 'throttleMaxPeersSeed')} defaultValue={getChangedClientSetting(changedClientSettings, 'throttleMaxPeersSeed')}
id="throttleMaxPeersSeed" id="throttleMaxPeersSeed"
label={<FormattedMessage id="settings.connectivity.peers.seeding.max.label" />} label={<Trans id="settings.connectivity.peers.seeding.max.label" />}
/> />
</FormRow> </FormRow>
<FormRow> <FormRow>
<Textbox <Textbox
defaultValue={getChangedClientSetting(changedClientSettings, 'trackersNumWant')} defaultValue={getChangedClientSetting(changedClientSettings, 'trackersNumWant')}
id="trackersNumWant" id="trackersNumWant"
label={<FormattedMessage id="settings.connectivity.peers.desired.label" />} label={<Trans id="settings.connectivity.peers.desired.label" />}
width="one-half" width="one-half"
/> />
</FormRow> </FormRow>

View File

@@ -1,5 +1,5 @@
import {FC} from 'react'; import {FC} from 'react';
import {FormattedMessage} from 'react-intl'; import {Trans} from '@lingui/react';
import {Form, FormRow} from '@client/ui'; import {Form, FormRow} from '@client/ui';
@@ -15,7 +15,7 @@ interface DiskUsageTabProps {
const DiskUsageTab: FC<DiskUsageTabProps> = (props: DiskUsageTabProps) => ( const DiskUsageTab: FC<DiskUsageTabProps> = (props: DiskUsageTabProps) => (
<Form> <Form>
<ModalFormSectionHeader> <ModalFormSectionHeader>
<FormattedMessage id="settings.diskusage.mount.points" /> <Trans id="settings.diskusage.mount.points" />
</ModalFormSectionHeader> </ModalFormSectionHeader>
<FormRow> <FormRow>
<MountPointsList onSettingsChange={props.onSettingsChange} /> <MountPointsList onSettingsChange={props.onSettingsChange} />

View File

@@ -1,5 +1,5 @@
import {FC, useState} from 'react'; import {FC, useState} from 'react';
import {FormattedMessage} from 'react-intl'; import {Trans} from '@lingui/react';
import {Checkbox, Form, FormRow, Textbox} from '@client/ui'; import {Checkbox, Form, FormRow, Textbox} from '@client/ui';
@@ -27,20 +27,20 @@ const ResourcesTab: FC<ResourcesTabProps> = ({onClientSettingsChange}: Resources
onClientSettingsChange(newChangedClientSettings); onClientSettingsChange(newChangedClientSettings);
}}> }}>
<ModalFormSectionHeader> <ModalFormSectionHeader>
<FormattedMessage id="settings.resources.disk.heading" /> <Trans id="settings.resources.disk.heading" />
</ModalFormSectionHeader> </ModalFormSectionHeader>
<FormRow> <FormRow>
<Textbox <Textbox
defaultValue={getChangedClientSetting(changedClientSettings, 'directoryDefault')} defaultValue={getChangedClientSetting(changedClientSettings, 'directoryDefault')}
id="directoryDefault" id="directoryDefault"
label={<FormattedMessage id="settings.resources.disk.download.location.label" />} label={<Trans id="settings.resources.disk.download.location.label" />}
/> />
</FormRow> </FormRow>
<FormRow> <FormRow>
<Textbox <Textbox
defaultValue={getChangedClientSetting(changedClientSettings, 'networkMaxOpenFiles')} defaultValue={getChangedClientSetting(changedClientSettings, 'networkMaxOpenFiles')}
id="networkMaxOpenFiles" id="networkMaxOpenFiles"
label={<FormattedMessage id="settings.resources.max.open.files" />} label={<Trans id="settings.resources.max.open.files" />}
width="one-half" width="one-half"
/> />
<Checkbox <Checkbox
@@ -49,11 +49,11 @@ const ResourcesTab: FC<ResourcesTabProps> = ({onClientSettingsChange}: Resources
id="piecesHashOnCompletion" id="piecesHashOnCompletion"
labelOffset labelOffset
matchTextboxHeight> matchTextboxHeight>
<FormattedMessage id="settings.resources.disk.check.hash.label" /> <Trans id="settings.resources.disk.check.hash.label" />
</Checkbox> </Checkbox>
</FormRow> </FormRow>
<ModalFormSectionHeader> <ModalFormSectionHeader>
<FormattedMessage id="settings.resources.memory.heading" /> <Trans id="settings.resources.memory.heading" />
</ModalFormSectionHeader> </ModalFormSectionHeader>
<FormRow> <FormRow>
<Textbox <Textbox
@@ -61,7 +61,7 @@ const ResourcesTab: FC<ResourcesTabProps> = ({onClientSettingsChange}: Resources
id="piecesMemoryMax" id="piecesMemoryMax"
label={ label={
<div> <div>
<FormattedMessage id="settings.resources.memory.max.label" /> <em className="unit">(MB)</em> <Trans id="settings.resources.memory.max.label" /> <em className="unit">(MB)</em>
</div> </div>
} }
width="one-half" width="one-half"

View File

@@ -1,5 +1,5 @@
import {FC, useState} from 'react'; import {FC, useState} from 'react';
import {useIntl} from 'react-intl'; import {useLingui} from '@lingui/react';
import {useMedia} from 'react-use'; import {useMedia} from 'react-use';
import type {ClientSettings} from '@shared/types/ClientSettings'; import type {ClientSettings} from '@shared/types/ClientSettings';
@@ -19,7 +19,7 @@ import UITab from './UITab';
import UIStore from '../../../stores/UIStore'; import UIStore from '../../../stores/UIStore';
const SettingsModal: FC = () => { const SettingsModal: FC = () => {
const intl = useIntl(); const {i18n} = useLingui();
const isSmallScreen = useMedia('(max-width: 720px)'); const isSmallScreen = useMedia('(max-width: 720px)');
const [changedClientSettings, setChangedClientSettings] = useState<Partial<ClientSettings>>({}); const [changedClientSettings, setChangedClientSettings] = useState<Partial<ClientSettings>>({});
@@ -47,61 +47,47 @@ const SettingsModal: FC = () => {
onClientSettingsChange: handleClientSettingsChange, onClientSettingsChange: handleClientSettingsChange,
onSettingsChange: handleFloodSettingsChange, onSettingsChange: handleFloodSettingsChange,
}, },
label: intl.formatMessage({ label: i18n._('settings.tabs.bandwidth'),
id: 'settings.tabs.bandwidth',
}),
}, },
connectivity: { connectivity: {
content: ConnectivityTab, content: ConnectivityTab,
props: { props: {
onClientSettingsChange: handleClientSettingsChange, onClientSettingsChange: handleClientSettingsChange,
}, },
label: intl.formatMessage({ label: i18n._('settings.tabs.connectivity'),
id: 'settings.tabs.connectivity',
}),
}, },
resources: { resources: {
content: ResourcesTab, content: ResourcesTab,
props: { props: {
onClientSettingsChange: handleClientSettingsChange, onClientSettingsChange: handleClientSettingsChange,
}, },
label: intl.formatMessage({ label: i18n._('settings.tabs.resources'),
id: 'settings.tabs.resources',
}),
}, },
...(ConfigStore.authMethod !== 'none' ...(ConfigStore.authMethod !== 'none'
? { ? {
authentication: { authentication: {
content: AuthTab, content: AuthTab,
label: intl.formatMessage({ label: i18n._('settings.tabs.authentication'),
id: 'settings.tabs.authentication',
}),
}, },
} }
: {}), : {}),
ui: { ui: {
content: UITab, content: UITab,
label: intl.formatMessage({ label: i18n._('settings.tabs.userinterface'),
id: 'settings.tabs.userinterface',
}),
props: { props: {
onSettingsChange: handleFloodSettingsChange, onSettingsChange: handleFloodSettingsChange,
}, },
}, },
diskusage: { diskusage: {
content: DiskUsageTab, content: DiskUsageTab,
label: intl.formatMessage({ label: i18n._('settings.tabs.diskusage'),
id: 'settings.tabs.diskusage',
}),
props: { props: {
onSettingsChange: handleFloodSettingsChange, onSettingsChange: handleFloodSettingsChange,
}, },
}, },
about: { about: {
content: AboutTab, content: AboutTab,
label: intl.formatMessage({ label: i18n._('settings.tabs.about'),
id: 'settings.tabs.about',
}),
}, },
}; };
@@ -110,9 +96,7 @@ const SettingsModal: FC = () => {
actions={[ actions={[
{ {
clickHandler: null, clickHandler: null,
content: intl.formatMessage({ content: i18n._('button.cancel'),
id: 'button.cancel',
}),
triggerDismiss: true, triggerDismiss: true,
type: 'tertiary', type: 'tertiary',
}, },
@@ -132,17 +116,13 @@ const SettingsModal: FC = () => {
}); });
}, },
isLoading: isSavingSettings, isLoading: isSavingSettings,
content: intl.formatMessage({ content: i18n._('button.save'),
id: 'button.save',
}),
triggerDismiss: false, triggerDismiss: false,
type: 'primary', type: 'primary',
}, },
]} ]}
size="large" size="large"
heading={intl.formatMessage({ heading={i18n._('settings.tabs.heading')}
id: 'settings.tabs.heading',
})}
orientation={isSmallScreen ? 'horizontal' : 'vertical'} orientation={isSmallScreen ? 'horizontal' : 'vertical'}
tabs={tabs} tabs={tabs}
/> />

View File

@@ -1,5 +1,5 @@
import {FormattedMessage, useIntl} from 'react-intl';
import {FC, useState} from 'react'; import {FC, useState} from 'react';
import {Trans, useLingui} from '@lingui/react';
import {Form, FormRow, Select, SelectItem, Radio} from '@client/ui'; import {Form, FormRow, Select, SelectItem, Radio} from '@client/ui';
import Languages from '@client/constants/Languages'; import Languages from '@client/constants/Languages';
@@ -18,7 +18,7 @@ interface UITabProps {
} }
const UITab: FC<UITabProps> = ({onSettingsChange}: UITabProps) => { const UITab: FC<UITabProps> = ({onSettingsChange}: UITabProps) => {
const intl = useIntl(); const {i18n} = useLingui();
const [torrentListViewSize, setTorrentListViewSize] = useState(SettingStore.floodSettings.torrentListViewSize); const [torrentListViewSize, setTorrentListViewSize] = useState(SettingStore.floodSettings.torrentListViewSize);
const [selectedLanguage, setSelectedLanguage] = useState(SettingStore.floodSettings.language); const [selectedLanguage, setSelectedLanguage] = useState(SettingStore.floodSettings.language);
const [UITagSelectorMode, setUITagSelectorMode] = useState(SettingStore.floodSettings.UITagSelectorMode); const [UITagSelectorMode, setUITagSelectorMode] = useState(SettingStore.floodSettings.UITagSelectorMode);
@@ -46,55 +46,53 @@ const UITab: FC<UITabProps> = ({onSettingsChange}: UITabProps) => {
} }
}}> }}>
<ModalFormSectionHeader key="locale-header"> <ModalFormSectionHeader key="locale-header">
<FormattedMessage id="settings.ui.language" /> <Trans id="settings.ui.language" />
</ModalFormSectionHeader> </ModalFormSectionHeader>
<FormRow key="locale-selection"> <FormRow key="locale-selection">
<Select defaultID={selectedLanguage} id="language"> <Select defaultID={selectedLanguage} id="language">
{Object.keys(Languages).map((languageID) => ( {Object.keys(Languages).map((languageID) => (
<SelectItem key={languageID} id={languageID}> <SelectItem key={languageID} id={languageID}>
{Languages[languageID as 'auto'].id != null {Languages[languageID as 'auto'].id != null
? intl.formatMessage({ ? i18n._(Languages[languageID as 'auto'].id)
id: Languages[languageID as 'auto'].id,
})
: Languages[languageID as Language]} : Languages[languageID as Language]}
</SelectItem> </SelectItem>
))} ))}
</Select> </Select>
</FormRow> </FormRow>
<ModalFormSectionHeader> <ModalFormSectionHeader>
<FormattedMessage id="settings.ui.tag.selector.mode" /> <Trans id="settings.ui.tag.selector.mode" />
</ModalFormSectionHeader> </ModalFormSectionHeader>
<FormRow> <FormRow>
<Radio defaultChecked={UITagSelectorMode === 'single'} groupID="ui-tag-selector-mode" id="single" width="auto"> <Radio defaultChecked={UITagSelectorMode === 'single'} groupID="ui-tag-selector-mode" id="single" width="auto">
<FormattedMessage id="settings.ui.tag.selector.mode.single" /> <Trans id="settings.ui.tag.selector.mode.single" />
</Radio> </Radio>
<Radio defaultChecked={UITagSelectorMode === 'multi'} groupID="ui-tag-selector-mode" id="multi" width="auto"> <Radio defaultChecked={UITagSelectorMode === 'multi'} groupID="ui-tag-selector-mode" id="multi" width="auto">
<FormattedMessage id="settings.ui.tag.selector.mode.multi" /> <Trans id="settings.ui.tag.selector.mode.multi" />
</Radio> </Radio>
</FormRow> </FormRow>
<ModalFormSectionHeader> <ModalFormSectionHeader>
<FormattedMessage id="settings.ui.torrent.list" /> <Trans id="settings.ui.torrent.list" />
</ModalFormSectionHeader> </ModalFormSectionHeader>
<FormRow> <FormRow>
<Radio defaultChecked={torrentListViewSize === 'expanded'} groupID="ui-torrent-size" id="expanded" width="auto"> <Radio defaultChecked={torrentListViewSize === 'expanded'} groupID="ui-torrent-size" id="expanded" width="auto">
<FormattedMessage id="settings.ui.torrent.size.expanded" /> <Trans id="settings.ui.torrent.size.expanded" />
</Radio> </Radio>
<Radio <Radio
defaultChecked={torrentListViewSize === 'condensed'} defaultChecked={torrentListViewSize === 'condensed'}
groupID="ui-torrent-size" groupID="ui-torrent-size"
id="condensed" id="condensed"
width="auto"> width="auto">
<FormattedMessage id="settings.ui.torrent.size.condensed" /> <Trans id="settings.ui.torrent.size.condensed" />
</Radio> </Radio>
</FormRow> </FormRow>
<ModalFormSectionHeader> <ModalFormSectionHeader>
<FormattedMessage id="settings.ui.displayed.details" /> <Trans id="settings.ui.displayed.details" />
</ModalFormSectionHeader> </ModalFormSectionHeader>
<FormRow> <FormRow>
<TorrentListColumnsList torrentListViewSize={torrentListViewSize} onSettingsChange={onSettingsChange} /> <TorrentListColumnsList torrentListViewSize={torrentListViewSize} onSettingsChange={onSettingsChange} />
</FormRow> </FormRow>
<ModalFormSectionHeader> <ModalFormSectionHeader>
<FormattedMessage id="settings.ui.displayed.context.menu.items" /> <Trans id="settings.ui.displayed.context.menu.items" />
</ModalFormSectionHeader> </ModalFormSectionHeader>
<FormRow> <FormRow>
<TorrentContextMenuActionsList onSettingsChange={onSettingsChange} /> <TorrentContextMenuActionsList onSettingsChange={onSettingsChange} />

View File

@@ -1,5 +1,5 @@
import {Component} from 'react'; import {Component} from 'react';
import {FormattedMessage} from 'react-intl'; import {Trans} from '@lingui/react';
import {Checkbox} from '@client/ui'; import {Checkbox} from '@client/ui';
import DiskUsageStore from '@client/stores/DiskUsageStore'; import DiskUsageStore from '@client/stores/DiskUsageStore';
@@ -88,7 +88,7 @@ class MountPointsList extends Component<MountPointsListProps, MountPointsListSta
<Checkbox <Checkbox
defaultChecked={visible} defaultChecked={visible}
onClick={(event) => this.handleCheckboxValueChange(id, (event.target as HTMLInputElement).checked)}> onClick={(event) => this.handleCheckboxValueChange(id, (event.target as HTMLInputElement).checked)}>
<FormattedMessage id="settings.diskusage.show" /> <Trans id="settings.diskusage.show" />
</Checkbox> </Checkbox>
</span> </span>
); );

View File

@@ -1,5 +1,5 @@
import {Component} from 'react'; import {Component} from 'react';
import {FormattedMessage} from 'react-intl'; import {Trans} from '@lingui/react';
import {Checkbox} from '@client/ui'; import {Checkbox} from '@client/ui';
import SettingStore from '@client/stores/SettingStore'; import SettingStore from '@client/stores/SettingStore';
@@ -76,7 +76,7 @@ class TorrentContextMenuActionsList extends Component<
<Checkbox <Checkbox
defaultChecked={visible} defaultChecked={visible}
onClick={(event) => this.handleCheckboxValueChange(id, (event.target as HTMLInputElement).checked)}> onClick={(event) => this.handleCheckboxValueChange(id, (event.target as HTMLInputElement).checked)}>
<FormattedMessage id="settings.ui.torrent.context.menu.items.show" /> <Trans id="settings.ui.torrent.context.menu.items.show" />
</Checkbox> </Checkbox>
</span> </span>
); );
@@ -85,7 +85,7 @@ class TorrentContextMenuActionsList extends Component<
const content = ( const content = (
<div className="sortable-list__content sortable-list__content__wrapper"> <div className="sortable-list__content sortable-list__content__wrapper">
<span className="sortable-list__content sortable-list__content--primary"> <span className="sortable-list__content sortable-list__content--primary">
<FormattedMessage id={TorrentContextMenuActions[id].id} /> <Trans id={TorrentContextMenuActions[id]} />
</span> </span>
{checkbox} {checkbox}
</div> </div>

View File

@@ -1,5 +1,5 @@
import {Component, ReactNode} from 'react'; import {Component, ReactNode} from 'react';
import {FormattedMessage} from 'react-intl'; import {Trans} from '@lingui/react';
import {Checkbox} from '@client/ui'; import {Checkbox} from '@client/ui';
import {Error} from '@client/ui/icons'; import {Error} from '@client/ui/icons';
@@ -95,7 +95,7 @@ class TorrentListColumnsList extends Component<TorrentListColumnsListProps, Torr
<Checkbox <Checkbox
defaultChecked={visible} defaultChecked={visible}
onClick={(event) => this.handleCheckboxValueChange(id, (event.target as HTMLInputElement).checked)}> onClick={(event) => this.handleCheckboxValueChange(id, (event.target as HTMLInputElement).checked)}>
<FormattedMessage id="settings.ui.torrent.details.enabled" /> <Trans id="settings.ui.torrent.details.enabled" />
</Checkbox> </Checkbox>
</span> </span>
); );
@@ -106,7 +106,7 @@ class TorrentListColumnsList extends Component<TorrentListColumnsListProps, Torr
this.props.torrentListViewSize === 'expanded' && this.props.torrentListViewSize === 'expanded' &&
index < this.state.torrentListColumns.length - 1 index < this.state.torrentListColumns.length - 1
) { ) {
const tooltipContent = <FormattedMessage id="settings.ui.torrent.details.tags.placement" />; const tooltipContent = <Trans id="settings.ui.torrent.details.tags.placement" />;
warning = ( warning = (
<Tooltip <Tooltip
@@ -128,7 +128,7 @@ class TorrentListColumnsList extends Component<TorrentListColumnsListProps, Torr
<div className="sortable-list__content sortable-list__content__wrapper"> <div className="sortable-list__content sortable-list__content__wrapper">
{warning} {warning}
<span className="sortable-list__content sortable-list__content--primary"> <span className="sortable-list__content sortable-list__content--primary">
<FormattedMessage id={TorrentListColumns[id].id} /> <Trans id={TorrentListColumns[id]} />
</span> </span>
{checkbox} {checkbox}
</div> </div>

View File

@@ -1,7 +1,7 @@
import classnames from 'classnames'; import classnames from 'classnames';
import {FormattedMessage, useIntl} from 'react-intl';
import {observer} from 'mobx-react'; import {observer} from 'mobx-react';
import {FC, useEffect, useState} from 'react'; import {FC, useEffect, useState} from 'react';
import {Trans, useLingui} from '@lingui/react';
import {Button, Checkbox, Form, FormRow, FormRowItem, Select, SelectItem} from '@client/ui'; import {Button, Checkbox, Form, FormRow, FormRowItem, Select, SelectItem} from '@client/ui';
import ConfigStore from '@client/stores/ConfigStore'; import ConfigStore from '@client/stores/ConfigStore';
@@ -19,7 +19,7 @@ const TorrentContents: FC = observer(() => {
const [contents, setContents] = useState<TorrentContent[]>([]); const [contents, setContents] = useState<TorrentContent[]>([]);
const [itemsTree, setItemsTree] = useState<TorrentContentSelectionTree>({}); const [itemsTree, setItemsTree] = useState<TorrentContentSelectionTree>({});
const [selectedIndices, setSelectedIndices] = useState<number[]>([]); const [selectedIndices, setSelectedIndices] = useState<number[]>([]);
const intl = useIntl(); const {i18n} = useLingui();
useEffect(() => { useEffect(() => {
if (UIStore.activeModal?.id === 'torrent-details') { if (UIStore.activeModal?.id === 'torrent-details') {
@@ -85,7 +85,7 @@ const TorrentContents: FC = observer(() => {
directoryHeadingIconContent = <Disk />; directoryHeadingIconContent = <Disk />;
fileDetailContent = ( fileDetailContent = (
<div className="directory-tree__node directory-tree__node--file"> <div className="directory-tree__node directory-tree__node--file">
<FormattedMessage id="torrents.details.files.loading" /> <Trans id="torrents.details.files.loading" />
</div> </div>
); );
} }
@@ -128,7 +128,7 @@ const TorrentContents: FC = observer(() => {
<div className="directory-tree__selection-toolbar"> <div className="directory-tree__selection-toolbar">
<FormRow align="center"> <FormRow align="center">
<FormRowItem width="one-quarter" grow={false} shrink={false}> <FormRowItem width="one-quarter" grow={false} shrink={false}>
<FormattedMessage <Trans
id="torrents.details.selected.files" id="torrents.details.selected.files"
values={{ values={{
count: selectedIndices.length, count: selectedIndices.length,
@@ -154,7 +154,7 @@ const TorrentContents: FC = observer(() => {
}} }}
grow={false} grow={false}
shrink={false}> shrink={false}>
<FormattedMessage <Trans
id="torrents.details.files.download.file" id="torrents.details.files.download.file"
values={{ values={{
count: selectedIndices.length, count: selectedIndices.length,
@@ -163,23 +163,11 @@ const TorrentContents: FC = observer(() => {
</Button> </Button>
<Select id="file-priority" persistentPlaceholder shrink={false} defaultID=""> <Select id="file-priority" persistentPlaceholder shrink={false} defaultID="">
<SelectItem id={-1} isPlaceholder> <SelectItem id={-1} isPlaceholder>
<FormattedMessage id="torrents.details.selected.files.set.priority" /> <Trans id="torrents.details.selected.files.set.priority" />
</SelectItem>
<SelectItem id={0}>
{intl.formatMessage({
id: 'priority.dont.download',
})}
</SelectItem>
<SelectItem id={1}>
{intl.formatMessage({
id: 'priority.normal',
})}
</SelectItem>
<SelectItem id={2}>
{intl.formatMessage({
id: 'priority.high',
})}
</SelectItem> </SelectItem>
<SelectItem id={0}>{i18n._('priority.dont.download')}</SelectItem>
<SelectItem id={1}>{i18n._('priority.normal')}</SelectItem>
<SelectItem id={2}>{i18n._('priority.high')}</SelectItem>
</Select> </Select>
</FormRow> </FormRow>
</div> </div>

View File

@@ -1,5 +1,5 @@
import {FC} from 'react'; import {FC} from 'react';
import {useIntl} from 'react-intl'; import {useLingui} from '@lingui/react';
import {useMedia} from 'react-use'; import {useMedia} from 'react-use';
import Modal from '../Modal'; import Modal from '../Modal';
@@ -11,40 +11,30 @@ import TorrentPeers from './TorrentPeers';
import TorrentTrackers from './TorrentTrackers'; import TorrentTrackers from './TorrentTrackers';
const TorrentDetailsModal: FC = () => { const TorrentDetailsModal: FC = () => {
const intl = useIntl(); const {i18n} = useLingui();
const isSmallScreen = useMedia('(max-width: 720px)'); const isSmallScreen = useMedia('(max-width: 720px)');
const tabs = { const tabs = {
'torrent-details': { 'torrent-details': {
content: TorrentGeneralInfo, content: TorrentGeneralInfo,
label: intl.formatMessage({ label: i18n._('torrents.details.details'),
id: 'torrents.details.details',
}),
}, },
'torrent-contents': { 'torrent-contents': {
content: TorrentContents, content: TorrentContents,
label: intl.formatMessage({ label: i18n._('torrents.details.files'),
id: 'torrents.details.files',
}),
modalContentClasses: 'modal__content--nested-scroll', modalContentClasses: 'modal__content--nested-scroll',
}, },
'torrent-peers': { 'torrent-peers': {
content: TorrentPeers, content: TorrentPeers,
label: intl.formatMessage({ label: i18n._('torrents.details.peers'),
id: 'torrents.details.peers',
}),
}, },
'torrent-trackers': { 'torrent-trackers': {
content: TorrentTrackers, content: TorrentTrackers,
label: intl.formatMessage({ label: i18n._('torrents.details.trackers'),
id: 'torrents.details.trackers',
}),
}, },
'torrent-mediainfo': { 'torrent-mediainfo': {
content: TorrentMediainfo, content: TorrentMediainfo,
label: intl.formatMessage({ label: i18n._('torrents.details.mediainfo'),
id: 'torrents.details.mediainfo',
}),
modalContentClasses: 'modal__content--nested-scroll', modalContentClasses: 'modal__content--nested-scroll',
}, },
}; };

View File

@@ -1,6 +1,6 @@
import {FC} from 'react'; import {FC} from 'react';
import {FormattedMessage, FormattedNumber, useIntl} from 'react-intl';
import {observer} from 'mobx-react'; import {observer} from 'mobx-react';
import {Trans, useLingui} from '@lingui/react';
import type {TorrentProperties} from '@shared/types/Torrent'; import type {TorrentProperties} from '@shared/types/Torrent';
@@ -16,7 +16,7 @@ const getTags = (tags: TorrentProperties['tags']) =>
)); ));
const TorrentGeneralInfo: FC = observer(() => { const TorrentGeneralInfo: FC = observer(() => {
const intl = useIntl(); const {i18n} = useLingui();
if (UIStore.activeModal?.id !== 'torrent-details') { if (UIStore.activeModal?.id !== 'torrent-details') {
return null; return null;
@@ -39,7 +39,7 @@ const TorrentGeneralInfo: FC = observer(() => {
const VALUE_NOT_AVAILABLE = ( const VALUE_NOT_AVAILABLE = (
<span className="not-available"> <span className="not-available">
<FormattedMessage id="torrents.details.general.none" /> <Trans id="torrents.details.general.none" />
</span> </span>
); );
@@ -49,32 +49,34 @@ const TorrentGeneralInfo: FC = observer(() => {
<tbody> <tbody>
<tr className="torrent-details__table__heading"> <tr className="torrent-details__table__heading">
<td className="torrent-details__table__heading--tertiary" colSpan={2}> <td className="torrent-details__table__heading--tertiary" colSpan={2}>
<FormattedMessage id="torrents.details.general.heading.general" /> <Trans id="torrents.details.general.heading.general" />
</td> </td>
</tr> </tr>
<tr className="torrent-details__detail torrent-details__detail--dateAdded"> <tr className="torrent-details__detail torrent-details__detail--dateAdded">
<td className="torrent-details__detail__label"> <td className="torrent-details__detail__label">
<FormattedMessage id="torrents.details.general.date.added" /> <Trans id="torrents.details.general.date.added" />
</td> </td>
<td className="torrent-details__detail__value"> <td className="torrent-details__detail__value">
{dateAdded {dateAdded
? `${intl.formatDate(dateAdded, { ? `${i18n.date(dateAdded, {
year: 'numeric', year: 'numeric',
month: 'long', month: 'long',
day: '2-digit', day: '2-digit',
})} ${intl.formatTime(dateAdded)}` hour: 'numeric',
minute: 'numeric',
})}`
: VALUE_NOT_AVAILABLE} : VALUE_NOT_AVAILABLE}
</td> </td>
</tr> </tr>
<tr className="torrent-details__detail torrent-details__detail--location"> <tr className="torrent-details__detail torrent-details__detail--location">
<td className="torrent-details__detail__label"> <td className="torrent-details__detail__label">
<FormattedMessage id="torrents.details.general.location" /> <Trans id="torrents.details.general.location" />
</td> </td>
<td className="torrent-details__detail__value">{torrent.directory}</td> <td className="torrent-details__detail__value">{torrent.directory}</td>
</tr> </tr>
<tr className="torrent-details__detail torrent-details__detail--tags"> <tr className="torrent-details__detail torrent-details__detail--tags">
<td className="torrent-details__detail__label"> <td className="torrent-details__detail__label">
<FormattedMessage id="torrents.details.general.tags" /> <Trans id="torrents.details.general.tags" />
</td> </td>
<td className="torrent-details__detail__value"> <td className="torrent-details__detail__value">
{torrent.tags.length ? getTags(torrent.tags) : VALUE_NOT_AVAILABLE} {torrent.tags.length ? getTags(torrent.tags) : VALUE_NOT_AVAILABLE}
@@ -82,76 +84,78 @@ const TorrentGeneralInfo: FC = observer(() => {
</tr> </tr>
<tr className="torrent-details__table__heading"> <tr className="torrent-details__table__heading">
<td className="torrent-details__table__heading--tertiary" colSpan={2}> <td className="torrent-details__table__heading--tertiary" colSpan={2}>
<FormattedMessage id="torrents.details.general.heading.transfer" /> <Trans id="torrents.details.general.heading.transfer" />
</td> </td>
</tr> </tr>
<tr className="torrent-details__detail torrent-details__detail--downloaded"> <tr className="torrent-details__detail torrent-details__detail--downloaded">
<td className="torrent-details__detail__label"> <td className="torrent-details__detail__label">
<FormattedMessage id="torrents.details.general.downloaded" /> <Trans id="torrents.details.general.downloaded" />
</td> </td>
<td className="torrent-details__detail__value"> <td className="torrent-details__detail__value">
<FormattedNumber value={torrent.percentComplete} /> {i18n.number(torrent.percentComplete)}
<em className="unit">%</em> <em className="unit">%</em>
</td> </td>
</tr> </tr>
<tr className="torrent-details__detail torrent-details__detail--peers"> <tr className="torrent-details__detail torrent-details__detail--peers">
<td className="torrent-details__detail__label"> <td className="torrent-details__detail__label">
<FormattedMessage id="torrents.details.general.peers" /> <Trans id="torrents.details.general.peers" />
</td> </td>
<td className="torrent-details__detail__value"> <td className="torrent-details__detail__value">
<FormattedMessage <Trans
id="torrents.details.general.connected" id="torrents.details.general.connected"
values={{ values={{
connectedCount: torrent.peersConnected, connectedCount: torrent.peersConnected,
connected: <FormattedNumber value={torrent.peersConnected} />, connected: i18n.number(torrent.peersConnected),
total: <FormattedNumber value={torrent.peersTotal} />, total: i18n.number(torrent.peersTotal),
}} }}
/> />
</td> </td>
</tr> </tr>
<tr className="torrent-details__detail torrent-details__detail--seeds"> <tr className="torrent-details__detail torrent-details__detail--seeds">
<td className="torrent-details__detail__label"> <td className="torrent-details__detail__label">
<FormattedMessage id="torrents.details.general.seeds" /> <Trans id="torrents.details.general.seeds" />
</td> </td>
<td className="torrent-details__detail__value"> <td className="torrent-details__detail__value">
<FormattedMessage <Trans
id="torrents.details.general.connected" id="torrents.details.general.connected"
values={{ values={{
connectedCount: torrent.seedsConnected, connectedCount: torrent.seedsConnected,
connected: <FormattedNumber value={torrent.seedsConnected} />, connected: i18n.number(torrent.seedsConnected),
total: <FormattedNumber value={torrent.seedsTotal} />, total: i18n.number(torrent.seedsTotal),
}} }}
/> />
</td> </td>
</tr> </tr>
<tr className="torrent-details__table__heading"> <tr className="torrent-details__table__heading">
<td className="torrent-details__table__heading--tertiary" colSpan={2}> <td className="torrent-details__table__heading--tertiary" colSpan={2}>
<FormattedMessage id="torrents.details.general.heading.torrent" /> <Trans id="torrents.details.general.heading.torrent" />
</td> </td>
</tr> </tr>
<tr className="torrent-details__detail torrent-details__detail--created"> <tr className="torrent-details__detail torrent-details__detail--created">
<td className="torrent-details__detail__label"> <td className="torrent-details__detail__label">
<FormattedMessage id="torrents.details.general.date.created" /> <Trans id="torrents.details.general.date.created" />
</td> </td>
<td className="torrent-details__detail__value"> <td className="torrent-details__detail__value">
{creation {creation
? `${intl.formatDate(creation, { ? `${i18n.date(creation, {
year: 'numeric', year: 'numeric',
month: 'long', month: 'long',
day: '2-digit', day: '2-digit',
})} ${intl.formatTime(creation)}` hour: 'numeric',
minute: 'numeric',
})}`
: VALUE_NOT_AVAILABLE} : VALUE_NOT_AVAILABLE}
</td> </td>
</tr> </tr>
<tr className="torrent-details__detail torrent-details__detail--hash"> <tr className="torrent-details__detail torrent-details__detail--hash">
<td className="torrent-details__detail__label"> <td className="torrent-details__detail__label">
<FormattedMessage id="torrents.details.general.hash" /> <Trans id="torrents.details.general.hash" />
</td> </td>
<td className="torrent-details__detail__value">{torrent.hash}</td> <td className="torrent-details__detail__value">{torrent.hash}</td>
</tr> </tr>
<tr className="torrent-details__detail torrent-details__detail--size"> <tr className="torrent-details__detail torrent-details__detail--size">
<td className="torrent-details__detail__label"> <td className="torrent-details__detail__label">
<FormattedMessage id="torrents.details.general.size" /> <Trans id="torrents.details.general.size" />
</td> </td>
<td className="torrent-details__detail__value"> <td className="torrent-details__detail__value">
<Size value={torrent.sizeBytes} /> <Size value={torrent.sizeBytes} />
@@ -159,26 +163,22 @@ const TorrentGeneralInfo: FC = observer(() => {
</tr> </tr>
<tr className="torrent-details__detail torrent-details__detail--type"> <tr className="torrent-details__detail torrent-details__detail--type">
<td className="torrent-details__detail__label"> <td className="torrent-details__detail__label">
<FormattedMessage id="torrents.details.general.type" /> <Trans id="torrents.details.general.type" />
</td> </td>
<td className="torrent-details__detail__value"> <td className="torrent-details__detail__value">
{torrent.isPrivate {torrent.isPrivate
? intl.formatMessage({ ? i18n._('torrents.details.general.type.private')
id: 'torrents.details.general.type.private', : i18n._('torrents.details.general.type.public')}
})
: intl.formatMessage({
id: 'torrents.details.general.type.public',
})}
</td> </td>
</tr> </tr>
<tr className="torrent-details__table__heading"> <tr className="torrent-details__table__heading">
<td className="torrent-details__table__heading--tertiary" colSpan={2}> <td className="torrent-details__table__heading--tertiary" colSpan={2}>
<FormattedMessage id="torrents.details.general.heading.tracker" /> <Trans id="torrents.details.general.heading.tracker" />
</td> </td>
</tr> </tr>
<tr className="torrent-details__detail torrent-details__detail--tracker-message"> <tr className="torrent-details__detail torrent-details__detail--tracker-message">
<td className="torrent-details__detail__label"> <td className="torrent-details__detail__label">
<FormattedMessage id="torrents.details.general.tracker.message" /> <Trans id="torrents.details.general.tracker.message" />
</td> </td>
<td className="torrent-details__detail__value"> <td className="torrent-details__detail__value">
{torrent.message ? torrent.message : VALUE_NOT_AVAILABLE} {torrent.message ? torrent.message : VALUE_NOT_AVAILABLE}

View File

@@ -1,7 +1,7 @@
import classnames from 'classnames'; import classnames from 'classnames';
import {FC, useEffect, useState} from 'react'; import {FC, useEffect, useState} from 'react';
import {FormattedMessage, FormattedNumber} from 'react-intl';
import {observer} from 'mobx-react'; import {observer} from 'mobx-react';
import {Trans, useLingui} from '@lingui/react';
import {Clock, DownloadThick, Ratio, Start, Stop, UploadThick} from '@client/ui/icons'; import {Clock, DownloadThick, Ratio, Start, Stop, UploadThick} from '@client/ui/icons';
import TorrentActions from '@client/actions/TorrentActions'; import TorrentActions from '@client/actions/TorrentActions';
@@ -16,6 +16,7 @@ import ProgressBar from '../../general/ProgressBar';
import Size from '../../general/Size'; import Size from '../../general/Size';
const TorrentHeading: FC = observer(() => { const TorrentHeading: FC = observer(() => {
const {i18n} = useLingui();
const torrent = const torrent =
UIStore.activeModal?.id === 'torrent-details' ? TorrentStore.torrents[UIStore.activeModal.hash] : undefined; UIStore.activeModal?.id === 'torrent-details' ? TorrentStore.torrents[UIStore.activeModal.hash] : undefined;
const [torrentStatus, setTorrentStatus] = useState<'start' | 'stop'>('stop'); const [torrentStatus, setTorrentStatus] = useState<'start' | 'stop'>('stop');
@@ -61,7 +62,7 @@ const TorrentHeading: FC = observer(() => {
</li> </li>
<li className="torrent-details__sub-heading__tertiary"> <li className="torrent-details__sub-heading__tertiary">
<Ratio /> <Ratio />
<FormattedNumber value={torrent.ratio} /> {i18n.number(torrent.ratio)}
</li> </li>
<li className="torrent-details__sub-heading__tertiary"> <li className="torrent-details__sub-heading__tertiary">
<Clock /> <Clock />
@@ -95,7 +96,7 @@ const TorrentHeading: FC = observer(() => {
}); });
}}> }}>
<Start /> <Start />
<FormattedMessage id="torrents.details.actions.start" /> <Trans id="torrents.details.actions.start" />
</li> </li>
<li <li
className={classnames('torrent-details__sub-heading__tertiary', 'torrent-details__action', { className={classnames('torrent-details__sub-heading__tertiary', 'torrent-details__action', {
@@ -109,7 +110,7 @@ const TorrentHeading: FC = observer(() => {
}); });
}}> }}>
<Stop /> <Stop />
<FormattedMessage id="torrents.details.actions.stop" /> <Trans id="torrents.details.actions.stop" />
</li> </li>
</ul> </ul>
</div> </div>

View File

@@ -1,6 +1,6 @@
import axios, {CancelTokenSource} from 'axios'; import axios, {CancelTokenSource} from 'axios';
import {FC, useEffect, useRef, useState} from 'react'; import {FC, useEffect, useRef, useState} from 'react';
import {FormattedMessage, useIntl} from 'react-intl'; import {Trans, useLingui} from '@lingui/react';
import {Button} from '@client/ui'; import {Button} from '@client/ui';
import {Checkmark, Clipboard} from '@client/ui/icons'; import {Checkmark, Clipboard} from '@client/ui/icons';
@@ -10,7 +10,7 @@ import UIStore from '@client/stores/UIStore';
import Tooltip from '../../general/Tooltip'; import Tooltip from '../../general/Tooltip';
const TorrentMediainfo: FC = () => { const TorrentMediainfo: FC = () => {
const intl = useIntl(); const {i18n} = useLingui();
const cancelToken = useRef<CancelTokenSource>(axios.CancelToken.source()); const cancelToken = useRef<CancelTokenSource>(axios.CancelToken.source());
const clipboardRef = useRef<HTMLInputElement>(null); const clipboardRef = useRef<HTMLInputElement>(null);
const [mediainfo, setMediainfo] = useState<string | null>(null); const [mediainfo, setMediainfo] = useState<string | null>(null);
@@ -53,14 +53,12 @@ const TorrentMediainfo: FC = () => {
<div className="mediainfo__toolbar"> <div className="mediainfo__toolbar">
<div className="mediainfo__toolbar__item"> <div className="mediainfo__toolbar__item">
<span className="torrent-details__table__heading--tertiary"> <span className="torrent-details__table__heading--tertiary">
<FormattedMessage id={headingMessageId} /> <Trans id={headingMessageId} />
</span> </span>
</div> </div>
{mediainfo && ( {mediainfo && (
<Tooltip <Tooltip
content={intl.formatMessage({ content={i18n._(isCopiedToClipboard ? 'general.clipboard.copied' : 'general.clipboard.copy')}
id: isCopiedToClipboard ? 'general.clipboard.copied' : 'general.clipboard.copy',
})}
wrapperClassName="tooltip__wrapper mediainfo__toolbar__item"> wrapperClassName="tooltip__wrapper mediainfo__toolbar__item">
<Button <Button
priority="tertiary" priority="tertiary"

View File

@@ -1,5 +1,5 @@
import {FC, Suspense, useEffect, useState} from 'react'; import {FC, Suspense, useEffect, useState} from 'react';
import {FormattedMessage} from 'react-intl'; import {Trans} from '@lingui/react';
import {useInterval} from 'react-use'; import {useInterval} from 'react-use';
import {CheckmarkThick, CountryFlag, Lock, Spinner} from '@client/ui/icons'; import {CheckmarkThick, CountryFlag, Lock, Spinner} from '@client/ui/icons';
@@ -37,7 +37,7 @@ const TorrentPeers: FC = () => {
<thead className="torrent-details__table__heading"> <thead className="torrent-details__table__heading">
<tr> <tr>
<th className="torrent-details__table__heading--primary"> <th className="torrent-details__table__heading--primary">
<FormattedMessage id="torrents.details.peers" /> <Trans id="torrents.details.peers" />
<Badge>{peers.length}</Badge> <Badge>{peers.length}</Badge>
</th> </th>
<th className="torrent-details__table__heading--secondary">DL</th> <th className="torrent-details__table__heading--secondary">DL</th>

View File

@@ -1,5 +1,5 @@
import {FC, useEffect, useState} from 'react'; import {FC, useEffect, useState} from 'react';
import {FormattedMessage} from 'react-intl'; import {Trans} from '@lingui/react';
import type {TorrentTracker} from '@shared/types/TorrentTracker'; import type {TorrentTracker} from '@shared/types/TorrentTracker';
@@ -36,11 +36,11 @@ const TorrentTrackers: FC = () => {
<thead className="torrent-details__table__heading"> <thead className="torrent-details__table__heading">
<tr> <tr>
<th className="torrent-details__table__heading--primary"> <th className="torrent-details__table__heading--primary">
<FormattedMessage id="torrents.details.trackers" /> <Trans id="torrents.details.trackers" />
<Badge>{trackerCount}</Badge> <Badge>{trackerCount}</Badge>
</th> </th>
<th className="torrent-details__table__heading--secondary"> <th className="torrent-details__table__heading--secondary">
<FormattedMessage id="torrents.details.trackers.type" /> <Trans id="torrents.details.trackers.type" />
</th> </th>
</tr> </tr>
</thead> </thead>

View File

@@ -1,6 +1,6 @@
import {FC, ReactNode, ReactNodeArray} from 'react'; import {FC, ReactNode, ReactNodeArray} from 'react';
import {FormattedMessage} from 'react-intl';
import {observer} from 'mobx-react'; import {observer} from 'mobx-react';
import {Trans} from '@lingui/react';
import type {Disk} from '@shared/types/DiskUsage'; import type {Disk} from '@shared/types/DiskUsage';
@@ -46,9 +46,9 @@ const DiskUsage: FC = observer(() => {
<Tooltip <Tooltip
content={ content={
<ul className="diskusage__details-list"> <ul className="diskusage__details-list">
<DiskUsageTooltipItem value={d.used} label={<FormattedMessage id="status.diskusage.used" />} /> <DiskUsageTooltipItem value={d.used} label={<Trans id="status.diskusage.used" />} />
<DiskUsageTooltipItem value={d.avail} label={<FormattedMessage id="status.diskusage.free" />} /> <DiskUsageTooltipItem value={d.avail} label={<Trans id="status.diskusage.free" />} />
<DiskUsageTooltipItem value={d.size} label={<FormattedMessage id="status.diskusage.total" />} /> <DiskUsageTooltipItem value={d.size} label={<Trans id="status.diskusage.total" />} />
</ul> </ul>
} }
position="top" position="top"
@@ -69,7 +69,7 @@ const DiskUsage: FC = observer(() => {
return ( return (
<ul className="sidebar-filter sidebar__item"> <ul className="sidebar-filter sidebar__item">
<li className="sidebar-filter__item sidebar-filter__item--heading"> <li className="sidebar-filter__item sidebar-filter__item--heading">
<FormattedMessage id="status.diskusage.title" /> <Trans id="status.diskusage.title" />
</li> </li>
{diskNodes} {diskNodes}
</ul> </ul>

View File

@@ -1,25 +1,18 @@
import {defineMessages, useIntl} from 'react-intl';
import {FC, useRef} from 'react'; import {FC, useRef} from 'react';
import {useLingui} from '@lingui/react';
import {Feed} from '@client/ui/icons'; import {Feed} from '@client/ui/icons';
import UIActions from '@client/actions/UIActions'; import UIActions from '@client/actions/UIActions';
import Tooltip from '../general/Tooltip'; import Tooltip from '../general/Tooltip';
const MESSAGES = defineMessages({
feeds: {
id: 'sidebar.button.feeds',
},
});
const FeedsButton: FC = () => { const FeedsButton: FC = () => {
const intl = useIntl(); const {i18n} = useLingui();
const label = intl.formatMessage(MESSAGES.feeds);
const tooltipRef = useRef<Tooltip>(null); const tooltipRef = useRef<Tooltip>(null);
return ( return (
<Tooltip <Tooltip
content={label} content={i18n._('sidebar.button.feeds')}
onClick={() => { onClick={() => {
if (tooltipRef.current != null) { if (tooltipRef.current != null) {
tooltipRef.current.dismissTooltip(); tooltipRef.current.dismissTooltip();

View File

@@ -1,5 +1,5 @@
import {FC} from 'react'; import {FC} from 'react';
import {useIntl} from 'react-intl'; import {useLingui} from '@lingui/react';
import AuthActions from '@client/actions/AuthActions'; import AuthActions from '@client/actions/AuthActions';
import ConfigStore from '@client/stores/ConfigStore'; import ConfigStore from '@client/stores/ConfigStore';
@@ -8,7 +8,7 @@ import {Logout} from '@client/ui/icons';
import Tooltip from '../general/Tooltip'; import Tooltip from '../general/Tooltip';
const LogoutButton: FC = () => { const LogoutButton: FC = () => {
const intl = useIntl(); const {i18n} = useLingui();
if (ConfigStore.authMethod === 'none') { if (ConfigStore.authMethod === 'none') {
return null; return null;
@@ -16,9 +16,7 @@ const LogoutButton: FC = () => {
return ( return (
<Tooltip <Tooltip
content={intl.formatMessage({ content={i18n._('sidebar.button.log.out')}
id: 'sidebar.button.log.out',
})}
onClick={() => onClick={() =>
AuthActions.logout().then(() => { AuthActions.logout().then(() => {
window.location.reload(); window.location.reload();

View File

@@ -1,7 +1,7 @@
import classnames from 'classnames'; import classnames from 'classnames';
import {defineMessages, useIntl} from 'react-intl';
import {FC, useEffect, useRef, useState} from 'react'; import {FC, useEffect, useRef, useState} from 'react';
import {observer} from 'mobx-react'; import {observer} from 'mobx-react';
import {useLingui} from '@lingui/react';
import FloodActions from '@client/actions/FloodActions'; import FloodActions from '@client/actions/FloodActions';
import {ChevronLeft, ChevronRight, LoadingIndicatorDots, Notification as NotificationIcon} from '@client/ui/icons'; import {ChevronLeft, ChevronRight, LoadingIndicatorDots, Notification as NotificationIcon} from '@client/ui/icons';
@@ -20,27 +20,6 @@ const fetchNotifications = (paginationStart: number) =>
start: paginationStart, start: paginationStart,
}); });
const MESSAGES = defineMessages({
'notification.torrent.finished.heading': {
id: 'notification.torrent.finished.heading',
},
'notification.torrent.finished.body': {
id: 'notification.torrent.finished.body',
},
'notification.torrent.errored.heading': {
id: 'notification.torrent.errored.heading',
},
'notification.torrent.errored.body': {
id: 'notification.torrent.errored.body',
},
'notification.feed.torrent.added.heading': {
id: 'notification.feed.torrent.added.heading',
},
'notification.feed.torrent.added.body': {
id: 'notification.feed.torrent.added.body',
},
});
interface NotificationTopToolbarProps { interface NotificationTopToolbarProps {
paginationStart: number; paginationStart: number;
notificationTotal: number; notificationTotal: number;
@@ -50,7 +29,7 @@ const NotificationTopToolbar: FC<NotificationTopToolbarProps> = ({
paginationStart, paginationStart,
notificationTotal, notificationTotal,
}: NotificationTopToolbarProps) => { }: NotificationTopToolbarProps) => {
const intl = useIntl(); const {i18n} = useLingui();
if (notificationTotal > NOTIFICATIONS_PER_PAGE) { if (notificationTotal > NOTIFICATIONS_PER_PAGE) {
let countStart = paginationStart + 1; let countStart = paginationStart + 1;
@@ -67,19 +46,13 @@ const NotificationTopToolbar: FC<NotificationTopToolbarProps> = ({
return ( return (
<div className="toolbar toolbar--dark toolbar--top tooltip__toolbar tooltip__content--padding-surrogate"> <div className="toolbar toolbar--dark toolbar--top tooltip__toolbar tooltip__content--padding-surrogate">
<span className="toolbar__item toolbar__item--label"> <span className="toolbar__item toolbar__item--label">
{`${intl.formatMessage({ {`${i18n._('notification.showing')} `}
id: 'notification.showing',
})} `}
<strong> <strong>
{countStart} {countStart}
{` ${intl.formatMessage({ {` ${i18n._('general.to')} `}
id: 'general.to',
})} `}
{countEnd} {countEnd}
</strong> </strong>
{` ${intl.formatMessage({ {` ${i18n._('general.of')} `}
id: 'general.of',
})} `}
<strong>{notificationTotal}</strong> <strong>{notificationTotal}</strong>
</span> </span>
</div> </div>
@@ -95,35 +68,24 @@ interface NotificationItemProps {
} }
const NotificationItem: FC<NotificationItemProps> = ({index, notification}: NotificationItemProps) => { const NotificationItem: FC<NotificationItemProps> = ({index, notification}: NotificationItemProps) => {
const intl = useIntl(); const {i18n} = useLingui();
const date = intl.formatDate(notification.ts, {
year: 'numeric',
month: 'long',
day: '2-digit',
});
const time = intl.formatTime(notification.ts);
return ( return (
<li className="notifications__list__item" key={index}> <li className="notifications__list__item" key={index}>
<div className="notification__heading"> <div className="notification__heading">
<span className="notification__category"> <span className="notification__category">{i18n._(`${notification.id}.heading`)}</span>
{intl.formatMessage(
MESSAGES[`${notification.id}.heading` as keyof typeof MESSAGES] || {id: 'general.error.unknown'},
)}
</span>
{' — '} {' — '}
<span className="notification__timestamp">{`${date} ${intl.formatMessage({ <span className="notification__timestamp">
id: 'general.at', {i18n.date(new Date(notification.ts), {
})} ${time}`}</span> year: 'numeric',
</div> month: 'long',
<div className="notification__message"> day: '2-digit',
{intl.formatMessage( hour: 'numeric',
MESSAGES[`${notification.id}.body` as keyof typeof MESSAGES] || { minute: 'numeric',
id: 'general.error.unknown', })}
}, </span>
notification.data,
)}
</div> </div>
<div className="notification__message">{i18n._(`${notification.id}.body`, notification.data)}</div>
</li> </li>
); );
}; };
@@ -143,7 +105,7 @@ const NotificationBottomToolbar: FC<NotificationBottomToolbarProps> = ({
onClearClick, onClearClick,
onNextClick, onNextClick,
}: NotificationBottomToolbarProps) => { }: NotificationBottomToolbarProps) => {
const intl = useIntl(); const {i18n} = useLingui();
if (notificationTotal > 0) { if (notificationTotal > 0) {
const newerButtonClass = classnames('toolbar__item toolbar__item--button', 'tooltip__content--padding-surrogate', { const newerButtonClass = classnames('toolbar__item toolbar__item--button', 'tooltip__content--padding-surrogate', {
@@ -178,9 +140,7 @@ const NotificationBottomToolbar: FC<NotificationBottomToolbarProps> = ({
className="toolbar__item toolbar__item--button className="toolbar__item toolbar__item--button
tooltip__content--padding-surrogate" tooltip__content--padding-surrogate"
onClick={onClearClick}> onClick={onClearClick}>
{intl.formatMessage({ {i18n._('notification.clear.all')}
id: 'notification.clear.all',
})}
</li> </li>
<li className={olderButtonClass} onClick={onNextClick}> <li className={olderButtonClass} onClick={onNextClick}>
{`${olderFrom} - ${olderTo}`} {`${olderFrom} - ${olderTo}`}
@@ -194,7 +154,7 @@ const NotificationBottomToolbar: FC<NotificationBottomToolbarProps> = ({
}; };
const NotificationsButton: FC = observer(() => { const NotificationsButton: FC = observer(() => {
const intl = useIntl(); const {i18n} = useLingui();
const tooltipRef = useRef<Tooltip>(null); const tooltipRef = useRef<Tooltip>(null);
const notificationsListRef = useRef<HTMLUListElement>(null); const notificationsListRef = useRef<HTMLUListElement>(null);
@@ -264,9 +224,7 @@ const NotificationsButton: FC = observer(() => {
</div> </div>
) : ( ) : (
<div className="notifications tooltip__content--padding-surrogate" style={{textAlign: 'center'}}> <div className="notifications tooltip__content--padding-surrogate" style={{textAlign: 'center'}}>
{intl.formatMessage({ {i18n._('notification.no.notification')}
id: 'notification.no.notification',
})}
</div> </div>
) )
} }

View File

@@ -1,14 +1,14 @@
import classnames from 'classnames'; import classnames from 'classnames';
import {FC, useEffect, useRef} from 'react'; import {FC, useEffect, useRef} from 'react';
import {observer} from 'mobx-react'; import {observer} from 'mobx-react';
import {useIntl} from 'react-intl'; import {useLingui} from '@lingui/react';
import {Close, Search} from '@client/ui/icons'; import {Close, Search} from '@client/ui/icons';
import TorrentFilterStore from '@client/stores/TorrentFilterStore'; import TorrentFilterStore from '@client/stores/TorrentFilterStore';
import UIActions from '@client/actions/UIActions'; import UIActions from '@client/actions/UIActions';
const SearchBox: FC = observer(() => { const SearchBox: FC = observer(() => {
const intl = useIntl(); const {i18n} = useLingui();
const inputRef = useRef<HTMLInputElement>(null); const inputRef = useRef<HTMLInputElement>(null);
const {searchFilter} = TorrentFilterStore.filters; const {searchFilter} = TorrentFilterStore.filters;
@@ -48,9 +48,7 @@ const SearchBox: FC = observer(() => {
className="textbox" className="textbox"
ref={inputRef} ref={inputRef}
type="text" type="text"
placeholder={intl.formatMessage({ placeholder={i18n._('sidebar.search.placeholder')}
id: 'sidebar.search.placeholder',
})}
onChange={(event) => { onChange={(event) => {
UIActions.setTorrentsSearchFilter(event.target.value); UIActions.setTorrentsSearchFilter(event.target.value);
}} }}

View File

@@ -1,25 +1,18 @@
import {defineMessages, useIntl} from 'react-intl';
import {FC, useRef} from 'react'; import {FC, useRef} from 'react';
import {useLingui} from '@lingui/react';
import {Settings} from '@client/ui/icons'; import {Settings} from '@client/ui/icons';
import UIActions from '@client/actions/UIActions'; import UIActions from '@client/actions/UIActions';
import Tooltip from '../general/Tooltip'; import Tooltip from '../general/Tooltip';
const MESSAGES = defineMessages({
settings: {
id: 'sidebar.button.settings',
},
});
const SettingsButton: FC = () => { const SettingsButton: FC = () => {
const intl = useIntl(); const {i18n} = useLingui();
const label = intl.formatMessage(MESSAGES.settings);
const tooltipRef = useRef<Tooltip>(null); const tooltipRef = useRef<Tooltip>(null);
return ( return (
<Tooltip <Tooltip
content={label} content={i18n._('sidebar.button.settings')}
onClick={() => { onClick={() => {
if (tooltipRef.current != null) { if (tooltipRef.current != null) {
tooltipRef.current.dismissTooltip(); tooltipRef.current.dismissTooltip();

View File

@@ -1,6 +1,6 @@
import classnames from 'classnames'; import classnames from 'classnames';
import {FC} from 'react'; import {FC} from 'react';
import {useIntl} from 'react-intl'; import {useLingui} from '@lingui/react';
import Badge from '../general/Badge'; import Badge from '../general/Badge';
@@ -15,7 +15,7 @@ interface SidebarFilterProps {
const SidebarFilter: FC<SidebarFilterProps> = (props: SidebarFilterProps) => { const SidebarFilter: FC<SidebarFilterProps> = (props: SidebarFilterProps) => {
const {isActive, count, slug, icon, handleClick} = props; const {isActive, count, slug, icon, handleClick} = props;
const intl = useIntl(); const {i18n} = useLingui();
const classNames = classnames('sidebar-filter__item', { const classNames = classnames('sidebar-filter__item', {
'is-active': isActive, 'is-active': isActive,
@@ -23,16 +23,12 @@ const SidebarFilter: FC<SidebarFilterProps> = (props: SidebarFilterProps) => {
let {name} = props; let {name} = props;
if (name === '') { if (name === '') {
name = intl.formatMessage({ name = i18n._('filter.all');
id: 'filter.all',
});
} else if (name === 'untagged') { } else if (name === 'untagged') {
if (count === 0) { if (count === 0) {
return null; return null;
} }
name = intl.formatMessage({ name = i18n._('filter.untagged');
id: 'filter.untagged',
});
} }
if (slug === 'checking' || slug === 'error') { if (slug === 'checking' || slug === 'error') {

View File

@@ -1,7 +1,9 @@
import {FC, useRef} from 'react'; import {FC, useRef} from 'react';
import {FormattedMessage, IntlShape, useIntl} from 'react-intl';
import {observer} from 'mobx-react'; import {observer} from 'mobx-react';
import sortedIndex from 'lodash/sortedIndex'; import sortedIndex from 'lodash/sortedIndex';
import {Trans, useLingui} from '@lingui/react';
import type {I18n} from '@lingui/core';
import ClientActions from '@client/actions/ClientActions'; import ClientActions from '@client/actions/ClientActions';
import {Limits} from '@client/ui/icons'; import {Limits} from '@client/ui/icons';
@@ -16,16 +18,16 @@ import Tooltip from '../general/Tooltip';
import type {DropdownItem} from '../general/form-elements/Dropdown'; import type {DropdownItem} from '../general/form-elements/Dropdown';
const HumanReadableSpeed: FC<{bytes: number}> = ({bytes}: {bytes: number}) => const HumanReadableSpeed: FC<{bytes: number}> = ({bytes}: {bytes: number}) =>
bytes === 0 ? <FormattedMessage id="speed.unlimited" /> : <Size value={bytes} isSpeed precision={1} />; bytes === 0 ? <Trans id="speed.unlimited" /> : <Size value={bytes} isSpeed precision={1} />;
const getSpeedList = ({ const getSpeedList = ({
intl, i18n,
direction, direction,
speedLimits, speedLimits,
throttleGlobalDownSpeed, throttleGlobalDownSpeed,
throttleGlobalUpSpeed, throttleGlobalUpSpeed,
}: { }: {
intl: IntlShape; i18n: I18n;
direction: TransferDirection; direction: TransferDirection;
speedLimits: { speedLimits: {
download: Array<number>; download: Array<number>;
@@ -37,8 +39,8 @@ const getSpeedList = ({
const heading = { const heading = {
className: `dropdown__label dropdown__label--${direction}`, className: `dropdown__label dropdown__label--${direction}`,
...(direction === 'download' ...(direction === 'download'
? {displayName: intl.formatMessage({id: 'sidebar.speedlimits.download'})} ? {displayName: i18n._('sidebar.speedlimits.download')}
: {displayName: intl.formatMessage({id: 'sidebar.speedlimits.upload'})}), : {displayName: i18n._('sidebar.speedlimits.upload')}),
selectable: false, selectable: false,
value: null, value: null,
}; };
@@ -92,12 +94,12 @@ const getSpeedList = ({
}; };
const SpeedLimitDropdown: FC = observer(() => { const SpeedLimitDropdown: FC = observer(() => {
const intl = useIntl(); const {i18n} = useLingui();
const tooltipRef = useRef<Tooltip>(null); const tooltipRef = useRef<Tooltip>(null);
const label = intl.formatMessage({id: 'sidebar.button.speedlimits'}); const label = i18n._('sidebar.button.speedlimits');
const speedListOptions = { const speedListOptions = {
intl, i18n,
speedLimits: SettingStore.floodSettings.speedLimits, speedLimits: SettingStore.floodSettings.speedLimits,
throttleGlobalDownSpeed: SettingStore.clientSettings?.throttleGlobalDownSpeed ?? 0, throttleGlobalDownSpeed: SettingStore.clientSettings?.throttleGlobalDownSpeed ?? 0,
throttleGlobalUpSpeed: SettingStore.clientSettings?.throttleGlobalUpSpeed ?? 0, throttleGlobalUpSpeed: SettingStore.clientSettings?.throttleGlobalUpSpeed ?? 0,

View File

@@ -1,6 +1,6 @@
import {FC} from 'react'; import {FC} from 'react';
import {FormattedMessage, useIntl} from 'react-intl';
import {observer} from 'mobx-react'; import {observer} from 'mobx-react';
import {Trans, useLingui} from '@lingui/react';
import {Active, All, Completed, DownloadSmall, Error, Inactive, Stop, Spinner, UploadSmall} from '@client/ui/icons'; import {Active, All, Completed, DownloadSmall, Error, Inactive, Stop, Spinner, UploadSmall} from '@client/ui/icons';
import TorrentFilterStore from '@client/stores/TorrentFilterStore'; import TorrentFilterStore from '@client/stores/TorrentFilterStore';
@@ -11,7 +11,7 @@ import type {TorrentStatus} from '@shared/constants/torrentStatusMap';
import SidebarFilter from './SidebarFilter'; import SidebarFilter from './SidebarFilter';
const StatusFilters: FC = observer(() => { const StatusFilters: FC = observer(() => {
const intl = useIntl(); const {i18n} = useLingui();
const filters: Array<{ const filters: Array<{
label: string; label: string;
@@ -19,65 +19,47 @@ const StatusFilters: FC = observer(() => {
icon: JSX.Element; icon: JSX.Element;
}> = [ }> = [
{ {
label: intl.formatMessage({ label: i18n._('filter.all'),
id: 'filter.all',
}),
slug: '', slug: '',
icon: <All />, icon: <All />,
}, },
{ {
label: intl.formatMessage({ label: i18n._('filter.status.downloading'),
id: 'filter.status.downloading',
}),
slug: 'downloading', slug: 'downloading',
icon: <DownloadSmall />, icon: <DownloadSmall />,
}, },
{ {
label: intl.formatMessage({ label: i18n._('filter.status.seeding'),
id: 'filter.status.seeding',
}),
slug: 'seeding', slug: 'seeding',
icon: <UploadSmall />, icon: <UploadSmall />,
}, },
{ {
label: intl.formatMessage({ label: i18n._('filter.status.checking'),
id: 'filter.status.checking',
}),
slug: 'checking', slug: 'checking',
icon: <Spinner />, icon: <Spinner />,
}, },
{ {
label: intl.formatMessage({ label: i18n._('filter.status.completed'),
id: 'filter.status.completed',
}),
slug: 'complete', slug: 'complete',
icon: <Completed />, icon: <Completed />,
}, },
{ {
label: intl.formatMessage({ label: i18n._('filter.status.stopped'),
id: 'filter.status.stopped',
}),
slug: 'stopped', slug: 'stopped',
icon: <Stop />, icon: <Stop />,
}, },
{ {
label: intl.formatMessage({ label: i18n._('filter.status.active'),
id: 'filter.status.active',
}),
slug: 'active', slug: 'active',
icon: <Active />, icon: <Active />,
}, },
{ {
label: intl.formatMessage({ label: i18n._('filter.status.inactive'),
id: 'filter.status.inactive',
}),
slug: 'inactive', slug: 'inactive',
icon: <Inactive />, icon: <Inactive />,
}, },
{ {
label: intl.formatMessage({ label: i18n._('filter.status.error'),
id: 'filter.status.error',
}),
slug: 'error', slug: 'error',
icon: <Error />, icon: <Error />,
}, },
@@ -98,7 +80,7 @@ const StatusFilters: FC = observer(() => {
return ( return (
<ul className="sidebar-filter sidebar__item"> <ul className="sidebar-filter sidebar__item">
<li className="sidebar-filter__item sidebar-filter__item--heading"> <li className="sidebar-filter__item sidebar-filter__item--heading">
<FormattedMessage id="filter.status.title" /> <Trans id="filter.status.title" />
</li> </li>
{filterElements} {filterElements}
</ul> </ul>

View File

@@ -1,6 +1,6 @@
import {FC} from 'react'; import {FC} from 'react';
import {FormattedMessage} from 'react-intl';
import {observer} from 'mobx-react'; import {observer} from 'mobx-react';
import {Trans} from '@lingui/react';
import SidebarFilter from './SidebarFilter'; import SidebarFilter from './SidebarFilter';
import TorrentFilterStore from '../../stores/TorrentFilterStore'; import TorrentFilterStore from '../../stores/TorrentFilterStore';
@@ -39,7 +39,7 @@ const TagFilters: FC = observer(() => {
return ( return (
<ul className="sidebar-filter sidebar__item"> <ul className="sidebar-filter sidebar__item">
<li className="sidebar-filter__item sidebar-filter__item--heading"> <li className="sidebar-filter__item sidebar-filter__item--heading">
<FormattedMessage id="filter.tag.title" /> <Trans id="filter.tag.title" />
</li> </li>
{filterElements} {filterElements}
</ul> </ul>

View File

@@ -1,5 +1,5 @@
import {FC} from 'react'; import {FC} from 'react';
import {useIntl} from 'react-intl'; import {useLingui} from '@lingui/react';
import ConfigStore from '@client/stores/ConfigStore'; import ConfigStore from '@client/stores/ConfigStore';
import {ThemeSwitch} from '@client/ui/icons'; import {ThemeSwitch} from '@client/ui/icons';
@@ -7,13 +7,11 @@ import {ThemeSwitch} from '@client/ui/icons';
import Tooltip from '../general/Tooltip'; import Tooltip from '../general/Tooltip';
const ThemeSwitchButton: FC = () => { const ThemeSwitchButton: FC = () => {
const intl = useIntl(); const {i18n} = useLingui();
return ( return (
<Tooltip <Tooltip
content={intl.formatMessage({ content={i18n._(ConfigStore.preferDark ? 'sidebar.button.theme.light' : 'sidebar.button.theme.dark')}
id: ConfigStore.preferDark ? 'sidebar.button.theme.light' : 'sidebar.button.theme.dark',
})}
onClick={() => ConfigStore.setUserPreferDark(!ConfigStore.preferDark)} onClick={() => ConfigStore.setUserPreferDark(!ConfigStore.preferDark)}
position="bottom" position="bottom"
wrapperClassName="sidebar__action sidebar__icon-button wrapperClassName="sidebar__action sidebar__icon-button

View File

@@ -1,6 +1,6 @@
import {FC} from 'react'; import {FC} from 'react';
import {FormattedMessage} from 'react-intl';
import {observer} from 'mobx-react'; import {observer} from 'mobx-react';
import {Trans} from '@lingui/react';
import SidebarFilter from './SidebarFilter'; import SidebarFilter from './SidebarFilter';
import TorrentFilterStore from '../../stores/TorrentFilterStore'; import TorrentFilterStore from '../../stores/TorrentFilterStore';
@@ -38,7 +38,7 @@ const TrackerFilters: FC = observer(() => {
return ( return (
<ul className="sidebar-filter sidebar__item"> <ul className="sidebar-filter sidebar__item">
<li className="sidebar-filter__item sidebar-filter__item--heading"> <li className="sidebar-filter__item sidebar-filter__item--heading">
<FormattedMessage id="filter.tracker.title" /> <Trans id="filter.tracker.title" />
</li> </li>
{filterElements} {filterElements}
</ul> </ul>

View File

@@ -1,7 +1,7 @@
import classnames from 'classnames'; import classnames from 'classnames';
import {defineMessages, useIntl} from 'react-intl';
import {FC} from 'react'; import {FC} from 'react';
import {observer} from 'mobx-react'; import {observer} from 'mobx-react';
import {useLingui} from '@lingui/react';
import ClientStatusStore from '@client/stores/ClientStatusStore'; import ClientStatusStore from '@client/stores/ClientStatusStore';
import {Download, InfinityIcon, Upload} from '@client/ui/icons'; import {Download, InfinityIcon, Upload} from '@client/ui/icons';
@@ -15,12 +15,6 @@ import Size from '../general/Size';
import type {TransferRateGraphInspectorPoint} from './TransferRateGraph'; import type {TransferRateGraphInspectorPoint} from './TransferRateGraph';
const messages = defineMessages({
ago: {
id: 'general.ago',
},
});
const icons = { const icons = {
download: <Download />, download: <Download />,
infinity: <InfinityIcon />, infinity: <InfinityIcon />,
@@ -32,7 +26,7 @@ interface TransferRateDetailsProps {
} }
const TransferRateDetails: FC<TransferRateDetailsProps> = observer(({inspectorPoint}: TransferRateDetailsProps) => { const TransferRateDetails: FC<TransferRateDetailsProps> = observer(({inspectorPoint}: TransferRateDetailsProps) => {
const intl = useIntl(); const {i18n} = useLingui();
const getCurrentTransferRate = (direction: TransferDirection, options: {showHoverDuration?: boolean} = {}) => { const getCurrentTransferRate = (direction: TransferDirection, options: {showHoverDuration?: boolean} = {}) => {
const {throttleGlobalDownSpeed = 0, throttleGlobalUpSpeed = 0} = SettingStore.clientSettings || {}; const {throttleGlobalDownSpeed = 0, throttleGlobalUpSpeed = 0} = SettingStore.clientSettings || {};
@@ -73,7 +67,7 @@ const TransferRateDetails: FC<TransferRateDetailsProps> = observer(({inspectorPo
timestamp = ( timestamp = (
<div className={timestampClasses}> <div className={timestampClasses}>
<Duration <Duration
suffix={intl.formatMessage(messages.ago)} suffix={i18n._('general.ago')}
value={Math.trunc((Date.now() - inspectorPoint.nearestTimestamp) / 1000)} value={Math.trunc((Date.now() - inspectorPoint.nearestTimestamp) / 1000)}
/> />
</div> </div>

View File

@@ -1,7 +1,7 @@
import classnames from 'classnames'; import classnames from 'classnames';
import {FC} from 'react'; import {FC} from 'react';
import {observer} from 'mobx-react'; import {observer} from 'mobx-react';
import {useIntl} from 'react-intl'; import {useLingui} from '@lingui/react';
import {Add, Menu, Remove, Start, Stop} from '@client/ui/icons'; import {Add, Menu, Remove, Start, Stop} from '@client/ui/icons';
import SettingActions from '@client/actions/SettingActions'; import SettingActions from '@client/actions/SettingActions';
@@ -14,7 +14,7 @@ import Action from './Action';
import SortDropdown from './SortDropdown'; import SortDropdown from './SortDropdown';
const ActionBar: FC = observer(() => { const ActionBar: FC = observer(() => {
const intl = useIntl(); const {i18n} = useLingui();
const {sortTorrents: sortBy, torrentListViewSize} = SettingStore.floodSettings; const {sortTorrents: sortBy, torrentListViewSize} = SettingStore.floodSettings;
const classes = classnames('action-bar', { const classes = classnames('action-bar', {
@@ -49,9 +49,7 @@ const ActionBar: FC = observer(() => {
<div className="actions action-bar__item action-bar__item--torrent-operations"> <div className="actions action-bar__item action-bar__item--torrent-operations">
<div className="action-bar__group"> <div className="action-bar__group">
<Action <Action
label={intl.formatMessage({ label={i18n._('actionbar.button.start.torrent')}
id: 'actionbar.button.start.torrent',
})}
slug="start-torrent" slug="start-torrent"
icon={<Start />} icon={<Start />}
clickHandler={() => clickHandler={() =>
@@ -61,9 +59,7 @@ const ActionBar: FC = observer(() => {
} }
/> />
<Action <Action
label={intl.formatMessage({ label={i18n._('actionbar.button.stop.torrent')}
id: 'actionbar.button.stop.torrent',
})}
slug="stop-torrent" slug="stop-torrent"
icon={<Stop />} icon={<Stop />}
clickHandler={() => clickHandler={() =>
@@ -75,17 +71,13 @@ const ActionBar: FC = observer(() => {
</div> </div>
<div className="action-bar__group action-bar__group--has-divider"> <div className="action-bar__group action-bar__group--has-divider">
<Action <Action
label={intl.formatMessage({ label={i18n._('actionbar.button.add.torrent')}
id: 'actionbar.button.add.torrent',
})}
slug="add-torrent" slug="add-torrent"
icon={<Add />} icon={<Add />}
clickHandler={() => UIActions.displayModal({id: 'add-torrents'})} clickHandler={() => UIActions.displayModal({id: 'add-torrents'})}
/> />
<Action <Action
label={intl.formatMessage({ label={i18n._('actionbar.button.remove.torrent')}
id: 'actionbar.button.remove.torrent',
})}
slug="remove-torrent" slug="remove-torrent"
icon={<Remove />} icon={<Remove />}
clickHandler={() => clickHandler={() =>

View File

@@ -1,5 +1,5 @@
import {FC} from 'react'; import {FC} from 'react';
import {FormattedMessage, useIntl} from 'react-intl'; import {Trans, useLingui} from '@lingui/react';
import type {FloodSettings} from '@shared/types/FloodSettings'; import type {FloodSettings} from '@shared/types/FloodSettings';
@@ -29,7 +29,7 @@ interface SortDropdownProps {
const SortDropdown: FC<SortDropdownProps> = (props: SortDropdownProps) => { const SortDropdown: FC<SortDropdownProps> = (props: SortDropdownProps) => {
const {direction, selectedProperty, onSortChange} = props; const {direction, selectedProperty, onSortChange} = props;
const intl = useIntl(); const {i18n} = useLingui();
if (selectedProperty == null) { if (selectedProperty == null) {
return null; return null;
@@ -38,10 +38,10 @@ const SortDropdown: FC<SortDropdownProps> = (props: SortDropdownProps) => {
const header = ( const header = (
<button className="dropdown__button" type="button"> <button className="dropdown__button" type="button">
<label className="dropdown__label"> <label className="dropdown__label">
<FormattedMessage id="torrents.sort.title" /> <Trans id="torrents.sort.title" />
</label> </label>
<span className="dropdown__value"> <span className="dropdown__value">
<FormattedMessage id={TorrentListColumns[selectedProperty]?.id || TorrentListColumns.dateAdded.id} /> <Trans id={TorrentListColumns[selectedProperty]} />
</span> </span>
</button> </button>
); );
@@ -56,7 +56,7 @@ const SortDropdown: FC<SortDropdownProps> = (props: SortDropdownProps) => {
return { return {
displayName: ( displayName: (
<div className="sort-dropdown__item"> <div className="sort-dropdown__item">
{intl.formatMessage(TorrentListColumns[sortProp])} {i18n._(TorrentListColumns[sortProp])}
{directionIndicator} {directionIndicator}
</div> </div>
), ),

View File

@@ -1,7 +1,7 @@
import classnames from 'classnames'; import classnames from 'classnames';
import {forwardRef, MutableRefObject, ReactNodeArray, useRef, useState} from 'react'; import {forwardRef, MutableRefObject, ReactNodeArray, useRef, useState} from 'react';
import {FormattedMessage, useIntl} from 'react-intl';
import {observer} from 'mobx-react'; import {observer} from 'mobx-react';
import {Trans, useLingui} from '@lingui/react';
import {useEnsuredForwardedRef} from 'react-use'; import {useEnsuredForwardedRef} from 'react-use';
import TorrentListColumns, {TorrentListColumn} from '../../constants/TorrentListColumns'; import TorrentListColumns, {TorrentListColumn} from '../../constants/TorrentListColumns';
@@ -28,7 +28,7 @@ const TableHeading = observer(
const tableHeading = useEnsuredForwardedRef<HTMLDivElement>(ref as MutableRefObject<HTMLDivElement>); const tableHeading = useEnsuredForwardedRef<HTMLDivElement>(ref as MutableRefObject<HTMLDivElement>);
const resizeLine = useRef<HTMLDivElement>(null); const resizeLine = useRef<HTMLDivElement>(null);
const intl = useIntl(); const {i18n} = useLingui();
const handlePointerMove = (event: PointerEvent) => { const handlePointerMove = (event: PointerEvent) => {
let widthDelta = 0; let widthDelta = 0;
@@ -79,7 +79,7 @@ const TableHeading = observer(
return accumulator; return accumulator;
} }
const labelID = TorrentListColumns[id]?.id; const labelID = TorrentListColumns[id];
if (labelID == null) { if (labelID == null) {
return accumulator; return accumulator;
} }
@@ -121,12 +121,8 @@ const TableHeading = observer(
accumulator.push( accumulator.push(
<div className={classes} key={id} onClick={() => onCellClick(id)} style={{width: `${width}px`}}> <div className={classes} key={id} onClick={() => onCellClick(id)} style={{width: `${width}px`}}>
<span <span className="table__heading__label" title={i18n._(labelID)}>
className="table__heading__label" <Trans id={labelID} />
title={intl.formatMessage({
id: labelID,
})}>
<FormattedMessage id={labelID} />
</span> </span>
{handle} {handle}
</div>, </div>,

View File

@@ -1,7 +1,7 @@
import {FC, ReactNode, useEffect, useRef} from 'react'; import {FC, ReactNode, useEffect, useRef} from 'react';
import {FormattedMessage} from 'react-intl';
import {observer} from 'mobx-react'; import {observer} from 'mobx-react';
import {reaction} from 'mobx'; import {reaction} from 'mobx';
import {Trans} from '@lingui/react';
import type {FixedSizeList} from 'react-window'; import type {FixedSizeList} from 'react-window';
@@ -54,7 +54,7 @@ const TorrentList: FC = observer(() => {
content = ( content = (
<div className="torrents__alert__wrapper"> <div className="torrents__alert__wrapper">
<div className="torrents__alert"> <div className="torrents__alert">
<FormattedMessage id="torrents.list.cannot.connect" /> <Trans id="torrents.list.cannot.connect" />
</div> </div>
</div> </div>
); );
@@ -62,7 +62,7 @@ const TorrentList: FC = observer(() => {
content = ( content = (
<div className="torrents__alert__wrapper"> <div className="torrents__alert__wrapper">
<div className="torrents__alert"> <div className="torrents__alert">
<FormattedMessage id="torrents.list.no.torrents" /> <Trans id="torrents.list.no.torrents" />
</div> </div>
{TorrentFilterStore.isFilterActive && ( {TorrentFilterStore.isFilterActive && (
<div className="torrents__alert__action"> <div className="torrents__alert__action">
@@ -71,7 +71,7 @@ const TorrentList: FC = observer(() => {
TorrentFilterStore.clearAllFilters(); TorrentFilterStore.clearAllFilters();
}} }}
priority="tertiary"> priority="tertiary">
<FormattedMessage id="torrents.list.clear.filters" /> <Trans id="torrents.list.clear.filters" />
</Button> </Button>
</div> </div>
)} )}
@@ -148,7 +148,7 @@ const TorrentList: FC = observer(() => {
<div className="dropzone__icon"> <div className="dropzone__icon">
<Files /> <Files />
</div> </div>
<FormattedMessage id="torrents.list.drop" /> <Trans id="torrents.list.drop" />
</div> </div>
</div> </div>
</TorrentListDropzone> </TorrentListDropzone>

View File

@@ -39,7 +39,7 @@ const getContextMenuItems = (torrent: TorrentProperties): Array<ContextMenuItem>
{ {
type: 'action', type: 'action',
action: 'start', action: 'start',
label: TorrentContextMenuActions.start.id, label: TorrentContextMenuActions.start,
clickHandler: () => { clickHandler: () => {
TorrentActions.startTorrents({ TorrentActions.startTorrents({
hashes: TorrentStore.selectedTorrents, hashes: TorrentStore.selectedTorrents,
@@ -49,7 +49,7 @@ const getContextMenuItems = (torrent: TorrentProperties): Array<ContextMenuItem>
{ {
type: 'action', type: 'action',
action: 'stop', action: 'stop',
label: TorrentContextMenuActions.stop.id, label: TorrentContextMenuActions.stop,
clickHandler: () => { clickHandler: () => {
TorrentActions.stopTorrents({ TorrentActions.stopTorrents({
hashes: TorrentStore.selectedTorrents, hashes: TorrentStore.selectedTorrents,
@@ -59,7 +59,7 @@ const getContextMenuItems = (torrent: TorrentProperties): Array<ContextMenuItem>
{ {
type: 'action', type: 'action',
action: 'remove', action: 'remove',
label: TorrentContextMenuActions.remove.id, label: TorrentContextMenuActions.remove,
clickHandler: () => { clickHandler: () => {
UIActions.displayModal({id: 'remove-torrents'}); UIActions.displayModal({id: 'remove-torrents'});
}, },
@@ -67,7 +67,7 @@ const getContextMenuItems = (torrent: TorrentProperties): Array<ContextMenuItem>
{ {
type: 'action', type: 'action',
action: 'checkHash', action: 'checkHash',
label: TorrentContextMenuActions.checkHash.id, label: TorrentContextMenuActions.checkHash,
clickHandler: () => { clickHandler: () => {
TorrentActions.checkHash({ TorrentActions.checkHash({
hashes: TorrentStore.selectedTorrents, hashes: TorrentStore.selectedTorrents,
@@ -80,7 +80,7 @@ const getContextMenuItems = (torrent: TorrentProperties): Array<ContextMenuItem>
{ {
type: 'action', type: 'action',
action: 'setTaxonomy', action: 'setTaxonomy',
label: TorrentContextMenuActions.setTaxonomy.id, label: TorrentContextMenuActions.setTaxonomy,
clickHandler: () => { clickHandler: () => {
UIActions.displayModal({id: 'set-taxonomy'}); UIActions.displayModal({id: 'set-taxonomy'});
}, },
@@ -88,7 +88,7 @@ const getContextMenuItems = (torrent: TorrentProperties): Array<ContextMenuItem>
{ {
type: 'action', type: 'action',
action: 'move', action: 'move',
label: TorrentContextMenuActions.move.id, label: TorrentContextMenuActions.move,
clickHandler: () => { clickHandler: () => {
UIActions.displayModal({id: 'move-torrents'}); UIActions.displayModal({id: 'move-torrents'});
}, },
@@ -96,7 +96,7 @@ const getContextMenuItems = (torrent: TorrentProperties): Array<ContextMenuItem>
{ {
type: 'action', type: 'action',
action: 'setTrackers', action: 'setTrackers',
label: TorrentContextMenuActions.setTrackers.id, label: TorrentContextMenuActions.setTrackers,
clickHandler: () => { clickHandler: () => {
UIActions.displayModal({id: 'set-trackers'}); UIActions.displayModal({id: 'set-trackers'});
}, },
@@ -107,7 +107,7 @@ const getContextMenuItems = (torrent: TorrentProperties): Array<ContextMenuItem>
{ {
type: 'action', type: 'action',
action: 'torrentDetails', action: 'torrentDetails',
label: TorrentContextMenuActions.torrentDetails.id, label: TorrentContextMenuActions.torrentDetails,
clickHandler: () => { clickHandler: () => {
UIActions.displayModal({ UIActions.displayModal({
id: 'torrent-details', id: 'torrent-details',
@@ -118,7 +118,7 @@ const getContextMenuItems = (torrent: TorrentProperties): Array<ContextMenuItem>
{ {
type: 'action', type: 'action',
action: 'downloadContents', action: 'downloadContents',
label: TorrentContextMenuActions.downloadContents.id, label: TorrentContextMenuActions.downloadContents,
clickHandler: (e) => { clickHandler: (e) => {
e.preventDefault(); e.preventDefault();
@@ -136,7 +136,7 @@ const getContextMenuItems = (torrent: TorrentProperties): Array<ContextMenuItem>
{ {
type: 'action', type: 'action',
action: 'downloadMetainfo', action: 'downloadMetainfo',
label: TorrentContextMenuActions.downloadMetainfo.id, label: TorrentContextMenuActions.downloadMetainfo,
clickHandler: (e) => { clickHandler: (e) => {
e.preventDefault(); e.preventDefault();
@@ -154,7 +154,7 @@ const getContextMenuItems = (torrent: TorrentProperties): Array<ContextMenuItem>
{ {
type: 'action', type: 'action',
action: 'generateMagnet', action: 'generateMagnet',
label: TorrentContextMenuActions.generateMagnet.id, label: TorrentContextMenuActions.generateMagnet,
clickHandler: () => { clickHandler: () => {
UIActions.displayModal({id: 'generate-magnet'}); UIActions.displayModal({id: 'generate-magnet'});
}, },
@@ -162,7 +162,7 @@ const getContextMenuItems = (torrent: TorrentProperties): Array<ContextMenuItem>
{ {
type: 'action', type: 'action',
action: 'setInitialSeeding', action: 'setInitialSeeding',
label: TorrentContextMenuActions.setInitialSeeding.id, label: TorrentContextMenuActions.setInitialSeeding,
clickHandler: () => { clickHandler: () => {
const {selectedTorrents} = TorrentStore; const {selectedTorrents} = TorrentStore;
TorrentActions.setInitialSeeding({ TorrentActions.setInitialSeeding({
@@ -176,7 +176,7 @@ const getContextMenuItems = (torrent: TorrentProperties): Array<ContextMenuItem>
{ {
type: 'action', type: 'action',
action: 'setSequential', action: 'setSequential',
label: TorrentContextMenuActions.setSequential.id, label: TorrentContextMenuActions.setSequential,
clickHandler: () => { clickHandler: () => {
const {selectedTorrents} = TorrentStore; const {selectedTorrents} = TorrentStore;
TorrentActions.setSequential({ TorrentActions.setSequential({
@@ -190,7 +190,7 @@ const getContextMenuItems = (torrent: TorrentProperties): Array<ContextMenuItem>
{ {
type: 'action', type: 'action',
action: 'setPriority', action: 'setPriority',
label: TorrentContextMenuActions.setPriority.id, label: TorrentContextMenuActions.setPriority,
clickHandler: () => { clickHandler: () => {
if (changePriorityFuncRef.current != null) { if (changePriorityFuncRef.current != null) {
TorrentActions.setPriority({ TorrentActions.setPriority({

View File

@@ -1,6 +1,6 @@
import {FormattedNumber} from 'react-intl';
import {forwardRef} from 'react'; import {forwardRef} from 'react';
import {observer} from 'mobx-react'; import {observer} from 'mobx-react';
import {useLingui} from '@lingui/react';
import ProgressBar from '../general/ProgressBar'; import ProgressBar from '../general/ProgressBar';
import SettingStore from '../../stores/SettingStore'; import SettingStore from '../../stores/SettingStore';
@@ -35,6 +35,7 @@ const TorrentListRowExpanded = observer(
}: TorrentListRowExpandedProps, }: TorrentListRowExpandedProps,
ref, ref,
) => { ) => {
const {i18n} = useLingui();
const columns = SettingStore.floodSettings.torrentListColumns; const columns = SettingStore.floodSettings.torrentListColumns;
const primarySection: React.ReactNodeArray = [ const primarySection: React.ReactNodeArray = [
@@ -57,7 +58,7 @@ const TorrentListRowExpanded = observer(
column="percentComplete" column="percentComplete"
content={(torrent) => ( content={(torrent) => (
<span> <span>
<FormattedNumber value={torrent.percentComplete} maximumFractionDigits={1} /> {i18n.number(torrent.percentComplete, {maximumFractionDigits: 1})}
<em className="unit">%</em> <em className="unit">%</em>
&nbsp;&mdash;&nbsp; &nbsp;&mdash;&nbsp;
<Size value={torrent.downTotal} /> <Size value={torrent.downTotal} />

View File

@@ -1,46 +1,18 @@
const TorrentContextMenuActions = { const TorrentContextMenuActions = {
start: { start: 'torrents.list.context.start',
id: 'torrents.list.context.start', stop: 'torrents.list.context.stop',
}, remove: 'torrents.list.context.remove',
stop: { checkHash: 'torrents.list.context.check.hash',
id: 'torrents.list.context.stop', setTaxonomy: 'torrents.list.context.set.tags',
}, move: 'torrents.list.context.move',
remove: { setTrackers: 'torrents.list.context.set.trackers',
id: 'torrents.list.context.remove', torrentDetails: 'torrents.list.context.details',
}, downloadContents: 'torrents.list.context.download.contents',
checkHash: { downloadMetainfo: 'torrents.list.context.download.metainfo',
id: 'torrents.list.context.check.hash', generateMagnet: 'torrents.list.context.generate.magnet',
}, setInitialSeeding: 'torrents.list.context.initial.seeding',
setTaxonomy: { setSequential: 'torrents.list.context.sequential',
id: 'torrents.list.context.set.tags', setPriority: 'torrents.list.context.priority',
},
move: {
id: 'torrents.list.context.move',
},
setTrackers: {
id: 'torrents.list.context.set.trackers',
},
torrentDetails: {
id: 'torrents.list.context.details',
},
downloadContents: {
id: 'torrents.list.context.download.contents',
},
downloadMetainfo: {
id: 'torrents.list.context.download.metainfo',
},
generateMagnet: {
id: 'torrents.list.context.generate.magnet',
},
setInitialSeeding: {
id: 'torrents.list.context.initial.seeding',
},
setSequential: {
id: 'torrents.list.context.sequential',
},
setPriority: {
id: 'torrents.list.context.priority',
},
} as const; } as const;
export default TorrentContextMenuActions; export default TorrentContextMenuActions;

View File

@@ -1,61 +1,23 @@
const TorrentListColumns = { const TorrentListColumns = {
dateAdded: { dateAdded: 'torrents.properties.date.added',
id: 'torrents.properties.date.added', downRate: 'torrents.properties.download.speed',
}, downTotal: 'torrents.properties.download.total',
downRate: { eta: 'torrents.properties.eta',
id: 'torrents.properties.download.speed', name: 'torrents.properties.name',
}, peers: 'torrents.properties.peers',
downTotal: { percentComplete: 'torrents.properties.percentage',
id: 'torrents.properties.download.total', ratio: 'torrents.properties.ratio',
}, seeds: 'torrents.properties.seeds',
eta: { sizeBytes: 'torrents.properties.size',
id: 'torrents.properties.eta', tags: 'torrents.properties.tags',
}, upRate: 'torrents.properties.upload.speed',
name: { upTotal: 'torrents.properties.upload.total',
id: 'torrents.properties.name', dateCreated: 'torrents.properties.creation.date',
}, directory: 'torrents.properties.directory',
peers: { hash: 'torrents.properties.hash',
id: 'torrents.properties.peers', isPrivate: 'torrents.properties.is.private',
}, message: 'torrents.properties.tracker.message',
percentComplete: { trackerURIs: 'torrents.properties.trackers',
id: 'torrents.properties.percentage',
},
ratio: {
id: 'torrents.properties.ratio',
},
seeds: {
id: 'torrents.properties.seeds',
},
sizeBytes: {
id: 'torrents.properties.size',
},
tags: {
id: 'torrents.properties.tags',
},
upRate: {
id: 'torrents.properties.upload.speed',
},
upTotal: {
id: 'torrents.properties.upload.total',
},
dateCreated: {
id: 'torrents.properties.creation.date',
},
directory: {
id: 'torrents.properties.directory',
},
hash: {
id: 'torrents.properties.hash',
},
isPrivate: {
id: 'torrents.properties.is.private',
},
message: {
id: 'torrents.properties.tracker.message',
},
trackerURIs: {
id: 'torrents.properties.trackers',
},
} as const; } as const;
export default TorrentListColumns; export default TorrentListColumns;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

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