diff --git a/app/features/navbar/components/HelpButton.js b/app/features/navbar/components/HelpButton.js index 8df9a46..9adcbda 100644 --- a/app/features/navbar/components/HelpButton.js +++ b/app/features/navbar/components/HelpButton.js @@ -4,6 +4,7 @@ import Droplist, { Item, Group } from '@atlaskit/droplist'; import HelpIcon from '@atlaskit/icon/glyph/question-circle'; import React, { Component } from 'react'; +import { withTranslation } from 'react-i18next'; import config from '../../config'; import { openExternalLink } from '../../utils'; @@ -20,7 +21,7 @@ type State = { /** * Help button for Navigation Bar. */ -export default class HelpButton extends Component< *, State> { +class HelpButton extends Component<*, State> { /** * Initializes a new {@code HelpButton} instance. * @@ -87,6 +88,8 @@ export default class HelpButton extends Component< *, State> { * @returns {ReactElement} */ render() { + const { t } = this.props; + return ( { onOpenChange = { this._onOpenChange } position = 'right bottom' trigger = { }> - + - Terms + { t('termsLink') } - Privacy + { t('privacyLink') } - Send Feedback + { t('sendFeedbackLink') } - About + { t('aboutLink') } - Source + { t('sourceLink') } - Version: { version } + { t('versionLabel', { version }) } ); } } + +export default withTranslation()(HelpButton); diff --git a/app/features/onboarding/components/AlwaysOnTopWindowSpotlight.js b/app/features/onboarding/components/AlwaysOnTopWindowSpotlight.js deleted file mode 100644 index 53a8f2b..0000000 --- a/app/features/onboarding/components/AlwaysOnTopWindowSpotlight.js +++ /dev/null @@ -1,78 +0,0 @@ -// @flow - -import { Spotlight } from '@atlaskit/onboarding'; - -import React, { Component } from 'react'; -import { connect } from 'react-redux'; -import type { Dispatch } from 'redux'; - -import { closeDrawer } from '../../navbar'; - -import { continueOnboarding } from '../actions'; - -type Props = { - - /** - * Redux dispatch. - */ - dispatch: Dispatch<*>; -}; - -/** - * Always on Top Windows Spotlight Component. - */ -class AlwaysOnTopWindowSpotlight extends Component { - /** - * Initializes a new {@code StartMutedTogglesSpotlight} instance. - * - * @inheritdoc - */ - constructor(props: Props) { - super(props); - - this._next = this._next.bind(this); - } - - /** - * Render function of component. - * - * @returns {ReactElement} - */ - render() { - return ( - - You can toggle whether you want to enable the "always-on-top" window, - which is displayed when the main window loses focus. - This will be applied to all conferences. - - ); - } - - _next: (*) => void; - - /** - * Close the spotlight component. - * - * @returns {void} - */ - _next() { - const { dispatch } = this.props; - - dispatch(continueOnboarding()); - - // FIXME: find a better way to do this. - setTimeout(() => { - dispatch(closeDrawer()); - }, 300); - } -} - -export default connect()(AlwaysOnTopWindowSpotlight); diff --git a/app/features/onboarding/components/ConferenceURLSpotlight.js b/app/features/onboarding/components/ConferenceURLSpotlight.js deleted file mode 100644 index 178e114..0000000 --- a/app/features/onboarding/components/ConferenceURLSpotlight.js +++ /dev/null @@ -1,70 +0,0 @@ -// @flow - -import { Spotlight } from '@atlaskit/onboarding'; - -import React, { Component } from 'react'; -import { connect } from 'react-redux'; -import type { Dispatch } from 'redux'; - -import { continueOnboarding } from '../actions'; - -type Props = { - - /** - * Redux dispatch. - */ - dispatch: Dispatch<*>; -}; - -/** - * Conference URL Spotlight Component. - */ -class ConferenceURLSpotlight extends Component { - /** - * Initializes a new {@code ComponentURLSpotlight} instance. - * - * @inheritdoc - */ - constructor(props: Props) { - super(props); - - this._next = this._next.bind(this); - } - - /** - * Render function of component. - * - * @returns {ReactElement} - */ - render() { - return ( - - Enter the name (or full URL) of the room you want to join. You - may make a name up, just let others know so they enter the same - name. - - ); - } - - _next: (*) => void; - - /** - * Close the spotlight component. - * - * @returns {void} - */ - _next() { - this.props.dispatch(continueOnboarding()); - } -} - -export default connect()(ConferenceURLSpotlight); - diff --git a/app/features/onboarding/components/EmailSettingSpotlight.js b/app/features/onboarding/components/EmailSettingSpotlight.js deleted file mode 100644 index d1bee0e..0000000 --- a/app/features/onboarding/components/EmailSettingSpotlight.js +++ /dev/null @@ -1,68 +0,0 @@ -// @flow - -import { Spotlight } from '@atlaskit/onboarding'; - -import React, { Component } from 'react'; -import { connect } from 'react-redux'; -import type { Dispatch } from 'redux'; - -import { continueOnboarding } from '../actions'; - -type Props = { - - /** - * Redux dispatch. - */ - dispatch: Dispatch<*>; -}; - -/** - * Email Setting Spotlight Component. - */ -class EmailSettingSpotlight extends Component { - /** - * Initializes a new {@code EmailSettingSpotlight} instance. - * - * @inheritdoc - */ - constructor(props: Props) { - super(props); - - this._next = this._next.bind(this); - } - - /** - * Render function of component. - * - * @returns {ReactElement} - */ - render() { - return ( - - The email you enter here will be part of your user profile. - - ); - } - - _next: (*) => void; - - /** - * Close the spotlight component. - * - * @returns {void} - */ - _next() { - this.props.dispatch(continueOnboarding()); - } -} - -export default connect()(EmailSettingSpotlight); - diff --git a/app/features/onboarding/components/NameSettingSpotlight.js b/app/features/onboarding/components/NameSettingSpotlight.js deleted file mode 100644 index cb5ce51..0000000 --- a/app/features/onboarding/components/NameSettingSpotlight.js +++ /dev/null @@ -1,69 +0,0 @@ -// @flow - -import { Spotlight } from '@atlaskit/onboarding'; - -import React, { Component } from 'react'; -import { connect } from 'react-redux'; -import type { Dispatch } from 'redux'; - -import { continueOnboarding } from '../actions'; - -type Props = { - - /** - * Redux dispatch. - */ - dispatch: Dispatch<*>; -}; - -/** - * Name Setting Spotlight Component. - */ -class NameSettingSpotlight extends Component { - /** - * Initializes a new {@code NameSettingSpotlight} instance. - * - * @inheritdoc - */ - constructor(props: Props) { - super(props); - - this._next = this._next.bind(this); - } - - /** - * Render function of component. - * - * @returns {ReactElement} - */ - render() { - return ( - - This will be your display name, others will see you with this - name. - - ); - } - - _next: (*) => void; - - /** - * Close the spotlight component. - * - * @returns {void} - */ - _next() { - this.props.dispatch(continueOnboarding()); - } -} - -export default connect()(NameSettingSpotlight); - diff --git a/app/features/onboarding/components/Onboarding.js b/app/features/onboarding/components/Onboarding.js index 31ea1d4..973beff 100644 --- a/app/features/onboarding/components/Onboarding.js +++ b/app/features/onboarding/components/Onboarding.js @@ -38,9 +38,9 @@ class Onboarding extends Component { const steps = onboardingSteps[section]; if (_activeOnboarding && steps.includes(_activeOnboarding)) { - const ActiveOnboarding = onboardingComponents[_activeOnboarding]; + const { type: ActiveOnboarding, ...props } = onboardingComponents[_activeOnboarding]; - return ; + return ; } return null; diff --git a/app/features/onboarding/components/OnboardingModal.js b/app/features/onboarding/components/OnboardingModal.js index fe38c2d..57f129a 100644 --- a/app/features/onboarding/components/OnboardingModal.js +++ b/app/features/onboarding/components/OnboardingModal.js @@ -3,8 +3,10 @@ import { Modal } from '@atlaskit/onboarding'; import React, { Component } from 'react'; +import { withTranslation } from 'react-i18next'; import { connect } from 'react-redux'; import type { Dispatch } from 'redux'; +import { compose } from 'redux'; import OnboardingModalImage from '../../../images/onboarding.png'; @@ -18,6 +20,11 @@ type Props = { * Redux dispatch. */ dispatch: Dispatch<*>; + + /** + * I18next translation function. + */ + t: Function; }; /** @@ -43,21 +50,23 @@ class OnboardingModal extends Component { * @returns {ReactElement} */ render() { + const { t } = this.props; + return ( -

Let us show you around!

+

{ t('onboarding.letUsShowYouAround') }

); } @@ -86,4 +95,4 @@ class OnboardingModal extends Component { } -export default connect()(OnboardingModal); +export default compose(connect(), withTranslation())(OnboardingModal); diff --git a/app/features/onboarding/components/OnboardingSpotlight.js b/app/features/onboarding/components/OnboardingSpotlight.js new file mode 100644 index 0000000..9eccff2 --- /dev/null +++ b/app/features/onboarding/components/OnboardingSpotlight.js @@ -0,0 +1,68 @@ +// @flow + +import { Spotlight } from '@atlaskit/onboarding'; + +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { connect } from 'react-redux'; +import type { Dispatch } from 'redux'; + +import { continueOnboarding } from '../actions'; + +type Props = { + + /** + * Redux dispatch. + */ + dispatch: Dispatch<*>; + + /** + * Spotlight dialog placement. + */ + dialogPlacement: String; + + /** + * Callback when "next" clicked. + */ + onNext: Function; + + /** + * I18next translation function. + */ + t: Function; + + /** + * Spotlight target. + */ + target: String; + + /** + * Spotlight text. + */ + text: String; + +}; + +const OnboardingSpotlight = (props: Props) => { + const { t } = useTranslation(); + + return ( + { + props.dispatch(continueOnboarding()); + props.onNext && props.onNext(props); + }, + text: t('onboarding.next') + } + ] } + dialogPlacement = { props.dialogPlacement } + target = { props.target } > + { t(props.text) } + + ); +}; + + +export default connect()(OnboardingSpotlight); diff --git a/app/features/onboarding/components/ServerSettingSpotlight.js b/app/features/onboarding/components/ServerSettingSpotlight.js deleted file mode 100644 index 84b4c4c..0000000 --- a/app/features/onboarding/components/ServerSettingSpotlight.js +++ /dev/null @@ -1,69 +0,0 @@ -// @flow - -import { Spotlight } from '@atlaskit/onboarding'; - -import React, { Component } from 'react'; -import { connect } from 'react-redux'; -import type { Dispatch } from 'redux'; - -import { continueOnboarding } from '../actions'; - -type Props = { - - /** - * Redux dispatch. - */ - dispatch: Dispatch<*>; -}; - -/** - * Server Setting Spotlight Component. - */ -class ServerSettingSpotlight extends Component { - /** - * Initializes a new {@code ServerSettingSpotlight} instance. - * - * @inheritdoc - */ - constructor(props: Props) { - super(props); - - this._next = this._next.bind(this); - } - - /** - * Render function of component. - * - * @returns {ReactElement} - */ - render() { - return ( - - This will be the server where your conferences will take place. - You can use your own, but you don't need to! - - ); - } - - _next: (*) => void; - - /** - * Close the spotlight component. - * - * @returns {void} - */ - _next() { - this.props.dispatch(continueOnboarding()); - } -} - -export default connect()(ServerSettingSpotlight); - diff --git a/app/features/onboarding/components/ServerTimeoutSpotlight.js b/app/features/onboarding/components/ServerTimeoutSpotlight.js deleted file mode 100644 index 9a016d9..0000000 --- a/app/features/onboarding/components/ServerTimeoutSpotlight.js +++ /dev/null @@ -1,67 +0,0 @@ -// @flow - -import { Spotlight } from '@atlaskit/onboarding'; - -import React, { Component } from 'react'; -import { connect } from 'react-redux'; -import type { Dispatch } from 'redux'; - -import { continueOnboarding } from '../actions'; - -type Props = { - - /** - * Redux dispatch. - */ - dispatch: Dispatch<*>; -}; - -/** - * Server Setting Spotlight Component. - */ -class ServerTimeoutSpotlight extends Component { - /** - * Initializes a new {@code ServerSettingSpotlight} instance. - * - * @inheritdoc - */ - constructor(props: Props) { - super(props); - - this._next = this._next.bind(this); - } - - /** - * Render function of component. - * - * @returns {ReactElement} - */ - render() { - return ( - - Timeout to join a meeting, if the meeting hasn't been joined before the timeout hits, it's cancelled. - - ); - } - - _next: (*) => void; - - /** - * Close the spotlight component. - * - * @returns {void} - */ - _next() { - this.props.dispatch(continueOnboarding()); - } -} - -export default connect()(ServerTimeoutSpotlight); diff --git a/app/features/onboarding/components/SettingsDrawerSpotlight.js b/app/features/onboarding/components/SettingsDrawerSpotlight.js deleted file mode 100644 index f0bb0e8..0000000 --- a/app/features/onboarding/components/SettingsDrawerSpotlight.js +++ /dev/null @@ -1,68 +0,0 @@ -// @flow - -import { Spotlight } from '@atlaskit/onboarding'; - -import React, { Component } from 'react'; -import { connect } from 'react-redux'; -import type { Dispatch } from 'redux'; - -import { openDrawer } from '../../navbar'; -import { SettingsDrawer } from '../../settings'; - -import { continueOnboarding } from '../actions'; - -type Props = { - - /** - * Redux dispatch. - */ - dispatch: Dispatch<*>; -}; - -/** - * Settings Drawer Spotlight Component. - */ -class SettingsDrawerSpotlight extends Component { - /** - * Initializes a new {@code SettingsDrawerSpotlight} instance. - * - * @inheritdoc - */ - constructor(props: Props) { - super(props); - - this._next = this._next.bind(this); - } - - /** - * Render function of component. - * - * @returns {ReactElement} - */ - render() { - return ( - - Click here to open the settings drawer. - - ); - } - - _next: (*) => void; - - /** - * Close the spotlight component and opens Settings Drawer and shows - * onboarding. - * - * @returns {void} - */ - _next() { - this.props.dispatch(openDrawer(SettingsDrawer)); - this.props.dispatch(continueOnboarding()); - } -} - -export default connect()(SettingsDrawerSpotlight); - diff --git a/app/features/onboarding/components/StartMutedTogglesSpotlight.js b/app/features/onboarding/components/StartMutedTogglesSpotlight.js deleted file mode 100644 index 4b4215f..0000000 --- a/app/features/onboarding/components/StartMutedTogglesSpotlight.js +++ /dev/null @@ -1,69 +0,0 @@ -// @flow - -import { Spotlight } from '@atlaskit/onboarding'; - -import React, { Component } from 'react'; -import { connect } from 'react-redux'; -import type { Dispatch } from 'redux'; - -import { continueOnboarding } from '../actions'; - -type Props = { - - /** - * Redux dispatch. - */ - dispatch: Dispatch<*>; -}; - -/** - * Start Muted Toggles Spotlight Component. - */ -class StartMutedTogglesSpotlight extends Component { - /** - * Initializes a new {@code StartMutedTogglesSpotlight} instance. - * - * @inheritdoc - */ - constructor(props: Props) { - super(props); - - this._next = this._next.bind(this); - } - - /** - * Render function of component. - * - * @returns {ReactElement} - */ - render() { - return ( - - You can toggle if you want to start with your audio or video - muted here. This will be applied to all conferences. - - ); - } - - _next: (*) => void; - - /** - * Close the spotlight component. - * - * @returns {void} - */ - _next() { - this.props.dispatch(continueOnboarding()); - } -} - -export default connect()(StartMutedTogglesSpotlight); - diff --git a/app/features/onboarding/components/index.js b/app/features/onboarding/components/index.js index 70e25e2..9119eba 100644 --- a/app/features/onboarding/components/index.js +++ b/app/features/onboarding/components/index.js @@ -1,10 +1,3 @@ -export { default as ConferenceURLSpotlight } from './ConferenceURLSpotlight'; -export { default as EmailSettingSpotlight } from './EmailSettingSpotlight'; -export { default as NameSettingSpotlight } from './NameSettingSpotlight'; +export { default as OnboardingSpotlight } from './OnboardingSpotlight'; export { default as Onboarding } from './Onboarding'; export { default as OnboardingModal } from './OnboardingModal'; -export { default as ServerSettingSpotlight } from './ServerSettingSpotlight'; -export { default as ServerTimeoutSpotlight } from './ServerTimeoutSpotlight'; -export { default as SettingsDrawerSpotlight } from './SettingsDrawerSpotlight'; -export { default as StartMutedTogglesSpotlight } from './StartMutedTogglesSpotlight'; -export { default as AlwaysOnTopWindowSpotlight } from './AlwaysOnTopWindowSpotlight'; diff --git a/app/features/onboarding/constants.js b/app/features/onboarding/constants.js index c6ac5ca..7b971f3 100644 --- a/app/features/onboarding/constants.js +++ b/app/features/onboarding/constants.js @@ -1,16 +1,7 @@ // @flow - -import { - OnboardingModal, - ConferenceURLSpotlight, - SettingsDrawerSpotlight, - NameSettingSpotlight, - EmailSettingSpotlight, - StartMutedTogglesSpotlight, - ServerSettingSpotlight, - ServerTimeoutSpotlight, - AlwaysOnTopWindowSpotlight -} from './components'; +import { OnboardingModal, OnboardingSpotlight } from './components'; +import { openDrawer, closeDrawer } from '../navbar'; +import { SettingsDrawer } from '../settings'; export const advenaceSettingsSteps = [ 'server-setting', @@ -33,13 +24,57 @@ export const onboardingSteps = { }; export const onboardingComponents = { - 'onboarding-modal': OnboardingModal, - 'conference-url': ConferenceURLSpotlight, - 'settings-drawer-button': SettingsDrawerSpotlight, - 'name-setting': NameSettingSpotlight, - 'email-setting': EmailSettingSpotlight, - 'start-muted-toggles': StartMutedTogglesSpotlight, - 'server-setting': ServerSettingSpotlight, - 'server-timeout': ServerTimeoutSpotlight, - 'always-on-top-window': AlwaysOnTopWindowSpotlight + 'onboarding-modal': { type: OnboardingModal }, + 'conference-url': { + type: OnboardingSpotlight, + dialogPlacement: 'bottom center', + target: 'conference-url', + text: 'onboarding.conferenceUrl' + }, + 'settings-drawer-button': { + type: OnboardingSpotlight, + dialogPlacement: 'top right', + target: 'settings-drawer-button', + text: 'onboarding.settingsDrawerButton', + onNext: (props: OnboardingSpotlight.props) => props.dispatch(openDrawer(SettingsDrawer)) + }, + 'name-setting': { + type: OnboardingSpotlight, + dialogPlacement: 'top right', + target: 'name-setting', + text: 'onboarding.nameSetting' + }, + 'email-setting': { + type: OnboardingSpotlight, + dialogPlacement: 'top right', + target: 'email-setting', + text: 'onboarding.emailSetting' + }, + 'start-muted-toggles': { + type: OnboardingSpotlight, + dialogPlacement: 'top right', + target: 'start-muted-toggles', + text: 'onboarding.startMutedToggles' + }, + 'server-setting': { + type: OnboardingSpotlight, + dialogPlacement: 'top right', + target: 'server-setting', + text: 'onboarding.serverSetting' + }, + 'server-timeout': { + type: OnboardingSpotlight, + dialogPlacement: 'top right', + target: 'server-timeout', + text: 'onboarding.serverTimeout' + }, + 'always-on-top-window': { + type: OnboardingSpotlight, + dialogPlacement: 'top right', + target: 'always-on-top-window', + text: 'onboarding.alwaysOnTop', + onNext: (props: OnboardingSpotlight.props) => setTimeout(() => { + props.dispatch(closeDrawer()); + }, 300) + } }; diff --git a/app/features/settings/components/AlwaysOnTopWindowToggle.js b/app/features/settings/components/AlwaysOnTopWindowToggle.js deleted file mode 100644 index dd77e60..0000000 --- a/app/features/settings/components/AlwaysOnTopWindowToggle.js +++ /dev/null @@ -1,84 +0,0 @@ -// @flow - -import React, { Component } from 'react'; -import { connect } from 'react-redux'; -import type { Dispatch } from 'redux'; - -import { setWindowAlwaysOnTop } from '../actions'; - -import ToggleWithLabel from './ToggleWithLabel'; - -type Props = { - - /** - * Redux dispatch. - */ - dispatch: Dispatch<*>; - - /** - * Window Always on Top value in (redux) state. - */ - _alwaysOnTopWindowEnabled: boolean; -}; - -/** - * Window always open on top placed in Settings Drawer. - */ -class AlwaysOnTopWindowToggle extends Component { - /** - * Initializes a new {@code AlwaysOnTopWindowToggle} instance. - * - * @inheritdoc - */ - constructor(props) { - super(props); - - this._onAlwaysOnTopWindowToggleChange - = this._onAlwaysOnTopWindowToggleChange.bind(this); - } - - /** - * Render function of component. - * - * @returns {ReactElement} - */ - render() { - return ( - - ); - } - - _onAlwaysOnTopWindowToggleChange: (*) => void; - - /** - * Toggles alwaysOnTopWindowEnabled. - * - * @returns {void} - */ - _onAlwaysOnTopWindowToggleChange() { - const { _alwaysOnTopWindowEnabled } = this.props; - const newState = !_alwaysOnTopWindowEnabled; - - this.props.dispatch(setWindowAlwaysOnTop(newState)); - } -} - -/** - * Maps (parts of) the redux state to the React props. - * - * @param {Object} state - The redux state. - * @returns {{ - * _alwaysOnTopWindowEnabled: boolean, - * }} - */ -function _mapStateToProps(state: Object) { - return { - _alwaysOnTopWindowEnabled: state.settings.alwaysOnTopWindowEnabled - }; -} - -export default connect(_mapStateToProps)(AlwaysOnTopWindowToggle); diff --git a/app/features/settings/components/ServerTimeoutField.js b/app/features/settings/components/ServerTimeoutField.js index 742f49f..3e2d50a 100644 --- a/app/features/settings/components/ServerTimeoutField.js +++ b/app/features/settings/components/ServerTimeoutField.js @@ -3,7 +3,9 @@ import { FieldTextStateless } from '@atlaskit/field-text'; import React, { Component } from 'react'; +import { withTranslation } from 'react-i18next'; import { connect } from 'react-redux'; +import { compose } from 'redux'; import type { Dispatch } from 'redux'; import config from '../../config'; @@ -22,6 +24,11 @@ type Props = { * Default Jitsi Meet Server Timeout in (redux) store. */ _serverTimeout: number; + + /** + * I18next translation function. + */ + t: Function; }; type State = { @@ -64,6 +71,8 @@ class ServerTimeoutField extends Component { * @returns {ReactElement} */ render() { + const { t } = this.props; + return (
{ = { 'Invalid Timeout' } isInvalid = { !this.state.isValid } isValidationHidden = { this.state.isValid } - label = 'Server Timeout (in seconds)' + label = { t('settings.serverTimeout') } onBlur = { this._onServerTimeoutSubmit } onChange = { this._onServerTimeoutChange } placeholder = { config.defaultServerTimeout } @@ -138,4 +147,4 @@ function _mapStateToProps(state: Object) { }; } -export default connect(_mapStateToProps)(ServerTimeoutField); +export default compose(connect(_mapStateToProps), withTranslation())(ServerTimeoutField); diff --git a/app/features/settings/components/ServerURLField.js b/app/features/settings/components/ServerURLField.js index b85ba4a..09ae945 100644 --- a/app/features/settings/components/ServerURLField.js +++ b/app/features/settings/components/ServerURLField.js @@ -3,8 +3,10 @@ import { FieldTextStateless } from '@atlaskit/field-text'; import React, { Component } from 'react'; +import { withTranslation } from 'react-i18next'; import { connect } from 'react-redux'; import type { Dispatch } from 'redux'; +import { compose } from 'redux'; import config from '../../config'; import { getExternalApiURL } from '../../utils'; @@ -23,6 +25,11 @@ type Props = { * Default Jitsi Meet Server URL in (redux) store. */ _serverURL: string; + + /** + * I18next translation function. + */ + t: Function; }; type State = { @@ -65,14 +72,15 @@ class ServerURLField extends Component { * @returns {ReactElement} */ render() { + const { t } = this.props; + return ( ; + + /** + * The label for the toggle. + */ + label: String; + + /** + * The name of the setting. + */ + settingName: String; + + /** + * A function to produce setting change events. + */ + settingChangeEvent: Function; + +}; + +/** + * Maps (parts of) the redux state to the React props. + * + * @param {Object} state - The redux state. + * @param {Object} ownProps - The props of the redux wrapper component. + * @returns {Object} A props object including the current value of the setting. + */ +const mapStateToProps = (state, ownProps: Props) => { + return { + value: state.settings[ownProps.settingName], + ...ownProps + }; +}; + +/** + * A component to control a single boolean redux setting. + * + * @param {Object} props - The props provided by mapStateToProps. + * @returns {Object} A rendered toggle component with correct state. + */ +function SettingToggle(props: Object) { + const onChange = useCallback( + () => props.dispatch(props.settingChangeEvent(!props.value))); + + return ( + + ); +} + +export default connect(mapStateToProps)(SettingToggle); diff --git a/app/features/settings/components/SettingsDrawer.js b/app/features/settings/components/SettingsDrawer.js index 970963d..985123d 100644 --- a/app/features/settings/components/SettingsDrawer.js +++ b/app/features/settings/components/SettingsDrawer.js @@ -7,18 +7,22 @@ import { SpotlightTarget } from '@atlaskit/onboarding'; import Panel from '@atlaskit/panel'; import React, { Component } from 'react'; +import { withTranslation } from 'react-i18next'; import { connect } from 'react-redux'; import type { Dispatch } from 'redux'; +import { compose } from 'redux'; import { closeDrawer, DrawerContainer, Logo } from '../../navbar'; import { Onboarding, advenaceSettingsSteps, startOnboarding } from '../../onboarding'; import { Form, SettingsContainer, TogglesContainer } from '../styled'; -import { setEmail, setName } from '../actions'; +import { + setEmail, setName, setWindowAlwaysOnTop, + setStartWithAudioMuted, setStartWithVideoMuted +} from '../actions'; -import AlwaysOnTopWindowToggle from './AlwaysOnTopWindowToggle'; +import SettingToggle from './SettingToggle'; import ServerURLField from './ServerURLField'; import ServerTimeoutField from './ServerTimeoutField'; -import StartMutedToggles from './StartMutedToggles'; type Props = { @@ -46,6 +50,11 @@ type Props = { * Name of the user. */ _name: string; + + /** + * I18next translation function. + */ + t: Function; }; /** @@ -92,9 +101,11 @@ class SettingsDrawer extends Component { * @returns {ReactElement} */ render() { + const { t } = this.props; + return ( } + backIcon = { } isOpen = { this.props.isOpen } onBackButton = { this._onBackButton } primaryIcon = { } > @@ -104,7 +115,7 @@ class SettingsDrawer extends Component { name = 'name-setting'> { name = 'email-setting'> { - + + @@ -140,7 +158,10 @@ class SettingsDrawer extends Component { - + @@ -236,4 +257,4 @@ function _mapStateToProps(state: Object) { }; } -export default connect(_mapStateToProps)(SettingsDrawer); +export default compose(connect(_mapStateToProps), withTranslation())(SettingsDrawer); diff --git a/app/features/settings/components/StartMutedToggles.js b/app/features/settings/components/StartMutedToggles.js deleted file mode 100644 index 3cee823..0000000 --- a/app/features/settings/components/StartMutedToggles.js +++ /dev/null @@ -1,145 +0,0 @@ -// @flow - -import React, { Component } from 'react'; -import { connect } from 'react-redux'; -import type { Dispatch } from 'redux'; - -import { - setStartWithAudioMuted, - setStartWithVideoMuted -} from '../actions'; - -import ToggleWithLabel from './ToggleWithLabel'; - -type Props = { - - /** - * Redux dispatch. - */ - dispatch: Dispatch<*>; - - /** - * Start with Audio Muted value in (redux) state. - */ - _startWithAudioMuted: boolean; - - /** - * Start with Video Muted value in (redux) state. - */ - _startWithVideoMuted: boolean; -}; - -type State = { - - /** - * Start with Audio Muted value in (local) state. - */ - startWithAudioMuted: boolean; - - /** - * Start with Video Muted value in (local) state. - */ - startWithVideoMuted: boolean; -}; - -/** - * Start Muted toggles for audio and video placed in Settings Drawer. - */ -class StartMutedToggles extends Component { - /** - * Initializes a new {@code StartMutedToggles} instance. - * - * @inheritdoc - */ - constructor(props) { - super(props); - - this.state = { - startWithAudioMuted: false, - startWithVideoMuted: false - }; - - this._onAudioToggleChange = this._onAudioToggleChange.bind(this); - this._onVideoToggleChange = this._onVideoToggleChange.bind(this); - } - - /** - * This updates the startWithAudioMuted and startWithVideoMuted in (local) - * state when there is a change in redux store. - * - * @param {Props} props - New props of the component. - * @returns {State} - New state of the component. - */ - static getDerivedStateFromProps(props) { - return { - startWithAudioMuted: props._startWithAudioMuted, - startWithVideoMuted: props._startWithVideoMuted - }; - } - - /** - * Render function of component. - * - * @returns {ReactElement} - */ - render() { - return ( - <> - - - - ); - } - - _onAudioToggleChange: (*) => void; - - /** - * Toggles startWithAudioMuted. - * - * @returns {void} - */ - _onAudioToggleChange() { - const { startWithAudioMuted } = this.state; - - this.props.dispatch(setStartWithAudioMuted(!startWithAudioMuted)); - } - - _onVideoToggleChange: (*) => void; - - /** - * Toggles startWithVideoMuted. - * - * @returns {void} - */ - _onVideoToggleChange() { - const { startWithVideoMuted } = this.state; - - this.props.dispatch(setStartWithVideoMuted(!startWithVideoMuted)); - } -} - -/** - * Maps (parts of) the redux state to the React props. - * - * @param {Object} state - The redux state. - * @returns {{ - * _startWithAudioMuted: boolean, - * _startWithVideoMuted: boolean - * }} - */ -function _mapStateToProps(state: Object) { - return { - _startWithAudioMuted: state.settings.startWithAudioMuted, - _startWithVideoMuted: state.settings.startWithVideoMuted - }; -} - -export default connect(_mapStateToProps)(StartMutedToggles); diff --git a/app/features/welcome/components/Welcome.js b/app/features/welcome/components/Welcome.js index f0bb138..7a7b7a4 100644 --- a/app/features/welcome/components/Welcome.js +++ b/app/features/welcome/components/Welcome.js @@ -8,6 +8,8 @@ import { AtlasKitThemeProvider } from '@atlaskit/theme'; import { generateRoomWithoutSeparator } from 'js-utils/random'; import React, { Component } from 'react'; +import { withTranslation } from 'react-i18next'; +import { compose } from 'redux'; import type { Dispatch } from 'redux'; import { connect } from 'react-redux'; import { push } from 'react-router-redux'; @@ -19,7 +21,6 @@ import { createConferenceObjectFromURL } from '../../utils'; import { Body, FieldWrapper, Form, Header, Label, Wrapper } from '../styled'; - type Props = { /** @@ -31,6 +32,11 @@ type Props = { * React Router location object. */ location: Object; + + /** + * I18next translate function. + */ + t: Function; }; type State = { @@ -252,12 +258,13 @@ class Welcome extends Component { _renderHeader() { const locationState = this.props.location.state; const locationError = locationState && locationState.error; + const { t } = this.props; return (
- + { appearance = 'primary' onClick = { this._onJoin } type = 'button'> - GO + { t('go') } @@ -306,4 +313,4 @@ class Welcome extends Component { } } -export default connect()(Welcome); +export default compose(connect(), withTranslation())(Welcome); diff --git a/app/i18n/index.js b/app/i18n/index.js new file mode 100644 index 0000000..4f08390 --- /dev/null +++ b/app/i18n/index.js @@ -0,0 +1,24 @@ +import i18n from 'i18next'; +import { initReactI18next } from 'react-i18next'; +import moment from 'moment'; + +const languages = { + en: { translation: require('./lang/en.json') } +}; + +const detectedLocale = window.jitsiNodeAPI.getLocale(); + +i18n + .use(initReactI18next) + .init({ + resources: languages, + lng: detectedLocale, + fallbackLng: 'en', + interpolation: { + escapeValue: false // not needed for react as it escapes by default + } + }); + +moment.locale(detectedLocale); + +export default i18n; diff --git a/app/i18n/lang/en.json b/app/i18n/lang/en.json new file mode 100644 index 0000000..08a3632 --- /dev/null +++ b/app/i18n/lang/en.json @@ -0,0 +1,38 @@ +{ + "enterConferenceNameOrUrl": "Enter a name for your conference or a Jitsi URL", + "go": "GO", + "help": "Help", + "termsLink": "Terms", + "privacyLink": "Privacy", + "sendFeedbackLink": "Send Feedback", + "aboutLink": "About", + "sourceLink": "Source Code", + "versionLabel": "Version: {{version}}", + "onboarding": { + "startTour": "Start Tour", + "skip": "Skip", + "welcome": "Welcome to {{appName}}", + "letUsShowYouAround": "Let us show you around!", + "next": "Next", + "conferenceUrl": "Enter the name (or full URL) of the room you want to join. You may make a name up, just let others know so they enter the same name.", + "settingsDrawerButton": "Click here to open the settings drawer.", + "nameSetting": "This will be your display name, others will see you with this name.", + "emailSetting": "The email you enter here will be part of your user profile.", + "startMutedToggles": "You can toggle if you want to start with your audio or video muted here. This will be applied to all conferences.", + "serverSetting": "This will be the server where your conferences will take place. You can use your own, but you don't need to!", + "serverTimeout": "Timeout to join a meeting, if the meeting hasn't been joined before the timeout hits, it's cancelled.", + "alwaysOnTop": "You can toggle whether you want to enable the \"always-on-top\" window, which is displayed when the main window loses focus. This will be applied to all conferences." + }, + "settings": { + "back": "Back", + "name": "Name", + "email": "Email", + "advancedSettings": "Advanced Settings", + "alwaysOnTopWindow": "Always on Top Window", + "startWithAudioMuted": "Start with Audio muted", + "startWithVideoMuted": "Start with Video muted", + "invalidServer": "Invalid Server URL or external API not enabled", + "serverUrl": "Server URL", + "serverTimeout": "Server Timeout (in seconds)" + } +} diff --git a/app/index.html b/app/index.html index 405d3ac..68706f4 100644 --- a/app/index.html +++ b/app/index.html @@ -1,6 +1,7 @@ +