* Added user language setting * Added translation string for setting * Fixed import order + typo * improved checking if the user has a language saved in the db * The current saved language is now set a default inside the dropdown * fmt * When a user signs in and doesn't have a language saved, the current browser language is saved * updated gitea-sdk * Merge branch 'master' of https://github.com/go-gitea/gitea into save-user-language # Conflicts: # models/migrations/migrations.go # models/migrations/v62.go * Made tests work again * trigger CI * trigger CI * fmt * re-trigger that FUCKING CI SO IT REALLY PICKS UP THE LATEST COMMIT ISTEAD OF PREDENDING TO DO SO * re-trigger that FUCKING CI SO IT REALLY PICKS UP THE LATEST COMMIT ISTEAD OF PREDENDING TO DO SO * When loggin in, only the language col gets updated instead of everythingtags/v1.5.0-dev
@@ -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) | |||
@@ -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) | |||
@@ -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 |
@@ -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 | |||
} |
@@ -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, | |||
} | |||
} | |||
@@ -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 |
@@ -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 |
@@ -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", |
@@ -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 + "/") | |||
} | |||
@@ -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") | |||
} | |||
@@ -40,6 +40,20 @@ | |||
<input id="location" name="location" value="{{.SignedUser.Location}}"> | |||
</div> | |||
<div class="field"> | |||
<label for="language">{{.i18n.Tr "settings.language"}}</label> | |||
<div class="ui language selection dropdown" id="language"> | |||
<input name="language" type="hidden"> | |||
<i class="dropdown icon"></i> | |||
<div class="text">{{range .AllLangs}}{{if eq $.SignedUser.Language .Lang}}{{.Name}}{{end}}{{end}}</div> | |||
<div class="menu"> | |||
{{range .AllLangs}} | |||
<div class="item{{if eq $.SignedUser.Language .Lang}} active selected{{end}}" data-value="{{.Lang}}">{{.Name}}</div> | |||
{{end}} | |||
</div> | |||
</div> | |||
</div> | |||
<div class="field"> | |||
<button class="ui green button">{{$.i18n.Tr "settings.update_profile"}}</button> | |||
</div> |
@@ -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 |
@@ -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=", |