diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index 31d5bf08c6..309ff99c43 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -490,6 +490,7 @@ form.name_chars_not_allowed = User name '%s' contains invalid characters.
 [settings]
 profile = Profile
 account = Account
+appearance = Appearance
 password = Password
 security = Security
 avatar = Avatar
@@ -514,7 +515,9 @@ website = Website
 location = Location
 update_theme = Update Theme
 update_profile = Update Profile
+update_language = Update Language
 update_language_not_found = Language '%s' is not available.
+update_language_success = Language has been updated.
 update_profile_success = Your profile has been updated.
 change_username = Your username has been changed.
 change_username_prompt = Note: username changes also change your account URL.
diff --git a/routers/web/user/setting/account.go b/routers/web/user/setting/account.go
index 249793578a..47014dc814 100644
--- a/routers/web/user/setting/account.go
+++ b/routers/web/user/setting/account.go
@@ -257,34 +257,6 @@ func DeleteAccount(ctx *context.Context) {
 	}
 }
 
-// UpdateUIThemePost is used to update users' specific theme
-func UpdateUIThemePost(ctx *context.Context) {
-	form := web.GetForm(ctx).(*forms.UpdateThemeForm)
-	ctx.Data["Title"] = ctx.Tr("settings")
-	ctx.Data["PageIsSettingsAccount"] = true
-
-	if ctx.HasError() {
-		ctx.Redirect(setting.AppSubURL + "/user/settings/account")
-		return
-	}
-
-	if !form.IsThemeExists() {
-		ctx.Flash.Error(ctx.Tr("settings.theme_update_error"))
-		ctx.Redirect(setting.AppSubURL + "/user/settings/account")
-		return
-	}
-
-	if err := ctx.User.UpdateTheme(form.Theme); err != nil {
-		ctx.Flash.Error(ctx.Tr("settings.theme_update_error"))
-		ctx.Redirect(setting.AppSubURL + "/user/settings/account")
-		return
-	}
-
-	log.Trace("Update user theme: %s", ctx.User.Name)
-	ctx.Flash.Success(ctx.Tr("settings.theme_update_success"))
-	ctx.Redirect(setting.AppSubURL + "/user/settings/account")
-}
-
 func loadAccountData(ctx *context.Context) {
 	emlist, err := models.GetEmailAddresses(ctx.User.ID)
 	if err != nil {
diff --git a/routers/web/user/setting/profile.go b/routers/web/user/setting/profile.go
index d75149b8fc..d181ae1720 100644
--- a/routers/web/user/setting/profile.go
+++ b/routers/web/user/setting/profile.go
@@ -32,6 +32,7 @@ import (
 
 const (
 	tplSettingsProfile      base.TplName = "user/settings/profile"
+	tplSettingsAppearance   base.TplName = "user/settings/appearance"
 	tplSettingsOrganization base.TplName = "user/settings/organization"
 	tplSettingsRepositories base.TplName = "user/settings/repos"
 )
@@ -115,14 +116,6 @@ func ProfilePost(ctx *context.Context) {
 	ctx.User.KeepEmailPrivate = form.KeepEmailPrivate
 	ctx.User.Website = form.Website
 	ctx.User.Location = form.Location
-	if len(form.Language) != 0 {
-		if !util.IsStringInSlice(form.Language, setting.Langs) {
-			ctx.Flash.Error(ctx.Tr("settings.update_language_not_found", form.Language))
-			ctx.Redirect(setting.AppSubURL + "/user/settings")
-			return
-		}
-		ctx.User.Language = form.Language
-	}
 	ctx.User.Description = form.Description
 	ctx.User.KeepActivityPrivate = form.KeepActivityPrivate
 	ctx.User.Visibility = form.Visibility
@@ -329,3 +322,68 @@ func Repos(ctx *context.Context) {
 	ctx.Data["Page"] = pager
 	ctx.HTML(http.StatusOK, tplSettingsRepositories)
 }
+
+// Appearance render user's appearance settings
+func Appearance(ctx *context.Context) {
+	ctx.Data["Title"] = ctx.Tr("settings")
+	ctx.Data["PageIsSettingsAppearance"] = true
+
+	ctx.HTML(http.StatusOK, tplSettingsAppearance)
+}
+
+// UpdateUIThemePost is used to update users' specific theme
+func UpdateUIThemePost(ctx *context.Context) {
+	form := web.GetForm(ctx).(*forms.UpdateThemeForm)
+	ctx.Data["Title"] = ctx.Tr("settings")
+	ctx.Data["PageIsSettingsAppearance"] = true
+
+	if ctx.HasError() {
+		ctx.Redirect(setting.AppSubURL + "/user/settings/appearance")
+		return
+	}
+
+	if !form.IsThemeExists() {
+		ctx.Flash.Error(ctx.Tr("settings.theme_update_error"))
+		ctx.Redirect(setting.AppSubURL + "/user/settings/appearance")
+		return
+	}
+
+	if err := ctx.User.UpdateTheme(form.Theme); err != nil {
+		ctx.Flash.Error(ctx.Tr("settings.theme_update_error"))
+		ctx.Redirect(setting.AppSubURL + "/user/settings/appearance")
+		return
+	}
+
+	log.Trace("Update user theme: %s", ctx.User.Name)
+	ctx.Flash.Success(ctx.Tr("settings.theme_update_success"))
+	ctx.Redirect(setting.AppSubURL + "/user/settings/appearance")
+}
+
+// UpdateUserLang update a user's language
+func UpdateUserLang(ctx *context.Context) {
+	form := web.GetForm(ctx).(*forms.UpdateLanguageForm)
+	ctx.Data["Title"] = ctx.Tr("settings")
+	ctx.Data["PageIsSettingsAppearance"] = true
+
+	if len(form.Language) != 0 {
+		if !util.IsStringInSlice(form.Language, setting.Langs) {
+			ctx.Flash.Error(ctx.Tr("settings.update_language_not_found", form.Language))
+			ctx.Redirect(setting.AppSubURL + "/user/settings/appearance")
+			return
+		}
+		ctx.User.Language = form.Language
+	}
+
+	if err := models.UpdateUserSetting(ctx.User); err != nil {
+		ctx.ServerError("UpdateUserSetting", err)
+		return
+	}
+
+	// Update the language to the one we just set
+	middleware.SetLocaleCookie(ctx.Resp, ctx.User.Language, 0)
+
+	log.Trace("User settings updated: %s", ctx.User.Name)
+	ctx.Flash.Success(i18n.Tr(ctx.User.Language, "settings.update_language_success"))
+	ctx.Redirect(setting.AppSubURL + "/user/settings/appearance")
+
+}
diff --git a/routers/web/web.go b/routers/web/web.go
index 88565d6d85..c0bb74da2e 100644
--- a/routers/web/web.go
+++ b/routers/web/web.go
@@ -317,6 +317,10 @@ func RegisterRoutes(m *web.Route) {
 			m.Post("/email", bindIgnErr(forms.AddEmailForm{}), userSetting.EmailPost)
 			m.Post("/email/delete", userSetting.DeleteEmail)
 			m.Post("/delete", userSetting.DeleteAccount)
+		})
+		m.Group("/appearance", func() {
+			m.Get("", userSetting.Appearance)
+			m.Post("/language", bindIgnErr(forms.UpdateLanguageForm{}), userSetting.UpdateUserLang)
 			m.Post("/theme", bindIgnErr(forms.UpdateThemeForm{}), userSetting.UpdateUIThemePost)
 		})
 		m.Group("/security", func() {
diff --git a/services/forms/user_form.go b/services/forms/user_form.go
index 241ebdc0d9..9f86bf6166 100644
--- a/services/forms/user_form.go
+++ b/services/forms/user_form.go
@@ -240,7 +240,6 @@ type UpdateProfileForm struct {
 	KeepEmailPrivate    bool
 	Website             string `binding:"ValidSiteUrl;MaxSize(255)"`
 	Location            string `binding:"MaxSize(50)"`
-	Language            string
 	Description         string `binding:"MaxSize(255)"`
 	Visibility          structs.VisibleType
 	KeepActivityPrivate bool
@@ -252,6 +251,17 @@ func (f *UpdateProfileForm) Validate(req *http.Request, errs binding.Errors) bin
 	return middleware.Validate(errs, ctx.Data, f, ctx.Locale)
 }
 
+// UpdateLanguageForm form for updating profile
+type UpdateLanguageForm struct {
+	Language string
+}
+
+// Validate validates the fields
+func (f *UpdateLanguageForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
+	ctx := context.GetContext(req)
+	return middleware.Validate(errs, ctx.Data, f, ctx.Locale)
+}
+
 // Avatar types
 const (
 	AvatarLocal  string = "local"
diff --git a/templates/user/settings/account.tmpl b/templates/user/settings/account.tmpl
index 9ed5d3a6dd..3753a77cec 100644
--- a/templates/user/settings/account.tmpl
+++ b/templates/user/settings/account.tmpl
@@ -130,44 +130,6 @@
 			</form>
 		</div>
 
-		<h4 class="ui top attached header">
-			{{.i18n.Tr "settings.manage_themes"}}
-		</h4>
-		<div class="ui attached segment">
-			<div class="ui email list">
-				<div class="item">
-					{{.i18n.Tr "settings.theme_desc"}}
-				</div>
-
-			<form class="ui form" action="{{.Link}}/theme" method="post">
-				{{.CsrfTokenHtml}}
-					<div class="field">
-						<label for="ui">{{.i18n.Tr "settings.ui"}}</label>
-						<div class="ui selection dropdown" id="ui">
-							<input name="theme" type="hidden" value="{{.SignedUser.Theme}}">
-							{{svg "octicon-triangle-down" 14 "dropdown icon"}}
-							<div class="text">
-								{{range $i,$a := .AllThemes}}
-									{{if eq $.SignedUser.Theme $a}}{{$a}}{{end}}
-								{{end}}
-							</div>
-
-							<div class="menu">
-							{{range $i,$a := .AllThemes}}
-								<div class="item{{if eq $.SignedUser.Theme $a}} active selected{{end}}" data-value="{{$a}}">
-									{{$a}}
-								</div>
-							{{end}}
-							</div>
-						</div>
-					</div>
-
-				<div class="field">
-					<button class="ui green button">{{$.i18n.Tr "settings.update_theme"}}</button>
-				</div>
-			</form>
-			</div>
-		</div>
 		<h4 class="ui top attached error header">
 			{{.i18n.Tr "settings.delete_account"}}
 		</h4>
diff --git a/templates/user/settings/appearance.tmpl b/templates/user/settings/appearance.tmpl
new file mode 100644
index 0000000000..777b89c750
--- /dev/null
+++ b/templates/user/settings/appearance.tmpl
@@ -0,0 +1,74 @@
+{{template "base/head" .}}
+<div class="page-content user settings sshkeys">
+	{{template "user/settings/navbar" .}}
+	<div class="ui container">
+		{{template "base/alert" .}}
+
+		<!-- Theme -->
+		<h4 class="ui top attached header">
+			{{.i18n.Tr "settings.manage_themes"}}
+		</h4>
+		<div class="ui attached segment">
+			<div class="ui email list">
+				<div class="item">
+					{{.i18n.Tr "settings.theme_desc"}}
+				</div>
+
+			<form class="ui form" action="{{.Link}}/theme" method="post">
+				{{.CsrfTokenHtml}}
+					<div class="field">
+						<label for="ui">{{.i18n.Tr "settings.ui"}}</label>
+						<div class="ui selection dropdown" id="ui">
+							<input name="theme" type="hidden" value="{{.SignedUser.Theme}}">
+							{{svg "octicon-triangle-down" 14 "dropdown icon"}}
+							<div class="text">
+								{{range $i,$a := .AllThemes}}
+									{{if eq $.SignedUser.Theme $a}}{{$a}}{{end}}
+								{{end}}
+							</div>
+
+							<div class="menu">
+							{{range $i,$a := .AllThemes}}
+								<div class="item{{if eq $.SignedUser.Theme $a}} active selected{{end}}" data-value="{{$a}}">
+									{{$a}}
+								</div>
+							{{end}}
+							</div>
+						</div>
+					</div>
+
+				<div class="field">
+					<button class="ui green button">{{$.i18n.Tr "settings.update_theme"}}</button>
+				</div>
+			</form>
+			</div>
+		</div>
+
+		<!-- Language -->
+		<h4 class="ui top attached header">
+			{{.i18n.Tr "settings.language"}}
+		</h4>
+		<div class="ui attached segment">
+			<form class="ui form" action="{{.Link}}/language" method="post">
+				{{.CsrfTokenHtml}}
+				<div class="field">
+					<div class="ui language selection dropdown" id="language">
+						<input name="language" type="hidden" value="{{.SignedUser.Language}}">
+						{{svg "octicon-triangle-down" 14 "dropdown icon"}}
+						<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_language"}}</button>
+				</div>
+			</form>
+		</div>
+	</div>
+</div>
+
+{{template "base/footer" .}}
diff --git a/templates/user/settings/navbar.tmpl b/templates/user/settings/navbar.tmpl
index 7255819374..3477a5949b 100644
--- a/templates/user/settings/navbar.tmpl
+++ b/templates/user/settings/navbar.tmpl
@@ -6,6 +6,9 @@
 		<a class="{{if .PageIsSettingsAccount}}active{{end}} item" href="{{AppSubUrl}}/user/settings/account">
 			{{.i18n.Tr "settings.account"}}
 		</a>
+		<a class="{{if .PageIsSettingsAppearance}}active{{end}} item" href="{{AppSubUrl}}/user/settings/appearance">
+			{{.i18n.Tr "settings.appearance"}}
+		</a>
 		<a class="{{if .PageIsSettingsSecurity}}active{{end}} item" href="{{AppSubUrl}}/user/settings/security">
 			{{.i18n.Tr "settings.security"}}
 		</a>
diff --git a/templates/user/settings/profile.tmpl b/templates/user/settings/profile.tmpl
index 1f1585a787..0b14e3c0d3 100644
--- a/templates/user/settings/profile.tmpl
+++ b/templates/user/settings/profile.tmpl
@@ -47,20 +47,6 @@
 					<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" value="{{.SignedUser.Language}}">
-						{{svg "octicon-triangle-down" 14 "dropdown icon"}}
-						<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="ui divider"></div>
 				<!-- private block -->