From c12057e53c9e603cd206a6d85d9d8d2e3d81a577 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Wed, 6 Nov 2019 23:11:42 +0100 Subject: [PATCH] Added internationalization support, and translated to nb-NO. --- app/package.json | 1 + app/src/RoomClient.js | 284 +++++++++++++----- .../AccessControl/LockDialog/ListLobbyPeer.js | 10 +- .../AccessControl/LockDialog/LockDialog.js | 23 +- app/src/components/Containers/HiddenPeers.js | 16 +- app/src/components/Containers/Me.js | 88 ++++-- app/src/components/Containers/Peer.js | 219 +++++++++----- app/src/components/Containers/SpeakerPeer.js | 15 +- app/src/components/Controls/TopBar.js | 96 +++++- app/src/components/JoinDialog.js | 71 ++++- .../MeetingDrawer/Chat/ChatInput.js | 119 ++++---- .../components/MeetingDrawer/Chat/Message.js | 2 +- .../MeetingDrawer/Chat/MessageList.js | 13 +- .../MeetingDrawer/FileSharing/File.js | 39 ++- .../MeetingDrawer/FileSharing/FileList.js | 15 +- .../MeetingDrawer/FileSharing/FileSharing.js | 82 ++--- .../components/MeetingDrawer/MeetingDrawer.js | 20 +- .../ParticipantList/ParticipantList.js | 22 +- app/src/components/Room.js | 7 +- app/src/components/Settings/Settings.js | 144 ++++++--- app/src/index.js | 37 ++- app/src/translations/en.json | 133 ++++++++ app/src/translations/nb.json | 133 ++++++++ 23 files changed, 1214 insertions(+), 375 deletions(-) create mode 100644 app/src/translations/en.json create mode 100644 app/src/translations/nb.json diff --git a/app/package.json b/app/package.json index de631ba..8cb5d4a 100644 --- a/app/package.json +++ b/app/package.json @@ -20,6 +20,7 @@ "react": "^16.10.2", "react-cookie-consent": "^2.5.0", "react-dom": "^16.10.2", + "react-intl": "^3.4.0", "react-redux": "^7.1.1", "react-scripts": "3.2.0", "redux": "^4.0.4", diff --git a/app/src/RoomClient.js b/app/src/RoomClient.js index c75248e..6ae30c6 100644 --- a/app/src/RoomClient.js +++ b/app/src/RoomClient.js @@ -81,15 +81,19 @@ const VIDEO_ENCODINGS = let store; +let intl; + export default class RoomClient { /** * @param {Object} data * @param {Object} data.store - The Redux store. + * @param {Object} data.intl - react-intl object */ static init(data) { store = data.store; + intl = data.intl; } constructor( @@ -230,7 +234,10 @@ export default class RoomClient store.dispatch(settingsActions.toggleAdvancedMode()); store.dispatch(requestActions.notify( { - text : 'Toggled advanced mode.' + text : intl.formatMessage({ + id : 'room.toggleAdvancedMode', + defaultMessage : 'Toggled advanced mode' + }) })); break; } @@ -240,7 +247,10 @@ export default class RoomClient store.dispatch(roomActions.setDisplayMode('democratic')); store.dispatch(requestActions.notify( { - text : 'Changed layout to democratic view.' + text : intl.formatMessage({ + id : 'room.setDemocraticView', + defaultMessage : 'Changed layout to democratic view' + }) })); break; } @@ -250,7 +260,10 @@ export default class RoomClient store.dispatch(roomActions.setDisplayMode('filmstrip')); store.dispatch(requestActions.notify( { - text : 'Changed layout to filmstrip view.' + text : intl.formatMessage({ + id : 'room.setFilmStripView', + defaultMessage : 'Changed layout to filmstrip view' + }) })); break; } @@ -265,7 +278,10 @@ export default class RoomClient store.dispatch(requestActions.notify( { - text : 'Muted your microphone.' + text : intl.formatMessage({ + id : 'devices.microPhoneMute', + defaultMessage : 'Muted your microphone' + }) })); } else @@ -274,7 +290,10 @@ export default class RoomClient 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( { - 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( { - 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( { - 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( { - 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( { - text : `Your display name changed to ${displayName}.` + text : intl.formatMessage({ + id : 'room.changedDisplayName', + defaultMessage : 'Your display name changed to {displayName}', + values : { + displayName + } + }) })); } catch (error) @@ -468,7 +505,10 @@ export default class RoomClient store.dispatch(requestActions.notify( { 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( { 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( { 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( { - text : 'Starting file share.' + text : intl.formatMessage({ + id : 'filesharing.startingFileShare', + defaultMessage : 'Attempting to share file' + }) })); this._webTorrent.seed(files, (torrent) => @@ -603,7 +652,10 @@ export default class RoomClient store.dispatch(requestActions.notify( { - text : 'File successfully shared.' + text : intl.formatMessage({ + id : 'filesharing.successfulFileShare', + defaultMessage : 'File successfully shared' + }) })); store.dispatch(fileActions.addFile( @@ -631,7 +683,10 @@ export default class RoomClient store.dispatch(requestActions.notify( { 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) { 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( { 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( { 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); - 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 store.dispatch(meActions.setMyRaiseHandState(!state)); } @@ -1213,7 +1262,7 @@ export default class RoomClient store.dispatch(requestActions.notify( { 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( { - text : 'You are disconnected.' + text : intl.formatMessage({ + id : 'socket.disconnected', + defaultMessage : 'You are disconnected' + }) })); this.close(); @@ -1249,7 +1301,10 @@ export default class RoomClient 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')); @@ -1261,7 +1316,10 @@ export default class RoomClient store.dispatch(requestActions.notify( { - text : 'You are disconnected.' + text : intl.formatMessage({ + id : 'socket.disconnected', + defaultMessage : 'You are disconnected' + }) })); this.close(); @@ -1273,7 +1331,10 @@ export default class RoomClient store.dispatch(requestActions.notify( { - text : 'You are reconnected.' + text : intl.formatMessage({ + id : 'socket.reconnected', + defaultMessage : 'You are reconnected' + }) })); store.dispatch(roomActions.setRoomState('connected')); @@ -1450,7 +1511,10 @@ export default class RoomClient store.dispatch(requestActions.notify( { - text : 'Room is now locked.' + text : intl.formatMessage({ + id : 'room.locked', + defaultMessage : 'Room is now locked' + }) })); break; @@ -1463,7 +1527,10 @@ export default class RoomClient store.dispatch(requestActions.notify( { - text : 'Room is now unlocked.' + text : intl.formatMessage({ + id : 'room.unlocked', + defaultMessage : 'Room is now unlocked' + }) })); break; @@ -1480,7 +1547,10 @@ export default class RoomClient store.dispatch(requestActions.notify( { - text : 'New participant entered the lobby.' + text : intl.formatMessage({ + id : 'room.newLobbyPeer', + defaultMessage : 'New participant entered the lobby' + }) })); break; @@ -1495,7 +1565,10 @@ export default class RoomClient store.dispatch(requestActions.notify( { - text : 'Participant in lobby left.' + text : intl.formatMessage({ + id : 'room.lobbyPeerLeft', + defaultMessage : 'Participant in lobby left' + }) })); break; @@ -1520,7 +1593,13 @@ export default class RoomClient 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; @@ -1535,7 +1614,10 @@ export default class RoomClient store.dispatch(requestActions.notify( { - text : 'Participant in lobby changed picture.' + text : intl.formatMessage({ + id : 'room.lobbyPeerChangedPicture', + defaultMessage : 'Participant in lobby changed picture' + }) })); break; @@ -1550,7 +1632,10 @@ export default class RoomClient store.dispatch(requestActions.notify( { - text : 'Access code for room updated' + text : intl.formatMessage({ + id : 'room.setAccessCode', + defaultMessage : 'Access code for room updated' + }) })); break; @@ -1567,14 +1652,20 @@ export default class RoomClient { 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 { 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( { - text : `${oldDisplayName} is now ${displayName}` + text : intl.formatMessage({ + id : 'room.peerChangedDisplayName', + defaultMessage : '{oldDisplayName} is now {displayName}', + values : { + oldDisplayName, + displayName + } + }) })); break; @@ -1647,7 +1745,10 @@ export default class RoomClient store.dispatch(requestActions.notify( { - text : 'New file available.' + text : intl.formatMessage({ + id : 'room.newFile', + defaultMessage : 'New file available' + }) })); if ( @@ -1683,7 +1784,13 @@ export default class RoomClient store.dispatch(requestActions.notify( { - text : `${displayName} joined the room.` + text : intl.formatMessage({ + id : 'room.newPeer', + defaultMessage : '{displayName} joined the room', + values : { + displayName + } + }) })); break; @@ -1788,7 +1895,10 @@ export default class RoomClient store.dispatch(requestActions.notify( { 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( { - text : 'You have joined the room.' + text : intl.formatMessage({ + id : 'room.joined', + defaultMessage : 'You have joined the room' + }) })); this._spotlights.start(); @@ -1969,7 +2082,10 @@ export default class RoomClient store.dispatch(requestActions.notify( { type : 'error', - text : 'Unable to join the room.' + text : intl.formatMessage({ + id : 'room.cantJoin', + defaultMessage : 'Unable to join the room' + }) })); this.close(); @@ -1989,7 +2105,10 @@ export default class RoomClient store.dispatch(requestActions.notify( { - text : 'You locked the room.' + text : intl.formatMessage({ + id : 'room.youLocked', + defaultMessage : 'You locked the room' + }) })); } catch (error) @@ -1997,7 +2116,10 @@ export default class RoomClient store.dispatch(requestActions.notify( { 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); @@ -2017,7 +2139,10 @@ export default class RoomClient store.dispatch(requestActions.notify( { - text : 'You unlocked the room.' + text : intl.formatMessage({ + id : 'room.youUnLocked', + defaultMessage : 'You unlocked the room' + }) })); } catch (error) @@ -2025,7 +2150,10 @@ export default class RoomClient store.dispatch(requestActions.notify( { 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); @@ -2164,7 +2292,10 @@ export default class RoomClient store.dispatch(requestActions.notify( { type : 'error', - text : 'Microphone disconnected!' + text : intl.formatMessage({ + id : 'devices.microphoneDisconnected', + defaultMessage : 'Microphone disconnected' + }) })); this.disableMic() @@ -2215,7 +2346,10 @@ export default class RoomClient store.dispatch(requestActions.notify( { 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) @@ -2248,11 +2382,7 @@ export default class RoomClient } catch (error) { - store.dispatch(requestActions.notify( - { - type : 'error', - text : `Error closing server-side mic Producer: ${error}` - })); + logger.error('disableMic() [error:"%o"]', error); } this._micProducer = null; @@ -2341,7 +2471,10 @@ export default class RoomClient store.dispatch(requestActions.notify( { type : 'error', - text : 'Screen sharing disconnected!' + text : intl.formatMessage({ + id : 'devices.screenSharingDisconnected', + defaultMessage : 'Screen sharing disconnected' + }) })); this.disableScreenSharing() @@ -2357,7 +2490,10 @@ export default class RoomClient store.dispatch(requestActions.notify( { 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) @@ -2388,11 +2524,7 @@ export default class RoomClient } catch (error) { - store.dispatch(requestActions.notify( - { - type : 'error', - text : `Error closing server-side screen Producer: ${error}` - })); + logger.error('disableScreenSharing() [error:"%o"]', error); } this._screenSharingProducer = null; @@ -2497,7 +2629,10 @@ export default class RoomClient store.dispatch(requestActions.notify( { type : 'error', - text : 'Webcam disconnected!' + text : intl.formatMessage({ + id : 'devices.cameraDisconnected', + defaultMessage : 'Camera disconnected' + }) })); this.disableWebcam() @@ -2513,7 +2648,10 @@ export default class RoomClient store.dispatch(requestActions.notify( { 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) @@ -2545,11 +2683,7 @@ export default class RoomClient } catch (error) { - store.dispatch(requestActions.notify( - { - type : 'error', - text : `Error closing server-side webcam Producer: ${error}` - })); + logger.error('disableWebcam() [error:"%o"]', error); } this._webcamProducer = null; diff --git a/app/src/components/AccessControl/LockDialog/ListLobbyPeer.js b/app/src/components/AccessControl/LockDialog/ListLobbyPeer.js index ba4adde..94d04d2 100644 --- a/app/src/components/AccessControl/LockDialog/ListLobbyPeer.js +++ b/app/src/components/AccessControl/LockDialog/ListLobbyPeer.js @@ -4,6 +4,7 @@ import { withStyles } from '@material-ui/core/styles'; import PropTypes from 'prop-types'; import classnames from 'classnames'; import { withRoomContext } from '../../../RoomContext'; +import { useIntl } from 'react-intl'; import ListItem from '@material-ui/core/ListItem'; import ListItemText from '@material-ui/core/ListItemText'; import ListItemIcon from '@material-ui/core/ListItemIcon'; @@ -85,6 +86,8 @@ const ListLobbyPeer = (props) => classes } = props; + const intl = useIntl(); + const picture = peer.picture || EmptyAvatar; return ( @@ -100,7 +103,12 @@ const ListLobbyPeer = (props) => - + - Lobby administration + + + {/* Room lock @@ -129,7 +135,10 @@ const LockDialog = ({ dense subheader={ - Participants in Lobby + } > @@ -143,13 +152,19 @@ const LockDialog = ({ : - There are currently no one in the lobby. + } diff --git a/app/src/components/Containers/HiddenPeers.js b/app/src/components/Containers/HiddenPeers.js index 0dd811a..49b5462 100644 --- a/app/src/components/Containers/HiddenPeers.js +++ b/app/src/components/Containers/HiddenPeers.js @@ -3,6 +3,7 @@ import { connect } from 'react-redux'; import PropTypes from 'prop-types'; import classnames from 'classnames'; import { withStyles } from '@material-ui/core/styles'; +import { FormattedMessage } from 'react-intl'; import * as toolareaActions from '../../actions/toolareaActions'; import BuddyImage from '../../images/buddy.svg'; @@ -95,8 +96,19 @@ class HiddenPeers extends React.PureComponent className={classnames(classes.root, this.state.className)} onClick={() => openUsersTab()} > -

+{hiddenPeersCount}
participant - {(hiddenPeersCount > 1) && 's'} +

+ +{hiddenPeersCount}
+

); diff --git a/app/src/components/Containers/Me.js b/app/src/components/Containers/Me.js index 2ff2adf..37e0068 100644 --- a/app/src/components/Containers/Me.js +++ b/app/src/components/Containers/Me.js @@ -7,6 +7,7 @@ import useMediaQuery from '@material-ui/core/useMediaQuery'; import PropTypes from 'prop-types'; import classnames from 'classnames'; import * as appPropTypes from '../appPropTypes'; +import { useIntl, FormattedMessage } from 'react-intl'; import VideoView from '../VideoContainers/VideoView'; import Volume from './Volume'; import Fab from '@material-ui/core/Fab'; @@ -96,6 +97,8 @@ const Me = (props) => { const [ hover, setHover ] = useState(false); + const intl = useIntl(); + let touchTimeout = null; const { @@ -133,22 +136,34 @@ const Me = (props) => if (!me.canSendMic) { micState = 'unsupported'; - micTip = 'Audio unsupported'; + micTip = intl.formatMessage({ + id : 'device.audioUnsupported', + defaultMessage : 'Audio unsupported' + }); } else if (!micProducer) { micState = 'off'; - micTip = 'Activate audio'; + micTip = intl.formatMessage({ + id : 'device.activateAudio', + defaultMessage : 'Activate audio' + }); } else if (!micProducer.locallyPaused && !micProducer.remotelyPaused) { micState = 'on'; - micTip = 'Mute audio'; + micTip = intl.formatMessage({ + id : 'device.muteAudio', + defaultMessage : 'Mute audio' + }); } else { micState = 'muted'; - micTip = 'Unmute audio'; + micTip = intl.formatMessage({ + id : 'device.unMuteAudio', + defaultMessage : 'Unmute audio' + }); } let webcamState; @@ -158,17 +173,26 @@ const Me = (props) => if (!me.canSendWebcam) { webcamState = 'unsupported'; - webcamTip = 'Video unsupported'; + webcamTip = intl.formatMessage({ + id : 'device.videoUnsupported', + defaultMessage : 'Video unsupported' + }); } else if (webcamProducer) { webcamState = 'on'; - webcamTip = 'Stop video'; + webcamTip = intl.formatMessage({ + id : 'device.stopVideo', + defaultMessage : 'Stop video' + }); } else { webcamState = 'off'; - webcamTip = 'Start video'; + webcamTip = intl.formatMessage({ + id : 'device.startVideo', + defaultMessage : 'Start video' + }); } let screenState; @@ -178,17 +202,26 @@ const Me = (props) => if (!me.canShareScreen) { screenState = 'unsupported'; - screenTip = 'Screen sharing not supported'; + screenTip = intl.formatMessage({ + id : 'device.screenSharingUnsupported', + defaultMessage : 'Screen sharing not supported' + }); } else if (screenProducer) { screenState = 'on'; - screenTip = 'Stop screen sharing'; + screenTip = intl.formatMessage({ + id : 'device.stopScreenSharing', + defaultMessage : 'Stop screen sharing' + }); } else { screenState = 'off'; - screenTip = 'Start screen sharing'; + screenTip = intl.formatMessage({ + id : 'device.startScreenSharing', + defaultMessage : 'Start screen sharing' + }); } const spacingStyle = @@ -253,11 +286,19 @@ const Me = (props) => }, 2000); }} > -

ME

- +

+ +

+
- +
- +
}, 2000); }} > -

ME

+

+ +

{ const [ hover, setHover ] = useState(false); + const intl = useIntl(); + let touchTimeout = null; const { @@ -194,7 +198,12 @@ const Peer = (props) =>
{ !videoVisible &&
-

this video is paused

+

+ +

} @@ -220,56 +229,95 @@ const Peer = (props) => }, 2000); }} > - - { - micEnabled ? - roomClient.modifyPeerConsumer(peer.id, 'mic', true) : - roomClient.modifyPeerConsumer(peer.id, 'mic', false); - }} + - { micEnabled ? - - : - - } - +
+ + { + micEnabled ? + roomClient.modifyPeerConsumer(peer.id, 'mic', true) : + roomClient.modifyPeerConsumer(peer.id, 'mic', false); + }} + > + { micEnabled ? + + : + + } + +
+ { !smallScreen && - - { - toggleConsumerWindow(webcamConsumer); - }} + - - +
+ + { + toggleConsumerWindow(webcamConsumer); + }} + > + + +
+ } - - { - toggleConsumerFullscreen(webcamConsumer); - }} + - - +
+ + { + toggleConsumerFullscreen(webcamConsumer); + }} + > + + +
+
> { !screenVisible &&
-

this video is paused

+

+ +

} @@ -344,35 +397,61 @@ const Peer = (props) => }} > { !smallScreen && - - { - toggleConsumerWindow(screenConsumer); - }} + - - +
+ + { + toggleConsumerWindow(screenConsumer); + }} + > + + +
+
} - - { - toggleConsumerFullscreen(screenConsumer); - }} + - - +
+ + { + toggleConsumerFullscreen(screenConsumer); + }} + > + + +
+
{ !videoVisible &&
-

this video is paused

+

+ +

} @@ -145,7 +151,12 @@ const SpeakerPeer = (props) => > { !screenVisible &&
-

this video is paused

+

+ +

} diff --git a/app/src/components/Controls/TopBar.js b/app/src/components/Controls/TopBar.js index d597387..21c09b0 100644 --- a/app/src/components/Controls/TopBar.js +++ b/app/src/components/Controls/TopBar.js @@ -9,6 +9,7 @@ import { withRoomContext } from '../../RoomContext'; import { withStyles } from '@material-ui/core/styles'; import * as roomActions from '../../actions/roomActions'; import * as toolareaActions from '../../actions/toolareaActions'; +import { useIntl, FormattedMessage } from 'react-intl'; import AppBar from '@material-ui/core/AppBar'; import Toolbar from '@material-ui/core/Toolbar'; import Typography from '@material-ui/core/Typography'; @@ -109,6 +110,8 @@ const PulsingBadge = withStyles((theme) => const TopBar = (props) => { + const intl = useIntl(); + const { roomClient, room, @@ -126,6 +129,39 @@ const TopBar = (props) => classes } = 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 ( > toggleToolArea()} className={classes.menuButton} > @@ -156,9 +195,12 @@ const TopBar = (props) =>
- + @@ -181,9 +223,17 @@ const TopBar = (props) => { lobbyPeers.length > 0 && - + setLockDialogOpen(!room.lockDialogOpen)} > @@ -197,9 +247,12 @@ const TopBar = (props) => } { fullscreenEnabled && - + } - + setSettingsOpen(!room.settingsOpen)} @@ -223,9 +284,12 @@ const TopBar = (props) => { loginEnabled && - + @@ -242,13 +306,19 @@ const TopBar = (props) => }
diff --git a/app/src/components/JoinDialog.js b/app/src/components/JoinDialog.js index 759bd89..4054919 100644 --- a/app/src/components/JoinDialog.js +++ b/app/src/components/JoinDialog.js @@ -4,6 +4,7 @@ import { withStyles } from '@material-ui/core/styles'; import { withRoomContext } from '../RoomContext'; import * as settingsActions from '../actions/settingsActions'; import PropTypes from 'prop-types'; +import { useIntl, FormattedMessage } from 'react-intl'; import Dialog from '@material-ui/core/Dialog'; import DialogContentText from '@material-ui/core/DialogContentText'; import IconButton from '@material-ui/core/IconButton'; @@ -87,6 +88,8 @@ const DialogTitle = withStyles(styles)((props) => { const [ open, setOpen ] = useState(false); + const intl = useIntl(); + useEffect(() => { const openTimer = setTimeout(() => setOpen(true), 1000); @@ -120,7 +123,10 @@ const DialogTitle = withStyles(styles)((props) => onClose={handleTooltipClose} onOpen={handleTooltipOpen} open={open} - title='Click to log in' + title={intl.formatMessage({ + id : 'tooltip.login', + defaultMessage : 'Click to log in' + })} placement='left' > { + const intl = useIntl(); + const handleKeyDown = (event) => { const { key } = event; @@ -207,21 +215,38 @@ const JoinDialog = ({ - You are about to join a meeting. + - Room ID: { room.name } + - Set your name for participation, - and choose how you want to join: + - Audio only + : @@ -276,23 +307,37 @@ const JoinDialog = ({ variant='h6' align='center' > - Ok, you are ready + { room.signInRequired ? - The room is empty! - You can Log In to start the meeting or wait until the host joins. + : - The room is locked - hang on until somebody lets you in ... + } } - This website uses cookies to enhance the user experience. +
diff --git a/app/src/components/MeetingDrawer/Chat/ChatInput.js b/app/src/components/MeetingDrawer/Chat/ChatInput.js index 0f0235d..f84af96 100644 --- a/app/src/components/MeetingDrawer/Chat/ChatInput.js +++ b/app/src/components/MeetingDrawer/Chat/ChatInput.js @@ -1,8 +1,9 @@ -import React from 'react'; +import React, { useState } from 'react'; import { connect } from 'react-redux'; import PropTypes from 'prop-types'; import { withStyles } from '@material-ui/core/styles'; import { withRoomContext } from '../../../RoomContext'; +import { useIntl } from 'react-intl'; import Paper from '@material-ui/core/Paper'; import InputBase from '@material-ui/core/InputBase'; import IconButton from '@material-ui/core/IconButton'; @@ -28,19 +29,13 @@ const styles = (theme) => } }); -class ChatInput extends React.PureComponent +const ChatInput = (props) => { - constructor(props) - { - super(props); + const [ message, setMessage ] = useState(''); - this.state = - { - message : '' - }; - } + const intl = useIntl(); - createNewMessage = (text, sender, name, picture) => + const createNewMessage = (text, sender, name, picture) => ({ type : 'message', text, @@ -50,67 +45,67 @@ class ChatInput extends React.PureComponent picture }); - handleChange = (e) => + const handleChange = (e) => { - this.setState({ message: e.target.value }); - } + setMessage(e.target.value); + }; - render() - { - const { - roomClient, - displayName, - picture, - classes - } = this.props; - - return ( - - + const { + roomClient, + displayName, + picture, + classes + } = props; + + return ( + + + { + 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 !== '') - { - const message = this.createNewMessage(this.state.message, 'response', displayName, picture); + roomClient.sendChatMessage(sendMessage); - roomClient.sendChatMessage(message); - - this.setState({ message: '' }); - } + setMessage(''); } - }} - autoFocus - /> - + } + }} + autoFocus + /> + + { + if (message && message !== '') { - if (this.state.message && this.state.message !== '') - { - const message = this.createNewMessage(this.state.message, 'response', displayName, picture); + const sendMessage = this.createNewMessage(message, 'response', displayName, picture); - roomClient.sendChatMessage(message); + roomClient.sendChatMessage(sendMessage); - this.setState({ message: '' }); - } - }} - > - - - - ); - } -} + setMessage(''); + } + }} + > + +
+ + ); +}; ChatInput.propTypes = { diff --git a/app/src/components/MeetingDrawer/Chat/Message.js b/app/src/components/MeetingDrawer/Chat/Message.js index 856ac92..5a0d812 100644 --- a/app/src/components/MeetingDrawer/Chat/Message.js +++ b/app/src/components/MeetingDrawer/Chat/Message.js @@ -92,7 +92,7 @@ Message.propTypes = self : PropTypes.bool, picture : PropTypes.string, text : PropTypes.string, - time : PropTypes.string, + time : PropTypes.object, name : PropTypes.string, classes : PropTypes.object.isRequired }; diff --git a/app/src/components/MeetingDrawer/Chat/MessageList.js b/app/src/components/MeetingDrawer/Chat/MessageList.js index 0664f04..da6891b 100644 --- a/app/src/components/MeetingDrawer/Chat/MessageList.js +++ b/app/src/components/MeetingDrawer/Chat/MessageList.js @@ -2,6 +2,7 @@ import React from 'react'; import { connect } from 'react-redux'; import PropTypes from 'prop-types'; import { withStyles } from '@material-ui/core/styles'; +import { FormattedTime } from 'react-intl'; import Message from './Message'; import EmptyAvatar from '../../../images/avatar-empty.jpeg'; @@ -49,7 +50,7 @@ class MessageList extends React.Component getTimeString(time) { - return `${(time.getHours() < 10 ? '0' : '')}${time.getHours()}:${(time.getMinutes() < 10 ? '0' : '')}${time.getMinutes()}`; + return (); } render() @@ -65,8 +66,6 @@ class MessageList extends React.Component { chat.map((message, index) => { - const messageTime = new Date(message.time); - const picture = (message.sender === 'response' ? message.picture : myPicture) || EmptyAvatar; @@ -76,7 +75,7 @@ class MessageList extends React.Component self={message.sender === 'client'} picture={picture} text={message.text} - time={this.getTimeString(messageTime)} + time={this.getTimeString(message.time)} name={message.name} /> ); @@ -89,9 +88,9 @@ class MessageList extends React.Component MessageList.propTypes = { - chat : PropTypes.array, - myPicture : PropTypes.string, - classes : PropTypes.object.isRequired + chat : PropTypes.array, + myPicture : PropTypes.string, + classes : PropTypes.object.isRequired }; const mapStateToProps = (state) => diff --git a/app/src/components/MeetingDrawer/FileSharing/File.js b/app/src/components/MeetingDrawer/FileSharing/File.js index dd89cd2..fbcad8b 100644 --- a/app/src/components/MeetingDrawer/FileSharing/File.js +++ b/app/src/components/MeetingDrawer/FileSharing/File.js @@ -3,6 +3,7 @@ import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import { withRoomContext } from '../../../RoomContext'; import { withStyles } from '@material-ui/core/styles'; +import { FormattedMessage } from 'react-intl'; import magnet from 'magnet-uri'; import Typography from '@material-ui/core/Typography'; import Button from '@material-ui/core/Button'; @@ -70,7 +71,10 @@ class File extends React.PureComponent { file.files && - File finished downloading + { file.files.map((sharedFile, i) => ( @@ -87,14 +91,23 @@ class File extends React.PureComponent roomClient.saveFile(sharedFile); }} > - Save +
))} } - { `${displayName} shared a file` } + { (!file.active && !file.files) && @@ -112,11 +125,17 @@ class File extends React.PureComponent roomClient.handleDownload(magnetUri); }} > - Download + : - Your browser does not support downloading files using WebTorrent. + } @@ -124,8 +143,14 @@ class File extends React.PureComponent { file.timeout && - 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. + } diff --git a/app/src/components/MeetingDrawer/FileSharing/FileList.js b/app/src/components/MeetingDrawer/FileSharing/FileList.js index d2142d6..8585aaa 100644 --- a/app/src/components/MeetingDrawer/FileSharing/FileList.js +++ b/app/src/components/MeetingDrawer/FileSharing/FileList.js @@ -3,6 +3,7 @@ import { connect } from 'react-redux'; import PropTypes from 'prop-types'; import * as appPropTypes from '../../appPropTypes'; import { withStyles } from '@material-ui/core/styles'; +import { injectIntl } from 'react-intl'; import File from './File'; import EmptyAvatar from '../../../images/avatar-empty.jpeg'; @@ -46,6 +47,7 @@ class FileList extends React.PureComponent files, me, peers, + intl, classes } = this.props; @@ -59,7 +61,10 @@ class FileList extends React.PureComponent if (me.id === file.peerId) { - displayName = 'You'; + displayName = intl.formatMessage({ + id : 'room.me', + defaultMessage : 'Me' + }); filePicture = me.picture; } else if (peers[file.peerId]) @@ -69,7 +74,10 @@ class FileList extends React.PureComponent } else { - displayName = 'Unknown'; + displayName = intl.formatMessage({ + id : 'label.unknown', + defaultMessage : 'Unknown' + }); } return ( @@ -91,6 +99,7 @@ FileList.propTypes = files : PropTypes.object.isRequired, me : appPropTypes.Me.isRequired, peers : PropTypes.object.isRequired, + intl : PropTypes.object.isRequired, classes : PropTypes.object.isRequired }; @@ -117,4 +126,4 @@ export default connect( ); } } -)(withStyles(styles)(FileList)); +)(withStyles(styles)(injectIntl(FileList))); diff --git a/app/src/components/MeetingDrawer/FileSharing/FileSharing.js b/app/src/components/MeetingDrawer/FileSharing/FileSharing.js index 2c33c4b..8713134 100644 --- a/app/src/components/MeetingDrawer/FileSharing/FileSharing.js +++ b/app/src/components/MeetingDrawer/FileSharing/FileSharing.js @@ -3,6 +3,7 @@ import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import { withStyles } from '@material-ui/core/styles'; import { withRoomContext } from '../../../RoomContext'; +import { useIntl } from 'react-intl'; import FileList from './FileList'; import Paper from '@material-ui/core/Paper'; import Button from '@material-ui/core/Button'; @@ -26,58 +27,57 @@ const styles = (theme) => } }); -class FileSharing extends React.PureComponent +const FileSharing = (props) => { - constructor(props) - { - super(props); + const intl = useIntl(); - this._fileInput = React.createRef(); - } - - handleFileChange = async (event) => + const handleFileChange = async (event) => { if (event.target.files.length > 0) { - this.props.roomClient.shareFiles(event.target.files); + props.roomClient.shareFiles(event.target.files); } }; - render() - { - const { - canShareFiles, - classes - } = this.props; + const { + canShareFiles, + classes + } = props; - const buttonDescription = canShareFiles ? - 'Share file' : 'File sharing not supported'; + const buttonDescription = canShareFiles ? + intl.formatMessage({ + id : 'label.shareFile', + defaultMessage : 'Share file' + }) + : + intl.formatMessage({ + id : 'label.fileSharingUnsupported', + defaultMessage : 'File sharing not supported' + }); - return ( - - - + return ( + + + - - - ); - } -} + + + ); +}; FileSharing.propTypes = { roomClient : PropTypes.any.isRequired, diff --git a/app/src/components/MeetingDrawer/MeetingDrawer.js b/app/src/components/MeetingDrawer/MeetingDrawer.js index 03bc893..0f81cde 100644 --- a/app/src/components/MeetingDrawer/MeetingDrawer.js +++ b/app/src/components/MeetingDrawer/MeetingDrawer.js @@ -3,6 +3,7 @@ import { connect } from 'react-redux'; import PropTypes from 'prop-types'; import { withStyles } from '@material-ui/core/styles'; import * as toolareaActions from '../../actions/toolareaActions'; +import { useIntl } from 'react-intl'; import AppBar from '@material-ui/core/AppBar'; import Tabs from '@material-ui/core/Tabs'; import Tab from '@material-ui/core/Tab'; @@ -44,6 +45,8 @@ const styles = (theme) => const MeetingDrawer = (props) => { + const intl = useIntl(); + const { currentToolTab, unreadMessages, @@ -72,18 +75,29 @@ const MeetingDrawer = (props) => - Chat + {intl.formatMessage({ + id : 'label.chat', + defaultMessage : 'Chat' + })} } /> - File sharing + {intl.formatMessage({ + id : 'label.filesharing', + defaultMessage : 'File sharing' + })} } /> - + {theme.direction === 'ltr' ? : } diff --git a/app/src/components/MeetingDrawer/ParticipantList/ParticipantList.js b/app/src/components/MeetingDrawer/ParticipantList/ParticipantList.js index 484c7ba..0cb9c2b 100644 --- a/app/src/components/MeetingDrawer/ParticipantList/ParticipantList.js +++ b/app/src/components/MeetingDrawer/ParticipantList/ParticipantList.js @@ -8,6 +8,7 @@ import classNames from 'classnames'; import { withStyles } from '@material-ui/core/styles'; import { withRoomContext } from '../../../RoomContext'; import PropTypes from 'prop-types'; +import { FormattedMessage } from 'react-intl'; import ListPeer from './ListPeer'; import ListMe from './ListMe'; import Volume from '../../Containers/Volume'; @@ -84,11 +85,21 @@ class ParticipantList extends React.PureComponent return (
{ this.node = node; }}>
    -
  • Me:
  • +
  • + +
    -
  • Participants in Spotlight:
  • +
  • + +
  • { spotlightPeers.map((peer) => (
    • -
    • Passive Participants:
    • +
    • + +
    • { passivePeers.map((peerId) => (
    • - This website uses cookies to enhance the user experience. + @@ -237,6 +241,7 @@ export default connect( { return ( prev.room === next.room && + prev.settings.advancedMode === next.settings.advancedMode && prev.toolarea.toolAreaOpen === next.toolarea.toolAreaOpen ); } diff --git a/app/src/components/Settings/Settings.js b/app/src/components/Settings/Settings.js index 61de221..99dca97 100644 --- a/app/src/components/Settings/Settings.js +++ b/app/src/components/Settings/Settings.js @@ -6,6 +6,7 @@ import { withRoomContext } from '../../RoomContext'; import * as roomActions from '../../actions/roomActions'; import * as settingsActions from '../../actions/settingsActions'; import PropTypes from 'prop-types'; +import { useIntl, FormattedMessage } from 'react-intl'; import Dialog from '@material-ui/core/Dialog'; import DialogTitle from '@material-ui/core/DialogTitle'; 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 = ({ roomClient, room, @@ -92,6 +64,58 @@ const Settings = ({ 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; if (me.webcamDevices) @@ -115,7 +139,12 @@ const Settings = ({ paper : classes.dialogPaper }} > - Settings + + +
      { 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' + }) } @@ -201,7 +248,10 @@ const Settings = ({ })} - Select your video resolution +
      @@ -214,7 +264,10 @@ const Settings = ({ if (event.target.value) handleChangeMode(event.target.value); }} - name='Room layout' + name={intl.formatMessage({ + id : 'settings.layout', + defaultMessage : 'Room layout' + })} autoWidth className={classes.selectEmpty} > @@ -228,18 +281,27 @@ const Settings = ({ })} - Select room layout + } - label='Advanced mode' + label={intl.formatMessage({ + id : 'settings.advancedMode', + defaultMessage : 'Advanced mode' + })} /> diff --git a/app/src/index.js b/app/src/index.js index 713a8fb..e433357 100644 --- a/app/src/index.js +++ b/app/src/index.js @@ -2,6 +2,7 @@ import domready from 'domready'; import React from 'react'; import { render } from 'react-dom'; import { Provider } from 'react-redux'; +import { createIntl, createIntlCache, RawIntlProvider } from 'react-intl'; import randomString from 'random-string'; import Logger from './Logger'; import debug from 'debug'; @@ -18,8 +19,26 @@ import { persistor, store } from './store'; import { SnackbarProvider } from 'notistack'; import * as serviceWorker from './serviceWorker'; +import messagesEnglish from './translations/en'; +import messagesNorwegian from './translations/nb'; + 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') { debug.enable('* -engine* -socket* -RIE* *WARN* *ERROR*'); @@ -29,7 +48,7 @@ const logger = new Logger(); let roomClient; -RoomClient.init({ store }); +RoomClient.init({ store, intl }); const theme = createMuiTheme(window.config.theme); @@ -92,13 +111,15 @@ function run() render( - } persistor={persistor}> - - - - - - + + } persistor={persistor}> + + + + + + + , document.getElementById('multiparty-meeting') diff --git a/app/src/translations/en.json b/app/src/translations/en.json new file mode 100644 index 0000000..1a7a12b --- /dev/null +++ b/app/src/translations/en.json @@ -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" +} \ No newline at end of file diff --git a/app/src/translations/nb.json b/app/src/translations/nb.json new file mode 100644 index 0000000..0f1527d --- /dev/null +++ b/app/src/translations/nb.json @@ -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" +} \ No newline at end of file