/* GNU gettext for Java * Copyright (C) 2001, 2007, 2015-2016 Free Software Foundation, Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 of the License, 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ 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); } }