/*
* Translatrix.java
*
* Created on March 11, 2004, 1:57 PM
*/
package lu.tudor.santec.i18n;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.List;
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import de.lazyzero.kkMulticopterFlashTool.KKMulticopterFlashTool;
/**
* The Translatrix class is a kind of translation engine for localized
* applications. It handles the language resource bundles required by an
* application and treats them as one. Usage of the Translatrix class is as
* follows:
*
* 1.) The Translatrix class should be used in a static context, i.e. you don't
* need to instantiate it. 2.) First thing to do is to call the
* loadSupportedLocales method. Doing so, enables the Translatrix to provide a
* default Locale if none was specified and to check specified Locales against
* supported Locales. 3.) Bundles need to be added only once. Subsequent calls
* to SetLocale will automatically reload the previously specified Bundles for
* the given Language.
*
* @author nico.mack@tudor.lu
* @version 1.0
*/
// ***************************************************************************
// * Class Definition and Members *
// ***************************************************************************
public class Translatrix {
private static Locale m_Locale = Locale.getDefault();
private static Hashtable<String,String> m_Translations = new Hashtable<String,String>();
private static Hashtable<String,String> m_Defaults = new Hashtable<String,String>();
private static List<String> m_Bundles = new ArrayList<String>();
private static List<Locale> m_SupportedLocales = new ArrayList<Locale>();
private static boolean m_DefaultWhenMissing = false;
private static String c_Null = "NULL";
private static Pattern c_KeyPattern = Pattern.compile("^language_(\\d{1,2})$",
Pattern.CASE_INSENSITIVE);
private static Pattern c_LocalePattern = Pattern.compile(
"^([a-zA-Z]{2})_([a-zA-Z]{2})$", Pattern.CASE_INSENSITIVE);
private static Pattern c_PlaceHolderPattern = Pattern.compile(
"(\\$)(\\d+)", Pattern.CASE_INSENSITIVE);
private static Logger logger = KKMulticopterFlashTool.getLogger();
// ***************************************************************************
// * Constructor *
// ***************************************************************************
// ---------------------------------------------------------------------------
/**
* Creates a new empty instance of Translatrix. This method will hardly
* every be called as the Translatrix is most often used in a static context
*/
// ---------------------------------------------------------------------------
public Translatrix() {
}
// ***************************************************************************
// * Class Primitives *
// ***************************************************************************
// ---------------------------------------------------------------------------
/**
* loads the specified bundle for the specified Locale.
*
* @param p_Bundle
* specifies the language resource bundle to load
* @param p_Locale
* specifies the locale to load the bundle for. Setting p_Locale
* to <CODE>null</CODE> will load the default bundle
* @return <CODE>true</CODE> if the specified bundle was successfully
* loaded, <CODE>false</CODE> otherwise
*/
// ---------------------------------------------------------------------------
private static boolean loadBundle(String p_Bundle, Locale p_Locale) {
ResourceBundle l_Bundle = null;
Enumeration<String> l_Keys;
String l_Key, l_Value;
boolean l_LoadedSuccessfully = false;
try {
if (p_Locale == null)
l_Bundle = ResourceBundle.getBundle(p_Bundle);
else
l_Bundle = ResourceBundle.getBundle(p_Bundle, p_Locale);
if (l_Bundle != null) {
l_Keys = l_Bundle.getKeys();
while (l_Keys.hasMoreElements()) {
l_Key = l_Keys.nextElement();
l_Value = l_Bundle.getString(l_Key);
if (l_Value != null && l_Value.trim().length() > 0)
m_Translations.put(l_Key, l_Value);
}
l_LoadedSuccessfully = true;
}
loadDefaultBundle(p_Bundle);
} catch (MissingResourceException p_Exception) {
logException(
"MissingResourceException while loading language file",
p_Exception);
}
return l_LoadedSuccessfully;
}
// ---------------------------------------------------------------------------
private static void loadDefaultBundle(String p_Bundle) {
ResourceBundle l_Bundle = null;
Enumeration<String> l_Keys;
String l_Key, l_Value;
// // check if defaults exist....
// URL defaults = Translatrix.class.getResource(p_Bundle +
// ".properties");
// if (defaults == null) {
// System.out.println("No default translations found at: " + p_Bundle +
// ".properties .... trying en_US");
// }
//
// defaults = Translatrix.class.getResource(p_Bundle +
// "_en_US.properties");
// l_Bundle = ResourceBundle.getBundle(p_Bundle);
l_Bundle = ResourceBundle.getBundle(p_Bundle, new Locale("en", "US"));
if (l_Bundle == null || !l_Bundle.getKeys().hasMoreElements()) {
logger.info("No default/en_US translations found at: " + p_Bundle
+ "_en_US.properties .... skipping");
return;
}
if (l_Bundle != null) {
l_Keys = l_Bundle.getKeys();
while (l_Keys.hasMoreElements()) {
l_Key = l_Keys.nextElement();
l_Value = l_Bundle.getString(l_Key);
if (l_Value != null && l_Value.trim().length() > 0)
m_Defaults.put(l_Key, l_Value);
}
}
}
// ---------------------------------------------------------------------------
/**
* Prints the given message and a stack trace of the specified exception to
* Stderr.
*
* @param p_Message
* Message to log with exception stack trace
* @param p_Exception
* Exception to be logged
*/
// ---------------------------------------------------------------------------
private static void logException(String p_Message, Exception p_Exception) {
logger.warning(p_Message + ": " + p_Exception);
}
// ---------------------------------------------------------------------------
/**
* checks whether the specified Locale is one of the supported Locales
*
* @return <CODE>true</CODE> if specified Locale is part of the supported
* Locales, <CODE>false</CODE> otherwise
*/
// ---------------------------------------------------------------------------
private static boolean isSupportedLocale(Locale p_Locale) {
Locale l_Locale;
int l_LocaleID;
boolean l_LocaleIsSupported = false;
for (l_LocaleID = 0; l_LocaleID < m_SupportedLocales.size(); l_LocaleID++) {
l_Locale = m_SupportedLocales.get(l_LocaleID);
if (p_Locale.equals(l_Locale))
l_LocaleIsSupported = true;
}
return l_LocaleIsSupported;
}
// ---------------------------------------------------------------------------
/**
* sets current translation locale to specified one
*
* @param p_Locale
* New translation Locale
*/
// ---------------------------------------------------------------------------
public static void setLocale(Locale p_Locale) {
String l_Bundle;
int l_BundleID;
if (isSupportedLocale(p_Locale)) {
m_Locale = p_Locale;
m_Translations = new Hashtable<String,String>();
for (l_BundleID = 0; l_BundleID < m_Bundles.size(); l_BundleID++) {
l_Bundle = m_Bundles.get(l_BundleID);
loadBundle(l_Bundle, m_Locale);
}
} else
logger.info("Unsupported Locale " + p_Locale.toString()
+ " specified in call to setLocale()");
}
// ---------------------------------------------------------------------------
/**
* sets current translation locale to specified one
*
* @param p_Locale
* New translation Locale expressed as string in the xx_YY format
*/
// ---------------------------------------------------------------------------
public static void setLocale(String p_Locale) {
Matcher l_Matcher = c_LocalePattern.matcher(p_Locale);
if (l_Matcher.matches())
setLocale(new Locale(l_Matcher.group(1), l_Matcher.group(2)));
}
// ---------------------------------------------------------------------------
/**
* Returns the currently set translation locale
*
* @return currently set translation locale
*/
// ---------------------------------------------------------------------------
public static Locale getLocale() {
return m_Locale;
}
// ---------------------------------------------------------------------------
/**
* Returns the default Locale for this application
*
* @return the default Locale for this application
*/
// ---------------------------------------------------------------------------
public static Locale getDefaultLocale() {
if ((m_SupportedLocales != null) && (m_SupportedLocales.size() > 0)) {
return m_SupportedLocales.get(0);
}
return Locale.getDefault();
}
// ---------------------------------------------------------------------------
/**
* Adds a new language resource bundle to the Translatrix
*
* @param p_Bundle
* specifies the bundle to add
*/
// ---------------------------------------------------------------------------
public static void addBundle(String p_Bundle) {
if (loadBundle(p_Bundle, m_Locale))
m_Bundles.add(p_Bundle);
}
// ---------------------------------------------------------------------------
/**
* loads the supported locales bundle specified by p_SupportedLocalesBundle.
* Supported locales resource file defines the locales supported by this
* application.
*
* @param p_SupportedLocalesBundle
* specifies the resource bundle containing the supported locales
* @param p_LoaderClass
* specifies the class to use class loader of to load supported
* Locales file with
*/
// ---------------------------------------------------------------------------
public static void loadSupportedLocales(String p_SupportedLocalesBundle) {
ResourceBundle l_Bundle;
Matcher l_Matcher;
Enumeration<String> l_Keys;// <String>
Hashtable<Integer, Locale> l_Locales;// <Integer,Locale>
String l_Key;
Integer l_ID;
int l_Index;
try {
l_Bundle = ResourceBundle.getBundle(p_SupportedLocalesBundle,
Locale.getDefault());
} catch (Exception p_Exception) {
logException("Failed to load supportedLocales file", p_Exception);
return;
}
if (l_Bundle != null) {
l_Locales = new Hashtable<Integer, Locale>();// <Integer,Locale>
l_Keys = l_Bundle.getKeys();
// Read supported Locales properties from property file. Properties
// are not read in an ordered fashion.
while (l_Keys.hasMoreElements()) {
l_Key = l_Keys.nextElement();
l_Matcher = c_KeyPattern.matcher(l_Key);
if (l_Matcher.matches()) {
l_ID = new Integer(l_Matcher.group(1));
l_Matcher = c_LocalePattern.matcher(l_Bundle
.getString(l_Key));
if (l_Matcher.matches()) {
l_Locales.put(l_ID, new Locale(l_Matcher.group(1),
l_Matcher.group(2)));
}
}
}
if (l_Locales.size() > 0) {
m_SupportedLocales = new ArrayList<Locale>();
for (l_Index = 0; l_Index < l_Locales.size(); l_Index++) {
l_ID = new Integer(l_Index);
if (l_Locales.containsKey(l_ID))
m_SupportedLocales.add(l_Locales.get(l_ID));
}
}
}
}
// ---------------------------------------------------------------------------
/**
* Returns a Vector holding all the locales supported by this application
*
* @return Vector holding all the locales supported by this application
*/
// ---------------------------------------------------------------------------
public static List<Locale> getSupportedLocales()
{
return m_SupportedLocales;
}
// ---------------------------------------------------------------------------
/**
* Returns a Vector holding the names of the Bundles added so far
*
* @return Vector holding the names of the Bundles loaded so far
*/
// ---------------------------------------------------------------------------
public static List<String> getBundles()
{
return m_Bundles;
}
// ---------------------------------------------------------------------------
/**
* returns a vector holding the translation keys stored in the Translatrix
*
* @return vector holding the translation keys stored in the Translatrix
*/
// ---------------------------------------------------------------------------
public static List<String> getTranslationKeys()
{
List<String> l_Keys;
Enumeration<String> l_TranslationKeys;
l_Keys = new ArrayList<String>();
for (l_TranslationKeys = m_Translations.keys(); l_TranslationKeys.hasMoreElements();)
l_Keys.add(l_TranslationKeys.nextElement());
return l_Keys;
}
// ---------------------------------------------------------------------------
/**
* Checks whether the specified key is known to the Translatrix, i.e. a
* translation exists for the given key.
*
* @param p_Key
* Specifies the key in the property file referencing the
* translation string
* @return <code>true</code> if a translation exists for the specified key,
* <code>false</code> otherwise.
*/
// ---------------------------------------------------------------------------
public static boolean hasTranslationFor(String p_Key) {
if ((p_Key != null) && (m_Translations != null)) {
return m_Translations.containsKey(p_Key);
}
return false;
}
// ---------------------------------------------------------------------------
/**
* Returns the translation String for the given Key in the currently set
* translation Locale
*
* @param p_Key
* Specifies the key in the property file referencing the
* translation string
* @return the translation String for the given Key in the currently set
* translation Locale. If the specified key is <code>null</code> or
* <code>""</code>, the method returns the p_Key itself of "null".
*/
// ---------------------------------------------------------------------------
public static String getTranslationString(String p_Key) {
String l_Translation = p_Key;
if (p_Key == null)
return "null";
logger.fine("key: " + p_Key);
if (m_Translations != null) {
l_Translation = m_Translations.get(p_Key);
if ((l_Translation == null || l_Translation.trim().equals(""))
&& isDefaultWhenMissing()) {
l_Translation = m_Defaults.get(p_Key);
}
if (l_Translation == null || l_Translation.trim().equals("")) {
l_Translation = p_Key;
}
}
logger.fine("\tTranslation: " + l_Translation);
return l_Translation;
}
// ---------------------------------------------------------------------------
/**
* Returns the translation String for the given Key in the currently set
* translation Locale
*
* @param p_Key
* Specifies the key in the property file referencing the
* translation string
* @return the translation String for the given Key in the currently set
* translation Locale. If the specified key is <code>null</code> or
* <code>""</code>, the method returns the p_Key itself of "null".
*/
// ---------------------------------------------------------------------------
public static String getDefaultString(String p_Key) {
String l_Translation = p_Key;
if (p_Key == null)
return "null";
logger.fine("key: " + p_Key);
if (m_Defaults != null) {
l_Translation = m_Defaults.get(p_Key);
if (l_Translation == null || l_Translation.trim().equals("")) {
l_Translation = p_Key;
}
}
logger.fine("\tTranslation: " + l_Translation);
return l_Translation;
}
/**
* This is a alias for the getTranslationString Method.
*
* @see getTranslationString
* @param p_Key
* Specifies the key in the property file referencing the
* translation string
* @return the translation String for the given Key in the currently set
* translation Locale. If the specified key is <code>null</code> or
* <code>""</code>, the method returns the p_Key itself or "null".
*/
public static String _(String p_Key) {
return getTranslationString(p_Key);
}
// ---------------------------------------------------------------------------
/**
* Returns the translation String for the given Key in the currently set
* translation Locale. Occurences of $x ($0, $1, $2 ...) placeholders in
* translation String will be replaced with the corresponding filler Strings
* specified by p_Filler array.
*
* @param p_Key
* Specifies the key in the property file referencing the
* translation string
* @param p_Filler
* Specifies the Strings to use as replacement for matching
* placeholder occurences
* @return the translation String for the given Key in the currently set
* translation Locale
*/
// ---------------------------------------------------------------------------
public static String getTranslationString(String p_Key, String[] p_Filler) {
String l_Translation = p_Key;
int l_Index;
StringBuffer l_Buffer;
String l_Filler;
Matcher l_Matcher;
if (p_Key == null)
return "null";
if (m_Translations != null) {
l_Translation = m_Translations.get(p_Key);
if (l_Translation != null) {
if ((p_Filler != null) && (p_Filler.length > 0)) {
l_Buffer = new StringBuffer();
l_Matcher = c_PlaceHolderPattern.matcher(l_Translation);
while (l_Matcher.find()) {
l_Index = Integer.valueOf(l_Matcher.group(2))
.intValue();
if ((l_Index >= 0) && (l_Index < p_Filler.length)) {
l_Filler = (p_Filler[l_Index] != null) ? p_Filler[l_Index] : c_Null;
l_Matcher.appendReplacement(l_Buffer, l_Filler);
}
}
l_Matcher.appendTail(l_Buffer);
l_Translation = l_Buffer.toString();
}
} else {
l_Translation = p_Key;
}
}
return l_Translation;
}
// ---------------------------------------------------------------------------
/**
* Returns the translation String for the given Key in the currently set
* translation Locale. Occurences of $x ($0, $1, $2 ...) placeholders in
* translation String will be replaced with the corresponding filler Strings
* specified by p_Filler array.
*
* @see getTranslationString
* @param p_Key
* Specifies the key in the property file referencing the
* translation string
* @param p_Filler
* Specifies the Strings to use as replacement for matching
* placeholder occurences
* @return the translation String for the given Key in the currently set
* translation Locale
*/
// ---------------------------------------------------------------------------
public static String _(String p_Key, String[] p_Filler) {
return getTranslationString(p_Key, p_Filler);
}
public static boolean isDefaultWhenMissing() {
return m_DefaultWhenMissing;
}
public static void setDefaultWhenMissing(boolean defaultWhenMissing) {
m_DefaultWhenMissing = defaultWhenMissing;
}
// ***************************************************************************
// * End Of Class *
// ***************************************************************************
}