forked from I2P_Developers/i2p.i2p
* Router Console translation infrastructure:
- Persistent lang setting with routerconsole.lang=xx - Loading any page with ?lang=xx changes the persistent setting - Add a custom Jetty handler to load foo_xx.jsp if it exists for language xx. This is for jsp files with lots of text in them. Otherwise use inline translate methods. Not for included jsps. - Add a script to create and update messages_xx.po translation files, and create ResourceBundles from them - Add class to translate strings from cached ResourceBundles - Add translate wrappers to HelperBase, FormHandler, and *Renderer, so calls can be made from both jsp and java files - Add two example translations on configupdate.jsp - one in the jsp itself and one in the helper. - This is for strings in routerconsole only. Will be expanded to other webapps and the router later.
This commit is contained in:
@ -73,6 +73,15 @@
|
||||
<delete dir="./tmpextract" />
|
||||
|
||||
<ant target="war" />
|
||||
|
||||
<!-- Update the messages_*.po files.
|
||||
We need to supply something for windows, and then change the fail property to true,
|
||||
or else not run this on windows -->
|
||||
<exec executable="sh" failifexecutionfails="false" >
|
||||
<arg value="./bundle-messages.sh" />
|
||||
</exec>
|
||||
<!-- jar again to get the latest messages_*.class files -->
|
||||
<jar destfile="./build/routerconsole.jar" basedir="./build/obj" includes="**/*.class" update="true" />
|
||||
</target>
|
||||
<target name="war" depends="precompilejsp">
|
||||
<!-- Don't include the css in the war, the main build.xml will copy it to docs/themes/console/ -->
|
||||
@ -115,6 +124,7 @@
|
||||
</java>
|
||||
|
||||
<javac debug="true" deprecation="on" source="1.5" target="1.5"
|
||||
encoding="UTF-8"
|
||||
destdir="../jsp/WEB-INF/classes/"
|
||||
srcdir="../jsp/WEB-INF/classes" includes="**/*.java">
|
||||
<compilerarg line="${javac.compilerargs}" />
|
||||
@ -133,10 +143,13 @@
|
||||
<pathelement location="../../../core/java/build/i2p.jar" />
|
||||
</classpath>
|
||||
</javac>
|
||||
|
||||
<!-- save these so we can run gettext on the generated java files later
|
||||
<delete>
|
||||
<fileset dir="../jsp/WEB-INF/" includes="**/*.java" />
|
||||
<fileset dir="../jsp/WEB-INF/" includes="**/*.jsp" />
|
||||
</delete>
|
||||
-->
|
||||
<copy file="../jsp/web.xml" tofile="../jsp/web-out.xml" />
|
||||
<loadfile property="jspc.web.fragment" srcfile="../jsp/web-fragment.xml" />
|
||||
<replace file="../jsp/web-out.xml">
|
||||
|
51
apps/routerconsole/java/bundle-messages.sh
Executable file
51
apps/routerconsole/java/bundle-messages.sh
Executable file
@ -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
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -87,7 +87,7 @@ public class ConfigUpdateHelper extends HelperBase {
|
||||
buf.append("<select name=\"updatePolicy\">");
|
||||
|
||||
if ("notify".equals(policy))
|
||||
buf.append("<option value=\"notify\" selected=\"true\">Notify only</option>");
|
||||
buf.append("<option value=\"notify\" selected=\"true\">").append(_("Notify only")).append("</option>");
|
||||
else
|
||||
buf.append("<option value=\"notify\">Notify only</option>");
|
||||
|
||||
|
@ -62,18 +62,23 @@ 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('.');
|
||||
if (lastdot <= 0)
|
||||
return _page;
|
||||
String lang = _lang;
|
||||
if (lang == null || lang.length() <= 0) {
|
||||
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;
|
||||
String newname = _page.substring(0, lastdot) + '_' + lang + _page.substring(lastdot);
|
||||
|
@ -190,4 +190,8 @@ public class FormHandler {
|
||||
}
|
||||
}
|
||||
|
||||
/** translate a string */
|
||||
public String _(String s) {
|
||||
return Messages.getString(s, _context);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
78
apps/routerconsole/java/src/net/i2p/router/web/Messages.java
Normal file
78
apps/routerconsole/java/src/net/i2p/router/web/Messages.java
Normal file
@ -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<String, ResourceBundle> _bundles = new ConcurrentHashMap(2);
|
||||
private static final Set<String> _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;
|
||||
}
|
||||
}
|
@ -268,4 +268,8 @@ public class NetDbRenderer {
|
||||
buf.append("</td></tr>\n");
|
||||
}
|
||||
|
||||
/** translate a string */
|
||||
private String _(String s) {
|
||||
return Messages.getString(s, _context);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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());
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -36,7 +36,7 @@
|
||||
<td><input type="text" size="60" name="newsURL" value="<jsp:getProperty name="updatehelper" property="newsURL" />"></td>
|
||||
</tr><tr><td class= "mediumtags" align="right"><b>Refresh frequency:</b>
|
||||
<td><jsp:getProperty name="updatehelper" property="refreshFrequencySelectBox" /></td><tr>
|
||||
<td class= "mediumtags" align="right"><b>Update policy:</b></td>
|
||||
<td class= "mediumtags" align="right"><b><%=formhandler._("Update policy")%>:</b></td>
|
||||
<td><jsp:getProperty name="updatehelper" property="updatePolicySelectBox" /></td>
|
||||
<tr><td class= "mediumtags" align="right"><b>Update through the eepProxy?</b></td>
|
||||
<td><jsp:getProperty name="updatehelper" property="updateThroughProxy" /></td>
|
||||
|
@ -25,5 +25,8 @@
|
||||
<link rel="shortcut icon" href="/themes/console/images/favicon.ico">
|
||||
<jsp:useBean class="net.i2p.router.web.CSSHelper" id="cssHelper" scope="request" />
|
||||
<jsp:setProperty name="cssHelper" property="contextId" value="<%=(String)session.getAttribute("i2p.contextId")%>" />
|
||||
<%
|
||||
cssHelper.setLang(request.getParameter("lang"));
|
||||
%>
|
||||
<link href="<%=cssHelper.getTheme(request.getHeader("User-Agent"))%>console.css" rel="stylesheet" type="text/css">
|
||||
<!--[if IE]><link href="/themes/console/classic/ieshim.css" rel="stylesheet" type="text/css" /><![endif]-->
|
||||
|
@ -29,5 +29,6 @@ if (System.getProperty("router.consoleNonce") == null) {
|
||||
<jsp:setProperty name="contenthelper" property="page" value="<%=fpath.getAbsolutePath()%>" />
|
||||
<jsp:setProperty name="contenthelper" property="maxLines" value="300" />
|
||||
<jsp:setProperty name="contenthelper" property="lang" value="<%=request.getParameter("lang")%>" />
|
||||
<jsp:setProperty name="contenthelper" property="contextId" value="<%=(String)session.getAttribute("i2p.contextId")%>" />
|
||||
<jsp:getProperty name="contenthelper" property="content" />
|
||||
</div></body></html>
|
||||
|
25
apps/routerconsole/locale/messages_de.po
Normal file
25
apps/routerconsole/locale/messages_de.po
Normal file
@ -0,0 +1,25 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2009-10-18 10:03-0400\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/router/web/jsp/configupdate_jsp.java:441
|
||||
msgid "Update policy"
|
||||
msgstr "Update policy in german foobarbaz"
|
||||
|
||||
#: src/net/i2p/router/web/ConfigUpdateHelper.java:90
|
||||
msgid "Notify only"
|
||||
msgstr "Notify only in german"
|
25
apps/routerconsole/locale/messages_zh.po
Normal file
25
apps/routerconsole/locale/messages_zh.po
Normal file
@ -0,0 +1,25 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2009-10-18 10:03-0400\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/router/web/jsp/configupdate_jsp.java:441
|
||||
msgid "Update policy"
|
||||
msgstr ""
|
||||
|
||||
#: src/net/i2p/router/web/ConfigUpdateHelper.java:90
|
||||
msgid "Notify only"
|
||||
msgstr ""
|
Reference in New Issue
Block a user