Added internationalization support, and translated to nb-NO.

master
Håvar Aambø Fosstveit 2019-11-06 23:11:42 +01:00
parent 23e02d4865
commit c12057e53c
23 changed files with 1214 additions and 375 deletions

View File

@ -20,6 +20,7 @@
"react": "^16.10.2", "react": "^16.10.2",
"react-cookie-consent": "^2.5.0", "react-cookie-consent": "^2.5.0",
"react-dom": "^16.10.2", "react-dom": "^16.10.2",
"react-intl": "^3.4.0",
"react-redux": "^7.1.1", "react-redux": "^7.1.1",
"react-scripts": "3.2.0", "react-scripts": "3.2.0",
"redux": "^4.0.4", "redux": "^4.0.4",

View File

@ -81,15 +81,19 @@ const VIDEO_ENCODINGS =
let store; let store;
let intl;
export default class RoomClient export default class RoomClient
{ {
/** /**
* @param {Object} data * @param {Object} data
* @param {Object} data.store - The Redux store. * @param {Object} data.store - The Redux store.
* @param {Object} data.intl - react-intl object
*/ */
static init(data) static init(data)
{ {
store = data.store; store = data.store;
intl = data.intl;
} }
constructor( constructor(
@ -230,7 +234,10 @@ export default class RoomClient
store.dispatch(settingsActions.toggleAdvancedMode()); store.dispatch(settingsActions.toggleAdvancedMode());
store.dispatch(requestActions.notify( store.dispatch(requestActions.notify(
{ {
text : 'Toggled advanced mode.' text : intl.formatMessage({
id : 'room.toggleAdvancedMode',
defaultMessage : 'Toggled advanced mode'
})
})); }));
break; break;
} }
@ -240,7 +247,10 @@ export default class RoomClient
store.dispatch(roomActions.setDisplayMode('democratic')); store.dispatch(roomActions.setDisplayMode('democratic'));
store.dispatch(requestActions.notify( store.dispatch(requestActions.notify(
{ {
text : 'Changed layout to democratic view.' text : intl.formatMessage({
id : 'room.setDemocraticView',
defaultMessage : 'Changed layout to democratic view'
})
})); }));
break; break;
} }
@ -250,7 +260,10 @@ export default class RoomClient
store.dispatch(roomActions.setDisplayMode('filmstrip')); store.dispatch(roomActions.setDisplayMode('filmstrip'));
store.dispatch(requestActions.notify( store.dispatch(requestActions.notify(
{ {
text : 'Changed layout to filmstrip view.' text : intl.formatMessage({
id : 'room.setFilmStripView',
defaultMessage : 'Changed layout to filmstrip view'
})
})); }));
break; break;
} }
@ -265,7 +278,10 @@ export default class RoomClient
store.dispatch(requestActions.notify( store.dispatch(requestActions.notify(
{ {
text : 'Muted your microphone.' text : intl.formatMessage({
id : 'devices.microPhoneMute',
defaultMessage : 'Muted your microphone'
})
})); }));
} }
else else
@ -274,7 +290,10 @@ export default class RoomClient
store.dispatch(requestActions.notify( store.dispatch(requestActions.notify(
{ {
text : 'Unmuted your microphone.' text : intl.formatMessage({
id : 'devices.microPhoneUnMute',
defaultMessage : 'Unmuted your microphone'
})
})); }));
} }
} }
@ -284,7 +303,10 @@ export default class RoomClient
store.dispatch(requestActions.notify( store.dispatch(requestActions.notify(
{ {
text : 'Enabled your microphone.' text : intl.formatMessage({
id : 'devices.microphoneEnable',
defaultMessage : 'Enabled your microphone'
})
})); }));
} }
@ -311,7 +333,10 @@ export default class RoomClient
store.dispatch(requestActions.notify( store.dispatch(requestActions.notify(
{ {
text : 'Your devices changed, configure your devices in the settings dialog.' text : intl.formatMessage({
id : 'devices.devicesChanged',
defaultMessage : 'Your devices changed, configure your devices in the settings dialog'
})
})); }));
}); });
} }
@ -349,7 +374,10 @@ export default class RoomClient
store.dispatch(requestActions.notify( store.dispatch(requestActions.notify(
{ {
text : 'You are logged in.' text : intl.formatMessage({
id : 'room.loggedIn',
defaultMessage : 'You are logged in'
})
})); }));
} }
@ -361,7 +389,10 @@ export default class RoomClient
store.dispatch(requestActions.notify( store.dispatch(requestActions.notify(
{ {
text : 'You are logged out.' text : intl.formatMessage({
id : 'room.loggedOut',
defaultMessage : 'You are logged out'
})
})); }));
} }
@ -458,7 +489,13 @@ export default class RoomClient
store.dispatch(requestActions.notify( store.dispatch(requestActions.notify(
{ {
text : `Your display name changed to ${displayName}.` text : intl.formatMessage({
id : 'room.changedDisplayName',
defaultMessage : 'Your display name changed to {displayName}',
values : {
displayName
}
})
})); }));
} }
catch (error) catch (error)
@ -468,7 +505,10 @@ export default class RoomClient
store.dispatch(requestActions.notify( store.dispatch(requestActions.notify(
{ {
type : 'error', type : 'error',
text : 'An error occured while changing your display name.' text : intl.formatMessage({
id : 'room.changeDisplayNameError',
defaultMessage : 'An error occured while changing your display name'
})
})); }));
} }
@ -508,7 +548,10 @@ export default class RoomClient
store.dispatch(requestActions.notify( store.dispatch(requestActions.notify(
{ {
type : 'error', type : 'error',
text : 'Unable to send chat message.' text : intl.formatMessage({
id : 'room.chatError',
defaultMessage : 'Unable to send chat message'
})
})); }));
} }
} }
@ -522,7 +565,10 @@ export default class RoomClient
return store.dispatch(requestActions.notify( return store.dispatch(requestActions.notify(
{ {
type : 'error', type : 'error',
text : 'Unable to save file.' text : intl.formatMessage({
id : 'filesharing.saveFileError',
defaultMessage : 'Unable to save file'
})
})); }));
} }
@ -589,7 +635,10 @@ export default class RoomClient
{ {
store.dispatch(requestActions.notify( store.dispatch(requestActions.notify(
{ {
text : 'Starting file share.' text : intl.formatMessage({
id : 'filesharing.startingFileShare',
defaultMessage : 'Attempting to share file'
})
})); }));
this._webTorrent.seed(files, (torrent) => this._webTorrent.seed(files, (torrent) =>
@ -603,7 +652,10 @@ export default class RoomClient
store.dispatch(requestActions.notify( store.dispatch(requestActions.notify(
{ {
text : 'File successfully shared.' text : intl.formatMessage({
id : 'filesharing.successfulFileShare',
defaultMessage : 'File successfully shared'
})
})); }));
store.dispatch(fileActions.addFile( store.dispatch(fileActions.addFile(
@ -631,7 +683,10 @@ export default class RoomClient
store.dispatch(requestActions.notify( store.dispatch(requestActions.notify(
{ {
type : 'error', type : 'error',
text : 'Unable to share file.' text : intl.formatMessage({
id : 'filesharing.unableToShare',
defaultMessage : 'Unable to share file'
})
})); }));
} }
} }
@ -689,12 +744,6 @@ export default class RoomClient
catch (error) catch (error)
{ {
logger.error('getServerHistory() | failed: %o', error); logger.error('getServerHistory() | failed: %o', error);
store.dispatch(requestActions.notify(
{
type : 'error',
text : 'Unable to retrieve room history.'
}));
} }
} }
@ -719,7 +768,10 @@ export default class RoomClient
store.dispatch(requestActions.notify( store.dispatch(requestActions.notify(
{ {
type : 'error', type : 'error',
text : 'Unable to access your microphone.' text : intl.formatMessage({
id : 'devices.microphoneMuteError',
defaultMessage : 'Unable to mute your microphone'
})
})); }));
} }
} }
@ -751,7 +803,10 @@ export default class RoomClient
store.dispatch(requestActions.notify( store.dispatch(requestActions.notify(
{ {
type : 'error', type : 'error',
text : 'An error occured while accessing your microphone.' text : intl.formatMessage({
id : 'devices.microphoneUnMuteError',
defaultMessage : 'Unable to unmute your microphone'
})
})); }));
} }
} }
@ -1133,12 +1188,6 @@ export default class RoomClient
{ {
logger.error('sendRaiseHandState() | failed: %o', error); logger.error('sendRaiseHandState() | failed: %o', error);
store.dispatch(requestActions.notify(
{
type : 'error',
text : `An error occured while ${state ? 'raising' : 'lowering'} hand.`
}));
// We need to refresh the component for it to render changed state // We need to refresh the component for it to render changed state
store.dispatch(meActions.setMyRaiseHandState(!state)); store.dispatch(meActions.setMyRaiseHandState(!state));
} }
@ -1213,7 +1262,7 @@ export default class RoomClient
store.dispatch(requestActions.notify( store.dispatch(requestActions.notify(
{ {
type : 'error', type : 'error',
text : `An error occured with file sharing` text : intl.formatMessage({ id: 'filesharing.error', defaultMessage: 'There was a filesharing error' })
})); }));
}); });
@ -1241,7 +1290,10 @@ export default class RoomClient
{ {
store.dispatch(requestActions.notify( store.dispatch(requestActions.notify(
{ {
text : 'You are disconnected.' text : intl.formatMessage({
id : 'socket.disconnected',
defaultMessage : 'You are disconnected'
})
})); }));
this.close(); this.close();
@ -1249,7 +1301,10 @@ export default class RoomClient
store.dispatch(requestActions.notify( store.dispatch(requestActions.notify(
{ {
text : 'You are disconnected, attempting to reconnect.' text : intl.formatMessage({
id : 'socket.reconnecting',
defaultMessage : 'You are disconnected, attempting to reconnect'
})
})); }));
store.dispatch(roomActions.setRoomState('connecting')); store.dispatch(roomActions.setRoomState('connecting'));
@ -1261,7 +1316,10 @@ export default class RoomClient
store.dispatch(requestActions.notify( store.dispatch(requestActions.notify(
{ {
text : 'You are disconnected.' text : intl.formatMessage({
id : 'socket.disconnected',
defaultMessage : 'You are disconnected'
})
})); }));
this.close(); this.close();
@ -1273,7 +1331,10 @@ export default class RoomClient
store.dispatch(requestActions.notify( store.dispatch(requestActions.notify(
{ {
text : 'You are reconnected.' text : intl.formatMessage({
id : 'socket.reconnected',
defaultMessage : 'You are reconnected'
})
})); }));
store.dispatch(roomActions.setRoomState('connected')); store.dispatch(roomActions.setRoomState('connected'));
@ -1450,7 +1511,10 @@ export default class RoomClient
store.dispatch(requestActions.notify( store.dispatch(requestActions.notify(
{ {
text : 'Room is now locked.' text : intl.formatMessage({
id : 'room.locked',
defaultMessage : 'Room is now locked'
})
})); }));
break; break;
@ -1463,7 +1527,10 @@ export default class RoomClient
store.dispatch(requestActions.notify( store.dispatch(requestActions.notify(
{ {
text : 'Room is now unlocked.' text : intl.formatMessage({
id : 'room.unlocked',
defaultMessage : 'Room is now unlocked'
})
})); }));
break; break;
@ -1480,7 +1547,10 @@ export default class RoomClient
store.dispatch(requestActions.notify( store.dispatch(requestActions.notify(
{ {
text : 'New participant entered the lobby.' text : intl.formatMessage({
id : 'room.newLobbyPeer',
defaultMessage : 'New participant entered the lobby'
})
})); }));
break; break;
@ -1495,7 +1565,10 @@ export default class RoomClient
store.dispatch(requestActions.notify( store.dispatch(requestActions.notify(
{ {
text : 'Participant in lobby left.' text : intl.formatMessage({
id : 'room.lobbyPeerLeft',
defaultMessage : 'Participant in lobby left'
})
})); }));
break; break;
@ -1520,7 +1593,13 @@ export default class RoomClient
store.dispatch(requestActions.notify( store.dispatch(requestActions.notify(
{ {
text : `Participant in lobby changed name to ${displayName}.` text : intl.formatMessage({
id : 'room.lobbyPeerChangedDisplayName',
defaultMessage : 'Participant in lobby changed name to {displayName}',
values : {
displayName
}
})
})); }));
break; break;
@ -1535,7 +1614,10 @@ export default class RoomClient
store.dispatch(requestActions.notify( store.dispatch(requestActions.notify(
{ {
text : 'Participant in lobby changed picture.' text : intl.formatMessage({
id : 'room.lobbyPeerChangedPicture',
defaultMessage : 'Participant in lobby changed picture'
})
})); }));
break; break;
@ -1550,7 +1632,10 @@ export default class RoomClient
store.dispatch(requestActions.notify( store.dispatch(requestActions.notify(
{ {
text : 'Access code for room updated' text : intl.formatMessage({
id : 'room.setAccessCode',
defaultMessage : 'Access code for room updated'
})
})); }));
break; break;
@ -1567,14 +1652,20 @@ export default class RoomClient
{ {
store.dispatch(requestActions.notify( store.dispatch(requestActions.notify(
{ {
text : 'Access code for room is now activated' text : intl.formatMessage({
id : 'room.accessCodeOn',
defaultMessage : 'Access code for room is now activated'
})
})); }));
} }
else else
{ {
store.dispatch(requestActions.notify( store.dispatch(requestActions.notify(
{ {
text : 'Access code for room is now deactivated' text : intl.formatMessage({
id : 'room.accessCodeOff',
defaultMessage : 'Access code for room is now deactivated'
})
})); }));
} }
@ -1603,7 +1694,14 @@ export default class RoomClient
store.dispatch(requestActions.notify( store.dispatch(requestActions.notify(
{ {
text : `${oldDisplayName} is now ${displayName}` text : intl.formatMessage({
id : 'room.peerChangedDisplayName',
defaultMessage : '{oldDisplayName} is now {displayName}',
values : {
oldDisplayName,
displayName
}
})
})); }));
break; break;
@ -1647,7 +1745,10 @@ export default class RoomClient
store.dispatch(requestActions.notify( store.dispatch(requestActions.notify(
{ {
text : 'New file available.' text : intl.formatMessage({
id : 'room.newFile',
defaultMessage : 'New file available'
})
})); }));
if ( if (
@ -1683,7 +1784,13 @@ export default class RoomClient
store.dispatch(requestActions.notify( store.dispatch(requestActions.notify(
{ {
text : `${displayName} joined the room.` text : intl.formatMessage({
id : 'room.newPeer',
defaultMessage : '{displayName} joined the room',
values : {
displayName
}
})
})); }));
break; break;
@ -1788,7 +1895,10 @@ export default class RoomClient
store.dispatch(requestActions.notify( store.dispatch(requestActions.notify(
{ {
type : 'error', type : 'error',
text : 'Error on server request.' text : intl.formatMessage({
id : 'socket.requestError',
defaultMessage : 'Error on server request'
})
})); }));
} }
@ -1957,7 +2067,10 @@ export default class RoomClient
store.dispatch(requestActions.notify( store.dispatch(requestActions.notify(
{ {
text : 'You have joined the room.' text : intl.formatMessage({
id : 'room.joined',
defaultMessage : 'You have joined the room'
})
})); }));
this._spotlights.start(); this._spotlights.start();
@ -1969,7 +2082,10 @@ export default class RoomClient
store.dispatch(requestActions.notify( store.dispatch(requestActions.notify(
{ {
type : 'error', type : 'error',
text : 'Unable to join the room.' text : intl.formatMessage({
id : 'room.cantJoin',
defaultMessage : 'Unable to join the room'
})
})); }));
this.close(); this.close();
@ -1989,7 +2105,10 @@ export default class RoomClient
store.dispatch(requestActions.notify( store.dispatch(requestActions.notify(
{ {
text : 'You locked the room.' text : intl.formatMessage({
id : 'room.youLocked',
defaultMessage : 'You locked the room'
})
})); }));
} }
catch (error) catch (error)
@ -1997,7 +2116,10 @@ export default class RoomClient
store.dispatch(requestActions.notify( store.dispatch(requestActions.notify(
{ {
type : 'error', type : 'error',
text : 'Unable to lock the room.' text : intl.formatMessage({
id : 'room.cantLock',
defaultMessage : 'Unable to lock the room'
})
})); }));
logger.error('lockRoom() | failed: %o', error); logger.error('lockRoom() | failed: %o', error);
@ -2017,7 +2139,10 @@ export default class RoomClient
store.dispatch(requestActions.notify( store.dispatch(requestActions.notify(
{ {
text : 'You unlocked the room.' text : intl.formatMessage({
id : 'room.youUnLocked',
defaultMessage : 'You unlocked the room'
})
})); }));
} }
catch (error) catch (error)
@ -2025,7 +2150,10 @@ export default class RoomClient
store.dispatch(requestActions.notify( store.dispatch(requestActions.notify(
{ {
type : 'error', type : 'error',
text : 'Unable to unlock the room.' text : intl.formatMessage({
id : 'room.cantUnLock',
defaultMessage : 'Unable to unlock the room'
})
})); }));
logger.error('unlockRoom() | failed: %o', error); logger.error('unlockRoom() | failed: %o', error);
@ -2164,7 +2292,10 @@ export default class RoomClient
store.dispatch(requestActions.notify( store.dispatch(requestActions.notify(
{ {
type : 'error', type : 'error',
text : 'Microphone disconnected!' text : intl.formatMessage({
id : 'devices.microphoneDisconnected',
defaultMessage : 'Microphone disconnected'
})
})); }));
this.disableMic() this.disableMic()
@ -2215,7 +2346,10 @@ export default class RoomClient
store.dispatch(requestActions.notify( store.dispatch(requestActions.notify(
{ {
type : 'error', type : 'error',
text : 'An error occured while accessing your microphone.' text : intl.formatMessage({
id : 'devices.microphoneError',
defaultMessage : 'An error occured while accessing your microphone'
})
})); }));
if (track) if (track)
@ -2248,11 +2382,7 @@ export default class RoomClient
} }
catch (error) catch (error)
{ {
store.dispatch(requestActions.notify( logger.error('disableMic() [error:"%o"]', error);
{
type : 'error',
text : `Error closing server-side mic Producer: ${error}`
}));
} }
this._micProducer = null; this._micProducer = null;
@ -2341,7 +2471,10 @@ export default class RoomClient
store.dispatch(requestActions.notify( store.dispatch(requestActions.notify(
{ {
type : 'error', type : 'error',
text : 'Screen sharing disconnected!' text : intl.formatMessage({
id : 'devices.screenSharingDisconnected',
defaultMessage : 'Screen sharing disconnected'
})
})); }));
this.disableScreenSharing() this.disableScreenSharing()
@ -2357,7 +2490,10 @@ export default class RoomClient
store.dispatch(requestActions.notify( store.dispatch(requestActions.notify(
{ {
type : 'error', type : 'error',
text : 'An error occured while accessing your camera.' text : intl.formatMessage({
id : 'devices.screenSharingError',
defaultMessage : 'An error occured while accessing your screen'
})
})); }));
if (track) if (track)
@ -2388,11 +2524,7 @@ export default class RoomClient
} }
catch (error) catch (error)
{ {
store.dispatch(requestActions.notify( logger.error('disableScreenSharing() [error:"%o"]', error);
{
type : 'error',
text : `Error closing server-side screen Producer: ${error}`
}));
} }
this._screenSharingProducer = null; this._screenSharingProducer = null;
@ -2497,7 +2629,10 @@ export default class RoomClient
store.dispatch(requestActions.notify( store.dispatch(requestActions.notify(
{ {
type : 'error', type : 'error',
text : 'Webcam disconnected!' text : intl.formatMessage({
id : 'devices.cameraDisconnected',
defaultMessage : 'Camera disconnected'
})
})); }));
this.disableWebcam() this.disableWebcam()
@ -2513,7 +2648,10 @@ export default class RoomClient
store.dispatch(requestActions.notify( store.dispatch(requestActions.notify(
{ {
type : 'error', type : 'error',
text : 'An error occured while accessing your camera.' text : intl.formatMessage({
id : 'devices.cameraError',
defaultMessage : 'An error occured while accessing your camera'
})
})); }));
if (track) if (track)
@ -2545,11 +2683,7 @@ export default class RoomClient
} }
catch (error) catch (error)
{ {
store.dispatch(requestActions.notify( logger.error('disableWebcam() [error:"%o"]', error);
{
type : 'error',
text : `Error closing server-side webcam Producer: ${error}`
}));
} }
this._webcamProducer = null; this._webcamProducer = null;

View File

@ -4,6 +4,7 @@ import { withStyles } from '@material-ui/core/styles';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import classnames from 'classnames'; import classnames from 'classnames';
import { withRoomContext } from '../../../RoomContext'; import { withRoomContext } from '../../../RoomContext';
import { useIntl } from 'react-intl';
import ListItem from '@material-ui/core/ListItem'; import ListItem from '@material-ui/core/ListItem';
import ListItemText from '@material-ui/core/ListItemText'; import ListItemText from '@material-ui/core/ListItemText';
import ListItemIcon from '@material-ui/core/ListItemIcon'; import ListItemIcon from '@material-ui/core/ListItemIcon';
@ -85,6 +86,8 @@ const ListLobbyPeer = (props) =>
classes classes
} = props; } = props;
const intl = useIntl();
const picture = peer.picture || EmptyAvatar; const picture = peer.picture || EmptyAvatar;
return ( return (
@ -100,7 +103,12 @@ const ListLobbyPeer = (props) =>
<ListItemText <ListItemText
primary={peer.displayName} primary={peer.displayName}
/> />
<Tooltip title='Click to let them in'> <Tooltip
title={intl.formatMessage({
id : 'tooltip.admitFromLobby',
defaultMessage : 'Click to let them in'
})}
>
<ListItemIcon <ListItemIcon
className={classnames(classes.button, 'promote', { className={classnames(classes.button, 'promote', {
disabled : peer.promotionInProgress disabled : peer.promotionInProgress

View File

@ -8,6 +8,7 @@ import { withStyles } from '@material-ui/core/styles';
import { withRoomContext } from '../../../RoomContext'; import { withRoomContext } from '../../../RoomContext';
import * as roomActions from '../../../actions/roomActions'; import * as roomActions from '../../../actions/roomActions';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
import Dialog from '@material-ui/core/Dialog'; import Dialog from '@material-ui/core/Dialog';
import DialogTitle from '@material-ui/core/DialogTitle'; import DialogTitle from '@material-ui/core/DialogTitle';
import DialogActions from '@material-ui/core/DialogActions'; import DialogActions from '@material-ui/core/DialogActions';
@ -75,7 +76,12 @@ const LockDialog = ({
paper : classes.dialogPaper paper : classes.dialogPaper
}} }}
> >
<DialogTitle id='form-dialog-title'>Lobby administration</DialogTitle> <DialogTitle id='form-dialog-title'>
<FormattedMessage
id='room.lobbyAdministration'
defaultMessage='Lobby administration'
/>
</DialogTitle>
{/* {/*
<FormControl component='fieldset' className={classes.formControl}> <FormControl component='fieldset' className={classes.formControl}>
<FormLabel component='legend'>Room lock</FormLabel> <FormLabel component='legend'>Room lock</FormLabel>
@ -129,7 +135,10 @@ const LockDialog = ({
dense dense
subheader={ subheader={
<ListSubheader component='div'> <ListSubheader component='div'>
Participants in Lobby <FormattedMessage
id='room.peersInLobby'
defaultMessage='Participants in Lobby'
/>
</ListSubheader> </ListSubheader>
} }
> >
@ -143,13 +152,19 @@ const LockDialog = ({
: :
<DialogContent> <DialogContent>
<DialogContentText gutterBottom> <DialogContentText gutterBottom>
There are currently no one in the lobby. <FormattedMessage
id='room.lobbyEmpty'
defaultMessage='There are currently no one in the lobby'
/>
</DialogContentText> </DialogContentText>
</DialogContent> </DialogContent>
} }
<DialogActions> <DialogActions>
<Button onClick={() => handleCloseLockDialog({ lockDialogOpen: false })} color='primary'> <Button onClick={() => handleCloseLockDialog({ lockDialogOpen: false })} color='primary'>
Close <FormattedMessage
id='label.close'
defaultMessage='Close'
/>
</Button> </Button>
</DialogActions> </DialogActions>
</Dialog> </Dialog>

View File

@ -3,6 +3,7 @@ import { connect } from 'react-redux';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import classnames from 'classnames'; import classnames from 'classnames';
import { withStyles } from '@material-ui/core/styles'; import { withStyles } from '@material-ui/core/styles';
import { FormattedMessage } from 'react-intl';
import * as toolareaActions from '../../actions/toolareaActions'; import * as toolareaActions from '../../actions/toolareaActions';
import BuddyImage from '../../images/buddy.svg'; import BuddyImage from '../../images/buddy.svg';
@ -95,8 +96,19 @@ class HiddenPeers extends React.PureComponent
className={classnames(classes.root, this.state.className)} className={classnames(classes.root, this.state.className)}
onClick={() => openUsersTab()} onClick={() => openUsersTab()}
> >
<p>+{hiddenPeersCount} <br /> participant <p>
{(hiddenPeersCount > 1) && 's'} +{hiddenPeersCount} <br />
<FormattedMessage
id='room.hiddenPeers'
defaultMessage={
`{hiddenPeersCount, plural,
one {participant}
other {participants}}`
}
values={{
hiddenPeersCount
}}
/>
</p> </p>
</div> </div>
); );

View File

@ -7,6 +7,7 @@ import useMediaQuery from '@material-ui/core/useMediaQuery';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import classnames from 'classnames'; import classnames from 'classnames';
import * as appPropTypes from '../appPropTypes'; import * as appPropTypes from '../appPropTypes';
import { useIntl, FormattedMessage } from 'react-intl';
import VideoView from '../VideoContainers/VideoView'; import VideoView from '../VideoContainers/VideoView';
import Volume from './Volume'; import Volume from './Volume';
import Fab from '@material-ui/core/Fab'; import Fab from '@material-ui/core/Fab';
@ -96,6 +97,8 @@ const Me = (props) =>
{ {
const [ hover, setHover ] = useState(false); const [ hover, setHover ] = useState(false);
const intl = useIntl();
let touchTimeout = null; let touchTimeout = null;
const { const {
@ -133,22 +136,34 @@ const Me = (props) =>
if (!me.canSendMic) if (!me.canSendMic)
{ {
micState = 'unsupported'; micState = 'unsupported';
micTip = 'Audio unsupported'; micTip = intl.formatMessage({
id : 'device.audioUnsupported',
defaultMessage : 'Audio unsupported'
});
} }
else if (!micProducer) else if (!micProducer)
{ {
micState = 'off'; micState = 'off';
micTip = 'Activate audio'; micTip = intl.formatMessage({
id : 'device.activateAudio',
defaultMessage : 'Activate audio'
});
} }
else if (!micProducer.locallyPaused && !micProducer.remotelyPaused) else if (!micProducer.locallyPaused && !micProducer.remotelyPaused)
{ {
micState = 'on'; micState = 'on';
micTip = 'Mute audio'; micTip = intl.formatMessage({
id : 'device.muteAudio',
defaultMessage : 'Mute audio'
});
} }
else else
{ {
micState = 'muted'; micState = 'muted';
micTip = 'Unmute audio'; micTip = intl.formatMessage({
id : 'device.unMuteAudio',
defaultMessage : 'Unmute audio'
});
} }
let webcamState; let webcamState;
@ -158,17 +173,26 @@ const Me = (props) =>
if (!me.canSendWebcam) if (!me.canSendWebcam)
{ {
webcamState = 'unsupported'; webcamState = 'unsupported';
webcamTip = 'Video unsupported'; webcamTip = intl.formatMessage({
id : 'device.videoUnsupported',
defaultMessage : 'Video unsupported'
});
} }
else if (webcamProducer) else if (webcamProducer)
{ {
webcamState = 'on'; webcamState = 'on';
webcamTip = 'Stop video'; webcamTip = intl.formatMessage({
id : 'device.stopVideo',
defaultMessage : 'Stop video'
});
} }
else else
{ {
webcamState = 'off'; webcamState = 'off';
webcamTip = 'Start video'; webcamTip = intl.formatMessage({
id : 'device.startVideo',
defaultMessage : 'Start video'
});
} }
let screenState; let screenState;
@ -178,17 +202,26 @@ const Me = (props) =>
if (!me.canShareScreen) if (!me.canShareScreen)
{ {
screenState = 'unsupported'; screenState = 'unsupported';
screenTip = 'Screen sharing not supported'; screenTip = intl.formatMessage({
id : 'device.screenSharingUnsupported',
defaultMessage : 'Screen sharing not supported'
});
} }
else if (screenProducer) else if (screenProducer)
{ {
screenState = 'on'; screenState = 'on';
screenTip = 'Stop screen sharing'; screenTip = intl.formatMessage({
id : 'device.stopScreenSharing',
defaultMessage : 'Stop screen sharing'
});
} }
else else
{ {
screenState = 'off'; screenState = 'off';
screenTip = 'Start screen sharing'; screenTip = intl.formatMessage({
id : 'device.startScreenSharing',
defaultMessage : 'Start screen sharing'
});
} }
const spacingStyle = const spacingStyle =
@ -253,11 +286,19 @@ const Me = (props) =>
}, 2000); }, 2000);
}} }}
> >
<p>ME</p> <p>
<Tooltip title={micTip} placement={smallScreen ? 'top' : 'right'}> <FormattedMessage
id='room.me'
defaultMessage='ME'
/>
</p>
<Tooltip title={micTip} placement={smallScreen ? 'top' : 'left'}>
<div> <div>
<Fab <Fab
aria-label='Mute mic' aria-label={intl.formatMessage({
id : 'device.muteAudio',
defaultMessage : 'Mute audio'
})}
className={classes.fab} className={classes.fab}
disabled={!me.canSendMic || me.audioInProgress} disabled={!me.canSendMic || me.audioInProgress}
color={micState === 'on' ? 'default' : 'secondary'} color={micState === 'on' ? 'default' : 'secondary'}
@ -280,10 +321,13 @@ const Me = (props) =>
</Fab> </Fab>
</div> </div>
</Tooltip> </Tooltip>
<Tooltip title={webcamTip} placement={smallScreen ? 'top' : 'right'}> <Tooltip title={webcamTip} placement={smallScreen ? 'top' : 'left'}>
<div> <div>
<Fab <Fab
aria-label='Mute video' aria-label={intl.formatMessage({
id : 'device.startVideo',
defaultMessage : 'Start video'
})}
className={classes.fab} className={classes.fab}
disabled={!me.canSendWebcam || me.webcamInProgress} disabled={!me.canSendWebcam || me.webcamInProgress}
color={webcamState === 'on' ? 'default' : 'secondary'} color={webcamState === 'on' ? 'default' : 'secondary'}
@ -303,10 +347,13 @@ const Me = (props) =>
</Fab> </Fab>
</div> </div>
</Tooltip> </Tooltip>
<Tooltip title={screenTip} placement={smallScreen ? 'top' : 'right'}> <Tooltip title={screenTip} placement={smallScreen ? 'top' : 'left'}>
<div> <div>
<Fab <Fab
aria-label='Share screen' aria-label={intl.formatMessage({
id : 'device.startScreenSharing',
defaultMessage : 'Start screen sharing'
})}
className={classes.fab} className={classes.fab}
disabled={!me.canShareScreen || me.screenShareInProgress} disabled={!me.canShareScreen || me.screenShareInProgress}
color={screenState === 'on' ? 'primary' : 'default'} color={screenState === 'on' ? 'primary' : 'default'}
@ -410,7 +457,12 @@ const Me = (props) =>
}, 2000); }, 2000);
}} }}
> >
<p>ME</p> <p>
<FormattedMessage
id='room.me'
defaultMessage='ME'
/>
</p>
</div> </div>
<VideoView <VideoView

View File

@ -8,7 +8,9 @@ import { withRoomContext } from '../../RoomContext';
import { withStyles } from '@material-ui/core/styles'; import { withStyles } from '@material-ui/core/styles';
import useMediaQuery from '@material-ui/core/useMediaQuery'; import useMediaQuery from '@material-ui/core/useMediaQuery';
import * as roomActions from '../../actions/roomActions'; import * as roomActions from '../../actions/roomActions';
import { useIntl, FormattedMessage } from 'react-intl';
import VideoView from '../VideoContainers/VideoView'; import VideoView from '../VideoContainers/VideoView';
import Tooltip from '@material-ui/core/Tooltip';
import Fab from '@material-ui/core/Fab'; import Fab from '@material-ui/core/Fab';
import MicIcon from '@material-ui/icons/Mic'; import MicIcon from '@material-ui/icons/Mic';
import MicOffIcon from '@material-ui/icons/MicOff'; import MicOffIcon from '@material-ui/icons/MicOff';
@ -103,6 +105,8 @@ const Peer = (props) =>
{ {
const [ hover, setHover ] = useState(false); const [ hover, setHover ] = useState(false);
const intl = useIntl();
let touchTimeout = null; let touchTimeout = null;
const { const {
@ -194,7 +198,12 @@ const Peer = (props) =>
<div className={classnames(classes.viewContainer)}> <div className={classnames(classes.viewContainer)}>
{ !videoVisible && { !videoVisible &&
<div className={classes.videoInfo}> <div className={classes.videoInfo}>
<p>this video is paused</p> <p>
<FormattedMessage
id='room.videoPaused'
defaultMessage='This video is paused'
/>
</p>
</div> </div>
} }
@ -220,56 +229,95 @@ const Peer = (props) =>
}, 2000); }, 2000);
}} }}
> >
<Fab <Tooltip
aria-label='Mute mic' title={intl.formatMessage({
className={classes.fab} id : 'device.muteAudio',
disabled={!micConsumer} defaultMessage : 'Mute audio'
color={micEnabled ? 'default' : 'secondary'} })}
size={smallButtons ? 'small' : 'large'} placement={smallScreen ? 'top' : 'left'}
onClick={() =>
{
micEnabled ?
roomClient.modifyPeerConsumer(peer.id, 'mic', true) :
roomClient.modifyPeerConsumer(peer.id, 'mic', false);
}}
> >
{ micEnabled ? <div>
<MicIcon /> <Fab
: aria-label={intl.formatMessage({
<MicOffIcon /> id : 'device.muteAudio',
} defaultMessage : 'Mute audio'
</Fab> })}
className={classes.fab}
disabled={!micConsumer}
color={micEnabled ? 'default' : 'secondary'}
size={smallButtons ? 'small' : 'large'}
onClick={() =>
{
micEnabled ?
roomClient.modifyPeerConsumer(peer.id, 'mic', true) :
roomClient.modifyPeerConsumer(peer.id, 'mic', false);
}}
>
{ micEnabled ?
<MicIcon />
:
<MicOffIcon />
}
</Fab>
</div>
</Tooltip>
{ !smallScreen && { !smallScreen &&
<Fab <Tooltip
aria-label='New window' title={intl.formatMessage({
className={classes.fab} id : 'label.newWindow',
disabled={ defaultMessage : 'New window'
!videoVisible || })}
(windowConsumer === webcamConsumer.id) placement={smallScreen ? 'top' : 'left'}
}
size={smallButtons ? 'small' : 'large'}
onClick={() =>
{
toggleConsumerWindow(webcamConsumer);
}}
> >
<NewWindowIcon /> <div>
</Fab> <Fab
aria-label={intl.formatMessage({
id : 'label.newWindow',
defaultMessage : 'New window'
})}
className={classes.fab}
disabled={
!videoVisible ||
(windowConsumer === webcamConsumer.id)
}
size={smallButtons ? 'small' : 'large'}
onClick={() =>
{
toggleConsumerWindow(webcamConsumer);
}}
>
<NewWindowIcon />
</Fab>
</div>
</Tooltip>
} }
<Fab <Tooltip
aria-label='Fullscreen' title={intl.formatMessage({
className={classes.fab} id : 'label.fullscreen',
disabled={!videoVisible} defaultMessage : 'Fullscreen'
size={smallButtons ? 'small' : 'large'} })}
onClick={() => placement={smallScreen ? 'top' : 'left'}
{
toggleConsumerFullscreen(webcamConsumer);
}}
> >
<FullScreenIcon /> <div>
</Fab> <Fab
aria-label={intl.formatMessage({
id : 'label.fullscreen',
defaultMessage : 'Fullscreen'
})}
className={classes.fab}
disabled={!videoVisible}
size={smallButtons ? 'small' : 'large'}
onClick={() =>
{
toggleConsumerFullscreen(webcamConsumer);
}}
>
<FullScreenIcon />
</Fab>
</div>
</Tooltip>
</div> </div>
<VideoView <VideoView
@ -314,7 +362,12 @@ const Peer = (props) =>
> >
{ !screenVisible && { !screenVisible &&
<div className={classes.videoInfo}> <div className={classes.videoInfo}>
<p>this video is paused</p> <p>
<FormattedMessage
id='room.videoPaused'
defaultMessage='This video is paused'
/>
</p>
</div> </div>
} }
@ -344,35 +397,61 @@ const Peer = (props) =>
}} }}
> >
{ !smallScreen && { !smallScreen &&
<Fab <Tooltip
aria-label='New window' title={intl.formatMessage({
className={classes.fab} id : 'label.newWindow',
disabled={ defaultMessage : 'New window'
!screenVisible || })}
(windowConsumer === screenConsumer.id) placement={smallScreen ? 'top' : 'left'}
}
size={smallButtons ? 'small' : 'large'}
onClick={() =>
{
toggleConsumerWindow(screenConsumer);
}}
> >
<NewWindowIcon /> <div>
</Fab> <Fab
aria-label={intl.formatMessage({
id : 'label.newWindow',
defaultMessage : 'New window'
})}
className={classes.fab}
disabled={
!screenVisible ||
(windowConsumer === screenConsumer.id)
}
size={smallButtons ? 'small' : 'large'}
onClick={() =>
{
toggleConsumerWindow(screenConsumer);
}}
>
<NewWindowIcon />
</Fab>
</div>
</Tooltip>
} }
<Fab <Tooltip
aria-label='Fullscreen' title={intl.formatMessage({
className={classes.fab} id : 'label.fullscreen',
disabled={!screenVisible} defaultMessage : 'Fullscreen'
size={smallButtons ? 'small' : 'large'} })}
onClick={() => placement={smallScreen ? 'top' : 'left'}
{
toggleConsumerFullscreen(screenConsumer);
}}
> >
<FullScreenIcon /> <div>
</Fab> <Fab
aria-label={intl.formatMessage({
id : 'label.fullscreen',
defaultMessage : 'Fullscreen'
})}
className={classes.fab}
disabled={!screenVisible}
size={smallButtons ? 'small' : 'large'}
onClick={() =>
{
toggleConsumerFullscreen(screenConsumer);
}}
>
<FullScreenIcon />
</Fab>
</div>
</Tooltip>
</div> </div>
<VideoView <VideoView
advancedMode={advancedMode} advancedMode={advancedMode}

View File

@ -5,6 +5,7 @@ import PropTypes from 'prop-types';
import classnames from 'classnames'; import classnames from 'classnames';
import * as appPropTypes from '../appPropTypes'; import * as appPropTypes from '../appPropTypes';
import { withStyles } from '@material-ui/core/styles'; import { withStyles } from '@material-ui/core/styles';
import { FormattedMessage } from 'react-intl';
import VideoView from '../VideoContainers/VideoView'; import VideoView from '../VideoContainers/VideoView';
import Volume from './Volume'; import Volume from './Volume';
@ -119,7 +120,12 @@ const SpeakerPeer = (props) =>
<div className={classnames(classes.viewContainer)} style={style}> <div className={classnames(classes.viewContainer)} style={style}>
{ !videoVisible && { !videoVisible &&
<div className={classes.videoInfo}> <div className={classes.videoInfo}>
<p>this video is paused</p> <p>
<FormattedMessage
id='room.videoPaused'
defaultMessage='This video is paused'
/>
</p>
</div> </div>
} }
@ -145,7 +151,12 @@ const SpeakerPeer = (props) =>
> >
{ !screenVisible && { !screenVisible &&
<div className={classes.videoInfo} style={style}> <div className={classes.videoInfo} style={style}>
<p>this video is paused</p> <p>
<FormattedMessage
id='room.videoPaused'
defaultMessage='This video is paused'
/>
</p>
</div> </div>
} }

View File

@ -9,6 +9,7 @@ import { withRoomContext } from '../../RoomContext';
import { withStyles } from '@material-ui/core/styles'; import { withStyles } from '@material-ui/core/styles';
import * as roomActions from '../../actions/roomActions'; import * as roomActions from '../../actions/roomActions';
import * as toolareaActions from '../../actions/toolareaActions'; import * as toolareaActions from '../../actions/toolareaActions';
import { useIntl, FormattedMessage } from 'react-intl';
import AppBar from '@material-ui/core/AppBar'; import AppBar from '@material-ui/core/AppBar';
import Toolbar from '@material-ui/core/Toolbar'; import Toolbar from '@material-ui/core/Toolbar';
import Typography from '@material-ui/core/Typography'; import Typography from '@material-ui/core/Typography';
@ -109,6 +110,8 @@ const PulsingBadge = withStyles((theme) =>
const TopBar = (props) => const TopBar = (props) =>
{ {
const intl = useIntl();
const { const {
roomClient, roomClient,
room, room,
@ -126,6 +129,39 @@ const TopBar = (props) =>
classes classes
} = props; } = props;
const lockTooltip = room.locked ?
intl.formatMessage({
id : 'tooltip.unLockRoom',
defaultMessage : 'Unlock room'
})
:
intl.formatMessage({
id : 'tooltip.lockRoom',
defaultMessage : 'Lock room'
});
const fullscreenTooltip = fullscreen ?
intl.formatMessage({
id : 'tooltip.leaveFullscreen',
defaultMessage : 'Leave fullscreen'
})
:
intl.formatMessage({
id : 'tooltip.enterFullscreen',
defaultMessage : 'Enter fullscreen'
});
const loginTooltip = loggedIn ?
intl.formatMessage({
id : 'tooltip.logout',
defaultMessage : 'Log out'
})
:
intl.formatMessage({
id : 'tooltip.login',
defaultMessage : 'Log in'
});
return ( return (
<AppBar <AppBar
position='fixed' position='fixed'
@ -138,7 +174,10 @@ const TopBar = (props) =>
> >
<IconButton <IconButton
color='inherit' color='inherit'
aria-label='Open drawer' aria-label={intl.formatMessage({
id : 'label.openDrawer',
defaultMessage : 'Open drawer'
})}
onClick={() => toggleToolArea()} onClick={() => toggleToolArea()}
className={classes.menuButton} className={classes.menuButton}
> >
@ -156,9 +195,12 @@ const TopBar = (props) =>
</Typography> </Typography>
<div className={classes.grow} /> <div className={classes.grow} />
<div className={classes.actionButtons}> <div className={classes.actionButtons}>
<Tooltip title={`${room.locked ? 'Unlock' : 'Lock'} room`}> <Tooltip title={lockTooltip}>
<IconButton <IconButton
aria-label='Lock room' aria-label={intl.formatMessage({
id : 'tooltip.lockRoom',
defaultMessage : 'Lock room'
})}
className={classes.actionButton} className={classes.actionButton}
color='inherit' color='inherit'
onClick={() => onClick={() =>
@ -181,9 +223,17 @@ const TopBar = (props) =>
</IconButton> </IconButton>
</Tooltip> </Tooltip>
{ lobbyPeers.length > 0 && { lobbyPeers.length > 0 &&
<Tooltip title='Show lobby'> <Tooltip
title={intl.formatMessage({
id : 'tooltip.lobby',
defaultMessage : 'Show lobby'
})}
>
<IconButton <IconButton
aria-label='Lobby' aria-label={intl.formatMessage({
id : 'tooltip.lobby',
defaultMessage : 'Show lobby'
})}
color='inherit' color='inherit'
onClick={() => setLockDialogOpen(!room.lockDialogOpen)} onClick={() => setLockDialogOpen(!room.lockDialogOpen)}
> >
@ -197,9 +247,12 @@ const TopBar = (props) =>
</Tooltip> </Tooltip>
} }
{ fullscreenEnabled && { fullscreenEnabled &&
<Tooltip title={`${fullscreen ? 'Leave' : 'Enter'} fullscreen`}> <Tooltip title={fullscreenTooltip}>
<IconButton <IconButton
aria-label='Fullscreen' aria-label={intl.formatMessage({
id : 'tooltip.enterFullscreen',
defaultMessage : 'Enter fullscreen'
})}
className={classes.actionButton} className={classes.actionButton}
color='inherit' color='inherit'
onClick={onFullscreen} onClick={onFullscreen}
@ -212,9 +265,17 @@ const TopBar = (props) =>
</IconButton> </IconButton>
</Tooltip> </Tooltip>
} }
<Tooltip title='Show settings'> <Tooltip
title={intl.formatMessage({
id : 'tooltip.settings',
defaultMessage : 'Show settings'
})}
>
<IconButton <IconButton
aria-label='Settings' aria-label={intl.formatMessage({
id : 'tooltip.settings',
defaultMessage : 'Show settings'
})}
className={classes.actionButton} className={classes.actionButton}
color='inherit' color='inherit'
onClick={() => setSettingsOpen(!room.settingsOpen)} onClick={() => setSettingsOpen(!room.settingsOpen)}
@ -223,9 +284,12 @@ const TopBar = (props) =>
</IconButton> </IconButton>
</Tooltip> </Tooltip>
{ loginEnabled && { loginEnabled &&
<Tooltip title={`Log ${loggedIn ? 'out' : 'in'}`}> <Tooltip title={loginTooltip}>
<IconButton <IconButton
aria-label='Account' aria-label={intl.formatMessage({
id : 'tooltip.login',
defaultMessage : 'Log in'
})}
className={classes.actionButton} className={classes.actionButton}
color='inherit' color='inherit'
onClick={() => onClick={() =>
@ -242,13 +306,19 @@ const TopBar = (props) =>
</Tooltip> </Tooltip>
} }
<Button <Button
aria-label='Leave meeting' aria-label={intl.formatMessage({
id : 'label.leave',
defaultMessage : 'Leave'
})}
className={classes.actionButton} className={classes.actionButton}
variant='contained' variant='contained'
color='secondary' color='secondary'
onClick={() => roomClient.close()} onClick={() => roomClient.close()}
> >
Leave <FormattedMessage
id='label.leave'
defaultMessage='Leave'
/>
</Button> </Button>
</div> </div>
</Toolbar> </Toolbar>

View File

@ -4,6 +4,7 @@ import { withStyles } from '@material-ui/core/styles';
import { withRoomContext } from '../RoomContext'; import { withRoomContext } from '../RoomContext';
import * as settingsActions from '../actions/settingsActions'; import * as settingsActions from '../actions/settingsActions';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { useIntl, FormattedMessage } from 'react-intl';
import Dialog from '@material-ui/core/Dialog'; import Dialog from '@material-ui/core/Dialog';
import DialogContentText from '@material-ui/core/DialogContentText'; import DialogContentText from '@material-ui/core/DialogContentText';
import IconButton from '@material-ui/core/IconButton'; import IconButton from '@material-ui/core/IconButton';
@ -87,6 +88,8 @@ const DialogTitle = withStyles(styles)((props) =>
{ {
const [ open, setOpen ] = useState(false); const [ open, setOpen ] = useState(false);
const intl = useIntl();
useEffect(() => useEffect(() =>
{ {
const openTimer = setTimeout(() => setOpen(true), 1000); const openTimer = setTimeout(() => setOpen(true), 1000);
@ -120,7 +123,10 @@ const DialogTitle = withStyles(styles)((props) =>
onClose={handleTooltipClose} onClose={handleTooltipClose}
onOpen={handleTooltipOpen} onOpen={handleTooltipOpen}
open={open} open={open}
title='Click to log in' title={intl.formatMessage({
id : 'tooltip.login',
defaultMessage : 'Click to log in'
})}
placement='left' placement='left'
> >
<IconButton <IconButton
@ -167,6 +173,8 @@ const JoinDialog = ({
classes classes
}) => }) =>
{ {
const intl = useIntl();
const handleKeyDown = (event) => const handleKeyDown = (event) =>
{ {
const { key } = event; const { key } = event;
@ -207,21 +215,38 @@ const JoinDialog = ({
</DialogTitle> </DialogTitle>
<DialogContent> <DialogContent>
<DialogContentText gutterBottom> <DialogContentText gutterBottom>
You are about to join a meeting. <FormattedMessage
id='room.aboutToJoin'
defaultMessage='You are about to join a meeting'
/>
</DialogContentText> </DialogContentText>
<DialogContentText variant='h6' gutterBottom align='center'> <DialogContentText variant='h6' gutterBottom align='center'>
Room ID: { room.name } <FormattedMessage
id='room.roomId'
defaultMessage='Room ID: {roomName}'
values={{
roomName : room.name
}}
/>
</DialogContentText> </DialogContentText>
<DialogContentText gutterBottom> <DialogContentText gutterBottom>
Set your name for participation, <FormattedMessage
and choose how you want to join: id='room.setYourName'
defaultMessage={
`Set your name for participation,
and choose how you want to join:`
}
/>
</DialogContentText> </DialogContentText>
<TextField <TextField
id='displayname' id='displayname'
label='Your name' label={intl.formatMessage({
id : 'label.yourName',
defaultMessage : 'Your name'
})}
value={displayName} value={displayName}
variant='outlined' variant='outlined'
margin='normal' margin='normal'
@ -255,7 +280,10 @@ const JoinDialog = ({
variant='contained' variant='contained'
color='secondary' color='secondary'
> >
Audio only <FormattedMessage
id='room.audioOnly'
defaultMessage='Audio only'
/>
</Button> </Button>
<Button <Button
onClick={() => onClick={() =>
@ -265,7 +293,10 @@ const JoinDialog = ({
variant='contained' variant='contained'
color='secondary' color='secondary'
> >
Audio and Video <FormattedMessage
id='room.audioVideo'
defaultMessage='Audio and Video'
/>
</Button> </Button>
</DialogActions> </DialogActions>
: :
@ -276,23 +307,37 @@ const JoinDialog = ({
variant='h6' variant='h6'
align='center' align='center'
> >
Ok, you are ready <FormattedMessage
id='room.youAreReady'
defaultMessage='Ok, you are ready'
/>
</DialogContentText> </DialogContentText>
{ room.signInRequired ? { room.signInRequired ?
<DialogContentText gutterBottom> <DialogContentText gutterBottom>
The room is empty! <FormattedMessage
You can Log In to start the meeting or wait until the host joins. id='room.emptyRequireLogin'
defaultMessage={
`The room is empty! You can Log In to start
the meeting or wait until the host joins`
}
/>
</DialogContentText> </DialogContentText>
: :
<DialogContentText gutterBottom> <DialogContentText gutterBottom>
The room is locked - hang on until somebody lets you in ... <FormattedMessage
id='room.locketWait'
defaultMessage='The room is locked - hang on until somebody lets you in ...'
/>
</DialogContentText> </DialogContentText>
} }
</DialogContent> </DialogContent>
} }
<CookieConsent> <CookieConsent>
This website uses cookies to enhance the user experience. <FormattedMessage
id='room.cookieConsent'
defaultMessage='This website uses cookies to enhance the user experience'
/>
</CookieConsent> </CookieConsent>
</Dialog> </Dialog>
</div> </div>

View File

@ -1,8 +1,9 @@
import React from 'react'; import React, { useState } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { withStyles } from '@material-ui/core/styles'; import { withStyles } from '@material-ui/core/styles';
import { withRoomContext } from '../../../RoomContext'; import { withRoomContext } from '../../../RoomContext';
import { useIntl } from 'react-intl';
import Paper from '@material-ui/core/Paper'; import Paper from '@material-ui/core/Paper';
import InputBase from '@material-ui/core/InputBase'; import InputBase from '@material-ui/core/InputBase';
import IconButton from '@material-ui/core/IconButton'; import IconButton from '@material-ui/core/IconButton';
@ -28,19 +29,13 @@ const styles = (theme) =>
} }
}); });
class ChatInput extends React.PureComponent const ChatInput = (props) =>
{ {
constructor(props) const [ message, setMessage ] = useState('');
{
super(props);
this.state = const intl = useIntl();
{
message : ''
};
}
createNewMessage = (text, sender, name, picture) => const createNewMessage = (text, sender, name, picture) =>
({ ({
type : 'message', type : 'message',
text, text,
@ -50,67 +45,67 @@ class ChatInput extends React.PureComponent
picture picture
}); });
handleChange = (e) => const handleChange = (e) =>
{ {
this.setState({ message: e.target.value }); setMessage(e.target.value);
} };
render() const {
{ roomClient,
const { displayName,
roomClient, picture,
displayName, classes
picture, } = props;
classes
} = this.props;
return ( return (
<Paper className={classes.root}> <Paper className={classes.root}>
<InputBase <InputBase
className={classes.input} className={classes.input}
placeholder='Enter chat message...' placeholder={intl.formatMessage({
value={this.state.message || ''} id : 'label.chatInput',
onChange={this.handleChange} defaultMessage : 'Enter chat message...'
onKeyPress={(ev) => })}
value={message || ''}
onChange={handleChange}
onKeyPress={(ev) =>
{
if (ev.key === 'Enter')
{ {
if (ev.key === 'Enter') ev.preventDefault();
if (message && message !== '')
{ {
ev.preventDefault(); const sendMessage = createNewMessage(message, 'response', displayName, picture);
if (this.state.message && this.state.message !== '') roomClient.sendChatMessage(sendMessage);
{
const message = this.createNewMessage(this.state.message, 'response', displayName, picture);
roomClient.sendChatMessage(message); setMessage('');
this.setState({ message: '' });
}
} }
}} }
autoFocus }}
/> autoFocus
<IconButton />
color='primary' <IconButton
className={classes.iconButton} color='primary'
aria-label='Send' className={classes.iconButton}
onClick={() => aria-label='Send'
onClick={() =>
{
if (message && message !== '')
{ {
if (this.state.message && this.state.message !== '') const sendMessage = this.createNewMessage(message, 'response', displayName, picture);
{
const message = this.createNewMessage(this.state.message, 'response', displayName, picture);
roomClient.sendChatMessage(message); roomClient.sendChatMessage(sendMessage);
this.setState({ message: '' }); setMessage('');
} }
}} }}
> >
<SendIcon /> <SendIcon />
</IconButton> </IconButton>
</Paper> </Paper>
); );
} };
}
ChatInput.propTypes = ChatInput.propTypes =
{ {

View File

@ -92,7 +92,7 @@ Message.propTypes =
self : PropTypes.bool, self : PropTypes.bool,
picture : PropTypes.string, picture : PropTypes.string,
text : PropTypes.string, text : PropTypes.string,
time : PropTypes.string, time : PropTypes.object,
name : PropTypes.string, name : PropTypes.string,
classes : PropTypes.object.isRequired classes : PropTypes.object.isRequired
}; };

View File

@ -2,6 +2,7 @@ import React from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { withStyles } from '@material-ui/core/styles'; import { withStyles } from '@material-ui/core/styles';
import { FormattedTime } from 'react-intl';
import Message from './Message'; import Message from './Message';
import EmptyAvatar from '../../../images/avatar-empty.jpeg'; import EmptyAvatar from '../../../images/avatar-empty.jpeg';
@ -49,7 +50,7 @@ class MessageList extends React.Component
getTimeString(time) getTimeString(time)
{ {
return `${(time.getHours() < 10 ? '0' : '')}${time.getHours()}:${(time.getMinutes() < 10 ? '0' : '')}${time.getMinutes()}`; return (<FormattedTime value={new Date(time)} />);
} }
render() render()
@ -65,8 +66,6 @@ class MessageList extends React.Component
{ {
chat.map((message, index) => chat.map((message, index) =>
{ {
const messageTime = new Date(message.time);
const picture = (message.sender === 'response' ? const picture = (message.sender === 'response' ?
message.picture : myPicture) || EmptyAvatar; message.picture : myPicture) || EmptyAvatar;
@ -76,7 +75,7 @@ class MessageList extends React.Component
self={message.sender === 'client'} self={message.sender === 'client'}
picture={picture} picture={picture}
text={message.text} text={message.text}
time={this.getTimeString(messageTime)} time={this.getTimeString(message.time)}
name={message.name} name={message.name}
/> />
); );
@ -89,9 +88,9 @@ class MessageList extends React.Component
MessageList.propTypes = MessageList.propTypes =
{ {
chat : PropTypes.array, chat : PropTypes.array,
myPicture : PropTypes.string, myPicture : PropTypes.string,
classes : PropTypes.object.isRequired classes : PropTypes.object.isRequired
}; };
const mapStateToProps = (state) => const mapStateToProps = (state) =>

View File

@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { withRoomContext } from '../../../RoomContext'; import { withRoomContext } from '../../../RoomContext';
import { withStyles } from '@material-ui/core/styles'; import { withStyles } from '@material-ui/core/styles';
import { FormattedMessage } from 'react-intl';
import magnet from 'magnet-uri'; import magnet from 'magnet-uri';
import Typography from '@material-ui/core/Typography'; import Typography from '@material-ui/core/Typography';
import Button from '@material-ui/core/Button'; import Button from '@material-ui/core/Button';
@ -70,7 +71,10 @@ class File extends React.PureComponent
{ file.files && { file.files &&
<Fragment> <Fragment>
<Typography className={classes.text}> <Typography className={classes.text}>
File finished downloading <FormattedMessage
id='filesharing.finished'
defaultMessage='File finished downloading'
/>
</Typography> </Typography>
{ file.files.map((sharedFile, i) => ( { file.files.map((sharedFile, i) => (
@ -87,14 +91,23 @@ class File extends React.PureComponent
roomClient.saveFile(sharedFile); roomClient.saveFile(sharedFile);
}} }}
> >
Save <FormattedMessage
id='filesharing.save'
defaultMessage='Save'
/>
</Button> </Button>
</div> </div>
))} ))}
</Fragment> </Fragment>
} }
<Typography className={classes.text}> <Typography className={classes.text}>
{ `${displayName} shared a file` } <FormattedMessage
id='filesharing.sharedFile'
defaultMessage='{displayName} shared a file'
values={{
displayName
}}
/>
</Typography> </Typography>
{ (!file.active && !file.files) && { (!file.active && !file.files) &&
@ -112,11 +125,17 @@ class File extends React.PureComponent
roomClient.handleDownload(magnetUri); roomClient.handleDownload(magnetUri);
}} }}
> >
Download <FormattedMessage
id='filesharing.download'
defaultMessage='Download'
/>
</Button> </Button>
: :
<Typography className={classes.text}> <Typography className={classes.text}>
Your browser does not support downloading files using WebTorrent. <FormattedMessage
id='label.fileSharingUnsupported'
defaultMessage='File sharing not supported'
/>
</Typography> </Typography>
} }
</div> </div>
@ -124,8 +143,14 @@ class File extends React.PureComponent
{ file.timeout && { file.timeout &&
<Typography className={classes.text}> <Typography className={classes.text}>
If this process takes a long time, there might not be anyone seeding <FormattedMessage
this torrent. Try asking someone to reupload the file that you want. id='filesharing.missingSeeds'
defaultMessage={
`If this process takes a long time, there might not
be anyone seeding this torrent. Try asking someone to
reupload the file that you want.`
}
/>
</Typography> </Typography>
} }

View File

@ -3,6 +3,7 @@ import { connect } from 'react-redux';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import * as appPropTypes from '../../appPropTypes'; import * as appPropTypes from '../../appPropTypes';
import { withStyles } from '@material-ui/core/styles'; import { withStyles } from '@material-ui/core/styles';
import { injectIntl } from 'react-intl';
import File from './File'; import File from './File';
import EmptyAvatar from '../../../images/avatar-empty.jpeg'; import EmptyAvatar from '../../../images/avatar-empty.jpeg';
@ -46,6 +47,7 @@ class FileList extends React.PureComponent
files, files,
me, me,
peers, peers,
intl,
classes classes
} = this.props; } = this.props;
@ -59,7 +61,10 @@ class FileList extends React.PureComponent
if (me.id === file.peerId) if (me.id === file.peerId)
{ {
displayName = 'You'; displayName = intl.formatMessage({
id : 'room.me',
defaultMessage : 'Me'
});
filePicture = me.picture; filePicture = me.picture;
} }
else if (peers[file.peerId]) else if (peers[file.peerId])
@ -69,7 +74,10 @@ class FileList extends React.PureComponent
} }
else else
{ {
displayName = 'Unknown'; displayName = intl.formatMessage({
id : 'label.unknown',
defaultMessage : 'Unknown'
});
} }
return ( return (
@ -91,6 +99,7 @@ FileList.propTypes =
files : PropTypes.object.isRequired, files : PropTypes.object.isRequired,
me : appPropTypes.Me.isRequired, me : appPropTypes.Me.isRequired,
peers : PropTypes.object.isRequired, peers : PropTypes.object.isRequired,
intl : PropTypes.object.isRequired,
classes : PropTypes.object.isRequired classes : PropTypes.object.isRequired
}; };
@ -117,4 +126,4 @@ export default connect(
); );
} }
} }
)(withStyles(styles)(FileList)); )(withStyles(styles)(injectIntl(FileList)));

View File

@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { withStyles } from '@material-ui/core/styles'; import { withStyles } from '@material-ui/core/styles';
import { withRoomContext } from '../../../RoomContext'; import { withRoomContext } from '../../../RoomContext';
import { useIntl } from 'react-intl';
import FileList from './FileList'; import FileList from './FileList';
import Paper from '@material-ui/core/Paper'; import Paper from '@material-ui/core/Paper';
import Button from '@material-ui/core/Button'; import Button from '@material-ui/core/Button';
@ -26,58 +27,57 @@ const styles = (theme) =>
} }
}); });
class FileSharing extends React.PureComponent const FileSharing = (props) =>
{ {
constructor(props) const intl = useIntl();
{
super(props);
this._fileInput = React.createRef(); const handleFileChange = async (event) =>
}
handleFileChange = async (event) =>
{ {
if (event.target.files.length > 0) if (event.target.files.length > 0)
{ {
this.props.roomClient.shareFiles(event.target.files); props.roomClient.shareFiles(event.target.files);
} }
}; };
render() const {
{ canShareFiles,
const { classes
canShareFiles, } = props;
classes
} = this.props;
const buttonDescription = canShareFiles ? const buttonDescription = canShareFiles ?
'Share file' : 'File sharing not supported'; intl.formatMessage({
id : 'label.shareFile',
defaultMessage : 'Share file'
})
:
intl.formatMessage({
id : 'label.fileSharingUnsupported',
defaultMessage : 'File sharing not supported'
});
return ( return (
<Paper className={classes.root}> <Paper className={classes.root}>
<input <input
ref={this._fileInput} className={classes.input}
className={classes.input} type='file'
type='file' onChange={handleFileChange}
onChange={this.handleFileChange} id='share-files-button'
id='share-files-button' />
/> <label htmlFor='share-files-button'>
<label htmlFor='share-files-button'> <Button
<Button variant='contained'
variant='contained' component='span'
component='span' className={classes.button}
className={classes.button} disabled={!canShareFiles}
disabled={!canShareFiles} >
> {buttonDescription}
{buttonDescription} </Button>
</Button> </label>
</label>
<FileList /> <FileList />
</Paper> </Paper>
); );
} };
}
FileSharing.propTypes = { FileSharing.propTypes = {
roomClient : PropTypes.any.isRequired, roomClient : PropTypes.any.isRequired,

View File

@ -3,6 +3,7 @@ import { connect } from 'react-redux';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { withStyles } from '@material-ui/core/styles'; import { withStyles } from '@material-ui/core/styles';
import * as toolareaActions from '../../actions/toolareaActions'; import * as toolareaActions from '../../actions/toolareaActions';
import { useIntl } from 'react-intl';
import AppBar from '@material-ui/core/AppBar'; import AppBar from '@material-ui/core/AppBar';
import Tabs from '@material-ui/core/Tabs'; import Tabs from '@material-ui/core/Tabs';
import Tab from '@material-ui/core/Tab'; import Tab from '@material-ui/core/Tab';
@ -44,6 +45,8 @@ const styles = (theme) =>
const MeetingDrawer = (props) => const MeetingDrawer = (props) =>
{ {
const intl = useIntl();
const { const {
currentToolTab, currentToolTab,
unreadMessages, unreadMessages,
@ -72,18 +75,29 @@ const MeetingDrawer = (props) =>
<Tab <Tab
label={ label={
<Badge color='secondary' badgeContent={unreadMessages}> <Badge color='secondary' badgeContent={unreadMessages}>
Chat {intl.formatMessage({
id : 'label.chat',
defaultMessage : 'Chat'
})}
</Badge> </Badge>
} }
/> />
<Tab <Tab
label={ label={
<Badge color='secondary' badgeContent={unreadFiles}> <Badge color='secondary' badgeContent={unreadFiles}>
File sharing {intl.formatMessage({
id : 'label.filesharing',
defaultMessage : 'File sharing'
})}
</Badge> </Badge>
} }
/> />
<Tab label='Participants' /> <Tab
label={intl.formatMessage({
id : 'label.participants',
defaultMessage : 'Participants'
})}
/>
</Tabs> </Tabs>
<IconButton onClick={closeDrawer}> <IconButton onClick={closeDrawer}>
{theme.direction === 'ltr' ? <ChevronLeftIcon /> : <ChevronRightIcon />} {theme.direction === 'ltr' ? <ChevronLeftIcon /> : <ChevronRightIcon />}

View File

@ -8,6 +8,7 @@ import classNames from 'classnames';
import { withStyles } from '@material-ui/core/styles'; import { withStyles } from '@material-ui/core/styles';
import { withRoomContext } from '../../../RoomContext'; import { withRoomContext } from '../../../RoomContext';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
import ListPeer from './ListPeer'; import ListPeer from './ListPeer';
import ListMe from './ListMe'; import ListMe from './ListMe';
import Volume from '../../Containers/Volume'; import Volume from '../../Containers/Volume';
@ -84,11 +85,21 @@ class ParticipantList extends React.PureComponent
return ( return (
<div className={classes.root} ref={(node) => { this.node = node; }}> <div className={classes.root} ref={(node) => { this.node = node; }}>
<ul className={classes.list}> <ul className={classes.list}>
<li className={classes.listheader}>Me:</li> <li className={classes.listheader}>
<FormattedMessage
id='room.me'
defaultMessage='Me'
/>
</li>
<ListMe /> <ListMe />
</ul> </ul>
<ul className={classes.list}> <ul className={classes.list}>
<li className={classes.listheader}>Participants in Spotlight:</li> <li className={classes.listheader}>
<FormattedMessage
id='room.spotlights'
defaultMessage='Participants in Spotlight'
/>
</li>
{ spotlightPeers.map((peer) => ( { spotlightPeers.map((peer) => (
<li <li
key={peer.id} key={peer.id}
@ -104,7 +115,12 @@ class ParticipantList extends React.PureComponent
))} ))}
</ul> </ul>
<ul className={classes.list}> <ul className={classes.list}>
<li className={classes.listheader}>Passive Participants:</li> <li className={classes.listheader}>
<FormattedMessage
id='room.passive'
defaultMessage='Passive Participants'
/>
</li>
{ passivePeers.map((peerId) => ( { passivePeers.map((peerId) => (
<li <li
key={peerId} key={peerId}

View File

@ -7,6 +7,7 @@ import * as roomActions from '../actions/roomActions';
import * as toolareaActions from '../actions/toolareaActions'; import * as toolareaActions from '../actions/toolareaActions';
import { idle } from '../utils'; import { idle } from '../utils';
import FullScreen from './FullScreen'; import FullScreen from './FullScreen';
import { FormattedMessage } from 'react-intl';
import CookieConsent from 'react-cookie-consent'; import CookieConsent from 'react-cookie-consent';
import CssBaseline from '@material-ui/core/CssBaseline'; import CssBaseline from '@material-ui/core/CssBaseline';
import SwipeableDrawer from '@material-ui/core/SwipeableDrawer'; import SwipeableDrawer from '@material-ui/core/SwipeableDrawer';
@ -152,7 +153,10 @@ class Room extends React.PureComponent
return ( return (
<div className={classes.root}> <div className={classes.root}>
<CookieConsent> <CookieConsent>
This website uses cookies to enhance the user experience. <FormattedMessage
id='room.cookieConsent'
defaultMessage='This website uses cookies to enhance the user experience'
/>
</CookieConsent> </CookieConsent>
<FullScreenView advancedMode={advancedMode} /> <FullScreenView advancedMode={advancedMode} />
@ -237,6 +241,7 @@ export default connect(
{ {
return ( return (
prev.room === next.room && prev.room === next.room &&
prev.settings.advancedMode === next.settings.advancedMode &&
prev.toolarea.toolAreaOpen === next.toolarea.toolAreaOpen prev.toolarea.toolAreaOpen === next.toolarea.toolAreaOpen
); );
} }

View File

@ -6,6 +6,7 @@ import { withRoomContext } from '../../RoomContext';
import * as roomActions from '../../actions/roomActions'; import * as roomActions from '../../actions/roomActions';
import * as settingsActions from '../../actions/settingsActions'; import * as settingsActions from '../../actions/settingsActions';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { useIntl, FormattedMessage } from 'react-intl';
import Dialog from '@material-ui/core/Dialog'; import Dialog from '@material-ui/core/Dialog';
import DialogTitle from '@material-ui/core/DialogTitle'; import DialogTitle from '@material-ui/core/DialogTitle';
import DialogActions from '@material-ui/core/DialogActions'; import DialogActions from '@material-ui/core/DialogActions';
@ -52,35 +53,6 @@ const styles = (theme) =>
} }
}); });
const modes = [ {
value : 'democratic',
label : 'Democratic view'
}, {
value : 'filmstrip',
label : 'Filmstrip view'
} ];
const resolutions = [ {
value : 'low',
label : 'Low'
},
{
value : 'medium',
label : 'Medium'
},
{
value : 'high',
label : 'High (HD)'
},
{
value : 'veryhigh',
label : 'Very high (FHD)'
},
{
value : 'ultra',
label : 'Ultra (UHD)'
} ];
const Settings = ({ const Settings = ({
roomClient, roomClient,
room, room,
@ -92,6 +64,58 @@ const Settings = ({
classes classes
}) => }) =>
{ {
const intl = useIntl();
const modes = [ {
value : 'democratic',
label : intl.formatMessage({
id : 'label.democratic',
defaultMessage : 'Democratic view'
})
}, {
value : 'filmstrip',
label : intl.formatMessage({
id : 'label.filmstrip',
defaultMessage : 'Filmstrip view'
})
} ];
const resolutions = [ {
value : 'low',
label : intl.formatMessage({
id : 'label.low',
defaultMessage : 'Low'
})
},
{
value : 'medium',
label : intl.formatMessage({
id : 'label.medium',
defaultMessage : 'Medium'
})
},
{
value : 'high',
label : intl.formatMessage({
id : 'label.high',
defaultMessage : 'High (HD)'
})
},
{
value : 'veryhigh',
label : intl.formatMessage({
id : 'label.veryHigh',
defaultMessage : 'Very high (FHD)'
})
},
{
value : 'ultra',
label : intl.formatMessage({
id : 'label.ultra',
defaultMessage : 'Ultra (UHD)'
})
} ];
let webcams; let webcams;
if (me.webcamDevices) if (me.webcamDevices)
@ -115,7 +139,12 @@ const Settings = ({
paper : classes.dialogPaper paper : classes.dialogPaper
}} }}
> >
<DialogTitle id='form-dialog-title'>Settings</DialogTitle> <DialogTitle id='form-dialog-title'>
<FormattedMessage
id='settings.settings'
defaultMessage='Settings'
/>
</DialogTitle>
<form className={classes.setting} autoComplete='off'> <form className={classes.setting} autoComplete='off'>
<FormControl className={classes.formControl}> <FormControl className={classes.formControl}>
<Select <Select
@ -126,7 +155,10 @@ const Settings = ({
roomClient.changeWebcam(event.target.value); roomClient.changeWebcam(event.target.value);
}} }}
displayEmpty displayEmpty
name='Camera' name={intl.formatMessage({
id : 'settings.camera',
defaultMessage : 'Camera'
})}
autoWidth autoWidth
className={classes.selectEmpty} className={classes.selectEmpty}
disabled={webcams.length === 0 || me.webcamInProgress} disabled={webcams.length === 0 || me.webcamInProgress}
@ -140,9 +172,15 @@ const Settings = ({
</Select> </Select>
<FormHelperText> <FormHelperText>
{ webcams.length > 0 ? { webcams.length > 0 ?
'Select video device' intl.formatMessage({
id : 'settings.selectCamera',
defaultMessage : 'Select video device'
})
: :
'Unable to select video device' intl.formatMessage({
id : 'settings.cantSelectCamera',
defaultMessage : 'Unable to select video device'
})
} }
</FormHelperText> </FormHelperText>
</FormControl> </FormControl>
@ -157,7 +195,10 @@ const Settings = ({
roomClient.changeAudioDevice(event.target.value); roomClient.changeAudioDevice(event.target.value);
}} }}
displayEmpty displayEmpty
name='Audio device' name={intl.formatMessage({
id : 'settings.audio',
defaultMessage : 'Audio device'
})}
autoWidth autoWidth
className={classes.selectEmpty} className={classes.selectEmpty}
disabled={audioDevices.length === 0 || me.audioInProgress} disabled={audioDevices.length === 0 || me.audioInProgress}
@ -171,9 +212,15 @@ const Settings = ({
</Select> </Select>
<FormHelperText> <FormHelperText>
{ audioDevices.length > 0 ? { audioDevices.length > 0 ?
'Select audio device' intl.formatMessage({
id : 'settings.selectAudio',
defaultMessage : 'Select audio device'
})
: :
'Unable to select audio device' intl.formatMessage({
id : 'settings.cantSelectAudio',
defaultMessage : 'Unable to select audio device'
})
} }
</FormHelperText> </FormHelperText>
</FormControl> </FormControl>
@ -201,7 +248,10 @@ const Settings = ({
})} })}
</Select> </Select>
<FormHelperText> <FormHelperText>
Select your video resolution <FormattedMessage
id='settings.resolution'
defaultMessage='Select your video resolution'
/>
</FormHelperText> </FormHelperText>
</FormControl> </FormControl>
</form> </form>
@ -214,7 +264,10 @@ const Settings = ({
if (event.target.value) if (event.target.value)
handleChangeMode(event.target.value); handleChangeMode(event.target.value);
}} }}
name='Room layout' name={intl.formatMessage({
id : 'settings.layout',
defaultMessage : 'Room layout'
})}
autoWidth autoWidth
className={classes.selectEmpty} className={classes.selectEmpty}
> >
@ -228,18 +281,27 @@ const Settings = ({
})} })}
</Select> </Select>
<FormHelperText> <FormHelperText>
Select room layout <FormattedMessage
id='settings.selectRoomLayout'
defaultMessage='Select room layout'
/>
</FormHelperText> </FormHelperText>
</FormControl> </FormControl>
</form> </form>
<FormControlLabel <FormControlLabel
className={classes.setting} className={classes.setting}
control={<Checkbox checked={settings.advancedMode} onChange={onToggleAdvancedMode} value='advancedMode' />} control={<Checkbox checked={settings.advancedMode} onChange={onToggleAdvancedMode} value='advancedMode' />}
label='Advanced mode' label={intl.formatMessage({
id : 'settings.advancedMode',
defaultMessage : 'Advanced mode'
})}
/> />
<DialogActions> <DialogActions>
<Button onClick={() => handleCloseSettings({ settingsOpen: false })} color='primary'> <Button onClick={() => handleCloseSettings({ settingsOpen: false })} color='primary'>
Close <FormattedMessage
id='label.close'
defaultMessage='Close'
/>
</Button> </Button>
</DialogActions> </DialogActions>
</Dialog> </Dialog>

View File

@ -2,6 +2,7 @@ import domready from 'domready';
import React from 'react'; import React from 'react';
import { render } from 'react-dom'; import { render } from 'react-dom';
import { Provider } from 'react-redux'; import { Provider } from 'react-redux';
import { createIntl, createIntlCache, RawIntlProvider } from 'react-intl';
import randomString from 'random-string'; import randomString from 'random-string';
import Logger from './Logger'; import Logger from './Logger';
import debug from 'debug'; import debug from 'debug';
@ -18,8 +19,26 @@ import { persistor, store } from './store';
import { SnackbarProvider } from 'notistack'; import { SnackbarProvider } from 'notistack';
import * as serviceWorker from './serviceWorker'; import * as serviceWorker from './serviceWorker';
import messagesEnglish from './translations/en';
import messagesNorwegian from './translations/nb';
import './index.css'; import './index.css';
const cache = createIntlCache();
const messages =
{
'en' : messagesEnglish,
'nb' : messagesNorwegian
};
const locale = navigator.language.split(/[-_]/)[0]; // language without region code
const intl = createIntl({
locale,
messages : messages[locale]
}, cache);
if (process.env.REACT_APP_DEBUG === '*' || process.env.NODE_ENV !== 'production') if (process.env.REACT_APP_DEBUG === '*' || process.env.NODE_ENV !== 'production')
{ {
debug.enable('* -engine* -socket* -RIE* *WARN* *ERROR*'); debug.enable('* -engine* -socket* -RIE* *WARN* *ERROR*');
@ -29,7 +48,7 @@ const logger = new Logger();
let roomClient; let roomClient;
RoomClient.init({ store }); RoomClient.init({ store, intl });
const theme = createMuiTheme(window.config.theme); const theme = createMuiTheme(window.config.theme);
@ -92,13 +111,15 @@ function run()
render( render(
<Provider store={store}> <Provider store={store}>
<MuiThemeProvider theme={theme}> <MuiThemeProvider theme={theme}>
<PersistGate loading={<LoadingView />} persistor={persistor}> <RawIntlProvider value={intl}>
<RoomContext.Provider value={roomClient}> <PersistGate loading={<LoadingView />} persistor={persistor}>
<SnackbarProvider> <RoomContext.Provider value={roomClient}>
<App /> <SnackbarProvider>
</SnackbarProvider> <App />
</RoomContext.Provider> </SnackbarProvider>
</PersistGate> </RoomContext.Provider>
</PersistGate>
</RawIntlProvider>
</MuiThemeProvider> </MuiThemeProvider>
</Provider>, </Provider>,
document.getElementById('multiparty-meeting') document.getElementById('multiparty-meeting')

View File

@ -0,0 +1,133 @@
{
"socket.disconnected": "You are disconnected",
"socket.reconnecting": "You are disconnected, attempting to reconnect",
"socket.reconnected": "You are reconnected",
"socket.requestError": "Error on server request",
"room.cookieConsent": "This website uses cookies to enhance the user experience",
"room.joined": "You have joined the room",
"room.cantJoin": "Unable to join the room",
"room.youLocked": "You locked the room",
"room.cantLock": "Unable to lock the room",
"room.youUnLocked": "You unlocked the room",
"room.cantUnLock": "Unable to unlock the room",
"room.locked": "Room is now locked",
"room.unlocked": "Room is now unlocked",
"room.newLobbyPeer": "New participant entered the lobby",
"room.lobbyPeerLeft": "Participant in lobby left",
"room.lobbyPeerChangedDisplayName": "Participant in lobby changed name to {displayName}",
"room.lobbyPeerChangedPicture": "Participant in lobby changed picture",
"room.setAccessCode": "Access code for room updated",
"room.accessCodeOn": "Access code for room is now activated",
"room.accessCodeOff": "Access code for room is now deactivated",
"room.peerChangedDisplayName": "{oldDisplayName} is now {displayName}",
"room.newPeer": "{displayName} joined the room",
"room.newFile": "New file available",
"room.toggleAdvancedMode": "Toggled advanced mode",
"room.setDemocraticView": "Changed layout to democratic view",
"room.setFilmStripView": "Changed layout to filmstrip view",
"room.loggedIn": "You are logged in",
"room.loggedOut": "You are logged out",
"room.changedDisplayName": "Your display name changed to {displayName}",
"room.changeDisplayNameError": "An error occured while changing your display name",
"room.chatError": "Unable to send chat message",
"room.aboutToJoin": "You are about to join a meeting",
"room.roomId": "Room ID: {roomName}",
"room.setYourName": "Set your name for participation, and choose how you want to join:",
"room.audioOnly": "Audio only",
"room.audioVideo": "Audio and Video",
"room.youAreReady": "Ok, you are ready",
"room.emptyRequireLogin": "The room is empty! You can Log In to start the meeting or wait until the host joins",
"room.locketWait": "The room is locked - hang on until somebody lets you in ...",
"room.lobbyAdministration": "Lobby administration",
"room.peersInLobby": "Participants in Lobby",
"room.lobbyEmpty": "There are currently no one in the lobby",
"room.hiddenPeers": "{hiddenPeersCount, plural, one {participant} other {participants}}",
"room.me": "Me",
"room.spotlights": "Participants in Spotlight",
"room.passive": "Passive Participants",
"room.videoPaused": "This video is paused",
"tooltip.login": "Log in",
"tooltip.logout": "Log out",
"tooltip.admitFromLobby": "Admit from lobby",
"tooltip.lockRoom": "Lock room",
"tooltip.unLockRoom": "Unlock room",
"tooltip.enterFullscreen": "Enter fullscreen",
"tooltip.leaveFullscreen": "Leave fullscreen",
"tooltip.lobby": "Show lobby",
"tooltip.settings": "Show settings",
"label.yourName": "Your name",
"label.newWindow": "New window",
"label.fullscreen": "Fullscreen",
"label.openDrawer": "Open drawer",
"label.leave": "Leave",
"label.chatInput": "Enter chat message...",
"label.chat": "Chat",
"label.filesharing": "File sharing",
"label.participants": "Participants",
"label.shareFile": "Share file",
"label.fileSharingUnsupported": "File sharing not supported",
"label.unknown": "Unknown",
"label.democratic": "Democratic view",
"label.filmstrip": "Filmstrip view",
"label.low": "Low",
"label.medium": "Medium",
"label.high": "High (HD)",
"label.veryHigh": "Very high (FHD)",
"label.ultra": "Ultra (UHD)",
"label.close": "Close",
"settings.settings": "Settings",
"settings.camera": "Camera",
"settings.selectCamera": "Select video device",
"settings.cantSelectCamera": "Unable to select video device",
"settings.audio": "Audio device",
"settings.selectAudio": "Select audio device",
"settings.cantSelectAudio": "Unable to select audio device",
"settings.resolution": "Select your video resolution",
"settings.layout": "Room layout",
"settings.selectRoomLayout": "Select room layout",
"settings.advancedMode": "Advanced mode",
"filesharing.saveFileError": "Unable to save file",
"filesharing.startingFileShare": "Attempting to share file",
"filesharing.successfulFileShare": "File successfully shared",
"filesharing.unableToShare": "Unable to share file",
"filesharing.error": "There was a filesharing error",
"filesharing.finished": "File finished downloading",
"filesharing.save": "Save",
"filesharing.sharedFile": "{displayName} shared a file",
"filesharing.download": "Download",
"filesharing.missingSeeds": "If this process takes a long time, there might not be anyone seeding this torrent. Try asking someone to reupload the file that you want.",
"devices.devicesChanged": "Your devices changed, configure your devices in the settings dialog",
"device.audioUnsupported": "Audio unsupported",
"device.activateAudio": "Activate audio",
"device.muteAudio": "Mute audio",
"device.unMuteAudio": "Unmute audio",
"device.videoUnsupported": "Video unsupported",
"device.startVideo": "Start video",
"device.stopVideo": "Stop video",
"device.screenSharingUnsupported": "Screen sharing not supported",
"device.startScreenSharing": "Start screen sharing",
"device.stopScreenSharing": "Stop screen sharing",
"devices.microphoneDisconnected": "Microphone disconnected",
"devices.microphoneError": "An error occured while accessing your microphone",
"devices.microPhoneMute": "Muted your microphone",
"devices.micophoneUnMute": "Unmuted your microphone",
"devices.microphoneEnable": "Enabled your microphone",
"devices.microphoneMuteError": "Unable to mute your microphone",
"devices.microphoneUnMuteError": "Unable to unmute your microphone",
"devices.screenSharingDisconnected" : "Screen sharing disconnected",
"devices.screenSharingError": "An error occured while accessing your screen",
"devices.cameraDisconnected": "Camera disconnected",
"devices.cameraError": "An error occured while accessing your camera"
}

View File

@ -0,0 +1,133 @@
{
"socket.disconnected": "Du er frakoblet",
"socket.reconnecting": "Du er frakoblet, forsøker å koble til på nytt",
"socket.reconnected": "Du er koblet til igjen",
"socket.requestError": "Feil på server melding",
"room.cookieConsent": "Denne siden bruker cookies for å forbedre brukeropplevelsen",
"room.joined": "Du ble med i møtet",
"room.cantJoin": "Kunne ikke bli med i møtet",
"room.youLocked": "Du låste møtet",
"room.cantLock": "Klarte ikke å låse møtet",
"room.youUnLocked": "Du låste opp møtet",
"room.cantUnLock": "Klarte ikke å låse opp møtet",
"room.locked": "Møtet er låst",
"room.unlocked": "Møtet er låst opp",
"room.newLobbyPeer": "Ny deltaker i lobbyen",
"room.lobbyPeerLeft": "Deltaker i lobbyen forsvant",
"room.lobbyPeerChangedDisplayName": "Deltaker i lobbyen endret navn til {displayName}",
"room.lobbyPeerChangedPicture": "Deltaker i lobbyen endret bilde",
"room.setAccessCode": "Tilgangskode for møtet er oppdatert",
"room.accessCodeOn": "Tilgangskode for møtet er aktivert",
"room.accessCodeOff": "Tilgangskode for møtet er deaktivert",
"room.peerChangedDisplayName": "{oldDisplayName} heter nå {displayName}",
"room.newPeer": "{displayName} ble med i møtet",
"room.newFile": "Ny fil tilgjengelig",
"room.toggleAdvancedMode": "Aktiver avansert modus",
"room.setDemocraticView": "Endret layout til demokratisk",
"room.setFilmStripView": "Endret layout til filmstripe",
"room.loggedIn": "Du er logget inn",
"room.loggedOut": "Du er logget ut",
"room.changedDisplayName": "Navnet ditt er nå {displayName}",
"room.changeDisplayNameError": "Det skjedde en feil ved endring av navnet ditt",
"room.chatError": "Klarte ikke sende melding",
"room.aboutToJoin": "Du er i ferd med å bli med i et møte",
"room.roomId": "Møte ID: {roomName}",
"room.setYourName": "Skriv inn navnet ditt, og velg hvordan du vil bli med i møtet",
"room.audioOnly": "Kun lyd",
"room.audioVideo": "Lyd og bilde",
"room.youAreReady": "Ok, du er klar",
"room.emptyRequireLogin": "Møtet er tomt. Du kan logge inn for å starte møtet, eller vente til verten kommer",
"room.locketWait": "Møtet er låst, vent til noen slipper deg inn",
"room.lobbyAdministration": "Lobby administrasjon",
"room.peersInLobby": "Deltakere i lobbyen",
"room.lobbyEmpty": "Det er for øyeblikket ingen deltakere i lobbyen",
"room.hiddenPeers": "{hiddenPeersCount, plural, one {deltaker} other {deltakere}}",
"room.me": "Meg",
"room.spotlights": "Deltakere i fokus",
"room.passive": "Passive deltakere",
"room.videoPaused": "Denne videoen er inaktiv",
"tooltip.login": "Logg in",
"tooltip.logout": "Logg ut",
"tooltip.admitFromLobby": "Slipp inn fra lobby",
"tooltip.lockRoom": "Lås møtet",
"tooltip.unLockRoom": "Lås opp møtet",
"tooltip.enterFullscreen": "Gå til fullskjerm",
"tooltip.leaveFullscreen": "Forlat fullskjerm",
"tooltip.lobby": "Vis lobby",
"tooltip.settings": "Vis innstillinger",
"label.yourName": "Ditt navn",
"label.newWindow": "Flytt til separat vindu",
"label.fullscreen": "Fullskjerm",
"label.openDrawer": "Åpne meny",
"label.leave": "Avslutt",
"label.chatInput": "Skriv melding...",
"label.chat": "Chat",
"label.filesharing": "Fildeling",
"label.participants": "Deltakere",
"label.shareFile": "Del fil",
"label.fileSharingUnsupported": "Fildeling ikke støttet",
"label.unknown": "Ukjent",
"label.democratic": "Demokratisk",
"label.filmstrip": "Filmstripe",
"label.low": "Lav",
"label.medium": "Medium",
"label.high": "Høy (HD)",
"label.veryHigh": "Veldig høy (FHD)",
"label.ultra": "Ultra (UHD)",
"label.close": "Lukk",
"settings.settings": "Innstillinger",
"settings.camera": "Kamera",
"settings.selectCamera": "Velg videoenhet",
"settings.cantSelectCamera": "Kan ikke velge videoenhet",
"settings.audio": "Lydenhet",
"settings.selectAudio": "Velg lydenhet",
"settings.cantSelectAudio": "Kan ikke velge lydenhet",
"settings.resolution": "Velg oppløsning",
"settings.layout": "Møtelayout",
"settings.selectRoomLayout": "Velg møtelayout",
"settings.advancedMode": "Avansert modus",
"filesharing.saveFileError": "Klarte ikke å lagre fil",
"filesharing.startingFileShare": "Starter fildeling",
"filesharing.successfulFileShare": "Filen ble delt",
"filesharing.unableToShare": "Klarte ikke å dele fil",
"filesharing.error": "Det skjedde noe feil med fildeling",
"filesharing.finished": "Fil ferdig lastet ned",
"filesharing.save": "Lagre",
"filesharing.sharedFile": "{displayName} delte en fil",
"filesharing.download": "Last ned",
"filesharing.missingSeeds": "Dersom dette tar lang til mangler det kanskje noen som kan dele denne filen. Prøv å spørre noen om å laste opp filen på nytt.",
"devices.devicesChanged": "Medieenhetene dine endret seg, du kan konfigurere enheter i innstillinger",
"device.audioUnsupported": "Lyd ikke støttet",
"device.activateAudio": "Aktiver lyd",
"device.muteAudio": "Demp lyd",
"device.unMuteAudio": "Aktiver lyd",
"device.videoUnsupported": "Video ikke støttet",
"device.startVideo": "Start video",
"device.stopVideo": "Stopp video",
"device.screenSharingUnsupported": "Skjermdeling ikke støttet",
"device.startScreenSharing": "Start skjermdeling",
"device.stopScreenSharing": "Stopp skjermdeling",
"devices.microphoneDisconnected": "Mikrofon koblet fra",
"devices.microphoneError": "Det skjedde noe feil med mikrofonen din",
"devices.microPhoneMute": "Dempet mikrofonen",
"devices.micophoneUnMute": "Aktiverte mikrofonen",
"devices.microphoneEnable": "Aktiverte mikrofonen",
"devices.microphoneMuteError": "Klarte ikke å dempe mikrofonen",
"devices.microphoneUnMuteError": "Klarte ikke å aktivere mikrofonen",
"devices.screenSharingDisconnected" : "Skjermdelingen forsvant",
"devices.screenSharingError": "Det skjedde noe feil med skjermdelingen din",
"devices.cameraDisconnected": "Kamera koblet fra",
"devices.cameraError": "Det skjedde noe feil med kameraet ditt"
}