summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--api/admin_test.go14
-rw-r--r--api/api_test.go2
-rw-r--r--api/channel_benchmark_test.go2
-rw-r--r--api/channel_test.go58
-rw-r--r--api/command_test.go12
-rw-r--r--api/file_test.go10
-rw-r--r--api/oauth_test.go4
-rw-r--r--api/post_test.go40
-rw-r--r--api/preference_test.go14
-rw-r--r--api/team_test.go24
-rw-r--r--api/user.go34
-rw-r--r--api/user_test.go76
-rw-r--r--api/web_socket_test.go4
-rw-r--r--api/webhook_test.go14
-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.json36
-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/about_build_modal.jsx65
-rw-r--r--web/react/components/access_history_modal.jsx311
-rw-r--r--web/react/components/activity_log_modal.jsx99
-rw-r--r--web/react/components/admin_console/analytics.jsx20
-rw-r--r--web/react/components/admin_console/email_settings.jsx84
-rw-r--r--web/react/components/admin_console/user_item.jsx155
-rw-r--r--web/react/components/center_panel.jsx2
-rw-r--r--web/react/components/change_url_modal.jsx54
-rw-r--r--web/react/components/confirm_modal.jsx6
-rw-r--r--web/react/components/create_comment.jsx52
-rw-r--r--web/react/components/file_upload.jsx38
-rw-r--r--web/react/components/file_upload_overlay.jsx9
-rw-r--r--web/react/components/get_link_modal.jsx45
-rw-r--r--web/react/components/get_team_invite_link_modal.jsx37
-rw-r--r--web/react/components/invite_member_modal.jsx126
-rw-r--r--web/react/components/login.jsx15
-rw-r--r--web/react/components/login_username.jsx181
-rw-r--r--web/react/components/member_list_team_item.jsx50
-rw-r--r--web/react/components/more_channels.jsx45
-rw-r--r--web/react/components/more_direct_channels.jsx71
-rw-r--r--web/react/components/msg_typing.jsx42
-rw-r--r--web/react/components/navbar.jsx8
-rw-r--r--web/react/components/navbar_dropdown.jsx68
-rw-r--r--web/react/components/new_channel_flow.jsx66
-rw-r--r--web/react/components/new_channel_modal.jsx118
-rw-r--r--web/react/components/post_info.jsx41
-rw-r--r--web/react/components/register_app_modal.jsx157
-rw-r--r--web/react/components/rhs_comment.jsx41
-rw-r--r--web/react/components/rhs_header_post.jsx11
-rw-r--r--web/react/components/rhs_root_post.jsx29
-rw-r--r--web/react/components/search_bar.jsx39
-rw-r--r--web/react/components/search_results.jsx32
-rw-r--r--web/react/components/search_results_header.jsx17
-rw-r--r--web/react/components/search_results_item.jsx26
-rw-r--r--web/react/components/setting_item_max.jsx12
-rw-r--r--web/react/components/setting_item_min.jsx7
-rw-r--r--web/react/components/setting_picture.jsx32
-rw-r--r--web/react/components/setting_upload.jsx21
-rw-r--r--web/react/components/sidebar.jsx114
-rw-r--r--web/react/components/sidebar_header.jsx23
-rw-r--r--web/react/components/sidebar_right_menu.jsx68
-rw-r--r--web/react/components/team_export_tab.jsx44
-rw-r--r--web/react/components/team_general_tab.jsx178
-rw-r--r--web/react/components/team_import_tab.jsx67
-rw-r--r--web/react/components/team_members_modal.jsx15
-rw-r--r--web/react/components/team_settings_modal.jsx34
-rw-r--r--web/react/components/textbox.jsx20
-rw-r--r--web/react/components/unread_channel_indicator.jsx2
-rw-r--r--web/react/components/user_profile.jsx8
-rw-r--r--web/react/components/user_settings/custom_theme_chooser.jsx104
-rw-r--r--web/react/components/user_settings/import_theme_modal.jsx44
-rw-r--r--web/react/components/user_settings/manage_incoming_hooks.jsx49
-rw-r--r--web/react/components/user_settings/manage_languages.jsx14
-rw-r--r--web/react/components/user_settings/manage_outgoing_hooks.jsx131
-rw-r--r--web/react/components/user_settings/user_settings_advanced.jsx101
-rw-r--r--web/react/components/user_settings/user_settings_appearance.jsx40
-rw-r--r--web/react/components/user_settings/user_settings_developer.jsx42
-rw-r--r--web/react/components/user_settings/user_settings_display.jsx140
-rw-r--r--web/react/components/user_settings/user_settings_general.jsx254
-rw-r--r--web/react/components/user_settings/user_settings_integrations.jsx51
-rw-r--r--web/react/components/user_settings/user_settings_modal.jsx84
-rw-r--r--web/react/components/user_settings/user_settings_notifications.jsx226
-rw-r--r--web/react/components/user_settings/user_settings_security.jsx153
-rw-r--r--web/react/package.json14
-rw-r--r--web/react/stores/user_store.jsx10
-rw-r--r--web/react/utils/client.jsx22
-rw-r--r--web/sass-files/sass/partials/_post.scss4
-rw-r--r--web/sass-files/sass/partials/_responsive.scss40
-rw-r--r--web/static/i18n/en.json476
-rw-r--r--web/static/i18n/es.json466
-rw-r--r--web/web_test.go4
94 files changed, 4806 insertions, 854 deletions
diff --git a/api/admin_test.go b/api/admin_test.go
index c2f4e9c76..2552e642c 100644
--- a/api/admin_test.go
+++ b/api/admin_test.go
@@ -17,7 +17,7 @@ func TestGetLogs(t *testing.T) {
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
- user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
@@ -61,7 +61,7 @@ func TestGetConfig(t *testing.T) {
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
- user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
@@ -95,7 +95,7 @@ func TestSaveConfig(t *testing.T) {
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
- user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
@@ -129,7 +129,7 @@ func TestEmailTest(t *testing.T) {
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
- user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
@@ -157,7 +157,7 @@ func TestGetTeamAnalyticsStandard(t *testing.T) {
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
- user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
@@ -279,7 +279,7 @@ func TestGetPostCount(t *testing.T) {
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
- user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
@@ -324,7 +324,7 @@ func TestUserCountsWithPostsByDay(t *testing.T) {
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
- user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
diff --git a/api/api_test.go b/api/api_test.go
index 4d7192e4b..94691ab4b 100644
--- a/api/api_test.go
+++ b/api/api_test.go
@@ -30,7 +30,7 @@ func SetupBenchmark() (*model.Team, *model.User, *model.Channel) {
team := &model.Team{DisplayName: "Benchmark Team", Name: "z-z-" + model.NewId() + "a", Email: "benchmark@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
- user := &model.User{TeamId: team.Id, Email: model.NewId() + "benchmark@test.com", Nickname: "Mr. Benchmarker", Password: "pwd"}
+ user := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Mr. Benchmarker", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
Client.LoginByEmail(team.Name, user.Email, "pwd")
diff --git a/api/channel_benchmark_test.go b/api/channel_benchmark_test.go
index d6e1e5a55..09c734cc2 100644
--- a/api/channel_benchmark_test.go
+++ b/api/channel_benchmark_test.go
@@ -138,7 +138,7 @@ func BenchmarkJoinChannel(b *testing.B) {
}
// Secondary test user to join channels created by primary test user
- user := &model.User{TeamId: team.Id, Email: model.NewId() + "random@test.com", Nickname: "That Guy", Password: "pwd"}
+ user := &model.User{TeamId: team.Id, Email: "success+" + model.NewId() + "@simulator.amazonses.com", Nickname: "That Guy", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
Client.LoginByEmail(team.Name, user.Email, "pwd")
diff --git a/api/channel_test.go b/api/channel_test.go
index 117278378..c3015f924 100644
--- a/api/channel_test.go
+++ b/api/channel_test.go
@@ -22,7 +22,7 @@ func TestCreateChannel(t *testing.T) {
team2 := &model.Team{DisplayName: "Name Team 2", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team2 = Client.Must(Client.CreateTeam(team2)).Data.(*model.Team)
- user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
@@ -99,11 +99,11 @@ func TestCreateDirectChannel(t *testing.T) {
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
- user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
- user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user2.Id))
@@ -158,11 +158,11 @@ func TestUpdateChannel(t *testing.T) {
userTeamAdmin = Client.Must(Client.CreateUser(userTeamAdmin, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(userTeamAdmin.Id))
- userChannelAdmin := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ userChannelAdmin := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
userChannelAdmin = Client.Must(Client.CreateUser(userChannelAdmin, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(userChannelAdmin.Id))
- userStd := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ userStd := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
userStd = Client.Must(Client.CreateUser(userStd, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(userStd.Id))
userStd.Roles = ""
@@ -236,7 +236,7 @@ func TestUpdateChannelHeader(t *testing.T) {
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
- user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
@@ -276,7 +276,7 @@ func TestUpdateChannelHeader(t *testing.T) {
t.Fatal("should have errored on bad channel header")
}
- user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user2.Id))
@@ -295,7 +295,7 @@ func TestUpdateChannelPurpose(t *testing.T) {
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
- user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
@@ -335,7 +335,7 @@ func TestUpdateChannelPurpose(t *testing.T) {
t.Fatal("should have errored on bad channel purpose")
}
- user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user2.Id))
@@ -354,7 +354,7 @@ func TestGetChannel(t *testing.T) {
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
- user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
@@ -417,7 +417,7 @@ func TestGetMoreChannel(t *testing.T) {
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user1.Id))
@@ -429,7 +429,7 @@ func TestGetMoreChannel(t *testing.T) {
channel2 := &model.Channel{DisplayName: "B Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
channel2 = Client.Must(Client.CreateChannel(channel2)).Data.(*model.Channel)
- user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user2.Id))
@@ -461,7 +461,7 @@ func TestGetChannelCounts(t *testing.T) {
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
- user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
@@ -502,7 +502,7 @@ func TestJoinChannel(t *testing.T) {
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user1.Id))
@@ -514,7 +514,7 @@ func TestJoinChannel(t *testing.T) {
channel3 := &model.Channel{DisplayName: "B Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_PRIVATE, TeamId: team.Id}
channel3 = Client.Must(Client.CreateChannel(channel3)).Data.(*model.Channel)
- user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user2.Id))
@@ -530,7 +530,7 @@ func TestJoinChannel(t *testing.T) {
data["user_id"] = user1.Id
rchannel := Client.Must(Client.CreateDirectChannel(data)).Data.(*model.Channel)
- user3 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user3 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user3 = Client.Must(Client.CreateUser(user3, "")).Data.(*model.User)
Client.LoginByEmail(team.Name, user3.Email, "pwd")
@@ -546,7 +546,7 @@ func TestLeaveChannel(t *testing.T) {
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user1.Id))
@@ -558,7 +558,7 @@ func TestLeaveChannel(t *testing.T) {
channel3 := &model.Channel{DisplayName: "B Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_PRIVATE, TeamId: team.Id}
channel3 = Client.Must(Client.CreateChannel(channel3)).Data.(*model.Channel)
- user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user2.Id))
@@ -599,7 +599,7 @@ func TestDeleteChannel(t *testing.T) {
userTeamAdmin = Client.Must(Client.CreateUser(userTeamAdmin, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(userTeamAdmin.Id))
- userChannelAdmin := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ userChannelAdmin := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
userChannelAdmin = Client.Must(Client.CreateUser(userChannelAdmin, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(userChannelAdmin.Id))
@@ -631,7 +631,7 @@ func TestDeleteChannel(t *testing.T) {
t.Fatal("should have failed to post to deleted channel")
}
- userStd := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ userStd := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
userStd = Client.Must(Client.CreateUser(userStd, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(userStd.Id))
@@ -665,7 +665,7 @@ func TestGetChannelExtraInfo(t *testing.T) {
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
- user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
@@ -701,7 +701,7 @@ func TestGetChannelExtraInfo(t *testing.T) {
Client2 := model.NewClient("http://localhost" + utils.Cfg.ServiceSettings.ListenAddress)
- user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "tester2@test.com", Nickname: "Tester 2", Password: "pwd"}
+ user2 := &model.User{TeamId: team.Id, Email: "success+" + model.NewId() + "@simulator.amazonses.com", Nickname: "Tester 2", Password: "pwd"}
user2 = Client2.Must(Client2.CreateUser(user2, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user2.Id))
@@ -789,7 +789,7 @@ func TestAddChannelMember(t *testing.T) {
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user1.Id))
@@ -798,7 +798,7 @@ func TestAddChannelMember(t *testing.T) {
channel1 := &model.Channel{DisplayName: "A Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel)
- user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user2.Id))
@@ -851,7 +851,7 @@ func TestRemoveChannelMember(t *testing.T) {
userTeamAdmin = Client.Must(Client.CreateUser(userTeamAdmin, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(userTeamAdmin.Id))
- userChannelAdmin := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ userChannelAdmin := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
userChannelAdmin = Client.Must(Client.CreateUser(userChannelAdmin, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(userChannelAdmin.Id))
@@ -867,7 +867,7 @@ func TestRemoveChannelMember(t *testing.T) {
channel1 := &model.Channel{DisplayName: "A Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel)
- userStd := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ userStd := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
userStd = Client.Must(Client.CreateUser(userStd, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(userStd.Id))
@@ -917,7 +917,7 @@ func TestUpdateNotifyProps(t *testing.T) {
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
- user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
@@ -1019,7 +1019,7 @@ func TestUpdateNotifyProps(t *testing.T) {
t.Fatal("Should have errored - bad mark unread level")
}
- user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User)
Client.LoginByEmail(team.Name, user2.Email, "pwd")
@@ -1039,7 +1039,7 @@ func TestFuzzyChannel(t *testing.T) {
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
- user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
diff --git a/api/command_test.go b/api/command_test.go
index b31aec03a..86eb297d5 100644
--- a/api/command_test.go
+++ b/api/command_test.go
@@ -19,7 +19,7 @@ func TestSuggestRootCommands(t *testing.T) {
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user1.Id))
@@ -68,7 +68,7 @@ func TestLogoutCommands(t *testing.T) {
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user1.Id))
@@ -86,7 +86,7 @@ func TestJoinCommands(t *testing.T) {
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user1.Id))
@@ -100,7 +100,7 @@ func TestJoinCommands(t *testing.T) {
channel2 = Client.Must(Client.CreateChannel(channel2)).Data.(*model.Channel)
Client.Must(Client.LeaveChannel(channel2.Id))
- user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user1.Id))
@@ -162,7 +162,7 @@ func TestEchoCommand(t *testing.T) {
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user1.Id))
@@ -200,7 +200,7 @@ func TestLoadTestUrlCommand(t *testing.T) {
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
- user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
diff --git a/api/file_test.go b/api/file_test.go
index b3fbd2a27..c3ece7199 100644
--- a/api/file_test.go
+++ b/api/file_test.go
@@ -27,7 +27,7 @@ func TestUploadFile(t *testing.T) {
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user1.Id))
@@ -142,7 +142,7 @@ func TestGetFile(t *testing.T) {
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user1.Id))
@@ -212,7 +212,7 @@ func TestGetFile(t *testing.T) {
team2 := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team2 = Client.Must(Client.CreateTeam(team2)).Data.(*model.Team)
- user2 := &model.User{TeamId: team2.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user2 := &model.User{TeamId: team2.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user2.Id))
@@ -320,11 +320,11 @@ func TestGetPublicLink(t *testing.T) {
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user1.Id))
- user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user2.Id))
diff --git a/api/oauth_test.go b/api/oauth_test.go
index 7d825ef5a..57772ccc5 100644
--- a/api/oauth_test.go
+++ b/api/oauth_test.go
@@ -18,7 +18,7 @@ func TestRegisterApp(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()) + "corey+test@test.com", Password: "pwd"}
+ user := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Password: "pwd"}
ruser := Client.Must(Client.CreateUser(&user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(ruser.Id))
@@ -75,7 +75,7 @@ func TestAllowOAuth(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()) + "corey+test@test.com", Password: "pwd"}
+ user := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Password: "pwd"}
ruser := Client.Must(Client.CreateUser(&user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(ruser.Id))
diff --git a/api/post_test.go b/api/post_test.go
index 72abcd5cc..1a9fd2579 100644
--- a/api/post_test.go
+++ b/api/post_test.go
@@ -21,11 +21,11 @@ func TestCreatePost(t *testing.T) {
team2 := &model.Team{DisplayName: "Name Team 2", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team2 = Client.Must(Client.CreateTeam(team2)).Data.(*model.Team)
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user1.Id))
- user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user2.Id))
@@ -103,7 +103,7 @@ func TestCreatePost(t *testing.T) {
t.Fatal("Should have been forbidden")
}
- user3 := &model.User{TeamId: team2.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user3 := &model.User{TeamId: team2.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user3 = Client.Must(Client.CreateUser(user3, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user3.Id))
@@ -132,11 +132,11 @@ func TestUpdatePost(t *testing.T) {
team2 := &model.Team{DisplayName: "Name Team 2", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team2 = Client.Must(Client.CreateTeam(team2)).Data.(*model.Team)
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user1.Id))
- user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user2.Id))
@@ -200,7 +200,7 @@ func TestGetPosts(t *testing.T) {
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user1.Id))
@@ -265,7 +265,7 @@ func TestGetPostsSince(t *testing.T) {
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user1.Id))
@@ -335,7 +335,7 @@ func TestGetPostsBeforeAfter(t *testing.T) {
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user1.Id))
@@ -412,7 +412,7 @@ func TestSearchPosts(t *testing.T) {
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user1.Id))
@@ -462,7 +462,7 @@ func TestSearchHashtagPosts(t *testing.T) {
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user1.Id))
@@ -493,7 +493,7 @@ func TestSearchPostsInChannel(t *testing.T) {
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user1.Id))
@@ -575,7 +575,7 @@ func TestSearchPostsFromUser(t *testing.T) {
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user1.Id))
@@ -590,7 +590,7 @@ func TestSearchPostsFromUser(t *testing.T) {
post1 := &model.Post{ChannelId: channel1.Id, Message: "sgtitlereview with space"}
post1 = Client.Must(Client.CreatePost(post1)).Data.(*model.Post)
- user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user2.Id))
@@ -620,7 +620,7 @@ func TestSearchPostsFromUser(t *testing.T) {
t.Fatalf("wrong number of posts returned %v", len(result.Order))
}
- user3 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user3 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user3 = Client.Must(Client.CreateUser(user3, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user3.Id))
@@ -657,7 +657,7 @@ func TestGetPostsCache(t *testing.T) {
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user1.Id))
@@ -710,7 +710,7 @@ func TestDeletePosts(t *testing.T) {
userAdmin = Client.Must(Client.CreateUser(userAdmin, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(userAdmin.Id))
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user1.Id))
@@ -767,7 +767,7 @@ func TestEmailMention(t *testing.T) {
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
- user1 := &model.User{TeamId: team.Id, Email: "corey+test@test.com", Nickname: "Bob Bobby", Password: "pwd"}
+ user1 := &model.User{TeamId: team.Id, Email: "success+test@simulator.amazonses.com", Nickname: "Bob Bobby", Password: "pwd"}
user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user1.Id))
@@ -789,7 +789,7 @@ func TestFuzzyPosts(t *testing.T) {
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
- user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
@@ -816,11 +816,11 @@ func TestMakeDirectChannelVisible(t *testing.T) {
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user1.Id))
- user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user2.Id))
diff --git a/api/preference_test.go b/api/preference_test.go
index 6bebe205c..82bee6315 100644
--- a/api/preference_test.go
+++ b/api/preference_test.go
@@ -15,11 +15,11 @@ func TestGetAllPreferences(t *testing.T) {
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user1.Id))
- user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user2.Id))
@@ -68,7 +68,7 @@ func TestSetPreferences(t *testing.T) {
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user1.Id))
@@ -99,7 +99,7 @@ func TestSetPreferences(t *testing.T) {
}
// not able to update as a different user
- user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user2.Id))
@@ -116,11 +116,11 @@ func TestGetPreferenceCategory(t *testing.T) {
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user1.Id))
- user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user2.Id))
@@ -172,7 +172,7 @@ func TestGetPreference(t *testing.T) {
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
- user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
diff --git a/api/team_test.go b/api/team_test.go
index cba043bbb..c942e2e1f 100644
--- a/api/team_test.go
+++ b/api/team_test.go
@@ -25,7 +25,7 @@ func TestCreateFromSignupTeam(t *testing.T) {
Setup()
props := make(map[string]string)
- props["email"] = strings.ToLower(model.NewId()) + "corey+test@test.com"
+ props["email"] = strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com"
props["name"] = "Test Company name"
props["time"] = fmt.Sprintf("%v", model.GetMillis())
@@ -35,7 +35,7 @@ func TestCreateFromSignupTeam(t *testing.T) {
team := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
user := model.User{Email: props["email"], Nickname: "Corey Hulen", Password: "hello"}
- ts := model.TeamSignup{Team: team, User: user, Invites: []string{"corey+test@test.com"}, Data: data, Hash: hash}
+ ts := model.TeamSignup{Team: team, User: user, Invites: []string{"success+test@simulator.amazonses.com"}, Data: data, Hash: hash}
rts, err := Client.CreateTeamFromSignup(&ts)
if err != nil {
@@ -77,7 +77,7 @@ func TestCreateTeam(t *testing.T) {
t.Fatal(err)
}
- user := &model.User{TeamId: rteam.Data.(*model.Team).Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := &model.User{TeamId: rteam.Data.(*model.Team).Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
@@ -114,7 +114,7 @@ func TestFindTeamByEmail(t *testing.T) {
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
- user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
@@ -141,7 +141,7 @@ func TestGetAllTeams(t *testing.T) {
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
- user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
@@ -174,7 +174,7 @@ func TestTeamPermDelete(t *testing.T) {
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user1.Id))
@@ -217,7 +217,7 @@ func TestFindTeamByDomain(t *testing.T) {
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
- user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
@@ -257,7 +257,7 @@ func TestFindTeamByEmailSend(t *testing.T) {
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
- user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
Client.LoginByEmail(team.Name, user.Email, "pwd")
@@ -282,14 +282,14 @@ func TestInviteMembers(t *testing.T) {
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
- user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
Client.LoginByEmail(team.Name, user.Email, "pwd")
invite := make(map[string]string)
- invite["email"] = model.NewId() + "corey+test@test.com"
+ invite["email"] = model.NewId() + "success+test@simulator.amazonses.com"
invite["first_name"] = "Test"
invite["last_name"] = "Guy"
invites := &model.Invites{Invites: []map[string]string{invite}}
@@ -315,7 +315,7 @@ func TestUpdateTeamDisplayName(t *testing.T) {
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
- user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user2.Id))
@@ -368,7 +368,7 @@ func TestGetMyTeam(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()) + "corey+test@test.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", Password: "pwd"}
ruser, _ := Client.CreateUser(&user, "")
store.Must(Srv.Store.User().VerifyEmail(ruser.Data.(*model.User).Id))
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 5f85bda0f..1a1cf9634 100644
--- a/api/user_test.go
+++ b/api/user_test.go
@@ -28,7 +28,7 @@ func TestCreateUser(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()) + "corey+test@test.com", Nickname: "Corey Hulen", Password: "hello"}
+ user := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "hello"}
ruser, err := Client.CreateUser(&user, "")
if err != nil {
@@ -79,7 +79,7 @@ func TestCreateUserAllowedDomains(t *testing.T) {
team := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_INVITE, AllowedDomains: "spinpunch.com, @nowh.com,@hello.com"}
rteam, _ := Client.CreateTeam(&team)
- user := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "corey+test@test.com", Nickname: "Corey Hulen", Password: "hello"}
+ user := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "hello"}
_, err := Client.CreateUser(&user, "")
if err == nil {
@@ -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()) + "corey+test@test.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"
@@ -139,7 +155,7 @@ func TestLogin(t *testing.T) {
team2 := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_INVITE}
rteam2 := Client.Must(Client.CreateTeam(&team2))
- user2 := model.User{TeamId: rteam2.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user2 := model.User{TeamId: rteam2.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
if _, err := Client.CreateUserFromSignup(&user2, "junk", "1231312"); err == nil {
t.Fatal("Should have errored, signed up without hashed email")
@@ -168,7 +184,7 @@ func TestLoginWithDeviceId(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()) + "corey+test@test.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", Password: "pwd"}
ruser := Client.Must(Client.CreateUser(&user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(ruser.Id))
@@ -199,7 +215,7 @@ func TestSessions(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()) + "corey+test@test.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", Password: "pwd"}
ruser := Client.Must(Client.CreateUser(&user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(ruser.Id))
@@ -251,18 +267,18 @@ func TestGetUser(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()) + "corey+test@test.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", Password: "pwd"}
ruser, _ := Client.CreateUser(&user, "")
store.Must(Srv.Store.User().VerifyEmail(ruser.Data.(*model.User).Id))
- user2 := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user2 := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
ruser2, _ := Client.CreateUser(&user2, "")
store.Must(Srv.Store.User().VerifyEmail(ruser2.Data.(*model.User).Id))
team2 := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
rteam2, _ := Client.CreateTeam(&team2)
- user3 := model.User{TeamId: rteam2.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user3 := model.User{TeamId: rteam2.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
ruser3, _ := Client.CreateUser(&user3, "")
store.Must(Srv.Store.User().VerifyEmail(ruser3.Data.(*model.User).Id))
@@ -343,7 +359,7 @@ func TestGetAudits(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()) + "corey+test@test.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", Password: "pwd"}
ruser, _ := Client.CreateUser(&user, "")
store.Must(Srv.Store.User().VerifyEmail(ruser.Data.(*model.User).Id))
@@ -396,7 +412,7 @@ func TestUserCreateImage(t *testing.T) {
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
- user := &model.User{TeamId: team.Id, Email: strings.ToLower(model.NewId()) + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := &model.User{TeamId: team.Id, Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
@@ -430,7 +446,7 @@ func TestUserUploadProfileImage(t *testing.T) {
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
- user := &model.User{TeamId: team.Id, Email: strings.ToLower(model.NewId()) + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := &model.User{TeamId: team.Id, Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
@@ -536,7 +552,7 @@ func TestUserUpdate(t *testing.T) {
time1 := model.GetMillis()
- user := &model.User{TeamId: team.Id, Email: strings.ToLower(model.NewId()) + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd", LastActivityAt: time1, LastPingAt: time1, Roles: ""}
+ user := &model.User{TeamId: team.Id, Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd", LastActivityAt: time1, LastPingAt: time1, Roles: ""}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
@@ -592,7 +608,7 @@ func TestUserUpdate(t *testing.T) {
t.Fatal("Should have errored")
}
- user2 := &model.User{TeamId: team.Id, Email: strings.ToLower(model.NewId()) + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user2 := &model.User{TeamId: team.Id, Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user2.Id))
@@ -611,7 +627,7 @@ func TestUserUpdatePassword(t *testing.T) {
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
- user := &model.User{TeamId: team.Id, Email: strings.ToLower(model.NewId()) + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := &model.User{TeamId: team.Id, Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
@@ -654,7 +670,7 @@ func TestUserUpdatePassword(t *testing.T) {
t.Fatal(err)
}
- user2 := &model.User{TeamId: team.Id, Email: strings.ToLower(model.NewId()) + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user2 := &model.User{TeamId: team.Id, Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User)
Client.LoginByEmail(team.Name, user2.Email, "pwd")
@@ -674,7 +690,7 @@ func TestUserUpdateRoles(t *testing.T) {
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
- user2 := &model.User{TeamId: team.Id, Email: strings.ToLower(model.NewId()) + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user2 := &model.User{TeamId: team.Id, Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user2.Id))
@@ -772,7 +788,7 @@ func TestUserUpdateActive(t *testing.T) {
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
- user2 := &model.User{TeamId: team.Id, Email: strings.ToLower(model.NewId()) + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user2 := &model.User{TeamId: team.Id, Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user2.Id))
@@ -832,7 +848,7 @@ func TestUserPermDelete(t *testing.T) {
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user1.Id))
@@ -871,7 +887,7 @@ func TestSendPasswordReset(t *testing.T) {
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
- user := &model.User{TeamId: team.Id, Email: strings.ToLower(model.NewId()) + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := &model.User{TeamId: team.Id, Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
@@ -904,7 +920,7 @@ func TestSendPasswordReset(t *testing.T) {
t.Fatal("Should have errored - bad name")
}
- user2 := &model.User{TeamId: team.Id, Email: strings.ToLower(model.NewId()) + "corey+test@test.com", Nickname: "Corey Hulen", AuthData: "1", AuthService: "random"}
+ user2 := &model.User{TeamId: team.Id, Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", AuthData: "1", AuthService: "random"}
user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user2.Id))
@@ -921,7 +937,7 @@ func TestResetPassword(t *testing.T) {
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
- user := &model.User{TeamId: team.Id, Email: strings.ToLower(model.NewId()) + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := &model.User{TeamId: team.Id, Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
@@ -998,7 +1014,7 @@ func TestResetPassword(t *testing.T) {
t.Fatal("Should have errored - domain team doesn't match user team")
}
- user2 := &model.User{TeamId: team.Id, Email: strings.ToLower(model.NewId()) + "corey+test@test.com", Nickname: "Corey Hulen", AuthData: "1", AuthService: "random"}
+ user2 := &model.User{TeamId: team.Id, Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", AuthData: "1", AuthService: "random"}
user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user2.Id))
@@ -1019,7 +1035,7 @@ func TestUserUpdateNotify(t *testing.T) {
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
- user := &model.User{TeamId: team.Id, Email: strings.ToLower(model.NewId()) + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd", Roles: ""}
+ user := &model.User{TeamId: team.Id, Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd", Roles: ""}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
@@ -1114,11 +1130,11 @@ func TestStatuses(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()) + "corey+test@test.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", Password: "pwd"}
ruser := Client.Must(Client.CreateUser(&user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(ruser.Id))
- user2 := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user2 := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
ruser2 := Client.Must(Client.CreateUser(&user2, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(ruser2.Id))
@@ -1151,7 +1167,7 @@ func TestSwitchToSSO(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()) + "corey+test@test.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", Password: "pwd"}
ruser := Client.Must(Client.CreateUser(&user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(ruser.Id))
@@ -1200,11 +1216,11 @@ func TestSwitchToEmail(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()) + "corey+test@test.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", Password: "pwd"}
ruser := Client.Must(Client.CreateUser(&user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(ruser.Id))
- user2 := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user2 := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
ruser2 := Client.Must(Client.CreateUser(&user2, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(ruser2.Id))
diff --git a/api/web_socket_test.go b/api/web_socket_test.go
index 24e860628..2c0ac61eb 100644
--- a/api/web_socket_test.go
+++ b/api/web_socket_test.go
@@ -20,7 +20,7 @@ func TestSocket(t *testing.T) {
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
- user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user1.Id))
Client.LoginByEmail(team.Name, user1.Email, "pwd")
@@ -39,7 +39,7 @@ func TestSocket(t *testing.T) {
t.Fatal(err)
}
- user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user2.Id))
Client.LoginByEmail(team.Name, user2.Email, "pwd")
diff --git a/api/webhook_test.go b/api/webhook_test.go
index 85117ec18..0a464656b 100644
--- a/api/webhook_test.go
+++ b/api/webhook_test.go
@@ -17,7 +17,7 @@ func TestCreateIncomingHook(t *testing.T) {
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
- user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
@@ -80,7 +80,7 @@ func TestListIncomingHooks(t *testing.T) {
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
- user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
@@ -118,7 +118,7 @@ func TestDeleteIncomingHook(t *testing.T) {
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
- user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
@@ -158,7 +158,7 @@ func TestCreateOutgoingHook(t *testing.T) {
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
- user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
@@ -221,7 +221,7 @@ func TestListOutgoingHooks(t *testing.T) {
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
- user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
@@ -259,7 +259,7 @@ func TestDeleteOutgoingHook(t *testing.T) {
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
- user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
@@ -299,7 +299,7 @@ func TestRegenOutgoingHookToken(t *testing.T) {
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
- user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
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 a5eb4f607..7b7ee344a 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -401,7 +401,7 @@
},
{
"id": "api.context.unknown.app_error",
- "translation": "An unknown error has occured. Please contact support."
+ "translation": "An unknown error has occurred. Please contact support."
},
{
"id": "api.export.json.app_error",
@@ -957,7 +957,7 @@
},
{
"id": "api.team.email_teams.sending.error",
- "translation": "An error occured while sending an email in emailTeams err=%v"
+ "translation": "An error occurred while sending an email in emailTeams err=%v"
},
{
"id": "api.team.export_team.admin.app_error",
@@ -3406,5 +3406,37 @@
{
"id": "web.watcher_fail.error",
"translation": "Failed to add directory to watcher %v"
+ },
+ {
+ "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.unable_to_connect.app_error",
+ "translation": "Unable to connect to LDAP server"
+ },
+ {
+ "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.search_ldap_server.app_error",
+ "translation": "Failed to search LDAP server"
+ },
+ {
+ "id": "ent.ldap.do_login.user_not_registered.app_error",
+ "translation": "User not registered on LDAP server"
+ },
+ {
+ "id": "ent.ldap.do_login.matched_to_many_users.app_error",
+ "translation": "Username given matches multiple users"
+ },
+ {
+ "id": "ent.ldap.do_login.invalid_password.app_error",
+ "translation": "Invalid Password"
+ },
+ {
+ "id": "ent.ldap.do_login.unable_to_create_user.app_error",
+ "translation": "Credentials valid but unable to create user."
}
]
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/about_build_modal.jsx b/web/react/components/about_build_modal.jsx
index f70027498..fe48bb48e 100644
--- a/web/react/components/about_build_modal.jsx
+++ b/web/react/components/about_build_modal.jsx
@@ -3,6 +3,8 @@
var Modal = ReactBootstrap.Modal;
+import {FormattedMessage} from 'mm-intl';
+
export default class AboutBuildModal extends React.Component {
constructor(props) {
super(props);
@@ -17,13 +19,28 @@ export default class AboutBuildModal extends React.Component {
const config = global.window.mm_config;
const license = global.window.mm_license;
- let title = 'Team Edition';
+ let title = (
+ <FormattedMessage
+ id='about.teamEdtion'
+ defaultMessage='Team Edition'
+ />
+ );
let licensee;
if (config.BuildEnterpriseReady === 'true' && license.IsLicensed === 'true') {
- title = 'Enterprise Edition';
+ title = (
+ <FormattedMessage
+ id='about.enterpriseEdition'
+ defaultMessage='Enterprise Edition'
+ />
+ );
licensee = (
<div className='row form-group'>
- <div className='col-sm-3 info__label'>{'Licensed by:'}</div>
+ <div className='col-sm-3 info__label'>
+ <FormattedMessage
+ id='about.licensed'
+ defaultMessage='Licensed by:'
+ />
+ </div>
<div className='col-sm-9'>{license.Company}</div>
</div>
);
@@ -35,25 +52,50 @@ export default class AboutBuildModal extends React.Component {
onHide={this.doHide}
>
<Modal.Header closeButton={true}>
- <Modal.Title>{'About Mattermost'}</Modal.Title>
+ <Modal.Title>
+ <FormattedMessage
+ id='about.title'
+ defaultMessage='About Mattermost'
+ />
+ </Modal.Title>
</Modal.Header>
<Modal.Body>
- <h4>{`Mattermost ${title}`}</h4>
+ <h4>{'Mattermost'} {title}</h4>
{licensee}
<div className='row form-group'>
- <div className='col-sm-3 info__label'>{'Version:'}</div>
+ <div className='col-sm-3 info__label'>
+ <FormattedMessage
+ id='about.version'
+ defaultMessage='Version:'
+ />
+ </div>
<div className='col-sm-9'>{config.Version}</div>
</div>
<div className='row form-group'>
- <div className='col-sm-3 info__label'>{'Build Number:'}</div>
+ <div className='col-sm-3 info__label'>
+ <FormattedMessage
+ id='about.number'
+ defaultMessage='Build Number:'
+ />
+ </div>
<div className='col-sm-9'>{config.BuildNumber}</div>
</div>
<div className='row form-group'>
- <div className='col-sm-3 info__label'>{'Build Date:'}</div>
+ <div className='col-sm-3 info__label'>
+ <FormattedMessage
+ id='about.date'
+ defaultMessage='Build Date:'
+ />
+ </div>
<div className='col-sm-9'>{config.BuildDate}</div>
</div>
<div className='row form-group'>
- <div className='col-sm-3 info__label'>{'Build Hash:'}</div>
+ <div className='col-sm-3 info__label'>
+ <FormattedMessage
+ id='about.hash'
+ defaultMessage='Build Hash:'
+ />
+ </div>
<div className='col-sm-9'>{config.BuildHash}</div>
</div>
</Modal.Body>
@@ -63,7 +105,10 @@ export default class AboutBuildModal extends React.Component {
className='btn btn-default'
onClick={this.doHide}
>
- {'Close'}
+ <FormattedMessage
+ id='about.close'
+ defaultMessage='Close'
+ />
</button>
</Modal.Footer>
</Modal>
diff --git a/web/react/components/access_history_modal.jsx b/web/react/components/access_history_modal.jsx
index 85c28ca5c..6319b5681 100644
--- a/web/react/components/access_history_modal.jsx
+++ b/web/react/components/access_history_modal.jsx
@@ -8,7 +8,188 @@ import * as AsyncClient from '../utils/async_client.jsx';
import LoadingScreen from './loading_screen.jsx';
import * as Utils from '../utils/utils.jsx';
-export default class AccessHistoryModal extends React.Component {
+import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl';
+
+const holders = defineMessages({
+ sessionRevoked: {
+ id: 'access_history.sessionRevoked',
+ defaultMessage: 'The session with id {sessionId} was revoked'
+ },
+ channelCreated: {
+ id: 'access_history.channelCreated',
+ defaultMessage: 'Created the {channelName} channel/group'
+ },
+ establishedDM: {
+ id: 'access_history.establishedDM',
+ defaultMessage: 'Established a direct message channel with {username}'
+ },
+ nameUpdated: {
+ id: 'access_history.nameUpdated',
+ defaultMessage: 'Updated the {channelName} channel/group name'
+ },
+ headerUpdated: {
+ id: 'access_history.headerUpdated',
+ defaultMessage: 'Updated the {channelName} channel/group header'
+ },
+ channelDeleted: {
+ id: 'access_history.channelDeleted',
+ defaultMessage: 'Deleted the channel/group with the URL {url}'
+ },
+ userAdded: {
+ id: 'access_history.userAdded',
+ defaultMessage: 'Added {username} to the {channelName} channel/group'
+ },
+ userRemoved: {
+ id: 'access_history.userRemoved',
+ defaultMessage: 'Removed {username} to the {channelName} channel/group'
+ },
+ attemptedRegisterApp: {
+ id: 'access_history.attemptedRegisterApp',
+ defaultMessage: 'Attempted to register a new OAuth Application with ID {id}'
+ },
+ attemptedAllowOAuthAccess: {
+ id: 'access_history.attemptedAllowOAuthAccess',
+ defaultMessage: 'Attempted to allow a new OAuth service access'
+ },
+ successfullOAuthAccess: {
+ id: 'access_history.successfullOAuthAccess',
+ defaultMessage: 'Successfully gave a new OAuth service access'
+ },
+ failedOAuthAccess: {
+ id: 'access_history.failedOAuthAccess',
+ defaultMessage: 'Failed to allow a new OAuth service access - the redirect URI did not match the previously registered callback'
+ },
+ attemptedOAuthToken: {
+ id: 'access_history.attemptedOAuthToken',
+ defaultMessage: 'Attempted to get an OAuth access token'
+ },
+ successfullOAuthToken: {
+ id: 'access_history.successfullOAuthToken',
+ defaultMessage: 'Successfully added a new OAuth service'
+ },
+ oauthTokenFailed: {
+ id: 'access_history.oauthTokenFailed',
+ defaultMessage: 'Failed to get an OAuth access token - {token}'
+ },
+ attemptedLogin: {
+ id: 'access_history.attemptedLogin',
+ defaultMessage: 'Attempted to login'
+ },
+ successfullLogin: {
+ id: 'access_history.successfullLogin',
+ defaultMessage: 'Successfully logged in'
+ },
+ failedLogin: {
+ id: 'access_history.failedLogin',
+ defaultMessage: 'FAILED login attempt'
+ },
+ updatePicture: {
+ id: 'access_history.updatePicture',
+ defaultMessage: 'Updated your profile picture'
+ },
+ updateGeneral: {
+ id: 'access_history.updateGeneral',
+ defaultMessage: 'Updated the general settings of your account'
+ },
+ attemptedPassword: {
+ id: 'access_history.attemptedPassword',
+ defaultMessage: 'Attempted to change password'
+ },
+ successfullPassword: {
+ id: 'access_history.successfullPassword',
+ defaultMessage: 'Successfully changed password'
+ },
+ failedPassword: {
+ id: 'access_history.failedPassword',
+ defaultMessage: 'Failed to change password - tried to update user password who was logged in through oauth'
+ },
+ updatedRol: {
+ id: 'access_history.updatedRol',
+ defaultMessage: 'Updated user role(s) to '
+ },
+ member: {
+ id: 'access_history.member',
+ defaultMessage: 'member'
+ },
+ accountActive: {
+ id: 'access_history.accountActive',
+ defaultMessage: 'Account made active'
+ },
+ accountInactive: {
+ id: 'access_history.accountInactive',
+ defaultMessage: 'Account made inactive'
+ },
+ by: {
+ id: 'access_history.by',
+ defaultMessage: ' by {username}'
+ },
+ byAdmin: {
+ id: 'access_history.byAdmin',
+ defaultMessage: ' by an admin'
+ },
+ sentEmail: {
+ id: 'access_history.sentEmail',
+ defaultMessage: 'Sent an email to {email} to reset your password'
+ },
+ attemptedReset: {
+ id: 'access_history.attemptedReset',
+ defaultMessage: 'Attempted to reset password'
+ },
+ successfullReset: {
+ id: 'access_history.successfullReset',
+ defaultMessage: 'Successfully reset password'
+ },
+ updateGlobalNotifications: {
+ id: 'access_history.updateGlobalNotifications',
+ defaultMessage: 'Updated your global notification settings'
+ },
+ attemptedWebhookCreate: {
+ id: 'access_history.attemptedWebhookCreate',
+ defaultMessage: 'Attempted to create a webhook'
+ },
+ succcessfullWebhookCreate: {
+ id: 'access_history.successfullWebhookCreate',
+ defaultMessage: 'Successfully created a webhook'
+ },
+ failedWebhookCreate: {
+ id: 'access_history.failedWebhookCreate',
+ defaultMessage: 'Failed to create a webhook - bad channel permissions'
+ },
+ attemptedWebhookDelete: {
+ id: 'access_history.attemptedWebhookDelete',
+ defaultMessage: 'Attempted to delete a webhook'
+ },
+ successfullWebhookDelete: {
+ id: 'access_history.successfullWebhookDelete',
+ defaultMessage: 'Successfully deleted a webhook'
+ },
+ failedWebhookDelete: {
+ id: 'access_history.failedWebhookDelete',
+ defaultMessage: 'Failed to delete a webhook - inappropriate conditions'
+ },
+ logout: {
+ id: 'access_history.logout',
+ defaultMessage: 'Logged out of your account'
+ },
+ verified: {
+ id: 'access_history.verified',
+ defaultMessage: 'Sucessfully verified your email address'
+ },
+ revokedAll: {
+ id: 'access_history.revokedAll',
+ defaultMessage: 'Revoked all current sessions for the team'
+ },
+ loginAttempt: {
+ id: 'access_history.loginAttempt',
+ defaultMessage: ' (Login attempt)'
+ },
+ loginFailure: {
+ id: 'access_history.loginFailure',
+ defaultMessage: ' (Login failure)'
+ }
+});
+
+class AccessHistoryModal extends React.Component {
constructor(props) {
super(props);
@@ -70,11 +251,12 @@ export default class AccessHistoryModal extends React.Component {
this.setState({moreInfo: newMoreInfo});
}
handleRevokedSession(sessionId) {
- return 'The session with id ' + sessionId + ' was revoked';
+ return this.props.intl.formatMessage(holders.sessionRevoked, {sessionId: sessionId});
}
formatAuditInfo(currentAudit) {
const currentActionURL = currentAudit.action.replace(/\/api\/v[1-9]/, '');
+ const {formatMessage} = this.props.intl;
let currentAuditDesc = '';
if (currentActionURL.indexOf('/channels') === 0) {
@@ -96,17 +278,17 @@ export default class AccessHistoryModal extends React.Component {
switch (currentActionURL) {
case '/channels/create':
- currentAuditDesc = 'Created the ' + channelName + ' channel/group';
+ currentAuditDesc = formatMessage(holders.channelCreated, {channelName: channelName});
break;
case '/channels/create_direct':
- currentAuditDesc = 'Established a direct message channel with ' + Utils.getDirectTeammate(channelObj.id).username;
+ currentAuditDesc = formatMessage(holders.establishedDM, {username: Utils.getDirectTeammate(channelObj.id).username});
break;
case '/channels/update':
- currentAuditDesc = 'Updated the ' + channelName + ' channel/group name';
+ currentAuditDesc = formatMessage(holders.nameUpdated, {channelName: channelName});
break;
case '/channels/update_desc': // support the old path
case '/channels/update_header':
- currentAuditDesc = 'Updated the ' + channelName + ' channel/group header';
+ currentAuditDesc = formatMessage(holders.headerUpdated, {channelName: channelName});
break;
default: {
let userIdField = [];
@@ -123,11 +305,11 @@ export default class AccessHistoryModal extends React.Component {
}
if (/\/channels\/[A-Za-z0-9]+\/delete/.test(currentActionURL)) {
- currentAuditDesc = 'Deleted the channel/group with the URL ' + channelURL;
+ currentAuditDesc = formatMessage(holders.channelDeleted, {url: channelURL});
} else if (/\/channels\/[A-Za-z0-9]+\/add/.test(currentActionURL)) {
- currentAuditDesc = 'Added ' + username + ' to the ' + channelName + ' channel/group';
+ currentAuditDesc = formatMessage(holders.userAdded, {username: username, channelName: channelName});
} else if (/\/channels\/[A-Za-z0-9]+\/remove/.test(currentActionURL)) {
- currentAuditDesc = 'Removed ' + username + ' from the ' + channelName + ' channel/group';
+ currentAuditDesc = formatMessage(holders.userRemoved, {username: username, channelName: channelName});
}
break;
@@ -141,31 +323,31 @@ export default class AccessHistoryModal extends React.Component {
const clientIdField = oauthInfo[0].split('=');
if (clientIdField[0] === 'client_id') {
- currentAuditDesc = 'Attempted to register a new OAuth Application with ID ' + clientIdField[1];
+ currentAuditDesc = formatMessage(holders.attemptedRegisterApp, {id: clientIdField[1]});
}
break;
}
case '/oauth/allow':
if (oauthInfo[0] === 'attempt') {
- currentAuditDesc = 'Attempted to allow a new OAuth service access';
+ currentAuditDesc = formatMessage(holders.attemptedAllowOAuthAccess);
} else if (oauthInfo[0] === 'success') {
- currentAuditDesc = 'Successfully gave a new OAuth service access';
+ currentAuditDesc = formatMessage(holders.successfullOAuthAccess);
} else if (oauthInfo[0] === 'fail - redirect_uri did not match registered callback') {
- currentAuditDesc = 'Failed to allow a new OAuth service access - the redirect URI did not match the previously registered callback';
+ currentAuditDesc = formatMessage(holders.failedOAuthAccess);
}
break;
case '/oauth/access_token':
if (oauthInfo[0] === 'attempt') {
- currentAuditDesc = 'Attempted to get an OAuth access token';
+ currentAuditDesc = formatMessage(holders.attemptedOAuthToken);
} else if (oauthInfo[0] === 'success') {
- currentAuditDesc = 'Successfully added a new OAuth service';
+ currentAuditDesc = formatMessage(holders.successfullOAuthToken);
} else {
const oauthTokenFailure = oauthInfo[0].split('-');
if (oauthTokenFailure[0].trim() === 'fail' && oauthTokenFailure[1]) {
- currentAuditDesc = 'Failed to get an OAuth access token - ' + oauthTokenFailure[1].trim();
+ currentAuditDesc = formatMessage(oauthTokenFailure, {token: oauthTokenFailure[1].trim()});
}
}
@@ -179,11 +361,11 @@ export default class AccessHistoryModal extends React.Component {
switch (currentActionURL) {
case '/users/login':
if (userInfo[0] === 'attempt') {
- currentAuditDesc = 'Attempted to login';
+ currentAuditDesc = formatMessage(holders.attemptedLogin);
} else if (userInfo[0] === 'success') {
- currentAuditDesc = 'Successfully logged in';
+ currentAuditDesc = formatMessage(holders.successfullLogin);
} else if (userInfo[0]) {
- currentAuditDesc = 'FAILED login attempt';
+ currentAuditDesc = formatMessage(holders.failedLogin);
}
break;
@@ -191,29 +373,29 @@ export default class AccessHistoryModal extends React.Component {
currentAuditDesc = this.handleRevokedSession(userInfo[0].split('=')[1]);
break;
case '/users/newimage':
- currentAuditDesc = 'Updated your profile picture';
+ currentAuditDesc = formatMessage(holders.updatePicture);
break;
case '/users/update':
- currentAuditDesc = 'Updated the general settings of your account';
+ currentAuditDesc = formatMessage(holders.updateGeneral);
break;
case '/users/newpassword':
if (userInfo[0] === 'attempted') {
- currentAuditDesc = 'Attempted to change password';
+ currentAuditDesc = formatMessage(holders.attemptedPassword);
} else if (userInfo[0] === 'completed') {
- currentAuditDesc = 'Successfully changed password';
+ currentAuditDesc = formatMessage(holders.successfullPassword);
} else if (userInfo[0] === 'failed - tried to update user password who was logged in through oauth') {
- currentAuditDesc = 'Failed to change password - tried to update user password who was logged in through oauth';
+ currentAuditDesc = formatMessage(holders.failedPassword);
}
break;
case '/users/update_roles': {
const userRoles = userInfo[0].split('=')[1];
- currentAuditDesc = 'Updated user role(s) to ';
+ currentAuditDesc = formatMessage(holders.updatedRol);
if (userRoles.trim()) {
currentAuditDesc += userRoles;
} else {
- currentAuditDesc += 'member';
+ currentAuditDesc += formatMessage(holders.member);
}
break;
@@ -225,9 +407,9 @@ export default class AccessHistoryModal extends React.Component {
/* Either describes account activation/deactivation or a revoked session as part of an account deactivation */
if (updateType === 'active') {
if (updateField === 'true') {
- currentAuditDesc = 'Account made active';
+ currentAuditDesc = formatMessage(holders.accountActive);
} else if (updateField === 'false') {
- currentAuditDesc = 'Account made inactive';
+ currentAuditDesc = formatMessage(holders.accountInactive);
}
const actingUserInfo = userInfo[1].split('=');
@@ -235,9 +417,9 @@ export default class AccessHistoryModal extends React.Component {
const actingUser = UserStore.getProfile(actingUserInfo[1]);
const currentUser = UserStore.getCurrentUser();
if (currentUser && actingUser && (Utils.isAdmin(currentUser.roles) || Utils.isSystemAdmin(currentUser.roles))) {
- currentAuditDesc += ' by ' + actingUser.username;
+ currentAuditDesc += formatMessage(holders.by, {username: actingUser.username});
} else if (currentUser && actingUser) {
- currentAuditDesc += ' by an admin';
+ currentAuditDesc += formatMessage(holders.byAdmin);
}
}
} else if (updateType === 'session_id') {
@@ -247,18 +429,18 @@ export default class AccessHistoryModal extends React.Component {
break;
}
case '/users/send_password_reset':
- currentAuditDesc = 'Sent an email to ' + userInfo[0].split('=')[1] + ' to reset your password';
+ currentAuditDesc = formatMessage(holders.sentEmail, {email: userInfo[0].split('=')[1]});
break;
case '/users/reset_password':
if (userInfo[0] === 'attempt') {
- currentAuditDesc = 'Attempted to reset password';
+ currentAuditDesc = formatMessage(holders.attemptedReset);
} else if (userInfo[0] === 'success') {
- currentAuditDesc = 'Successfully reset password';
+ currentAuditDesc = formatMessage(holders.successfullReset);
}
break;
case '/users/update_notify':
- currentAuditDesc = 'Updated your global notification settings';
+ currentAuditDesc = formatMessage(holders.updateGlobalNotifications);
break;
default:
break;
@@ -269,21 +451,21 @@ export default class AccessHistoryModal extends React.Component {
switch (currentActionURL) {
case '/hooks/incoming/create':
if (webhookInfo[0] === 'attempt') {
- currentAuditDesc = 'Attempted to create a webhook';
+ currentAuditDesc = formatMessage(holders.attemptedWebhookCreate);
} else if (webhookInfo[0] === 'success') {
- currentAuditDesc = 'Successfully created a webhook';
+ currentAuditDesc = formatMessage(holders.succcessfullWebhookCreate);
} else if (webhookInfo[0] === 'fail - bad channel permissions') {
- currentAuditDesc = 'Failed to create a webhook - bad channel permissions';
+ currentAuditDesc = formatMessage(holders.failedWebhookCreate);
}
break;
case '/hooks/incoming/delete':
if (webhookInfo[0] === 'attempt') {
- currentAuditDesc = 'Attempted to delete a webhook';
+ currentAuditDesc = formatMessage(holders.attemptedWebhookDelete);
} else if (webhookInfo[0] === 'success') {
- currentAuditDesc = 'Successfully deleted a webhook';
+ currentAuditDesc = formatMessage(holders.successfullWebhookDelete);
} else if (webhookInfo[0] === 'fail - inappropriate conditions') {
- currentAuditDesc = 'Failed to delete a webhook - inappropriate conditions';
+ currentAuditDesc = formatMessage(holders.failedWebhookDelete);
}
break;
@@ -293,10 +475,10 @@ export default class AccessHistoryModal extends React.Component {
} else {
switch (currentActionURL) {
case '/logout':
- currentAuditDesc = 'Logged out of your account';
+ currentAuditDesc = formatMessage(holders.logout);
break;
case '/verify_email':
- currentAuditDesc = 'Sucessfully verified your email address';
+ currentAuditDesc = formatMessage(holders.verified);
break;
default:
break;
@@ -307,7 +489,7 @@ export default class AccessHistoryModal extends React.Component {
if (!currentAuditDesc) {
/* Currently not called anywhere */
if (currentAudit.extra_info.indexOf('revoked_all=') >= 0) {
- currentAuditDesc = 'Revoked all current sessions for the team';
+ currentAuditDesc = formatMessage(holders.revokedAll);
} else {
let currentActionDesc = '';
if (currentActionURL && currentActionURL.lastIndexOf('/') !== -1) {
@@ -328,12 +510,14 @@ export default class AccessHistoryModal extends React.Component {
}
const currentDate = new Date(currentAudit.create_at);
- const currentAuditInfo = currentDate.toDateString() + ' - ' + currentDate.toLocaleTimeString(navigator.language, {hour: '2-digit', minute: '2-digit'}) + ' | ' + currentAuditDesc;
+ const currentAuditInfo = currentDate.toLocaleDateString(global.window.mm_locale, {month: 'short', day: '2-digit', year: 'numeric'}) + ' - ' +
+ currentDate.toLocaleTimeString(global.window.mm_locale, {hour: '2-digit', minute: '2-digit'}) + ' | ' + currentAuditDesc;
return currentAuditInfo;
}
render() {
var accessList = [];
+ const {formatMessage} = this.props.intl;
for (var i = 0; i < this.state.audits.length; i++) {
const currentAudit = this.state.audits[i];
const currentAuditInfo = this.formatAuditInfo(currentAudit);
@@ -344,7 +528,10 @@ export default class AccessHistoryModal extends React.Component {
className='theme'
onClick={this.handleMoreInfo.bind(this, i)}
>
- {'More info'}
+ <FormattedMessage
+ id='access_history.moreInfo'
+ defaultMessage='More info'
+ />
</a>
);
@@ -354,17 +541,33 @@ export default class AccessHistoryModal extends React.Component {
if (currentAudit.action.search('/users/login') >= 0) {
if (currentAudit.extra_info === 'attempt') {
- currentAudit.session_id += ' (Login attempt)';
+ currentAudit.session_id += formatMessage(holders.loginAttempt);
} else {
- currentAudit.session_id += ' (Login failure)';
+ currentAudit.session_id += formatMessage(holders.loginFailure);
}
}
}
moreInfo = (
<div>
- <div>{'IP: ' + currentAudit.ip_address}</div>
- <div>{'Session ID: ' + currentAudit.session_id}</div>
+ <div>
+ <FormattedMessage
+ id='access_history.ip'
+ defaultMessage='IP: {ip}'
+ values={{
+ ip: currentAudit.ip_address
+ }}
+ />
+ </div>
+ <div>
+ <FormattedMessage
+ id='access_history.session'
+ defaultMessage='Session ID: {id}'
+ values={{
+ id: currentAudit.session_id
+ }}
+ />
+ </div>
</div>
);
}
@@ -404,7 +607,12 @@ export default class AccessHistoryModal extends React.Component {
bsSize='large'
>
<Modal.Header closeButton={true}>
- <Modal.Title>{'Access History'}</Modal.Title>
+ <Modal.Title>
+ <FormattedMessage
+ id='access_history.title'
+ defaultMessage='Access History'
+ />
+ </Modal.Title>
</Modal.Header>
<Modal.Body ref='modalBody'>
{content}
@@ -415,6 +623,9 @@ export default class AccessHistoryModal extends React.Component {
}
AccessHistoryModal.propTypes = {
+ intl: intlShape.isRequired,
show: React.PropTypes.bool.isRequired,
onHide: React.PropTypes.func.isRequired
};
+
+export default injectIntl(AccessHistoryModal); \ No newline at end of file
diff --git a/web/react/components/activity_log_modal.jsx b/web/react/components/activity_log_modal.jsx
index 2c42f5971..f8a2af571 100644
--- a/web/react/components/activity_log_modal.jsx
+++ b/web/react/components/activity_log_modal.jsx
@@ -8,6 +8,8 @@ const Modal = ReactBootstrap.Modal;
import LoadingScreen from './loading_screen.jsx';
import * as Utils from '../utils/utils.jsx';
+import {FormattedMessage} from 'mm-intl';
+
export default class ActivityLogModal extends React.Component {
constructor(props) {
super(props);
@@ -102,16 +104,31 @@ export default class ActivityLogModal extends React.Component {
devicePicture = 'fa fa-windows';
} else if (currentSession.device_id && currentSession.device_id.indexOf('apple:') === 0) {
devicePicture = 'fa fa-apple';
- devicePlatform = 'iPhone Native App';
+ devicePlatform = (
+ <FormattedMessage
+ id='activity_log_modal.iphoneNativeApp'
+ defaultMessage='iPhone Native App'
+ />
+ );
} else if (currentSession.device_id && currentSession.device_id.indexOf('android:') === 0) {
- devicePlatform = 'Android Native App';
+ devicePlatform = (
+ <FormattedMessage
+ id='activity_log_modal.androidNativeApp'
+ defaultMessage='Android Native App'
+ />
+ );
devicePicture = 'fa fa-android';
} else if (currentSession.props.platform === 'Macintosh' ||
currentSession.props.platform === 'iPhone') {
devicePicture = 'fa fa-apple';
} else if (currentSession.props.platform === 'Linux') {
if (currentSession.props.os.indexOf('Android') >= 0) {
- devicePlatform = 'Android';
+ devicePlatform = (
+ <FormattedMessage
+ id='activity_log_modal.android'
+ defaultMessage='Android'
+ />
+ );
devicePicture = 'fa fa-android';
} else {
devicePicture = 'fa fa-linux';
@@ -122,10 +139,43 @@ export default class ActivityLogModal extends React.Component {
if (this.state.moreInfo[i]) {
moreInfo = (
<div>
- <div>{`First time active: ${firstAccessTime.toDateString()}, ${lastAccessTime.toLocaleTimeString()}`}</div>
- <div>{`OS: ${currentSession.props.os}`}</div>
- <div>{`Browser: ${currentSession.props.browser}`}</div>
- <div>{`Session ID: ${currentSession.id}`}</div>
+ <div>
+ <FormattedMessage
+ id='activity_log.firstTime'
+ defaultMessage='First time active: {date}, {time}'
+ values={{
+ date: firstAccessTime.toLocaleDateString(global.window.mm_locale, {month: 'short', day: '2-digit', year: 'numeric'}),
+ time: lastAccessTime.toLocaleTimeString(global.window.mm_locale, {hour: '2-digit', minute: '2-digit'})
+ }}
+ />
+ </div>
+ <div>
+ <FormattedMessage
+ id='activity_log.os'
+ defaultMessage='OS: {os}'
+ values={{
+ os: currentSession.props.os
+ }}
+ />
+ </div>
+ <div>
+ <FormattedMessage
+ id='activity_log.browser'
+ defaultMessage='Browser: {browser}'
+ values={{
+ browser: currentSession.props.browser
+ }}
+ />
+ </div>
+ <div>
+ <FormattedMessage
+ id='activity_log.sessionId'
+ defaultMessage='Session ID: {id}'
+ values={{
+ id: currentSession.id
+ }}
+ />
+ </div>
</div>
);
} else {
@@ -135,7 +185,10 @@ export default class ActivityLogModal extends React.Component {
href='#'
onClick={this.handleMoreInfo.bind(this, i)}
>
- More info
+ <FormattedMessage
+ id='activity_log.moreInfo'
+ defaultMessage='More info'
+ />
</a>
);
}
@@ -148,7 +201,16 @@ export default class ActivityLogModal extends React.Component {
<div className='activity-log__report'>
<div className='report__platform'><i className={devicePicture} />{devicePlatform}</div>
<div className='report__info'>
- <div>{`Last activity: ${lastAccessTime.toDateString()}, ${lastAccessTime.toLocaleTimeString()}`}</div>
+ <div>
+ <FormattedMessage
+ id='activity_log.lastActivity'
+ defaultMessage='Last activity: {date}, {time}'
+ values={{
+ date: lastAccessTime.toLocaleDateString(global.window.mm_locale, {month: 'short', day: '2-digit', year: 'numeric'}),
+ time: lastAccessTime.toLocaleTimeString(global.window.mm_locale, {hour: '2-digit', minute: '2-digit'})
+ }}
+ />
+ </div>
{moreInfo}
</div>
</div>
@@ -157,7 +219,10 @@ export default class ActivityLogModal extends React.Component {
onClick={this.submitRevoke.bind(this, currentSession.id)}
className='btn btn-primary'
>
- Logout
+ <FormattedMessage
+ id='activity_log.logout'
+ defaultMessage='Logout'
+ />
</button>
</div>
</div>
@@ -178,10 +243,20 @@ export default class ActivityLogModal extends React.Component {
bsSize='large'
>
<Modal.Header closeButton={true}>
- <Modal.Title>{'Active Sessions'}</Modal.Title>
+ <Modal.Title>
+ <FormattedMessage
+ id='activity_log.activeSessions'
+ defaultMessage='Active Sessions'
+ />
+ </Modal.Title>
</Modal.Header>
<Modal.Body ref='modalBody'>
- <p className='session-help-text'>{'Sessions are created when you log in with your email and password to a new browser on a device. Sessions let you use Mattermost for up to 30 days without having to log in again. If you want to log out sooner, use the \'Logout\' button below to end a session.'}</p>
+ <p className='session-help-text'>
+ <FormattedMessage
+ id='activity_log.sessionsDescription'
+ defaultMessage="Sessions are created when you log in with your email and password to a new browser on a device. Sessions let you use Mattermost for up to 30 days without having to log in again. If you want to log out sooner, use the 'Logout' button below to end a session."
+ />
+ </p>
{content}
</Modal.Body>
</Modal>
diff --git a/web/react/components/admin_console/analytics.jsx b/web/react/components/admin_console/analytics.jsx
index ff5903c62..a22c26c34 100644
--- a/web/react/components/admin_console/analytics.jsx
+++ b/web/react/components/admin_console/analytics.jsx
@@ -104,10 +104,12 @@ export default class Analytics extends React.Component {
let content;
if (this.props.postCountsDay.labels.length === 0) {
content = (
- <FormattedMessage
- id='admin.analytics.meaningful'
- defaultMessage='Not enough data for a meaningful representation.'
- />
+ <h5>
+ <FormattedMessage
+ id='admin.analytics.meaningful'
+ defaultMessage='Not enough data for a meaningful representation.'
+ />
+ </h5>
);
} else {
content = (
@@ -153,10 +155,12 @@ export default class Analytics extends React.Component {
let content;
if (this.props.userCountsWithPostsDay.labels.length === 0) {
content = (
- <FormattedMessage
- id='admin.analytics.meaningful'
- defaultMessage='Not enough data for a meaningful representation.'
- />
+ <h5>
+ <FormattedMessage
+ id='admin.analytics.meaningful'
+ defaultMessage='Not enough data for a meaningful representation.'
+ />
+ </h5>
);
} else {
content = (
diff --git a/web/react/components/admin_console/email_settings.jsx b/web/react/components/admin_console/email_settings.jsx
index ce3c8cd12..17f25a04c 100644
--- a/web/react/components/admin_console/email_settings.jsx
+++ b/web/react/components/admin_console/email_settings.jsx
@@ -112,6 +112,8 @@ class EmailSettings extends React.Component {
buildConfig() {
var config = this.props.config;
config.EmailSettings.EnableSignUpWithEmail = ReactDOM.findDOMNode(this.refs.allowSignUpWithEmail).checked;
+ config.EmailSettings.EnableSignInWithEmail = ReactDOM.findDOMNode(this.refs.allowSignInWithEmail).checked;
+ config.EmailSettings.EnableSignInWithUsername = ReactDOM.findDOMNode(this.refs.allowSignInWithUsername).checked;
config.EmailSettings.SendEmailNotifications = ReactDOM.findDOMNode(this.refs.sendEmailNotifications).checked;
config.EmailSettings.SendPushNotifications = ReactDOM.findDOMNode(this.refs.sendPushNotifications).checked;
config.EmailSettings.RequireEmailVerification = ReactDOM.findDOMNode(this.refs.requireEmailVerification).checked;
@@ -320,6 +322,88 @@ class EmailSettings extends React.Component {
<div className='form-group'>
<label
className='control-label col-sm-4'
+ htmlFor='allowSignInWithEmail'
+ >
+ <FormattedMessage
+ id='admin.email.allowEmailSignInTitle'
+ defaultMessage='Allow Sign In With Email: '
+ />
+ </label>
+ <div className='col-sm-8'>
+ <label className='radio-inline'>
+ <input
+ type='radio'
+ name='allowSignInWithEmail'
+ value='true'
+ ref='allowSignInWithEmail'
+ defaultChecked={this.props.config.EmailSettings.EnableSignInWithEmail}
+ onChange={this.handleChange.bind(this, 'allowSignInWithEmail_true')}
+ />
+ {'true'}
+ </label>
+ <label className='radio-inline'>
+ <input
+ type='radio'
+ name='allowSignInWithEmail'
+ value='false'
+ defaultChecked={!this.props.config.EmailSettings.EnableSignInWithEmail}
+ onChange={this.handleChange.bind(this, 'allowSignInWithEmail_false')}
+ />
+ {'false'}
+ </label>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.email.allowEmailSignInDescription'
+ defaultMessage='When true, Mattermost allows users to sign in using their email and password.'
+ />
+ </p>
+ </div>
+ </div>
+
+ <div className='form-group'>
+ <label
+ className='control-label col-sm-4'
+ htmlFor='allowSignInWithUsername'
+ >
+ <FormattedMessage
+ id='admin.email.allowUsernameSignInTitle'
+ defaultMessage='Allow Sign In With Username: '
+ />
+ </label>
+ <div className='col-sm-8'>
+ <label className='radio-inline'>
+ <input
+ type='radio'
+ name='allowSignInWithUsername'
+ value='true'
+ ref='allowSignInWithUsername'
+ defaultChecked={this.props.config.EmailSettings.EnableSignInWithUsername}
+ onChange={this.handleChange.bind(this, 'allowSignInWithUsername_true')}
+ />
+ {'true'}
+ </label>
+ <label className='radio-inline'>
+ <input
+ type='radio'
+ name='allowSignInWithUsername'
+ value='false'
+ defaultChecked={!this.props.config.EmailSettings.EnableSignInWithUsername}
+ onChange={this.handleChange.bind(this, 'allowSignInWithUsername_false')}
+ />
+ {'false'}
+ </label>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.email.allowUsernameSignInDescription'
+ defaultMessage='When true, Mattermost allows users to sign in using their username and password. This setting is typically only used when email verification is disabled.'
+ />
+ </p>
+ </div>
+ </div>
+
+ <div className='form-group'>
+ <label
+ className='control-label col-sm-4'
htmlFor='sendEmailNotifications'
>
<FormattedMessage
diff --git a/web/react/components/admin_console/user_item.jsx b/web/react/components/admin_console/user_item.jsx
index 59b4861e4..02b01b090 100644
--- a/web/react/components/admin_console/user_item.jsx
+++ b/web/react/components/admin_console/user_item.jsx
@@ -3,8 +3,30 @@
import * as Client from '../../utils/client.jsx';
import * as Utils from '../../utils/utils.jsx';
+import UserStore from '../../stores/user_store.jsx';
+import ConfirmModal from '../confirm_modal.jsx';
+import TeamStore from '../../stores/team_store.jsx';
-import {FormattedMessage} from 'mm-intl';
+import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'mm-intl';
+
+var holders = defineMessages({
+ confirmDemoteRoleTitle: {
+ id: 'admin.user_item.confirmDemoteRoleTitle',
+ defaultMessage: 'Confirm demotion from System Admin role'
+ },
+ confirmDemotion: {
+ id: 'admin.user_item.confirmDemotion',
+ defaultMessage: 'Confirm Demotion'
+ },
+ confirmDemoteDescription: {
+ id: 'admin.user_item.confirmDemoteDescription',
+ defaultMessage: 'If you demote yourself from the System Admin role and there is not another user with System Admin privileges, you\'ll need to re-assign a System Admin by accessing the Mattermost server through a terminal and running the following command.'
+ },
+ confirmDemotionCmd: {
+ id: 'admin.user_item.confirmDemotionCmd',
+ defaultMessage: 'platform -assign_role -team_name="yourteam" -email="name@yourcompany.com" -role="system_admin"'
+ }
+});
export default class UserItem extends React.Component {
constructor(props) {
@@ -16,25 +38,38 @@ export default class UserItem extends React.Component {
this.handleMakeAdmin = this.handleMakeAdmin.bind(this);
this.handleMakeSystemAdmin = this.handleMakeSystemAdmin.bind(this);
this.handleResetPassword = this.handleResetPassword.bind(this);
+ this.handleDemote = this.handleDemote.bind(this);
+ this.handleDemoteSubmit = this.handleDemoteSubmit.bind(this);
+ this.handleDemoteCancel = this.handleDemoteCancel.bind(this);
- this.state = {};
+ this.state = {
+ serverError: null,
+ showDemoteModal: false,
+ user: null,
+ role: null
+ };
}
handleMakeMember(e) {
e.preventDefault();
- const data = {
- user_id: this.props.user.id,
- new_roles: ''
- };
+ const me = UserStore.getCurrentUser();
+ if (this.props.user.id === me.id) {
+ this.handleDemote(this.props.user, '');
+ } else {
+ const data = {
+ user_id: this.props.user.id,
+ new_roles: ''
+ };
- Client.updateRoles(data,
- () => {
- this.props.refreshProfiles();
- },
- (err) => {
- this.setState({serverError: err.message});
- }
- );
+ Client.updateRoles(data,
+ () => {
+ this.props.refreshProfiles();
+ },
+ (err) => {
+ this.setState({serverError: err.message});
+ }
+ );
+ }
}
handleMakeActive(e) {
@@ -63,9 +98,31 @@ export default class UserItem extends React.Component {
handleMakeAdmin(e) {
e.preventDefault();
+ const me = UserStore.getCurrentUser();
+ if (this.props.user.id === me.id) {
+ this.handleDemote(this.props.user, 'admin');
+ } else {
+ const data = {
+ user_id: this.props.user.id,
+ new_roles: 'admin'
+ };
+
+ Client.updateRoles(data,
+ () => {
+ this.props.refreshProfiles();
+ },
+ (err) => {
+ this.setState({serverError: err.message});
+ }
+ );
+ }
+ }
+
+ handleMakeSystemAdmin(e) {
+ e.preventDefault();
const data = {
user_id: this.props.user.id,
- new_roles: 'admin'
+ new_roles: 'system_admin'
};
Client.updateRoles(data,
@@ -78,28 +135,59 @@ export default class UserItem extends React.Component {
);
}
- handleMakeSystemAdmin(e) {
+ handleResetPassword(e) {
e.preventDefault();
+ this.props.doPasswordReset(this.props.user);
+ }
+
+ handleDemote(user, role) {
+ this.setState({
+ serverError: this.state.serverError,
+ showDemoteModal: true,
+ user,
+ role
+ });
+ }
+
+ handleDemoteCancel() {
+ this.setState({
+ serverError: null,
+ showDemoteModal: false,
+ user: null,
+ role: null
+ });
+ }
+
+ handleDemoteSubmit() {
const data = {
user_id: this.props.user.id,
- new_roles: 'system_admin'
+ new_roles: this.state.role
};
Client.updateRoles(data,
() => {
- this.props.refreshProfiles();
+ this.setState({
+ serverError: null,
+ showDemoteModal: false,
+ user: null,
+ role: null
+ });
+
+ const teamUrl = TeamStore.getCurrentTeamUrl();
+ if (teamUrl) {
+ window.location.href = teamUrl;
+ } else {
+ window.location.href = '/';
+ }
},
(err) => {
- this.setState({serverError: err.message});
+ this.setState({
+ serverError: err.message
+ });
}
);
}
- handleResetPassword(e) {
- e.preventDefault();
- this.props.doPasswordReset(this.props.user);
- }
-
render() {
let serverError = null;
if (this.state.serverError) {
@@ -247,6 +335,21 @@ export default class UserItem extends React.Component {
</li>
);
}
+ const me = UserStore.getCurrentUser();
+ const {formatMessage} = this.props.intl;
+ let makeDemoteModal = null;
+ if (this.props.user.id === me.id) {
+ makeDemoteModal = (
+ <ConfirmModal
+ show={this.state.showDemoteModal}
+ title={formatMessage(holders.confirmDemoteRoleTitle)}
+ message={[formatMessage(holders.confirmDemoteDescription), React.createElement('br'), React.createElement('br'), formatMessage(holders.confirmDemotionCmd), serverError]}
+ confirm_button={formatMessage(holders.confirmDemotion)}
+ onConfirm={this.handleDemoteSubmit}
+ onCancel={this.handleDemoteCancel}
+ />
+ );
+ }
return (
<tr>
@@ -293,6 +396,7 @@ export default class UserItem extends React.Component {
</li>
</ul>
</div>
+ {makeDemoteModal}
{serverError}
</td>
</tr>
@@ -301,7 +405,10 @@ export default class UserItem extends React.Component {
}
UserItem.propTypes = {
+ intl: intlShape.isRequired,
user: React.PropTypes.object.isRequired,
refreshProfiles: React.PropTypes.func.isRequired,
doPasswordReset: React.PropTypes.func.isRequired
};
+
+export default injectIntl(UserItem);
diff --git a/web/react/components/center_panel.jsx b/web/react/components/center_panel.jsx
index 53dad1306..443ecefde 100644
--- a/web/react/components/center_panel.jsx
+++ b/web/react/components/center_panel.jsx
@@ -69,7 +69,7 @@ export default class CenterPanel extends React.Component {
onClick={handleClick}
>
<a href=''>
- {'You are viewing the Archives. Click here to jump to recent messages. '}
+ {'Click here to jump to recent messages. '}
{<i className='fa fa-arrow-down'></i>}
</a>
</div>
diff --git a/web/react/components/change_url_modal.jsx b/web/react/components/change_url_modal.jsx
index bbe93f58d..49d1b86b4 100644
--- a/web/react/components/change_url_modal.jsx
+++ b/web/react/components/change_url_modal.jsx
@@ -4,6 +4,8 @@
var Modal = ReactBootstrap.Modal;
import * as Utils from '../utils/utils.jsx';
+import {FormattedMessage} from 'mm-intl';
+
export default class ChangeUrlModal extends React.Component {
constructor(props) {
super(props);
@@ -39,21 +41,58 @@ export default class ChangeUrlModal extends React.Component {
getURLError(url) {
let error = []; //eslint-disable-line prefer-const
if (url.length < 2) {
- error.push(<span key='error1'>{'Must be longer than two characters'}<br/></span>);
+ error.push(
+ <span key='error1'>
+ <FormattedMessage
+ id='change_url.longer'
+ defaultMessage='Must be longer than two characters'
+ />
+ <br/>
+ </span>
+ );
}
if (url.charAt(0) === '-' || url.charAt(0) === '_') {
- error.push(<span key='error2'>{'Must start with a letter or number'}<br/></span>);
+ error.push(
+ <span key='error2'>
+ <FormattedMessage
+ id='change_url.startWithLetter'
+ defaultMessage='Must start with a letter or number'
+ />
+ <br/>
+ </span>
+ );
}
if (url.length > 1 && (url.charAt(url.length - 1) === '-' || url.charAt(url.length - 1) === '_')) {
- error.push(<span key='error3'>{'Must end with a letter or number'}<br/></span>);
+ error.push(
+ <span key='error3'>
+ <FormattedMessage
+ id='change_url.endWithLetter'
+ defaultMessage='Must end with a letter or number'
+ />
+ <br/>
+ </span>);
}
if (url.indexOf('__') > -1) {
- error.push(<span key='error4'>{'Can not contain two underscores in a row.'}<br/></span>);
+ error.push(
+ <span key='error4'>
+ <FormattedMessage
+ id='change_url.noUnderscore'
+ defaultMessage='Can not contain two underscores in a row.'
+ />
+ <br/>
+ </span>);
}
// In case of error we don't detect
if (error.length === 0) {
- error.push(<span key='errorlast'>{'Invalid URL'}<br/></span>);
+ error.push(
+ <span key='errorlast'>
+ <FormattedMessage
+ id='change_url.invalidUrl'
+ defaultMessage='Invalid URL'
+ />
+ <br/>
+ </span>);
}
return error;
}
@@ -137,7 +176,10 @@ export default class ChangeUrlModal extends React.Component {
className='btn btn-default'
onClick={this.doCancel}
>
- {'Close'}
+ <FormattedMessage
+ id='change_url.close'
+ defaultMessage='Close'
+ />
</button>
<button
onClick={this.doSubmit}
diff --git a/web/react/components/confirm_modal.jsx b/web/react/components/confirm_modal.jsx
index cdef1c1ea..987649f38 100644
--- a/web/react/components/confirm_modal.jsx
+++ b/web/react/components/confirm_modal.jsx
@@ -1,6 +1,7 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
+import {FormattedMessage} from 'mm-intl';
const Modal = ReactBootstrap.Modal;
export default class ConfirmModal extends React.Component {
@@ -33,7 +34,10 @@ export default class ConfirmModal extends React.Component {
className='btn btn-default'
onClick={this.props.onCancel}
>
- {'Cancel'}
+ <FormattedMessage
+ id='confirm_modal.cancel'
+ defaultMessage='Cancel'
+ />
</button>
<button
type='button'
diff --git a/web/react/components/create_comment.jsx b/web/react/components/create_comment.jsx
index aa7ab6a7b..1b552838a 100644
--- a/web/react/components/create_comment.jsx
+++ b/web/react/components/create_comment.jsx
@@ -16,10 +16,32 @@ import FilePreview from './file_preview.jsx';
import * as Utils from '../utils/utils.jsx';
import Constants from '../utils/constants.jsx';
+
+import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl';
+
const ActionTypes = Constants.ActionTypes;
const KeyCodes = Constants.KeyCodes;
-export default class CreateComment extends React.Component {
+const holders = defineMessages({
+ commentLength: {
+ id: 'create_comment.commentLength',
+ defaultMessage: 'Comment length must be less than {max} characters.'
+ },
+ comment: {
+ id: 'create_comment.comment',
+ defaultMessage: 'Add Comment'
+ },
+ addComment: {
+ id: 'create_comment.addComment',
+ defaultMessage: 'Add a comment...'
+ },
+ commentTitle: {
+ id: 'create_comment.commentTitle',
+ defaultMessage: 'Comment'
+ }
+});
+
+class CreateComment extends React.Component {
constructor(props) {
super(props);
@@ -93,7 +115,7 @@ export default class CreateComment extends React.Component {
}
if (post.message.length > Constants.CHARACTER_LIMIT) {
- this.setState({postError: `Comment length must be less than ${Constants.CHARACTER_LIMIT} characters.`});
+ this.setState({postError: this.props.intl.formatMessage(holders.commentLength, {max: Constants.CHARACTER_LIMIT})});
return;
}
@@ -189,7 +211,7 @@ export default class CreateComment extends React.Component {
AppDispatcher.handleViewAction({
type: ActionTypes.RECIEVED_EDIT_POST,
refocusId: '#reply_textbox',
- title: 'Comment',
+ title: this.props.intl.formatMessage(holders.commentTitle),
message: lastPost.message,
postId: lastPost.id,
channelId: lastPost.channel_id,
@@ -305,14 +327,23 @@ export default class CreateComment extends React.Component {
let uploadsInProgressText = null;
if (this.state.uploadsInProgress.length > 0) {
uploadsInProgressText = (
- <span
- className='pull-right post-right-comments-upload-in-progress'
- >
- {this.state.uploadsInProgress.length === 1 ? 'File uploading' : 'Files uploading'}
+ <span className='pull-right post-right-comments-upload-in-progress'>
+ {this.state.uploadsInProgress.length === 1 ? (
+ <FormattedMessage
+ id='create_comment.file'
+ defaultMessage='File uploading'
+ />
+ ) : (
+ <FormattedMessage
+ id='create_comment.files'
+ defaultMessage='Files uploading'
+ />
+ )}
</span>
);
}
+ const {formatMessage} = this.props.intl;
return (
<form onSubmit={this.handleSubmit}>
<div className='post-create'>
@@ -326,7 +357,7 @@ export default class CreateComment extends React.Component {
onKeyPress={this.commentMsgKeyPress}
onKeyDown={this.handleKeyDown}
messageText={this.state.messageText}
- createMessage='Add a comment...'
+ createMessage={formatMessage(holders.addComment)}
initialText=''
supportsCommands={false}
id='reply_textbox'
@@ -351,7 +382,7 @@ export default class CreateComment extends React.Component {
<input
type='button'
className='btn btn-primary comment-btn pull-right'
- value='Add Comment'
+ value={formatMessage(holders.comment)}
onClick={this.handleSubmit}
/>
{uploadsInProgressText}
@@ -366,6 +397,9 @@ export default class CreateComment extends React.Component {
}
CreateComment.propTypes = {
+ intl: intlShape.isRequired,
channelId: React.PropTypes.string.isRequired,
rootId: React.PropTypes.string.isRequired
};
+
+export default injectIntl(CreateComment); \ No newline at end of file
diff --git a/web/react/components/file_upload.jsx b/web/react/components/file_upload.jsx
index 7e6cc2942..626dbc5b3 100644
--- a/web/react/components/file_upload.jsx
+++ b/web/react/components/file_upload.jsx
@@ -6,7 +6,28 @@ import Constants from '../utils/constants.jsx';
import ChannelStore from '../stores/channel_store.jsx';
import * as Utils from '../utils/utils.jsx';
-export default class FileUpload extends React.Component {
+import {intlShape, injectIntl, defineMessages} from 'mm-intl';
+
+const holders = defineMessages({
+ limited: {
+ id: 'file_upload.limited',
+ defaultMessage: 'Uploads limited to {count} files maximum. Please use additional posts for more files.'
+ },
+ filesAbove: {
+ id: 'file_upload.filesAbove',
+ defaultMessage: 'Files above {max}MB could not be uploaded: {filenames}'
+ },
+ fileAbove: {
+ id: 'file_upload.fileAbove',
+ defaultMessage: 'File above {max}MB could not be uploaded: {filename}'
+ },
+ pasted: {
+ id: 'file_upload.pasted',
+ defaultMessage: 'Image Pasted at '
+ }
+});
+
+class FileUpload extends React.Component {
constructor(props) {
super(props);
@@ -74,14 +95,15 @@ export default class FileUpload extends React.Component {
numUploads += 1;
}
+ const {formatMessage} = this.props.intl;
if (files.length > uploadsRemaining) {
- this.props.onUploadError(`Uploads limited to ${Constants.MAX_UPLOAD_FILES} files maximum. Please use additional posts for more files.`);
+ this.props.onUploadError(formatMessage(holders.limited, {count: Constants.MAX_UPLOAD_FILES}));
} else if (tooLargeFiles.length > 1) {
var tooLargeFilenames = tooLargeFiles.map((file) => file.name).join(', ');
- this.props.onUploadError(`Files above ${Constants.MAX_FILE_SIZE / 1000000}MB could not be uploaded: ${tooLargeFilenames}`);
+ this.props.onUploadError(formatMessage(holders.filesAbove, {max: (Constants.MAX_FILE_SIZE / 1000000), files: tooLargeFilenames}));
} else if (tooLargeFiles.length > 0) {
- this.props.onUploadError(`File above ${Constants.MAX_FILE_SIZE / 1000000}MB could not be uploaded: ${tooLargeFiles[0].name}`);
+ this.props.onUploadError(formatMessage(holders.fileAbove, {max: (Constants.MAX_FILE_SIZE / 1000000), file: tooLargeFiles[0].name}));
}
}
@@ -106,6 +128,7 @@ export default class FileUpload extends React.Component {
componentDidMount() {
var inputDiv = ReactDOM.findDOMNode(this.refs.input);
var self = this;
+ const {formatMessage} = this.props.intl;
if (this.props.postType === 'post') {
$('.row.main').dragster({
@@ -184,7 +207,7 @@ export default class FileUpload extends React.Component {
var numToUpload = Math.min(Constants.MAX_UPLOAD_FILES - self.props.getFileCount(ChannelStore.getCurrentId()), numItems);
if (numItems > numToUpload) {
- self.props.onUploadError('Uploads limited to ' + Constants.MAX_UPLOAD_FILES + ' files maximum. Please use additional posts for more files.');
+ self.props.onUploadError(formatMessage(holders.limited, {count: Constants.MAX_UPLOAD_FILES}));
}
for (var i = 0; i < items.length && i < numToUpload; i++) {
@@ -218,7 +241,7 @@ export default class FileUpload extends React.Component {
min = String(d.getMinutes());
}
- var name = 'Image Pasted at ' + d.getFullYear() + '-' + d.getMonth() + '-' + d.getDate() + ' ' + hour + '-' + min + '.' + ext;
+ var name = formatMessage(holders.pasted) + d.getFullYear() + '-' + d.getMonth() + '-' + d.getDate() + ' ' + hour + '-' + min + '.' + ext;
formData.append('files', file, name);
formData.append('client_ids', clientId);
@@ -296,6 +319,7 @@ export default class FileUpload extends React.Component {
}
FileUpload.propTypes = {
+ intl: intlShape.isRequired,
onUploadError: React.PropTypes.func,
getFileCount: React.PropTypes.func,
onFileUpload: React.PropTypes.func,
@@ -304,3 +328,5 @@ FileUpload.propTypes = {
channelId: React.PropTypes.string,
postType: React.PropTypes.string
};
+
+export default injectIntl(FileUpload); \ No newline at end of file
diff --git a/web/react/components/file_upload_overlay.jsx b/web/react/components/file_upload_overlay.jsx
index dbba00022..497d5aee2 100644
--- a/web/react/components/file_upload_overlay.jsx
+++ b/web/react/components/file_upload_overlay.jsx
@@ -1,6 +1,8 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
+import {FormattedMessage} from 'mm-intl';
+
export default class FileUploadOverlay extends React.Component {
render() {
var overlayClass = 'file-overlay hidden';
@@ -19,7 +21,12 @@ export default class FileUploadOverlay extends React.Component {
src='/static/images/filesOverlay.png'
alt='Files'
/>
- <span><i className='fa fa-upload'></i>{'Drop a file to upload it.'}</span>
+ <span><i className='fa fa-upload'></i>
+ <FormattedMessage
+ id='upload_overlay.info'
+ defaultMessage='Drop a file to upload it.'
+ />
+ </span>
<img
className='overlay__logo'
src='/static/images/logoWhite.png'
diff --git a/web/react/components/get_link_modal.jsx b/web/react/components/get_link_modal.jsx
index fd20834f4..de3387a35 100644
--- a/web/react/components/get_link_modal.jsx
+++ b/web/react/components/get_link_modal.jsx
@@ -1,6 +1,8 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
+import {FormattedMessage} from 'mm-intl';
+
const Modal = ReactBootstrap.Modal;
export default class GetLinkModal extends React.Component {
@@ -39,6 +41,8 @@ export default class GetLinkModal extends React.Component {
}
render() {
+ const userCreationEnabled = global.window.mm_config.EnableUserCreation === 'true';
+
let helpText = null;
if (this.props.helpText) {
helpText = (
@@ -51,7 +55,7 @@ export default class GetLinkModal extends React.Component {
}
let copyLink = null;
- if (document.queryCommandSupported('copy')) {
+ if (userCreationEnabled && document.queryCommandSupported('copy')) {
copyLink = (
<button
data-copy-btn='true'
@@ -59,14 +63,37 @@ export default class GetLinkModal extends React.Component {
className='btn btn-primary pull-left'
onClick={this.copyLink}
>
- {'Copy Link'}
+ <FormattedMessage
+ id='get_link.copy'
+ defaultMessage='Copy Link'
+ />
</button>
);
}
+ let linkText = null;
+ if (userCreationEnabled) {
+ linkText = (
+ <textarea
+ className='form-control no-resize min-height'
+ readOnly='true'
+ ref='textarea'
+ value={this.props.link}
+ />
+ );
+ }
+
var copyLinkConfirm = null;
if (this.state.copiedLink) {
- copyLinkConfirm = <p className='alert alert-success copy-link-confirm'><i className='fa fa-check'></i>{' Link copied to clipboard.'}</p>;
+ copyLinkConfirm = (
+ <p className='alert alert-success copy-link-confirm'>
+ <i className='fa fa-check'></i>
+ <FormattedMessage
+ id='get_link.clipboard'
+ defaultMessage=' Link copied to clipboard.'
+ />
+ </p>
+ );
}
return (
@@ -79,12 +106,7 @@ export default class GetLinkModal extends React.Component {
</Modal.Header>
<Modal.Body>
{helpText}
- <textarea
- className='form-control no-resize min-height'
- readOnly='true'
- ref='textarea'
- value={this.props.link}
- />
+ {linkText}
</Modal.Body>
<Modal.Footer>
<button
@@ -92,7 +114,10 @@ export default class GetLinkModal extends React.Component {
className='btn btn-default'
onClick={this.onHide}
>
- {'Close'}
+ <FormattedMessage
+ id='get_link.close'
+ defaultMessage='Close'
+ />
</button>
{copyLink}
{copyLinkConfirm}
diff --git a/web/react/components/get_team_invite_link_modal.jsx b/web/react/components/get_team_invite_link_modal.jsx
index a926c4451..299729250 100644
--- a/web/react/components/get_team_invite_link_modal.jsx
+++ b/web/react/components/get_team_invite_link_modal.jsx
@@ -6,7 +6,24 @@ import GetLinkModal from './get_link_modal.jsx';
import ModalStore from '../stores/modal_store.jsx';
import TeamStore from '../stores/team_store.jsx';
-export default class GetTeamInviteLinkModal extends React.Component {
+import {intlShape, injectIntl, defineMessages} from 'mm-intl';
+
+const holders = defineMessages({
+ title: {
+ id: 'get_team_invite_link_modal.title',
+ defaultMessage: 'Team Invite Link'
+ },
+ help: {
+ id: 'get_team_invite_link_modal.help',
+ defaultMessage: 'Send teammates the link below for them to sign-up to this team site.'
+ },
+ helpDisabled: {
+ id: 'get_team_invite_link_modal.helpDisabled',
+ defaultMessage: 'User creation has been disabled for your team. Please ask your team administrator for details.'
+ }
+});
+
+class GetTeamInviteLinkModal extends React.Component {
constructor(props) {
super(props);
@@ -32,14 +49,28 @@ export default class GetTeamInviteLinkModal extends React.Component {
}
render() {
+ const {formatMessage} = this.props.intl;
+
+ let helpText = formatMessage(holders.helpDisabled);
+
+ if (global.window.mm_config.EnableUserCreation === 'true') {
+ helpText = formatMessage(holders.help);
+ }
+
return (
<GetLinkModal
show={this.state.show}
onHide={() => this.setState({show: false})}
- title='Team Invite Link'
- helpText='Send teammates the link below for them to sign-up to this team site.'
+ title={formatMessage(holders.title)}
+ helpText={helpText}
link={TeamStore.getCurrentInviteLink()}
/>
);
}
}
+
+GetTeamInviteLinkModal.propTypes = {
+ intl: intlShape.isRequired
+};
+
+export default injectIntl(GetTeamInviteLinkModal); \ No newline at end of file
diff --git a/web/react/components/invite_member_modal.jsx b/web/react/components/invite_member_modal.jsx
index 7e1627555..f2a0a7565 100644
--- a/web/react/components/invite_member_modal.jsx
+++ b/web/react/components/invite_member_modal.jsx
@@ -12,9 +12,38 @@ import ChannelStore from '../stores/channel_store.jsx';
import TeamStore from '../stores/team_store.jsx';
import ConfirmModal from './confirm_modal.jsx';
+import {intlShape, injectIntl, defineMessages, FormattedMessage, FormattedHTMLMessage} from 'mm-intl';
+
const Modal = ReactBootstrap.Modal;
-export default class InviteMemberModal extends React.Component {
+const holders = defineMessages({
+ emailError: {
+ id: 'invite_member.emailError',
+ defaultMessage: 'Please enter a valid email address'
+ },
+ firstname: {
+ id: 'invite_member.firstname',
+ defaultMessage: 'First name'
+ },
+ lastname: {
+ id: 'invite_member.lastname',
+ defaultMessage: 'Last name'
+ },
+ modalTitle: {
+ id: 'invite_member.modalTitle',
+ defaultMessage: 'Discard Invitations?'
+ },
+ modalMessage: {
+ id: 'invite_member.modalMessage',
+ defaultMessage: 'You have unsent invitations, are you sure you want to discard them?'
+ },
+ modalButton: {
+ id: 'invite_member.modalButton',
+ defaultMessage: 'Yes, Discard'
+ }
+});
+
+class InviteMemberModal extends React.Component {
constructor(props) {
super(props);
@@ -72,7 +101,7 @@ export default class InviteMemberModal extends React.Component {
var invite = {};
invite.email = ReactDOM.findDOMNode(this.refs['email' + index]).value.trim();
if (!invite.email || !utils.isEmail(invite.email)) {
- emailErrors[index] = 'Please enter a valid email address';
+ emailErrors[index] = this.props.intl.formatMessage(holders.emailError);
valid = false;
} else {
emailErrors[index] = '';
@@ -103,7 +132,7 @@ export default class InviteMemberModal extends React.Component {
this.setState({isSendingEmails: false});
},
(err) => {
- if (err.message === 'This person is already on your team') {
+ if (err.id === 'api.team.invite_members.already.app_error') {
emailErrors[err.detailed_error] = err.message;
this.setState({emailErrors: emailErrors});
} else {
@@ -199,6 +228,7 @@ export default class InviteMemberModal extends React.Component {
render() {
var currentUser = UserStore.getCurrentUser();
+ const {formatMessage} = this.props.intl;
if (currentUser != null) {
var inviteSections = [];
@@ -252,7 +282,7 @@ export default class InviteMemberModal extends React.Component {
type='text'
className='form-control'
ref={'first_name' + index}
- placeholder='First name'
+ placeholder={formatMessage(holders.firstname)}
maxLength='64'
disabled={!this.state.emailEnabled || !this.state.userCreationEnabled}
spellCheck='false'
@@ -266,7 +296,7 @@ export default class InviteMemberModal extends React.Component {
type='text'
className='form-control'
ref={'last_name' + index}
- placeholder='Last name'
+ placeholder={formatMessage(holders.lastname)}
maxLength='64'
disabled={!this.state.emailEnabled || !this.state.userCreationEnabled}
spellCheck='false'
@@ -318,20 +348,48 @@ export default class InviteMemberModal extends React.Component {
type='button'
className='btn btn-default'
onClick={this.addInviteFields}
- >{'Add another'}</button>
+ >
+ <FormattedMessage
+ id='invite_member.addAnother'
+ defaultMessage='Add another'
+ />
+ </button>
<br/>
<br/>
- <span>{'People invited automatically join the '}<strong>{defaultChannelName}</strong>{' channel.'}</span>
+ <span>
+ <FormattedHTMLMessage
+ id='invite_member.autoJoin'
+ defaultMessage='People invited automatically join the <strong>{channel}</strong> channel.'
+ values={{
+ channel: defaultChannelName
+ }}
+ />
+ </span>
</div>
);
- var sendButtonLabel = 'Send Invitation';
+ var sendButtonLabel = (
+ <FormattedMessage
+ id='invite_member.send'
+ defaultMessage='Send Invitation'
+ />
+ );
if (this.state.isSendingEmails) {
sendButtonLabel = (
- <span><i className='fa fa-spinner fa-spin' />{' Sending'}</span>
+ <span><i className='fa fa-spinner fa-spin' />
+ <FormattedMessage
+ id='invite_member.sending'
+ defaultMessage=' Sending'
+ />
+ </span>
);
} else if (this.state.inviteIds.length > 1) {
- sendButtonLabel = 'Send Invitations';
+ sendButtonLabel = (
+ <FormattedMessage
+ id='invite_member.send2'
+ defaultMessage='Send Invitations'
+ />
+ );
}
sendButton = (
@@ -352,27 +410,46 @@ export default class InviteMemberModal extends React.Component {
href='#'
onClick={this.showGetTeamInviteLinkModal}
>
- {'Team Invite Link'}
+ <FormattedMessage
+ id='invite_member.inviteLink'
+ defaultMessage='Team Invite Link'
+ />
</a>
);
teamInviteLink = (
<p>
- {'You can also invite people using the '}{link}{'.'}
+ <FormattedMessage
+ id='invite_member.teamInviteLink'
+ defaultMessage='You can also invite people using the {link}.'
+ values={{
+ link: (link)
+ }}
+ />
</p>
);
}
content = (
<div>
- <p>{'Email is currently disabled for your team, and email invitations cannot be sent. Contact your system administrator to enable email and email invitations.'}</p>
+ <p>
+ <FormattedMessage
+ id='invite_member.content'
+ defaultMessage='Email is currently disabled for your team, and email invitations cannot be sent. Contact your system administrator to enable email and email invitations.'
+ />
+ </p>
{teamInviteLink}
</div>
);
} else {
content = (
<div>
- <p>{'User creation has been disabled for your team. Please ask your team administrator for details.'}</p>
+ <p>
+ <FormattedMessage
+ id='invite_member.disabled'
+ defaultMessage='User creation has been disabled for your team. Please ask your team administrator for details.'
+ />
+ </p>
</div>
);
}
@@ -387,7 +464,12 @@ export default class InviteMemberModal extends React.Component {
backdrop={this.state.isSendingEmails ? 'static' : true}
>
<Modal.Header closeButton={!this.state.isSendingEmails}>
- <Modal.Title>{'Invite New Member'}</Modal.Title>
+ <Modal.Title>
+ <FormattedMessage
+ id='invite_member.newMember'
+ defaultMessage='Invite New Member'
+ />
+ </Modal.Title>
</Modal.Header>
<Modal.Body ref='modalBody'>
<form role='form'>
@@ -402,15 +484,18 @@ export default class InviteMemberModal extends React.Component {
onClick={this.handleHide.bind(this, true)}
disabled={this.state.isSendingEmails}
>
- {'Cancel'}
+ <FormattedMessage
+ id='invite_member.cancel'
+ defaultMessage='Cancel'
+ />
</button>
{sendButton}
</Modal.Footer>
</Modal>
<ConfirmModal
- title='Discard Invitations?'
- message='You have unsent invitations, are you sure you want to discard them?'
- confirm_button='Yes, Discard'
+ title={formatMessage(holders.modalTitle)}
+ message={formatMessage(holders.modalMessage)}
+ confirm_button={formatMessage(holders.modalButton)}
show={this.state.showConfirmModal}
onConfirm={this.handleHide.bind(this, false)}
onCancel={() => this.setState({showConfirmModal: false})}
@@ -424,4 +509,7 @@ export default class InviteMemberModal extends React.Component {
}
InviteMemberModal.propTypes = {
+ intl: intlShape.isRequired
};
+
+export default injectIntl(InviteMemberModal); \ No newline at end of file
diff --git a/web/react/components/login.jsx b/web/react/components/login.jsx
index c4f530af0..0123a0f3c 100644
--- a/web/react/components/login.jsx
+++ b/web/react/components/login.jsx
@@ -2,6 +2,7 @@
// See License.txt for license information.
import LoginEmail from './login_email.jsx';
+import LoginUsername from './login_username.jsx';
import LoginLdap from './login_ldap.jsx';
import * as Utils from '../utils/utils.jsx';
@@ -35,7 +36,7 @@ export default class Login extends React.Component {
/>
</span>
</a>
- );
+ );
}
if (global.window.mm_config.EnableSignUpWithGoogle === 'true') {
@@ -87,7 +88,7 @@ export default class Login extends React.Component {
}
let emailSignup;
- if (global.window.mm_config.EnableSignUpWithEmail === 'true') {
+ if (global.window.mm_config.EnableSignInWithEmail === 'true') {
emailSignup = (
<LoginEmail
teamName={this.props.teamName}
@@ -189,6 +190,15 @@ export default class Login extends React.Component {
);
}
+ let usernameLogin = null;
+ if (global.window.mm_config.EnableSignInWithUsername === 'true') {
+ usernameLogin = (
+ <LoginUsername
+ teamName={this.props.teamName}
+ />
+ );
+ }
+
return (
<div className='signup-team__container'>
<h5 className='margin--less'>
@@ -210,6 +220,7 @@ export default class Login extends React.Component {
{extraBox}
{loginMessage}
{emailSignup}
+ {usernameLogin}
{ldapLogin}
{userSignUp}
{findTeams}
diff --git a/web/react/components/login_username.jsx b/web/react/components/login_username.jsx
new file mode 100644
index 000000000..f787490fa
--- /dev/null
+++ b/web/react/components/login_username.jsx
@@ -0,0 +1,181 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import * as Utils from '../utils/utils.jsx';
+import * as Client from '../utils/client.jsx';
+import UserStore from '../stores/user_store.jsx';
+
+import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'mm-intl';
+
+var holders = defineMessages({
+ badTeam: {
+ id: 'login_username.badTeam',
+ defaultMessage: 'Bad team name'
+ },
+ usernameReq: {
+ id: 'login_username.usernameReq',
+ defaultMessage: 'A username is required'
+ },
+ pwdReq: {
+ id: 'login_username.pwdReq',
+ defaultMessage: 'A password is required'
+ },
+ verifyEmailError: {
+ id: 'login_username.verifyEmailError',
+ defaultMessage: 'Please verify your email address. Check your inbox for an email.'
+ },
+ userNotFoundError: {
+ id: 'login_username.userNotFoundError',
+ defaultMessage: "We couldn't find an existing account matching your username for this team."
+ },
+ username: {
+ id: 'login_username.username',
+ defaultMessage: 'Username'
+ },
+ pwd: {
+ id: 'login_username.pwd',
+ defaultMessage: 'Password'
+ }
+});
+
+export default class LoginUsername extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.handleSubmit = this.handleSubmit.bind(this);
+
+ this.state = {
+ serverError: ''
+ };
+ }
+ handleSubmit(e) {
+ e.preventDefault();
+ const {formatMessage} = this.props.intl;
+ var state = {};
+
+ const name = this.props.teamName;
+ if (!name) {
+ state.serverError = formatMessage(holders.badTeam);
+ this.setState(state);
+ return;
+ }
+
+ const username = this.refs.username.value.trim();
+ if (!username) {
+ state.serverError = formatMessage(holders.usernameReq);
+ this.setState(state);
+ return;
+ }
+
+ const password = this.refs.password.value.trim();
+ if (!password) {
+ state.serverError = formatMessage(holders.pwdReq);
+ this.setState(state);
+ return;
+ }
+
+ state.serverError = '';
+ this.setState(state);
+
+ Client.loginByUsername(name, username, password,
+ () => {
+ UserStore.setLastUsername(username);
+
+ const redirect = Utils.getUrlParameter('redirect');
+ if (redirect) {
+ window.location.href = decodeURIComponent(redirect);
+ } else {
+ window.location.href = '/' + name + '/channels/town-square';
+ }
+ },
+ (err) => {
+ if (err.message === 'api.user.login.not_verified.app_error') {
+ state.serverError = formatMessage(holders.verifyEmailError);
+ } else if (err.message === 'store.sql_user.get_by_username.app_error') {
+ state.serverError = formatMessage(holders.userNotFoundError);
+ } else {
+ state.serverError = err.message;
+ }
+
+ this.valid = false;
+ this.setState(state);
+ }
+ );
+ }
+ render() {
+ let serverError;
+ let errorClass = '';
+ if (this.state.serverError) {
+ serverError = <label className='control-label'>{this.state.serverError}</label>;
+ errorClass = ' has-error';
+ }
+
+ let priorUsername = UserStore.getLastUsername();
+ let focusUsername = false;
+ let focusPassword = false;
+ if (priorUsername === '') {
+ focusUsername = true;
+ } else {
+ focusPassword = true;
+ }
+
+ const emailParam = Utils.getUrlParameter('email');
+ if (emailParam) {
+ priorUsername = decodeURIComponent(emailParam);
+ }
+
+ const {formatMessage} = this.props.intl;
+ return (
+ <form onSubmit={this.handleSubmit}>
+ <div className='signup__email-container'>
+ <div className={'form-group' + errorClass}>
+ {serverError}
+ </div>
+ <div className={'form-group' + errorClass}>
+ <input
+ autoFocus={focusUsername}
+ type='username'
+ className='form-control'
+ name='username'
+ defaultValue={priorUsername}
+ ref='username'
+ placeholder={formatMessage(holders.username)}
+ spellCheck='false'
+ />
+ </div>
+ <div className={'form-group' + errorClass}>
+ <input
+ autoFocus={focusPassword}
+ type='password'
+ className='form-control'
+ name='password'
+ ref='password'
+ placeholder={formatMessage(holders.pwd)}
+ spellCheck='false'
+ />
+ </div>
+ <div className='form-group'>
+ <button
+ type='submit'
+ className='btn btn-primary'
+ >
+ <FormattedMessage
+ id='login_username.signin'
+ defaultMessage='Sign in'
+ />
+ </button>
+ </div>
+ </div>
+ </form>
+ );
+ }
+}
+LoginUsername.defaultProps = {
+};
+
+LoginUsername.propTypes = {
+ intl: intlShape.isRequired,
+ teamName: React.PropTypes.string.isRequired
+};
+
+export default injectIntl(LoginUsername);
diff --git a/web/react/components/member_list_team_item.jsx b/web/react/components/member_list_team_item.jsx
index 7967c410d..6e1006911 100644
--- a/web/react/components/member_list_team_item.jsx
+++ b/web/react/components/member_list_team_item.jsx
@@ -6,6 +6,8 @@ import * as Client from '../utils/client.jsx';
import * as AsyncClient from '../utils/async_client.jsx';
import * as Utils from '../utils/utils.jsx';
+import {FormattedMessage} from 'mm-intl';
+
export default class MemberListTeamItem extends React.Component {
constructor(props) {
super(props);
@@ -78,14 +80,29 @@ export default class MemberListTeamItem extends React.Component {
}
const user = this.props.user;
- let currentRoles = 'Member';
+ let currentRoles = (
+ <FormattedMessage
+ id='member_team_item.member'
+ defaultMessage='Member'
+ />
+ );
const timestamp = UserStore.getCurrentUser().update_at;
if (user.roles.length > 0) {
if (Utils.isSystemAdmin(user.roles)) {
- currentRoles = 'System Admin';
+ currentRoles = (
+ <FormattedMessage
+ id='member_team_item.systemAdmin'
+ defaultMessage='System Admin'
+ />
+ );
} else if (Utils.isAdmin(user.roles)) {
- currentRoles = 'Team Admin';
+ currentRoles = (
+ <FormattedMessage
+ id='member_team_item.teamAdmin'
+ defaultMessage='Team Admin'
+ />
+ );
} else {
currentRoles = user.roles.charAt(0).toUpperCase() + user.roles.slice(1);
}
@@ -98,7 +115,12 @@ export default class MemberListTeamItem extends React.Component {
let showMakeNotActive = user.roles !== 'system_admin';
if (user.delete_at > 0) {
- currentRoles = 'Inactive';
+ currentRoles = (
+ <FormattedMessage
+ id='member_team_item.inactive'
+ defaultMessage='Inactive'
+ />
+ );
showMakeMember = false;
showMakeAdmin = false;
showMakeActive = true;
@@ -114,7 +136,10 @@ export default class MemberListTeamItem extends React.Component {
href='#'
onClick={this.handleMakeAdmin}
>
- {'Make Team Admin'}
+ <FormattedMessage
+ id='member_team_item.makeAdmin'
+ defaultMessage='Make Team Admin'
+ />
</a>
</li>
);
@@ -129,7 +154,10 @@ export default class MemberListTeamItem extends React.Component {
href='#'
onClick={this.handleMakeMember}
>
- {'Make Member'}
+ <FormattedMessage
+ id='member_team_item.makeMember'
+ defaultMessage='Make Member'
+ />
</a>
</li>
);
@@ -144,7 +172,10 @@ export default class MemberListTeamItem extends React.Component {
href='#'
onClick={this.handleMakeActive}
>
- {'Make Active'}
+ <FormattedMessage
+ id='member_team_item.makeActive'
+ defaultMessage='Make Active'
+ />
</a>
</li>
);
@@ -159,7 +190,10 @@ export default class MemberListTeamItem extends React.Component {
href='#'
onClick={this.handleMakeNotActive}
>
- {'Make Inactive'}
+ <FormattedMessage
+ id='member_team_item.makeInactive'
+ defaultMessage='Make Inactive'
+ />
</a>
</li>
);
diff --git a/web/react/components/more_channels.jsx b/web/react/components/more_channels.jsx
index 29512b9b7..d12ea4703 100644
--- a/web/react/components/more_channels.jsx
+++ b/web/react/components/more_channels.jsx
@@ -8,6 +8,8 @@ import ChannelStore from '../stores/channel_store.jsx';
import LoadingScreen from './loading_screen.jsx';
import NewChannelFlow from './new_channel_flow.jsx';
+import {FormattedMessage} from 'mm-intl';
+
function getStateFromStores() {
return {
channels: ChannelStore.getMoreAll(),
@@ -100,7 +102,10 @@ export default class MoreChannels extends React.Component {
onClick={self.handleJoin.bind(self, channel, index)}
className='btn btn-primary'
>
- Join
+ <FormattedMessage
+ id='more_channels.join'
+ defaultMessage='Join'
+ />
</button>
);
}
@@ -123,8 +128,18 @@ export default class MoreChannels extends React.Component {
} else {
moreChannels = (
<div className='no-channel-message'>
- <p className='primary-message'>No more channels to join</p>
- <p className='secondary-message'>Click 'Create New Channel' to make a new one</p>
+ <p className='primary-message'>
+ <FormattedMessage
+ id='more_channels.noMore'
+ defaultMessage='No more channels to join'
+ />
+ </p>
+ <p className='secondary-message'>
+ <FormattedMessage
+ id='more_channels.createClick'
+ defaultMessage="Click 'Create New Channel' to make a new one"
+ />
+ </p>
</div>
);
}
@@ -148,15 +163,28 @@ export default class MoreChannels extends React.Component {
data-dismiss='modal'
>
<span aria-hidden='true'>{'×'}</span>
- <span className='sr-only'>{'Close'}</span>
+ <span className='sr-only'>
+ <FormattedMessage
+ id='more_channels.close'
+ defaultMessage='Close'
+ />
+ </span>
</button>
- <h4 className='modal-title'>{'More Channels'}</h4>
+ <h4 className='modal-title'>
+ <FormattedMessage
+ id='more_channels.title'
+ defaultMessage='More Channels'
+ />
+ </h4>
<button
type='button'
className='btn btn-primary channel-create-btn'
onClick={this.handleNewChannel}
>
- {'Create New Channel'}
+ <FormattedMessage
+ id='more_channels.create'
+ defaultMessage='Create New Channel'
+ />
</button>
<NewChannelFlow
show={this.state.showNewChannelModal}
@@ -174,7 +202,10 @@ export default class MoreChannels extends React.Component {
className='btn btn-default'
data-dismiss='modal'
>
- {'Close'}
+ <FormattedMessage
+ id='more_channels.close'
+ defaultMessage='Close'
+ />
</button>
</div>
</div>
diff --git a/web/react/components/more_direct_channels.jsx b/web/react/components/more_direct_channels.jsx
index 3661b19e6..f8a6884d0 100644
--- a/web/react/components/more_direct_channels.jsx
+++ b/web/react/components/more_direct_channels.jsx
@@ -5,7 +5,20 @@ const Modal = ReactBootstrap.Modal;
import UserStore from '../stores/user_store.jsx';
import * as Utils from '../utils/utils.jsx';
-export default class MoreDirectChannels extends React.Component {
+import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl';
+
+const holders = defineMessages({
+ member: {
+ id: 'more_direct_channels.member',
+ defaultMessage: 'Member'
+ },
+ search: {
+ id: 'more_direct_channels.search',
+ defaultMessage: 'Search members'
+ }
+});
+
+class MoreDirectChannels extends React.Component {
constructor(props) {
super(props);
@@ -148,7 +161,10 @@ export default class MoreDirectChannels extends React.Component {
className='btn btn-primary btn-message'
onClick={this.handleShowDirectChannel.bind(this, user)}
>
- {'Message'}
+ <FormattedMessage
+ id='more_direct_channels.message'
+ defaultMessage='Message'
+ />
</button>
);
}
@@ -180,6 +196,7 @@ export default class MoreDirectChannels extends React.Component {
}
render() {
+ const {formatMessage} = this.props.intl;
if (!this.props.show) {
return null;
}
@@ -199,19 +216,44 @@ export default class MoreDirectChannels extends React.Component {
const userEntries = users.map(this.createRowForUser);
if (userEntries.length === 0) {
- userEntries.push(<tr key='no-users-found'><td>{'No users found :('}</td></tr>);
+ userEntries.push(
+ <tr key='no-users-found'><td>
+ <FormattedMessage
+ id='more_direct_channels.notFound'
+ defaultMessage='No users found :('
+ />
+ </td></tr>);
}
- let memberString = 'Member';
+ let memberString = formatMessage(holders.member);
if (users.length !== 1) {
memberString += 's';
}
let count;
if (users.length === this.state.users.length) {
- count = `${users.length} ${memberString}`;
+ count = (
+ <FormattedMessage
+ id='more_direct_channels.count'
+ defaultMessage='{count} {member}'
+ values={{
+ count: users.length,
+ member: memberString
+ }}
+ />
+ );
} else {
- count = `${users.length} ${memberString} of ${this.state.users.length} Total`;
+ count = (
+ <FormattedMessage
+ id='more_direct_channels.countTotal'
+ defaultMessage='{count} {member} of {total} Total'
+ values={{
+ count: users.length,
+ member: memberString,
+ total: this.state.users.length
+ }}
+ />
+ );
}
return (
@@ -221,7 +263,12 @@ export default class MoreDirectChannels extends React.Component {
onHide={this.handleHide}
>
<Modal.Header closeButton={true}>
- <Modal.Title>{'Direct Messages'}</Modal.Title>
+ <Modal.Title>
+ <FormattedMessage
+ id='more_direct_channels.title'
+ defaultMessage='Direct Messages'
+ />
+ </Modal.Title>
</Modal.Header>
<Modal.Body ref='modalBody'>
<div className='filter-row'>
@@ -229,7 +276,7 @@ export default class MoreDirectChannels extends React.Component {
<input
ref='filter'
className='form-control filter-textbox'
- placeholder='Search members'
+ placeholder={formatMessage(holders.search)}
onInput={this.handleFilterChange}
/>
</div>
@@ -254,7 +301,10 @@ export default class MoreDirectChannels extends React.Component {
className='btn btn-default'
onClick={this.handleHide}
>
- {'Close'}
+ <FormattedMessage
+ id='more_direct_channels.close'
+ defaultMessage='Close'
+ />
</button>
</Modal.Footer>
</Modal>
@@ -263,6 +313,9 @@ export default class MoreDirectChannels extends React.Component {
}
MoreDirectChannels.propTypes = {
+ intl: intlShape.isRequired,
show: React.PropTypes.bool.isRequired,
onModalDismissed: React.PropTypes.func
};
+
+export default injectIntl(MoreDirectChannels); \ No newline at end of file
diff --git a/web/react/components/msg_typing.jsx b/web/react/components/msg_typing.jsx
index 35a832875..b95b06260 100644
--- a/web/react/components/msg_typing.jsx
+++ b/web/react/components/msg_typing.jsx
@@ -5,9 +5,19 @@ import SocketStore from '../stores/socket_store.jsx';
import UserStore from '../stores/user_store.jsx';
import Constants from '../utils/constants.jsx';
+
+import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl';
+
const SocketEvents = Constants.SocketEvents;
-export default class MsgTyping extends React.Component {
+const holders = defineMessages({
+ someone: {
+ id: 'msg_typing.someone',
+ defaultMessage: 'Someone'
+ }
+});
+
+class MsgTyping extends React.Component {
constructor(props) {
super(props);
@@ -44,10 +54,10 @@ export default class MsgTyping extends React.Component {
}
onChange(msg) {
- let username = 'Someone';
+ let username = this.props.intl.formatMessage(holders.someone);
if (msg.action === SocketEvents.TYPING &&
- this.props.channelId === msg.channel_id &&
- this.props.parentId === msg.props.parent_id) {
+ this.props.channelId === msg.channel_id &&
+ this.props.parentId === msg.props.parent_id) {
if (UserStore.hasProfile(msg.user_id)) {
username = UserStore.getProfile(msg.user_id).username;
}
@@ -80,11 +90,28 @@ export default class MsgTyping extends React.Component {
text = '';
break;
case 1:
- text = users[0] + ' is typing...';
+ text = (
+ <FormattedMessage
+ id='msg_typing.isTyping'
+ defaultMessage='{user} is typing...'
+ values={{
+ user: users[0]
+ }}
+ />
+ );
break;
default: {
const last = users.pop();
- text = users.join(', ') + ' and ' + last + ' are typing...';
+ text = (
+ <FormattedMessage
+ id='msg_typing.areTyping'
+ defaultMessage='{users} and {last} are typing...'
+ vaues={{
+ users: users.join(', '),
+ last: last
+ }}
+ />
+ );
break;
}
}
@@ -100,6 +127,9 @@ export default class MsgTyping extends React.Component {
}
MsgTyping.propTypes = {
+ intl: intlShape.isRequired,
channelId: React.PropTypes.string,
parentId: React.PropTypes.string
};
+
+export default injectIntl(MsgTyping); \ No newline at end of file
diff --git a/web/react/components/navbar.jsx b/web/react/components/navbar.jsx
index ae14fca2f..7326a9ef8 100644
--- a/web/react/components/navbar.jsx
+++ b/web/react/components/navbar.jsx
@@ -392,10 +392,14 @@ export default class Navbar extends React.Component {
} else if (channel.type === 'D') {
isDirect = true;
if (this.state.users.length > 1) {
+ let p;
if (this.state.users[0].id === currentId) {
- channelTitle = UserStore.getProfile(this.state.users[1].id).username;
+ p = UserStore.getProfile(this.state.users[1].id);
} else {
- channelTitle = UserStore.getProfile(this.state.users[0].id).username;
+ p = UserStore.getProfile(this.state.users[0].id);
+ }
+ if (p != null) {
+ channelTitle = p.username;
}
}
}
diff --git a/web/react/components/navbar_dropdown.jsx b/web/react/components/navbar_dropdown.jsx
index d4ec5a5f5..e9df03c33 100644
--- a/web/react/components/navbar_dropdown.jsx
+++ b/web/react/components/navbar_dropdown.jsx
@@ -14,6 +14,8 @@ import UserSettingsModal from './user_settings/user_settings_modal.jsx';
import Constants from '../utils/constants.jsx';
+import {FormattedMessage} from 'mm-intl';
+
function getStateFromStores() {
const teams = [];
const teamsObject = UserStore.getTeams();
@@ -97,7 +99,10 @@ export default class NavbarDropdown extends React.Component {
href='#'
onClick={EventHelpers.showInviteMemberModal}
>
- {'Invite New Member'}
+ <FormattedMessage
+ id='navbar_dropdown.inviteMember'
+ defaultMessage='Invite New Member'
+ />
</a>
</li>
);
@@ -109,7 +114,10 @@ export default class NavbarDropdown extends React.Component {
href='#'
onClick={EventHelpers.showGetTeamInviteLinkModal}
>
- {'Get Team Invite Link'}
+ <FormattedMessage
+ id='navbar_dropdown.teamLink'
+ defaultMessage='Get Team Invite Link'
+ />
</a>
</li>
);
@@ -120,7 +128,10 @@ export default class NavbarDropdown extends React.Component {
manageLink = (
<li>
<ToggleModalButton dialogType={TeamMembersModal}>
- {'Manage Members'}
+ <FormattedMessage
+ id='navbar_dropdown.manageMembers'
+ defaultMessage='Manage Members'
+ />
</ToggleModalButton>
</li>
);
@@ -134,7 +145,10 @@ export default class NavbarDropdown extends React.Component {
data-toggle='modal'
data-target='#team_settings'
>
- {'Team Settings'}
+ <FormattedMessage
+ id='navbar_dropdown.teamSettings'
+ defaultMessage='Team Settings'
+ />
</a>
</li>
);
@@ -146,7 +160,10 @@ export default class NavbarDropdown extends React.Component {
<a
href={'/admin_console?' + Utils.getSessionIndex()}
>
- {'System Console'}
+ <FormattedMessage
+ id='navbar_dropdown.console'
+ defaultMessage='System Console'
+ />
</a>
</li>
);
@@ -165,7 +182,16 @@ export default class NavbarDropdown extends React.Component {
this.state.teams.forEach((team) => {
if (team.name !== this.props.teamName) {
- teams.push(<li key={team.name}><a href={Utils.getWindowLocationOrigin() + '/' + team.name}>{'Switch to ' + team.display_name}</a></li>);
+ teams.push(
+ <li key={team.name}><a href={Utils.getWindowLocationOrigin() + '/' + team.name}>
+ <FormattedMessage
+ id='navbar_dropdown.switchTeam'
+ defaultMessage='Switch to {team}'
+ values={{
+ team: team.display_name
+ }}
+ />
+ </a></li>);
}
});
}
@@ -178,7 +204,10 @@ export default class NavbarDropdown extends React.Component {
target='_blank'
href={Utils.getWindowLocationOrigin() + '/signup_team'}
>
- {'Create a New Team'}
+ <FormattedMessage
+ id='navbar_dropdown.create'
+ defaultMessage='Create a New Team'
+ />
</a>
</li>
);
@@ -192,7 +221,10 @@ export default class NavbarDropdown extends React.Component {
target='_blank'
href={global.window.mm_config.HelpLink}
>
- {'Help'}
+ <FormattedMessage
+ id='navbar_dropdown.help'
+ defaultMessage='Help'
+ />
</a>
</li>
);
@@ -206,7 +238,10 @@ export default class NavbarDropdown extends React.Component {
target='_blank'
href={global.window.mm_config.ReportAProblemLink}
>
- {'Report a Problem'}
+ <FormattedMessage
+ id='navbar_dropdown.report'
+ defaultMessage='Report a Problem'
+ />
</a>
</li>
);
@@ -239,7 +274,10 @@ export default class NavbarDropdown extends React.Component {
href='#'
onClick={() => this.setState({showUserSettingsModal: true})}
>
- {'Account Settings'}
+ <FormattedMessage
+ id='navbar_dropdown.accountSettings'
+ defaultMessage='Account Settings'
+ />
</a>
</li>
{inviteLink}
@@ -249,7 +287,10 @@ export default class NavbarDropdown extends React.Component {
href='#'
onClick={this.handleLogoutClick}
>
- {'Logout'}
+ <FormattedMessage
+ id='navbar_dropdown.logout'
+ defaultMessage='Logout'
+ />
</a>
</li>
{adminDivider}
@@ -265,7 +306,10 @@ export default class NavbarDropdown extends React.Component {
href='#'
onClick={this.handleAboutModal}
>
- {'About Mattermost'}
+ <FormattedMessage
+ id='navbar_dropdown.about'
+ defaultMessage='About Mattermost'
+ />
</a>
</li>
<UserSettingsModal
diff --git a/web/react/components/new_channel_flow.jsx b/web/react/components/new_channel_flow.jsx
index 3a114aa19..a0bb14e8f 100644
--- a/web/react/components/new_channel_flow.jsx
+++ b/web/react/components/new_channel_flow.jsx
@@ -9,11 +9,47 @@ import UserStore from '../stores/user_store.jsx';
import NewChannelModal from './new_channel_modal.jsx';
import ChangeURLModal from './change_url_modal.jsx';
+import {intlShape, injectIntl, defineMessages} from 'mm-intl';
+
const SHOW_NEW_CHANNEL = 1;
const SHOW_EDIT_URL = 2;
const SHOW_EDIT_URL_THEN_COMPLETE = 3;
+const messages = defineMessages({
+ invalidName: {
+ id: 'channel_flow.invalidName',
+ defaultMessage: 'Invalid Channel Name'
+ },
+ alreadyExist: {
+ id: 'channel_flow.alreadyExist',
+ defaultMessage: 'A channel with that URL already exists'
+ },
+ channel: {
+ id: 'channel_flow.channel',
+ defaultMessage: 'Channel'
+ },
+ group: {
+ id: 'channel_flow.group',
+ defaultMessage: 'Group'
+ },
+ change: {
+ id: 'channel_flow.changeUrlTitle',
+ defaultMessage: 'Change {term} URL'
+ },
+ set: {
+ id: 'channel_flow.set_url_title',
+ defaultMessage: 'Set {term} URL'
+ },
+ create: {
+ id: 'channel_flow.create',
+ defaultMessage: 'Create {term}'
+ },
+ changeUrlDescription: {
+ id: 'channel_flow.changeUrlDescription',
+ defaultMessage: 'Some characters are not allowed in URLs and may be removed.'
+ }
+});
-export default class NewChannelFlow extends React.Component {
+class NewChannelFlow extends React.Component {
constructor(props) {
super(props);
@@ -51,9 +87,10 @@ export default class NewChannelFlow extends React.Component {
doSubmit() {
var channel = {};
+ const {formatMessage} = this.props.intl;
channel.display_name = this.state.channelDisplayName;
if (!channel.display_name) {
- this.setState({serverError: 'Invalid Channel Name'});
+ this.setState({serverError: formatMessage(messages.invalidName)});
return;
}
@@ -75,11 +112,11 @@ export default class NewChannelFlow extends React.Component {
Utils.switchChannel(data);
},
(err) => {
- if (err.message === 'Name must be 2 or more lowercase alphanumeric characters') {
+ if (err.id === 'model.channel.is_valid.2_or_more.app_error') {
this.setState({flowState: SHOW_EDIT_URL_THEN_COMPLETE});
}
- if (err.message === 'A channel with that handle already exists') {
- this.setState({serverError: 'A channel with that URL already exists'});
+ if (err.id === 'store.sql_channel.update.exists.app_error') {
+ this.setState({serverError: formatMessage(messages.alreadyExist)});
return;
}
this.setState({serverError: err.message});
@@ -130,27 +167,29 @@ export default class NewChannelFlow extends React.Component {
let changeURLSubmitButtonText = '';
let channelTerm = '';
+ const {formatMessage} = this.props.intl;
+
// Only listen to flow state if we are being shown
if (this.props.show) {
switch (this.state.flowState) {
case SHOW_NEW_CHANNEL:
if (this.state.channelType === 'O') {
showChannelModal = true;
- channelTerm = 'Channel';
+ channelTerm = formatMessage(messages.channel);
} else {
showGroupModal = true;
- channelTerm = 'Group';
+ channelTerm = formatMessage(messages.group);
}
break;
case SHOW_EDIT_URL:
showChangeURLModal = true;
- changeURLTitle = 'Change ' + channelTerm + ' URL';
- changeURLSubmitButtonText = 'Change ' + channelTerm + ' URL';
+ changeURLTitle = formatMessage(messages.change, {term: channelTerm});
+ changeURLSubmitButtonText = formatMessage(messages.change, {term: channelTerm});
break;
case SHOW_EDIT_URL_THEN_COMPLETE:
showChangeURLModal = true;
- changeURLTitle = 'Set ' + channelTerm + ' URL';
- changeURLSubmitButtonText = 'Create ' + channelTerm;
+ changeURLTitle = formatMessage(messages.set, {term: channelTerm});
+ changeURLSubmitButtonText = formatMessage(messages.create, {term: channelTerm});
break;
}
}
@@ -181,7 +220,7 @@ export default class NewChannelFlow extends React.Component {
<ChangeURLModal
show={showChangeURLModal}
title={changeURLTitle}
- description={'Some characters are not allowed in URLs and may be removed.'}
+ description={formatMessage(messages.changeUrlDescription)}
urlLabel={channelTerm + ' URL'}
submitButtonText={changeURLSubmitButtonText}
currentURL={this.state.channelName}
@@ -200,7 +239,10 @@ NewChannelFlow.defaultProps = {
};
NewChannelFlow.propTypes = {
+ intl: intlShape.isRequired,
show: React.PropTypes.bool.isRequired,
channelType: React.PropTypes.string.isRequired,
onModalDismissed: React.PropTypes.func.isRequired
};
+
+export default injectIntl(NewChannelFlow); \ No newline at end of file
diff --git a/web/react/components/new_channel_modal.jsx b/web/react/components/new_channel_modal.jsx
index 9f733c476..788e6dc1b 100644
--- a/web/react/components/new_channel_modal.jsx
+++ b/web/react/components/new_channel_modal.jsx
@@ -2,9 +2,19 @@
// See License.txt for license information.
import * as Utils from '../utils/utils.jsx';
+
+import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl';
+
var Modal = ReactBootstrap.Modal;
-export default class NewChannelModal extends React.Component {
+const holders = defineMessages({
+ nameEx: {
+ id: 'channel_modal.nameEx',
+ defaultMessage: 'E.g.: "Bugs", "Marketing", "办公室恋情"'
+ }
+});
+
+class NewChannelModal extends React.Component {
constructor(props) {
super(props);
@@ -32,7 +42,7 @@ export default class NewChannelModal extends React.Component {
const displayName = ReactDOM.findDOMNode(this.refs.display_name).value.trim();
if (displayName.length < 1) {
- this.setState({displayNameError: 'This field is required'});
+ this.setState({displayNameError: true});
return;
}
@@ -51,7 +61,15 @@ export default class NewChannelModal extends React.Component {
var displayNameClass = 'form-group';
if (this.state.displayNameError) {
- displayNameError = <p className='input__help error'>{this.state.displayNameError}</p>;
+ displayNameError = (
+ <p className='input__help error'>
+ <FormattedMessage
+ id='channel_modal.displayNameError'
+ defaultMessage='This field is required'
+ />
+ {this.state.displayNameError}
+ </p>
+ );
displayNameClass += ' has-error';
}
@@ -63,29 +81,51 @@ export default class NewChannelModal extends React.Component {
var channelSwitchText = '';
switch (this.props.channelType) {
case 'P':
- channelTerm = 'Group';
+ channelTerm = (
+ <FormattedMessage
+ id='channel_modal.group'
+ defaultMessage='Group'
+ />
+ );
channelSwitchText = (
<div className='modal-intro'>
- {'Create a new private group with restricted membership. '}
+ <FormattedMessage
+ id='channel_modal.privateGroup1'
+ defaultMessage='Create a new private group with restricted membership. '
+ />
<a
href='#'
onClick={this.props.onTypeSwitched}
>
- {'Create a public channel'}
+ <FormattedMessage
+ id='channel_modal.publicChannel1'
+ defaultMessage='Create a public channel'
+ />
</a>
</div>
);
break;
case 'O':
- channelTerm = 'Channel';
+ channelTerm = (
+ <FormattedMessage
+ id='channel_modal.channel'
+ defaultMessage='Channel'
+ />
+ );
channelSwitchText = (
<div className='modal-intro'>
- {'Create a new public channel anyone can join. '}
+ <FormattedMessage
+ id='channel_modal.publicChannel2'
+ defaultMessage='Create a new public channel anyone can join. '
+ />
<a
href='#'
onClick={this.props.onTypeSwitched}
>
- {'Create a private group'}
+ <FormattedMessage
+ id='channel_modal.privateGroup2'
+ defaultMessage='Create a private group'
+ />
</a>
</div>
);
@@ -102,7 +142,13 @@ export default class NewChannelModal extends React.Component {
onHide={this.props.onModalDismissed}
>
<Modal.Header closeButton={true}>
- <Modal.Title>{'New ' + channelTerm}</Modal.Title>
+ <Modal.Title>
+ <FormattedMessage
+ id='channel_modal.modalTitle'
+ defaultMessage='New '
+ />
+ {channelTerm}
+ </Modal.Title>
</Modal.Header>
<form
role='form'
@@ -113,14 +159,19 @@ export default class NewChannelModal extends React.Component {
{channelSwitchText}
</div>
<div className={displayNameClass}>
- <label className='col-sm-3 form__label control-label'>{'Name'}</label>
+ <label className='col-sm-3 form__label control-label'>
+ <FormattedMessage
+ id='channel_modal.name'
+ defaultMessage='Name'
+ />
+ </label>
<div className='col-sm-9'>
<input
onChange={this.handleChange}
type='text'
ref='display_name'
className='form-control'
- placeholder='E.g.: "Bugs", "Marketing", "办公室恋情"'
+ placeholder={this.props.intl.formatMessage(holders.nameEx)}
maxLength='22'
value={this.props.channelData.displayName}
autoFocus={true}
@@ -133,7 +184,10 @@ export default class NewChannelModal extends React.Component {
href='#'
onClick={this.props.onChangeURLPressed}
>
- {'Edit'}
+ <FormattedMessage
+ id='channel_modal.edit'
+ defaultMessage='Edit'
+ />
</a>
{')'}
</p>
@@ -141,22 +195,38 @@ export default class NewChannelModal extends React.Component {
</div>
<div className='form-group less'>
<div className='col-sm-3'>
- <label className='form__label control-label'>{'Purpose'}</label>
- <label className='form__label light'>{'(optional)'}</label>
+ <label className='form__label control-label'>
+ <FormattedMessage
+ id='channel_modal.purpose'
+ defaultMessage='Purpose'
+ />
+ </label>
+ <label className='form__label light'>
+ <FormattedMessage
+ id='channel_modal.optional'
+ defaultMessage='(optional)'
+ />
+ </label>
</div>
<div className='col-sm-9'>
<textarea
className='form-control no-resize'
ref='channel_purpose'
rows='4'
- placeholder='Purpose'
+ placeholder={this.props.intl.formatMessage({id: 'channel_modal.purpose'})}
maxLength='128'
value={this.props.channelData.purpose}
onChange={this.handleChange}
tabIndex='2'
/>
<p className='input__help'>
- {`Describe how this ${channelTerm} should be used.`}
+ <FormattedMessage
+ id='channel_modal.descriptionHelp'
+ defaultMessage='Describe how this {term} should be used.'
+ values={{
+ term: (channelTerm)
+ }}
+ />
</p>
{serverError}
</div>
@@ -168,7 +238,10 @@ export default class NewChannelModal extends React.Component {
className='btn btn-default'
onClick={this.props.onModalDismissed}
>
- {'Cancel'}
+ <FormattedMessage
+ id='channel_modal.cancel'
+ defaultMessage='Cancel'
+ />
</button>
<button
onClick={this.handleSubmit}
@@ -176,7 +249,11 @@ export default class NewChannelModal extends React.Component {
className='btn btn-primary'
tabIndex='3'
>
- {'Create New ' + channelTerm}
+ <FormattedMessage
+ id='channel_modal.createNew'
+ defaultMessage='Create New '
+ />
+ {channelTerm}
</button>
</Modal.Footer>
</form>
@@ -192,6 +269,7 @@ NewChannelModal.defaultProps = {
serverError: ''
};
NewChannelModal.propTypes = {
+ intl: intlShape.isRequired,
show: React.PropTypes.bool.isRequired,
channelType: React.PropTypes.string.isRequired,
channelData: React.PropTypes.object.isRequired,
@@ -202,3 +280,5 @@ NewChannelModal.propTypes = {
onChangeURLPressed: React.PropTypes.func.isRequired,
onDataChanged: React.PropTypes.func.isRequired
};
+
+export default injectIntl(NewChannelModal); \ No newline at end of file
diff --git a/web/react/components/post_info.jsx b/web/react/components/post_info.jsx
index 73b47024c..2bff675a9 100644
--- a/web/react/components/post_info.jsx
+++ b/web/react/components/post_info.jsx
@@ -22,26 +22,6 @@ export default class PostInfo extends React.Component {
this.handlePermalinkCopy = this.handlePermalinkCopy.bind(this);
}
- createReplyLink() {
- if (this.props.allowReply === 'true') {
- var hideReply = '';
-
- if (this.props.commentCount >= 1) {
- hideReply = ' post__reply--hide';
- }
-
- return (
- <div className={'post__reply' + hideReply}>
- <a
- onClick={this.props.handleCommentClick}
- href='#'
- >
- <span dangerouslySetInnerHTML={{__html: Constants.REPLY_ICON}}/>
- </a>
- </div>
- );
- }
- }
createDropdown() {
var post = this.props.post;
var isOwner = UserStore.getCurrentId() === post.user_id;
@@ -62,6 +42,23 @@ export default class PostInfo extends React.Component {
dataComments = this.props.commentCount;
}
+ if (this.props.allowReply === 'true') {
+ dropdownContents.push(
+ <li
+ key='replyLink'
+ role='presentation'
+ >
+ <a
+ className='link__reply theme'
+ href='#'
+ onClick={this.props.handleCommentClick}
+ >
+ {'Reply'}
+ </a>
+ </li>
+ );
+ }
+
dropdownContents.push(
<li
key='copyLink'
@@ -176,7 +173,7 @@ export default class PostInfo extends React.Component {
>
<span
className='comment-icon'
- dangerouslySetInnerHTML={{__html: Constants.COMMENT_ICON}}
+ dangerouslySetInnerHTML={{__html: Constants.REPLY_ICON}}
/>
{commentCountText}
</a>
@@ -184,7 +181,6 @@ export default class PostInfo extends React.Component {
}
var dropdown = this.createDropdown();
- var replyLink = this.createReplyLink();
const permalink = TeamStore.getCurrentTeamUrl() + '/pl/' + post.id;
const copyButtonText = this.state.copiedLink ? (<div>{'Copy '}<i className='fa fa-check'/></div>) : 'Copy';
@@ -227,7 +223,6 @@ export default class PostInfo extends React.Component {
/>
</li>
<li className='col col__reply'>
- {replyLink}
<div
className='dropdown'
ref='dotMenu'
diff --git a/web/react/components/register_app_modal.jsx b/web/react/components/register_app_modal.jsx
index f49b33f73..e6d13863b 100644
--- a/web/react/components/register_app_modal.jsx
+++ b/web/react/components/register_app_modal.jsx
@@ -7,9 +7,22 @@ import ModalStore from '../stores/modal_store.jsx';
const Modal = ReactBootstrap.Modal;
import Constants from '../utils/constants.jsx';
+import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl';
+
const ActionTypes = Constants.ActionTypes;
-export default class RegisterAppModal extends React.Component {
+const holders = defineMessages({
+ required: {
+ id: 'register_app.required',
+ defaultMessage: 'Required'
+ },
+ optional: {
+ id: 'register_app.optional',
+ defaultMessage: 'Optional'
+ }
+});
+
+class RegisterAppModal extends React.Component {
constructor() {
super();
@@ -60,7 +73,7 @@ export default class RegisterAppModal extends React.Component {
var name = this.refs.name.value;
if (!name || name.length === 0) {
- state.nameError = 'Application name must be filled in.';
+ state.nameError = true;
this.setState(state);
return;
}
@@ -69,7 +82,7 @@ export default class RegisterAppModal extends React.Component {
var homepage = this.refs.homepage.value;
if (!homepage || homepage.length === 0) {
- state.homepageError = 'Homepage must be filled in.';
+ state.homepageError = true;
this.setState(state);
return;
}
@@ -81,7 +94,7 @@ export default class RegisterAppModal extends React.Component {
var rawCallbacks = this.refs.callback.value.trim();
if (!rawCallbacks || rawCallbacks.length === 0) {
- state.callbackError = 'At least one callback URL must be filled in.';
+ state.callbackError = true;
this.setState(state);
return;
}
@@ -112,17 +125,45 @@ export default class RegisterAppModal extends React.Component {
this.setState({saved: this.refs.save.checked});
}
render() {
+ const {formatMessage} = this.props.intl;
var nameError;
if (this.state.nameError) {
- nameError = <div className='form-group has-error'><label className='control-label'>{this.state.nameError}</label></div>;
+ nameError = (
+ <div className='form-group has-error'>
+ <label className='control-label'>
+ <FormattedMessage
+ id='register_app.nameError'
+ defaultMessage='Application name must be filled in.'
+ />
+ </label>
+ </div>
+ );
}
var homepageError;
if (this.state.homepageError) {
- homepageError = <div className='form-group has-error'><label className='control-label'>{this.state.homepageError}</label></div>;
+ homepageError = (
+ <div className='form-group has-error'>
+ <label className='control-label'>
+ <FormattedMessage
+ id='register_app.homepageError'
+ defaultMessage='Homepage must be filled in.'
+ />
+ </label>
+ </div>
+ );
}
var callbackError;
if (this.state.callbackError) {
- callbackError = <div className='form-group has-error'><label className='control-label'>{this.state.callbackError}</label></div>;
+ callbackError = (
+ <div className='form-group has-error'>
+ <label className='control-label'>
+ <FormattedMessage
+ id='register_app.callbackError'
+ defaultMessage='At least one callback URL must be filled in.'
+ />
+ </label>
+ </div>
+ );
}
var serverError;
if (this.state.serverError) {
@@ -135,50 +176,75 @@ export default class RegisterAppModal extends React.Component {
body = (
<div className='settings-modal'>
<div className='form-horizontal user-settings'>
- <h4 className='padding-bottom x3'>{'Register a New Application'}</h4>
+ <h4 className='padding-bottom x3'>
+ <FormattedMessage
+ id='register_app.title'
+ defaultMessage='Register a New Application'
+ />
+ </h4>
<div className='row'>
- <label className='col-sm-4 control-label'>{'Application Name'}</label>
+ <label className='col-sm-4 control-label'>
+ <FormattedMessage
+ id='register_app.name'
+ defaultMessage='Application Name'
+ />
+ </label>
<div className='col-sm-7'>
<input
ref='name'
className='form-control'
type='text'
- placeholder='Required'
+ placeholder={formatMessage(holders.required)}
/>
{nameError}
</div>
</div>
<div className='row padding-top x2'>
- <label className='col-sm-4 control-label'>{'Homepage URL'}</label>
+ <label className='col-sm-4 control-label'>
+ <FormattedMessage
+ id='register_app.homepage'
+ defaultMessage='Homepage URL'
+ />
+ </label>
<div className='col-sm-7'>
<input
ref='homepage'
className='form-control'
type='text'
- placeholder='Required'
+ placeholder={formatMessage(holders.required)}
/>
{homepageError}
</div>
</div>
<div className='row padding-top x2'>
- <label className='col-sm-4 control-label'>{'Description'}</label>
+ <label className='col-sm-4 control-label'>
+ <FormattedMessage
+ id='register_app.description'
+ defaultMessage='Description'
+ />
+ </label>
<div className='col-sm-7'>
<input
ref='desc'
className='form-control'
type='text'
- placeholder='Optional'
+ placeholder={formatMessage(holders.optional)}
/>
</div>
</div>
<div className='row padding-top padding-bottom x2'>
- <label className='col-sm-4 control-label'>{'Callback URL'}</label>
+ <label className='col-sm-4 control-label'>
+ <FormattedMessage
+ id='register_app.callback'
+ defaultMessage='Callback URL'
+ />
+ </label>
<div className='col-sm-7'>
<textarea
ref='callback'
className='form-control'
type='text'
- placeholder='Required'
+ placeholder={formatMessage(holders.required)}
rows='5'
/>
{callbackError}
@@ -196,7 +262,10 @@ export default class RegisterAppModal extends React.Component {
className='btn btn-default'
onClick={() => this.updateShow(false)}
>
- {'Cancel'}
+ <FormattedMessage
+ id='register_app.cancel'
+ defaultMessage='Cancel'
+ />
</button>
<button
onClick={this.handleSubmit}
@@ -204,7 +273,10 @@ export default class RegisterAppModal extends React.Component {
className='btn btn-primary'
tabIndex='3'
>
- {'Register'}
+ <FormattedMessage
+ id='register_app.register'
+ defaultMessage='Register'
+ />
</button>
</div>
);
@@ -216,10 +288,20 @@ export default class RegisterAppModal extends React.Component {
body = (
<div className='form-horizontal user-settings'>
- <h4 className='padding-bottom x3'>{'Your Application Credentials'}</h4>
+ <h4 className='padding-bottom x3'>
+ <FormattedMessage
+ id='register_app.credentialsTitle'
+ defaultMessage='Your Application Credentials'
+ />
+ </h4>
<br/>
<div className='row'>
- <label className='col-sm-4 control-label'>{'Client ID'}</label>
+ <label className='col-sm-4 control-label'>
+ <FormattedMessage
+ id='register_app.clientId'
+ defaultMessage='Client ID'
+ />
+ </label>
<div className='col-sm-7'>
<input
className='form-control'
@@ -231,7 +313,11 @@ export default class RegisterAppModal extends React.Component {
</div>
<br/>
<div className='row padding-top x2'>
- <label className='col-sm-4 control-label'>{'Client Secret'}</label>
+ <label className='col-sm-4 control-label'>
+ <FormattedMessage
+ id='register_app.clientSecret'
+ defaultMessage='Client Secret'
+ /></label>
<div className='col-sm-7'>
<input
className='form-control'
@@ -243,7 +329,12 @@ export default class RegisterAppModal extends React.Component {
</div>
<br/>
<br/>
- <strong>{'Save these somewhere SAFE and SECURE. Treat your Client ID as your app\'s username and your Client Secret as the app\'s password.'}</strong>
+ <strong>
+ <FormattedMessage
+ id='register_app.credentialsDescription'
+ defaultMessage="Save these somewhere SAFE and SECURE. Treat your Client ID as your app's username and your Client Secret as the app's password."
+ />
+ </strong>
<br/>
<br/>
<div className='checkbox'>
@@ -254,7 +345,10 @@ export default class RegisterAppModal extends React.Component {
checked={this.state.saved}
onChange={this.save}
/>
- {'I have saved both my Client Id and Client Secret somewhere safe'}
+ <FormattedMessage
+ id='register_app.credentialsSave'
+ defaultMessage='I have saved both my Client Id and Client Secret somewhere safe'
+ />
</label>
</div>
</div>
@@ -269,7 +363,10 @@ export default class RegisterAppModal extends React.Component {
this.updateShow(false);
}}
>
- {'Close'}
+ <FormattedMessage
+ id='register_app.close'
+ defaultMessage='Close'
+ />
</a>
);
}
@@ -281,7 +378,12 @@ export default class RegisterAppModal extends React.Component {
onHide={() => this.updateShow(false)}
>
<Modal.Header closeButton={true}>
- <Modal.Title>{'Developer Applications'}</Modal.Title>
+ <Modal.Title>
+ <FormattedMessage
+ id='register_app.dev'
+ defaultMessage='Developer Applications'
+ />
+ </Modal.Title>
</Modal.Header>
<form
role='form'
@@ -300,3 +402,8 @@ export default class RegisterAppModal extends React.Component {
}
}
+RegisterAppModal.propTypes = {
+ intl: intlShape.isRequired
+};
+
+export default injectIntl(RegisterAppModal);
diff --git a/web/react/components/rhs_comment.jsx b/web/react/components/rhs_comment.jsx
index 7aae5177e..1addebbe4 100644
--- a/web/react/components/rhs_comment.jsx
+++ b/web/react/components/rhs_comment.jsx
@@ -16,7 +16,16 @@ import * as TextFormatting from '../utils/text_formatting.jsx';
import twemoji from 'twemoji';
import * as EventHelpers from '../dispatcher/event_helpers.jsx';
-export default class RhsComment extends React.Component {
+import {intlShape, injectIntl, defineMessages, FormattedMessage, FormattedDate} from 'mm-intl';
+
+const holders = defineMessages({
+ comment: {
+ id: 'rhs_comment.comment',
+ defaultMessage: 'Comment'
+ }
+});
+
+class RhsComment extends React.Component {
constructor(props) {
super(props);
@@ -95,12 +104,15 @@ export default class RhsComment extends React.Component {
data-toggle='modal'
data-target='#edit_post'
data-refocusid='#reply_textbox'
- data-title='Comment'
+ data-title={this.props.intl.formatMessage(holders.comment)}
data-message={post.message}
data-postid={post.id}
data-channelid={post.channel_id}
>
- {'Edit'}
+ <FormattedMessage
+ id='rhs_comment.edit'
+ defaultMessage='Edit'
+ />
</a>
</li>
);
@@ -117,7 +129,10 @@ export default class RhsComment extends React.Component {
role='menuitem'
onClick={() => EventHelpers.showDeletePostModal(post, 0)}
>
- {'Delete'}
+ <FormattedMessage
+ id='rhs_comment.del'
+ defaultMessage='Delete'
+ />
</a>
</li>
);
@@ -165,7 +180,10 @@ export default class RhsComment extends React.Component {
href='#'
onClick={this.retryComment}
>
- {'Retry'}
+ <FormattedMessage
+ id='rhs_comment.retry'
+ defaultMessage='Retry'
+ />
</a>
);
} else if (post.state === Constants.POST_LOADING) {
@@ -208,7 +226,15 @@ export default class RhsComment extends React.Component {
</li>
<li className='col'>
<time className='post__time'>
- {Utils.displayCommentDateTime(post.create_at)}
+ <FormattedDate
+ value={post.create_at}
+ day='numeric'
+ month='long'
+ year='numeric'
+ hour12={true}
+ hour='2-digit'
+ minute='2-digit'
+ />
</time>
</li>
<li className='col col__reply'>
@@ -237,5 +263,8 @@ RhsComment.defaultProps = {
post: null
};
RhsComment.propTypes = {
+ intl: intlShape.isRequired,
post: React.PropTypes.object
};
+
+export default injectIntl(RhsComment); \ No newline at end of file
diff --git a/web/react/components/rhs_header_post.jsx b/web/react/components/rhs_header_post.jsx
index 990b33eb5..d56ba76f8 100644
--- a/web/react/components/rhs_header_post.jsx
+++ b/web/react/components/rhs_header_post.jsx
@@ -3,6 +3,9 @@
import AppDispatcher from '../dispatcher/app_dispatcher.jsx';
import Constants from '../utils/constants.jsx';
+
+import {FormattedMessage} from 'mm-intl';
+
const ActionTypes = Constants.ActionTypes;
export default class RhsHeaderPost extends React.Component {
@@ -58,7 +61,13 @@ export default class RhsHeaderPost extends React.Component {
return (
<div className='sidebar--right__header'>
- <span className='sidebar--right__title'>{back}Message Details</span>
+ <span className='sidebar--right__title'>
+ {back}
+ <FormattedMessage
+ id='rhs_header.details'
+ defaultMessage='Message Details'
+ />
+ </span>
<button
type='button'
className='sidebar--right__close'
diff --git a/web/react/components/rhs_root_post.jsx b/web/react/components/rhs_root_post.jsx
index cd7f6766c..f9f7f8f81 100644
--- a/web/react/components/rhs_root_post.jsx
+++ b/web/react/components/rhs_root_post.jsx
@@ -14,6 +14,8 @@ import * as EventHelpers from '../dispatcher/event_helpers.jsx';
import Constants from '../utils/constants.jsx';
+import {FormattedMessage, FormattedDate} from 'mm-intl';
+
export default class RhsRootPost extends React.Component {
constructor(props) {
super(props);
@@ -68,7 +70,12 @@ export default class RhsRootPost extends React.Component {
var channelName;
if (channel) {
if (channel.type === 'D') {
- channelName = 'Direct Message';
+ channelName = (
+ <FormattedMessage
+ id='rhs_root.direct'
+ defaultMessage='Direct Message'
+ />
+ );
} else {
channelName = channel.display_name;
}
@@ -93,7 +100,10 @@ export default class RhsRootPost extends React.Component {
data-postid={post.id}
data-channelid={post.channel_id}
>
- {'Edit'}
+ <FormattedMessage
+ id='rhs_root.edit'
+ defaultMessage='Edit'
+ />
</a>
</li>
);
@@ -110,7 +120,10 @@ export default class RhsRootPost extends React.Component {
role='menuitem'
onClick={() => EventHelpers.showDeletePostModal(post, this.props.commentCount)}
>
- {'Delete'}
+ <FormattedMessage
+ id='rhs_root.del'
+ defaultMessage='Delete'
+ />
</a>
</li>
);
@@ -205,7 +218,15 @@ export default class RhsRootPost extends React.Component {
{botIndicator}
<li className='col'>
<time className='post__time'>
- {utils.displayCommentDateTime(post.create_at)}
+ <FormattedDate
+ value={post.create_at}
+ day='numeric'
+ month='long'
+ year='numeric'
+ hour12={true}
+ hour='2-digit'
+ minute='2-digit'
+ />
</time>
</li>
<li className='col col__reply'>
diff --git a/web/react/components/search_bar.jsx b/web/react/components/search_bar.jsx
index 77c9e39b9..35d7e9514 100644
--- a/web/react/components/search_bar.jsx
+++ b/web/react/components/search_bar.jsx
@@ -11,10 +11,20 @@ import SearchSuggestionList from './suggestion/search_suggestion_list.jsx';
import SearchUserProvider from './suggestion/search_user_provider.jsx';
import * as utils from '../utils/utils.jsx';
import Constants from '../utils/constants.jsx';
+
+import {intlShape, injectIntl, defineMessages, FormattedMessage, FormattedHTMLMessage} from 'mm-intl';
+
var ActionTypes = Constants.ActionTypes;
var Popover = ReactBootstrap.Popover;
-export default class SearchBar extends React.Component {
+const holders = defineMessages({
+ search: {
+ id: 'search_bar.search',
+ defaultMessage: 'Search'
+ }
+});
+
+class SearchBar extends React.Component {
constructor() {
super();
this.mounted = false;
@@ -147,7 +157,10 @@ export default class SearchBar extends React.Component {
className='search__clear'
onClick={this.clearFocus}
>
- {'Cancel'}
+ <FormattedMessage
+ id='search_bar.cancel'
+ defaultMessage='Cancel'
+ />
</span>
<form
role='form'
@@ -160,7 +173,7 @@ export default class SearchBar extends React.Component {
<SuggestionBox
ref='search'
className='form-control search-bar'
- placeholder='Search'
+ placeholder={this.props.intl.formatMessage(holders.search)}
value={this.state.searchTerm}
onFocus={this.handleUserFocus}
onBlur={this.handleUserBlur}
@@ -174,18 +187,20 @@ export default class SearchBar extends React.Component {
placement='bottom'
className={helpClass}
>
- <h4>{'Search Options'}</h4>
- <ul>
- <li>
- <span>{'Use '}</span><b>{'"quotation marks"'}</b><span>{' to search for phrases'}</span>
- </li>
- <li>
- <span>{'Use '}</span><b>{'from:'}</b><span>{' to find posts from specific users and '}</span><b>{'in:'}</b><span>{' to find posts in specific channels'}</span>
- </li>
- </ul>
+ <FormattedHTMLMessage
+ id='search_bar.usage'
+ defaultMessage='<h4>Search Options</h4><ul><li><span>Use </span><b>"quotation marks"</b><span> to search for phrases</span></li><li><span>Use </span><b>from:</b><span> to find posts from specific users and </span><b>in:</b><span> to find posts in specific channels</span></li></ul>'
+ />
</Popover>
</form>
</div>
);
}
}
+
+SearchBar.propTypes = {
+ intl: intlShape.isRequired
+};
+
+export default injectIntl(SearchBar);
+
diff --git a/web/react/components/search_results.jsx b/web/react/components/search_results.jsx
index 141181701..9dcc99061 100644
--- a/web/react/components/search_results.jsx
+++ b/web/react/components/search_results.jsx
@@ -8,6 +8,8 @@ import * as Utils from '../utils/utils.jsx';
import SearchResultsHeader from './search_results_header.jsx';
import SearchResultsItem from './search_results_item.jsx';
+import {FormattedMessage, FormattedHTMLMessage} from 'mm-intl';
+
function getStateFromStores() {
return {results: SearchStore.getSearchResults()};
}
@@ -83,25 +85,29 @@ export default class SearchResults extends React.Component {
if (!searchTerm && noResults) {
ctls = (
<div className='sidebar--right__subheader'>
- <ul>
- <li>
- {'Use '}<b>{'"quotation marks"'}</b>{' to search for phrases'}
- </li>
- <li>
- {'Use '}<b>{'from:'}</b>{' to find posts from specific users and '}<b>{'in:'}</b>{' to find posts in specific channels'}
- </li>
- </ul>
+ <FormattedHTMLMessage
+ id='search_results.usage'
+ defaultMessage='<ul><li>Use <b>"quotation marks"</b> to search for phrases</li><li>Use <b>from:</b> to find posts from specific users and <b>in:</b> to find posts in specific channels</li></ul>'
+ />
</div>
);
} else if (noResults) {
ctls =
(
<div className='sidebar--right__subheader'>
- <h4>{'NO RESULTS'}</h4>
- <ul>
- <li>{'If you\'re searching a partial phrase (ex. searching "rea", looking for "reach" or "reaction"), append a * to your search term'}</li>
- <li>{'Due to the volume of results, two letter searches and common words like "this", "a" and "is" won\'t appear in search results'}</li>
- </ul>
+ <h4>
+ <FormattedMessage
+ id='search_results.noResults'
+ defaultMessage='NO RESULTS'
+ />
+ </h4>
+ <FormattedHTMLMessage
+ id='search_results.because'
+ defaultMessage='<ul>
+ <li>If you&#39;re searching a partial phrase (ex. searching "rea", looking for "reach" or "reaction"), append a * to your search term</li>
+ <li>Due to the volume of results, two letter searches and common words like "this", "a" and "is" won&#39;t appear in search results</li>
+ </ul>'
+ />
</div>
);
} else {
diff --git a/web/react/components/search_results_header.jsx b/web/react/components/search_results_header.jsx
index 581976494..45f56f65a 100644
--- a/web/react/components/search_results_header.jsx
+++ b/web/react/components/search_results_header.jsx
@@ -3,6 +3,9 @@
import AppDispatcher from '../dispatcher/app_dispatcher.jsx';
import Constants from '../utils/constants.jsx';
+
+import {FormattedMessage} from 'mm-intl';
+
var ActionTypes = Constants.ActionTypes;
export default class SearchResultsHeader extends React.Component {
@@ -34,10 +37,20 @@ export default class SearchResultsHeader extends React.Component {
}
render() {
- var title = 'Search Results';
+ var title = (
+ <FormattedMessage
+ id='search_header.results'
+ defaultMessage='Search Results'
+ />
+ );
if (this.props.isMentionSearch) {
- title = 'Recent Mentions';
+ title = (
+ <FormattedMessage
+ id='search_header.title2'
+ defaultMessage='Recent Mentions'
+ />
+ );
}
return (
diff --git a/web/react/components/search_results_item.jsx b/web/react/components/search_results_item.jsx
index f235cac0a..0ad091d5b 100644
--- a/web/react/components/search_results_item.jsx
+++ b/web/react/components/search_results_item.jsx
@@ -10,6 +10,8 @@ import * as TextFormatting from '../utils/text_formatting.jsx';
import Constants from '../utils/constants.jsx';
+import {FormattedMessage, FormattedDate} from 'mm-intl';
+
export default class SearchResultsItem extends React.Component {
constructor(props) {
super(props);
@@ -42,7 +44,12 @@ export default class SearchResultsItem extends React.Component {
if (channel) {
channelName = channel.display_name;
if (channel.type === 'D') {
- channelName = 'Direct Message';
+ channelName = (
+ <FormattedMessage
+ id='search_item.direct'
+ defaultMessage='Direct Message'
+ />
+ );
}
}
@@ -69,7 +76,15 @@ export default class SearchResultsItem extends React.Component {
<li className='col__name'><strong><UserProfile userId={this.props.post.user_id} /></strong></li>
<li className='col'>
<time className='search-item-time'>
- {utils.displayDate(this.props.post.create_at) + ' ' + utils.displayTime(this.props.post.create_at)}
+ <FormattedDate
+ value={this.props.post.create_at}
+ day='numeric'
+ month='long'
+ year='numeric'
+ hour12={true}
+ hour='2-digit'
+ minute='2-digit'
+ />
</time>
</li>
<li>
@@ -78,7 +93,10 @@ export default class SearchResultsItem extends React.Component {
className='search-item__jump'
onClick={this.handleClick}
>
- {'Jump'}
+ <FormattedMessage
+ id='search_item.jump'
+ defaultMessage='Jump'
+ />
</a>
</li>
<li>
@@ -89,7 +107,7 @@ export default class SearchResultsItem extends React.Component {
>
<span
className='comment-icon'
- dangerouslySetInnerHTML={{__html: Constants.COMMENT_ICON}}
+ dangerouslySetInnerHTML={{__html: Constants.REPLY_ICON}}
/>
</a>
</li>
diff --git a/web/react/components/setting_item_max.jsx b/web/react/components/setting_item_max.jsx
index d6c4b0d4b..52f1906c3 100644
--- a/web/react/components/setting_item_max.jsx
+++ b/web/react/components/setting_item_max.jsx
@@ -1,6 +1,8 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
+import {FormattedMessage} from 'mm-intl';
+
export default class SettingItemMax extends React.Component {
render() {
var clientError = null;
@@ -26,7 +28,10 @@ export default class SettingItemMax extends React.Component {
href='#'
onClick={this.props.submit}
>
- Save
+ <FormattedMessage
+ id='setting_item_max.save'
+ defaultMessage='Save'
+ />
</a>
);
}
@@ -60,7 +65,10 @@ export default class SettingItemMax extends React.Component {
href='#'
onClick={this.props.updateSection}
>
- Cancel
+ <FormattedMessage
+ id='setting_item_max.cancel'
+ defaultMessage='Cancel'
+ />
</a>
</li>
</ul>
diff --git a/web/react/components/setting_item_min.jsx b/web/react/components/setting_item_min.jsx
index ffd2061fe..db5513b14 100644
--- a/web/react/components/setting_item_min.jsx
+++ b/web/react/components/setting_item_min.jsx
@@ -1,6 +1,8 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
+import {FormattedMessage} from 'mm-intl';
+
export default class SettingItemMin extends React.Component {
render() {
let editButton = null;
@@ -13,7 +15,10 @@ export default class SettingItemMin extends React.Component {
onClick={this.props.updateSection}
>
<i className='fa fa-pencil'/>
- {'Edit'}
+ <FormattedMessage
+ id='setting_item_min.edit'
+ defaultMessage='Edit'
+ />
</a>
</li>
);
diff --git a/web/react/components/setting_picture.jsx b/web/react/components/setting_picture.jsx
index e69412cca..70e0e6755 100644
--- a/web/react/components/setting_picture.jsx
+++ b/web/react/components/setting_picture.jsx
@@ -1,6 +1,8 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
+import {FormattedMessage} from 'mm-intl';
+
export default class SettingPicture extends React.Component {
constructor(props) {
super(props);
@@ -76,10 +78,24 @@ export default class SettingPicture extends React.Component {
<a
className={confirmButtonClass}
onClick={this.props.submit}
- >Save</a>
+ >
+ <FormattedMessage
+ id='setting_picture.save'
+ defaultMessage='Save'
+ />
+ </a>
);
}
- var helpText = 'Upload a profile picture in either JPG or PNG format, at least ' + global.window.mm_config.ProfileWidth + 'px in width and ' + global.window.mm_config.ProfileHeight + 'px height.';
+ var helpText = (
+ <FormattedMessage
+ id='setting_picture.help'
+ defaultMessage='Upload a profile picture in either JPG or PNG format, at least {width}px in width and {height}px height.'
+ values={{
+ width: global.window.mm_config.ProfileWidth,
+ height: global.window.mm_config.ProfileHeight
+ }}
+ />
+ );
var self = this;
return (
@@ -97,7 +113,10 @@ export default class SettingPicture extends React.Component {
{serverError}
{clientError}
<span className='btn btn-sm btn-primary btn-file sel-btn'>
- Select
+ <FormattedMessage
+ id='setting_picture.select'
+ defaultMessage='Select'
+ />
<input
ref='input'
accept='.jpg,.png,.bmp'
@@ -110,7 +129,12 @@ export default class SettingPicture extends React.Component {
className='btn btn-sm theme'
href='#'
onClick={self.props.updateSection}
- >Cancel</a>
+ >
+ <FormattedMessage
+ id='setting_picture.cancel'
+ defaultMessage='Cancel'
+ />
+ </a>
</li>
</ul>
</li>
diff --git a/web/react/components/setting_upload.jsx b/web/react/components/setting_upload.jsx
index a25789dff..5d5cdfdf7 100644
--- a/web/react/components/setting_upload.jsx
+++ b/web/react/components/setting_upload.jsx
@@ -1,6 +1,8 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
+import {FormattedMessage} from 'mm-intl';
+
export default class SettingsUpload extends React.Component {
constructor(props) {
super(props);
@@ -41,7 +43,7 @@ export default class SettingsUpload extends React.Component {
if (inputnode.files && inputnode.files[0]) {
this.props.submit(inputnode.files[0]);
} else {
- this.setState({clientError: 'No file selected.'});
+ this.setState({clientError: true});
}
}
@@ -49,7 +51,12 @@ export default class SettingsUpload extends React.Component {
let clientError = null;
if (this.state.clientError) {
clientError = (
- <div className='file-status'>{this.state.clientError}</div>
+ <div className='file-status'>
+ <FormattedMessage
+ id='setting_upload.noFile'
+ defaultMessage='No file selected.'
+ />
+ </div>
);
}
let serverError = null;
@@ -75,7 +82,10 @@ export default class SettingsUpload extends React.Component {
<ul className='setting-list'>
<li className='setting-list-item'>
<span className='btn btn-sm btn-primary btn-file sel-btn'>
- {'Select file'}
+ <FormattedMessage
+ id='setting_upload.select'
+ defaultMessage='Select file'
+ />
<input
ref='uploadinput'
accept={this.props.fileTypesAccepted}
@@ -87,7 +97,10 @@ export default class SettingsUpload extends React.Component {
className={submitButtonClass}
onClick={this.doSubmit}
>
- {'Import'}
+ <FormattedMessage
+ id='setting_upload.import'
+ defaultMessage='Import'
+ />
</a>
{fileNameText}
{serverError}
diff --git a/web/react/components/sidebar.jsx b/web/react/components/sidebar.jsx
index c902731c9..14790fbec 100644
--- a/web/react/components/sidebar.jsx
+++ b/web/react/components/sidebar.jsx
@@ -17,6 +17,9 @@ import * as Client from '../utils/client.jsx';
import * as Utils from '../utils/utils.jsx';
import Constants from '../utils/constants.jsx';
+
+import {FormattedMessage, FormattedHTMLMessage} from 'mm-intl';
+
const Preferences = Constants.Preferences;
const TutorialSteps = Constants.TutorialSteps;
@@ -182,7 +185,10 @@ export default class Sidebar extends React.Component {
let currentChannelName = channel.display_name;
if (channel.type === 'D') {
- currentChannelName = Utils.getDirectTeammate(channel.id).username;
+ const teammate = Utils.getDirectTeammate(channel.id);
+ if (teammate != null) {
+ currentChannelName = teammate.username;
+ }
}
const unread = this.getTotalUnreadCount();
@@ -275,34 +281,33 @@ export default class Sidebar extends React.Component {
screens.push(
<div>
- <h4>{'Channels'}</h4>
- <p><strong>{'Channels'}</strong>{' organize conversations across different topics. They’re open to everyone on your team. To send private communications use '}<strong>{'Direct Messages'}</strong>{' for a single person or '}<strong>{'Private Groups'}</strong>{' for multiple people.'}
- </p>
+ <FormattedHTMLMessage
+ id='sidebar.tutorialScreen1'
+ defaultMessage='<h4>Channels</h4><p><strong>Channels</strong> organize conversations across different topics. They’re open to everyone on your team. To send private communications use <strong>Direct Messages</strong> for a single person or <strong>Private Groups</strong> for multiple people.</p>'
+ />
</div>
);
screens.push(
<div>
- <h4>{'"Town Square" and "Off-Topic" channels'}</h4>
- <p>{'Here are two public channels to start:'}</p>
- <p>
- <strong>{'Town Square'}</strong>{' is a place for team-wide communication. Everyone in your team is a member of this channel.'}
- </p>
- <p>
- <strong>{'Off-Topic'}</strong>{' is a place for fun and humor outside of work-related channels. You and your team can decide what other channels to create.'}
- </p>
+ <FormattedHTMLMessage
+ id='sidebar.tutorialScreen2'
+ defaultMessage='<h4>"Town Square" and "Off-Topic" channels</h4>
+ <p>Here are two public channels to start:</p>
+ <p><strong>Town Square</strong> is a place for team-wide communication. Everyone in your team is a member of this channel.</p>
+ <p><strong>Off-Topic</strong> is a place for fun and humor outside of work-related channels. You and your team can decide what other channels to create.</p>'
+ />
</div>
);
screens.push(
<div>
- <h4>{'Creating and Joining Channels'}</h4>
- <p>
- {'Click '}<strong>{'"More..."'}</strong>{' to create a new channel or join an existing one.'}
- </p>
- <p>
- {'You can also create a new channel or private group by clicking the '}<strong>{'"+" symbol'}</strong>{' next to the channel or private group header.'}
- </p>
+ <FormattedHTMLMessage
+ id='sidebar.tutorialScreen3'
+ defaultMessage='<h4>Creating and Joining Channels</h4>
+ <p>Click <strong>"More..."</strong> to create a new channel or join an existing one.</p>
+ <p>You can also create a new channel or private group by clicking the <strong>"+" symbol</strong> next to the channel or private group header.</p>'
+ />
</div>
);
@@ -437,7 +442,12 @@ export default class Sidebar extends React.Component {
let closeButton = null;
const removeTooltip = (
- <Tooltip id='remove-dm-tooltip'>{'Remove from list'}</Tooltip>
+ <Tooltip id='remove-dm-tooltip'>
+ <FormattedMessage
+ id='sidebar.removeList'
+ defaultMessage='Remove from list'
+ />
+ </Tooltip>
);
if (handleClose && !badge) {
closeButton = (
@@ -525,7 +535,13 @@ export default class Sidebar extends React.Component {
href='#'
onClick={this.showMoreDirectChannelsModal}
>
- {'More (' + this.state.hiddenDirectChannelCount + ')'}
+ <FormattedMessage
+ id='sidebar.more'
+ defaultMessage='More ({count})'
+ values={{
+ count: this.state.hiddenDirectChannelCount
+ }}
+ />
</a>
</li>
);
@@ -537,10 +553,34 @@ export default class Sidebar extends React.Component {
}
const createChannelTootlip = (
- <Tooltip id='new-channel-tooltip' >{'Create new channel'}</Tooltip>
+ <Tooltip id='new-channel-tooltip' >
+ <FormattedMessage
+ id='sidebar.createChannel'
+ defaultMessage='Create new channel'
+ />
+ </Tooltip>
);
const createGroupTootlip = (
- <Tooltip id='new-group-tooltip'>{'Create new group'}</Tooltip>
+ <Tooltip id='new-group-tooltip'>
+ <FormattedMessage
+ id='sidebar.createGroup'
+ defaultMessage='Create new group'
+ />
+ </Tooltip>
+ );
+
+ const above = (
+ <FormattedMessage
+ id='sidebar.unreadAbove'
+ defaultMessage='Unread post(s) above'
+ />
+ );
+
+ const below = (
+ <FormattedMessage
+ id='sidebar.unreadBelow'
+ defaultMessage='Unread post(s) below'
+ />
);
return (
@@ -564,12 +604,12 @@ export default class Sidebar extends React.Component {
<UnreadChannelIndicator
show={this.state.showTopUnread}
extraClass='nav-pills__unread-indicator-top'
- text={'Unread post(s) above'}
+ text={above}
/>
<UnreadChannelIndicator
show={this.state.showBottomUnread}
extraClass='nav-pills__unread-indicator-bottom'
- text={'Unread post(s) below'}
+ text={below}
/>
<div
@@ -580,7 +620,10 @@ export default class Sidebar extends React.Component {
<ul className='nav nav-pills nav-stacked'>
<li>
<h4>
- {'Channels'}
+ <FormattedMessage
+ id='sidebar.channels'
+ defaultMessage='Channels'
+ />
<OverlayTrigger
delayShow={500}
placement='top'
@@ -603,7 +646,10 @@ export default class Sidebar extends React.Component {
className='nav-more'
onClick={this.showMoreChannelsModal}
>
- {'More...'}
+ <FormattedMessage
+ id='sidebar.moreElips'
+ defaultMessage='More...'
+ />
</a>
</li>
</ul>
@@ -611,7 +657,10 @@ export default class Sidebar extends React.Component {
<ul className='nav nav-pills nav-stacked'>
<li>
<h4>
- {'Private Groups'}
+ <FormattedMessage
+ id='sidebar.pg'
+ defaultMessage='Private Groups'
+ />
<OverlayTrigger
delayShow={500}
placement='top'
@@ -630,7 +679,14 @@ export default class Sidebar extends React.Component {
{privateChannelItems}
</ul>
<ul className='nav nav-pills nav-stacked'>
- <li><h4>{'Direct Messages'}</h4></li>
+ <li>
+ <h4>
+ <FormattedMessage
+ id='sidebar.direct'
+ defaultMessage='Direct Messages'
+ />
+ </h4>
+ </li>
{directMessageItems}
{directMessageMore}
</ul>
diff --git a/web/react/components/sidebar_header.jsx b/web/react/components/sidebar_header.jsx
index a9616cfc3..45b0a5fc4 100644
--- a/web/react/components/sidebar_header.jsx
+++ b/web/react/components/sidebar_header.jsx
@@ -9,6 +9,9 @@ import PreferenceStore from '../stores/preference_store.jsx';
import * as Utils from '../utils/utils.jsx';
import Constants from '../utils/constants.jsx';
+
+import {FormattedHTMLMessage} from 'mm-intl';
+
const Preferences = Constants.Preferences;
const TutorialSteps = Constants.TutorialSteps;
@@ -51,20 +54,12 @@ export default class SidebarHeader extends React.Component {
screens.push(
<div>
- <h4>{'Main Menu'}</h4>
- <p>
- {'The '}<strong>{'Main Menu'}</strong>{' is where you can '}
- <strong>{'Invite New Members'}</strong>
- {', access your '}
- <strong>{'Account Settings'}</strong>
- {' and set your '}<strong>{'Theme Color'}</strong>{'.'}
- </p>
- <p>
- {'Team administrators can also access their '}<strong>{'Team Settings'}</strong>{' from this menu.'}
- </p>
- <p>
- {'System administrators will find a '}<strong>{'System Console'}</strong>{' option to administrate the entire system.'}
- </p>
+ <FormattedHTMLMessage
+ id='sidebar_header.tutorial'
+ defaultMessage='<h4>Main Menu</h4>
+ <p>The <strong>Main Menu</strong> is where you can <strong>Invite New Members</strong>, access your <strong>Account Settings</strong> and set your <strong>Theme Color</strong>.</p>
+ <p>Team administrators can also access their <strong>Team Settings</strong> from this menu.</p><p>System administrators will find a <strong>System Console</strong> option to administrate the entire system.</p>'
+ />
</div>
);
diff --git a/web/react/components/sidebar_right_menu.jsx b/web/react/components/sidebar_right_menu.jsx
index 20c2bf696..4d714e9f1 100644
--- a/web/react/components/sidebar_right_menu.jsx
+++ b/web/react/components/sidebar_right_menu.jsx
@@ -9,6 +9,8 @@ import * as client from '../utils/client.jsx';
import * as EventHelpers from '../dispatcher/event_helpers.jsx';
import * as utils from '../utils/utils.jsx';
+import {FormattedMessage} from 'mm-intl';
+
export default class SidebarRightMenu extends React.Component {
componentDidMount() {
$('.sidebar--left .dropdown-menu').perfectScrollbar();
@@ -49,7 +51,11 @@ export default class SidebarRightMenu extends React.Component {
href='#'
onClick={EventHelpers.showInviteMemberModal}
>
- <i className='fa fa-user'></i>{'Invite New Member'}
+ <i className='fa fa-user'></i>
+ <FormattedMessage
+ id='sidebar_right_menu.inviteNew'
+ defaultMessage='Invite New Member'
+ />
</a>
</li>
);
@@ -61,7 +67,11 @@ export default class SidebarRightMenu extends React.Component {
href='#'
onClick={EventHelpers.showGetTeamInviteLinkModal}
>
- <i className='glyphicon glyphicon-link'></i>{'Get Team Invite Link'}
+ <i className='glyphicon glyphicon-link'></i>
+ <FormattedMessage
+ id='sidebar_right_menu.teamLink'
+ defaultMessage='Get Team Invite Link'
+ />
</a>
</li>
);
@@ -75,13 +85,23 @@ export default class SidebarRightMenu extends React.Component {
href='#'
data-toggle='modal'
data-target='#team_settings'
- ><i className='fa fa-globe'></i>{'Team Settings'}</a>
+ >
+ <i className='fa fa-globe'></i>
+ <FormattedMessage
+ id='sidebar_right_menu.teamSettings'
+ defaultMessage='Team Settings'
+ />
+ </a>
</li>
);
manageLink = (
<li>
<ToggleModalButton dialogType={TeamMembersModal}>
- <i className='fa fa-users'></i>{'Manage Members'}
+ <i className='fa fa-users'></i>
+ <FormattedMessage
+ id='sidebar_right_menu.manageMembers'
+ defaultMessage='Manage Members'
+ />
</ToggleModalButton>
</li>
);
@@ -93,7 +113,12 @@ export default class SidebarRightMenu extends React.Component {
<a
href={'/admin_console?' + utils.getSessionIndex()}
>
- <i className='fa fa-wrench'></i>{'System Console'}</a>
+ <i className='fa fa-wrench'></i>
+ <FormattedMessage
+ id='sidebar_right_menu.console'
+ defaultMessage='System Console'
+ />
+ </a>
</li>
);
}
@@ -114,7 +139,14 @@ export default class SidebarRightMenu extends React.Component {
<a
target='_blank'
href={global.window.mm_config.HelpLink}
- ><i className='fa fa-question'></i>{'Help'}</a></li>
+ >
+ <i className='fa fa-question'></i>
+ <FormattedMessage
+ id='sidebar_right_menu.help'
+ defaultMessage='Help'
+ />
+ </a>
+ </li>
);
}
@@ -125,7 +157,14 @@ export default class SidebarRightMenu extends React.Component {
<a
target='_blank'
href={global.window.mm_config.ReportAProblemLink}
- ><i className='fa fa-phone'></i>{'Report a Problem'}</a></li>
+ >
+ <i className='fa fa-phone'></i>
+ <FormattedMessage
+ id='sidebar_right_menu.report'
+ defaultMessage='Report a Problem'
+ />
+ </a>
+ </li>
);
}
return (
@@ -144,7 +183,11 @@ export default class SidebarRightMenu extends React.Component {
href='#'
onClick={() => this.setState({showUserSettingsModal: true})}
>
- <i className='fa fa-cog'></i>{'Account Settings'}
+ <i className='fa fa-cog'></i>
+ <FormattedMessage
+ id='sidebar_right_menu.accountSettings'
+ defaultMessage='Account Settings'
+ />
</a>
</li>
{teamSettingsLink}
@@ -156,7 +199,14 @@ export default class SidebarRightMenu extends React.Component {
<a
href='#'
onClick={this.handleLogoutClick}
- ><i className='fa fa-sign-out'></i>{'Logout'}</a></li>
+ >
+ <i className='fa fa-sign-out'></i>
+ <FormattedMessage
+ id='sidebar_right_menu.logout'
+ defaultMessage='Logout'
+ />
+ </a>
+ </li>
<li className='divider'></li>
{helpLink}
{reportLink}
diff --git a/web/react/components/team_export_tab.jsx b/web/react/components/team_export_tab.jsx
index 14df7fffc..8330637d8 100644
--- a/web/react/components/team_export_tab.jsx
+++ b/web/react/components/team_export_tab.jsx
@@ -3,6 +3,8 @@
import * as Client from '../utils/client.jsx';
+import {FormattedMessage} from 'mm-intl';
+
export default class TeamExportTab extends React.Component {
constructor(props) {
super(props);
@@ -35,7 +37,10 @@ export default class TeamExportTab extends React.Component {
messageSection = (
<p className='confirm-import alert alert-warning'>
<i className='fa fa-spinner fa-pulse' />
- {' Exporting...'}
+ <FormattedMessage
+ id='team_export_tab.exporting'
+ defaultMessage=' Exporting...'
+ />
</p>
);
break;
@@ -43,12 +48,18 @@ export default class TeamExportTab extends React.Component {
messageSection = (
<p className='confirm-import alert alert-success'>
<i className='fa fa-check' />
- {' Ready for '}
+ <FormattedMessage
+ id='team_export_tab.ready'
+ defaultMessage=' Ready for '
+ />
<a
href={this.state.link}
download={true}
>
- {'download'}
+ <FormattedMessage
+ id='team_export_tab.download'
+ defaultMessage='download'
+ />
</a>
</p>
);
@@ -57,7 +68,13 @@ export default class TeamExportTab extends React.Component {
messageSection = (
<p className='confirm-import alert alert-warning'>
<i className='fa fa-warning' />
- {' Unable to export: ' + this.state.err}
+ <FormattedMessage
+ id='team_export_tab.unable'
+ defaultMessage=' Unable to export: {error}'
+ values={{
+ error: this.state.err
+ }}
+ />
</p>
);
break;
@@ -68,10 +85,20 @@ export default class TeamExportTab extends React.Component {
ref='wrapper'
className='user-settings'
>
- <h3 className='tab-header'>{'Export'}</h3>
+ <h3 className='tab-header'>
+ <FormattedMessage
+ id='team_export_tab.export'
+ defaultMessage='Export'
+ />
+ </h3>
<div className='divider-dark first'/>
<ul className='section-max'>
- <li className='col-xs-12 section-title'>{'Export your team'}</li>
+ <li className='col-xs-12 section-title'>
+ <FormattedMessage
+ id='team_export_tab.exportTeam'
+ defaultMessage='Export your team'
+ />
+ </li>
<li className='col-xs-offset-3 col-xs-8'>
<ul className='setting-list'>
<li className='setting-list-item'>
@@ -80,7 +107,10 @@ export default class TeamExportTab extends React.Component {
href='#'
onClick={this.doExport}
>
- {'Export'}
+ <FormattedMessage
+ id='team_export_tab.export'
+ defaultMessage='Export'
+ />
</a>
</li>
</ul>
diff --git a/web/react/components/team_general_tab.jsx b/web/react/components/team_general_tab.jsx
index b6fb3389f..0a1b02853 100644
--- a/web/react/components/team_general_tab.jsx
+++ b/web/react/components/team_general_tab.jsx
@@ -8,7 +8,56 @@ import * as Client from '../utils/client.jsx';
import * as Utils from '../utils/utils.jsx';
import TeamStore from '../stores/team_store.jsx';
-export default class GeneralTab extends React.Component {
+import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl';
+
+const holders = defineMessages({
+ dirDisabled: {
+ id: 'general_tab.dirDisabled',
+ defaultMessage: 'Team Directory has been disabled. Please ask a System Admin to enable the Team Directory in the System Console team settings.'
+ },
+ required: {
+ id: 'general_tab.required',
+ defaultMessage: 'This field is required'
+ },
+ chooseName: {
+ id: 'general_tab.chooseName',
+ defaultMessage: 'Please choose a new name for your team'
+ },
+ includeDirTitle: {
+ id: 'general_tab.includeDirTitle',
+ defaultMessage: 'Include this team in the Team Directory'
+ },
+ yes: {
+ id: 'general_tab.yes',
+ defaultMessage: 'Yes'
+ },
+ no: {
+ id: 'general_tab.no',
+ defaultMessage: 'No'
+ },
+ dirOff: {
+ id: 'general_tab.dirOff',
+ defaultMessage: 'Team directory is turned off for this system.'
+ },
+ openInviteTitle: {
+ id: 'general_tab.openInviteTitle',
+ defaultMessage: 'Allow anyone to sign-up from login page'
+ },
+ codeTitle: {
+ id: 'general_tab.codeTitle',
+ defaultMessage: 'Invite Code'
+ },
+ codeDesc: {
+ id: 'general_tab.codeDesc',
+ defaultMessage: "Click 'Edit' to regenerate Invite Code."
+ },
+ teamNameInfo: {
+ id: 'general_tab.teamNameInfo',
+ defaultMessage: 'Set the name of the team as it appears on your sign-in screen and at the top of the left-hand sidebar.'
+ }
+});
+
+class GeneralTab extends React.Component {
constructor(props) {
super(props);
@@ -66,7 +115,7 @@ export default class GeneralTab extends React.Component {
handleTeamListingRadio(listing) {
if (global.window.mm_config.EnableTeamListing !== 'true' && listing) {
- this.setState({clientError: 'Team Directory has been disabled. Please ask a System Admin to enable the Team Directory in the System Console team settings.'});
+ this.setState({clientError: this.props.intl.formatMessage(holders.dirDisabled)});
} else {
this.setState({allow_team_listing: listing});
}
@@ -118,12 +167,13 @@ export default class GeneralTab extends React.Component {
var state = {serverError: '', clientError: ''};
let valid = true;
+ const {formatMessage} = this.props.intl;
const name = this.state.name.trim();
if (!name) {
- state.clientError = 'This field is required';
+ state.clientError = formatMessage(holders.required);
valid = false;
} else if (name === this.props.team.display_name) {
- state.clientError = 'Please choose a new name for your team';
+ state.clientError = formatMessage(holders.chooseName);
valid = false;
} else {
state.clientError = '';
@@ -160,7 +210,7 @@ export default class GeneralTab extends React.Component {
if (inviteId) {
state.clientError = '';
} else {
- state.clientError = 'This field is required';
+ state.clientError = this.props.intl.fromatMessage(holders.required);
valid = false;
}
@@ -260,6 +310,7 @@ export default class GeneralTab extends React.Component {
}
const enableTeamListing = global.window.mm_config.EnableTeamListing === 'true';
+ const {formatMessage} = this.props.intl;
let teamListingSection;
if (this.props.activeSection === 'team_listing') {
@@ -279,7 +330,10 @@ export default class GeneralTab extends React.Component {
defaultChecked={this.state.allow_team_listing}
onChange={this.handleTeamListingRadio.bind(this, true)}
/>
- {'Yes'}
+ <FormattedMessage
+ id='general_tab.yes'
+ defaultMessage='Yes'
+ />
</label>
<br/>
</div>
@@ -292,24 +346,39 @@ export default class GeneralTab extends React.Component {
defaultChecked={!this.state.allow_team_listing}
onChange={this.handleTeamListingRadio.bind(this, false)}
/>
- {'No'}
+ <FormattedMessage
+ id='general_tab.no'
+ defaultMessage='No'
+ />
</label>
<br/>
</div>
- <div><br/>{'Including this team will display the team name from the Team Directory section of the Home Page, and provide a link to the sign-in page.'}</div>
+ <div>
+ <br/>
+ <FormattedMessage
+ id='general_tab.includeDirDesc'
+ defaultMessage='Including this team will display the team name from the Team Directory section of the Home Page, and provide a link to the sign-in page.'
+ />
+ </div>
</div>
);
} else {
inputs.push(
<div key='userTeamListingOptions'>
- <div><br/>{'Contact your system administrator to turn on the team directory on the system home page.'}</div>
+ <div>
+ <br/>
+ <FormattedMessage
+ id='general_tab.dirContact'
+ defaultMessage='Contact your system administrator to turn on the team directory on the system home page.'
+ />
+ </div>
</div>
);
}
teamListingSection = (
<SettingItemMax
- title='Include this team in the Team Directory'
+ title={formatMessage(holders.includeDirTitle)}
inputs={inputs}
submit={submitHandle}
server_error={serverError}
@@ -322,17 +391,17 @@ export default class GeneralTab extends React.Component {
if (enableTeamListing) {
if (this.state.allow_team_listing === true) {
- describe = 'Yes';
+ describe = formatMessage(holders.yes);
} else {
- describe = 'No';
+ describe = formatMessage(holders.no);
}
} else {
- describe = 'Team directory is turned off for this system.';
+ describe = formatMessage(holders.dirOff);
}
teamListingSection = (
<SettingItemMin
- title='Include this team in the Team Directory'
+ title={formatMessage(holders.includeDirTitle)}
describe={describe}
updateSection={this.onUpdateTeamListingSection}
/>
@@ -351,7 +420,10 @@ export default class GeneralTab extends React.Component {
defaultChecked={this.state.allow_open_invite}
onChange={this.handleOpenInviteRadio.bind(this, true)}
/>
- {'Yes'}
+ <FormattedMessage
+ id='general_tab.yes'
+ defaultMessage='Yes'
+ />
</label>
<br/>
</div>
@@ -363,17 +435,26 @@ export default class GeneralTab extends React.Component {
defaultChecked={!this.state.allow_open_invite}
onChange={this.handleOpenInviteRadio.bind(this, false)}
/>
- {'No'}
+ <FormattedMessage
+ id='general_tab.no'
+ defaultMessage='No'
+ />
</label>
<br/>
</div>
- <div><br/>{'When allowed, a link to account creation will be included on the sign-in page of this team and allow any visitor to sign-up.'}</div>
+ <div>
+ <br/>
+ <FormattedMessage
+ id='general_tab.openInviteDesc'
+ defaultMessage='When allowed, a link to account creation will be included on the sign-in page of this team and allow any visitor to sign-up.'
+ />
+ </div>
</div>
];
openInviteSection = (
<SettingItemMax
- title='Allow anyone to sign-up from login page'
+ title={formatMessage(holders.openInviteTitle)}
inputs={inputs}
submit={this.handleOpenInviteSubmit}
server_error={serverError}
@@ -383,14 +464,14 @@ export default class GeneralTab extends React.Component {
} else {
let describe = '';
if (this.state.allow_open_invite === true) {
- describe = 'Yes';
+ describe = formatMessage(holders.yes);
} else {
- describe = 'No';
+ describe = formatMessage(holders.no);
}
openInviteSection = (
<SettingItemMin
- title='Allow anyone to sign-up from login page'
+ title={formatMessage(holders.openInviteTitle)}
describe={describe}
updateSection={this.onUpdateOpenInviteSection}
/>
@@ -405,7 +486,12 @@ export default class GeneralTab extends React.Component {
inputs.push(
<div key='teamInviteSetting'>
<div className='row'>
- <label className='col-sm-5 control-label'>{'Invite Code'}</label>
+ <label className='col-sm-5 control-label'>
+ <FormattedMessage
+ id='general_tab.codeTitle'
+ defaultMessage='Invite Code'
+ />
+ </label>
<div className='col-sm-7'>
<input
className='form-control'
@@ -419,18 +505,26 @@ export default class GeneralTab extends React.Component {
href='#'
onClick={this.handleGenerateInviteId}
>
- {'Re-Generate'}
+ <FormattedMessage
+ id='general_tab.regenerate'
+ defaultMessage='Re-Generate'
+ />
</a>
</div>
</div>
</div>
- <div className='setting-list__hint'>{'The Invite Code is used as part of the URL in the team invitation link created by **Get Team Invite Link** in the main menu. Regenerating creates a new team invitation link and invalidates the previous link.'}</div>
+ <div className='setting-list__hint'>
+ <FormattedMessage
+ id='general_tab.codeLongDesc'
+ defaultMessage='The Invite Code is used as part of the URL in the team invitation link created by **Get Team Invite Link** in the main menu. Regenerating creates a new team invitation link and invalidates the previous link.'
+ />
+ </div>
</div>
);
inviteSection = (
<SettingItemMax
- title={`Invite Code`}
+ title={formatMessage(holders.codeTitle)}
inputs={inputs}
submit={this.handleInviteIdSubmit}
server_error={serverError}
@@ -441,8 +535,8 @@ export default class GeneralTab extends React.Component {
} else {
inviteSection = (
<SettingItemMin
- title={`Invite Code`}
- describe={`Click 'Edit' to regenerate Invite Code.`}
+ title={formatMessage(holders.codeTitle)}
+ describe={formatMessage(holders.codeDesc)}
updateSection={this.onUpdateInviteIdSection}
/>
);
@@ -453,7 +547,12 @@ export default class GeneralTab extends React.Component {
if (this.props.activeSection === 'name') {
const inputs = [];
- let teamNameLabel = 'Team Name';
+ let teamNameLabel = (
+ <FormattedMessage
+ id='general_tab.teamName'
+ defaultMessage='Team Name'
+ />
+ );
if (Utils.isMobile()) {
teamNameLabel = '';
}
@@ -476,15 +575,17 @@ export default class GeneralTab extends React.Component {
</div>
);
+ const nameExtraInfo = <span>{formatMessage(holders.teamNameInfo)}</span>;
+
nameSection = (
<SettingItemMax
- title={`Team Name`}
+ title={formatMessage({id: 'general_tab.teamName'})}
inputs={inputs}
submit={this.handleNameSubmit}
server_error={serverError}
client_error={clientError}
updateSection={this.onUpdateNameSection}
- extraInfo='Set the name of the team as it appears on your sign-in screen and at the top of the left-hand sidebar.'
+ extraInfo={nameExtraInfo}
/>
);
} else {
@@ -492,7 +593,7 @@ export default class GeneralTab extends React.Component {
nameSection = (
<SettingItemMin
- title={`Team Name`}
+ title={formatMessage({id: 'general_tab.teamName'})}
describe={describe}
updateSection={this.onUpdateNameSection}
/>
@@ -515,14 +616,22 @@ export default class GeneralTab extends React.Component {
ref='title'
>
<i className='modal-back'></i>
- {'General Settings'}
+ <FormattedMessage
+ id='general_tab.title'
+ defaultMessage='General Settings'
+ />
</h4>
</div>
<div
ref='wrapper'
className='user-settings'
>
- <h3 className='tab-header'>{'General Settings'}</h3>
+ <h3 className='tab-header'>
+ <FormattedMessage
+ id='general_tab.title'
+ defaultMessage='General Settings'
+ />
+ </h3>
<div className='divider-dark first'/>
{nameSection}
<div className='divider-light'/>
@@ -539,7 +648,10 @@ export default class GeneralTab extends React.Component {
}
GeneralTab.propTypes = {
+ intl: intlShape.isRequired,
updateSection: React.PropTypes.func.isRequired,
team: React.PropTypes.object.isRequired,
activeSection: React.PropTypes.string.isRequired
};
+
+export default injectIntl(GeneralTab); \ No newline at end of file
diff --git a/web/react/components/team_import_tab.jsx b/web/react/components/team_import_tab.jsx
index 37f8746d7..adf990672 100644
--- a/web/react/components/team_import_tab.jsx
+++ b/web/react/components/team_import_tab.jsx
@@ -4,7 +4,16 @@
import * as utils from '../utils/utils.jsx';
import SettingUpload from './setting_upload.jsx';
-export default class TeamImportTab extends React.Component {
+import {intlShape, injectIntl, defineMessages, FormattedMessage, FormattedHTMLMessage} from 'mm-intl';
+
+const holders = defineMessages({
+ importSlack: {
+ id: 'team_import_tab.importSlack',
+ defaultMessage: 'Import from Slack (Beta)'
+ }
+});
+
+class TeamImportTab extends React.Component {
constructor(props) {
super(props);
@@ -32,16 +41,19 @@ export default class TeamImportTab extends React.Component {
}
render() {
+ const {formatMessage} = this.props.intl;
var uploadHelpText = (
<div>
- <p>{'To import a team from Slack go to Slack > Team Settings > Import/Export Data > Export > Start Export. Slack does not allow you to export files, images, private groups or direct messages stored in Slack. Therefore, Slack import to Mattermost only supports importing of text messages in your Slack team\'\s public channels.'}</p>
- <p>{'The Slack import to Mattermost is in "Beta". Slack bot posts do not yet import and Slack @mentions are not currently supported.'}</p>
+ <FormattedHTMLMessage
+ id='team_import_tab.importHelp'
+ defaultMessage="<p>To import a team from Slack go to Slack > Team Settings > Import/Export Data > Export > Start Export. Slack does not allow you to export files, images, private groups or direct messages stored in Slack. Therefore, Slack import to Mattermost only supports importing of text messages in your Slack team's public channels.</p><p>The Slack import to Mattermost is in 'Beta'. Slack bot posts do not yet import and Slack @mentions are not currently supported.</p>"
+ />
</div>
);
var uploadSection = (
<SettingUpload
- title='Import from Slack (Beta)'
+ title={formatMessage(holders.importSlack)}
submit={this.doImportSlack}
helpText={uploadHelpText}
fileTypesAccepted='.zip'
@@ -56,19 +68,30 @@ export default class TeamImportTab extends React.Component {
break;
case 'in-progress':
messageSection = (
- <p className='confirm-import alert alert-warning'><i className='fa fa-spinner fa-pulse'></i>{' Importing...'}</p>
+ <p className='confirm-import alert alert-warning'><i className='fa fa-spinner fa-pulse'></i>
+ <FormattedMessage
+ id='team_import_tab.importing'
+ defaultMessage=' Importing...'
+ />
+ </p>
);
break;
case 'done':
messageSection = (
<p className='confirm-import alert alert-success'>
<i className='fa fa-check' />
- {' Import successful: '}
+ <FormattedMessage
+ id='team_import_tab.successful'
+ defaultMessage=' Import successful: '
+ />
<a
href={this.state.link}
download='MattermostImportSummary.txt'
>
- {'View Summary'}
+ <FormattedMessage
+ id='team_import_tab.summary'
+ defaultMessage='View Summary'
+ />
</a>
</p>
);
@@ -77,12 +100,18 @@ export default class TeamImportTab extends React.Component {
messageSection = (
<p className='confirm-import alert alert-warning'>
<i className='fa fa-warning' />
- {' Import failure: '}
+ <FormattedMessage
+ id='team_import_tab.failure'
+ defaultMessage=' Import failure: '
+ />
<a
href={this.state.link}
download='MattermostImportSummary.txt'
>
- {'View Summary'}
+ <FormattedMessage
+ id='team_import_tab.summary'
+ defaultMessage='View Summary'
+ />
</a>
</p>
);
@@ -102,13 +131,23 @@ export default class TeamImportTab extends React.Component {
<h4
className='modal-title'
ref='title'
- ><i className='modal-back'></i>{'Import'}</h4>
+ ><i className='modal-back'></i>
+ <FormattedMessage
+ id='team_import_tab.import'
+ defaultMessage='Import'
+ />
+ </h4>
</div>
<div
ref='wrapper'
className='user-settings'
>
- <h3 className='tab-header'>{'Import'}</h3>
+ <h3 className='tab-header'>
+ <FormattedMessage
+ id='team_import_tab.import'
+ defaultMessage='Import'
+ />
+ </h3>
<div className='divider-dark first'/>
{uploadSection}
<div className='divider-dark'/>
@@ -118,3 +157,9 @@ export default class TeamImportTab extends React.Component {
);
}
}
+
+TeamImportTab.propTypes = {
+ intl: intlShape.isRequired
+};
+
+export default injectIntl(TeamImportTab); \ No newline at end of file
diff --git a/web/react/components/team_members_modal.jsx b/web/react/components/team_members_modal.jsx
index 27224c283..92adb6e2a 100644
--- a/web/react/components/team_members_modal.jsx
+++ b/web/react/components/team_members_modal.jsx
@@ -4,6 +4,8 @@
import MemberListTeam from './member_list_team.jsx';
import TeamStore from '../stores/team_store.jsx';
+import {FormattedMessage} from 'mm-intl';
+
const Modal = ReactBootstrap.Modal;
export default class TeamMembersModal extends React.Component {
@@ -44,7 +46,13 @@ export default class TeamMembersModal extends React.Component {
onHide={this.props.onHide}
>
<Modal.Header closeButton={true}>
- {team.display_name + ' Members'}
+ <FormattedMessage
+ id='team_member_modal.members'
+ defaultMessage='{team} Members'
+ values={{
+ team: team.display_name
+ }}
+ />
</Modal.Header>
<Modal.Body ref='modalBody'>
<div className='team-member-list'>
@@ -57,7 +65,10 @@ export default class TeamMembersModal extends React.Component {
className='btn btn-default'
onClick={this.props.onHide}
>
- {'Close'}
+ <FormattedMessage
+ id='team_member_modal.close'
+ defaultMessage='Close'
+ />
</button>
</Modal.Footer>
</Modal>
diff --git a/web/react/components/team_settings_modal.jsx b/web/react/components/team_settings_modal.jsx
index dbdbde958..d517f92fb 100644
--- a/web/react/components/team_settings_modal.jsx
+++ b/web/react/components/team_settings_modal.jsx
@@ -4,7 +4,24 @@
import SettingsSidebar from './settings_sidebar.jsx';
import TeamSettings from './team_settings.jsx';
-export default class TeamSettingsModal extends React.Component {
+import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl';
+
+const holders = defineMessages({
+ generalTab: {
+ id: 'team_settings_modal.generalTab',
+ defaultMessage: 'General'
+ },
+ importTab: {
+ id: 'team_settings_modal.importTab',
+ defaultMessage: 'Import'
+ },
+ exportTab: {
+ id: 'team_settings_modal.exportTab',
+ defaultMessage: 'Export'
+ }
+});
+
+class TeamSettingsModal extends React.Component {
constructor(props) {
super(props);
@@ -36,12 +53,13 @@ export default class TeamSettingsModal extends React.Component {
this.setState({activeSection: section});
}
render() {
+ const {formatMessage} = this.props.intl;
const tabs = [];
- tabs.push({name: 'general', uiName: 'General', icon: 'glyphicon glyphicon-cog'});
- tabs.push({name: 'import', uiName: 'Import', icon: 'glyphicon glyphicon-upload'});
+ tabs.push({name: 'general', uiName: formatMessage(holders.generalTab), icon: 'glyphicon glyphicon-cog'});
+ tabs.push({name: 'import', uiName: formatMessage(holders.importTab), icon: 'glyphicon glyphicon-upload'});
// To enable export uncomment this line
- //tabs.push({name: 'export', uiName: 'Export', icon: 'glyphicon glyphicon-download'});
+ //tabs.push({name: 'export', uiName: formatMessage(holders.exportTab), icon: 'glyphicon glyphicon-download'});
return (
<div
@@ -67,7 +85,10 @@ export default class TeamSettingsModal extends React.Component {
className='modal-title'
ref='title'
>
- {'Team Settings'}
+ <FormattedMessage
+ id='team_settings_modal.title'
+ defaultMessage='Team Settings'
+ />
</h4>
</div>
<div className='modal-body'>
@@ -96,4 +117,7 @@ export default class TeamSettingsModal extends React.Component {
}
TeamSettingsModal.propTypes = {
+ intl: intlShape.isRequired
};
+
+export default injectIntl(TeamSettingsModal); \ No newline at end of file
diff --git a/web/react/components/textbox.jsx b/web/react/components/textbox.jsx
index 62c0d5218..bb383aca1 100644
--- a/web/react/components/textbox.jsx
+++ b/web/react/components/textbox.jsx
@@ -11,6 +11,9 @@ import ErrorStore from '../stores/error_store.jsx';
import * as TextFormatting from '../utils/text_formatting.jsx';
import * as Utils from '../utils/utils.jsx';
import Constants from '../utils/constants.jsx';
+
+import {FormattedMessage} from 'mm-intl';
+
const PreReleaseFeatures = Constants.PRE_RELEASE_FEATURES;
export default class Textbox extends React.Component {
@@ -143,7 +146,17 @@ export default class Textbox extends React.Component {
onClick={this.showPreview}
className='textbox-preview-link'
>
- {this.state.preview ? 'Edit message' : 'Preview'}
+ {this.state.preview ? (
+ <FormattedMessage
+ id='textbox.edit'
+ defaultMessage='Edit message'
+ />
+ ) : (
+ <FormattedMessage
+ id='textbox.preview'
+ defaultMessage='Preview'
+ />
+ )}
</a>
);
}
@@ -184,7 +197,10 @@ export default class Textbox extends React.Component {
onClick={this.showHelp}
className='textbox-help-link'
>
- {'Help'}
+ <FormattedMessage
+ id='textbox.help'
+ defaultMessage='Help'
+ />
</a>
</div>
);
diff --git a/web/react/components/unread_channel_indicator.jsx b/web/react/components/unread_channel_indicator.jsx
index c0c34584f..509ac9e4d 100644
--- a/web/react/components/unread_channel_indicator.jsx
+++ b/web/react/components/unread_channel_indicator.jsx
@@ -31,5 +31,5 @@ UnreadChannelIndicator.defaultProps = {
UnreadChannelIndicator.propTypes = {
show: React.PropTypes.bool,
extraClass: React.PropTypes.string,
- text: React.PropTypes.string
+ text: React.PropTypes.object
};
diff --git a/web/react/components/user_profile.jsx b/web/react/components/user_profile.jsx
index 385cd0f52..1e393cfe9 100644
--- a/web/react/components/user_profile.jsx
+++ b/web/react/components/user_profile.jsx
@@ -3,6 +3,9 @@
import * as Utils from '../utils/utils.jsx';
import UserStore from '../stores/user_store.jsx';
+
+import {FormattedMessage} from 'mm-intl';
+
var Popover = ReactBootstrap.Popover;
var OverlayTrigger = ReactBootstrap.OverlayTrigger;
@@ -87,7 +90,10 @@ export default class UserProfile extends React.Component {
className='text-nowrap'
key='user-popover-no-email'
>
- {'Email not shared'}
+ <FormattedMessage
+ id='user_profile.notShared'
+ defaultMessage='Email not shared'
+ />
</div>
);
} else {
diff --git a/web/react/components/user_settings/custom_theme_chooser.jsx b/web/react/components/user_settings/custom_theme_chooser.jsx
index 8ec3863f3..9116dd938 100644
--- a/web/react/components/user_settings/custom_theme_chooser.jsx
+++ b/web/react/components/user_settings/custom_theme_chooser.jsx
@@ -6,7 +6,96 @@ import Constants from '../../utils/constants.jsx';
const OverlayTrigger = ReactBootstrap.OverlayTrigger;
const Popover = ReactBootstrap.Popover;
-export default class CustomThemeChooser extends React.Component {
+import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl';
+
+const messages = defineMessages({
+ sidebarBg: {
+ id: 'user.settings.custom_theme.sidebarBg',
+ defaultMessage: 'Sidebar BG'
+ },
+ sidebarText: {
+ id: 'user.settings.custom_theme.sidebarText',
+ defaultMessage: 'Sidebar Text'
+ },
+ sidebarHeaderBg: {
+ id: 'user.settings.custom_theme.sidebarHeaderBg',
+ defaultMessage: 'Sidebar Header BG'
+ },
+ sidebarHeaderTextColor: {
+ id: 'user.settings.custom_theme.sidebarHeaderTextColor',
+ defaultMessage: 'Sidebar Header Text'
+ },
+ sidebarUnreadText: {
+ id: 'user.settings.custom_theme.sidebarUnreadText',
+ defaultMessage: 'Sidebar Unread Text'
+ },
+ sidebarTextHoverBg: {
+ id: 'user.settings.custom_theme.sidebarTextHoverBg',
+ defaultMessage: 'Sidebar Text Hover BG'
+ },
+ sidebarTextActiveBorder: {
+ id: 'user.settings.custom_theme.sidebarTextActiveBorder',
+ defaultMessage: 'Sidebar Text Active Border'
+ },
+ sidebarTextActiveColor: {
+ id: 'user.settings.custom_theme.sidebarTextActiveColor',
+ defaultMessage: 'Sidebar Text Active Color'
+ },
+ onlineIndicator: {
+ id: 'user.settings.custom_theme.onlineIndicator',
+ defaultMessage: 'Online Indicator'
+ },
+ awayIndicator: {
+ id: 'user.settings.custom_theme.awayIndicator',
+ defaultMessage: 'Away Indicator'
+ },
+ mentionBj: {
+ id: 'user.settings.custom_theme.mentionBj',
+ defaultMessage: 'Mention Jewel BG'
+ },
+ mentionColor: {
+ id: 'user.settings.custom_theme.mentionColor',
+ defaultMessage: 'Mention Jewel Text'
+ },
+ centerChannelBg: {
+ id: 'user.settings.custom_theme.centerChannelBg',
+ defaultMessage: 'Center Channel BG'
+ },
+ centerChannelColor: {
+ id: 'user.settings.custom_theme.centerChannelColor',
+ defaultMessage: 'Center Channel Text'
+ },
+ newMessageSeparator: {
+ id: 'user.settings.custom_theme.newMessageSeparator',
+ defaultMessage: 'New Message Separator'
+ },
+ linkColor: {
+ id: 'user.settings.custom_theme.linkColor',
+ defaultMessage: 'Link Color'
+ },
+ buttonBg: {
+ id: 'user.settings.custom_theme.buttonBg',
+ defaultMessage: 'Button BG'
+ },
+ buttonColor: {
+ id: 'user.settings.custom_theme.buttonColor',
+ defaultMessage: 'Button Text'
+ },
+ mentionHighlightBg: {
+ id: 'user.settings.custom_theme.mentionHighlightBg',
+ defaultMessage: 'Mention Highlight BG'
+ },
+ mentionHighlightLink: {
+ id: 'user.settings.custom_theme.mentionHighlightLink',
+ defaultMessage: 'Mention Highlight Link'
+ },
+ codeTheme: {
+ id: 'user.settings.custom_theme.codeTheme',
+ defaultMessage: 'Code Theme'
+ }
+});
+
+class CustomThemeChooser extends React.Component {
constructor(props) {
super(props);
@@ -65,6 +154,7 @@ export default class CustomThemeChooser extends React.Component {
this.props.updateTheme(theme);
}
render() {
+ const {formatMessage} = this.props.intl;
const theme = this.props.theme;
const elements = [];
@@ -102,7 +192,7 @@ export default class CustomThemeChooser extends React.Component {
className='col-sm-4 form-group'
key={'custom-theme-key' + index}
>
- <label className='custom-label'>{element.uiName}</label>
+ <label className='custom-label'>{formatMessage(messages[element.id])}</label>
<div
className='input-group theme-group group--code dropdown'
id={element.id}
@@ -135,7 +225,7 @@ export default class CustomThemeChooser extends React.Component {
className='col-sm-4 form-group'
key={'custom-theme-key' + index}
>
- <label className='custom-label'>{element.uiName}</label>
+ <label className='custom-label'>{formatMessage(messages[element.id])}</label>
<div
className='input-group color-picker'
id={element.id}
@@ -160,7 +250,10 @@ export default class CustomThemeChooser extends React.Component {
const pasteBox = (
<div className='col-sm-12'>
<label className='custom-label'>
- {'Copy and paste to share theme colors:'}
+ <FormattedMessage
+ id='user.settings.custom_theme.copyPaste'
+ defaultMessage='Copy and paste to share theme colors:'
+ />
</label>
<input
type='text'
@@ -185,6 +278,9 @@ export default class CustomThemeChooser extends React.Component {
}
CustomThemeChooser.propTypes = {
+ intl: intlShape.isRequired,
theme: React.PropTypes.object.isRequired,
updateTheme: React.PropTypes.func.isRequired
};
+
+export default injectIntl(CustomThemeChooser); \ No newline at end of file
diff --git a/web/react/components/user_settings/import_theme_modal.jsx b/web/react/components/user_settings/import_theme_modal.jsx
index 45b05f19b..66bed0b0b 100644
--- a/web/react/components/user_settings/import_theme_modal.jsx
+++ b/web/react/components/user_settings/import_theme_modal.jsx
@@ -9,9 +9,19 @@ const Modal = ReactBootstrap.Modal;
import AppDispatcher from '../../dispatcher/app_dispatcher.jsx';
import Constants from '../../utils/constants.jsx';
+
+import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl';
+
+const holders = defineMessages({
+ submitError: {
+ id: 'user.settings.import_theme.submitError',
+ defaultMessage: 'Invalid format, please try copying and pasting in again.'
+ }
+});
+
const ActionTypes = Constants.ActionTypes;
-export default class ImportThemeModal extends React.Component {
+class ImportThemeModal extends React.Component {
constructor(props) {
super(props);
@@ -39,7 +49,7 @@ export default class ImportThemeModal extends React.Component {
const text = ReactDOM.findDOMNode(this.refs.input).value;
if (!this.isInputValid(text)) {
- this.setState({inputError: 'Invalid format, please try copying and pasting in again.'});
+ this.setState({inputError: this.props.intl.formatMessage(holders.submitError)});
return;
}
@@ -125,7 +135,7 @@ export default class ImportThemeModal extends React.Component {
if (this.isInputValid(e.target.value)) {
this.setState({inputError: null});
} else {
- this.setState({inputError: 'Invalid format, please try copying and pasting in again.'});
+ this.setState({inputError: this.props.intl.formatMessage(holders.submitError)});
}
}
render() {
@@ -136,7 +146,12 @@ export default class ImportThemeModal extends React.Component {
onHide={() => this.setState({show: false})}
>
<Modal.Header closeButton={true}>
- <Modal.Title>{'Import Slack Theme'}</Modal.Title>
+ <Modal.Title>
+ <FormattedMessage
+ id='user.settings.import_theme.importHeader'
+ defaultMessage='Import Slack Theme'
+ />
+ </Modal.Title>
</Modal.Header>
<form
role='form'
@@ -144,7 +159,10 @@ export default class ImportThemeModal extends React.Component {
>
<Modal.Body>
<p>
- {'To import a theme, go to a Slack team and look for “Preferences -> Sidebar Theme”. Open the custom theme option, copy the theme color values and paste them here:'}
+ <FormattedMessage
+ id='user.settings.import_theme.importBody'
+ defaultMessage='To import a theme, go to a Slack team and look for “Preferences -> Sidebar Theme”. Open the custom theme option, copy the theme color values and paste them here:'
+ />
</p>
<div className='form-group less'>
<div className='col-sm-9'>
@@ -166,7 +184,10 @@ export default class ImportThemeModal extends React.Component {
className='btn btn-default'
onClick={() => this.setState({show: false})}
>
- {'Cancel'}
+ <FormattedMessage
+ id='user.settings.import_theme.cancel'
+ defaultMessage='Cancel'
+ />
</button>
<button
onClick={this.handleSubmit}
@@ -174,7 +195,10 @@ export default class ImportThemeModal extends React.Component {
className='btn btn-primary'
tabIndex='3'
>
- {'Submit'}
+ <FormattedMessage
+ id='user.settings.import_theme.submit'
+ defaultMessage='Submit'
+ />
</button>
</Modal.Footer>
</form>
@@ -183,3 +207,9 @@ export default class ImportThemeModal extends React.Component {
);
}
}
+
+ImportThemeModal.propTypes = {
+ intl: intlShape.isRequired
+};
+
+export default injectIntl(ImportThemeModal); \ No newline at end of file
diff --git a/web/react/components/user_settings/manage_incoming_hooks.jsx b/web/react/components/user_settings/manage_incoming_hooks.jsx
index 1506e3c98..c6532b018 100644
--- a/web/react/components/user_settings/manage_incoming_hooks.jsx
+++ b/web/react/components/user_settings/manage_incoming_hooks.jsx
@@ -7,6 +7,8 @@ import Constants from '../../utils/constants.jsx';
import ChannelStore from '../../stores/channel_store.jsx';
import LoadingScreen from '../loading_screen.jsx';
+import {FormattedMessage, FormattedHTMLMessage} from 'mm-intl';
+
export default class ManageIncomingHooks extends React.Component {
constructor() {
super();
@@ -126,7 +128,12 @@ export default class ManageIncomingHooks extends React.Component {
<span className='word-break--all'>{Utils.getWindowLocationOrigin() + '/hooks/' + hook.id}</span>
</div>
<div className='padding-top'>
- <strong>{'Channel: '}</strong>{c.display_name}
+ <strong>
+ <FormattedMessage
+ id='user.settings.hooks_in.channel'
+ defaultMessage='Channel: '
+ />
+ </strong>{c.display_name}
</div>
<a
className={'webhook__remove'}
@@ -147,12 +154,24 @@ export default class ManageIncomingHooks extends React.Component {
} else if (hooks.length > 0) {
displayHooks = hooks;
} else {
- displayHooks = <div className='padding-top x2'>{'None'}</div>;
+ displayHooks = (
+ <div className='padding-top x2'>
+ <FormattedMessage
+ id='user.settings.hooks_in.none'
+ defaultMessage='None'
+ />
+ </div>
+ );
}
const existingHooks = (
<div className='webhooks__container'>
- <label className='control-label padding-top x2'>{'Existing incoming webhooks'}</label>
+ <label className='control-label padding-top x2'>
+ <FormattedMessage
+ id='user.settings.hooks_in.existing'
+ defaultMessage='Existing incoming webhooks'
+ />
+ </label>
<div className='padding-top divider-light'></div>
<div className='webhooks__list'>
{displayHooks}
@@ -162,15 +181,16 @@ export default class ManageIncomingHooks extends React.Component {
return (
<div key='addIncomingHook'>
- {'Create webhook URLs for use in external integrations. Please see '}
- <a
- href='http://mattermost.org/webhooks'
- target='_blank'
- >
- {'http://mattermost.org/webhooks'}
- </a>
- {' to learn more.'}
- <div><label className='control-label padding-top x2'>{'Add a new incoming webhook'}</label></div>
+ <FormattedHTMLMessage
+ id='user.settings.hooks_in.description'
+ defaultMessage='Create webhook URLs for use in external integrations. Please see<a href="http://mattermost.org/webhooks" target="_blank">http://mattermost.org/webhooks</a> to learn more.'
+ />
+ <div><label className='control-label padding-top x2'>
+ <FormattedMessage
+ id='user.settings.hooks_in.addTitle'
+ defaultMessage='Add a new incoming webhook'
+ />
+ </label></div>
<div className='row padding-top'>
<div className='col-sm-10 padding-bottom'>
<select
@@ -189,7 +209,10 @@ export default class ManageIncomingHooks extends React.Component {
href='#'
onClick={this.addNewHook}
>
- {'Add'}
+ <FormattedMessage
+ id='user.settings.hooks_in.add'
+ defaultMessage='Add'
+ />
</a>
</div>
</div>
diff --git a/web/react/components/user_settings/manage_languages.jsx b/web/react/components/user_settings/manage_languages.jsx
index 123165b76..fee6d9da2 100644
--- a/web/react/components/user_settings/manage_languages.jsx
+++ b/web/react/components/user_settings/manage_languages.jsx
@@ -4,6 +4,8 @@
import * as Client from '../../utils/client.jsx';
import * as Utils from '../../utils/utils.jsx';
+import {FormattedMessage} from 'mm-intl';
+
export default class ManageLanguage extends React.Component {
constructor(props) {
super(props);
@@ -70,7 +72,12 @@ export default class ManageLanguage extends React.Component {
return (
<div key='changeLanguage'>
<br/>
- <label className='control-label'>{'Change interface language'}</label>
+ <label className='control-label'>
+ <FormattedMessage
+ id='user.settings.languages.change'
+ defaultMessage='Change interface language'
+ />
+ </label>
<div className='padding-top'>
<select
ref='language'
@@ -87,7 +94,10 @@ export default class ManageLanguage extends React.Component {
href='#'
onClick={this.changeLanguage}
>
- {'Set language'}
+ <FormattedMessage
+ id='user.settings.languages'
+ defaultMessage='Set language'
+ />
</a>
</div>
</div>
diff --git a/web/react/components/user_settings/manage_outgoing_hooks.jsx b/web/react/components/user_settings/manage_outgoing_hooks.jsx
index 17acf0f10..3f88e9f41 100644
--- a/web/react/components/user_settings/manage_outgoing_hooks.jsx
+++ b/web/react/components/user_settings/manage_outgoing_hooks.jsx
@@ -8,7 +8,20 @@ import ChannelStore from '../../stores/channel_store.jsx';
import * as Client from '../../utils/client.jsx';
import Constants from '../../utils/constants.jsx';
-export default class ManageOutgoingHooks extends React.Component {
+import {intlShape, injectIntl, defineMessages, FormattedMessage, FormattedHTMLMessage} from 'mm-intl';
+
+const holders = defineMessages({
+ optional: {
+ id: 'user.settings.hooks_out.optional',
+ defaultMessage: 'Optional if channel selected'
+ },
+ callbackHolder: {
+ id: 'user.settings.hooks_out.callbackHolder',
+ defaultMessage: 'Each URL must start with http:// or https://'
+ }
+});
+
+class ManageOutgoingHooks extends React.Component {
constructor() {
super();
@@ -140,7 +153,10 @@ export default class ManageOutgoingHooks extends React.Component {
key='select-channel'
value=''
>
- {'--- Select a channel ---'}
+ <FormattedMessage
+ id='user.settings.hooks_out.select'
+ defaultMessage='--- Select a channel ---'
+ />
</option>
);
@@ -169,7 +185,12 @@ export default class ManageOutgoingHooks extends React.Component {
if (c) {
channelDiv = (
<div className='padding-top'>
- <strong>{'Channel: '}</strong>{c.display_name}
+ <strong>
+ <FormattedMessage
+ id='user.settings.hooks_out.channel'
+ defaultMessage='Channel: '
+ />
+ </strong>{c.display_name}
</div>
);
}
@@ -178,7 +199,12 @@ export default class ManageOutgoingHooks extends React.Component {
if (hook.trigger_words && hook.trigger_words.length !== 0) {
triggerDiv = (
<div className='padding-top'>
- <strong>{'Trigger Words: '}</strong>{hook.trigger_words.join(', ')}
+ <strong>
+ <FormattedMessage
+ id='user.settings.hooks_out.trigger'
+ defaultMessage='Trigger Words: '
+ />
+ </strong>{hook.trigger_words.join(', ')}
</div>
);
}
@@ -202,7 +228,10 @@ export default class ManageOutgoingHooks extends React.Component {
href='#'
onClick={this.regenToken.bind(this, hook.id)}
>
- {'Regen Token'}
+ <FormattedMessage
+ id='user.settings.hooks_out.regen'
+ defaultMessage='Regen Token'
+ />
</a>
<a
className='webhook__remove'
@@ -223,12 +252,24 @@ export default class ManageOutgoingHooks extends React.Component {
} else if (hooks.length > 0) {
displayHooks = hooks;
} else {
- displayHooks = <div className='padding-top x2'>{'None'}</div>;
+ displayHooks = (
+ <div className='padding-top x2'>
+ <FormattedMessage
+ id='user.settings.hooks_out.none'
+ defaultMessage='None'
+ />
+ </div>
+ );
}
const existingHooks = (
<div className='webhooks__container'>
- <label className='control-label padding-top x2'>{'Existing outgoing webhooks'}</label>
+ <label className='control-label padding-top x2'>
+ <FormattedMessage
+ id='user.settings.hooks_out.existing'
+ defaultMessage='Existing outgoing webhooks'
+ />
+ </label>
<div className='padding-top divider-light'></div>
<div className='webhooks__list'>
{displayHooks}
@@ -240,19 +281,25 @@ export default class ManageOutgoingHooks extends React.Component {
return (
<div key='addOutgoingHook'>
- {'Create webhooks to send new message events to an external integration. Please see '}
- <a
- href='http://mattermost.org/webhooks'
- target='_blank'
- >
- {'http://mattermost.org/webhooks'}
- </a>
- {' to learn more.'}
- <div><label className='control-label padding-top x2'>{'Add a new outgoing webhook'}</label></div>
+ <FormattedHTMLMessage
+ id='user.settings.hooks_out.addDescription'
+ defaultMessage='Create webhooks to send new message events to an external integration. Please see <a href="http://mattermost.org/webhooks">http://mattermost.org/webhooks</a> to learn more.'
+ />
+ <div><label className='control-label padding-top x2'>
+ <FormattedMessage
+ id='user.settings.hooks_out.addTitle'
+ defaultMessage='Add a new outgoing webhook'
+ />
+ </label></div>
<div className='padding-top divider-light'></div>
<div className='padding-top'>
<div>
- <label className='control-label'>{'Channel'}</label>
+ <label className='control-label'>
+ <FormattedMessage
+ id='user.settings.hooks_out.channel'
+ defaultMessage='Channel: '
+ />
+ </label>
<div className='padding-top'>
<select
ref='channelName'
@@ -263,23 +310,43 @@ export default class ManageOutgoingHooks extends React.Component {
{options}
</select>
</div>
- <div className='padding-top'>{'Only public channels can be used'}</div>
+ <div className='padding-top'>
+ <FormattedMessage
+ id='user.settings.hooks_out.only'
+ defaultMessage='Only public channels can be used'
+ />
+ </div>
</div>
<div className='padding-top x2'>
- <label className='control-label'>{'Trigger Words:'}</label>
+ <label className='control-label'>
+ <FormattedMessage
+ id='user.settings.hooks_out.trigger'
+ defaultMessage='Trigger Words: '
+ />
+ </label>
<div className='padding-top'>
<input
ref='triggerWords'
className='form-control'
value={this.state.triggerWords}
onChange={this.updateTriggerWords}
- placeholder='Optional if channel selected'
+ placeholder={this.props.intl.formatMessage(holders.optional)}
+ />
+ </div>
+ <div className='padding-top'>
+ <FormattedMessage
+ id='user.settings.hooks_out.comma'
+ defaultMessage='Comma separated words to trigger on'
/>
</div>
- <div className='padding-top'>{'Comma separated words to trigger on'}</div>
</div>
<div className='padding-top x2'>
- <label className='control-label'>{'Callback URLs:'}</label>
+ <label className='control-label'>
+ <FormattedMessage
+ id='user.settings.hooks_out.callback'
+ defaultMessage='Callback URLs: '
+ />
+ </label>
<div className='padding-top'>
<textarea
ref='callbackURLs'
@@ -288,10 +355,15 @@ export default class ManageOutgoingHooks extends React.Component {
resize={false}
rows={3}
onChange={this.updateCallbackURLs}
- placeholder='Each URL must start with http:// or https://'
+ placeholder={this.props.intl.formatMessage(holders.callbackHolder)}
/>
</div>
- <div className='padding-top'>{'New line separated URLs that will receive the HTTP POST event'}</div>
+ <div className='padding-top'>
+ <FormattedMessage
+ id='user.settings.hooks_out.callbackDesc'
+ defaultMessage='New line separated URLs that will receive the HTTP POST event'
+ />
+ </div>
{addError}
</div>
<div className='padding-top padding-bottom'>
@@ -301,7 +373,10 @@ export default class ManageOutgoingHooks extends React.Component {
disabled={disableButton}
onClick={this.addNewHook}
>
- {'Add'}
+ <FormattedMessage
+ id='user.settings.hooks_out.add'
+ defaultMessage='Add'
+ />
</a>
</div>
</div>
@@ -311,3 +386,9 @@ export default class ManageOutgoingHooks extends React.Component {
);
}
}
+
+ManageOutgoingHooks.propTypes = {
+ intl: intlShape.isRequired
+};
+
+export default injectIntl(ManageOutgoingHooks); \ No newline at end of file
diff --git a/web/react/components/user_settings/user_settings_advanced.jsx b/web/react/components/user_settings/user_settings_advanced.jsx
index d4bd00bb5..5c0757589 100644
--- a/web/react/components/user_settings/user_settings_advanced.jsx
+++ b/web/react/components/user_settings/user_settings_advanced.jsx
@@ -6,9 +6,55 @@ import SettingItemMin from '../setting_item_min.jsx';
import SettingItemMax from '../setting_item_max.jsx';
import Constants from '../../utils/constants.jsx';
import PreferenceStore from '../../stores/preference_store.jsx';
+
+import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl';
+
const PreReleaseFeatures = Constants.PRE_RELEASE_FEATURES;
-export default class AdvancedSettingsDisplay extends React.Component {
+const holders = defineMessages({
+ sendTitle: {
+ id: 'user.settings.advance.sendTitle',
+ defaultMessage: 'Send messages on Ctrl + Enter'
+ },
+ on: {
+ id: 'user.settings.advance.on',
+ defaultMessage: 'On'
+ },
+ off: {
+ id: 'user.settings.advance.off',
+ defaultMessage: 'Off'
+ },
+ preReleaseTitle: {
+ id: 'user.settings.advance.preReleaseTitle',
+ defaultMessage: 'Preview pre-release features'
+ },
+ feature: {
+ id: 'user.settings.advance.feature',
+ defaultMessage: ' Feature '
+ },
+ features: {
+ id: 'user.settings.advance.features',
+ defaultMessage: ' Features '
+ },
+ enabled: {
+ id: 'user.settings.advance.enabled',
+ defaultMessage: 'enabled'
+ },
+ MARKDOWN_PREVIEW: {
+ id: 'user.settings.advance.markdown_preview',
+ defaultMessage: 'Show markdown preview option in message input box'
+ },
+ EMBED_PREVIEW: {
+ id: 'user.settings.advance.embed_preview',
+ defaultMessage: 'Show preview snippet of links below message'
+ },
+ LOC_PREVIEW: {
+ id: 'user.settings.advance.loc_preview',
+ defaultMessage: 'Show user language in display settings'
+ }
+});
+
+class AdvancedSettingsDisplay extends React.Component {
constructor(props) {
super(props);
@@ -104,6 +150,7 @@ export default class AdvancedSettingsDisplay extends React.Component {
render() {
const serverError = this.state.serverError || null;
+ const {formatMessage} = this.props.intl;
let ctrlSendSection;
if (this.props.activeSection === 'advancedCtrlSend') {
@@ -121,7 +168,10 @@ export default class AdvancedSettingsDisplay extends React.Component {
checked={ctrlSendActive[0]}
onChange={this.updateSetting.bind(this, 'send_on_ctrl_enter', 'true')}
/>
- {'On'}
+ <FormattedMessage
+ id='user.settings.advance.on'
+ defaultMessage='On'
+ />
</label>
<br/>
</div>
@@ -132,17 +182,26 @@ export default class AdvancedSettingsDisplay extends React.Component {
checked={ctrlSendActive[1]}
onChange={this.updateSetting.bind(this, 'send_on_ctrl_enter', 'false')}
/>
- {'Off'}
+ <FormattedMessage
+ id='user.settings.advance.off'
+ defaultMessage='Off'
+ />
</label>
<br/>
</div>
- <div><br/>{'If enabled \'Enter\' inserts a new line and \'Ctrl + Enter\' submits the message.'}</div>
+ <div>
+ <br/>
+ <FormattedMessage
+ id='user.settings.advance.sendDesc'
+ defaultMessage="If enabled 'Enter' inserts a new line and 'Ctrl + Enter' submits the message."
+ />
+ </div>
</div>
];
ctrlSendSection = (
<SettingItemMax
- title='Send messages on Ctrl + Enter'
+ title={formatMessage(holders.sendTitle)}
inputs={inputs}
submit={() => this.handleSubmit('send_on_ctrl_enter')}
server_error={serverError}
@@ -155,8 +214,8 @@ export default class AdvancedSettingsDisplay extends React.Component {
} else {
ctrlSendSection = (
<SettingItemMin
- title='Send messages on Ctrl + Enter'
- describe={this.state.settings.send_on_ctrl_enter === 'true' ? 'On' : 'Off'}
+ title={formatMessage(holders.sendTitle)}
+ describe={this.state.settings.send_on_ctrl_enter === 'true' ? formatMessage(holders.on) : formatMessage(holders.off)}
updateSection={() => this.props.updateSection('advancedCtrlSend')}
/>
);
@@ -185,7 +244,7 @@ export default class AdvancedSettingsDisplay extends React.Component {
this.toggleFeature(feature.label, e.target.checked);
}}
/>
- {feature.description}
+ {formatMessage({id: 'user.settings.advance.' + feature.label})}
</label>
</div>
</div>
@@ -195,13 +254,16 @@ export default class AdvancedSettingsDisplay extends React.Component {
inputs.push(
<div key='advancedPreviewFeatures_helptext'>
<br/>
- {'Check any pre-released features you\'d like to preview. You may also need to refresh the page before the setting will take effect.'}
+ <FormattedMessage
+ id='user.settings.advance.preReleaseDesc'
+ defaultMessage="Check any pre-released features you'd like to preview. You may also need to refresh the page before the setting will take effect."
+ />
</div>
);
previewFeaturesSection = (
<SettingItemMax
- title='Preview pre-release features'
+ title={formatMessage(holders.preReleaseTitle)}
inputs={inputs}
submit={this.saveEnabledFeatures}
server_error={serverError}
@@ -214,8 +276,8 @@ export default class AdvancedSettingsDisplay extends React.Component {
} else {
previewFeaturesSection = (
<SettingItemMin
- title='Preview pre-release features'
- describe={this.state.enabledFeatures + (this.state.enabledFeatures === 1 ? ' Feature ' : ' Features ') + 'enabled'}
+ title={formatMessage(holders.preReleaseTitle)}
+ describe={this.state.enabledFeatures + (this.state.enabledFeatures === 1 ? formatMessage(holders.feature) : formatMessage(holders.features)) + formatMessage(holders.enabled)}
updateSection={() => this.props.updateSection('advancedPreviewFeatures')}
/>
);
@@ -242,11 +304,19 @@ export default class AdvancedSettingsDisplay extends React.Component {
className='modal-back'
onClick={this.props.collapseModal}
/>
- {'Advanced Settings'}
+ <FormattedMessage
+ id='user.settings.advance.title'
+ defaultMessage='Advanced Settings'
+ />
</h4>
</div>
<div className='user-settings'>
- <h3 className='tab-header'>{'Advanced Settings'}</h3>
+ <h3 className='tab-header'>
+ <FormattedMessage
+ id='user.settings.advance.title'
+ defaultMessage='Advanced Settings'
+ />
+ </h3>
<div className='divider-dark first'/>
{ctrlSendSection}
{previewFeaturesSectionDivider}
@@ -259,6 +329,7 @@ export default class AdvancedSettingsDisplay extends React.Component {
}
AdvancedSettingsDisplay.propTypes = {
+ intl: intlShape.isRequired,
user: React.PropTypes.object,
updateSection: React.PropTypes.func,
updateTab: React.PropTypes.func,
@@ -266,3 +337,5 @@ AdvancedSettingsDisplay.propTypes = {
closeModal: React.PropTypes.func.isRequired,
collapseModal: React.PropTypes.func.isRequired
};
+
+export default injectIntl(AdvancedSettingsDisplay); \ No newline at end of file
diff --git a/web/react/components/user_settings/user_settings_appearance.jsx b/web/react/components/user_settings/user_settings_appearance.jsx
index 7bfc9fdbd..fb11dc81b 100644
--- a/web/react/components/user_settings/user_settings_appearance.jsx
+++ b/web/react/components/user_settings/user_settings_appearance.jsx
@@ -11,6 +11,9 @@ import * as Client from '../../utils/client.jsx';
import * as Utils from '../../utils/utils.jsx';
import Constants from '../../utils/constants.jsx';
+
+import {FormattedMessage} from 'mm-intl';
+
const ActionTypes = Constants.ActionTypes;
export default class UserSettingsAppearance extends React.Component {
@@ -180,7 +183,10 @@ export default class UserSettingsAppearance extends React.Component {
checked={!displayCustom}
onChange={this.updateType.bind(this, 'premade')}
/>
- {'Theme Colors'}
+ <FormattedMessage
+ id='user.settings.appearance.themeColors'
+ defaultMessage='Theme Colors'
+ />
</label>
<br/>
</div>
@@ -191,7 +197,10 @@ export default class UserSettingsAppearance extends React.Component {
checked={displayCustom}
onChange={this.updateType.bind(this, 'custom')}
/>
- {'Custom Theme'}
+ <FormattedMessage
+ id='user.settings.appearance.customTheme'
+ defaultMessage='Custom Theme'
+ />
</label>
<br/>
</div>
@@ -203,14 +212,20 @@ export default class UserSettingsAppearance extends React.Component {
href='#'
onClick={this.submitTheme}
>
- {'Save'}
+ <FormattedMessage
+ id='user.settings.appearance.save'
+ defaultMessage='Save'
+ />
</a>
<a
className='btn btn-sm theme'
href='#'
onClick={this.resetFields}
>
- {'Cancel'}
+ <FormattedMessage
+ id='user.settings.appearance.cancel'
+ defaultMessage='Cancel'
+ />
</a>
</div>
</div>
@@ -235,11 +250,19 @@ export default class UserSettingsAppearance extends React.Component {
className='modal-back'
onClick={this.props.collapseModal}
/>
- {'Appearance Settings'}
+ <FormattedMessage
+ id='user.settings.appearance.title'
+ defaultMessage='Appearance Settings'
+ />
</h4>
</div>
<div className='user-settings'>
- <h3 className='tab-header'>{'Appearance Settings'}</h3>
+ <h3 className='tab-header'>
+ <FormattedMessage
+ id='user.settings.appearance.title'
+ defaultMessage='Appearance Settings'
+ />
+ </h3>
<div className='divider-dark first'/>
{themeUI}
<div className='divider-dark'/>
@@ -248,7 +271,10 @@ export default class UserSettingsAppearance extends React.Component {
className='theme'
onClick={this.handleImportModal}
>
- {'Import theme colors from Slack'}
+ <FormattedMessage
+ id='user.settings.appearance.import'
+ defaultMessage='Import theme colors from Slack'
+ />
</a>
</div>
</div>
diff --git a/web/react/components/user_settings/user_settings_developer.jsx b/web/react/components/user_settings/user_settings_developer.jsx
index 01e13be57..5868e0ad3 100644
--- a/web/react/components/user_settings/user_settings_developer.jsx
+++ b/web/react/components/user_settings/user_settings_developer.jsx
@@ -5,7 +5,20 @@ import SettingItemMin from '../setting_item_min.jsx';
import SettingItemMax from '../setting_item_max.jsx';
import * as EventHelpers from '../../dispatcher/event_helpers.jsx';
-export default class DeveloperTab extends React.Component {
+import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl';
+
+const holders = defineMessages({
+ applicationsPreview: {
+ id: 'user.settings.developer.applicationsPreview',
+ defaultMessage: 'Applications (Preview)'
+ },
+ thirdParty: {
+ id: 'user.settings.developer.thirdParty',
+ defaultMessage: 'Open to register a new third-party application'
+ }
+});
+
+class DeveloperTab extends React.Component {
constructor(props) {
super(props);
@@ -20,6 +33,7 @@ export default class DeveloperTab extends React.Component {
render() {
var appSection;
var self = this;
+ const {formatMessage} = this.props.intl;
if (this.props.activeSection === 'app') {
var inputs = [];
@@ -33,7 +47,10 @@ export default class DeveloperTab extends React.Component {
className='btn btn-sm btn-primary'
onClick={this.register}
>
- {'Register New Application'}
+ <FormattedMessage
+ id='user.settings.developer.register'
+ defaultMessage='Register New Application'
+ />
</a>
</div>
</div>
@@ -41,7 +58,7 @@ export default class DeveloperTab extends React.Component {
appSection = (
<SettingItemMax
- title='Applications (Preview)'
+ title={formatMessage(holders.applicationsPreview)}
inputs={inputs}
updateSection={function updateSection(e) {
self.props.updateSection('');
@@ -52,8 +69,8 @@ export default class DeveloperTab extends React.Component {
} else {
appSection = (
<SettingItemMin
- title='Applications (Preview)'
- describe='Open to register a new third-party application'
+ title={formatMessage(holders.applicationsPreview)}
+ describe={formatMessage(holders.thirdParty)}
updateSection={function updateSection() {
self.props.updateSection('app');
}}
@@ -81,11 +98,19 @@ export default class DeveloperTab extends React.Component {
className='modal-back'
onClick={this.props.collapseModal}
/>
- {'Developer Settings'}
+ <FormattedMessage
+ id='user.settings.developer.title'
+ defaultMessage='Developer Settings'
+ />
</h4>
</div>
<div className='user-settings'>
- <h3 className='tab-header'>{'Developer Settings'}</h3>
+ <h3 className='tab-header'>
+ <FormattedMessage
+ id='user.settings.developer.title'
+ defaultMessage='Developer Settings'
+ />
+ </h3>
<div className='divider-dark first'/>
{appSection}
<div className='divider-dark'/>
@@ -99,8 +124,11 @@ DeveloperTab.defaultProps = {
activeSection: ''
};
DeveloperTab.propTypes = {
+ intl: intlShape.isRequired,
activeSection: React.PropTypes.string,
updateSection: React.PropTypes.func,
closeModal: React.PropTypes.func.isRequired,
collapseModal: React.PropTypes.func.isRequired
};
+
+export default injectIntl(DeveloperTab); \ No newline at end of file
diff --git a/web/react/components/user_settings/user_settings_display.jsx b/web/react/components/user_settings/user_settings_display.jsx
index f2c2502fb..3b2a2065b 100644
--- a/web/react/components/user_settings/user_settings_display.jsx
+++ b/web/react/components/user_settings/user_settings_display.jsx
@@ -10,6 +10,47 @@ import PreferenceStore from '../../stores/preference_store.jsx';
import ManageLanguages from './manage_languages.jsx';
import * as Utils from '../../utils/utils.jsx';
+import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl';
+
+const holders = defineMessages({
+ normalClock: {
+ id: 'user.settings.display.normalClock',
+ defaultMessage: '12-hour clock (example: 4:00 PM)'
+ },
+ militaryClock: {
+ id: 'user.settings.display.militaryClock',
+ defaultMessage: '24-hour clock (example: 16:00)'
+ },
+ clockDisplay: {
+ id: 'user.settings.display.clockDisplay',
+ defaultMessage: 'Clock Display'
+ },
+ teammateDisplay: {
+ id: 'user.settings.display.teammateDisplay',
+ defaultMessage: 'Teammate Name Display'
+ },
+ showNickname: {
+ id: 'user.settings.display.showNickname',
+ defaultMessage: 'Show nickname if one exists, otherwise show first and last name'
+ },
+ showUsername: {
+ id: 'user.settings.display.showUsername',
+ defaultMessage: 'Show username (team default)'
+ },
+ showFullname: {
+ id: 'user.settings.display.showFullname',
+ defaultMessage: 'Show first and last name'
+ },
+ fontTitle: {
+ id: 'user.settings.display.fontTitle',
+ defaultMessage: 'Display Font'
+ },
+ language: {
+ id: 'user.settings.display.language',
+ defaultMessage: 'Language'
+ }
+});
+
function getDisplayStateFromStores() {
const militaryTime = PreferenceStore.getPreference(Constants.Preferences.CATEGORY_DISPLAY_SETTINGS, 'use_military_time', {value: 'false'});
const nameFormat = PreferenceStore.getPreference(Constants.Preferences.CATEGORY_DISPLAY_SETTINGS, 'name_format', {value: 'username'});
@@ -22,7 +63,7 @@ function getDisplayStateFromStores() {
};
}
-export default class UserSettingsDisplay extends React.Component {
+class UserSettingsDisplay extends React.Component {
constructor(props) {
super(props);
@@ -76,6 +117,7 @@ export default class UserSettingsDisplay extends React.Component {
this.updateState();
}
render() {
+ const {formatMessage} = this.props.intl;
const serverError = this.state.serverError || null;
let clockSection;
let nameFormatSection;
@@ -104,7 +146,10 @@ export default class UserSettingsDisplay extends React.Component {
checked={clockFormat[0]}
onChange={this.handleClockRadio.bind(this, 'false')}
/>
- {'12-hour clock (example: 4:00 PM)'}
+ <FormattedMessage
+ id='user.settings.display.normalClock'
+ defaultMessage='12-hour clock (example: 4:00 PM)'
+ />
</label>
<br/>
</div>
@@ -115,17 +160,26 @@ export default class UserSettingsDisplay extends React.Component {
checked={clockFormat[1]}
onChange={this.handleClockRadio.bind(this, 'true')}
/>
- {'24-hour clock (example: 16:00)'}
+ <FormattedMessage
+ id='user.settings.display.militaryClock'
+ defaultMessage='24-hour clock (example: 16:00)'
+ />
</label>
<br/>
</div>
- <div><br/>{'Select how you prefer time displayed.'}</div>
+ <div>
+ <br/>
+ <FormattedMessage
+ id='user.settings.display.preferTime'
+ defaultMessage='Select how you prefer time displayed.'
+ />
+ </div>
</div>
];
clockSection = (
<SettingItemMax
- title='Clock Display'
+ title={formatMessage(holders.clockDisplay)}
inputs={inputs}
submit={this.handleSubmit}
server_error={serverError}
@@ -135,9 +189,9 @@ export default class UserSettingsDisplay extends React.Component {
} else {
let describe = '';
if (this.state.militaryTime === 'true') {
- describe = '24-hour clock (example: 16:00)';
+ describe = formatMessage(holders.militaryClock);
} else {
- describe = '12-hour clock (example: 4:00 PM)';
+ describe = formatMessage(holders.normalClock);
}
const handleUpdateClockSection = () => {
@@ -146,16 +200,31 @@ export default class UserSettingsDisplay extends React.Component {
clockSection = (
<SettingItemMin
- title='Clock Display'
+ title={formatMessage(holders.clockDisplay)}
describe={describe}
updateSection={handleUpdateClockSection}
/>
);
}
- const showUsername = 'Show username (team default)';
- const showNickname = 'Show nickname if one exists, otherwise show first and last name';
- const showFullName = 'Show first and last name';
+ const showUsername = (
+ <FormattedMessage
+ id='user.settings.display.showUsername'
+ defaultMessage='Show username (team default)'
+ />
+ );
+ const showNickname = (
+ <FormattedMessage
+ id='user.settings.display.showNickname'
+ defaultMessage='Show nickname if one exists, otherwise show first and last name'
+ />
+ );
+ const showFullName = (
+ <FormattedMessage
+ id='user.settings.display.showFullname'
+ defaultMessage='Show first and last name'
+ />
+ );
if (this.props.activeSection === 'name_format') {
const nameFormat = [false, false, false];
if (this.state.nameFormat === 'nickname_full_name') {
@@ -201,13 +270,19 @@ export default class UserSettingsDisplay extends React.Component {
</label>
<br/>
</div>
- <div><br/>{'Set how to display other user\'s names in posts and the Direct Messages list.'}</div>
+ <div>
+ <br/>
+ <FormattedMessage
+ id='user.settings.display.nameOptsDesc'
+ defaultMessage="Set how to display other user's names in posts and the Direct Messages list."
+ />
+ </div>
</div>
];
nameFormatSection = (
<SettingItemMax
- title='Teammate Name Display'
+ title={formatMessage(holders.teammateDisplay)}
inputs={inputs}
submit={this.handleSubmit}
server_error={serverError}
@@ -220,16 +295,16 @@ export default class UserSettingsDisplay extends React.Component {
} else {
let describe = '';
if (this.state.nameFormat === 'username') {
- describe = showUsername;
+ describe = formatMessage(holders.showUsername);
} else if (this.state.nameFormat === 'full_name') {
- describe = showFullName;
+ describe = formatMessage(holders.showFullName);
} else {
- describe = showNickname;
+ describe = formatMessage(holders.showNickname);
}
nameFormatSection = (
<SettingItemMin
- title='Teammate Name Display'
+ title={formatMessage(holders.teammateDisplay)}
describe={describe}
updateSection={() => {
this.props.updateSection('name_format');
@@ -267,13 +342,19 @@ export default class UserSettingsDisplay extends React.Component {
{options}
</select>
</div>
- <div><br/>{'Select the font displayed in the Mattermost user interface.'}</div>
+ <div>
+ <br/>
+ <FormattedMessage
+ id='user.settings.display.fontDesc'
+ defaultMessage='Select the font displayed in the Mattermost user interface.'
+ />
+ </div>
</div>
];
fontSection = (
<SettingItemMax
- title='Display Font'
+ title={formatMessage(holders.fontTitle)}
inputs={inputs}
submit={this.handleSubmit}
server_error={serverError}
@@ -286,7 +367,7 @@ export default class UserSettingsDisplay extends React.Component {
} else {
fontSection = (
<SettingItemMin
- title='Display Font'
+ title={formatMessage(holders.fontTitle)}
describe={this.state.selectedFont}
updateSection={() => {
this.props.updateSection('font');
@@ -307,7 +388,7 @@ export default class UserSettingsDisplay extends React.Component {
languagesSection = (
<SettingItemMax
- title={'Language'}
+ title={formatMessage(holders.language)}
width='medium'
inputs={inputs}
updateSection={(e) => {
@@ -326,7 +407,7 @@ export default class UserSettingsDisplay extends React.Component {
languagesSection = (
<SettingItemMin
- title={'Language'}
+ title={formatMessage(holders.language)}
width='medium'
describe={locale}
updateSection={() => {
@@ -357,11 +438,19 @@ export default class UserSettingsDisplay extends React.Component {
className='modal-back'
onClick={this.props.collapseModal}
/>
- {'Display Settings'}
+ <FormattedMessage
+ id='user.settings.display.title'
+ defaultMessage='Display Settings'
+ />
</h4>
</div>
<div className='user-settings'>
- <h3 className='tab-header'>{'Display Settings'}</h3>
+ <h3 className='tab-header'>
+ <FormattedMessage
+ id='user.settings.display.title'
+ defaultMessage='Display Settings'
+ />
+ </h3>
<div className='divider-dark first'/>
{fontSection}
<div className='divider-dark'/>
@@ -377,6 +466,7 @@ export default class UserSettingsDisplay extends React.Component {
}
UserSettingsDisplay.propTypes = {
+ intl: intlShape.isRequired,
user: React.PropTypes.object,
updateSection: React.PropTypes.func,
updateTab: React.PropTypes.func,
@@ -384,3 +474,5 @@ UserSettingsDisplay.propTypes = {
closeModal: React.PropTypes.func.isRequired,
collapseModal: React.PropTypes.func.isRequired
};
+
+export default injectIntl(UserSettingsDisplay); \ No newline at end of file
diff --git a/web/react/components/user_settings/user_settings_general.jsx b/web/react/components/user_settings/user_settings_general.jsx
index df7ae4a25..f20b4b807 100644
--- a/web/react/components/user_settings/user_settings_general.jsx
+++ b/web/react/components/user_settings/user_settings_general.jsx
@@ -13,7 +13,84 @@ import Constants from '../../utils/constants.jsx';
import * as AsyncClient from '../../utils/async_client.jsx';
import * as Utils from '../../utils/utils.jsx';
-export default class UserSettingsGeneralTab extends React.Component {
+import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl';
+
+const holders = defineMessages({
+ usernameReserved: {
+ id: 'user.settings.general.usernameReserved',
+ defaultMessage: 'This username is reserved, please choose a new one.'
+ },
+ usernameRestrictions: {
+ id: 'user.settings.general.usernameRestrictions',
+ defaultMessage: "'Username must begin with a letter, and contain between {min} to {max} lowercase characters made up of numbers, letters, and the symbols '.', '-' and '_'."
+ },
+ validEmail: {
+ id: 'user.settings.general.validEmail',
+ defaultMessage: 'Please enter a valid email address'
+ },
+ emailMatch: {
+ id: 'user.settings.general.emailMatch',
+ defaultMessage: 'The new emails you entered do not match.'
+ },
+ checkEmail: {
+ id: 'user.settings.general.checkEmail',
+ defaultMessage: 'Check your email at {email} to verify the address.'
+ },
+ newAddress: {
+ id: 'user.settings.general.newAddress',
+ defaultMessage: 'New Address: {email}<br />Check your email to verify the above address.'
+ },
+ checkEmailNoAddress: {
+ id: 'user.settings.general.checkEmailNoAddress',
+ defaultMessage: 'Check your email to verify your new address'
+ },
+ loginGitlab: {
+ id: 'user.settings.general.loginGitlab',
+ defaultMessage: 'Log in done through GitLab'
+ },
+ validImage: {
+ id: 'user.settings.general.validImage',
+ defaultMessage: 'Only JPG or PNG images may be used for profile pictures'
+ },
+ imageTooLarge: {
+ id: 'user.settings.general.imageTooLarge',
+ defaultMessage: 'Unable to upload profile image. File is too large.'
+ },
+ uploadImage: {
+ id: 'user.settings.general.uploadImage',
+ defaultMessage: "Click 'Edit' to upload an image."
+ },
+ imageUpdated: {
+ id: 'user.settings.general.imageUpdated',
+ defaultMessage: 'Image last updated {date}'
+ },
+ fullName: {
+ id: 'user.settings.general.fullName',
+ defaultMessage: 'Full Name'
+ },
+ nickname: {
+ id: 'user.settings.general.nickname',
+ defaultMessage: 'Nickname'
+ },
+ username: {
+ id: 'user.settings.general.username',
+ defaultMessage: 'Username'
+ },
+ email: {
+ id: 'user.settings.general.email',
+ defaultMessage: 'Email'
+ },
+ profilePicture: {
+ id: 'user.settings.general.profilePicture',
+ defaultMessage: 'Profile Picture'
+ },
+ close: {
+ id: 'user.settings.general.close',
+ defaultMessage: 'Close'
+ }
+});
+
+class UserSettingsGeneralTab extends React.Component {
constructor(props) {
super(props);
this.submitActive = false;
@@ -42,12 +119,13 @@ export default class UserSettingsGeneralTab extends React.Component {
const user = Object.assign({}, this.props.user);
const username = this.state.username.trim().toLowerCase();
+ const {formatMessage} = this.props.intl;
const usernameError = Utils.isValidUsername(username);
if (usernameError === 'Cannot use a reserved word as a username.') {
- this.setState({clientError: 'This username is reserved, please choose a new one.'});
+ this.setState({clientError: formatMessage(holders.usernameReserved)});
return;
} else if (usernameError) {
- this.setState({clientError: 'Username must begin with a letter, and contain between ' + Constants.MIN_USERNAME_LENGTH + ' to ' + Constants.MAX_USERNAME_LENGTH + " lowercase characters made up of numbers, letters, and the symbols '.', '-' and '_'."});
+ this.setState({clientError: formatMessage(holders.usernameRestrictions, {min: Constants.MIN_USERNAME_LENGTH, max: Constants.MAX_USERNAME_LENGTH})});
return;
}
@@ -99,13 +177,14 @@ export default class UserSettingsGeneralTab extends React.Component {
const email = this.state.email.trim().toLowerCase();
const confirmEmail = this.state.confirmEmail.trim().toLowerCase();
+ const {formatMessage} = this.props.intl;
if (email === '' || !Utils.isEmail(email)) {
- this.setState({emailError: 'Please enter a valid email address.', clientError: '', serverError: ''});
+ this.setState({emailError: formatMessage(holders.validEmail), clientError: '', serverError: ''});
return;
}
if (email !== confirmEmail) {
- this.setState({emailError: 'The new emails you entered do not match.', clientError: '', serverError: ''});
+ this.setState({emailError: formatMessage(holders.emailMatch), clientError: '', serverError: ''});
return;
}
@@ -125,7 +204,7 @@ export default class UserSettingsGeneralTab extends React.Component {
const verificationEnabled = global.window.mm_config.SendEmailNotifications === 'true' && global.window.mm_config.RequireEmailVerification === 'true' && emailUpdated;
if (verificationEnabled) {
- ErrorStore.storeLastError({message: 'Check your email at ' + user.email + ' to verify the address.'});
+ ErrorStore.storeLastError({message: this.props.intl.formatMessage(holders.checkEmail, {email: user.email})});
ErrorStore.emitChange();
this.setState({emailChangeInProgress: true});
}
@@ -152,13 +231,14 @@ export default class UserSettingsGeneralTab extends React.Component {
return;
}
+ const {formatMessage} = this.props.intl;
const picture = this.state.picture;
if (picture.type !== 'image/jpeg' && picture.type !== 'image/png') {
- this.setState({clientError: 'Only JPG or PNG images may be used for profile pictures.'});
+ this.setState({clientError: formatMessage(holders.validImage)});
return;
} else if (picture.size > Constants.MAX_FILE_SIZE) {
- this.setState({clientError: 'Unable to upload profile image. File is too large.'});
+ this.setState({clientError: formatMessage(holders.imageTooLarge)});
return;
}
@@ -221,6 +301,7 @@ export default class UserSettingsGeneralTab extends React.Component {
}
render() {
const user = this.props.user;
+ const {formatMessage, formatHTMLMessage} = this.props.intl;
let clientError = null;
if (this.state.clientError) {
@@ -244,7 +325,12 @@ export default class UserSettingsGeneralTab extends React.Component {
key='firstNameSetting'
className='form-group'
>
- <label className='col-sm-5 control-label'>{'First Name'}</label>
+ <label className='col-sm-5 control-label'>
+ <FormattedMessage
+ id='user.settings.general.firstName'
+ defaultMessage='First Name'
+ />
+ </label>
<div className='col-sm-7'>
<input
className='form-control'
@@ -261,7 +347,12 @@ export default class UserSettingsGeneralTab extends React.Component {
key='lastNameSetting'
className='form-group'
>
- <label className='col-sm-5 control-label'>{'Last Name'}</label>
+ <label className='col-sm-5 control-label'>
+ <FormattedMessage
+ id='user.settings.general.lastName'
+ defaultMessage='Last Name'
+ />
+ </label>
<div className='col-sm-7'>
<input
className='form-control'
@@ -284,20 +375,28 @@ export default class UserSettingsGeneralTab extends React.Component {
href='#'
onClick={notifClick.bind(this)}
>
- {'Notifications'}
+ <FormattedMessage
+ id='user.settings.general.notificationsLink'
+ defaultMessage='Notifications'
+ />
</a>
);
const extraInfo = (
<span>
- {'By default, you will receive mention notifications when someone types your first name. '}
- {'Go to '} {notifLink} {'settings to change this default.'}
+ <FormattedMessage
+ id='user.settings.general.notificationsExtra'
+ defaultMessage='By default, you will receive mention notifications when someone types your first name. Go to {notify} settings to change this default.'
+ values={{
+ notify: (notifLink)
+ }}
+ />
</span>
);
nameSection = (
<SettingItemMax
- title='Full Name'
+ title={formatMessage(holders.fullName)}
inputs={inputs}
submit={this.submitName}
server_error={serverError}
@@ -322,7 +421,7 @@ export default class UserSettingsGeneralTab extends React.Component {
nameSection = (
<SettingItemMin
- title='Full Name'
+ title={formatMessage(holders.fullName)}
describe={fullName}
updateSection={() => {
this.updateSection('name');
@@ -333,7 +432,12 @@ export default class UserSettingsGeneralTab extends React.Component {
let nicknameSection;
if (this.props.activeSection === 'nickname') {
- let nicknameLabel = 'Nickname';
+ let nicknameLabel = (
+ <FormattedMessage
+ id='user.settings.general.nickname'
+ defaultMessage='Nickname'
+ />
+ );
if (Utils.isMobile()) {
nicknameLabel = '';
}
@@ -357,13 +461,16 @@ export default class UserSettingsGeneralTab extends React.Component {
const extraInfo = (
<span>
- {'Use Nickname for a name you might be called that is different from your first name and username. This is most often used when two or more people have similar sounding names and usernames.'}
+ <FormattedMessage
+ id='user.settings.general.nicknameExtra'
+ defaultMessage='Use Nickname for a name you might be called that is different from your first name and username. This is most often used when two or more people have similar sounding names and usernames.'
+ />
</span>
);
nicknameSection = (
<SettingItemMax
- title='Nickname'
+ title={formatMessage(holders.nickname)}
inputs={inputs}
submit={this.submitNickname}
server_error={serverError}
@@ -378,7 +485,7 @@ export default class UserSettingsGeneralTab extends React.Component {
} else {
nicknameSection = (
<SettingItemMin
- title='Nickname'
+ title={formatMessage(holders.nickname)}
describe={UserStore.getCurrentUser().nickname}
updateSection={() => {
this.updateSection('nickname');
@@ -389,7 +496,12 @@ export default class UserSettingsGeneralTab extends React.Component {
let usernameSection;
if (this.props.activeSection === 'username') {
- let usernameLabel = 'Username';
+ let usernameLabel = (
+ <FormattedMessage
+ id='user.settings.general.username'
+ defaultMessage='Username'
+ />
+ );
if (Utils.isMobile()) {
usernameLabel = '';
}
@@ -411,11 +523,18 @@ export default class UserSettingsGeneralTab extends React.Component {
</div>
);
- const extraInfo = (<span>{'Pick something easy for teammates to recognize and recall.'}</span>);
+ const extraInfo = (
+ <span>
+ <FormattedMessage
+ id='user.settings.general.usernameInfo'
+ defaultMessage='Pick something easy for teammates to recognize and recall.'
+ />
+ </span>
+ );
usernameSection = (
<SettingItemMax
- title='Username'
+ title={formatMessage(holders.username)}
inputs={inputs}
submit={this.submitUsername}
server_error={serverError}
@@ -430,7 +549,7 @@ export default class UserSettingsGeneralTab extends React.Component {
} else {
usernameSection = (
<SettingItemMin
- title='Username'
+ title={formatMessage(holders.username)}
describe={UserStore.getCurrentUser().username}
updateSection={() => {
this.updateSection('username');
@@ -443,16 +562,41 @@ export default class UserSettingsGeneralTab extends React.Component {
if (this.props.activeSection === 'email') {
const emailEnabled = global.window.mm_config.SendEmailNotifications === 'true';
const emailVerificationEnabled = global.window.mm_config.RequireEmailVerification === 'true';
- let helpText = 'Email is used for sign-in, notifications, and password reset. Email requires verification if changed.';
+ let helpText = (
+ <FormattedMessage
+ id='user.settings.general.emailHelp1'
+ defaultMessage='Email is used for sign-in, notifications, and password reset. Email requires verification if changed.'
+ />
+ );
if (!emailEnabled) {
- helpText = <div className='setting-list__hint text-danger'>{'Email has been disabled by your system administrator. No notification emails will be sent until it is enabled.'}</div>;
+ helpText = (
+ <div className='setting-list__hint text-danger'>
+ <FormattedMessage
+ id='user.settings.general.emailHelp2'
+ defaultMessage='Email has been disabled by your system administrator. No notification emails will be sent until it is enabled.'
+ />
+ </div>
+ );
} else if (!emailVerificationEnabled) {
- helpText = 'Email is used for sign-in, notifications, and password reset.';
+ helpText = (
+ <FormattedMessage
+ id='user.settings.general.emailHelp3'
+ defaultMessage='Email is used for sign-in, notifications, and password reset.'
+ />
+ );
} else if (this.state.emailChangeInProgress) {
const newEmail = UserStore.getCurrentUser().email;
if (newEmail) {
- helpText = 'A verification email was sent to ' + newEmail + '.';
+ helpText = (
+ <FormattedMessage
+ id='user.settings.general.emailHelp4'
+ defaultMessage='A verification email was sent to {email}.'
+ values={{
+ email: newEmail
+ }}
+ />
+ );
}
}
@@ -462,7 +606,12 @@ export default class UserSettingsGeneralTab extends React.Component {
inputs.push(
<div key='emailSetting'>
<div className='form-group'>
- <label className='col-sm-5 control-label'>{'Primary Email'}</label>
+ <label className='col-sm-5 control-label'>
+ <FormattedMessage
+ id='user.settings.general.primaryEmail'
+ defaultMessage='Primary Email'
+ />
+ </label>
<div className='col-sm-7'>
<input
className='form-control'
@@ -478,7 +627,12 @@ export default class UserSettingsGeneralTab extends React.Component {
inputs.push(
<div key='confirmEmailSetting'>
<div className='form-group'>
- <label className='col-sm-5 control-label'>{'Confirm Email'}</label>
+ <label className='col-sm-5 control-label'>
+ <FormattedMessage
+ id='user.settings.general.confirmEmail'
+ defaultMessage='Confirm Email'
+ />
+ </label>
<div className='col-sm-7'>
<input
className='form-control'
@@ -499,7 +653,12 @@ export default class UserSettingsGeneralTab extends React.Component {
key='oauthEmailInfo'
className='form-group'
>
- <div className='setting-list__hint'>{'Log in occurs through GitLab. Email cannot be updated.'}</div>
+ <div className='setting-list__hint'>
+ <FormattedMessage
+ id='user.settings.general.emailCantUpdate'
+ defaultMessage='Log in occurs through GitLab. Email cannot be updated.'
+ />
+ </div>
{helpText}
</div>
);
@@ -524,20 +683,20 @@ export default class UserSettingsGeneralTab extends React.Component {
if (this.state.emailChangeInProgress) {
const newEmail = UserStore.getCurrentUser().email;
if (newEmail) {
- describe = 'New Address: ' + newEmail + '\nCheck your email to verify the above address.';
+ describe = formatHTMLMessage(holders.newAddress, {email: newEmail});
} else {
- describe = 'Check your email to verify your new address';
+ describe = formatMessage(holders.checkEmailNoAddress);
}
} else {
describe = UserStore.getCurrentUser().email;
}
} else if (this.props.user.auth_service === Constants.GITLAB_SERVICE) {
- describe = 'Log in done through GitLab';
+ describe = formatMessage(holders.loginGitlab);
}
emailSection = (
<SettingItemMin
- title='Email'
+ title={formatMessage(holders.email)}
describe={describe}
updateSection={() => {
this.updateSection('email');
@@ -550,7 +709,7 @@ export default class UserSettingsGeneralTab extends React.Component {
if (this.props.activeSection === 'picture') {
pictureSection = (
<SettingPicture
- title='Profile Picture'
+ title={formatMessage(holders.profilePicture)}
submit={this.submitPicture}
src={'/api/v1/users/' + user.id + '/image?time=' + user.last_picture_update + '&' + Utils.getSessionIndex()}
server_error={serverError}
@@ -566,13 +725,15 @@ export default class UserSettingsGeneralTab extends React.Component {
/>
);
} else {
- let minMessage = 'Click \'Edit\' to upload an image.';
+ let minMessage = formatMessage(holders.uploadImage);
if (user.last_picture_update) {
- minMessage = 'Image last updated ' + Utils.displayDate(user.last_picture_update);
+ minMessage = formatMessage(holders.imageUpdated, {
+ date: new Date(user.last_picture_update).toLocaleDateString(global.window.mm_locale, {month: 'short', day: '2-digit', year: 'numeric'})
+ });
}
pictureSection = (
<SettingItemMin
- title='Profile Picture'
+ title={formatMessage(holders.profilePicture)}
describe={minMessage}
updateSection={() => {
this.updateSection('picture');
@@ -588,7 +749,7 @@ export default class UserSettingsGeneralTab extends React.Component {
type='button'
className='close'
data-dismiss='modal'
- aria-label='Close'
+ aria-label={formatMessage(holders.close)}
onClick={this.props.closeModal}
>
<span aria-hidden='true'>{'×'}</span>
@@ -601,11 +762,19 @@ export default class UserSettingsGeneralTab extends React.Component {
className='modal-back'
onClick={this.props.collapseModal}
/>
- {'General Settings'}
+ <FormattedMessage
+ id='user.settings.general.title'
+ defaultMessage='General Settings'
+ />
</h4>
</div>
<div className='user-settings'>
- <h3 className='tab-header'>{'General Settings'}</h3>
+ <h3 className='tab-header'>
+ <FormattedMessage
+ id='user.settings.general.title'
+ defaultMessage='General Settings'
+ />
+ </h3>
<div className='divider-dark first'/>
{nameSection}
<div className='divider-light'/>
@@ -624,6 +793,7 @@ export default class UserSettingsGeneralTab extends React.Component {
}
UserSettingsGeneralTab.propTypes = {
+ intl: intlShape.isRequired,
user: React.PropTypes.object.isRequired,
updateSection: React.PropTypes.func.isRequired,
updateTab: React.PropTypes.func.isRequired,
@@ -631,3 +801,5 @@ UserSettingsGeneralTab.propTypes = {
closeModal: React.PropTypes.func.isRequired,
collapseModal: React.PropTypes.func.isRequired
};
+
+export default injectIntl(UserSettingsGeneralTab); \ No newline at end of file
diff --git a/web/react/components/user_settings/user_settings_integrations.jsx b/web/react/components/user_settings/user_settings_integrations.jsx
index a86510eb3..abd04a301 100644
--- a/web/react/components/user_settings/user_settings_integrations.jsx
+++ b/web/react/components/user_settings/user_settings_integrations.jsx
@@ -6,7 +6,28 @@ import SettingItemMax from '../setting_item_max.jsx';
import ManageIncomingHooks from './manage_incoming_hooks.jsx';
import ManageOutgoingHooks from './manage_outgoing_hooks.jsx';
-export default class UserSettingsIntegrationsTab extends React.Component {
+import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl';
+
+const holders = defineMessages({
+ inName: {
+ id: 'user.settings.integrations.incomingWebhooks',
+ defaultMessage: 'Incoming Webhooks'
+ },
+ inDesc: {
+ id: 'user.settings.integrations.incomingWebhooksDescription',
+ defaultMessage: 'Manage your incoming webhooks'
+ },
+ outName: {
+ id: 'user.settings.integrations.outWebhooks',
+ defaultMessage: 'Outgoing Webhooks'
+ },
+ outDesc: {
+ id: 'user.settings.integrations.outWebhooksDescription',
+ defaultMessage: 'Manage your outgoing webhooks'
+ }
+});
+
+class UserSettingsIntegrationsTab extends React.Component {
constructor(props) {
super(props);
@@ -21,6 +42,7 @@ export default class UserSettingsIntegrationsTab extends React.Component {
let incomingHooksSection;
let outgoingHooksSection;
var inputs = [];
+ const {formatMessage} = this.props.intl;
if (global.window.mm_config.EnableIncomingWebhooks === 'true') {
if (this.props.activeSection === 'incoming-hooks') {
@@ -30,7 +52,7 @@ export default class UserSettingsIntegrationsTab extends React.Component {
incomingHooksSection = (
<SettingItemMax
- title='Incoming Webhooks'
+ title={formatMessage(holders.inName)}
width='medium'
inputs={inputs}
updateSection={(e) => {
@@ -42,9 +64,9 @@ export default class UserSettingsIntegrationsTab extends React.Component {
} else {
incomingHooksSection = (
<SettingItemMin
- title='Incoming Webhooks'
+ title={formatMessage(holders.inName)}
width='medium'
- describe='Manage your incoming webhooks'
+ describe={formatMessage(holders.inDesc)}
updateSection={() => {
this.updateSection('incoming-hooks');
}}
@@ -61,7 +83,7 @@ export default class UserSettingsIntegrationsTab extends React.Component {
outgoingHooksSection = (
<SettingItemMax
- title='Outgoing Webhooks'
+ title={formatMessage(holders.outName)}
width='medium'
inputs={inputs}
updateSection={(e) => {
@@ -73,9 +95,9 @@ export default class UserSettingsIntegrationsTab extends React.Component {
} else {
outgoingHooksSection = (
<SettingItemMin
- title='Outgoing Webhooks'
+ title={formatMessage(holders.outName)}
width='medium'
- describe='Manage your outgoing webhooks'
+ describe={formatMessage(holders.outDesc)}
updateSection={() => {
this.updateSection('outgoing-hooks');
}}
@@ -104,11 +126,19 @@ export default class UserSettingsIntegrationsTab extends React.Component {
className='modal-back'
onClick={this.props.collapseModal}
/>
- {'Integration Settings'}
+ <FormattedMessage
+ id='user.settings.integrations.title'
+ defaultMessage='Integration Settings'
+ />
</h4>
</div>
<div className='user-settings'>
- <h3 className='tab-header'>{'Integration Settings'}</h3>
+ <h3 className='tab-header'>
+ <FormattedMessage
+ id='user.settings.integrations.title'
+ defaultMessage='Integration Settings'
+ />
+ </h3>
<div className='divider-dark first'/>
{incomingHooksSection}
<div className='divider-light'/>
@@ -121,6 +151,7 @@ export default class UserSettingsIntegrationsTab extends React.Component {
}
UserSettingsIntegrationsTab.propTypes = {
+ intl: intlShape.isRequired,
user: React.PropTypes.object,
updateSection: React.PropTypes.func,
updateTab: React.PropTypes.func,
@@ -128,3 +159,5 @@ UserSettingsIntegrationsTab.propTypes = {
closeModal: React.PropTypes.func.isRequired,
collapseModal: React.PropTypes.func.isRequired
};
+
+export default injectIntl(UserSettingsIntegrationsTab); \ No newline at end of file
diff --git a/web/react/components/user_settings/user_settings_modal.jsx b/web/react/components/user_settings/user_settings_modal.jsx
index 36e1aa217..2a0a90cf5 100644
--- a/web/react/components/user_settings/user_settings_modal.jsx
+++ b/web/react/components/user_settings/user_settings_modal.jsx
@@ -6,7 +6,56 @@ const Modal = ReactBootstrap.Modal;
import SettingsSidebar from '../settings_sidebar.jsx';
import UserSettings from './user_settings.jsx';
-export default class UserSettingsModal extends React.Component {
+import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl';
+
+const holders = defineMessages({
+ general: {
+ id: 'user.settings.modal.general',
+ defaultMessage: 'General'
+ },
+ security: {
+ id: 'user.settings.modal.security',
+ defaultMessage: 'Security'
+ },
+ notifications: {
+ id: 'user.settings.modal.notifications',
+ defaultMessage: 'Notifications'
+ },
+ appearance: {
+ id: 'user.settings.modal.appearance',
+ defaultMessage: 'Appearance'
+ },
+ developer: {
+ id: 'user.settings.modal.developer',
+ defaultMessage: 'Developer'
+ },
+ integrations: {
+ id: 'user.settings.modal.integrations',
+ defaultMessage: 'Integrations'
+ },
+ display: {
+ id: 'user.settings.modal.display',
+ defaultMessage: 'Display'
+ },
+ advanced: {
+ id: 'user.settings.modal.advanced',
+ defaultMessage: 'Advanced'
+ },
+ confirmTitle: {
+ id: 'user.settings.modal.confirmTitle',
+ defaultMessage: 'Discard Changes?'
+ },
+ confirmMsg: {
+ id: 'user.settings.modal.confirmMsg',
+ defaultMessage: 'You have unsaved changes, are you sure you want to discard them?'
+ },
+ confirmBtns: {
+ id: 'user.settings.modal.confirmBtns',
+ defaultMessage: 'Yes, Discard'
+ }
+});
+
+class UserSettingsModal extends React.Component {
constructor(props) {
super(props);
@@ -170,20 +219,21 @@ export default class UserSettingsModal extends React.Component {
}
render() {
+ const {formatMessage} = this.props.intl;
var tabs = [];
- tabs.push({name: 'general', uiName: 'General', icon: 'glyphicon glyphicon-cog'});
- tabs.push({name: 'security', uiName: 'Security', icon: 'glyphicon glyphicon-lock'});
- tabs.push({name: 'notifications', uiName: 'Notifications', icon: 'glyphicon glyphicon-exclamation-sign'});
- tabs.push({name: 'appearance', uiName: 'Appearance', icon: 'glyphicon glyphicon-wrench'});
+ tabs.push({name: 'general', uiName: formatMessage(holders.general), icon: 'glyphicon glyphicon-cog'});
+ tabs.push({name: 'security', uiName: formatMessage(holders.security), icon: 'glyphicon glyphicon-lock'});
+ tabs.push({name: 'notifications', uiName: formatMessage(holders.notifications), icon: 'glyphicon glyphicon-exclamation-sign'});
+ tabs.push({name: 'appearance', uiName: formatMessage(holders.appearance), icon: 'glyphicon glyphicon-wrench'});
if (global.window.mm_config.EnableOAuthServiceProvider === 'true') {
- tabs.push({name: 'developer', uiName: 'Developer', icon: 'glyphicon glyphicon-th'});
+ tabs.push({name: 'developer', uiName: formatMessage(holders.developer), icon: 'glyphicon glyphicon-th'});
}
if (global.window.mm_config.EnableIncomingWebhooks === 'true' || global.window.mm_config.EnableOutgoingWebhooks === 'true') {
- tabs.push({name: 'integrations', uiName: 'Integrations', icon: 'glyphicon glyphicon-transfer'});
+ tabs.push({name: 'integrations', uiName: formatMessage(holders.integrations), icon: 'glyphicon glyphicon-transfer'});
}
- tabs.push({name: 'display', uiName: 'Display', icon: 'glyphicon glyphicon-eye-open'});
- tabs.push({name: 'advanced', uiName: 'Advanced', icon: 'glyphicon glyphicon-list-alt'});
+ tabs.push({name: 'display', uiName: formatMessage(holders.display), icon: 'glyphicon glyphicon-eye-open'});
+ tabs.push({name: 'advanced', uiName: formatMessage(holders.advanced), icon: 'glyphicon glyphicon-list-alt'});
return (
<Modal
@@ -194,7 +244,12 @@ export default class UserSettingsModal extends React.Component {
enforceFocus={this.state.enforceFocus}
>
<Modal.Header closeButton={true}>
- <Modal.Title>{'Account Settings'}</Modal.Title>
+ <Modal.Title>
+ <FormattedMessage
+ id='user.settings.modal.title'
+ defaultMessage='Account Settings'
+ />
+ </Modal.Title>
</Modal.Header>
<Modal.Body ref='modalBody'>
<div className='settings-table'>
@@ -221,9 +276,9 @@ export default class UserSettingsModal extends React.Component {
</div>
</Modal.Body>
<ConfirmModal
- title='Discard Changes?'
- message='You have unsaved changes, are you sure you want to discard them?'
- confirm_button='Yes, Discard'
+ title={formatMessage(holders.confirmTitle)}
+ message={formatMessage(holders.confirmMsg)}
+ confirm_button={formatMessage(holders.confirmBtns)}
show={this.state.showConfirmModal}
onConfirm={this.handleConfirm}
onCancel={this.handleCancelConfirmation}
@@ -234,6 +289,9 @@ export default class UserSettingsModal extends React.Component {
}
UserSettingsModal.propTypes = {
+ intl: intlShape.isRequired,
show: React.PropTypes.bool.isRequired,
onModalDismissed: React.PropTypes.func.isRequired
};
+
+export default injectIntl(UserSettingsModal); \ No newline at end of file
diff --git a/web/react/components/user_settings/user_settings_notifications.jsx b/web/react/components/user_settings/user_settings_notifications.jsx
index f762405af..91a03eb70 100644
--- a/web/react/components/user_settings/user_settings_notifications.jsx
+++ b/web/react/components/user_settings/user_settings_notifications.jsx
@@ -10,6 +10,8 @@ import * as Client from '../../utils/client.jsx';
import * as AsyncClient from '../../utils/async_client.jsx';
import * as Utils from '../../utils/utils.jsx';
+import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl';
+
function getNotificationsStateFromStores() {
var user = UserStore.getCurrentUser();
var soundNeeded = !Utils.isBrowserFirefox();
@@ -73,7 +75,30 @@ function getNotificationsStateFromStores() {
firstNameKey: firstNameKey, allKey: allKey, channelKey: channelKey};
}
-export default class NotificationsTab extends React.Component {
+const holders = defineMessages({
+ desktop: {
+ id: 'user.settings.notifications.desktop',
+ defaultMessage: 'Send desktop notifications'
+ },
+ desktopSounds: {
+ id: 'user.settings.notifications.desktopSounds',
+ defaultMessage: 'Desktop notification sounds'
+ },
+ emailNotifications: {
+ id: 'user.settings.notifications.emailNotifications',
+ defaultMessage: 'Email notifications'
+ },
+ wordsTrigger: {
+ id: 'user.settings.notifications.wordsTrigger',
+ defaultMessage: 'Words that trigger mentions'
+ },
+ close: {
+ id: 'user.settings.notifications.close',
+ defaultMessage: 'Close'
+ }
+});
+
+class NotificationsTab extends React.Component {
constructor(props) {
super(props);
@@ -198,6 +223,7 @@ export default class NotificationsTab extends React.Component {
this.updateCustomMentionKeys();
}
render() {
+ const {formatMessage} = this.props.intl;
var serverError = null;
if (this.state.serverError) {
serverError = this.state.serverError;
@@ -227,7 +253,10 @@ export default class NotificationsTab extends React.Component {
checked={notifyActive[0]}
onChange={this.handleNotifyRadio.bind(this, 'all')}
/>
- {'For all activity'}
+ <FormattedMessage
+ id='user.settings.notification.allActivity'
+ defaultMessage='For all activity'
+ />
</label>
<br/>
</div>
@@ -238,7 +267,10 @@ export default class NotificationsTab extends React.Component {
checked={notifyActive[1]}
onChange={this.handleNotifyRadio.bind(this, 'mention')}
/>
- {'Only for mentions and direct messages'}
+ <FormattedMessage
+ id='user.settings.notifications.onlyMentions'
+ defaultMessage='Only for mentions and direct messages'
+ />
</label>
<br/>
</div>
@@ -249,17 +281,27 @@ export default class NotificationsTab extends React.Component {
checked={notifyActive[2]}
onChange={this.handleNotifyRadio.bind(this, 'none')}
/>
- {'Never'}
+ <FormattedMessage
+ id='user.settings.notifications.never'
+ defaultMessage='Never'
+ />
</label>
</div>
</div>
);
- const extraInfo = <span>{'Desktop notifications are available on Firefox, Safari, and Chrome.'}</span>;
+ const extraInfo = (
+ <span>
+ <FormattedMessage
+ id='user.settings.notifications.info'
+ defaultMessage='Desktop notification sounds are available on Firefox, Safari, Chrome, Internet Explorer, and Edge.'
+ />
+ </span>
+ );
desktopSection = (
<SettingItemMax
- title='Send desktop notifications'
+ title={formatMessage(holders.desktop)}
extraInfo={extraInfo}
inputs={inputs}
submit={this.handleSubmit}
@@ -270,11 +312,26 @@ export default class NotificationsTab extends React.Component {
} else {
let describe = '';
if (this.state.notifyLevel === 'mention') {
- describe = 'Only for mentions and direct messages';
+ describe = (
+ <FormattedMessage
+ id='user.settings.notifications.onlyMentions'
+ defaultMessage='Only for mentions and direct messages'
+ />
+ );
} else if (this.state.notifyLevel === 'none') {
- describe = 'Never';
+ describe = (
+ <FormattedMessage
+ id='user.settings.notifications.never'
+ defaultMessage='Never'
+ />
+ );
} else {
- describe = 'For all activity';
+ describe = (
+ <FormattedMessage
+ id='user.settings.notification.allActivity'
+ defaultMessage='For all activity'
+ />
+ );
}
handleUpdateDesktopSection = function updateDesktopSection() {
@@ -283,7 +340,7 @@ export default class NotificationsTab extends React.Component {
desktopSection = (
<SettingItemMin
- title='Send desktop notifications'
+ title={formatMessage(holders.desktop)}
describe={describe}
updateSection={handleUpdateDesktopSection}
/>
@@ -311,7 +368,10 @@ export default class NotificationsTab extends React.Component {
checked={soundActive[0]}
onChange={this.handleSoundRadio.bind(this, 'true')}
/>
- {'On'}
+ <FormattedMessage
+ id='user.settings.notifications.on'
+ defaultMessage='On'
+ />
</label>
<br/>
</div>
@@ -322,18 +382,28 @@ export default class NotificationsTab extends React.Component {
checked={soundActive[1]}
onChange={this.handleSoundRadio.bind(this, 'false')}
/>
- {'Off'}
+ <FormattedMessage
+ id='user.settings.notifications.off'
+ defaultMessage='Off'
+ />
</label>
<br/>
</div>
</div>
);
- const extraInfo = <span>{'Desktop notification sounds are available on Firefox, Safari, Chrome, Internet Explorer, and Edge.'}</span>;
+ const extraInfo = (
+ <span>
+ <FormattedMessage
+ id='user.settings.notifications.info'
+ defaultMessage='Desktop notification sounds are available on Firefox, Safari, Chrome, Internet Explorer, and Edge.'
+ />
+ </span>
+ );
soundSection = (
<SettingItemMax
- title='Desktop notification sounds'
+ title={formatMessage(holders.desktopSounds)}
extraInfo={extraInfo}
inputs={inputs}
submit={this.handleSubmit}
@@ -344,11 +414,26 @@ export default class NotificationsTab extends React.Component {
} else {
let describe = '';
if (!this.state.soundNeeded) {
- describe = 'Please configure notification sounds in your browser settings';
+ describe = (
+ <FormattedMessage
+ id='user.settings.notification.soundConfig'
+ defaultMessage='Please configure notification sounds in your browser settings'
+ />
+ );
} else if (this.state.enableSound === 'false') {
- describe = 'Off';
+ describe = (
+ <FormattedMessage
+ id='user.settings.notifications.off'
+ defaultMessage='Off'
+ />
+ );
} else {
- describe = 'On';
+ describe = (
+ <FormattedMessage
+ id='user.settings.notifications.on'
+ defaultMessage='On'
+ />
+ );
}
handleUpdateSoundSection = function updateSoundSection() {
@@ -357,7 +442,7 @@ export default class NotificationsTab extends React.Component {
soundSection = (
<SettingItemMin
- title='Desktop notification sounds'
+ title={formatMessage(holders.desktopSounds)}
describe={describe}
updateSection={handleUpdateSoundSection}
disableOpen = {!this.state.soundNeeded}
@@ -386,7 +471,10 @@ export default class NotificationsTab extends React.Component {
checked={emailActive[0]}
onChange={this.handleEmailRadio.bind(this, 'true')}
/>
- {'On'}
+ <FormattedMessage
+ id='user.settings.notifications.on'
+ defaultMessage='On'
+ />
</label>
<br/>
</div>
@@ -397,17 +485,28 @@ export default class NotificationsTab extends React.Component {
checked={emailActive[1]}
onChange={this.handleEmailRadio.bind(this, 'false')}
/>
- {'Off'}
+ <FormattedMessage
+ id='user.settings.notifications.off'
+ defaultMessage='Off'
+ />
</label>
<br/>
</div>
- <div><br/>{'Email notifications are sent for mentions and direct messages after you’ve been offline for more than 60 seconds or away from ' + global.window.mm_config.SiteName + ' for more than 5 minutes.'}</div>
+ <div><br/>
+ <FormattedMessage
+ id='user.settings.notifications.emailInfo'
+ defaultMessage='Email notifications are sent for mentions and direct messages after you’ve been offline for more than 60 seconds or away from {siteName} for more than 5 minutes.'
+ values={{
+ siteName: global.window.mm_config.SiteName
+ }}
+ />
+ </div>
</div>
);
emailSection = (
<SettingItemMax
- title='Email notifications'
+ title={formatMessage(holders.emailNotifications)}
inputs={inputs}
submit={this.handleSubmit}
server_error={serverError}
@@ -417,9 +516,19 @@ export default class NotificationsTab extends React.Component {
} else {
let describe = '';
if (this.state.enableEmail === 'false') {
- describe = 'Off';
+ describe = (
+ <FormattedMessage
+ id='user.settings.notifications.off'
+ defaultMessage='Off'
+ />
+ );
} else {
- describe = 'On';
+ describe = (
+ <FormattedMessage
+ id='user.settings.notifications.on'
+ defaultMessage='On'
+ />
+ );
}
handleUpdateEmailSection = function updateEmailSection() {
@@ -428,7 +537,7 @@ export default class NotificationsTab extends React.Component {
emailSection = (
<SettingItemMin
- title='Email notifications'
+ title={formatMessage(holders.emailNotifications)}
describe={describe}
updateSection={handleUpdateEmailSection}
/>
@@ -459,7 +568,13 @@ export default class NotificationsTab extends React.Component {
checked={this.state.firstNameKey}
onChange={handleUpdateFirstNameKey}
/>
- {'Your case sensitive first name "' + user.first_name + '"'}
+ <FormattedMessage
+ id='user.settings.notifications.sensitiveName'
+ defaultMessage='Your case sensitive first name "{first_name}"'
+ values={{
+ first_name: user.first_name
+ }}
+ />
</label>
</div>
</div>
@@ -478,7 +593,13 @@ export default class NotificationsTab extends React.Component {
checked={this.state.usernameKey}
onChange={handleUpdateUsernameKey}
/>
- {'Your non-case sensitive username "' + user.username + '"'}
+ <FormattedMessage
+ id='user.settings.notifications.sensitiveUsername'
+ defaultMessage='Your non-case sensitive username "{username}"'
+ values={{
+ username: user.username
+ }}
+ />
</label>
</div>
</div>
@@ -496,7 +617,13 @@ export default class NotificationsTab extends React.Component {
checked={this.state.mentionKey}
onChange={handleUpdateMentionKey}
/>
- {'Your username mentioned "@' + user.username + '"'}
+ <FormattedMessage
+ id='user.settings.notifications.usernameMention'
+ defaultMessage='Your username mentioned "@{username}"'
+ values={{
+ username: user.username
+ }}
+ />
</label>
</div>
</div>
@@ -514,7 +641,10 @@ export default class NotificationsTab extends React.Component {
checked={this.state.allKey}
onChange={handleUpdateAllKey}
/>
- {'Team-wide mentions "@all"'}
+ <FormattedMessage
+ id='user.settings.notifications.teamWide'
+ defaultMessage='Team-wide mentions "@all"'
+ />
</label>
</div>
</div>
@@ -532,7 +662,10 @@ export default class NotificationsTab extends React.Component {
checked={this.state.channelKey}
onChange={handleUpdateChannelKey}
/>
- {'Channel-wide mentions "@channel"'}
+ <FormattedMessage
+ id='user.settings.notifications.channelWide'
+ defaultMessage='Channel-wide mentions "@channel"'
+ />
</label>
</div>
</div>
@@ -548,7 +681,10 @@ export default class NotificationsTab extends React.Component {
checked={this.state.customKeysChecked}
onChange={this.updateCustomMentionKeys}
/>
- {'Other non-case sensitive words, separated by commas:'}
+ <FormattedMessage
+ id='user.settings.notifications.sensitiveWords'
+ defaultMessage='Other non-case sensitive words, separated by commas:'
+ />
</label>
</div>
<input
@@ -563,7 +699,7 @@ export default class NotificationsTab extends React.Component {
keysSection = (
<SettingItemMax
- title='Words that trigger mentions'
+ title={formatMessage(holders.wordsTrigger)}
inputs={inputs}
submit={this.handleSubmit}
server_error={serverError}
@@ -601,7 +737,12 @@ export default class NotificationsTab extends React.Component {
if (describe.length > 0) {
describe = describe.substring(0, describe.length - 2);
} else {
- describe = 'No words configured';
+ describe = (
+ <FormattedMessage
+ id='user.settings.notifications.noWords'
+ defaultMessage='No words configured'
+ />
+ );
}
handleUpdateKeysSection = function updateKeysSection() {
@@ -610,7 +751,7 @@ export default class NotificationsTab extends React.Component {
keysSection = (
<SettingItemMin
- title='Words that trigger mentions'
+ title={formatMessage(holders.wordsTrigger)}
describe={describe}
updateSection={handleUpdateKeysSection}
/>
@@ -624,7 +765,7 @@ export default class NotificationsTab extends React.Component {
type='button'
className='close'
data-dismiss='modal'
- aria-label='Close'
+ aria-label={formatMessage(holders.close)}
onClick={this.props.closeModal}
>
<span aria-hidden='true'>{'×'}</span>
@@ -637,14 +778,22 @@ export default class NotificationsTab extends React.Component {
className='modal-back'
onClick={this.props.collapseModal}
/>
- {'Notification Settings'}
+ <FormattedMessage
+ id='user.settings.notifications.title'
+ defaultMessage='Notification Settings'
+ />
</h4>
</div>
<div
ref='wrapper'
className='user-settings'
>
- <h3 className='tab-header'>{'Notifications'}</h3>
+ <h3 className='tab-header'>
+ <FormattedMessage
+ id='user.settings.notifications.header'
+ defaultMessage='Notifications'
+ />
+ </h3>
<div className='divider-dark first'/>
{desktopSection}
<div className='divider-light'/>
@@ -667,6 +816,7 @@ NotificationsTab.defaultProps = {
activeTab: ''
};
NotificationsTab.propTypes = {
+ intl: intlShape.isRequired,
user: React.PropTypes.object,
updateSection: React.PropTypes.func,
updateTab: React.PropTypes.func,
@@ -675,3 +825,5 @@ NotificationsTab.propTypes = {
closeModal: React.PropTypes.func.isRequired,
collapseModal: React.PropTypes.func.isRequired
};
+
+export default injectIntl(NotificationsTab); \ No newline at end of file
diff --git a/web/react/components/user_settings/user_settings_security.jsx b/web/react/components/user_settings/user_settings_security.jsx
index 5a21abd19..0c9e722de 100644
--- a/web/react/components/user_settings/user_settings_security.jsx
+++ b/web/react/components/user_settings/user_settings_security.jsx
@@ -13,7 +13,40 @@ import * as Client from '../../utils/client.jsx';
import * as AsyncClient from '../../utils/async_client.jsx';
import Constants from '../../utils/constants.jsx';
-export default class SecurityTab extends React.Component {
+import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl';
+
+const holders = defineMessages({
+ currentPasswordError: {
+ id: 'user.settings.security.currentPasswordError',
+ defaultMessage: 'Please enter your current password'
+ },
+ passwordLengthError: {
+ id: 'user.settings.security.passwordLengthError',
+ defaultMessage: 'New passwords must be at least {chars} characters'
+ },
+ passwordMatchError: {
+ id: 'user.settings.security.passwordMatchError',
+ defaultMessage: 'The new passwords you entered do not match'
+ },
+ password: {
+ id: 'user.settings.security.password',
+ defaultMessage: 'Password'
+ },
+ lastUpdated: {
+ id: 'user.settings.security.lastUpdated',
+ defaultMessage: 'Last updated {date} at {time}'
+ },
+ method: {
+ id: 'user.settings.security.method',
+ defaultMessage: 'Sign-in Method'
+ },
+ close: {
+ id: 'user.settings.security.close',
+ defaultMessage: 'Close'
+ }
+});
+
+class SecurityTab extends React.Component {
constructor(props) {
super(props);
@@ -43,18 +76,19 @@ export default class SecurityTab extends React.Component {
var newPassword = this.state.newPassword;
var confirmPassword = this.state.confirmPassword;
+ const {formatMessage} = this.props.intl;
if (currentPassword === '') {
- this.setState({passwordError: 'Please enter your current password', serverError: ''});
+ this.setState({passwordError: formatMessage(holders.currentPasswordError), serverError: ''});
return;
}
if (newPassword.length < Constants.MIN_PASSWORD_LENGTH) {
- this.setState({passwordError: 'New passwords must be at least ' + Constants.MIN_PASSWORD_LENGTH + ' characters', serverError: ''});
+ this.setState({passwordError: formatMessage(holders.passwordLengthError, {chars: Constants.MIN_PASSWORD_LENGTH}), serverError: ''});
return;
}
if (newPassword !== confirmPassword) {
- this.setState({passwordError: 'The new passwords you entered do not match', serverError: ''});
+ this.setState({passwordError: formatMessage(holders.passwordMatchError), serverError: ''});
return;
}
@@ -92,6 +126,7 @@ export default class SecurityTab extends React.Component {
}
createPasswordSection() {
let updateSectionStatus;
+ const {formatMessage} = this.props.intl;
if (this.props.activeSection === 'password' && this.props.user.auth_service === '') {
const inputs = [];
@@ -101,7 +136,12 @@ export default class SecurityTab extends React.Component {
key='currentPasswordUpdateForm'
className='form-group'
>
- <label className='col-sm-5 control-label'>{'Current Password'}</label>
+ <label className='col-sm-5 control-label'>
+ <FormattedMessage
+ id='user.settings.security.currentPassword'
+ defaultMessage='Current Password'
+ />
+ </label>
<div className='col-sm-7'>
<input
className='form-control'
@@ -117,7 +157,12 @@ export default class SecurityTab extends React.Component {
key='newPasswordUpdateForm'
className='form-group'
>
- <label className='col-sm-5 control-label'>{'New Password'}</label>
+ <label className='col-sm-5 control-label'>
+ <FormattedMessage
+ id='user.settings.security.newPassword'
+ defaultMessage='New Password'
+ />
+ </label>
<div className='col-sm-7'>
<input
className='form-control'
@@ -133,7 +178,12 @@ export default class SecurityTab extends React.Component {
key='retypeNewPasswordUpdateForm'
className='form-group'
>
- <label className='col-sm-5 control-label'>{'Retype New Password'}</label>
+ <label className='col-sm-5 control-label'>
+ <FormattedMessage
+ id='user.settings.security.retypePassword'
+ defaultMessage='Retype New Password'
+ />
+ </label>
<div className='col-sm-7'>
<input
className='form-control'
@@ -153,7 +203,7 @@ export default class SecurityTab extends React.Component {
return (
<SettingItemMax
- title='Password'
+ title={formatMessage(holders.password)}
inputs={inputs}
submit={this.submitPassword}
server_error={this.state.serverError}
@@ -165,20 +215,16 @@ export default class SecurityTab extends React.Component {
var describe;
var d = new Date(this.props.user.last_password_update);
- var hour = '12';
- if (d.getHours() % 12) {
- hour = String(d.getHours() % 12);
- }
- var min = String(d.getMinutes());
- if (d.getMinutes() < 10) {
- min = '0' + d.getMinutes();
- }
var timeOfDay = ' am';
if (d.getHours() >= 12) {
timeOfDay = ' pm';
}
- describe = 'Last updated ' + Constants.MONTHS[d.getMonth()] + ' ' + d.getDate() + ', ' + d.getFullYear() + ' at ' + hour + ':' + min + timeOfDay;
+ const locale = global.window.mm_locale;
+ describe = formatMessage(holders.lastUpdated, {
+ date: d.toLocaleDateString(locale, {month: 'short', day: '2-digit', year: 'numeric'}),
+ time: d.toLocaleTimeString(locale, {hours12: true, hour: '2-digit', minute: '2-digit'}) + timeOfDay
+ });
updateSectionStatus = function updateSection() {
this.props.updateSection('password');
@@ -186,7 +232,7 @@ export default class SecurityTab extends React.Component {
return (
<SettingItemMin
- title='Password'
+ title={formatMessage(holders.password)}
describe={describe}
updateSection={updateSectionStatus}
/>
@@ -208,7 +254,10 @@ export default class SecurityTab extends React.Component {
className='btn btn-primary'
href={'/' + teamName + '/claim?email=' + encodeURIComponent(user.email)}
>
- {'Switch to using email and password'}
+ <FormattedMessage
+ id='user.settings.security.switchEmail'
+ defaultMessage='Switch to using email and password'
+ />
</a>
<br/>
</div>
@@ -223,7 +272,10 @@ export default class SecurityTab extends React.Component {
className='btn btn-primary'
href={'/' + teamName + '/claim?email=' + encodeURIComponent(user.email) + '&new_type=' + Constants.GITLAB_SERVICE}
>
- {'Switch to using GitLab SSO'}
+ <FormattedMessage
+ id='user.settings.security.switchGitlab'
+ defaultMessage='Switch to using GitLab SSO'
+ />
</a>
<br/>
</div>
@@ -238,7 +290,10 @@ export default class SecurityTab extends React.Component {
className='btn btn-primary'
href={'/' + teamName + '/claim?email=' + encodeURIComponent(user.email) + '&new_type=' + Constants.GOOGLE_SERVICE}
>
- {'Switch to using Google SSO'}
+ <FormattedMessage
+ id='user.settings.security.switchGoogle'
+ defaultMessage='Switch to using Google SSO'
+ />
</a>
<br/>
</div>
@@ -260,11 +315,18 @@ export default class SecurityTab extends React.Component {
e.preventDefault();
}.bind(this);
- const extraInfo = <span>{'You may only have one sign-in method at a time. Switching sign-in method will send an email notifying you if the change was successful.'}</span>;
+ const extraInfo = (
+ <span>
+ <FormattedMessage
+ id='user.settings.security.oneSignin'
+ defaultMessage='You may only have one sign-in method at a time. Switching sign-in method will send an email notifying you if the change was successful.'
+ />
+ </span>
+ );
return (
<SettingItemMax
- title='Sign-in Method'
+ title={this.props.intl.formatMessage(holders.method)}
extraInfo={extraInfo}
inputs={inputs}
server_error={this.state.serverError}
@@ -277,14 +339,24 @@ export default class SecurityTab extends React.Component {
this.props.updateSection('signin');
}.bind(this);
- let describe = 'Email and Password';
+ let describe = (
+ <FormattedMessage
+ id='user.settings.security.emailPwd'
+ defaultMessage='Email and Password'
+ />
+ );
if (this.props.user.auth_service === Constants.GITLAB_SERVICE) {
- describe = 'GitLab SSO';
+ describe = (
+ <FormattedMessage
+ id='user.settings.security.gitlab'
+ defaultMessage='GitLab SSO'
+ />
+ );
}
return (
<SettingItemMin
- title='Sign-in Method'
+ title={this.props.intl.formatMessage(holders.method)}
describe={describe}
updateSection={updateSectionStatus}
/>
@@ -309,7 +381,7 @@ export default class SecurityTab extends React.Component {
type='button'
className='close'
data-dismiss='modal'
- aria-label='Close'
+ aria-label={this.props.intl.formatMessage(holders.close)}
onClick={this.props.closeModal}
>
<span aria-hidden='true'>{'×'}</span>
@@ -322,11 +394,19 @@ export default class SecurityTab extends React.Component {
className='modal-back'
onClick={this.props.collapseModal}
/>
- {'Security Settings'}
+ <FormattedMessage
+ id='user.settings.security.title'
+ defaultMessage='Security Settings'
+ />
</h4>
</div>
<div className='user-settings'>
- <h3 className='tab-header'>{'Security Settings'}</h3>
+ <h3 className='tab-header'>
+ <FormattedMessage
+ id='user.settings.security.title'
+ defaultMessage='Security Settings'
+ />
+ </h3>
<div className='divider-dark first'/>
{passwordSection}
<div className='divider-light'/>
@@ -337,14 +417,22 @@ export default class SecurityTab extends React.Component {
className='security-links theme'
dialogType={AccessHistoryModal}
>
- <i className='fa fa-clock-o'></i>{'View Access History'}
+ <i className='fa fa-clock-o'></i>
+ <FormattedMessage
+ id='user.settings.security.viewHistory'
+ defaultMessage='View Access History'
+ />
</ToggleModalButton>
<b> </b>
<ToggleModalButton
className='security-links theme'
dialogType={ActivityLogModal}
>
- <i className='fa fa-clock-o'></i>{'View and Logout of Active Sessions'}
+ <i className='fa fa-clock-o'></i>
+ <FormattedMessage
+ id='user.settings.security.logoutActiveSessions'
+ defaultMessage='View and Logout of Active Sessions'
+ />
</ToggleModalButton>
</div>
</div>
@@ -357,6 +445,7 @@ SecurityTab.defaultProps = {
activeSection: ''
};
SecurityTab.propTypes = {
+ intl: intlShape.isRequired,
user: React.PropTypes.object,
activeSection: React.PropTypes.string,
updateSection: React.PropTypes.func,
@@ -365,3 +454,5 @@ SecurityTab.propTypes = {
collapseModal: React.PropTypes.func.isRequired,
setEnforceFocus: React.PropTypes.func.isRequired
};
+
+export default injectIntl(SecurityTab); \ No newline at end of file
diff --git a/web/react/package.json b/web/react/package.json
index f36e55b40..fce3e6555 100644
--- a/web/react/package.json
+++ b/web/react/package.json
@@ -13,18 +13,18 @@
"twemoji": "1.4.1"
},
"devDependencies": {
- "browserify": "12.0.1",
- "babelify": "7.2.0",
+ "babel-eslint": "4.1.7",
+ "babel-plugin-transform-runtime": "6.1.4",
"babel-preset-es2015": "6.1.18",
- "babel-preset-stage-0": "6.1.18",
"babel-preset-react": "6.1.18",
- "babel-plugin-transform-runtime": "6.1.4",
- "uglify-js": "2.6.1",
- "watchify": "3.6.1",
+ "babel-preset-stage-0": "6.1.18",
+ "babelify": "7.2.0",
+ "browserify": "12.0.1",
"eslint": "1.9.0",
"eslint-plugin-react": "3.9.0",
"exorcist": "0.4.0",
- "babel-eslint": "4.1.5"
+ "uglify-js": "2.6.1",
+ "watchify": "3.6.1"
},
"scripts": {
"check": "",
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/sass-files/sass/partials/_post.scss b/web/sass-files/sass/partials/_post.scss
index 1f7a55cd0..be85ef07b 100644
--- a/web/sass-files/sass/partials/_post.scss
+++ b/web/sass-files/sass/partials/_post.scss
@@ -597,7 +597,7 @@ body.ios {
position: absolute;
right: 0;
top: 30px;
- width: 85px;
+ width: 65px;
white-space: nowrap;
}
@@ -666,7 +666,7 @@ body.ios {
word-wrap: break-word;
padding: 0.2em 0.5em 0em;
@include legacy-pie-clearfix;
- width: calc(100% - 95px);
+ width: calc(100% - 75px);
p {
margin: 0 0 0.4em;
diff --git a/web/sass-files/sass/partials/_responsive.scss b/web/sass-files/sass/partials/_responsive.scss
index a37974c7a..832481cc5 100644
--- a/web/sass-files/sass/partials/_responsive.scss
+++ b/web/sass-files/sass/partials/_responsive.scss
@@ -72,23 +72,19 @@
}
- .post {
-
- .post-list__content & {
+}
- &:hover {
- background: transparent;
- }
+@media screen and (max-width: 768px) {
- .comment-icon__container {
- visibility: hidden;
- }
+ .signup-team__container {
+ font-size: 1em;
+ }
- }
+ .date-separator.hovered--after:before, .new-separator.hovered--after:before {
+ background: none !important;
+ }
- .dropdown, .post__reply {
- visibility: visible;
- }
+ .post {
.post__dropdown {
line-height: 9px;
@@ -106,17 +102,21 @@
}
- }
+ .post-list__content & {
-}
+ &:hover {
+ background: transparent;
+ }
-@media screen and (max-width: 768px) {
+ .comment-icon__container {
+ visibility: visible;
+ }
- .signup-team__container {
- font-size: 1em;
- }
+ }
- .post {
+ .dropdown, .post__reply {
+ visibility: visible;
+ }
.post__body {
width: calc(100% - 75px);
diff --git a/web/static/i18n/en.json b/web/static/i18n/en.json
index 45000a890..de955349e 100644
--- a/web/static/i18n/en.json
+++ b/web/static/i18n/en.json
@@ -1,4 +1,73 @@
{
+ "about.teamEdtion": "Team Edition",
+ "about.enterpriseEdition": "Enterprise Edition",
+ "about.licensed": "Licensed by:",
+ "about.title": "About Mattermost",
+ "about.version": "Version:",
+ "about.number": "Build Number:",
+ "about.date": "Build Date:",
+ "about.hash": "Build Hash:",
+ "about.close": "Close",
+ "access_history.sessionRevoked": "The session with id {sessionId} was revoked",
+ "access_history.channelCreated": "Created the {channelName} channel/group",
+ "access_history.establishedDM": "Established a direct message channel with {username}",
+ "access_history.nameUpdated": "Updated the {channelName} channel/group name",
+ "access_history.headerUpdated": "Updated the {channelName} channel/group header",
+ "access_history.channelDeleted": "Deleted the channel/group with the URL {url}",
+ "access_history.userAdded": "Added {username} to the {channelName} channel/group",
+ "access_history.userRemoved": "Removed {username} to the {channelName} channel/group",
+ "access_history.attemptedRegisterApp": "Attempted to register a new OAuth Application with ID {id}",
+ "access_history.attemptedAllowOAuthAccess": "Attempted to allow a new OAuth service access",
+ "access_history.successfullOAuthAccess": "Successfully gave a new OAuth service access",
+ "access_history.failedOAuthAccess": "Failed to allow a new OAuth service access - the redirect URI did not match the previously registered callback",
+ "access_history.attemptedOAuthToken": "Attempted to get an OAuth access token",
+ "access_history.successfullOAuthToken": "Successfully added a new OAuth service",
+ "access_history.oauthTokenFailed": "Failed to get an OAuth access token - {token}",
+ "access_history.attemptedLogin": "Attempted to login",
+ "access_history.successfullLogin": "Successfully logged in",
+ "access_history.failedLogin": "FAILED login attempt",
+ "access_history.updatePicture": "Updated your profile picture",
+ "access_history.updateGeneral": "Updated the general settings of your account",
+ "access_history.attemptedPassword": "Attempted to change password",
+ "access_history.successfullPassword": "Successfully changed password",
+ "access_history.failedPassword": "Failed to change password - tried to update user password who was logged in through oauth",
+ "access_history.updatedRol": "Updated user role(s) to ",
+ "access_history.member": "member",
+ "access_history.accountActive": "Account made active",
+ "access_history.accountInactive": "Account made inactive",
+ "access_history.by": " by {username}",
+ "access_history.byAdmin": " by an admin",
+ "access_history.sentEmail": "Sent an email to {email} to reset your password",
+ "access_history.attemptedReset": "Attempted to reset password",
+ "access_history.successfullReset": "Successfully reset password",
+ "access_history.updateGlobalNotifications": "Updated your global notification settings",
+ "access_history.attemptedWebhookCreate": "Attempted to create a webhook",
+ "access_history.successfullWebhookCreate": "Successfully created a webhook",
+ "access_history.failedWebhookCreate": "Failed to create a webhook - bad channel permissions",
+ "access_history.attemptedWebhookDelete": "Attempted to delete a webhook",
+ "access_history.successfullWebhookDelete": "Successfully deleted a webhook",
+ "access_history.failedWebhookDelete": "Failed to delete a webhook - inappropriate conditions",
+ "access_history.logout": "Logged out of your account",
+ "access_history.verified": "Sucessfully verified your email address",
+ "access_history.revokedAll": "Revoked all current sessions for the team",
+ "access_history.loginAttempt": " (Login attempt)",
+ "access_history.loginFailure": " (Login failure)",
+ "access_history.moreInfo": "More info",
+ "access_history.ip": "IP: {ip}",
+ "access_history.session": "Session ID: {id}",
+ "access_history.title": "Access History",
+ "activity_log_modal.iphoneNativeApp": "iPhone Native App",
+ "activity_log_modal.androidNativeApp": "Android Native App",
+ "activity_log_modal.android": "Android",
+ "activity_log.firstTime": "First time active: {date}, {time}",
+ "activity_log.os": "OS: {os}",
+ "activity_log.browser": "Browser: {browser}",
+ "activity_log.sessionId": "Session ID: {id}",
+ "activity_log.moreInfo": "More info",
+ "activity_log.lastActivity": "Last activity: {date}, {time}",
+ "activity_log.logout": "Logout",
+ "activity_log.activeSessions": "Active Sessions",
+ "activity_log.sessionsDescription": "Sessions are created when you log in with your email and password to a new browser on a device. Sessions let you use Mattermost for up to 30 days without having to log in again. If you want to log out sooner, use the 'Logout' button below to end a session.",
"admin.nav.switch": "Switch to {display_name}",
"admin.nav.logout": "Logout",
"admin.nav.help": "Help",
@@ -58,6 +127,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: ",
@@ -386,6 +459,10 @@
"admin.team.save": "Save",
"admin.userList.title": "Users for {team}",
"admin.userList.title2": "Users for {team} ({count})",
+ "admin.user_item.confirmDemoteRoleTitle": "Confirm demotion from System Admin role",
+ "admin.user_item.confirmDemotion": "Confirm Demotion",
+ "admin.user_item.confirmDemoteDescription": "If you demote yourself from the System Admin role and there is not another user with System Admin privileges, you'll need to re-assign a System Admin by accessing the Mattermost server through a terminal and running the following command.",
+ "admin.user_item.confirmDemotionCmd": "platform -assign_role -team_name=\"yourteam\" -email=\"name@yourcompany.com\" -role=\"system_admin\"",
"admin.user_item.member": "Member",
"admin.user_item.sysAdmin": "System Admin",
"admin.user_item.teamAdmin": "Team Admin",
@@ -401,6 +478,12 @@
"authorize.access": "Allow <strong>{appName}</strong> access?",
"authorize.deny": "Deny",
"authorize.allow": "Allow",
+ "change_url.longer": "Must be longer than two characters",
+ "change_url.startWithLetter": "Must start with a letter or number",
+ "change_url.endWithLetter": "Must end with a letter or number",
+ "change_url.noUnderscore": "Can not contain two underscores in a row.",
+ "change_url.invalidUrl": "Invalid URL",
+ "change_url.close": "Close",
"claim.account.noEmail": "No email specified",
"claim.email_to_sso.pwdError": "Please enter your password.",
"claim.email_to_sso.pwd": "Password",
@@ -416,6 +499,13 @@
"claim.sso_to_email.description": "Upon changing your account type, you will only be able to login with your email and password.",
"claim.sso_to_email_newPwd": "Enter a new password for your {team} {site} account",
"claim.sso_to_email.switchTo": "Switch {type} to email and password",
+ "confirm_modal.cancel": "Cancel",
+ "create_comment.commentLength": "Comment length must be less than {max} characters.",
+ "create_comment.comment": "Add Comment",
+ "create_comment.addComment": "Add a comment...",
+ "create_comment.commentTitle": "Comment",
+ "create_comment.file": "File uploading",
+ "create_comment.files": "Files uploading",
"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",
@@ -423,6 +513,11 @@
"email_verify.resend": "Resend Email",
"email_verify.sent": " Verification email sent.",
"error_bar.preview_mode": "Preview Mode: Email notifications have not been configured",
+ "upload_overlay.info": "Drop a file to upload it.",
+ "file_upload.limited": "Uploads limited to {count} files maximum. Please use additional posts for more files.",
+ "file_upload.filesAbove": "Files above {max}MB could not be uploaded: {filenames}",
+ "file_upload.fileAbove": "File above {max}MB could not be uploaded: {filename}",
+ "file_upload.pasted": "Image Pasted at ",
"find_team.submitError": "Please enter a valid email address",
"find_team.placeholder": "you@domain.com",
"find_team.findTitle": "Find Your Team",
@@ -430,6 +525,29 @@
"find_team.getLinks": "Get an email with links to any teams to which you are a member.",
"find_team.email": "Email",
"find_team.send": "Send",
+ "get_link.copy": "Copy Link",
+ "get_link.clipboard": " Link copied to clipboard.",
+ "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",
+ "invite_member.modalTitle": "Discard Invitations?",
+ "invite_member.modalMessage": "You have unsent invitations, are you sure you want to discard them?",
+ "invite_member.modalButton": "Yes, Discard",
+ "invite_member.addAnother": "Add another",
+ "invite_member.autoJoin": "People invited automatically join the <strong>{channel}</strong> channel.",
+ "invite_member.send": "Send Invitation",
+ "invite_member.sending": " Sending",
+ "invite_member.send2": "Send Invitations",
+ "invite_member.inviteLink": "Team Invite Link",
+ "invite_member.teamInviteLink": "You can also invite people using the {link}.",
+ "invite_member.content": "Email is currently disabled for your team, and email invitations cannot be sent. Contact your system administrator to enable email and email invitations.",
+ "invite_member.disabled": "User creation has been disabled for your team. Please ask your team administrator for details.",
+ "invite_member.newMember": "Invite New Member",
+ "invite_member.cancel": "Cancel",
"loading_screen.loading": "Loading",
"login_email.badTeam": "Bad team name",
"login_email.emailReq": "An email is required",
@@ -437,6 +555,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",
@@ -455,6 +581,67 @@
"login.find": "Find your other teams",
"login.signTo": "Sign in to:",
"login.on": "on {siteName}",
+ "member_team_item.member": "Member",
+ "member_team_item.systemAdmin": "System Admin",
+ "member_team_item.teamAdmin": "Team Admin",
+ "member_team_item.inactive": "Inactive",
+ "member_team_item.makeAdmin": "Make Team Admin",
+ "member_team_item.makeMember": "Make Member",
+ "member_team_item.makeActive": "Make Active",
+ "member_team_item.makeInactive": "Make Inactive",
+ "more_channels.join": "Join",
+ "more_channels.noMore": "No more channels to join",
+ "more_channels.createClick": "Click 'Create New Channel' to make a new one",
+ "more_channels.close": "Close",
+ "more_channels.title": "More Channels",
+ "more_channels.create": "Create New Channel",
+ "more_direct_channels.member": "Member",
+ "more_direct_channels.search": "Search members",
+ "more_direct_channels.message": "Message",
+ "more_direct_channels.notFound": "No users found :(",
+ "more_direct_channels.count": "{count} {member}",
+ "more_direct_channels.countTotal": "{count} {member} of {total} Total",
+ "more_direct_channels.title": "Direct Messages",
+ "more_direct_channels.close": "Close",
+ "msg_typing.someone": "Someone",
+ "msg_typing.isTyping": "{user} is typing...",
+ "msg_typing.areTyping": "{users} and {last} are typing...",
+ "navbar_dropdown.inviteMember": "Invite New Member",
+ "navbar_dropdown.teamLink": "Get Team Invite Link",
+ "navbar_dropdown.manageMembers": "Manage Members",
+ "navbar_dropdown.teamSettings": "Team Settings",
+ "navbar_dropdown.console": "System Console",
+ "navbar_dropdown.switchTeam": "Switch to {team}",
+ "navbar_dropdown.create": "Create a New Team",
+ "navbar_dropdown.help": "Help",
+ "navbar_dropdown.report": "Report a Problem",
+ "navbar_dropdown.accountSettings": "Account Settings",
+ "navbar_dropdown.logout": "Logout",
+ "navbar_dropdown.about": "About Mattermost",
+ "channel_flow.invalidName": "Invalid Channel Name",
+ "channel_flow.alreadyExist": "A channel with that URL already exists",
+ "channel_flow.channel": "Channel",
+ "channel_flow.group": "Group",
+ "channel_flow.changeUrlTitle": "Change {term} URL",
+ "channel_flow.set_url_title": "Set {term} URL",
+ "channel_flow.create": "Create {term}",
+ "channel_flow.changeUrlDescription": "Some characters are not allowed in URLs and may be removed.",
+ "channel_modal.nameEx": "E.g.: \"Bugs\", \"Marketing\", \"办公室恋情\"",
+ "channel_modal.displayNameError": "This field is required",
+ "channel_modal.group": "Group",
+ "channel_modal.privateGroup1": "Create a new private group with restricted membership. ",
+ "channel_modal.publicChannel1": "Create a public channel",
+ "channel_modal.channel": "Channel",
+ "channel_modal.publicChannel2": "Create a new public channel anyone can join. ",
+ "channel_modal.privateGroup2": "Create a private group",
+ "channel_modal.modalTitle": "New ",
+ "channel_modal.name": "Name",
+ "channel_modal.edit": "Edit",
+ "channel_modal.purpose": "Purpose",
+ "channel_modal.optional": "(optional)",
+ "channel_modal.descriptionHelp": "Describe how this {term} should be used.",
+ "channel_modal.cancel": "Cancel",
+ "channel_modal.createNew": "Create New ",
"password_form.error": "Please enter at least {chars} characters.",
"password_form.update": "Your password has been updated successfully.",
"password_form.pwd": "Password",
@@ -469,6 +656,76 @@
"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",
+ "register_app.required": "Required",
+ "register_app.optional": "Optional",
+ "register_app.nameError": "Application name must be filled in.",
+ "register_app.homepageError": "Homepage must be filled in.",
+ "register_app.callbackError": "At least one callback URL must be filled in.",
+ "register_app.title": "Register a New Application",
+ "register_app.name": "Application Name",
+ "register_app.homepage": "Homepage URL",
+ "register_app.description": "Description",
+ "register_app.callback": "Callback URL",
+ "register_app.cancel": "Cancel",
+ "register_app.register": "Register",
+ "register_app.credentialsTitle": "Your Application Credentials",
+ "register_app.clientId": "Client ID",
+ "register_app.clientSecret": "Client Secret",
+ "register_app.credentialsDescription": "Save these somewhere SAFE and SECURE. Treat your Client ID as your app's username and your Client Secret as the app's password.",
+ "register_app.credentialsSave": "I have saved both my Client Id and Client Secret somewhere safe",
+ "register_app.close": "Close",
+ "register_app.dev": "Developer Applications",
+ "rhs_comment.comment": "Comment",
+ "rhs_comment.edit": "Edit",
+ "rhs_comment.del": "Delete",
+ "rhs_comment.retry": "Retry",
+ "rhs_header.details": "Message Details",
+ "rhs_root.direct": "Direct Message",
+ "rhs_root.edit": "Edit",
+ "rhs_root.del": "Delete",
+ "search_bar.search": "Search",
+ "search_bar.cancel": "Cancel",
+ "search_bar.usage": "<h4>Search Options</h4><ul><li><span>Use </span><b>\"quotation marks\"</b><span> to search for phrases</span></li><li><span>Use </span><b>from:</b><span> to find posts from specific users and </span><b>in:</b><span> to find posts in specific channels</span></li></ul>",
+ "search_header.results": "Search Results",
+ "search_header.title2": "Recent Mentions",
+ "search_item.direct": "Direct Message",
+ "search_item.jump": "Jump",
+ "search_results.usage": "<ul><li>Use <b>\"quotation marks\"</b> to search for phrases</li><li>Use <b>from:</b> to find posts from specific users and <b>in:</b> to find posts in specific channels</li></ul>",
+ "search_results.noResults": "NO RESULTS",
+ "search_results.because": "<ul>\n <li>If you're searching a partial phrase (ex. searching \"rea\", looking for \"reach\" or \"reaction\"), append a * to your search term</li>\n <li>Due to the volume of results, two letter searches and common words like \"this\", \"a\" and \"is\" won't appear in search results</li>\n </ul>",
+ "setting_item_max.save": "Save",
+ "setting_item_max.cancel": "Cancel",
+ "setting_item_min.edit": "Edit",
+ "setting_picture.save": "Save",
+ "setting_picture.help": "Upload a profile picture in either JPG or PNG format, at least {width}px in width and {height}px height.",
+ "setting_picture.select": "Select",
+ "setting_picture.cancel": "Cancel",
+ "setting_upload.noFile": "No file selected.",
+ "setting_upload.select": "Select file",
+ "setting_upload.import": "Import",
+ "sidebar_header.tutorial": "<h4>Main Menu</h4>\n <p>The <strong>Main Menu</strong> is where you can <strong>Invite New Members</strong>, access your <strong>Account Settings</strong> and set your <strong>Theme Color</strong>.</p>\n <p>Team administrators can also access their <strong>Team Settings</strong> from this menu.</p><p>System administrators will find a <strong>System Console</strong> option to administrate the entire system.</p>",
+ "sidebar_right_menu.inviteNew": "Invite New Member",
+ "sidebar_right_menu.teamLink": "Get Team Invite Link",
+ "sidebar_right_menu.teamSettings": "Team Settings",
+ "sidebar_right_menu.manageMembers": "Manage Members",
+ "sidebar_right_menu.console": "System Console",
+ "sidebar_right_menu.help": "Help",
+ "sidebar_right_menu.report": "Report a Problem",
+ "sidebar_right_menu.accountSettings": "Account Settings",
+ "sidebar_right_menu.logout": "Logout",
+ "sidebar.tutorialScreen1": "<h4>Channels</h4><p><strong>Channels</strong> organize conversations across different topics. They’re open to everyone on your team. To send private communications use <strong>Direct Messages</strong> for a single person or <strong>Private Groups</strong> for multiple people.</p>",
+ "sidebar.tutorialScreen2": "<h4>\"Town Square\" and \"Off-Topic\" channels</h4>\n <p>Here are two public channels to start:</p>\n <p><strong>Town Square</strong> is a place for team-wide communication. Everyone in your team is a member of this channel.</p>\n <p><strong>Off-Topic</strong> is a place for fun and humor outside of work-related channels. You and your team can decide what other channels to create.</p>",
+ "sidebar.tutorialScreen3": "<h4>Creating and Joining Channels</h4>\n <p>Click <strong>\"More...\"</strong> to create a new channel or join an existing one.</p>\n <p>You can also create a new channel or private group by clicking the <strong>\"+\" symbol</strong> next to the channel or private group header.</p>",
+ "sidebar.removeList": "Remove from list",
+ "sidebar.more": "More ({count})",
+ "sidebar.createChannel": "Create new channel",
+ "sidebar.createGroup": "Create new group",
+ "sidebar.unreadAbove": "Unread post(s) above",
+ "sidebar.unreadBelow": "Unread post(s) below",
+ "sidebar.channels": "Channels",
+ "sidebar.moreElips": "More...",
+ "sidebar.pg": "Private Groups",
+ "sidebar.direct": "Direct Messages",
"signup_team_complete.completed": "You've already completed the signup process for this invitation or this invitation has expired.",
"signup_team_confirm.title": "Sign up Complete",
"signup_team_confirm.checkEmail": "Please check your email: <strong>{email}</strong><br />Your email contains a link to set up your team",
@@ -499,6 +756,43 @@
"suggestion.mention.channel": "Notifies everyone in the channel",
"suggestion.search.public": "Public Channels",
"suggestion.search.private": "Public Groups",
+ "team_export_tab.exporting": " Exporting...",
+ "team_export_tab.ready": " Ready for ",
+ "team_export_tab.download": "download",
+ "team_export_tab.unable": " Unable to export: {error}",
+ "team_export_tab.export": "Export",
+ "team_export_tab.exportTeam": "Export your team",
+ "general_tab.dirDisabled": "Team Directory has been disabled. Please ask a System Admin to enable the Team Directory in the System Console team settings.",
+ "general_tab.required": "This field is required",
+ "general_tab.chooseName": "Please choose a new name for your team",
+ "general_tab.includeDirTitle": "Include this team in the Team Directory",
+ "general_tab.yes": "Yes",
+ "general_tab.no": "No",
+ "general_tab.dirOff": "Team directory is turned off for this system.",
+ "general_tab.openInviteTitle": "Allow anyone to sign-up from login page",
+ "general_tab.codeTitle": "Invite Code",
+ "general_tab.codeDesc": "Click 'Edit' to regenerate Invite Code.",
+ "general_tab.teamNameInfo": "Set the name of the team as it appears on your sign-in screen and at the top of the left-hand sidebar.",
+ "general_tab.includeDirDesc": "Including this team will display the team name from the Team Directory section of the Home Page, and provide a link to the sign-in page.",
+ "general_tab.dirContact": "Contact your system administrator to turn on the team directory on the system home page.",
+ "general_tab.openInviteDesc": "When allowed, a link to account creation will be included on the sign-in page of this team and allow any visitor to sign-up.",
+ "general_tab.regenerate": "Re-Generate",
+ "general_tab.codeLongDesc": "The Invite Code is used as part of the URL in the team invitation link created by **Get Team Invite Link** in the main menu. Regenerating creates a new team invitation link and invalidates the previous link.",
+ "general_tab.teamName": "Team Name",
+ "general_tab.title": "General Settings",
+ "team_import_tab.importSlack": "Import from Slack (Beta)",
+ "team_import_tab.importHelp": "<p>To import a team from Slack go to Slack > Team Settings > Import/Export Data > Export > Start Export. Slack does not allow you to export files, images, private groups or direct messages stored in Slack. Therefore, Slack import to Mattermost only supports importing of text messages in your Slack team's public channels.</p><p>The Slack import to Mattermost is in 'Beta'. Slack bot posts do not yet import and Slack @mentions are not currently supported.</p>",
+ "team_import_tab.importing": " Importing...",
+ "team_import_tab.successful": " Import successful: ",
+ "team_import_tab.summary": "View Summary",
+ "team_import_tab.failure": " Import failure: ",
+ "team_import_tab.import": "Import",
+ "team_member_modal.members": "{team} Members",
+ "team_member_modal.close": "Close",
+ "team_settings_modal.generalTab": "General",
+ "team_settings_modal.importTab": "Import",
+ "team_settings_modal.exportTab": "Export",
+ "team_settings_modal.title": "Team Settings",
"choose_auth_page.gitlabCreate": "Create new team with GitLab Account",
"choose_auth_page.googleCreate": "Create new team with Google Apps Account",
"choose_auth_page.emailCreate": "Create new team with email address",
@@ -569,6 +863,9 @@
"sso_signup.gitlab": "Create team with GitLab Account",
"sso_signup.google": "Create team with Google Apps Account",
"sso_signup.find": "Find my teams",
+ "textbox.edit": "Edit message",
+ "textbox.preview": "Preview",
+ "textbox.help": "Help",
"tutorial_intro.screenOne": "<h3>Welcome to:</h3>\n <h1>Mattermost</h1>\n <p>Your team communication all in one place, instantly searchable and available anywhere</p>\n <p>Keep your team connected to help them achieve what matters most.</p>",
"tutorial_intro.screenTwo": "<h3>How Mattermost works:</h3>\n <p>Communication happens in public discussion channels, private groups and direct messages.</p>\n <p>Everything is archived and searchable from any web-enabled desktop, laptop or phone.</p>",
"tutorial_intro.invite": "Invite teammates",
@@ -582,5 +879,182 @@
"tutorial_tip.ok": "Okay",
"tutorial_tip.next": "Next",
"tutorial_tip.seen": "Seen this before? ",
- "tutorial_tip.out": "Opt out of these tips."
+ "tutorial_tip.out": "Opt out of these tips.",
+ "user_profile.notShared": "Email not shared",
+ "user.settings.custom_theme.sidebarBg": "Sidebar BG",
+ "user.settings.custom_theme.sidebarText": "Sidebar Text",
+ "user.settings.custom_theme.sidebarHeaderBg": "Sidebar Header BG",
+ "user.settings.custom_theme.sidebarHeaderTextColor": "Sidebar Header Text",
+ "user.settings.custom_theme.sidebarUnreadText": "Sidebar Unread Text",
+ "user.settings.custom_theme.sidebarTextHoverBg": "Sidebar Text Hover BG",
+ "user.settings.custom_theme.sidebarTextActiveBorder": "Sidebar Text Active Border",
+ "user.settings.custom_theme.sidebarTextActiveColor": "Sidebar Text Active Color",
+ "user.settings.custom_theme.onlineIndicator": "Online Indicator",
+ "user.settings.custom_theme.awayIndicator": "Away Indicator",
+ "user.settings.custom_theme.mentionBj": "Mention Jewel BG",
+ "user.settings.custom_theme.mentionColor": "Mention Jewel Text",
+ "user.settings.custom_theme.centerChannelBg": "Center Channel BG",
+ "user.settings.custom_theme.centerChannelColor": "Center Channel Text",
+ "user.settings.custom_theme.newMessageSeparator": "New Message Separator",
+ "user.settings.custom_theme.linkColor": "Link Color",
+ "user.settings.custom_theme.buttonBg": "Button BG",
+ "user.settings.custom_theme.buttonColor": "Button Text",
+ "user.settings.custom_theme.mentionHighlightBg": "Mention Highlight BG",
+ "user.settings.custom_theme.mentionHighlightLink": "Mention Highlight Link",
+ "user.settings.custom_theme.codeTheme": "Code Theme",
+ "user.settings.custom_theme.copyPaste": "Copy and paste to share theme colors:",
+ "user.settings.import_theme.submitError": "Invalid format, please try copying and pasting in again.",
+ "user.settings.import_theme.importHeader": "Import Slack Theme",
+ "user.settings.import_theme.importBody": "To import a theme, go to a Slack team and look for “Preferences -> Sidebar Theme”. Open the custom theme option, copy the theme color values and paste them here:",
+ "user.settings.import_theme.cancel": "Cancel",
+ "user.settings.import_theme.submit": "Submit",
+ "user.settings.hooks_in.channel": "Channel: ",
+ "user.settings.hooks_in.none": "None",
+ "user.settings.hooks_in.existing": "Existing incoming webhooks",
+ "user.settings.hooks_in.description": "Create webhook URLs for use in external integrations. Please see<a href=\"http://mattermost.org/webhooks\" target=\"_blank\">http://mattermost.org/webhooks</a> to learn more.",
+ "user.settings.hooks_in.addTitle": "Add a new incoming webhook",
+ "user.settings.hooks_in.add": "Add",
+ "user.settings.languages.change": "Change interface language",
+ "user.settings.languages": "Set language",
+ "user.settings.hooks_out.optional": "Optional if channel selected",
+ "user.settings.hooks_out.callbackHolder": "Each URL must start with http:// or https://",
+ "user.settings.hooks_out.select": "--- Select a channel ---",
+ "user.settings.hooks_out.channel": "Channel: ",
+ "user.settings.hooks_out.trigger": "Trigger Words: ",
+ "user.settings.hooks_out.regen": "Regen Token",
+ "user.settings.hooks_out.none": "None",
+ "user.settings.hooks_out.existing": "Existing outgoing webhooks",
+ "user.settings.hooks_out.addDescription": "Create webhooks to send new message events to an external integration. Please see <a href=\"http://mattermost.org/webhooks\">http://mattermost.org/webhooks</a> to learn more.",
+ "user.settings.hooks_out.addTitle": "Add a new outgoing webhook",
+ "user.settings.hooks_out.only": "Only public channels can be used",
+ "user.settings.hooks_out.comma": "Comma separated words to trigger on",
+ "user.settings.hooks_out.callback": "Callback URLs: ",
+ "user.settings.hooks_out.callbackDesc": "New line separated URLs that will receive the HTTP POST event",
+ "user.settings.hooks_out.add": "Add",
+ "user.settings.advance.sendTitle": "Send messages on Ctrl + Enter",
+ "user.settings.advance.on": "On",
+ "user.settings.advance.off": "Off",
+ "user.settings.advance.preReleaseTitle": "Preview pre-release features",
+ "user.settings.advance.feature": " Feature ",
+ "user.settings.advance.features": " Features ",
+ "user.settings.advance.enabled": "enabled",
+ "user.settings.advance.markdown_preview": "Show markdown preview option in message input box",
+ "user.settings.advance.embed_preview": "Show preview snippet of links below message",
+ "user.settings.advance.loc_preview": "Show user language in display settings",
+ "user.settings.advance.sendDesc": "If enabled 'Enter' inserts a new line and 'Ctrl + Enter' submits the message.",
+ "user.settings.advance.preReleaseDesc": "Check any pre-released features you'd like to preview. You may also need to refresh the page before the setting will take effect.",
+ "user.settings.advance.title": "Advanced Settings",
+ "user.settings.appearance.themeColors": "Theme Colors",
+ "user.settings.appearance.customTheme": "Custom Theme",
+ "user.settings.appearance.save": "Save",
+ "user.settings.appearance.cancel": "Cancel",
+ "user.settings.appearance.title": "Appearance Settings",
+ "user.settings.appearance.import": "Import theme colors from Slack",
+ "user.settings.developer.applicationsPreview": "Applications (Preview)",
+ "user.settings.developer.thirdParty": "Open to register a new third-party application",
+ "user.settings.developer.register": "Register New Application",
+ "user.settings.developer.title": "Developer Settings",
+ "user.settings.display.normalClock": "12-hour clock (example: 4:00 PM)",
+ "user.settings.display.militaryClock": "24-hour clock (example: 16:00)",
+ "user.settings.display.clockDisplay": "Clock Display",
+ "user.settings.display.teammateDisplay": "Teammate Name Display",
+ "user.settings.display.showNickname": "Show nickname if one exists, otherwise show first and last name",
+ "user.settings.display.showUsername": "Show username (team default)",
+ "user.settings.display.showFullname": "Show first and last name",
+ "user.settings.display.fontTitle": "Display Font",
+ "user.settings.display.language": "Language",
+ "user.settings.display.preferTime": "Select how you prefer time displayed.",
+ "user.settings.display.nameOptsDesc": "Set how to display other user's names in posts and the Direct Messages list.",
+ "user.settings.display.fontDesc": "Select the font displayed in the Mattermost user interface.",
+ "user.settings.display.title": "Display Settings",
+ "user.settings.general.usernameReserved": "This username is reserved, please choose a new one.",
+ "user.settings.general.usernameRestrictions": "'Username must begin with a letter, and contain between {min} to {max} lowercase characters made up of numbers, letters, and the symbols '.', '-' and '_'.",
+ "user.settings.general.validEmail": "Please enter a valid email address",
+ "user.settings.general.emailMatch": "The new emails you entered do not match.",
+ "user.settings.general.checkEmail": "Check your email at {email} to verify the address.",
+ "user.settings.general.newAddress": "New Address: {email}<br />Check your email to verify the above address.",
+ "user.settings.general.checkEmailNoAddress": "Check your email to verify your new address",
+ "user.settings.general.loginGitlab": "Log in done through GitLab",
+ "user.settings.general.validImage": "Only JPG or PNG images may be used for profile pictures",
+ "user.settings.general.imageTooLarge": "Unable to upload profile image. File is too large.",
+ "user.settings.general.uploadImage": "Click 'Edit' to upload an image.",
+ "user.settings.general.imageUpdated": "Image last updated {date}",
+ "user.settings.general.fullName": "Full Name",
+ "user.settings.general.nickname": "Nickname",
+ "user.settings.general.username": "Username",
+ "user.settings.general.email": "Email",
+ "user.settings.general.profilePicture": "Profile Picture",
+ "user.settings.general.close": "Close",
+ "user.settings.general.firstName": "First Name",
+ "user.settings.general.lastName": "Last Name",
+ "user.settings.general.notificationsLink": "Notifications",
+ "user.settings.general.notificationsExtra": "By default, you will receive mention notifications when someone types your first name. Go to {notify} settings to change this default.",
+ "user.settings.general.nicknameExtra": "Use Nickname for a name you might be called that is different from your first name and username. This is most often used when two or more people have similar sounding names and usernames.",
+ "user.settings.general.usernameInfo": "Pick something easy for teammates to recognize and recall.",
+ "user.settings.general.emailHelp1": "Email is used for sign-in, notifications, and password reset. Email requires verification if changed.",
+ "user.settings.general.emailHelp2": "Email has been disabled by your system administrator. No notification emails will be sent until it is enabled.",
+ "user.settings.general.emailHelp3": "Email is used for sign-in, notifications, and password reset.",
+ "user.settings.general.emailHelp4": "A verification email was sent to {email}.",
+ "user.settings.general.primaryEmail": "Primary Email",
+ "user.settings.general.confirmEmail": "Confirm Email",
+ "user.settings.general.emailCantUpdate": "Log in occurs through GitLab. Email cannot be updated.",
+ "user.settings.general.title": "General Settings",
+ "user.settings.integrations.incomingWebhooks": "Incoming Webhooks",
+ "user.settings.integrations.incomingWebhooksDescription": "Manage your incoming webhooks",
+ "user.settings.integrations.outWebhooks": "Outgoing Webhooks",
+ "user.settings.integrations.outWebhooksDescription": "Manage your outgoing webhooks",
+ "user.settings.integrations.title": "Integration Settings",
+ "user.settings.modal.general": "General",
+ "user.settings.modal.security": "Security",
+ "user.settings.modal.notifications": "Notifications",
+ "user.settings.modal.appearance": "Appearance",
+ "user.settings.modal.developer": "Developer",
+ "user.settings.modal.integrations": "Integrations",
+ "user.settings.modal.display": "Display",
+ "user.settings.modal.advanced": "Advanced",
+ "user.settings.modal.confirmTitle": "Discard Changes?",
+ "user.settings.modal.confirmMsg": "You have unsaved changes, are you sure you want to discard them?",
+ "user.settings.modal.confirmBtns": "Yes, Discard",
+ "user.settings.modal.title": "Account Settings",
+ "user.settings.notifications.desktop": "Send desktop notifications",
+ "user.settings.notifications.desktopSounds": "Desktop notification sounds",
+ "user.settings.notifications.emailNotifications": "Email notifications",
+ "user.settings.notifications.wordsTrigger": "Words that trigger mentions",
+ "user.settings.notifications.close": "Close",
+ "user.settings.notification.allActivity": "For all activity",
+ "user.settings.notifications.onlyMentions": "Only for mentions and direct messages",
+ "user.settings.notifications.never": "Never",
+ "user.settings.notifications.info": "Desktop notification sounds are available on Firefox, Safari, Chrome, Internet Explorer, and Edge.",
+ "user.settings.notifications.on": "On",
+ "user.settings.notifications.off": "Off",
+ "user.settings.notification.soundConfig": "Please configure notification sounds in your browser settings",
+ "user.settings.notifications.emailInfo": "Email notifications are sent for mentions and direct messages after you’ve been offline for more than 60 seconds or away from {siteName} for more than 5 minutes.",
+ "user.settings.notifications.sensitiveName": "Your case sensitive first name \"{first_name}\"",
+ "user.settings.notifications.sensitiveUsername": "Your non-case sensitive username \"{username}\"",
+ "user.settings.notifications.usernameMention": "Your username mentioned \"@{username}\"",
+ "user.settings.notifications.teamWide": "Team-wide mentions \"@all\"",
+ "user.settings.notifications.channelWide": "Channel-wide mentions \"@channel\"",
+ "user.settings.notifications.sensitiveWords": "Other non-case sensitive words, separated by commas:",
+ "user.settings.notifications.noWords": "No words configured",
+ "user.settings.notifications.title": "Notification Settings",
+ "user.settings.notifications.header": "Notifications",
+ "user.settings.security.currentPasswordError": "Please enter your current password",
+ "user.settings.security.passwordLengthError": "New passwords must be at least {chars} characters",
+ "user.settings.security.passwordMatchError": "The new passwords you entered do not match",
+ "user.settings.security.password": "Password",
+ "user.settings.security.lastUpdated": "Last updated {date} at {time}",
+ "user.settings.security.method": "Sign-in Method",
+ "user.settings.security.close": "Close",
+ "user.settings.security.currentPassword": "Current Password",
+ "user.settings.security.newPassword": "New Password",
+ "user.settings.security.retypePassword": "Retype New Password",
+ "user.settings.security.switchEmail": "Switch to using email and password",
+ "user.settings.security.switchGitlab": "Switch to using GitLab SSO",
+ "user.settings.security.switchGoogle": "Switch to using Google SSO",
+ "user.settings.security.oneSignin": "You may only have one sign-in method at a time. Switching sign-in method will send an email notifying you if the change was successful.",
+ "user.settings.security.emailPwd": "Email and Password",
+ "user.settings.security.gitlab": "GitLab SSO",
+ "user.settings.security.title": "Security Settings",
+ "user.settings.security.viewHistory": "View Access History",
+ "user.settings.security.logoutActiveSessions": "View and Logout of Active Sessions"
} \ No newline at end of file
diff --git a/web/static/i18n/es.json b/web/static/i18n/es.json
index 48412510d..cb3e8a199 100644
--- a/web/static/i18n/es.json
+++ b/web/static/i18n/es.json
@@ -1,4 +1,73 @@
{
+ "about.close": "Cerrar",
+ "about.date": "Fecha de compilación:",
+ "about.enterpriseEdition": "Edición Enterprise",
+ "about.hash": "Hash de compilación:",
+ "about.licensed": "Licenciado por:",
+ "about.number": "Número de compilación:",
+ "about.teamEdtion": "Edición Team",
+ "about.title": "Acerca de Mattermost",
+ "about.version": "Versión:",
+ "access_history.accountActive": "La cuenta se ha activado",
+ "access_history.accountInactive": "La cuenta se ha desactivado",
+ "access_history.attemptedAllowOAuthAccess": "Intento para permitir acceso a un nuevo servicio de OAuth",
+ "access_history.attemptedLogin": "Intento de inicio de sesión",
+ "access_history.attemptedOAuthToken": "Intento de obtener un token de acceso de OAuth",
+ "access_history.attemptedPassword": "Intento de cambio de contraseña",
+ "access_history.attemptedRegisterApp": "Intento de registrar una nueva aplicación OAuth con el ID {id}",
+ "access_history.attemptedReset": "Intento de reestablecer al contraseña",
+ "access_history.attemptedWebhookCreate": "Intento de creación de un webhook",
+ "access_history.attemptedWebhookDelete": "Intento de eliminación de un webhook",
+ "access_history.by": " por {username}",
+ "access_history.byAdmin": " por un admin",
+ "access_history.channelCreated": "Creado el canal/grupo {channelName}",
+ "access_history.channelDeleted": "Eliminado el canal/grupo con el URL {url}",
+ "access_history.establishedDM": "Establecido un canal de mensajes directos con {username}",
+ "access_history.failedLogin": "Intento de inicio de sesión FALLIDO",
+ "access_history.failedOAuthAccess": "Fallo al permitir acceso a un nuevo servicio de OAuth - la URI de redirección no concuerda con la previamente registrada",
+ "access_history.failedPassword": "Fallo al cambiar la contraseña - se trató de actualizar la contraseña de un usuario que ingresó por medio de un servicio de OAuth",
+ "access_history.failedWebhookCreate": "Falló la creación del webhook - permisos inadecuados del canal",
+ "access_history.failedWebhookDelete": "Falló la eliminación del webhook - condiciones inapropiadas",
+ "access_history.headerUpdated": "Actualizado el cancabezado del canal/grupo {channelName}",
+ "access_history.ip": "IP: {ip}",
+ "access_history.loginAttempt": " (intento de inicio de sesión)",
+ "access_history.loginFailure": " (Fallo de inicio de sesión)",
+ "access_history.logout": "Cerrada la sesión de tu cuenta",
+ "access_history.member": "miembro",
+ "access_history.moreInfo": "Más información",
+ "access_history.nameUpdated": "Actualizado el nombre del canal/grupo {channelName}",
+ "access_history.oauthTokenFailed": "Fallo al obtener un token de acceso de OAuth - {token}",
+ "access_history.revokedAll": "Revocadas todas las sesiones actuales para el equipo",
+ "access_history.sentEmail": "Enviado un correo electrónico a {email} para restablecer tu contraseña",
+ "access_history.session": "Sesión ID: {id}",
+ "access_history.sessionRevoked": "La sesión con el id {sessionId} fue revocada",
+ "access_history.successfullLogin": "Inicio de sesión con éxito",
+ "access_history.successfullOAuthAccess": "Se asignó un nuevo servicio de OAuth con éxito",
+ "access_history.successfullOAuthToken": "Se agregó un nuevo servicio de OAuth con éxito",
+ "access_history.successfullPassword": "La contraseña se cambió satisfactoriamente",
+ "access_history.successfullReset": "La contraseña fue reestablecida con éxito",
+ "access_history.successfullWebhookCreate": "Creación del webhook con éxito",
+ "access_history.successfullWebhookDelete": "Eliminación del webhook con éxito",
+ "access_history.title": "Historial de Acceso",
+ "access_history.updateGeneral": "Actualizada la configuración general de tu cuenta",
+ "access_history.updateGlobalNotifications": "Actualizada la configuración global de tus notificaciones",
+ "access_history.updatePicture": "Actualizada tu imagén de perfil",
+ "access_history.updatedRol": "Actualizado rol(es) de usuario a ",
+ "access_history.userAdded": "Agregado {username} al canal/grupo {channelName}",
+ "access_history.userRemoved": "Removido {username} del canal/grupo {channelName}",
+ "access_history.verified": "Se verificó tu dirección de correo electrónico con éxito",
+ "activity_log.activeSessions": "Sesiones Activas",
+ "activity_log.browser": "Navegador: {browser}",
+ "activity_log.firstTime": "Primera actividad: {date}, {time}",
+ "activity_log.lastActivity": "Última actividad: {date}, {time}",
+ "activity_log.logout": "Cerrar sesión",
+ "activity_log.moreInfo": "Mas información",
+ "activity_log.os": "Sistema Operativo: {os}",
+ "activity_log.sessionId": "Sesión ID: {id}",
+ "activity_log.sessionsDescription": "Las sesiones son creadas cuando inicias sesión con tus credenciales en un nuevo navegador desde cualquier dispositivo. Las sesiones te permiten utilizar Mattermost por un período de hasta 30 días sin tener que iniciar sesión nuevamente. Si quieres cerrar tu sesión antes de ese tiempo, utiliza el botón de 'Cerrar Sesión' en la parte de abajo.",
+ "activity_log_modal.android": "Android",
+ "activity_log_modal.androidNativeApp": "Android App Nativa",
+ "activity_log_modal.iphoneNativeApp": "iPhone App Nativa",
"admin.analytics.activeUsers": "Usuarios Activos con Mensajes",
"admin.analytics.loading": "Cargando...",
"admin.analytics.meaningful": "No hay suficiente data para tener una representación significativa.",
@@ -386,13 +455,17 @@
"admin.team_analytics.totalPosts": "Total de Mensajes",
"admin.userList.title": "Usuarios para ",
"admin.userList.title2": "Usuarios para {team} ({count})",
+ "admin.user_item.confirmDemoteDescription": "Si te degradas a ti mismo de la función de Administrador de Sistema y no hay otro usuario con privilegios de Administrador de Sistema, tendrás que volver a asignar un Administrador del Sistema accediendo al servidor de Mattermost a través de un terminal y ejecutar el siguiente comando.",
+ "admin.user_item.confirmDemoteRoleTitle": "Confirmar el decenso del rol de Administrador de Sistema",
+ "admin.user_item.confirmDemotion": "Confirmar decenso",
+ "admin.user_item.confirmDemotionCmd": "platform -assign_role -team_name=\"tuequipo\" -email=\"nombre@tuempresa.com\" -role=\"system_admin\"",
"admin.user_item.inactive": "Inactivo",
"admin.user_item.makeActive": "Activar",
"admin.user_item.makeInactive": "Inactivar",
"admin.user_item.makeMember": "Hacer Miembro",
"admin.user_item.makeSysAdmin": "Hacer Admin del Sistema",
"admin.user_item.makeTeamAdmin": "Hacer Admin de Equipo",
- "admin.user_item.member": "Meiembro",
+ "admin.user_item.member": "Miembro",
"admin.user_item.resetPwd": "Reiniciar Contraseña",
"admin.user_item.sysAdmin": "Admin de Sistema",
"admin.user_item.teamAdmin": "Admin de Equipo",
@@ -401,6 +474,36 @@
"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}",
+ "change_url.close": "Cerrar",
+ "change_url.endWithLetter": "Debe terminar con una letra o número",
+ "change_url.invalidUrl": "URL Inválida",
+ "change_url.longer": "Debe ser mayor a 2 caracteres",
+ "change_url.noUnderscore": "No puede tener dos guíones bajos seguidos",
+ "change_url.startWithLetter": "Debe comenzar con una letra o número",
+ "channel_flow.alreadyExist": "Un canal con este identificador ya existe",
+ "channel_flow.changeUrlDescription": "Algunos caracteres no están permitidos en las URLs y pueden ser removidos.",
+ "channel_flow.changeUrlTitle": "Cambiar URL de {term}",
+ "channel_flow.channel": "Canal",
+ "channel_flow.create": "Crear {term}",
+ "channel_flow.group": "Grupo",
+ "channel_flow.invalidName": "Nombre de Canal Inválido",
+ "channel_flow.set_url_title": "Asignar URL de {term}",
+ "channel_modal.cancel": "Cancelar",
+ "channel_modal.channel": "Canal",
+ "channel_modal.createNew": "Crear Nuevo ",
+ "channel_modal.descriptionHelp": "Describe como este {term} debería ser utilizado.",
+ "channel_modal.displayNameError": "Este campo es obligatorio",
+ "channel_modal.edit": "Editar",
+ "channel_modal.group": "Grupo",
+ "channel_modal.modalTitle": "Nuevo ",
+ "channel_modal.name": "Nombre",
+ "channel_modal.nameEx": "Ej: \"Errores\", \"Mercadeo\", \"办公室恋情\"",
+ "channel_modal.optional": "(opcional)",
+ "channel_modal.privateGroup1": "Crear un grupo privado con acceso restringido. ",
+ "channel_modal.privateGroup2": "Crear un grupo privado",
+ "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",
"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",
@@ -421,6 +524,13 @@
"claim.sso_to_email.switchTo": "Cambiar {type} a correo electrónico y contraseña",
"claim.sso_to_email.title": "Cambiar la cuenta de {type} a Correo Electrónico",
"claim.sso_to_email_newPwd": "Ingresa una nueva contraseña para tu cuenta de {team} {site}",
+ "confirm_modal.cancel": "Cancelar",
+ "create_comment.addComment": "Agregar un comentario...",
+ "create_comment.comment": "Agregar Comentario",
+ "create_comment.commentLength": "El largo del Comentario debe ser menor de {max} caracteres.",
+ "create_comment.commentTitle": "Comentario",
+ "create_comment.file": "Subiendo archivo",
+ "create_comment.files": "Subiendo archivos",
"email_signup.address": "Correo electrónico",
"email_signup.createTeam": "Crear Equipo",
"email_signup.emailError": "Por favor ingresa una dirección de correos válida",
@@ -432,6 +542,10 @@
"email_verify.verified": "{siteName} Correo electrónico verificado",
"email_verify.verifiedBody": "<p>Tu correo electrónico ha sido verificado!! Pincha <a href={url}>aquí</a> para iniciar sesión.</p>",
"error_bar.preview_mode": "Modo de prueba: Las notificaciones por correo electrónico no han sido configuradas",
+ "file_upload.fileAbove": "No se puede subir un archivo que pesa más de {max}MB: {filename}",
+ "file_upload.filesAbove": "No se pueden subir archivos de más de {max}MB: {filenames}",
+ "file_upload.limited": "Se pueden subir un máximo de {count} archivos. Por favor envía otros mensajes para adjuntar más archivos.",
+ "file_upload.pasted": "Imagen Pegada el ",
"find_team.email": "Correo electrónico",
"find_team.findDescription": "Enviamos un correo electrónico con los equipos a los que perteneces.",
"find_team.findTitle": "Encuentra tu equipo",
@@ -439,6 +553,47 @@
"find_team.placeholder": "tu@ejemplo.com",
"find_team.send": "Enviar",
"find_team.submitError": "Por favor ingresa una dirección válida",
+ "general_tab.chooseName": "Por favor escoge otro nombre para tu equipo",
+ "general_tab.codeDesc": "Pincha 'Editar' para regenerar el código de Invitación.",
+ "general_tab.codeLongDesc": "El Código de Invitación es utilizado como parte del URL del enlace creado por la opción **Obtener Enlace de invitación** en el menú principal. Regenerar este código crea un nuevo enlace e invalida los enlaces anteriores.",
+ "general_tab.codeTitle": "Código de Invitación",
+ "general_tab.dirContact": "Contácta a un administrador del sistema para habilitar el directorio de equipos en la página principal.",
+ "general_tab.dirDisabled": "El directorio de Equipos ha sido deshabilitado. Por favor solicita a un Administrador de Sistema que habilite la opción de Directorio de Equipos en la Consola del Sistema.",
+ "general_tab.dirOff": "El directorio de Equipos ha sido deshabilitado para este sistema.",
+ "general_tab.includeDirDesc": "Incluir este equipo mostrará el nombre del equipo en la sección de Directorio de Equipos en la página de inicio, y proveerá un enlace para la página de inicio de sesión.",
+ "general_tab.includeDirTitle": "Incluir este Equipo en el Directorio de Equipos",
+ "general_tab.no": "No",
+ "general_tab.openInviteDesc": "Cuando está permitido, un enlace para la creación de cuentas será incluido en la página de registro de este equipo y permitirá a cualquier visitante registrarse.",
+ "general_tab.openInviteTitle": "Permitir a cualquiera a inscribirse desde la página de inicio de sesión",
+ "general_tab.regenerate": "Regenerar",
+ "general_tab.required": "Este campo es obligatorio",
+ "general_tab.teamName": "Nombre del Equipo",
+ "general_tab.teamNameInfo": "Asigna el nombre del equipo como aparecerá en la página de inicio de sesión y en la parte superior izquierda de la barra lateral.",
+ "general_tab.title": "Configuración General",
+ "general_tab.yes": "Sí",
+ "get_link.clipboard": " Enlace copiado al portapapeles.",
+ "get_link.close": "Cerrar",
+ "get_link.copy": "Copiar Enlace",
+ "get_team_invite_link_modal.help": "Enviar a los compañeros de equipo el enlace que se muestra a continuación para permitirles registrarse a este equipo.",
+ "get_team_invite_link_modal.title": "Enlace de Invitación al Equipo",
+ "invite_member.addAnother": "Agregar otro",
+ "invite_member.autoJoin": "Las personas invitadas se unirán automáticamente al canal <strong>{channel}</strong>.",
+ "invite_member.cancel": "Cancelar",
+ "invite_member.content": "El envio de correos está actualmente desactivado para tu equipo, por lo que no puedes enviar invitaciones.",
+ "invite_member.disabled": "La creación de usuarios ha sido deshabilitada para tu equipo. Por favor consulta con un administrador de tu equipo.",
+ "invite_member.emailError": "Por favor ingresa un correo electrónico válido",
+ "invite_member.firstname": "Nombre",
+ "invite_member.inviteLink": "Enlace de Invitación al Equipo",
+ "invite_member.lastname": "Apellido",
+ "invite_member.modalButton": "Sí, Borrar",
+ "invite_member.modalMessage": "Tienes invitaciones sin usar, estás seguro que quieres borrarlas?",
+ "invite_member.modalTitle": "¿Descartar Invitaciones?",
+ "invite_member.newMember": "Invitar nuevo Miembro",
+ "invite_member.send": "Enviar Invitaciones",
+ "invite_member.send2": "Enviar Invitaciones",
+ "invite_member.sending": " Enviando",
+ "invite_member.teamInvite": "Invitación de Equipo",
+ "invite_member.teamInviteLink": "También puedes invitar personas usando el {link}.",
"loading_screen.loading": "Cargando",
"login.changed": " Cambiado el método de inicio de sesión satisfactoriamente",
"login.create": "Crea una ahora",
@@ -464,6 +619,43 @@
"login_ldap.pwdReq": "La contraseña LDAP es obligatoria",
"login_ldap.signin": "Entrar",
"login_ldap.username": "Usuario LDAP",
+ "member_team_item.inactive": "Inactivo",
+ "member_team_item.makeActive": "Activar",
+ "member_team_item.makeAdmin": "Convertir a Admin de Equipo",
+ "member_team_item.makeInactive": "Desactivar",
+ "member_team_item.makeMember": "Convertir en Miembro",
+ "member_team_item.member": "Miembro",
+ "member_team_item.systemAdmin": "Administrador de Sistema",
+ "member_team_item.teamAdmin": "Admin de Equipo",
+ "more_channels.close": "Cerrar",
+ "more_channels.create": "Crear Nuevo Canal",
+ "more_channels.createClick": "Pincha 'Crear Nuevo Canal' para crear uno nuevo",
+ "more_channels.join": "Unirme",
+ "more_channels.noMore": "No hay más canales para unirse",
+ "more_channels.title": "Más Canales",
+ "more_direct_channels.close": "Cerrar",
+ "more_direct_channels.count": "{count} {member}",
+ "more_direct_channels.countTotal": "{count} {member} de {total} Total",
+ "more_direct_channels.member": "Miembro",
+ "more_direct_channels.message": "Mensaje",
+ "more_direct_channels.notFound": "No se encontraron usuarios :(",
+ "more_direct_channels.search": "Buscar miembros",
+ "more_direct_channels.title": "Mensajes Directos",
+ "msg_typing.areTyping": "{users} y {last} están escribiendo...",
+ "msg_typing.isTyping": "{user} está escribiendo...",
+ "msg_typing.someone": "Alguien",
+ "navbar_dropdown.about": "Acerca de Mattermost",
+ "navbar_dropdown.accountSettings": "Configurar Cuenta",
+ "navbar_dropdown.console": "Consola de Sistema",
+ "navbar_dropdown.create": "Crear nuevo Equipo",
+ "navbar_dropdown.help": "Ayuda",
+ "navbar_dropdown.inviteMember": "Invitar Nuevo Miembro",
+ "navbar_dropdown.logout": "Cerrar sesión",
+ "navbar_dropdown.manageMembers": "Administrar Miembros",
+ "navbar_dropdown.report": "Reportar un Problema",
+ "navbar_dropdown.switchTeam": "Cambiar a {team}",
+ "navbar_dropdown.teamLink": "Enlace invitación al equipo",
+ "navbar_dropdown.teamSettings": "Configurar Equipo",
"password_form.change": "Cambiar mi contraseña",
"password_form.click": " Pincha <a href={url}>aquí</a> para iniciar sesión.",
"password_form.enter": "Ingresa una nueva contraseña para tu cuenta en {teamDisplayName} {SiteName}.",
@@ -478,6 +670,76 @@
"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",
+ "register_app.callback": "Callback URL",
+ "register_app.callbackError": "Al menos un callback URL debe ser ingresado.",
+ "register_app.cancel": "Cancelar",
+ "register_app.clientId": "ID del Cliente",
+ "register_app.clientSecret": "Clave secreta del Cliente",
+ "register_app.close": "Cerrar",
+ "register_app.credentialsDescription": "Guarde estos datos en un lugar SEGURO. Siempre podemos obtener el ID si lo pierdes, pero el SECRET se perderá por siempre si lo llegas a perder.",
+ "register_app.credentialsSave": "Ya guarde tanto mi ID como el SECRET en un lugar seguro",
+ "register_app.credentialsTitle": "Las Credenciales de tu Aplicación",
+ "register_app.description": "Descripción",
+ "register_app.dev": "Aplicaciones de Desarrollador",
+ "register_app.homepage": "URL de tu página",
+ "register_app.homepageError": "Se debe ingresar la Página de Inicio.",
+ "register_app.name": "Nombre de la Aplicación",
+ "register_app.nameError": "Se debe ingresar el Nombre de la Aplicación.",
+ "register_app.optional": "Opcional",
+ "register_app.register": "Registrar",
+ "register_app.required": "Requerido",
+ "register_app.title": "Registra una Nueva Aplicación",
+ "rhs_comment.comment": "Comentario",
+ "rhs_comment.del": "Borrar",
+ "rhs_comment.edit": "Editar",
+ "rhs_comment.retry": "Reintentar",
+ "rhs_header.details": "Detalles del Mensaje",
+ "rhs_root.del": "Borrar",
+ "rhs_root.direct": "Mensaje Directo",
+ "rhs_root.edit": "Editar",
+ "search_bar.cancel": "Cancelar",
+ "search_bar.search": "Buscar",
+ "search_bar.usage": "<h4>Opciones de Búsqueda</h4><ul><li><span>Utiliza </span><b>\"comillas\"</b><span> para buscar frases</span></li><li><span>Utiliza </span><b>from:</b><span> para encontrar mensajes de usuarios en específico e </span><b>in:</b><span> para encontrar mensajes de canales específicos</span></li></ul>",
+ "search_header.results": "Resultados de la Búsqueda",
+ "search_header.title2": "Menciones Recientes",
+ "search_item.direct": "Mensjae Directo",
+ "search_item.jump": "Ir ",
+ "search_results.because": "<ul><li>Si estás buscando un frase parcial (ej. buscando \"aud\", tratando de encontrar \"audible\" o \"audifonos\"), agrega un * a la palabra que estás buscando</li><li>Debido a la cantidad de resultados, la búsqueda con dos letras y palabras comunes como \"this\", \"a\" and \"es\" no aparecerán en los resultados</li>/ul>",
+ "search_results.noResults": "SIN RESULTADOS",
+ "search_results.usage": "<ul><li>Utiliza <b>\"comillas\"</b> para buscar frases</li><li>Utiliza <b>from:</b> para encontrar mensajes de usuarios en específico e <b>in:</b> para encontrar mensajes de canales específicos</li></ul>",
+ "setting_item_max.cancel": "Cancelar",
+ "setting_item_max.save": "Guardar",
+ "setting_item_min.edit": "Editar",
+ "setting_picture.cancel": "Cancelar",
+ "setting_picture.help": "Sube una imagen de tu perfil en formato JPG o PNG de al menos {width}px de ancho y {height}px de alto.",
+ "setting_picture.save": "Guardar",
+ "setting_picture.select": "Selecciona",
+ "setting_upload.import": "Importar",
+ "setting_upload.noFile": "No ha seleccionado un archivo",
+ "setting_upload.select": "Selecciona un archivo",
+ "sidebar.channels": "Canales",
+ "sidebar.createChannel": "Crear un nuevo canal",
+ "sidebar.createGroup": "Crear un nuevo grupo",
+ "sidebar.direct": "Mensajes Directos",
+ "sidebar.more": "Más ({count})",
+ "sidebar.moreElips": "Más...",
+ "sidebar.pg": "Grupos Privados",
+ "sidebar.removeList": "Remover de la lista",
+ "sidebar.tutorialScreen1": "<h4>Canales</h4><p><strong>Canales</strong> organizan las conversaciones en diferentes tópicos. Son abiertos para cualquier persona de tu equipo. Para enviar comunicaciones privadas con una sola persona utiliza <strong>Mensajes Directos</strong> o con multiples personas utilizando <strong>Grupos Privados</strong>.</p>",
+ "sidebar.tutorialScreen2": "<h4>Canal \"General\"</h4><p>Este es un canal para comenzar:</p><p><strong>General</strong> es el lugar para tener comunicación con todo el equipo. Todos los integrantes de tu equipo son miembros de este canal.</p>",
+ "sidebar.tutorialScreen3": "<h4>Creando y Uniendose a Canales</h4><p>Pincha en <strong>\"Más...\"</strong> para crear un nuevo canal o unirte a uno existente.</p><p>También puedes crear un nuevo canal o grupo privado al pinchar el simbolo de <strong>\"+\"</strong> que se encuentra al lado del encabezado de Canales o Grupos Privados.</p>",
+ "sidebar.unreadAbove": "Mensaje(s) sin leer arriba",
+ "sidebar.unreadBelow": "Mensaje(s) sin leer abajo",
+ "sidebar_header.tutorial": "<h4>Menú Principal</h4><p>El <strong>Menú Principal</strong> es donde puedes <strong>Invitar a nuevos miembros</strong>, podrás <strong>Configurar tu Cuenta</strong> y seleccionar un <strong>Tema</strong> para personalizar la apariencia.</p><p>Los administradores del Equipo podrán <strong>Configurar el Equipo</strong> desde este menú.</p><p>Los administradores del Sistema encontrarán una opción para ir a la <strong>Consola de Sistema</strong> para administrar el sistema completo.</p>",
+ "sidebar_right_menu.accountSettings": "Configurar tu Cuenta",
+ "sidebar_right_menu.console": "Consola del Sistema",
+ "sidebar_right_menu.help": "Ayuda",
+ "sidebar_right_menu.inviteNew": "Invitar Nuevo Miembro",
+ "sidebar_right_menu.logout": "Cerrar sesión",
+ "sidebar_right_menu.manageMembers": "Adminisrar Miembros",
+ "sidebar_right_menu.report": "Reporta un Problema",
+ "sidebar_right_menu.teamLink": "Enlace Invitación al Equipo",
+ "sidebar_right_menu.teamSettings": "Configurar Equipo",
"signup_team.choose": "Selecciona un Equipo",
"signup_team.createTeam": "O Crea un Equipo",
"signup_team.disabled": "La creación de Equipos ha sido deshabilitada.",
@@ -514,6 +776,25 @@
"suggestion.mention.channel": "Notifica a todas las personas en el canal",
"suggestion.search.private": "Grupos Privados",
"suggestion.search.public": "Canales Públicos",
+ "team_export_tab.download": "descargar",
+ "team_export_tab.export": "Exportar",
+ "team_export_tab.exportTeam": "Exportar tu equipo",
+ "team_export_tab.exporting": " Exportando...",
+ "team_export_tab.ready": " Listo para ",
+ "team_export_tab.unable": " No se pudo exportar: {error}",
+ "team_import_tab.failure": " Fallo al importar: ",
+ "team_import_tab.import": "Importar",
+ "team_import_tab.importHelp": "<p>Para importar un equipo desde Slack dirigete a Slack > Team Settings > Import/Export Data > Export > Start Export. Slack no permite exportar archivos, imágenes, grupos privados o mensajes directos almacenados en Slack. Por ende, La importación desde Slack hacia Mattermost sólo soporta la importación de los mensajes de texto de los canales públicos de tu equipo.</p><p>La importación desde Slack hacia Mattermost está en fase \"Beta\". Los mensajes enviados por Bots en y las @menciones actualmente no son soportados.</p>",
+ "team_import_tab.importSlack": "Importar desde Slack (Beta)",
+ "team_import_tab.importing": " Importando...",
+ "team_import_tab.successful": " Importado con éxito: ",
+ "team_import_tab.summary": "Ver Resumen",
+ "team_member_modal.close": "Cerrar",
+ "team_member_modal.members": "{team} Miembros",
+ "team_settings_modal.exportTab": "Exportar",
+ "team_settings_modal.generalTab": "General",
+ "team_settings_modal.importTab": "Importar",
+ "team_settings_modal.title": "Configuración del Equipo",
"team_signup_display_name.back": "Volver al paso previo",
"team_signup_display_name.charLength": "El nombre debe tener 4 o más caracteres hasta un máximo de 15",
"team_signup_display_name.nameHelp": "Nombre tu equipo en cualquier idioma. El nombre de tu equipo aparecerá en menús y en inicios",
@@ -569,6 +850,9 @@
"team_signup_welcome.validEmailError": "Por favor ingresa una dirección de correo electrónico válida",
"team_signup_welcome.welcome": "Bienvenido a:",
"team_signup_welcome.yes": "Sí, esta dirección es correcta",
+ "textbox.edit": "Editar mensaje",
+ "textbox.help": "Ayuda",
+ "textbox.preview": "Previsualizar",
"tutorial_intro.allSet": "Ya estás listo para comenzar",
"tutorial_intro.end": "Pincha “Siguiente” para entrar al Canal General. Este es el primer canal que ven tus compañeros cuando ingresan. Utilizalo para mandar mensajes que todos deben leer.",
"tutorial_intro.invite": "Invitar compañeros",
@@ -582,5 +866,183 @@
"tutorial_tip.next": "Siguiente",
"tutorial_tip.ok": "Aceptar",
"tutorial_tip.out": "No optar por estos consejos.",
- "tutorial_tip.seen": "¿Haz visto esto antes? "
+ "tutorial_tip.seen": "¿Haz visto esto antes? ",
+ "upload_overlay.info": "Arrastra un archivo para subirlo.",
+ "user.settings.advance.embed_preview": "Mostrar la previsualización de enlaces",
+ "user.settings.advance.enabled": "habilitada(s)",
+ "user.settings.advance.feature": " Característica ",
+ "user.settings.advance.features": " Características ",
+ "user.settings.advance.loc_preview": "Mostrar el idioma del usuario en la configuración de visualización",
+ "user.settings.advance.markdown_preview": "Mostrar la previsualización de mensajes escritos con markdown",
+ "user.settings.advance.off": "Encendido",
+ "user.settings.advance.on": "Apagado",
+ "user.settings.advance.preReleaseDesc": "Marca las características de pre-lanzamiento que quieras previsualizar. Es posible que necesites refrescar la página antes de que los cambios se vean reflejados.",
+ "user.settings.advance.preReleaseTitle": "Previsualizar características de pre-lanzamiento",
+ "user.settings.advance.sendDesc": "Si está habilitado 'Retorno' inserta una nueva linea y 'Ctrl + Retorno' envía el mensaje.",
+ "user.settings.advance.sendTitle": "Enviar mensajes con Ctrl + Retorno",
+ "user.settings.advance.title": "Configuración Avanzada",
+ "user.settings.appearance.cancel": "Cancelar",
+ "user.settings.appearance.customTheme": "Tema personalizado",
+ "user.settings.appearance.import": "Importar los colores de tema de Slack",
+ "user.settings.appearance.save": "Guardar",
+ "user.settings.appearance.themeColors": "Selecciona un Tema",
+ "user.settings.appearance.title": "Configuraciones de Apariencia",
+ "user.settings.custom_theme.awayIndicator": "Indicador Ausente",
+ "user.settings.custom_theme.buttonBg": "Fondo Botón",
+ "user.settings.custom_theme.buttonColor": "Texto Botón",
+ "user.settings.custom_theme.centerChannelBg": "Fondo Centro Canal",
+ "user.settings.custom_theme.centerChannelColor": "Texto Centro Canal",
+ "user.settings.custom_theme.codeTheme": "Tema para Código",
+ "user.settings.custom_theme.copyPaste": "Copia y pega para compartir los colores del tema:",
+ "user.settings.custom_theme.linkColor": "Color Enclaces",
+ "user.settings.custom_theme.mentionBj": "Fondo Joya Mención",
+ "user.settings.custom_theme.mentionColor": "Texto Joya Mención",
+ "user.settings.custom_theme.mentionHighlightBg": "Fondo resaltado Mención",
+ "user.settings.custom_theme.mentionHighlightLink": "Enlace resaltado Mención",
+ "user.settings.custom_theme.newMessageSeparator": "Sep. Nuevos Mensajes",
+ "user.settings.custom_theme.onlineIndicator": "Inicador En Linea",
+ "user.settings.custom_theme.sidebarBg": "Fondo Barra lateral",
+ "user.settings.custom_theme.sidebarHeaderBg": "Fondo Encab. Barra lateral",
+ "user.settings.custom_theme.sidebarHeaderTextColor": "Texto Encabezado Barra lateral",
+ "user.settings.custom_theme.sidebarText": "Texto Barra lateral",
+ "user.settings.custom_theme.sidebarTextActiveBorder": "Borde Activo Barra lateral",
+ "user.settings.custom_theme.sidebarTextActiveColor": "Color Activo Barra lateral",
+ "user.settings.custom_theme.sidebarTextHoverBg": "Fondo Texto pasada Barra lateral",
+ "user.settings.custom_theme.sidebarUnreadText": "Texto No Leidos Barra Lateral",
+ "user.settings.developer.applicationsPreview": "Applicaciones (Vista previa)",
+ "user.settings.developer.register": "Registrar una Nueva Aplicación",
+ "user.settings.developer.thirdParty": "Abrir para registrar una nueva aplicación externa",
+ "user.settings.developer.title": "Configuraciones de Desarrollo",
+ "user.settings.display.clockDisplay": "Visualización del Reloj",
+ "user.settings.display.fontDesc": "Selecciona la fuente con la que quieres ver la interfaz de Mattermost.",
+ "user.settings.display.fontTitle": "Fuente de Visualización",
+ "user.settings.display.language": "Idioma",
+ "user.settings.display.militaryClock": "Reloj de 24 horas (ejemplo: 16:00)",
+ "user.settings.display.nameOptsDesc": "Asigna como mostrar los nombres de los otros usuarios en los mensajes y en la lista de Mensajes Directos.",
+ "user.settings.display.normalClock": "Reloj de 12 horas (ejemplo: 4:00 pm)",
+ "user.settings.display.preferTime": "Selecciona como prefieres mostrar la hora.",
+ "user.settings.display.showFullname": "Mostrar nombre y apellido",
+ "user.settings.display.showNickname": "Mostrar el sobrenombre si existe, de lo contrario mostrar el nombre y apellido",
+ "user.settings.display.showUsername": "Mostrar el nombre de usuario (predeterminado)",
+ "user.settings.display.teammateDisplay": "Visualización del nombre de los integrantes",
+ "user.settings.display.title": "Configuración de Visualización",
+ "user.settings.general.checkEmail": "Revisa tu correo electrónico {email} para verificar la dirección.",
+ "user.settings.general.checkEmailNoAddress": "Revisa tu correo electrónico para verificar la dirección",
+ "user.settings.general.close": "Cerrar",
+ "user.settings.general.confirmEmail": "Confirmar Correo electrónico",
+ "user.settings.general.email": "Correo electrónico",
+ "user.settings.general.emailCantUpdate": "El inicio de sesión ocurre a través de GitLab. El correo electrónico no puede ser cambiado.",
+ "user.settings.general.emailHelp1": "El correo electrónico es utilizado para iniciar sesión, recibir notificaciones y para restablecer la contraseña. Si se cambia el correo electrónico deberás verificarlo nuevamente.",
+ "user.settings.general.emailHelp2": "El correo ha sido deshabilitado por el administrador de sistemas. No llegarán correos de notificación hasta que se vuelva a habilitar.",
+ "user.settings.general.emailHelp3": "El correo electrónico es utilizado para iniciar sesión, recibir notificaciones y para restablecer la contraseña.",
+ "user.settings.general.emailHelp4": "Un correo de verificación ha sido enviado a {email}.",
+ "user.settings.general.emailMatch": "El nuevo correo electrónico introducido no coincide.",
+ "user.settings.general.firstName": "Nombre",
+ "user.settings.general.fullName": "Nombre completo",
+ "user.settings.general.imageTooLarge": "No se puede subir la imagen del perfil. El archivo es muy grande.",
+ "user.settings.general.imageUpdated": "Última actualizacón de la imagen {date}",
+ "user.settings.general.lastName": "Apellido",
+ "user.settings.general.loginGitlab": "Inicio de sesión realizado a través de GitLab",
+ "user.settings.general.newAddress": "Nueva dirección: {email}<br />Revisa tu correo electrónico para verificar tu nueva dirección.",
+ "user.settings.general.nickname": "Sobrenombre",
+ "user.settings.general.nicknameExtra": "Utiliza un Sobrenombre por el cual te conocen que sea diferente de tu nombre y del nombre de tu usuario. Esto se utiliza con mayor frecuencia cuando dos o más personas tienen nombres y nombres de usuario que suenan similares.",
+ "user.settings.general.notificationsExtra": "De forma predeterminada, recibirás notificaciones cuando alguien escribe tu nombre. Ve a la configuración de {notify} para cambiar este comportamiento.",
+ "user.settings.general.notificationsLink": "Notificaciones",
+ "user.settings.general.primaryEmail": "Correo Principal",
+ "user.settings.general.profilePicture": "Foto del Perfil",
+ "user.settings.general.title": "Configuración General",
+ "user.settings.general.uploadImage": "Pinchar 'Editar' para subir una imagen.",
+ "user.settings.general.username": "Nombre de usuario",
+ "user.settings.general.usernameInfo": "Escoje algo fácil para que tus compañeros de equipo puedan reconocerte y recordar.",
+ "user.settings.general.usernameReserved": "Este nombre de usuario está reservado, por favor escoge otro",
+ "user.settings.general.usernameRestrictions": "El nombre de usuario debe empezar con una letra, y contener entre {min} a {max} caracteres en minúscula con números, lettras, y los símbolos '.', '-' y '_'.",
+ "user.settings.general.validEmail": "Por favor ingresa una dirección de correo electrónico válida",
+ "user.settings.general.validImage": "Sólo pueden ser utilizadas imágenes JPG o PNG en el perfil",
+ "user.settings.hooks_in.add": "Agregar",
+ "user.settings.hooks_in.addTitle": "Agregar un nuevo webhook de entrada",
+ "user.settings.hooks_in.channel": "Canal: ",
+ "user.settings.hooks_in.description": "Crea webhook URLs para ser utilizados por integraciones externas. Por favor revisa <a href=\"http://mattermost.org/webhooks\">http://mattermost.org/webhooks</a> para conocer más.",
+ "user.settings.hooks_in.existing": "Webhooks de entrada existentes",
+ "user.settings.hooks_in.none": "Ninguno",
+ "user.settings.hooks_out.add": "Agregar",
+ "user.settings.hooks_out.addDescription": "Crea webhooks para enviar los nuevos mensajes a una integración externa. Por favor revisa <a href=\"http://mattermost.org/webhooks\">http://mattermost.org/webhooks</a> para conocer más.",
+ "user.settings.hooks_out.addTitle": "Agregar un nuevo webhook de salida",
+ "user.settings.hooks_out.callback": "Callback URLs:",
+ "user.settings.hooks_out.callbackDesc": "Separa por una nueva linea cada URL donde quieres recibir el evento de HTTP POST",
+ "user.settings.hooks_out.callbackHolder": "Cada URL debe comenzar con http:// o https://",
+ "user.settings.hooks_out.channel": "Canal: ",
+ "user.settings.hooks_out.comma": "Escribe las palabras de activación que ejecutan el evento separadas por coma",
+ "user.settings.hooks_out.existing": "Webhooks de salida existentes",
+ "user.settings.hooks_out.none": "Ninguno",
+ "user.settings.hooks_out.only": "Sólo se pueden utilizar Canales",
+ "user.settings.hooks_out.optional": "Opcional si se selecciona un canal",
+ "user.settings.hooks_out.regen": "Regenerar Token",
+ "user.settings.hooks_out.select": "--- Selecciona un canal ---",
+ "user.settings.hooks_out.trigger": "Palabras de activación: ",
+ "user.settings.import_theme.cancel": "Cancelar",
+ "user.settings.import_theme.importBody": "Para importar un tema, anda al equipo Slack y busca en [Preferences -> Sidebar Theme]. Abre las opciones del tema, copia los valores de color del tema y pégalo aquí:",
+ "user.settings.import_theme.importHeader": "Importar Tema de Slack",
+ "user.settings.import_theme.submit": "Enviar",
+ "user.settings.import_theme.submitError": "Formato inválido, por favor intenta copiando y pegando nuevamente.",
+ "user.settings.integrations.incomingWebhooks": "Webhooks de entrada",
+ "user.settings.integrations.incomingWebhooksDescription": "Administra tus webhooks de entrada",
+ "user.settings.integrations.outWebhooks": "Webhooks de salida",
+ "user.settings.integrations.outWebhooksDescription": "Administra tus webhooks de salida",
+ "user.settings.integrations.title": "Configuraciones de Integración",
+ "user.settings.languages": "Cambiar Idioma",
+ "user.settings.languages.change": "Cambia el idioma con el que se muestra la intefaz de usuario",
+ "user.settings.modal.advanced": "Avanzada",
+ "user.settings.modal.appearance": "Apariencia",
+ "user.settings.modal.confirmBtns": "Sí, Descartar",
+ "user.settings.modal.confirmMsg": "Tienes cambios sin guardar, ¿Estás seguro que los quieres descartar?",
+ "user.settings.modal.confirmTitle": "¿Descartar Cambios?",
+ "user.settings.modal.developer": "Desarrollo",
+ "user.settings.modal.display": "Visualización",
+ "user.settings.modal.general": "General",
+ "user.settings.modal.integrations": "Integraciones",
+ "user.settings.modal.notifications": "Notificaciones",
+ "user.settings.modal.security": "Seguridad",
+ "user.settings.modal.title": "Configuración de la Cuenta",
+ "user.settings.notification.allActivity": "Para toda actividad",
+ "user.settings.notification.soundConfig": "Por favor configura los sonidos de notificación en las configuraciones de tu navegador",
+ "user.settings.notifications.channelWide": "Menciones a todo el canal \"@channel\"",
+ "user.settings.notifications.close": "Cerrar",
+ "user.settings.notifications.desktop": "Enviar notifiaciones de escritorio",
+ "user.settings.notifications.desktopSounds": "Sonidos de notificación de escritorio",
+ "user.settings.notifications.emailInfo": "Las notificaciones por correo son enviadas para menciones y mensajes directos después de que estés fuera de línea por más de 60 segundos o sin participar en {siteName} por más de 5 minutos.",
+ "user.settings.notifications.emailNotifications": "Notificaciones de correo",
+ "user.settings.notifications.header": "Notificaciones",
+ "user.settings.notifications.info": "Las notificaciones de escritorio están disponibles en Firefox, Safari, Chrome, Internet Explorer, y Edge.",
+ "user.settings.notifications.never": "Nunca",
+ "user.settings.notifications.noWords": "No hay palabras configuradas",
+ "user.settings.notifications.off": "Apagado",
+ "user.settings.notifications.on": "Encendido",
+ "user.settings.notifications.onlyMentions": "Sólo para menciones y mensajes directos",
+ "user.settings.notifications.sensitiveName": "Tu nombre con distinción de mayúsculas \"{first_name}\"",
+ "user.settings.notifications.sensitiveUsername": "Tu nombre de usuario sin distinción de mayúsculas \"{username}\"",
+ "user.settings.notifications.sensitiveWords": "Otras palabras sin distinción de mayúsculas, separadas por comas:",
+ "user.settings.notifications.teamWide": "Menciones para todo el equipo \"@all\"",
+ "user.settings.notifications.title": "Configuracón de Notificaciones",
+ "user.settings.notifications.usernameMention": "Tu nombre de usuario mencionado \"@{username}\"",
+ "user.settings.notifications.wordsTrigger": "Palabras que gatillan menciones",
+ "user.settings.security.close": "Cerrar",
+ "user.settings.security.currentPassword": "Contraseña Actual",
+ "user.settings.security.currentPasswordError": "Por favor ingresa tu contraseña actual",
+ "user.settings.security.emailPwd": "Correo electrónico y Contraseña",
+ "user.settings.security.gitlab": "GitLab SSO",
+ "user.settings.security.lastUpdated": "Última actualización {date} a las {time}",
+ "user.settings.security.logoutActiveSessions": "Visualizar y cerrar las sesiones activas",
+ "user.settings.security.method": "Método de inicio de sesión",
+ "user.settings.security.newPassword": "Nueva Contraseña",
+ "user.settings.security.oneSignin": "Sólo puedes tener un método de inicio de sesión a la vez. El cambio del método de inicio de sesión te enviará un correo notificandote que el cambio se realizó con éxito.",
+ "user.settings.security.password": "Contraseña",
+ "user.settings.security.passwordLengthError": "La nueva contraseña debe contener al menos {chars} carácteres",
+ "user.settings.security.passwordMatchError": "La nueva contraseña que ingresaste no coincide",
+ "user.settings.security.retypePassword": "Reescribe la Nueva Contraseña",
+ "user.settings.security.switchEmail": "Cambiar para utilizar correo electrónico y contraseña",
+ "user.settings.security.switchGitlab": "Cambiar para utilizar GitLab SSO",
+ "user.settings.security.switchGoogle": "Cambiar para utilizar Google SSO",
+ "user.settings.security.title": "Configuración de Seguridad",
+ "user.settings.security.viewHistory": "Visualizar historial de acceso",
+ "user_profile.notShared": "Correo no compartido"
} \ No newline at end of file
diff --git a/web/web_test.go b/web/web_test.go
index efe681a55..cc7d22559 100644
--- a/web/web_test.go
+++ b/web/web_test.go
@@ -61,7 +61,7 @@ func TestGetAccessToken(t *testing.T) {
team := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
rteam, _ := ApiClient.CreateTeam(&team)
- user := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "corey+test@test.com", Password: "pwd"}
+ user := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Password: "pwd"}
ruser := ApiClient.Must(ApiClient.CreateUser(&user, "")).Data.(*model.User)
store.Must(api.Srv.Store.User().VerifyEmail(ruser.Id))
@@ -190,7 +190,7 @@ func TestIncomingWebhook(t *testing.T) {
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = ApiClient.Must(ApiClient.CreateTeam(team)).Data.(*model.Team)
- user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user = ApiClient.Must(ApiClient.CreateUser(user, "")).Data.(*model.User)
store.Must(api.Srv.Store.User().VerifyEmail(user.Id))