summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Makefile6
-rw-r--r--api/user.go34
-rw-r--r--api/user_test.go20
-rw-r--r--config/config.json2
-rw-r--r--doc/developer/tests/test-attachments.md7
-rw-r--r--docker/dev/config_docker.json2
-rw-r--r--docker/local/config_docker.json2
-rw-r--r--i18n/en.json34
-rw-r--r--i18n/es.json34
-rw-r--r--mattermost.go2
-rw-r--r--model/client.go8
-rw-r--r--model/config.go17
-rw-r--r--utils/config.go2
-rw-r--r--web/react/components/admin_console/email_settings.jsx84
-rw-r--r--web/react/components/center_panel.jsx2
-rw-r--r--web/react/components/channel_header.jsx94
-rw-r--r--web/react/components/channel_info_modal.jsx57
-rw-r--r--web/react/components/channel_invite_modal.jsx15
-rw-r--r--web/react/components/channel_members_modal.jsx21
-rw-r--r--web/react/components/channel_notifications_modal.jsx111
-rw-r--r--web/react/components/delete_channel_modal.jsx45
-rw-r--r--web/react/components/delete_post_modal.jsx53
-rw-r--r--web/react/components/edit_channel_header_modal.jsx45
-rw-r--r--web/react/components/edit_channel_purpose_modal.jsx72
-rw-r--r--web/react/components/edit_post_modal.jsx40
-rw-r--r--web/react/components/get_link_modal.jsx23
-rw-r--r--web/react/components/get_team_invite_link_modal.jsx12
-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_item.jsx28
-rw-r--r--web/react/components/msg_typing.jsx6
-rw-r--r--web/react/components/popover_list_members.jsx15
-rw-r--r--web/react/components/post.jsx1
-rw-r--r--web/react/components/post_deleted_modal.jsx20
-rw-r--r--web/react/components/post_header.jsx7
-rw-r--r--web/react/components/post_info.jsx7
-rw-r--r--web/react/components/removed_from_channel_modal.jsx41
-rw-r--r--web/react/components/rename_channel_modal.jsx91
-rw-r--r--web/react/components/setting_item_max.jsx2
-rw-r--r--web/react/components/setting_item_min.jsx4
-rw-r--r--web/react/components/signup_user_complete.jsx10
-rw-r--r--web/react/components/team_general_tab.jsx4
-rw-r--r--web/react/components/textbox.jsx10
-rw-r--r--web/react/components/time_since.jsx17
-rw-r--r--web/react/stores/user_store.jsx10
-rw-r--r--web/react/utils/client.jsx22
-rw-r--r--web/react/utils/constants.jsx3
-rw-r--r--web/sass-files/sass/partials/_post.scss24
-rw-r--r--web/static/i18n/en.json103
-rw-r--r--web/static/i18n/es.json90
50 files changed, 1378 insertions, 177 deletions
diff --git a/Makefile b/Makefile
index 9c4e6ee1f..eccdf39ba 100644
--- a/Makefile
+++ b/Makefile
@@ -151,7 +151,7 @@ package:
sed -i'.bak' 's|bundle.js|bundle-$(BUILD_NUMBER).min.js|g' $(DIST_PATH)/web/templates/head.html
sed -i'.bak' 's|libs.min.js|libs-$(BUILD_NUMBER).min.js|g' $(DIST_PATH)/web/templates/head.html
rm $(DIST_PATH)/web/templates/*.bak
-
+
sudo mv -f $(DIST_PATH)/config/config.json.bak $(DIST_PATH)/config/config.json || echo 'nomv'
tar -C dist -czf $(DIST_PATH).tar.gz mattermost
@@ -283,7 +283,7 @@ run: start-docker .prepare-go .prepare-jsx
$(GO) run $(GOFLAGS) mattermost.go -config=config.json &
@echo Starting compass watch
- cd web/sass-files && compass watch &
+ cd web/sass-files && compass compile && compass watch &
stop:
@for PID in $$(ps -ef | grep [c]ompass | awk '{ print $$2 }'); do \
@@ -296,7 +296,7 @@ stop:
kill $$PID; \
done
- @for PID in $$(ps -ef | grep [m]atterm | awk '{ print $$2 }'); do \
+ @for PID in $$(ps -ef | grep [m]atterm | grep -v VirtualBox | awk '{ print $$2 }'); do \
echo stopping go web $$PID; \
kill $$PID; \
done
diff --git a/api/user.go b/api/user.go
index 6ad0f67ac..91c8c022a 100644
--- a/api/user.go
+++ b/api/user.go
@@ -444,6 +444,38 @@ func LoginByEmail(c *Context, w http.ResponseWriter, r *http.Request, email, nam
return nil
}
+func LoginByUsername(c *Context, w http.ResponseWriter, r *http.Request, username, name, password, deviceId string) *model.User {
+ var team *model.Team
+
+ if result := <-Srv.Store.Team().GetByName(name); result.Err != nil {
+ c.Err = result.Err
+ return nil
+ } else {
+ team = result.Data.(*model.Team)
+ }
+
+ if result := <-Srv.Store.User().GetByUsername(team.Id, username); result.Err != nil {
+ c.Err = result.Err
+ c.Err.StatusCode = http.StatusForbidden
+ return nil
+ } else {
+ user := result.Data.(*model.User)
+
+ if len(user.AuthData) != 0 {
+ c.Err = model.NewLocAppError("LoginByUsername", "api.user.login_by_email.sign_in.app_error",
+ map[string]interface{}{"AuthService": user.AuthService}, "")
+ return nil
+ }
+
+ if checkUserLoginAttempts(c, user) && checkUserPassword(c, user, password) {
+ Login(c, w, r, user, deviceId)
+ return user
+ }
+ }
+
+ return nil
+}
+
func LoginByOAuth(c *Context, w http.ResponseWriter, r *http.Request, service string, userData io.ReadCloser, team *model.Team) *model.User {
authData := ""
provider := einterfaces.GetOauthProvider(service)
@@ -629,6 +661,8 @@ func login(c *Context, w http.ResponseWriter, r *http.Request) {
user = LoginById(c, w, r, props["id"], props["password"], props["device_id"])
} else if len(props["email"]) != 0 && len(props["name"]) != 0 {
user = LoginByEmail(c, w, r, props["email"], props["name"], props["password"], props["device_id"])
+ } else if len(props["username"]) != 0 && len(props["name"]) != 0 {
+ user = LoginByUsername(c, w, r, props["username"], props["name"], props["password"], props["device_id"])
} else {
c.Err = model.NewLocAppError("login", "api.user.login.not_provided.app_error", nil, "")
c.Err.StatusCode = http.StatusForbidden
diff --git a/api/user_test.go b/api/user_test.go
index b2ae113f1..1a1cf9634 100644
--- a/api/user_test.go
+++ b/api/user_test.go
@@ -99,7 +99,7 @@ func TestLogin(t *testing.T) {
team := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
rteam, _ := Client.CreateTeam(&team)
- user := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Username: "corey", Password: "pwd"}
ruser, _ := Client.CreateUser(&user, "")
store.Must(Srv.Store.User().VerifyEmail(ruser.Data.(*model.User).Id))
@@ -107,7 +107,7 @@ func TestLogin(t *testing.T) {
t.Fatal(err)
} else {
if result.Data.(*model.User).Email != user.Email {
- t.Fatal("email's didn't match")
+ t.Fatal("emails didn't match")
}
}
@@ -119,14 +119,30 @@ func TestLogin(t *testing.T) {
}
}
+ if result, err := Client.LoginByUsername(team.Name, user.Username, user.Password); err != nil {
+ t.Fatal(err)
+ } else {
+ if result.Data.(*model.User).Email != user.Email {
+ t.Fatal("emails didn't match")
+ }
+ }
+
if _, err := Client.LoginByEmail(team.Name, user.Email, user.Password+"invalid"); err == nil {
t.Fatal("Invalid Password")
}
+ if _, err := Client.LoginByUsername(team.Name, user.Username, user.Password+"invalid"); err == nil {
+ t.Fatal("Invalid Password")
+ }
+
if _, err := Client.LoginByEmail(team.Name, "", user.Password); err == nil {
t.Fatal("should have failed")
}
+ if _, err := Client.LoginByUsername(team.Name, "", user.Password); err == nil {
+ t.Fatal("should have failed")
+ }
+
authToken := Client.AuthToken
Client.AuthToken = "invalid"
diff --git a/config/config.json b/config/config.json
index 076f795cc..560073ad2 100644
--- a/config/config.json
+++ b/config/config.json
@@ -66,6 +66,8 @@
},
"EmailSettings": {
"EnableSignUpWithEmail": true,
+ "EnableSignInWithEmail": true,
+ "EnableSignInWithUsername": false,
"SendEmailNotifications": false,
"RequireEmailVerification": false,
"FeedbackName": "",
diff --git a/doc/developer/tests/test-attachments.md b/doc/developer/tests/test-attachments.md
index e2fda0eb6..75a2285f8 100644
--- a/doc/developer/tests/test-attachments.md
+++ b/doc/developer/tests/test-attachments.md
@@ -7,8 +7,9 @@ This test contains instructions for the core team to manually test common attach
**Notes:**
- All file types should upload and post.
-- Read the expected for details on the behavior of the thumbnail and preview window.
+- Read the expected for details on the behavior of the thumbnail and preview window.
- The expected behavior of video and audio formats depends on the operating system, browser and plugins. View the permalinks to the Public Test Channel on Pre-Release Core to see the expected cases.
+- If the browser can play the media file, media player controls should appear. If the browser cannot play the file, it should show appear as a regular attachment without the media controls.
### Images
@@ -72,7 +73,7 @@ Expected: Generic Word thumbnail & preview window.
**MP4**
`Videos/MP4.mp4`
-Expected: Generic video thumbnail & playable preview window. View Permalink.
+Expected: Generic video thumbnail, view Permalink for preview window behavior. Expected depends on the operating system, browser and plugins.
[Permalink](https://pre-release.mattermost.com/core/pl/5dx5qx9t9brqfnhohccxjynx7c)
**AVI**
@@ -114,7 +115,7 @@ Expected: Generic audio thumbnail & playable preview window
**M4A**
`Audio/M4a.m4a`
-Expected: Generic audio thumbnail & playable preview window
+Expected: Generic audio thumbnail, view Permalink for preview window behavior. Expected depends on the operating system, browser and plugins.
[Permalink](https://pre-release.mattermost.com/core/pl/6c7qsw48ybd88bktgeykodsrrc)
**AAC**
diff --git a/docker/dev/config_docker.json b/docker/dev/config_docker.json
index 1aa2ee843..80b99b66d 100644
--- a/docker/dev/config_docker.json
+++ b/docker/dev/config_docker.json
@@ -66,6 +66,8 @@
},
"EmailSettings": {
"EnableSignUpWithEmail": true,
+ "EnableSignInWithEmail": true,
+ "EnableSignInWithUsername": false,
"SendEmailNotifications": false,
"RequireEmailVerification": false,
"FeedbackName": "",
diff --git a/docker/local/config_docker.json b/docker/local/config_docker.json
index 1aa2ee843..80b99b66d 100644
--- a/docker/local/config_docker.json
+++ b/docker/local/config_docker.json
@@ -66,6 +66,8 @@
},
"EmailSettings": {
"EnableSignUpWithEmail": true,
+ "EnableSignInWithEmail": true,
+ "EnableSignInWithUsername": false,
"SendEmailNotifications": false,
"RequireEmailVerification": false,
"FeedbackName": "",
diff --git a/i18n/en.json b/i18n/en.json
index b1848e163..2d86e1ee5 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -1648,6 +1648,38 @@
"translation": "Inappropriate permissions to regenerate outcoming webhook token"
},
{
+ "id": "ent.ldap.do_login.bind_admin_user.app_error",
+ "translation": "Unable to bind to LDAP server. Check BindUsername and BindPassword."
+ },
+ {
+ "id": "ent.ldap.do_login.invalid_password.app_error",
+ "translation": "Invalid Password"
+ },
+ {
+ "id": "ent.ldap.do_login.licence_disable.app_error",
+ "translation": "LDAP functionality disabled by current license. Please contact your system administrator about upgrading your enterprise license."
+ },
+ {
+ "id": "ent.ldap.do_login.matched_to_many_users.app_error",
+ "translation": "Username given matches multiple users"
+ },
+ {
+ "id": "ent.ldap.do_login.search_ldap_server.app_error",
+ "translation": "Failed to search LDAP server"
+ },
+ {
+ "id": "ent.ldap.do_login.unable_to_connect.app_error",
+ "translation": "Unable to connect to LDAP server"
+ },
+ {
+ "id": "ent.ldap.do_login.unable_to_create_user.app_error",
+ "translation": "Credentials valid but unable to create user."
+ },
+ {
+ "id": "ent.ldap.do_login.user_not_registered.app_error",
+ "translation": "User not registered on LDAP server"
+ },
+ {
"id": "manaultesting.get_channel_id.no_found.debug",
"translation": "Could not find channel: %v, %v possibilites searched"
},
@@ -3415,4 +3447,4 @@
"id": "web.watcher_fail.error",
"translation": "Failed to add directory to watcher %v"
}
-]
+] \ No newline at end of file
diff --git a/i18n/es.json b/i18n/es.json
index f0f157474..bd29c9860 100644
--- a/i18n/es.json
+++ b/i18n/es.json
@@ -1648,6 +1648,38 @@
"translation": "Permisos inapropiados para regenerar un token para el Webhook saliente"
},
{
+ "id": "ent.ldap.do_login.bind_admin_user.app_error",
+ "translation": "No se pudo enlazar con el servidor LDAP. Revisa las opciones de BindUsername y BindPassword."
+ },
+ {
+ "id": "ent.ldap.do_login.invalid_password.app_error",
+ "translation": "Contraseña inválida"
+ },
+ {
+ "id": "ent.ldap.do_login.licence_disable.app_error",
+ "translation": "Las funcionalidades de LDAP están deshabilitadas con la licencia actual. Por favor contacta a un administrador del sistema acerca de mejorar la licencia enterprise."
+ },
+ {
+ "id": "ent.ldap.do_login.matched_to_many_users.app_error",
+ "translation": "Nombre de usuario dado coincide con varios usuarios"
+ },
+ {
+ "id": "ent.ldap.do_login.search_ldap_server.app_error",
+ "translation": "Falla al buscar en el servidor LDAP"
+ },
+ {
+ "id": "ent.ldap.do_login.unable_to_connect.app_error",
+ "translation": "No se pudo conectar con el servidor LDAP"
+ },
+ {
+ "id": "ent.ldap.do_login.unable_to_create_user.app_error",
+ "translation": "Credenciales válidas pero no se pudo crear el usuario."
+ },
+ {
+ "id": "ent.ldap.do_login.user_not_registered.app_error",
+ "translation": "Usuario no registrado en el servidor LDAP"
+ },
+ {
"id": "manaultesting.get_channel_id.no_found.debug",
"translation": "No pudimos encontrar el canal: %v, búsqueda realizada con estas posibilidades %v"
},
@@ -3407,4 +3439,4 @@
"id": "web.watcher_fail.error",
"translation": "Falla al agregar el directorio a ser vigilado %v"
}
-]
+] \ No newline at end of file
diff --git a/mattermost.go b/mattermost.go
index b6652d812..43fa06601 100644
--- a/mattermost.go
+++ b/mattermost.go
@@ -31,6 +31,8 @@ import (
_ "github.com/go-ldap/ldap"
)
+//ENTERPRISE_IMPORTS
+
var flagCmdCreateTeam bool
var flagCmdCreateUser bool
var flagCmdAssignRole bool
diff --git a/model/client.go b/model/client.go
index d31ac1592..3b72f65e4 100644
--- a/model/client.go
+++ b/model/client.go
@@ -280,6 +280,14 @@ func (c *Client) LoginByEmail(name string, email string, password string) (*Resu
return c.login(m)
}
+func (c *Client) LoginByUsername(name string, username string, password string) (*Result, *AppError) {
+ m := make(map[string]string)
+ m["name"] = name
+ m["username"] = username
+ m["password"] = password
+ return c.login(m)
+}
+
func (c *Client) LoginByEmailWithDevice(name string, email string, password string, deviceId string) (*Result, *AppError) {
m := make(map[string]string)
m["name"] = name
diff --git a/model/config.go b/model/config.go
index 5c8604ff1..a6d1c21dc 100644
--- a/model/config.go
+++ b/model/config.go
@@ -97,6 +97,8 @@ type FileSettings struct {
type EmailSettings struct {
EnableSignUpWithEmail bool
+ EnableSignInWithEmail *bool
+ EnableSignInWithUsername *bool
SendEmailNotifications bool
RequireEmailVerification bool
FeedbackName string
@@ -258,6 +260,21 @@ func (o *Config) SetDefaults() {
*o.TeamSettings.EnableTeamListing = false
}
+ if o.EmailSettings.EnableSignInWithEmail == nil {
+ o.EmailSettings.EnableSignInWithEmail = new(bool)
+
+ if o.EmailSettings.EnableSignUpWithEmail == true {
+ *o.EmailSettings.EnableSignInWithEmail = true
+ } else {
+ *o.EmailSettings.EnableSignInWithEmail = false
+ }
+ }
+
+ if o.EmailSettings.EnableSignInWithUsername == nil {
+ o.EmailSettings.EnableSignInWithUsername = new(bool)
+ *o.EmailSettings.EnableSignInWithUsername = false
+ }
+
if o.EmailSettings.SendPushNotifications == nil {
o.EmailSettings.SendPushNotifications = new(bool)
*o.EmailSettings.SendPushNotifications = false
diff --git a/utils/config.go b/utils/config.go
index 9d2c2f588..e9b7e1878 100644
--- a/utils/config.go
+++ b/utils/config.go
@@ -208,6 +208,8 @@ func getClientConfig(c *model.Config) map[string]string {
props["SendEmailNotifications"] = strconv.FormatBool(c.EmailSettings.SendEmailNotifications)
props["EnableSignUpWithEmail"] = strconv.FormatBool(c.EmailSettings.EnableSignUpWithEmail)
+ props["EnableSignInWithEmail"] = strconv.FormatBool(*c.EmailSettings.EnableSignInWithEmail)
+ props["EnableSignInWithUsername"] = strconv.FormatBool(*c.EmailSettings.EnableSignInWithUsername)
props["RequireEmailVerification"] = strconv.FormatBool(c.EmailSettings.RequireEmailVerification)
props["FeedbackEmail"] = c.EmailSettings.FeedbackEmail
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/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/channel_header.jsx b/web/react/components/channel_header.jsx
index f64834775..005a82209 100644
--- a/web/react/components/channel_header.jsx
+++ b/web/react/components/channel_header.jsx
@@ -24,8 +24,10 @@ import * as TextFormatting from '../utils/text_formatting.jsx';
import * as AsyncClient from '../utils/async_client.jsx';
import * as Client from '../utils/client.jsx';
import Constants from '../utils/constants.jsx';
-const ActionTypes = Constants.ActionTypes;
+import {FormattedMessage} from 'mm-intl';
+
+const ActionTypes = Constants.ActionTypes;
const Popover = ReactBootstrap.Popover;
const OverlayTrigger = ReactBootstrap.OverlayTrigger;
const Tooltip = ReactBootstrap.Tooltip;
@@ -124,7 +126,14 @@ export default class ChannelHeader extends React.Component {
}
const channel = this.state.channel;
- const recentMentionsTooltip = <Tooltip id='recentMentionsTooltip'>{'Recent Mentions'}</Tooltip>;
+ const recentMentionsTooltip = (
+ <Tooltip id='recentMentionsTooltip'>
+ <FormattedMessage
+ id='channel_header.recentMentions'
+ defaultMessage='Recent Mentions'
+ />
+ </Tooltip>
+ );
const popoverContent = (
<Popover
id='hader-popover'
@@ -157,9 +166,19 @@ export default class ChannelHeader extends React.Component {
}
}
- let channelTerm = 'Channel';
- if (channel.type === 'P') {
- channelTerm = 'Group';
+ let channelTerm = (
+ <FormattedMessage
+ id='channel_header.channel'
+ defaultMessage='Channel'
+ />
+ );
+ if (channel.type === Constants.PRIVATE_CHANNEL) {
+ channelTerm = (
+ <FormattedMessage
+ id='channel_header.group'
+ defaultMessage='Group'
+ />
+ );
}
const dropdownContents = [];
@@ -174,7 +193,10 @@ export default class ChannelHeader extends React.Component {
dialogType={EditChannelHeaderModal}
dialogProps={{channel}}
>
- {'Set Channel Header...'}
+ <FormattedMessage
+ id='channel_header.channelHeader'
+ defaultMessage='Set Channel Header...'
+ />
</ToggleModalButton>
</li>
);
@@ -189,7 +211,10 @@ export default class ChannelHeader extends React.Component {
dialogType={ChannelInfoModal}
dialogProps={{channel}}
>
- {'View Info'}
+ <FormattedMessage
+ id='channel_header.viewInfo'
+ defaultMessage='View Info'
+ />
</ToggleModalButton>
</li>
);
@@ -205,7 +230,10 @@ export default class ChannelHeader extends React.Component {
dialogType={ChannelInviteModal}
dialogProps={{channel}}
>
- {'Add Members'}
+ <FormattedMessage
+ id='chanel_header.addMembers'
+ defaultMessage='Add Members'
+ />
</ToggleModalButton>
</li>
);
@@ -221,7 +249,10 @@ export default class ChannelHeader extends React.Component {
href='#'
onClick={() => this.setState({showMembersModal: true})}
>
- {'Manage Members'}
+ <FormattedMessage
+ id='channel_header.manageMembers'
+ defaultMessage='Manage Members'
+ />
</a>
</li>
);
@@ -238,7 +269,13 @@ export default class ChannelHeader extends React.Component {
dialogType={EditChannelHeaderModal}
dialogProps={{channel}}
>
- {`Set ${channelTerm} Header...`}
+ <FormattedMessage
+ id='channel_header.setHeader'
+ defaultMessage='Set {term} Header...'
+ values={{
+ term: (channelTerm)
+ }}
+ />
</ToggleModalButton>
</li>
);
@@ -252,7 +289,13 @@ export default class ChannelHeader extends React.Component {
href='#'
onClick={() => this.setState({showEditChannelPurposeModal: true})}
>
- {'Set '}{channelTerm}{' Purpose...'}
+ <FormattedMessage
+ id='channel_header.setPurpose'
+ defaultMessage='Set {term} Purpose...'
+ values={{
+ term: (channelTerm)
+ }}
+ />
</a>
</li>
);
@@ -266,7 +309,10 @@ export default class ChannelHeader extends React.Component {
dialogType={ChannelNotificationsModal}
dialogProps={{channel}}
>
- {'Notification Preferences'}
+ <FormattedMessage
+ id='channel_header.notificationPreferences'
+ defaultMessage='Notification Preferences'
+ />
</ToggleModalButton>
</li>
);
@@ -286,7 +332,13 @@ export default class ChannelHeader extends React.Component {
data-name={channel.name}
data-channelid={channel.id}
>
- {'Rename '}{channelTerm}{'...'}
+ <FormattedMessage
+ id='channel_header.rename'
+ defaultMessage='Rename {term}...'
+ values={{
+ term: (channelTerm)
+ }}
+ />
</a>
</li>
);
@@ -302,7 +354,13 @@ export default class ChannelHeader extends React.Component {
dialogType={DeleteChannelModal}
dialogProps={{channel}}
>
- {'Delete '}{channelTerm}{'...'}
+ <FormattedMessage
+ id='channel_header.delete'
+ defaultMessage='Delete {term}...'
+ values={{
+ term: (channelTerm)
+ }}
+ />
</ToggleModalButton>
</li>
);
@@ -320,7 +378,13 @@ export default class ChannelHeader extends React.Component {
href='#'
onClick={this.handleLeave}
>
- {'Leave '}{channelTerm}
+ <FormattedMessage
+ id='channel_header.leave'
+ defaultMessage='Leave {term}'
+ values={{
+ term: (channelTerm)
+ }}
+ />
</a>
</li>
);
diff --git a/web/react/components/channel_info_modal.jsx b/web/react/components/channel_info_modal.jsx
index 72c7c3daa..5067f5913 100644
--- a/web/react/components/channel_info_modal.jsx
+++ b/web/react/components/channel_info_modal.jsx
@@ -2,17 +2,28 @@
// See License.txt for license information.
import * as Utils from '../utils/utils.jsx';
+
+import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl';
+
const Modal = ReactBootstrap.Modal;
-export default class ChannelInfoModal extends React.Component {
+const holders = defineMessages({
+ notFound: {
+ id: 'channel_info.notFound',
+ defaultMessage: 'No Channel Found'
+ }
+});
+
+class ChannelInfoModal extends React.Component {
render() {
+ const {formatMessage} = this.props.intl;
let channel = this.props.channel;
if (!channel) {
channel = {
- display_name: 'No Channel Found',
- name: 'No Channel Found',
- purpose: 'No Channel Found',
- id: 'No Channel Found'
+ display_name: formatMessage(holders.notFound),
+ name: formatMessage(holders.notFound),
+ purpose: formatMessage(holders.notFound),
+ id: formatMessage(holders.notFound)
};
}
@@ -28,19 +39,39 @@ export default class ChannelInfoModal extends React.Component {
</Modal.Header>
<Modal.Body ref='modalBody'>
<div className='row form-group'>
- <div className='col-sm-3 info__label'>{'Channel Name:'}</div>
+ <div className='col-sm-3 info__label'>
+ <FormattedMessage
+ id='channel_info.name'
+ defaultMessage='Channel Name:'
+ />
+ </div>
<div className='col-sm-9'>{channel.display_name}</div>
</div>
<div className='row form-group'>
- <div className='col-sm-3 info__label'>{'Channel URL:'}</div>
+ <div className='col-sm-3 info__label'>
+ <FormattedMessage
+ id='channel_info.url'
+ defaultMessage='Channel URL:'
+ />
+ </div>
<div className='col-sm-9'>{channelURL}</div>
</div>
<div className='row'>
- <div className='col-sm-3 info__label'>{'Channel ID:'}</div>
+ <div className='col-sm-3 info__label'>
+ <FormattedMessage
+ id='channel_info.id'
+ defaultMessage='Channel ID:'
+ />
+ </div>
<div className='col-sm-9'>{channel.id}</div>
</div>
<div className='row'>
- <div className='col-sm-3 info__label'>{'Channel Purpose:'}</div>
+ <div className='col-sm-3 info__label'>
+ <FormattedMessage
+ id='channel_info.purpose'
+ defaultMessage='Channel Purpose:'
+ />
+ </div>
<div className='col-sm-9'>{channel.purpose}</div>
</div>
</Modal.Body>
@@ -50,7 +81,10 @@ export default class ChannelInfoModal extends React.Component {
className='btn btn-default'
onClick={this.props.onHide}
>
- {'Close'}
+ <FormattedMessage
+ id='channel_info.close'
+ defaultMessage='Close'
+ />
</button>
</Modal.Footer>
</Modal>
@@ -59,7 +93,10 @@ export default class ChannelInfoModal extends React.Component {
}
ChannelInfoModal.propTypes = {
+ intl: intlShape.isRequired,
show: React.PropTypes.bool.isRequired,
onHide: React.PropTypes.func.isRequired,
channel: React.PropTypes.object.isRequired
};
+
+export default injectIntl(ChannelInfoModal); \ No newline at end of file
diff --git a/web/react/components/channel_invite_modal.jsx b/web/react/components/channel_invite_modal.jsx
index 8b7485e5f..7dc2c0a11 100644
--- a/web/react/components/channel_invite_modal.jsx
+++ b/web/react/components/channel_invite_modal.jsx
@@ -11,6 +11,8 @@ import * as Utils from '../utils/utils.jsx';
import * as Client from '../utils/client.jsx';
import * as AsyncClient from '../utils/async_client.jsx';
+import {FormattedMessage} from 'mm-intl';
+
const Modal = ReactBootstrap.Modal;
export default class ChannelInviteModal extends React.Component {
@@ -154,7 +156,13 @@ export default class ChannelInviteModal extends React.Component {
onHide={this.props.onHide}
>
<Modal.Header closeButton={true}>
- <Modal.Title>{'Add New Members to '}<span className='name'>{this.props.channel.display_name}</span></Modal.Title>
+ <Modal.Title>
+ <FormattedMessage
+ id='channel_invite.addNewMembers'
+ defaultMessage='Add New Members to '
+ />
+ <span className='name'>{this.props.channel.display_name}</span>
+ </Modal.Title>
</Modal.Header>
<Modal.Body
ref='modalBody'
@@ -168,7 +176,10 @@ export default class ChannelInviteModal extends React.Component {
className='btn btn-default'
onClick={this.props.onHide}
>
- {'Close'}
+ <FormattedMessage
+ id='channel_invite.close'
+ defaultMessage='Close'
+ />
</button>
</Modal.Footer>
</Modal>
diff --git a/web/react/components/channel_members_modal.jsx b/web/react/components/channel_members_modal.jsx
index 513a720e7..f3cbef719 100644
--- a/web/react/components/channel_members_modal.jsx
+++ b/web/react/components/channel_members_modal.jsx
@@ -12,6 +12,8 @@ import * as AsyncClient from '../utils/async_client.jsx';
import * as Client from '../utils/client.jsx';
import * as Utils from '../utils/utils.jsx';
+import {FormattedMessage} from 'mm-intl';
+
const Modal = ReactBootstrap.Modal;
export default class ChannelMembersModal extends React.Component {
@@ -191,7 +193,13 @@ export default class ChannelMembersModal extends React.Component {
onHide={this.props.onModalDismissed}
>
<Modal.Header closeButton={true}>
- <Modal.Title><span className='name'>{this.props.channel.display_name}</span>{' Members'}</Modal.Title>
+ <Modal.Title>
+ <span className='name'>{this.props.channel.display_name}</span>
+ <FormattedMessage
+ id='channel_memebers_modal.members'
+ defaultMessage=' Members'
+ />
+ </Modal.Title>
<a
className='btn btn-md btn-primary'
href='#'
@@ -200,7 +208,11 @@ export default class ChannelMembersModal extends React.Component {
this.props.onModalDismissed();
}}
>
- <i className='glyphicon glyphicon-envelope'/>{' Add New Members'}
+ <i className='glyphicon glyphicon-envelope'/>
+ <FormattedMessage
+ id='channel_members_modal.addNew'
+ defaultMessage=' Add New Members'
+ />
</a>
</Modal.Header>
<Modal.Body
@@ -215,7 +227,10 @@ export default class ChannelMembersModal extends React.Component {
className='btn btn-default'
onClick={this.props.onModalDismissed}
>
- {'Close'}
+ <FormattedMessage
+ id='channel_members_modal.close'
+ defaultMessage='Close'
+ />
</button>
</Modal.Footer>
</Modal>
diff --git a/web/react/components/channel_notifications_modal.jsx b/web/react/components/channel_notifications_modal.jsx
index e70d3a634..59ef8966e 100644
--- a/web/react/components/channel_notifications_modal.jsx
+++ b/web/react/components/channel_notifications_modal.jsx
@@ -9,6 +9,8 @@ import * as Client from '../utils/client.jsx';
import UserStore from '../stores/user_store.jsx';
import ChannelStore from '../stores/channel_store.jsx';
+import {FormattedMessage} from 'mm-intl';
+
export default class ChannelNotificationsModal extends React.Component {
constructor(props) {
super(props);
@@ -97,13 +99,35 @@ export default class ChannelNotificationsModal extends React.Component {
let globalNotifyLevelName;
if (globalNotifyLevel === 'all') {
- globalNotifyLevelName = 'For all activity';
+ globalNotifyLevelName = (
+ <FormattedMessage
+ id='channel_notifications.allActivity'
+ defaultMessage='For all activity'
+ />
+ );
} else if (globalNotifyLevel === 'mention') {
- globalNotifyLevelName = 'Only for mentions';
+ globalNotifyLevelName = (
+ <FormattedMessage
+ id='channel_notifications.onlyMentions'
+ defaultMessage='Only for mentions'
+ />
+ );
} else {
- globalNotifyLevelName = 'Never';
+ globalNotifyLevelName = (
+ <FormattedMessage
+ id='channel_notifications.never'
+ defaultMessage='Never'
+ />
+ );
}
+ const sendDesktop = (
+ <FormattedMessage
+ id='channel_notifications.sendDesktop'
+ defaultMessage='Send desktop notifications'
+ />
+ );
+
if (this.state.activeSection === 'desktop') {
var notifyActive = [false, false, false, false];
if (this.state.notifyLevel === 'default') {
@@ -127,7 +151,13 @@ export default class ChannelNotificationsModal extends React.Component {
checked={notifyActive[0]}
onChange={this.handleUpdateNotifyLevel.bind(this, 'default')}
/>
- {`Global default (${globalNotifyLevelName})`}
+ <FormattedMessage
+ id='channel_notifications.globalDefault'
+ defaultMessage='Global default ({notifyLevel}'
+ values={{
+ notifyLevel: (globalNotifyLevelName)
+ }}
+ />
</label>
<br/>
</div>
@@ -138,7 +168,7 @@ export default class ChannelNotificationsModal extends React.Component {
checked={notifyActive[1]}
onChange={this.handleUpdateNotifyLevel.bind(this, 'all')}
/>
- {'For all activity'}
+ <FormattedMessage id='channel_notifications.allActivity' />
</label>
<br/>
</div>
@@ -149,7 +179,7 @@ export default class ChannelNotificationsModal extends React.Component {
checked={notifyActive[2]}
onChange={this.handleUpdateNotifyLevel.bind(this, 'mention')}
/>
- {'Only for mentions'}
+ <FormattedMessage id='channel_notifications.onlyMentions' />
</label>
<br/>
</div>
@@ -160,7 +190,7 @@ export default class ChannelNotificationsModal extends React.Component {
checked={notifyActive[3]}
onChange={this.handleUpdateNotifyLevel.bind(this, 'none')}
/>
- {'Never'}
+ <FormattedMessage id='channel_notifications.never' />
</label>
</div>
</div>
@@ -174,13 +204,16 @@ export default class ChannelNotificationsModal extends React.Component {
const extraInfo = (
<span>
- {'Selecting an option other than "Default" will override the global notification settings. Desktop notifications are available on Firefox, Safari, and Chrome.'}
+ <FormattedMessage
+ id='channel_notifications.override'
+ defaultMessage='Selecting an option other than "Default" will override the global notification settings. Desktop notifications are available on Firefox, Safari, and Chrome.'
+ />
</span>
);
return (
<SettingItemMax
- title='Send desktop notifications'
+ title={sendDesktop}
inputs={inputs}
submit={this.handleSubmitNotifyLevel}
server_error={serverError}
@@ -192,13 +225,20 @@ export default class ChannelNotificationsModal extends React.Component {
var describe;
if (this.state.notifyLevel === 'default') {
- describe = `Global default (${globalNotifyLevelName})`;
+ describe = (
+ <FormattedMessage
+ id='channel_notifications.globalDefault'
+ values={{
+ notifyLevel: (globalNotifyLevelName)
+ }}
+ />
+ );
} else if (this.state.notifyLevel === 'mention') {
- describe = 'Only for mentions';
+ describe = (<FormattedMessage id='channel_notifications.onlyMentions' />);
} else if (this.state.notifyLevel === 'all') {
- describe = 'For all activity';
+ describe = (<FormattedMessage id='channel_notifications.allActivity' />);
} else {
- describe = 'Never';
+ describe = (<FormattedMessage id='channel_notifications.never' />);
}
handleUpdateSection = function updateSection(e) {
@@ -208,7 +248,7 @@ export default class ChannelNotificationsModal extends React.Component {
return (
<SettingItemMin
- title='Send desktop notifications'
+ title={sendDesktop}
describe={describe}
updateSection={handleUpdateSection}
/>
@@ -250,6 +290,12 @@ export default class ChannelNotificationsModal extends React.Component {
createMarkUnreadLevelSection(serverError) {
let content;
+ const markUnread = (
+ <FormattedMessage
+ id='channel_notifications.markUnread'
+ defaultMessage='Mark Channel Unread'
+ />
+ );
if (this.state.activeSection === 'markUnreadLevel') {
const inputs = [(
<div key='channel-notification-unread-radio'>
@@ -260,7 +306,10 @@ export default class ChannelNotificationsModal extends React.Component {
checked={this.state.markUnreadLevel === 'all'}
onChange={this.handleUpdateMarkUnreadLevel.bind(this, 'all')}
/>
- {'For all unread messages'}
+ <FormattedMessage
+ id='channel_notifications.allUnread'
+ defaultMessage='For all unread messages'
+ />
</label>
<br />
</div>
@@ -271,7 +320,7 @@ export default class ChannelNotificationsModal extends React.Component {
checked={this.state.markUnreadLevel === 'mention'}
onChange={this.handleUpdateMarkUnreadLevel.bind(this, 'mention')}
/>
- {'Only for mentions'}
+ <FormattedMessage id='channel_notifications.onlyMentions' />
</label>
<br />
</div>
@@ -284,11 +333,18 @@ export default class ChannelNotificationsModal extends React.Component {
e.preventDefault();
}.bind(this);
- const extraInfo = <span>{'The channel name is bolded in the sidebar when there are unread messages. Selecting "Only for mentions" will bold the channel only when you are mentioned.'}</span>;
+ const extraInfo = (
+ <span>
+ <FormattedMessage
+ id='channel_notifications.unreadInfo'
+ defaultMessage='The channel name is bolded in the sidebar when there are unread messages. Selecting "Only for mentions" will bold the channel only when you are mentioned.'
+ />
+ </span>
+ );
content = (
<SettingItemMax
- title='Mark Channel Unread'
+ title={markUnread}
inputs={inputs}
submit={this.handleSubmitMarkUnreadLevel}
server_error={serverError}
@@ -300,9 +356,14 @@ export default class ChannelNotificationsModal extends React.Component {
let describe;
if (!this.state.markUnreadLevel || this.state.markUnreadLevel === 'all') {
- describe = 'For all unread messages';
+ describe = (
+ <FormattedMessage
+ id='channel_notifications.allUnread'
+ defaultMessage='For all unread messages'
+ />
+ );
} else {
- describe = 'Only for mentions';
+ describe = (<FormattedMessage id='channel_notifications.onlyMentions' />);
}
const handleUpdateSection = function handleUpdateSection(e) {
@@ -312,7 +373,7 @@ export default class ChannelNotificationsModal extends React.Component {
content = (
<SettingItemMin
- title='Mark Channel Unread'
+ title={markUnread}
describe={describe}
updateSection={handleUpdateSection}
/>
@@ -335,7 +396,13 @@ export default class ChannelNotificationsModal extends React.Component {
onHide={this.props.onHide}
>
<Modal.Header closeButton={true}>
- <Modal.Title>{'Notification Preferences for '}<span className='name'>{this.props.channel.display_name}</span></Modal.Title>
+ <Modal.Title>
+ <FormattedMessage
+ id='channel_notifications.preferences'
+ defaultMessage='Notification Preferences for '
+ />
+ <span className='name'>{this.props.channel.display_name}</span>
+ </Modal.Title>
</Modal.Header>
<Modal.Body>
<div className='settings-table'>
diff --git a/web/react/components/delete_channel_modal.jsx b/web/react/components/delete_channel_modal.jsx
index 1255067fd..d9113bc9f 100644
--- a/web/react/components/delete_channel_modal.jsx
+++ b/web/react/components/delete_channel_modal.jsx
@@ -5,7 +5,9 @@ import * as AsyncClient from '../utils/async_client.jsx';
import * as Client from '../utils/client.jsx';
const Modal = ReactBootstrap.Modal;
import TeamStore from '../stores/team_store.jsx';
-import * as Utils from '../utils/utils.jsx';
+import Constants from '../utils/constants.jsx';
+
+import {FormattedMessage} from 'mm-intl';
export default class DeleteChannelModal extends React.Component {
constructor(props) {
@@ -32,7 +34,20 @@ export default class DeleteChannelModal extends React.Component {
}
render() {
- const channelTerm = Utils.getChannelTerm(this.props.channel.type).toLowerCase();
+ let channelTerm = (
+ <FormattedMessage
+ id='delete_channel.channel'
+ defaultMessage='channel'
+ />
+ );
+ if (this.props.channel.type === Constants.PRIVATE_CHANNEL) {
+ channelTerm = (
+ <FormattedMessage
+ id='delete_channel.group'
+ defaultMessage='group'
+ />
+ );
+ }
return (
<Modal
@@ -40,10 +55,22 @@ export default class DeleteChannelModal extends React.Component {
onHide={this.props.onHide}
>
<Modal.Header closeButton={true}>
- <h4 className='modal-title'>{'Confirm DELETE Channel'}</h4>
+ <h4 className='modal-title'>
+ <FormattedMessage
+ id='delete_channel.confirm'
+ defaultMessage='Confirm DELETE Channel'
+ />
+ </h4>
</Modal.Header>
<Modal.Body>
- {`Are you sure you wish to delete the ${this.props.channel.display_name} ${channelTerm}?`}
+ <FormattedMessage
+ id='delete_channel.question'
+ defaultMessage='Are you sure you wish to delete the {display_name} {term}?'
+ values={{
+ display_name: this.props.channel.display_name,
+ term: (channelTerm)
+ }}
+ />
</Modal.Body>
<Modal.Footer>
<button
@@ -51,7 +78,10 @@ export default class DeleteChannelModal extends React.Component {
className='btn btn-default'
onClick={this.props.onHide}
>
- {'Cancel'}
+ <FormattedMessage
+ id='delete_channel.cancel'
+ defaultMessage='Cancel'
+ />
</button>
<button
type='button'
@@ -59,7 +89,10 @@ export default class DeleteChannelModal extends React.Component {
data-dismiss='modal'
onClick={this.handleDelete}
>
- {'Delete'}
+ <FormattedMessage
+ id='delete_channel.del'
+ defaultMessage='Delete'
+ />
</button>
</Modal.Footer>
</Modal>
diff --git a/web/react/components/delete_post_modal.jsx b/web/react/components/delete_post_modal.jsx
index 4cde5feed..34fd724f5 100644
--- a/web/react/components/delete_post_modal.jsx
+++ b/web/react/components/delete_post_modal.jsx
@@ -9,6 +9,9 @@ import * as Utils from '../utils/utils.jsx';
import * as AsyncClient from '../utils/async_client.jsx';
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 DeletePostModal extends React.Component {
@@ -128,10 +131,28 @@ export default class DeletePostModal extends React.Component {
var commentWarning = '';
if (this.state.commentCount > 0) {
- commentWarning = 'This post has ' + this.state.commentCount + ' comment(s) on it.';
+ commentWarning = (
+ <FormattedMessage
+ id='delete_post.warning'
+ defaultMessage='This post has {count} comment(s) on it.'
+ values={{
+ count: this.state.commentCount
+ }}
+ />
+ );
}
- const postTerm = Utils.getPostTerm(this.state.post);
+ const postTerm = this.state.post.root_id ? (
+ <FormattedMessage
+ id='delete_post.comment'
+ defaultMessage='Comment'
+ />
+ ) : (
+ <FormattedMessage
+ id='delete_post.post'
+ defaultMessage='Post'
+ />
+ );
return (
<Modal
@@ -139,10 +160,24 @@ export default class DeletePostModal extends React.Component {
onHide={this.handleHide}
>
<Modal.Header closeButton={true}>
- <Modal.Title>{`Confirm ${postTerm} Delete`}</Modal.Title>
+ <Modal.Title>
+ <FormattedMessage
+ id='delete_post.confirm'
+ defaultMessage='Confirm {term} Delete'
+ values={{
+ term: (postTerm)
+ }}
+ />
+ </Modal.Title>
</Modal.Header>
<Modal.Body>
- {`Are you sure you want to delete this ${postTerm.toLowerCase()}?`}
+ <FormattedMessage
+ id='delete_post.question'
+ defaultMessage='Are you sure you want to delete this ${term}?'
+ values={{
+ term: (postTerm)
+ }}
+ />
<br />
<br />
{commentWarning}
@@ -154,7 +189,10 @@ export default class DeletePostModal extends React.Component {
className='btn btn-default'
onClick={this.handleHide}
>
- {'Cancel'}
+ <FormattedMessage
+ id='delete_post.cancel'
+ defaultMessage='Cancel'
+ />
</button>
<button
ref='deletePostBtn'
@@ -162,7 +200,10 @@ export default class DeletePostModal extends React.Component {
className='btn btn-danger'
onClick={this.handleDelete}
>
- {'Delete'}
+ <FormattedMessage
+ id='delete_post.del'
+ defaultMessage='Delete'
+ />
</button>
</Modal.Footer>
</Modal>
diff --git a/web/react/components/edit_channel_header_modal.jsx b/web/react/components/edit_channel_header_modal.jsx
index e4817f6e4..1066d123e 100644
--- a/web/react/components/edit_channel_header_modal.jsx
+++ b/web/react/components/edit_channel_header_modal.jsx
@@ -6,9 +6,18 @@ import * as Client from '../utils/client.jsx';
import Constants from '../utils/constants.jsx';
import * as Utils from '../utils/utils.jsx';
+import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl';
+
const Modal = ReactBootstrap.Modal;
-export default class EditChannelHeaderModal extends React.Component {
+const holders = defineMessages({
+ error: {
+ id: 'edit_channel_header_modal.error',
+ defaultMessage: 'This channel header is too long, please enter a shorter one'
+ }
+});
+
+class EditChannelHeaderModal extends React.Component {
constructor(props) {
super(props);
@@ -64,8 +73,8 @@ export default class EditChannelHeaderModal extends React.Component {
});
},
(err) => {
- if (err.message === 'Invalid channel_header parameter') {
- this.setState({serverError: 'This channel header is too long, please enter a shorter one'});
+ if (err.id === 'api.context.invalid_param.app_error') {
+ this.setState({serverError: this.props.intl.formatMessage(holders.error)});
} else {
this.setState({serverError: err.message});
}
@@ -99,10 +108,23 @@ export default class EditChannelHeaderModal extends React.Component {
onHide={this.onHide}
>
<Modal.Header closeButton={true}>
- <Modal.Title>{'Edit Header for ' + this.props.channel.display_name}</Modal.Title>
+ <Modal.Title>
+ <FormattedMessage
+ id='edit_channel_header_modal.title'
+ defaultMessage='Edit Header for {channel}'
+ values={{
+ channel: this.props.channel.display_name
+ }}
+ />
+ </Modal.Title>
</Modal.Header>
<Modal.Body>
- <p>{'Edit the text appearing next to the channel name in the channel header.'}</p>
+ <p>
+ <FormattedMessage
+ id='edit_channel_header_modal.description'
+ defaultMessage='Edit the text appearing next to the channel name in the channel header.'
+ />
+ </p>
<textarea
ref='textarea'
className='form-control no-resize'
@@ -120,14 +142,20 @@ export default class EditChannelHeaderModal extends React.Component {
className='btn btn-default'
onClick={this.onHide}
>
- {'Cancel'}
+ <FormattedMessage
+ id='edit_channel_header_modal.cancel'
+ defaultMessage='Cancel'
+ />
</button>
<button
type='button'
className='btn btn-primary'
onClick={this.handleSubmit}
>
- {'Save'}
+ <FormattedMessage
+ id='edit_channel_header_modal.save'
+ defaultMessage='Save'
+ />
</button>
</Modal.Footer>
</Modal>
@@ -136,7 +164,10 @@ export default class EditChannelHeaderModal extends React.Component {
}
EditChannelHeaderModal.propTypes = {
+ intl: intlShape.isRequired,
show: React.PropTypes.bool.isRequired,
onHide: React.PropTypes.func.isRequired,
channel: React.PropTypes.object.isRequired
};
+
+export default injectIntl(EditChannelHeaderModal);
diff --git a/web/react/components/edit_channel_purpose_modal.jsx b/web/react/components/edit_channel_purpose_modal.jsx
index af23342ae..d8354f59d 100644
--- a/web/react/components/edit_channel_purpose_modal.jsx
+++ b/web/react/components/edit_channel_purpose_modal.jsx
@@ -3,10 +3,19 @@
import * as AsyncClient from '../utils/async_client.jsx';
import * as Client from '../utils/client.jsx';
-import * as Utils from '../utils/utils.jsx';
+import Constants from '../utils/constants.jsx';
+
+import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl';
const Modal = ReactBootstrap.Modal;
+const holders = defineMessages({
+ error: {
+ id: 'edit_channel_purpose_modal.error',
+ defaultMessage: 'This channel purpose is too long, please enter a shorter one'
+ }
+});
+
export default class EditChannelPurposeModal extends React.Component {
constructor(props) {
super(props);
@@ -48,8 +57,8 @@ export default class EditChannelPurposeModal extends React.Component {
this.handleHide();
},
(err) => {
- if (err.message === 'Invalid channel_purpose parameter') {
- this.setState({serverError: 'This channel purpose is too long, please enter a shorter one'});
+ if (err.id === 'api.context.invalid_param.app_error') {
+ this.setState({serverError: this.props.intl.formatMessage(holders.error)});
} else {
this.setState({serverError: err.message});
}
@@ -72,9 +81,39 @@ export default class EditChannelPurposeModal extends React.Component {
);
}
- let title = <span>{'Edit Purpose'}</span>;
+ let title = (
+ <span>
+ <FormattedMessage
+ id='edit_channel_purpose_modal.title1'
+ defaultMessage='Edit Purpose'
+ />
+ </span>
+ );
if (this.props.channel.display_name) {
- title = <span>{'Edit Purpose for '}<span className='name'>{this.props.channel.display_name}</span></span>;
+ title = (
+ <span>
+ <FormattedMessage
+ id='edit_channel_purpose_modal.title2'
+ defaultMessage='Edit Purpose for '
+ />
+ <span className='name'>{this.props.channel.display_name}</span>
+ </span>
+ );
+ }
+
+ let channelType = (
+ <FormattedMessage
+ id='edit_channel_purpose_modal.channel'
+ defaultMessage='Channel'
+ />
+ );
+ if (this.props.channel.type === Constants.PRIVATE_CHANNEL) {
+ channelType = (
+ <FormattedMessage
+ id='edit_channel_purpose_modal.group'
+ defaultMessage='Group'
+ />
+ );
}
return (
@@ -90,7 +129,15 @@ export default class EditChannelPurposeModal extends React.Component {
</Modal.Title>
</Modal.Header>
<Modal.Body>
- <p>{`Describe how this ${Utils.getChannelTerm(this.props.channel.channelType)} should be used. This text appears in the channel list in the "More..." menu and helps others decide whether to join.`}</p>
+ <p>
+ <FormattedMessage
+ id='edit_channel_purpose_modal.body'
+ defaultMessage='Describe how this {type} should be used. This text appears in the channel list in the "More..." menu and helps others decide whether to join.'
+ values={{
+ type: (channelType)
+ }}
+ />
+ </p>
<textarea
ref='purpose'
className='form-control no-resize'
@@ -106,14 +153,20 @@ export default class EditChannelPurposeModal extends React.Component {
className='btn btn-default'
onClick={this.handleHide}
>
- {'Cancel'}
+ <FormattedMessage
+ id='edit_channel_purpose_modal.cancel'
+ defaultMessage='Cancel'
+ />
</button>
<button
type='button'
className='btn btn-primary'
onClick={this.handleSave}
>
- {'Save'}
+ <FormattedMessage
+ id='edit_channel_purpose_modal.save'
+ defaultMessage='Save'
+ />
</button>
</Modal.Footer>
</Modal>
@@ -122,7 +175,10 @@ export default class EditChannelPurposeModal extends React.Component {
}
EditChannelPurposeModal.propTypes = {
+ intl: intlShape.isRequired,
show: React.PropTypes.bool.isRequired,
channel: React.PropTypes.object,
onModalDismissed: React.PropTypes.func.isRequired
};
+
+export default injectIntl(EditChannelPurposeModal); \ No newline at end of file
diff --git a/web/react/components/edit_post_modal.jsx b/web/react/components/edit_post_modal.jsx
index e4e77a943..e54b7d9b8 100644
--- a/web/react/components/edit_post_modal.jsx
+++ b/web/react/components/edit_post_modal.jsx
@@ -10,9 +10,19 @@ import PostStore from '../stores/post_store.jsx';
import PreferenceStore from '../stores/preference_store.jsx';
import Constants from '../utils/constants.jsx';
+
+import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl';
+
var KeyCodes = Constants.KeyCodes;
-export default class EditPostModal extends React.Component {
+const holders = defineMessages({
+ editPost: {
+ id: 'edit_post.editPost',
+ defaultMessage: 'Edit the post...'
+ }
+});
+
+class EditPostModal extends React.Component {
constructor() {
super();
@@ -151,7 +161,15 @@ export default class EditPostModal extends React.Component {
>
<span aria-hidden='true'>&times;</span>
</button>
- <h4 className='modal-title'>Edit {this.state.title}</h4>
+ <h4 className='modal-title'>
+ <FormattedMessage
+ id='edit_post.edit'
+ defaultMessage='Edit {title}'
+ values={{
+ title: this.state.title
+ }}
+ />
+ </h4>
</div>
<div className='edit-modal-body modal-body'>
<Textbox
@@ -159,7 +177,7 @@ export default class EditPostModal extends React.Component {
onKeyPress={this.handleEditKeyPress}
onKeyDown={this.handleKeyDown}
messageText={this.state.editText}
- createMessage='Edit the post...'
+ createMessage={this.props.intl.formatMessage(holders.editPost)}
supportsCommands={false}
id='edit_textbox'
ref='editbox'
@@ -172,14 +190,20 @@ export default class EditPostModal extends React.Component {
className='btn btn-default'
data-dismiss='modal'
>
- Cancel
+ <FormattedMessage
+ id='edit_post.cancel'
+ defaultMessage='Cancel'
+ />
</button>
<button
type='button'
className='btn btn-primary'
onClick={this.handleEdit}
>
- Save
+ <FormattedMessage
+ id='edit_post.save'
+ defaultMessage='Save'
+ />
</button>
</div>
</div>
@@ -188,3 +212,9 @@ export default class EditPostModal extends React.Component {
);
}
}
+
+EditPostModal.propTypes = {
+ intl: intlShape.isRequired
+};
+
+export default injectIntl(EditPostModal); \ No newline at end of file
diff --git a/web/react/components/get_link_modal.jsx b/web/react/components/get_link_modal.jsx
index 3fc71ff96..de3387a35 100644
--- a/web/react/components/get_link_modal.jsx
+++ b/web/react/components/get_link_modal.jsx
@@ -41,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 = (
@@ -53,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'
@@ -69,6 +71,18 @@ export default class GetLinkModal extends React.Component {
);
}
+ 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 = (
@@ -92,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
diff --git a/web/react/components/get_team_invite_link_modal.jsx b/web/react/components/get_team_invite_link_modal.jsx
index 883871267..299729250 100644
--- a/web/react/components/get_team_invite_link_modal.jsx
+++ b/web/react/components/get_team_invite_link_modal.jsx
@@ -16,6 +16,10 @@ const holders = defineMessages({
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.'
}
});
@@ -47,12 +51,18 @@ 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={formatMessage(holders.title)}
- helpText={formatMessage(holders.help)}
+ helpText={helpText}
link={TeamStore.getCurrentInviteLink()}
/>
);
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_item.jsx b/web/react/components/member_list_item.jsx
index a7273f280..c50ee5c96 100644
--- a/web/react/components/member_list_item.jsx
+++ b/web/react/components/member_list_item.jsx
@@ -4,6 +4,8 @@
import UserStore from '../stores/user_store.jsx';
import * as Utils from '../utils/utils.jsx';
+import {FormattedMessage} from 'mm-intl';
+
export default class MemberListItem extends React.Component {
constructor(props) {
super(props);
@@ -38,7 +40,10 @@ export default class MemberListItem extends React.Component {
className='btn btn-sm btn-primary'
>
<i className='glyphicon glyphicon-envelope'/>
- {' Add'}
+ <FormattedMessage
+ id='member_item.add'
+ defaultMessage=' Add'
+ />
</a>
);
} else if (isAdmin && !isMemberAdmin && (member.id !== UserStore.getCurrentId())) {
@@ -53,7 +58,10 @@ export default class MemberListItem extends React.Component {
role='menuitem'
onClick={self.handleMakeAdmin}
>
- Make Admin
+ <FormattedMessage
+ id='member_item.makeAdmin'
+ defaultMessage='Make Admin'
+ />
</a>
</li>);
}
@@ -67,7 +75,10 @@ export default class MemberListItem extends React.Component {
role='menuitem'
onClick={self.handleRemove}
>
- Remove Member
+ <FormattedMessage
+ id='member_item.removeMember'
+ defaultMessage='Remove Member'
+ />
</a>
</li>);
}
@@ -82,7 +93,14 @@ export default class MemberListItem extends React.Component {
aria-expanded='true'
>
<span className='fa fa-pencil'></span>
- <span className='text-capitalize'>{member.roles || 'Member'} </span>
+ <span className='text-capitalize'>
+ {member.roles ||
+ <FormattedMessage
+ id='member_item.member'
+ defaultMessage='Member'
+ />
+ }
+ </span>
</a>
<ul
className='dropdown-menu member-menu'
@@ -94,7 +112,7 @@ export default class MemberListItem extends React.Component {
</div>
);
} else {
- invite = <div className='member-role text-capitalize'><span className='fa fa-pencil hidden'></span>{member.roles || 'Member'}</div>;
+ invite = (<div className='member-role text-capitalize'><span className='fa fa-pencil hidden'></span>{member.roles || <FormattedMessage id='member_item.member' />}</div>);
}
return (
diff --git a/web/react/components/msg_typing.jsx b/web/react/components/msg_typing.jsx
index b95b06260..f7a40b54e 100644
--- a/web/react/components/msg_typing.jsx
+++ b/web/react/components/msg_typing.jsx
@@ -106,9 +106,9 @@ class MsgTyping extends React.Component {
<FormattedMessage
id='msg_typing.areTyping'
defaultMessage='{users} and {last} are typing...'
- vaues={{
- users: users.join(', '),
- last: last
+ values={{
+ users: (users.join(', ')),
+ last: (last)
}}
/>
);
diff --git a/web/react/components/popover_list_members.jsx b/web/react/components/popover_list_members.jsx
index f4cb542e4..f217229ed 100644
--- a/web/react/components/popover_list_members.jsx
+++ b/web/react/components/popover_list_members.jsx
@@ -9,6 +9,8 @@ import Constants from '../utils/constants.jsx';
import ChannelStore from '../stores/channel_store.jsx';
+import {FormattedMessage} from 'mm-intl';
+
export default class PopoverListMembers extends React.Component {
constructor(props) {
super(props);
@@ -92,7 +94,10 @@ export default class PopoverListMembers extends React.Component {
className='btn-message'
onClick={(e) => this.handleShowDirectChannel(m, e)}
>
- {'Message'}
+ <FormattedMessage
+ id='members_popover.msg'
+ defaultMessage='Message'
+ />
</a>
);
}
@@ -147,6 +152,12 @@ export default class PopoverListMembers extends React.Component {
countText = count.toString();
}
+ const title = (
+ <FormattedMessage
+ id='members_popover.title'
+ defaultMessage='Members'
+ />
+ );
return (
<div>
<div
@@ -171,7 +182,7 @@ export default class PopoverListMembers extends React.Component {
>
<Popover
ref='memebersPopover'
- title='Members'
+ title={title}
id='member-list-popover'
>
{popoverHtml}
diff --git a/web/react/components/post.jsx b/web/react/components/post.jsx
index 695d7daef..53fe7fb5d 100644
--- a/web/react/components/post.jsx
+++ b/web/react/components/post.jsx
@@ -214,6 +214,7 @@ export default class Post extends React.Component {
commentCount={commentCount}
handleCommentClick={this.handleCommentClick}
isLastComment={this.props.isLastComment}
+ sameUser={this.props.sameUser}
/>
<PostBody
post={post}
diff --git a/web/react/components/post_deleted_modal.jsx b/web/react/components/post_deleted_modal.jsx
index 3723bcaba..218f57eb5 100644
--- a/web/react/components/post_deleted_modal.jsx
+++ b/web/react/components/post_deleted_modal.jsx
@@ -4,6 +4,9 @@
import UserStore from '../stores/user_store.jsx';
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 PostDeletedModal extends React.Component {
@@ -65,11 +68,19 @@ export default class PostDeletedModal extends React.Component {
className='modal-title'
id='myModalLabel'
>
- {'Comment could not be posted'}
+ <FormattedMessage
+ id='post_delete.notPosted'
+ defaultMessage='Comment could not be posted'
+ />
</h4>
</div>
<div className='modal-body'>
- <p>{'Someone deleted the message on which you tried to post a comment.'}</p>
+ <p>
+ <FormattedMessage
+ id='post_delete.someone'
+ defaultMessage='Someone deleted the message on which you tried to post a comment.'
+ />
+ </p>
</div>
<div className='modal-footer'>
<button
@@ -77,7 +88,10 @@ export default class PostDeletedModal extends React.Component {
className='btn btn-primary'
data-dismiss='modal'
>
- {'Okay'}
+ <FormattedMessage
+ id='post_delete.okay'
+ defaultMessage='Okay'
+ />
</button>
</div>
</div>
diff --git a/web/react/components/post_header.jsx b/web/react/components/post_header.jsx
index f18024343..037b48096 100644
--- a/web/react/components/post_header.jsx
+++ b/web/react/components/post_header.jsx
@@ -52,6 +52,7 @@ export default class PostHeader extends React.Component {
handleCommentClick={this.props.handleCommentClick}
allowReply='true'
isLastComment={this.props.isLastComment}
+ sameUser={this.props.sameUser}
/>
</li>
</ul>
@@ -62,11 +63,13 @@ export default class PostHeader extends React.Component {
PostHeader.defaultProps = {
post: null,
commentCount: 0,
- isLastComment: false
+ isLastComment: false,
+ sameUser: false
};
PostHeader.propTypes = {
post: React.PropTypes.object,
commentCount: React.PropTypes.number,
isLastComment: React.PropTypes.bool,
- handleCommentClick: React.PropTypes.func
+ handleCommentClick: React.PropTypes.func,
+ sameUser: React.PropTypes.bool
};
diff --git a/web/react/components/post_info.jsx b/web/react/components/post_info.jsx
index 2bff675a9..0fb9d7f4a 100644
--- a/web/react/components/post_info.jsx
+++ b/web/react/components/post_info.jsx
@@ -220,6 +220,7 @@ export default class PostInfo extends React.Component {
<li className='col'>
<TimeSince
eventTime={post.create_at}
+ sameUser={this.props.sameUser}
/>
</li>
<li className='col col__reply'>
@@ -251,12 +252,14 @@ PostInfo.defaultProps = {
post: null,
commentCount: 0,
isLastComment: false,
- allowReply: false
+ allowReply: false,
+ sameUser: false
};
PostInfo.propTypes = {
post: React.PropTypes.object,
commentCount: React.PropTypes.number,
isLastComment: React.PropTypes.bool,
allowReply: React.PropTypes.string,
- handleCommentClick: React.PropTypes.func
+ handleCommentClick: React.PropTypes.func,
+ sameUser: React.PropTypes.bool
};
diff --git a/web/react/components/removed_from_channel_modal.jsx b/web/react/components/removed_from_channel_modal.jsx
index 69d038c22..748baa32b 100644
--- a/web/react/components/removed_from_channel_modal.jsx
+++ b/web/react/components/removed_from_channel_modal.jsx
@@ -6,6 +6,8 @@ import UserStore from '../stores/user_store.jsx';
import BrowserStore from '../stores/browser_store.jsx';
import * as utils from '../utils/utils.jsx';
+import {FormattedMessage} from 'mm-intl';
+
export default class RemovedFromChannelModal extends React.Component {
constructor(props) {
super(props);
@@ -49,12 +51,22 @@ export default class RemovedFromChannelModal extends React.Component {
render() {
var currentUser = UserStore.getCurrentUser();
- var channelName = 'the channel';
+ var channelName = (
+ <FormattedMessage
+ id='removed_channel.channelName'
+ defaultMessage='the channel'
+ />
+ );
if (this.state.channelName) {
channelName = this.state.channelName;
}
- var remover = 'Someone';
+ var remover = (
+ <FormattedMessage
+ id='removed_channel.someone'
+ defaultMessage='Someone'
+ />
+ );
if (this.state.remover) {
remover = this.state.remover;
}
@@ -78,17 +90,36 @@ export default class RemovedFromChannelModal extends React.Component {
data-dismiss='modal'
aria-label='Close'
><span aria-hidden='true'>&times;</span></button>
- <h4 className='modal-title'>Removed from <span className='name'>{channelName}</span></h4>
+ <h4 className='modal-title'>
+ <FormattedMessage
+ id='removed_channel.from'
+ defaultMessage='Removed from '
+ />
+ <span className='name'>{channelName}</span></h4>
</div>
<div className='modal-body'>
- <p>{remover} removed you from {channelName}</p>
+ <p>
+ <FormattedMessage
+ id='removed_channel.remover'
+ defaultMessage='{remover} removed you from {channel}'
+ values={{
+ remover: (remover),
+ channel: (channelName)
+ }}
+ />
+ </p>
</div>
<div className='modal-footer'>
<button
type='button'
className='btn btn-primary'
data-dismiss='modal'
- >Okay</button>
+ >
+ <FormattedMessage
+ id='removed_channel.okay'
+ defaultMessage='Okay'
+ />
+ </button>
</div>
</div>
</div>
diff --git a/web/react/components/rename_channel_modal.jsx b/web/react/components/rename_channel_modal.jsx
index c16216c68..c467c0d87 100644
--- a/web/react/components/rename_channel_modal.jsx
+++ b/web/react/components/rename_channel_modal.jsx
@@ -7,6 +7,39 @@ import * as AsyncClient from '../utils/async_client.jsx';
import ChannelStore from '../stores/channel_store.jsx';
import Constants from '../utils/constants.jsx';
+import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl';
+
+const holders = defineMessages({
+ required: {
+ id: 'rename_channel.required',
+ defaultMessage: 'This field is required'
+ },
+ maxLength: {
+ id: 'rename_channel.maxLength',
+ defaultMessage: 'This field must be less than 22 characters'
+ },
+ lowercase: {
+ id: 'rename_channel.lowercase',
+ defaultMessage: 'Must be lowercase alphanumeric characters'
+ },
+ handle: {
+ id: 'rename_channel.handle',
+ defaultMessage: 'Handle'
+ },
+ defaultError: {
+ id: 'rename_channel.defaultError',
+ defaultMessage: ' - Cannot be changed for the default channel'
+ },
+ displayNameHolder: {
+ id: 'rename_channel.displayNameHolder',
+ defaultMessage: 'Enter display name'
+ },
+ handleHolder: {
+ id: 'rename_channel.handleHolder',
+ defaultMessage: 'lowercase alphanumeric&#39;s only'
+ }
+});
+
export default class RenameChannelModal extends React.Component {
constructor(props) {
super(props);
@@ -41,13 +74,14 @@ export default class RenameChannelModal extends React.Component {
const oldName = channel.name;
const oldDisplayName = channel.displayName;
const state = {serverError: ''};
+ const {formatMessage} = this.props.intl;
channel.display_name = this.state.displayName.trim();
if (!channel.display_name) {
- state.displayNameError = 'This field is required';
+ state.displayNameError = formatMessage(holders.required);
state.invalid = true;
} else if (channel.display_name.length > 22) {
- state.displayNameError = 'This field must be less than 22 characters';
+ state.displayNameError = formatMessage(holders.maxLength);
state.invalid = true;
} else {
state.displayNameError = '';
@@ -55,17 +89,17 @@ export default class RenameChannelModal extends React.Component {
channel.name = this.state.channelName.trim();
if (!channel.name) {
- state.nameError = 'This field is required';
+ state.nameError = formatMessage(holders.required);
state.invalid = true;
} else if (channel.name.length > 22) {
- state.nameError = 'This field must be less than 22 characters';
+ state.nameError = formatMessage(holders.maxLength);
state.invalid = true;
} else {
const cleanedName = Utils.cleanUpUrlable(channel.name);
if (cleanedName === channel.name) {
state.nameError = '';
} else {
- state.nameError = 'Must be lowercase alphanumeric characters';
+ state.nameError = formatMessage(holders.lowercase);
state.invalid = true;
}
}
@@ -153,11 +187,13 @@ export default class RenameChannelModal extends React.Component {
serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>;
}
- let handleInputLabel = 'Handle';
+ const {formatMessage} = this.props.intl;
+
+ let handleInputLabel = formatMessage(holders.handle);
let handleInputClass = 'form-control';
let readOnlyHandleInput = false;
if (this.state.channelName === Constants.DEFAULT_CHANNEL) {
- handleInputLabel += ' - Cannot be changed for the default channel';
+ handleInputLabel += formatMessage(holders.defaultError);
handleInputClass += ' disabled-input';
readOnlyHandleInput = true;
}
@@ -180,14 +216,29 @@ export default class RenameChannelModal extends React.Component {
data-dismiss='modal'
>
<span aria-hidden='true'>{'×'}</span>
- <span className='sr-only'>{'Close'}</span>
+ <span className='sr-only'>
+ <FormattedMessage
+ id='rename_channel.close'
+ defaultMessage='Close'
+ />
+ </span>
</button>
- <h4 className='modal-title'>{'Rename Channel'}</h4>
+ <h4 className='modal-title'>
+ <FormattedMessage
+ id='rename_channel.title'
+ defaultMessage='Rename Channel'
+ />
+ </h4>
</div>
<form role='form'>
<div className='modal-body'>
<div className={displayNameClass}>
- <label className='control-label'>{'Display Name'}</label>
+ <label className='control-label'>
+ <FormattedMessage
+ id='rename_channel.displayName'
+ defaultMessage='Display Name'
+ />
+ </label>
<input
onKeyUp={this.displayNameKeyUp}
onChange={this.onDisplayNameChange}
@@ -195,7 +246,7 @@ export default class RenameChannelModal extends React.Component {
ref='displayName'
id='display_name'
className='form-control'
- placeholder='Enter display name'
+ placeholder={formatMessage(holders.displayNameHolder)}
value={this.state.displayName}
maxLength='64'
/>
@@ -208,7 +259,7 @@ export default class RenameChannelModal extends React.Component {
type='text'
className={handleInputClass}
ref='channelName'
- placeholder='lowercase alphanumeric&#39;s only'
+ placeholder={formatMessage(holders.handleHolder)}
value={this.state.channelName}
maxLength='64'
readOnly={readOnlyHandleInput}
@@ -223,14 +274,20 @@ export default class RenameChannelModal extends React.Component {
className='btn btn-default'
data-dismiss='modal'
>
- {'Cancel'}
+ <FormattedMessage
+ id='rename_channel.cancel'
+ defaultMessage='Cancel'
+ />
</button>
<button
onClick={this.handleSubmit}
type='submit'
className='btn btn-primary'
>
- {'Save'}
+ <FormattedMessage
+ id='rename_channel.save'
+ defaultMessage='Save'
+ />
</button>
</div>
</form>
@@ -240,3 +297,9 @@ export default class RenameChannelModal extends React.Component {
);
}
}
+
+RenameChannelModal.propTypes = {
+ intl: intlShape.isRequired
+};
+
+export default injectIntl(RenameChannelModal); \ No newline at end of file
diff --git a/web/react/components/setting_item_max.jsx b/web/react/components/setting_item_max.jsx
index 52f1906c3..537055641 100644
--- a/web/react/components/setting_item_max.jsx
+++ b/web/react/components/setting_item_max.jsx
@@ -85,6 +85,6 @@ SettingItemMax.propTypes = {
extraInfo: React.PropTypes.element,
updateSection: React.PropTypes.func,
submit: React.PropTypes.func,
- title: React.PropTypes.string,
+ title: React.PropTypes.node,
width: React.PropTypes.string
};
diff --git a/web/react/components/setting_item_min.jsx b/web/react/components/setting_item_min.jsx
index db5513b14..868b7e1b2 100644
--- a/web/react/components/setting_item_min.jsx
+++ b/web/react/components/setting_item_min.jsx
@@ -38,8 +38,8 @@ export default class SettingItemMin extends React.Component {
}
SettingItemMin.propTypes = {
- title: React.PropTypes.string,
+ title: React.PropTypes.node,
disableOpen: React.PropTypes.bool,
updateSection: React.PropTypes.func,
- describe: React.PropTypes.string
+ describe: React.PropTypes.node
};
diff --git a/web/react/components/signup_user_complete.jsx b/web/react/components/signup_user_complete.jsx
index 47ec58e98..98a832542 100644
--- a/web/react/components/signup_user_complete.jsx
+++ b/web/react/components/signup_user_complete.jsx
@@ -150,9 +150,18 @@ class SignupUserComplete extends React.Component {
// set up error labels
var emailError = null;
+ var emailHelpText = (
+ <span className='help-block'>
+ <FormattedMessage
+ id='signup_user_completed.emailHelp'
+ defaultMessage='Valid email required for sign-up'
+ />
+ </span>
+ );
var emailDivStyle = 'form-group';
if (this.state.emailError) {
emailError = <label className='control-label'>{this.state.emailError}</label>;
+ emailHelpText = '';
emailDivStyle += ' has-error';
}
@@ -232,6 +241,7 @@ class SignupUserComplete extends React.Component {
spellCheck='false'
/>
{emailError}
+ {emailHelpText}
</div>
</div>
);
diff --git a/web/react/components/team_general_tab.jsx b/web/react/components/team_general_tab.jsx
index 0656d3b03..0a1b02853 100644
--- a/web/react/components/team_general_tab.jsx
+++ b/web/react/components/team_general_tab.jsx
@@ -575,6 +575,8 @@ class GeneralTab extends React.Component {
</div>
);
+ const nameExtraInfo = <span>{formatMessage(holders.teamNameInfo)}</span>;
+
nameSection = (
<SettingItemMax
title={formatMessage({id: 'general_tab.teamName'})}
@@ -583,7 +585,7 @@ class GeneralTab extends React.Component {
server_error={serverError}
client_error={clientError}
updateSection={this.onUpdateNameSection}
- extraInfo={formatMessage(holders.teamNameInfo)}
+ extraInfo={nameExtraInfo}
/>
);
} else {
diff --git a/web/react/components/textbox.jsx b/web/react/components/textbox.jsx
index bb383aca1..00e5ace98 100644
--- a/web/react/components/textbox.jsx
+++ b/web/react/components/textbox.jsx
@@ -129,13 +129,6 @@ export default class Textbox extends React.Component {
this.resize();
}
- showHelp(e) {
- e.preventDefault();
- e.target.blur();
-
- global.window.open('/docs/Messaging');
- }
-
render() {
let previewLink = null;
if (Utils.isFeatureEnabled(PreReleaseFeatures.MARKDOWN_PREVIEW)) {
@@ -194,7 +187,8 @@ export default class Textbox extends React.Component {
</div>
{previewLink}
<a
- onClick={this.showHelp}
+ target='_blank'
+ href='http://docs.mattermost.com/help/getting-started/messaging-basics.html'
className='textbox-help-link'
>
<FormattedMessage
diff --git a/web/react/components/time_since.jsx b/web/react/components/time_since.jsx
index 32947bd60..0b549b1e6 100644
--- a/web/react/components/time_since.jsx
+++ b/web/react/components/time_since.jsx
@@ -14,7 +14,7 @@ export default class TimeSince extends React.Component {
componentDidMount() {
this.intervalId = setInterval(() => {
this.forceUpdate();
- }, 30000);
+ }, Constants.TIME_SINCE_UPDATE_INTERVAL);
}
componentWillUnmount() {
clearInterval(this.intervalId);
@@ -23,6 +23,14 @@ export default class TimeSince extends React.Component {
const displayDate = Utils.displayDate(this.props.eventTime);
const displayTime = Utils.displayTime(this.props.eventTime);
+ if (this.props.sameUser) {
+ return (
+ <time className='post__time'>
+ {Utils.displayTime(this.props.eventTime)}
+ </time>
+ );
+ }
+
const tooltip = (
<Tooltip id={'time-since-tooltip-' + this.props.eventTime}>
{displayDate + ' at ' + displayTime}
@@ -42,10 +50,13 @@ export default class TimeSince extends React.Component {
);
}
}
+
TimeSince.defaultProps = {
- eventTime: 0
+ eventTime: 0,
+ sameUser: false
};
TimeSince.propTypes = {
- eventTime: React.PropTypes.number.isRequired
+ eventTime: React.PropTypes.number.isRequired,
+ sameUser: React.PropTypes.bool
};
diff --git a/web/react/stores/user_store.jsx b/web/react/stores/user_store.jsx
index 3e1871180..b97a0d87b 100644
--- a/web/react/stores/user_store.jsx
+++ b/web/react/stores/user_store.jsx
@@ -38,6 +38,8 @@ class UserStoreClass extends EventEmitter {
this.setCurrentUser = this.setCurrentUser.bind(this);
this.getLastEmail = this.getLastEmail.bind(this);
this.setLastEmail = this.setLastEmail.bind(this);
+ this.getLastUsername = this.getLastUsername.bind(this);
+ this.setLastUsername = this.setLastUsername.bind(this);
this.hasProfile = this.hasProfile.bind(this);
this.getProfile = this.getProfile.bind(this);
this.getProfileByUsername = this.getProfileByUsername.bind(this);
@@ -159,6 +161,14 @@ class UserStoreClass extends EventEmitter {
BrowserStore.setGlobalItem('last_email', email);
}
+ getLastUsername() {
+ return BrowserStore.getGlobalItem('last_username', '');
+ }
+
+ setLastUsername(username) {
+ BrowserStore.setGlobalItem('last_username', username);
+ }
+
hasProfile(userId) {
return this.getProfiles()[userId] != null;
}
diff --git a/web/react/utils/client.jsx b/web/react/utils/client.jsx
index 09cd4162a..c4b1bc061 100644
--- a/web/react/utils/client.jsx
+++ b/web/react/utils/client.jsx
@@ -305,6 +305,28 @@ export function loginByEmail(name, email, password, success, error) {
});
}
+export function loginByUsername(name, username, password, success, error) {
+ $.ajax({
+ url: '/api/v1/users/login',
+ dataType: 'json',
+ contentType: 'application/json',
+ type: 'POST',
+ data: JSON.stringify({name, username, password}),
+ success: function onSuccess(data, textStatus, xhr) {
+ track('api', 'api_users_login_success', data.team_id, 'username', data.username);
+ sessionStorage.removeItem(data.id + '_last_error');
+ BrowserStore.signalLogin();
+ success(data, textStatus, xhr);
+ },
+ error: function onError(xhr, status, err) {
+ track('api', 'api_users_login_fail', name, 'username', username);
+
+ var e = handleError('loginByUsername', xhr, status, err);
+ error(e);
+ }
+ });
+}
+
export function loginByLdap(teamName, id, password, success, error) {
$.ajax({
url: '/api/v1/users/login_ldap',
diff --git a/web/react/utils/constants.jsx b/web/react/utils/constants.jsx
index e1a4b8a8a..ad0e4b2fe 100644
--- a/web/react/utils/constants.jsx
+++ b/web/react/utils/constants.jsx
@@ -462,5 +462,6 @@ export default {
MIN_USERNAME_LENGTH: 3,
MAX_USERNAME_LENGTH: 15,
MIN_PASSWORD_LENGTH: 5,
- MAX_PASSWORD_LENGTH: 50
+ MAX_PASSWORD_LENGTH: 50,
+ TIME_SINCE_UPDATE_INTERVAL: 30000
};
diff --git a/web/sass-files/sass/partials/_post.scss b/web/sass-files/sass/partials/_post.scss
index be85ef07b..73c7bd9cb 100644
--- a/web/sass-files/sass/partials/_post.scss
+++ b/web/sass-files/sass/partials/_post.scss
@@ -456,11 +456,7 @@ body.ios {
&:hover {
.post__time {
-
- &:before {
- @include opacity(0.5);
- }
-
+ @include opacity(0.5);
}
}
@@ -484,27 +480,15 @@ body.ios {
}
.post__time {
- display: inline-block;
- font: normal normal normal 14px/1 FontAwesome;
- font-size: inherit;
+ font: normal normal normal FontAwesome;
text-rendering: auto;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
- font-size: 0;
position: absolute;
top: -3px;
- left: 17px;
- width: 30px;
- height: 30px;
+ left: -1.0em;
line-height: 37px;
-
- &:before {
- @include opacity(0);
- content: "\f017";
- content: "\f017";
- font-size: 19px;
- }
-
+ @include opacity(0);
}
}
diff --git a/web/static/i18n/en.json b/web/static/i18n/en.json
index ae3a38c71..e163b6a54 100644
--- a/web/static/i18n/en.json
+++ b/web/static/i18n/en.json
@@ -134,6 +134,10 @@
"admin.email.true": "true",
"admin.email.false": "false",
"admin.email.allowSignupDescription": "When true, Mattermost allows team creation and account signup using email and password. This value should be false only when you want to limit signup to a single-sign-on service like OAuth or LDAP.",
+ "admin.email.allowEmailSignInTitle": "Allow Sign In With Email: ",
+ "admin.email.allowEmailSignInDescription": "When true, Mattermost allows users to sign in using their email and password.",
+ "admin.email.allowUsernameSignInTitle": "Allow Sign In With Username: ",
+ "admin.email.allowUsernameSignInDescription": "When true, Mattermost allows users to sign in using their username and password. This setting is typically only used when email verification is disabled.",
"admin.email.notificationsTitle": "Send Email Notifications: ",
"admin.email.notificationsDescription": "Typically set to true in production. When true, Mattermost attempts to send email notifications. Developers may set this field to false to skip email setup for faster development.<br />Setting this to true removes the Preview Mode banner (requires logging out and logging back in after setting is changed).",
"admin.email.requireVerificationTitle": "Require Email Verification: ",
@@ -487,6 +491,40 @@
"change_url.noUnderscore": "Can not contain two underscores in a row.",
"change_url.invalidUrl": "Invalid URL",
"change_url.close": "Close",
+ "channel_header.recentMentions": "Recent Mentions",
+ "channel_header.channel": "Channel",
+ "channel_header.group": "Group",
+ "channel_header.channelHeader": "Set Channel Header...",
+ "channel_header.viewInfo": "View Info",
+ "chanel_header.addMembers": "Add Members",
+ "channel_header.manageMembers": "Manage Members",
+ "channel_header.setHeader": "Set {term} Header...",
+ "channel_header.setPurpose": "Set {term} Purpose...",
+ "channel_header.notificationPreferences": "Notification Preferences",
+ "channel_header.rename": "Rename {term}...",
+ "channel_header.delete": "Delete {term}...",
+ "channel_header.leave": "Leave {term}",
+ "channel_info.notFound": "No Channel Found",
+ "channel_info.name": "Channel Name:",
+ "channel_info.url": "Channel URL:",
+ "channel_info.id": "Channel ID:",
+ "channel_info.purpose": "Channel Purpose:",
+ "channel_info.close": "Close",
+ "channel_invite.addNewMembers": "Add New Members to ",
+ "channel_invite.close": "Close",
+ "channel_memebers_modal.members": " Members",
+ "channel_members_modal.addNew": " Add New Members",
+ "channel_members_modal.close": "Close",
+ "channel_notifications.allActivity": "For all activity",
+ "channel_notifications.onlyMentions": "Only for mentions",
+ "channel_notifications.never": "Never",
+ "channel_notifications.sendDesktop": "Send desktop notifications",
+ "channel_notifications.globalDefault": "Global default ({notifyLevel}",
+ "channel_notifications.override": "Selecting an option other than \"Default\" will override the global notification settings. Desktop notifications are available on Firefox, Safari, and Chrome.",
+ "channel_notifications.markUnread": "Mark Channel Unread",
+ "channel_notifications.allUnread": "For all unread messages",
+ "channel_notifications.unreadInfo": "The channel name is bolded in the sidebar when there are unread messages. Selecting \"Only for mentions\" will bold the channel only when you are mentioned.",
+ "channel_notifications.preferences": "Notification Preferences for ",
"claim.account.noEmail": "No email specified",
"claim.email_to_sso.pwdError": "Please enter your password.",
"claim.email_to_sso.pwd": "Password",
@@ -509,6 +547,36 @@
"create_comment.commentTitle": "Comment",
"create_comment.file": "File uploading",
"create_comment.files": "Files uploading",
+ "delete_channel.channel": "channel",
+ "delete_channel.group": "group",
+ "delete_channel.confirm": "Confirm DELETE Channel",
+ "delete_channel.question": "Are you sure you wish to delete the {display_name} {term}?",
+ "delete_channel.cancel": "Cancel",
+ "delete_channel.del": "Delete",
+ "delete_post.warning": "This post has {count} comment(s) on it.",
+ "delete_post.comment": "Comment",
+ "delete_post.post": "Post",
+ "delete_post.confirm": "Confirm {term} Delete",
+ "delete_post.question": "Are you sure you want to delete this ${term}?",
+ "delete_post.cancel": "Cancel",
+ "delete_post.del": "Delete",
+ "edit_channel_header_modal.error": "This channel header is too long, please enter a shorter one",
+ "edit_channel_header_modal.title": "Edit Header for {channel}",
+ "edit_channel_header_modal.description": "Edit the text appearing next to the channel name in the channel header.",
+ "edit_channel_header_modal.cancel": "Cancel",
+ "edit_channel_header_modal.save": "Save",
+ "edit_channel_purpose_modal.error": "This channel purpose is too long, please enter a shorter one",
+ "edit_channel_purpose_modal.title1": "Edit Purpose",
+ "edit_channel_purpose_modal.title2": "Edit Purpose for ",
+ "edit_channel_purpose_modal.channel": "Channel",
+ "edit_channel_purpose_modal.group": "Group",
+ "edit_channel_purpose_modal.body": "Describe how this {type} should be used. This text appears in the channel list in the \"More...\" menu and helps others decide whether to join.",
+ "edit_channel_purpose_modal.cancel": "Cancel",
+ "edit_channel_purpose_modal.save": "Save",
+ "edit_post.editPost": "Edit the post...",
+ "edit_post.edit": "Edit {title}",
+ "edit_post.cancel": "Cancel",
+ "edit_post.save": "Save",
"email_verify.verified": "{siteName} Email Verified",
"email_verify.verifiedBody": "<p>Your email has been verified! Click <a href={url}>here</a> to log in.</p>",
"email_verify.almost": "{siteName}: You are almost done",
@@ -533,6 +601,7 @@
"get_link.close": "Close",
"get_team_invite_link_modal.title": "Team Invite Link",
"get_team_invite_link_modal.help": "Send teammates the link below for them to sign-up to this team site.",
+ "get_team_invite_link_modal.helpDisabled": "User creation has been disabled for your team. Please ask your team administrator for details.",
"invite_member.emailError": "Please enter a valid email address",
"invite_member.firstname": "First name",
"invite_member.lastname": "Last name",
@@ -557,6 +626,14 @@
"login_email.email": "Email",
"login_email.pwd": "Password",
"login_email.signin": "Sign in",
+ "login_username.badTeam": "Bad team name",
+ "login_username.usernameReq": "A username is required",
+ "login_username.pwdReq": "A password is required",
+ "login_username.verifyEmailError": "Please verify your email address. Check your inbox for an email.",
+ "login_username.userNotFoundError": "We couldn't find an existing account matching your username for this team.",
+ "login_username.username": "Username",
+ "login_username.pwd": "Password",
+ "login_username.signin": "Sign in",
"login_ldap.badTeam": "Bad team name",
"login_ldap.idlReq": "An LDAP ID is required",
"login_ldap.pwdReq": "An LDAP password is required",
@@ -575,6 +652,10 @@
"login.find": "Find your other teams",
"login.signTo": "Sign in to:",
"login.on": "on {siteName}",
+ "member_item.add": " Add",
+ "member_item.makeAdmin": "Make Admin",
+ "member_item.removeMember": "Remove Member",
+ "member_item.member": "Member",
"member_team_item.member": "Member",
"member_team_item.systemAdmin": "System Admin",
"member_team_item.teamAdmin": "Team Admin",
@@ -650,6 +731,11 @@
"password_send.title": "Password Reset",
"password_send.description": "To reset your password, enter the email address you used to sign up for {teamName}.",
"password_send.reset": "Reset my password",
+ "members_popover.msg": "Message",
+ "members_popover.title": "Members",
+ "post_delete.notPosted": "Comment could not be posted",
+ "post_delete.someone": "Someone deleted the message on which you tried to post a comment.",
+ "post_delete.okay": "Okay",
"register_app.required": "Required",
"register_app.optional": "Optional",
"register_app.nameError": "Application name must be filled in.",
@@ -669,6 +755,23 @@
"register_app.credentialsSave": "I have saved both my Client Id and Client Secret somewhere safe",
"register_app.close": "Close",
"register_app.dev": "Developer Applications",
+ "removed_channel.channelName": "the channel",
+ "removed_channel.someone": "Someone",
+ "removed_channel.from": "Removed from ",
+ "removed_channel.remover": "{remover} removed you from {channel}",
+ "removed_channel.okay": "Okay",
+ "rename_channel.required": "This field is required",
+ "rename_channel.maxLength": "This field must be less than 22 characters",
+ "rename_channel.lowercase": "Must be lowercase alphanumeric characters",
+ "rename_channel.handle": "Handle",
+ "rename_channel.defaultError": " - Cannot be changed for the default channel",
+ "rename_channel.displayNameHolder": "Enter display name",
+ "rename_channel.handleHolder": "lowercase alphanumeric&#39;s only",
+ "rename_channel.close": "Close",
+ "rename_channel.title": "Rename Channel",
+ "rename_channel.displayName": "Display Name",
+ "rename_channel.cancel": "Cancel",
+ "rename_channel.save": "Save",
"rhs_comment.comment": "Comment",
"rhs_comment.edit": "Edit",
"rhs_comment.del": "Delete",
diff --git a/web/static/i18n/es.json b/web/static/i18n/es.json
index 01d703416..92f3ba2ea 100644
--- a/web/static/i18n/es.json
+++ b/web/static/i18n/es.json
@@ -474,6 +474,7 @@
"authorize.app": "La app {appName} quiere tener la abilidad de accesar y modificar tu información básica.",
"authorize.deny": "Denegar",
"authorize.title": "Una aplicación quiere conectarse con tu cuenta de {teamName}",
+ "chanel_header.addMembers": "Agregar Miembros",
"change_url.close": "Cerrar",
"change_url.endWithLetter": "Debe terminar con una letra o número",
"change_url.invalidUrl": "URL Inválida",
@@ -488,6 +489,29 @@
"channel_flow.group": "Grupo",
"channel_flow.invalidName": "Nombre de Canal Inválido",
"channel_flow.set_url_title": "Asignar URL de {term}",
+ "channel_header.channel": "Canal",
+ "channel_header.channelHeader": "Encabezado del Canal...",
+ "channel_header.delete": "Eliminar {term}...",
+ "channel_header.group": "Grupo",
+ "channel_header.leave": "Abondanar {term}",
+ "channel_header.manageMembers": "Administrar Miembros",
+ "channel_header.notificationPreferences": "Preferencias de Notificación",
+ "channel_header.recentMentions": "Menciones recientes",
+ "channel_header.rename": "Renombrar {term}...",
+ "channel_header.setHeader": "Encabezado del {term}...",
+ "channel_header.setPurpose": "Propósito del {term}...",
+ "channel_header.viewInfo": "Ver Info",
+ "channel_info.close": "Cerrar",
+ "channel_info.id": "ID del Canal:",
+ "channel_info.name": "Nombre del Canal:",
+ "channel_info.notFound": "Canal no encontrado",
+ "channel_info.purpose": "Propósito del Canal:",
+ "channel_info.url": "URL del Canal:",
+ "channel_invite.addNewMembers": "Agregar nuevos Miembros a ",
+ "channel_invite.close": "Cerrar",
+ "channel_members_modal.addNew": " Agregar nuevos Miembros",
+ "channel_members_modal.close": "Cerrar",
+ "channel_memebers_modal.members": " Miembros",
"channel_modal.cancel": "Cancelar",
"channel_modal.channel": "Canal",
"channel_modal.createNew": "Crear Nuevo ",
@@ -504,6 +528,16 @@
"channel_modal.publicChannel1": "Crear un canal público",
"channel_modal.publicChannel2": "Crear un canal público al que cualquiera puede unirse. ",
"channel_modal.purpose": "Propósito",
+ "channel_notifications.allActivity": "Para toda actividad",
+ "channel_notifications.allUnread": "Para todos los mensajes sin leer",
+ "channel_notifications.globalDefault": "Predeterminado global ({notifyLevel})",
+ "channel_notifications.markUnread": "Marcar Canal como No Leido",
+ "channel_notifications.never": "Nunca",
+ "channel_notifications.onlyMentions": "Sólo para menciones",
+ "channel_notifications.override": "Seleccionar una opción diferente a \"Predeterminada\" anulará las configuraciones globales de notificación. Las notificaciones de Escritorio están disponibles para Firefox, Safari, y Chrome.",
+ "channel_notifications.preferences": "Preferencias de Notificación de ",
+ "channel_notifications.sendDesktop": "Enviar notificaciones de escritorio",
+ "channel_notifications.unreadInfo": "El nombre del canal está en negritas en la barra lateral cuando hay mensajes sin leer. Al elegir \"Sólo para menciones\" sólo lo dejará en negritas cuando seas mencionado.",
"choose_auth_page.emailCreate": "Crea un nuevo equipo con tu cuenta de correo",
"choose_auth_page.find": "Encontrar mi equipo",
"choose_auth_page.gitlabCreate": "Crear un nuevo equipo con una cuenta de GitLab",
@@ -531,6 +565,36 @@
"create_comment.commentTitle": "Comentario",
"create_comment.file": "Subiendo archivo",
"create_comment.files": "Subiendo archivos",
+ "delete_channel.cancel": "Cancelar",
+ "delete_channel.channel": "canal",
+ "delete_channel.confirm": "Confirmar la ELIMINACIÓN del Canal",
+ "delete_channel.del": "Eliminar",
+ "delete_channel.group": "grupo",
+ "delete_channel.question": "¿Estás seguro de querer eliminar el {term} {display_name}?",
+ "delete_post.cancel": "Cancelar",
+ "delete_post.comment": "Comentario",
+ "delete_post.confirm": "Confirmar Eliminación del {term}",
+ "delete_post.del": "Eliminar",
+ "delete_post.post": "Mensaje",
+ "delete_post.question": "¿Estás seguro(a) de querer eliminar este {term}?",
+ "delete_post.warning": "Este mensaje tiene {count} comentario(s).",
+ "edit_channel_header_modal.cancel": "Cancelar",
+ "edit_channel_header_modal.description": "Edita el texto que aparece al lado del nombre del canal en el encabezado del canal.",
+ "edit_channel_header_modal.error": "Este encabezado es demasiado largo, por favor ingresa uno más corto",
+ "edit_channel_header_modal.save": "Guardar",
+ "edit_channel_header_modal.title": "Edita el Encabezado de {channel}",
+ "edit_channel_purpose_modal.body": "Describe como este {type} debería ser usado. Este texto aparace en la lista de canales dentro del menú de \"Más...\" y ayuda a otros en decidir si unirse.",
+ "edit_channel_purpose_modal.cancel": "Cancelar",
+ "edit_channel_purpose_modal.channel": "Canal",
+ "edit_channel_purpose_modal.error": "El propósito de este canal es muy largo, por favor ingresa uno más corto",
+ "edit_channel_purpose_modal.group": "Grupo",
+ "edit_channel_purpose_modal.save": "Guardar",
+ "edit_channel_purpose_modal.title1": "Editar Propósito",
+ "edit_channel_purpose_modal.title2": "Editar el Propósito de ",
+ "edit_post.cancel": "Cancelar",
+ "edit_post.edit": "Editar {title}",
+ "edit_post.editPost": "Editar el mensaje...",
+ "edit_post.save": "Guardar",
"email_signup.address": "Correo electrónico",
"email_signup.createTeam": "Crear Equipo",
"email_signup.emailError": "Por favor ingresa una dirección de correos válida",
@@ -619,6 +683,10 @@
"login_ldap.pwdReq": "La contraseña LDAP es obligatoria",
"login_ldap.signin": "Entrar",
"login_ldap.username": "Usuario LDAP",
+ "member_item.add": " Agregar",
+ "member_item.makeAdmin": "Convertir en Admin de Equipo",
+ "member_item.member": "Miembro",
+ "member_item.removeMember": "Elminar Miembro",
"member_team_item.inactive": "Inactivo",
"member_team_item.makeActive": "Activar",
"member_team_item.makeAdmin": "Convertir a Admin de Equipo",
@@ -627,6 +695,8 @@
"member_team_item.member": "Miembro",
"member_team_item.systemAdmin": "Administrador de Sistema",
"member_team_item.teamAdmin": "Admin de Equipo",
+ "members_popover.msg": "Mensaje",
+ "members_popover.title": "Miembros",
"more_channels.close": "Cerrar",
"more_channels.create": "Crear Nuevo Canal",
"more_channels.createClick": "Pincha 'Crear Nuevo Canal' para crear uno nuevo",
@@ -670,6 +740,9 @@
"password_send.link": "<p>Se ha enviado un enlace para restablecer la contraseña a <b>{email}</b> para tu equipo <b>{teamDisplayName}</b> en {hostname}.</p>",
"password_send.reset": "Restablecer mi contraseña",
"password_send.title": "Restablecer Contraseña",
+ "post_delete.notPosted": "No se pudo enviar el comentario",
+ "post_delete.okay": "Ok",
+ "post_delete.someone": "Alguien borró el mensaje que querías comentar.",
"register_app.callback": "Callback URL",
"register_app.callbackError": "Al menos un callback URL debe ser ingresado.",
"register_app.cancel": "Cancelar",
@@ -689,6 +762,23 @@
"register_app.register": "Registrar",
"register_app.required": "Requerido",
"register_app.title": "Registra una Nueva Aplicación",
+ "removed_channel.channelName": "el canal",
+ "removed_channel.from": "Removido de ",
+ "removed_channel.okay": "OK",
+ "removed_channel.remover": "{remover} te removió de {channel}",
+ "removed_channel.someone": "Alguien",
+ "rename_channel.cancel": "Cancelar",
+ "rename_channel.close": "Cerrado",
+ "rename_channel.defaultError": " - No se puede cambiar el del canal predeterminado",
+ "rename_channel.displayName": "Nombre a Mostrar",
+ "rename_channel.displayNameHolder": "Ingresa el nombre a mostrar",
+ "rename_channel.handle": "Identificador",
+ "rename_channel.handleHolder": "Sólo caracteres alfanumericos y en minúscula",
+ "rename_channel.lowercase": "Debe tener caracteres alfanumericos y minúscula",
+ "rename_channel.maxLength": "Este campo debe tener menos de 22 caracteres",
+ "rename_channel.required": "Este campo es obligatorio",
+ "rename_channel.save": "Guardar",
+ "rename_channel.title": "Renombrar Canal",
"rhs_comment.comment": "Comentario",
"rhs_comment.del": "Borrar",
"rhs_comment.edit": "Editar",