diff --git a/integrations/user_test.go b/integrations/user_test.go
index 0b59663a4f4c..7ff986d5460c 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 d71c680d6f92..d93d0ec036ab 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 522086a52092..aa9dd1310775 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 000000000000..6e7d940edc70
--- /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 2167e269b6b5..106d79ffcc21 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 d913822a8d10..956ebd944be4 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 6348a275aa14..379da8aabcef 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 6fea729c05c9..baacdfdf87a3 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 4249f9e5f922..2a5cb8e4b29f 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 2d8b53ff63e2..f4326bf0f57c 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 091c2e2c53b9..4e6930e0f596 100644
--- a/templates/user/settings/profile.tmpl
+++ b/templates/user/settings/profile.tmpl
@@ -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>
diff --git a/vendor/code.gitea.io/sdk/gitea/user.go b/vendor/code.gitea.io/sdk/gitea/user.go
index f6b687e979ee..85160c3fd98d 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 5b5b640f4720..e4f9dd5c9263 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=",