From dbe0a8240ef80236af584abfa553ae2122689cfb Mon Sep 17 00:00:00 2001 From: zzz Date: Mon, 18 Nov 2013 23:18:46 +0000 Subject: [PATCH] Translations: - Add support for country variants (ticket #1133) - Refactor data in ConfigUIHelper Config files: Allow empty values --- .../src/net/i2p/router/web/CSSHelper.java | 25 ++++++-- .../net/i2p/router/web/ConfigUIHelper.java | 61 ++++++++++++++----- .../java/src/net/i2p/router/web/Messages.java | 2 + .../src/net/i2p/router/web/NetDbRenderer.java | 4 +- core/java/src/net/i2p/data/DataHelper.java | 12 ++-- core/java/src/net/i2p/util/Translate.java | 47 +++++++++++--- 6 files changed, 116 insertions(+), 35 deletions(-) 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) {