diff --git a/LICENSE.txt b/LICENSE.txt index d178230aa..53e248f9b 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -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: diff --git a/apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java b/apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java index a82debab3..fdae016fc 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java +++ b/apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java @@ -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); + } } diff --git a/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java b/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java index af793721d..5ec39ff90 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java +++ b/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java @@ -269,10 +269,10 @@ public class I2PSnarkServlet extends Default { "
+ * Using the GNU gettext approach, compiled message catalogs are normal + * Java ResourceBundle classes and are thus interoperable with standard + * ResourceBundle based code. + *
+ * The main differences between the Sun ResourceBundle approach and the + * GNU gettext approach are: + *
\
unnnn
syntax. Very few editors
+ * can natively display international characters in this format. In the
+ * GNU gettext approach, the translation files are called
+ * "Resource.locale.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).
+ * ResourceBundle.getString
throws a
+ * MissingResourceException
when no translation is found.
+ * In the GNU gettext approach, the gettext
function
+ * returns the (English) message key in that case.
+ * ngettext
function.
+ *
+ * To compile GNU gettext message catalogs into Java ResourceBundle classes,
+ * the msgfmt
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 null
+ * 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 msgid.
+ * @param catalog a ResourceBundle
+ * @param msgid the key string to be translated, an ASCII string
+ * @return the translation of msgid, or msgid 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
+ * null
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 n of the translation of
+ * msgid.
+ * @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 msgid depending on n,
+ * or msgid or msgid_plural 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 msgid in the context of
+ * msgctxt.
+ * @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 msgid, or msgid 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 n of the translation of
+ * msgid in the context of msgctxt.
+ * @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 msgid depending on n,
+ * or msgid or msgid_plural 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);
+ }
+}
diff --git a/core/java/src/net/i2p/util/Translate.java b/core/java/src/net/i2p/util/Translate.java
index 799b89c00..40d07f76c 100644
--- a/core/java/src/net/i2p/util/Translate.java
+++ b/core/java/src/net/i2p/util/Translate.java
@@ -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);
diff --git a/history.txt b/history.txt
index a16a2758c..b5de3e81c 100644
--- a/history.txt
+++ b/history.txt
@@ -1,3 +1,6 @@
+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
diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java
index e36cd3b14..7f4edb975 100644
--- a/router/java/src/net/i2p/router/RouterVersion.java
+++ b/router/java/src/net/i2p/router/RouterVersion.java
@@ -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 = "";