/* * 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 * // *************************************************************************** }