* Translate: Add GNU ngettext (plurals) support
This commit is contained in:
269
core/java/src/gnu/gettext/GettextResource.java
Normal file
269
core/java/src/gnu/gettext/GettextResource.java
Normal file
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
Reference in New Issue
Block a user