diff --git a/.babelrc b/.babelrc index 238ad86869..e9c56edca1 100644 --- a/.babelrc +++ b/.babelrc @@ -4,7 +4,8 @@ "react" ], "plugins": [ - 'transform-class-properties', - 'transform-object-rest-spread' + "transform-class-properties", + "transform-object-rest-spread", + "transform-decorators-legacy" ] } diff --git a/.eslintrc.js b/.eslintrc.js index 58abd8a2e6..622f7cefee 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -4,6 +4,10 @@ module.exports = { "commonjs": true, "es6": true }, + "globals": { + "process": true, + "Raven": true + }, "extends": "eslint:recommended", "parser": "babel-eslint", "parserOptions": { diff --git a/.gitignore b/.gitignore index 6b02cb7fd2..a292b8890b 100644 --- a/.gitignore +++ b/.gitignore @@ -65,4 +65,9 @@ bundle.electron.js # Prettier configuration file .prettierrc -package-lock.json \ No newline at end of file +# npm +package-lock.json + +# vscode +.vscode/ +tsconfig.json \ No newline at end of file diff --git a/app/App.js b/app/App.js index b7c1142e89..11fd16a4df 100644 --- a/app/App.js +++ b/app/App.js @@ -6,6 +6,7 @@ import { NavLink, withRouter } from 'react-router-dom'; import classnames from 'classnames'; import _ from 'lodash'; import Sound from 'react-hifi'; +import { withTranslation } from 'react-i18next'; import * as Actions from './actions'; import * as PlayerActions from './actions/player'; @@ -54,6 +55,7 @@ import TrackInfo from './components/TrackInfo'; import WindowControls from './components/WindowControls'; import VolumeControls from './components/VolumeControls'; +@withTranslation('app') class App extends React.Component { constructor(props) { super(props); @@ -132,6 +134,8 @@ class App extends React.Component { } renderSidebarMenu (settings, toggleOption) { + const { t } = this.props; + return ( Main - {this.renderNavLink('dashboard', 'dashboard', 'Dashboard', settings)} - {this.renderNavLink('downloads', 'download', 'Downloads', settings)} - {this.renderNavLink('lyrics', 'microphone', 'Lyrics', settings)} - {this.renderNavLink('plugins', 'flask', 'Plugins', settings)} - {this.renderNavLink('search', 'search', 'Search Results', settings)} - {this.renderNavLink('settings', 'cogs', 'Settings', settings)} - {this.renderNavLink('equalizer', 'sliders', 'Equalizer', settings)} + {this.renderNavLink('dashboard', 'dashboard', t('dashboard'), settings)} + {this.renderNavLink('downloads', 'download', t('downloads'), settings)} + {this.renderNavLink('lyrics', 'microphone', t('lyrics'), settings)} + {this.renderNavLink('plugins', 'flask', t('plugins'), settings)} + {this.renderNavLink('search', 'search', t('search'), settings)} + {this.renderNavLink('settings', 'cogs', t('settings'), settings)} + {this.renderNavLink('equalizer', 'sliders', t('equalizer'), settings)} - Collection + {t('collection')} - {this.renderNavLink('favorites/tracks', 'star', 'Favorite tracks', settings)} - {this.renderNavLink('library', 'file-sound-o', 'Local library', settings)} + {this.renderNavLink('favorites/tracks', 'star', t('favorite'), settings)} + {this.renderNavLink('library', 'file-sound-o', t('library'), settings)} { !_.isEmpty(this.props.playlists) && - Playlists + {t('playlists')} } ( +
+ {this.props.nameOnly ? ( +
{this.props.artist}
+ ) : ( +
{this.props.title}
+ )} - render() { - return ( -
- { - this.props.nameOnly - ?( -
{this.props.artist}
- ) - :
{this.props.title}
- } - - { - !this.props.nameOnly - ?
{this.props.artist}
- : null - } -
- ); - } -} + {!this.props.nameOnly ? ( +
{this.props.artist}
+ ) : null} +
+); export default AlbumInfo; diff --git a/app/components/AlbumCover/AlbumOverlay/index.js b/app/components/AlbumCover/AlbumOverlay/index.js index 692f12b909..a834892c9e 100644 --- a/app/components/AlbumCover/AlbumOverlay/index.js +++ b/app/components/AlbumCover/AlbumOverlay/index.js @@ -5,23 +5,18 @@ import Spacer from '../../Spacer'; import styles from './styles.css'; -class AlbumOverlay extends React.Component { - - render() { - return ( -
- - - - - -
- ); - } -} +const AlbumOverlay = () => ( +
+ + + + + +
+); export default AlbumOverlay; diff --git a/app/components/AlbumCover/index.js b/app/components/AlbumCover/index.js index d74b578d26..e74f292e4c 100644 --- a/app/components/AlbumCover/index.js +++ b/app/components/AlbumCover/index.js @@ -6,39 +6,30 @@ import styles from './styles.css'; import AlbumInfo from './AlbumInfo'; import AlbumOverlay from './AlbumOverlay'; -class AlbumCover extends React.Component { - - render() { - let style = {}; - - if (this.props.nameOnly) { - style = { - backgroundImage: `url(${this.props.cover})`, - height: '250px' - }; - } - - return ( -
- - - { - this.props.nameOnly - ? null - : - } - - -
- ); +const AlbumCover = ({ artist, cover, nameOnly, handlePlay, title }) => { + let style = {}; + + if (nameOnly) { + style = { + backgroundImage: `url(${cover})`, + height: '250px' + }; } -} + + return ( +
+ + + {nameOnly ? null : } + + +
+ ); +}; AlbumCover.propTypes = { nameOnly: PropTypes.bool, diff --git a/app/components/AlbumList/index.js b/app/components/AlbumList/index.js index 162ffbc3ed..79d5f3ddfd 100644 --- a/app/components/AlbumList/index.js +++ b/app/components/AlbumList/index.js @@ -1,5 +1,5 @@ import React from 'react'; -import {Dimmer, Image, Loader} from 'semantic-ui-react'; +import {Dimmer, Loader} from 'semantic-ui-react'; import Card from '../Card'; import styles from './styles.scss'; diff --git a/app/components/AlbumView/index.js b/app/components/AlbumView/index.js index 80e2fda466..1252f0e8fd 100644 --- a/app/components/AlbumView/index.js +++ b/app/components/AlbumView/index.js @@ -2,6 +2,7 @@ import React from 'react'; import FontAwesome from 'react-fontawesome'; import { Dimmer, Loader } from 'semantic-ui-react'; import _ from 'lodash'; +import { withTranslation } from 'react-i18next'; import ContextPopup from '../ContextPopup'; import TrackRow from '../TrackRow'; @@ -10,6 +11,7 @@ import * as Utils from '../../utils'; import styles from './styles.scss'; import artPlaceholder from '../../../resources/media/art_placeholder.png'; +@withTranslation('album') class AlbumView extends React.Component { constructor(props) { super(props); @@ -225,7 +227,7 @@ class AlbumView extends React.Component { href='#' onClick={() => this.addAlbumToQueue(album)} className={styles.add_button} - aria-label='Add album to queue' + aria-label={this.props.t('queue')} > Add to queue diff --git a/app/components/ArtistView/PopularTracks/index.js b/app/components/ArtistView/PopularTracks/index.js index 0289aa8db2..73831aeefd 100644 --- a/app/components/ArtistView/PopularTracks/index.js +++ b/app/components/ArtistView/PopularTracks/index.js @@ -1,6 +1,7 @@ import React from 'react'; import FontAwesome from 'react-fontawesome'; import cx from 'classnames'; +import { withTranslation } from 'react-i18next'; import TrackRow from '../../TrackRow'; import artPlaceholder from '../../../../resources/media/art_placeholder.png'; @@ -8,6 +9,7 @@ import artPlaceholder from '../../../../resources/media/art_placeholder.png'; import trackRowStyles from '../../TrackRow/styles.scss'; import styles from './styles.scss'; +@withTranslation('artist') class PopularTracks extends React.Component { constructor(props) { super(props); @@ -40,7 +42,7 @@ class PopularTracks extends React.Component { }); }} className={styles.add_button} - aria-label='Add all tracks to queue' + aria-label={this.props.t('queue')} > Add all @@ -48,7 +50,7 @@ class PopularTracks extends React.Component { } render () { - let { artist, tracks } = this.props; + let { artist, tracks, t } = this.props; return (
- Title - Play Counts + {t('title')} + {t('count')} diff --git a/app/components/ArtistView/SimilarArtists/index.js b/app/components/ArtistView/SimilarArtists/index.js index 4ce984f4dc..676ed6c5d1 100644 --- a/app/components/ArtistView/SimilarArtists/index.js +++ b/app/components/ArtistView/SimilarArtists/index.js @@ -1,9 +1,9 @@ import React from 'react'; +import { withTranslation } from 'react-i18next'; import styles from './styles.scss'; -const _ = require('lodash'); - +@withTranslation('artist') class SimilarArtists extends React.Component { constructor(props) { super(props); @@ -17,7 +17,7 @@ class SimilarArtists extends React.Component { return (
- Similar artists + {this.props.t('similar')}
{ this.props.artists.map((artist, index) => { diff --git a/app/components/ArtistView/index.js b/app/components/ArtistView/index.js index 32ad74d5f4..ab06cf4cb3 100644 --- a/app/components/ArtistView/index.js +++ b/app/components/ArtistView/index.js @@ -1,6 +1,7 @@ import React from 'react'; import _ from 'lodash'; import { Dimmer, Loader } from 'semantic-ui-react'; +import { withTranslation } from 'react-i18next'; import AlbumList from '../AlbumList'; import ArtistTags from './ArtistTags'; import SimilarArtists from './SimilarArtists'; @@ -9,6 +10,7 @@ import PopularTracks from './PopularTracks'; import styles from './styles.scss'; import artPlaceholder from '../../../resources/media/art_placeholder.png'; +@withTranslation('artist') class ArtistView extends React.Component { constructor(props) { super(props); @@ -51,7 +53,7 @@ class ArtistView extends React.Component { - On tour + {this.props.t('tour')} }
diff --git a/app/components/Card/index.js b/app/components/Card/index.js index 0977bd2bc3..807315c027 100644 --- a/app/components/Card/index.js +++ b/app/components/Card/index.js @@ -1,6 +1,5 @@ import React from 'react'; import classnames from 'classnames'; -import { Image } from 'semantic-ui-react'; import Img from 'react-image-smooth-loading'; import artPlaceholder from '../../../resources/media/art_placeholder.png'; @@ -8,38 +7,24 @@ import styles from './styles.scss'; Img.globalPlaceholder = artPlaceholder; -class Card extends React.Component { - constructor(props) { - super(props); - } - - render() { - return ( -
-
-
- -
-
-

{this.props.header}

- { - this.props.content - ?

{this.props.content}

- : null - } -
-
+const Card = () => ( +
+
+
+ +
+
+

{this.props.header}

+ {this.props.content ?

{this.props.content}

: null}
- ); - } -} +
+
+); export default Card; diff --git a/app/components/Cover/index.js b/app/components/Cover/index.js index 20804a65dd..ab6448c9a1 100644 --- a/app/components/Cover/index.js +++ b/app/components/Cover/index.js @@ -2,15 +2,10 @@ import React from 'react'; import styles from './styles.scss'; -class Cover extends React.Component { - - render() { - return ( -
- -
- ); - } -} +const Cover = () => ( +
+ +
+); export default Cover; diff --git a/app/components/Dashboard/NewsTab/NewsItem/index.js b/app/components/Dashboard/NewsTab/NewsItem/index.js index f3351da810..1e0f45f5ba 100644 --- a/app/components/Dashboard/NewsTab/NewsItem/index.js +++ b/app/components/Dashboard/NewsTab/NewsItem/index.js @@ -3,34 +3,25 @@ import moment from 'moment'; import styles from './styles.scss'; -class NewsItem extends React.Component { - constructor(props) { - super(props); - } +const NewsItem = ({ item }) => ( +
+

{item.title}

+

+ {moment.unix(item.timestamp).format('dddd, MMMM, Do YYYY, h:mm:ss A')} +

- render() { - let { item } = this.props; - return ( -
-

{item.title}

-

- {moment.unix(item.timestamp).format('dddd, MMMM, Do YYYY, h:mm:ss A')} -

+

-

- -

- {item.tags.map((tag, i) => { - return ( - - {tag} - - ); - })} -
-
- ); - } -} +
+ {item.tags.map((tag, i) => { + return ( + + {tag} + + ); + })} +
+
+); export default NewsItem; diff --git a/app/components/Dashboard/NewsTab/index.js b/app/components/Dashboard/NewsTab/index.js index dbed6608e1..be2e654a86 100644 --- a/app/components/Dashboard/NewsTab/index.js +++ b/app/components/Dashboard/NewsTab/index.js @@ -1,34 +1,22 @@ import React from 'react'; -import { Dimmer, Loader, Tab } from 'semantic-ui-react'; -import moment from 'moment'; +import { Tab } from 'semantic-ui-react'; import _ from 'lodash'; import NewsItem from './NewsItem'; import styles from './styles.scss'; -class NewsTab extends React.Component { - constructor(props) { - super(props); - } - - render() { - let { news } = this.props; - - return ( - -
- {_(news) - .sortBy('timestamp') - .reverse() - .value() - .map((item, i) => { - - return ; - })} -
-
- ); - } -} +const NewsTab = ({ news }) => ( + +
+ {_(news) + .sortBy('timestamp') + .reverse() + .value() + .map((item, i) => { + return ; + })} +
+
+); export default NewsTab; diff --git a/app/components/Dashboard/index.js b/app/components/Dashboard/index.js index f9040426b0..d0a16edfaf 100644 --- a/app/components/Dashboard/index.js +++ b/app/components/Dashboard/index.js @@ -1,5 +1,6 @@ import React from 'react'; import { Tab } from 'semantic-ui-react'; +import { withTranslation } from 'react-i18next'; import BestNewMusicTab from './BestNewMusicTab'; import ChartsTab from './ChartsTab'; @@ -8,6 +9,7 @@ import NewsTab from './NewsTab'; import styles from './styles.scss'; +@withTranslation('dashboard') class Dashboard extends React.Component { @@ -17,7 +19,7 @@ class Dashboard extends React.Component { dashboardData, history, musicSources, - settings + t } = this.props; const { @@ -26,14 +28,12 @@ class Dashboard extends React.Component { addToQueue, selectSong, clearQueue, - startPlayback, - addFavoriteTrack, - info + startPlayback } = actions; return [ { - menuItem: 'Best new music', + menuItem: t('best'), render: () => ( ( ( } ]; diff --git a/app/components/Downloads/DownloadsItem/index.js b/app/components/Downloads/DownloadsItem/index.js index ae81a379c7..0f7fc9a5c0 100644 --- a/app/components/Downloads/DownloadsItem/index.js +++ b/app/components/Downloads/DownloadsItem/index.js @@ -3,8 +3,6 @@ import PropTypes from 'prop-types'; import { Icon, Table } from 'semantic-ui-react'; import _ from 'lodash'; -import styles from './styles.scss'; - const StatusIcon = props => { switch (props.status) { case 'Waiting': diff --git a/app/components/Downloads/DownloadsList/index.js b/app/components/Downloads/DownloadsList/index.js index 8868450b7f..01dcb6ca84 100644 --- a/app/components/Downloads/DownloadsList/index.js +++ b/app/components/Downloads/DownloadsList/index.js @@ -1,6 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { Button, Icon, Segment, Table } from 'semantic-ui-react'; +import { useTranslation } from 'react-i18next'; import DownloadsItem from '../DownloadsItem'; @@ -11,19 +12,21 @@ const DownloadsList = props => { items, clearFinishedTracks } = props; + + const { t } = useTranslation('downloads'); return ( - Status - Name - Completion + {t('status')} + {t('name')} + {t('completion')} diff --git a/app/components/Downloads/index.js b/app/components/Downloads/index.js index de895c0bad..6ac37043e2 100644 --- a/app/components/Downloads/index.js +++ b/app/components/Downloads/index.js @@ -6,46 +6,36 @@ import Header from '../Header'; import DownloadsList from './DownloadsList'; import styles from './styles.scss'; +import { useTranslation } from 'react-i18next'; const EmptyState = () => { + const { t } = useTranslation('downloads'); + return (
- -

- Downloads are empty. -

-
- Add something to your download queue and you'll see it here! -
+ +

{t('empty')}

+
{t('empty-help')}
); }; const Downloads = props => { - const { - downloads, - clearFinishedTracks - } = props; - + const { downloads, clearFinishedTracks } = props; + const { t } = useTranslation('downloads'); + return (
- - { - downloads.length === 0 && - - } - { - downloads.length > 0 && + {downloads.length === 0 && } + {downloads.length > 0 && ( -
- Downloads -
+
{t('downloads')}
- } + )}
); }; diff --git a/app/components/FavoriteTracksView/index.js b/app/components/FavoriteTracksView/index.js index cb3637ebd8..93a4d5f707 100644 --- a/app/components/FavoriteTracksView/index.js +++ b/app/components/FavoriteTracksView/index.js @@ -2,6 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { Icon, Segment } from 'semantic-ui-react'; import _ from 'lodash'; +import { useTranslation } from 'react-i18next'; import Header from '../Header'; import TrackRow from '../TrackRow'; @@ -10,11 +11,13 @@ import trackRowStyles from '../TrackRow/styles.scss'; import styles from './styles.scss'; const EmptyState = () => { + const { t } = useTranslation('favorites'); + return (
-

No favorites added.

-
Try favoriting some tracks and they will appear here!
+

{t('empty')}

+
{t('empty-extra')}
); }; @@ -24,6 +27,7 @@ const FavoriteTracksView = props => { tracks, removeFavoriteTrack } = props; + const { t } = useTranslation('favorites'); return (
@@ -35,7 +39,7 @@ const FavoriteTracksView = props => { !_.isEmpty(tracks) &&
- Your favorite tracks + {t('header')}
@@ -43,8 +47,8 @@ const FavoriteTracksView = props => { - - + + diff --git a/app/components/Footer/index.js b/app/components/Footer/index.js index 95b20cd718..ec845325ec 100644 --- a/app/components/Footer/index.js +++ b/app/components/Footer/index.js @@ -1,15 +1,7 @@ import React from 'react'; -import styles from './styles.css'; - -class Footer extends React.Component { - render() { - return ( -
- {this.props.children} -
- ); - } -} +const Footer = ({ className, children }) => ( +
{children}
+); export default Footer; diff --git a/app/components/Header/index.js b/app/components/Header/index.js index 97487fe4ec..d6d1d39da3 100644 --- a/app/components/Header/index.js +++ b/app/components/Header/index.js @@ -2,15 +2,8 @@ import React from 'react'; import styles from './styles.scss'; -class Header extends React.Component { - - render() { - return ( -
- {this.props.children} -
- ); - } -} +const Header = ({ children }) => ( +
{children}
+); export default Header; diff --git a/app/components/HelpButton/index.js b/app/components/HelpButton/index.js index fc3cb7c73d..0c95baf72d 100644 --- a/app/components/HelpButton/index.js +++ b/app/components/HelpButton/index.js @@ -1,5 +1,4 @@ import React from 'react'; -import PropTypes from 'prop-types'; import { Button, Icon } from 'semantic-ui-react'; import styles from './styles.scss'; @@ -15,12 +14,4 @@ const HelpButton = props => { ); }; -HelpButton.propTypes = { - -}; - -HelpButton.defaultProps = { - -}; - export default HelpButton; diff --git a/app/components/HelpModal/index.js b/app/components/HelpModal/index.js index 5385e7da10..e11b31739b 100644 --- a/app/components/HelpModal/index.js +++ b/app/components/HelpModal/index.js @@ -1,10 +1,6 @@ -import React from 'react'; -import { - Button, - Header, - Image, - Modal -} from 'semantic-ui-react'; +import React, { useState, useCallback } from 'react'; +import { Header, Image, Modal } from 'semantic-ui-react'; +import { useTranslation } from 'react-i18next'; import HelpButton from '../HelpButton'; import { agplDisclaimer } from './const'; @@ -12,51 +8,43 @@ import { agplDisclaimer } from './const'; import logoImg from '../../../resources/media/logo_full_light.png'; import styles from './styles.scss'; -class HelpModal extends React.Component { - constructor(props) { - super(props); +const HelpModal = () => { + const [isOpen, setIsOpen] = useState(false); + const handleOpen = useCallback(() => setIsOpen(true), []); + const handleClose = useCallback(() => setIsOpen(false), []); + const { t } = useTranslation('help'); - this.state = { - open: false - }; - } - - handleOpen() { - this.setState({ open: true }); - } - - handleClose() { - this.setState({ open: false }); - } - - render() { - return ( - } - className={ styles.help_modal } - > - About Nuclear Music Player - - - -
Desktop music player for streaming from free sources
-

Copyright © nukeop 2019, released under AGPL-3.0

-

Many thanks to our contributors on Github, your help was vital in creating this program.

-
-
- - - { agplDisclaimer } - - -
- ); - } -} + return ( + } + className={styles.help_modal} + > + {t('about')} + + + +
+ Desktop music player for streaming from free sources +
+

+ Copyright © nukeop 2019, + {t('released')} +

+

+ {t('thanks')} +

+
+
+ + {agplDisclaimer} + +
+ ); +}; export default HelpModal; diff --git a/app/components/InputDialog/index.js b/app/components/InputDialog/index.js index c781df4629..ce1e8dcac5 100644 --- a/app/components/InputDialog/index.js +++ b/app/components/InputDialog/index.js @@ -1,85 +1,53 @@ -import React from 'react'; +import React, { useState, useCallback } from 'react'; import { Button, Input, Modal } from 'semantic-ui-react'; - -class InputDialog extends React.Component { - constructor(props) { - super(props); - - this.state = { - isOpen: false, - inputString: this.props.initialString - }; - - this.handleClose = this.handleClose.bind(this); - this.handleChange = this.handleChange.bind(this); - } - - handleClose() { - this.setState({ - isOpen: false - }); - } - - handleOpen() { - this.setState({ - isOpen: true - }); - } - - handleChange(e) { - this.setState({ - inputString: e.target.value - }); - } - - render() { - let { - trigger, - header, - placeholder, - accept, - onAccept - } = this.props; - - let onClick = () => { - onAccept(this.state.inputString); - this.handleClose(); - }; - - return ( - - - {header} - { - input && input.focus(); - }} - placeholder={placeholder} - onChange={this.handleChange} - value={this.state.inputString} - /> - - - - - - - ); - } -} +import { useTranslation } from 'react-i18next'; + +const InputDialog = ({ initialString, trigger, header, placeholder, accept, onAccept }) => { + const [isOpen, setIsOpen] = useState(false); + const [inputString, setInputString] = useState(initialString); + const { t } = useTranslation('input-dialog'); + + const handleOpen = useCallback(() => setIsOpen(true), []); + const handleClose = useCallback(() => setIsOpen(false), []); + const handleChange = useCallback(e => setInputString(e.target.value), []); + const onClick = useCallback(e => { + setInputString(e.target.value); + onAccept(inputString); + }, [inputString, onAccept]); + + return ( + + + {header} + { + input && input.focus(); + }} + placeholder={placeholder} + onChange={handleChange} + value={inputString} + /> + + + + + + + ); +}; export default InputDialog; diff --git a/app/components/LibraryView/index.js b/app/components/LibraryView/index.js index bf1f84bd0b..597e3b0a26 100644 --- a/app/components/LibraryView/index.js +++ b/app/components/LibraryView/index.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useCallback } from 'react'; import PropTypes from 'prop-types'; import { Button, @@ -10,6 +10,7 @@ import { Segment, Table } from 'semantic-ui-react'; +import { useTranslation } from 'react-i18next'; import Header from '../Header'; import TrackRow from '../TrackRow'; @@ -25,15 +26,17 @@ const LibraryView = ({ sortBy, direction }) => { - const handleSort = columnName => () => { - actions.updateLocalSort(columnName, sortBy, direction); - }; - + const handleSort = useCallback( + columnName => () => { + actions.updateLocalSort(columnName, sortBy, direction); + }, + [sortBy, direction, actions.updateLocalSort] + ); + const { t } = useTranslation('library'); + return (
-
- Local Library -
+
{t('header')}
ArtistTitle{t('artist')}{t('title')}
@@ -95,35 +96,36 @@ const LibraryView = ({ sorted={sortBy === 'artist' ? direction : null} onClick={handleSort('artist')} > - Artist + {t('artist')} - Title + {t('title')} - Album + {t('album')} - {tracks && tracks.map((track, idx) => ( - - ))} + {tracks && + tracks.map((track, idx) => ( + + ))}
)} diff --git a/app/components/LyricsView/index.js b/app/components/LyricsView/index.js index 087c075155..67638d2436 100644 --- a/app/components/LyricsView/index.js +++ b/app/components/LyricsView/index.js @@ -1,11 +1,13 @@ import React from 'react'; import FontAwesome from 'react-fontawesome'; import _ from 'lodash'; +import { withTranslation } from 'react-i18next'; import LyricsHeader from './LyricsHeader'; import styles from './styles.scss'; +@withTranslation('lyrics') class LyricsView extends React.Component { constructor(props) { super(props); @@ -16,7 +18,7 @@ class LyricsView extends React.Component { let lyricsStr = _.get(lyrics, 'lyricsSearchResults', ''); lyricsStr = _.get(lyricsStr, 'type', ''); if (lyricsStr === '') { - lyricsStr = 'No lyrics were found for this song.'; + lyricsStr = this.props.t('not-found'); } return (
@@ -38,8 +40,8 @@ class LyricsView extends React.Component { return (
-

Nothing is playing.

-
Add some music to the queue to display the lyrics here!
+

{this.props.t('empty')}

+
{this.props.t('empty-help')}
); } diff --git a/app/components/MainLayout/index.js b/app/components/MainLayout/index.js index b00342fb11..e4106ef2bd 100644 --- a/app/components/MainLayout/index.js +++ b/app/components/MainLayout/index.js @@ -1,17 +1,12 @@ import React from 'react'; -import { Link } from 'react-router-dom'; import cx from 'classnames'; import styles from './styles.scss'; -class MainLayout extends React.Component { - render() { - return ( -
- {this.props.children} -
- ); - } -} +const MainLayout = ({ className, children }) => ( +
+ {children} +
+); export default MainLayout; diff --git a/app/components/Navbar/index.js b/app/components/Navbar/index.js index 6bc818e696..11638d9818 100644 --- a/app/components/Navbar/index.js +++ b/app/components/Navbar/index.js @@ -1,15 +1,7 @@ import React from 'react'; -import styles from './styles.css'; - -class Navbar extends React.Component { - render() { - return ( -
- {this.props.children} -
- ); - } -} +const Navbar = ({ className, children }) => ( +
{children}
+); export default Navbar; diff --git a/app/components/PlayOptionsControls/index.js b/app/components/PlayOptionsControls/index.js index 7c1bbe1079..27e507d4ea 100644 --- a/app/components/PlayOptionsControls/index.js +++ b/app/components/PlayOptionsControls/index.js @@ -2,6 +2,7 @@ import React from 'react'; import classnames from 'classnames'; import FontAwesome from 'react-fontawesome'; import _ from 'lodash'; +import { useTranslation } from 'react-i18next'; import styles from './styles.scss'; import settingsConst from '../../constants/settings'; @@ -26,11 +27,13 @@ function renderOptionControl(props, settingName, fontAwesomeName, title) { } const PlayOptionsControls = props => { + const { t } = useTranslation('option-control'); + return (
- {renderOptionControl(props, 'loopAfterQueueEnd', 'repeat', 'Loop')} - {renderOptionControl(props, 'shuffleQueue', 'random', 'Shuffle')} - {renderOptionControl(props, 'autoradio', 'magic', 'Autoradio')} + {renderOptionControl(props, 'loopAfterQueueEnd', 'repeat', t('loop'))} + {renderOptionControl(props, 'shuffleQueue', 'random', t('shuffle'))} + {renderOptionControl(props, 'autoradio', 'magic', t('autoradio'))}
); }; diff --git a/app/components/PlayQueue/QueueItem/index.js b/app/components/PlayQueue/QueueItem/index.js index 9a7aa6cd8b..89697b8b43 100644 --- a/app/components/PlayQueue/QueueItem/index.js +++ b/app/components/PlayQueue/QueueItem/index.js @@ -1,7 +1,6 @@ import React from 'react'; import classNames from 'classnames'; import FontAwesome from 'react-fontawesome'; -import _ from 'lodash'; import { formatDuration, getSelectedStream } from '../../../utils'; import styles from './styles.scss'; diff --git a/app/components/PlayQueue/QueueMenu/QueueMenuMore/index.js b/app/components/PlayQueue/QueueMenu/QueueMenuMore/index.js index 13575a39de..d62c3d8dde 100644 --- a/app/components/PlayQueue/QueueMenu/QueueMenuMore/index.js +++ b/app/components/PlayQueue/QueueMenu/QueueMenuMore/index.js @@ -2,93 +2,87 @@ import React from 'react'; import PropTypes from 'prop-types'; import cx from 'classnames'; import _ from 'lodash'; -import { - Dropdown, - Icon -} from 'semantic-ui-react'; +import { Dropdown, Icon } from 'semantic-ui-react'; +import { useTranslation } from 'react-i18next'; import styles from './styles.scss'; const addTrackToPlaylist = (updatePlaylist, playlist, track) => { if (track.name) { - playlist.tracks.push(track); - updatePlaylist(playlist); + playlist.tracks.push(track); + updatePlaylist(playlist); } }; const addFavoriteTrackFromQueue = (addFavoriteTrack, track) => { if (track.name) { - addFavoriteTrack({ - artist: { - name: track.artist - }, - name: track.name, - image: [ - { - '#text': track.thumbnail - } - ] - }); + addFavoriteTrack({ + artist: { + name: track.artist + }, + name: track.name, + image: [ + { + '#text': track.thumbnail + } + ] + }); } }; -const QueueMenuMore = props => { - const { - clearQueue, - savePlaylistDialog, - addFavoriteTrack, - addToDownloads, - updatePlaylist, - playlists, - currentItem - } = props; - +const QueueMenuMore = ({ + clearQueue, + savePlaylistDialog, + addFavoriteTrack, + addToDownloads, + updatePlaylist, + playlists, + currentItem +}) => { + const { t } = useTranslation('queue'); + return ( - + - - Queue - - - - Clear queue + {t('header')} + + + {t('clear')} - { savePlaylistDialog } + {savePlaylistDialog} - - - Current track - + + {t('header-track')} - - - { - _.map(playlists, playlist => { - return ( - addTrackToPlaylist(updatePlaylist, playlist, currentItem) }> - - { playlist.name } - - ); - }) - } + + + {_.map(playlists, (playlist, i) => { + return ( + + addTrackToPlaylist(updatePlaylist, playlist, currentItem) + } + > + + {playlist.name} + + ); + })} - addFavoriteTrackFromQueue(addFavoriteTrack, currentItem) - }> - - Add to favorites + + addFavoriteTrackFromQueue(addFavoriteTrack, currentItem) + } + > + + {t('favorite-add')} - addToDownloads(currentItem) - }> - - Download + addToDownloads(currentItem)}> + + {t('download')} diff --git a/app/components/PlayQueue/QueueMenu/index.js b/app/components/PlayQueue/QueueMenu/index.js index da88209350..e18e2bea36 100644 --- a/app/components/PlayQueue/QueueMenu/index.js +++ b/app/components/PlayQueue/QueueMenu/index.js @@ -5,6 +5,7 @@ import { Dropdown, Icon } from 'semantic-ui-react'; +import { withTranslation } from 'react-i18next'; import InputDialog from '../../InputDialog'; import QueueMenuMore from './QueueMenuMore'; @@ -12,6 +13,7 @@ import QueueMenuMore from './QueueMenuMore'; import styles from './styles.scss'; import settingsConst from '../../../constants/settings'; +@withTranslation('queue') class QueueMenu extends React.Component { constructor(props){ super(props); @@ -21,8 +23,8 @@ class QueueMenu extends React.Component { return name => { addPlaylist(items, name); notify( - 'Playlist created', - `Playlist ${name} has been created.`, + this.props.t('playlist-toast-title'), + this.props.t('playlist-toast-content', { name }), null, settings ); @@ -40,7 +42,8 @@ class QueueMenu extends React.Component { items, toggleOption, settings, - playlists + playlists, + t } = this.props; const firstTitle = _.get(_.head(items), 'name'); @@ -62,13 +65,13 @@ class QueueMenu extends React.Component { savePlaylistDialog={ Input playlist name:} - placeholder='Playlist name...' - accept='Save' + placeholder={t('dialog-placeholder')} + accept={t('dialog-accept')} onAccept={this.handleAddPlaylist(addPlaylist, success, items, settings)} trigger={ - Save as playlist + {t('dialog-trigger')} } initialString={ firstTitle } diff --git a/app/components/PlayQueue/index.js b/app/components/PlayQueue/index.js index 87f31599b6..01cc255248 100644 --- a/app/components/PlayQueue/index.js +++ b/app/components/PlayQueue/index.js @@ -2,6 +2,8 @@ import React from 'react'; import classnames from 'classnames'; import { ipcRenderer } from 'electron'; import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd'; +import _ from 'lodash'; +import { withTranslation } from 'react-i18next'; import styles from './styles.scss'; @@ -9,6 +11,7 @@ import QueuePopup from '../QueuePopup'; import QueueItem from './QueueItem'; import QueueMenu from './QueueMenu'; +@withTranslation('queue') class PlayQueue extends React.Component { constructor(props){ super(props); @@ -19,7 +22,7 @@ class PlayQueue extends React.Component { } onAddToDownloads(track) { - const { actions, plugins, settings } = this.props; + const { actions, plugins, settings, t } = this.props; const { addToDownloads, info } = actions; const artistName = _.isString(_.get(track, 'artist')) ? _.get(track, 'artist') @@ -27,8 +30,8 @@ class PlayQueue extends React.Component { ipcRenderer.send('start-download', track); addToDownloads(plugins.plugins.musicSources, track); info( - 'Track added to downloads', - `${artistName} - ${track.name} has been added to downloads.`, + t('download-toast-title'), + t('download-toast-content', { artist: artistName, title: track.name }), , settings ); @@ -42,7 +45,7 @@ class PlayQueue extends React.Component { return this.props.items.map((el, i) => { return ( - {(provided, snapshot) => ( + {(provided) => (
{ this.getIcon() diff --git a/app/components/PlayerControls/PlayerButton/index.js b/app/components/PlayerControls/PlayerButton/index.js index 2f913d7577..389b8ed1f8 100644 --- a/app/components/PlayerControls/PlayerButton/index.js +++ b/app/components/PlayerControls/PlayerButton/index.js @@ -4,21 +4,18 @@ import classNames from 'classnames'; import styles from './styles.scss'; -class PlayerButton extends React.Component { - - render() { - return ( -
- -
- ); - } -} +const PlayerButton = ({ ariaLabel, icon, onClick }) => ( +
+ + + +
+); export default PlayerButton; diff --git a/app/components/PlayerControls/index.js b/app/components/PlayerControls/index.js index bdb63b59ba..037dc4aa9d 100644 --- a/app/components/PlayerControls/index.js +++ b/app/components/PlayerControls/index.js @@ -4,29 +4,30 @@ import styles from './styles.scss'; import PlayerButton from './PlayerButton'; import PlayPauseButton from './PlayPauseButton'; +import { useTranslation } from 'react-i18next'; -class PlayerControls extends React.Component { - render() { - return ( -
- - - -
- ); - } -} +const PlayerControls = ({ back, togglePlay, playing, loading, forward }) => { + const { t } = useTranslation('player'); + + return ( +
+ + + +
+ ); +}; export default PlayerControls; diff --git a/app/components/PlaylistView/index.js b/app/components/PlaylistView/index.js index 9ebcbb32f2..1c735a17ce 100644 --- a/app/components/PlaylistView/index.js +++ b/app/components/PlaylistView/index.js @@ -4,6 +4,7 @@ import { Button, Icon } from 'semantic-ui-react'; +import { withTranslation } from 'react-i18next'; import InputDialog from '../InputDialog'; import ContextPopup from '../ContextPopup'; @@ -13,6 +14,7 @@ import artPlaceholder from '../../../resources/media/art_placeholder.png'; import styles from './styles.scss'; +@withTranslation('playlists') class PlaylistView extends React.Component { constructor(props) { super(props); @@ -58,9 +60,9 @@ class PlaylistView extends React.Component { onClick={() => this.deletePlaylist(this.props.playlist) } - ariaLabel='Delete this playlist' + ariaLabel={this.props.t('delete')} icon='trash' - label='Delete this playlist' + label={this.props.t('delete')} /> ); @@ -116,7 +118,7 @@ class PlaylistView extends React.Component { { playlist.name } Input new playlist name:} - placeholder='Playlist name...' + placeholder={this.props.t('dialog-placeholder')} accept='Rename' initialString={ playlist.name } onAccept={ @@ -125,7 +127,7 @@ class PlaylistView extends React.Component { trigger={ - } /> - } - { - !_.isNil(username) && - - } + + {_.isNil(username) && ( + logIn(code)} + render={oauthProps => ( + + )} + /> + )} + {!_.isNil(username) && ( + + )}
); }; diff --git a/app/components/Settings/SocialIntegration/index.js b/app/components/Settings/SocialIntegration/index.js index 98add120c9..8eecb19ee1 100644 --- a/app/components/Settings/SocialIntegration/index.js +++ b/app/components/Settings/SocialIntegration/index.js @@ -1,42 +1,25 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { - Grid -} from 'semantic-ui-react'; +import { Grid } from 'semantic-ui-react'; import styles from './styles.scss'; -const SocialIntegration = props => { - const { - logo, - title, - description, - children - } = props; - - return ( - - - - - - { logo } - - - { title } - - - -

- { description } -

-
-
-
- { children } -
- ); -}; +const SocialIntegration = ({ logo, title, description, children }) => ( + + + + + {logo} + {title} + + +

{description}

+
+
+
+ {children} +
+); SocialIntegration.propTypes = { logo: PropTypes.node, diff --git a/app/components/Settings/index.js b/app/components/Settings/index.js index 95c52e2aea..3ec33b550c 100644 --- a/app/components/Settings/index.js +++ b/app/components/Settings/index.js @@ -1,6 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { Button, Input, Radio, Segment } from 'semantic-ui-react'; +import { Button, Input, Radio, Segment, Select } from 'semantic-ui-react'; import Range from 'react-range-progress'; import cx from 'classnames'; import _ from 'lodash'; @@ -8,6 +8,7 @@ import { Divider, Icon } from 'semantic-ui-react'; +import i18n from 'i18next'; import Header from '../Header'; import Spacer from '../Spacer'; @@ -16,6 +17,7 @@ import GithubSettings from './GithubSettings'; import settingsEnum from '../../constants/settingsEnum'; import styles from './styles.scss'; +import { withTranslation } from 'react-i18next'; const volumeSliderColors = { fillColor: { r: 248, g: 248, b: 242, a: 1 }, @@ -23,6 +25,7 @@ const volumeSliderColors = { thumbColor: { r: 248, g: 248, b: 242, a: 1 } }; +@withTranslation('settings') class Settings extends React.Component { toggleScrobbling ( lastFmScrobblingEnabled, @@ -56,11 +59,8 @@ class Settings extends React.Component { } - title='Last.fm' - description={ - 'In order to enable scrobbling, you first have to' - + ' connect and authorize Nuclear on Last.fm, then click log in.' - } + title={this.props.t('lastfm-title')} + description={this.props.t('lastfm-description')} > {this.renderLastFmLoginButtons()} {this.renderLastFmOptionRadio()} @@ -83,12 +83,12 @@ class Settings extends React.Component { return (
- User: {lastFmName ? lastFmName : 'Not logged in'} + {this.props.t('user')} {lastFmName ? lastFmName : this.props.t('notlogged')} {!lastFmSessionKey && ( )} {!lastFmSessionKey && ( @@ -96,13 +96,13 @@ class Settings extends React.Component { onClick={() => lastFmLoginAction(lastFmAuthToken)} color='red' > - Log in + {this.props.t('login')} )} { lastFmSessionKey && }
@@ -114,7 +114,7 @@ class Settings extends React.Component { const { enableScrobbling, disableScrobbling } = this.props.actions; return (
- + } - title='Github' - description={ - 'Log in via Github to be able to create and share your playlists online (upcoming feature).' - } + title={t('github-title')} + description={t('github-description')} > -
Social
+
{this.props.t('social')}

{ this.renderLastFmSocialIntegration() } @@ -178,6 +177,21 @@ class Settings extends React.Component { this.props.actions.setNumberOption(option.name, _.parseInt(value)); } + renderListOption({ placeholder, options }) { + return ( +