Translations:

- Add support for country variants (ticket #1133)
  - Refactor data in ConfigUIHelper
Config files: Allow empty values
This commit is contained in:
zzz
2013-11-18 23:18:46 +00:00
parent 7e3e08532f
commit dbe0a8240e
6 changed files with 116 additions and 35 deletions

View File

@ -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);
}

View File

@ -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("<input type=\"radio\" class=\"optbox\" name=\"lang\" ");
if (langs[i].equals(current))
String lang = langs[i][0];
if (lang.equals(current))
buf.append("checked=\"checked\" ");
buf.append("value=\"").append(langs[i]).append("\">")
.append("<img height=\"11\" width=\"16\" alt=\"\" src=\"/flags.jsp?c=").append(flags[i]).append("\"> ")
.append(Messages.getDisplayLanguage(langs[i], xlangs[i], _context)).append("<br>\n");
buf.append("value=\"").append(lang).append("\">")
.append("<img height=\"11\" width=\"16\" alt=\"\" src=\"/flags.jsp?c=").append(langs[i][1]).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("<br>\n");
}
return buf.toString();
}

View File

@ -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);

View File

@ -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 */

View File

@ -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,7 +443,8 @@ 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) )
// as of 0.9.10, an empty value is allowed
if (forceLowerCase)
props.setProperty(key.toLowerCase(Locale.US), val);
else

View File

@ -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<String, ResourceBundle> _bundles = new ConcurrentHashMap(16);
private static final Set<String> _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) {