package com.gmail.dpierron.tools.i18n; import com.gmail.dpierron.tools.Helper; import com.gmail.dpierron.tools.Utf8ResourceBundle; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import java.io.IOException; import java.text.MessageFormat; import java.util.*; /** * Manages the localization of the messages in an application */ public enum Localization { Enum("Enumerations"), Main("Localization"); Logger logger = LogManager.getLogger(Localization.class); private boolean localization_initialized = false; /** * the name of the resource bundle properties file used by this localization * resource */ private String localizationBundleName; /** * the resource bundle used by this localization resource */ private ResourceBundle currentLocalizations; /** * english currentLocalizations */ private ResourceBundle englishLocalizations; /** * The name of the last loaded locale */ private Locale lastLocalLanguage = null; private Locale profileLanguage = null; public void setProfileLanguage(Locale lang) { profileLanguage = lang; } private Locale getProfileLanguage() { return profileLanguage == null ? Locale.ENGLISH : profileLanguage; } /** * Get the loaded bundle for the last localization locale used * @return */ public ResourceBundle getBundle() { if (currentLocalizations == null) reloadLocalizations(); return currentLocalizations; } /** * Get the bundle for the English locale * (we always default to English if a dfferent locale fails) * @return */ public ResourceBundle getEnglishBundle() { if (englishLocalizations == null) reloadLocalizations(); return englishLocalizations; } /* * all currentLocalizations we have loaded so far. * Loaded once to avoid (expensive) reloads later. */ private Map<String,ResourceBundle> loadedResourceBundles; /** * Get the bundle for a specific locale * NOTE Default loaded locale is not changed * * For performance reasons we cache resourcebundles for * later use to avoid constant reloads from scratch. * * @param language * @return */ public ResourceBundle getBundle(Locale language) { if (loadedResourceBundles == null) { loadedResourceBundles = new HashMap<String, ResourceBundle>(); } ResourceBundle localizations = null; // We always want the English currentLocalizations loaded if (englishLocalizations == null) { englishLocalizations = getResourceBundle(localizationBundleName, Locale.ENGLISH, true); if (loadedResourceBundles.get(Locale.ENGLISH.getISO3Language()) == null) { loadedResourceBundles.put(Locale.ENGLISH.getISO3Language(), englishLocalizations); } } // No need to load english currentLocalizations twice! assert englishLocalizations != null : "Program Error: English currentLocalizations should always have loaded OK"; if (Helper.isNullOrEmpty(language) || language.equals(Locale.ENGLISH)) { localizations = englishLocalizations; } else { localizations = loadedResourceBundles.get(language.getISO3Language()); if (localizations == null) { localizations = getResourceBundle(localizationBundleName, language, true); if (! localizations.equals(englishLocalizations)) { loadedResourceBundles.put(language.getISO3Language(), localizations); } } } return localizations; } /** * Constructs a new {@link Localization} * We initally load the English localization as we always want that * * @param localizationBundleName the properties files to load */ private Localization(String localizationBundleName) { this.localizationBundleName = localizationBundleName; reloadLocalizations(Locale.ENGLISH); } // Cache results to improve efficency on subsequent calls private static Vector<Locale> localeAvailableLocalizations = null; private static Vector<String> iso2AvailableLicalizations = null; private static Vector<String> iso3AvailableLicalizations = null; /** * Get the kist of currentLocalizations that we have available * in the properties file for Calibre2opds indexed by locale. * * @return */ public Vector<Locale> getAvailableLocalizationsAsLocales() { if (Helper.isNullOrEmpty(localeAvailableLocalizations)) { localeAvailableLocalizations = new Vector<Locale>(); iso2AvailableLicalizations = new Vector<String>(); iso3AvailableLicalizations = new Vector<String>(); for (Locale locale : Locale.getAvailableLocales()) { ResourceBundle bundle = getResourceBundle(localizationBundleName, locale, false); if (bundle != null) { if (bundle.getLocale().equals(locale)) { localeAvailableLocalizations.add(locale); iso2AvailableLicalizations.add(locale.getLanguage()); iso3AvailableLicalizations.add(locale.getISO3Language()); } } } } return localeAvailableLocalizations; } /** * Get the list of available currentLocalizations indexed by the 2 character language code * @return */ public Vector<String> getAvailableLocalizationsAsIso2() { if (iso2AvailableLicalizations == null ) getAvailableLocalizationsAsLocales(); return iso2AvailableLicalizations; } /** * Get the list of available currentLocalizations indexed by the 3 character ISO langiage code * @return */ public Vector<String> getAvailableLocalizationsAsiso3() { if (iso3AvailableLicalizations == null ) getAvailableLocalizationsAsLocales(); return iso3AvailableLicalizations; } /** * Get the Localization Locale corresponding to a particular iso2 language code * * @param iso2 * @return locale, or null if not available */ public Locale getLocaleFromiso2(String iso2) { int i = getAvailableLocalizationsAsIso2().indexOf(iso2.substring(0, 2)); return i == -1 ? null : getAvailableLocalizationsAsLocales().get(i); } /** * Get the Localization Locale corresponding to a particular iso3 language code * * @param iso3 * @return Locale, or null if not available */ public Locale getLocaleFromIso3(String iso3) { int i = getAvailableLocalizationsAsiso3().indexOf(iso3); return i == -1 ? null : getAvailableLocalizationsAsLocales().get(i); } /** * Get a given resource bundle corresponding to the specified locale. * The caller can specify whether falling back to English is the * desired result if there is no specific bundle for the locale. * * @param name * @param locale Set to the locale we want. * @param englishIfNull Set to true if we should fall back to English if * the requested bundle cannot be found. * @return */ private ResourceBundle getResourceBundle(String name, Locale locale, boolean englishIfNull) { ResourceBundle result = null; try { if (locale != null) result = Utf8ResourceBundle.getBundle(name, locale); else result = Utf8ResourceBundle.getBundle(name); } catch (Exception e) { // do nothing } if (result == null && englishIfNull) return getResourceBundle(name, Locale.ENGLISH, false); // English is always there return result; } /** * Reloads the currentLocalizations according to the language set in Configuration manager Helper */ public void reloadLocalizations() { reloadLocalizations(getProfileLanguage()); } /** * forces the resource bundle to reload from the properties file * (use when the locale changes) * * @throws IOException */ public void reloadLocalizations(Locale language) { // We always want the English currentLocalizations loaded if (englishLocalizations == null) { englishLocalizations = getResourceBundle(localizationBundleName, Locale.ENGLISH, true); lastLocalLanguage = Locale.ENGLISH; if (currentLocalizations == null) currentLocalizations = englishLocalizations; } assert englishLocalizations != null : "Program Error: English currentLocalizations should always have loaded OK"; if (Helper.isNullOrEmpty(language)) { currentLocalizations = getResourceBundle(localizationBundleName, Locale.ENGLISH, true); } else { currentLocalizations = getResourceBundle(localizationBundleName, language, true); lastLocalLanguage = language; } localization_initialized = true; } /** * Get the text for the current localization that corresponds to the given key. * If it cannot be found in the current localization we fall back to English. * If it does not exist in English either we simply return the key name. * * @param key Key to be used * @return The localized text */ private String lookupText(String key) { try { return getBundle().getString(key); } catch (MissingResourceException e) { // try english try { return getEnglishBundle().getString(key); } catch (MissingResourceException ee) { return key; } } } /** * Get the text for the specified locale that corresponds to the given key. * If it cannot be found in the specified localewe fall back to English. * If it does not exist in English either we simply return the key name. * NOTE: Does not change the default for the currently loaded locale * * @param locale * @param key * @return */ private String lookupText(Locale locale, String key) { try { ResourceBundle b = getBundle(locale); return getBundle(locale).getString(key); } catch (MissingResourceException e) { // try english try { return getEnglishBundle().getString(key); } catch (MissingResourceException ee) { return key; } } } /** * fetches a localized message. * Optionally parameters can be added to embed in the * message. * * @param key the key in the resource bundle * @param parameters the parameters used to construct the message * @return the message */ public String getText(String key, Object... parameters) { String message = lookupText(key); if (message == null) return null; if (parameters.length != 0) { message = MessageFormat.format(message, parameters); } return message; } /** * fetches a locale specific message with (optional) parameters * * @param locale * @param key the key in the resource bundle * @param parameters the parameters used to construct the message * @return */ public String getText(Locale locale, String key, Object... parameters) { String message = lookupText(locale, key); if (message == null) return null; if (parameters.length != 0) { message = MessageFormat.format(message, parameters); } return message; } /** * Check if the localization bundle has been loaded * @return */ public boolean isLocalization_initialized() { return localization_initialized; } }