diff --git a/LICENSE.txt b/LICENSE.txt index d178230aa0a14527224de2f1a0a5c2adcbe068a7..53e248f9b07603e7f2221d873f2f91a9a9a211fc 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 a82debab367c21f0ff23b3d1a415a168b8c266f4..fdae016fca78d6f10a3167a638df744d7b5484ab 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 af793721de52a3bedabba4c83fe190d381b50de9..5ec39ff90a60453d750122bbc9e2fe5d786f650a 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 { " <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> </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) + "…"; @@ -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 :) diff --git a/apps/i2psnark/locale/messages_ru.po b/apps/i2psnark/locale/messages_ru.po index ca02857f9b276508c5f44e8d0f8b0fd827188db3..853edcf36c3ec844b4bb19887144bc9fb5833b2d 100644 --- a/apps/i2psnark/locale/messages_ru.po +++ b/apps/i2psnark/locale/messages_ru.po @@ -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 diff --git a/apps/i2psnark/locale/messages_zh.po b/apps/i2psnark/locale/messages_zh.po index 283f79d121d427aae8cfae9d68d0699e13f60ed6..860e93fcff8126664b55bc78874f1cc01280b35d 100644 --- a/apps/i2psnark/locale/messages_zh.po +++ b/apps/i2psnark/locale/messages_zh.po @@ -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 diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigTunnelsHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigTunnelsHelper.java index 4d007e9d76162b5b9356ea4c05d91167da4d80ed..d3579f17c56be33ef7f4a85ae8538c0933c6ac84 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigTunnelsHelper.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigTunnelsHelper.java @@ -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; + } } 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 c43caa550adb91cdac14869abdf8c12ae29567dc..1d1d03d59239ef787a52017166f5e48d197d8102 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/HelperBase.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/HelperBase.java @@ -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. diff --git a/apps/routerconsole/java/src/net/i2p/router/web/Messages.java b/apps/routerconsole/java/src/net/i2p/router/web/Messages.java index 39b7c2380e9ad20c3094581652d9e8755c18c329..427ac9d8aefc78f1022dfa16332a6fc207d7bcb3 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/Messages.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/Messages.java @@ -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); + } } diff --git a/apps/routerconsole/locale/messages_ru.po b/apps/routerconsole/locale/messages_ru.po index a22569b68801fd5ee354346eb656324ccafc166b..89ef1782baa999eab554f6b0d972669690d9296b 100644 --- a/apps/routerconsole/locale/messages_ru.po +++ b/apps/routerconsole/locale/messages_ru.po @@ -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 diff --git a/apps/routerconsole/locale/messages_zh.po b/apps/routerconsole/locale/messages_zh.po index 89ab35634c0ec07cfdc47b4acb6b413b9eed18b4..f5fd269e66e46f97461b1e61521410026c40c7e7 100644 --- a/apps/routerconsole/locale/messages_zh.po +++ b/apps/routerconsole/locale/messages_zh.po @@ -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 diff --git a/core/java/src/gnu/gettext/GettextResource.java b/core/java/src/gnu/gettext/GettextResource.java new file mode 100644 index 0000000000000000000000000000000000000000..727508f62eb069dff9bdf6995ab043461bf2b1fc --- /dev/null +++ b/core/java/src/gnu/gettext/GettextResource.java @@ -0,0 +1,269 @@ +/* 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); + } +} diff --git a/core/java/src/net/i2p/util/Translate.java b/core/java/src/net/i2p/util/Translate.java index 799b89c00cc2e0b5e0c5af4f22cfc7e21a93f4fb..40d07f76ce59c834ad88f31c894a8d8cab54125e 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 a16a2758c5feb42aa1d0e963b9490523d65a366e..b5de3e81cab42bce73822b73f41fc15c92885339 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 e36cd3b14352a16a1e79c5fea474cb2f20cbaa5c..7f4edb97520b081fa27255ad38c4da1068327822 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 = "";