I18n.java
/*
* This file is part of Indicators.
*
* Indicators is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Indicators 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 Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Indicators. If not, see <https://www.gnu.org/licenses/>.
*/
package fr.inrae.agroclim.indicators.resources;
import static java.lang.Math.abs;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.Optional;
import java.util.ResourceBundle;
import java.util.function.BiFunction;
import java.util.function.BiPredicate;
import java.util.function.Predicate;
/**
* Localized messages with plural handling à la GWT.
*
* Last change $Date$
*
* @author $Author$
* @version $Revision$
*/
public final class I18n {
/**
* Accepted operators, ordered.
*/
public enum Operator implements BiFunction<Integer, Integer, Boolean> {
/**
* Equals.
*/
AEQ("=", Objects::equals),
/**
* Inferior or equals.
*/
BINFEQ("<=", (a, b) -> a <= b),
/**
* Strictly inferior.
*/
CINF("<", (a, b) -> a < b),
/**
* Superior or equals.
*/
DSUPEQ(">=", (a, b) -> a >= b),
/**
* Strictly superior.
*/
ESUP(">", (a, b) -> a > b);
/**
* Guess the right operator in the string comparison.
*
* @param string string comparison (eg.: ">=10")
* @return operator matching the string comparison
*/
static Optional<Operator> extract(final String string) {
for (final Operator op : values()) {
if (string.startsWith(op.symbol)) {
return Optional.of(op);
}
}
return Optional.empty();
}
/**
* Comparison function of the operator.
*/
private final BiPredicate<Integer, Integer> function;
/**
* String representation of the operator.
*/
private final String symbol;
/**
* Constructor.
*
* @param string String representation of the operator.
* @param func Comparison function of the operator.
*/
Operator(final String string,
final BiPredicate<Integer, Integer> func) {
symbol = string;
function = func;
}
@Override
public Boolean apply(final Integer arg0, final Integer arg1) {
return function.test(arg0, arg1);
}
/**
* @return length of string representation
*/
public int getLength() {
return symbol.length();
}
}
/**
* Standard suffix for keys in .properties, à la GWT.
*
* http://www.gwtproject.org/doc/latest/DevGuideI18nPluralForms.html
*/
@SuppressWarnings("checkstyle:MagicNumber")
public enum PluralSuffix implements Predicate<Integer> {
/**
* The count is 0.
*/
NONE(nb -> nb == 0),
/**
* The count is 1.
*/
ONE(nb -> nb == 1),
/**
* The count is 2.
*/
TWO(nb -> nb == 2),
/**
* The last two digits are from 03-10.
*/
FEW(nb -> nb % 100 >= 3 && nb % 100 <= 10),
/**
* the last two digits are from 11-99.
*/
MANY(nb -> nb % 100 >= 11 && nb % 100 <= 99);
/**
* Find the suffix according to the value.
*
* @param value value to find the prefix
* @return found prefix or empty
*/
static Optional<String> getSuffix(final Integer value) {
for (final PluralSuffix suffix : values()) {
if (suffix.test(value)) {
return Optional.of(suffix.name().toLowerCase());
}
}
return Optional.empty();
}
/**
* Predicate to check if the value match the plural suffix.
*/
private final Predicate<Integer> predicate;
/**
* Constructor.
*
* @param pr predicate for the prefix
*/
PluralSuffix(final Predicate<Integer> pr) {
predicate = pr;
}
@Override
public boolean test(final Integer value) {
if (value != null) {
return predicate.test(abs(value));
}
return false;
}
}
/**
* Check if the comparison string matches the value.
*
* @param comparison comparison string (eg.: ">10")
* @param plural value
* @return if the comparison string matches the value.
*/
static boolean matches(final String comparison, final int plural) {
final Optional<Operator> operator = Operator.extract(comparison);
if (operator.isPresent()) {
final Operator op = operator.get();
String val = comparison.substring(op.getLength());
if (val != null) {
val = val.trim();
if (!val.isEmpty()) {
final Integer value = Integer.valueOf(val);
return op.apply(plural, value);
}
}
}
return false;
}
/**
* Resources from .properties file.
*/
private final Resources resources;
/**
* Constructor.
*
* @param bundle Resources bundle build outside.
*/
public I18n(final ResourceBundle bundle) {
resources = new Resources(bundle);
}
/**
* Constructor.
*
* @param bundleName Path of .property resource.
* @param locale The locale for the bundle.
*/
public I18n(final String bundleName, final Locale locale) {
resources = new Resources(bundleName, locale);
}
/**
* Return message with inlined arguments.
*
* @param plural value for plural form
* @param key message key
* @param messageArguments arguments for the message.
* @return message with arguments or exclamation message
*/
public String format(final int plural, final String key,
final Object... messageArguments) {
String keyWithSuffix;
// the suffix for the value
keyWithSuffix = key + "[=" + plural + "]";
if (resources.getKeys().contains(keyWithSuffix)) {
return resources.format(keyWithSuffix, messageArguments);
}
// find the right standard suffix
final Optional<String> suffix = PluralSuffix.getSuffix(plural);
if (suffix.isPresent()) {
keyWithSuffix = key + "[" + suffix.get() + "]";
if (resources.getKeys().contains(keyWithSuffix)) {
return resources.format(keyWithSuffix, messageArguments);
}
}
// with comparators <, <=, >, >=
final List<String> suffixes = resources.getKeys().stream() //
.filter(k -> k.startsWith(key + "[") && k.endsWith("]")) //
.map(k -> k.substring(key.length() + 1, k.length() - 1)) //
.toList();
for (final String suf: suffixes) {
if (matches(suf, plural)) {
keyWithSuffix = key + "[" + suf + "]";
return resources.format(keyWithSuffix, messageArguments);
}
}
// if not defined, used default
return resources.format(key, messageArguments);
}
/**
* Return message with inlined arguments.
*
* @param key message key
* @param messageArguments arguments for the message.
* @return message with arguments or exclamation message
*/
public String format(final String key,
final Object... messageArguments) {
return resources.format(key, messageArguments);
}
/**
* Retrieve message from key.
*
* @param key message key
* @return message value or exclamation message
*/
public String get(final String key) {
return resources.getString(key);
}
/**
* @return Path of .property resource.
*/
public String getBundleName() {
return resources.getBundleName();
}
/**
* @param locale Locale for the bundle.
*/
public void setLocale(final Locale locale) {
resources.setLocale(locale);
}
}