I2P Address: [http://git.idk.i2p]

Skip to content
Snippets Groups Projects
Commit dc3378d0 authored by zzz's avatar zzz
Browse files

* Translate: Add GNU ngettext (plurals) support

parent 9132e941
No related branches found
No related tags found
No related merge requests found
Showing
with 377 additions and 39 deletions
......@@ -64,6 +64,9 @@ Public domain except as listed below:
Copyright 2006 Gregory Rubin grrubin@gmail.com
See licenses/LICENSE-HashCash.txt
GettextResource from gettext v0.18:
Copyright (C) 2001, 2007 Free Software Foundation, Inc.
See licenses/LICENSE-LGPLv2.1.txt
Router:
......
......@@ -430,4 +430,9 @@ public class I2PSnarkUtil {
public String getString(String s, Object o, Object o2) {
return Translate.getString(s, o, o2, _context, BUNDLE_NAME);
}
/** ngettext @since 0.7.14 */
public String getString(int n, String s, String p) {
return Translate.getString(n, s, p, _context, BUNDLE_NAME);
}
}
......@@ -269,10 +269,10 @@ public class I2PSnarkServlet extends Default {
" <th align=\"left\" colspan=\"2\">");
out.write(_("Totals"));
out.write(" (");
out.write(_("{0} torrents", snarks.size()));
out.write(ngettext("1 torrent", "{0} torrents", snarks.size()));
out.write(", ");
out.write(DataHelper.formatSize2(stats[5]) + "B, ");
out.write(_("{0} connected peers", stats[4]));
out.write(ngettext("1 connected peer", "{0} connected peers", (int) stats[4]));
out.write(")</th>\n" +
" <th>&nbsp;</th>\n" +
" <th align=\"right\">" + formatSize(stats[0]) + "</th>\n" +
......@@ -629,10 +629,12 @@ public class I2PSnarkServlet extends Default {
if (err != null) {
if (isRunning && curPeers > 0 && !showPeers)
statusString = "<a title=\"" + err + "\">" + _("TrackerErr") + "</a> (" +
curPeers + "/" + knownPeers +
" <a href=\"" + uri + "?p=" + Base64.encode(snark.meta.getInfoHash()) + "\">" + _("peers") + "</a>)";
"<a href=\"" + uri + "?p=" + Base64.encode(snark.meta.getInfoHash()) + "\">" +
curPeers + '/' +
ngettext("1 peer", "{0} peers", knownPeers) + "</a>)";
else if (isRunning)
statusString = "<a title=\"" + err + "\">" + _("TrackerErr") + " (" + curPeers + '/' + knownPeers + ' ' + _("peers") + ')';
statusString = "<a title=\"" + err + "\">" + _("TrackerErr") + " (" + curPeers + '/' +
ngettext("1 peer", "{0} peers", knownPeers) + ')';
else {
if (err.length() > MAX_DISPLAYED_ERROR_LENGTH)
err = err.substring(0, MAX_DISPLAYED_ERROR_LENGTH) + "&hellip;";
......@@ -641,25 +643,31 @@ public class I2PSnarkServlet extends Default {
} else if (remaining <= 0) {
if (isRunning && curPeers > 0 && !showPeers)
statusString = _("Seeding") + " (" +
curPeers + '/' + knownPeers +
" <a href=\"" + uri + "?p=" + Base64.encode(snark.meta.getInfoHash()) + "\">" + _("peers") + "</a>)";
"<a href=\"" + uri + "?p=" + Base64.encode(snark.meta.getInfoHash()) + "\">" +
curPeers + '/' +
ngettext("1 peer", "{0} peers", knownPeers) + "</a>)";
else if (isRunning)
statusString = _("Seeding") + " (" + curPeers + "/" + knownPeers + ' ' + _("peers") + ')';
statusString = _("Seeding") + " (" + curPeers + "/" +
ngettext("1 peer", "{0} peers", knownPeers) + ')';
else
statusString = _("Complete");
} else {
if (isRunning && curPeers > 0 && downBps > 0 && !showPeers)
statusString = _("OK") + " (" +
curPeers + "/" + knownPeers +
" <a href=\"" + uri + "?p=" + Base64.encode(snark.meta.getInfoHash()) + "\">" + _("peers") + "</a>)";
"<a href=\"" + uri + "?p=" + Base64.encode(snark.meta.getInfoHash()) + "\">" +
curPeers + "/" +
ngettext("1 peer", "{0} peers", knownPeers) + "</a>)";
else if (isRunning && curPeers > 0 && downBps > 0)
statusString = _("OK") + " (" + curPeers + "/" + knownPeers + ' ' + _("peers") + ')';
statusString = _("OK") + " (" + curPeers + "/" +
ngettext("1 peer", "{0} peers", knownPeers) + ')';
else if (isRunning && curPeers > 0 && !showPeers)
statusString = _("Stalled") + " (" +
curPeers + '/' + knownPeers +
" <a href=\"" + uri + "?p=" + Base64.encode(snark.meta.getInfoHash()) + "\">" + _("peers") + "</a>)";
"<a href=\"" + uri + "?p=" + Base64.encode(snark.meta.getInfoHash()) + "\">" +
curPeers + '/' +
ngettext("1 peer", "{0} peers", knownPeers) + "</a>)";
else if (isRunning && curPeers > 0)
statusString = _("Stalled") + " (" + curPeers + '/' + knownPeers + ' ' + _("peers") + ')';
statusString = _("Stalled") + " (" + curPeers + '/' +
ngettext("1 peer", "{0} peers", knownPeers) + ')';
else if (isRunning)
statusString = _("No Peers") + " (0/" + knownPeers + ')';
else
......@@ -1078,11 +1086,11 @@ public class I2PSnarkServlet extends Default {
}
/** copied from ConfigTunnelsHelper */
private static final String HOP = _x("hop");
private static final String TUNNEL = _x("tunnel");
private static final String HOP = "hop";
private static final String TUNNEL = "tunnel";
/** dummies for translation */
private static final String HOPS = _x("hops");
private static final String TUNNELS = _x("tunnels");
private static final String HOPS = ngettext("1 hop", "{0} hops");
private static final String TUNNELS = ngettext("1 tunnel", "{0} tunnels");
/** modded from ConfigTunnelsHelper @since 0.7.14 */
private String renderOptions(int min, int max, String strNow, String selName, String name) {
......@@ -1096,13 +1104,7 @@ public class I2PSnarkServlet extends Default {
buf.append("<option value=\"").append(i).append("\" ");
if (i == now)
buf.append("selected=\"true\" ");
String pname;
// pluralize and then translate
if (i != 1 && i != -1)
pname = name + 's';
else
pname = name;
buf.append(">").append(i).append(' ').append(_(pname));
buf.append(">").append(ngettext("1 " + name, "{0} " + name + 's', i));
buf.append("</option>\n");
}
buf.append("</select>\n");
......@@ -1119,9 +1121,14 @@ public class I2PSnarkServlet extends Default {
return _manager.util().getString(s, o);
}
/** translate (ngettext) @since 0.7.14 */
private String ngettext(String s, String p, int n) {
return _manager.util().getString(n, s, p);
}
/** dummy for tagging */
private static String _x(String s) {
return s;
private static String ngettext(String s, String p) {
return null;
}
// rounding makes us look faster :)
......
......@@ -16,6 +16,8 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Poedit-Language: Russian\n"
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%"
"10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)\n"
#: ../java/src/org/klomp/snark/SnarkManager.java:87
#, java-format
......
......@@ -16,6 +16,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Poedit-Language: Chinese\n"
"Plural-Forms: nplurals=1; plural=0\n"
#: ../java/src/org/klomp/snark/SnarkManager.java:84
#, java-format
......
......@@ -8,11 +8,11 @@ import net.i2p.data.Destination;
import net.i2p.router.TunnelPoolSettings;
public class ConfigTunnelsHelper extends HelperBase {
static final String HOP = _x("hop");
static final String TUNNEL = _x("tunnel");
private static final String HOP = "hop";
private static final String TUNNEL = "tunnel";
/** dummies for translation */
static final String HOPS = _x("hops");
static final String TUNNELS = _x("tunnels");
private static final String HOPS = ngettext("1 hop", "{0} hops");
private static final String TUNNELS = ngettext("1 tunnel", "{0} tunnels");
public ConfigTunnelsHelper() {}
......@@ -196,14 +196,13 @@ public class ConfigTunnelsHelper extends HelperBase {
buf.append("<option value=\"").append(i).append("\" ");
if (i == now)
buf.append("selected=\"true\" ");
String pname;
// pluralize and then translate
if (i != 1 && i != -1)
pname = name + 's';
else
pname = name;
buf.append(">").append(prefix).append(i).append(' ').append(_(pname));
buf.append(">").append(_(i, "1 " + name, "{0} " + name + 's'));
buf.append("</option>\n");
}
}
/** dummy for tagging */
private static String ngettext(String s, String p) {
return null;
}
}
......@@ -51,6 +51,11 @@ public abstract class HelperBase {
return Messages.getString(s, o, _context);
}
/** translate (ngettext) @since 0.7.14 */
public String _(int n, String s, String p) {
return Messages.getString(n, s, p, _context);
}
/**
* Mark a string for extraction by xgettext and translation.
* Use this only in static initializers.
......
......@@ -29,4 +29,9 @@ public class Messages extends Translate {
public static String getString(String s, Object o, I2PAppContext ctx) {
return Translate.getString(s, o, ctx, BUNDLE_NAME);
}
/** translate (ngettext) @since 0.7.14 */
public static String getString(int n, String s, String p, I2PAppContext ctx) {
return Translate.getString(n, s, p, ctx, BUNDLE_NAME);
}
}
......@@ -16,6 +16,8 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Poedit-Language: Russian\n"
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%"
"10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)\n"
"X-Poedit-Bookmarks: 283,-1,-1,-1,-1,-1,-1,-1,-1,-1\n"
#: ../../../router/java/src/net/i2p/router/Blocklist.java:126
......
......@@ -17,6 +17,7 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"X-Poedit-Language: Chinese\n"
"X-Poedit-Country: CHINA\n"
"Plural-Forms: nplurals=1; plural=0\n"
#: ../../../router/java/src/net/i2p/router/Blocklist.java:117
#, java-format
......
/* GNU gettext for Java
* Copyright (C) 2001, 2007 Free Software Foundation, Inc.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Library General Public License as published
* by the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
* USA.
*/
package gnu.gettext;
import java.lang.reflect.*;
import java.util.*;
/**
* This class implements the main GNU libintl functions in Java.
* <P>
* Using the GNU gettext approach, compiled message catalogs are normal
* Java ResourceBundle classes and are thus interoperable with standard
* ResourceBundle based code.
* <P>
* The main differences between the Sun ResourceBundle approach and the
* GNU gettext approach are:
* <UL>
* <LI>In the Sun approach, the keys are abstract textual shortcuts.
* In the GNU gettext approach, the keys are the English/ASCII version
* of the messages.
* <LI>In the Sun approach, the translation files are called
* "<VAR>Resource</VAR>_<VAR>locale</VAR>.properties" and have non-ASCII
* characters encoded in the Java
* <CODE>\</CODE><CODE>u<VAR>nnnn</VAR></CODE> syntax. Very few editors
* can natively display international characters in this format. In the
* GNU gettext approach, the translation files are called
* "<VAR>Resource</VAR>.<VAR>locale</VAR>.po"
* and are in the encoding the translator has chosen. Many editors
* can be used. There are at least three GUI translating tools
* (Emacs PO mode, KDE KBabel, GNOME gtranslator).
* <LI>In the Sun approach, the function
* <CODE>ResourceBundle.getString</CODE> throws a
* <CODE>MissingResourceException</CODE> when no translation is found.
* In the GNU gettext approach, the <CODE>gettext</CODE> function
* returns the (English) message key in that case.
* <LI>In the Sun approach, there is no support for plural handling.
* Even the most elaborate MessageFormat strings cannot provide decent
* plural handling. In the GNU gettext approach, we have the
* <CODE>ngettext</CODE> function.
* </UL>
* <P>
* To compile GNU gettext message catalogs into Java ResourceBundle classes,
* the <CODE>msgfmt</CODE> program can be used.
*
* @author Bruno Haible
*/
public abstract class GettextResource extends ResourceBundle {
public static boolean verbose = false;
/**
* Like gettext(catalog,msgid), except that it returns <CODE>null</CODE>
* when no translation was found.
*/
private static String gettextnull (ResourceBundle catalog, String msgid) {
try {
return (String)catalog.getObject(msgid);
} catch (MissingResourceException e) {
return null;
}
}
/**
* Returns the translation of <VAR>msgid</VAR>.
* @param catalog a ResourceBundle
* @param msgid the key string to be translated, an ASCII string
* @return the translation of <VAR>msgid</VAR>, or <VAR>msgid</VAR> if
* none is found
*/
public static String gettext (ResourceBundle catalog, String msgid) {
String result = gettextnull(catalog,msgid);
if (result != null)
return result;
return msgid;
}
/**
* Like ngettext(catalog,msgid,msgid_plural,n), except that it returns
* <CODE>null</CODE> when no translation was found.
*/
private static String ngettextnull (ResourceBundle catalog, String msgid, long n) {
// The reason why we use so many reflective API calls instead of letting
// the GNU gettext generated ResourceBundles implement some interface,
// is that we want the generated ResourceBundles to be completely
// standalone, so that migration from the Sun approach to the GNU gettext
// approach (without use of plurals) is as straightforward as possible.
ResourceBundle origCatalog = catalog;
do {
// Try catalog itself.
if (verbose)
System.out.println("ngettext on "+catalog);
Method handleGetObjectMethod = null;
Method getParentMethod = null;
try {
handleGetObjectMethod = catalog.getClass().getMethod("handleGetObject", new Class[] { java.lang.String.class });
getParentMethod = catalog.getClass().getMethod("getParent", new Class[0]);
} catch (NoSuchMethodException e) {
} catch (SecurityException e) {
}
if (verbose)
System.out.println("handleGetObject = "+(handleGetObjectMethod!=null)+", getParent = "+(getParentMethod!=null));
if (handleGetObjectMethod != null
&& Modifier.isPublic(handleGetObjectMethod.getModifiers())
&& getParentMethod != null) {
// A GNU gettext created class.
Method lookupMethod = null;
Method pluralEvalMethod = null;
try {
lookupMethod = catalog.getClass().getMethod("lookup", new Class[] { java.lang.String.class });
pluralEvalMethod = catalog.getClass().getMethod("pluralEval", new Class[] { Long.TYPE });
} catch (NoSuchMethodException e) {
} catch (SecurityException e) {
}
if (verbose)
System.out.println("lookup = "+(lookupMethod!=null)+", pluralEval = "+(pluralEvalMethod!=null));
if (lookupMethod != null && pluralEvalMethod != null) {
// A GNU gettext created class with plural handling.
Object localValue = null;
try {
localValue = lookupMethod.invoke(catalog, new Object[] { msgid });
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.getTargetException().printStackTrace();
}
if (localValue != null) {
if (verbose)
System.out.println("localValue = "+localValue);
if (localValue instanceof String)
// Found the value. It doesn't depend on n in this case.
return (String)localValue;
else {
String[] pluralforms = (String[])localValue;
long i = 0;
try {
i = ((Long) pluralEvalMethod.invoke(catalog, new Object[] { new Long(n) })).longValue();
if (!(i >= 0 && i < pluralforms.length))
i = 0;
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.getTargetException().printStackTrace();
}
return pluralforms[(int)i];
}
}
} else {
// A GNU gettext created class without plural handling.
Object localValue = null;
try {
localValue = handleGetObjectMethod.invoke(catalog, new Object[] { msgid });
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.getTargetException().printStackTrace();
}
if (localValue != null) {
// Found the value. It doesn't depend on n in this case.
if (verbose)
System.out.println("localValue = "+localValue);
return (String)localValue;
}
}
Object parentCatalog = catalog;
try {
parentCatalog = getParentMethod.invoke(catalog, new Object[0]);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.getTargetException().printStackTrace();
}
if (parentCatalog != catalog)
catalog = (ResourceBundle)parentCatalog;
else
break;
} else
// Not a GNU gettext created class.
break;
} while (catalog != null);
// The end of chain of GNU gettext ResourceBundles is reached.
if (catalog != null) {
// For a non-GNU ResourceBundle we cannot access 'parent' and
// 'handleGetObject', so make a single call to catalog and all
// its parent catalogs at once.
Object value;
try {
value = catalog.getObject(msgid);
} catch (MissingResourceException e) {
value = null;
}
if (value != null)
// Found the value. It doesn't depend on n in this case.
return (String)value;
}
// Default: null.
return null;
}
/**
* Returns the plural form for <VAR>n</VAR> of the translation of
* <VAR>msgid</VAR>.
* @param catalog a ResourceBundle
* @param msgid the key string to be translated, an ASCII string
* @param msgid_plural its English plural form
* @return the translation of <VAR>msgid</VAR> depending on <VAR>n</VAR>,
* or <VAR>msgid</VAR> or <VAR>msgid_plural</VAR> if none is found
*/
public static String ngettext (ResourceBundle catalog, String msgid, String msgid_plural, long n) {
String result = ngettextnull(catalog,msgid,n);
if (result != null)
return result;
// Default: English strings and Germanic plural rule.
return (n != 1 ? msgid_plural : msgid);
}
/* The separator between msgctxt and msgid. */
private static final String CONTEXT_GLUE = "\u0004";
/**
* Returns the translation of <VAR>msgid</VAR> in the context of
* <VAR>msgctxt</VAR>.
* @param catalog a ResourceBundle
* @param msgctxt the context for the key string, an ASCII string
* @param msgid the key string to be translated, an ASCII string
* @return the translation of <VAR>msgid</VAR>, or <VAR>msgid</VAR> if
* none is found
*/
public static String pgettext (ResourceBundle catalog, String msgctxt, String msgid) {
String result = gettextnull(catalog,msgctxt+CONTEXT_GLUE+msgid);
if (result != null)
return result;
return msgid;
}
/**
* Returns the plural form for <VAR>n</VAR> of the translation of
* <VAR>msgid</VAR> in the context of <VAR>msgctxt</VAR>.
* @param catalog a ResourceBundle
* @param msgctxt the context for the key string, an ASCII string
* @param msgid the key string to be translated, an ASCII string
* @param msgid_plural its English plural form
* @return the translation of <VAR>msgid</VAR> depending on <VAR>n</VAR>,
* or <VAR>msgid</VAR> or <VAR>msgid_plural</VAR> if none is found
*/
public static String npgettext (ResourceBundle catalog, String msgctxt, String msgid, String msgid_plural, long n) {
String result = ngettextnull(catalog,msgctxt+CONTEXT_GLUE+msgid,n);
if (result != null)
return result;
// Default: English strings and Germanic plural rule.
return (n != 1 ? msgid_plural : msgid);
}
}
......@@ -8,6 +8,8 @@ import java.util.ResourceBundle;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import gnu.gettext.GettextResource;
import net.i2p.I2PAppContext;
import net.i2p.util.ConcurrentHashSet;
......@@ -102,6 +104,40 @@ public abstract class Translate {
}
}
/**
* Use GNU ngettext
* For .po file format see http://www.gnu.org/software/gettext/manual/gettext.html.gz#Translating-plural-forms
*
* @param n how many
* @param s singluar string, optionally with {0} e.g. "one tunnel"
* @param s plural string optionally with {0} e.g. "{0} tunnels"
* @since 0.7.14
*/
public static String getString(int n, String s, String p, I2PAppContext ctx, String bun) {
String lang = getLanguage(ctx);
if (lang.equals(TEST_LANG))
return TEST_STRING + '(' + n + ')' + TEST_STRING;
ResourceBundle bundle = null;
if (!lang.equals("en"))
bundle = findBundle(bun, lang);
String x;
if (bundle == null)
x = n == 1 ? s : p;
else
x = GettextResource.ngettext(bundle, s, p, n);
Object[] oArray = new Object[1];
oArray[0] = Integer.valueOf(n);
try {
MessageFormat fmt = new MessageFormat(x, new Locale(lang));
return fmt.format(oArray, new StringBuffer(), null).toString();
} catch (IllegalArgumentException iae) {
System.err.println("Bad format: sing: \"" + s +
"\" plural: \"" + p +
"\" lang: " + lang);
return "FIXME: " + s + ' ' + p + ',' + n;
}
}
/** @return lang in routerconsole.lang property, else current locale */
public static String getLanguage(I2PAppContext ctx) {
String lang = ctx.getProperty(PROP_LANG);
......
2010-05-27 zzz
* Translate: Add GNU ngettext (plurals) support
2010-05-26 zzz
* i2psnark: Listing fixes and cleanups;
icons on front page; tweak bw choker again
......
......@@ -18,7 +18,7 @@ public class RouterVersion {
/** deprecated */
public final static String ID = "Monotone";
public final static String VERSION = CoreVersion.VERSION;
public final static long BUILD = 12;
public final static long BUILD = 13;
/** for example "-test" */
public final static String EXTRA = "";
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment