org.nuiton.i18n.I18n Maven / Gradle / Ivy
/*
* #%L
* I18n :: Api
* %%
* Copyright (C) 2004 - 2017 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%
*/
package org.nuiton.i18n;
import java.util.Arrays;
import java.util.Locale;
import java.util.Objects;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
import org.nuiton.i18n.format.I18nMessageFormatter;
import org.nuiton.i18n.init.ClassPathI18nInitializer;
import org.nuiton.i18n.init.DefaultI18nInitializer;
import org.nuiton.i18n.init.I18nInitializer;
/**
* New generation I18n class.
*
* Note: This class replace the previous one in project {@code
* nuiton-utils}.
*
* This class is a facility for internationalization. To use it in your soft,
* you can either :
- import the org.nuiton.i18n.I18n class,
- init
* the translation support with the init(String language) or init(String
* language, String country), init(Localelocale) static methods in your main, (
* eg: I18n.init("fr","FR") )
- call the translate static method for each
* sentence, ( eg: I18n.l("hello you !") )
- create a resource file for each
* language following the naming convention given in the
* java.util.ResourceBundle javadoc and translate all the sentence.
*
* @author Tony Chemit - [email protected]
* @since 1.1
*/
public class I18n {
/** Logger. */
private static final Logger log = LogManager.getLogger(I18n.class);
/** I18n store of languages */
protected static I18nStore store;
/** Filtre a appliquer avant de retourner les chaines */
protected static I18nFilter filter;
/**
* 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 I18nInitializer#setMessageFormatter(I18nMessageFormatter)
* @since 2.4
*/
private static I18nMessageFormatter messageFormatter;
/**
* Change le filtre des chaines traduites
*
* @param filter l'objet filtre a utiliser
*/
public static void setFilter(I18nFilter filter) {
I18n.filter = filter;
}
/**
* Initialize I18n system.
*
* The {@code initializer} can be null, in that case it will use the default
* implementation of it given by the method {@link I18n#getDefaultInitializer()}.
*
* The {@code locale} can also be null, and in that case we will use the
* default locale of the system given by the method
* {@link Locale#getDefault()}.
*
* We strongly recommand not to use the default initializer,
* since this one scanq all the class-path to detects i18n files and can
* NOT garantee the order of loading such files.
*
* Prefer use at least the {@link DefaultI18nInitializer} instead.
*
* In version 3.0, we will try to make the {@link DefaultI18nInitializer} the
* default initializer using a convention over i18n files names.
*
* @param initializer the initializer to use to detect bundles
* @param locale the default locale to use
* @since 2.1
*/
public static void init(I18nInitializer initializer, Locale locale) {
if (initializer == null) {
// get a default initializer
initializer = getDefaultInitializer();
}
if (locale == null) {
// use default locale
locale = I18nUtil.newLocale(null, null);
}
initStore(initializer, locale);
}
public static void reload() {
checkInit();
// get back initializer
I18nInitializer initializer = store.getResolver();
// get back default locale
Locale locale = store.getCurrentLocale();
// close the current store
close();
// reload store
init(initializer, locale);
}
/**
* 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.
* @since 2.1
*/
public static void setDefaultLocale(Locale locale) {
checkInit();
store.setCurrentLocale(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 store.getCurrentLocale();
}
/**
* 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.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 (store != null) {
store.close();
store = null;
}
if (messageFormatter != null) {
messageFormatter = null;
}
}
/**
* Get the i18n store.
*
* If store is not init, then instanciate it.
*
* @return the instanciated i18n store
*/
public static I18nStore getStore() {
return store;
}
/**
* 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 registed in store, use the default locale from store
locale = store.getDefaultLocale();
}
}
// get the given language from the store
language = store.getLanguage(locale);
return language;
}
protected static I18nFilter getFilter() {
return filter;
}
public static I18nMessageFormatter getMessageFormatter() {
return messageFormatter;
}
/**
* 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 initializer 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(I18nInitializer initializer, Locale locale) throws NullPointerException {
Objects.requireNonNull(initializer, "initializer parameter can not be null");
Objects.requireNonNull(locale, "locale parameter can not be null");
if (store != null) {
store.close();
store = null;
}
store = new I18nStore(locale, initializer);
setDefaultLocale(locale);
messageFormatter = initializer.getMessageFormatter();
}
protected static I18nInitializer getDefaultInitializer() {
I18nInitializer initializer = new ClassPathI18nInitializer();
if (log.isWarnEnabled()) {
log.warn("\n\nI18n was not initialized! will init it with " +
"default initializer and default locale, it might " +
"not translate anything for you...\nPlease use the " +
"method I18n.init(I18nInitializer, Locale) before " +
"any calling to a translation...\n\n");
}
return initializer;
}
/**
* 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(I18nInitializer, Locale)} is
* mandatory if you want to be safe.
*
* @since 2.1
*/
protected static void checkInit() {
if (store == null) {
init(null, null);
}
}
}