diff --git a/apps/routerconsole/java/src/net/i2p/router/web/CSSHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/CSSHelper.java
index 2c0a70c90..030d0b7e6 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/CSSHelper.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/CSSHelper.java
@@ -1,5 +1,7 @@
package net.i2p.router.web;
+import java.util.HashMap;
+import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@@ -49,15 +51,30 @@ public class CSSHelper extends HelperBase {
return url;
}
- /** change default language for the router AND save it */
+ /**
+ * change default language for the router AND save it
+ * @param lang xx OR xx_XX
+ */
public void setLang(String lang) {
// Protected with nonce in css.jsi
- if (lang != null && lang.length() == 2 && !lang.equals(_context.getProperty(Messages.PROP_LANG))) {
- _context.router().saveConfig(Messages.PROP_LANG, lang);
+ if (lang != null) {
+ Map m = new HashMap(2);
+ if (lang.length() == 2) {
+ m.put(Messages.PROP_LANG, lang.toLowerCase(Locale.US));
+ m.put(Messages.PROP_COUNTRY, "");
+ _context.router().saveConfig(m, null);
+ } else if (lang.length() == 5) {
+ m.put(Messages.PROP_LANG, lang.substring(0, 2).toLowerCase(Locale.US));
+ m.put(Messages.PROP_COUNTRY, lang.substring(3, 5).toUpperCase(Locale.US));
+ _context.router().saveConfig(m, null);
+ }
}
}
- /** needed for conditional css loads for zh */
+ /**
+ * needed for conditional css loads for zh
+ * @return two-letter only, lower-case
+ */
public String getLang() {
return Messages.getLanguage(_context);
}
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigUIHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigUIHelper.java
index 57a195238..d69899807 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigUIHelper.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigUIHelper.java
@@ -67,36 +67,65 @@ public class ConfigUIHelper extends HelperBase {
}
/**
- * Each language has the ISO code, the flag, and the name.
+ * Each language has the ISO code, the flag, the name, and the optional country name.
* Alphabetical by the ISO code please.
* See http://en.wikipedia.org/wiki/ISO_639-1 .
* Any language-specific flag added to the icon set must be
* added to the top-level build.xml for the updater.
*/
- private static final String langs[] = {"ar", "cs", "da", "de", "et", "el", "en", "es", "fi",
- "fr", "hu", "it", "nb", "nl", "pl", "pt", "ro", "ru",
- "sv", "tr", "uk", "vi", "zh"};
- private static final String flags[] = {"lang_ar", "cz", "dk", "de", "ee", "gr", "us", "es", "fi",
- "fr", "hu", "it", "nl", "no", "pl", "pt", "ro", "ru",
- "se", "tr", "ua", "vn", "cn"};
- private static final String xlangs[] = {_x("Arabic"), _x("Czech"), _x("Danish"),
- _x("German"), _x("Estonian"), _x("Greek"), _x("English"), _x("Spanish"), _x("Finnish"),
- _x("French"), _x("Hungarian"), _x("Italian"), _x("Dutch"), _x("Norwegian Bokmaal"), _x("Polish"),
- _x("Portuguese"), _x("Romanian"), _x("Russian"), _x("Swedish"),
- _x("Turkish"), _x("Ukrainian"), _x("Vietnamese"), _x("Chinese")};
+ private static final String langs[][] = {
+ { "ar", "lang_ar", _x("Arabic"), null },
+ { "cs", "cz", _x("Czech"), null },
+ { "da", "dk", _x("Danish"), null },
+ { "de", "de", _x("German"), null },
+ { "et", "ee", _x("Estonian"), null },
+ { "el", "gr", _x("Greek"), null },
+ { "en", "us", _x("English"), null },
+ { "es", "es", _x("Spanish"), null },
+ { "fi", "fi", _x("Finnish"), null },
+ { "fr", "fr", _x("French"), null },
+ { "hu", "hu", _x("Hungarian"), null },
+ { "it", "it", _x("Italian"), null },
+ { "nb", "nl", _x("Dutch"), null },
+ { "nl", "no", _x("Norwewgian Bokmaal"), null },
+ { "pl", "pl", _x("Polish"), null },
+ { "pt", "pt", _x("Portuguese"), null },
+ // { "pt_BR", "br", _x("Portuguese"), "Brazil" },
+ { "ro", "ro", _x("Romainian"), null },
+ { "ru", "ru", _x("Russian"), null },
+ { "sv", "se", _x("Swedish"), null },
+ { "tr", "tr", _x("Turkish"), null },
+ { "uk", "ua", _x("Ukrainian"), null },
+ { "vi", "vn", _x("Vietnamese"), null },
+ { "zh", "cn", _x("Chinese"), null }
+ };
+
+
/** todo sort by translated string */
public String getLangSettings() {
StringBuilder buf = new StringBuilder(512);
String current = Messages.getLanguage(_context);
+ String country = Messages.getCountry(_context);
+ if (country != null && country.length() > 0)
+ current += '_' + country;
for (int i = 0; i < langs.length; i++) {
// we use "lang" so it is set automagically in CSSHelper
buf.append("")
- .append("
")
- .append(Messages.getDisplayLanguage(langs[i], xlangs[i], _context)).append("
\n");
+ buf.append("value=\"").append(lang).append("\">")
+ .append("
");
+ String slang = lang.length() > 2 ? lang.substring(0, 2) : lang;
+ buf.append(Messages.getDisplayLanguage(slang, langs[i][2], _context));
+ String name = langs[i][3];
+ if (name != null) {
+ buf.append(" (")
+ .append(Messages.getString(name, _context, Messages.COUNTRY_BUNDLE_NAME))
+ .append(')');
+ }
+ buf.append("
\n");
}
return buf.toString();
}
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/Messages.java b/apps/routerconsole/java/src/net/i2p/router/web/Messages.java
index 7becafbe9..c76f260e8 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/Messages.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/Messages.java
@@ -9,6 +9,8 @@ import net.i2p.util.Translate;
public class Messages extends Translate {
private static final String BUNDLE_NAME = "net.i2p.router.web.messages";
+ static final String COUNTRY_BUNDLE_NAME = "net.i2p.router.countries.messages";
+
/** lang in routerconsole.lang property, else current locale */
public static String getString(String key, I2PAppContext ctx) {
return Translate.getString(key, ctx, BUNDLE_NAME);
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/NetDbRenderer.java b/apps/routerconsole/java/src/net/i2p/router/web/NetDbRenderer.java
index 64922a43e..eac49219a 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/NetDbRenderer.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/NetDbRenderer.java
@@ -350,8 +350,6 @@ public class NetDbRenderer {
out.flush();
}
- private static final String COUNTRY_BUNDLE_NAME = "net.i2p.router.countries.messages";
-
/**
* Countries now in a separate bundle
* @param code two-letter country code
@@ -359,7 +357,7 @@ public class NetDbRenderer {
*/
private String getTranslatedCountry(String code) {
String name = _context.commSystem().getCountryName(code);
- return Translate.getString(name, _context, COUNTRY_BUNDLE_NAME);
+ return Translate.getString(name, _context, Messages.COUNTRY_BUNDLE_NAME);
}
/** sort by translated country name using rules for the current language setting */
diff --git a/core/java/src/net/i2p/data/DataHelper.java b/core/java/src/net/i2p/data/DataHelper.java
index 36b36a068..5237e9c0e 100644
--- a/core/java/src/net/i2p/data/DataHelper.java
+++ b/core/java/src/net/i2p/data/DataHelper.java
@@ -399,6 +399,7 @@ public class DataHelper {
* - Leading whitespace is not trimmed
* - '=' is the only key-termination character (not ':' or whitespace)
*
+ * As of 0.9.10, an empty value is allowed.
*/
public static void loadProps(Properties props, File file) throws IOException {
loadProps(props, file, false);
@@ -442,11 +443,12 @@ public class DataHelper {
// it was a horrible idea anyway
//val = val.replaceAll("\\\\r","\r");
//val = val.replaceAll("\\\\n","\n");
- if ( (key.length() > 0) && (val.length() > 0) )
- if (forceLowerCase)
- props.setProperty(key.toLowerCase(Locale.US), val);
- else
- props.setProperty(key, val);
+
+ // as of 0.9.10, an empty value is allowed
+ if (forceLowerCase)
+ props.setProperty(key.toLowerCase(Locale.US), val);
+ else
+ props.setProperty(key, val);
}
} finally {
if (in != null) try { in.close(); } catch (IOException ioe) {}
diff --git a/core/java/src/net/i2p/util/Translate.java b/core/java/src/net/i2p/util/Translate.java
index 3139787db..e302de238 100644
--- a/core/java/src/net/i2p/util/Translate.java
+++ b/core/java/src/net/i2p/util/Translate.java
@@ -25,7 +25,12 @@ import net.i2p.util.ConcurrentHashSet;
*/
public abstract class Translate {
public static final String PROP_LANG = "routerconsole.lang";
+ /** @since 0.9.10 */
+ public static final String PROP_COUNTRY = "routerconsole.country";
+ /** non-null, two-letter lower case, may be "" */
private static final String _localeLang = Locale.getDefault().getLanguage();
+ /** non-null, two-letter upper case, may be "" */
+ private static final String _localeCountry = Locale.getDefault().getCountry();
private static final Map _bundles = new ConcurrentHashMap(16);
private static final Set _missing = new ConcurrentHashSet(16);
/** use to look for untagged strings */
@@ -42,7 +47,7 @@ public abstract class Translate {
// shouldnt happen but dont dump the po headers if it does
if (key.equals(""))
return key;
- ResourceBundle bundle = findBundle(bun, lang);
+ ResourceBundle bundle = findBundle(bun, lang, getCountry(ctx));
if (bundle == null)
return key;
try {
@@ -110,7 +115,7 @@ public abstract class Translate {
return TEST_STRING + '(' + n + ')' + TEST_STRING;
ResourceBundle bundle = null;
if (!lang.equals("en"))
- bundle = findBundle(bun, lang);
+ bundle = findBundle(bun, lang, getCountry(ctx));
String x;
if (bundle == null)
x = n == 1 ? s : p;
@@ -129,7 +134,10 @@ public abstract class Translate {
}
}
- /** @return lang in routerconsole.lang property, else current locale */
+ /**
+ * Two-letter lower case
+ * @return lang in routerconsole.lang property, else current locale
+ */
public static String getLanguage(I2PAppContext ctx) {
String lang = ctx.getProperty(PROP_LANG);
if (lang == null || lang.length() <= 0)
@@ -137,14 +145,39 @@ public abstract class Translate {
return lang;
}
- /** cache both found and not found for speed */
- private static ResourceBundle findBundle(String bun, String lang) {
- String key = bun + '-' + lang;
+ /**
+ * Two-letter upper case or ""
+ * @return country in routerconsole.country property, else current locale
+ * @since 0.9.10
+ */
+ public static String getCountry(I2PAppContext ctx) {
+ // property may be empty so we don't have a non-default
+ // language and a default country
+ return ctx.getProperty(PROP_COUNTRY, _localeCountry);
+ }
+
+ /**
+ * cache both found and not found for speed
+ * @param lang non-null, if "" returns null
+ * @param country non-null, may be ""
+ * @return null if not found
+ */
+ private static ResourceBundle findBundle(String bun, String lang, String country) {
+ String key = bun + '-' + lang + '-' + country;
ResourceBundle rv = _bundles.get(key);
if (rv == null && !_missing.contains(key)) {
+ if ("".equals(lang)) {
+ _missing.add(key);
+ return null;
+ }
try {
+ Locale loc;
+ if ("".equals(country))
+ loc = new Locale(lang);
+ else
+ loc = new Locale(lang, country);
// We must specify the class loader so that a webapp can find the bundle in the .war
- rv = ResourceBundle.getBundle(bun, new Locale(lang), Thread.currentThread().getContextClassLoader());
+ rv = ResourceBundle.getBundle(bun, loc, Thread.currentThread().getContextClassLoader());
if (rv != null)
_bundles.put(key, rv);
} catch (MissingResourceException e) {