diff --git a/integrations/user_test.go b/integrations/user_test.go index 0b59663a4f..7ff986d546 100644 --- a/integrations/user_test.go +++ b/integrations/user_test.go @@ -27,9 +27,10 @@ func TestRenameUsername(t *testing.T) { session := loginUser(t, "user2") req := NewRequestWithValues(t, "POST", "/user/settings", map[string]string{ - "_csrf": GetCSRF(t, session, "/user/settings"), - "name": "newUsername", - "email": "user2@example.com", + "_csrf": GetCSRF(t, session, "/user/settings"), + "name": "newUsername", + "email": "user2@example.com", + "language": "en-us", }) session.MakeRequest(t, req, http.StatusFound) @@ -81,9 +82,10 @@ func TestRenameReservedUsername(t *testing.T) { for _, reservedUsername := range reservedUsernames { t.Logf("Testing username %s", reservedUsername) req := NewRequestWithValues(t, "POST", "/user/settings", map[string]string{ - "_csrf": GetCSRF(t, session, "/user/settings"), - "name": reservedUsername, - "email": "user2@example.com", + "_csrf": GetCSRF(t, session, "/user/settings"), + "name": reservedUsername, + "email": "user2@example.com", + "language": "en-us", }) resp := session.MakeRequest(t, req, http.StatusFound) diff --git a/integrations/xss_test.go b/integrations/xss_test.go index d71c680d6f..d93d0ec036 100644 --- a/integrations/xss_test.go +++ b/integrations/xss_test.go @@ -24,6 +24,7 @@ func TestXSSUserFullName(t *testing.T) { "name": user.Name, "full_name": fullName, "email": user.Email, + "language": "en-us", }) session.MakeRequest(t, req, http.StatusFound) diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index 522086a520..aa9dd13107 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -178,6 +178,8 @@ var migrations = []Migration{ NewMigration("add size column for attachments", addSizeToAttachment), // v62 -> v63 NewMigration("add last used passcode column for TOTP", addLastUsedPasscodeTOTP), + // v63 -> v64 + NewMigration("add language column for user setting", addLanguageSetting), } // Migrate database to current version diff --git a/models/migrations/v63.go b/models/migrations/v63.go new file mode 100644 index 0000000000..6e7d940edc --- /dev/null +++ b/models/migrations/v63.go @@ -0,0 +1,23 @@ +// Copyright 2018 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package migrations + +import ( + "fmt" + + "github.com/go-xorm/xorm" +) + +func addLanguageSetting(x *xorm.Engine) error { + type User struct { + Language string `xorm:"VARCHAR(5)"` + } + + if err := x.Sync2(new(User)); err != nil { + return fmt.Errorf("Sync2: %v", err) + } + + return nil +} diff --git a/models/user.go b/models/user.go index 2167e269b6..106d79ffcc 100644 --- a/models/user.go +++ b/models/user.go @@ -94,6 +94,7 @@ type User struct { Website string Rands string `xorm:"VARCHAR(10)"` Salt string `xorm:"VARCHAR(10)"` + Language string `xorm:"VARCHAR(5)"` CreatedUnix util.TimeStamp `xorm:"INDEX created"` UpdatedUnix util.TimeStamp `xorm:"INDEX updated"` @@ -185,6 +186,7 @@ func (u *User) APIFormat() *api.User { FullName: u.FullName, Email: u.getEmail(), AvatarURL: u.AvatarLink(), + Language: u.Language, } } diff --git a/modules/auth/user_form.go b/modules/auth/user_form.go index d913822a8d..956ebd944b 100644 --- a/modules/auth/user_form.go +++ b/modules/auth/user_form.go @@ -109,6 +109,7 @@ type UpdateProfileForm struct { KeepEmailPrivate bool Website string `binding:"ValidUrl;MaxSize(255)"` Location string `binding:"MaxSize(50)"` + Language string `binding:"Size(5)"` } // Validate validates the fields diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 6348a275aa..379da8aabc 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -331,6 +331,7 @@ change_username = Your username has been changed. change_username_prompt = Note: username changes also change your account URL. continue = Continue cancel = Cancel +language = Language lookup_avatar_by_mail = Look Up Avatar by Email Address federated_avatar_lookup = Federated Avatar Lookup diff --git a/public/swagger.v1.json b/public/swagger.v1.json index 6fea729c05..baacdfdf87 100644 --- a/public/swagger.v1.json +++ b/public/swagger.v1.json @@ -7318,6 +7318,11 @@ "format": "int64", "x-go-name": "ID" }, + "language": { + "description": "User locale", + "type": "string", + "x-go-name": "Language" + }, "login": { "description": "the user's username", "type": "string", diff --git a/routers/user/auth.go b/routers/user/auth.go index 4249f9e5f9..2a5cb8e4b2 100644 --- a/routers/user/auth.go +++ b/routers/user/auth.go @@ -339,6 +339,18 @@ func handleSignInFull(ctx *context.Context, u *models.User, remember bool, obeyR ctx.Session.Set("uid", u.ID) ctx.Session.Set("uname", u.Name) + // Language setting of the user overwrites the one previously set + // If the user does not have a locale set, we save the current one. + if len(u.Language) == 0 { + u.Language = ctx.Locale.Language() + if err := models.UpdateUserCols(u, "language"); err != nil { + log.Error(4, fmt.Sprintf("Error updating user language [user: %d, locale: %s]", u.ID, u.Language)) + return + } + } + + ctx.SetCookie("lang", u.Language, nil, setting.AppSubURL) + // Clear whatever CSRF has right now, force to generate a new one ctx.SetCookie(setting.CSRFCookieName, "", -1, setting.AppSubURL) @@ -704,6 +716,7 @@ func SignOut(ctx *context.Context) { ctx.SetCookie(setting.CookieUserName, "", -1, setting.AppSubURL) ctx.SetCookie(setting.CookieRememberName, "", -1, setting.AppSubURL) ctx.SetCookie(setting.CSRFCookieName, "", -1, setting.AppSubURL) + ctx.SetCookie("lang", "", -1, setting.AppSubURL) // Setting the lang cookie will trigger the middleware to reset the language ot previous state. ctx.Redirect(setting.AppSubURL + "/") } diff --git a/routers/user/setting.go b/routers/user/setting.go index 2d8b53ff63..f4326bf0f5 100644 --- a/routers/user/setting.go +++ b/routers/user/setting.go @@ -12,6 +12,7 @@ import ( "strings" "github.com/Unknwon/com" + "github.com/Unknwon/i18n" "github.com/pquerna/otp" "github.com/pquerna/otp/totp" @@ -105,6 +106,7 @@ func SettingsPost(ctx *context.Context, form auth.UpdateProfileForm) { ctx.User.KeepEmailPrivate = form.KeepEmailPrivate ctx.User.Website = form.Website ctx.User.Location = form.Location + ctx.User.Language = form.Language if err := models.UpdateUserSetting(ctx.User); err != nil { if _, ok := err.(models.ErrEmailAlreadyUsed); ok { ctx.Flash.Error(ctx.Tr("form.email_been_used")) @@ -115,8 +117,11 @@ func SettingsPost(ctx *context.Context, form auth.UpdateProfileForm) { return } + // Update the language to the one we just set + ctx.SetCookie("lang", ctx.User.Language, nil, setting.AppSubURL) + log.Trace("User settings updated: %s", ctx.User.Name) - ctx.Flash.Success(ctx.Tr("settings.update_profile_success")) + ctx.Flash.Success(i18n.Tr(ctx.User.Language, "settings.update_profile_success")) ctx.Redirect(setting.AppSubURL + "/user/settings") } diff --git a/templates/user/settings/profile.tmpl b/templates/user/settings/profile.tmpl index 091c2e2c53..4e6930e0f5 100644 --- a/templates/user/settings/profile.tmpl +++ b/templates/user/settings/profile.tmpl @@ -40,6 +40,20 @@ +
+ + +
+
diff --git a/vendor/code.gitea.io/sdk/gitea/user.go b/vendor/code.gitea.io/sdk/gitea/user.go index f6b687e979..85160c3fd9 100644 --- a/vendor/code.gitea.io/sdk/gitea/user.go +++ b/vendor/code.gitea.io/sdk/gitea/user.go @@ -22,6 +22,8 @@ type User struct { Email string `json:"email"` // URL to the user's avatar AvatarURL string `json:"avatar_url"` + // User locale + Language string `json:"language"` } // MarshalJSON implements the json.Marshaler interface for User, adding field(s) for backward compatibility diff --git a/vendor/vendor.json b/vendor/vendor.json index 5b5b640f47..e4f9dd5c92 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -9,10 +9,10 @@ "revisionTime": "2018-04-21T01:08:19Z" }, { - "checksumSHA1": "xXzi8Xx7HA3M0z3lR/1wr1Vz1fc=", + "checksumSHA1": "WMD6+Qh2+5hd9uiq910pF/Ihylw=", "path": "code.gitea.io/sdk/gitea", - "revision": "142acef5ce79f78585afcce31748af46c72a3dea", - "revisionTime": "2018-04-17T00:54:29Z" + "revision": "1c8d12f79a51605ed91587aa6b86cf38fc0f987f", + "revisionTime": "2018-05-01T11:15:19Z" }, { "checksumSHA1": "bOODD4Gbw3GfcuQPU2dI40crxxk=",