io.ultreia.java4all.i18n.I18n Maven / Gradle / Ivy
package io.ultreia.java4all.i18n;
/*-
* #%L
* I18n :: Runtime
* %%
* Copyright (C) 2018 Code Lutin, Ultreia.io
* %%
* This program 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 3 of the
* License, or (at your option) any later version.
*
* This program 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 General Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* .
* #L%
*/
import io.ultreia.java4all.i18n.runtime.I18nConfiguration;
import io.ultreia.java4all.i18n.runtime.I18nFilter;
import io.ultreia.java4all.i18n.runtime.I18nLanguage;
import io.ultreia.java4all.i18n.runtime.I18nLanguageProvider;
import io.ultreia.java4all.i18n.runtime.boot.I18nBootLoader;
import io.ultreia.java4all.i18n.runtime.format.I18nMessageFormatter;
import io.ultreia.java4all.i18n.spi.I18nLocaleHelper;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.Arrays;
import java.util.Locale;
import java.util.Objects;
/**
* Created by tchemit on 06/11/2018.
*
* @author Tony Chemit - [email protected]
*/
@SuppressWarnings("WeakerAccess")
public class I18n {
private static final Logger log = LogManager.getLogger(I18n.class);
/**
* Optional filter.
*/
private static I18nFilter filter;
/**
* Boot loader used to init I18n.
*/
private static I18nBootLoader bootLoader;
/**
* Boot loader used to init I18n.
*/
private static I18nLanguageProvider languageProvider;
/**
* Formatter to apply on each translation.
*
* This formatter can not be configured directly here, but must be setted
* into the {@code initializer} used to configure the I18n system.
*
* @see I18nConfiguration#setMessageFormatter(I18nMessageFormatter)
* @since 2.4
*/
private static I18nMessageFormatter messageFormatter;
public static I18nFilter getFilter() {
return filter;
}
public static void setFilter(I18nFilter filter) {
I18n.filter = filter;
}
public static I18nMessageFormatter getMessageFormatter() {
return messageFormatter;
}
public static I18nBootLoader getBootLoader() {
return bootLoader;
}
public static I18nLanguageProvider getLanguageProvider() {
return languageProvider;
}
public static void init(I18nBootLoader bootLoader, Locale locale) {
if (bootLoader == null) {
bootLoader = I18nBootLoader.DEFAULT_BOOT_LOADER;
}
if (locale == null) {
locale = I18nLocaleHelper.newLocale(null, null);
}
initStore(bootLoader, locale);
}
public static void reload() {
checkInit();
// get back initializer
I18nBootLoader initializer = bootLoader;
// get back default locale
Locale locale = languageProvider.getCurrentLocale();
// close the current store
close();
// reload store
init(initializer, locale);
}
/**
* Obtain the default locale setted in I18n. This very locale is used in
* translation when no locale information is given
* (says in method {@link I18n#t(String, Object...)}.
*
* Note : The I18n system must have been initialized by one of the
* {@code init} method.
*
* @return the default locale initialized in I18n
*/
public static Locale getDefaultLocale() {
checkInit();
return languageProvider.getCurrentLocale();
}
/**
* Sets the default locale used by I18n for the method
* {@link I18n#t(String, Object...)}. *
*
* As a side effect, it will also set this locale is the default locale in
* the system (says the method {@link Locale#getDefault()} will return the
* given locale).
*
* Note : The I18n system must have been initialized by one of the
* {@code init} method.
*
* @param locale the new default locale.
*/
public static void setDefaultLocale(Locale locale) {
checkInit();
languageProvider.setCurrentLocale(locale);
}
/**
* Look into the default {@link I18nLanguage} if the given {@code message}
* can be found.
*
* @param message the message to check presence
* @return true/false whether the message is present in the default language
* @see #getDefaultLocale()
* @see #hasKey(java.util.Locale, String)
* @since 2.4.1
*/
public static boolean hasKey(String message) {
boolean result = false;
// if the key to find is null, just return false
if (message != null) {
// checkInit() will be done by 'getDefaultLocale' or 'hasKey' method
// get current locale (from the language in the store)
Locale locale = getDefaultLocale();
result = hasKey(locale, message);
}
return result;
}
/**
* Look into the {@link I18nLanguage} associated to the {@code locale} if
* the given {@code message} can be found.
*
* @param locale the locale to be used to get the I18nLanguage
* @param message the message to check presence
* @return true/false whether the message is present for the given locale
* @since 2.4.1
*/
public static boolean hasKey(Locale locale, String message) {
boolean result = false;
// if the key to find is null, just return false
if (message != null) {
checkInit();
// Get the language associated to this locale...
I18nLanguage language = getLanguage(locale);
// ... and check key presence
result = language.hasRecord(message);
}
return result;
}
/**
* Retourne la chaine traduite si possible dans la locale demandée.
*
* @param locale la locale dans lequel on souhaite la traduction
* @param message message formate avec {@link I18nMessageFormatter}
* @param args les parametres pour le message.
* @return la traduction si possible ou la chaine passee en parametre
* sinon.
* @since 2.1
*/
public static String l(Locale locale, String message, Object... args) {
checkInit();
// if the key to translate is null, just return null
if (message == null) {
return null;
}
I18nLanguage language = getLanguage(locale);
String result = language == null ? message : language.translate(message);
try {
if (result != null) {
result = applyFilter(messageFormatter.format(locale, result, args));
}
return result;
} catch (Exception eee) {
try {
return applyFilter(messageFormatter.format(locale, message, args));
} catch (Exception zzz) {
if (log.isWarnEnabled()) {
log.warn(t("nuitonutil.error.i18n.untranslated.message", message), zzz);
}
return applyFilter(message);
}
}
}
/**
* Retourne la chaine traduite si possible.
*
* @param message message formate avec {@link I18nMessageFormatter}
* @param args les parametres pour le message.
* @return la traduction si possible ou la chaine passee en parametre
* sinon.
*/
public static String t(String message, Object... args) {
// if the key to translate is null, just return null
if (message == null) {
return null;
}
// get current locale (from the language in the store)
Locale locale = getDefaultLocale();
// translate with this locale
return l(locale, message, args);
}
/**
* Retourne la chaine passée en argument.
*
* Utile surtout pour collecter les chaines et ne pas les traduires à leur
* apparition.
*
* Par exemple :
*
String key = "nuitonutils.key";
* String result = l(key)
* fonctionnera, mais la chaine n'aura pas été marquée comme devant être
* internationalisé.
*
* Tres utile par exemple, pour crée des objets non internationnalisé, et
* devant être traduit seulement à leur lecture suivant la locale du lecteur
* et non du créateur.
*
* @param message message formate avec {@link I18nMessageFormatter}
* @param args les parametres pour le message.
* @return le message passe en argument mais formatté avec les parametres
*/
public static String n(String message, Object... args) {
if (args.length == 0) {
return message;
}
try {
// XXX-fdesbois-2011-05-05 : don't know if it's relevant to format here,
// seems pretty useless because we will not use the value
// corresponding to given key message
return messageFormatter.format(getDefaultLocale(), message, args);
} catch (Exception eee) {
if (log.isWarnEnabled()) {
log.warn(
t("nuitonutil.error.i18n.unformated.message", message, Arrays.toString(args)),
eee);
}
return message;
}
}
/**
* Close i18n caches, says the store if exists.
*
* This method should be called to reset all caches (languages,
* bundles,...)
*/
public static void close() {
if (languageProvider != null) {
// languageProvider.close();
languageProvider = null;
}
if (messageFormatter != null) {
messageFormatter = null;
}
}
/**
* Applique le filtre s'il y en a un
*
* @param message le message qui devrait etre retourne avant application du
* filtre.
* @return le message filtre
*/
protected static String applyFilter(String message) {
if (getFilter() != null) {
return getFilter().applyFilter(message);
}
return message;
}
/**
* Obtain the registred language from the store.
*
* If no language were registred in the store, then use the language
* with the default locale of the store.
*
* @return the current language of the store, or the default one if store is
* not init.
*/
protected static I18nLanguage getCurrentLanguage() {
return getLanguage(null);
}
/**
* Obtain the language for the given {@code locale}.
*
* If locale is {@code null}, it means we wants to use the language
* registred in the store.
*
* As a fallback if this language is not defined, we use the language of
* the default locale of the store.
*
* @param locale the required locale or {@code null} if we wants to use the
* one from the store
* @return the language associated with the given locale.
* @since 2.1
*/
protected static I18nLanguage getLanguage(Locale locale) {
checkInit();
I18nLanguage language;
if (locale == null) {
// default locale required : means wants the one of the store
locale = getDefaultLocale();
if (locale == null) {
// no locale registred in store, use the default locale from store
locale = languageProvider.getDefaultLocale();
}
}
// get the given language from the store
return languageProvider.getLanguage(locale);
}
/**
* Init the store with given parameters and set the current language in the
* store to the default given {@code locale}.
*
* All values must be none {@code null}.
*
* @param bootLoader the initializer to use to detect bundles
* @param locale the default locale to set in the store
* @throws NullPointerException if any parameter is {@code null}
*/
protected static void initStore(I18nBootLoader bootLoader, Locale locale) throws NullPointerException {
I18n.bootLoader = Objects.requireNonNull(bootLoader);
if (languageProvider != null) {
// languageProvider.close();
languageProvider = null;
}
I18n.languageProvider = I18n.bootLoader.init(locale);
setDefaultLocale(Objects.requireNonNull(locale));
messageFormatter = bootLoader.getConfiguration().getMessageFormatter();
}
/**
* Checks if the I18n was initialized and if not as a fall-back, init it
* with default initializer and default locale. It could not works for
* you... A call to the method {@link #init(I18nBootLoader, Locale)} is
* mandatory if you want to be safe.
*
* @since 2.1
*/
protected static void checkInit() {
if (languageProvider == null) {
init(null, null);
}
}
}