forked from I2P_Developers/i2p.i2p
Translations:
- Add support for country variants (ticket #1133) - Refactor data in ConfigUIHelper Config files: Allow empty values
This commit is contained in:
@ -1,5 +1,7 @@
|
|||||||
package net.i2p.router.web;
|
package net.i2p.router.web;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
@ -49,15 +51,30 @@ public class CSSHelper extends HelperBase {
|
|||||||
return url;
|
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) {
|
public void setLang(String lang) {
|
||||||
// Protected with nonce in css.jsi
|
// Protected with nonce in css.jsi
|
||||||
if (lang != null && lang.length() == 2 && !lang.equals(_context.getProperty(Messages.PROP_LANG))) {
|
if (lang != null) {
|
||||||
_context.router().saveConfig(Messages.PROP_LANG, lang);
|
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() {
|
public String getLang() {
|
||||||
return Messages.getLanguage(_context);
|
return Messages.getLanguage(_context);
|
||||||
}
|
}
|
||||||
|
@ -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.
|
* Alphabetical by the ISO code please.
|
||||||
* See http://en.wikipedia.org/wiki/ISO_639-1 .
|
* See http://en.wikipedia.org/wiki/ISO_639-1 .
|
||||||
* Any language-specific flag added to the icon set must be
|
* Any language-specific flag added to the icon set must be
|
||||||
* added to the top-level build.xml for the updater.
|
* added to the top-level build.xml for the updater.
|
||||||
*/
|
*/
|
||||||
private static final String langs[] = {"ar", "cs", "da", "de", "et", "el", "en", "es", "fi",
|
private static final String langs[][] = {
|
||||||
"fr", "hu", "it", "nb", "nl", "pl", "pt", "ro", "ru",
|
{ "ar", "lang_ar", _x("Arabic"), null },
|
||||||
"sv", "tr", "uk", "vi", "zh"};
|
{ "cs", "cz", _x("Czech"), null },
|
||||||
private static final String flags[] = {"lang_ar", "cz", "dk", "de", "ee", "gr", "us", "es", "fi",
|
{ "da", "dk", _x("Danish"), null },
|
||||||
"fr", "hu", "it", "nl", "no", "pl", "pt", "ro", "ru",
|
{ "de", "de", _x("German"), null },
|
||||||
"se", "tr", "ua", "vn", "cn"};
|
{ "et", "ee", _x("Estonian"), null },
|
||||||
private static final String xlangs[] = {_x("Arabic"), _x("Czech"), _x("Danish"),
|
{ "el", "gr", _x("Greek"), null },
|
||||||
_x("German"), _x("Estonian"), _x("Greek"), _x("English"), _x("Spanish"), _x("Finnish"),
|
{ "en", "us", _x("English"), null },
|
||||||
_x("French"), _x("Hungarian"), _x("Italian"), _x("Dutch"), _x("Norwegian Bokmaal"), _x("Polish"),
|
{ "es", "es", _x("Spanish"), null },
|
||||||
_x("Portuguese"), _x("Romanian"), _x("Russian"), _x("Swedish"),
|
{ "fi", "fi", _x("Finnish"), null },
|
||||||
_x("Turkish"), _x("Ukrainian"), _x("Vietnamese"), _x("Chinese")};
|
{ "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 */
|
/** todo sort by translated string */
|
||||||
public String getLangSettings() {
|
public String getLangSettings() {
|
||||||
StringBuilder buf = new StringBuilder(512);
|
StringBuilder buf = new StringBuilder(512);
|
||||||
String current = Messages.getLanguage(_context);
|
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++) {
|
for (int i = 0; i < langs.length; i++) {
|
||||||
// we use "lang" so it is set automagically in CSSHelper
|
// we use "lang" so it is set automagically in CSSHelper
|
||||||
buf.append("<input type=\"radio\" class=\"optbox\" name=\"lang\" ");
|
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("checked=\"checked\" ");
|
||||||
buf.append("value=\"").append(langs[i]).append("\">")
|
buf.append("value=\"").append(lang).append("\">")
|
||||||
.append("<img height=\"11\" width=\"16\" alt=\"\" src=\"/flags.jsp?c=").append(flags[i]).append("\"> ")
|
.append("<img height=\"11\" width=\"16\" alt=\"\" src=\"/flags.jsp?c=").append(langs[i][1]).append("\"> ");
|
||||||
.append(Messages.getDisplayLanguage(langs[i], xlangs[i], _context)).append("<br>\n");
|
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();
|
return buf.toString();
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,8 @@ import net.i2p.util.Translate;
|
|||||||
public class Messages extends Translate {
|
public class Messages extends Translate {
|
||||||
private static final String BUNDLE_NAME = "net.i2p.router.web.messages";
|
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 */
|
/** lang in routerconsole.lang property, else current locale */
|
||||||
public static String getString(String key, I2PAppContext ctx) {
|
public static String getString(String key, I2PAppContext ctx) {
|
||||||
return Translate.getString(key, ctx, BUNDLE_NAME);
|
return Translate.getString(key, ctx, BUNDLE_NAME);
|
||||||
|
@ -350,8 +350,6 @@ public class NetDbRenderer {
|
|||||||
out.flush();
|
out.flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final String COUNTRY_BUNDLE_NAME = "net.i2p.router.countries.messages";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Countries now in a separate bundle
|
* Countries now in a separate bundle
|
||||||
* @param code two-letter country code
|
* @param code two-letter country code
|
||||||
@ -359,7 +357,7 @@ public class NetDbRenderer {
|
|||||||
*/
|
*/
|
||||||
private String getTranslatedCountry(String code) {
|
private String getTranslatedCountry(String code) {
|
||||||
String name = _context.commSystem().getCountryName(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 */
|
/** sort by translated country name using rules for the current language setting */
|
||||||
|
@ -399,6 +399,7 @@ public class DataHelper {
|
|||||||
* - Leading whitespace is not trimmed
|
* - Leading whitespace is not trimmed
|
||||||
* - '=' is the only key-termination character (not ':' or whitespace)
|
* - '=' 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 {
|
public static void loadProps(Properties props, File file) throws IOException {
|
||||||
loadProps(props, file, false);
|
loadProps(props, file, false);
|
||||||
@ -442,11 +443,12 @@ public class DataHelper {
|
|||||||
// it was a horrible idea anyway
|
// it was a horrible idea anyway
|
||||||
//val = val.replaceAll("\\\\r","\r");
|
//val = val.replaceAll("\\\\r","\r");
|
||||||
//val = val.replaceAll("\\\\n","\n");
|
//val = val.replaceAll("\\\\n","\n");
|
||||||
if ( (key.length() > 0) && (val.length() > 0) )
|
|
||||||
if (forceLowerCase)
|
// as of 0.9.10, an empty value is allowed
|
||||||
props.setProperty(key.toLowerCase(Locale.US), val);
|
if (forceLowerCase)
|
||||||
else
|
props.setProperty(key.toLowerCase(Locale.US), val);
|
||||||
props.setProperty(key, val);
|
else
|
||||||
|
props.setProperty(key, val);
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
if (in != null) try { in.close(); } catch (IOException ioe) {}
|
if (in != null) try { in.close(); } catch (IOException ioe) {}
|
||||||
|
@ -25,7 +25,12 @@ import net.i2p.util.ConcurrentHashSet;
|
|||||||
*/
|
*/
|
||||||
public abstract class Translate {
|
public abstract class Translate {
|
||||||
public static final String PROP_LANG = "routerconsole.lang";
|
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();
|
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 Map<String, ResourceBundle> _bundles = new ConcurrentHashMap(16);
|
||||||
private static final Set<String> _missing = new ConcurrentHashSet(16);
|
private static final Set<String> _missing = new ConcurrentHashSet(16);
|
||||||
/** use to look for untagged strings */
|
/** 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
|
// shouldnt happen but dont dump the po headers if it does
|
||||||
if (key.equals(""))
|
if (key.equals(""))
|
||||||
return key;
|
return key;
|
||||||
ResourceBundle bundle = findBundle(bun, lang);
|
ResourceBundle bundle = findBundle(bun, lang, getCountry(ctx));
|
||||||
if (bundle == null)
|
if (bundle == null)
|
||||||
return key;
|
return key;
|
||||||
try {
|
try {
|
||||||
@ -110,7 +115,7 @@ public abstract class Translate {
|
|||||||
return TEST_STRING + '(' + n + ')' + TEST_STRING;
|
return TEST_STRING + '(' + n + ')' + TEST_STRING;
|
||||||
ResourceBundle bundle = null;
|
ResourceBundle bundle = null;
|
||||||
if (!lang.equals("en"))
|
if (!lang.equals("en"))
|
||||||
bundle = findBundle(bun, lang);
|
bundle = findBundle(bun, lang, getCountry(ctx));
|
||||||
String x;
|
String x;
|
||||||
if (bundle == null)
|
if (bundle == null)
|
||||||
x = n == 1 ? s : p;
|
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) {
|
public static String getLanguage(I2PAppContext ctx) {
|
||||||
String lang = ctx.getProperty(PROP_LANG);
|
String lang = ctx.getProperty(PROP_LANG);
|
||||||
if (lang == null || lang.length() <= 0)
|
if (lang == null || lang.length() <= 0)
|
||||||
@ -137,14 +145,39 @@ public abstract class Translate {
|
|||||||
return lang;
|
return lang;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** cache both found and not found for speed */
|
/**
|
||||||
private static ResourceBundle findBundle(String bun, String lang) {
|
* Two-letter upper case or ""
|
||||||
String key = bun + '-' + lang;
|
* @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);
|
ResourceBundle rv = _bundles.get(key);
|
||||||
if (rv == null && !_missing.contains(key)) {
|
if (rv == null && !_missing.contains(key)) {
|
||||||
|
if ("".equals(lang)) {
|
||||||
|
_missing.add(key);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
try {
|
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
|
// 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)
|
if (rv != null)
|
||||||
_bundles.put(key, rv);
|
_bundles.put(key, rv);
|
||||||
} catch (MissingResourceException e) {
|
} catch (MissingResourceException e) {
|
||||||
|
Reference in New Issue
Block a user