
org.nuiton.i18n.plugin.bundle.ApplicationBundleMojo Maven / Gradle / Ivy
Show all versions of i18n-maven-plugin Show documentation
package org.nuiton.i18n.plugin.bundle;
/*-
* #%L
* I18n :: Maven Plugin
* %%
* Copyright (C) 2007 - 2024 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 com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
import io.ultreia.java4all.i18n.spi.I18nResource;
import io.ultreia.java4all.i18n.spi.I18nTemplateDefinition;
import io.ultreia.java4all.i18n.spi.I18nTranslationSetDefinition;
import io.ultreia.java4all.i18n.spi.builder.I18nModule;
import io.ultreia.java4all.i18n.spi.builder.I18nTranslationSet;
import org.apache.commons.lang3.StringUtils;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.nuiton.i18n.plugin.I18nMojoHelper;
import org.nuiton.i18n.plugin.I18nMojoWithI18nModuleSupport;
import org.nuiton.io.SortedProperties;
import org.nuiton.plugin.PluginHelper;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Path;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.TreeSet;
import java.util.stream.Collectors;
/**
* Generate application bundle.
*
* This replace the {@code bundle} mojo using only i18n convention over configuration.
*
* Created by tchemit on 31/10/2018.
*
* @author Tony Chemit - [email protected]
* @since 4.0
*/
@Mojo(name = "application-bundle", threadSafe = true, defaultPhase = LifecyclePhase.PROCESS_RESOURCES, requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME)
public class ApplicationBundleMojo extends I18nMojoWithI18nModuleSupport {
/**
* A flag to check that bundles are complete (no missing i18n translations).
*/
@Parameter(property = "i18n.checkBundle", defaultValue = "true")
private boolean checkBundle;
/**
* A flag to show missing i18n translation.
*
* Note : Need the {@link #checkBundle} to be activated).
*/
@Parameter(property = "i18n.showEmpty", defaultValue = "false")
private boolean showEmpty;
/**
* A flag to make the build fails if there is some missing key values.
*
* Note : This parameter should be used in a release profile to ensure bundles are complete.
*/
@Parameter(property = "i18n.failsIfAnyKeyMissingValue", defaultValue = "false")
private boolean failsIfAnyKeyMissingValue;
/**
* A flag to make the build fails if there is some missing keys.
*
* Note : This parameter should be used in a release profile to ensure bundles are complete.
*/
@Parameter(property = "i18n.failsIfAnyKeyMissingInBundle", defaultValue = "false")
private boolean failsIfAnyKeyMissingInBundle;
/**
* Converter used to change format of bundles.
*/
@Parameter(property = "i18n.bundleFormatConverter")
private String bundleFormatConverter;
/**
* Map of all available {@link BundleFormatConverter}.
*/
@Component(role = BundleFormatConverter.class)
private Map bundleFormatConverters;
/**
* To categorize i18n keys (used by the i18n editor).
*
* Values are comma separated like this:
* {@code ui.config=observe.ui.config,ui=observe.ui, service=observe.service}
*
* This is used by the I18n Editor as tabs in translation part.
*/
@Parameter(property = "i18n.keyCategories")
private LinkedHashMap keyCategories;
/**
* Contains validation result after {@link I18nMojoHelper#checkBundle(Locale, Properties, boolean, BundleValidation)}.
*
* May be null if validation is disabled.
*/
private BundleValidation bundleValidation;
/**
* Format converter to apply if
*/
private BundleFormatConverter converter;
@Override
public void init() throws Exception {
super.init();
bundleValidation = new BundleValidation(getLocales());
if (StringUtils.isNotEmpty(bundleFormatConverter)) {
// get converter from universe
converter = bundleFormatConverters.get(bundleFormatConverter);
if (converter == null) {
// unknown converter
throw new MojoExecutionException(
String.format("There is no bundleFormatConverter named \"%s\", known ones are %s", bundleFormatConverter, bundleFormatConverters.keySet()));
}
}
}
@Override
protected void doAction() throws Exception {
String version = PluginHelper.removeSnapshotSuffix(getProject().getVersion());
I18nModule i18nModule = getI18nModule();
Charset encoding = getEncoding();
Set locales = getLocales();
String moduleName = i18nModule.getName();
String bundleOutputName = moduleName + "-i18n";
Log log = getLog();
if (isNotSilent()) {
log.info(String.format("config - resources dir : %s", getBuildOutputDirectory()));
log.info(String.format("config - package name : %s", i18nModule.getPackageName()));
log.info(String.format("config - bundle name : %s", bundleOutputName));
log.info(String.format("config - encoding : %s", encoding));
if (bundleFormatConverter != null) {
log.info(String.format("config - format converter : %s", bundleFormatConverter));
}
log.info(String.format("config - locales : %s", locales));
log.info(String.format("config - version : %s", version));
}
I18nMojoHelper helper = getHelper();
Set dependenciesTranslationsIds = getHelper().translationsToStr(i18nModule.getDependenciesTranslations(), "Load %d dependencies translations%s");
Set moduleTranslationsIds = getHelper().translationsToStr(i18nModule.getModuleTranslations(), "Load %d module translations%s");
Set translationIds = new TreeSet<>();
translationIds.addAll(dependenciesTranslationsIds);
translationIds.addAll(moduleTranslationsIds);
Set dependenciesTemplatesIds = getHelper().templatesToStr(i18nModule.getDependenciesTemplates(), "Load %d dependencies templates%s");
Set moduleTemplatesIds = getHelper().templatesToStr(i18nModule.getModuleTemplates(), "Load %d module templates%s");
Set templatesIds = new TreeSet<>();
templatesIds.addAll(dependenciesTemplatesIds);
templatesIds.addAll(moduleTemplatesIds);
Multimap dependencies = ArrayListMultimap.create();
Path translationsDirectory = getBuildOutputDirectory().toPath().resolve(I18nTranslationSetDefinition.I18N_CLASS_PATH);
StringBuilder applicationTranslationIds = new StringBuilder(i18nModule.getId(bundleOutputName));
for (Locale locale : locales) {
long t0 = System.nanoTime();
applicationTranslationIds.append(I18nResource.GROUP_ID_SEPARATOR).append(locale);
SortedProperties propertiesOut = new SortedProperties(encoding.name(), false);
List dependenciesTranslations = i18nModule.getDependenciesTranslations(locale);
for (I18nTranslationSet dependenciesTranslation : dependenciesTranslations) {
propertiesOut.putAll(dependenciesTranslation.getTranslations());
dependencies.put(locale, dependenciesTranslation.getDefinition().getId());
}
int count = dependenciesTranslations.size();
I18nTranslationSet i18nTranslationFile = i18nModule.getModuleTranslation(locale);
if (i18nTranslationFile != null) {
propertiesOut.putAll(i18nTranslationFile.getTranslations());
dependencies.put(locale, i18nTranslationFile.getDefinition().getId());
count++;
}
if (count == 0) {
log.warn(String.format("No bundle for locale %s", locale));
continue;
}
if (isNotSilent()) {
log.info(String.format("generate bundle for locale %s from %d i18n resources (%d sentences)", locale, count, propertiesOut.size()));
}
// Apply conversion if necessary, depends on input bundleFormatConverter
if (converter != null) {
applyConversion(propertiesOut);
}
I18nTranslationSetDefinition applicationTranslationDefinition = new I18nTranslationSetDefinition(i18nModule.getPackageName(), bundleOutputName, locale);
i18nModule.exportModuleTranslation(applicationTranslationDefinition, translationsDirectory, propertiesOut);
if (isNotSilent() && isVerbose()) {
log.info(String.format("bundle created in %s (%d sentences)", PluginHelper.convertTime(t0, System.nanoTime()), propertiesOut.size()));
}
if (checkBundle) {
helper.checkBundle(locale, propertiesOut, showEmpty, bundleValidation);
}
}
helper.failsIfAnyKeyMissingValue(failsIfAnyKeyMissingValue, bundleValidation);
helper.failsIfAnyKeyMissingInBundle(failsIfAnyKeyMissingInBundle, bundleValidation);
if (i18nModule.withDependenciesTemplates()) {
Path templatesDirectory = getBuildOutputDirectory().toPath().resolve(I18nTemplateDefinition.I18N_CLASS_PATH);
i18nModule.exportDependenciesTemplates(templatesDirectory);
}
String filename = getFilename(moduleName, "I18nApplicationDefinition");
String finalApplicationTranslationIds = applicationTranslationIds.toString();
Path target = generateJavaFile(i18nModule, "I18nApplicationDefinition", writer -> {
String finalModuleName = moduleName + "-i18n";
String groupId = i18nModule.getPackageName();
String moduleTemplateStr = templatesIds.isEmpty() ? "" : templatesIds.stream().map(s -> "\"" + s + "\"").collect(Collectors.joining(",\n "));
String dependenciesTranslationStr = translationIds.isEmpty() ? "" : translationIds.stream().map(s -> "\"" + s + "\"").collect(Collectors.joining(",\n "));
String keyCategoriesStr = keyCategories.isEmpty() ? "ImmutableMap.of()" : "ImmutableMap.builder()" + keyCategories.entrySet().stream().map(s -> ".put(\"" + s.getKey() + "\",\"" + s.getValue() + "\")").collect(Collectors.joining("")) + ".build()";
String localesStr = getI18nModuleConfiguration().getLocales().stream().map(s -> "\"" + s.toString() + "\"").collect(Collectors.joining(",\n "));
String encodingStr = "Charset.forName(\"" + encoding + "\")";
String content = String.format(
"package %1$s;\n\n" +
"import com.google.auto.service.AutoService;\n" +
"import com.google.common.collect.ImmutableMap;\n" +
"import io.ultreia.java4all.i18n.spi.I18nApplicationDefinition;\n\n" +
"import java.nio.charset.Charset;\n" +
"import javax.annotation.Generated;\n\n" +
"@AutoService(I18nApplicationDefinition.class)\n" +
"@Generated(value = \"%5$s\", date = \"%6$s\")\n" +
"public class %2$s extends I18nApplicationDefinition {\n\n" +
" public %2$s() {\n" +
" super(\"%1$s\", \"%4$s\", \"%3$s\",\n" +
" %7$s, // encoding \n" +
" \"%8$s\", // version\n" +
" new String[]{%9$s}, // locales\n" +
" %10$s, // keys categories\n" +
" new String[]{\"%11$s\"}, // Application translations\n" +
" new String[]{%12$s}, // Dependencies translations\n" +
" new String[]{%13$s} // Application templates\n" +
" );\n" +
" }\n" +
"}\n",
groupId,
filename,
i18nModule.getConfiguration().getTemplateExtension(),
finalModuleName,
getClass().getName(),
new Date(),
encodingStr,
version,
localesStr,
keyCategoriesStr,
finalApplicationTranslationIds,
dependenciesTranslationStr,
moduleTemplateStr);
try {
writer.write(content);
} catch (IOException e) {
throw new RuntimeException("Can't generate java file content", e);
}
});
log.info(String.format("Application definition %s generated to %s", filename, target));
}
/**
* Apply conversion over {@code properties} with internal converter.
*
* @param properties Properties to walk through
* @since 2.4
*/
private void applyConversion(Properties properties) {
for (Map.Entry