diff options
30 files changed, 837 insertions, 156 deletions
diff --git a/api/file.go b/api/file.go index 44ae775c9..7dcfc691f 100644 --- a/api/file.go +++ b/api/file.go @@ -398,13 +398,6 @@ func getFile(c *Context, w http.ResponseWriter, r *http.Request) { c.Err = model.NewLocAppError("getFile", "api.file.get_file.public_invalid.app_error", nil, "") return } - props := model.MapFromJson(strings.NewReader(data)) - - t, err := strconv.ParseInt(props["time"], 10, 64) - if err != nil || model.GetMillis()-t > 1000*60*60*24*7 { // one week - c.Err = model.NewLocAppError("getFile", "api.file.get_file.public_expired.app_error", nil, "") - return - } } else if !c.HasPermissionsToChannel(cchan, "getFile") { return } @@ -484,7 +477,6 @@ func getPublicLink(c *Context, w http.ResponseWriter, r *http.Request) { newProps := make(map[string]string) newProps["filename"] = filename - newProps["time"] = fmt.Sprintf("%v", model.GetMillis()) data := model.MapToJson(newProps) hash := model.HashPassword(fmt.Sprintf("%v:%v", data, utils.Cfg.FileSettings.PublicLinkSalt)) diff --git a/i18n/en.json b/i18n/en.json index 12741fc68..fafc29de7 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -456,10 +456,6 @@ "translation": "Could not find file." }, { - "id": "api.file.get_file.public_expired.app_error", - "translation": "The public link has expired" - }, - { "id": "api.file.get_file.public_invalid.app_error", "translation": "The public link does not appear to be valid" }, @@ -580,6 +576,10 @@ "translation": "Invalid license file." }, { + "id": "api.license.add_license.invalid_count.app_error", + "translation": "Unable to count total unique users." + }, + { "id": "api.license.add_license.no_file.app_error", "translation": "No file under 'license' in request" }, @@ -588,18 +588,14 @@ "translation": "Could not open license file" }, { - "id": "api.license.add_license.invalid_count.app_error", - "translation": "Unable to count total unique users." + "id": "api.license.add_license.save.app_error", + "translation": "License did not save properly." }, { "id": "api.license.add_license.unique_users.app_error", "translation": "This license only supports {{.Users}} users, when your system has {{.Count}} unique users. Unique users are counted distinctly by email address. You can see total user count under Site Reports -> View Statistics." }, { - "id": "api.license.add_license.save.app_error", - "translation": "License did not save properly." - }, - { "id": "api.license.init.debug", "translation": "Initializing license api routes" }, @@ -1552,10 +1548,6 @@ "translation": "Could not encode profile image" }, { - "id": "api.user.upload_profile_user.upload_profile.app_error", - "translation": "Couldn't upload profile image" - }, - { "id": "api.user.upload_profile_user.no_file.app_error", "translation": "No file under 'image' in request" }, @@ -1576,6 +1568,10 @@ "translation": "Unable to upload profile image. File is too large." }, { + "id": "api.user.upload_profile_user.upload_profile.app_error", + "translation": "Couldn't upload profile image" + }, + { "id": "api.web_conn.new_web_conn.last_activity.error", "translation": "Failed to update LastActivityAt for user_id=%v and session_id=%v, err=%v" }, @@ -2776,6 +2772,10 @@ "translation": "Cannot update existing session" }, { + "id": "store.sql_session.update_device_id.app_error", + "translation": "We couldn't update the device id" + }, + { "id": "store.sql_session.update_last_activity.app_error", "translation": "We couldn't update the last_activity_at" }, @@ -2784,10 +2784,6 @@ "translation": "We couldn't update the roles" }, { - "id": "store.sql_session.update_device_id.app_error", - "translation": "We couldn't update the device id" - }, - { "id": "store.sql_system.get.app_error", "translation": "We encountered an error finding the system properties" }, @@ -3336,6 +3332,10 @@ "translation": "Home" }, { + "id": "web.root.singup_info", + "translation": "All team communication in one place, searchable and accessible anywhere" + }, + { "id": "web.root.singup_title", "translation": "Signup" }, @@ -3391,4 +3391,4 @@ "id": "web.watcher_fail.error", "translation": "Failed to add directory to watcher %v" } -]
\ No newline at end of file +] diff --git a/i18n/es.json b/i18n/es.json index c4e7a552c..511cc5f28 100644 --- a/i18n/es.json +++ b/i18n/es.json @@ -456,10 +456,6 @@ "translation": "No se encontró el archivo." }, { - "id": "api.file.get_file.public_expired.app_error", - "translation": "El enlace público ha expirado" - }, - { "id": "api.file.get_file.public_invalid.app_error", "translation": "El enlace público parece ser inválido" }, @@ -3336,6 +3332,10 @@ "translation": "Inicio" }, { + "id": "web.root.singup_info", + "translation": "Todas las comunicaciones del equipo en un sólo lugar, con búsquedas y accesible desde cualquier parte" + }, + { "id": "web.root.singup_title", "translation": "Registrar" }, @@ -3391,4 +3391,4 @@ "id": "web.watcher_fail.error", "translation": "Falla al agregar el directorio a ser vigilado %v" } -]
\ No newline at end of file +] diff --git a/web/react/components/authorize.jsx b/web/react/components/authorize.jsx index 32e39fbff..90cbe3289 100644 --- a/web/react/components/authorize.jsx +++ b/web/react/components/authorize.jsx @@ -3,6 +3,8 @@ import * as Client from '../utils/client.jsx'; +import {FormattedMessage} from 'mm-intl'; + export default class Authorize extends React.Component { constructor(props) { super(props); @@ -35,25 +37,55 @@ export default class Authorize extends React.Component { return ( <div className='authorize-box'> <div className='authorize-inner'> - <h3>{'An application would like to connect to your '}{this.props.teamName}{' account'}</h3> - <label>{'The app '}{this.props.appName}{' would like the ability to access and modify your basic information.'}</label> + <h3> + <FormattedMessage + id='authorize.title' + defaultMessage='An application would like to connect to your {teamName} account' + values={{ + teamName: this.props.teamName + }} + /> + </h3> + <label> + <FormattedMessage + id='authorize.app' + defaultMessage='The app {appName} would like the ability to access and modify your basic information.' + values={{ + appName: this.props.appName + }} + /> + </label> <br/> <br/> - <label>{'Allow '}{this.props.appName}{' access?'}</label> + <label> + <FormattedMessage + id='authorize.access' + defaultMessage='Allow {appName} access?' + values={{ + appName: this.props.appName + }} + /> + </label> <br/> <button type='submit' className='btn authorize-btn' onClick={this.handleDeny} > - {'Deny'} + <FormattedMessage + id='authorize.deny' + defaultMessage='Deny' + /> </button> <button type='submit' className='btn btn-primary authorize-btn' onClick={this.handleAllow} > - {'Allow'} + <FormattedMessage + id='authorize.allow' + defaultMessage='Allow' + /> </button> </div> </div> diff --git a/web/react/components/docs.jsx b/web/react/components/docs.jsx index 188ca340b..6d3a109c2 100644 --- a/web/react/components/docs.jsx +++ b/web/react/components/docs.jsx @@ -13,7 +13,7 @@ export default class Docs extends React.Component { const errorState = {text: '## 404'}; if (props.site) { - $.get('/static/help/' + props.site + '.md').then((response) => { + $.get(`/static/help/${props.site}_${global.window.mm_locale}.md`).then((response) => { this.setState({text: response}); }, () => { this.setState(errorState); diff --git a/web/react/components/find_team.jsx b/web/react/components/find_team.jsx index 94ca48dbf..3ff9787ad 100644 --- a/web/react/components/find_team.jsx +++ b/web/react/components/find_team.jsx @@ -4,7 +4,20 @@ import * as utils from '../utils/utils.jsx'; import * as client from '../utils/client.jsx'; -export default class FindTeam extends React.Component { +import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'mm-intl'; + +var holders = defineMessages({ + submitError: { + id: 'find_team.submitError', + defaultMessage: 'Please enter a valid email address' + }, + placeholder: { + id: 'find_team.placeholder', + defaultMessage: 'you@domain.com' + } +}); + +class FindTeam extends React.Component { constructor(props) { super(props); this.state = {}; @@ -19,7 +32,7 @@ export default class FindTeam extends React.Component { var email = ReactDOM.findDOMNode(this.refs.email).value.trim().toLowerCase(); if (!email || !utils.isEmail(email)) { - state.email_error = 'Please enter a valid email address'; + state.email_error = this.props.intl.formatMessage(holders.submitError); this.setState(state); return; } @@ -50,25 +63,50 @@ export default class FindTeam extends React.Component { if (this.state.sent) { return ( <div> - <h4>{'Find Your teams'}</h4> - <p>{'An email was sent with links to any teams to which you are a member.'}</p> + <h4> + <FormattedMessage + id='find_team.findTitle' + defaultMessage='Find Your Team' + /> + </h4> + <p> + <FormattedMessage + id='find_team.findDescription' + defaultMessage='An email was sent with links to any teams to which you are a member.' + /> + </p> </div> ); } return ( <div> - <h4>Find Your Team</h4> + <h4> + <FormattedMessage + id='find_team.findTitle' + defaultMessage='Find Your Team' + /> + </h4> <form onSubmit={this.handleSubmit}> - <p>{'Get an email with links to any teams to which you are a member.'}</p> + <p> + <FormattedMessage + id='find_team.getLinks' + defaultMessage='Get an email with links to any teams to which you are a member.' + /> + </p> <div className='form-group'> - <label className='control-label'>Email</label> + <label className='control-label'> + <FormattedMessage + id='find_team.email' + defaultMessage='Email' + /> + </label> <div className={emailErrorClass}> <input type='text' ref='email' className='form-control' - placeholder='you@domain.com' + placeholder={this.props.intl.formatMessage(holders.placeholder)} maxLength='128' spellCheck='false' /> @@ -79,10 +117,19 @@ export default class FindTeam extends React.Component { className='btn btn-md btn-primary' type='submit' > - Send + <FormattedMessage + id='find_team.send' + defaultMessage='Send' + /> </button> </form> </div> ); } } + +FindTeam.propTypes = { + intl: intlShape.isRequired +}; + +export default injectIntl(FindTeam);
\ No newline at end of file diff --git a/web/react/components/login.jsx b/web/react/components/login.jsx index 3c1d66334..c4f530af0 100644 --- a/web/react/components/login.jsx +++ b/web/react/components/login.jsx @@ -7,7 +7,7 @@ import LoginLdap from './login_ldap.jsx'; import * as Utils from '../utils/utils.jsx'; import Constants from '../utils/constants.jsx'; -var FormattedMessage = ReactIntl.FormattedMessage; +import {FormattedMessage} from 'mm-intl'; export default class Login extends React.Component { constructor(props) { @@ -24,10 +24,16 @@ export default class Login extends React.Component { loginMessage.push( <a className='btn btn-custom-login gitlab' + key='gitlab' href={'/' + teamName + '/login/gitlab'} > <span className='icon' /> - <span>{'with GitLab'}</span> + <span> + <FormattedMessage + id='login.gitlab' + defaultMessage='with GitLab' + /> + </span> </a> ); } @@ -36,10 +42,16 @@ export default class Login extends React.Component { loginMessage.push( <a className='btn btn-custom-login google' + key='google' href={'/' + teamName + '/login/google'} > <span className='icon' /> - <span>{'with Google Apps'}</span> + <span> + <FormattedMessage + id='login.google' + defaultMessage='with Google Apps' + /> + </span> </a> ); } @@ -49,9 +61,19 @@ export default class Login extends React.Component { if (extraParam) { let msg; if (extraParam === Constants.SIGNIN_CHANGE) { - msg = ' Sign-in method changed successfully'; + msg = ( + <FormattedMessage + id='login.changed' + defaultMessage=' Sign-in method changed successfully' + /> + ); } else if (extraParam === Constants.SIGNIN_VERIFIED) { - msg = ' Email Verified'; + msg = ( + <FormattedMessage + id='login.verified' + defaultMessage=' Email Verified' + /> + ); } if (msg != null) { @@ -78,7 +100,12 @@ export default class Login extends React.Component { <div> {loginMessage} <div className='or__container'> - <span>{'or'}</span> + <span> + <FormattedMessage + id='login.or' + defaultMessage='or' + /> + </span> </div> </div> ); @@ -90,7 +117,7 @@ export default class Login extends React.Component { <div className='form-group'> <a href={'/' + teamName + '/reset_password'}> <FormattedMessage - id='login.forgot_password' + id='login.forgot' defaultMessage='I forgot my password' /> </a> @@ -102,12 +129,19 @@ export default class Login extends React.Component { if (this.props.inviteId) { userSignUp = ( <div> - <span>{`Don't have an account? `} + <span> + <FormattedMessage + id='login.noAccount' + defaultMessage="Don't have an account? " + /> <a href={'/signup_user_complete/?id=' + this.props.inviteId} className='signup-team-login' > - {'Create one now'} + <FormattedMessage + id='login.create' + defaultMessage='Create one now' + /> </a> </span> </div> @@ -122,7 +156,10 @@ export default class Login extends React.Component { href='/' className='signup-team-login' > - {'Create a new team'} + <FormattedMessage + id='login.createTeam' + defaultMessage='Create a new team' + /> </a> </div> ); @@ -144,7 +181,7 @@ export default class Login extends React.Component { <span> <a href='/find_team'> <FormattedMessage - id='login.find_teams' + id='login.find' defaultMessage='Find your other teams' /> </a></span> @@ -154,9 +191,22 @@ export default class Login extends React.Component { return ( <div className='signup-team__container'> - <h5 className='margin--less'>{'Sign in to:'}</h5> + <h5 className='margin--less'> + <FormattedMessage + id='login.signTo' + defaultMessage='Sign in to:' + /> + </h5> <h2 className='signup-team__name'>{teamDisplayName}</h2> - <h2 className='signup-team__subdomain'>{'on '}{global.window.mm_config.SiteName}</h2> + <h2 className='signup-team__subdomain'> + <FormattedMessage + id='login.on' + defaultMessage='on {siteName}' + values={{ + siteName: global.window.mm_config.SiteName + }} + /> + </h2> {extraBox} {loginMessage} {emailSignup} diff --git a/web/react/components/login_email.jsx b/web/react/components/login_email.jsx index cfe34d1c7..cf1e1bc40 100644 --- a/web/react/components/login_email.jsx +++ b/web/react/components/login_email.jsx @@ -5,7 +5,32 @@ import * as Utils from '../utils/utils.jsx'; import * as Client from '../utils/client.jsx'; import UserStore from '../stores/user_store.jsx'; -export default class LoginEmail extends React.Component { +import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'mm-intl'; + +var holders = defineMessages({ + badTeam: { + id: 'login_email.badTeam', + defaultMessage: 'Bad team name' + }, + emailReq: { + id: 'login_email.emailReq', + defaultMessage: 'An email is required' + }, + pwdReq: { + id: 'login_email.pwdReq', + defaultMessage: 'A password is required' + }, + email: { + id: 'login_email.email', + defaultMessage: 'Email' + }, + pwd: { + id: 'login_email.pwd', + defaultMessage: 'Password' + } +}); + +class LoginEmail extends React.Component { constructor(props) { super(props); @@ -17,25 +42,26 @@ export default class LoginEmail extends React.Component { } handleSubmit(e) { e.preventDefault(); + const {formatMessage} = this.props.intl; var state = {}; const name = this.props.teamName; if (!name) { - state.serverError = 'Bad team name'; + state.serverError = formatMessage(holders.badTeam); this.setState(state); return; } const email = this.refs.email.value.trim(); if (!email) { - state.serverError = 'An email is required'; + state.serverError = formatMessage(holders.emailReq); this.setState(state); return; } const password = this.refs.password.value.trim(); if (!password) { - state.serverError = 'A password is required'; + state.serverError = formatMessage(holders.pwdReq); this.setState(state); return; } @@ -55,7 +81,7 @@ export default class LoginEmail extends React.Component { } }, (err) => { - if (err.message === 'Login failed because email address has not been verified') { + if (err.id === 'api.user.login.not_verified.app_error') { window.location.href = '/verify_email?teamname=' + encodeURIComponent(name) + '&email=' + encodeURIComponent(email); return; } @@ -87,6 +113,7 @@ export default class LoginEmail extends React.Component { priorEmail = decodeURIComponent(emailParam); } + const {formatMessage} = this.props.intl; return ( <form onSubmit={this.handleSubmit}> <div className='signup__email-container'> @@ -101,7 +128,7 @@ export default class LoginEmail extends React.Component { name='email' defaultValue={priorEmail} ref='email' - placeholder='Email' + placeholder={formatMessage(holders.email)} spellCheck='false' /> </div> @@ -112,7 +139,7 @@ export default class LoginEmail extends React.Component { className='form-control' name='password' ref='password' - placeholder='Password' + placeholder={formatMessage(holders.pwd)} spellCheck='false' /> </div> @@ -121,7 +148,10 @@ export default class LoginEmail extends React.Component { type='submit' className='btn btn-primary' > - {'Sign in'} + <FormattedMessage + id='login_email.signin' + defaultMessage='Sign in' + /> </button> </div> </div> @@ -133,5 +163,8 @@ LoginEmail.defaultProps = { }; LoginEmail.propTypes = { + intl: intlShape.isRequired, teamName: React.PropTypes.string.isRequired }; + +export default injectIntl(LoginEmail);
\ No newline at end of file diff --git a/web/react/components/login_ldap.jsx b/web/react/components/login_ldap.jsx index 1e0e32f4f..d67f15fa5 100644 --- a/web/react/components/login_ldap.jsx +++ b/web/react/components/login_ldap.jsx @@ -4,7 +4,32 @@ import * as Utils from '../utils/utils.jsx'; import * as Client from '../utils/client.jsx'; -export default class LoginLdap extends React.Component { +import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'mm-intl'; + +const holders = defineMessages({ + badTeam: { + id: 'login_ldap.badTeam', + defaultMessage: 'Bad team name' + }, + idReq: { + id: 'login_ldap.idlReq', + defaultMessage: 'An LDAP ID is required' + }, + pwdReq: { + id: 'login_ldap.pwdReq', + defaultMessage: 'An LDAP password is required' + }, + username: { + id: 'login_ldap.username', + defaultMessage: 'LDAP Username' + }, + pwd: { + id: 'login_ldap.pwd', + defaultMessage: 'LDAP Password' + } +}); + +class LoginLdap extends React.Component { constructor(props) { super(props); @@ -16,25 +41,26 @@ export default class LoginLdap extends React.Component { } handleSubmit(e) { e.preventDefault(); + const {formatMessage} = this.props.intl; var state = {}; const teamName = this.props.teamName; if (!teamName) { - state.serverError = 'Bad team name'; + state.serverError = formatMessage(holders.badTeam); this.setState(state); return; } const id = this.refs.id.value.trim(); if (!id) { - state.serverError = 'An LDAP ID is required'; + state.serverError = formatMessage(holders.idReq); this.setState(state); return; } const password = this.refs.password.value.trim(); if (!password) { - state.serverError = 'An LDAP password is required'; + state.serverError = formatMessage(holders.pwdReq); this.setState(state); return; } @@ -64,7 +90,7 @@ export default class LoginLdap extends React.Component { serverError = <label className='control-label'>{this.state.serverError}</label>; errorClass = ' has-error'; } - + const {formatMessage} = this.props.intl; return ( <form onSubmit={this.handleSubmit}> <div className='signup__email-container'> @@ -76,7 +102,7 @@ export default class LoginLdap extends React.Component { autoFocus={true} className='form-control' ref='id' - placeholder='LDAP Username' + placeholder={formatMessage(holders.username)} spellCheck='false' /> </div> @@ -85,7 +111,7 @@ export default class LoginLdap extends React.Component { type='password' className='form-control' ref='password' - placeholder='LDAP Password' + placeholder={formatMessage(holders.pwd)} spellCheck='false' /> </div> @@ -94,7 +120,10 @@ export default class LoginLdap extends React.Component { type='submit' className='btn btn-primary' > - {'Sign in'} + <FormattedMessage + id='login_ldap.signin' + defaultMessage='Sign in' + /> </button> </div> </div> @@ -106,5 +135,8 @@ LoginLdap.defaultProps = { }; LoginLdap.propTypes = { + intl: intlShape.isRequired, teamName: React.PropTypes.string.isRequired }; + +export default injectIntl(LoginLdap);
\ No newline at end of file diff --git a/web/react/components/password_reset_form.jsx b/web/react/components/password_reset_form.jsx index 8063db05a..380dbe973 100644 --- a/web/react/components/password_reset_form.jsx +++ b/web/react/components/password_reset_form.jsx @@ -4,7 +4,24 @@ import * as Client from '../utils/client.jsx'; import Constants from '../utils/constants.jsx'; -export default class PasswordResetForm extends React.Component { +import {injectIntl, intlShape, defineMessages, FormattedMessage, FormattedHTMLMessage} from 'mm-intl'; + +const holders = defineMessages({ + error: { + id: 'password_form.error', + defaultMessage: 'Please enter at least {chars} characters.' + }, + update: { + id: 'password_form.update', + defaultMessage: 'Your password has been updated successfully.' + }, + pwd: { + id: 'password_form.pwd', + defaultMessage: 'Password' + } +}); + +class PasswordResetForm extends React.Component { constructor(props) { super(props); @@ -14,11 +31,13 @@ export default class PasswordResetForm extends React.Component { } handlePasswordReset(e) { e.preventDefault(); + + const {formatMessage} = this.props.intl; var state = {}; var password = ReactDOM.findDOMNode(this.refs.password).value.trim(); if (!password || password.length < Constants.MIN_PASSWORD_LENGTH) { - state.error = 'Please enter at least ' + Constants.MIN_PASSWORD_LENGTH + ' characters.'; + state.error = formatMessage(holders.error, {chars: Constants.MIN_PASSWORD_LENGTH}); this.setState(state); return; } @@ -34,7 +53,7 @@ export default class PasswordResetForm extends React.Component { Client.resetPassword(data, function resetSuccess() { - this.setState({error: null, updateText: 'Your password has been updated successfully.'}); + this.setState({error: null, updateText: formatMessage(holders.update)}); }.bind(this), function resetFailure(err) { this.setState({error: err.message, updateText: null}); @@ -44,7 +63,15 @@ export default class PasswordResetForm extends React.Component { render() { var updateText = null; if (this.state.updateText) { - updateText = <div className='form-group'><br/><label className='control-label reset-form'>{this.state.updateText} Click <a href={'/' + this.props.teamName + '/login'}>here</a> to log in.</label></div>; + updateText = (<div className='form-group'><br/><label className='control-label reset-form'>{this.state.updateText} + <FormattedHTMLMessage + id='password_form.click' + defaultMessage='Click <a href={url}>here</a> to log in.' + values={{ + url: '/' + this.props.teamName + '/login' + }} + /> + </label></div>); } var error = null; @@ -57,19 +84,34 @@ export default class PasswordResetForm extends React.Component { formClass += ' has-error'; } + const {formatMessage} = this.props.intl; return ( <div className='col-sm-12'> <div className='signup-team__container'> - <h3>{'Password Reset'}</h3> + <h3> + <FormattedMessage + id='password_form.title' + defaultMessage='Password Reset' + /> + </h3> <form onSubmit={this.handlePasswordReset}> - <p>{'Enter a new password for your ' + this.props.teamDisplayName + ' ' + global.window.mm_config.SiteName + ' account.'}</p> + <p> + <FormattedMessage + id='password_form.enter' + defaultMessage='Enter a new password for your {teamDisplayName} {siteName} account.' + values={{ + teamDisplayName: this.props.teamDisplayName, + siteName: global.window.mm_config.SiteName + }} + /> + </p> <div className={formClass}> <input type='password' className='form-control' name='password' ref='password' - placeholder='Password' + placeholder={formatMessage(holders.pwd)} spellCheck='false' /> </div> @@ -78,7 +120,10 @@ export default class PasswordResetForm extends React.Component { type='submit' className='btn btn-primary' > - {'Change my password'} + <FormattedMessage + id='password_form.change' + defaultMessage='Change my password' + /> </button> {updateText} </form> @@ -95,8 +140,11 @@ PasswordResetForm.defaultProps = { data: '' }; PasswordResetForm.propTypes = { + intl: intlShape.isRequired, teamName: React.PropTypes.string, teamDisplayName: React.PropTypes.string, hash: React.PropTypes.string, data: React.PropTypes.string }; + +export default injectIntl(PasswordResetForm);
\ No newline at end of file diff --git a/web/react/components/password_reset_send_link.jsx b/web/react/components/password_reset_send_link.jsx index 051b8b02c..8cc8a050d 100644 --- a/web/react/components/password_reset_send_link.jsx +++ b/web/react/components/password_reset_send_link.jsx @@ -4,7 +4,28 @@ import * as Utils from '../utils/utils.jsx'; import * as client from '../utils/client.jsx'; -export default class PasswordResetSendLink extends React.Component { +import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'mm-intl'; + +const holders = defineMessages({ + error: { + id: 'password_send.error', + defaultMessage: 'Please enter a valid email address.' + }, + link: { + id: 'password_send.link', + defaultMessage: '<p>A password reset link has been sent to <b>{email}</b> for your <b>{teamDisplayName}</b> team on {hostname}.</p>' + }, + checkInbox: { + id: 'password_send.checkInbox', + defaultMessage: 'Please check your inbox.' + }, + email: { + id: 'password_send.email', + defaultMessage: 'Email' + } +}); + +class PasswordResetSendLink extends React.Component { constructor(props) { super(props); @@ -15,10 +36,11 @@ export default class PasswordResetSendLink extends React.Component { handleSendLink(e) { e.preventDefault(); var state = {}; + const {formatMessage} = this.props.intl; var email = ReactDOM.findDOMNode(this.refs.email).value.trim().toLowerCase(); if (!email || !Utils.isEmail(email)) { - state.error = 'Please enter a valid email address.'; + state.error = formatMessage(holders.error); this.setState(state); return; } @@ -32,7 +54,7 @@ export default class PasswordResetSendLink extends React.Component { client.sendPasswordReset(data, function passwordResetSent() { - this.setState({error: null, updateText: <p>A password reset link has been sent to <b>{email}</b> for your <b>{this.props.teamDisplayName}</b> team on {window.location.hostname}.</p>, moreUpdateText: 'Please check your inbox.'}); + this.setState({error: null, updateText: formatMessage(holders.link, {email: email, teamDisplayName: this.props.teamDisplayName, hostname: window.location.hostname}), moreUpdateText: formatMessage(holders.checkInbox)}); $(ReactDOM.findDOMNode(this.refs.reset_form)).hide(); }.bind(this), function passwordResetFailedToSend(err) { @@ -43,7 +65,12 @@ export default class PasswordResetSendLink extends React.Component { render() { var updateText = null; if (this.state.updateText) { - updateText = <div className='reset-form alert alert-success'>{this.state.updateText}{this.state.moreUpdateText}</div>; + updateText = ( + <div className='reset-form alert alert-success' + dangerouslySetInnerHTML={{__html: this.state.updateText + this.state.moreUpdateText}} + > + </div> + ); } var error = null; @@ -56,23 +83,37 @@ export default class PasswordResetSendLink extends React.Component { formClass += ' has-error'; } + const {formatMessage} = this.props.intl; return ( <div className='col-sm-12'> <div className='signup-team__container'> - <h3>Password Reset</h3> + <h3> + <FormattedMessage + id='password_send.title' + defaultMessage='Password Reset' + /> + </h3> {updateText} <form onSubmit={this.handleSendLink} ref='reset_form' > - <p>{'To reset your password, enter the email address you used to sign up for ' + this.props.teamDisplayName + '.'}</p> + <p> + <FormattedMessage + id='password_send.description' + defaultMessage='To reset your password, enter the email address you used to sign up for {teamName}.' + values={{ + teamName: this.props.teamDisplayName + }} + /> + </p> <div className={formClass}> <input type='email' className='form-control' name='email' ref='email' - placeholder='Email' + placeholder={formatMessage(holders.email)} spellCheck='false' /> </div> @@ -81,7 +122,10 @@ export default class PasswordResetSendLink extends React.Component { type='submit' className='btn btn-primary' > - Reset my password + <FormattedMessage + id='password_send.reset' + defaultMessage='Reset my password' + /> </button> </form> </div> @@ -95,6 +139,9 @@ PasswordResetSendLink.defaultProps = { teamDisplayName: '' }; PasswordResetSendLink.propTypes = { + intl: intlShape.isRequired, teamName: React.PropTypes.string, teamDisplayName: React.PropTypes.string }; + +export default injectIntl(PasswordResetSendLink);
\ No newline at end of file diff --git a/web/react/components/post_info.jsx b/web/react/components/post_info.jsx index 26bd6adde..73b47024c 100644 --- a/web/react/components/post_info.jsx +++ b/web/react/components/post_info.jsx @@ -22,6 +22,26 @@ export default class PostInfo extends React.Component { this.handlePermalinkCopy = this.handlePermalinkCopy.bind(this); } + createReplyLink() { + if (this.props.allowReply === 'true') { + var hideReply = ''; + + if (this.props.commentCount >= 1) { + hideReply = ' post__reply--hide'; + } + + return ( + <div className={'post__reply' + hideReply}> + <a + onClick={this.props.handleCommentClick} + href='#' + > + <span dangerouslySetInnerHTML={{__html: Constants.REPLY_ICON}}/> + </a> + </div> + ); + } + } createDropdown() { var post = this.props.post; var isOwner = UserStore.getCurrentId() === post.user_id; @@ -42,23 +62,6 @@ export default class PostInfo extends React.Component { dataComments = this.props.commentCount; } - if (this.props.allowReply === 'true') { - dropdownContents.push( - <li - key='replyLink' - role='presentation' - > - <a - className='link__reply theme' - href='#' - onClick={this.props.handleCommentClick} - > - {'Reply'} - </a> - </li> - ); - } - dropdownContents.push( <li key='copyLink' @@ -181,6 +184,7 @@ export default class PostInfo extends React.Component { } var dropdown = this.createDropdown(); + var replyLink = this.createReplyLink(); const permalink = TeamStore.getCurrentTeamUrl() + '/pl/' + post.id; const copyButtonText = this.state.copiedLink ? (<div>{'Copy '}<i className='fa fa-check'/></div>) : 'Copy'; @@ -223,6 +227,7 @@ export default class PostInfo extends React.Component { /> </li> <li className='col col__reply'> + {replyLink} <div className='dropdown' ref='dotMenu' diff --git a/web/react/components/signup_team.jsx b/web/react/components/signup_team.jsx index a554427d5..098e9f65a 100644 --- a/web/react/components/signup_team.jsx +++ b/web/react/components/signup_team.jsx @@ -6,6 +6,8 @@ import EmailSignUpPage from './team_signup_with_email.jsx'; import SSOSignupPage from './team_signup_with_sso.jsx'; import Constants from '../utils/constants.jsx'; +import {FormattedMessage} from 'mm-intl'; + export default class TeamSignUp extends React.Component { constructor(props) { super(props); @@ -43,12 +45,24 @@ export default class TeamSignUp extends React.Component { if (global.window.mm_config.EnableTeamListing === 'true') { if (this.props.teams.length === 0) { if (global.window.mm_config.EnableTeamCreation !== 'true') { - teamListing = (<div>{'There are no teams include in the Team Directory and team creation has been disabled.'}</div>); + teamListing = ( + <div> + <FormattedMessage + id='signup_team.noTeams' + defaultMessage='There are no teams include in the Team Directory and team creation has been disabled.' + /> + </div> + ); } } else { teamListing = ( <div> - <h4>{'Choose a Team'}</h4> + <h4> + <FormattedMessage + id='signup_team.choose' + defaultMessage='Choose a Team' + /> + </h4> <div className='signup-team-all'> { this.props.teams.map((team) => { @@ -71,7 +85,12 @@ export default class TeamSignUp extends React.Component { }) } </div> - <h4>{'Or Create a Team'}</h4> + <h4> + <FormattedMessage + id='signup_team.createTeam' + defaultMessage='Or Create a Team' + /> + </h4> </div> ); } @@ -79,7 +98,14 @@ export default class TeamSignUp extends React.Component { if (global.window.mm_config.EnableTeamCreation !== 'true') { if (teamListing == null) { - return (<div>{'Team creation has been disabled. Please contact an administrator for access.'}</div>); + return ( + <div> + <FormattedMessage + id='signup_team.disabled' + defaultMessage='Team creation has been disabled. Please contact an administrator for access.' + /> + </div> + ); } return ( @@ -122,7 +148,14 @@ export default class TeamSignUp extends React.Component { </div> ); } else if (this.state.page === 'none') { - return (<div>{'No team creation method has been enabled. Please contact an administrator for access.'}</div>); + return ( + <div> + <FormattedMessage + id='signup_team.none' + defaultMessage='No team creation method has been enabled. Please contact an administrator for access.' + /> + </div> + ); } } } diff --git a/web/react/components/team_signup_choose_auth.jsx b/web/react/components/team_signup_choose_auth.jsx index 19b9750b3..2dc67e92e 100644 --- a/web/react/components/team_signup_choose_auth.jsx +++ b/web/react/components/team_signup_choose_auth.jsx @@ -1,6 +1,8 @@ // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. +import {FormattedMessage} from 'mm-intl'; + export default class ChooseAuthPage extends React.Component { constructor(props) { super(props); @@ -12,6 +14,7 @@ export default class ChooseAuthPage extends React.Component { buttons.push( <a className='btn btn-custom-login gitlab btn-full' + key='gitlab' href='#' onClick={ function clickGit(e) { @@ -21,7 +24,12 @@ export default class ChooseAuthPage extends React.Component { } > <span className='icon' /> - <span>{'Create new team with GitLab Account'}</span> + <span> + <FormattedMessage + id='choose_auth_page.gitlabCreate' + defaultMessage='Create new team with GitLab Account' + /> + </span> </a> ); } @@ -30,6 +38,7 @@ export default class ChooseAuthPage extends React.Component { buttons.push( <a className='btn btn-custom-login google btn-full' + key='google' href='#' onClick={ (e) => { @@ -39,7 +48,12 @@ export default class ChooseAuthPage extends React.Component { } > <span className='icon' /> - <span>{'Create new team with Google Apps Account'}</span> + <span> + <FormattedMessage + id='choose_auth_page.googleCreate' + defaultMessage='Create new team with Google Apps Account' + /> + </span> </a> ); } @@ -48,6 +62,7 @@ export default class ChooseAuthPage extends React.Component { buttons.push( <a className='btn btn-custom-login email btn-full' + key='email' href='#' onClick={ function clickEmail(e) { @@ -57,20 +72,37 @@ export default class ChooseAuthPage extends React.Component { } > <span className='fa fa-envelope' /> - <span>{'Create new team with email address'}</span> + <span> + <FormattedMessage + id='choose_auth_page.emailCreate' + defaultMessage='Create new team with email address' + /> + </span> </a> ); } if (buttons.length === 0) { - buttons = <span>{'No sign-up methods configured, please contact your system administrator.'}</span>; + buttons = ( + <span> + <FormattedMessage + id='choose_auth_page.noSignup' + defaultMessage='No sign-up methods configured, please contact your system administrator.' + /> + </span> + ); } return ( <div> {buttons} <div className='form-group margin--extra-2x'> - <span><a href='/find_team'>{'Find my teams'}</a></span> + <span><a href='/find_team'> + <FormattedMessage + id='choose_auth_page.find' + defaultMessage='Find my teams' + /> + </a></span> </div> </div> ); diff --git a/web/react/components/team_signup_with_email.jsx b/web/react/components/team_signup_with_email.jsx index 4150a0013..7dd645b25 100644 --- a/web/react/components/team_signup_with_email.jsx +++ b/web/react/components/team_signup_with_email.jsx @@ -4,7 +4,20 @@ import * as Utils from '../utils/utils.jsx'; import * as Client from '../utils/client.jsx'; -export default class EmailSignUpPage extends React.Component { +import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'mm-intl'; + +const holders = defineMessages({ + emailError: { + id: 'email_signup.emailError', + defaultMessage: 'Please enter a valid email address' + }, + address: { + id: 'email_signup.address', + defaultMessage: 'Email Address' + } +}); + +class EmailSignUpPage extends React.Component { constructor() { super(); @@ -20,7 +33,7 @@ export default class EmailSignUpPage extends React.Component { team.email = ReactDOM.findDOMNode(this.refs.email).value.trim().toLowerCase(); if (!team.email || !Utils.isEmail(team.email)) { - state.emailError = 'Please enter a valid email address'; + state.emailError = this.props.intl.formatMessage(holders.emailError); isValid = false; } else { state.emailError = null; @@ -67,7 +80,7 @@ export default class EmailSignUpPage extends React.Component { type='email' ref='email' className='form-control' - placeholder='Email Address' + placeholder={this.props.intl.formatMessage(holders.address)} maxLength='128' spellCheck='false' /> @@ -78,12 +91,20 @@ export default class EmailSignUpPage extends React.Component { className='btn btn-md btn-primary' type='submit' > - {'Create Team'} + <FormattedMessage + id='email_signup.createTeam' + defaultMessage='Create Team' + /> </button> {serverError} </div> <div className='form-group margin--extra-2x'> - <span><a href='/find_team'>{`Find my teams`}</a></span> + <span><a href='/find_team'> + <FormattedMessage + id='email_signup.find' + defaultMessage='Find my teams' + /> + </a></span> </div> </form> ); @@ -93,4 +114,7 @@ export default class EmailSignUpPage extends React.Component { EmailSignUpPage.defaultProps = { }; EmailSignUpPage.propTypes = { + intl: intlShape.isRequired }; + +export default injectIntl(EmailSignUpPage);
\ No newline at end of file diff --git a/web/react/components/team_signup_with_sso.jsx b/web/react/components/team_signup_with_sso.jsx index f4b323956..465f73fd2 100644 --- a/web/react/components/team_signup_with_sso.jsx +++ b/web/react/components/team_signup_with_sso.jsx @@ -5,7 +5,24 @@ import * as utils from '../utils/utils.jsx'; import * as client from '../utils/client.jsx'; import Constants from '../utils/constants.jsx'; -export default class SSOSignUpPage extends React.Component { +import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'mm-intl'; + +const holders = defineMessages({ + team_error: { + id: 'sso_signup.team_error', + defaultMessage: 'Please enter a team name' + }, + length_error: { + id: 'sso_signup.length_error', + defaultMessage: 'Name must be 3 or more characters up to a maximum of 15' + }, + teamName: { + id: 'sso_signup.teamName', + defaultMessage: 'Enter name of new team' + } +}); + +class SSOSignUpPage extends React.Component { constructor(props) { super(props); @@ -16,6 +33,7 @@ export default class SSOSignUpPage extends React.Component { } handleSubmit(e) { e.preventDefault(); + const {formatMessage} = this.props.intl; var team = {}; var state = this.state; state.nameError = null; @@ -24,13 +42,13 @@ export default class SSOSignUpPage extends React.Component { team.display_name = this.state.name; if (!team.display_name) { - state.nameError = 'Please enter a team name'; + state.nameError = formatMessage(holders.team_error); this.setState(state); return; } if (team.display_name.length <= 2) { - state.nameError = 'Name must be 3 or more characters up to a maximum of 15'; + state.nameError = formatMessage(holders.length_error); this.setState(state); return; } @@ -80,24 +98,36 @@ export default class SSOSignUpPage extends React.Component { button = ( <a className='btn btn-custom-login gitlab btn-full' + key='gitlab' href='#' onClick={this.handleSubmit} disabled={disabled} > <span className='icon'/> - <span>{'Create team with GitLab Account'}</span> + <span> + <FormattedMessage + id='sso_signup.gitlab' + defaultMessage='Create team with GitLab Account' + /> + </span> </a> ); } else if (this.props.service === Constants.GOOGLE_SERVICE) { button = ( <a className='btn btn-custom-login google btn-full' + key='google' href='#' onClick={this.handleSubmit} disabled={disabled} > <span className='icon'/> - <span>{'Create team with Google Apps Account'}</span> + <span> + <FormattedMessage + id='sso_signup.google' + defaultMessage='Create team with Google Apps Account' + /> + </span> </a> ); } @@ -113,7 +143,7 @@ export default class SSOSignUpPage extends React.Component { type='text' ref='teamname' className='form-control' - placeholder='Enter name of new team' + placeholder={this.props.intl.formatMessage(holders.teamName)} maxLength='128' onChange={this.nameChange} spellCheck='false' @@ -125,7 +155,12 @@ export default class SSOSignUpPage extends React.Component { {serverError} </div> <div className='form-group margin--extra-2x'> - <span><a href='/find_team'>{'Find my teams'}</a></span> + <span><a href='/find_team'> + <FormattedMessage + id='sso_signup.find' + defaultMessage='Find my teams' + /> + </a></span> </div> </form> ); @@ -136,5 +171,8 @@ SSOSignUpPage.defaultProps = { service: '' }; SSOSignUpPage.propTypes = { + intl: intlShape.isRequired, service: React.PropTypes.string }; + +export default injectIntl(SSOSignUpPage);
\ No newline at end of file diff --git a/web/react/pages/find_team.jsx b/web/react/pages/find_team.jsx index c4653fd77..ee2cf0de1 100644 --- a/web/react/pages/find_team.jsx +++ b/web/react/pages/find_team.jsx @@ -2,12 +2,61 @@ // See License.txt for license information. import FindTeam from '../components/find_team.jsx'; +import * as Client from '../utils/client.jsx'; -function setupFindTeamPage() { +var IntlProvider = ReactIntl.IntlProvider; + +class Root extends React.Component { + constructor() { + super(); + this.state = { + translations: null, + loaded: false + }; + } + + static propTypes() { + return { + map: React.PropTypes.object.isRequired + }; + } + + componentWillMount() { + Client.getTranslations( + this.props.map.Locale, + (data) => { + this.setState({ + translations: data, + loaded: true + }); + }, + () => { + this.setState({ + loaded: true + }); + } + ); + } + + render() { + if (!this.state.loaded) { + return <div></div>; + } + + return ( + <IntlProvider + locale={this.props.map.Locale} + messages={this.state.translations} + > + <FindTeam /> + </IntlProvider> + ); + } +} + +global.window.setup_find_team_page = function setup(props) { ReactDOM.render( - <FindTeam />, + <Root map={props} />, document.getElementById('find-team') ); -} - -global.window.setup_find_team_page = setupFindTeamPage; +};
\ No newline at end of file diff --git a/web/react/pages/signup_team.jsx b/web/react/pages/signup_team.jsx index 8f4f86a7c..c80b65580 100644 --- a/web/react/pages/signup_team.jsx +++ b/web/react/pages/signup_team.jsx @@ -60,7 +60,7 @@ global.window.setup_signup_team_page = function setup(props) { for (var prop in props) { if (props.hasOwnProperty(prop)) { - if (prop !== 'Title') { + if (prop !== 'Title' && prop !== 'Locale' && prop !== 'Info') { teams.push({name: prop, display_name: props[prop]}); } } diff --git a/web/react/utils/constants.jsx b/web/react/utils/constants.jsx index 851bc5f6c..e1a4b8a8a 100644 --- a/web/react/utils/constants.jsx +++ b/web/react/utils/constants.jsx @@ -168,6 +168,7 @@ export default { OFFLINE_ICON_SVG: "<svg version='1.1'id='Layer_1' xmlns:dc='http://purl.org/dc/elements/1.1/' xmlns:inkscape='http://www.inkscape.org/namespaces/inkscape' xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#' xmlns:svg='http://www.w3.org/2000/svg' xmlns:sodipodi='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd' xmlns:cc='http://creativecommons.org/ns#' inkscape:version='0.48.4 r9939' sodipodi:docname='TRASH_1_4.svg'xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' viewBox='-299 391 12 12'style='enable-background:new -299 391 12 12;' xml:space='preserve'> <sodipodi:namedview inkscape:cx='26.358185' inkscape:zoom='1.18' bordercolor='#666666' pagecolor='#ffffff' borderopacity='1' objecttolerance='10' inkscape:cy='139.7898' gridtolerance='10' guidetolerance='10' showgrid='false' showguides='true' id='namedview6' inkscape:pageopacity='0' inkscape:pageshadow='2' inkscape:guide-bbox='true' inkscape:window-width='1366' inkscape:current-layer='Layer_1' inkscape:window-height='705' inkscape:window-y='-8' inkscape:window-maximized='1' inkscape:window-x='-8'> <sodipodi:guide position='50.036793,85.991376' orientation='1,0' id='guide2986'></sodipodi:guide> <sodipodi:guide position='58.426196,66.216355' orientation='0,1' id='guide3047'></sodipodi:guide> </sodipodi:namedview> <g> <g> <ellipse class='offline--icon' cx='-294.5' cy='394' rx='2.5' ry='2.5'/> <path class='offline--icon' d='M-294.3,399.7c0-0.4,0.1-0.8,0.2-1.2c-0.1,0-0.2,0-0.4,0c-2.5,0-2.5-2-2.5-2s-1,0.1-1.2,0.5c-0.4,0.6-0.6,1.7-0.7,2.5 c0,0.1-0.1,0.5,0,0.6c0.2,1.3,2.2,2.3,4.4,2.4h0.1h0.1c0.3,0,0.7,0,1-0.1C-293.9,401.6-294.3,400.7-294.3,399.7z'/> </g> </g> <g> <path class='offline--icon' d='M-288.9,399.4l1.8-1.8c0.1-0.1,0.1-0.3,0-0.3l-0.7-0.7c-0.1-0.1-0.3-0.1-0.3,0l-1.8,1.8l-1.8-1.8c-0.1-0.1-0.3-0.1-0.3,0 l-0.7,0.7c-0.1,0.1-0.1,0.3,0,0.3l1.8,1.8l-1.8,1.8c-0.1,0.1-0.1,0.3,0,0.3l0.7,0.7c0.1,0.1,0.3,0.1,0.3,0l1.8-1.8l1.8,1.8 c0.1,0.1,0.3,0.1,0.3,0l0.7-0.7c0.1-0.1,0.1-0.3,0-0.3L-288.9,399.4z'/> </g> </svg>", MENU_ICON: "<svg version='1.1' id='Layer_1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px'width='4px' height='16px' viewBox='0 0 8 32' enable-background='new 0 0 8 32' xml:space='preserve'> <g> <circle cx='4' cy='4.062' r='4'/> <circle cx='4' cy='16' r='4'/> <circle cx='4' cy='28' r='4'/> </g> </svg>", COMMENT_ICON: "<svg version='1.1' id='Layer_2' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px'width='15px' height='15px' viewBox='1 1.5 15 15' enable-background='new 1 1.5 15 15' xml:space='preserve'> <g> <g> <path fill='#211B1B' d='M14,1.5H3c-1.104,0-2,0.896-2,2v8c0,1.104,0.896,2,2,2h1.628l1.884,3l1.866-3H14c1.104,0,2-0.896,2-2v-8 C16,2.396,15.104,1.5,14,1.5z M15,11.5c0,0.553-0.447,1-1,1H8l-1.493,2l-1.504-1.991L5,12.5H3c-0.552,0-1-0.447-1-1v-8 c0-0.552,0.448-1,1-1h11c0.553,0,1,0.448,1,1V11.5z'/> </g> </g> </svg>", + REPLY_ICON: "<svg version='1.1' id='Layer_1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px'viewBox='-158 242 18 18' style='enable-background:new -158 242 18 18;' xml:space='preserve'> <path d='M-142.2,252.6c-2-3-4.8-4.7-8.3-4.8v-3.3c0-0.2-0.1-0.3-0.2-0.3s-0.3,0-0.4,0.1l-6.9,6.2c-0.1,0.1-0.1,0.2-0.1,0.3 c0,0.1,0,0.2,0.1,0.3l6.9,6.4c0.1,0.1,0.3,0.1,0.4,0.1c0.1-0.1,0.2-0.2,0.2-0.4v-3.8c4.2,0,7.4,0.4,9.6,4.4c0.1,0.1,0.2,0.2,0.3,0.2 c0,0,0.1,0,0.1,0c0.2-0.1,0.3-0.3,0.2-0.4C-140.2,257.3-140.6,255-142.2,252.6z M-150.8,252.5c-0.2,0-0.4,0.2-0.4,0.4v3.3l-6-5.5 l6-5.3v2.8c0,0.2,0.2,0.4,0.4,0.4c3.3,0,6,1.5,8,4.5c0.5,0.8,0.9,1.6,1.2,2.3C-144,252.8-147.1,252.5-150.8,252.5z'/> </svg>", UPDATE_TYPING_MS: 5000, THEMES: { default: { diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx index 82e9bc447..494c38bdb 100644 --- a/web/react/utils/utils.jsx +++ b/web/react/utils/utils.jsx @@ -714,7 +714,7 @@ export function applyTheme(theme) { if (theme.linkColor) { changeCss('a, a:focus, a:hover, .btn, .btn:focus, .btn:hover', 'color:' + theme.linkColor, 1); - changeCss('.post .comment-icon__container', 'fill:' + theme.linkColor, 1); + changeCss('.post .comment-icon__container, .post .post__reply', 'fill:' + theme.linkColor, 1); } if (theme.buttonBg) { diff --git a/web/sass-files/sass/partials/_post.scss b/web/sass-files/sass/partials/_post.scss index ef2366686..1f7a55cd0 100644 --- a/web/sass-files/sass/partials/_post.scss +++ b/web/sass-files/sass/partials/_post.scss @@ -408,7 +408,7 @@ body.ios { @include legacy-pie-clearfix; &:hover { - .dropdown, .comment-icon__container { + .dropdown, .comment-icon__container, .post__reply { visibility: visible; } .permalink-icon { @@ -597,7 +597,7 @@ body.ios { position: absolute; right: 0; top: 30px; - width: 65px; + width: 85px; white-space: nowrap; } @@ -666,7 +666,7 @@ body.ios { word-wrap: break-word; padding: 0.2em 0.5em 0em; @include legacy-pie-clearfix; - width: calc(100% - 75px); + width: calc(100% - 95px); p { margin: 0 0 0.4em; @@ -754,6 +754,18 @@ body.ios { visibility: hidden; } + .post__reply { + display: inline-block; + margin-right: 6px; + visibility: hidden; + svg { + width: 18px; + top: 3px; + fill: inherit; + position: relative; + } + } + .comment-icon__container { fill: $primary-color; display: inline-block; diff --git a/web/sass-files/sass/partials/_responsive.scss b/web/sass-files/sass/partials/_responsive.scss index 8491869a6..a37974c7a 100644 --- a/web/sass-files/sass/partials/_responsive.scss +++ b/web/sass-files/sass/partials/_responsive.scss @@ -78,21 +78,15 @@ &:hover { background: transparent; + } - .comment-icon__container { - visibility: hidden; - - &.icon--show { - visibility: visible; - } - - } - + .comment-icon__container { + visibility: hidden; } } - .dropdown { + .dropdown, .post__reply { visibility: visible; } @@ -124,8 +118,34 @@ .post { + .post__body { + width: calc(100% - 75px); + } + + .post__reply { + margin-right: 20px; + float: right; + + svg { + top: 1px; + } + + } + + &.other--root .post__reply { + + &.post__reply--hide { + visibility: hidden; + } + + } + .post__header { + .col__reply { + width: 65px; + } + .col__name { pointer-events: none; } diff --git a/web/static/help/Messaging.md b/web/static/help/Messaging.md deleted file mode 120000 index f74c0b879..000000000 --- a/web/static/help/Messaging.md +++ /dev/null @@ -1 +0,0 @@ -../../../doc/help/Messaging.md
\ No newline at end of file diff --git a/doc/help/Messaging.md b/web/static/help/Messaging_en.md index 2063ad41c..2063ad41c 100644 --- a/doc/help/Messaging.md +++ b/web/static/help/Messaging_en.md diff --git a/web/static/help/Messaging_es.md b/web/static/help/Messaging_es.md new file mode 100644 index 000000000..d3947f36a --- /dev/null +++ b/web/static/help/Messaging_es.md @@ -0,0 +1,37 @@ +# Mensajes + +## Escribiendo Mensajes + +Puedes escribir mensajes utilizando el cuadro de texto que dice "Escribe un mensaje..." al final de Mattermost. + +Presiona **RETORNO** para enviar un mensaje. Utiliza **Shift+RETORNO** para crear una nueva linea sin enviar el mensaje. + +## Darle formato a los Mensajes + +Los mensajes de Mattermost se les asigna formato utilizando un estándard que se llama "markdown". Aquí algunos ejemplos: + +| Texto escrito | Como aparece | +|:--------------|:-------------| +|`**negrita**`| **negrita** | +| `_italica_`|_italica_| +|`[hipervinculo](http://mattermost.org)`|[hipervinculo](http://mattermost.org)| +|`![imagen embebida](https://travis-ci.org/mattermost/platform.svg)`|![imagen embebida](https://travis-ci.org/mattermost/platform.svg)| +|`:smile:` `:sheep:` `:alien:`|:smile: :sheep: :alien:| + +Revisa la lista completa de Emojis [aquí](http://www.emoji-cheat-sheet.com/). + +## Mencionando a compañeros + +Puedes mencionar a un compañero al utilizar el simbolo `@` más el nombre de usuario para enviarles una notificación especial que llame su atención. + +Por ejemplo, podrías escribir: + +``` +@alicia como te fue con la entrevista del nuevo candidato? +``` + +Lo cual enviará una notificación especial de mención a **alicia** para que lea tu mensaje. + +Para mencionar un compañero, presiona `@` y podrás ver una lista de los miembros de equipo a quienes puedes mandarles un mensaje. Puedes escribir su nombre de usuario o utilizar las flechas de **Arriba** y **Abajo** y presionar **RETORNO** para seleccionarlos. + +Puedes configurar como te gustaría ser notificado cuando alguien te menciona por nombre de usuario, tu primer nombre, sobrenombre o cualquier otra palabra clave en **Configurar Cuenta** > **Notificaciones** y puedes asignar preferencias especificas para un canal en **[Nombre del Canal]** > **Preferencias de Notificación** diff --git a/web/static/i18n/en.json b/web/static/i18n/en.json index ae302b86e..5127d38d5 100644 --- a/web/static/i18n/en.json +++ b/web/static/i18n/en.json @@ -396,6 +396,11 @@ "admin.user_item.makeActive": "Make Active", "admin.user_item.makeInactive": "Make Inactive", "admin.user_item.resetPwd": "Reset Password", + "authorize.title": "An application would like to connect to your {teamName} account", + "authorize.app": "The app {appName} would like the ability to access and modify your basic information.", + "authorize.access": "Allow {appName} access?", + "authorize.deny": "Deny", + "authorize.allow": "Allow", "claim.account.noEmail": "No email specified", "claim.email_to_sso.pwdError": "Please enter your password.", "claim.email_to_sso.pwd": "Password", @@ -412,11 +417,76 @@ "claim.sso_to_email_newPwd": "Enter a new password for your {team} {site} account", "claim.sso_to_email.switchTo": "Switch {type} to email and password", "error_bar.preview_mode": "Preview Mode: Email notifications have not been configured", + "find_team.submitError": "Please enter a valid email address", + "find_team.placeholder": "you@domain.com", + "find_team.findTitle": "Find Your Team", + "find_team.findDescription": "An email was sent with links to any teams to which you are a member.", + "find_team.getLinks": "Get an email with links to any teams to which you are a member.", + "find_team.email": "Email", + "find_team.send": "Send", "loading_screen.loading": "Loading", + "login_email.badTeam": "Bad team name", + "login_email.emailReq": "An email is required", + "login_email.pwdReq": "A password is required", + "login_email.email": "Email", + "login_email.pwd": "Password", + "login_email.signin": "Sign in", + "login_ldap.badTeam": "Bad team name", + "login_ldap.idlReq": "An LDAP ID is required", + "login_ldap.pwdReq": "An LDAP password is required", + "login_ldap.username": "LDAP Username", + "login_ldap.pwd": "LDAP Password", + "login_ldap.signin": "Sign in", + "login.gitlab": "with GitLab", + "login.google": "with Google Apps", + "login.changed": " Sign-in method changed successfully", + "login.verified": " Email Verified", + "login.or": "or", + "login.forgot": "I forgot my password", + "login.noAccount": "Don't have an account? ", + "login.create": "Create one now", + "login.createTeam": "Create a new team", + "login.find": "Find your other teams", + "login.signTo": "Sign in to:", + "login.on": "on {siteName}", + "password_form.error": "Please enter at least {chars} characters.", + "password_form.update": "Your password has been updated successfully.", + "password_form.pwd": "Password", + "password_form.click": "Click <a href={url}>here</a> to log in.", + "password_form.title": "Password Reset", + "password_form.enter": "Enter a new password for your {teamDisplayName} {siteName} account.", + "password_form.change": "Change my password", + "password_send.error": "Please enter a valid email address.", + "password_send.link": "<p>A password reset link has been sent to <b>{email}</b> for your <b>{teamDisplayName}</b> team on {hostname}.</p>", + "password_send.checkInbox": "Please check your inbox.", + "password_send.email": "Email", + "password_send.title": "Password Reset", + "password_send.description": "To reset your password, enter the email address you used to sign up for {teamName}.", + "password_send.reset": "Reset my password", + "signup_team.noTeams": "There are no teams include in the Team Directory and team creation has been disabled.", + "signup_team.choose": "Choose a Team", + "signup_team.createTeam": "Or Create a Team", + "signup_team.disabled": "Team creation has been disabled. Please contact an administrator for access.", + "signup_team.none": "No team creation method has been enabled. Please contact an administrator for access.", "suggestion.mention.all": "Notifies everyone in the team", "suggestion.mention.channel": "Notifies everyone in the channel", "suggestion.search.public": "Public Channels", - "suggestion.search.private": "Private Groups", + "suggestion.search.private": "Public Groups", + "choose_auth_page.gitlabCreate": "Create new team with GitLab Account", + "choose_auth_page.googleCreate": "Create new team with Google Apps Account", + "choose_auth_page.emailCreate": "Create new team with email address", + "choose_auth_page.noSignup": "No sign-up methods configured, please contact your system administrator.", + "choose_auth_page.find": "Find my teams", + "email_signup.emailError": "Please enter a valid email address", + "email_signup.address": "Email Address", + "email_signup.createTeam": "Create Team", + "email_signup.find": "Find my teams", + "sso_signup.team_error": "Please enter a team name", + "sso_signup.length_error": "Name must be 3 or more characters up to a maximum of 15", + "sso_signup.teamName": "Enter name of new team", + "sso_signup.gitlab": "Create team with GitLab Account", + "sso_signup.google": "Create team with Google Apps Account", + "sso_signup.find": "Find my teams", "tutorial_intro.screenOne": "<h3>Welcome to:</h3>\n <h1>Mattermost</h1>\n <p>Your team communication all in one place, instantly searchable and available anywhere</p>\n <p>Keep your team connected to help them achieve what matters most.</p>", "tutorial_intro.screenTwo": "<h3>How Mattermost works:</h3>\n <p>Communication happens in public discussion channels, private groups and direct messages.</p>\n <p>Everything is archived and searchable from any web-enabled desktop, laptop or phone.</p>", "tutorial_intro.invite": "Invite teammates", diff --git a/web/static/i18n/es.json b/web/static/i18n/es.json index 12f9b5fc5..8743a9b4f 100644 --- a/web/static/i18n/es.json +++ b/web/static/i18n/es.json @@ -396,6 +396,16 @@ "admin.user_item.resetPwd": "Reiniciar Contraseña", "admin.user_item.sysAdmin": "Admin de Sistema", "admin.user_item.teamAdmin": "Admin de Equipo", + "authorize.access": "¿Permitir acceso a {appName}?", + "authorize.allow": "Permitir", + "authorize.app": "La app {appName} quiere tener la abilidad de accesar y modificar tu información básica.", + "authorize.deny": "Denegar", + "authorize.title": "Una aplicación quiere conectarse con tu cuenta de {teamName}", + "choose_auth_page.emailCreate": "Crea un nuevo equipo con tu cuenta de correo", + "choose_auth_page.find": "Encontrar mi equipo", + "choose_auth_page.gitlabCreate": "Crear un nuevo equipo con una cuenta de GitLab", + "choose_auth_page.googleCreate": "Crear un nuevo equipo con una cuenta de Google Apps", + "choose_auth_page.noSignup": "No hay métodos de inicio de sesión configurad, por favor contacte al administrador de sistemasos", "claim.account.noEmail": "No se especifico un correo electrónico.", "claim.email_to_sso.enterPwd": "Ingresa la contraseña para tu cuenta para {team} {site}", "claim.email_to_sso.pwd": "Contraseña", @@ -411,8 +421,68 @@ "claim.sso_to_email.switchTo": "Cambiar {type} a correo electrónico y contraseña", "claim.sso_to_email.title": "Cambiar la cuenta de {type} a Correo Electrónico", "claim.sso_to_email_newPwd": "Ingresa una nueva contraseña para tu cuenta de {team} {site}", + "email_signup.address": "Correo electrónico", + "email_signup.createTeam": "Crear Equipo", + "email_signup.emailError": "Por favor ingresa una dirección de correos válida", + "email_signup.find": "Encontrar mi equipo", "error_bar.preview_mode": "Modo de prueba: Las notificaciones por correo electrónico no han sido configuradas", + "find_team.email": "Correo electrónico", + "find_team.findDescription": "Enviamos un correo electrónico con los equipos a los que perteneces.", + "find_team.findTitle": "Encuentra tu equipo", + "find_team.getLinks": "Obtén un correo electrónico con los equipos a los que perteneces.", + "find_team.placeholder": "tu@ejemplo.com", + "find_team.send": "Enviar", + "find_team.submitError": "Por favor ingresa una dirección válida", "loading_screen.loading": "Cargando", + "login.changed": " Cambiado el método de inicio de sesión satisfactoriamente", + "login.create": "Crea uno ahora", + "login.createTeam": "Crear un nuevo equipo", + "login.find": "Encuentra tus otros equipos", + "login.forgot": "Olvide mi contraseña", + "login.gitlab": "con GitLab", + "login.google": "con Google Apps", + "login.noAccount": "¿No tienes una cuenta? ", + "login.on": "en {siteName}", + "login.or": "o", + "login.signTo": "Ingresar a:", + "login.verified": " Correo electrónico Verificado", + "login_email.badTeam": "Nombre de Equipo inválido", + "login_email.email": "Correo electrónico", + "login_email.emailReq": "El correo electrónico es obligatorio", + "login_email.pwd": "Contraseña", + "login_email.pwdReq": "La contraseña es obligatoria", + "login_email.signin": "Entrar", + "login_ldap.badTeam": "Nombre de Equipo inválido", + "login_ldap.idlReq": "El ID de LDAP es obligatorio", + "login_ldap.pwd": "Contraseña LDAP", + "login_ldap.pwdReq": "La contraseña LDAP es obligatoria", + "login_ldap.signin": "Entrar", + "login_ldap.username": "Usuario LDAP", + "password_form.change": "Cambiar mi contraseña", + "password_form.click": " Pincha <a href={url}>aquí</a> para iniciar sesión.", + "password_form.enter": "Ingresa una nueva contraseña para tu cuenta en {teamDisplayName} {SiteName}.", + "password_form.error": "Por favor ingresa al menos {chars} caracteres.", + "password_form.pwd": "Contraseña", + "password_form.title": "Restablecer Contraseña", + "password_form.update": "Tu contraseña ha sido actualizada satisfactoriamente.", + "password_send.checkInbox": "Por favor revisa tu bandeja de entrada.", + "password_send.description": "Para restablecer tu contraseña, ingresa la dirección de correo electrónico que utilizaste para registrarte en {teamName}.", + "password_send.email": "Correo electrónico", + "password_send.error": "Por favor ingresa una dirección correo electrónico válida.", + "password_send.link": "<p>Se ha enviado un enlace para restablecer la contraseña a <b>{email}</b> para tu equipo <b>{teamDisplayName}</b> en {hostname}.</p>", + "password_send.reset": "Restablecer mi contraseña", + "password_send.title": "Restablecer Contraseña", + "signup_team.choose": "Selecciona un Equipo", + "signup_team.createTeam": "O Crea un Equipo", + "signup_team.disabled": "La creación de Equipos ha sido deshabilitada.", + "signup_team.noTeams": "No hay equipos en el Directorio de Equipos y la creación de equipos ha sido deshabilitada.", + "signup_team.none": "No se ha habilitado ningún método para creación de equipos. Por favor contacta a un administrador de sistema para solicitar acceso.", + "sso_signup.find": "Encontrar mi equipo", + "sso_signup.gitlab": "Crea un equipo con una cuenta de GitLab", + "sso_signup.google": "Crea un equipo con una cuenta de Google Apps", + "sso_signup.length_error": "Name must be 3 or more characters up to a maximum of 15", + "sso_signup.teamName": "Ingresa el nombre del nuevo equipo", + "sso_signup.team_error": "Please enter a team name", "suggestion.mention.all": "Notifica a todas las personas en el equipo", "suggestion.mention.channel": "Notifica a todas las personas en el canal", "suggestion.search.private": "Grupos Privados", @@ -420,7 +490,7 @@ "tutorial_intro.allSet": "Ya estás listo para comenzar", "tutorial_intro.end": "Pincha “Siguiente” para entrar al Canal General. Este es el primer canal que ven tus compañeros cuando ingresan. Utilizalo para mandar mensajes que todos deben leer.", "tutorial_intro.invite": "Invitar compañeros", - "tutorial_intro.next": "Seguir", + "tutorial_intro.next": "Siguiente", "tutorial_intro.screenOne": "<h3>Bienvenido a:</h3> <h1>Mattermost</h1> <p>Las comunicaciones de tu equipo en un solo lugar, con búsquedas instantáneas y disponible desde donde sea.</p> <p>Mantén a tu equipo conectado para ayudarlos a conseguir lo que realmente importa.</p>", "tutorial_intro.screenTwo": "<h3>Cómo funciona Mattermost:</h3> <p>Las comunicaciones ocurren en los canales de discusión los cuales son públicos, o en grupos privados e incluso con mensajes privados.</p> <p>Todo lo que ocurre es archivado y se puede buscar en cualquier momento desde cualquier dispositivo con acceso a Mattermost.</p>", "tutorial_intro.skip": "Saltar el tutorial", diff --git a/web/templates/find_team.html b/web/templates/find_team.html index d32ea0dc8..15b8bf463 100644 --- a/web/templates/find_team.html +++ b/web/templates/find_team.html @@ -23,7 +23,7 @@ </div> </div> <script> - window.setup_find_team_page(); + window.setup_find_team_page({{ .Props }}); </script> </body> </html> diff --git a/web/templates/signup_team.html b/web/templates/signup_team.html index 39fd3791b..afba58066 100644 --- a/web/templates/signup_team.html +++ b/web/templates/signup_team.html @@ -10,7 +10,7 @@ <div class="signup-team__container"> <img class="signup-team-logo" src="/static/images/logo.png" /> <h1>{{ .ClientCfg.SiteName }}</h1> - <h4 class="color--light">All team communication in one place, searchable and accessible anywhere</h4> + <h4 class="color--light">{{.Props.Info}}</h4> <div id="signup-team"></div> </div> </div> diff --git a/web/web.go b/web/web.go index 36349dd5e..beb0eff06 100644 --- a/web/web.go +++ b/web/web.go @@ -173,6 +173,7 @@ func root(c *api.Context, w http.ResponseWriter, r *http.Request) { if len(c.Session.UserId) == 0 { page := NewHtmlTemplatePage("signup_team", c.T("web.root.singup_title"), c.Locale) + page.Props["Info"] = c.T("web.root.singup_info") if result := <-api.Srv.Store.Team().GetAllTeamListing(); result.Err != nil { c.Err = result.Err @@ -611,8 +612,17 @@ func docs(c *api.Context, w http.ResponseWriter, r *http.Request) { params := mux.Vars(r) doc := params["doc"] + var user *model.User + if len(c.Session.UserId) != 0 { + userChan := api.Srv.Store.User().Get(c.Session.UserId) + if userChan := <-userChan; userChan.Err == nil { + user = userChan.Data.(*model.User) + } + } + page := NewHtmlTemplatePage("docs", c.T("web.doc.title"), c.Locale) page.Props["Site"] = doc + page.User = user page.Render(c, w) } |