diff options
Diffstat (limited to 'web/react/components/user_settings')
14 files changed, 1199 insertions, 234 deletions
diff --git a/web/react/components/user_settings/custom_theme_chooser.jsx b/web/react/components/user_settings/custom_theme_chooser.jsx index 8ec3863f3..9116dd938 100644 --- a/web/react/components/user_settings/custom_theme_chooser.jsx +++ b/web/react/components/user_settings/custom_theme_chooser.jsx @@ -6,7 +6,96 @@ import Constants from '../../utils/constants.jsx'; const OverlayTrigger = ReactBootstrap.OverlayTrigger; const Popover = ReactBootstrap.Popover; -export default class CustomThemeChooser extends React.Component { +import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl'; + +const messages = defineMessages({ + sidebarBg: { + id: 'user.settings.custom_theme.sidebarBg', + defaultMessage: 'Sidebar BG' + }, + sidebarText: { + id: 'user.settings.custom_theme.sidebarText', + defaultMessage: 'Sidebar Text' + }, + sidebarHeaderBg: { + id: 'user.settings.custom_theme.sidebarHeaderBg', + defaultMessage: 'Sidebar Header BG' + }, + sidebarHeaderTextColor: { + id: 'user.settings.custom_theme.sidebarHeaderTextColor', + defaultMessage: 'Sidebar Header Text' + }, + sidebarUnreadText: { + id: 'user.settings.custom_theme.sidebarUnreadText', + defaultMessage: 'Sidebar Unread Text' + }, + sidebarTextHoverBg: { + id: 'user.settings.custom_theme.sidebarTextHoverBg', + defaultMessage: 'Sidebar Text Hover BG' + }, + sidebarTextActiveBorder: { + id: 'user.settings.custom_theme.sidebarTextActiveBorder', + defaultMessage: 'Sidebar Text Active Border' + }, + sidebarTextActiveColor: { + id: 'user.settings.custom_theme.sidebarTextActiveColor', + defaultMessage: 'Sidebar Text Active Color' + }, + onlineIndicator: { + id: 'user.settings.custom_theme.onlineIndicator', + defaultMessage: 'Online Indicator' + }, + awayIndicator: { + id: 'user.settings.custom_theme.awayIndicator', + defaultMessage: 'Away Indicator' + }, + mentionBj: { + id: 'user.settings.custom_theme.mentionBj', + defaultMessage: 'Mention Jewel BG' + }, + mentionColor: { + id: 'user.settings.custom_theme.mentionColor', + defaultMessage: 'Mention Jewel Text' + }, + centerChannelBg: { + id: 'user.settings.custom_theme.centerChannelBg', + defaultMessage: 'Center Channel BG' + }, + centerChannelColor: { + id: 'user.settings.custom_theme.centerChannelColor', + defaultMessage: 'Center Channel Text' + }, + newMessageSeparator: { + id: 'user.settings.custom_theme.newMessageSeparator', + defaultMessage: 'New Message Separator' + }, + linkColor: { + id: 'user.settings.custom_theme.linkColor', + defaultMessage: 'Link Color' + }, + buttonBg: { + id: 'user.settings.custom_theme.buttonBg', + defaultMessage: 'Button BG' + }, + buttonColor: { + id: 'user.settings.custom_theme.buttonColor', + defaultMessage: 'Button Text' + }, + mentionHighlightBg: { + id: 'user.settings.custom_theme.mentionHighlightBg', + defaultMessage: 'Mention Highlight BG' + }, + mentionHighlightLink: { + id: 'user.settings.custom_theme.mentionHighlightLink', + defaultMessage: 'Mention Highlight Link' + }, + codeTheme: { + id: 'user.settings.custom_theme.codeTheme', + defaultMessage: 'Code Theme' + } +}); + +class CustomThemeChooser extends React.Component { constructor(props) { super(props); @@ -65,6 +154,7 @@ export default class CustomThemeChooser extends React.Component { this.props.updateTheme(theme); } render() { + const {formatMessage} = this.props.intl; const theme = this.props.theme; const elements = []; @@ -102,7 +192,7 @@ export default class CustomThemeChooser extends React.Component { className='col-sm-4 form-group' key={'custom-theme-key' + index} > - <label className='custom-label'>{element.uiName}</label> + <label className='custom-label'>{formatMessage(messages[element.id])}</label> <div className='input-group theme-group group--code dropdown' id={element.id} @@ -135,7 +225,7 @@ export default class CustomThemeChooser extends React.Component { className='col-sm-4 form-group' key={'custom-theme-key' + index} > - <label className='custom-label'>{element.uiName}</label> + <label className='custom-label'>{formatMessage(messages[element.id])}</label> <div className='input-group color-picker' id={element.id} @@ -160,7 +250,10 @@ export default class CustomThemeChooser extends React.Component { const pasteBox = ( <div className='col-sm-12'> <label className='custom-label'> - {'Copy and paste to share theme colors:'} + <FormattedMessage + id='user.settings.custom_theme.copyPaste' + defaultMessage='Copy and paste to share theme colors:' + /> </label> <input type='text' @@ -185,6 +278,9 @@ export default class CustomThemeChooser extends React.Component { } CustomThemeChooser.propTypes = { + intl: intlShape.isRequired, theme: React.PropTypes.object.isRequired, updateTheme: React.PropTypes.func.isRequired }; + +export default injectIntl(CustomThemeChooser);
\ No newline at end of file diff --git a/web/react/components/user_settings/import_theme_modal.jsx b/web/react/components/user_settings/import_theme_modal.jsx index 45b05f19b..66bed0b0b 100644 --- a/web/react/components/user_settings/import_theme_modal.jsx +++ b/web/react/components/user_settings/import_theme_modal.jsx @@ -9,9 +9,19 @@ const Modal = ReactBootstrap.Modal; import AppDispatcher from '../../dispatcher/app_dispatcher.jsx'; import Constants from '../../utils/constants.jsx'; + +import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl'; + +const holders = defineMessages({ + submitError: { + id: 'user.settings.import_theme.submitError', + defaultMessage: 'Invalid format, please try copying and pasting in again.' + } +}); + const ActionTypes = Constants.ActionTypes; -export default class ImportThemeModal extends React.Component { +class ImportThemeModal extends React.Component { constructor(props) { super(props); @@ -39,7 +49,7 @@ export default class ImportThemeModal extends React.Component { const text = ReactDOM.findDOMNode(this.refs.input).value; if (!this.isInputValid(text)) { - this.setState({inputError: 'Invalid format, please try copying and pasting in again.'}); + this.setState({inputError: this.props.intl.formatMessage(holders.submitError)}); return; } @@ -125,7 +135,7 @@ export default class ImportThemeModal extends React.Component { if (this.isInputValid(e.target.value)) { this.setState({inputError: null}); } else { - this.setState({inputError: 'Invalid format, please try copying and pasting in again.'}); + this.setState({inputError: this.props.intl.formatMessage(holders.submitError)}); } } render() { @@ -136,7 +146,12 @@ export default class ImportThemeModal extends React.Component { onHide={() => this.setState({show: false})} > <Modal.Header closeButton={true}> - <Modal.Title>{'Import Slack Theme'}</Modal.Title> + <Modal.Title> + <FormattedMessage + id='user.settings.import_theme.importHeader' + defaultMessage='Import Slack Theme' + /> + </Modal.Title> </Modal.Header> <form role='form' @@ -144,7 +159,10 @@ export default class ImportThemeModal extends React.Component { > <Modal.Body> <p> - {'To import a theme, go to a Slack team and look for “Preferences -> Sidebar Theme”. Open the custom theme option, copy the theme color values and paste them here:'} + <FormattedMessage + id='user.settings.import_theme.importBody' + defaultMessage='To import a theme, go to a Slack team and look for “Preferences -> Sidebar Theme”. Open the custom theme option, copy the theme color values and paste them here:' + /> </p> <div className='form-group less'> <div className='col-sm-9'> @@ -166,7 +184,10 @@ export default class ImportThemeModal extends React.Component { className='btn btn-default' onClick={() => this.setState({show: false})} > - {'Cancel'} + <FormattedMessage + id='user.settings.import_theme.cancel' + defaultMessage='Cancel' + /> </button> <button onClick={this.handleSubmit} @@ -174,7 +195,10 @@ export default class ImportThemeModal extends React.Component { className='btn btn-primary' tabIndex='3' > - {'Submit'} + <FormattedMessage + id='user.settings.import_theme.submit' + defaultMessage='Submit' + /> </button> </Modal.Footer> </form> @@ -183,3 +207,9 @@ export default class ImportThemeModal extends React.Component { ); } } + +ImportThemeModal.propTypes = { + intl: intlShape.isRequired +}; + +export default injectIntl(ImportThemeModal);
\ No newline at end of file diff --git a/web/react/components/user_settings/manage_incoming_hooks.jsx b/web/react/components/user_settings/manage_incoming_hooks.jsx index 1506e3c98..c6532b018 100644 --- a/web/react/components/user_settings/manage_incoming_hooks.jsx +++ b/web/react/components/user_settings/manage_incoming_hooks.jsx @@ -7,6 +7,8 @@ import Constants from '../../utils/constants.jsx'; import ChannelStore from '../../stores/channel_store.jsx'; import LoadingScreen from '../loading_screen.jsx'; +import {FormattedMessage, FormattedHTMLMessage} from 'mm-intl'; + export default class ManageIncomingHooks extends React.Component { constructor() { super(); @@ -126,7 +128,12 @@ export default class ManageIncomingHooks extends React.Component { <span className='word-break--all'>{Utils.getWindowLocationOrigin() + '/hooks/' + hook.id}</span> </div> <div className='padding-top'> - <strong>{'Channel: '}</strong>{c.display_name} + <strong> + <FormattedMessage + id='user.settings.hooks_in.channel' + defaultMessage='Channel: ' + /> + </strong>{c.display_name} </div> <a className={'webhook__remove'} @@ -147,12 +154,24 @@ export default class ManageIncomingHooks extends React.Component { } else if (hooks.length > 0) { displayHooks = hooks; } else { - displayHooks = <div className='padding-top x2'>{'None'}</div>; + displayHooks = ( + <div className='padding-top x2'> + <FormattedMessage + id='user.settings.hooks_in.none' + defaultMessage='None' + /> + </div> + ); } const existingHooks = ( <div className='webhooks__container'> - <label className='control-label padding-top x2'>{'Existing incoming webhooks'}</label> + <label className='control-label padding-top x2'> + <FormattedMessage + id='user.settings.hooks_in.existing' + defaultMessage='Existing incoming webhooks' + /> + </label> <div className='padding-top divider-light'></div> <div className='webhooks__list'> {displayHooks} @@ -162,15 +181,16 @@ export default class ManageIncomingHooks extends React.Component { return ( <div key='addIncomingHook'> - {'Create webhook URLs for use in external integrations. Please see '} - <a - href='http://mattermost.org/webhooks' - target='_blank' - > - {'http://mattermost.org/webhooks'} - </a> - {' to learn more.'} - <div><label className='control-label padding-top x2'>{'Add a new incoming webhook'}</label></div> + <FormattedHTMLMessage + id='user.settings.hooks_in.description' + defaultMessage='Create webhook URLs for use in external integrations. Please see<a href="http://mattermost.org/webhooks" target="_blank">http://mattermost.org/webhooks</a> to learn more.' + /> + <div><label className='control-label padding-top x2'> + <FormattedMessage + id='user.settings.hooks_in.addTitle' + defaultMessage='Add a new incoming webhook' + /> + </label></div> <div className='row padding-top'> <div className='col-sm-10 padding-bottom'> <select @@ -189,7 +209,10 @@ export default class ManageIncomingHooks extends React.Component { href='#' onClick={this.addNewHook} > - {'Add'} + <FormattedMessage + id='user.settings.hooks_in.add' + defaultMessage='Add' + /> </a> </div> </div> diff --git a/web/react/components/user_settings/manage_languages.jsx b/web/react/components/user_settings/manage_languages.jsx index 123165b76..fee6d9da2 100644 --- a/web/react/components/user_settings/manage_languages.jsx +++ b/web/react/components/user_settings/manage_languages.jsx @@ -4,6 +4,8 @@ import * as Client from '../../utils/client.jsx'; import * as Utils from '../../utils/utils.jsx'; +import {FormattedMessage} from 'mm-intl'; + export default class ManageLanguage extends React.Component { constructor(props) { super(props); @@ -70,7 +72,12 @@ export default class ManageLanguage extends React.Component { return ( <div key='changeLanguage'> <br/> - <label className='control-label'>{'Change interface language'}</label> + <label className='control-label'> + <FormattedMessage + id='user.settings.languages.change' + defaultMessage='Change interface language' + /> + </label> <div className='padding-top'> <select ref='language' @@ -87,7 +94,10 @@ export default class ManageLanguage extends React.Component { href='#' onClick={this.changeLanguage} > - {'Set language'} + <FormattedMessage + id='user.settings.languages' + defaultMessage='Set language' + /> </a> </div> </div> diff --git a/web/react/components/user_settings/manage_outgoing_hooks.jsx b/web/react/components/user_settings/manage_outgoing_hooks.jsx index 17acf0f10..3f88e9f41 100644 --- a/web/react/components/user_settings/manage_outgoing_hooks.jsx +++ b/web/react/components/user_settings/manage_outgoing_hooks.jsx @@ -8,7 +8,20 @@ import ChannelStore from '../../stores/channel_store.jsx'; import * as Client from '../../utils/client.jsx'; import Constants from '../../utils/constants.jsx'; -export default class ManageOutgoingHooks extends React.Component { +import {intlShape, injectIntl, defineMessages, FormattedMessage, FormattedHTMLMessage} from 'mm-intl'; + +const holders = defineMessages({ + optional: { + id: 'user.settings.hooks_out.optional', + defaultMessage: 'Optional if channel selected' + }, + callbackHolder: { + id: 'user.settings.hooks_out.callbackHolder', + defaultMessage: 'Each URL must start with http:// or https://' + } +}); + +class ManageOutgoingHooks extends React.Component { constructor() { super(); @@ -140,7 +153,10 @@ export default class ManageOutgoingHooks extends React.Component { key='select-channel' value='' > - {'--- Select a channel ---'} + <FormattedMessage + id='user.settings.hooks_out.select' + defaultMessage='--- Select a channel ---' + /> </option> ); @@ -169,7 +185,12 @@ export default class ManageOutgoingHooks extends React.Component { if (c) { channelDiv = ( <div className='padding-top'> - <strong>{'Channel: '}</strong>{c.display_name} + <strong> + <FormattedMessage + id='user.settings.hooks_out.channel' + defaultMessage='Channel: ' + /> + </strong>{c.display_name} </div> ); } @@ -178,7 +199,12 @@ export default class ManageOutgoingHooks extends React.Component { if (hook.trigger_words && hook.trigger_words.length !== 0) { triggerDiv = ( <div className='padding-top'> - <strong>{'Trigger Words: '}</strong>{hook.trigger_words.join(', ')} + <strong> + <FormattedMessage + id='user.settings.hooks_out.trigger' + defaultMessage='Trigger Words: ' + /> + </strong>{hook.trigger_words.join(', ')} </div> ); } @@ -202,7 +228,10 @@ export default class ManageOutgoingHooks extends React.Component { href='#' onClick={this.regenToken.bind(this, hook.id)} > - {'Regen Token'} + <FormattedMessage + id='user.settings.hooks_out.regen' + defaultMessage='Regen Token' + /> </a> <a className='webhook__remove' @@ -223,12 +252,24 @@ export default class ManageOutgoingHooks extends React.Component { } else if (hooks.length > 0) { displayHooks = hooks; } else { - displayHooks = <div className='padding-top x2'>{'None'}</div>; + displayHooks = ( + <div className='padding-top x2'> + <FormattedMessage + id='user.settings.hooks_out.none' + defaultMessage='None' + /> + </div> + ); } const existingHooks = ( <div className='webhooks__container'> - <label className='control-label padding-top x2'>{'Existing outgoing webhooks'}</label> + <label className='control-label padding-top x2'> + <FormattedMessage + id='user.settings.hooks_out.existing' + defaultMessage='Existing outgoing webhooks' + /> + </label> <div className='padding-top divider-light'></div> <div className='webhooks__list'> {displayHooks} @@ -240,19 +281,25 @@ export default class ManageOutgoingHooks extends React.Component { return ( <div key='addOutgoingHook'> - {'Create webhooks to send new message events to an external integration. Please see '} - <a - href='http://mattermost.org/webhooks' - target='_blank' - > - {'http://mattermost.org/webhooks'} - </a> - {' to learn more.'} - <div><label className='control-label padding-top x2'>{'Add a new outgoing webhook'}</label></div> + <FormattedHTMLMessage + id='user.settings.hooks_out.addDescription' + defaultMessage='Create webhooks to send new message events to an external integration. Please see <a href="http://mattermost.org/webhooks">http://mattermost.org/webhooks</a> to learn more.' + /> + <div><label className='control-label padding-top x2'> + <FormattedMessage + id='user.settings.hooks_out.addTitle' + defaultMessage='Add a new outgoing webhook' + /> + </label></div> <div className='padding-top divider-light'></div> <div className='padding-top'> <div> - <label className='control-label'>{'Channel'}</label> + <label className='control-label'> + <FormattedMessage + id='user.settings.hooks_out.channel' + defaultMessage='Channel: ' + /> + </label> <div className='padding-top'> <select ref='channelName' @@ -263,23 +310,43 @@ export default class ManageOutgoingHooks extends React.Component { {options} </select> </div> - <div className='padding-top'>{'Only public channels can be used'}</div> + <div className='padding-top'> + <FormattedMessage + id='user.settings.hooks_out.only' + defaultMessage='Only public channels can be used' + /> + </div> </div> <div className='padding-top x2'> - <label className='control-label'>{'Trigger Words:'}</label> + <label className='control-label'> + <FormattedMessage + id='user.settings.hooks_out.trigger' + defaultMessage='Trigger Words: ' + /> + </label> <div className='padding-top'> <input ref='triggerWords' className='form-control' value={this.state.triggerWords} onChange={this.updateTriggerWords} - placeholder='Optional if channel selected' + placeholder={this.props.intl.formatMessage(holders.optional)} + /> + </div> + <div className='padding-top'> + <FormattedMessage + id='user.settings.hooks_out.comma' + defaultMessage='Comma separated words to trigger on' /> </div> - <div className='padding-top'>{'Comma separated words to trigger on'}</div> </div> <div className='padding-top x2'> - <label className='control-label'>{'Callback URLs:'}</label> + <label className='control-label'> + <FormattedMessage + id='user.settings.hooks_out.callback' + defaultMessage='Callback URLs: ' + /> + </label> <div className='padding-top'> <textarea ref='callbackURLs' @@ -288,10 +355,15 @@ export default class ManageOutgoingHooks extends React.Component { resize={false} rows={3} onChange={this.updateCallbackURLs} - placeholder='Each URL must start with http:// or https://' + placeholder={this.props.intl.formatMessage(holders.callbackHolder)} /> </div> - <div className='padding-top'>{'New line separated URLs that will receive the HTTP POST event'}</div> + <div className='padding-top'> + <FormattedMessage + id='user.settings.hooks_out.callbackDesc' + defaultMessage='New line separated URLs that will receive the HTTP POST event' + /> + </div> {addError} </div> <div className='padding-top padding-bottom'> @@ -301,7 +373,10 @@ export default class ManageOutgoingHooks extends React.Component { disabled={disableButton} onClick={this.addNewHook} > - {'Add'} + <FormattedMessage + id='user.settings.hooks_out.add' + defaultMessage='Add' + /> </a> </div> </div> @@ -311,3 +386,9 @@ export default class ManageOutgoingHooks extends React.Component { ); } } + +ManageOutgoingHooks.propTypes = { + intl: intlShape.isRequired +}; + +export default injectIntl(ManageOutgoingHooks);
\ No newline at end of file diff --git a/web/react/components/user_settings/user_settings_advanced.jsx b/web/react/components/user_settings/user_settings_advanced.jsx index d4bd00bb5..5c0757589 100644 --- a/web/react/components/user_settings/user_settings_advanced.jsx +++ b/web/react/components/user_settings/user_settings_advanced.jsx @@ -6,9 +6,55 @@ import SettingItemMin from '../setting_item_min.jsx'; import SettingItemMax from '../setting_item_max.jsx'; import Constants from '../../utils/constants.jsx'; import PreferenceStore from '../../stores/preference_store.jsx'; + +import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl'; + const PreReleaseFeatures = Constants.PRE_RELEASE_FEATURES; -export default class AdvancedSettingsDisplay extends React.Component { +const holders = defineMessages({ + sendTitle: { + id: 'user.settings.advance.sendTitle', + defaultMessage: 'Send messages on Ctrl + Enter' + }, + on: { + id: 'user.settings.advance.on', + defaultMessage: 'On' + }, + off: { + id: 'user.settings.advance.off', + defaultMessage: 'Off' + }, + preReleaseTitle: { + id: 'user.settings.advance.preReleaseTitle', + defaultMessage: 'Preview pre-release features' + }, + feature: { + id: 'user.settings.advance.feature', + defaultMessage: ' Feature ' + }, + features: { + id: 'user.settings.advance.features', + defaultMessage: ' Features ' + }, + enabled: { + id: 'user.settings.advance.enabled', + defaultMessage: 'enabled' + }, + MARKDOWN_PREVIEW: { + id: 'user.settings.advance.markdown_preview', + defaultMessage: 'Show markdown preview option in message input box' + }, + EMBED_PREVIEW: { + id: 'user.settings.advance.embed_preview', + defaultMessage: 'Show preview snippet of links below message' + }, + LOC_PREVIEW: { + id: 'user.settings.advance.loc_preview', + defaultMessage: 'Show user language in display settings' + } +}); + +class AdvancedSettingsDisplay extends React.Component { constructor(props) { super(props); @@ -104,6 +150,7 @@ export default class AdvancedSettingsDisplay extends React.Component { render() { const serverError = this.state.serverError || null; + const {formatMessage} = this.props.intl; let ctrlSendSection; if (this.props.activeSection === 'advancedCtrlSend') { @@ -121,7 +168,10 @@ export default class AdvancedSettingsDisplay extends React.Component { checked={ctrlSendActive[0]} onChange={this.updateSetting.bind(this, 'send_on_ctrl_enter', 'true')} /> - {'On'} + <FormattedMessage + id='user.settings.advance.on' + defaultMessage='On' + /> </label> <br/> </div> @@ -132,17 +182,26 @@ export default class AdvancedSettingsDisplay extends React.Component { checked={ctrlSendActive[1]} onChange={this.updateSetting.bind(this, 'send_on_ctrl_enter', 'false')} /> - {'Off'} + <FormattedMessage + id='user.settings.advance.off' + defaultMessage='Off' + /> </label> <br/> </div> - <div><br/>{'If enabled \'Enter\' inserts a new line and \'Ctrl + Enter\' submits the message.'}</div> + <div> + <br/> + <FormattedMessage + id='user.settings.advance.sendDesc' + defaultMessage="If enabled 'Enter' inserts a new line and 'Ctrl + Enter' submits the message." + /> + </div> </div> ]; ctrlSendSection = ( <SettingItemMax - title='Send messages on Ctrl + Enter' + title={formatMessage(holders.sendTitle)} inputs={inputs} submit={() => this.handleSubmit('send_on_ctrl_enter')} server_error={serverError} @@ -155,8 +214,8 @@ export default class AdvancedSettingsDisplay extends React.Component { } else { ctrlSendSection = ( <SettingItemMin - title='Send messages on Ctrl + Enter' - describe={this.state.settings.send_on_ctrl_enter === 'true' ? 'On' : 'Off'} + title={formatMessage(holders.sendTitle)} + describe={this.state.settings.send_on_ctrl_enter === 'true' ? formatMessage(holders.on) : formatMessage(holders.off)} updateSection={() => this.props.updateSection('advancedCtrlSend')} /> ); @@ -185,7 +244,7 @@ export default class AdvancedSettingsDisplay extends React.Component { this.toggleFeature(feature.label, e.target.checked); }} /> - {feature.description} + {formatMessage({id: 'user.settings.advance.' + feature.label})} </label> </div> </div> @@ -195,13 +254,16 @@ export default class AdvancedSettingsDisplay extends React.Component { inputs.push( <div key='advancedPreviewFeatures_helptext'> <br/> - {'Check any pre-released features you\'d like to preview. You may also need to refresh the page before the setting will take effect.'} + <FormattedMessage + id='user.settings.advance.preReleaseDesc' + defaultMessage="Check any pre-released features you'd like to preview. You may also need to refresh the page before the setting will take effect." + /> </div> ); previewFeaturesSection = ( <SettingItemMax - title='Preview pre-release features' + title={formatMessage(holders.preReleaseTitle)} inputs={inputs} submit={this.saveEnabledFeatures} server_error={serverError} @@ -214,8 +276,8 @@ export default class AdvancedSettingsDisplay extends React.Component { } else { previewFeaturesSection = ( <SettingItemMin - title='Preview pre-release features' - describe={this.state.enabledFeatures + (this.state.enabledFeatures === 1 ? ' Feature ' : ' Features ') + 'enabled'} + title={formatMessage(holders.preReleaseTitle)} + describe={this.state.enabledFeatures + (this.state.enabledFeatures === 1 ? formatMessage(holders.feature) : formatMessage(holders.features)) + formatMessage(holders.enabled)} updateSection={() => this.props.updateSection('advancedPreviewFeatures')} /> ); @@ -242,11 +304,19 @@ export default class AdvancedSettingsDisplay extends React.Component { className='modal-back' onClick={this.props.collapseModal} /> - {'Advanced Settings'} + <FormattedMessage + id='user.settings.advance.title' + defaultMessage='Advanced Settings' + /> </h4> </div> <div className='user-settings'> - <h3 className='tab-header'>{'Advanced Settings'}</h3> + <h3 className='tab-header'> + <FormattedMessage + id='user.settings.advance.title' + defaultMessage='Advanced Settings' + /> + </h3> <div className='divider-dark first'/> {ctrlSendSection} {previewFeaturesSectionDivider} @@ -259,6 +329,7 @@ export default class AdvancedSettingsDisplay extends React.Component { } AdvancedSettingsDisplay.propTypes = { + intl: intlShape.isRequired, user: React.PropTypes.object, updateSection: React.PropTypes.func, updateTab: React.PropTypes.func, @@ -266,3 +337,5 @@ AdvancedSettingsDisplay.propTypes = { closeModal: React.PropTypes.func.isRequired, collapseModal: React.PropTypes.func.isRequired }; + +export default injectIntl(AdvancedSettingsDisplay);
\ No newline at end of file diff --git a/web/react/components/user_settings/user_settings_appearance.jsx b/web/react/components/user_settings/user_settings_appearance.jsx index 7bfc9fdbd..fb11dc81b 100644 --- a/web/react/components/user_settings/user_settings_appearance.jsx +++ b/web/react/components/user_settings/user_settings_appearance.jsx @@ -11,6 +11,9 @@ import * as Client from '../../utils/client.jsx'; import * as Utils from '../../utils/utils.jsx'; import Constants from '../../utils/constants.jsx'; + +import {FormattedMessage} from 'mm-intl'; + const ActionTypes = Constants.ActionTypes; export default class UserSettingsAppearance extends React.Component { @@ -180,7 +183,10 @@ export default class UserSettingsAppearance extends React.Component { checked={!displayCustom} onChange={this.updateType.bind(this, 'premade')} /> - {'Theme Colors'} + <FormattedMessage + id='user.settings.appearance.themeColors' + defaultMessage='Theme Colors' + /> </label> <br/> </div> @@ -191,7 +197,10 @@ export default class UserSettingsAppearance extends React.Component { checked={displayCustom} onChange={this.updateType.bind(this, 'custom')} /> - {'Custom Theme'} + <FormattedMessage + id='user.settings.appearance.customTheme' + defaultMessage='Custom Theme' + /> </label> <br/> </div> @@ -203,14 +212,20 @@ export default class UserSettingsAppearance extends React.Component { href='#' onClick={this.submitTheme} > - {'Save'} + <FormattedMessage + id='user.settings.appearance.save' + defaultMessage='Save' + /> </a> <a className='btn btn-sm theme' href='#' onClick={this.resetFields} > - {'Cancel'} + <FormattedMessage + id='user.settings.appearance.cancel' + defaultMessage='Cancel' + /> </a> </div> </div> @@ -235,11 +250,19 @@ export default class UserSettingsAppearance extends React.Component { className='modal-back' onClick={this.props.collapseModal} /> - {'Appearance Settings'} + <FormattedMessage + id='user.settings.appearance.title' + defaultMessage='Appearance Settings' + /> </h4> </div> <div className='user-settings'> - <h3 className='tab-header'>{'Appearance Settings'}</h3> + <h3 className='tab-header'> + <FormattedMessage + id='user.settings.appearance.title' + defaultMessage='Appearance Settings' + /> + </h3> <div className='divider-dark first'/> {themeUI} <div className='divider-dark'/> @@ -248,7 +271,10 @@ export default class UserSettingsAppearance extends React.Component { className='theme' onClick={this.handleImportModal} > - {'Import theme colors from Slack'} + <FormattedMessage + id='user.settings.appearance.import' + defaultMessage='Import theme colors from Slack' + /> </a> </div> </div> diff --git a/web/react/components/user_settings/user_settings_developer.jsx b/web/react/components/user_settings/user_settings_developer.jsx index 01e13be57..5868e0ad3 100644 --- a/web/react/components/user_settings/user_settings_developer.jsx +++ b/web/react/components/user_settings/user_settings_developer.jsx @@ -5,7 +5,20 @@ import SettingItemMin from '../setting_item_min.jsx'; import SettingItemMax from '../setting_item_max.jsx'; import * as EventHelpers from '../../dispatcher/event_helpers.jsx'; -export default class DeveloperTab extends React.Component { +import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl'; + +const holders = defineMessages({ + applicationsPreview: { + id: 'user.settings.developer.applicationsPreview', + defaultMessage: 'Applications (Preview)' + }, + thirdParty: { + id: 'user.settings.developer.thirdParty', + defaultMessage: 'Open to register a new third-party application' + } +}); + +class DeveloperTab extends React.Component { constructor(props) { super(props); @@ -20,6 +33,7 @@ export default class DeveloperTab extends React.Component { render() { var appSection; var self = this; + const {formatMessage} = this.props.intl; if (this.props.activeSection === 'app') { var inputs = []; @@ -33,7 +47,10 @@ export default class DeveloperTab extends React.Component { className='btn btn-sm btn-primary' onClick={this.register} > - {'Register New Application'} + <FormattedMessage + id='user.settings.developer.register' + defaultMessage='Register New Application' + /> </a> </div> </div> @@ -41,7 +58,7 @@ export default class DeveloperTab extends React.Component { appSection = ( <SettingItemMax - title='Applications (Preview)' + title={formatMessage(holders.applicationsPreview)} inputs={inputs} updateSection={function updateSection(e) { self.props.updateSection(''); @@ -52,8 +69,8 @@ export default class DeveloperTab extends React.Component { } else { appSection = ( <SettingItemMin - title='Applications (Preview)' - describe='Open to register a new third-party application' + title={formatMessage(holders.applicationsPreview)} + describe={formatMessage(holders.thirdParty)} updateSection={function updateSection() { self.props.updateSection('app'); }} @@ -81,11 +98,19 @@ export default class DeveloperTab extends React.Component { className='modal-back' onClick={this.props.collapseModal} /> - {'Developer Settings'} + <FormattedMessage + id='user.settings.developer.title' + defaultMessage='Developer Settings' + /> </h4> </div> <div className='user-settings'> - <h3 className='tab-header'>{'Developer Settings'}</h3> + <h3 className='tab-header'> + <FormattedMessage + id='user.settings.developer.title' + defaultMessage='Developer Settings' + /> + </h3> <div className='divider-dark first'/> {appSection} <div className='divider-dark'/> @@ -99,8 +124,11 @@ DeveloperTab.defaultProps = { activeSection: '' }; DeveloperTab.propTypes = { + intl: intlShape.isRequired, activeSection: React.PropTypes.string, updateSection: React.PropTypes.func, closeModal: React.PropTypes.func.isRequired, collapseModal: React.PropTypes.func.isRequired }; + +export default injectIntl(DeveloperTab);
\ No newline at end of file diff --git a/web/react/components/user_settings/user_settings_display.jsx b/web/react/components/user_settings/user_settings_display.jsx index f2c2502fb..3b2a2065b 100644 --- a/web/react/components/user_settings/user_settings_display.jsx +++ b/web/react/components/user_settings/user_settings_display.jsx @@ -10,6 +10,47 @@ import PreferenceStore from '../../stores/preference_store.jsx'; import ManageLanguages from './manage_languages.jsx'; import * as Utils from '../../utils/utils.jsx'; +import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl'; + +const holders = defineMessages({ + normalClock: { + id: 'user.settings.display.normalClock', + defaultMessage: '12-hour clock (example: 4:00 PM)' + }, + militaryClock: { + id: 'user.settings.display.militaryClock', + defaultMessage: '24-hour clock (example: 16:00)' + }, + clockDisplay: { + id: 'user.settings.display.clockDisplay', + defaultMessage: 'Clock Display' + }, + teammateDisplay: { + id: 'user.settings.display.teammateDisplay', + defaultMessage: 'Teammate Name Display' + }, + showNickname: { + id: 'user.settings.display.showNickname', + defaultMessage: 'Show nickname if one exists, otherwise show first and last name' + }, + showUsername: { + id: 'user.settings.display.showUsername', + defaultMessage: 'Show username (team default)' + }, + showFullname: { + id: 'user.settings.display.showFullname', + defaultMessage: 'Show first and last name' + }, + fontTitle: { + id: 'user.settings.display.fontTitle', + defaultMessage: 'Display Font' + }, + language: { + id: 'user.settings.display.language', + defaultMessage: 'Language' + } +}); + function getDisplayStateFromStores() { const militaryTime = PreferenceStore.getPreference(Constants.Preferences.CATEGORY_DISPLAY_SETTINGS, 'use_military_time', {value: 'false'}); const nameFormat = PreferenceStore.getPreference(Constants.Preferences.CATEGORY_DISPLAY_SETTINGS, 'name_format', {value: 'username'}); @@ -22,7 +63,7 @@ function getDisplayStateFromStores() { }; } -export default class UserSettingsDisplay extends React.Component { +class UserSettingsDisplay extends React.Component { constructor(props) { super(props); @@ -76,6 +117,7 @@ export default class UserSettingsDisplay extends React.Component { this.updateState(); } render() { + const {formatMessage} = this.props.intl; const serverError = this.state.serverError || null; let clockSection; let nameFormatSection; @@ -104,7 +146,10 @@ export default class UserSettingsDisplay extends React.Component { checked={clockFormat[0]} onChange={this.handleClockRadio.bind(this, 'false')} /> - {'12-hour clock (example: 4:00 PM)'} + <FormattedMessage + id='user.settings.display.normalClock' + defaultMessage='12-hour clock (example: 4:00 PM)' + /> </label> <br/> </div> @@ -115,17 +160,26 @@ export default class UserSettingsDisplay extends React.Component { checked={clockFormat[1]} onChange={this.handleClockRadio.bind(this, 'true')} /> - {'24-hour clock (example: 16:00)'} + <FormattedMessage + id='user.settings.display.militaryClock' + defaultMessage='24-hour clock (example: 16:00)' + /> </label> <br/> </div> - <div><br/>{'Select how you prefer time displayed.'}</div> + <div> + <br/> + <FormattedMessage + id='user.settings.display.preferTime' + defaultMessage='Select how you prefer time displayed.' + /> + </div> </div> ]; clockSection = ( <SettingItemMax - title='Clock Display' + title={formatMessage(holders.clockDisplay)} inputs={inputs} submit={this.handleSubmit} server_error={serverError} @@ -135,9 +189,9 @@ export default class UserSettingsDisplay extends React.Component { } else { let describe = ''; if (this.state.militaryTime === 'true') { - describe = '24-hour clock (example: 16:00)'; + describe = formatMessage(holders.militaryClock); } else { - describe = '12-hour clock (example: 4:00 PM)'; + describe = formatMessage(holders.normalClock); } const handleUpdateClockSection = () => { @@ -146,16 +200,31 @@ export default class UserSettingsDisplay extends React.Component { clockSection = ( <SettingItemMin - title='Clock Display' + title={formatMessage(holders.clockDisplay)} describe={describe} updateSection={handleUpdateClockSection} /> ); } - const showUsername = 'Show username (team default)'; - const showNickname = 'Show nickname if one exists, otherwise show first and last name'; - const showFullName = 'Show first and last name'; + const showUsername = ( + <FormattedMessage + id='user.settings.display.showUsername' + defaultMessage='Show username (team default)' + /> + ); + const showNickname = ( + <FormattedMessage + id='user.settings.display.showNickname' + defaultMessage='Show nickname if one exists, otherwise show first and last name' + /> + ); + const showFullName = ( + <FormattedMessage + id='user.settings.display.showFullname' + defaultMessage='Show first and last name' + /> + ); if (this.props.activeSection === 'name_format') { const nameFormat = [false, false, false]; if (this.state.nameFormat === 'nickname_full_name') { @@ -201,13 +270,19 @@ export default class UserSettingsDisplay extends React.Component { </label> <br/> </div> - <div><br/>{'Set how to display other user\'s names in posts and the Direct Messages list.'}</div> + <div> + <br/> + <FormattedMessage + id='user.settings.display.nameOptsDesc' + defaultMessage="Set how to display other user's names in posts and the Direct Messages list." + /> + </div> </div> ]; nameFormatSection = ( <SettingItemMax - title='Teammate Name Display' + title={formatMessage(holders.teammateDisplay)} inputs={inputs} submit={this.handleSubmit} server_error={serverError} @@ -220,16 +295,16 @@ export default class UserSettingsDisplay extends React.Component { } else { let describe = ''; if (this.state.nameFormat === 'username') { - describe = showUsername; + describe = formatMessage(holders.showUsername); } else if (this.state.nameFormat === 'full_name') { - describe = showFullName; + describe = formatMessage(holders.showFullName); } else { - describe = showNickname; + describe = formatMessage(holders.showNickname); } nameFormatSection = ( <SettingItemMin - title='Teammate Name Display' + title={formatMessage(holders.teammateDisplay)} describe={describe} updateSection={() => { this.props.updateSection('name_format'); @@ -267,13 +342,19 @@ export default class UserSettingsDisplay extends React.Component { {options} </select> </div> - <div><br/>{'Select the font displayed in the Mattermost user interface.'}</div> + <div> + <br/> + <FormattedMessage + id='user.settings.display.fontDesc' + defaultMessage='Select the font displayed in the Mattermost user interface.' + /> + </div> </div> ]; fontSection = ( <SettingItemMax - title='Display Font' + title={formatMessage(holders.fontTitle)} inputs={inputs} submit={this.handleSubmit} server_error={serverError} @@ -286,7 +367,7 @@ export default class UserSettingsDisplay extends React.Component { } else { fontSection = ( <SettingItemMin - title='Display Font' + title={formatMessage(holders.fontTitle)} describe={this.state.selectedFont} updateSection={() => { this.props.updateSection('font'); @@ -307,7 +388,7 @@ export default class UserSettingsDisplay extends React.Component { languagesSection = ( <SettingItemMax - title={'Language'} + title={formatMessage(holders.language)} width='medium' inputs={inputs} updateSection={(e) => { @@ -326,7 +407,7 @@ export default class UserSettingsDisplay extends React.Component { languagesSection = ( <SettingItemMin - title={'Language'} + title={formatMessage(holders.language)} width='medium' describe={locale} updateSection={() => { @@ -357,11 +438,19 @@ export default class UserSettingsDisplay extends React.Component { className='modal-back' onClick={this.props.collapseModal} /> - {'Display Settings'} + <FormattedMessage + id='user.settings.display.title' + defaultMessage='Display Settings' + /> </h4> </div> <div className='user-settings'> - <h3 className='tab-header'>{'Display Settings'}</h3> + <h3 className='tab-header'> + <FormattedMessage + id='user.settings.display.title' + defaultMessage='Display Settings' + /> + </h3> <div className='divider-dark first'/> {fontSection} <div className='divider-dark'/> @@ -377,6 +466,7 @@ export default class UserSettingsDisplay extends React.Component { } UserSettingsDisplay.propTypes = { + intl: intlShape.isRequired, user: React.PropTypes.object, updateSection: React.PropTypes.func, updateTab: React.PropTypes.func, @@ -384,3 +474,5 @@ UserSettingsDisplay.propTypes = { closeModal: React.PropTypes.func.isRequired, collapseModal: React.PropTypes.func.isRequired }; + +export default injectIntl(UserSettingsDisplay);
\ No newline at end of file diff --git a/web/react/components/user_settings/user_settings_general.jsx b/web/react/components/user_settings/user_settings_general.jsx index df7ae4a25..f20b4b807 100644 --- a/web/react/components/user_settings/user_settings_general.jsx +++ b/web/react/components/user_settings/user_settings_general.jsx @@ -13,7 +13,84 @@ import Constants from '../../utils/constants.jsx'; import * as AsyncClient from '../../utils/async_client.jsx'; import * as Utils from '../../utils/utils.jsx'; -export default class UserSettingsGeneralTab extends React.Component { +import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl'; + +const holders = defineMessages({ + usernameReserved: { + id: 'user.settings.general.usernameReserved', + defaultMessage: 'This username is reserved, please choose a new one.' + }, + usernameRestrictions: { + id: 'user.settings.general.usernameRestrictions', + defaultMessage: "'Username must begin with a letter, and contain between {min} to {max} lowercase characters made up of numbers, letters, and the symbols '.', '-' and '_'." + }, + validEmail: { + id: 'user.settings.general.validEmail', + defaultMessage: 'Please enter a valid email address' + }, + emailMatch: { + id: 'user.settings.general.emailMatch', + defaultMessage: 'The new emails you entered do not match.' + }, + checkEmail: { + id: 'user.settings.general.checkEmail', + defaultMessage: 'Check your email at {email} to verify the address.' + }, + newAddress: { + id: 'user.settings.general.newAddress', + defaultMessage: 'New Address: {email}<br />Check your email to verify the above address.' + }, + checkEmailNoAddress: { + id: 'user.settings.general.checkEmailNoAddress', + defaultMessage: 'Check your email to verify your new address' + }, + loginGitlab: { + id: 'user.settings.general.loginGitlab', + defaultMessage: 'Log in done through GitLab' + }, + validImage: { + id: 'user.settings.general.validImage', + defaultMessage: 'Only JPG or PNG images may be used for profile pictures' + }, + imageTooLarge: { + id: 'user.settings.general.imageTooLarge', + defaultMessage: 'Unable to upload profile image. File is too large.' + }, + uploadImage: { + id: 'user.settings.general.uploadImage', + defaultMessage: "Click 'Edit' to upload an image." + }, + imageUpdated: { + id: 'user.settings.general.imageUpdated', + defaultMessage: 'Image last updated {date}' + }, + fullName: { + id: 'user.settings.general.fullName', + defaultMessage: 'Full Name' + }, + nickname: { + id: 'user.settings.general.nickname', + defaultMessage: 'Nickname' + }, + username: { + id: 'user.settings.general.username', + defaultMessage: 'Username' + }, + email: { + id: 'user.settings.general.email', + defaultMessage: 'Email' + }, + profilePicture: { + id: 'user.settings.general.profilePicture', + defaultMessage: 'Profile Picture' + }, + close: { + id: 'user.settings.general.close', + defaultMessage: 'Close' + } +}); + +class UserSettingsGeneralTab extends React.Component { constructor(props) { super(props); this.submitActive = false; @@ -42,12 +119,13 @@ export default class UserSettingsGeneralTab extends React.Component { const user = Object.assign({}, this.props.user); const username = this.state.username.trim().toLowerCase(); + const {formatMessage} = this.props.intl; const usernameError = Utils.isValidUsername(username); if (usernameError === 'Cannot use a reserved word as a username.') { - this.setState({clientError: 'This username is reserved, please choose a new one.'}); + this.setState({clientError: formatMessage(holders.usernameReserved)}); return; } else if (usernameError) { - this.setState({clientError: 'Username must begin with a letter, and contain between ' + Constants.MIN_USERNAME_LENGTH + ' to ' + Constants.MAX_USERNAME_LENGTH + " lowercase characters made up of numbers, letters, and the symbols '.', '-' and '_'."}); + this.setState({clientError: formatMessage(holders.usernameRestrictions, {min: Constants.MIN_USERNAME_LENGTH, max: Constants.MAX_USERNAME_LENGTH})}); return; } @@ -99,13 +177,14 @@ export default class UserSettingsGeneralTab extends React.Component { const email = this.state.email.trim().toLowerCase(); const confirmEmail = this.state.confirmEmail.trim().toLowerCase(); + const {formatMessage} = this.props.intl; if (email === '' || !Utils.isEmail(email)) { - this.setState({emailError: 'Please enter a valid email address.', clientError: '', serverError: ''}); + this.setState({emailError: formatMessage(holders.validEmail), clientError: '', serverError: ''}); return; } if (email !== confirmEmail) { - this.setState({emailError: 'The new emails you entered do not match.', clientError: '', serverError: ''}); + this.setState({emailError: formatMessage(holders.emailMatch), clientError: '', serverError: ''}); return; } @@ -125,7 +204,7 @@ export default class UserSettingsGeneralTab extends React.Component { const verificationEnabled = global.window.mm_config.SendEmailNotifications === 'true' && global.window.mm_config.RequireEmailVerification === 'true' && emailUpdated; if (verificationEnabled) { - ErrorStore.storeLastError({message: 'Check your email at ' + user.email + ' to verify the address.'}); + ErrorStore.storeLastError({message: this.props.intl.formatMessage(holders.checkEmail, {email: user.email})}); ErrorStore.emitChange(); this.setState({emailChangeInProgress: true}); } @@ -152,13 +231,14 @@ export default class UserSettingsGeneralTab extends React.Component { return; } + const {formatMessage} = this.props.intl; const picture = this.state.picture; if (picture.type !== 'image/jpeg' && picture.type !== 'image/png') { - this.setState({clientError: 'Only JPG or PNG images may be used for profile pictures.'}); + this.setState({clientError: formatMessage(holders.validImage)}); return; } else if (picture.size > Constants.MAX_FILE_SIZE) { - this.setState({clientError: 'Unable to upload profile image. File is too large.'}); + this.setState({clientError: formatMessage(holders.imageTooLarge)}); return; } @@ -221,6 +301,7 @@ export default class UserSettingsGeneralTab extends React.Component { } render() { const user = this.props.user; + const {formatMessage, formatHTMLMessage} = this.props.intl; let clientError = null; if (this.state.clientError) { @@ -244,7 +325,12 @@ export default class UserSettingsGeneralTab extends React.Component { key='firstNameSetting' className='form-group' > - <label className='col-sm-5 control-label'>{'First Name'}</label> + <label className='col-sm-5 control-label'> + <FormattedMessage + id='user.settings.general.firstName' + defaultMessage='First Name' + /> + </label> <div className='col-sm-7'> <input className='form-control' @@ -261,7 +347,12 @@ export default class UserSettingsGeneralTab extends React.Component { key='lastNameSetting' className='form-group' > - <label className='col-sm-5 control-label'>{'Last Name'}</label> + <label className='col-sm-5 control-label'> + <FormattedMessage + id='user.settings.general.lastName' + defaultMessage='Last Name' + /> + </label> <div className='col-sm-7'> <input className='form-control' @@ -284,20 +375,28 @@ export default class UserSettingsGeneralTab extends React.Component { href='#' onClick={notifClick.bind(this)} > - {'Notifications'} + <FormattedMessage + id='user.settings.general.notificationsLink' + defaultMessage='Notifications' + /> </a> ); const extraInfo = ( <span> - {'By default, you will receive mention notifications when someone types your first name. '} - {'Go to '} {notifLink} {'settings to change this default.'} + <FormattedMessage + id='user.settings.general.notificationsExtra' + defaultMessage='By default, you will receive mention notifications when someone types your first name. Go to {notify} settings to change this default.' + values={{ + notify: (notifLink) + }} + /> </span> ); nameSection = ( <SettingItemMax - title='Full Name' + title={formatMessage(holders.fullName)} inputs={inputs} submit={this.submitName} server_error={serverError} @@ -322,7 +421,7 @@ export default class UserSettingsGeneralTab extends React.Component { nameSection = ( <SettingItemMin - title='Full Name' + title={formatMessage(holders.fullName)} describe={fullName} updateSection={() => { this.updateSection('name'); @@ -333,7 +432,12 @@ export default class UserSettingsGeneralTab extends React.Component { let nicknameSection; if (this.props.activeSection === 'nickname') { - let nicknameLabel = 'Nickname'; + let nicknameLabel = ( + <FormattedMessage + id='user.settings.general.nickname' + defaultMessage='Nickname' + /> + ); if (Utils.isMobile()) { nicknameLabel = ''; } @@ -357,13 +461,16 @@ export default class UserSettingsGeneralTab extends React.Component { const extraInfo = ( <span> - {'Use Nickname for a name you might be called that is different from your first name and username. This is most often used when two or more people have similar sounding names and usernames.'} + <FormattedMessage + id='user.settings.general.nicknameExtra' + defaultMessage='Use Nickname for a name you might be called that is different from your first name and username. This is most often used when two or more people have similar sounding names and usernames.' + /> </span> ); nicknameSection = ( <SettingItemMax - title='Nickname' + title={formatMessage(holders.nickname)} inputs={inputs} submit={this.submitNickname} server_error={serverError} @@ -378,7 +485,7 @@ export default class UserSettingsGeneralTab extends React.Component { } else { nicknameSection = ( <SettingItemMin - title='Nickname' + title={formatMessage(holders.nickname)} describe={UserStore.getCurrentUser().nickname} updateSection={() => { this.updateSection('nickname'); @@ -389,7 +496,12 @@ export default class UserSettingsGeneralTab extends React.Component { let usernameSection; if (this.props.activeSection === 'username') { - let usernameLabel = 'Username'; + let usernameLabel = ( + <FormattedMessage + id='user.settings.general.username' + defaultMessage='Username' + /> + ); if (Utils.isMobile()) { usernameLabel = ''; } @@ -411,11 +523,18 @@ export default class UserSettingsGeneralTab extends React.Component { </div> ); - const extraInfo = (<span>{'Pick something easy for teammates to recognize and recall.'}</span>); + const extraInfo = ( + <span> + <FormattedMessage + id='user.settings.general.usernameInfo' + defaultMessage='Pick something easy for teammates to recognize and recall.' + /> + </span> + ); usernameSection = ( <SettingItemMax - title='Username' + title={formatMessage(holders.username)} inputs={inputs} submit={this.submitUsername} server_error={serverError} @@ -430,7 +549,7 @@ export default class UserSettingsGeneralTab extends React.Component { } else { usernameSection = ( <SettingItemMin - title='Username' + title={formatMessage(holders.username)} describe={UserStore.getCurrentUser().username} updateSection={() => { this.updateSection('username'); @@ -443,16 +562,41 @@ export default class UserSettingsGeneralTab extends React.Component { if (this.props.activeSection === 'email') { const emailEnabled = global.window.mm_config.SendEmailNotifications === 'true'; const emailVerificationEnabled = global.window.mm_config.RequireEmailVerification === 'true'; - let helpText = 'Email is used for sign-in, notifications, and password reset. Email requires verification if changed.'; + let helpText = ( + <FormattedMessage + id='user.settings.general.emailHelp1' + defaultMessage='Email is used for sign-in, notifications, and password reset. Email requires verification if changed.' + /> + ); if (!emailEnabled) { - helpText = <div className='setting-list__hint text-danger'>{'Email has been disabled by your system administrator. No notification emails will be sent until it is enabled.'}</div>; + helpText = ( + <div className='setting-list__hint text-danger'> + <FormattedMessage + id='user.settings.general.emailHelp2' + defaultMessage='Email has been disabled by your system administrator. No notification emails will be sent until it is enabled.' + /> + </div> + ); } else if (!emailVerificationEnabled) { - helpText = 'Email is used for sign-in, notifications, and password reset.'; + helpText = ( + <FormattedMessage + id='user.settings.general.emailHelp3' + defaultMessage='Email is used for sign-in, notifications, and password reset.' + /> + ); } else if (this.state.emailChangeInProgress) { const newEmail = UserStore.getCurrentUser().email; if (newEmail) { - helpText = 'A verification email was sent to ' + newEmail + '.'; + helpText = ( + <FormattedMessage + id='user.settings.general.emailHelp4' + defaultMessage='A verification email was sent to {email}.' + values={{ + email: newEmail + }} + /> + ); } } @@ -462,7 +606,12 @@ export default class UserSettingsGeneralTab extends React.Component { inputs.push( <div key='emailSetting'> <div className='form-group'> - <label className='col-sm-5 control-label'>{'Primary Email'}</label> + <label className='col-sm-5 control-label'> + <FormattedMessage + id='user.settings.general.primaryEmail' + defaultMessage='Primary Email' + /> + </label> <div className='col-sm-7'> <input className='form-control' @@ -478,7 +627,12 @@ export default class UserSettingsGeneralTab extends React.Component { inputs.push( <div key='confirmEmailSetting'> <div className='form-group'> - <label className='col-sm-5 control-label'>{'Confirm Email'}</label> + <label className='col-sm-5 control-label'> + <FormattedMessage + id='user.settings.general.confirmEmail' + defaultMessage='Confirm Email' + /> + </label> <div className='col-sm-7'> <input className='form-control' @@ -499,7 +653,12 @@ export default class UserSettingsGeneralTab extends React.Component { key='oauthEmailInfo' className='form-group' > - <div className='setting-list__hint'>{'Log in occurs through GitLab. Email cannot be updated.'}</div> + <div className='setting-list__hint'> + <FormattedMessage + id='user.settings.general.emailCantUpdate' + defaultMessage='Log in occurs through GitLab. Email cannot be updated.' + /> + </div> {helpText} </div> ); @@ -524,20 +683,20 @@ export default class UserSettingsGeneralTab extends React.Component { if (this.state.emailChangeInProgress) { const newEmail = UserStore.getCurrentUser().email; if (newEmail) { - describe = 'New Address: ' + newEmail + '\nCheck your email to verify the above address.'; + describe = formatHTMLMessage(holders.newAddress, {email: newEmail}); } else { - describe = 'Check your email to verify your new address'; + describe = formatMessage(holders.checkEmailNoAddress); } } else { describe = UserStore.getCurrentUser().email; } } else if (this.props.user.auth_service === Constants.GITLAB_SERVICE) { - describe = 'Log in done through GitLab'; + describe = formatMessage(holders.loginGitlab); } emailSection = ( <SettingItemMin - title='Email' + title={formatMessage(holders.email)} describe={describe} updateSection={() => { this.updateSection('email'); @@ -550,7 +709,7 @@ export default class UserSettingsGeneralTab extends React.Component { if (this.props.activeSection === 'picture') { pictureSection = ( <SettingPicture - title='Profile Picture' + title={formatMessage(holders.profilePicture)} submit={this.submitPicture} src={'/api/v1/users/' + user.id + '/image?time=' + user.last_picture_update + '&' + Utils.getSessionIndex()} server_error={serverError} @@ -566,13 +725,15 @@ export default class UserSettingsGeneralTab extends React.Component { /> ); } else { - let minMessage = 'Click \'Edit\' to upload an image.'; + let minMessage = formatMessage(holders.uploadImage); if (user.last_picture_update) { - minMessage = 'Image last updated ' + Utils.displayDate(user.last_picture_update); + minMessage = formatMessage(holders.imageUpdated, { + date: new Date(user.last_picture_update).toLocaleDateString(global.window.mm_locale, {month: 'short', day: '2-digit', year: 'numeric'}) + }); } pictureSection = ( <SettingItemMin - title='Profile Picture' + title={formatMessage(holders.profilePicture)} describe={minMessage} updateSection={() => { this.updateSection('picture'); @@ -588,7 +749,7 @@ export default class UserSettingsGeneralTab extends React.Component { type='button' className='close' data-dismiss='modal' - aria-label='Close' + aria-label={formatMessage(holders.close)} onClick={this.props.closeModal} > <span aria-hidden='true'>{'×'}</span> @@ -601,11 +762,19 @@ export default class UserSettingsGeneralTab extends React.Component { className='modal-back' onClick={this.props.collapseModal} /> - {'General Settings'} + <FormattedMessage + id='user.settings.general.title' + defaultMessage='General Settings' + /> </h4> </div> <div className='user-settings'> - <h3 className='tab-header'>{'General Settings'}</h3> + <h3 className='tab-header'> + <FormattedMessage + id='user.settings.general.title' + defaultMessage='General Settings' + /> + </h3> <div className='divider-dark first'/> {nameSection} <div className='divider-light'/> @@ -624,6 +793,7 @@ export default class UserSettingsGeneralTab extends React.Component { } UserSettingsGeneralTab.propTypes = { + intl: intlShape.isRequired, user: React.PropTypes.object.isRequired, updateSection: React.PropTypes.func.isRequired, updateTab: React.PropTypes.func.isRequired, @@ -631,3 +801,5 @@ UserSettingsGeneralTab.propTypes = { closeModal: React.PropTypes.func.isRequired, collapseModal: React.PropTypes.func.isRequired }; + +export default injectIntl(UserSettingsGeneralTab);
\ No newline at end of file diff --git a/web/react/components/user_settings/user_settings_integrations.jsx b/web/react/components/user_settings/user_settings_integrations.jsx index a86510eb3..abd04a301 100644 --- a/web/react/components/user_settings/user_settings_integrations.jsx +++ b/web/react/components/user_settings/user_settings_integrations.jsx @@ -6,7 +6,28 @@ import SettingItemMax from '../setting_item_max.jsx'; import ManageIncomingHooks from './manage_incoming_hooks.jsx'; import ManageOutgoingHooks from './manage_outgoing_hooks.jsx'; -export default class UserSettingsIntegrationsTab extends React.Component { +import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl'; + +const holders = defineMessages({ + inName: { + id: 'user.settings.integrations.incomingWebhooks', + defaultMessage: 'Incoming Webhooks' + }, + inDesc: { + id: 'user.settings.integrations.incomingWebhooksDescription', + defaultMessage: 'Manage your incoming webhooks' + }, + outName: { + id: 'user.settings.integrations.outWebhooks', + defaultMessage: 'Outgoing Webhooks' + }, + outDesc: { + id: 'user.settings.integrations.outWebhooksDescription', + defaultMessage: 'Manage your outgoing webhooks' + } +}); + +class UserSettingsIntegrationsTab extends React.Component { constructor(props) { super(props); @@ -21,6 +42,7 @@ export default class UserSettingsIntegrationsTab extends React.Component { let incomingHooksSection; let outgoingHooksSection; var inputs = []; + const {formatMessage} = this.props.intl; if (global.window.mm_config.EnableIncomingWebhooks === 'true') { if (this.props.activeSection === 'incoming-hooks') { @@ -30,7 +52,7 @@ export default class UserSettingsIntegrationsTab extends React.Component { incomingHooksSection = ( <SettingItemMax - title='Incoming Webhooks' + title={formatMessage(holders.inName)} width='medium' inputs={inputs} updateSection={(e) => { @@ -42,9 +64,9 @@ export default class UserSettingsIntegrationsTab extends React.Component { } else { incomingHooksSection = ( <SettingItemMin - title='Incoming Webhooks' + title={formatMessage(holders.inName)} width='medium' - describe='Manage your incoming webhooks' + describe={formatMessage(holders.inDesc)} updateSection={() => { this.updateSection('incoming-hooks'); }} @@ -61,7 +83,7 @@ export default class UserSettingsIntegrationsTab extends React.Component { outgoingHooksSection = ( <SettingItemMax - title='Outgoing Webhooks' + title={formatMessage(holders.outName)} width='medium' inputs={inputs} updateSection={(e) => { @@ -73,9 +95,9 @@ export default class UserSettingsIntegrationsTab extends React.Component { } else { outgoingHooksSection = ( <SettingItemMin - title='Outgoing Webhooks' + title={formatMessage(holders.outName)} width='medium' - describe='Manage your outgoing webhooks' + describe={formatMessage(holders.outDesc)} updateSection={() => { this.updateSection('outgoing-hooks'); }} @@ -104,11 +126,19 @@ export default class UserSettingsIntegrationsTab extends React.Component { className='modal-back' onClick={this.props.collapseModal} /> - {'Integration Settings'} + <FormattedMessage + id='user.settings.integrations.title' + defaultMessage='Integration Settings' + /> </h4> </div> <div className='user-settings'> - <h3 className='tab-header'>{'Integration Settings'}</h3> + <h3 className='tab-header'> + <FormattedMessage + id='user.settings.integrations.title' + defaultMessage='Integration Settings' + /> + </h3> <div className='divider-dark first'/> {incomingHooksSection} <div className='divider-light'/> @@ -121,6 +151,7 @@ export default class UserSettingsIntegrationsTab extends React.Component { } UserSettingsIntegrationsTab.propTypes = { + intl: intlShape.isRequired, user: React.PropTypes.object, updateSection: React.PropTypes.func, updateTab: React.PropTypes.func, @@ -128,3 +159,5 @@ UserSettingsIntegrationsTab.propTypes = { closeModal: React.PropTypes.func.isRequired, collapseModal: React.PropTypes.func.isRequired }; + +export default injectIntl(UserSettingsIntegrationsTab);
\ No newline at end of file diff --git a/web/react/components/user_settings/user_settings_modal.jsx b/web/react/components/user_settings/user_settings_modal.jsx index 36e1aa217..2a0a90cf5 100644 --- a/web/react/components/user_settings/user_settings_modal.jsx +++ b/web/react/components/user_settings/user_settings_modal.jsx @@ -6,7 +6,56 @@ const Modal = ReactBootstrap.Modal; import SettingsSidebar from '../settings_sidebar.jsx'; import UserSettings from './user_settings.jsx'; -export default class UserSettingsModal extends React.Component { +import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl'; + +const holders = defineMessages({ + general: { + id: 'user.settings.modal.general', + defaultMessage: 'General' + }, + security: { + id: 'user.settings.modal.security', + defaultMessage: 'Security' + }, + notifications: { + id: 'user.settings.modal.notifications', + defaultMessage: 'Notifications' + }, + appearance: { + id: 'user.settings.modal.appearance', + defaultMessage: 'Appearance' + }, + developer: { + id: 'user.settings.modal.developer', + defaultMessage: 'Developer' + }, + integrations: { + id: 'user.settings.modal.integrations', + defaultMessage: 'Integrations' + }, + display: { + id: 'user.settings.modal.display', + defaultMessage: 'Display' + }, + advanced: { + id: 'user.settings.modal.advanced', + defaultMessage: 'Advanced' + }, + confirmTitle: { + id: 'user.settings.modal.confirmTitle', + defaultMessage: 'Discard Changes?' + }, + confirmMsg: { + id: 'user.settings.modal.confirmMsg', + defaultMessage: 'You have unsaved changes, are you sure you want to discard them?' + }, + confirmBtns: { + id: 'user.settings.modal.confirmBtns', + defaultMessage: 'Yes, Discard' + } +}); + +class UserSettingsModal extends React.Component { constructor(props) { super(props); @@ -170,20 +219,21 @@ export default class UserSettingsModal extends React.Component { } render() { + const {formatMessage} = this.props.intl; var tabs = []; - tabs.push({name: 'general', uiName: 'General', icon: 'glyphicon glyphicon-cog'}); - tabs.push({name: 'security', uiName: 'Security', icon: 'glyphicon glyphicon-lock'}); - tabs.push({name: 'notifications', uiName: 'Notifications', icon: 'glyphicon glyphicon-exclamation-sign'}); - tabs.push({name: 'appearance', uiName: 'Appearance', icon: 'glyphicon glyphicon-wrench'}); + tabs.push({name: 'general', uiName: formatMessage(holders.general), icon: 'glyphicon glyphicon-cog'}); + tabs.push({name: 'security', uiName: formatMessage(holders.security), icon: 'glyphicon glyphicon-lock'}); + tabs.push({name: 'notifications', uiName: formatMessage(holders.notifications), icon: 'glyphicon glyphicon-exclamation-sign'}); + tabs.push({name: 'appearance', uiName: formatMessage(holders.appearance), icon: 'glyphicon glyphicon-wrench'}); if (global.window.mm_config.EnableOAuthServiceProvider === 'true') { - tabs.push({name: 'developer', uiName: 'Developer', icon: 'glyphicon glyphicon-th'}); + tabs.push({name: 'developer', uiName: formatMessage(holders.developer), icon: 'glyphicon glyphicon-th'}); } if (global.window.mm_config.EnableIncomingWebhooks === 'true' || global.window.mm_config.EnableOutgoingWebhooks === 'true') { - tabs.push({name: 'integrations', uiName: 'Integrations', icon: 'glyphicon glyphicon-transfer'}); + tabs.push({name: 'integrations', uiName: formatMessage(holders.integrations), icon: 'glyphicon glyphicon-transfer'}); } - tabs.push({name: 'display', uiName: 'Display', icon: 'glyphicon glyphicon-eye-open'}); - tabs.push({name: 'advanced', uiName: 'Advanced', icon: 'glyphicon glyphicon-list-alt'}); + tabs.push({name: 'display', uiName: formatMessage(holders.display), icon: 'glyphicon glyphicon-eye-open'}); + tabs.push({name: 'advanced', uiName: formatMessage(holders.advanced), icon: 'glyphicon glyphicon-list-alt'}); return ( <Modal @@ -194,7 +244,12 @@ export default class UserSettingsModal extends React.Component { enforceFocus={this.state.enforceFocus} > <Modal.Header closeButton={true}> - <Modal.Title>{'Account Settings'}</Modal.Title> + <Modal.Title> + <FormattedMessage + id='user.settings.modal.title' + defaultMessage='Account Settings' + /> + </Modal.Title> </Modal.Header> <Modal.Body ref='modalBody'> <div className='settings-table'> @@ -221,9 +276,9 @@ export default class UserSettingsModal extends React.Component { </div> </Modal.Body> <ConfirmModal - title='Discard Changes?' - message='You have unsaved changes, are you sure you want to discard them?' - confirm_button='Yes, Discard' + title={formatMessage(holders.confirmTitle)} + message={formatMessage(holders.confirmMsg)} + confirm_button={formatMessage(holders.confirmBtns)} show={this.state.showConfirmModal} onConfirm={this.handleConfirm} onCancel={this.handleCancelConfirmation} @@ -234,6 +289,9 @@ export default class UserSettingsModal extends React.Component { } UserSettingsModal.propTypes = { + intl: intlShape.isRequired, show: React.PropTypes.bool.isRequired, onModalDismissed: React.PropTypes.func.isRequired }; + +export default injectIntl(UserSettingsModal);
\ No newline at end of file diff --git a/web/react/components/user_settings/user_settings_notifications.jsx b/web/react/components/user_settings/user_settings_notifications.jsx index f762405af..91a03eb70 100644 --- a/web/react/components/user_settings/user_settings_notifications.jsx +++ b/web/react/components/user_settings/user_settings_notifications.jsx @@ -10,6 +10,8 @@ import * as Client from '../../utils/client.jsx'; import * as AsyncClient from '../../utils/async_client.jsx'; import * as Utils from '../../utils/utils.jsx'; +import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl'; + function getNotificationsStateFromStores() { var user = UserStore.getCurrentUser(); var soundNeeded = !Utils.isBrowserFirefox(); @@ -73,7 +75,30 @@ function getNotificationsStateFromStores() { firstNameKey: firstNameKey, allKey: allKey, channelKey: channelKey}; } -export default class NotificationsTab extends React.Component { +const holders = defineMessages({ + desktop: { + id: 'user.settings.notifications.desktop', + defaultMessage: 'Send desktop notifications' + }, + desktopSounds: { + id: 'user.settings.notifications.desktopSounds', + defaultMessage: 'Desktop notification sounds' + }, + emailNotifications: { + id: 'user.settings.notifications.emailNotifications', + defaultMessage: 'Email notifications' + }, + wordsTrigger: { + id: 'user.settings.notifications.wordsTrigger', + defaultMessage: 'Words that trigger mentions' + }, + close: { + id: 'user.settings.notifications.close', + defaultMessage: 'Close' + } +}); + +class NotificationsTab extends React.Component { constructor(props) { super(props); @@ -198,6 +223,7 @@ export default class NotificationsTab extends React.Component { this.updateCustomMentionKeys(); } render() { + const {formatMessage} = this.props.intl; var serverError = null; if (this.state.serverError) { serverError = this.state.serverError; @@ -227,7 +253,10 @@ export default class NotificationsTab extends React.Component { checked={notifyActive[0]} onChange={this.handleNotifyRadio.bind(this, 'all')} /> - {'For all activity'} + <FormattedMessage + id='user.settings.notification.allActivity' + defaultMessage='For all activity' + /> </label> <br/> </div> @@ -238,7 +267,10 @@ export default class NotificationsTab extends React.Component { checked={notifyActive[1]} onChange={this.handleNotifyRadio.bind(this, 'mention')} /> - {'Only for mentions and direct messages'} + <FormattedMessage + id='user.settings.notifications.onlyMentions' + defaultMessage='Only for mentions and direct messages' + /> </label> <br/> </div> @@ -249,17 +281,27 @@ export default class NotificationsTab extends React.Component { checked={notifyActive[2]} onChange={this.handleNotifyRadio.bind(this, 'none')} /> - {'Never'} + <FormattedMessage + id='user.settings.notifications.never' + defaultMessage='Never' + /> </label> </div> </div> ); - const extraInfo = <span>{'Desktop notifications are available on Firefox, Safari, and Chrome.'}</span>; + const extraInfo = ( + <span> + <FormattedMessage + id='user.settings.notifications.info' + defaultMessage='Desktop notification sounds are available on Firefox, Safari, Chrome, Internet Explorer, and Edge.' + /> + </span> + ); desktopSection = ( <SettingItemMax - title='Send desktop notifications' + title={formatMessage(holders.desktop)} extraInfo={extraInfo} inputs={inputs} submit={this.handleSubmit} @@ -270,11 +312,26 @@ export default class NotificationsTab extends React.Component { } else { let describe = ''; if (this.state.notifyLevel === 'mention') { - describe = 'Only for mentions and direct messages'; + describe = ( + <FormattedMessage + id='user.settings.notifications.onlyMentions' + defaultMessage='Only for mentions and direct messages' + /> + ); } else if (this.state.notifyLevel === 'none') { - describe = 'Never'; + describe = ( + <FormattedMessage + id='user.settings.notifications.never' + defaultMessage='Never' + /> + ); } else { - describe = 'For all activity'; + describe = ( + <FormattedMessage + id='user.settings.notification.allActivity' + defaultMessage='For all activity' + /> + ); } handleUpdateDesktopSection = function updateDesktopSection() { @@ -283,7 +340,7 @@ export default class NotificationsTab extends React.Component { desktopSection = ( <SettingItemMin - title='Send desktop notifications' + title={formatMessage(holders.desktop)} describe={describe} updateSection={handleUpdateDesktopSection} /> @@ -311,7 +368,10 @@ export default class NotificationsTab extends React.Component { checked={soundActive[0]} onChange={this.handleSoundRadio.bind(this, 'true')} /> - {'On'} + <FormattedMessage + id='user.settings.notifications.on' + defaultMessage='On' + /> </label> <br/> </div> @@ -322,18 +382,28 @@ export default class NotificationsTab extends React.Component { checked={soundActive[1]} onChange={this.handleSoundRadio.bind(this, 'false')} /> - {'Off'} + <FormattedMessage + id='user.settings.notifications.off' + defaultMessage='Off' + /> </label> <br/> </div> </div> ); - const extraInfo = <span>{'Desktop notification sounds are available on Firefox, Safari, Chrome, Internet Explorer, and Edge.'}</span>; + const extraInfo = ( + <span> + <FormattedMessage + id='user.settings.notifications.info' + defaultMessage='Desktop notification sounds are available on Firefox, Safari, Chrome, Internet Explorer, and Edge.' + /> + </span> + ); soundSection = ( <SettingItemMax - title='Desktop notification sounds' + title={formatMessage(holders.desktopSounds)} extraInfo={extraInfo} inputs={inputs} submit={this.handleSubmit} @@ -344,11 +414,26 @@ export default class NotificationsTab extends React.Component { } else { let describe = ''; if (!this.state.soundNeeded) { - describe = 'Please configure notification sounds in your browser settings'; + describe = ( + <FormattedMessage + id='user.settings.notification.soundConfig' + defaultMessage='Please configure notification sounds in your browser settings' + /> + ); } else if (this.state.enableSound === 'false') { - describe = 'Off'; + describe = ( + <FormattedMessage + id='user.settings.notifications.off' + defaultMessage='Off' + /> + ); } else { - describe = 'On'; + describe = ( + <FormattedMessage + id='user.settings.notifications.on' + defaultMessage='On' + /> + ); } handleUpdateSoundSection = function updateSoundSection() { @@ -357,7 +442,7 @@ export default class NotificationsTab extends React.Component { soundSection = ( <SettingItemMin - title='Desktop notification sounds' + title={formatMessage(holders.desktopSounds)} describe={describe} updateSection={handleUpdateSoundSection} disableOpen = {!this.state.soundNeeded} @@ -386,7 +471,10 @@ export default class NotificationsTab extends React.Component { checked={emailActive[0]} onChange={this.handleEmailRadio.bind(this, 'true')} /> - {'On'} + <FormattedMessage + id='user.settings.notifications.on' + defaultMessage='On' + /> </label> <br/> </div> @@ -397,17 +485,28 @@ export default class NotificationsTab extends React.Component { checked={emailActive[1]} onChange={this.handleEmailRadio.bind(this, 'false')} /> - {'Off'} + <FormattedMessage + id='user.settings.notifications.off' + defaultMessage='Off' + /> </label> <br/> </div> - <div><br/>{'Email notifications are sent for mentions and direct messages after you’ve been offline for more than 60 seconds or away from ' + global.window.mm_config.SiteName + ' for more than 5 minutes.'}</div> + <div><br/> + <FormattedMessage + id='user.settings.notifications.emailInfo' + defaultMessage='Email notifications are sent for mentions and direct messages after you’ve been offline for more than 60 seconds or away from {siteName} for more than 5 minutes.' + values={{ + siteName: global.window.mm_config.SiteName + }} + /> + </div> </div> ); emailSection = ( <SettingItemMax - title='Email notifications' + title={formatMessage(holders.emailNotifications)} inputs={inputs} submit={this.handleSubmit} server_error={serverError} @@ -417,9 +516,19 @@ export default class NotificationsTab extends React.Component { } else { let describe = ''; if (this.state.enableEmail === 'false') { - describe = 'Off'; + describe = ( + <FormattedMessage + id='user.settings.notifications.off' + defaultMessage='Off' + /> + ); } else { - describe = 'On'; + describe = ( + <FormattedMessage + id='user.settings.notifications.on' + defaultMessage='On' + /> + ); } handleUpdateEmailSection = function updateEmailSection() { @@ -428,7 +537,7 @@ export default class NotificationsTab extends React.Component { emailSection = ( <SettingItemMin - title='Email notifications' + title={formatMessage(holders.emailNotifications)} describe={describe} updateSection={handleUpdateEmailSection} /> @@ -459,7 +568,13 @@ export default class NotificationsTab extends React.Component { checked={this.state.firstNameKey} onChange={handleUpdateFirstNameKey} /> - {'Your case sensitive first name "' + user.first_name + '"'} + <FormattedMessage + id='user.settings.notifications.sensitiveName' + defaultMessage='Your case sensitive first name "{first_name}"' + values={{ + first_name: user.first_name + }} + /> </label> </div> </div> @@ -478,7 +593,13 @@ export default class NotificationsTab extends React.Component { checked={this.state.usernameKey} onChange={handleUpdateUsernameKey} /> - {'Your non-case sensitive username "' + user.username + '"'} + <FormattedMessage + id='user.settings.notifications.sensitiveUsername' + defaultMessage='Your non-case sensitive username "{username}"' + values={{ + username: user.username + }} + /> </label> </div> </div> @@ -496,7 +617,13 @@ export default class NotificationsTab extends React.Component { checked={this.state.mentionKey} onChange={handleUpdateMentionKey} /> - {'Your username mentioned "@' + user.username + '"'} + <FormattedMessage + id='user.settings.notifications.usernameMention' + defaultMessage='Your username mentioned "@{username}"' + values={{ + username: user.username + }} + /> </label> </div> </div> @@ -514,7 +641,10 @@ export default class NotificationsTab extends React.Component { checked={this.state.allKey} onChange={handleUpdateAllKey} /> - {'Team-wide mentions "@all"'} + <FormattedMessage + id='user.settings.notifications.teamWide' + defaultMessage='Team-wide mentions "@all"' + /> </label> </div> </div> @@ -532,7 +662,10 @@ export default class NotificationsTab extends React.Component { checked={this.state.channelKey} onChange={handleUpdateChannelKey} /> - {'Channel-wide mentions "@channel"'} + <FormattedMessage + id='user.settings.notifications.channelWide' + defaultMessage='Channel-wide mentions "@channel"' + /> </label> </div> </div> @@ -548,7 +681,10 @@ export default class NotificationsTab extends React.Component { checked={this.state.customKeysChecked} onChange={this.updateCustomMentionKeys} /> - {'Other non-case sensitive words, separated by commas:'} + <FormattedMessage + id='user.settings.notifications.sensitiveWords' + defaultMessage='Other non-case sensitive words, separated by commas:' + /> </label> </div> <input @@ -563,7 +699,7 @@ export default class NotificationsTab extends React.Component { keysSection = ( <SettingItemMax - title='Words that trigger mentions' + title={formatMessage(holders.wordsTrigger)} inputs={inputs} submit={this.handleSubmit} server_error={serverError} @@ -601,7 +737,12 @@ export default class NotificationsTab extends React.Component { if (describe.length > 0) { describe = describe.substring(0, describe.length - 2); } else { - describe = 'No words configured'; + describe = ( + <FormattedMessage + id='user.settings.notifications.noWords' + defaultMessage='No words configured' + /> + ); } handleUpdateKeysSection = function updateKeysSection() { @@ -610,7 +751,7 @@ export default class NotificationsTab extends React.Component { keysSection = ( <SettingItemMin - title='Words that trigger mentions' + title={formatMessage(holders.wordsTrigger)} describe={describe} updateSection={handleUpdateKeysSection} /> @@ -624,7 +765,7 @@ export default class NotificationsTab extends React.Component { type='button' className='close' data-dismiss='modal' - aria-label='Close' + aria-label={formatMessage(holders.close)} onClick={this.props.closeModal} > <span aria-hidden='true'>{'×'}</span> @@ -637,14 +778,22 @@ export default class NotificationsTab extends React.Component { className='modal-back' onClick={this.props.collapseModal} /> - {'Notification Settings'} + <FormattedMessage + id='user.settings.notifications.title' + defaultMessage='Notification Settings' + /> </h4> </div> <div ref='wrapper' className='user-settings' > - <h3 className='tab-header'>{'Notifications'}</h3> + <h3 className='tab-header'> + <FormattedMessage + id='user.settings.notifications.header' + defaultMessage='Notifications' + /> + </h3> <div className='divider-dark first'/> {desktopSection} <div className='divider-light'/> @@ -667,6 +816,7 @@ NotificationsTab.defaultProps = { activeTab: '' }; NotificationsTab.propTypes = { + intl: intlShape.isRequired, user: React.PropTypes.object, updateSection: React.PropTypes.func, updateTab: React.PropTypes.func, @@ -675,3 +825,5 @@ NotificationsTab.propTypes = { closeModal: React.PropTypes.func.isRequired, collapseModal: React.PropTypes.func.isRequired }; + +export default injectIntl(NotificationsTab);
\ No newline at end of file diff --git a/web/react/components/user_settings/user_settings_security.jsx b/web/react/components/user_settings/user_settings_security.jsx index 5a21abd19..0c9e722de 100644 --- a/web/react/components/user_settings/user_settings_security.jsx +++ b/web/react/components/user_settings/user_settings_security.jsx @@ -13,7 +13,40 @@ import * as Client from '../../utils/client.jsx'; import * as AsyncClient from '../../utils/async_client.jsx'; import Constants from '../../utils/constants.jsx'; -export default class SecurityTab extends React.Component { +import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl'; + +const holders = defineMessages({ + currentPasswordError: { + id: 'user.settings.security.currentPasswordError', + defaultMessage: 'Please enter your current password' + }, + passwordLengthError: { + id: 'user.settings.security.passwordLengthError', + defaultMessage: 'New passwords must be at least {chars} characters' + }, + passwordMatchError: { + id: 'user.settings.security.passwordMatchError', + defaultMessage: 'The new passwords you entered do not match' + }, + password: { + id: 'user.settings.security.password', + defaultMessage: 'Password' + }, + lastUpdated: { + id: 'user.settings.security.lastUpdated', + defaultMessage: 'Last updated {date} at {time}' + }, + method: { + id: 'user.settings.security.method', + defaultMessage: 'Sign-in Method' + }, + close: { + id: 'user.settings.security.close', + defaultMessage: 'Close' + } +}); + +class SecurityTab extends React.Component { constructor(props) { super(props); @@ -43,18 +76,19 @@ export default class SecurityTab extends React.Component { var newPassword = this.state.newPassword; var confirmPassword = this.state.confirmPassword; + const {formatMessage} = this.props.intl; if (currentPassword === '') { - this.setState({passwordError: 'Please enter your current password', serverError: ''}); + this.setState({passwordError: formatMessage(holders.currentPasswordError), serverError: ''}); return; } if (newPassword.length < Constants.MIN_PASSWORD_LENGTH) { - this.setState({passwordError: 'New passwords must be at least ' + Constants.MIN_PASSWORD_LENGTH + ' characters', serverError: ''}); + this.setState({passwordError: formatMessage(holders.passwordLengthError, {chars: Constants.MIN_PASSWORD_LENGTH}), serverError: ''}); return; } if (newPassword !== confirmPassword) { - this.setState({passwordError: 'The new passwords you entered do not match', serverError: ''}); + this.setState({passwordError: formatMessage(holders.passwordMatchError), serverError: ''}); return; } @@ -92,6 +126,7 @@ export default class SecurityTab extends React.Component { } createPasswordSection() { let updateSectionStatus; + const {formatMessage} = this.props.intl; if (this.props.activeSection === 'password' && this.props.user.auth_service === '') { const inputs = []; @@ -101,7 +136,12 @@ export default class SecurityTab extends React.Component { key='currentPasswordUpdateForm' className='form-group' > - <label className='col-sm-5 control-label'>{'Current Password'}</label> + <label className='col-sm-5 control-label'> + <FormattedMessage + id='user.settings.security.currentPassword' + defaultMessage='Current Password' + /> + </label> <div className='col-sm-7'> <input className='form-control' @@ -117,7 +157,12 @@ export default class SecurityTab extends React.Component { key='newPasswordUpdateForm' className='form-group' > - <label className='col-sm-5 control-label'>{'New Password'}</label> + <label className='col-sm-5 control-label'> + <FormattedMessage + id='user.settings.security.newPassword' + defaultMessage='New Password' + /> + </label> <div className='col-sm-7'> <input className='form-control' @@ -133,7 +178,12 @@ export default class SecurityTab extends React.Component { key='retypeNewPasswordUpdateForm' className='form-group' > - <label className='col-sm-5 control-label'>{'Retype New Password'}</label> + <label className='col-sm-5 control-label'> + <FormattedMessage + id='user.settings.security.retypePassword' + defaultMessage='Retype New Password' + /> + </label> <div className='col-sm-7'> <input className='form-control' @@ -153,7 +203,7 @@ export default class SecurityTab extends React.Component { return ( <SettingItemMax - title='Password' + title={formatMessage(holders.password)} inputs={inputs} submit={this.submitPassword} server_error={this.state.serverError} @@ -165,20 +215,16 @@ export default class SecurityTab extends React.Component { var describe; var d = new Date(this.props.user.last_password_update); - var hour = '12'; - if (d.getHours() % 12) { - hour = String(d.getHours() % 12); - } - var min = String(d.getMinutes()); - if (d.getMinutes() < 10) { - min = '0' + d.getMinutes(); - } var timeOfDay = ' am'; if (d.getHours() >= 12) { timeOfDay = ' pm'; } - describe = 'Last updated ' + Constants.MONTHS[d.getMonth()] + ' ' + d.getDate() + ', ' + d.getFullYear() + ' at ' + hour + ':' + min + timeOfDay; + const locale = global.window.mm_locale; + describe = formatMessage(holders.lastUpdated, { + date: d.toLocaleDateString(locale, {month: 'short', day: '2-digit', year: 'numeric'}), + time: d.toLocaleTimeString(locale, {hours12: true, hour: '2-digit', minute: '2-digit'}) + timeOfDay + }); updateSectionStatus = function updateSection() { this.props.updateSection('password'); @@ -186,7 +232,7 @@ export default class SecurityTab extends React.Component { return ( <SettingItemMin - title='Password' + title={formatMessage(holders.password)} describe={describe} updateSection={updateSectionStatus} /> @@ -208,7 +254,10 @@ export default class SecurityTab extends React.Component { className='btn btn-primary' href={'/' + teamName + '/claim?email=' + encodeURIComponent(user.email)} > - {'Switch to using email and password'} + <FormattedMessage + id='user.settings.security.switchEmail' + defaultMessage='Switch to using email and password' + /> </a> <br/> </div> @@ -223,7 +272,10 @@ export default class SecurityTab extends React.Component { className='btn btn-primary' href={'/' + teamName + '/claim?email=' + encodeURIComponent(user.email) + '&new_type=' + Constants.GITLAB_SERVICE} > - {'Switch to using GitLab SSO'} + <FormattedMessage + id='user.settings.security.switchGitlab' + defaultMessage='Switch to using GitLab SSO' + /> </a> <br/> </div> @@ -238,7 +290,10 @@ export default class SecurityTab extends React.Component { className='btn btn-primary' href={'/' + teamName + '/claim?email=' + encodeURIComponent(user.email) + '&new_type=' + Constants.GOOGLE_SERVICE} > - {'Switch to using Google SSO'} + <FormattedMessage + id='user.settings.security.switchGoogle' + defaultMessage='Switch to using Google SSO' + /> </a> <br/> </div> @@ -260,11 +315,18 @@ export default class SecurityTab extends React.Component { e.preventDefault(); }.bind(this); - const extraInfo = <span>{'You may only have one sign-in method at a time. Switching sign-in method will send an email notifying you if the change was successful.'}</span>; + const extraInfo = ( + <span> + <FormattedMessage + id='user.settings.security.oneSignin' + defaultMessage='You may only have one sign-in method at a time. Switching sign-in method will send an email notifying you if the change was successful.' + /> + </span> + ); return ( <SettingItemMax - title='Sign-in Method' + title={this.props.intl.formatMessage(holders.method)} extraInfo={extraInfo} inputs={inputs} server_error={this.state.serverError} @@ -277,14 +339,24 @@ export default class SecurityTab extends React.Component { this.props.updateSection('signin'); }.bind(this); - let describe = 'Email and Password'; + let describe = ( + <FormattedMessage + id='user.settings.security.emailPwd' + defaultMessage='Email and Password' + /> + ); if (this.props.user.auth_service === Constants.GITLAB_SERVICE) { - describe = 'GitLab SSO'; + describe = ( + <FormattedMessage + id='user.settings.security.gitlab' + defaultMessage='GitLab SSO' + /> + ); } return ( <SettingItemMin - title='Sign-in Method' + title={this.props.intl.formatMessage(holders.method)} describe={describe} updateSection={updateSectionStatus} /> @@ -309,7 +381,7 @@ export default class SecurityTab extends React.Component { type='button' className='close' data-dismiss='modal' - aria-label='Close' + aria-label={this.props.intl.formatMessage(holders.close)} onClick={this.props.closeModal} > <span aria-hidden='true'>{'×'}</span> @@ -322,11 +394,19 @@ export default class SecurityTab extends React.Component { className='modal-back' onClick={this.props.collapseModal} /> - {'Security Settings'} + <FormattedMessage + id='user.settings.security.title' + defaultMessage='Security Settings' + /> </h4> </div> <div className='user-settings'> - <h3 className='tab-header'>{'Security Settings'}</h3> + <h3 className='tab-header'> + <FormattedMessage + id='user.settings.security.title' + defaultMessage='Security Settings' + /> + </h3> <div className='divider-dark first'/> {passwordSection} <div className='divider-light'/> @@ -337,14 +417,22 @@ export default class SecurityTab extends React.Component { className='security-links theme' dialogType={AccessHistoryModal} > - <i className='fa fa-clock-o'></i>{'View Access History'} + <i className='fa fa-clock-o'></i> + <FormattedMessage + id='user.settings.security.viewHistory' + defaultMessage='View Access History' + /> </ToggleModalButton> <b> </b> <ToggleModalButton className='security-links theme' dialogType={ActivityLogModal} > - <i className='fa fa-clock-o'></i>{'View and Logout of Active Sessions'} + <i className='fa fa-clock-o'></i> + <FormattedMessage + id='user.settings.security.logoutActiveSessions' + defaultMessage='View and Logout of Active Sessions' + /> </ToggleModalButton> </div> </div> @@ -357,6 +445,7 @@ SecurityTab.defaultProps = { activeSection: '' }; SecurityTab.propTypes = { + intl: intlShape.isRequired, user: React.PropTypes.object, activeSection: React.PropTypes.string, updateSection: React.PropTypes.func, @@ -365,3 +454,5 @@ SecurityTab.propTypes = { collapseModal: React.PropTypes.func.isRequired, setEnforceFocus: React.PropTypes.func.isRequired }; + +export default injectIntl(SecurityTab);
\ No newline at end of file |