public record TranslatableString()

in api/applib/src/main/java/org/apache/causeway/applib/services/i18n/TranslatableString.java [34:199]


public record TranslatableString(
        /**
         * The text as provided in (either of the {@link #tr(String, Object...) factory} {@link #trn(String, String, int, Object...) method}s,
         * with placeholders rather than substituted arguments; if {@link #isPluralForm()} is <code>true</code> then used only
         * for the singular form.
         */
        String singularText,
        /**
         * The plural text as provided in the {@link #trn(String, String, int, Object...) factory method}, with placeholders
         * rather than substituted arguments; but will be <code>null</code> if {@link #isPluralForm()} is <code>false</code>.
         */
        String pluralText,
        int number,
        /**
         * The arguments; excluded from {@link #equals(Object)} comparison.
         */
        Map<String, Object> argumentsByParameterName) {

    /**
     * A translatable string with a single pattern for both singular and plural forms.
     *
     * @param pattern - pattern for all (singular and plural) form, with <code>${xxx}</code> placeholders
     * @param paramArgs - parameter/argument pairs (string and object, string and object, ...)
     */
    public static TranslatableString tr(
            final String pattern,
            final Object... paramArgs) {
        if(pattern == null) return null;
        return new TranslatableString(pattern, null, 1, asMap(paramArgs));
    }

    /**
     * A translatable string with different patterns for singular and plural forms, selected automatically by the number
     *
     * @param singularPattern - pattern for the singular form, with <code>${xxx}</code> placeholders
     * @param pluralPattern - pattern for the singular form, with <code>${xxx}</code> placeholders
     * @param number - whether to use singular or plural form when rendering
     * @param paramArgs - parameter/argument pairs (string and object, string and object, ...)
     */
    public static TranslatableString trn(
            final String singularPattern,
            final String pluralPattern,
            final int number,
            final Object... paramArgs) {
        return new TranslatableString(singularPattern, pluralPattern, number, asMap(paramArgs));
    }

    /**
     * Translates this string using the provided {@link org.apache.causeway.applib.services.i18n.TranslationService}, selecting
     * either the single or plural form as per {@link #pattern()}.
     */
    public String translate(final TranslationService translationService, final TranslationContext context) {

        final String translatedText = !isPluralForm()
                ? translationService.translate(context, singularText())
                : translationService.translate(context, singularText(), pluralText(), number);
        return translated(translatedText);
    }

    /**
     * The text to be translated; depends on whether {@link #isPluralForm()} and whether to be translated.
     * <p>
     * Any placeholders will <i>not</i> have been replaced.
     * <p>
     * NB: this method is exposed only so that implementations of
     * {@link org.apache.causeway.applib.exceptions.TranslatableException} can return a non-null
     * {@link Exception#getMessage() message} when only a translatable message has been provided.
     */
    public String pattern() {
        return !isPluralForm()
                || number == 1
            ? singularText()
            : pluralText();
    }

    @Override
    public boolean equals(final Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        final TranslatableString that = (TranslatableString) o;

        if (pluralText != null ? !pluralText.equals(that.pluralText) : that.pluralText != null)
            return false;
        if (singularText != null ? !singularText.equals(that.singularText) : that.singularText != null)
            return false;

        return true;
    }

    @Override
    public int hashCode() {
        int result = isPluralForm() ? 13 : 7;
        result = 31 * result + (singularText != null ? singularText.hashCode() : 0);
        result = 31 * result + (pluralText != null ? pluralText.hashCode() : 0);
        return result;
    }

    @Override
    public String toString() {
        return isPluralForm()
                ? "tr: " + singularText()
                : "trn: " + pluralText();
    }

    // -- HELPER

    // not private for JUnit tests
    String translated(final String translatedText) {
        return format(translatedText, argumentsByParameterName);
    }

    private static final Pattern PATTERN = Pattern.compile("\\{(\\w+)}");

    private static String format(final String format, final Map<String, Object> values) {
        StringBuilder formatter = new StringBuilder(format);
        List<Object> valueList = _Lists.newArrayList();
        Matcher matcher = PATTERN.matcher(format);

        while (matcher.find()) {
            String key = matcher.group(1);

            String formatKey = String.format("{%s}", key);
            int index = formatter.indexOf(formatKey);

            if (index != -1) {
                formatter.replace(index, index + formatKey.length(), "%s");
                valueList.add(values.get(key));
            }
        }

        return String.format(formatter.toString(), valueList.toArray());
    }

    /**
     * Converts a list of objects [a, 1, b, 2] into a map {a -> 1; b -> 2}
     */
    private static Map<String, Object> asMap(final Object[] paramArgs) {
        final HashMap<String, Object> map = new HashMap<String, Object>();
        boolean param = true;
        String paramStr = null;
        for (final Object paramArg : paramArgs) {
            if (param) {
                if (paramArg instanceof String) {
                    paramStr = (String) paramArg;
                } else {
                    throw new IllegalArgumentException("Parameter must be a string");
                }
            } else {
                final Object arg = paramArg;
                map.put(paramStr, arg);
                paramStr = null;
            }
            param = !param;
        }
        if (paramStr != null) {
            throw new IllegalArgumentException("Must have equal number of parameters and arguments");
        }
        return map;
    }

    private boolean isPluralForm() {
        return pluralText!=null;
    }

}