Merge pull request #64 from tsynik/MatriX-i18n

MatriX i18n
This commit is contained in:
dancheskus
2021-06-06 21:37:36 +03:00
committed by GitHub
26 changed files with 552 additions and 267 deletions

View File

@@ -34,6 +34,7 @@ OUTPUT="${ROOT}/dist/TorrServer"
#### Build web #### Build web
echo "Build web" echo "Build web"
cd "${ROOT}/web" || exit 1 cd "${ROOT}/web" || exit 1
npm install --silent
npm run --silent build-js npm run --silent build-js
cp "${ROOT}/web/dest/index.html" "${ROOT}/server/web/pages/template/pages/" cp "${ROOT}/web/dest/index.html" "${ROOT}/server/web/pages/template/pages/"

View File

@@ -8,6 +8,8 @@
"axios": "^0.21.1", "axios": "^0.21.1",
"clsx": "^1.1.1", "clsx": "^1.1.1",
"fontsource-roboto": "^4.0.0", "fontsource-roboto": "^4.0.0",
"i18next": "^20.3.1",
"i18next-browser-languagedetector": "^6.1.1",
"konva": "^8.0.1", "konva": "^8.0.1",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"material-ui-image": "^3.3.2", "material-ui-image": "^3.3.2",
@@ -16,6 +18,7 @@
"react-copy-to-clipboard": "^5.0.3", "react-copy-to-clipboard": "^5.0.3",
"react-div-100vh": "^0.6.0", "react-div-100vh": "^0.6.0",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"react-i18next": "^11.10.0",
"react-konva": "^17.0.2-4", "react-konva": "^17.0.2-4",
"react-measure": "^2.5.2", "react-measure": "^2.5.2",
"react-scripts": "4.0.3", "react-scripts": "4.0.3",

File diff suppressed because one or more lines are too long

View File

@@ -11,21 +11,23 @@ import UploadDialog from 'components/Upload'
import { CreditCard as CreditCardIcon, List as ListIcon } from '@material-ui/icons' import { CreditCard as CreditCardIcon, List as ListIcon } from '@material-ui/icons'
import List from '@material-ui/core/List' import List from '@material-ui/core/List'
import CloseServer from 'components/CloseServer' import CloseServer from 'components/CloseServer'
import { useTranslation } from 'react-i18next'
import { AppSidebarStyle } from './style' import { AppSidebarStyle } from './style'
export default function Sidebar({ isDrawerOpen, setIsDonationDialogOpen }) { export default function Sidebar({ isDrawerOpen, setIsDonationDialogOpen }) {
const { t } = useTranslation()
return ( return (
<AppSidebarStyle isDrawerOpen={isDrawerOpen}> <AppSidebarStyle isDrawerOpen={isDrawerOpen}>
<List> <List>
<AddDialogButton /> <AddDialogButton />
<UploadDialog /> <UploadDialog />
<RemoveAll /> <RemoveAll />
<ListItem button component='a' key='Playlist all torrents' target='_blank' href={playlistAllHost()}> <ListItem button component='a' key={t('PlaylistAll')} target='_blank' href={playlistAllHost()}>
<ListItemIcon> <ListItemIcon>
<ListIcon /> <ListIcon />
</ListItemIcon> </ListItemIcon>
<ListItemText primary='Playlist all torrents' /> <ListItemText primary={t('PlaylistAll')} />
</ListItem> </ListItem>
</List> </List>
@@ -44,7 +46,7 @@ export default function Sidebar({ isDrawerOpen, setIsDonationDialogOpen }) {
<ListItemIcon> <ListItemIcon>
<CreditCardIcon /> <CreditCardIcon />
</ListItemIcon> </ListItemIcon>
<ListItemText primary='Donate' /> <ListItemText primary={t('Donate')} />
</ListItem> </ListItem>
</List> </List>
</AppSidebarStyle> </AppSidebarStyle>

View File

@@ -16,7 +16,7 @@ import Sidebar from './Sidebar'
const baseTheme = createMuiTheme({ const baseTheme = createMuiTheme({
overrides: { MuiCssBaseline: { '@global': { html: { WebkitFontSmoothing: 'auto' } } } }, overrides: { MuiCssBaseline: { '@global': { html: { WebkitFontSmoothing: 'auto' } } } },
palette: { primary: { main: '#3fb57a' }, secondary: { main: '#FFA724' }, tonalOffset: 0.2 }, palette: { primary: { main: '#00a572' }, secondary: { main: '#ffa724' }, tonalOffset: 0.2 },
}) })
export default function App() { export default function App() {

View File

@@ -2,6 +2,7 @@ import styled, { css } from 'styled-components'
export const AppWrapper = styled.div` export const AppWrapper = styled.div`
height: 100%; height: 100%;
background: #cbe8d9;
display: grid; display: grid;
grid-template-columns: 60px 1fr; grid-template-columns: 60px 1fr;
grid-template-rows: 60px 1fr; grid-template-rows: 60px 1fr;
@@ -17,7 +18,7 @@ export const CenteredGrid = styled.div`
` `
export const AppHeader = styled.div` export const AppHeader = styled.div`
background: #3fb57a; background: #00a572;
color: rgba(0, 0, 0, 0.87); color: rgba(0, 0, 0, 0.87);
grid-area: head; grid-area: head;
display: flex; display: flex;
@@ -34,7 +35,7 @@ export const AppSidebarStyle = styled.div`
overflow-x: hidden; overflow-x: hidden;
transition: width 195ms cubic-bezier(0.4, 0, 0.6, 1) 0ms; transition: width 195ms cubic-bezier(0.4, 0, 0.6, 1) 0ms;
border-right: 1px solid rgba(0, 0, 0, 0.12); border-right: 1px solid rgba(0, 0, 0, 0.12);
background: #fff; background: #eee;
white-space: nowrap; white-space: nowrap;
`} `}
` `

View File

@@ -1,4 +1,5 @@
import { useState } from 'react' import axios from 'axios'
import { useEffect, useState } from 'react'
import Button from '@material-ui/core/Button' import Button from '@material-ui/core/Button'
import Dialog from '@material-ui/core/Dialog' import Dialog from '@material-ui/core/Dialog'
import DialogActions from '@material-ui/core/DialogActions' import DialogActions from '@material-ui/core/DialogActions'
@@ -8,9 +9,16 @@ import InfoIcon from '@material-ui/icons/Info'
import ListItem from '@material-ui/core/ListItem' import ListItem from '@material-ui/core/ListItem'
import ListItemIcon from '@material-ui/core/ListItemIcon' import ListItemIcon from '@material-ui/core/ListItemIcon'
import ListItemText from '@material-ui/core/ListItemText' import ListItemText from '@material-ui/core/ListItemText'
import { useTranslation } from 'react-i18next'
import { echoHost } from 'utils/Hosts'
export default function AboutDialog() { export default function AboutDialog() {
const { t } = useTranslation()
const [open, setOpen] = useState(false) const [open, setOpen] = useState(false)
const [torrServerVersion, setTorrServerVersion] = useState('')
useEffect(() => {
axios.get(echoHost()).then(({ data }) => setTorrServerVersion(data))
}, [])
return ( return (
<div> <div>
@@ -18,26 +26,30 @@ export default function AboutDialog() {
<ListItemIcon> <ListItemIcon>
<InfoIcon /> <InfoIcon />
</ListItemIcon> </ListItemIcon>
<ListItemText primary='About' /> <ListItemText primary={t('About')} />
</ListItem> </ListItem>
<Dialog open={open} onClose={() => setOpen(false)} aria-labelledby='form-dialog-title' fullWidth maxWidth='lg'> <Dialog open={open} onClose={() => setOpen(false)} aria-labelledby='form-dialog-title' fullWidth maxWidth='lg'>
<DialogTitle id='form-dialog-title'>About</DialogTitle> <DialogTitle id='form-dialog-title'>{t('About')}</DialogTitle>
<DialogContent> <DialogContent>
<center>
<h2>TorrServer {torrServerVersion}</h2>
<a href='https://github.com/YouROK/TorrServer'>https://github.com/YouROK/TorrServer</a>
</center>
<DialogContent> <DialogContent>
<center> <center>
<h2>Thanks to everyone who tested and helped.</h2> <h2>{t('ThanksToEveryone')}</h2>
</center> </center>
<br /> <br />
<h2>Special thanks:</h2> <h2>{t('SpecialThanks')}</h2>
<b>Anacrolix Matt Joiner</b> <a href='https://github.com/anacrolix/'>github.com/anacrolix</a> <b>anacrolix Matt Joiner</b> <a href='https://github.com/anacrolix/'>github.com/anacrolix</a>
<br /> <br />
<b>tsynik nikk Никита</b> <a href='https://github.com/tsynik'>github.com/tsynik</a> <b>nikk</b> <a href='https://github.com/tsynik'>github.com/tsynik</a>
<br /> <br />
<b>dancheskus</b> <a href='https://github.com/dancheskus'>github.com/dancheskus</a> <b>dancheskus</b> <a href='https://github.com/dancheskus'>github.com/dancheskus</a>
<br /> <br />
<b>Tw1cker Руслан Пахнев</b> <a href='https://github.com/Nemiroff'>github.com/Nemiroff</a> <b>tw1cker Руслан Пахнев</b> <a href='https://github.com/Nemiroff'>github.com/Nemiroff</a>
<br /> <br />
<b>SpAwN_LMG</b> <b>SpAwN_LMG</b>
<br /> <br />
@@ -46,7 +58,7 @@ export default function AboutDialog() {
<DialogActions> <DialogActions>
<Button onClick={() => setOpen(false)} color='primary' variant='outlined' autoFocus> <Button onClick={() => setOpen(false)} color='primary' variant='outlined' autoFocus>
Close {t('Close')}
</Button> </Button>
</DialogActions> </DialogActions>
</Dialog> </Dialog>

View File

@@ -7,8 +7,10 @@ import DialogContent from '@material-ui/core/DialogContent'
import DialogTitle from '@material-ui/core/DialogTitle' import DialogTitle from '@material-ui/core/DialogTitle'
import { torrentsHost } from 'utils/Hosts' import { torrentsHost } from 'utils/Hosts'
import axios from 'axios' import axios from 'axios'
import { useTranslation } from 'react-i18next'
export default function AddDialog({ handleClose }) { export default function AddDialog({ handleClose }) {
const { t } = useTranslation()
const [link, setLink] = useState('') const [link, setLink] = useState('')
const [title, setTitle] = useState('') const [title, setTitle] = useState('')
const [poster, setPoster] = useState('') const [poster, setPoster] = useState('')
@@ -23,17 +25,17 @@ export default function AddDialog({ handleClose }) {
return ( return (
<Dialog open onClose={handleClose} aria-labelledby='form-dialog-title' fullWidth> <Dialog open onClose={handleClose} aria-labelledby='form-dialog-title' fullWidth>
<DialogTitle id='form-dialog-title'>Add magnet or link to torrent file</DialogTitle> <DialogTitle id='form-dialog-title'>{t('AddMagnetOrLink')}</DialogTitle>
<DialogContent> <DialogContent>
<TextField onChange={inputTitle} margin='dense' id='title' label='Title' type='text' fullWidth /> <TextField onChange={inputTitle} margin='dense' id='title' label={t('Title')} type='text' fullWidth />
<TextField onChange={inputPoster} margin='dense' id='poster' label='Poster' type='url' fullWidth /> <TextField onChange={inputPoster} margin='dense' id='poster' label={t('Poster')} type='url' fullWidth />
<TextField <TextField
onChange={inputMagnet} onChange={inputMagnet}
autoFocus autoFocus
margin='dense' margin='dense'
id='magnet' id='magnet'
label='Magnet or torrent file link' label={t('MagnetOrTorrentFileLink')}
type='text' type='text'
fullWidth fullWidth
/> />
@@ -41,11 +43,11 @@ export default function AddDialog({ handleClose }) {
<DialogActions> <DialogActions>
<Button onClick={handleClose} color='primary' variant='outlined'> <Button onClick={handleClose} color='primary' variant='outlined'>
Cancel {t('Cancel')}
</Button> </Button>
<Button variant='contained' disabled={!link} onClick={handleSave} color='primary'> <Button variant='contained' disabled={!link} onClick={handleSave} color='primary'>
Add {t('Add')}
</Button> </Button>
</DialogActions> </DialogActions>
</Dialog> </Dialog>

View File

@@ -3,12 +3,13 @@ import ListItemIcon from '@material-ui/core/ListItemIcon'
import LibraryAddIcon from '@material-ui/icons/LibraryAdd' import LibraryAddIcon from '@material-ui/icons/LibraryAdd'
import ListItemText from '@material-ui/core/ListItemText' import ListItemText from '@material-ui/core/ListItemText'
import ListItem from '@material-ui/core/ListItem' import ListItem from '@material-ui/core/ListItem'
import { useTranslation } from 'react-i18next'
import AddDialog from './AddDialog' import AddDialog from './AddDialog'
export default function AddDialogButton() { export default function AddDialogButton() {
const { t } = useTranslation()
const [isDialogOpen, setIsDialogOpen] = useState(false) const [isDialogOpen, setIsDialogOpen] = useState(false)
const handleClickOpen = () => setIsDialogOpen(true) const handleClickOpen = () => setIsDialogOpen(true)
const handleClose = () => setIsDialogOpen(false) const handleClose = () => setIsDialogOpen(false)
@@ -18,7 +19,7 @@ export default function AddDialogButton() {
<ListItemIcon> <ListItemIcon>
<LibraryAddIcon /> <LibraryAddIcon />
</ListItemIcon> </ListItemIcon>
<ListItemText primary='Add from link' /> <ListItemText primary={t('AddFromLink')} />
</ListItem> </ListItem>
{isDialogOpen && <AddDialog handleClose={handleClose} />} {isDialogOpen && <AddDialog handleClose={handleClose} />}

View File

@@ -2,27 +2,29 @@ import { useState } from 'react'
import { Button, Dialog, DialogActions, DialogTitle, ListItem, ListItemIcon, ListItemText } from '@material-ui/core' import { Button, Dialog, DialogActions, DialogTitle, ListItem, ListItemIcon, ListItemText } from '@material-ui/core'
import { PowerSettingsNew as PowerSettingsNewIcon } from '@material-ui/icons' import { PowerSettingsNew as PowerSettingsNewIcon } from '@material-ui/icons'
import { shutdownHost } from 'utils/Hosts' import { shutdownHost } from 'utils/Hosts'
import { useTranslation } from 'react-i18next'
export default function CloseServer() { export default function CloseServer() {
const { t } = useTranslation()
const [open, setOpen] = useState(false) const [open, setOpen] = useState(false)
const closeDialog = () => setOpen(false) const closeDialog = () => setOpen(false)
const openDialog = () => setOpen(true) const openDialog = () => setOpen(true)
return ( return (
<> <>
<ListItem button key='Close server' onClick={openDialog}> <ListItem button key={t('CloseServer')} onClick={openDialog}>
<ListItemIcon> <ListItemIcon>
<PowerSettingsNewIcon /> <PowerSettingsNewIcon />
</ListItemIcon> </ListItemIcon>
<ListItemText primary='Close server' /> <ListItemText primary={t('CloseServer')} />
</ListItem> </ListItem>
<Dialog open={open} onClose={closeDialog}> <Dialog open={open} onClose={closeDialog}>
<DialogTitle>Close server?</DialogTitle> <DialogTitle>{t('CloseServer?')}</DialogTitle>
<DialogActions> <DialogActions>
<Button variant='outlined' onClick={closeDialog} color='primary'> <Button variant='outlined' onClick={closeDialog} color='primary'>
Cancel {t('Cancel')}
</Button> </Button>
<Button <Button
@@ -34,7 +36,7 @@ export default function CloseServer() {
color='primary' color='primary'
autoFocus autoFocus
> >
Ok {t('OK')}
</Button> </Button>
</DialogActions> </DialogActions>
</Dialog> </Dialog>

View File

@@ -2,6 +2,7 @@ import Button from '@material-ui/core/Button'
import { AppBar, IconButton, makeStyles, Toolbar, Typography } from '@material-ui/core' import { AppBar, IconButton, makeStyles, Toolbar, Typography } from '@material-ui/core'
import CloseIcon from '@material-ui/icons/Close' import CloseIcon from '@material-ui/icons/Close'
import { ArrowBack } from '@material-ui/icons' import { ArrowBack } from '@material-ui/icons'
import { useTranslation } from 'react-i18next'
const useStyles = makeStyles(theme => ({ const useStyles = makeStyles(theme => ({
appBar: { position: 'relative' }, appBar: { position: 'relative' },
@@ -9,6 +10,7 @@ const useStyles = makeStyles(theme => ({
})) }))
export default function DialogHeader({ title, onClose, onBack }) { export default function DialogHeader({ title, onClose, onBack }) {
const { t } = useTranslation()
const classes = useStyles() const classes = useStyles()
return ( return (
@@ -24,7 +26,7 @@ export default function DialogHeader({ title, onClose, onBack }) {
{onBack && ( {onBack && (
<Button autoFocus color='inherit' onClick={onClose}> <Button autoFocus color='inherit' onClick={onClose}>
close {t('Close')}
</Button> </Button>
)} )}
</Toolbar> </Toolbar>

View File

@@ -4,6 +4,7 @@ import { humanizeSize } from 'utils/Utils'
import ptt from 'parse-torrent-title' import ptt from 'parse-torrent-title'
import { Button } from '@material-ui/core' import { Button } from '@material-ui/core'
import CopyToClipboard from 'react-copy-to-clipboard' import CopyToClipboard from 'react-copy-to-clipboard'
import { useTranslation } from 'react-i18next'
import { TableStyle, ShortTableWrapper, ShortTable } from './style' import { TableStyle, ShortTableWrapper, ShortTable } from './style'
@@ -11,6 +12,7 @@ const { memo } = require('react')
const Table = memo( const Table = memo(
({ playableFileList, viewedFileList, selectedSeason, seasonAmount, hash }) => { ({ playableFileList, viewedFileList, selectedSeason, seasonAmount, hash }) => {
const { t } = useTranslation()
const preloadBuffer = fileId => fetch(`${streamHost()}?link=${hash}&index=${fileId}&preload`) const preloadBuffer = fileId => fetch(`${streamHost()}?link=${hash}&index=${fileId}&preload`)
const getFileLink = (path, id) => const getFileLink = (path, id) =>
`${streamHost()}/${encodeURIComponent(path.split('\\').pop().split('/').pop())}?link=${hash}&index=${id}&play` `${streamHost()}/${encodeURIComponent(path.split('\\').pop().split('/').pop())}?link=${hash}&index=${id}&play`
@@ -25,13 +27,13 @@ const Table = memo(
<TableStyle> <TableStyle>
<thead> <thead>
<tr> <tr>
<th style={{ width: '0' }}>viewed</th> <th style={{ width: '0' }}>{t('Viewed')}</th>
<th>name</th> <th>{t('Name')}</th>
{fileHasSeasonText && seasonAmount?.length === 1 && <th style={{ width: '0' }}>season</th>} {fileHasSeasonText && seasonAmount?.length === 1 && <th style={{ width: '0' }}>{t('Season')}</th>}
{fileHasEpisodeText && <th style={{ width: '0' }}>episode</th>} {fileHasEpisodeText && <th style={{ width: '0' }}>{t('Episode')}</th>}
{fileHasResolutionText && <th style={{ width: '0' }}>resolution</th>} {fileHasResolutionText && <th style={{ width: '0' }}>{t('Resolution')}</th>}
<th style={{ width: '100px' }}>size</th> <th style={{ width: '100px' }}>{t('Size')}</th>
<th style={{ width: '400px' }}>actions</th> <th style={{ width: '400px' }}>{t('Actions')}</th>
</tr> </tr>
</thead> </thead>
@@ -52,18 +54,18 @@ const Table = memo(
<td data-label='size'>{humanizeSize(length)}</td> <td data-label='size'>{humanizeSize(length)}</td>
<td className='button-cell'> <td className='button-cell'>
<Button onClick={() => preloadBuffer(id)} variant='outlined' color='primary' size='small'> <Button onClick={() => preloadBuffer(id)} variant='outlined' color='primary' size='small'>
Preload {t('Preload')}
</Button> </Button>
<a style={{ textDecoration: 'none' }} href={link} target='_blank' rel='noreferrer'> <a style={{ textDecoration: 'none' }} href={link} target='_blank' rel='noreferrer'>
<Button style={{ width: '100%' }} variant='outlined' color='primary' size='small'> <Button style={{ width: '100%' }} variant='outlined' color='primary' size='small'>
Open link {t('OpenLink')}
</Button> </Button>
</a> </a>
<CopyToClipboard text={link}> <CopyToClipboard text={link}>
<Button variant='outlined' color='primary' size='small'> <Button variant='outlined' color='primary' size='small'>
Copy link {t('CopyLink')}
</Button> </Button>
</CopyToClipboard> </CopyToClipboard>
</td> </td>
@@ -87,7 +89,7 @@ const Table = memo(
<div className='short-table-data'> <div className='short-table-data'>
{isViewed && ( {isViewed && (
<div className='short-table-field'> <div className='short-table-field'>
<div className='short-table-field-name'>viewed</div> <div className='short-table-field-name'>{t('Viewed')}</div>
<div className='short-table-field-value'> <div className='short-table-field-value'>
<div className='short-table-viewed-indicator' /> <div className='short-table-viewed-indicator' />
</div> </div>
@@ -95,41 +97,41 @@ const Table = memo(
)} )}
{fileHasSeasonText && seasonAmount?.length === 1 && ( {fileHasSeasonText && seasonAmount?.length === 1 && (
<div className='short-table-field'> <div className='short-table-field'>
<div className='short-table-field-name'>season</div> <div className='short-table-field-name'>{t('Season')}</div>
<div className='short-table-field-value'>{season}</div> <div className='short-table-field-value'>{season}</div>
</div> </div>
)} )}
{fileHasEpisodeText && ( {fileHasEpisodeText && (
<div className='short-table-field'> <div className='short-table-field'>
<div className='short-table-field-name'>epoisode</div> <div className='short-table-field-name'>{t('Episode')}</div>
<div className='short-table-field-value'>{episode}</div> <div className='short-table-field-value'>{episode}</div>
</div> </div>
)} )}
{fileHasResolutionText && ( {fileHasResolutionText && (
<div className='short-table-field'> <div className='short-table-field'>
<div className='short-table-field-name'>resolution</div> <div className='short-table-field-name'>{t('Resolution')}</div>
<div className='short-table-field-value'>{resolution}</div> <div className='short-table-field-value'>{resolution}</div>
</div> </div>
)} )}
<div className='short-table-field'> <div className='short-table-field'>
<div className='short-table-field-name'>size</div> <div className='short-table-field-name'>{t('Size')}</div>
<div className='short-table-field-value'>{humanizeSize(length)}</div> <div className='short-table-field-value'>{humanizeSize(length)}</div>
</div> </div>
</div> </div>
<div className='short-table-buttons'> <div className='short-table-buttons'>
<Button onClick={() => preloadBuffer(id)} variant='outlined' color='primary' size='small'> <Button onClick={() => preloadBuffer(id)} variant='outlined' color='primary' size='small'>
Preload {t('Preload')}
</Button> </Button>
<a style={{ textDecoration: 'none' }} href={link} target='_blank' rel='noreferrer'> <a style={{ textDecoration: 'none' }} href={link} target='_blank' rel='noreferrer'>
<Button style={{ width: '100%' }} variant='outlined' color='primary' size='small'> <Button style={{ width: '100%' }} variant='outlined' color='primary' size='small'>
Open link {t('OpenLink')}
</Button> </Button>
</a> </a>
<CopyToClipboard text={link}> <CopyToClipboard text={link}>
<Button variant='outlined' color='primary' size='small'> <Button variant='outlined' color='primary' size='small'>
Copy link {t('CopyLink')}
</Button> </Button>
</CopyToClipboard> </CopyToClipboard>
</div> </div>

View File

@@ -1,7 +1,7 @@
export const defaultBorderColor = '#eef2f4' export const defaultBorderColor = '#eef2f4'
export const defaultBackgroundColor = '#fff' export const defaultBackgroundColor = '#fff'
export const completeColor = '#3fb57a' export const completeColor = '#00a572'
export const progressColor = '#00d0d0' export const progressColor = '#ffa724'
export const activeColor = '#000' export const activeColor = '#000'
export const rangeColor = '#9a9aff' export const rangeColor = '#9a9aff'

View File

@@ -4,12 +4,14 @@ import { playlistTorrHost, torrentsHost, viewedHost } from 'utils/Hosts'
import { CopyToClipboard } from 'react-copy-to-clipboard' import { CopyToClipboard } from 'react-copy-to-clipboard'
import { Button } from '@material-ui/core' import { Button } from '@material-ui/core'
import ptt from 'parse-torrent-title' import ptt from 'parse-torrent-title'
import { useTranslation } from 'react-i18next'
import { SmallLabel, MainSectionButtonGroup } from './style' import { SmallLabel, MainSectionButtonGroup } from './style'
import { SectionSubName } from '../style' import { SectionSubName } from '../style'
const TorrentFunctions = memo( const TorrentFunctions = memo(
({ hash, viewedFileList, playableFileList, name, title, setViewedFileList }) => { ({ hash, viewedFileList, playableFileList, name, title, setViewedFileList }) => {
const { t } = useTranslation()
const latestViewedFileId = viewedFileList?.[viewedFileList?.length - 1] const latestViewedFileId = viewedFileList?.[viewedFileList?.length - 1]
const latestViewedFile = playableFileList?.find(({ id }) => id === latestViewedFileId)?.path const latestViewedFile = playableFileList?.find(({ id }) => id === latestViewedFileId)?.path
const isOnlyOnePlayableFile = playableFileList?.length === 1 const isOnlyOnePlayableFile = playableFileList?.length === 1
@@ -24,13 +26,13 @@ const TorrentFunctions = memo(
<> <>
{!isOnlyOnePlayableFile && !!viewedFileList?.length && ( {!isOnlyOnePlayableFile && !!viewedFileList?.length && (
<> <>
<SmallLabel>Download Playlist</SmallLabel> <SmallLabel>{t('DownloadPlaylist')}</SmallLabel>
<SectionSubName mb={10}> <SectionSubName mb={10}>
<strong>Latest file played:</strong> {latestViewedFileData?.title}. <strong>{t('LatestFilePlayed')}</strong> {latestViewedFileData?.title}.
{latestViewedFileData?.season && ( {latestViewedFileData?.season && (
<> <>
{' '} {' '}
Season: {latestViewedFileData?.season}. Episode: {latestViewedFileData?.episode}. {t('Season')}: {latestViewedFileData?.season}. {t('Episode')}: {latestViewedFileData?.episode}.
</> </>
)} )}
</SectionSubName> </SectionSubName>
@@ -38,39 +40,39 @@ const TorrentFunctions = memo(
<MainSectionButtonGroup> <MainSectionButtonGroup>
<a style={{ textDecoration: 'none' }} href={fullPlaylistLink}> <a style={{ textDecoration: 'none' }} href={fullPlaylistLink}>
<Button style={{ width: '100%' }} variant='contained' color='primary' size='large'> <Button style={{ width: '100%' }} variant='contained' color='primary' size='large'>
full {t('Full')}
</Button> </Button>
</a> </a>
<a style={{ textDecoration: 'none' }} href={partialPlaylistLink}> <a style={{ textDecoration: 'none' }} href={partialPlaylistLink}>
<Button style={{ width: '100%' }} variant='contained' color='primary' size='large'> <Button style={{ width: '100%' }} variant='contained' color='primary' size='large'>
from latest file {t('FromLatestFile')}
</Button> </Button>
</a> </a>
</MainSectionButtonGroup> </MainSectionButtonGroup>
</> </>
)} )}
<SmallLabel mb={10}>Torrent State</SmallLabel> <SmallLabel mb={10}>{t('TorrentState')}</SmallLabel>
<MainSectionButtonGroup> <MainSectionButtonGroup>
<Button onClick={() => removeTorrentViews()} variant='contained' color='primary' size='large'> <Button onClick={() => removeTorrentViews()} variant='contained' color='primary' size='large'>
remove views {t('RemoveViews')}
</Button> </Button>
<Button onClick={() => dropTorrent()} variant='contained' color='primary' size='large'> <Button onClick={() => dropTorrent()} variant='contained' color='primary' size='large'>
reset torrent {t('DropTorrent')}
</Button> </Button>
</MainSectionButtonGroup> </MainSectionButtonGroup>
<SmallLabel mb={10}>Info</SmallLabel> <SmallLabel mb={10}>{t('Info')}</SmallLabel>
<MainSectionButtonGroup> <MainSectionButtonGroup>
{(isOnlyOnePlayableFile || !viewedFileList?.length) && ( {(isOnlyOnePlayableFile || !viewedFileList?.length) && (
<a style={{ textDecoration: 'none' }} href={fullPlaylistLink}> <a style={{ textDecoration: 'none' }} href={fullPlaylistLink}>
<Button style={{ width: '100%' }} variant='contained' color='primary' size='large'> <Button style={{ width: '100%' }} variant='contained' color='primary' size='large'>
download playlist {t('DownloadPlaylist')}
</Button> </Button>
</a> </a>
)} )}
<CopyToClipboard text={hash}> <CopyToClipboard text={hash}>
<Button variant='contained' color='primary' size='large'> <Button variant='contained' color='primary' size='large'>
copy hash {t('CopyHash')}
</Button> </Button>
</CopyToClipboard> </CopyToClipboard>
</MainSectionButtonGroup> </MainSectionButtonGroup>

View File

@@ -7,6 +7,7 @@ import axios from 'axios'
import { viewedHost } from 'utils/Hosts' import { viewedHost } from 'utils/Hosts'
import { GETTING_INFO, IN_DB } from 'torrentStates' import { GETTING_INFO, IN_DB } from 'torrentStates'
import CircularProgress from '@material-ui/core/CircularProgress' import CircularProgress from '@material-ui/core/CircularProgress'
import { useTranslation } from 'react-i18next'
import { useUpdateCache, useGetSettings } from './customHooks' import { useUpdateCache, useGetSettings } from './customHooks'
import DialogHeader from './DialogHeader' import DialogHeader from './DialogHeader'
@@ -37,6 +38,7 @@ const Loader = () => (
) )
export default function DialogTorrentDetailsContent({ closeDialog, torrent }) { export default function DialogTorrentDetailsContent({ closeDialog, torrent }) {
const { t } = useTranslation()
const [isLoading, setIsLoading] = useState(true) const [isLoading, setIsLoading] = useState(true)
const [isDetailedCacheView, setIsDetailedCacheView] = useState(false) const [isDetailedCacheView, setIsDetailedCacheView] = useState(false)
const [viewedFileList, setViewedFileList] = useState() const [viewedFileList, setViewedFileList] = useState()
@@ -99,6 +101,7 @@ export default function DialogTorrentDetailsContent({ closeDialog, torrent }) {
}, [hash]) }, [hash])
const bufferSize = settings?.PreloadBuffer ? Capacity : 33554432 // Default is 32mb if PreloadBuffer is false const bufferSize = settings?.PreloadBuffer ? Capacity : 33554432 // Default is 32mb if PreloadBuffer is false
// const bufferSize = Capacity
const getTitle = value => { const getTitle = value => {
const torrentParsedName = value && ptt.parse(value) const torrentParsedName = value && ptt.parse(value)
@@ -115,7 +118,7 @@ export default function DialogTorrentDetailsContent({ closeDialog, torrent }) {
<> <>
<DialogHeader <DialogHeader
onClose={closeDialog} onClose={closeDialog}
title={isDetailedCacheView ? 'Detailed Cache View' : 'Torrent Details'} title={isDetailedCacheView ? t('DetailedCacheView') : t('TorrentDetails')}
{...(isDetailedCacheView && { onBack: () => setIsDetailedCacheView(false) })} {...(isDetailedCacheView && { onBack: () => setIsDetailedCacheView(false) })}
/> />
@@ -171,10 +174,8 @@ export default function DialogTorrentDetailsContent({ closeDialog, torrent }) {
<CacheSection> <CacheSection>
<SectionHeader> <SectionHeader>
<SectionTitle mb={20}>Buffer</SectionTitle> <SectionTitle mb={20}>{t('Buffer')}</SectionTitle>
{!settings?.PreloadBuffer && ( {!settings?.PreloadBuffer && <SectionSubName>{t('BufferNote')}</SectionSubName>}
<SectionSubName>Enable &quot;Preload Buffer&quot; in settings to change buffer size</SectionSubName>
)}
<LoadingProgress <LoadingProgress
value={Filled} value={Filled}
fullAmount={bufferSize} fullAmount={bufferSize}
@@ -190,16 +191,16 @@ export default function DialogTorrentDetailsContent({ closeDialog, torrent }) {
size='large' size='large'
onClick={() => setIsDetailedCacheView(true)} onClick={() => setIsDetailedCacheView(true)}
> >
Detailed cache view {t('DetailedCacheView')}
</Button> </Button>
</CacheSection> </CacheSection>
<TorrentFilesSection> <TorrentFilesSection>
<SectionTitle mb={20}>Torrent Content</SectionTitle> <SectionTitle mb={20}>{t('TorrentContent')}</SectionTitle>
{seasonAmount?.length > 1 && ( {seasonAmount?.length > 1 && (
<> <>
<SectionSubName mb={7}>Select Season</SectionSubName> <SectionSubName mb={7}>{t('SelectSeason')}</SectionSubName>
<ButtonGroup style={{ marginBottom: '30px' }} color='primary'> <ButtonGroup style={{ marginBottom: '30px' }} color='primary'>
{seasonAmount.map(season => ( {seasonAmount.map(season => (
<Button <Button
@@ -212,7 +213,9 @@ export default function DialogTorrentDetailsContent({ closeDialog, torrent }) {
))} ))}
</ButtonGroup> </ButtonGroup>
<SectionTitle mb={20}>Season {selectedSeason}</SectionTitle> <SectionTitle mb={20}>
{t('Season')} {selectedSeason}
</SectionTitle>
</> </>
)} )}

View File

@@ -8,61 +8,81 @@ import {
Build as BuildIcon, Build as BuildIcon,
} from '@material-ui/icons' } from '@material-ui/icons'
import { getPeerString, humanizeSize } from 'utils/Utils' import { getPeerString, humanizeSize } from 'utils/Utils'
import { useTranslation } from 'react-i18next'
import StatisticsField from './StatisticsField' import StatisticsField from './StatisticsField'
export const DownlodSpeedWidget = ({ data }) => ( export const DownlodSpeedWidget = ({ data }) => {
const { t } = useTranslation()
return (
<StatisticsField <StatisticsField
title='Download speed' title={t('DownloadSpeed')}
value={humanizeSize(data) || '0 B'} value={humanizeSize(data) || '0 B'}
iconBg='#118f00' iconBg='#118f00'
valueBg='#13a300' valueBg='#13a300'
icon={ArrowDownwardIcon} icon={ArrowDownwardIcon}
/> />
) )
}
export const UploadSpeedWidget = ({ data }) => ( export const UploadSpeedWidget = ({ data }) => {
const { t } = useTranslation()
return (
<StatisticsField <StatisticsField
title='Upload speed' title={t('UploadSpeed')}
value={humanizeSize(data) || '0 B'} value={humanizeSize(data) || '0 B'}
iconBg='#0146ad' iconBg='#0146ad'
valueBg='#0058db' valueBg='#0058db'
icon={ArrowUpwardIcon} icon={ArrowUpwardIcon}
/> />
) )
}
export const PeersWidget = ({ data }) => ( export const PeersWidget = ({ data }) => {
const { t } = useTranslation()
return (
<StatisticsField <StatisticsField
title='Peers' title={t('Peers')}
value={getPeerString(data) || '[0] 0 / 0'} value={getPeerString(data) || '[0] 0 / 0'}
iconBg='#cdc118' iconBg='#cdc118'
valueBg='#d8cb18' valueBg='#d8cb18'
icon={SwapVerticalCircleIcon} icon={SwapVerticalCircleIcon}
/> />
) )
}
export const PiecesCountWidget = ({ data }) => ( export const PiecesCountWidget = ({ data }) => {
<StatisticsField title='Pieces count' value={data} iconBg='#b6c95e' valueBg='#c0d076' icon={WidgetsIcon} /> const { t } = useTranslation()
) return <StatisticsField title={t('PiecesCount')} value={data} iconBg='#b6c95e' valueBg='#c0d076' icon={WidgetsIcon} />
export const PiecesLengthWidget = ({ data }) => ( }
export const PiecesLengthWidget = ({ data }) => {
const { t } = useTranslation()
return (
<StatisticsField <StatisticsField
title='Pieces length' title={t('PiecesLength')}
value={humanizeSize(data)} value={humanizeSize(data)}
iconBg='#0982c8' iconBg='#0982c8'
valueBg='#098cd7' valueBg='#098cd7'
icon={PhotoSizeSelectSmallIcon} icon={PhotoSizeSelectSmallIcon}
/> />
) )
export const StatusWidget = ({ data }) => ( }
<StatisticsField title='Torrent status' value={data} iconBg='#aea25b' valueBg='#b4aa6e' icon={BuildIcon} />
)
export const SizeWidget = ({ data }) => ( export const StatusWidget = ({ data }) => {
const { t } = useTranslation()
return <StatisticsField title={t('TorrentStatus')} value={data} iconBg='#aea25b' valueBg='#b4aa6e' icon={BuildIcon} />
}
export const SizeWidget = ({ data }) => {
const { t } = useTranslation()
return (
<StatisticsField <StatisticsField
title='Torrent size' title={t('TorrentSize')}
value={humanizeSize(data)} value={humanizeSize(data)}
iconBg='#9b01ad' iconBg='#9b01ad'
valueBg='#ac03bf' valueBg='#ac03bf'
icon={ViewAgendaIcon} icon={ViewAgendaIcon}
/> />
) )
}

View File

@@ -6,22 +6,23 @@ import DialogActions from '@material-ui/core/DialogActions'
import List from '@material-ui/core/List' import List from '@material-ui/core/List'
import ButtonGroup from '@material-ui/core/ButtonGroup' import ButtonGroup from '@material-ui/core/ButtonGroup'
import Button from '@material-ui/core/Button' import Button from '@material-ui/core/Button'
import { useTranslation } from 'react-i18next'
const donateFrame = const donateFrame =
'<iframe src="https://yoomoney.ru/quickpay/shop-widget?writer=seller&targets=TorrServer Donate&targets-hint=&default-sum=200&button-text=14&payment-type-choice=on&mobile-payment-type-choice=on&comment=on&hint=&successURL=&quickpay=shop&account=410013733697114" width="100%" height="302" frameborder="0" allowtransparency="true" scrolling="no"></iframe>' '<iframe src="https://yoomoney.ru/quickpay/shop-widget?writer=seller&targets=TorrServer Donate&targets-hint=&default-sum=200&button-text=14&payment-type-choice=on&mobile-payment-type-choice=on&comment=on&hint=&successURL=&quickpay=shop&account=410013733697114" width="320" height="320" frameborder="0" allowtransparency="true" scrolling="no"></iframe>'
export default function DonateDialog({ onClose }) { export default function DonateDialog({ onClose }) {
const { t } = useTranslation()
return ( return (
<Dialog open onClose={onClose} aria-labelledby='form-dialog-title' fullWidth> <Dialog open onClose={onClose} aria-labelledby='form-dialog-title' fullWidth>
<DialogTitle id='form-dialog-title'>Donate</DialogTitle> <DialogTitle id='form-dialog-title'>{t('Donate')}</DialogTitle>
<DialogContent> <DialogContent>
<List> <List>
<ListItem key='DonateLinks'> <ListItem key='DonateLinks'>
<ButtonGroup variant='outlined' color='primary' aria-label='contained primary button group'> <ButtonGroup variant='outlined' color='primary' aria-label='contained primary button group'>
<Button onClick={() => window.open('https://www.paypal.com/paypalme/yourok', '_blank')}>PayPal</Button> <Button onClick={() => window.open('https://www.paypal.com/paypalme/yourok', '_blank')}>PayPal</Button>
<Button onClick={() => window.open('https://yoomoney.ru/to/410013733697114', '_blank')}> <Button onClick={() => window.open('https://yoomoney.ru/to/410013733697114', '_blank')}>IO.Money</Button>
Yandex.Money
</Button>
</ButtonGroup> </ButtonGroup>
</ListItem> </ListItem>
<ListItem key='DonateForm'> <ListItem key='DonateForm'>

View File

@@ -5,6 +5,7 @@ import ListItemText from '@material-ui/core/ListItemText'
import DeleteIcon from '@material-ui/icons/Delete' import DeleteIcon from '@material-ui/icons/Delete'
import { useState } from 'react' import { useState } from 'react'
import { torrentsHost } from 'utils/Hosts' import { torrentsHost } from 'utils/Hosts'
import { useTranslation } from 'react-i18next'
const fnRemoveAll = () => { const fnRemoveAll = () => {
fetch(torrentsHost(), { fetch(torrentsHost(), {
@@ -31,25 +32,25 @@ const fnRemoveAll = () => {
} }
export default function RemoveAll() { export default function RemoveAll() {
const { t } = useTranslation()
const [open, setOpen] = useState(false) const [open, setOpen] = useState(false)
const closeDialog = () => setOpen(false) const closeDialog = () => setOpen(false)
const openDialog = () => setOpen(true) const openDialog = () => setOpen(true)
return ( return (
<> <>
<ListItem button key='Remove all' onClick={openDialog}> <ListItem button key={t('RemoveAll')} onClick={openDialog}>
<ListItemIcon> <ListItemIcon>
<DeleteIcon /> <DeleteIcon />
</ListItemIcon> </ListItemIcon>
<ListItemText primary='Remove all' /> <ListItemText primary={t('RemoveAll')} />
</ListItem> </ListItem>
<Dialog open={open} onClose={closeDialog}> <Dialog open={open} onClose={closeDialog}>
<DialogTitle>Delete Torrent?</DialogTitle> <DialogTitle>{t('DeleteTorrents?')}</DialogTitle>
<DialogActions> <DialogActions>
<Button variant='outlined' onClick={closeDialog} color='primary'> <Button variant='outlined' onClick={closeDialog} color='primary'>
Cancel {t('Cancel')}
</Button> </Button>
<Button <Button
@@ -61,7 +62,7 @@ export default function RemoveAll() {
color='primary' color='primary'
autoFocus autoFocus
> >
Ok {t('OK')}
</Button> </Button>
</DialogActions> </DialogActions>
</Dialog> </Dialog>

View File

@@ -1,3 +1,4 @@
import axios from 'axios'
import ListItem from '@material-ui/core/ListItem' import ListItem from '@material-ui/core/ListItem'
import ListItemIcon from '@material-ui/core/ListItemIcon' import ListItemIcon from '@material-ui/core/ListItemIcon'
import ListItemText from '@material-ui/core/ListItemText' import ListItemText from '@material-ui/core/ListItemText'
@@ -11,9 +12,10 @@ import DialogActions from '@material-ui/core/DialogActions'
import Button from '@material-ui/core/Button' import Button from '@material-ui/core/Button'
import { FormControlLabel, InputLabel, Select, Switch } from '@material-ui/core' import { FormControlLabel, InputLabel, Select, Switch } from '@material-ui/core'
import { settingsHost, setTorrServerHost, getTorrServerHost } from 'utils/Hosts' import { settingsHost, setTorrServerHost, getTorrServerHost } from 'utils/Hosts'
import axios from 'axios' import { useTranslation } from 'react-i18next'
export default function SettingsDialog() { export default function SettingsDialog() {
const { t } = useTranslation()
const [open, setOpen] = useState(false) const [open, setOpen] = useState(false)
const [settings, setSets] = useState({}) const [settings, setSets] = useState({})
const [show, setShow] = useState(false) const [show, setShow] = useState(false)
@@ -49,7 +51,16 @@ export default function SettingsDialog() {
if (type === 'number' || type === 'select-one') { if (type === 'number' || type === 'select-one') {
sets[id] = Number(value) sets[id] = Number(value)
} else if (type === 'checkbox') { } else if (type === 'checkbox') {
sets[id] = Boolean(checked) if (
id === 'DisableTCP' ||
id === 'DisableUTP' ||
id === 'DisableUPNP' ||
id === 'DisableDHT' ||
id === 'DisablePEX' ||
id === 'DisableUpload'
)
sets[id] = Boolean(!checked)
else sets[id] = Boolean(checked)
} else if (type === 'url') { } else if (type === 'url') {
sets[id] = value sets[id] = value
} }
@@ -82,21 +93,21 @@ export default function SettingsDialog() {
return ( return (
<div> <div>
<ListItem button key='Settings' onClick={handleClickOpen}> <ListItem button key={t('Settings')} onClick={handleClickOpen}>
<ListItemIcon> <ListItemIcon>
<SettingsIcon /> <SettingsIcon />
</ListItemIcon> </ListItemIcon>
<ListItemText primary='Settings' /> <ListItemText primary={t('Settings')} />
</ListItem> </ListItem>
<Dialog open={open} onClose={handleClose} aria-labelledby='form-dialog-title' fullWidth> <Dialog open={open} onClose={handleClose} aria-labelledby='form-dialog-title' fullWidth>
<DialogTitle id='form-dialog-title'>Settings</DialogTitle> <DialogTitle id='form-dialog-title'>{t('Settings')}</DialogTitle>
<DialogContent> <DialogContent>
<TextField <TextField
onChange={onInputHost} onChange={onInputHost}
margin='dense' margin='dense'
id='TorrServerHost' id='TorrServerHost'
label='Host' label={t('Host')}
value={tsHost} value={tsHost}
type='url' type='url'
fullWidth fullWidth
@@ -107,162 +118,170 @@ export default function SettingsDialog() {
onChange={inputForm} onChange={inputForm}
margin='dense' margin='dense'
id='CacheSize' id='CacheSize'
label='Cache size' label={t('CacheSize')}
value={CacheSize} value={CacheSize}
type='number' type='number'
fullWidth fullWidth
/> />
<FormControlLabel <br />
control={<Switch checked={PreloadBuffer} onChange={inputForm} id='PreloadBuffer' color='primary' />}
label='Preload buffer'
/>
<TextField <TextField
onChange={inputForm} onChange={inputForm}
margin='dense' margin='dense'
id='ReaderReadAHead' id='ReaderReadAHead'
label='Reader readahead' label={t('ReaderReadAHead')}
value={ReaderReadAHead} value={ReaderReadAHead}
type='number' type='number'
fullWidth fullWidth
/> />
<br /> <br />
<br />
<InputLabel htmlFor='RetrackersMode'>Retracker mode</InputLabel>
<Select onChange={inputForm} type='number' native id='RetrackersMode' value={RetrackersMode}>
<option value={0}>Don&apos;t add retrackers</option>
<option value={1}>Add retrackers</option>
<option value={2}>Remove retrackers</option>
<option value={3}>Replace retrackers</option>
</Select>
<TextField
onChange={inputForm}
margin='dense'
id='TorrentDisconnectTimeout'
label='Torrent disconnect timeout'
value={TorrentDisconnectTimeout}
type='number'
fullWidth
/>
<FormControlLabel <FormControlLabel
control={<Switch checked={EnableIPv6} onChange={inputForm} id='EnableIPv6' color='primary' />} control={<Switch checked={PreloadBuffer} onChange={inputForm} id='PreloadBuffer' color='primary' />}
label='Enable IPv6' label={t('PreloadBuffer')}
/>
<br />
<FormControlLabel
control={<Switch checked={ForceEncrypt} onChange={inputForm} id='ForceEncrypt' color='primary' />}
label='Force encrypt'
/>
<br />
<FormControlLabel
control={<Switch checked={DisableTCP} onChange={inputForm} id='DisableTCP' color='primary' />}
label='Disable TCP'
/>
<br />
<FormControlLabel
control={<Switch checked={DisableUTP} onChange={inputForm} id='DisableUTP' color='primary' />}
label='Disable UTP'
/>
<br />
<FormControlLabel
control={<Switch checked={DisableUPNP} onChange={inputForm} id='DisableUPNP' color='primary' />}
label='Disable UPNP'
/>
<br />
<FormControlLabel
control={<Switch checked={DisableDHT} onChange={inputForm} id='DisableDHT' color='primary' />}
label='Disable DHT'
/>
<br />
<FormControlLabel
control={<Switch checked={DisablePEX} onChange={inputForm} id='DisablePEX' color='primary' />}
label='Disable PEX'
/>
<br />
<FormControlLabel
control={<Switch checked={DisableUpload} onChange={inputForm} id='DisableUpload' color='primary' />}
label='Disable upload'
/>
<br />
<TextField
onChange={inputForm}
margin='dense'
id='DownloadRateLimit'
label='Download rate limit'
value={DownloadRateLimit}
type='number'
fullWidth
/>
<TextField
onChange={inputForm}
margin='dense'
id='UploadRateLimit'
label='Upload rate limit'
value={UploadRateLimit}
type='number'
fullWidth
/>
<TextField
onChange={inputForm}
margin='dense'
id='ConnectionsLimit'
label='Connections limit'
value={ConnectionsLimit}
type='number'
fullWidth
/>
<TextField
onChange={inputForm}
margin='dense'
id='DhtConnectionLimit'
label='Dht connection limit'
value={DhtConnectionLimit}
type='number'
fullWidth
/>
<TextField
onChange={inputForm}
margin='dense'
id='PeersListenPort'
label='Peers listen port'
value={PeersListenPort}
type='number'
fullWidth
/> />
<br /> <br />
<FormControlLabel <FormControlLabel
control={<Switch checked={UseDisk} onChange={inputForm} id='UseDisk' color='primary' />} control={<Switch checked={UseDisk} onChange={inputForm} id='UseDisk' color='primary' />}
label='Use disk' label={t('UseDisk')}
/> />
<br /> <br />
<FormControlLabel <FormControlLabel
control={ control={
<Switch checked={RemoveCacheOnDrop} onChange={inputForm} id='RemoveCacheOnDrop' color='primary' /> <Switch checked={RemoveCacheOnDrop} onChange={inputForm} id='RemoveCacheOnDrop' color='primary' />
} }
label='Remove cache from disk on drop torrent' label={t('RemoveCacheOnDrop')}
/> />
<br /> <br />
<small>If disabled, remove cache on delete torrent</small> <small>{t('RemoveCacheOnDropDesc')}</small>
<br /> <br />
<TextField <TextField
onChange={inputForm} onChange={inputForm}
margin='dense' margin='dense'
id='TorrentsSavePath' id='TorrentsSavePath'
label='Torrents save path' label={t('TorrentsSavePath')}
value={TorrentsSavePath} value={TorrentsSavePath}
type='url' type='url'
fullWidth fullWidth
/> />
<br />
<FormControlLabel
control={<Switch checked={EnableIPv6} onChange={inputForm} id='EnableIPv6' color='primary' />}
label={t('EnableIPv6')}
/>
<br />
<FormControlLabel
control={<Switch checked={!DisableTCP} onChange={inputForm} id='DisableTCP' color='primary' />}
label={t('TCP')}
/>
<br />
<FormControlLabel
control={<Switch checked={!DisableUTP} onChange={inputForm} id='DisableUTP' color='primary' />}
label={t('UTP')}
/>
<br />
<FormControlLabel
control={<Switch checked={!DisablePEX} onChange={inputForm} id='DisablePEX' color='primary' />}
label={t('PEX')}
/>
<br />
<FormControlLabel
control={<Switch checked={ForceEncrypt} onChange={inputForm} id='ForceEncrypt' color='primary' />}
label={t('ForceEncrypt')}
/>
<br />
<TextField
onChange={inputForm}
margin='dense'
id='TorrentDisconnectTimeout'
label={t('TorrentDisconnectTimeout')}
value={TorrentDisconnectTimeout}
type='number'
fullWidth
/>
<br />
<TextField
onChange={inputForm}
margin='dense'
id='ConnectionsLimit'
label={t('ConnectionsLimit')}
value={ConnectionsLimit}
type='number'
fullWidth
/>
<br />
<FormControlLabel
control={<Switch checked={!DisableDHT} onChange={inputForm} id='DisableDHT' color='primary' />}
label={t('DHT')}
/>
<br />
<TextField
onChange={inputForm}
margin='dense'
id='DhtConnectionLimit'
label={t('DhtConnectionLimit')}
value={DhtConnectionLimit}
type='number'
fullWidth
/>
<br />
<TextField
onChange={inputForm}
margin='dense'
id='DownloadRateLimit'
label={t('DownloadRateLimit')}
value={DownloadRateLimit}
type='number'
fullWidth
/>
<br />
<FormControlLabel
control={<Switch checked={!DisableUpload} onChange={inputForm} id='DisableUpload' color='primary' />}
label={t('Upload')}
/>
<br />
<TextField
onChange={inputForm}
margin='dense'
id='UploadRateLimit'
label={t('UploadRateLimit')}
value={UploadRateLimit}
type='number'
fullWidth
/>
<br />
<TextField
onChange={inputForm}
margin='dense'
id='PeersListenPort'
label={t('PeersListenPort')}
value={PeersListenPort}
type='number'
fullWidth
/>
<br />
<FormControlLabel
control={<Switch checked={!DisableUPNP} onChange={inputForm} id='DisableUPNP' color='primary' />}
label={t('UPNP')}
/>
<br />
<InputLabel htmlFor='RetrackersMode'>{t('RetrackersMode')}</InputLabel>
<Select onChange={inputForm} type='number' native id='RetrackersMode' value={RetrackersMode}>
<option value={0}>{t('DontAddRetrackers')}</option>
<option value={1}>{t('AddRetrackers')}</option>
<option value={2}>{t('RemoveRetrackers')}</option>
<option value={3}>{t('ReplaceRetrackers')}</option>
</Select>
<br />
</> </>
)} )}
</DialogContent> </DialogContent>
<DialogActions> <DialogActions>
<Button onClick={handleClose} color='primary' variant='outlined'> <Button onClick={handleClose} color='primary' variant='outlined'>
Cancel {t('Cancel')}
</Button> </Button>
<Button onClick={handleSave} color='primary' variant='outlined'> <Button onClick={handleSave} color='primary' variant='outlined'>
Save {t('Save')}
</Button> </Button>
</DialogActions> </DialogActions>
</Dialog> </Dialog>

View File

@@ -10,12 +10,14 @@ import Slide from '@material-ui/core/Slide'
import { Button, DialogActions, DialogTitle, useMediaQuery, useTheme } from '@material-ui/core' import { Button, DialogActions, DialogTitle, useMediaQuery, useTheme } from '@material-ui/core'
import axios from 'axios' import axios from 'axios'
import ptt from 'parse-torrent-title' import ptt from 'parse-torrent-title'
import { useTranslation } from 'react-i18next'
import { StyledButton, TorrentCard, TorrentCardButtons, TorrentCardDescription, TorrentCardPoster } from './style' import { StyledButton, TorrentCard, TorrentCardButtons, TorrentCardDescription, TorrentCardPoster } from './style'
const Transition = forwardRef((props, ref) => <Slide direction='up' ref={ref} {...props} />) const Transition = forwardRef((props, ref) => <Slide direction='up' ref={ref} {...props} />)
export default function Torrent({ torrent }) { export default function Torrent({ torrent }) {
const { t } = useTranslation()
const [isDetailedInfoOpened, setIsDetailedInfoOpened] = useState(false) const [isDetailedInfoOpened, setIsDetailedInfoOpened] = useState(false)
const [isDeleteTorrentOpened, setIsDeleteTorrentOpened] = useState(false) const [isDeleteTorrentOpened, setIsDeleteTorrentOpened] = useState(false)
@@ -44,41 +46,41 @@ export default function Torrent({ torrent }) {
<TorrentCardButtons> <TorrentCardButtons>
<StyledButton onClick={openDetailedInfo}> <StyledButton onClick={openDetailedInfo}>
<UnfoldMoreIcon /> <UnfoldMoreIcon />
<span>Details</span> <span>{t('Details')}</span>
</StyledButton> </StyledButton>
<StyledButton onClick={() => dropTorrent(torrent)}> <StyledButton onClick={() => dropTorrent(torrent)}>
<CloseIcon /> <CloseIcon />
<span>Drop</span> <span>{t('Drop')}</span>
</StyledButton> </StyledButton>
<StyledButton onClick={openDeleteTorrentAlert}> <StyledButton onClick={openDeleteTorrentAlert}>
<DeleteIcon /> <DeleteIcon />
<span>Delete</span> <span>{t('Delete')}</span>
</StyledButton> </StyledButton>
</TorrentCardButtons> </TorrentCardButtons>
<TorrentCardDescription> <TorrentCardDescription>
<div className='description-title-wrapper'> <div className='description-title-wrapper'>
<div className='description-section-name'>Name</div> <div className='description-section-name'>{t('Name')}</div>
<div className='description-torrent-title'>{shortenText(parsedTitle, 100)}</div> <div className='description-torrent-title'>{shortenText(parsedTitle, 100)}</div>
</div> </div>
<div className='description-statistics-wrapper'> <div className='description-statistics-wrapper'>
<div className='description-statistics-element-wrapper'> <div className='description-statistics-element-wrapper'>
<div className='description-section-name'>Size</div> <div className='description-section-name'>{t('Size')}</div>
<div className='description-statistics-element-value'>{torrentSize > 0 && humanizeSize(torrentSize)}</div> <div className='description-statistics-element-value'>{torrentSize > 0 && humanizeSize(torrentSize)}</div>
</div> </div>
<div className='description-statistics-element-wrapper'> <div className='description-statistics-element-wrapper'>
<div className='description-section-name'>Speed</div> <div className='description-section-name'>{t('Speed')}</div>
<div className='description-statistics-element-value'> <div className='description-statistics-element-value'>
{downloadSpeed > 0 ? humanizeSize(downloadSpeed) : '---'} {downloadSpeed > 0 ? humanizeSize(downloadSpeed) : '---'}
</div> </div>
</div> </div>
<div className='description-statistics-element-wrapper'> <div className='description-statistics-element-wrapper'>
<div className='description-section-name'>Peers</div> <div className='description-section-name'>{t('Peers')}</div>
<div className='description-statistics-element-value'>{getPeerString(torrent) || '---'}</div> <div className='description-statistics-element-value'>{getPeerString(torrent) || '---'}</div>
</div> </div>
</div> </div>
@@ -97,10 +99,10 @@ export default function Torrent({ torrent }) {
</Dialog> </Dialog>
<Dialog open={isDeleteTorrentOpened} onClose={closeDeleteTorrentAlert}> <Dialog open={isDeleteTorrentOpened} onClose={closeDeleteTorrentAlert}>
<DialogTitle>Delete Torrent?</DialogTitle> <DialogTitle>{t('DeleteTorrent?')}</DialogTitle>
<DialogActions> <DialogActions>
<Button variant='outlined' onClick={closeDeleteTorrentAlert} color='primary'> <Button variant='outlined' onClick={closeDeleteTorrentAlert} color='primary'>
Cancel {t('Cancel')}
</Button> </Button>
<Button <Button
@@ -112,7 +114,7 @@ export default function Torrent({ torrent }) {
color='primary' color='primary'
autoFocus autoFocus
> >
Ok {t('OK')}
</Button> </Button>
</DialogActions> </DialogActions>
</Dialog> </Dialog>

View File

@@ -8,7 +8,7 @@ export const TorrentCard = styled.div`
grid-template-areas: 'poster description buttons'; grid-template-areas: 'poster description buttons';
gap: 10px; gap: 10px;
padding: 10px; padding: 10px;
background: #3fb57a; background: #00a572;
box-shadow: 0px 2px 4px -1px rgb(0 0 0 / 20%), 0px 4px 5px 0px rgb(0 0 0 / 14%), 0px 1px 10px 0px rgb(0 0 0 / 12%); box-shadow: 0px 2px 4px -1px rgb(0 0 0 / 20%), 0px 4px 5px 0px rgb(0 0 0 / 14%), 0px 1px 10px 0px rgb(0 0 0 / 12%);
@media (max-width: 1260px), (max-height: 500px) { @media (max-width: 1260px), (max-height: 500px) {

View File

@@ -5,8 +5,10 @@ import TorrentCard from 'components/TorrentCard'
import axios from 'axios' import axios from 'axios'
import CircularProgress from '@material-ui/core/CircularProgress' import CircularProgress from '@material-ui/core/CircularProgress'
import { TorrentListWrapper, CenteredGrid } from 'App/style' import { TorrentListWrapper, CenteredGrid } from 'App/style'
import { useTranslation } from 'react-i18next'
export default function TorrentList() { export default function TorrentList() {
const { t } = useTranslation()
const [torrents, setTorrents] = useState([]) const [torrents, setTorrents] = useState([])
const [isLoading, setIsLoading] = useState(true) const [isLoading, setIsLoading] = useState(true)
const [isOffline, setIsOffline] = useState(true) const [isOffline, setIsOffline] = useState(true)
@@ -39,9 +41,9 @@ export default function TorrentList() {
{isLoading ? ( {isLoading ? (
<CircularProgress /> <CircularProgress />
) : isOffline ? ( ) : isOffline ? (
<Typography>Offline</Typography> <Typography>{t('Offline')}</Typography>
) : ( ) : (
!torrents.length && <Typography>No torrents added</Typography> !torrents.length && <Typography>{t('NoTorrentsAdded')}</Typography>
)} )}
</CenteredGrid> </CenteredGrid>
) )

View File

@@ -4,8 +4,10 @@ import ListItem from '@material-ui/core/ListItem'
import PublishIcon from '@material-ui/icons/Publish' import PublishIcon from '@material-ui/icons/Publish'
import { torrentUploadHost } from 'utils/Hosts' import { torrentUploadHost } from 'utils/Hosts'
import axios from 'axios' import axios from 'axios'
import { useTranslation } from 'react-i18next'
export default function UploadDialog() { export default function UploadDialog() {
const { t } = useTranslation()
const handleCapture = ({ target: { files } }) => { const handleCapture = ({ target: { files } }) => {
const [file] = files const [file] = files
const data = new FormData() const data = new FormData()
@@ -13,18 +15,17 @@ export default function UploadDialog() {
data.append('file', file) data.append('file', file)
axios.post(torrentUploadHost(), data) axios.post(torrentUploadHost(), data)
} }
return ( return (
<div> <div>
<label htmlFor='raised-button-file'> <label htmlFor='raised-button-file'>
<input onChange={handleCapture} accept='*/*' type='file' style={{ display: 'none' }} id='raised-button-file' /> <input onChange={handleCapture} accept='*/*' type='file' style={{ display: 'none' }} id='raised-button-file' />
<ListItem button variant='raised' type='submit' component='span' key='Upload file'> <ListItem button variant='raised' type='submit' component='span' key={t('UploadFile')}>
<ListItemIcon> <ListItemIcon>
<PublishIcon /> <PublishIcon />
</ListItemIcon> </ListItemIcon>
<ListItemText primary='Upload file' /> <ListItemText primary={t('UploadFile')} />
</ListItem> </ListItem>
</label> </label>
</div> </div>

View File

@@ -1,12 +1,40 @@
import { StrictMode } from 'react' import { StrictMode } from 'react'
import ReactDOM from 'react-dom' import ReactDOM from 'react-dom'
import { I18nextProvider } from 'react-i18next'
import i18n from 'i18next'
import LanguageDetector from 'i18next-browser-languagedetector'
import './index.css' import './index.css'
import App from './App' import App from './App'
i18n.use(LanguageDetector).init({
lng: 'en', // default
fallbackLng: 'en', // use en if detected lng is not available
keySeparator: false, // we do not use keys in form messages.welcome
interpolation: {
escapeValue: false, // react already safes from xss
},
resources: {
en: {
// eslint-disable-next-line global-require
translations: require('./locales/en/translation.json'),
},
ru: {
// eslint-disable-next-line global-require
translations: require('./locales/ru/translation.json'),
},
},
ns: ['translations'],
defaultNS: 'translations',
})
i18n.languages = ['en', 'ru']
ReactDOM.render( ReactDOM.render(
<StrictMode> <StrictMode>
<I18nextProvider i18n={i18n}>
<App /> <App />
</I18nextProvider>
</StrictMode>, </StrictMode>,
document.getElementById('root'), document.getElementById('root'),
) )

View File

@@ -0,0 +1,89 @@
{
"About": "About",
"Actions": "Actions",
"Add": "Add",
"AddFromLink": "Add from Link",
"AddMagnetOrLink": "Add magnet or link to torrent file",
"AddRetrackers": "Add retrackers",
"Buffer": "Preload Buffer / Cache",
"BufferNote": "Enable &quot;Preload Buffer&quot; in settings to see cache loading progress",
"CacheSize": "Cache Size (Megabytes)",
"Cancel": "Cancel",
"Close": "Close",
"CloseServer": "Close Server",
"CloseServer?": "Close server?",
"ConnectionsLimit": "Connections Limit",
"CopyHash": "Copy Hash",
"CopyLink": "Copy link",
"Delete": "Delete",
"DeleteTorrent?": "Delete Torrent?",
"DeleteTorrents?": "Delete All Torrents?",
"DetailedCacheView": "Detailed Cache View",
"Details": "Details",
"DHT": "DHT (Distributed Hash Table)",
"DhtConnectionLimit": "DHT Connection Limit",
"Donate": "Donate",
"DontAddRetrackers": "Don&apos;t add retrackers",
"DownloadPlaylist": "Download Playlist",
"DownloadRateLimit": "Download Rate Limit (Kilobytes)",
"DownloadSpeed": "Download speed",
"Drop": "Drop",
"DropTorrent": "Reset Torrent",
"EnableIPv6": "IPv6",
"Episode": "Episode",
"ForceEncrypt": "Force Encrypt Headers",
"FromLatestFile": "From Latest File",
"Full": "Full",
"Host": "Host",
"Info": "Info",
"LatestFilePlayed": "Latest file played:",
"MagnetOrTorrentFileLink": "Magnet or torrent file link",
"Name": "Name",
"NoTorrentsAdded": "No torrents added",
"Offline": "Offline",
"OK": "OK",
"OpenLink": "Open link",
"Peers": "Peers",
"PeersListenPort": "Peers Listen Port",
"PEX": "PEX (Peer Exchange)",
"PiecesCount": "Pieces count",
"PiecesLength": "Pieces length",
"PlaylistAll": "Playlist All",
"Poster": "Poster",
"Preload": "Preload",
"PreloadBuffer": "Preload Buffer",
"ReaderReadAHead": "Reader Read Ahead (5-100%)",
"RemoveAll": "Remove All",
"RemoveCacheOnDrop": "Remove Cache from Disk on Drop Torrent",
"RemoveCacheOnDropDesc": "If disabled, remove cache on delete torrent.",
"RemoveRetrackers": "Remove retrackers",
"RemoveViews": "Remove View States",
"ReplaceRetrackers": "Replace retrackers",
"Resolution": "Resolution",
"RetrackersMode": "Retrackers Mode",
"Save": "Save",
"Season": "Season",
"SelectSeason": "Select Season",
"Settings": "Settings",
"Size": "Size",
"SpecialThanks": "Special Thanks:",
"Speed": "Speed",
"TCP": "TCP (Transmission Control Protocol)",
"ThanksToEveryone": "Thanks to everyone who tested and helped.",
"Title": "Title",
"TorrentContent": "Torrent Content",
"TorrentDetails": "Torrent Details",
"TorrentDisconnectTimeout": "Torrent Disconnect Timeout",
"TorrentSize": "Torrent size",
"TorrentsSavePath": "Torrents Save Path",
"TorrentState": "Torrent State",
"TorrentStatus": "Torrent Status",
"Upload": "Upload (not recommended to disable)",
"UploadFile": "Upload File",
"UploadRateLimit": "Upload Rate Limit (Kilobytes)",
"UploadSpeed": "Upload speed",
"UPNP": "UPnP (Universal Plug and Play)",
"UseDisk": "Use Disk",
"UTP": "μTP (Micro Transport Protocol)",
"Viewed": "Viewed"
}

View File

@@ -0,0 +1,89 @@
{
"About": "О сервере",
"Actions": "Действия",
"Add": "Добавить",
"AddFromLink": "Добавить",
"AddMagnetOrLink": "Добавьте magnet или ссылку на торрент",
"AddRetrackers": "Добавлять",
"Buffer": "Предзагрузка / Кеш",
"BufferNote": "Включите «Наполнять кеш перед началом воспроизведения» в настройках для показа заполнения кеша",
"CacheSize": "Размер кеша (Мегабайты)",
"Cancel": "Отмена",
"Close": "Закрыть",
"CloseServer": "Выкл. сервер",
"CloseServer?": "Остановить сервер?",
"ConnectionsLimit": "Торрент-соединения (рек. 20-25)",
"CopyHash": "Скопировать хеш",
"CopyLink": "Копировать",
"Delete": "Удалить",
"DeleteTorrent?": "Удалить торрент?",
"DeleteTorrents?": "Удалить все торренты?",
"DetailedCacheView": "Информация о заполнении кеша",
"Details": "Инфо",
"DHT": "DHT (Distributed Hash Table)",
"DhtConnectionLimit": "Лимит подключений DHT",
"Donate": "Поддержка",
"DontAddRetrackers": "Ничего не делать",
"DownloadPlaylist": "Скачать плейлист",
"DownloadRateLimit": "Ограничение скорости загрузки (Килобайты)",
"DownloadSpeed": "Скорость загрузки",
"Drop": "Сброс",
"DropTorrent": "Сбросить торрент",
"EnableIPv6": "IPv6",
"Episode": "Серия",
"ForceEncrypt": "Принудительное шифрование заголовков",
"FromLatestFile": "C последнего файла",
"Full": "Полный",
"Host": "Хост",
"Info": "Инфо",
"LatestFilePlayed": "Последний воспроизведенный файл:",
"MagnetOrTorrentFileLink": "Ссылка на файл торрента или magnet-ссылка",
"Name": "Название",
"NoTorrentsAdded": "Нет торрентов",
"Offline": "Сервер не доступен",
"OK": "OK",
"OpenLink": "Открыть",
"Peers": "Подкл./Пиры",
"PeersListenPort": "Порт для входящих подключений",
"PEX": "PEX (Peer Exchange)",
"PiecesCount": "Кол-во блоков",
"PiecesLength": "Размер блока",
"PlaylistAll": "Плейлист всех",
"Poster": "Постер",
"Preload": "Предзагр.",
"PreloadBuffer": "Наполнять кеш перед началом воспроизведения",
"ReaderReadAHead": "Кеш предзагрузки (5-100%, рек. 95%)",
"RemoveAll": "Удалить все",
"RemoveCacheOnDrop": "Очищать кеш на диске при отключении торрента",
"RemoveCacheOnDropDesc": "Если отключено, кэш очищается при удалении торрента.",
"RemoveRetrackers": "Удалять",
"RemoveViews": "Очистить просмотры",
"ReplaceRetrackers": "Заменять",
"Resolution": "Разреш.",
"RetrackersMode": "Ретрекеры",
"Save": "Сохранить",
"Season": "Сезон",
"SelectSeason": "Выбор сезона",
"Settings": "Настройки",
"Size": "Размер",
"SpecialThanks": "Отдельное спасибо:",
"Speed": "Скорость",
"TCP": "TCP (Transmission Control Protocol)",
"ThanksToEveryone": "Спасибо всем, кто тестировал и помогал!",
"Title": "Название",
"TorrentContent": "Содержимое торрента",
"TorrentDetails": "Информация о торренте",
"TorrentDisconnectTimeout": "Тайм-аут отключения торрента (секунды)",
"TorrentSize": "Размер торрента",
"TorrentsSavePath": "Путь хранения кеша",
"TorrentState": "Данные торрента",
"TorrentStatus": "Состояние",
"Upload": "Отдача (не рекомендуется отключать)",
"UploadFile": "Загрузить файл",
"UploadRateLimit": "Ограничение скорости отдачи (Килобайты)",
"UploadSpeed": "Скорость отдачи",
"UPNP": "UPnP (Universal Plug and Play)",
"UseDisk": "Использовать кеш на диске",
"UTP": "μTP (Micro Transport Protocol)",
"Viewed": "Просм."
}