summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
author=Corey Hulen <corey@hulen.com>2016-02-01 14:44:17 -0800
committer=Corey Hulen <corey@hulen.com>2016-02-01 14:44:17 -0800
commitb4ec6900510077253290e361d1a706e5368a45de (patch)
tree6fc7c131fa7aa5f32e3aba8102416aa23c65963d
parentea71731f838fc010cfc7511c09875184d1b2396b (diff)
parentf28486c4553f7f4bccf7bf69153c2f12699705f9 (diff)
downloadchat-b4ec6900510077253290e361d1a706e5368a45de.tar.gz
chat-b4ec6900510077253290e361d1a706e5368a45de.tar.bz2
chat-b4ec6900510077253290e361d1a706e5368a45de.zip
Fixing merge
-rw-r--r--Makefile4
-rw-r--r--api/admin.go4
-rw-r--r--api/admin_test.go14
-rw-r--r--api/api_test.go2
-rw-r--r--api/channel.go2
-rw-r--r--api/channel_benchmark_test.go2
-rw-r--r--api/channel_test.go58
-rw-r--r--api/command_test.go8
-rw-r--r--api/context.go1
-rw-r--r--api/file.go8
-rw-r--r--api/file_test.go10
-rw-r--r--api/license.go5
-rw-r--r--api/oauth_test.go4
-rw-r--r--api/post.go1
-rw-r--r--api/post_test.go58
-rw-r--r--api/preference_test.go14
-rw-r--r--api/team.go2
-rw-r--r--api/team_test.go24
-rw-r--r--api/user.go47
-rw-r--r--api/user_test.go86
-rw-r--r--api/web_socket_test.go4
-rw-r--r--api/webhook_test.go14
-rw-r--r--i18n/en.json1168
-rw-r--r--i18n/es.json1158
-rw-r--r--mattermost.go24
-rw-r--r--model/client.go11
-rw-r--r--model/search_params.go18
-rw-r--r--model/search_params_test.go10
-rw-r--r--model/utils.go20
-rw-r--r--model/utils_test.go43
-rw-r--r--model/version.go69
-rw-r--r--model/version_test.go52
-rw-r--r--store/sql_audit_store.go10
-rw-r--r--store/sql_channel_store.go90
-rw-r--r--store/sql_oauth_store.go36
-rw-r--r--store/sql_post_store.go50
-rw-r--r--store/sql_post_store_test.go7
-rw-r--r--store/sql_preference_store.go30
-rw-r--r--store/sql_session_store.go45
-rw-r--r--store/sql_session_store_test.go22
-rw-r--r--store/sql_store.go131
-rw-r--r--store/sql_store_test.go1
-rw-r--r--store/sql_system_store.go6
-rw-r--r--store/sql_team_store.go36
-rw-r--r--store/sql_user_store.go73
-rw-r--r--store/sql_webhook_store.go34
-rw-r--r--store/store.go1
-rw-r--r--utils/i18n.go22
-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.jsx104
-rw-r--r--web/react/components/admin_console/admin_navbar_dropdown.jsx25
-rw-r--r--web/react/components/admin_console/admin_sidebar.jsx134
-rw-r--r--web/react/components/admin_console/admin_sidebar_header.jsx9
-rw-r--r--web/react/components/admin_console/analytics.jsx123
-rw-r--r--web/react/components/admin_console/email_settings.jsx364
-rw-r--r--web/react/components/admin_console/gitlab_settings.jsx150
-rw-r--r--web/react/components/admin_console/image_settings.jsx295
-rw-r--r--web/react/components/admin_console/ldap_settings.jsx266
-rw-r--r--web/react/components/admin_console/legal_and_support_settings.jsx100
-rw-r--r--web/react/components/admin_console/license_settings.jsx153
-rw-r--r--web/react/components/admin_console/log_settings.jsx173
-rw-r--r--web/react/components/admin_console/logs.jsx14
-rw-r--r--web/react/components/admin_console/privacy_settings.jsx72
-rw-r--r--web/react/components/admin_console/rate_settings.jsx141
-rw-r--r--web/react/components/admin_console/reset_password_modal.jsx38
-rw-r--r--web/react/components/admin_console/select_team_modal.jsx21
-rw-r--r--web/react/components/admin_console/service_settings.jsx322
-rw-r--r--web/react/components/admin_console/sql_settings.jsx145
-rw-r--r--web/react/components/admin_console/system_analytics.jsx29
-rw-r--r--web/react/components/admin_console/team_analytics.jsx23
-rw-r--r--web/react/components/admin_console/team_settings.jsx171
-rw-r--r--web/react/components/admin_console/team_users.jsx23
-rw-r--r--web/react/components/admin_console/user_item.jsx213
-rw-r--r--web/react/components/authorize.jsx85
-rw-r--r--web/react/components/center_panel.jsx6
-rw-r--r--web/react/components/change_url_modal.jsx54
-rw-r--r--web/react/components/claim/claim_account.jsx11
-rw-r--r--web/react/components/claim/email_to_sso.jsx61
-rw-r--r--web/react/components/claim/sso_to_email.jsx72
-rw-r--r--web/react/components/confirm_modal.jsx6
-rw-r--r--web/react/components/create_comment.jsx52
-rw-r--r--web/react/components/docs.jsx2
-rw-r--r--web/react/components/email_verify.jsx54
-rw-r--r--web/react/components/error_bar.jsx26
-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/find_team.jsx65
-rw-r--r--web/react/components/get_link_modal.jsx22
-rw-r--r--web/react/components/get_team_invite_link_modal.jsx27
-rw-r--r--web/react/components/invite_member_modal.jsx126
-rw-r--r--web/react/components/loading_screen.jsx9
-rw-r--r--web/react/components/login.jsx101
-rw-r--r--web/react/components/login_email.jsx49
-rw-r--r--web/react/components/login_ldap.jsx48
-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.jsx56
-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.jsx123
-rw-r--r--web/react/components/password_reset_form.jsx64
-rw-r--r--web/react/components/password_reset_send_link.jsx63
-rw-r--r--web/react/components/post_info.jsx28
-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/signup_team.jsx43
-rw-r--r--web/react/components/signup_team_complete.jsx11
-rw-r--r--web/react/components/signup_team_confirm.jsx39
-rw-r--r--web/react/components/signup_user_complete.jsx151
-rw-r--r--web/react/components/suggestion/at_mention_provider.jsx16
-rw-r--r--web/react/components/suggestion/search_suggestion_list.jsx17
-rw-r--r--web/react/components/team_export_tab.jsx44
-rw-r--r--web/react/components/team_general_tab.jsx176
-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/team_signup_choose_auth.jsx42
-rw-r--r--web/react/components/team_signup_display_name_page.jsx45
-rw-r--r--web/react/components/team_signup_email_item.jsx29
-rw-r--r--web/react/components/team_signup_password_page.jsx83
-rw-r--r--web/react/components/team_signup_send_invites_page.jsx46
-rw-r--r--web/react/components/team_signup_url_page.jsx74
-rw-r--r--web/react/components/team_signup_username_page.jsx69
-rw-r--r--web/react/components/team_signup_welcome_page.jsx75
-rw-r--r--web/react/components/team_signup_with_email.jsx34
-rw-r--r--web/react/components/team_signup_with_sso.jsx52
-rw-r--r--web/react/components/textbox.jsx20
-rw-r--r--web/react/components/tutorial/tutorial_intro_screens.jsx65
-rw-r--r--web/react/components/tutorial/tutorial_tip.jsx27
-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/dispatcher/event_helpers.jsx3
-rw-r--r--web/react/package.json15
-rw-r--r--web/react/pages/channel.jsx159
-rw-r--r--web/react/pages/find_team.jsx59
-rw-r--r--web/react/pages/signup_team.jsx2
-rw-r--r--web/react/pages/signup_team_confirm.jsx64
-rw-r--r--web/react/stores/channel_store.jsx56
-rw-r--r--web/react/stores/error_store.jsx4
-rw-r--r--web/react/stores/post_store.jsx4
-rw-r--r--web/react/stores/preference_store.jsx21
-rw-r--r--web/react/stores/socket_store.jsx3
-rw-r--r--web/react/utils/constants.jsx1
-rw-r--r--web/react/utils/utils.jsx2
-rw-r--r--web/sass-files/sass/partials/_admin-console.scss9
-rw-r--r--web/sass-files/sass/partials/_content.scss1
-rw-r--r--web/sass-files/sass/partials/_markdown.scss2
-rw-r--r--web/sass-files/sass/partials/_modal.scss5
-rw-r--r--web/sass-files/sass/partials/_post.scss22
-rw-r--r--web/sass-files/sass/partials/_responsive.scss72
-rw-r--r--web/sass-files/sass/partials/_tutorial.scss1
l---------web/static/help/Messaging.md1
-rw-r--r--web/static/help/Messaging_en.md (renamed from doc/help/Messaging.md)0
-rw-r--r--web/static/help/Messaging_es.md37
-rw-r--r--web/static/i18n/en.json1047
-rw-r--r--web/static/i18n/es.json1048
-rw-r--r--web/templates/authorize.html18
-rw-r--r--web/templates/channel.html22
-rw-r--r--web/templates/claim_account.html18
-rw-r--r--web/templates/docs.html3
-rw-r--r--web/templates/find_team.html5
-rw-r--r--web/templates/footer.html8
-rw-r--r--web/templates/head.html20
-rw-r--r--web/templates/login.html2
-rw-r--r--web/templates/password_reset.html16
-rw-r--r--web/templates/signup_team.html2
-rw-r--r--web/templates/signup_team_confirm.html9
-rw-r--r--web/templates/verify.html11
-rw-r--r--web/templates/welcome.html37
-rw-r--r--web/web.go156
-rw-r--r--web/web_test.go4
201 files changed, 12602 insertions, 2301 deletions
diff --git a/Makefile b/Makefile
index c6668fbd4..9c4e6ee1f 100644
--- a/Makefile
+++ b/Makefile
@@ -197,7 +197,7 @@ travis-init:
build-container:
@echo Building in container
- docker run -e TRAVIS_BUILD_NUMBER=$(TRAVIS_BUILD_NUMBER) --link mattermost-mysql:mysql --link mattermost-postgres:postgres -v `pwd`:/go/src/github.com/mattermost/platform mattermost/builder:latest
+ cd .. && docker run -e TRAVIS_BUILD_NUMBER=$(TRAVIS_BUILD_NUMBER) --link mattermost-mysql:mysql --link mattermost-postgres:postgres -v `pwd`:/go/src/github.com/mattermost mattermost/builder:latest
stop-docker:
@echo Stopping docker containers
@@ -255,7 +255,7 @@ nuke: | clean clean-docker
touch $@
-.prepare-jsx:
+.prepare-jsx: web/react/package.json
@echo Preparation for compiling jsx code
cd web/react/ && npm install
diff --git a/api/admin.go b/api/admin.go
index b19772fdf..0ea6341e2 100644
--- a/api/admin.go
+++ b/api/admin.go
@@ -73,7 +73,9 @@ func logClient(c *Context, w http.ResponseWriter, r *http.Request) {
}
if lvl == "ERROR" {
- err := model.NewAppError("client", msg, "")
+ err := &model.AppError{}
+ err.Message = msg
+ err.Where = "client"
c.LogError(err)
}
diff --git a/api/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.go b/api/channel.go
index de39ad1a8..a27b3c1bf 100644
--- a/api/channel.go
+++ b/api/channel.go
@@ -368,7 +368,7 @@ func getChannels(c *Context, w http.ResponseWriter, r *http.Request) {
// user is already in the team
if result := <-Srv.Store.Channel().GetChannels(c.Session.TeamId, c.Session.UserId); result.Err != nil {
- if result.Err.Message == "No channels were found" { // store translation dependant
+ if result.Err.Id == "store.sql_channel.get_channels.not_found.app_error" {
// lets make sure the user is valid
if result := <-Srv.Store.User().Get(c.Session.UserId); result.Err != nil {
c.Err = result.Err
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 541e62e51..22e2bd666 100644
--- a/api/command_test.go
+++ b/api/command_test.go
@@ -18,7 +18,7 @@ func TestListCommands(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))
@@ -111,7 +111,7 @@ func TestListTeamCommands(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))
@@ -182,7 +182,7 @@ func TestDeleteCommand(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))
@@ -219,7 +219,7 @@ func TestTestCommand(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/context.go b/api/context.go
index 41a52fa0c..b91981ecd 100644
--- a/api/context.go
+++ b/api/context.go
@@ -45,6 +45,7 @@ type Page struct {
User *model.User
Team *model.Team
Channel *model.Channel
+ Preferences *model.Preferences
PostID string
SessionTokenIndex int64
Locale string
diff --git a/api/file.go b/api/file.go
index 44ae775c9..7dcfc691f 100644
--- a/api/file.go
+++ b/api/file.go
@@ -398,13 +398,6 @@ func getFile(c *Context, w http.ResponseWriter, r *http.Request) {
c.Err = model.NewLocAppError("getFile", "api.file.get_file.public_invalid.app_error", nil, "")
return
}
- props := model.MapFromJson(strings.NewReader(data))
-
- t, err := strconv.ParseInt(props["time"], 10, 64)
- if err != nil || model.GetMillis()-t > 1000*60*60*24*7 { // one week
- c.Err = model.NewLocAppError("getFile", "api.file.get_file.public_expired.app_error", nil, "")
- return
- }
} else if !c.HasPermissionsToChannel(cchan, "getFile") {
return
}
@@ -484,7 +477,6 @@ func getPublicLink(c *Context, w http.ResponseWriter, r *http.Request) {
newProps := make(map[string]string)
newProps["filename"] = filename
- newProps["time"] = fmt.Sprintf("%v", model.GetMillis())
data := model.MapToJson(newProps)
hash := model.HashPassword(fmt.Sprintf("%v:%v", data, utils.Cfg.FileSettings.PublicLinkSalt))
diff --git a/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/license.go b/api/license.go
index 5c602a68e..4077c0e46 100644
--- a/api/license.go
+++ b/api/license.go
@@ -5,7 +5,6 @@ package api
import (
"bytes"
- "fmt"
l4g "github.com/alecthomas/log4go"
"github.com/gorilla/mux"
"github.com/mattermost/platform/model"
@@ -65,13 +64,13 @@ func addLicense(c *Context, w http.ResponseWriter, r *http.Request) {
license = model.LicenseFromJson(strings.NewReader(licenseStr))
if result := <-Srv.Store.User().AnalyticsUniqueUserCount(""); result.Err != nil {
- c.Err = model.NewAppError("addLicense", "Unable to count total unique users.", fmt.Sprintf("err=%v", result.Err.Error()))
+ c.Err = model.NewLocAppError("addLicense", "api.license.add_license.invalid_count.app_error", nil, result.Err.Error())
return
} else {
uniqueUserCount := result.Data.(int64)
if uniqueUserCount > int64(*license.Features.Users) {
- c.Err = model.NewAppError("addLicense", fmt.Sprintf("This license only supports %d users, when your system has %d unique users. Unique users are counted distinctly by email address. You can see total user count under Site Reports -> View Statistics.", *license.Features.Users, uniqueUserCount), "")
+ c.Err = model.NewLocAppError("addLicense", "api.license.add_license.unique_users.app_error", map[string]interface{}{"Users": *license.Features.Users, "Count": uniqueUserCount}, "")
return
}
}
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.go b/api/post.go
index d3807653d..e8345b5e5 100644
--- a/api/post.go
+++ b/api/post.go
@@ -698,6 +698,7 @@ func sendNotificationsAndForget(c *Context, post *model.Post, team *model.Team,
message := model.NewMessage(c.Session.TeamId, post.ChannelId, post.UserId, model.ACTION_POSTED)
message.Add("post", post.ToJson())
+ message.Add("channel_type", channel.Type)
if len(post.Filenames) != 0 {
message.Add("otherFile", "true")
diff --git a/api/post_test.go b/api/post_test.go
index a0b8cc9bd..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))
@@ -601,13 +601,11 @@ func TestSearchPostsFromUser(t *testing.T) {
post2 := &model.Post{ChannelId: channel2.Id, Message: "sgtitlereview\n with return"}
post2 = Client.Must(Client.CreatePost(post2)).Data.(*model.Post)
- // includes "X has joined the channel" messages for both user2 and user3
-
if result := Client.Must(Client.SearchPosts("from: " + user1.Username)).Data.(*model.PostList); len(result.Order) != 1 {
t.Fatalf("wrong number of posts returned %v", len(result.Order))
}
- if result := Client.Must(Client.SearchPosts("from: " + user2.Username)).Data.(*model.PostList); len(result.Order) != 3 {
+ if result := Client.Must(Client.SearchPosts("from: " + user2.Username)).Data.(*model.PostList); len(result.Order) != 1 {
t.Fatalf("wrong number of posts returned %v", len(result.Order))
}
@@ -615,11 +613,14 @@ func TestSearchPostsFromUser(t *testing.T) {
t.Fatalf("wrong number of posts returned %v", len(result.Order))
}
+ post3 := &model.Post{ChannelId: channel1.Id, Message: "hullo"}
+ post3 = Client.Must(Client.CreatePost(post3)).Data.(*model.Post)
+
if result := Client.Must(Client.SearchPosts("from: " + user2.Username + " in:" + channel1.Name)).Data.(*model.PostList); len(result.Order) != 1 {
t.Fatalf("wrong number of posts returned %v", len(result.Order))
}
- 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))
@@ -630,19 +631,22 @@ func TestSearchPostsFromUser(t *testing.T) {
// wait for the join/leave messages to be created for user3 since they're done asynchronously
time.Sleep(100 * time.Millisecond)
- if result := Client.Must(Client.SearchPosts("from: " + user2.Username)).Data.(*model.PostList); len(result.Order) != 3 {
+ if result := Client.Must(Client.SearchPosts("from: " + user2.Username)).Data.(*model.PostList); len(result.Order) != 2 {
t.Fatalf("wrong number of posts returned %v", len(result.Order))
}
- if result := Client.Must(Client.SearchPosts("from: " + user2.Username + " from: " + user3.Username)).Data.(*model.PostList); len(result.Order) != 5 {
+ if result := Client.Must(Client.SearchPosts("from: " + user2.Username + " from: " + user3.Username)).Data.(*model.PostList); len(result.Order) != 2 {
t.Fatalf("wrong number of posts returned %v", len(result.Order))
}
- if result := Client.Must(Client.SearchPosts("from: " + user2.Username + " from: " + user3.Username + " in:" + channel2.Name)).Data.(*model.PostList); len(result.Order) != 3 {
+ if result := Client.Must(Client.SearchPosts("from: " + user2.Username + " from: " + user3.Username + " in:" + channel2.Name)).Data.(*model.PostList); len(result.Order) != 1 {
t.Fatalf("wrong number of posts returned %v", len(result.Order))
}
- if result := Client.Must(Client.SearchPosts("from: " + user2.Username + " from: " + user3.Username + " in:" + channel2.Name + " joined")).Data.(*model.PostList); len(result.Order) != 2 {
+ post4 := &model.Post{ChannelId: channel2.Id, Message: "coconut"}
+ post4 = Client.Must(Client.CreatePost(post4)).Data.(*model.Post)
+
+ if result := Client.Must(Client.SearchPosts("from: " + user2.Username + " from: " + user3.Username + " in:" + channel2.Name + " coconut")).Data.(*model.PostList); len(result.Order) != 1 {
t.Fatalf("wrong number of posts returned %v", len(result.Order))
}
}
@@ -653,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))
@@ -706,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))
@@ -763,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))
@@ -785,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))
@@ -812,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.go b/api/team.go
index 779a6affe..8b25e3316 100644
--- a/api/team.go
+++ b/api/team.go
@@ -494,7 +494,7 @@ func inviteMembers(c *Context, w http.ResponseWriter, r *http.Request) {
var invNum int64 = 0
for i, invite := range invites.Invites {
- if result := <-Srv.Store.User().GetByEmail(c.Session.TeamId, invite["email"]); result.Err == nil || result.Err.Message != store.MISSING_ACCOUNT_ERROR {
+ if result := <-Srv.Store.User().GetByEmail(c.Session.TeamId, invite["email"]); result.Err == nil || result.Err.Id != store.MISSING_ACCOUNT_ERROR {
invNum = int64(i)
c.Err = model.NewLocAppError("invite_members", "api.team.invite_members.already.app_error", nil, strconv.FormatInt(invNum, 10))
return
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 33fe6f6dd..27afbd469 100644
--- a/api/user.go
+++ b/api/user.go
@@ -48,6 +48,7 @@ func InitUser(r *mux.Router) {
sr.Handle("/logout", ApiUserRequired(logout)).Methods("POST")
sr.Handle("/login_ldap", ApiAppHandler(loginLdap)).Methods("POST")
sr.Handle("/revoke_session", ApiUserRequired(revokeSession)).Methods("POST")
+ sr.Handle("/attach_device", ApiUserRequired(attachDeviceId)).Methods("POST")
sr.Handle("/switch_to_sso", ApiAppHandler(switchToSSO)).Methods("POST")
sr.Handle("/switch_to_email", ApiUserRequired(switchToEmail)).Methods("POST")
@@ -546,7 +547,6 @@ func Login(c *Context, w http.ResponseWriter, r *http.Request, user *model.User,
}
}
}
-
} else {
session.SetExpireInDays(*utils.Cfg.ServiceSettings.SessionLengthWebInDays)
}
@@ -718,6 +718,49 @@ func revokeSession(c *Context, w http.ResponseWriter, r *http.Request) {
w.Write([]byte(model.MapToJson(props)))
}
+func attachDeviceId(c *Context, w http.ResponseWriter, r *http.Request) {
+ props := model.MapFromJson(r.Body)
+
+ deviceId := props["device_id"]
+ if len(deviceId) == 0 {
+ c.SetInvalidParam("attachDevice", "deviceId")
+ return
+ }
+
+ if !(strings.HasPrefix(deviceId, model.PUSH_NOTIFY_APPLE+":") || strings.HasPrefix(deviceId, model.PUSH_NOTIFY_ANDROID+":")) {
+ c.SetInvalidParam("attachDevice", "deviceId")
+ return
+ }
+
+ // A special case where we logout of all other sessions with the same Id
+ if result := <-Srv.Store.Session().GetSessions(c.Session.UserId); result.Err != nil {
+ c.Err = result.Err
+ c.Err.StatusCode = http.StatusForbidden
+ return
+ } else {
+ sessions := result.Data.([]*model.Session)
+ for _, session := range sessions {
+ if session.DeviceId == deviceId && session.Id != c.Session.Id {
+ l4g.Debug(utils.T("api.user.login.revoking.app_error"), session.Id, c.Session.UserId)
+ RevokeSessionById(c, session.Id)
+ if c.Err != nil {
+ c.LogError(c.Err)
+ c.Err = nil
+ }
+ }
+ }
+ }
+
+ sessionCache.Remove(c.Session.Token)
+
+ if result := <-Srv.Store.Session().UpdateDeviceId(c.Session.Id, deviceId); result.Err != nil {
+ c.Err = result.Err
+ return
+ }
+
+ w.Write([]byte(deviceId))
+}
+
func RevokeSessionById(c *Context, sessionId string) {
if result := <-Srv.Store.Session().Get(sessionId); result.Err != nil {
c.Err = result.Err
@@ -1114,7 +1157,7 @@ func uploadProfileImage(c *Context, w http.ResponseWriter, r *http.Request) {
path := "teams/" + c.Session.TeamId + "/users/" + c.Session.UserId + "/profile.png"
if err := writeFile(buf.Bytes(), path); err != nil {
- c.Err = model.NewAppError("uploadProfileImage", "Couldn't upload profile image", "")
+ c.Err = model.NewLocAppError("uploadProfileImage", "api.user.upload_profile_user.upload_profile.app_error", nil, "")
return
}
diff --git a/api/user_test.go b/api/user_test.go
index 9a172805a..b2ae113f1 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", Password: "pwd"}
ruser, _ := Client.CreateUser(&user, "")
store.Must(Srv.Store.User().VerifyEmail(ruser.Data.(*model.User).Id))
@@ -139,7 +139,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 +168,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 +199,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 +251,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 +343,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 +396,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 +430,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 +536,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 +592,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 +611,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 +654,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 +674,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))
@@ -734,6 +734,34 @@ func TestUserUpdateRoles(t *testing.T) {
}
}
+func TestUserUpdateDeviceId(t *testing.T) {
+ Setup()
+
+ team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
+ team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
+
+ user := &model.User{TeamId: team.Id, Email: "test@nowhere.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
+ store.Must(Srv.Store.User().VerifyEmail(user.Id))
+
+ Client.LoginByEmail(team.Name, user.Email, "pwd")
+ deviceId := model.PUSH_NOTIFY_APPLE + ":1234567890"
+
+ if _, err := Client.AttachDeviceId(deviceId); err != nil {
+ t.Fatal(err)
+ }
+
+ if result := <-Srv.Store.Session().GetSessions(user.Id); result.Err != nil {
+ t.Fatal(result.Err)
+ } else {
+ sessions := result.Data.([]*model.Session)
+
+ if sessions[0].DeviceId != deviceId {
+ t.Fatal("Missing device Id")
+ }
+ }
+}
+
func TestUserUpdateActive(t *testing.T) {
Setup()
@@ -744,7 +772,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))
@@ -804,7 +832,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))
@@ -843,7 +871,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))
@@ -876,7 +904,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))
@@ -893,7 +921,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))
@@ -970,7 +998,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))
@@ -991,7 +1019,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))
@@ -1086,11 +1114,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))
@@ -1123,7 +1151,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))
@@ -1172,11 +1200,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 89c06317f..4f85d178d 100644
--- a/api/webhook_test.go
+++ b/api/webhook_test.go
@@ -25,7 +25,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))
@@ -100,7 +100,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))
@@ -150,7 +150,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))
@@ -202,7 +202,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))
@@ -277,7 +277,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))
@@ -327,7 +327,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))
@@ -379,7 +379,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/i18n/en.json b/i18n/en.json
index 6a7a858e7..0f219be0e 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",
@@ -456,10 +456,6 @@
"translation": "Could not find file."
},
{
- "id": "api.file.get_file.public_expired.app_error",
- "translation": "The public link has expired"
- },
- {
"id": "api.file.get_file.public_invalid.app_error",
"translation": "The public link does not appear to be valid"
},
@@ -580,6 +576,10 @@
"translation": "Invalid license file."
},
{
+ "id": "api.license.add_license.invalid_count.app_error",
+ "translation": "Unable to count total unique users."
+ },
+ {
"id": "api.license.add_license.no_file.app_error",
"translation": "No file under 'license' in request"
},
@@ -592,6 +592,10 @@
"translation": "License did not save properly."
},
{
+ "id": "api.license.add_license.unique_users.app_error",
+ "translation": "This license only supports {{.Users}} users, when your system has {{.Count}} unique users. Unique users are counted distinctly by email address. You can see total user count under Site Reports -> View Statistics."
+ },
+ {
"id": "api.license.init.debug",
"translation": "Initializing license api routes"
},
@@ -953,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",
@@ -1077,7 +1081,7 @@
},
{
"id": "api.templates.error.link",
- "translation": "Go back to team site"
+ "translation": "Go back to Mattermost"
},
{
"id": "api.templates.error.title",
@@ -1149,7 +1153,7 @@
},
{
"id": "api.templates.reset_body.info",
- "translation": "To change your password, click \"Reset Password\" below.<br>If you did not mean to reset your password, please ignore this email and your password will remain the same."
+ "translation": "To change your password, click \"Reset Password\" below.<br>If you did not mean to reset your password, please ignore this email and your password will remain the same. The password reset link expires in 24 hours."
},
{
"id": "api.templates.reset_body.title",
@@ -1429,7 +1433,7 @@
},
{
"id": "api.user.reset_password.link_expired.app_error",
- "translation": "The reset link has expired"
+ "translation": "The password reset link has expired"
},
{
"id": "api.user.reset_password.method",
@@ -1564,6 +1568,10 @@
"translation": "Unable to upload profile image. File is too large."
},
{
+ "id": "api.user.upload_profile_user.upload_profile.app_error",
+ "translation": "Couldn't upload profile image"
+ },
+ {
"id": "api.web_conn.new_web_conn.last_activity.error",
"translation": "Failed to update LastActivityAt for user_id=%v and session_id=%v, err=%v"
},
@@ -1672,10 +1680,50 @@
"translation": "Unable to get channels"
},
{
+ "id": "mattermost.bulletin.subject",
+ "translation": "Mattermost Security Bulletin"
+ },
+ {
+ "id": "mattermost.config_file",
+ "translation": "Loaded config file from %v"
+ },
+ {
"id": "mattermost.current_version",
"translation": "Current version is %v (%v/%v/%v)"
},
{
+ "id": "mattermost.entreprise_enabled",
+ "translation": "Enterprise Enabled: %v"
+ },
+ {
+ "id": "mattermost.security_bulletin.error",
+ "translation": "Failed to get security bulletin details"
+ },
+ {
+ "id": "mattermost.security_bulletin_read.error",
+ "translation": "Failed to read security bulletin details"
+ },
+ {
+ "id": "mattermost.security_checks.debug",
+ "translation": "Checking for security update from Mattermost"
+ },
+ {
+ "id": "mattermost.security_info.error",
+ "translation": "Failed to get security update information from Mattermost."
+ },
+ {
+ "id": "mattermost.send_bulletin.info",
+ "translation": "Sending security bulletin for %v to %v"
+ },
+ {
+ "id": "mattermost.system_admins.error",
+ "translation": "Failed to get system admins for security update information from Mattermost."
+ },
+ {
+ "id": "mattermost.working_dir",
+ "translation": "Current working directory is %v"
+ },
+ {
"id": "model.access.is_valid.access_token.app_error",
"translation": "Invalid access token"
},
@@ -2156,6 +2204,846 @@
"translation": "could not decode"
},
{
+ "id": "store.sql.check_index.critical",
+ "translation": "Failed to check index %v"
+ },
+ {
+ "id": "store.sql.closing.info",
+ "translation": "Closing SqlStore"
+ },
+ {
+ "id": "store.sql.column_exists.critical",
+ "translation": "Failed to check if column exists %v"
+ },
+ {
+ "id": "store.sql.column_exists_missing_driver.critical",
+ "translation": "Failed to check if column exists because of missing driver"
+ },
+ {
+ "id": "store.sql.convert_encrypt_string_map",
+ "translation": "FromDb: Unable to convert EncryptStringMap to *string"
+ },
+ {
+ "id": "store.sql.convert_string_array",
+ "translation": "FromDb: Unable to convert StringArray to *string"
+ },
+ {
+ "id": "store.sql.convert_string_interface",
+ "translation": "FromDb: Unable to convert StringInterface to *string"
+ },
+ {
+ "id": "store.sql.convert_string_map",
+ "translation": "FromDb: Unable to convert StringMap to *string"
+ },
+ {
+ "id": "store.sql.create_column.critical",
+ "translation": "Failed to create column %v"
+ },
+ {
+ "id": "store.sql.create_column_missing_driver.critical",
+ "translation": "Failed to create column because of missing driver"
+ },
+ {
+ "id": "store.sql.create_index.critical",
+ "translation": "Failed to create index %v"
+ },
+ {
+ "id": "store.sql.create_index_missing_driver.critical",
+ "translation": "Failed to create index because of missing driver"
+ },
+ {
+ "id": "store.sql.creating_tables.critical",
+ "translation": "Error creating database tables: %v"
+ },
+ {
+ "id": "store.sql.dialect_driver.critical",
+ "translation": "Failed to create dialect specific driver"
+ },
+ {
+ "id": "store.sql.dialect_driver.panic",
+ "translation": "Failed to create dialect specific driver %v"
+ },
+ {
+ "id": "store.sql.drop_column.critical",
+ "translation": "Failed to drop column %v"
+ },
+ {
+ "id": "store.sql.incorrect_mac",
+ "translation": "Incorrect MAC for the given ciphertext"
+ },
+ {
+ "id": "store.sql.open_conn.critical",
+ "translation": "Failed to open sql connection to err:%v"
+ },
+ {
+ "id": "store.sql.open_conn.panic",
+ "translation": "Failed to open sql connection %v"
+ },
+ {
+ "id": "store.sql.ping.critical",
+ "translation": "Failed to ping db err:%v"
+ },
+ {
+ "id": "store.sql.pinging.info",
+ "translation": "Pinging sql %v database"
+ },
+ {
+ "id": "store.sql.rename_column.critical",
+ "translation": "Failed to rename column %v"
+ },
+ {
+ "id": "store.sql.schema_out_of_date.warn",
+ "translation": "The database schema version of %v appears to be out of date"
+ },
+ {
+ "id": "store.sql.schema_set.info",
+ "translation": "The database schema has been set to version %v"
+ },
+ {
+ "id": "store.sql.schema_upgrade_attempt.warn",
+ "translation": "Attempting to upgrade the database schema version to %v"
+ },
+ {
+ "id": "store.sql.schema_version.critical",
+ "translation": "The database schema version of %v cannot be upgraded. You must not skip a version."
+ },
+ {
+ "id": "store.sql.short_ciphertext",
+ "translation": "short ciphertext"
+ },
+ {
+ "id": "store.sql.table_column_type.critical",
+ "translation": "Failed to get data type for column %s from table %s: %v"
+ },
+ {
+ "id": "store.sql.table_exists.critical",
+ "translation": "Failed to check if table exists %v"
+ },
+ {
+ "id": "store.sql.too_short_ciphertext",
+ "translation": "ciphertext too short"
+ },
+ {
+ "id": "store.sql.upgraded.warn",
+ "translation": "The database schema has been upgraded to version %v"
+ },
+ {
+ "id": "store.sql_audit.get.finding.app_error",
+ "translation": "We encountered an error finding the audits"
+ },
+ {
+ "id": "store.sql_audit.get.limit.app_error",
+ "translation": "Limit exceeded for paging"
+ },
+ {
+ "id": "store.sql_audit.permanent_delete_by_user.app_error",
+ "translation": "We encountered an error deleting the audits"
+ },
+ {
+ "id": "store.sql_audit.save.saving.app_error",
+ "translation": "We encountered an error saving the audit"
+ },
+ {
+ "id": "store.sql_channel.analytics_type_count.app_error",
+ "translation": "We couldn't get channel type counts"
+ },
+ {
+ "id": "store.sql_channel.check_open_channel_permissions.app_error",
+ "translation": "We couldn't check the permissions"
+ },
+ {
+ "id": "store.sql_channel.check_permissions.app_error",
+ "translation": "We couldn't check the permissions"
+ },
+ {
+ "id": "store.sql_channel.check_permissions_by_name.app_error",
+ "translation": "We couldn't check the permissions"
+ },
+ {
+ "id": "store.sql_channel.delete.channel.app_error",
+ "translation": "We couldn't delete the channel"
+ },
+ {
+ "id": "store.sql_channel.extra_updated.app_error",
+ "translation": "Problem updating members last updated time"
+ },
+ {
+ "id": "store.sql_channel.get.existing.app_error",
+ "translation": "We couldn't find the existing channel"
+ },
+ {
+ "id": "store.sql_channel.get.find.app_error",
+ "translation": "We encountered an error finding the channel"
+ },
+ {
+ "id": "store.sql_channel.get_by_name.existing.app_error",
+ "translation": "We couldn't find the existing channel"
+ },
+ {
+ "id": "store.sql_channel.get_channel_counts.get.app_error",
+ "translation": "We couldn't get the channel counts"
+ },
+ {
+ "id": "store.sql_channel.get_channels.get.app_error",
+ "translation": "We couldn't get the channels"
+ },
+ {
+ "id": "store.sql_channel.get_channels.not_found.app_error",
+ "translation": "No channels were found"
+ },
+ {
+ "id": "store.sql_channel.get_extra_members.app_error",
+ "translation": "We couldn't get the extra info for channel members"
+ },
+ {
+ "id": "store.sql_channel.get_for_export.app_error",
+ "translation": "We couldn't get all the channels"
+ },
+ {
+ "id": "store.sql_channel.get_member.app_error",
+ "translation": "We couldn't get the channel member"
+ },
+ {
+ "id": "store.sql_channel.get_member_count.app_error",
+ "translation": "We couldn't get the channel member count"
+ },
+ {
+ "id": "store.sql_channel.get_members.app_error",
+ "translation": "We couldn't get the channel members"
+ },
+ {
+ "id": "store.sql_channel.get_more_channels.get.app_error",
+ "translation": "We couldn't get the channels"
+ },
+ {
+ "id": "store.sql_channel.increment_mention_count.app_error",
+ "translation": "We couldn't increment the mention count"
+ },
+ {
+ "id": "store.sql_channel.permanent_delete_by_team.app_error",
+ "translation": "We couldn't delete the channels"
+ },
+ {
+ "id": "store.sql_channel.permanent_delete_members_by_user.app_error",
+ "translation": "We couldn't remove the channel member"
+ },
+ {
+ "id": "store.sql_channel.remove_member.app_error",
+ "translation": "We couldn't remove the channel member"
+ },
+ {
+ "id": "store.sql_channel.save.commit_transaction.app_error",
+ "translation": "Unable to commit transaction"
+ },
+ {
+ "id": "store.sql_channel.save.direct_channel.app_error",
+ "translation": "Use SaveDirectChannel to create a direct channel"
+ },
+ {
+ "id": "store.sql_channel.save.open_transaction.app_error",
+ "translation": "Unable to open transaction"
+ },
+ {
+ "id": "store.sql_channel.save_channel.current_count.app_error",
+ "translation": "Failed to get current channel count"
+ },
+ {
+ "id": "store.sql_channel.save_channel.existing.app_error",
+ "translation": "Must call update for exisiting channel"
+ },
+ {
+ "id": "store.sql_channel.save_channel.exists.app_error",
+ "translation": "A channel with that URL already exists"
+ },
+ {
+ "id": "store.sql_channel.save_channel.limit.app_error",
+ "translation": "You've reached the limit of the number of allowed channels."
+ },
+ {
+ "id": "store.sql_channel.save_channel.previously.app_error",
+ "translation": "A channel with that URL was previously created"
+ },
+ {
+ "id": "store.sql_channel.save_channel.save.app_error",
+ "translation": "We couldn't save the channel"
+ },
+ {
+ "id": "store.sql_channel.save_direct_channel.add_members.app_error",
+ "translation": "Unable to add direct channel members"
+ },
+ {
+ "id": "store.sql_channel.save_direct_channel.commit.app_error",
+ "translation": "Unable to commit transaction"
+ },
+ {
+ "id": "store.sql_channel.save_direct_channel.not_direct.app_error",
+ "translation": "Not a direct channel attempted to be created with SaveDirectChannel"
+ },
+ {
+ "id": "store.sql_channel.save_direct_channel.open_transaction.app_error",
+ "translation": "Unable to open transaction"
+ },
+ {
+ "id": "store.sql_channel.save_member.commit_transaction.app_error",
+ "translation": "Unable to commit transaction"
+ },
+ {
+ "id": "store.sql_channel.save_member.exists.app_error",
+ "translation": "A channel member with that id already exists"
+ },
+ {
+ "id": "store.sql_channel.save_member.open_transaction.app_error",
+ "translation": "Unable to open transaction"
+ },
+ {
+ "id": "store.sql_channel.save_member.save.app_error",
+ "translation": "We couldn't save the channel member"
+ },
+ {
+ "id": "store.sql_channel.update.app_error",
+ "translation": "We couldn't update the channel"
+ },
+ {
+ "id": "store.sql_channel.update.exists.app_error",
+ "translation": "A channel with that handle already exists"
+ },
+ {
+ "id": "store.sql_channel.update.previously.app_error",
+ "translation": "A channel with that handle was previously created"
+ },
+ {
+ "id": "store.sql_channel.update.updating.app_error",
+ "translation": "We encountered an error updating the channel"
+ },
+ {
+ "id": "store.sql_channel.update_last_viewed_at.app_error",
+ "translation": "We couldn't update the last viewed at time"
+ },
+ {
+ "id": "store.sql_channel.update_member.app_error",
+ "translation": "We encountered an error updating the channel member"
+ },
+ {
+ "id": "store.sql_oauth.get_access_data.app_error",
+ "translation": "We encountered an error finding the access token"
+ },
+ {
+ "id": "store.sql_oauth.get_access_data_by_code.app_error",
+ "translation": "We encountered an error finding the access token"
+ },
+ {
+ "id": "store.sql_oauth.get_app.find.app_error",
+ "translation": "We couldn't find the existing app"
+ },
+ {
+ "id": "store.sql_oauth.get_app.finding.app_error",
+ "translation": "We encountered an error finding the app"
+ },
+ {
+ "id": "store.sql_oauth.get_app_by_user.find.app_error",
+ "translation": "We couldn't find any existing apps"
+ },
+ {
+ "id": "store.sql_oauth.get_auth_data.find.app_error",
+ "translation": "We couldn't find the existing authorization code"
+ },
+ {
+ "id": "store.sql_oauth.get_auth_data.finding.app_error",
+ "translation": "We encountered an error finding the authorization code"
+ },
+ {
+ "id": "store.sql_oauth.permanent_delete_auth_data_by_user.app_error",
+ "translation": "We couldn't remove the authorization code"
+ },
+ {
+ "id": "store.sql_oauth.remove_access_data.app_error",
+ "translation": "We couldn't remove the access token"
+ },
+ {
+ "id": "store.sql_oauth.remove_auth_data.app_error",
+ "translation": "We couldn't remove the authorization code"
+ },
+ {
+ "id": "store.sql_oauth.save_access_data.app_error",
+ "translation": "We couldn't save the access token."
+ },
+ {
+ "id": "store.sql_oauth.save_app.existing.app_error",
+ "translation": "Must call update for exisiting app"
+ },
+ {
+ "id": "store.sql_oauth.save_app.save.app_error",
+ "translation": "We couldn't save the app."
+ },
+ {
+ "id": "store.sql_oauth.save_auth_data.app_error",
+ "translation": "We couldn't save the authorization code."
+ },
+ {
+ "id": "store.sql_oauth.update_app.find.app_error",
+ "translation": "We couldn't find the existing app to update"
+ },
+ {
+ "id": "store.sql_oauth.update_app.finding.app_error",
+ "translation": "We encountered an error finding the app"
+ },
+ {
+ "id": "store.sql_oauth.update_app.update.app_error",
+ "translation": "We couldn't update the app"
+ },
+ {
+ "id": "store.sql_oauth.update_app.updating.app_error",
+ "translation": "We encountered an error updating the app"
+ },
+ {
+ "id": "store.sql_post.analytics_posts_count.app_error",
+ "translation": "We couldn't get post counts"
+ },
+ {
+ "id": "store.sql_post.analytics_posts_count_by_day.app_error",
+ "translation": "We couldn't get post counts by day"
+ },
+ {
+ "id": "store.sql_post.analytics_user_counts_posts_by_day.app_error",
+ "translation": "We couldn't get user counts with posts"
+ },
+ {
+ "id": "store.sql_post.delete.app_error",
+ "translation": "We couldn't delete the post"
+ },
+ {
+ "id": "store.sql_post.get.app_error",
+ "translation": "We couldn't get the post"
+ },
+ {
+ "id": "store.sql_post.get_for_export.app_error",
+ "translation": "We couldn't get the posts for the channel"
+ },
+ {
+ "id": "store.sql_post.get_parents_posts.app_error",
+ "translation": "We couldn't get the parent post for the channel"
+ },
+ {
+ "id": "store.sql_post.get_posts.app_error",
+ "translation": "Limit exceeded for paging"
+ },
+ {
+ "id": "store.sql_post.get_posts_around.get.app_error",
+ "translation": "We couldn't get the posts for the channel"
+ },
+ {
+ "id": "store.sql_post.get_posts_around.get_parent.app_error",
+ "translation": "We couldn't get the parent posts for the channel"
+ },
+ {
+ "id": "store.sql_post.get_posts_since.app_error",
+ "translation": "We couldn't get the posts for the channel"
+ },
+ {
+ "id": "store.sql_post.get_root_posts.app_error",
+ "translation": "We couldn't get the posts for the channel"
+ },
+ {
+ "id": "store.sql_post.permanent_delete.app_error",
+ "translation": "We couldn't delete the post"
+ },
+ {
+ "id": "store.sql_post.permanent_delete_all_comments_by_user.app_error",
+ "translation": "We couldn't delete the comments for user"
+ },
+ {
+ "id": "store.sql_post.permanent_delete_by_user.app_error",
+ "translation": "We couldn't select the posts to delete for the user"
+ },
+ {
+ "id": "store.sql_post.permanent_delete_by_user.too_many.app_error",
+ "translation": "We couldn't select the posts to delete for the user (too many), please re-run"
+ },
+ {
+ "id": "store.sql_post.save.app_error",
+ "translation": "We couldn't save the Post"
+ },
+ {
+ "id": "store.sql_post.save.existing.app_error",
+ "translation": "You cannot update an existing Post"
+ },
+ {
+ "id": "store.sql_post.search.app_error",
+ "translation": "We encountered an error while searching for posts"
+ },
+ {
+ "id": "store.sql_post.update.app_error",
+ "translation": "We couldn't update the Post"
+ },
+ {
+ "id": "store.sql_preference.delete_unused_features.debug",
+ "translation": "Deleting any unused pre-release features"
+ },
+ {
+ "id": "store.sql_preference.get.app_error",
+ "translation": "We encountered an error while finding preferences"
+ },
+ {
+ "id": "store.sql_preference.get_all.app_error",
+ "translation": "We encountered an error while finding preferences"
+ },
+ {
+ "id": "store.sql_preference.get_category.app_error",
+ "translation": "We encountered an error while finding preferences"
+ },
+ {
+ "id": "store.sql_preference.insert.exists.app_error",
+ "translation": "A preference with that user id, category, and name already exists"
+ },
+ {
+ "id": "store.sql_preference.insert.save.app_error",
+ "translation": "We couldn't save the preference"
+ },
+ {
+ "id": "store.sql_preference.is_feature_enabled.app_error",
+ "translation": "We encountered an error while finding a pre release feature preference"
+ },
+ {
+ "id": "store.sql_preference.permanent_delete_by_user.app_error",
+ "translation": "We encountered an error while deleteing preferences"
+ },
+ {
+ "id": "store.sql_preference.save.commit_transaction.app_error",
+ "translation": "Unable to commit transaction to save preferences"
+ },
+ {
+ "id": "store.sql_preference.save.missing_driver.app_error",
+ "translation": "We encountered an error while updating preferences"
+ },
+ {
+ "id": "store.sql_preference.save.open_transaction.app_error",
+ "translation": "Unable to open transaction to save preferences"
+ },
+ {
+ "id": "store.sql_preference.save.rollback_transaction.app_error",
+ "translation": "Unable to rollback transaction to save preferences"
+ },
+ {
+ "id": "store.sql_preference.save.updating.app_error",
+ "translation": "We encountered an error while updating preferences"
+ },
+ {
+ "id": "store.sql_preference.update.app_error",
+ "translation": "We couldn't update the preference"
+ },
+ {
+ "id": "store.sql_session.cleanup_expired_sessions.app_error",
+ "translation": "We encountered an error while deleting expired user sessions"
+ },
+ {
+ "id": "store.sql_session.get.app_error",
+ "translation": "We encountered an error finding the session"
+ },
+ {
+ "id": "store.sql_session.get_sessions.app_error",
+ "translation": "We encountered an error while finding user sessions"
+ },
+ {
+ "id": "store.sql_session.get_sessions.error",
+ "translation": "Failed to cleanup sessions in getSessions err=%v"
+ },
+ {
+ "id": "store.sql_session.permanent_delete_sessions_by_user.app_error",
+ "translation": "We couldn't remove all the sessions for the user"
+ },
+ {
+ "id": "store.sql_session.remove.app_error",
+ "translation": "We couldn't remove the session"
+ },
+ {
+ "id": "store.sql_session.remove_all_sessions_for_team.app_error",
+ "translation": "We couldn't remove all the sessions for the team"
+ },
+ {
+ "id": "store.sql_session.save.app_error",
+ "translation": "We couldn't save the session"
+ },
+ {
+ "id": "store.sql_session.save.cleanup.error",
+ "translation": "Failed to cleanup sessions in Save err=%v"
+ },
+ {
+ "id": "store.sql_session.save.existing.app_error",
+ "translation": "Cannot update existing session"
+ },
+ {
+ "id": "store.sql_session.update_device_id.app_error",
+ "translation": "We couldn't update the device id"
+ },
+ {
+ "id": "store.sql_session.update_last_activity.app_error",
+ "translation": "We couldn't update the last_activity_at"
+ },
+ {
+ "id": "store.sql_session.update_roles.app_error",
+ "translation": "We couldn't update the roles"
+ },
+ {
+ "id": "store.sql_system.get.app_error",
+ "translation": "We encountered an error finding the system properties"
+ },
+ {
+ "id": "store.sql_system.save.app_error",
+ "translation": "We encountered an error saving the system property"
+ },
+ {
+ "id": "store.sql_system.update.app_error",
+ "translation": "We encountered an error updating the system property"
+ },
+ {
+ "id": "store.sql_team.get.find.app_error",
+ "translation": "We couldn't find the existing team"
+ },
+ {
+ "id": "store.sql_team.get.finding.app_error",
+ "translation": "We encountered an error finding the team"
+ },
+ {
+ "id": "store.sql_team.get_all.app_error",
+ "translation": "We could not get all teams"
+ },
+ {
+ "id": "store.sql_team.get_all_team_listing.app_error",
+ "translation": "We could not get all teams"
+ },
+ {
+ "id": "store.sql_team.get_by_invite_id.find.app_error",
+ "translation": "We couldn't find the existing team"
+ },
+ {
+ "id": "store.sql_team.get_by_invite_id.finding.app_error",
+ "translation": "We couldn't find the existing team"
+ },
+ {
+ "id": "store.sql_team.get_by_name.app_error",
+ "translation": "We couldn't find the existing team"
+ },
+ {
+ "id": "store.sql_team.get_teams_for_email.app_error",
+ "translation": "We encountered a problem when looking up teams"
+ },
+ {
+ "id": "store.sql_team.permanent_delete.app_error",
+ "translation": "We couldn't delete the existing team"
+ },
+ {
+ "id": "store.sql_team.save.app_error",
+ "translation": "We couldn't save the team"
+ },
+ {
+ "id": "store.sql_team.save.domain_exists.app_error",
+ "translation": "A team with that domain already exists"
+ },
+ {
+ "id": "store.sql_team.save.existing.app_error",
+ "translation": "Must call update for exisiting team"
+ },
+ {
+ "id": "store.sql_team.update.app_error",
+ "translation": "We couldn't update the team"
+ },
+ {
+ "id": "store.sql_team.update.find.app_error",
+ "translation": "We couldn't find the existing team to update"
+ },
+ {
+ "id": "store.sql_team.update.finding.app_error",
+ "translation": "We encountered an error finding the team"
+ },
+ {
+ "id": "store.sql_team.update.updating.app_error",
+ "translation": "We encountered an error updating the team"
+ },
+ {
+ "id": "store.sql_team.update_display_name.app_error",
+ "translation": "We couldn't update the team name"
+ },
+ {
+ "id": "store.sql_user.analytics_unique_user_count.app_error",
+ "translation": "We couldn't get the unique user count"
+ },
+ {
+ "id": "store.sql_user.get.app_error",
+ "translation": "We encountered an error finding the account"
+ },
+ {
+ "id": "store.sql_user.get_by_auth.app_error",
+ "translation": "We couldn't find an existing account matching your authentication type for this team. This team may require an invite from the team owner to join."
+ },
+ {
+ "id": "store.sql_user.get_by_username.app_error",
+ "translation": "We couldn't find an existing account matching your username for this team. This team may require an invite from the team owner to join."
+ },
+ {
+ "id": "store.sql_user.get_for_export.app_error",
+ "translation": "We encountered an error while finding user profiles"
+ },
+ {
+ "id": "store.sql_user.get_profiles.app_error",
+ "translation": "We encountered an error while finding user profiles"
+ },
+ {
+ "id": "store.sql_user.get_sysadmin_profiles.app_error",
+ "translation": "We encountered an error while finding user profiles"
+ },
+ {
+ "id": "store.sql_user.get_total_active_users_count.app_error",
+ "translation": "We could not count the users"
+ },
+ {
+ "id": "store.sql_user.get_total_users_count.app_error",
+ "translation": "We could not count the users"
+ },
+ {
+ "id": "store.sql_user.missing_account.const",
+ "translation": "We couldn't find an existing account matching your email address for this team. This team may require an invite from the team owner to join."
+ },
+ {
+ "id": "store.sql_user.permanent_delete.app_error",
+ "translation": "We couldn't delete the existing account"
+ },
+ {
+ "id": "store.sql_user.save.app_error",
+ "translation": "We couldn't save the account."
+ },
+ {
+ "id": "store.sql_user.save.email_exists.app_error",
+ "translation": "An account with that email already exists."
+ },
+ {
+ "id": "store.sql_user.save.existing.app_error",
+ "translation": "Must call update for exisiting user"
+ },
+ {
+ "id": "store.sql_user.save.max_accounts.app_error",
+ "translation": "This team has reached the maxmium number of allowed accounts. Contact your systems administrator to set a higher limit."
+ },
+ {
+ "id": "store.sql_user.save.member_count.app_error",
+ "translation": "Failed to get current team member count"
+ },
+ {
+ "id": "store.sql_user.save.username_exists.app_error",
+ "translation": "An account with that username already exists."
+ },
+ {
+ "id": "store.sql_user.update.app_error",
+ "translation": "We couldn't update the account"
+ },
+ {
+ "id": "store.sql_user.update.email_taken.app_error",
+ "translation": "This email is already taken. Please choose another."
+ },
+ {
+ "id": "store.sql_user.update.find.app_error",
+ "translation": "We couldn't find the existing account to update"
+ },
+ {
+ "id": "store.sql_user.update.finding.app_error",
+ "translation": "We encountered an error finding the account"
+ },
+ {
+ "id": "store.sql_user.update.updating.app_error",
+ "translation": "We encountered an error updating the account"
+ },
+ {
+ "id": "store.sql_user.update.username_taken.app_error",
+ "translation": "This username is already taken. Please choose another."
+ },
+ {
+ "id": "store.sql_user.update_auth_data.app_error",
+ "translation": "We couldn't update the auth data"
+ },
+ {
+ "id": "store.sql_user.update_failed_pwd_attempts.app_error",
+ "translation": "We couldn't update the failed_attempts"
+ },
+ {
+ "id": "store.sql_user.update_last_activity.app_error",
+ "translation": "We couldn't update the last_activity_at"
+ },
+ {
+ "id": "store.sql_user.update_last_picture_update.app_error",
+ "translation": "We couldn't update the update_at"
+ },
+ {
+ "id": "store.sql_user.update_last_ping.app_error",
+ "translation": "We couldn't update the last_ping_at"
+ },
+ {
+ "id": "store.sql_user.update_password.app_error",
+ "translation": "We couldn't update the user password"
+ },
+ {
+ "id": "store.sql_user.verify_email.app_error",
+ "translation": "Unable to update verify email field"
+ },
+ {
+ "id": "store.sql_webhooks.delete_incoming.app_error",
+ "translation": "We couldn't delete the webhook"
+ },
+ {
+ "id": "store.sql_webhooks.delete_outgoing.app_error",
+ "translation": "We couldn't delete the webhook"
+ },
+ {
+ "id": "store.sql_webhooks.get_incoming.app_error",
+ "translation": "We couldn't get the webhook"
+ },
+ {
+ "id": "store.sql_webhooks.get_incoming_by_channel.app_error",
+ "translation": "We couldn't get the webhooks"
+ },
+ {
+ "id": "store.sql_webhooks.get_incoming_by_user.app_error",
+ "translation": "We couldn't get the webhook"
+ },
+ {
+ "id": "store.sql_webhooks.get_outgoing.app_error",
+ "translation": "We couldn't get the webhook"
+ },
+ {
+ "id": "store.sql_webhooks.get_outgoing_by_channel.app_error",
+ "translation": "We couldn't get the webhooks"
+ },
+ {
+ "id": "store.sql_webhooks.get_outgoing_by_team.app_error",
+ "translation": "We couldn't get the webhooks"
+ },
+ {
+ "id": "store.sql_webhooks.permanent_delete_incoming_by_user.app_error",
+ "translation": "We couldn't delete the webhook"
+ },
+ {
+ "id": "store.sql_webhooks.permanent_delete_outgoing_by_user.app_error",
+ "translation": "We couldn't delete the webhook"
+ },
+ {
+ "id": "store.sql_webhooks.save_incoming.app_error",
+ "translation": "We couldn't save the IncomingWebhook"
+ },
+ {
+ "id": "store.sql_webhooks.save_incoming.existing.app_error",
+ "translation": "You cannot overwrite an existing IncomingWebhook"
+ },
+ {
+ "id": "store.sql_webhooks.save_outgoing.app_error",
+ "translation": "We couldn't save the OutgoingWebhook"
+ },
+ {
+ "id": "store.sql_webhooks.save_outgoing.override.app_error",
+ "translation": "You cannot overwrite an existing OutgoingWebhook"
+ },
+ {
+ "id": "store.sql_webhooks.update_outgoing.app_error",
+ "translation": "We couldn't update the webhook"
+ },
+ {
"id": "utils.config.load_config.decoding.panic",
"translation": "Error decoding config file={{.Filename}}, err={{.Error}}"
},
@@ -2254,5 +3142,265 @@
{
"id": "utils.mail.test.configured.error",
"translation": "SMTP server settings do not appear to be configured properly err=%v details=%v"
+ },
+ {
+ "id": "web.admin_console.title",
+ "translation": "Admin Console"
+ },
+ {
+ "id": "web.authorize_oauth.disabled.app_error",
+ "translation": "The system admin has turned off OAuth service providing."
+ },
+ {
+ "id": "web.authorize_oauth.missing.app_error",
+ "translation": "Missing one or more of response_type, client_id, or redirect_uri"
+ },
+ {
+ "id": "web.authorize_oauth.title",
+ "translation": "Authorize Application"
+ },
+ {
+ "id": "web.check_browser_compatibility.app_error",
+ "translation": "Your current browser is not supported, please upgrade to one of the following browsers: Google Chrome 21 or higher, Internet Explorer 11 or higher, FireFox 14 or higher, Safari 9 or higher"
+ },
+ {
+ "id": "web.claim_account.team.error",
+ "translation": "Couldn't find team name=%v, err=%v"
+ },
+ {
+ "id": "web.claim_account.title",
+ "translation": "Claim Account"
+ },
+ {
+ "id": "web.claim_account.user.error",
+ "translation": "Couldn't find user teamid=%v, email=%v, err=%v"
+ },
+ {
+ "id": "web.create_dir.error",
+ "translation": "Failed to create directory watcher %v"
+ },
+ {
+ "id": "web.dir_fail.error",
+ "translation": "Failed in directory watcher %v"
+ },
+ {
+ "id": "web.do_load_channel.error",
+ "translation": "Error in getting users profile for id=%v forcing logout"
+ },
+ {
+ "id": "web.doc.title",
+ "translation": "Documentation"
+ },
+ {
+ "id": "web.email_verified.title",
+ "translation": "Email Verified"
+ },
+ {
+ "id": "web.find_team.title",
+ "translation": "Find Team"
+ },
+ {
+ "id": "web.footer.about",
+ "translation": "About"
+ },
+ {
+ "id": "web.footer.help",
+ "translation": "Help"
+ },
+ {
+ "id": "web.footer.privacy",
+ "translation": "Privacy"
+ },
+ {
+ "id": "web.footer.terms",
+ "translation": "Terms"
+ },
+ {
+ "id": "web.get_access_token.bad_client_id.app_error",
+ "translation": "invalid_request: Bad client_id"
+ },
+ {
+ "id": "web.get_access_token.bad_client_secret.app_error",
+ "translation": "invalid_request: Missing client_secret"
+ },
+ {
+ "id": "web.get_access_token.bad_grant.app_error",
+ "translation": "invalid_request: Bad grant_type"
+ },
+ {
+ "id": "web.get_access_token.credentials.app_error",
+ "translation": "invalid_client: Invalid client credentials"
+ },
+ {
+ "id": "web.get_access_token.disabled.app_error",
+ "translation": "The system admin has turned off OAuth service providing."
+ },
+ {
+ "id": "web.get_access_token.exchanged.app_error",
+ "translation": "invalid_grant: Authorization code already exchanged for an access token"
+ },
+ {
+ "id": "web.get_access_token.expired_code.app_error",
+ "translation": "invalid_grant: Invalid or expired authorization code"
+ },
+ {
+ "id": "web.get_access_token.internal.app_error",
+ "translation": "server_error: Encountered internal server error while accessing database"
+ },
+ {
+ "id": "web.get_access_token.internal_saving.app_error",
+ "translation": "server_error: Encountered internal server error while saving access token to database"
+ },
+ {
+ "id": "web.get_access_token.internal_session.app_error",
+ "translation": "server_error: Encountered internal server error while saving session to database"
+ },
+ {
+ "id": "web.get_access_token.internal_user.app_error",
+ "translation": "server_error: Encountered internal server error while pulling user from database"
+ },
+ {
+ "id": "web.get_access_token.missing_code.app_error",
+ "translation": "invalid_request: Missing code"
+ },
+ {
+ "id": "web.get_access_token.redirect_uri.app_error",
+ "translation": "invalid_request: Supplied redirect_uri does not match authorization code redirect_uri"
+ },
+ {
+ "id": "web.get_access_token.revoking.error",
+ "translation": "Encountered an error revoking an access token, err="
+ },
+ {
+ "id": "web.incoming_webhook.channel.app_error",
+ "translation": "Couldn't find the channel"
+ },
+ {
+ "id": "web.incoming_webhook.disabled.app_error",
+ "translation": "Incoming webhooks have been disabled by the system admin."
+ },
+ {
+ "id": "web.incoming_webhook.invalid.app_error",
+ "translation": "Invalid webhook"
+ },
+ {
+ "id": "web.incoming_webhook.parse.app_error",
+ "translation": "Unable to parse incoming data"
+ },
+ {
+ "id": "web.incoming_webhook.permissions.app_error",
+ "translation": "Inappropriate channel permissions"
+ },
+ {
+ "id": "web.incoming_webhook.text.app_error",
+ "translation": "No text specified"
+ },
+ {
+ "id": "web.incoming_webhook.user.app_error",
+ "translation": "Couldn't find the user"
+ },
+ {
+ "id": "web.init.debug",
+ "translation": "Initializing web routes"
+ },
+ {
+ "id": "web.login.error",
+ "translation": "Couldn't find team name=%v, err=%v"
+ },
+ {
+ "id": "web.login.login_title",
+ "translation": "Login"
+ },
+ {
+ "id": "web.login_with_oauth.invalid_team.app_error",
+ "translation": "Invalid team name"
+ },
+ {
+ "id": "web.parsing_templates.debug",
+ "translation": "Parsing templates at %v"
+ },
+ {
+ "id": "web.parsing_templates.error",
+ "translation": "Failed to parse templates %v"
+ },
+ {
+ "id": "web.post_permalink.app_error",
+ "translation": "Invalid Post ID"
+ },
+ {
+ "id": "web.reparse_templates.info",
+ "translation": "Re-parsing templates because of modified file %v"
+ },
+ {
+ "id": "web.reset_password.expired_link.app_error",
+ "translation": "The password reset link has expired"
+ },
+ {
+ "id": "web.reset_password.invalid_link.app_error",
+ "translation": "The reset link does not appear to be valid"
+ },
+ {
+ "id": "web.root.home_title",
+ "translation": "Home"
+ },
+ {
+ "id": "web.root.singup_info",
+ "translation": "All team communication in one place, searchable and accessible anywhere"
+ },
+ {
+ "id": "web.root.singup_title",
+ "translation": "Signup"
+ },
+ {
+ "id": "web.signup_team_complete.invalid_link.app_error",
+ "translation": "The signup link does not appear to be valid"
+ },
+ {
+ "id": "web.signup_team_complete.link_expired.app_error",
+ "translation": "The signup link has expired"
+ },
+ {
+ "id": "web.signup_team_complete.title",
+ "translation": "Complete Team Sign Up"
+ },
+ {
+ "id": "web.signup_team_confirm.title",
+ "translation": "Signup Email Sent"
+ },
+ {
+ "id": "web.signup_user_complete.link_expired.app_error",
+ "translation": "The signup link has expired"
+ },
+ {
+ "id": "web.signup_user_complete.link_invalid.app_error",
+ "translation": "The signup link does not appear to be valid"
+ },
+ {
+ "id": "web.signup_user_complete.no_invites.app_error",
+ "translation": "The team type doesn't allow open invites"
+ },
+ {
+ "id": "web.signup_user_complete.title",
+ "translation": "Complete User Sign Up"
+ },
+ {
+ "id": "web.singup_with_oauth.disabled.app_error",
+ "translation": "User sign-up is disabled."
+ },
+ {
+ "id": "web.singup_with_oauth.expired_link.app_error",
+ "translation": "The signup link has expired"
+ },
+ {
+ "id": "web.singup_with_oauth.invalid_link.app_error",
+ "translation": "The signup link does not appear to be valid"
+ },
+ {
+ "id": "web.singup_with_oauth.invalid_team.app_error",
+ "translation": "Invalid team name"
+ },
+ {
+ "id": "web.watcher_fail.error",
+ "translation": "Failed to add directory to watcher %v"
}
-] \ No newline at end of file
+]
diff --git a/i18n/es.json b/i18n/es.json
index 57dd22bc8..ee48c9acb 100644
--- a/i18n/es.json
+++ b/i18n/es.json
@@ -456,10 +456,6 @@
"translation": "No se encontró el archivo."
},
{
- "id": "api.file.get_file.public_expired.app_error",
- "translation": "El enlace público ha expirado"
- },
- {
"id": "api.file.get_file.public_invalid.app_error",
"translation": "El enlace público parece ser inválido"
},
@@ -580,6 +576,10 @@
"translation": "Archivo de licencia inválido."
},
{
+ "id": "api.license.add_license.invalid_count.app_error",
+ "translation": "No se pudo obtener el número total de usuarios únicos."
+ },
+ {
"id": "api.license.add_license.no_file.app_error",
"translation": "No hay un archivo bajo 'license' en la solicitud"
},
@@ -592,6 +592,10 @@
"translation": "La licencia no fue guardada correctamente."
},
{
+ "id": "api.license.add_license.unique_users.app_error",
+ "translation": "Esta licencia sólo soporta {{.Users}} usuarios, cuando tu sistema tiene {{.Count}} usuarios únicos. Los usuarios únicos se cuentan por direcciónes de correo electrónico distintas. Puedes ver el totoal de usuarios en REPORTES DEL SITIO -> Ver Estadísticas."
+ },
+ {
"id": "api.license.init.debug",
"translation": "Inicializando rutas del API para las licencias"
},
@@ -1564,6 +1568,10 @@
"translation": "No se pudo actualizar la imagen del perfil. El archivo es muy grande."
},
{
+ "id": "api.user.upload_profile_user.upload_profile.app_error",
+ "translation": "No se pudo subir la imagen del perfil"
+ },
+ {
"id": "api.web_conn.new_web_conn.last_activity.error",
"translation": "Falla al actualizar LastActivityAt para user_id=%v and session_id=%v, err=%v"
},
@@ -1672,10 +1680,50 @@
"translation": "No se pudo obtener los canales"
},
{
+ "id": "mattermost.bulletin.subject",
+ "translation": "Boletín de Seguridad Mattermost"
+ },
+ {
+ "id": "mattermost.config_file",
+ "translation": "Cargado el archivo de configuración desde %v"
+ },
+ {
"id": "mattermost.current_version",
"translation": "La versión actual es %v (%v/%v/%v)"
},
{
+ "id": "mattermost.entreprise_enabled",
+ "translation": "Empresa Habilitada: %v"
+ },
+ {
+ "id": "mattermost.security_bulletin.error",
+ "translation": "Falla al obtener el detalle del boletín de seguridad"
+ },
+ {
+ "id": "mattermost.security_bulletin_read.error",
+ "translation": "Falla al leer el detalle del boletín de seguridad"
+ },
+ {
+ "id": "mattermost.security_checks.debug",
+ "translation": "Consultando si existen actualizaciones de seguridad para Mattermost"
+ },
+ {
+ "id": "mattermost.security_info.error",
+ "translation": "Falla al obtener información sobre actualizaciones de seguridad para Mattermost."
+ },
+ {
+ "id": "mattermost.send_bulletin.info",
+ "translation": "Enviando boletín de seguridad para %v a %v"
+ },
+ {
+ "id": "mattermost.system_admins.error",
+ "translation": "Falla al obtener los administradores de sistema que reciben información referente a las actualizaciones de seguridade de Mattermost."
+ },
+ {
+ "id": "mattermost.working_dir",
+ "translation": "El directorio de trabajo actual es %v"
+ },
+ {
"id": "model.access.is_valid.access_token.app_error",
"translation": "Token de acceso inválido"
},
@@ -2156,6 +2204,846 @@
"translation": "no se puede decodificar"
},
{
+ "id": "store.sql.check_index.critical",
+ "translation": "Falla al revisar el indice %v"
+ },
+ {
+ "id": "store.sql.closing.info",
+ "translation": "Cerrando SqlStore"
+ },
+ {
+ "id": "store.sql.column_exists.critical",
+ "translation": "Falla al revisar si la columna existe %v"
+ },
+ {
+ "id": "store.sql.column_exists_missing_driver.critical",
+ "translation": "Falla al revisar si la columna existe porque el controlador no se encuentra"
+ },
+ {
+ "id": "store.sql.convert_encrypt_string_map",
+ "translation": "Desde BD: No se puede convertir EncryptStringMap a *string"
+ },
+ {
+ "id": "store.sql.convert_string_array",
+ "translation": "Desde BD: No se puede convertir StringArray a *string"
+ },
+ {
+ "id": "store.sql.convert_string_interface",
+ "translation": "Desde BD: No se puede convertir StringInterface a *string"
+ },
+ {
+ "id": "store.sql.convert_string_map",
+ "translation": "Desde BD: No se puede convertir StringMap a *string"
+ },
+ {
+ "id": "store.sql.create_column.critical",
+ "translation": "Falla al crear la columna %v"
+ },
+ {
+ "id": "store.sql.create_column_missing_driver.critical",
+ "translation": "Falla al crear la columna porque el controlador no se encuentra"
+ },
+ {
+ "id": "store.sql.create_index.critical",
+ "translation": "Falla al crear el indice %v"
+ },
+ {
+ "id": "store.sql.create_index_missing_driver.critical",
+ "translation": "Falla al crear el indice porque el controlador no se encuentra"
+ },
+ {
+ "id": "store.sql.creating_tables.critical",
+ "translation": "Error creando las tablas de la base de datos: %v"
+ },
+ {
+ "id": "store.sql.dialect_driver.critical",
+ "translation": "Falla al crear el controlador de base de datos especificado"
+ },
+ {
+ "id": "store.sql.dialect_driver.panic",
+ "translation": "Failed to create dialect specific driver %v"
+ },
+ {
+ "id": "store.sql.drop_column.critical",
+ "translation": "Falla al borrar la columna %v"
+ },
+ {
+ "id": "store.sql.incorrect_mac",
+ "translation": "MAC incorrecto para el ciphertext dado"
+ },
+ {
+ "id": "store.sql.open_conn.critical",
+ "translation": "Falla al abrir una conexión sql a err:%v"
+ },
+ {
+ "id": "store.sql.open_conn.panic",
+ "translation": "Failed to open sql connection %v"
+ },
+ {
+ "id": "store.sql.ping.critical",
+ "translation": "Falla al hacer ping a la base de datos err:%v"
+ },
+ {
+ "id": "store.sql.pinging.info",
+ "translation": "Verificando conexión con la base de datos sql %v"
+ },
+ {
+ "id": "store.sql.rename_column.critical",
+ "translation": "Falla al renombrar la columna %v"
+ },
+ {
+ "id": "store.sql.schema_out_of_date.warn",
+ "translation": "La versión del esquema de la base de datos %v parece estar desactualizada"
+ },
+ {
+ "id": "store.sql.schema_set.info",
+ "translation": "El esquema de la base de datos ha sido asignado a la versión %v"
+ },
+ {
+ "id": "store.sql.schema_upgrade_attempt.warn",
+ "translation": "Intentando actualizar el esquema de la base de datos a la versión %v"
+ },
+ {
+ "id": "store.sql.schema_version.critical",
+ "translation": "La version del esquema de la base de datos %v no puede ser actualizada. No debes saltarte ninguna versión anterior."
+ },
+ {
+ "id": "store.sql.short_ciphertext",
+ "translation": "ciphertext corto"
+ },
+ {
+ "id": "store.sql.table_column_type.critical",
+ "translation": "Falla al obtener el tipo de dato de la columna %s de la tabla %s: %v"
+ },
+ {
+ "id": "store.sql.table_exists.critical",
+ "translation": "Falla al revisar si la tabla existe %v"
+ },
+ {
+ "id": "store.sql.too_short_ciphertext",
+ "translation": "ciphertext muy corto"
+ },
+ {
+ "id": "store.sql.upgraded.warn",
+ "translation": "El esquema de la base de datos ha sido actualizado a la versión %v"
+ },
+ {
+ "id": "store.sql_audit.get.finding.app_error",
+ "translation": "Encontramos un error al buscar las auditorias"
+ },
+ {
+ "id": "store.sql_audit.get.limit.app_error",
+ "translation": "Límite de paginación excedido"
+ },
+ {
+ "id": "store.sql_audit.permanent_delete_by_user.app_error",
+ "translation": "Encontramos un error al eliminar los audits"
+ },
+ {
+ "id": "store.sql_audit.save.saving.app_error",
+ "translation": "Encontramos un error guardando la auditoria"
+ },
+ {
+ "id": "store.sql_channel.analytics_type_count.app_error",
+ "translation": "No pudimos obtener la cantidad de canales de este tipo"
+ },
+ {
+ "id": "store.sql_channel.check_open_channel_permissions.app_error",
+ "translation": "No pudimos revisar los permisos"
+ },
+ {
+ "id": "store.sql_channel.check_permissions.app_error",
+ "translation": "No pudimos revisar los permisos"
+ },
+ {
+ "id": "store.sql_channel.check_permissions_by_name.app_error",
+ "translation": "No pudimos revisar los permisos"
+ },
+ {
+ "id": "store.sql_channel.delete.channel.app_error",
+ "translation": "No pudimos eliminar el canal"
+ },
+ {
+ "id": "store.sql_channel.extra_updated.app_error",
+ "translation": "Problema actualizando el último momento de actualización de los miembros"
+ },
+ {
+ "id": "store.sql_channel.get.existing.app_error",
+ "translation": "No pudimos encontrar el canal"
+ },
+ {
+ "id": "store.sql_channel.get.find.app_error",
+ "translation": "Encontramos un error buscando el canal"
+ },
+ {
+ "id": "store.sql_channel.get_by_name.existing.app_error",
+ "translation": "No pudimos encontrar el canal"
+ },
+ {
+ "id": "store.sql_channel.get_channel_counts.get.app_error",
+ "translation": "No pudimos obtener la cantidad de canales"
+ },
+ {
+ "id": "store.sql_channel.get_channels.get.app_error",
+ "translation": "No pudimos obtener los canales"
+ },
+ {
+ "id": "store.sql_channel.get_channels.not_found.app_error",
+ "translation": "No se encontró ningún canal"
+ },
+ {
+ "id": "store.sql_channel.get_extra_members.app_error",
+ "translation": "No pudimos obtener información extra de los miembros del canal"
+ },
+ {
+ "id": "store.sql_channel.get_for_export.app_error",
+ "translation": "No pudimos obtener todos los canales"
+ },
+ {
+ "id": "store.sql_channel.get_member.app_error",
+ "translation": "No pudimos obtener el miembro del canal"
+ },
+ {
+ "id": "store.sql_channel.get_member_count.app_error",
+ "translation": "No pudimos obtener la cantidad de miembros del canal"
+ },
+ {
+ "id": "store.sql_channel.get_members.app_error",
+ "translation": "No pudimos obtener los miembros del canal"
+ },
+ {
+ "id": "store.sql_channel.get_more_channels.get.app_error",
+ "translation": "No pudimos obtener los canales"
+ },
+ {
+ "id": "store.sql_channel.increment_mention_count.app_error",
+ "translation": "No pudimos incrementar la cantidad de menciones"
+ },
+ {
+ "id": "store.sql_channel.permanent_delete_by_team.app_error",
+ "translation": "No pudimos eliminar los canales"
+ },
+ {
+ "id": "store.sql_channel.permanent_delete_members_by_user.app_error",
+ "translation": "No pudimos remover al miembro del canal"
+ },
+ {
+ "id": "store.sql_channel.remove_member.app_error",
+ "translation": "No pudimos remover al miembro del canal"
+ },
+ {
+ "id": "store.sql_channel.save.commit_transaction.app_error",
+ "translation": "No se puede perpetrar la transacción"
+ },
+ {
+ "id": "store.sql_channel.save.direct_channel.app_error",
+ "translation": "Utiliza SaveDirectChannel para crear un canal directo"
+ },
+ {
+ "id": "store.sql_channel.save.open_transaction.app_error",
+ "translation": "No se puede abrir la transacción"
+ },
+ {
+ "id": "store.sql_channel.save_channel.current_count.app_error",
+ "translation": "Falla obteniendo la cantidad de canales actual"
+ },
+ {
+ "id": "store.sql_channel.save_channel.existing.app_error",
+ "translation": "Debe llamarse a actualizar para un canal existente"
+ },
+ {
+ "id": "store.sql_channel.save_channel.exists.app_error",
+ "translation": "Un canal con este URL ya existe"
+ },
+ {
+ "id": "store.sql_channel.save_channel.limit.app_error",
+ "translation": "Se ha alcanzado el límite de canales permitidos."
+ },
+ {
+ "id": "store.sql_channel.save_channel.previously.app_error",
+ "translation": "Un canal con este URL fue creado previamente"
+ },
+ {
+ "id": "store.sql_channel.save_channel.save.app_error",
+ "translation": "No pudimos guardar el canal"
+ },
+ {
+ "id": "store.sql_channel.save_direct_channel.add_members.app_error",
+ "translation": "No se pueden agregar miembros a un canal directo"
+ },
+ {
+ "id": "store.sql_channel.save_direct_channel.commit.app_error",
+ "translation": "No se puede perpetrar la transacción"
+ },
+ {
+ "id": "store.sql_channel.save_direct_channel.not_direct.app_error",
+ "translation": "No es un canal directo, se intentó crear con SaveDirectChannel"
+ },
+ {
+ "id": "store.sql_channel.save_direct_channel.open_transaction.app_error",
+ "translation": "No se puede abrir la transacción"
+ },
+ {
+ "id": "store.sql_channel.save_member.commit_transaction.app_error",
+ "translation": "No se puede perpetrar la transacción"
+ },
+ {
+ "id": "store.sql_channel.save_member.exists.app_error",
+ "translation": "Un miembro del canal con este id ya existe"
+ },
+ {
+ "id": "store.sql_channel.save_member.open_transaction.app_error",
+ "translation": "No se puede abrir la transacción"
+ },
+ {
+ "id": "store.sql_channel.save_member.save.app_error",
+ "translation": "No pudimos guardar el miembro del canal"
+ },
+ {
+ "id": "store.sql_channel.update.app_error",
+ "translation": "No pudimos actualizar el canal"
+ },
+ {
+ "id": "store.sql_channel.update.exists.app_error",
+ "translation": "Un canal con este identificador ya existe"
+ },
+ {
+ "id": "store.sql_channel.update.previously.app_error",
+ "translation": "Un canal con este identificador fue creado previamente"
+ },
+ {
+ "id": "store.sql_channel.update.updating.app_error",
+ "translation": "Encontramos un error actualizando el canal"
+ },
+ {
+ "id": "store.sql_channel.update_last_viewed_at.app_error",
+ "translation": "No pudimos actualizar el tiempo de la última vista"
+ },
+ {
+ "id": "store.sql_channel.update_member.app_error",
+ "translation": "Encontramos un error actualizando el miembro del canal"
+ },
+ {
+ "id": "store.sql_oauth.get_access_data.app_error",
+ "translation": "Encontramos un error buscando el token de acceso"
+ },
+ {
+ "id": "store.sql_oauth.get_access_data_by_code.app_error",
+ "translation": "Encontramos un error buscando el token de acceso"
+ },
+ {
+ "id": "store.sql_oauth.get_app.find.app_error",
+ "translation": "No pudimos encontrar la app"
+ },
+ {
+ "id": "store.sql_oauth.get_app.finding.app_error",
+ "translation": "Encontramos un error buscando la app"
+ },
+ {
+ "id": "store.sql_oauth.get_app_by_user.find.app_error",
+ "translation": "No pudimos encontrar ninguna app"
+ },
+ {
+ "id": "store.sql_oauth.get_auth_data.find.app_error",
+ "translation": "No pudimos encontrar el código de autorización"
+ },
+ {
+ "id": "store.sql_oauth.get_auth_data.finding.app_error",
+ "translation": "Encontramos un error buscando el código de autorización"
+ },
+ {
+ "id": "store.sql_oauth.permanent_delete_auth_data_by_user.app_error",
+ "translation": "No pudimos remover el código de autorización"
+ },
+ {
+ "id": "store.sql_oauth.remove_access_data.app_error",
+ "translation": "No pudimos remover el token de acceso"
+ },
+ {
+ "id": "store.sql_oauth.remove_auth_data.app_error",
+ "translation": "No pudimos remover el código de autorización"
+ },
+ {
+ "id": "store.sql_oauth.save_access_data.app_error",
+ "translation": "No pudimos guardar el token de acceso."
+ },
+ {
+ "id": "store.sql_oauth.save_app.existing.app_error",
+ "translation": "Se debe ejecutar actualizar para una app existente"
+ },
+ {
+ "id": "store.sql_oauth.save_app.save.app_error",
+ "translation": "No pudimos guardar el app."
+ },
+ {
+ "id": "store.sql_oauth.save_auth_data.app_error",
+ "translation": "No pudimos guardar el código de autorización."
+ },
+ {
+ "id": "store.sql_oauth.update_app.find.app_error",
+ "translation": "No pudimos encontrar una app para actualizar"
+ },
+ {
+ "id": "store.sql_oauth.update_app.finding.app_error",
+ "translation": "Encontramos un error buscando la app"
+ },
+ {
+ "id": "store.sql_oauth.update_app.update.app_error",
+ "translation": "No pudimos actualizar la app"
+ },
+ {
+ "id": "store.sql_oauth.update_app.updating.app_error",
+ "translation": "Encontramos un error actualizando la app"
+ },
+ {
+ "id": "store.sql_post.analytics_posts_count.app_error",
+ "translation": "No pudimos obtener la cantidad de mensajes"
+ },
+ {
+ "id": "store.sql_post.analytics_posts_count_by_day.app_error",
+ "translation": "No pudimos obtener la cantidad de mensajes por día"
+ },
+ {
+ "id": "store.sql_post.analytics_user_counts_posts_by_day.app_error",
+ "translation": "No pudimos obtener la cantidad de usuarios con mensajes"
+ },
+ {
+ "id": "store.sql_post.delete.app_error",
+ "translation": "No pudimos eliminar el mensaje"
+ },
+ {
+ "id": "store.sql_post.get.app_error",
+ "translation": "No pudimos obtener el mensaje"
+ },
+ {
+ "id": "store.sql_post.get_for_export.app_error",
+ "translation": "No pudimos obtener los mensajes para el canal"
+ },
+ {
+ "id": "store.sql_post.get_parents_posts.app_error",
+ "translation": "No pudimos obtener los mensajes padres del canal"
+ },
+ {
+ "id": "store.sql_post.get_posts.app_error",
+ "translation": "Límite de paginación excedido"
+ },
+ {
+ "id": "store.sql_post.get_posts_around.get.app_error",
+ "translation": "No pudimos obtener los mensajes para el canal"
+ },
+ {
+ "id": "store.sql_post.get_posts_around.get_parent.app_error",
+ "translation": "No pudimos obtener los mensajes padres del canal"
+ },
+ {
+ "id": "store.sql_post.get_posts_since.app_error",
+ "translation": "No pudimos obtener los mensajes para el canal"
+ },
+ {
+ "id": "store.sql_post.get_root_posts.app_error",
+ "translation": "No pudimos obtener los mensajes para el canal"
+ },
+ {
+ "id": "store.sql_post.permanent_delete.app_error",
+ "translation": "No pudimos eliminar el mensaje"
+ },
+ {
+ "id": "store.sql_post.permanent_delete_all_comments_by_user.app_error",
+ "translation": "No pudimos eliminar los comentarios para el usuario"
+ },
+ {
+ "id": "store.sql_post.permanent_delete_by_user.app_error",
+ "translation": "No pudimos seleccionar los mensajes a borrar para el usuario"
+ },
+ {
+ "id": "store.sql_post.permanent_delete_by_user.too_many.app_error",
+ "translation": "No pudimos seleccionar todos los mensajes a eliminar por el usuario (son demasiados), por favor ejecuta de nuevo"
+ },
+ {
+ "id": "store.sql_post.save.app_error",
+ "translation": "No pudimos guardar el Mensaje"
+ },
+ {
+ "id": "store.sql_post.save.existing.app_error",
+ "translation": "No puedes actualizar el Mensaje"
+ },
+ {
+ "id": "store.sql_post.search.app_error",
+ "translation": "Encontramos un error mientras buscabamos los mensajes"
+ },
+ {
+ "id": "store.sql_post.update.app_error",
+ "translation": "No pudimos actualizar el Mensaje"
+ },
+ {
+ "id": "store.sql_preference.delete_unused_features.debug",
+ "translation": "Eliminando las características de pre-release"
+ },
+ {
+ "id": "store.sql_preference.get.app_error",
+ "translation": "Encontramos un error mientras buscabamos las preferencias"
+ },
+ {
+ "id": "store.sql_preference.get_all.app_error",
+ "translation": "Encontramos un error mientras buscabamos las preferencias"
+ },
+ {
+ "id": "store.sql_preference.get_category.app_error",
+ "translation": "Encontramos un error mientras buscabamos las preferencias"
+ },
+ {
+ "id": "store.sql_preference.insert.exists.app_error",
+ "translation": "La preferencia para ese usuario, categoria y nombre ya existe"
+ },
+ {
+ "id": "store.sql_preference.insert.save.app_error",
+ "translation": "No pudimos guardar la preferencia"
+ },
+ {
+ "id": "store.sql_preference.is_feature_enabled.app_error",
+ "translation": "Encontramos un error mientras buscabamos las preferencias de las características de pre-lanzamiento"
+ },
+ {
+ "id": "store.sql_preference.permanent_delete_by_user.app_error",
+ "translation": "Encontramos un error mientras eliminabamos las preferencias"
+ },
+ {
+ "id": "store.sql_preference.save.commit_transaction.app_error",
+ "translation": "No se pudo hacer commit de la transacción para guardar las preferencias"
+ },
+ {
+ "id": "store.sql_preference.save.missing_driver.app_error",
+ "translation": "Encontramos un error mientras actualizabamos las preferencias"
+ },
+ {
+ "id": "store.sql_preference.save.open_transaction.app_error",
+ "translation": "No se pudo abrir la transacción para guardar las preferencias"
+ },
+ {
+ "id": "store.sql_preference.save.rollback_transaction.app_error",
+ "translation": "No se pudo deshacer la transaccion para guardar las preferencias"
+ },
+ {
+ "id": "store.sql_preference.save.updating.app_error",
+ "translation": "Encontramos un error mientras actualizabamos las preferencias"
+ },
+ {
+ "id": "store.sql_preference.update.app_error",
+ "translation": "No pudimos actualizar la preferencia"
+ },
+ {
+ "id": "store.sql_session.cleanup_expired_sessions.app_error",
+ "translation": "Encontramos un error mientras se eliminaban las sesiones expiradas del usuario"
+ },
+ {
+ "id": "store.sql_session.get.app_error",
+ "translation": "Encontramos un error buscando las sesiones"
+ },
+ {
+ "id": "store.sql_session.get_sessions.app_error",
+ "translation": "Encontramos un error mientras buscabamos las sesiones de usuario"
+ },
+ {
+ "id": "store.sql_session.get_sessions.error",
+ "translation": "Falla al limpiar las sesiones en getSessions err=%v"
+ },
+ {
+ "id": "store.sql_session.permanent_delete_sessions_by_user.app_error",
+ "translation": "No pudimos remover todas las sesiones del usuario"
+ },
+ {
+ "id": "store.sql_session.remove.app_error",
+ "translation": "No pudimos remover la sesión"
+ },
+ {
+ "id": "store.sql_session.remove_all_sessions_for_team.app_error",
+ "translation": "No pudimos remover todas las sesiones para el equipo"
+ },
+ {
+ "id": "store.sql_session.save.app_error",
+ "translation": "No pudimos guardar la sesión"
+ },
+ {
+ "id": "store.sql_session.save.cleanup.error",
+ "translation": "Falla al limpiar las sesiones mientras se Guardaba err=%v"
+ },
+ {
+ "id": "store.sql_session.save.existing.app_error",
+ "translation": "No se puede actualizar la sesión"
+ },
+ {
+ "id": "store.sql_session.update_device_id.app_error",
+ "translation": "No pudimos actualizar el id del dispositivo"
+ },
+ {
+ "id": "store.sql_session.update_last_activity.app_error",
+ "translation": "No pudimos actualizar el campo last_activity_at"
+ },
+ {
+ "id": "store.sql_session.update_roles.app_error",
+ "translation": "No pudimos actualizar los roles"
+ },
+ {
+ "id": "store.sql_system.get.app_error",
+ "translation": "Encontramos un error buscando las propiedades del sistema"
+ },
+ {
+ "id": "store.sql_system.save.app_error",
+ "translation": "Entrontramos un error mientras se guardaban las propiedades del sistema"
+ },
+ {
+ "id": "store.sql_system.update.app_error",
+ "translation": "Encontramos un error actualizando las propiedades del sistema"
+ },
+ {
+ "id": "store.sql_team.get.find.app_error",
+ "translation": "No encontramos el equipo al que perteneces"
+ },
+ {
+ "id": "store.sql_team.get.finding.app_error",
+ "translation": "Encontramos un error buscando el equipo"
+ },
+ {
+ "id": "store.sql_team.get_all.app_error",
+ "translation": "No pudimos obtener todos los equipos"
+ },
+ {
+ "id": "store.sql_team.get_all_team_listing.app_error",
+ "translation": "No pudimos obtener todos los equipos"
+ },
+ {
+ "id": "store.sql_team.get_by_invite_id.find.app_error",
+ "translation": "No encontramos el equipo al que perteneces"
+ },
+ {
+ "id": "store.sql_team.get_by_invite_id.finding.app_error",
+ "translation": "No encontramos el equipo al que perteneces"
+ },
+ {
+ "id": "store.sql_team.get_by_name.app_error",
+ "translation": "No encontramos el equipo al que perteneces"
+ },
+ {
+ "id": "store.sql_team.get_teams_for_email.app_error",
+ "translation": "Encontramos un problema cuando buscabamos los equipos"
+ },
+ {
+ "id": "store.sql_team.permanent_delete.app_error",
+ "translation": "No pudimos eliminar el equipo"
+ },
+ {
+ "id": "store.sql_team.save.app_error",
+ "translation": "No pudimos guardar el equipo"
+ },
+ {
+ "id": "store.sql_team.save.domain_exists.app_error",
+ "translation": "Ya existe un equipo con ese dominio"
+ },
+ {
+ "id": "store.sql_team.save.existing.app_error",
+ "translation": "Debe ejecturase actualizar para un equipo existente"
+ },
+ {
+ "id": "store.sql_team.update.app_error",
+ "translation": "No pudimos actualizar el equipo"
+ },
+ {
+ "id": "store.sql_team.update.find.app_error",
+ "translation": "No pudimos encontrar el equipo a actualizar"
+ },
+ {
+ "id": "store.sql_team.update.finding.app_error",
+ "translation": "Encontramos un error buscando el equipo"
+ },
+ {
+ "id": "store.sql_team.update.updating.app_error",
+ "translation": "Encontramos un error actualizando el equipo"
+ },
+ {
+ "id": "store.sql_team.update_display_name.app_error",
+ "translation": "No pudimos actualizar el nombre del equipo"
+ },
+ {
+ "id": "store.sql_user.analytics_unique_user_count.app_error",
+ "translation": "No se pudo obtener el conteo de usuarios unicos"
+ },
+ {
+ "id": "store.sql_user.get.app_error",
+ "translation": "Encontramos un error buscando la cuenta"
+ },
+ {
+ "id": "store.sql_user.get_by_auth.app_error",
+ "translation": "No pudimos encontrar una cuenta existente que coincida con tu tipo de autenticación para este equipo. Es posible que necesites una invitación por aprte del dueño del equipo para unirte."
+ },
+ {
+ "id": "store.sql_user.get_by_username.app_error",
+ "translation": "No pudimos encontrar una cuenta existente que coincida con tu nombre de usuario para este equipo. Es posible que necesites una invitación por aprte del dueño del equipo para unirte."
+ },
+ {
+ "id": "store.sql_user.get_for_export.app_error",
+ "translation": "Encontramos un error mientras buscabamos los perfiles de usuario"
+ },
+ {
+ "id": "store.sql_user.get_profiles.app_error",
+ "translation": "Encontramos un error mientras buscabamos los perfiles de usuario"
+ },
+ {
+ "id": "store.sql_user.get_sysadmin_profiles.app_error",
+ "translation": "Encontramos un error mientras buscabamos los perfiles de usuario"
+ },
+ {
+ "id": "store.sql_user.get_total_active_users_count.app_error",
+ "translation": "No pudimos contar los usuarios"
+ },
+ {
+ "id": "store.sql_user.get_total_users_count.app_error",
+ "translation": "No pudimos contar los usuarios"
+ },
+ {
+ "id": "store.sql_user.missing_account.const",
+ "translation": "No pudimos encontrar una cuenta existente que coincida con tu dirección de correo electrónico para este equipo. Es posible que necesites una invitación del dueño del equipo para poder unirte."
+ },
+ {
+ "id": "store.sql_user.permanent_delete.app_error",
+ "translation": "No pudimos eliminar la cuenta"
+ },
+ {
+ "id": "store.sql_user.save.app_error",
+ "translation": "No pudimos guardar la cuenta."
+ },
+ {
+ "id": "store.sql_user.save.email_exists.app_error",
+ "translation": "Ya existe una cuenta con ese correo electrónico."
+ },
+ {
+ "id": "store.sql_user.save.existing.app_error",
+ "translation": "Debe ejecutarse actualizar para un usuario existente"
+ },
+ {
+ "id": "store.sql_user.save.max_accounts.app_error",
+ "translation": "Este equipo ha alcanzado el número máximo de cuentas permitidas. Contacta a un administrador de sistema para que asigne un límite mayor."
+ },
+ {
+ "id": "store.sql_user.save.member_count.app_error",
+ "translation": "Falla obteniendo la cantidad de miembros del equipo actual"
+ },
+ {
+ "id": "store.sql_user.save.username_exists.app_error",
+ "translation": "Una cuenta con ese nombre de usuario ya existe."
+ },
+ {
+ "id": "store.sql_user.update.app_error",
+ "translation": "No pudimos realizar la actualización de los datos de la cuenta"
+ },
+ {
+ "id": "store.sql_user.update.email_taken.app_error",
+ "translation": "Este correo electrónico ya está siendo utilizado. Por favor escoge otro."
+ },
+ {
+ "id": "store.sql_user.update.find.app_error",
+ "translation": "No pudimos encontrar la cuenta a actualizar"
+ },
+ {
+ "id": "store.sql_user.update.finding.app_error",
+ "translation": "Encontramos un error buscando la cuenta"
+ },
+ {
+ "id": "store.sql_user.update.updating.app_error",
+ "translation": "Encontramos un error actualizando la cuenta"
+ },
+ {
+ "id": "store.sql_user.update.username_taken.app_error",
+ "translation": "Este nombre de usuario ya está siendo utiizado. Por favor selecciona otro."
+ },
+ {
+ "id": "store.sql_user.update_auth_data.app_error",
+ "translation": "No pudimos actualizar la data de autorización"
+ },
+ {
+ "id": "store.sql_user.update_failed_pwd_attempts.app_error",
+ "translation": "No pudimos actualizar el campo failed_attempts"
+ },
+ {
+ "id": "store.sql_user.update_last_activity.app_error",
+ "translation": "No pudimos actualizar el campo last_activity_at"
+ },
+ {
+ "id": "store.sql_user.update_last_picture_update.app_error",
+ "translation": "No pudimos actualizar el campo update_at"
+ },
+ {
+ "id": "store.sql_user.update_last_ping.app_error",
+ "translation": "No pudimos actualizar el campo last_ping_at"
+ },
+ {
+ "id": "store.sql_user.update_password.app_error",
+ "translation": "No pudimos actualizar la contraseña del usuario"
+ },
+ {
+ "id": "store.sql_user.verify_email.app_error",
+ "translation": "No se puede actualizar el campo de verificar correo"
+ },
+ {
+ "id": "store.sql_webhooks.delete_incoming.app_error",
+ "translation": "No pudimos eliminar el webhook"
+ },
+ {
+ "id": "store.sql_webhooks.delete_outgoing.app_error",
+ "translation": "No pudimos eliminar el webhook"
+ },
+ {
+ "id": "store.sql_webhooks.get_incoming.app_error",
+ "translation": "No pudimos obtener el webhook"
+ },
+ {
+ "id": "store.sql_webhooks.get_incoming_by_channel.app_error",
+ "translation": "No pudimos obtener los webhooks"
+ },
+ {
+ "id": "store.sql_webhooks.get_incoming_by_user.app_error",
+ "translation": "No pudimos obtener el webhook"
+ },
+ {
+ "id": "store.sql_webhooks.get_outgoing.app_error",
+ "translation": "No pudimos obtener el webhook"
+ },
+ {
+ "id": "store.sql_webhooks.get_outgoing_by_channel.app_error",
+ "translation": "No pudimos obtener los webhooks"
+ },
+ {
+ "id": "store.sql_webhooks.get_outgoing_by_team.app_error",
+ "translation": "No pudimos obtener los webhooks"
+ },
+ {
+ "id": "store.sql_webhooks.permanent_delete_incoming_by_user.app_error",
+ "translation": "No pudimos eliminar el webhook"
+ },
+ {
+ "id": "store.sql_webhooks.permanent_delete_outgoing_by_user.app_error",
+ "translation": "No pudimos eliminar el webhook"
+ },
+ {
+ "id": "store.sql_webhooks.save_incoming.app_error",
+ "translation": "No pudimos guardar el Webhook de Entrada"
+ },
+ {
+ "id": "store.sql_webhooks.save_incoming.existing.app_error",
+ "translation": "No puedes sobreescribir un Webhook de Entrada existente"
+ },
+ {
+ "id": "store.sql_webhooks.save_outgoing.app_error",
+ "translation": "No pudimos guardar el Webhook de Salida"
+ },
+ {
+ "id": "store.sql_webhooks.save_outgoing.override.app_error",
+ "translation": "No puedes sobreescribir un Webhook de Salida existente"
+ },
+ {
+ "id": "store.sql_webhooks.update_outgoing.app_error",
+ "translation": "No pudimos actualizar el webhook"
+ },
+ {
"id": "utils.config.load_config.decoding.panic",
"translation": "Error decifrando la configuración del archivo={{.Filename}}, err={{.Error}}"
},
@@ -2254,5 +3142,265 @@
{
"id": "utils.mail.test.configured.error",
"translation": "El servidor SMTP parece no estar configurado apropiadamente err=%v details=%v"
+ },
+ {
+ "id": "web.admin_console.title",
+ "translation": "Consola de Administración"
+ },
+ {
+ "id": "web.authorize_oauth.disabled.app_error",
+ "translation": "El administrador de sistema ha desactivado el servicio de autenticación por OAuth."
+ },
+ {
+ "id": "web.authorize_oauth.missing.app_error",
+ "translation": "Falta uno o más de response_type, client_id, or redirect_uri"
+ },
+ {
+ "id": "web.authorize_oauth.title",
+ "translation": "Autorizar Aplicación"
+ },
+ {
+ "id": "web.check_browser_compatibility.app_error",
+ "translation": "Tu navegador actual no está soportado, por favor actualiza a uno de los siguientes navegadores: Google Chrome 21 o superior, Internet Explorer 11 o superior, FireFox 14 o superior, Safari 9 o superior"
+ },
+ {
+ "id": "web.claim_account.team.error",
+ "translation": "No se encontro el equipo con nombre=%v, err=%v"
+ },
+ {
+ "id": "web.claim_account.title",
+ "translation": "Reclamar cuenta"
+ },
+ {
+ "id": "web.claim_account.user.error",
+ "translation": "No se encotró el usuario teamid=%v, email=%v, err=%v"
+ },
+ {
+ "id": "web.create_dir.error",
+ "translation": "Falla al crear el vigilante de directorio %v"
+ },
+ {
+ "id": "web.dir_fail.error",
+ "translation": "Falla al vigilar el directorio %v"
+ },
+ {
+ "id": "web.do_load_channel.error",
+ "translation": "Error obteniendo el pérfil de usuario para id=%v forzando el cierre de sesión"
+ },
+ {
+ "id": "web.doc.title",
+ "translation": "Documentación"
+ },
+ {
+ "id": "web.email_verified.title",
+ "translation": "Correo electrónico verificado"
+ },
+ {
+ "id": "web.find_team.title",
+ "translation": "Encontrar Equipo"
+ },
+ {
+ "id": "web.footer.about",
+ "translation": "Acerca"
+ },
+ {
+ "id": "web.footer.help",
+ "translation": "Ayuda"
+ },
+ {
+ "id": "web.footer.privacy",
+ "translation": "Privacidad"
+ },
+ {
+ "id": "web.footer.terms",
+ "translation": "Términos"
+ },
+ {
+ "id": "web.get_access_token.bad_client_id.app_error",
+ "translation": "invalid_request: client_id malo"
+ },
+ {
+ "id": "web.get_access_token.bad_client_secret.app_error",
+ "translation": "invalid_request: Falta client_secret"
+ },
+ {
+ "id": "web.get_access_token.bad_grant.app_error",
+ "translation": "invalid_request: grant_type malo"
+ },
+ {
+ "id": "web.get_access_token.credentials.app_error",
+ "translation": "invalid_client: credenciales inválidas de cliente"
+ },
+ {
+ "id": "web.get_access_token.disabled.app_error",
+ "translation": "El administrador de sistema ha desactivado el servicio de autenticación por OAuth."
+ },
+ {
+ "id": "web.get_access_token.exchanged.app_error",
+ "translation": "invalid_grant: El código de autorización ya fue intercambiado por un token de acceso"
+ },
+ {
+ "id": "web.get_access_token.expired_code.app_error",
+ "translation": "invalid_grant: Código de autorización inválido o expirado"
+ },
+ {
+ "id": "web.get_access_token.internal.app_error",
+ "translation": "server_error: Se encontró un error interno al intentar de accesar la base de datos"
+ },
+ {
+ "id": "web.get_access_token.internal_saving.app_error",
+ "translation": "server_error: Se encontró un error interno al guardar el token de acceso en la base de datos"
+ },
+ {
+ "id": "web.get_access_token.internal_session.app_error",
+ "translation": "server_error: Se enconttró un error interno al guardar la sesión en la base de datos"
+ },
+ {
+ "id": "web.get_access_token.internal_user.app_error",
+ "translation": "server_error: Se encontró un error interno al extraer el usuario de la base de datos"
+ },
+ {
+ "id": "web.get_access_token.missing_code.app_error",
+ "translation": "invalid_request: Falta el código"
+ },
+ {
+ "id": "web.get_access_token.redirect_uri.app_error",
+ "translation": "invalid_request: El redirect_uri suministrado no coincide con el código de autorización"
+ },
+ {
+ "id": "web.get_access_token.revoking.error",
+ "translation": "Se encontró un error al revocar el acceso del token, err="
+ },
+ {
+ "id": "web.incoming_webhook.channel.app_error",
+ "translation": "No se encontró el canal"
+ },
+ {
+ "id": "web.incoming_webhook.disabled.app_error",
+ "translation": "Webhooks entrantes han sido deshabilitados por el administrador del sistema."
+ },
+ {
+ "id": "web.incoming_webhook.invalid.app_error",
+ "translation": "Webhook inválido"
+ },
+ {
+ "id": "web.incoming_webhook.parse.app_error",
+ "translation": "No se puede analizar la data entrante"
+ },
+ {
+ "id": "web.incoming_webhook.permissions.app_error",
+ "translation": "Permisos del canal inapropiados"
+ },
+ {
+ "id": "web.incoming_webhook.text.app_error",
+ "translation": "No se especificó un texto"
+ },
+ {
+ "id": "web.incoming_webhook.user.app_error",
+ "translation": "No se encontró el usuario"
+ },
+ {
+ "id": "web.init.debug",
+ "translation": "Inicializando rutas Web"
+ },
+ {
+ "id": "web.login.error",
+ "translation": "No se encontro el equipo con nombre=%v, err=%v"
+ },
+ {
+ "id": "web.login.login_title",
+ "translation": "Inicio de sesión"
+ },
+ {
+ "id": "web.login_with_oauth.invalid_team.app_error",
+ "translation": "Nombre del equipo inválido"
+ },
+ {
+ "id": "web.parsing_templates.debug",
+ "translation": "Analizando el contenido de las plantillas en %v"
+ },
+ {
+ "id": "web.parsing_templates.error",
+ "translation": "Falla al analizar el contenido de las plantillas %v"
+ },
+ {
+ "id": "web.post_permalink.app_error",
+ "translation": "El ID del Mensaje es inválido"
+ },
+ {
+ "id": "web.reparse_templates.info",
+ "translation": "Re-analizando el contenido de las plantillas porque el archivo %v fue modificado"
+ },
+ {
+ "id": "web.reset_password.expired_link.app_error",
+ "translation": "El enlace de registro ha expirado"
+ },
+ {
+ "id": "web.reset_password.invalid_link.app_error",
+ "translation": "El enlace para restablecer la contraseña parece ser inválido"
+ },
+ {
+ "id": "web.root.home_title",
+ "translation": "Inicio"
+ },
+ {
+ "id": "web.root.singup_info",
+ "translation": "Todas las comunicaciones del equipo en un sólo lugar, con búsquedas y accesible desde cualquier parte"
+ },
+ {
+ "id": "web.root.singup_title",
+ "translation": "Registrar"
+ },
+ {
+ "id": "web.signup_team_complete.invalid_link.app_error",
+ "translation": "El enlace de registro parece ser inválido"
+ },
+ {
+ "id": "web.signup_team_complete.link_expired.app_error",
+ "translation": "El enlace de registro ha expirado"
+ },
+ {
+ "id": "web.signup_team_complete.title",
+ "translation": "Registro del equipo completado"
+ },
+ {
+ "id": "web.signup_team_confirm.title",
+ "translation": "Correo de registro enviado"
+ },
+ {
+ "id": "web.signup_user_complete.link_expired.app_error",
+ "translation": "El enlace de registro ha expirado"
+ },
+ {
+ "id": "web.signup_user_complete.link_invalid.app_error",
+ "translation": "El enlace de registro parece ser inválido"
+ },
+ {
+ "id": "web.signup_user_complete.no_invites.app_error",
+ "translation": "El tipo de equipo no permite realizar invitaciones"
+ },
+ {
+ "id": "web.signup_user_complete.title",
+ "translation": "Registro de usuario completado"
+ },
+ {
+ "id": "web.singup_with_oauth.disabled.app_error",
+ "translation": "El registro de usuario está deshabilitado."
+ },
+ {
+ "id": "web.singup_with_oauth.expired_link.app_error",
+ "translation": "El enlace de registro ha expirado"
+ },
+ {
+ "id": "web.singup_with_oauth.invalid_link.app_error",
+ "translation": "El enlace de registro parece ser inválido"
+ },
+ {
+ "id": "web.singup_with_oauth.invalid_team.app_error",
+ "translation": "Nombre del equipo inválido"
+ },
+ {
+ "id": "web.watcher_fail.error",
+ "translation": "Falla al agregar el directorio a ser vigilado %v"
}
-] \ No newline at end of file
+]
diff --git a/mattermost.go b/mattermost.go
index 51a9591db..b6652d812 100644
--- a/mattermost.go
+++ b/mattermost.go
@@ -58,9 +58,9 @@ func main() {
pwd, _ := os.Getwd()
l4g.Info(utils.T("mattermost.current_version"), model.CurrentVersion, model.BuildNumber, model.BuildDate, model.BuildHash)
- l4g.Info("Enterprise Enabled: %v", model.BuildEnterpriseReady)
- l4g.Info("Current working directory is %v", pwd)
- l4g.Info("Loaded config file from %v", utils.FindConfigFile(flagConfigFile))
+ l4g.Info(utils.T("mattermost.entreprise_enabled"), model.BuildEnterpriseReady)
+ l4g.Info(utils.T("mattermost.working_dir"), pwd)
+ l4g.Info(utils.T("mattermost.config_file"), utils.FindConfigFile(flagConfigFile))
api.NewServer()
api.InitApi()
@@ -118,7 +118,7 @@ func runSecurityAndDiagnosticsJobAndForget() {
currentTime := model.GetMillis()
if (currentTime - lastSecurityTime) > 1000*60*60*24*1 {
- l4g.Debug("Checking for security update from Mattermost")
+ l4g.Debug(utils.T("mattermost.security_checks.debug"))
v := url.Values{}
@@ -152,7 +152,7 @@ func runSecurityAndDiagnosticsJobAndForget() {
res, err := http.Get(utils.DIAGNOSTIC_URL + "/security?" + v.Encode())
if err != nil {
- l4g.Error("Failed to get security update information from Mattermost.")
+ l4g.Error(utils.T("mattermost.security_info.error"))
return
}
@@ -162,27 +162,27 @@ func runSecurityAndDiagnosticsJobAndForget() {
if bulletin.AppliesToVersion == model.CurrentVersion {
if props["SecurityBulletin_"+bulletin.Id] == "" {
if results := <-api.Srv.Store.User().GetSystemAdminProfiles(); results.Err != nil {
- l4g.Error("Failed to get system admins for security update information from Mattermost.")
+ l4g.Error(utils.T("mattermost.system_admins.error"))
return
} else {
users := results.Data.(map[string]*model.User)
resBody, err := http.Get(utils.DIAGNOSTIC_URL + "/bulletins/" + bulletin.Id)
if err != nil {
- l4g.Error("Failed to get security bulletin details")
+ l4g.Error(utils.T("mattermost.security_bulletin.error"))
return
}
body, err := ioutil.ReadAll(resBody.Body)
res.Body.Close()
if err != nil || resBody.StatusCode != 200 {
- l4g.Error("Failed to read security bulletin details")
+ l4g.Error(utils.T("mattermost.security_bulletin_read.error"))
return
}
for _, user := range users {
- l4g.Info("Sending security bulletin for " + bulletin.Id + " to " + user.Email)
- utils.SendMail(user.Email, "Mattermost Security Bulletin", string(body))
+ l4g.Info(utils.T("mattermost.send_bulletin.info"), bulletin.Id, user.Email)
+ utils.SendMail(user.Email, utils.T("mattermost.bulletin.subject"), string(body))
}
}
@@ -266,7 +266,7 @@ func cmdCreateTeam() {
api.CreateTeam(c, team)
if c.Err != nil {
- if c.Err.Message != "A team with that domain already exists" {
+ if c.Err.Id != "store.sql_team.save.domain_exists.app_error" {
l4g.Error("%v", c.Err)
flushLogAndExit(1)
}
@@ -313,7 +313,7 @@ func cmdCreateUser() {
_, err := api.CreateUser(team, user)
if err != nil {
- if err.Message != "An account with that email already exists." {
+ if err.Id != "store.sql_user.save.email_exists.app_error" {
l4g.Error("%v", err)
flushLogAndExit(1)
}
diff --git a/model/client.go b/model/client.go
index 21b8d8f7a..a271e6162 100644
--- a/model/client.go
+++ b/model/client.go
@@ -820,6 +820,17 @@ func (c *Client) UpdateUserRoles(data map[string]string) (*Result, *AppError) {
}
}
+func (c *Client) AttachDeviceId(deviceId string) (*Result, *AppError) {
+ data := make(map[string]string)
+ data["device_id"] = deviceId
+ if r, err := c.DoApiPost("/users/attach_device", MapToJson(data)); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), UserFromJson(r.Body)}, nil
+ }
+}
+
func (c *Client) UpdateActive(userId string, active bool) (*Result, *AppError) {
data := make(map[string]string)
data["user_id"] = userId
diff --git a/model/search_params.go b/model/search_params.go
index 17a64d980..9a7406a07 100644
--- a/model/search_params.go
+++ b/model/search_params.go
@@ -20,12 +20,7 @@ func splitWordsNoQuotes(text string) []string {
words := []string{}
for _, word := range strings.Fields(text) {
- word = puncStart.ReplaceAllString(word, "")
- word = puncEnd.ReplaceAllString(word, "")
-
- if len(word) != 0 {
- words = append(words, word)
- }
+ words = append(words, word)
}
return words
@@ -94,7 +89,16 @@ func parseSearchFlags(input []string) ([]string, [][2]string) {
}
if !isFlag {
- words = append(words, word)
+ // trim off surrounding punctuation
+ word = puncStart.ReplaceAllString(word, "")
+ word = puncEnd.ReplaceAllString(word, "")
+
+ // and remove extra pound #s
+ word = hashtagStart.ReplaceAllString(word, "#")
+
+ if len(word) != 0 {
+ words = append(words, word)
+ }
}
}
diff --git a/model/search_params_test.go b/model/search_params_test.go
index af4cbe595..59eb0a113 100644
--- a/model/search_params_test.go
+++ b/model/search_params_test.go
@@ -118,19 +118,19 @@ func TestParseSearchFlags(t *testing.T) {
t.Fatalf("got incorrect flags %v", flags)
}
- if words, flags := parseSearchFlags(splitWords("fruit: cherry")); len(words) != 2 || words[0] != "fruit:" || words[1] != "cherry" {
+ if words, flags := parseSearchFlags(splitWords("fruit: cherry")); len(words) != 2 || words[0] != "fruit" || words[1] != "cherry" {
t.Fatalf("got incorrect words %v", words)
} else if len(flags) != 0 {
t.Fatalf("got incorrect flags %v", flags)
}
- if words, flags := parseSearchFlags(splitWords("channel:")); len(words) != 1 || words[0] != "channel:" {
+ if words, flags := parseSearchFlags(splitWords("channel:")); len(words) != 1 || words[0] != "channel" {
t.Fatalf("got incorrect words %v", words)
} else if len(flags) != 0 {
t.Fatalf("got incorrect flags %v", flags)
}
- if words, flags := parseSearchFlags(splitWords("channel: first in: second from:")); len(words) != 1 || words[0] != "from:" {
+ if words, flags := parseSearchFlags(splitWords("channel: first in: second from:")); len(words) != 1 || words[0] != "from" {
t.Fatalf("got incorrect words %v", words)
} else if len(flags) != 2 || flags[0][0] != "channel" || flags[0][1] != "first" || flags[1][0] != "in" || flags[1][1] != "second" {
t.Fatalf("got incorrect flags %v", flags)
@@ -212,4 +212,8 @@ func TestParseSearchParams(t *testing.T) {
if sp := ParseSearchParams("testing in:channel from:someone"); len(sp) != 1 || sp[0].Terms != "testing" || len(sp[0].InChannels) != 1 || sp[0].InChannels[0] != "channel" || len(sp[0].FromUsers) != 1 || sp[0].FromUsers[0] != "someone" {
t.Fatalf("Incorrect output from parse search params: %v", sp[0])
}
+
+ if sp := ParseSearchParams("##hashtag +#plus+"); len(sp) != 1 || sp[0].Terms != "#hashtag #plus" || sp[0].IsHashtag != true || len(sp[0].InChannels) != 0 || len(sp[0].FromUsers) != 0 {
+ t.Fatalf("Incorrect output from parse search params: %v", sp[0])
+ }
}
diff --git a/model/utils.go b/model/utils.go
index 70b7e3bbd..695d4a0cb 100644
--- a/model/utils.go
+++ b/model/utils.go
@@ -71,16 +71,6 @@ func AppErrorFromJson(data io.Reader) *AppError {
}
}
-func NewAppError(where string, message string, details string) *AppError {
- ap := &AppError{}
- ap.Message = message
- ap.Where = where
- ap.DetailedError = details
- ap.StatusCode = 500
- ap.IsOAuth = false
- return ap
-}
-
func NewLocAppError(where string, id string, params map[string]interface{}, details string) *AppError {
ap := &AppError{}
ap.Id = id
@@ -298,8 +288,9 @@ func Etag(parts ...interface{}) string {
}
var validHashtag = regexp.MustCompile(`^(#[A-Za-zäöüÄÖÜß]+[A-Za-z0-9äöüÄÖÜß_\-]*[A-Za-z0-9äöüÄÖÜß])$`)
-var puncStart = regexp.MustCompile(`^[.,()&$!\?\[\]{}':;\\]+`)
-var puncEnd = regexp.MustCompile(`[.,()&$#!\?\[\]{}';\\]+$`)
+var puncStart = regexp.MustCompile(`^[.,()&$!\?\[\]{}':;\\<>\-+=%^*|]+`)
+var hashtagStart = regexp.MustCompile(`^#{2,}`)
+var puncEnd = regexp.MustCompile(`[.,()&$#!\?\[\]{}':;\\<>\-+=%^*|]+$`)
func ParseHashtags(text string) (string, string) {
words := strings.Fields(text)
@@ -307,8 +298,13 @@ func ParseHashtags(text string) (string, string) {
hashtagString := ""
plainString := ""
for _, word := range words {
+ // trim off surrounding punctuation
word = puncStart.ReplaceAllString(word, "")
word = puncEnd.ReplaceAllString(word, "")
+
+ // and remove extra pound #s
+ word = hashtagStart.ReplaceAllString(word, "#")
+
if validHashtag.MatchString(word) {
hashtagString += " " + word
} else {
diff --git a/model/utils_test.go b/model/utils_test.go
index 24ee4b7a6..02a08d113 100644
--- a/model/utils_test.go
+++ b/model/utils_test.go
@@ -27,7 +27,7 @@ func TestRandomString(t *testing.T) {
}
func TestAppError(t *testing.T) {
- err := NewAppError("TestAppError", "message", "")
+ err := NewLocAppError("TestAppError", "message", nil, "")
json := err.ToJson()
rerr := AppErrorFromJson(strings.NewReader(json))
if err.Message != rerr.Message {
@@ -83,19 +83,34 @@ func TestEtag(t *testing.T) {
}
var hashtags map[string]string = map[string]string{
- "#test": "#test",
- "test": "",
- "#test123": "#test123",
- "#123test123": "",
- "#test-test": "#test-test",
- "#test?": "#test",
- "hi #there": "#there",
- "#bug #idea": "#bug #idea",
- "#bug or #gif!": "#bug #gif",
- "#hüllo": "#hüllo",
- "#?test": "",
- "#-test": "",
- "#yo_yo": "#yo_yo",
+ "#test": "#test",
+ "test": "",
+ "#test123": "#test123",
+ "#123test123": "",
+ "#test-test": "#test-test",
+ "#test?": "#test",
+ "hi #there": "#there",
+ "#bug #idea": "#bug #idea",
+ "#bug or #gif!": "#bug #gif",
+ "#hüllo": "#hüllo",
+ "#?test": "",
+ "#-test": "",
+ "#yo_yo": "#yo_yo",
+ "(#brakets)": "#brakets",
+ ")#stekarb(": "#stekarb",
+ "<#less_than<": "#less_than",
+ ">#greater_than>": "#greater_than",
+ "-#minus-": "#minus",
+ "+#plus+": "#plus",
+ "=#equals=": "#equals",
+ "%#pct%": "#pct",
+ "&#and&": "#and",
+ "^#hat^": "#hat",
+ "##brown#": "#brown",
+ "*#star*": "#star",
+ "|#pipe|": "#pipe",
+ ":#colon:": "#colon",
+ ";#semi;": "#semi",
}
func TestParseHashtags(t *testing.T) {
diff --git a/model/version.go b/model/version.go
index 4a642d017..a5227f328 100644
--- a/model/version.go
+++ b/model/version.go
@@ -4,6 +4,7 @@
package model
import (
+ "fmt"
"strconv"
"strings"
)
@@ -25,10 +26,27 @@ var versions = []string{
}
var CurrentVersion string = versions[0]
-var BuildNumber = "dev"
-var BuildDate = "Fri Jan 8 14:19:26 UTC 2016"
-var BuildHash = "001a4448ca5fb0018eeb442915b473b121c04bf3"
-var BuildEnterpriseReady = "false"
+
+var BuildNumber = "_BUILD_NUMBER_"
+var BuildDate = "_BUILD_DATE_"
+var BuildHash = "_BUILD_HASH_"
+var BuildEnterpriseReady = "_BUILD_ENTERPRISE_READY_"
+var versionsWithoutHotFixes []string
+
+func init() {
+ versionsWithoutHotFixes = make([]string, 0, len(versions))
+ seen := make(map[string]string)
+
+ for _, version := range versions {
+ maj, min, _ := SplitVersion(version)
+ verStr := fmt.Sprintf("%v.%v.0", maj, min)
+
+ if seen[verStr] == "" {
+ versionsWithoutHotFixes = append(versionsWithoutHotFixes, verStr)
+ seen[verStr] = verStr
+ }
+ }
+}
func SplitVersion(version string) (int64, int64, int64) {
parts := strings.Split(version, ".")
@@ -52,25 +70,17 @@ func SplitVersion(version string) (int64, int64, int64) {
return major, minor, patch
}
-func GetPreviousVersion(currentVersion string) (int64, int64) {
- currentIndex := -1
- currentMajor, currentMinor, _ := SplitVersion(currentVersion)
-
- for index, version := range versions {
- major, minor, _ := SplitVersion(version)
-
- if currentMajor == major && currentMinor == minor {
- currentIndex = index
- }
+func GetPreviousVersion(version string) string {
+ verMajor, verMinor, _ := SplitVersion(version)
+ verStr := fmt.Sprintf("%v.%v.0", verMajor, verMinor)
- if currentIndex >= 0 {
- if currentMajor != major || currentMinor != minor {
- return major, minor
- }
+ for index, v := range versionsWithoutHotFixes {
+ if v == verStr && len(versionsWithoutHotFixes) > index+1 {
+ return versionsWithoutHotFixes[index+1]
}
}
- return 0, 0
+ return ""
}
func IsOfficalBuild() bool {
@@ -88,13 +98,24 @@ func IsCurrentVersion(versionToCheck string) bool {
}
}
-func IsPreviousVersion(versionToCheck string) bool {
+func IsPreviousVersionsSupported(versionToCheck string) bool {
toCheckMajor, toCheckMinor, _ := SplitVersion(versionToCheck)
- prevMajor, prevMinor := GetPreviousVersion(CurrentVersion)
+ versionToCheckStr := fmt.Sprintf("%v.%v.0", toCheckMajor, toCheckMinor)
- if toCheckMajor == prevMajor && toCheckMinor == prevMinor {
+ // Current Supported
+ if versionsWithoutHotFixes[0] == versionToCheckStr {
+ return true
+ }
+
+ // Current - 1 Supported
+ if versionsWithoutHotFixes[1] == versionToCheckStr {
return true
- } else {
- return false
}
+
+ // Current - 2 Supported
+ if versionsWithoutHotFixes[2] == versionToCheckStr {
+ return true
+ }
+
+ return false
}
diff --git a/model/version_test.go b/model/version_test.go
index 33e8dc93e..d73273ce5 100644
--- a/model/version_test.go
+++ b/model/version_test.go
@@ -36,20 +36,28 @@ func TestSplitVersion(t *testing.T) {
}
func TestGetPreviousVersion(t *testing.T) {
- if major, minor := GetPreviousVersion("1.0.0"); major != 0 || minor != 7 {
- t.Fatal(major, minor)
+ if GetPreviousVersion("1.3.0") != "1.2.0" {
+ t.Fatal()
+ }
+
+ if GetPreviousVersion("1.2.1") != "1.1.0" {
+ t.Fatal()
+ }
+
+ if GetPreviousVersion("1.1.0") != "1.0.0" {
+ t.Fatal()
}
- if major, minor := GetPreviousVersion("0.7.0"); major != 0 || minor != 6 {
- t.Fatal(major, minor)
+ if GetPreviousVersion("1.0.0") != "0.7.0" {
+ t.Fatal()
}
- if major, minor := GetPreviousVersion("0.7.1"); major != 0 || minor != 6 {
- t.Fatal(major, minor)
+ if GetPreviousVersion("0.7.1") != "0.6.0" {
+ t.Fatal()
}
- if major, minor := GetPreviousVersion("0.7111.1"); major != 0 || minor != 0 {
- t.Fatal(major, minor)
+ if GetPreviousVersion("0.5.0") != "" {
+ t.Fatal()
}
}
@@ -72,3 +80,31 @@ func TestIsCurrentVersion(t *testing.T) {
t.Fatal()
}
}
+
+func TestIsPreviousVersionsSupported(t *testing.T) {
+
+ // 1.4.0 CURRENT RELEASED VERSION
+ if !IsPreviousVersionsSupported(versions[0]) {
+ t.Fatal()
+ }
+
+ // 1.3.0
+ if !IsPreviousVersionsSupported(versions[1]) {
+ t.Fatal()
+ }
+
+ // 1.2.1
+ if !IsPreviousVersionsSupported(versions[2]) {
+ t.Fatal()
+ }
+
+ // 1.2.0
+ if !IsPreviousVersionsSupported(versions[3]) {
+ t.Fatal()
+ }
+
+ // 1.1.0 NOT SUPPORTED
+ if IsPreviousVersionsSupported(versions[4]) {
+ t.Fatal()
+ }
+}
diff --git a/store/sql_audit_store.go b/store/sql_audit_store.go
index f4fd29aab..97df5f7e7 100644
--- a/store/sql_audit_store.go
+++ b/store/sql_audit_store.go
@@ -45,8 +45,8 @@ func (s SqlAuditStore) Save(audit *model.Audit) StoreChannel {
audit.CreateAt = model.GetMillis()
if err := s.GetMaster().Insert(audit); err != nil {
- result.Err = model.NewAppError("SqlAuditStore.Save",
- "We encountered an error saving the audit", "user_id="+
+ result.Err = model.NewLocAppError("SqlAuditStore.Save",
+ "store.sql_audit.save.saving.app_error", nil, "user_id="+
audit.UserId+" action="+audit.Action)
}
@@ -66,7 +66,7 @@ func (s SqlAuditStore) Get(user_id string, limit int) StoreChannel {
if limit > 1000 {
limit = 1000
- result.Err = model.NewAppError("SqlAuditStore.Get", "Limit exceeded for paging", "user_id="+user_id)
+ result.Err = model.NewLocAppError("SqlAuditStore.Get", "store.sql_audit.get.limit.app_error", nil, "user_id="+user_id)
storeChannel <- result
close(storeChannel)
return
@@ -75,7 +75,7 @@ func (s SqlAuditStore) Get(user_id string, limit int) StoreChannel {
var audits model.Audits
if _, err := s.GetReplica().Select(&audits, "SELECT * FROM Audits WHERE UserId = :user_id ORDER BY CreateAt DESC LIMIT :limit",
map[string]interface{}{"user_id": user_id, "limit": limit}); err != nil {
- result.Err = model.NewAppError("SqlAuditStore.Get", "We encountered an error finding the audits", "user_id="+user_id)
+ result.Err = model.NewLocAppError("SqlAuditStore.Get", "store.sql_audit.get.finding.app_error", nil, "user_id="+user_id)
} else {
result.Data = audits
}
@@ -96,7 +96,7 @@ func (s SqlAuditStore) PermanentDeleteByUser(userId string) StoreChannel {
if _, err := s.GetMaster().Exec("DELETE FROM Audits WHERE UserId = :userId",
map[string]interface{}{"userId": userId}); err != nil {
- result.Err = model.NewAppError("SqlAuditStore.Delete", "We encountered an error deleting the audits", "user_id="+userId)
+ result.Err = model.NewLocAppError("SqlAuditStore.Delete", "store.sql_audit.permanent_delete_by_user.app_error", nil, "user_id="+userId)
}
storeChannel <- result
diff --git a/store/sql_channel_store.go b/store/sql_channel_store.go
index 7400df8d2..8b52dae12 100644
--- a/store/sql_channel_store.go
+++ b/store/sql_channel_store.go
@@ -55,17 +55,17 @@ func (s SqlChannelStore) Save(channel *model.Channel) StoreChannel {
go func() {
var result StoreResult
if channel.Type == model.CHANNEL_DIRECT {
- result.Err = model.NewAppError("SqlChannelStore.Save", "Use SaveDirectChannel to create a direct channel", "")
+ result.Err = model.NewLocAppError("SqlChannelStore.Save", "store.sql_channel.save.direct_channel.app_error", nil, "")
} else {
if transaction, err := s.GetMaster().Begin(); err != nil {
- result.Err = model.NewAppError("SqlChannelStore.Save", "Unable to open transaction", err.Error())
+ result.Err = model.NewLocAppError("SqlChannelStore.Save", "store.sql_channel.save.open_transaction.app_error", nil, err.Error())
} else {
result = s.saveChannelT(transaction, channel)
if result.Err != nil {
transaction.Rollback()
} else {
if err := transaction.Commit(); err != nil {
- result.Err = model.NewAppError("SqlChannelStore.Save", "Unable to commit transaction", err.Error())
+ result.Err = model.NewLocAppError("SqlChannelStore.Save", "store.sql_channel.save.commit_transaction.app_error", nil, err.Error())
}
}
}
@@ -85,10 +85,10 @@ func (s SqlChannelStore) SaveDirectChannel(directchannel *model.Channel, member1
var result StoreResult
if directchannel.Type != model.CHANNEL_DIRECT {
- result.Err = model.NewAppError("SqlChannelStore.SaveDirectChannel", "Not a direct channel attempted to be created with SaveDirectChannel", "")
+ result.Err = model.NewLocAppError("SqlChannelStore.SaveDirectChannel", "store.sql_channel.save_direct_channel.not_direct.app_error", nil, "")
} else {
if transaction, err := s.GetMaster().Begin(); err != nil {
- result.Err = model.NewAppError("SqlChannelStore.SaveDirectChannel", "Unable to open transaction", err.Error())
+ result.Err = model.NewLocAppError("SqlChannelStore.SaveDirectChannel", "store.sql_channel.save_direct_channel.open_transaction.app_error", nil, err.Error())
} else {
channelResult := s.saveChannelT(transaction, directchannel)
@@ -113,10 +113,10 @@ func (s SqlChannelStore) SaveDirectChannel(directchannel *model.Channel, member1
if member2Result.Err != nil {
details += "Member2Err: " + member2Result.Err.Message
}
- result.Err = model.NewAppError("SqlChannelStore.SaveDirectChannel", "Unable to add direct channel members", details)
+ result.Err = model.NewLocAppError("SqlChannelStore.SaveDirectChannel", "store.sql_channel.save_direct_channel.add_members.app_error", nil, details)
} else {
if err := transaction.Commit(); err != nil {
- result.Err = model.NewAppError("SqlChannelStore.SaveDirectChannel", "Ubable to commit transaction", err.Error())
+ result.Err = model.NewLocAppError("SqlChannelStore.SaveDirectChannel", "store.sql_channel.save_direct_channel.commit.app_error", nil, err.Error())
} else {
result = channelResult
}
@@ -136,7 +136,7 @@ func (s SqlChannelStore) saveChannelT(transaction *gorp.Transaction, channel *mo
result := StoreResult{}
if len(channel.Id) > 0 {
- result.Err = model.NewAppError("SqlChannelStore.Save", "Must call update for exisiting channel", "id="+channel.Id)
+ result.Err = model.NewLocAppError("SqlChannelStore.Save", "store.sql_channel.save_channel.existing.app_error", nil, "id="+channel.Id)
return result
}
@@ -147,10 +147,10 @@ func (s SqlChannelStore) saveChannelT(transaction *gorp.Transaction, channel *mo
if channel.Type != model.CHANNEL_DIRECT {
if count, err := transaction.SelectInt("SELECT COUNT(0) FROM Channels WHERE TeamId = :TeamId AND DeleteAt = 0 AND (Type = 'O' OR Type = 'P')", map[string]interface{}{"TeamId": channel.TeamId}); err != nil {
- result.Err = model.NewAppError("SqlChannelStore.Save", "Failed to get current channel count", "teamId="+channel.TeamId+", "+err.Error())
+ result.Err = model.NewLocAppError("SqlChannelStore.Save", "store.sql_channel.save_channel.current_count.app_error", nil, "teamId="+channel.TeamId+", "+err.Error())
return result
} else if count > 1000 {
- result.Err = model.NewAppError("SqlChannelStore.Save", "You've reached the limit of the number of allowed channels.", "teamId="+channel.TeamId)
+ result.Err = model.NewLocAppError("SqlChannelStore.Save", "store.sql_channel.save_channel.limit.app_error", nil, "teamId="+channel.TeamId)
return result
}
}
@@ -160,12 +160,12 @@ func (s SqlChannelStore) saveChannelT(transaction *gorp.Transaction, channel *mo
dupChannel := model.Channel{}
s.GetMaster().SelectOne(&dupChannel, "SELECT * FROM Channels WHERE TeamId = :TeamId AND Name = :Name AND DeleteAt > 0", map[string]interface{}{"TeamId": channel.TeamId, "Name": channel.Name})
if dupChannel.DeleteAt > 0 {
- result.Err = model.NewAppError("SqlChannelStore.Update", "A channel with that URL was previously created", "id="+channel.Id+", "+err.Error())
+ result.Err = model.NewLocAppError("SqlChannelStore.Update", "store.sql_channel.save_channel.previously.app_error", nil, "id="+channel.Id+", "+err.Error())
} else {
- result.Err = model.NewAppError("SqlChannelStore.Update", "A channel with that URL already exists", "id="+channel.Id+", "+err.Error())
+ result.Err = model.NewLocAppError("SqlChannelStore.Update", "store.sql_channel.save_channel.exists.app_error", nil, "id="+channel.Id+", "+err.Error())
}
} else {
- result.Err = model.NewAppError("SqlChannelStore.Save", "We couldn't save the channel", "id="+channel.Id+", "+err.Error())
+ result.Err = model.NewLocAppError("SqlChannelStore.Save", "store.sql_channel.save_channel.save.app_error", nil, "id="+channel.Id+", "+err.Error())
}
} else {
result.Data = channel
@@ -194,15 +194,15 @@ func (s SqlChannelStore) Update(channel *model.Channel) StoreChannel {
dupChannel := model.Channel{}
s.GetReplica().SelectOne(&dupChannel, "SELECT * FROM Channels WHERE TeamId = :TeamId AND Name= :Name AND DeleteAt > 0", map[string]interface{}{"TeamId": channel.TeamId, "Name": channel.Name})
if dupChannel.DeleteAt > 0 {
- result.Err = model.NewAppError("SqlChannelStore.Update", "A channel with that handle was previously created", "id="+channel.Id+", "+err.Error())
+ result.Err = model.NewLocAppError("SqlChannelStore.Update", "store.sql_channel.update.previously.app_error", nil, "id="+channel.Id+", "+err.Error())
} else {
- result.Err = model.NewAppError("SqlChannelStore.Update", "A channel with that handle already exists", "id="+channel.Id+", "+err.Error())
+ result.Err = model.NewLocAppError("SqlChannelStore.Update", "store.sql_channel.update.exists.app_error", nil, "id="+channel.Id+", "+err.Error())
}
} else {
- result.Err = model.NewAppError("SqlChannelStore.Update", "We encountered an error updating the channel", "id="+channel.Id+", "+err.Error())
+ result.Err = model.NewLocAppError("SqlChannelStore.Update", "store.sql_channel.update.updating.app_error", nil, "id="+channel.Id+", "+err.Error())
}
} else if count != 1 {
- result.Err = model.NewAppError("SqlChannelStore.Update", "We couldn't update the channel", "id="+channel.Id)
+ result.Err = model.NewLocAppError("SqlChannelStore.Update", "store.sql_channel.update.app_error", nil, "id="+channel.Id)
} else {
result.Data = channel
}
@@ -232,7 +232,7 @@ func (s SqlChannelStore) extraUpdated(channel *model.Channel) StoreChannel {
map[string]interface{}{"Id": channel.Id, "Time": channel.ExtraUpdateAt})
if err != nil {
- result.Err = model.NewAppError("SqlChannelStore.extraUpdated", "Problem updating members last updated time", "id="+channel.Id+", "+err.Error())
+ result.Err = model.NewLocAppError("SqlChannelStore.extraUpdated", "store.sql_channel.extra_updated.app_error", nil, "id="+channel.Id+", "+err.Error())
}
storeChannel <- result
@@ -264,9 +264,9 @@ func (s SqlChannelStore) get(id string, master bool) StoreChannel {
}
if obj, err := db.Get(model.Channel{}, id); err != nil {
- result.Err = model.NewAppError("SqlChannelStore.Get", "We encountered an error finding the channel", "id="+id+", "+err.Error())
+ result.Err = model.NewLocAppError("SqlChannelStore.Get", "store.sql_channel.get.find.app_error", nil, "id="+id+", "+err.Error())
} else if obj == nil {
- result.Err = model.NewAppError("SqlChannelStore.Get", "We couldn't find the existing channel", "id="+id)
+ result.Err = model.NewLocAppError("SqlChannelStore.Get", "store.sql_channel.get.existing.app_error", nil, "id="+id)
} else {
result.Data = obj.(*model.Channel)
}
@@ -286,7 +286,7 @@ func (s SqlChannelStore) Delete(channelId string, time int64) StoreChannel {
_, err := s.GetMaster().Exec("Update Channels SET DeleteAt = :Time, UpdateAt = :Time WHERE Id = :ChannelId", map[string]interface{}{"Time": time, "ChannelId": channelId})
if err != nil {
- result.Err = model.NewAppError("SqlChannelStore.Delete", "We couldn't delete the channel", "id="+channelId+", err="+err.Error())
+ result.Err = model.NewLocAppError("SqlChannelStore.Delete", "store.sql_channel.delete.channel.app_error", nil, "id="+channelId+", err="+err.Error())
}
storeChannel <- result
@@ -303,7 +303,7 @@ func (s SqlChannelStore) PermanentDeleteByTeam(teamId string) StoreChannel {
result := StoreResult{}
if _, err := s.GetMaster().Exec("DELETE FROM Channels WHERE TeamId = :TeamId", map[string]interface{}{"TeamId": teamId}); err != nil {
- result.Err = model.NewAppError("SqlChannelStore.PermanentDeleteByTeam", "We couldn't delete the channels", "teamId="+teamId+", "+err.Error())
+ result.Err = model.NewLocAppError("SqlChannelStore.PermanentDeleteByTeam", "store.sql_channel.permanent_delete_by_team.app_error", nil, "teamId="+teamId+", "+err.Error())
}
storeChannel <- result
@@ -328,7 +328,7 @@ func (s SqlChannelStore) GetChannels(teamId string, userId string) StoreChannel
_, err := s.GetReplica().Select(&data, "SELECT * FROM Channels, ChannelMembers WHERE Id = ChannelId AND TeamId = :TeamId AND UserId = :UserId AND DeleteAt = 0 ORDER BY DisplayName", map[string]interface{}{"TeamId": teamId, "UserId": userId})
if err != nil {
- result.Err = model.NewAppError("SqlChannelStore.GetChannels", "We couldn't get the channels", "teamId="+teamId+", userId="+userId+", err="+err.Error())
+ result.Err = model.NewLocAppError("SqlChannelStore.GetChannels", "store.sql_channel.get_channels.get.app_error", nil, "teamId="+teamId+", userId="+userId+", err="+err.Error())
} else {
channels := &model.ChannelList{make([]*model.Channel, len(data)), make(map[string]*model.ChannelMember)}
for i := range data {
@@ -338,7 +338,7 @@ func (s SqlChannelStore) GetChannels(teamId string, userId string) StoreChannel
}
if len(channels.Channels) == 0 {
- result.Err = model.NewAppError("SqlChannelStore.GetChannels", "No channels were found", "teamId="+teamId+", userId="+userId)
+ result.Err = model.NewLocAppError("SqlChannelStore.GetChannels", "store.sql_channel.get_channels.not_found.app_error", nil, "teamId="+teamId+", userId="+userId)
} else {
result.Data = channels
}
@@ -381,7 +381,7 @@ func (s SqlChannelStore) GetMoreChannels(teamId string, userId string) StoreChan
map[string]interface{}{"TeamId1": teamId, "TeamId2": teamId, "UserId": userId})
if err != nil {
- result.Err = model.NewAppError("SqlChannelStore.GetMoreChannels", "We couldn't get the channels", "teamId="+teamId+", userId="+userId+", err="+err.Error())
+ result.Err = model.NewLocAppError("SqlChannelStore.GetMoreChannels", "store.sql_channel.get_more_channels.get.app_error", nil, "teamId="+teamId+", userId="+userId+", err="+err.Error())
} else {
result.Data = &model.ChannelList{data, make(map[string]*model.ChannelMember)}
}
@@ -409,7 +409,7 @@ func (s SqlChannelStore) GetChannelCounts(teamId string, userId string) StoreCha
_, err := s.GetReplica().Select(&data, "SELECT Id, TotalMsgCount, UpdateAt FROM Channels WHERE Id IN (SELECT ChannelId FROM ChannelMembers WHERE UserId = :UserId) AND TeamId = :TeamId AND DeleteAt = 0 ORDER BY DisplayName", map[string]interface{}{"TeamId": teamId, "UserId": userId})
if err != nil {
- result.Err = model.NewAppError("SqlChannelStore.GetChannelCounts", "We couldn't get the channel counts", "teamId="+teamId+", userId="+userId+", err="+err.Error())
+ result.Err = model.NewLocAppError("SqlChannelStore.GetChannelCounts", "store.sql_channel.get_channel_counts.get.app_error", nil, "teamId="+teamId+", userId="+userId+", err="+err.Error())
} else {
counts := &model.ChannelCounts{Counts: make(map[string]int64), UpdateTimes: make(map[string]int64)}
for i := range data {
@@ -437,7 +437,7 @@ func (s SqlChannelStore) GetByName(teamId string, name string) StoreChannel {
channel := model.Channel{}
if err := s.GetReplica().SelectOne(&channel, "SELECT * FROM Channels WHERE TeamId = :TeamId AND Name= :Name AND DeleteAt = 0", map[string]interface{}{"TeamId": teamId, "Name": name}); err != nil {
- result.Err = model.NewAppError("SqlChannelStore.GetByName", "We couldn't find the existing channel", "teamId="+teamId+", "+"name="+name+", "+err.Error())
+ result.Err = model.NewLocAppError("SqlChannelStore.GetByName", "store.sql_channel.get_by_name.existing.app_error", nil, "teamId="+teamId+", "+"name="+name+", "+err.Error())
} else {
result.Data = &channel
}
@@ -461,14 +461,14 @@ func (s SqlChannelStore) SaveMember(member *model.ChannelMember) StoreChannel {
channel := cr.Data.(*model.Channel)
if transaction, err := s.GetMaster().Begin(); err != nil {
- result.Err = model.NewAppError("SqlChannelStore.SaveMember", "Unable to open transaction", err.Error())
+ result.Err = model.NewLocAppError("SqlChannelStore.SaveMember", "store.sql_channel.save_member.open_transaction.app_error", nil, err.Error())
} else {
result = s.saveMemberT(transaction, member, channel)
if result.Err != nil {
transaction.Rollback()
} else {
if err := transaction.Commit(); err != nil {
- result.Err = model.NewAppError("SqlChannelStore.SaveMember", "Unable to commit transaction", err.Error())
+ result.Err = model.NewLocAppError("SqlChannelStore.SaveMember", "store.sql_channel.save_member.commit_transaction.app_error", nil, err.Error())
}
// If sucessfull record members have changed in channel
if mu := <-s.extraUpdated(channel); mu.Err != nil {
@@ -495,9 +495,9 @@ func (s SqlChannelStore) saveMemberT(transaction *gorp.Transaction, member *mode
if err := transaction.Insert(member); err != nil {
if IsUniqueConstraintError(err.Error(), "ChannelId", "channelmembers_pkey") {
- result.Err = model.NewAppError("SqlChannelStore.SaveMember", "A channel member with that id already exists", "channel_id="+member.ChannelId+", user_id="+member.UserId+", "+err.Error())
+ result.Err = model.NewLocAppError("SqlChannelStore.SaveMember", "store.sql_channel.save_member.exists.app_error", nil, "channel_id="+member.ChannelId+", user_id="+member.UserId+", "+err.Error())
} else {
- result.Err = model.NewAppError("SqlChannelStore.SaveMember", "We couldn't save the channel member", "channel_id="+member.ChannelId+", user_id="+member.UserId+", "+err.Error())
+ result.Err = model.NewLocAppError("SqlChannelStore.SaveMember", "store.sql_channel.save_member.save.app_error", nil, "channel_id="+member.ChannelId+", user_id="+member.UserId+", "+err.Error())
}
} else {
result.Data = member
@@ -521,7 +521,7 @@ func (s SqlChannelStore) UpdateMember(member *model.ChannelMember) StoreChannel
}
if _, err := s.GetMaster().Update(member); err != nil {
- result.Err = model.NewAppError("SqlChannelStore.UpdateMember", "We encountered an error updating the channel member",
+ result.Err = model.NewLocAppError("SqlChannelStore.UpdateMember", "store.sql_channel.update_member.app_error", nil,
"channel_id="+member.ChannelId+", "+"user_id="+member.UserId+", "+err.Error())
} else {
result.Data = member
@@ -543,7 +543,7 @@ func (s SqlChannelStore) GetMembers(channelId string) StoreChannel {
var members []model.ChannelMember
_, err := s.GetReplica().Select(&members, "SELECT * FROM ChannelMembers WHERE ChannelId = :ChannelId", map[string]interface{}{"ChannelId": channelId})
if err != nil {
- result.Err = model.NewAppError("SqlChannelStore.GetMembers", "We couldn't get the channel members", "channel_id="+channelId+err.Error())
+ result.Err = model.NewLocAppError("SqlChannelStore.GetMembers", "store.sql_channel.get_members.app_error", nil, "channel_id="+channelId+err.Error())
} else {
result.Data = members
}
@@ -564,7 +564,7 @@ func (s SqlChannelStore) GetMember(channelId string, userId string) StoreChannel
var member model.ChannelMember
err := s.GetReplica().SelectOne(&member, "SELECT * FROM ChannelMembers WHERE ChannelId = :ChannelId AND UserId = :UserId", map[string]interface{}{"ChannelId": channelId, "UserId": userId})
if err != nil {
- result.Err = model.NewAppError("SqlChannelStore.GetMember", "We couldn't get the channel member", "channel_id="+channelId+"user_id="+userId+","+err.Error())
+ result.Err = model.NewLocAppError("SqlChannelStore.GetMember", "store.sql_channel.get_member.app_error", nil, "channel_id="+channelId+"user_id="+userId+","+err.Error())
} else {
result.Data = member
}
@@ -593,7 +593,7 @@ func (s SqlChannelStore) GetMemberCount(channelId string) StoreChannel {
AND ChannelMembers.ChannelId = :ChannelId
AND Users.DeleteAt = 0`, map[string]interface{}{"ChannelId": channelId})
if err != nil {
- result.Err = model.NewAppError("SqlChannelStore.GetMemberCount", "We couldn't get the channel member count", "channel_id="+channelId+", "+err.Error())
+ result.Err = model.NewLocAppError("SqlChannelStore.GetMemberCount", "store.sql_channel.get_member_count.app_error", nil, "channel_id="+channelId+", "+err.Error())
} else {
result.Data = count
}
@@ -621,7 +621,7 @@ func (s SqlChannelStore) GetExtraMembers(channelId string, limit int) StoreChann
}
if err != nil {
- result.Err = model.NewAppError("SqlChannelStore.GetExtraMembers", "We couldn't get the extra info for channel members", "channel_id="+channelId+", "+err.Error())
+ result.Err = model.NewLocAppError("SqlChannelStore.GetExtraMembers", "store.sql_channel.get_extra_members.app_error", nil, "channel_id="+channelId+", "+err.Error())
} else {
for i := range members {
members[i].Sanitize(utils.Cfg.GetSanitizeOptions())
@@ -650,7 +650,7 @@ func (s SqlChannelStore) RemoveMember(channelId string, userId string) StoreChan
_, err := s.GetMaster().Exec("DELETE FROM ChannelMembers WHERE ChannelId = :ChannelId AND UserId = :UserId", map[string]interface{}{"ChannelId": channelId, "UserId": userId})
if err != nil {
- result.Err = model.NewAppError("SqlChannelStore.RemoveMember", "We couldn't remove the channel member", "channel_id="+channelId+", user_id="+userId+", "+err.Error())
+ result.Err = model.NewLocAppError("SqlChannelStore.RemoveMember", "store.sql_channel.remove_member.app_error", nil, "channel_id="+channelId+", user_id="+userId+", "+err.Error())
} else {
// If sucessfull record members have changed in channel
if mu := <-s.extraUpdated(channel); mu.Err != nil {
@@ -673,7 +673,7 @@ func (s SqlChannelStore) PermanentDeleteMembersByUser(userId string) StoreChanne
result := StoreResult{}
if _, err := s.GetMaster().Exec("DELETE FROM ChannelMembers WHERE UserId = :UserId", map[string]interface{}{"UserId": userId}); err != nil {
- result.Err = model.NewAppError("SqlChannelStore.RemoveMember", "We couldn't remove the channel member", "user_id="+userId+", "+err.Error())
+ result.Err = model.NewLocAppError("SqlChannelStore.RemoveMember", "store.sql_channel.permanent_delete_members_by_user.app_error", nil, "user_id="+userId+", "+err.Error())
}
storeChannel <- result
@@ -703,7 +703,7 @@ func (s SqlChannelStore) CheckPermissionsTo(teamId string, channelId string, use
AND ChannelMembers.UserId = :UserId`,
map[string]interface{}{"TeamId": teamId, "ChannelId": channelId, "UserId": userId})
if err != nil {
- result.Err = model.NewAppError("SqlChannelStore.CheckPermissionsTo", "We couldn't check the permissions", "channel_id="+channelId+", user_id="+userId+", "+err.Error())
+ result.Err = model.NewLocAppError("SqlChannelStore.CheckPermissionsTo", "store.sql_channel.check_permissions.app_error", nil, "channel_id="+channelId+", user_id="+userId+", "+err.Error())
} else {
result.Data = count
}
@@ -735,7 +735,7 @@ func (s SqlChannelStore) CheckPermissionsToByName(teamId string, channelName str
AND ChannelMembers.UserId = :UserId`,
map[string]interface{}{"TeamId": teamId, "Name": channelName, "UserId": userId})
if err != nil {
- result.Err = model.NewAppError("SqlChannelStore.CheckPermissionsToByName", "We couldn't check the permissions", "channel_id="+channelName+", user_id="+userId+", "+err.Error())
+ result.Err = model.NewLocAppError("SqlChannelStore.CheckPermissionsToByName", "store.sql_channel.check_permissions_by_name.app_error", nil, "channel_id="+channelName+", user_id="+userId+", "+err.Error())
} else {
result.Data = channelId
}
@@ -764,7 +764,7 @@ func (s SqlChannelStore) CheckOpenChannelPermissions(teamId string, channelId st
AND Channels.Type = :ChannelType`,
map[string]interface{}{"ChannelId": channelId, "TeamId": teamId, "ChannelType": model.CHANNEL_OPEN})
if err != nil {
- result.Err = model.NewAppError("SqlChannelStore.CheckOpenChannelPermissions", "We couldn't check the permissions", "channel_id="+channelId+", "+err.Error())
+ result.Err = model.NewLocAppError("SqlChannelStore.CheckOpenChannelPermissions", "store.sql_channel.check_open_channel_permissions.app_error", nil, "channel_id="+channelId+", "+err.Error())
} else {
result.Data = count
}
@@ -814,7 +814,7 @@ func (s SqlChannelStore) UpdateLastViewedAt(channelId string, userId string) Sto
_, err := s.GetMaster().Exec(query, map[string]interface{}{"ChannelId": channelId, "UserId": userId})
if err != nil {
- result.Err = model.NewAppError("SqlChannelStore.UpdateLastViewedAt", "We couldn't update the last viewed at time", "channel_id="+channelId+", user_id="+userId+", "+err.Error())
+ result.Err = model.NewLocAppError("SqlChannelStore.UpdateLastViewedAt", "store.sql_channel.update_last_viewed_at.app_error", nil, "channel_id="+channelId+", user_id="+userId+", "+err.Error())
}
storeChannel <- result
@@ -840,7 +840,7 @@ func (s SqlChannelStore) IncrementMentionCount(channelId string, userId string)
AND ChannelId = :ChannelId`,
map[string]interface{}{"ChannelId": channelId, "UserId": userId})
if err != nil {
- result.Err = model.NewAppError("SqlChannelStore.IncrementMentionCount", "We couldn't increment the mention count", "channel_id="+channelId+", user_id="+userId+", "+err.Error())
+ result.Err = model.NewLocAppError("SqlChannelStore.IncrementMentionCount", "store.sql_channel.increment_mention_count.app_error", nil, "channel_id="+channelId+", user_id="+userId+", "+err.Error())
}
storeChannel <- result
@@ -860,7 +860,7 @@ func (s SqlChannelStore) GetForExport(teamId string) StoreChannel {
_, err := s.GetReplica().Select(&data, "SELECT * FROM Channels WHERE TeamId = :TeamId AND DeleteAt = 0 AND Type = 'O'", map[string]interface{}{"TeamId": teamId})
if err != nil {
- result.Err = model.NewAppError("SqlChannelStore.GetAllChannels", "We couldn't get all the channels", "teamId="+teamId+", err="+err.Error())
+ result.Err = model.NewLocAppError("SqlChannelStore.GetAllChannels", "store.sql_channel.get_for_export.app_error", nil, "teamId="+teamId+", err="+err.Error())
} else {
result.Data = data
}
@@ -886,7 +886,7 @@ func (s SqlChannelStore) AnalyticsTypeCount(teamId string, channelType string) S
v, err := s.GetReplica().SelectInt(query, map[string]interface{}{"TeamId": teamId, "ChannelType": channelType})
if err != nil {
- result.Err = model.NewAppError("SqlChannelStore.AnalyticsTypeCount", "We couldn't get channel type counts", err.Error())
+ result.Err = model.NewLocAppError("SqlChannelStore.AnalyticsTypeCount", "store.sql_channel.analytics_type_count.app_error", nil, err.Error())
} else {
result.Data = v
}
diff --git a/store/sql_oauth_store.go b/store/sql_oauth_store.go
index 43a5bee31..e41f584a6 100644
--- a/store/sql_oauth_store.go
+++ b/store/sql_oauth_store.go
@@ -60,7 +60,7 @@ func (as SqlOAuthStore) SaveApp(app *model.OAuthApp) StoreChannel {
result := StoreResult{}
if len(app.Id) > 0 {
- result.Err = model.NewAppError("SqlOAuthStore.SaveApp", "Must call update for exisiting app", "app_id="+app.Id)
+ result.Err = model.NewLocAppError("SqlOAuthStore.SaveApp", "store.sql_oauth.save_app.existing.app_error", nil, "app_id="+app.Id)
storeChannel <- result
close(storeChannel)
return
@@ -74,7 +74,7 @@ func (as SqlOAuthStore) SaveApp(app *model.OAuthApp) StoreChannel {
}
if err := as.GetMaster().Insert(app); err != nil {
- result.Err = model.NewAppError("SqlOAuthStore.SaveApp", "We couldn't save the app.", "app_id="+app.Id+", "+err.Error())
+ result.Err = model.NewLocAppError("SqlOAuthStore.SaveApp", "store.sql_oauth.save_app.save.app_error", nil, "app_id="+app.Id+", "+err.Error())
} else {
result.Data = app
}
@@ -102,9 +102,9 @@ func (as SqlOAuthStore) UpdateApp(app *model.OAuthApp) StoreChannel {
}
if oldAppResult, err := as.GetMaster().Get(model.OAuthApp{}, app.Id); err != nil {
- result.Err = model.NewAppError("SqlOAuthStore.UpdateApp", "We encountered an error finding the app", "app_id="+app.Id+", "+err.Error())
+ result.Err = model.NewLocAppError("SqlOAuthStore.UpdateApp", "store.sql_oauth.update_app.finding.app_error", nil, "app_id="+app.Id+", "+err.Error())
} else if oldAppResult == nil {
- result.Err = model.NewAppError("SqlOAuthStore.UpdateApp", "We couldn't find the existing app to update", "app_id="+app.Id)
+ result.Err = model.NewLocAppError("SqlOAuthStore.UpdateApp", "store.sql_oauth.update_app.find.app_error", nil, "app_id="+app.Id)
} else {
oldApp := oldAppResult.(*model.OAuthApp)
app.CreateAt = oldApp.CreateAt
@@ -112,9 +112,9 @@ func (as SqlOAuthStore) UpdateApp(app *model.OAuthApp) StoreChannel {
app.CreatorId = oldApp.CreatorId
if count, err := as.GetMaster().Update(app); err != nil {
- result.Err = model.NewAppError("SqlOAuthStore.UpdateApp", "We encountered an error updating the app", "app_id="+app.Id+", "+err.Error())
+ result.Err = model.NewLocAppError("SqlOAuthStore.UpdateApp", "store.sql_oauth.update_app.updating.app_error", nil, "app_id="+app.Id+", "+err.Error())
} else if count != 1 {
- result.Err = model.NewAppError("SqlOAuthStore.UpdateApp", "We couldn't update the app", "app_id="+app.Id)
+ result.Err = model.NewLocAppError("SqlOAuthStore.UpdateApp", "store.sql_oauth.update_app.update.app_error", nil, "app_id="+app.Id)
} else {
result.Data = [2]*model.OAuthApp{app, oldApp}
}
@@ -135,9 +135,9 @@ func (as SqlOAuthStore) GetApp(id string) StoreChannel {
result := StoreResult{}
if obj, err := as.GetReplica().Get(model.OAuthApp{}, id); err != nil {
- result.Err = model.NewAppError("SqlOAuthStore.GetApp", "We encountered an error finding the app", "app_id="+id+", "+err.Error())
+ result.Err = model.NewLocAppError("SqlOAuthStore.GetApp", "store.sql_oauth.get_app.finding.app_error", nil, "app_id="+id+", "+err.Error())
} else if obj == nil {
- result.Err = model.NewAppError("SqlOAuthStore.GetApp", "We couldn't find the existing app", "app_id="+id)
+ result.Err = model.NewLocAppError("SqlOAuthStore.GetApp", "store.sql_oauth.get_app.find.app_error", nil, "app_id="+id)
} else {
result.Data = obj.(*model.OAuthApp)
}
@@ -160,7 +160,7 @@ func (as SqlOAuthStore) GetAppByUser(userId string) StoreChannel {
var apps []*model.OAuthApp
if _, err := as.GetReplica().Select(&apps, "SELECT * FROM OAuthApps WHERE CreatorId = :UserId", map[string]interface{}{"UserId": userId}); err != nil {
- result.Err = model.NewAppError("SqlOAuthStore.GetAppByUser", "We couldn't find any existing apps", "user_id="+userId+", "+err.Error())
+ result.Err = model.NewLocAppError("SqlOAuthStore.GetAppByUser", "store.sql_oauth.get_app_by_user.find.app_error", nil, "user_id="+userId+", "+err.Error())
}
result.Data = apps
@@ -186,7 +186,7 @@ func (as SqlOAuthStore) SaveAccessData(accessData *model.AccessData) StoreChanne
}
if err := as.GetMaster().Insert(accessData); err != nil {
- result.Err = model.NewAppError("SqlOAuthStore.SaveAccessData", "We couldn't save the access token.", err.Error())
+ result.Err = model.NewLocAppError("SqlOAuthStore.SaveAccessData", "store.sql_oauth.save_access_data.app_error", nil, err.Error())
} else {
result.Data = accessData
}
@@ -208,7 +208,7 @@ func (as SqlOAuthStore) GetAccessData(token string) StoreChannel {
accessData := model.AccessData{}
if err := as.GetReplica().SelectOne(&accessData, "SELECT * FROM OAuthAccessData WHERE Token = :Token", map[string]interface{}{"Token": token}); err != nil {
- result.Err = model.NewAppError("SqlOAuthStore.GetAccessData", "We encountered an error finding the access token", err.Error())
+ result.Err = model.NewLocAppError("SqlOAuthStore.GetAccessData", "store.sql_oauth.get_access_data.app_error", nil, err.Error())
} else {
result.Data = &accessData
}
@@ -234,7 +234,7 @@ func (as SqlOAuthStore) GetAccessDataByAuthCode(authCode string) StoreChannel {
if strings.Contains(err.Error(), "no rows") {
result.Data = nil
} else {
- result.Err = model.NewAppError("SqlOAuthStore.GetAccessDataByAuthCode", "We encountered an error finding the access token", err.Error())
+ result.Err = model.NewLocAppError("SqlOAuthStore.GetAccessDataByAuthCode", "store.sql_oauth.get_access_data_by_code.app_error", nil, err.Error())
}
} else {
result.Data = &accessData
@@ -255,7 +255,7 @@ func (as SqlOAuthStore) RemoveAccessData(token string) StoreChannel {
result := StoreResult{}
if _, err := as.GetMaster().Exec("DELETE FROM OAuthAccessData WHERE Token = :Token", map[string]interface{}{"Token": token}); err != nil {
- result.Err = model.NewAppError("SqlOAuthStore.RemoveAccessData", "We couldn't remove the access token", "err="+err.Error())
+ result.Err = model.NewLocAppError("SqlOAuthStore.RemoveAccessData", "store.sql_oauth.remove_access_data.app_error", nil, "err="+err.Error())
}
storeChannel <- result
@@ -280,7 +280,7 @@ func (as SqlOAuthStore) SaveAuthData(authData *model.AuthData) StoreChannel {
}
if err := as.GetMaster().Insert(authData); err != nil {
- result.Err = model.NewAppError("SqlOAuthStore.SaveAuthData", "We couldn't save the authorization code.", err.Error())
+ result.Err = model.NewLocAppError("SqlOAuthStore.SaveAuthData", "store.sql_oauth.save_auth_data.app_error", nil, err.Error())
} else {
result.Data = authData
}
@@ -300,9 +300,9 @@ func (as SqlOAuthStore) GetAuthData(code string) StoreChannel {
result := StoreResult{}
if obj, err := as.GetReplica().Get(model.AuthData{}, code); err != nil {
- result.Err = model.NewAppError("SqlOAuthStore.GetAuthData", "We encountered an error finding the authorization code", err.Error())
+ result.Err = model.NewLocAppError("SqlOAuthStore.GetAuthData", "store.sql_oauth.get_auth_data.finding.app_error", nil, err.Error())
} else if obj == nil {
- result.Err = model.NewAppError("SqlOAuthStore.GetAuthData", "We couldn't find the existing authorization code", "")
+ result.Err = model.NewLocAppError("SqlOAuthStore.GetAuthData", "store.sql_oauth.get_auth_data.find.app_error", nil, "")
} else {
result.Data = obj.(*model.AuthData)
}
@@ -323,7 +323,7 @@ func (as SqlOAuthStore) RemoveAuthData(code string) StoreChannel {
_, err := as.GetMaster().Exec("DELETE FROM OAuthAuthData WHERE Code = :Code", map[string]interface{}{"Code": code})
if err != nil {
- result.Err = model.NewAppError("SqlOAuthStore.RemoveAuthData", "We couldn't remove the authorization code", "err="+err.Error())
+ result.Err = model.NewLocAppError("SqlOAuthStore.RemoveAuthData", "store.sql_oauth.remove_auth_data.app_error", nil, "err="+err.Error())
}
storeChannel <- result
@@ -341,7 +341,7 @@ func (as SqlOAuthStore) PermanentDeleteAuthDataByUser(userId string) StoreChanne
_, err := as.GetMaster().Exec("DELETE FROM OAuthAuthData WHERE UserId = :UserId", map[string]interface{}{"UserId": userId})
if err != nil {
- result.Err = model.NewAppError("SqlOAuthStore.RemoveAuthDataByUserId", "We couldn't remove the authorization code", "err="+err.Error())
+ result.Err = model.NewLocAppError("SqlOAuthStore.RemoveAuthDataByUserId", "store.sql_oauth.permanent_delete_auth_data_by_user.app_error", nil, "err="+err.Error())
}
storeChannel <- result
diff --git a/store/sql_post_store.go b/store/sql_post_store.go
index e332858e4..aeaa5922c 100644
--- a/store/sql_post_store.go
+++ b/store/sql_post_store.go
@@ -38,6 +38,11 @@ func NewSqlPostStore(sqlStore *SqlStore) PostStore {
}
func (s SqlPostStore) UpgradeSchemaIfNeeded() {
+ // ADDED for 1.3 REMOVE for 1.6
+ s.RemoveColumnIfExists("Posts", "ImgCount")
+
+ // ADDED for 1.3 REMOVE for 1.6
+ s.GetMaster().Exec(`UPDATE Preferences SET Type = :NewType WHERE Type = :CurrentType`, map[string]string{"NewType": model.POST_JOIN_LEAVE, "CurrentType": "join_leave"})
}
func (s SqlPostStore) CreateIndexesIfNotExists() {
@@ -57,8 +62,8 @@ func (s SqlPostStore) Save(post *model.Post) StoreChannel {
result := StoreResult{}
if len(post.Id) > 0 {
- result.Err = model.NewAppError("SqlPostStore.Save",
- "You cannot update an existing Post", "id="+post.Id)
+ result.Err = model.NewLocAppError("SqlPostStore.Save",
+ "store.sql_post.save.existing.app_error", nil, "id="+post.Id)
storeChannel <- result
close(storeChannel)
return
@@ -72,7 +77,7 @@ func (s SqlPostStore) Save(post *model.Post) StoreChannel {
}
if err := s.GetMaster().Insert(post); err != nil {
- result.Err = model.NewAppError("SqlPostStore.Save", "We couldn't save the Post", "id="+post.Id+", "+err.Error())
+ result.Err = model.NewLocAppError("SqlPostStore.Save", "store.sql_post.save.app_error", nil, "id="+post.Id+", "+err.Error())
} else {
time := model.GetMillis()
@@ -120,7 +125,7 @@ func (s SqlPostStore) Update(oldPost *model.Post, newMessage string, newHashtags
}
if _, err := s.GetMaster().Update(&editPost); err != nil {
- result.Err = model.NewAppError("SqlPostStore.Update", "We couldn't update the Post", "id="+editPost.Id+", "+err.Error())
+ result.Err = model.NewLocAppError("SqlPostStore.Update", "store.sql_post.update.app_error", nil, "id="+editPost.Id+", "+err.Error())
} else {
time := model.GetMillis()
s.GetMaster().Exec("UPDATE Channels SET LastPostAt = :LastPostAt WHERE Id = :ChannelId", map[string]interface{}{"LastPostAt": time, "ChannelId": editPost.ChannelId})
@@ -152,7 +157,7 @@ func (s SqlPostStore) Get(id string) StoreChannel {
var post model.Post
err := s.GetReplica().SelectOne(&post, "SELECT * FROM Posts WHERE Id = :Id AND DeleteAt = 0", map[string]interface{}{"Id": id})
if err != nil {
- result.Err = model.NewAppError("SqlPostStore.GetPost", "We couldn't get the post", "id="+id+err.Error())
+ result.Err = model.NewLocAppError("SqlPostStore.GetPost", "store.sql_post.get.app_error", nil, "id="+id+err.Error())
}
pl.AddPost(&post)
@@ -167,7 +172,7 @@ func (s SqlPostStore) Get(id string) StoreChannel {
var posts []*model.Post
_, err = s.GetReplica().Select(&posts, "SELECT * FROM Posts WHERE (Id = :Id OR RootId = :RootId) AND DeleteAt = 0", map[string]interface{}{"Id": rootId, "RootId": rootId})
if err != nil {
- result.Err = model.NewAppError("SqlPostStore.GetPost", "We couldn't get the post", "root_id="+rootId+err.Error())
+ result.Err = model.NewLocAppError("SqlPostStore.GetPost", "store.sql_post.get.app_error", nil, "root_id="+rootId+err.Error())
} else {
for _, p := range posts {
pl.AddPost(p)
@@ -217,7 +222,7 @@ func (s SqlPostStore) Delete(postId string, time int64) StoreChannel {
_, err := s.GetMaster().Exec("Update Posts SET DeleteAt = :DeleteAt, UpdateAt = :UpdateAt WHERE Id = :Id OR ParentId = :ParentId OR RootId = :RootId", map[string]interface{}{"DeleteAt": time, "UpdateAt": time, "Id": postId, "ParentId": postId, "RootId": postId})
if err != nil {
- result.Err = model.NewAppError("SqlPostStore.Delete", "We couldn't delete the post", "id="+postId+", err="+err.Error())
+ result.Err = model.NewLocAppError("SqlPostStore.Delete", "store.sql_post.delete.app_error", nil, "id="+postId+", err="+err.Error())
}
storeChannel <- result
@@ -235,7 +240,7 @@ func (s SqlPostStore) permanentDelete(postId string) StoreChannel {
_, err := s.GetMaster().Exec("DELETE FROM Posts WHERE Id = :Id OR ParentId = :ParentId OR RootId = :RootId", map[string]interface{}{"Id": postId, "ParentId": postId, "RootId": postId})
if err != nil {
- result.Err = model.NewAppError("SqlPostStore.Delete", "We couldn't delete the post", "id="+postId+", err="+err.Error())
+ result.Err = model.NewLocAppError("SqlPostStore.Delete", "store.sql_post.permanent_delete.app_error", nil, "id="+postId+", err="+err.Error())
}
storeChannel <- result
@@ -253,7 +258,7 @@ func (s SqlPostStore) permanentDeleteAllCommentByUser(userId string) StoreChanne
_, err := s.GetMaster().Exec("DELETE FROM Posts WHERE UserId = :UserId AND RootId != ''", map[string]interface{}{"UserId": userId})
if err != nil {
- result.Err = model.NewAppError("SqlPostStore.permanentDeleteAllCommentByUser", "We couldn't delete the comments for user", "userId="+userId+", err="+err.Error())
+ result.Err = model.NewLocAppError("SqlPostStore.permanentDeleteAllCommentByUser", "store.sql_post.permanent_delete_all_comments_by_user.app_error", nil, "userId="+userId+", err="+err.Error())
}
storeChannel <- result
@@ -286,7 +291,7 @@ func (s SqlPostStore) PermanentDeleteByUser(userId string) StoreChannel {
var ids []string
_, err := s.GetMaster().Select(&ids, "SELECT Id FROM Posts WHERE UserId = :UserId LIMIT 1000", map[string]interface{}{"UserId": userId})
if err != nil {
- result.Err = model.NewAppError("SqlPostStore.PermanentDeleteByUser.select", "We couldn't select the posts to delete for the user", "userId="+userId+", err="+err.Error())
+ result.Err = model.NewLocAppError("SqlPostStore.PermanentDeleteByUser.select", "store.sql_post.permanent_delete_by_user.app_error", nil, "userId="+userId+", err="+err.Error())
storeChannel <- result
close(storeChannel)
return
@@ -306,7 +311,7 @@ func (s SqlPostStore) PermanentDeleteByUser(userId string) StoreChannel {
// This is a fail safe, give up if more than 10K messages
count = count + 1
if count >= 10 {
- result.Err = model.NewAppError("SqlPostStore.PermanentDeleteByUser.toolarge", "We couldn't select the posts to delete for the user (too many), please re-run", "userId="+userId)
+ result.Err = model.NewLocAppError("SqlPostStore.PermanentDeleteByUser.toolarge", "store.sql_post.permanent_delete_by_user.too_many.app_error", nil, "userId="+userId)
storeChannel <- result
close(storeChannel)
return
@@ -327,7 +332,7 @@ func (s SqlPostStore) GetPosts(channelId string, offset int, limit int) StoreCha
result := StoreResult{}
if limit > 1000 {
- result.Err = model.NewAppError("SqlPostStore.GetLinearPosts", "Limit exceeded for paging", "channelId="+channelId)
+ result.Err = model.NewLocAppError("SqlPostStore.GetLinearPosts", "store.sql_post.get_posts.app_error", nil, "channelId="+channelId)
storeChannel <- result
close(storeChannel)
return
@@ -403,7 +408,7 @@ func (s SqlPostStore) GetPostsSince(channelId string, time int64) StoreChannel {
map[string]interface{}{"ChannelId": channelId, "Time": time})
if err != nil {
- result.Err = model.NewAppError("SqlPostStore.GetPostsSince", "We couldn't get the posts for the channel", "channelId="+channelId+err.Error())
+ result.Err = model.NewLocAppError("SqlPostStore.GetPostsSince", "store.sql_post.get_posts_since.app_error", nil, "channelId="+channelId+err.Error())
} else {
list := &model.PostList{Order: make([]string, 0, len(posts))}
@@ -488,9 +493,9 @@ func (s SqlPostStore) getPostsAround(channelId string, postId string, numPosts i
map[string]interface{}{"ChannelId": channelId, "PostId": postId, "NumPosts": numPosts, "Offset": offset})
if err1 != nil {
- result.Err = model.NewAppError("SqlPostStore.GetPostContext", "We couldn't get the posts for the channel", "channelId="+channelId+err1.Error())
+ result.Err = model.NewLocAppError("SqlPostStore.GetPostContext", "store.sql_post.get_posts_around.get.app_error", nil, "channelId="+channelId+err1.Error())
} else if err2 != nil {
- result.Err = model.NewAppError("SqlPostStore.GetPostContext", "We couldn't get the parent posts for the channel", "channelId="+channelId+err2.Error())
+ result.Err = model.NewLocAppError("SqlPostStore.GetPostContext", "store.sql_post.get_posts_around.get_parent.app_error", nil, "channelId="+channelId+err2.Error())
} else {
list := &model.PostList{Order: make([]string, 0, len(posts))}
@@ -532,7 +537,7 @@ func (s SqlPostStore) getRootPosts(channelId string, offset int, limit int) Stor
var posts []*model.Post
_, err := s.GetReplica().Select(&posts, "SELECT * FROM Posts WHERE ChannelId = :ChannelId AND DeleteAt = 0 ORDER BY CreateAt DESC LIMIT :Limit OFFSET :Offset", map[string]interface{}{"ChannelId": channelId, "Offset": offset, "Limit": limit})
if err != nil {
- result.Err = model.NewAppError("SqlPostStore.GetLinearPosts", "We couldn't get the posts for the channel", "channelId="+channelId+err.Error())
+ result.Err = model.NewLocAppError("SqlPostStore.GetLinearPosts", "store.sql_post.get_root_posts.app_error", nil, "channelId="+channelId+err.Error())
} else {
result.Data = posts
}
@@ -577,7 +582,7 @@ func (s SqlPostStore) getParentsPosts(channelId string, offset int, limit int) S
ORDER BY CreateAt`,
map[string]interface{}{"ChannelId1": channelId, "Offset": offset, "Limit": limit, "ChannelId2": channelId})
if err != nil {
- result.Err = model.NewAppError("SqlPostStore.GetLinearPosts", "We couldn't get the parent post for the channel", "channelId="+channelId+err.Error())
+ result.Err = model.NewLocAppError("SqlPostStore.GetLinearPosts", "store.sql_post.get_parents_posts.app_error", nil, "channelId="+channelId+err.Error())
} else {
result.Data = posts
}
@@ -642,6 +647,7 @@ func (s SqlPostStore) Search(teamId string, userId string, params *model.SearchP
Posts
WHERE
DeleteAt = 0
+ AND Type NOT LIKE '` + model.POST_SYSTEM_MESSAGE_PREFIX + `%'
POST_FILTER
AND ChannelId IN (
SELECT
@@ -733,7 +739,7 @@ func (s SqlPostStore) Search(teamId string, userId string, params *model.SearchP
_, err := s.GetReplica().Select(&posts, searchQuery, queryParams)
if err != nil {
- result.Err = model.NewAppError("SqlPostStore.Search", "We encountered an error while searching for posts", "teamId="+teamId+", err="+err.Error())
+ result.Err = model.NewLocAppError("SqlPostStore.Search", "store.sql_post.search.app_error", nil, "teamId="+teamId+", err="+err.Error())
}
list := &model.PostList{Order: make([]string, 0, len(posts))}
@@ -777,7 +783,7 @@ func (s SqlPostStore) GetForExport(channelId string) StoreChannel {
"SELECT * FROM Posts WHERE ChannelId = :ChannelId AND DeleteAt = 0",
map[string]interface{}{"ChannelId": channelId})
if err != nil {
- result.Err = model.NewAppError("SqlPostStore.GetForExport", "We couldn't get the posts for the channel", "channelId="+channelId+err.Error())
+ result.Err = model.NewLocAppError("SqlPostStore.GetForExport", "store.sql_post.get_for_export.app_error", nil, "channelId="+channelId+err.Error())
} else {
result.Data = posts
}
@@ -849,7 +855,7 @@ func (s SqlPostStore) AnalyticsUserCountsWithPostsByDay(teamId string) StoreChan
query,
map[string]interface{}{"TeamId": teamId, "EndTime": end})
if err != nil {
- result.Err = model.NewAppError("SqlPostStore.AnalyticsUserCountsWithPostsByDay", "We couldn't get user counts with posts", err.Error())
+ result.Err = model.NewLocAppError("SqlPostStore.AnalyticsUserCountsWithPostsByDay", "store.sql_post.analytics_user_counts_posts_by_day.app_error", nil, err.Error())
} else {
result.Data = rows
}
@@ -922,7 +928,7 @@ func (s SqlPostStore) AnalyticsPostCountsByDay(teamId string) StoreChannel {
query,
map[string]interface{}{"TeamId": teamId, "StartTime": start, "EndTime": end})
if err != nil {
- result.Err = model.NewAppError("SqlPostStore.AnalyticsPostCountsByDay", "We couldn't get post counts by day", err.Error())
+ result.Err = model.NewLocAppError("SqlPostStore.AnalyticsPostCountsByDay", "store.sql_post.analytics_posts_count_by_day.app_error", nil, err.Error())
} else {
result.Data = rows
}
@@ -955,7 +961,7 @@ func (s SqlPostStore) AnalyticsPostCount(teamId string) StoreChannel {
v, err := s.GetReplica().SelectInt(query, map[string]interface{}{"TeamId": teamId})
if err != nil {
- result.Err = model.NewAppError("SqlPostStore.AnalyticsPostCount", "We couldn't get post counts", err.Error())
+ result.Err = model.NewLocAppError("SqlPostStore.AnalyticsPostCount", "store.sql_post.analytics_posts_count.app_error", nil, err.Error())
} else {
result.Data = v
}
diff --git a/store/sql_post_store_test.go b/store/sql_post_store_test.go
index a3e3e10dd..46b8d7678 100644
--- a/store/sql_post_store_test.go
+++ b/store/sql_post_store_test.go
@@ -676,6 +676,13 @@ func TestPostStoreSearch(t *testing.T) {
o1.Message = "corey mattermost new york"
o1 = (<-store.Post().Save(o1)).Data.(*model.Post)
+ o1a := &model.Post{}
+ o1a.ChannelId = c1.Id
+ o1a.UserId = model.NewId()
+ o1a.Message = "corey mattermost new york"
+ o1a.Type = model.POST_JOIN_LEAVE
+ o1a = (<-store.Post().Save(o1a)).Data.(*model.Post)
+
o2 := &model.Post{}
o2.ChannelId = c1.Id
o2.UserId = model.NewId()
diff --git a/store/sql_preference_store.go b/store/sql_preference_store.go
index 6302d2f4f..bb3fca3e3 100644
--- a/store/sql_preference_store.go
+++ b/store/sql_preference_store.go
@@ -42,7 +42,7 @@ func (s SqlPreferenceStore) CreateIndexesIfNotExists() {
}
func (s SqlPreferenceStore) DeleteUnusedFeatures() {
- l4g.Debug("Deleting any unused pre-release features")
+ l4g.Debug(utils.T("store.sql_preference.delete_unused_features.debug"))
sql := `DELETE
FROM Preferences
@@ -67,7 +67,7 @@ func (s SqlPreferenceStore) Save(preferences *model.Preferences) StoreChannel {
// wrap in a transaction so that if one fails, everything fails
transaction, err := s.GetMaster().Begin()
if err != nil {
- result.Err = model.NewAppError("SqlPreferenceStore.Save", "Unable to open transaction to save preferences", err.Error())
+ result.Err = model.NewLocAppError("SqlPreferenceStore.Save", "store.sql_preference.save.open_transaction.app_error", nil, err.Error())
} else {
for _, preference := range *preferences {
if upsertResult := s.save(transaction, &preference); upsertResult.Err != nil {
@@ -79,13 +79,13 @@ func (s SqlPreferenceStore) Save(preferences *model.Preferences) StoreChannel {
if result.Err == nil {
if err := transaction.Commit(); err != nil {
// don't need to rollback here since the transaction is already closed
- result.Err = model.NewAppError("SqlPreferenceStore.Save", "Unable to commit transaction to save preferences", err.Error())
+ result.Err = model.NewLocAppError("SqlPreferenceStore.Save", "store.sql_preference.save.commit_transaction.app_error", nil, err.Error())
} else {
result.Data = len(*preferences)
}
} else {
if err := transaction.Rollback(); err != nil {
- result.Err = model.NewAppError("SqlPreferenceStore.Save", "Unable to rollback transaction to save preferences", err.Error())
+ result.Err = model.NewLocAppError("SqlPreferenceStore.Save", "store.sql_preference.save.rollback_transaction.app_error", nil, err.Error())
}
}
}
@@ -120,7 +120,7 @@ func (s SqlPreferenceStore) save(transaction *gorp.Transaction, preference *mode
(:UserId, :Category, :Name, :Value)
ON DUPLICATE KEY UPDATE
Value = :Value`, params); err != nil {
- result.Err = model.NewAppError("SqlPreferenceStore.save", "We encountered an error while updating preferences", err.Error())
+ result.Err = model.NewLocAppError("SqlPreferenceStore.save", "store.sql_preference.save.updating.app_error", nil, err.Error())
}
} else if utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_POSTGRES {
// postgres has no way to upsert values until version 9.5 and trying inserting and then updating causes transactions to abort
@@ -134,7 +134,7 @@ func (s SqlPreferenceStore) save(transaction *gorp.Transaction, preference *mode
AND Category = :Category
AND Name = :Name`, params)
if err != nil {
- result.Err = model.NewAppError("SqlPreferenceStore.save", "We encountered an error while updating preferences", err.Error())
+ result.Err = model.NewLocAppError("SqlPreferenceStore.save", "store.sql_preference.save.updating.app_error", nil, err.Error())
return result
}
@@ -144,7 +144,7 @@ func (s SqlPreferenceStore) save(transaction *gorp.Transaction, preference *mode
s.insert(transaction, preference)
}
} else {
- result.Err = model.NewAppError("SqlPreferenceStore.save", "We encountered an error while updating preferences",
+ result.Err = model.NewLocAppError("SqlPreferenceStore.save", "store.sql_preference.save.missing_driver.app_error", nil,
"Failed to update preference because of missing driver")
}
@@ -156,10 +156,10 @@ func (s SqlPreferenceStore) insert(transaction *gorp.Transaction, preference *mo
if err := transaction.Insert(preference); err != nil {
if IsUniqueConstraintError(err.Error(), "UserId", "preferences_pkey") {
- result.Err = model.NewAppError("SqlPreferenceStore.insert", "A preference with that user id, category, and name already exists",
+ result.Err = model.NewLocAppError("SqlPreferenceStore.insert", "store.sql_preference.insert.exists.app_error", nil,
"user_id="+preference.UserId+", category="+preference.Category+", name="+preference.Name+", "+err.Error())
} else {
- result.Err = model.NewAppError("SqlPreferenceStore.insert", "We couldn't save the preference",
+ result.Err = model.NewLocAppError("SqlPreferenceStore.insert", "store.sql_preference.insert.save.app_error", nil,
"user_id="+preference.UserId+", category="+preference.Category+", name="+preference.Name+", "+err.Error())
}
}
@@ -171,7 +171,7 @@ func (s SqlPreferenceStore) update(transaction *gorp.Transaction, preference *mo
result := StoreResult{}
if _, err := transaction.Update(preference); err != nil {
- result.Err = model.NewAppError("SqlPreferenceStore.update", "We couldn't update the preference",
+ result.Err = model.NewLocAppError("SqlPreferenceStore.update", "store.sql_preference.update.app_error", nil,
"user_id="+preference.UserId+", category="+preference.Category+", name="+preference.Name+", "+err.Error())
}
@@ -195,7 +195,7 @@ func (s SqlPreferenceStore) Get(userId string, category string, name string) Sto
UserId = :UserId
AND Category = :Category
AND Name = :Name`, map[string]interface{}{"UserId": userId, "Category": category, "Name": name}); err != nil {
- result.Err = model.NewAppError("SqlPreferenceStore.Get", "We encountered an error while finding preferences", err.Error())
+ result.Err = model.NewLocAppError("SqlPreferenceStore.Get", "store.sql_preference.get.app_error", nil, err.Error())
} else {
result.Data = preference
}
@@ -223,7 +223,7 @@ func (s SqlPreferenceStore) GetCategory(userId string, category string) StoreCha
WHERE
UserId = :UserId
AND Category = :Category`, map[string]interface{}{"UserId": userId, "Category": category}); err != nil {
- result.Err = model.NewAppError("SqlPreferenceStore.GetCategory", "We encountered an error while finding preferences", err.Error())
+ result.Err = model.NewLocAppError("SqlPreferenceStore.GetCategory", "store.sql_preference.get_category.app_error", nil, err.Error())
} else {
result.Data = preferences
}
@@ -250,7 +250,7 @@ func (s SqlPreferenceStore) GetAll(userId string) StoreChannel {
Preferences
WHERE
UserId = :UserId`, map[string]interface{}{"UserId": userId}); err != nil {
- result.Err = model.NewAppError("SqlPreferenceStore.GetAll", "We encountered an error while finding preferences", err.Error())
+ result.Err = model.NewLocAppError("SqlPreferenceStore.GetAll", "store.sql_preference.get_all.app_error", nil, err.Error())
} else {
result.Data = preferences
}
@@ -270,7 +270,7 @@ func (s SqlPreferenceStore) PermanentDeleteByUser(userId string) StoreChannel {
if _, err := s.GetMaster().Exec(
`DELETE FROM Preferences WHERE UserId = :UserId`, map[string]interface{}{"UserId": userId}); err != nil {
- result.Err = model.NewAppError("SqlPreferenceStore.Delete", "We encountered an error while deleteing preferences", err.Error())
+ result.Err = model.NewLocAppError("SqlPreferenceStore.Delete", "store.sql_preference.permanent_delete_by_user.app_error", nil, err.Error())
}
storeChannel <- result
@@ -293,7 +293,7 @@ func (s SqlPreferenceStore) IsFeatureEnabled(feature, userId string) StoreChanne
UserId = :UserId
AND Category = :Category
AND Name = :Name`, map[string]interface{}{"UserId": userId, "Category": model.PREFERENCE_CATEGORY_ADVANCED_SETTINGS, "Name": FEATURE_TOGGLE_PREFIX + feature}); err != nil {
- result.Err = model.NewAppError("SqlPreferenceStore.IsFeatureEnabled", "We encountered an error while finding a pre release feature preference", err.Error())
+ result.Err = model.NewLocAppError("SqlPreferenceStore.IsFeatureEnabled", "store.sql_preference.is_feature_enabled.app_error", nil, err.Error())
} else {
result.Data = value == "true"
}
diff --git a/store/sql_session_store.go b/store/sql_session_store.go
index 6b0a31443..6532947f4 100644
--- a/store/sql_session_store.go
+++ b/store/sql_session_store.go
@@ -6,6 +6,7 @@ package store
import (
l4g "github.com/alecthomas/log4go"
"github.com/mattermost/platform/model"
+ "github.com/mattermost/platform/utils"
)
type SqlSessionStore struct {
@@ -45,7 +46,7 @@ func (me SqlSessionStore) Save(session *model.Session) StoreChannel {
result := StoreResult{}
if len(session.Id) > 0 {
- result.Err = model.NewAppError("SqlSessionStore.Save", "Cannot update existing session", "id="+session.Id)
+ result.Err = model.NewLocAppError("SqlSessionStore.Save", "store.sql_session.save.existing.app_error", nil, "id="+session.Id)
storeChannel <- result
close(storeChannel)
return
@@ -54,11 +55,11 @@ func (me SqlSessionStore) Save(session *model.Session) StoreChannel {
session.PreSave()
if cur := <-me.CleanUpExpiredSessions(session.UserId); cur.Err != nil {
- l4g.Error("Failed to cleanup sessions in Save err=%v", cur.Err)
+ l4g.Error(utils.T("store.sql_session.save.cleanup.error"), cur.Err)
}
if err := me.GetMaster().Insert(session); err != nil {
- result.Err = model.NewAppError("SqlSessionStore.Save", "We couldn't save the session", "id="+session.Id+", "+err.Error())
+ result.Err = model.NewLocAppError("SqlSessionStore.Save", "store.sql_session.save.app_error", nil, "id="+session.Id+", "+err.Error())
} else {
result.Data = session
}
@@ -80,9 +81,9 @@ func (me SqlSessionStore) Get(sessionIdOrToken string) StoreChannel {
var sessions []*model.Session
if _, err := me.GetReplica().Select(&sessions, "SELECT * FROM Sessions WHERE Token = :Token OR Id = :Id LIMIT 1", map[string]interface{}{"Token": sessionIdOrToken, "Id": sessionIdOrToken}); err != nil {
- result.Err = model.NewAppError("SqlSessionStore.Get", "We encountered an error finding the session", "sessionIdOrToken="+sessionIdOrToken+", "+err.Error())
+ result.Err = model.NewLocAppError("SqlSessionStore.Get", "store.sql_session.get.app_error", nil, "sessionIdOrToken="+sessionIdOrToken+", "+err.Error())
} else if sessions == nil || len(sessions) == 0 {
- result.Err = model.NewAppError("SqlSessionStore.Get", "We encountered an error finding the session", "sessionIdOrToken="+sessionIdOrToken)
+ result.Err = model.NewLocAppError("SqlSessionStore.Get", "store.sql_session.get.app_error", nil, "sessionIdOrToken="+sessionIdOrToken)
} else {
result.Data = sessions[0]
}
@@ -101,7 +102,7 @@ func (me SqlSessionStore) GetSessions(userId string) StoreChannel {
go func() {
if cur := <-me.CleanUpExpiredSessions(userId); cur.Err != nil {
- l4g.Error("Failed to cleanup sessions in getSessions err=%v", cur.Err)
+ l4g.Error(utils.T("store.sql_session.get_sessions.error"), cur.Err)
}
result := StoreResult{}
@@ -109,7 +110,7 @@ func (me SqlSessionStore) GetSessions(userId string) StoreChannel {
var sessions []*model.Session
if _, err := me.GetReplica().Select(&sessions, "SELECT * FROM Sessions WHERE UserId = :UserId ORDER BY LastActivityAt DESC", map[string]interface{}{"UserId": userId}); err != nil {
- result.Err = model.NewAppError("SqlSessionStore.GetSessions", "We encountered an error while finding user sessions", err.Error())
+ result.Err = model.NewLocAppError("SqlSessionStore.GetSessions", "store.sql_session.get_sessions.app_error", nil, err.Error())
} else {
result.Data = sessions
@@ -130,7 +131,7 @@ func (me SqlSessionStore) Remove(sessionIdOrToken string) StoreChannel {
_, err := me.GetMaster().Exec("DELETE FROM Sessions WHERE Id = :Id Or Token = :Token", map[string]interface{}{"Id": sessionIdOrToken, "Token": sessionIdOrToken})
if err != nil {
- result.Err = model.NewAppError("SqlSessionStore.RemoveSession", "We couldn't remove the session", "id="+sessionIdOrToken+", err="+err.Error())
+ result.Err = model.NewLocAppError("SqlSessionStore.RemoveSession", "store.sql_session.remove.app_error", nil, "id="+sessionIdOrToken+", err="+err.Error())
}
storeChannel <- result
@@ -148,7 +149,7 @@ func (me SqlSessionStore) RemoveAllSessionsForTeam(teamId string) StoreChannel {
_, err := me.GetMaster().Exec("DELETE FROM Sessions WHERE TeamId = :TeamId", map[string]interface{}{"TeamId": teamId})
if err != nil {
- result.Err = model.NewAppError("SqlSessionStore.RemoveAllSessionsForTeam", "We couldn't remove all the sessions for the team", "id="+teamId+", err="+err.Error())
+ result.Err = model.NewLocAppError("SqlSessionStore.RemoveAllSessionsForTeam", "store.sql_session.remove_all_sessions_for_team.app_error", nil, "id="+teamId+", err="+err.Error())
}
storeChannel <- result
@@ -166,7 +167,7 @@ func (me SqlSessionStore) PermanentDeleteSessionsByUser(userId string) StoreChan
_, err := me.GetMaster().Exec("DELETE FROM Sessions WHERE UserId = :UserId", map[string]interface{}{"UserId": userId})
if err != nil {
- result.Err = model.NewAppError("SqlSessionStore.RemoveAllSessionsForUser", "We couldn't remove all the sessions for the user", "id="+userId+", err="+err.Error())
+ result.Err = model.NewLocAppError("SqlSessionStore.RemoveAllSessionsForUser", "store.sql_session.permanent_delete_sessions_by_user.app_error", nil, "id="+userId+", err="+err.Error())
}
storeChannel <- result
@@ -183,7 +184,7 @@ func (me SqlSessionStore) CleanUpExpiredSessions(userId string) StoreChannel {
result := StoreResult{}
if _, err := me.GetMaster().Exec("DELETE FROM Sessions WHERE UserId = :UserId AND ExpiresAt != 0 AND :ExpiresAt > ExpiresAt", map[string]interface{}{"UserId": userId, "ExpiresAt": model.GetMillis()}); err != nil {
- result.Err = model.NewAppError("SqlSessionStore.CleanUpExpiredSessions", "We encountered an error while deleting expired user sessions", err.Error())
+ result.Err = model.NewLocAppError("SqlSessionStore.CleanUpExpiredSessions", "store.sql_session.cleanup_expired_sessions.app_error", nil, err.Error())
} else {
result.Data = userId
}
@@ -202,7 +203,7 @@ func (me SqlSessionStore) UpdateLastActivityAt(sessionId string, time int64) Sto
result := StoreResult{}
if _, err := me.GetMaster().Exec("UPDATE Sessions SET LastActivityAt = :LastActivityAt WHERE Id = :Id", map[string]interface{}{"LastActivityAt": time, "Id": sessionId}); err != nil {
- result.Err = model.NewAppError("SqlSessionStore.UpdateLastActivityAt", "We couldn't update the last_activity_at", "sessionId="+sessionId)
+ result.Err = model.NewLocAppError("SqlSessionStore.UpdateLastActivityAt", "store.sql_session.update_last_activity.app_error", nil, "sessionId="+sessionId)
} else {
result.Data = sessionId
}
@@ -220,7 +221,7 @@ func (me SqlSessionStore) UpdateRoles(userId, roles string) StoreChannel {
go func() {
result := StoreResult{}
if _, err := me.GetMaster().Exec("UPDATE Sessions SET Roles = :Roles WHERE UserId = :UserId", map[string]interface{}{"Roles": roles, "UserId": userId}); err != nil {
- result.Err = model.NewAppError("SqlSessionStore.UpdateRoles", "We couldn't update the roles", "userId="+userId)
+ result.Err = model.NewLocAppError("SqlSessionStore.UpdateRoles", "store.sql_session.update_roles.app_error", nil, "userId="+userId)
} else {
result.Data = userId
}
@@ -231,3 +232,21 @@ func (me SqlSessionStore) UpdateRoles(userId, roles string) StoreChannel {
return storeChannel
}
+
+func (me SqlSessionStore) UpdateDeviceId(id, deviceId string) StoreChannel {
+ storeChannel := make(StoreChannel)
+
+ go func() {
+ result := StoreResult{}
+ if _, err := me.GetMaster().Exec("UPDATE Sessions SET DeviceId = :DeviceId WHERE Id = :Id", map[string]interface{}{"DeviceId": deviceId, "Id": id}); err != nil {
+ result.Err = model.NewLocAppError("SqlSessionStore.UpdateDeviceId", "store.sql_session.update_device_id.app_error", nil, "")
+ } else {
+ result.Data = deviceId
+ }
+
+ storeChannel <- result
+ close(storeChannel)
+ }()
+
+ return storeChannel
+}
diff --git a/store/sql_session_store_test.go b/store/sql_session_store_test.go
index cec8e93b0..34d3128a6 100644
--- a/store/sql_session_store_test.go
+++ b/store/sql_session_store_test.go
@@ -157,6 +157,28 @@ func TestSessionRemoveToken(t *testing.T) {
}
}
+func TestSessionUpdateDeviceId(t *testing.T) {
+ Setup()
+
+ s1 := model.Session{}
+ s1.UserId = model.NewId()
+ s1.TeamId = model.NewId()
+ Must(store.Session().Save(&s1))
+
+ if rs1 := (<-store.Session().UpdateDeviceId(s1.Id, model.PUSH_NOTIFY_APPLE+":1234567890")); rs1.Err != nil {
+ t.Fatal(rs1.Err)
+ }
+
+ s2 := model.Session{}
+ s2.UserId = model.NewId()
+ s2.TeamId = model.NewId()
+ Must(store.Session().Save(&s2))
+
+ if rs2 := (<-store.Session().UpdateDeviceId(s2.Id, model.PUSH_NOTIFY_APPLE+":1234567890")); rs2.Err != nil {
+ t.Fatal(rs2.Err)
+ }
+}
+
func TestSessionStoreUpdateLastActivityAt(t *testing.T) {
Setup()
diff --git a/store/sql_store.go b/store/sql_store.go
index 5ed715c2c..8517eb1a2 100644
--- a/store/sql_store.go
+++ b/store/sql_store.go
@@ -74,43 +74,24 @@ func NewSqlStore() Store {
}
schemaVersion := sqlStore.GetCurrentSchemaVersion()
- isSchemaVersion07 := false // REMOVE AFTER 1.2 SHIP see PLT-828
- isSchemaVersion10 := false // REMOVE AFTER 1.2 SHIP see PLT-828
// If the version is already set then we are potentially in an 'upgrade needed' state
if schemaVersion != "" {
// Check to see if it's the most current database schema version
if !model.IsCurrentVersion(schemaVersion) {
// If we are upgrading from the previous version then print a warning and continue
-
- // Special case
- if schemaVersion == "0.7.1" || schemaVersion == "0.7.0" {
- isSchemaVersion07 = true
- }
-
- if schemaVersion == "1.0.0" {
- isSchemaVersion10 = true
- }
-
- if model.IsPreviousVersion(schemaVersion) || isSchemaVersion07 || isSchemaVersion10 {
- l4g.Warn("The database schema version of " + schemaVersion + " appears to be out of date")
- l4g.Warn("Attempting to upgrade the database schema version to " + model.CurrentVersion)
+ if model.IsPreviousVersionsSupported(schemaVersion) {
+ l4g.Warn(utils.T("store.sql.schema_out_of_date.warn"), schemaVersion)
+ l4g.Warn(utils.T("store.sql.schema_upgrade_attempt.warn"), model.CurrentVersion)
} else {
// If this is an 'upgrade needed' state but the user is attempting to skip a version then halt the world
- l4g.Critical("The database schema version of " + schemaVersion + " cannot be upgraded. You must not skip a version.")
+ l4g.Critical(utils.T("store.sql.schema_version.critical"), schemaVersion)
time.Sleep(time.Second)
- panic("The database schema version of " + schemaVersion + " cannot be upgraded. You must not skip a version.")
+ panic(fmt.Sprintf(utils.T("store.sql.schema_version.critical"), schemaVersion))
}
}
}
- // REMOVE AFTER 1.2 SHIP see PLT-828
- if sqlStore.DoesTableExist("Sessions") {
- if sqlStore.DoesColumnExist("Sessions", "AltId") {
- sqlStore.GetMaster().Exec("DROP TABLE IF EXISTS Sessions")
- }
- }
-
sqlStore.team = NewSqlTeamStore(sqlStore)
sqlStore.channel = NewSqlChannelStore(sqlStore)
sqlStore.post = NewSqlPostStore(sqlStore)
@@ -125,7 +106,7 @@ func NewSqlStore() Store {
err := sqlStore.master.CreateTablesIfNotExists()
if err != nil {
- l4g.Critical("Error creating database tables: %v", err)
+ l4g.Critical(utils.T("store.sql.creating_tables.critical"), err)
}
sqlStore.team.(*SqlTeamStore).UpgradeSchemaIfNeeded()
@@ -154,14 +135,14 @@ func NewSqlStore() Store {
sqlStore.preference.(*SqlPreferenceStore).DeleteUnusedFeatures()
- if model.IsPreviousVersion(schemaVersion) || isSchemaVersion07 || isSchemaVersion10 {
+ if model.IsPreviousVersionsSupported(schemaVersion) {
sqlStore.system.Update(&model.System{Name: "Version", Value: model.CurrentVersion})
- l4g.Warn("The database schema has been upgraded to version " + model.CurrentVersion)
+ l4g.Warn(utils.T("store.sql.upgraded.warn"), model.CurrentVersion)
}
if schemaVersion == "" {
sqlStore.system.Save(&model.System{Name: "Version", Value: model.CurrentVersion})
- l4g.Info("The database schema has been set to version " + model.CurrentVersion)
+ l4g.Info(utils.T("store.sql.schema_set.info"), model.CurrentVersion)
}
return sqlStore
@@ -171,17 +152,17 @@ func setupConnection(con_type string, driver string, dataSource string, maxIdle
db, err := dbsql.Open(driver, dataSource)
if err != nil {
- l4g.Critical("Failed to open sql connection to err:%v", err)
+ l4g.Critical(utils.T("store.sql.open_conn.critical"), err)
time.Sleep(time.Second)
- panic("Failed to open sql connection" + err.Error())
+ panic(fmt.Sprintf(utils.T("store.sql.open_conn.critical"), err.Error()))
}
- l4g.Info("Pinging sql %v database", con_type)
+ l4g.Info(utils.T("store.sql.pinging.info"), con_type)
err = db.Ping()
if err != nil {
- l4g.Critical("Failed to ping db err:%v", err)
+ l4g.Critical(utils.T("store.sql.ping.critical"), err)
time.Sleep(time.Second)
- panic("Failed to open sql connection " + err.Error())
+ panic(fmt.Sprintf(utils.T("store.sql.open_conn.panic"), err.Error()))
}
db.SetMaxIdleConns(maxIdle)
@@ -196,9 +177,9 @@ func setupConnection(con_type string, driver string, dataSource string, maxIdle
} else if driver == model.DATABASE_DRIVER_POSTGRES {
dbmap = &gorp.DbMap{Db: db, TypeConverter: mattermConverter{}, Dialect: gorp.PostgresDialect{}}
} else {
- l4g.Critical("Failed to create dialect specific driver")
+ l4g.Critical(utils.T("store.sql.dialect_driver.critical"))
time.Sleep(time.Second)
- panic("Failed to create dialect specific driver " + err.Error())
+ panic(fmt.Sprintf(utils.T("store.sql.dialect_driver.panic"), err.Error()))
}
if trace {
@@ -232,9 +213,9 @@ func (ss SqlStore) DoesTableExist(tableName string) bool {
)
if err != nil {
- l4g.Critical("Failed to check if table exists %v", err)
+ l4g.Critical(utils.T("store.sql.table_exists.critical"), err)
time.Sleep(time.Second)
- panic("Failed to check if table exists " + err.Error())
+ panic(fmt.Sprintf(utils.T("store.sql.table_exists.critical"), err.Error()))
}
return count > 0
@@ -254,17 +235,17 @@ func (ss SqlStore) DoesTableExist(tableName string) bool {
)
if err != nil {
- l4g.Critical("Failed to check if table exists %v", err)
+ l4g.Critical(utils.T("store.sql.table_exists.critical"), err)
time.Sleep(time.Second)
- panic("Failed to check if table exists " + err.Error())
+ panic(fmt.Sprintf(utils.T("store.sql.table_exists.critical"), err.Error()))
}
return count > 0
} else {
- l4g.Critical("Failed to check if column exists because of missing driver")
+ l4g.Critical(utils.T("store.sql.column_exists_missing_driver.critical"))
time.Sleep(time.Second)
- panic("Failed to check if column exists because of missing driver")
+ panic(utils.T("store.sql.column_exists_missing_driver.critical"))
}
}
@@ -286,9 +267,9 @@ func (ss SqlStore) DoesColumnExist(tableName string, columnName string) bool {
return false
}
- l4g.Critical("Failed to check if column exists %v", err)
+ l4g.Critical(utils.T("store.sql.column_exists.critical"), err)
time.Sleep(time.Second)
- panic("Failed to check if column exists " + err.Error())
+ panic(fmt.Sprintf(utils.T("store.sql.column_exists.critical"), err.Error()))
}
return count > 0
@@ -309,17 +290,17 @@ func (ss SqlStore) DoesColumnExist(tableName string, columnName string) bool {
)
if err != nil {
- l4g.Critical("Failed to check if column exists %v", err)
+ l4g.Critical(utils.T("store.sql.column_exists.critical"), err)
time.Sleep(time.Second)
- panic("Failed to check if column exists " + err.Error())
+ panic(fmt.Sprintf(utils.T("store.sql.column_exists.critical"), err.Error()))
}
return count > 0
} else {
- l4g.Critical("Failed to check if column exists because of missing driver")
+ l4g.Critical(utils.T("store.sql.column_exists_missing_driver.critical"))
time.Sleep(time.Second)
- panic("Failed to check if column exists because of missing driver")
+ panic(utils.T("store.sql.column_exists_missing_driver.critical"))
}
}
@@ -333,9 +314,9 @@ func (ss SqlStore) CreateColumnIfNotExists(tableName string, columnName string,
if utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_POSTGRES {
_, err := ss.GetMaster().Exec("ALTER TABLE " + tableName + " ADD " + columnName + " " + postgresColType + " DEFAULT '" + defaultValue + "'")
if err != nil {
- l4g.Critical("Failed to create column %v", err)
+ l4g.Critical(utils.T("store.sql.create_column.critical"), err)
time.Sleep(time.Second)
- panic("Failed to create column " + err.Error())
+ panic(fmt.Sprintf(utils.T("store.sql.create_column.critical"), err.Error()))
}
return true
@@ -343,17 +324,17 @@ func (ss SqlStore) CreateColumnIfNotExists(tableName string, columnName string,
} else if utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_MYSQL {
_, err := ss.GetMaster().Exec("ALTER TABLE " + tableName + " ADD " + columnName + " " + mySqlColType + " DEFAULT '" + defaultValue + "'")
if err != nil {
- l4g.Critical("Failed to create column %v", err)
+ l4g.Critical(utils.T("store.sql.create_column.critical"), err)
time.Sleep(time.Second)
- panic("Failed to create column " + err.Error())
+ panic(fmt.Sprintf(utils.T("store.sql.create_column.critical"), err.Error()))
}
return true
} else {
- l4g.Critical("Failed to create column because of missing driver")
+ l4g.Critical(utils.T("store.sql.create_column_missing_driver.critical"))
time.Sleep(time.Second)
- panic("Failed to create column because of missing driver")
+ panic(utils.T("store.sql.create_column_missing_driver.critical"))
}
}
@@ -365,9 +346,9 @@ func (ss SqlStore) RemoveColumnIfExists(tableName string, columnName string) boo
_, err := ss.GetMaster().Exec("ALTER TABLE " + tableName + " DROP COLUMN " + columnName)
if err != nil {
- l4g.Critical("Failed to drop column %v", err)
+ l4g.Critical(utils.T("store.sql.drop_column.critical"), err)
time.Sleep(time.Second)
- panic("Failed to drop column " + err.Error())
+ panic(fmt.Sprintf(utils.T("store.sql.drop_column.critical"), err.Error()))
}
return true
@@ -386,9 +367,9 @@ func (ss SqlStore) RenameColumnIfExists(tableName string, oldColumnName string,
}
if err != nil {
- l4g.Critical("Failed to rename column %v", err)
+ l4g.Critical(utils.T("store.sql.rename_column.critical"), err)
time.Sleep(time.Second)
- panic("Failed to drop column " + err.Error())
+ panic(fmt.Sprintf(utils.T("store.sql.rename_column.critical"), err.Error()))
}
return true
@@ -420,17 +401,17 @@ func (ss SqlStore) createIndexIfNotExists(indexName string, tableName string, co
_, err = ss.GetMaster().Exec(query)
if err != nil {
- l4g.Critical("Failed to create index %v", err)
+ l4g.Critical(utils.T("store.sql.create_index.critical"), err)
time.Sleep(time.Second)
- panic("Failed to create index " + err.Error())
+ panic(fmt.Sprintf(utils.T("store.sql.create_index.critical"), err.Error()))
}
} else if utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_MYSQL {
count, err := ss.GetMaster().SelectInt("SELECT COUNT(0) AS index_exists FROM information_schema.statistics WHERE TABLE_SCHEMA = DATABASE() and table_name = ? AND index_name = ?", tableName, indexName)
if err != nil {
- l4g.Critical("Failed to check index %v", err)
+ l4g.Critical(utils.T("store.sql.check_index.critical"), err)
time.Sleep(time.Second)
- panic("Failed to check index " + err.Error())
+ panic(fmt.Sprintf(utils.T("store.sql.check_index.critical"), err.Error()))
}
if count > 0 {
@@ -444,14 +425,14 @@ func (ss SqlStore) createIndexIfNotExists(indexName string, tableName string, co
_, err = ss.GetMaster().Exec("CREATE " + fullTextIndex + " INDEX " + indexName + " ON " + tableName + " (" + columnName + ")")
if err != nil {
- l4g.Critical("Failed to create index %v", err)
+ l4g.Critical(utils.T("store.sql.create_index.critical"), err)
time.Sleep(time.Second)
- panic("Failed to create index " + err.Error())
+ panic(fmt.Sprintf(utils.T("store.sql.create_index.critical"), err.Error()))
}
} else {
- l4g.Critical("Failed to create index because of missing driver")
+ l4g.Critical(utils.T("store.sql.create_index_missing_driver.critical"))
time.Sleep(time.Second)
- panic("Failed to create index because of missing driver")
+ panic(utils.T("store.sql.create_index_missing_driver.critical"))
}
}
@@ -467,9 +448,9 @@ func (ss SqlStore) GetColumnDataType(tableName, columnName string) string {
"Columnname": columnName,
})
if err != nil {
- l4g.Critical("Failed to get data type for column %s from table %s: %v", columnName, tableName, err.Error())
+ l4g.Critical(utils.T("store.sql.table_column_type.critical"), columnName, tableName, err.Error())
time.Sleep(time.Second)
- panic("Failed to get get data type for column " + columnName + " from table " + tableName + ": " + err.Error())
+ panic(fmt.Sprintf(utils.T("store.sql.table_column_type.critical"), columnName, tableName, err.Error()))
}
return dataType
@@ -491,7 +472,7 @@ func (ss SqlStore) GetAllConns() []*gorp.DbMap {
}
func (ss SqlStore) Close() {
- l4g.Info("Closing SqlStore")
+ l4g.Info(utils.T("store.sql.closing.info"))
ss.master.Db.Close()
for _, replica := range ss.replicas {
replica.Db.Close()
@@ -566,7 +547,7 @@ func (me mattermConverter) FromDb(target interface{}) (gorp.CustomScanner, bool)
binder := func(holder, target interface{}) error {
s, ok := holder.(*string)
if !ok {
- return errors.New("FromDb: Unable to convert StringMap to *string")
+ return errors.New(utils.T("store.sql.convert_string_map"))
}
b := []byte(*s)
return json.Unmarshal(b, target)
@@ -576,7 +557,7 @@ func (me mattermConverter) FromDb(target interface{}) (gorp.CustomScanner, bool)
binder := func(holder, target interface{}) error {
s, ok := holder.(*string)
if !ok {
- return errors.New("FromDb: Unable to convert StringArray to *string")
+ return errors.New(utils.T("store.sql.convert_string_array"))
}
b := []byte(*s)
return json.Unmarshal(b, target)
@@ -586,7 +567,7 @@ func (me mattermConverter) FromDb(target interface{}) (gorp.CustomScanner, bool)
binder := func(holder, target interface{}) error {
s, ok := holder.(*string)
if !ok {
- return errors.New("FromDb: Unable to convert EncryptStringMap to *string")
+ return errors.New(utils.T("store.sql.convert_encrypt_string_map"))
}
ue, err := decrypt([]byte(utils.Cfg.SqlSettings.AtRestEncryptKey), *s)
@@ -602,7 +583,7 @@ func (me mattermConverter) FromDb(target interface{}) (gorp.CustomScanner, bool)
binder := func(holder, target interface{}) error {
s, ok := holder.(*string)
if !ok {
- return errors.New("FromDb: Unable to convert StringInterface to *string")
+ return errors.New(utils.T("store.sql.convert_string_interface"))
}
b := []byte(*s)
return json.Unmarshal(b, target)
@@ -659,14 +640,14 @@ func decrypt(key []byte, cryptoText string) (string, error) {
ekey, akey := skey[:32], skey[32:]
macfn := hmac.New(sha256.New, akey)
if len(ciphertext) < aes.BlockSize+macfn.Size() {
- return "", errors.New("short ciphertext")
+ return "", errors.New(utils.T("store.sql.short_ciphertext"))
}
macfn.Write(ciphertext[aes.BlockSize+macfn.Size():])
expectedMac := macfn.Sum(nil)
mac := ciphertext[aes.BlockSize : aes.BlockSize+macfn.Size()]
if hmac.Equal(expectedMac, mac) != true {
- return "", errors.New("Incorrect MAC for the given ciphertext")
+ return "", errors.New(utils.T("store.sql.incorrect_mac"))
}
block, err := aes.NewCipher(ekey)
@@ -675,7 +656,7 @@ func decrypt(key []byte, cryptoText string) (string, error) {
}
if len(ciphertext) < aes.BlockSize {
- return "", errors.New("ciphertext too short")
+ return "", errors.New(utils.T("store.sql.too_short_ciphertext"))
}
iv := ciphertext[:aes.BlockSize]
ciphertext = ciphertext[aes.BlockSize+macfn.Size():]
diff --git a/store/sql_store_test.go b/store/sql_store_test.go
index 1e04b676c..1be87dec9 100644
--- a/store/sql_store_test.go
+++ b/store/sql_store_test.go
@@ -16,6 +16,7 @@ var store Store
func Setup() {
if store == nil {
utils.LoadConfig("config.json")
+ utils.InitTranslations()
store = NewSqlStore()
store.MarkSystemRanUnitTests()
diff --git a/store/sql_system_store.go b/store/sql_system_store.go
index 1fbdfb333..cfd4a670f 100644
--- a/store/sql_system_store.go
+++ b/store/sql_system_store.go
@@ -37,7 +37,7 @@ func (s SqlSystemStore) Save(system *model.System) StoreChannel {
result := StoreResult{}
if err := s.GetMaster().Insert(system); err != nil {
- result.Err = model.NewAppError("SqlSystemStore.Save", "We encountered an error saving the system property", "")
+ result.Err = model.NewLocAppError("SqlSystemStore.Save", "store.sql_system.save.app_error", nil, "")
}
storeChannel <- result
@@ -55,7 +55,7 @@ func (s SqlSystemStore) Update(system *model.System) StoreChannel {
result := StoreResult{}
if _, err := s.GetMaster().Update(system); err != nil {
- result.Err = model.NewAppError("SqlSystemStore.Save", "We encountered an error updating the system property", "")
+ result.Err = model.NewLocAppError("SqlSystemStore.Update", "store.sql_system.update.app_error", nil, "")
}
storeChannel <- result
@@ -75,7 +75,7 @@ func (s SqlSystemStore) Get() StoreChannel {
var systems []model.System
props := make(model.StringMap)
if _, err := s.GetReplica().Select(&systems, "SELECT * FROM Systems"); err != nil {
- result.Err = model.NewAppError("SqlSystemStore.Get", "We encountered an error finding the system properties", "")
+ result.Err = model.NewLocAppError("SqlSystemStore.Get", "store.sql_system.get.app_error", nil, "")
} else {
for _, prop := range systems {
props[prop.Name] = prop.Value
diff --git a/store/sql_team_store.go b/store/sql_team_store.go
index 9578549ca..86ab9ac04 100644
--- a/store/sql_team_store.go
+++ b/store/sql_team_store.go
@@ -44,8 +44,8 @@ func (s SqlTeamStore) Save(team *model.Team) StoreChannel {
result := StoreResult{}
if len(team.Id) > 0 {
- result.Err = model.NewAppError("SqlTeamStore.Save",
- "Must call update for exisiting team", "id="+team.Id)
+ result.Err = model.NewLocAppError("SqlTeamStore.Save",
+ "store.sql_team.save.existing.app_error", nil, "id="+team.Id)
storeChannel <- result
close(storeChannel)
return
@@ -61,9 +61,9 @@ func (s SqlTeamStore) Save(team *model.Team) StoreChannel {
if err := s.GetMaster().Insert(team); err != nil {
if IsUniqueConstraintError(err.Error(), "Name", "teams_name_key") {
- result.Err = model.NewAppError("SqlTeamStore.Save", "A team with that domain already exists", "id="+team.Id+", "+err.Error())
+ result.Err = model.NewLocAppError("SqlTeamStore.Save", "store.sql_team.save.domain_exists.app_error", nil, "id="+team.Id+", "+err.Error())
} else {
- result.Err = model.NewAppError("SqlTeamStore.Save", "We couldn't save the team", "id="+team.Id+", "+err.Error())
+ result.Err = model.NewLocAppError("SqlTeamStore.Save", "store.sql_team.save.app_error", nil, "id="+team.Id+", "+err.Error())
}
} else {
result.Data = team
@@ -92,9 +92,9 @@ func (s SqlTeamStore) Update(team *model.Team) StoreChannel {
}
if oldResult, err := s.GetMaster().Get(model.Team{}, team.Id); err != nil {
- result.Err = model.NewAppError("SqlTeamStore.Update", "We encountered an error finding the team", "id="+team.Id+", "+err.Error())
+ result.Err = model.NewLocAppError("SqlTeamStore.Update", "store.sql_team.update.finding.app_error", nil, "id="+team.Id+", "+err.Error())
} else if oldResult == nil {
- result.Err = model.NewAppError("SqlTeamStore.Update", "We couldn't find the existing team to update", "id="+team.Id)
+ result.Err = model.NewLocAppError("SqlTeamStore.Update", "store.sql_team.update.find.app_error", nil, "id="+team.Id)
} else {
oldTeam := oldResult.(*model.Team)
team.CreateAt = oldTeam.CreateAt
@@ -102,9 +102,9 @@ func (s SqlTeamStore) Update(team *model.Team) StoreChannel {
team.Name = oldTeam.Name
if count, err := s.GetMaster().Update(team); err != nil {
- result.Err = model.NewAppError("SqlTeamStore.Update", "We encountered an error updating the team", "id="+team.Id+", "+err.Error())
+ result.Err = model.NewLocAppError("SqlTeamStore.Update", "store.sql_team.update.updating.app_error", nil, "id="+team.Id+", "+err.Error())
} else if count != 1 {
- result.Err = model.NewAppError("SqlTeamStore.Update", "We couldn't update the team", "id="+team.Id)
+ result.Err = model.NewLocAppError("SqlTeamStore.Update", "store.sql_team.update.app_error", nil, "id="+team.Id)
} else {
result.Data = team
}
@@ -125,7 +125,7 @@ func (s SqlTeamStore) UpdateDisplayName(name string, teamId string) StoreChannel
result := StoreResult{}
if _, err := s.GetMaster().Exec("UPDATE Teams SET DisplayName = :Name WHERE Id = :Id", map[string]interface{}{"Name": name, "Id": teamId}); err != nil {
- result.Err = model.NewAppError("SqlTeamStore.UpdateName", "We couldn't update the team name", "team_id="+teamId)
+ result.Err = model.NewLocAppError("SqlTeamStore.UpdateName", "store.sql_team.update_display_name.app_error", nil, "team_id="+teamId)
} else {
result.Data = teamId
}
@@ -144,9 +144,9 @@ func (s SqlTeamStore) Get(id string) StoreChannel {
result := StoreResult{}
if obj, err := s.GetReplica().Get(model.Team{}, id); err != nil {
- result.Err = model.NewAppError("SqlTeamStore.Get", "We encountered an error finding the team", "id="+id+", "+err.Error())
+ result.Err = model.NewLocAppError("SqlTeamStore.Get", "store.sql_team.get.finding.app_error", nil, "id="+id+", "+err.Error())
} else if obj == nil {
- result.Err = model.NewAppError("SqlTeamStore.Get", "We couldn't find the existing team", "id="+id)
+ result.Err = model.NewLocAppError("SqlTeamStore.Get", "store.sql_team.get.find.app_error", nil, "id="+id)
} else {
team := obj.(*model.Team)
if len(team.InviteId) == 0 {
@@ -172,7 +172,7 @@ func (s SqlTeamStore) GetByInviteId(inviteId string) StoreChannel {
team := model.Team{}
if err := s.GetReplica().SelectOne(&team, "SELECT * FROM Teams WHERE Id = :InviteId OR InviteId = :InviteId", map[string]interface{}{"InviteId": inviteId}); err != nil {
- result.Err = model.NewAppError("SqlTeamStore.GetByInviteId", "We couldn't find the existing team", "inviteId="+inviteId+", "+err.Error())
+ result.Err = model.NewLocAppError("SqlTeamStore.GetByInviteId", "store.sql_team.get_by_invite_id.finding.app_error", nil, "inviteId="+inviteId+", "+err.Error())
}
if len(team.InviteId) == 0 {
@@ -180,7 +180,7 @@ func (s SqlTeamStore) GetByInviteId(inviteId string) StoreChannel {
}
if len(inviteId) == 0 || team.InviteId != inviteId {
- result.Err = model.NewAppError("SqlTeamStore.GetByInviteId", "We couldn't find the existing team", "inviteId="+inviteId)
+ result.Err = model.NewLocAppError("SqlTeamStore.GetByInviteId", "store.sql_team.get_by_invite_id.find.app_error", nil, "inviteId="+inviteId)
}
result.Data = &team
@@ -201,7 +201,7 @@ func (s SqlTeamStore) GetByName(name string) StoreChannel {
team := model.Team{}
if err := s.GetReplica().SelectOne(&team, "SELECT * FROM Teams WHERE Name = :Name", map[string]interface{}{"Name": name}); err != nil {
- result.Err = model.NewAppError("SqlTeamStore.GetByName", "We couldn't find the existing team", "name="+name+", "+err.Error())
+ result.Err = model.NewLocAppError("SqlTeamStore.GetByName", "store.sql_team.get_by_name.app_error", nil, "name="+name+", "+err.Error())
}
if len(team.InviteId) == 0 {
@@ -225,7 +225,7 @@ func (s SqlTeamStore) GetTeamsForEmail(email string) StoreChannel {
var data []*model.Team
if _, err := s.GetReplica().Select(&data, "SELECT Teams.* FROM Teams, Users WHERE Teams.Id = Users.TeamId AND Users.Email = :Email", map[string]interface{}{"Email": email}); err != nil {
- result.Err = model.NewAppError("SqlTeamStore.GetTeamsForEmail", "We encountered a problem when looking up teams", "email="+email+", "+err.Error())
+ result.Err = model.NewLocAppError("SqlTeamStore.GetTeamsForEmail", "store.sql_team.get_teams_for_email.app_error", nil, "email="+email+", "+err.Error())
}
for _, team := range data {
@@ -251,7 +251,7 @@ func (s SqlTeamStore) GetAll() StoreChannel {
var data []*model.Team
if _, err := s.GetReplica().Select(&data, "SELECT * FROM Teams"); err != nil {
- result.Err = model.NewAppError("SqlTeamStore.GetAllTeams", "We could not get all teams", err.Error())
+ result.Err = model.NewLocAppError("SqlTeamStore.GetAllTeams", "store.sql_team.get_all.app_error", nil, err.Error())
}
for _, team := range data {
@@ -283,7 +283,7 @@ func (s SqlTeamStore) GetAllTeamListing() StoreChannel {
var data []*model.Team
if _, err := s.GetReplica().Select(&data, query); err != nil {
- result.Err = model.NewAppError("SqlTeamStore.GetAllTeams", "We could not get all teams", err.Error())
+ result.Err = model.NewLocAppError("SqlTeamStore.GetAllTeams", "store.sql_team.get_all_team_listing.app_error", nil, err.Error())
}
for _, team := range data {
@@ -308,7 +308,7 @@ func (s SqlTeamStore) PermanentDelete(teamId string) StoreChannel {
result := StoreResult{}
if _, err := s.GetMaster().Exec("DELETE FROM Teams WHERE Id = :TeamId", map[string]interface{}{"TeamId": teamId}); err != nil {
- result.Err = model.NewAppError("SqlTeamStore.Delete", "We couldn't delete the existing team", "teamId="+teamId+", "+err.Error())
+ result.Err = model.NewLocAppError("SqlTeamStore.Delete", "store.sql_team.permanent_delete.app_error", nil, "teamId="+teamId+", "+err.Error())
}
storeChannel <- result
diff --git a/store/sql_user_store.go b/store/sql_user_store.go
index efd8b7f33..0b6970c96 100644
--- a/store/sql_user_store.go
+++ b/store/sql_user_store.go
@@ -11,7 +11,7 @@ import (
)
const (
- MISSING_ACCOUNT_ERROR = "We couldn't find an existing account matching your email address for this team. This team may require an invite from the team owner to join."
+ MISSING_ACCOUNT_ERROR = "store.sql_user.missing_account.const"
)
type SqlUserStore struct {
@@ -46,7 +46,8 @@ func NewSqlUserStore(sqlStore *SqlStore) UserStore {
}
func (us SqlUserStore) UpgradeSchemaIfNeeded() {
- us.CreateColumnIfNotExists("Users", "Locale", "varchar(5)", "character varying(5)", model.DEFAULT_LOCALE) // Added After 1.4
+ // ADDED for 1.5 REMOVE for 1.8
+ us.CreateColumnIfNotExists("Users", "Locale", "varchar(5)", "character varying(5)", model.DEFAULT_LOCALE)
}
func (us SqlUserStore) CreateIndexesIfNotExists() {
@@ -62,7 +63,7 @@ func (us SqlUserStore) Save(user *model.User) StoreChannel {
result := StoreResult{}
if len(user.Id) > 0 {
- result.Err = model.NewAppError("SqlUserStore.Save", "Must call update for exisiting user", "user_id="+user.Id)
+ result.Err = model.NewLocAppError("SqlUserStore.Save", "store.sql_user.save.existing.app_error", nil, "user_id="+user.Id)
storeChannel <- result
close(storeChannel)
return
@@ -76,12 +77,12 @@ func (us SqlUserStore) Save(user *model.User) StoreChannel {
}
if count, err := us.GetMaster().SelectInt("SELECT COUNT(0) FROM Users WHERE TeamId = :TeamId AND DeleteAt = 0", map[string]interface{}{"TeamId": user.TeamId}); err != nil {
- result.Err = model.NewAppError("SqlUserStore.Save", "Failed to get current team member count", "teamId="+user.TeamId+", "+err.Error())
+ result.Err = model.NewLocAppError("SqlUserStore.Save", "store.sql_user.save.member_count.app_error", nil, "teamId="+user.TeamId+", "+err.Error())
storeChannel <- result
close(storeChannel)
return
} else if int(count) > utils.Cfg.TeamSettings.MaxUsersPerTeam {
- result.Err = model.NewAppError("SqlUserStore.Save", "This team has reached the maxmium number of allowed accounts. Contact your systems administrator to set a higher limit.", "teamId="+user.TeamId)
+ result.Err = model.NewLocAppError("SqlUserStore.Save", "store.sql_user.save.max_accounts.app_error", nil, "teamId="+user.TeamId)
storeChannel <- result
close(storeChannel)
return
@@ -89,11 +90,11 @@ func (us SqlUserStore) Save(user *model.User) StoreChannel {
if err := us.GetMaster().Insert(user); err != nil {
if IsUniqueConstraintError(err.Error(), "Email", "users_email_teamid_key") {
- result.Err = model.NewAppError("SqlUserStore.Save", "An account with that email already exists.", "user_id="+user.Id+", "+err.Error())
+ result.Err = model.NewLocAppError("SqlUserStore.Save", "store.sql_user.save.email_exists.app_error", nil, "user_id="+user.Id+", "+err.Error())
} else if IsUniqueConstraintError(err.Error(), "Username", "users_username_teamid_key") {
- result.Err = model.NewAppError("SqlUserStore.Save", "An account with that username already exists.", "user_id="+user.Id+", "+err.Error())
+ result.Err = model.NewLocAppError("SqlUserStore.Save", "store.sql_user.save.username_exists.app_error", nil, "user_id="+user.Id+", "+err.Error())
} else {
- result.Err = model.NewAppError("SqlUserStore.Save", "We couldn't save the account.", "user_id="+user.Id+", "+err.Error())
+ result.Err = model.NewLocAppError("SqlUserStore.Save", "store.sql_user.save.app_error", nil, "user_id="+user.Id+", "+err.Error())
}
} else {
result.Data = user
@@ -122,9 +123,9 @@ func (us SqlUserStore) Update(user *model.User, allowActiveUpdate bool) StoreCha
}
if oldUserResult, err := us.GetMaster().Get(model.User{}, user.Id); err != nil {
- result.Err = model.NewAppError("SqlUserStore.Update", "We encountered an error finding the account", "user_id="+user.Id+", "+err.Error())
+ result.Err = model.NewLocAppError("SqlUserStore.Update", "store.sql_user.update.finding.app_error", nil, "user_id="+user.Id+", "+err.Error())
} else if oldUserResult == nil {
- result.Err = model.NewAppError("SqlUserStore.Update", "We couldn't find the existing account to update", "user_id="+user.Id)
+ result.Err = model.NewLocAppError("SqlUserStore.Update", "store.sql_user.update.find.app_error", nil, "user_id="+user.Id)
} else {
oldUser := oldUserResult.(*model.User)
user.CreateAt = oldUser.CreateAt
@@ -163,14 +164,14 @@ func (us SqlUserStore) Update(user *model.User, allowActiveUpdate bool) StoreCha
if count, err := us.GetMaster().Update(user); err != nil {
if IsUniqueConstraintError(err.Error(), "Email", "users_email_teamid_key") {
- result.Err = model.NewAppError("SqlUserStore.Update", "This email is already taken. Please choose another.", "user_id="+user.Id+", "+err.Error())
+ result.Err = model.NewLocAppError("SqlUserStore.Update", "store.sql_user.update.email_taken.app_error", nil, "user_id="+user.Id+", "+err.Error())
} else if IsUniqueConstraintError(err.Error(), "Username", "users_username_teamid_key") {
- result.Err = model.NewAppError("SqlUserStore.Update", "This username is already taken. Please choose another.", "user_id="+user.Id+", "+err.Error())
+ result.Err = model.NewLocAppError("SqlUserStore.Update", "store.sql_user.update.username_taken.app_error", nil, "user_id="+user.Id+", "+err.Error())
} else {
- result.Err = model.NewAppError("SqlUserStore.Update", "We encountered an error updating the account", "user_id="+user.Id+", "+err.Error())
+ result.Err = model.NewLocAppError("SqlUserStore.Update", "store.sql_user.update.updating.app_error", nil, "user_id="+user.Id+", "+err.Error())
}
} else if count != 1 {
- result.Err = model.NewAppError("SqlUserStore.Update", "We couldn't update the account", fmt.Sprintf("user_id=%v, count=%v", user.Id, count))
+ result.Err = model.NewLocAppError("SqlUserStore.Update", "store.sql_user.update.app_error", nil, fmt.Sprintf("user_id=%v, count=%v", user.Id, count))
} else {
result.Data = [2]*model.User{user, oldUser}
}
@@ -192,7 +193,7 @@ func (us SqlUserStore) UpdateLastPictureUpdate(userId string) StoreChannel {
curTime := model.GetMillis()
if _, err := us.GetMaster().Exec("UPDATE Users SET LastPictureUpdate = :Time, UpdateAt = :Time WHERE Id = :UserId", map[string]interface{}{"Time": curTime, "UserId": userId}); err != nil {
- result.Err = model.NewAppError("SqlUserStore.UpdateUpdateAt", "We couldn't update the update_at", "user_id="+userId)
+ result.Err = model.NewLocAppError("SqlUserStore.UpdateUpdateAt", "store.sql_user.update_last_picture_update.app_error", nil, "user_id="+userId)
} else {
result.Data = userId
}
@@ -211,7 +212,7 @@ func (us SqlUserStore) UpdateLastPingAt(userId string, time int64) StoreChannel
result := StoreResult{}
if _, err := us.GetMaster().Exec("UPDATE Users SET LastPingAt = :LastPingAt WHERE Id = :UserId", map[string]interface{}{"LastPingAt": time, "UserId": userId}); err != nil {
- result.Err = model.NewAppError("SqlUserStore.UpdateLastPingAt", "We couldn't update the last_ping_at", "user_id="+userId)
+ result.Err = model.NewLocAppError("SqlUserStore.UpdateLastPingAt", "store.sql_user.update_last_ping.app_error", nil, "user_id="+userId)
} else {
result.Data = userId
}
@@ -230,7 +231,7 @@ func (us SqlUserStore) UpdateLastActivityAt(userId string, time int64) StoreChan
result := StoreResult{}
if _, err := us.GetMaster().Exec("UPDATE Users SET LastActivityAt = :LastActivityAt WHERE Id = :UserId", map[string]interface{}{"LastActivityAt": time, "UserId": userId}); err != nil {
- result.Err = model.NewAppError("SqlUserStore.UpdateLastActivityAt", "We couldn't update the last_activity_at", "user_id="+userId)
+ result.Err = model.NewLocAppError("SqlUserStore.UpdateLastActivityAt", "store.sql_user.update_last_activity.app_error", nil, "user_id="+userId)
} else {
result.Data = userId
}
@@ -249,9 +250,9 @@ func (us SqlUserStore) UpdateUserAndSessionActivity(userId string, sessionId str
result := StoreResult{}
if _, err := us.GetMaster().Exec("UPDATE Users SET LastActivityAt = :UserLastActivityAt WHERE Id = :UserId", map[string]interface{}{"UserLastActivityAt": time, "UserId": userId}); err != nil {
- result.Err = model.NewAppError("SqlUserStore.UpdateLastActivityAt", "We couldn't update the last_activity_at", "1 user_id="+userId+" session_id="+sessionId+" err="+err.Error())
+ result.Err = model.NewLocAppError("SqlUserStore.UpdateLastActivityAt", "store.sql_user.update_last_activity.app_error", nil, "1 user_id="+userId+" session_id="+sessionId+" err="+err.Error())
} else if _, err := us.GetMaster().Exec("UPDATE Sessions SET LastActivityAt = :SessionLastActivityAt WHERE Id = :SessionId", map[string]interface{}{"SessionLastActivityAt": time, "SessionId": sessionId}); err != nil {
- result.Err = model.NewAppError("SqlUserStore.UpdateLastActivityAt", "We couldn't update the last_activity_at", "2 user_id="+userId+" session_id="+sessionId+" err="+err.Error())
+ result.Err = model.NewLocAppError("SqlUserStore.UpdateLastActivityAt", "store.sql_user.update_last_activity.app_error", nil, "2 user_id="+userId+" session_id="+sessionId+" err="+err.Error())
} else {
result.Data = userId
}
@@ -273,7 +274,7 @@ func (us SqlUserStore) UpdatePassword(userId, hashedPassword string) StoreChanne
updateAt := model.GetMillis()
if _, err := us.GetMaster().Exec("UPDATE Users SET Password = :Password, LastPasswordUpdate = :LastPasswordUpdate, UpdateAt = :UpdateAt, AuthData = '', AuthService = '', FailedAttempts = 0 WHERE Id = :UserId", map[string]interface{}{"Password": hashedPassword, "LastPasswordUpdate": updateAt, "UpdateAt": updateAt, "UserId": userId}); err != nil {
- result.Err = model.NewAppError("SqlUserStore.UpdatePassword", "We couldn't update the user password", "id="+userId+", "+err.Error())
+ result.Err = model.NewLocAppError("SqlUserStore.UpdatePassword", "store.sql_user.update_password.app_error", nil, "id="+userId+", "+err.Error())
} else {
result.Data = userId
}
@@ -292,7 +293,7 @@ func (us SqlUserStore) UpdateFailedPasswordAttempts(userId string, attempts int)
result := StoreResult{}
if _, err := us.GetMaster().Exec("UPDATE Users SET FailedAttempts = :FailedAttempts WHERE Id = :UserId", map[string]interface{}{"FailedAttempts": attempts, "UserId": userId}); err != nil {
- result.Err = model.NewAppError("SqlUserStore.UpdateFailedPasswordAttempts", "We couldn't update the failed_attempts", "user_id="+userId)
+ result.Err = model.NewLocAppError("SqlUserStore.UpdateFailedPasswordAttempts", "store.sql_user.update_failed_pwd_attempts.app_error", nil, "user_id="+userId)
} else {
result.Data = userId
}
@@ -314,7 +315,7 @@ func (us SqlUserStore) UpdateAuthData(userId, service, authData string) StoreCha
updateAt := model.GetMillis()
if _, err := us.GetMaster().Exec("UPDATE Users SET Password = '', LastPasswordUpdate = :LastPasswordUpdate, UpdateAt = :UpdateAt, FailedAttempts = 0, AuthService = :AuthService, AuthData = :AuthData WHERE Id = :UserId", map[string]interface{}{"LastPasswordUpdate": updateAt, "UpdateAt": updateAt, "UserId": userId, "AuthService": service, "AuthData": authData}); err != nil {
- result.Err = model.NewAppError("SqlUserStore.UpdateAuthData", "We couldn't update the auth data", "id="+userId+", "+err.Error())
+ result.Err = model.NewLocAppError("SqlUserStore.UpdateAuthData", "store.sql_user.update_auth_data.app_error", nil, "id="+userId+", "+err.Error())
} else {
result.Data = userId
}
@@ -334,9 +335,9 @@ func (us SqlUserStore) Get(id string) StoreChannel {
result := StoreResult{}
if obj, err := us.GetReplica().Get(model.User{}, id); err != nil {
- result.Err = model.NewAppError("SqlUserStore.Get", "We encountered an error finding the account", "user_id="+id+", "+err.Error())
+ result.Err = model.NewLocAppError("SqlUserStore.Get", "store.sql_user.get.app_error", nil, "user_id="+id+", "+err.Error())
} else if obj == nil {
- result.Err = model.NewAppError("SqlUserStore.Get", MISSING_ACCOUNT_ERROR, "user_id="+id)
+ result.Err = model.NewLocAppError("SqlUserStore.Get", MISSING_ACCOUNT_ERROR, nil, "user_id="+id)
} else {
result.Data = obj.(*model.User)
}
@@ -379,7 +380,7 @@ func (us SqlUserStore) GetProfiles(teamId string) StoreChannel {
var users []*model.User
if _, err := us.GetReplica().Select(&users, "SELECT * FROM Users WHERE TeamId = :TeamId", map[string]interface{}{"TeamId": teamId}); err != nil {
- result.Err = model.NewAppError("SqlUserStore.GetProfiles", "We encountered an error while finding user profiles", err.Error())
+ result.Err = model.NewLocAppError("SqlUserStore.GetProfiles", "store.sql_user.get_profiles.app_error", nil, err.Error())
} else {
userMap := make(map[string]*model.User)
@@ -410,7 +411,7 @@ func (us SqlUserStore) GetSystemAdminProfiles() StoreChannel {
var users []*model.User
if _, err := us.GetReplica().Select(&users, "SELECT * FROM Users WHERE Roles = :Roles", map[string]interface{}{"Roles": "system_admin"}); err != nil {
- result.Err = model.NewAppError("SqlUserStore.GetSystemAdminProfiles", "We encountered an error while finding user profiles", err.Error())
+ result.Err = model.NewLocAppError("SqlUserStore.GetSystemAdminProfiles", "store.sql_user.get_sysadmin_profiles.app_error", nil, err.Error())
} else {
userMap := make(map[string]*model.User)
@@ -441,7 +442,7 @@ func (us SqlUserStore) GetByEmail(teamId string, email string) StoreChannel {
user := model.User{}
if err := us.GetReplica().SelectOne(&user, "SELECT * FROM Users WHERE TeamId = :TeamId AND Email = :Email", map[string]interface{}{"TeamId": teamId, "Email": email}); err != nil {
- result.Err = model.NewAppError("SqlUserStore.GetByEmail", MISSING_ACCOUNT_ERROR, "teamId="+teamId+", email="+email+", "+err.Error())
+ result.Err = model.NewLocAppError("SqlUserStore.GetByEmail", MISSING_ACCOUNT_ERROR, nil, "teamId="+teamId+", email="+email+", "+err.Error())
}
result.Data = &user
@@ -463,7 +464,8 @@ func (us SqlUserStore) GetByAuth(teamId string, authData string, authService str
user := model.User{}
if err := us.GetReplica().SelectOne(&user, "SELECT * FROM Users WHERE TeamId = :TeamId AND AuthData = :AuthData AND AuthService = :AuthService", map[string]interface{}{"TeamId": teamId, "AuthData": authData, "AuthService": authService}); err != nil {
- result.Err = model.NewAppError("SqlUserStore.GetByAuth", "We couldn't find an existing account matching your authentication type for this team. This team may require an invite from the team owner to join.", "teamId="+teamId+", authData="+authData+", authService="+authService+", "+err.Error())
+ result.Err = model.NewLocAppError("SqlUserStore.GetByAuth", "store.sql_user.get_by_auth.app_error",
+ nil, "teamId="+teamId+", authData="+authData+", authService="+authService+", "+err.Error())
}
result.Data = &user
@@ -485,7 +487,8 @@ func (us SqlUserStore) GetByUsername(teamId string, username string) StoreChanne
user := model.User{}
if err := us.GetReplica().SelectOne(&user, "SELECT * FROM Users WHERE TeamId = :TeamId AND Username = :Username", map[string]interface{}{"TeamId": teamId, "Username": username}); err != nil {
- result.Err = model.NewAppError("SqlUserStore.GetByUsername", "We couldn't find an existing account matching your username for this team. This team may require an invite from the team owner to join.", "teamId="+teamId+", username="+username+", "+err.Error())
+ result.Err = model.NewLocAppError("SqlUserStore.GetByUsername", "store.sql_user.get_by_username.app_error",
+ nil, "teamId="+teamId+", username="+username+", "+err.Error())
}
result.Data = &user
@@ -504,7 +507,7 @@ func (us SqlUserStore) VerifyEmail(userId string) StoreChannel {
result := StoreResult{}
if _, err := us.GetMaster().Exec("UPDATE Users SET EmailVerified = '1' WHERE Id = :UserId", map[string]interface{}{"UserId": userId}); err != nil {
- result.Err = model.NewAppError("SqlUserStore.VerifyEmail", "Unable to update verify email field", "userId="+userId+", "+err.Error())
+ result.Err = model.NewLocAppError("SqlUserStore.VerifyEmail", "store.sql_user.verify_email.app_error", nil, "userId="+userId+", "+err.Error())
}
result.Data = userId
@@ -526,7 +529,7 @@ func (us SqlUserStore) GetForExport(teamId string) StoreChannel {
var users []*model.User
if _, err := us.GetReplica().Select(&users, "SELECT * FROM Users WHERE TeamId = :TeamId", map[string]interface{}{"TeamId": teamId}); err != nil {
- result.Err = model.NewAppError("SqlUserStore.GetProfiles", "We encountered an error while finding user profiles", err.Error())
+ result.Err = model.NewLocAppError("SqlUserStore.GetProfiles", "store.sql_user.get_for_export.app_error", nil, err.Error())
} else {
for _, u := range users {
u.Password = ""
@@ -550,7 +553,7 @@ func (us SqlUserStore) GetTotalUsersCount() StoreChannel {
result := StoreResult{}
if count, err := us.GetReplica().SelectInt("SELECT COUNT(Id) FROM Users"); err != nil {
- result.Err = model.NewAppError("SqlUserStore.GetTotalUsersCount", "We could not count the users", err.Error())
+ result.Err = model.NewLocAppError("SqlUserStore.GetTotalUsersCount", "store.sql_user.get_total_users_count.app_error", nil, err.Error())
} else {
result.Data = count
}
@@ -571,7 +574,7 @@ func (us SqlUserStore) GetTotalActiveUsersCount() StoreChannel {
time := model.GetMillis() - (1000 * 60 * 60 * 24)
if count, err := us.GetReplica().SelectInt("SELECT COUNT(Id) FROM Users WHERE LastActivityAt > :Time", map[string]interface{}{"Time": time}); err != nil {
- result.Err = model.NewAppError("SqlUserStore.GetTotalActiveUsersCount", "We could not count the users", err.Error())
+ result.Err = model.NewLocAppError("SqlUserStore.GetTotalActiveUsersCount", "store.sql_user.get_total_active_users_count.app_error", nil, err.Error())
} else {
result.Data = count
}
@@ -591,7 +594,7 @@ func (us SqlUserStore) PermanentDelete(userId string) StoreChannel {
result := StoreResult{}
if _, err := us.GetMaster().Exec("DELETE FROM Users WHERE Id = :UserId", map[string]interface{}{"UserId": userId}); err != nil {
- result.Err = model.NewAppError("SqlUserStore.GetByEmail", "We couldn't delete the existing account", "userId="+userId+", "+err.Error())
+ result.Err = model.NewLocAppError("SqlUserStore.GetByEmail", "store.sql_user.permanent_delete.app_error", nil, "userId="+userId+", "+err.Error())
}
storeChannel <- result
@@ -616,7 +619,7 @@ func (us SqlUserStore) AnalyticsUniqueUserCount(teamId string) StoreChannel {
v, err := us.GetReplica().SelectInt(query, map[string]interface{}{"TeamId": teamId})
if err != nil {
- result.Err = model.NewAppError("SqlUserStore.AnalyticsUniqueUserCount", "We couldn't get the unique user count", err.Error())
+ result.Err = model.NewLocAppError("SqlUserStore.AnalyticsUniqueUserCount", "store.sql_user.analytics_unique_user_count.app_error", nil, err.Error())
} else {
result.Data = v
}
diff --git a/store/sql_webhook_store.go b/store/sql_webhook_store.go
index c65384ec1..939574b9f 100644
--- a/store/sql_webhook_store.go
+++ b/store/sql_webhook_store.go
@@ -50,8 +50,8 @@ func (s SqlWebhookStore) SaveIncoming(webhook *model.IncomingWebhook) StoreChann
result := StoreResult{}
if len(webhook.Id) > 0 {
- result.Err = model.NewAppError("SqlWebhookStore.SaveIncoming",
- "You cannot overwrite an existing IncomingWebhook", "id="+webhook.Id)
+ result.Err = model.NewLocAppError("SqlWebhookStore.SaveIncoming",
+ "store.sql_webhooks.save_incoming.existing.app_error", nil, "id="+webhook.Id)
storeChannel <- result
close(storeChannel)
return
@@ -65,7 +65,7 @@ func (s SqlWebhookStore) SaveIncoming(webhook *model.IncomingWebhook) StoreChann
}
if err := s.GetMaster().Insert(webhook); err != nil {
- result.Err = model.NewAppError("SqlWebhookStore.SaveIncoming", "We couldn't save the IncomingWebhook", "id="+webhook.Id+", "+err.Error())
+ result.Err = model.NewLocAppError("SqlWebhookStore.SaveIncoming", "store.sql_webhooks.save_incoming.app_error", nil, "id="+webhook.Id+", "+err.Error())
} else {
result.Data = webhook
}
@@ -86,7 +86,7 @@ func (s SqlWebhookStore) GetIncoming(id string) StoreChannel {
var webhook model.IncomingWebhook
if err := s.GetReplica().SelectOne(&webhook, "SELECT * FROM IncomingWebhooks WHERE Id = :Id AND DeleteAt = 0", map[string]interface{}{"Id": id}); err != nil {
- result.Err = model.NewAppError("SqlWebhookStore.GetIncoming", "We couldn't get the webhook", "id="+id+", err="+err.Error())
+ result.Err = model.NewLocAppError("SqlWebhookStore.GetIncoming", "store.sql_webhooks.get_incoming.app_error", nil, "id="+id+", err="+err.Error())
}
result.Data = &webhook
@@ -106,7 +106,7 @@ func (s SqlWebhookStore) DeleteIncoming(webhookId string, time int64) StoreChann
_, err := s.GetMaster().Exec("Update IncomingWebhooks SET DeleteAt = :DeleteAt, UpdateAt = :UpdateAt WHERE Id = :Id", map[string]interface{}{"DeleteAt": time, "UpdateAt": time, "Id": webhookId})
if err != nil {
- result.Err = model.NewAppError("SqlWebhookStore.DeleteIncoming", "We couldn't delete the webhook", "id="+webhookId+", err="+err.Error())
+ result.Err = model.NewLocAppError("SqlWebhookStore.DeleteIncoming", "store.sql_webhooks.delete_incoming.app_error", nil, "id="+webhookId+", err="+err.Error())
}
storeChannel <- result
@@ -124,7 +124,7 @@ func (s SqlWebhookStore) PermanentDeleteIncomingByUser(userId string) StoreChann
_, err := s.GetMaster().Exec("DELETE FROM IncomingWebhooks WHERE UserId = :UserId", map[string]interface{}{"UserId": userId})
if err != nil {
- result.Err = model.NewAppError("SqlWebhookStore.DeleteIncomingByUser", "We couldn't delete the webhook", "id="+userId+", err="+err.Error())
+ result.Err = model.NewLocAppError("SqlWebhookStore.DeleteIncomingByUser", "store.sql_webhooks.permanent_delete_incoming_by_user.app_error", nil, "id="+userId+", err="+err.Error())
}
storeChannel <- result
@@ -143,7 +143,7 @@ func (s SqlWebhookStore) GetIncomingByTeam(teamId string) StoreChannel {
var webhooks []*model.IncomingWebhook
if _, err := s.GetReplica().Select(&webhooks, "SELECT * FROM IncomingWebhooks WHERE TeamId = :TeamId AND DeleteAt = 0", map[string]interface{}{"TeamId": teamId}); err != nil {
- result.Err = model.NewAppError("SqlWebhookStore.GetIncomingByUser", "We couldn't get the webhook", "teamId="+teamId+", err="+err.Error())
+ result.Err = model.NewLocAppError("SqlWebhookStore.GetIncomingByUser", "store.sql_webhooks.get_incoming_by_user.app_error", nil, "teamId="+teamId+", err="+err.Error())
}
result.Data = webhooks
@@ -164,7 +164,7 @@ func (s SqlWebhookStore) GetIncomingByChannel(channelId string) StoreChannel {
var webhooks []*model.IncomingWebhook
if _, err := s.GetReplica().Select(&webhooks, "SELECT * FROM IncomingWebhooks WHERE ChannelId = :ChannelId AND DeleteAt = 0", map[string]interface{}{"ChannelId": channelId}); err != nil {
- result.Err = model.NewAppError("SqlWebhookStore.GetIncomingByChannel", "We couldn't get the webhooks", "channelId="+channelId+", err="+err.Error())
+ result.Err = model.NewLocAppError("SqlWebhookStore.GetIncomingByChannel", "store.sql_webhooks.get_incoming_by_channel.app_error", nil, "channelId="+channelId+", err="+err.Error())
}
result.Data = webhooks
@@ -183,8 +183,8 @@ func (s SqlWebhookStore) SaveOutgoing(webhook *model.OutgoingWebhook) StoreChann
result := StoreResult{}
if len(webhook.Id) > 0 {
- result.Err = model.NewAppError("SqlWebhookStore.SaveOutgoing",
- "You cannot overwrite an existing OutgoingWebhook", "id="+webhook.Id)
+ result.Err = model.NewLocAppError("SqlWebhookStore.SaveOutgoing",
+ "store.sql_webhooks.save_outgoing.override.app_error", nil, "id="+webhook.Id)
storeChannel <- result
close(storeChannel)
return
@@ -198,7 +198,7 @@ func (s SqlWebhookStore) SaveOutgoing(webhook *model.OutgoingWebhook) StoreChann
}
if err := s.GetMaster().Insert(webhook); err != nil {
- result.Err = model.NewAppError("SqlWebhookStore.SaveOutgoing", "We couldn't save the OutgoingWebhook", "id="+webhook.Id+", "+err.Error())
+ result.Err = model.NewLocAppError("SqlWebhookStore.SaveOutgoing", "store.sql_webhooks.save_outgoing.app_error", nil, "id="+webhook.Id+", "+err.Error())
} else {
result.Data = webhook
}
@@ -219,7 +219,7 @@ func (s SqlWebhookStore) GetOutgoing(id string) StoreChannel {
var webhook model.OutgoingWebhook
if err := s.GetReplica().SelectOne(&webhook, "SELECT * FROM OutgoingWebhooks WHERE Id = :Id AND DeleteAt = 0", map[string]interface{}{"Id": id}); err != nil {
- result.Err = model.NewAppError("SqlWebhookStore.GetOutgoing", "We couldn't get the webhook", "id="+id+", err="+err.Error())
+ result.Err = model.NewLocAppError("SqlWebhookStore.GetOutgoing", "store.sql_webhooks.get_outgoing.app_error", nil, "id="+id+", err="+err.Error())
}
result.Data = &webhook
@@ -240,7 +240,7 @@ func (s SqlWebhookStore) GetOutgoingByChannel(channelId string) StoreChannel {
var webhooks []*model.OutgoingWebhook
if _, err := s.GetReplica().Select(&webhooks, "SELECT * FROM OutgoingWebhooks WHERE ChannelId = :ChannelId AND DeleteAt = 0", map[string]interface{}{"ChannelId": channelId}); err != nil {
- result.Err = model.NewAppError("SqlWebhookStore.GetOutgoingByChannel", "We couldn't get the webhooks", "channelId="+channelId+", err="+err.Error())
+ result.Err = model.NewLocAppError("SqlWebhookStore.GetOutgoingByChannel", "store.sql_webhooks.get_outgoing_by_channel.app_error", nil, "channelId="+channelId+", err="+err.Error())
}
result.Data = webhooks
@@ -261,7 +261,7 @@ func (s SqlWebhookStore) GetOutgoingByTeam(teamId string) StoreChannel {
var webhooks []*model.OutgoingWebhook
if _, err := s.GetReplica().Select(&webhooks, "SELECT * FROM OutgoingWebhooks WHERE TeamId = :TeamId AND DeleteAt = 0", map[string]interface{}{"TeamId": teamId}); err != nil {
- result.Err = model.NewAppError("SqlWebhookStore.GetOutgoingByTeam", "We couldn't get the webhooks", "teamId="+teamId+", err="+err.Error())
+ result.Err = model.NewLocAppError("SqlWebhookStore.GetOutgoingByTeam", "store.sql_webhooks.get_outgoing_by_team.app_error", nil, "teamId="+teamId+", err="+err.Error())
}
result.Data = webhooks
@@ -281,7 +281,7 @@ func (s SqlWebhookStore) DeleteOutgoing(webhookId string, time int64) StoreChann
_, err := s.GetMaster().Exec("Update OutgoingWebhooks SET DeleteAt = :DeleteAt, UpdateAt = :UpdateAt WHERE Id = :Id", map[string]interface{}{"DeleteAt": time, "UpdateAt": time, "Id": webhookId})
if err != nil {
- result.Err = model.NewAppError("SqlWebhookStore.DeleteOutgoing", "We couldn't delete the webhook", "id="+webhookId+", err="+err.Error())
+ result.Err = model.NewLocAppError("SqlWebhookStore.DeleteOutgoing", "store.sql_webhooks.delete_outgoing.app_error", nil, "id="+webhookId+", err="+err.Error())
}
storeChannel <- result
@@ -299,7 +299,7 @@ func (s SqlWebhookStore) PermanentDeleteOutgoingByUser(userId string) StoreChann
_, err := s.GetMaster().Exec("DELETE FROM OutgoingWebhooks WHERE CreatorId = :UserId", map[string]interface{}{"UserId": userId})
if err != nil {
- result.Err = model.NewAppError("SqlWebhookStore.DeleteOutgoingByUser", "We couldn't delete the webhook", "id="+userId+", err="+err.Error())
+ result.Err = model.NewLocAppError("SqlWebhookStore.DeleteOutgoingByUser", "store.sql_webhooks.permanent_delete_outgoing_by_user.app_error", nil, "id="+userId+", err="+err.Error())
}
storeChannel <- result
@@ -318,7 +318,7 @@ func (s SqlWebhookStore) UpdateOutgoing(hook *model.OutgoingWebhook) StoreChanne
hook.UpdateAt = model.GetMillis()
if _, err := s.GetMaster().Update(hook); err != nil {
- result.Err = model.NewAppError("SqlWebhookStore.UpdateOutgoing", "We couldn't update the webhook", "id="+hook.Id+", "+err.Error())
+ result.Err = model.NewLocAppError("SqlWebhookStore.UpdateOutgoing", "store.sql_webhooks.update_outgoing.app_error", nil, "id="+hook.Id+", "+err.Error())
} else {
result.Data = hook
}
diff --git a/store/store.go b/store/store.go
index 8a362bc8f..a25d8b204 100644
--- a/store/store.go
+++ b/store/store.go
@@ -138,6 +138,7 @@ type SessionStore interface {
PermanentDeleteSessionsByUser(teamId string) StoreChannel
UpdateLastActivityAt(sessionId string, time int64) StoreChannel
UpdateRoles(userId string, roles string) StoreChannel
+ UpdateDeviceId(id string, deviceId string) StoreChannel
}
type AuditStore interface {
diff --git a/utils/i18n.go b/utils/i18n.go
index 05154bd92..e809ae883 100644
--- a/utils/i18n.go
+++ b/utils/i18n.go
@@ -44,7 +44,7 @@ func GetTranslationsBySystemLocale() i18n.TranslateFunc {
panic("Failed to load system translations for '" + model.DEFAULT_LOCALE + "'")
}
- translations, _ := i18n.Tfunc(locale)
+ translations := TfuncWithFallback(locale)
if translations == nil {
panic("Failed to load system translations")
}
@@ -58,22 +58,34 @@ func GetUserTranslations(locale string) i18n.TranslateFunc {
locale = model.DEFAULT_LOCALE
}
- translations, _ := i18n.Tfunc(locale)
+ translations := TfuncWithFallback(locale)
return translations
}
func SetTranslations(locale string) i18n.TranslateFunc {
- translations, _ := i18n.Tfunc(locale)
+ translations := TfuncWithFallback(locale)
return translations
}
func GetTranslationsAndLocale(w http.ResponseWriter, r *http.Request) (i18n.TranslateFunc, string) {
headerLocale := strings.Split(strings.Split(r.Header.Get("Accept-Language"), ",")[0], "-")[0]
if locales[headerLocale] != "" {
- translations, _ := i18n.Tfunc(headerLocale)
+ translations := TfuncWithFallback(headerLocale)
return translations, headerLocale
}
- translations, _ := i18n.Tfunc(model.DEFAULT_LOCALE)
+ translations := TfuncWithFallback(model.DEFAULT_LOCALE)
return translations, model.DEFAULT_LOCALE
}
+
+func TfuncWithFallback(pref string) i18n.TranslateFunc {
+ t, _ := i18n.Tfunc(pref)
+ return func(translationID string, args ...interface{}) string {
+ if translated := t(translationID, args...); translated != translationID {
+ return translated
+ }
+
+ t, _ := i18n.Tfunc("en")
+ return t(translationID, args...)
+ }
+}
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 6a880f0ee..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);
@@ -100,15 +102,33 @@ export default class ActivityLogModal extends React.Component {
if (currentSession.props.platform === 'Windows') {
devicePicture = 'fa fa-windows';
+ } else if (currentSession.device_id && currentSession.device_id.indexOf('apple:') === 0) {
+ devicePicture = 'fa fa-apple';
+ devicePlatform = (
+ <FormattedMessage
+ id='activity_log_modal.iphoneNativeApp'
+ defaultMessage='iPhone Native App'
+ />
+ );
+ } else if (currentSession.device_id && currentSession.device_id.indexOf('android:') === 0) {
+ 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.browser.indexOf('Mattermost/') === 0) {
- devicePicture = 'fa fa-apple';
- devicePlatform = 'iPhone';
} else if (currentSession.props.platform === 'Linux') {
if (currentSession.props.os.indexOf('Android') >= 0) {
- devicePlatform = 'Android';
+ devicePlatform = (
+ <FormattedMessage
+ id='activity_log_modal.android'
+ defaultMessage='Android'
+ />
+ );
devicePicture = 'fa fa-android';
} else {
devicePicture = 'fa fa-linux';
@@ -119,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 {
@@ -132,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>
);
}
@@ -145,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>
@@ -154,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>
@@ -175,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/admin_navbar_dropdown.jsx b/web/react/components/admin_console/admin_navbar_dropdown.jsx
index 783d45de6..dc0b3c4cb 100644
--- a/web/react/components/admin_console/admin_navbar_dropdown.jsx
+++ b/web/react/components/admin_console/admin_navbar_dropdown.jsx
@@ -7,6 +7,8 @@ import TeamStore from '../../stores/team_store.jsx';
import Constants from '../../utils/constants.jsx';
+import {FormattedMessage} from 'mm-intl';
+
function getStateFromStores() {
return {currentTeam: TeamStore.getCurrent()};
}
@@ -66,7 +68,13 @@ export default class AdminNavbarDropdown extends React.Component {
<a
href={Utils.getWindowLocationOrigin() + '/' + this.state.currentTeam.name}
>
- {'Switch to ' + this.state.currentTeam.display_name}
+ <FormattedMessage
+ id='admin.nav.switch'
+ defaultMessage='Switch to {display_name}'
+ values={{
+ display_name: this.state.currentTeam.display_name
+ }}
+ />
</a>
</li>
<li>
@@ -74,7 +82,10 @@ export default class AdminNavbarDropdown extends React.Component {
href='#'
onClick={this.handleLogoutClick}
>
- {'Logout'}
+ <FormattedMessage
+ id='admin.nav.logout'
+ defaultMessage='Logout'
+ />
</a>
</li>
<li className='divider'></li>
@@ -83,7 +94,10 @@ export default class AdminNavbarDropdown extends React.Component {
target='_blank'
href='/static/help/help.html'
>
- {'Help'}
+ <FormattedMessage
+ id='admin.nav.help'
+ defaultMessage='Help'
+ />
</a>
</li>
<li>
@@ -91,7 +105,10 @@ export default class AdminNavbarDropdown extends React.Component {
target='_blank'
href='/static/help/report_problem.html'
>
- {'Report a Problem'}
+ <FormattedMessage
+ id='admin.nav.report'
+ defaultMessage='Report a Problem'
+ />
</a>
</li>
</ul>
diff --git a/web/react/components/admin_console/admin_sidebar.jsx b/web/react/components/admin_console/admin_sidebar.jsx
index 66f82c55b..d6bae1feb 100644
--- a/web/react/components/admin_console/admin_sidebar.jsx
+++ b/web/react/components/admin_console/admin_sidebar.jsx
@@ -5,6 +5,8 @@ import AdminSidebarHeader from './admin_sidebar_header.jsx';
import SelectTeamModal from './select_team_modal.jsx';
import * as Utils from '../../utils/utils.jsx';
+import {FormattedMessage} from 'mm-intl';
+
const Tooltip = ReactBootstrap.Tooltip;
const OverlayTrigger = ReactBootstrap.OverlayTrigger;
@@ -82,12 +84,27 @@ export default class AdminSidebar extends React.Component {
render() {
var count = '*';
- var teams = 'Loading';
+ var teams = (
+ <FormattedMessage
+ id='admin.sidebar.loading'
+ defaultMessage='Loading'
+ />
+ );
const removeTooltip = (
- <Tooltip id='remove-team-tooltip'>{'Remove team from sidebar menu'}</Tooltip>
+ <Tooltip id='remove-team-tooltip'>
+ <FormattedMessage
+ id='admin.sidebar.rmTeamSidebar'
+ defaultMessage='Remove team from sidebar menu'
+ />
+ </Tooltip>
);
const addTeamTooltip = (
- <Tooltip id='add-team-tooltip'>{'Add team from sidebar menu'}</Tooltip>
+ <Tooltip id='add-team-tooltip'>
+ <FormattedMessage
+ id='admin.sidebar.addTeamSidebar'
+ defaultMessage='Add team from sidebar menu'
+ />
+ </Tooltip>
);
if (this.props.teams != null) {
@@ -134,7 +151,10 @@ export default class AdminSidebar extends React.Component {
className={this.isSelected('team_users', team.id)}
onClick={this.handleClick.bind(this, 'team_users', team.id)}
>
- {'- Users'}
+ <FormattedMessage
+ id='admin.sidebar.users'
+ defaultMessage='- Users'
+ />
</a>
</li>
<li>
@@ -143,7 +163,10 @@ export default class AdminSidebar extends React.Component {
className={this.isSelected('team_analytics', team.id)}
onClick={this.handleClick.bind(this, 'team_analytics', team.id)}
>
- {'- Statistics'}
+ <FormattedMessage
+ id='admin.sidebar.statistics'
+ defaultMessage='- Statistics'
+ />
</a>
</li>
</ul>
@@ -166,7 +189,10 @@ export default class AdminSidebar extends React.Component {
className={this.isSelected('ldap_settings')}
onClick={this.handleClick.bind(this, 'ldap_settings', null)}
>
- {'LDAP Settings'}
+ <FormattedMessage
+ id='admin.sidebar.ldap'
+ defaultMessage='LDAP Settings'
+ />
</a>
</li>
);
@@ -179,7 +205,10 @@ export default class AdminSidebar extends React.Component {
className={this.isSelected('license')}
onClick={this.handleClick.bind(this, 'license', null)}
>
- {'Edition and License'}
+ <FormattedMessage
+ id='admin.sidebar.license'
+ defaultMessage='Edition and License'
+ />
</a>
</li>
);
@@ -196,7 +225,12 @@ export default class AdminSidebar extends React.Component {
<li>
<h4>
<span className='icon fa fa-gear'></span>
- <span>{'SITE REPORTS'}</span>
+ <span>
+ <FormattedMessage
+ id='admin.sidebar.reports'
+ defaultMessage='SITE REPORTS'
+ />
+ </span>
</h4>
</li>
</ul>
@@ -207,7 +241,10 @@ export default class AdminSidebar extends React.Component {
className={this.isSelected('system_analytics')}
onClick={this.handleClick.bind(this, 'system_analytics', null)}
>
- {'View Statistics'}
+ <FormattedMessage
+ id='admin.sidebar.view_statistics'
+ defaultMessage='View Statistics'
+ />
</a>
</li>
</ul>
@@ -215,7 +252,12 @@ export default class AdminSidebar extends React.Component {
<li>
<h4>
<span className='icon fa fa-gear'></span>
- <span>{'SETTINGS'}</span>
+ <span>
+ <FormattedMessage
+ id='admin.sidebar.settings'
+ defaultMessage='SETTINGS'
+ />
+ </span>
</h4>
</li>
</ul>
@@ -226,7 +268,10 @@ export default class AdminSidebar extends React.Component {
className={this.isSelected('service_settings')}
onClick={this.handleClick.bind(this, 'service_settings', null)}
>
- {'Service Settings'}
+ <FormattedMessage
+ id='admin.sidebar.service'
+ defaultMessage='Service Settings'
+ />
</a>
</li>
<li>
@@ -235,7 +280,10 @@ export default class AdminSidebar extends React.Component {
className={this.isSelected('team_settings')}
onClick={this.handleClick.bind(this, 'team_settings', null)}
>
- {'Team Settings'}
+ <FormattedMessage
+ id='admin.sidebar.team'
+ defaultMessage='Team Settings'
+ />
</a>
</li>
<li>
@@ -244,7 +292,10 @@ export default class AdminSidebar extends React.Component {
className={this.isSelected('sql_settings')}
onClick={this.handleClick.bind(this, 'sql_settings', null)}
>
- {'SQL Settings'}
+ <FormattedMessage
+ id='admin.sidebar.sql'
+ defaultMessage='SQL Settings'
+ />
</a>
</li>
<li>
@@ -253,7 +304,10 @@ export default class AdminSidebar extends React.Component {
className={this.isSelected('email_settings')}
onClick={this.handleClick.bind(this, 'email_settings', null)}
>
- {'Email Settings'}
+ <FormattedMessage
+ id='admin.sidebar.email'
+ defaultMessage='Email Settings'
+ />
</a>
</li>
<li>
@@ -262,7 +316,10 @@ export default class AdminSidebar extends React.Component {
className={this.isSelected('image_settings')}
onClick={this.handleClick.bind(this, 'image_settings', null)}
>
- {'File Settings'}
+ <FormattedMessage
+ id='admin.sidebar.file'
+ defaultMessage='File Settings'
+ />
</a>
</li>
<li>
@@ -271,7 +328,10 @@ export default class AdminSidebar extends React.Component {
className={this.isSelected('log_settings')}
onClick={this.handleClick.bind(this, 'log_settings', null)}
>
- {'Log Settings'}
+ <FormattedMessage
+ id='admin.sidebar.log'
+ defaultMessage='Log Settings'
+ />
</a>
</li>
<li>
@@ -280,7 +340,10 @@ export default class AdminSidebar extends React.Component {
className={this.isSelected('rate_settings')}
onClick={this.handleClick.bind(this, 'rate_settings', null)}
>
- {'Rate Limit Settings'}
+ <FormattedMessage
+ id='admin.sidebar.rate_limit'
+ defaultMessage='Rate Limit Settings'
+ />
</a>
</li>
<li>
@@ -289,7 +352,10 @@ export default class AdminSidebar extends React.Component {
className={this.isSelected('privacy_settings')}
onClick={this.handleClick.bind(this, 'privacy_settings', null)}
>
- {'Privacy Settings'}
+ <FormattedMessage
+ id='admin.sidebar.privacy'
+ defaultMessage='Privacy Settings'
+ />
</a>
</li>
<li>
@@ -298,7 +364,10 @@ export default class AdminSidebar extends React.Component {
className={this.isSelected('gitlab_settings')}
onClick={this.handleClick.bind(this, 'gitlab_settings', null)}
>
- {'GitLab Settings'}
+ <FormattedMessage
+ id='admin.sidebar.gitlab'
+ defaultMessage='GitLab Settings'
+ />
</a>
</li>
{ldapSettings}
@@ -308,7 +377,10 @@ export default class AdminSidebar extends React.Component {
className={this.isSelected('legal_and_support_settings')}
onClick={this.handleClick.bind(this, 'legal_and_support_settings', null)}
>
- {'Legal and Support Settings'}
+ <FormattedMessage
+ id='admin.sidebar.support'
+ defaultMessage='Legal and Support Settings'
+ />
</a>
</li>
</ul>
@@ -316,7 +388,15 @@ export default class AdminSidebar extends React.Component {
<li>
<h4>
<span className='icon fa fa-gear'></span>
- <span>{'TEAMS (' + count + ')'}</span>
+ <span>
+ <FormattedMessage
+ id='admin.sidebar.teams'
+ defaultMessage='TEAMS ({count})'
+ values={{
+ count: count
+ }}
+ />
+ </span>
<span className='menu-icon--right'>
<OverlayTrigger
delayShow={1000}
@@ -345,7 +425,12 @@ export default class AdminSidebar extends React.Component {
<li>
<h4>
<span className='icon fa fa-gear'></span>
- <span>{'OTHER'}</span>
+ <span>
+ <FormattedMessage
+ id='admin.sidebar.other'
+ defaultMessage='OTHER'
+ />
+ </span>
</h4>
</li>
</ul>
@@ -357,7 +442,10 @@ export default class AdminSidebar extends React.Component {
className={this.isSelected('logs')}
onClick={this.handleClick.bind(this, 'logs', null)}
>
- {'Logs'}
+ <FormattedMessage
+ id='admin.sidebar.logs'
+ defaultMessage='Logs'
+ />
</a>
</li>
</ul>
diff --git a/web/react/components/admin_console/admin_sidebar_header.jsx b/web/react/components/admin_console/admin_sidebar_header.jsx
index bfd479939..db499265e 100644
--- a/web/react/components/admin_console/admin_sidebar_header.jsx
+++ b/web/react/components/admin_console/admin_sidebar_header.jsx
@@ -5,6 +5,8 @@ import AdminNavbarDropdown from './admin_navbar_dropdown.jsx';
import UserStore from '../../stores/user_store.jsx';
import * as Utils from '../../utils/utils.jsx';
+import {FormattedMessage} from 'mm-intl';
+
export default class SidebarHeader extends React.Component {
constructor(props) {
super(props);
@@ -51,7 +53,12 @@ export default class SidebarHeader extends React.Component {
{profilePicture}
<div className='header__info'>
<div className='user__name'>{'@' + me.username}</div>
- <div className='team__name'>{'System Console'}</div>
+ <div className='team__name'>
+ <FormattedMessage
+ id='admin.sidebarHeader.systemConsole'
+ defaultMessage='System Console'
+ />
+ </div>
</div>
</a>
<AdminNavbarDropdown ref='dropdown' />
diff --git a/web/react/components/admin_console/analytics.jsx b/web/react/components/admin_console/analytics.jsx
index 70ef1ecab..a22c26c34 100644
--- a/web/react/components/admin_console/analytics.jsx
+++ b/web/react/components/admin_console/analytics.jsx
@@ -8,6 +8,8 @@ import LineChart from './line_chart.jsx';
var Tooltip = ReactBootstrap.Tooltip;
var OverlayTrigger = ReactBootstrap.OverlayTrigger;
+import {FormattedMessage} from 'mm-intl';
+
export default class Analytics extends React.Component {
constructor(props) {
super(props);
@@ -21,11 +23,23 @@ export default class Analytics extends React.Component {
serverError = <div className='form-group has-error'><label className='control-label'>{this.props.serverError}</label></div>;
}
+ let loading = (
+ <FormattedMessage
+ id='admin.analytics.loading'
+ defaultMessage='Loading...'
+ />
+ );
+
var totalCount = (
<div className='col-sm-3'>
<div className='total-count'>
- <div className='title'>{'Total Users'}<i className='fa fa-users'/></div>
- <div className='content'>{this.props.uniqueUserCount == null ? 'Loading...' : this.props.uniqueUserCount}</div>
+ <div className='title'>
+ <FormattedMessage
+ id='admin.analytics.totalUsers'
+ defaultMessage='Total Users'
+ />
+ <i className='fa fa-users'/></div>
+ <div className='content'>{this.props.uniqueUserCount == null ? loading : this.props.uniqueUserCount}</div>
</div>
</div>
);
@@ -33,8 +47,13 @@ export default class Analytics extends React.Component {
var openChannelCount = (
<div className='col-sm-3'>
<div className='total-count'>
- <div className='title'>{'Public Channels'}<i className='fa fa-globe'/></div>
- <div className='content'>{this.props.channelOpenCount == null ? 'Loading...' : this.props.channelOpenCount}</div>
+ <div className='title'>
+ <FormattedMessage
+ id='admin.analytics.publicChannels'
+ defaultMessage='Public Channels'
+ />
+ <i className='fa fa-globe'/></div>
+ <div className='content'>{this.props.channelOpenCount == null ? loading : this.props.channelOpenCount}</div>
</div>
</div>
);
@@ -42,8 +61,13 @@ export default class Analytics extends React.Component {
var openPrivateCount = (
<div className='col-sm-3'>
<div className='total-count'>
- <div className='title'>{'Private Groups'}<i className='fa fa-lock'/></div>
- <div className='content'>{this.props.channelPrivateCount == null ? 'Loading...' : this.props.channelPrivateCount}</div>
+ <div className='title'>
+ <FormattedMessage
+ id='admin.analytics.privateGroups'
+ defaultMessage='Private Groups'
+ />
+ <i className='fa fa-lock'/></div>
+ <div className='content'>{this.props.channelPrivateCount == null ? loading : this.props.channelPrivateCount}</div>
</div>
</div>
);
@@ -51,8 +75,13 @@ export default class Analytics extends React.Component {
var postCount = (
<div className='col-sm-3'>
<div className='total-count'>
- <div className='title'>{'Total Posts'}<i className='fa fa-comment'/></div>
- <div className='content'>{this.props.postCount == null ? 'Loading...' : this.props.postCount}</div>
+ <div className='title'>
+ <FormattedMessage
+ id='admin.analytics.totalPosts'
+ defaultMessage='Total Posts'
+ />
+ <i className='fa fa-comment'/></div>
+ <div className='content'>{this.props.postCount == null ? loading : this.props.postCount}</div>
</div>
</div>
);
@@ -60,8 +89,13 @@ export default class Analytics extends React.Component {
var postCountsByDay = (
<div className='col-sm-12'>
<div className='total-count by-day'>
- <div className='title'>{'Total Posts'}</div>
- <div className='content'>{'Loading...'}</div>
+ <div className='title'>
+ <FormattedMessage
+ id='admin.analytics.totalPosts'
+ defaultMessage='Total Posts'
+ />
+ </div>
+ <div className='content'>{loading}</div>
</div>
</div>
);
@@ -69,7 +103,14 @@ export default class Analytics extends React.Component {
if (this.props.postCountsDay != null) {
let content;
if (this.props.postCountsDay.labels.length === 0) {
- content = 'Not enough data for a meaningful representation.';
+ content = (
+ <h5>
+ <FormattedMessage
+ id='admin.analytics.meaningful'
+ defaultMessage='Not enough data for a meaningful representation.'
+ />
+ </h5>
+ );
} else {
content = (
<LineChart
@@ -82,7 +123,12 @@ export default class Analytics extends React.Component {
postCountsByDay = (
<div className='col-sm-12'>
<div className='total-count by-day'>
- <div className='title'>{'Total Posts'}</div>
+ <div className='title'>
+ <FormattedMessage
+ id='admin.analytics.totalPosts'
+ defaultMessage='Total Posts'
+ />
+ </div>
<div className='content'>
{content}
</div>
@@ -94,8 +140,13 @@ export default class Analytics extends React.Component {
var usersWithPostsByDay = (
<div className='col-sm-12'>
<div className='total-count by-day'>
- <div className='title'>{'Active Users With Posts'}</div>
- <div className='content'>{'Loading...'}</div>
+ <div className='title'>
+ <FormattedMessage
+ id='admin.analytics.activeUsers'
+ defaultMessage='Active Users With Posts'
+ />
+ </div>
+ <div className='content'>{loading}</div>
</div>
</div>
);
@@ -103,7 +154,14 @@ export default class Analytics extends React.Component {
if (this.props.userCountsWithPostsDay != null) {
let content;
if (this.props.userCountsWithPostsDay.labels.length === 0) {
- content = 'Not enough data for a meaningful representation.';
+ content = (
+ <h5>
+ <FormattedMessage
+ id='admin.analytics.meaningful'
+ defaultMessage='Not enough data for a meaningful representation.'
+ />
+ </h5>
+ );
} else {
content = (
<LineChart
@@ -116,7 +174,12 @@ export default class Analytics extends React.Component {
usersWithPostsByDay = (
<div className='col-sm-12'>
<div className='total-count by-day'>
- <div className='title'>{'Active Users With Posts'}</div>
+ <div className='title'>
+ <FormattedMessage
+ id='admin.analytics.activeUsers'
+ defaultMessage='Active Users With Posts'
+ />
+ </div>
<div className='content'>
{content}
</div>
@@ -129,7 +192,7 @@ export default class Analytics extends React.Component {
if (this.props.recentActiveUsers != null) {
let content;
if (this.props.recentActiveUsers.length === 0) {
- content = 'Loading...';
+ content = loading;
} else {
content = (
<table>
@@ -167,7 +230,12 @@ export default class Analytics extends React.Component {
recentActiveUser = (
<div className='col-sm-6'>
<div className='total-count recent-active-users'>
- <div className='title'>{'Recent Active Users'}</div>
+ <div className='title'>
+ <FormattedMessage
+ id='admin.analytics.recentActive'
+ defaultMessage='Recent Active Users'
+ />
+ </div>
<div className='content'>
{content}
</div>
@@ -180,7 +248,7 @@ export default class Analytics extends React.Component {
if (this.props.newlyCreatedUsers != null) {
let content;
if (this.props.newlyCreatedUsers.length === 0) {
- content = 'Loading...';
+ content = loading;
} else {
content = (
<table>
@@ -218,7 +286,12 @@ export default class Analytics extends React.Component {
newUsers = (
<div className='col-sm-6'>
<div className='total-count recent-active-users'>
- <div className='title'>{'Newly Created Users'}</div>
+ <div className='title'>
+ <FormattedMessage
+ id='admin.analytics.newlyCreated'
+ defaultMessage='Newly Created Users'
+ />
+ </div>
<div className='content'>
{content}
</div>
@@ -229,7 +302,15 @@ export default class Analytics extends React.Component {
return (
<div className='wrapper--fixed team_statistics'>
- <h3>{'Statistics for ' + this.props.title}</h3>
+ <h3>
+ <FormattedMessage
+ id='admin.analytics.title'
+ defaultMessage='Statistics for {title}'
+ values={{
+ title: this.props.title
+ }}
+ />
+ </h3>
{serverError}
<div className='row'>
{totalCount}
diff --git a/web/react/components/admin_console/email_settings.jsx b/web/react/components/admin_console/email_settings.jsx
index c568c5a77..ce3c8cd12 100644
--- a/web/react/components/admin_console/email_settings.jsx
+++ b/web/react/components/admin_console/email_settings.jsx
@@ -5,7 +5,68 @@ import * as Client from '../../utils/client.jsx';
import * as AsyncClient from '../../utils/async_client.jsx';
import crypto from 'crypto';
-export default class EmailSettings extends React.Component {
+import {injectIntl, intlShape, defineMessages, FormattedMessage, FormattedHTMLMessage} from 'mm-intl';
+
+var holders = defineMessages({
+ notificationDisplayExample: {
+ id: 'admin.email.notificationDisplayExample',
+ defaultMessage: 'Ex: "Mattermost Notification", "System", "No-Reply"'
+ },
+ notificationEmailExample: {
+ id: 'admin.email.notificationEmailExample',
+ defaultMessage: 'Ex: "mattermost@yourcompany.com", "admin@yourcompany.com"'
+ },
+ smtpUsernameExample: {
+ id: 'admin.email.smtpUsernameExample',
+ defaultMessage: 'Ex: "admin@yourcompany.com", "AKIADTOVBGERKLCBV"'
+ },
+ smtpPasswordExample: {
+ id: 'admin.email.smtpPasswordExample',
+ defaultMessage: 'Ex: "yourpassword", "jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY"'
+ },
+ smtpServerExample: {
+ id: 'admin.email.smtpServerExample',
+ defaultMessage: 'Ex: "smtp.yourcompany.com", "email-smtp.us-east-1.amazonaws.com"'
+ },
+ smtpPortExample: {
+ id: 'admin.email.smtpPortExample',
+ defaultMessage: 'Ex: "25", "465"'
+ },
+ connectionSecurityNone: {
+ id: 'admin.email.connectionSecurityNone',
+ defaultMessage: 'None'
+ },
+ connectionSecurityTls: {
+ id: 'admin.email.connectionSecurityTls',
+ defaultMessage: 'TLS (Recommended)'
+ },
+ connectionSecurityStart: {
+ id: 'admin.email.connectionSecurityStart',
+ defaultMessage: 'STARTTLS'
+ },
+ inviteSaltExample: {
+ id: 'admin.email.inviteSaltExample',
+ defaultMessage: 'Ex "bjlSR4QqkXFBr7TP4oDzlfZmcNuH9Yo"'
+ },
+ passwordSaltExample: {
+ id: 'admin.email.passwordSaltExample',
+ defaultMessage: 'Ex "bjlSR4QqkXFBr7TP4oDzlfZmcNuH9Yo"'
+ },
+ pushServerEx: {
+ id: 'admin.email.pushServerEx',
+ defaultMessage: 'E.g.: "https://push-test.mattermost.com"'
+ },
+ testing: {
+ id: 'admin.email.testing',
+ defaultMessage: 'Testing...'
+ },
+ saving: {
+ id: 'admin.email.saving',
+ defaultMessage: 'Saving Config...'
+ }
+});
+
+class EmailSettings extends React.Component {
constructor(props) {
super(props);
@@ -156,6 +217,7 @@ export default class EmailSettings extends React.Component {
}
render() {
+ const {formatMessage} = this.props.intl;
var serverError = '';
if (this.state.serverError) {
serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>;
@@ -170,7 +232,11 @@ export default class EmailSettings extends React.Component {
if (this.state.emailSuccess) {
emailSuccess = (
<div className='alert alert-success'>
- <i className='fa fa-check'></i>{'No errors were reported while sending an email. Please check your inbox to make sure.'}
+ <i className='fa fa-check'></i>
+ <FormattedMessage
+ id='admin.email.emailSuccess'
+ defaultMessage='No errors were reported while sending an email. Please check your inbox to make sure.'
+ />
</div>
);
}
@@ -179,14 +245,26 @@ export default class EmailSettings extends React.Component {
if (this.state.emailFail) {
emailSuccess = (
<div className='alert alert-warning'>
- <i className='fa fa-warning'></i>{'Connection unsuccessful: ' + this.state.emailFail}
+ <i className='fa fa-warning'></i>
+ <FormattedMessage
+ id='admin.email.emailFail'
+ defaultMessage='Connection unsuccessful: {error}'
+ values={{
+ error: this.state.emailFail
+ }}
+ />
</div>
);
}
return (
<div className='wrapper--fixed'>
- <h3>{'Email Settings'}</h3>
+ <h3>
+ <FormattedMessage
+ id='admin.email.emailSettings'
+ defaultMessage='Email Settings'
+ />
+ </h3>
<form
className='form-horizontal'
role='form'
@@ -197,7 +275,10 @@ export default class EmailSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='allowSignUpWithEmail'
>
- {'Allow Sign Up With Email: '}
+ <FormattedMessage
+ id='admin.email.allowSignupTitle'
+ defaultMessage='Allow Sign Up With Email: '
+ />
</label>
<div className='col-sm-8'>
<label className='radio-inline'>
@@ -209,7 +290,10 @@ export default class EmailSettings extends React.Component {
defaultChecked={this.props.config.EmailSettings.EnableSignUpWithEmail}
onChange={this.handleChange.bind(this, 'allowSignUpWithEmail_true')}
/>
- {'true'}
+ <FormattedMessage
+ id='admin.email.true'
+ defaultMessage='true'
+ />
</label>
<label className='radio-inline'>
<input
@@ -219,9 +303,17 @@ export default class EmailSettings extends React.Component {
defaultChecked={!this.props.config.EmailSettings.EnableSignUpWithEmail}
onChange={this.handleChange.bind(this, 'allowSignUpWithEmail_false')}
/>
- {'false'}
+ <FormattedMessage
+ id='admin.email.false'
+ defaultMessage='false'
+ />
</label>
- <p className='help-text'>{'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.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.email.allowSignupDescription'
+ defaultMessage='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.'
+ />
+ </p>
</div>
</div>
@@ -230,7 +322,10 @@ export default class EmailSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='sendEmailNotifications'
>
- {'Send Email Notifications: '}
+ <FormattedMessage
+ id='admin.email.notificationsTitle'
+ defaultMessage='Send Email Notifications: '
+ />
</label>
<div className='col-sm-8'>
<label className='radio-inline'>
@@ -242,7 +337,10 @@ export default class EmailSettings extends React.Component {
defaultChecked={this.props.config.EmailSettings.SendEmailNotifications}
onChange={this.handleChange.bind(this, 'sendEmailNotifications_true')}
/>
- {'true'}
+ <FormattedMessage
+ id='admin.email.true'
+ defaultMessage='true'
+ />
</label>
<label className='radio-inline'>
<input
@@ -252,9 +350,17 @@ export default class EmailSettings extends React.Component {
defaultChecked={!this.props.config.EmailSettings.SendEmailNotifications}
onChange={this.handleChange.bind(this, 'sendEmailNotifications_false')}
/>
- {'false'}
+ <FormattedMessage
+ id='admin.email.false'
+ defaultMessage='false'
+ />
</label>
- <p className='help-text'>{'Typically set to true in production. When true, Mattermost attempts to send email notifications. Developers may set this field to false to skip email setup for faster development.\nSetting this to true removes the Preview Mode banner (requires logging out and logging back in after setting is changed).'}</p>
+ <p className='help-text'>
+ <FormattedHTMLMessage
+ id='admin.email.notificationsDescription'
+ defaultMessage='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).'
+ />
+ </p>
</div>
</div>
@@ -263,7 +369,10 @@ export default class EmailSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='requireEmailVerification'
>
- {'Require Email Verification: '}
+ <FormattedMessage
+ id='admin.email.requireVerificationTitle'
+ defaultMessage='Require Email Verification: '
+ />
</label>
<div className='col-sm-8'>
<label className='radio-inline'>
@@ -276,7 +385,10 @@ export default class EmailSettings extends React.Component {
onChange={this.handleChange.bind(this, 'requireEmailVerification_true')}
disabled={!this.state.sendEmailNotifications}
/>
- {'true'}
+ <FormattedMessage
+ id='admin.email.true'
+ defaultMessage='true'
+ />
</label>
<label className='radio-inline'>
<input
@@ -287,9 +399,17 @@ export default class EmailSettings extends React.Component {
onChange={this.handleChange.bind(this, 'requireEmailVerification_false')}
disabled={!this.state.sendEmailNotifications}
/>
- {'false'}
+ <FormattedMessage
+ id='admin.email.false'
+ defaultMessage='false'
+ />
</label>
- <p className='help-text'>{'Typically set to true in production. When true, Mattermost requires email verification after account creation prior to allowing login. Developers may set this field to false so skip sending verification emails for faster development.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.email.requireVerificationDescription'
+ defaultMessage='Typically set to true in production. When true, Mattermost requires email verification after account creation prior to allowing login. Developers may set this field to false so skip sending verification emails for faster development.'
+ />
+ </p>
</div>
</div>
@@ -298,7 +418,10 @@ export default class EmailSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='feedbackName'
>
- {'Notification Display Name:'}
+ <FormattedMessage
+ id='admin.email.notificationDisplayTitle'
+ defaultMessage='Notification Display Name:'
+ />
</label>
<div className='col-sm-8'>
<input
@@ -306,12 +429,17 @@ export default class EmailSettings extends React.Component {
className='form-control'
id='feedbackName'
ref='feedbackName'
- placeholder='E.g.: "Mattermost Notification", "System", "No-Reply"'
+ placeholder={formatMessage(holders.notificationDisplayExample)}
defaultValue={this.props.config.EmailSettings.FeedbackName}
onChange={this.handleChange}
disabled={!this.state.sendEmailNotifications}
/>
- <p className='help-text'>{'Display name on email account used when sending notification emails from Mattermost.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.email.notificationDisplayDescription'
+ defaultMessage='Display name on email account used when sending notification emails from Mattermost.'
+ />
+ </p>
</div>
</div>
@@ -320,7 +448,10 @@ export default class EmailSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='feedbackEmail'
>
- {'Notification Email Address:'}
+ <FormattedMessage
+ id='admin.email.notificationEmailTitle'
+ defaultMessage='Notification Email Address:'
+ />
</label>
<div className='col-sm-8'>
<input
@@ -328,12 +459,17 @@ export default class EmailSettings extends React.Component {
className='form-control'
id='feedbackEmail'
ref='feedbackEmail'
- placeholder='E.g.: "mattermost@yourcompany.com", "admin@yourcompany.com"'
+ placeholder={formatMessage(holders.notificationEmailExample)}
defaultValue={this.props.config.EmailSettings.FeedbackEmail}
onChange={this.handleChange}
disabled={!this.state.sendEmailNotifications}
/>
- <p className='help-text'>{'Email address displayed on email account used when sending notification emails from Mattermost.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.email.notificationEmailDescription'
+ defaultMessage='Email address displayed on email account used when sending notification emails from Mattermost.'
+ />
+ </p>
</div>
</div>
@@ -342,7 +478,10 @@ export default class EmailSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='SMTPUsername'
>
- {'SMTP Username:'}
+ <FormattedMessage
+ id='admin.email.smtpUsernameTitle'
+ defaultMessage='SMTP Username:'
+ />
</label>
<div className='col-sm-8'>
<input
@@ -350,12 +489,17 @@ export default class EmailSettings extends React.Component {
className='form-control'
id='SMTPUsername'
ref='SMTPUsername'
- placeholder='E.g.: "admin@yourcompany.com", "AKIADTOVBGERKLCBV"'
+ placeholder={formatMessage(holders.smtpUsernameExample)}
defaultValue={this.props.config.EmailSettings.SMTPUsername}
onChange={this.handleChange}
disabled={!this.state.sendEmailNotifications}
/>
- <p className='help-text'>{' Obtain this credential from administrator setting up your email server.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.email.smtpUsernameDescription'
+ defaultMessage=' Obtain this credential from administrator setting up your email server.'
+ />
+ </p>
</div>
</div>
@@ -364,7 +508,10 @@ export default class EmailSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='SMTPPassword'
>
- {'SMTP Password:'}
+ <FormattedMessage
+ id='admin.email.smtpPasswordTitle'
+ defaultMessage='SMTP Password:'
+ />
</label>
<div className='col-sm-8'>
<input
@@ -372,12 +519,17 @@ export default class EmailSettings extends React.Component {
className='form-control'
id='SMTPPassword'
ref='SMTPPassword'
- placeholder='E.g.: "yourpassword", "jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY"'
+ placeholder={formatMessage(holders.smtpPasswordExample)}
defaultValue={this.props.config.EmailSettings.SMTPPassword}
onChange={this.handleChange}
disabled={!this.state.sendEmailNotifications}
/>
- <p className='help-text'>{' Obtain this credential from administrator setting up your email server.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.email.smtpPasswordDescription'
+ defaultMessage=' Obtain this credential from administrator setting up your email server.'
+ />
+ </p>
</div>
</div>
@@ -386,7 +538,10 @@ export default class EmailSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='SMTPServer'
>
- {'SMTP Server:'}
+ <FormattedMessage
+ id='admin.email.smtpServerTitle'
+ defaultMessage='SMTP Server:'
+ />
</label>
<div className='col-sm-8'>
<input
@@ -394,12 +549,17 @@ export default class EmailSettings extends React.Component {
className='form-control'
id='SMTPServer'
ref='SMTPServer'
- placeholder='E.g.: "smtp.yourcompany.com", "email-smtp.us-east-1.amazonaws.com"'
+ placeholder={formatMessage(holders.smtpServerExample)}
defaultValue={this.props.config.EmailSettings.SMTPServer}
onChange={this.handleChange}
disabled={!this.state.sendEmailNotifications}
/>
- <p className='help-text'>{'Location of SMTP email server.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.email.smtpServerDescription'
+ defaultMessage='Location of SMTP email server.'
+ />
+ </p>
</div>
</div>
@@ -408,7 +568,10 @@ export default class EmailSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='SMTPPort'
>
- {'SMTP Port:'}
+ <FormattedMessage
+ id='admin.email.smtpPortTitle'
+ defaultMessage='SMTP Port:'
+ />
</label>
<div className='col-sm-8'>
<input
@@ -416,12 +579,17 @@ export default class EmailSettings extends React.Component {
className='form-control'
id='SMTPPort'
ref='SMTPPort'
- placeholder='E.g.: "25", "465"'
+ placeholder={formatMessage(holders.smtpPortExample)}
defaultValue={this.props.config.EmailSettings.SMTPPort}
onChange={this.handleChange}
disabled={!this.state.sendEmailNotifications}
/>
- <p className='help-text'>{'Port of SMTP email server.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.email.smtpPortDescription'
+ defaultMessage='Port of SMTP email server.'
+ />
+ </p>
</div>
</div>
@@ -430,7 +598,10 @@ export default class EmailSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='ConnectionSecurity'
>
- {'Connection Security:'}
+ <FormattedMessage
+ id='admin.email.connectionSecurityTitle'
+ defaultMessage='Connection Security:'
+ />
</label>
<div className='col-sm-8'>
<select
@@ -441,9 +612,9 @@ export default class EmailSettings extends React.Component {
onChange={this.handleChange}
disabled={!this.state.sendEmailNotifications}
>
- <option value=''>{'None'}</option>
- <option value='TLS'>{'TLS (Recommended)'}</option>
- <option value='STARTTLS'>{'STARTTLS'}</option>
+ <option value=''>{formatMessage(holders.connectionSecurityNone)}</option>
+ <option value='TLS'>{formatMessage(holders.connectionSecurityTls)}</option>
+ <option value='STARTTLS'>{formatMessage(holders.connectionSecurityStart)}</option>
</select>
<div className='help-text'>
<table
@@ -451,9 +622,29 @@ export default class EmailSettings extends React.Component {
cellPadding='5'
>
<tbody>
- <tr><td className='help-text'>{'None'}</td><td className='help-text'>{'Mattermost will send email over an unsecure connection.'}</td></tr>
- <tr><td className='help-text'>{'TLS'}</td><td className='help-text'>{'Encrypts the communication between Mattermost and your email server.'}</td></tr>
- <tr><td className='help-text'>{'STARTTLS'}</td><td className='help-text'>{'Takes an existing insecure connection and attempts to upgrade it to a secure connection using TLS.'}</td></tr>
+ <tr><td className='help-text'>
+ <FormattedMessage
+ id='admin.email.connectionSecurityNone'
+ defaultMessage='None'
+ />
+ </td><td className='help-text'>
+ <FormattedMessage
+ id='admin.email.connectionSecurityNoneDescription'
+ defaultMessage='Mattermost will send email over an unsecure connection.'
+ />
+ </td></tr>
+ <tr><td className='help-text'>{'TLS'}</td><td className='help-text'>
+ <FormattedMessage
+ id='admin.email.connectionSecurityTlsDescription'
+ defaultMessage='Encrypts the communication between Mattermost and your email server.'
+ />
+ </td></tr>
+ <tr><td className='help-text'>{'STARTTLS'}</td><td className='help-text'>
+ <FormattedMessage
+ id='admin.email.connectionSecurityStartDescription'
+ defaultMessage='Takes an existing insecure connection and attempts to upgrade it to a secure connection using TLS.'
+ />
+ </td></tr>
</tbody>
</table>
</div>
@@ -463,9 +654,12 @@ export default class EmailSettings extends React.Component {
onClick={this.handleTestConnection}
disabled={!this.state.sendEmailNotifications}
id='connection-button'
- data-loading-text={'<span class=\'glyphicon glyphicon-refresh glyphicon-refresh-animate\'></span> Testing...'}
+ data-loading-text={'<span class=\'glyphicon glyphicon-refresh glyphicon-refresh-animate\'></span> ' + formatMessage(holders.testing)}
>
- {'Test Connection'}
+ <FormattedMessage
+ id='admin.email.connectionSecurityTest'
+ defaultMessage='Test Connection'
+ />
</button>
{emailSuccess}
{emailFail}
@@ -478,7 +672,10 @@ export default class EmailSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='InviteSalt'
>
- {'Invite Salt:'}
+ <FormattedMessage
+ id='admin.email.inviteSaltTitle'
+ defaultMessage='Invite Salt:'
+ />
</label>
<div className='col-sm-8'>
<input
@@ -486,19 +683,27 @@ export default class EmailSettings extends React.Component {
className='form-control'
id='InviteSalt'
ref='InviteSalt'
- placeholder='E.g.: "bjlSR4QqkXFBr7TP4oDzlfZmcNuH9Yo"'
+ placeholder={formatMessage(holders.inviteSaltExample)}
defaultValue={this.props.config.EmailSettings.InviteSalt}
onChange={this.handleChange}
disabled={!this.state.sendEmailNotifications}
/>
- <p className='help-text'>{'32-character salt added to signing of email invites. Randomly generated on install. Click "Re-Generate" to create new salt.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.email.inviteSaltDescription'
+ defaultMessage='32-character salt added to signing of email invites. Randomly generated on install. Click "Re-Generate" to create new salt.'
+ />
+ </p>
<div className='help-text'>
<button
className='btn btn-default'
onClick={this.handleGenerateInvite}
disabled={!this.state.sendEmailNotifications}
>
- {'Re-Generate'}
+ <FormattedMessage
+ id='admin.email.regenerate'
+ defaultMessage='Re-Generate'
+ />
</button>
</div>
</div>
@@ -509,7 +714,10 @@ export default class EmailSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='PasswordResetSalt'
>
- {'Password Reset Salt:'}
+ <FormattedMessage
+ id='admin.email.passwordSaltTitle'
+ defaultMessage='Password Reset Salt:'
+ />
</label>
<div className='col-sm-8'>
<input
@@ -517,19 +725,27 @@ export default class EmailSettings extends React.Component {
className='form-control'
id='PasswordResetSalt'
ref='PasswordResetSalt'
- placeholder='E.g.: "bjlSR4QqkXFBr7TP4oDzlfZmcNuH9Yo"'
+ placeholder={formatMessage(holders.passwordSaltExample)}
defaultValue={this.props.config.EmailSettings.PasswordResetSalt}
onChange={this.handleChange}
disabled={!this.state.sendEmailNotifications}
/>
- <p className='help-text'>{'32-character salt added to signing of password reset emails. Randomly generated on install. Click "Re-Generate" to create new salt.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.email.passwordSaltDescription'
+ defaultMessage='32-character salt added to signing of password reset emails. Randomly generated on install. Click "Re-Generate" to create new salt.'
+ />
+ </p>
<div className='help-text'>
<button
className='btn btn-default'
onClick={this.handleGenerateReset}
disabled={!this.state.sendEmailNotifications}
>
- {'Re-Generate'}
+ <FormattedMessage
+ id='admin.email.regenerate'
+ defaultMessage='Re-Generate'
+ />
</button>
</div>
</div>
@@ -540,7 +756,10 @@ export default class EmailSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='sendPushNotifications'
>
- {'Send Push Notifications: '}
+ <FormattedMessage
+ id='admin.email.pushTitle'
+ defaultMessage='Send Push Notifications: '
+ />
</label>
<div className='col-sm-8'>
<label className='radio-inline'>
@@ -552,7 +771,10 @@ export default class EmailSettings extends React.Component {
defaultChecked={this.props.config.EmailSettings.SendPushNotifications}
onChange={this.handleChange.bind(this, 'sendPushNotifications_true')}
/>
- {'true'}
+ <FormattedMessage
+ id='admin.email.true'
+ defaultMessage='true'
+ />
</label>
<label className='radio-inline'>
<input
@@ -562,9 +784,17 @@ export default class EmailSettings extends React.Component {
defaultChecked={!this.props.config.EmailSettings.SendPushNotifications}
onChange={this.handleChange.bind(this, 'sendPushNotifications_false')}
/>
- {'false'}
+ <FormattedMessage
+ id='admin.email.false'
+ defaultMessage='false'
+ />
</label>
- <p className='help-text'>{'Typically set to true in production. When true, Mattermost attempts to send iOS and Android push notifications through the push notification server.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.email.pushDesc'
+ defaultMessage='Typically set to true in production. When true, Mattermost attempts to send iOS and Android push notifications through the push notification server.'
+ />
+ </p>
</div>
</div>
@@ -573,7 +803,10 @@ export default class EmailSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='PushNotificationServer'
>
- {'Push Notification Server:'}
+ <FormattedMessage
+ id='admin.email.pushServerTitle'
+ defaultMessage='Push Notification Server:'
+ />
</label>
<div className='col-sm-8'>
<input
@@ -581,12 +814,17 @@ export default class EmailSettings extends React.Component {
className='form-control'
id='PushNotificationServer'
ref='PushNotificationServer'
- placeholder='E.g.: "https://push-test.mattermost.com"'
+ placeholder={formatMessage(holders.pushServerEx)}
defaultValue={this.props.config.EmailSettings.PushNotificationServer}
onChange={this.handleChange}
disabled={!this.state.sendPushNotifications}
/>
- <p className='help-text'>{'Location of Mattermost push notification service you can set up behind your firewall using https://github.com/mattermost/push-proxy. For testing you can use https://push-test.mattermost.com, which connects to the sample Mattermost iOS app in the public Apple AppStore. Please do not use test service for production deployments.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.email.pushServerDesc'
+ defaultMessage='Location of Mattermost push notification service you can set up behind your firewall using https://github.com/mattermost/push-proxy. For testing you can use https://push-test.mattermost.com, which connects to the sample Mattermost iOS app in the public Apple AppStore. Please do not use test service for production deployments.'
+ />
+ </p>
</div>
</div>
@@ -599,9 +837,12 @@ export default class EmailSettings extends React.Component {
className={saveClass}
onClick={this.handleSubmit}
id='save-button'
- data-loading-text={'<span class=\'glyphicon glyphicon-refresh glyphicon-refresh-animate\'></span> Saving Config...'}
+ data-loading-text={'<span class=\'glyphicon glyphicon-refresh glyphicon-refresh-animate\'></span> ' + formatMessage(holders.saving)}
>
- {'Save'}
+ <FormattedMessage
+ id='admin.email.save'
+ defaultMessage='Save'
+ />
</button>
</div>
</div>
@@ -613,5 +854,8 @@ export default class EmailSettings extends React.Component {
}
EmailSettings.propTypes = {
+ intl: intlShape.isRequired,
config: React.PropTypes.object
};
+
+export default injectIntl(EmailSettings); \ No newline at end of file
diff --git a/web/react/components/admin_console/gitlab_settings.jsx b/web/react/components/admin_console/gitlab_settings.jsx
index 8c689a2d8..744fa3b19 100644
--- a/web/react/components/admin_console/gitlab_settings.jsx
+++ b/web/react/components/admin_console/gitlab_settings.jsx
@@ -4,7 +4,36 @@
import * as Client from '../../utils/client.jsx';
import * as AsyncClient from '../../utils/async_client.jsx';
-export default class GitLabSettings extends React.Component {
+import {injectIntl, intlShape, defineMessages, FormattedMessage, FormattedHTMLMessage} from 'mm-intl';
+
+const holders = defineMessages({
+ clientIdExample: {
+ id: 'admin.gitlab.clientIdExample',
+ defaultMessage: 'Ex "jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY"'
+ },
+ clientSecretExample: {
+ id: 'admin.gitlab.clientSecretExample',
+ defaultMessage: 'Ex "jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY"'
+ },
+ authExample: {
+ id: 'admin.gitlab.authExample',
+ defaultMessage: 'Ex ""'
+ },
+ tokenExample: {
+ id: 'admin.gitlab.tokenExample',
+ defaultMessage: 'Ex ""'
+ },
+ userExample: {
+ id: 'admin.gitlab.userExample',
+ defaultMessage: 'Ex ""'
+ },
+ saving: {
+ id: 'admin.gitlab.saving',
+ defaultMessage: 'Saving Config...'
+ }
+});
+
+class GitLabSettings extends React.Component {
constructor(props) {
super(props);
@@ -65,6 +94,7 @@ export default class GitLabSettings extends React.Component {
}
render() {
+ const {formatMessage} = this.props.intl;
var serverError = '';
if (this.state.serverError) {
serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>;
@@ -78,7 +108,12 @@ export default class GitLabSettings extends React.Component {
return (
<div className='wrapper--fixed'>
- <h3>{'GitLab Settings'}</h3>
+ <h3>
+ <FormattedMessage
+ id='admin.gitlab.settingsTitle'
+ defaultMessage='GitLab Settings'
+ />
+ </h3>
<form
className='form-horizontal'
role='form'
@@ -89,7 +124,10 @@ export default class GitLabSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='Enable'
>
- {'Enable Sign Up With GitLab: '}
+ <FormattedMessage
+ id='admin.gitlab.enableTitle'
+ defaultMessage='Enable Sign Up With GitLab: '
+ />
</label>
<div className='col-sm-8'>
<label className='radio-inline'>
@@ -101,7 +139,10 @@ export default class GitLabSettings extends React.Component {
defaultChecked={this.props.config.GitLabSettings.Enable}
onChange={this.handleChange.bind(this, 'EnableTrue')}
/>
- {'true'}
+ <FormattedMessage
+ id='admin.gitlab.true'
+ defaultMessage='true'
+ />
</label>
<label className='radio-inline'>
<input
@@ -111,18 +152,23 @@ export default class GitLabSettings extends React.Component {
defaultChecked={!this.props.config.GitLabSettings.Enable}
onChange={this.handleChange.bind(this, 'EnableFalse')}
/>
- {'false'}
+ <FormattedMessage
+ id='admin.gitlab.false'
+ defaultMessage='false'
+ />
</label>
<p className='help-text'>
- {'When true, Mattermost allows team creation and account signup using GitLab OAuth.'} <br/>
+ <FormattedMessage
+ id='admin.gitlab.enableDescription'
+ defaultMessage='When true, Mattermost allows team creation and account signup using GitLab OAuth.'
+ />
+ <br/>
</p>
<div className='help-text'>
- <ol>
- <li>{'Log in to your GitLab account and go to Applications -> Profile Settings.'}</li>
- <li>{'Enter Redirect URIs "<your-mattermost-url>/login/gitlab/complete" (example: http://localhost:8065/login/gitlab/complete) and "<your-mattermost-url>/signup/gitlab/complete". '}</li>
- <li>{'Then use "Secret" and "Id" fields from GitLab to complete the options below.'}</li>
- <li>{'Complete the Endpoint URLs below. '}</li>
- </ol>
+ <FormattedHTMLMessage
+ id='admin.gitlab.EnableHtmlDesc'
+ defaultMessage='<ol><li>Log in to your GitLab account and go to Applications -> Profile Settings.</li><li>Enter Redirect URIs "<your-mattermost-url>/login/gitlab/complete" (example: http://localhost:8065/login/gitlab/complete) and "<your-mattermost-url>/signup/gitlab/complete". </li><li>Then use "Secret" and "Id" fields from GitLab to complete the options below.</li><li>Complete the Endpoint URLs below. </li></ol>'
+ />
</div>
</div>
</div>
@@ -132,7 +178,10 @@ export default class GitLabSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='Id'
>
- {'Id:'}
+ <FormattedMessage
+ id='admin.gitlab.clientIdTitle'
+ defaultMessage='Id:'
+ />
</label>
<div className='col-sm-8'>
<input
@@ -140,12 +189,17 @@ export default class GitLabSettings extends React.Component {
className='form-control'
id='Id'
ref='Id'
- placeholder='Ex "jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY"'
+ placeholder={formatMessage(holders.clientIdExample)}
defaultValue={this.props.config.GitLabSettings.Id}
onChange={this.handleChange}
disabled={!this.state.Enable}
/>
- <p className='help-text'>{'Obtain this value via the instructions above for logging into GitLab'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.gitlab.clientIdDescription'
+ defaultMessage='Obtain this value via the instructions above for logging into GitLab'
+ />
+ </p>
</div>
</div>
@@ -154,7 +208,10 @@ export default class GitLabSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='Secret'
>
- {'Secret:'}
+ <FormattedMessage
+ id='admin.gitlab.clientSecretTitle'
+ defaultMessage='Secret:'
+ />
</label>
<div className='col-sm-8'>
<input
@@ -162,12 +219,17 @@ export default class GitLabSettings extends React.Component {
className='form-control'
id='Secret'
ref='Secret'
- placeholder='Ex "jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY"'
+ placeholder={formatMessage(holders.clientSecretExample)}
defaultValue={this.props.config.GitLabSettings.Secret}
onChange={this.handleChange}
disabled={!this.state.Enable}
/>
- <p className='help-text'>{'Obtain this value via the instructions above for logging into GitLab.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.gitab.clientSecretDescription'
+ defaultMessage='Obtain this value via the instructions above for logging into GitLab.'
+ />
+ </p>
</div>
</div>
@@ -176,7 +238,10 @@ export default class GitLabSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='AuthEndpoint'
>
- {'Auth Endpoint:'}
+ <FormattedMessage
+ id='admin.gitlab.authTitle'
+ defaultMessage='Auth Endpoint:'
+ />
</label>
<div className='col-sm-8'>
<input
@@ -184,12 +249,17 @@ export default class GitLabSettings extends React.Component {
className='form-control'
id='AuthEndpoint'
ref='AuthEndpoint'
- placeholder='Ex ""'
+ placeholder={formatMessage(holders.authExample)}
defaultValue={this.props.config.GitLabSettings.AuthEndpoint}
onChange={this.handleChange}
disabled={!this.state.Enable}
/>
- <p className='help-text'>{'Enter https://<your-gitlab-url>/oauth/authorize (example https://example.com:3000/oauth/authorize). Make sure you use HTTP or HTTPS in your URL depending on your server configuration.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.gitlab.authDescription'
+ defaultMessage='Enter https://<your-gitlab-url>/oauth/authorize (example https://example.com:3000/oauth/authorize). Make sure you use HTTP or HTTPS in your URL depending on your server configuration.'
+ />
+ </p>
</div>
</div>
@@ -198,7 +268,10 @@ export default class GitLabSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='TokenEndpoint'
>
- {'Token Endpoint:'}
+ <FormattedMessage
+ id='admin.gitlab.tokenTitle'
+ defaultMessage='Token Endpoint:'
+ />
</label>
<div className='col-sm-8'>
<input
@@ -206,12 +279,17 @@ export default class GitLabSettings extends React.Component {
className='form-control'
id='TokenEndpoint'
ref='TokenEndpoint'
- placeholder='Ex ""'
+ placeholder={formatMessage(holders.tokenExample)}
defaultValue={this.props.config.GitLabSettings.TokenEndpoint}
onChange={this.handleChange}
disabled={!this.state.Enable}
/>
- <p className='help-text'>{'Enter https://<your-gitlab-url>/oauth/token. Make sure you use HTTP or HTTPS in your URL depending on your server configuration.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.gitlab.tokenDescription'
+ defaultMessage='Enter https://<your-gitlab-url>/oauth/token. Make sure you use HTTP or HTTPS in your URL depending on your server configuration.'
+ />
+ </p>
</div>
</div>
@@ -220,7 +298,10 @@ export default class GitLabSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='UserApiEndpoint'
>
- {'User API Endpoint:'}
+ <FormattedMessage
+ id='admin.gitlab.userTitle'
+ defaultMessage='User API Endpoint:'
+ />
</label>
<div className='col-sm-8'>
<input
@@ -228,12 +309,17 @@ export default class GitLabSettings extends React.Component {
className='form-control'
id='UserApiEndpoint'
ref='UserApiEndpoint'
- placeholder='Ex ""'
+ placeholder={formatMessage(holders.userExample)}
defaultValue={this.props.config.GitLabSettings.UserApiEndpoint}
onChange={this.handleChange}
disabled={!this.state.Enable}
/>
- <p className='help-text'>{'Enter https://<your-gitlab-url>/api/v3/user. Make sure you use HTTP or HTTPS in your URL depending on your server configuration.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.gitlab.userDescription'
+ defaultMessage='Enter https://<your-gitlab-url>/api/v3/user. Make sure you use HTTP or HTTPS in your URL depending on your server configuration.'
+ />
+ </p>
</div>
</div>
@@ -246,9 +332,12 @@ export default class GitLabSettings extends React.Component {
className={saveClass}
onClick={this.handleSubmit}
id='save-button'
- data-loading-text={'<span class=\'glyphicon glyphicon-refresh glyphicon-refresh-animate\'></span> Saving Config...'}
+ data-loading-text={'<span class=\'glyphicon glyphicon-refresh glyphicon-refresh-animate\'></span> ' + formatMessage(holders.saving)}
>
- {'Save'}
+ <FormattedMessage
+ id='admin.gitlab.save'
+ defaultMessage='Save'
+ />
</button>
</div>
</div>
@@ -283,5 +372,8 @@ export default class GitLabSettings extends React.Component {
// </div>
GitLabSettings.propTypes = {
+ intl: intlShape.isRequired,
config: React.PropTypes.object
};
+
+export default injectIntl(GitLabSettings); \ No newline at end of file
diff --git a/web/react/components/admin_console/image_settings.jsx b/web/react/components/admin_console/image_settings.jsx
index e1ffad7d3..12bf554ea 100644
--- a/web/react/components/admin_console/image_settings.jsx
+++ b/web/react/components/admin_console/image_settings.jsx
@@ -5,7 +5,76 @@ import * as Client from '../../utils/client.jsx';
import * as AsyncClient from '../../utils/async_client.jsx';
import crypto from 'crypto';
-export default class FileSettings extends React.Component {
+import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'mm-intl';
+
+const holders = defineMessages({
+ storeDisabled: {
+ id: 'admin.image.storeDisabled',
+ defaultMessage: 'Disable File Storage'
+ },
+ storeLocal: {
+ id: 'admin.image.storeLocal',
+ defaultMessage: 'Local File System'
+ },
+ storeAmazonS3: {
+ id: 'admin.image.storeAmazonS3',
+ defaultMessage: 'Amazon S3'
+ },
+ localExample: {
+ id: 'admin.image.localExample',
+ defaultMessage: 'Ex "./data/"'
+ },
+ amazonS3IdExample: {
+ id: 'admin.image.amazonS3IdExample',
+ defaultMessage: 'Ex "AKIADTOVBGERKLCBV"'
+ },
+ amazonS3SecretExample: {
+ id: 'admin.image.amazonS3SecretExample',
+ defaultMessage: 'Ex "jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY"'
+ },
+ amazonS3BucketExample: {
+ id: 'admin.image.amazonS3BucketExample',
+ defaultMessage: 'Ex "mattermost-media"'
+ },
+ amazonS3RegionExample: {
+ id: 'admin.image.amazonS3RegionExample',
+ defaultMessage: 'Ex "us-east-1"'
+ },
+ thumbWidthExample: {
+ id: 'admin.image.thumbWidthExample',
+ defaultMessage: 'Ex "120"'
+ },
+ thumbHeightExample: {
+ id: 'admin.image.thumbHeightExample',
+ defaultMessage: 'Ex "100"'
+ },
+ previewWidthExample: {
+ id: 'admin.image.previewWidthExample',
+ defaultMessage: 'Ex "1024"'
+ },
+ previewHeightExample: {
+ id: 'admin.image.previewHeightExample',
+ defaultMessage: 'Ex "0"'
+ },
+ profileWidthExample: {
+ id: 'admin.image.profileWidthExample',
+ defaultMessage: 'Ex "1024"'
+ },
+ profileHeightExample: {
+ id: 'admin.image.profileHeightExample',
+ defaultMessage: 'Ex "0"'
+ },
+ publicLinkExample: {
+ id: 'admin.image.publicLinkExample',
+ defaultMessage: 'Ex "gxHVDcKUyP2y1eiyW8S8na1UYQAfq6J6"'
+ },
+ saving: {
+ id: 'admin.image.saving',
+ defaultMessage: 'Saving Config...'
+ }
+});
+
+class FileSettings extends React.Component {
constructor(props) {
super(props);
@@ -120,6 +189,7 @@ export default class FileSettings extends React.Component {
}
render() {
+ const {formatMessage} = this.props.intl;
var serverError = '';
if (this.state.serverError) {
serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>;
@@ -143,7 +213,12 @@ export default class FileSettings extends React.Component {
return (
<div className='wrapper--fixed'>
- <h3>{'File Settings'}</h3>
+ <h3>
+ <FormattedMessage
+ id='admin.image.fileSettings'
+ defaultMessage='File Settings'
+ />
+ </h3>
<form
className='form-horizontal'
role='form'
@@ -154,7 +229,10 @@ export default class FileSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='DriverName'
>
- {'Store Files In:'}
+ <FormattedMessage
+ id='admin.image.storeTitle'
+ defaultMessage='Store Files In:'
+ />
</label>
<div className='col-sm-8'>
<select
@@ -164,9 +242,9 @@ export default class FileSettings extends React.Component {
defaultValue={this.props.config.FileSettings.DriverName}
onChange={this.handleChange.bind(this, 'DriverName')}
>
- <option value=''>{'Disable File Storage'}</option>
- <option value='local'>{'Local File System'}</option>
- <option value='amazons3'>{'Amazon S3'}</option>
+ <option value=''>{formatMessage(holders.storeDisabled)}</option>
+ <option value='local'>{formatMessage(holders.storeLocal)}</option>
+ <option value='amazons3'>{formatMessage(holders.storeAmazonS3)}</option>
</select>
</div>
</div>
@@ -176,7 +254,10 @@ export default class FileSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='Directory'
>
- {'Local Directory Location:'}
+ <FormattedMessage
+ id='admin.image.localTitle'
+ defaultMessage='Local Directory Location:'
+ />
</label>
<div className='col-sm-8'>
<input
@@ -184,12 +265,17 @@ export default class FileSettings extends React.Component {
className='form-control'
id='Directory'
ref='Directory'
- placeholder='Ex "./data/"'
+ placeholder={formatMessage(holders.localExample)}
defaultValue={this.props.config.FileSettings.Directory}
onChange={this.handleChange}
disabled={!enableFile}
/>
- <p className='help-text'>{'Directory to which image files are written. If blank, will be set to ./data/.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.image.localDescription'
+ defaultMessage='Directory to which image files are written. If blank, will be set to ./data/.'
+ />
+ </p>
</div>
</div>
@@ -198,7 +284,10 @@ export default class FileSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='AmazonS3AccessKeyId'
>
- {'Amazon S3 Access Key Id:'}
+ <FormattedMessage
+ id='admin.image.amazonS3IdTitle'
+ defaultMessage='Amazon S3 Access Key Id:'
+ />
</label>
<div className='col-sm-8'>
<input
@@ -206,12 +295,17 @@ export default class FileSettings extends React.Component {
className='form-control'
id='AmazonS3AccessKeyId'
ref='AmazonS3AccessKeyId'
- placeholder='Ex "AKIADTOVBGERKLCBV"'
+ placeholder={formatMessage(holders.amazonS3IdExample)}
defaultValue={this.props.config.FileSettings.AmazonS3AccessKeyId}
onChange={this.handleChange}
disabled={!enableS3}
/>
- <p className='help-text'>{'Obtain this credential from your Amazon EC2 administrator.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.image.amazonS3IdDescription'
+ defaultMessage='Obtain this credential from your Amazon EC2 administrator.'
+ />
+ </p>
</div>
</div>
@@ -220,7 +314,10 @@ export default class FileSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='AmazonS3SecretAccessKey'
>
- {'Amazon S3 Secret Access Key:'}
+ <FormattedMessage
+ id='admin.image.amazonS3SecretTitle'
+ defaultMessage='Amazon S3 Secret Access Key:'
+ />
</label>
<div className='col-sm-8'>
<input
@@ -228,12 +325,17 @@ export default class FileSettings extends React.Component {
className='form-control'
id='AmazonS3SecretAccessKey'
ref='AmazonS3SecretAccessKey'
- placeholder='Ex "jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY"'
+ placeholder={formatMessage(holders.amazonS3SecretExample)}
defaultValue={this.props.config.FileSettings.AmazonS3SecretAccessKey}
onChange={this.handleChange}
disabled={!enableS3}
/>
- <p className='help-text'>{'Obtain this credential from your Amazon EC2 administrator.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.image.amazonS3SecretDescription'
+ defaultMessage='Obtain this credential from your Amazon EC2 administrator.'
+ />
+ </p>
</div>
</div>
@@ -242,7 +344,10 @@ export default class FileSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='AmazonS3Bucket'
>
- {'Amazon S3 Bucket:'}
+ <FormattedMessage
+ id='admin.image.amazonS3BucketTitle'
+ defaultMessage='Amazon S3 Bucket:'
+ />
</label>
<div className='col-sm-8'>
<input
@@ -250,12 +355,17 @@ export default class FileSettings extends React.Component {
className='form-control'
id='AmazonS3Bucket'
ref='AmazonS3Bucket'
- placeholder='Ex "mattermost-media"'
+ placeholder={formatMessage(holders.amazonS3BucketExample)}
defaultValue={this.props.config.FileSettings.AmazonS3Bucket}
onChange={this.handleChange}
disabled={!enableS3}
/>
- <p className='help-text'>{'Name you selected for your S3 bucket in AWS.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.image.amazonS3BucketDescription'
+ defaultMessage='Name you selected for your S3 bucket in AWS.'
+ />
+ </p>
</div>
</div>
@@ -264,7 +374,10 @@ export default class FileSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='AmazonS3Region'
>
- {'Amazon S3 Region:'}
+ <FormattedMessage
+ id='admin.image.amazonS3RegionTitle'
+ defaultMessage='Amazon S3 Region:'
+ />
</label>
<div className='col-sm-8'>
<input
@@ -272,12 +385,17 @@ export default class FileSettings extends React.Component {
className='form-control'
id='AmazonS3Region'
ref='AmazonS3Region'
- placeholder='Ex "us-east-1"'
+ placeholder={formatMessage(holders.amazonS3RegionExample)}
defaultValue={this.props.config.FileSettings.AmazonS3Region}
onChange={this.handleChange}
disabled={!enableS3}
/>
- <p className='help-text'>{'AWS region you selected for creating your S3 bucket.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.image.amazonS3RegionDescription'
+ defaultMessage='AWS region you selected for creating your S3 bucket.'
+ />
+ </p>
</div>
</div>
@@ -286,7 +404,10 @@ export default class FileSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='ThumbnailWidth'
>
- {'Thumbnail Width:'}
+ <FormattedMessage
+ id='admin.image.thumbWidthTitle'
+ defaultMessage='Thumbnail Width:'
+ />
</label>
<div className='col-sm-8'>
<input
@@ -294,11 +415,16 @@ export default class FileSettings extends React.Component {
className='form-control'
id='ThumbnailWidth'
ref='ThumbnailWidth'
- placeholder='Ex "120"'
+ placeholder={formatMessage(holders.thumbWidthExample)}
defaultValue={this.props.config.FileSettings.ThumbnailWidth}
onChange={this.handleChange}
/>
- <p className='help-text'>{'Width of thumbnails generated from uploaded images. Updating this value changes how thumbnail images render in future, but does not change images created in the past.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.image.thumbWidthDescription'
+ defaultMessage='Width of thumbnails generated from uploaded images. Updating this value changes how thumbnail images render in future, but does not change images created in the past.'
+ />
+ </p>
</div>
</div>
@@ -307,7 +433,10 @@ export default class FileSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='ThumbnailHeight'
>
- {'Thumbnail Height:'}
+ <FormattedMessage
+ id='admin.image.thumbHeightTitle'
+ defaultMessage='Thumbnail Height:'
+ />
</label>
<div className='col-sm-8'>
<input
@@ -315,11 +444,16 @@ export default class FileSettings extends React.Component {
className='form-control'
id='ThumbnailHeight'
ref='ThumbnailHeight'
- placeholder='Ex "100"'
+ placeholder={formatMessage(holders.thumbHeightExample)}
defaultValue={this.props.config.FileSettings.ThumbnailHeight}
onChange={this.handleChange}
/>
- <p className='help-text'>{'Height of thumbnails generated from uploaded images. Updating this value changes how thumbnail images render in future, but does not change images created in the past.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.image.thumbHeightDescription'
+ defaultMessage='Height of thumbnails generated from uploaded images. Updating this value changes how thumbnail images render in future, but does not change images created in the past.'
+ />
+ </p>
</div>
</div>
@@ -328,7 +462,10 @@ export default class FileSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='PreviewWidth'
>
- {'Preview Width:'}
+ <FormattedMessage
+ id='admin.image.previewWidthTitle'
+ defaultMessage='Preview Width:'
+ />
</label>
<div className='col-sm-8'>
<input
@@ -336,11 +473,16 @@ export default class FileSettings extends React.Component {
className='form-control'
id='PreviewWidth'
ref='PreviewWidth'
- placeholder='Ex "1024"'
+ placeholder={formatMessage(holders.previewWidthExample)}
defaultValue={this.props.config.FileSettings.PreviewWidth}
onChange={this.handleChange}
/>
- <p className='help-text'>{'Maximum width of preview image. Updating this value changes how preview images render in future, but does not change images created in the past.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.image.previewWidthDescription'
+ defaultMessage='Maximum width of preview image. Updating this value changes how preview images render in future, but does not change images created in the past.'
+ />
+ </p>
</div>
</div>
@@ -349,7 +491,10 @@ export default class FileSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='PreviewHeight'
>
- {'Preview Height:'}
+ <FormattedMessage
+ id='admin.image.previewHeightTitle'
+ defaultMessage='Preview Height:'
+ />
</label>
<div className='col-sm-8'>
<input
@@ -357,11 +502,16 @@ export default class FileSettings extends React.Component {
className='form-control'
id='PreviewHeight'
ref='PreviewHeight'
- placeholder='Ex "0"'
+ placeholder={formatMessage(holders.previewHeightExample)}
defaultValue={this.props.config.FileSettings.PreviewHeight}
onChange={this.handleChange}
/>
- <p className='help-text'>{'Maximum height of preview image ("0": Sets to auto-size). Updating this value changes how preview images render in future, but does not change images created in the past.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.image.previewHeightDescription'
+ defaultMessage='Maximum height of preview image ("0": Sets to auto-size). Updating this value changes how preview images render in future, but does not change images created in the past.'
+ />
+ </p>
</div>
</div>
@@ -370,7 +520,10 @@ export default class FileSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='ProfileWidth'
>
- {'Profile Width:'}
+ <FormattedMessage
+ id='admin.image.profileWidthTitle'
+ defaultMessage='Profile Width:'
+ />
</label>
<div className='col-sm-8'>
<input
@@ -378,11 +531,16 @@ export default class FileSettings extends React.Component {
className='form-control'
id='ProfileWidth'
ref='ProfileWidth'
- placeholder='Ex "1024"'
+ placeholder={formatMessage(holders.profileWidthExample)}
defaultValue={this.props.config.FileSettings.ProfileWidth}
onChange={this.handleChange}
/>
- <p className='help-text'>{'Width of profile picture.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.image.profileWidthDescription'
+ defaultMessage='Width of profile picture.'
+ />
+ </p>
</div>
</div>
@@ -391,7 +549,10 @@ export default class FileSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='ProfileHeight'
>
- {'Profile Height:'}
+ <FormattedMessage
+ id='admin.image.profileHeightTitle'
+ defaultMessage='Profile Height:'
+ />
</label>
<div className='col-sm-8'>
<input
@@ -399,11 +560,16 @@ export default class FileSettings extends React.Component {
className='form-control'
id='ProfileHeight'
ref='ProfileHeight'
- placeholder='Ex "0"'
+ placeholder={formatMessage(holders.profileHeightExample)}
defaultValue={this.props.config.FileSettings.ProfileHeight}
onChange={this.handleChange}
/>
- <p className='help-text'>{'Height of profile picture.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.image.profileHeightDescription'
+ defaultMessage='Height of profile picture.'
+ />
+ </p>
</div>
</div>
@@ -412,7 +578,10 @@ export default class FileSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='EnablePublicLink'
>
- {'Share Public File Link: '}
+ <FormattedMessage
+ id='admin.image.shareTitle'
+ defaultMessage='Share Public File Link: '
+ />
</label>
<div className='col-sm-8'>
<label className='radio-inline'>
@@ -424,7 +593,10 @@ export default class FileSettings extends React.Component {
defaultChecked={this.props.config.FileSettings.EnablePublicLink}
onChange={this.handleChange}
/>
- {'true'}
+ <FormattedMessage
+ id='admin.image.true'
+ defaultMessage='true'
+ />
</label>
<label className='radio-inline'>
<input
@@ -434,9 +606,17 @@ export default class FileSettings extends React.Component {
defaultChecked={!this.props.config.FileSettings.EnablePublicLink}
onChange={this.handleChange}
/>
- {'false'}
+ <FormattedMessage
+ id='admin.image.false'
+ defaultMessage='false'
+ />
</label>
- <p className='help-text'>{'Allow users to share public links to files and images.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.image.shareDescription'
+ defaultMessage='Allow users to share public links to files and images.'
+ />
+ </p>
</div>
</div>
@@ -445,7 +625,10 @@ export default class FileSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='PublicLinkSalt'
>
- {'Public Link Salt:'}
+ <FormattedMessage
+ id='admin.image.publicLinkTitle'
+ defaultMessage='Public Link Salt:'
+ />
</label>
<div className='col-sm-8'>
<input
@@ -453,17 +636,25 @@ export default class FileSettings extends React.Component {
className='form-control'
id='PublicLinkSalt'
ref='PublicLinkSalt'
- placeholder='Ex "gxHVDcKUyP2y1eiyW8S8na1UYQAfq6J6"'
+ placeholder={formatMessage(holders.publicLinkExample)}
defaultValue={this.props.config.FileSettings.PublicLinkSalt}
onChange={this.handleChange}
/>
- <p className='help-text'>{'32-character salt added to signing of public image links. Randomly generated on install. Click "Re-Generate" to create new salt.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.image.publicLinkDescription'
+ defaultMessage='32-character salt added to signing of public image links. Randomly generated on install. Click "Re-Generate" to create new salt.'
+ />
+ </p>
<div className='help-text'>
<button
className='btn btn-default'
onClick={this.handleGenerate}
>
- {'Re-Generate'}
+ <FormattedMessage
+ id='admin.image.regenerate'
+ defaultMessage='Re-Generate'
+ />
</button>
</div>
</div>
@@ -478,9 +669,12 @@ export default class FileSettings extends React.Component {
className={saveClass}
onClick={this.handleSubmit}
id='save-button'
- data-loading-text={'<span class=\'glyphicon glyphicon-refresh glyphicon-refresh-animate\'></span> Saving Config...'}
+ data-loading-text={'<span class=\'glyphicon glyphicon-refresh glyphicon-refresh-animate\'></span> ' + formatMessage(holders.saving)}
>
- {'Save'}
+ <FormattedMessage
+ id='admin.image.save'
+ defaultMessage='Save'
+ />
</button>
</div>
</div>
@@ -492,5 +686,8 @@ export default class FileSettings extends React.Component {
}
FileSettings.propTypes = {
+ intl: intlShape.isRequired,
config: React.PropTypes.object
};
+
+export default injectIntl(FileSettings); \ No newline at end of file
diff --git a/web/react/components/admin_console/ldap_settings.jsx b/web/react/components/admin_console/ldap_settings.jsx
index 1447f3bd7..bc13b3bcd 100644
--- a/web/react/components/admin_console/ldap_settings.jsx
+++ b/web/react/components/admin_console/ldap_settings.jsx
@@ -4,10 +4,55 @@
import * as Client from '../../utils/client.jsx';
import * as AsyncClient from '../../utils/async_client.jsx';
+import {injectIntl, intlShape, defineMessages, FormattedMessage, FormattedHTMLMessage} from 'mm-intl';
+
const DEFAULT_LDAP_PORT = 389;
const DEFAULT_QUERY_TIMEOUT = 60;
-export default class LdapSettings extends React.Component {
+var holders = defineMessages({
+ serverEx: {
+ id: 'admin.ldap.serverEx',
+ defaultMessage: 'Ex "10.0.0.23"'
+ },
+ portEx: {
+ id: 'admin.ldap.portEx',
+ defaultMessage: 'Ex "389"'
+ },
+ baseEx: {
+ id: 'admin.ldap.baseEx',
+ defaultMessage: 'Ex "dc=mydomain,dc=com"'
+ },
+ firstnameAttrEx: {
+ id: 'admin.ldap.firstnameAttrEx',
+ defaultMessage: 'Ex "givenName"'
+ },
+ lastnameAttrEx: {
+ id: 'admin.ldap.lastnameAttrEx',
+ defaultMessage: 'Ex "sn"'
+ },
+ emailAttrEx: {
+ id: 'admin.ldap.emailAttrEx',
+ defaultMessage: 'Ex "mail"'
+ },
+ usernameAttrEx: {
+ id: 'admin.ldap.usernameAttrEx',
+ defaultMessage: 'Ex "sAMAccountName"'
+ },
+ idAttrEx: {
+ id: 'admin.ldap.idAttrEx',
+ defaultMessage: 'Ex "sAMAccountName"'
+ },
+ queryEx: {
+ id: 'admin.ldap.queryEx',
+ defaultMessage: 'Ex "60"'
+ },
+ saving: {
+ id: 'admin.ldap.saving',
+ defaultMessage: 'Saving Config...'
+ }
+});
+
+class LdapSettings extends React.Component {
constructor(props) {
super(props);
@@ -80,6 +125,7 @@ export default class LdapSettings extends React.Component {
);
}
render() {
+ const {formatMessage} = this.props.intl;
let serverError = '';
if (this.state.serverError) {
serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>;
@@ -97,8 +143,18 @@ export default class LdapSettings extends React.Component {
bannerContent = (
<div className='banner'>
<div className='banner__content'>
- <h4 className='banner__heading'>{'Note:'}</h4>
- <p>{'If a user attribute changes on the LDAP server it will be updated the next time the user enters their credentials to log in to Mattermost. This includes if a user is made inactive or removed from an LDAP server. Synchronization with LDAP servers is planned in a future release.'}</p>
+ <h4 className='banner__heading'>
+ <FormattedMessage
+ id='admin.ldap.bannerHeading'
+ defaultMessage='Note:'
+ />
+ </h4>
+ <p>
+ <FormattedMessage
+ id='admin.ldap.bannerDesc'
+ defaultMessage='If a user attribute changes on the LDAP server it will be updated the next time the user enters their credentials to log in to Mattermost. This includes if a user is made inactive or removed from an LDAP server. Synchronization with LDAP servers is planned in a future release.'
+ />
+ </p>
</div>
</div>
);
@@ -106,17 +162,10 @@ export default class LdapSettings extends React.Component {
bannerContent = (
<div className='banner warning'>
<div className='banner__content'>
- <h4 className='banner__heading'>{'Note:'}</h4>
- <p>
- {'LDAP is an enterprise feature. Your current license does not support LDAP. Click '}
- <a
- href='http://mattermost.com'
- target='_blank'
- >
- {'here'}
- </a>
- {' for information and pricing on enterprise licenses.'}
- </p>
+ <FormattedHTMLMessage
+ id='admin.ldap.noLicense'
+ defaultMessage='<h4 className="banner__heading">Note:</h4><p>LDAP is an enterprise feature. Your current license does not support LDAP. Click <a href="http://mattermost.com"target="_blank">here</a> for information and pricing on enterprise licenses.</p>'
+ />
</div>
</div>
);
@@ -125,7 +174,12 @@ export default class LdapSettings extends React.Component {
return (
<div className='wrapper--fixed'>
{bannerContent}
- <h3>{'LDAP Settings'}</h3>
+ <h3>
+ <FormattedMessage
+ id='admin.ldap.title'
+ defaultMessage='LDAP Settings'
+ />
+ </h3>
<form
className='form-horizontal'
role='form'
@@ -135,7 +189,10 @@ export default class LdapSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='Enable'
>
- {'Enable Login With LDAP:'}
+ <FormattedMessage
+ id='admin.ldap.enableTitle'
+ defaultMessage='Enable Login With LDAP:'
+ />
</label>
<div className='col-sm-8'>
<label className='radio-inline'>
@@ -148,7 +205,10 @@ export default class LdapSettings extends React.Component {
onChange={this.handleEnable}
disabled={!licenseEnabled}
/>
- {'true'}
+ <FormattedMessage
+ id='admin.ldap.true'
+ defaultMessage='true'
+ />
</label>
<label className='radio-inline'>
<input
@@ -158,9 +218,17 @@ export default class LdapSettings extends React.Component {
defaultChecked={!this.props.config.LdapSettings.Enable}
onChange={this.handleDisable}
/>
- {'false'}
+ <FormattedMessage
+ id='admin.ldap.false'
+ defaultMessage='false'
+ />
</label>
- <p className='help-text'>{'When true, Mattermost allows login using LDAP'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.ldap.enableDesc'
+ defaultMessage='When true, Mattermost allows login using LDAP'
+ />
+ </p>
</div>
</div>
<div className='form-group'>
@@ -168,7 +236,10 @@ export default class LdapSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='LdapServer'
>
- {'LDAP Server:'}
+ <FormattedMessage
+ id='admin.ldap.serverTitle'
+ defaultMessage='LDAP Server:'
+ />
</label>
<div className='col-sm-8'>
<input
@@ -176,12 +247,17 @@ export default class LdapSettings extends React.Component {
className='form-control'
id='LdapServer'
ref='LdapServer'
- placeholder='Ex "10.0.0.23"'
+ placeholder={formatMessage(holders.serverEx)}
defaultValue={this.props.config.LdapSettings.LdapServer}
onChange={this.handleChange}
disabled={!this.state.enable}
/>
- <p className='help-text'>{'The domain or IP address of LDAP server.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.ldap.serverDesc'
+ defaultMessage='The domain or IP address of LDAP server.'
+ />
+ </p>
</div>
</div>
<div className='form-group'>
@@ -189,7 +265,10 @@ export default class LdapSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='LdapPort'
>
- {'LDAP Port:'}
+ <FormattedMessage
+ id='admin.ldap.portTitle'
+ defaultMessage='LDAP Port:'
+ />
</label>
<div className='col-sm-8'>
<input
@@ -197,12 +276,17 @@ export default class LdapSettings extends React.Component {
className='form-control'
id='LdapPort'
ref='LdapPort'
- placeholder='Ex "389"'
+ placeholder={formatMessage(holders.portEx)}
defaultValue={this.props.config.LdapSettings.LdapPort}
onChange={this.handleChange}
disabled={!this.state.enable}
/>
- <p className='help-text'>{'The port Mattermost will use to connect to the LDAP server. Default is 389.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.ldap.portDesc'
+ defaultMessage='The port Mattermost will use to connect to the LDAP server. Default is 389.'
+ />
+ </p>
</div>
</div>
<div className='form-group'>
@@ -210,7 +294,10 @@ export default class LdapSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='BaseDN'
>
- {'BaseDN:'}
+ <FormattedMessage
+ id='admin.ldap.baseTitle'
+ defaultMessage='BaseDN:'
+ />
</label>
<div className='col-sm-8'>
<input
@@ -218,12 +305,17 @@ export default class LdapSettings extends React.Component {
className='form-control'
id='BaseDN'
ref='BaseDN'
- placeholder='Ex "dc=mydomain,dc=com"'
+ placeholder={formatMessage(holders.baseEx)}
defaultValue={this.props.config.LdapSettings.BaseDN}
onChange={this.handleChange}
disabled={!this.state.enable}
/>
- <p className='help-text'>{'The Base DN is the Distinguished Name of the location where Mattermost should start its search for users in the LDAP tree.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.ldap.baseDesc'
+ defaultMessage='The Base DN is the Distinguished Name of the location where Mattermost should start its search for users in the LDAP tree.'
+ />
+ </p>
</div>
</div>
<div className='form-group'>
@@ -231,7 +323,10 @@ export default class LdapSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='BindUsername'
>
- {'Bind Username:'}
+ <FormattedMessage
+ id='admin.ldap.bindUserTitle'
+ defaultMessage='Bind Username:'
+ />
</label>
<div className='col-sm-8'>
<input
@@ -244,7 +339,12 @@ export default class LdapSettings extends React.Component {
onChange={this.handleChange}
disabled={!this.state.enable}
/>
- <p className='help-text'>{'The username used to perform the LDAP search. This should typically be an account created specifically for use with Mattermost. It should have access limited to read the portion of the LDAP tree specified in the BaseDN field.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.ldap.bindUserDesc'
+ defaultMessage='The username used to perform the LDAP search. This should typically be an account created specifically for use with Mattermost. It should have access limited to read the portion of the LDAP tree specified in the BaseDN field.'
+ />
+ </p>
</div>
</div>
<div className='form-group'>
@@ -252,7 +352,10 @@ export default class LdapSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='BindPassword'
>
- {'Bind Password:'}
+ <FormattedMessage
+ id='admin.ldap.bindPwdTitle'
+ defaultMessage='Bind Password:'
+ />
</label>
<div className='col-sm-8'>
<input
@@ -265,7 +368,12 @@ export default class LdapSettings extends React.Component {
onChange={this.handleChange}
disabled={!this.state.enable}
/>
- <p className='help-text'>{'Password of the user given in "Bind Username".'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.ldap.bindPwdDesc'
+ defaultMessage='Password of the user given in "Bind Username".'
+ />
+ </p>
</div>
</div>
<div className='form-group'>
@@ -273,7 +381,10 @@ export default class LdapSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='FirstNameAttribute'
>
- {'First Name Attrubute'}
+ <FormattedMessage
+ id='admin.ldap.firstnameAttrTitle'
+ defaultMessage='First Name Attrubute'
+ />
</label>
<div className='col-sm-8'>
<input
@@ -281,12 +392,17 @@ export default class LdapSettings extends React.Component {
className='form-control'
id='FirstNameAttribute'
ref='FirstNameAttribute'
- placeholder='Ex "givenName"'
+ placeholder={formatMessage(holders.firstnameAttrEx)}
defaultValue={this.props.config.LdapSettings.FirstNameAttribute}
onChange={this.handleChange}
disabled={!this.state.enable}
/>
- <p className='help-text'>{'The attribute in the LDAP server that will be used to populate the first name of users in Mattermost.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.ldap.firstnameAttrDesc'
+ defaultMessage='The attribute in the LDAP server that will be used to populate the first name of users in Mattermost.'
+ />
+ </p>
</div>
</div>
<div className='form-group'>
@@ -294,7 +410,10 @@ export default class LdapSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='LastNameAttribute'
>
- {'Last Name Attribute:'}
+ <FormattedMessage
+ id='admin.ldap.lastnameAttrTitle'
+ defaultMessage='Last Name Attribute:'
+ />
</label>
<div className='col-sm-8'>
<input
@@ -302,12 +421,17 @@ export default class LdapSettings extends React.Component {
className='form-control'
id='LastNameAttribute'
ref='LastNameAttribute'
- placeholder='Ex "sn"'
+ placeholder={formatMessage(holders.lastnameAttrEx)}
defaultValue={this.props.config.LdapSettings.LastNameAttribute}
onChange={this.handleChange}
disabled={!this.state.enable}
/>
- <p className='help-text'>{'The attribute in the LDAP server that will be used to populate the last name of users in Mattermost.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.ldap.lastnameAttrDesc'
+ defaultMessage='The attribute in the LDAP server that will be used to populate the last name of users in Mattermost.'
+ />
+ </p>
</div>
</div>
<div className='form-group'>
@@ -315,7 +439,10 @@ export default class LdapSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='EmailAttribute'
>
- {'Email Attribute:'}
+ <FormattedMessage
+ id='admin.ldap.emailAttrTitle'
+ defaultMessage='Email Attribute:'
+ />
</label>
<div className='col-sm-8'>
<input
@@ -323,12 +450,17 @@ export default class LdapSettings extends React.Component {
className='form-control'
id='EmailAttribute'
ref='EmailAttribute'
- placeholder='Ex "mail"'
+ placeholder={formatMessage(holders.emailAttrEx)}
defaultValue={this.props.config.LdapSettings.EmailAttribute}
onChange={this.handleChange}
disabled={!this.state.enable}
/>
- <p className='help-text'>{'The attribute in the LDAP server that will be used to populate the email addresses of users in Mattermost.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.ldap.emailAttrDesc'
+ defaultMessage='The attribute in the LDAP server that will be used to populate the email addresses of users in Mattermost.'
+ />
+ </p>
</div>
</div>
<div className='form-group'>
@@ -336,7 +468,10 @@ export default class LdapSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='UsernameAttribute'
>
- {'Username Attribute:'}
+ <FormattedMessage
+ id='admin.ldap.usernameAttrTitle'
+ defaultMessage='Username Attribute:'
+ />
</label>
<div className='col-sm-8'>
<input
@@ -344,12 +479,17 @@ export default class LdapSettings extends React.Component {
className='form-control'
id='UsernameAttribute'
ref='UsernameAttribute'
- placeholder='Ex "sAMAccountName"'
+ placeholder={formatMessage(holders.usernameAttrEx)}
defaultValue={this.props.config.LdapSettings.UsernameAttribute}
onChange={this.handleChange}
disabled={!this.state.enable}
/>
- <p className='help-text'>{'The attribute in the LDAP server that will be used to populate the username field in Mattermost. This may be the same as the ID Attribute.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.ldap.uernameAttrDesc'
+ defaultMessage='The attribute in the LDAP server that will be used to populate the username field in Mattermost. This may be the same as the ID Attribute.'
+ />
+ </p>
</div>
</div>
<div className='form-group'>
@@ -357,7 +497,10 @@ export default class LdapSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='IdAttribute'
>
- {'Id Attribute: '}
+ <FormattedMessage
+ id='admin.ldap.idAttrTitle'
+ defaultMessage='Id Attribute: '
+ />
</label>
<div className='col-sm-8'>
<input
@@ -365,12 +508,17 @@ export default class LdapSettings extends React.Component {
className='form-control'
id='IdAttribute'
ref='IdAttribute'
- placeholder='Ex "sAMAccountName"'
+ placeholder={formatMessage(holders.idAttrEx)}
defaultValue={this.props.config.LdapSettings.IdAttribute}
onChange={this.handleChange}
disabled={!this.state.enable}
/>
- <p className='help-text'>{'The attribute in the LDAP server that will be used as a unique identifier in Mattermost. It should be an LDAP attribute with a value that does not change, such as username or uid. If a user’s Id Attribute changes, it will create a new Mattermost account unassociated with their old one. This is the value used to log in to Mattermost in the "LDAP Username" field on the sign in page. Normally this attribute is the same as the “Username Attribute” field above. If your team typically uses domain\\username to sign in to other services with LDAP, you may choose to put domain\\username in this field to maintain consistency between sites.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.ldap.idAttrDesc'
+ defaultMessage='The attribute in the LDAP server that will be used as a unique identifier in Mattermost. It should be an LDAP attribute with a value that does not change, such as username or uid. If a user’s Id Attribute changes, it will create a new Mattermost account unassociated with their old one. This is the value used to log in to Mattermost in the "LDAP Username" field on the sign in page. Normally this attribute is the same as the “Username Attribute” field above. If your team typically uses domain\\username to sign in to other services with LDAP, you may choose to put domain\\username in this field to maintain consistency between sites.'
+ />
+ </p>
</div>
</div>
<div className='form-group'>
@@ -378,7 +526,10 @@ export default class LdapSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='QueryTimeout'
>
- {'Query Timeout (seconds):'}
+ <FormattedMessage
+ id='admin.ldap.queryTitle'
+ defaultMessage='Query Timeout (seconds):'
+ />
</label>
<div className='col-sm-8'>
<input
@@ -386,12 +537,17 @@ export default class LdapSettings extends React.Component {
className='form-control'
id='QueryTimeout'
ref='QueryTimeout'
- placeholder='Ex "60"'
+ placeholder={formatMessage(holders.queryEx)}
defaultValue={this.props.config.LdapSettings.QueryTimeout}
onChange={this.handleChange}
disabled={!this.state.enable}
/>
- <p className='help-text'>{'The timeout value for queries to the LDAP server. Increase if you are getting timeout errors caused by a slow LDAP server.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.ldap.queryDesc'
+ defaultMessage='The timeout value for queries to the LDAP server. Increase if you are getting timeout errors caused by a slow LDAP server.'
+ />
+ </p>
</div>
</div>
<div className='form-group'>
@@ -403,9 +559,12 @@ export default class LdapSettings extends React.Component {
className={saveClass}
onClick={this.handleSubmit}
id='save-button'
- data-loading-text={'<span class=\'glyphicon glyphicon-refresh glyphicon-refresh-animate\'></span> Saving Config...'}
+ data-loading-text={'<span class=\'glyphicon glyphicon-refresh glyphicon-refresh-animate\'></span> ' + formatMessage(holders.saving)}
>
- {'Save'}
+ <FormattedMessage
+ id='admin.ldap.save'
+ defaultMessage='Save'
+ />
</button>
</div>
</div>
@@ -418,5 +577,8 @@ LdapSettings.defaultProps = {
};
LdapSettings.propTypes = {
+ intl: intlShape.isRequired,
config: React.PropTypes.object
};
+
+export default injectIntl(LdapSettings); \ No newline at end of file
diff --git a/web/react/components/admin_console/legal_and_support_settings.jsx b/web/react/components/admin_console/legal_and_support_settings.jsx
index b00e4b6bd..a6c6a0626 100644
--- a/web/react/components/admin_console/legal_and_support_settings.jsx
+++ b/web/react/components/admin_console/legal_and_support_settings.jsx
@@ -4,7 +4,16 @@
import * as Client from '../../utils/client.jsx';
import * as AsyncClient from '../../utils/async_client.jsx';
-export default class LegalAndSupportSettings extends React.Component {
+import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'mm-intl';
+
+var holders = defineMessages({
+ saving: {
+ id: 'admin.support.saving',
+ defaultMessage: 'Saving Config...'
+ }
+});
+
+class LegalAndSupportSettings extends React.Component {
constructor(props) {
super(props);
@@ -69,7 +78,12 @@ export default class LegalAndSupportSettings extends React.Component {
return (
<div className='wrapper--fixed'>
- <h3>{'Legal and Support Settings'}</h3>
+ <h3>
+ <FormattedMessage
+ id='admin.support.title'
+ defaultMessage='Legal and Support Settings'
+ />
+ </h3>
<form
className='form-horizontal'
role='form'
@@ -80,7 +94,10 @@ export default class LegalAndSupportSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='TermsOfServiceLink'
>
- {'Terms of Service link:'}
+ <FormattedMessage
+ id='admin.support.termsTitle'
+ defaultMessage='Terms of Service link:'
+ />
</label>
<div className='col-sm-8'>
<input
@@ -91,7 +108,12 @@ export default class LegalAndSupportSettings extends React.Component {
defaultValue={this.props.config.SupportSettings.TermsOfServiceLink}
onChange={this.handleChange}
/>
- <p className='help-text'>{'Link to Terms of Service available to users on desktop and on mobile. Leaving this blank will hide the option to display a notice.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.support.termsDesc'
+ defaultMessage='Link to Terms of Service available to users on desktop and on mobile. Leaving this blank will hide the option to display a notice.'
+ />
+ </p>
</div>
</div>
@@ -100,7 +122,10 @@ export default class LegalAndSupportSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='PrivacyPolicyLink'
>
- {'Privacy Policy link:'}
+ <FormattedMessage
+ id='admin.support.privacyTitle'
+ defaultMessage='Privacy Policy link:'
+ />
</label>
<div className='col-sm-8'>
<input
@@ -111,7 +136,12 @@ export default class LegalAndSupportSettings extends React.Component {
defaultValue={this.props.config.SupportSettings.PrivacyPolicyLink}
onChange={this.handleChange}
/>
- <p className='help-text'>{'Link to Privacy Policy available to users on desktop and on mobile. Leaving this blank will hide the option to display a notice.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.support.privacyDesc'
+ defaultMessage='Link to Privacy Policy available to users on desktop and on mobile. Leaving this blank will hide the option to display a notice.'
+ />
+ </p>
</div>
</div>
@@ -120,7 +150,10 @@ export default class LegalAndSupportSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='AboutLink'
>
- {'About link:'}
+ <FormattedMessage
+ id='admin.support.aboutTitle'
+ defaultMessage='About link:'
+ />
</label>
<div className='col-sm-8'>
<input
@@ -131,7 +164,12 @@ export default class LegalAndSupportSettings extends React.Component {
defaultValue={this.props.config.SupportSettings.AboutLink}
onChange={this.handleChange}
/>
- <p className='help-text'>{'Link to About page for more information on your Mattermost deployment, for example its purpose and audience within your organization. Defaults to Mattermost information page.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.support.aboutDesc'
+ defaultMessage='Link to About page for more information on your Mattermost deployment, for example its purpose and audience within your organization. Defaults to Mattermost information page.'
+ />
+ </p>
</div>
</div>
@@ -140,7 +178,10 @@ export default class LegalAndSupportSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='HelpLink'
>
- {'Help link:'}
+ <FormattedMessage
+ id='admin.support.helpTitle'
+ defaultMessage='Help link:'
+ />
</label>
<div className='col-sm-8'>
<input
@@ -151,7 +192,12 @@ export default class LegalAndSupportSettings extends React.Component {
defaultValue={this.props.config.SupportSettings.HelpLink}
onChange={this.handleChange}
/>
- <p className='help-text'>{'Link to help documentation from team site main menu. Typically not changed unless your organization chooses to create custom documentation.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.support.helpDesc'
+ defaultMessage='Link to help documentation from team site main menu. Typically not changed unless your organization chooses to create custom documentation.'
+ />
+ </p>
</div>
</div>
@@ -160,7 +206,10 @@ export default class LegalAndSupportSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='ReportAProblemLink'
>
- {'Report a Problem link:'}
+ <FormattedMessage
+ id='admin.support.problemTitle'
+ defaultMessage='Report a Problem link:'
+ />
</label>
<div className='col-sm-8'>
<input
@@ -171,7 +220,12 @@ export default class LegalAndSupportSettings extends React.Component {
defaultValue={this.props.config.SupportSettings.ReportAProblemLink}
onChange={this.handleChange}
/>
- <p className='help-text'>{'Link to help documentation from team site main menu. By default this points to the peer-to-peer troubleshooting forum where users can search for, find and request help with technical issues.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.support.problemDesc'
+ defaultMessage='Link to help documentation from team site main menu. By default this points to the peer-to-peer troubleshooting forum where users can search for, find and request help with technical issues.'
+ />
+ </p>
</div>
</div>
@@ -180,7 +234,10 @@ export default class LegalAndSupportSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='SupportEmail'
>
- {'Support email:'}
+ <FormattedMessage
+ id='admin.support.emailTitle'
+ defaultMessage='Support email:'
+ />
</label>
<div className='col-sm-8'>
<input
@@ -191,7 +248,12 @@ export default class LegalAndSupportSettings extends React.Component {
defaultValue={this.props.config.SupportSettings.SupportEmail}
onChange={this.handleChange}
/>
- <p className='help-text'>{'Email shown during tutorial for end users to ask support questions.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.support.emailHelp'
+ defaultMessage='Email shown during tutorial for end users to ask support questions.'
+ />
+ </p>
</div>
</div>
@@ -204,9 +266,12 @@ export default class LegalAndSupportSettings extends React.Component {
className={saveClass}
onClick={this.handleSubmit}
id='save-button'
- data-loading-text={'<span class=\'glyphicon glyphicon-refresh glyphicon-refresh-animate\'></span> Saving Config...'}
+ data-loading-text={'<span class=\'glyphicon glyphicon-refresh glyphicon-refresh-animate\'></span> ' + this.props.intl.formatMessage(holders.saving)}
>
- {'Save'}
+ <FormattedMessage
+ id='admin.support.save'
+ defaultMessage='Save'
+ />
</button>
</div>
</div>
@@ -218,5 +283,8 @@ export default class LegalAndSupportSettings extends React.Component {
}
LegalAndSupportSettings.propTypes = {
+ intl: intlShape.isRequired,
config: React.PropTypes.object
};
+
+export default injectIntl(LegalAndSupportSettings); \ No newline at end of file
diff --git a/web/react/components/admin_console/license_settings.jsx b/web/react/components/admin_console/license_settings.jsx
index ba953f3bd..539acd869 100644
--- a/web/react/components/admin_console/license_settings.jsx
+++ b/web/react/components/admin_console/license_settings.jsx
@@ -4,7 +4,20 @@
import * as Utils from '../../utils/utils.jsx';
import * as Client from '../../utils/client.jsx';
-export default class LicenseSettings extends React.Component {
+import {injectIntl, intlShape, defineMessages, FormattedMessage, FormattedHTMLMessage} from 'mm-intl';
+
+const holders = defineMessages({
+ removing: {
+ id: 'admin.license.removing',
+ defaultMessage: 'Removing License...'
+ },
+ uploading: {
+ id: 'admin.license.uploading',
+ defaultMessage: 'Uploading License...'
+ }
+});
+
+class LicenseSettings extends React.Component {
constructor(props) {
super(props);
@@ -88,41 +101,26 @@ export default class LicenseSettings extends React.Component {
let licenseKey;
if (global.window.mm_license.IsLicensed === 'true') {
- edition = 'Mattermost Enterprise Edition. Designed for enterprise-scale communication.';
+ edition = (
+ <FormattedMessage
+ id='admin.license.enterpriseEdition'
+ defaultMessage='Mattermost Enterprise Edition. Designed for enterprise-scale communication.'
+ />
+ );
licenseType = (
- <div>
- <p>
- {'This compiled release of Mattermost platform is provided under a '}
- <a
- href='http://mattermost.com'
- target='_blank'
- >
- {'commercial license'}
- </a>
- {' from Mattermost, Inc. based on your subscription level and is subject to the '}
- <a
- href={global.window.mm_config.TermsOfServiceLink}
- target='_blank'
- >
- {'Terms of Service.'}
- </a>
- </p>
- <p>{'Your subscription details are as follows:'}</p>
- {'Name: ' + global.window.mm_license.Name}
- <br/>
- {'Company or organization name: ' + global.window.mm_license.Company}
- <br/>
- {'Number of users: ' + global.window.mm_license.Users}
- <br/>
- {`License issued: ${Utils.displayDate(parseInt(global.window.mm_license.IssuedAt, 10))} ${Utils.displayTime(parseInt(global.window.mm_license.IssuedAt, 10), true)}`}
- <br/>
- {'Start date of license: ' + Utils.displayDate(parseInt(global.window.mm_license.StartsAt, 10))}
- <br/>
- {'Expiry date of license: ' + Utils.displayDate(parseInt(global.window.mm_license.ExpiresAt, 10))}
- <br/>
- {'LDAP: ' + global.window.mm_license.LDAP}
- <br/>
- </div>
+ <FormattedHTMLMessage
+ id='admin.license.entrepriseType'
+ defaultMessage='<div><p>This compiled release of Mattermost platform is provided under a <a href="http://mattermost.com" target="_blank">commercial license</a>
+ from Mattermost, Inc. based on your subscription level and is subject to the <a href="{terms}" target="_blank">Terms of Service.</a></p>
+ <p>Your subscription details are as follows:</p>
+ Name: {name}<br />
+ Company or organization name: {company}<br/>
+ Number of users: {users}<br/>
+ License issued: {issued}<br/>
+ Start date of license: {start}<br/>
+ Expiry date of license: {expires}<br/>
+ LDAP: {ldap}<br/></div>'
+ />
);
licenseKey = (
@@ -131,32 +129,39 @@ export default class LicenseSettings extends React.Component {
className='btn btn-danger'
onClick={this.handleRemove}
id='remove-button'
- data-loading-text={'<span class=\'glyphicon glyphicon-refresh glyphicon-refresh-animate\'></span> Removing License...'}
+ data-loading-text={'<span class=\'glyphicon glyphicon-refresh glyphicon-refresh-animate\'></span> ' + this.props.intl.formatMessage(holders.removing)}
>
- {'Remove Enterprise License and Downgrade Server'}
+ <FormattedMessage
+ id='admin.license.keyRemove'
+ defaultMessage='Remove Enterprise License and Downgrade Server'
+ />
</button>
<br/>
<br/>
<p className='help-text'>
- {'If you’re migrating servers you may need to remove your license key from this server in order to install it on a new server. To start, '}
- <a
- href='http://mattermost.com'
- target='_blank'
- >
- {'disable all Enterprise Edition features on this server'}
- </a>
- {'. This will enable the ability to remove the license key and downgrade this server from Enterprise Edition to Team Edition.'}
+ <FormattedHTMLMessage
+ id='admin.licence.keyMigration'
+ defaultMessage='If you’re migrating servers you may need to remove your license key from this server in order to install it on a new server. To start,
+ <a href="http://mattermost.com" target="_blank">disable all Enterprise Edition features on this server</a>.
+ This will enable the ability to remove the license key and downgrade this server from Enterprise Edition to Team Edition.'
+ />
</p>
</div>
);
} else {
- edition = 'Mattermost Team Edition. Designed for teams from 5 to 50 users.';
+ edition = (
+ <FormattedMessage
+ id='admin.license.teamEdition'
+ defaultMessage='Mattermost Team Edition. Designed for teams from 5 to 50 users.'
+ />
+ );
licenseType = (
- <span>
- <p>{'This compiled release of Mattermost platform is offered under an MIT license.'}</p>
- <p>{'See MIT-COMPILED-LICENSE.txt in your root install directory for details. See NOTICES.txt for information about open source software used in this system.'}</p>
- </span>
+ <FormattedHTMLMessage
+ id='admin.license.teamType'
+ defaultMessage='<span><p>This compiled release of Mattermost platform is offered under an MIT license.</p>
+ <p>See MIT-COMPILED-LICENSE.txt in your root install directory for details. See NOTICES.txt for information about open source software used in this system.</p></span>'
+ />
);
licenseKey = (
@@ -173,23 +178,23 @@ export default class LicenseSettings extends React.Component {
disabled={!this.state.fileSelected}
onClick={this.handleSubmit}
id='upload-button'
- data-loading-text={'<span class=\'glyphicon glyphicon-refresh glyphicon-refresh-animate\'></span> Uploading License...'}
+ data-loading-text={'<span class=\'glyphicon glyphicon-refresh glyphicon-refresh-animate\'></span> ' + this.props.intl.formatMessage(holders.uploading)}
>
- {'Upload'}
+ <FormattedMessage
+ id='admin.license.upload'
+ defaultMessage='Upload'
+ />
</button>
<br/>
<br/>
<br/>
{serverError}
<p className='help-text'>
- {'Upload a license key for Mattermost Enterprise Edition to upgrade this server. '}
- <a
- href='http://mattermost.com'
- target='_blank'
- >
- {'Visit us online'}
- </a>
- {' to learn more about the benefits of Enterprise Edition or to purchase a key.'}
+ <FormattedHTMLMessage
+ id='admin.license.uploadDesc'
+ defaultMessage='Upload a license key for Mattermost Enterprise Edition to upgrade this server. <a href="http://mattermost.com" target="_blank">Visit us online</a>
+ to learn more about the benefits of Enterprise Edition or to purchase a key.'
+ />
</p>
</div>
);
@@ -197,7 +202,12 @@ export default class LicenseSettings extends React.Component {
return (
<div className='wrapper--fixed'>
- <h3>{'Edition and License'}</h3>
+ <h3>
+ <FormattedMessage
+ id='admin.license.title'
+ defaultMessage='Edition and License'
+ />
+ </h3>
<form
className='form-horizontal'
role='form'
@@ -206,7 +216,10 @@ export default class LicenseSettings extends React.Component {
<label
className='control-label col-sm-4'
>
- {'Edition: '}
+ <FormattedMessage
+ id='admin.license.edition'
+ defaultMessage='Edition: '
+ />
</label>
<div className='col-sm-8'>
{edition}
@@ -216,7 +229,10 @@ export default class LicenseSettings extends React.Component {
<label
className='control-label col-sm-4'
>
- {'License: '}
+ <FormattedMessage
+ id='admin.license.type'
+ defaultMessage='License: '
+ />
</label>
<div className='col-sm-8'>
{licenseType}
@@ -226,7 +242,10 @@ export default class LicenseSettings extends React.Component {
<label
className='control-label col-sm-4'
>
- {'License Key: '}
+ <FormattedMessage
+ id='admin.license.key'
+ defaultMessage='License Key: '
+ />
</label>
{licenseKey}
</div>
@@ -235,3 +254,9 @@ export default class LicenseSettings extends React.Component {
);
}
}
+
+LicenseSettings.propTypes = {
+ intl: intlShape.isRequired
+};
+
+export default injectIntl(LicenseSettings); \ No newline at end of file
diff --git a/web/react/components/admin_console/log_settings.jsx b/web/react/components/admin_console/log_settings.jsx
index a91cc57ab..cefe6afba 100644
--- a/web/react/components/admin_console/log_settings.jsx
+++ b/web/react/components/admin_console/log_settings.jsx
@@ -4,7 +4,24 @@
import * as Client from '../../utils/client.jsx';
import * as AsyncClient from '../../utils/async_client.jsx';
-export default class LogSettings extends React.Component {
+import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'mm-intl';
+
+const holders = defineMessages({
+ locationPlaceholder: {
+ id: 'admin.log.locationPlaceholder',
+ defaultMessage: 'Enter your file location'
+ },
+ formatPlaceholder: {
+ id: 'admin.log.formatPlaceholder',
+ defaultMessage: 'Enter your file format'
+ },
+ saving: {
+ id: 'admin.log.saving',
+ defaultMessage: 'Saving Config...'
+ }
+});
+
+class LogSettings extends React.Component {
constructor(props) {
super(props);
@@ -78,6 +95,7 @@ export default class LogSettings extends React.Component {
}
render() {
+ const {formatMessage} = this.props.intl;
var serverError = '';
if (this.state.serverError) {
serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>;
@@ -90,7 +108,12 @@ export default class LogSettings extends React.Component {
return (
<div className='wrapper--fixed'>
- <h3>{'Log Settings'}</h3>
+ <h3>
+ <FormattedMessage
+ id='admin.log.logSettings'
+ defaultMessage='Log Settings'
+ />
+ </h3>
<form
className='form-horizontal'
role='form'
@@ -101,7 +124,10 @@ export default class LogSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='consoleEnable'
>
- {'Log To The Console: '}
+ <FormattedMessage
+ id='admin.log.consoleTitle'
+ defaultMessage='Log To The Console: '
+ />
</label>
<div className='col-sm-8'>
<label className='radio-inline'>
@@ -113,7 +139,10 @@ export default class LogSettings extends React.Component {
defaultChecked={this.props.config.LogSettings.EnableConsole}
onChange={this.handleChange.bind(this, 'console_true')}
/>
- {'true'}
+ <FormattedMessage
+ id='admin.log.true'
+ defaultMessage='true'
+ />
</label>
<label className='radio-inline'>
<input
@@ -123,9 +152,17 @@ export default class LogSettings extends React.Component {
defaultChecked={!this.props.config.LogSettings.EnableConsole}
onChange={this.handleChange.bind(this, 'console_false')}
/>
- {'false'}
+ <FormattedMessage
+ id='admin.log.false'
+ defaultMessage='false'
+ />
</label>
- <p className='help-text'>{'Typically set to false in production. Developers may set this field to true to output log messages to console based on the console level option. If true, server writes messages to the standard output stream (stdout).'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.log.consoleDescription'
+ defaultMessage='Typically set to false in production. Developers may set this field to true to output log messages to console based on the console level option. If true, server writes messages to the standard output stream (stdout).'
+ />
+ </p>
</div>
</div>
@@ -134,7 +171,10 @@ export default class LogSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='consoleLevel'
>
- {'Console Log Level:'}
+ <FormattedMessage
+ id='admin.log.levelTitle'
+ defaultMessage='Console Log Level:'
+ />
</label>
<div className='col-sm-8'>
<select
@@ -149,7 +189,12 @@ export default class LogSettings extends React.Component {
<option value='INFO'>{'INFO'}</option>
<option value='ERROR'>{'ERROR'}</option>
</select>
- <p className='help-text'>{'This setting determines the level of detail at which log events are written to the console. ERROR: Outputs only error messages. INFO: Outputs error messages and information around startup and initialization. DEBUG: Prints high detail for developers working on debugging issues.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.log.levelDescription'
+ defaultMessage='This setting determines the level of detail at which log events are written to the console. ERROR: Outputs only error messages. INFO: Outputs error messages and information around startup and initialization. DEBUG: Prints high detail for developers working on debugging issues.'
+ />
+ </p>
</div>
</div>
@@ -157,7 +202,10 @@ export default class LogSettings extends React.Component {
<label
className='control-label col-sm-4'
>
- {'Log To File: '}
+ <FormattedMessage
+ id='admin.log.fileTitle'
+ defaultMessage='Log To File: '
+ />
</label>
<div className='col-sm-8'>
<label className='radio-inline'>
@@ -169,7 +217,10 @@ export default class LogSettings extends React.Component {
defaultChecked={this.props.config.LogSettings.EnableFile}
onChange={this.handleChange.bind(this, 'file_true')}
/>
- {'true'}
+ <FormattedMessage
+ id='admin.log.true'
+ defaultMessage='true'
+ />
</label>
<label className='radio-inline'>
<input
@@ -179,9 +230,17 @@ export default class LogSettings extends React.Component {
defaultChecked={!this.props.config.LogSettings.EnableFile}
onChange={this.handleChange.bind(this, 'file_false')}
/>
- {'false'}
+ <FormattedMessage
+ id='admin.log.false'
+ defaultMessage='false'
+ />
</label>
- <p className='help-text'>{'Typically set to true in production. When true, log files are written to the log file specified in file location field below.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.log.fileDescription'
+ defaultMessage='Typically set to true in production. When true, log files are written to the log file specified in file location field below.'
+ />
+ </p>
</div>
</div>
@@ -190,7 +249,10 @@ export default class LogSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='fileLevel'
>
- {'File Log Level:'}
+ <FormattedMessage
+ id='admin.log.fileLevelTitle'
+ defaultMessage='File Log Level:'
+ />
</label>
<div className='col-sm-8'>
<select
@@ -205,7 +267,12 @@ export default class LogSettings extends React.Component {
<option value='INFO'>{'INFO'}</option>
<option value='ERROR'>{'ERROR'}</option>
</select>
- <p className='help-text'>{'This setting determines the level of detail at which log events are written to the log file. ERROR: Outputs only error messages. INFO: Outputs error messages and information around startup and initialization. DEBUG: Prints high detail for developers working on debugging issues.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.log.fileLevelDescription'
+ defaultMessage='This setting determines the level of detail at which log events are written to the log file. ERROR: Outputs only error messages. INFO: Outputs error messages and information around startup and initialization. DEBUG: Prints high detail for developers working on debugging issues.'
+ />
+ </p>
</div>
</div>
@@ -214,7 +281,10 @@ export default class LogSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='fileLocation'
>
- {'File Location:'}
+ <FormattedMessage
+ id='admin.log.locationTitle'
+ defaultMessage='File Location:'
+ />
</label>
<div className='col-sm-8'>
<input
@@ -222,12 +292,17 @@ export default class LogSettings extends React.Component {
className='form-control'
id='fileLocation'
ref='fileLocation'
- placeholder='Enter your file location'
+ placeholder={formatMessage(holders.locationPlaceholder)}
defaultValue={this.props.config.LogSettings.FileLocation}
onChange={this.handleChange}
disabled={!this.state.fileEnable}
/>
- <p className='help-text'>{'File to which log files are written. If blank, will be set to ./logs/mattermost, which writes logs to mattermost.log. Log rotation is enabled and every 10,000 lines of log information is written to new files stored in the same directory, for example mattermost.2015-09-23.001, mattermost.2015-09-23.002, and so forth.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.log.locationDescription'
+ defaultMessage='File to which log files are written. If blank, will be set to ./logs/mattermost, which writes logs to mattermost.log. Log rotation is enabled and every 10,000 lines of log information is written to new files stored in the same directory, for example mattermost.2015-09-23.001, mattermost.2015-09-23.002, and so forth.'
+ />
+ </p>
</div>
</div>
@@ -236,7 +311,10 @@ export default class LogSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='fileFormat'
>
- {'File Format:'}
+ <FormattedMessage
+ id='admin.log.formatTitle'
+ defaultMessage='File Format:'
+ />
</label>
<div className='col-sm-8'>
<input
@@ -244,25 +322,58 @@ export default class LogSettings extends React.Component {
className='form-control'
id='fileFormat'
ref='fileFormat'
- placeholder='Enter your file format'
+ placeholder={formatMessage(holders.formatPlaceholder)}
defaultValue={this.props.config.LogSettings.FileFormat}
onChange={this.handleChange}
disabled={!this.state.fileEnable}
/>
<div className='help-text'>
- {'Format of log message output. If blank will be set to "[%D %T] [%L] %M", where:'}
+ <FormattedMessage
+ id='admin.log.formatDescription'
+ defaultMessage='Format of log message output. If blank will be set to "[%D %T] [%L] %M", where:'
+ />
<div className='help-text'>
<table
className='table table-bordered'
cellPadding='5'
>
<tbody>
- <tr><td className='help-text'>{'%T'}</td><td className='help-text'>{'Time (15:04:05 MST)'}</td></tr>
- <tr><td className='help-text'>{'%D'}</td><td className='help-text'>{'Date (2006/01/02)'}</td></tr>
- <tr><td className='help-text'>{'%d'}</td><td className='help-text'>{'Date (01/02/06)'}</td></tr>
- <tr><td className='help-text'>{'%L'}</td><td className='help-text'>{'Level (DEBG, INFO, EROR)'}</td></tr>
- <tr><td className='help-text'>{'%S'}</td><td className='help-text'>{'Source'}</td></tr>
- <tr><td className='help-text'>{'%M'}</td><td className='help-text'>{'Message'}</td></tr>
+ <tr><td className='help-text'>{'%T'}</td><td className='help-text'>
+ <FormattedMessage
+ id='admin.log.formatTime'
+ defaultMessage='Time (15:04:05 MST)'
+ />
+ </td></tr>
+ <tr><td className='help-text'>{'%D'}</td><td className='help-text'>
+ <FormattedMessage
+ id='admin.log.formatDateLong'
+ defaultMessage='Date (2006/01/02)'
+ />
+ </td></tr>
+ <tr><td className='help-text'>{'%d'}</td><td className='help-text'>
+ <FormattedMessage
+ id='admin.log.formatDateShort'
+ defaultMessage='Date (01/02/06)'
+ />
+ </td></tr>
+ <tr><td className='help-text'>{'%L'}</td><td className='help-text'>
+ <FormattedMessage
+ id='admin.log.formatLevel'
+ defaultMessage='Level (DEBG, INFO, EROR)'
+ />
+ </td></tr>
+ <tr><td className='help-text'>{'%S'}</td><td className='help-text'>
+ <FormattedMessage
+ id='admin.log.formatSource'
+ defaultMessage='Source'
+ />
+ </td></tr>
+ <tr><td className='help-text'>{'%M'}</td><td className='help-text'>
+ <FormattedMessage
+ id='admin.log.formatMessage'
+ defaultMessage='Message'
+ />
+ </td></tr>
</tbody>
</table>
</div>
@@ -279,9 +390,12 @@ export default class LogSettings extends React.Component {
className={saveClass}
onClick={this.handleSubmit}
id='save-button'
- data-loading-text={'<span class=\'glyphicon glyphicon-refresh glyphicon-refresh-animate\'></span> Saving Config...'}
+ data-loading-text={'<span class=\'glyphicon glyphicon-refresh glyphicon-refresh-animate\'></span> ' + formatMessage(holders.saving)}
>
- {'Save'}
+ <FormattedMessage
+ id='admin.log.save'
+ defaultMessage='Save'
+ />
</button>
</div>
</div>
@@ -293,5 +407,8 @@ export default class LogSettings extends React.Component {
}
LogSettings.propTypes = {
+ intl: intlShape.isRequired,
config: React.PropTypes.object
};
+
+export default injectIntl(LogSettings); \ No newline at end of file
diff --git a/web/react/components/admin_console/logs.jsx b/web/react/components/admin_console/logs.jsx
index 01135f1b8..71a4a5d8c 100644
--- a/web/react/components/admin_console/logs.jsx
+++ b/web/react/components/admin_console/logs.jsx
@@ -5,6 +5,8 @@ import AdminStore from '../../stores/admin_store.jsx';
import LoadingScreen from '../loading_screen.jsx';
import * as AsyncClient from '../../utils/async_client.jsx';
+import {FormattedMessage} from 'mm-intl';
+
export default class Logs extends React.Component {
constructor(props) {
super(props);
@@ -73,13 +75,21 @@ export default class Logs extends React.Component {
return (
<div className='panel'>
- <h3>{'Server Logs'}</h3>
+ <h3>
+ <FormattedMessage
+ id='admin.logs.title'
+ defaultMessage='Server Logs'
+ />
+ </h3>
<button
type='submit'
className='btn btn-primary'
onClick={this.reload}
>
- {'Reload'}
+ <FormattedMessage
+ id='admin.logs.reload'
+ defaultMessage='Reload'
+ />
</button>
<div className='log__panel'>
{content}
diff --git a/web/react/components/admin_console/privacy_settings.jsx b/web/react/components/admin_console/privacy_settings.jsx
index 78747d9f2..1ab625049 100644
--- a/web/react/components/admin_console/privacy_settings.jsx
+++ b/web/react/components/admin_console/privacy_settings.jsx
@@ -4,7 +4,16 @@
import * as Client from '../../utils/client.jsx';
import * as AsyncClient from '../../utils/async_client.jsx';
-export default class PrivacySettings extends React.Component {
+import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'mm-intl';
+
+const holders = defineMessages({
+ saving: {
+ id: 'admin.privacy.saving',
+ defaultMessage: 'Saving Config...'
+ }
+});
+
+class PrivacySettings extends React.Component {
constructor(props) {
super(props);
@@ -64,7 +73,12 @@ export default class PrivacySettings extends React.Component {
return (
<div className='wrapper--fixed'>
- <h3>{'Privacy Settings'}</h3>
+ <h3>
+ <FormattedMessage
+ id='admin.privacy.title'
+ defaultMessage='Privacy Settings'
+ />
+ </h3>
<form
className='form-horizontal'
role='form'
@@ -75,7 +89,10 @@ export default class PrivacySettings extends React.Component {
className='control-label col-sm-4'
htmlFor='ShowEmailAddress'
>
- {'Show Email Address: '}
+ <FormattedMessage
+ id='admin.privacy.showEmailTitle'
+ defaultMessage='Show Email Address: '
+ />
</label>
<div className='col-sm-8'>
<label className='radio-inline'>
@@ -87,7 +104,10 @@ export default class PrivacySettings extends React.Component {
defaultChecked={this.props.config.PrivacySettings.ShowEmailAddress}
onChange={this.handleChange}
/>
- {'true'}
+ <FormattedMessage
+ id='admin.privacy.true'
+ defaultMessage='true'
+ />
</label>
<label className='radio-inline'>
<input
@@ -97,9 +117,17 @@ export default class PrivacySettings extends React.Component {
defaultChecked={!this.props.config.PrivacySettings.ShowEmailAddress}
onChange={this.handleChange}
/>
- {'false'}
+ <FormattedMessage
+ id='admin.privacy.false'
+ defaultMessage='false'
+ />
</label>
- <p className='help-text'>{'When false, hides email address of users from other users in the user interface, including team owners and team administrators. Used when system is set up for managing teams where some users choose to keep their contact information private.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.privacy.showEmailDescription'
+ defaultMessage='When false, hides email address of users from other users in the user interface, including team owners and team administrators. Used when system is set up for managing teams where some users choose to keep their contact information private.'
+ />
+ </p>
</div>
</div>
@@ -108,7 +136,10 @@ export default class PrivacySettings extends React.Component {
className='control-label col-sm-4'
htmlFor='ShowFullName'
>
- {'Show Full Name: '}
+ <FormattedMessage
+ id='admin.privacy.showFullNameTitle'
+ defaultMessage='Show Full Name: '
+ />
</label>
<div className='col-sm-8'>
<label className='radio-inline'>
@@ -120,7 +151,10 @@ export default class PrivacySettings extends React.Component {
defaultChecked={this.props.config.PrivacySettings.ShowFullName}
onChange={this.handleChange}
/>
- {'true'}
+ <FormattedMessage
+ id='admin.privacy.true'
+ defaultMessage='true'
+ />
</label>
<label className='radio-inline'>
<input
@@ -130,9 +164,17 @@ export default class PrivacySettings extends React.Component {
defaultChecked={!this.props.config.PrivacySettings.ShowFullName}
onChange={this.handleChange}
/>
- {'false'}
+ <FormattedMessage
+ id='admin.privacy.false'
+ defaultMessage='false'
+ />
</label>
- <p className='help-text'>{'When false, hides full name of users from other users, including team owners and team administrators. Username is shown in place of full name.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.privacy.showFullNameDescription'
+ defaultMessage='When false, hides full name of users from other users, including team owners and team administrators. Username is shown in place of full name.'
+ />
+ </p>
</div>
</div>
@@ -145,9 +187,12 @@ export default class PrivacySettings extends React.Component {
className={saveClass}
onClick={this.handleSubmit}
id='save-button'
- data-loading-text={'<span class=\'glyphicon glyphicon-refresh glyphicon-refresh-animate\'></span> Saving Config...'}
+ data-loading-text={'<span class=\'glyphicon glyphicon-refresh glyphicon-refresh-animate\'></span> ' + this.props.intl.formatMessage(holders.saving)}
>
- {'Save'}
+ <FormattedMessage
+ id='admin.privacy.save'
+ defaultMessage='Save'
+ />
</button>
</div>
</div>
@@ -159,5 +204,8 @@ export default class PrivacySettings extends React.Component {
}
PrivacySettings.propTypes = {
+ intl: intlShape.isRequired,
config: React.PropTypes.object
};
+
+export default injectIntl(PrivacySettings); \ No newline at end of file
diff --git a/web/react/components/admin_console/rate_settings.jsx b/web/react/components/admin_console/rate_settings.jsx
index aabb24326..d3c1bffa2 100644
--- a/web/react/components/admin_console/rate_settings.jsx
+++ b/web/react/components/admin_console/rate_settings.jsx
@@ -4,7 +4,28 @@
import * as Client from '../../utils/client.jsx';
import * as AsyncClient from '../../utils/async_client.jsx';
-export default class RateSettings extends React.Component {
+import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'mm-intl';
+
+const holders = defineMessages({
+ queriesExample: {
+ id: 'admin.rate.queriesExample',
+ defaultMessage: 'Ex "10"'
+ },
+ memoryExample: {
+ id: 'admin.rate.memoryExample',
+ defaultMessage: 'Ex "10000"'
+ },
+ httpHeaderExample: {
+ id: 'admin.rate.httpHeaderExample',
+ defaultMessage: 'Ex "X-Real-IP", "X-Forwarded-For"'
+ },
+ saving: {
+ id: 'admin.rate.saving',
+ defaultMessage: 'Saving Config...'
+ }
+});
+
+class RateSettings extends React.Component {
constructor(props) {
super(props);
@@ -85,6 +106,7 @@ export default class RateSettings extends React.Component {
}
render() {
+ const {formatMessage} = this.props.intl;
var serverError = '';
if (this.state.serverError) {
serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>;
@@ -100,12 +122,27 @@ export default class RateSettings extends React.Component {
<div className='banner'>
<div className='banner__content'>
- <h4 className='banner__heading'>{'Note:'}</h4>
- <p>{'Changing properties in this section will require a server restart before taking effect.'}</p>
+ <h4 className='banner__heading'>
+ <FormattedMessage
+ id='admin.rate.noteTitle'
+ defaultMessage='Note:'
+ />
+ </h4>
+ <p>
+ <FormattedMessage
+ id='admin.rate.noteDescription'
+ defaultMessage='Changing properties in this section will require a server restart before taking effect.'
+ />
+ </p>
</div>
</div>
- <h3>{'Rate Limit Settings'}</h3>
+ <h3>
+ <FormattedMessage
+ id='admin.rate.title'
+ defaultMessage='Rate Limit Settings'
+ />
+ </h3>
<form
className='form-horizontal'
role='form'
@@ -116,7 +153,10 @@ export default class RateSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='EnableRateLimiter'
>
- {'Enable Rate Limiter: '}
+ <FormattedMessage
+ id='admin.rate.enableLimiterTitle'
+ defaultMessage='Enable Rate Limiter: '
+ />
</label>
<div className='col-sm-8'>
<label className='radio-inline'>
@@ -128,7 +168,10 @@ export default class RateSettings extends React.Component {
defaultChecked={this.props.config.RateLimitSettings.EnableRateLimiter}
onChange={this.handleChange.bind(this, 'EnableRateLimiterTrue')}
/>
- {'true'}
+ <FormattedMessage
+ id='admin.rate.true'
+ defaultMessage='true'
+ />
</label>
<label className='radio-inline'>
<input
@@ -138,9 +181,17 @@ export default class RateSettings extends React.Component {
defaultChecked={!this.props.config.RateLimitSettings.EnableRateLimiter}
onChange={this.handleChange.bind(this, 'EnableRateLimiterFalse')}
/>
- {'false'}
+ <FormattedMessage
+ id='admin.rate.false'
+ defaultMessage='false'
+ />
</label>
- <p className='help-text'>{'When true, APIs are throttled at rates specified below.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.rate.enableLimiterDescription'
+ defaultMessage='When true, APIs are throttled at rates specified below.'
+ />
+ </p>
</div>
</div>
@@ -149,7 +200,10 @@ export default class RateSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='PerSec'
>
- {'Number Of Queries Per Second:'}
+ <FormattedMessage
+ id='admin.rate.queriesTitle'
+ defaultMessage='Number Of Queries Per Second:'
+ />
</label>
<div className='col-sm-8'>
<input
@@ -157,12 +211,17 @@ export default class RateSettings extends React.Component {
className='form-control'
id='PerSec'
ref='PerSec'
- placeholder='Ex "10"'
+ placeholder={formatMessage(holders.queriesExample)}
defaultValue={this.props.config.RateLimitSettings.PerSec}
onChange={this.handleChange}
disabled={!this.state.EnableRateLimiter}
/>
- <p className='help-text'>{'Throttles API at this number of requests per second.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.rate.queriesDescription'
+ defaultMessage='Throttles API at this number of requests per second.'
+ />
+ </p>
</div>
</div>
@@ -171,7 +230,10 @@ export default class RateSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='MemoryStoreSize'
>
- {'Memory Store Size:'}
+ <FormattedMessage
+ id='admin.rate.memoryTitle'
+ defaultMessage='Memory Store Size:'
+ />
</label>
<div className='col-sm-8'>
<input
@@ -179,12 +241,17 @@ export default class RateSettings extends React.Component {
className='form-control'
id='MemoryStoreSize'
ref='MemoryStoreSize'
- placeholder='Ex "10000"'
+ placeholder={formatMessage(holders.memoryExample)}
defaultValue={this.props.config.RateLimitSettings.MemoryStoreSize}
onChange={this.handleChange}
disabled={!this.state.EnableRateLimiter}
/>
- <p className='help-text'>{'Maximum number of users sessions connected to the system as determined by "Vary By Remote Address" and "Vary By Header" settings below.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.rate.memoryDescription'
+ defaultMessage='Maximum number of users sessions connected to the system as determined by "Vary By Remote Address" and "Vary By Header" settings below.'
+ />
+ </p>
</div>
</div>
@@ -193,7 +260,10 @@ export default class RateSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='VaryByRemoteAddr'
>
- {'Vary By Remote Address: '}
+ <FormattedMessage
+ id='admin.rate.remoteTitle'
+ defaultMessage='Vary By Remote Address: '
+ />
</label>
<div className='col-sm-8'>
<label className='radio-inline'>
@@ -206,7 +276,10 @@ export default class RateSettings extends React.Component {
onChange={this.handleChange.bind(this, 'VaryByRemoteAddrTrue')}
disabled={!this.state.EnableRateLimiter}
/>
- {'true'}
+ <FormattedMessage
+ id='admin.rate.true'
+ defaultMessage='true'
+ />
</label>
<label className='radio-inline'>
<input
@@ -217,9 +290,17 @@ export default class RateSettings extends React.Component {
onChange={this.handleChange.bind(this, 'VaryByRemoteAddrFalse')}
disabled={!this.state.EnableRateLimiter}
/>
- {'false'}
+ <FormattedMessage
+ id='admin.rate.false'
+ defaultMessage='false'
+ />
</label>
- <p className='help-text'>{'When true, rate limit API access by IP address.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.rate.remoteDescription'
+ defaultMessage='When true, rate limit API access by IP address.'
+ />
+ </p>
</div>
</div>
@@ -228,7 +309,10 @@ export default class RateSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='VaryByHeader'
>
- {'Vary By HTTP Header:'}
+ <FormattedMessage
+ id='admin.rate.httpHeaderTitle'
+ defaultMessage='Vary By HTTP Header:'
+ />
</label>
<div className='col-sm-8'>
<input
@@ -236,12 +320,17 @@ export default class RateSettings extends React.Component {
className='form-control'
id='VaryByHeader'
ref='VaryByHeader'
- placeholder='Ex "X-Real-IP", "X-Forwarded-For"'
+ placeholder={formatMessage(holders.httpHeaderExample)}
defaultValue={this.props.config.RateLimitSettings.VaryByHeader}
onChange={this.handleChange}
disabled={!this.state.EnableRateLimiter || this.state.VaryByRemoteAddr}
/>
- <p className='help-text'>{'When filled in, vary rate limiting by HTTP header field specified (e.g. when configuring NGINX set to "X-Real-IP", when configuring AmazonELB set to "X-Forwarded-For").'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.rate.httpHeaderDescription'
+ defaultMessage='When filled in, vary rate limiting by HTTP header field specified (e.g. when configuring NGINX set to "X-Real-IP", when configuring AmazonELB set to "X-Forwarded-For").'
+ />
+ </p>
</div>
</div>
@@ -254,9 +343,12 @@ export default class RateSettings extends React.Component {
className={saveClass}
onClick={this.handleSubmit}
id='save-button'
- data-loading-text={'<span class=\'glyphicon glyphicon-refresh glyphicon-refresh-animate\'></span> Saving Config...'}
+ data-loading-text={'<span class=\'glyphicon glyphicon-refresh glyphicon-refresh-animate\'></span> ' + formatMessage(holders.saving)}
>
- {'Save'}
+ <FormattedMessage
+ id='admin.rate.save'
+ defaultMessage='Save'
+ />
</button>
</div>
</div>
@@ -268,5 +360,8 @@ export default class RateSettings extends React.Component {
}
RateSettings.propTypes = {
+ intl: intlShape.isRequired,
config: React.PropTypes.object
};
+
+export default injectIntl(RateSettings); \ No newline at end of file
diff --git a/web/react/components/admin_console/reset_password_modal.jsx b/web/react/components/admin_console/reset_password_modal.jsx
index bf7d5f7e5..8ed519ffb 100644
--- a/web/react/components/admin_console/reset_password_modal.jsx
+++ b/web/react/components/admin_console/reset_password_modal.jsx
@@ -5,7 +5,16 @@ import * as Client from '../../utils/client.jsx';
import Constants from '../../utils/constants.jsx';
var Modal = ReactBootstrap.Modal;
-export default class ResetPasswordModal extends React.Component {
+import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'mm-intl';
+
+var holders = defineMessages({
+ submit: {
+ id: 'admin.reset_password.submit',
+ defaultMessage: 'Please enter at least {chars} characters.'
+ }
+});
+
+class ResetPasswordModal extends React.Component {
constructor(props) {
super(props);
@@ -22,7 +31,7 @@ export default class ResetPasswordModal extends React.Component {
var password = ReactDOM.findDOMNode(this.refs.password).value;
if (!password || password.length < Constants.MIN_PASSWORD_LENGTH) {
- this.setState({serverError: 'Please enter at least ' + Constants.MIN_PASSWORD_LENGTH + ' characters.'});
+ this.setState({serverError: this.props.intl.formatMessage(holders.submit, {chars: Constants.MIN_PASSWORD_LENGTH})});
return;
}
@@ -67,7 +76,12 @@ export default class ResetPasswordModal extends React.Component {
onHide={this.doCancel}
>
<Modal.Header closeButton={true}>
- <Modal.Title>{'Reset Password'}</Modal.Title>
+ <Modal.Title>
+ <FormattedMessage
+ id='admin.reset_password.title'
+ defaultMessage='Reset Password'
+ />
+ </Modal.Title>
</Modal.Header>
<form
role='form'
@@ -82,7 +96,10 @@ export default class ResetPasswordModal extends React.Component {
title='New Password'
className='input-group-addon'
>
- {'New Password'}
+ <FormattedMessage
+ id='admin.reset_password.newPassword'
+ defaultMessage='New Password'
+ />
</span>
<input
type='password'
@@ -103,7 +120,10 @@ export default class ResetPasswordModal extends React.Component {
className='btn btn-default'
onClick={this.doCancel}
>
- {'Close'}
+ <FormattedMessage
+ id='admin.reset_password.close'
+ defaultMessage='Close'
+ />
</button>
<button
onClick={this.doSubmit}
@@ -111,7 +131,10 @@ export default class ResetPasswordModal extends React.Component {
className='btn btn-primary'
tabIndex='2'
>
- {'Select'}
+ <FormattedMessage
+ id='admin.reset_password.select'
+ defaultMessage='Select'
+ />
</button>
</Modal.Footer>
</form>
@@ -125,9 +148,12 @@ ResetPasswordModal.defaultProps = {
};
ResetPasswordModal.propTypes = {
+ intl: intlShape.isRequired,
user: React.PropTypes.object,
team: React.PropTypes.object,
show: React.PropTypes.bool.isRequired,
onModalSubmit: React.PropTypes.func,
onModalDismissed: React.PropTypes.func
};
+
+export default injectIntl(ResetPasswordModal); \ No newline at end of file
diff --git a/web/react/components/admin_console/select_team_modal.jsx b/web/react/components/admin_console/select_team_modal.jsx
index 858b6bbfe..e0d070b28 100644
--- a/web/react/components/admin_console/select_team_modal.jsx
+++ b/web/react/components/admin_console/select_team_modal.jsx
@@ -1,6 +1,8 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
+import {FormattedMessage} from 'mm-intl';
+
var Modal = ReactBootstrap.Modal;
export default class SelectTeamModal extends React.Component {
@@ -45,7 +47,12 @@ export default class SelectTeamModal extends React.Component {
onHide={this.doCancel}
>
<Modal.Header closeButton={true}>
- <Modal.Title>{'Select Team'}</Modal.Title>
+ <Modal.Title>
+ <FormattedMessage
+ id='admin.select_team.selectTeam'
+ defaultMessage='Select Team'
+ />
+ </Modal.Title>
</Modal.Header>
<form
role='form'
@@ -70,7 +77,10 @@ export default class SelectTeamModal extends React.Component {
className='btn btn-default'
onClick={this.doCancel}
>
- {'Close'}
+ <FormattedMessage
+ id='admin.select_team.close'
+ defaultMessage='Close'
+ />
</button>
<button
onClick={this.doSubmit}
@@ -78,7 +88,10 @@ export default class SelectTeamModal extends React.Component {
className='btn btn-primary'
tabIndex='2'
>
- {'Select'}
+ <FormattedMessage
+ id='admin.select_team.select'
+ defaultMessage='Select'
+ />
</button>
</Modal.Footer>
</form>
@@ -96,4 +109,4 @@ SelectTeamModal.propTypes = {
show: React.PropTypes.bool.isRequired,
onModalSubmit: React.PropTypes.func,
onModalDismissed: React.PropTypes.func
-};
+}; \ No newline at end of file
diff --git a/web/react/components/admin_console/service_settings.jsx b/web/react/components/admin_console/service_settings.jsx
index f10721ffa..7021900eb 100644
--- a/web/react/components/admin_console/service_settings.jsx
+++ b/web/react/components/admin_console/service_settings.jsx
@@ -4,11 +4,40 @@
import * as Client from '../../utils/client.jsx';
import * as AsyncClient from '../../utils/async_client.jsx';
+import {injectIntl, intlShape, defineMessages, FormattedMessage, FormattedHTMLMessage} from 'mm-intl';
+
const DefaultSessionLength = 30;
const DefaultMaximumLoginAttempts = 10;
const DefaultSessionCacheInMinutes = 10;
-export default class ServiceSettings extends React.Component {
+var holders = defineMessages({
+ listenExample: {
+ id: 'admin.service.listenExample',
+ defaultMessage: 'Ex ":8065"'
+ },
+ attemptExample: {
+ id: 'admin.service.attemptExample',
+ defaultMessage: 'Ex "10"'
+ },
+ segmentExample: {
+ id: 'admin.service.segmentExample',
+ defaultMessage: 'Ex "g3fgGOXJAQ43QV7rAh6iwQCkV4cA1Gs"'
+ },
+ googleExample: {
+ id: 'admin.service.googleExample',
+ defaultMessage: 'Ex "7rAh6iwQCkV4cA1Gsg3fgGOXJAQ43QV"'
+ },
+ sessionDaysEx: {
+ id: 'admin.service.sessionDaysEx',
+ defaultMessage: 'Ex "30"'
+ },
+ saving: {
+ id: 'admin.service.saving',
+ defaultMessage: 'Saving Config...'
+ }
+});
+
+class ServiceSettings extends React.Component {
constructor(props) {
super(props);
@@ -120,6 +149,7 @@ export default class ServiceSettings extends React.Component {
}
render() {
+ const {formatMessage} = this.props.intl;
var serverError = '';
if (this.state.serverError) {
serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>;
@@ -133,7 +163,12 @@ export default class ServiceSettings extends React.Component {
return (
<div className='wrapper--fixed'>
- <h3>{'Service Settings'}</h3>
+ <h3>
+ <FormattedMessage
+ id='admin.service.title'
+ defaultMessage='Service Settings'
+ />
+ </h3>
<form
className='form-horizontal'
role='form'
@@ -144,7 +179,10 @@ export default class ServiceSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='ListenAddress'
>
- {'Listen Address:'}
+ <FormattedMessage
+ id='admin.service.listenAddress'
+ defaultMessage='Listen Address:'
+ />
</label>
<div className='col-sm-8'>
<input
@@ -152,11 +190,16 @@ export default class ServiceSettings extends React.Component {
className='form-control'
id='ListenAddress'
ref='ListenAddress'
- placeholder='Ex ":8065"'
+ placeholder={formatMessage(holders.listenExample)}
defaultValue={this.props.config.ServiceSettings.ListenAddress}
onChange={this.handleChange}
/>
- <p className='help-text'>{'The address to which to bind and listen. Entering ":8065" will bind to all interfaces or you can choose one like "127.0.0.1:8065". Changing this will require a server restart before taking effect.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.service.listenDescription'
+ defaultMessage='The address to which to bind and listen. Entering ":8065" will bind to all interfaces or you can choose one like "127.0.0.1:8065". Changing this will require a server restart before taking effect.'
+ />
+ </p>
</div>
</div>
@@ -165,7 +208,10 @@ export default class ServiceSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='MaximumLoginAttempts'
>
- {'Maximum Login Attempts:'}
+ <FormattedMessage
+ id='admin.service.attemptTitle'
+ defaultMessage='Maximum Login Attempts:'
+ />
</label>
<div className='col-sm-8'>
<input
@@ -173,11 +219,16 @@ export default class ServiceSettings extends React.Component {
className='form-control'
id='MaximumLoginAttempts'
ref='MaximumLoginAttempts'
- placeholder='Ex "10"'
+ placeholder={formatMessage(holders.attemptExample)}
defaultValue={this.props.config.ServiceSettings.MaximumLoginAttempts}
onChange={this.handleChange}
/>
- <p className='help-text'>{'Login attempts allowed before user is locked out and required to reset password via email.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.service.attemptDescription'
+ defaultMessage='Login attempts allowed before user is locked out and required to reset password via email.'
+ />
+ </p>
</div>
</div>
@@ -186,7 +237,10 @@ export default class ServiceSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='SegmentDeveloperKey'
>
- {'Segment Developer Key:'}
+ <FormattedMessage
+ id='admin.service.segmentTitle'
+ defaultMessage='Segment Developer Key:'
+ />
</label>
<div className='col-sm-8'>
<input
@@ -194,11 +248,16 @@ export default class ServiceSettings extends React.Component {
className='form-control'
id='SegmentDeveloperKey'
ref='SegmentDeveloperKey'
- placeholder='Ex "g3fgGOXJAQ43QV7rAh6iwQCkV4cA1Gs"'
+ placeholder={formatMessage(holders.segmentExample)}
defaultValue={this.props.config.ServiceSettings.SegmentDeveloperKey}
onChange={this.handleChange}
/>
- <p className='help-text'>{'For users running a SaaS services, sign up for a key at Segment.com to track metrics.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.service.segmentDescription'
+ defaultMessage='For users running a SaaS services, sign up for a key at Segment.com to track metrics.'
+ />
+ </p>
</div>
</div>
@@ -207,7 +266,10 @@ export default class ServiceSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='GoogleDeveloperKey'
>
- {'Google Developer Key:'}
+ <FormattedMessage
+ id='admin.service.googleTitle'
+ defaultMessage='Google Developer Key:'
+ />
</label>
<div className='col-sm-8'>
<input
@@ -215,19 +277,17 @@ export default class ServiceSettings extends React.Component {
className='form-control'
id='GoogleDeveloperKey'
ref='GoogleDeveloperKey'
- placeholder='Ex "7rAh6iwQCkV4cA1Gsg3fgGOXJAQ43QV"'
+ placeholder={formatMessage(holders.googleExample)}
defaultValue={this.props.config.ServiceSettings.GoogleDeveloperKey}
onChange={this.handleChange}
/>
<p className='help-text'>
- {'Set this key to enable embedding of YouTube video previews based on hyperlinks appearing in messages or comments. Instructions to obtain a key available at '}
- <a
- href='https://www.youtube.com/watch?v=Im69kzhpR3I'
- target='_blank'
- >
- {'https://www.youtube.com/watch?v=Im69kzhpR3I'}
- </a>
- {'. Leaving the field blank disables the automatic generation of YouTube video previews from links.'}
+ <FormattedHTMLMessage
+ id='admin.service.googleDescription'
+ defaultMessage='Set this key to enable embedding of YouTube video previews based on hyperlinks appearing in messages or comments. Instructions to obtain a key available at
+ <a href="https://www.youtube.com/watch?v=Im69kzhpR3I" target="_blank">https://www.youtube.com/watch?v=Im69kzhpR3I</a>.
+ Leaving the field blank disables the automatic generation of YouTube video previews from links.'
+ />
</p>
</div>
</div>
@@ -237,7 +297,10 @@ export default class ServiceSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='EnableIncomingWebhooks'
>
- {'Enable Incoming Webhooks: '}
+ <FormattedMessage
+ id='admin.service.webhooksTitle'
+ defaultMessage='Enable Incoming Webhooks: '
+ />
</label>
<div className='col-sm-8'>
<label className='radio-inline'>
@@ -249,7 +312,10 @@ export default class ServiceSettings extends React.Component {
defaultChecked={this.props.config.ServiceSettings.EnableIncomingWebhooks}
onChange={this.handleChange}
/>
- {'true'}
+ <FormattedMessage
+ id='admin.service.true'
+ defaultMessage='true'
+ />
</label>
<label className='radio-inline'>
<input
@@ -259,9 +325,17 @@ export default class ServiceSettings extends React.Component {
defaultChecked={!this.props.config.ServiceSettings.EnableIncomingWebhooks}
onChange={this.handleChange}
/>
- {'false'}
+ <FormattedMessage
+ id='admin.service.false'
+ defaultMessage='false'
+ />
</label>
- <p className='help-text'>{'When true, incoming webhooks will be allowed. To help combat phishing attacks, all posts from webhooks will be labelled by a BOT tag.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.service.webhooksDescription'
+ defaultMessage='When true, incoming webhooks will be allowed. To help combat phishing attacks, all posts from webhooks will be labelled by a BOT tag.'
+ />
+ </p>
</div>
</div>
@@ -270,7 +344,10 @@ export default class ServiceSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='EnableOutgoingWebhooks'
>
- {'Enable Outgoing Webhooks: '}
+ <FormattedMessage
+ id='admin.service.outWebhooksTitle'
+ defaultMessage='Enable Outgoing Webhooks: '
+ />
</label>
<div className='col-sm-8'>
<label className='radio-inline'>
@@ -282,7 +359,10 @@ export default class ServiceSettings extends React.Component {
defaultChecked={this.props.config.ServiceSettings.EnableOutgoingWebhooks}
onChange={this.handleChange}
/>
- {'true'}
+ <FormattedMessage
+ id='admin.service.true'
+ defaultMessage='true'
+ />
</label>
<label className='radio-inline'>
<input
@@ -292,9 +372,17 @@ export default class ServiceSettings extends React.Component {
defaultChecked={!this.props.config.ServiceSettings.EnableOutgoingWebhooks}
onChange={this.handleChange}
/>
- {'false'}
+ <FormattedMessage
+ id='admin.service.false'
+ defaultMessage='false'
+ />
</label>
- <p className='help-text'>{'When true, outgoing webhooks will be allowed.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.service.outWebhooksDesc'
+ defaultMessage='When true, outgoing webhooks will be allowed.'
+ />
+ </p>
</div>
</div>
@@ -303,7 +391,10 @@ export default class ServiceSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='EnablePostUsernameOverride'
>
- {'Enable Overriding Usernames from Webhooks: '}
+ <FormattedMessage
+ id='admin.service.overrideTitle'
+ defaultMessage='Enable Overriding Usernames from Webhooks: '
+ />
</label>
<div className='col-sm-8'>
<label className='radio-inline'>
@@ -315,7 +406,10 @@ export default class ServiceSettings extends React.Component {
defaultChecked={this.props.config.ServiceSettings.EnablePostUsernameOverride}
onChange={this.handleChange}
/>
- {'true'}
+ <FormattedMessage
+ id='admin.service.true'
+ defaultMessage='true'
+ />
</label>
<label className='radio-inline'>
<input
@@ -325,9 +419,17 @@ export default class ServiceSettings extends React.Component {
defaultChecked={!this.props.config.ServiceSettings.EnablePostUsernameOverride}
onChange={this.handleChange}
/>
- {'false'}
+ <FormattedMessage
+ id='admin.service.false'
+ defaultMessage='false'
+ />
</label>
- <p className='help-text'>{'When true, webhooks will be allowed to change the username they are posting as. Note, combined with allowing icon overriding, this could open users up to phishing attacks.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.service.overrideDescription'
+ defaultMessage='When true, webhooks will be allowed to change the username they are posting as. Note, combined with allowing icon overriding, this could open users up to phishing attacks.'
+ />
+ </p>
</div>
</div>
@@ -336,7 +438,10 @@ export default class ServiceSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='EnablePostIconOverride'
>
- {'Enable Overriding Icon from Webhooks: '}
+ <FormattedMessage
+ id='admin.service.iconTitle'
+ defaultMessage='Enable Overriding Icon from Webhooks: '
+ />
</label>
<div className='col-sm-8'>
<label className='radio-inline'>
@@ -348,7 +453,10 @@ export default class ServiceSettings extends React.Component {
defaultChecked={this.props.config.ServiceSettings.EnablePostIconOverride}
onChange={this.handleChange}
/>
- {'true'}
+ <FormattedMessage
+ id='admin.service.true'
+ defaultMessage='true'
+ />
</label>
<label className='radio-inline'>
<input
@@ -358,9 +466,17 @@ export default class ServiceSettings extends React.Component {
defaultChecked={!this.props.config.ServiceSettings.EnablePostIconOverride}
onChange={this.handleChange}
/>
- {'false'}
+ <FormattedMessage
+ id='admin.service.false'
+ defaultMessage='false'
+ />
</label>
- <p className='help-text'>{'When true, webhooks will be allowed to change the icon they post with. Note, combined with allowing username overriding, this could open users up to phishing attacks.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.service.iconDescription'
+ defaultMessage='When true, webhooks will be allowed to change the icon they post with. Note, combined with allowing username overriding, this could open users up to phishing attacks.'
+ />
+ </p>
</div>
</div>
@@ -369,7 +485,10 @@ export default class ServiceSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='EnableTesting'
>
- {'Enable Testing: '}
+ <FormattedMessage
+ id='admin.service.testingTitle'
+ defaultMessage='Enable Testing: '
+ />
</label>
<div className='col-sm-8'>
<label className='radio-inline'>
@@ -381,7 +500,10 @@ export default class ServiceSettings extends React.Component {
defaultChecked={this.props.config.ServiceSettings.EnableTesting}
onChange={this.handleChange}
/>
- {'true'}
+ <FormattedMessage
+ id='admin.service.true'
+ defaultMessage='true'
+ />
</label>
<label className='radio-inline'>
<input
@@ -391,9 +513,17 @@ export default class ServiceSettings extends React.Component {
defaultChecked={!this.props.config.ServiceSettings.EnableTesting}
onChange={this.handleChange}
/>
- {'false'}
+ <FormattedMessage
+ id='admin.service.false'
+ defaultMessage='false'
+ />
</label>
- <p className='help-text'>{'(Developer Option) When true, /loadtest slash command is enabled to load test accounts and test data. Changing this will require a server restart before taking effect.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.service.testingDescription'
+ defaultMessage='(Developer Option) When true, /loadtest slash command is enabled to load test accounts and test data. Changing this will require a server restart before taking effect.'
+ />
+ </p>
</div>
</div>
@@ -402,7 +532,10 @@ export default class ServiceSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='EnableDeveloper'
>
- {'Enable Developer Mode: '}
+ <FormattedMessage
+ id='admin.service.developerTitle'
+ defaultMessage='Enable Developer Mode: '
+ />
</label>
<div className='col-sm-8'>
<label className='radio-inline'>
@@ -414,7 +547,10 @@ export default class ServiceSettings extends React.Component {
defaultChecked={this.props.config.ServiceSettings.EnableDeveloper}
onChange={this.handleChange}
/>
- {'true'}
+ <FormattedMessage
+ id='admin.service.true'
+ defaultMessage='true'
+ />
</label>
<label className='radio-inline'>
<input
@@ -424,9 +560,17 @@ export default class ServiceSettings extends React.Component {
defaultChecked={!this.props.config.ServiceSettings.EnableDeveloper}
onChange={this.handleChange}
/>
- {'false'}
+ <FormattedMessage
+ id='admin.service.false'
+ defaultMessage='false'
+ />
</label>
- <p className='help-text'>{'(Developer Option) When true, extra information around errors will be displayed in the UI.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.service.developerDesc'
+ defaultMessage='(Developer Option) When true, extra information around errors will be displayed in the UI.'
+ />
+ </p>
</div>
</div>
@@ -435,7 +579,10 @@ export default class ServiceSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='EnableSecurityFixAlert'
>
- {'Enable Security Alerts: '}
+ <FormattedMessage
+ id='admin.service.securityTitle'
+ defaultMessage='Enable Security Alerts: '
+ />
</label>
<div className='col-sm-8'>
<label className='radio-inline'>
@@ -447,7 +594,10 @@ export default class ServiceSettings extends React.Component {
defaultChecked={this.props.config.ServiceSettings.EnableSecurityFixAlert}
onChange={this.handleChange}
/>
- {'true'}
+ <FormattedMessage
+ id='admin.service.true'
+ defaultMessage='true'
+ />
</label>
<label className='radio-inline'>
<input
@@ -457,9 +607,17 @@ export default class ServiceSettings extends React.Component {
defaultChecked={!this.props.config.ServiceSettings.EnableSecurityFixAlert}
onChange={this.handleChange}
/>
- {'false'}
+ <FormattedMessage
+ id='admin.service.false'
+ defaultMessage='false'
+ />
</label>
- <p className='help-text'>{'When true, System Administrators are notified by email if a relevant security fix alert has been announced in the last 12 hours. Requires email to be enabled.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.service.securityDesc'
+ defaultMessage='When true, System Administrators are notified by email if a relevant security fix alert has been announced in the last 12 hours. Requires email to be enabled.'
+ />
+ </p>
</div>
</div>
@@ -468,7 +626,10 @@ export default class ServiceSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='SessionLengthWebInDays'
>
- {'Session Length for Web in Days:'}
+ <FormattedMessage
+ id='admin.service.webSessionDays'
+ defaultMessage='Session Length for Web in Days:'
+ />
</label>
<div className='col-sm-8'>
<input
@@ -476,11 +637,16 @@ export default class ServiceSettings extends React.Component {
className='form-control'
id='SessionLengthWebInDays'
ref='SessionLengthWebInDays'
- placeholder='Ex "30"'
+ placeholder={formatMessage(holders.sessionDaysEx)}
defaultValue={this.props.config.ServiceSettings.SessionLengthWebInDays}
onChange={this.handleChange}
/>
- <p className='help-text'>{'The web session will expire after the number of days specified and will require a user to login again.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.service.webSessionDaysDesc'
+ defaultMessage='The web session will expire after the number of days specified and will require a user to login again.'
+ />
+ </p>
</div>
</div>
@@ -489,7 +655,10 @@ export default class ServiceSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='SessionLengthMobileInDays'
>
- {'Session Length for Mobile Device in Days:'}
+ <FormattedMessage
+ id='admin.service.mobileSessionDays'
+ defaultMessage='Session Length for Mobile Device in Days:'
+ />
</label>
<div className='col-sm-8'>
<input
@@ -497,11 +666,16 @@ export default class ServiceSettings extends React.Component {
className='form-control'
id='SessionLengthMobileInDays'
ref='SessionLengthMobileInDays'
- placeholder='Ex "30"'
+ placeholder={formatMessage(holders.sessionDaysEx)}
defaultValue={this.props.config.ServiceSettings.SessionLengthMobileInDays}
onChange={this.handleChange}
/>
- <p className='help-text'>{'The native mobile session will expire after the number of days specified and will require a user to login again.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.service.mobileSessionDaysDesc'
+ defaultMessage='The native mobile session will expire after the number of days specified and will require a user to login again.'
+ />
+ </p>
</div>
</div>
@@ -510,7 +684,10 @@ export default class ServiceSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='SessionLengthSSOInDays'
>
- {'Session Length for SSO in Days:'}
+ <FormattedMessage
+ id='admin.service.ssoSessionDays'
+ defaultMessage='Session Length for SSO in Days:'
+ />
</label>
<div className='col-sm-8'>
<input
@@ -518,11 +695,16 @@ export default class ServiceSettings extends React.Component {
className='form-control'
id='SessionLengthSSOInDays'
ref='SessionLengthSSOInDays'
- placeholder='Ex "30"'
+ placeholder={formatMessage(holders.sessionDaysEx)}
defaultValue={this.props.config.ServiceSettings.SessionLengthSSOInDays}
onChange={this.handleChange}
/>
- <p className='help-text'>{'The SSO session will expire after the number of days specified and will require a user to login again.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.service.ssoSessionDaysDesc'
+ defaultMessage='The SSO session will expire after the number of days specified and will require a user to login again.'
+ />
+ </p>
</div>
</div>
@@ -531,7 +713,10 @@ export default class ServiceSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='SessionCacheInMinutes'
>
- {'Session Cache in Minutes:'}
+ <FormattedMessage
+ id='admin.service.sessionCache'
+ defaultMessage='Session Cache in Minutes:'
+ />
</label>
<div className='col-sm-8'>
<input
@@ -539,11 +724,16 @@ export default class ServiceSettings extends React.Component {
className='form-control'
id='SessionCacheInMinutes'
ref='SessionCacheInMinutes'
- placeholder='Ex "30"'
+ placeholder={formatMessage(holders.sessionDaysEx)}
defaultValue={this.props.config.ServiceSettings.SessionCacheInMinutes}
onChange={this.handleChange}
/>
- <p className='help-text'>{'The number of minutes to cache a session in memory.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.service.sessionCacheDesc'
+ defaultMessage='The number of minutes to cache a session in memory.'
+ />
+ </p>
</div>
</div>
@@ -556,9 +746,12 @@ export default class ServiceSettings extends React.Component {
className={saveClass}
onClick={this.handleSubmit}
id='save-button'
- data-loading-text={'<span class=\'glyphicon glyphicon-refresh glyphicon-refresh-animate\'></span> Saving Config...'}
+ data-loading-text={'<span class=\'glyphicon glyphicon-refresh glyphicon-refresh-animate\'></span> ' + formatMessage(holders.saving)}
>
- {'Save'}
+ <FormattedMessage
+ id='admin.service.save'
+ defaultMessage='Save'
+ />
</button>
</div>
</div>
@@ -603,5 +796,8 @@ export default class ServiceSettings extends React.Component {
// </div>
ServiceSettings.propTypes = {
+ intl: intlShape.isRequired,
config: React.PropTypes.object
};
+
+export default injectIntl(ServiceSettings); \ No newline at end of file
diff --git a/web/react/components/admin_console/sql_settings.jsx b/web/react/components/admin_console/sql_settings.jsx
index 2a55f7324..69ae808f6 100644
--- a/web/react/components/admin_console/sql_settings.jsx
+++ b/web/react/components/admin_console/sql_settings.jsx
@@ -5,7 +5,32 @@ import * as Client from '../../utils/client.jsx';
import * as AsyncClient from '../../utils/async_client.jsx';
import crypto from 'crypto';
-export default class SqlSettings extends React.Component {
+import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'mm-intl';
+
+const holders = defineMessages({
+ warning: {
+ id: 'admin.sql.warning',
+ defaultMessage: 'Warning: re-generating this salt may cause some columns in the database to return empty results.'
+ },
+ maxConnectionsExample: {
+ id: 'admin.sql.maxConnectionsExample',
+ defaultMessage: 'Ex "10"'
+ },
+ maxOpenExample: {
+ id: 'admin.sql.maxOpenExample',
+ defaultMessage: 'Ex "10"'
+ },
+ keyExample: {
+ id: 'admin.sql.keyExample',
+ defaultMessage: 'Ex "gxHVDcKUyP2y1eiyW8S8na1UYQAfq6J6"'
+ },
+ saving: {
+ id: 'admin.sql.saving',
+ defaultMessage: 'Saving Config...'
+ }
+});
+
+class SqlSettings extends React.Component {
constructor(props) {
super(props);
@@ -74,7 +99,7 @@ export default class SqlSettings extends React.Component {
handleGenerate(e) {
e.preventDefault();
- var cfm = global.window.confirm('Warning: re-generating this salt may cause some columns in the database to return empty results.');
+ var cfm = global.window.confirm(this.props.intl.formatMessage(holders.warning));
if (cfm === false) {
return;
}
@@ -85,6 +110,7 @@ export default class SqlSettings extends React.Component {
}
render() {
+ const {formatMessage} = this.props.intl;
var serverError = '';
if (this.state.serverError) {
serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>;
@@ -111,12 +137,27 @@ export default class SqlSettings extends React.Component {
<div className='banner'>
<div className='banner__content'>
- <h4 className='banner__heading'>{'Note:'}</h4>
- <p>{'Changing properties in this section will require a server restart before taking effect.'}</p>
+ <h4 className='banner__heading'>
+ <FormattedMessage
+ id='admin.sql.noteTitle'
+ defaultMessage='Note:'
+ />
+ </h4>
+ <p>
+ <FormattedMessage
+ id='admin.sql.noteDescription'
+ defaultMessage='Changing properties in this section will require a server restart before taking effect.'
+ />
+ </p>
</div>
</div>
- <h3>{'SQL Settings'}</h3>
+ <h3>
+ <FormattedMessage
+ id='admin.sql.title'
+ defaultMessage='SQL Settings'
+ />
+ </h3>
<form
className='form-horizontal'
role='form'
@@ -127,7 +168,10 @@ export default class SqlSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='DriverName'
>
- {'Driver Name:'}
+ <FormattedMessage
+ id='admin.sql.driverName'
+ defaultMessage='Driver Name:'
+ />
</label>
<div className='col-sm-8'>
<p className='help-text'>{this.props.config.SqlSettings.DriverName}</p>
@@ -139,7 +183,10 @@ export default class SqlSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='DataSource'
>
- {'Data Source:'}
+ <FormattedMessage
+ id='admin.sql.dataSource'
+ defaultMessage='Data Source:'
+ />
</label>
<div className='col-sm-8'>
<p className='help-text'>{dataSource}</p>
@@ -151,7 +198,10 @@ export default class SqlSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='DataSourceReplicas'
>
- {'Data Source Replicas:'}
+ <FormattedMessage
+ id='admin.sql.replicas'
+ defaultMessage='Data Source Replicas:'
+ />
</label>
<div className='col-sm-8'>
<p className='help-text'>{dataSourceReplicas}</p>
@@ -163,7 +213,10 @@ export default class SqlSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='MaxIdleConns'
>
- {'Maximum Idle Connections:'}
+ <FormattedMessage
+ id='admin.sql.maxConnectionsTitle'
+ defaultMessage='Maximum Idle Connections:'
+ />
</label>
<div className='col-sm-8'>
<input
@@ -171,11 +224,16 @@ export default class SqlSettings extends React.Component {
className='form-control'
id='MaxIdleConns'
ref='MaxIdleConns'
- placeholder='Ex "10"'
+ placeholder={formatMessage(holders.maxConnectionsExample)}
defaultValue={this.props.config.SqlSettings.MaxIdleConns}
onChange={this.handleChange}
/>
- <p className='help-text'>{'Maximum number of idle connections held open to the database.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.sql.maxConnectionsDescription'
+ defaultMessage='Maximum number of idle connections held open to the database.'
+ />
+ </p>
</div>
</div>
@@ -184,7 +242,10 @@ export default class SqlSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='MaxOpenConns'
>
- {'Maximum Open Connections:'}
+ <FormattedMessage
+ id='admin.sql.maxOpenTitle'
+ defaultMessage='Maximum Open Connections:'
+ />
</label>
<div className='col-sm-8'>
<input
@@ -192,11 +253,16 @@ export default class SqlSettings extends React.Component {
className='form-control'
id='MaxOpenConns'
ref='MaxOpenConns'
- placeholder='Ex "10"'
+ placeholder={formatMessage(holders.maxOpenExample)}
defaultValue={this.props.config.SqlSettings.MaxOpenConns}
onChange={this.handleChange}
/>
- <p className='help-text'>{'Maximum number of open connections held open to the database.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.sql.maxOpenDescription'
+ defaultMessage='Maximum number of open connections held open to the database.'
+ />
+ </p>
</div>
</div>
@@ -205,7 +271,10 @@ export default class SqlSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='AtRestEncryptKey'
>
- {'At Rest Encrypt Key:'}
+ <FormattedMessage
+ id='admin.sql.keyTitle'
+ defaultMessage='At Rest Encrypt Key:'
+ />
</label>
<div className='col-sm-8'>
<input
@@ -213,17 +282,25 @@ export default class SqlSettings extends React.Component {
className='form-control'
id='AtRestEncryptKey'
ref='AtRestEncryptKey'
- placeholder='Ex "gxHVDcKUyP2y1eiyW8S8na1UYQAfq6J6"'
+ placeholder={formatMessage(holders.keyExample)}
defaultValue={this.props.config.SqlSettings.AtRestEncryptKey}
onChange={this.handleChange}
/>
- <p className='help-text'>{'32-character salt available to encrypt and decrypt sensitive fields in database.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.sql.keyDescription'
+ defaultMessage='32-character salt available to encrypt and decrypt sensitive fields in database.'
+ />
+ </p>
<div className='help-text'>
<button
className='btn btn-default'
onClick={this.handleGenerate}
>
- {'Re-Generate'}
+ <FormattedMessage
+ id='admin.sql.regenerate'
+ defaultMessage='Re-Generate'
+ />
</button>
</div>
</div>
@@ -234,7 +311,10 @@ export default class SqlSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='Trace'
>
- {'Trace: '}
+ <FormattedMessage
+ id='admin.sql.traceTitle'
+ defaultMessage='Trace: '
+ />
</label>
<div className='col-sm-8'>
<label className='radio-inline'>
@@ -246,7 +326,10 @@ export default class SqlSettings extends React.Component {
defaultChecked={this.props.config.SqlSettings.Trace}
onChange={this.handleChange}
/>
- {'true'}
+ <FormattedMessage
+ id='admin.sql.true'
+ defaultMessage='true'
+ />
</label>
<label className='radio-inline'>
<input
@@ -256,9 +339,17 @@ export default class SqlSettings extends React.Component {
defaultChecked={!this.props.config.SqlSettings.Trace}
onChange={this.handleChange}
/>
- {'false'}
+ <FormattedMessage
+ id='admin.sql.false'
+ defaultMessage='false'
+ />
</label>
- <p className='help-text'>{'(Development Mode) When true, executing SQL statements are written to the log.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.sql.traceDescription'
+ defaultMessage='(Development Mode) When true, executing SQL statements are written to the log.'
+ />
+ </p>
</div>
</div>
@@ -271,9 +362,12 @@ export default class SqlSettings extends React.Component {
className={saveClass}
onClick={this.handleSubmit}
id='save-button'
- data-loading-text={'<span class=\'glyphicon glyphicon-refresh glyphicon-refresh-animate\'></span> Saving Config...'}
+ data-loading-text={'<span class=\'glyphicon glyphicon-refresh glyphicon-refresh-animate\'></span> ' + formatMessage(holders.saving)}
>
- {'Save'}
+ <FormattedMessage
+ id='admin.sql.save'
+ defaultMessage='Save'
+ />
</button>
</div>
</div>
@@ -285,5 +379,8 @@ export default class SqlSettings extends React.Component {
}
SqlSettings.propTypes = {
+ intl: intlShape.isRequired,
config: React.PropTypes.object
};
+
+export default injectIntl(SqlSettings); \ No newline at end of file
diff --git a/web/react/components/admin_console/system_analytics.jsx b/web/react/components/admin_console/system_analytics.jsx
index f54813a94..2dd833fb2 100644
--- a/web/react/components/admin_console/system_analytics.jsx
+++ b/web/react/components/admin_console/system_analytics.jsx
@@ -4,7 +4,24 @@
import Analytics from './analytics.jsx';
import * as Client from '../../utils/client.jsx';
-export default class SystemAnalytics extends React.Component {
+import {injectIntl, intlShape, defineMessages} from 'mm-intl';
+
+const labels = defineMessages({
+ totalPosts: {
+ id: 'admin.system_analytics.totalPosts',
+ defaultMessage: 'Total Posts'
+ },
+ activeUsers: {
+ id: 'admin.system_analytics.activeUsers',
+ defaultMessage: 'Active Users With Posts'
+ },
+ title: {
+ id: 'admin.system_analytics.title',
+ defaultMessage: 'the System'
+ }
+});
+
+class SystemAnalytics extends React.Component {
constructor(props) {
super(props);
@@ -29,6 +46,7 @@ export default class SystemAnalytics extends React.Component {
}
getData() { // should be moved to an action creator eventually
+ const {formatMessage} = this.props.intl;
Client.getSystemAnalytics(
'standard',
(data) => {
@@ -63,7 +81,7 @@ export default class SystemAnalytics extends React.Component {
var chartData = {
labels: [],
datasets: [{
- label: 'Total Posts',
+ label: formatMessage(labels.totalPosts),
fillColor: 'rgba(151,187,205,0.2)',
strokeColor: 'rgba(151,187,205,1)',
pointColor: 'rgba(151,187,205,1)',
@@ -97,7 +115,7 @@ export default class SystemAnalytics extends React.Component {
var chartData = {
labels: [],
datasets: [{
- label: 'Active Users With Posts',
+ label: formatMessage(labels.activeUsers),
fillColor: 'rgba(151,187,205,0.2)',
strokeColor: 'rgba(151,187,205,1)',
pointColor: 'rgba(151,187,205,1)',
@@ -142,7 +160,7 @@ export default class SystemAnalytics extends React.Component {
return (
<div>
<Analytics
- title={'the System'}
+ title={this.props.intl.formatMessage(labels.title)}
channelOpenCount={this.state.channel_open_count}
channelPrivateCount={this.state.channel_private_count}
postCount={this.state.post_count}
@@ -157,5 +175,8 @@ export default class SystemAnalytics extends React.Component {
}
SystemAnalytics.propTypes = {
+ intl: intlShape.isRequired,
team: React.PropTypes.object
};
+
+export default injectIntl(SystemAnalytics); \ No newline at end of file
diff --git a/web/react/components/admin_console/team_analytics.jsx b/web/react/components/admin_console/team_analytics.jsx
index c164dd98c..ee59b0e66 100644
--- a/web/react/components/admin_console/team_analytics.jsx
+++ b/web/react/components/admin_console/team_analytics.jsx
@@ -4,7 +4,20 @@
import Analytics from './analytics.jsx';
import * as Client from '../../utils/client.jsx';
-export default class TeamAnalytics extends React.Component {
+import {injectIntl, intlShape, defineMessages} from 'mm-intl';
+
+const labels = defineMessages({
+ totalPosts: {
+ id: 'admin.team_analytics.totalPosts',
+ defaultMessage: 'Total Posts'
+ },
+ activeUsers: {
+ id: 'admin.team_analytics.activeUsers',
+ defaultMessage: 'Active Users With Posts'
+ }
+});
+
+class TeamAnalytics extends React.Component {
constructor(props) {
super(props);
@@ -29,6 +42,7 @@ export default class TeamAnalytics extends React.Component {
}
getData(teamId) { // should be moved to an action creator eventually
+ const {formatMessage} = this.props.intl;
Client.getTeamAnalytics(
teamId,
'standard',
@@ -65,7 +79,7 @@ export default class TeamAnalytics extends React.Component {
var chartData = {
labels: [],
datasets: [{
- label: 'Total Posts',
+ label: formatMessage(labels.totalPosts),
fillColor: 'rgba(151,187,205,0.2)',
strokeColor: 'rgba(151,187,205,1)',
pointColor: 'rgba(151,187,205,1)',
@@ -100,7 +114,7 @@ export default class TeamAnalytics extends React.Component {
var chartData = {
labels: [],
datasets: [{
- label: 'Active Users With Posts',
+ label: formatMessage(labels.activeUsers),
fillColor: 'rgba(151,187,205,0.2)',
strokeColor: 'rgba(151,187,205,1)',
pointColor: 'rgba(151,187,205,1)',
@@ -231,5 +245,8 @@ export default class TeamAnalytics extends React.Component {
}
TeamAnalytics.propTypes = {
+ intl: intlShape.isRequired,
team: React.PropTypes.object
};
+
+export default injectIntl(TeamAnalytics); \ No newline at end of file
diff --git a/web/react/components/admin_console/team_settings.jsx b/web/react/components/admin_console/team_settings.jsx
index 9d958ce91..cc4ff38ba 100644
--- a/web/react/components/admin_console/team_settings.jsx
+++ b/web/react/components/admin_console/team_settings.jsx
@@ -4,7 +4,28 @@
import * as Client from '../../utils/client.jsx';
import * as AsyncClient from '../../utils/async_client.jsx';
-export default class TeamSettings extends React.Component {
+import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'mm-intl';
+
+const holders = defineMessages({
+ siteNameExample: {
+ id: 'admin.team.siteNameExample',
+ defaultMessage: 'Ex "Mattermost"'
+ },
+ maxUsersExample: {
+ id: 'admin.team.maxUsersExample',
+ defaultMessage: 'Ex "25"'
+ },
+ restrictExample: {
+ id: 'admin.team.restrictExample',
+ defaultMessage: 'Ex "corp.mattermost.com, mattermost.org"'
+ },
+ saving: {
+ id: 'admin.team.saving',
+ defaultMessage: 'Saving Config...'
+ }
+});
+
+class TeamSettings extends React.Component {
constructor(props) {
super(props);
@@ -62,6 +83,7 @@ export default class TeamSettings extends React.Component {
}
render() {
+ const {formatMessage} = this.props.intl;
var serverError = '';
if (this.state.serverError) {
serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>;
@@ -75,7 +97,12 @@ export default class TeamSettings extends React.Component {
return (
<div className='wrapper--fixed'>
- <h3>{'Team Settings'}</h3>
+ <h3>
+ <FormattedMessage
+ id='admin.team.title'
+ defaultMessage='Team Settings'
+ />
+ </h3>
<form
className='form-horizontal'
role='form'
@@ -86,7 +113,10 @@ export default class TeamSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='SiteName'
>
- {'Site Name:'}
+ <FormattedMessage
+ id='admin.team.siteNameTitle'
+ defaultMessage='Site Name:'
+ />
</label>
<div className='col-sm-8'>
<input
@@ -94,11 +124,16 @@ export default class TeamSettings extends React.Component {
className='form-control'
id='SiteName'
ref='SiteName'
- placeholder='Ex "Mattermost"'
+ placeholder={formatMessage(holders.siteNameExample)}
defaultValue={this.props.config.TeamSettings.SiteName}
onChange={this.handleChange}
/>
- <p className='help-text'>{'Name of service shown in login screens and UI.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.team.siteNameDescription'
+ defaultMessage='Name of service shown in login screens and UI.'
+ />
+ </p>
</div>
</div>
@@ -107,7 +142,10 @@ export default class TeamSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='MaxUsersPerTeam'
>
- {'Max Users Per Team:'}
+ <FormattedMessage
+ id='admin.team.maxUsersTitle'
+ defaultMessage='Max Users Per Team:'
+ />
</label>
<div className='col-sm-8'>
<input
@@ -115,11 +153,16 @@ export default class TeamSettings extends React.Component {
className='form-control'
id='MaxUsersPerTeam'
ref='MaxUsersPerTeam'
- placeholder='Ex "25"'
+ placeholder={formatMessage(holders.maxUsersExample)}
defaultValue={this.props.config.TeamSettings.MaxUsersPerTeam}
onChange={this.handleChange}
/>
- <p className='help-text'>{'Maximum total number of users per team, including both active and inactive users.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.team.maxUsersDescription'
+ defaultMessage='Maximum total number of users per team, including both active and inactive users.'
+ />
+ </p>
</div>
</div>
@@ -128,7 +171,10 @@ export default class TeamSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='EnableTeamCreation'
>
- {'Enable Team Creation: '}
+ <FormattedMessage
+ id='admin.team.teamCreationTitle'
+ defaultMessage='Enable Team Creation: '
+ />
</label>
<div className='col-sm-8'>
<label className='radio-inline'>
@@ -140,7 +186,10 @@ export default class TeamSettings extends React.Component {
defaultChecked={this.props.config.TeamSettings.EnableTeamCreation}
onChange={this.handleChange}
/>
- {'true'}
+ <FormattedMessage
+ id='admin.team.true'
+ defaultMessage='true'
+ />
</label>
<label className='radio-inline'>
<input
@@ -150,9 +199,17 @@ export default class TeamSettings extends React.Component {
defaultChecked={!this.props.config.TeamSettings.EnableTeamCreation}
onChange={this.handleChange}
/>
- {'false'}
+ <FormattedMessage
+ id='admin.team.false'
+ defaultMessage='false'
+ />
</label>
- <p className='help-text'>{'When false, the ability to create teams is disabled. The create team button displays error when pressed.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.team.teamCreationDescription'
+ defaultMessage='When false, the ability to create teams is disabled. The create team button displays error when pressed.'
+ />
+ </p>
</div>
</div>
@@ -161,7 +218,10 @@ export default class TeamSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='EnableUserCreation'
>
- {'Enable User Creation: '}
+ <FormattedMessage
+ id='admin.team.userCreationTitle'
+ defaultMessage='Enable User Creation: '
+ />
</label>
<div className='col-sm-8'>
<label className='radio-inline'>
@@ -173,7 +233,10 @@ export default class TeamSettings extends React.Component {
defaultChecked={this.props.config.TeamSettings.EnableUserCreation}
onChange={this.handleChange}
/>
- {'true'}
+ <FormattedMessage
+ id='admin.team.true'
+ defaultMessage='true'
+ />
</label>
<label className='radio-inline'>
<input
@@ -183,9 +246,17 @@ export default class TeamSettings extends React.Component {
defaultChecked={!this.props.config.TeamSettings.EnableUserCreation}
onChange={this.handleChange}
/>
- {'false'}
+ <FormattedMessage
+ id='admin.team.false'
+ defaultMessage='false'
+ />
</label>
- <p className='help-text'>{'When false, the ability to create accounts is disabled. The create account button displays error when pressed.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.team.userCreationDescription'
+ defaultMessage='When false, the ability to create accounts is disabled. The create account button displays error when pressed.'
+ />
+ </p>
</div>
</div>
@@ -194,7 +265,10 @@ export default class TeamSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='RestrictCreationToDomains'
>
- {'Restrict Creation To Domains:'}
+ <FormattedMessage
+ id='admin.team.restrictTitle'
+ defaultMessage='Restrict Creation To Domains:'
+ />
</label>
<div className='col-sm-8'>
<input
@@ -202,11 +276,16 @@ export default class TeamSettings extends React.Component {
className='form-control'
id='RestrictCreationToDomains'
ref='RestrictCreationToDomains'
- placeholder='Ex "corp.mattermost.com, mattermost.org"'
+ placeholder={formatMessage(holders.restrictExample)}
defaultValue={this.props.config.TeamSettings.RestrictCreationToDomains}
onChange={this.handleChange}
/>
- <p className='help-text'>{'Teams and user accounts can only be created from a specific domain (e.g. "mattermost.org") or list of comma-separated domains (e.g. "corp.mattermost.com, mattermost.org").'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.team.restrictDescription'
+ defaultMessage='Teams and user accounts can only be created from a specific domain (e.g. "mattermost.org") or list of comma-separated domains (e.g. "corp.mattermost.com, mattermost.org").'
+ />
+ </p>
</div>
</div>
@@ -215,7 +294,10 @@ export default class TeamSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='RestrictTeamNames'
>
- {'Restrict Team Names: '}
+ <FormattedMessage
+ id='admin.team.restrictNameTitle'
+ defaultMessage='Restrict Team Names: '
+ />
</label>
<div className='col-sm-8'>
<label className='radio-inline'>
@@ -227,7 +309,10 @@ export default class TeamSettings extends React.Component {
defaultChecked={this.props.config.TeamSettings.RestrictTeamNames}
onChange={this.handleChange}
/>
- {'true'}
+ <FormattedMessage
+ id='admin.team.true'
+ defaultMessage='true'
+ />
</label>
<label className='radio-inline'>
<input
@@ -237,9 +322,17 @@ export default class TeamSettings extends React.Component {
defaultChecked={!this.props.config.TeamSettings.RestrictTeamNames}
onChange={this.handleChange}
/>
- {'false'}
+ <FormattedMessage
+ id='admin.team.false'
+ defaultMessage='false'
+ />
</label>
- <p className='help-text'>{'When true, You cannot create a team name with reserved words like www, admin, support, test, channel, etc'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.team.restrictNameDesc'
+ defaultMessage='When true, You cannot create a team name with reserved words like www, admin, support, test, channel, etc'
+ />
+ </p>
</div>
</div>
@@ -248,7 +341,10 @@ export default class TeamSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='EnableTeamListing'
>
- {'Enable Team Directory: '}
+ <FormattedMessage
+ id='admin.team.dirTitle'
+ defaultMessage='Enable Team Directory: '
+ />
</label>
<div className='col-sm-8'>
<label className='radio-inline'>
@@ -260,7 +356,10 @@ export default class TeamSettings extends React.Component {
defaultChecked={this.props.config.TeamSettings.EnableTeamListing}
onChange={this.handleChange}
/>
- {'true'}
+ <FormattedMessage
+ id='admin.team.true'
+ defaultMessage='true'
+ />
</label>
<label className='radio-inline'>
<input
@@ -270,9 +369,17 @@ export default class TeamSettings extends React.Component {
defaultChecked={!this.props.config.TeamSettings.EnableTeamListing}
onChange={this.handleChange}
/>
- {'false'}
+ <FormattedMessage
+ id='admin.team.false'
+ defaultMessage='false'
+ />
</label>
- <p className='help-text'>{'When true, teams that are configured to show in team directory will show on main page inplace of creating a new team.'}</p>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.team.dirDesc'
+ defaultMessage='When true, teams that are configured to show in team directory will show on main page inplace of creating a new team.'
+ />
+ </p>
</div>
</div>
@@ -285,9 +392,12 @@ export default class TeamSettings extends React.Component {
className={saveClass}
onClick={this.handleSubmit}
id='save-button'
- data-loading-text={'<span class=\'glyphicon glyphicon-refresh glyphicon-refresh-animate\'></span> Saving Config...'}
+ data-loading-text={'<span class=\'glyphicon glyphicon-refresh glyphicon-refresh-animate\'></span> ' + formatMessage(holders.saving)}
>
- {'Save'}
+ <FormattedMessage
+ id='admin.team.save'
+ defaultMessage='Save'
+ />
</button>
</div>
</div>
@@ -299,5 +409,8 @@ export default class TeamSettings extends React.Component {
}
TeamSettings.propTypes = {
+ intl: intlShape.isRequired,
config: React.PropTypes.object
};
+
+export default injectIntl(TeamSettings); \ No newline at end of file
diff --git a/web/react/components/admin_console/team_users.jsx b/web/react/components/admin_console/team_users.jsx
index 2d9657956..1177c9c56 100644
--- a/web/react/components/admin_console/team_users.jsx
+++ b/web/react/components/admin_console/team_users.jsx
@@ -6,6 +6,8 @@ import LoadingScreen from '../loading_screen.jsx';
import UserItem from './user_item.jsx';
import ResetPasswordModal from './reset_password_modal.jsx';
+import {FormattedMessage} from 'mm-intl';
+
export default class UserList extends React.Component {
constructor(props) {
super(props);
@@ -122,7 +124,15 @@ export default class UserList extends React.Component {
if (this.state.users == null) {
return (
<div className='wrapper--fixed'>
- <h3>{'Users for ' + this.props.team.name}</h3>
+ <h3>
+ <FormattedMessage
+ id='admin.userList.title'
+ defaultMessage='Users for {team}'
+ values={{
+ team: this.props.team.name
+ }}
+ />
+ </h3>
{serverError}
<LoadingScreen />
</div>
@@ -141,7 +151,16 @@ export default class UserList extends React.Component {
return (
<div className='wrapper--fixed'>
- <h3>{'Users for ' + this.props.team.name + ' (' + this.state.users.length + ')'}</h3>
+ <h3>
+ <FormattedMessage
+ id='admin.userList.title2'
+ defaultMessage='Users for {team} ({count})'
+ values={{
+ team: this.props.team.name,
+ count: this.state.users.length
+ }}
+ />
+ </h3>
{serverError}
<form
className='form-horizontal'
diff --git a/web/react/components/admin_console/user_item.jsx b/web/react/components/admin_console/user_item.jsx
index ef0b61460..02b01b090 100644
--- a/web/react/components/admin_console/user_item.jsx
+++ b/web/react/components/admin_console/user_item.jsx
@@ -3,6 +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 {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) {
@@ -14,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) {
@@ -61,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,
@@ -76,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) {
@@ -109,12 +199,27 @@ export default class UserItem extends React.Component {
}
const user = this.props.user;
- let currentRoles = 'Member';
+ let currentRoles = (
+ <FormattedMessage
+ id='admin.user_item.member'
+ defaultMessage='Member'
+ />
+ );
if (user.roles.length > 0) {
if (Utils.isSystemAdmin(user.roles)) {
- currentRoles = 'System Admin';
+ currentRoles = (
+ <FormattedMessage
+ id='admin.user_item.sysAdmin'
+ defaultMessage='System Admin'
+ />
+ );
} else if (Utils.isAdmin(user.roles)) {
- currentRoles = 'Team Admin';
+ currentRoles = (
+ <FormattedMessage
+ id='admin.user_item.teamAdmin'
+ defaultMessage='Team Admin'
+ />
+ );
} else {
currentRoles = user.roles.charAt(0).toUpperCase() + user.roles.slice(1);
}
@@ -128,7 +233,12 @@ export default class UserItem extends React.Component {
let showMakeNotActive = user.roles !== 'system_admin';
if (user.delete_at > 0) {
- currentRoles = 'Inactive';
+ currentRoles = (
+ <FormattedMessage
+ id='admin.user_item.inactive'
+ defaultMessage='Inactive'
+ />
+ );
showMakeMember = false;
showMakeAdmin = false;
showMakeSystemAdmin = false;
@@ -145,7 +255,10 @@ export default class UserItem extends React.Component {
href='#'
onClick={this.handleMakeSystemAdmin}
>
- {'Make System Admin'}
+ <FormattedMessage
+ id='admin.user_item.makeSysAdmin'
+ defaultMessage='Make System Admin'
+ />
</a>
</li>
);
@@ -160,7 +273,10 @@ export default class UserItem extends React.Component {
href='#'
onClick={this.handleMakeAdmin}
>
- {'Make Team Admin'}
+ <FormattedMessage
+ id='admin.user_item.makeTeamAdmin'
+ defaultMessage='Make Team Admin'
+ />
</a>
</li>
);
@@ -175,7 +291,10 @@ export default class UserItem extends React.Component {
href='#'
onClick={this.handleMakeMember}
>
- {'Make Member'}
+ <FormattedMessage
+ id='admin.user_item.makeMember'
+ defaultMessage='Make Member'
+ />
</a>
</li>
);
@@ -190,7 +309,10 @@ export default class UserItem extends React.Component {
href='#'
onClick={this.handleMakeActive}
>
- {'Make Active'}
+ <FormattedMessage
+ id='admin.user_item.makeActive'
+ defaultMessage='Make Active'
+ />
</a>
</li>
);
@@ -205,11 +327,29 @@ export default class UserItem extends React.Component {
href='#'
onClick={this.handleMakeNotActive}
>
- {'Make Inactive'}
+ <FormattedMessage
+ id='admin.user_item.makeInactive'
+ defaultMessage='Make Inactive'
+ />
</a>
</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>
@@ -248,11 +388,15 @@ export default class UserItem extends React.Component {
href='#'
onClick={this.handleResetPassword}
>
- {'Reset Password'}
+ <FormattedMessage
+ id='admin.user_item.resetPwd'
+ defaultMessage='Reset Password'
+ />
</a>
</li>
</ul>
</div>
+ {makeDemoteModal}
{serverError}
</td>
</tr>
@@ -261,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/authorize.jsx b/web/react/components/authorize.jsx
index 32e39fbff..4a4985268 100644
--- a/web/react/components/authorize.jsx
+++ b/web/react/components/authorize.jsx
@@ -3,6 +3,8 @@
import * as Client from '../utils/client.jsx';
+import {FormattedMessage, FormattedHTMLMessage} from 'mm-intl';
+
export default class Authorize extends React.Component {
constructor(props) {
super(props);
@@ -33,28 +35,67 @@ export default class Authorize extends React.Component {
}
render() {
return (
- <div className='authorize-box'>
- <div className='authorize-inner'>
- <h3>{'An application would like to connect to your '}{this.props.teamName}{' account'}</h3>
- <label>{'The app '}{this.props.appName}{' would like the ability to access and modify your basic information.'}</label>
- <br/>
- <br/>
- <label>{'Allow '}{this.props.appName}{' access?'}</label>
- <br/>
- <button
- type='submit'
- className='btn authorize-btn'
- onClick={this.handleDeny}
- >
- {'Deny'}
- </button>
- <button
- type='submit'
- className='btn btn-primary authorize-btn'
- onClick={this.handleAllow}
- >
- {'Allow'}
- </button>
+ <div className='container-fluid'>
+ <div className='oauth-prompt'>
+ <div className='prompt__heading'>
+ <div className='prompt__app-icon'>
+ <img
+ src='/static/images/icon50x50.png'
+ width='50'
+ height='50'
+ alt=''
+ />
+ </div>
+ <div className='text'>
+ <FormattedMessage
+ id='authorize.title'
+ defaultMessage='An application would like to connect to your {teamName} account'
+ values={{
+ teamName: this.props.teamName
+ }}
+ />
+ </div>
+ </div>
+ <p>
+ <FormattedHTMLMessage
+ id='authorize.app'
+ defaultMessage='The app <strong>{appName}</strong> would like the ability to access and modify your basic information.'
+ values={{
+ appName: this.props.appName
+ }}
+ />
+ </p>
+ <h2 className='prompt__allow'>
+ <FormattedHTMLMessage
+ id='authorize.access'
+ defaultMessage='Allow <strong>{appName}</strong> access?'
+ values={{
+ appName: this.props.appName
+ }}
+ />
+ </h2>
+ <div className='prompt__buttons'>
+ <button
+ type='submit'
+ className='btn authorize-btn'
+ onClick={this.handleDeny}
+ >
+ <FormattedMessage
+ id='authorize.deny'
+ defaultMessage='Deny'
+ />
+ </button>
+ <button
+ type='submit'
+ className='btn btn-primary authorize-btn'
+ onClick={this.handleAllow}
+ >
+ <FormattedMessage
+ id='authorize.allow'
+ defaultMessage='Allow'
+ />
+ </button>
+ </div>
</div>
</div>
);
diff --git a/web/react/components/center_panel.jsx b/web/react/components/center_panel.jsx
index 7eef329c3..53dad1306 100644
--- a/web/react/components/center_panel.jsx
+++ b/web/react/components/center_panel.jsx
@@ -66,11 +66,9 @@ export default class CenterPanel extends React.Component {
createPost = (
<div
id='archive-link-home'
+ onClick={handleClick}
>
- <a
- href=''
- onClick={handleClick}
- >
+ <a href=''>
{'You are viewing the Archives. Click here to jump to recent messages. '}
{<i className='fa fa-arrow-down'></i>}
</a>
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/claim/claim_account.jsx b/web/react/components/claim/claim_account.jsx
index f38f558db..87026b762 100644
--- a/web/react/components/claim/claim_account.jsx
+++ b/web/react/components/claim/claim_account.jsx
@@ -4,6 +4,8 @@
import EmailToSSO from './email_to_sso.jsx';
import SSOToEmail from './sso_to_email.jsx';
+import {FormattedMessage} from 'mm-intl';
+
export default class ClaimAccount extends React.Component {
constructor(props) {
super(props);
@@ -13,7 +15,14 @@ export default class ClaimAccount extends React.Component {
render() {
let content;
if (this.props.email === '') {
- content = <p>{'No email specified.'}</p>;
+ content = (
+ <p>
+ <FormattedMessage
+ id='claim.account.noEmail'
+ defaultMessage='No email specified'
+ />
+ </p>
+ );
} else if (this.props.currentType === '' && this.props.newType !== '') {
content = (
<EmailToSSO
diff --git a/web/react/components/claim/email_to_sso.jsx b/web/react/components/claim/email_to_sso.jsx
index ac0cf876b..3d4b9e91f 100644
--- a/web/react/components/claim/email_to_sso.jsx
+++ b/web/react/components/claim/email_to_sso.jsx
@@ -4,7 +4,20 @@
import * as Utils from '../../utils/utils.jsx';
import * as Client from '../../utils/client.jsx';
-export default class EmailToSSO extends React.Component {
+import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl';
+
+const holders = defineMessages({
+ pwdError: {
+ id: 'claim.email_to_sso.pwdError',
+ defaultMessage: 'Please enter your password.'
+ },
+ pwd: {
+ id: 'claim.email_to_sso.pwd',
+ defaultMessage: 'Password'
+ }
+});
+
+class EmailToSSO extends React.Component {
constructor(props) {
super(props);
@@ -18,7 +31,7 @@ export default class EmailToSSO extends React.Component {
var password = ReactDOM.findDOMNode(this.refs.password).value.trim();
if (!password) {
- state.error = 'Please enter your password.';
+ state.error = this.props.intl.formatMessage(holders.pwdError);
this.setState(state);
return;
}
@@ -59,17 +72,42 @@ export default class EmailToSSO extends React.Component {
return (
<div className='col-sm-12'>
<div className='signup-team__container'>
- <h3>{'Switch Email/Password Account to ' + uiType}</h3>
+ <h3>
+ <FormattedMessage
+ id='claim.email_to_sso.title'
+ defaultMessage='Switch Email/Password Account to {uiType}'
+ values={{
+ uiType: uiType
+ }}
+ />
+ </h3>
<form onSubmit={this.submit}>
- <p>{'Upon claiming your account, you will only be able to login with ' + Utils.toTitleCase(this.props.type) + ' SSO.'}</p>
- <p>{'Enter the password for your ' + this.props.teamDisplayName + ' ' + global.window.mm_config.SiteName + ' account.'}</p>
+ <p>
+ <FormattedMessage
+ id='claim.email_to_sso.ssoType'
+ defaultMessage='Upon claiming your account, you will only be able to login with {type} SSO'
+ values={{
+ type: Utils.toTitleCase(this.props.type)
+ }}
+ />
+ </p>
+ <p>
+ <FormattedMessage
+ id='claim.email_to_sso.enterPwd'
+ defaultMessage='Enter the password for your {team} {site} account'
+ values={{
+ team: this.props.teamDisplayName,
+ site: global.window.mm_config.SiteName
+ }}
+ />
+ </p>
<div className={formClass}>
<input
type='password'
className='form-control'
name='password'
ref='password'
- placeholder='Password'
+ placeholder={this.props.intl.formatMessage(holders.pwd)}
spellCheck='false'
/>
</div>
@@ -78,7 +116,13 @@ export default class EmailToSSO extends React.Component {
type='submit'
className='btn btn-primary'
>
- {'Switch account to ' + uiType}
+ <FormattedMessage
+ id='claim.email_to_sso.switchTo'
+ defaultMessage='Switch account to {uiType}'
+ values={{
+ uiType: uiType
+ }}
+ />
</button>
</form>
</div>
@@ -90,8 +134,11 @@ export default class EmailToSSO extends React.Component {
EmailToSSO.defaultProps = {
};
EmailToSSO.propTypes = {
+ intl: intlShape.isRequired,
type: React.PropTypes.string.isRequired,
email: React.PropTypes.string.isRequired,
teamName: React.PropTypes.string.isRequired,
teamDisplayName: React.PropTypes.string.isRequired
};
+
+export default injectIntl(EmailToSSO); \ No newline at end of file
diff --git a/web/react/components/claim/sso_to_email.jsx b/web/react/components/claim/sso_to_email.jsx
index 0868b7f2f..73ff13cc9 100644
--- a/web/react/components/claim/sso_to_email.jsx
+++ b/web/react/components/claim/sso_to_email.jsx
@@ -4,7 +4,28 @@
import * as Utils from '../../utils/utils.jsx';
import * as Client from '../../utils/client.jsx';
-export default class SSOToEmail extends React.Component {
+import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl';
+
+const holders = defineMessages({
+ enterPwd: {
+ id: 'claim.sso_to_email.enterPwd',
+ defaultMessage: 'Please enter a password.'
+ },
+ pwdNotMatch: {
+ id: 'claim.sso_to_email.pwdNotMatch',
+ defaultMessage: 'Password do not match.'
+ },
+ newPwd: {
+ id: 'claim.sso_to_email.newPwd',
+ defaultMessage: 'New Password'
+ },
+ confirm: {
+ id: 'claim.sso_to_email.confirm',
+ defaultMessage: 'Confirm Password'
+ }
+});
+
+class SSOToEmail extends React.Component {
constructor(props) {
super(props);
@@ -13,19 +34,20 @@ export default class SSOToEmail extends React.Component {
this.state = {};
}
submit(e) {
+ const {formatMessage} = this.props.intl;
e.preventDefault();
const state = {};
const password = ReactDOM.findDOMNode(this.refs.password).value.trim();
if (!password) {
- state.error = 'Please enter a password.';
+ state.error = formatMessage(holders.enterPwd);
this.setState(state);
return;
}
const confirmPassword = ReactDOM.findDOMNode(this.refs.passwordconfirm).value.trim();
if (!confirmPassword || password !== confirmPassword) {
- state.error = 'Passwords do not match.';
+ state.error = formatMessage(holders.pwdNotMatch);
this.setState(state);
return;
}
@@ -50,6 +72,7 @@ export default class SSOToEmail extends React.Component {
);
}
render() {
+ const {formatMessage} = this.props.intl;
var error = null;
if (this.state.error) {
error = <div className='form-group has-error'><label className='control-label'>{this.state.error}</label></div>;
@@ -65,17 +88,39 @@ export default class SSOToEmail extends React.Component {
return (
<div className='col-sm-12'>
<div className='signup-team__container'>
- <h3>{'Switch ' + uiType + ' Account to Email'}</h3>
+ <h3>
+ <FormattedMessage
+ id='claim.sso_to_email.title'
+ defaultMessage='Switch {type} Account to Email'
+ values={{
+ type: uiType
+ }}
+ />
+ </h3>
<form onSubmit={this.submit}>
- <p>{'Upon changing your account type, you will only be able to login with your email and password.'}</p>
- <p>{'Enter a new password for your ' + this.props.teamDisplayName + ' ' + global.window.mm_config.SiteName + ' account.'}</p>
+ <p>
+ <FormattedMessage
+ id='claim.sso_to_email.description'
+ defaultMessage='Upon changing your account type, you will only be able to login with your email and password.'
+ />
+ </p>
+ <p>
+ <FormattedMessage
+ id='claim.sso_to_email_newPwd'
+ defaultMessage='Enter a new password for your {team} {site} account'
+ values={{
+ team: this.props.teamDisplayName,
+ site: global.window.mm_config.SiteName
+ }}
+ />
+ </p>
<div className={formClass}>
<input
type='password'
className='form-control'
name='password'
ref='password'
- placeholder='New Password'
+ placeholder={formatMessage(holders.newPwd)}
spellCheck='false'
/>
</div>
@@ -85,7 +130,7 @@ export default class SSOToEmail extends React.Component {
className='form-control'
name='passwordconfirm'
ref='passwordconfirm'
- placeholder='Confirm Password'
+ placeholder={formatMessage(holders.confirm)}
spellCheck='false'
/>
</div>
@@ -94,7 +139,13 @@ export default class SSOToEmail extends React.Component {
type='submit'
className='btn btn-primary'
>
- {'Switch ' + uiType + ' account to email and password'}
+ <FormattedMessage
+ id='claim.sso_to_email.switchTo'
+ defaultMessage='Switch {type} to email and password'
+ values={{
+ type: uiType
+ }}
+ />
</button>
</form>
</div>
@@ -106,8 +157,11 @@ export default class SSOToEmail extends React.Component {
SSOToEmail.defaultProps = {
};
SSOToEmail.propTypes = {
+ intl: intlShape.isRequired,
currentType: React.PropTypes.string.isRequired,
email: React.PropTypes.string.isRequired,
teamName: React.PropTypes.string.isRequired,
teamDisplayName: React.PropTypes.string.isRequired
};
+
+export default injectIntl(SSOToEmail); \ No newline at end of file
diff --git a/web/react/components/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/docs.jsx b/web/react/components/docs.jsx
index 188ca340b..6d3a109c2 100644
--- a/web/react/components/docs.jsx
+++ b/web/react/components/docs.jsx
@@ -13,7 +13,7 @@ export default class Docs extends React.Component {
const errorState = {text: '## 404'};
if (props.site) {
- $.get('/static/help/' + props.site + '.md').then((response) => {
+ $.get(`/static/help/${props.site}_${global.window.mm_locale}.md`).then((response) => {
this.setState({text: response});
}, () => {
this.setState(errorState);
diff --git a/web/react/components/email_verify.jsx b/web/react/components/email_verify.jsx
index 9c07853b7..ef1a62130 100644
--- a/web/react/components/email_verify.jsx
+++ b/web/react/components/email_verify.jsx
@@ -1,6 +1,8 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
+import {FormattedMessage, FormattedHTMLMessage} from 'mm-intl';
+
export default class EmailVerify extends React.Component {
constructor(props) {
super(props);
@@ -19,21 +21,61 @@ export default class EmailVerify extends React.Component {
var resend = '';
var resendConfirm = '';
if (this.props.isVerified === 'true') {
- title = global.window.mm_config.SiteName + ' Email Verified';
- body = <p>Your email has been verified! Click <a href={this.props.teamURL + '?email=' + this.props.userEmail}>here</a> to log in.</p>;
+ title = (
+ <FormattedMessage
+ id='email_verify.verified'
+ defaultMessage='{siteName} Email Verified'
+ values={{
+ siteName: global.window.mm_config.SiteName
+ }}
+ />
+ );
+ body = (
+ <FormattedHTMLMessage
+ id='email_verify.verifiedBody'
+ defaultMessage='<p>Your email has been verified! Click <a href={url}>here</a> to log in.</p>'
+ values={{
+ url: this.props.teamURL + '?email=' + this.props.userEmail
+ }}
+ />
+ );
} else {
- title = global.window.mm_config.SiteName + ': You are almost done';
- body = <p>Please verify your email address. Check your inbox for an email.</p>;
+ title = (
+ <FormattedMessage
+ id='email_verify.almost'
+ defaultMessage='{siteName}: You are almost done'
+ values={{
+ siteName: global.window.mm_config.SiteName
+ }}
+ />
+ );
+ body = (
+ <p>
+ <FormattedMessage
+ id='email_verify.notVerifiedBody'
+ defaultMessage='Please verify your email address. Check your inbox for an email.'
+ />
+ </p>
+ );
resend = (
<button
onClick={this.handleResend}
className='btn btn-primary'
>
- Resend Email
+ <FormattedMessage
+ id='email_verify.resend'
+ defaultMessage='Resend Email'
+ />
</button>
);
if (this.props.resendSuccess) {
- resendConfirm = <div><br /><p className='alert alert-success'><i className='fa fa-check'></i>{' Verification email sent.'}</p></div>;
+ resendConfirm = (
+ <div><br /><p className='alert alert-success'><i className='fa fa-check'></i>
+ <FormattedMessage
+ id='email_verify.sent'
+ defaultMessage=' Verification email sent.'
+ />
+ </p></div>);
}
}
diff --git a/web/react/components/error_bar.jsx b/web/react/components/error_bar.jsx
index 921e8afe1..f04185b46 100644
--- a/web/react/components/error_bar.jsx
+++ b/web/react/components/error_bar.jsx
@@ -3,6 +3,16 @@
import ErrorStore from '../stores/error_store.jsx';
+// import mm-intl is required for the tool to be able to extract the messages
+import {defineMessages} from 'mm-intl';
+
+var messages = defineMessages({
+ preview: {
+ id: 'error_bar.preview_mode',
+ defaultMessage: 'Preview Mode: Email notifications have not been configured'
+ }
+});
+
export default class ErrorBar extends React.Component {
constructor() {
super();
@@ -13,6 +23,12 @@ export default class ErrorBar extends React.Component {
this.state = ErrorStore.getLastError();
}
+ static propTypes() {
+ return {
+ intl: ReactIntl.intlShape.isRequired
+ };
+ }
+
isValidError(s) {
if (!s) {
return false;
@@ -41,6 +57,13 @@ export default class ErrorBar extends React.Component {
return false;
}
+ componentWillMount() {
+ if (global.window.mm_config.SendEmailNotifications === 'false') {
+ ErrorStore.storeLastError({message: this.props.intl.formatMessage(messages.preview)});
+ this.onErrorChange();
+ }
+ }
+
componentDidMount() {
ErrorStore.addChangeListener(this.onErrorChange);
}
@@ -64,6 +87,7 @@ export default class ErrorBar extends React.Component {
e.preventDefault();
}
+ ErrorStore.clearLastError();
this.setState({message: null});
}
@@ -86,3 +110,5 @@ export default class ErrorBar extends React.Component {
);
}
}
+
+export default ReactIntl.injectIntl(ErrorBar);
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/find_team.jsx b/web/react/components/find_team.jsx
index 94ca48dbf..3ff9787ad 100644
--- a/web/react/components/find_team.jsx
+++ b/web/react/components/find_team.jsx
@@ -4,7 +4,20 @@
import * as utils from '../utils/utils.jsx';
import * as client from '../utils/client.jsx';
-export default class FindTeam extends React.Component {
+import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'mm-intl';
+
+var holders = defineMessages({
+ submitError: {
+ id: 'find_team.submitError',
+ defaultMessage: 'Please enter a valid email address'
+ },
+ placeholder: {
+ id: 'find_team.placeholder',
+ defaultMessage: 'you@domain.com'
+ }
+});
+
+class FindTeam extends React.Component {
constructor(props) {
super(props);
this.state = {};
@@ -19,7 +32,7 @@ export default class FindTeam extends React.Component {
var email = ReactDOM.findDOMNode(this.refs.email).value.trim().toLowerCase();
if (!email || !utils.isEmail(email)) {
- state.email_error = 'Please enter a valid email address';
+ state.email_error = this.props.intl.formatMessage(holders.submitError);
this.setState(state);
return;
}
@@ -50,25 +63,50 @@ export default class FindTeam extends React.Component {
if (this.state.sent) {
return (
<div>
- <h4>{'Find Your teams'}</h4>
- <p>{'An email was sent with links to any teams to which you are a member.'}</p>
+ <h4>
+ <FormattedMessage
+ id='find_team.findTitle'
+ defaultMessage='Find Your Team'
+ />
+ </h4>
+ <p>
+ <FormattedMessage
+ id='find_team.findDescription'
+ defaultMessage='An email was sent with links to any teams to which you are a member.'
+ />
+ </p>
</div>
);
}
return (
<div>
- <h4>Find Your Team</h4>
+ <h4>
+ <FormattedMessage
+ id='find_team.findTitle'
+ defaultMessage='Find Your Team'
+ />
+ </h4>
<form onSubmit={this.handleSubmit}>
- <p>{'Get an email with links to any teams to which you are a member.'}</p>
+ <p>
+ <FormattedMessage
+ id='find_team.getLinks'
+ defaultMessage='Get an email with links to any teams to which you are a member.'
+ />
+ </p>
<div className='form-group'>
- <label className='control-label'>Email</label>
+ <label className='control-label'>
+ <FormattedMessage
+ id='find_team.email'
+ defaultMessage='Email'
+ />
+ </label>
<div className={emailErrorClass}>
<input
type='text'
ref='email'
className='form-control'
- placeholder='you@domain.com'
+ placeholder={this.props.intl.formatMessage(holders.placeholder)}
maxLength='128'
spellCheck='false'
/>
@@ -79,10 +117,19 @@ export default class FindTeam extends React.Component {
className='btn btn-md btn-primary'
type='submit'
>
- Send
+ <FormattedMessage
+ id='find_team.send'
+ defaultMessage='Send'
+ />
</button>
</form>
</div>
);
}
}
+
+FindTeam.propTypes = {
+ intl: intlShape.isRequired
+};
+
+export default injectIntl(FindTeam); \ No newline at end of file
diff --git a/web/react/components/get_link_modal.jsx b/web/react/components/get_link_modal.jsx
index fd20834f4..3fc71ff96 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 {
@@ -59,14 +61,25 @@ 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>
);
}
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 (
@@ -92,7 +105,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..883871267 100644
--- a/web/react/components/get_team_invite_link_modal.jsx
+++ b/web/react/components/get_team_invite_link_modal.jsx
@@ -6,7 +6,20 @@ 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.'
+ }
+});
+
+class GetTeamInviteLinkModal extends React.Component {
constructor(props) {
super(props);
@@ -32,14 +45,22 @@ export default class GetTeamInviteLinkModal extends React.Component {
}
render() {
+ const {formatMessage} = this.props.intl;
+
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={formatMessage(holders.help)}
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/loading_screen.jsx b/web/react/components/loading_screen.jsx
index 9849205f2..143b94467 100644
--- a/web/react/components/loading_screen.jsx
+++ b/web/react/components/loading_screen.jsx
@@ -1,6 +1,8 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
+import {FormattedMessage} from 'mm-intl';
+
export default class LoadingScreen extends React.Component {
constructor(props) {
super(props);
@@ -13,7 +15,12 @@ export default class LoadingScreen extends React.Component {
style={{position: this.props.position}}
>
<div className='loading__content'>
- <h3>Loading</h3>
+ <h3>
+ <FormattedMessage
+ id='loading_screen.loading'
+ defaultMessage='Loading'
+ />
+ </h3>
<div className='round round-1'></div>
<div className='round round-2'></div>
<div className='round round-3'></div>
diff --git a/web/react/components/login.jsx b/web/react/components/login.jsx
index 6887489a7..c4f530af0 100644
--- a/web/react/components/login.jsx
+++ b/web/react/components/login.jsx
@@ -7,7 +7,7 @@ import LoginLdap from './login_ldap.jsx';
import * as Utils from '../utils/utils.jsx';
import Constants from '../utils/constants.jsx';
-var FormattedMessage = ReactIntl.FormattedMessage;
+import {FormattedMessage} from 'mm-intl';
export default class Login extends React.Component {
constructor(props) {
@@ -24,10 +24,16 @@ export default class Login extends React.Component {
loginMessage.push(
<a
className='btn btn-custom-login gitlab'
+ key='gitlab'
href={'/' + teamName + '/login/gitlab'}
>
<span className='icon' />
- <span>{'with GitLab'}</span>
+ <span>
+ <FormattedMessage
+ id='login.gitlab'
+ defaultMessage='with GitLab'
+ />
+ </span>
</a>
);
}
@@ -36,10 +42,16 @@ export default class Login extends React.Component {
loginMessage.push(
<a
className='btn btn-custom-login google'
+ key='google'
href={'/' + teamName + '/login/google'}
>
<span className='icon' />
- <span>{'with Google Apps'}</span>
+ <span>
+ <FormattedMessage
+ id='login.google'
+ defaultMessage='with Google Apps'
+ />
+ </span>
</a>
);
}
@@ -49,9 +61,19 @@ export default class Login extends React.Component {
if (extraParam) {
let msg;
if (extraParam === Constants.SIGNIN_CHANGE) {
- msg = ' Sign-in method changed successfully';
+ msg = (
+ <FormattedMessage
+ id='login.changed'
+ defaultMessage=' Sign-in method changed successfully'
+ />
+ );
} else if (extraParam === Constants.SIGNIN_VERIFIED) {
- msg = ' Email Verified';
+ msg = (
+ <FormattedMessage
+ id='login.verified'
+ defaultMessage=' Email Verified'
+ />
+ );
}
if (msg != null) {
@@ -78,7 +100,12 @@ export default class Login extends React.Component {
<div>
{loginMessage}
<div className='or__container'>
- <span>{'or'}</span>
+ <span>
+ <FormattedMessage
+ id='login.or'
+ defaultMessage='or'
+ />
+ </span>
</div>
</div>
);
@@ -90,7 +117,7 @@ export default class Login extends React.Component {
<div className='form-group'>
<a href={'/' + teamName + '/reset_password'}>
<FormattedMessage
- id='login.forgot_password'
+ id='login.forgot'
defaultMessage='I forgot my password'
/>
</a>
@@ -102,12 +129,19 @@ export default class Login extends React.Component {
if (this.props.inviteId) {
userSignUp = (
<div>
- <span>{`Don't have an account? `}
+ <span>
+ <FormattedMessage
+ id='login.noAccount'
+ defaultMessage="Don't have an account? "
+ />
<a
href={'/signup_user_complete/?id=' + this.props.inviteId}
className='signup-team-login'
>
- {'Create one now'}
+ <FormattedMessage
+ id='login.create'
+ defaultMessage='Create one now'
+ />
</a>
</span>
</div>
@@ -115,14 +149,17 @@ export default class Login extends React.Component {
}
let teamSignUp = null;
- if (global.window.mm_config.EnableTeamCreation === 'true') {
+ if (global.window.mm_config.EnableTeamCreation === 'true' && !Utils.isMobileApp()) {
teamSignUp = (
<div className='margin--extra'>
<a
href='/'
className='signup-team-login'
>
- {'Create a new team'}
+ <FormattedMessage
+ id='login.createTeam'
+ defaultMessage='Create a new team'
+ />
</a>
</div>
);
@@ -137,25 +174,45 @@ export default class Login extends React.Component {
);
}
+ let findTeams = null;
+ if (!Utils.isMobileApp()) {
+ findTeams = (
+ <div className='form-group margin--extra form-group--small'>
+ <span>
+ <a href='/find_team'>
+ <FormattedMessage
+ id='login.find'
+ defaultMessage='Find your other teams'
+ />
+ </a></span>
+ </div>
+ );
+ }
+
return (
<div className='signup-team__container'>
- <h5 className='margin--less'>{'Sign in to:'}</h5>
+ <h5 className='margin--less'>
+ <FormattedMessage
+ id='login.signTo'
+ defaultMessage='Sign in to:'
+ />
+ </h5>
<h2 className='signup-team__name'>{teamDisplayName}</h2>
- <h2 className='signup-team__subdomain'>{'on '}{global.window.mm_config.SiteName}</h2>
+ <h2 className='signup-team__subdomain'>
+ <FormattedMessage
+ id='login.on'
+ defaultMessage='on {siteName}'
+ values={{
+ siteName: global.window.mm_config.SiteName
+ }}
+ />
+ </h2>
{extraBox}
{loginMessage}
{emailSignup}
{ldapLogin}
{userSignUp}
- <div className='form-group margin--extra form-group--small'>
- <span>
- <a href='/find_team'>
- <FormattedMessage
- id='login.find_teams'
- defaultMessage='Find your other teams'
- />
- </a></span>
- </div>
+ {findTeams}
{forgotPassword}
{teamSignUp}
</div>
diff --git a/web/react/components/login_email.jsx b/web/react/components/login_email.jsx
index cfe34d1c7..cf1e1bc40 100644
--- a/web/react/components/login_email.jsx
+++ b/web/react/components/login_email.jsx
@@ -5,7 +5,32 @@ import * as Utils from '../utils/utils.jsx';
import * as Client from '../utils/client.jsx';
import UserStore from '../stores/user_store.jsx';
-export default class LoginEmail extends React.Component {
+import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'mm-intl';
+
+var holders = defineMessages({
+ badTeam: {
+ id: 'login_email.badTeam',
+ defaultMessage: 'Bad team name'
+ },
+ emailReq: {
+ id: 'login_email.emailReq',
+ defaultMessage: 'An email is required'
+ },
+ pwdReq: {
+ id: 'login_email.pwdReq',
+ defaultMessage: 'A password is required'
+ },
+ email: {
+ id: 'login_email.email',
+ defaultMessage: 'Email'
+ },
+ pwd: {
+ id: 'login_email.pwd',
+ defaultMessage: 'Password'
+ }
+});
+
+class LoginEmail extends React.Component {
constructor(props) {
super(props);
@@ -17,25 +42,26 @@ export default class LoginEmail extends React.Component {
}
handleSubmit(e) {
e.preventDefault();
+ const {formatMessage} = this.props.intl;
var state = {};
const name = this.props.teamName;
if (!name) {
- state.serverError = 'Bad team name';
+ state.serverError = formatMessage(holders.badTeam);
this.setState(state);
return;
}
const email = this.refs.email.value.trim();
if (!email) {
- state.serverError = 'An email is required';
+ state.serverError = formatMessage(holders.emailReq);
this.setState(state);
return;
}
const password = this.refs.password.value.trim();
if (!password) {
- state.serverError = 'A password is required';
+ state.serverError = formatMessage(holders.pwdReq);
this.setState(state);
return;
}
@@ -55,7 +81,7 @@ export default class LoginEmail extends React.Component {
}
},
(err) => {
- if (err.message === 'Login failed because email address has not been verified') {
+ if (err.id === 'api.user.login.not_verified.app_error') {
window.location.href = '/verify_email?teamname=' + encodeURIComponent(name) + '&email=' + encodeURIComponent(email);
return;
}
@@ -87,6 +113,7 @@ export default class LoginEmail extends React.Component {
priorEmail = decodeURIComponent(emailParam);
}
+ const {formatMessage} = this.props.intl;
return (
<form onSubmit={this.handleSubmit}>
<div className='signup__email-container'>
@@ -101,7 +128,7 @@ export default class LoginEmail extends React.Component {
name='email'
defaultValue={priorEmail}
ref='email'
- placeholder='Email'
+ placeholder={formatMessage(holders.email)}
spellCheck='false'
/>
</div>
@@ -112,7 +139,7 @@ export default class LoginEmail extends React.Component {
className='form-control'
name='password'
ref='password'
- placeholder='Password'
+ placeholder={formatMessage(holders.pwd)}
spellCheck='false'
/>
</div>
@@ -121,7 +148,10 @@ export default class LoginEmail extends React.Component {
type='submit'
className='btn btn-primary'
>
- {'Sign in'}
+ <FormattedMessage
+ id='login_email.signin'
+ defaultMessage='Sign in'
+ />
</button>
</div>
</div>
@@ -133,5 +163,8 @@ LoginEmail.defaultProps = {
};
LoginEmail.propTypes = {
+ intl: intlShape.isRequired,
teamName: React.PropTypes.string.isRequired
};
+
+export default injectIntl(LoginEmail); \ No newline at end of file
diff --git a/web/react/components/login_ldap.jsx b/web/react/components/login_ldap.jsx
index 1e0e32f4f..d67f15fa5 100644
--- a/web/react/components/login_ldap.jsx
+++ b/web/react/components/login_ldap.jsx
@@ -4,7 +4,32 @@
import * as Utils from '../utils/utils.jsx';
import * as Client from '../utils/client.jsx';
-export default class LoginLdap extends React.Component {
+import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'mm-intl';
+
+const holders = defineMessages({
+ badTeam: {
+ id: 'login_ldap.badTeam',
+ defaultMessage: 'Bad team name'
+ },
+ idReq: {
+ id: 'login_ldap.idlReq',
+ defaultMessage: 'An LDAP ID is required'
+ },
+ pwdReq: {
+ id: 'login_ldap.pwdReq',
+ defaultMessage: 'An LDAP password is required'
+ },
+ username: {
+ id: 'login_ldap.username',
+ defaultMessage: 'LDAP Username'
+ },
+ pwd: {
+ id: 'login_ldap.pwd',
+ defaultMessage: 'LDAP Password'
+ }
+});
+
+class LoginLdap extends React.Component {
constructor(props) {
super(props);
@@ -16,25 +41,26 @@ export default class LoginLdap extends React.Component {
}
handleSubmit(e) {
e.preventDefault();
+ const {formatMessage} = this.props.intl;
var state = {};
const teamName = this.props.teamName;
if (!teamName) {
- state.serverError = 'Bad team name';
+ state.serverError = formatMessage(holders.badTeam);
this.setState(state);
return;
}
const id = this.refs.id.value.trim();
if (!id) {
- state.serverError = 'An LDAP ID is required';
+ state.serverError = formatMessage(holders.idReq);
this.setState(state);
return;
}
const password = this.refs.password.value.trim();
if (!password) {
- state.serverError = 'An LDAP password is required';
+ state.serverError = formatMessage(holders.pwdReq);
this.setState(state);
return;
}
@@ -64,7 +90,7 @@ export default class LoginLdap extends React.Component {
serverError = <label className='control-label'>{this.state.serverError}</label>;
errorClass = ' has-error';
}
-
+ const {formatMessage} = this.props.intl;
return (
<form onSubmit={this.handleSubmit}>
<div className='signup__email-container'>
@@ -76,7 +102,7 @@ export default class LoginLdap extends React.Component {
autoFocus={true}
className='form-control'
ref='id'
- placeholder='LDAP Username'
+ placeholder={formatMessage(holders.username)}
spellCheck='false'
/>
</div>
@@ -85,7 +111,7 @@ export default class LoginLdap extends React.Component {
type='password'
className='form-control'
ref='password'
- placeholder='LDAP Password'
+ placeholder={formatMessage(holders.pwd)}
spellCheck='false'
/>
</div>
@@ -94,7 +120,10 @@ export default class LoginLdap extends React.Component {
type='submit'
className='btn btn-primary'
>
- {'Sign in'}
+ <FormattedMessage
+ id='login_ldap.signin'
+ defaultMessage='Sign in'
+ />
</button>
</div>
</div>
@@ -106,5 +135,8 @@ LoginLdap.defaultProps = {
};
LoginLdap.propTypes = {
+ intl: intlShape.isRequired,
teamName: React.PropTypes.string.isRequired
};
+
+export default injectIntl(LoginLdap); \ No newline at end of file
diff --git a/web/react/components/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 78b67a216..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);
@@ -25,9 +35,17 @@ export default class MsgTyping extends React.Component {
SocketStore.addChangeListener(this.onChange);
}
- componentWillReceiveProps(newProps) {
- if (this.props.channelId !== newProps.channelId) {
- this.updateTypingText();
+ componentWillReceiveProps(nextProps) {
+ if (this.props.channelId !== nextProps.channelId) {
+ for (const u in this.typingUsers) {
+ if (!this.typingUsers.hasOwnProperty(u)) {
+ continue;
+ }
+
+ clearTimeout(this.typingUsers[u]);
+ }
+ this.typingUsers = {};
+ this.setState({text: ''});
}
}
@@ -36,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;
}
@@ -72,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;
}
}
@@ -92,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 70fe10eef..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);
@@ -22,12 +32,17 @@ export default class NewChannelModal extends React.Component {
});
}
}
+ componentDidMount() {
+ if (Utils.isBrowserIE()) {
+ $('body').addClass('browser--IE');
+ }
+ }
handleSubmit(e) {
e.preventDefault();
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;
}
@@ -46,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';
}
@@ -58,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>
);
@@ -97,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'
@@ -108,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}
@@ -128,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>
@@ -136,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>
@@ -163,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}
@@ -171,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>
@@ -187,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,
@@ -197,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/password_reset_form.jsx b/web/react/components/password_reset_form.jsx
index 8063db05a..380dbe973 100644
--- a/web/react/components/password_reset_form.jsx
+++ b/web/react/components/password_reset_form.jsx
@@ -4,7 +4,24 @@
import * as Client from '../utils/client.jsx';
import Constants from '../utils/constants.jsx';
-export default class PasswordResetForm extends React.Component {
+import {injectIntl, intlShape, defineMessages, FormattedMessage, FormattedHTMLMessage} from 'mm-intl';
+
+const holders = defineMessages({
+ error: {
+ id: 'password_form.error',
+ defaultMessage: 'Please enter at least {chars} characters.'
+ },
+ update: {
+ id: 'password_form.update',
+ defaultMessage: 'Your password has been updated successfully.'
+ },
+ pwd: {
+ id: 'password_form.pwd',
+ defaultMessage: 'Password'
+ }
+});
+
+class PasswordResetForm extends React.Component {
constructor(props) {
super(props);
@@ -14,11 +31,13 @@ export default class PasswordResetForm extends React.Component {
}
handlePasswordReset(e) {
e.preventDefault();
+
+ const {formatMessage} = this.props.intl;
var state = {};
var password = ReactDOM.findDOMNode(this.refs.password).value.trim();
if (!password || password.length < Constants.MIN_PASSWORD_LENGTH) {
- state.error = 'Please enter at least ' + Constants.MIN_PASSWORD_LENGTH + ' characters.';
+ state.error = formatMessage(holders.error, {chars: Constants.MIN_PASSWORD_LENGTH});
this.setState(state);
return;
}
@@ -34,7 +53,7 @@ export default class PasswordResetForm extends React.Component {
Client.resetPassword(data,
function resetSuccess() {
- this.setState({error: null, updateText: 'Your password has been updated successfully.'});
+ this.setState({error: null, updateText: formatMessage(holders.update)});
}.bind(this),
function resetFailure(err) {
this.setState({error: err.message, updateText: null});
@@ -44,7 +63,15 @@ export default class PasswordResetForm extends React.Component {
render() {
var updateText = null;
if (this.state.updateText) {
- updateText = <div className='form-group'><br/><label className='control-label reset-form'>{this.state.updateText} Click <a href={'/' + this.props.teamName + '/login'}>here</a> to log in.</label></div>;
+ updateText = (<div className='form-group'><br/><label className='control-label reset-form'>{this.state.updateText}
+ <FormattedHTMLMessage
+ id='password_form.click'
+ defaultMessage='Click <a href={url}>here</a> to log in.'
+ values={{
+ url: '/' + this.props.teamName + '/login'
+ }}
+ />
+ </label></div>);
}
var error = null;
@@ -57,19 +84,34 @@ export default class PasswordResetForm extends React.Component {
formClass += ' has-error';
}
+ const {formatMessage} = this.props.intl;
return (
<div className='col-sm-12'>
<div className='signup-team__container'>
- <h3>{'Password Reset'}</h3>
+ <h3>
+ <FormattedMessage
+ id='password_form.title'
+ defaultMessage='Password Reset'
+ />
+ </h3>
<form onSubmit={this.handlePasswordReset}>
- <p>{'Enter a new password for your ' + this.props.teamDisplayName + ' ' + global.window.mm_config.SiteName + ' account.'}</p>
+ <p>
+ <FormattedMessage
+ id='password_form.enter'
+ defaultMessage='Enter a new password for your {teamDisplayName} {siteName} account.'
+ values={{
+ teamDisplayName: this.props.teamDisplayName,
+ siteName: global.window.mm_config.SiteName
+ }}
+ />
+ </p>
<div className={formClass}>
<input
type='password'
className='form-control'
name='password'
ref='password'
- placeholder='Password'
+ placeholder={formatMessage(holders.pwd)}
spellCheck='false'
/>
</div>
@@ -78,7 +120,10 @@ export default class PasswordResetForm extends React.Component {
type='submit'
className='btn btn-primary'
>
- {'Change my password'}
+ <FormattedMessage
+ id='password_form.change'
+ defaultMessage='Change my password'
+ />
</button>
{updateText}
</form>
@@ -95,8 +140,11 @@ PasswordResetForm.defaultProps = {
data: ''
};
PasswordResetForm.propTypes = {
+ intl: intlShape.isRequired,
teamName: React.PropTypes.string,
teamDisplayName: React.PropTypes.string,
hash: React.PropTypes.string,
data: React.PropTypes.string
};
+
+export default injectIntl(PasswordResetForm); \ No newline at end of file
diff --git a/web/react/components/password_reset_send_link.jsx b/web/react/components/password_reset_send_link.jsx
index 051b8b02c..8cc8a050d 100644
--- a/web/react/components/password_reset_send_link.jsx
+++ b/web/react/components/password_reset_send_link.jsx
@@ -4,7 +4,28 @@
import * as Utils from '../utils/utils.jsx';
import * as client from '../utils/client.jsx';
-export default class PasswordResetSendLink extends React.Component {
+import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'mm-intl';
+
+const holders = defineMessages({
+ error: {
+ id: 'password_send.error',
+ defaultMessage: 'Please enter a valid email address.'
+ },
+ link: {
+ id: 'password_send.link',
+ defaultMessage: '<p>A password reset link has been sent to <b>{email}</b> for your <b>{teamDisplayName}</b> team on {hostname}.</p>'
+ },
+ checkInbox: {
+ id: 'password_send.checkInbox',
+ defaultMessage: 'Please check your inbox.'
+ },
+ email: {
+ id: 'password_send.email',
+ defaultMessage: 'Email'
+ }
+});
+
+class PasswordResetSendLink extends React.Component {
constructor(props) {
super(props);
@@ -15,10 +36,11 @@ export default class PasswordResetSendLink extends React.Component {
handleSendLink(e) {
e.preventDefault();
var state = {};
+ const {formatMessage} = this.props.intl;
var email = ReactDOM.findDOMNode(this.refs.email).value.trim().toLowerCase();
if (!email || !Utils.isEmail(email)) {
- state.error = 'Please enter a valid email address.';
+ state.error = formatMessage(holders.error);
this.setState(state);
return;
}
@@ -32,7 +54,7 @@ export default class PasswordResetSendLink extends React.Component {
client.sendPasswordReset(data,
function passwordResetSent() {
- this.setState({error: null, updateText: <p>A password reset link has been sent to <b>{email}</b> for your <b>{this.props.teamDisplayName}</b> team on {window.location.hostname}.</p>, moreUpdateText: 'Please check your inbox.'});
+ this.setState({error: null, updateText: formatMessage(holders.link, {email: email, teamDisplayName: this.props.teamDisplayName, hostname: window.location.hostname}), moreUpdateText: formatMessage(holders.checkInbox)});
$(ReactDOM.findDOMNode(this.refs.reset_form)).hide();
}.bind(this),
function passwordResetFailedToSend(err) {
@@ -43,7 +65,12 @@ export default class PasswordResetSendLink extends React.Component {
render() {
var updateText = null;
if (this.state.updateText) {
- updateText = <div className='reset-form alert alert-success'>{this.state.updateText}{this.state.moreUpdateText}</div>;
+ updateText = (
+ <div className='reset-form alert alert-success'
+ dangerouslySetInnerHTML={{__html: this.state.updateText + this.state.moreUpdateText}}
+ >
+ </div>
+ );
}
var error = null;
@@ -56,23 +83,37 @@ export default class PasswordResetSendLink extends React.Component {
formClass += ' has-error';
}
+ const {formatMessage} = this.props.intl;
return (
<div className='col-sm-12'>
<div className='signup-team__container'>
- <h3>Password Reset</h3>
+ <h3>
+ <FormattedMessage
+ id='password_send.title'
+ defaultMessage='Password Reset'
+ />
+ </h3>
{updateText}
<form
onSubmit={this.handleSendLink}
ref='reset_form'
>
- <p>{'To reset your password, enter the email address you used to sign up for ' + this.props.teamDisplayName + '.'}</p>
+ <p>
+ <FormattedMessage
+ id='password_send.description'
+ defaultMessage='To reset your password, enter the email address you used to sign up for {teamName}.'
+ values={{
+ teamName: this.props.teamDisplayName
+ }}
+ />
+ </p>
<div className={formClass}>
<input
type='email'
className='form-control'
name='email'
ref='email'
- placeholder='Email'
+ placeholder={formatMessage(holders.email)}
spellCheck='false'
/>
</div>
@@ -81,7 +122,10 @@ export default class PasswordResetSendLink extends React.Component {
type='submit'
className='btn btn-primary'
>
- Reset my password
+ <FormattedMessage
+ id='password_send.reset'
+ defaultMessage='Reset my password'
+ />
</button>
</form>
</div>
@@ -95,6 +139,9 @@ PasswordResetSendLink.defaultProps = {
teamDisplayName: ''
};
PasswordResetSendLink.propTypes = {
+ intl: intlShape.isRequired,
teamName: React.PropTypes.string,
teamDisplayName: React.PropTypes.string
};
+
+export default injectIntl(PasswordResetSendLink); \ No newline at end of file
diff --git a/web/react/components/post_info.jsx b/web/react/components/post_info.jsx
index 26bd6adde..2bff675a9 100644
--- a/web/react/components/post_info.jsx
+++ b/web/react/components/post_info.jsx
@@ -44,19 +44,19 @@ export default class PostInfo extends React.Component {
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>
- );
+ <li
+ key='replyLink'
+ role='presentation'
+ >
+ <a
+ className='link__reply theme'
+ href='#'
+ onClick={this.props.handleCommentClick}
+ >
+ {'Reply'}
+ </a>
+ </li>
+ );
}
dropdownContents.push(
@@ -173,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>
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/signup_team.jsx b/web/react/components/signup_team.jsx
index a554427d5..098e9f65a 100644
--- a/web/react/components/signup_team.jsx
+++ b/web/react/components/signup_team.jsx
@@ -6,6 +6,8 @@ import EmailSignUpPage from './team_signup_with_email.jsx';
import SSOSignupPage from './team_signup_with_sso.jsx';
import Constants from '../utils/constants.jsx';
+import {FormattedMessage} from 'mm-intl';
+
export default class TeamSignUp extends React.Component {
constructor(props) {
super(props);
@@ -43,12 +45,24 @@ export default class TeamSignUp extends React.Component {
if (global.window.mm_config.EnableTeamListing === 'true') {
if (this.props.teams.length === 0) {
if (global.window.mm_config.EnableTeamCreation !== 'true') {
- teamListing = (<div>{'There are no teams include in the Team Directory and team creation has been disabled.'}</div>);
+ teamListing = (
+ <div>
+ <FormattedMessage
+ id='signup_team.noTeams'
+ defaultMessage='There are no teams include in the Team Directory and team creation has been disabled.'
+ />
+ </div>
+ );
}
} else {
teamListing = (
<div>
- <h4>{'Choose a Team'}</h4>
+ <h4>
+ <FormattedMessage
+ id='signup_team.choose'
+ defaultMessage='Choose a Team'
+ />
+ </h4>
<div className='signup-team-all'>
{
this.props.teams.map((team) => {
@@ -71,7 +85,12 @@ export default class TeamSignUp extends React.Component {
})
}
</div>
- <h4>{'Or Create a Team'}</h4>
+ <h4>
+ <FormattedMessage
+ id='signup_team.createTeam'
+ defaultMessage='Or Create a Team'
+ />
+ </h4>
</div>
);
}
@@ -79,7 +98,14 @@ export default class TeamSignUp extends React.Component {
if (global.window.mm_config.EnableTeamCreation !== 'true') {
if (teamListing == null) {
- return (<div>{'Team creation has been disabled. Please contact an administrator for access.'}</div>);
+ return (
+ <div>
+ <FormattedMessage
+ id='signup_team.disabled'
+ defaultMessage='Team creation has been disabled. Please contact an administrator for access.'
+ />
+ </div>
+ );
}
return (
@@ -122,7 +148,14 @@ export default class TeamSignUp extends React.Component {
</div>
);
} else if (this.state.page === 'none') {
- return (<div>{'No team creation method has been enabled. Please contact an administrator for access.'}</div>);
+ return (
+ <div>
+ <FormattedMessage
+ id='signup_team.none'
+ defaultMessage='No team creation method has been enabled. Please contact an administrator for access.'
+ />
+ </div>
+ );
}
}
}
diff --git a/web/react/components/signup_team_complete.jsx b/web/react/components/signup_team_complete.jsx
index 6c7fd57b3..16553daeb 100644
--- a/web/react/components/signup_team_complete.jsx
+++ b/web/react/components/signup_team_complete.jsx
@@ -9,6 +9,8 @@ import UsernamePage from './team_signup_username_page.jsx';
import PasswordPage from './team_signup_password_page.jsx';
import BrowserStore from '../stores/browser_store.jsx';
+import {FormattedMessage} from 'mm-intl';
+
export default class SignupTeamComplete extends React.Component {
constructor(props) {
super(props);
@@ -96,7 +98,14 @@ export default class SignupTeamComplete extends React.Component {
);
}
- return (<div>You've already completed the signup process for this invitation or this invitation has expired.</div>);
+ return (
+ <div>
+ <FormattedMessage
+ id='signup_team_complete.completed'
+ defaultMessage="You've already completed the signup process for this invitation or this invitation has expired."
+ />
+ </div>
+ );
}
}
diff --git a/web/react/components/signup_team_confirm.jsx b/web/react/components/signup_team_confirm.jsx
new file mode 100644
index 000000000..de83285db
--- /dev/null
+++ b/web/react/components/signup_team_confirm.jsx
@@ -0,0 +1,39 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import {FormattedMessage, FormattedHTMLMessage} from 'mm-intl';
+
+export default class SignupTeamConfirm extends React.Component {
+ constructor(props) {
+ super(props);
+ }
+
+ render() {
+ return (
+ <div className='signup-team__container'>
+ <h3>
+ <FormattedMessage
+ id='signup_team_confirm.title'
+ defaultMessage='Sign up Complete'
+ />
+ </h3>
+ <p>
+ <FormattedHTMLMessage
+ id='signup_team_confirm.checkEmail'
+ defaultMessage='Please check your email: <strong>{email}</strong><br />Your email contains a link to set up your team'
+ values={{
+ email: this.props.email
+ }}
+ />
+ </p>
+ </div>
+ );
+ }
+}
+
+SignupTeamConfirm.defaultProps = {
+ email: ''
+};
+SignupTeamConfirm.propTypes = {
+ email: React.PropTypes.string
+};
diff --git a/web/react/components/signup_user_complete.jsx b/web/react/components/signup_user_complete.jsx
index ace0d28ae..47ec58e98 100644
--- a/web/react/components/signup_user_complete.jsx
+++ b/web/react/components/signup_user_complete.jsx
@@ -7,7 +7,32 @@ import UserStore from '../stores/user_store.jsx';
import BrowserStore from '../stores/browser_store.jsx';
import Constants from '../utils/constants.jsx';
-export default class SignupUserComplete extends React.Component {
+import {intlShape, injectIntl, defineMessages, FormattedMessage, FormattedHTMLMessage} from 'mm-intl';
+
+const holders = defineMessages({
+ required: {
+ id: 'signup_user_completed.required',
+ defaultMessage: 'This field is required'
+ },
+ validEmail: {
+ id: 'signup_user_completed.validEmail',
+ defaultMessage: 'Please enter a valid email address'
+ },
+ reserved: {
+ id: 'signup_user_completed.reserved',
+ defaultMessage: 'This username is reserved, please choose a new one.'
+ },
+ usernameLength: {
+ id: 'signup_user_completed.usernameLength',
+ defaultMessage: 'Username must begin with a letter, and contain between {min} to {max} lowercase characters made up of numbers, letters, and the symbols \'.\', \'-\' and \'_\'.'
+ },
+ passwordLength: {
+ id: 'signup_user_completed.passwordLength',
+ defaultMessage: 'Please enter at least {min} characters'
+ }
+});
+
+class SignupUserComplete extends React.Component {
constructor(props) {
super(props);
@@ -29,30 +54,31 @@ export default class SignupUserComplete extends React.Component {
handleSubmit(e) {
e.preventDefault();
+ const {formatMessage} = this.props.intl;
const providedEmail = ReactDOM.findDOMNode(this.refs.email).value.trim();
if (!providedEmail) {
- this.setState({nameError: '', emailError: 'This field is required', passwordError: ''});
+ this.setState({nameError: '', emailError: formatMessage(holders.required), passwordError: ''});
return;
}
if (!Utils.isEmail(providedEmail)) {
- this.setState({nameError: '', emailError: 'Please enter a valid email address', passwordError: ''});
+ this.setState({nameError: '', emailError: formatMessage(holders.validEmail), passwordError: ''});
return;
}
const providedUsername = ReactDOM.findDOMNode(this.refs.name).value.trim().toLowerCase();
if (!providedUsername) {
- this.setState({nameError: 'This field is required', emailError: '', passwordError: '', serverError: ''});
+ this.setState({nameError: formatMessage(holders.required), emailError: '', passwordError: '', serverError: ''});
return;
}
const usernameError = Utils.isValidUsername(providedUsername);
if (usernameError === 'Cannot use a reserved word as a username.') {
- this.setState({nameError: 'This username is reserved, please choose a new one.', emailError: '', passwordError: '', serverError: ''});
+ this.setState({nameError: formatMessage(holders.reserved), emailError: '', passwordError: '', serverError: ''});
return;
} else if (usernameError) {
this.setState({
- nameError: 'Username must begin with a letter, and contain between ' + Constants.MIN_USERNAME_LENGTH + ' to ' + Constants.MAX_USERNAME_LENGTH + ' lowercase characters made up of numbers, letters, and the symbols \'.\', \'-\' and \'_\'.',
+ nameError: formatMessage(holders.usernameLength, {min: Constants.MIN_USERNAME_LENGTH, max: Constants.MAX_USERNAME_LENGTH}),
emailError: '',
passwordError: '',
serverError: ''
@@ -62,7 +88,7 @@ export default class SignupUserComplete extends React.Component {
const providedPassword = ReactDOM.findDOMNode(this.refs.password).value.trim();
if (!providedPassword || providedPassword.length < Constants.MIN_PASSWORD_LENGTH) {
- this.setState({nameError: '', emailError: '', passwordError: 'Please enter at least ' + Constants.MIN_PASSWORD_LENGTH + ' characters', serverError: ''});
+ this.setState({nameError: '', emailError: '', passwordError: formatMessage(holders.passwordLength, {min: Constants.MIN_PASSWORD_LENGTH}), serverError: ''});
return;
}
@@ -95,7 +121,7 @@ export default class SignupUserComplete extends React.Component {
window.location.href = '/' + this.props.teamName + '/channels/town-square';
},
(err) => {
- if (err.message === 'Login failed because email address has not been verified') {
+ if (err.id === 'api.user.login.not_verified.app_error') {
window.location.href = '/verify_email?email=' + encodeURIComponent(user.email) + '&teamname=' + encodeURIComponent(this.props.teamName);
} else {
this.setState({serverError: err.message});
@@ -112,7 +138,14 @@ export default class SignupUserComplete extends React.Component {
client.track('signup', 'signup_user_01_welcome');
if (this.state.wizard === 'finished') {
- return <div>{"You've already completed the signup process for this invitation or this invitation has expired."}</div>;
+ return (
+ <div>
+ <FormattedMessage
+ id='signup_user_completed.expired'
+ defaultMessage="You've already completed the signup process for this invitation or this invitation has expired."
+ />
+ </div>
+ );
}
// set up error labels
@@ -124,7 +157,18 @@ export default class SignupUserComplete extends React.Component {
}
var nameError = null;
- var nameHelpText = <span className='help-block'>{'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 '_'"}</span>;
+ var nameHelpText = (
+ <span className='help-block'>
+ <FormattedMessage
+ id='signup_user_completed.userHelp'
+ defaultMessage="Username must begin with a letter, and contain between {min} to {max} lowercase characters made up of numbers, letters, and the symbols '.', '-' and '_'"
+ values={{
+ min: Constants.MIN_USERNAME_LENGTH,
+ max: Constants.MAX_USERNAME_LENGTH
+ }}
+ />
+ </span>
+ );
var nameDivStyle = 'form-group';
if (this.state.nameError) {
nameError = <label className='control-label'>{this.state.nameError}</label>;
@@ -151,7 +195,16 @@ export default class SignupUserComplete extends React.Component {
// set up the email entry and hide it if an email was provided
var yourEmailIs = '';
if (this.state.user.email) {
- yourEmailIs = <span>{'Your email address is '}<strong>{this.state.user.email}</strong>{". You'll use this address to sign in to " + global.window.mm_config.SiteName + '.'}</span>;
+ yourEmailIs = (
+ <FormattedHTMLMessage
+ id='signup_user_completed.emailIs'
+ defaultMessage="Your email address is <strong>{email}</strong>. You'll use this address to sign in to {siteName}."
+ values={{
+ email: this.state.user.email,
+ siteName: global.window.mm_config.SiteName
+ }}
+ />
+ );
}
var emailContainerStyle = 'margin--extra';
@@ -161,7 +214,12 @@ export default class SignupUserComplete extends React.Component {
var email = (
<div className={emailContainerStyle}>
- <h5><strong>{"What's your email address?"}</strong></h5>
+ <h5><strong>
+ <FormattedMessage
+ id='signup_user_completed.whatis'
+ defaultMessage="What's your email address?"
+ />
+ </strong></h5>
<div className={emailDivStyle}>
<input
type='email'
@@ -183,10 +241,16 @@ export default class SignupUserComplete extends React.Component {
signupMessage.push(
<a
className='btn btn-custom-login gitlab'
+ key='gitlab'
href={'/' + this.props.teamName + '/signup/gitlab' + window.location.search}
>
<span className='icon' />
- <span>{'with GitLab'}</span>
+ <span>
+ <FormattedMessage
+ id='signup_user_completed.gitlab'
+ defaultMessage='with GitLab'
+ />
+ </span>
</a>
);
}
@@ -195,10 +259,16 @@ export default class SignupUserComplete extends React.Component {
signupMessage.push(
<a
className='btn btn-custom-login google'
+ key='google'
href={'/' + this.props.teamName + '/signup/google' + window.location.search}
>
<span className='icon' />
- <span>{'with Google'}</span>
+ <span>
+ <FormattedMessage
+ id='signup_user_completed.google'
+ defaultMessage='with Google'
+ />
+ </span>
</a>
);
}
@@ -211,7 +281,12 @@ export default class SignupUserComplete extends React.Component {
{email}
{yourEmailIs}
<div className='margin--extra'>
- <h5><strong>{'Choose your username'}</strong></h5>
+ <h5><strong>
+ <FormattedMessage
+ id='signup_user_completed.chooseUser'
+ defaultMessage='Choose your username'
+ />
+ </strong></h5>
<div className={nameDivStyle}>
<input
type='text'
@@ -226,7 +301,12 @@ export default class SignupUserComplete extends React.Component {
</div>
</div>
<div className='margin--extra'>
- <h5><strong>{'Choose your password'}</strong></h5>
+ <h5><strong>
+ <FormattedMessage
+ id='signup_user_completed.choosePwd'
+ defaultMessage='Choose your password'
+ />
+ </strong></h5>
<div className={passwordDivStyle}>
<input
type='password'
@@ -246,7 +326,10 @@ export default class SignupUserComplete extends React.Component {
onClick={this.handleSubmit}
className='btn-primary btn'
>
- {'Create Account'}
+ <FormattedMessage
+ id='signup_user_completed.create'
+ defaultMessage='Create Account'
+ />
</button>
</p>
</div>
@@ -258,7 +341,12 @@ export default class SignupUserComplete extends React.Component {
<div>
{signupMessage}
<div className='or__container'>
- <span>{'or'}</span>
+ <span>
+ <FormattedMessage
+ id='signup_user_completed.or'
+ defaultMessage='or'
+ />
+ </span>
</div>
</div>
);
@@ -271,10 +359,28 @@ export default class SignupUserComplete extends React.Component {
className='signup-team-logo'
src='/static/images/logo.png'
/>
- <h5 className='margin--less'>{'Welcome to:'}</h5>
+ <h5 className='margin--less'>
+ <FormattedMessage
+ id='signup_user_completed.welcome'
+ defaultMessage='Welcome to:'
+ />
+ </h5>
<h2 className='signup-team__name'>{this.props.teamDisplayName}</h2>
- <h2 className='signup-team__subdomain'>{'on ' + global.window.mm_config.SiteName}</h2>
- <h4 className='color--light'>{"Let's create your account"}</h4>
+ <h2 className='signup-team__subdomain'>
+ <FormattedMessage
+ id='signup_user_completed.onSite'
+ defaultMessage='on {siteName}'
+ values={{
+ siteName: global.window.mm_config.SiteName
+ }}
+ />
+ </h2>
+ <h4 className='color--light'>
+ <FormattedMessage
+ id='signup_user_completed.lets'
+ defaultMessage="Let's create your account"
+ />
+ </h4>
{signupMessage}
{emailSignup}
{serverError}
@@ -293,6 +399,7 @@ SignupUserComplete.defaultProps = {
teamDisplayName: ''
};
SignupUserComplete.propTypes = {
+ intl: intlShape.isRequired,
teamName: React.PropTypes.string,
hash: React.PropTypes.string,
teamId: React.PropTypes.string,
@@ -300,3 +407,5 @@ SignupUserComplete.propTypes = {
data: React.PropTypes.string,
teamDisplayName: React.PropTypes.string
};
+
+export default injectIntl(SignupUserComplete); \ No newline at end of file
diff --git a/web/react/components/suggestion/at_mention_provider.jsx b/web/react/components/suggestion/at_mention_provider.jsx
index e502c981d..50231ad15 100644
--- a/web/react/components/suggestion/at_mention_provider.jsx
+++ b/web/react/components/suggestion/at_mention_provider.jsx
@@ -5,6 +5,8 @@ import SuggestionStore from '../../stores/suggestion_store.jsx';
import UserStore from '../../stores/user_store.jsx';
import * as Utils from '../../utils/utils.jsx';
+import {FormattedMessage} from 'mm-intl';
+
const MaxUserSuggestions = 40;
class AtMentionSuggestion extends React.Component {
@@ -16,11 +18,21 @@ class AtMentionSuggestion extends React.Component {
let icon;
if (item.username === 'all') {
username = 'all';
- description = 'Notifies everyone in the team';
+ description = (
+ <FormattedMessage
+ id='suggestion.mention.all'
+ defaultMessage='Notifies everyone in the team'
+ />
+ );
icon = <i className='mention-img fa fa-users fa-2x' />;
} else if (item.username === 'channel') {
username = 'channel';
- description = 'Notifies everyone in the channel';
+ description = (
+ <FormattedMessage
+ id='suggestion.mention.channel'
+ defaultMessage='Notifies everyone in the channel'
+ />
+ );
icon = <i className='mention-img fa fa-users fa-2x' />;
} else {
username = item.username;
diff --git a/web/react/components/suggestion/search_suggestion_list.jsx b/web/react/components/suggestion/search_suggestion_list.jsx
index 3378a33a0..40f5d8777 100644
--- a/web/react/components/suggestion/search_suggestion_list.jsx
+++ b/web/react/components/suggestion/search_suggestion_list.jsx
@@ -3,7 +3,8 @@
import Constants from '../../utils/constants.jsx';
import SuggestionList from './suggestion_list.jsx';
-import * as Utils from '../../utils/utils.jsx';
+
+import {FormattedMessage} from 'mm-intl';
export default class SearchSuggestionList extends SuggestionList {
componentDidUpdate(prevProps, prevState) {
@@ -19,9 +20,19 @@ export default class SearchSuggestionList extends SuggestionList {
renderChannelDivider(type) {
let text;
if (type === Constants.OPEN_CHANNEL) {
- text = 'Public ' + Utils.getChannelTerm(type) + 's';
+ text = (
+ <FormattedMessage
+ id='suggestion.search.public'
+ defaultMessage='Public Channels'
+ />
+ );
} else {
- text = 'Private ' + Utils.getChannelTerm(type) + 's';
+ text = (
+ <FormattedMessage
+ id='suggestion.search.private'
+ defaultMessage='Public Groups'
+ />
+ );
}
return (
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..0656d3b03 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 = '';
}
@@ -478,13 +577,13 @@ export default class GeneralTab extends React.Component {
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={formatMessage(holders.teamNameInfo)}
/>
);
} else {
@@ -492,7 +591,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 +614,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 +646,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/team_signup_choose_auth.jsx b/web/react/components/team_signup_choose_auth.jsx
index 19b9750b3..2dc67e92e 100644
--- a/web/react/components/team_signup_choose_auth.jsx
+++ b/web/react/components/team_signup_choose_auth.jsx
@@ -1,6 +1,8 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
+import {FormattedMessage} from 'mm-intl';
+
export default class ChooseAuthPage extends React.Component {
constructor(props) {
super(props);
@@ -12,6 +14,7 @@ export default class ChooseAuthPage extends React.Component {
buttons.push(
<a
className='btn btn-custom-login gitlab btn-full'
+ key='gitlab'
href='#'
onClick={
function clickGit(e) {
@@ -21,7 +24,12 @@ export default class ChooseAuthPage extends React.Component {
}
>
<span className='icon' />
- <span>{'Create new team with GitLab Account'}</span>
+ <span>
+ <FormattedMessage
+ id='choose_auth_page.gitlabCreate'
+ defaultMessage='Create new team with GitLab Account'
+ />
+ </span>
</a>
);
}
@@ -30,6 +38,7 @@ export default class ChooseAuthPage extends React.Component {
buttons.push(
<a
className='btn btn-custom-login google btn-full'
+ key='google'
href='#'
onClick={
(e) => {
@@ -39,7 +48,12 @@ export default class ChooseAuthPage extends React.Component {
}
>
<span className='icon' />
- <span>{'Create new team with Google Apps Account'}</span>
+ <span>
+ <FormattedMessage
+ id='choose_auth_page.googleCreate'
+ defaultMessage='Create new team with Google Apps Account'
+ />
+ </span>
</a>
);
}
@@ -48,6 +62,7 @@ export default class ChooseAuthPage extends React.Component {
buttons.push(
<a
className='btn btn-custom-login email btn-full'
+ key='email'
href='#'
onClick={
function clickEmail(e) {
@@ -57,20 +72,37 @@ export default class ChooseAuthPage extends React.Component {
}
>
<span className='fa fa-envelope' />
- <span>{'Create new team with email address'}</span>
+ <span>
+ <FormattedMessage
+ id='choose_auth_page.emailCreate'
+ defaultMessage='Create new team with email address'
+ />
+ </span>
</a>
);
}
if (buttons.length === 0) {
- buttons = <span>{'No sign-up methods configured, please contact your system administrator.'}</span>;
+ buttons = (
+ <span>
+ <FormattedMessage
+ id='choose_auth_page.noSignup'
+ defaultMessage='No sign-up methods configured, please contact your system administrator.'
+ />
+ </span>
+ );
}
return (
<div>
{buttons}
<div className='form-group margin--extra-2x'>
- <span><a href='/find_team'>{'Find my teams'}</a></span>
+ <span><a href='/find_team'>
+ <FormattedMessage
+ id='choose_auth_page.find'
+ defaultMessage='Find my teams'
+ />
+ </a></span>
</div>
</div>
);
diff --git a/web/react/components/team_signup_display_name_page.jsx b/web/react/components/team_signup_display_name_page.jsx
index f4d5ea162..f07b50756 100644
--- a/web/react/components/team_signup_display_name_page.jsx
+++ b/web/react/components/team_signup_display_name_page.jsx
@@ -4,7 +4,20 @@
import * as utils from '../utils/utils.jsx';
import * as client from '../utils/client.jsx';
-export default class TeamSignupDisplayNamePage extends React.Component {
+import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'mm-intl';
+
+const holders = defineMessages({
+ required: {
+ id: 'team_signup_display_name.required',
+ defaultMessage: 'This field is required'
+ },
+ charLength: {
+ id: 'team_signup_display_name.charLength',
+ defaultMessage: 'Name must be 4 or more characters up to a maximum of 15'
+ }
+});
+
+class TeamSignupDisplayNamePage extends React.Component {
constructor(props) {
super(props);
@@ -21,12 +34,13 @@ export default class TeamSignupDisplayNamePage extends React.Component {
submitNext(e) {
e.preventDefault();
+ const {formatMessage} = this.props.intl;
var displayName = ReactDOM.findDOMNode(this.refs.name).value.trim();
if (!displayName) {
- this.setState({nameError: 'This field is required'});
+ this.setState({nameError: formatMessage(holders.required)});
return;
} else if (displayName.length < 4 || displayName.length > 15) {
- this.setState({nameError: 'Name must be 4 or more characters up to a maximum of 15'});
+ this.setState({nameError: formatMessage(holders.charLength)});
return;
}
@@ -56,7 +70,12 @@ export default class TeamSignupDisplayNamePage extends React.Component {
className='signup-team-logo'
src='/static/images/logo.png'
/>
- <h2>{'Team Name'}</h2>
+ <h2>
+ <FormattedMessage
+ id='team_signup_display_name.teamName'
+ defaultMessage='Team Name'
+ />
+ </h2>
<div className={nameDivClass}>
<div className='row'>
<div className='col-sm-9'>
@@ -76,21 +95,30 @@ export default class TeamSignupDisplayNamePage extends React.Component {
{nameError}
</div>
<div>
- {'Name your team in any language. Your team name shows in menus and headings.'}
+ <FormattedMessage
+ id='team_signup_display_name.nameHelp'
+ defaultMessage='Name your team in any language. Your team name shows in menus and headings.'
+ />
</div>
<button
type='submit'
className='btn btn-primary margin--extra'
onClick={this.submitNext}
>
- Next<i className='glyphicon glyphicon-chevron-right'></i>
+ <FormattedMessage
+ id='team_signup_display_name.next'
+ defaultMessage='Next'
+ /><i className='glyphicon glyphicon-chevron-right'></i>
</button>
<div className='margin--extra'>
<a
href='#'
onClick={this.submitBack}
>
- Back to previous step
+ <FormattedMessage
+ id='team_signup_display_name.back'
+ defaultMessage='Back to previous step'
+ />
</a>
</div>
</form>
@@ -100,6 +128,9 @@ export default class TeamSignupDisplayNamePage extends React.Component {
}
TeamSignupDisplayNamePage.propTypes = {
+ intl: intlShape.isRequired,
state: React.PropTypes.object,
updateParent: React.PropTypes.func
};
+
+export default injectIntl(TeamSignupDisplayNamePage); \ No newline at end of file
diff --git a/web/react/components/team_signup_email_item.jsx b/web/react/components/team_signup_email_item.jsx
index 59c4771d7..feb70dc71 100644
--- a/web/react/components/team_signup_email_item.jsx
+++ b/web/react/components/team_signup_email_item.jsx
@@ -3,7 +3,24 @@
import * as Utils from '../utils/utils.jsx';
-export default class TeamSignupEmailItem extends React.Component {
+import {intlShape, injectIntl, defineMessages} from 'mm-intl';
+
+const holders = defineMessages({
+ validEmail: {
+ id: 'team_signup_email.validEmail',
+ defaultMessage: 'Please enter a valid email address'
+ },
+ different: {
+ id: 'team_signup_email.different',
+ defaultMessage: 'Please use a different email than the one used at signup'
+ },
+ address: {
+ id: 'team_signup_email.address',
+ defaultMessage: 'Email Address'
+ }
+});
+
+class TeamSignupEmailItem extends React.Component {
constructor(props) {
super(props);
@@ -16,6 +33,7 @@ export default class TeamSignupEmailItem extends React.Component {
return ReactDOM.findDOMNode(this.refs.email).value.trim();
}
validate(teamEmail) {
+ const {formatMessage} = this.props.intl;
const email = ReactDOM.findDOMNode(this.refs.email).value.trim().toLowerCase();
if (!email) {
@@ -23,10 +41,10 @@ export default class TeamSignupEmailItem extends React.Component {
}
if (!Utils.isEmail(email)) {
- this.setState({emailError: 'Please enter a valid email address'});
+ this.setState({emailError: formatMessage(holders.validEmail)});
return false;
} else if (email === teamEmail) {
- this.setState({emailError: 'Please use a different email than the one used at signup'});
+ this.setState({emailError: formatMessage(holders.different)});
return false;
}
@@ -48,7 +66,7 @@ export default class TeamSignupEmailItem extends React.Component {
type='email'
ref='email'
className='form-control'
- placeholder='Email Address'
+ placeholder={this.props.intl.formatMessage(holders.address)}
defaultValue={this.props.email}
maxLength='128'
spellCheck='false'
@@ -60,6 +78,9 @@ export default class TeamSignupEmailItem extends React.Component {
}
TeamSignupEmailItem.propTypes = {
+ intl: intlShape.isRequired,
focus: React.PropTypes.bool,
email: React.PropTypes.string
};
+
+export default injectIntl(TeamSignupEmailItem); \ No newline at end of file
diff --git a/web/react/components/team_signup_password_page.jsx b/web/react/components/team_signup_password_page.jsx
index 7e11d38c3..06c04854f 100644
--- a/web/react/components/team_signup_password_page.jsx
+++ b/web/react/components/team_signup_password_page.jsx
@@ -6,7 +6,20 @@ import BrowserStore from '../stores/browser_store.jsx';
import UserStore from '../stores/user_store.jsx';
import Constants from '../utils/constants.jsx';
-export default class TeamSignupPasswordPage extends React.Component {
+import {intlShape, injectIntl, defineMessages, FormattedMessage, FormattedHTMLMessage} from 'mm-intl';
+
+const holders = defineMessages({
+ passwordError: {
+ id: 'team_signup_password.passwordError',
+ defaultMessage: 'Please enter at least {chars} characters'
+ },
+ creating: {
+ id: 'team_signup_password.creating',
+ defaultMessage: 'Creating team...'
+ }
+});
+
+class TeamSignupPasswordPage extends React.Component {
constructor(props) {
super(props);
@@ -25,7 +38,7 @@ export default class TeamSignupPasswordPage extends React.Component {
var password = ReactDOM.findDOMNode(this.refs.password).value.trim();
if (!password || password.length < Constants.MIN_PASSWORD_LENGTH) {
- this.setState({passwordError: 'Please enter at least ' + Constants.MIN_PASSWORD_LENGTH + ' characters'});
+ this.setState({passwordError: this.props.intl.formatMessage(holders.passwordError, {chars: Constants.MIN_PASSWORD_LENGTH})});
return;
}
@@ -56,7 +69,7 @@ export default class TeamSignupPasswordPage extends React.Component {
window.location.href = '/' + teamSignup.team.name + '/channels/town-square';
},
(err) => {
- if (err.message === 'Login failed because email address has not been verified') {
+ if (err.id === 'api.user.login.not_verified.app_error') {
window.location.href = '/verify_email?email=' + encodeURIComponent(teamSignup.team.email) + '&teamname=' + encodeURIComponent(teamSignup.team.name);
} else {
this.setState({serverError: err.message});
@@ -93,15 +106,35 @@ export default class TeamSignupPasswordPage extends React.Component {
className='signup-team-logo'
src='/static/images/logo.png'
/>
- <h2 className='margin--less'>{'Your password'}</h2>
- <h5 className='color--light'>{"Select a password that you'll use to login with your email address:"}</h5>
+ <h2 className='margin--less'>
+ <FormattedMessage
+ id='team_signup_password.yourPassword'
+ defaultMessage='Your password'
+ />
+ </h2>
+ <h5 className='color--light'>
+ <FormattedMessage
+ id='team_signup_password.selectPassword'
+ defaultMessage="Select a password that you'll use to login with your email address:"
+ />
+ </h5>
<div className='inner__content margin--extra'>
- <h5><strong>{'Email'}</strong></h5>
+ <h5><strong>
+ <FormattedMessage
+ id='team_signup_password.email'
+ defaultMessage='Email'
+ />
+ </strong></h5>
<div className='block--gray form-group'>{this.props.state.team.email}</div>
<div className={passwordDivStyle}>
<div className='row'>
<div className='col-sm-11'>
- <h5><strong>{'Choose your password'}</strong></h5>
+ <h5><strong>
+ <FormattedMessage
+ id='team_signup_password.choosePwd'
+ defaultMessage='Choose your password'
+ />
+ </strong></h5>
<input
autoFocus={true}
type='password'
@@ -111,7 +144,16 @@ export default class TeamSignupPasswordPage extends React.Component {
maxLength='128'
spellCheck='false'
/>
- <span className='color--light help-block'>{'Passwords must contain ' + Constants.MIN_PASSWORD_LENGTH + ' to ' + Constants.MAX_PASSWORD_LENGTH + ' characters. Your password will be strongest if it contains a mix of symbols, numbers, and upper and lowercase characters.'}</span>
+ <span className='color--light help-block'>
+ <FormattedMessage
+ id='team_signup_password.hint'
+ defaultMessage='Passwords must contain {min} to {max} characters. Your password will be strongest if it contains a mix of symbols, numbers, and upper and lowercase characters.'
+ values={{
+ min: Constants.MIN_PASSWORD_LENGTH,
+ max: Constants.MAX_PASSWORD_LENGTH
+ }}
+ />
+ </span>
</div>
</div>
{passwordError}
@@ -123,19 +165,33 @@ export default class TeamSignupPasswordPage extends React.Component {
type='submit'
className='btn btn-primary margin--extra'
id='finish-button'
- data-loading-text={'<span class=\'glyphicon glyphicon-refresh glyphicon-refresh-animate\'></span> Creating team...'}
+ data-loading-text={'<span class=\'glyphicon glyphicon-refresh glyphicon-refresh-animate\'></span> ' + this.props.intl.formatMessage(holders.creating)}
onClick={this.submitNext}
>
- {'Finish'}
+ <FormattedMessage
+ id='team_signup_password.finish'
+ defaultMessage='Finish'
+ />
</button>
</div>
- <p>By proceeding to create your account and use {global.window.mm_config.SiteName}, you agree to our <a href='/static/help/terms.html'>Terms of Service</a> and <a href='/static/help/privacy.html'>Privacy Policy</a>. If you do not agree, you cannot use {global.window.mm_config.SiteName}.</p>
+ <p>
+ <FormattedHTMLMessage
+ id='team_signup_password.agreement'
+ defaultMessage="By proceeding to create your account and use {siteName}, you agree to our <a href='/static/help/terms.html'>Terms of Service</a> and <a href='/static/help/privacy.html'>Privacy Policy</a>. If you do not agree, you cannot use {siteName}."
+ values={{
+ siteName: global.window.mm_config.SiteName
+ }}
+ />
+ </p>
<div className='margin--extra'>
<a
href='#'
onClick={this.submitBack}
>
- {'Back to previous step'}
+ <FormattedMessage
+ id='team_signup_password.back'
+ defaultMessage='Back to previous step'
+ />
</a>
</div>
</form>
@@ -149,7 +205,10 @@ TeamSignupPasswordPage.defaultProps = {
hash: ''
};
TeamSignupPasswordPage.propTypes = {
+ intl: intlShape.isRequired,
state: React.PropTypes.object,
hash: React.PropTypes.string,
updateParent: React.PropTypes.func
};
+
+export default injectIntl(TeamSignupPasswordPage); \ No newline at end of file
diff --git a/web/react/components/team_signup_send_invites_page.jsx b/web/react/components/team_signup_send_invites_page.jsx
index a580623e4..46a6bc68e 100644
--- a/web/react/components/team_signup_send_invites_page.jsx
+++ b/web/react/components/team_signup_send_invites_page.jsx
@@ -4,6 +4,8 @@
import EmailItem from './team_signup_email_item.jsx';
import * as Client from '../utils/client.jsx';
+import {FormattedMessage, FormattedHTMLMessage} from 'mm-intl';
+
export default class TeamSignupSendInvitesPage extends React.Component {
constructor(props) {
super(props);
@@ -117,7 +119,10 @@ export default class TeamSignupSendInvitesPage extends React.Component {
href='#'
onClick={this.submitAddInvite}
>
- Add Invitation
+ <FormattedMessage
+ id='team_signup_send_invites.addInvitation'
+ defaultMessage='Add Invitation'
+ />
</a>
</div>
</div>
@@ -125,22 +130,32 @@ export default class TeamSignupSendInvitesPage extends React.Component {
bottomContent = (
<p className='color--light'>
- {'if you prefer, you can invite team members later'}
- <br />
- {' and '}
+ <FormattedHTMLMessage
+ id='team_signup_send_invites.prefer'
+ defaultMessage='if you prefer, you can invite team members later<br /> and '
+ />
<a
href='#'
onClick={this.submitSkip}
>
- {'skip this step '}
+ <FormattedMessage
+ id='team_signup_send_invites.skip'
+ defaultMessage='skip this step '
+ />
</a>
- {'for now.'}
+ <FormattedMessage
+ id='team_signup_send_invites.forNow'
+ defaultMessage='for now.'
+ />
</p>
);
} else {
content = (
<div className='form-group color--light'>
- {'Email is currently disabled for your team, and emails cannot be sent. Contact your system administrator to enable email and email invitations.'}
+ <FormattedMessage
+ id='team_signup_send_invites.disabled'
+ defaultMessage='Email is currently disabled for your team, and emails cannot be sent. Contact your system administrator to enable email and email invitations.'
+ />
</div>
);
}
@@ -152,7 +167,12 @@ export default class TeamSignupSendInvitesPage extends React.Component {
className='signup-team-logo'
src='/static/images/logo.png'
/>
- <h2>{'Invite Team Members'}</h2>
+ <h2>
+ <FormattedMessage
+ id='team_signup_send_invites.title'
+ defaultMessage='Invite Team Members'
+ />
+ </h2>
{content}
<div className='form-group'>
<button
@@ -160,7 +180,10 @@ export default class TeamSignupSendInvitesPage extends React.Component {
className='btn-primary btn'
onClick={this.submitNext}
>
- Next<i className='glyphicon glyphicon-chevron-right' />
+ <FormattedMessage
+ id='team_signup_send_invites.next'
+ defaultMessage='Next'
+ /><i className='glyphicon glyphicon-chevron-right' />
</button>
</div>
</form>
@@ -170,7 +193,10 @@ export default class TeamSignupSendInvitesPage extends React.Component {
href='#'
onClick={this.submitBack}
>
- Back to previous step
+ <FormattedMessage
+ id='team_signup_send_invites.back'
+ defaultMessage='Back to previous step'
+ />
</a>
</div>
</div>
diff --git a/web/react/components/team_signup_url_page.jsx b/web/react/components/team_signup_url_page.jsx
index 30459fc67..2f6c3df49 100644
--- a/web/react/components/team_signup_url_page.jsx
+++ b/web/react/components/team_signup_url_page.jsx
@@ -5,7 +5,32 @@ import * as Utils from '../utils/utils.jsx';
import * as Client from '../utils/client.jsx';
import Constants from '../utils/constants.jsx';
-export default class TeamSignupUrlPage extends React.Component {
+import {injectIntl, intlShape, defineMessages, FormattedMessage, FormattedHTMLMessage} from 'mm-intl';
+
+const holders = defineMessages({
+ required: {
+ id: 'team_signup_url.required',
+ defaultMessage: 'This field is required'
+ },
+ regex: {
+ id: 'team_signup_url.regex',
+ defaultMessage: "Use only lower case letters, numbers and dashes. Must start with a letter and can't end in a dash."
+ },
+ charLength: {
+ id: 'team_signup_url.charLength',
+ defaultMessage: 'Name must be 4 or more characters up to a maximum of 15'
+ },
+ taken: {
+ id: 'team_signup_url.taken',
+ defaultMessage: 'URL is taken or contains a reserved word'
+ },
+ unavailable: {
+ id: 'team_signup_url.unavailable',
+ defaultMessage: 'This URL is unavailable. Please try another.'
+ }
+});
+
+class TeamSignupUrlPage extends React.Component {
constructor(props) {
super(props);
@@ -23,9 +48,10 @@ export default class TeamSignupUrlPage extends React.Component {
submitNext(e) {
e.preventDefault();
+ const {formatMessage} = this.props.intl;
const name = ReactDOM.findDOMNode(this.refs.name).value.trim();
if (!name) {
- this.setState({nameError: 'This field is required'});
+ this.setState({nameError: formatMessage(holders.required)});
return;
}
@@ -33,17 +59,17 @@ export default class TeamSignupUrlPage extends React.Component {
const urlRegex = /^[a-z]+([a-z\-0-9]+|(__)?)[a-z0-9]+$/g;
if (cleanedName !== name || !urlRegex.test(name)) {
- this.setState({nameError: "Use only lower case letters, numbers and dashes. Must start with a letter and can't end in a dash."});
+ this.setState({nameError: formatMessage(holders.regex)});
return;
} else if (cleanedName.length < 4 || cleanedName.length > 15) {
- this.setState({nameError: 'Name must be 4 or more characters up to a maximum of 15'});
+ this.setState({nameError: formatMessage(holders.charLength)});
return;
}
if (global.window.mm_config.RestrictTeamNames === 'true') {
for (let index = 0; index < Constants.RESERVED_TEAM_NAMES.length; index++) {
if (cleanedName.indexOf(Constants.RESERVED_TEAM_NAMES[index]) === 0) {
- this.setState({nameError: 'URL is taken or contains a reserved word'});
+ this.setState({nameError: formatMessage(holders.taken)});
return;
}
}
@@ -52,7 +78,7 @@ export default class TeamSignupUrlPage extends React.Component {
Client.findTeamByName(name,
(data) => {
if (data) {
- this.setState({nameError: 'This URL is unavailable. Please try another.'});
+ this.setState({nameError: formatMessage(holders.unavailable)});
} else {
if (global.window.mm_config.SendEmailNotifications === 'true') {
this.props.state.wizard = 'send_invites';
@@ -96,7 +122,12 @@ export default class TeamSignupUrlPage extends React.Component {
className='signup-team-logo'
src='/static/images/logo.png'
/>
- <h2>{`Team URL`}</h2>
+ <h2>
+ <FormattedMessage
+ id='team_signup_url.teamUrl'
+ defaultMessage='Team URL'
+ />
+ </h2>
<div className={nameDivClass}>
<div className='row'>
<div className='col-sm-11'>
@@ -124,25 +155,39 @@ export default class TeamSignupUrlPage extends React.Component {
</div>
{nameError}
</div>
- <p>{`Choose the web address of your new team:`}</p>
+ <p>
+ <FormattedMessage
+ id='team_signup_url.webAddress'
+ defaultMessage='Choose the web address of your new team:'
+ />
+ </p>
<ul className='color--light'>
- <li>Short and memorable is best</li>
- <li>Use lowercase letters, numbers and dashes</li>
- <li>Must start with a letter and can't end in a dash</li>
+ <FormattedHTMLMessage
+ id='team_signup_url.hint'
+ defaultMessage="<li>Short and memorable is best</li>
+ <li>Use lowercase letters, numbers and dashes</li>
+ <li>Must start with a letter and can't end in a dash</li>"
+ />
</ul>
<button
type='submit'
className='btn btn-primary margin--extra'
onClick={this.submitNext}
>
- Next<i className='glyphicon glyphicon-chevron-right'></i>
+ <FormattedMessage
+ id='team_signup_url.next'
+ defaultMessage='Next'
+ /><i className='glyphicon glyphicon-chevron-right'></i>
</button>
<div className='margin--extra'>
<a
href='#'
onClick={this.submitBack}
>
- Back to previous step
+ <FormattedMessage
+ id='team_signup_url.back'
+ defaultMessage='Back to previous step'
+ />
</a>
</div>
</form>
@@ -152,6 +197,9 @@ export default class TeamSignupUrlPage extends React.Component {
}
TeamSignupUrlPage.propTypes = {
+ intl: intlShape.isRequired,
state: React.PropTypes.object,
updateParent: React.PropTypes.func
};
+
+export default injectIntl(TeamSignupUrlPage); \ No newline at end of file
diff --git a/web/react/components/team_signup_username_page.jsx b/web/react/components/team_signup_username_page.jsx
index 6ccab6656..a7332975d 100644
--- a/web/react/components/team_signup_username_page.jsx
+++ b/web/react/components/team_signup_username_page.jsx
@@ -5,7 +5,20 @@ import * as Utils from '../utils/utils.jsx';
import * as Client from '../utils/client.jsx';
import Constants from '../utils/constants.jsx';
-export default class TeamSignupUsernamePage extends React.Component {
+import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl';
+
+const holders = defineMessages({
+ reserved: {
+ id: 'team_signup_username.reserved',
+ defaultMessage: 'This username is reserved, please choose a new one.'
+ },
+ invalid: {
+ id: 'team_signup_username.invalid',
+ defaultMessage: 'Username must begin with a letter, and contain between {min} to {max} characters in total, which may be numbers, lowercase letters, or any of the symbols \'.\', \'-\', or \'_\''
+ }
+});
+
+class TeamSignupUsernamePage extends React.Component {
constructor(props) {
super(props);
@@ -27,14 +40,15 @@ export default class TeamSignupUsernamePage extends React.Component {
submitNext(e) {
e.preventDefault();
+ const {formatMessage} = this.props.intl;
var name = ReactDOM.findDOMNode(this.refs.name).value.trim().toLowerCase();
var usernameError = Utils.isValidUsername(name);
- if (usernameError === 'Cannot use a reserved word as a username.') {
- this.setState({nameError: 'This username is reserved, please choose a new one.'});
+ if (usernameError === 'Cannot use a reserved word as a username.') { //this should be change to some kind of ID
+ this.setState({nameError: formatMessage(holders.reserved)});
return;
} else if (usernameError) {
- this.setState({nameError: 'Username must begin with a letter, and contain between ' + Constants.MIN_USERNAME_LENGTH + ' to ' + Constants.MAX_USERNAME_LENGTH + ' characters in total, which may be numbers, lowercase letters, or any of the symbols \'.\', \'-\', or \'_\''});
+ this.setState({nameError: formatMessage(holders.invalid, {min: Constants.MIN_USERNAME_LENGTH, max: Constants.MAX_USERNAME_LENGTH})});
return;
}
@@ -46,7 +60,18 @@ export default class TeamSignupUsernamePage extends React.Component {
Client.track('signup', 'signup_team_06_username');
var nameError = null;
- var nameHelpText = <span className='color--light help-block'>{'Usernames must begin with a letter and contain between ' + Constants.MIN_USERNAME_LENGTH + ' to ' + Constants.MAX_USERNAME_LENGTH + " characters made up of lowercase letters, numbers, and the symbols '.', '-' and '_'"}</span>;
+ var nameHelpText = (
+ <span className='color--light help-block'>
+ <FormattedMessage
+ id='team_signup_username.hint'
+ defaultMessage="Usernames must begin with a letter and contain between {min} to {max} characters made up of lowercase letters, numbers, and the symbols '.', '-' and '_'"
+ values={{
+ min: Constants.MIN_USERNAME_LENGTH,
+ max: Constants.MAX_USERNAME_LENGTH
+ }}
+ />
+ </span>
+ );
var nameDivClass = 'form-group';
if (this.state.nameError) {
nameError = <label className='control-label'>{this.state.nameError}</label>;
@@ -61,13 +86,28 @@ export default class TeamSignupUsernamePage extends React.Component {
className='signup-team-logo'
src='/static/images/logo.png'
/>
- <h2 className='margin--less'>{'Your username'}</h2>
- <h5 className='color--light'>{'Select a memorable username that makes it easy for teammates to identify you:'}</h5>
+ <h2 className='margin--less'>
+ <FormattedMessage
+ id='team_signup_username.username'
+ defaultMessage='Your username'
+ />
+ </h2>
+ <h5 className='color--light'>
+ <FormattedMessage
+ id='team_signup_username.memorable'
+ defaultMessage='Select a memorable username that makes it easy for teammates to identify you:'
+ />
+ </h5>
<div className='inner__content margin--extra'>
<div className={nameDivClass}>
<div className='row'>
<div className='col-sm-11'>
- <h5><strong>{'Choose your username'}</strong></h5>
+ <h5><strong>
+ <FormattedMessage
+ id='team_signup_username.chooseUsername'
+ defaultMessage='Choose your username'
+ />
+ </strong></h5>
<input
autoFocus={true}
type='text'
@@ -89,7 +129,10 @@ export default class TeamSignupUsernamePage extends React.Component {
className='btn btn-primary margin--extra'
onClick={this.submitNext}
>
- {'Next'}
+ <FormattedMessage
+ id='team_signup_username.next'
+ defaultMessage='Next'
+ />
<i className='glyphicon glyphicon-chevron-right'></i>
</button>
<div className='margin--extra'>
@@ -97,7 +140,10 @@ export default class TeamSignupUsernamePage extends React.Component {
href='#'
onClick={this.submitBack}
>
- {'Back to previous step'}
+ <FormattedMessage
+ id='team_signup_username.back'
+ defaultMessage='Back to previous step'
+ />
</a>
</div>
</form>
@@ -110,6 +156,9 @@ TeamSignupUsernamePage.defaultProps = {
state: null
};
TeamSignupUsernamePage.propTypes = {
+ intl: intlShape.isRequired,
state: React.PropTypes.object,
updateParent: React.PropTypes.func
};
+
+export default injectIntl(TeamSignupUsernamePage); \ No newline at end of file
diff --git a/web/react/components/team_signup_welcome_page.jsx b/web/react/components/team_signup_welcome_page.jsx
index a374dd363..18951be72 100644
--- a/web/react/components/team_signup_welcome_page.jsx
+++ b/web/react/components/team_signup_welcome_page.jsx
@@ -5,7 +5,24 @@ import * as Utils from '../utils/utils.jsx';
import * as Client from '../utils/client.jsx';
import BrowserStore from '../stores/browser_store.jsx';
-export default class TeamSignupWelcomePage extends React.Component {
+import {injectIntl, intlShape, defineMessages, FormattedMessage, FormattedHTMLMessage} from 'mm-intl';
+
+const holders = defineMessages({
+ storageError: {
+ id: 'team_signup_welcome.storageError',
+ defaultMessage: 'This service requires local storage to be enabled. Please enable it or exit private browsing.'
+ },
+ validEmailError: {
+ id: 'team_signup_welcome.validEmailError',
+ defaultMessage: 'Please enter a valid email address'
+ },
+ address: {
+ id: 'team_signup_welcome.address',
+ defaultMessage: 'Email Address'
+ }
+});
+
+class TeamSignupWelcomePage extends React.Component {
constructor(props) {
super(props);
@@ -20,7 +37,7 @@ export default class TeamSignupWelcomePage extends React.Component {
}
submitNext(e) {
if (!BrowserStore.isLocalStorageSupported()) {
- this.setState({storageError: 'This service requires local storage to be enabled. Please enable it or exit private browsing.'});
+ this.setState({storageError: this.props.intl.formatMessage(holders.storageError)});
return;
}
e.preventDefault();
@@ -34,15 +51,16 @@ export default class TeamSignupWelcomePage extends React.Component {
handleDiffSubmit(e) {
e.preventDefault();
+ const {formatMessage} = this.props.intl;
var state = {useDiff: true, serverError: ''};
var email = ReactDOM.findDOMNode(this.refs.email).value.trim().toLowerCase();
if (!email || !Utils.isEmail(email)) {
- state.emailError = 'Please enter a valid email address';
+ state.emailError = formatMessage(holders.validEmailError);
this.setState(state);
return;
} else if (!BrowserStore.isLocalStorageSupported()) {
- state.emailError = 'This service requires local storage to be enabled. Please enable it or exit private browsing.';
+ state.emailError = formatMessage(holders.storageError);
this.setState(state);
return;
}
@@ -62,7 +80,7 @@ export default class TeamSignupWelcomePage extends React.Component {
let errorMsg = err.message;
if (err.detailed_error.indexOf('Invalid RCPT TO address provided') >= 0) {
- errorMsg = 'Please enter a valid email address';
+ errorMsg = formatMessage(holders.validEmailError);
}
this.setState({emailError: '', serverError: errorMsg});
@@ -114,18 +132,35 @@ export default class TeamSignupWelcomePage extends React.Component {
className='signup-team-logo'
src='/static/images/logo.png'
/>
- <h3 className='sub-heading'>Welcome to:</h3>
+ <h3 className='sub-heading'>
+ <FormattedMessage
+ id='team_signup_welcome.welcome'
+ defaultMessage='Welcome to:'
+ />
+ </h3>
<h1 className='margin--top-none'>{global.window.mm_config.SiteName}</h1>
- <p className='margin--less'>Let's set up your new team</p>
+ <p className='margin--less'>
+ <FormattedMessage
+ id='team_signup_welcome.lets'
+ defaultMessage="Let's set up your new team"
+ />
+ </p>
<div>
- Please confirm your email address:<br />
+ <FormattedMessage
+ id='team_signup_welcome.confirm'
+ defaultMessage='Please confirm your email address:'
+ />
+ <br />
<div className='inner__content'>
<div className='block--gray'>{this.props.state.team.email}</div>
</div>
</div>
<p className='margin--extra color--light'>
- Your account will administer the new team site. <br />
- You can add other administrators later.
+ <FormattedHTMLMessage
+ id='team_signup_welcome.admin'
+ defaultMessage='Your account will administer the new team site. <br />
+ You can add other administrators later.'
+ />
</p>
<div className='form-group'>
<button
@@ -134,7 +169,10 @@ export default class TeamSignupWelcomePage extends React.Component {
onClick={this.submitNext}
>
<i className='glyphicon glyphicon-ok'></i>
- Yes, this address is correct
+ <FormattedMessage
+ id='team_signup_welcome.yes'
+ defaultMessage='Yes, this address is correct'
+ />
</button>
{storageError}
</div>
@@ -147,7 +185,7 @@ export default class TeamSignupWelcomePage extends React.Component {
type='email'
ref='email'
className='form-control'
- placeholder='Email Address'
+ placeholder={this.props.intl.formatMessage(holders.address)}
maxLength='128'
spellCheck='false'
/>
@@ -161,7 +199,10 @@ export default class TeamSignupWelcomePage extends React.Component {
type='button'
onClick={this.handleDiffSubmit}
>
- Use this instead
+ <FormattedMessage
+ id='team_signup_welcome.instead'
+ defaultMessage='Use this instead'
+ />
</button>
</div>
<a
@@ -169,7 +210,10 @@ export default class TeamSignupWelcomePage extends React.Component {
onClick={this.handleDiffEmail}
className={differentEmailLinkClass}
>
- Use a different email
+ <FormattedMessage
+ id='team_signup_welcome.different'
+ defaultMessage='Use a different email'
+ />
</a>
</div>
);
@@ -180,6 +224,9 @@ TeamSignupWelcomePage.defaultProps = {
state: {}
};
TeamSignupWelcomePage.propTypes = {
+ intl: intlShape.isRequired,
updateParent: React.PropTypes.func.isRequired,
state: React.PropTypes.object
};
+
+export default injectIntl(TeamSignupWelcomePage); \ No newline at end of file
diff --git a/web/react/components/team_signup_with_email.jsx b/web/react/components/team_signup_with_email.jsx
index 4150a0013..7dd645b25 100644
--- a/web/react/components/team_signup_with_email.jsx
+++ b/web/react/components/team_signup_with_email.jsx
@@ -4,7 +4,20 @@
import * as Utils from '../utils/utils.jsx';
import * as Client from '../utils/client.jsx';
-export default class EmailSignUpPage extends React.Component {
+import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'mm-intl';
+
+const holders = defineMessages({
+ emailError: {
+ id: 'email_signup.emailError',
+ defaultMessage: 'Please enter a valid email address'
+ },
+ address: {
+ id: 'email_signup.address',
+ defaultMessage: 'Email Address'
+ }
+});
+
+class EmailSignUpPage extends React.Component {
constructor() {
super();
@@ -20,7 +33,7 @@ export default class EmailSignUpPage extends React.Component {
team.email = ReactDOM.findDOMNode(this.refs.email).value.trim().toLowerCase();
if (!team.email || !Utils.isEmail(team.email)) {
- state.emailError = 'Please enter a valid email address';
+ state.emailError = this.props.intl.formatMessage(holders.emailError);
isValid = false;
} else {
state.emailError = null;
@@ -67,7 +80,7 @@ export default class EmailSignUpPage extends React.Component {
type='email'
ref='email'
className='form-control'
- placeholder='Email Address'
+ placeholder={this.props.intl.formatMessage(holders.address)}
maxLength='128'
spellCheck='false'
/>
@@ -78,12 +91,20 @@ export default class EmailSignUpPage extends React.Component {
className='btn btn-md btn-primary'
type='submit'
>
- {'Create Team'}
+ <FormattedMessage
+ id='email_signup.createTeam'
+ defaultMessage='Create Team'
+ />
</button>
{serverError}
</div>
<div className='form-group margin--extra-2x'>
- <span><a href='/find_team'>{`Find my teams`}</a></span>
+ <span><a href='/find_team'>
+ <FormattedMessage
+ id='email_signup.find'
+ defaultMessage='Find my teams'
+ />
+ </a></span>
</div>
</form>
);
@@ -93,4 +114,7 @@ export default class EmailSignUpPage extends React.Component {
EmailSignUpPage.defaultProps = {
};
EmailSignUpPage.propTypes = {
+ intl: intlShape.isRequired
};
+
+export default injectIntl(EmailSignUpPage); \ No newline at end of file
diff --git a/web/react/components/team_signup_with_sso.jsx b/web/react/components/team_signup_with_sso.jsx
index f4b323956..465f73fd2 100644
--- a/web/react/components/team_signup_with_sso.jsx
+++ b/web/react/components/team_signup_with_sso.jsx
@@ -5,7 +5,24 @@ import * as utils from '../utils/utils.jsx';
import * as client from '../utils/client.jsx';
import Constants from '../utils/constants.jsx';
-export default class SSOSignUpPage extends React.Component {
+import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'mm-intl';
+
+const holders = defineMessages({
+ team_error: {
+ id: 'sso_signup.team_error',
+ defaultMessage: 'Please enter a team name'
+ },
+ length_error: {
+ id: 'sso_signup.length_error',
+ defaultMessage: 'Name must be 3 or more characters up to a maximum of 15'
+ },
+ teamName: {
+ id: 'sso_signup.teamName',
+ defaultMessage: 'Enter name of new team'
+ }
+});
+
+class SSOSignUpPage extends React.Component {
constructor(props) {
super(props);
@@ -16,6 +33,7 @@ export default class SSOSignUpPage extends React.Component {
}
handleSubmit(e) {
e.preventDefault();
+ const {formatMessage} = this.props.intl;
var team = {};
var state = this.state;
state.nameError = null;
@@ -24,13 +42,13 @@ export default class SSOSignUpPage extends React.Component {
team.display_name = this.state.name;
if (!team.display_name) {
- state.nameError = 'Please enter a team name';
+ state.nameError = formatMessage(holders.team_error);
this.setState(state);
return;
}
if (team.display_name.length <= 2) {
- state.nameError = 'Name must be 3 or more characters up to a maximum of 15';
+ state.nameError = formatMessage(holders.length_error);
this.setState(state);
return;
}
@@ -80,24 +98,36 @@ export default class SSOSignUpPage extends React.Component {
button = (
<a
className='btn btn-custom-login gitlab btn-full'
+ key='gitlab'
href='#'
onClick={this.handleSubmit}
disabled={disabled}
>
<span className='icon'/>
- <span>{'Create team with GitLab Account'}</span>
+ <span>
+ <FormattedMessage
+ id='sso_signup.gitlab'
+ defaultMessage='Create team with GitLab Account'
+ />
+ </span>
</a>
);
} else if (this.props.service === Constants.GOOGLE_SERVICE) {
button = (
<a
className='btn btn-custom-login google btn-full'
+ key='google'
href='#'
onClick={this.handleSubmit}
disabled={disabled}
>
<span className='icon'/>
- <span>{'Create team with Google Apps Account'}</span>
+ <span>
+ <FormattedMessage
+ id='sso_signup.google'
+ defaultMessage='Create team with Google Apps Account'
+ />
+ </span>
</a>
);
}
@@ -113,7 +143,7 @@ export default class SSOSignUpPage extends React.Component {
type='text'
ref='teamname'
className='form-control'
- placeholder='Enter name of new team'
+ placeholder={this.props.intl.formatMessage(holders.teamName)}
maxLength='128'
onChange={this.nameChange}
spellCheck='false'
@@ -125,7 +155,12 @@ export default class SSOSignUpPage extends React.Component {
{serverError}
</div>
<div className='form-group margin--extra-2x'>
- <span><a href='/find_team'>{'Find my teams'}</a></span>
+ <span><a href='/find_team'>
+ <FormattedMessage
+ id='sso_signup.find'
+ defaultMessage='Find my teams'
+ />
+ </a></span>
</div>
</form>
);
@@ -136,5 +171,8 @@ SSOSignUpPage.defaultProps = {
service: ''
};
SSOSignUpPage.propTypes = {
+ intl: intlShape.isRequired,
service: React.PropTypes.string
};
+
+export default injectIntl(SSOSignUpPage); \ No newline at end of file
diff --git a/web/react/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/tutorial/tutorial_intro_screens.jsx b/web/react/components/tutorial/tutorial_intro_screens.jsx
index 7ab1e5512..78cfb7b60 100644
--- a/web/react/components/tutorial/tutorial_intro_screens.jsx
+++ b/web/react/components/tutorial/tutorial_intro_screens.jsx
@@ -9,6 +9,9 @@ import * as Utils from '../../utils/utils.jsx';
import * as AsyncClient from '../../utils/async_client.jsx';
import Constants from '../../utils/constants.jsx';
+
+import {FormattedMessage, FormattedHTMLMessage} from 'mm-intl';
+
const Preferences = Constants.Preferences;
const NUM_SCREENS = 3;
@@ -61,10 +64,13 @@ export default class TutorialIntroScreens extends React.Component {
return (
<div>
- <h3>{'Welcome to:'}</h3>
- <h1>{'Mattermost'}</h1>
- <p>{'Your team communication all in one place, instantly searchable and available anywhere.'}</p>
- <p>{'Keep your team connected to help them achieve what matters most.'}</p>
+ <FormattedHTMLMessage
+ id='tutorial_intro.screenOne'
+ defaultMessage='<h3>Welcome to:</h3>
+ <h1>Mattermost</h1>
+ <p>Your team communication all in one place, instantly searchable and available anywhere</p>
+ <p>Keep your team connected to help them achieve what matters most.</p>'
+ />
{circles}
</div>
);
@@ -74,9 +80,12 @@ export default class TutorialIntroScreens extends React.Component {
return (
<div>
- <h3>{'How Mattermost works:'}</h3>
- <p>{'Communication happens in public discussion channels, private groups and direct messages.'}</p>
- <p>{'Everything is archived and searchable from any web-enabled desktop, laptop or phone.'}</p>
+ <FormattedHTMLMessage
+ id='tutorial_intro.screenTwo'
+ defaultMessage='<h3>How Mattermost works:</h3>
+ <p>Communication happens in public discussion channels, private groups and direct messages.</p>
+ <p>Everything is archived and searchable from any web-enabled desktop, laptop or phone.</p>'
+ />
{circles}
</div>
);
@@ -92,7 +101,10 @@ export default class TutorialIntroScreens extends React.Component {
data-toggle='modal'
data-target='#invite_member'
>
- {'Invite teammates'}
+ <FormattedMessage
+ id='tutorial_intro.invite'
+ defaultMessage='Invite teammates'
+ />
</a>
);
} else {
@@ -105,7 +117,10 @@ export default class TutorialIntroScreens extends React.Component {
data-title='Team Invite'
data-value={Utils.getWindowLocationOrigin() + '/signup_user_complete/?id=' + team.id}
>
- {'Invite teammates'}
+ <FormattedMessage
+ id='tutorial_intro.teamInvite'
+ defaultMessage='Team Invite'
+ />
</a>
);
}
@@ -116,7 +131,10 @@ export default class TutorialIntroScreens extends React.Component {
if (global.window.mm_config.SupportEmail) {
supportInfo = (
<p>
- {'Need anything, just email us at '}
+ <FormattedMessage
+ id='tutorial_intro.support'
+ defaultMessage='Need anything, just email us at '
+ />
<a
href={'mailto:' + global.window.mm_config.SupportEmail}
target='_blank'
@@ -130,13 +148,24 @@ export default class TutorialIntroScreens extends React.Component {
return (
<div>
- <h3>{'You’re all set'}</h3>
+ <h3>
+ <FormattedMessage
+ id='tutorial_intro.allSet'
+ defaultMessage='You’re all set'
+ />
+ </h3>
<p>
{inviteModalLink}
- {' when you’re ready.'}
+ <FormattedMessage
+ id='tutorial_intro.whenReady'
+ defaultMessage=' when you’re ready.'
+ />
</p>
{supportInfo}
- {'Click “Next” to enter Town Square. This is the first channel teammates see when they sign up. Use it for posting updates everyone needs to know.'}
+ <FormattedMessage
+ id='tutorial_intro.end'
+ defaultMessage='Click “Next” to enter Town Square. This is the first channel teammates see when they sign up. Use it for posting updates everyone needs to know.'
+ />
{circles}
</div>
);
@@ -186,14 +215,20 @@ export default class TutorialIntroScreens extends React.Component {
tabIndex='1'
onClick={this.handleNext}
>
- {'Next'}
+ <FormattedMessage
+ id='tutorial_intro.next'
+ defaultMessage='Next'
+ />
</button>
<a
className='tutorial-skip'
href='#'
onClick={this.skipTutorial}
>
- {'Skip tutorial'}
+ <FormattedMessage
+ id='tutorial_intro.skip'
+ defaultMessage='Skip tutorial'
+ />
</a>
</div>
</div>
diff --git a/web/react/components/tutorial/tutorial_tip.jsx b/web/react/components/tutorial/tutorial_tip.jsx
index 03ecdabab..6bd7d89a4 100644
--- a/web/react/components/tutorial/tutorial_tip.jsx
+++ b/web/react/components/tutorial/tutorial_tip.jsx
@@ -6,6 +6,9 @@ import PreferenceStore from '../../stores/preference_store.jsx';
import * as AsyncClient from '../../utils/async_client.jsx';
import Constants from '../../utils/constants.jsx';
+
+import {FormattedMessage} from 'mm-intl';
+
const Preferences = Constants.Preferences;
const Overlay = ReactBootstrap.Overlay;
@@ -46,7 +49,17 @@ export default class TutorialTip extends React.Component {
AsyncClient.savePreferences([preference]);
}
render() {
- const buttonText = this.state.currentScreen === this.props.screens.length - 1 ? 'Okay' : 'Next';
+ const buttonText = this.state.currentScreen === this.props.screens.length - 1 ? (
+ <FormattedMessage
+ id='tutorial_tip.ok'
+ defaultMessage='Okay'
+ />
+ ) : (
+ <FormattedMessage
+ id='tutorial_tip.next'
+ defaultMessage='Next'
+ />
+ );
const dots = [];
if (this.props.screens.length > 1) {
@@ -71,7 +84,7 @@ export default class TutorialTip extends React.Component {
}
var tipColor = '';
- if (this.props.overlayClass === 'tip-overlay--header') {
+ if (this.props.overlayClass === 'tip-overlay--header' || this.props.overlayClass === 'tip-overlay--sidebar') {
tipColor = 'White';
}
@@ -111,12 +124,18 @@ export default class TutorialTip extends React.Component {
{buttonText}
</button>
<div className='tip-opt'>
- {'Seen this before? '}
+ <FormattedMessage
+ id='tutorial_tip.seen'
+ defaultMessage='Seen this before? '
+ />
<a
href='#'
onClick={this.skipTutorial}
>
- {'Opt out of these tips.'}
+ <FormattedMessage
+ id='tutorial_tip.out'
+ defaultMessage='Opt out of these tips.'
+ />
</a>
</div>
</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 bcd1be13d..cd9903878 100644
--- a/web/react/components/user_settings/user_settings_integrations.jsx
+++ b/web/react/components/user_settings/user_settings_integrations.jsx
@@ -7,7 +7,28 @@ import ManageIncomingHooks from './manage_incoming_hooks.jsx';
import ManageOutgoingHooks from './manage_outgoing_hooks.jsx';
import ManageCommandHooks from './manage_command_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);
@@ -23,6 +44,7 @@ export default class UserSettingsIntegrationsTab extends React.Component {
let outgoingHooksSection;
let commandHooksSection;
var inputs = [];
+ const {formatMessage} = this.props.intl;
if (global.window.mm_config.EnableIncomingWebhooks === 'true') {
if (this.props.activeSection === 'incoming-hooks') {
@@ -32,7 +54,7 @@ export default class UserSettingsIntegrationsTab extends React.Component {
incomingHooksSection = (
<SettingItemMax
- title='Incoming Webhooks'
+ title={formatMessage(holders.inName)}
width='medium'
inputs={inputs}
updateSection={(e) => {
@@ -44,9 +66,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');
}}
@@ -63,7 +85,7 @@ export default class UserSettingsIntegrationsTab extends React.Component {
outgoingHooksSection = (
<SettingItemMax
- title='Outgoing Webhooks'
+ title={formatMessage(holders.outName)}
width='medium'
inputs={inputs}
updateSection={(e) => {
@@ -75,9 +97,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');
}}
@@ -137,11 +159,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'/>
@@ -156,6 +186,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,
@@ -163,3 +194,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/dispatcher/event_helpers.jsx b/web/react/dispatcher/event_helpers.jsx
index a03923c1a..5eb319320 100644
--- a/web/react/dispatcher/event_helpers.jsx
+++ b/web/react/dispatcher/event_helpers.jsx
@@ -19,7 +19,8 @@ export function emitChannelClickEvent(channel) {
AppDispatcher.handleViewAction({
type: ActionTypes.CLICK_CHANNEL,
name: channel.name,
- id: channel.id
+ id: channel.id,
+ prev: ChannelStore.getCurrentId()
});
}
diff --git a/web/react/package.json b/web/react/package.json
index 14b16b4e4..fce3e6555 100644
--- a/web/react/package.json
+++ b/web/react/package.json
@@ -8,22 +8,23 @@
"highlight.js": "8.9.1",
"keymirror": "0.1.1",
"marked": "mattermost/marked#cb85e5cc81bc7937dbb73c3c53d9532b1b97e3ca",
+ "mm-intl": "mattermost/mm-intl#805442fd474fa40cd586ddeda404dbbe8e60626d",
"object-assign": "4.0.1",
"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/pages/channel.jsx b/web/react/pages/channel.jsx
index ffe232ab6..bfb95e1fc 100644
--- a/web/react/pages/channel.jsx
+++ b/web/react/pages/channel.jsx
@@ -4,7 +4,7 @@
import ChannelView from '../components/channel_view.jsx';
import ChannelLoader from '../components/channel_loader.jsx';
import ErrorBar from '../components/error_bar.jsx';
-import ErrorStore from '../stores/error_store.jsx';
+import * as Client from '../utils/client.jsx';
import GetTeamInviteLinkModal from '../components/get_team_invite_link_modal.jsx';
import RenameChannelModal from '../components/rename_channel_modal.jsx';
@@ -18,108 +18,83 @@ import RegisterAppModal from '../components/register_app_modal.jsx';
import ImportThemeModal from '../components/user_settings/import_theme_modal.jsx';
import InviteMemberModal from '../components/invite_member_modal.jsx';
-import PreferenceStore from '../stores/preference_store.jsx';
-
-import * as Utils from '../utils/utils.jsx';
-import * as AsyncClient from '../utils/async_client.jsx';
import * as EventHelpers from '../dispatcher/event_helpers.jsx';
-import Constants from '../utils/constants.jsx';
+var IntlProvider = ReactIntl.IntlProvider;
+
+class Root extends React.Component {
+ constructor() {
+ super();
+ this.state = {
+ translations: null,
+ loaded: false
+ };
+ }
+
+ static propTypes() {
+ return {
+ map: React.PropTypes.object.isRequired
+ };
+ }
+
+ componentWillMount() {
+ Client.getTranslations(
+ this.props.map.Locale,
+ (data) => {
+ this.setState({
+ translations: data,
+ loaded: true
+ });
+ },
+ () => {
+ this.setState({
+ loaded: true
+ });
+ }
+ );
+ }
-function onPreferenceChange() {
- const selectedFont = PreferenceStore.get(Constants.Preferences.CATEGORY_DISPLAY_SETTINGS, 'selected_font', Constants.DEFAULT_FONT);
- Utils.applyFont(selectedFont);
- PreferenceStore.removeChangeListener(onPreferenceChange);
+ render() {
+ if (!this.state.loaded) {
+ return <div></div>;
+ }
+
+ return (
+ <IntlProvider
+ locale={this.props.map.Locale}
+ messages={this.state.translations}
+ >
+ <div className='channel-view'>
+ <ChannelLoader/>
+ <ErrorBar/>
+ <ChannelView/>
+
+ <GetTeamInviteLinkModal />
+ <InviteMemberModal />
+ <ImportThemeModal />
+ <TeamSettingsModal />
+ <RenameChannelModal />
+ <MoreChannelsModal />
+ <EditPostModal />
+ <DeletePostModal />
+ <PostDeletedModal />
+ <RemovedFromChannelModal />
+ <RegisterAppModal />
+ </div>
+ </IntlProvider>
+ );
+ }
}
-function setupChannelPage(props, team, channel) {
+global.window.setup_channel_page = function setup(props, team, channel) {
if (props.PostId === '') {
EventHelpers.emitChannelClickEvent(channel);
} else {
EventHelpers.emitPostFocusEvent(props.PostId);
}
- PreferenceStore.addChangeListener(onPreferenceChange);
- AsyncClient.getAllPreferences();
-
- // ChannelLoader must be rendered first
- ReactDOM.render(
- <ChannelLoader/>,
- document.getElementById('channel_loader')
- );
-
- ReactDOM.render(
- <ErrorBar/>,
- document.getElementById('error_bar')
- );
-
ReactDOM.render(
- <ChannelView/>,
+ <Root map={props} />,
document.getElementById('channel_view')
);
-
- //
- // Modals
- //
- ReactDOM.render(
- <GetTeamInviteLinkModal />,
- document.getElementById('get_team_invite_link_modal')
- );
-
- ReactDOM.render(
- <InviteMemberModal />,
- document.getElementById('invite_member_modal')
- );
-
- ReactDOM.render(
- <ImportThemeModal />,
- document.getElementById('import_theme_modal')
- );
-
- ReactDOM.render(
- <TeamSettingsModal />,
- document.getElementById('team_settings_modal')
- );
-
- ReactDOM.render(
- <RenameChannelModal />,
- document.getElementById('rename_channel_modal')
- );
-
- ReactDOM.render(
- <MoreChannelsModal />,
- document.getElementById('more_channels_modal')
- );
-
- ReactDOM.render(
- <EditPostModal />,
- document.getElementById('edit_post_modal')
- );
-
- ReactDOM.render(
- <DeletePostModal />,
- document.getElementById('delete_post_modal')
- );
-
- ReactDOM.render(
- <PostDeletedModal />,
- document.getElementById('post_deleted_modal')
- );
-
- ReactDOM.render(
- <RemovedFromChannelModal />,
- document.getElementById('removed_from_channel_modal')
- );
-
- ReactDOM.render(
- <RegisterAppModal />,
- document.getElementById('register_app_modal')
- );
-
- if (global.window.mm_config.SendEmailNotifications === 'false') {
- ErrorStore.storeLastError({message: 'Preview Mode: Email notifications have not been configured'});
- ErrorStore.emitChange();
- }
-}
-
-global.window.setup_channel_page = setupChannelPage;
+};
diff --git a/web/react/pages/find_team.jsx b/web/react/pages/find_team.jsx
index c4653fd77..ee2cf0de1 100644
--- a/web/react/pages/find_team.jsx
+++ b/web/react/pages/find_team.jsx
@@ -2,12 +2,61 @@
// See License.txt for license information.
import FindTeam from '../components/find_team.jsx';
+import * as Client from '../utils/client.jsx';
-function setupFindTeamPage() {
+var IntlProvider = ReactIntl.IntlProvider;
+
+class Root extends React.Component {
+ constructor() {
+ super();
+ this.state = {
+ translations: null,
+ loaded: false
+ };
+ }
+
+ static propTypes() {
+ return {
+ map: React.PropTypes.object.isRequired
+ };
+ }
+
+ componentWillMount() {
+ Client.getTranslations(
+ this.props.map.Locale,
+ (data) => {
+ this.setState({
+ translations: data,
+ loaded: true
+ });
+ },
+ () => {
+ this.setState({
+ loaded: true
+ });
+ }
+ );
+ }
+
+ render() {
+ if (!this.state.loaded) {
+ return <div></div>;
+ }
+
+ return (
+ <IntlProvider
+ locale={this.props.map.Locale}
+ messages={this.state.translations}
+ >
+ <FindTeam />
+ </IntlProvider>
+ );
+ }
+}
+
+global.window.setup_find_team_page = function setup(props) {
ReactDOM.render(
- <FindTeam />,
+ <Root map={props} />,
document.getElementById('find-team')
);
-}
-
-global.window.setup_find_team_page = setupFindTeamPage;
+}; \ No newline at end of file
diff --git a/web/react/pages/signup_team.jsx b/web/react/pages/signup_team.jsx
index 8f4f86a7c..c80b65580 100644
--- a/web/react/pages/signup_team.jsx
+++ b/web/react/pages/signup_team.jsx
@@ -60,7 +60,7 @@ global.window.setup_signup_team_page = function setup(props) {
for (var prop in props) {
if (props.hasOwnProperty(prop)) {
- if (prop !== 'Title') {
+ if (prop !== 'Title' && prop !== 'Locale' && prop !== 'Info') {
teams.push({name: prop, display_name: props[prop]});
}
}
diff --git a/web/react/pages/signup_team_confirm.jsx b/web/react/pages/signup_team_confirm.jsx
new file mode 100644
index 000000000..9a536c92e
--- /dev/null
+++ b/web/react/pages/signup_team_confirm.jsx
@@ -0,0 +1,64 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import SignupTeamConfirm from '../components/signup_team_confirm.jsx';
+import * as Client from '../utils/client.jsx';
+
+var IntlProvider = ReactIntl.IntlProvider;
+
+class Root extends React.Component {
+ constructor() {
+ super();
+ this.state = {
+ translations: null,
+ loaded: false
+ };
+ }
+
+ static propTypes() {
+ return {
+ map: React.PropTypes.object.isRequired
+ };
+ }
+
+ componentWillMount() {
+ Client.getTranslations(
+ this.props.map.Locale,
+ (data) => {
+ this.setState({
+ translations: data,
+ loaded: true
+ });
+ },
+ () => {
+ this.setState({
+ loaded: true
+ });
+ }
+ );
+ }
+
+ render() {
+ if (!this.state.loaded) {
+ return <div></div>;
+ }
+
+ return (
+ <IntlProvider
+ locale={this.props.map.Locale}
+ messages={this.state.translations}
+ >
+ <SignupTeamConfirm
+ email={this.props.map.Email}
+ />
+ </IntlProvider>
+ );
+ }
+}
+
+global.window.setup_signup_team_confirm_page = function setup(props) {
+ ReactDOM.render(
+ <Root map={props} />,
+ document.getElementById('signup-team-confirm')
+ );
+}; \ No newline at end of file
diff --git a/web/react/stores/channel_store.jsx b/web/react/stores/channel_store.jsx
index 93d996e0b..2337a573d 100644
--- a/web/react/stores/channel_store.jsx
+++ b/web/react/stores/channel_store.jsx
@@ -36,7 +36,7 @@ class ChannelStoreClass extends EventEmitter {
this.get = this.get.bind(this);
this.getMember = this.getMember.bind(this);
this.getByName = this.getByName.bind(this);
- this.pSetPostMode = this.pSetPostMode.bind(this);
+ this.setPostMode = this.setPostMode.bind(this);
this.getPostMode = this.getPostMode.bind(this);
this.setUnreadCount = this.setUnreadCount.bind(this);
this.setUnreadCounts = this.setUnreadCounts.bind(this);
@@ -95,7 +95,7 @@ class ChannelStoreClass extends EventEmitter {
this.removeListener(LEAVE_EVENT, callback);
}
findFirstBy(field, value) {
- var channels = this.pGetChannels();
+ var channels = this.getChannels();
for (var i = 0; i < channels.length; i++) {
if (channels[i][field] === value) {
return channels[i];
@@ -114,13 +114,13 @@ class ChannelStoreClass extends EventEmitter {
return this.findFirstBy('name', name);
}
getAll() {
- return this.pGetChannels();
+ return this.getChannels();
}
getAllMembers() {
- return this.pGetChannelMembers();
+ return this.getChannelMembers();
}
getMoreAll() {
- return this.pGetMoreChannels();
+ return this.getMoreChannels();
}
setCurrentId(id) {
this.currentId = id;
@@ -161,9 +161,9 @@ class ChannelStoreClass extends EventEmitter {
return null;
}
setChannelMember(member) {
- var members = this.pGetChannelMembers();
+ var members = this.getChannelMembers();
members[member.channel_id] = member;
- this.pStoreChannelMembers(members);
+ this.storeChannelMembers(members);
this.emitChange();
}
getCurrentExtraInfo() {
@@ -173,7 +173,7 @@ class ChannelStoreClass extends EventEmitter {
var extra = null;
if (channelId) {
- extra = this.pGetExtraInfos()[channelId];
+ extra = this.getExtraInfos()[channelId];
}
if (extra) {
@@ -186,7 +186,7 @@ class ChannelStoreClass extends EventEmitter {
return extra;
}
pStoreChannel(channel) {
- var channels = this.pGetChannels();
+ var channels = this.getChannels();
var found;
for (var i = 0; i < channels.length; i++) {
@@ -206,42 +206,42 @@ class ChannelStoreClass extends EventEmitter {
}
channels.sort(Utils.sortByDisplayName);
- this.pStoreChannels(channels);
+ this.storeChannels(channels);
}
- pStoreChannels(channels) {
+ storeChannels(channels) {
this.channels = channels;
}
- pGetChannels() {
+ getChannels() {
return this.channels;
}
pStoreChannelMember(channelMember) {
- var members = this.pGetChannelMembers();
+ var members = this.getChannelMembers();
members[channelMember.channel_id] = channelMember;
- this.pStoreChannelMembers(members);
+ this.storeChannelMembers(members);
}
- pStoreChannelMembers(channelMembers) {
+ storeChannelMembers(channelMembers) {
this.channelMembers = channelMembers;
}
- pGetChannelMembers() {
+ getChannelMembers() {
return this.channelMembers;
}
- pStoreMoreChannels(channels) {
+ storeMoreChannels(channels) {
this.moreChannels = channels;
}
- pGetMoreChannels() {
+ getMoreChannels() {
return this.moreChannels;
}
- pStoreExtraInfos(extraInfos) {
+ storeExtraInfos(extraInfos) {
this.extraInfos = extraInfos;
}
- pGetExtraInfos() {
+ getExtraInfos() {
return this.extraInfos;
}
isDefault(channel) {
return channel.name === Constants.DEFAULT_CHANNEL;
}
- pSetPostMode(mode) {
+ setPostMode(mode) {
this.postMode = mode;
}
@@ -292,21 +292,21 @@ ChannelStore.dispatchToken = AppDispatcher.register((payload) => {
case ActionTypes.CLICK_CHANNEL:
ChannelStore.setCurrentId(action.id);
ChannelStore.resetCounts(action.id);
- ChannelStore.pSetPostMode(ChannelStore.POST_MODE_CHANNEL);
+ ChannelStore.setPostMode(ChannelStore.POST_MODE_CHANNEL);
ChannelStore.emitChange();
break;
case ActionTypes.RECIEVED_FOCUSED_POST: {
const post = action.post_list.posts[action.postId];
ChannelStore.setCurrentId(post.channel_id);
- ChannelStore.pSetPostMode(ChannelStore.POST_MODE_FOCUS);
+ ChannelStore.setPostMode(ChannelStore.POST_MODE_FOCUS);
ChannelStore.emitChange();
break;
}
case ActionTypes.RECIEVED_CHANNELS:
- ChannelStore.pStoreChannels(action.channels);
- ChannelStore.pStoreChannelMembers(action.members);
+ ChannelStore.storeChannels(action.channels);
+ ChannelStore.storeChannelMembers(action.members);
currentId = ChannelStore.getCurrentId();
if (currentId) {
ChannelStore.resetCounts(currentId);
@@ -329,14 +329,14 @@ ChannelStore.dispatchToken = AppDispatcher.register((payload) => {
break;
case ActionTypes.RECIEVED_MORE_CHANNELS:
- ChannelStore.pStoreMoreChannels(action.channels);
+ ChannelStore.storeMoreChannels(action.channels);
ChannelStore.emitMoreChange();
break;
case ActionTypes.RECIEVED_CHANNEL_EXTRA_INFO:
- var extraInfos = ChannelStore.pGetExtraInfos();
+ var extraInfos = ChannelStore.getExtraInfos();
extraInfos[action.extra_info.id] = action.extra_info;
- ChannelStore.pStoreExtraInfos(extraInfos);
+ ChannelStore.storeExtraInfos(extraInfos);
ChannelStore.emitExtraInfoChange();
break;
diff --git a/web/react/stores/error_store.jsx b/web/react/stores/error_store.jsx
index 69d6cca7f..ed46d6b68 100644
--- a/web/react/stores/error_store.jsx
+++ b/web/react/stores/error_store.jsx
@@ -46,6 +46,10 @@ class ErrorStoreClass extends EventEmitter {
storeLastError(error) {
BrowserStore.setItem('last_error', error);
}
+
+ clearLastError() {
+ BrowserStore.removeItem('last_error');
+ }
}
var ErrorStore = new ErrorStoreClass();
diff --git a/web/react/stores/post_store.jsx b/web/react/stores/post_store.jsx
index 2212edadb..7abadf2b1 100644
--- a/web/react/stores/post_store.jsx
+++ b/web/react/stores/post_store.jsx
@@ -451,6 +451,8 @@ class PostStoreClass extends EventEmitter {
post.filenames = [];
posts[post.id] = post;
+
+ this.makePostsInfo(post.channel_id);
this.postsInfo[post.channel_id].deletedPosts = posts;
}
@@ -610,7 +612,7 @@ PostStore.dispatchToken = AppDispatcher.register((payload) => {
case ActionTypes.CLICK_CHANNEL:
PostStore.clearFocusedPost();
PostStore.clearChannelVisibility(action.id, true);
- PostStore.clearUnseenDeletedPosts(action.id);
+ PostStore.clearUnseenDeletedPosts(action.prev);
break;
case ActionTypes.CREATE_POST:
PostStore.storePendingPost(action.post);
diff --git a/web/react/stores/preference_store.jsx b/web/react/stores/preference_store.jsx
index 79eab4fe1..7ecaf0a95 100644
--- a/web/react/stores/preference_store.jsx
+++ b/web/react/stores/preference_store.jsx
@@ -133,6 +133,16 @@ class PreferenceStoreClass extends EventEmitter {
return preference;
}
+ setPreferences(newPreferences) {
+ const preferences = this.getAllPreferences();
+
+ for (const preference of newPreferences) {
+ preferences.set(getPreferenceKeyForModel(preference), preference);
+ }
+
+ this.setAllPreferences(preferences);
+ }
+
emitChange() {
this.emit(CHANGE_EVENT);
}
@@ -155,18 +165,11 @@ class PreferenceStoreClass extends EventEmitter {
this.emitChange();
break;
}
- case ActionTypes.RECIEVED_PREFERENCES: {
- const preferences = this.getAllPreferences();
-
- for (const preference of action.preferences) {
- preferences.set(getPreferenceKeyForModel(preference), preference);
- }
-
- this.setAllPreferences(preferences);
+ case ActionTypes.RECIEVED_PREFERENCES:
+ this.setPreferences(action.preferences);
this.emitChange();
break;
}
- }
}
}
diff --git a/web/react/stores/socket_store.jsx b/web/react/stores/socket_store.jsx
index f1fade305..736b0ca27 100644
--- a/web/react/stores/socket_store.jsx
+++ b/web/react/stores/socket_store.jsx
@@ -176,6 +176,7 @@ function handleNewPostEvent(msg) {
mentions = JSON.parse(msg.props.mentions);
}
+ const channelType = msgProps.channel_type;
const channel = ChannelStore.get(msg.channel_id);
const user = UserStore.getCurrentUser();
const member = ChannelStore.getMember(msg.channel_id);
@@ -187,7 +188,7 @@ function handleNewPostEvent(msg) {
if (notifyLevel === 'none') {
return;
- } else if (notifyLevel === 'mention' && mentions.indexOf(user.id) === -1 && channel.type !== 'D') {
+ } else if (notifyLevel === 'mention' && mentions.indexOf(user.id) === -1 && channelType !== Constants.DM_CHANNEL) {
return;
}
diff --git a/web/react/utils/constants.jsx b/web/react/utils/constants.jsx
index 851bc5f6c..e1a4b8a8a 100644
--- a/web/react/utils/constants.jsx
+++ b/web/react/utils/constants.jsx
@@ -168,6 +168,7 @@ export default {
OFFLINE_ICON_SVG: "<svg version='1.1'id='Layer_1' xmlns:dc='http://purl.org/dc/elements/1.1/' xmlns:inkscape='http://www.inkscape.org/namespaces/inkscape' xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#' xmlns:svg='http://www.w3.org/2000/svg' xmlns:sodipodi='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd' xmlns:cc='http://creativecommons.org/ns#' inkscape:version='0.48.4 r9939' sodipodi:docname='TRASH_1_4.svg'xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' viewBox='-299 391 12 12'style='enable-background:new -299 391 12 12;' xml:space='preserve'> <sodipodi:namedview inkscape:cx='26.358185' inkscape:zoom='1.18' bordercolor='#666666' pagecolor='#ffffff' borderopacity='1' objecttolerance='10' inkscape:cy='139.7898' gridtolerance='10' guidetolerance='10' showgrid='false' showguides='true' id='namedview6' inkscape:pageopacity='0' inkscape:pageshadow='2' inkscape:guide-bbox='true' inkscape:window-width='1366' inkscape:current-layer='Layer_1' inkscape:window-height='705' inkscape:window-y='-8' inkscape:window-maximized='1' inkscape:window-x='-8'> <sodipodi:guide position='50.036793,85.991376' orientation='1,0' id='guide2986'></sodipodi:guide> <sodipodi:guide position='58.426196,66.216355' orientation='0,1' id='guide3047'></sodipodi:guide> </sodipodi:namedview> <g> <g> <ellipse class='offline--icon' cx='-294.5' cy='394' rx='2.5' ry='2.5'/> <path class='offline--icon' d='M-294.3,399.7c0-0.4,0.1-0.8,0.2-1.2c-0.1,0-0.2,0-0.4,0c-2.5,0-2.5-2-2.5-2s-1,0.1-1.2,0.5c-0.4,0.6-0.6,1.7-0.7,2.5 c0,0.1-0.1,0.5,0,0.6c0.2,1.3,2.2,2.3,4.4,2.4h0.1h0.1c0.3,0,0.7,0,1-0.1C-293.9,401.6-294.3,400.7-294.3,399.7z'/> </g> </g> <g> <path class='offline--icon' d='M-288.9,399.4l1.8-1.8c0.1-0.1,0.1-0.3,0-0.3l-0.7-0.7c-0.1-0.1-0.3-0.1-0.3,0l-1.8,1.8l-1.8-1.8c-0.1-0.1-0.3-0.1-0.3,0 l-0.7,0.7c-0.1,0.1-0.1,0.3,0,0.3l1.8,1.8l-1.8,1.8c-0.1,0.1-0.1,0.3,0,0.3l0.7,0.7c0.1,0.1,0.3,0.1,0.3,0l1.8-1.8l1.8,1.8 c0.1,0.1,0.3,0.1,0.3,0l0.7-0.7c0.1-0.1,0.1-0.3,0-0.3L-288.9,399.4z'/> </g> </svg>",
MENU_ICON: "<svg version='1.1' id='Layer_1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px'width='4px' height='16px' viewBox='0 0 8 32' enable-background='new 0 0 8 32' xml:space='preserve'> <g> <circle cx='4' cy='4.062' r='4'/> <circle cx='4' cy='16' r='4'/> <circle cx='4' cy='28' r='4'/> </g> </svg>",
COMMENT_ICON: "<svg version='1.1' id='Layer_2' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px'width='15px' height='15px' viewBox='1 1.5 15 15' enable-background='new 1 1.5 15 15' xml:space='preserve'> <g> <g> <path fill='#211B1B' d='M14,1.5H3c-1.104,0-2,0.896-2,2v8c0,1.104,0.896,2,2,2h1.628l1.884,3l1.866-3H14c1.104,0,2-0.896,2-2v-8 C16,2.396,15.104,1.5,14,1.5z M15,11.5c0,0.553-0.447,1-1,1H8l-1.493,2l-1.504-1.991L5,12.5H3c-0.552,0-1-0.447-1-1v-8 c0-0.552,0.448-1,1-1h11c0.553,0,1,0.448,1,1V11.5z'/> </g> </g> </svg>",
+ REPLY_ICON: "<svg version='1.1' id='Layer_1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px'viewBox='-158 242 18 18' style='enable-background:new -158 242 18 18;' xml:space='preserve'> <path d='M-142.2,252.6c-2-3-4.8-4.7-8.3-4.8v-3.3c0-0.2-0.1-0.3-0.2-0.3s-0.3,0-0.4,0.1l-6.9,6.2c-0.1,0.1-0.1,0.2-0.1,0.3 c0,0.1,0,0.2,0.1,0.3l6.9,6.4c0.1,0.1,0.3,0.1,0.4,0.1c0.1-0.1,0.2-0.2,0.2-0.4v-3.8c4.2,0,7.4,0.4,9.6,4.4c0.1,0.1,0.2,0.2,0.3,0.2 c0,0,0.1,0,0.1,0c0.2-0.1,0.3-0.3,0.2-0.4C-140.2,257.3-140.6,255-142.2,252.6z M-150.8,252.5c-0.2,0-0.4,0.2-0.4,0.4v3.3l-6-5.5 l6-5.3v2.8c0,0.2,0.2,0.4,0.4,0.4c3.3,0,6,1.5,8,4.5c0.5,0.8,0.9,1.6,1.2,2.3C-144,252.8-147.1,252.5-150.8,252.5z'/> </svg>",
UPDATE_TYPING_MS: 5000,
THEMES: {
default: {
diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx
index 82e9bc447..494c38bdb 100644
--- a/web/react/utils/utils.jsx
+++ b/web/react/utils/utils.jsx
@@ -714,7 +714,7 @@ export function applyTheme(theme) {
if (theme.linkColor) {
changeCss('a, a:focus, a:hover, .btn, .btn:focus, .btn:hover', 'color:' + theme.linkColor, 1);
- changeCss('.post .comment-icon__container', 'fill:' + theme.linkColor, 1);
+ changeCss('.post .comment-icon__container, .post .post__reply', 'fill:' + theme.linkColor, 1);
}
if (theme.buttonBg) {
diff --git a/web/sass-files/sass/partials/_admin-console.scss b/web/sass-files/sass/partials/_admin-console.scss
index b28c7d984..dc8b950e4 100644
--- a/web/sass-files/sass/partials/_admin-console.scss
+++ b/web/sass-files/sass/partials/_admin-console.scss
@@ -5,6 +5,15 @@
height: 100%;
}
+ .inner__wrap {
+ position: absolute;
+ width: 100%;
+ }
+
+ .row {
+ margin: 0;
+ }
+
h3 {
font-weight: 600;
border-bottom: 1px solid #ddd;
diff --git a/web/sass-files/sass/partials/_content.scss b/web/sass-files/sass/partials/_content.scss
index b54c97b41..fb2355da7 100644
--- a/web/sass-files/sass/partials/_content.scss
+++ b/web/sass-files/sass/partials/_content.scss
@@ -38,6 +38,7 @@
font-size: 13px;
.fa {
+ font-size: 11px;
@include opacity(0.7);
}
diff --git a/web/sass-files/sass/partials/_markdown.scss b/web/sass-files/sass/partials/_markdown.scss
index 9ad15f91a..7aa29d95d 100644
--- a/web/sass-files/sass/partials/_markdown.scss
+++ b/web/sass-files/sass/partials/_markdown.scss
@@ -43,7 +43,7 @@
@include opacity(0.2);
}
code {
- white-space: pre;
+ white-space: pre-line;
}
}
.markdown__table {
diff --git a/web/sass-files/sass/partials/_modal.scss b/web/sass-files/sass/partials/_modal.scss
index 7627f6a4c..b451adb75 100644
--- a/web/sass-files/sass/partials/_modal.scss
+++ b/web/sass-files/sass/partials/_modal.scss
@@ -10,6 +10,11 @@
.modal {
width: 100%;
color: #333;
+ body.browser--IE & {
+ .modal-dialog {
+ @include translateY(0);
+ }
+ }
&.image_modal {
.modal-backdrop.in {
@include opacity(0.7);
diff --git a/web/sass-files/sass/partials/_post.scss b/web/sass-files/sass/partials/_post.scss
index 34ff7e5a9..be85ef07b 100644
--- a/web/sass-files/sass/partials/_post.scss
+++ b/web/sass-files/sass/partials/_post.scss
@@ -408,7 +408,7 @@ body.ios {
@include legacy-pie-clearfix;
&:hover {
- .dropdown, .comment-icon__container {
+ .dropdown, .comment-icon__container, .post__reply {
visibility: visible;
}
.permalink-icon {
@@ -754,15 +754,35 @@ body.ios {
visibility: hidden;
}
+ .post__reply {
+ display: inline-block;
+ margin-right: 6px;
+ visibility: hidden;
+ svg {
+ width: 18px;
+ top: 3px;
+ fill: inherit;
+ position: relative;
+ }
+ }
+
.comment-icon__container {
fill: $primary-color;
display: inline-block;
visibility: hidden;
+ &:focus {
+ outline: none;
+ }
+
&.icon--visible {
visibility: visible;
}
+ svg {
+ width: 17px;
+ }
+
.comment-icon {
display: inline-block;
top: 3px;
diff --git a/web/sass-files/sass/partials/_responsive.scss b/web/sass-files/sass/partials/_responsive.scss
index 8491869a6..832481cc5 100644
--- a/web/sass-files/sass/partials/_responsive.scss
+++ b/web/sass-files/sass/partials/_responsive.scss
@@ -72,29 +72,19 @@
}
- .post {
-
- .post-list__content & {
-
- &:hover {
- background: transparent;
-
- .comment-icon__container {
- visibility: hidden;
-
- &.icon--show {
- visibility: visible;
- }
+}
- }
+@media screen and (max-width: 768px) {
- }
+ .signup-team__container {
+ font-size: 1em;
+ }
- }
+ .date-separator.hovered--after:before, .new-separator.hovered--after:before {
+ background: none !important;
+ }
- .dropdown {
- visibility: visible;
- }
+ .post {
.post__dropdown {
line-height: 9px;
@@ -112,20 +102,50 @@
}
- }
+ .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);
+ }
+
+ .post__reply {
+ margin-right: 20px;
+ float: right;
+
+ svg {
+ top: 1px;
+ }
+
+ }
+
+ &.other--root .post__reply {
+
+ &.post__reply--hide {
+ visibility: hidden;
+ }
+
+ }
.post__header {
+ .col__reply {
+ width: 65px;
+ }
+
.col__name {
pointer-events: none;
}
diff --git a/web/sass-files/sass/partials/_tutorial.scss b/web/sass-files/sass/partials/_tutorial.scss
index 20a15441e..0a2d1e704 100644
--- a/web/sass-files/sass/partials/_tutorial.scss
+++ b/web/sass-files/sass/partials/_tutorial.scss
@@ -146,6 +146,7 @@
&.tip-overlay--sidebar {
left: 0;
+ @include opacity(0.8);
top: -9px;
}
diff --git a/web/static/help/Messaging.md b/web/static/help/Messaging.md
deleted file mode 120000
index f74c0b879..000000000
--- a/web/static/help/Messaging.md
+++ /dev/null
@@ -1 +0,0 @@
-../../../doc/help/Messaging.md \ No newline at end of file
diff --git a/doc/help/Messaging.md b/web/static/help/Messaging_en.md
index 2063ad41c..2063ad41c 100644
--- a/doc/help/Messaging.md
+++ b/web/static/help/Messaging_en.md
diff --git a/web/static/help/Messaging_es.md b/web/static/help/Messaging_es.md
new file mode 100644
index 000000000..d3947f36a
--- /dev/null
+++ b/web/static/help/Messaging_es.md
@@ -0,0 +1,37 @@
+# Mensajes
+
+## Escribiendo Mensajes
+
+Puedes escribir mensajes utilizando el cuadro de texto que dice "Escribe un mensaje..." al final de Mattermost.
+
+Presiona **RETORNO** para enviar un mensaje. Utiliza **Shift+RETORNO** para crear una nueva linea sin enviar el mensaje.
+
+## Darle formato a los Mensajes
+
+Los mensajes de Mattermost se les asigna formato utilizando un estándard que se llama "markdown". Aquí algunos ejemplos:
+
+| Texto escrito | Como aparece |
+|:--------------|:-------------|
+|`**negrita**`| **negrita** |
+| `_italica_`|_italica_|
+|`[hipervinculo](http://mattermost.org)`|[hipervinculo](http://mattermost.org)|
+|`![imagen embebida](https://travis-ci.org/mattermost/platform.svg)`|![imagen embebida](https://travis-ci.org/mattermost/platform.svg)|
+|`:smile:` `:sheep:` `:alien:`|:smile: :sheep: :alien:|
+
+Revisa la lista completa de Emojis [aquí](http://www.emoji-cheat-sheet.com/).
+
+## Mencionando a compañeros
+
+Puedes mencionar a un compañero al utilizar el simbolo `@` más el nombre de usuario para enviarles una notificación especial que llame su atención.
+
+Por ejemplo, podrías escribir:
+
+```
+@alicia como te fue con la entrevista del nuevo candidato?
+```
+
+Lo cual enviará una notificación especial de mención a **alicia** para que lea tu mensaje.
+
+Para mencionar un compañero, presiona `@` y podrás ver una lista de los miembros de equipo a quienes puedes mandarles un mensaje. Puedes escribir su nombre de usuario o utilizar las flechas de **Arriba** y **Abajo** y presionar **RETORNO** para seleccionarlos.
+
+Puedes configurar como te gustaría ser notificado cuando alguien te menciona por nombre de usuario, tu primer nombre, sobrenombre o cualquier otra palabra clave en **Configurar Cuenta** > **Notificaciones** y puedes asignar preferencias especificas para un canal en **[Nombre del Canal]** > **Preferencias de Notificación**
diff --git a/web/static/i18n/en.json b/web/static/i18n/en.json
index 038b1342d..d6401ab6e 100644
--- a/web/static/i18n/en.json
+++ b/web/static/i18n/en.json
@@ -1,4 +1,1047 @@
{
- "login.find_teams": "Find your other teams",
- "login.forgot_password": "I forgot my password"
+ "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",
+ "admin.nav.report": "Report a Problem",
+ "admin.sidebarHeader.systemConsole": "System Console",
+ "admin.sidebar.loading": "Loading",
+ "admin.sidebar.rmTeamSidebar": "Remove team from sidebar menu",
+ "admin.sidebar.addTeamSidebar": "Add team from sidebar menu",
+ "admin.sidebar.users": "- Users",
+ "admin.sidebar.statistics": "- Statistics",
+ "admin.sidebar.ldap": "LDAP Settings",
+ "admin.sidebar.license": "Edition and License",
+ "admin.sidebar.reports": "SITE REPORTS",
+ "admin.sidebar.view_statistics": "View Statistics",
+ "admin.sidebar.settings": "SETTINGS",
+ "admin.sidebar.service": "Service Settings",
+ "admin.sidebar.team": "Team Settings",
+ "admin.sidebar.sql": "SQL Settings",
+ "admin.sidebar.email": "Email Settings",
+ "admin.sidebar.file": "File Settings",
+ "admin.sidebar.log": "Log Settings",
+ "admin.sidebar.rate_limit": "Rate Limit Settings",
+ "admin.sidebar.privacy": "Privacy Settings",
+ "admin.sidebar.gitlab": "GitLab Settings",
+ "admin.sidebar.support": "Legal and Support Settings",
+ "admin.sidebar.teams": "TEAMS ({count})",
+ "admin.sidebar.other": "OTHER",
+ "admin.sidebar.logs": "Logs",
+ "admin.analytics.loading": "Loading...",
+ "admin.analytics.totalUsers": "Total Users",
+ "admin.analytics.publicChannels": "Public Channels",
+ "admin.analytics.privateGroups": "Private Groups",
+ "admin.analytics.totalPosts": "Total Posts",
+ "admin.analytics.meaningful": "Not enough data for a meaningful representation.",
+ "admin.analytics.activeUsers": "Active Users With Posts",
+ "admin.analytics.recentActive": "Recent Active Users",
+ "admin.analytics.newlyCreated": "Newly Created Users",
+ "admin.analytics.title": "Statistics for {title}",
+ "admin.email.notificationDisplayExample": "Ex: \"Mattermost Notification\", \"System\", \"No-Reply\"",
+ "admin.email.notificationEmailExample": "Ex: \"mattermost@yourcompany.com\", \"admin@yourcompany.com\"",
+ "admin.email.smtpUsernameExample": "Ex: \"admin@yourcompany.com\", \"AKIADTOVBGERKLCBV\"",
+ "admin.email.smtpPasswordExample": "Ex: \"yourpassword\", \"jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY\"",
+ "admin.email.smtpServerExample": "Ex: \"smtp.yourcompany.com\", \"email-smtp.us-east-1.amazonaws.com\"",
+ "admin.email.smtpPortExample": "Ex: \"25\", \"465\"",
+ "admin.email.connectionSecurityNone": "None",
+ "admin.email.connectionSecurityTls": "TLS (Recommended)",
+ "admin.email.connectionSecurityStart": "STARTTLS",
+ "admin.email.inviteSaltExample": "Ex \"bjlSR4QqkXFBr7TP4oDzlfZmcNuH9Yo\"",
+ "admin.email.passwordSaltExample": "Ex \"bjlSR4QqkXFBr7TP4oDzlfZmcNuH9Yo\"",
+ "admin.email.pushServerEx": "E.g.: \"https://push-test.mattermost.com\"",
+ "admin.email.testing": "Testing...",
+ "admin.email.saving": "Saving Config...",
+ "admin.email.emailSuccess": "No errors were reported while sending an email. Please check your inbox to make sure.",
+ "admin.email.emailFail": "Connection unsuccessful: {error}",
+ "admin.email.emailSettings": "Email Settings",
+ "admin.email.allowSignupTitle": "Allow Sign Up With Email: ",
+ "admin.email.true": "true",
+ "admin.email.false": "false",
+ "admin.email.allowSignupDescription": "When true, Mattermost allows team creation and account signup using email and password. This value should be false only when you want to limit signup to a single-sign-on service like OAuth or LDAP.",
+ "admin.email.notificationsTitle": "Send Email Notifications: ",
+ "admin.email.notificationsDescription": "Typically set to true in production. When true, Mattermost attempts to send email notifications. Developers may set this field to false to skip email setup for faster development.<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: ",
+ "admin.email.requireVerificationDescription": "Typically set to true in production. When true, Mattermost requires email verification after account creation prior to allowing login. Developers may set this field to false so skip sending verification emails for faster development.",
+ "admin.email.notificationDisplayTitle": "Notification Display Name:",
+ "admin.email.notificationDisplayDescription": "Display name on email account used when sending notification emails from Mattermost.",
+ "admin.email.notificationEmailTitle": "Notification Email Address:",
+ "admin.email.notificationEmailDescription": "Email address displayed on email account used when sending notification emails from Mattermost.",
+ "admin.email.smtpUsernameTitle": "SMTP Username:",
+ "admin.email.smtpUsernameDescription": " Obtain this credential from administrator setting up your email server.",
+ "admin.email.smtpPasswordTitle": "SMTP Password:",
+ "admin.email.smtpPasswordDescription": " Obtain this credential from administrator setting up your email server.",
+ "admin.email.smtpServerTitle": "SMTP Server:",
+ "admin.email.smtpServerDescription": "Location of SMTP email server.",
+ "admin.email.smtpPortTitle": "SMTP Port:",
+ "admin.email.smtpPortDescription": "Port of SMTP email server.",
+ "admin.email.connectionSecurityTitle": "Connection Security:",
+ "admin.email.connectionSecurityNoneDescription": "Mattermost will send email over an unsecure connection.",
+ "admin.email.connectionSecurityTlsDescription": "Encrypts the communication between Mattermost and your email server.",
+ "admin.email.connectionSecurityStartDescription": "Takes an existing insecure connection and attempts to upgrade it to a secure connection using TLS.",
+ "admin.email.connectionSecurityTest": "Test Connection",
+ "admin.email.inviteSaltTitle": "Invite Salt:",
+ "admin.email.inviteSaltDescription": "32-character salt added to signing of email invites. Randomly generated on install. Click \"Re-Generate\" to create new salt.",
+ "admin.email.regenerate": "Re-Generate",
+ "admin.email.passwordSaltTitle": "Password Reset Salt:",
+ "admin.email.passwordSaltDescription": "32-character salt added to signing of password reset emails. Randomly generated on install. Click \"Re-Generate\" to create new salt.",
+ "admin.email.pushTitle": "Send Push Notifications: ",
+ "admin.email.pushDesc": "Typically set to true in production. When true, Mattermost attempts to send iOS and Android push notifications through the push notification server.",
+ "admin.email.pushServerTitle": "Push Notification Server:",
+ "admin.email.pushServerDesc": "Location of Mattermost push notification service you can set up behind your firewall using https://github.com/mattermost/push-proxy. For testing you can use https://push-test.mattermost.com, which connects to the sample Mattermost iOS app in the public Apple AppStore. Please do not use test service for production deployments.",
+ "admin.email.save": "Save",
+ "admin.gitlab.clientIdExample": "Ex \"jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY\"",
+ "admin.gitlab.clientSecretExample": "Ex \"jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY\"",
+ "admin.gitlab.authExample": "Ex \"\"",
+ "admin.gitlab.tokenExample": "Ex \"\"",
+ "admin.gitlab.userExample": "Ex \"\"",
+ "admin.gitlab.saving": "Saving Config...",
+ "admin.gitlab.settingsTitle": "GitLab Settings",
+ "admin.gitlab.enableTitle": "Enable Sign Up With GitLab: ",
+ "admin.gitlab.true": "true",
+ "admin.gitlab.false": "false",
+ "admin.gitlab.enableDescription": "When true, Mattermost allows team creation and account signup using GitLab OAuth.",
+ "admin.gitlab.EnableHtmlDesc": "<ol><li>Log in to your GitLab account and go to Applications -> Profile Settings.</li><li>Enter Redirect URIs \"<your-mattermost-url>/login/gitlab/complete\" (example: http://localhost:8065/login/gitlab/complete) and \"<your-mattermost-url>/signup/gitlab/complete\". </li><li>Then use \"Secret\" and \"Id\" fields from GitLab to complete the options below.</li><li>Complete the Endpoint URLs below. </li></ol>",
+ "admin.gitlab.clientIdTitle": "Id:",
+ "admin.gitlab.clientIdDescription": "Obtain this value via the instructions above for logging into GitLab",
+ "admin.gitlab.clientSecretTitle": "Secret:",
+ "admin.gitab.clientSecretDescription": "Obtain this value via the instructions above for logging into GitLab.",
+ "admin.gitlab.authTitle": "Auth Endpoint:",
+ "admin.gitlab.authDescription": "Enter https://<your-gitlab-url>/oauth/authorize (example https://example.com:3000/oauth/authorize). Make sure you use HTTP or HTTPS in your URL depending on your server configuration.",
+ "admin.gitlab.tokenTitle": "Token Endpoint:",
+ "admin.gitlab.tokenDescription": "Enter https://<your-gitlab-url>/oauth/token. Make sure you use HTTP or HTTPS in your URL depending on your server configuration.",
+ "admin.gitlab.userTitle": "User API Endpoint:",
+ "admin.gitlab.userDescription": "Enter https://<your-gitlab-url>/api/v3/user. Make sure you use HTTP or HTTPS in your URL depending on your server configuration.",
+ "admin.gitlab.save": "Save",
+ "admin.image.storeDisabled": "Disable File Storage",
+ "admin.image.storeLocal": "Local File System",
+ "admin.image.storeAmazonS3": "Amazon S3",
+ "admin.image.localExample": "Ex \"./data/\"",
+ "admin.image.amazonS3IdExample": "Ex \"AKIADTOVBGERKLCBV\"",
+ "admin.image.amazonS3SecretExample": "Ex \"jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY\"",
+ "admin.image.amazonS3BucketExample": "Ex \"mattermost-media\"",
+ "admin.image.amazonS3RegionExample": "Ex \"us-east-1\"",
+ "admin.image.thumbWidthExample": "Ex \"120\"",
+ "admin.image.thumbHeightExample": "Ex \"100\"",
+ "admin.image.previewWidthExample": "Ex \"1024\"",
+ "admin.image.previewHeightExample": "Ex \"0\"",
+ "admin.image.profileWidthExample": "Ex \"1024\"",
+ "admin.image.profileHeightExample": "Ex \"0\"",
+ "admin.image.publicLinkExample": "Ex \"gxHVDcKUyP2y1eiyW8S8na1UYQAfq6J6\"",
+ "admin.image.saving": "Saving Config...",
+ "admin.image.fileSettings": "File Settings",
+ "admin.image.storeTitle": "Store Files In:",
+ "admin.image.localTitle": "Local Directory Location:",
+ "admin.image.localDescription": "Directory to which image files are written. If blank, will be set to ./data/.",
+ "admin.image.amazonS3IdTitle": "Amazon S3 Access Key Id:",
+ "admin.image.amazonS3IdDescription": "Obtain this credential from your Amazon EC2 administrator.",
+ "admin.image.amazonS3SecretTitle": "Amazon S3 Secret Access Key:",
+ "admin.image.amazonS3SecretDescription": "Obtain this credential from your Amazon EC2 administrator.",
+ "admin.image.amazonS3BucketTitle": "Amazon S3 Bucket:",
+ "admin.image.amazonS3BucketDescription": "Name you selected for your S3 bucket in AWS.",
+ "admin.image.amazonS3RegionTitle": "Amazon S3 Region:",
+ "admin.image.amazonS3RegionDescription": "AWS region you selected for creating your S3 bucket.",
+ "admin.image.thumbWidthTitle": "Thumbnail Width:",
+ "admin.image.thumbWidthDescription": "Width of thumbnails generated from uploaded images. Updating this value changes how thumbnail images render in future, but does not change images created in the past.",
+ "admin.image.thumbHeightTitle": "Thumbnail Height:",
+ "admin.image.thumbHeightDescription": "Height of thumbnails generated from uploaded images. Updating this value changes how thumbnail images render in future, but does not change images created in the past.",
+ "admin.image.previewWidthTitle": "Preview Width:",
+ "admin.image.previewWidthDescription": "Maximum width of preview image. Updating this value changes how preview images render in future, but does not change images created in the past.",
+ "admin.image.previewHeightTitle": "Preview Height:",
+ "admin.image.previewHeightDescription": "Maximum height of preview image (\"0\": Sets to auto-size). Updating this value changes how preview images render in future, but does not change images created in the past.",
+ "admin.image.profileWidthTitle": "Profile Width:",
+ "admin.image.profileWidthDescription": "Width of profile picture.",
+ "admin.image.profileHeightTitle": "Profile Height:",
+ "admin.image.profileHeightDescription": "Height of profile picture.",
+ "admin.image.shareTitle": "Share Public File Link: ",
+ "admin.image.true": "true",
+ "admin.image.false": "false",
+ "admin.image.shareDescription": "Allow users to share public links to files and images.",
+ "admin.image.publicLinkTitle": "Public Link Salt:",
+ "admin.image.publicLinkDescription": "32-character salt added to signing of public image links. Randomly generated on install. Click \"Re-Generate\" to create new salt.",
+ "admin.image.regenerate": "Re-Generate",
+ "admin.image.save": "Save",
+ "admin.ldap.serverEx": "Ex \"10.0.0.23\"",
+ "admin.ldap.portEx": "Ex \"389\"",
+ "admin.ldap.baseEx": "Ex \"dc=mydomain,dc=com\"",
+ "admin.ldap.firstnameAttrEx": "Ex \"givenName\"",
+ "admin.ldap.lastnameAttrEx": "Ex \"sn\"",
+ "admin.ldap.emailAttrEx": "Ex \"mail\"",
+ "admin.ldap.usernameAttrEx": "Ex \"sAMAccountName\"",
+ "admin.ldap.idAttrEx": "Ex \"sAMAccountName\"",
+ "admin.ldap.queryEx": "Ex \"60\"",
+ "admin.ldap.saving": "Saving Config...",
+ "admin.ldap.bannerHeading": "Note:",
+ "admin.ldap.bannerDesc": "If a user attribute changes on the LDAP server it will be updated the next time the user enters their credentials to log in to Mattermost. This includes if a user is made inactive or removed from an LDAP server. Synchronization with LDAP servers is planned in a future release.",
+ "admin.ldap.noLicense": "<h4 className=\"banner__heading\">Note:</h4><p>LDAP is an enterprise feature. Your current license does not support LDAP. Click <a href=\"http://mattermost.com\"target=\"_blank\">here</a> for information and pricing on enterprise licenses.</p>",
+ "admin.ldap.title": "LDAP Settings",
+ "admin.ldap.enableTitle": "Enable Login With LDAP:",
+ "admin.ldap.true": "true",
+ "admin.ldap.false": "false",
+ "admin.ldap.enableDesc": "When true, Mattermost allows login using LDAP",
+ "admin.ldap.serverTitle": "LDAP Server:",
+ "admin.ldap.serverDesc": "The domain or IP address of LDAP server.",
+ "admin.ldap.portTitle": "LDAP Port:",
+ "admin.ldap.portDesc": "The port Mattermost will use to connect to the LDAP server. Default is 389.",
+ "admin.ldap.baseTitle": "BaseDN:",
+ "admin.ldap.baseDesc": "The Base DN is the Distinguished Name of the location where Mattermost should start its search for users in the LDAP tree.",
+ "admin.ldap.bindUserTitle": "Bind Username:",
+ "admin.ldap.bindUserDesc": "The username used to perform the LDAP search. This should typically be an account created specifically for use with Mattermost. It should have access limited to read the portion of the LDAP tree specified in the BaseDN field.",
+ "admin.ldap.bindPwdTitle": "Bind Password:",
+ "admin.ldap.bindPwdDesc": "Password of the user given in \"Bind Username\".",
+ "admin.ldap.firstnameAttrTitle": "First Name Attrubute",
+ "admin.ldap.firstnameAttrDesc": "The attribute in the LDAP server that will be used to populate the first name of users in Mattermost.",
+ "admin.ldap.lastnameAttrTitle": "Last Name Attribute:",
+ "admin.ldap.lastnameAttrDesc": "The attribute in the LDAP server that will be used to populate the last name of users in Mattermost.",
+ "admin.ldap.emailAttrTitle": "Email Attribute:",
+ "admin.ldap.emailAttrDesc": "The attribute in the LDAP server that will be used to populate the email addresses of users in Mattermost.",
+ "admin.ldap.usernameAttrTitle": "Username Attribute:",
+ "admin.ldap.uernameAttrDesc": "The attribute in the LDAP server that will be used to populate the username field in Mattermost. This may be the same as the ID Attribute.",
+ "admin.ldap.idAttrTitle": "Id Attribute: ",
+ "admin.ldap.idAttrDesc": "The attribute in the LDAP server that will be used as a unique identifier in Mattermost. It should be an LDAP attribute with a value that does not change, such as username or uid. If a user’s Id Attribute changes, it will create a new Mattermost account unassociated with their old one. This is the value used to log in to Mattermost in the \"LDAP Username\" field on the sign in page. Normally this attribute is the same as the “Username Attribute” field above. If your team typically uses domain\\\\username to sign in to other services with LDAP, you may choose to put domain\\\\username in this field to maintain consistency between sites.",
+ "admin.ldap.queryTitle": "Query Timeout (seconds):",
+ "admin.ldap.queryDesc": "The timeout value for queries to the LDAP server. Increase if you are getting timeout errors caused by a slow LDAP server.",
+ "admin.ldap.save": "Save",
+ "admin.support.saving": "Saving Config...",
+ "admin.support.title": "Legal and Support Settings",
+ "admin.support.termsTitle": "Terms of Service link:",
+ "admin.support.termsDesc": "Link to Terms of Service available to users on desktop and on mobile. Leaving this blank will hide the option to display a notice.",
+ "admin.support.privacyTitle": "Privacy Policy link:",
+ "admin.support.privacyDesc": "Link to Privacy Policy available to users on desktop and on mobile. Leaving this blank will hide the option to display a notice.",
+ "admin.support.aboutTitle": "About link:",
+ "admin.support.aboutDesc": "Link to About page for more information on your Mattermost deployment, for example its purpose and audience within your organization. Defaults to Mattermost information page.",
+ "admin.support.helpTitle": "Help link:",
+ "admin.support.helpDesc": "Link to help documentation from team site main menu. Typically not changed unless your organization chooses to create custom documentation.",
+ "admin.support.problemTitle": "Report a Problem link:",
+ "admin.support.problemDesc": "Link to help documentation from team site main menu. By default this points to the peer-to-peer troubleshooting forum where users can search for, find and request help with technical issues.",
+ "admin.support.emailTitle": "Support email:",
+ "admin.support.emailHelp": "Email shown during tutorial for end users to ask support questions.",
+ "admin.support.save": "Save",
+ "admin.license.removing": "Removing License...",
+ "admin.license.uploading": "Uploading License...",
+ "admin.license.enterpriseEdition": "Mattermost Enterprise Edition. Designed for enterprise-scale communication.",
+ "admin.license.entrepriseType": "<div><p>This compiled release of Mattermost platform is provided under a <a href=\"http://mattermost.com\" target=\"_blank\">commercial license</a>\n from Mattermost, Inc. based on your subscription level and is subject to the <a href=\"{terms}\" target=\"_blank\">Terms of Service.</a></p>\n <p>Your subscription details are as follows:</p>\n Name: {name}<br />\n Company or organization name: {company}<br/>\n Number of users: {users}<br/>\n License issued: {issued}<br/>\n Start date of license: {start}<br/>\n Expiry date of license: {expires}<br/>\n LDAP: {ldap}<br/></div>",
+ "admin.license.keyRemove": "Remove Enterprise License and Downgrade Server",
+ "admin.licence.keyMigration": "If you’re migrating servers you may need to remove your license key from this server in order to install it on a new server. To start,\n <a href=\"http://mattermost.com\" target=\"_blank\">disable all Enterprise Edition features on this server</a>.\n This will enable the ability to remove the license key and downgrade this server from Enterprise Edition to Team Edition.",
+ "admin.license.teamEdition": "Mattermost Team Edition. Designed for teams from 5 to 50 users.",
+ "admin.license.teamType": "<span><p>This compiled release of Mattermost platform is offered under an MIT license.</p>\n <p>See MIT-COMPILED-LICENSE.txt in your root install directory for details. See NOTICES.txt for information about open source software used in this system.</p></span>",
+ "admin.license.upload": "Upload",
+ "admin.license.uploadDesc": "Upload a license key for Mattermost Enterprise Edition to upgrade this server. <a href=\"http://mattermost.com\" target=\"_blank\">Visit us online</a>\n to learn more about the benefits of Enterprise Edition or to purchase a key.",
+ "admin.license.title": "Edition and License",
+ "admin.license.edition": "Edition: ",
+ "admin.license.type": "License: ",
+ "admin.license.key": "License Key: ",
+ "admin.log.locationPlaceholder": "Enter your file location",
+ "admin.log.formatPlaceholder": "Enter your file format",
+ "admin.log.saving": "Saving Config...",
+ "admin.log.logSettings": "Log Settings",
+ "admin.log.consoleTitle": "Log To The Console: ",
+ "admin.log.true": "true",
+ "admin.log.false": "false",
+ "admin.log.consoleDescription": "Typically set to false in production. Developers may set this field to true to output log messages to console based on the console level option. If true, server writes messages to the standard output stream (stdout).",
+ "admin.log.levelTitle": "Console Log Level:",
+ "admin.log.levelDescription": "This setting determines the level of detail at which log events are written to the console. ERROR: Outputs only error messages. INFO: Outputs error messages and information around startup and initialization. DEBUG: Prints high detail for developers working on debugging issues.",
+ "admin.log.fileTitle": "Log To File: ",
+ "admin.log.fileDescription": "Typically set to true in production. When true, log files are written to the log file specified in file location field below.",
+ "admin.log.fileLevelTitle": "File Log Level:",
+ "admin.log.fileLevelDescription": "This setting determines the level of detail at which log events are written to the log file. ERROR: Outputs only error messages. INFO: Outputs error messages and information around startup and initialization. DEBUG: Prints high detail for developers working on debugging issues.",
+ "admin.log.locationTitle": "File Location:",
+ "admin.log.locationDescription": "File to which log files are written. If blank, will be set to ./logs/mattermost, which writes logs to mattermost.log. Log rotation is enabled and every 10,000 lines of log information is written to new files stored in the same directory, for example mattermost.2015-09-23.001, mattermost.2015-09-23.002, and so forth.",
+ "admin.log.formatTitle": "File Format:",
+ "admin.log.formatDescription": "Format of log message output. If blank will be set to \"[%D %T] [%L] %M\", where:",
+ "admin.log.formatTime": "Time (15:04:05 MST)",
+ "admin.log.formatDateLong": "Date (2006/01/02)",
+ "admin.log.formatDateShort": "Date (01/02/06)",
+ "admin.log.formatLevel": "Level (DEBG, INFO, EROR)",
+ "admin.log.formatSource": "Source",
+ "admin.log.formatMessage": "Message",
+ "admin.log.save": "Save",
+ "admin.logs.title": "Server Logs",
+ "admin.logs.reload": "Reload",
+ "admin.privacy.saving": "Saving Config...",
+ "admin.privacy.title": "Privacy Settings",
+ "admin.privacy.showEmailTitle": "Show Email Address: ",
+ "admin.privacy.true": "true",
+ "admin.privacy.false": "false",
+ "admin.privacy.showEmailDescription": "When false, hides email address of users from other users in the user interface, including team owners and team administrators. Used when system is set up for managing teams where some users choose to keep their contact information private.",
+ "admin.privacy.showFullNameTitle": "Show Full Name: ",
+ "admin.privacy.showFullNameDescription": "When false, hides full name of users from other users, including team owners and team administrators. Username is shown in place of full name.",
+ "admin.privacy.save": "Save",
+ "admin.rate.queriesExample": "Ex \"10\"",
+ "admin.rate.memoryExample": "Ex \"10000\"",
+ "admin.rate.httpHeaderExample": "Ex \"X-Real-IP\", \"X-Forwarded-For\"",
+ "admin.rate.saving": "Saving Config...",
+ "admin.rate.noteTitle": "Note:",
+ "admin.rate.noteDescription": "Changing properties in this section will require a server restart before taking effect.",
+ "admin.rate.title": "Rate Limit Settings",
+ "admin.rate.enableLimiterTitle": "Enable Rate Limiter: ",
+ "admin.rate.true": "true",
+ "admin.rate.false": "false",
+ "admin.rate.enableLimiterDescription": "When true, APIs are throttled at rates specified below.",
+ "admin.rate.queriesTitle": "Number Of Queries Per Second:",
+ "admin.rate.queriesDescription": "Throttles API at this number of requests per second.",
+ "admin.rate.memoryTitle": "Memory Store Size:",
+ "admin.rate.memoryDescription": "Maximum number of users sessions connected to the system as determined by \"Vary By Remote Address\" and \"Vary By Header\" settings below.",
+ "admin.rate.remoteTitle": "Vary By Remote Address: ",
+ "admin.rate.remoteDescription": "When true, rate limit API access by IP address.",
+ "admin.rate.httpHeaderTitle": "Vary By HTTP Header:",
+ "admin.rate.httpHeaderDescription": "When filled in, vary rate limiting by HTTP header field specified (e.g. when configuring NGINX set to \"X-Real-IP\", when configuring AmazonELB set to \"X-Forwarded-For\").",
+ "admin.rate.save": "Save",
+ "admin.reset_password.submit": "Please enter at least {chars} characters.",
+ "admin.reset_password.title": "Reset Password",
+ "admin.reset_password.newPassword": "New Password",
+ "admin.reset_password.close": "Close",
+ "admin.reset_password.select": "Select",
+ "admin.select_team.selectTeam": "Select Team",
+ "admin.select_team.close": "Close",
+ "admin.select_team.select": "Select",
+ "admin.service.listenExample": "Ex \":8065\"",
+ "admin.service.attemptExample": "Ex \"10\"",
+ "admin.service.segmentExample": "Ex \"g3fgGOXJAQ43QV7rAh6iwQCkV4cA1Gs\"",
+ "admin.service.googleExample": "Ex \"7rAh6iwQCkV4cA1Gsg3fgGOXJAQ43QV\"",
+ "admin.service.sessionDaysEx": "Ex \"30\"",
+ "admin.service.saving": "Saving Config...",
+ "admin.service.title": "Service Settings",
+ "admin.service.listenAddress": "Listen Address:",
+ "admin.service.listenDescription": "The address to which to bind and listen. Entering \":8065\" will bind to all interfaces or you can choose one like \"127.0.0.1:8065\". Changing this will require a server restart before taking effect.",
+ "admin.service.attemptTitle": "Maximum Login Attempts:",
+ "admin.service.attemptDescription": "Login attempts allowed before user is locked out and required to reset password via email.",
+ "admin.service.segmentTitle": "Segment Developer Key:",
+ "admin.service.segmentDescription": "For users running a SaaS services, sign up for a key at Segment.com to track metrics.",
+ "admin.service.googleTitle": "Google Developer Key:",
+ "admin.service.googleDescription": "Set this key to enable embedding of YouTube video previews based on hyperlinks appearing in messages or comments. Instructions to obtain a key available at\n <a href=\"https://www.youtube.com/watch?v=Im69kzhpR3I\" target=\"_blank\">https://www.youtube.com/watch?v=Im69kzhpR3I</a>.\n Leaving the field blank disables the automatic generation of YouTube video previews from links.",
+ "admin.service.webhooksTitle": "Enable Incoming Webhooks: ",
+ "admin.service.true": "true",
+ "admin.service.false": "false",
+ "admin.service.webhooksDescription": "When true, incoming webhooks will be allowed. To help combat phishing attacks, all posts from webhooks will be labelled by a BOT tag.",
+ "admin.service.outWebhooksTitle": "Enable Outgoing Webhooks: ",
+ "admin.service.outWebhooksDesc": "When true, outgoing webhooks will be allowed.",
+ "admin.service.overrideTitle": "Enable Overriding Usernames from Webhooks: ",
+ "admin.service.overrideDescription": "When true, webhooks will be allowed to change the username they are posting as. Note, combined with allowing icon overriding, this could open users up to phishing attacks.",
+ "admin.service.iconTitle": "Enable Overriding Icon from Webhooks: ",
+ "admin.service.iconDescription": "When true, webhooks will be allowed to change the icon they post with. Note, combined with allowing username overriding, this could open users up to phishing attacks.",
+ "admin.service.testingTitle": "Enable Testing: ",
+ "admin.service.testingDescription": "(Developer Option) When true, /loadtest slash command is enabled to load test accounts and test data. Changing this will require a server restart before taking effect.",
+ "admin.service.developerTitle": "Enable Developer Mode: ",
+ "admin.service.developerDesc": "(Developer Option) When true, extra information around errors will be displayed in the UI.",
+ "admin.service.securityTitle": "Enable Security Alerts: ",
+ "admin.service.securityDesc": "When true, System Administrators are notified by email if a relevant security fix alert has been announced in the last 12 hours. Requires email to be enabled.",
+ "admin.service.webSessionDays": "Session Length for Web in Days:",
+ "admin.service.webSessionDaysDesc": "The web session will expire after the number of days specified and will require a user to login again.",
+ "admin.service.mobileSessionDays": "Session Length for Mobile Device in Days:",
+ "admin.service.mobileSessionDaysDesc": "The native mobile session will expire after the number of days specified and will require a user to login again.",
+ "admin.service.ssoSessionDays": "Session Length for SSO in Days:",
+ "admin.service.ssoSessionDaysDesc": "The SSO session will expire after the number of days specified and will require a user to login again.",
+ "admin.service.sessionCache": "Session Cache in Minutes:",
+ "admin.service.sessionCacheDesc": "The number of minutes to cache a session in memory.",
+ "admin.service.save": "Save",
+ "admin.sql.warning": "Warning: re-generating this salt may cause some columns in the database to return empty results.",
+ "admin.sql.maxConnectionsExample": "Ex \"10\"",
+ "admin.sql.maxOpenExample": "Ex \"10\"",
+ "admin.sql.keyExample": "Ex \"gxHVDcKUyP2y1eiyW8S8na1UYQAfq6J6\"",
+ "admin.sql.saving": "Saving Config...",
+ "admin.sql.noteTitle": "Note:",
+ "admin.sql.noteDescription": "Changing properties in this section will require a server restart before taking effect.",
+ "admin.sql.title": "SQL Settings",
+ "admin.sql.driverName": "Driver Name:",
+ "admin.sql.dataSource": "Data Source:",
+ "admin.sql.replicas": "Data Source Replicas:",
+ "admin.sql.maxConnectionsTitle": "Maximum Idle Connections:",
+ "admin.sql.maxConnectionsDescription": "Maximum number of idle connections held open to the database.",
+ "admin.sql.maxOpenTitle": "Maximum Open Connections:",
+ "admin.sql.maxOpenDescription": "Maximum number of open connections held open to the database.",
+ "admin.sql.keyTitle": "At Rest Encrypt Key:",
+ "admin.sql.keyDescription": "32-character salt available to encrypt and decrypt sensitive fields in database.",
+ "admin.sql.regenerate": "Re-Generate",
+ "admin.sql.traceTitle": "Trace: ",
+ "admin.sql.true": "true",
+ "admin.sql.false": "false",
+ "admin.sql.traceDescription": "(Development Mode) When true, executing SQL statements are written to the log.",
+ "admin.sql.save": "Save",
+ "admin.system_analytics.totalPosts": "Total Posts",
+ "admin.system_analytics.activeUsers": "Active Users With Posts",
+ "admin.system_analytics.title": "the System",
+ "admin.team_analytics.totalPosts": "Total Posts",
+ "admin.team_analytics.activeUsers": "Active Users With Posts",
+ "admin.team.siteNameExample": "Ex \"Mattermost\"",
+ "admin.team.maxUsersExample": "Ex \"25\"",
+ "admin.team.restrictExample": "Ex \"corp.mattermost.com, mattermost.org\"",
+ "admin.team.saving": "Saving Config...",
+ "admin.team.title": "Team Settings",
+ "admin.team.siteNameTitle": "Site Name:",
+ "admin.team.siteNameDescription": "Name of service shown in login screens and UI.",
+ "admin.team.maxUsersTitle": "Max Users Per Team:",
+ "admin.team.maxUsersDescription": "Maximum total number of users per team, including both active and inactive users.",
+ "admin.team.teamCreationTitle": "Enable Team Creation: ",
+ "admin.team.true": "true",
+ "admin.team.false": "false",
+ "admin.team.teamCreationDescription": "When false, the ability to create teams is disabled. The create team button displays error when pressed.",
+ "admin.team.userCreationTitle": "Enable User Creation: ",
+ "admin.team.userCreationDescription": "When false, the ability to create accounts is disabled. The create account button displays error when pressed.",
+ "admin.team.restrictTitle": "Restrict Creation To Domains:",
+ "admin.team.restrictDescription": "Teams and user accounts can only be created from a specific domain (e.g. \"mattermost.org\") or list of comma-separated domains (e.g. \"corp.mattermost.com, mattermost.org\").",
+ "admin.team.restrictNameTitle": "Restrict Team Names: ",
+ "admin.team.restrictNameDesc": "When true, You cannot create a team name with reserved words like www, admin, support, test, channel, etc",
+ "admin.team.dirTitle": "Enable Team Directory: ",
+ "admin.team.dirDesc": "When true, teams that are configured to show in team directory will show on main page inplace of creating a new team.",
+ "admin.team.save": "Save",
+ "admin.userList.title": "Users for {team}",
+ "admin.userList.title2": "Users for {team} ({count})",
+ "admin.user_item.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",
+ "admin.user_item.inactive": "Inactive",
+ "admin.user_item.makeSysAdmin": "Make System Admin",
+ "admin.user_item.makeTeamAdmin": "Make Team Admin",
+ "admin.user_item.makeMember": "Make Member",
+ "admin.user_item.makeActive": "Make Active",
+ "admin.user_item.makeInactive": "Make Inactive",
+ "admin.user_item.resetPwd": "Reset Password",
+ "authorize.title": "An application would like to connect to your {teamName} account",
+ "authorize.app": "The app <strong>{appName}</strong> would like the ability to access and modify your basic information.",
+ "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",
+ "claim.email_to_sso.title": "Switch Email/Password Account to {uiType}",
+ "claim.email_to_sso.ssoType": "Upon claiming your account, you will only be able to login with {type} SSO",
+ "claim.email_to_sso.enterPwd": "Enter the password for your {team} {site} account",
+ "claim.email_to_sso.switchTo": "Switch account to {uiType}",
+ "claim.sso_to_email.enterPwd": "Please enter a password.",
+ "claim.sso_to_email.pwdNotMatch": "Password do not match.",
+ "claim.sso_to_email.newPwd": "New Password",
+ "claim.sso_to_email.confirm": "Confirm Password",
+ "claim.sso_to_email.title": "Switch {type} Account to Email",
+ "claim.sso_to_email.description": "Upon changing your account type, you will only be able to login with your email and password.",
+ "claim.sso_to_email_newPwd": "Enter a new password for your {team} {site} account",
+ "claim.sso_to_email.switchTo": "Switch {type} to email and password",
+ "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",
+ "email_verify.notVerifiedBody": "Please verify your email address. Check your inbox for an email.",
+ "email_verify.resend": "Resend Email",
+ "email_verify.sent": " Verification email sent.",
+ "error_bar.preview_mode": "Preview Mode: Email notifications have not been configured",
+ "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",
+ "find_team.findDescription": "An email was sent with links to any teams to which you are a member.",
+ "find_team.getLinks": "Get an email with links to any teams to which you are a member.",
+ "find_team.email": "Email",
+ "find_team.send": "Send",
+ "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.",
+ "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",
+ "login_email.pwdReq": "A password is required",
+ "login_email.email": "Email",
+ "login_email.pwd": "Password",
+ "login_email.signin": "Sign in",
+ "login_ldap.badTeam": "Bad team name",
+ "login_ldap.idlReq": "An LDAP ID is required",
+ "login_ldap.pwdReq": "An LDAP password is required",
+ "login_ldap.username": "LDAP Username",
+ "login_ldap.pwd": "LDAP Password",
+ "login_ldap.signin": "Sign in",
+ "login.gitlab": "with GitLab",
+ "login.google": "with Google Apps",
+ "login.changed": " Sign-in method changed successfully",
+ "login.verified": " Email Verified",
+ "login.or": "or",
+ "login.forgot": "I forgot my password",
+ "login.noAccount": "Don't have an account? ",
+ "login.create": "Create one now",
+ "login.createTeam": "Create a new team",
+ "login.find": "Find your other teams",
+ "login.signTo": "Sign in to:",
+ "login.on": "on {siteName}",
+ "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",
+ "password_form.click": "Click <a href={url}>here</a> to log in.",
+ "password_form.title": "Password Reset",
+ "password_form.enter": "Enter a new password for your {teamDisplayName} {siteName} account.",
+ "password_form.change": "Change my password",
+ "password_send.error": "Please enter a valid email address.",
+ "password_send.link": "<p>A password reset link has been sent to <b>{email}</b> for your <b>{teamDisplayName}</b> team on {hostname}.</p>",
+ "password_send.checkInbox": "Please check your inbox.",
+ "password_send.email": "Email",
+ "password_send.title": "Password Reset",
+ "password_send.description": "To reset your password, enter the email address you used to sign up for {teamName}.",
+ "password_send.reset": "Reset my password",
+ "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",
+ "signup_team.noTeams": "There are no teams include in the Team Directory and team creation has been disabled.",
+ "signup_team.choose": "Choose a Team",
+ "signup_team.createTeam": "Or Create a Team",
+ "signup_team.disabled": "Team creation has been disabled. Please contact an administrator for access.",
+ "signup_team.none": "No team creation method has been enabled. Please contact an administrator for access.",
+ "signup_user_completed.required": "This field is required",
+ "signup_user_completed.validEmail": "Please enter a valid email address",
+ "signup_user_completed.reserved": "This username is reserved, please choose a new one.",
+ "signup_user_completed.usernameLength": "Username must begin with a letter, and contain between {min} to {max} lowercase characters made up of numbers, letters, and the symbols '.', '-' and '_'.",
+ "signup_user_completed.passwordLength": "Please enter at least {min} characters",
+ "signup_user_completed.expired": "You've already completed the signup process for this invitation or this invitation has expired.",
+ "signup_user_completed.userHelp": "Username must begin with a letter, and contain between {min} to {max} lowercase characters made up of numbers, letters, and the symbols '.', '-' and '_'",
+ "signup_user_completed.emailIs": "Your email address is <strong>{email}</strong>. You'll use this address to sign in to {siteName}.",
+ "signup_user_completed.whatis": "What's your email address?",
+ "signup_user_completed.gitlab": "with GitLab",
+ "signup_user_completed.google": "with Google",
+ "signup_user_completed.chooseUser": "Choose your username",
+ "signup_user_completed.choosePwd": "Choose your password",
+ "signup_user_completed.create": "Create Account",
+ "signup_user_completed.or": "or",
+ "signup_user_completed.welcome": "Welcome to:",
+ "signup_user_completed.onSite": "on {siteName}",
+ "signup_user_completed.lets": "Let's create your account",
+ "suggestion.mention.all": "Notifies everyone in the team",
+ "suggestion.mention.channel": "Notifies everyone in the channel",
+ "suggestion.search.public": "Public Channels",
+ "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",
+ "choose_auth_page.noSignup": "No sign-up methods configured, please contact your system administrator.",
+ "choose_auth_page.find": "Find my teams",
+ "team_signup_display_name.required": "This field is required",
+ "team_signup_display_name.charLength": "Name must be 4 or more characters up to a maximum of 15",
+ "team_signup_display_name.teamName": "Team Name",
+ "team_signup_display_name.nameHelp": "Name your team in any language. Your team name shows in menus and headings.",
+ "team_signup_display_name.next": "Next",
+ "team_signup_display_name.back": "Back to previous step",
+ "team_signup_email.validEmail": "Please enter a valid email address",
+ "team_signup_email.different": "Please use a different email than the one used at signup",
+ "team_signup_email.address": "Email Address",
+ "team_signup_password.passwordError": "Please enter at least {chars} characters",
+ "team_signup_password.creating": "Creating team...",
+ "team_signup_password.yourPassword": "Your password",
+ "team_signup_password.selectPassword": "Select a password that you'll use to login with your email address:",
+ "team_signup_password.email": "Email",
+ "team_signup_password.choosePwd": "Choose your password",
+ "team_signup_password.hint": "Passwords must contain {min} to {max} characters. Your password will be strongest if it contains a mix of symbols, numbers, and upper and lowercase characters.",
+ "team_signup_password.finish": "Finish",
+ "team_signup_password.agreement": "By proceeding to create your account and use {siteName}, you agree to our <a href='/static/help/terms.html'>Terms of Service</a> and <a href='/static/help/privacy.html'>Privacy Policy</a>. If you do not agree, you cannot use {siteName}.",
+ "team_signup_password.back": "Back to previous step",
+ "team_signup_send_invites.addInvitation": "Add Invitation",
+ "team_signup_send_invites.prefer": "if you prefer, you can invite team members later<br /> and ",
+ "team_signup_send_invites.skip": "skip this step ",
+ "team_signup_send_invites.forNow": "for now.",
+ "team_signup_send_invites.disabled": "Email is currently disabled for your team, and emails cannot be sent. Contact your system administrator to enable email and email invitations.",
+ "team_signup_send_invites.title": "Invite Team Members",
+ "team_signup_send_invites.next": "Next",
+ "team_signup_send_invites.back": "Back to previous step",
+ "team_signup_url.required": "This field is required",
+ "team_signup_url.regex": "Use only lower case letters, numbers and dashes. Must start with a letter and can't end in a dash.",
+ "team_signup_url.charLength": "Name must be 4 or more characters up to a maximum of 15",
+ "team_signup_url.taken": "URL is taken or contains a reserved word",
+ "team_signup_url.unavailable": "This URL is unavailable. Please try another.",
+ "team_signup_url.teamUrl": "Team URL",
+ "team_signup_url.webAddress": "Choose the web address of your new team:",
+ "team_signup_url.hint": "<li>Short and memorable is best</li>\n <li>Use lowercase letters, numbers and dashes</li>\n <li>Must start with a letter and can't end in a dash</li>",
+ "team_signup_url.next": "Next",
+ "team_signup_url.back": "Back to previous step",
+ "team_signup_username.reserved": "This username is reserved, please choose a new one.",
+ "team_signup_username.invalid": "Username must begin with a letter, and contain between {min} to {max} characters in total, which may be numbers, lowercase letters, or any of the symbols '.', '-', or '_'",
+ "team_signup_username.hint": "Usernames must begin with a letter and contain between {min} to {max} characters made up of lowercase letters, numbers, and the symbols '.', '-' and '_'",
+ "team_signup_username.username": "Your username",
+ "team_signup_username.memorable": "Select a memorable username that makes it easy for teammates to identify you:",
+ "team_signup_username.chooseUsername": "Choose your username",
+ "team_signup_username.next": "Next",
+ "team_signup_username.back": "Back to previous step",
+ "team_signup_welcome.storageError": "This service requires local storage to be enabled. Please enable it or exit private browsing.",
+ "team_signup_welcome.validEmailError": "Please enter a valid email address",
+ "team_signup_welcome.address": "Email Address",
+ "team_signup_welcome.welcome": "Welcome to:",
+ "team_signup_welcome.lets": "Let's set up your new team",
+ "team_signup_welcome.confirm": "Please confirm your email address:",
+ "team_signup_welcome.admin": "Your account will administer the new team site. <br />\n You can add other administrators later.",
+ "team_signup_welcome.yes": "Yes, this address is correct",
+ "team_signup_welcome.instead": "Use this instead",
+ "team_signup_welcome.different": "Use a different email",
+ "email_signup.emailError": "Please enter a valid email address",
+ "email_signup.address": "Email Address",
+ "email_signup.createTeam": "Create Team",
+ "email_signup.find": "Find my teams",
+ "sso_signup.team_error": "Please enter a team name",
+ "sso_signup.length_error": "Name must be 3 or more characters up to a maximum of 15",
+ "sso_signup.teamName": "Enter name of new team",
+ "sso_signup.gitlab": "Create team with GitLab Account",
+ "sso_signup.google": "Create team with Google Apps Account",
+ "sso_signup.find": "Find my teams",
+ "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",
+ "tutorial_intro.teamInvite": "Team Invite",
+ "tutorial_intro.support": "Need anything, just email us at ",
+ "tutorial_intro.allSet": "You’re all set",
+ "tutorial_intro.whenReady": " when you’re ready.",
+ "tutorial_intro.end": "Click “Next” to enter Town Square. This is the first channel teammates see when they sign up. Use it for posting updates everyone needs to know.",
+ "tutorial_intro.next": "Next",
+ "tutorial_intro.skip": "Skip tutorial",
+ "tutorial_tip.ok": "Okay",
+ "tutorial_tip.next": "Next",
+ "tutorial_tip.seen": "Seen this before? ",
+ "tutorial_tip.out": "Opt out of these tips.",
+ "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 b849489cb..cb3e8a199 100644
--- a/web/static/i18n/es.json
+++ b/web/static/i18n/es.json
@@ -1,4 +1,1048 @@
{
- "login.find_teams": "Find your other teams (spanish!)",
- "login.forgot_password": "I forgot my password (spanish!)"
+ "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.",
+ "admin.analytics.newlyCreated": "Nuevos Usuarios Creados",
+ "admin.analytics.privateGroups": "Grupos Privados",
+ "admin.analytics.publicChannels": "Canales Públicos",
+ "admin.analytics.recentActive": "Usuarios Recientemente Activos",
+ "admin.analytics.title": "Estadísticas para {title}",
+ "admin.analytics.totalPosts": "Total de Mensajes",
+ "admin.analytics.totalUsers": "Total de Usuarios",
+ "admin.email.allowSignupDescription": "Cuando está en verdadero, Mattermost permite la creación de equipos y cuentas utilizando el correo electrónico y contraseña. Este valor debe estar en falso sólo cuando quieres limitar el inicio de sesión a través de servicios tipo OAuth o LDAP.",
+ "admin.email.allowSignupTitle": "Permitir inicio de sesión con correo:",
+ "admin.email.connectionSecurityNone": "Ninguno",
+ "admin.email.connectionSecurityNoneDescription": "Mattermost enviará los correos electrónicos sobre conexiones no seguras.",
+ "admin.email.connectionSecurityStart": "STARTTLS",
+ "admin.email.connectionSecurityStartDescription": "Tomar la conexión insegura e intentar actualizarla hacia una conexión segura utilizando TLS.",
+ "admin.email.connectionSecurityTest": "Prueba de conexión",
+ "admin.email.connectionSecurityTitle": "Seguridad de conexión:",
+ "admin.email.connectionSecurityTls": "TLS (Recomendado)",
+ "admin.email.connectionSecurityTlsDescription": "Cifra la comunicación entre Mattermost y tu servidor de correo electrónico.",
+ "admin.email.emailFail": "Conexión fallida: {error}",
+ "admin.email.emailSettings": "Configuraciones de correo",
+ "admin.email.emailSuccess": "No fueron reportados errores mientras se enviada el correo. Favor validar en tu bandeja de entrada.",
+ "admin.email.false": "falso",
+ "admin.email.inviteSaltDescription": "32-caracter salt añadido a la firma de invitación de correos. Aleatoriamente generado en la instalación. Click \"Re-Generar\" para crear nuevo salt.",
+ "admin.email.inviteSaltExample": "Ej \"bjlSR4QqkXFBr7TP4oDzlfZmcNuH9Yo\"",
+ "admin.email.inviteSaltTitle": "Salt de las invitaciones:",
+ "admin.email.notificationDisplayDescription": "Muestra el nombre en la cuenta del email utilizada para enviar notificaciones por correo electrónico desde Mattermost.",
+ "admin.email.notificationDisplayExample": "Ej: \"Notificación de Mattermost\", \"Sistema\", \"No-Responder\"",
+ "admin.email.notificationDisplayTitle": "Notificación de nombre mostrado:",
+ "admin.email.notificationEmailDescription": "La dirección de correo electrónico a mostrar en la cuenta de correo utilizada cuando se envian notificaciones por correo electrónico desde Mattermost.",
+ "admin.email.notificationEmailExample": "Ej: \"mattermost@tuempresa.com\", \"admin@tuempresa.com\"",
+ "admin.email.notificationEmailTitle": "Notificación de correo electrónico:",
+ "admin.email.notificationsDescription": "Normalmente se asigna como verdadero en producción. Cuando es verdadero, Mattermost intenta enviar las notificaciones por correo electrónico. Los desarrolladores puede que quieran dejar esta opción en falso para saltarse la configuración de correos para desarrollar más rápido.\nAsignar está opción como verdadero remueve la notificación de Modo de Prueba (requiere cerrar la sesión y abrirla nuevamente para que los cambios surjan efecto).",
+ "admin.email.notificationsTitle": "Enviar notificaciones por correo electrónico: ",
+ "admin.email.passwordSaltDescription": "Un salt de 32-caracteres es añadido a la firma de correos para restablecer la contraseña. Aleatoriamente generado en la instalación. Pincha \"Regenerar\" para crear un nuevo salt.",
+ "admin.email.passwordSaltExample": "Ej \"bjlSR4QqkXFBr7TP4oDzlfZmcNuH9Yo\"",
+ "admin.email.passwordSaltTitle": "Resetear el Salt para las contraseñas:",
+ "admin.email.pushDesc": "Normalmente se asigna como verdadero en producción. Cuando está en verdadero, Mattermost intenta enviar notificaciones a dispositivos iOS y Android a través del servidor de notificaciones.",
+ "admin.email.pushServerDesc": "Ubicación del servicio de notificaciones push de Mattermost, puedes ubicarlo detras de un cortafuego utilizando https://github.com/mattermost/push-proxy. Para realizar pruebas puedes utilizar https://push-test.mattermost.com, el cual conecta con la ap de ejemplo de Mattermost en iOS publicada en el Apple AppStore. Por favor no utilices este servicio en producción.",
+ "admin.email.pushServerEx": "Ej.: \"https://push-test.mattermost.com\"",
+ "admin.email.pushServerTitle": "Servidor de Notificaciones:",
+ "admin.email.pushTitle": "Envío de Notificaciones: ",
+ "admin.email.regenerate": "Regenerar",
+ "admin.email.requireVerificationDescription": "Normalmente asignado como verdadero en producción. Cuando es verdadero, Mattermost requiere una verificación del correo electrónico después de crear la cuenta y antes de iniciar sesión por primera vez. Los desarrolladores pude que quieran dejar esta opción en falso para evitar la necesidad de verificar correos y así desarrollar más rápido.",
+ "admin.email.requireVerificationTitle": "Require verificación de correo electrónico: ",
+ "admin.email.save": "Guardar",
+ "admin.email.saving": "Guardando...",
+ "admin.email.smtpPasswordDescription": " Obten esta credencial del administrador del servidor de correos.",
+ "admin.email.smtpPasswordExample": "Ej: \"tucontraseña\", \"jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY\"",
+ "admin.email.smtpPasswordTitle": "Contraseña del SMTP:",
+ "admin.email.smtpPortDescription": "Puerto de servidor SMTP",
+ "admin.email.smtpPortExample": "Ej: \"25\", \"465\"",
+ "admin.email.smtpPortTitle": "Puerto del SMTP:",
+ "admin.email.smtpServerDescription": "Ubicación de SMTP.",
+ "admin.email.smtpServerExample": "Ej: \"smtp.tuempresa.com\", \"email-smtp.us-east-1.amazonaws.com\"",
+ "admin.email.smtpServerTitle": "Servidor SMTP:",
+ "admin.email.smtpUsernameDescription": " Obten esta credencial del administrado del servidor de correos.",
+ "admin.email.smtpUsernameExample": "Ej: \"admin@tuempresa.com\", \"AKIADTOVBGERKLCBV\"",
+ "admin.email.smtpUsernameTitle": "Usuario SMTP:",
+ "admin.email.testing": "Probando...",
+ "admin.email.true": "verdadero",
+ "admin.gitab.clientSecretDescription": "Utilizar este valor vía instrucciones suministradas anteriormente para iniciar sesión en GitLab.",
+ "admin.gitlab.EnableHtmlDesc": "<ol><li>Inicia sesión con tu cuenta en GitLab y dirigete a Applications -> Profile Settings.</li><li>Ingresa los URIs \"<tu-mattermost-url>/login/gitlab/complete\" (ejemplo: http://localhost:8065/login/gitlab/complete) y \"<tu-mattermost-url>/signup/gitlab/complete\". </li><li>Luego utiliza los valores de los campos \"Secret\" e \"Id\" de GitLab y completa las opciones que abajo se presentan.</li><li>Completa las dirección URLs abajo. </li></ol>",
+ "admin.gitlab.authDescription": "Ingresar <your-gitlab-url>/oauth/authorize (example http://localhost:3000/oauth/authorize). Asegurate que si utilizas HTTPS o HTTPS tus URLS sean correctas",
+ "admin.gitlab.authExample": "Ej \"\"",
+ "admin.gitlab.authTitle": "URL para autentificación:",
+ "admin.gitlab.clientIdDescription": "Utilizar este valor vía instrucciones suministradas anteriormente para iniciar sesión en GitLab.",
+ "admin.gitlab.clientIdExample": "Ej \"jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY\"",
+ "admin.gitlab.clientIdTitle": "Id:",
+ "admin.gitlab.clientSecretExample": "Ej \"jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY\"",
+ "admin.gitlab.clientSecretTitle": "Secreto:",
+ "admin.gitlab.enableDescription": "Cuando está asignado como verdadero, Mattermost permite la creación de equipos y cuentas utilizando el servicio de OAuth de GitLab. Para configurarlo, inicia sesión en GitLab con tu cuenta y dirigete a Applications -> Profile Settings. Ingresa los URIs de redireccionamiento \"<tu-mattermost-url>/login/gitlab/complete\" (ejemplo: http://localhost:8065/login/gitlab/complete) y \"<tu-mattermost-url>/signup/gitlab/complete\". Luego utiliza los campos de \"Secret\" y \"Id\" para completar las opciones de abajo.",
+ "admin.gitlab.enableTitle": "Enable Sign Up With GitLab: ",
+ "admin.gitlab.false": "falso",
+ "admin.gitlab.save": "Guardar",
+ "admin.gitlab.saving": "Guardando...",
+ "admin.gitlab.settingsTitle": "Configuración de GitLab",
+ "admin.gitlab.tokenDescription": "Ingresar <your-gitlab-url>/oauth/token. Asegurate que si utilizas HTTPS o HTTPS tus URLS sean correctas",
+ "admin.gitlab.tokenExample": "Ej \"\"",
+ "admin.gitlab.tokenTitle": "Url para obteción de Token:",
+ "admin.gitlab.true": "verdadero",
+ "admin.gitlab.userDescription": "Ingresar <tu-gitlab-url>/api/v3/user. Asegurate que si utilizas HTTPS o HTTPS tus URLS sean correctas",
+ "admin.gitlab.userExample": "Ej \"\"",
+ "admin.gitlab.userTitle": "URL para obtener datos de usuario:",
+ "admin.image.amazonS3BucketDescription": "Nombre que ha seleccionado para el bucket S3 en AWS.",
+ "admin.image.amazonS3BucketExample": "Ex \"mattermost-media\"",
+ "admin.image.amazonS3BucketTitle": "Amazon S3 Bucket:",
+ "admin.image.amazonS3IdDescription": "Obetener esta credencial del administrador de tu Amazon EC2",
+ "admin.image.amazonS3IdExample": "Ej \"AKIADTOVBGERKLCBV\"",
+ "admin.image.amazonS3IdTitle": "Llave de acceso Amazon S3:",
+ "admin.image.amazonS3RegionDescription": "Región que ha seleccionado en AWS para la creación de tu bucket S3.",
+ "admin.image.amazonS3RegionExample": "Ej \"us-east-1\"",
+ "admin.image.amazonS3RegionTitle": "Región de Amazon S3:",
+ "admin.image.amazonS3SecretDescription": "Obetener esta credencial del administrador de tu Amazon EC2.",
+ "admin.image.amazonS3SecretExample": "Ej \"jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY\"",
+ "admin.image.amazonS3SecretTitle": "Llave secreta de Amazon S3:",
+ "admin.image.false": "falso",
+ "admin.image.fileSettings": "Configuración de archivos",
+ "admin.image.localDescription": "Directorio al que se esriben los archivos de imágen. Si es vacio, se establecerá en ./data/.",
+ "admin.image.localExample": "Ej \"./dato/\"",
+ "admin.image.localTitle": "Directorio local de ubicación:",
+ "admin.image.previewHeightDescription": "Altura máxima para la vista previa de la imágen (\"0\": Establecer a tamaño-automático). Actualizando este valor los cambios de vista previa se hacen en el futuro, pero no cambia para imagenes creadas en el pasado.",
+ "admin.image.previewHeightExample": "Ej \"0\"",
+ "admin.image.previewHeightTitle": "Previsualzar alto:",
+ "admin.image.previewWidthDescription": "Ancho máximo para la vista previa de la imágen. Actualizando este valor los cambios de vista previa se hacen en el futuro, pero no cambia para imagenes creadas en el pasado.",
+ "admin.image.previewWidthExample": "Ej \"1024\"",
+ "admin.image.previewWidthTitle": "Previsualizar ancho:",
+ "admin.image.profileHeightDescription": "Alto de imagen de perfil.",
+ "admin.image.profileHeightExample": "Ej \"0\"",
+ "admin.image.profileHeightTitle": "Alto de perfil:",
+ "admin.image.profileWidthDescription": "Ancho de la imagen de perfil.",
+ "admin.image.profileWidthExample": "Ej \"1024\"",
+ "admin.image.profileWidthTitle": "Ancho de perfil:",
+ "admin.image.publicLinkDescription": "Salt de 32-characteres agregado para firmar los enlaces para las imagenes públicas. Aleatoriamente generados en la instalación. Pincha \"Regenerar\" para crear un nuevo salt.",
+ "admin.image.publicLinkExample": "Ej \"gxHVDcKUyP2y1eiyW8S8na1UYQAfq6J6\"",
+ "admin.image.publicLinkTitle": "Título del enlace público:",
+ "admin.image.regenerate": "Regenerar",
+ "admin.image.save": "Guardar",
+ "admin.image.saving": "Guardando...",
+ "admin.image.shareDescription": "Permitir a los usuarios compartir enlaces públicos para archivos e imágenes.",
+ "admin.image.shareTitle": "Compartir publicamente enlaces de archivos: ",
+ "admin.image.storeAmazonS3": "Amazon S3",
+ "admin.image.storeDisabled": "Deshabilitar almacenamiento de archivos",
+ "admin.image.storeLocal": "Sistema local de archivos",
+ "admin.image.storeTitle": "Almacenar archivos en:",
+ "admin.image.thumbHeightDescription": "Alto de imágen miniatura subida. Actualizando este valor la imagen miniatura cambia en el futuro, pero no cambia para imagenes creadas en el pasado.",
+ "admin.image.thumbHeightExample": "Ej \"100\"",
+ "admin.image.thumbHeightTitle": "Alto de imágen miniatura:",
+ "admin.image.thumbWidthDescription": "Ancho de imágen miniatura subida. Actualizando este valor la imagen miniatura cambia en el futuro, pero no cambia para imagenes creadas en el pasado.",
+ "admin.image.thumbWidthExample": "Ej \"120\"",
+ "admin.image.thumbWidthTitle": "Ancho de imágen miniatura:",
+ "admin.image.true": "verdadero",
+ "admin.ldap.bannerDesc": "Si el atributo de un usuario cambia en el servidor LDAP será actualizado la próxima vez que el usuario ingrese sus credenciales para iniciar sesión en Mattermost. Esto incluye si un usuario se inactiva o se remueve en el servidor LDAP. Sincronización con servidores LDAP está planificado para futuras versiones.",
+ "admin.ldap.bannerHeading": "Nota:",
+ "admin.ldap.baseDesc": "El DN Base es el Nombre Distinguido de la ubicación donde Mattermost debe comenzar a buscar a los usuarios en el árbol del LDAP.",
+ "admin.ldap.baseEx": "Ex \"dc=midominio,dc=com\"",
+ "admin.ldap.baseTitle": "DN Base:",
+ "admin.ldap.bindPwdDesc": "Contraseña del usuario asignado en \"Usuario de Enlace\".",
+ "admin.ldap.bindPwdTitle": "Contraseña de Enlace:",
+ "admin.ldap.bindUserDesc": "El usuario que realizará las busquedas LDAP. Normalmente este debería ser una cuenta específicamente creada para ser utilizada por Mattermost. Debería contat con acceso limitado para leer la porción del árbol LDAP especificada en el campo DN Base.",
+ "admin.ldap.bindUserTitle": "Usuario de Enlace:",
+ "admin.ldap.emailAttrDesc": "El atributo en el servidor LDAP que será utilizado para poblar la dirección de correo electrónico de los usuarios en Mattermost.",
+ "admin.ldap.emailAttrEx": "Ej \"mail\"",
+ "admin.ldap.emailAttrTitle": "Atributo de Correo Electrónico:",
+ "admin.ldap.enableDesc": "Cuando es verdadero, Mattermost permite realizar inicio de sesión utilizando LDAP",
+ "admin.ldap.enableTitle": "Habilitar inicio de sesión con LDAP:",
+ "admin.ldap.false": "falso",
+ "admin.ldap.firstnameAttrDesc": "El atributo en el servidor LDAP que será utilizado para poblar el nombre de los usuarios en Mattermost.",
+ "admin.ldap.firstnameAttrEx": "Ej \"givenName\"",
+ "admin.ldap.firstnameAttrTitle": "Atributo del Nombre",
+ "admin.ldap.idAttrDesc": "El atributo en el servidor LDAP que será utilizado para poblar el identificador único en Mattermost. Debe ser un atributo de LDAP que no cambie con el tiempo, como el nombre de usuario o el uid. Si el atributo del identificador cambia, se creará una nueva cuenta en Mattermost que no está asociada a la anterior. Este valor es utilizada para iniciar sesión en Mattermost en el campo \"Usuario LDAP\" en la página de inicio de sesión. Normalmente este atributo es el mismo que el campo “Atributo Usuario”. Si el equipo normalmente utiliza dominio\\\\usuario para iniciar sesión en otros servicios con LDAP, puedes elegir llenar este campo como dominio\\\\usuario para mantener consistencia entre los servicios.",
+ "admin.ldap.idAttrEx": "Ej \"sAMAccountName\"",
+ "admin.ldap.idAttrTitle": "Atributo Id: ",
+ "admin.ldap.lastnameAttrDesc": "El atributo en el servidor LDAP que será utilizado para poblar el apellido de los usuarios en Mattermost.",
+ "admin.ldap.lastnameAttrEx": "Ej \"sn\"",
+ "admin.ldap.lastnameAttrTitle": "Atributo Apellido:",
+ "admin.ldap.noLicense": "<h4 className=\"banner__heading\">Nota:</h4><p>LDAP es una característica de la edición enterprise. Tu licencia actual no soporta LDAP. Pincha <a href=\"http://mattermost.com\"target=\"_blank\">aquí</a> para información y precios de las licencias enterprise.</p>",
+ "admin.ldap.portDesc": "El puerto que Mattermost utilizará para conectarse al servidor LDAP. El predeterminado es 389.",
+ "admin.ldap.portEx": "Ej \"389\"",
+ "admin.ldap.portTitle": "Puerto LDAP:",
+ "admin.ldap.queryDesc": "El tiempo de espera para las consultas en el servidor LDAP. Aumenta este valor si estás obteniendo errores por falta de tiempo debido a un servidor de LDAP lento.",
+ "admin.ldap.queryEx": "Ej \"60\"",
+ "admin.ldap.queryTitle": "Tiempo de espera para las Consultas (segundos):",
+ "admin.ldap.save": "Guardar",
+ "admin.ldap.saving": "Guardando...",
+ "admin.ldap.serverDesc": "El dominio o dirección IP del servidor LDAP.",
+ "admin.ldap.serverEx": "Ej \"10.0.0.23\"",
+ "admin.ldap.serverTitle": "Servidor LDAP:",
+ "admin.ldap.title": "Configuración de LDAP",
+ "admin.ldap.true": "verdadero",
+ "admin.ldap.uernameAttrDesc": "El atributo en el servidor LDAP que se utilizará para poblar el nombre de usuario en Mattermost. Este puede ser igual al Attributo Id.",
+ "admin.ldap.usernameAttrEx": "Ej \"sAMAccountName\"",
+ "admin.ldap.usernameAttrTitle": "Atributo Usuario:",
+ "admin.licence.keyMigration": "Si estás migrando servidores es posible que necesites remover tu licencia de este servidor para poder instalarlo en un servidor nuevo. Para empezar,\n <a href=\"http://mattermost.com\" target=\"_blank\">deshabilita todas las características de la Edición Enterprise de este servidor</a>.\n Esta operación habilitará la opción para remover la licencia y degradar este servidor de la Edición Enterprise a la Edición Team.",
+ "admin.license.edition": "Edición: ",
+ "admin.license.enterpriseEdition": "Mattermost Edición Enterprise. Diseñada para comunicación de escala empresarial.",
+ "admin.license.entrepriseType": "<div><p>Esta versión compilada de la plataforma de Mattermost es proporcionada bajo una <a href=\"http://mattermost.com\" target=\"_blank\">licencia comercial</a>\n de Mattermost, Inc. basado en tu nivel de subscripción y sujeto a los <a href=\"{terms}\" target=\"_blank\">Términos del Servicio.</a></p>\n <p>Los detalles de tu subscripción son los siguientes:</p>\n Nombre: {name}<br />\n Nombre de compañia u organización: {company}<br/>\n Cantidad de usuarios: {users}<br/>\n Licencia emitida por: {issued}<br/>\n Inicio de la licencia: {start}<br/>\n Fecha de expiración: {expires}<br/>\n LDAP: {ldap}<br/></div>",
+ "admin.license.key": "Llave de la Licencia: ",
+ "admin.license.keyRemove": "Remover la Licencia Enterprise y Degradar el Servidor",
+ "admin.license.removing": "Removiendo Licencia...",
+ "admin.license.teamEdition": "Mattermost Edición Team. Diseñado para equipos desde 5 hasta 50 usuarios.",
+ "admin.license.teamType": "<span><p>Esta versión compilada de la plataforma de Mattermost es proporcionada bajo la licencia MIT.</p>\n <p>Lea MIT-COMPILED-LICENSE.txt en el directorio raíz de la instalación para más detalles. Lea NOTICES.txt para información sobre software libre utilizado en este sistema.</p></span>",
+ "admin.license.title": "Edición y Licencia",
+ "admin.license.type": "Licencia: ",
+ "admin.license.upload": "Subir",
+ "admin.license.uploadDesc": "Subir una llave de licencia de Mattermost Edición Enterprise para mejorar este servidor. <a href=\"http://mattermost.com\" target=\"_blank\">Visitanos en línea</a>\n para conocer más acerca de los beneficios de la Edición Enterprise or para comprar una licencia.",
+ "admin.license.uploading": "Subiendo Licencia...",
+ "admin.log.consoleDescription": "Normalmente asignado en falso en producción. Los desarolladores pueden configurar este campo en verdadero para ver de mensajes de consola basado en las opciones de nivel configuradas. Si es verdadera, el servidor escribirá los mensajes en una salida estandar (stdout).",
+ "admin.log.consoleTitle": "Mostrar registros en la consola: ",
+ "admin.log.false": "falso",
+ "admin.log.fileDescription": "Normalmente asignado en verdadero en producción. Cueando es verdadero, los archivos de registro son escritos en la ubicación especificada a continuación.",
+ "admin.log.fileLevelDescription": "Esta configuración determina el nivel de detalle con el cual los eventos serán escritos en el archivo de registro. ERROR: Sólo salida de mensajes de error. INFO: Salida de mensaje de error y información acerca de la partida e inicialización. DEBUG: Muestra un alto detalle para que los desarolladores que trabajan con eventos de depuración.",
+ "admin.log.fileLevelTitle": "Nivel registro:",
+ "admin.log.fileTitle": "Archivos de registro: ",
+ "admin.log.formatDateLong": "Fecha (2006/01/02)",
+ "admin.log.formatDateShort": "Fecha (01/02/06)",
+ "admin.log.formatDescription": "Formato del mensaje de registro de salida. Si es blanco se establecerá en \"[%D %T] [%L] %M\", donde:",
+ "admin.log.formatLevel": "Nivel (DEBG, INFO, EROR)",
+ "admin.log.formatMessage": "Mensaje",
+ "admin.log.formatPlaceholder": "Ingresar tu formato de archivo",
+ "admin.log.formatSource": "Fuente",
+ "admin.log.formatTime": "Hora (15:04:05 MST)",
+ "admin.log.formatTitle": "Formato de archivo:",
+ "admin.log.levelDescription": "Esta configuración determina el nivel de detalle con el cual los eventos serán escritos en la consola. ERROR: Sólo salida de mensajes de error. INFO: Salida de mensaje de error y información acerca de la partida e inicialización. DEBUG: Muestra un alto detalle para que los desarolladores que trabajan con eventos de depuración.",
+ "admin.log.levelTitle": "Nivel de log de consola:",
+ "admin.log.locationDescription": "Archivo en el cual se escribirán los registros. Si lo dejas en blanco, será asignado de forma predeterminada ./logs/mattermost, lo que escribirá los registros a mattermost.log. La rotación de los registros está habilitada y cada 10,000 lineas de registro se escriben en un nuevo archivo almacenado en el mismo directorio, por ejemplo mattermost.2015-09-23.001, mattermost.2015-09-23.002, y así sucesivamente.",
+ "admin.log.locationPlaceholder": "Ingresar locación de archivo",
+ "admin.log.locationTitle": "Ubicación de archivo:",
+ "admin.log.logSettings": "Configuración de registro",
+ "admin.log.save": "Guardar",
+ "admin.log.saving": "Guardando...",
+ "admin.log.true": "verdadero",
+ "admin.logs.reload": "Recargar",
+ "admin.logs.title": "Servidor de registros",
+ "admin.nav.help": "Ayuda",
+ "admin.nav.logout": "Cerrar sesión",
+ "admin.nav.report": "Reportar problema",
+ "admin.nav.switch": "Cambiar a {display_name}",
+ "admin.privacy.false": "falso",
+ "admin.privacy.save": "Guardar",
+ "admin.privacy.saving": "Guardando...",
+ "admin.privacy.showEmailDescription": "Cuando es falso, oculta la dirección de correo para otros usuarios en la interfaz de usuario, incluyendo a los dueños y administradores del grupo. Usado cuando el sistema es configurado para administrar grupos y donde algunos usuarios escogen mantener su información de contacto como privada.",
+ "admin.privacy.showEmailTitle": "Mostrar dirección de correo electrónico: ",
+ "admin.privacy.showFullNameDescription": "Cuando está asignado en falso, esconde el nombre completo de los usuarios para otros usuarios, incluyendo dueños de equipos y administradores de equipos. El nombre de usuario es mostrado en vez del nombre completo.",
+ "admin.privacy.showFullNameTitle": "Mostrar nombre completo: ",
+ "admin.privacy.title": "Configuraciones de privacidad",
+ "admin.privacy.true": "verdadero",
+ "admin.rate.enableLimiterDescription": "Cuando es verdadero, La APIs son reguladas a tasas especificadas a continuación.",
+ "admin.rate.enableLimiterTitle": "Habilitar el limitador de velocidad: ",
+ "admin.rate.false": "falso",
+ "admin.rate.httpHeaderDescription": "Al llenar este campo, se limita la velocidad según el encabezado HTTP especificado (e.j. cuando se configura con NGINX asigna \"X-Real-IP\", cuando se configura con AmazonELB asigna \"X-Forwarded-For\").",
+ "admin.rate.httpHeaderExample": "Ej \"X-Real-IP\", \"X-Forwarded-For\"",
+ "admin.rate.httpHeaderTitle": "Variar para encabezado HTTP:",
+ "admin.rate.memoryDescription": "Número máximo de sesiones de usuarios conectados en el sistema es determinado por \"Variar para dirección remota\" y \"Variar para encabezado HTTP\" en la configuración siguiente.",
+ "admin.rate.memoryExample": "Ej \"10000\"",
+ "admin.rate.memoryTitle": "Tamaño de memoria de almacenamiento:",
+ "admin.rate.noteDescription": "Cambiando propiedades en esta sección el servidor necesitará reiniciar antes de que los cambios tomen efecto.",
+ "admin.rate.noteTitle": "Nota:",
+ "admin.rate.queriesDescription": "Regulador de solicitudes API por sgundos.",
+ "admin.rate.queriesExample": "Ej \"10\"",
+ "admin.rate.queriesTitle": "Número de consultas por minuto:",
+ "admin.rate.remoteDescription": "Cuando es verdadero, límite de velocidad para el accedo a la API desde dirección IP.",
+ "admin.rate.remoteTitle": "Variar por direcciones remotas: ",
+ "admin.rate.save": "Guardar",
+ "admin.rate.saving": "Guardando...",
+ "admin.rate.title": "Configuración de velocidad",
+ "admin.rate.true": "verdadero",
+ "admin.reset_password.close": "Cerrar",
+ "admin.reset_password.newPassword": "Nueva contraseña",
+ "admin.reset_password.select": "Seleccionar",
+ "admin.reset_password.submit": "Por favor, introducir como mínimo 5 caracteres.",
+ "admin.reset_password.title": "Reiniciar Contraseña",
+ "admin.select_team.close": "Cerrar",
+ "admin.select_team.select": "Seleccionar",
+ "admin.select_team.selectTeam": "Seleccionar grupo",
+ "admin.service.attemptDescription": "Inicio de sesión permitidos antes que el usuario sea bloqueado y se requiera volver a configurar la contraseña vía correo electrónico.",
+ "admin.service.attemptExample": "Ej \"10\"",
+ "admin.service.attemptTitle": "Máximo de intentos de conexión:",
+ "admin.service.developerDesc": "(Opción de Desarrollador) Cuando está asignado en verdadero, información extra sobre errores se muestra en el UI.",
+ "admin.service.developerTitle": "Habilitar modo de Desarrollador: ",
+ "admin.service.false": "falso",
+ "admin.service.googleDescription": "Asigna una llave a este campo para habilitar la previsualización de videos de YouTube tomados de los enlaces que aparecen en los mensajes o comentarios. Las instrucciones de como obtener una llave está disponible en <a href=\"https://www.youtube.com/watch?v=Im69kzhpR3I\" target=\"_blank\">https://www.youtube.com/watch?v=Im69kzhpR3I</a>. Al dejar este campo en blanco deshabilita la generación de previsualizaciones de videos de YouTube desde los enlaces.",
+ "admin.service.googleExample": "Ej \"7rAh6iwQCkV4cA1Gsg3fgGOXJAQ43QV\"",
+ "admin.service.googleTitle": "Llave de desarrolador Google:",
+ "admin.service.iconDescription": "Cuando es verdadero, se le permitirá cambiar el icono del mensaje desde webhooks. Nota, en combinación con permitir el cambio de nombre de usuario, podría exponer a los usuarios a sufrir ataques de phishing.",
+ "admin.service.iconTitle": "Habilitar el cambio de icono desde los Webhooks: ",
+ "admin.service.listenAddress": "Dirección de escucha:",
+ "admin.service.listenDescription": "La dirección a la que se unirá y escuchará. Ingresar \":8065\" se podrá unir a todas las interfaces o podrá seleccionar una como ej: \"127.0.0.1:8065\". Cambiando este valor es necesario reiniciar el servidor.",
+ "admin.service.listenExample": "Ej \":8065\"",
+ "admin.service.mobileSessionDays": "Duración de la Sesión en Días para Dispositivos Moviles:",
+ "admin.service.mobileSessionDaysDesc": "La sesión nativa de los dispositivos moviles expirará luego de transcurrido el numero de días especificado y se solicitará al usuario que inicie sesión nuevamente.",
+ "admin.service.outWebhooksDesc": "Cuando es verdadero, los webhooks de salida serán permitidos.",
+ "admin.service.outWebhooksTitle": "Habilitar Webhooks de Salida: ",
+ "admin.service.overrideDescription": "Cuando es verdadero, se le permitirá cambiar el nombre de usuario desde webhooks. Nota, en conjunto con cambio de icono, podría exponer a los usuarios a sufrir ataques de phishing.",
+ "admin.service.overrideTitle": "Habilitar el cambio de nombres de usuario desde los Webhooks: ",
+ "admin.service.save": "Guardar",
+ "admin.service.saving": "Guardando....",
+ "admin.service.securityDesc": "Cuando es verdadero, Los Administradores del Sistema serán notificados por correo electrónico se han anunciado alertas de seguridad relevantes en las últimas 12 horas. Requiere que los correos estén habilitados.",
+ "admin.service.securityTitle": "Habilitar Alertas de Seguridad: ",
+ "admin.service.segmentDescription": "Para usuarios que corren en servicios SaaS, registarse en Segment.com para obtener su llave.",
+ "admin.service.segmentExample": "Ej \"g3fgGOXJAQ43QV7rAh6iwQCkV4cA1Gs\"",
+ "admin.service.segmentTitle": "Llave de segmento para desarollador:",
+ "admin.service.sessionCache": "Duración del Cache de la Sesión en Minutos:",
+ "admin.service.sessionCacheDesc": "La cantidad de minutes que el cache de la sesión se guardará en memoria.",
+ "admin.service.sessionDaysEx": "Ej \"30\"",
+ "admin.service.ssoSessionDays": "Duración de la Sesión en Días para SSO:",
+ "admin.service.ssoSessionDaysDesc": "Las sesión expirará lugo de transcurrido el numero de días especificado y se solicitará al usuario que inicie sesión nuevamente.",
+ "admin.service.testingDescription": "(Opción de desarollo) Cuando es verdadero, /pruebadecarga el comando slash es habilitado para cargar el nombre de la cuenta y probar la data. Cambiando esto será necesario reiniciar el servidor para que haga efecto.",
+ "admin.service.testingTitle": "Habilitar Pruebas: ",
+ "admin.service.title": "Configuracion de servicios",
+ "admin.service.true": "verdadero",
+ "admin.service.webSessionDays": "Duración de la Sesión en Días para Web:",
+ "admin.service.webSessionDaysDesc": "La sesión web expirará luego de transcurrido el número de días especificado y se solicitará al usuaio que inicie sesión nuevamente.",
+ "admin.service.webhooksDescription": "Cuando es verdadero, la entradas de webhooks será permitida. Para ayudar a combatir ataques phishing, todos los comentarios de webhooks serán marcados con una etiqueta BOT.",
+ "admin.service.webhooksTitle": "Habilitar Webhooks de Entrada: ",
+ "admin.sidebar.addTeamSidebar": "Agregar un equipo el menú lateral",
+ "admin.sidebar.email": "Configuración de correo",
+ "admin.sidebar.file": "Configuracion de archivos",
+ "admin.sidebar.gitlab": "Configuración de GitLab",
+ "admin.sidebar.ldap": "Configuración LDAP",
+ "admin.sidebar.license": "Edición y Licencia",
+ "admin.sidebar.loading": "Cargando",
+ "admin.sidebar.log": "Configuracion de log",
+ "admin.sidebar.logs": "Registros",
+ "admin.sidebar.other": "OTROS",
+ "admin.sidebar.privacy": "Configuración de privacidad",
+ "admin.sidebar.rate_limit": "Configuración de velocidad",
+ "admin.sidebar.reports": "REPORTES DEL SITIO",
+ "admin.sidebar.rmTeamSidebar": "Remover un equipo del menú lateral",
+ "admin.sidebar.service": "Configuración de servicio",
+ "admin.sidebar.settings": "CONFIGURACIONES",
+ "admin.sidebar.sql": "Configuración de SQL",
+ "admin.sidebar.statistics": "- Estadísticas",
+ "admin.sidebar.support": "Configuración de Soporte",
+ "admin.sidebar.team": "Configuración de equipo",
+ "admin.sidebar.teams": "EQUIPOS ({count})",
+ "admin.sidebar.users": "- Usuarios",
+ "admin.sidebar.view_statistics": "Ver Estadísticas",
+ "admin.sidebarHeader.systemConsole": "Consola de sistema",
+ "admin.sql.dataSource": "Origen de datos:",
+ "admin.sql.driverName": "Nombre de controlador:",
+ "admin.sql.false": "falso",
+ "admin.sql.keyDescription": "32-caracter disponible para encriptar y desincriptar campos sencible de la base de datos.",
+ "admin.sql.keyExample": "Ej \"gxHVDcKUyP2y1eiyW8S8na1UYQAfq6J6\"",
+ "admin.sql.keyTitle": "At Rest Encrypt Key:",
+ "admin.sql.maxConnectionsDescription": "Número máximo de conecciones inactivas que mantiene abierta la base de datos.",
+ "admin.sql.maxConnectionsExample": "Ej \"10\"",
+ "admin.sql.maxConnectionsTitle": "Maxímo de coneciones inactivas:",
+ "admin.sql.maxOpenDescription": "Número máximo de conexiones abiertas y retenidas en la base de datos.",
+ "admin.sql.maxOpenExample": "Ej \"10\"",
+ "admin.sql.maxOpenTitle": "Máximo de conexiones abiertas:",
+ "admin.sql.noteDescription": "Cambiando las propiedades de esta sección se requerirá reiniciar el servidor para que los cambios tomen efecto",
+ "admin.sql.noteTitle": "Nota:",
+ "admin.sql.regenerate": "Regenerar",
+ "admin.sql.replicas": "Origen de datos de réplica:",
+ "admin.sql.save": "Guardar",
+ "admin.sql.saving": "Guardando...",
+ "admin.sql.title": "Configuración de SQL",
+ "admin.sql.traceDescription": "(Modo desarrolador) Cuando es verdadero, la ejecución de sentencias SQL se escriben en el registro.",
+ "admin.sql.traceTitle": "Traza: ",
+ "admin.sql.true": "verdadero",
+ "admin.sql.warning": "Precaución: re-generando esto puede causar que algunas columnas de la base de datos retornen resultados vacíos.",
+ "admin.support.aboutDesc": "Enlace para la página de Acerca que contiene más información sobre Mattermost, por ejemplo el propósito y audiencia dentro de la organización. De forma predeterminada apunta a la página de información de Mattermost.",
+ "admin.support.aboutTitle": "Enlace de Acerca:",
+ "admin.support.emailHelp": "El correo electrónico mostrado durante el tutorial para que los usuarios puedan realizar preguntas de soporte.",
+ "admin.support.emailTitle": "Correo electrónico de Soporte:",
+ "admin.support.helpDesc": "Enlace con la documentación de ayuda para el equipo desde el menú principal. Normalmente no cambia a menos que tu organización decida crear una documentación personalizada.",
+ "admin.support.helpTitle": "Enlace de Ayuda:",
+ "admin.support.privacyDesc": "Enlace para las políticas de Privacidad disponible para los usuarios en versión de escritorio y movil. Al dejarlo en blanco esconderá la opción que muestra el aviso.",
+ "admin.support.privacyTitle": "Enlace de políticas de Privacidad:",
+ "admin.support.problemDesc": "Enlace con la documentación de ayuda para el equipo desde el menú principal. Como predeterminado esto apunta a un foro de ayuda donde los usuarios pueden buscar, encontrar y solicitar ayuda sobre temas técnicos.",
+ "admin.support.problemTitle": "Enlace de Reportar un Problema:",
+ "admin.support.save": "Guradar",
+ "admin.support.saving": "Guardando...",
+ "admin.support.termsDesc": "Enlace para los Terminos y Condiciones disponible para los usuarios en versión de escritorio y movil. Al dejarlo en blanco esconderá la opción que muestra el aviso.",
+ "admin.support.termsTitle": "Enlace de Terminos y Condiciones:",
+ "admin.support.title": "Configuración de Soporte",
+ "admin.system_analytics.activeUsers": "Usuarios Activos con Mensajes",
+ "admin.system_analytics.title": "el Sistema",
+ "admin.system_analytics.totalPosts": "Total de Mensajes",
+ "admin.team.dirDesc": "Cuando es verdadero, Los equipos que esten configurados para mostrarse en el directorio de equipos se mostrarán en vez de crear un nuevo equipo.",
+ "admin.team.dirTitle": "Habilitar Directorio de Equipos: ",
+ "admin.team.false": "falso",
+ "admin.team.maxUsersDescription": "Número máximo de usuarios por equipo, incluyendo usuarios activos e inactivos.",
+ "admin.team.maxUsersExample": "Ej \"25\"",
+ "admin.team.maxUsersTitle": "Máximo de usuarios por equipo:",
+ "admin.team.restrictDescription": "Equipos y las cuentas de usuario sólo pueden ser creadas para dominios especificos (ej. \"mattermost.org\") o una lista de dominios separado por comas (ej. \"corp.mattermost.com, mattermost.org\").",
+ "admin.team.restrictExample": "Ej \"corp.mattermost.com, mattermost.org\"",
+ "admin.team.restrictNameDesc": "Cuando es verdadero, No puedes crear equipos cuyo nombre tenga palabras reservadas como: www, admin, support, test, channel, etc",
+ "admin.team.restrictNameTitle": "Restringir Nombre de los Equipos: ",
+ "admin.team.restrictTitle": "Restringir la creación de dominios:",
+ "admin.team.save": "Guardar",
+ "admin.team.saving": "Guardando...",
+ "admin.team.siteNameDescription": "Nombre de servicios mostrados en pantalla login y UI.",
+ "admin.team.siteNameExample": "Ex \"Mattermost\"",
+ "admin.team.siteNameTitle": "Nombre de sitio:",
+ "admin.team.teamCreationDescription": "Cuando es falso, la posibilidad de crear equipos es deshabilitada. El botón crear equipo arrojará error cuando sea presionado.",
+ "admin.team.teamCreationTitle": "Habilitar Creación de Equipos: ",
+ "admin.team.title": "Configuración de equipo",
+ "admin.team.true": "Verdadero",
+ "admin.team.userCreationDescription": "Cuando es falso, a posibilidad de crear cuentas es deshabilitada. El botón crear cuentas arrojará error cuando sea presionado.",
+ "admin.team.userCreationTitle": "Habilitar Creación de Usuarios: ",
+ "admin.team_analytics.activeUsers": "Active Users With Posts",
+ "admin.team_analytics.totalPosts": "Total de Mensajes",
+ "admin.userList.title": "Usuarios para ",
+ "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": "Miembro",
+ "admin.user_item.resetPwd": "Reiniciar Contraseña",
+ "admin.user_item.sysAdmin": "Admin de Sistema",
+ "admin.user_item.teamAdmin": "Admin de Equipo",
+ "authorize.access": "¿Permitir acceso a {appName}?",
+ "authorize.allow": "Permitir",
+ "authorize.app": "La app {appName} quiere tener la abilidad de accesar y modificar tu información básica.",
+ "authorize.deny": "Denegar",
+ "authorize.title": "Una aplicación quiere conectarse con tu cuenta de {teamName}",
+ "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",
+ "choose_auth_page.googleCreate": "Crear un nuevo equipo con una cuenta de Google Apps",
+ "choose_auth_page.noSignup": "No hay métodos de inicio de sesión configurad, por favor contacte al administrador de sistemasos",
+ "claim.account.noEmail": "No se especifico un correo electrónico.",
+ "claim.email_to_sso.enterPwd": "Ingresa la contraseña para tu cuenta para {team} {site}",
+ "claim.email_to_sso.pwd": "Contraseña",
+ "claim.email_to_sso.pwdError": "Por favor introduce tu contraseña.",
+ "claim.email_to_sso.ssoType": "Al reclamar tu cuenta, sólo podrás iniciar sesión con {type} SSO",
+ "claim.email_to_sso.switchTo": "Cambiar cuenta a ",
+ "claim.email_to_sso.title": "Cambiar Cuenta de Correo/Contraseña a ",
+ "claim.sso_to_email.confirm": "Confirmar Contraseña",
+ "claim.sso_to_email.description": "Al cambiar el tipo de cuenta, sólo podrás iniciar sesión con tu correo electrónico y contraseña.",
+ "claim.sso_to_email.enterPwd": "Por favor ingresa una contraseña.",
+ "claim.sso_to_email.newPwd": "Nueva Contraseña",
+ "claim.sso_to_email.pwdNotMatch": "Las contraseñas no coinciden.",
+ "claim.sso_to_email.switchTo": "Cambiar {type} a correo electrónico y contraseña",
+ "claim.sso_to_email.title": "Cambiar la cuenta de {type} a Correo Electrónico",
+ "claim.sso_to_email_newPwd": "Ingresa una nueva contraseña para tu cuenta de {team} {site}",
+ "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",
+ "email_signup.find": "Encontrar mi equipo",
+ "email_verify.almost": "{siteName}: Ya casi estás listo",
+ "email_verify.notVerifiedBody": "Por favor verifica tu correo electrónico. Revisa tu bandeja de entrada, hemos enviado un correo de verificación.",
+ "email_verify.resend": "Reenviar Correo",
+ "email_verify.sent": " Correo de verificación enviado.",
+ "email_verify.verified": "{siteName} Correo electrónico verificado",
+ "email_verify.verifiedBody": "<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",
+ "find_team.getLinks": "Obtén un correo electrónico con los equipos a los que perteneces.",
+ "find_team.placeholder": "tu@ejemplo.com",
+ "find_team.send": "Enviar",
+ "find_team.submitError": "Por favor ingresa una dirección válida",
+ "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",
+ "login.createTeam": "Crear un nuevo equipo",
+ "login.find": "Encuentra tus otros equipos",
+ "login.forgot": "Olvide mi contraseña",
+ "login.gitlab": "con GitLab",
+ "login.google": "con Google Apps",
+ "login.noAccount": "¿No tienes una cuenta? ",
+ "login.on": "en {siteName}",
+ "login.or": "o",
+ "login.signTo": "Ingresar a:",
+ "login.verified": " Correo electrónico Verificado",
+ "login_email.badTeam": "Nombre de Equipo inválido",
+ "login_email.email": "Correo electrónico",
+ "login_email.emailReq": "El correo electrónico es obligatorio",
+ "login_email.pwd": "Contraseña",
+ "login_email.pwdReq": "La contraseña es obligatoria",
+ "login_email.signin": "Entrar",
+ "login_ldap.badTeam": "Nombre de Equipo inválido",
+ "login_ldap.idlReq": "El ID de LDAP es obligatorio",
+ "login_ldap.pwd": "Contraseña LDAP",
+ "login_ldap.pwdReq": "La contraseña LDAP es obligatoria",
+ "login_ldap.signin": "Entrar",
+ "login_ldap.username": "Usuario LDAP",
+ "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}.",
+ "password_form.error": "Por favor ingresa al menos {chars} caracteres.",
+ "password_form.pwd": "Contraseña",
+ "password_form.title": "Restablecer Contraseña",
+ "password_form.update": "Tu contraseña ha sido actualizada satisfactoriamente.",
+ "password_send.checkInbox": "Por favor revisa tu bandeja de entrada.",
+ "password_send.description": "Para restablecer tu contraseña, ingresa la dirección de correo electrónico que utilizaste para registrarte en {teamName}.",
+ "password_send.email": "Correo electrónico",
+ "password_send.error": "Por favor ingresa una dirección correo electrónico válida.",
+ "password_send.link": "<p>Se ha enviado un enlace para restablecer la contraseña a <b>{email}</b> para tu equipo <b>{teamDisplayName}</b> en {hostname}.</p>",
+ "password_send.reset": "Restablecer mi contraseña",
+ "password_send.title": "Restablecer Contraseña",
+ "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.",
+ "signup_team.noTeams": "No hay equipos en el Directorio de Equipos y la creación de equipos ha sido deshabilitada.",
+ "signup_team.none": "No se ha habilitado ningún método para creación de equipos. Por favor contacta a un administrador de sistema para solicitar acceso.",
+ "signup_team_complete.completed": "Ya haz completado el proceso de entrada para esta invitación, o esta invitación ya ha expirado.",
+ "signup_team_confirm.checkEmail": "Por favor revisa tu correo electrónico: <strong>{email}</strong><br />Se ha enviado un correo con un enlace para configurar tu equipo",
+ "signup_team_confirm.title": "Registro Completado",
+ "signup_user_completed.choosePwd": "Escoge tu contraseña",
+ "signup_user_completed.chooseUser": "Escoge tu nombre de usuario",
+ "signup_user_completed.create": "Crea una Cuenta",
+ "signup_user_completed.emailIs": "Tu dirección de correo electrónico es <strong>{email}</strong>. Utiliza está dirección para ingresar a {siteName}.",
+ "signup_user_completed.expired": "Ya haz completado el proceso de registro para esta invitación, o esta invitación ya ha expirado.",
+ "signup_user_completed.gitlab": "con GitLab",
+ "signup_user_completed.google": "con Google",
+ "signup_user_completed.lets": "Vamos a crear tu cuenta",
+ "signup_user_completed.onSite": "en {siteName}",
+ "signup_user_completed.or": "o",
+ "signup_user_completed.passwordLength": "Por favor ingresa al menos {min} caracteres",
+ "signup_user_completed.required": "Este campo es obligatorio",
+ "signup_user_completed.reserved": "Este nombre de usuario está reservado, por favor escoge uno nuevo.",
+ "signup_user_completed.userHelp": "El nombre de usuario debe empezar con una letra, y contener entre {min} a {max} caracteres en minúscula con números, letras, y los símbolos '.', '-' y '_'.",
+ "signup_user_completed.usernameLength": "El nombre de usuario debe comenzar con una letra, y debe contener entre {min} y {max} caracteres en minúscula y confeccionado por numeros, letras y los simbolos '.', '-' and '_'.",
+ "signup_user_completed.validEmail": "Por favor ingresa una dirección de correo electrónico válida",
+ "signup_user_completed.welcome": "Bienvenido a:",
+ "signup_user_completed.whatis": "¿Cuál es tu dirección de correo electrónico?",
+ "sso_signup.find": "Encontrar mi equipo",
+ "sso_signup.gitlab": "Crea un equipo con una cuenta de GitLab",
+ "sso_signup.google": "Crea un equipo con una cuenta de Google Apps",
+ "sso_signup.length_error": "Name must be 3 or more characters up to a maximum of 15",
+ "sso_signup.teamName": "Ingresa el nombre del nuevo equipo",
+ "sso_signup.team_error": "Please enter a team name",
+ "suggestion.mention.all": "Notifica a todas las personas en el equipo",
+ "suggestion.mention.channel": "Notifica a todas las personas en el canal",
+ "suggestion.search.private": "Grupos Privados",
+ "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",
+ "team_signup_display_name.next": "Siguiente",
+ "team_signup_display_name.required": "Este campo es obligatorio",
+ "team_signup_display_name.teamName": "Nombre del Equipo",
+ "team_signup_email.address": "Dirección de correo electrónico",
+ "team_signup_email.different": "Please use a different email than the one used at signup",
+ "team_signup_email.validEmail": "Por favor ingresa una dirección de correo electrónico válida",
+ "team_signup_password.agreement": "Procediendo a crear tu cuenta y el uso de {siteName}, indicas que estás de acuerdo con nuestros <a href='/static/help/terms.html'>Términos de Servicio</a> y <a href='/static/help/privacy.html'>Políticas de Privacidad</a>. Si no estás de acuerdo, no debes utilizar {siteName}.",
+ "team_signup_password.back": "Volver al paso previo",
+ "team_signup_password.choosePwd": "Escoge tu contraseña",
+ "team_signup_password.creating": "Creando equipo...",
+ "team_signup_password.email": "Correo electrónico",
+ "team_signup_password.finish": "Finalizar",
+ "team_signup_password.hint": "Las contraseñas deben contener de {min} a {max} caracteres. Su contraseña será más fuerte si contiene una mezcla de símbolos, números y caracteres en mayúsculas y minúsculas.",
+ "team_signup_password.passwordError": "Por favor ingrese al menos {chars} caracteres",
+ "team_signup_password.selectPassword": "Selecciona la contraseña que estás usando con tu dirección de correos:",
+ "team_signup_password.yourPassword": "Tu contraseña",
+ "team_signup_send_invites.addInvitation": "Agrega una Invitación",
+ "team_signup_send_invites.back": "Volver al paso previo",
+ "team_signup_send_invites.disabled": "Este correo electrónico está actualmente deshabilitado para tu equipo, y los correos no podrán ser enviados. Contacta a tu administrador de sistemas",
+ "team_signup_send_invites.forNow": "por ahora.",
+ "team_signup_send_invites.next": "Siguiente",
+ "team_signup_send_invites.prefer": "Si prefieres, puedes invitar a miembros de equipo más tarde <br /> y ",
+ "team_signup_send_invites.skip": "saltarte este paso ",
+ "team_signup_send_invites.title": "Invita Miembros al Equipo",
+ "team_signup_url.back": "Volver al paso previo",
+ "team_signup_url.charLength": "El nombre debe tener 4 o más caracteres hasta un máximo de 15",
+ "team_signup_url.hint": "<li>Corto y memorizable es mejor</li><li>Use letras en minúsculas, números y guiones</li><li>Debe empezar con una letra y no puede finalizar con un guión</li>",
+ "team_signup_url.next": "Siguiente",
+ "team_signup_url.regex": "Sólo utiliza letras en minúsculas, numeros y guiones. Debe comenzar con una letra y no puede terminar en un guión.",
+ "team_signup_url.required": "Este campo es obligatorio",
+ "team_signup_url.taken": "Este URL ya fue asignado o contiene una palabra reservada",
+ "team_signup_url.teamUrl": "URL de Equipo",
+ "team_signup_url.unavailable": "Este URL no está disponible. Por favor intenta con otro.",
+ "team_signup_url.webAddress": "Escoge la dirección web de tu nuevo equipo:",
+ "team_signup_username.back": "Volver al paso previo",
+ "team_signup_username.chooseUsername": "Escoge un nombre de usuario",
+ "team_signup_username.hint": "El nombre de usuario debe empezar con una letra, y contener entre {min} a {max} caracteres en minúscula con números, letras, y los símbolos '.', '-' y '_'.",
+ "team_signup_username.invalid": "El nombre de usuario debe comenzar con una letra, y tener entre {min} y {max} de caracteres en total, los cuales pueden ser numeros, letras en minúsculas, o cualquiera de los simbolos '.', '-', o '_'",
+ "team_signup_username.memorable": "Selecciona un nombre de usuario sencillo de recordar y que sea fácil para a tus compañeros de equipo identificarte:",
+ "team_signup_username.next": "Siguiente",
+ "team_signup_username.reserved": "Este nombre de usuario está reservado. Por favor escoge otro.",
+ "team_signup_username.username": "Tu nombre de usuario",
+ "team_signup_welcome.address": "Dirección de correo",
+ "team_signup_welcome.admin": "Tu cuenta administrará un nuevo sitio del equipo. <br />Puedes agregar otros administradores más adelante.",
+ "team_signup_welcome.confirm": "Por favor confirma tu dirección de correos:",
+ "team_signup_welcome.different": "Usa un correo diferente",
+ "team_signup_welcome.instead": "Usa este en vez de",
+ "team_signup_welcome.lets": "permítenos setear tu nuevo equipo",
+ "team_signup_welcome.storageError": "Este servicio requiere de almacenamiento local para ser habilitado. Por favor habilítalo o sale de la navegación privad.",
+ "team_signup_welcome.validEmailError": "Por favor ingresa una dirección de correo electrónico válida",
+ "team_signup_welcome.welcome": "Bienvenido a:",
+ "team_signup_welcome.yes": "Sí, esta dirección es correcta",
+ "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",
+ "tutorial_intro.next": "Siguiente",
+ "tutorial_intro.screenOne": "<h3>Bienvenido a:</h3> <h1>Mattermost</h1> <p>Las comunicaciones de tu equipo en un solo lugar, con búsquedas instantáneas y disponible desde donde sea.</p> <p>Mantén a tu equipo conectado para ayudarlos a conseguir lo que realmente importa.</p>",
+ "tutorial_intro.screenTwo": "<h3>Cómo funciona Mattermost:</h3> <p>Las comunicaciones ocurren en los canales de discusión los cuales son públicos, o en grupos privados e incluso con mensajes privados.</p> <p>Todo lo que ocurre es archivado y se puede buscar en cualquier momento desde cualquier dispositivo con acceso a Mattermost.</p>",
+ "tutorial_intro.skip": "Saltar el tutorial",
+ "tutorial_intro.support": "Necesitas algo, escribemos a ",
+ "tutorial_intro.teamInvite": "Invitar a tu Equipo",
+ "tutorial_intro.whenReady": " cuando estés listo(a).",
+ "tutorial_tip.next": "Siguiente",
+ "tutorial_tip.ok": "Aceptar",
+ "tutorial_tip.out": "No optar por estos consejos.",
+ "tutorial_tip.seen": "¿Haz visto esto antes? ",
+ "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/templates/authorize.html b/web/templates/authorize.html
index 430291676..0fa36b0ab 100644
--- a/web/templates/authorize.html
+++ b/web/templates/authorize.html
@@ -2,24 +2,10 @@
<html>
{{template "head" . }}
<body>
- <div class="container-fluid">
- <div class="oauth-prompt">
- <div class="prompt__heading">
- <div class="prompt__app-icon">
- <img src="/static/images/icon50x50.png" width="50" height="50" alt="">
- </div>
- <div class="text">An application would like to connect to your {{.Props.TeamName}} account.</div>
- </div>
- <p>The app <strong>{{.Props.AppName}}</strong> would like the ability to access Mattermost on your behalf.</p>
- <h2 class="prompt__allow">Allow <strong>{{.Props.AppName}}</strong> access?</h2>
- <div class="prompt__buttons">
- <input type="button" class="btn btn-link" value="Deny">
- <input type="button" class="btn btn-primary" value="Allow">
- </div>
- </div>
+ <div id="authorize">
</div>
<script>
- window.setup_authorize_page('{{.Props}}');
+ window.setup_authorize_page({{.Props}});
</script>
</body>
</html>
diff --git a/web/templates/channel.html b/web/templates/channel.html
index 8abbe36df..94d79a022 100644
--- a/web/templates/channel.html
+++ b/web/templates/channel.html
@@ -4,27 +4,9 @@
<html>
{{template "head" . }}
<body>
- <div id="error_bar"></div>
- <div id="channel_view" class="channel-view"></div>
- <div id="channel_loader"></div>
- <div id="post_mention_tab"></div>
- <div id="reply_mention_tab"></div>
- <div id="edit_mention_tab"></div>
- <div id="get_team_invite_link_modal"></div>
- <div id="import_theme_modal"></div>
- <div id="team_settings_modal"></div>
- <div id="invite_member_modal"></div>
- <div id="rename_channel_modal"></div>
- <div id="edit_post_modal"></div>
- <div id="delete_post_modal"></div>
- <div id="more_channels_modal"></div>
- <div id="post_deleted_modal"></div>
- <div id="channel_notifications_modal"></div>
- <div id="team_members_modal"></div>
- <div id="removed_from_channel_modal"></div>
- <div id="register_app_modal"></div>
+ <div id="channel_view" class='channel-view'></div>
<script>
-window.setup_channel_page({{ .Props }}, {{ .Team }}, {{ .Channel }}, {{ .User }});
+ window.setup_channel_page({{ .Props }}, {{ .Team }}, {{ .Channel }});
$('body').tooltip( {selector: '[data-toggle=tooltip]'} );
var modals = $('.modal-body').not('.edit-modal-body');
if($(window).height() > 1200){
diff --git a/web/templates/claim_account.html b/web/templates/claim_account.html
index 6c9f36fa7..bcf63fd95 100644
--- a/web/templates/claim_account.html
+++ b/web/templates/claim_account.html
@@ -5,8 +5,22 @@
<body class="white">
<div class="container-fluid">
<div class="inner__wrap">
- <div class="row content" id="claim"></div>
- </div>
+ <div class="row content">
+ <div class="signup-header">
+ <a href="/{{.Props.TeamName}}">{{.Props.TeamDisplayName}}</a>
+ </div>
+ <div class="col-sm-12">
+ <div class="signup-team__container">
+ <img class="signup-team-logo" src="/static/images/logo.png" />
+ <div id="claim"></div>
+ </div>
+ </div>
+ <div class="footer-push"></div>
+ </div>
+ <div class="row footer">
+ {{template "footer" . }}
+ </div>
+ <div>
</div>
<script>
window.setup_claim_account_page({{ .Props }});
diff --git a/web/templates/docs.html b/web/templates/docs.html
index 0e0f51648..1a20580fb 100644
--- a/web/templates/docs.html
+++ b/web/templates/docs.html
@@ -6,6 +6,9 @@
<div class="container-fluid">
<div class="inner__wrap">
<div class="row content">
+ <div class="signup-header">
+ <a href="/">{{ .ClientCfg.SiteName }}</a>
+ </div>
<div class="col-sm-12">
<div class="docs__page" id="docs"></div>
</div>
diff --git a/web/templates/find_team.html b/web/templates/find_team.html
index 9acf3ac64..15b8bf463 100644
--- a/web/templates/find_team.html
+++ b/web/templates/find_team.html
@@ -6,6 +6,9 @@
<div class="container-fluid">
<div class="inner__wrap">
<div class="row content">
+ <div class="signup-header">
+ <a href="/">{{ .ClientCfg.SiteName }}</a>
+ </div>
<div class="col-sm-12">
<div class="signup-team__container">
<img class="signup-team-logo" src="/static/images/logo.png" />
@@ -20,7 +23,7 @@
</div>
</div>
<script>
- window.setup_find_team_page();
+ window.setup_find_team_page({{ .Props }});
</script>
</body>
</html>
diff --git a/web/templates/footer.html b/web/templates/footer.html
index 60dd5a40e..5b11328fb 100644
--- a/web/templates/footer.html
+++ b/web/templates/footer.html
@@ -5,10 +5,10 @@
</div>
<div class="col-xs-12">
<span class="pull-right footer-link copyright">© 2015 Mattermost, Inc.</span>
- <a id="help_link" class="pull-right footer-link" href="#">Help</a>
- <a id="terms_link" class="pull-right footer-link" href="#">Terms</a>
- <a id="privacy_link" class="pull-right footer-link" href="#">Privacy</a>
- <a id="about_link" class="pull-right footer-link" href="#">About</a>
+ <a id="help_link" class="pull-right footer-link" href="#">{{ .ClientCfg.FooterHelp }}</a>
+ <a id="terms_link" class="pull-right footer-link" href="#">{{ .ClientCfg.FooterTerms }}</a>
+ <a id="privacy_link" class="pull-right footer-link" href="#">{{ .ClientCfg.FooterPrivacy }}</a>
+ <a id="about_link" class="pull-right footer-link" href="#">{{ .ClientCfg.FooterAbout }}</a>
</div>
</div>
<script>
diff --git a/web/templates/head.html b/web/templates/head.html
index 4060d2410..b1ec905b5 100644
--- a/web/templates/head.html
+++ b/web/templates/head.html
@@ -62,8 +62,11 @@
<style id="antiClickjack">body{display:none !important;}</style>
<script>
- ReactIntl.addLocaleData(ReactIntlLocaleData.en);
- ReactIntl.addLocaleData(ReactIntlLocaleData.es);
+ if ('ReactIntl' in window && 'ReactIntlLocaleData' in window) {
+ Object.keys(ReactIntlLocaleData).forEach(function(lang) {
+ ReactIntl.addLocaleData(ReactIntlLocaleData[lang]);
+ });
+ }
window.mm_config = {{ .ClientCfg }};
window.mm_license = {{ .ClientLicense }};
@@ -71,6 +74,13 @@
window.mm_user = {{ .User }};
window.mm_channel = {{ .Channel }};
window.mm_locale = {{ .Locale }};
+ window.mm_preferences = {{ .Preferences }};
+
+ $(function() {
+ if (window.mm_preferences != null) {
+ PreferenceStore.setPreferences(window.mm_preferences);
+ }
+ });
if ({{.SessionTokenIndex}} >= 0) {
window.mm_session_token_index = {{.SessionTokenIndex}};
@@ -78,13 +88,13 @@
headers: {
'X-MM-TokenIndex': mm_session_token_index,
'Accept-Language': mm_locale
- }
+ }
});
} else {
$.ajaxSetup({
headers: {
'Accept-Language': mm_locale
- }
+ }
});
}
@@ -108,7 +118,7 @@
}
console.log('detected login from a different tab');
- window.location.href = '/' + window.mm_team.name;
+ location.reload();
}
});
});
diff --git a/web/templates/login.html b/web/templates/login.html
index f6a551220..be5e6bf4f 100644
--- a/web/templates/login.html
+++ b/web/templates/login.html
@@ -7,7 +7,7 @@
<div class="inner__wrap">
<div class="row content">
<div class="signup-header">
- {{.Props.TeamDisplayName}}
+ <a href="/">{{ .ClientCfg.SiteName }}</a>
</div>
<div class="col-sm-12">
<div id="login"></div>
diff --git a/web/templates/password_reset.html b/web/templates/password_reset.html
index 7f6335c92..df82285ef 100644
--- a/web/templates/password_reset.html
+++ b/web/templates/password_reset.html
@@ -5,7 +5,21 @@
<body class="white">
<div class="container-fluid">
<div class="inner__wrap">
- <div class="row content" id="reset"></div>
+ <div class="row content">
+ <div class="signup-header">
+ <a href="/{{.Props.TeamName}}">{{.Props.TeamDisplayName}}</a>
+ </div>
+ <div class="col-sm-12">
+ <div class="signup-team__container">
+ <img class="signup-team-logo" src="/static/images/logo.png" />
+ <div id="reset"></div>
+ </div>
+ </div>
+ <div class="footer-push"></div>
+ </div>
+ <div class="row footer">
+ {{template "footer" . }}
+ </div>
</div>
</div>
<script>
diff --git a/web/templates/signup_team.html b/web/templates/signup_team.html
index 39fd3791b..afba58066 100644
--- a/web/templates/signup_team.html
+++ b/web/templates/signup_team.html
@@ -10,7 +10,7 @@
<div class="signup-team__container">
<img class="signup-team-logo" src="/static/images/logo.png" />
<h1>{{ .ClientCfg.SiteName }}</h1>
- <h4 class="color--light">All team communication in one place, searchable and accessible anywhere</h4>
+ <h4 class="color--light">{{.Props.Info}}</h4>
<div id="signup-team"></div>
</div>
</div>
diff --git a/web/templates/signup_team_confirm.html b/web/templates/signup_team_confirm.html
index 2d27194bc..d16861808 100644
--- a/web/templates/signup_team_confirm.html
+++ b/web/templates/signup_team_confirm.html
@@ -7,11 +7,7 @@
<div class="inner__wrap">
<div class="row content">
<div class="col-sm-12">
- <div class="signup-team__container">
- <h3>Sign up Complete</h3>
- <p>Please check your email: {{ .Props.Email }}<br>
- Your email contains a link to set up your team</p>
- </div>
+ <div id="signup-team-confirm"></div>
</div>
</div>
<div class="row footer">
@@ -19,6 +15,9 @@
</div>
</div>
</div>
+ <script>
+window.setup_signup_team_confirm_page({{ .Props }});
+ </script>
</body>
</html>
{{end}}
diff --git a/web/templates/verify.html b/web/templates/verify.html
index a49ba7930..bab329c7d 100644
--- a/web/templates/verify.html
+++ b/web/templates/verify.html
@@ -6,7 +6,16 @@
<div class="container-fluid">
<div class="inner__wrap">
<div class="row content">
- <div id="verify"></div>
+ <div class="signup-header">
+ <a href="/{{.Props.TeamName}}">{{.Props.TeamDisplayName}}</a>
+ </div>
+ <div class="col-sm-12">
+ <div class="signup-team__container">
+ <img class="signup-team-logo" src="/static/images/logo.png" />
+ <div id="verify"></div>
+ </div>
+ </div>
+ <div class="footer-push"></div>
</div>
<div class="row footer">
{{template "footer" . }}
diff --git a/web/templates/welcome.html b/web/templates/welcome.html
deleted file mode 100644
index 15c072226..000000000
--- a/web/templates/welcome.html
+++ /dev/null
@@ -1,37 +0,0 @@
-{{define "welcome"}}
-<!DOCTYPE html>
-<html>
-{{template "head" . }}
-<body>
- <div class="container-fluid">
- <div class="inner__wrap">
- <div class="row header">
- <div id="navbar"></div>
- </div>
- <div class="row main">
- <div class="app__content">
- <div class="welcome-info">
- <h1>Welcome to {{ .ClientCfg.SiteName }}!</h1>
- <p>
- You do not appear to be part of any teams. Please contact your
- administrator to have him send you an invitation to a private team.
- Or you can start a new private team.
- </p>
- <div class="alert alert-warning">
- If you where invited to a team that you do not see you must
- confirm your email address first before gaining access to the
- team.
- </div>
- <div id="new_channel">
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- <script>
- window.setup_welcome_page();
- </script>
-</body>
-</html>
-{{end}}
diff --git a/web/web.go b/web/web.go
index 48755f94f..efe0e6b13 100644
--- a/web/web.go
+++ b/web/web.go
@@ -54,13 +54,18 @@ func (me *HtmlTemplatePage) Render(c *api.Context, w http.ResponseWriter) {
me.Props["Locale"] = me.Locale
me.SessionTokenIndex = c.SessionTokenIndex
+ me.ClientCfg["FooterHelp"] = c.T("web.footer.help")
+ me.ClientCfg["FooterTerms"] = c.T("web.footer.terms")
+ me.ClientCfg["FooterPrivacy"] = c.T("web.footer.privacy")
+ me.ClientCfg["FooterAbout"] = c.T("web.footer.about")
+
if err := Templates.ExecuteTemplate(w, me.TemplateName, me); err != nil {
c.SetUnknownError(me.TemplateName, err.Error())
}
}
func InitWeb() {
- l4g.Debug("Initializing web routes")
+ l4g.Debug(utils.T("web.init.debug"))
mainrouter := api.Srv.Router
@@ -112,15 +117,15 @@ func InitWeb() {
func watchAndParseTemplates() {
templatesDir := utils.FindDir("web/templates")
- l4g.Debug("Parsing templates at %v", templatesDir)
+ l4g.Debug(utils.T("web.parsing_templates.debug"), templatesDir)
var err error
if Templates, err = template.ParseGlob(templatesDir + "*.html"); err != nil {
- l4g.Error("Failed to parse templates %v", err)
+ l4g.Error(utils.T("web.parsing_templates.error"), err)
}
watcher, err := fsnotify.NewWatcher()
if err != nil {
- l4g.Error("Failed to create directory watcher %v", err)
+ l4g.Error(utils.T("web.create_dir.error"), err)
}
go func() {
@@ -128,20 +133,20 @@ func watchAndParseTemplates() {
select {
case event := <-watcher.Events:
if event.Op&fsnotify.Write == fsnotify.Write {
- l4g.Info("Re-parsing templates because of modified file %v", event.Name)
+ l4g.Info(utils.T("web.reparse_templates.info"), event.Name)
if Templates, err = template.ParseGlob(templatesDir + "*.html"); err != nil {
- l4g.Error("Failed to parse templates %v", err)
+ l4g.Error(utils.T("web.parsing_templates.error"), err)
}
}
case err := <-watcher.Errors:
- l4g.Error("Failed in directory watcher %v", err)
+ l4g.Error(utils.T("web.dir_fail.error"), err)
}
}
}()
err = watcher.Add(templatesDir)
if err != nil {
- l4g.Error("Failed to add directory to watcher %v", err)
+ l4g.Error(utils.T("web.watcher_fail.error"), err)
}
}
@@ -156,7 +161,7 @@ func CheckBrowserCompatability(c *api.Context, r *http.Request) bool {
version := strings.Split(browser, "/")
if strings.HasPrefix(bname, version[0]) && strings.HasPrefix(bversion, version[1]) {
- c.Err = model.NewAppError("CheckBrowserCompatability", "Your current browser is not supported, please upgrade to one of the following browsers: Google Chrome 21 or higher, Internet Explorer 11 or higher, FireFox 14 or higher, Safari 9 or higher", "")
+ c.Err = model.NewLocAppError("CheckBrowserCompatability", "web.check_browser_compatibility.app_error", nil, "")
return false
}
}
@@ -172,7 +177,8 @@ func root(c *api.Context, w http.ResponseWriter, r *http.Request) {
}
if len(c.Session.UserId) == 0 {
- page := NewHtmlTemplatePage("signup_team", "Signup", c.Locale)
+ page := NewHtmlTemplatePage("signup_team", c.T("web.root.singup_title"), c.Locale)
+ page.Props["Info"] = c.T("web.root.singup_info")
if result := <-api.Srv.Store.Team().GetAllTeamListing(); result.Err != nil {
c.Err = result.Err
@@ -211,7 +217,7 @@ func root(c *api.Context, w http.ResponseWriter, r *http.Request) {
user = ur.Data.(*model.User)
}
- page := NewHtmlTemplatePage("home", "Home", c.Locale)
+ page := NewHtmlTemplatePage("home", c.T("web.root.home_title"), c.Locale)
page.Team = team
page.User = user
page.Render(c, w)
@@ -224,7 +230,7 @@ func signup(c *api.Context, w http.ResponseWriter, r *http.Request) {
return
}
- page := NewHtmlTemplatePage("signup_team", "Signup", c.Locale)
+ page := NewHtmlTemplatePage("signup_team", c.T("web.root.singup_title"), c.Locale)
page.Render(c, w)
}
@@ -237,7 +243,7 @@ func login(c *api.Context, w http.ResponseWriter, r *http.Request) {
var team *model.Team
if tResult := <-api.Srv.Store.Team().GetByName(teamName); tResult.Err != nil {
- l4g.Error("Couldn't find team name=%v, err=%v", teamName, tResult.Err.Message)
+ l4g.Error(utils.T("web.login.error"), teamName, tResult.Err.Message)
http.Redirect(w, r, api.GetProtocol(r)+"://"+r.Host, http.StatusTemporaryRedirect)
return
} else {
@@ -259,7 +265,7 @@ func login(c *api.Context, w http.ResponseWriter, r *http.Request) {
return
}
- page := NewHtmlTemplatePage("login", "Login", c.Locale)
+ page := NewHtmlTemplatePage("login", c.T("web.login.login_title"), c.Locale)
page.Props["TeamDisplayName"] = team.DisplayName
page.Props["TeamName"] = team.Name
@@ -273,7 +279,7 @@ func login(c *api.Context, w http.ResponseWriter, r *http.Request) {
func signupTeamConfirm(c *api.Context, w http.ResponseWriter, r *http.Request) {
email := r.FormValue("email")
- page := NewHtmlTemplatePage("signup_team_confirm", "Signup Email Sent", c.Locale)
+ page := NewHtmlTemplatePage("signup_team_confirm", c.T("web.signup_team_confirm.title"), c.Locale)
page.Props["Email"] = email
page.Render(c, w)
}
@@ -283,7 +289,7 @@ func signupTeamComplete(c *api.Context, w http.ResponseWriter, r *http.Request)
hash := r.FormValue("h")
if !model.ComparePassword(hash, fmt.Sprintf("%v:%v", data, utils.Cfg.EmailSettings.InviteSalt)) {
- c.Err = model.NewAppError("signupTeamComplete", "The signup link does not appear to be valid", "")
+ c.Err = model.NewLocAppError("signupTeamComplete", "web.signup_team_complete.invalid_link.app_error", nil, "")
return
}
@@ -291,11 +297,11 @@ func signupTeamComplete(c *api.Context, w http.ResponseWriter, r *http.Request)
t, err := strconv.ParseInt(props["time"], 10, 64)
if err != nil || model.GetMillis()-t > 1000*60*60*24*30 { // 30 days
- c.Err = model.NewAppError("signupTeamComplete", "The signup link has expired", "")
+ c.Err = model.NewLocAppError("signupTeamComplete", "web.signup_team_complete.link_expired.app_error", nil, "")
return
}
- page := NewHtmlTemplatePage("signup_team_complete", "Complete Team Sign Up", c.Locale)
+ page := NewHtmlTemplatePage("signup_team_complete", c.T("web.signup_team_complete.title"), c.Locale)
page.Props["Email"] = props["email"]
page.Props["Data"] = data
page.Props["Hash"] = hash
@@ -318,7 +324,7 @@ func signupUserComplete(c *api.Context, w http.ResponseWriter, r *http.Request)
} else {
team := result.Data.(*model.Team)
if !(team.Type == model.TEAM_OPEN || (team.Type == model.TEAM_INVITE && len(team.AllowedDomains) > 0)) {
- c.Err = model.NewAppError("signupUserComplete", "The team type doesn't allow open invites", "id="+id)
+ c.Err = model.NewLocAppError("signupUserComplete", "web.signup_user_complete.no_invites.app_error", nil, "id="+id)
return
}
@@ -332,7 +338,7 @@ func signupUserComplete(c *api.Context, w http.ResponseWriter, r *http.Request)
} else {
if !model.ComparePassword(hash, fmt.Sprintf("%v:%v", data, utils.Cfg.EmailSettings.InviteSalt)) {
- c.Err = model.NewAppError("signupTeamComplete", "The signup link does not appear to be valid", "")
+ c.Err = model.NewLocAppError("signupTeamComplete", "web.signup_user_complete.link_invalid.app_error", nil, "")
return
}
@@ -340,12 +346,12 @@ func signupUserComplete(c *api.Context, w http.ResponseWriter, r *http.Request)
t, err := strconv.ParseInt(props["time"], 10, 64)
if err != nil || model.GetMillis()-t > 1000*60*60*48 { // 48 hour
- c.Err = model.NewAppError("signupTeamComplete", "The signup link has expired", "")
+ c.Err = model.NewLocAppError("signupTeamComplete", "web.signup_user_complete.link_expired.app_error", nil, "")
return
}
}
- page := NewHtmlTemplatePage("signup_user_complete", "Complete User Sign Up", c.Locale)
+ page := NewHtmlTemplatePage("signup_user_complete", c.T("web.signup_user_complete.title"), c.Locale)
page.Props["Email"] = props["email"]
page.Props["TeamDisplayName"] = props["display_name"]
page.Props["TeamName"] = props["name"]
@@ -366,7 +372,7 @@ func postPermalink(c *api.Context, w http.ResponseWriter, r *http.Request) {
postId := params["postid"]
if len(postId) != 26 {
- c.Err = model.NewAppError("postPermalink", "Invalid Post ID", "id="+postId)
+ c.Err = model.NewLocAppError("postPermalink", "web.post_permalink.app_error", nil, "id="+postId)
return
}
@@ -518,17 +524,25 @@ func checkSessionSwitch(c *api.Context, w http.ResponseWriter, r *http.Request,
func doLoadChannel(c *api.Context, w http.ResponseWriter, r *http.Request, team *model.Team, channel *model.Channel, postid string) {
userChan := api.Srv.Store.User().Get(c.Session.UserId)
+ prefChan := api.Srv.Store.Preference().GetAll(c.Session.UserId)
var user *model.User
if ur := <-userChan; ur.Err != nil {
c.Err = ur.Err
c.RemoveSessionCookie(w, r)
- l4g.Error("Error in getting users profile for id=%v forcing logout", c.Session.UserId)
+ l4g.Error(utils.T("web.do_load_channel.error"), c.Session.UserId)
return
} else {
user = ur.Data.(*model.User)
}
+ var preferences model.Preferences
+ if result := <-prefChan; result.Err != nil {
+ l4g.Error("Error in getting preferences for id=%v", c.Session.UserId)
+ } else {
+ preferences = result.Data.(model.Preferences)
+ }
+
page := NewHtmlTemplatePage("channel", "", c.Locale)
page.Props["Title"] = channel.DisplayName + " - " + team.DisplayName + " " + page.ClientCfg["SiteName"]
page.Props["TeamDisplayName"] = team.DisplayName
@@ -538,6 +552,7 @@ func doLoadChannel(c *api.Context, w http.ResponseWriter, r *http.Request, team
page.Team = team
page.User = user
page.Channel = channel
+ page.Preferences = &preferences
page.Render(c, w)
}
@@ -586,7 +601,7 @@ func verifyEmail(c *api.Context, w http.ResponseWriter, r *http.Request) {
}
}
- page := NewHtmlTemplatePage("verify", "Email Verified", c.Locale)
+ page := NewHtmlTemplatePage("verify", c.T("web.email_verified.title"), c.Locale)
page.Props["TeamURL"] = c.GetTeamURLFromTeam(team)
page.Props["UserEmail"] = email
page.Props["ResendSuccess"] = resendSuccess
@@ -594,7 +609,7 @@ func verifyEmail(c *api.Context, w http.ResponseWriter, r *http.Request) {
}
func findTeam(c *api.Context, w http.ResponseWriter, r *http.Request) {
- page := NewHtmlTemplatePage("find_team", "Find Team", c.Locale)
+ page := NewHtmlTemplatePage("find_team", c.T("web.find_team.title"), c.Locale)
page.Render(c, w)
}
@@ -602,8 +617,17 @@ func docs(c *api.Context, w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
doc := params["doc"]
- page := NewHtmlTemplatePage("docs", "Documentation", c.Locale)
+ var user *model.User
+ if len(c.Session.UserId) != 0 {
+ userChan := api.Srv.Store.User().Get(c.Session.UserId)
+ if userChan := <-userChan; userChan.Err == nil {
+ user = userChan.Data.(*model.User)
+ }
+ }
+
+ page := NewHtmlTemplatePage("docs", c.T("web.doc.title"), c.Locale)
page.Props["Site"] = doc
+ page.User = user
page.Render(c, w)
}
@@ -618,7 +642,7 @@ func resetPassword(c *api.Context, w http.ResponseWriter, r *http.Request) {
isResetLink = false
} else {
if !model.ComparePassword(hash, fmt.Sprintf("%v:%v", data, utils.Cfg.EmailSettings.PasswordResetSalt)) {
- c.Err = model.NewAppError("resetPassword", "The reset link does not appear to be valid", "")
+ c.Err = model.NewLocAppError("resetPassword", "web.reset_password.invalid_link.app_error", nil, "")
return
}
@@ -626,7 +650,7 @@ func resetPassword(c *api.Context, w http.ResponseWriter, r *http.Request) {
t, err := strconv.ParseInt(props["time"], 10, 64)
if err != nil || model.GetMillis()-t > 1000*60*60 { // one hour
- c.Err = model.NewAppError("resetPassword", "The signup link has expired", "")
+ c.Err = model.NewLocAppError("resetPassword", "web.reset_password.expired_link.app_error", nil, "")
return
}
}
@@ -661,13 +685,13 @@ func signupWithOAuth(c *api.Context, w http.ResponseWriter, r *http.Request) {
teamName := params["team"]
if !utils.Cfg.TeamSettings.EnableUserCreation {
- c.Err = model.NewAppError("signupTeam", "User sign-up is disabled.", "")
+ c.Err = model.NewLocAppError("signupTeam", "web.singup_with_oauth.disabled.app_error", nil, "")
c.Err.StatusCode = http.StatusNotImplemented
return
}
if len(teamName) == 0 {
- c.Err = model.NewAppError("signupWithOAuth", "Invalid team name", "team_name="+teamName)
+ c.Err = model.NewLocAppError("signupWithOAuth", "web.singup_with_oauth.invalid_team.app_error", nil, "team_name="+teamName)
c.Err.StatusCode = http.StatusBadRequest
return
}
@@ -687,18 +711,18 @@ func signupWithOAuth(c *api.Context, w http.ResponseWriter, r *http.Request) {
props := model.MapFromJson(strings.NewReader(data))
if !model.ComparePassword(hash, fmt.Sprintf("%v:%v", data, utils.Cfg.EmailSettings.InviteSalt)) {
- c.Err = model.NewAppError("signupWithOAuth", "The signup link does not appear to be valid", "")
+ c.Err = model.NewLocAppError("signupWithOAuth", "web.singup_with_oauth.invalid_link.app_error", nil, "")
return
}
t, err := strconv.ParseInt(props["time"], 10, 64)
if err != nil || model.GetMillis()-t > 1000*60*60*48 { // 48 hours
- c.Err = model.NewAppError("signupWithOAuth", "The signup link has expired", "")
+ c.Err = model.NewLocAppError("signupWithOAuth", "web.singup_with_oauth.expired_link.app_error", nil, "")
return
}
if team.Id != props["id"] {
- c.Err = model.NewAppError("signupWithOAuth", "Invalid team name", data)
+ c.Err = model.NewLocAppError("signupWithOAuth", "web.singup_with_oauth.invalid_team.app_error", nil, data)
return
}
}
@@ -770,7 +794,7 @@ func loginWithOAuth(c *api.Context, w http.ResponseWriter, r *http.Request) {
loginHint := r.URL.Query().Get("login_hint")
if len(teamName) == 0 {
- c.Err = model.NewAppError("loginWithOAuth", "Invalid team name", "team_name="+teamName)
+ c.Err = model.NewLocAppError("loginWithOAuth", "web.login_with_oauth.invalid_team.app_error", nil, "team_name="+teamName)
c.Err.StatusCode = http.StatusBadRequest
return
}
@@ -822,7 +846,7 @@ func adminConsole(c *api.Context, w http.ResponseWriter, r *http.Request) {
activeTab := params["tab"]
teamId := params["team"]
- page := NewHtmlTemplatePage("admin_console", "Admin Console", c.Locale)
+ page := NewHtmlTemplatePage("admin_console", c.T("web.admin_console.title"), c.Locale)
page.User = user
page.Team = team
page.Props["ActiveTab"] = activeTab
@@ -832,7 +856,7 @@ func adminConsole(c *api.Context, w http.ResponseWriter, r *http.Request) {
func authorizeOAuth(c *api.Context, w http.ResponseWriter, r *http.Request) {
if !utils.Cfg.ServiceSettings.EnableOAuthServiceProvider {
- c.Err = model.NewAppError("authorizeOAuth", "The system admin has turned off OAuth service providing.", "")
+ c.Err = model.NewLocAppError("authorizeOAuth", "web.authorize_oauth.disabled.app_error", nil, "")
c.Err.StatusCode = http.StatusNotImplemented
return
}
@@ -848,7 +872,7 @@ func authorizeOAuth(c *api.Context, w http.ResponseWriter, r *http.Request) {
state := r.URL.Query().Get("state")
if len(responseType) == 0 || len(clientId) == 0 || len(redirect) == 0 {
- c.Err = model.NewAppError("authorizeOAuth", "Missing one or more of response_type, client_id, or redirect_uri", "")
+ c.Err = model.NewLocAppError("authorizeOAuth", "web.authorize_oauth.missing.app_error", nil, "")
return
}
@@ -868,7 +892,7 @@ func authorizeOAuth(c *api.Context, w http.ResponseWriter, r *http.Request) {
team = result.Data.(*model.Team)
}
- page := NewHtmlTemplatePage("authorize", "Authorize Application", c.Locale)
+ page := NewHtmlTemplatePage("authorize", c.T("web.authorize_oauth.title"), c.Locale)
page.Props["TeamName"] = team.Name
page.Props["AppName"] = app.Name
page.Props["ResponseType"] = responseType
@@ -881,7 +905,7 @@ func authorizeOAuth(c *api.Context, w http.ResponseWriter, r *http.Request) {
func getAccessToken(c *api.Context, w http.ResponseWriter, r *http.Request) {
if !utils.Cfg.ServiceSettings.EnableOAuthServiceProvider {
- c.Err = model.NewAppError("getAccessToken", "The system admin has turned off OAuth service providing.", "")
+ c.Err = model.NewLocAppError("getAccessToken", "web.get_access_token.disabled.app_error", nil, "")
c.Err.StatusCode = http.StatusNotImplemented
return
}
@@ -892,25 +916,25 @@ func getAccessToken(c *api.Context, w http.ResponseWriter, r *http.Request) {
grantType := r.FormValue("grant_type")
if grantType != model.ACCESS_TOKEN_GRANT_TYPE {
- c.Err = model.NewAppError("getAccessToken", "invalid_request: Bad grant_type", "")
+ c.Err = model.NewLocAppError("getAccessToken", "web.get_access_token.bad_grant.app_error", nil, "")
return
}
clientId := r.FormValue("client_id")
if len(clientId) != 26 {
- c.Err = model.NewAppError("getAccessToken", "invalid_request: Bad client_id", "")
+ c.Err = model.NewLocAppError("getAccessToken", "web.get_access_token.bad_client_id.app_error", nil, "")
return
}
secret := r.FormValue("client_secret")
if len(secret) == 0 {
- c.Err = model.NewAppError("getAccessToken", "invalid_request: Missing client_secret", "")
+ c.Err = model.NewLocAppError("getAccessToken", "web.get_access_token.bad_client_secret.app_error", nil, "")
return
}
code := r.FormValue("code")
if len(code) == 0 {
- c.Err = model.NewAppError("getAccessToken", "invalid_request: Missing code", "")
+ c.Err = model.NewLocAppError("getAccessToken", "web.get_access_token.missing_code.app_error", nil, "")
return
}
@@ -923,7 +947,7 @@ func getAccessToken(c *api.Context, w http.ResponseWriter, r *http.Request) {
if authData == nil {
c.LogAudit("fail - invalid auth code")
- c.Err = model.NewAppError("getAccessToken", "invalid_grant: Invalid or expired authorization code", "")
+ c.Err = model.NewLocAppError("getAccessToken", "web.get_access_token.expired_code.app_error", nil, "")
return
}
@@ -931,25 +955,25 @@ func getAccessToken(c *api.Context, w http.ResponseWriter, r *http.Request) {
if authData.IsExpired() {
c.LogAudit("fail - auth code expired")
- c.Err = model.NewAppError("getAccessToken", "invalid_grant: Invalid or expired authorization code", "")
+ c.Err = model.NewLocAppError("getAccessToken", "web.get_access_token.expired_code.app_error", nil, "")
return
}
if authData.RedirectUri != redirectUri {
c.LogAudit("fail - redirect uri provided did not match previous redirect uri")
- c.Err = model.NewAppError("getAccessToken", "invalid_request: Supplied redirect_uri does not match authorization code redirect_uri", "")
+ c.Err = model.NewLocAppError("getAccessToken", "web.get_access_token.redirect_uri.app_error", nil, "")
return
}
if !model.ComparePassword(code, fmt.Sprintf("%v:%v:%v:%v", clientId, redirectUri, authData.CreateAt, authData.UserId)) {
c.LogAudit("fail - auth code is invalid")
- c.Err = model.NewAppError("getAccessToken", "invalid_grant: Invalid or expired authorization code", "")
+ c.Err = model.NewLocAppError("getAccessToken", "web.get_access_token.expired_code.app_error", nil, "")
return
}
var app *model.OAuthApp
if result := <-achan; result.Err != nil {
- c.Err = model.NewAppError("getAccessToken", "invalid_client: Invalid client credentials", "")
+ c.Err = model.NewLocAppError("getAccessToken", "web.get_access_token.credentials.app_error", nil, "")
return
} else {
app = result.Data.(*model.OAuthApp)
@@ -957,7 +981,7 @@ func getAccessToken(c *api.Context, w http.ResponseWriter, r *http.Request) {
if !model.ComparePassword(app.ClientSecret, secret) {
c.LogAudit("fail - invalid client credentials")
- c.Err = model.NewAppError("getAccessToken", "invalid_client: Invalid client credentials", "")
+ c.Err = model.NewLocAppError("getAccessToken", "web.get_access_token.credentials.app_error", nil, "")
return
}
@@ -967,7 +991,7 @@ func getAccessToken(c *api.Context, w http.ResponseWriter, r *http.Request) {
}
if result := <-tchan; result.Err != nil {
- c.Err = model.NewAppError("getAccessToken", "server_error: Encountered internal server error while accessing database", "")
+ c.Err = model.NewLocAppError("getAccessToken", "web.get_access_token.internal.app_error", nil, "")
return
} else if result.Data != nil {
c.LogAudit("fail - auth code has been used previously")
@@ -975,16 +999,16 @@ func getAccessToken(c *api.Context, w http.ResponseWriter, r *http.Request) {
// Revoke access token, related auth code, and session from DB as well as from cache
if err := api.RevokeAccessToken(accessData.Token); err != nil {
- l4g.Error("Encountered an error revoking an access token, err=" + err.Message)
+ l4g.Error(utils.T("web.get_access_token.revoking.error") + err.Message)
}
- c.Err = model.NewAppError("getAccessToken", "invalid_grant: Authorization code already exchanged for an access token", "")
+ c.Err = model.NewLocAppError("getAccessToken", "web.get_access_token.exchanged.app_error", nil, "")
return
}
var user *model.User
if result := <-uchan; result.Err != nil {
- c.Err = model.NewAppError("getAccessToken", "server_error: Encountered internal server error while pulling user from database", "")
+ c.Err = model.NewLocAppError("getAccessToken", "web.get_access_token.internal_user.app_error", nil, "")
return
} else {
user = result.Data.(*model.User)
@@ -993,7 +1017,7 @@ func getAccessToken(c *api.Context, w http.ResponseWriter, r *http.Request) {
session := &model.Session{UserId: user.Id, TeamId: user.TeamId, Roles: user.Roles, IsOAuth: true}
if result := <-api.Srv.Store.Session().Save(session); result.Err != nil {
- c.Err = model.NewAppError("getAccessToken", "server_error: Encountered internal server error while saving session to database", "")
+ c.Err = model.NewLocAppError("getAccessToken", "web.get_access_token.internal_session.app_error", nil, "")
return
} else {
session = result.Data.(*model.Session)
@@ -1004,7 +1028,7 @@ func getAccessToken(c *api.Context, w http.ResponseWriter, r *http.Request) {
if result := <-api.Srv.Store.OAuth().SaveAccessData(accessData); result.Err != nil {
l4g.Error(result.Err)
- c.Err = model.NewAppError("getAccessToken", "server_error: Encountered internal server error while saving access token to database", "")
+ c.Err = model.NewLocAppError("getAccessToken", "web.get_access_token.internal_saving.app_error", nil, "")
return
}
@@ -1021,7 +1045,7 @@ func getAccessToken(c *api.Context, w http.ResponseWriter, r *http.Request) {
func incomingWebhook(c *api.Context, w http.ResponseWriter, r *http.Request) {
if !utils.Cfg.ServiceSettings.EnableIncomingWebhooks {
- c.Err = model.NewAppError("incomingWebhook", "Incoming webhooks have been disabled by the system admin.", "")
+ c.Err = model.NewLocAppError("incomingWebhook", "web.incoming_webhook.disabled.app_error", nil, "")
c.Err.StatusCode = http.StatusNotImplemented
return
}
@@ -1042,13 +1066,13 @@ func incomingWebhook(c *api.Context, w http.ResponseWriter, r *http.Request) {
}
if parsedRequest == nil {
- c.Err = model.NewAppError("incomingWebhook", "Unable to parse incoming data", "")
+ c.Err = model.NewLocAppError("incomingWebhook", "web.incoming_webhook.parse.app_error", nil, "")
return
}
text := parsedRequest.Text
if len(text) == 0 && parsedRequest.Attachments == nil {
- c.Err = model.NewAppError("incomingWebhook", "No text specified", "")
+ c.Err = model.NewLocAppError("incomingWebhook", "web.incoming_webhook.text.app_error", nil, "")
return
}
@@ -1066,7 +1090,7 @@ func incomingWebhook(c *api.Context, w http.ResponseWriter, r *http.Request) {
var hook *model.IncomingWebhook
if result := <-hchan; result.Err != nil {
- c.Err = model.NewAppError("incomingWebhook", "Invalid webhook", "err="+result.Err.Message)
+ c.Err = model.NewLocAppError("incomingWebhook", "web.incoming_webhook.invalid.app_error", nil, "err="+result.Err.Message)
return
} else {
hook = result.Data.(*model.IncomingWebhook)
@@ -1078,7 +1102,7 @@ func incomingWebhook(c *api.Context, w http.ResponseWriter, r *http.Request) {
if len(channelName) != 0 {
if channelName[0] == '@' {
if result := <-api.Srv.Store.User().GetByUsername(hook.TeamId, channelName[1:]); result.Err != nil {
- c.Err = model.NewAppError("incomingWebhook", "Couldn't find the user", "err="+result.Err.Message)
+ c.Err = model.NewLocAppError("incomingWebhook", "web.incoming_webhook.user.app_error", nil, "err="+result.Err.Message)
return
} else {
channelName = model.GetDMNameFromIds(result.Data.(*model.User).Id, hook.UserId)
@@ -1096,7 +1120,7 @@ func incomingWebhook(c *api.Context, w http.ResponseWriter, r *http.Request) {
overrideIconUrl := parsedRequest.IconURL
if result := <-cchan; result.Err != nil {
- c.Err = model.NewAppError("incomingWebhook", "Couldn't find the channel", "err="+result.Err.Message)
+ c.Err = model.NewLocAppError("incomingWebhook", "web.incoming_webhook.channel.app_error", nil, "err="+result.Err.Message)
return
} else {
channel = result.Data.(*model.Channel)
@@ -1108,7 +1132,7 @@ func incomingWebhook(c *api.Context, w http.ResponseWriter, r *http.Request) {
c.Session = model.Session{UserId: hook.UserId, TeamId: hook.TeamId, IsOAuth: false}
if !c.HasPermissionsToChannel(pchan, "createIncomingHook") && channel.Type != model.CHANNEL_OPEN {
- c.Err = model.NewAppError("incomingWebhook", "Inappropriate channel permissions", "")
+ c.Err = model.NewLocAppError("incomingWebhook", "web.incoming_webhook.permissions.app_error", nil, "")
return
}
@@ -1133,7 +1157,7 @@ func claimAccount(c *api.Context, w http.ResponseWriter, r *http.Request) {
var team *model.Team
if tResult := <-api.Srv.Store.Team().GetByName(teamName); tResult.Err != nil {
- l4g.Error("Couldn't find team name=%v, err=%v", teamName, tResult.Err.Message)
+ l4g.Error(utils.T("web.claim_account.team.error"), teamName, tResult.Err.Message)
http.Redirect(w, r, api.GetProtocol(r)+"://"+r.Host, http.StatusTemporaryRedirect)
return
} else {
@@ -1143,7 +1167,7 @@ func claimAccount(c *api.Context, w http.ResponseWriter, r *http.Request) {
authType := ""
if len(email) != 0 {
if uResult := <-api.Srv.Store.User().GetByEmail(team.Id, email); uResult.Err != nil {
- l4g.Error("Couldn't find user teamid=%v, email=%v, err=%v", team.Id, email, uResult.Err.Message)
+ l4g.Error(utils.T("web.claim_account.user.error"), team.Id, email, uResult.Err.Message)
http.Redirect(w, r, api.GetProtocol(r)+"://"+r.Host, http.StatusTemporaryRedirect)
return
} else {
@@ -1166,7 +1190,7 @@ func claimAccount(c *api.Context, w http.ResponseWriter, r *http.Request) {
}
}
- page := NewHtmlTemplatePage("claim_account", "Claim Account", c.Locale)
+ page := NewHtmlTemplatePage("claim_account", c.T("web.claim_account.title"), c.Locale)
page.Props["Email"] = email
page.Props["CurrentType"] = authType
page.Props["NewType"] = newType
diff --git a/web/web_test.go b/web/web_test.go
index 26239c34a..7617ae54a 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))