From dc3378d0842a85ce5fafbe40c6bcb21793af4889 Mon Sep 17 00:00:00 2001 From: zzz <zzz@mail.i2p> Date: Thu, 27 May 2010 00:38:32 +0000 Subject: [PATCH] * Translate: Add GNU ngettext (plurals) support --- LICENSE.txt | 3 + .../src/org/klomp/snark/I2PSnarkUtil.java | 5 + .../org/klomp/snark/web/I2PSnarkServlet.java | 61 ++-- apps/i2psnark/locale/messages_ru.po | 2 + apps/i2psnark/locale/messages_zh.po | 1 + .../i2p/router/web/ConfigTunnelsHelper.java | 21 +- .../src/net/i2p/router/web/HelperBase.java | 5 + .../java/src/net/i2p/router/web/Messages.java | 5 + apps/routerconsole/locale/messages_ru.po | 2 + apps/routerconsole/locale/messages_zh.po | 1 + .../java/src/gnu/gettext/GettextResource.java | 269 ++++++++++++++++++ core/java/src/net/i2p/util/Translate.java | 36 +++ history.txt | 3 + .../src/net/i2p/router/RouterVersion.java | 2 +- 14 files changed, 377 insertions(+), 39 deletions(-) create mode 100644 core/java/src/gnu/gettext/GettextResource.java 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 { " <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 ca02857f9..853edcf36 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 283f79d12..860e93fcf 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 4d007e9d7..d3579f17c 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 c43caa550..1d1d03d59 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 39b7c2380..427ac9d8a 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 a22569b68..89ef1782b 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 89ab35634..f5fd269e6 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 000000000..727508f62 --- /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 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 = ""; -- GitLab