From e67cb399fbba87e792ae33b3cf524c0aa66e2f8a Mon Sep 17 00:00:00 2001 From: =Corey Hulen Date: Mon, 25 Jan 2016 09:22:56 -0500 Subject: PLT-7 client side infra --- web/react/pages/channel.jsx | 144 +++++++++++++++++++++----------------------- web/templates/channel.html | 20 +----- 2 files changed, 71 insertions(+), 93 deletions(-) diff --git a/web/react/pages/channel.jsx b/web/react/pages/channel.jsx index ffe232ab6..37c59b75f 100644 --- a/web/react/pages/channel.jsx +++ b/web/react/pages/channel.jsx @@ -5,6 +5,7 @@ import ChannelView from '../components/channel_view.jsx'; import ChannelLoader from '../components/channel_loader.jsx'; import ErrorBar from '../components/error_bar.jsx'; import ErrorStore from '../stores/error_store.jsx'; +import * as Client from '../utils/client.jsx'; import GetTeamInviteLinkModal from '../components/get_team_invite_link_modal.jsx'; import RenameChannelModal from '../components/rename_channel_modal.jsx'; @@ -26,13 +27,79 @@ import * as EventHelpers from '../dispatcher/event_helpers.jsx'; import Constants from '../utils/constants.jsx'; +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
; + } + + return ( + +
+ + + + + + + + + + + + + + + +
+
+ ); + } +} + function onPreferenceChange() { const selectedFont = PreferenceStore.get(Constants.Preferences.CATEGORY_DISPLAY_SETTINGS, 'selected_font', Constants.DEFAULT_FONT); Utils.applyFont(selectedFont); PreferenceStore.removeChangeListener(onPreferenceChange); } -function setupChannelPage(props, team, channel) { +global.window.setup_channel_page = function setup(props, team, channel) { if (props.PostId === '') { EventHelpers.emitChannelClickEvent(channel); } else { @@ -42,84 +109,13 @@ function setupChannelPage(props, team, channel) { PreferenceStore.addChangeListener(onPreferenceChange); AsyncClient.getAllPreferences(); - // ChannelLoader must be rendered first - ReactDOM.render( - , - document.getElementById('channel_loader') - ); - ReactDOM.render( - , - document.getElementById('error_bar') - ); - - ReactDOM.render( - , + , document.getElementById('channel_view') ); - // - // Modals - // - ReactDOM.render( - , - document.getElementById('get_team_invite_link_modal') - ); - - ReactDOM.render( - , - document.getElementById('invite_member_modal') - ); - - ReactDOM.render( - , - document.getElementById('import_theme_modal') - ); - - ReactDOM.render( - , - document.getElementById('team_settings_modal') - ); - - ReactDOM.render( - , - document.getElementById('rename_channel_modal') - ); - - ReactDOM.render( - , - document.getElementById('more_channels_modal') - ); - - ReactDOM.render( - , - document.getElementById('edit_post_modal') - ); - - ReactDOM.render( - , - document.getElementById('delete_post_modal') - ); - - ReactDOM.render( - , - document.getElementById('post_deleted_modal') - ); - - ReactDOM.render( - , - document.getElementById('removed_from_channel_modal') - ); - - ReactDOM.render( - , - document.getElementById('register_app_modal') - ); - if (global.window.mm_config.SendEmailNotifications === 'false') { ErrorStore.storeLastError({message: 'Preview Mode: Email notifications have not been configured'}); ErrorStore.emitChange(); } -} - -global.window.setup_channel_page = setupChannelPage; +}; \ No newline at end of file diff --git a/web/templates/channel.html b/web/templates/channel.html index 8abbe36df..dcc50115b 100644 --- a/web/templates/channel.html +++ b/web/templates/channel.html @@ -4,25 +4,7 @@ {{template "head" . }} -
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
-
+
- - -{{end}} -- cgit v1.2.3-1-g7c22 From 85d4ed21c73cf51f112ff944080e8bf54d53608a Mon Sep 17 00:00:00 2001 From: Elias Nahum Date: Tue, 26 Jan 2016 22:19:51 -0300 Subject: PLT-7: Refactoring frontend (chunk 1) - System console sidebar - Sytem console email settings - Error Bar - Loading Screen - Select Team Modal - Add npm mm-intl package --- .../admin_console/admin_navbar_dropdown.jsx | 25 +- .../components/admin_console/admin_sidebar.jsx | 134 ++++++-- .../admin_console/admin_sidebar_header.jsx | 9 +- .../components/admin_console/email_settings.jsx | 364 +++++++++++++++++---- .../components/admin_console/select_team_modal.jsx | 21 +- web/react/components/error_bar.jsx | 17 +- web/react/components/loading_screen.jsx | 9 +- web/react/package.json | 1 + web/static/i18n/en.json | 88 ++++- web/static/i18n/es.json | 88 ++++- 10 files changed, 651 insertions(+), 105 deletions(-) diff --git a/web/react/components/admin_console/admin_navbar_dropdown.jsx b/web/react/components/admin_console/admin_navbar_dropdown.jsx index 783d45de6..dc0b3c4cb 100644 --- a/web/react/components/admin_console/admin_navbar_dropdown.jsx +++ b/web/react/components/admin_console/admin_navbar_dropdown.jsx @@ -7,6 +7,8 @@ import TeamStore from '../../stores/team_store.jsx'; import Constants from '../../utils/constants.jsx'; +import {FormattedMessage} from 'mm-intl'; + function getStateFromStores() { return {currentTeam: TeamStore.getCurrent()}; } @@ -66,7 +68,13 @@ export default class AdminNavbarDropdown extends React.Component { - {'Switch to ' + this.state.currentTeam.display_name} +
  • @@ -74,7 +82,10 @@ export default class AdminNavbarDropdown extends React.Component { href='#' onClick={this.handleLogoutClick} > - {'Logout'} +
  • @@ -83,7 +94,10 @@ export default class AdminNavbarDropdown extends React.Component { target='_blank' href='/static/help/help.html' > - {'Help'} +
  • @@ -91,7 +105,10 @@ export default class AdminNavbarDropdown extends React.Component { target='_blank' href='/static/help/report_problem.html' > - {'Report a Problem'} +
  • diff --git a/web/react/components/admin_console/admin_sidebar.jsx b/web/react/components/admin_console/admin_sidebar.jsx index 66f82c55b..d6bae1feb 100644 --- a/web/react/components/admin_console/admin_sidebar.jsx +++ b/web/react/components/admin_console/admin_sidebar.jsx @@ -5,6 +5,8 @@ import AdminSidebarHeader from './admin_sidebar_header.jsx'; import SelectTeamModal from './select_team_modal.jsx'; import * as Utils from '../../utils/utils.jsx'; +import {FormattedMessage} from 'mm-intl'; + const Tooltip = ReactBootstrap.Tooltip; const OverlayTrigger = ReactBootstrap.OverlayTrigger; @@ -82,12 +84,27 @@ export default class AdminSidebar extends React.Component { render() { var count = '*'; - var teams = 'Loading'; + var teams = ( + + ); const removeTooltip = ( - {'Remove team from sidebar menu'} + + + ); const addTeamTooltip = ( - {'Add team from sidebar menu'} + + + ); if (this.props.teams != null) { @@ -134,7 +151,10 @@ export default class AdminSidebar extends React.Component { className={this.isSelected('team_users', team.id)} onClick={this.handleClick.bind(this, 'team_users', team.id)} > - {'- Users'} +
  • @@ -143,7 +163,10 @@ export default class AdminSidebar extends React.Component { className={this.isSelected('team_analytics', team.id)} onClick={this.handleClick.bind(this, 'team_analytics', team.id)} > - {'- Statistics'} +
  • @@ -166,7 +189,10 @@ export default class AdminSidebar extends React.Component { className={this.isSelected('ldap_settings')} onClick={this.handleClick.bind(this, 'ldap_settings', null)} > - {'LDAP Settings'} + ); @@ -179,7 +205,10 @@ export default class AdminSidebar extends React.Component { className={this.isSelected('license')} onClick={this.handleClick.bind(this, 'license', null)} > - {'Edition and License'} + ); @@ -196,7 +225,12 @@ export default class AdminSidebar extends React.Component {
  • - {'SITE REPORTS'} + + +

  • @@ -207,7 +241,10 @@ export default class AdminSidebar extends React.Component { className={this.isSelected('system_analytics')} onClick={this.handleClick.bind(this, 'system_analytics', null)} > - {'View Statistics'} + @@ -215,7 +252,12 @@ export default class AdminSidebar extends React.Component {
  • - {'SETTINGS'} + + +

  • @@ -226,7 +268,10 @@ export default class AdminSidebar extends React.Component { className={this.isSelected('service_settings')} onClick={this.handleClick.bind(this, 'service_settings', null)} > - {'Service Settings'} +
  • @@ -235,7 +280,10 @@ export default class AdminSidebar extends React.Component { className={this.isSelected('team_settings')} onClick={this.handleClick.bind(this, 'team_settings', null)} > - {'Team Settings'} +
  • @@ -244,7 +292,10 @@ export default class AdminSidebar extends React.Component { className={this.isSelected('sql_settings')} onClick={this.handleClick.bind(this, 'sql_settings', null)} > - {'SQL Settings'} +
  • @@ -253,7 +304,10 @@ export default class AdminSidebar extends React.Component { className={this.isSelected('email_settings')} onClick={this.handleClick.bind(this, 'email_settings', null)} > - {'Email Settings'} +
  • @@ -262,7 +316,10 @@ export default class AdminSidebar extends React.Component { className={this.isSelected('image_settings')} onClick={this.handleClick.bind(this, 'image_settings', null)} > - {'File Settings'} +
  • @@ -271,7 +328,10 @@ export default class AdminSidebar extends React.Component { className={this.isSelected('log_settings')} onClick={this.handleClick.bind(this, 'log_settings', null)} > - {'Log Settings'} +
  • @@ -280,7 +340,10 @@ export default class AdminSidebar extends React.Component { className={this.isSelected('rate_settings')} onClick={this.handleClick.bind(this, 'rate_settings', null)} > - {'Rate Limit Settings'} +
  • @@ -289,7 +352,10 @@ export default class AdminSidebar extends React.Component { className={this.isSelected('privacy_settings')} onClick={this.handleClick.bind(this, 'privacy_settings', null)} > - {'Privacy Settings'} +
  • @@ -298,7 +364,10 @@ export default class AdminSidebar extends React.Component { className={this.isSelected('gitlab_settings')} onClick={this.handleClick.bind(this, 'gitlab_settings', null)} > - {'GitLab Settings'} +
  • {ldapSettings} @@ -308,7 +377,10 @@ export default class AdminSidebar extends React.Component { className={this.isSelected('legal_and_support_settings')} onClick={this.handleClick.bind(this, 'legal_and_support_settings', null)} > - {'Legal and Support Settings'} + @@ -316,7 +388,15 @@ export default class AdminSidebar extends React.Component {
  • - {'TEAMS (' + count + ')'} + + +

    - {'OTHER'} + + +

  • @@ -357,7 +442,10 @@ export default class AdminSidebar extends React.Component { className={this.isSelected('logs')} onClick={this.handleClick.bind(this, 'logs', null)} > - {'Logs'} + diff --git a/web/react/components/admin_console/admin_sidebar_header.jsx b/web/react/components/admin_console/admin_sidebar_header.jsx index bfd479939..db499265e 100644 --- a/web/react/components/admin_console/admin_sidebar_header.jsx +++ b/web/react/components/admin_console/admin_sidebar_header.jsx @@ -5,6 +5,8 @@ import AdminNavbarDropdown from './admin_navbar_dropdown.jsx'; import UserStore from '../../stores/user_store.jsx'; import * as Utils from '../../utils/utils.jsx'; +import {FormattedMessage} from 'mm-intl'; + export default class SidebarHeader extends React.Component { constructor(props) { super(props); @@ -51,7 +53,12 @@ export default class SidebarHeader extends React.Component { {profilePicture}
    {'@' + me.username}
    -
    {'System Console'}
    +
    + +
    diff --git a/web/react/components/admin_console/email_settings.jsx b/web/react/components/admin_console/email_settings.jsx index c568c5a77..ce3c8cd12 100644 --- a/web/react/components/admin_console/email_settings.jsx +++ b/web/react/components/admin_console/email_settings.jsx @@ -5,7 +5,68 @@ import * as Client from '../../utils/client.jsx'; import * as AsyncClient from '../../utils/async_client.jsx'; import crypto from 'crypto'; -export default class EmailSettings extends React.Component { +import {injectIntl, intlShape, defineMessages, FormattedMessage, FormattedHTMLMessage} from 'mm-intl'; + +var holders = defineMessages({ + notificationDisplayExample: { + id: 'admin.email.notificationDisplayExample', + defaultMessage: 'Ex: "Mattermost Notification", "System", "No-Reply"' + }, + notificationEmailExample: { + id: 'admin.email.notificationEmailExample', + defaultMessage: 'Ex: "mattermost@yourcompany.com", "admin@yourcompany.com"' + }, + smtpUsernameExample: { + id: 'admin.email.smtpUsernameExample', + defaultMessage: 'Ex: "admin@yourcompany.com", "AKIADTOVBGERKLCBV"' + }, + smtpPasswordExample: { + id: 'admin.email.smtpPasswordExample', + defaultMessage: 'Ex: "yourpassword", "jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY"' + }, + smtpServerExample: { + id: 'admin.email.smtpServerExample', + defaultMessage: 'Ex: "smtp.yourcompany.com", "email-smtp.us-east-1.amazonaws.com"' + }, + smtpPortExample: { + id: 'admin.email.smtpPortExample', + defaultMessage: 'Ex: "25", "465"' + }, + connectionSecurityNone: { + id: 'admin.email.connectionSecurityNone', + defaultMessage: 'None' + }, + connectionSecurityTls: { + id: 'admin.email.connectionSecurityTls', + defaultMessage: 'TLS (Recommended)' + }, + connectionSecurityStart: { + id: 'admin.email.connectionSecurityStart', + defaultMessage: 'STARTTLS' + }, + inviteSaltExample: { + id: 'admin.email.inviteSaltExample', + defaultMessage: 'Ex "bjlSR4QqkXFBr7TP4oDzlfZmcNuH9Yo"' + }, + passwordSaltExample: { + id: 'admin.email.passwordSaltExample', + defaultMessage: 'Ex "bjlSR4QqkXFBr7TP4oDzlfZmcNuH9Yo"' + }, + pushServerEx: { + id: 'admin.email.pushServerEx', + defaultMessage: 'E.g.: "https://push-test.mattermost.com"' + }, + testing: { + id: 'admin.email.testing', + defaultMessage: 'Testing...' + }, + saving: { + id: 'admin.email.saving', + defaultMessage: 'Saving Config...' + } +}); + +class EmailSettings extends React.Component { constructor(props) { super(props); @@ -156,6 +217,7 @@ export default class EmailSettings extends React.Component { } render() { + const {formatMessage} = this.props.intl; var serverError = ''; if (this.state.serverError) { serverError =
    ; @@ -170,7 +232,11 @@ export default class EmailSettings extends React.Component { if (this.state.emailSuccess) { emailSuccess = (
    - {'No errors were reported while sending an email. Please check your inbox to make sure.'} + +
    ); } @@ -179,14 +245,26 @@ export default class EmailSettings extends React.Component { if (this.state.emailFail) { emailSuccess = (
    - {'Connection unsuccessful: ' + this.state.emailFail} + +
    ); } return (
    -

    {'Email Settings'}

    +

    + +

    - {'Allow Sign Up With Email: '} +
    -

    {'When true, Mattermost allows team creation and account signup using email and password. This value should be false only when you want to limit signup to a single-sign-on service like OAuth or LDAP.'}

    +

    + +

    @@ -230,7 +322,10 @@ export default class EmailSettings extends React.Component { className='control-label col-sm-4' htmlFor='sendEmailNotifications' > - {'Send Email Notifications: '} +
    -

    {'Typically set to true in production. When true, Mattermost attempts to send email notifications. Developers may set this field to false to skip email setup for faster development.\nSetting this to true removes the Preview Mode banner (requires logging out and logging back in after setting is changed).'}

    +

    + +

    @@ -263,7 +369,10 @@ export default class EmailSettings extends React.Component { className='control-label col-sm-4' htmlFor='requireEmailVerification' > - {'Require Email Verification: '} +
    -

    {'Typically set to true in production. When true, Mattermost requires email verification after account creation prior to allowing login. Developers may set this field to false so skip sending verification emails for faster development.'}

    +

    + +

    @@ -298,7 +418,10 @@ export default class EmailSettings extends React.Component { className='control-label col-sm-4' htmlFor='feedbackName' > - {'Notification Display Name:'} +
    -

    {'Display name on email account used when sending notification emails from Mattermost.'}

    +

    + +

    @@ -320,7 +448,10 @@ export default class EmailSettings extends React.Component { className='control-label col-sm-4' htmlFor='feedbackEmail' > - {'Notification Email Address:'} +
    -

    {'Email address displayed on email account used when sending notification emails from Mattermost.'}

    +

    + +

    @@ -342,7 +478,10 @@ export default class EmailSettings extends React.Component { className='control-label col-sm-4' htmlFor='SMTPUsername' > - {'SMTP Username:'} +
    -

    {' Obtain this credential from administrator setting up your email server.'}

    +

    + +

    @@ -364,7 +508,10 @@ export default class EmailSettings extends React.Component { className='control-label col-sm-4' htmlFor='SMTPPassword' > - {'SMTP Password:'} +
    -

    {' Obtain this credential from administrator setting up your email server.'}

    +

    + +

    @@ -386,7 +538,10 @@ export default class EmailSettings extends React.Component { className='control-label col-sm-4' htmlFor='SMTPServer' > - {'SMTP Server:'} +
    -

    {'Location of SMTP email server.'}

    +

    + +

    @@ -408,7 +568,10 @@ export default class EmailSettings extends React.Component { className='control-label col-sm-4' htmlFor='SMTPPort' > - {'SMTP Port:'} +
    -

    {'Port of SMTP email server.'}

    +

    + +

    @@ -430,7 +598,10 @@ export default class EmailSettings extends React.Component { className='control-label col-sm-4' htmlFor='ConnectionSecurity' > - {'Connection Security:'} +
    - - - + + +
    {'None'}{'Mattermost will send email over an unsecure connection.'}
    {'TLS'}{'Encrypts the communication between Mattermost and your email server.'}
    {'STARTTLS'}{'Takes an existing insecure connection and attempts to upgrade it to a secure connection using TLS.'}
    + + + +
    {'TLS'} + +
    {'STARTTLS'} + +
    @@ -463,9 +654,12 @@ export default class EmailSettings extends React.Component { onClick={this.handleTestConnection} disabled={!this.state.sendEmailNotifications} id='connection-button' - data-loading-text={' Testing...'} + data-loading-text={' ' + formatMessage(holders.testing)} > - {'Test Connection'} + {emailSuccess} {emailFail} @@ -478,7 +672,10 @@ export default class EmailSettings extends React.Component { className='control-label col-sm-4' htmlFor='InviteSalt' > - {'Invite Salt:'} +
    -

    {'32-character salt added to signing of email invites. Randomly generated on install. Click "Re-Generate" to create new salt.'}

    +

    + +

    @@ -509,7 +714,10 @@ export default class EmailSettings extends React.Component { className='control-label col-sm-4' htmlFor='PasswordResetSalt' > - {'Password Reset Salt:'} +
    -

    {'32-character salt added to signing of password reset emails. Randomly generated on install. Click "Re-Generate" to create new salt.'}

    +

    + +

    @@ -540,7 +756,10 @@ export default class EmailSettings extends React.Component { className='control-label col-sm-4' htmlFor='sendPushNotifications' > - {'Send Push Notifications: '} +
    -

    {'Typically set to true in production. When true, Mattermost attempts to send iOS and Android push notifications through the push notification server.'}

    +

    + +

    @@ -573,7 +803,10 @@ export default class EmailSettings extends React.Component { className='control-label col-sm-4' htmlFor='PushNotificationServer' > - {'Push Notification Server:'} +
    -

    {'Location of Mattermost push notification service you can set up behind your firewall using https://github.com/mattermost/push-proxy. For testing you can use https://push-test.mattermost.com, which connects to the sample Mattermost iOS app in the public Apple AppStore. Please do not use test service for production deployments.'}

    +

    + +

    @@ -599,9 +837,12 @@ export default class EmailSettings extends React.Component { className={saveClass} onClick={this.handleSubmit} id='save-button' - data-loading-text={' Saving Config...'} + data-loading-text={' ' + formatMessage(holders.saving)} > - {'Save'} + @@ -613,5 +854,8 @@ export default class EmailSettings extends React.Component { } EmailSettings.propTypes = { + intl: intlShape.isRequired, config: React.PropTypes.object }; + +export default injectIntl(EmailSettings); \ No newline at end of file diff --git a/web/react/components/admin_console/select_team_modal.jsx b/web/react/components/admin_console/select_team_modal.jsx index 858b6bbfe..e0d070b28 100644 --- a/web/react/components/admin_console/select_team_modal.jsx +++ b/web/react/components/admin_console/select_team_modal.jsx @@ -1,6 +1,8 @@ // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. +import {FormattedMessage} from 'mm-intl'; + var Modal = ReactBootstrap.Modal; export default class SelectTeamModal extends React.Component { @@ -45,7 +47,12 @@ export default class SelectTeamModal extends React.Component { onHide={this.doCancel} > - {'Select Team'} + + + - {'Close'} + @@ -96,4 +109,4 @@ SelectTeamModal.propTypes = { show: React.PropTypes.bool.isRequired, onModalSubmit: React.PropTypes.func, onModalDismissed: React.PropTypes.func -}; +}; \ No newline at end of file diff --git a/web/react/components/error_bar.jsx b/web/react/components/error_bar.jsx index e93545c25..e9aa8197c 100644 --- a/web/react/components/error_bar.jsx +++ b/web/react/components/error_bar.jsx @@ -3,6 +3,16 @@ import ErrorStore from '../stores/error_store.jsx'; +// import mm-intl is required for the tool to be able to extract the messages +import {defineMessages} from 'mm-intl'; + +var messages = defineMessages({ + preview: { + id: 'error_bar.preview_mode', + defaultMessage: 'Preview Mode: Email notifications have not been configured' + } +}); + export default class ErrorBar extends React.Component { constructor() { super(); @@ -49,12 +59,7 @@ export default class ErrorBar extends React.Component { componentWillMount() { if (global.window.mm_config.SendEmailNotifications === 'false') { - ErrorStore.storeLastError({message: this.props.intl.formatMessage( - { - id: 'error_bar.preview_mode', - defaultMessage: 'Preview Mode: Email notifications have not been configured' - } - )}); + ErrorStore.storeLastError({message: this.props.intl.formatMessage(messages.preview)}); this.onErrorChange(); } } diff --git a/web/react/components/loading_screen.jsx b/web/react/components/loading_screen.jsx index 9849205f2..143b94467 100644 --- a/web/react/components/loading_screen.jsx +++ b/web/react/components/loading_screen.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 LoadingScreen extends React.Component { constructor(props) { super(props); @@ -13,7 +15,12 @@ export default class LoadingScreen extends React.Component { style={{position: this.props.position}} >
    -

    Loading

    +

    + +

    diff --git a/web/react/package.json b/web/react/package.json index 14b16b4e4..1d3389896 100644 --- a/web/react/package.json +++ b/web/react/package.json @@ -8,6 +8,7 @@ "highlight.js": "8.9.1", "keymirror": "0.1.1", "marked": "mattermost/marked#cb85e5cc81bc7937dbb73c3c53d9532b1b97e3ca", + "mm-intl": "^1.0.0", "object-assign": "4.0.1", "twemoji": "1.4.1" }, diff --git a/web/static/i18n/en.json b/web/static/i18n/en.json index cb955ee6b..56744ddac 100644 --- a/web/static/i18n/en.json +++ b/web/static/i18n/en.json @@ -1,5 +1,87 @@ { - "login.find_teams": "Find your other teams", - "login.forgot_password": "I forgot my password", - "error_bar.preview_mode": "Preview Mode: Email notifications have not been configured" + "admin.nav.switch": "Switch to {display_name}", + "admin.nav.logout": "Logout", + "admin.nav.help": "Help", + "admin.nav.report": "Report a Problem", + "admin.sidebarHeader.systemConsole": "System Console", + "admin.sidebar.loading": "Loading", + "admin.sidebar.rmTeamSidebar": "Remove team from sidebar menu", + "admin.sidebar.addTeamSidebar": "Add team from sidebar menu", + "admin.sidebar.users": "- Users", + "admin.sidebar.statistics": "- Statistics", + "admin.sidebar.ldap": "LDAP Settings", + "admin.sidebar.license": "Edition and License", + "admin.sidebar.reports": "SITE REPORTS", + "admin.sidebar.view_statistics": "View Statistics", + "admin.sidebar.settings": "SETTINGS", + "admin.sidebar.service": "Service Settings", + "admin.sidebar.team": "Team Settings", + "admin.sidebar.sql": "SQL Settings", + "admin.sidebar.email": "Email Settings", + "admin.sidebar.file": "File Settings", + "admin.sidebar.log": "Log Settings", + "admin.sidebar.rate_limit": "Rate Limit Settings", + "admin.sidebar.privacy": "Privacy Settings", + "admin.sidebar.gitlab": "GitLab Settings", + "admin.sidebar.support": "Legal and Support Settings", + "admin.sidebar.teams": "TEAMS ({count})", + "admin.sidebar.other": "OTHER", + "admin.sidebar.logs": "Logs", + "admin.email.notificationDisplayExample": "Ex: \"Mattermost Notification\", \"System\", \"No-Reply\"", + "admin.email.notificationEmailExample": "Ex: \"mattermost@yourcompany.com\", \"admin@yourcompany.com\"", + "admin.email.smtpUsernameExample": "Ex: \"admin@yourcompany.com\", \"AKIADTOVBGERKLCBV\"", + "admin.email.smtpPasswordExample": "Ex: \"yourpassword\", \"jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY\"", + "admin.email.smtpServerExample": "Ex: \"smtp.yourcompany.com\", \"email-smtp.us-east-1.amazonaws.com\"", + "admin.email.smtpPortExample": "Ex: \"25\", \"465\"", + "admin.email.connectionSecurityNone": "None", + "admin.email.connectionSecurityTls": "TLS (Recommended)", + "admin.email.connectionSecurityStart": "STARTTLS", + "admin.email.inviteSaltExample": "Ex \"bjlSR4QqkXFBr7TP4oDzlfZmcNuH9Yo\"", + "admin.email.passwordSaltExample": "Ex \"bjlSR4QqkXFBr7TP4oDzlfZmcNuH9Yo\"", + "admin.email.pushServerEx": "E.g.: \"https://push-test.mattermost.com\"", + "admin.email.testing": "Testing...", + "admin.email.saving": "Saving Config...", + "admin.email.emailSuccess": "No errors were reported while sending an email. Please check your inbox to make sure.", + "admin.email.emailFail": "Connection unsuccessful: {error}", + "admin.email.emailSettings": "Email Settings", + "admin.email.allowSignupTitle": "Allow Sign Up With Email: ", + "admin.email.true": "true", + "admin.email.false": "false", + "admin.email.allowSignupDescription": "When true, Mattermost allows team creation and account signup using email and password. This value should be false only when you want to limit signup to a single-sign-on service like OAuth or LDAP.", + "admin.email.notificationsTitle": "Send Email Notifications: ", + "admin.email.notificationsDescription": "Typically set to true in production. When true, Mattermost attempts to send email notifications. Developers may set this field to false to skip email setup for faster development.
    Setting this to true removes the Preview Mode banner (requires logging out and logging back in after setting is changed).", + "admin.email.requireVerificationTitle": "Require Email Verification: ", + "admin.email.requireVerificationDescription": "Typically set to true in production. When true, Mattermost requires email verification after account creation prior to allowing login. Developers may set this field to false so skip sending verification emails for faster development.", + "admin.email.notificationDisplayTitle": "Notification Display Name:", + "admin.email.notificationDisplayDescription": "Display name on email account used when sending notification emails from Mattermost.", + "admin.email.notificationEmailTitle": "Notification Email Address:", + "admin.email.notificationEmailDescription": "Email address displayed on email account used when sending notification emails from Mattermost.", + "admin.email.smtpUsernameTitle": "SMTP Username:", + "admin.email.smtpUsernameDescription": " Obtain this credential from administrator setting up your email server.", + "admin.email.smtpPasswordTitle": "SMTP Password:", + "admin.email.smtpPasswordDescription": " Obtain this credential from administrator setting up your email server.", + "admin.email.smtpServerTitle": "SMTP Server:", + "admin.email.smtpServerDescription": "Location of SMTP email server.", + "admin.email.smtpPortTitle": "SMTP Port:", + "admin.email.smtpPortDescription": "Port of SMTP email server.", + "admin.email.connectionSecurityTitle": "Connection Security:", + "admin.email.connectionSecurityNoneDescription": "Mattermost will send email over an unsecure connection.", + "admin.email.connectionSecurityTlsDescription": "Encrypts the communication between Mattermost and your email server.", + "admin.email.connectionSecurityStartDescription": "Takes an existing insecure connection and attempts to upgrade it to a secure connection using TLS.", + "admin.email.connectionSecurityTest": "Test Connection", + "admin.email.inviteSaltTitle": "Invite Salt:", + "admin.email.inviteSaltDescription": "32-character salt added to signing of email invites. Randomly generated on install. Click \"Re-Generate\" to create new salt.", + "admin.email.regenerate": "Re-Generate", + "admin.email.passwordSaltTitle": "Password Reset Salt:", + "admin.email.passwordSaltDescription": "32-character salt added to signing of password reset emails. Randomly generated on install. Click \"Re-Generate\" to create new salt.", + "admin.email.pushTitle": "Send Push Notifications: ", + "admin.email.pushDesc": "Typically set to true in production. When true, Mattermost attempts to send iOS and Android push notifications through the push notification server.", + "admin.email.pushServerTitle": "Push Notification Server:", + "admin.email.pushServerDesc": "Location of Mattermost push notification service you can set up behind your firewall using https://github.com/mattermost/push-proxy. For testing you can use https://push-test.mattermost.com, which connects to the sample Mattermost iOS app in the public Apple AppStore. Please do not use test service for production deployments.", + "admin.email.save": "Save", + "admin.select_team.selectTeam": "Select Team", + "admin.select_team.close": "Close", + "admin.select_team.select": "Select", + "error_bar.preview_mode": "Preview Mode: Email notifications have not been configured", + "loading_screen.loading": "Loading" } \ No newline at end of file diff --git a/web/static/i18n/es.json b/web/static/i18n/es.json index ade5f585e..bc53a78b0 100644 --- a/web/static/i18n/es.json +++ b/web/static/i18n/es.json @@ -1,5 +1,87 @@ { - "login.find_teams": "Find your other teams (spanish!)", - "login.forgot_password": "I forgot my password (spanish!)", - "error_bar.preview_mode": "Preview Mode: Email notifications have not been configured (spanish!)" + "admin.email.allowSignupDescription": "Cuando está en verdadero, Mattermost permite la creación de equipos y cuentas utilizando el correo electrónico y contraseña. Este valor debe estar en falso sólo cuando quieres limitar el inicio de sesión a través de servicios tipo OAuth o LDAP.", + "admin.email.allowSignupTitle": "Permitir inicio de sesión con correo:", + "admin.email.connectionSecurityNone": "Ninguno", + "admin.email.connectionSecurityNoneDescription": "Mattermost enviará los correos electrónicos sobre conexiones no seguras.", + "admin.email.connectionSecurityStart": "STARTTLS", + "admin.email.connectionSecurityStartDescription": "Tomar la conexión insegura e intentar actualizarla hacia una conexión segura utilizando TLS.", + "admin.email.connectionSecurityTest": "Prueba de conexión", + "admin.email.connectionSecurityTitle": "Seguridad de conexión:", + "admin.email.connectionSecurityTls": "TLS (Recomendado)", + "admin.email.connectionSecurityTlsDescription": "Cifra la comunicación entre Mattermost y tu servidor de correo electrónico.", + "admin.email.emailFail": "Conexión fallida: {error}", + "admin.email.emailSettings": "Configuraciones de correo", + "admin.email.emailSuccess": "No fueron reportados errores mientras se enviada el correo. Favor validar en tu bandeja de entrada.", + "admin.email.false": "falso", + "admin.email.inviteSaltDescription": "32-caracter salt añadido a la firma de invitación de correos. Aleatoriamente generado en la instalación. Click \"Re-Generar\" para crear nuevo salt.", + "admin.email.inviteSaltExample": "Ej \"bjlSR4QqkXFBr7TP4oDzlfZmcNuH9Yo\"", + "admin.email.inviteSaltTitle": "Salt de las invitaciones:", + "admin.email.notificationDisplayDescription": "Muestra el nombre en la cuenta del email utilizada para enviar notificaciones por correo electrónico desde Mattermost.", + "admin.email.notificationDisplayExample": "Ej: \"Notificación de Mattermost\", \"Sistema\", \"No-Responder\"", + "admin.email.notificationDisplayTitle": "Notificación de nombre mostrado:", + "admin.email.notificationEmailDescription": "La dirección de correo electrónico a mostrar en la cuenta de correo utilizada cuando se envian notificaciones por correo electrónico desde Mattermost.", + "admin.email.notificationEmailExample": "Ej: \"mattermost@tuempresa.com\", \"admin@tuempresa.com\"", + "admin.email.notificationEmailTitle": "Notificación de correo electrónico:", + "admin.email.notificationsDescription": "Normalmente se asigna como verdadero en producción. Cuando es verdadero, Mattermost intenta enviar las notificaciones por correo electrónico. Los desarrolladores puede que quieran dejar esta opción en falso para saltarse la configuración de correos para desarrollar más rápido.\nAsignar está opción como verdadero remueve la notificación de Modo de Prueba (requiere cerrar la sesión y abrirla nuevamente para que los cambios surjan efecto).", + "admin.email.notificationsTitle": "Enviar notificaciones por correo electrónico: ", + "admin.email.passwordSaltDescription": "Un salt de 32-caracteres es añadido a la firma de correos para restablecer la contraseña. Aleatoriamente generado en la instalación. Pincha \"Regenerar\" para crear un nuevo salt.", + "admin.email.passwordSaltExample": "Ej \"bjlSR4QqkXFBr7TP4oDzlfZmcNuH9Yo\"", + "admin.email.passwordSaltTitle": "Resetear el Salt para las contraseñas:", + "admin.email.pushDesc": "Normalmente se asigna como verdadero en producción. Cuando está en verdadero, Mattermost intenta enviar notificaciones a dispositivos iOS y Android a través del servidor de notificaciones.", + "admin.email.pushServerDesc": "Ubicación del servicio de notificaciones push de Mattermost, puedes ubicarlo detras de un cortafuego utilizando https://github.com/mattermost/push-proxy. Para realizar pruebas puedes utilizar https://push-test.mattermost.com, el cual conecta con la ap de ejemplo de Mattermost en iOS publicada en el Apple AppStore. Por favor no utilices este servicio en producción.", + "admin.email.pushServerEx": "Ej.: \"https://push-test.mattermost.com\"", + "admin.email.pushServerTitle": "Servidor de Notificaciones:", + "admin.email.pushTitle": "Envío de Notificaciones: ", + "admin.email.regenerate": "Regenerar", + "admin.email.requireVerificationDescription": "Normalmente asignado como verdadero en producción. Cuando es verdadero, Mattermost requiere una verificación del correo electrónico después de crear la cuenta y antes de iniciar sesión por primera vez. Los desarrolladores pude que quieran dejar esta opción en falso para evitar la necesidad de verificar correos y así desarrollar más rápido.", + "admin.email.requireVerificationTitle": "Require verificación de correo electrónico: ", + "admin.email.save": "Guardar", + "admin.email.saving": "Guardando...", + "admin.email.smtpPasswordDescription": " Obten esta credencial del administrador del servidor de correos.", + "admin.email.smtpPasswordExample": "Ej: \"tucontraseña\", \"jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY\"", + "admin.email.smtpPasswordTitle": "Contraseña del SMTP:", + "admin.email.smtpPortDescription": "Puerto de servidor SMTP", + "admin.email.smtpPortExample": "Ej: \"25\", \"465\"", + "admin.email.smtpPortTitle": "Puerto del SMTP:", + "admin.email.smtpServerDescription": "Ubicación de SMTP.", + "admin.email.smtpServerExample": "Ej: \"smtp.tuempresa.com\", \"email-smtp.us-east-1.amazonaws.com\"", + "admin.email.smtpServerTitle": "Servidor SMTP:", + "admin.email.smtpUsernameDescription": " Obten esta credencial del administrado del servidor de correos.", + "admin.email.smtpUsernameExample": "Ej: \"admin@tuempresa.com\", \"AKIADTOVBGERKLCBV\"", + "admin.email.smtpUsernameTitle": "Usuario SMTP:", + "admin.email.testing": "Probando...", + "admin.email.true": "verdadero", + "admin.nav.help": "Ayuda", + "admin.nav.logout": "Cerrar sesión", + "admin.nav.report": "Reportar problema", + "admin.nav.switch": "Cambiar a {display_name}", + "admin.select_team.close": "Cerrar", + "admin.select_team.select": "Seleccionar", + "admin.select_team.selectTeam": "Seleccionar grupo", + "admin.sidebar.addTeamSidebar": "Agregar un equipo el menú lateral", + "admin.sidebar.email": "Configuración de correo", + "admin.sidebar.file": "Configuracion de archivos", + "admin.sidebar.gitlab": "Configuración de GitLab", + "admin.sidebar.ldap": "Configuración LDAP", + "admin.sidebar.license": "Edición y Licencia", + "admin.sidebar.loading": "Cargando", + "admin.sidebar.log": "Configuracion de log", + "admin.sidebar.logs": "Registros", + "admin.sidebar.other": "OTROS", + "admin.sidebar.privacy": "Configuración de privacidad", + "admin.sidebar.rate_limit": "Configuración de velocidad", + "admin.sidebar.reports": "REPORTES DEL SITIO", + "admin.sidebar.rmTeamSidebar": "Remover un equipo del menú lateral", + "admin.sidebar.service": "Configuración de servicio", + "admin.sidebar.settings": "CONFIGURACIONES", + "admin.sidebar.sql": "Configuración de SQL", + "admin.sidebar.statistics": "- Estadísticas", + "admin.sidebar.support": "Configuración de Soporte", + "admin.sidebar.team": "Configuración de equipo", + "admin.sidebar.teams": "EQUIPOS ({count})", + "admin.sidebar.users": "- Usuarios", + "admin.sidebar.view_statistics": "Ver Estadísticas", + "admin.sidebarHeader.systemConsole": "Consola de sistema", + "error_bar.preview_mode": "Modo de prueba: Las notificaciones por correo electrónico no han sido configuradas", + "loading_screen.loading": "Cargando" } \ No newline at end of file -- cgit v1.2.3-1-g7c22 From c2bc9454ce5550a180d8ee9fec75db7f3841b1ad Mon Sep 17 00:00:00 2001 From: =Corey Hulen Date: Tue, 26 Jan 2016 20:32:24 -0500 Subject: PLT-1586 adding attach device id method --- api/user.go | 45 ++++++++++++++++++++++++++++++++++++++++- api/user_test.go | 28 +++++++++++++++++++++++++ i18n/en.json | 4 ++++ model/client.go | 11 ++++++++++ model/version.go | 2 +- store/sql_session_store.go | 18 +++++++++++++++++ store/sql_session_store_test.go | 22 ++++++++++++++++++++ store/store.go | 1 + web/react/components/login.jsx | 25 ++++++++++++++--------- 9 files changed, 145 insertions(+), 11 deletions(-) diff --git a/api/user.go b/api/user.go index 473f0da54..ceaf1fc2d 100644 --- a/api/user.go +++ b/api/user.go @@ -48,6 +48,7 @@ func InitUser(r *mux.Router) { sr.Handle("/logout", ApiUserRequired(logout)).Methods("POST") sr.Handle("/login_ldap", ApiAppHandler(loginLdap)).Methods("POST") sr.Handle("/revoke_session", ApiUserRequired(revokeSession)).Methods("POST") + sr.Handle("/attach_device", ApiUserRequired(attachDeviceId)).Methods("POST") sr.Handle("/switch_to_sso", ApiAppHandler(switchToSSO)).Methods("POST") sr.Handle("/switch_to_email", ApiUserRequired(switchToEmail)).Methods("POST") @@ -546,7 +547,6 @@ func Login(c *Context, w http.ResponseWriter, r *http.Request, user *model.User, } } } - } else { session.SetExpireInDays(*utils.Cfg.ServiceSettings.SessionLengthWebInDays) } @@ -718,6 +718,49 @@ func revokeSession(c *Context, w http.ResponseWriter, r *http.Request) { w.Write([]byte(model.MapToJson(props))) } +func attachDeviceId(c *Context, w http.ResponseWriter, r *http.Request) { + props := model.MapFromJson(r.Body) + + deviceId := props["device_id"] + if len(deviceId) == 0 { + c.SetInvalidParam("attachDevice", "deviceId") + return + } + + if !(strings.HasPrefix(deviceId, model.PUSH_NOTIFY_APPLE+":") || strings.HasPrefix(deviceId, model.PUSH_NOTIFY_ANDROID+":")) { + c.SetInvalidParam("attachDevice", "deviceId") + return + } + + // A special case where we logout of all other sessions with the same Id + if result := <-Srv.Store.Session().GetSessions(c.Session.UserId); result.Err != nil { + c.Err = result.Err + c.Err.StatusCode = http.StatusForbidden + return + } else { + sessions := result.Data.([]*model.Session) + for _, session := range sessions { + if session.DeviceId == deviceId && session.Id != c.Session.Id { + l4g.Debug(utils.T("api.user.login.revoking.app_error"), session.Id, c.Session.UserId) + RevokeSessionById(c, session.Id) + if c.Err != nil { + c.LogError(c.Err) + c.Err = nil + } + } + } + } + + sessionCache.Remove(c.Session.Token) + + if result := <-Srv.Store.Session().UpdateDeviceId(c.Session.Id, deviceId); result.Err != nil { + c.Err = result.Err + return + } + + w.Write([]byte(deviceId)) +} + func RevokeSessionById(c *Context, sessionId string) { if result := <-Srv.Store.Session().Get(sessionId); result.Err != nil { c.Err = result.Err diff --git a/api/user_test.go b/api/user_test.go index 9a172805a..5f85bda0f 100644 --- a/api/user_test.go +++ b/api/user_test.go @@ -734,6 +734,34 @@ func TestUserUpdateRoles(t *testing.T) { } } +func TestUserUpdateDeviceId(t *testing.T) { + Setup() + + team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) + + user := &model.User{TeamId: team.Id, Email: "test@nowhere.com", Nickname: "Corey Hulen", Password: "pwd"} + user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) + store.Must(Srv.Store.User().VerifyEmail(user.Id)) + + Client.LoginByEmail(team.Name, user.Email, "pwd") + deviceId := model.PUSH_NOTIFY_APPLE + ":1234567890" + + if _, err := Client.AttachDeviceId(deviceId); err != nil { + t.Fatal(err) + } + + if result := <-Srv.Store.Session().GetSessions(user.Id); result.Err != nil { + t.Fatal(result.Err) + } else { + sessions := result.Data.([]*model.Session) + + if sessions[0].DeviceId != deviceId { + t.Fatal("Missing device Id") + } + } +} + func TestUserUpdateActive(t *testing.T) { Setup() diff --git a/i18n/en.json b/i18n/en.json index 72863acd9..14a2e3148 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -2771,6 +2771,10 @@ "id": "store.sql_session.update_roles.app_error", "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" diff --git a/model/client.go b/model/client.go index 8021c7039..d31ac1592 100644 --- a/model/client.go +++ b/model/client.go @@ -775,6 +775,17 @@ func (c *Client) UpdateUserRoles(data map[string]string) (*Result, *AppError) { } } +func (c *Client) AttachDeviceId(deviceId string) (*Result, *AppError) { + data := make(map[string]string) + data["device_id"] = deviceId + if r, err := c.DoApiPost("/users/attach_device", MapToJson(data)); err != nil { + return nil, err + } else { + return &Result{r.Header.Get(HEADER_REQUEST_ID), + r.Header.Get(HEADER_ETAG_SERVER), UserFromJson(r.Body)}, nil + } +} + func (c *Client) UpdateActive(userId string, active bool) (*Result, *AppError) { data := make(map[string]string) data["user_id"] = userId diff --git a/model/version.go b/model/version.go index 88334ceea..f503ddb84 100644 --- a/model/version.go +++ b/model/version.go @@ -28,7 +28,7 @@ var CurrentVersion string = versions[0] var BuildNumber = "_BUILD_NUMBER_" var BuildDate = "_BUILD_DATE_" var BuildHash = "_BUILD_HASH_" -var BuildEnterpriseReady = "_BUILD_ENTERPRISE_READY_" +var BuildEnterpriseReady = "false" func SplitVersion(version string) (int64, int64, int64) { parts := strings.Split(version, ".") diff --git a/store/sql_session_store.go b/store/sql_session_store.go index 4762a1dfd..6532947f4 100644 --- a/store/sql_session_store.go +++ b/store/sql_session_store.go @@ -232,3 +232,21 @@ func (me SqlSessionStore) UpdateRoles(userId, roles string) StoreChannel { return storeChannel } + +func (me SqlSessionStore) UpdateDeviceId(id, deviceId string) StoreChannel { + storeChannel := make(StoreChannel) + + go func() { + result := StoreResult{} + if _, err := me.GetMaster().Exec("UPDATE Sessions SET DeviceId = :DeviceId WHERE Id = :Id", map[string]interface{}{"DeviceId": deviceId, "Id": id}); err != nil { + result.Err = model.NewLocAppError("SqlSessionStore.UpdateDeviceId", "store.sql_session.update_device_id.app_error", nil, "") + } else { + result.Data = deviceId + } + + storeChannel <- result + close(storeChannel) + }() + + return storeChannel +} diff --git a/store/sql_session_store_test.go b/store/sql_session_store_test.go index cec8e93b0..34d3128a6 100644 --- a/store/sql_session_store_test.go +++ b/store/sql_session_store_test.go @@ -157,6 +157,28 @@ func TestSessionRemoveToken(t *testing.T) { } } +func TestSessionUpdateDeviceId(t *testing.T) { + Setup() + + s1 := model.Session{} + s1.UserId = model.NewId() + s1.TeamId = model.NewId() + Must(store.Session().Save(&s1)) + + if rs1 := (<-store.Session().UpdateDeviceId(s1.Id, model.PUSH_NOTIFY_APPLE+":1234567890")); rs1.Err != nil { + t.Fatal(rs1.Err) + } + + s2 := model.Session{} + s2.UserId = model.NewId() + s2.TeamId = model.NewId() + Must(store.Session().Save(&s2)) + + if rs2 := (<-store.Session().UpdateDeviceId(s2.Id, model.PUSH_NOTIFY_APPLE+":1234567890")); rs2.Err != nil { + t.Fatal(rs2.Err) + } +} + func TestSessionStoreUpdateLastActivityAt(t *testing.T) { Setup() diff --git a/store/store.go b/store/store.go index b91b08f27..e8c5537ac 100644 --- a/store/store.go +++ b/store/store.go @@ -137,6 +137,7 @@ type SessionStore interface { PermanentDeleteSessionsByUser(teamId string) StoreChannel UpdateLastActivityAt(sessionId string, time int64) StoreChannel UpdateRoles(userId string, roles string) StoreChannel + UpdateDeviceId(sessionIdOrToken string, deviceId string) StoreChannel } type AuditStore interface { diff --git a/web/react/components/login.jsx b/web/react/components/login.jsx index 57e9a3788..3c1d66334 100644 --- a/web/react/components/login.jsx +++ b/web/react/components/login.jsx @@ -137,6 +137,21 @@ export default class Login extends React.Component { ); } + let findTeams = null; + if (!Utils.isMobileApp()) { + findTeams = ( +
    + + + + +
    + ); + } + return (
    {'Sign in to:'}
    @@ -147,15 +162,7 @@ export default class Login extends React.Component { {emailSignup} {ldapLogin} {userSignUp} -
    - - - - -
    + {findTeams} {forgotPassword} {teamSignUp}
    -- cgit v1.2.3-1-g7c22 From 038e50b5b5a2d813a3ac82850ce866c1a13089e4 Mon Sep 17 00:00:00 2001 From: =Corey Hulen Date: Tue, 26 Jan 2016 21:04:29 -0500 Subject: PLT-1586 Changing session reporting --- web/react/components/activity_log_modal.jsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/web/react/components/activity_log_modal.jsx b/web/react/components/activity_log_modal.jsx index 6a880f0ee..91ca87647 100644 --- a/web/react/components/activity_log_modal.jsx +++ b/web/react/components/activity_log_modal.jsx @@ -100,12 +100,15 @@ export default class ActivityLogModal extends React.Component { if (currentSession.props.platform === 'Windows') { devicePicture = 'fa fa-windows'; + } else if (currentSession.device_id.indexOf('apple:') === 0) { + devicePicture = 'fa fa-apple'; + devicePlatform = 'iPhone Native App'; + } else if (currentSession.device_id.indexOf('Android:') === 0) { + devicePlatform = 'Android Native App'; + devicePicture = 'fa fa-android'; } else if (currentSession.props.platform === 'Macintosh' || currentSession.props.platform === 'iPhone') { devicePicture = 'fa fa-apple'; - } else if (currentSession.props.platform.browser.indexOf('Mattermost/') === 0) { - devicePicture = 'fa fa-apple'; - devicePlatform = 'iPhone'; } else if (currentSession.props.platform === 'Linux') { if (currentSession.props.os.indexOf('Android') >= 0) { devicePlatform = 'Android'; -- cgit v1.2.3-1-g7c22 From 0dec26bb9f3e05a84873996c9ecb3bd87b7dc925 Mon Sep 17 00:00:00 2001 From: =Corey Hulen Date: Tue, 26 Jan 2016 21:11:39 -0500 Subject: Fixing version --- model/version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/model/version.go b/model/version.go index f503ddb84..88334ceea 100644 --- a/model/version.go +++ b/model/version.go @@ -28,7 +28,7 @@ var CurrentVersion string = versions[0] var BuildNumber = "_BUILD_NUMBER_" var BuildDate = "_BUILD_DATE_" var BuildHash = "_BUILD_HASH_" -var BuildEnterpriseReady = "false" +var BuildEnterpriseReady = "_BUILD_ENTERPRISE_READY_" func SplitVersion(version string) (int64, int64, int64) { parts := strings.Split(version, ".") -- cgit v1.2.3-1-g7c22 From 6fd0f651b94e594dddb00800bf295af7ff42934e Mon Sep 17 00:00:00 2001 From: =Corey Hulen Date: Tue, 26 Jan 2016 21:13:44 -0500 Subject: Fixing naming --- store/store.go | 2 +- web/react/components/activity_log_modal.jsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/store/store.go b/store/store.go index e8c5537ac..3988f0c6a 100644 --- a/store/store.go +++ b/store/store.go @@ -137,7 +137,7 @@ type SessionStore interface { PermanentDeleteSessionsByUser(teamId string) StoreChannel UpdateLastActivityAt(sessionId string, time int64) StoreChannel UpdateRoles(userId string, roles string) StoreChannel - UpdateDeviceId(sessionIdOrToken string, deviceId string) StoreChannel + UpdateDeviceId(id string, deviceId string) StoreChannel } type AuditStore interface { diff --git a/web/react/components/activity_log_modal.jsx b/web/react/components/activity_log_modal.jsx index 91ca87647..eec4d8f8d 100644 --- a/web/react/components/activity_log_modal.jsx +++ b/web/react/components/activity_log_modal.jsx @@ -103,7 +103,7 @@ export default class ActivityLogModal extends React.Component { } else if (currentSession.device_id.indexOf('apple:') === 0) { devicePicture = 'fa fa-apple'; devicePlatform = 'iPhone Native App'; - } else if (currentSession.device_id.indexOf('Android:') === 0) { + } else if (currentSession.device_id.indexOf('android:') === 0) { devicePlatform = 'Android Native App'; devicePicture = 'fa fa-android'; } else if (currentSession.props.platform === 'Macintosh' || -- cgit v1.2.3-1-g7c22 From c4cca4d41835bb5a2532adb8be05bdd2c97d2544 Mon Sep 17 00:00:00 2001 From: JoramWilander Date: Wed, 27 Jan 2016 07:37:05 -0500 Subject: Updated search test cases --- api/post_test.go | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/api/post_test.go b/api/post_test.go index a18c72082..72abcd5cc 100644 --- a/api/post_test.go +++ b/api/post_test.go @@ -613,7 +613,10 @@ func TestSearchPostsFromUser(t *testing.T) { t.Fatalf("wrong number of posts returned %v", len(result.Order)) } - if result := Client.Must(Client.SearchPosts("from: " + user2.Username + " in:" + channel1.Name)).Data.(*model.PostList); len(result.Order) != 0 { + post3 := &model.Post{ChannelId: channel1.Id, Message: "hullo"} + post3 = Client.Must(Client.CreatePost(post3)).Data.(*model.Post) + + if result := Client.Must(Client.SearchPosts("from: " + user2.Username + " in:" + channel1.Name)).Data.(*model.PostList); len(result.Order) != 1 { t.Fatalf("wrong number of posts returned %v", len(result.Order)) } @@ -628,11 +631,11 @@ func TestSearchPostsFromUser(t *testing.T) { // wait for the join/leave messages to be created for user3 since they're done asynchronously time.Sleep(100 * time.Millisecond) - if result := Client.Must(Client.SearchPosts("from: " + user2.Username)).Data.(*model.PostList); len(result.Order) != 1 { + if result := Client.Must(Client.SearchPosts("from: " + user2.Username)).Data.(*model.PostList); len(result.Order) != 2 { t.Fatalf("wrong number of posts returned %v", len(result.Order)) } - if result := Client.Must(Client.SearchPosts("from: " + user2.Username + " from: " + user3.Username)).Data.(*model.PostList); len(result.Order) != 1 { + if result := Client.Must(Client.SearchPosts("from: " + user2.Username + " from: " + user3.Username)).Data.(*model.PostList); len(result.Order) != 2 { t.Fatalf("wrong number of posts returned %v", len(result.Order)) } @@ -640,7 +643,10 @@ func TestSearchPostsFromUser(t *testing.T) { t.Fatalf("wrong number of posts returned %v", len(result.Order)) } - if result := Client.Must(Client.SearchPosts("from: " + user2.Username + " from: " + user3.Username + " in:" + channel2.Name + " joined")).Data.(*model.PostList); len(result.Order) != 0 { + post4 := &model.Post{ChannelId: channel2.Id, Message: "coconut"} + post4 = Client.Must(Client.CreatePost(post4)).Data.(*model.Post) + + if result := Client.Must(Client.SearchPosts("from: " + user2.Username + " from: " + user3.Username + " in:" + channel2.Name + " coconut")).Data.(*model.PostList); len(result.Order) != 1 { t.Fatalf("wrong number of posts returned %v", len(result.Order)) } } -- cgit v1.2.3-1-g7c22 From 8497c97a6fff097b8b58a4aa65bb7f068bfd3dca Mon Sep 17 00:00:00 2001 From: Elias Nahum Date: Wed, 27 Jan 2016 11:51:16 -0300 Subject: Update mm-intl reference --- web/react/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/react/package.json b/web/react/package.json index 1d3389896..f36e55b40 100644 --- a/web/react/package.json +++ b/web/react/package.json @@ -8,7 +8,7 @@ "highlight.js": "8.9.1", "keymirror": "0.1.1", "marked": "mattermost/marked#cb85e5cc81bc7937dbb73c3c53d9532b1b97e3ca", - "mm-intl": "^1.0.0", + "mm-intl": "mattermost/mm-intl#805442fd474fa40cd586ddeda404dbbe8e60626d", "object-assign": "4.0.1", "twemoji": "1.4.1" }, -- cgit v1.2.3-1-g7c22 From 41013daa0b86a450ade9cdac911ccdb7722eb473 Mon Sep 17 00:00:00 2001 From: =Corey Hulen Date: Wed, 27 Jan 2016 10:07:26 -0500 Subject: Fixing small issue with displaying activity log --- web/react/components/activity_log_modal.jsx | 4 ++-- web/react/components/error_bar.jsx | 1 + web/react/stores/error_store.jsx | 4 ++++ 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/web/react/components/activity_log_modal.jsx b/web/react/components/activity_log_modal.jsx index eec4d8f8d..2c42f5971 100644 --- a/web/react/components/activity_log_modal.jsx +++ b/web/react/components/activity_log_modal.jsx @@ -100,10 +100,10 @@ export default class ActivityLogModal extends React.Component { if (currentSession.props.platform === 'Windows') { devicePicture = 'fa fa-windows'; - } else if (currentSession.device_id.indexOf('apple:') === 0) { + } else if (currentSession.device_id && currentSession.device_id.indexOf('apple:') === 0) { devicePicture = 'fa fa-apple'; devicePlatform = 'iPhone Native App'; - } else if (currentSession.device_id.indexOf('android:') === 0) { + } else if (currentSession.device_id && currentSession.device_id.indexOf('android:') === 0) { devicePlatform = 'Android Native App'; devicePicture = 'fa fa-android'; } else if (currentSession.props.platform === 'Macintosh' || diff --git a/web/react/components/error_bar.jsx b/web/react/components/error_bar.jsx index e93545c25..27d885aa8 100644 --- a/web/react/components/error_bar.jsx +++ b/web/react/components/error_bar.jsx @@ -82,6 +82,7 @@ export default class ErrorBar extends React.Component { e.preventDefault(); } + ErrorStore.clearLastError(); this.setState({message: null}); } diff --git a/web/react/stores/error_store.jsx b/web/react/stores/error_store.jsx index 69d6cca7f..ed46d6b68 100644 --- a/web/react/stores/error_store.jsx +++ b/web/react/stores/error_store.jsx @@ -46,6 +46,10 @@ class ErrorStoreClass extends EventEmitter { storeLastError(error) { BrowserStore.setItem('last_error', error); } + + clearLastError() { + BrowserStore.removeItem('last_error'); + } } var ErrorStore = new ErrorStoreClass(); -- cgit v1.2.3-1-g7c22 From 1575488650f4d6df49820043af68e57489c66a46 Mon Sep 17 00:00:00 2001 From: JoramWilander Date: Wed, 27 Jan 2016 10:50:48 -0500 Subject: Clear deleted messages after channel leave and fix missig postlist error --- web/react/dispatcher/event_helpers.jsx | 3 ++- web/react/stores/post_store.jsx | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/web/react/dispatcher/event_helpers.jsx b/web/react/dispatcher/event_helpers.jsx index a03923c1a..5eb319320 100644 --- a/web/react/dispatcher/event_helpers.jsx +++ b/web/react/dispatcher/event_helpers.jsx @@ -19,7 +19,8 @@ export function emitChannelClickEvent(channel) { AppDispatcher.handleViewAction({ type: ActionTypes.CLICK_CHANNEL, name: channel.name, - id: channel.id + id: channel.id, + prev: ChannelStore.getCurrentId() }); } diff --git a/web/react/stores/post_store.jsx b/web/react/stores/post_store.jsx index 2212edadb..7abadf2b1 100644 --- a/web/react/stores/post_store.jsx +++ b/web/react/stores/post_store.jsx @@ -451,6 +451,8 @@ class PostStoreClass extends EventEmitter { post.filenames = []; posts[post.id] = post; + + this.makePostsInfo(post.channel_id); this.postsInfo[post.channel_id].deletedPosts = posts; } @@ -610,7 +612,7 @@ PostStore.dispatchToken = AppDispatcher.register((payload) => { case ActionTypes.CLICK_CHANNEL: PostStore.clearFocusedPost(); PostStore.clearChannelVisibility(action.id, true); - PostStore.clearUnseenDeletedPosts(action.id); + PostStore.clearUnseenDeletedPosts(action.prev); break; case ActionTypes.CREATE_POST: PostStore.storePendingPost(action.post); -- cgit v1.2.3-1-g7c22 From dc4b71bba2cad5e85e86cc6ecb743bea84f9ccc8 Mon Sep 17 00:00:00 2001 From: =Corey Hulen Date: Wed, 27 Jan 2016 10:56:15 -0500 Subject: PLT-7 Removing AppError ctor --- api/admin.go | 4 +++- api/license.go | 4 ++-- api/user.go | 2 +- i18n/en.json | 12 ++++++++++++ model/utils.go | 10 ---------- 5 files changed, 18 insertions(+), 14 deletions(-) diff --git a/api/admin.go b/api/admin.go index b19772fdf..0ea6341e2 100644 --- a/api/admin.go +++ b/api/admin.go @@ -73,7 +73,9 @@ func logClient(c *Context, w http.ResponseWriter, r *http.Request) { } if lvl == "ERROR" { - err := model.NewAppError("client", msg, "") + err := &model.AppError{} + err.Message = msg + err.Where = "client" c.LogError(err) } diff --git a/api/license.go b/api/license.go index 5c602a68e..5c97637e7 100644 --- a/api/license.go +++ b/api/license.go @@ -65,13 +65,13 @@ func addLicense(c *Context, w http.ResponseWriter, r *http.Request) { license = model.LicenseFromJson(strings.NewReader(licenseStr)) if result := <-Srv.Store.User().AnalyticsUniqueUserCount(""); result.Err != nil { - c.Err = model.NewAppError("addLicense", "Unable to count total unique users.", fmt.Sprintf("err=%v", result.Err.Error())) + c.Err = model.NewLocAppError("addLicense", api.license.add_license.invalid_count.app_error, nil, result.Err.Error()) return } else { uniqueUserCount := result.Data.(int64) if uniqueUserCount > int64(*license.Features.Users) { - c.Err = model.NewAppError("addLicense", fmt.Sprintf("This license only supports %d users, when your system has %d unique users. Unique users are counted distinctly by email address. You can see total user count under Site Reports -> View Statistics.", *license.Features.Users, uniqueUserCount), "") + c.Err = model.NewAppError("addLicense", api.license.add_license.unique_users.app_error, map[string]interface{}{"Users": *license.Features.Users, "Count": uniqueUserCount}) return } } diff --git a/api/user.go b/api/user.go index ceaf1fc2d..6ad0f67ac 100644 --- a/api/user.go +++ b/api/user.go @@ -1157,7 +1157,7 @@ func uploadProfileImage(c *Context, w http.ResponseWriter, r *http.Request) { path := "teams/" + c.Session.TeamId + "/users/" + c.Session.UserId + "/profile.png" if err := writeFile(buf.Bytes(), path); err != nil { - c.Err = model.NewAppError("uploadProfileImage", "Couldn't upload profile image", "") + c.Err = model.NewLocAppError("uploadProfileImage", "api.user.upload_profile_user.upload_profile.app_error", nil, "") return } diff --git a/i18n/en.json b/i18n/en.json index 14a2e3148..12741fc68 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -587,6 +587,14 @@ "id": "api.license.add_license.open.app_error", "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.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." @@ -1543,6 +1551,10 @@ "id": "api.user.upload_profile_user.encode.app_error", "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" diff --git a/model/utils.go b/model/utils.go index 719a2ad1b..695d4a0cb 100644 --- a/model/utils.go +++ b/model/utils.go @@ -71,16 +71,6 @@ func AppErrorFromJson(data io.Reader) *AppError { } } -func NewAppError(where string, message string, details string) *AppError { - ap := &AppError{} - ap.Message = message - ap.Where = where - ap.DetailedError = details - ap.StatusCode = 500 - ap.IsOAuth = false - return ap -} - func NewLocAppError(where string, id string, params map[string]interface{}, details string) *AppError { ap := &AppError{} ap.Id = id -- cgit v1.2.3-1-g7c22 From c50b8661ec0c1925062d3d5584ed5899c9e7ff44 Mon Sep 17 00:00:00 2001 From: =Corey Hulen Date: Wed, 27 Jan 2016 11:05:46 -0500 Subject: Fixing build --- api/license.go | 5 ++--- model/utils_test.go | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/api/license.go b/api/license.go index 5c97637e7..4077c0e46 100644 --- a/api/license.go +++ b/api/license.go @@ -5,7 +5,6 @@ package api import ( "bytes" - "fmt" l4g "github.com/alecthomas/log4go" "github.com/gorilla/mux" "github.com/mattermost/platform/model" @@ -65,13 +64,13 @@ func addLicense(c *Context, w http.ResponseWriter, r *http.Request) { license = model.LicenseFromJson(strings.NewReader(licenseStr)) if result := <-Srv.Store.User().AnalyticsUniqueUserCount(""); result.Err != nil { - c.Err = model.NewLocAppError("addLicense", api.license.add_license.invalid_count.app_error, nil, result.Err.Error()) + c.Err = model.NewLocAppError("addLicense", "api.license.add_license.invalid_count.app_error", nil, result.Err.Error()) return } else { uniqueUserCount := result.Data.(int64) if uniqueUserCount > int64(*license.Features.Users) { - c.Err = model.NewAppError("addLicense", api.license.add_license.unique_users.app_error, map[string]interface{}{"Users": *license.Features.Users, "Count": uniqueUserCount}) + c.Err = model.NewLocAppError("addLicense", "api.license.add_license.unique_users.app_error", map[string]interface{}{"Users": *license.Features.Users, "Count": uniqueUserCount}, "") return } } diff --git a/model/utils_test.go b/model/utils_test.go index b8eabe264..02a08d113 100644 --- a/model/utils_test.go +++ b/model/utils_test.go @@ -27,7 +27,7 @@ func TestRandomString(t *testing.T) { } func TestAppError(t *testing.T) { - err := NewAppError("TestAppError", "message", "") + err := NewLocAppError("TestAppError", "message", nil, "") json := err.ToJson() rerr := AppErrorFromJson(strings.NewReader(json)) if err.Message != rerr.Message { -- cgit v1.2.3-1-g7c22 From e20f78ad0d20ca7de6acc1150a0d544aebebe9d0 Mon Sep 17 00:00:00 2001 From: JoramWilander Date: Wed, 27 Jan 2016 11:27:16 -0500 Subject: Clear user typing text on channel switch --- web/react/components/msg_typing.jsx | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/web/react/components/msg_typing.jsx b/web/react/components/msg_typing.jsx index 78b67a216..35a832875 100644 --- a/web/react/components/msg_typing.jsx +++ b/web/react/components/msg_typing.jsx @@ -25,9 +25,17 @@ export default class MsgTyping extends React.Component { SocketStore.addChangeListener(this.onChange); } - componentWillReceiveProps(newProps) { - if (this.props.channelId !== newProps.channelId) { - this.updateTypingText(); + componentWillReceiveProps(nextProps) { + if (this.props.channelId !== nextProps.channelId) { + for (const u in this.typingUsers) { + if (!this.typingUsers.hasOwnProperty(u)) { + continue; + } + + clearTimeout(this.typingUsers[u]); + } + this.typingUsers = {}; + this.setState({text: ''}); } } -- cgit v1.2.3-1-g7c22 From 5c27aff06ffecc249edcdc6cc9d6daea8f137add Mon Sep 17 00:00:00 2001 From: JoramWilander Date: Wed, 27 Jan 2016 11:44:00 -0500 Subject: Only set store preferences if not null in head.html --- web/templates/head.html | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/web/templates/head.html b/web/templates/head.html index 257877376..b1ec905b5 100644 --- a/web/templates/head.html +++ b/web/templates/head.html @@ -77,7 +77,9 @@ window.mm_preferences = {{ .Preferences }}; $(function() { - PreferenceStore.setPreferences(window.mm_preferences); + if (window.mm_preferences != null) { + PreferenceStore.setPreferences(window.mm_preferences); + } }); if ({{.SessionTokenIndex}} >= 0) { -- cgit v1.2.3-1-g7c22 From b51845fde54230c12b5fc250109a5b08f4b5a0de Mon Sep 17 00:00:00 2001 From: Asaad Mahmood Date: Wed, 27 Jan 2016 23:45:02 +0500 Subject: Updating tutorial gif for sidebar --- web/react/components/tutorial/tutorial_tip.jsx | 2 +- web/sass-files/sass/partials/_tutorial.scss | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/web/react/components/tutorial/tutorial_tip.jsx b/web/react/components/tutorial/tutorial_tip.jsx index 03ecdabab..23daa4707 100644 --- a/web/react/components/tutorial/tutorial_tip.jsx +++ b/web/react/components/tutorial/tutorial_tip.jsx @@ -71,7 +71,7 @@ export default class TutorialTip extends React.Component { } var tipColor = ''; - if (this.props.overlayClass === 'tip-overlay--header') { + if (this.props.overlayClass === 'tip-overlay--header' || this.props.overlayClass === 'tip-overlay--sidebar') { tipColor = 'White'; } diff --git a/web/sass-files/sass/partials/_tutorial.scss b/web/sass-files/sass/partials/_tutorial.scss index 20a15441e..0a2d1e704 100644 --- a/web/sass-files/sass/partials/_tutorial.scss +++ b/web/sass-files/sass/partials/_tutorial.scss @@ -146,6 +146,7 @@ &.tip-overlay--sidebar { left: 0; + @include opacity(0.8); top: -9px; } -- cgit v1.2.3-1-g7c22 From c8ca70870f1e202eb5784839520199fdf0beaeec Mon Sep 17 00:00:00 2001 From: Elias Nahum Date: Wed, 27 Jan 2016 15:49:26 -0300 Subject: PLT-7: Refactoring frontend (chunk 2) - System Console complete --- web/react/components/admin_console/analytics.jsx | 119 ++++++-- .../components/admin_console/gitlab_settings.jsx | 150 ++++++++-- .../components/admin_console/image_settings.jsx | 295 +++++++++++++++---- .../components/admin_console/ldap_settings.jsx | 266 +++++++++++++---- .../admin_console/legal_and_support_settings.jsx | 100 ++++++- .../components/admin_console/license_settings.jsx | 153 ++++++---- .../components/admin_console/log_settings.jsx | 173 +++++++++-- web/react/components/admin_console/logs.jsx | 14 +- .../components/admin_console/privacy_settings.jsx | 72 ++++- .../components/admin_console/rate_settings.jsx | 141 +++++++-- .../admin_console/reset_password_modal.jsx | 38 ++- .../components/admin_console/service_settings.jsx | 322 +++++++++++++++++---- .../components/admin_console/sql_settings.jsx | 145 ++++++++-- .../components/admin_console/system_analytics.jsx | 29 +- .../components/admin_console/team_analytics.jsx | 23 +- .../components/admin_console/team_settings.jsx | 171 +++++++++-- web/react/components/admin_console/team_users.jsx | 23 +- web/react/components/admin_console/user_item.jsx | 60 +++- web/static/i18n/en.json | 314 ++++++++++++++++++++ web/static/i18n/es.json | 314 ++++++++++++++++++++ 20 files changed, 2485 insertions(+), 437 deletions(-) diff --git a/web/react/components/admin_console/analytics.jsx b/web/react/components/admin_console/analytics.jsx index 70ef1ecab..ff5903c62 100644 --- a/web/react/components/admin_console/analytics.jsx +++ b/web/react/components/admin_console/analytics.jsx @@ -8,6 +8,8 @@ import LineChart from './line_chart.jsx'; var Tooltip = ReactBootstrap.Tooltip; var OverlayTrigger = ReactBootstrap.OverlayTrigger; +import {FormattedMessage} from 'mm-intl'; + export default class Analytics extends React.Component { constructor(props) { super(props); @@ -21,11 +23,23 @@ export default class Analytics extends React.Component { serverError =
    ; } + let loading = ( + + ); + var totalCount = (
    -
    {'Total Users'}
    -
    {this.props.uniqueUserCount == null ? 'Loading...' : this.props.uniqueUserCount}
    +
    + +
    +
    {this.props.uniqueUserCount == null ? loading : this.props.uniqueUserCount}
    ); @@ -33,8 +47,13 @@ export default class Analytics extends React.Component { var openChannelCount = (
    -
    {'Public Channels'}
    -
    {this.props.channelOpenCount == null ? 'Loading...' : this.props.channelOpenCount}
    +
    + +
    +
    {this.props.channelOpenCount == null ? loading : this.props.channelOpenCount}
    ); @@ -42,8 +61,13 @@ export default class Analytics extends React.Component { var openPrivateCount = (
    -
    {'Private Groups'}
    -
    {this.props.channelPrivateCount == null ? 'Loading...' : this.props.channelPrivateCount}
    +
    + +
    +
    {this.props.channelPrivateCount == null ? loading : this.props.channelPrivateCount}
    ); @@ -51,8 +75,13 @@ export default class Analytics extends React.Component { var postCount = (
    -
    {'Total Posts'}
    -
    {this.props.postCount == null ? 'Loading...' : this.props.postCount}
    +
    + +
    +
    {this.props.postCount == null ? loading : this.props.postCount}
    ); @@ -60,8 +89,13 @@ export default class Analytics extends React.Component { var postCountsByDay = (
    -
    {'Total Posts'}
    -
    {'Loading...'}
    +
    + +
    +
    {loading}
    ); @@ -69,7 +103,12 @@ export default class Analytics extends React.Component { if (this.props.postCountsDay != null) { let content; if (this.props.postCountsDay.labels.length === 0) { - content = 'Not enough data for a meaningful representation.'; + content = ( + + ); } else { content = (
    -
    {'Total Posts'}
    +
    + +
    {content}
    @@ -94,8 +138,13 @@ export default class Analytics extends React.Component { var usersWithPostsByDay = (
    -
    {'Active Users With Posts'}
    -
    {'Loading...'}
    +
    + +
    +
    {loading}
    ); @@ -103,7 +152,12 @@ export default class Analytics extends React.Component { if (this.props.userCountsWithPostsDay != null) { let content; if (this.props.userCountsWithPostsDay.labels.length === 0) { - content = 'Not enough data for a meaningful representation.'; + content = ( + + ); } else { content = (
    -
    {'Active Users With Posts'}
    +
    + +
    {content}
    @@ -129,7 +188,7 @@ export default class Analytics extends React.Component { if (this.props.recentActiveUsers != null) { let content; if (this.props.recentActiveUsers.length === 0) { - content = 'Loading...'; + content = loading; } else { content = ( @@ -167,7 +226,12 @@ export default class Analytics extends React.Component { recentActiveUser = (
    -
    {'Recent Active Users'}
    +
    + +
    {content}
    @@ -180,7 +244,7 @@ export default class Analytics extends React.Component { if (this.props.newlyCreatedUsers != null) { let content; if (this.props.newlyCreatedUsers.length === 0) { - content = 'Loading...'; + content = loading; } else { content = (
    @@ -218,7 +282,12 @@ export default class Analytics extends React.Component { newUsers = (
    -
    {'Newly Created Users'}
    +
    + +
    {content}
    @@ -229,7 +298,15 @@ export default class Analytics extends React.Component { return (
    -

    {'Statistics for ' + this.props.title}

    +

    + +

    {serverError}
    {totalCount} diff --git a/web/react/components/admin_console/gitlab_settings.jsx b/web/react/components/admin_console/gitlab_settings.jsx index 8c689a2d8..744fa3b19 100644 --- a/web/react/components/admin_console/gitlab_settings.jsx +++ b/web/react/components/admin_console/gitlab_settings.jsx @@ -4,7 +4,36 @@ import * as Client from '../../utils/client.jsx'; import * as AsyncClient from '../../utils/async_client.jsx'; -export default class GitLabSettings extends React.Component { +import {injectIntl, intlShape, defineMessages, FormattedMessage, FormattedHTMLMessage} from 'mm-intl'; + +const holders = defineMessages({ + clientIdExample: { + id: 'admin.gitlab.clientIdExample', + defaultMessage: 'Ex "jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY"' + }, + clientSecretExample: { + id: 'admin.gitlab.clientSecretExample', + defaultMessage: 'Ex "jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY"' + }, + authExample: { + id: 'admin.gitlab.authExample', + defaultMessage: 'Ex ""' + }, + tokenExample: { + id: 'admin.gitlab.tokenExample', + defaultMessage: 'Ex ""' + }, + userExample: { + id: 'admin.gitlab.userExample', + defaultMessage: 'Ex ""' + }, + saving: { + id: 'admin.gitlab.saving', + defaultMessage: 'Saving Config...' + } +}); + +class GitLabSettings extends React.Component { constructor(props) { super(props); @@ -65,6 +94,7 @@ export default class GitLabSettings extends React.Component { } render() { + const {formatMessage} = this.props.intl; var serverError = ''; if (this.state.serverError) { serverError =
    ; @@ -78,7 +108,12 @@ export default class GitLabSettings extends React.Component { return (
    -

    {'GitLab Settings'}

    +

    + +

    - {'Enable Sign Up With GitLab: '} +

    - {'When true, Mattermost allows team creation and account signup using GitLab OAuth.'}
    + +

    -
      -
    1. {'Log in to your GitLab account and go to Applications -> Profile Settings.'}
    2. -
    3. {'Enter Redirect URIs "/login/gitlab/complete" (example: http://localhost:8065/login/gitlab/complete) and "/signup/gitlab/complete". '}
    4. -
    5. {'Then use "Secret" and "Id" fields from GitLab to complete the options below.'}
    6. -
    7. {'Complete the Endpoint URLs below. '}
    8. -
    +
    @@ -132,7 +178,10 @@ export default class GitLabSettings extends React.Component { className='control-label col-sm-4' htmlFor='Id' > - {'Id:'} +
    -

    {'Obtain this value via the instructions above for logging into GitLab'}

    +

    + +

    @@ -154,7 +208,10 @@ export default class GitLabSettings extends React.Component { className='control-label col-sm-4' htmlFor='Secret' > - {'Secret:'} +
    -

    {'Obtain this value via the instructions above for logging into GitLab.'}

    +

    + +

    @@ -176,7 +238,10 @@ export default class GitLabSettings extends React.Component { className='control-label col-sm-4' htmlFor='AuthEndpoint' > - {'Auth Endpoint:'} +
    -

    {'Enter https:///oauth/authorize (example https://example.com:3000/oauth/authorize). Make sure you use HTTP or HTTPS in your URL depending on your server configuration.'}

    +

    + +

    @@ -198,7 +268,10 @@ export default class GitLabSettings extends React.Component { className='control-label col-sm-4' htmlFor='TokenEndpoint' > - {'Token Endpoint:'} +
    -

    {'Enter https:///oauth/token. Make sure you use HTTP or HTTPS in your URL depending on your server configuration.'}

    +

    + +

    @@ -220,7 +298,10 @@ export default class GitLabSettings extends React.Component { className='control-label col-sm-4' htmlFor='UserApiEndpoint' > - {'User API Endpoint:'} +
    -

    {'Enter https:///api/v3/user. Make sure you use HTTP or HTTPS in your URL depending on your server configuration.'}

    +

    + +

    @@ -246,9 +332,12 @@ export default class GitLabSettings extends React.Component { className={saveClass} onClick={this.handleSubmit} id='save-button' - data-loading-text={' Saving Config...'} + data-loading-text={' ' + formatMessage(holders.saving)} > - {'Save'} + @@ -283,5 +372,8 @@ export default class GitLabSettings extends React.Component { // GitLabSettings.propTypes = { + intl: intlShape.isRequired, config: React.PropTypes.object }; + +export default injectIntl(GitLabSettings); \ No newline at end of file diff --git a/web/react/components/admin_console/image_settings.jsx b/web/react/components/admin_console/image_settings.jsx index e1ffad7d3..12bf554ea 100644 --- a/web/react/components/admin_console/image_settings.jsx +++ b/web/react/components/admin_console/image_settings.jsx @@ -5,7 +5,76 @@ import * as Client from '../../utils/client.jsx'; import * as AsyncClient from '../../utils/async_client.jsx'; import crypto from 'crypto'; -export default class FileSettings extends React.Component { +import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'mm-intl'; + +const holders = defineMessages({ + storeDisabled: { + id: 'admin.image.storeDisabled', + defaultMessage: 'Disable File Storage' + }, + storeLocal: { + id: 'admin.image.storeLocal', + defaultMessage: 'Local File System' + }, + storeAmazonS3: { + id: 'admin.image.storeAmazonS3', + defaultMessage: 'Amazon S3' + }, + localExample: { + id: 'admin.image.localExample', + defaultMessage: 'Ex "./data/"' + }, + amazonS3IdExample: { + id: 'admin.image.amazonS3IdExample', + defaultMessage: 'Ex "AKIADTOVBGERKLCBV"' + }, + amazonS3SecretExample: { + id: 'admin.image.amazonS3SecretExample', + defaultMessage: 'Ex "jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY"' + }, + amazonS3BucketExample: { + id: 'admin.image.amazonS3BucketExample', + defaultMessage: 'Ex "mattermost-media"' + }, + amazonS3RegionExample: { + id: 'admin.image.amazonS3RegionExample', + defaultMessage: 'Ex "us-east-1"' + }, + thumbWidthExample: { + id: 'admin.image.thumbWidthExample', + defaultMessage: 'Ex "120"' + }, + thumbHeightExample: { + id: 'admin.image.thumbHeightExample', + defaultMessage: 'Ex "100"' + }, + previewWidthExample: { + id: 'admin.image.previewWidthExample', + defaultMessage: 'Ex "1024"' + }, + previewHeightExample: { + id: 'admin.image.previewHeightExample', + defaultMessage: 'Ex "0"' + }, + profileWidthExample: { + id: 'admin.image.profileWidthExample', + defaultMessage: 'Ex "1024"' + }, + profileHeightExample: { + id: 'admin.image.profileHeightExample', + defaultMessage: 'Ex "0"' + }, + publicLinkExample: { + id: 'admin.image.publicLinkExample', + defaultMessage: 'Ex "gxHVDcKUyP2y1eiyW8S8na1UYQAfq6J6"' + }, + saving: { + id: 'admin.image.saving', + defaultMessage: 'Saving Config...' + } +}); + +class FileSettings extends React.Component { constructor(props) { super(props); @@ -120,6 +189,7 @@ export default class FileSettings extends React.Component { } render() { + const {formatMessage} = this.props.intl; var serverError = ''; if (this.state.serverError) { serverError =
    ; @@ -143,7 +213,12 @@ export default class FileSettings extends React.Component { return (
    -

    {'File Settings'}

    +

    + +

    - {'Store Files In:'} +
    @@ -176,7 +254,10 @@ export default class FileSettings extends React.Component { className='control-label col-sm-4' htmlFor='Directory' > - {'Local Directory Location:'} +
    -

    {'Directory to which image files are written. If blank, will be set to ./data/.'}

    +

    + +

    @@ -198,7 +284,10 @@ export default class FileSettings extends React.Component { className='control-label col-sm-4' htmlFor='AmazonS3AccessKeyId' > - {'Amazon S3 Access Key Id:'} +
    -

    {'Obtain this credential from your Amazon EC2 administrator.'}

    +

    + +

    @@ -220,7 +314,10 @@ export default class FileSettings extends React.Component { className='control-label col-sm-4' htmlFor='AmazonS3SecretAccessKey' > - {'Amazon S3 Secret Access Key:'} +
    -

    {'Obtain this credential from your Amazon EC2 administrator.'}

    +

    + +

    @@ -242,7 +344,10 @@ export default class FileSettings extends React.Component { className='control-label col-sm-4' htmlFor='AmazonS3Bucket' > - {'Amazon S3 Bucket:'} +
    -

    {'Name you selected for your S3 bucket in AWS.'}

    +

    + +

    @@ -264,7 +374,10 @@ export default class FileSettings extends React.Component { className='control-label col-sm-4' htmlFor='AmazonS3Region' > - {'Amazon S3 Region:'} +
    -

    {'AWS region you selected for creating your S3 bucket.'}

    +

    + +

    @@ -286,7 +404,10 @@ export default class FileSettings extends React.Component { className='control-label col-sm-4' htmlFor='ThumbnailWidth' > - {'Thumbnail Width:'} +
    -

    {'Width of thumbnails generated from uploaded images. Updating this value changes how thumbnail images render in future, but does not change images created in the past.'}

    +

    + +

    @@ -307,7 +433,10 @@ export default class FileSettings extends React.Component { className='control-label col-sm-4' htmlFor='ThumbnailHeight' > - {'Thumbnail Height:'} +
    -

    {'Height of thumbnails generated from uploaded images. Updating this value changes how thumbnail images render in future, but does not change images created in the past.'}

    +

    + +

    @@ -328,7 +462,10 @@ export default class FileSettings extends React.Component { className='control-label col-sm-4' htmlFor='PreviewWidth' > - {'Preview Width:'} +
    -

    {'Maximum width of preview image. Updating this value changes how preview images render in future, but does not change images created in the past.'}

    +

    + +

    @@ -349,7 +491,10 @@ export default class FileSettings extends React.Component { className='control-label col-sm-4' htmlFor='PreviewHeight' > - {'Preview Height:'} +
    -

    {'Maximum height of preview image ("0": Sets to auto-size). Updating this value changes how preview images render in future, but does not change images created in the past.'}

    +

    + +

    @@ -370,7 +520,10 @@ export default class FileSettings extends React.Component { className='control-label col-sm-4' htmlFor='ProfileWidth' > - {'Profile Width:'} +
    -

    {'Width of profile picture.'}

    +

    + +

    @@ -391,7 +549,10 @@ export default class FileSettings extends React.Component { className='control-label col-sm-4' htmlFor='ProfileHeight' > - {'Profile Height:'} +
    -

    {'Height of profile picture.'}

    +

    + +

    @@ -412,7 +578,10 @@ export default class FileSettings extends React.Component { className='control-label col-sm-4' htmlFor='EnablePublicLink' > - {'Share Public File Link: '} +
    -

    {'Allow users to share public links to files and images.'}

    +

    + +

    @@ -445,7 +625,10 @@ export default class FileSettings extends React.Component { className='control-label col-sm-4' htmlFor='PublicLinkSalt' > - {'Public Link Salt:'} +
    -

    {'32-character salt added to signing of public image links. Randomly generated on install. Click "Re-Generate" to create new salt.'}

    +

    + +

    @@ -478,9 +669,12 @@ export default class FileSettings extends React.Component { className={saveClass} onClick={this.handleSubmit} id='save-button' - data-loading-text={' Saving Config...'} + data-loading-text={' ' + formatMessage(holders.saving)} > - {'Save'} + @@ -492,5 +686,8 @@ export default class FileSettings extends React.Component { } FileSettings.propTypes = { + intl: intlShape.isRequired, config: React.PropTypes.object }; + +export default injectIntl(FileSettings); \ No newline at end of file diff --git a/web/react/components/admin_console/ldap_settings.jsx b/web/react/components/admin_console/ldap_settings.jsx index 1447f3bd7..bc13b3bcd 100644 --- a/web/react/components/admin_console/ldap_settings.jsx +++ b/web/react/components/admin_console/ldap_settings.jsx @@ -4,10 +4,55 @@ import * as Client from '../../utils/client.jsx'; import * as AsyncClient from '../../utils/async_client.jsx'; +import {injectIntl, intlShape, defineMessages, FormattedMessage, FormattedHTMLMessage} from 'mm-intl'; + const DEFAULT_LDAP_PORT = 389; const DEFAULT_QUERY_TIMEOUT = 60; -export default class LdapSettings extends React.Component { +var holders = defineMessages({ + serverEx: { + id: 'admin.ldap.serverEx', + defaultMessage: 'Ex "10.0.0.23"' + }, + portEx: { + id: 'admin.ldap.portEx', + defaultMessage: 'Ex "389"' + }, + baseEx: { + id: 'admin.ldap.baseEx', + defaultMessage: 'Ex "dc=mydomain,dc=com"' + }, + firstnameAttrEx: { + id: 'admin.ldap.firstnameAttrEx', + defaultMessage: 'Ex "givenName"' + }, + lastnameAttrEx: { + id: 'admin.ldap.lastnameAttrEx', + defaultMessage: 'Ex "sn"' + }, + emailAttrEx: { + id: 'admin.ldap.emailAttrEx', + defaultMessage: 'Ex "mail"' + }, + usernameAttrEx: { + id: 'admin.ldap.usernameAttrEx', + defaultMessage: 'Ex "sAMAccountName"' + }, + idAttrEx: { + id: 'admin.ldap.idAttrEx', + defaultMessage: 'Ex "sAMAccountName"' + }, + queryEx: { + id: 'admin.ldap.queryEx', + defaultMessage: 'Ex "60"' + }, + saving: { + id: 'admin.ldap.saving', + defaultMessage: 'Saving Config...' + } +}); + +class LdapSettings extends React.Component { constructor(props) { super(props); @@ -80,6 +125,7 @@ export default class LdapSettings extends React.Component { ); } render() { + const {formatMessage} = this.props.intl; let serverError = ''; if (this.state.serverError) { serverError =
    ; @@ -97,8 +143,18 @@ export default class LdapSettings extends React.Component { bannerContent = (
    -

    {'Note:'}

    -

    {'If a user attribute changes on the LDAP server it will be updated the next time the user enters their credentials to log in to Mattermost. This includes if a user is made inactive or removed from an LDAP server. Synchronization with LDAP servers is planned in a future release.'}

    +

    + +

    +

    + +

    ); @@ -106,17 +162,10 @@ export default class LdapSettings extends React.Component { bannerContent = (
    -

    {'Note:'}

    -

    - {'LDAP is an enterprise feature. Your current license does not support LDAP. Click '} - - {'here'} - - {' for information and pricing on enterprise licenses.'} -

    +
    ); @@ -125,7 +174,12 @@ export default class LdapSettings extends React.Component { return (
    {bannerContent} -

    {'LDAP Settings'}

    +

    + +

    - {'Enable Login With LDAP:'} +
    -

    {'When true, Mattermost allows login using LDAP'}

    +

    + +

    @@ -168,7 +236,10 @@ export default class LdapSettings extends React.Component { className='control-label col-sm-4' htmlFor='LdapServer' > - {'LDAP Server:'} +
    -

    {'The domain or IP address of LDAP server.'}

    +

    + +

    @@ -189,7 +265,10 @@ export default class LdapSettings extends React.Component { className='control-label col-sm-4' htmlFor='LdapPort' > - {'LDAP Port:'} +
    -

    {'The port Mattermost will use to connect to the LDAP server. Default is 389.'}

    +

    + +

    @@ -210,7 +294,10 @@ export default class LdapSettings extends React.Component { className='control-label col-sm-4' htmlFor='BaseDN' > - {'BaseDN:'} +
    -

    {'The Base DN is the Distinguished Name of the location where Mattermost should start its search for users in the LDAP tree.'}

    +

    + +

    @@ -231,7 +323,10 @@ export default class LdapSettings extends React.Component { className='control-label col-sm-4' htmlFor='BindUsername' > - {'Bind Username:'} +
    -

    {'The username used to perform the LDAP search. This should typically be an account created specifically for use with Mattermost. It should have access limited to read the portion of the LDAP tree specified in the BaseDN field.'}

    +

    + +

    @@ -252,7 +352,10 @@ export default class LdapSettings extends React.Component { className='control-label col-sm-4' htmlFor='BindPassword' > - {'Bind Password:'} +
    -

    {'Password of the user given in "Bind Username".'}

    +

    + +

    @@ -273,7 +381,10 @@ export default class LdapSettings extends React.Component { className='control-label col-sm-4' htmlFor='FirstNameAttribute' > - {'First Name Attrubute'} +
    -

    {'The attribute in the LDAP server that will be used to populate the first name of users in Mattermost.'}

    +

    + +

    @@ -294,7 +410,10 @@ export default class LdapSettings extends React.Component { className='control-label col-sm-4' htmlFor='LastNameAttribute' > - {'Last Name Attribute:'} +
    -

    {'The attribute in the LDAP server that will be used to populate the last name of users in Mattermost.'}

    +

    + +

    @@ -315,7 +439,10 @@ export default class LdapSettings extends React.Component { className='control-label col-sm-4' htmlFor='EmailAttribute' > - {'Email Attribute:'} +
    -

    {'The attribute in the LDAP server that will be used to populate the email addresses of users in Mattermost.'}

    +

    + +

    @@ -336,7 +468,10 @@ export default class LdapSettings extends React.Component { className='control-label col-sm-4' htmlFor='UsernameAttribute' > - {'Username Attribute:'} +
    -

    {'The attribute in the LDAP server that will be used to populate the username field in Mattermost. This may be the same as the ID Attribute.'}

    +

    + +

    @@ -357,7 +497,10 @@ export default class LdapSettings extends React.Component { className='control-label col-sm-4' htmlFor='IdAttribute' > - {'Id Attribute: '} +
    -

    {'The attribute in the LDAP server that will be used as a unique identifier in Mattermost. It should be an LDAP attribute with a value that does not change, such as username or uid. If a user’s Id Attribute changes, it will create a new Mattermost account unassociated with their old one. This is the value used to log in to Mattermost in the "LDAP Username" field on the sign in page. Normally this attribute is the same as the “Username Attribute” field above. If your team typically uses domain\\username to sign in to other services with LDAP, you may choose to put domain\\username in this field to maintain consistency between sites.'}

    +

    + +

    @@ -378,7 +526,10 @@ export default class LdapSettings extends React.Component { className='control-label col-sm-4' htmlFor='QueryTimeout' > - {'Query Timeout (seconds):'} +
    -

    {'The timeout value for queries to the LDAP server. Increase if you are getting timeout errors caused by a slow LDAP server.'}

    +

    + +

    @@ -403,9 +559,12 @@ export default class LdapSettings extends React.Component { className={saveClass} onClick={this.handleSubmit} id='save-button' - data-loading-text={' Saving Config...'} + data-loading-text={' ' + formatMessage(holders.saving)} > - {'Save'} +
    @@ -418,5 +577,8 @@ LdapSettings.defaultProps = { }; LdapSettings.propTypes = { + intl: intlShape.isRequired, config: React.PropTypes.object }; + +export default injectIntl(LdapSettings); \ No newline at end of file diff --git a/web/react/components/admin_console/legal_and_support_settings.jsx b/web/react/components/admin_console/legal_and_support_settings.jsx index b00e4b6bd..a6c6a0626 100644 --- a/web/react/components/admin_console/legal_and_support_settings.jsx +++ b/web/react/components/admin_console/legal_and_support_settings.jsx @@ -4,7 +4,16 @@ import * as Client from '../../utils/client.jsx'; import * as AsyncClient from '../../utils/async_client.jsx'; -export default class LegalAndSupportSettings extends React.Component { +import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'mm-intl'; + +var holders = defineMessages({ + saving: { + id: 'admin.support.saving', + defaultMessage: 'Saving Config...' + } +}); + +class LegalAndSupportSettings extends React.Component { constructor(props) { super(props); @@ -69,7 +78,12 @@ export default class LegalAndSupportSettings extends React.Component { return (
    -

    {'Legal and Support Settings'}

    +

    + +

    - {'Terms of Service link:'} +
    -

    {'Link to Terms of Service available to users on desktop and on mobile. Leaving this blank will hide the option to display a notice.'}

    +

    + +

    @@ -100,7 +122,10 @@ export default class LegalAndSupportSettings extends React.Component { className='control-label col-sm-4' htmlFor='PrivacyPolicyLink' > - {'Privacy Policy link:'} +
    -

    {'Link to Privacy Policy available to users on desktop and on mobile. Leaving this blank will hide the option to display a notice.'}

    +

    + +

    @@ -120,7 +150,10 @@ export default class LegalAndSupportSettings extends React.Component { className='control-label col-sm-4' htmlFor='AboutLink' > - {'About link:'} +
    -

    {'Link to About page for more information on your Mattermost deployment, for example its purpose and audience within your organization. Defaults to Mattermost information page.'}

    +

    + +

    @@ -140,7 +178,10 @@ export default class LegalAndSupportSettings extends React.Component { className='control-label col-sm-4' htmlFor='HelpLink' > - {'Help link:'} +
    -

    {'Link to help documentation from team site main menu. Typically not changed unless your organization chooses to create custom documentation.'}

    +

    + +

    @@ -160,7 +206,10 @@ export default class LegalAndSupportSettings extends React.Component { className='control-label col-sm-4' htmlFor='ReportAProblemLink' > - {'Report a Problem link:'} +
    -

    {'Link to help documentation from team site main menu. By default this points to the peer-to-peer troubleshooting forum where users can search for, find and request help with technical issues.'}

    +

    + +

    @@ -180,7 +234,10 @@ export default class LegalAndSupportSettings extends React.Component { className='control-label col-sm-4' htmlFor='SupportEmail' > - {'Support email:'} +
    -

    {'Email shown during tutorial for end users to ask support questions.'}

    +

    + +

    @@ -204,9 +266,12 @@ export default class LegalAndSupportSettings extends React.Component { className={saveClass} onClick={this.handleSubmit} id='save-button' - data-loading-text={' Saving Config...'} + data-loading-text={' ' + this.props.intl.formatMessage(holders.saving)} > - {'Save'} + @@ -218,5 +283,8 @@ export default class LegalAndSupportSettings extends React.Component { } LegalAndSupportSettings.propTypes = { + intl: intlShape.isRequired, config: React.PropTypes.object }; + +export default injectIntl(LegalAndSupportSettings); \ No newline at end of file diff --git a/web/react/components/admin_console/license_settings.jsx b/web/react/components/admin_console/license_settings.jsx index ba953f3bd..539acd869 100644 --- a/web/react/components/admin_console/license_settings.jsx +++ b/web/react/components/admin_console/license_settings.jsx @@ -4,7 +4,20 @@ import * as Utils from '../../utils/utils.jsx'; import * as Client from '../../utils/client.jsx'; -export default class LicenseSettings extends React.Component { +import {injectIntl, intlShape, defineMessages, FormattedMessage, FormattedHTMLMessage} from 'mm-intl'; + +const holders = defineMessages({ + removing: { + id: 'admin.license.removing', + defaultMessage: 'Removing License...' + }, + uploading: { + id: 'admin.license.uploading', + defaultMessage: 'Uploading License...' + } +}); + +class LicenseSettings extends React.Component { constructor(props) { super(props); @@ -88,41 +101,26 @@ export default class LicenseSettings extends React.Component { let licenseKey; if (global.window.mm_license.IsLicensed === 'true') { - edition = 'Mattermost Enterprise Edition. Designed for enterprise-scale communication.'; + edition = ( + + ); licenseType = ( -
    -

    - {'This compiled release of Mattermost platform is provided under a '} - - {'commercial license'} - - {' from Mattermost, Inc. based on your subscription level and is subject to the '} - - {'Terms of Service.'} - -

    -

    {'Your subscription details are as follows:'}

    - {'Name: ' + global.window.mm_license.Name} -
    - {'Company or organization name: ' + global.window.mm_license.Company} -
    - {'Number of users: ' + global.window.mm_license.Users} -
    - {`License issued: ${Utils.displayDate(parseInt(global.window.mm_license.IssuedAt, 10))} ${Utils.displayTime(parseInt(global.window.mm_license.IssuedAt, 10), true)}`} -
    - {'Start date of license: ' + Utils.displayDate(parseInt(global.window.mm_license.StartsAt, 10))} -
    - {'Expiry date of license: ' + Utils.displayDate(parseInt(global.window.mm_license.ExpiresAt, 10))} -
    - {'LDAP: ' + global.window.mm_license.LDAP} -
    -
    + ); licenseKey = ( @@ -131,32 +129,39 @@ export default class LicenseSettings extends React.Component { className='btn btn-danger' onClick={this.handleRemove} id='remove-button' - data-loading-text={' Removing License...'} + data-loading-text={' ' + this.props.intl.formatMessage(holders.removing)} > - {'Remove Enterprise License and Downgrade Server'} +

    - {'If you’re migrating servers you may need to remove your license key from this server in order to install it on a new server. To start, '} - - {'disable all Enterprise Edition features on this server'} - - {'. This will enable the ability to remove the license key and downgrade this server from Enterprise Edition to Team Edition.'} +

    ); } else { - edition = 'Mattermost Team Edition. Designed for teams from 5 to 50 users.'; + edition = ( + + ); licenseType = ( - -

    {'This compiled release of Mattermost platform is offered under an MIT license.'}

    -

    {'See MIT-COMPILED-LICENSE.txt in your root install directory for details. See NOTICES.txt for information about open source software used in this system.'}

    -
    + ); licenseKey = ( @@ -173,23 +178,23 @@ export default class LicenseSettings extends React.Component { disabled={!this.state.fileSelected} onClick={this.handleSubmit} id='upload-button' - data-loading-text={' Uploading License...'} + data-loading-text={' ' + this.props.intl.formatMessage(holders.uploading)} > - {'Upload'} +


    {serverError}

    - {'Upload a license key for Mattermost Enterprise Edition to upgrade this server. '} - - {'Visit us online'} - - {' to learn more about the benefits of Enterprise Edition or to purchase a key.'} +

    ); @@ -197,7 +202,12 @@ export default class LicenseSettings extends React.Component { return (
    -

    {'Edition and License'}

    +

    + +

    - {'Edition: '} +
    {edition} @@ -216,7 +229,10 @@ export default class LicenseSettings extends React.Component {
    {licenseType} @@ -226,7 +242,10 @@ export default class LicenseSettings extends React.Component { {licenseKey}
    @@ -235,3 +254,9 @@ export default class LicenseSettings extends React.Component { ); } } + +LicenseSettings.propTypes = { + intl: intlShape.isRequired +}; + +export default injectIntl(LicenseSettings); \ No newline at end of file diff --git a/web/react/components/admin_console/log_settings.jsx b/web/react/components/admin_console/log_settings.jsx index a91cc57ab..cefe6afba 100644 --- a/web/react/components/admin_console/log_settings.jsx +++ b/web/react/components/admin_console/log_settings.jsx @@ -4,7 +4,24 @@ import * as Client from '../../utils/client.jsx'; import * as AsyncClient from '../../utils/async_client.jsx'; -export default class LogSettings extends React.Component { +import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'mm-intl'; + +const holders = defineMessages({ + locationPlaceholder: { + id: 'admin.log.locationPlaceholder', + defaultMessage: 'Enter your file location' + }, + formatPlaceholder: { + id: 'admin.log.formatPlaceholder', + defaultMessage: 'Enter your file format' + }, + saving: { + id: 'admin.log.saving', + defaultMessage: 'Saving Config...' + } +}); + +class LogSettings extends React.Component { constructor(props) { super(props); @@ -78,6 +95,7 @@ export default class LogSettings extends React.Component { } render() { + const {formatMessage} = this.props.intl; var serverError = ''; if (this.state.serverError) { serverError =
    ; @@ -90,7 +108,12 @@ export default class LogSettings extends React.Component { return (
    -

    {'Log Settings'}

    +

    + +

    - {'Log To The Console: '} +
    -

    {'Typically set to false in production. Developers may set this field to true to output log messages to console based on the console level option. If true, server writes messages to the standard output stream (stdout).'}

    +

    + +

    @@ -134,7 +171,10 @@ export default class LogSettings extends React.Component { className='control-label col-sm-4' htmlFor='consoleLevel' > - {'Console Log Level:'} +
    -

    {'This setting determines the level of detail at which log events are written to the console. ERROR: Outputs only error messages. INFO: Outputs error messages and information around startup and initialization. DEBUG: Prints high detail for developers working on debugging issues.'}

    +

    + +

    @@ -157,7 +202,10 @@ export default class LogSettings extends React.Component {
    -

    {'Typically set to true in production. When true, log files are written to the log file specified in file location field below.'}

    +

    + +

    @@ -190,7 +249,10 @@ export default class LogSettings extends React.Component { className='control-label col-sm-4' htmlFor='fileLevel' > - {'File Log Level:'} +
    -

    {'This setting determines the level of detail at which log events are written to the log file. ERROR: Outputs only error messages. INFO: Outputs error messages and information around startup and initialization. DEBUG: Prints high detail for developers working on debugging issues.'}

    +

    + +

    @@ -214,7 +281,10 @@ export default class LogSettings extends React.Component { className='control-label col-sm-4' htmlFor='fileLocation' > - {'File Location:'} +
    -

    {'File to which log files are written. If blank, will be set to ./logs/mattermost, which writes logs to mattermost.log. Log rotation is enabled and every 10,000 lines of log information is written to new files stored in the same directory, for example mattermost.2015-09-23.001, mattermost.2015-09-23.002, and so forth.'}

    +

    + +

    @@ -236,7 +311,10 @@ export default class LogSettings extends React.Component { className='control-label col-sm-4' htmlFor='fileFormat' > - {'File Format:'} +
    - {'Format of log message output. If blank will be set to "[%D %T] [%L] %M", where:'} +
    - - - - - - + + + + + +
    {'%T'}{'Time (15:04:05 MST)'}
    {'%D'}{'Date (2006/01/02)'}
    {'%d'}{'Date (01/02/06)'}
    {'%L'}{'Level (DEBG, INFO, EROR)'}
    {'%S'}{'Source'}
    {'%M'}{'Message'}
    {'%T'} + +
    {'%D'} + +
    {'%d'} + +
    {'%L'} + +
    {'%S'} + +
    {'%M'} + +
    @@ -279,9 +390,12 @@ export default class LogSettings extends React.Component { className={saveClass} onClick={this.handleSubmit} id='save-button' - data-loading-text={' Saving Config...'} + data-loading-text={' ' + formatMessage(holders.saving)} > - {'Save'} +
    @@ -293,5 +407,8 @@ export default class LogSettings extends React.Component { } LogSettings.propTypes = { + intl: intlShape.isRequired, config: React.PropTypes.object }; + +export default injectIntl(LogSettings); \ No newline at end of file diff --git a/web/react/components/admin_console/logs.jsx b/web/react/components/admin_console/logs.jsx index 01135f1b8..71a4a5d8c 100644 --- a/web/react/components/admin_console/logs.jsx +++ b/web/react/components/admin_console/logs.jsx @@ -5,6 +5,8 @@ import AdminStore from '../../stores/admin_store.jsx'; import LoadingScreen from '../loading_screen.jsx'; import * as AsyncClient from '../../utils/async_client.jsx'; +import {FormattedMessage} from 'mm-intl'; + export default class Logs extends React.Component { constructor(props) { super(props); @@ -73,13 +75,21 @@ export default class Logs extends React.Component { return (
    -

    {'Server Logs'}

    +

    + +

    {content} diff --git a/web/react/components/admin_console/privacy_settings.jsx b/web/react/components/admin_console/privacy_settings.jsx index 78747d9f2..1ab625049 100644 --- a/web/react/components/admin_console/privacy_settings.jsx +++ b/web/react/components/admin_console/privacy_settings.jsx @@ -4,7 +4,16 @@ import * as Client from '../../utils/client.jsx'; import * as AsyncClient from '../../utils/async_client.jsx'; -export default class PrivacySettings extends React.Component { +import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'mm-intl'; + +const holders = defineMessages({ + saving: { + id: 'admin.privacy.saving', + defaultMessage: 'Saving Config...' + } +}); + +class PrivacySettings extends React.Component { constructor(props) { super(props); @@ -64,7 +73,12 @@ export default class PrivacySettings extends React.Component { return (
    -

    {'Privacy Settings'}

    +

    + +

    - {'Show Email Address: '} +
    -

    {'When false, hides email address of users from other users in the user interface, including team owners and team administrators. Used when system is set up for managing teams where some users choose to keep their contact information private.'}

    +

    + +

    @@ -108,7 +136,10 @@ export default class PrivacySettings extends React.Component { className='control-label col-sm-4' htmlFor='ShowFullName' > - {'Show Full Name: '} +
    -

    {'When false, hides full name of users from other users, including team owners and team administrators. Username is shown in place of full name.'}

    +

    + +

    @@ -145,9 +187,12 @@ export default class PrivacySettings extends React.Component { className={saveClass} onClick={this.handleSubmit} id='save-button' - data-loading-text={' Saving Config...'} + data-loading-text={' ' + this.props.intl.formatMessage(holders.saving)} > - {'Save'} +
    @@ -159,5 +204,8 @@ export default class PrivacySettings extends React.Component { } PrivacySettings.propTypes = { + intl: intlShape.isRequired, config: React.PropTypes.object }; + +export default injectIntl(PrivacySettings); \ No newline at end of file diff --git a/web/react/components/admin_console/rate_settings.jsx b/web/react/components/admin_console/rate_settings.jsx index aabb24326..d3c1bffa2 100644 --- a/web/react/components/admin_console/rate_settings.jsx +++ b/web/react/components/admin_console/rate_settings.jsx @@ -4,7 +4,28 @@ import * as Client from '../../utils/client.jsx'; import * as AsyncClient from '../../utils/async_client.jsx'; -export default class RateSettings extends React.Component { +import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'mm-intl'; + +const holders = defineMessages({ + queriesExample: { + id: 'admin.rate.queriesExample', + defaultMessage: 'Ex "10"' + }, + memoryExample: { + id: 'admin.rate.memoryExample', + defaultMessage: 'Ex "10000"' + }, + httpHeaderExample: { + id: 'admin.rate.httpHeaderExample', + defaultMessage: 'Ex "X-Real-IP", "X-Forwarded-For"' + }, + saving: { + id: 'admin.rate.saving', + defaultMessage: 'Saving Config...' + } +}); + +class RateSettings extends React.Component { constructor(props) { super(props); @@ -85,6 +106,7 @@ export default class RateSettings extends React.Component { } render() { + const {formatMessage} = this.props.intl; var serverError = ''; if (this.state.serverError) { serverError =
    ; @@ -100,12 +122,27 @@ export default class RateSettings extends React.Component {
    -

    {'Note:'}

    -

    {'Changing properties in this section will require a server restart before taking effect.'}

    +

    + +

    +

    + +

    -

    {'Rate Limit Settings'}

    +

    + +

    - {'Enable Rate Limiter: '} +
    -

    {'When true, APIs are throttled at rates specified below.'}

    +

    + +

    @@ -149,7 +200,10 @@ export default class RateSettings extends React.Component { className='control-label col-sm-4' htmlFor='PerSec' > - {'Number Of Queries Per Second:'} +
    -

    {'Throttles API at this number of requests per second.'}

    +

    + +

    @@ -171,7 +230,10 @@ export default class RateSettings extends React.Component { className='control-label col-sm-4' htmlFor='MemoryStoreSize' > - {'Memory Store Size:'} +
    -

    {'Maximum number of users sessions connected to the system as determined by "Vary By Remote Address" and "Vary By Header" settings below.'}

    +

    + +

    @@ -193,7 +260,10 @@ export default class RateSettings extends React.Component { className='control-label col-sm-4' htmlFor='VaryByRemoteAddr' > - {'Vary By Remote Address: '} +
    -

    {'When true, rate limit API access by IP address.'}

    +

    + +

    @@ -228,7 +309,10 @@ export default class RateSettings extends React.Component { className='control-label col-sm-4' htmlFor='VaryByHeader' > - {'Vary By HTTP Header:'} +
    -

    {'When filled in, vary rate limiting by HTTP header field specified (e.g. when configuring NGINX set to "X-Real-IP", when configuring AmazonELB set to "X-Forwarded-For").'}

    +

    + +

    @@ -254,9 +343,12 @@ export default class RateSettings extends React.Component { className={saveClass} onClick={this.handleSubmit} id='save-button' - data-loading-text={' Saving Config...'} + data-loading-text={' ' + formatMessage(holders.saving)} > - {'Save'} + @@ -268,5 +360,8 @@ export default class RateSettings extends React.Component { } RateSettings.propTypes = { + intl: intlShape.isRequired, config: React.PropTypes.object }; + +export default injectIntl(RateSettings); \ No newline at end of file diff --git a/web/react/components/admin_console/reset_password_modal.jsx b/web/react/components/admin_console/reset_password_modal.jsx index bf7d5f7e5..8ed519ffb 100644 --- a/web/react/components/admin_console/reset_password_modal.jsx +++ b/web/react/components/admin_console/reset_password_modal.jsx @@ -5,7 +5,16 @@ import * as Client from '../../utils/client.jsx'; import Constants from '../../utils/constants.jsx'; var Modal = ReactBootstrap.Modal; -export default class ResetPasswordModal extends React.Component { +import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'mm-intl'; + +var holders = defineMessages({ + submit: { + id: 'admin.reset_password.submit', + defaultMessage: 'Please enter at least {chars} characters.' + } +}); + +class ResetPasswordModal extends React.Component { constructor(props) { super(props); @@ -22,7 +31,7 @@ export default class ResetPasswordModal extends React.Component { var password = ReactDOM.findDOMNode(this.refs.password).value; if (!password || password.length < Constants.MIN_PASSWORD_LENGTH) { - this.setState({serverError: 'Please enter at least ' + Constants.MIN_PASSWORD_LENGTH + ' characters.'}); + this.setState({serverError: this.props.intl.formatMessage(holders.submit, {chars: Constants.MIN_PASSWORD_LENGTH})}); return; } @@ -67,7 +76,12 @@ export default class ResetPasswordModal extends React.Component { onHide={this.doCancel} > - {'Reset Password'} + + + - {'New Password'} + - {'Close'} + @@ -125,9 +148,12 @@ ResetPasswordModal.defaultProps = { }; ResetPasswordModal.propTypes = { + intl: intlShape.isRequired, user: React.PropTypes.object, team: React.PropTypes.object, show: React.PropTypes.bool.isRequired, onModalSubmit: React.PropTypes.func, onModalDismissed: React.PropTypes.func }; + +export default injectIntl(ResetPasswordModal); \ No newline at end of file diff --git a/web/react/components/admin_console/service_settings.jsx b/web/react/components/admin_console/service_settings.jsx index f10721ffa..7021900eb 100644 --- a/web/react/components/admin_console/service_settings.jsx +++ b/web/react/components/admin_console/service_settings.jsx @@ -4,11 +4,40 @@ import * as Client from '../../utils/client.jsx'; import * as AsyncClient from '../../utils/async_client.jsx'; +import {injectIntl, intlShape, defineMessages, FormattedMessage, FormattedHTMLMessage} from 'mm-intl'; + const DefaultSessionLength = 30; const DefaultMaximumLoginAttempts = 10; const DefaultSessionCacheInMinutes = 10; -export default class ServiceSettings extends React.Component { +var holders = defineMessages({ + listenExample: { + id: 'admin.service.listenExample', + defaultMessage: 'Ex ":8065"' + }, + attemptExample: { + id: 'admin.service.attemptExample', + defaultMessage: 'Ex "10"' + }, + segmentExample: { + id: 'admin.service.segmentExample', + defaultMessage: 'Ex "g3fgGOXJAQ43QV7rAh6iwQCkV4cA1Gs"' + }, + googleExample: { + id: 'admin.service.googleExample', + defaultMessage: 'Ex "7rAh6iwQCkV4cA1Gsg3fgGOXJAQ43QV"' + }, + sessionDaysEx: { + id: 'admin.service.sessionDaysEx', + defaultMessage: 'Ex "30"' + }, + saving: { + id: 'admin.service.saving', + defaultMessage: 'Saving Config...' + } +}); + +class ServiceSettings extends React.Component { constructor(props) { super(props); @@ -120,6 +149,7 @@ export default class ServiceSettings extends React.Component { } render() { + const {formatMessage} = this.props.intl; var serverError = ''; if (this.state.serverError) { serverError =
    ; @@ -133,7 +163,12 @@ export default class ServiceSettings extends React.Component { return (
    -

    {'Service Settings'}

    +

    + +

    - {'Listen Address:'} +
    -

    {'The address to which to bind and listen. Entering ":8065" will bind to all interfaces or you can choose one like "127.0.0.1:8065". Changing this will require a server restart before taking effect.'}

    +

    + +

    @@ -165,7 +208,10 @@ export default class ServiceSettings extends React.Component { className='control-label col-sm-4' htmlFor='MaximumLoginAttempts' > - {'Maximum Login Attempts:'} +
    -

    {'Login attempts allowed before user is locked out and required to reset password via email.'}

    +

    + +

    @@ -186,7 +237,10 @@ export default class ServiceSettings extends React.Component { className='control-label col-sm-4' htmlFor='SegmentDeveloperKey' > - {'Segment Developer Key:'} +
    -

    {'For users running a SaaS services, sign up for a key at Segment.com to track metrics.'}

    +

    + +

    @@ -207,7 +266,10 @@ export default class ServiceSettings extends React.Component { className='control-label col-sm-4' htmlFor='GoogleDeveloperKey' > - {'Google Developer Key:'} +

    - {'Set this key to enable embedding of YouTube video previews based on hyperlinks appearing in messages or comments. Instructions to obtain a key available at '} - - {'https://www.youtube.com/watch?v=Im69kzhpR3I'} - - {'. Leaving the field blank disables the automatic generation of YouTube video previews from links.'} +

    @@ -237,7 +297,10 @@ export default class ServiceSettings extends React.Component { className='control-label col-sm-4' htmlFor='EnableIncomingWebhooks' > - {'Enable Incoming Webhooks: '} +
    -

    {'When true, incoming webhooks will be allowed. To help combat phishing attacks, all posts from webhooks will be labelled by a BOT tag.'}

    +

    + +

    @@ -270,7 +344,10 @@ export default class ServiceSettings extends React.Component { className='control-label col-sm-4' htmlFor='EnableOutgoingWebhooks' > - {'Enable Outgoing Webhooks: '} +
    -

    {'When true, outgoing webhooks will be allowed.'}

    +

    + +

    @@ -303,7 +391,10 @@ export default class ServiceSettings extends React.Component { className='control-label col-sm-4' htmlFor='EnablePostUsernameOverride' > - {'Enable Overriding Usernames from Webhooks: '} +
    -

    {'When true, webhooks will be allowed to change the username they are posting as. Note, combined with allowing icon overriding, this could open users up to phishing attacks.'}

    +

    + +

    @@ -336,7 +438,10 @@ export default class ServiceSettings extends React.Component { className='control-label col-sm-4' htmlFor='EnablePostIconOverride' > - {'Enable Overriding Icon from Webhooks: '} +
    -

    {'When true, webhooks will be allowed to change the icon they post with. Note, combined with allowing username overriding, this could open users up to phishing attacks.'}

    +

    + +

    @@ -369,7 +485,10 @@ export default class ServiceSettings extends React.Component { className='control-label col-sm-4' htmlFor='EnableTesting' > - {'Enable Testing: '} +
    -

    {'(Developer Option) When true, /loadtest slash command is enabled to load test accounts and test data. Changing this will require a server restart before taking effect.'}

    +

    + +

    @@ -402,7 +532,10 @@ export default class ServiceSettings extends React.Component { className='control-label col-sm-4' htmlFor='EnableDeveloper' > - {'Enable Developer Mode: '} +
    -

    {'(Developer Option) When true, extra information around errors will be displayed in the UI.'}

    +

    + +

    @@ -435,7 +579,10 @@ export default class ServiceSettings extends React.Component { className='control-label col-sm-4' htmlFor='EnableSecurityFixAlert' > - {'Enable Security Alerts: '} +
    -

    {'When true, System Administrators are notified by email if a relevant security fix alert has been announced in the last 12 hours. Requires email to be enabled.'}

    +

    + +

    @@ -468,7 +626,10 @@ export default class ServiceSettings extends React.Component { className='control-label col-sm-4' htmlFor='SessionLengthWebInDays' > - {'Session Length for Web in Days:'} +
    -

    {'The web session will expire after the number of days specified and will require a user to login again.'}

    +

    + +

    @@ -489,7 +655,10 @@ export default class ServiceSettings extends React.Component { className='control-label col-sm-4' htmlFor='SessionLengthMobileInDays' > - {'Session Length for Mobile Device in Days:'} +
    -

    {'The native mobile session will expire after the number of days specified and will require a user to login again.'}

    +

    + +

    @@ -510,7 +684,10 @@ export default class ServiceSettings extends React.Component { className='control-label col-sm-4' htmlFor='SessionLengthSSOInDays' > - {'Session Length for SSO in Days:'} +
    -

    {'The SSO session will expire after the number of days specified and will require a user to login again.'}

    +

    + +

    @@ -531,7 +713,10 @@ export default class ServiceSettings extends React.Component { className='control-label col-sm-4' htmlFor='SessionCacheInMinutes' > - {'Session Cache in Minutes:'} +
    -

    {'The number of minutes to cache a session in memory.'}

    +

    + +

    @@ -556,9 +746,12 @@ export default class ServiceSettings extends React.Component { className={saveClass} onClick={this.handleSubmit} id='save-button' - data-loading-text={' Saving Config...'} + data-loading-text={' ' + formatMessage(holders.saving)} > - {'Save'} + @@ -603,5 +796,8 @@ export default class ServiceSettings extends React.Component { // ServiceSettings.propTypes = { + intl: intlShape.isRequired, config: React.PropTypes.object }; + +export default injectIntl(ServiceSettings); \ No newline at end of file diff --git a/web/react/components/admin_console/sql_settings.jsx b/web/react/components/admin_console/sql_settings.jsx index 2a55f7324..69ae808f6 100644 --- a/web/react/components/admin_console/sql_settings.jsx +++ b/web/react/components/admin_console/sql_settings.jsx @@ -5,7 +5,32 @@ import * as Client from '../../utils/client.jsx'; import * as AsyncClient from '../../utils/async_client.jsx'; import crypto from 'crypto'; -export default class SqlSettings extends React.Component { +import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'mm-intl'; + +const holders = defineMessages({ + warning: { + id: 'admin.sql.warning', + defaultMessage: 'Warning: re-generating this salt may cause some columns in the database to return empty results.' + }, + maxConnectionsExample: { + id: 'admin.sql.maxConnectionsExample', + defaultMessage: 'Ex "10"' + }, + maxOpenExample: { + id: 'admin.sql.maxOpenExample', + defaultMessage: 'Ex "10"' + }, + keyExample: { + id: 'admin.sql.keyExample', + defaultMessage: 'Ex "gxHVDcKUyP2y1eiyW8S8na1UYQAfq6J6"' + }, + saving: { + id: 'admin.sql.saving', + defaultMessage: 'Saving Config...' + } +}); + +class SqlSettings extends React.Component { constructor(props) { super(props); @@ -74,7 +99,7 @@ export default class SqlSettings extends React.Component { handleGenerate(e) { e.preventDefault(); - var cfm = global.window.confirm('Warning: re-generating this salt may cause some columns in the database to return empty results.'); + var cfm = global.window.confirm(this.props.intl.formatMessage(holders.warning)); if (cfm === false) { return; } @@ -85,6 +110,7 @@ export default class SqlSettings extends React.Component { } render() { + const {formatMessage} = this.props.intl; var serverError = ''; if (this.state.serverError) { serverError =
    ; @@ -111,12 +137,27 @@ export default class SqlSettings extends React.Component {
    -

    {'Note:'}

    -

    {'Changing properties in this section will require a server restart before taking effect.'}

    +

    + +

    +

    + +

    -

    {'SQL Settings'}

    +

    + +

    - {'Driver Name:'} +

    {this.props.config.SqlSettings.DriverName}

    @@ -139,7 +183,10 @@ export default class SqlSettings extends React.Component { className='control-label col-sm-4' htmlFor='DataSource' > - {'Data Source:'} +

    {dataSource}

    @@ -151,7 +198,10 @@ export default class SqlSettings extends React.Component { className='control-label col-sm-4' htmlFor='DataSourceReplicas' > - {'Data Source Replicas:'} +

    {dataSourceReplicas}

    @@ -163,7 +213,10 @@ export default class SqlSettings extends React.Component { className='control-label col-sm-4' htmlFor='MaxIdleConns' > - {'Maximum Idle Connections:'} +
    -

    {'Maximum number of idle connections held open to the database.'}

    +

    + +

    @@ -184,7 +242,10 @@ export default class SqlSettings extends React.Component { className='control-label col-sm-4' htmlFor='MaxOpenConns' > - {'Maximum Open Connections:'} +
    -

    {'Maximum number of open connections held open to the database.'}

    +

    + +

    @@ -205,7 +271,10 @@ export default class SqlSettings extends React.Component { className='control-label col-sm-4' htmlFor='AtRestEncryptKey' > - {'At Rest Encrypt Key:'} +
    -

    {'32-character salt available to encrypt and decrypt sensitive fields in database.'}

    +

    + +

    @@ -234,7 +311,10 @@ export default class SqlSettings extends React.Component { className='control-label col-sm-4' htmlFor='Trace' > - {'Trace: '} +
    -

    {'(Development Mode) When true, executing SQL statements are written to the log.'}

    +

    + +

    @@ -271,9 +362,12 @@ export default class SqlSettings extends React.Component { className={saveClass} onClick={this.handleSubmit} id='save-button' - data-loading-text={' Saving Config...'} + data-loading-text={' ' + formatMessage(holders.saving)} > - {'Save'} + @@ -285,5 +379,8 @@ export default class SqlSettings extends React.Component { } SqlSettings.propTypes = { + intl: intlShape.isRequired, config: React.PropTypes.object }; + +export default injectIntl(SqlSettings); \ No newline at end of file diff --git a/web/react/components/admin_console/system_analytics.jsx b/web/react/components/admin_console/system_analytics.jsx index f54813a94..2dd833fb2 100644 --- a/web/react/components/admin_console/system_analytics.jsx +++ b/web/react/components/admin_console/system_analytics.jsx @@ -4,7 +4,24 @@ import Analytics from './analytics.jsx'; import * as Client from '../../utils/client.jsx'; -export default class SystemAnalytics extends React.Component { +import {injectIntl, intlShape, defineMessages} from 'mm-intl'; + +const labels = defineMessages({ + totalPosts: { + id: 'admin.system_analytics.totalPosts', + defaultMessage: 'Total Posts' + }, + activeUsers: { + id: 'admin.system_analytics.activeUsers', + defaultMessage: 'Active Users With Posts' + }, + title: { + id: 'admin.system_analytics.title', + defaultMessage: 'the System' + } +}); + +class SystemAnalytics extends React.Component { constructor(props) { super(props); @@ -29,6 +46,7 @@ export default class SystemAnalytics extends React.Component { } getData() { // should be moved to an action creator eventually + const {formatMessage} = this.props.intl; Client.getSystemAnalytics( 'standard', (data) => { @@ -63,7 +81,7 @@ export default class SystemAnalytics extends React.Component { var chartData = { labels: [], datasets: [{ - label: 'Total Posts', + label: formatMessage(labels.totalPosts), fillColor: 'rgba(151,187,205,0.2)', strokeColor: 'rgba(151,187,205,1)', pointColor: 'rgba(151,187,205,1)', @@ -97,7 +115,7 @@ export default class SystemAnalytics extends React.Component { var chartData = { labels: [], datasets: [{ - label: 'Active Users With Posts', + label: formatMessage(labels.activeUsers), fillColor: 'rgba(151,187,205,0.2)', strokeColor: 'rgba(151,187,205,1)', pointColor: 'rgba(151,187,205,1)', @@ -142,7 +160,7 @@ export default class SystemAnalytics extends React.Component { return (
    ; @@ -75,7 +97,12 @@ export default class TeamSettings extends React.Component { return (
    -

    {'Team Settings'}

    +

    + +

    - {'Site Name:'} +
    -

    {'Name of service shown in login screens and UI.'}

    +

    + +

    @@ -107,7 +142,10 @@ export default class TeamSettings extends React.Component { className='control-label col-sm-4' htmlFor='MaxUsersPerTeam' > - {'Max Users Per Team:'} +
    -

    {'Maximum total number of users per team, including both active and inactive users.'}

    +

    + +

    @@ -128,7 +171,10 @@ export default class TeamSettings extends React.Component { className='control-label col-sm-4' htmlFor='EnableTeamCreation' > - {'Enable Team Creation: '} +
    -

    {'When false, the ability to create teams is disabled. The create team button displays error when pressed.'}

    +

    + +

    @@ -161,7 +218,10 @@ export default class TeamSettings extends React.Component { className='control-label col-sm-4' htmlFor='EnableUserCreation' > - {'Enable User Creation: '} +
    -

    {'When false, the ability to create accounts is disabled. The create account button displays error when pressed.'}

    +

    + +

    @@ -194,7 +265,10 @@ export default class TeamSettings extends React.Component { className='control-label col-sm-4' htmlFor='RestrictCreationToDomains' > - {'Restrict Creation To Domains:'} +
    -

    {'Teams and user accounts can only be created from a specific domain (e.g. "mattermost.org") or list of comma-separated domains (e.g. "corp.mattermost.com, mattermost.org").'}

    +

    + +

    @@ -215,7 +294,10 @@ export default class TeamSettings extends React.Component { className='control-label col-sm-4' htmlFor='RestrictTeamNames' > - {'Restrict Team Names: '} +
    -

    {'When true, You cannot create a team name with reserved words like www, admin, support, test, channel, etc'}

    +

    + +

    @@ -248,7 +341,10 @@ export default class TeamSettings extends React.Component { className='control-label col-sm-4' htmlFor='EnableTeamListing' > - {'Enable Team Directory: '} +
    -

    {'When true, teams that are configured to show in team directory will show on main page inplace of creating a new team.'}

    +

    + +

    @@ -285,9 +392,12 @@ export default class TeamSettings extends React.Component { className={saveClass} onClick={this.handleSubmit} id='save-button' - data-loading-text={' Saving Config...'} + data-loading-text={' ' + formatMessage(holders.saving)} > - {'Save'} + @@ -299,5 +409,8 @@ export default class TeamSettings extends React.Component { } TeamSettings.propTypes = { + intl: intlShape.isRequired, config: React.PropTypes.object }; + +export default injectIntl(TeamSettings); \ No newline at end of file diff --git a/web/react/components/admin_console/team_users.jsx b/web/react/components/admin_console/team_users.jsx index 2d9657956..1177c9c56 100644 --- a/web/react/components/admin_console/team_users.jsx +++ b/web/react/components/admin_console/team_users.jsx @@ -6,6 +6,8 @@ import LoadingScreen from '../loading_screen.jsx'; import UserItem from './user_item.jsx'; import ResetPasswordModal from './reset_password_modal.jsx'; +import {FormattedMessage} from 'mm-intl'; + export default class UserList extends React.Component { constructor(props) { super(props); @@ -122,7 +124,15 @@ export default class UserList extends React.Component { if (this.state.users == null) { return (
    -

    {'Users for ' + this.props.team.name}

    +

    + +

    {serverError}
    @@ -141,7 +151,16 @@ export default class UserList extends React.Component { return (
    -

    {'Users for ' + this.props.team.name + ' (' + this.state.users.length + ')'}

    +

    + +

    {serverError} + ); if (user.roles.length > 0) { if (Utils.isSystemAdmin(user.roles)) { - currentRoles = 'System Admin'; + currentRoles = ( + + ); } else if (Utils.isAdmin(user.roles)) { - currentRoles = 'Team Admin'; + currentRoles = ( + + ); } else { currentRoles = user.roles.charAt(0).toUpperCase() + user.roles.slice(1); } @@ -128,7 +145,12 @@ export default class UserItem extends React.Component { let showMakeNotActive = user.roles !== 'system_admin'; if (user.delete_at > 0) { - currentRoles = 'Inactive'; + currentRoles = ( + + ); showMakeMember = false; showMakeAdmin = false; showMakeSystemAdmin = false; @@ -145,7 +167,10 @@ export default class UserItem extends React.Component { href='#' onClick={this.handleMakeSystemAdmin} > - {'Make System Admin'} + ); @@ -160,7 +185,10 @@ export default class UserItem extends React.Component { href='#' onClick={this.handleMakeAdmin} > - {'Make Team Admin'} + ); @@ -175,7 +203,10 @@ export default class UserItem extends React.Component { href='#' onClick={this.handleMakeMember} > - {'Make Member'} + ); @@ -190,7 +221,10 @@ export default class UserItem extends React.Component { href='#' onClick={this.handleMakeActive} > - {'Make Active'} + ); @@ -205,7 +239,10 @@ export default class UserItem extends React.Component { href='#' onClick={this.handleMakeNotActive} > - {'Make Inactive'} + ); @@ -248,7 +285,10 @@ export default class UserItem extends React.Component { href='#' onClick={this.handleResetPassword} > - {'Reset Password'} + diff --git a/web/static/i18n/en.json b/web/static/i18n/en.json index 56744ddac..a713be74c 100644 --- a/web/static/i18n/en.json +++ b/web/static/i18n/en.json @@ -27,6 +27,16 @@ "admin.sidebar.teams": "TEAMS ({count})", "admin.sidebar.other": "OTHER", "admin.sidebar.logs": "Logs", + "admin.analytics.loading": "Loading...", + "admin.analytics.totalUsers": "Total Users", + "admin.analytics.publicChannels": "Public Channels", + "admin.analytics.privateGroups": "Private Groups", + "admin.analytics.totalPosts": "Total Posts", + "admin.analytics.meaningful": "Not enough data for a meaningful representation.", + "admin.analytics.activeUsers": "Active Users With Posts", + "admin.analytics.recentActive": "Recent Active Users", + "admin.analytics.newlyCreated": "Newly Created Users", + "admin.analytics.title": "Statistics for {title}", "admin.email.notificationDisplayExample": "Ex: \"Mattermost Notification\", \"System\", \"No-Reply\"", "admin.email.notificationEmailExample": "Ex: \"mattermost@yourcompany.com\", \"admin@yourcompany.com\"", "admin.email.smtpUsernameExample": "Ex: \"admin@yourcompany.com\", \"AKIADTOVBGERKLCBV\"", @@ -79,9 +89,313 @@ "admin.email.pushServerTitle": "Push Notification Server:", "admin.email.pushServerDesc": "Location of Mattermost push notification service you can set up behind your firewall using https://github.com/mattermost/push-proxy. For testing you can use https://push-test.mattermost.com, which connects to the sample Mattermost iOS app in the public Apple AppStore. Please do not use test service for production deployments.", "admin.email.save": "Save", + "admin.gitlab.clientIdExample": "Ex \"jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY\"", + "admin.gitlab.clientSecretExample": "Ex \"jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY\"", + "admin.gitlab.authExample": "Ex \"\"", + "admin.gitlab.tokenExample": "Ex \"\"", + "admin.gitlab.userExample": "Ex \"\"", + "admin.gitlab.saving": "Saving Config...", + "admin.gitlab.settingsTitle": "GitLab Settings", + "admin.gitlab.enableTitle": "Enable Sign Up With GitLab: ", + "admin.gitlab.true": "true", + "admin.gitlab.false": "false", + "admin.gitlab.enableDescription": "When true, Mattermost allows team creation and account signup using GitLab OAuth.", + "admin.gitlab.EnableHtmlDesc": "
    1. Log in to your GitLab account and go to Applications -> Profile Settings.
    2. Enter Redirect URIs \"/login/gitlab/complete\" (example: http://localhost:8065/login/gitlab/complete) and \"/signup/gitlab/complete\".
    3. Then use \"Secret\" and \"Id\" fields from GitLab to complete the options below.
    4. Complete the Endpoint URLs below.
    ", + "admin.gitlab.clientIdTitle": "Id:", + "admin.gitlab.clientIdDescription": "Obtain this value via the instructions above for logging into GitLab", + "admin.gitlab.clientSecretTitle": "Secret:", + "admin.gitab.clientSecretDescription": "Obtain this value via the instructions above for logging into GitLab.", + "admin.gitlab.authTitle": "Auth Endpoint:", + "admin.gitlab.authDescription": "Enter https:///oauth/authorize (example https://example.com:3000/oauth/authorize). Make sure you use HTTP or HTTPS in your URL depending on your server configuration.", + "admin.gitlab.tokenTitle": "Token Endpoint:", + "admin.gitlab.tokenDescription": "Enter https:///oauth/token. Make sure you use HTTP or HTTPS in your URL depending on your server configuration.", + "admin.gitlab.userTitle": "User API Endpoint:", + "admin.gitlab.userDescription": "Enter https:///api/v3/user. Make sure you use HTTP or HTTPS in your URL depending on your server configuration.", + "admin.gitlab.save": "Save", + "admin.image.storeDisabled": "Disable File Storage", + "admin.image.storeLocal": "Local File System", + "admin.image.storeAmazonS3": "Amazon S3", + "admin.image.localExample": "Ex \"./data/\"", + "admin.image.amazonS3IdExample": "Ex \"AKIADTOVBGERKLCBV\"", + "admin.image.amazonS3SecretExample": "Ex \"jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY\"", + "admin.image.amazonS3BucketExample": "Ex \"mattermost-media\"", + "admin.image.amazonS3RegionExample": "Ex \"us-east-1\"", + "admin.image.thumbWidthExample": "Ex \"120\"", + "admin.image.thumbHeightExample": "Ex \"100\"", + "admin.image.previewWidthExample": "Ex \"1024\"", + "admin.image.previewHeightExample": "Ex \"0\"", + "admin.image.profileWidthExample": "Ex \"1024\"", + "admin.image.profileHeightExample": "Ex \"0\"", + "admin.image.publicLinkExample": "Ex \"gxHVDcKUyP2y1eiyW8S8na1UYQAfq6J6\"", + "admin.image.saving": "Saving Config...", + "admin.image.fileSettings": "File Settings", + "admin.image.storeTitle": "Store Files In:", + "admin.image.localTitle": "Local Directory Location:", + "admin.image.localDescription": "Directory to which image files are written. If blank, will be set to ./data/.", + "admin.image.amazonS3IdTitle": "Amazon S3 Access Key Id:", + "admin.image.amazonS3IdDescription": "Obtain this credential from your Amazon EC2 administrator.", + "admin.image.amazonS3SecretTitle": "Amazon S3 Secret Access Key:", + "admin.image.amazonS3SecretDescription": "Obtain this credential from your Amazon EC2 administrator.", + "admin.image.amazonS3BucketTitle": "Amazon S3 Bucket:", + "admin.image.amazonS3BucketDescription": "Name you selected for your S3 bucket in AWS.", + "admin.image.amazonS3RegionTitle": "Amazon S3 Region:", + "admin.image.amazonS3RegionDescription": "AWS region you selected for creating your S3 bucket.", + "admin.image.thumbWidthTitle": "Thumbnail Width:", + "admin.image.thumbWidthDescription": "Width of thumbnails generated from uploaded images. Updating this value changes how thumbnail images render in future, but does not change images created in the past.", + "admin.image.thumbHeightTitle": "Thumbnail Height:", + "admin.image.thumbHeightDescription": "Height of thumbnails generated from uploaded images. Updating this value changes how thumbnail images render in future, but does not change images created in the past.", + "admin.image.previewWidthTitle": "Preview Width:", + "admin.image.previewWidthDescription": "Maximum width of preview image. Updating this value changes how preview images render in future, but does not change images created in the past.", + "admin.image.previewHeightTitle": "Preview Height:", + "admin.image.previewHeightDescription": "Maximum height of preview image (\"0\": Sets to auto-size). Updating this value changes how preview images render in future, but does not change images created in the past.", + "admin.image.profileWidthTitle": "Profile Width:", + "admin.image.profileWidthDescription": "Width of profile picture.", + "admin.image.profileHeightTitle": "Profile Height:", + "admin.image.profileHeightDescription": "Height of profile picture.", + "admin.image.shareTitle": "Share Public File Link: ", + "admin.image.true": "true", + "admin.image.false": "false", + "admin.image.shareDescription": "Allow users to share public links to files and images.", + "admin.image.publicLinkTitle": "Public Link Salt:", + "admin.image.publicLinkDescription": "32-character salt added to signing of public image links. Randomly generated on install. Click \"Re-Generate\" to create new salt.", + "admin.image.regenerate": "Re-Generate", + "admin.image.save": "Save", + "admin.ldap.serverEx": "Ex \"10.0.0.23\"", + "admin.ldap.portEx": "Ex \"389\"", + "admin.ldap.baseEx": "Ex \"dc=mydomain,dc=com\"", + "admin.ldap.firstnameAttrEx": "Ex \"givenName\"", + "admin.ldap.lastnameAttrEx": "Ex \"sn\"", + "admin.ldap.emailAttrEx": "Ex \"mail\"", + "admin.ldap.usernameAttrEx": "Ex \"sAMAccountName\"", + "admin.ldap.idAttrEx": "Ex \"sAMAccountName\"", + "admin.ldap.queryEx": "Ex \"60\"", + "admin.ldap.saving": "Saving Config...", + "admin.ldap.bannerHeading": "Note:", + "admin.ldap.bannerDesc": "If a user attribute changes on the LDAP server it will be updated the next time the user enters their credentials to log in to Mattermost. This includes if a user is made inactive or removed from an LDAP server. Synchronization with LDAP servers is planned in a future release.", + "admin.ldap.noLicense": "

    Note:

    LDAP is an enterprise feature. Your current license does not support LDAP. Click here for information and pricing on enterprise licenses.

    ", + "admin.ldap.title": "LDAP Settings", + "admin.ldap.enableTitle": "Enable Login With LDAP:", + "admin.ldap.true": "true", + "admin.ldap.false": "false", + "admin.ldap.enableDesc": "When true, Mattermost allows login using LDAP", + "admin.ldap.serverTitle": "LDAP Server:", + "admin.ldap.serverDesc": "The domain or IP address of LDAP server.", + "admin.ldap.portTitle": "LDAP Port:", + "admin.ldap.portDesc": "The port Mattermost will use to connect to the LDAP server. Default is 389.", + "admin.ldap.baseTitle": "BaseDN:", + "admin.ldap.baseDesc": "The Base DN is the Distinguished Name of the location where Mattermost should start its search for users in the LDAP tree.", + "admin.ldap.bindUserTitle": "Bind Username:", + "admin.ldap.bindUserDesc": "The username used to perform the LDAP search. This should typically be an account created specifically for use with Mattermost. It should have access limited to read the portion of the LDAP tree specified in the BaseDN field.", + "admin.ldap.bindPwdTitle": "Bind Password:", + "admin.ldap.bindPwdDesc": "Password of the user given in \"Bind Username\".", + "admin.ldap.firstnameAttrTitle": "First Name Attrubute", + "admin.ldap.firstnameAttrDesc": "The attribute in the LDAP server that will be used to populate the first name of users in Mattermost.", + "admin.ldap.lastnameAttrTitle": "Last Name Attribute:", + "admin.ldap.lastnameAttrDesc": "The attribute in the LDAP server that will be used to populate the last name of users in Mattermost.", + "admin.ldap.emailAttrTitle": "Email Attribute:", + "admin.ldap.emailAttrDesc": "The attribute in the LDAP server that will be used to populate the email addresses of users in Mattermost.", + "admin.ldap.usernameAttrTitle": "Username Attribute:", + "admin.ldap.uernameAttrDesc": "The attribute in the LDAP server that will be used to populate the username field in Mattermost. This may be the same as the ID Attribute.", + "admin.ldap.idAttrTitle": "Id Attribute: ", + "admin.ldap.idAttrDesc": "The attribute in the LDAP server that will be used as a unique identifier in Mattermost. It should be an LDAP attribute with a value that does not change, such as username or uid. If a user’s Id Attribute changes, it will create a new Mattermost account unassociated with their old one. This is the value used to log in to Mattermost in the \"LDAP Username\" field on the sign in page. Normally this attribute is the same as the “Username Attribute” field above. If your team typically uses domain\\\\username to sign in to other services with LDAP, you may choose to put domain\\\\username in this field to maintain consistency between sites.", + "admin.ldap.queryTitle": "Query Timeout (seconds):", + "admin.ldap.queryDesc": "The timeout value for queries to the LDAP server. Increase if you are getting timeout errors caused by a slow LDAP server.", + "admin.ldap.save": "Save", + "admin.support.saving": "Saving Config...", + "admin.support.title": "Legal and Support Settings", + "admin.support.termsTitle": "Terms of Service link:", + "admin.support.termsDesc": "Link to Terms of Service available to users on desktop and on mobile. Leaving this blank will hide the option to display a notice.", + "admin.support.privacyTitle": "Privacy Policy link:", + "admin.support.privacyDesc": "Link to Privacy Policy available to users on desktop and on mobile. Leaving this blank will hide the option to display a notice.", + "admin.support.aboutTitle": "About link:", + "admin.support.aboutDesc": "Link to About page for more information on your Mattermost deployment, for example its purpose and audience within your organization. Defaults to Mattermost information page.", + "admin.support.helpTitle": "Help link:", + "admin.support.helpDesc": "Link to help documentation from team site main menu. Typically not changed unless your organization chooses to create custom documentation.", + "admin.support.problemTitle": "Report a Problem link:", + "admin.support.problemDesc": "Link to help documentation from team site main menu. By default this points to the peer-to-peer troubleshooting forum where users can search for, find and request help with technical issues.", + "admin.support.emailTitle": "Support email:", + "admin.support.emailHelp": "Email shown during tutorial for end users to ask support questions.", + "admin.support.save": "Save", + "admin.license.removing": "Removing License...", + "admin.license.uploading": "Uploading License...", + "admin.license.enterpriseEdition": "Mattermost Enterprise Edition. Designed for enterprise-scale communication.", + "admin.license.entrepriseType": "

    This compiled release of Mattermost platform is provided under a commercial license\n from Mattermost, Inc. based on your subscription level and is subject to the Terms of Service.

    \n

    Your subscription details are as follows:

    \n Name: {name}
    \n Company or organization name: {company}
    \n Number of users: {users}
    \n License issued: {issued}
    \n Start date of license: {start}
    \n Expiry date of license: {expires}
    \n LDAP: {ldap}
    ", + "admin.license.keyRemove": "Remove Enterprise License and Downgrade Server", + "admin.licence.keyMigration": "If you’re migrating servers you may need to remove your license key from this server in order to install it on a new server. To start,\n disable all Enterprise Edition features on this server.\n This will enable the ability to remove the license key and downgrade this server from Enterprise Edition to Team Edition.", + "admin.license.teamEdition": "Mattermost Team Edition. Designed for teams from 5 to 50 users.", + "admin.license.teamType": "

    This compiled release of Mattermost platform is offered under an MIT license.

    \n

    See MIT-COMPILED-LICENSE.txt in your root install directory for details. See NOTICES.txt for information about open source software used in this system.

    ", + "admin.license.upload": "Upload", + "admin.license.uploadDesc": "Upload a license key for Mattermost Enterprise Edition to upgrade this server. Visit us online\n to learn more about the benefits of Enterprise Edition or to purchase a key.", + "admin.license.title": "Edition and License", + "admin.license.edition": "Edition: ", + "admin.license.type": "License: ", + "admin.license.key": "License Key: ", + "admin.log.locationPlaceholder": "Enter your file location", + "admin.log.formatPlaceholder": "Enter your file format", + "admin.log.saving": "Saving Config...", + "admin.log.logSettings": "Log Settings", + "admin.log.consoleTitle": "Log To The Console: ", + "admin.log.true": "true", + "admin.log.false": "false", + "admin.log.consoleDescription": "Typically set to false in production. Developers may set this field to true to output log messages to console based on the console level option. If true, server writes messages to the standard output stream (stdout).", + "admin.log.levelTitle": "Console Log Level:", + "admin.log.levelDescription": "This setting determines the level of detail at which log events are written to the console. ERROR: Outputs only error messages. INFO: Outputs error messages and information around startup and initialization. DEBUG: Prints high detail for developers working on debugging issues.", + "admin.log.fileTitle": "Log To File: ", + "admin.log.fileDescription": "Typically set to true in production. When true, log files are written to the log file specified in file location field below.", + "admin.log.fileLevelTitle": "File Log Level:", + "admin.log.fileLevelDescription": "This setting determines the level of detail at which log events are written to the log file. ERROR: Outputs only error messages. INFO: Outputs error messages and information around startup and initialization. DEBUG: Prints high detail for developers working on debugging issues.", + "admin.log.locationTitle": "File Location:", + "admin.log.locationDescription": "File to which log files are written. If blank, will be set to ./logs/mattermost, which writes logs to mattermost.log. Log rotation is enabled and every 10,000 lines of log information is written to new files stored in the same directory, for example mattermost.2015-09-23.001, mattermost.2015-09-23.002, and so forth.", + "admin.log.formatTitle": "File Format:", + "admin.log.formatDescription": "Format of log message output. If blank will be set to \"[%D %T] [%L] %M\", where:", + "admin.log.formatTime": "Time (15:04:05 MST)", + "admin.log.formatDateLong": "Date (2006/01/02)", + "admin.log.formatDateShort": "Date (01/02/06)", + "admin.log.formatLevel": "Level (DEBG, INFO, EROR)", + "admin.log.formatSource": "Source", + "admin.log.formatMessage": "Message", + "admin.log.save": "Save", + "admin.logs.title": "Server Logs", + "admin.logs.reload": "Reload", + "admin.privacy.saving": "Saving Config...", + "admin.privacy.title": "Privacy Settings", + "admin.privacy.showEmailTitle": "Show Email Address: ", + "admin.privacy.true": "true", + "admin.privacy.false": "false", + "admin.privacy.showEmailDescription": "When false, hides email address of users from other users in the user interface, including team owners and team administrators. Used when system is set up for managing teams where some users choose to keep their contact information private.", + "admin.privacy.showFullNameTitle": "Show Full Name: ", + "admin.privacy.showFullNameDescription": "When false, hides full name of users from other users, including team owners and team administrators. Username is shown in place of full name.", + "admin.privacy.save": "Save", + "admin.rate.queriesExample": "Ex \"10\"", + "admin.rate.memoryExample": "Ex \"10000\"", + "admin.rate.httpHeaderExample": "Ex \"X-Real-IP\", \"X-Forwarded-For\"", + "admin.rate.saving": "Saving Config...", + "admin.rate.noteTitle": "Note:", + "admin.rate.noteDescription": "Changing properties in this section will require a server restart before taking effect.", + "admin.rate.title": "Rate Limit Settings", + "admin.rate.enableLimiterTitle": "Enable Rate Limiter: ", + "admin.rate.true": "true", + "admin.rate.false": "false", + "admin.rate.enableLimiterDescription": "When true, APIs are throttled at rates specified below.", + "admin.rate.queriesTitle": "Number Of Queries Per Second:", + "admin.rate.queriesDescription": "Throttles API at this number of requests per second.", + "admin.rate.memoryTitle": "Memory Store Size:", + "admin.rate.memoryDescription": "Maximum number of users sessions connected to the system as determined by \"Vary By Remote Address\" and \"Vary By Header\" settings below.", + "admin.rate.remoteTitle": "Vary By Remote Address: ", + "admin.rate.remoteDescription": "When true, rate limit API access by IP address.", + "admin.rate.httpHeaderTitle": "Vary By HTTP Header:", + "admin.rate.httpHeaderDescription": "When filled in, vary rate limiting by HTTP header field specified (e.g. when configuring NGINX set to \"X-Real-IP\", when configuring AmazonELB set to \"X-Forwarded-For\").", + "admin.rate.save": "Save", + "admin.reset_password.submit": "Please enter at least {chars} characters.", + "admin.reset_password.title": "Reset Password", + "admin.reset_password.newPassword": "New Password", + "admin.reset_password.close": "Close", + "admin.reset_password.select": "Select", "admin.select_team.selectTeam": "Select Team", "admin.select_team.close": "Close", "admin.select_team.select": "Select", + "admin.service.listenExample": "Ex \":8065\"", + "admin.service.attemptExample": "Ex \"10\"", + "admin.service.segmentExample": "Ex \"g3fgGOXJAQ43QV7rAh6iwQCkV4cA1Gs\"", + "admin.service.googleExample": "Ex \"7rAh6iwQCkV4cA1Gsg3fgGOXJAQ43QV\"", + "admin.service.sessionDaysEx": "Ex \"30\"", + "admin.service.saving": "Saving Config...", + "admin.service.title": "Service Settings", + "admin.service.listenAddress": "Listen Address:", + "admin.service.listenDescription": "The address to which to bind and listen. Entering \":8065\" will bind to all interfaces or you can choose one like \"127.0.0.1:8065\". Changing this will require a server restart before taking effect.", + "admin.service.attemptTitle": "Maximum Login Attempts:", + "admin.service.attemptDescription": "Login attempts allowed before user is locked out and required to reset password via email.", + "admin.service.segmentTitle": "Segment Developer Key:", + "admin.service.segmentDescription": "For users running a SaaS services, sign up for a key at Segment.com to track metrics.", + "admin.service.googleTitle": "Google Developer Key:", + "admin.service.googleDescription": "Set this key to enable embedding of YouTube video previews based on hyperlinks appearing in messages or comments. Instructions to obtain a key available at\n https://www.youtube.com/watch?v=Im69kzhpR3I.\n Leaving the field blank disables the automatic generation of YouTube video previews from links.", + "admin.service.webhooksTitle": "Enable Incoming Webhooks: ", + "admin.service.true": "true", + "admin.service.false": "false", + "admin.service.webhooksDescription": "When true, incoming webhooks will be allowed. To help combat phishing attacks, all posts from webhooks will be labelled by a BOT tag.", + "admin.service.outWebhooksTitle": "Enable Outgoing Webhooks: ", + "admin.service.outWebhooksDesc": "When true, outgoing webhooks will be allowed.", + "admin.service.overrideTitle": "Enable Overriding Usernames from Webhooks: ", + "admin.service.overrideDescription": "When true, webhooks will be allowed to change the username they are posting as. Note, combined with allowing icon overriding, this could open users up to phishing attacks.", + "admin.service.iconTitle": "Enable Overriding Icon from Webhooks: ", + "admin.service.iconDescription": "When true, webhooks will be allowed to change the icon they post with. Note, combined with allowing username overriding, this could open users up to phishing attacks.", + "admin.service.testingTitle": "Enable Testing: ", + "admin.service.testingDescription": "(Developer Option) When true, /loadtest slash command is enabled to load test accounts and test data. Changing this will require a server restart before taking effect.", + "admin.service.developerTitle": "Enable Developer Mode: ", + "admin.service.developerDesc": "(Developer Option) When true, extra information around errors will be displayed in the UI.", + "admin.service.securityTitle": "Enable Security Alerts: ", + "admin.service.securityDesc": "When true, System Administrators are notified by email if a relevant security fix alert has been announced in the last 12 hours. Requires email to be enabled.", + "admin.service.webSessionDays": "Session Length for Web in Days:", + "admin.service.webSessionDaysDesc": "The web session will expire after the number of days specified and will require a user to login again.", + "admin.service.mobileSessionDays": "Session Length for Mobile Device in Days:", + "admin.service.mobileSessionDaysDesc": "The native mobile session will expire after the number of days specified and will require a user to login again.", + "admin.service.ssoSessionDays": "Session Length for SSO in Days:", + "admin.service.ssoSessionDaysDesc": "The SSO session will expire after the number of days specified and will require a user to login again.", + "admin.service.sessionCache": "Session Cache in Minutes:", + "admin.service.sessionCacheDesc": "The number of minutes to cache a session in memory.", + "admin.service.save": "Save", + "admin.sql.warning": "Warning: re-generating this salt may cause some columns in the database to return empty results.", + "admin.sql.maxConnectionsExample": "Ex \"10\"", + "admin.sql.maxOpenExample": "Ex \"10\"", + "admin.sql.keyExample": "Ex \"gxHVDcKUyP2y1eiyW8S8na1UYQAfq6J6\"", + "admin.sql.saving": "Saving Config...", + "admin.sql.noteTitle": "Note:", + "admin.sql.noteDescription": "Changing properties in this section will require a server restart before taking effect.", + "admin.sql.title": "SQL Settings", + "admin.sql.driverName": "Driver Name:", + "admin.sql.dataSource": "Data Source:", + "admin.sql.replicas": "Data Source Replicas:", + "admin.sql.maxConnectionsTitle": "Maximum Idle Connections:", + "admin.sql.maxConnectionsDescription": "Maximum number of idle connections held open to the database.", + "admin.sql.maxOpenTitle": "Maximum Open Connections:", + "admin.sql.maxOpenDescription": "Maximum number of open connections held open to the database.", + "admin.sql.keyTitle": "At Rest Encrypt Key:", + "admin.sql.keyDescription": "32-character salt available to encrypt and decrypt sensitive fields in database.", + "admin.sql.regenerate": "Re-Generate", + "admin.sql.traceTitle": "Trace: ", + "admin.sql.true": "true", + "admin.sql.false": "false", + "admin.sql.traceDescription": "(Development Mode) When true, executing SQL statements are written to the log.", + "admin.sql.save": "Save", + "admin.system_analytics.totalPosts": "Total Posts", + "admin.system_analytics.activeUsers": "Active Users With Posts", + "admin.system_analytics.title": "the System", + "admin.team_analytics.totalPosts": "Total Posts", + "admin.team_analytics.activeUsers": "Active Users With Posts", + "admin.team.siteNameExample": "Ex \"Mattermost\"", + "admin.team.maxUsersExample": "Ex \"25\"", + "admin.team.restrictExample": "Ex \"corp.mattermost.com, mattermost.org\"", + "admin.team.saving": "Saving Config...", + "admin.team.title": "Team Settings", + "admin.team.siteNameTitle": "Site Name:", + "admin.team.siteNameDescription": "Name of service shown in login screens and UI.", + "admin.team.maxUsersTitle": "Max Users Per Team:", + "admin.team.maxUsersDescription": "Maximum total number of users per team, including both active and inactive users.", + "admin.team.teamCreationTitle": "Enable Team Creation: ", + "admin.team.true": "true", + "admin.team.false": "false", + "admin.team.teamCreationDescription": "When false, the ability to create teams is disabled. The create team button displays error when pressed.", + "admin.team.userCreationTitle": "Enable User Creation: ", + "admin.team.userCreationDescription": "When false, the ability to create accounts is disabled. The create account button displays error when pressed.", + "admin.team.restrictTitle": "Restrict Creation To Domains:", + "admin.team.restrictDescription": "Teams and user accounts can only be created from a specific domain (e.g. \"mattermost.org\") or list of comma-separated domains (e.g. \"corp.mattermost.com, mattermost.org\").", + "admin.team.restrictNameTitle": "Restrict Team Names: ", + "admin.team.restrictNameDesc": "When true, You cannot create a team name with reserved words like www, admin, support, test, channel, etc", + "admin.team.dirTitle": "Enable Team Directory: ", + "admin.team.dirDesc": "When true, teams that are configured to show in team directory will show on main page inplace of creating a new team.", + "admin.team.save": "Save", + "admin.userList.title": "Users for {team}", + "admin.userList.title2": "Users for {team} ({count})", + "admin.user_item.member": "Member", + "admin.user_item.sysAdmin": "System Admin", + "admin.user_item.teamAdmin": "Team Admin", + "admin.user_item.inactive": "Inactive", + "admin.user_item.makeSysAdmin": "Make System Admin", + "admin.user_item.makeTeamAdmin": "Make Team Admin", + "admin.user_item.makeMember": "Make Member", + "admin.user_item.makeActive": "Make Active", + "admin.user_item.makeInactive": "Make Inactive", + "admin.user_item.resetPwd": "Reset Password", "error_bar.preview_mode": "Preview Mode: Email notifications have not been configured", "loading_screen.loading": "Loading" } \ No newline at end of file diff --git a/web/static/i18n/es.json b/web/static/i18n/es.json index bc53a78b0..45f8754bd 100644 --- a/web/static/i18n/es.json +++ b/web/static/i18n/es.json @@ -1,4 +1,14 @@ { + "admin.analytics.activeUsers": "Usuarios Activos con Mensajes", + "admin.analytics.loading": "Cargando...", + "admin.analytics.meaningful": "No hay suficiente data para tener una representación significativa.", + "admin.analytics.newlyCreated": "Nuevos Usuarios Creados", + "admin.analytics.privateGroups": "Grupos Privados", + "admin.analytics.publicChannels": "Canales Públicos", + "admin.analytics.recentActive": "Usuarios Recientemente Activos", + "admin.analytics.title": "Estadísticas para {title}", + "admin.analytics.totalPosts": "Total de Mensajes", + "admin.analytics.totalUsers": "Total de Usuarios", "admin.email.allowSignupDescription": "Cuando está en verdadero, Mattermost permite la creación de equipos y cuentas utilizando el correo electrónico y contraseña. Este valor debe estar en falso sólo cuando quieres limitar el inicio de sesión a través de servicios tipo OAuth o LDAP.", "admin.email.allowSignupTitle": "Permitir inicio de sesión con correo:", "admin.email.connectionSecurityNone": "Ninguno", @@ -51,13 +61,240 @@ "admin.email.smtpUsernameTitle": "Usuario SMTP:", "admin.email.testing": "Probando...", "admin.email.true": "verdadero", + "admin.gitab.clientSecretDescription": "Utilizar este valor vía instrucciones suministradas anteriormente para iniciar sesión en GitLab.", + "admin.gitlab.EnableHtmlDesc": "
    1. Inicia sesión con tu cuenta en GitLab y dirigete a Applications -> Profile Settings.
    2. Ingresa los URIs \"/login/gitlab/complete\" (ejemplo: http://localhost:8065/login/gitlab/complete) y \"/signup/gitlab/complete\".
    3. Luego utiliza los valores de los campos \"Secret\" e \"Id\" de GitLab y completa las opciones que abajo se presentan.
    4. Completa las dirección URLs abajo.
    ", + "admin.gitlab.authDescription": "Ingresar /oauth/authorize (example http://localhost:3000/oauth/authorize). Asegurate que si utilizas HTTPS o HTTPS tus URLS sean correctas", + "admin.gitlab.authExample": "Ej \"\"", + "admin.gitlab.authTitle": "URL para autentificación:", + "admin.gitlab.clientIdDescription": "Utilizar este valor vía instrucciones suministradas anteriormente para iniciar sesión en GitLab.", + "admin.gitlab.clientIdExample": "Ej \"jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY\"", + "admin.gitlab.clientIdTitle": "Id:", + "admin.gitlab.clientSecretExample": "Ej \"jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY\"", + "admin.gitlab.clientSecretTitle": "Secreto:", + "admin.gitlab.enableDescription": "Cuando está asignado como verdadero, Mattermost permite la creación de equipos y cuentas utilizando el servicio de OAuth de GitLab. Para configurarlo, inicia sesión en GitLab con tu cuenta y dirigete a Applications -> Profile Settings. Ingresa los URIs de redireccionamiento \"/login/gitlab/complete\" (ejemplo: http://localhost:8065/login/gitlab/complete) y \"/signup/gitlab/complete\". Luego utiliza los campos de \"Secret\" y \"Id\" para completar las opciones de abajo.", + "admin.gitlab.enableTitle": "Enable Sign Up With GitLab: ", + "admin.gitlab.false": "falso", + "admin.gitlab.save": "Guardar", + "admin.gitlab.saving": "Guardando...", + "admin.gitlab.settingsTitle": "Configuración de GitLab", + "admin.gitlab.tokenDescription": "Ingresar /oauth/token. Asegurate que si utilizas HTTPS o HTTPS tus URLS sean correctas", + "admin.gitlab.tokenExample": "Ej \"\"", + "admin.gitlab.tokenTitle": "Url para obteción de Token:", + "admin.gitlab.true": "verdadero", + "admin.gitlab.userDescription": "Ingresar /api/v3/user. Asegurate que si utilizas HTTPS o HTTPS tus URLS sean correctas", + "admin.gitlab.userExample": "Ej \"\"", + "admin.gitlab.userTitle": "URL para obtener datos de usuario:", + "admin.image.amazonS3BucketDescription": "Nombre que ha seleccionado para el bucket S3 en AWS.", + "admin.image.amazonS3BucketExample": "Ex \"mattermost-media\"", + "admin.image.amazonS3BucketTitle": "Amazon S3 Bucket:", + "admin.image.amazonS3IdDescription": "Obetener esta credencial del administrador de tu Amazon EC2", + "admin.image.amazonS3IdExample": "Ej \"AKIADTOVBGERKLCBV\"", + "admin.image.amazonS3IdTitle": "Llave de acceso Amazon S3:", + "admin.image.amazonS3RegionDescription": "Región que ha seleccionado en AWS para la creación de tu bucket S3.", + "admin.image.amazonS3RegionExample": "Ej \"us-east-1\"", + "admin.image.amazonS3RegionTitle": "Región de Amazon S3:", + "admin.image.amazonS3SecretDescription": "Obetener esta credencial del administrador de tu Amazon EC2.", + "admin.image.amazonS3SecretExample": "Ej \"jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY\"", + "admin.image.amazonS3SecretTitle": "Llave secreta de Amazon S3:", + "admin.image.false": "falso", + "admin.image.fileSettings": "Configuración de archivos", + "admin.image.localDescription": "Directorio al que se esriben los archivos de imágen. Si es vacio, se establecerá en ./data/.", + "admin.image.localExample": "Ej \"./dato/\"", + "admin.image.localTitle": "Directorio local de ubicación:", + "admin.image.previewHeightDescription": "Altura máxima para la vista previa de la imágen (\"0\": Establecer a tamaño-automático). Actualizando este valor los cambios de vista previa se hacen en el futuro, pero no cambia para imagenes creadas en el pasado.", + "admin.image.previewHeightExample": "Ej \"0\"", + "admin.image.previewHeightTitle": "Previsualzar alto:", + "admin.image.previewWidthDescription": "Ancho máximo para la vista previa de la imágen. Actualizando este valor los cambios de vista previa se hacen en el futuro, pero no cambia para imagenes creadas en el pasado.", + "admin.image.previewWidthExample": "Ej \"1024\"", + "admin.image.previewWidthTitle": "Previsualizar ancho:", + "admin.image.profileHeightDescription": "Alto de imagen de perfil.", + "admin.image.profileHeightExample": "Ej \"0\"", + "admin.image.profileHeightTitle": "Alto de perfil:", + "admin.image.profileWidthDescription": "Ancho de la imagen de perfil.", + "admin.image.profileWidthExample": "Ej \"1024\"", + "admin.image.profileWidthTitle": "Ancho de perfil:", + "admin.image.publicLinkDescription": "Salt de 32-characteres agregado para firmar los enlaces para las imagenes públicas. Aleatoriamente generados en la instalación. Pincha \"Regenerar\" para crear un nuevo salt.", + "admin.image.publicLinkExample": "Ej \"gxHVDcKUyP2y1eiyW8S8na1UYQAfq6J6\"", + "admin.image.publicLinkTitle": "Título del enlace público:", + "admin.image.regenerate": "Regenerar", + "admin.image.save": "Guardar", + "admin.image.saving": "Guardando...", + "admin.image.shareDescription": "Permitir a los usuarios compartir enlaces públicos para archivos e imágenes.", + "admin.image.shareTitle": "Compartir publicamente enlaces de archivos: ", + "admin.image.storeAmazonS3": "Amazon S3", + "admin.image.storeDisabled": "Deshabilitar almacenamiento de archivos", + "admin.image.storeLocal": "Sistema local de archivos", + "admin.image.storeTitle": "Almacenar archivos en:", + "admin.image.thumbHeightDescription": "Alto de imágen miniatura subida. Actualizando este valor la imagen miniatura cambia en el futuro, pero no cambia para imagenes creadas en el pasado.", + "admin.image.thumbHeightExample": "Ej \"100\"", + "admin.image.thumbHeightTitle": "Alto de imágen miniatura:", + "admin.image.thumbWidthDescription": "Ancho de imágen miniatura subida. Actualizando este valor la imagen miniatura cambia en el futuro, pero no cambia para imagenes creadas en el pasado.", + "admin.image.thumbWidthExample": "Ej \"120\"", + "admin.image.thumbWidthTitle": "Ancho de imágen miniatura:", + "admin.image.true": "verdadero", + "admin.ldap.bannerDesc": "Si el atributo de un usuario cambia en el servidor LDAP será actualizado la próxima vez que el usuario ingrese sus credenciales para iniciar sesión en Mattermost. Esto incluye si un usuario se inactiva o se remueve en el servidor LDAP. Sincronización con servidores LDAP está planificado para futuras versiones.", + "admin.ldap.bannerHeading": "Nota:", + "admin.ldap.baseDesc": "El DN Base es el Nombre Distinguido de la ubicación donde Mattermost debe comenzar a buscar a los usuarios en el árbol del LDAP.", + "admin.ldap.baseEx": "Ex \"dc=midominio,dc=com\"", + "admin.ldap.baseTitle": "DN Base:", + "admin.ldap.bindPwdDesc": "Contraseña del usuario asignado en \"Usuario de Enlace\".", + "admin.ldap.bindPwdTitle": "Contraseña de Enlace:", + "admin.ldap.bindUserDesc": "El usuario que realizará las busquedas LDAP. Normalmente este debería ser una cuenta específicamente creada para ser utilizada por Mattermost. Debería contat con acceso limitado para leer la porción del árbol LDAP especificada en el campo DN Base.", + "admin.ldap.bindUserTitle": "Usuario de Enlace:", + "admin.ldap.emailAttrDesc": "El atributo en el servidor LDAP que será utilizado para poblar la dirección de correo electrónico de los usuarios en Mattermost.", + "admin.ldap.emailAttrEx": "Ej \"mail\"", + "admin.ldap.emailAttrTitle": "Atributo de Correo Electrónico:", + "admin.ldap.enableDesc": "Cuando es verdadero, Mattermost permite realizar inicio de sesión utilizando LDAP", + "admin.ldap.enableTitle": "Habilitar inicio de sesión con LDAP:", + "admin.ldap.false": "falso", + "admin.ldap.firstnameAttrDesc": "El atributo en el servidor LDAP que será utilizado para poblar el nombre de los usuarios en Mattermost.", + "admin.ldap.firstnameAttrEx": "Ej \"givenName\"", + "admin.ldap.firstnameAttrTitle": "Atributo del Nombre", + "admin.ldap.idAttrDesc": "El atributo en el servidor LDAP que será utilizado para poblar el identificador único en Mattermost. Debe ser un atributo de LDAP que no cambie con el tiempo, como el nombre de usuario o el uid. Si el atributo del identificador cambia, se creará una nueva cuenta en Mattermost que no está asociada a la anterior. Este valor es utilizada para iniciar sesión en Mattermost en el campo \"Usuario LDAP\" en la página de inicio de sesión. Normalmente este atributo es el mismo que el campo “Atributo Usuario”. Si el equipo normalmente utiliza dominio\\\\usuario para iniciar sesión en otros servicios con LDAP, puedes elegir llenar este campo como dominio\\\\usuario para mantener consistencia entre los servicios.", + "admin.ldap.idAttrEx": "Ej \"sAMAccountName\"", + "admin.ldap.idAttrTitle": "Atributo Id: ", + "admin.ldap.lastnameAttrDesc": "El atributo en el servidor LDAP que será utilizado para poblar el apellido de los usuarios en Mattermost.", + "admin.ldap.lastnameAttrEx": "Ej \"sn\"", + "admin.ldap.lastnameAttrTitle": "Atributo Apellido:", + "admin.ldap.noLicense": "

    Nota:

    LDAP es una característica de la edición enterprise. Tu licencia actual no soporta LDAP. Pincha aquí para información y precios de las licencias enterprise.

    ", + "admin.ldap.portDesc": "El puerto que Mattermost utilizará para conectarse al servidor LDAP. El predeterminado es 389.", + "admin.ldap.portEx": "Ej \"389\"", + "admin.ldap.portTitle": "Puerto LDAP:", + "admin.ldap.queryDesc": "El tiempo de espera para las consultas en el servidor LDAP. Aumenta este valor si estás obteniendo errores por falta de tiempo debido a un servidor de LDAP lento.", + "admin.ldap.queryEx": "Ej \"60\"", + "admin.ldap.queryTitle": "Tiempo de espera para las Consultas (segundos):", + "admin.ldap.save": "Guardar", + "admin.ldap.saving": "Guardando...", + "admin.ldap.serverDesc": "El dominio o dirección IP del servidor LDAP.", + "admin.ldap.serverEx": "Ej \"10.0.0.23\"", + "admin.ldap.serverTitle": "Servidor LDAP:", + "admin.ldap.title": "Configuración de LDAP", + "admin.ldap.true": "verdadero", + "admin.ldap.uernameAttrDesc": "El atributo en el servidor LDAP que se utilizará para poblar el nombre de usuario en Mattermost. Este puede ser igual al Attributo Id.", + "admin.ldap.usernameAttrEx": "Ej \"sAMAccountName\"", + "admin.ldap.usernameAttrTitle": "Atributo Usuario:", + "admin.licence.keyMigration": "Si estás migrando servidores es posible que necesites remover tu licencia de este servidor para poder instalarlo en un servidor nuevo. Para empezar,\n deshabilita todas las características de la Edición Enterprise de este servidor.\n Esta operación habilitará la opción para remover la licencia y degradar este servidor de la Edición Enterprise a la Edición Team.", + "admin.license.edition": "Edición: ", + "admin.license.enterpriseEdition": "Mattermost Edición Enterprise. Diseñada para comunicación de escala empresarial.", + "admin.license.entrepriseType": "

    Esta versión compilada de la plataforma de Mattermost es proporcionada bajo una licencia comercial\n de Mattermost, Inc. basado en tu nivel de subscripción y sujeto a los Términos del Servicio.

    \n

    Los detalles de tu subscripción son los siguientes:

    \n Nombre: {name}
    \n Nombre de compañia u organización: {company}
    \n Cantidad de usuarios: {users}
    \n Licencia emitida por: {issued}
    \n Inicio de la licencia: {start}
    \n Fecha de expiración: {expires}
    \n LDAP: {ldap}
    ", + "admin.license.key": "Llave de la Licencia: ", + "admin.license.keyRemove": "Remover la Licencia Enterprise y Degradar el Servidor", + "admin.license.removing": "Removiendo Licencia...", + "admin.license.teamEdition": "Mattermost Edición Team. Diseñado para equipos desde 5 hasta 50 usuarios.", + "admin.license.teamType": "

    Esta versión compilada de la plataforma de Mattermost es proporcionada bajo la licencia MIT.

    \n

    Lea MIT-COMPILED-LICENSE.txt en el directorio raíz de la instalación para más detalles. Lea NOTICES.txt para información sobre software libre utilizado en este sistema.

    ", + "admin.license.title": "Edición y Licencia", + "admin.license.type": "Licencia: ", + "admin.license.upload": "Subir", + "admin.license.uploadDesc": "Subir una llave de licencia de Mattermost Edición Enterprise para mejorar este servidor. Visitanos en línea\n para conocer más acerca de los beneficios de la Edición Enterprise or para comprar una licencia.", + "admin.license.uploading": "Subiendo Licencia...", + "admin.log.consoleDescription": "Normalmente asignado en falso en producción. Los desarolladores pueden configurar este campo en verdadero para ver de mensajes de consola basado en las opciones de nivel configuradas. Si es verdadera, el servidor escribirá los mensajes en una salida estandar (stdout).", + "admin.log.consoleTitle": "Mostrar registros en la consola: ", + "admin.log.false": "falso", + "admin.log.fileDescription": "Normalmente asignado en verdadero en producción. Cueando es verdadero, los archivos de registro son escritos en la ubicación especificada a continuación.", + "admin.log.fileLevelDescription": "Esta configuración determina el nivel de detalle con el cual los eventos serán escritos en el archivo de registro. ERROR: Sólo salida de mensajes de error. INFO: Salida de mensaje de error y información acerca de la partida e inicialización. DEBUG: Muestra un alto detalle para que los desarolladores que trabajan con eventos de depuración.", + "admin.log.fileLevelTitle": "Nivel registro:", + "admin.log.fileTitle": "Archivos de registro: ", + "admin.log.formatDateLong": "Fecha (2006/01/02)", + "admin.log.formatDateShort": "Fecha (01/02/06)", + "admin.log.formatDescription": "Formato del mensaje de registro de salida. Si es blanco se establecerá en \"[%D %T] [%L] %M\", donde:", + "admin.log.formatLevel": "Nivel (DEBG, INFO, EROR)", + "admin.log.formatMessage": "Mensaje", + "admin.log.formatPlaceholder": "Ingresar tu formato de archivo", + "admin.log.formatSource": "Fuente", + "admin.log.formatTime": "Hora (15:04:05 MST)", + "admin.log.formatTitle": "Formato de archivo:", + "admin.log.levelDescription": "Esta configuración determina el nivel de detalle con el cual los eventos serán escritos en la consola. ERROR: Sólo salida de mensajes de error. INFO: Salida de mensaje de error y información acerca de la partida e inicialización. DEBUG: Muestra un alto detalle para que los desarolladores que trabajan con eventos de depuración.", + "admin.log.levelTitle": "Nivel de log de consola:", + "admin.log.locationDescription": "Archivo en el cual se escribirán los registros. Si lo dejas en blanco, será asignado de forma predeterminada ./logs/mattermost, lo que escribirá los registros a mattermost.log. La rotación de los registros está habilitada y cada 10,000 lineas de registro se escriben en un nuevo archivo almacenado en el mismo directorio, por ejemplo mattermost.2015-09-23.001, mattermost.2015-09-23.002, y así sucesivamente.", + "admin.log.locationPlaceholder": "Ingresar locación de archivo", + "admin.log.locationTitle": "Ubicación de archivo:", + "admin.log.logSettings": "Configuración de registro", + "admin.log.save": "Guardar", + "admin.log.saving": "Guardando...", + "admin.log.true": "verdadero", + "admin.logs.reload": "Recargar", + "admin.logs.title": "Servidor de registros", "admin.nav.help": "Ayuda", "admin.nav.logout": "Cerrar sesión", "admin.nav.report": "Reportar problema", "admin.nav.switch": "Cambiar a {display_name}", + "admin.privacy.false": "falso", + "admin.privacy.save": "Guardar", + "admin.privacy.saving": "Guardando...", + "admin.privacy.showEmailDescription": "Cuando es falso, oculta la dirección de correo para otros usuarios en la interfaz de usuario, incluyendo a los dueños y administradores del grupo. Usado cuando el sistema es configurado para administrar grupos y donde algunos usuarios escogen mantener su información de contacto como privada.", + "admin.privacy.showEmailTitle": "Mostrar dirección de correo electrónico: ", + "admin.privacy.showFullNameDescription": "Cuando está asignado en falso, esconde el nombre completo de los usuarios para otros usuarios, incluyendo dueños de equipos y administradores de equipos. El nombre de usuario es mostrado en vez del nombre completo.", + "admin.privacy.showFullNameTitle": "Mostrar nombre completo: ", + "admin.privacy.title": "Configuraciones de privacidad", + "admin.privacy.true": "verdadero", + "admin.rate.enableLimiterDescription": "Cuando es verdadero, La APIs son reguladas a tasas especificadas a continuación.", + "admin.rate.enableLimiterTitle": "Habilitar el limitador de velocidad: ", + "admin.rate.false": "falso", + "admin.rate.httpHeaderDescription": "Al llenar este campo, se limita la velocidad según el encabezado HTTP especificado (e.j. cuando se configura con NGINX asigna \"X-Real-IP\", cuando se configura con AmazonELB asigna \"X-Forwarded-For\").", + "admin.rate.httpHeaderExample": "Ej \"X-Real-IP\", \"X-Forwarded-For\"", + "admin.rate.httpHeaderTitle": "Variar para encabezado HTTP:", + "admin.rate.memoryDescription": "Número máximo de sesiones de usuarios conectados en el sistema es determinado por \"Variar para dirección remota\" y \"Variar para encabezado HTTP\" en la configuración siguiente.", + "admin.rate.memoryExample": "Ej \"10000\"", + "admin.rate.memoryTitle": "Tamaño de memoria de almacenamiento:", + "admin.rate.noteDescription": "Cambiando propiedades en esta sección el servidor necesitará reiniciar antes de que los cambios tomen efecto.", + "admin.rate.noteTitle": "Nota:", + "admin.rate.queriesDescription": "Regulador de solicitudes API por sgundos.", + "admin.rate.queriesExample": "Ej \"10\"", + "admin.rate.queriesTitle": "Número de consultas por minuto:", + "admin.rate.remoteDescription": "Cuando es verdadero, límite de velocidad para el accedo a la API desde dirección IP.", + "admin.rate.remoteTitle": "Variar por direcciones remotas: ", + "admin.rate.save": "Guardar", + "admin.rate.saving": "Guardando...", + "admin.rate.title": "Configuración de velocidad", + "admin.rate.true": "verdadero", + "admin.reset_password.close": "Cerrar", + "admin.reset_password.newPassword": "Nueva contraseña", + "admin.reset_password.select": "Seleccionar", + "admin.reset_password.submit": "Por favor, introducir como mínimo {chars} caracteres.", + "admin.reset_password.title": "Reiniciar Contraseña", "admin.select_team.close": "Cerrar", "admin.select_team.select": "Seleccionar", "admin.select_team.selectTeam": "Seleccionar grupo", + "admin.service.attemptDescription": "Inicio de sesión permitidos antes que el usuario sea bloqueado y se requiera volver a configurar la contraseña vía correo electrónico.", + "admin.service.attemptExample": "Ej \"10\"", + "admin.service.attemptTitle": "Máximo de intentos de conexión:", + "admin.service.developerDesc": "(Opción de Desarrollador) Cuando está asignado en verdadero, información extra sobre errores se muestra en el UI.", + "admin.service.developerTitle": "Habilitar modo de Desarrollador: ", + "admin.service.false": "falso", + "admin.service.googleDescription": "Asigna una llave a este campo para habilitar la previsualización de videos de YouTube tomados de los enlaces que aparecen en los mensajes o comentarios. Las instrucciones de como obtener una llave está disponible en https://www.youtube.com/watch?v=Im69kzhpR3I. Al dejar este campo en blanco deshabilita la generación de previsualizaciones de videos de YouTube desde los enlaces.", + "admin.service.googleExample": "Ej \"7rAh6iwQCkV4cA1Gsg3fgGOXJAQ43QV\"", + "admin.service.googleTitle": "Llave de desarrolador Google:", + "admin.service.iconDescription": "Cuando es verdadero, se le permitirá cambiar el icono del mensaje desde webhooks. Nota, en combinación con permitir el cambio de nombre de usuario, podría exponer a los usuarios a sufrir ataques de phishing.", + "admin.service.iconTitle": "Habilitar el cambio de icono desde los Webhooks: ", + "admin.service.listenAddress": "Dirección de escucha:", + "admin.service.listenDescription": "La dirección a la que se unirá y escuchará. Ingresar \":8065\" se podrá unir a todas las interfaces o podrá seleccionar una como ej: \"127.0.0.1:8065\". Cambiando este valor es necesario reiniciar el servidor.", + "admin.service.listenExample": "Ej \":8065\"", + "admin.service.mobileSessionDays": "Duración de la Sesión en Días para Dispositivos Moviles:", + "admin.service.mobileSessionDaysDesc": "La sesión nativa de los dispositivos moviles expirará luego de transcurrido el numero de días especificado y se solicitará al usuario que inicie sesión nuevamente.", + "admin.service.outWebhooksDesc": "Cuando es verdadero, los webhooks de salida serán permitidos.", + "admin.service.outWebhooksTitle": "Habilitar Webhooks de Salida: ", + "admin.service.overrideDescription": "Cuando es verdadero, se le permitirá cambiar el nombre de usuario desde webhooks. Nota, en conjunto con cambio de icono, podría exponer a los usuarios a sufrir ataques de phishing.", + "admin.service.overrideTitle": "Habilitar el cambio de nombres de usuario desde los Webhooks: ", + "admin.service.save": "Guardar", + "admin.service.saving": "Guardando....", + "admin.service.securityDesc": "Cuando es verdadero, Los Administradores del Sistema serán notificados por correo electrónico se han anunciado alertas de seguridad relevantes en las últimas 12 horas. Requiere que los correos estén habilitados.", + "admin.service.securityTitle": "Habilitar Alertas de Seguridad: ", + "admin.service.segmentDescription": "Para usuarios que corren en servicios SaaS, registarse en Segment.com para obtener su llave.", + "admin.service.segmentExample": "Ej \"g3fgGOXJAQ43QV7rAh6iwQCkV4cA1Gs\"", + "admin.service.segmentTitle": "Llave de segmento para desarollador:", + "admin.service.sessionCache": "Duración del Cache de la Sesión en Minutos:", + "admin.service.sessionCacheDesc": "La cantidad de minutes que el cache de la sesión se guardará en memoria.", + "admin.service.sessionDaysEx": "Ej \"30\"", + "admin.service.ssoSessionDays": "Duración de la Sesión en Días para SSO:", + "admin.service.ssoSessionDaysDesc": "Las sesión expirará lugo de transcurrido el numero de días especificado y se solicitará al usuario que inicie sesión nuevamente.", + "admin.service.testingDescription": "(Opción de desarollo) Cuando es verdadero, /pruebadecarga el comando slash es habilitado para cargar el nombre de la cuenta y probar la data. Cambiando esto será necesario reiniciar el servidor para que haga efecto.", + "admin.service.testingTitle": "Habilitar Pruebas: ", + "admin.service.title": "Configuracion de servicios", + "admin.service.true": "verdadero", + "admin.service.webSessionDays": "Duración de la Sesión en Días para Web:", + "admin.service.webSessionDaysDesc": "La sesión web expirará luego de transcurrido el número de días especificado y se solicitará al usuaio que inicie sesión nuevamente.", + "admin.service.webhooksDescription": "Cuando es verdadero, la entradas de webhooks será permitida. Para ayudar a combatir ataques phishing, todos los comentarios de webhooks serán marcados con una etiqueta BOT.", + "admin.service.webhooksTitle": "Habilitar Webhooks de Entrada: ", "admin.sidebar.addTeamSidebar": "Agregar un equipo el menú lateral", "admin.sidebar.email": "Configuración de correo", "admin.sidebar.file": "Configuracion de archivos", @@ -82,6 +319,83 @@ "admin.sidebar.users": "- Usuarios", "admin.sidebar.view_statistics": "Ver Estadísticas", "admin.sidebarHeader.systemConsole": "Consola de sistema", + "admin.sql.dataSource": "Origen de datos:", + "admin.sql.driverName": "Nombre de controlador:", + "admin.sql.false": "falso", + "admin.sql.keyDescription": "32-caracter disponible para encriptar y desincriptar campos sencible de la base de datos.", + "admin.sql.keyExample": "Ej \"gxHVDcKUyP2y1eiyW8S8na1UYQAfq6J6\"", + "admin.sql.keyTitle": "At Rest Encrypt Key:", + "admin.sql.maxConnectionsDescription": "Número máximo de conecciones inactivas que mantiene abierta la base de datos.", + "admin.sql.maxConnectionsExample": "Ej \"10\"", + "admin.sql.maxConnectionsTitle": "Maxímo de coneciones inactivas:", + "admin.sql.maxOpenDescription": "Número máximo de conexiones abiertas y retenidas en la base de datos.", + "admin.sql.maxOpenExample": "Ej \"10\"", + "admin.sql.maxOpenTitle": "Máximo de conexiones abiertas:", + "admin.sql.noteDescription": "Cambiando las propiedades de esta sección se requerirá reiniciar el servidor para que los cambios tomen efecto", + "admin.sql.noteTitle": "Nota:", + "admin.sql.regenerate": "Regenerar", + "admin.sql.replicas": "Origen de datos de réplica:", + "admin.sql.save": "Guardar", + "admin.sql.saving": "Guardando...", + "admin.sql.title": "Configuración de SQL", + "admin.sql.traceDescription": "(Modo desarrolador) Cuando es verdadero, la ejecución de sentencias SQL se escriben en el registro.", + "admin.sql.traceTitle": "Traza: ", + "admin.sql.true": "verdadero", + "admin.sql.warning": "Precaución: re-generando esto puede causar que algunas columnas de la base de datos retornen resultados vacíos.", + "admin.support.aboutDesc": "Enlace para la página de Acerca que contiene más información sobre Mattermost, por ejemplo el propósito y audiencia dentro de la organización. De forma predeterminada apunta a la página de información de Mattermost.", + "admin.support.aboutTitle": "Enlace de Acerca:", + "admin.support.emailHelp": "El correo electrónico mostrado durante el tutorial para que los usuarios puedan realizar preguntas de soporte.", + "admin.support.emailTitle": "Correo electrónico de Soporte:", + "admin.support.helpDesc": "Enlace con la documentación de ayuda para el equipo desde el menú principal. Normalmente no cambia a menos que tu organización decida crear una documentación personalizada.", + "admin.support.helpTitle": "Enlace de Ayuda:", + "admin.support.privacyDesc": "Enlace para las políticas de Privacidad disponible para los usuarios en versión de escritorio y movil. Al dejarlo en blanco esconderá la opción que muestra el aviso.", + "admin.support.privacyTitle": "Enlace de políticas de Privacidad:", + "admin.support.problemDesc": "Enlace con la documentación de ayuda para el equipo desde el menú principal. Como predeterminado esto apunta a un foro de ayuda donde los usuarios pueden buscar, encontrar y solicitar ayuda sobre temas técnicos.", + "admin.support.problemTitle": "Enlace de Reportar un Problema:", + "admin.support.save": "Guradar", + "admin.support.saving": "Guardando...", + "admin.support.termsDesc": "Enlace para los Terminos y Condiciones disponible para los usuarios en versión de escritorio y movil. Al dejarlo en blanco esconderá la opción que muestra el aviso.", + "admin.support.termsTitle": "Enlace de Terminos y Condiciones:", + "admin.support.title": "Configuración de Soporte", + "admin.system_analytics.activeUsers": "Usuarios Activos con Mensajes", + "admin.system_analytics.title": "el Sistema", + "admin.system_analytics.totalPosts": "Total de Mensajes", + "admin.team.dirDesc": "Cuando es verdadero, Los equipos que esten configurados para mostrarse en el directorio de equipos se mostrarán en vez de crear un nuevo equipo.", + "admin.team.dirTitle": "Habilitar Directorio de Equipos: ", + "admin.team.false": "falso", + "admin.team.maxUsersDescription": "Número máximo de usuarios por equipo, incluyendo usuarios activos e inactivos.", + "admin.team.maxUsersExample": "Ej \"25\"", + "admin.team.maxUsersTitle": "Máximo de usuarios por equipo:", + "admin.team.restrictDescription": "Equipos y las cuentas de usuario sólo pueden ser creadas para dominios especificos (ej. \"mattermost.org\") o una lista de dominios separado por comas (ej. \"corp.mattermost.com, mattermost.org\").", + "admin.team.restrictExample": "Ej \"corp.mattermost.com, mattermost.org\"", + "admin.team.restrictNameDesc": "Cuando es verdadero, No puedes crear equipos cuyo nombre tenga palabras reservadas como: www, admin, support, test, channel, etc", + "admin.team.restrictNameTitle": "Restringir Nombre de los Equipos: ", + "admin.team.restrictTitle": "Restringir la creación de dominios:", + "admin.team.save": "Guardar", + "admin.team.saving": "Guardando...", + "admin.team.siteNameDescription": "Nombre de servicios mostrados en pantalla login y UI.", + "admin.team.siteNameExample": "Ex \"Mattermost\"", + "admin.team.siteNameTitle": "Nombre de sitio:", + "admin.team.teamCreationDescription": "Cuando es falso, la posibilidad de crear equipos es deshabilitada. El botón crear equipo arrojará error cuando sea presionado.", + "admin.team.teamCreationTitle": "Habilitar Creación de Equipos: ", + "admin.team.title": "Configuración de equipo", + "admin.team.true": "Verdadero", + "admin.team.userCreationDescription": "Cuando es falso, a posibilidad de crear cuentas es deshabilitada. El botón crear cuentas arrojará error cuando sea presionado.", + "admin.team.userCreationTitle": "Habilitar Creación de Usuarios: ", + "admin.team_analytics.activeUsers": "Active Users With Posts", + "admin.team_analytics.totalPosts": "Total de Mensajes", + "admin.userList.title": "Usuarios para {team}", + "admin.userList.title2": "Usuarios para {team} ({count})", + "admin.user_item.inactive": "Inactivo", + "admin.user_item.makeActive": "Activar", + "admin.user_item.makeInactive": "Inactivar", + "admin.user_item.makeMember": "Hacer Miembro", + "admin.user_item.makeSysAdmin": "Hacer Admin del Sistema", + "admin.user_item.makeTeamAdmin": "Hacer Admin de Equipo", + "admin.user_item.member": "Meiembro", + "admin.user_item.resetPwd": "Reiniciar Contraseña", + "admin.user_item.sysAdmin": "Admin de Sistema", + "admin.user_item.teamAdmin": "Admin de Equipo", "error_bar.preview_mode": "Modo de prueba: Las notificaciones por correo electrónico no han sido configuradas", "loading_screen.loading": "Cargando" } \ No newline at end of file -- cgit v1.2.3-1-g7c22 From fb4afee92c0e14ecdb5dc1889fc5e45f329a785a Mon Sep 17 00:00:00 2001 From: Elias Nahum Date: Wed, 27 Jan 2016 16:06:22 -0300 Subject: PLT-7: Missing server translations --- i18n/es.json | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/i18n/es.json b/i18n/es.json index 70802b07a..c4e7a552c 100644 --- a/i18n/es.json +++ b/i18n/es.json @@ -579,6 +579,10 @@ "id": "api.license.add_license.invalid.app_error", "translation": "Archivo de licencia inválido." }, + { + "id": "api.license.add_license.invalid_count.app_error", + "translation": "No se pudo obtener el número total de usuarios únicos." + }, { "id": "api.license.add_license.no_file.app_error", "translation": "No hay un archivo bajo 'license' en la solicitud" @@ -591,6 +595,10 @@ "id": "api.license.add_license.save.app_error", "translation": "La licencia no fue guardada correctamente." }, + { + "id": "api.license.add_license.unique_users.app_error", + "translation": "Esta licencia sólo soporta {{.Users}} usuarios, cuando tu sistema tiene {{.Count}} usuarios únicos. Los usuarios únicos se cuentan por direcciónes de correo electrónico distintas. Puedes ver el totoal de usuarios en REPORTES DEL SITIO -> Ver Estadísticas." + }, { "id": "api.license.init.debug", "translation": "Inicializando rutas del API para las licencias" @@ -1563,6 +1571,10 @@ "id": "api.user.upload_profile_user.too_large.app_error", "translation": "No se pudo actualizar la imagen del perfil. El archivo es muy grande." }, + { + "id": "api.user.upload_profile_user.upload_profile.app_error", + "translation": "No se pudo subir la imagen del perfil" + }, { "id": "api.web_conn.new_web_conn.last_activity.error", "translation": "Falla al actualizar LastActivityAt para user_id=%v and session_id=%v, err=%v" @@ -2763,6 +2775,10 @@ "id": "store.sql_session.save.existing.app_error", "translation": "No se puede actualizar la sesión" }, + { + "id": "store.sql_session.update_device_id.app_error", + "translation": "No pudimos actualizar el id del dispositivo" + }, { "id": "store.sql_session.update_last_activity.app_error", "translation": "No pudimos actualizar el campo last_activity_at" -- cgit v1.2.3-1-g7c22 From 16707836ac5b624ee0a4cf1ff9610e25a54f6675 Mon Sep 17 00:00:00 2001 From: hmhealey Date: Wed, 27 Jan 2016 16:31:24 -0500 Subject: Updated makefile to repackage client libraries when package.json changes --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 1012b786e..9c4e6ee1f 100644 --- a/Makefile +++ b/Makefile @@ -255,7 +255,7 @@ nuke: | clean clean-docker touch $@ -.prepare-jsx: +.prepare-jsx: web/react/package.json @echo Preparation for compiling jsx code cd web/react/ && npm install -- cgit v1.2.3-1-g7c22 From a72ba140240266b03558dba716e6f4815392d491 Mon Sep 17 00:00:00 2001 From: Elias Nahum Date: Thu, 28 Jan 2016 11:21:12 -0300 Subject: PLT-7: Refactoring frontend (chunk 3) - Tutorial components - Claim components - Suggestion components --- web/react/components/claim/claim_account.jsx | 11 +++- web/react/components/claim/email_to_sso.jsx | 61 +++++++++++++++--- web/react/components/claim/sso_to_email.jsx | 72 +++++++++++++++++++--- .../components/suggestion/at_mention_provider.jsx | 16 ++++- .../suggestion/search_suggestion_list.jsx | 17 ++++- .../components/tutorial/tutorial_intro_screens.jsx | 65 ++++++++++++++----- web/react/components/tutorial/tutorial_tip.jsx | 25 +++++++- web/static/i18n/en.json | 35 ++++++++++- web/static/i18n/es.json | 39 +++++++++++- 9 files changed, 297 insertions(+), 44 deletions(-) diff --git a/web/react/components/claim/claim_account.jsx b/web/react/components/claim/claim_account.jsx index f38f558db..87026b762 100644 --- a/web/react/components/claim/claim_account.jsx +++ b/web/react/components/claim/claim_account.jsx @@ -4,6 +4,8 @@ import EmailToSSO from './email_to_sso.jsx'; import SSOToEmail from './sso_to_email.jsx'; +import {FormattedMessage} from 'mm-intl'; + export default class ClaimAccount extends React.Component { constructor(props) { super(props); @@ -13,7 +15,14 @@ export default class ClaimAccount extends React.Component { render() { let content; if (this.props.email === '') { - content =

    {'No email specified.'}

    ; + content = ( +

    + +

    + ); } else if (this.props.currentType === '' && this.props.newType !== '') { content = (
    -

    {'Switch Email/Password Account to ' + uiType}

    +

    + +

    -

    {'Upon claiming your account, you will only be able to login with ' + Utils.toTitleCase(this.props.type) + ' SSO.'}

    -

    {'Enter the password for your ' + this.props.teamDisplayName + ' ' + global.window.mm_config.SiteName + ' account.'}

    +

    + +

    +

    + +

    @@ -78,7 +116,13 @@ export default class EmailToSSO extends React.Component { type='submit' className='btn btn-primary' > - {'Switch account to ' + uiType} +
    @@ -90,8 +134,11 @@ export default class EmailToSSO extends React.Component { EmailToSSO.defaultProps = { }; EmailToSSO.propTypes = { + intl: intlShape.isRequired, type: React.PropTypes.string.isRequired, email: React.PropTypes.string.isRequired, teamName: React.PropTypes.string.isRequired, teamDisplayName: React.PropTypes.string.isRequired }; + +export default injectIntl(EmailToSSO); \ No newline at end of file diff --git a/web/react/components/claim/sso_to_email.jsx b/web/react/components/claim/sso_to_email.jsx index 0868b7f2f..73ff13cc9 100644 --- a/web/react/components/claim/sso_to_email.jsx +++ b/web/react/components/claim/sso_to_email.jsx @@ -4,7 +4,28 @@ import * as Utils from '../../utils/utils.jsx'; import * as Client from '../../utils/client.jsx'; -export default class SSOToEmail extends React.Component { +import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl'; + +const holders = defineMessages({ + enterPwd: { + id: 'claim.sso_to_email.enterPwd', + defaultMessage: 'Please enter a password.' + }, + pwdNotMatch: { + id: 'claim.sso_to_email.pwdNotMatch', + defaultMessage: 'Password do not match.' + }, + newPwd: { + id: 'claim.sso_to_email.newPwd', + defaultMessage: 'New Password' + }, + confirm: { + id: 'claim.sso_to_email.confirm', + defaultMessage: 'Confirm Password' + } +}); + +class SSOToEmail extends React.Component { constructor(props) { super(props); @@ -13,19 +34,20 @@ export default class SSOToEmail extends React.Component { this.state = {}; } submit(e) { + const {formatMessage} = this.props.intl; e.preventDefault(); const state = {}; const password = ReactDOM.findDOMNode(this.refs.password).value.trim(); if (!password) { - state.error = 'Please enter a password.'; + state.error = formatMessage(holders.enterPwd); this.setState(state); return; } const confirmPassword = ReactDOM.findDOMNode(this.refs.passwordconfirm).value.trim(); if (!confirmPassword || password !== confirmPassword) { - state.error = 'Passwords do not match.'; + state.error = formatMessage(holders.pwdNotMatch); this.setState(state); return; } @@ -50,6 +72,7 @@ export default class SSOToEmail extends React.Component { ); } render() { + const {formatMessage} = this.props.intl; var error = null; if (this.state.error) { error =
    ; @@ -65,17 +88,39 @@ export default class SSOToEmail extends React.Component { return (
    -

    {'Switch ' + uiType + ' Account to Email'}

    +

    + +

    -

    {'Upon changing your account type, you will only be able to login with your email and password.'}

    -

    {'Enter a new password for your ' + this.props.teamDisplayName + ' ' + global.window.mm_config.SiteName + ' account.'}

    +

    + +

    +

    + +

    @@ -85,7 +130,7 @@ export default class SSOToEmail extends React.Component { className='form-control' name='passwordconfirm' ref='passwordconfirm' - placeholder='Confirm Password' + placeholder={formatMessage(holders.confirm)} spellCheck='false' />
    @@ -94,7 +139,13 @@ export default class SSOToEmail extends React.Component { type='submit' className='btn btn-primary' > - {'Switch ' + uiType + ' account to email and password'} +
    @@ -106,8 +157,11 @@ export default class SSOToEmail extends React.Component { SSOToEmail.defaultProps = { }; SSOToEmail.propTypes = { + intl: intlShape.isRequired, currentType: React.PropTypes.string.isRequired, email: React.PropTypes.string.isRequired, teamName: React.PropTypes.string.isRequired, teamDisplayName: React.PropTypes.string.isRequired }; + +export default injectIntl(SSOToEmail); \ No newline at end of file diff --git a/web/react/components/suggestion/at_mention_provider.jsx b/web/react/components/suggestion/at_mention_provider.jsx index e502c981d..50231ad15 100644 --- a/web/react/components/suggestion/at_mention_provider.jsx +++ b/web/react/components/suggestion/at_mention_provider.jsx @@ -5,6 +5,8 @@ import SuggestionStore from '../../stores/suggestion_store.jsx'; import UserStore from '../../stores/user_store.jsx'; import * as Utils from '../../utils/utils.jsx'; +import {FormattedMessage} from 'mm-intl'; + const MaxUserSuggestions = 40; class AtMentionSuggestion extends React.Component { @@ -16,11 +18,21 @@ class AtMentionSuggestion extends React.Component { let icon; if (item.username === 'all') { username = 'all'; - description = 'Notifies everyone in the team'; + description = ( + + ); icon = ; } else if (item.username === 'channel') { username = 'channel'; - description = 'Notifies everyone in the channel'; + description = ( + + ); icon = ; } else { username = item.username; diff --git a/web/react/components/suggestion/search_suggestion_list.jsx b/web/react/components/suggestion/search_suggestion_list.jsx index 3378a33a0..40f5d8777 100644 --- a/web/react/components/suggestion/search_suggestion_list.jsx +++ b/web/react/components/suggestion/search_suggestion_list.jsx @@ -3,7 +3,8 @@ import Constants from '../../utils/constants.jsx'; import SuggestionList from './suggestion_list.jsx'; -import * as Utils from '../../utils/utils.jsx'; + +import {FormattedMessage} from 'mm-intl'; export default class SearchSuggestionList extends SuggestionList { componentDidUpdate(prevProps, prevState) { @@ -19,9 +20,19 @@ export default class SearchSuggestionList extends SuggestionList { renderChannelDivider(type) { let text; if (type === Constants.OPEN_CHANNEL) { - text = 'Public ' + Utils.getChannelTerm(type) + 's'; + text = ( + + ); } else { - text = 'Private ' + Utils.getChannelTerm(type) + 's'; + text = ( + + ); } return ( diff --git a/web/react/components/tutorial/tutorial_intro_screens.jsx b/web/react/components/tutorial/tutorial_intro_screens.jsx index 7ab1e5512..78cfb7b60 100644 --- a/web/react/components/tutorial/tutorial_intro_screens.jsx +++ b/web/react/components/tutorial/tutorial_intro_screens.jsx @@ -9,6 +9,9 @@ import * as Utils from '../../utils/utils.jsx'; import * as AsyncClient from '../../utils/async_client.jsx'; import Constants from '../../utils/constants.jsx'; + +import {FormattedMessage, FormattedHTMLMessage} from 'mm-intl'; + const Preferences = Constants.Preferences; const NUM_SCREENS = 3; @@ -61,10 +64,13 @@ export default class TutorialIntroScreens extends React.Component { return (
    -

    {'Welcome to:'}

    -

    {'Mattermost'}

    -

    {'Your team communication all in one place, instantly searchable and available anywhere.'}

    -

    {'Keep your team connected to help them achieve what matters most.'}

    + {circles}
    ); @@ -74,9 +80,12 @@ export default class TutorialIntroScreens extends React.Component { return (
    -

    {'How Mattermost works:'}

    -

    {'Communication happens in public discussion channels, private groups and direct messages.'}

    -

    {'Everything is archived and searchable from any web-enabled desktop, laptop or phone.'}

    + {circles}
    ); @@ -92,7 +101,10 @@ export default class TutorialIntroScreens extends React.Component { data-toggle='modal' data-target='#invite_member' > - {'Invite teammates'} + ); } else { @@ -105,7 +117,10 @@ export default class TutorialIntroScreens extends React.Component { data-title='Team Invite' data-value={Utils.getWindowLocationOrigin() + '/signup_user_complete/?id=' + team.id} > - {'Invite teammates'} + ); } @@ -116,7 +131,10 @@ export default class TutorialIntroScreens extends React.Component { if (global.window.mm_config.SupportEmail) { supportInfo = (

    - {'Need anything, just email us at '} + -

    {'You’re all set'}

    +

    + +

    {inviteModalLink} - {' when you’re ready.'} +

    {supportInfo} - {'Click “Next” to enter Town Square. This is the first channel teammates see when they sign up. Use it for posting updates everyone needs to know.'} + {circles}
    ); @@ -186,14 +215,20 @@ export default class TutorialIntroScreens extends React.Component { tabIndex='1' onClick={this.handleNext} > - {'Next'} + - {'Skip tutorial'} + diff --git a/web/react/components/tutorial/tutorial_tip.jsx b/web/react/components/tutorial/tutorial_tip.jsx index 03ecdabab..3bab7570a 100644 --- a/web/react/components/tutorial/tutorial_tip.jsx +++ b/web/react/components/tutorial/tutorial_tip.jsx @@ -6,6 +6,9 @@ import PreferenceStore from '../../stores/preference_store.jsx'; import * as AsyncClient from '../../utils/async_client.jsx'; import Constants from '../../utils/constants.jsx'; + +import {FormattedMessage} from 'mm-intl'; + const Preferences = Constants.Preferences; const Overlay = ReactBootstrap.Overlay; @@ -46,7 +49,17 @@ export default class TutorialTip extends React.Component { AsyncClient.savePreferences([preference]); } render() { - const buttonText = this.state.currentScreen === this.props.screens.length - 1 ? 'Okay' : 'Next'; + const buttonText = this.state.currentScreen === this.props.screens.length - 1 ? ( + + ) : ( + + ); const dots = []; if (this.props.screens.length > 1) { @@ -111,12 +124,18 @@ export default class TutorialTip extends React.Component { {buttonText}
    - {'Seen this before? '} + - {'Opt out of these tips.'} +
    diff --git a/web/static/i18n/en.json b/web/static/i18n/en.json index a713be74c..ae302b86e 100644 --- a/web/static/i18n/en.json +++ b/web/static/i18n/en.json @@ -396,6 +396,39 @@ "admin.user_item.makeActive": "Make Active", "admin.user_item.makeInactive": "Make Inactive", "admin.user_item.resetPwd": "Reset Password", + "claim.account.noEmail": "No email specified", + "claim.email_to_sso.pwdError": "Please enter your password.", + "claim.email_to_sso.pwd": "Password", + "claim.email_to_sso.title": "Switch Email/Password Account to {uiType}", + "claim.email_to_sso.ssoType": "Upon claiming your account, you will only be able to login with {type} SSO", + "claim.email_to_sso.enterPwd": "Enter the password for your {team} {site} account", + "claim.email_to_sso.switchTo": "Switch account to {uiType}", + "claim.sso_to_email.enterPwd": "Please enter a password.", + "claim.sso_to_email.pwdNotMatch": "Password do not match.", + "claim.sso_to_email.newPwd": "New Password", + "claim.sso_to_email.confirm": "Confirm Password", + "claim.sso_to_email.title": "Switch {type} Account to Email", + "claim.sso_to_email.description": "Upon changing your account type, you will only be able to login with your email and password.", + "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", - "loading_screen.loading": "Loading" + "loading_screen.loading": "Loading", + "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", + "tutorial_intro.screenOne": "

    Welcome to:

    \n

    Mattermost

    \n

    Your team communication all in one place, instantly searchable and available anywhere

    \n

    Keep your team connected to help them achieve what matters most.

    ", + "tutorial_intro.screenTwo": "

    How Mattermost works:

    \n

    Communication happens in public discussion channels, private groups and direct messages.

    \n

    Everything is archived and searchable from any web-enabled desktop, laptop or phone.

    ", + "tutorial_intro.invite": "Invite teammates", + "tutorial_intro.teamInvite": "Team Invite", + "tutorial_intro.support": "Need anything, just email us at ", + "tutorial_intro.allSet": "You’re all set", + "tutorial_intro.whenReady": " when you’re ready.", + "tutorial_intro.end": "Click “Next” to enter Town Square. This is the first channel teammates see when they sign up. Use it for posting updates everyone needs to know.", + "tutorial_intro.next": "Next", + "tutorial_intro.skip": "Skip tutorial", + "tutorial_tip.ok": "Okay", + "tutorial_tip.next": "Next", + "tutorial_tip.seen": "Seen this before? ", + "tutorial_tip.out": "Opt out of these tips." } \ No newline at end of file diff --git a/web/static/i18n/es.json b/web/static/i18n/es.json index 45f8754bd..12f9b5fc5 100644 --- a/web/static/i18n/es.json +++ b/web/static/i18n/es.json @@ -250,7 +250,7 @@ "admin.reset_password.close": "Cerrar", "admin.reset_password.newPassword": "Nueva contraseña", "admin.reset_password.select": "Seleccionar", - "admin.reset_password.submit": "Por favor, introducir como mínimo {chars} caracteres.", + "admin.reset_password.submit": "Por favor, introducir como mínimo 5 caracteres.", "admin.reset_password.title": "Reiniciar Contraseña", "admin.select_team.close": "Cerrar", "admin.select_team.select": "Seleccionar", @@ -384,7 +384,7 @@ "admin.team.userCreationTitle": "Habilitar Creación de Usuarios: ", "admin.team_analytics.activeUsers": "Active Users With Posts", "admin.team_analytics.totalPosts": "Total de Mensajes", - "admin.userList.title": "Usuarios para {team}", + "admin.userList.title": "Usuarios para ", "admin.userList.title2": "Usuarios para {team} ({count})", "admin.user_item.inactive": "Inactivo", "admin.user_item.makeActive": "Activar", @@ -396,6 +396,39 @@ "admin.user_item.resetPwd": "Reiniciar Contraseña", "admin.user_item.sysAdmin": "Admin de Sistema", "admin.user_item.teamAdmin": "Admin de Equipo", + "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", + "claim.email_to_sso.pwdError": "Por favor introduce tu contraseña.", + "claim.email_to_sso.ssoType": "Al reclamar tu cuenta, sólo podrás iniciar sesión con {type} SSO", + "claim.email_to_sso.switchTo": "Cambiar cuenta a ", + "claim.email_to_sso.title": "Cambiar Cuenta de Correo/Contraseña a ", + "claim.sso_to_email.confirm": "Confirmar Contraseña", + "claim.sso_to_email.description": "Al cambiar el tipo de cuenta, sólo podrás iniciar sesión con tu correo electrónico y contraseña.", + "claim.sso_to_email.enterPwd": "Por favor ingresa una contraseña.", + "claim.sso_to_email.newPwd": "Nueva Contraseña", + "claim.sso_to_email.pwdNotMatch": "Las contraseñas no coinciden.", + "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}", "error_bar.preview_mode": "Modo de prueba: Las notificaciones por correo electrónico no han sido configuradas", - "loading_screen.loading": "Cargando" + "loading_screen.loading": "Cargando", + "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", + "suggestion.search.public": "Canales Públicos", + "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.screenOne": "

    Bienvenido a:

    Mattermost

    Las comunicaciones de tu equipo en un solo lugar, con búsquedas instantáneas y disponible desde donde sea.

    Mantén a tu equipo conectado para ayudarlos a conseguir lo que realmente importa.

    ", + "tutorial_intro.screenTwo": "

    Cómo funciona Mattermost:

    Las comunicaciones ocurren en los canales de discusión los cuales son públicos, o en grupos privados e incluso con mensajes privados.

    Todo lo que ocurre es archivado y se puede buscar en cualquier momento desde cualquier dispositivo con acceso a Mattermost.

    ", + "tutorial_intro.skip": "Saltar el tutorial", + "tutorial_intro.support": "Necesitas algo, escribemos a ", + "tutorial_intro.teamInvite": "Invitar a tu Equipo", + "tutorial_intro.whenReady": " cuando estés listo(a).", + "tutorial_tip.next": "Siguiente", + "tutorial_tip.ok": "Aceptar", + "tutorial_tip.out": "No optar por estos consejos.", + "tutorial_tip.seen": "¿Haz visto esto antes? " } \ No newline at end of file -- cgit v1.2.3-1-g7c22 From c7d9754f4b8ce8eefb0c9608cf6c2cec2a7d44fe Mon Sep 17 00:00:00 2001 From: =Corey Hulen Date: Thu, 28 Jan 2016 09:36:32 -0500 Subject: Adding english fallback to server --- utils/i18n.go | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/utils/i18n.go b/utils/i18n.go index 05154bd92..d545d18b8 100644 --- a/utils/i18n.go +++ b/utils/i18n.go @@ -44,7 +44,7 @@ func GetTranslationsBySystemLocale() i18n.TranslateFunc { panic("Failed to load system translations for '" + model.DEFAULT_LOCALE + "'") } - translations, _ := i18n.Tfunc(locale) + translations, _ := TfuncWithFallback(locale) if translations == nil { panic("Failed to load system translations") } @@ -58,22 +58,32 @@ func GetUserTranslations(locale string) i18n.TranslateFunc { locale = model.DEFAULT_LOCALE } - translations, _ := i18n.Tfunc(locale) + translations, _ := TfuncWithFallback(locale) return translations } func SetTranslations(locale string) i18n.TranslateFunc { - translations, _ := i18n.Tfunc(locale) + translations, _ := TfuncWithFallback(locale) return translations } func GetTranslationsAndLocale(w http.ResponseWriter, r *http.Request) (i18n.TranslateFunc, string) { headerLocale := strings.Split(strings.Split(r.Header.Get("Accept-Language"), ",")[0], "-")[0] if locales[headerLocale] != "" { - translations, _ := i18n.Tfunc(headerLocale) + translations, _ := TfuncWithFallback(headerLocale) return translations, headerLocale } - translations, _ := i18n.Tfunc(model.DEFAULT_LOCALE) + translations, _ := TfuncWithFallback(model.DEFAULT_LOCALE) return translations, model.DEFAULT_LOCALE } + +func TfuncWithFallback(pref string, prefs ...string) TranslateFunc { + t := i18n.MustTfunc(pref, prefs...) + return func(translationID string, args ...interface{}) string { + if translated := t(translationID, args...); translated != translationID { + return translated + } + return i18n.MustTfunc("en", args...) + } +} -- cgit v1.2.3-1-g7c22 From 5d92fda12d0b75bbbcb830d6aeb2f0faef1bc6e8 Mon Sep 17 00:00:00 2001 From: Asaad Mahmood Date: Thu, 28 Jan 2016 19:38:23 +0500 Subject: Fixing modals for IE --- web/react/components/new_channel_modal.jsx | 5 +++++ web/sass-files/sass/partials/_modal.scss | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/web/react/components/new_channel_modal.jsx b/web/react/components/new_channel_modal.jsx index 70fe10eef..9f733c476 100644 --- a/web/react/components/new_channel_modal.jsx +++ b/web/react/components/new_channel_modal.jsx @@ -22,6 +22,11 @@ export default class NewChannelModal extends React.Component { }); } } + componentDidMount() { + if (Utils.isBrowserIE()) { + $('body').addClass('browser--IE'); + } + } handleSubmit(e) { e.preventDefault(); diff --git a/web/sass-files/sass/partials/_modal.scss b/web/sass-files/sass/partials/_modal.scss index 7627f6a4c..b451adb75 100644 --- a/web/sass-files/sass/partials/_modal.scss +++ b/web/sass-files/sass/partials/_modal.scss @@ -10,6 +10,11 @@ .modal { width: 100%; color: #333; + body.browser--IE & { + .modal-dialog { + @include translateY(0); + } + } &.image_modal { .modal-backdrop.in { @include opacity(0.7); -- cgit v1.2.3-1-g7c22 From c7c30a063da5c45a66b0447c07c0089379c5a99b Mon Sep 17 00:00:00 2001 From: =Corey Hulen Date: Thu, 28 Jan 2016 09:51:37 -0500 Subject: Adding english fallback --- utils/i18n.go | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/utils/i18n.go b/utils/i18n.go index d545d18b8..e809ae883 100644 --- a/utils/i18n.go +++ b/utils/i18n.go @@ -44,7 +44,7 @@ func GetTranslationsBySystemLocale() i18n.TranslateFunc { panic("Failed to load system translations for '" + model.DEFAULT_LOCALE + "'") } - translations, _ := TfuncWithFallback(locale) + translations := TfuncWithFallback(locale) if translations == nil { panic("Failed to load system translations") } @@ -58,32 +58,34 @@ func GetUserTranslations(locale string) i18n.TranslateFunc { locale = model.DEFAULT_LOCALE } - translations, _ := TfuncWithFallback(locale) + translations := TfuncWithFallback(locale) return translations } func SetTranslations(locale string) i18n.TranslateFunc { - translations, _ := TfuncWithFallback(locale) + translations := TfuncWithFallback(locale) return translations } func GetTranslationsAndLocale(w http.ResponseWriter, r *http.Request) (i18n.TranslateFunc, string) { headerLocale := strings.Split(strings.Split(r.Header.Get("Accept-Language"), ",")[0], "-")[0] if locales[headerLocale] != "" { - translations, _ := TfuncWithFallback(headerLocale) + translations := TfuncWithFallback(headerLocale) return translations, headerLocale } - translations, _ := TfuncWithFallback(model.DEFAULT_LOCALE) + translations := TfuncWithFallback(model.DEFAULT_LOCALE) return translations, model.DEFAULT_LOCALE } -func TfuncWithFallback(pref string, prefs ...string) TranslateFunc { - t := i18n.MustTfunc(pref, prefs...) +func TfuncWithFallback(pref string) i18n.TranslateFunc { + t, _ := i18n.Tfunc(pref) return func(translationID string, args ...interface{}) string { if translated := t(translationID, args...); translated != translationID { return translated } - return i18n.MustTfunc("en", args...) + + t, _ := i18n.Tfunc("en") + return t(translationID, args...) } } -- cgit v1.2.3-1-g7c22 From 1f02b09dae64f895b79a45fc5e5da10dd381e446 Mon Sep 17 00:00:00 2001 From: Asaad Mahmood Date: Thu, 28 Jan 2016 20:07:25 +0500 Subject: Improving jump to recent messages UI --- web/react/components/center_panel.jsx | 6 ++---- web/sass-files/sass/partials/_content.scss | 1 + 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/web/react/components/center_panel.jsx b/web/react/components/center_panel.jsx index 7eef329c3..53dad1306 100644 --- a/web/react/components/center_panel.jsx +++ b/web/react/components/center_panel.jsx @@ -66,11 +66,9 @@ export default class CenterPanel extends React.Component { createPost = ( ) : 'Copy'; @@ -223,6 +221,7 @@ export default class PostInfo extends React.Component { />
  • + {replyLink}
    ", MENU_ICON: " ", COMMENT_ICON: " ", + REPLY_ICON: " ", 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..7d1210f64 100644 --- a/web/sass-files/sass/partials/_responsive.scss +++ b/web/sass-files/sass/partials/_responsive.scss @@ -92,7 +92,7 @@ } - .dropdown { + .dropdown, .post__reply { visibility: visible; } -- cgit v1.2.3-1-g7c22 From ce2ec34e585bd5f9c319936ae66fe764dabac0e3 Mon Sep 17 00:00:00 2001 From: Asaad Mahmood Date: Fri, 29 Jan 2016 00:04:42 +0500 Subject: Adding reply icon --- web/react/components/post_info.jsx | 8 +++++- web/sass-files/sass/partials/_responsive.scss | 38 ++++++++++++++++++++------- 2 files changed, 36 insertions(+), 10 deletions(-) diff --git a/web/react/components/post_info.jsx b/web/react/components/post_info.jsx index 2bd1a57f7..73b47024c 100644 --- a/web/react/components/post_info.jsx +++ b/web/react/components/post_info.jsx @@ -24,8 +24,14 @@ export default class PostInfo extends React.Component { } createReplyLink() { if (this.props.allowReply === 'true') { + var hideReply = ''; + + if (this.props.commentCount >= 1) { + hideReply = ' post__reply--hide'; + } + return ( -
    +
    Date: Thu, 28 Jan 2016 16:16:39 -0300 Subject: PLT-7: Refactoring frontend (chunk 4) --- doc/help/Messaging_es.md | 37 +++++++++++ i18n/en.json | 32 +++++----- i18n/es.json | 4 ++ web/react/components/authorize.jsx | 42 +++++++++++-- web/react/components/docs.jsx | 2 +- web/react/components/find_team.jsx | 65 ++++++++++++++++--- web/react/components/login.jsx | 76 +++++++++++++++++++---- web/react/components/login_email.jsx | 49 ++++++++++++--- web/react/components/login_ldap.jsx | 48 +++++++++++--- web/react/components/password_reset_form.jsx | 64 ++++++++++++++++--- web/react/components/password_reset_send_link.jsx | 63 ++++++++++++++++--- web/react/components/signup_team.jsx | 43 +++++++++++-- web/react/components/team_signup_choose_auth.jsx | 42 +++++++++++-- web/react/components/team_signup_with_email.jsx | 34 ++++++++-- web/react/components/team_signup_with_sso.jsx | 52 +++++++++++++--- web/react/pages/find_team.jsx | 59 ++++++++++++++++-- web/react/pages/signup_team.jsx | 2 +- web/static/help/Messaging.md | 1 - web/static/help/Messaging_en.md | 1 + web/static/help/Messaging_es.md | 1 + web/static/i18n/en.json | 72 ++++++++++++++++++++- web/static/i18n/es.json | 72 ++++++++++++++++++++- web/templates/find_team.html | 2 +- web/templates/signup_team.html | 2 +- web/web.go | 1 + 25 files changed, 759 insertions(+), 107 deletions(-) create mode 100644 doc/help/Messaging_es.md delete mode 120000 web/static/help/Messaging.md create mode 120000 web/static/help/Messaging_en.md create mode 120000 web/static/help/Messaging_es.md diff --git a/doc/help/Messaging_es.md b/doc/help/Messaging_es.md new file mode 100644 index 000000000..d3947f36a --- /dev/null +++ b/doc/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/i18n/en.json b/i18n/en.json index 12741fc68..b9b6b5fab 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -579,6 +579,10 @@ "id": "api.license.add_license.invalid.app_error", "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,17 +592,13 @@ "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" @@ -1551,10 +1551,6 @@ "id": "api.user.upload_profile_user.encode.app_error", "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" @@ -1575,6 +1571,10 @@ "id": "api.user.upload_profile_user.too_large.app_error", "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" @@ -2775,6 +2775,10 @@ "id": "store.sql_session.save.existing.app_error", "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" @@ -2783,10 +2787,6 @@ "id": "store.sql_session.update_roles.app_error", "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" @@ -3335,6 +3335,10 @@ "id": "web.root.home_title", "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" diff --git a/i18n/es.json b/i18n/es.json index c4e7a552c..9933aa284 100644 --- a/i18n/es.json +++ b/i18n/es.json @@ -3335,6 +3335,10 @@ "id": "web.root.home_title", "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" 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 (
    -

    {'An application would like to connect to your '}{this.props.teamName}{' account'}

    - +

    + +

    +

    - +
    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 (
    -

    {'Find Your teams'}

    -

    {'An email was sent with links to any teams to which you are a member.'}

    +

    + +

    +

    + +

    ); } return (
    -

    Find Your Team

    +

    + +

    -

    {'Get an email with links to any teams to which you are a member.'}

    +

    + +

    - +
    @@ -79,10 +117,19 @@ export default class FindTeam extends React.Component { className='btn btn-md btn-primary' type='submit' > - Send +
    ); } } + +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(
    - {'with GitLab'} + + + ); } @@ -36,10 +42,16 @@ export default class Login extends React.Component { loginMessage.push( - {'with Google Apps'} + + + ); } @@ -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 = ( + + ); } else if (extraParam === Constants.SIGNIN_VERIFIED) { - msg = ' Email Verified'; + msg = ( + + ); } if (msg != null) { @@ -78,7 +100,12 @@ export default class Login extends React.Component {
    {loginMessage}
    - {'or'} + + +
    ); @@ -90,7 +117,7 @@ export default class Login extends React.Component {
    @@ -102,12 +129,19 @@ export default class Login extends React.Component { if (this.props.inviteId) { userSignUp = (
    - {`Don't have an account? `} + + - {'Create one now'} +
    @@ -122,7 +156,10 @@ export default class Login extends React.Component { href='/' className='signup-team-login' > - {'Create a new team'} +
    ); @@ -144,7 +181,7 @@ export default class Login extends React.Component { @@ -154,9 +191,22 @@ export default class Login extends React.Component { return (
    -
    {'Sign in to:'}
    +
    + +

    {teamDisplayName}

    -

    {'on '}{global.window.mm_config.SiteName}

    +

    + +

    {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 (
    @@ -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' />
    @@ -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' />
    @@ -121,7 +148,10 @@ export default class LoginEmail extends React.Component { type='submit' className='btn btn-primary' > - {'Sign in'} +
    @@ -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 = ; errorClass = ' has-error'; } - + const {formatMessage} = this.props.intl; return (
    @@ -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' />
    @@ -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' />
    @@ -94,7 +120,10 @@ export default class LoginLdap extends React.Component { type='submit' className='btn btn-primary' > - {'Sign in'} +
    @@ -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 =

    ; + updateText = (

    ); } var error = null; @@ -57,19 +84,34 @@ export default class PasswordResetForm extends React.Component { formClass += ' has-error'; } + const {formatMessage} = this.props.intl; return (
    -

    {'Password Reset'}

    +

    + +

    -

    {'Enter a new password for your ' + this.props.teamDisplayName + ' ' + global.window.mm_config.SiteName + ' account.'}

    +

    + +

    @@ -78,7 +120,10 @@ export default class PasswordResetForm extends React.Component { type='submit' className='btn btn-primary' > - {'Change my password'} + {updateText} @@ -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: '

    A password reset link has been sent to {email} for your {teamDisplayName} team on {hostname}.

    ' + }, + 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:

    A password reset link has been sent to {email} for your {this.props.teamDisplayName} team on {window.location.hostname}.

    , 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 =
    {this.state.updateText}{this.state.moreUpdateText}
    ; + updateText = ( +
    +
    + ); } var error = null; @@ -56,23 +83,37 @@ export default class PasswordResetSendLink extends React.Component { formClass += ' has-error'; } + const {formatMessage} = this.props.intl; return (
    -

    Password Reset

    +

    + +

    {updateText}
    -

    {'To reset your password, enter the email address you used to sign up for ' + this.props.teamDisplayName + '.'}

    +

    + +

    @@ -81,7 +122,10 @@ export default class PasswordResetSendLink extends React.Component { type='submit' className='btn btn-primary' > - Reset my password +
    @@ -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/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 = (
    {'There are no teams include in the Team Directory and team creation has been disabled.'}
    ); + teamListing = ( +
    + +
    + ); } } else { teamListing = (
    -

    {'Choose a Team'}

    +

    + +

    { this.props.teams.map((team) => { @@ -71,7 +85,12 @@ export default class TeamSignUp extends React.Component { }) }
    -

    {'Or Create a Team'}

    +

    + +

    ); } @@ -79,7 +98,14 @@ export default class TeamSignUp extends React.Component { if (global.window.mm_config.EnableTeamCreation !== 'true') { if (teamListing == null) { - return (
    {'Team creation has been disabled. Please contact an administrator for access.'}
    ); + return ( +
    + +
    + ); } return ( @@ -122,7 +148,14 @@ export default class TeamSignUp extends React.Component {
    ); } else if (this.state.page === 'none') { - return (
    {'No team creation method has been enabled. Please contact an administrator for access.'}
    ); + return ( +
    + +
    + ); } } } 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( - {'Create new team with GitLab Account'} + + + ); } @@ -30,6 +38,7 @@ export default class ChooseAuthPage extends React.Component { buttons.push( { @@ -39,7 +48,12 @@ export default class ChooseAuthPage extends React.Component { } > - {'Create new team with Google Apps Account'} + + + ); } @@ -48,6 +62,7 @@ export default class ChooseAuthPage extends React.Component { buttons.push( - {'Create new team with email address'} + + + ); } if (buttons.length === 0) { - buttons = {'No sign-up methods configured, please contact your system administrator.'}; + buttons = ( + + + + ); } return (
    {buttons}
    ); 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'} + {serverError}
    ); @@ -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 = ( - {'Create team with GitLab Account'} + + + ); } else if (this.props.service === Constants.GOOGLE_SERVICE) { button = ( - {'Create team with Google Apps Account'} + + + ); } @@ -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}
    ); @@ -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
    ; + } + + return ( + + + + ); + } +} + +global.window.setup_find_team_page = function setup(props) { ReactDOM.render( - , + , 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/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/web/static/help/Messaging_en.md b/web/static/help/Messaging_en.md new file mode 120000 index 000000000..f74c0b879 --- /dev/null +++ b/web/static/help/Messaging_en.md @@ -0,0 +1 @@ +../../../doc/help/Messaging.md \ No newline at end of file diff --git a/web/static/help/Messaging_es.md b/web/static/help/Messaging_es.md new file mode 120000 index 000000000..a1890b510 --- /dev/null +++ b/web/static/help/Messaging_es.md @@ -0,0 +1 @@ +../../../doc/help/Messaging_es.md \ No newline at end of file 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 here 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": "

    A password reset link has been sent to {email} for your {teamDisplayName} team on {hostname}.

    ", + "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": "

    Welcome to:

    \n

    Mattermost

    \n

    Your team communication all in one place, instantly searchable and available anywhere

    \n

    Keep your team connected to help them achieve what matters most.

    ", "tutorial_intro.screenTwo": "

    How Mattermost works:

    \n

    Communication happens in public discussion channels, private groups and direct messages.

    \n

    Everything is archived and searchable from any web-enabled desktop, laptop or phone.

    ", "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 aquí 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": "

    Se ha enviado un enlace para restablecer la contraseña a {email} para tu equipo {teamDisplayName} en {hostname}.

    ", + "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": "

    Bienvenido a:

    Mattermost

    Las comunicaciones de tu equipo en un solo lugar, con búsquedas instantáneas y disponible desde donde sea.

    Mantén a tu equipo conectado para ayudarlos a conseguir lo que realmente importa.

    ", "tutorial_intro.screenTwo": "

    Cómo funciona Mattermost:

    Las comunicaciones ocurren en los canales de discusión los cuales son públicos, o en grupos privados e incluso con mensajes privados.

    Todo lo que ocurre es archivado y se puede buscar en cualquier momento desde cualquier dispositivo con acceso a Mattermost.

    ", "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 @@ 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 @@ diff --git a/web/web.go b/web/web.go index 36349dd5e..c0651c885 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 -- cgit v1.2.3-1-g7c22 From e88156ea2449c39c7ac3a78963e26b73af085071 Mon Sep 17 00:00:00 2001 From: Elias Nahum Date: Thu, 28 Jan 2016 17:14:11 -0300 Subject: Fix docs route to include the user locale - Move Messaging_{locale}.md files to static/help --- web/static/help/Messaging_en.md | 48 ++++++++++++++++++++++++++++++++++++++++- web/static/help/Messaging_es.md | 38 +++++++++++++++++++++++++++++++- web/web.go | 9 ++++++++ 3 files changed, 93 insertions(+), 2 deletions(-) mode change 120000 => 100644 web/static/help/Messaging_en.md mode change 120000 => 100644 web/static/help/Messaging_es.md diff --git a/web/static/help/Messaging_en.md b/web/static/help/Messaging_en.md deleted file mode 120000 index f74c0b879..000000000 --- a/web/static/help/Messaging_en.md +++ /dev/null @@ -1 +0,0 @@ -../../../doc/help/Messaging.md \ No newline at end of file diff --git a/web/static/help/Messaging_en.md b/web/static/help/Messaging_en.md new file mode 100644 index 000000000..2063ad41c --- /dev/null +++ b/web/static/help/Messaging_en.md @@ -0,0 +1,47 @@ +# Messaging + +### Writing Messages + +You can write messages using the input box with the text "Write a message..." at the bottom of Mattermost. + +Press **ENTER** to send a message. Use **Shift+ENTER** to create a new line without sending a message. + +### Formatting Messages + +Mattermost messages are formatted using a standard called "markdown". Here are examples: + +| Text Entered | How it appears | +|:---------------|:---------------| +|`**bold**`| **bold** | +| `_italic_`|_italic_| +|`[hyperlink](http://mattermost.org)`|[hyperlink](http://mattermost.org)| +|`![embedded image](https://travis-ci.org/mattermost/platform.svg)`|![embedded image](https://travis-ci.org/mattermost/platform.svg)| +|`:smile:` `:sheep:` `:alien:`|:smile: :sheep: :alien:| + +Emojis provided free from [Emoji One](http://emojione.com/). Check out a full list of Emojis [here](http://emoji.codes/). + + +### Mentioning Teammates + +You can mention a teammate by using the `@` symbol plus their username to send them a special notification to draw their attention. + +For example, you might write: + +``` +@alice how did your interview go with the new candidate? +``` + +Which sends a special mention notification to **alice** to check your message. + +To mention a teammate, press `@` and you should see a list of team members who can be messaged. You can either type their username or use the **Up** and **Down** arrow keys and then **ENTER** to select them to be mentioned. + +You can configure how you'd like to be alerted about mentions of your username, your first name, your nickname, or other keywords from **Account Settings** > **Notifications** and you can set channel-specific preferences from **[Channel Name]** > **Notification Preferences** + +### Messages Dropdown Menu + +To get to the Messages Dropdown Menu, hover over a message and click on the [...] menu. This shows a dropdown list containing additional actions you can perform on a message: + +- **Reply:** Opens up the sidebar so you can reply to a message in a comment thread. +- **Permalink:** Creates a link to the message. Sharing this link with other users in the channel lets them view the linked message in the Message Archives. +- **Delete:** Deletes the message so it is no longer visible. Team Administrators and System Administrators can also delete another user's message. +- **Edit:** Lets you edit your own message. diff --git a/web/static/help/Messaging_es.md b/web/static/help/Messaging_es.md deleted file mode 120000 index a1890b510..000000000 --- a/web/static/help/Messaging_es.md +++ /dev/null @@ -1 +0,0 @@ -../../../doc/help/Messaging_es.md \ No newline at end of file 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/web.go b/web/web.go index c0651c885..beb0eff06 100644 --- a/web/web.go +++ b/web/web.go @@ -612,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) } -- cgit v1.2.3-1-g7c22 From baa499d97b3ae6eca7f3f8d5af01ed129a4bd847 Mon Sep 17 00:00:00 2001 From: Elias Nahum Date: Thu, 28 Jan 2016 18:56:07 -0300 Subject: Removed duplicates Messaging md files from doc folder --- doc/help/Messaging.md | 47 ----------------------------------------------- doc/help/Messaging_es.md | 37 ------------------------------------- 2 files changed, 84 deletions(-) delete mode 100644 doc/help/Messaging.md delete mode 100644 doc/help/Messaging_es.md diff --git a/doc/help/Messaging.md b/doc/help/Messaging.md deleted file mode 100644 index 2063ad41c..000000000 --- a/doc/help/Messaging.md +++ /dev/null @@ -1,47 +0,0 @@ -# Messaging - -### Writing Messages - -You can write messages using the input box with the text "Write a message..." at the bottom of Mattermost. - -Press **ENTER** to send a message. Use **Shift+ENTER** to create a new line without sending a message. - -### Formatting Messages - -Mattermost messages are formatted using a standard called "markdown". Here are examples: - -| Text Entered | How it appears | -|:---------------|:---------------| -|`**bold**`| **bold** | -| `_italic_`|_italic_| -|`[hyperlink](http://mattermost.org)`|[hyperlink](http://mattermost.org)| -|`![embedded image](https://travis-ci.org/mattermost/platform.svg)`|![embedded image](https://travis-ci.org/mattermost/platform.svg)| -|`:smile:` `:sheep:` `:alien:`|:smile: :sheep: :alien:| - -Emojis provided free from [Emoji One](http://emojione.com/). Check out a full list of Emojis [here](http://emoji.codes/). - - -### Mentioning Teammates - -You can mention a teammate by using the `@` symbol plus their username to send them a special notification to draw their attention. - -For example, you might write: - -``` -@alice how did your interview go with the new candidate? -``` - -Which sends a special mention notification to **alice** to check your message. - -To mention a teammate, press `@` and you should see a list of team members who can be messaged. You can either type their username or use the **Up** and **Down** arrow keys and then **ENTER** to select them to be mentioned. - -You can configure how you'd like to be alerted about mentions of your username, your first name, your nickname, or other keywords from **Account Settings** > **Notifications** and you can set channel-specific preferences from **[Channel Name]** > **Notification Preferences** - -### Messages Dropdown Menu - -To get to the Messages Dropdown Menu, hover over a message and click on the [...] menu. This shows a dropdown list containing additional actions you can perform on a message: - -- **Reply:** Opens up the sidebar so you can reply to a message in a comment thread. -- **Permalink:** Creates a link to the message. Sharing this link with other users in the channel lets them view the linked message in the Message Archives. -- **Delete:** Deletes the message so it is no longer visible. Team Administrators and System Administrators can also delete another user's message. -- **Edit:** Lets you edit your own message. diff --git a/doc/help/Messaging_es.md b/doc/help/Messaging_es.md deleted file mode 100644 index d3947f36a..000000000 --- a/doc/help/Messaging_es.md +++ /dev/null @@ -1,37 +0,0 @@ -# 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** -- cgit v1.2.3-1-g7c22 From e7be676969f78f313aacbd1d7911ad1a511dcf6d Mon Sep 17 00:00:00 2001 From: Frank Allenby Date: Fri, 29 Jan 2016 10:32:11 +0200 Subject: Completed PLT-1592 --- i18n/en.json | 14 +++++++++----- model/version.go | 2 +- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/i18n/en.json b/i18n/en.json index 12741fc68..0b0f72628 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -1085,7 +1085,7 @@ }, { "id": "api.templates.error.link", - "translation": "Go back to team site" + "translation": "Go back to Mattermost" }, { "id": "api.templates.error.title", @@ -1157,7 +1157,7 @@ }, { "id": "api.templates.reset_body.info", - "translation": "To change your password, click \"Reset Password\" below.
    If you did not mean to reset your password, please ignore this email and your password will remain the same." + "translation": "To change your password, click \"Reset Password\" below.
    If you did not mean to reset your password, please ignore this email and your password will remain the same. The password reset link expires in 24 hours." }, { "id": "api.templates.reset_body.title", @@ -1437,7 +1437,7 @@ }, { "id": "api.user.reset_password.link_expired.app_error", - "translation": "The reset link has expired" + "translation": "The password reset link has expired" }, { "id": "api.user.reset_password.method", @@ -3325,7 +3325,11 @@ }, { "id": "web.reset_password.expired_link.app_error", - "translation": "The signup link has expired" + "translation": "The password reset link has expired" + }, + { + "id": "web.reset_password.expired_link.app_error.link_text", + "translation": "Go to Password Reset page" }, { "id": "web.reset_password.invalid_link.app_error", @@ -3391,4 +3395,4 @@ "id": "web.watcher_fail.error", "translation": "Failed to add directory to watcher %v" } -] \ No newline at end of file +] diff --git a/model/version.go b/model/version.go index 88334ceea..f503ddb84 100644 --- a/model/version.go +++ b/model/version.go @@ -28,7 +28,7 @@ var CurrentVersion string = versions[0] var BuildNumber = "_BUILD_NUMBER_" var BuildDate = "_BUILD_DATE_" var BuildHash = "_BUILD_HASH_" -var BuildEnterpriseReady = "_BUILD_ENTERPRISE_READY_" +var BuildEnterpriseReady = "false" func SplitVersion(version string) (int64, int64, int64) { parts := strings.Split(version, ".") -- cgit v1.2.3-1-g7c22 From ca95adc41fcef5676a15784c11a63d9f44a1e11c Mon Sep 17 00:00:00 2001 From: Frank Allenby Date: Fri, 29 Jan 2016 10:39:07 +0200 Subject: Revised PLT-1592 --- i18n/en.json | 4 ---- model/version.go | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/i18n/en.json b/i18n/en.json index 0b0f72628..e93e3c280 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -3327,10 +3327,6 @@ "id": "web.reset_password.expired_link.app_error", "translation": "The password reset link has expired" }, - { - "id": "web.reset_password.expired_link.app_error.link_text", - "translation": "Go to Password Reset page" - }, { "id": "web.reset_password.invalid_link.app_error", "translation": "The reset link does not appear to be valid" diff --git a/model/version.go b/model/version.go index f503ddb84..88334ceea 100644 --- a/model/version.go +++ b/model/version.go @@ -28,7 +28,7 @@ var CurrentVersion string = versions[0] var BuildNumber = "_BUILD_NUMBER_" var BuildDate = "_BUILD_DATE_" var BuildHash = "_BUILD_HASH_" -var BuildEnterpriseReady = "false" +var BuildEnterpriseReady = "_BUILD_ENTERPRISE_READY_" func SplitVersion(version string) (int64, int64, int64) { parts := strings.Split(version, ".") -- cgit v1.2.3-1-g7c22 From 94a19ae75e71b59d01a37cc1c0415d5796d45440 Mon Sep 17 00:00:00 2001 From: Elias Nahum Date: Fri, 29 Jan 2016 00:24:43 -0300 Subject: PLT-7: Refactoring frontend (chunk 5) - Signup Team Complete - Signup User Complete - Email Verify --- web/react/components/email_verify.jsx | 54 +++++++- web/react/components/signup_team_complete.jsx | 11 +- web/react/components/signup_user_complete.jsx | 151 ++++++++++++++++++--- .../components/team_signup_display_name_page.jsx | 45 +++++- web/react/components/team_signup_email_item.jsx | 29 +++- web/react/components/team_signup_password_page.jsx | 83 +++++++++-- .../components/team_signup_send_invites_page.jsx | 46 +++++-- web/react/components/team_signup_url_page.jsx | 74 ++++++++-- web/react/components/team_signup_username_page.jsx | 69 ++++++++-- web/react/components/team_signup_welcome_page.jsx | 75 ++++++++-- web/static/i18n/en.json | 80 +++++++++++ web/static/i18n/es.json | 82 ++++++++++- 12 files changed, 700 insertions(+), 99 deletions(-) diff --git a/web/react/components/email_verify.jsx b/web/react/components/email_verify.jsx index 9c07853b7..ef1a62130 100644 --- a/web/react/components/email_verify.jsx +++ b/web/react/components/email_verify.jsx @@ -1,6 +1,8 @@ // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. +import {FormattedMessage, FormattedHTMLMessage} from 'mm-intl'; + export default class EmailVerify extends React.Component { constructor(props) { super(props); @@ -19,21 +21,61 @@ export default class EmailVerify extends React.Component { var resend = ''; var resendConfirm = ''; if (this.props.isVerified === 'true') { - title = global.window.mm_config.SiteName + ' Email Verified'; - body =

    Your email has been verified! Click + ); + body = ( + Please verify your email address. Check your inbox for an email.

    ; + title = ( + + ); + body = ( +

    + +

    + ); resend = ( ); if (this.props.resendSuccess) { - resendConfirm =

    {' Verification email sent.'}

    ; + resendConfirm = ( +

    + +

    ); } } diff --git a/web/react/components/signup_team_complete.jsx b/web/react/components/signup_team_complete.jsx index 6c7fd57b3..16553daeb 100644 --- a/web/react/components/signup_team_complete.jsx +++ b/web/react/components/signup_team_complete.jsx @@ -9,6 +9,8 @@ import UsernamePage from './team_signup_username_page.jsx'; import PasswordPage from './team_signup_password_page.jsx'; import BrowserStore from '../stores/browser_store.jsx'; +import {FormattedMessage} from 'mm-intl'; + export default class SignupTeamComplete extends React.Component { constructor(props) { super(props); @@ -96,7 +98,14 @@ export default class SignupTeamComplete extends React.Component { ); } - return (
    You've already completed the signup process for this invitation or this invitation has expired.
    ); + return ( +
    + +
    + ); } } diff --git a/web/react/components/signup_user_complete.jsx b/web/react/components/signup_user_complete.jsx index ace0d28ae..47ec58e98 100644 --- a/web/react/components/signup_user_complete.jsx +++ b/web/react/components/signup_user_complete.jsx @@ -7,7 +7,32 @@ import UserStore from '../stores/user_store.jsx'; import BrowserStore from '../stores/browser_store.jsx'; import Constants from '../utils/constants.jsx'; -export default class SignupUserComplete extends React.Component { +import {intlShape, injectIntl, defineMessages, FormattedMessage, FormattedHTMLMessage} from 'mm-intl'; + +const holders = defineMessages({ + required: { + id: 'signup_user_completed.required', + defaultMessage: 'This field is required' + }, + validEmail: { + id: 'signup_user_completed.validEmail', + defaultMessage: 'Please enter a valid email address' + }, + reserved: { + id: 'signup_user_completed.reserved', + defaultMessage: 'This username is reserved, please choose a new one.' + }, + usernameLength: { + id: 'signup_user_completed.usernameLength', + defaultMessage: 'Username must begin with a letter, and contain between {min} to {max} lowercase characters made up of numbers, letters, and the symbols \'.\', \'-\' and \'_\'.' + }, + passwordLength: { + id: 'signup_user_completed.passwordLength', + defaultMessage: 'Please enter at least {min} characters' + } +}); + +class SignupUserComplete extends React.Component { constructor(props) { super(props); @@ -29,30 +54,31 @@ export default class SignupUserComplete extends React.Component { handleSubmit(e) { e.preventDefault(); + const {formatMessage} = this.props.intl; const providedEmail = ReactDOM.findDOMNode(this.refs.email).value.trim(); if (!providedEmail) { - this.setState({nameError: '', emailError: 'This field is required', passwordError: ''}); + this.setState({nameError: '', emailError: formatMessage(holders.required), passwordError: ''}); return; } if (!Utils.isEmail(providedEmail)) { - this.setState({nameError: '', emailError: 'Please enter a valid email address', passwordError: ''}); + this.setState({nameError: '', emailError: formatMessage(holders.validEmail), passwordError: ''}); return; } const providedUsername = ReactDOM.findDOMNode(this.refs.name).value.trim().toLowerCase(); if (!providedUsername) { - this.setState({nameError: 'This field is required', emailError: '', passwordError: '', serverError: ''}); + this.setState({nameError: formatMessage(holders.required), emailError: '', passwordError: '', serverError: ''}); return; } const usernameError = Utils.isValidUsername(providedUsername); if (usernameError === 'Cannot use a reserved word as a username.') { - this.setState({nameError: 'This username is reserved, please choose a new one.', emailError: '', passwordError: '', serverError: ''}); + this.setState({nameError: formatMessage(holders.reserved), emailError: '', passwordError: '', serverError: ''}); return; } else if (usernameError) { this.setState({ - nameError: '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 \'_\'.', + nameError: formatMessage(holders.usernameLength, {min: Constants.MIN_USERNAME_LENGTH, max: Constants.MAX_USERNAME_LENGTH}), emailError: '', passwordError: '', serverError: '' @@ -62,7 +88,7 @@ export default class SignupUserComplete extends React.Component { const providedPassword = ReactDOM.findDOMNode(this.refs.password).value.trim(); if (!providedPassword || providedPassword.length < Constants.MIN_PASSWORD_LENGTH) { - this.setState({nameError: '', emailError: '', passwordError: 'Please enter at least ' + Constants.MIN_PASSWORD_LENGTH + ' characters', serverError: ''}); + this.setState({nameError: '', emailError: '', passwordError: formatMessage(holders.passwordLength, {min: Constants.MIN_PASSWORD_LENGTH}), serverError: ''}); return; } @@ -95,7 +121,7 @@ export default class SignupUserComplete extends React.Component { window.location.href = '/' + this.props.teamName + '/channels/town-square'; }, (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?email=' + encodeURIComponent(user.email) + '&teamname=' + encodeURIComponent(this.props.teamName); } else { this.setState({serverError: err.message}); @@ -112,7 +138,14 @@ export default class SignupUserComplete extends React.Component { client.track('signup', 'signup_user_01_welcome'); if (this.state.wizard === 'finished') { - return
    {"You've already completed the signup process for this invitation or this invitation has expired."}
    ; + return ( +
    + +
    + ); } // set up error labels @@ -124,7 +157,18 @@ export default class SignupUserComplete extends React.Component { } var nameError = null; - var nameHelpText = {'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 '_'"}; + var nameHelpText = ( + + + + ); var nameDivStyle = 'form-group'; if (this.state.nameError) { nameError = ; @@ -151,7 +195,16 @@ export default class SignupUserComplete extends React.Component { // set up the email entry and hide it if an email was provided var yourEmailIs = ''; if (this.state.user.email) { - yourEmailIs = {'Your email address is '}{this.state.user.email}{". You'll use this address to sign in to " + global.window.mm_config.SiteName + '.'}; + yourEmailIs = ( + + ); } var emailContainerStyle = 'margin--extra'; @@ -161,7 +214,12 @@ export default class SignupUserComplete extends React.Component { var email = (
    -
    {"What's your email address?"}
    +
    + +
    - {'with GitLab'} + + + ); } @@ -195,10 +259,16 @@ export default class SignupUserComplete extends React.Component { signupMessage.push( - {'with Google'} + + + ); } @@ -211,7 +281,12 @@ export default class SignupUserComplete extends React.Component { {email} {yourEmailIs}
    -
    {'Choose your username'}
    +
    + +
    -
    {'Choose your password'}
    +
    + +
    - {'Create Account'} +

    @@ -258,7 +341,12 @@ export default class SignupUserComplete extends React.Component {
    {signupMessage}
    - {'or'} + + +
    ); @@ -271,10 +359,28 @@ export default class SignupUserComplete extends React.Component { className='signup-team-logo' src='/static/images/logo.png' /> -
    {'Welcome to:'}
    +
    + +

    {this.props.teamDisplayName}

    -

    {'on ' + global.window.mm_config.SiteName}

    -

    {"Let's create your account"}

    +

    + +

    +

    + +

    {signupMessage} {emailSignup} {serverError} @@ -293,6 +399,7 @@ SignupUserComplete.defaultProps = { teamDisplayName: '' }; SignupUserComplete.propTypes = { + intl: intlShape.isRequired, teamName: React.PropTypes.string, hash: React.PropTypes.string, teamId: React.PropTypes.string, @@ -300,3 +407,5 @@ SignupUserComplete.propTypes = { data: React.PropTypes.string, teamDisplayName: React.PropTypes.string }; + +export default injectIntl(SignupUserComplete); \ No newline at end of file diff --git a/web/react/components/team_signup_display_name_page.jsx b/web/react/components/team_signup_display_name_page.jsx index f4d5ea162..f07b50756 100644 --- a/web/react/components/team_signup_display_name_page.jsx +++ b/web/react/components/team_signup_display_name_page.jsx @@ -4,7 +4,20 @@ import * as utils from '../utils/utils.jsx'; import * as client from '../utils/client.jsx'; -export default class TeamSignupDisplayNamePage extends React.Component { +import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'mm-intl'; + +const holders = defineMessages({ + required: { + id: 'team_signup_display_name.required', + defaultMessage: 'This field is required' + }, + charLength: { + id: 'team_signup_display_name.charLength', + defaultMessage: 'Name must be 4 or more characters up to a maximum of 15' + } +}); + +class TeamSignupDisplayNamePage extends React.Component { constructor(props) { super(props); @@ -21,12 +34,13 @@ export default class TeamSignupDisplayNamePage extends React.Component { submitNext(e) { e.preventDefault(); + const {formatMessage} = this.props.intl; var displayName = ReactDOM.findDOMNode(this.refs.name).value.trim(); if (!displayName) { - this.setState({nameError: 'This field is required'}); + this.setState({nameError: formatMessage(holders.required)}); return; } else if (displayName.length < 4 || displayName.length > 15) { - this.setState({nameError: 'Name must be 4 or more characters up to a maximum of 15'}); + this.setState({nameError: formatMessage(holders.charLength)}); return; } @@ -56,7 +70,12 @@ export default class TeamSignupDisplayNamePage extends React.Component { className='signup-team-logo' src='/static/images/logo.png' /> -

    {'Team Name'}

    +

    + +

    @@ -76,21 +95,30 @@ export default class TeamSignupDisplayNamePage extends React.Component { {nameError}
    - {'Name your team in any language. Your team name shows in menus and headings.'} +
    @@ -100,6 +128,9 @@ export default class TeamSignupDisplayNamePage extends React.Component { } TeamSignupDisplayNamePage.propTypes = { + intl: intlShape.isRequired, state: React.PropTypes.object, updateParent: React.PropTypes.func }; + +export default injectIntl(TeamSignupDisplayNamePage); \ No newline at end of file diff --git a/web/react/components/team_signup_email_item.jsx b/web/react/components/team_signup_email_item.jsx index 59c4771d7..feb70dc71 100644 --- a/web/react/components/team_signup_email_item.jsx +++ b/web/react/components/team_signup_email_item.jsx @@ -3,7 +3,24 @@ import * as Utils from '../utils/utils.jsx'; -export default class TeamSignupEmailItem extends React.Component { +import {intlShape, injectIntl, defineMessages} from 'mm-intl'; + +const holders = defineMessages({ + validEmail: { + id: 'team_signup_email.validEmail', + defaultMessage: 'Please enter a valid email address' + }, + different: { + id: 'team_signup_email.different', + defaultMessage: 'Please use a different email than the one used at signup' + }, + address: { + id: 'team_signup_email.address', + defaultMessage: 'Email Address' + } +}); + +class TeamSignupEmailItem extends React.Component { constructor(props) { super(props); @@ -16,6 +33,7 @@ export default class TeamSignupEmailItem extends React.Component { return ReactDOM.findDOMNode(this.refs.email).value.trim(); } validate(teamEmail) { + const {formatMessage} = this.props.intl; const email = ReactDOM.findDOMNode(this.refs.email).value.trim().toLowerCase(); if (!email) { @@ -23,10 +41,10 @@ export default class TeamSignupEmailItem extends React.Component { } if (!Utils.isEmail(email)) { - this.setState({emailError: 'Please enter a valid email address'}); + this.setState({emailError: formatMessage(holders.validEmail)}); return false; } else if (email === teamEmail) { - this.setState({emailError: 'Please use a different email than the one used at signup'}); + this.setState({emailError: formatMessage(holders.different)}); return false; } @@ -48,7 +66,7 @@ export default class TeamSignupEmailItem extends React.Component { type='email' ref='email' className='form-control' - placeholder='Email Address' + placeholder={this.props.intl.formatMessage(holders.address)} defaultValue={this.props.email} maxLength='128' spellCheck='false' @@ -60,6 +78,9 @@ export default class TeamSignupEmailItem extends React.Component { } TeamSignupEmailItem.propTypes = { + intl: intlShape.isRequired, focus: React.PropTypes.bool, email: React.PropTypes.string }; + +export default injectIntl(TeamSignupEmailItem); \ No newline at end of file diff --git a/web/react/components/team_signup_password_page.jsx b/web/react/components/team_signup_password_page.jsx index 7e11d38c3..06c04854f 100644 --- a/web/react/components/team_signup_password_page.jsx +++ b/web/react/components/team_signup_password_page.jsx @@ -6,7 +6,20 @@ import BrowserStore from '../stores/browser_store.jsx'; import UserStore from '../stores/user_store.jsx'; import Constants from '../utils/constants.jsx'; -export default class TeamSignupPasswordPage extends React.Component { +import {intlShape, injectIntl, defineMessages, FormattedMessage, FormattedHTMLMessage} from 'mm-intl'; + +const holders = defineMessages({ + passwordError: { + id: 'team_signup_password.passwordError', + defaultMessage: 'Please enter at least {chars} characters' + }, + creating: { + id: 'team_signup_password.creating', + defaultMessage: 'Creating team...' + } +}); + +class TeamSignupPasswordPage extends React.Component { constructor(props) { super(props); @@ -25,7 +38,7 @@ export default class TeamSignupPasswordPage extends React.Component { var password = ReactDOM.findDOMNode(this.refs.password).value.trim(); if (!password || password.length < Constants.MIN_PASSWORD_LENGTH) { - this.setState({passwordError: 'Please enter at least ' + Constants.MIN_PASSWORD_LENGTH + ' characters'}); + this.setState({passwordError: this.props.intl.formatMessage(holders.passwordError, {chars: Constants.MIN_PASSWORD_LENGTH})}); return; } @@ -56,7 +69,7 @@ export default class TeamSignupPasswordPage extends React.Component { window.location.href = '/' + teamSignup.team.name + '/channels/town-square'; }, (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?email=' + encodeURIComponent(teamSignup.team.email) + '&teamname=' + encodeURIComponent(teamSignup.team.name); } else { this.setState({serverError: err.message}); @@ -93,15 +106,35 @@ export default class TeamSignupPasswordPage extends React.Component { className='signup-team-logo' src='/static/images/logo.png' /> -

    {'Your password'}

    -
    {"Select a password that you'll use to login with your email address:"}
    +

    + +

    +
    + +
    -
    {'Email'}
    +
    + +
    {this.props.state.team.email}
    -
    {'Choose your password'}
    +
    + +
    - {'Passwords must contain ' + Constants.MIN_PASSWORD_LENGTH + ' to ' + Constants.MAX_PASSWORD_LENGTH + ' characters. Your password will be strongest if it contains a mix of symbols, numbers, and upper and lowercase characters.'} + + +
    {passwordError} @@ -123,19 +165,33 @@ export default class TeamSignupPasswordPage extends React.Component { type='submit' className='btn btn-primary margin--extra' id='finish-button' - data-loading-text={' Creating team...'} + data-loading-text={' ' + this.props.intl.formatMessage(holders.creating)} onClick={this.submitNext} > - {'Finish'} +
    -

    By proceeding to create your account and use {global.window.mm_config.SiteName}, you agree to our Terms of Service and Privacy Policy. If you do not agree, you cannot use {global.window.mm_config.SiteName}.

    +

    + +

    @@ -149,7 +205,10 @@ TeamSignupPasswordPage.defaultProps = { hash: '' }; TeamSignupPasswordPage.propTypes = { + intl: intlShape.isRequired, state: React.PropTypes.object, hash: React.PropTypes.string, updateParent: React.PropTypes.func }; + +export default injectIntl(TeamSignupPasswordPage); \ No newline at end of file diff --git a/web/react/components/team_signup_send_invites_page.jsx b/web/react/components/team_signup_send_invites_page.jsx index a580623e4..46a6bc68e 100644 --- a/web/react/components/team_signup_send_invites_page.jsx +++ b/web/react/components/team_signup_send_invites_page.jsx @@ -4,6 +4,8 @@ import EmailItem from './team_signup_email_item.jsx'; import * as Client from '../utils/client.jsx'; +import {FormattedMessage, FormattedHTMLMessage} from 'mm-intl'; + export default class TeamSignupSendInvitesPage extends React.Component { constructor(props) { super(props); @@ -117,7 +119,10 @@ export default class TeamSignupSendInvitesPage extends React.Component { href='#' onClick={this.submitAddInvite} > - Add Invitation +
    @@ -125,22 +130,32 @@ export default class TeamSignupSendInvitesPage extends React.Component { bottomContent = (

    - {'if you prefer, you can invite team members later'} -
    - {' and '} + - {'skip this step '} + - {'for now.'} +

    ); } else { content = (
    - {'Email is currently disabled for your team, and emails cannot be sent. Contact your system administrator to enable email and email invitations.'} +
    ); } @@ -152,7 +167,12 @@ export default class TeamSignupSendInvitesPage extends React.Component { className='signup-team-logo' src='/static/images/logo.png' /> -

    {'Invite Team Members'}

    +

    + +

    {content}
    @@ -170,7 +193,10 @@ export default class TeamSignupSendInvitesPage extends React.Component { href='#' onClick={this.submitBack} > - Back to previous step +
    diff --git a/web/react/components/team_signup_url_page.jsx b/web/react/components/team_signup_url_page.jsx index 30459fc67..2f6c3df49 100644 --- a/web/react/components/team_signup_url_page.jsx +++ b/web/react/components/team_signup_url_page.jsx @@ -5,7 +5,32 @@ import * as Utils from '../utils/utils.jsx'; import * as Client from '../utils/client.jsx'; import Constants from '../utils/constants.jsx'; -export default class TeamSignupUrlPage extends React.Component { +import {injectIntl, intlShape, defineMessages, FormattedMessage, FormattedHTMLMessage} from 'mm-intl'; + +const holders = defineMessages({ + required: { + id: 'team_signup_url.required', + defaultMessage: 'This field is required' + }, + regex: { + id: 'team_signup_url.regex', + defaultMessage: "Use only lower case letters, numbers and dashes. Must start with a letter and can't end in a dash." + }, + charLength: { + id: 'team_signup_url.charLength', + defaultMessage: 'Name must be 4 or more characters up to a maximum of 15' + }, + taken: { + id: 'team_signup_url.taken', + defaultMessage: 'URL is taken or contains a reserved word' + }, + unavailable: { + id: 'team_signup_url.unavailable', + defaultMessage: 'This URL is unavailable. Please try another.' + } +}); + +class TeamSignupUrlPage extends React.Component { constructor(props) { super(props); @@ -23,9 +48,10 @@ export default class TeamSignupUrlPage extends React.Component { submitNext(e) { e.preventDefault(); + const {formatMessage} = this.props.intl; const name = ReactDOM.findDOMNode(this.refs.name).value.trim(); if (!name) { - this.setState({nameError: 'This field is required'}); + this.setState({nameError: formatMessage(holders.required)}); return; } @@ -33,17 +59,17 @@ export default class TeamSignupUrlPage extends React.Component { const urlRegex = /^[a-z]+([a-z\-0-9]+|(__)?)[a-z0-9]+$/g; if (cleanedName !== name || !urlRegex.test(name)) { - this.setState({nameError: "Use only lower case letters, numbers and dashes. Must start with a letter and can't end in a dash."}); + this.setState({nameError: formatMessage(holders.regex)}); return; } else if (cleanedName.length < 4 || cleanedName.length > 15) { - this.setState({nameError: 'Name must be 4 or more characters up to a maximum of 15'}); + this.setState({nameError: formatMessage(holders.charLength)}); return; } if (global.window.mm_config.RestrictTeamNames === 'true') { for (let index = 0; index < Constants.RESERVED_TEAM_NAMES.length; index++) { if (cleanedName.indexOf(Constants.RESERVED_TEAM_NAMES[index]) === 0) { - this.setState({nameError: 'URL is taken or contains a reserved word'}); + this.setState({nameError: formatMessage(holders.taken)}); return; } } @@ -52,7 +78,7 @@ export default class TeamSignupUrlPage extends React.Component { Client.findTeamByName(name, (data) => { if (data) { - this.setState({nameError: 'This URL is unavailable. Please try another.'}); + this.setState({nameError: formatMessage(holders.unavailable)}); } else { if (global.window.mm_config.SendEmailNotifications === 'true') { this.props.state.wizard = 'send_invites'; @@ -96,7 +122,12 @@ export default class TeamSignupUrlPage extends React.Component { className='signup-team-logo' src='/static/images/logo.png' /> -

    {`Team URL`}

    +

    + +

    @@ -124,25 +155,39 @@ export default class TeamSignupUrlPage extends React.Component {
    {nameError}
    -

    {`Choose the web address of your new team:`}

    +

    + +

      -
    • Short and memorable is best
    • -
    • Use lowercase letters, numbers and dashes
    • -
    • Must start with a letter and can't end in a dash
    • +
    @@ -152,6 +197,9 @@ export default class TeamSignupUrlPage extends React.Component { } TeamSignupUrlPage.propTypes = { + intl: intlShape.isRequired, state: React.PropTypes.object, updateParent: React.PropTypes.func }; + +export default injectIntl(TeamSignupUrlPage); \ No newline at end of file diff --git a/web/react/components/team_signup_username_page.jsx b/web/react/components/team_signup_username_page.jsx index 6ccab6656..a7332975d 100644 --- a/web/react/components/team_signup_username_page.jsx +++ b/web/react/components/team_signup_username_page.jsx @@ -5,7 +5,20 @@ import * as Utils from '../utils/utils.jsx'; import * as Client from '../utils/client.jsx'; import Constants from '../utils/constants.jsx'; -export default class TeamSignupUsernamePage extends React.Component { +import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl'; + +const holders = defineMessages({ + reserved: { + id: 'team_signup_username.reserved', + defaultMessage: 'This username is reserved, please choose a new one.' + }, + invalid: { + id: 'team_signup_username.invalid', + defaultMessage: 'Username must begin with a letter, and contain between {min} to {max} characters in total, which may be numbers, lowercase letters, or any of the symbols \'.\', \'-\', or \'_\'' + } +}); + +class TeamSignupUsernamePage extends React.Component { constructor(props) { super(props); @@ -27,14 +40,15 @@ export default class TeamSignupUsernamePage extends React.Component { submitNext(e) { e.preventDefault(); + const {formatMessage} = this.props.intl; var name = ReactDOM.findDOMNode(this.refs.name).value.trim().toLowerCase(); var usernameError = Utils.isValidUsername(name); - if (usernameError === 'Cannot use a reserved word as a username.') { - this.setState({nameError: 'This username is reserved, please choose a new one.'}); + if (usernameError === 'Cannot use a reserved word as a username.') { //this should be change to some kind of ID + this.setState({nameError: formatMessage(holders.reserved)}); return; } else if (usernameError) { - this.setState({nameError: 'Username must begin with a letter, and contain between ' + Constants.MIN_USERNAME_LENGTH + ' to ' + Constants.MAX_USERNAME_LENGTH + ' characters in total, which may be numbers, lowercase letters, or any of the symbols \'.\', \'-\', or \'_\''}); + this.setState({nameError: formatMessage(holders.invalid, {min: Constants.MIN_USERNAME_LENGTH, max: Constants.MAX_USERNAME_LENGTH})}); return; } @@ -46,7 +60,18 @@ export default class TeamSignupUsernamePage extends React.Component { Client.track('signup', 'signup_team_06_username'); var nameError = null; - var nameHelpText = {'Usernames must begin with a letter and contain between ' + Constants.MIN_USERNAME_LENGTH + ' to ' + Constants.MAX_USERNAME_LENGTH + " characters made up of lowercase letters, numbers, and the symbols '.', '-' and '_'"}; + var nameHelpText = ( + + + + ); var nameDivClass = 'form-group'; if (this.state.nameError) { nameError = ; @@ -61,13 +86,28 @@ export default class TeamSignupUsernamePage extends React.Component { className='signup-team-logo' src='/static/images/logo.png' /> -

    {'Your username'}

    -
    {'Select a memorable username that makes it easy for teammates to identify you:'}
    +

    + +

    +
    + +
    -
    {'Choose your username'}
    +
    + +
    - {'Next'} +
    @@ -97,7 +140,10 @@ export default class TeamSignupUsernamePage extends React.Component { href='#' onClick={this.submitBack} > - {'Back to previous step'} +
    @@ -110,6 +156,9 @@ TeamSignupUsernamePage.defaultProps = { state: null }; TeamSignupUsernamePage.propTypes = { + intl: intlShape.isRequired, state: React.PropTypes.object, updateParent: React.PropTypes.func }; + +export default injectIntl(TeamSignupUsernamePage); \ No newline at end of file diff --git a/web/react/components/team_signup_welcome_page.jsx b/web/react/components/team_signup_welcome_page.jsx index a374dd363..18951be72 100644 --- a/web/react/components/team_signup_welcome_page.jsx +++ b/web/react/components/team_signup_welcome_page.jsx @@ -5,7 +5,24 @@ import * as Utils from '../utils/utils.jsx'; import * as Client from '../utils/client.jsx'; import BrowserStore from '../stores/browser_store.jsx'; -export default class TeamSignupWelcomePage extends React.Component { +import {injectIntl, intlShape, defineMessages, FormattedMessage, FormattedHTMLMessage} from 'mm-intl'; + +const holders = defineMessages({ + storageError: { + id: 'team_signup_welcome.storageError', + defaultMessage: 'This service requires local storage to be enabled. Please enable it or exit private browsing.' + }, + validEmailError: { + id: 'team_signup_welcome.validEmailError', + defaultMessage: 'Please enter a valid email address' + }, + address: { + id: 'team_signup_welcome.address', + defaultMessage: 'Email Address' + } +}); + +class TeamSignupWelcomePage extends React.Component { constructor(props) { super(props); @@ -20,7 +37,7 @@ export default class TeamSignupWelcomePage extends React.Component { } submitNext(e) { if (!BrowserStore.isLocalStorageSupported()) { - this.setState({storageError: 'This service requires local storage to be enabled. Please enable it or exit private browsing.'}); + this.setState({storageError: this.props.intl.formatMessage(holders.storageError)}); return; } e.preventDefault(); @@ -34,15 +51,16 @@ export default class TeamSignupWelcomePage extends React.Component { handleDiffSubmit(e) { e.preventDefault(); + const {formatMessage} = this.props.intl; var state = {useDiff: true, serverError: ''}; var email = ReactDOM.findDOMNode(this.refs.email).value.trim().toLowerCase(); if (!email || !Utils.isEmail(email)) { - state.emailError = 'Please enter a valid email address'; + state.emailError = formatMessage(holders.validEmailError); this.setState(state); return; } else if (!BrowserStore.isLocalStorageSupported()) { - state.emailError = 'This service requires local storage to be enabled. Please enable it or exit private browsing.'; + state.emailError = formatMessage(holders.storageError); this.setState(state); return; } @@ -62,7 +80,7 @@ export default class TeamSignupWelcomePage extends React.Component { let errorMsg = err.message; if (err.detailed_error.indexOf('Invalid RCPT TO address provided') >= 0) { - errorMsg = 'Please enter a valid email address'; + errorMsg = formatMessage(holders.validEmailError); } this.setState({emailError: '', serverError: errorMsg}); @@ -114,18 +132,35 @@ export default class TeamSignupWelcomePage extends React.Component { className='signup-team-logo' src='/static/images/logo.png' /> -

    Welcome to:

    +

    + +

    {global.window.mm_config.SiteName}

    -

    Let's set up your new team

    +

    + +

    - Please confirm your email address:
    + +
    {this.props.state.team.email}

    - Your account will administer the new team site.
    - You can add other administrators later. +

    {storageError}
    @@ -147,7 +185,7 @@ export default class TeamSignupWelcomePage extends React.Component { type='email' ref='email' className='form-control' - placeholder='Email Address' + placeholder={this.props.intl.formatMessage(holders.address)} maxLength='128' spellCheck='false' /> @@ -161,7 +199,10 @@ export default class TeamSignupWelcomePage extends React.Component { type='button' onClick={this.handleDiffSubmit} > - Use this instead +
    - Use a different email +
    ); @@ -180,6 +224,9 @@ TeamSignupWelcomePage.defaultProps = { state: {} }; TeamSignupWelcomePage.propTypes = { + intl: intlShape.isRequired, updateParent: React.PropTypes.func.isRequired, state: React.PropTypes.object }; + +export default injectIntl(TeamSignupWelcomePage); \ No newline at end of file diff --git a/web/static/i18n/en.json b/web/static/i18n/en.json index 5127d38d5..1a82660fd 100644 --- a/web/static/i18n/en.json +++ b/web/static/i18n/en.json @@ -416,6 +416,12 @@ "claim.sso_to_email.description": "Upon changing your account type, you will only be able to login with your email and password.", "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", + "email_verify.verified": "{siteName} Email Verified", + "email_verify.verifiedBody": "

    Your email has been verified! Click here to log in.

    ", + "email_verify.almost": "{siteName}: You are almost done", + "email_verify.notVerifiedBody": "Please verify your email address. Check your inbox for an email.", + "email_verify.resend": "Resend Email", + "email_verify.sent": " Verification email sent.", "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", @@ -463,11 +469,30 @@ "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_complete.completed": "You've already completed the signup process for this invitation or this invitation has expired.", "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.", + "signup_user_completed.required": "This field is required", + "signup_user_completed.validEmail": "Please enter a valid email address", + "signup_user_completed.reserved": "This username is reserved, please choose a new one.", + "signup_user_completed.usernameLength": "Username must begin with a letter, and contain between {min} to {max} lowercase characters made up of numbers, letters, and the symbols '.', '-' and '_'.", + "signup_user_completed.passwordLength": "Please enter at least {min} characters", + "signup_user_completed.expired": "You've already completed the signup process for this invitation or this invitation has expired.", + "signup_user_completed.userHelp": "Username must begin with a letter, and contain between {min} to {max} lowercase characters made up of numbers, letters, and the symbols '.', '-' and '_'", + "signup_user_completed.emailIs": "Your email address is {email}. You'll use this address to sign in to {siteName}.", + "signup_user_completed.whatis": "What's your email address?", + "signup_user_completed.gitlab": "with GitLab", + "signup_user_completed.google": "with Google", + "signup_user_completed.chooseUser": "Choose your username", + "signup_user_completed.choosePwd": "Choose your password", + "signup_user_completed.create": "Create Account", + "signup_user_completed.or": "or", + "signup_user_completed.welcome": "Welcome to:", + "signup_user_completed.onSite": "on {siteName}", + "signup_user_completed.lets": "Let's create your account", "suggestion.mention.all": "Notifies everyone in the team", "suggestion.mention.channel": "Notifies everyone in the channel", "suggestion.search.public": "Public Channels", @@ -477,6 +502,61 @@ "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", + "team_signup_display_name.required": "This field is required", + "team_signup_display_name.charLength": "Name must be 4 or more characters up to a maximum of 15", + "team_signup_display_name.teamName": "Team Name", + "team_signup_display_name.nameHelp": "Name your team in any language. Your team name shows in menus and headings.", + "team_signup_display_name.next": "Next", + "team_signup_display_name.back": "Back to previous step", + "team_signup_email.validEmail": "Please enter a valid email address", + "team_signup_email.different": "Please use a different email than the one used at signup", + "team_signup_email.address": "Email Address", + "team_signup_password.passwordError": "Please enter at least {chars} characters", + "team_signup_password.creating": "Creating team...", + "team_signup_password.yourPassword": "Your password", + "team_signup_password.selectPassword": "Select a password that you'll use to login with your email address:", + "team_signup_password.email": "Email", + "team_signup_password.choosePwd": "Choose your password", + "team_signup_password.hint": "Passwords must contain {min} to {max} characters. Your password will be strongest if it contains a mix of symbols, numbers, and upper and lowercase characters.", + "team_signup_password.finish": "Finish", + "team_signup_password.agreement": "By proceeding to create your account and use {siteName}, you agree to our Terms of Service and Privacy Policy. If you do not agree, you cannot use {siteName}.", + "team_signup_password.back": "Back to previous step", + "team_signup_send_invites.addInvitation": "Add Invitation", + "team_signup_send_invites.prefer": "if you prefer, you can invite team members later
    and ", + "team_signup_send_invites.skip": "skip this step ", + "team_signup_send_invites.forNow": "for now.", + "team_signup_send_invites.disabled": "Email is currently disabled for your team, and emails cannot be sent. Contact your system administrator to enable email and email invitations.", + "team_signup_send_invites.title": "Invite Team Members", + "team_signup_send_invites.next": "Next", + "team_signup_send_invites.back": "Back to previous step", + "team_signup_url.required": "This field is required", + "team_signup_url.regex": "Use only lower case letters, numbers and dashes. Must start with a letter and can't end in a dash.", + "team_signup_url.charLength": "Name must be 4 or more characters up to a maximum of 15", + "team_signup_url.taken": "URL is taken or contains a reserved word", + "team_signup_url.unavailable": "This URL is unavailable. Please try another.", + "team_signup_url.teamUrl": "Team URL", + "team_signup_url.webAddress": "Choose the web address of your new team:", + "team_signup_url.hint": "
  • Short and memorable is best
  • \n
  • Use lowercase letters, numbers and dashes
  • \n
  • Must start with a letter and can't end in a dash
  • ", + "team_signup_url.next": "Next", + "team_signup_url.back": "Back to previous step", + "team_signup_username.reserved": "This username is reserved, please choose a new one.", + "team_signup_username.invalid": "Username must begin with a letter, and contain between {min} to {max} characters in total, which may be numbers, lowercase letters, or any of the symbols '.', '-', or '_'", + "team_signup_username.hint": "Usernames must begin with a letter and contain between {min} to {max} characters made up of lowercase letters, numbers, and the symbols '.', '-' and '_'", + "team_signup_username.username": "Your username", + "team_signup_username.memorable": "Select a memorable username that makes it easy for teammates to identify you:", + "team_signup_username.chooseUsername": "Choose your username", + "team_signup_username.next": "Next", + "team_signup_username.back": "Back to previous step", + "team_signup_welcome.storageError": "This service requires local storage to be enabled. Please enable it or exit private browsing.", + "team_signup_welcome.validEmailError": "Please enter a valid email address", + "team_signup_welcome.address": "Email Address", + "team_signup_welcome.welcome": "Welcome to:", + "team_signup_welcome.lets": "Let's set up your new team", + "team_signup_welcome.confirm": "Please confirm your email address:", + "team_signup_welcome.admin": "Your account will administer the new team site.
    \n You can add other administrators later.", + "team_signup_welcome.yes": "Yes, this address is correct", + "team_signup_welcome.instead": "Use this instead", + "team_signup_welcome.different": "Use a different email", "email_signup.emailError": "Please enter a valid email address", "email_signup.address": "Email Address", "email_signup.createTeam": "Create Team", diff --git a/web/static/i18n/es.json b/web/static/i18n/es.json index 8743a9b4f..d54c99535 100644 --- a/web/static/i18n/es.json +++ b/web/static/i18n/es.json @@ -425,6 +425,12 @@ "email_signup.createTeam": "Crear Equipo", "email_signup.emailError": "Por favor ingresa una dirección de correos válida", "email_signup.find": "Encontrar mi equipo", + "email_verify.almost": "{siteName}: Ya casi estás listo", + "email_verify.notVerifiedBody": "Por favor verifica tu correo electrónico. Revisa tu bandeja de entrada, hemos enviado un correo de verificación.", + "email_verify.resend": "Reenviar Correo", + "email_verify.sent": " Correo de verificación enviado.", + "email_verify.verified": "{siteName} Correo electrónico verificado", + "email_verify.verifiedBody": "

    Tu correo electrónico ha sido verificado!! Pincha aquí para iniciar sesión.

    ", "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.", @@ -435,7 +441,7 @@ "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.create": "Crea una ahora", "login.createTeam": "Crear un nuevo equipo", "login.find": "Encuentra tus otros equipos", "login.forgot": "Olvide mi contraseña", @@ -477,6 +483,25 @@ "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.", + "signup_team_complete.completed": "Ya haz completado el proceso de entrada para esta invitación, o esta invitación ya ha expirado.", + "signup_user_completed.choosePwd": "Escoge tu contraseña", + "signup_user_completed.chooseUser": "Escoge tu nombre de usuario", + "signup_user_completed.create": "Crea una Cuenta", + "signup_user_completed.emailIs": "Tu dirección de correo electrónico es {email}. Utiliza está dirección para ingresar a {siteName}.", + "signup_user_completed.expired": "Ya haz completado el proceso de registro para esta invitación, o esta invitación ya ha expirado.", + "signup_user_completed.gitlab": "con GitLab", + "signup_user_completed.google": "con Google", + "signup_user_completed.lets": "Vamos a crear tu cuenta", + "signup_user_completed.onSite": "en {siteName}", + "signup_user_completed.or": "o", + "signup_user_completed.passwordLength": "Por favor ingresa al menos {min} caracteres", + "signup_user_completed.required": "Este campo es obligatorio", + "signup_user_completed.reserved": "Este nombre de usuario está reservado, por favor escoge uno nuevo.", + "signup_user_completed.userHelp": "El nombre de usuario debe empezar con una letra, y contener entre {min} a {max} caracteres en minúscula con números, letras, y los símbolos '.', '-' y '_'.", + "signup_user_completed.usernameLength": "El nombre de usuario debe comenzar con una letra, y debe contener entre {min} y {max} caracteres en minúscula y confeccionado por numeros, letras y los simbolos '.', '-' and '_'.", + "signup_user_completed.validEmail": "Por favor ingresa una dirección de correo electrónico válida", + "signup_user_completed.welcome": "Bienvenido a:", + "signup_user_completed.whatis": "¿Cuál es tu dirección de correo electrónico?", "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", @@ -487,6 +512,61 @@ "suggestion.mention.channel": "Notifica a todas las personas en el canal", "suggestion.search.private": "Grupos Privados", "suggestion.search.public": "Canales Públicos", + "team_signup_display_name.back": "Volver al paso previo", + "team_signup_display_name.charLength": "El nombre debe tener 4 o más caracteres hasta un máximo de 15", + "team_signup_display_name.nameHelp": "Nombre tu equipo en cualquier idioma. El nombre de tu equipo aparecerá en menús y en inicios", + "team_signup_display_name.next": "Siguiente", + "team_signup_display_name.required": "Este campo es obligatorio", + "team_signup_display_name.teamName": "Nombre del Equipo", + "team_signup_email.address": "Dirección de correo electrónico", + "team_signup_email.different": "Please use a different email than the one used at signup", + "team_signup_email.validEmail": "Por favor ingresa una dirección de correo electrónico válida", + "team_signup_password.agreement": "Procediendo a crear tu cuenta y el uso de {siteName}, indicas que estás de acuerdo con nuestros Términos de Servicio y Políticas de Privacidad. Si no estás de acuerdo, no debes utilizar {siteName}.", + "team_signup_password.back": "Volver al paso previo", + "team_signup_password.choosePwd": "Escoge tu contraseña", + "team_signup_password.creating": "Creando equipo...", + "team_signup_password.email": "Correo electrónico", + "team_signup_password.finish": "Finalizar", + "team_signup_password.hint": "Las contraseñas deben contener de {min} a {max} caracteres. Su contraseña será más fuerte si contiene una mezcla de símbolos, números y caracteres en mayúsculas y minúsculas.", + "team_signup_password.passwordError": "Por favor ingrese al menos {chars} caracteres", + "team_signup_password.selectPassword": "Selecciona la contraseña que estás usando con tu dirección de correos:", + "team_signup_password.yourPassword": "Tu contraseña", + "team_signup_send_invites.addInvitation": "Agrega una Invitación", + "team_signup_send_invites.back": "Volver al paso previo", + "team_signup_send_invites.disabled": "Este correo electrónico está actualmente deshabilitado para tu equipo, y los correos no podrán ser enviados. Contacta a tu administrador de sistemas", + "team_signup_send_invites.forNow": "por ahora.", + "team_signup_send_invites.next": "Siguiente", + "team_signup_send_invites.prefer": "Si prefieres, puedes invitar a miembros de equipo más tarde
    y ", + "team_signup_send_invites.skip": "saltarte este paso ", + "team_signup_send_invites.title": "Invita Miembros al Equipo", + "team_signup_url.back": "Volver al paso previo", + "team_signup_url.charLength": "El nombre debe tener 4 o más caracteres hasta un máximo de 15", + "team_signup_url.hint": "
  • Corto y memorizable es mejor
  • Use letras en minúsculas, números y guiones
  • Debe empezar con una letra y no puede finalizar con un guión
  • ", + "team_signup_url.next": "Siguiente", + "team_signup_url.regex": "Sólo utiliza letras en minúsculas, numeros y guiones. Debe comenzar con una letra y no puede terminar en un guión.", + "team_signup_url.required": "Este campo es obligatorio", + "team_signup_url.taken": "Este URL ya fue asignado o contiene una palabra reservada", + "team_signup_url.teamUrl": "URL de Equipo", + "team_signup_url.unavailable": "Este URL no está disponible. Por favor intenta con otro.", + "team_signup_url.webAddress": "Escoge la dirección web de tu nuevo equipo:", + "team_signup_username.back": "Volver al paso previo", + "team_signup_username.chooseUsername": "Escoge un nombre de usuario", + "team_signup_username.hint": "El nombre de usuario debe empezar con una letra, y contener entre {min} a {max} caracteres en minúscula con números, letras, y los símbolos '.', '-' y '_'.", + "team_signup_username.invalid": "El nombre de usuario debe comenzar con una letra, y tener entre {min} y {max} de caracteres en total, los cuales pueden ser numeros, letras en minúsculas, o cualquiera de los simbolos '.', '-', o '_'", + "team_signup_username.memorable": "Selecciona un nombre de usuario sencillo de recordar y que sea fácil para a tus compañeros de equipo identificarte:", + "team_signup_username.next": "Siguiente", + "team_signup_username.reserved": "Este nombre de usuario está reservado. Por favor escoge otro.", + "team_signup_username.username": "Tu nombre de usuario", + "team_signup_welcome.address": "Dirección de correo", + "team_signup_welcome.admin": "Tu cuenta administrará un nuevo sitio del equipo.
    Puedes agregar otros administradores más adelante.", + "team_signup_welcome.confirm": "Por favor confirma tu dirección de correos:", + "team_signup_welcome.different": "Usa un correo diferente", + "team_signup_welcome.instead": "Usa este en vez de", + "team_signup_welcome.lets": "permítenos setear tu nuevo equipo", + "team_signup_welcome.storageError": "Este servicio requiere de almacenamiento local para ser habilitado. Por favor habilítalo o sale de la navegación privad.", + "team_signup_welcome.validEmailError": "Por favor ingresa una dirección de correo electrónico válida", + "team_signup_welcome.welcome": "Bienvenido a:", + "team_signup_welcome.yes": "Sí, esta dirección es correcta", "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", -- cgit v1.2.3-1-g7c22 From 28fe2356866a472f5e118898dec572313560081b Mon Sep 17 00:00:00 2001 From: Asaad Mahmood Date: Fri, 29 Jan 2016 21:11:46 +0500 Subject: Improvements to system console --- web/sass-files/sass/partials/_admin-console.scss | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/web/sass-files/sass/partials/_admin-console.scss b/web/sass-files/sass/partials/_admin-console.scss index b28c7d984..dc8b950e4 100644 --- a/web/sass-files/sass/partials/_admin-console.scss +++ b/web/sass-files/sass/partials/_admin-console.scss @@ -5,6 +5,15 @@ height: 100%; } + .inner__wrap { + position: absolute; + width: 100%; + } + + .row { + margin: 0; + } + h3 { font-weight: 600; border-bottom: 1px solid #ddd; -- cgit v1.2.3-1-g7c22 From 6b7438b52fdaabe46cb298af1b325c3d8ece1af4 Mon Sep 17 00:00:00 2001 From: Elias Nahum Date: Fri, 29 Jan 2016 13:48:12 -0300 Subject: PLT-7: Refactoring frontend (chunk 6) - Authorize - Signup Team Confirm - Footer --- i18n/en.json | 16 +++++ i18n/es.json | 16 +++++ web/react/components/authorize.jsx | 95 +++++++++++++++------------- web/react/components/signup_team_confirm.jsx | 42 ++++++++++++ web/react/pages/signup_team_confirm.jsx | 67 ++++++++++++++++++++ web/static/i18n/en.json | 6 +- web/static/i18n/es.json | 2 + web/templates/authorize.html | 18 +----- web/templates/footer.html | 8 +-- web/templates/signup_team_confirm.html | 9 ++- web/web.go | 5 ++ 11 files changed, 214 insertions(+), 70 deletions(-) create mode 100644 web/react/components/signup_team_confirm.jsx create mode 100644 web/react/pages/signup_team_confirm.jsx diff --git a/i18n/en.json b/i18n/en.json index ef432f499..a5eb4f607 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -3203,6 +3203,22 @@ "id": "web.find_team.title", "translation": "Find Team" }, + { + "id": "web.footer.about", + "translation": "About" + }, + { + "id": "web.footer.help", + "translation": "Help" + }, + { + "id": "web.footer.privacy", + "translation": "Privacy" + }, + { + "id": "web.footer.terms", + "translation": "Terms" + }, { "id": "web.get_access_token.bad_client_id.app_error", "translation": "invalid_request: Bad client_id" diff --git a/i18n/es.json b/i18n/es.json index 511cc5f28..f0f157474 100644 --- a/i18n/es.json +++ b/i18n/es.json @@ -3203,6 +3203,22 @@ "id": "web.find_team.title", "translation": "Encontrar Equipo" }, + { + "id": "web.footer.about", + "translation": "Acerca" + }, + { + "id": "web.footer.help", + "translation": "Ayuda" + }, + { + "id": "web.footer.privacy", + "translation": "Privacidad" + }, + { + "id": "web.footer.terms", + "translation": "Términos" + }, { "id": "web.get_access_token.bad_client_id.app_error", "translation": "invalid_request: client_id malo" diff --git a/web/react/components/authorize.jsx b/web/react/components/authorize.jsx index 90cbe3289..4a4985268 100644 --- a/web/react/components/authorize.jsx +++ b/web/react/components/authorize.jsx @@ -3,7 +3,7 @@ import * as Client from '../utils/client.jsx'; -import {FormattedMessage} from 'mm-intl'; +import {FormattedMessage, FormattedHTMLMessage} from 'mm-intl'; export default class Authorize extends React.Component { constructor(props) { @@ -35,58 +35,67 @@ export default class Authorize extends React.Component { } render() { return ( -
    -
    -

    - -

    -
    ); diff --git a/web/react/components/signup_team_confirm.jsx b/web/react/components/signup_team_confirm.jsx new file mode 100644 index 000000000..84f8e34aa --- /dev/null +++ b/web/react/components/signup_team_confirm.jsx @@ -0,0 +1,42 @@ +/** + * Created by enahum on 1/29/16. + */ +// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import {FormattedMessage, FormattedHTMLMessage} from 'mm-intl'; + +export default class SignupTeamConfirm extends React.Component { + constructor(props) { + super(props); + } + + render() { + return ( +
    +

    + +

    +

    + +

    +
    + ); + } +} + +SignupTeamConfirm.defaultProps = { + email: '' +}; +SignupTeamConfirm.propTypes = { + email: React.PropTypes.string +}; diff --git a/web/react/pages/signup_team_confirm.jsx b/web/react/pages/signup_team_confirm.jsx new file mode 100644 index 000000000..335be6a49 --- /dev/null +++ b/web/react/pages/signup_team_confirm.jsx @@ -0,0 +1,67 @@ +/** + * Created by enahum on 1/29/16. + */ +// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import SignupTeamConfirm from '../components/signup_team_confirm.jsx'; +import * as Client from '../utils/client.jsx'; + +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
    ; + } + + return ( + + + + ); + } +} + +global.window.setup_signup_team_confirm_page = function setup(props) { + ReactDOM.render( + , + document.getElementById('signup-team-confirm') + ); +}; \ No newline at end of file diff --git a/web/static/i18n/en.json b/web/static/i18n/en.json index 1a82660fd..45000a890 100644 --- a/web/static/i18n/en.json +++ b/web/static/i18n/en.json @@ -397,8 +397,8 @@ "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.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", @@ -470,6 +470,8 @@ "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_complete.completed": "You've already completed the signup process for this invitation or this invitation has expired.", + "signup_team_confirm.title": "Sign up Complete", + "signup_team_confirm.checkEmail": "Please check your email: {email}
    Your email contains a link to set up your team", "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", diff --git a/web/static/i18n/es.json b/web/static/i18n/es.json index d54c99535..48412510d 100644 --- a/web/static/i18n/es.json +++ b/web/static/i18n/es.json @@ -484,6 +484,8 @@ "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.", "signup_team_complete.completed": "Ya haz completado el proceso de entrada para esta invitación, o esta invitación ya ha expirado.", + "signup_team_confirm.checkEmail": "Por favor revisa tu correo electrónico: {email}
    Se ha enviado un correo con un enlace para configurar tu equipo", + "signup_team_confirm.title": "Registro Completado", "signup_user_completed.choosePwd": "Escoge tu contraseña", "signup_user_completed.chooseUser": "Escoge tu nombre de usuario", "signup_user_completed.create": "Crea una Cuenta", diff --git a/web/templates/authorize.html b/web/templates/authorize.html index 430291676..0fa36b0ab 100644 --- a/web/templates/authorize.html +++ b/web/templates/authorize.html @@ -2,24 +2,10 @@ {{template "head" . }} -
    -
    -
    -
    - -
    -
    An application would like to connect to your {{.Props.TeamName}} account.
    -
    -

    The app {{.Props.AppName}} would like the ability to access Mattermost on your behalf.

    -

    Allow {{.Props.AppName}} access?

    -
    - - -
    -
    +
    diff --git a/web/templates/footer.html b/web/templates/footer.html index 60dd5a40e..5b11328fb 100644 --- a/web/templates/footer.html +++ b/web/templates/footer.html @@ -5,10 +5,10 @@
    {{end}} diff --git a/web/web.go b/web/web.go index beb0eff06..efe0e6b13 100644 --- a/web/web.go +++ b/web/web.go @@ -54,6 +54,11 @@ func (me *HtmlTemplatePage) Render(c *api.Context, w http.ResponseWriter) { me.Props["Locale"] = me.Locale me.SessionTokenIndex = c.SessionTokenIndex + me.ClientCfg["FooterHelp"] = c.T("web.footer.help") + me.ClientCfg["FooterTerms"] = c.T("web.footer.terms") + me.ClientCfg["FooterPrivacy"] = c.T("web.footer.privacy") + me.ClientCfg["FooterAbout"] = c.T("web.footer.about") + if err := Templates.ExecuteTemplate(w, me.TemplateName, me); err != nil { c.SetUnknownError(me.TemplateName, err.Error()) } -- cgit v1.2.3-1-g7c22 From edc0e3868790f12920543a6bd9fa96aa4ca3b4ed Mon Sep 17 00:00:00 2001 From: Elias Nahum Date: Fri, 29 Jan 2016 13:56:33 -0300 Subject: Remove author --- web/react/components/signup_team_confirm.jsx | 3 --- web/react/pages/signup_team_confirm.jsx | 3 --- 2 files changed, 6 deletions(-) diff --git a/web/react/components/signup_team_confirm.jsx b/web/react/components/signup_team_confirm.jsx index 84f8e34aa..de83285db 100644 --- a/web/react/components/signup_team_confirm.jsx +++ b/web/react/components/signup_team_confirm.jsx @@ -1,6 +1,3 @@ -/** - * Created by enahum on 1/29/16. - */ // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. diff --git a/web/react/pages/signup_team_confirm.jsx b/web/react/pages/signup_team_confirm.jsx index 335be6a49..9a536c92e 100644 --- a/web/react/pages/signup_team_confirm.jsx +++ b/web/react/pages/signup_team_confirm.jsx @@ -1,6 +1,3 @@ -/** - * Created by enahum on 1/29/16. - */ // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. -- cgit v1.2.3-1-g7c22 From 3d5bf17349fb139f627bc4916d6f704ad159d5a6 Mon Sep 17 00:00:00 2001 From: Tatsuya Niwa Date: Sun, 24 Jan 2016 23:54:49 +0900 Subject: PLT-588 Add a warning when a user demotes themselves from System Admin to another role. --- .../admin_console/demote_own_role_modal.jsx | 91 +++++++++++++++++ web/react/components/admin_console/user_item.jsx | 111 ++++++++++++++++----- 2 files changed, 177 insertions(+), 25 deletions(-) create mode 100644 web/react/components/admin_console/demote_own_role_modal.jsx diff --git a/web/react/components/admin_console/demote_own_role_modal.jsx b/web/react/components/admin_console/demote_own_role_modal.jsx new file mode 100644 index 000000000..63d4dca5c --- /dev/null +++ b/web/react/components/admin_console/demote_own_role_modal.jsx @@ -0,0 +1,91 @@ +// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import * as AsyncClient from '../../utils/async_client.jsx'; +import * as Client from '../../utils/client.jsx'; +const Modal = ReactBootstrap.Modal; +import * as Utils from '../../utils/utils.jsx'; + +export default class DemoteOwnRoleModal extends React.Component { + constructor(props) { + super(props); + + this.doDemote = this.doDemote.bind(this); + this.doCancel = this.doCancel.bind(this); + + this.state = { + serverError: null + }; + } + + doDemote() { + const data = { + user_id: this.props.user.id, + new_roles: this.props.role + }; + console.log(JSON.stringify(data)); + + Client.updateRoles(data, + () => { + this.setState({serverError: null}); + this.props.onModalSubmit(); + }, + (err) => { + this.setState({serverError: err.message}); + } + ); + } + + doCancel() { + this.setState({serverError: null}); + this.props.onModalDismissed(); + } + + render() { + let serverError = null; + + if (this.state.serverError) { + serverError =
    + } + + return ( + + +

    {'Confirm demotion from System Admin role'}

    +
    + + If you demote yourself from the System Admin role and there is not another user with System Admin privileges, you'll need to re-assign a System Admin by accessing the Mattermost server through a terminal and running the following command.

    ./platform -assign_role -team_name="yourteam" -email="name@yourcompany.com" -role="system_admin" + {serverError} +
    + + + + +
    + ); + } +} + +DemoteOwnRoleModal.propTypes = { + user: React.PropTypes.object, + role: React.PropTypes.string, + show: React.PropTypes.bool.isRequired, + onModalSubmit: React.PropTypes.func, + onModalDismissed: React.PropTypes.func +}; diff --git a/web/react/components/admin_console/user_item.jsx b/web/react/components/admin_console/user_item.jsx index 59b4861e4..9220b2cc3 100644 --- a/web/react/components/admin_console/user_item.jsx +++ b/web/react/components/admin_console/user_item.jsx @@ -3,6 +3,8 @@ import * as Client from '../../utils/client.jsx'; import * as Utils from '../../utils/utils.jsx'; +import UserStore from '../../stores/user_store.jsx'; +import DemoteOwnRoleModal from './demote_own_role_modal.jsx'; import {FormattedMessage} from 'mm-intl'; @@ -16,25 +18,38 @@ export default class UserItem extends React.Component { this.handleMakeAdmin = this.handleMakeAdmin.bind(this); this.handleMakeSystemAdmin = this.handleMakeSystemAdmin.bind(this); this.handleResetPassword = this.handleResetPassword.bind(this); + this.doDemote = this.doDemote.bind(this); + this.doDemoteSubmit = this.doDemoteSubmit.bind(this); + this.doDemoteDismiss = this.doDemoteDismiss.bind(this); - this.state = {}; + this.state = { + serverError: null, + showDemoteModal: false, + user: null, + role: null + }; } handleMakeMember(e) { e.preventDefault(); - const data = { - user_id: this.props.user.id, - new_roles: '' - }; + var me = UserStore.getCurrentUser(); + if (this.props.user.id === me.id) { + this.doDemote(this.props.user, ''); + } else { + const data = { + user_id: this.props.user.id, + new_roles: '' + }; - Client.updateRoles(data, - () => { - this.props.refreshProfiles(); - }, - (err) => { - this.setState({serverError: err.message}); - } - ); + Client.updateRoles(data, + () => { + this.props.refreshProfiles(); + }, + (err) => { + this.setState({serverError: err.message}); + } + ); + } } handleMakeActive(e) { @@ -63,19 +78,24 @@ export default class UserItem extends React.Component { handleMakeAdmin(e) { e.preventDefault(); - const data = { - user_id: this.props.user.id, - new_roles: 'admin' - }; + var me = UserStore.getCurrentUser(); + if (this.props.user.id === me.id) { + this.doDemote(this.props.user, 'admin'); + } else { + const data = { + user_id: this.props.user.id, + new_roles: 'admin' + }; - Client.updateRoles(data, - () => { - this.props.refreshProfiles(); - }, - (err) => { - this.setState({serverError: err.message}); - } - ); + Client.updateRoles(data, + () => { + this.props.refreshProfiles(); + }, + (err) => { + this.setState({serverError: err.message}); + } + ); + } } handleMakeSystemAdmin(e) { @@ -100,6 +120,33 @@ export default class UserItem extends React.Component { this.props.doPasswordReset(this.props.user); } + doDemote(user, role) { + this.setState({ + serverError: this.state.serverError, + showDemoteModal: true, + user, + role + }); + } + + doDemoteDismiss() { + this.setState({ + serverError: this.state.serverError, + showDemoteModal: false, + user: null, + role: null + }); + } + + doDemoteSubmit() { + this.setState({ + serverError: this.state.serverError, + showDemoteModal: false, + user: null, + role: null + }); + } + render() { let serverError = null; if (this.state.serverError) { @@ -247,6 +294,19 @@ export default class UserItem extends React.Component { ); } + var me = UserStore.getCurrentUser(); + let makeDemoteModal = null; + if (this.props.user.id === me.id) { + makeDemoteModal = ( + + ); + } return ( @@ -293,6 +353,7 @@ export default class UserItem extends React.Component { + {makeDemoteModal} {serverError} -- cgit v1.2.3-1-g7c22 From fecb5870185d36c0649bb2ec343796fc00a4344f Mon Sep 17 00:00:00 2001 From: Tatsuya Niwa Date: Mon, 25 Jan 2016 00:39:40 +0900 Subject: fix ESLint warnings. --- web/react/components/admin_console/demote_own_role_modal.jsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/web/react/components/admin_console/demote_own_role_modal.jsx b/web/react/components/admin_console/demote_own_role_modal.jsx index 63d4dca5c..18747e33e 100644 --- a/web/react/components/admin_console/demote_own_role_modal.jsx +++ b/web/react/components/admin_console/demote_own_role_modal.jsx @@ -1,10 +1,8 @@ // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. -import * as AsyncClient from '../../utils/async_client.jsx'; import * as Client from '../../utils/client.jsx'; const Modal = ReactBootstrap.Modal; -import * as Utils from '../../utils/utils.jsx'; export default class DemoteOwnRoleModal extends React.Component { constructor(props) { @@ -23,7 +21,6 @@ export default class DemoteOwnRoleModal extends React.Component { user_id: this.props.user.id, new_roles: this.props.role }; - console.log(JSON.stringify(data)); Client.updateRoles(data, () => { @@ -45,7 +42,7 @@ export default class DemoteOwnRoleModal extends React.Component { let serverError = null; if (this.state.serverError) { - serverError =
    + serverError =
    } return ( -- cgit v1.2.3-1-g7c22 From 27f7b9d1219257958579fee104adf8a0e2e37a9f Mon Sep 17 00:00:00 2001 From: Tatsuya Niwa Date: Mon, 25 Jan 2016 00:46:59 +0900 Subject: fix ESLint warnings. --- web/react/components/admin_console/demote_own_role_modal.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/react/components/admin_console/demote_own_role_modal.jsx b/web/react/components/admin_console/demote_own_role_modal.jsx index 18747e33e..58646186d 100644 --- a/web/react/components/admin_console/demote_own_role_modal.jsx +++ b/web/react/components/admin_console/demote_own_role_modal.jsx @@ -42,7 +42,7 @@ export default class DemoteOwnRoleModal extends React.Component { let serverError = null; if (this.state.serverError) { - serverError =
    + serverError =
    ; } return ( -- cgit v1.2.3-1-g7c22 From d8c4705bba161342bb7d2b4dcd8954203d972614 Mon Sep 17 00:00:00 2001 From: Tatsuya Niwa Date: Tue, 26 Jan 2016 23:05:26 +0900 Subject: delete DemoteOwnRoleModal --- .../admin_console/demote_own_role_modal.jsx | 88 ---------------------- web/react/components/admin_console/user_item.jsx | 57 ++++++++------ 2 files changed, 36 insertions(+), 109 deletions(-) delete mode 100644 web/react/components/admin_console/demote_own_role_modal.jsx diff --git a/web/react/components/admin_console/demote_own_role_modal.jsx b/web/react/components/admin_console/demote_own_role_modal.jsx deleted file mode 100644 index 58646186d..000000000 --- a/web/react/components/admin_console/demote_own_role_modal.jsx +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -import * as Client from '../../utils/client.jsx'; -const Modal = ReactBootstrap.Modal; - -export default class DemoteOwnRoleModal extends React.Component { - constructor(props) { - super(props); - - this.doDemote = this.doDemote.bind(this); - this.doCancel = this.doCancel.bind(this); - - this.state = { - serverError: null - }; - } - - doDemote() { - const data = { - user_id: this.props.user.id, - new_roles: this.props.role - }; - - Client.updateRoles(data, - () => { - this.setState({serverError: null}); - this.props.onModalSubmit(); - }, - (err) => { - this.setState({serverError: err.message}); - } - ); - } - - doCancel() { - this.setState({serverError: null}); - this.props.onModalDismissed(); - } - - render() { - let serverError = null; - - if (this.state.serverError) { - serverError =
    ; - } - - return ( - - -

    {'Confirm demotion from System Admin role'}

    -
    - - If you demote yourself from the System Admin role and there is not another user with System Admin privileges, you'll need to re-assign a System Admin by accessing the Mattermost server through a terminal and running the following command.

    ./platform -assign_role -team_name="yourteam" -email="name@yourcompany.com" -role="system_admin" - {serverError} -
    - - - - -
    - ); - } -} - -DemoteOwnRoleModal.propTypes = { - user: React.PropTypes.object, - role: React.PropTypes.string, - show: React.PropTypes.bool.isRequired, - onModalSubmit: React.PropTypes.func, - onModalDismissed: React.PropTypes.func -}; diff --git a/web/react/components/admin_console/user_item.jsx b/web/react/components/admin_console/user_item.jsx index 9220b2cc3..41bf0c99f 100644 --- a/web/react/components/admin_console/user_item.jsx +++ b/web/react/components/admin_console/user_item.jsx @@ -4,7 +4,7 @@ import * as Client from '../../utils/client.jsx'; import * as Utils from '../../utils/utils.jsx'; import UserStore from '../../stores/user_store.jsx'; -import DemoteOwnRoleModal from './demote_own_role_modal.jsx'; +import ConfirmModal from '../confirm_modal.jsx'; import {FormattedMessage} from 'mm-intl'; @@ -18,9 +18,9 @@ export default class UserItem extends React.Component { this.handleMakeAdmin = this.handleMakeAdmin.bind(this); this.handleMakeSystemAdmin = this.handleMakeSystemAdmin.bind(this); this.handleResetPassword = this.handleResetPassword.bind(this); - this.doDemote = this.doDemote.bind(this); - this.doDemoteSubmit = this.doDemoteSubmit.bind(this); - this.doDemoteDismiss = this.doDemoteDismiss.bind(this); + this.handleDemote = this.handleDemote.bind(this); + this.handleDemoteSubmit = this.handleDemoteSubmit.bind(this); + this.handleDemoteCancel = this.handleDemoteCancel.bind(this); this.state = { serverError: null, @@ -34,7 +34,7 @@ export default class UserItem extends React.Component { e.preventDefault(); var me = UserStore.getCurrentUser(); if (this.props.user.id === me.id) { - this.doDemote(this.props.user, ''); + this.handleDemote(this.props.user, ''); } else { const data = { user_id: this.props.user.id, @@ -80,7 +80,7 @@ export default class UserItem extends React.Component { e.preventDefault(); var me = UserStore.getCurrentUser(); if (this.props.user.id === me.id) { - this.doDemote(this.props.user, 'admin'); + this.handleDemote(this.props.user, 'admin'); } else { const data = { user_id: this.props.user.id, @@ -120,7 +120,7 @@ export default class UserItem extends React.Component { this.props.doPasswordReset(this.props.user); } - doDemote(user, role) { + handleDemote(user, role) { this.setState({ serverError: this.state.serverError, showDemoteModal: true, @@ -129,22 +129,36 @@ export default class UserItem extends React.Component { }); } - doDemoteDismiss() { + handleDemoteCancel() { this.setState({ - serverError: this.state.serverError, + serverError: null, showDemoteModal: false, user: null, role: null }); } - doDemoteSubmit() { - this.setState({ - serverError: this.state.serverError, - showDemoteModal: false, - user: null, - role: null - }); + handleDemoteSubmit() { + const data = { + user_id: this.props.user.id, + new_roles: this.props.role + }; + + Client.updateRoles(data, + () => { + this.setState({ + serverError: null, + showDemoteModal: false, + user: null, + role: null + }); + }, + (err) => { + this.setState({ + serverError: err.message + }); + } + ); } render() { @@ -298,12 +312,13 @@ export default class UserItem extends React.Component { let makeDemoteModal = null; if (this.props.user.id === me.id) { makeDemoteModal = ( - ,
    ,`./platform -assign_role -team_name="yourteam" -email="name@yourcompany.com" -role="system_admin"`,serverError]} + confirm_button='Confirm Demotion' + onConfirm={this.handleDemoteSubmit} + onCancel={this.handleDemoteCancel} /> ); } -- cgit v1.2.3-1-g7c22 From 1b804d563dbcbfec776b4083ae89b1222bafa76d Mon Sep 17 00:00:00 2001 From: Tatsuya Niwa Date: Tue, 26 Jan 2016 23:32:35 +0900 Subject: fix ESLint warnings. --- web/react/components/admin_console/user_item.jsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/web/react/components/admin_console/user_item.jsx b/web/react/components/admin_console/user_item.jsx index 41bf0c99f..511cab7ef 100644 --- a/web/react/components/admin_console/user_item.jsx +++ b/web/react/components/admin_console/user_item.jsx @@ -315,7 +315,7 @@ export default class UserItem extends React.Component { ,
    ,`./platform -assign_role -team_name="yourteam" -email="name@yourcompany.com" -role="system_admin"`,serverError]} + message={[`If you demote yourself from the System Admin role and there is not another user with System Admin privileges, you'll need to re-assign a System Admin by accessing the Mattermost server through a terminal and running the following command.`, React.createElement('br'), React.createElement('br'), `./platform -assign_role -team_name="yourteam" -email="name@yourcompany.com" -role="system_admin"`, serverError]} confirm_button='Confirm Demotion' onConfirm={this.handleDemoteSubmit} onCancel={this.handleDemoteCancel} @@ -378,6 +378,7 @@ export default class UserItem extends React.Component { UserItem.propTypes = { user: React.PropTypes.object.isRequired, + role: React.PropTypes.string, refreshProfiles: React.PropTypes.func.isRequired, doPasswordReset: React.PropTypes.func.isRequired }; -- cgit v1.2.3-1-g7c22 From c1b38e7119ac537f2dc6e446aec3990474ae8deb Mon Sep 17 00:00:00 2001 From: Tatsuya Niwa Date: Wed, 27 Jan 2016 20:49:33 +0900 Subject: fix role bug. add redirect after demote own role. --- web/react/components/admin_console/user_item.jsx | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/web/react/components/admin_console/user_item.jsx b/web/react/components/admin_console/user_item.jsx index 511cab7ef..26d294661 100644 --- a/web/react/components/admin_console/user_item.jsx +++ b/web/react/components/admin_console/user_item.jsx @@ -5,6 +5,7 @@ import * as Client from '../../utils/client.jsx'; import * as Utils from '../../utils/utils.jsx'; import UserStore from '../../stores/user_store.jsx'; import ConfirmModal from '../confirm_modal.jsx'; +import TeamStore from '../../stores/team_store.jsx'; import {FormattedMessage} from 'mm-intl'; @@ -32,7 +33,7 @@ export default class UserItem extends React.Component { handleMakeMember(e) { e.preventDefault(); - var me = UserStore.getCurrentUser(); + let me = UserStore.getCurrentUser(); if (this.props.user.id === me.id) { this.handleDemote(this.props.user, ''); } else { @@ -78,7 +79,7 @@ export default class UserItem extends React.Component { handleMakeAdmin(e) { e.preventDefault(); - var me = UserStore.getCurrentUser(); + let me = UserStore.getCurrentUser(); if (this.props.user.id === me.id) { this.handleDemote(this.props.user, 'admin'); } else { @@ -141,7 +142,7 @@ export default class UserItem extends React.Component { handleDemoteSubmit() { const data = { user_id: this.props.user.id, - new_roles: this.props.role + new_roles: this.state.role }; Client.updateRoles(data, @@ -152,6 +153,13 @@ export default class UserItem extends React.Component { user: null, role: null }); + + let teamUrl = TeamStore.getCurrentTeamUrl(); + if (teamUrl) { + window.location.href = teamUrl; + } else { + window.location.href = '/'; + } }, (err) => { this.setState({ @@ -308,7 +316,7 @@ export default class UserItem extends React.Component { ); } - var me = UserStore.getCurrentUser(); + let me = UserStore.getCurrentUser(); let makeDemoteModal = null; if (this.props.user.id === me.id) { makeDemoteModal = ( @@ -378,7 +386,6 @@ export default class UserItem extends React.Component { UserItem.propTypes = { user: React.PropTypes.object.isRequired, - role: React.PropTypes.string, refreshProfiles: React.PropTypes.func.isRequired, doPasswordReset: React.PropTypes.func.isRequired }; -- cgit v1.2.3-1-g7c22 From ae2201b304dc5da907d23dfe1f7da6f7a01c2b6d Mon Sep 17 00:00:00 2001 From: Tatsuya Niwa Date: Wed, 27 Jan 2016 21:02:21 +0900 Subject: fix ESLint warnings. --- web/react/components/admin_console/user_item.jsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/web/react/components/admin_console/user_item.jsx b/web/react/components/admin_console/user_item.jsx index 26d294661..032b698cd 100644 --- a/web/react/components/admin_console/user_item.jsx +++ b/web/react/components/admin_console/user_item.jsx @@ -33,7 +33,7 @@ export default class UserItem extends React.Component { handleMakeMember(e) { e.preventDefault(); - let me = UserStore.getCurrentUser(); + const me = UserStore.getCurrentUser(); if (this.props.user.id === me.id) { this.handleDemote(this.props.user, ''); } else { @@ -79,7 +79,7 @@ export default class UserItem extends React.Component { handleMakeAdmin(e) { e.preventDefault(); - let me = UserStore.getCurrentUser(); + const me = UserStore.getCurrentUser(); if (this.props.user.id === me.id) { this.handleDemote(this.props.user, 'admin'); } else { @@ -154,7 +154,7 @@ export default class UserItem extends React.Component { role: null }); - let teamUrl = TeamStore.getCurrentTeamUrl(); + const teamUrl = TeamStore.getCurrentTeamUrl(); if (teamUrl) { window.location.href = teamUrl; } else { @@ -316,7 +316,7 @@ export default class UserItem extends React.Component { ); } - let me = UserStore.getCurrentUser(); + const me = UserStore.getCurrentUser(); let makeDemoteModal = null; if (this.props.user.id === me.id) { makeDemoteModal = ( -- cgit v1.2.3-1-g7c22 From 8b2fb86c3a21f7d4bccc4f3497e6f9d514cbad0c Mon Sep 17 00:00:00 2001 From: Tatsuya Niwa Date: Sat, 30 Jan 2016 02:19:41 +0900 Subject: fix text localization. --- web/react/components/admin_console/user_item.jsx | 26 ++++++++++++++++++++---- web/static/i18n/en.json | 3 +++ web/static/i18n/es.json | 3 +++ 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/web/react/components/admin_console/user_item.jsx b/web/react/components/admin_console/user_item.jsx index 032b698cd..5ab429dd5 100644 --- a/web/react/components/admin_console/user_item.jsx +++ b/web/react/components/admin_console/user_item.jsx @@ -7,7 +7,22 @@ import UserStore from '../../stores/user_store.jsx'; import ConfirmModal from '../confirm_modal.jsx'; import TeamStore from '../../stores/team_store.jsx'; -import {FormattedMessage} from 'mm-intl'; +import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'mm-intl'; + +var messages = defineMessages({ + confirmDemoteRoleTitle: { + id: 'admin.user_item.confirmDemoteRoleTitle', + defaultMessage: 'Confirm demotion from System Admin role' + }, + confirmDemotion: { + id: 'admin.user_item.confirmDemotion', + defaultMessage: 'Confirm Demotion' + }, + confirmDemoteDescription: { + id: 'admin.user_item.confirmDemoteDescription', + defaultMessage: 'If you demote yourself from the System Admin role and there is not another user with System Admin privileges, you\'ll need to re-assign a System Admin by accessing the Mattermost server through a terminal and running the following command.' + } +}); export default class UserItem extends React.Component { constructor(props) { @@ -322,9 +337,9 @@ export default class UserItem extends React.Component { makeDemoteModal = ( @@ -385,7 +400,10 @@ export default class UserItem extends React.Component { } UserItem.propTypes = { + intl: intlShape.isRequired, user: React.PropTypes.object.isRequired, refreshProfiles: React.PropTypes.func.isRequired, doPasswordReset: React.PropTypes.func.isRequired }; + +export default injectIntl(UserItem); diff --git a/web/static/i18n/en.json b/web/static/i18n/en.json index 1a82660fd..6afdafcff 100644 --- a/web/static/i18n/en.json +++ b/web/static/i18n/en.json @@ -396,6 +396,9 @@ "admin.user_item.makeActive": "Make Active", "admin.user_item.makeInactive": "Make Inactive", "admin.user_item.resetPwd": "Reset Password", + "admin.user_item.confirmDemoteRoleTitle": "Confirm demotion from System Admin role", + "admin.user_item.confirmDemotion": "Confirm Demotion", + "admin.user_item.confirmDemoteDescription": "If you demote yourself from the System Admin role and there is not another user with System Admin privileges, you'll need to re-assign a System Admin by accessing the Mattermost server through a terminal and running the following command.", "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?", diff --git a/web/static/i18n/es.json b/web/static/i18n/es.json index d54c99535..71465ad1e 100644 --- a/web/static/i18n/es.json +++ b/web/static/i18n/es.json @@ -396,6 +396,9 @@ "admin.user_item.resetPwd": "Reiniciar Contraseña", "admin.user_item.sysAdmin": "Admin de Sistema", "admin.user_item.teamAdmin": "Admin de Equipo", + "admin.user_item.confirmDemoteRoleTitle": "Confirm demotion from System Admin role", + "admin.user_item.confirmDemotion": "Confirm Demotion", + "admin.user_item.confirmDemoteDescription": "If you demote yourself from the System Admin role and there is not another user with System Admin privileges, you'll need to re-assign a System Admin by accessing the Mattermost server through a terminal and running the following command.", "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.", -- cgit v1.2.3-1-g7c22 From 78314c7d4d1417fd42ab48cbe41d360f80915453 Mon Sep 17 00:00:00 2001 From: Elias Nahum Date: Sat, 30 Jan 2016 18:10:04 -0300 Subject: PLT-7: Refactoring frontend (chunk 6) - User settings --- web/react/components/access_history_modal.jsx | 311 +++++++++++++++++---- web/react/components/activity_log_modal.jsx | 99 ++++++- web/react/components/confirm_modal.jsx | 6 +- web/react/components/register_app_modal.jsx | 157 +++++++++-- web/react/components/setting_item_max.jsx | 12 +- web/react/components/setting_item_min.jsx | 7 +- web/react/components/setting_picture.jsx | 32 ++- .../user_settings/custom_theme_chooser.jsx | 104 ++++++- .../user_settings/import_theme_modal.jsx | 44 ++- .../user_settings/manage_incoming_hooks.jsx | 49 +++- .../components/user_settings/manage_languages.jsx | 14 +- .../user_settings/manage_outgoing_hooks.jsx | 131 +++++++-- .../user_settings/user_settings_advanced.jsx | 101 ++++++- .../user_settings/user_settings_appearance.jsx | 40 ++- .../user_settings/user_settings_developer.jsx | 42 ++- .../user_settings/user_settings_display.jsx | 140 ++++++++-- .../user_settings/user_settings_general.jsx | 262 ++++++++++++++--- .../user_settings/user_settings_integrations.jsx | 51 +++- .../user_settings/user_settings_modal.jsx | 84 +++++- .../user_settings/user_settings_notifications.jsx | 226 ++++++++++++--- .../user_settings/user_settings_security.jsx | 153 ++++++++-- web/static/i18n/en.json | 266 +++++++++++++++++- web/static/i18n/es.json | 266 +++++++++++++++++- 23 files changed, 2266 insertions(+), 331 deletions(-) diff --git a/web/react/components/access_history_modal.jsx b/web/react/components/access_history_modal.jsx index 85c28ca5c..6319b5681 100644 --- a/web/react/components/access_history_modal.jsx +++ b/web/react/components/access_history_modal.jsx @@ -8,7 +8,188 @@ import * as AsyncClient from '../utils/async_client.jsx'; import LoadingScreen from './loading_screen.jsx'; import * as Utils from '../utils/utils.jsx'; -export default class AccessHistoryModal extends React.Component { +import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl'; + +const holders = defineMessages({ + sessionRevoked: { + id: 'access_history.sessionRevoked', + defaultMessage: 'The session with id {sessionId} was revoked' + }, + channelCreated: { + id: 'access_history.channelCreated', + defaultMessage: 'Created the {channelName} channel/group' + }, + establishedDM: { + id: 'access_history.establishedDM', + defaultMessage: 'Established a direct message channel with {username}' + }, + nameUpdated: { + id: 'access_history.nameUpdated', + defaultMessage: 'Updated the {channelName} channel/group name' + }, + headerUpdated: { + id: 'access_history.headerUpdated', + defaultMessage: 'Updated the {channelName} channel/group header' + }, + channelDeleted: { + id: 'access_history.channelDeleted', + defaultMessage: 'Deleted the channel/group with the URL {url}' + }, + userAdded: { + id: 'access_history.userAdded', + defaultMessage: 'Added {username} to the {channelName} channel/group' + }, + userRemoved: { + id: 'access_history.userRemoved', + defaultMessage: 'Removed {username} to the {channelName} channel/group' + }, + attemptedRegisterApp: { + id: 'access_history.attemptedRegisterApp', + defaultMessage: 'Attempted to register a new OAuth Application with ID {id}' + }, + attemptedAllowOAuthAccess: { + id: 'access_history.attemptedAllowOAuthAccess', + defaultMessage: 'Attempted to allow a new OAuth service access' + }, + successfullOAuthAccess: { + id: 'access_history.successfullOAuthAccess', + defaultMessage: 'Successfully gave a new OAuth service access' + }, + failedOAuthAccess: { + id: 'access_history.failedOAuthAccess', + defaultMessage: 'Failed to allow a new OAuth service access - the redirect URI did not match the previously registered callback' + }, + attemptedOAuthToken: { + id: 'access_history.attemptedOAuthToken', + defaultMessage: 'Attempted to get an OAuth access token' + }, + successfullOAuthToken: { + id: 'access_history.successfullOAuthToken', + defaultMessage: 'Successfully added a new OAuth service' + }, + oauthTokenFailed: { + id: 'access_history.oauthTokenFailed', + defaultMessage: 'Failed to get an OAuth access token - {token}' + }, + attemptedLogin: { + id: 'access_history.attemptedLogin', + defaultMessage: 'Attempted to login' + }, + successfullLogin: { + id: 'access_history.successfullLogin', + defaultMessage: 'Successfully logged in' + }, + failedLogin: { + id: 'access_history.failedLogin', + defaultMessage: 'FAILED login attempt' + }, + updatePicture: { + id: 'access_history.updatePicture', + defaultMessage: 'Updated your profile picture' + }, + updateGeneral: { + id: 'access_history.updateGeneral', + defaultMessage: 'Updated the general settings of your account' + }, + attemptedPassword: { + id: 'access_history.attemptedPassword', + defaultMessage: 'Attempted to change password' + }, + successfullPassword: { + id: 'access_history.successfullPassword', + defaultMessage: 'Successfully changed password' + }, + failedPassword: { + id: 'access_history.failedPassword', + defaultMessage: 'Failed to change password - tried to update user password who was logged in through oauth' + }, + updatedRol: { + id: 'access_history.updatedRol', + defaultMessage: 'Updated user role(s) to ' + }, + member: { + id: 'access_history.member', + defaultMessage: 'member' + }, + accountActive: { + id: 'access_history.accountActive', + defaultMessage: 'Account made active' + }, + accountInactive: { + id: 'access_history.accountInactive', + defaultMessage: 'Account made inactive' + }, + by: { + id: 'access_history.by', + defaultMessage: ' by {username}' + }, + byAdmin: { + id: 'access_history.byAdmin', + defaultMessage: ' by an admin' + }, + sentEmail: { + id: 'access_history.sentEmail', + defaultMessage: 'Sent an email to {email} to reset your password' + }, + attemptedReset: { + id: 'access_history.attemptedReset', + defaultMessage: 'Attempted to reset password' + }, + successfullReset: { + id: 'access_history.successfullReset', + defaultMessage: 'Successfully reset password' + }, + updateGlobalNotifications: { + id: 'access_history.updateGlobalNotifications', + defaultMessage: 'Updated your global notification settings' + }, + attemptedWebhookCreate: { + id: 'access_history.attemptedWebhookCreate', + defaultMessage: 'Attempted to create a webhook' + }, + succcessfullWebhookCreate: { + id: 'access_history.successfullWebhookCreate', + defaultMessage: 'Successfully created a webhook' + }, + failedWebhookCreate: { + id: 'access_history.failedWebhookCreate', + defaultMessage: 'Failed to create a webhook - bad channel permissions' + }, + attemptedWebhookDelete: { + id: 'access_history.attemptedWebhookDelete', + defaultMessage: 'Attempted to delete a webhook' + }, + successfullWebhookDelete: { + id: 'access_history.successfullWebhookDelete', + defaultMessage: 'Successfully deleted a webhook' + }, + failedWebhookDelete: { + id: 'access_history.failedWebhookDelete', + defaultMessage: 'Failed to delete a webhook - inappropriate conditions' + }, + logout: { + id: 'access_history.logout', + defaultMessage: 'Logged out of your account' + }, + verified: { + id: 'access_history.verified', + defaultMessage: 'Sucessfully verified your email address' + }, + revokedAll: { + id: 'access_history.revokedAll', + defaultMessage: 'Revoked all current sessions for the team' + }, + loginAttempt: { + id: 'access_history.loginAttempt', + defaultMessage: ' (Login attempt)' + }, + loginFailure: { + id: 'access_history.loginFailure', + defaultMessage: ' (Login failure)' + } +}); + +class AccessHistoryModal extends React.Component { constructor(props) { super(props); @@ -70,11 +251,12 @@ export default class AccessHistoryModal extends React.Component { this.setState({moreInfo: newMoreInfo}); } handleRevokedSession(sessionId) { - return 'The session with id ' + sessionId + ' was revoked'; + return this.props.intl.formatMessage(holders.sessionRevoked, {sessionId: sessionId}); } formatAuditInfo(currentAudit) { const currentActionURL = currentAudit.action.replace(/\/api\/v[1-9]/, ''); + const {formatMessage} = this.props.intl; let currentAuditDesc = ''; if (currentActionURL.indexOf('/channels') === 0) { @@ -96,17 +278,17 @@ export default class AccessHistoryModal extends React.Component { switch (currentActionURL) { case '/channels/create': - currentAuditDesc = 'Created the ' + channelName + ' channel/group'; + currentAuditDesc = formatMessage(holders.channelCreated, {channelName: channelName}); break; case '/channels/create_direct': - currentAuditDesc = 'Established a direct message channel with ' + Utils.getDirectTeammate(channelObj.id).username; + currentAuditDesc = formatMessage(holders.establishedDM, {username: Utils.getDirectTeammate(channelObj.id).username}); break; case '/channels/update': - currentAuditDesc = 'Updated the ' + channelName + ' channel/group name'; + currentAuditDesc = formatMessage(holders.nameUpdated, {channelName: channelName}); break; case '/channels/update_desc': // support the old path case '/channels/update_header': - currentAuditDesc = 'Updated the ' + channelName + ' channel/group header'; + currentAuditDesc = formatMessage(holders.headerUpdated, {channelName: channelName}); break; default: { let userIdField = []; @@ -123,11 +305,11 @@ export default class AccessHistoryModal extends React.Component { } if (/\/channels\/[A-Za-z0-9]+\/delete/.test(currentActionURL)) { - currentAuditDesc = 'Deleted the channel/group with the URL ' + channelURL; + currentAuditDesc = formatMessage(holders.channelDeleted, {url: channelURL}); } else if (/\/channels\/[A-Za-z0-9]+\/add/.test(currentActionURL)) { - currentAuditDesc = 'Added ' + username + ' to the ' + channelName + ' channel/group'; + currentAuditDesc = formatMessage(holders.userAdded, {username: username, channelName: channelName}); } else if (/\/channels\/[A-Za-z0-9]+\/remove/.test(currentActionURL)) { - currentAuditDesc = 'Removed ' + username + ' from the ' + channelName + ' channel/group'; + currentAuditDesc = formatMessage(holders.userRemoved, {username: username, channelName: channelName}); } break; @@ -141,31 +323,31 @@ export default class AccessHistoryModal extends React.Component { const clientIdField = oauthInfo[0].split('='); if (clientIdField[0] === 'client_id') { - currentAuditDesc = 'Attempted to register a new OAuth Application with ID ' + clientIdField[1]; + currentAuditDesc = formatMessage(holders.attemptedRegisterApp, {id: clientIdField[1]}); } break; } case '/oauth/allow': if (oauthInfo[0] === 'attempt') { - currentAuditDesc = 'Attempted to allow a new OAuth service access'; + currentAuditDesc = formatMessage(holders.attemptedAllowOAuthAccess); } else if (oauthInfo[0] === 'success') { - currentAuditDesc = 'Successfully gave a new OAuth service access'; + currentAuditDesc = formatMessage(holders.successfullOAuthAccess); } else if (oauthInfo[0] === 'fail - redirect_uri did not match registered callback') { - currentAuditDesc = 'Failed to allow a new OAuth service access - the redirect URI did not match the previously registered callback'; + currentAuditDesc = formatMessage(holders.failedOAuthAccess); } break; case '/oauth/access_token': if (oauthInfo[0] === 'attempt') { - currentAuditDesc = 'Attempted to get an OAuth access token'; + currentAuditDesc = formatMessage(holders.attemptedOAuthToken); } else if (oauthInfo[0] === 'success') { - currentAuditDesc = 'Successfully added a new OAuth service'; + currentAuditDesc = formatMessage(holders.successfullOAuthToken); } else { const oauthTokenFailure = oauthInfo[0].split('-'); if (oauthTokenFailure[0].trim() === 'fail' && oauthTokenFailure[1]) { - currentAuditDesc = 'Failed to get an OAuth access token - ' + oauthTokenFailure[1].trim(); + currentAuditDesc = formatMessage(oauthTokenFailure, {token: oauthTokenFailure[1].trim()}); } } @@ -179,11 +361,11 @@ export default class AccessHistoryModal extends React.Component { switch (currentActionURL) { case '/users/login': if (userInfo[0] === 'attempt') { - currentAuditDesc = 'Attempted to login'; + currentAuditDesc = formatMessage(holders.attemptedLogin); } else if (userInfo[0] === 'success') { - currentAuditDesc = 'Successfully logged in'; + currentAuditDesc = formatMessage(holders.successfullLogin); } else if (userInfo[0]) { - currentAuditDesc = 'FAILED login attempt'; + currentAuditDesc = formatMessage(holders.failedLogin); } break; @@ -191,29 +373,29 @@ export default class AccessHistoryModal extends React.Component { currentAuditDesc = this.handleRevokedSession(userInfo[0].split('=')[1]); break; case '/users/newimage': - currentAuditDesc = 'Updated your profile picture'; + currentAuditDesc = formatMessage(holders.updatePicture); break; case '/users/update': - currentAuditDesc = 'Updated the general settings of your account'; + currentAuditDesc = formatMessage(holders.updateGeneral); break; case '/users/newpassword': if (userInfo[0] === 'attempted') { - currentAuditDesc = 'Attempted to change password'; + currentAuditDesc = formatMessage(holders.attemptedPassword); } else if (userInfo[0] === 'completed') { - currentAuditDesc = 'Successfully changed password'; + currentAuditDesc = formatMessage(holders.successfullPassword); } else if (userInfo[0] === 'failed - tried to update user password who was logged in through oauth') { - currentAuditDesc = 'Failed to change password - tried to update user password who was logged in through oauth'; + currentAuditDesc = formatMessage(holders.failedPassword); } break; case '/users/update_roles': { const userRoles = userInfo[0].split('=')[1]; - currentAuditDesc = 'Updated user role(s) to '; + currentAuditDesc = formatMessage(holders.updatedRol); if (userRoles.trim()) { currentAuditDesc += userRoles; } else { - currentAuditDesc += 'member'; + currentAuditDesc += formatMessage(holders.member); } break; @@ -225,9 +407,9 @@ export default class AccessHistoryModal extends React.Component { /* Either describes account activation/deactivation or a revoked session as part of an account deactivation */ if (updateType === 'active') { if (updateField === 'true') { - currentAuditDesc = 'Account made active'; + currentAuditDesc = formatMessage(holders.accountActive); } else if (updateField === 'false') { - currentAuditDesc = 'Account made inactive'; + currentAuditDesc = formatMessage(holders.accountInactive); } const actingUserInfo = userInfo[1].split('='); @@ -235,9 +417,9 @@ export default class AccessHistoryModal extends React.Component { const actingUser = UserStore.getProfile(actingUserInfo[1]); const currentUser = UserStore.getCurrentUser(); if (currentUser && actingUser && (Utils.isAdmin(currentUser.roles) || Utils.isSystemAdmin(currentUser.roles))) { - currentAuditDesc += ' by ' + actingUser.username; + currentAuditDesc += formatMessage(holders.by, {username: actingUser.username}); } else if (currentUser && actingUser) { - currentAuditDesc += ' by an admin'; + currentAuditDesc += formatMessage(holders.byAdmin); } } } else if (updateType === 'session_id') { @@ -247,18 +429,18 @@ export default class AccessHistoryModal extends React.Component { break; } case '/users/send_password_reset': - currentAuditDesc = 'Sent an email to ' + userInfo[0].split('=')[1] + ' to reset your password'; + currentAuditDesc = formatMessage(holders.sentEmail, {email: userInfo[0].split('=')[1]}); break; case '/users/reset_password': if (userInfo[0] === 'attempt') { - currentAuditDesc = 'Attempted to reset password'; + currentAuditDesc = formatMessage(holders.attemptedReset); } else if (userInfo[0] === 'success') { - currentAuditDesc = 'Successfully reset password'; + currentAuditDesc = formatMessage(holders.successfullReset); } break; case '/users/update_notify': - currentAuditDesc = 'Updated your global notification settings'; + currentAuditDesc = formatMessage(holders.updateGlobalNotifications); break; default: break; @@ -269,21 +451,21 @@ export default class AccessHistoryModal extends React.Component { switch (currentActionURL) { case '/hooks/incoming/create': if (webhookInfo[0] === 'attempt') { - currentAuditDesc = 'Attempted to create a webhook'; + currentAuditDesc = formatMessage(holders.attemptedWebhookCreate); } else if (webhookInfo[0] === 'success') { - currentAuditDesc = 'Successfully created a webhook'; + currentAuditDesc = formatMessage(holders.succcessfullWebhookCreate); } else if (webhookInfo[0] === 'fail - bad channel permissions') { - currentAuditDesc = 'Failed to create a webhook - bad channel permissions'; + currentAuditDesc = formatMessage(holders.failedWebhookCreate); } break; case '/hooks/incoming/delete': if (webhookInfo[0] === 'attempt') { - currentAuditDesc = 'Attempted to delete a webhook'; + currentAuditDesc = formatMessage(holders.attemptedWebhookDelete); } else if (webhookInfo[0] === 'success') { - currentAuditDesc = 'Successfully deleted a webhook'; + currentAuditDesc = formatMessage(holders.successfullWebhookDelete); } else if (webhookInfo[0] === 'fail - inappropriate conditions') { - currentAuditDesc = 'Failed to delete a webhook - inappropriate conditions'; + currentAuditDesc = formatMessage(holders.failedWebhookDelete); } break; @@ -293,10 +475,10 @@ export default class AccessHistoryModal extends React.Component { } else { switch (currentActionURL) { case '/logout': - currentAuditDesc = 'Logged out of your account'; + currentAuditDesc = formatMessage(holders.logout); break; case '/verify_email': - currentAuditDesc = 'Sucessfully verified your email address'; + currentAuditDesc = formatMessage(holders.verified); break; default: break; @@ -307,7 +489,7 @@ export default class AccessHistoryModal extends React.Component { if (!currentAuditDesc) { /* Currently not called anywhere */ if (currentAudit.extra_info.indexOf('revoked_all=') >= 0) { - currentAuditDesc = 'Revoked all current sessions for the team'; + currentAuditDesc = formatMessage(holders.revokedAll); } else { let currentActionDesc = ''; if (currentActionURL && currentActionURL.lastIndexOf('/') !== -1) { @@ -328,12 +510,14 @@ export default class AccessHistoryModal extends React.Component { } const currentDate = new Date(currentAudit.create_at); - const currentAuditInfo = currentDate.toDateString() + ' - ' + currentDate.toLocaleTimeString(navigator.language, {hour: '2-digit', minute: '2-digit'}) + ' | ' + currentAuditDesc; + const currentAuditInfo = currentDate.toLocaleDateString(global.window.mm_locale, {month: 'short', day: '2-digit', year: 'numeric'}) + ' - ' + + currentDate.toLocaleTimeString(global.window.mm_locale, {hour: '2-digit', minute: '2-digit'}) + ' | ' + currentAuditDesc; return currentAuditInfo; } render() { var accessList = []; + const {formatMessage} = this.props.intl; for (var i = 0; i < this.state.audits.length; i++) { const currentAudit = this.state.audits[i]; const currentAuditInfo = this.formatAuditInfo(currentAudit); @@ -344,7 +528,10 @@ export default class AccessHistoryModal extends React.Component { className='theme' onClick={this.handleMoreInfo.bind(this, i)} > - {'More info'} + ); @@ -354,17 +541,33 @@ export default class AccessHistoryModal extends React.Component { if (currentAudit.action.search('/users/login') >= 0) { if (currentAudit.extra_info === 'attempt') { - currentAudit.session_id += ' (Login attempt)'; + currentAudit.session_id += formatMessage(holders.loginAttempt); } else { - currentAudit.session_id += ' (Login failure)'; + currentAudit.session_id += formatMessage(holders.loginFailure); } } } moreInfo = (
    -
    {'IP: ' + currentAudit.ip_address}
    -
    {'Session ID: ' + currentAudit.session_id}
    +
    + +
    +
    + +
    ); } @@ -404,7 +607,12 @@ export default class AccessHistoryModal extends React.Component { bsSize='large' > - {'Access History'} + + + {content} @@ -415,6 +623,9 @@ export default class AccessHistoryModal extends React.Component { } AccessHistoryModal.propTypes = { + intl: intlShape.isRequired, show: React.PropTypes.bool.isRequired, onHide: React.PropTypes.func.isRequired }; + +export default injectIntl(AccessHistoryModal); \ No newline at end of file diff --git a/web/react/components/activity_log_modal.jsx b/web/react/components/activity_log_modal.jsx index 2c42f5971..f8a2af571 100644 --- a/web/react/components/activity_log_modal.jsx +++ b/web/react/components/activity_log_modal.jsx @@ -8,6 +8,8 @@ const Modal = ReactBootstrap.Modal; import LoadingScreen from './loading_screen.jsx'; import * as Utils from '../utils/utils.jsx'; +import {FormattedMessage} from 'mm-intl'; + export default class ActivityLogModal extends React.Component { constructor(props) { super(props); @@ -102,16 +104,31 @@ export default class ActivityLogModal extends React.Component { devicePicture = 'fa fa-windows'; } else if (currentSession.device_id && currentSession.device_id.indexOf('apple:') === 0) { devicePicture = 'fa fa-apple'; - devicePlatform = 'iPhone Native App'; + devicePlatform = ( + + ); } else if (currentSession.device_id && currentSession.device_id.indexOf('android:') === 0) { - devicePlatform = 'Android Native App'; + devicePlatform = ( + + ); devicePicture = 'fa fa-android'; } else if (currentSession.props.platform === 'Macintosh' || currentSession.props.platform === 'iPhone') { devicePicture = 'fa fa-apple'; } else if (currentSession.props.platform === 'Linux') { if (currentSession.props.os.indexOf('Android') >= 0) { - devicePlatform = 'Android'; + devicePlatform = ( + + ); devicePicture = 'fa fa-android'; } else { devicePicture = 'fa fa-linux'; @@ -122,10 +139,43 @@ export default class ActivityLogModal extends React.Component { if (this.state.moreInfo[i]) { moreInfo = (
    -
    {`First time active: ${firstAccessTime.toDateString()}, ${lastAccessTime.toLocaleTimeString()}`}
    -
    {`OS: ${currentSession.props.os}`}
    -
    {`Browser: ${currentSession.props.browser}`}
    -
    {`Session ID: ${currentSession.id}`}
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    ); } else { @@ -135,7 +185,10 @@ export default class ActivityLogModal extends React.Component { href='#' onClick={this.handleMoreInfo.bind(this, i)} > - More info + ); } @@ -148,7 +201,16 @@ export default class ActivityLogModal extends React.Component {
    {devicePlatform}
    -
    {`Last activity: ${lastAccessTime.toDateString()}, ${lastAccessTime.toLocaleTimeString()}`}
    +
    + +
    {moreInfo}
    @@ -157,7 +219,10 @@ export default class ActivityLogModal extends React.Component { onClick={this.submitRevoke.bind(this, currentSession.id)} className='btn btn-primary' > - Logout + @@ -178,10 +243,20 @@ export default class ActivityLogModal extends React.Component { bsSize='large' > - {'Active Sessions'} + + + -

    {'Sessions are created when you log in with your email and password to a new browser on a device. Sessions let you use Mattermost for up to 30 days without having to log in again. If you want to log out sooner, use the \'Logout\' button below to end a session.'}

    +

    + +

    {content}
    diff --git a/web/react/components/confirm_modal.jsx b/web/react/components/confirm_modal.jsx index cdef1c1ea..987649f38 100644 --- a/web/react/components/confirm_modal.jsx +++ b/web/react/components/confirm_modal.jsx @@ -1,6 +1,7 @@ // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. +import {FormattedMessage} from 'mm-intl'; const Modal = ReactBootstrap.Modal; export default class ConfirmModal extends React.Component { @@ -33,7 +34,10 @@ export default class ConfirmModal extends React.Component { className='btn btn-default' onClick={this.props.onCancel} > - {'Cancel'} +