All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.xnap.commons.i18n.I18n Maven / Gradle / Ivy

Go to download

A library that supports gettext-based internationalization (i18n) of Java applications.

The newest version!
/*
 *  Gettext Commons
 *
 *  Copyright (C) 2005  Felix Berger
 *  Copyright (C) 2005  Steffen Pingel
 *
 *  This library 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 library 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 library; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
package org.xnap.commons.i18n;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.text.MessageFormat;
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.ResourceBundle;

/**
 * Provides methods for internationalization.
 * 

* To learn how message strings wrapped in one of the tr*() * methods can be extracted and localized, see this tutorial. * * @author Steffen Pingel * @author Felix Berger * @author Tammo van Lessen * @since 0.9 */ public class I18n { private static final String CONTEXT_GLUE = "\u0004"; /** * Reference to the current localization bundles. */ private volatile ResourceBundle bundle; /** * The locale of the strings used in the source code. * * @see #trc(String, String) */ private volatile Locale sourceCodeLocale = Locale.ENGLISH; private String baseName; private ClassLoader loader; private volatile Locale locale; /** * Constructs an I18n object for a resource bundle. * * @param bundle * must not be null * @throws NullPointerException * if bundle is null * @since 0.9 */ public I18n(ResourceBundle bundle) { setResources(bundle); } /** * Constructs an I18n object by calling {@link #setResources(String, Locale, * ClassLoader)}. * * @throws MissingResourceException * if the resource bundle could not be loaded * @throws NullPointerException * if one of the arguments is null * @since 0.9 */ public I18n(String baseName, Locale locale, ClassLoader loader) { setResources(baseName, locale, loader); } /** * Returns the current resource bundle. * * @since 0.9 */ public ResourceBundle getResources() { return bundle; } /** * Returns the locale this instance was created with. This can be different * from the locale of the resource bundle returned by * {@link #getResources()}. * * @return the locale or null, if this instance was directly created from a * resource bundle * @since 0.9 */ public Locale getLocale() { return locale; } /** * Sets a resource bundle to be used for message translations. *

* If this is called, the possibly previously specified class loader and * baseName are invalidated, since the bundle might be from a different * context. Subsequent calls to {@link #setLocale(Locale)} won't have any * effect. * * @since 0.9 */ public synchronized void setResources(ResourceBundle bundle) { if (bundle == null) { throw new NullPointerException(); } this.bundle = bundle; this.baseName = null; this.locale = bundle.getLocale(); this.loader = null; } /** * Tries to load a resource bundle using {@link * ResourceBundle#getBundle(java.lang.String, java.util.Locale, * java.lang.ClassLoader)}. * * @throws MissingResourceException * if the bundle could not be loaded * @throws NullPointerException * if one of the arguments is null * @since 0.9 */ public synchronized void setResources(String baseName, Locale locale, ClassLoader loader) { this.bundle = ResourceBundle.getBundle(baseName, locale, loader); this.baseName = baseName; this.locale = locale; this.loader = loader; } /** * Marks text to be translated, but doesn't return the * translation but text itself. * * @since 0.9 */ public static final String marktr(String text) { return text; } /** * Tries to load a resource bundle for the locale. *

* The resource bundle is then used for message translations. Note, you have * to retrieve all messages anew after a locale change in order for them to * be translated to the language specified by the new locale. *

* * @return false if there is not enough information for loading a new * resource bundle, see {@link #setResources(ResourceBundle)}. * @throws MissingResourceException * if the resource bundle for locale could not be * found * @throws NullPointerException * if locale is null * @since 0.9 */ public synchronized boolean setLocale(Locale locale) { if (baseName != null && loader != null) { setResources(baseName, locale, loader); return true; } else { this.locale = locale; } return false; } /** * Sets the locale of the text in the source code. *

* Only languages that have one singular and one plural form can be used as * source code locales, since {@link #trn(String, String, long)} takes * exactly these two forms as parameters. * * @param locale * the locale * @throws NullPointerException * if locale is null * @see #trc(String, String) * @since 0.9 */ public void setSourceCodeLocale(Locale locale) { if (locale == null) { throw new NullPointerException("locale must not be null"); } sourceCodeLocale = locale; } /** * Returns text translated into the currently selected * language. Every user-visible string in the program must be wrapped into * this function. * * @param text * text to translate * @return the translation * @since 0.9 */ public final String tr(String text) { try { return bundle.getString(text); } catch (MissingResourceException e) { return text; } } /** * Returns text translated into the currently selected * language. *

* Occurrences of {number} placeholders in text are replaced by * objects. *

* Invokes * {@link MessageFormat#format(java.lang.String, java.lang.Object[])}. * * @param text * text to translate * @param objects * arguments to MessageFormat.format() * @return the translated text * @since 0.9 */ public final String tr(String text, Object[] objects) { return MessageFormat.format(tr(text), objects); } /** * Convenience method that invokes {@link #tr(String, Object[])}. * * @since 0.9 */ public final String tr(String text, Object o1) { return tr(text, new Object[]{ o1 }); } /** * Convenience method that invokes {@link #tr(String, Object[])}. * * @since 0.9 */ public final String tr(String text, Object o1, Object o2) { return tr(text, new Object[]{ o1, o2 }); } /** * Convenience method that invokes {@link #tr(String, Object[])}. * * @since 0.9 */ public final String tr(String text, Object o1, Object o2, Object o3) { return tr(text, new Object[]{ o1, o2, o3 }); } /** * Convenience method that invokes {@link #tr(String, Object[])}. * * @since 0.9 */ public final String tr(String text, Object o1, Object o2, Object o3, Object o4) { return tr(text, new Object[]{ o1, o2, o3, o4 }); } /** * Returns the plural form for n of the translation of * text. * * @param text * the key string to be translated. * @param pluralText * the plural form of text. * @param n * value that determines the plural form * @return the translated text * @since 0.9 */ public final String trn(String text, String pluralText, long n) { try { return trnInternal(bundle, text, pluralText, n); } catch (MissingResourceException e) { return (n == 1) ? text : pluralText; } } /** * Returns the plural form for n of the translation of * text. * * @param text * the key string to be translated. * @param pluralText * the plural form of text. * @param n * value that determines the plural form * @param objects * object args to be formatted and substituted. * @return the translated text * @since 0.9 */ public final String trn(String text, String pluralText, long n, Object[] objects) { return MessageFormat.format(trn(text, pluralText, n), objects); } /** * Overloaded method that invokes * {@link #trn(String, String, long, Object[])} passing Object * arguments as an array. * * @since 0.9 */ public final String trn(String text, String pluralText, long n, Object o1) { return trn(text, pluralText, n, new Object[]{ o1 }); } /** * Overloaded method that invokes * {@link #trn(String, String, long, Object[])} passing Object * arguments as an array. * * @since 0.9 */ public final String trn(String text, String pluralText, long n, Object o1, Object o2) { return trn(text, pluralText, n, new Object[]{ o1, o2 }); } /** * Overloaded method that invokes * {@link #trn(String, String, long, Object[])} passing Object * arguments as an array. * * @since 0.9 */ public final String trn(String text, String pluralText, long n, Object o1, Object o2, Object o3) { return trn(text, pluralText, n, new Object[]{ o1, o2, o3 }); } /** * Overloaded method that invokes * {@link #trn(String, String, long, Object[])} passing Object * arguments as an array. * * @since 0.9 */ public final String trn(String text, String pluralText, long n, Object o1, Object o2, Object o3, Object o4) { return trn(text, pluralText, n, new Object[]{ o1, o2, o3, o4 }); } /** * Returns the plural form for n of the translation of ??? * * Based on GettextResource.java that is part of GNU gettext for Java * Copyright (C) 2001 Free Software Foundation, Inc. * * @param bundle a ResourceBundle * @param text the key string to be translated, an ASCII string * @param pluralText its English plural form * @return the translation of text depending on n, * or text or pluralText if none is found */ private static String trnInternal(ResourceBundle orgBundle, String text, String pluralText, long n) { ResourceBundle bundle = orgBundle; do { boolean isGetTextBundle = false; boolean hasPluralHandling = false; Method handleGetObjectMethod = null; Method getParentMethod = null; Method lookupMethod = null; Method pluralEvalMethod = null; try { handleGetObjectMethod = bundle.getClass().getMethod("handleGetObject", new Class[]{ String.class }); getParentMethod = bundle.getClass().getMethod("getParent", new Class[0]); isGetTextBundle = Modifier.isPublic(handleGetObjectMethod.getModifiers()); lookupMethod = bundle.getClass().getMethod("lookup", new Class[]{ String.class }); pluralEvalMethod = bundle.getClass().getMethod("pluralEval", new Class[]{ Long.TYPE }); hasPluralHandling = true; } catch (Exception e) {} if (isGetTextBundle) { // GNU gettext generated bundle if (hasPluralHandling) { // GNU gettext generated bundle w/ plural handling try { Object localValue = lookupMethod.invoke(bundle, new Object[]{ text }); if (localValue.getClass().isArray()) { String[] pluralforms = (String[])localValue; long index = 0; try { index = ((Long)pluralEvalMethod.invoke(bundle, new Object[]{ new Long(n) })) .longValue(); if (!(index >= 0 && index < pluralforms.length)) { index = 0; } } catch (IllegalAccessException e) {} return pluralforms[(int)index]; } else { // Found the value. It doesn't depend on n in this // case. return (String)localValue; } } catch (Exception e) {} } else { // GNU gettext generated bundle w/o plural handling try { Object localValue = handleGetObjectMethod.invoke(bundle, new Object[]{ text }); if (localValue != null) { return (String)localValue; } } catch (Exception e) {} } bundle = null; try { bundle = (ResourceBundle)getParentMethod.invoke(bundle, new Object[0]); } catch (Exception e) {} } else { return bundle.getString(text); } } while (bundle != null); throw new MissingResourceException("Can not find resource for key " + text + " in bundle " + orgBundle.getClass().getName(), orgBundle.getClass().getName(), text); } /** * Disambiguates translation keys. * * @param context * the context of the text to be translated * @param text * the ambiguous key message in the source locale * @return text if the locale of the underlying resource * bundle equals the source code locale, the disambiguated * translation of text otherwise * * @see #setSourceCodeLocale(Locale) * @since 0.9 */ public final String trc(String context, String text) { if (sourceCodeLocale.equals(getResources().getLocale())) { return text; } else { String key = context + CONTEXT_GLUE + text; String translated = tr(key); // if no translation was found return text in source locale return translated == key ? text : translated; } } /** * Returns the plural form for n of the translation of * text. * * @param context * the context of the message to disambiguate it when translating * @param singularText * the key string to be translated. * @param pluralText * the plural form of text. * @param n * value that determines the plural form * @return the translated text * @since 0.9.5 */ public final String trnc(String context, String singularText, String pluralText, long n) { try { return trnInternal(bundle, context + CONTEXT_GLUE + singularText, pluralText, n); } catch (MissingResourceException e) { return (n == 1) ? singularText : pluralText; } } /** * Returns the plural form for n of the translation of * text. * * @param context * the context of the message to disambiguate it when translating * @param singularText * the key string to be translated. * @param pluralText * the plural form of text. * @param n * value that determines the plural form * @param objects * object args to be formatted and substituted. * @return the translated text * @since 0.9 */ public final String trnc(String context, String singularText, String pluralText, long n, Object[] objects) { return MessageFormat.format(trnc(context, singularText, pluralText, n), objects); } /** * Overloaded method that invokes * {@link #trnc(String, String, String, long, Object[])} passing obj * arguments as an array. * * @since 0.9.5 */ public final String trnc(String comment, String singularText, String pluralText, long n, Object obj) { return MessageFormat.format(trnc(comment, singularText, pluralText, n), new Object[] { obj }); } /** * Overloaded method that invokes * {@link #trnc(String, String, String, long, Object[])} passing obj1 and obj2 * arguments as an array. * * @since 0.9.5 */ public final String trnc(String comment, String singularText, String pluralText, long n, Object obj1, Object obj2) { return MessageFormat.format(trnc(comment, singularText, pluralText, n), new Object[] { obj1, obj2 }); } /** * Overloaded method that invokes * {@link #trnc(String, String, String, long, Object[])} passing obj1, obj2 and obj3 * arguments as an array. * * @since 0.9.5 */ public final String trnc(String comment, String singularText, String pluralText, long n, Object obj1, Object obj2, Object obj3) { return MessageFormat.format(trnc(comment, singularText, pluralText, n), new Object[] { obj1, obj2, obj3 }); } /** * Overloaded method that invokes * {@link #trnc(String, String, String, long, Object[])} passing obj1, obj2, obj3 and obj4 * arguments as an array. * * @since 0.9.5 */ public final String trnc(String comment, String singularText, String pluralText, long n, Object obj1, Object obj2, Object obj3, Object obj4) { return MessageFormat.format(trnc(comment, singularText, pluralText, n), new Object[] { obj1, obj2, obj3, obj4 }); } }