Blob Blame History Raw
/* GNU gettext for Java
 * Copyright (C) 2001, 2007, 2015 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);
  }
}