summaryrefslogtreecommitdiffstats
path: root/web/react/components
diff options
context:
space:
mode:
Diffstat (limited to 'web/react/components')
-rw-r--r--web/react/components/about_build_modal.jsx65
-rw-r--r--web/react/components/access_history_modal.jsx311
-rw-r--r--web/react/components/activity_log_modal.jsx99
-rw-r--r--web/react/components/admin_console/analytics.jsx20
-rw-r--r--web/react/components/admin_console/email_settings.jsx84
-rw-r--r--web/react/components/admin_console/user_item.jsx155
-rw-r--r--web/react/components/center_panel.jsx2
-rw-r--r--web/react/components/change_url_modal.jsx54
-rw-r--r--web/react/components/confirm_modal.jsx6
-rw-r--r--web/react/components/create_comment.jsx52
-rw-r--r--web/react/components/file_upload.jsx38
-rw-r--r--web/react/components/file_upload_overlay.jsx9
-rw-r--r--web/react/components/get_link_modal.jsx45
-rw-r--r--web/react/components/get_team_invite_link_modal.jsx37
-rw-r--r--web/react/components/invite_member_modal.jsx126
-rw-r--r--web/react/components/login.jsx15
-rw-r--r--web/react/components/login_username.jsx181
-rw-r--r--web/react/components/member_list_team_item.jsx50
-rw-r--r--web/react/components/more_channels.jsx45
-rw-r--r--web/react/components/more_direct_channels.jsx71
-rw-r--r--web/react/components/msg_typing.jsx42
-rw-r--r--web/react/components/navbar.jsx8
-rw-r--r--web/react/components/navbar_dropdown.jsx68
-rw-r--r--web/react/components/new_channel_flow.jsx66
-rw-r--r--web/react/components/new_channel_modal.jsx118
-rw-r--r--web/react/components/post_info.jsx41
-rw-r--r--web/react/components/register_app_modal.jsx157
-rw-r--r--web/react/components/rhs_comment.jsx41
-rw-r--r--web/react/components/rhs_header_post.jsx11
-rw-r--r--web/react/components/rhs_root_post.jsx29
-rw-r--r--web/react/components/search_bar.jsx39
-rw-r--r--web/react/components/search_results.jsx32
-rw-r--r--web/react/components/search_results_header.jsx17
-rw-r--r--web/react/components/search_results_item.jsx26
-rw-r--r--web/react/components/setting_item_max.jsx12
-rw-r--r--web/react/components/setting_item_min.jsx7
-rw-r--r--web/react/components/setting_picture.jsx32
-rw-r--r--web/react/components/setting_upload.jsx21
-rw-r--r--web/react/components/sidebar.jsx114
-rw-r--r--web/react/components/sidebar_header.jsx23
-rw-r--r--web/react/components/sidebar_right_menu.jsx68
-rw-r--r--web/react/components/team_export_tab.jsx44
-rw-r--r--web/react/components/team_general_tab.jsx178
-rw-r--r--web/react/components/team_import_tab.jsx67
-rw-r--r--web/react/components/team_members_modal.jsx15
-rw-r--r--web/react/components/team_settings_modal.jsx34
-rw-r--r--web/react/components/textbox.jsx20
-rw-r--r--web/react/components/unread_channel_indicator.jsx2
-rw-r--r--web/react/components/user_profile.jsx8
-rw-r--r--web/react/components/user_settings/custom_theme_chooser.jsx104
-rw-r--r--web/react/components/user_settings/import_theme_modal.jsx44
-rw-r--r--web/react/components/user_settings/manage_incoming_hooks.jsx49
-rw-r--r--web/react/components/user_settings/manage_languages.jsx14
-rw-r--r--web/react/components/user_settings/manage_outgoing_hooks.jsx131
-rw-r--r--web/react/components/user_settings/user_settings_advanced.jsx101
-rw-r--r--web/react/components/user_settings/user_settings_appearance.jsx40
-rw-r--r--web/react/components/user_settings/user_settings_developer.jsx42
-rw-r--r--web/react/components/user_settings/user_settings_display.jsx140
-rw-r--r--web/react/components/user_settings/user_settings_general.jsx254
-rw-r--r--web/react/components/user_settings/user_settings_integrations.jsx51
-rw-r--r--web/react/components/user_settings/user_settings_modal.jsx84
-rw-r--r--web/react/components/user_settings/user_settings_notifications.jsx226
-rw-r--r--web/react/components/user_settings/user_settings_security.jsx153
63 files changed, 3552 insertions, 686 deletions
diff --git a/web/react/components/about_build_modal.jsx b/web/react/components/about_build_modal.jsx
index f70027498..fe48bb48e 100644
--- a/web/react/components/about_build_modal.jsx
+++ b/web/react/components/about_build_modal.jsx
@@ -3,6 +3,8 @@
var Modal = ReactBootstrap.Modal;
+import {FormattedMessage} from 'mm-intl';
+
export default class AboutBuildModal extends React.Component {
constructor(props) {
super(props);
@@ -17,13 +19,28 @@ export default class AboutBuildModal extends React.Component {
const config = global.window.mm_config;
const license = global.window.mm_license;
- let title = 'Team Edition';
+ let title = (
+ <FormattedMessage
+ id='about.teamEdtion'
+ defaultMessage='Team Edition'
+ />
+ );
let licensee;
if (config.BuildEnterpriseReady === 'true' && license.IsLicensed === 'true') {
- title = 'Enterprise Edition';
+ title = (
+ <FormattedMessage
+ id='about.enterpriseEdition'
+ defaultMessage='Enterprise Edition'
+ />
+ );
licensee = (
<div className='row form-group'>
- <div className='col-sm-3 info__label'>{'Licensed by:'}</div>
+ <div className='col-sm-3 info__label'>
+ <FormattedMessage
+ id='about.licensed'
+ defaultMessage='Licensed by:'
+ />
+ </div>
<div className='col-sm-9'>{license.Company}</div>
</div>
);
@@ -35,25 +52,50 @@ export default class AboutBuildModal extends React.Component {
onHide={this.doHide}
>
<Modal.Header closeButton={true}>
- <Modal.Title>{'About Mattermost'}</Modal.Title>
+ <Modal.Title>
+ <FormattedMessage
+ id='about.title'
+ defaultMessage='About Mattermost'
+ />
+ </Modal.Title>
</Modal.Header>
<Modal.Body>
- <h4>{`Mattermost ${title}`}</h4>
+ <h4>{'Mattermost'} {title}</h4>
{licensee}
<div className='row form-group'>
- <div className='col-sm-3 info__label'>{'Version:'}</div>
+ <div className='col-sm-3 info__label'>
+ <FormattedMessage
+ id='about.version'
+ defaultMessage='Version:'
+ />
+ </div>
<div className='col-sm-9'>{config.Version}</div>
</div>
<div className='row form-group'>
- <div className='col-sm-3 info__label'>{'Build Number:'}</div>
+ <div className='col-sm-3 info__label'>
+ <FormattedMessage
+ id='about.number'
+ defaultMessage='Build Number:'
+ />
+ </div>
<div className='col-sm-9'>{config.BuildNumber}</div>
</div>
<div className='row form-group'>
- <div className='col-sm-3 info__label'>{'Build Date:'}</div>
+ <div className='col-sm-3 info__label'>
+ <FormattedMessage
+ id='about.date'
+ defaultMessage='Build Date:'
+ />
+ </div>
<div className='col-sm-9'>{config.BuildDate}</div>
</div>
<div className='row form-group'>
- <div className='col-sm-3 info__label'>{'Build Hash:'}</div>
+ <div className='col-sm-3 info__label'>
+ <FormattedMessage
+ id='about.hash'
+ defaultMessage='Build Hash:'
+ />
+ </div>
<div className='col-sm-9'>{config.BuildHash}</div>
</div>
</Modal.Body>
@@ -63,7 +105,10 @@ export default class AboutBuildModal extends React.Component {
className='btn btn-default'
onClick={this.doHide}
>
- {'Close'}
+ <FormattedMessage
+ id='about.close'
+ defaultMessage='Close'
+ />
</button>
</Modal.Footer>
</Modal>
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'}
+ <FormattedMessage
+ id='access_history.moreInfo'
+ defaultMessage='More info'
+ />
</a>
);
@@ -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 = (
<div>
- <div>{'IP: ' + currentAudit.ip_address}</div>
- <div>{'Session ID: ' + currentAudit.session_id}</div>
+ <div>
+ <FormattedMessage
+ id='access_history.ip'
+ defaultMessage='IP: {ip}'
+ values={{
+ ip: currentAudit.ip_address
+ }}
+ />
+ </div>
+ <div>
+ <FormattedMessage
+ id='access_history.session'
+ defaultMessage='Session ID: {id}'
+ values={{
+ id: currentAudit.session_id
+ }}
+ />
+ </div>
</div>
);
}
@@ -404,7 +607,12 @@ export default class AccessHistoryModal extends React.Component {
bsSize='large'
>
<Modal.Header closeButton={true}>
- <Modal.Title>{'Access History'}</Modal.Title>
+ <Modal.Title>
+ <FormattedMessage
+ id='access_history.title'
+ defaultMessage='Access History'
+ />
+ </Modal.Title>
</Modal.Header>
<Modal.Body ref='modalBody'>
{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 = (
+ <FormattedMessage
+ id='activity_log_modal.iphoneNativeApp'
+ defaultMessage='iPhone Native App'
+ />
+ );
} else if (currentSession.device_id && currentSession.device_id.indexOf('android:') === 0) {
- devicePlatform = 'Android Native App';
+ devicePlatform = (
+ <FormattedMessage
+ id='activity_log_modal.androidNativeApp'
+ defaultMessage='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 === 'Linux') {
if (currentSession.props.os.indexOf('Android') >= 0) {
- devicePlatform = 'Android';
+ devicePlatform = (
+ <FormattedMessage
+ id='activity_log_modal.android'
+ defaultMessage='Android'
+ />
+ );
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 = (
<div>
- <div>{`First time active: ${firstAccessTime.toDateString()}, ${lastAccessTime.toLocaleTimeString()}`}</div>
- <div>{`OS: ${currentSession.props.os}`}</div>
- <div>{`Browser: ${currentSession.props.browser}`}</div>
- <div>{`Session ID: ${currentSession.id}`}</div>
+ <div>
+ <FormattedMessage
+ id='activity_log.firstTime'
+ defaultMessage='First time active: {date}, {time}'
+ values={{
+ date: firstAccessTime.toLocaleDateString(global.window.mm_locale, {month: 'short', day: '2-digit', year: 'numeric'}),
+ time: lastAccessTime.toLocaleTimeString(global.window.mm_locale, {hour: '2-digit', minute: '2-digit'})
+ }}
+ />
+ </div>
+ <div>
+ <FormattedMessage
+ id='activity_log.os'
+ defaultMessage='OS: {os}'
+ values={{
+ os: currentSession.props.os
+ }}
+ />
+ </div>
+ <div>
+ <FormattedMessage
+ id='activity_log.browser'
+ defaultMessage='Browser: {browser}'
+ values={{
+ browser: currentSession.props.browser
+ }}
+ />
+ </div>
+ <div>
+ <FormattedMessage
+ id='activity_log.sessionId'
+ defaultMessage='Session ID: {id}'
+ values={{
+ id: currentSession.id
+ }}
+ />
+ </div>
</div>
);
} else {
@@ -135,7 +185,10 @@ export default class ActivityLogModal extends React.Component {
href='#'
onClick={this.handleMoreInfo.bind(this, i)}
>
- More info
+ <FormattedMessage
+ id='activity_log.moreInfo'
+ defaultMessage='More info'
+ />
</a>
);
}
@@ -148,7 +201,16 @@ export default class ActivityLogModal extends React.Component {
<div className='activity-log__report'>
<div className='report__platform'><i className={devicePicture} />{devicePlatform}</div>
<div className='report__info'>
- <div>{`Last activity: ${lastAccessTime.toDateString()}, ${lastAccessTime.toLocaleTimeString()}`}</div>
+ <div>
+ <FormattedMessage
+ id='activity_log.lastActivity'
+ defaultMessage='Last activity: {date}, {time}'
+ values={{
+ date: lastAccessTime.toLocaleDateString(global.window.mm_locale, {month: 'short', day: '2-digit', year: 'numeric'}),
+ time: lastAccessTime.toLocaleTimeString(global.window.mm_locale, {hour: '2-digit', minute: '2-digit'})
+ }}
+ />
+ </div>
{moreInfo}
</div>
</div>
@@ -157,7 +219,10 @@ export default class ActivityLogModal extends React.Component {
onClick={this.submitRevoke.bind(this, currentSession.id)}
className='btn btn-primary'
>
- Logout
+ <FormattedMessage
+ id='activity_log.logout'
+ defaultMessage='Logout'
+ />
</button>
</div>
</div>
@@ -178,10 +243,20 @@ export default class ActivityLogModal extends React.Component {
bsSize='large'
>
<Modal.Header closeButton={true}>
- <Modal.Title>{'Active Sessions'}</Modal.Title>
+ <Modal.Title>
+ <FormattedMessage
+ id='activity_log.activeSessions'
+ defaultMessage='Active Sessions'
+ />
+ </Modal.Title>
</Modal.Header>
<Modal.Body ref='modalBody'>
- <p className='session-help-text'>{'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.'}</p>
+ <p className='session-help-text'>
+ <FormattedMessage
+ id='activity_log.sessionsDescription'
+ defaultMessage="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."
+ />
+ </p>
{content}
</Modal.Body>
</Modal>
diff --git a/web/react/components/admin_console/analytics.jsx b/web/react/components/admin_console/analytics.jsx
index ff5903c62..a22c26c34 100644
--- a/web/react/components/admin_console/analytics.jsx
+++ b/web/react/components/admin_console/analytics.jsx
@@ -104,10 +104,12 @@ export default class Analytics extends React.Component {
let content;
if (this.props.postCountsDay.labels.length === 0) {
content = (
- <FormattedMessage
- id='admin.analytics.meaningful'
- defaultMessage='Not enough data for a meaningful representation.'
- />
+ <h5>
+ <FormattedMessage
+ id='admin.analytics.meaningful'
+ defaultMessage='Not enough data for a meaningful representation.'
+ />
+ </h5>
);
} else {
content = (
@@ -153,10 +155,12 @@ export default class Analytics extends React.Component {
let content;
if (this.props.userCountsWithPostsDay.labels.length === 0) {
content = (
- <FormattedMessage
- id='admin.analytics.meaningful'
- defaultMessage='Not enough data for a meaningful representation.'
- />
+ <h5>
+ <FormattedMessage
+ id='admin.analytics.meaningful'
+ defaultMessage='Not enough data for a meaningful representation.'
+ />
+ </h5>
);
} else {
content = (
diff --git a/web/react/components/admin_console/email_settings.jsx b/web/react/components/admin_console/email_settings.jsx
index ce3c8cd12..17f25a04c 100644
--- a/web/react/components/admin_console/email_settings.jsx
+++ b/web/react/components/admin_console/email_settings.jsx
@@ -112,6 +112,8 @@ class EmailSettings extends React.Component {
buildConfig() {
var config = this.props.config;
config.EmailSettings.EnableSignUpWithEmail = ReactDOM.findDOMNode(this.refs.allowSignUpWithEmail).checked;
+ config.EmailSettings.EnableSignInWithEmail = ReactDOM.findDOMNode(this.refs.allowSignInWithEmail).checked;
+ config.EmailSettings.EnableSignInWithUsername = ReactDOM.findDOMNode(this.refs.allowSignInWithUsername).checked;
config.EmailSettings.SendEmailNotifications = ReactDOM.findDOMNode(this.refs.sendEmailNotifications).checked;
config.EmailSettings.SendPushNotifications = ReactDOM.findDOMNode(this.refs.sendPushNotifications).checked;
config.EmailSettings.RequireEmailVerification = ReactDOM.findDOMNode(this.refs.requireEmailVerification).checked;
@@ -320,6 +322,88 @@ class EmailSettings extends React.Component {
<div className='form-group'>
<label
className='control-label col-sm-4'
+ htmlFor='allowSignInWithEmail'
+ >
+ <FormattedMessage
+ id='admin.email.allowEmailSignInTitle'
+ defaultMessage='Allow Sign In With Email: '
+ />
+ </label>
+ <div className='col-sm-8'>
+ <label className='radio-inline'>
+ <input
+ type='radio'
+ name='allowSignInWithEmail'
+ value='true'
+ ref='allowSignInWithEmail'
+ defaultChecked={this.props.config.EmailSettings.EnableSignInWithEmail}
+ onChange={this.handleChange.bind(this, 'allowSignInWithEmail_true')}
+ />
+ {'true'}
+ </label>
+ <label className='radio-inline'>
+ <input
+ type='radio'
+ name='allowSignInWithEmail'
+ value='false'
+ defaultChecked={!this.props.config.EmailSettings.EnableSignInWithEmail}
+ onChange={this.handleChange.bind(this, 'allowSignInWithEmail_false')}
+ />
+ {'false'}
+ </label>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.email.allowEmailSignInDescription'
+ defaultMessage='When true, Mattermost allows users to sign in using their email and password.'
+ />
+ </p>
+ </div>
+ </div>
+
+ <div className='form-group'>
+ <label
+ className='control-label col-sm-4'
+ htmlFor='allowSignInWithUsername'
+ >
+ <FormattedMessage
+ id='admin.email.allowUsernameSignInTitle'
+ defaultMessage='Allow Sign In With Username: '
+ />
+ </label>
+ <div className='col-sm-8'>
+ <label className='radio-inline'>
+ <input
+ type='radio'
+ name='allowSignInWithUsername'
+ value='true'
+ ref='allowSignInWithUsername'
+ defaultChecked={this.props.config.EmailSettings.EnableSignInWithUsername}
+ onChange={this.handleChange.bind(this, 'allowSignInWithUsername_true')}
+ />
+ {'true'}
+ </label>
+ <label className='radio-inline'>
+ <input
+ type='radio'
+ name='allowSignInWithUsername'
+ value='false'
+ defaultChecked={!this.props.config.EmailSettings.EnableSignInWithUsername}
+ onChange={this.handleChange.bind(this, 'allowSignInWithUsername_false')}
+ />
+ {'false'}
+ </label>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.email.allowUsernameSignInDescription'
+ defaultMessage='When true, Mattermost allows users to sign in using their username and password. This setting is typically only used when email verification is disabled.'
+ />
+ </p>
+ </div>
+ </div>
+
+ <div className='form-group'>
+ <label
+ className='control-label col-sm-4'
htmlFor='sendEmailNotifications'
>
<FormattedMessage
diff --git a/web/react/components/admin_console/user_item.jsx b/web/react/components/admin_console/user_item.jsx
index 59b4861e4..02b01b090 100644
--- a/web/react/components/admin_console/user_item.jsx
+++ b/web/react/components/admin_console/user_item.jsx
@@ -3,8 +3,30 @@
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';
+import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'mm-intl';
+
+var holders = 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.'
+ },
+ confirmDemotionCmd: {
+ id: 'admin.user_item.confirmDemotionCmd',
+ defaultMessage: 'platform -assign_role -team_name="yourteam" -email="name@yourcompany.com" -role="system_admin"'
+ }
+});
export default class UserItem extends React.Component {
constructor(props) {
@@ -16,25 +38,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.handleDemote = this.handleDemote.bind(this);
+ this.handleDemoteSubmit = this.handleDemoteSubmit.bind(this);
+ this.handleDemoteCancel = this.handleDemoteCancel.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: ''
- };
+ const me = UserStore.getCurrentUser();
+ if (this.props.user.id === me.id) {
+ this.handleDemote(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,9 +98,31 @@ export default class UserItem extends React.Component {
handleMakeAdmin(e) {
e.preventDefault();
+ const me = UserStore.getCurrentUser();
+ if (this.props.user.id === me.id) {
+ this.handleDemote(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});
+ }
+ );
+ }
+ }
+
+ handleMakeSystemAdmin(e) {
+ e.preventDefault();
const data = {
user_id: this.props.user.id,
- new_roles: 'admin'
+ new_roles: 'system_admin'
};
Client.updateRoles(data,
@@ -78,28 +135,59 @@ export default class UserItem extends React.Component {
);
}
- handleMakeSystemAdmin(e) {
+ handleResetPassword(e) {
e.preventDefault();
+ this.props.doPasswordReset(this.props.user);
+ }
+
+ handleDemote(user, role) {
+ this.setState({
+ serverError: this.state.serverError,
+ showDemoteModal: true,
+ user,
+ role
+ });
+ }
+
+ handleDemoteCancel() {
+ this.setState({
+ serverError: null,
+ showDemoteModal: false,
+ user: null,
+ role: null
+ });
+ }
+
+ handleDemoteSubmit() {
const data = {
user_id: this.props.user.id,
- new_roles: 'system_admin'
+ new_roles: this.state.role
};
Client.updateRoles(data,
() => {
- this.props.refreshProfiles();
+ this.setState({
+ serverError: null,
+ showDemoteModal: false,
+ user: null,
+ role: null
+ });
+
+ const teamUrl = TeamStore.getCurrentTeamUrl();
+ if (teamUrl) {
+ window.location.href = teamUrl;
+ } else {
+ window.location.href = '/';
+ }
},
(err) => {
- this.setState({serverError: err.message});
+ this.setState({
+ serverError: err.message
+ });
}
);
}
- handleResetPassword(e) {
- e.preventDefault();
- this.props.doPasswordReset(this.props.user);
- }
-
render() {
let serverError = null;
if (this.state.serverError) {
@@ -247,6 +335,21 @@ export default class UserItem extends React.Component {
</li>
);
}
+ const me = UserStore.getCurrentUser();
+ const {formatMessage} = this.props.intl;
+ let makeDemoteModal = null;
+ if (this.props.user.id === me.id) {
+ makeDemoteModal = (
+ <ConfirmModal
+ show={this.state.showDemoteModal}
+ title={formatMessage(holders.confirmDemoteRoleTitle)}
+ message={[formatMessage(holders.confirmDemoteDescription), React.createElement('br'), React.createElement('br'), formatMessage(holders.confirmDemotionCmd), serverError]}
+ confirm_button={formatMessage(holders.confirmDemotion)}
+ onConfirm={this.handleDemoteSubmit}
+ onCancel={this.handleDemoteCancel}
+ />
+ );
+ }
return (
<tr>
@@ -293,6 +396,7 @@ export default class UserItem extends React.Component {
</li>
</ul>
</div>
+ {makeDemoteModal}
{serverError}
</td>
</tr>
@@ -301,7 +405,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/react/components/center_panel.jsx b/web/react/components/center_panel.jsx
index 53dad1306..443ecefde 100644
--- a/web/react/components/center_panel.jsx
+++ b/web/react/components/center_panel.jsx
@@ -69,7 +69,7 @@ export default class CenterPanel extends React.Component {
onClick={handleClick}
>
<a href=''>
- {'You are viewing the Archives. Click here to jump to recent messages. '}
+ {'Click here to jump to recent messages. '}
{<i className='fa fa-arrow-down'></i>}
</a>
</div>
diff --git a/web/react/components/change_url_modal.jsx b/web/react/components/change_url_modal.jsx
index bbe93f58d..49d1b86b4 100644
--- a/web/react/components/change_url_modal.jsx
+++ b/web/react/components/change_url_modal.jsx
@@ -4,6 +4,8 @@
var Modal = ReactBootstrap.Modal;
import * as Utils from '../utils/utils.jsx';
+import {FormattedMessage} from 'mm-intl';
+
export default class ChangeUrlModal extends React.Component {
constructor(props) {
super(props);
@@ -39,21 +41,58 @@ export default class ChangeUrlModal extends React.Component {
getURLError(url) {
let error = []; //eslint-disable-line prefer-const
if (url.length < 2) {
- error.push(<span key='error1'>{'Must be longer than two characters'}<br/></span>);
+ error.push(
+ <span key='error1'>
+ <FormattedMessage
+ id='change_url.longer'
+ defaultMessage='Must be longer than two characters'
+ />
+ <br/>
+ </span>
+ );
}
if (url.charAt(0) === '-' || url.charAt(0) === '_') {
- error.push(<span key='error2'>{'Must start with a letter or number'}<br/></span>);
+ error.push(
+ <span key='error2'>
+ <FormattedMessage
+ id='change_url.startWithLetter'
+ defaultMessage='Must start with a letter or number'
+ />
+ <br/>
+ </span>
+ );
}
if (url.length > 1 && (url.charAt(url.length - 1) === '-' || url.charAt(url.length - 1) === '_')) {
- error.push(<span key='error3'>{'Must end with a letter or number'}<br/></span>);
+ error.push(
+ <span key='error3'>
+ <FormattedMessage
+ id='change_url.endWithLetter'
+ defaultMessage='Must end with a letter or number'
+ />
+ <br/>
+ </span>);
}
if (url.indexOf('__') > -1) {
- error.push(<span key='error4'>{'Can not contain two underscores in a row.'}<br/></span>);
+ error.push(
+ <span key='error4'>
+ <FormattedMessage
+ id='change_url.noUnderscore'
+ defaultMessage='Can not contain two underscores in a row.'
+ />
+ <br/>
+ </span>);
}
// In case of error we don't detect
if (error.length === 0) {
- error.push(<span key='errorlast'>{'Invalid URL'}<br/></span>);
+ error.push(
+ <span key='errorlast'>
+ <FormattedMessage
+ id='change_url.invalidUrl'
+ defaultMessage='Invalid URL'
+ />
+ <br/>
+ </span>);
}
return error;
}
@@ -137,7 +176,10 @@ export default class ChangeUrlModal extends React.Component {
className='btn btn-default'
onClick={this.doCancel}
>
- {'Close'}
+ <FormattedMessage
+ id='change_url.close'
+ defaultMessage='Close'
+ />
</button>
<button
onClick={this.doSubmit}
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'}
+ <FormattedMessage
+ id='confirm_modal.cancel'
+ defaultMessage='Cancel'
+ />
</button>
<button
type='button'
diff --git a/web/react/components/create_comment.jsx b/web/react/components/create_comment.jsx
index aa7ab6a7b..1b552838a 100644
--- a/web/react/components/create_comment.jsx
+++ b/web/react/components/create_comment.jsx
@@ -16,10 +16,32 @@ import FilePreview from './file_preview.jsx';
import * as Utils from '../utils/utils.jsx';
import Constants from '../utils/constants.jsx';
+
+import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl';
+
const ActionTypes = Constants.ActionTypes;
const KeyCodes = Constants.KeyCodes;
-export default class CreateComment extends React.Component {
+const holders = defineMessages({
+ commentLength: {
+ id: 'create_comment.commentLength',
+ defaultMessage: 'Comment length must be less than {max} characters.'
+ },
+ comment: {
+ id: 'create_comment.comment',
+ defaultMessage: 'Add Comment'
+ },
+ addComment: {
+ id: 'create_comment.addComment',
+ defaultMessage: 'Add a comment...'
+ },
+ commentTitle: {
+ id: 'create_comment.commentTitle',
+ defaultMessage: 'Comment'
+ }
+});
+
+class CreateComment extends React.Component {
constructor(props) {
super(props);
@@ -93,7 +115,7 @@ export default class CreateComment extends React.Component {
}
if (post.message.length > Constants.CHARACTER_LIMIT) {
- this.setState({postError: `Comment length must be less than ${Constants.CHARACTER_LIMIT} characters.`});
+ this.setState({postError: this.props.intl.formatMessage(holders.commentLength, {max: Constants.CHARACTER_LIMIT})});
return;
}
@@ -189,7 +211,7 @@ export default class CreateComment extends React.Component {
AppDispatcher.handleViewAction({
type: ActionTypes.RECIEVED_EDIT_POST,
refocusId: '#reply_textbox',
- title: 'Comment',
+ title: this.props.intl.formatMessage(holders.commentTitle),
message: lastPost.message,
postId: lastPost.id,
channelId: lastPost.channel_id,
@@ -305,14 +327,23 @@ export default class CreateComment extends React.Component {
let uploadsInProgressText = null;
if (this.state.uploadsInProgress.length > 0) {
uploadsInProgressText = (
- <span
- className='pull-right post-right-comments-upload-in-progress'
- >
- {this.state.uploadsInProgress.length === 1 ? 'File uploading' : 'Files uploading'}
+ <span className='pull-right post-right-comments-upload-in-progress'>
+ {this.state.uploadsInProgress.length === 1 ? (
+ <FormattedMessage
+ id='create_comment.file'
+ defaultMessage='File uploading'
+ />
+ ) : (
+ <FormattedMessage
+ id='create_comment.files'
+ defaultMessage='Files uploading'
+ />
+ )}
</span>
);
}
+ const {formatMessage} = this.props.intl;
return (
<form onSubmit={this.handleSubmit}>
<div className='post-create'>
@@ -326,7 +357,7 @@ export default class CreateComment extends React.Component {
onKeyPress={this.commentMsgKeyPress}
onKeyDown={this.handleKeyDown}
messageText={this.state.messageText}
- createMessage='Add a comment...'
+ createMessage={formatMessage(holders.addComment)}
initialText=''
supportsCommands={false}
id='reply_textbox'
@@ -351,7 +382,7 @@ export default class CreateComment extends React.Component {
<input
type='button'
className='btn btn-primary comment-btn pull-right'
- value='Add Comment'
+ value={formatMessage(holders.comment)}
onClick={this.handleSubmit}
/>
{uploadsInProgressText}
@@ -366,6 +397,9 @@ export default class CreateComment extends React.Component {
}
CreateComment.propTypes = {
+ intl: intlShape.isRequired,
channelId: React.PropTypes.string.isRequired,
rootId: React.PropTypes.string.isRequired
};
+
+export default injectIntl(CreateComment); \ No newline at end of file
diff --git a/web/react/components/file_upload.jsx b/web/react/components/file_upload.jsx
index 7e6cc2942..626dbc5b3 100644
--- a/web/react/components/file_upload.jsx
+++ b/web/react/components/file_upload.jsx
@@ -6,7 +6,28 @@ import Constants from '../utils/constants.jsx';
import ChannelStore from '../stores/channel_store.jsx';
import * as Utils from '../utils/utils.jsx';
-export default class FileUpload extends React.Component {
+import {intlShape, injectIntl, defineMessages} from 'mm-intl';
+
+const holders = defineMessages({
+ limited: {
+ id: 'file_upload.limited',
+ defaultMessage: 'Uploads limited to {count} files maximum. Please use additional posts for more files.'
+ },
+ filesAbove: {
+ id: 'file_upload.filesAbove',
+ defaultMessage: 'Files above {max}MB could not be uploaded: {filenames}'
+ },
+ fileAbove: {
+ id: 'file_upload.fileAbove',
+ defaultMessage: 'File above {max}MB could not be uploaded: {filename}'
+ },
+ pasted: {
+ id: 'file_upload.pasted',
+ defaultMessage: 'Image Pasted at '
+ }
+});
+
+class FileUpload extends React.Component {
constructor(props) {
super(props);
@@ -74,14 +95,15 @@ export default class FileUpload extends React.Component {
numUploads += 1;
}
+ const {formatMessage} = this.props.intl;
if (files.length > uploadsRemaining) {
- this.props.onUploadError(`Uploads limited to ${Constants.MAX_UPLOAD_FILES} files maximum. Please use additional posts for more files.`);
+ this.props.onUploadError(formatMessage(holders.limited, {count: Constants.MAX_UPLOAD_FILES}));
} else if (tooLargeFiles.length > 1) {
var tooLargeFilenames = tooLargeFiles.map((file) => file.name).join(', ');
- this.props.onUploadError(`Files above ${Constants.MAX_FILE_SIZE / 1000000}MB could not be uploaded: ${tooLargeFilenames}`);
+ this.props.onUploadError(formatMessage(holders.filesAbove, {max: (Constants.MAX_FILE_SIZE / 1000000), files: tooLargeFilenames}));
} else if (tooLargeFiles.length > 0) {
- this.props.onUploadError(`File above ${Constants.MAX_FILE_SIZE / 1000000}MB could not be uploaded: ${tooLargeFiles[0].name}`);
+ this.props.onUploadError(formatMessage(holders.fileAbove, {max: (Constants.MAX_FILE_SIZE / 1000000), file: tooLargeFiles[0].name}));
}
}
@@ -106,6 +128,7 @@ export default class FileUpload extends React.Component {
componentDidMount() {
var inputDiv = ReactDOM.findDOMNode(this.refs.input);
var self = this;
+ const {formatMessage} = this.props.intl;
if (this.props.postType === 'post') {
$('.row.main').dragster({
@@ -184,7 +207,7 @@ export default class FileUpload extends React.Component {
var numToUpload = Math.min(Constants.MAX_UPLOAD_FILES - self.props.getFileCount(ChannelStore.getCurrentId()), numItems);
if (numItems > numToUpload) {
- self.props.onUploadError('Uploads limited to ' + Constants.MAX_UPLOAD_FILES + ' files maximum. Please use additional posts for more files.');
+ self.props.onUploadError(formatMessage(holders.limited, {count: Constants.MAX_UPLOAD_FILES}));
}
for (var i = 0; i < items.length && i < numToUpload; i++) {
@@ -218,7 +241,7 @@ export default class FileUpload extends React.Component {
min = String(d.getMinutes());
}
- var name = 'Image Pasted at ' + d.getFullYear() + '-' + d.getMonth() + '-' + d.getDate() + ' ' + hour + '-' + min + '.' + ext;
+ var name = formatMessage(holders.pasted) + d.getFullYear() + '-' + d.getMonth() + '-' + d.getDate() + ' ' + hour + '-' + min + '.' + ext;
formData.append('files', file, name);
formData.append('client_ids', clientId);
@@ -296,6 +319,7 @@ export default class FileUpload extends React.Component {
}
FileUpload.propTypes = {
+ intl: intlShape.isRequired,
onUploadError: React.PropTypes.func,
getFileCount: React.PropTypes.func,
onFileUpload: React.PropTypes.func,
@@ -304,3 +328,5 @@ FileUpload.propTypes = {
channelId: React.PropTypes.string,
postType: React.PropTypes.string
};
+
+export default injectIntl(FileUpload); \ No newline at end of file
diff --git a/web/react/components/file_upload_overlay.jsx b/web/react/components/file_upload_overlay.jsx
index dbba00022..497d5aee2 100644
--- a/web/react/components/file_upload_overlay.jsx
+++ b/web/react/components/file_upload_overlay.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 FileUploadOverlay extends React.Component {
render() {
var overlayClass = 'file-overlay hidden';
@@ -19,7 +21,12 @@ export default class FileUploadOverlay extends React.Component {
src='/static/images/filesOverlay.png'
alt='Files'
/>
- <span><i className='fa fa-upload'></i>{'Drop a file to upload it.'}</span>
+ <span><i className='fa fa-upload'></i>
+ <FormattedMessage
+ id='upload_overlay.info'
+ defaultMessage='Drop a file to upload it.'
+ />
+ </span>
<img
className='overlay__logo'
src='/static/images/logoWhite.png'
diff --git a/web/react/components/get_link_modal.jsx b/web/react/components/get_link_modal.jsx
index fd20834f4..de3387a35 100644
--- a/web/react/components/get_link_modal.jsx
+++ b/web/react/components/get_link_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';
+
const Modal = ReactBootstrap.Modal;
export default class GetLinkModal extends React.Component {
@@ -39,6 +41,8 @@ export default class GetLinkModal extends React.Component {
}
render() {
+ const userCreationEnabled = global.window.mm_config.EnableUserCreation === 'true';
+
let helpText = null;
if (this.props.helpText) {
helpText = (
@@ -51,7 +55,7 @@ export default class GetLinkModal extends React.Component {
}
let copyLink = null;
- if (document.queryCommandSupported('copy')) {
+ if (userCreationEnabled && document.queryCommandSupported('copy')) {
copyLink = (
<button
data-copy-btn='true'
@@ -59,14 +63,37 @@ export default class GetLinkModal extends React.Component {
className='btn btn-primary pull-left'
onClick={this.copyLink}
>
- {'Copy Link'}
+ <FormattedMessage
+ id='get_link.copy'
+ defaultMessage='Copy Link'
+ />
</button>
);
}
+ let linkText = null;
+ if (userCreationEnabled) {
+ linkText = (
+ <textarea
+ className='form-control no-resize min-height'
+ readOnly='true'
+ ref='textarea'
+ value={this.props.link}
+ />
+ );
+ }
+
var copyLinkConfirm = null;
if (this.state.copiedLink) {
- copyLinkConfirm = <p className='alert alert-success copy-link-confirm'><i className='fa fa-check'></i>{' Link copied to clipboard.'}</p>;
+ copyLinkConfirm = (
+ <p className='alert alert-success copy-link-confirm'>
+ <i className='fa fa-check'></i>
+ <FormattedMessage
+ id='get_link.clipboard'
+ defaultMessage=' Link copied to clipboard.'
+ />
+ </p>
+ );
}
return (
@@ -79,12 +106,7 @@ export default class GetLinkModal extends React.Component {
</Modal.Header>
<Modal.Body>
{helpText}
- <textarea
- className='form-control no-resize min-height'
- readOnly='true'
- ref='textarea'
- value={this.props.link}
- />
+ {linkText}
</Modal.Body>
<Modal.Footer>
<button
@@ -92,7 +114,10 @@ export default class GetLinkModal extends React.Component {
className='btn btn-default'
onClick={this.onHide}
>
- {'Close'}
+ <FormattedMessage
+ id='get_link.close'
+ defaultMessage='Close'
+ />
</button>
{copyLink}
{copyLinkConfirm}
diff --git a/web/react/components/get_team_invite_link_modal.jsx b/web/react/components/get_team_invite_link_modal.jsx
index a926c4451..299729250 100644
--- a/web/react/components/get_team_invite_link_modal.jsx
+++ b/web/react/components/get_team_invite_link_modal.jsx
@@ -6,7 +6,24 @@ import GetLinkModal from './get_link_modal.jsx';
import ModalStore from '../stores/modal_store.jsx';
import TeamStore from '../stores/team_store.jsx';
-export default class GetTeamInviteLinkModal extends React.Component {
+import {intlShape, injectIntl, defineMessages} from 'mm-intl';
+
+const holders = defineMessages({
+ title: {
+ id: 'get_team_invite_link_modal.title',
+ defaultMessage: 'Team Invite Link'
+ },
+ help: {
+ id: 'get_team_invite_link_modal.help',
+ defaultMessage: 'Send teammates the link below for them to sign-up to this team site.'
+ },
+ helpDisabled: {
+ id: 'get_team_invite_link_modal.helpDisabled',
+ defaultMessage: 'User creation has been disabled for your team. Please ask your team administrator for details.'
+ }
+});
+
+class GetTeamInviteLinkModal extends React.Component {
constructor(props) {
super(props);
@@ -32,14 +49,28 @@ export default class GetTeamInviteLinkModal extends React.Component {
}
render() {
+ const {formatMessage} = this.props.intl;
+
+ let helpText = formatMessage(holders.helpDisabled);
+
+ if (global.window.mm_config.EnableUserCreation === 'true') {
+ helpText = formatMessage(holders.help);
+ }
+
return (
<GetLinkModal
show={this.state.show}
onHide={() => this.setState({show: false})}
- title='Team Invite Link'
- helpText='Send teammates the link below for them to sign-up to this team site.'
+ title={formatMessage(holders.title)}
+ helpText={helpText}
link={TeamStore.getCurrentInviteLink()}
/>
);
}
}
+
+GetTeamInviteLinkModal.propTypes = {
+ intl: intlShape.isRequired
+};
+
+export default injectIntl(GetTeamInviteLinkModal); \ No newline at end of file
diff --git a/web/react/components/invite_member_modal.jsx b/web/react/components/invite_member_modal.jsx
index 7e1627555..f2a0a7565 100644
--- a/web/react/components/invite_member_modal.jsx
+++ b/web/react/components/invite_member_modal.jsx
@@ -12,9 +12,38 @@ import ChannelStore from '../stores/channel_store.jsx';
import TeamStore from '../stores/team_store.jsx';
import ConfirmModal from './confirm_modal.jsx';
+import {intlShape, injectIntl, defineMessages, FormattedMessage, FormattedHTMLMessage} from 'mm-intl';
+
const Modal = ReactBootstrap.Modal;
-export default class InviteMemberModal extends React.Component {
+const holders = defineMessages({
+ emailError: {
+ id: 'invite_member.emailError',
+ defaultMessage: 'Please enter a valid email address'
+ },
+ firstname: {
+ id: 'invite_member.firstname',
+ defaultMessage: 'First name'
+ },
+ lastname: {
+ id: 'invite_member.lastname',
+ defaultMessage: 'Last name'
+ },
+ modalTitle: {
+ id: 'invite_member.modalTitle',
+ defaultMessage: 'Discard Invitations?'
+ },
+ modalMessage: {
+ id: 'invite_member.modalMessage',
+ defaultMessage: 'You have unsent invitations, are you sure you want to discard them?'
+ },
+ modalButton: {
+ id: 'invite_member.modalButton',
+ defaultMessage: 'Yes, Discard'
+ }
+});
+
+class InviteMemberModal extends React.Component {
constructor(props) {
super(props);
@@ -72,7 +101,7 @@ export default class InviteMemberModal extends React.Component {
var invite = {};
invite.email = ReactDOM.findDOMNode(this.refs['email' + index]).value.trim();
if (!invite.email || !utils.isEmail(invite.email)) {
- emailErrors[index] = 'Please enter a valid email address';
+ emailErrors[index] = this.props.intl.formatMessage(holders.emailError);
valid = false;
} else {
emailErrors[index] = '';
@@ -103,7 +132,7 @@ export default class InviteMemberModal extends React.Component {
this.setState({isSendingEmails: false});
},
(err) => {
- if (err.message === 'This person is already on your team') {
+ if (err.id === 'api.team.invite_members.already.app_error') {
emailErrors[err.detailed_error] = err.message;
this.setState({emailErrors: emailErrors});
} else {
@@ -199,6 +228,7 @@ export default class InviteMemberModal extends React.Component {
render() {
var currentUser = UserStore.getCurrentUser();
+ const {formatMessage} = this.props.intl;
if (currentUser != null) {
var inviteSections = [];
@@ -252,7 +282,7 @@ export default class InviteMemberModal extends React.Component {
type='text'
className='form-control'
ref={'first_name' + index}
- placeholder='First name'
+ placeholder={formatMessage(holders.firstname)}
maxLength='64'
disabled={!this.state.emailEnabled || !this.state.userCreationEnabled}
spellCheck='false'
@@ -266,7 +296,7 @@ export default class InviteMemberModal extends React.Component {
type='text'
className='form-control'
ref={'last_name' + index}
- placeholder='Last name'
+ placeholder={formatMessage(holders.lastname)}
maxLength='64'
disabled={!this.state.emailEnabled || !this.state.userCreationEnabled}
spellCheck='false'
@@ -318,20 +348,48 @@ export default class InviteMemberModal extends React.Component {
type='button'
className='btn btn-default'
onClick={this.addInviteFields}
- >{'Add another'}</button>
+ >
+ <FormattedMessage
+ id='invite_member.addAnother'
+ defaultMessage='Add another'
+ />
+ </button>
<br/>
<br/>
- <span>{'People invited automatically join the '}<strong>{defaultChannelName}</strong>{' channel.'}</span>
+ <span>
+ <FormattedHTMLMessage
+ id='invite_member.autoJoin'
+ defaultMessage='People invited automatically join the <strong>{channel}</strong> channel.'
+ values={{
+ channel: defaultChannelName
+ }}
+ />
+ </span>
</div>
);
- var sendButtonLabel = 'Send Invitation';
+ var sendButtonLabel = (
+ <FormattedMessage
+ id='invite_member.send'
+ defaultMessage='Send Invitation'
+ />
+ );
if (this.state.isSendingEmails) {
sendButtonLabel = (
- <span><i className='fa fa-spinner fa-spin' />{' Sending'}</span>
+ <span><i className='fa fa-spinner fa-spin' />
+ <FormattedMessage
+ id='invite_member.sending'
+ defaultMessage=' Sending'
+ />
+ </span>
);
} else if (this.state.inviteIds.length > 1) {
- sendButtonLabel = 'Send Invitations';
+ sendButtonLabel = (
+ <FormattedMessage
+ id='invite_member.send2'
+ defaultMessage='Send Invitations'
+ />
+ );
}
sendButton = (
@@ -352,27 +410,46 @@ export default class InviteMemberModal extends React.Component {
href='#'
onClick={this.showGetTeamInviteLinkModal}
>
- {'Team Invite Link'}
+ <FormattedMessage
+ id='invite_member.inviteLink'
+ defaultMessage='Team Invite Link'
+ />
</a>
);
teamInviteLink = (
<p>
- {'You can also invite people using the '}{link}{'.'}
+ <FormattedMessage
+ id='invite_member.teamInviteLink'
+ defaultMessage='You can also invite people using the {link}.'
+ values={{
+ link: (link)
+ }}
+ />
</p>
);
}
content = (
<div>
- <p>{'Email is currently disabled for your team, and email invitations cannot be sent. Contact your system administrator to enable email and email invitations.'}</p>
+ <p>
+ <FormattedMessage
+ id='invite_member.content'
+ defaultMessage='Email is currently disabled for your team, and email invitations cannot be sent. Contact your system administrator to enable email and email invitations.'
+ />
+ </p>
{teamInviteLink}
</div>
);
} else {
content = (
<div>
- <p>{'User creation has been disabled for your team. Please ask your team administrator for details.'}</p>
+ <p>
+ <FormattedMessage
+ id='invite_member.disabled'
+ defaultMessage='User creation has been disabled for your team. Please ask your team administrator for details.'
+ />
+ </p>
</div>
);
}
@@ -387,7 +464,12 @@ export default class InviteMemberModal extends React.Component {
backdrop={this.state.isSendingEmails ? 'static' : true}
>
<Modal.Header closeButton={!this.state.isSendingEmails}>
- <Modal.Title>{'Invite New Member'}</Modal.Title>
+ <Modal.Title>
+ <FormattedMessage
+ id='invite_member.newMember'
+ defaultMessage='Invite New Member'
+ />
+ </Modal.Title>
</Modal.Header>
<Modal.Body ref='modalBody'>
<form role='form'>
@@ -402,15 +484,18 @@ export default class InviteMemberModal extends React.Component {
onClick={this.handleHide.bind(this, true)}
disabled={this.state.isSendingEmails}
>
- {'Cancel'}
+ <FormattedMessage
+ id='invite_member.cancel'
+ defaultMessage='Cancel'
+ />
</button>
{sendButton}
</Modal.Footer>
</Modal>
<ConfirmModal
- title='Discard Invitations?'
- message='You have unsent invitations, are you sure you want to discard them?'
- confirm_button='Yes, Discard'
+ title={formatMessage(holders.modalTitle)}
+ message={formatMessage(holders.modalMessage)}
+ confirm_button={formatMessage(holders.modalButton)}
show={this.state.showConfirmModal}
onConfirm={this.handleHide.bind(this, false)}
onCancel={() => this.setState({showConfirmModal: false})}
@@ -424,4 +509,7 @@ export default class InviteMemberModal extends React.Component {
}
InviteMemberModal.propTypes = {
+ intl: intlShape.isRequired
};
+
+export default injectIntl(InviteMemberModal); \ No newline at end of file
diff --git a/web/react/components/login.jsx b/web/react/components/login.jsx
index c4f530af0..0123a0f3c 100644
--- a/web/react/components/login.jsx
+++ b/web/react/components/login.jsx
@@ -2,6 +2,7 @@
// See License.txt for license information.
import LoginEmail from './login_email.jsx';
+import LoginUsername from './login_username.jsx';
import LoginLdap from './login_ldap.jsx';
import * as Utils from '../utils/utils.jsx';
@@ -35,7 +36,7 @@ export default class Login extends React.Component {
/>
</span>
</a>
- );
+ );
}
if (global.window.mm_config.EnableSignUpWithGoogle === 'true') {
@@ -87,7 +88,7 @@ export default class Login extends React.Component {
}
let emailSignup;
- if (global.window.mm_config.EnableSignUpWithEmail === 'true') {
+ if (global.window.mm_config.EnableSignInWithEmail === 'true') {
emailSignup = (
<LoginEmail
teamName={this.props.teamName}
@@ -189,6 +190,15 @@ export default class Login extends React.Component {
);
}
+ let usernameLogin = null;
+ if (global.window.mm_config.EnableSignInWithUsername === 'true') {
+ usernameLogin = (
+ <LoginUsername
+ teamName={this.props.teamName}
+ />
+ );
+ }
+
return (
<div className='signup-team__container'>
<h5 className='margin--less'>
@@ -210,6 +220,7 @@ export default class Login extends React.Component {
{extraBox}
{loginMessage}
{emailSignup}
+ {usernameLogin}
{ldapLogin}
{userSignUp}
{findTeams}
diff --git a/web/react/components/login_username.jsx b/web/react/components/login_username.jsx
new file mode 100644
index 000000000..f787490fa
--- /dev/null
+++ b/web/react/components/login_username.jsx
@@ -0,0 +1,181 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import * as Utils from '../utils/utils.jsx';
+import * as Client from '../utils/client.jsx';
+import UserStore from '../stores/user_store.jsx';
+
+import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'mm-intl';
+
+var holders = defineMessages({
+ badTeam: {
+ id: 'login_username.badTeam',
+ defaultMessage: 'Bad team name'
+ },
+ usernameReq: {
+ id: 'login_username.usernameReq',
+ defaultMessage: 'A username is required'
+ },
+ pwdReq: {
+ id: 'login_username.pwdReq',
+ defaultMessage: 'A password is required'
+ },
+ verifyEmailError: {
+ id: 'login_username.verifyEmailError',
+ defaultMessage: 'Please verify your email address. Check your inbox for an email.'
+ },
+ userNotFoundError: {
+ id: 'login_username.userNotFoundError',
+ defaultMessage: "We couldn't find an existing account matching your username for this team."
+ },
+ username: {
+ id: 'login_username.username',
+ defaultMessage: 'Username'
+ },
+ pwd: {
+ id: 'login_username.pwd',
+ defaultMessage: 'Password'
+ }
+});
+
+export default class LoginUsername extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.handleSubmit = this.handleSubmit.bind(this);
+
+ this.state = {
+ serverError: ''
+ };
+ }
+ handleSubmit(e) {
+ e.preventDefault();
+ const {formatMessage} = this.props.intl;
+ var state = {};
+
+ const name = this.props.teamName;
+ if (!name) {
+ state.serverError = formatMessage(holders.badTeam);
+ this.setState(state);
+ return;
+ }
+
+ const username = this.refs.username.value.trim();
+ if (!username) {
+ state.serverError = formatMessage(holders.usernameReq);
+ this.setState(state);
+ return;
+ }
+
+ const password = this.refs.password.value.trim();
+ if (!password) {
+ state.serverError = formatMessage(holders.pwdReq);
+ this.setState(state);
+ return;
+ }
+
+ state.serverError = '';
+ this.setState(state);
+
+ Client.loginByUsername(name, username, password,
+ () => {
+ UserStore.setLastUsername(username);
+
+ const redirect = Utils.getUrlParameter('redirect');
+ if (redirect) {
+ window.location.href = decodeURIComponent(redirect);
+ } else {
+ window.location.href = '/' + name + '/channels/town-square';
+ }
+ },
+ (err) => {
+ if (err.message === 'api.user.login.not_verified.app_error') {
+ state.serverError = formatMessage(holders.verifyEmailError);
+ } else if (err.message === 'store.sql_user.get_by_username.app_error') {
+ state.serverError = formatMessage(holders.userNotFoundError);
+ } else {
+ state.serverError = err.message;
+ }
+
+ this.valid = false;
+ this.setState(state);
+ }
+ );
+ }
+ render() {
+ let serverError;
+ let errorClass = '';
+ if (this.state.serverError) {
+ serverError = <label className='control-label'>{this.state.serverError}</label>;
+ errorClass = ' has-error';
+ }
+
+ let priorUsername = UserStore.getLastUsername();
+ let focusUsername = false;
+ let focusPassword = false;
+ if (priorUsername === '') {
+ focusUsername = true;
+ } else {
+ focusPassword = true;
+ }
+
+ const emailParam = Utils.getUrlParameter('email');
+ if (emailParam) {
+ priorUsername = decodeURIComponent(emailParam);
+ }
+
+ const {formatMessage} = this.props.intl;
+ return (
+ <form onSubmit={this.handleSubmit}>
+ <div className='signup__email-container'>
+ <div className={'form-group' + errorClass}>
+ {serverError}
+ </div>
+ <div className={'form-group' + errorClass}>
+ <input
+ autoFocus={focusUsername}
+ type='username'
+ className='form-control'
+ name='username'
+ defaultValue={priorUsername}
+ ref='username'
+ placeholder={formatMessage(holders.username)}
+ spellCheck='false'
+ />
+ </div>
+ <div className={'form-group' + errorClass}>
+ <input
+ autoFocus={focusPassword}
+ type='password'
+ className='form-control'
+ name='password'
+ ref='password'
+ placeholder={formatMessage(holders.pwd)}
+ spellCheck='false'
+ />
+ </div>
+ <div className='form-group'>
+ <button
+ type='submit'
+ className='btn btn-primary'
+ >
+ <FormattedMessage
+ id='login_username.signin'
+ defaultMessage='Sign in'
+ />
+ </button>
+ </div>
+ </div>
+ </form>
+ );
+ }
+}
+LoginUsername.defaultProps = {
+};
+
+LoginUsername.propTypes = {
+ intl: intlShape.isRequired,
+ teamName: React.PropTypes.string.isRequired
+};
+
+export default injectIntl(LoginUsername);
diff --git a/web/react/components/member_list_team_item.jsx b/web/react/components/member_list_team_item.jsx
index 7967c410d..6e1006911 100644
--- a/web/react/components/member_list_team_item.jsx
+++ b/web/react/components/member_list_team_item.jsx
@@ -6,6 +6,8 @@ import * as Client from '../utils/client.jsx';
import * as AsyncClient from '../utils/async_client.jsx';
import * as Utils from '../utils/utils.jsx';
+import {FormattedMessage} from 'mm-intl';
+
export default class MemberListTeamItem extends React.Component {
constructor(props) {
super(props);
@@ -78,14 +80,29 @@ export default class MemberListTeamItem extends React.Component {
}
const user = this.props.user;
- let currentRoles = 'Member';
+ let currentRoles = (
+ <FormattedMessage
+ id='member_team_item.member'
+ defaultMessage='Member'
+ />
+ );
const timestamp = UserStore.getCurrentUser().update_at;
if (user.roles.length > 0) {
if (Utils.isSystemAdmin(user.roles)) {
- currentRoles = 'System Admin';
+ currentRoles = (
+ <FormattedMessage
+ id='member_team_item.systemAdmin'
+ defaultMessage='System Admin'
+ />
+ );
} else if (Utils.isAdmin(user.roles)) {
- currentRoles = 'Team Admin';
+ currentRoles = (
+ <FormattedMessage
+ id='member_team_item.teamAdmin'
+ defaultMessage='Team Admin'
+ />
+ );
} else {
currentRoles = user.roles.charAt(0).toUpperCase() + user.roles.slice(1);
}
@@ -98,7 +115,12 @@ export default class MemberListTeamItem extends React.Component {
let showMakeNotActive = user.roles !== 'system_admin';
if (user.delete_at > 0) {
- currentRoles = 'Inactive';
+ currentRoles = (
+ <FormattedMessage
+ id='member_team_item.inactive'
+ defaultMessage='Inactive'
+ />
+ );
showMakeMember = false;
showMakeAdmin = false;
showMakeActive = true;
@@ -114,7 +136,10 @@ export default class MemberListTeamItem extends React.Component {
href='#'
onClick={this.handleMakeAdmin}
>
- {'Make Team Admin'}
+ <FormattedMessage
+ id='member_team_item.makeAdmin'
+ defaultMessage='Make Team Admin'
+ />
</a>
</li>
);
@@ -129,7 +154,10 @@ export default class MemberListTeamItem extends React.Component {
href='#'
onClick={this.handleMakeMember}
>
- {'Make Member'}
+ <FormattedMessage
+ id='member_team_item.makeMember'
+ defaultMessage='Make Member'
+ />
</a>
</li>
);
@@ -144,7 +172,10 @@ export default class MemberListTeamItem extends React.Component {
href='#'
onClick={this.handleMakeActive}
>
- {'Make Active'}
+ <FormattedMessage
+ id='member_team_item.makeActive'
+ defaultMessage='Make Active'
+ />
</a>
</li>
);
@@ -159,7 +190,10 @@ export default class MemberListTeamItem extends React.Component {
href='#'
onClick={this.handleMakeNotActive}
>
- {'Make Inactive'}
+ <FormattedMessage
+ id='member_team_item.makeInactive'
+ defaultMessage='Make Inactive'
+ />
</a>
</li>
);
diff --git a/web/react/components/more_channels.jsx b/web/react/components/more_channels.jsx
index 29512b9b7..d12ea4703 100644
--- a/web/react/components/more_channels.jsx
+++ b/web/react/components/more_channels.jsx
@@ -8,6 +8,8 @@ import ChannelStore from '../stores/channel_store.jsx';
import LoadingScreen from './loading_screen.jsx';
import NewChannelFlow from './new_channel_flow.jsx';
+import {FormattedMessage} from 'mm-intl';
+
function getStateFromStores() {
return {
channels: ChannelStore.getMoreAll(),
@@ -100,7 +102,10 @@ export default class MoreChannels extends React.Component {
onClick={self.handleJoin.bind(self, channel, index)}
className='btn btn-primary'
>
- Join
+ <FormattedMessage
+ id='more_channels.join'
+ defaultMessage='Join'
+ />
</button>
);
}
@@ -123,8 +128,18 @@ export default class MoreChannels extends React.Component {
} else {
moreChannels = (
<div className='no-channel-message'>
- <p className='primary-message'>No more channels to join</p>
- <p className='secondary-message'>Click 'Create New Channel' to make a new one</p>
+ <p className='primary-message'>
+ <FormattedMessage
+ id='more_channels.noMore'
+ defaultMessage='No more channels to join'
+ />
+ </p>
+ <p className='secondary-message'>
+ <FormattedMessage
+ id='more_channels.createClick'
+ defaultMessage="Click 'Create New Channel' to make a new one"
+ />
+ </p>
</div>
);
}
@@ -148,15 +163,28 @@ export default class MoreChannels extends React.Component {
data-dismiss='modal'
>
<span aria-hidden='true'>{'×'}</span>
- <span className='sr-only'>{'Close'}</span>
+ <span className='sr-only'>
+ <FormattedMessage
+ id='more_channels.close'
+ defaultMessage='Close'
+ />
+ </span>
</button>
- <h4 className='modal-title'>{'More Channels'}</h4>
+ <h4 className='modal-title'>
+ <FormattedMessage
+ id='more_channels.title'
+ defaultMessage='More Channels'
+ />
+ </h4>
<button
type='button'
className='btn btn-primary channel-create-btn'
onClick={this.handleNewChannel}
>
- {'Create New Channel'}
+ <FormattedMessage
+ id='more_channels.create'
+ defaultMessage='Create New Channel'
+ />
</button>
<NewChannelFlow
show={this.state.showNewChannelModal}
@@ -174,7 +202,10 @@ export default class MoreChannels extends React.Component {
className='btn btn-default'
data-dismiss='modal'
>
- {'Close'}
+ <FormattedMessage
+ id='more_channels.close'
+ defaultMessage='Close'
+ />
</button>
</div>
</div>
diff --git a/web/react/components/more_direct_channels.jsx b/web/react/components/more_direct_channels.jsx
index 3661b19e6..f8a6884d0 100644
--- a/web/react/components/more_direct_channels.jsx
+++ b/web/react/components/more_direct_channels.jsx
@@ -5,7 +5,20 @@ const Modal = ReactBootstrap.Modal;
import UserStore from '../stores/user_store.jsx';
import * as Utils from '../utils/utils.jsx';
-export default class MoreDirectChannels extends React.Component {
+import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl';
+
+const holders = defineMessages({
+ member: {
+ id: 'more_direct_channels.member',
+ defaultMessage: 'Member'
+ },
+ search: {
+ id: 'more_direct_channels.search',
+ defaultMessage: 'Search members'
+ }
+});
+
+class MoreDirectChannels extends React.Component {
constructor(props) {
super(props);
@@ -148,7 +161,10 @@ export default class MoreDirectChannels extends React.Component {
className='btn btn-primary btn-message'
onClick={this.handleShowDirectChannel.bind(this, user)}
>
- {'Message'}
+ <FormattedMessage
+ id='more_direct_channels.message'
+ defaultMessage='Message'
+ />
</button>
);
}
@@ -180,6 +196,7 @@ export default class MoreDirectChannels extends React.Component {
}
render() {
+ const {formatMessage} = this.props.intl;
if (!this.props.show) {
return null;
}
@@ -199,19 +216,44 @@ export default class MoreDirectChannels extends React.Component {
const userEntries = users.map(this.createRowForUser);
if (userEntries.length === 0) {
- userEntries.push(<tr key='no-users-found'><td>{'No users found :('}</td></tr>);
+ userEntries.push(
+ <tr key='no-users-found'><td>
+ <FormattedMessage
+ id='more_direct_channels.notFound'
+ defaultMessage='No users found :('
+ />
+ </td></tr>);
}
- let memberString = 'Member';
+ let memberString = formatMessage(holders.member);
if (users.length !== 1) {
memberString += 's';
}
let count;
if (users.length === this.state.users.length) {
- count = `${users.length} ${memberString}`;
+ count = (
+ <FormattedMessage
+ id='more_direct_channels.count'
+ defaultMessage='{count} {member}'
+ values={{
+ count: users.length,
+ member: memberString
+ }}
+ />
+ );
} else {
- count = `${users.length} ${memberString} of ${this.state.users.length} Total`;
+ count = (
+ <FormattedMessage
+ id='more_direct_channels.countTotal'
+ defaultMessage='{count} {member} of {total} Total'
+ values={{
+ count: users.length,
+ member: memberString,
+ total: this.state.users.length
+ }}
+ />
+ );
}
return (
@@ -221,7 +263,12 @@ export default class MoreDirectChannels extends React.Component {
onHide={this.handleHide}
>
<Modal.Header closeButton={true}>
- <Modal.Title>{'Direct Messages'}</Modal.Title>
+ <Modal.Title>
+ <FormattedMessage
+ id='more_direct_channels.title'
+ defaultMessage='Direct Messages'
+ />
+ </Modal.Title>
</Modal.Header>
<Modal.Body ref='modalBody'>
<div className='filter-row'>
@@ -229,7 +276,7 @@ export default class MoreDirectChannels extends React.Component {
<input
ref='filter'
className='form-control filter-textbox'
- placeholder='Search members'
+ placeholder={formatMessage(holders.search)}
onInput={this.handleFilterChange}
/>
</div>
@@ -254,7 +301,10 @@ export default class MoreDirectChannels extends React.Component {
className='btn btn-default'
onClick={this.handleHide}
>
- {'Close'}
+ <FormattedMessage
+ id='more_direct_channels.close'
+ defaultMessage='Close'
+ />
</button>
</Modal.Footer>
</Modal>
@@ -263,6 +313,9 @@ export default class MoreDirectChannels extends React.Component {
}
MoreDirectChannels.propTypes = {
+ intl: intlShape.isRequired,
show: React.PropTypes.bool.isRequired,
onModalDismissed: React.PropTypes.func
};
+
+export default injectIntl(MoreDirectChannels); \ No newline at end of file
diff --git a/web/react/components/msg_typing.jsx b/web/react/components/msg_typing.jsx
index 35a832875..b95b06260 100644
--- a/web/react/components/msg_typing.jsx
+++ b/web/react/components/msg_typing.jsx
@@ -5,9 +5,19 @@ import SocketStore from '../stores/socket_store.jsx';
import UserStore from '../stores/user_store.jsx';
import Constants from '../utils/constants.jsx';
+
+import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl';
+
const SocketEvents = Constants.SocketEvents;
-export default class MsgTyping extends React.Component {
+const holders = defineMessages({
+ someone: {
+ id: 'msg_typing.someone',
+ defaultMessage: 'Someone'
+ }
+});
+
+class MsgTyping extends React.Component {
constructor(props) {
super(props);
@@ -44,10 +54,10 @@ export default class MsgTyping extends React.Component {
}
onChange(msg) {
- let username = 'Someone';
+ let username = this.props.intl.formatMessage(holders.someone);
if (msg.action === SocketEvents.TYPING &&
- this.props.channelId === msg.channel_id &&
- this.props.parentId === msg.props.parent_id) {
+ this.props.channelId === msg.channel_id &&
+ this.props.parentId === msg.props.parent_id) {
if (UserStore.hasProfile(msg.user_id)) {
username = UserStore.getProfile(msg.user_id).username;
}
@@ -80,11 +90,28 @@ export default class MsgTyping extends React.Component {
text = '';
break;
case 1:
- text = users[0] + ' is typing...';
+ text = (
+ <FormattedMessage
+ id='msg_typing.isTyping'
+ defaultMessage='{user} is typing...'
+ values={{
+ user: users[0]
+ }}
+ />
+ );
break;
default: {
const last = users.pop();
- text = users.join(', ') + ' and ' + last + ' are typing...';
+ text = (
+ <FormattedMessage
+ id='msg_typing.areTyping'
+ defaultMessage='{users} and {last} are typing...'
+ vaues={{
+ users: users.join(', '),
+ last: last
+ }}
+ />
+ );
break;
}
}
@@ -100,6 +127,9 @@ export default class MsgTyping extends React.Component {
}
MsgTyping.propTypes = {
+ intl: intlShape.isRequired,
channelId: React.PropTypes.string,
parentId: React.PropTypes.string
};
+
+export default injectIntl(MsgTyping); \ No newline at end of file
diff --git a/web/react/components/navbar.jsx b/web/react/components/navbar.jsx
index ae14fca2f..7326a9ef8 100644
--- a/web/react/components/navbar.jsx
+++ b/web/react/components/navbar.jsx
@@ -392,10 +392,14 @@ export default class Navbar extends React.Component {
} else if (channel.type === 'D') {
isDirect = true;
if (this.state.users.length > 1) {
+ let p;
if (this.state.users[0].id === currentId) {
- channelTitle = UserStore.getProfile(this.state.users[1].id).username;
+ p = UserStore.getProfile(this.state.users[1].id);
} else {
- channelTitle = UserStore.getProfile(this.state.users[0].id).username;
+ p = UserStore.getProfile(this.state.users[0].id);
+ }
+ if (p != null) {
+ channelTitle = p.username;
}
}
}
diff --git a/web/react/components/navbar_dropdown.jsx b/web/react/components/navbar_dropdown.jsx
index d4ec5a5f5..e9df03c33 100644
--- a/web/react/components/navbar_dropdown.jsx
+++ b/web/react/components/navbar_dropdown.jsx
@@ -14,6 +14,8 @@ import UserSettingsModal from './user_settings/user_settings_modal.jsx';
import Constants from '../utils/constants.jsx';
+import {FormattedMessage} from 'mm-intl';
+
function getStateFromStores() {
const teams = [];
const teamsObject = UserStore.getTeams();
@@ -97,7 +99,10 @@ export default class NavbarDropdown extends React.Component {
href='#'
onClick={EventHelpers.showInviteMemberModal}
>
- {'Invite New Member'}
+ <FormattedMessage
+ id='navbar_dropdown.inviteMember'
+ defaultMessage='Invite New Member'
+ />
</a>
</li>
);
@@ -109,7 +114,10 @@ export default class NavbarDropdown extends React.Component {
href='#'
onClick={EventHelpers.showGetTeamInviteLinkModal}
>
- {'Get Team Invite Link'}
+ <FormattedMessage
+ id='navbar_dropdown.teamLink'
+ defaultMessage='Get Team Invite Link'
+ />
</a>
</li>
);
@@ -120,7 +128,10 @@ export default class NavbarDropdown extends React.Component {
manageLink = (
<li>
<ToggleModalButton dialogType={TeamMembersModal}>
- {'Manage Members'}
+ <FormattedMessage
+ id='navbar_dropdown.manageMembers'
+ defaultMessage='Manage Members'
+ />
</ToggleModalButton>
</li>
);
@@ -134,7 +145,10 @@ export default class NavbarDropdown extends React.Component {
data-toggle='modal'
data-target='#team_settings'
>
- {'Team Settings'}
+ <FormattedMessage
+ id='navbar_dropdown.teamSettings'
+ defaultMessage='Team Settings'
+ />
</a>
</li>
);
@@ -146,7 +160,10 @@ export default class NavbarDropdown extends React.Component {
<a
href={'/admin_console?' + Utils.getSessionIndex()}
>
- {'System Console'}
+ <FormattedMessage
+ id='navbar_dropdown.console'
+ defaultMessage='System Console'
+ />
</a>
</li>
);
@@ -165,7 +182,16 @@ export default class NavbarDropdown extends React.Component {
this.state.teams.forEach((team) => {
if (team.name !== this.props.teamName) {
- teams.push(<li key={team.name}><a href={Utils.getWindowLocationOrigin() + '/' + team.name}>{'Switch to ' + team.display_name}</a></li>);
+ teams.push(
+ <li key={team.name}><a href={Utils.getWindowLocationOrigin() + '/' + team.name}>
+ <FormattedMessage
+ id='navbar_dropdown.switchTeam'
+ defaultMessage='Switch to {team}'
+ values={{
+ team: team.display_name
+ }}
+ />
+ </a></li>);
}
});
}
@@ -178,7 +204,10 @@ export default class NavbarDropdown extends React.Component {
target='_blank'
href={Utils.getWindowLocationOrigin() + '/signup_team'}
>
- {'Create a New Team'}
+ <FormattedMessage
+ id='navbar_dropdown.create'
+ defaultMessage='Create a New Team'
+ />
</a>
</li>
);
@@ -192,7 +221,10 @@ export default class NavbarDropdown extends React.Component {
target='_blank'
href={global.window.mm_config.HelpLink}
>
- {'Help'}
+ <FormattedMessage
+ id='navbar_dropdown.help'
+ defaultMessage='Help'
+ />
</a>
</li>
);
@@ -206,7 +238,10 @@ export default class NavbarDropdown extends React.Component {
target='_blank'
href={global.window.mm_config.ReportAProblemLink}
>
- {'Report a Problem'}
+ <FormattedMessage
+ id='navbar_dropdown.report'
+ defaultMessage='Report a Problem'
+ />
</a>
</li>
);
@@ -239,7 +274,10 @@ export default class NavbarDropdown extends React.Component {
href='#'
onClick={() => this.setState({showUserSettingsModal: true})}
>
- {'Account Settings'}
+ <FormattedMessage
+ id='navbar_dropdown.accountSettings'
+ defaultMessage='Account Settings'
+ />
</a>
</li>
{inviteLink}
@@ -249,7 +287,10 @@ export default class NavbarDropdown extends React.Component {
href='#'
onClick={this.handleLogoutClick}
>
- {'Logout'}
+ <FormattedMessage
+ id='navbar_dropdown.logout'
+ defaultMessage='Logout'
+ />
</a>
</li>
{adminDivider}
@@ -265,7 +306,10 @@ export default class NavbarDropdown extends React.Component {
href='#'
onClick={this.handleAboutModal}
>
- {'About Mattermost'}
+ <FormattedMessage
+ id='navbar_dropdown.about'
+ defaultMessage='About Mattermost'
+ />
</a>
</li>
<UserSettingsModal
diff --git a/web/react/components/new_channel_flow.jsx b/web/react/components/new_channel_flow.jsx
index 3a114aa19..a0bb14e8f 100644
--- a/web/react/components/new_channel_flow.jsx
+++ b/web/react/components/new_channel_flow.jsx
@@ -9,11 +9,47 @@ import UserStore from '../stores/user_store.jsx';
import NewChannelModal from './new_channel_modal.jsx';
import ChangeURLModal from './change_url_modal.jsx';
+import {intlShape, injectIntl, defineMessages} from 'mm-intl';
+
const SHOW_NEW_CHANNEL = 1;
const SHOW_EDIT_URL = 2;
const SHOW_EDIT_URL_THEN_COMPLETE = 3;
+const messages = defineMessages({
+ invalidName: {
+ id: 'channel_flow.invalidName',
+ defaultMessage: 'Invalid Channel Name'
+ },
+ alreadyExist: {
+ id: 'channel_flow.alreadyExist',
+ defaultMessage: 'A channel with that URL already exists'
+ },
+ channel: {
+ id: 'channel_flow.channel',
+ defaultMessage: 'Channel'
+ },
+ group: {
+ id: 'channel_flow.group',
+ defaultMessage: 'Group'
+ },
+ change: {
+ id: 'channel_flow.changeUrlTitle',
+ defaultMessage: 'Change {term} URL'
+ },
+ set: {
+ id: 'channel_flow.set_url_title',
+ defaultMessage: 'Set {term} URL'
+ },
+ create: {
+ id: 'channel_flow.create',
+ defaultMessage: 'Create {term}'
+ },
+ changeUrlDescription: {
+ id: 'channel_flow.changeUrlDescription',
+ defaultMessage: 'Some characters are not allowed in URLs and may be removed.'
+ }
+});
-export default class NewChannelFlow extends React.Component {
+class NewChannelFlow extends React.Component {
constructor(props) {
super(props);
@@ -51,9 +87,10 @@ export default class NewChannelFlow extends React.Component {
doSubmit() {
var channel = {};
+ const {formatMessage} = this.props.intl;
channel.display_name = this.state.channelDisplayName;
if (!channel.display_name) {
- this.setState({serverError: 'Invalid Channel Name'});
+ this.setState({serverError: formatMessage(messages.invalidName)});
return;
}
@@ -75,11 +112,11 @@ export default class NewChannelFlow extends React.Component {
Utils.switchChannel(data);
},
(err) => {
- if (err.message === 'Name must be 2 or more lowercase alphanumeric characters') {
+ if (err.id === 'model.channel.is_valid.2_or_more.app_error') {
this.setState({flowState: SHOW_EDIT_URL_THEN_COMPLETE});
}
- if (err.message === 'A channel with that handle already exists') {
- this.setState({serverError: 'A channel with that URL already exists'});
+ if (err.id === 'store.sql_channel.update.exists.app_error') {
+ this.setState({serverError: formatMessage(messages.alreadyExist)});
return;
}
this.setState({serverError: err.message});
@@ -130,27 +167,29 @@ export default class NewChannelFlow extends React.Component {
let changeURLSubmitButtonText = '';
let channelTerm = '';
+ const {formatMessage} = this.props.intl;
+
// Only listen to flow state if we are being shown
if (this.props.show) {
switch (this.state.flowState) {
case SHOW_NEW_CHANNEL:
if (this.state.channelType === 'O') {
showChannelModal = true;
- channelTerm = 'Channel';
+ channelTerm = formatMessage(messages.channel);
} else {
showGroupModal = true;
- channelTerm = 'Group';
+ channelTerm = formatMessage(messages.group);
}
break;
case SHOW_EDIT_URL:
showChangeURLModal = true;
- changeURLTitle = 'Change ' + channelTerm + ' URL';
- changeURLSubmitButtonText = 'Change ' + channelTerm + ' URL';
+ changeURLTitle = formatMessage(messages.change, {term: channelTerm});
+ changeURLSubmitButtonText = formatMessage(messages.change, {term: channelTerm});
break;
case SHOW_EDIT_URL_THEN_COMPLETE:
showChangeURLModal = true;
- changeURLTitle = 'Set ' + channelTerm + ' URL';
- changeURLSubmitButtonText = 'Create ' + channelTerm;
+ changeURLTitle = formatMessage(messages.set, {term: channelTerm});
+ changeURLSubmitButtonText = formatMessage(messages.create, {term: channelTerm});
break;
}
}
@@ -181,7 +220,7 @@ export default class NewChannelFlow extends React.Component {
<ChangeURLModal
show={showChangeURLModal}
title={changeURLTitle}
- description={'Some characters are not allowed in URLs and may be removed.'}
+ description={formatMessage(messages.changeUrlDescription)}
urlLabel={channelTerm + ' URL'}
submitButtonText={changeURLSubmitButtonText}
currentURL={this.state.channelName}
@@ -200,7 +239,10 @@ NewChannelFlow.defaultProps = {
};
NewChannelFlow.propTypes = {
+ intl: intlShape.isRequired,
show: React.PropTypes.bool.isRequired,
channelType: React.PropTypes.string.isRequired,
onModalDismissed: React.PropTypes.func.isRequired
};
+
+export default injectIntl(NewChannelFlow); \ No newline at end of file
diff --git a/web/react/components/new_channel_modal.jsx b/web/react/components/new_channel_modal.jsx
index 9f733c476..788e6dc1b 100644
--- a/web/react/components/new_channel_modal.jsx
+++ b/web/react/components/new_channel_modal.jsx
@@ -2,9 +2,19 @@
// See License.txt for license information.
import * as Utils from '../utils/utils.jsx';
+
+import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl';
+
var Modal = ReactBootstrap.Modal;
-export default class NewChannelModal extends React.Component {
+const holders = defineMessages({
+ nameEx: {
+ id: 'channel_modal.nameEx',
+ defaultMessage: 'E.g.: "Bugs", "Marketing", "办公室恋情"'
+ }
+});
+
+class NewChannelModal extends React.Component {
constructor(props) {
super(props);
@@ -32,7 +42,7 @@ export default class NewChannelModal extends React.Component {
const displayName = ReactDOM.findDOMNode(this.refs.display_name).value.trim();
if (displayName.length < 1) {
- this.setState({displayNameError: 'This field is required'});
+ this.setState({displayNameError: true});
return;
}
@@ -51,7 +61,15 @@ export default class NewChannelModal extends React.Component {
var displayNameClass = 'form-group';
if (this.state.displayNameError) {
- displayNameError = <p className='input__help error'>{this.state.displayNameError}</p>;
+ displayNameError = (
+ <p className='input__help error'>
+ <FormattedMessage
+ id='channel_modal.displayNameError'
+ defaultMessage='This field is required'
+ />
+ {this.state.displayNameError}
+ </p>
+ );
displayNameClass += ' has-error';
}
@@ -63,29 +81,51 @@ export default class NewChannelModal extends React.Component {
var channelSwitchText = '';
switch (this.props.channelType) {
case 'P':
- channelTerm = 'Group';
+ channelTerm = (
+ <FormattedMessage
+ id='channel_modal.group'
+ defaultMessage='Group'
+ />
+ );
channelSwitchText = (
<div className='modal-intro'>
- {'Create a new private group with restricted membership. '}
+ <FormattedMessage
+ id='channel_modal.privateGroup1'
+ defaultMessage='Create a new private group with restricted membership. '
+ />
<a
href='#'
onClick={this.props.onTypeSwitched}
>
- {'Create a public channel'}
+ <FormattedMessage
+ id='channel_modal.publicChannel1'
+ defaultMessage='Create a public channel'
+ />
</a>
</div>
);
break;
case 'O':
- channelTerm = 'Channel';
+ channelTerm = (
+ <FormattedMessage
+ id='channel_modal.channel'
+ defaultMessage='Channel'
+ />
+ );
channelSwitchText = (
<div className='modal-intro'>
- {'Create a new public channel anyone can join. '}
+ <FormattedMessage
+ id='channel_modal.publicChannel2'
+ defaultMessage='Create a new public channel anyone can join. '
+ />
<a
href='#'
onClick={this.props.onTypeSwitched}
>
- {'Create a private group'}
+ <FormattedMessage
+ id='channel_modal.privateGroup2'
+ defaultMessage='Create a private group'
+ />
</a>
</div>
);
@@ -102,7 +142,13 @@ export default class NewChannelModal extends React.Component {
onHide={this.props.onModalDismissed}
>
<Modal.Header closeButton={true}>
- <Modal.Title>{'New ' + channelTerm}</Modal.Title>
+ <Modal.Title>
+ <FormattedMessage
+ id='channel_modal.modalTitle'
+ defaultMessage='New '
+ />
+ {channelTerm}
+ </Modal.Title>
</Modal.Header>
<form
role='form'
@@ -113,14 +159,19 @@ export default class NewChannelModal extends React.Component {
{channelSwitchText}
</div>
<div className={displayNameClass}>
- <label className='col-sm-3 form__label control-label'>{'Name'}</label>
+ <label className='col-sm-3 form__label control-label'>
+ <FormattedMessage
+ id='channel_modal.name'
+ defaultMessage='Name'
+ />
+ </label>
<div className='col-sm-9'>
<input
onChange={this.handleChange}
type='text'
ref='display_name'
className='form-control'
- placeholder='E.g.: "Bugs", "Marketing", "办公室恋情"'
+ placeholder={this.props.intl.formatMessage(holders.nameEx)}
maxLength='22'
value={this.props.channelData.displayName}
autoFocus={true}
@@ -133,7 +184,10 @@ export default class NewChannelModal extends React.Component {
href='#'
onClick={this.props.onChangeURLPressed}
>
- {'Edit'}
+ <FormattedMessage
+ id='channel_modal.edit'
+ defaultMessage='Edit'
+ />
</a>
{')'}
</p>
@@ -141,22 +195,38 @@ export default class NewChannelModal extends React.Component {
</div>
<div className='form-group less'>
<div className='col-sm-3'>
- <label className='form__label control-label'>{'Purpose'}</label>
- <label className='form__label light'>{'(optional)'}</label>
+ <label className='form__label control-label'>
+ <FormattedMessage
+ id='channel_modal.purpose'
+ defaultMessage='Purpose'
+ />
+ </label>
+ <label className='form__label light'>
+ <FormattedMessage
+ id='channel_modal.optional'
+ defaultMessage='(optional)'
+ />
+ </label>
</div>
<div className='col-sm-9'>
<textarea
className='form-control no-resize'
ref='channel_purpose'
rows='4'
- placeholder='Purpose'
+ placeholder={this.props.intl.formatMessage({id: 'channel_modal.purpose'})}
maxLength='128'
value={this.props.channelData.purpose}
onChange={this.handleChange}
tabIndex='2'
/>
<p className='input__help'>
- {`Describe how this ${channelTerm} should be used.`}
+ <FormattedMessage
+ id='channel_modal.descriptionHelp'
+ defaultMessage='Describe how this {term} should be used.'
+ values={{
+ term: (channelTerm)
+ }}
+ />
</p>
{serverError}
</div>
@@ -168,7 +238,10 @@ export default class NewChannelModal extends React.Component {
className='btn btn-default'
onClick={this.props.onModalDismissed}
>
- {'Cancel'}
+ <FormattedMessage
+ id='channel_modal.cancel'
+ defaultMessage='Cancel'
+ />
</button>
<button
onClick={this.handleSubmit}
@@ -176,7 +249,11 @@ export default class NewChannelModal extends React.Component {
className='btn btn-primary'
tabIndex='3'
>
- {'Create New ' + channelTerm}
+ <FormattedMessage
+ id='channel_modal.createNew'
+ defaultMessage='Create New '
+ />
+ {channelTerm}
</button>
</Modal.Footer>
</form>
@@ -192,6 +269,7 @@ NewChannelModal.defaultProps = {
serverError: ''
};
NewChannelModal.propTypes = {
+ intl: intlShape.isRequired,
show: React.PropTypes.bool.isRequired,
channelType: React.PropTypes.string.isRequired,
channelData: React.PropTypes.object.isRequired,
@@ -202,3 +280,5 @@ NewChannelModal.propTypes = {
onChangeURLPressed: React.PropTypes.func.isRequired,
onDataChanged: React.PropTypes.func.isRequired
};
+
+export default injectIntl(NewChannelModal); \ No newline at end of file
diff --git a/web/react/components/post_info.jsx b/web/react/components/post_info.jsx
index 73b47024c..2bff675a9 100644
--- a/web/react/components/post_info.jsx
+++ b/web/react/components/post_info.jsx
@@ -22,26 +22,6 @@ export default class PostInfo extends React.Component {
this.handlePermalinkCopy = this.handlePermalinkCopy.bind(this);
}
- createReplyLink() {
- if (this.props.allowReply === 'true') {
- var hideReply = '';
-
- if (this.props.commentCount >= 1) {
- hideReply = ' post__reply--hide';
- }
-
- return (
- <div className={'post__reply' + hideReply}>
- <a
- onClick={this.props.handleCommentClick}
- href='#'
- >
- <span dangerouslySetInnerHTML={{__html: Constants.REPLY_ICON}}/>
- </a>
- </div>
- );
- }
- }
createDropdown() {
var post = this.props.post;
var isOwner = UserStore.getCurrentId() === post.user_id;
@@ -62,6 +42,23 @@ export default class PostInfo extends React.Component {
dataComments = this.props.commentCount;
}
+ if (this.props.allowReply === 'true') {
+ dropdownContents.push(
+ <li
+ key='replyLink'
+ role='presentation'
+ >
+ <a
+ className='link__reply theme'
+ href='#'
+ onClick={this.props.handleCommentClick}
+ >
+ {'Reply'}
+ </a>
+ </li>
+ );
+ }
+
dropdownContents.push(
<li
key='copyLink'
@@ -176,7 +173,7 @@ export default class PostInfo extends React.Component {
>
<span
className='comment-icon'
- dangerouslySetInnerHTML={{__html: Constants.COMMENT_ICON}}
+ dangerouslySetInnerHTML={{__html: Constants.REPLY_ICON}}
/>
{commentCountText}
</a>
@@ -184,7 +181,6 @@ export default class PostInfo extends React.Component {
}
var dropdown = this.createDropdown();
- var replyLink = this.createReplyLink();
const permalink = TeamStore.getCurrentTeamUrl() + '/pl/' + post.id;
const copyButtonText = this.state.copiedLink ? (<div>{'Copy '}<i className='fa fa-check'/></div>) : 'Copy';
@@ -227,7 +223,6 @@ export default class PostInfo extends React.Component {
/>
</li>
<li className='col col__reply'>
- {replyLink}
<div
className='dropdown'
ref='dotMenu'
diff --git a/web/react/components/register_app_modal.jsx b/web/react/components/register_app_modal.jsx
index f49b33f73..e6d13863b 100644
--- a/web/react/components/register_app_modal.jsx
+++ b/web/react/components/register_app_modal.jsx
@@ -7,9 +7,22 @@ import ModalStore from '../stores/modal_store.jsx';
const Modal = ReactBootstrap.Modal;
import Constants from '../utils/constants.jsx';
+import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl';
+
const ActionTypes = Constants.ActionTypes;
-export default class RegisterAppModal extends React.Component {
+const holders = defineMessages({
+ required: {
+ id: 'register_app.required',
+ defaultMessage: 'Required'
+ },
+ optional: {
+ id: 'register_app.optional',
+ defaultMessage: 'Optional'
+ }
+});
+
+class RegisterAppModal extends React.Component {
constructor() {
super();
@@ -60,7 +73,7 @@ export default class RegisterAppModal extends React.Component {
var name = this.refs.name.value;
if (!name || name.length === 0) {
- state.nameError = 'Application name must be filled in.';
+ state.nameError = true;
this.setState(state);
return;
}
@@ -69,7 +82,7 @@ export default class RegisterAppModal extends React.Component {
var homepage = this.refs.homepage.value;
if (!homepage || homepage.length === 0) {
- state.homepageError = 'Homepage must be filled in.';
+ state.homepageError = true;
this.setState(state);
return;
}
@@ -81,7 +94,7 @@ export default class RegisterAppModal extends React.Component {
var rawCallbacks = this.refs.callback.value.trim();
if (!rawCallbacks || rawCallbacks.length === 0) {
- state.callbackError = 'At least one callback URL must be filled in.';
+ state.callbackError = true;
this.setState(state);
return;
}
@@ -112,17 +125,45 @@ export default class RegisterAppModal extends React.Component {
this.setState({saved: this.refs.save.checked});
}
render() {
+ const {formatMessage} = this.props.intl;
var nameError;
if (this.state.nameError) {
- nameError = <div className='form-group has-error'><label className='control-label'>{this.state.nameError}</label></div>;
+ nameError = (
+ <div className='form-group has-error'>
+ <label className='control-label'>
+ <FormattedMessage
+ id='register_app.nameError'
+ defaultMessage='Application name must be filled in.'
+ />
+ </label>
+ </div>
+ );
}
var homepageError;
if (this.state.homepageError) {
- homepageError = <div className='form-group has-error'><label className='control-label'>{this.state.homepageError}</label></div>;
+ homepageError = (
+ <div className='form-group has-error'>
+ <label className='control-label'>
+ <FormattedMessage
+ id='register_app.homepageError'
+ defaultMessage='Homepage must be filled in.'
+ />
+ </label>
+ </div>
+ );
}
var callbackError;
if (this.state.callbackError) {
- callbackError = <div className='form-group has-error'><label className='control-label'>{this.state.callbackError}</label></div>;
+ callbackError = (
+ <div className='form-group has-error'>
+ <label className='control-label'>
+ <FormattedMessage
+ id='register_app.callbackError'
+ defaultMessage='At least one callback URL must be filled in.'
+ />
+ </label>
+ </div>
+ );
}
var serverError;
if (this.state.serverError) {
@@ -135,50 +176,75 @@ export default class RegisterAppModal extends React.Component {
body = (
<div className='settings-modal'>
<div className='form-horizontal user-settings'>
- <h4 className='padding-bottom x3'>{'Register a New Application'}</h4>
+ <h4 className='padding-bottom x3'>
+ <FormattedMessage
+ id='register_app.title'
+ defaultMessage='Register a New Application'
+ />
+ </h4>
<div className='row'>
- <label className='col-sm-4 control-label'>{'Application Name'}</label>
+ <label className='col-sm-4 control-label'>
+ <FormattedMessage
+ id='register_app.name'
+ defaultMessage='Application Name'
+ />
+ </label>
<div className='col-sm-7'>
<input
ref='name'
className='form-control'
type='text'
- placeholder='Required'
+ placeholder={formatMessage(holders.required)}
/>
{nameError}
</div>
</div>
<div className='row padding-top x2'>
- <label className='col-sm-4 control-label'>{'Homepage URL'}</label>
+ <label className='col-sm-4 control-label'>
+ <FormattedMessage
+ id='register_app.homepage'
+ defaultMessage='Homepage URL'
+ />
+ </label>
<div className='col-sm-7'>
<input
ref='homepage'
className='form-control'
type='text'
- placeholder='Required'
+ placeholder={formatMessage(holders.required)}
/>
{homepageError}
</div>
</div>
<div className='row padding-top x2'>
- <label className='col-sm-4 control-label'>{'Description'}</label>
+ <label className='col-sm-4 control-label'>
+ <FormattedMessage
+ id='register_app.description'
+ defaultMessage='Description'
+ />
+ </label>
<div className='col-sm-7'>
<input
ref='desc'
className='form-control'
type='text'
- placeholder='Optional'
+ placeholder={formatMessage(holders.optional)}
/>
</div>
</div>
<div className='row padding-top padding-bottom x2'>
- <label className='col-sm-4 control-label'>{'Callback URL'}</label>
+ <label className='col-sm-4 control-label'>
+ <FormattedMessage
+ id='register_app.callback'
+ defaultMessage='Callback URL'
+ />
+ </label>
<div className='col-sm-7'>
<textarea
ref='callback'
className='form-control'
type='text'
- placeholder='Required'
+ placeholder={formatMessage(holders.required)}
rows='5'
/>
{callbackError}
@@ -196,7 +262,10 @@ export default class RegisterAppModal extends React.Component {
className='btn btn-default'
onClick={() => this.updateShow(false)}
>
- {'Cancel'}
+ <FormattedMessage
+ id='register_app.cancel'
+ defaultMessage='Cancel'
+ />
</button>
<button
onClick={this.handleSubmit}
@@ -204,7 +273,10 @@ export default class RegisterAppModal extends React.Component {
className='btn btn-primary'
tabIndex='3'
>
- {'Register'}
+ <FormattedMessage
+ id='register_app.register'
+ defaultMessage='Register'
+ />
</button>
</div>
);
@@ -216,10 +288,20 @@ export default class RegisterAppModal extends React.Component {
body = (
<div className='form-horizontal user-settings'>
- <h4 className='padding-bottom x3'>{'Your Application Credentials'}</h4>
+ <h4 className='padding-bottom x3'>
+ <FormattedMessage
+ id='register_app.credentialsTitle'
+ defaultMessage='Your Application Credentials'
+ />
+ </h4>
<br/>
<div className='row'>
- <label className='col-sm-4 control-label'>{'Client ID'}</label>
+ <label className='col-sm-4 control-label'>
+ <FormattedMessage
+ id='register_app.clientId'
+ defaultMessage='Client ID'
+ />
+ </label>
<div className='col-sm-7'>
<input
className='form-control'
@@ -231,7 +313,11 @@ export default class RegisterAppModal extends React.Component {
</div>
<br/>
<div className='row padding-top x2'>
- <label className='col-sm-4 control-label'>{'Client Secret'}</label>
+ <label className='col-sm-4 control-label'>
+ <FormattedMessage
+ id='register_app.clientSecret'
+ defaultMessage='Client Secret'
+ /></label>
<div className='col-sm-7'>
<input
className='form-control'
@@ -243,7 +329,12 @@ export default class RegisterAppModal extends React.Component {
</div>
<br/>
<br/>
- <strong>{'Save these somewhere SAFE and SECURE. Treat your Client ID as your app\'s username and your Client Secret as the app\'s password.'}</strong>
+ <strong>
+ <FormattedMessage
+ id='register_app.credentialsDescription'
+ defaultMessage="Save these somewhere SAFE and SECURE. Treat your Client ID as your app's username and your Client Secret as the app's password."
+ />
+ </strong>
<br/>
<br/>
<div className='checkbox'>
@@ -254,7 +345,10 @@ export default class RegisterAppModal extends React.Component {
checked={this.state.saved}
onChange={this.save}
/>
- {'I have saved both my Client Id and Client Secret somewhere safe'}
+ <FormattedMessage
+ id='register_app.credentialsSave'
+ defaultMessage='I have saved both my Client Id and Client Secret somewhere safe'
+ />
</label>
</div>
</div>
@@ -269,7 +363,10 @@ export default class RegisterAppModal extends React.Component {
this.updateShow(false);
}}
>
- {'Close'}
+ <FormattedMessage
+ id='register_app.close'
+ defaultMessage='Close'
+ />
</a>
);
}
@@ -281,7 +378,12 @@ export default class RegisterAppModal extends React.Component {
onHide={() => this.updateShow(false)}
>
<Modal.Header closeButton={true}>
- <Modal.Title>{'Developer Applications'}</Modal.Title>
+ <Modal.Title>
+ <FormattedMessage
+ id='register_app.dev'
+ defaultMessage='Developer Applications'
+ />
+ </Modal.Title>
</Modal.Header>
<form
role='form'
@@ -300,3 +402,8 @@ export default class RegisterAppModal extends React.Component {
}
}
+RegisterAppModal.propTypes = {
+ intl: intlShape.isRequired
+};
+
+export default injectIntl(RegisterAppModal);
diff --git a/web/react/components/rhs_comment.jsx b/web/react/components/rhs_comment.jsx
index 7aae5177e..1addebbe4 100644
--- a/web/react/components/rhs_comment.jsx
+++ b/web/react/components/rhs_comment.jsx
@@ -16,7 +16,16 @@ import * as TextFormatting from '../utils/text_formatting.jsx';
import twemoji from 'twemoji';
import * as EventHelpers from '../dispatcher/event_helpers.jsx';
-export default class RhsComment extends React.Component {
+import {intlShape, injectIntl, defineMessages, FormattedMessage, FormattedDate} from 'mm-intl';
+
+const holders = defineMessages({
+ comment: {
+ id: 'rhs_comment.comment',
+ defaultMessage: 'Comment'
+ }
+});
+
+class RhsComment extends React.Component {
constructor(props) {
super(props);
@@ -95,12 +104,15 @@ export default class RhsComment extends React.Component {
data-toggle='modal'
data-target='#edit_post'
data-refocusid='#reply_textbox'
- data-title='Comment'
+ data-title={this.props.intl.formatMessage(holders.comment)}
data-message={post.message}
data-postid={post.id}
data-channelid={post.channel_id}
>
- {'Edit'}
+ <FormattedMessage
+ id='rhs_comment.edit'
+ defaultMessage='Edit'
+ />
</a>
</li>
);
@@ -117,7 +129,10 @@ export default class RhsComment extends React.Component {
role='menuitem'
onClick={() => EventHelpers.showDeletePostModal(post, 0)}
>
- {'Delete'}
+ <FormattedMessage
+ id='rhs_comment.del'
+ defaultMessage='Delete'
+ />
</a>
</li>
);
@@ -165,7 +180,10 @@ export default class RhsComment extends React.Component {
href='#'
onClick={this.retryComment}
>
- {'Retry'}
+ <FormattedMessage
+ id='rhs_comment.retry'
+ defaultMessage='Retry'
+ />
</a>
);
} else if (post.state === Constants.POST_LOADING) {
@@ -208,7 +226,15 @@ export default class RhsComment extends React.Component {
</li>
<li className='col'>
<time className='post__time'>
- {Utils.displayCommentDateTime(post.create_at)}
+ <FormattedDate
+ value={post.create_at}
+ day='numeric'
+ month='long'
+ year='numeric'
+ hour12={true}
+ hour='2-digit'
+ minute='2-digit'
+ />
</time>
</li>
<li className='col col__reply'>
@@ -237,5 +263,8 @@ RhsComment.defaultProps = {
post: null
};
RhsComment.propTypes = {
+ intl: intlShape.isRequired,
post: React.PropTypes.object
};
+
+export default injectIntl(RhsComment); \ No newline at end of file
diff --git a/web/react/components/rhs_header_post.jsx b/web/react/components/rhs_header_post.jsx
index 990b33eb5..d56ba76f8 100644
--- a/web/react/components/rhs_header_post.jsx
+++ b/web/react/components/rhs_header_post.jsx
@@ -3,6 +3,9 @@
import AppDispatcher from '../dispatcher/app_dispatcher.jsx';
import Constants from '../utils/constants.jsx';
+
+import {FormattedMessage} from 'mm-intl';
+
const ActionTypes = Constants.ActionTypes;
export default class RhsHeaderPost extends React.Component {
@@ -58,7 +61,13 @@ export default class RhsHeaderPost extends React.Component {
return (
<div className='sidebar--right__header'>
- <span className='sidebar--right__title'>{back}Message Details</span>
+ <span className='sidebar--right__title'>
+ {back}
+ <FormattedMessage
+ id='rhs_header.details'
+ defaultMessage='Message Details'
+ />
+ </span>
<button
type='button'
className='sidebar--right__close'
diff --git a/web/react/components/rhs_root_post.jsx b/web/react/components/rhs_root_post.jsx
index cd7f6766c..f9f7f8f81 100644
--- a/web/react/components/rhs_root_post.jsx
+++ b/web/react/components/rhs_root_post.jsx
@@ -14,6 +14,8 @@ import * as EventHelpers from '../dispatcher/event_helpers.jsx';
import Constants from '../utils/constants.jsx';
+import {FormattedMessage, FormattedDate} from 'mm-intl';
+
export default class RhsRootPost extends React.Component {
constructor(props) {
super(props);
@@ -68,7 +70,12 @@ export default class RhsRootPost extends React.Component {
var channelName;
if (channel) {
if (channel.type === 'D') {
- channelName = 'Direct Message';
+ channelName = (
+ <FormattedMessage
+ id='rhs_root.direct'
+ defaultMessage='Direct Message'
+ />
+ );
} else {
channelName = channel.display_name;
}
@@ -93,7 +100,10 @@ export default class RhsRootPost extends React.Component {
data-postid={post.id}
data-channelid={post.channel_id}
>
- {'Edit'}
+ <FormattedMessage
+ id='rhs_root.edit'
+ defaultMessage='Edit'
+ />
</a>
</li>
);
@@ -110,7 +120,10 @@ export default class RhsRootPost extends React.Component {
role='menuitem'
onClick={() => EventHelpers.showDeletePostModal(post, this.props.commentCount)}
>
- {'Delete'}
+ <FormattedMessage
+ id='rhs_root.del'
+ defaultMessage='Delete'
+ />
</a>
</li>
);
@@ -205,7 +218,15 @@ export default class RhsRootPost extends React.Component {
{botIndicator}
<li className='col'>
<time className='post__time'>
- {utils.displayCommentDateTime(post.create_at)}
+ <FormattedDate
+ value={post.create_at}
+ day='numeric'
+ month='long'
+ year='numeric'
+ hour12={true}
+ hour='2-digit'
+ minute='2-digit'
+ />
</time>
</li>
<li className='col col__reply'>
diff --git a/web/react/components/search_bar.jsx b/web/react/components/search_bar.jsx
index 77c9e39b9..35d7e9514 100644
--- a/web/react/components/search_bar.jsx
+++ b/web/react/components/search_bar.jsx
@@ -11,10 +11,20 @@ import SearchSuggestionList from './suggestion/search_suggestion_list.jsx';
import SearchUserProvider from './suggestion/search_user_provider.jsx';
import * as utils from '../utils/utils.jsx';
import Constants from '../utils/constants.jsx';
+
+import {intlShape, injectIntl, defineMessages, FormattedMessage, FormattedHTMLMessage} from 'mm-intl';
+
var ActionTypes = Constants.ActionTypes;
var Popover = ReactBootstrap.Popover;
-export default class SearchBar extends React.Component {
+const holders = defineMessages({
+ search: {
+ id: 'search_bar.search',
+ defaultMessage: 'Search'
+ }
+});
+
+class SearchBar extends React.Component {
constructor() {
super();
this.mounted = false;
@@ -147,7 +157,10 @@ export default class SearchBar extends React.Component {
className='search__clear'
onClick={this.clearFocus}
>
- {'Cancel'}
+ <FormattedMessage
+ id='search_bar.cancel'
+ defaultMessage='Cancel'
+ />
</span>
<form
role='form'
@@ -160,7 +173,7 @@ export default class SearchBar extends React.Component {
<SuggestionBox
ref='search'
className='form-control search-bar'
- placeholder='Search'
+ placeholder={this.props.intl.formatMessage(holders.search)}
value={this.state.searchTerm}
onFocus={this.handleUserFocus}
onBlur={this.handleUserBlur}
@@ -174,18 +187,20 @@ export default class SearchBar extends React.Component {
placement='bottom'
className={helpClass}
>
- <h4>{'Search Options'}</h4>
- <ul>
- <li>
- <span>{'Use '}</span><b>{'"quotation marks"'}</b><span>{' to search for phrases'}</span>
- </li>
- <li>
- <span>{'Use '}</span><b>{'from:'}</b><span>{' to find posts from specific users and '}</span><b>{'in:'}</b><span>{' to find posts in specific channels'}</span>
- </li>
- </ul>
+ <FormattedHTMLMessage
+ id='search_bar.usage'
+ defaultMessage='<h4>Search Options</h4><ul><li><span>Use </span><b>"quotation marks"</b><span> to search for phrases</span></li><li><span>Use </span><b>from:</b><span> to find posts from specific users and </span><b>in:</b><span> to find posts in specific channels</span></li></ul>'
+ />
</Popover>
</form>
</div>
);
}
}
+
+SearchBar.propTypes = {
+ intl: intlShape.isRequired
+};
+
+export default injectIntl(SearchBar);
+
diff --git a/web/react/components/search_results.jsx b/web/react/components/search_results.jsx
index 141181701..9dcc99061 100644
--- a/web/react/components/search_results.jsx
+++ b/web/react/components/search_results.jsx
@@ -8,6 +8,8 @@ import * as Utils from '../utils/utils.jsx';
import SearchResultsHeader from './search_results_header.jsx';
import SearchResultsItem from './search_results_item.jsx';
+import {FormattedMessage, FormattedHTMLMessage} from 'mm-intl';
+
function getStateFromStores() {
return {results: SearchStore.getSearchResults()};
}
@@ -83,25 +85,29 @@ export default class SearchResults extends React.Component {
if (!searchTerm && noResults) {
ctls = (
<div className='sidebar--right__subheader'>
- <ul>
- <li>
- {'Use '}<b>{'"quotation marks"'}</b>{' to search for phrases'}
- </li>
- <li>
- {'Use '}<b>{'from:'}</b>{' to find posts from specific users and '}<b>{'in:'}</b>{' to find posts in specific channels'}
- </li>
- </ul>
+ <FormattedHTMLMessage
+ id='search_results.usage'
+ defaultMessage='<ul><li>Use <b>"quotation marks"</b> to search for phrases</li><li>Use <b>from:</b> to find posts from specific users and <b>in:</b> to find posts in specific channels</li></ul>'
+ />
</div>
);
} else if (noResults) {
ctls =
(
<div className='sidebar--right__subheader'>
- <h4>{'NO RESULTS'}</h4>
- <ul>
- <li>{'If you\'re searching a partial phrase (ex. searching "rea", looking for "reach" or "reaction"), append a * to your search term'}</li>
- <li>{'Due to the volume of results, two letter searches and common words like "this", "a" and "is" won\'t appear in search results'}</li>
- </ul>
+ <h4>
+ <FormattedMessage
+ id='search_results.noResults'
+ defaultMessage='NO RESULTS'
+ />
+ </h4>
+ <FormattedHTMLMessage
+ id='search_results.because'
+ defaultMessage='<ul>
+ <li>If you&#39;re searching a partial phrase (ex. searching "rea", looking for "reach" or "reaction"), append a * to your search term</li>
+ <li>Due to the volume of results, two letter searches and common words like "this", "a" and "is" won&#39;t appear in search results</li>
+ </ul>'
+ />
</div>
);
} else {
diff --git a/web/react/components/search_results_header.jsx b/web/react/components/search_results_header.jsx
index 581976494..45f56f65a 100644
--- a/web/react/components/search_results_header.jsx
+++ b/web/react/components/search_results_header.jsx
@@ -3,6 +3,9 @@
import AppDispatcher from '../dispatcher/app_dispatcher.jsx';
import Constants from '../utils/constants.jsx';
+
+import {FormattedMessage} from 'mm-intl';
+
var ActionTypes = Constants.ActionTypes;
export default class SearchResultsHeader extends React.Component {
@@ -34,10 +37,20 @@ export default class SearchResultsHeader extends React.Component {
}
render() {
- var title = 'Search Results';
+ var title = (
+ <FormattedMessage
+ id='search_header.results'
+ defaultMessage='Search Results'
+ />
+ );
if (this.props.isMentionSearch) {
- title = 'Recent Mentions';
+ title = (
+ <FormattedMessage
+ id='search_header.title2'
+ defaultMessage='Recent Mentions'
+ />
+ );
}
return (
diff --git a/web/react/components/search_results_item.jsx b/web/react/components/search_results_item.jsx
index f235cac0a..0ad091d5b 100644
--- a/web/react/components/search_results_item.jsx
+++ b/web/react/components/search_results_item.jsx
@@ -10,6 +10,8 @@ import * as TextFormatting from '../utils/text_formatting.jsx';
import Constants from '../utils/constants.jsx';
+import {FormattedMessage, FormattedDate} from 'mm-intl';
+
export default class SearchResultsItem extends React.Component {
constructor(props) {
super(props);
@@ -42,7 +44,12 @@ export default class SearchResultsItem extends React.Component {
if (channel) {
channelName = channel.display_name;
if (channel.type === 'D') {
- channelName = 'Direct Message';
+ channelName = (
+ <FormattedMessage
+ id='search_item.direct'
+ defaultMessage='Direct Message'
+ />
+ );
}
}
@@ -69,7 +76,15 @@ export default class SearchResultsItem extends React.Component {
<li className='col__name'><strong><UserProfile userId={this.props.post.user_id} /></strong></li>
<li className='col'>
<time className='search-item-time'>
- {utils.displayDate(this.props.post.create_at) + ' ' + utils.displayTime(this.props.post.create_at)}
+ <FormattedDate
+ value={this.props.post.create_at}
+ day='numeric'
+ month='long'
+ year='numeric'
+ hour12={true}
+ hour='2-digit'
+ minute='2-digit'
+ />
</time>
</li>
<li>
@@ -78,7 +93,10 @@ export default class SearchResultsItem extends React.Component {
className='search-item__jump'
onClick={this.handleClick}
>
- {'Jump'}
+ <FormattedMessage
+ id='search_item.jump'
+ defaultMessage='Jump'
+ />
</a>
</li>
<li>
@@ -89,7 +107,7 @@ export default class SearchResultsItem extends React.Component {
>
<span
className='comment-icon'
- dangerouslySetInnerHTML={{__html: Constants.COMMENT_ICON}}
+ dangerouslySetInnerHTML={{__html: Constants.REPLY_ICON}}
/>
</a>
</li>
diff --git a/web/react/components/setting_item_max.jsx b/web/react/components/setting_item_max.jsx
index d6c4b0d4b..52f1906c3 100644
--- a/web/react/components/setting_item_max.jsx
+++ b/web/react/components/setting_item_max.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 SettingItemMax extends React.Component {
render() {
var clientError = null;
@@ -26,7 +28,10 @@ export default class SettingItemMax extends React.Component {
href='#'
onClick={this.props.submit}
>
- Save
+ <FormattedMessage
+ id='setting_item_max.save'
+ defaultMessage='Save'
+ />
</a>
);
}
@@ -60,7 +65,10 @@ export default class SettingItemMax extends React.Component {
href='#'
onClick={this.props.updateSection}
>
- Cancel
+ <FormattedMessage
+ id='setting_item_max.cancel'
+ defaultMessage='Cancel'
+ />
</a>
</li>
</ul>
diff --git a/web/react/components/setting_item_min.jsx b/web/react/components/setting_item_min.jsx
index ffd2061fe..db5513b14 100644
--- a/web/react/components/setting_item_min.jsx
+++ b/web/react/components/setting_item_min.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 SettingItemMin extends React.Component {
render() {
let editButton = null;
@@ -13,7 +15,10 @@ export default class SettingItemMin extends React.Component {
onClick={this.props.updateSection}
>
<i className='fa fa-pencil'/>
- {'Edit'}
+ <FormattedMessage
+ id='setting_item_min.edit'
+ defaultMessage='Edit'
+ />
</a>
</li>
);
diff --git a/web/react/components/setting_picture.jsx b/web/react/components/setting_picture.jsx
index e69412cca..70e0e6755 100644
--- a/web/react/components/setting_picture.jsx
+++ b/web/react/components/setting_picture.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 SettingPicture extends React.Component {
constructor(props) {
super(props);
@@ -76,10 +78,24 @@ export default class SettingPicture extends React.Component {
<a
className={confirmButtonClass}
onClick={this.props.submit}
- >Save</a>
+ >
+ <FormattedMessage
+ id='setting_picture.save'
+ defaultMessage='Save'
+ />
+ </a>
);
}
- var helpText = 'Upload a profile picture in either JPG or PNG format, at least ' + global.window.mm_config.ProfileWidth + 'px in width and ' + global.window.mm_config.ProfileHeight + 'px height.';
+ var helpText = (
+ <FormattedMessage
+ id='setting_picture.help'
+ defaultMessage='Upload a profile picture in either JPG or PNG format, at least {width}px in width and {height}px height.'
+ values={{
+ width: global.window.mm_config.ProfileWidth,
+ height: global.window.mm_config.ProfileHeight
+ }}
+ />
+ );
var self = this;
return (
@@ -97,7 +113,10 @@ export default class SettingPicture extends React.Component {
{serverError}
{clientError}
<span className='btn btn-sm btn-primary btn-file sel-btn'>
- Select
+ <FormattedMessage
+ id='setting_picture.select'
+ defaultMessage='Select'
+ />
<input
ref='input'
accept='.jpg,.png,.bmp'
@@ -110,7 +129,12 @@ export default class SettingPicture extends React.Component {
className='btn btn-sm theme'
href='#'
onClick={self.props.updateSection}
- >Cancel</a>
+ >
+ <FormattedMessage
+ id='setting_picture.cancel'
+ defaultMessage='Cancel'
+ />
+ </a>
</li>
</ul>
</li>
diff --git a/web/react/components/setting_upload.jsx b/web/react/components/setting_upload.jsx
index a25789dff..5d5cdfdf7 100644
--- a/web/react/components/setting_upload.jsx
+++ b/web/react/components/setting_upload.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 SettingsUpload extends React.Component {
constructor(props) {
super(props);
@@ -41,7 +43,7 @@ export default class SettingsUpload extends React.Component {
if (inputnode.files && inputnode.files[0]) {
this.props.submit(inputnode.files[0]);
} else {
- this.setState({clientError: 'No file selected.'});
+ this.setState({clientError: true});
}
}
@@ -49,7 +51,12 @@ export default class SettingsUpload extends React.Component {
let clientError = null;
if (this.state.clientError) {
clientError = (
- <div className='file-status'>{this.state.clientError}</div>
+ <div className='file-status'>
+ <FormattedMessage
+ id='setting_upload.noFile'
+ defaultMessage='No file selected.'
+ />
+ </div>
);
}
let serverError = null;
@@ -75,7 +82,10 @@ export default class SettingsUpload extends React.Component {
<ul className='setting-list'>
<li className='setting-list-item'>
<span className='btn btn-sm btn-primary btn-file sel-btn'>
- {'Select file'}
+ <FormattedMessage
+ id='setting_upload.select'
+ defaultMessage='Select file'
+ />
<input
ref='uploadinput'
accept={this.props.fileTypesAccepted}
@@ -87,7 +97,10 @@ export default class SettingsUpload extends React.Component {
className={submitButtonClass}
onClick={this.doSubmit}
>
- {'Import'}
+ <FormattedMessage
+ id='setting_upload.import'
+ defaultMessage='Import'
+ />
</a>
{fileNameText}
{serverError}
diff --git a/web/react/components/sidebar.jsx b/web/react/components/sidebar.jsx
index c902731c9..14790fbec 100644
--- a/web/react/components/sidebar.jsx
+++ b/web/react/components/sidebar.jsx
@@ -17,6 +17,9 @@ import * as Client from '../utils/client.jsx';
import * as Utils from '../utils/utils.jsx';
import Constants from '../utils/constants.jsx';
+
+import {FormattedMessage, FormattedHTMLMessage} from 'mm-intl';
+
const Preferences = Constants.Preferences;
const TutorialSteps = Constants.TutorialSteps;
@@ -182,7 +185,10 @@ export default class Sidebar extends React.Component {
let currentChannelName = channel.display_name;
if (channel.type === 'D') {
- currentChannelName = Utils.getDirectTeammate(channel.id).username;
+ const teammate = Utils.getDirectTeammate(channel.id);
+ if (teammate != null) {
+ currentChannelName = teammate.username;
+ }
}
const unread = this.getTotalUnreadCount();
@@ -275,34 +281,33 @@ export default class Sidebar extends React.Component {
screens.push(
<div>
- <h4>{'Channels'}</h4>
- <p><strong>{'Channels'}</strong>{' organize conversations across different topics. They’re open to everyone on your team. To send private communications use '}<strong>{'Direct Messages'}</strong>{' for a single person or '}<strong>{'Private Groups'}</strong>{' for multiple people.'}
- </p>
+ <FormattedHTMLMessage
+ id='sidebar.tutorialScreen1'
+ defaultMessage='<h4>Channels</h4><p><strong>Channels</strong> organize conversations across different topics. They’re open to everyone on your team. To send private communications use <strong>Direct Messages</strong> for a single person or <strong>Private Groups</strong> for multiple people.</p>'
+ />
</div>
);
screens.push(
<div>
- <h4>{'"Town Square" and "Off-Topic" channels'}</h4>
- <p>{'Here are two public channels to start:'}</p>
- <p>
- <strong>{'Town Square'}</strong>{' is a place for team-wide communication. Everyone in your team is a member of this channel.'}
- </p>
- <p>
- <strong>{'Off-Topic'}</strong>{' is a place for fun and humor outside of work-related channels. You and your team can decide what other channels to create.'}
- </p>
+ <FormattedHTMLMessage
+ id='sidebar.tutorialScreen2'
+ defaultMessage='<h4>"Town Square" and "Off-Topic" channels</h4>
+ <p>Here are two public channels to start:</p>
+ <p><strong>Town Square</strong> is a place for team-wide communication. Everyone in your team is a member of this channel.</p>
+ <p><strong>Off-Topic</strong> is a place for fun and humor outside of work-related channels. You and your team can decide what other channels to create.</p>'
+ />
</div>
);
screens.push(
<div>
- <h4>{'Creating and Joining Channels'}</h4>
- <p>
- {'Click '}<strong>{'"More..."'}</strong>{' to create a new channel or join an existing one.'}
- </p>
- <p>
- {'You can also create a new channel or private group by clicking the '}<strong>{'"+" symbol'}</strong>{' next to the channel or private group header.'}
- </p>
+ <FormattedHTMLMessage
+ id='sidebar.tutorialScreen3'
+ defaultMessage='<h4>Creating and Joining Channels</h4>
+ <p>Click <strong>"More..."</strong> to create a new channel or join an existing one.</p>
+ <p>You can also create a new channel or private group by clicking the <strong>"+" symbol</strong> next to the channel or private group header.</p>'
+ />
</div>
);
@@ -437,7 +442,12 @@ export default class Sidebar extends React.Component {
let closeButton = null;
const removeTooltip = (
- <Tooltip id='remove-dm-tooltip'>{'Remove from list'}</Tooltip>
+ <Tooltip id='remove-dm-tooltip'>
+ <FormattedMessage
+ id='sidebar.removeList'
+ defaultMessage='Remove from list'
+ />
+ </Tooltip>
);
if (handleClose && !badge) {
closeButton = (
@@ -525,7 +535,13 @@ export default class Sidebar extends React.Component {
href='#'
onClick={this.showMoreDirectChannelsModal}
>
- {'More (' + this.state.hiddenDirectChannelCount + ')'}
+ <FormattedMessage
+ id='sidebar.more'
+ defaultMessage='More ({count})'
+ values={{
+ count: this.state.hiddenDirectChannelCount
+ }}
+ />
</a>
</li>
);
@@ -537,10 +553,34 @@ export default class Sidebar extends React.Component {
}
const createChannelTootlip = (
- <Tooltip id='new-channel-tooltip' >{'Create new channel'}</Tooltip>
+ <Tooltip id='new-channel-tooltip' >
+ <FormattedMessage
+ id='sidebar.createChannel'
+ defaultMessage='Create new channel'
+ />
+ </Tooltip>
);
const createGroupTootlip = (
- <Tooltip id='new-group-tooltip'>{'Create new group'}</Tooltip>
+ <Tooltip id='new-group-tooltip'>
+ <FormattedMessage
+ id='sidebar.createGroup'
+ defaultMessage='Create new group'
+ />
+ </Tooltip>
+ );
+
+ const above = (
+ <FormattedMessage
+ id='sidebar.unreadAbove'
+ defaultMessage='Unread post(s) above'
+ />
+ );
+
+ const below = (
+ <FormattedMessage
+ id='sidebar.unreadBelow'
+ defaultMessage='Unread post(s) below'
+ />
);
return (
@@ -564,12 +604,12 @@ export default class Sidebar extends React.Component {
<UnreadChannelIndicator
show={this.state.showTopUnread}
extraClass='nav-pills__unread-indicator-top'
- text={'Unread post(s) above'}
+ text={above}
/>
<UnreadChannelIndicator
show={this.state.showBottomUnread}
extraClass='nav-pills__unread-indicator-bottom'
- text={'Unread post(s) below'}
+ text={below}
/>
<div
@@ -580,7 +620,10 @@ export default class Sidebar extends React.Component {
<ul className='nav nav-pills nav-stacked'>
<li>
<h4>
- {'Channels'}
+ <FormattedMessage
+ id='sidebar.channels'
+ defaultMessage='Channels'
+ />
<OverlayTrigger
delayShow={500}
placement='top'
@@ -603,7 +646,10 @@ export default class Sidebar extends React.Component {
className='nav-more'
onClick={this.showMoreChannelsModal}
>
- {'More...'}
+ <FormattedMessage
+ id='sidebar.moreElips'
+ defaultMessage='More...'
+ />
</a>
</li>
</ul>
@@ -611,7 +657,10 @@ export default class Sidebar extends React.Component {
<ul className='nav nav-pills nav-stacked'>
<li>
<h4>
- {'Private Groups'}
+ <FormattedMessage
+ id='sidebar.pg'
+ defaultMessage='Private Groups'
+ />
<OverlayTrigger
delayShow={500}
placement='top'
@@ -630,7 +679,14 @@ export default class Sidebar extends React.Component {
{privateChannelItems}
</ul>
<ul className='nav nav-pills nav-stacked'>
- <li><h4>{'Direct Messages'}</h4></li>
+ <li>
+ <h4>
+ <FormattedMessage
+ id='sidebar.direct'
+ defaultMessage='Direct Messages'
+ />
+ </h4>
+ </li>
{directMessageItems}
{directMessageMore}
</ul>
diff --git a/web/react/components/sidebar_header.jsx b/web/react/components/sidebar_header.jsx
index a9616cfc3..45b0a5fc4 100644
--- a/web/react/components/sidebar_header.jsx
+++ b/web/react/components/sidebar_header.jsx
@@ -9,6 +9,9 @@ import PreferenceStore from '../stores/preference_store.jsx';
import * as Utils from '../utils/utils.jsx';
import Constants from '../utils/constants.jsx';
+
+import {FormattedHTMLMessage} from 'mm-intl';
+
const Preferences = Constants.Preferences;
const TutorialSteps = Constants.TutorialSteps;
@@ -51,20 +54,12 @@ export default class SidebarHeader extends React.Component {
screens.push(
<div>
- <h4>{'Main Menu'}</h4>
- <p>
- {'The '}<strong>{'Main Menu'}</strong>{' is where you can '}
- <strong>{'Invite New Members'}</strong>
- {', access your '}
- <strong>{'Account Settings'}</strong>
- {' and set your '}<strong>{'Theme Color'}</strong>{'.'}
- </p>
- <p>
- {'Team administrators can also access their '}<strong>{'Team Settings'}</strong>{' from this menu.'}
- </p>
- <p>
- {'System administrators will find a '}<strong>{'System Console'}</strong>{' option to administrate the entire system.'}
- </p>
+ <FormattedHTMLMessage
+ id='sidebar_header.tutorial'
+ defaultMessage='<h4>Main Menu</h4>
+ <p>The <strong>Main Menu</strong> is where you can <strong>Invite New Members</strong>, access your <strong>Account Settings</strong> and set your <strong>Theme Color</strong>.</p>
+ <p>Team administrators can also access their <strong>Team Settings</strong> from this menu.</p><p>System administrators will find a <strong>System Console</strong> option to administrate the entire system.</p>'
+ />
</div>
);
diff --git a/web/react/components/sidebar_right_menu.jsx b/web/react/components/sidebar_right_menu.jsx
index 20c2bf696..4d714e9f1 100644
--- a/web/react/components/sidebar_right_menu.jsx
+++ b/web/react/components/sidebar_right_menu.jsx
@@ -9,6 +9,8 @@ import * as client from '../utils/client.jsx';
import * as EventHelpers from '../dispatcher/event_helpers.jsx';
import * as utils from '../utils/utils.jsx';
+import {FormattedMessage} from 'mm-intl';
+
export default class SidebarRightMenu extends React.Component {
componentDidMount() {
$('.sidebar--left .dropdown-menu').perfectScrollbar();
@@ -49,7 +51,11 @@ export default class SidebarRightMenu extends React.Component {
href='#'
onClick={EventHelpers.showInviteMemberModal}
>
- <i className='fa fa-user'></i>{'Invite New Member'}
+ <i className='fa fa-user'></i>
+ <FormattedMessage
+ id='sidebar_right_menu.inviteNew'
+ defaultMessage='Invite New Member'
+ />
</a>
</li>
);
@@ -61,7 +67,11 @@ export default class SidebarRightMenu extends React.Component {
href='#'
onClick={EventHelpers.showGetTeamInviteLinkModal}
>
- <i className='glyphicon glyphicon-link'></i>{'Get Team Invite Link'}
+ <i className='glyphicon glyphicon-link'></i>
+ <FormattedMessage
+ id='sidebar_right_menu.teamLink'
+ defaultMessage='Get Team Invite Link'
+ />
</a>
</li>
);
@@ -75,13 +85,23 @@ export default class SidebarRightMenu extends React.Component {
href='#'
data-toggle='modal'
data-target='#team_settings'
- ><i className='fa fa-globe'></i>{'Team Settings'}</a>
+ >
+ <i className='fa fa-globe'></i>
+ <FormattedMessage
+ id='sidebar_right_menu.teamSettings'
+ defaultMessage='Team Settings'
+ />
+ </a>
</li>
);
manageLink = (
<li>
<ToggleModalButton dialogType={TeamMembersModal}>
- <i className='fa fa-users'></i>{'Manage Members'}
+ <i className='fa fa-users'></i>
+ <FormattedMessage
+ id='sidebar_right_menu.manageMembers'
+ defaultMessage='Manage Members'
+ />
</ToggleModalButton>
</li>
);
@@ -93,7 +113,12 @@ export default class SidebarRightMenu extends React.Component {
<a
href={'/admin_console?' + utils.getSessionIndex()}
>
- <i className='fa fa-wrench'></i>{'System Console'}</a>
+ <i className='fa fa-wrench'></i>
+ <FormattedMessage
+ id='sidebar_right_menu.console'
+ defaultMessage='System Console'
+ />
+ </a>
</li>
);
}
@@ -114,7 +139,14 @@ export default class SidebarRightMenu extends React.Component {
<a
target='_blank'
href={global.window.mm_config.HelpLink}
- ><i className='fa fa-question'></i>{'Help'}</a></li>
+ >
+ <i className='fa fa-question'></i>
+ <FormattedMessage
+ id='sidebar_right_menu.help'
+ defaultMessage='Help'
+ />
+ </a>
+ </li>
);
}
@@ -125,7 +157,14 @@ export default class SidebarRightMenu extends React.Component {
<a
target='_blank'
href={global.window.mm_config.ReportAProblemLink}
- ><i className='fa fa-phone'></i>{'Report a Problem'}</a></li>
+ >
+ <i className='fa fa-phone'></i>
+ <FormattedMessage
+ id='sidebar_right_menu.report'
+ defaultMessage='Report a Problem'
+ />
+ </a>
+ </li>
);
}
return (
@@ -144,7 +183,11 @@ export default class SidebarRightMenu extends React.Component {
href='#'
onClick={() => this.setState({showUserSettingsModal: true})}
>
- <i className='fa fa-cog'></i>{'Account Settings'}
+ <i className='fa fa-cog'></i>
+ <FormattedMessage
+ id='sidebar_right_menu.accountSettings'
+ defaultMessage='Account Settings'
+ />
</a>
</li>
{teamSettingsLink}
@@ -156,7 +199,14 @@ export default class SidebarRightMenu extends React.Component {
<a
href='#'
onClick={this.handleLogoutClick}
- ><i className='fa fa-sign-out'></i>{'Logout'}</a></li>
+ >
+ <i className='fa fa-sign-out'></i>
+ <FormattedMessage
+ id='sidebar_right_menu.logout'
+ defaultMessage='Logout'
+ />
+ </a>
+ </li>
<li className='divider'></li>
{helpLink}
{reportLink}
diff --git a/web/react/components/team_export_tab.jsx b/web/react/components/team_export_tab.jsx
index 14df7fffc..8330637d8 100644
--- a/web/react/components/team_export_tab.jsx
+++ b/web/react/components/team_export_tab.jsx
@@ -3,6 +3,8 @@
import * as Client from '../utils/client.jsx';
+import {FormattedMessage} from 'mm-intl';
+
export default class TeamExportTab extends React.Component {
constructor(props) {
super(props);
@@ -35,7 +37,10 @@ export default class TeamExportTab extends React.Component {
messageSection = (
<p className='confirm-import alert alert-warning'>
<i className='fa fa-spinner fa-pulse' />
- {' Exporting...'}
+ <FormattedMessage
+ id='team_export_tab.exporting'
+ defaultMessage=' Exporting...'
+ />
</p>
);
break;
@@ -43,12 +48,18 @@ export default class TeamExportTab extends React.Component {
messageSection = (
<p className='confirm-import alert alert-success'>
<i className='fa fa-check' />
- {' Ready for '}
+ <FormattedMessage
+ id='team_export_tab.ready'
+ defaultMessage=' Ready for '
+ />
<a
href={this.state.link}
download={true}
>
- {'download'}
+ <FormattedMessage
+ id='team_export_tab.download'
+ defaultMessage='download'
+ />
</a>
</p>
);
@@ -57,7 +68,13 @@ export default class TeamExportTab extends React.Component {
messageSection = (
<p className='confirm-import alert alert-warning'>
<i className='fa fa-warning' />
- {' Unable to export: ' + this.state.err}
+ <FormattedMessage
+ id='team_export_tab.unable'
+ defaultMessage=' Unable to export: {error}'
+ values={{
+ error: this.state.err
+ }}
+ />
</p>
);
break;
@@ -68,10 +85,20 @@ export default class TeamExportTab extends React.Component {
ref='wrapper'
className='user-settings'
>
- <h3 className='tab-header'>{'Export'}</h3>
+ <h3 className='tab-header'>
+ <FormattedMessage
+ id='team_export_tab.export'
+ defaultMessage='Export'
+ />
+ </h3>
<div className='divider-dark first'/>
<ul className='section-max'>
- <li className='col-xs-12 section-title'>{'Export your team'}</li>
+ <li className='col-xs-12 section-title'>
+ <FormattedMessage
+ id='team_export_tab.exportTeam'
+ defaultMessage='Export your team'
+ />
+ </li>
<li className='col-xs-offset-3 col-xs-8'>
<ul className='setting-list'>
<li className='setting-list-item'>
@@ -80,7 +107,10 @@ export default class TeamExportTab extends React.Component {
href='#'
onClick={this.doExport}
>
- {'Export'}
+ <FormattedMessage
+ id='team_export_tab.export'
+ defaultMessage='Export'
+ />
</a>
</li>
</ul>
diff --git a/web/react/components/team_general_tab.jsx b/web/react/components/team_general_tab.jsx
index b6fb3389f..0a1b02853 100644
--- a/web/react/components/team_general_tab.jsx
+++ b/web/react/components/team_general_tab.jsx
@@ -8,7 +8,56 @@ import * as Client from '../utils/client.jsx';
import * as Utils from '../utils/utils.jsx';
import TeamStore from '../stores/team_store.jsx';
-export default class GeneralTab extends React.Component {
+import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl';
+
+const holders = defineMessages({
+ dirDisabled: {
+ id: 'general_tab.dirDisabled',
+ defaultMessage: 'Team Directory has been disabled. Please ask a System Admin to enable the Team Directory in the System Console team settings.'
+ },
+ required: {
+ id: 'general_tab.required',
+ defaultMessage: 'This field is required'
+ },
+ chooseName: {
+ id: 'general_tab.chooseName',
+ defaultMessage: 'Please choose a new name for your team'
+ },
+ includeDirTitle: {
+ id: 'general_tab.includeDirTitle',
+ defaultMessage: 'Include this team in the Team Directory'
+ },
+ yes: {
+ id: 'general_tab.yes',
+ defaultMessage: 'Yes'
+ },
+ no: {
+ id: 'general_tab.no',
+ defaultMessage: 'No'
+ },
+ dirOff: {
+ id: 'general_tab.dirOff',
+ defaultMessage: 'Team directory is turned off for this system.'
+ },
+ openInviteTitle: {
+ id: 'general_tab.openInviteTitle',
+ defaultMessage: 'Allow anyone to sign-up from login page'
+ },
+ codeTitle: {
+ id: 'general_tab.codeTitle',
+ defaultMessage: 'Invite Code'
+ },
+ codeDesc: {
+ id: 'general_tab.codeDesc',
+ defaultMessage: "Click 'Edit' to regenerate Invite Code."
+ },
+ teamNameInfo: {
+ id: 'general_tab.teamNameInfo',
+ defaultMessage: 'Set the name of the team as it appears on your sign-in screen and at the top of the left-hand sidebar.'
+ }
+});
+
+class GeneralTab extends React.Component {
constructor(props) {
super(props);
@@ -66,7 +115,7 @@ export default class GeneralTab extends React.Component {
handleTeamListingRadio(listing) {
if (global.window.mm_config.EnableTeamListing !== 'true' && listing) {
- this.setState({clientError: 'Team Directory has been disabled. Please ask a System Admin to enable the Team Directory in the System Console team settings.'});
+ this.setState({clientError: this.props.intl.formatMessage(holders.dirDisabled)});
} else {
this.setState({allow_team_listing: listing});
}
@@ -118,12 +167,13 @@ export default class GeneralTab extends React.Component {
var state = {serverError: '', clientError: ''};
let valid = true;
+ const {formatMessage} = this.props.intl;
const name = this.state.name.trim();
if (!name) {
- state.clientError = 'This field is required';
+ state.clientError = formatMessage(holders.required);
valid = false;
} else if (name === this.props.team.display_name) {
- state.clientError = 'Please choose a new name for your team';
+ state.clientError = formatMessage(holders.chooseName);
valid = false;
} else {
state.clientError = '';
@@ -160,7 +210,7 @@ export default class GeneralTab extends React.Component {
if (inviteId) {
state.clientError = '';
} else {
- state.clientError = 'This field is required';
+ state.clientError = this.props.intl.fromatMessage(holders.required);
valid = false;
}
@@ -260,6 +310,7 @@ export default class GeneralTab extends React.Component {
}
const enableTeamListing = global.window.mm_config.EnableTeamListing === 'true';
+ const {formatMessage} = this.props.intl;
let teamListingSection;
if (this.props.activeSection === 'team_listing') {
@@ -279,7 +330,10 @@ export default class GeneralTab extends React.Component {
defaultChecked={this.state.allow_team_listing}
onChange={this.handleTeamListingRadio.bind(this, true)}
/>
- {'Yes'}
+ <FormattedMessage
+ id='general_tab.yes'
+ defaultMessage='Yes'
+ />
</label>
<br/>
</div>
@@ -292,24 +346,39 @@ export default class GeneralTab extends React.Component {
defaultChecked={!this.state.allow_team_listing}
onChange={this.handleTeamListingRadio.bind(this, false)}
/>
- {'No'}
+ <FormattedMessage
+ id='general_tab.no'
+ defaultMessage='No'
+ />
</label>
<br/>
</div>
- <div><br/>{'Including this team will display the team name from the Team Directory section of the Home Page, and provide a link to the sign-in page.'}</div>
+ <div>
+ <br/>
+ <FormattedMessage
+ id='general_tab.includeDirDesc'
+ defaultMessage='Including this team will display the team name from the Team Directory section of the Home Page, and provide a link to the sign-in page.'
+ />
+ </div>
</div>
);
} else {
inputs.push(
<div key='userTeamListingOptions'>
- <div><br/>{'Contact your system administrator to turn on the team directory on the system home page.'}</div>
+ <div>
+ <br/>
+ <FormattedMessage
+ id='general_tab.dirContact'
+ defaultMessage='Contact your system administrator to turn on the team directory on the system home page.'
+ />
+ </div>
</div>
);
}
teamListingSection = (
<SettingItemMax
- title='Include this team in the Team Directory'
+ title={formatMessage(holders.includeDirTitle)}
inputs={inputs}
submit={submitHandle}
server_error={serverError}
@@ -322,17 +391,17 @@ export default class GeneralTab extends React.Component {
if (enableTeamListing) {
if (this.state.allow_team_listing === true) {
- describe = 'Yes';
+ describe = formatMessage(holders.yes);
} else {
- describe = 'No';
+ describe = formatMessage(holders.no);
}
} else {
- describe = 'Team directory is turned off for this system.';
+ describe = formatMessage(holders.dirOff);
}
teamListingSection = (
<SettingItemMin
- title='Include this team in the Team Directory'
+ title={formatMessage(holders.includeDirTitle)}
describe={describe}
updateSection={this.onUpdateTeamListingSection}
/>
@@ -351,7 +420,10 @@ export default class GeneralTab extends React.Component {
defaultChecked={this.state.allow_open_invite}
onChange={this.handleOpenInviteRadio.bind(this, true)}
/>
- {'Yes'}
+ <FormattedMessage
+ id='general_tab.yes'
+ defaultMessage='Yes'
+ />
</label>
<br/>
</div>
@@ -363,17 +435,26 @@ export default class GeneralTab extends React.Component {
defaultChecked={!this.state.allow_open_invite}
onChange={this.handleOpenInviteRadio.bind(this, false)}
/>
- {'No'}
+ <FormattedMessage
+ id='general_tab.no'
+ defaultMessage='No'
+ />
</label>
<br/>
</div>
- <div><br/>{'When allowed, a link to account creation will be included on the sign-in page of this team and allow any visitor to sign-up.'}</div>
+ <div>
+ <br/>
+ <FormattedMessage
+ id='general_tab.openInviteDesc'
+ defaultMessage='When allowed, a link to account creation will be included on the sign-in page of this team and allow any visitor to sign-up.'
+ />
+ </div>
</div>
];
openInviteSection = (
<SettingItemMax
- title='Allow anyone to sign-up from login page'
+ title={formatMessage(holders.openInviteTitle)}
inputs={inputs}
submit={this.handleOpenInviteSubmit}
server_error={serverError}
@@ -383,14 +464,14 @@ export default class GeneralTab extends React.Component {
} else {
let describe = '';
if (this.state.allow_open_invite === true) {
- describe = 'Yes';
+ describe = formatMessage(holders.yes);
} else {
- describe = 'No';
+ describe = formatMessage(holders.no);
}
openInviteSection = (
<SettingItemMin
- title='Allow anyone to sign-up from login page'
+ title={formatMessage(holders.openInviteTitle)}
describe={describe}
updateSection={this.onUpdateOpenInviteSection}
/>
@@ -405,7 +486,12 @@ export default class GeneralTab extends React.Component {
inputs.push(
<div key='teamInviteSetting'>
<div className='row'>
- <label className='col-sm-5 control-label'>{'Invite Code'}</label>
+ <label className='col-sm-5 control-label'>
+ <FormattedMessage
+ id='general_tab.codeTitle'
+ defaultMessage='Invite Code'
+ />
+ </label>
<div className='col-sm-7'>
<input
className='form-control'
@@ -419,18 +505,26 @@ export default class GeneralTab extends React.Component {
href='#'
onClick={this.handleGenerateInviteId}
>
- {'Re-Generate'}
+ <FormattedMessage
+ id='general_tab.regenerate'
+ defaultMessage='Re-Generate'
+ />
</a>
</div>
</div>
</div>
- <div className='setting-list__hint'>{'The Invite Code is used as part of the URL in the team invitation link created by **Get Team Invite Link** in the main menu. Regenerating creates a new team invitation link and invalidates the previous link.'}</div>
+ <div className='setting-list__hint'>
+ <FormattedMessage
+ id='general_tab.codeLongDesc'
+ defaultMessage='The Invite Code is used as part of the URL in the team invitation link created by **Get Team Invite Link** in the main menu. Regenerating creates a new team invitation link and invalidates the previous link.'
+ />
+ </div>
</div>
);
inviteSection = (
<SettingItemMax
- title={`Invite Code`}
+ title={formatMessage(holders.codeTitle)}
inputs={inputs}
submit={this.handleInviteIdSubmit}
server_error={serverError}
@@ -441,8 +535,8 @@ export default class GeneralTab extends React.Component {
} else {
inviteSection = (
<SettingItemMin
- title={`Invite Code`}
- describe={`Click 'Edit' to regenerate Invite Code.`}
+ title={formatMessage(holders.codeTitle)}
+ describe={formatMessage(holders.codeDesc)}
updateSection={this.onUpdateInviteIdSection}
/>
);
@@ -453,7 +547,12 @@ export default class GeneralTab extends React.Component {
if (this.props.activeSection === 'name') {
const inputs = [];
- let teamNameLabel = 'Team Name';
+ let teamNameLabel = (
+ <FormattedMessage
+ id='general_tab.teamName'
+ defaultMessage='Team Name'
+ />
+ );
if (Utils.isMobile()) {
teamNameLabel = '';
}
@@ -476,15 +575,17 @@ export default class GeneralTab extends React.Component {
</div>
);
+ const nameExtraInfo = <span>{formatMessage(holders.teamNameInfo)}</span>;
+
nameSection = (
<SettingItemMax
- title={`Team Name`}
+ title={formatMessage({id: 'general_tab.teamName'})}
inputs={inputs}
submit={this.handleNameSubmit}
server_error={serverError}
client_error={clientError}
updateSection={this.onUpdateNameSection}
- extraInfo='Set the name of the team as it appears on your sign-in screen and at the top of the left-hand sidebar.'
+ extraInfo={nameExtraInfo}
/>
);
} else {
@@ -492,7 +593,7 @@ export default class GeneralTab extends React.Component {
nameSection = (
<SettingItemMin
- title={`Team Name`}
+ title={formatMessage({id: 'general_tab.teamName'})}
describe={describe}
updateSection={this.onUpdateNameSection}
/>
@@ -515,14 +616,22 @@ export default class GeneralTab extends React.Component {
ref='title'
>
<i className='modal-back'></i>
- {'General Settings'}
+ <FormattedMessage
+ id='general_tab.title'
+ defaultMessage='General Settings'
+ />
</h4>
</div>
<div
ref='wrapper'
className='user-settings'
>
- <h3 className='tab-header'>{'General Settings'}</h3>
+ <h3 className='tab-header'>
+ <FormattedMessage
+ id='general_tab.title'
+ defaultMessage='General Settings'
+ />
+ </h3>
<div className='divider-dark first'/>
{nameSection}
<div className='divider-light'/>
@@ -539,7 +648,10 @@ export default class GeneralTab extends React.Component {
}
GeneralTab.propTypes = {
+ intl: intlShape.isRequired,
updateSection: React.PropTypes.func.isRequired,
team: React.PropTypes.object.isRequired,
activeSection: React.PropTypes.string.isRequired
};
+
+export default injectIntl(GeneralTab); \ No newline at end of file
diff --git a/web/react/components/team_import_tab.jsx b/web/react/components/team_import_tab.jsx
index 37f8746d7..adf990672 100644
--- a/web/react/components/team_import_tab.jsx
+++ b/web/react/components/team_import_tab.jsx
@@ -4,7 +4,16 @@
import * as utils from '../utils/utils.jsx';
import SettingUpload from './setting_upload.jsx';
-export default class TeamImportTab extends React.Component {
+import {intlShape, injectIntl, defineMessages, FormattedMessage, FormattedHTMLMessage} from 'mm-intl';
+
+const holders = defineMessages({
+ importSlack: {
+ id: 'team_import_tab.importSlack',
+ defaultMessage: 'Import from Slack (Beta)'
+ }
+});
+
+class TeamImportTab extends React.Component {
constructor(props) {
super(props);
@@ -32,16 +41,19 @@ export default class TeamImportTab extends React.Component {
}
render() {
+ const {formatMessage} = this.props.intl;
var uploadHelpText = (
<div>
- <p>{'To import a team from Slack go to Slack > Team Settings > Import/Export Data > Export > Start Export. Slack does not allow you to export files, images, private groups or direct messages stored in Slack. Therefore, Slack import to Mattermost only supports importing of text messages in your Slack team\'\s public channels.'}</p>
- <p>{'The Slack import to Mattermost is in "Beta". Slack bot posts do not yet import and Slack @mentions are not currently supported.'}</p>
+ <FormattedHTMLMessage
+ id='team_import_tab.importHelp'
+ defaultMessage="<p>To import a team from Slack go to Slack > Team Settings > Import/Export Data > Export > Start Export. Slack does not allow you to export files, images, private groups or direct messages stored in Slack. Therefore, Slack import to Mattermost only supports importing of text messages in your Slack team's public channels.</p><p>The Slack import to Mattermost is in 'Beta'. Slack bot posts do not yet import and Slack @mentions are not currently supported.</p>"
+ />
</div>
);
var uploadSection = (
<SettingUpload
- title='Import from Slack (Beta)'
+ title={formatMessage(holders.importSlack)}
submit={this.doImportSlack}
helpText={uploadHelpText}
fileTypesAccepted='.zip'
@@ -56,19 +68,30 @@ export default class TeamImportTab extends React.Component {
break;
case 'in-progress':
messageSection = (
- <p className='confirm-import alert alert-warning'><i className='fa fa-spinner fa-pulse'></i>{' Importing...'}</p>
+ <p className='confirm-import alert alert-warning'><i className='fa fa-spinner fa-pulse'></i>
+ <FormattedMessage
+ id='team_import_tab.importing'
+ defaultMessage=' Importing...'
+ />
+ </p>
);
break;
case 'done':
messageSection = (
<p className='confirm-import alert alert-success'>
<i className='fa fa-check' />
- {' Import successful: '}
+ <FormattedMessage
+ id='team_import_tab.successful'
+ defaultMessage=' Import successful: '
+ />
<a
href={this.state.link}
download='MattermostImportSummary.txt'
>
- {'View Summary'}
+ <FormattedMessage
+ id='team_import_tab.summary'
+ defaultMessage='View Summary'
+ />
</a>
</p>
);
@@ -77,12 +100,18 @@ export default class TeamImportTab extends React.Component {
messageSection = (
<p className='confirm-import alert alert-warning'>
<i className='fa fa-warning' />
- {' Import failure: '}
+ <FormattedMessage
+ id='team_import_tab.failure'
+ defaultMessage=' Import failure: '
+ />
<a
href={this.state.link}
download='MattermostImportSummary.txt'
>
- {'View Summary'}
+ <FormattedMessage
+ id='team_import_tab.summary'
+ defaultMessage='View Summary'
+ />
</a>
</p>
);
@@ -102,13 +131,23 @@ export default class TeamImportTab extends React.Component {
<h4
className='modal-title'
ref='title'
- ><i className='modal-back'></i>{'Import'}</h4>
+ ><i className='modal-back'></i>
+ <FormattedMessage
+ id='team_import_tab.import'
+ defaultMessage='Import'
+ />
+ </h4>
</div>
<div
ref='wrapper'
className='user-settings'
>
- <h3 className='tab-header'>{'Import'}</h3>
+ <h3 className='tab-header'>
+ <FormattedMessage
+ id='team_import_tab.import'
+ defaultMessage='Import'
+ />
+ </h3>
<div className='divider-dark first'/>
{uploadSection}
<div className='divider-dark'/>
@@ -118,3 +157,9 @@ export default class TeamImportTab extends React.Component {
);
}
}
+
+TeamImportTab.propTypes = {
+ intl: intlShape.isRequired
+};
+
+export default injectIntl(TeamImportTab); \ No newline at end of file
diff --git a/web/react/components/team_members_modal.jsx b/web/react/components/team_members_modal.jsx
index 27224c283..92adb6e2a 100644
--- a/web/react/components/team_members_modal.jsx
+++ b/web/react/components/team_members_modal.jsx
@@ -4,6 +4,8 @@
import MemberListTeam from './member_list_team.jsx';
import TeamStore from '../stores/team_store.jsx';
+import {FormattedMessage} from 'mm-intl';
+
const Modal = ReactBootstrap.Modal;
export default class TeamMembersModal extends React.Component {
@@ -44,7 +46,13 @@ export default class TeamMembersModal extends React.Component {
onHide={this.props.onHide}
>
<Modal.Header closeButton={true}>
- {team.display_name + ' Members'}
+ <FormattedMessage
+ id='team_member_modal.members'
+ defaultMessage='{team} Members'
+ values={{
+ team: team.display_name
+ }}
+ />
</Modal.Header>
<Modal.Body ref='modalBody'>
<div className='team-member-list'>
@@ -57,7 +65,10 @@ export default class TeamMembersModal extends React.Component {
className='btn btn-default'
onClick={this.props.onHide}
>
- {'Close'}
+ <FormattedMessage
+ id='team_member_modal.close'
+ defaultMessage='Close'
+ />
</button>
</Modal.Footer>
</Modal>
diff --git a/web/react/components/team_settings_modal.jsx b/web/react/components/team_settings_modal.jsx
index dbdbde958..d517f92fb 100644
--- a/web/react/components/team_settings_modal.jsx
+++ b/web/react/components/team_settings_modal.jsx
@@ -4,7 +4,24 @@
import SettingsSidebar from './settings_sidebar.jsx';
import TeamSettings from './team_settings.jsx';
-export default class TeamSettingsModal extends React.Component {
+import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl';
+
+const holders = defineMessages({
+ generalTab: {
+ id: 'team_settings_modal.generalTab',
+ defaultMessage: 'General'
+ },
+ importTab: {
+ id: 'team_settings_modal.importTab',
+ defaultMessage: 'Import'
+ },
+ exportTab: {
+ id: 'team_settings_modal.exportTab',
+ defaultMessage: 'Export'
+ }
+});
+
+class TeamSettingsModal extends React.Component {
constructor(props) {
super(props);
@@ -36,12 +53,13 @@ export default class TeamSettingsModal extends React.Component {
this.setState({activeSection: section});
}
render() {
+ const {formatMessage} = this.props.intl;
const tabs = [];
- tabs.push({name: 'general', uiName: 'General', icon: 'glyphicon glyphicon-cog'});
- tabs.push({name: 'import', uiName: 'Import', icon: 'glyphicon glyphicon-upload'});
+ tabs.push({name: 'general', uiName: formatMessage(holders.generalTab), icon: 'glyphicon glyphicon-cog'});
+ tabs.push({name: 'import', uiName: formatMessage(holders.importTab), icon: 'glyphicon glyphicon-upload'});
// To enable export uncomment this line
- //tabs.push({name: 'export', uiName: 'Export', icon: 'glyphicon glyphicon-download'});
+ //tabs.push({name: 'export', uiName: formatMessage(holders.exportTab), icon: 'glyphicon glyphicon-download'});
return (
<div
@@ -67,7 +85,10 @@ export default class TeamSettingsModal extends React.Component {
className='modal-title'
ref='title'
>
- {'Team Settings'}
+ <FormattedMessage
+ id='team_settings_modal.title'
+ defaultMessage='Team Settings'
+ />
</h4>
</div>
<div className='modal-body'>
@@ -96,4 +117,7 @@ export default class TeamSettingsModal extends React.Component {
}
TeamSettingsModal.propTypes = {
+ intl: intlShape.isRequired
};
+
+export default injectIntl(TeamSettingsModal); \ No newline at end of file
diff --git a/web/react/components/textbox.jsx b/web/react/components/textbox.jsx
index 62c0d5218..bb383aca1 100644
--- a/web/react/components/textbox.jsx
+++ b/web/react/components/textbox.jsx
@@ -11,6 +11,9 @@ import ErrorStore from '../stores/error_store.jsx';
import * as TextFormatting from '../utils/text_formatting.jsx';
import * as Utils from '../utils/utils.jsx';
import Constants from '../utils/constants.jsx';
+
+import {FormattedMessage} from 'mm-intl';
+
const PreReleaseFeatures = Constants.PRE_RELEASE_FEATURES;
export default class Textbox extends React.Component {
@@ -143,7 +146,17 @@ export default class Textbox extends React.Component {
onClick={this.showPreview}
className='textbox-preview-link'
>
- {this.state.preview ? 'Edit message' : 'Preview'}
+ {this.state.preview ? (
+ <FormattedMessage
+ id='textbox.edit'
+ defaultMessage='Edit message'
+ />
+ ) : (
+ <FormattedMessage
+ id='textbox.preview'
+ defaultMessage='Preview'
+ />
+ )}
</a>
);
}
@@ -184,7 +197,10 @@ export default class Textbox extends React.Component {
onClick={this.showHelp}
className='textbox-help-link'
>
- {'Help'}
+ <FormattedMessage
+ id='textbox.help'
+ defaultMessage='Help'
+ />
</a>
</div>
);
diff --git a/web/react/components/unread_channel_indicator.jsx b/web/react/components/unread_channel_indicator.jsx
index c0c34584f..509ac9e4d 100644
--- a/web/react/components/unread_channel_indicator.jsx
+++ b/web/react/components/unread_channel_indicator.jsx
@@ -31,5 +31,5 @@ UnreadChannelIndicator.defaultProps = {
UnreadChannelIndicator.propTypes = {
show: React.PropTypes.bool,
extraClass: React.PropTypes.string,
- text: React.PropTypes.string
+ text: React.PropTypes.object
};
diff --git a/web/react/components/user_profile.jsx b/web/react/components/user_profile.jsx
index 385cd0f52..1e393cfe9 100644
--- a/web/react/components/user_profile.jsx
+++ b/web/react/components/user_profile.jsx
@@ -3,6 +3,9 @@
import * as Utils from '../utils/utils.jsx';
import UserStore from '../stores/user_store.jsx';
+
+import {FormattedMessage} from 'mm-intl';
+
var Popover = ReactBootstrap.Popover;
var OverlayTrigger = ReactBootstrap.OverlayTrigger;
@@ -87,7 +90,10 @@ export default class UserProfile extends React.Component {
className='text-nowrap'
key='user-popover-no-email'
>
- {'Email not shared'}
+ <FormattedMessage
+ id='user_profile.notShared'
+ defaultMessage='Email not shared'
+ />
</div>
);
} else {
diff --git a/web/react/components/user_settings/custom_theme_chooser.jsx b/web/react/components/user_settings/custom_theme_chooser.jsx
index 8ec3863f3..9116dd938 100644
--- a/web/react/components/user_settings/custom_theme_chooser.jsx
+++ b/web/react/components/user_settings/custom_theme_chooser.jsx
@@ -6,7 +6,96 @@ import Constants from '../../utils/constants.jsx';
const OverlayTrigger = ReactBootstrap.OverlayTrigger;
const Popover = ReactBootstrap.Popover;
-export default class CustomThemeChooser extends React.Component {
+import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl';
+
+const messages = defineMessages({
+ sidebarBg: {
+ id: 'user.settings.custom_theme.sidebarBg',
+ defaultMessage: 'Sidebar BG'
+ },
+ sidebarText: {
+ id: 'user.settings.custom_theme.sidebarText',
+ defaultMessage: 'Sidebar Text'
+ },
+ sidebarHeaderBg: {
+ id: 'user.settings.custom_theme.sidebarHeaderBg',
+ defaultMessage: 'Sidebar Header BG'
+ },
+ sidebarHeaderTextColor: {
+ id: 'user.settings.custom_theme.sidebarHeaderTextColor',
+ defaultMessage: 'Sidebar Header Text'
+ },
+ sidebarUnreadText: {
+ id: 'user.settings.custom_theme.sidebarUnreadText',
+ defaultMessage: 'Sidebar Unread Text'
+ },
+ sidebarTextHoverBg: {
+ id: 'user.settings.custom_theme.sidebarTextHoverBg',
+ defaultMessage: 'Sidebar Text Hover BG'
+ },
+ sidebarTextActiveBorder: {
+ id: 'user.settings.custom_theme.sidebarTextActiveBorder',
+ defaultMessage: 'Sidebar Text Active Border'
+ },
+ sidebarTextActiveColor: {
+ id: 'user.settings.custom_theme.sidebarTextActiveColor',
+ defaultMessage: 'Sidebar Text Active Color'
+ },
+ onlineIndicator: {
+ id: 'user.settings.custom_theme.onlineIndicator',
+ defaultMessage: 'Online Indicator'
+ },
+ awayIndicator: {
+ id: 'user.settings.custom_theme.awayIndicator',
+ defaultMessage: 'Away Indicator'
+ },
+ mentionBj: {
+ id: 'user.settings.custom_theme.mentionBj',
+ defaultMessage: 'Mention Jewel BG'
+ },
+ mentionColor: {
+ id: 'user.settings.custom_theme.mentionColor',
+ defaultMessage: 'Mention Jewel Text'
+ },
+ centerChannelBg: {
+ id: 'user.settings.custom_theme.centerChannelBg',
+ defaultMessage: 'Center Channel BG'
+ },
+ centerChannelColor: {
+ id: 'user.settings.custom_theme.centerChannelColor',
+ defaultMessage: 'Center Channel Text'
+ },
+ newMessageSeparator: {
+ id: 'user.settings.custom_theme.newMessageSeparator',
+ defaultMessage: 'New Message Separator'
+ },
+ linkColor: {
+ id: 'user.settings.custom_theme.linkColor',
+ defaultMessage: 'Link Color'
+ },
+ buttonBg: {
+ id: 'user.settings.custom_theme.buttonBg',
+ defaultMessage: 'Button BG'
+ },
+ buttonColor: {
+ id: 'user.settings.custom_theme.buttonColor',
+ defaultMessage: 'Button Text'
+ },
+ mentionHighlightBg: {
+ id: 'user.settings.custom_theme.mentionHighlightBg',
+ defaultMessage: 'Mention Highlight BG'
+ },
+ mentionHighlightLink: {
+ id: 'user.settings.custom_theme.mentionHighlightLink',
+ defaultMessage: 'Mention Highlight Link'
+ },
+ codeTheme: {
+ id: 'user.settings.custom_theme.codeTheme',
+ defaultMessage: 'Code Theme'
+ }
+});
+
+class CustomThemeChooser extends React.Component {
constructor(props) {
super(props);
@@ -65,6 +154,7 @@ export default class CustomThemeChooser extends React.Component {
this.props.updateTheme(theme);
}
render() {
+ const {formatMessage} = this.props.intl;
const theme = this.props.theme;
const elements = [];
@@ -102,7 +192,7 @@ export default class CustomThemeChooser extends React.Component {
className='col-sm-4 form-group'
key={'custom-theme-key' + index}
>
- <label className='custom-label'>{element.uiName}</label>
+ <label className='custom-label'>{formatMessage(messages[element.id])}</label>
<div
className='input-group theme-group group--code dropdown'
id={element.id}
@@ -135,7 +225,7 @@ export default class CustomThemeChooser extends React.Component {
className='col-sm-4 form-group'
key={'custom-theme-key' + index}
>
- <label className='custom-label'>{element.uiName}</label>
+ <label className='custom-label'>{formatMessage(messages[element.id])}</label>
<div
className='input-group color-picker'
id={element.id}
@@ -160,7 +250,10 @@ export default class CustomThemeChooser extends React.Component {
const pasteBox = (
<div className='col-sm-12'>
<label className='custom-label'>
- {'Copy and paste to share theme colors:'}
+ <FormattedMessage
+ id='user.settings.custom_theme.copyPaste'
+ defaultMessage='Copy and paste to share theme colors:'
+ />
</label>
<input
type='text'
@@ -185,6 +278,9 @@ export default class CustomThemeChooser extends React.Component {
}
CustomThemeChooser.propTypes = {
+ intl: intlShape.isRequired,
theme: React.PropTypes.object.isRequired,
updateTheme: React.PropTypes.func.isRequired
};
+
+export default injectIntl(CustomThemeChooser); \ No newline at end of file
diff --git a/web/react/components/user_settings/import_theme_modal.jsx b/web/react/components/user_settings/import_theme_modal.jsx
index 45b05f19b..66bed0b0b 100644
--- a/web/react/components/user_settings/import_theme_modal.jsx
+++ b/web/react/components/user_settings/import_theme_modal.jsx
@@ -9,9 +9,19 @@ const Modal = ReactBootstrap.Modal;
import AppDispatcher from '../../dispatcher/app_dispatcher.jsx';
import Constants from '../../utils/constants.jsx';
+
+import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl';
+
+const holders = defineMessages({
+ submitError: {
+ id: 'user.settings.import_theme.submitError',
+ defaultMessage: 'Invalid format, please try copying and pasting in again.'
+ }
+});
+
const ActionTypes = Constants.ActionTypes;
-export default class ImportThemeModal extends React.Component {
+class ImportThemeModal extends React.Component {
constructor(props) {
super(props);
@@ -39,7 +49,7 @@ export default class ImportThemeModal extends React.Component {
const text = ReactDOM.findDOMNode(this.refs.input).value;
if (!this.isInputValid(text)) {
- this.setState({inputError: 'Invalid format, please try copying and pasting in again.'});
+ this.setState({inputError: this.props.intl.formatMessage(holders.submitError)});
return;
}
@@ -125,7 +135,7 @@ export default class ImportThemeModal extends React.Component {
if (this.isInputValid(e.target.value)) {
this.setState({inputError: null});
} else {
- this.setState({inputError: 'Invalid format, please try copying and pasting in again.'});
+ this.setState({inputError: this.props.intl.formatMessage(holders.submitError)});
}
}
render() {
@@ -136,7 +146,12 @@ export default class ImportThemeModal extends React.Component {
onHide={() => this.setState({show: false})}
>
<Modal.Header closeButton={true}>
- <Modal.Title>{'Import Slack Theme'}</Modal.Title>
+ <Modal.Title>
+ <FormattedMessage
+ id='user.settings.import_theme.importHeader'
+ defaultMessage='Import Slack Theme'
+ />
+ </Modal.Title>
</Modal.Header>
<form
role='form'
@@ -144,7 +159,10 @@ export default class ImportThemeModal extends React.Component {
>
<Modal.Body>
<p>
- {'To import a theme, go to a Slack team and look for “Preferences -> Sidebar Theme”. Open the custom theme option, copy the theme color values and paste them here:'}
+ <FormattedMessage
+ id='user.settings.import_theme.importBody'
+ defaultMessage='To import a theme, go to a Slack team and look for “Preferences -> Sidebar Theme”. Open the custom theme option, copy the theme color values and paste them here:'
+ />
</p>
<div className='form-group less'>
<div className='col-sm-9'>
@@ -166,7 +184,10 @@ export default class ImportThemeModal extends React.Component {
className='btn btn-default'
onClick={() => this.setState({show: false})}
>
- {'Cancel'}
+ <FormattedMessage
+ id='user.settings.import_theme.cancel'
+ defaultMessage='Cancel'
+ />
</button>
<button
onClick={this.handleSubmit}
@@ -174,7 +195,10 @@ export default class ImportThemeModal extends React.Component {
className='btn btn-primary'
tabIndex='3'
>
- {'Submit'}
+ <FormattedMessage
+ id='user.settings.import_theme.submit'
+ defaultMessage='Submit'
+ />
</button>
</Modal.Footer>
</form>
@@ -183,3 +207,9 @@ export default class ImportThemeModal extends React.Component {
);
}
}
+
+ImportThemeModal.propTypes = {
+ intl: intlShape.isRequired
+};
+
+export default injectIntl(ImportThemeModal); \ No newline at end of file
diff --git a/web/react/components/user_settings/manage_incoming_hooks.jsx b/web/react/components/user_settings/manage_incoming_hooks.jsx
index 1506e3c98..c6532b018 100644
--- a/web/react/components/user_settings/manage_incoming_hooks.jsx
+++ b/web/react/components/user_settings/manage_incoming_hooks.jsx
@@ -7,6 +7,8 @@ import Constants from '../../utils/constants.jsx';
import ChannelStore from '../../stores/channel_store.jsx';
import LoadingScreen from '../loading_screen.jsx';
+import {FormattedMessage, FormattedHTMLMessage} from 'mm-intl';
+
export default class ManageIncomingHooks extends React.Component {
constructor() {
super();
@@ -126,7 +128,12 @@ export default class ManageIncomingHooks extends React.Component {
<span className='word-break--all'>{Utils.getWindowLocationOrigin() + '/hooks/' + hook.id}</span>
</div>
<div className='padding-top'>
- <strong>{'Channel: '}</strong>{c.display_name}
+ <strong>
+ <FormattedMessage
+ id='user.settings.hooks_in.channel'
+ defaultMessage='Channel: '
+ />
+ </strong>{c.display_name}
</div>
<a
className={'webhook__remove'}
@@ -147,12 +154,24 @@ export default class ManageIncomingHooks extends React.Component {
} else if (hooks.length > 0) {
displayHooks = hooks;
} else {
- displayHooks = <div className='padding-top x2'>{'None'}</div>;
+ displayHooks = (
+ <div className='padding-top x2'>
+ <FormattedMessage
+ id='user.settings.hooks_in.none'
+ defaultMessage='None'
+ />
+ </div>
+ );
}
const existingHooks = (
<div className='webhooks__container'>
- <label className='control-label padding-top x2'>{'Existing incoming webhooks'}</label>
+ <label className='control-label padding-top x2'>
+ <FormattedMessage
+ id='user.settings.hooks_in.existing'
+ defaultMessage='Existing incoming webhooks'
+ />
+ </label>
<div className='padding-top divider-light'></div>
<div className='webhooks__list'>
{displayHooks}
@@ -162,15 +181,16 @@ export default class ManageIncomingHooks extends React.Component {
return (
<div key='addIncomingHook'>
- {'Create webhook URLs for use in external integrations. Please see '}
- <a
- href='http://mattermost.org/webhooks'
- target='_blank'
- >
- {'http://mattermost.org/webhooks'}
- </a>
- {' to learn more.'}
- <div><label className='control-label padding-top x2'>{'Add a new incoming webhook'}</label></div>
+ <FormattedHTMLMessage
+ id='user.settings.hooks_in.description'
+ defaultMessage='Create webhook URLs for use in external integrations. Please see<a href="http://mattermost.org/webhooks" target="_blank">http://mattermost.org/webhooks</a> to learn more.'
+ />
+ <div><label className='control-label padding-top x2'>
+ <FormattedMessage
+ id='user.settings.hooks_in.addTitle'
+ defaultMessage='Add a new incoming webhook'
+ />
+ </label></div>
<div className='row padding-top'>
<div className='col-sm-10 padding-bottom'>
<select
@@ -189,7 +209,10 @@ export default class ManageIncomingHooks extends React.Component {
href='#'
onClick={this.addNewHook}
>
- {'Add'}
+ <FormattedMessage
+ id='user.settings.hooks_in.add'
+ defaultMessage='Add'
+ />
</a>
</div>
</div>
diff --git a/web/react/components/user_settings/manage_languages.jsx b/web/react/components/user_settings/manage_languages.jsx
index 123165b76..fee6d9da2 100644
--- a/web/react/components/user_settings/manage_languages.jsx
+++ b/web/react/components/user_settings/manage_languages.jsx
@@ -4,6 +4,8 @@
import * as Client from '../../utils/client.jsx';
import * as Utils from '../../utils/utils.jsx';
+import {FormattedMessage} from 'mm-intl';
+
export default class ManageLanguage extends React.Component {
constructor(props) {
super(props);
@@ -70,7 +72,12 @@ export default class ManageLanguage extends React.Component {
return (
<div key='changeLanguage'>
<br/>
- <label className='control-label'>{'Change interface language'}</label>
+ <label className='control-label'>
+ <FormattedMessage
+ id='user.settings.languages.change'
+ defaultMessage='Change interface language'
+ />
+ </label>
<div className='padding-top'>
<select
ref='language'
@@ -87,7 +94,10 @@ export default class ManageLanguage extends React.Component {
href='#'
onClick={this.changeLanguage}
>
- {'Set language'}
+ <FormattedMessage
+ id='user.settings.languages'
+ defaultMessage='Set language'
+ />
</a>
</div>
</div>
diff --git a/web/react/components/user_settings/manage_outgoing_hooks.jsx b/web/react/components/user_settings/manage_outgoing_hooks.jsx
index 17acf0f10..3f88e9f41 100644
--- a/web/react/components/user_settings/manage_outgoing_hooks.jsx
+++ b/web/react/components/user_settings/manage_outgoing_hooks.jsx
@@ -8,7 +8,20 @@ import ChannelStore from '../../stores/channel_store.jsx';
import * as Client from '../../utils/client.jsx';
import Constants from '../../utils/constants.jsx';
-export default class ManageOutgoingHooks extends React.Component {
+import {intlShape, injectIntl, defineMessages, FormattedMessage, FormattedHTMLMessage} from 'mm-intl';
+
+const holders = defineMessages({
+ optional: {
+ id: 'user.settings.hooks_out.optional',
+ defaultMessage: 'Optional if channel selected'
+ },
+ callbackHolder: {
+ id: 'user.settings.hooks_out.callbackHolder',
+ defaultMessage: 'Each URL must start with http:// or https://'
+ }
+});
+
+class ManageOutgoingHooks extends React.Component {
constructor() {
super();
@@ -140,7 +153,10 @@ export default class ManageOutgoingHooks extends React.Component {
key='select-channel'
value=''
>
- {'--- Select a channel ---'}
+ <FormattedMessage
+ id='user.settings.hooks_out.select'
+ defaultMessage='--- Select a channel ---'
+ />
</option>
);
@@ -169,7 +185,12 @@ export default class ManageOutgoingHooks extends React.Component {
if (c) {
channelDiv = (
<div className='padding-top'>
- <strong>{'Channel: '}</strong>{c.display_name}
+ <strong>
+ <FormattedMessage
+ id='user.settings.hooks_out.channel'
+ defaultMessage='Channel: '
+ />
+ </strong>{c.display_name}
</div>
);
}
@@ -178,7 +199,12 @@ export default class ManageOutgoingHooks extends React.Component {
if (hook.trigger_words && hook.trigger_words.length !== 0) {
triggerDiv = (
<div className='padding-top'>
- <strong>{'Trigger Words: '}</strong>{hook.trigger_words.join(', ')}
+ <strong>
+ <FormattedMessage
+ id='user.settings.hooks_out.trigger'
+ defaultMessage='Trigger Words: '
+ />
+ </strong>{hook.trigger_words.join(', ')}
</div>
);
}
@@ -202,7 +228,10 @@ export default class ManageOutgoingHooks extends React.Component {
href='#'
onClick={this.regenToken.bind(this, hook.id)}
>
- {'Regen Token'}
+ <FormattedMessage
+ id='user.settings.hooks_out.regen'
+ defaultMessage='Regen Token'
+ />
</a>
<a
className='webhook__remove'
@@ -223,12 +252,24 @@ export default class ManageOutgoingHooks extends React.Component {
} else if (hooks.length > 0) {
displayHooks = hooks;
} else {
- displayHooks = <div className='padding-top x2'>{'None'}</div>;
+ displayHooks = (
+ <div className='padding-top x2'>
+ <FormattedMessage
+ id='user.settings.hooks_out.none'
+ defaultMessage='None'
+ />
+ </div>
+ );
}
const existingHooks = (
<div className='webhooks__container'>
- <label className='control-label padding-top x2'>{'Existing outgoing webhooks'}</label>
+ <label className='control-label padding-top x2'>
+ <FormattedMessage
+ id='user.settings.hooks_out.existing'
+ defaultMessage='Existing outgoing webhooks'
+ />
+ </label>
<div className='padding-top divider-light'></div>
<div className='webhooks__list'>
{displayHooks}
@@ -240,19 +281,25 @@ export default class ManageOutgoingHooks extends React.Component {
return (
<div key='addOutgoingHook'>
- {'Create webhooks to send new message events to an external integration. Please see '}
- <a
- href='http://mattermost.org/webhooks'
- target='_blank'
- >
- {'http://mattermost.org/webhooks'}
- </a>
- {' to learn more.'}
- <div><label className='control-label padding-top x2'>{'Add a new outgoing webhook'}</label></div>
+ <FormattedHTMLMessage
+ id='user.settings.hooks_out.addDescription'
+ defaultMessage='Create webhooks to send new message events to an external integration. Please see <a href="http://mattermost.org/webhooks">http://mattermost.org/webhooks</a> to learn more.'
+ />
+ <div><label className='control-label padding-top x2'>
+ <FormattedMessage
+ id='user.settings.hooks_out.addTitle'
+ defaultMessage='Add a new outgoing webhook'
+ />
+ </label></div>
<div className='padding-top divider-light'></div>
<div className='padding-top'>
<div>
- <label className='control-label'>{'Channel'}</label>
+ <label className='control-label'>
+ <FormattedMessage
+ id='user.settings.hooks_out.channel'
+ defaultMessage='Channel: '
+ />
+ </label>
<div className='padding-top'>
<select
ref='channelName'
@@ -263,23 +310,43 @@ export default class ManageOutgoingHooks extends React.Component {
{options}
</select>
</div>
- <div className='padding-top'>{'Only public channels can be used'}</div>
+ <div className='padding-top'>
+ <FormattedMessage
+ id='user.settings.hooks_out.only'
+ defaultMessage='Only public channels can be used'
+ />
+ </div>
</div>
<div className='padding-top x2'>
- <label className='control-label'>{'Trigger Words:'}</label>
+ <label className='control-label'>
+ <FormattedMessage
+ id='user.settings.hooks_out.trigger'
+ defaultMessage='Trigger Words: '
+ />
+ </label>
<div className='padding-top'>
<input
ref='triggerWords'
className='form-control'
value={this.state.triggerWords}
onChange={this.updateTriggerWords}
- placeholder='Optional if channel selected'
+ placeholder={this.props.intl.formatMessage(holders.optional)}
+ />
+ </div>
+ <div className='padding-top'>
+ <FormattedMessage
+ id='user.settings.hooks_out.comma'
+ defaultMessage='Comma separated words to trigger on'
/>
</div>
- <div className='padding-top'>{'Comma separated words to trigger on'}</div>
</div>
<div className='padding-top x2'>
- <label className='control-label'>{'Callback URLs:'}</label>
+ <label className='control-label'>
+ <FormattedMessage
+ id='user.settings.hooks_out.callback'
+ defaultMessage='Callback URLs: '
+ />
+ </label>
<div className='padding-top'>
<textarea
ref='callbackURLs'
@@ -288,10 +355,15 @@ export default class ManageOutgoingHooks extends React.Component {
resize={false}
rows={3}
onChange={this.updateCallbackURLs}
- placeholder='Each URL must start with http:// or https://'
+ placeholder={this.props.intl.formatMessage(holders.callbackHolder)}
/>
</div>
- <div className='padding-top'>{'New line separated URLs that will receive the HTTP POST event'}</div>
+ <div className='padding-top'>
+ <FormattedMessage
+ id='user.settings.hooks_out.callbackDesc'
+ defaultMessage='New line separated URLs that will receive the HTTP POST event'
+ />
+ </div>
{addError}
</div>
<div className='padding-top padding-bottom'>
@@ -301,7 +373,10 @@ export default class ManageOutgoingHooks extends React.Component {
disabled={disableButton}
onClick={this.addNewHook}
>
- {'Add'}
+ <FormattedMessage
+ id='user.settings.hooks_out.add'
+ defaultMessage='Add'
+ />
</a>
</div>
</div>
@@ -311,3 +386,9 @@ export default class ManageOutgoingHooks extends React.Component {
);
}
}
+
+ManageOutgoingHooks.propTypes = {
+ intl: intlShape.isRequired
+};
+
+export default injectIntl(ManageOutgoingHooks); \ No newline at end of file
diff --git a/web/react/components/user_settings/user_settings_advanced.jsx b/web/react/components/user_settings/user_settings_advanced.jsx
index d4bd00bb5..5c0757589 100644
--- a/web/react/components/user_settings/user_settings_advanced.jsx
+++ b/web/react/components/user_settings/user_settings_advanced.jsx
@@ -6,9 +6,55 @@ import SettingItemMin from '../setting_item_min.jsx';
import SettingItemMax from '../setting_item_max.jsx';
import Constants from '../../utils/constants.jsx';
import PreferenceStore from '../../stores/preference_store.jsx';
+
+import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl';
+
const PreReleaseFeatures = Constants.PRE_RELEASE_FEATURES;
-export default class AdvancedSettingsDisplay extends React.Component {
+const holders = defineMessages({
+ sendTitle: {
+ id: 'user.settings.advance.sendTitle',
+ defaultMessage: 'Send messages on Ctrl + Enter'
+ },
+ on: {
+ id: 'user.settings.advance.on',
+ defaultMessage: 'On'
+ },
+ off: {
+ id: 'user.settings.advance.off',
+ defaultMessage: 'Off'
+ },
+ preReleaseTitle: {
+ id: 'user.settings.advance.preReleaseTitle',
+ defaultMessage: 'Preview pre-release features'
+ },
+ feature: {
+ id: 'user.settings.advance.feature',
+ defaultMessage: ' Feature '
+ },
+ features: {
+ id: 'user.settings.advance.features',
+ defaultMessage: ' Features '
+ },
+ enabled: {
+ id: 'user.settings.advance.enabled',
+ defaultMessage: 'enabled'
+ },
+ MARKDOWN_PREVIEW: {
+ id: 'user.settings.advance.markdown_preview',
+ defaultMessage: 'Show markdown preview option in message input box'
+ },
+ EMBED_PREVIEW: {
+ id: 'user.settings.advance.embed_preview',
+ defaultMessage: 'Show preview snippet of links below message'
+ },
+ LOC_PREVIEW: {
+ id: 'user.settings.advance.loc_preview',
+ defaultMessage: 'Show user language in display settings'
+ }
+});
+
+class AdvancedSettingsDisplay extends React.Component {
constructor(props) {
super(props);
@@ -104,6 +150,7 @@ export default class AdvancedSettingsDisplay extends React.Component {
render() {
const serverError = this.state.serverError || null;
+ const {formatMessage} = this.props.intl;
let ctrlSendSection;
if (this.props.activeSection === 'advancedCtrlSend') {
@@ -121,7 +168,10 @@ export default class AdvancedSettingsDisplay extends React.Component {
checked={ctrlSendActive[0]}
onChange={this.updateSetting.bind(this, 'send_on_ctrl_enter', 'true')}
/>
- {'On'}
+ <FormattedMessage
+ id='user.settings.advance.on'
+ defaultMessage='On'
+ />
</label>
<br/>
</div>
@@ -132,17 +182,26 @@ export default class AdvancedSettingsDisplay extends React.Component {
checked={ctrlSendActive[1]}
onChange={this.updateSetting.bind(this, 'send_on_ctrl_enter', 'false')}
/>
- {'Off'}
+ <FormattedMessage
+ id='user.settings.advance.off'
+ defaultMessage='Off'
+ />
</label>
<br/>
</div>
- <div><br/>{'If enabled \'Enter\' inserts a new line and \'Ctrl + Enter\' submits the message.'}</div>
+ <div>
+ <br/>
+ <FormattedMessage
+ id='user.settings.advance.sendDesc'
+ defaultMessage="If enabled 'Enter' inserts a new line and 'Ctrl + Enter' submits the message."
+ />
+ </div>
</div>
];
ctrlSendSection = (
<SettingItemMax
- title='Send messages on Ctrl + Enter'
+ title={formatMessage(holders.sendTitle)}
inputs={inputs}
submit={() => this.handleSubmit('send_on_ctrl_enter')}
server_error={serverError}
@@ -155,8 +214,8 @@ export default class AdvancedSettingsDisplay extends React.Component {
} else {
ctrlSendSection = (
<SettingItemMin
- title='Send messages on Ctrl + Enter'
- describe={this.state.settings.send_on_ctrl_enter === 'true' ? 'On' : 'Off'}
+ title={formatMessage(holders.sendTitle)}
+ describe={this.state.settings.send_on_ctrl_enter === 'true' ? formatMessage(holders.on) : formatMessage(holders.off)}
updateSection={() => this.props.updateSection('advancedCtrlSend')}
/>
);
@@ -185,7 +244,7 @@ export default class AdvancedSettingsDisplay extends React.Component {
this.toggleFeature(feature.label, e.target.checked);
}}
/>
- {feature.description}
+ {formatMessage({id: 'user.settings.advance.' + feature.label})}
</label>
</div>
</div>
@@ -195,13 +254,16 @@ export default class AdvancedSettingsDisplay extends React.Component {
inputs.push(
<div key='advancedPreviewFeatures_helptext'>
<br/>
- {'Check any pre-released features you\'d like to preview. You may also need to refresh the page before the setting will take effect.'}
+ <FormattedMessage
+ id='user.settings.advance.preReleaseDesc'
+ defaultMessage="Check any pre-released features you'd like to preview. You may also need to refresh the page before the setting will take effect."
+ />
</div>
);
previewFeaturesSection = (
<SettingItemMax
- title='Preview pre-release features'
+ title={formatMessage(holders.preReleaseTitle)}
inputs={inputs}
submit={this.saveEnabledFeatures}
server_error={serverError}
@@ -214,8 +276,8 @@ export default class AdvancedSettingsDisplay extends React.Component {
} else {
previewFeaturesSection = (
<SettingItemMin
- title='Preview pre-release features'
- describe={this.state.enabledFeatures + (this.state.enabledFeatures === 1 ? ' Feature ' : ' Features ') + 'enabled'}
+ title={formatMessage(holders.preReleaseTitle)}
+ describe={this.state.enabledFeatures + (this.state.enabledFeatures === 1 ? formatMessage(holders.feature) : formatMessage(holders.features)) + formatMessage(holders.enabled)}
updateSection={() => this.props.updateSection('advancedPreviewFeatures')}
/>
);
@@ -242,11 +304,19 @@ export default class AdvancedSettingsDisplay extends React.Component {
className='modal-back'
onClick={this.props.collapseModal}
/>
- {'Advanced Settings'}
+ <FormattedMessage
+ id='user.settings.advance.title'
+ defaultMessage='Advanced Settings'
+ />
</h4>
</div>
<div className='user-settings'>
- <h3 className='tab-header'>{'Advanced Settings'}</h3>
+ <h3 className='tab-header'>
+ <FormattedMessage
+ id='user.settings.advance.title'
+ defaultMessage='Advanced Settings'
+ />
+ </h3>
<div className='divider-dark first'/>
{ctrlSendSection}
{previewFeaturesSectionDivider}
@@ -259,6 +329,7 @@ export default class AdvancedSettingsDisplay extends React.Component {
}
AdvancedSettingsDisplay.propTypes = {
+ intl: intlShape.isRequired,
user: React.PropTypes.object,
updateSection: React.PropTypes.func,
updateTab: React.PropTypes.func,
@@ -266,3 +337,5 @@ AdvancedSettingsDisplay.propTypes = {
closeModal: React.PropTypes.func.isRequired,
collapseModal: React.PropTypes.func.isRequired
};
+
+export default injectIntl(AdvancedSettingsDisplay); \ No newline at end of file
diff --git a/web/react/components/user_settings/user_settings_appearance.jsx b/web/react/components/user_settings/user_settings_appearance.jsx
index 7bfc9fdbd..fb11dc81b 100644
--- a/web/react/components/user_settings/user_settings_appearance.jsx
+++ b/web/react/components/user_settings/user_settings_appearance.jsx
@@ -11,6 +11,9 @@ import * as Client from '../../utils/client.jsx';
import * as Utils from '../../utils/utils.jsx';
import Constants from '../../utils/constants.jsx';
+
+import {FormattedMessage} from 'mm-intl';
+
const ActionTypes = Constants.ActionTypes;
export default class UserSettingsAppearance extends React.Component {
@@ -180,7 +183,10 @@ export default class UserSettingsAppearance extends React.Component {
checked={!displayCustom}
onChange={this.updateType.bind(this, 'premade')}
/>
- {'Theme Colors'}
+ <FormattedMessage
+ id='user.settings.appearance.themeColors'
+ defaultMessage='Theme Colors'
+ />
</label>
<br/>
</div>
@@ -191,7 +197,10 @@ export default class UserSettingsAppearance extends React.Component {
checked={displayCustom}
onChange={this.updateType.bind(this, 'custom')}
/>
- {'Custom Theme'}
+ <FormattedMessage
+ id='user.settings.appearance.customTheme'
+ defaultMessage='Custom Theme'
+ />
</label>
<br/>
</div>
@@ -203,14 +212,20 @@ export default class UserSettingsAppearance extends React.Component {
href='#'
onClick={this.submitTheme}
>
- {'Save'}
+ <FormattedMessage
+ id='user.settings.appearance.save'
+ defaultMessage='Save'
+ />
</a>
<a
className='btn btn-sm theme'
href='#'
onClick={this.resetFields}
>
- {'Cancel'}
+ <FormattedMessage
+ id='user.settings.appearance.cancel'
+ defaultMessage='Cancel'
+ />
</a>
</div>
</div>
@@ -235,11 +250,19 @@ export default class UserSettingsAppearance extends React.Component {
className='modal-back'
onClick={this.props.collapseModal}
/>
- {'Appearance Settings'}
+ <FormattedMessage
+ id='user.settings.appearance.title'
+ defaultMessage='Appearance Settings'
+ />
</h4>
</div>
<div className='user-settings'>
- <h3 className='tab-header'>{'Appearance Settings'}</h3>
+ <h3 className='tab-header'>
+ <FormattedMessage
+ id='user.settings.appearance.title'
+ defaultMessage='Appearance Settings'
+ />
+ </h3>
<div className='divider-dark first'/>
{themeUI}
<div className='divider-dark'/>
@@ -248,7 +271,10 @@ export default class UserSettingsAppearance extends React.Component {
className='theme'
onClick={this.handleImportModal}
>
- {'Import theme colors from Slack'}
+ <FormattedMessage
+ id='user.settings.appearance.import'
+ defaultMessage='Import theme colors from Slack'
+ />
</a>
</div>
</div>
diff --git a/web/react/components/user_settings/user_settings_developer.jsx b/web/react/components/user_settings/user_settings_developer.jsx
index 01e13be57..5868e0ad3 100644
--- a/web/react/components/user_settings/user_settings_developer.jsx
+++ b/web/react/components/user_settings/user_settings_developer.jsx
@@ -5,7 +5,20 @@ import SettingItemMin from '../setting_item_min.jsx';
import SettingItemMax from '../setting_item_max.jsx';
import * as EventHelpers from '../../dispatcher/event_helpers.jsx';
-export default class DeveloperTab extends React.Component {
+import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl';
+
+const holders = defineMessages({
+ applicationsPreview: {
+ id: 'user.settings.developer.applicationsPreview',
+ defaultMessage: 'Applications (Preview)'
+ },
+ thirdParty: {
+ id: 'user.settings.developer.thirdParty',
+ defaultMessage: 'Open to register a new third-party application'
+ }
+});
+
+class DeveloperTab extends React.Component {
constructor(props) {
super(props);
@@ -20,6 +33,7 @@ export default class DeveloperTab extends React.Component {
render() {
var appSection;
var self = this;
+ const {formatMessage} = this.props.intl;
if (this.props.activeSection === 'app') {
var inputs = [];
@@ -33,7 +47,10 @@ export default class DeveloperTab extends React.Component {
className='btn btn-sm btn-primary'
onClick={this.register}
>
- {'Register New Application'}
+ <FormattedMessage
+ id='user.settings.developer.register'
+ defaultMessage='Register New Application'
+ />
</a>
</div>
</div>
@@ -41,7 +58,7 @@ export default class DeveloperTab extends React.Component {
appSection = (
<SettingItemMax
- title='Applications (Preview)'
+ title={formatMessage(holders.applicationsPreview)}
inputs={inputs}
updateSection={function updateSection(e) {
self.props.updateSection('');
@@ -52,8 +69,8 @@ export default class DeveloperTab extends React.Component {
} else {
appSection = (
<SettingItemMin
- title='Applications (Preview)'
- describe='Open to register a new third-party application'
+ title={formatMessage(holders.applicationsPreview)}
+ describe={formatMessage(holders.thirdParty)}
updateSection={function updateSection() {
self.props.updateSection('app');
}}
@@ -81,11 +98,19 @@ export default class DeveloperTab extends React.Component {
className='modal-back'
onClick={this.props.collapseModal}
/>
- {'Developer Settings'}
+ <FormattedMessage
+ id='user.settings.developer.title'
+ defaultMessage='Developer Settings'
+ />
</h4>
</div>
<div className='user-settings'>
- <h3 className='tab-header'>{'Developer Settings'}</h3>
+ <h3 className='tab-header'>
+ <FormattedMessage
+ id='user.settings.developer.title'
+ defaultMessage='Developer Settings'
+ />
+ </h3>
<div className='divider-dark first'/>
{appSection}
<div className='divider-dark'/>
@@ -99,8 +124,11 @@ DeveloperTab.defaultProps = {
activeSection: ''
};
DeveloperTab.propTypes = {
+ intl: intlShape.isRequired,
activeSection: React.PropTypes.string,
updateSection: React.PropTypes.func,
closeModal: React.PropTypes.func.isRequired,
collapseModal: React.PropTypes.func.isRequired
};
+
+export default injectIntl(DeveloperTab); \ No newline at end of file
diff --git a/web/react/components/user_settings/user_settings_display.jsx b/web/react/components/user_settings/user_settings_display.jsx
index f2c2502fb..3b2a2065b 100644
--- a/web/react/components/user_settings/user_settings_display.jsx
+++ b/web/react/components/user_settings/user_settings_display.jsx
@@ -10,6 +10,47 @@ import PreferenceStore from '../../stores/preference_store.jsx';
import ManageLanguages from './manage_languages.jsx';
import * as Utils from '../../utils/utils.jsx';
+import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl';
+
+const holders = defineMessages({
+ normalClock: {
+ id: 'user.settings.display.normalClock',
+ defaultMessage: '12-hour clock (example: 4:00 PM)'
+ },
+ militaryClock: {
+ id: 'user.settings.display.militaryClock',
+ defaultMessage: '24-hour clock (example: 16:00)'
+ },
+ clockDisplay: {
+ id: 'user.settings.display.clockDisplay',
+ defaultMessage: 'Clock Display'
+ },
+ teammateDisplay: {
+ id: 'user.settings.display.teammateDisplay',
+ defaultMessage: 'Teammate Name Display'
+ },
+ showNickname: {
+ id: 'user.settings.display.showNickname',
+ defaultMessage: 'Show nickname if one exists, otherwise show first and last name'
+ },
+ showUsername: {
+ id: 'user.settings.display.showUsername',
+ defaultMessage: 'Show username (team default)'
+ },
+ showFullname: {
+ id: 'user.settings.display.showFullname',
+ defaultMessage: 'Show first and last name'
+ },
+ fontTitle: {
+ id: 'user.settings.display.fontTitle',
+ defaultMessage: 'Display Font'
+ },
+ language: {
+ id: 'user.settings.display.language',
+ defaultMessage: 'Language'
+ }
+});
+
function getDisplayStateFromStores() {
const militaryTime = PreferenceStore.getPreference(Constants.Preferences.CATEGORY_DISPLAY_SETTINGS, 'use_military_time', {value: 'false'});
const nameFormat = PreferenceStore.getPreference(Constants.Preferences.CATEGORY_DISPLAY_SETTINGS, 'name_format', {value: 'username'});
@@ -22,7 +63,7 @@ function getDisplayStateFromStores() {
};
}
-export default class UserSettingsDisplay extends React.Component {
+class UserSettingsDisplay extends React.Component {
constructor(props) {
super(props);
@@ -76,6 +117,7 @@ export default class UserSettingsDisplay extends React.Component {
this.updateState();
}
render() {
+ const {formatMessage} = this.props.intl;
const serverError = this.state.serverError || null;
let clockSection;
let nameFormatSection;
@@ -104,7 +146,10 @@ export default class UserSettingsDisplay extends React.Component {
checked={clockFormat[0]}
onChange={this.handleClockRadio.bind(this, 'false')}
/>
- {'12-hour clock (example: 4:00 PM)'}
+ <FormattedMessage
+ id='user.settings.display.normalClock'
+ defaultMessage='12-hour clock (example: 4:00 PM)'
+ />
</label>
<br/>
</div>
@@ -115,17 +160,26 @@ export default class UserSettingsDisplay extends React.Component {
checked={clockFormat[1]}
onChange={this.handleClockRadio.bind(this, 'true')}
/>
- {'24-hour clock (example: 16:00)'}
+ <FormattedMessage
+ id='user.settings.display.militaryClock'
+ defaultMessage='24-hour clock (example: 16:00)'
+ />
</label>
<br/>
</div>
- <div><br/>{'Select how you prefer time displayed.'}</div>
+ <div>
+ <br/>
+ <FormattedMessage
+ id='user.settings.display.preferTime'
+ defaultMessage='Select how you prefer time displayed.'
+ />
+ </div>
</div>
];
clockSection = (
<SettingItemMax
- title='Clock Display'
+ title={formatMessage(holders.clockDisplay)}
inputs={inputs}
submit={this.handleSubmit}
server_error={serverError}
@@ -135,9 +189,9 @@ export default class UserSettingsDisplay extends React.Component {
} else {
let describe = '';
if (this.state.militaryTime === 'true') {
- describe = '24-hour clock (example: 16:00)';
+ describe = formatMessage(holders.militaryClock);
} else {
- describe = '12-hour clock (example: 4:00 PM)';
+ describe = formatMessage(holders.normalClock);
}
const handleUpdateClockSection = () => {
@@ -146,16 +200,31 @@ export default class UserSettingsDisplay extends React.Component {
clockSection = (
<SettingItemMin
- title='Clock Display'
+ title={formatMessage(holders.clockDisplay)}
describe={describe}
updateSection={handleUpdateClockSection}
/>
);
}
- const showUsername = 'Show username (team default)';
- const showNickname = 'Show nickname if one exists, otherwise show first and last name';
- const showFullName = 'Show first and last name';
+ const showUsername = (
+ <FormattedMessage
+ id='user.settings.display.showUsername'
+ defaultMessage='Show username (team default)'
+ />
+ );
+ const showNickname = (
+ <FormattedMessage
+ id='user.settings.display.showNickname'
+ defaultMessage='Show nickname if one exists, otherwise show first and last name'
+ />
+ );
+ const showFullName = (
+ <FormattedMessage
+ id='user.settings.display.showFullname'
+ defaultMessage='Show first and last name'
+ />
+ );
if (this.props.activeSection === 'name_format') {
const nameFormat = [false, false, false];
if (this.state.nameFormat === 'nickname_full_name') {
@@ -201,13 +270,19 @@ export default class UserSettingsDisplay extends React.Component {
</label>
<br/>
</div>
- <div><br/>{'Set how to display other user\'s names in posts and the Direct Messages list.'}</div>
+ <div>
+ <br/>
+ <FormattedMessage
+ id='user.settings.display.nameOptsDesc'
+ defaultMessage="Set how to display other user's names in posts and the Direct Messages list."
+ />
+ </div>
</div>
];
nameFormatSection = (
<SettingItemMax
- title='Teammate Name Display'
+ title={formatMessage(holders.teammateDisplay)}
inputs={inputs}
submit={this.handleSubmit}
server_error={serverError}
@@ -220,16 +295,16 @@ export default class UserSettingsDisplay extends React.Component {
} else {
let describe = '';
if (this.state.nameFormat === 'username') {
- describe = showUsername;
+ describe = formatMessage(holders.showUsername);
} else if (this.state.nameFormat === 'full_name') {
- describe = showFullName;
+ describe = formatMessage(holders.showFullName);
} else {
- describe = showNickname;
+ describe = formatMessage(holders.showNickname);
}
nameFormatSection = (
<SettingItemMin
- title='Teammate Name Display'
+ title={formatMessage(holders.teammateDisplay)}
describe={describe}
updateSection={() => {
this.props.updateSection('name_format');
@@ -267,13 +342,19 @@ export default class UserSettingsDisplay extends React.Component {
{options}
</select>
</div>
- <div><br/>{'Select the font displayed in the Mattermost user interface.'}</div>
+ <div>
+ <br/>
+ <FormattedMessage
+ id='user.settings.display.fontDesc'
+ defaultMessage='Select the font displayed in the Mattermost user interface.'
+ />
+ </div>
</div>
];
fontSection = (
<SettingItemMax
- title='Display Font'
+ title={formatMessage(holders.fontTitle)}
inputs={inputs}
submit={this.handleSubmit}
server_error={serverError}
@@ -286,7 +367,7 @@ export default class UserSettingsDisplay extends React.Component {
} else {
fontSection = (
<SettingItemMin
- title='Display Font'
+ title={formatMessage(holders.fontTitle)}
describe={this.state.selectedFont}
updateSection={() => {
this.props.updateSection('font');
@@ -307,7 +388,7 @@ export default class UserSettingsDisplay extends React.Component {
languagesSection = (
<SettingItemMax
- title={'Language'}
+ title={formatMessage(holders.language)}
width='medium'
inputs={inputs}
updateSection={(e) => {
@@ -326,7 +407,7 @@ export default class UserSettingsDisplay extends React.Component {
languagesSection = (
<SettingItemMin
- title={'Language'}
+ title={formatMessage(holders.language)}
width='medium'
describe={locale}
updateSection={() => {
@@ -357,11 +438,19 @@ export default class UserSettingsDisplay extends React.Component {
className='modal-back'
onClick={this.props.collapseModal}
/>
- {'Display Settings'}
+ <FormattedMessage
+ id='user.settings.display.title'
+ defaultMessage='Display Settings'
+ />
</h4>
</div>
<div className='user-settings'>
- <h3 className='tab-header'>{'Display Settings'}</h3>
+ <h3 className='tab-header'>
+ <FormattedMessage
+ id='user.settings.display.title'
+ defaultMessage='Display Settings'
+ />
+ </h3>
<div className='divider-dark first'/>
{fontSection}
<div className='divider-dark'/>
@@ -377,6 +466,7 @@ export default class UserSettingsDisplay extends React.Component {
}
UserSettingsDisplay.propTypes = {
+ intl: intlShape.isRequired,
user: React.PropTypes.object,
updateSection: React.PropTypes.func,
updateTab: React.PropTypes.func,
@@ -384,3 +474,5 @@ UserSettingsDisplay.propTypes = {
closeModal: React.PropTypes.func.isRequired,
collapseModal: React.PropTypes.func.isRequired
};
+
+export default injectIntl(UserSettingsDisplay); \ No newline at end of file
diff --git a/web/react/components/user_settings/user_settings_general.jsx b/web/react/components/user_settings/user_settings_general.jsx
index df7ae4a25..f20b4b807 100644
--- a/web/react/components/user_settings/user_settings_general.jsx
+++ b/web/react/components/user_settings/user_settings_general.jsx
@@ -13,7 +13,84 @@ import Constants from '../../utils/constants.jsx';
import * as AsyncClient from '../../utils/async_client.jsx';
import * as Utils from '../../utils/utils.jsx';
-export default class UserSettingsGeneralTab extends React.Component {
+import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl';
+
+const holders = defineMessages({
+ usernameReserved: {
+ id: 'user.settings.general.usernameReserved',
+ defaultMessage: 'This username is reserved, please choose a new one.'
+ },
+ usernameRestrictions: {
+ id: 'user.settings.general.usernameRestrictions',
+ defaultMessage: "'Username must begin with a letter, and contain between {min} to {max} lowercase characters made up of numbers, letters, and the symbols '.', '-' and '_'."
+ },
+ validEmail: {
+ id: 'user.settings.general.validEmail',
+ defaultMessage: 'Please enter a valid email address'
+ },
+ emailMatch: {
+ id: 'user.settings.general.emailMatch',
+ defaultMessage: 'The new emails you entered do not match.'
+ },
+ checkEmail: {
+ id: 'user.settings.general.checkEmail',
+ defaultMessage: 'Check your email at {email} to verify the address.'
+ },
+ newAddress: {
+ id: 'user.settings.general.newAddress',
+ defaultMessage: 'New Address: {email}<br />Check your email to verify the above address.'
+ },
+ checkEmailNoAddress: {
+ id: 'user.settings.general.checkEmailNoAddress',
+ defaultMessage: 'Check your email to verify your new address'
+ },
+ loginGitlab: {
+ id: 'user.settings.general.loginGitlab',
+ defaultMessage: 'Log in done through GitLab'
+ },
+ validImage: {
+ id: 'user.settings.general.validImage',
+ defaultMessage: 'Only JPG or PNG images may be used for profile pictures'
+ },
+ imageTooLarge: {
+ id: 'user.settings.general.imageTooLarge',
+ defaultMessage: 'Unable to upload profile image. File is too large.'
+ },
+ uploadImage: {
+ id: 'user.settings.general.uploadImage',
+ defaultMessage: "Click 'Edit' to upload an image."
+ },
+ imageUpdated: {
+ id: 'user.settings.general.imageUpdated',
+ defaultMessage: 'Image last updated {date}'
+ },
+ fullName: {
+ id: 'user.settings.general.fullName',
+ defaultMessage: 'Full Name'
+ },
+ nickname: {
+ id: 'user.settings.general.nickname',
+ defaultMessage: 'Nickname'
+ },
+ username: {
+ id: 'user.settings.general.username',
+ defaultMessage: 'Username'
+ },
+ email: {
+ id: 'user.settings.general.email',
+ defaultMessage: 'Email'
+ },
+ profilePicture: {
+ id: 'user.settings.general.profilePicture',
+ defaultMessage: 'Profile Picture'
+ },
+ close: {
+ id: 'user.settings.general.close',
+ defaultMessage: 'Close'
+ }
+});
+
+class UserSettingsGeneralTab extends React.Component {
constructor(props) {
super(props);
this.submitActive = false;
@@ -42,12 +119,13 @@ export default class UserSettingsGeneralTab extends React.Component {
const user = Object.assign({}, this.props.user);
const username = this.state.username.trim().toLowerCase();
+ const {formatMessage} = this.props.intl;
const usernameError = Utils.isValidUsername(username);
if (usernameError === 'Cannot use a reserved word as a username.') {
- this.setState({clientError: 'This username is reserved, please choose a new one.'});
+ this.setState({clientError: formatMessage(holders.usernameReserved)});
return;
} else if (usernameError) {
- this.setState({clientError: 'Username must begin with a letter, and contain between ' + Constants.MIN_USERNAME_LENGTH + ' to ' + Constants.MAX_USERNAME_LENGTH + " lowercase characters made up of numbers, letters, and the symbols '.', '-' and '_'."});
+ this.setState({clientError: formatMessage(holders.usernameRestrictions, {min: Constants.MIN_USERNAME_LENGTH, max: Constants.MAX_USERNAME_LENGTH})});
return;
}
@@ -99,13 +177,14 @@ export default class UserSettingsGeneralTab extends React.Component {
const email = this.state.email.trim().toLowerCase();
const confirmEmail = this.state.confirmEmail.trim().toLowerCase();
+ const {formatMessage} = this.props.intl;
if (email === '' || !Utils.isEmail(email)) {
- this.setState({emailError: 'Please enter a valid email address.', clientError: '', serverError: ''});
+ this.setState({emailError: formatMessage(holders.validEmail), clientError: '', serverError: ''});
return;
}
if (email !== confirmEmail) {
- this.setState({emailError: 'The new emails you entered do not match.', clientError: '', serverError: ''});
+ this.setState({emailError: formatMessage(holders.emailMatch), clientError: '', serverError: ''});
return;
}
@@ -125,7 +204,7 @@ export default class UserSettingsGeneralTab extends React.Component {
const verificationEnabled = global.window.mm_config.SendEmailNotifications === 'true' && global.window.mm_config.RequireEmailVerification === 'true' && emailUpdated;
if (verificationEnabled) {
- ErrorStore.storeLastError({message: 'Check your email at ' + user.email + ' to verify the address.'});
+ ErrorStore.storeLastError({message: this.props.intl.formatMessage(holders.checkEmail, {email: user.email})});
ErrorStore.emitChange();
this.setState({emailChangeInProgress: true});
}
@@ -152,13 +231,14 @@ export default class UserSettingsGeneralTab extends React.Component {
return;
}
+ const {formatMessage} = this.props.intl;
const picture = this.state.picture;
if (picture.type !== 'image/jpeg' && picture.type !== 'image/png') {
- this.setState({clientError: 'Only JPG or PNG images may be used for profile pictures.'});
+ this.setState({clientError: formatMessage(holders.validImage)});
return;
} else if (picture.size > Constants.MAX_FILE_SIZE) {
- this.setState({clientError: 'Unable to upload profile image. File is too large.'});
+ this.setState({clientError: formatMessage(holders.imageTooLarge)});
return;
}
@@ -221,6 +301,7 @@ export default class UserSettingsGeneralTab extends React.Component {
}
render() {
const user = this.props.user;
+ const {formatMessage, formatHTMLMessage} = this.props.intl;
let clientError = null;
if (this.state.clientError) {
@@ -244,7 +325,12 @@ export default class UserSettingsGeneralTab extends React.Component {
key='firstNameSetting'
className='form-group'
>
- <label className='col-sm-5 control-label'>{'First Name'}</label>
+ <label className='col-sm-5 control-label'>
+ <FormattedMessage
+ id='user.settings.general.firstName'
+ defaultMessage='First Name'
+ />
+ </label>
<div className='col-sm-7'>
<input
className='form-control'
@@ -261,7 +347,12 @@ export default class UserSettingsGeneralTab extends React.Component {
key='lastNameSetting'
className='form-group'
>
- <label className='col-sm-5 control-label'>{'Last Name'}</label>
+ <label className='col-sm-5 control-label'>
+ <FormattedMessage
+ id='user.settings.general.lastName'
+ defaultMessage='Last Name'
+ />
+ </label>
<div className='col-sm-7'>
<input
className='form-control'
@@ -284,20 +375,28 @@ export default class UserSettingsGeneralTab extends React.Component {
href='#'
onClick={notifClick.bind(this)}
>
- {'Notifications'}
+ <FormattedMessage
+ id='user.settings.general.notificationsLink'
+ defaultMessage='Notifications'
+ />
</a>
);
const extraInfo = (
<span>
- {'By default, you will receive mention notifications when someone types your first name. '}
- {'Go to '} {notifLink} {'settings to change this default.'}
+ <FormattedMessage
+ id='user.settings.general.notificationsExtra'
+ defaultMessage='By default, you will receive mention notifications when someone types your first name. Go to {notify} settings to change this default.'
+ values={{
+ notify: (notifLink)
+ }}
+ />
</span>
);
nameSection = (
<SettingItemMax
- title='Full Name'
+ title={formatMessage(holders.fullName)}
inputs={inputs}
submit={this.submitName}
server_error={serverError}
@@ -322,7 +421,7 @@ export default class UserSettingsGeneralTab extends React.Component {
nameSection = (
<SettingItemMin
- title='Full Name'
+ title={formatMessage(holders.fullName)}
describe={fullName}
updateSection={() => {
this.updateSection('name');
@@ -333,7 +432,12 @@ export default class UserSettingsGeneralTab extends React.Component {
let nicknameSection;
if (this.props.activeSection === 'nickname') {
- let nicknameLabel = 'Nickname';
+ let nicknameLabel = (
+ <FormattedMessage
+ id='user.settings.general.nickname'
+ defaultMessage='Nickname'
+ />
+ );
if (Utils.isMobile()) {
nicknameLabel = '';
}
@@ -357,13 +461,16 @@ export default class UserSettingsGeneralTab extends React.Component {
const extraInfo = (
<span>
- {'Use Nickname for a name you might be called that is different from your first name and username. This is most often used when two or more people have similar sounding names and usernames.'}
+ <FormattedMessage
+ id='user.settings.general.nicknameExtra'
+ defaultMessage='Use Nickname for a name you might be called that is different from your first name and username. This is most often used when two or more people have similar sounding names and usernames.'
+ />
</span>
);
nicknameSection = (
<SettingItemMax
- title='Nickname'
+ title={formatMessage(holders.nickname)}
inputs={inputs}
submit={this.submitNickname}
server_error={serverError}
@@ -378,7 +485,7 @@ export default class UserSettingsGeneralTab extends React.Component {
} else {
nicknameSection = (
<SettingItemMin
- title='Nickname'
+ title={formatMessage(holders.nickname)}
describe={UserStore.getCurrentUser().nickname}
updateSection={() => {
this.updateSection('nickname');
@@ -389,7 +496,12 @@ export default class UserSettingsGeneralTab extends React.Component {
let usernameSection;
if (this.props.activeSection === 'username') {
- let usernameLabel = 'Username';
+ let usernameLabel = (
+ <FormattedMessage
+ id='user.settings.general.username'
+ defaultMessage='Username'
+ />
+ );
if (Utils.isMobile()) {
usernameLabel = '';
}
@@ -411,11 +523,18 @@ export default class UserSettingsGeneralTab extends React.Component {
</div>
);
- const extraInfo = (<span>{'Pick something easy for teammates to recognize and recall.'}</span>);
+ const extraInfo = (
+ <span>
+ <FormattedMessage
+ id='user.settings.general.usernameInfo'
+ defaultMessage='Pick something easy for teammates to recognize and recall.'
+ />
+ </span>
+ );
usernameSection = (
<SettingItemMax
- title='Username'
+ title={formatMessage(holders.username)}
inputs={inputs}
submit={this.submitUsername}
server_error={serverError}
@@ -430,7 +549,7 @@ export default class UserSettingsGeneralTab extends React.Component {
} else {
usernameSection = (
<SettingItemMin
- title='Username'
+ title={formatMessage(holders.username)}
describe={UserStore.getCurrentUser().username}
updateSection={() => {
this.updateSection('username');
@@ -443,16 +562,41 @@ export default class UserSettingsGeneralTab extends React.Component {
if (this.props.activeSection === 'email') {
const emailEnabled = global.window.mm_config.SendEmailNotifications === 'true';
const emailVerificationEnabled = global.window.mm_config.RequireEmailVerification === 'true';
- let helpText = 'Email is used for sign-in, notifications, and password reset. Email requires verification if changed.';
+ let helpText = (
+ <FormattedMessage
+ id='user.settings.general.emailHelp1'
+ defaultMessage='Email is used for sign-in, notifications, and password reset. Email requires verification if changed.'
+ />
+ );
if (!emailEnabled) {
- helpText = <div className='setting-list__hint text-danger'>{'Email has been disabled by your system administrator. No notification emails will be sent until it is enabled.'}</div>;
+ helpText = (
+ <div className='setting-list__hint text-danger'>
+ <FormattedMessage
+ id='user.settings.general.emailHelp2'
+ defaultMessage='Email has been disabled by your system administrator. No notification emails will be sent until it is enabled.'
+ />
+ </div>
+ );
} else if (!emailVerificationEnabled) {
- helpText = 'Email is used for sign-in, notifications, and password reset.';
+ helpText = (
+ <FormattedMessage
+ id='user.settings.general.emailHelp3'
+ defaultMessage='Email is used for sign-in, notifications, and password reset.'
+ />
+ );
} else if (this.state.emailChangeInProgress) {
const newEmail = UserStore.getCurrentUser().email;
if (newEmail) {
- helpText = 'A verification email was sent to ' + newEmail + '.';
+ helpText = (
+ <FormattedMessage
+ id='user.settings.general.emailHelp4'
+ defaultMessage='A verification email was sent to {email}.'
+ values={{
+ email: newEmail
+ }}
+ />
+ );
}
}
@@ -462,7 +606,12 @@ export default class UserSettingsGeneralTab extends React.Component {
inputs.push(
<div key='emailSetting'>
<div className='form-group'>
- <label className='col-sm-5 control-label'>{'Primary Email'}</label>
+ <label className='col-sm-5 control-label'>
+ <FormattedMessage
+ id='user.settings.general.primaryEmail'
+ defaultMessage='Primary Email'
+ />
+ </label>
<div className='col-sm-7'>
<input
className='form-control'
@@ -478,7 +627,12 @@ export default class UserSettingsGeneralTab extends React.Component {
inputs.push(
<div key='confirmEmailSetting'>
<div className='form-group'>
- <label className='col-sm-5 control-label'>{'Confirm Email'}</label>
+ <label className='col-sm-5 control-label'>
+ <FormattedMessage
+ id='user.settings.general.confirmEmail'
+ defaultMessage='Confirm Email'
+ />
+ </label>
<div className='col-sm-7'>
<input
className='form-control'
@@ -499,7 +653,12 @@ export default class UserSettingsGeneralTab extends React.Component {
key='oauthEmailInfo'
className='form-group'
>
- <div className='setting-list__hint'>{'Log in occurs through GitLab. Email cannot be updated.'}</div>
+ <div className='setting-list__hint'>
+ <FormattedMessage
+ id='user.settings.general.emailCantUpdate'
+ defaultMessage='Log in occurs through GitLab. Email cannot be updated.'
+ />
+ </div>
{helpText}
</div>
);
@@ -524,20 +683,20 @@ export default class UserSettingsGeneralTab extends React.Component {
if (this.state.emailChangeInProgress) {
const newEmail = UserStore.getCurrentUser().email;
if (newEmail) {
- describe = 'New Address: ' + newEmail + '\nCheck your email to verify the above address.';
+ describe = formatHTMLMessage(holders.newAddress, {email: newEmail});
} else {
- describe = 'Check your email to verify your new address';
+ describe = formatMessage(holders.checkEmailNoAddress);
}
} else {
describe = UserStore.getCurrentUser().email;
}
} else if (this.props.user.auth_service === Constants.GITLAB_SERVICE) {
- describe = 'Log in done through GitLab';
+ describe = formatMessage(holders.loginGitlab);
}
emailSection = (
<SettingItemMin
- title='Email'
+ title={formatMessage(holders.email)}
describe={describe}
updateSection={() => {
this.updateSection('email');
@@ -550,7 +709,7 @@ export default class UserSettingsGeneralTab extends React.Component {
if (this.props.activeSection === 'picture') {
pictureSection = (
<SettingPicture
- title='Profile Picture'
+ title={formatMessage(holders.profilePicture)}
submit={this.submitPicture}
src={'/api/v1/users/' + user.id + '/image?time=' + user.last_picture_update + '&' + Utils.getSessionIndex()}
server_error={serverError}
@@ -566,13 +725,15 @@ export default class UserSettingsGeneralTab extends React.Component {
/>
);
} else {
- let minMessage = 'Click \'Edit\' to upload an image.';
+ let minMessage = formatMessage(holders.uploadImage);
if (user.last_picture_update) {
- minMessage = 'Image last updated ' + Utils.displayDate(user.last_picture_update);
+ minMessage = formatMessage(holders.imageUpdated, {
+ date: new Date(user.last_picture_update).toLocaleDateString(global.window.mm_locale, {month: 'short', day: '2-digit', year: 'numeric'})
+ });
}
pictureSection = (
<SettingItemMin
- title='Profile Picture'
+ title={formatMessage(holders.profilePicture)}
describe={minMessage}
updateSection={() => {
this.updateSection('picture');
@@ -588,7 +749,7 @@ export default class UserSettingsGeneralTab extends React.Component {
type='button'
className='close'
data-dismiss='modal'
- aria-label='Close'
+ aria-label={formatMessage(holders.close)}
onClick={this.props.closeModal}
>
<span aria-hidden='true'>{'×'}</span>
@@ -601,11 +762,19 @@ export default class UserSettingsGeneralTab extends React.Component {
className='modal-back'
onClick={this.props.collapseModal}
/>
- {'General Settings'}
+ <FormattedMessage
+ id='user.settings.general.title'
+ defaultMessage='General Settings'
+ />
</h4>
</div>
<div className='user-settings'>
- <h3 className='tab-header'>{'General Settings'}</h3>
+ <h3 className='tab-header'>
+ <FormattedMessage
+ id='user.settings.general.title'
+ defaultMessage='General Settings'
+ />
+ </h3>
<div className='divider-dark first'/>
{nameSection}
<div className='divider-light'/>
@@ -624,6 +793,7 @@ export default class UserSettingsGeneralTab extends React.Component {
}
UserSettingsGeneralTab.propTypes = {
+ intl: intlShape.isRequired,
user: React.PropTypes.object.isRequired,
updateSection: React.PropTypes.func.isRequired,
updateTab: React.PropTypes.func.isRequired,
@@ -631,3 +801,5 @@ UserSettingsGeneralTab.propTypes = {
closeModal: React.PropTypes.func.isRequired,
collapseModal: React.PropTypes.func.isRequired
};
+
+export default injectIntl(UserSettingsGeneralTab); \ No newline at end of file
diff --git a/web/react/components/user_settings/user_settings_integrations.jsx b/web/react/components/user_settings/user_settings_integrations.jsx
index a86510eb3..abd04a301 100644
--- a/web/react/components/user_settings/user_settings_integrations.jsx
+++ b/web/react/components/user_settings/user_settings_integrations.jsx
@@ -6,7 +6,28 @@ import SettingItemMax from '../setting_item_max.jsx';
import ManageIncomingHooks from './manage_incoming_hooks.jsx';
import ManageOutgoingHooks from './manage_outgoing_hooks.jsx';
-export default class UserSettingsIntegrationsTab extends React.Component {
+import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl';
+
+const holders = defineMessages({
+ inName: {
+ id: 'user.settings.integrations.incomingWebhooks',
+ defaultMessage: 'Incoming Webhooks'
+ },
+ inDesc: {
+ id: 'user.settings.integrations.incomingWebhooksDescription',
+ defaultMessage: 'Manage your incoming webhooks'
+ },
+ outName: {
+ id: 'user.settings.integrations.outWebhooks',
+ defaultMessage: 'Outgoing Webhooks'
+ },
+ outDesc: {
+ id: 'user.settings.integrations.outWebhooksDescription',
+ defaultMessage: 'Manage your outgoing webhooks'
+ }
+});
+
+class UserSettingsIntegrationsTab extends React.Component {
constructor(props) {
super(props);
@@ -21,6 +42,7 @@ export default class UserSettingsIntegrationsTab extends React.Component {
let incomingHooksSection;
let outgoingHooksSection;
var inputs = [];
+ const {formatMessage} = this.props.intl;
if (global.window.mm_config.EnableIncomingWebhooks === 'true') {
if (this.props.activeSection === 'incoming-hooks') {
@@ -30,7 +52,7 @@ export default class UserSettingsIntegrationsTab extends React.Component {
incomingHooksSection = (
<SettingItemMax
- title='Incoming Webhooks'
+ title={formatMessage(holders.inName)}
width='medium'
inputs={inputs}
updateSection={(e) => {
@@ -42,9 +64,9 @@ export default class UserSettingsIntegrationsTab extends React.Component {
} else {
incomingHooksSection = (
<SettingItemMin
- title='Incoming Webhooks'
+ title={formatMessage(holders.inName)}
width='medium'
- describe='Manage your incoming webhooks'
+ describe={formatMessage(holders.inDesc)}
updateSection={() => {
this.updateSection('incoming-hooks');
}}
@@ -61,7 +83,7 @@ export default class UserSettingsIntegrationsTab extends React.Component {
outgoingHooksSection = (
<SettingItemMax
- title='Outgoing Webhooks'
+ title={formatMessage(holders.outName)}
width='medium'
inputs={inputs}
updateSection={(e) => {
@@ -73,9 +95,9 @@ export default class UserSettingsIntegrationsTab extends React.Component {
} else {
outgoingHooksSection = (
<SettingItemMin
- title='Outgoing Webhooks'
+ title={formatMessage(holders.outName)}
width='medium'
- describe='Manage your outgoing webhooks'
+ describe={formatMessage(holders.outDesc)}
updateSection={() => {
this.updateSection('outgoing-hooks');
}}
@@ -104,11 +126,19 @@ export default class UserSettingsIntegrationsTab extends React.Component {
className='modal-back'
onClick={this.props.collapseModal}
/>
- {'Integration Settings'}
+ <FormattedMessage
+ id='user.settings.integrations.title'
+ defaultMessage='Integration Settings'
+ />
</h4>
</div>
<div className='user-settings'>
- <h3 className='tab-header'>{'Integration Settings'}</h3>
+ <h3 className='tab-header'>
+ <FormattedMessage
+ id='user.settings.integrations.title'
+ defaultMessage='Integration Settings'
+ />
+ </h3>
<div className='divider-dark first'/>
{incomingHooksSection}
<div className='divider-light'/>
@@ -121,6 +151,7 @@ export default class UserSettingsIntegrationsTab extends React.Component {
}
UserSettingsIntegrationsTab.propTypes = {
+ intl: intlShape.isRequired,
user: React.PropTypes.object,
updateSection: React.PropTypes.func,
updateTab: React.PropTypes.func,
@@ -128,3 +159,5 @@ UserSettingsIntegrationsTab.propTypes = {
closeModal: React.PropTypes.func.isRequired,
collapseModal: React.PropTypes.func.isRequired
};
+
+export default injectIntl(UserSettingsIntegrationsTab); \ No newline at end of file
diff --git a/web/react/components/user_settings/user_settings_modal.jsx b/web/react/components/user_settings/user_settings_modal.jsx
index 36e1aa217..2a0a90cf5 100644
--- a/web/react/components/user_settings/user_settings_modal.jsx
+++ b/web/react/components/user_settings/user_settings_modal.jsx
@@ -6,7 +6,56 @@ const Modal = ReactBootstrap.Modal;
import SettingsSidebar from '../settings_sidebar.jsx';
import UserSettings from './user_settings.jsx';
-export default class UserSettingsModal extends React.Component {
+import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl';
+
+const holders = defineMessages({
+ general: {
+ id: 'user.settings.modal.general',
+ defaultMessage: 'General'
+ },
+ security: {
+ id: 'user.settings.modal.security',
+ defaultMessage: 'Security'
+ },
+ notifications: {
+ id: 'user.settings.modal.notifications',
+ defaultMessage: 'Notifications'
+ },
+ appearance: {
+ id: 'user.settings.modal.appearance',
+ defaultMessage: 'Appearance'
+ },
+ developer: {
+ id: 'user.settings.modal.developer',
+ defaultMessage: 'Developer'
+ },
+ integrations: {
+ id: 'user.settings.modal.integrations',
+ defaultMessage: 'Integrations'
+ },
+ display: {
+ id: 'user.settings.modal.display',
+ defaultMessage: 'Display'
+ },
+ advanced: {
+ id: 'user.settings.modal.advanced',
+ defaultMessage: 'Advanced'
+ },
+ confirmTitle: {
+ id: 'user.settings.modal.confirmTitle',
+ defaultMessage: 'Discard Changes?'
+ },
+ confirmMsg: {
+ id: 'user.settings.modal.confirmMsg',
+ defaultMessage: 'You have unsaved changes, are you sure you want to discard them?'
+ },
+ confirmBtns: {
+ id: 'user.settings.modal.confirmBtns',
+ defaultMessage: 'Yes, Discard'
+ }
+});
+
+class UserSettingsModal extends React.Component {
constructor(props) {
super(props);
@@ -170,20 +219,21 @@ export default class UserSettingsModal extends React.Component {
}
render() {
+ const {formatMessage} = this.props.intl;
var tabs = [];
- tabs.push({name: 'general', uiName: 'General', icon: 'glyphicon glyphicon-cog'});
- tabs.push({name: 'security', uiName: 'Security', icon: 'glyphicon glyphicon-lock'});
- tabs.push({name: 'notifications', uiName: 'Notifications', icon: 'glyphicon glyphicon-exclamation-sign'});
- tabs.push({name: 'appearance', uiName: 'Appearance', icon: 'glyphicon glyphicon-wrench'});
+ tabs.push({name: 'general', uiName: formatMessage(holders.general), icon: 'glyphicon glyphicon-cog'});
+ tabs.push({name: 'security', uiName: formatMessage(holders.security), icon: 'glyphicon glyphicon-lock'});
+ tabs.push({name: 'notifications', uiName: formatMessage(holders.notifications), icon: 'glyphicon glyphicon-exclamation-sign'});
+ tabs.push({name: 'appearance', uiName: formatMessage(holders.appearance), icon: 'glyphicon glyphicon-wrench'});
if (global.window.mm_config.EnableOAuthServiceProvider === 'true') {
- tabs.push({name: 'developer', uiName: 'Developer', icon: 'glyphicon glyphicon-th'});
+ tabs.push({name: 'developer', uiName: formatMessage(holders.developer), icon: 'glyphicon glyphicon-th'});
}
if (global.window.mm_config.EnableIncomingWebhooks === 'true' || global.window.mm_config.EnableOutgoingWebhooks === 'true') {
- tabs.push({name: 'integrations', uiName: 'Integrations', icon: 'glyphicon glyphicon-transfer'});
+ tabs.push({name: 'integrations', uiName: formatMessage(holders.integrations), icon: 'glyphicon glyphicon-transfer'});
}
- tabs.push({name: 'display', uiName: 'Display', icon: 'glyphicon glyphicon-eye-open'});
- tabs.push({name: 'advanced', uiName: 'Advanced', icon: 'glyphicon glyphicon-list-alt'});
+ tabs.push({name: 'display', uiName: formatMessage(holders.display), icon: 'glyphicon glyphicon-eye-open'});
+ tabs.push({name: 'advanced', uiName: formatMessage(holders.advanced), icon: 'glyphicon glyphicon-list-alt'});
return (
<Modal
@@ -194,7 +244,12 @@ export default class UserSettingsModal extends React.Component {
enforceFocus={this.state.enforceFocus}
>
<Modal.Header closeButton={true}>
- <Modal.Title>{'Account Settings'}</Modal.Title>
+ <Modal.Title>
+ <FormattedMessage
+ id='user.settings.modal.title'
+ defaultMessage='Account Settings'
+ />
+ </Modal.Title>
</Modal.Header>
<Modal.Body ref='modalBody'>
<div className='settings-table'>
@@ -221,9 +276,9 @@ export default class UserSettingsModal extends React.Component {
</div>
</Modal.Body>
<ConfirmModal
- title='Discard Changes?'
- message='You have unsaved changes, are you sure you want to discard them?'
- confirm_button='Yes, Discard'
+ title={formatMessage(holders.confirmTitle)}
+ message={formatMessage(holders.confirmMsg)}
+ confirm_button={formatMessage(holders.confirmBtns)}
show={this.state.showConfirmModal}
onConfirm={this.handleConfirm}
onCancel={this.handleCancelConfirmation}
@@ -234,6 +289,9 @@ export default class UserSettingsModal extends React.Component {
}
UserSettingsModal.propTypes = {
+ intl: intlShape.isRequired,
show: React.PropTypes.bool.isRequired,
onModalDismissed: React.PropTypes.func.isRequired
};
+
+export default injectIntl(UserSettingsModal); \ No newline at end of file
diff --git a/web/react/components/user_settings/user_settings_notifications.jsx b/web/react/components/user_settings/user_settings_notifications.jsx
index f762405af..91a03eb70 100644
--- a/web/react/components/user_settings/user_settings_notifications.jsx
+++ b/web/react/components/user_settings/user_settings_notifications.jsx
@@ -10,6 +10,8 @@ import * as Client from '../../utils/client.jsx';
import * as AsyncClient from '../../utils/async_client.jsx';
import * as Utils from '../../utils/utils.jsx';
+import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl';
+
function getNotificationsStateFromStores() {
var user = UserStore.getCurrentUser();
var soundNeeded = !Utils.isBrowserFirefox();
@@ -73,7 +75,30 @@ function getNotificationsStateFromStores() {
firstNameKey: firstNameKey, allKey: allKey, channelKey: channelKey};
}
-export default class NotificationsTab extends React.Component {
+const holders = defineMessages({
+ desktop: {
+ id: 'user.settings.notifications.desktop',
+ defaultMessage: 'Send desktop notifications'
+ },
+ desktopSounds: {
+ id: 'user.settings.notifications.desktopSounds',
+ defaultMessage: 'Desktop notification sounds'
+ },
+ emailNotifications: {
+ id: 'user.settings.notifications.emailNotifications',
+ defaultMessage: 'Email notifications'
+ },
+ wordsTrigger: {
+ id: 'user.settings.notifications.wordsTrigger',
+ defaultMessage: 'Words that trigger mentions'
+ },
+ close: {
+ id: 'user.settings.notifications.close',
+ defaultMessage: 'Close'
+ }
+});
+
+class NotificationsTab extends React.Component {
constructor(props) {
super(props);
@@ -198,6 +223,7 @@ export default class NotificationsTab extends React.Component {
this.updateCustomMentionKeys();
}
render() {
+ const {formatMessage} = this.props.intl;
var serverError = null;
if (this.state.serverError) {
serverError = this.state.serverError;
@@ -227,7 +253,10 @@ export default class NotificationsTab extends React.Component {
checked={notifyActive[0]}
onChange={this.handleNotifyRadio.bind(this, 'all')}
/>
- {'For all activity'}
+ <FormattedMessage
+ id='user.settings.notification.allActivity'
+ defaultMessage='For all activity'
+ />
</label>
<br/>
</div>
@@ -238,7 +267,10 @@ export default class NotificationsTab extends React.Component {
checked={notifyActive[1]}
onChange={this.handleNotifyRadio.bind(this, 'mention')}
/>
- {'Only for mentions and direct messages'}
+ <FormattedMessage
+ id='user.settings.notifications.onlyMentions'
+ defaultMessage='Only for mentions and direct messages'
+ />
</label>
<br/>
</div>
@@ -249,17 +281,27 @@ export default class NotificationsTab extends React.Component {
checked={notifyActive[2]}
onChange={this.handleNotifyRadio.bind(this, 'none')}
/>
- {'Never'}
+ <FormattedMessage
+ id='user.settings.notifications.never'
+ defaultMessage='Never'
+ />
</label>
</div>
</div>
);
- const extraInfo = <span>{'Desktop notifications are available on Firefox, Safari, and Chrome.'}</span>;
+ const extraInfo = (
+ <span>
+ <FormattedMessage
+ id='user.settings.notifications.info'
+ defaultMessage='Desktop notification sounds are available on Firefox, Safari, Chrome, Internet Explorer, and Edge.'
+ />
+ </span>
+ );
desktopSection = (
<SettingItemMax
- title='Send desktop notifications'
+ title={formatMessage(holders.desktop)}
extraInfo={extraInfo}
inputs={inputs}
submit={this.handleSubmit}
@@ -270,11 +312,26 @@ export default class NotificationsTab extends React.Component {
} else {
let describe = '';
if (this.state.notifyLevel === 'mention') {
- describe = 'Only for mentions and direct messages';
+ describe = (
+ <FormattedMessage
+ id='user.settings.notifications.onlyMentions'
+ defaultMessage='Only for mentions and direct messages'
+ />
+ );
} else if (this.state.notifyLevel === 'none') {
- describe = 'Never';
+ describe = (
+ <FormattedMessage
+ id='user.settings.notifications.never'
+ defaultMessage='Never'
+ />
+ );
} else {
- describe = 'For all activity';
+ describe = (
+ <FormattedMessage
+ id='user.settings.notification.allActivity'
+ defaultMessage='For all activity'
+ />
+ );
}
handleUpdateDesktopSection = function updateDesktopSection() {
@@ -283,7 +340,7 @@ export default class NotificationsTab extends React.Component {
desktopSection = (
<SettingItemMin
- title='Send desktop notifications'
+ title={formatMessage(holders.desktop)}
describe={describe}
updateSection={handleUpdateDesktopSection}
/>
@@ -311,7 +368,10 @@ export default class NotificationsTab extends React.Component {
checked={soundActive[0]}
onChange={this.handleSoundRadio.bind(this, 'true')}
/>
- {'On'}
+ <FormattedMessage
+ id='user.settings.notifications.on'
+ defaultMessage='On'
+ />
</label>
<br/>
</div>
@@ -322,18 +382,28 @@ export default class NotificationsTab extends React.Component {
checked={soundActive[1]}
onChange={this.handleSoundRadio.bind(this, 'false')}
/>
- {'Off'}
+ <FormattedMessage
+ id='user.settings.notifications.off'
+ defaultMessage='Off'
+ />
</label>
<br/>
</div>
</div>
);
- const extraInfo = <span>{'Desktop notification sounds are available on Firefox, Safari, Chrome, Internet Explorer, and Edge.'}</span>;
+ const extraInfo = (
+ <span>
+ <FormattedMessage
+ id='user.settings.notifications.info'
+ defaultMessage='Desktop notification sounds are available on Firefox, Safari, Chrome, Internet Explorer, and Edge.'
+ />
+ </span>
+ );
soundSection = (
<SettingItemMax
- title='Desktop notification sounds'
+ title={formatMessage(holders.desktopSounds)}
extraInfo={extraInfo}
inputs={inputs}
submit={this.handleSubmit}
@@ -344,11 +414,26 @@ export default class NotificationsTab extends React.Component {
} else {
let describe = '';
if (!this.state.soundNeeded) {
- describe = 'Please configure notification sounds in your browser settings';
+ describe = (
+ <FormattedMessage
+ id='user.settings.notification.soundConfig'
+ defaultMessage='Please configure notification sounds in your browser settings'
+ />
+ );
} else if (this.state.enableSound === 'false') {
- describe = 'Off';
+ describe = (
+ <FormattedMessage
+ id='user.settings.notifications.off'
+ defaultMessage='Off'
+ />
+ );
} else {
- describe = 'On';
+ describe = (
+ <FormattedMessage
+ id='user.settings.notifications.on'
+ defaultMessage='On'
+ />
+ );
}
handleUpdateSoundSection = function updateSoundSection() {
@@ -357,7 +442,7 @@ export default class NotificationsTab extends React.Component {
soundSection = (
<SettingItemMin
- title='Desktop notification sounds'
+ title={formatMessage(holders.desktopSounds)}
describe={describe}
updateSection={handleUpdateSoundSection}
disableOpen = {!this.state.soundNeeded}
@@ -386,7 +471,10 @@ export default class NotificationsTab extends React.Component {
checked={emailActive[0]}
onChange={this.handleEmailRadio.bind(this, 'true')}
/>
- {'On'}
+ <FormattedMessage
+ id='user.settings.notifications.on'
+ defaultMessage='On'
+ />
</label>
<br/>
</div>
@@ -397,17 +485,28 @@ export default class NotificationsTab extends React.Component {
checked={emailActive[1]}
onChange={this.handleEmailRadio.bind(this, 'false')}
/>
- {'Off'}
+ <FormattedMessage
+ id='user.settings.notifications.off'
+ defaultMessage='Off'
+ />
</label>
<br/>
</div>
- <div><br/>{'Email notifications are sent for mentions and direct messages after you’ve been offline for more than 60 seconds or away from ' + global.window.mm_config.SiteName + ' for more than 5 minutes.'}</div>
+ <div><br/>
+ <FormattedMessage
+ id='user.settings.notifications.emailInfo'
+ defaultMessage='Email notifications are sent for mentions and direct messages after you’ve been offline for more than 60 seconds or away from {siteName} for more than 5 minutes.'
+ values={{
+ siteName: global.window.mm_config.SiteName
+ }}
+ />
+ </div>
</div>
);
emailSection = (
<SettingItemMax
- title='Email notifications'
+ title={formatMessage(holders.emailNotifications)}
inputs={inputs}
submit={this.handleSubmit}
server_error={serverError}
@@ -417,9 +516,19 @@ export default class NotificationsTab extends React.Component {
} else {
let describe = '';
if (this.state.enableEmail === 'false') {
- describe = 'Off';
+ describe = (
+ <FormattedMessage
+ id='user.settings.notifications.off'
+ defaultMessage='Off'
+ />
+ );
} else {
- describe = 'On';
+ describe = (
+ <FormattedMessage
+ id='user.settings.notifications.on'
+ defaultMessage='On'
+ />
+ );
}
handleUpdateEmailSection = function updateEmailSection() {
@@ -428,7 +537,7 @@ export default class NotificationsTab extends React.Component {
emailSection = (
<SettingItemMin
- title='Email notifications'
+ title={formatMessage(holders.emailNotifications)}
describe={describe}
updateSection={handleUpdateEmailSection}
/>
@@ -459,7 +568,13 @@ export default class NotificationsTab extends React.Component {
checked={this.state.firstNameKey}
onChange={handleUpdateFirstNameKey}
/>
- {'Your case sensitive first name "' + user.first_name + '"'}
+ <FormattedMessage
+ id='user.settings.notifications.sensitiveName'
+ defaultMessage='Your case sensitive first name "{first_name}"'
+ values={{
+ first_name: user.first_name
+ }}
+ />
</label>
</div>
</div>
@@ -478,7 +593,13 @@ export default class NotificationsTab extends React.Component {
checked={this.state.usernameKey}
onChange={handleUpdateUsernameKey}
/>
- {'Your non-case sensitive username "' + user.username + '"'}
+ <FormattedMessage
+ id='user.settings.notifications.sensitiveUsername'
+ defaultMessage='Your non-case sensitive username "{username}"'
+ values={{
+ username: user.username
+ }}
+ />
</label>
</div>
</div>
@@ -496,7 +617,13 @@ export default class NotificationsTab extends React.Component {
checked={this.state.mentionKey}
onChange={handleUpdateMentionKey}
/>
- {'Your username mentioned "@' + user.username + '"'}
+ <FormattedMessage
+ id='user.settings.notifications.usernameMention'
+ defaultMessage='Your username mentioned "@{username}"'
+ values={{
+ username: user.username
+ }}
+ />
</label>
</div>
</div>
@@ -514,7 +641,10 @@ export default class NotificationsTab extends React.Component {
checked={this.state.allKey}
onChange={handleUpdateAllKey}
/>
- {'Team-wide mentions "@all"'}
+ <FormattedMessage
+ id='user.settings.notifications.teamWide'
+ defaultMessage='Team-wide mentions "@all"'
+ />
</label>
</div>
</div>
@@ -532,7 +662,10 @@ export default class NotificationsTab extends React.Component {
checked={this.state.channelKey}
onChange={handleUpdateChannelKey}
/>
- {'Channel-wide mentions "@channel"'}
+ <FormattedMessage
+ id='user.settings.notifications.channelWide'
+ defaultMessage='Channel-wide mentions "@channel"'
+ />
</label>
</div>
</div>
@@ -548,7 +681,10 @@ export default class NotificationsTab extends React.Component {
checked={this.state.customKeysChecked}
onChange={this.updateCustomMentionKeys}
/>
- {'Other non-case sensitive words, separated by commas:'}
+ <FormattedMessage
+ id='user.settings.notifications.sensitiveWords'
+ defaultMessage='Other non-case sensitive words, separated by commas:'
+ />
</label>
</div>
<input
@@ -563,7 +699,7 @@ export default class NotificationsTab extends React.Component {
keysSection = (
<SettingItemMax
- title='Words that trigger mentions'
+ title={formatMessage(holders.wordsTrigger)}
inputs={inputs}
submit={this.handleSubmit}
server_error={serverError}
@@ -601,7 +737,12 @@ export default class NotificationsTab extends React.Component {
if (describe.length > 0) {
describe = describe.substring(0, describe.length - 2);
} else {
- describe = 'No words configured';
+ describe = (
+ <FormattedMessage
+ id='user.settings.notifications.noWords'
+ defaultMessage='No words configured'
+ />
+ );
}
handleUpdateKeysSection = function updateKeysSection() {
@@ -610,7 +751,7 @@ export default class NotificationsTab extends React.Component {
keysSection = (
<SettingItemMin
- title='Words that trigger mentions'
+ title={formatMessage(holders.wordsTrigger)}
describe={describe}
updateSection={handleUpdateKeysSection}
/>
@@ -624,7 +765,7 @@ export default class NotificationsTab extends React.Component {
type='button'
className='close'
data-dismiss='modal'
- aria-label='Close'
+ aria-label={formatMessage(holders.close)}
onClick={this.props.closeModal}
>
<span aria-hidden='true'>{'×'}</span>
@@ -637,14 +778,22 @@ export default class NotificationsTab extends React.Component {
className='modal-back'
onClick={this.props.collapseModal}
/>
- {'Notification Settings'}
+ <FormattedMessage
+ id='user.settings.notifications.title'
+ defaultMessage='Notification Settings'
+ />
</h4>
</div>
<div
ref='wrapper'
className='user-settings'
>
- <h3 className='tab-header'>{'Notifications'}</h3>
+ <h3 className='tab-header'>
+ <FormattedMessage
+ id='user.settings.notifications.header'
+ defaultMessage='Notifications'
+ />
+ </h3>
<div className='divider-dark first'/>
{desktopSection}
<div className='divider-light'/>
@@ -667,6 +816,7 @@ NotificationsTab.defaultProps = {
activeTab: ''
};
NotificationsTab.propTypes = {
+ intl: intlShape.isRequired,
user: React.PropTypes.object,
updateSection: React.PropTypes.func,
updateTab: React.PropTypes.func,
@@ -675,3 +825,5 @@ NotificationsTab.propTypes = {
closeModal: React.PropTypes.func.isRequired,
collapseModal: React.PropTypes.func.isRequired
};
+
+export default injectIntl(NotificationsTab); \ No newline at end of file
diff --git a/web/react/components/user_settings/user_settings_security.jsx b/web/react/components/user_settings/user_settings_security.jsx
index 5a21abd19..0c9e722de 100644
--- a/web/react/components/user_settings/user_settings_security.jsx
+++ b/web/react/components/user_settings/user_settings_security.jsx
@@ -13,7 +13,40 @@ import * as Client from '../../utils/client.jsx';
import * as AsyncClient from '../../utils/async_client.jsx';
import Constants from '../../utils/constants.jsx';
-export default class SecurityTab extends React.Component {
+import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl';
+
+const holders = defineMessages({
+ currentPasswordError: {
+ id: 'user.settings.security.currentPasswordError',
+ defaultMessage: 'Please enter your current password'
+ },
+ passwordLengthError: {
+ id: 'user.settings.security.passwordLengthError',
+ defaultMessage: 'New passwords must be at least {chars} characters'
+ },
+ passwordMatchError: {
+ id: 'user.settings.security.passwordMatchError',
+ defaultMessage: 'The new passwords you entered do not match'
+ },
+ password: {
+ id: 'user.settings.security.password',
+ defaultMessage: 'Password'
+ },
+ lastUpdated: {
+ id: 'user.settings.security.lastUpdated',
+ defaultMessage: 'Last updated {date} at {time}'
+ },
+ method: {
+ id: 'user.settings.security.method',
+ defaultMessage: 'Sign-in Method'
+ },
+ close: {
+ id: 'user.settings.security.close',
+ defaultMessage: 'Close'
+ }
+});
+
+class SecurityTab extends React.Component {
constructor(props) {
super(props);
@@ -43,18 +76,19 @@ export default class SecurityTab extends React.Component {
var newPassword = this.state.newPassword;
var confirmPassword = this.state.confirmPassword;
+ const {formatMessage} = this.props.intl;
if (currentPassword === '') {
- this.setState({passwordError: 'Please enter your current password', serverError: ''});
+ this.setState({passwordError: formatMessage(holders.currentPasswordError), serverError: ''});
return;
}
if (newPassword.length < Constants.MIN_PASSWORD_LENGTH) {
- this.setState({passwordError: 'New passwords must be at least ' + Constants.MIN_PASSWORD_LENGTH + ' characters', serverError: ''});
+ this.setState({passwordError: formatMessage(holders.passwordLengthError, {chars: Constants.MIN_PASSWORD_LENGTH}), serverError: ''});
return;
}
if (newPassword !== confirmPassword) {
- this.setState({passwordError: 'The new passwords you entered do not match', serverError: ''});
+ this.setState({passwordError: formatMessage(holders.passwordMatchError), serverError: ''});
return;
}
@@ -92,6 +126,7 @@ export default class SecurityTab extends React.Component {
}
createPasswordSection() {
let updateSectionStatus;
+ const {formatMessage} = this.props.intl;
if (this.props.activeSection === 'password' && this.props.user.auth_service === '') {
const inputs = [];
@@ -101,7 +136,12 @@ export default class SecurityTab extends React.Component {
key='currentPasswordUpdateForm'
className='form-group'
>
- <label className='col-sm-5 control-label'>{'Current Password'}</label>
+ <label className='col-sm-5 control-label'>
+ <FormattedMessage
+ id='user.settings.security.currentPassword'
+ defaultMessage='Current Password'
+ />
+ </label>
<div className='col-sm-7'>
<input
className='form-control'
@@ -117,7 +157,12 @@ export default class SecurityTab extends React.Component {
key='newPasswordUpdateForm'
className='form-group'
>
- <label className='col-sm-5 control-label'>{'New Password'}</label>
+ <label className='col-sm-5 control-label'>
+ <FormattedMessage
+ id='user.settings.security.newPassword'
+ defaultMessage='New Password'
+ />
+ </label>
<div className='col-sm-7'>
<input
className='form-control'
@@ -133,7 +178,12 @@ export default class SecurityTab extends React.Component {
key='retypeNewPasswordUpdateForm'
className='form-group'
>
- <label className='col-sm-5 control-label'>{'Retype New Password'}</label>
+ <label className='col-sm-5 control-label'>
+ <FormattedMessage
+ id='user.settings.security.retypePassword'
+ defaultMessage='Retype New Password'
+ />
+ </label>
<div className='col-sm-7'>
<input
className='form-control'
@@ -153,7 +203,7 @@ export default class SecurityTab extends React.Component {
return (
<SettingItemMax
- title='Password'
+ title={formatMessage(holders.password)}
inputs={inputs}
submit={this.submitPassword}
server_error={this.state.serverError}
@@ -165,20 +215,16 @@ export default class SecurityTab extends React.Component {
var describe;
var d = new Date(this.props.user.last_password_update);
- var hour = '12';
- if (d.getHours() % 12) {
- hour = String(d.getHours() % 12);
- }
- var min = String(d.getMinutes());
- if (d.getMinutes() < 10) {
- min = '0' + d.getMinutes();
- }
var timeOfDay = ' am';
if (d.getHours() >= 12) {
timeOfDay = ' pm';
}
- describe = 'Last updated ' + Constants.MONTHS[d.getMonth()] + ' ' + d.getDate() + ', ' + d.getFullYear() + ' at ' + hour + ':' + min + timeOfDay;
+ const locale = global.window.mm_locale;
+ describe = formatMessage(holders.lastUpdated, {
+ date: d.toLocaleDateString(locale, {month: 'short', day: '2-digit', year: 'numeric'}),
+ time: d.toLocaleTimeString(locale, {hours12: true, hour: '2-digit', minute: '2-digit'}) + timeOfDay
+ });
updateSectionStatus = function updateSection() {
this.props.updateSection('password');
@@ -186,7 +232,7 @@ export default class SecurityTab extends React.Component {
return (
<SettingItemMin
- title='Password'
+ title={formatMessage(holders.password)}
describe={describe}
updateSection={updateSectionStatus}
/>
@@ -208,7 +254,10 @@ export default class SecurityTab extends React.Component {
className='btn btn-primary'
href={'/' + teamName + '/claim?email=' + encodeURIComponent(user.email)}
>
- {'Switch to using email and password'}
+ <FormattedMessage
+ id='user.settings.security.switchEmail'
+ defaultMessage='Switch to using email and password'
+ />
</a>
<br/>
</div>
@@ -223,7 +272,10 @@ export default class SecurityTab extends React.Component {
className='btn btn-primary'
href={'/' + teamName + '/claim?email=' + encodeURIComponent(user.email) + '&new_type=' + Constants.GITLAB_SERVICE}
>
- {'Switch to using GitLab SSO'}
+ <FormattedMessage
+ id='user.settings.security.switchGitlab'
+ defaultMessage='Switch to using GitLab SSO'
+ />
</a>
<br/>
</div>
@@ -238,7 +290,10 @@ export default class SecurityTab extends React.Component {
className='btn btn-primary'
href={'/' + teamName + '/claim?email=' + encodeURIComponent(user.email) + '&new_type=' + Constants.GOOGLE_SERVICE}
>
- {'Switch to using Google SSO'}
+ <FormattedMessage
+ id='user.settings.security.switchGoogle'
+ defaultMessage='Switch to using Google SSO'
+ />
</a>
<br/>
</div>
@@ -260,11 +315,18 @@ export default class SecurityTab extends React.Component {
e.preventDefault();
}.bind(this);
- const extraInfo = <span>{'You may only have one sign-in method at a time. Switching sign-in method will send an email notifying you if the change was successful.'}</span>;
+ const extraInfo = (
+ <span>
+ <FormattedMessage
+ id='user.settings.security.oneSignin'
+ defaultMessage='You may only have one sign-in method at a time. Switching sign-in method will send an email notifying you if the change was successful.'
+ />
+ </span>
+ );
return (
<SettingItemMax
- title='Sign-in Method'
+ title={this.props.intl.formatMessage(holders.method)}
extraInfo={extraInfo}
inputs={inputs}
server_error={this.state.serverError}
@@ -277,14 +339,24 @@ export default class SecurityTab extends React.Component {
this.props.updateSection('signin');
}.bind(this);
- let describe = 'Email and Password';
+ let describe = (
+ <FormattedMessage
+ id='user.settings.security.emailPwd'
+ defaultMessage='Email and Password'
+ />
+ );
if (this.props.user.auth_service === Constants.GITLAB_SERVICE) {
- describe = 'GitLab SSO';
+ describe = (
+ <FormattedMessage
+ id='user.settings.security.gitlab'
+ defaultMessage='GitLab SSO'
+ />
+ );
}
return (
<SettingItemMin
- title='Sign-in Method'
+ title={this.props.intl.formatMessage(holders.method)}
describe={describe}
updateSection={updateSectionStatus}
/>
@@ -309,7 +381,7 @@ export default class SecurityTab extends React.Component {
type='button'
className='close'
data-dismiss='modal'
- aria-label='Close'
+ aria-label={this.props.intl.formatMessage(holders.close)}
onClick={this.props.closeModal}
>
<span aria-hidden='true'>{'×'}</span>
@@ -322,11 +394,19 @@ export default class SecurityTab extends React.Component {
className='modal-back'
onClick={this.props.collapseModal}
/>
- {'Security Settings'}
+ <FormattedMessage
+ id='user.settings.security.title'
+ defaultMessage='Security Settings'
+ />
</h4>
</div>
<div className='user-settings'>
- <h3 className='tab-header'>{'Security Settings'}</h3>
+ <h3 className='tab-header'>
+ <FormattedMessage
+ id='user.settings.security.title'
+ defaultMessage='Security Settings'
+ />
+ </h3>
<div className='divider-dark first'/>
{passwordSection}
<div className='divider-light'/>
@@ -337,14 +417,22 @@ export default class SecurityTab extends React.Component {
className='security-links theme'
dialogType={AccessHistoryModal}
>
- <i className='fa fa-clock-o'></i>{'View Access History'}
+ <i className='fa fa-clock-o'></i>
+ <FormattedMessage
+ id='user.settings.security.viewHistory'
+ defaultMessage='View Access History'
+ />
</ToggleModalButton>
<b> </b>
<ToggleModalButton
className='security-links theme'
dialogType={ActivityLogModal}
>
- <i className='fa fa-clock-o'></i>{'View and Logout of Active Sessions'}
+ <i className='fa fa-clock-o'></i>
+ <FormattedMessage
+ id='user.settings.security.logoutActiveSessions'
+ defaultMessage='View and Logout of Active Sessions'
+ />
</ToggleModalButton>
</div>
</div>
@@ -357,6 +445,7 @@ SecurityTab.defaultProps = {
activeSection: ''
};
SecurityTab.propTypes = {
+ intl: intlShape.isRequired,
user: React.PropTypes.object,
activeSection: React.PropTypes.string,
updateSection: React.PropTypes.func,
@@ -365,3 +454,5 @@ SecurityTab.propTypes = {
collapseModal: React.PropTypes.func.isRequired,
setEnforceFocus: React.PropTypes.func.isRequired
};
+
+export default injectIntl(SecurityTab); \ No newline at end of file