diff options
53 files changed, 2848 insertions, 163 deletions
diff --git a/api/file_test.go b/api/file_test.go index 8e5fc6f67..7a04674cd 100644 --- a/api/file_test.go +++ b/api/file_test.go @@ -10,6 +10,7 @@ import ( "io/ioutil" "net/http" "os" + "path/filepath" "strings" "testing" "time" @@ -870,7 +871,7 @@ func TestGetInfoForFilename(t *testing.T) { func readTestFile(name string) ([]byte, error) { path, _ := utils.FindDir("tests") - file, err := os.Open(path + "/" + name) + file, err := os.Open(filepath.Join(path, name)) if err != nil { return nil, err } diff --git a/api/user.go b/api/user.go index 14cc881dc..560d722a4 100644 --- a/api/user.go +++ b/api/user.go @@ -1170,7 +1170,11 @@ func completeSaml(c *Context, w http.ResponseWriter, r *http.Request) { teamId := relayProps["team_id"] if len(teamId) > 0 { c.App.Go(func() { - c.App.AddDirectChannels(teamId, user) + if err := c.App.AddUserToTeamByTeamId(teamId, user); err != nil { + l4g.Error(err.Error()) + } else { + c.App.AddDirectChannels(teamId, user) + } }) } case model.OAUTH_ACTION_EMAIL_TO_SSO: diff --git a/api/user_test.go b/api/user_test.go index f65d7c45b..518379305 100644 --- a/api/user_test.go +++ b/api/user_test.go @@ -12,6 +12,7 @@ import ( "mime/multipart" "net/http" "os" + "path/filepath" "strings" "testing" "time" @@ -778,7 +779,7 @@ func TestUserUploadProfileImage(t *testing.T) { } path, _ := utils.FindDir("tests") - file, err := os.Open(path + "/test.png") + file, err := os.Open(filepath.Join(path, "test.png")) if err != nil { t.Fatal(err) } diff --git a/api4/apitestlib.go b/api4/apitestlib.go index 2de031f9b..4620c5f4e 100644 --- a/api4/apitestlib.go +++ b/api4/apitestlib.go @@ -11,6 +11,7 @@ import ( "net" "net/http" "os" + "path/filepath" "reflect" "strconv" "strings" @@ -303,7 +304,11 @@ func (me *TestHelper) CreateUserWithClient(client *model.Client4) *model.User { } utils.DisableDebugLogForTest() - ruser, _ := client.CreateUser(user) + ruser, response := client.CreateUser(user) + if response.Error != nil { + panic(response.Error) + } + ruser.Password = "Password1" store.Must(me.App.Srv.Store.User().VerifyEmail(ruser.Id)) utils.EnableDebugLogForTest() @@ -676,7 +681,7 @@ func CheckInternalErrorStatus(t *testing.T, resp *model.Response) { func readTestFile(name string) ([]byte, error) { path, _ := utils.FindDir("tests") - file, err := os.Open(path + "/" + name) + file, err := os.Open(filepath.Join(path, name)) if err != nil { return nil, err } diff --git a/api4/oauth.go b/api4/oauth.go index d0f43256a..a173159b6 100644 --- a/api4/oauth.go +++ b/api4/oauth.go @@ -6,6 +6,7 @@ package api4 import ( "net/http" "net/url" + "path/filepath" "strings" l4g "github.com/alecthomas/log4go" @@ -375,7 +376,7 @@ func authorizeOAuthPage(c *Context, w http.ResponseWriter, r *http.Request) { w.Header().Set("Cache-Control", "no-cache, max-age=31556926, public") staticDir, _ := utils.FindDir(model.CLIENT_DIR) - http.ServeFile(w, r, staticDir+"root.html") + http.ServeFile(w, r, filepath.Join(staticDir, "root.html")) } func getAccessToken(c *Context, w http.ResponseWriter, r *http.Request) { diff --git a/api4/plugin_test.go b/api4/plugin_test.go index e385b5c8c..045ae9212 100644 --- a/api4/plugin_test.go +++ b/api4/plugin_test.go @@ -8,6 +8,7 @@ import ( "encoding/json" "io/ioutil" "os" + "path/filepath" "testing" "github.com/mattermost/mattermost-server/model" @@ -53,7 +54,7 @@ func TestPlugin(t *testing.T) { }() path, _ := utils.FindDir("tests") - file, err := os.Open(path + "/testplugin.tar.gz") + file, err := os.Open(filepath.Join(path, "testplugin.tar.gz")) if err != nil { t.Fatal(err) } diff --git a/api4/system.go b/api4/system.go index 7b63afc0b..4ae8ee7b9 100644 --- a/api4/system.go +++ b/api4/system.go @@ -17,6 +17,8 @@ import ( func (api *API) InitSystem() { api.BaseRoutes.System.Handle("/ping", api.ApiHandler(getSystemPing)).Methods("GET") + api.BaseRoutes.System.Handle("/timezones", api.ApiSessionRequired(getSupportedTimezones)).Methods("GET") + api.BaseRoutes.ApiRoot.Handle("/config", api.ApiSessionRequired(getConfig)).Methods("GET") api.BaseRoutes.ApiRoot.Handle("/config", api.ApiSessionRequired(updateConfig)).Methods("PUT") api.BaseRoutes.ApiRoot.Handle("/config/reload", api.ApiSessionRequired(configReload)).Methods("POST") @@ -378,6 +380,18 @@ func getAnalytics(c *Context, w http.ResponseWriter, r *http.Request) { w.Write([]byte(rows.ToJson())) } +func getSupportedTimezones(c *Context, w http.ResponseWriter, r *http.Request) { + supportedTimezones := c.App.Timezones() + + if supportedTimezones != nil { + w.Write([]byte(model.TimezonesToJson(supportedTimezones))) + return + } + + emptyTimezones := make([]string, 0) + w.Write([]byte(model.TimezonesToJson(emptyTimezones))) +} + func testS3(c *Context, w http.ResponseWriter, r *http.Request) { cfg := model.ConfigFromJson(r.Body) if cfg == nil { diff --git a/api4/system_test.go b/api4/system_test.go index 6ef02cbfe..bb3790d4b 100644 --- a/api4/system_test.go +++ b/api4/system_test.go @@ -527,3 +527,15 @@ func TestS3TestConnection(t *testing.T) { t.Fatal("should return error ") } } + +func TestSupportedTimezones(t *testing.T) { + th := Setup().InitBasic() + defer th.TearDown() + Client := th.Client + + supportedTimezonesFromConfig := th.App.Timezones() + supportedTimezones, resp := Client.GetSupportedTimezone() + + CheckNoError(t, resp) + assert.Equal(t, supportedTimezonesFromConfig, supportedTimezones) +} diff --git a/api4/team.go b/api4/team.go index 8e4c5c312..f8a1c556c 100644 --- a/api4/team.go +++ b/api4/team.go @@ -285,7 +285,7 @@ func getTeamMembers(c *Context, w http.ResponseWriter, r *http.Request) { return } - if members, err := c.App.GetTeamMembers(c.Params.TeamId, c.Params.Page, c.Params.PerPage); err != nil { + if members, err := c.App.GetTeamMembers(c.Params.TeamId, c.Params.Page*c.Params.PerPage, c.Params.PerPage); err != nil { c.Err = err return } else { @@ -543,9 +543,9 @@ func getAllTeams(c *Context, w http.ResponseWriter, r *http.Request) { var err *model.AppError if c.App.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) { - teams, err = c.App.GetAllTeamsPage(c.Params.Page, c.Params.PerPage) + teams, err = c.App.GetAllTeamsPage(c.Params.Page*c.Params.PerPage, c.Params.PerPage) } else { - teams, err = c.App.GetAllOpenTeamsPage(c.Params.Page, c.Params.PerPage) + teams, err = c.App.GetAllOpenTeamsPage(c.Params.Page*c.Params.PerPage, c.Params.PerPage) } if err != nil { diff --git a/api4/team_test.go b/api4/team_test.go index faac81312..f969d05b2 100644 --- a/api4/team_test.go +++ b/api4/team_test.go @@ -573,6 +573,10 @@ func TestGetAllTeams(t *testing.T) { _, resp := Client.CreateTeam(team) CheckNoError(t, resp) + team2 := &model.Team{DisplayName: "Name2", Name: GenerateTestTeamName(), Email: th.GenerateTestEmail(), Type: model.TEAM_OPEN, AllowOpenInvite: true} + _, resp = Client.CreateTeam(team2) + CheckNoError(t, resp) + rrteams, resp := Client.GetAllTeams("", 0, 1) CheckNoError(t, resp) @@ -617,6 +621,17 @@ func TestGetAllTeams(t *testing.T) { t.Fatal("wrong number of teams - should be 0") } + rrteams, resp = Client.GetAllTeams("", 0, 2) + CheckNoError(t, resp) + rrteams2, resp = Client.GetAllTeams("", 1, 2) + CheckNoError(t, resp) + + for _, t1 := range rrteams { + for _, t2 := range rrteams2 { + assert.NotEqual(t, t1.Id, t2.Id, "different pages should not have the same teams") + } + } + Client.Logout() _, resp = Client.GetAllTeams("", 1, 10) CheckUnauthorizedStatus(t, resp) @@ -1146,6 +1161,17 @@ func TestGetTeamMembers(t *testing.T) { t.Fatal("should be no member") } + rmembers, resp = Client.GetTeamMembers(team.Id, 0, 2, "") + CheckNoError(t, resp) + rmembers2, resp := Client.GetTeamMembers(team.Id, 1, 2, "") + CheckNoError(t, resp) + + for _, tm1 := range rmembers { + for _, tm2 := range rmembers2 { + assert.NotEqual(t, tm1.UserId+tm1.TeamId, tm2.UserId+tm2.TeamId, "different pages should not have the same members") + } + } + _, resp = Client.GetTeamMembers("junk", 0, 100, "") CheckBadRequestStatus(t, resp) diff --git a/api4/user_test.go b/api4/user_test.go index f04cd6ab2..359756aeb 100644 --- a/api4/user_test.go +++ b/api4/user_test.go @@ -996,6 +996,10 @@ func TestPatchUser(t *testing.T) { patch.Position = new(string) patch.NotifyProps = model.StringMap{} patch.NotifyProps["comment"] = "somethingrandom" + patch.Timezone = model.StringMap{} + patch.Timezone["useAutomaticTimezone"] = "true" + patch.Timezone["automaticTimezone"] = "America/New_York" + patch.Timezone["manualTimezone"] = "" ruser, resp := Client.PatchUser(user.Id, patch) CheckNoError(t, resp) @@ -1019,6 +1023,15 @@ func TestPatchUser(t *testing.T) { if ruser.NotifyProps["comment"] != "somethingrandom" { t.Fatal("NotifyProps did not update properly") } + if ruser.Timezone["useAutomaticTimezone"] != "true" { + t.Fatal("useAutomaticTimezone did not update properly") + } + if ruser.Timezone["automaticTimezone"] != "America/New_York" { + t.Fatal("automaticTimezone did not update properly") + } + if ruser.Timezone["manualTimezone"] != "" { + t.Fatal("manualTimezone did not update properly") + } patch.Username = model.NewString(th.BasicUser2.Username) _, resp = Client.PatchUser(user.Id, patch) diff --git a/app/app.go b/app/app.go index c4d11cb63..2c179f812 100644 --- a/app/app.go +++ b/app/app.go @@ -66,6 +66,8 @@ type App struct { clientLicenseValue atomic.Value licenseListeners map[string]func() + timezones atomic.Value + siteURL string newStore func() store.Store @@ -127,6 +129,9 @@ func New(options ...Option) (outApp *App, outErr error) { return nil, err } app.EnableConfigWatch() + + app.LoadTimezones() + if err := utils.InitTranslations(app.Config().LocalizationSettings); err != nil { return nil, errors.Wrapf(err, "unable to load Mattermost translation files") } @@ -439,7 +444,11 @@ func (a *App) WaitForGoroutines() { } func (a *App) HTMLTemplates() *template.Template { - return a.htmlTemplateWatcher.Templates() + if a.htmlTemplateWatcher != nil { + return a.htmlTemplateWatcher.Templates() + } + + return nil } func (a *App) HTTPClient(trustURLs bool) *http.Client { diff --git a/app/auto_posts.go b/app/auto_posts.go index 379c74ab7..a2adb9d6c 100644 --- a/app/auto_posts.go +++ b/app/auto_posts.go @@ -7,6 +7,7 @@ import ( "bytes" "io" "os" + "path/filepath" "github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/utils" @@ -43,7 +44,7 @@ func (cfg *AutoPostCreator) UploadTestFile() ([]string, bool) { filename := cfg.ImageFilenames[utils.RandIntFromRange(utils.Range{Begin: 0, End: len(cfg.ImageFilenames) - 1})] path, _ := utils.FindDir("web/static/images") - file, err := os.Open(path + "/" + filename) + file, err := os.Open(filepath.Join(path, filename)) if err != nil { return nil, false } diff --git a/app/config.go b/app/config.go index 460d580d8..ccd7236a0 100644 --- a/app/config.go +++ b/app/config.go @@ -54,6 +54,7 @@ func (a *App) LoadConfig(configFile string) *model.AppError { a.configFile = configPath utils.ConfigureLog(&cfg.LogSettings) + l4g.Info("Using config file at %s", configPath) a.config.Store(cfg) diff --git a/app/import.go b/app/import.go index 5a3158fab..e2e3aa1b7 100644 --- a/app/import.go +++ b/app/import.go @@ -399,7 +399,7 @@ func (a *App) ImportChannel(data *ChannelImportData, dryRun bool) *model.AppErro var team *model.Team if result := <-a.Srv.Store.Team().GetByName(*data.Team); result.Err != nil { - return model.NewAppError("BulkImport", "app.import.import_channel.team_not_found.error", map[string]interface{}{"TeamName": *data.Team}, "", http.StatusBadRequest) + return model.NewAppError("BulkImport", "app.import.import_channel.team_not_found.error", map[string]interface{}{"TeamName": *data.Team}, result.Err.Error(), http.StatusBadRequest) } else { team = result.Data.(*model.Team) } @@ -781,7 +781,7 @@ func (a *App) ImportUser(data *UserImportData, dryRun bool) *model.AppError { if len(preferences) > 0 { if result := <-a.Srv.Store.Preference().Save(&preferences); result.Err != nil { - return model.NewAppError("BulkImport", "app.import.import_user.save_preferences.error", nil, "", http.StatusInternalServerError) + return model.NewAppError("BulkImport", "app.import.import_user.save_preferences.error", nil, result.Err.Error(), http.StatusInternalServerError) } } @@ -899,7 +899,7 @@ func (a *App) ImportUserChannels(user *model.User, team *model.Team, teamMember if len(preferences) > 0 { if result := <-a.Srv.Store.Preference().Save(&preferences); result.Err != nil { - return model.NewAppError("BulkImport", "app.import.import_user_channels.save_preferences.error", nil, "", http.StatusInternalServerError) + return model.NewAppError("BulkImport", "app.import.import_user_channels.save_preferences.error", nil, result.Err.Error(), http.StatusInternalServerError) } } @@ -1069,7 +1069,7 @@ func (a *App) ImportReaction(data *ReactionImportData, post *model.Post, dryRun var user *model.User if result := <-a.Srv.Store.User().GetByUsername(*data.User); result.Err != nil { - return model.NewAppError("BulkImport", "app.import.import_post.user_not_found.error", map[string]interface{}{"Username": data.User}, "", http.StatusBadRequest) + return model.NewAppError("BulkImport", "app.import.import_post.user_not_found.error", map[string]interface{}{"Username": data.User}, result.Err.Error(), http.StatusBadRequest) } else { user = result.Data.(*model.User) } @@ -1092,7 +1092,7 @@ func (a *App) ImportReply(data *ReplyImportData, post *model.Post, dryRun bool) var user *model.User if result := <-a.Srv.Store.User().GetByUsername(*data.User); result.Err != nil { - return model.NewAppError("BulkImport", "app.import.import_post.user_not_found.error", map[string]interface{}{"Username": data.User}, "", http.StatusBadRequest) + return model.NewAppError("BulkImport", "app.import.import_post.user_not_found.error", map[string]interface{}{"Username": data.User}, result.Err.Error(), http.StatusBadRequest) } else { user = result.Data.(*model.User) } @@ -1147,21 +1147,21 @@ func (a *App) ImportPost(data *PostImportData, dryRun bool) *model.AppError { var team *model.Team if result := <-a.Srv.Store.Team().GetByName(*data.Team); result.Err != nil { - return model.NewAppError("BulkImport", "app.import.import_post.team_not_found.error", map[string]interface{}{"TeamName": *data.Team}, "", http.StatusBadRequest) + return model.NewAppError("BulkImport", "app.import.import_post.team_not_found.error", map[string]interface{}{"TeamName": *data.Team}, result.Err.Error(), http.StatusBadRequest) } else { team = result.Data.(*model.Team) } var channel *model.Channel if result := <-a.Srv.Store.Channel().GetByName(team.Id, *data.Channel, false); result.Err != nil { - return model.NewAppError("BulkImport", "app.import.import_post.channel_not_found.error", map[string]interface{}{"ChannelName": *data.Channel}, "", http.StatusBadRequest) + return model.NewAppError("BulkImport", "app.import.import_post.channel_not_found.error", map[string]interface{}{"ChannelName": *data.Channel}, result.Err.Error(), http.StatusBadRequest) } else { channel = result.Data.(*model.Channel) } var user *model.User if result := <-a.Srv.Store.User().GetByUsername(*data.User); result.Err != nil { - return model.NewAppError("BulkImport", "app.import.import_post.user_not_found.error", map[string]interface{}{"Username": *data.User}, "", http.StatusBadRequest) + return model.NewAppError("BulkImport", "app.import.import_post.user_not_found.error", map[string]interface{}{"Username": *data.User}, result.Err.Error(), http.StatusBadRequest) } else { user = result.Data.(*model.User) } @@ -1210,7 +1210,7 @@ func (a *App) ImportPost(data *PostImportData, dryRun bool) *model.AppError { var user *model.User if result := <-a.Srv.Store.User().GetByUsername(username); result.Err != nil { - return model.NewAppError("BulkImport", "app.import.import_post.user_not_found.error", map[string]interface{}{"Username": username}, "", http.StatusBadRequest) + return model.NewAppError("BulkImport", "app.import.import_post.user_not_found.error", map[string]interface{}{"Username": username}, result.Err.Error(), http.StatusBadRequest) } else { user = result.Data.(*model.User) } @@ -1225,7 +1225,7 @@ func (a *App) ImportPost(data *PostImportData, dryRun bool) *model.AppError { if len(preferences) > 0 { if result := <-a.Srv.Store.Preference().Save(&preferences); result.Err != nil { - return model.NewAppError("BulkImport", "app.import.import_post.save_preferences.error", nil, "", http.StatusInternalServerError) + return model.NewAppError("BulkImport", "app.import.import_post.save_preferences.error", nil, result.Err.Error(), http.StatusInternalServerError) } } } @@ -1351,7 +1351,7 @@ func (a *App) ImportDirectChannel(data *DirectChannelImportData, dryRun bool) *m userIds = append(userIds, user.Id) userMap[username] = user.Id } else { - return model.NewAppError("BulkImport", "app.import.import_direct_channel.member_not_found.error", nil, "", http.StatusBadRequest) + return model.NewAppError("BulkImport", "app.import.import_direct_channel.member_not_found.error", nil, result.Err.Error(), http.StatusBadRequest) } } @@ -1360,14 +1360,14 @@ func (a *App) ImportDirectChannel(data *DirectChannelImportData, dryRun bool) *m if len(userIds) == 2 { ch, err := a.createDirectChannel(userIds[0], userIds[1]) if err != nil && err.Id != store.CHANNEL_EXISTS_ERROR { - return model.NewAppError("BulkImport", "app.import.import_direct_channel.create_direct_channel.error", nil, "", http.StatusBadRequest) + return model.NewAppError("BulkImport", "app.import.import_direct_channel.create_direct_channel.error", nil, err.Error(), http.StatusBadRequest) } else { channel = ch } } else { ch, err := a.createGroupChannel(userIds, userIds[0]) if err != nil && err.Id != store.CHANNEL_EXISTS_ERROR { - return model.NewAppError("BulkImport", "app.import.import_direct_channel.create_group_channel.error", nil, "", http.StatusBadRequest) + return model.NewAppError("BulkImport", "app.import.import_direct_channel.create_group_channel.error", nil, err.Error(), http.StatusBadRequest) } else { channel = ch } @@ -1403,7 +1403,7 @@ func (a *App) ImportDirectChannel(data *DirectChannelImportData, dryRun bool) *m if data.Header != nil { channel.Header = *data.Header if result := <-a.Srv.Store.Channel().Update(channel); result.Err != nil { - return model.NewAppError("BulkImport", "app.import.import_direct_channel.update_header_failed.error", nil, "", http.StatusBadRequest) + return model.NewAppError("BulkImport", "app.import.import_direct_channel.update_header_failed.error", nil, result.Err.Error(), http.StatusBadRequest) } } @@ -1461,7 +1461,7 @@ func (a *App) ImportDirectPost(data *DirectPostImportData, dryRun bool) *model.A user := result.Data.(*model.User) userIds = append(userIds, user.Id) } else { - return model.NewAppError("BulkImport", "app.import.import_direct_post.channel_member_not_found.error", nil, "", http.StatusBadRequest) + return model.NewAppError("BulkImport", "app.import.import_direct_post.channel_member_not_found.error", nil, result.Err.Error(), http.StatusBadRequest) } } @@ -1469,14 +1469,14 @@ func (a *App) ImportDirectPost(data *DirectPostImportData, dryRun bool) *model.A if len(userIds) == 2 { ch, err := a.createDirectChannel(userIds[0], userIds[1]) if err != nil && err.Id != store.CHANNEL_EXISTS_ERROR { - return model.NewAppError("BulkImport", "app.import.import_direct_post.create_direct_channel.error", nil, "", http.StatusBadRequest) + return model.NewAppError("BulkImport", "app.import.import_direct_post.create_direct_channel.error", nil, err.Error(), http.StatusBadRequest) } else { channel = ch } } else { ch, err := a.createGroupChannel(userIds, userIds[0]) if err != nil && err.Id != store.CHANNEL_EXISTS_ERROR { - return model.NewAppError("BulkImport", "app.import.import_direct_post.create_group_channel.error", nil, "", http.StatusBadRequest) + return model.NewAppError("BulkImport", "app.import.import_direct_post.create_group_channel.error", nil, err.Error(), http.StatusBadRequest) } else { channel = ch } @@ -1548,7 +1548,7 @@ func (a *App) ImportDirectPost(data *DirectPostImportData, dryRun bool) *model.A if len(preferences) > 0 { if result := <-a.Srv.Store.Preference().Save(&preferences); result.Err != nil { - return model.NewAppError("BulkImport", "app.import.import_direct_post.save_preferences.error", nil, "", http.StatusInternalServerError) + return model.NewAppError("BulkImport", "app.import.import_direct_post.save_preferences.error", nil, result.Err.Error(), http.StatusInternalServerError) } } } diff --git a/app/import_test.go b/app/import_test.go index be2befd82..ef2d01cc0 100644 --- a/app/import_test.go +++ b/app/import_test.go @@ -4,6 +4,7 @@ package app import ( + "path/filepath" "runtime/debug" "strings" "testing" @@ -370,7 +371,7 @@ func TestImportValidateUserImportData(t *testing.T) { // Test a valid User with all fields populated. testsDir, _ := utils.FindDir("tests") data = UserImportData{ - ProfileImage: ptrStr(testsDir + "test.png"), + ProfileImage: ptrStr(filepath.Join(testsDir, "test.png")), Username: ptrStr("bob"), Email: ptrStr("bob@example.com"), AuthService: ptrStr("ldap"), @@ -1471,7 +1472,7 @@ func TestImportImportUser(t *testing.T) { username := model.NewId() testsDir, _ := utils.FindDir("tests") data = UserImportData{ - ProfileImage: ptrStr(testsDir + "test.png"), + ProfileImage: ptrStr(filepath.Join(testsDir, "test.png")), Username: &username, Email: ptrStr(model.NewId() + "@example.com"), Nickname: ptrStr(model.NewId()), @@ -1527,7 +1528,7 @@ func TestImportImportUser(t *testing.T) { // Alter all the fields of that user. data.Email = ptrStr(model.NewId() + "@example.com") - data.ProfileImage = ptrStr(testsDir + "testgif.gif") + data.ProfileImage = ptrStr(filepath.Join(testsDir, "testgif.gif")) data.AuthService = ptrStr("ldap") data.AuthData = &username data.Nickname = ptrStr(model.NewId()) diff --git a/app/plugin.go b/app/plugin.go index 6702e9227..903f4b767 100644 --- a/app/plugin.go +++ b/app/plugin.go @@ -401,7 +401,7 @@ func (a *App) InitPlugins(pluginPath, webappPath string, supervisorOverride plug options = append(options, pluginenv.SupervisorProvider(supervisorOverride)) } else if err := sandbox.CheckSupport(); err != nil { l4g.Warn(err.Error()) - l4g.Warn("plugin sandboxing is not supported. plugins will run with the same access level as the server") + l4g.Warn("plugin sandboxing is not supported. plugins will run with the same access level as the server. See documentation to learn more: https://developers.mattermost.com/extend/plugins/security/") options = append(options, pluginenv.SupervisorProvider(rpcplugin.SupervisorProvider)) } else { options = append(options, pluginenv.SupervisorProvider(sandbox.SupervisorProvider)) diff --git a/app/saml.go b/app/saml.go index bd9f647a1..29d5f8772 100644 --- a/app/saml.go +++ b/app/saml.go @@ -8,7 +8,6 @@ import ( "mime/multipart" "net/http" "os" - "path/filepath" "github.com/mattermost/mattermost-server/model" @@ -42,7 +41,7 @@ func WriteSamlFile(fileData *multipart.FileHeader) *model.AppError { defer file.Close() configDir, _ := utils.FindDir("config") - out, err := os.Create(configDir + filename) + out, err := os.Create(filepath.Join(configDir, filename)) if err != nil { return model.NewAppError("AddSamlCertificate", "api.admin.add_certificate.saving.app_error", nil, err.Error(), http.StatusInternalServerError) } diff --git a/app/session.go b/app/session.go index 88f52477f..c9208f2b2 100644 --- a/app/session.go +++ b/app/session.go @@ -225,7 +225,7 @@ func (a *App) UpdateLastActivityAtIfNeeded(session model.Session) { } if result := <-a.Srv.Store.Session().UpdateLastActivityAt(session.Id, now); result.Err != nil { - l4g.Error(utils.T("api.status.last_activity.error"), session.UserId, session.Id) + l4g.Error(utils.T("api.status.last_activity.error"), session.UserId, session.Id, result.Err) } session.LastActivityAt = now diff --git a/app/timezone.go b/app/timezone.go new file mode 100644 index 000000000..84d912da6 --- /dev/null +++ b/app/timezone.go @@ -0,0 +1,28 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package app + +import ( + "github.com/mattermost/mattermost-server/model" + "github.com/mattermost/mattermost-server/utils" +) + +func (a *App) Timezones() model.SupportedTimezones { + if cfg := a.timezones.Load(); cfg != nil { + return cfg.(model.SupportedTimezones) + } + return model.SupportedTimezones{} +} + +func (a *App) LoadTimezones() { + timezonePath := "timezones.json" + + if a.Config().TimezoneSettings.SupportedTimezonesPath != nil && len(*a.Config().TimezoneSettings.SupportedTimezonesPath) > 0 { + timezonePath = *a.Config().TimezoneSettings.SupportedTimezonesPath + } + + timezoneCfg := utils.LoadTimezones(timezonePath) + + a.timezones.Store(timezoneCfg) +} diff --git a/app/user.go b/app/user.go index 1a444e123..fee5cb07f 100644 --- a/app/user.go +++ b/app/user.go @@ -18,6 +18,7 @@ import ( "io/ioutil" "mime/multipart" "net/http" + "path/filepath" "strconv" "strings" @@ -717,7 +718,7 @@ func CreateProfileImage(username string, userId string, initialFont string) ([]b initial := string(strings.ToUpper(username)[0]) fontDir, _ := utils.FindDir("fonts") - fontBytes, err := ioutil.ReadFile(fontDir + initialFont) + fontBytes, err := ioutil.ReadFile(filepath.Join(fontDir, initialFont)) if err != nil { return nil, model.NewAppError("CreateProfileImage", "api.user.create_profile_image.default_font.app_error", nil, err.Error(), http.StatusInternalServerError) } diff --git a/cmd/commands/config_flag_test.go b/cmd/commands/config_flag_test.go index 8d284ab73..7ea0d5153 100644 --- a/cmd/commands/config_flag_test.go +++ b/cmd/commands/config_flag_test.go @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/require" + "encoding/json" "github.com/mattermost/mattermost-server/cmd" "github.com/mattermost/mattermost-server/utils" ) @@ -26,6 +27,11 @@ func TestConfigFlag(t *testing.T) { configPath := filepath.Join(dir, "foo.json") require.NoError(t, ioutil.WriteFile(configPath, []byte(config.ToJson()), 0600)) + timezones := utils.LoadTimezones("timezones.json") + tzConfigPath := filepath.Join(dir, "timezones.json") + timezoneData, _ := json.Marshal(timezones) + require.NoError(t, ioutil.WriteFile(tzConfigPath, timezoneData, 0600)) + i18n, ok := utils.FindDir("i18n") require.True(t, ok) require.NoError(t, utils.CopyDir(i18n, filepath.Join(dir, "i18n"))) diff --git a/cmd/commands/import.go b/cmd/commands/import.go index 4058d175a..c572ec53a 100644 --- a/cmd/commands/import.go +++ b/cmd/commands/import.go @@ -130,6 +130,7 @@ func bulkImportCmdF(command *cobra.Command, args []string) error { if lineNumber != 0 { cmd.CommandPrettyPrintln(fmt.Sprintf("Error occurred on data file line %v", lineNumber)) } + return err } else { if apply { cmd.CommandPrettyPrintln("Finished Bulk Import.") diff --git a/config/default.json b/config/default.json index b05ff11e7..ed0af5cc6 100644 --- a/config/default.json +++ b/config/default.json @@ -211,6 +211,9 @@ "AllowCustomThemes": true, "AllowedThemes": [] }, + "TimezoneSettings": { + "SupportedTimezonesPath": "timezones.json" + }, "GitLabSettings": { "Enable": false, "Secret": "", @@ -282,6 +285,8 @@ "IdpUrl": "", "IdpDescriptorUrl": "", "AssertionConsumerServiceURL": "", + "ScopingIDPProviderId": "", + "ScopingIDPName": "", "IdpCertificateFile": "", "PublicCertificateFile": "", "PrivateKeyFile": "", diff --git a/config/timezones.json b/config/timezones.json new file mode 100644 index 000000000..146097134 --- /dev/null +++ b/config/timezones.json @@ -0,0 +1,596 @@ +[ + "Africa/Abidjan", + "Africa/Accra", + "Africa/Addis_Ababa", + "Africa/Algiers", + "Africa/Asmara", + "Africa/Asmera", + "Africa/Bamako", + "Africa/Bangui", + "Africa/Banjul", + "Africa/Bissau", + "Africa/Blantyre", + "Africa/Brazzaville", + "Africa/Bujumbura", + "Africa/Cairo", + "Africa/Casablanca", + "Africa/Ceuta", + "Africa/Conakry", + "Africa/Dakar", + "Africa/Dar_es_Salaam", + "Africa/Djibouti", + "Africa/Douala", + "Africa/El_Aaiun", + "Africa/Freetown", + "Africa/Gaborone", + "Africa/Harare", + "Africa/Johannesburg", + "Africa/Juba", + "Africa/Kampala", + "Africa/Khartoum", + "Africa/Kigali", + "Africa/Kinshasa", + "Africa/Lagos", + "Africa/Libreville", + "Africa/Lome", + "Africa/Luanda", + "Africa/Lubumbashi", + "Africa/Lusaka", + "Africa/Malabo", + "Africa/Maputo", + "Africa/Maseru", + "Africa/Mbabane", + "Africa/Mogadishu", + "Africa/Monrovia", + "Africa/Nairobi", + "Africa/Ndjamena", + "Africa/Niamey", + "Africa/Nouakchott", + "Africa/Ouagadougou", + "Africa/Porto-Novo", + "Africa/Sao_Tome", + "Africa/Timbuktu", + "Africa/Tripoli", + "Africa/Tunis", + "Africa/Windhoek", + "America/Adak", + "America/Anchorage", + "America/Anguilla", + "America/Antigua", + "America/Araguaina", + "America/Argentina/Buenos_Aires", + "America/Argentina/Catamarca", + "America/Argentina/ComodRivadavia", + "America/Argentina/Cordoba", + "America/Argentina/Jujuy", + "America/Argentina/La_Rioja", + "America/Argentina/Mendoza", + "America/Argentina/Rio_Gallegos", + "America/Argentina/Salta", + "America/Argentina/San_Juan", + "America/Argentina/San_Luis", + "America/Argentina/Tucuman", + "America/Argentina/Ushuaia", + "America/Aruba", + "America/Asuncion", + "America/Atikokan", + "America/Atka", + "America/Bahia", + "America/Bahia_Banderas", + "America/Barbados", + "America/Belem", + "America/Belize", + "America/Blanc-Sablon", + "America/Boa_Vista", + "America/Bogota", + "America/Boise", + "America/Buenos_Aires", + "America/Cambridge_Bay", + "America/Campo_Grande", + "America/Cancun", + "America/Caracas", + "America/Catamarca", + "America/Cayenne", + "America/Cayman", + "America/Chicago", + "America/Chihuahua", + "America/Coral_Harbour", + "America/Cordoba", + "America/Costa_Rica", + "America/Creston", + "America/Cuiaba", + "America/Curacao", + "America/Danmarkshavn", + "America/Dawson", + "America/Dawson_Creek", + "America/Denver", + "America/Detroit", + "America/Dominica", + "America/Edmonton", + "America/Eirunepe", + "America/El_Salvador", + "America/Ensenada", + "America/Fort_Nelson", + "America/Fort_Wayne", + "America/Fortaleza", + "America/Glace_Bay", + "America/Godthab", + "America/Goose_Bay", + "America/Grand_Turk", + "America/Grenada", + "America/Guadeloupe", + "America/Guatemala", + "America/Guayaquil", + "America/Guyana", + "America/Halifax", + "America/Havana", + "America/Hermosillo", + "America/Indiana/Indianapolis", + "America/Indiana/Knox", + "America/Indiana/Marengo", + "America/Indiana/Petersburg", + "America/Indiana/Tell_City", + "America/Indiana/Vevay", + "America/Indiana/Vincennes", + "America/Indiana/Winamac", + "America/Indianapolis", + "America/Inuvik", + "America/Iqaluit", + "America/Jamaica", + "America/Jujuy", + "America/Juneau", + "America/Kentucky/Louisville", + "America/Kentucky/Monticello", + "America/Knox_IN", + "America/Kralendijk", + "America/La_Paz", + "America/Lima", + "America/Los_Angeles", + "America/Louisville", + "America/Lower_Princes", + "America/Maceio", + "America/Managua", + "America/Manaus", + "America/Marigot", + "America/Martinique", + "America/Matamoros", + "America/Mazatlan", + "America/Mendoza", + "America/Menominee", + "America/Merida", + "America/Metlakatla", + "America/Mexico_City", + "America/Miquelon", + "America/Moncton", + "America/Monterrey", + "America/Montevideo", + "America/Montreal", + "America/Montserrat", + "America/Nassau", + "America/New_York", + "America/Nipigon", + "America/Nome", + "America/Noronha", + "America/North_Dakota/Beulah", + "America/North_Dakota/Center", + "America/North_Dakota/New_Salem", + "America/Ojinaga", + "America/Panama", + "America/Pangnirtung", + "America/Paramaribo", + "America/Phoenix", + "America/Port-au-Prince", + "America/Port_of_Spain", + "America/Porto_Acre", + "America/Porto_Velho", + "America/Puerto_Rico", + "America/Punta_Arenas", + "America/Rainy_River", + "America/Rankin_Inlet", + "America/Recife", + "America/Regina", + "America/Resolute", + "America/Rio_Branco", + "America/Rosario", + "America/Santa_Isabel", + "America/Santarem", + "America/Santiago", + "America/Santo_Domingo", + "America/Sao_Paulo", + "America/Scoresbysund", + "America/Shiprock", + "America/Sitka", + "America/St_Barthelemy", + "America/St_Johns", + "America/St_Kitts", + "America/St_Lucia", + "America/St_Thomas", + "America/St_Vincent", + "America/Swift_Current", + "America/Tegucigalpa", + "America/Thule", + "America/Thunder_Bay", + "America/Tijuana", + "America/Toronto", + "America/Tortola", + "America/Vancouver", + "America/Virgin", + "America/Whitehorse", + "America/Winnipeg", + "America/Yakutat", + "America/Yellowknife", + "Antarctica/Casey", + "Antarctica/Davis", + "Antarctica/DumontDUrville", + "Antarctica/Macquarie", + "Antarctica/Mawson", + "Antarctica/McMurdo", + "Antarctica/Palmer", + "Antarctica/Rothera", + "Antarctica/South_Pole", + "Antarctica/Syowa", + "Antarctica/Troll", + "Antarctica/Vostok", + "Arctic/Longyearbyen", + "Asia/Aden", + "Asia/Almaty", + "Asia/Amman", + "Asia/Anadyr", + "Asia/Aqtau", + "Asia/Aqtobe", + "Asia/Ashgabat", + "Asia/Ashkhabad", + "Asia/Atyrau", + "Asia/Baghdad", + "Asia/Bahrain", + "Asia/Baku", + "Asia/Bangkok", + "Asia/Barnaul", + "Asia/Beirut", + "Asia/Bishkek", + "Asia/Brunei", + "Asia/Calcutta", + "Asia/Chita", + "Asia/Choibalsan", + "Asia/Chongqing", + "Asia/Chungking", + "Asia/Colombo", + "Asia/Dacca", + "Asia/Damascus", + "Asia/Dhaka", + "Asia/Dili", + "Asia/Dubai", + "Asia/Dushanbe", + "Asia/Famagusta", + "Asia/Gaza", + "Asia/Harbin", + "Asia/Hebron", + "Asia/Ho_Chi_Minh", + "Asia/Hong_Kong", + "Asia/Hovd", + "Asia/Irkutsk", + "Asia/Istanbul", + "Asia/Jakarta", + "Asia/Jayapura", + "Asia/Jerusalem", + "Asia/Kabul", + "Asia/Kamchatka", + "Asia/Karachi", + "Asia/Kashgar", + "Asia/Kathmandu", + "Asia/Katmandu", + "Asia/Khandyga", + "Asia/Kolkata", + "Asia/Krasnoyarsk", + "Asia/Kuala_Lumpur", + "Asia/Kuching", + "Asia/Kuwait", + "Asia/Macao", + "Asia/Macau", + "Asia/Magadan", + "Asia/Makassar", + "Asia/Manila", + "Asia/Muscat", + "Asia/Nicosia", + "Asia/Novokuznetsk", + "Asia/Novosibirsk", + "Asia/Omsk", + "Asia/Oral", + "Asia/Phnom_Penh", + "Asia/Pontianak", + "Asia/Pyongyang", + "Asia/Qatar", + "Asia/Qyzylorda", + "Asia/Rangoon", + "Asia/Riyadh", + "Asia/Saigon", + "Asia/Sakhalin", + "Asia/Samarkand", + "Asia/Seoul", + "Asia/Shanghai", + "Asia/Singapore", + "Asia/Srednekolymsk", + "Asia/Taipei", + "Asia/Tashkent", + "Asia/Tbilisi", + "Asia/Tehran", + "Asia/Tel_Aviv", + "Asia/Thimbu", + "Asia/Thimphu", + "Asia/Tokyo", + "Asia/Tomsk", + "Asia/Ujung_Pandang", + "Asia/Ulaanbaatar", + "Asia/Ulan_Bator", + "Asia/Urumqi", + "Asia/Ust-Nera", + "Asia/Vientiane", + "Asia/Vladivostok", + "Asia/Yakutsk", + "Asia/Yangon", + "Asia/Yekaterinburg", + "Asia/Yerevan", + "Atlantic/Azores", + "Atlantic/Bermuda", + "Atlantic/Canary", + "Atlantic/Cape_Verde", + "Atlantic/Faeroe", + "Atlantic/Faroe", + "Atlantic/Jan_Mayen", + "Atlantic/Madeira", + "Atlantic/Reykjavik", + "Atlantic/South_Georgia", + "Atlantic/St_Helena", + "Atlantic/Stanley", + "Australia/ACT", + "Australia/Adelaide", + "Australia/Brisbane", + "Australia/Broken_Hill", + "Australia/Canberra", + "Australia/Currie", + "Australia/Darwin", + "Australia/Eucla", + "Australia/Hobart", + "Australia/LHI", + "Australia/Lindeman", + "Australia/Lord_Howe", + "Australia/Melbourne", + "Australia/NSW", + "Australia/North", + "Australia/Perth", + "Australia/Queensland", + "Australia/South", + "Australia/Sydney", + "Australia/Tasmania", + "Australia/Victoria", + "Australia/West", + "Australia/Yancowinna", + "Brazil/Acre", + "Brazil/DeNoronha", + "Brazil/East", + "Brazil/West", + "CET", + "CST6CDT", + "Canada/Atlantic", + "Canada/Central", + "Canada/East-Saskatchewan", + "Canada/Eastern", + "Canada/Mountain", + "Canada/Newfoundland", + "Canada/Pacific", + "Canada/Saskatchewan", + "Canada/Yukon", + "Chile/Continental", + "Chile/EasterIsland", + "Cuba", + "EET", + "EST", + "EST5EDT", + "Egypt", + "Eire", + "Etc/GMT", + "Etc/GMT+0", + "Etc/GMT+1", + "Etc/GMT+10", + "Etc/GMT+11", + "Etc/GMT+12", + "Etc/GMT+2", + "Etc/GMT+3", + "Etc/GMT+4", + "Etc/GMT+5", + "Etc/GMT+6", + "Etc/GMT+7", + "Etc/GMT+8", + "Etc/GMT+9", + "Etc/GMT-0", + "Etc/GMT-1", + "Etc/GMT-10", + "Etc/GMT-11", + "Etc/GMT-12", + "Etc/GMT-13", + "Etc/GMT-14", + "Etc/GMT-2", + "Etc/GMT-3", + "Etc/GMT-4", + "Etc/GMT-5", + "Etc/GMT-6", + "Etc/GMT-7", + "Etc/GMT-8", + "Etc/GMT-9", + "Etc/GMT0", + "Etc/Greenwich", + "Etc/UCT", + "Etc/UTC", + "Etc/Universal", + "Etc/Zulu", + "Europe/Amsterdam", + "Europe/Andorra", + "Europe/Astrakhan", + "Europe/Athens", + "Europe/Belfast", + "Europe/Belgrade", + "Europe/Berlin", + "Europe/Bratislava", + "Europe/Brussels", + "Europe/Bucharest", + "Europe/Budapest", + "Europe/Busingen", + "Europe/Chisinau", + "Europe/Copenhagen", + "Europe/Dublin", + "Europe/Gibraltar", + "Europe/Guernsey", + "Europe/Helsinki", + "Europe/Isle_of_Man", + "Europe/Istanbul", + "Europe/Jersey", + "Europe/Kaliningrad", + "Europe/Kiev", + "Europe/Kirov", + "Europe/Lisbon", + "Europe/Ljubljana", + "Europe/London", + "Europe/Luxembourg", + "Europe/Madrid", + "Europe/Malta", + "Europe/Mariehamn", + "Europe/Minsk", + "Europe/Monaco", + "Europe/Moscow", + "Europe/Nicosia", + "Europe/Oslo", + "Europe/Paris", + "Europe/Podgorica", + "Europe/Prague", + "Europe/Riga", + "Europe/Rome", + "Europe/Samara", + "Europe/San_Marino", + "Europe/Sarajevo", + "Europe/Saratov", + "Europe/Simferopol", + "Europe/Skopje", + "Europe/Sofia", + "Europe/Stockholm", + "Europe/Tallinn", + "Europe/Tirane", + "Europe/Tiraspol", + "Europe/Ulyanovsk", + "Europe/Uzhgorod", + "Europe/Vaduz", + "Europe/Vatican", + "Europe/Vienna", + "Europe/Vilnius", + "Europe/Volgograd", + "Europe/Warsaw", + "Europe/Zagreb", + "Europe/Zaporozhye", + "Europe/Zurich", + "Factory", + "GB", + "GB-Eire", + "GMT", + "GMT+0", + "GMT-0", + "GMT0", + "Greenwich", + "HST", + "Hongkong", + "Iceland", + "Indian/Antananarivo", + "Indian/Chagos", + "Indian/Christmas", + "Indian/Cocos", + "Indian/Comoro", + "Indian/Kerguelen", + "Indian/Mahe", + "Indian/Maldives", + "Indian/Mauritius", + "Indian/Mayotte", + "Indian/Reunion", + "Iran", + "Israel", + "Jamaica", + "Japan", + "Kwajalein", + "Libya", + "MET", + "MST", + "MST7MDT", + "Mexico/BajaNorte", + "Mexico/BajaSur", + "Mexico/General", + "NZ", + "NZ-CHAT", + "Navajo", + "PRC", + "PST8PDT", + "Pacific/Apia", + "Pacific/Auckland", + "Pacific/Bougainville", + "Pacific/Chatham", + "Pacific/Chuuk", + "Pacific/Easter", + "Pacific/Efate", + "Pacific/Enderbury", + "Pacific/Fakaofo", + "Pacific/Fiji", + "Pacific/Funafuti", + "Pacific/Galapagos", + "Pacific/Gambier", + "Pacific/Guadalcanal", + "Pacific/Guam", + "Pacific/Honolulu", + "Pacific/Johnston", + "Pacific/Kiritimati", + "Pacific/Kosrae", + "Pacific/Kwajalein", + "Pacific/Majuro", + "Pacific/Marquesas", + "Pacific/Midway", + "Pacific/Nauru", + "Pacific/Niue", + "Pacific/Norfolk", + "Pacific/Noumea", + "Pacific/Pago_Pago", + "Pacific/Palau", + "Pacific/Pitcairn", + "Pacific/Pohnpei", + "Pacific/Ponape", + "Pacific/Port_Moresby", + "Pacific/Rarotonga", + "Pacific/Saipan", + "Pacific/Samoa", + "Pacific/Tahiti", + "Pacific/Tarawa", + "Pacific/Tongatapu", + "Pacific/Truk", + "Pacific/Wake", + "Pacific/Wallis", + "Pacific/Yap", + "Poland", + "Portugal", + "ROC", + "ROK", + "Singapore", + "Turkey", + "UCT", + "US/Alaska", + "US/Aleutian", + "US/Arizona", + "US/Central", + "US/East-Indiana", + "US/Eastern", + "US/Hawaii", + "US/Indiana-Starke", + "US/Michigan", + "US/Mountain", + "US/Pacific", + "US/Pacific-New", + "US/Samoa", + "UTC", + "Universal", + "W-SU", + "WET", + "Zulu" +] diff --git a/i18n/de.json b/i18n/de.json index 2a7952315..f3703fbd5 100644 --- a/i18n/de.json +++ b/i18n/de.json @@ -108,6 +108,18 @@ "translation": "Mattermost - E-Mail-Einstellungen überprüfen" }, { + "id": "api.admin.test_s3.missing_s3_bucket", + "translation": "S3-Bucket wird benötigt" + }, + { + "id": "api.admin.test_s3.missing_s3_endpoint", + "translation": "S3-Endpunkt wird benötigt" + }, + { + "id": "api.admin.test_s3.missing_s3_region", + "translation": "S3-Region wird benötigt" + }, + { "id": "api.admin.upload_brand_image.array.app_error", "translation": "Leeres Array unterhalb von 'image' in der Anfrage" }, @@ -2331,6 +2343,50 @@ "translation": "%v wurde aus dem Team entfernt." }, { + "id": "api.team.set_team_icon.array.app_error", + "translation": "Leeres Array unterhalb von 'image' in der Anfrage" + }, + { + "id": "api.team.set_team_icon.decode.app_error", + "translation": "Could not decode team icon" + }, + { + "id": "api.team.set_team_icon.decode_config.app_error", + "translation": "Could not decode team icon metadata" + }, + { + "id": "api.team.set_team_icon.encode.app_error", + "translation": "Could not encode team icon" + }, + { + "id": "api.team.set_team_icon.get_team.app_error", + "translation": "An error occurred getting the team" + }, + { + "id": "api.team.set_team_icon.no_file.app_error", + "translation": "Keine Datei unter 'image' in der Anfrage" + }, + { + "id": "api.team.set_team_icon.open.app_error", + "translation": "Konnte Bild nicht öffnen" + }, + { + "id": "api.team.set_team_icon.parse.app_error", + "translation": "Kann Multipart-Formular nicht analysieren" + }, + { + "id": "api.team.set_team_icon.storage.app_error", + "translation": "Die Datei kann nicht hochgeladen werden. Ein Foto Speicherort ist nicht eingerichtet." + }, + { + "id": "api.team.set_team_icon.too_large.app_error", + "translation": "Bildupload nicht möglich. Datei ist zu groß." + }, + { + "id": "api.team.set_team_icon.write_file.app_error", + "translation": "Could not save team icon" + }, + { "id": "api.team.signup_team.email_disabled.app_error", "translation": "Team-Anmeldung mit einer E-Mail-Adresse ist deaktiviert." }, @@ -3647,6 +3703,10 @@ "translation": "Kann extrahiertes Plugin nicht aktivieren. Plugin könnte schon existieren und aktiviert sein." }, { + "id": "app.plugin.cluster.save_config.app_error", + "translation": "The plugin configuration in your config.json file must be updated manually when using ReadOnlyConfig with clustering enabled." + }, + { "id": "app.plugin.config.app_error", "translation": "Fehler beim Speichern des Plugin-Status in der Konfiguration" }, @@ -4907,6 +4967,10 @@ "translation": "'ExportFormat' des Nachrichten-Export-Jobs muss 'actiance' oder 'globalrelay' sein." }, { + "id": "model.config.is_valid.message_export.export_type.app_error", + "translation": "'ExportFormat' des Nachrichten-Export-Jobs muss 'actiance' oder 'globalrelay' sein." + }, + { "id": "model.config.is_valid.message_export.file_location.app_error", "translation": "Nachrichten-Export-Aufgabe \"FileLocation\" muss ein beschreibbares Verzeichnis sein in welches die Exportdaten geschrieben werden" }, @@ -4915,6 +4979,26 @@ "translation": "Nachrichten-Export-Aufgabe \"FileLocation\" muss ein Unterverzeichnis von \"FileSettings.Directory\" sein" }, { + "id": "model.config.is_valid.message_export.global_relay.config_missing.app_error", + "translation": "Message export job ExportFormat is set to 'globalrelay', but GlobalRelaySettings are missing" + }, + { + "id": "model.config.is_valid.message_export.global_relay.customer_type.app_error", + "translation": "Message export GlobalRelaySettings.CustomerType must be set to one of either 'A9' or 'A10'" + }, + { + "id": "model.config.is_valid.message_export.global_relay.email_address.app_error", + "translation": "Nachrichten-Export-Job GlobalRelayEmailAddress muss eine gültige E-Mail-Adresse sein." + }, + { + "id": "model.config.is_valid.message_export.global_relay.smtp_password.app_error", + "translation": "Message export job GlobalRelaySettings.SmtpPassword must be set" + }, + { + "id": "model.config.is_valid.message_export.global_relay.smtp_username.app_error", + "translation": "Message export job GlobalRelaySettings.SmtpUsername must be set" + }, + { "id": "model.config.is_valid.message_export.global_relay_email_address.app_error", "translation": "Nachrichten-Export-Job GlobalRelayEmailAddress muss eine gültige E-Mail-Adresse sein." }, diff --git a/i18n/en.json b/i18n/en.json index c3206caff..b6dcced2a 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -116,14 +116,14 @@ "translation": "S3 Bucket is required" }, { - "id": "api.admin.test_s3.missing_s3_region", - "translation": "S3 Region is required" - }, - { "id": "api.admin.test_s3.missing_s3_endpoint", "translation": "S3 Endpoint is required" }, { + "id": "api.admin.test_s3.missing_s3_region", + "translation": "S3 Region is required" + }, + { "id": "api.admin.upload_brand_image.array.app_error", "translation": "Empty array under 'image' in request" }, @@ -2203,50 +2203,6 @@ "translation": "The number of running goroutines is over the health threshold %v of %v" }, { - "id": "api.team.set_team_icon.get_team.app_error", - "translation": "An error occurred getting the team" - }, - { - "id": "api.team.set_team_icon.storage.app_error", - "translation": "Unable to upload team icon. Image storage is not configured." - }, - { - "id": "api.team.set_team_icon.too_large.app_error", - "translation": "Unable to upload team icon. File is too large." - }, - { - "id": "api.team.set_team_icon.parse.app_error", - "translation": "Could not parse multipart form" - }, - { - "id": "api.team.set_team_icon.no_file.app_error", - "translation": "No file under 'image' in request" - }, - { - "id": "api.team.set_team_icon.array.app_error", - "translation": "Empty array under 'image' in request" - }, - { - "id": "api.team.set_team_icon.open.app_error", - "translation": "Could not open image file" - }, - { - "id": "api.team.set_team_icon.decode_config.app_error", - "translation": "Could not decode team icon metadata" - }, - { - "id": "api.team.set_team_icon.decode.app_error", - "translation": "Could not decode team icon" - }, - { - "id": "api.team.set_team_icon.encode.app_error", - "translation": "Could not encode team icon" - }, - { - "id": "api.team.set_team_icon.write_file.app_error", - "translation": "Could not save team icon" - }, - { "id": "api.team.add_user_to_team.added", "translation": "%v added to the team by %v." }, @@ -2391,6 +2347,50 @@ "translation": "%v removed from the team." }, { + "id": "api.team.set_team_icon.array.app_error", + "translation": "Empty array under 'image' in request" + }, + { + "id": "api.team.set_team_icon.decode.app_error", + "translation": "Could not decode team icon" + }, + { + "id": "api.team.set_team_icon.decode_config.app_error", + "translation": "Could not decode team icon metadata" + }, + { + "id": "api.team.set_team_icon.encode.app_error", + "translation": "Could not encode team icon" + }, + { + "id": "api.team.set_team_icon.get_team.app_error", + "translation": "An error occurred getting the team" + }, + { + "id": "api.team.set_team_icon.no_file.app_error", + "translation": "No file under 'image' in request" + }, + { + "id": "api.team.set_team_icon.open.app_error", + "translation": "Could not open image file" + }, + { + "id": "api.team.set_team_icon.parse.app_error", + "translation": "Could not parse multipart form" + }, + { + "id": "api.team.set_team_icon.storage.app_error", + "translation": "Unable to upload team icon. Image storage is not configured." + }, + { + "id": "api.team.set_team_icon.too_large.app_error", + "translation": "Unable to upload team icon. File is too large." + }, + { + "id": "api.team.set_team_icon.write_file.app_error", + "translation": "Could not save team icon" + }, + { "id": "api.team.signup_team.email_disabled.app_error", "translation": "Team sign-up with email is disabled." }, @@ -3715,14 +3715,14 @@ "translation": "Unable to activate extracted plugin. Plugin may already exist and be activated." }, { - "id": "app.plugin.config.app_error", - "translation": "Error saving plugin state in config" - }, - { "id": "app.plugin.cluster.save_config.app_error", "translation": "The plugin configuration in your config.json file must be updated manually when using ReadOnlyConfig with clustering enabled." }, { + "id": "app.plugin.config.app_error", + "translation": "Error saving plugin state in config" + }, + { "id": "app.plugin.deactivate.app_error", "translation": "Unable to deactivate plugin" }, @@ -3783,6 +3783,18 @@ "translation": "This team has reached the maximum number of allowed accounts. Contact your systems administrator to set a higher limit." }, { + "id": "app.timezones.failed_deserialize.app_error", + "translation": "Failed to deserialize Timezone config file={{.Filename}}, err={{.Error}}" + }, + { + "id": "app.timezones.load_config.app_error", + "translation": "Timezone config file does not exists file={{.Filename}}" + }, + { + "id": "app.timezones.read_config.app_error", + "translation": "Failed to read Timezone config file={{.Filename}}, err={{.Error}}" + }, + { "id": "app.user_access_token.disabled", "translation": "Personal access tokens are disabled on this server. Please contact your system administrator for details." }, @@ -4963,30 +4975,6 @@ "translation": "Message export job BatchSize must be a positive integer" }, { - "id": "model.config.is_valid.message_export.export_type.app_error", - "translation": "Message export job ExportFormat must be one of either 'actiance' or 'globalrelay'" - }, - { - "id": "model.config.is_valid.message_export.global_relay.config_missing.app_error", - "translation": "Message export job ExportFormat is set to 'globalrelay', but GlobalRelaySettings are missing" - }, - { - "id": "model.config.is_valid.message_export.global_relay.customer_type.app_error", - "translation": "Message export GlobalRelaySettings.CustomerType must be set to one of either 'A9' or 'A10'" - }, - { - "id": "model.config.is_valid.message_export.global_relay.email_address.app_error", - "translation": "Message export job GlobalRelaySettings.EmailAddress must be set to a valid email address" - }, - { - "id": "model.config.is_valid.message_export.global_relay.smtp_username.app_error", - "translation": "Message export job GlobalRelaySettings.SmtpUsername must be set" - }, - { - "id": "model.config.is_valid.message_export.global_relay.smtp_password.app_error", - "translation": "Message export job GlobalRelaySettings.SmtpPassword must be set" - }, - { "id": "model.config.is_valid.message_export.daily_runtime.app_error", "translation": "Message export job DailyRuntime must be a 24-hour time stamp in the form HH:MM." }, @@ -5003,6 +4991,10 @@ "translation": "Message export job ExportFormat must be one of either 'actiance' or 'globalrelay'" }, { + "id": "model.config.is_valid.message_export.export_type.app_error", + "translation": "Message export job ExportFormat must be one of either 'actiance' or 'globalrelay'" + }, + { "id": "model.config.is_valid.message_export.file_location.app_error", "translation": "Message export job FileLocation must be a writable directory that export data will be written to" }, @@ -5011,6 +5003,26 @@ "translation": "Message export job FileLocation must be a sub-directory of FileSettings.Directory" }, { + "id": "model.config.is_valid.message_export.global_relay.config_missing.app_error", + "translation": "Message export job ExportFormat is set to 'globalrelay', but GlobalRelaySettings are missing" + }, + { + "id": "model.config.is_valid.message_export.global_relay.customer_type.app_error", + "translation": "Message export GlobalRelaySettings.CustomerType must be set to one of either 'A9' or 'A10'" + }, + { + "id": "model.config.is_valid.message_export.global_relay.email_address.app_error", + "translation": "Message export job GlobalRelaySettings.EmailAddress must be set to a valid email address" + }, + { + "id": "model.config.is_valid.message_export.global_relay.smtp_password.app_error", + "translation": "Message export job GlobalRelaySettings.SmtpPassword must be set" + }, + { + "id": "model.config.is_valid.message_export.global_relay.smtp_username.app_error", + "translation": "Message export job GlobalRelaySettings.SmtpUsername must be set" + }, + { "id": "model.config.is_valid.message_export.global_relay_email_address.app_error", "translation": "Message export job GlobalRelayEmailAddress must be set to a valid email address" }, diff --git a/i18n/es.json b/i18n/es.json index e6a721f68..49d0550b6 100644 --- a/i18n/es.json +++ b/i18n/es.json @@ -108,6 +108,18 @@ "translation": "Mattermost - Configuración de pruebas de correo" }, { + "id": "api.admin.test_s3.missing_s3_bucket", + "translation": "S3 Bucket es necesario" + }, + { + "id": "api.admin.test_s3.missing_s3_endpoint", + "translation": "S3 Endpoint es necesario" + }, + { + "id": "api.admin.test_s3.missing_s3_region", + "translation": "S3 Región es necesaria" + }, + { "id": "api.admin.upload_brand_image.array.app_error", "translation": "Arreglo vacio bajo 'image' en la solicitud" }, @@ -2331,6 +2343,50 @@ "translation": "%v removido del equipo." }, { + "id": "api.team.set_team_icon.array.app_error", + "translation": "Arreglo vacío bajo 'image' en la solicitud" + }, + { + "id": "api.team.set_team_icon.decode.app_error", + "translation": "No se pudo descodificar el icono de equipo" + }, + { + "id": "api.team.set_team_icon.decode_config.app_error", + "translation": "No se pudo descodificar la metadata del icono de equipo" + }, + { + "id": "api.team.set_team_icon.encode.app_error", + "translation": "No se pudo codificar el icono de equipo" + }, + { + "id": "api.team.set_team_icon.get_team.app_error", + "translation": "Ocurrió un error al obtener el equipo" + }, + { + "id": "api.team.set_team_icon.no_file.app_error", + "translation": "No hay un archivo bajo 'image' en la solicitud" + }, + { + "id": "api.team.set_team_icon.open.app_error", + "translation": "No se pudo abrir el archivo de imagen" + }, + { + "id": "api.team.set_team_icon.parse.app_error", + "translation": "No se pudo analizar el formulario multipart" + }, + { + "id": "api.team.set_team_icon.storage.app_error", + "translation": "No se puede cargar el icono del equipo. El almacenamiento de imágenes no está configurado." + }, + { + "id": "api.team.set_team_icon.too_large.app_error", + "translation": "No se pudo subir el icono del equipo. El archivo es demasiado grande." + }, + { + "id": "api.team.set_team_icon.write_file.app_error", + "translation": "No se pudo guardar el icono del equipo" + }, + { "id": "api.team.signup_team.email_disabled.app_error", "translation": "El registro a equipos por correo electrónico está inhabilitado." }, @@ -3647,6 +3703,10 @@ "translation": "No se puede activar el plugin extraído. Puede que el plugin ya exista y esté activo." }, { + "id": "app.plugin.cluster.save_config.app_error", + "translation": "La configuración de plugin en tu archivo config.json debe ser actualizado manualmente cuando se utiliza ReadOnlyConfig con el agrupamiento de servidores habilitado." + }, + { "id": "app.plugin.config.app_error", "translation": "Error al guardar el estado del plugin en la configuración" }, @@ -4907,6 +4967,10 @@ "translation": "El trabajo de exportación de Mensaje ExportFormat debe ser 'actiance' o 'globalrelay'" }, { + "id": "model.config.is_valid.message_export.export_type.app_error", + "translation": "El trabajo de exportación de Mensaje ExportFormat debe ser 'actiance' o 'globalrelay'" + }, + { "id": "model.config.is_valid.message_export.file_location.app_error", "translation": "El ajuste FileLocation para realizar el trabajo de Exportación de Mensajes debe ser un directorio con permiso de escritura donde la data de exportación será almacenada." }, @@ -4915,6 +4979,26 @@ "translation": "El ajuste FileLocation para realizar el trabajo de Exportación de Mensajes debe ser un sub-directorio de FileSettings.Directory" }, { + "id": "model.config.is_valid.message_export.global_relay.config_missing.app_error", + "translation": "El trabajo de exportación de mensajes ExportFormat está asignado a 'globalrelay', pero falta establecer los parámetros de GlobalRelaySettings" + }, + { + "id": "model.config.is_valid.message_export.global_relay.customer_type.app_error", + "translation": "El parámetro de exportación de mensajes GlobalRelaySettings.CustomerType debe ser establecido en 'A9' o 'A10'" + }, + { + "id": "model.config.is_valid.message_export.global_relay.email_address.app_error", + "translation": "El parámetro de exportación de mensajes GlobalRelaySettings.EmailAddress debe estar asignado a una dirección de correo electrónico válida" + }, + { + "id": "model.config.is_valid.message_export.global_relay.smtp_password.app_error", + "translation": "El parámetro de exportación de mensajes GlobalRelaySettings.SmtpPassword debe estar asignado" + }, + { + "id": "model.config.is_valid.message_export.global_relay.smtp_username.app_error", + "translation": "El parámetro de exportación de mensajes GlobalRelaySettings.SmtpUsername debe estar asignado" + }, + { "id": "model.config.is_valid.message_export.global_relay_email_address.app_error", "translation": "El trabajo de exportación de Mensaje GlobalRelayEmailAddress debe estar asignado a una dirección de correo electrónico válido." }, diff --git a/i18n/fr.json b/i18n/fr.json index 3a9fae840..dea342bd4 100644 --- a/i18n/fr.json +++ b/i18n/fr.json @@ -108,6 +108,18 @@ "translation": "Mattermost - Test des paramètres e-mail" }, { + "id": "api.admin.test_s3.missing_s3_bucket", + "translation": "S3 Bucket is required" + }, + { + "id": "api.admin.test_s3.missing_s3_endpoint", + "translation": "S3 Endpoint is required" + }, + { + "id": "api.admin.test_s3.missing_s3_region", + "translation": "S3 Region is required" + }, + { "id": "api.admin.upload_brand_image.array.app_error", "translation": "Aucune image transmise dans la requête" }, @@ -2331,6 +2343,50 @@ "translation": "%v a été retiré de l'équipe." }, { + "id": "api.team.set_team_icon.array.app_error", + "translation": "Aucune image transmise dans la requête" + }, + { + "id": "api.team.set_team_icon.decode.app_error", + "translation": "Could not decode team icon" + }, + { + "id": "api.team.set_team_icon.decode_config.app_error", + "translation": "Could not decode team icon metadata" + }, + { + "id": "api.team.set_team_icon.encode.app_error", + "translation": "Could not encode team icon" + }, + { + "id": "api.team.set_team_icon.get_team.app_error", + "translation": "An error occurred getting the team" + }, + { + "id": "api.team.set_team_icon.no_file.app_error", + "translation": "Pas de fichier dans le paramètre \"image\" de la requête" + }, + { + "id": "api.team.set_team_icon.open.app_error", + "translation": "Impossible d'ouvrir le fichier image" + }, + { + "id": "api.team.set_team_icon.parse.app_error", + "translation": "Impossible d'analyser le formulaire multipart" + }, + { + "id": "api.team.set_team_icon.storage.app_error", + "translation": "Impossible d'envoyer le fichier. Le stockage d'images n'est pas configuré." + }, + { + "id": "api.team.set_team_icon.too_large.app_error", + "translation": "Impossible d'envoyer le fichier. Le fichier est trop volumineux." + }, + { + "id": "api.team.set_team_icon.write_file.app_error", + "translation": "Could not save team icon" + }, + { "id": "api.team.signup_team.email_disabled.app_error", "translation": "L'inscription avec une adresse e-mail est désactivée." }, @@ -3647,6 +3703,10 @@ "translation": "Impossible d'activer le plugin extrait. Il se peut qu'il existe déjà et soit déjà activé." }, { + "id": "app.plugin.cluster.save_config.app_error", + "translation": "The plugin configuration in your config.json file must be updated manually when using ReadOnlyConfig with clustering enabled." + }, + { "id": "app.plugin.config.app_error", "translation": "Une erreur s'est produite lors de la sauvegarde de l'état du plugin dans la configuration" }, @@ -4907,6 +4967,10 @@ "translation": "Message export job ExportFormat must be one of either 'actiance' or 'globalrelay'" }, { + "id": "model.config.is_valid.message_export.export_type.app_error", + "translation": "Message export job ExportFormat must be one of either 'actiance' or 'globalrelay'" + }, + { "id": "model.config.is_valid.message_export.file_location.app_error", "translation": "Le paramètre FileLocation de la tâche d'exportation de messages doit être un dossier avec droits d'écriture. Il s'agit du dossier dans lequel les données seront exportées." }, @@ -4915,6 +4979,26 @@ "translation": "Le paramètre FileLocation de la tâche d'exportation de messages doit être un sous-dossier de FileSettings.Directory." }, { + "id": "model.config.is_valid.message_export.global_relay.config_missing.app_error", + "translation": "Message export job ExportFormat is set to 'globalrelay', but GlobalRelaySettings are missing" + }, + { + "id": "model.config.is_valid.message_export.global_relay.customer_type.app_error", + "translation": "Message export GlobalRelaySettings.CustomerType must be set to one of either 'A9' or 'A10'" + }, + { + "id": "model.config.is_valid.message_export.global_relay.email_address.app_error", + "translation": "Message export job GlobalRelaySettings.EmailAddress must be set to a valid email address" + }, + { + "id": "model.config.is_valid.message_export.global_relay.smtp_password.app_error", + "translation": "Message export job GlobalRelaySettings.SmtpPassword must be set" + }, + { + "id": "model.config.is_valid.message_export.global_relay.smtp_username.app_error", + "translation": "Message export job GlobalRelaySettings.SmtpUsername must be set" + }, + { "id": "model.config.is_valid.message_export.global_relay_email_address.app_error", "translation": "Message export job GlobalRelayEmailAddress must be set to a valid email address" }, diff --git a/i18n/it.json b/i18n/it.json index 5cb1c8a85..3a6f13f98 100644 --- a/i18n/it.json +++ b/i18n/it.json @@ -108,6 +108,18 @@ "translation": "Mattermost - Test delle impostazioni Email" }, { + "id": "api.admin.test_s3.missing_s3_bucket", + "translation": "Bucket S3 richiesto" + }, + { + "id": "api.admin.test_s3.missing_s3_endpoint", + "translation": "Endpoint S3 richiesto" + }, + { + "id": "api.admin.test_s3.missing_s3_region", + "translation": "Region S3 richiesta" + }, + { "id": "api.admin.upload_brand_image.array.app_error", "translation": "Lista vuota per il campo 'image' nella richiesta" }, @@ -2331,6 +2343,50 @@ "translation": "%v è stato rimosso dal gruppo." }, { + "id": "api.team.set_team_icon.array.app_error", + "translation": "La richiesta contiene una lista vuota nel campo 'image'" + }, + { + "id": "api.team.set_team_icon.decode.app_error", + "translation": "Impossibile decodificare l'icona del gruppo" + }, + { + "id": "api.team.set_team_icon.decode_config.app_error", + "translation": "Impossibile decodificare i metadati dell'icona del gruppo" + }, + { + "id": "api.team.set_team_icon.encode.app_error", + "translation": "Impossibile codificare l'icona del gruppo" + }, + { + "id": "api.team.set_team_icon.get_team.app_error", + "translation": "Si è verificato un errore recuperando il gruppo" + }, + { + "id": "api.team.set_team_icon.no_file.app_error", + "translation": "La richiesta non contiene file nel campo 'image'" + }, + { + "id": "api.team.set_team_icon.open.app_error", + "translation": "Impossibile aprire il file immagine" + }, + { + "id": "api.team.set_team_icon.parse.app_error", + "translation": "Errore durante l'analisi della form multipart" + }, + { + "id": "api.team.set_team_icon.storage.app_error", + "translation": "Impossibile caricare l'icona del gruppo. Lo spazio di archiviazione delle immagini non è configurato." + }, + { + "id": "api.team.set_team_icon.too_large.app_error", + "translation": "Impossibile caricare l'icona del gruppo. Il file è troppo grande." + }, + { + "id": "api.team.set_team_icon.write_file.app_error", + "translation": "Impossibile salvare l'icona del gruppo" + }, + { "id": "api.team.signup_team.email_disabled.app_error", "translation": "L'iscrizione al gruppo con email è disabilitata." }, @@ -3647,6 +3703,10 @@ "translation": "Impossibile attivare il plugin estratto. Il plugin può essere già disponibile e dev'essere attivato." }, { + "id": "app.plugin.cluster.save_config.app_error", + "translation": "The plugin configuration in your config.json file must be updated manually when using ReadOnlyConfig with clustering enabled." + }, + { "id": "app.plugin.config.app_error", "translation": "Errore durante il salvataggio della configurazione del plugin" }, @@ -4907,6 +4967,10 @@ "translation": "ExportFormat del lavoro di esportazione messaggi deve essere 'actianve' oppure 'globalrelay'" }, { + "id": "model.config.is_valid.message_export.export_type.app_error", + "translation": "ExportFormat del lavoro di esportazione messaggi deve essere 'actianve' oppure 'globalrelay'" + }, + { "id": "model.config.is_valid.message_export.file_location.app_error", "translation": "Lavoro esportazione messaggi il valore FileLocation deve essere una cartella accessibile in scrittura in cui verranno salvati i dati esportati" }, @@ -4915,6 +4979,26 @@ "translation": "Lavoro esportazione messaggi il valore FileLocation deve essere una sottocartella di FileSettings.Directory" }, { + "id": "model.config.is_valid.message_export.global_relay.config_missing.app_error", + "translation": "Message export job ExportFormat is set to 'globalrelay', but GlobalRelaySettings are missing" + }, + { + "id": "model.config.is_valid.message_export.global_relay.customer_type.app_error", + "translation": "Message export GlobalRelaySettings.CustomerType must be set to one of either 'A9' or 'A10'" + }, + { + "id": "model.config.is_valid.message_export.global_relay.email_address.app_error", + "translation": "GlobalRelayEmailAddress del lavoro di esportazione messaggi deve essere un indirizzo email valido" + }, + { + "id": "model.config.is_valid.message_export.global_relay.smtp_password.app_error", + "translation": "Message export job GlobalRelaySettings.SmtpPassword must be set" + }, + { + "id": "model.config.is_valid.message_export.global_relay.smtp_username.app_error", + "translation": "Message export job GlobalRelaySettings.SmtpUsername must be set" + }, + { "id": "model.config.is_valid.message_export.global_relay_email_address.app_error", "translation": "GlobalRelayEmailAddress del lavoro di esportazione messaggi deve essere un indirizzo email valido" }, diff --git a/i18n/ja.json b/i18n/ja.json index b3e90c61f..ecf4ccbe2 100644 --- a/i18n/ja.json +++ b/i18n/ja.json @@ -108,6 +108,18 @@ "translation": "Mattermost - 電子メール設定のテスト" }, { + "id": "api.admin.test_s3.missing_s3_bucket", + "translation": "S3バケット名が必要です" + }, + { + "id": "api.admin.test_s3.missing_s3_endpoint", + "translation": "S3エンドポイントが必要です" + }, + { + "id": "api.admin.test_s3.missing_s3_region", + "translation": "S3リージョンが必要です" + }, + { "id": "api.admin.upload_brand_image.array.app_error", "translation": "リクエストの'image'以下の配列が空です" }, @@ -2331,6 +2343,50 @@ "translation": "%v はチームから削除されました。" }, { + "id": "api.team.set_team_icon.array.app_error", + "translation": "リクエストの'image'以下の配列が空です" + }, + { + "id": "api.team.set_team_icon.decode.app_error", + "translation": "チームアイコンをデコードできませんでした" + }, + { + "id": "api.team.set_team_icon.decode_config.app_error", + "translation": "チームアイコンメタデータをデコードできませんでした" + }, + { + "id": "api.team.set_team_icon.encode.app_error", + "translation": "チームアイコンをエンコードできませんでした" + }, + { + "id": "api.team.set_team_icon.get_team.app_error", + "translation": "チームの取得中にエラーが発生しました" + }, + { + "id": "api.team.set_team_icon.no_file.app_error", + "translation": "リクエストの'image'以下にファイルがありません" + }, + { + "id": "api.team.set_team_icon.open.app_error", + "translation": "画像ファイルを開けません" + }, + { + "id": "api.team.set_team_icon.parse.app_error", + "translation": "マルチパートフォームを解析できませんでした" + }, + { + "id": "api.team.set_team_icon.storage.app_error", + "translation": "チームアイコンをアップロードできません。画像ストレージが設定されていません。" + }, + { + "id": "api.team.set_team_icon.too_large.app_error", + "translation": "チームアイコンをアップロードできません。ファイルが大き過ぎます。" + }, + { + "id": "api.team.set_team_icon.write_file.app_error", + "translation": "チームアイコンを保存できませんでした" + }, + { "id": "api.team.signup_team.email_disabled.app_error", "translation": "電子メールによるチームへの利用登録は無効です。" }, @@ -3647,6 +3703,10 @@ "translation": "抽出されたプラグインを有効化できませんでした。プラグインが既に存在し、有効化されている可能性があります。" }, { + "id": "app.plugin.cluster.save_config.app_error", + "translation": "クラスター化を有効にするとともにReadOnlyConfigを使用する際は、config.jsonファイルのプラグイン設定は手動で更新する必要があります。" + }, + { "id": "app.plugin.config.app_error", "translation": "設定にプラグインの状態を保存する際にエラーが発生しました" }, @@ -4144,7 +4204,7 @@ }, { "id": "ent.metrics.stopping.info", - "translation": "メトリクスとプロファイリングのサーバーは%vで停止しています" + "translation": "%v のメトリクスとプロファイリング用のサーバーは停止しています" }, { "id": "ent.mfa.activate.authenticate.app_error", @@ -4907,6 +4967,10 @@ "translation": "メッセージエクスポート処理の ExportFormat は 'actiance' か 'globalrelay' のいずれかでなくてはなりません" }, { + "id": "model.config.is_valid.message_export.export_type.app_error", + "translation": "メッセージエクスポート処理の ExportFormat は 'actiance' か 'globalrelay' のいずれかでなくてはなりません" + }, + { "id": "model.config.is_valid.message_export.file_location.app_error", "translation": "メッセーエクスポート処理の FileLocation はエクスポートデータが書き込まれる書き込み可能なディレクトリでなくてはなりません" }, @@ -4915,6 +4979,26 @@ "translation": "メッセージエクスポート処理の FileLocation は FileSettings.Directory のサブディレクトリでなくてはなりません" }, { + "id": "model.config.is_valid.message_export.global_relay.config_missing.app_error", + "translation": "メッセージエクスポート処理のExportFormatは 'globalrelay' に設定されていますが、GlobalRealySettingsが見つかりません。" + }, + { + "id": "model.config.is_valid.message_export.global_relay.customer_type.app_error", + "translation": "メッセージエクスポートのGlobalRelaySettings.CustomerTypeには 'A9' か 'A10' のいずれかを設定してください。" + }, + { + "id": "model.config.is_valid.message_export.global_relay.email_address.app_error", + "translation": "メッセージエクスポート処理の GlobalRelay.EmailAddress には有効な電子メールアドレスが設定されていなくてはなりません" + }, + { + "id": "model.config.is_valid.message_export.global_relay.smtp_password.app_error", + "translation": "メッセージエクスポート処理のGlobalRelaySettings.SmtpPasswordが設定されていなければなりません。" + }, + { + "id": "model.config.is_valid.message_export.global_relay.smtp_username.app_error", + "translation": "メッセージエクスポート処理のGlobalRelaySettings.SmtpUsernameが設定されていなくてはなりません。" + }, + { "id": "model.config.is_valid.message_export.global_relay_email_address.app_error", "translation": "メッセージエクスポート処理の GlobalRelayEmailAddress には有効な電子メールアドレスが設定されていなくてはなりません" }, diff --git a/i18n/ko.json b/i18n/ko.json index 13ba19b79..018ce5c00 100644 --- a/i18n/ko.json +++ b/i18n/ko.json @@ -108,6 +108,18 @@ "translation": "Mattermost - 이메일 설정 테스트중" }, { + "id": "api.admin.test_s3.missing_s3_bucket", + "translation": "S3 Bucket is required" + }, + { + "id": "api.admin.test_s3.missing_s3_endpoint", + "translation": "S3 Endpoint is required" + }, + { + "id": "api.admin.test_s3.missing_s3_region", + "translation": "S3 Region is required" + }, + { "id": "api.admin.upload_brand_image.array.app_error", "translation": "요청의 'image' 속성에 빈 배열이 전달되었습니다" }, @@ -641,7 +653,7 @@ }, { "id": "api.command_dnd.desc", - "translation": "Do not disturb disables desktop and mobile push notifications." + "translation": "방해 금지모드는 데스크탑과 모바일 푸시 알림을 비활성화합니다." }, { "id": "api.command_dnd.disabled", @@ -2331,6 +2343,50 @@ "translation": "%v 가 채널에서 제거되었습니다." }, { + "id": "api.team.set_team_icon.array.app_error", + "translation": "요청의 'image' 속성에 빈 배열이 전달되었습니다" + }, + { + "id": "api.team.set_team_icon.decode.app_error", + "translation": "Could not decode team icon" + }, + { + "id": "api.team.set_team_icon.decode_config.app_error", + "translation": "Could not decode team icon metadata" + }, + { + "id": "api.team.set_team_icon.encode.app_error", + "translation": "Could not encode team icon" + }, + { + "id": "api.team.set_team_icon.get_team.app_error", + "translation": "An error occurred getting the team" + }, + { + "id": "api.team.set_team_icon.no_file.app_error", + "translation": "요청의 'image' 속성 아래에 파일이 없습니다" + }, + { + "id": "api.team.set_team_icon.open.app_error", + "translation": "이미지 파일을 열 수 없습니다." + }, + { + "id": "api.team.set_team_icon.parse.app_error", + "translation": "잘못된 multipart form을 파싱할 수 없습니다." + }, + { + "id": "api.team.set_team_icon.storage.app_error", + "translation": "파일을 업로드할 수 없습니다. 이미지 저장소가 설정되지 않았습니다." + }, + { + "id": "api.team.set_team_icon.too_large.app_error", + "translation": "이미지 파일을 불러올 수 없습니다. 파일이 너무 큽니다." + }, + { + "id": "api.team.set_team_icon.write_file.app_error", + "translation": "Could not save team icon" + }, + { "id": "api.team.signup_team.email_disabled.app_error", "translation": "이메일을 통한 팀 가입이 비활성화 되어있습니다." }, @@ -3647,6 +3703,10 @@ "translation": "Unable to activate extracted plugin. Plugin may already exist and be activated." }, { + "id": "app.plugin.cluster.save_config.app_error", + "translation": "The plugin configuration in your config.json file must be updated manually when using ReadOnlyConfig with clustering enabled." + }, + { "id": "app.plugin.config.app_error", "translation": "Error saving plugin state in config" }, @@ -4907,6 +4967,10 @@ "translation": "Message export job ExportFormat must be one of either 'actiance' or 'globalrelay'" }, { + "id": "model.config.is_valid.message_export.export_type.app_error", + "translation": "Message export job ExportFormat must be one of either 'actiance' or 'globalrelay'" + }, + { "id": "model.config.is_valid.message_export.file_location.app_error", "translation": "Message export job FileLocation must be a writable directory that export data will be written to" }, @@ -4915,6 +4979,26 @@ "translation": "Message export job FileLocation must be a sub-directory of FileSettings.Directory" }, { + "id": "model.config.is_valid.message_export.global_relay.config_missing.app_error", + "translation": "Message export job ExportFormat is set to 'globalrelay', but GlobalRelaySettings are missing" + }, + { + "id": "model.config.is_valid.message_export.global_relay.customer_type.app_error", + "translation": "Message export GlobalRelaySettings.CustomerType must be set to one of either 'A9' or 'A10'" + }, + { + "id": "model.config.is_valid.message_export.global_relay.email_address.app_error", + "translation": "Message export job GlobalRelaySettings.EmailAddress must be set to a valid email address" + }, + { + "id": "model.config.is_valid.message_export.global_relay.smtp_password.app_error", + "translation": "Message export job GlobalRelaySettings.SmtpPassword must be set" + }, + { + "id": "model.config.is_valid.message_export.global_relay.smtp_username.app_error", + "translation": "Message export job GlobalRelaySettings.SmtpUsername must be set" + }, + { "id": "model.config.is_valid.message_export.global_relay_email_address.app_error", "translation": "Message export job GlobalRelayEmailAddress must be set to a valid email address" }, diff --git a/i18n/nl.json b/i18n/nl.json index 1777b59d1..7d24fdb45 100644 --- a/i18n/nl.json +++ b/i18n/nl.json @@ -108,6 +108,18 @@ "translation": "Mattermost - E-mail-instellingen testen" }, { + "id": "api.admin.test_s3.missing_s3_bucket", + "translation": "S3 Bucket is required" + }, + { + "id": "api.admin.test_s3.missing_s3_endpoint", + "translation": "S3 Endpoint is required" + }, + { + "id": "api.admin.test_s3.missing_s3_region", + "translation": "S3 Region is required" + }, + { "id": "api.admin.upload_brand_image.array.app_error", "translation": "Lege array bij 'image' in aanvraag" }, @@ -2331,6 +2343,50 @@ "translation": "%v was verwijdert van het kanaal." }, { + "id": "api.team.set_team_icon.array.app_error", + "translation": "Lege array bij 'image' in aanvraag" + }, + { + "id": "api.team.set_team_icon.decode.app_error", + "translation": "Could not decode team icon" + }, + { + "id": "api.team.set_team_icon.decode_config.app_error", + "translation": "Could not decode team icon metadata" + }, + { + "id": "api.team.set_team_icon.encode.app_error", + "translation": "Could not encode team icon" + }, + { + "id": "api.team.set_team_icon.get_team.app_error", + "translation": "An error occurred getting the team" + }, + { + "id": "api.team.set_team_icon.no_file.app_error", + "translation": "Geen bestand bij 'image' in aanvraag" + }, + { + "id": "api.team.set_team_icon.open.app_error", + "translation": "Kan licentie bestand niet openen" + }, + { + "id": "api.team.set_team_icon.parse.app_error", + "translation": "Kan het 'multipart'-formulier niet verwerken" + }, + { + "id": "api.team.set_team_icon.storage.app_error", + "translation": "Kan bestand niet uploaden. Afbeeldings opslag is niet geconfigureerd." + }, + { + "id": "api.team.set_team_icon.too_large.app_error", + "translation": "Kan bestand niet uploaden. Bestand is te groot." + }, + { + "id": "api.team.set_team_icon.write_file.app_error", + "translation": "Could not save team icon" + }, + { "id": "api.team.signup_team.email_disabled.app_error", "translation": "Team aanmelding via e-mail is uitgeschakeld." }, @@ -3647,6 +3703,10 @@ "translation": "Unable to activate extracted plugin. Plugin may already exist and be activated." }, { + "id": "app.plugin.cluster.save_config.app_error", + "translation": "The plugin configuration in your config.json file must be updated manually when using ReadOnlyConfig with clustering enabled." + }, + { "id": "app.plugin.config.app_error", "translation": "Error saving plugin state in config" }, @@ -4907,6 +4967,10 @@ "translation": "Message export job ExportFormat must be one of either 'actiance' or 'globalrelay'" }, { + "id": "model.config.is_valid.message_export.export_type.app_error", + "translation": "Message export job ExportFormat must be one of either 'actiance' or 'globalrelay'" + }, + { "id": "model.config.is_valid.message_export.file_location.app_error", "translation": "Message export job FileLocation must be a writable directory that export data will be written to" }, @@ -4915,6 +4979,26 @@ "translation": "Message export job FileLocation must be a sub-directory of FileSettings.Directory" }, { + "id": "model.config.is_valid.message_export.global_relay.config_missing.app_error", + "translation": "Message export job ExportFormat is set to 'globalrelay', but GlobalRelaySettings are missing" + }, + { + "id": "model.config.is_valid.message_export.global_relay.customer_type.app_error", + "translation": "Message export GlobalRelaySettings.CustomerType must be set to one of either 'A9' or 'A10'" + }, + { + "id": "model.config.is_valid.message_export.global_relay.email_address.app_error", + "translation": "Message export job GlobalRelaySettings.EmailAddress must be set to a valid email address" + }, + { + "id": "model.config.is_valid.message_export.global_relay.smtp_password.app_error", + "translation": "Message export job GlobalRelaySettings.SmtpPassword must be set" + }, + { + "id": "model.config.is_valid.message_export.global_relay.smtp_username.app_error", + "translation": "Message export job GlobalRelaySettings.SmtpUsername must be set" + }, + { "id": "model.config.is_valid.message_export.global_relay_email_address.app_error", "translation": "Message export job GlobalRelayEmailAddress must be set to a valid email address" }, diff --git a/i18n/pl.json b/i18n/pl.json index cf1911446..ee3d90055 100644 --- a/i18n/pl.json +++ b/i18n/pl.json @@ -108,6 +108,18 @@ "translation": "Mattermost - Testowanie ustawień e-mail" }, { + "id": "api.admin.test_s3.missing_s3_bucket", + "translation": "S3 Bucket is required" + }, + { + "id": "api.admin.test_s3.missing_s3_endpoint", + "translation": "S3 Endpoint is required" + }, + { + "id": "api.admin.test_s3.missing_s3_region", + "translation": "S3 Region is required" + }, + { "id": "api.admin.upload_brand_image.array.app_error", "translation": "Pusta tablica w kluczu 'image' w żądaniu" }, @@ -2331,6 +2343,50 @@ "translation": "%v został usunięty z zespołu." }, { + "id": "api.team.set_team_icon.array.app_error", + "translation": "Pusta tablica w kluczu 'image' w żądaniu" + }, + { + "id": "api.team.set_team_icon.decode.app_error", + "translation": "Could not decode team icon" + }, + { + "id": "api.team.set_team_icon.decode_config.app_error", + "translation": "Could not decode team icon metadata" + }, + { + "id": "api.team.set_team_icon.encode.app_error", + "translation": "Could not encode team icon" + }, + { + "id": "api.team.set_team_icon.get_team.app_error", + "translation": "An error occurred getting the team" + }, + { + "id": "api.team.set_team_icon.no_file.app_error", + "translation": "Brak pliku w polu 'image' w żądaniu" + }, + { + "id": "api.team.set_team_icon.open.app_error", + "translation": "Nie można otworzyć pliku obrazu" + }, + { + "id": "api.team.set_team_icon.parse.app_error", + "translation": "Nie udało się przetworzyć wieloczęściowego formularza" + }, + { + "id": "api.team.set_team_icon.storage.app_error", + "translation": "Nie można wgrać pliku. Przechowywanie obrazów nie jest skonfigurowane." + }, + { + "id": "api.team.set_team_icon.too_large.app_error", + "translation": "Nie można wgrać pliku obrazu. Plik jest zbyt duży." + }, + { + "id": "api.team.set_team_icon.write_file.app_error", + "translation": "Could not save team icon" + }, + { "id": "api.team.signup_team.email_disabled.app_error", "translation": "Zapisy do zespołu za pomocą email są wyłączone." }, @@ -3647,6 +3703,10 @@ "translation": "Unable to activate extracted plugin. Plugin may already exist and be activated." }, { + "id": "app.plugin.cluster.save_config.app_error", + "translation": "The plugin configuration in your config.json file must be updated manually when using ReadOnlyConfig with clustering enabled." + }, + { "id": "app.plugin.config.app_error", "translation": "Error saving plugin state in config" }, @@ -4907,6 +4967,10 @@ "translation": "Message export job ExportFormat must be one of either 'actiance' or 'globalrelay'" }, { + "id": "model.config.is_valid.message_export.export_type.app_error", + "translation": "Message export job ExportFormat must be one of either 'actiance' or 'globalrelay'" + }, + { "id": "model.config.is_valid.message_export.file_location.app_error", "translation": "Message export job FileLocation must be a writable directory that export data will be written to" }, @@ -4915,6 +4979,26 @@ "translation": "Message export job FileLocation must be a sub-directory of FileSettings.Directory" }, { + "id": "model.config.is_valid.message_export.global_relay.config_missing.app_error", + "translation": "Message export job ExportFormat is set to 'globalrelay', but GlobalRelaySettings are missing" + }, + { + "id": "model.config.is_valid.message_export.global_relay.customer_type.app_error", + "translation": "Message export GlobalRelaySettings.CustomerType must be set to one of either 'A9' or 'A10'" + }, + { + "id": "model.config.is_valid.message_export.global_relay.email_address.app_error", + "translation": "Message export job GlobalRelaySettings.EmailAddress must be set to a valid email address" + }, + { + "id": "model.config.is_valid.message_export.global_relay.smtp_password.app_error", + "translation": "Message export job GlobalRelaySettings.SmtpPassword must be set" + }, + { + "id": "model.config.is_valid.message_export.global_relay.smtp_username.app_error", + "translation": "Message export job GlobalRelaySettings.SmtpUsername must be set" + }, + { "id": "model.config.is_valid.message_export.global_relay_email_address.app_error", "translation": "Message export job GlobalRelayEmailAddress must be set to a valid email address" }, diff --git a/i18n/pt-BR.json b/i18n/pt-BR.json index 000d87a0c..87f5590c3 100644 --- a/i18n/pt-BR.json +++ b/i18n/pt-BR.json @@ -108,6 +108,18 @@ "translation": "Mattermost - Teste de Configurações de Email" }, { + "id": "api.admin.test_s3.missing_s3_bucket", + "translation": "S3 Bucket é obrigatório" + }, + { + "id": "api.admin.test_s3.missing_s3_endpoint", + "translation": "S3 Endpoint é obrigatório" + }, + { + "id": "api.admin.test_s3.missing_s3_region", + "translation": "S3 Region é obrigatório" + }, + { "id": "api.admin.upload_brand_image.array.app_error", "translation": "Matriz vazia em 'image' na requisição" }, @@ -2331,6 +2343,50 @@ "translation": "%v foi removido da equipe." }, { + "id": "api.team.set_team_icon.array.app_error", + "translation": "Matriz vazia em 'image' na requisição" + }, + { + "id": "api.team.set_team_icon.decode.app_error", + "translation": "Não foi possível ler o ícone da equipe" + }, + { + "id": "api.team.set_team_icon.decode_config.app_error", + "translation": "Não foi possível ler os metadados do ícone da equipe" + }, + { + "id": "api.team.set_team_icon.encode.app_error", + "translation": "Não foi possível codificar o ícone da equipe" + }, + { + "id": "api.team.set_team_icon.get_team.app_error", + "translation": "Ocorreu um erro ao obter a equipe" + }, + { + "id": "api.team.set_team_icon.no_file.app_error", + "translation": "Nenhum arquivo em 'image' na requisição" + }, + { + "id": "api.team.set_team_icon.open.app_error", + "translation": "Não foi possível abrir arquivo de imagem" + }, + { + "id": "api.team.set_team_icon.parse.app_error", + "translation": "Não foi possível processar o formulário multipart" + }, + { + "id": "api.team.set_team_icon.storage.app_error", + "translation": "Não é possível enviar o ícone para equipe. Armazenamento de imagem não está configurado." + }, + { + "id": "api.team.set_team_icon.too_large.app_error", + "translation": "Não é possível enviar o ícone para equipe. Arquivo muito grande." + }, + { + "id": "api.team.set_team_icon.write_file.app_error", + "translation": "Could not save team icon" + }, + { "id": "api.team.signup_team.email_disabled.app_error", "translation": "Inscrição com e-mail na equipe está desativado." }, @@ -3647,6 +3703,10 @@ "translation": "Não foi possível ativar o plugin extraído. O plugin já pode existir e estar ativado." }, { + "id": "app.plugin.cluster.save_config.app_error", + "translation": "The plugin configuration in your config.json file must be updated manually when using ReadOnlyConfig with clustering enabled." + }, + { "id": "app.plugin.config.app_error", "translation": "Falha ao salvar o estado do plugin na configuração" }, @@ -4907,6 +4967,10 @@ "translation": "Na tarefa de exportação de mensagens o ExportFormat deve ser 'actiance' ou 'globalrelay'" }, { + "id": "model.config.is_valid.message_export.export_type.app_error", + "translation": "Na tarefa de exportação de mensagens o ExportFormat deve ser 'actiance' ou 'globalrelay'" + }, + { "id": "model.config.is_valid.message_export.file_location.app_error", "translation": "Trabalho de exportação de mensagens FileLocation deve ser um diretório gravável onde os dados de exportação serão gravados" }, @@ -4915,6 +4979,26 @@ "translation": "Trabalho de exportação de mensagens FileLocation deve ser um subdiretório de FileSettings.Directory" }, { + "id": "model.config.is_valid.message_export.global_relay.config_missing.app_error", + "translation": "Message export job ExportFormat is set to 'globalrelay', but GlobalRelaySettings are missing" + }, + { + "id": "model.config.is_valid.message_export.global_relay.customer_type.app_error", + "translation": "Message export GlobalRelaySettings.CustomerType must be set to one of either 'A9' or 'A10'" + }, + { + "id": "model.config.is_valid.message_export.global_relay.email_address.app_error", + "translation": "Na tarefa de exportação de mensagens o GlobalRelaySettings.EmailAddress deve ser um endereço de email válido" + }, + { + "id": "model.config.is_valid.message_export.global_relay.smtp_password.app_error", + "translation": "Message export job GlobalRelaySettings.SmtpPassword must be set" + }, + { + "id": "model.config.is_valid.message_export.global_relay.smtp_username.app_error", + "translation": "Message export job GlobalRelaySettings.SmtpUsername must be set" + }, + { "id": "model.config.is_valid.message_export.global_relay_email_address.app_error", "translation": "Na tarefa de exportação de mensagens o GlobalRelayEmailAddress deve ser um endereço de email válido" }, diff --git a/i18n/ru.json b/i18n/ru.json index 2d2f9d91f..550176c0a 100644 --- a/i18n/ru.json +++ b/i18n/ru.json @@ -108,6 +108,18 @@ "translation": "Mattermost - Тестирование настроек электронной почты" }, { + "id": "api.admin.test_s3.missing_s3_bucket", + "translation": "S3 Bucket is required" + }, + { + "id": "api.admin.test_s3.missing_s3_endpoint", + "translation": "S3 Endpoint is required" + }, + { + "id": "api.admin.test_s3.missing_s3_region", + "translation": "S3 Region is required" + }, + { "id": "api.admin.upload_brand_image.array.app_error", "translation": "Пустой массив 'image' в запросе" }, @@ -2331,6 +2343,50 @@ "translation": " %v удален из команды." }, { + "id": "api.team.set_team_icon.array.app_error", + "translation": "Пустой массив 'image' в запросе" + }, + { + "id": "api.team.set_team_icon.decode.app_error", + "translation": "Could not decode team icon" + }, + { + "id": "api.team.set_team_icon.decode_config.app_error", + "translation": "Could not decode team icon metadata" + }, + { + "id": "api.team.set_team_icon.encode.app_error", + "translation": "Could not encode team icon" + }, + { + "id": "api.team.set_team_icon.get_team.app_error", + "translation": "An error occurred getting the team" + }, + { + "id": "api.team.set_team_icon.no_file.app_error", + "translation": "Нет файла 'image' в запросе" + }, + { + "id": "api.team.set_team_icon.open.app_error", + "translation": "Не могу открыть файл изображения" + }, + { + "id": "api.team.set_team_icon.parse.app_error", + "translation": "Невозможно обработать форму из нескольких частей" + }, + { + "id": "api.team.set_team_icon.storage.app_error", + "translation": "Не удалось загрузить файл. Хранилище изображений не настроено." + }, + { + "id": "api.team.set_team_icon.too_large.app_error", + "translation": "Невозможно загрузить изображение. Файл слишком большой." + }, + { + "id": "api.team.set_team_icon.write_file.app_error", + "translation": "Could not save team icon" + }, + { "id": "api.team.signup_team.email_disabled.app_error", "translation": "Вход в команду с электронной почтой отключен." }, @@ -3647,6 +3703,10 @@ "translation": "Unable to activate extracted plugin. Plugin may already exist and be activated." }, { + "id": "app.plugin.cluster.save_config.app_error", + "translation": "The plugin configuration in your config.json file must be updated manually when using ReadOnlyConfig with clustering enabled." + }, + { "id": "app.plugin.config.app_error", "translation": "Error saving plugin state in config" }, @@ -4907,6 +4967,10 @@ "translation": "Message export job ExportFormat must be one of either 'actiance' or 'globalrelay'" }, { + "id": "model.config.is_valid.message_export.export_type.app_error", + "translation": "Message export job ExportFormat must be one of either 'actiance' or 'globalrelay'" + }, + { "id": "model.config.is_valid.message_export.file_location.app_error", "translation": "Message export job FileLocation must be a writable directory that export data will be written to" }, @@ -4915,6 +4979,26 @@ "translation": "Message export job FileLocation must be a sub-directory of FileSettings.Directory" }, { + "id": "model.config.is_valid.message_export.global_relay.config_missing.app_error", + "translation": "Message export job ExportFormat is set to 'globalrelay', but GlobalRelaySettings are missing" + }, + { + "id": "model.config.is_valid.message_export.global_relay.customer_type.app_error", + "translation": "Message export GlobalRelaySettings.CustomerType must be set to one of either 'A9' or 'A10'" + }, + { + "id": "model.config.is_valid.message_export.global_relay.email_address.app_error", + "translation": "Message export job GlobalRelaySettings.EmailAddress must be set to a valid email address" + }, + { + "id": "model.config.is_valid.message_export.global_relay.smtp_password.app_error", + "translation": "Message export job GlobalRelaySettings.SmtpPassword must be set" + }, + { + "id": "model.config.is_valid.message_export.global_relay.smtp_username.app_error", + "translation": "Message export job GlobalRelaySettings.SmtpUsername must be set" + }, + { "id": "model.config.is_valid.message_export.global_relay_email_address.app_error", "translation": "Message export job GlobalRelayEmailAddress must be set to a valid email address" }, diff --git a/i18n/tr.json b/i18n/tr.json index 8eb6e20e2..576221cbc 100644 --- a/i18n/tr.json +++ b/i18n/tr.json @@ -108,6 +108,18 @@ "translation": "Mattermost - E-posta Ayarları Sınanıyor" }, { + "id": "api.admin.test_s3.missing_s3_bucket", + "translation": "S3 Buketi zorunludur" + }, + { + "id": "api.admin.test_s3.missing_s3_endpoint", + "translation": "S3 Bitiş Noktası zorunludur" + }, + { + "id": "api.admin.test_s3.missing_s3_region", + "translation": "S3 Bölgesi zorunludur" + }, + { "id": "api.admin.upload_brand_image.array.app_error", "translation": "İstekte 'görsel' altında boş dizi" }, @@ -629,7 +641,7 @@ }, { "id": "api.command_collapse.desc", - "translation": "Görsel önizlemeleri otomatik daraltılsın" + "translation": "Görsel ön izlemeleri otomatik daraltılsın" }, { "id": "api.command_collapse.name", @@ -689,7 +701,7 @@ }, { "id": "api.command_expand.desc", - "translation": "Görsel önizlemesi otomatik daraltılmasın" + "translation": "Görsel ön izlemeleri otomatik daraltılmasın" }, { "id": "api.command_expand.name", @@ -701,7 +713,7 @@ }, { "id": "api.command_expand_collapse.fail.app_error", - "translation": "Önizlemeler genişletilirken bir sorun çıktı" + "translation": "Ön izlemeler genişletilirken bir sorun çıktı" }, { "id": "api.command_groupmsg.desc", @@ -1222,7 +1234,7 @@ }, { "id": "api.file.get_file_preview.no_preview.app_error", - "translation": "Dosyanın önizleme görseli yok" + "translation": "Dosyanın ön izleme görseli yok" }, { "id": "api.file.get_file_thumbnail.no_thumbnail.app_error", @@ -1262,11 +1274,11 @@ }, { "id": "api.file.handle_images_forget.encode_preview.error", - "translation": "Görsel JPG önizlemesi olarak kodlanamadı. Yol: %v Hata: %v" + "translation": "Görsel JPG ön izlemesi olarak kodlanamadı. Yol: %v Hata: %v" }, { "id": "api.file.handle_images_forget.upload_preview.error", - "translation": "Önizleme yüklenemedi. Yol: %v Hata: %v" + "translation": "Ön izleme yüklenemedi. Yol: %v Hata: %v" }, { "id": "api.file.handle_images_forget.upload_thumb.error", @@ -1768,7 +1780,7 @@ }, { "id": "api.post.link_preview_disabled.app_error", - "translation": "Bağlantı önizleme özelliği sistem yöneticisi tarafından devre dışı bırakılmış." + "translation": "Bağlantı ön izleme özelliği sistem yöneticisi tarafından devre dışı bırakılmış." }, { "id": "api.post.make_direct_channel_visible.get_2_members.error", @@ -2331,6 +2343,50 @@ "translation": "%v takımdan çıkarıldı." }, { + "id": "api.team.set_team_icon.array.app_error", + "translation": "İstekte 'görsel' altındaki dizi boş" + }, + { + "id": "api.team.set_team_icon.decode.app_error", + "translation": "Takım simgesinin kodu çözülemedi" + }, + { + "id": "api.team.set_team_icon.decode_config.app_error", + "translation": "Takım simgesi üst verisinin kodu çözülemedi" + }, + { + "id": "api.team.set_team_icon.encode.app_error", + "translation": "Takım simgesi kodlanamadı" + }, + { + "id": "api.team.set_team_icon.get_team.app_error", + "translation": "Takım alınırken bir sorun çıktı" + }, + { + "id": "api.team.set_team_icon.no_file.app_error", + "translation": "İstekte 'görsel' altında dosya yok" + }, + { + "id": "api.team.set_team_icon.open.app_error", + "translation": "Görsel dosyası açılamadı" + }, + { + "id": "api.team.set_team_icon.parse.app_error", + "translation": "Çok parçalı form işlenemedi" + }, + { + "id": "api.team.set_team_icon.storage.app_error", + "translation": "Takım simgesi yüklenemedi. Görsel deposu ayarları yapılmamış." + }, + { + "id": "api.team.set_team_icon.too_large.app_error", + "translation": "Takım simgesi yüklenemedi. Dosya çok büyük." + }, + { + "id": "api.team.set_team_icon.write_file.app_error", + "translation": "Takım simgesi kaydedilemedi" + }, + { "id": "api.team.signup_team.email_disabled.app_error", "translation": "E-posta ile takım hesabı açma özelliği devre dışı bırakılmış." }, @@ -3647,6 +3703,10 @@ "translation": "Ayıklanan uygulama eki etkinleştirilemedi. Uygulama eki zaten var ve etkinleştirilmiş olabilir." }, { + "id": "app.plugin.cluster.save_config.app_error", + "translation": "Küme özelliği kullanılırken ve SaltOkunurYapılandırma seçeneği etkinken config.json dosyasındaki uygulama eki yapılandırması el ile güncellenmelidir" + }, + { "id": "app.plugin.config.app_error", "translation": "Uygulama eki durumu yapılandırmaya kaydedilirken sorun çıktı" }, @@ -4764,11 +4824,11 @@ }, { "id": "model.config.is_valid.file_preview_height.app_error", - "translation": "Önizleme yüksekliği dosya ayarı geçersiz. Sıfır ya da pozitif bir sayı olmalı." + "translation": "Ön izleme yüksekliği dosya ayarı geçersiz. Sıfır ya da pozitif bir sayı olmalı." }, { "id": "model.config.is_valid.file_preview_width.app_error", - "translation": "Önizleme genişliği dosya ayarı geçersiz. Sıfır ya da pozitif bir sayı olmalı." + "translation": "Ön izleme genişliği dosya ayarı geçersiz. Sıfır ya da pozitif bir sayı olmalı." }, { "id": "model.config.is_valid.file_profile_height.app_error", @@ -4907,6 +4967,10 @@ "translation": "İleti dışa aktarma görevinin Dışa Aktarma Biçimi 'actiance' ya da 'genelaktarım' olmalıdır" }, { + "id": "model.config.is_valid.message_export.export_type.app_error", + "translation": "İleti dışa aktarma görevinin Dışa Aktarma Biçimi 'actiance' ya da 'genelaktarım' olmalıdır" + }, + { "id": "model.config.is_valid.message_export.file_location.app_error", "translation": "İleti dışa aktarma görevi DosyaKonumu değeri dışa aktarılacak verilerin kaydedilebilmesi için yazılabilen bir klasör olmalıdır" }, @@ -4915,6 +4979,26 @@ "translation": "İleti dışa aktarma görevi DosyaKonumu DosyaAyarları.Klasör değerinin bir alt klasörü olmalıdır" }, { + "id": "model.config.is_valid.message_export.global_relay.config_missing.app_error", + "translation": "İleti dışa aktarma görevi DışaAktarmaBiçimi 'GenelAktarıcı' olarak ayarlanmış ancak GenelAktarıcıAyarları eksik" + }, + { + "id": "model.config.is_valid.message_export.global_relay.customer_type.app_error", + "translation": "İleti dışa aktarma görevi GenelAktarıcıAyarları.MüşteriTürü 'A9' ya da 'A10' olarak ayarlanmalıdır" + }, + { + "id": "model.config.is_valid.message_export.global_relay.email_address.app_error", + "translation": "İleti dışa aktarma görevinin GenelAktarıcıAyarları.E-postaAdresi ayarı geçerli bir e-posta adresi olmalıdır" + }, + { + "id": "model.config.is_valid.message_export.global_relay.smtp_password.app_error", + "translation": "İleti dışa aktarma görevi GenelAktarıcıAyarları.SmtpParolası ayarlanmalıdır" + }, + { + "id": "model.config.is_valid.message_export.global_relay.smtp_username.app_error", + "translation": "İleti dışa aktarma görevi GenelAktarıcıAyarları.SmtpKullanıcıAdı ayarlanmalıdır" + }, + { "id": "model.config.is_valid.message_export.global_relay_email_address.app_error", "translation": "İleti dışa aktarma görevinin GenelAktarıcıE-postaAdresi geçerli bir e-posta adresi olmalıdır" }, diff --git a/i18n/zh-CN.json b/i18n/zh-CN.json index 43d3fde87..7f8fa4ba1 100644 --- a/i18n/zh-CN.json +++ b/i18n/zh-CN.json @@ -108,6 +108,18 @@ "translation": "Mattermost - 测试邮箱设置" }, { + "id": "api.admin.test_s3.missing_s3_bucket", + "translation": "S3 Bucket is required" + }, + { + "id": "api.admin.test_s3.missing_s3_endpoint", + "translation": "S3 Endpoint is required" + }, + { + "id": "api.admin.test_s3.missing_s3_region", + "translation": "S3 Region is required" + }, + { "id": "api.admin.upload_brand_image.array.app_error", "translation": "请求中图片为空" }, @@ -2331,6 +2343,50 @@ "translation": "%v 已移出团队。" }, { + "id": "api.team.set_team_icon.array.app_error", + "translation": "请求中图片为空" + }, + { + "id": "api.team.set_team_icon.decode.app_error", + "translation": "无法解码团队图标" + }, + { + "id": "api.team.set_team_icon.decode_config.app_error", + "translation": "Could not decode team icon metadata" + }, + { + "id": "api.team.set_team_icon.encode.app_error", + "translation": "无法编码团队图标" + }, + { + "id": "api.team.set_team_icon.get_team.app_error", + "translation": "获取团队时发生错误" + }, + { + "id": "api.team.set_team_icon.no_file.app_error", + "translation": "请求中缺失图片文件" + }, + { + "id": "api.team.set_team_icon.open.app_error", + "translation": "无法打开图像文件" + }, + { + "id": "api.team.set_team_icon.parse.app_error", + "translation": "不能解析混合表单" + }, + { + "id": "api.team.set_team_icon.storage.app_error", + "translation": "不能上传团队图标。没有配置图片存储。" + }, + { + "id": "api.team.set_team_icon.too_large.app_error", + "translation": "不能上传团队图标。文件太大。" + }, + { + "id": "api.team.set_team_icon.write_file.app_error", + "translation": "无法保存团队图标" + }, + { "id": "api.team.signup_team.email_disabled.app_error", "translation": "使用电子邮件注册团队被禁用。" }, @@ -3647,6 +3703,10 @@ "translation": "无法激活揭开的插件。插件可能已存在并已激活。" }, { + "id": "app.plugin.cluster.save_config.app_error", + "translation": "The plugin configuration in your config.json file must be updated manually when using ReadOnlyConfig with clustering enabled." + }, + { "id": "app.plugin.config.app_error", "translation": "保存插件状态到配置出错" }, @@ -4907,6 +4967,10 @@ "translation": "消息导出任务 ExportFormat 必须为 'actiance' 或 'globalrelay'" }, { + "id": "model.config.is_valid.message_export.export_type.app_error", + "translation": "消息导出任务 ExportFormat 必须为 'actiance' 或 'globalrelay'" + }, + { "id": "model.config.is_valid.message_export.file_location.app_error", "translation": "消息导出任务 FileLocation 比如为可写的目录" }, @@ -4915,6 +4979,26 @@ "translation": "消息导出任务 FileLocation 必须为 FileSettings.Directory 的子目录" }, { + "id": "model.config.is_valid.message_export.global_relay.config_missing.app_error", + "translation": "Message export job ExportFormat is set to 'globalrelay', but GlobalRelaySettings are missing" + }, + { + "id": "model.config.is_valid.message_export.global_relay.customer_type.app_error", + "translation": "Message export GlobalRelaySettings.CustomerType must be set to one of either 'A9' or 'A10'" + }, + { + "id": "model.config.is_valid.message_export.global_relay.email_address.app_error", + "translation": "消息导出任务 GlobalRelayEmailAddress 必须为有效的电子邮箱地址" + }, + { + "id": "model.config.is_valid.message_export.global_relay.smtp_password.app_error", + "translation": "Message export job GlobalRelaySettings.SmtpPassword must be set" + }, + { + "id": "model.config.is_valid.message_export.global_relay.smtp_username.app_error", + "translation": "Message export job GlobalRelaySettings.SmtpUsername must be set" + }, + { "id": "model.config.is_valid.message_export.global_relay_email_address.app_error", "translation": "消息导出任务 GlobalRelayEmailAddress 必须为有效的电子邮箱地址" }, diff --git a/i18n/zh-TW.json b/i18n/zh-TW.json index 0700a15a5..f78f6b288 100644 --- a/i18n/zh-TW.json +++ b/i18n/zh-TW.json @@ -108,6 +108,18 @@ "translation": "Mattermost - 測試電子郵件設定" }, { + "id": "api.admin.test_s3.missing_s3_bucket", + "translation": "S3 Bucket is required" + }, + { + "id": "api.admin.test_s3.missing_s3_endpoint", + "translation": "S3 Endpoint is required" + }, + { + "id": "api.admin.test_s3.missing_s3_region", + "translation": "S3 Region is required" + }, + { "id": "api.admin.upload_brand_image.array.app_error", "translation": "要求的 'image' 欄位為空陣列" }, @@ -2331,6 +2343,50 @@ "translation": "%v 已從團隊中移除。" }, { + "id": "api.team.set_team_icon.array.app_error", + "translation": "要求的 'image' 欄位為空陣列" + }, + { + "id": "api.team.set_team_icon.decode.app_error", + "translation": "Could not decode team icon" + }, + { + "id": "api.team.set_team_icon.decode_config.app_error", + "translation": "Could not decode team icon metadata" + }, + { + "id": "api.team.set_team_icon.encode.app_error", + "translation": "Could not encode team icon" + }, + { + "id": "api.team.set_team_icon.get_team.app_error", + "translation": "An error occurred getting the team" + }, + { + "id": "api.team.set_team_icon.no_file.app_error", + "translation": "要求的 'image' 欄位沒有檔案" + }, + { + "id": "api.team.set_team_icon.open.app_error", + "translation": "無法開啟圖片檔案" + }, + { + "id": "api.team.set_team_icon.parse.app_error", + "translation": "無法解析 multipart 表單" + }, + { + "id": "api.team.set_team_icon.storage.app_error", + "translation": "無法上傳檔案。尚未設定圖片儲存位置。" + }, + { + "id": "api.team.set_team_icon.too_large.app_error", + "translation": "無法上傳圖片檔案。檔案太大。" + }, + { + "id": "api.team.set_team_icon.write_file.app_error", + "translation": "Could not save team icon" + }, + { "id": "api.team.signup_team.email_disabled.app_error", "translation": "使用電子郵件註冊團隊已被停用。" }, @@ -3647,6 +3703,10 @@ "translation": "無法啟動已解開的模組。模組可能已存在並已啟動。" }, { + "id": "app.plugin.cluster.save_config.app_error", + "translation": "The plugin configuration in your config.json file must be updated manually when using ReadOnlyConfig with clustering enabled." + }, + { "id": "app.plugin.config.app_error", "translation": "儲存模組狀態於設定時錯誤" }, @@ -4907,6 +4967,10 @@ "translation": "訊息匯出工作的 ExportFormat 必須為 'actiance' 或 'globalrelay'" }, { + "id": "model.config.is_valid.message_export.export_type.app_error", + "translation": "訊息匯出工作的 ExportFormat 必須為 'actiance' 或 'globalrelay'" + }, + { "id": "model.config.is_valid.message_export.file_location.app_error", "translation": "匯出訊息工作 FileLocation 設定必須為可寫入的目錄,匯出資料將寫入此處" }, @@ -4915,6 +4979,26 @@ "translation": "匯出訊息工作 FileLocation 設定必須為 FileSettings.Directory 的子目錄" }, { + "id": "model.config.is_valid.message_export.global_relay.config_missing.app_error", + "translation": "Message export job ExportFormat is set to 'globalrelay', but GlobalRelaySettings are missing" + }, + { + "id": "model.config.is_valid.message_export.global_relay.customer_type.app_error", + "translation": "Message export GlobalRelaySettings.CustomerType must be set to one of either 'A9' or 'A10'" + }, + { + "id": "model.config.is_valid.message_export.global_relay.email_address.app_error", + "translation": "訊息匯出工作的 GlobalRelayEmailAddress 必須為 'actiance' 或 'globalrelay'" + }, + { + "id": "model.config.is_valid.message_export.global_relay.smtp_password.app_error", + "translation": "Message export job GlobalRelaySettings.SmtpPassword must be set" + }, + { + "id": "model.config.is_valid.message_export.global_relay.smtp_username.app_error", + "translation": "Message export job GlobalRelaySettings.SmtpUsername must be set" + }, + { "id": "model.config.is_valid.message_export.global_relay_email_address.app_error", "translation": "訊息匯出工作的 GlobalRelayEmailAddress 必須為 'actiance' 或 'globalrelay'" }, diff --git a/model/client4.go b/model/client4.go index 693008734..091f33844 100644 --- a/model/client4.go +++ b/model/client4.go @@ -318,6 +318,10 @@ func (c *Client4) GetAnalyticsRoute() string { return fmt.Sprintf("/analytics") } +func (c *Client4) GetTimezonesRoute() string { + return fmt.Sprintf(c.GetSystemRoute() + "/timezones") +} + func (c *Client4) DoApiGet(url string, etag string) (*http.Response, *AppError) { return c.DoApiRequest(http.MethodGet, c.ApiUrl+url, "", etag) } @@ -3173,6 +3177,18 @@ func (c *Client4) DeleteReaction(reaction *Reaction) (bool, *Response) { } } +// Timezone Section + +// GetSupportedTimezone returns a page of supported timezones on the system. +func (c *Client4) GetSupportedTimezone() (SupportedTimezones, *Response) { + if r, err := c.DoApiGet(c.GetTimezonesRoute(), ""); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return TimezonesFromJson(r.Body), BuildResponse(r) + } +} + // Open Graph Metadata Section // OpenGraph return the open graph metadata for a particular url if the site have the metadata diff --git a/model/config.go b/model/config.go index 1d5e06fca..5cc2488e0 100644 --- a/model/config.go +++ b/model/config.go @@ -1295,6 +1295,9 @@ type SamlSettings struct { IdpDescriptorUrl *string AssertionConsumerServiceURL *string + ScopingIDPProviderId *string + ScopingIDPName *string + IdpCertificateFile *string PublicCertificateFile *string PrivateKeyFile *string @@ -1701,6 +1704,16 @@ func (s *MessageExportSettings) SetDefaults() { } } +type TimezoneSettings struct { + SupportedTimezonesPath *string +} + +func (s *TimezoneSettings) SetDefaults() { + if s.SupportedTimezonesPath == nil { + s.SupportedTimezonesPath = NewString("timezones.json") + } +} + type ConfigFunc func() *Config type Config struct { @@ -1734,6 +1747,7 @@ type Config struct { MessageExportSettings MessageExportSettings JobSettings JobSettings PluginSettings PluginSettings + TimezoneSettings TimezoneSettings } func (o *Config) Clone() *Config { @@ -1803,6 +1817,7 @@ func (o *Config) SetDefaults() { o.JobSettings.SetDefaults() o.WebrtcSettings.SetDefaults() o.MessageExportSettings.SetDefaults() + o.TimezoneSettings.SetDefaults() } func (o *Config) IsValid() *AppError { diff --git a/model/timezone.go b/model/timezone.go new file mode 100644 index 000000000..420b9d2e2 --- /dev/null +++ b/model/timezone.go @@ -0,0 +1,628 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "encoding/json" + "io" +) + +type SupportedTimezones []string + +func TimezonesToJson(timezoneList []string) string { + b, _ := json.Marshal(timezoneList) + return string(b) +} + +func TimezonesFromJson(data io.Reader) SupportedTimezones { + var timezones SupportedTimezones + json.NewDecoder(data).Decode(&timezones) + return timezones +} + +func DefaultUserTimezone() map[string]string { + defaultTimezone := make(map[string]string) + defaultTimezone["useAutomaticTimezone"] = "true" + defaultTimezone["automaticTimezone"] = "" + defaultTimezone["manualTimezone"] = "" + + return defaultTimezone +} + +var DefaultSupportedTimezones = []string{ + "Africa/Abidjan", + "Africa/Accra", + "Africa/Addis_Ababa", + "Africa/Algiers", + "Africa/Asmara", + "Africa/Asmera", + "Africa/Bamako", + "Africa/Bangui", + "Africa/Banjul", + "Africa/Bissau", + "Africa/Blantyre", + "Africa/Brazzaville", + "Africa/Bujumbura", + "Africa/Cairo", + "Africa/Casablanca", + "Africa/Ceuta", + "Africa/Conakry", + "Africa/Dakar", + "Africa/Dar_es_Salaam", + "Africa/Djibouti", + "Africa/Douala", + "Africa/El_Aaiun", + "Africa/Freetown", + "Africa/Gaborone", + "Africa/Harare", + "Africa/Johannesburg", + "Africa/Juba", + "Africa/Kampala", + "Africa/Khartoum", + "Africa/Kigali", + "Africa/Kinshasa", + "Africa/Lagos", + "Africa/Libreville", + "Africa/Lome", + "Africa/Luanda", + "Africa/Lubumbashi", + "Africa/Lusaka", + "Africa/Malabo", + "Africa/Maputo", + "Africa/Maseru", + "Africa/Mbabane", + "Africa/Mogadishu", + "Africa/Monrovia", + "Africa/Nairobi", + "Africa/Ndjamena", + "Africa/Niamey", + "Africa/Nouakchott", + "Africa/Ouagadougou", + "Africa/Porto-Novo", + "Africa/Sao_Tome", + "Africa/Timbuktu", + "Africa/Tripoli", + "Africa/Tunis", + "Africa/Windhoek", + "America/Adak", + "America/Anchorage", + "America/Anguilla", + "America/Antigua", + "America/Araguaina", + "America/Argentina/Buenos_Aires", + "America/Argentina/Catamarca", + "America/Argentina/ComodRivadavia", + "America/Argentina/Cordoba", + "America/Argentina/Jujuy", + "America/Argentina/La_Rioja", + "America/Argentina/Mendoza", + "America/Argentina/Rio_Gallegos", + "America/Argentina/Salta", + "America/Argentina/San_Juan", + "America/Argentina/San_Luis", + "America/Argentina/Tucuman", + "America/Argentina/Ushuaia", + "America/Aruba", + "America/Asuncion", + "America/Atikokan", + "America/Atka", + "America/Bahia", + "America/Bahia_Banderas", + "America/Barbados", + "America/Belem", + "America/Belize", + "America/Blanc-Sablon", + "America/Boa_Vista", + "America/Bogota", + "America/Boise", + "America/Buenos_Aires", + "America/Cambridge_Bay", + "America/Campo_Grande", + "America/Cancun", + "America/Caracas", + "America/Catamarca", + "America/Cayenne", + "America/Cayman", + "America/Chicago", + "America/Chihuahua", + "America/Coral_Harbour", + "America/Cordoba", + "America/Costa_Rica", + "America/Creston", + "America/Cuiaba", + "America/Curacao", + "America/Danmarkshavn", + "America/Dawson", + "America/Dawson_Creek", + "America/Denver", + "America/Detroit", + "America/Dominica", + "America/Edmonton", + "America/Eirunepe", + "America/El_Salvador", + "America/Ensenada", + "America/Fort_Nelson", + "America/Fort_Wayne", + "America/Fortaleza", + "America/Glace_Bay", + "America/Godthab", + "America/Goose_Bay", + "America/Grand_Turk", + "America/Grenada", + "America/Guadeloupe", + "America/Guatemala", + "America/Guayaquil", + "America/Guyana", + "America/Halifax", + "America/Havana", + "America/Hermosillo", + "America/Indiana/Indianapolis", + "America/Indiana/Knox", + "America/Indiana/Marengo", + "America/Indiana/Petersburg", + "America/Indiana/Tell_City", + "America/Indiana/Vevay", + "America/Indiana/Vincennes", + "America/Indiana/Winamac", + "America/Indianapolis", + "America/Inuvik", + "America/Iqaluit", + "America/Jamaica", + "America/Jujuy", + "America/Juneau", + "America/Kentucky/Louisville", + "America/Kentucky/Monticello", + "America/Knox_IN", + "America/Kralendijk", + "America/La_Paz", + "America/Lima", + "America/Los_Angeles", + "America/Louisville", + "America/Lower_Princes", + "America/Maceio", + "America/Managua", + "America/Manaus", + "America/Marigot", + "America/Martinique", + "America/Matamoros", + "America/Mazatlan", + "America/Mendoza", + "America/Menominee", + "America/Merida", + "America/Metlakatla", + "America/Mexico_City", + "America/Miquelon", + "America/Moncton", + "America/Monterrey", + "America/Montevideo", + "America/Montreal", + "America/Montserrat", + "America/Nassau", + "America/New_York", + "America/Nipigon", + "America/Nome", + "America/Noronha", + "America/North_Dakota/Beulah", + "America/North_Dakota/Center", + "America/North_Dakota/New_Salem", + "America/Ojinaga", + "America/Panama", + "America/Pangnirtung", + "America/Paramaribo", + "America/Phoenix", + "America/Port-au-Prince", + "America/Port_of_Spain", + "America/Porto_Acre", + "America/Porto_Velho", + "America/Puerto_Rico", + "America/Punta_Arenas", + "America/Rainy_River", + "America/Rankin_Inlet", + "America/Recife", + "America/Regina", + "America/Resolute", + "America/Rio_Branco", + "America/Rosario", + "America/Santa_Isabel", + "America/Santarem", + "America/Santiago", + "America/Santo_Domingo", + "America/Sao_Paulo", + "America/Scoresbysund", + "America/Shiprock", + "America/Sitka", + "America/St_Barthelemy", + "America/St_Johns", + "America/St_Kitts", + "America/St_Lucia", + "America/St_Thomas", + "America/St_Vincent", + "America/Swift_Current", + "America/Tegucigalpa", + "America/Thule", + "America/Thunder_Bay", + "America/Tijuana", + "America/Toronto", + "America/Tortola", + "America/Vancouver", + "America/Virgin", + "America/Whitehorse", + "America/Winnipeg", + "America/Yakutat", + "America/Yellowknife", + "Antarctica/Casey", + "Antarctica/Davis", + "Antarctica/DumontDUrville", + "Antarctica/Macquarie", + "Antarctica/Mawson", + "Antarctica/McMurdo", + "Antarctica/Palmer", + "Antarctica/Rothera", + "Antarctica/South_Pole", + "Antarctica/Syowa", + "Antarctica/Troll", + "Antarctica/Vostok", + "Arctic/Longyearbyen", + "Asia/Aden", + "Asia/Almaty", + "Asia/Amman", + "Asia/Anadyr", + "Asia/Aqtau", + "Asia/Aqtobe", + "Asia/Ashgabat", + "Asia/Ashkhabad", + "Asia/Atyrau", + "Asia/Baghdad", + "Asia/Bahrain", + "Asia/Baku", + "Asia/Bangkok", + "Asia/Barnaul", + "Asia/Beirut", + "Asia/Bishkek", + "Asia/Brunei", + "Asia/Calcutta", + "Asia/Chita", + "Asia/Choibalsan", + "Asia/Chongqing", + "Asia/Chungking", + "Asia/Colombo", + "Asia/Dacca", + "Asia/Damascus", + "Asia/Dhaka", + "Asia/Dili", + "Asia/Dubai", + "Asia/Dushanbe", + "Asia/Famagusta", + "Asia/Gaza", + "Asia/Harbin", + "Asia/Hebron", + "Asia/Ho_Chi_Minh", + "Asia/Hong_Kong", + "Asia/Hovd", + "Asia/Irkutsk", + "Asia/Istanbul", + "Asia/Jakarta", + "Asia/Jayapura", + "Asia/Jerusalem", + "Asia/Kabul", + "Asia/Kamchatka", + "Asia/Karachi", + "Asia/Kashgar", + "Asia/Kathmandu", + "Asia/Katmandu", + "Asia/Khandyga", + "Asia/Kolkata", + "Asia/Krasnoyarsk", + "Asia/Kuala_Lumpur", + "Asia/Kuching", + "Asia/Kuwait", + "Asia/Macao", + "Asia/Macau", + "Asia/Magadan", + "Asia/Makassar", + "Asia/Manila", + "Asia/Muscat", + "Asia/Nicosia", + "Asia/Novokuznetsk", + "Asia/Novosibirsk", + "Asia/Omsk", + "Asia/Oral", + "Asia/Phnom_Penh", + "Asia/Pontianak", + "Asia/Pyongyang", + "Asia/Qatar", + "Asia/Qyzylorda", + "Asia/Rangoon", + "Asia/Riyadh", + "Asia/Saigon", + "Asia/Sakhalin", + "Asia/Samarkand", + "Asia/Seoul", + "Asia/Shanghai", + "Asia/Singapore", + "Asia/Srednekolymsk", + "Asia/Taipei", + "Asia/Tashkent", + "Asia/Tbilisi", + "Asia/Tehran", + "Asia/Tel_Aviv", + "Asia/Thimbu", + "Asia/Thimphu", + "Asia/Tokyo", + "Asia/Tomsk", + "Asia/Ujung_Pandang", + "Asia/Ulaanbaatar", + "Asia/Ulan_Bator", + "Asia/Urumqi", + "Asia/Ust-Nera", + "Asia/Vientiane", + "Asia/Vladivostok", + "Asia/Yakutsk", + "Asia/Yangon", + "Asia/Yekaterinburg", + "Asia/Yerevan", + "Atlantic/Azores", + "Atlantic/Bermuda", + "Atlantic/Canary", + "Atlantic/Cape_Verde", + "Atlantic/Faeroe", + "Atlantic/Faroe", + "Atlantic/Jan_Mayen", + "Atlantic/Madeira", + "Atlantic/Reykjavik", + "Atlantic/South_Georgia", + "Atlantic/St_Helena", + "Atlantic/Stanley", + "Australia/ACT", + "Australia/Adelaide", + "Australia/Brisbane", + "Australia/Broken_Hill", + "Australia/Canberra", + "Australia/Currie", + "Australia/Darwin", + "Australia/Eucla", + "Australia/Hobart", + "Australia/LHI", + "Australia/Lindeman", + "Australia/Lord_Howe", + "Australia/Melbourne", + "Australia/NSW", + "Australia/North", + "Australia/Perth", + "Australia/Queensland", + "Australia/South", + "Australia/Sydney", + "Australia/Tasmania", + "Australia/Victoria", + "Australia/West", + "Australia/Yancowinna", + "Brazil/Acre", + "Brazil/DeNoronha", + "Brazil/East", + "Brazil/West", + "CET", + "CST6CDT", + "Canada/Atlantic", + "Canada/Central", + "Canada/East-Saskatchewan", + "Canada/Eastern", + "Canada/Mountain", + "Canada/Newfoundland", + "Canada/Pacific", + "Canada/Saskatchewan", + "Canada/Yukon", + "Chile/Continental", + "Chile/EasterIsland", + "Cuba", + "EET", + "EST", + "EST5EDT", + "Egypt", + "Eire", + "Etc/GMT", + "Etc/GMT+0", + "Etc/GMT+1", + "Etc/GMT+10", + "Etc/GMT+11", + "Etc/GMT+12", + "Etc/GMT+2", + "Etc/GMT+3", + "Etc/GMT+4", + "Etc/GMT+5", + "Etc/GMT+6", + "Etc/GMT+7", + "Etc/GMT+8", + "Etc/GMT+9", + "Etc/GMT-0", + "Etc/GMT-1", + "Etc/GMT-10", + "Etc/GMT-11", + "Etc/GMT-12", + "Etc/GMT-13", + "Etc/GMT-14", + "Etc/GMT-2", + "Etc/GMT-3", + "Etc/GMT-4", + "Etc/GMT-5", + "Etc/GMT-6", + "Etc/GMT-7", + "Etc/GMT-8", + "Etc/GMT-9", + "Etc/GMT0", + "Etc/Greenwich", + "Etc/UCT", + "Etc/UTC", + "Etc/Universal", + "Etc/Zulu", + "Europe/Amsterdam", + "Europe/Andorra", + "Europe/Astrakhan", + "Europe/Athens", + "Europe/Belfast", + "Europe/Belgrade", + "Europe/Berlin", + "Europe/Bratislava", + "Europe/Brussels", + "Europe/Bucharest", + "Europe/Budapest", + "Europe/Busingen", + "Europe/Chisinau", + "Europe/Copenhagen", + "Europe/Dublin", + "Europe/Gibraltar", + "Europe/Guernsey", + "Europe/Helsinki", + "Europe/Isle_of_Man", + "Europe/Istanbul", + "Europe/Jersey", + "Europe/Kaliningrad", + "Europe/Kiev", + "Europe/Kirov", + "Europe/Lisbon", + "Europe/Ljubljana", + "Europe/London", + "Europe/Luxembourg", + "Europe/Madrid", + "Europe/Malta", + "Europe/Mariehamn", + "Europe/Minsk", + "Europe/Monaco", + "Europe/Moscow", + "Europe/Nicosia", + "Europe/Oslo", + "Europe/Paris", + "Europe/Podgorica", + "Europe/Prague", + "Europe/Riga", + "Europe/Rome", + "Europe/Samara", + "Europe/San_Marino", + "Europe/Sarajevo", + "Europe/Saratov", + "Europe/Simferopol", + "Europe/Skopje", + "Europe/Sofia", + "Europe/Stockholm", + "Europe/Tallinn", + "Europe/Tirane", + "Europe/Tiraspol", + "Europe/Ulyanovsk", + "Europe/Uzhgorod", + "Europe/Vaduz", + "Europe/Vatican", + "Europe/Vienna", + "Europe/Vilnius", + "Europe/Volgograd", + "Europe/Warsaw", + "Europe/Zagreb", + "Europe/Zaporozhye", + "Europe/Zurich", + "Factory", + "GB", + "GB-Eire", + "GMT", + "GMT+0", + "GMT-0", + "GMT0", + "Greenwich", + "HST", + "Hongkong", + "Iceland", + "Indian/Antananarivo", + "Indian/Chagos", + "Indian/Christmas", + "Indian/Cocos", + "Indian/Comoro", + "Indian/Kerguelen", + "Indian/Mahe", + "Indian/Maldives", + "Indian/Mauritius", + "Indian/Mayotte", + "Indian/Reunion", + "Iran", + "Israel", + "Jamaica", + "Japan", + "Kwajalein", + "Libya", + "MET", + "MST", + "MST7MDT", + "Mexico/BajaNorte", + "Mexico/BajaSur", + "Mexico/General", + "NZ", + "NZ-CHAT", + "Navajo", + "PRC", + "PST8PDT", + "Pacific/Apia", + "Pacific/Auckland", + "Pacific/Bougainville", + "Pacific/Chatham", + "Pacific/Chuuk", + "Pacific/Easter", + "Pacific/Efate", + "Pacific/Enderbury", + "Pacific/Fakaofo", + "Pacific/Fiji", + "Pacific/Funafuti", + "Pacific/Galapagos", + "Pacific/Gambier", + "Pacific/Guadalcanal", + "Pacific/Guam", + "Pacific/Honolulu", + "Pacific/Johnston", + "Pacific/Kiritimati", + "Pacific/Kosrae", + "Pacific/Kwajalein", + "Pacific/Majuro", + "Pacific/Marquesas", + "Pacific/Midway", + "Pacific/Nauru", + "Pacific/Niue", + "Pacific/Norfolk", + "Pacific/Noumea", + "Pacific/Pago_Pago", + "Pacific/Palau", + "Pacific/Pitcairn", + "Pacific/Pohnpei", + "Pacific/Ponape", + "Pacific/Port_Moresby", + "Pacific/Rarotonga", + "Pacific/Saipan", + "Pacific/Samoa", + "Pacific/Tahiti", + "Pacific/Tarawa", + "Pacific/Tongatapu", + "Pacific/Truk", + "Pacific/Wake", + "Pacific/Wallis", + "Pacific/Yap", + "Poland", + "Portugal", + "ROC", + "ROK", + "Singapore", + "Turkey", + "UCT", + "US/Alaska", + "US/Aleutian", + "US/Arizona", + "US/Central", + "US/East-Indiana", + "US/Eastern", + "US/Hawaii", + "US/Indiana-Starke", + "US/Michigan", + "US/Mountain", + "US/Pacific", + "US/Pacific-New", + "US/Samoa", + "UTC", + "Universal", + "W-SU", + "WET", + "Zulu", +} diff --git a/model/user.go b/model/user.go index 59472d8e5..c3a6ec04a 100644 --- a/model/user.go +++ b/model/user.go @@ -71,6 +71,7 @@ type User struct { LastPictureUpdate int64 `json:"last_picture_update,omitempty"` FailedAttempts int `json:"failed_attempts,omitempty"` Locale string `json:"locale"` + Timezone StringMap `json:"timezone"` MfaActive bool `json:"mfa_active,omitempty"` MfaSecret string `json:"mfa_secret,omitempty"` LastActivityAt int64 `db:"-" json:"last_activity_at,omitempty"` @@ -86,6 +87,7 @@ type UserPatch struct { Props StringMap `json:"props,omitempty"` NotifyProps StringMap `json:"notify_props,omitempty"` Locale *string `json:"locale"` + Timezone StringMap `json:"timezone"` } type UserAuth struct { @@ -208,6 +210,10 @@ func (u *User) PreSave() { u.SetDefaultNotifications() } + if u.Timezone == nil { + u.Props = make(map[string]string) + } + if len(u.Password) > 0 { u.Password = HashPassword(u.Password) } @@ -302,6 +308,10 @@ func (u *User) Patch(patch *UserPatch) { if patch.Locale != nil { u.Locale = *patch.Locale } + + if patch.Timezone != nil { + u.Timezone = patch.Timezone + } } // ToJson convert a User to a json string diff --git a/store/sqlstore/upgrade.go b/store/sqlstore/upgrade.go index 2b4532817..174b434f8 100644 --- a/store/sqlstore/upgrade.go +++ b/store/sqlstore/upgrade.go @@ -4,6 +4,7 @@ package sqlstore import ( + "encoding/json" "os" "strings" "time" @@ -380,6 +381,12 @@ func UpgradeDatabaseToVersion49(sqlStore SqlStore) { //TODO: Uncomment the following condition when version 4.9.0 is released //if shouldPerformUpgrade(sqlStore, VERSION_4_8_0, VERSION_4_9_0) { sqlStore.CreateColumnIfNotExists("Teams", "LastTeamIconUpdate", "bigint", "bigint", "0") + defaultTimezone := model.DefaultUserTimezone() + defaultTimezoneValue, err := json.Marshal(defaultTimezone) + if err != nil { + l4g.Critical(err) + } + sqlStore.CreateColumnIfNotExists("Users", "Timezone", "varchar(256)", "varchar(256)", string(defaultTimezoneValue)) // saveSchemaVersion(sqlStore, VERSION_4_9_0) //} } diff --git a/store/sqlstore/user_store.go b/store/sqlstore/user_store.go index 5e84af930..f4ed3e400 100644 --- a/store/sqlstore/user_store.go +++ b/store/sqlstore/user_store.go @@ -79,6 +79,7 @@ func NewSqlUserStore(sqlStore SqlStore, metrics einterfaces.MetricsInterface) st table.ColMap("Locale").SetMaxSize(5) table.ColMap("MfaSecret").SetMaxSize(128) table.ColMap("Position").SetMaxSize(128) + table.ColMap("Timezone").SetMaxSize(256) } return us diff --git a/utils/config.go b/utils/config.go index 8befef94d..35a21dea4 100644 --- a/utils/config.go +++ b/utils/config.go @@ -9,7 +9,6 @@ import ( "io" "io/ioutil" "os" - "path" "path/filepath" "strconv" "strings" @@ -51,21 +50,17 @@ func FindConfigFile(fileName string) (path string) { return "" } +// FindDir looks for the given directory in nearby ancestors, falling back to `./` if not found. func FindDir(dir string) (string, bool) { - fileName := "." - found := false - if _, err := os.Stat("./" + dir + "/"); err == nil { - fileName, _ = filepath.Abs("./" + dir + "/") - found = true - } else if _, err := os.Stat("../" + dir + "/"); err == nil { - fileName, _ = filepath.Abs("../" + dir + "/") - found = true - } else if _, err := os.Stat("../../" + dir + "/"); err == nil { - fileName, _ = filepath.Abs("../../" + dir + "/") - found = true + for _, parent := range []string{".", "..", "../.."} { + foundDir, err := filepath.Abs(filepath.Join(parent, dir)) + if err != nil { + continue + } else if _, err := os.Stat(foundDir); err == nil { + return foundDir, true + } } - - return fileName + "/", found + return "./", false } func DisableDebugLogForTest() { @@ -136,11 +131,10 @@ func ConfigureLog(s *model.LogSettings) { func GetLogFileLocation(fileLocation string) string { if fileLocation == "" { - logDir, _ := FindDir("logs") - return logDir + LOG_FILENAME - } else { - return path.Join(fileLocation, LOG_FILENAME) + fileLocation, _ = FindDir("logs") } + + return filepath.Join(fileLocation, LOG_FILENAME) } func SaveConfig(fileName string, config *model.Config) *model.AppError { @@ -414,8 +408,6 @@ func GenerateClientConfig(c *model.Config, diagnosticId string, license *model.L props["SupportEmail"] = *c.SupportSettings.SupportEmail props["EnableFileAttachments"] = strconv.FormatBool(*c.FileSettings.EnableFileAttachments) - props["EnableMobileFileUpload"] = strconv.FormatBool(*c.FileSettings.EnableMobileUpload) - props["EnableMobileFileDownload"] = strconv.FormatBool(*c.FileSettings.EnableMobileDownload) props["EnablePublicLink"] = strconv.FormatBool(c.FileSettings.EnablePublicLink) props["WebsocketPort"] = fmt.Sprintf("%v", *c.ServiceSettings.WebsocketPort) @@ -449,8 +441,55 @@ func GenerateClientConfig(c *model.Config, diagnosticId string, license *model.L hasImageProxy := c.ServiceSettings.ImageProxyType != nil && *c.ServiceSettings.ImageProxyType != "" && c.ServiceSettings.ImageProxyURL != nil && *c.ServiceSettings.ImageProxyURL != "" props["HasImageProxy"] = strconv.FormatBool(hasImageProxy) + // Set default values for all options that require a license. + props["ExperimentalTownSquareIsReadOnly"] = "false" + props["ExperimentalEnableAuthenticationTransfer"] = "true" + props["EnableCustomBrand"] = "false" + props["CustomBrandText"] = "" + props["CustomDescriptionText"] = "" + props["EnableLdap"] = "false" + props["LdapLoginFieldName"] = "" + props["LdapNicknameAttributeSet"] = "false" + props["LdapFirstNameAttributeSet"] = "false" + props["LdapLastNameAttributeSet"] = "false" + props["LdapLoginButtonColor"] = "" + props["LdapLoginButtonBorderColor"] = "" + props["LdapLoginButtonTextColor"] = "" + props["EnableMultifactorAuthentication"] = "false" + props["EnforceMultifactorAuthentication"] = "false" + props["EnableCompliance"] = "false" + props["EnableMobileFileDownload"] = "true" + props["EnableMobileFileUpload"] = "true" + props["EnableSaml"] = "false" + props["SamlLoginButtonText"] = "" + props["SamlFirstNameAttributeSet"] = "false" + props["SamlLastNameAttributeSet"] = "false" + props["SamlNicknameAttributeSet"] = "false" + props["SamlLoginButtonColor"] = "" + props["SamlLoginButtonBorderColor"] = "" + props["SamlLoginButtonTextColor"] = "" + props["EnableCluster"] = "false" + props["EnableMetrics"] = "false" + props["EnableSignUpWithGoogle"] = "false" + props["EnableSignUpWithOffice365"] = "false" + props["PasswordMinimumLength"] = "0" + props["PasswordRequireLowercase"] = "false" + props["PasswordRequireUppercase"] = "false" + props["PasswordRequireNumber"] = "false" + props["PasswordRequireSymbol"] = "false" + props["EnableBanner"] = "false" + props["BannerText"] = "" + props["BannerColor"] = "" + props["BannerTextColor"] = "" + props["AllowBannerDismissal"] = "false" props["EnableThemeSelection"] = "true" + props["DefaultTheme"] = "" props["AllowCustomThemes"] = "true" + props["AllowedThemes"] = "" + props["DataRetentionEnableMessageDeletion"] = "false" + props["DataRetentionMessageRetentionDays"] = "0" + props["DataRetentionEnableFileDeletion"] = "false" + props["DataRetentionFileRetentionDays"] = "0" if license != nil { props["ExperimentalTownSquareIsReadOnly"] = strconv.FormatBool(*c.TeamSettings.ExperimentalTownSquareIsReadOnly) @@ -480,6 +519,8 @@ func GenerateClientConfig(c *model.Config, diagnosticId string, license *model.L if *license.Features.Compliance { props["EnableCompliance"] = strconv.FormatBool(*c.ComplianceSettings.Enable) + props["EnableMobileFileDownload"] = strconv.FormatBool(*c.FileSettings.EnableMobileDownload) + props["EnableMobileFileUpload"] = strconv.FormatBool(*c.FileSettings.EnableMobileUpload) } if *license.Features.SAML { diff --git a/utils/config_test.go b/utils/config_test.go index 5809422f1..f816e2ee8 100644 --- a/utils/config_test.go +++ b/utils/config_test.go @@ -12,6 +12,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/mattermost/mattermost-server/model" ) func TestConfig(t *testing.T) { @@ -21,6 +23,15 @@ func TestConfig(t *testing.T) { InitTranslations(cfg.LocalizationSettings) } +func TestTimezoneConfig(t *testing.T) { + TranslationsPreInit() + supportedTimezones := LoadTimezones("timezones.json") + assert.Equal(t, len(supportedTimezones) > 0, true) + + supportedTimezones2 := LoadTimezones("timezones_file_does_not_exists.json") + assert.Equal(t, len(supportedTimezones2) > 0, true) +} + func TestFindConfigFile(t *testing.T) { dir, err := ioutil.TempDir("", "") require.NoError(t, err) @@ -193,14 +204,97 @@ func TestValidateLocales(t *testing.T) { } func TestGetClientConfig(t *testing.T) { - TranslationsPreInit() - cfg, _, err := LoadConfig("config.json") - require.Nil(t, err) + t.Parallel() + testCases := []struct { + description string + config *model.Config + diagnosticId string + license *model.License + expectedFields map[string]string + }{ + { + "unlicensed", + &model.Config{ + EmailSettings: model.EmailSettings{ + EmailNotificationContentsType: sToP(model.EMAIL_NOTIFICATION_CONTENTS_FULL), + }, + ThemeSettings: model.ThemeSettings{ + // Ignored, since not licensed. + AllowCustomThemes: bToP(false), + }, + }, + "", + nil, + map[string]string{ + "DiagnosticId": "", + "EmailNotificationContentsType": "full", + "AllowCustomThemes": "true", + }, + }, + { + "licensed, but not for theme management", + &model.Config{ + EmailSettings: model.EmailSettings{ + EmailNotificationContentsType: sToP(model.EMAIL_NOTIFICATION_CONTENTS_FULL), + }, + ThemeSettings: model.ThemeSettings{ + // Ignored, since not licensed. + AllowCustomThemes: bToP(false), + }, + }, + "tag1", + &model.License{ + Features: &model.Features{ + ThemeManagement: bToP(false), + }, + }, + map[string]string{ + "DiagnosticId": "tag1", + "EmailNotificationContentsType": "full", + "AllowCustomThemes": "true", + }, + }, + { + "licensed for theme management", + &model.Config{ + EmailSettings: model.EmailSettings{ + EmailNotificationContentsType: sToP(model.EMAIL_NOTIFICATION_CONTENTS_FULL), + }, + ThemeSettings: model.ThemeSettings{ + AllowCustomThemes: bToP(false), + }, + }, + "tag2", + &model.License{ + Features: &model.Features{ + ThemeManagement: bToP(true), + }, + }, + map[string]string{ + "DiagnosticId": "tag2", + "EmailNotificationContentsType": "full", + "AllowCustomThemes": "false", + }, + }, + } - configMap := GenerateClientConfig(cfg, "", nil) - if configMap["EmailNotificationContentsType"] != *cfg.EmailSettings.EmailNotificationContentsType { - t.Fatal("EmailSettings.EmailNotificationContentsType not exposed to client config") + for _, testCase := range testCases { + testCase := testCase + t.Run(testCase.description, func(t *testing.T) { + t.Parallel() + + testCase.config.SetDefaults() + if testCase.license != nil { + testCase.license.Features.SetDefaults() + } + + configMap := GenerateClientConfig(testCase.config, testCase.diagnosticId, testCase.license) + for expectedField, expectedValue := range testCase.expectedFields { + assert.Equal(t, expectedValue, configMap[expectedField]) + } + }) } + } func TestReadConfig(t *testing.T) { @@ -213,3 +307,11 @@ func TestReadConfig(t *testing.T) { assert.Equal(t, "http://foo.bar", *config.ServiceSettings.SiteURL) } + +func sToP(s string) *string { + return &s +} + +func bToP(b bool) *bool { + return &b +} diff --git a/utils/html.go b/utils/html.go index 6bbe55c6d..f9a7abe5b 100644 --- a/utils/html.go +++ b/utils/html.go @@ -5,8 +5,10 @@ package utils import ( "bytes" + "errors" "html/template" "io" + "path/filepath" "reflect" "sync/atomic" @@ -39,7 +41,7 @@ func NewHTMLTemplateWatcher(directory string) (*HTMLTemplateWatcher, error) { return nil, err } - if htmlTemplates, err := template.ParseGlob(templatesDir + "*.html"); err != nil { + if htmlTemplates, err := template.ParseGlob(filepath.Join(templatesDir, "*.html")); err != nil { return nil, err } else { ret.templates.Store(htmlTemplates) @@ -56,7 +58,7 @@ func NewHTMLTemplateWatcher(directory string) (*HTMLTemplateWatcher, error) { case event := <-watcher.Events: if event.Op&fsnotify.Write == fsnotify.Write { l4g.Info("Re-parsing templates because of modified file %v", event.Name) - if htmlTemplates, err := template.ParseGlob(templatesDir + "*.html"); err != nil { + if htmlTemplates, err := template.ParseGlob(filepath.Join(templatesDir, "*.html")); err != nil { l4g.Error("Failed to parse templates %v", err) } else { ret.templates.Store(htmlTemplates) @@ -103,6 +105,10 @@ func (t *HTMLTemplate) Render() string { } func (t *HTMLTemplate) RenderToWriter(w io.Writer) error { + if t.Templates == nil { + return errors.New("no html templates") + } + if err := t.Templates.ExecuteTemplate(w, t.TemplateName, t); err != nil { l4g.Error(T("api.api.render.error"), t.TemplateName, err) return err diff --git a/utils/i18n.go b/utils/i18n.go index 8ed82d19f..7b8d1fef0 100644 --- a/utils/i18n.go +++ b/utils/i18n.go @@ -23,13 +23,15 @@ var settings model.LocalizationSettings // this functions loads translations from filesystem // and assign english while loading server config func TranslationsPreInit() error { + // Set T even if we fail to load the translations. Lots of shutdown handling code will + // segfault trying to handle the error, and the untranslated IDs are strictly better. + T = TfuncWithFallback("en") + TDefault = TfuncWithFallback("en") + if err := InitTranslationsWithDir("i18n"); err != nil { return err } - T = TfuncWithFallback("en") - TDefault = TfuncWithFallback("en") - return nil } @@ -51,9 +53,9 @@ func InitTranslationsWithDir(dir string) error { for _, f := range files { if filepath.Ext(f.Name()) == ".json" { filename := f.Name() - locales[strings.Split(filename, ".")[0]] = i18nDirectory + filename + locales[strings.Split(filename, ".")[0]] = filepath.Join(i18nDirectory, filename) - if err := i18n.LoadTranslationFile(i18nDirectory + filename); err != nil { + if err := i18n.LoadTranslationFile(filepath.Join(i18nDirectory, filename)); err != nil { return err } } diff --git a/utils/license.go b/utils/license.go index 2853a58d0..cf874b62b 100644 --- a/utils/license.go +++ b/utils/license.go @@ -12,6 +12,7 @@ import ( "encoding/pem" "io/ioutil" "os" + "path/filepath" "strconv" "strings" @@ -114,7 +115,7 @@ func GetLicenseFileFromDisk(fileName string) []byte { func GetLicenseFileLocation(fileLocation string) string { if fileLocation == "" { configDir, _ := FindDir("config") - return configDir + "mattermost.mattermost-license" + return filepath.Join(configDir, "mattermost.mattermost-license") } else { return fileLocation } diff --git a/utils/mail.go b/utils/mail.go index c59406a18..2a2da9bf1 100644 --- a/utils/mail.go +++ b/utils/mail.go @@ -221,10 +221,10 @@ func SendMailUsingConfigAdvanced(mimeTo, smtpTo string, from mail.Address, subje return err } - return SendMail(c, mimeTo, smtpTo, from, subject, htmlBody, attachments, mimeHeaders, fileBackend) + return SendMail(c, mimeTo, smtpTo, from, subject, htmlBody, attachments, mimeHeaders, fileBackend, time.Now()) } -func SendMail(c *smtp.Client, mimeTo, smtpTo string, from mail.Address, subject, htmlBody string, attachments []*model.FileInfo, mimeHeaders map[string]string, fileBackend FileBackend) *model.AppError { +func SendMail(c *smtp.Client, mimeTo, smtpTo string, from mail.Address, subject, htmlBody string, attachments []*model.FileInfo, mimeHeaders map[string]string, fileBackend FileBackend, date time.Time) *model.AppError { l4g.Debug(T("utils.mail.send_mail.sending.debug"), mimeTo, subject) htmlMessage := "\r\n<html><body>" + htmlBody + "</body></html>" @@ -249,7 +249,7 @@ func SendMail(c *smtp.Client, mimeTo, smtpTo string, from mail.Address, subject, m := gomail.NewMessage(gomail.SetCharset("UTF-8")) m.SetHeaders(headers) - m.SetDateHeader("Date", time.Now()) + m.SetDateHeader("Date", date) m.SetBody("text/plain", txtBody) m.AddAlternative("text/html", htmlMessage) diff --git a/utils/timezone.go b/utils/timezone.go new file mode 100644 index 000000000..ea5f15140 --- /dev/null +++ b/utils/timezone.go @@ -0,0 +1,25 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package utils + +import ( + "encoding/json" + "io/ioutil" + + "github.com/mattermost/mattermost-server/model" +) + +func LoadTimezones(fileName string) model.SupportedTimezones { + var supportedTimezones model.SupportedTimezones + + if timezoneFile := FindConfigFile(fileName); timezoneFile == "" { + return model.DefaultSupportedTimezones + } else if raw, err := ioutil.ReadFile(timezoneFile); err != nil { + return model.DefaultSupportedTimezones + } else if err := json.Unmarshal(raw, &supportedTimezones); err != nil { + return model.DefaultSupportedTimezones + } else { + return supportedTimezones + } +} diff --git a/web/web.go b/web/web.go index 22fe43923..c9d318397 100644 --- a/web/web.go +++ b/web/web.go @@ -5,6 +5,7 @@ package web import ( "net/http" + "path/filepath" "strings" "github.com/NYTimes/gziphandler" @@ -102,5 +103,5 @@ func root(c *api.Context, w http.ResponseWriter, r *http.Request) { w.Header().Set("Cache-Control", "no-cache, max-age=31556926, public") staticDir, _ := utils.FindDir(model.CLIENT_DIR) - http.ServeFile(w, r, staticDir+"root.html") + http.ServeFile(w, r, filepath.Join(staticDir, "root.html")) } |