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;
}
}