diff --git a/apps/routerconsole/java/build.xml b/apps/routerconsole/java/build.xml index f77a14387..f804d2fba 100644 --- a/apps/routerconsole/java/build.xml +++ b/apps/routerconsole/java/build.xml @@ -73,6 +73,15 @@ + + + + + + + @@ -115,6 +124,7 @@ @@ -133,10 +143,13 @@ + + diff --git a/apps/routerconsole/java/bundle-messages.sh b/apps/routerconsole/java/bundle-messages.sh new file mode 100755 index 000000000..6b2fe8cec --- /dev/null +++ b/apps/routerconsole/java/bundle-messages.sh @@ -0,0 +1,51 @@ +# +# Update messages_xx.po and messages_xx.class files, +# from both java and jsp sources. +# Requires installed programs xgettext, msgfmt, and find. +# zzz - public domain +# +CLASS=net.i2p.router.web.messages +TMPFILE=build/javafiles.txt + +for i in ../locale/messages_*.po +do + # get language + LG=${i#../locale/messages_} + LG=${LG%.po} + + # make list of java files newer than the .po file + find src ../jsp/WEB-INF -name *.java -newer $i > $TMPFILE + if [ -s build/obj/net/i2p/router/web/messages_$LG.class -a ! -s $TMPFILE ] + then + continue + fi + + echo "Generating ${CLASS}_$LG ResourceBundle..." + + # extract strings from java and jsp files, and update messages.po files + # translate calls must be one of the forms: + # _("foo") + # cssHelper._("foo") + # handler._("foo") + # formhandler._("foo") + # In a jsp, you must use a helper or handler that has the context set. + # To start a new translation, copy the header from an old translation to the new .po file, + # then ant distclean updater. + xgettext -f build/javafiles.txt -F -L java --keyword=_ --keyword=cssHelper._ --keyword=handler._ --keyword=formhandler._ -o $i -j + if [ $? -ne 0 ] + then + echo 'Warning - xgettext failed, not updating translations' + break + fi + + # convert to class files in build/obj + msgfmt --java -r $CLASS -l $LG -d build/obj $i + if [ $? -ne 0 ] + then + echo 'Warning - xgettext failed, not updating translations' + break + fi +done +rm -f $TMPFILE +# todo: return failure +exit 0 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 a2f3341f9..d12259e95 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/CSSHelper.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/CSSHelper.java @@ -22,4 +22,10 @@ public class CSSHelper extends HelperBase { } return url; } + + /** change default language for the router but don't save it */ + public void setLang(String lang) { + if (lang != null && lang.length() > 0) + _context.router().setConfigSetting(Messages.PROP_LANG, lang); + } } diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigUpdateHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigUpdateHelper.java index 82d8461f1..164ac8df2 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigUpdateHelper.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigUpdateHelper.java @@ -87,7 +87,7 @@ public class ConfigUpdateHelper extends HelperBase { buf.append(""); if ("notify".equals(policy)) - buf.append("Notify only"); + buf.append("").append(_("Notify only")).append(""); else buf.append("Notify only"); diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ContentHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/ContentHelper.java index 795eeb26c..0a1cbdc34 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/ContentHelper.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/ContentHelper.java @@ -62,7 +62,8 @@ public class ContentHelper extends HelperBase { /** * Convert file.ext to file_lang.ext if it exists. - * Get lang from either the cgi lang param or from the default locale. + * Get lang from the cgi lang param, then properties, then from the default locale. + * _context must be set to check the property. */ private String filename() { int lastdot = _page.lastIndexOf('.'); @@ -70,9 +71,13 @@ public class ContentHelper extends HelperBase { return _page; String lang = _lang; if (lang == null || lang.length() <= 0) { - lang = Locale.getDefault().getLanguage(); - if (lang == null || lang.length() <= 0) - return _page; + if (_context != null) + lang = _context.getProperty(Messages.PROP_LANG); + if (lang == null || lang.length() <= 0) { + lang = Locale.getDefault().getLanguage(); + if (lang == null || lang.length() <= 0) + return _page; + } } if (lang.equals("en")) return _page; diff --git a/apps/routerconsole/java/src/net/i2p/router/web/FormHandler.java b/apps/routerconsole/java/src/net/i2p/router/web/FormHandler.java index 0aba83517..8ed39f5c1 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/FormHandler.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/FormHandler.java @@ -190,4 +190,8 @@ public class FormHandler { } } + /** translate a string */ + public String _(String s) { + return Messages.getString(s, _context); + } } diff --git a/apps/routerconsole/java/src/net/i2p/router/web/HelperBase.java b/apps/routerconsole/java/src/net/i2p/router/web/HelperBase.java index 848e48d50..63762ec14 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/HelperBase.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/HelperBase.java @@ -29,4 +29,9 @@ public abstract class HelperBase { //public RouterContext getContext() { return _context; } public void setWriter(Writer out) { _out = out; } + + /** translate a string */ + public String _(String s) { + return Messages.getString(s, _context); + } } diff --git a/apps/routerconsole/java/src/net/i2p/router/web/LocaleWebAppHandler.java b/apps/routerconsole/java/src/net/i2p/router/web/LocaleWebAppHandler.java new file mode 100644 index 000000000..b7ac2b8f8 --- /dev/null +++ b/apps/routerconsole/java/src/net/i2p/router/web/LocaleWebAppHandler.java @@ -0,0 +1,69 @@ +package net.i2p.router.web; + +import java.io.IOException; +import java.util.Locale; +import java.util.Map; + +import net.i2p.I2PAppContext; + +import org.mortbay.http.HttpRequest; +import org.mortbay.http.HttpResponse; +import org.mortbay.jetty.servlet.WebApplicationHandler; + +/** + * Convert foo.jsp to foo_xx.jsp for language xx. + * This is appropriate for jsps with large amounts of text. + * This does not work for included jsps (e.g. summary*) + * + * @author zzz + */ +public class LocaleWebAppHandler extends WebApplicationHandler +{ + private I2PAppContext _context; + + public LocaleWebAppHandler(I2PAppContext ctx) { + super(); + _context = ctx; + } + + /** + * Handle foo.jsp by converting to foo_xx.jsp + * for language xx, where xx is the language for the default locale, + * or as specified in the routerconsole.lang property. + * Unless language==="en". + */ + public void handle(String pathInContext, + String pathParams, + HttpRequest httpRequest, + HttpResponse httpResponse) + throws IOException + { + //System.err.println("Path: " + pathInContext); + String newPath = pathInContext; + if (pathInContext.endsWith(".jsp")) { + int len = pathInContext.length(); + // ...but leave foo_xx.jsp alone + if (len < 8 || pathInContext.charAt(len - 7) != '_') { + String lang = _context.getProperty(Messages.PROP_LANG); + if (lang == null || lang.length() <= 0) + lang = Locale.getDefault().getLanguage(); + if (lang != null && lang.length() > 0 && !lang.equals("en")) { + String testPath = pathInContext.substring(0, len - 4) + '_' + lang + ".jsp"; + // Do we have a servlet for the new path that isn't the catchall *.jsp? + Map.Entry servlet = getHolderEntry(testPath); + if (servlet != null) { + String servletPath = (String) servlet.getKey(); + if (servletPath != null && !servletPath.startsWith("*")) { + // success!! + //System.err.println("Servlet is: " + servletPath); + newPath = testPath; + } + } + } + } + } + //System.err.println("New path: " + newPath); + super.handle(newPath, pathParams, httpRequest, httpResponse); + //System.err.println("Was handled? " + httpRequest.isHandled()); + } +} diff --git a/apps/routerconsole/java/src/net/i2p/router/web/Messages.java b/apps/routerconsole/java/src/net/i2p/router/web/Messages.java new file mode 100644 index 000000000..9837f010e --- /dev/null +++ b/apps/routerconsole/java/src/net/i2p/router/web/Messages.java @@ -0,0 +1,78 @@ +package net.i2p.router.web; + +import java.util.Locale; +import java.util.Map; +import java.util.MissingResourceException; +import java.util.ResourceBundle; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import net.i2p.I2PAppContext; +import net.i2p.util.ConcurrentHashSet; + +/** + * Translate strings efficiently. + * We don't include an English or default ResourceBundle, we simply check + * for "en" and return the original string. + * Support real-time language changing with the routerconsole.lang property. + * + * @author zzz, from a base generated by eclipse. + */ +public class Messages { + public static final String PROP_LANG = "routerconsole.lang"; + private static final String BUNDLE_NAME = "net.i2p.router.web.messages"; + private static final String _localeLang = Locale.getDefault().getLanguage(); + private static final Map _bundles = new ConcurrentHashMap(2); + private static final Set _missing = new ConcurrentHashSet(2); + + /** current locale **/ + public static String getString(String key) { + if (_localeLang.equals("en")) + return key; + ResourceBundle bundle = findBundle(_localeLang); + if (bundle == null) + return key; + try { + return bundle.getString(key); + } catch (MissingResourceException e) { + return key; + } + } + + /** lang in routerconsole.lang property, else current locale */ + public static String getString(String key, I2PAppContext ctx) { + String lang = getLanguage(ctx); + if (lang.equals("en")) + return key; + ResourceBundle bundle = findBundle(lang); + if (bundle == null) + return key; + try { + return bundle.getString(key); + } catch (MissingResourceException e) { + return key; + } + } + + private static String getLanguage(I2PAppContext ctx) { + String lang = ctx.getProperty(PROP_LANG); + if (lang == null || lang.length() <= 0) + lang = _localeLang; + return lang; + } + + /** cache both found and not found for speed */ + private static ResourceBundle findBundle(String lang) { + ResourceBundle rv = _bundles.get(lang); + if (rv == null && !_missing.contains(lang)) { + try { + // Would it be faster to specify a class loader? + // No matter we only do this once per lang. + rv = ResourceBundle.getBundle(BUNDLE_NAME, new Locale(lang)); + } catch (MissingResourceException e) { + _missing.add(lang); + } + } + return rv; + } +} 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 1e3e6dcc8..6e9f76fa8 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/NetDbRenderer.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/NetDbRenderer.java @@ -268,4 +268,8 @@ public class NetDbRenderer { buf.append("\n"); } + /** translate a string */ + private String _(String s) { + return Messages.getString(s, _context); + } } diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ProfileOrganizerRenderer.java b/apps/routerconsole/java/src/net/i2p/router/web/ProfileOrganizerRenderer.java index f0f44c4c1..0a96bdbc2 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/ProfileOrganizerRenderer.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/ProfileOrganizerRenderer.java @@ -319,4 +319,9 @@ class ProfileOrganizerRenderer { long c = r.getCurrentEventCount() + r.getLastEventCount(); return "" + c; } + + /** translate a string */ + private String _(String s) { + return Messages.getString(s, _context); + } } diff --git a/apps/routerconsole/java/src/net/i2p/router/web/RouterConsoleRunner.java b/apps/routerconsole/java/src/net/i2p/router/web/RouterConsoleRunner.java index 8529f13ff..ec2b10c24 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/RouterConsoleRunner.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/RouterConsoleRunner.java @@ -111,6 +111,7 @@ public class RouterConsoleRunner { File tmpdir = new File(workDir, ROUTERCONSOLE + "-" + _listenPort); tmpdir.mkdir(); wac.setTempDirectory(tmpdir); + wac.addHandler(0, new LocaleWebAppHandler(I2PAppContext.getGlobalContext())); initialize(wac); File dir = new File(_webAppsDir); String fileNames[] = dir.list(WarFilenameFilter.instance()); diff --git a/apps/routerconsole/java/src/net/i2p/router/web/StatsGenerator.java b/apps/routerconsole/java/src/net/i2p/router/web/StatsGenerator.java index 983a6ba8b..342d5d95f 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/StatsGenerator.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/StatsGenerator.java @@ -235,4 +235,9 @@ public class StatsGenerator { private final static DecimalFormat _pct = new DecimalFormat("#0.00%"); private final static String pct(double num) { synchronized (_pct) { return _pct.format(num); } } + + /** translate a string */ + private String _(String s) { + return Messages.getString(s, _context); + } } diff --git a/apps/routerconsole/java/src/net/i2p/router/web/TunnelRenderer.java b/apps/routerconsole/java/src/net/i2p/router/web/TunnelRenderer.java index 656068f11..b1536c911 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/TunnelRenderer.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/TunnelRenderer.java @@ -310,4 +310,9 @@ public class TunnelRenderer { private String netDbLink(Hash peer) { return _context.commSystem().renderPeerHTML(peer); } + + /** translate a string */ + private String _(String s) { + return Messages.getString(s, _context); + } } diff --git a/apps/routerconsole/jsp/configupdate.jsp b/apps/routerconsole/jsp/configupdate.jsp index 62677bbb0..99842aa10 100644 --- a/apps/routerconsole/jsp/configupdate.jsp +++ b/apps/routerconsole/jsp/configupdate.jsp @@ -36,7 +36,7 @@ "> Refresh frequency: - Update policy: + <%=formhandler._("Update policy")%>: Update through the eepProxy? diff --git a/apps/routerconsole/jsp/css.jsp b/apps/routerconsole/jsp/css.jsp index ed8675030..865e90723 100644 --- a/apps/routerconsole/jsp/css.jsp +++ b/apps/routerconsole/jsp/css.jsp @@ -25,5 +25,8 @@ " /> +<% + cssHelper.setLang(request.getParameter("lang")); +%> console.css" rel="stylesheet" type="text/css"> diff --git a/apps/routerconsole/jsp/index.jsp b/apps/routerconsole/jsp/index.jsp index b14f8e352..d2ce8ffc2 100644 --- a/apps/routerconsole/jsp/index.jsp +++ b/apps/routerconsole/jsp/index.jsp @@ -29,5 +29,6 @@ if (System.getProperty("router.consoleNonce") == null) { " /> + " />