Merge pull request #1198 from WilliamFromTW/master

New Setting "Default Language" when creating user
diff --git a/.classpath b/.classpath
index 095cb03..3dac201 100644
--- a/.classpath
+++ b/.classpath
@@ -49,7 +49,7 @@
 	<classpathentry kind="lib" path="ext/httpcore-4.3.3.jar" sourcepath="ext/src/httpcore-4.3.3.jar" />
 	<classpathentry kind="lib" path="ext/commons-logging-1.1.3.jar" sourcepath="ext/src/commons-logging-1.1.3.jar" />
 	<classpathentry kind="lib" path="ext/commons-codec-1.7.jar" sourcepath="ext/src/commons-codec-1.7.jar" />
-	<classpathentry kind="lib" path="ext/org.eclipse.jdt.annotation-1.1.0.jar" />
+	<classpathentry kind="lib" path="ext/org.eclipse.jdt.annotation-1.1.0.jar" sourcepath="ext/src/org.eclipse.jdt.annotation-1.1.0.jar" />
 	<classpathentry kind="lib" path="ext/org.eclipse.jgit.http.server-4.1.1.201511131810-r.jar" sourcepath="ext/src/org.eclipse.jgit.http.server-4.1.1.201511131810-r.jar" />
 	<classpathentry kind="lib" path="ext/bcprov-jdk15on-1.52.jar" sourcepath="ext/src/bcprov-jdk15on-1.52.jar" />
 	<classpathentry kind="lib" path="ext/bcmail-jdk15on-1.52.jar" sourcepath="ext/src/bcmail-jdk15on-1.52.jar" />
@@ -94,10 +94,5 @@
 	<classpathentry kind="lib" path="ext/mockito-core-1.10.19.jar" sourcepath="ext/src/mockito-core-1.10.19.jar" />
 	<classpathentry kind="lib" path="ext/objenesis-2.1.jar" sourcepath="ext/src/objenesis-2.1.jar" />
 	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER" />
-	<classpathentry kind="src" path="src/main/dagger">
-		<attributes>
-			<attribute name="optional" value="true"/>
-		</attributes>
-	</classpathentry>
 	<classpathentry kind="output" path="bin/classes" />
 </classpath>
diff --git a/gitblit.iml b/gitblit.iml
index 71907a5..038c4f4 100644
--- a/gitblit.iml
+++ b/gitblit.iml
@@ -491,7 +491,9 @@
           <root url="jar://$MODULE_DIR$/ext/org.eclipse.jdt.annotation-1.1.0.jar!/" />
         </CLASSES>
         <JAVADOC />
-        <SOURCES />
+        <SOURCES>
+          <root url="jar://$MODULE_DIR$/ext/src/org.eclipse.jdt.annotation-1.1.0.jar!/" />
+        </SOURCES>
       </library>
     </orderEntry>
     <orderEntry type="module-library">
diff --git a/src/main/distrib/data/certs/instructions.tmpl_zh_TW b/src/main/distrib/data/certs/instructions.tmpl_zh_TW
new file mode 100644
index 0000000..72b99e0
--- /dev/null
+++ b/src/main/distrib/data/certs/instructions.tmpl_zh_TW
@@ -0,0 +1,111 @@
+********************************************************************************
+ Gitblit 伺服器 $serverHostname 所需之 SSL 用戶端憑證
+********************************************************************************
+
+ $userDisplayname 您好,
+
+ 伺服器 $serverHostname 所需要的私鑰,公鑰以及Gitblit簽證檔案(CA)存放於 $username.p12, PKCS#12 certificate store[1] 以及 $username.pem.
+
+ 兩種證書皆受密碼保護. 
+ 密碼提示: $storePasswordHint
+
+
+Git 憑證匯入步驟
+=============================================
+
+ 附件之 PEM 檔案可以直接匯入至您的git程式裡.
+ 
+    git config [--global] http.sslCert path/to/$username.pem
+    
+ PEM檔案受密碼保護,因此匯入過程會提示多次.  如果您偏好不使用密碼,您需要另外匯出無密碼之私鑰後,再匯入git程式裡.
+ 
+    openssl rsa -in path/to/$username.pem -out path/to/$username.key    
+    git config [--global] http.sslKey path/to/$username.key
+
+ 此外,您應該妥善保管已經解除密碼保護之私鑰.
+ 
+ 註:
+ 如果沒有匯出私鑰, 有些早期git版本將會發生匯入失敗問題,例如:Ubuntu 12.04 with git 1.7.9.5.
+
+
+Firefox 憑證匯入步驟
+=============================================
+
+ Firefox 有自己的證書管理介面.
+
+ 1. 點選 "選項->進階->憑證"
+ 2. 點選 "檢視憑證清單"
+ 3. 切換至 "您的憑證"
+ 4. 點選 "匯入(M)"
+ 5. 選擇電腦中的憑證檔案 $username.p12
+ 6. 輸入憑證檔案所需的密碼    
+ 7. 切換至"憑證機構"
+ 8. 找到 "Gitblit Certificate Authority" 憑證
+ 9. 點選"編輯信任(E)"
+ 10.選擇信任網站.
+
+
+Chrome/IE (Windows) 憑證匯入步驟
+=============================================
+
+ 在Windows作業系統下, Chrome 與 IE 共用相同的憑證設定.
+
+ IE
+ ------------------------------------
+ 1. 選擇 "網際網路選項->內容"
+ 2. 點選"憑證"
+
+ Chrome
+ ------------------------------------
+ 1. 選擇 "設定->顯示進階設定->HTTP/SSL"
+ 2. 點選"管理憑證"
+
+ 共同步驟 (Windows)
+ ------------------------------------
+ 3. 切換至 "個人"
+ 4. 點選"匯入(I)" 
+ 5. 依照指示匯入.
+    請切換匯入檔案類型為p12並且找到 $username.p12 這個憑證檔案
+ 6. 輸入憑證檔案保護密碼
+ 7. 由於主要發放憑證單位(CA)與個人憑證檔案皆儲存於 $username.p12, 因此匯入時候,必須選擇 "自動根據憑證類型來選擇憑證存放區".
+    如果選擇預設值匯入, 將不會安裝主要發放憑證單位(CA)
+
+
+Chrome (Linux) Installation Instructions
+=============================================
+ 
+ On Linux, Chrome maintains it's own certificate store.
+ 
+ 1. Navigate to Settings->Show Advanced Settings->HTTP/SSL
+ 2. Click the "Manage Certificates..." button
+ 3. Navigate your filesystem and select $username.p12
+ 4. At the password prompt enter the certificate store password
+    You have now imported your private key, public certificate, and the CA certificate
+    but now we must manually set the trust settings of the CA certificate.
+ 5. Switch to the "Authorities" tab
+ 6. Scroll down and find "Gitblit-> Gitblit Certificate Authority"
+ 7. Select it and click "Edit Trust..."
+ 8. Check "This certificate can identify websites" and click OK.
+
+
+Chrome/Safari (Mac OS X) Installation Instructions
+=============================================
+
+On Mac OS X, Chrome and Safari both use Keychain Access to store certificates
+so configuring one will automatically apply for both.
+
+ 1. Double-click $username.pem
+ 2. At the password prompt enter the certificate store password
+    You have now imported your private key, public certificate, and the CA certificate
+    but now we must manually set the trust settings of the CA certificate.
+ 3. Find the Gitblit Certificate Authority certificate, it should have a red
+    indicator meaning untrusted, and double-click it.
+ 4. Open the "Trust" disclosure triangle and change "When using this certificate"
+    to "Always Trust".
+ 5. Close the certificate view and enter your system password to save the changes
+    to your keychain. 
+  
+    
+[1] PKCS#12 is one of the standard container formats for sharing private keys and
+    public certificates.
+[2] http://www.openssl.org
diff --git a/src/main/distrib/data/certs/mail.tmpl_zh_TW b/src/main/distrib/data/certs/mail.tmpl_zh_TW
new file mode 100644
index 0000000..20eb518
--- /dev/null
+++ b/src/main/distrib/data/certs/mail.tmpl_zh_TW
@@ -0,0 +1,5 @@
+$userDisplayname 您好
+
+ 伺服器 $serverHostname 所需要的私鑰,公鑰以及Gitblit簽證檔案(CA)已經全部打包並且以zip壓縮檔方式寄給您.
+
+ 此外,檔案還附上各瀏覽器設定步驟供您參考.
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/authority/GitblitAuthority.java b/src/main/java/com/gitblit/authority/GitblitAuthority.java
index 5f4a7e7..15c23a7 100644
--- a/src/main/java/com/gitblit/authority/GitblitAuthority.java
+++ b/src/main/java/com/gitblit/authority/GitblitAuthority.java
@@ -48,7 +48,9 @@
 import java.util.Date;

 import java.util.HashMap;

 import java.util.List;

+import java.util.Locale;

 import java.util.Map;

+import java.util.ResourceBundle;

 

 import javax.mail.Message;

 import javax.swing.ImageIcon;

@@ -98,6 +100,7 @@
 import com.gitblit.utils.X509Utils.RevocationReason;

 import com.gitblit.utils.X509Utils.X509Log;

 import com.gitblit.utils.X509Utils.X509Metadata;

+import com.gitblit.wicket.GitBlitWebSession;

 

 /**

  * Simple GUI tool for administering Gitblit client certificates.

@@ -447,7 +450,7 @@
 				}

 

 				File caKeystoreFile = new File(folder, X509Utils.CA_KEY_STORE);

-				File zip = X509Utils.newClientBundle(metadata, caKeystoreFile, caKeystorePassword, GitblitAuthority.this);

+				File zip = X509Utils.newClientBundle(user,metadata, caKeystoreFile, caKeystorePassword, GitblitAuthority.this);

 

 				// save latest expiration date

 				if (ucm.expires == null || metadata.notAfter.before(ucm.expires)) {

@@ -850,9 +853,19 @@
 		try {

 			if (mail.isReady()) {

 				Mailing mailing = Mailing.newPlain();

-				mailing.subject = "Your Gitblit client certificate for " + metadata.serverHostname;

+                if( user.getPreferences().getLocale()!=null )

+                    mailing.subject = MessageFormat.format(ResourceBundle.getBundle("com.gitblit.wicket.GitBlitWebApp",user.getPreferences().getLocale()).getString("gb.emailClientCertificateSubject"), metadata.serverHostname);

+                else

+                    mailing.subject = MessageFormat.format(ResourceBundle.getBundle("com.gitblit.wicket.GitBlitWebApp", Locale.ENGLISH).getString("gb.emailClientCertificateSubject") , metadata.serverHostname);

 				mailing.setRecipients(user.emailAddress);

-				String body = X509Utils.processTemplate(new File(folder, X509Utils.CERTS + File.separator + "mail.tmpl"), metadata);

+				File fileMailTmp = null;

+				String body = null;

+				if( (fileMailTmp = new File(folder, X509Utils.CERTS + File.separator +  "mail.tmpl"+"_"+user.getPreferences().getLocale())).exists())

+				  body = X509Utils.processTemplate(fileMailTmp, metadata);

+				else{

+				  fileMailTmp = new File(folder, X509Utils.CERTS + File.separator +  "mail.tmpl");

+				  body = X509Utils.processTemplate(fileMailTmp, metadata);

+				}  

 				if (StringUtils.isEmpty(body)) {

 					body = MessageFormat.format("Hi {0}\n\nHere is your client certificate bundle.\nInside the zip file are installation instructions.", user.getDisplayName());

 				}

diff --git a/src/main/java/com/gitblit/utils/X509Utils.java b/src/main/java/com/gitblit/utils/X509Utils.java
index a2650be..b661922 100644
--- a/src/main/java/com/gitblit/utils/X509Utils.java
+++ b/src/main/java/com/gitblit/utils/X509Utils.java
@@ -743,6 +743,25 @@
 	 */

 	public static File newClientBundle(X509Metadata clientMetadata, File caKeystoreFile,

 			String caKeystorePassword, X509Log x509log) {

+		return newClientBundle(null,clientMetadata,caKeystoreFile,caKeystorePassword,x509log);

+	}

+

+	/**

+	 * Creates a new client certificate PKCS#12 and PEM store.  Any existing

+	 * stores are destroyed.  After generation, the certificates are bundled

+	 * into a zip file with a personalized README file.

+	 *

+	 * The zip file reference is returned.

+	 *

+	 * @param user 

+	 * @param clientMetadata a container for dynamic parameters needed for generation

+	 * @param caKeystoreFile

+	 * @param caKeystorePassword

+	 * @param x509log

+	 * @return a zip file containing the P12, PEM, and personalized README

+	 */

+	public static File newClientBundle(com.gitblit.models.UserModel user,X509Metadata clientMetadata, File caKeystoreFile,

+				String caKeystorePassword, X509Log x509log) {		

 		try {

 			// read the Gitblit CA key and certificate

 			KeyStore store = openKeyStore(caKeystoreFile, caKeystorePassword);

@@ -755,8 +774,17 @@
 	        x509log.log(MessageFormat.format("New client certificate {0,number,0} [{1}]", cert.getSerialNumber(), cert.getSubjectDN().getName()));

 

 	        // process template message

-	        String readme = processTemplate(new File(caKeystoreFile.getParentFile(), "instructions.tmpl"), clientMetadata);

-

+	        String readme = null;

+	        String sInstructionsFileName = "instructions.tmpl";

+	        if( user == null )

+	        	readme = processTemplate(new File(caKeystoreFile.getParentFile(),sInstructionsFileName), clientMetadata);

+	        else{

+	        	File fileInstructionsTmp = null;

+	        	if( (fileInstructionsTmp = new File(caKeystoreFile.getParentFile(),sInstructionsFileName+"_"+user.getPreferences().getLocale())).exists() )

+	        		readme = processTemplate(fileInstructionsTmp,clientMetadata);

+	        	else

+	        		readme = processTemplate(new File(caKeystoreFile.getParentFile(),sInstructionsFileName),clientMetadata);

+	        }

 	        // Create a zip bundle with the p12, pem, and a personalized readme

 	        File zipFile = new File(targetFolder, clientMetadata.commonName + ".zip");

 	        if (zipFile.exists()) {

diff --git a/src/main/java/com/gitblit/wicket/GitBlitWebApp.properties b/src/main/java/com/gitblit/wicket/GitBlitWebApp.properties
index b3cbef8..66101f4 100644
--- a/src/main/java/com/gitblit/wicket/GitBlitWebApp.properties
+++ b/src/main/java/com/gitblit/wicket/GitBlitWebApp.properties
@@ -783,3 +783,4 @@
 gb.deletePatchsetFailure = Error deleting Patchset {0}.
 gb.referencedByCommit = Referenced by commit.
 gb.referencedByTicket = Referenced by ticket.
+gb.emailClientCertificateSubject = Your Gitblit client certificate for {0}
diff --git a/src/main/java/com/gitblit/wicket/GitBlitWebApp_zh_TW.properties b/src/main/java/com/gitblit/wicket/GitBlitWebApp_zh_TW.properties
index bf2d2c3..157dd56 100644
--- a/src/main/java/com/gitblit/wicket/GitBlitWebApp_zh_TW.properties
+++ b/src/main/java/com/gitblit/wicket/GitBlitWebApp_zh_TW.properties
@@ -781,3 +781,4 @@
 gb.deletePatchsetFailure = \u522a\u9664 Patchset {0} \u932f\u8aa4.
 gb.referencedByCommit = Referenced by commit.
 gb.referencedByTicket = Referenced by ticket.
+gb.emailClientCertificateSubject = \u4F3A\u670D\u5668 {0} \u9023\u7DDA\u6191\u8B49 
diff --git a/src/main/java/com/gitblit/wicket/pages/EditUserPage.html b/src/main/java/com/gitblit/wicket/pages/EditUserPage.html
index 3bccdd5..48e5339 100644
--- a/src/main/java/com/gitblit/wicket/pages/EditUserPage.html
+++ b/src/main/java/com/gitblit/wicket/pages/EditUserPage.html
@@ -31,11 +31,12 @@
 				<tr><th><wicket:message key="gb.confirmPassword"></wicket:message></th><td class="edit"><input type="password" wicket:id="confirmPassword" size="30" tabindex="3" /></td></tr>

 				<tr><th><wicket:message key="gb.displayName"></wicket:message></th><td class="edit"><input type="text" wicket:id="displayName" size="30" tabindex="4" /></td></tr>

 				<tr><th><wicket:message key="gb.emailAddress"></wicket:message></th><td class="edit"><input type="text" wicket:id="emailAddress" size="30" tabindex="5" /></td></tr>

-				<tr><th><wicket:message key="gb.canAdmin"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="canAdmin" tabindex="6" /> &nbsp;<span class="help-inline"><wicket:message key="gb.canAdminDescription"></wicket:message></span></label></td></tr>

-				<tr><th><wicket:message key="gb.canCreate"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="canCreate" tabindex="7" /> &nbsp;<span class="help-inline"><wicket:message key="gb.canCreateDescription"></wicket:message></span></label></td></tr>

-				<tr><th><wicket:message key="gb.canFork"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="canFork" tabindex="8" /> &nbsp;<span class="help-inline"><wicket:message key="gb.canForkDescription"></wicket:message></span></label></td></tr>

-				<tr><th><wicket:message key="gb.excludeFromFederation"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="excludeFromFederation" tabindex="9" /> &nbsp;<span class="help-inline"><wicket:message key="gb.excludeFromFederationDescription"></wicket:message></span></label></td></tr>				

-				<tr><th><wicket:message key="gb.disableUser"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="disabled" tabindex="10" /> &nbsp;<span class="help-inline"><wicket:message key="gb.disableUserDescription"></wicket:message></span></label></td></tr>

+				<tr><th><wicket:message key="gb.languagePreference"></wicket:message></th><td class="edit"><select wicket:id="language" ></select></td></tr>

+				<tr><th><wicket:message key="gb.canAdmin"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="canAdmin" tabindex="7" /> &nbsp;<span class="help-inline"><wicket:message key="gb.canAdminDescription"></wicket:message></span></label></td></tr>

+				<tr><th><wicket:message key="gb.canCreate"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="canCreate" tabindex="8" /> &nbsp;<span class="help-inline"><wicket:message key="gb.canCreateDescription"></wicket:message></span></label></td></tr>

+				<tr><th><wicket:message key="gb.canFork"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="canFork" tabindex="9" /> &nbsp;<span class="help-inline"><wicket:message key="gb.canForkDescription"></wicket:message></span></label></td></tr>

+				<tr><th><wicket:message key="gb.excludeFromFederation"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="excludeFromFederation" tabindex="10" /> &nbsp;<span class="help-inline"><wicket:message key="gb.excludeFromFederationDescription"></wicket:message></span></label></td></tr>				

+				<tr><th><wicket:message key="gb.disableUser"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="disabled" tabindex="11" /> &nbsp;<span class="help-inline"><wicket:message key="gb.disableUserDescription"></wicket:message></span></label></td></tr>

 			</tbody>

 		</table>

 		</div>

diff --git a/src/main/java/com/gitblit/wicket/pages/EditUserPage.java b/src/main/java/com/gitblit/wicket/pages/EditUserPage.java
index 72dee6b..0a6981b 100644
--- a/src/main/java/com/gitblit/wicket/pages/EditUserPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/EditUserPage.java
@@ -20,15 +20,18 @@
 import java.util.Collections;

 import java.util.Iterator;

 import java.util.List;

+import java.util.Locale;

 

 import org.apache.wicket.PageParameters;

 import org.apache.wicket.behavior.SimpleAttributeModifier;

 import org.apache.wicket.extensions.markup.html.form.palette.Palette;

 import org.apache.wicket.markup.html.form.Button;

 import org.apache.wicket.markup.html.form.CheckBox;

+import org.apache.wicket.markup.html.form.DropDownChoice;

 import org.apache.wicket.markup.html.form.Form;

 import org.apache.wicket.markup.html.form.TextField;

 import org.apache.wicket.model.CompoundPropertyModel;

+import org.apache.wicket.model.IModel;

 import org.apache.wicket.model.Model;

 import org.apache.wicket.model.util.CollectionModel;

 import org.apache.wicket.model.util.ListModel;

@@ -108,6 +111,30 @@
 		final Palette<String> teams = new Palette<String>("teams", new ListModel<String>(

 				new ArrayList<String>(userTeams)), new CollectionModel<String>(app().users()

 				.getAllTeamNames()), new StringChoiceRenderer(), 10, false);

+		Locale locale = userModel.getPreferences().getLocale();

+		if (locale == null) {

+			locale = Locale.ENGLISH;

+		}

+

+		List<Language> languages = UserPage.getLanguages();

+		Language preferredLanguage = null;

+		if (locale != null) {

+			String localeCode = locale.getLanguage();

+			if (!StringUtils.isEmpty(locale.getCountry())) {

+				localeCode += "_" + locale.getCountry();

+			}

+

+			for (Language lang : languages) {

+				if (lang.code.equals(localeCode)) {

+					// language_COUNTRY match

+					preferredLanguage = lang;

+				} else if (preferredLanguage != null && lang.code.startsWith(locale.getLanguage())) {

+					// language match

+					preferredLanguage = lang;

+				}

+			}

+		}

+		final IModel<Language> language = Model.of(preferredLanguage);		

 		Form<UserModel> form = new Form<UserModel>("editForm", model) {

 

 			private static final long serialVersionUID = 1L;

@@ -123,6 +150,10 @@
 					error(getString("gb.pleaseSetUsername"));

 					return;

 				}

+				Language lang = language.getObject();

+				if (lang != null) {

+					userModel.getPreferences().setLocale(lang.code);

+				}

 				// force username to lower-case

 				userModel.username = userModel.username.toLowerCase();

 				String username = userModel.username;

@@ -251,7 +282,10 @@
 		form.add(confirmPasswordField.setEnabled(editCredentials));

 		form.add(new TextField<String>("displayName").setEnabled(editDisplayName));

 		form.add(new TextField<String>("emailAddress").setEnabled(editEmailAddress));

+		

 

+		DropDownChoice<Language> choice = new DropDownChoice<Language>("language",language,languages	);

+		form.add( choice.setEnabled(languages.size()>0) );

 		if (userModel.canAdmin() && !userModel.canAdmin) {

 			// user inherits Admin permission

 			// display a disabled-yet-checked checkbox

diff --git a/src/main/java/com/gitblit/wicket/pages/Language.java b/src/main/java/com/gitblit/wicket/pages/Language.java
new file mode 100644
index 0000000..6eafb4d
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/Language.java
@@ -0,0 +1,21 @@
+package com.gitblit.wicket.pages;
+
+import java.io.Serializable;
+
+public class Language implements Serializable {
+
+	private static final long serialVersionUID = 1L;
+
+	final String name;
+	final String code;
+
+	public Language(String name, String code) {
+		this.name = name;
+		this.code = code;
+	}
+
+	@Override
+	public String toString() {
+		return name + " (" + code + ")";
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/pages/UserPage.java b/src/main/java/com/gitblit/wicket/pages/UserPage.java
index ea68f25..25a2249 100644
--- a/src/main/java/com/gitblit/wicket/pages/UserPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/UserPage.java
@@ -166,12 +166,9 @@
 

 		navLinks.add(menu);

 	}

-

-	private void addPreferences(UserModel user) {

-		// add preferences

-		Form<Void> prefs = new Form<Void>("prefsForm");

-

-		List<Language> languages = Arrays.asList(

+	

+	public static List<Language> getLanguages(){

+		return  Arrays.asList(

 				new Language("Deutsch","de"),

 				new Language("English","en"),

 				new Language("Español", "es"),

@@ -185,6 +182,13 @@
 				new Language("Português", "pt_BR"),

 				new Language("簡體中文", "zh_CN"),

 				new Language("正體中文", "zh_TW"));

+	}

+	

+	private void addPreferences(UserModel user) {

+		// add preferences

+		Form<Void> prefs = new Form<Void>("prefsForm");

+

+		List<Language> languages = getLanguages();

 

 		Locale locale = user.getPreferences().getLocale();

 		if (locale == null) {

@@ -315,22 +319,4 @@
 		add(new Fragment("sshKeysLink", "sshKeysLinkFragment", this).setRenderBodyOnly(true));

 		add(keysTab.setRenderBodyOnly(true));

 	}

-

-	private class Language implements Serializable {

-

-		private static final long serialVersionUID = 1L;

-

-		final String name;

-		final String code;

-

-		public Language(String name, String code) {

-			this.name = name;

-			this.code = code;

-		}

-

-		@Override

-		public String toString() {

-			return name + " (" + code +")";

-		}

-	}

 }