org.nuiton.i18n.plugin.bundle.BundleMojo Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of i18n-maven-plugin Show documentation
Show all versions of i18n-maven-plugin Show documentation
Maven plugin to deal with i18n stuff in a project, mainly base on the
nuiton-i18n api (but not only).
The newest version!
/*
* #%L
* I18n :: Maven Plugin
*
* $Id$
* $HeadURL$
* %%
* Copyright (C) 2007 - 2010 CodeLutin
* %%
* 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.plugin.bundle;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.Execute;
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.bundle.I18nBundleEntry;
import org.nuiton.i18n.bundle.I18nBundleUtil;
import org.nuiton.i18n.init.DefaultI18nInitializer;
import org.nuiton.i18n.plugin.I18nUtil;
import org.nuiton.io.SortedProperties;
import org.nuiton.plugin.PluginHelper;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;
/**
* Generate an aggregate i18n bundle for all dependencies of the project.
*
* The main idea is to have a final unique i18n bundle for an application to
* launch, this really improve i18n loading time to have a unique named bundle,
* no need to seek in all dependencies...
*
* Moreover, this permits also to deal with order of i18n keys, more precisely,
* we want to use the higher level i18n key for an application. If the i18n
* key is present on a library, we want to be able to override it in
* application (or user wants it:)).
*
* This goal permits this using the dependencies graph order of artifacts.
*
* @author Tony Chemit - [email protected]
* @since 0.12
*/
@Mojo(name = "bundle",
defaultPhase = LifecyclePhase.GENERATE_RESOURCES,
requiresProject = true,
requiresDependencyResolution = ResolutionScope.RUNTIME)
@Execute(goal = "collect-i18n-artifacts")
public class BundleMojo extends AbstractI18nBundleMojo {
/**
* Encoding used to load any i18n property files.
*
* If not defined, will use the {@link #encoding} parameter.
*
* @since 2.4
*/
@Parameter(property = "i18n.bundleInputEncoding")
protected String bundleInputEncoding;
/**
* Encoding used to write any i18n property files.
*
* If not defined, will use the {@link #encoding} parameter.
*
* @since 2.4
*/
@Parameter(property = "i18n.bundleOutputEncoding")
protected String bundleOutputEncoding;
/**
* Root directory where to generate aggregated bundles (this directory will
* be added as resources of the project).
*
* @since 1.0.0
*/
@Parameter(property = "i18n.bundleOutputDir", defaultValue = "${basedir}/target/generated-sources/resources", required = true)
protected File bundleOutputDir;
/**
* Package name of the generate aggregated bundles.
*
* Note: By default we use the META-INF
package
* since it is the favorite package of I18n
runtime initializer.
*
* The package name is dotted as it will be stored as folder like in Java
* language.
*
* Example :
*
* package name : foo.bar
* directory : foo/bar
*
*
* @since 2.3.2
*/
@Parameter(property = "i18n.bundleOutputPackage", defaultValue = "META-INF", required = true)
protected String bundleOutputPackage;
/**
* Name of the bundle to generate.
*
* @since 1.0.2
*/
@Parameter(property = "i18n.bundleOutputName", defaultValue = "${project.artifactId}-i18n", required = true)
protected String bundleOutputName;
/**
* A flag to generate a bundle with the first locale defined as a default
* bundle (with no locale specialization).
*
* @since 2.1
*/
@Parameter(property = "i18n.generateDefaultLocale", defaultValue = "false")
protected boolean generateDefaultLocale;
/**
* A flag to check that bundles are complete (no missing i18n translations).
*
* Note : This behaviour will be activated is {@link #failsIfWarning} is on.
*
* @since 1.0.0
*/
@Parameter(property = "i18n.checkBundle", defaultValue = "true")
protected boolean checkBundle;
/**
* A flag to show missing i18n translation.
*
* Note : Need the {@link #checkBundle} to be activated).
*
* @since 1.0.0
*/
@Parameter(property = "i18n.showEmpty", defaultValue = "false")
protected boolean showEmpty;
/**
* A flag to make the build fails if there is some warnings while generating
* bundle, says when it misses some translations.
*
* Note : This parameter should be used in a release profile to
* ensure bundles are complete.
*
* @since 2.0
* @deprecated since 3.6.1, prefer use now {@link #failsIfAnyKeyMissingValue} or {@link #failsIfAnyKeyMissingInBundle}.
*/
@Parameter(property = "i18n.failsIfWarning", defaultValue = "false")
@Deprecated
protected boolean failsIfWarning;
/**
* 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.
*
* @since 3.6.1
*/
@Parameter(property = "i18n.failsIfAnyKeyMissingValue", defaultValue = "false")
protected 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.
*
* @since 3.6.1
*/
@Parameter(property = "i18n.failsIfAnyKeyMissingInBundle", defaultValue = "false")
protected boolean failsIfAnyKeyMissingInBundle;
/**
* Contains validation result after {@link #checkBundle(Locale, Properties, boolean, BundleValidation)}.
*
* May be null if validation is disabled.
*
* @since 3.5
*/
protected BundleValidation bundleValidation;
/**
* The definitive directory where to generate the bundles (includes the
* package of bundle).
*
* @since 2.3.2
*/
protected File outputFolder;
/**
* A flag to generate the i18n definition file.
*
* This file contains all generated bundles and the paths of all i18n
* artifacts used to make it.
*
* @since 2.0
*/
@Parameter(property = "i18n.generateDefinitionFile", defaultValue = "true")
protected boolean generateDefinitionFile;
/**
* A flag to generate the i18n csv file wich contains all translation for
* all locales.
*
* Useful to translate from a language to another one.
*
* @since 2.5
* @deprecated since 3.3, prefer use the new mojo generate-csv-bundle instead
*/
@Parameter(property = "i18n.generateCsvFile", defaultValue = "false")
@Deprecated
protected boolean generateCsvFile;
/**
* Location of the csv file to generate if parameter
* {@link #generateCsvFile} is on.
*
* @since 2.5
* @deprecated since 3.3, prefer use the new mojo generate-csv-bundle instead
*/
@Parameter(property = "i18n.bundleCsvFile",
defaultValue = "${basedir}/target/${project.artifactId}-i18n.csv")
@Deprecated
protected File bundleCsvFile;
/**
* Char separator used when generating the csv bundle file if parameter
* {@link #generateCsvFile} is on.
*
* @since 2.5
* @deprecated since 3.3, prefer use the new mojo generate-csv-bundle instead
*/
@Parameter(property = "i18n.bundleCsvSeparator", defaultValue = "\t")
@Deprecated
protected String bundleCsvSeparator;
/**
* Converter used to change format of bundles.
*
* @since 2.4
*/
@Parameter(property = "i18n.bundleFormatConverter")
protected String bundleFormatConverter;
/**
* Map of all availables {@link BundleFormatConverter}.
*
* @since 2.4
*/
@Component(role = BundleFormatConverter.class)
protected Map bundleFormatConverters;
/** Format converter to apply if */
protected BundleFormatConverter converter;
@Override
public void init() throws Exception {
super.init();
if (failsIfWarning) {
// check bundle if wants to fail on unsafe bundles
checkBundle = true;
bundleValidation = new BundleValidation(locales);
} else {
bundleValidation = null;
}
// get the definitive folder where to generate bundles (including
// bundle package)
outputFolder = getBundleOutputFolder();
if (isVerbose()) {
getLog().info("Will generates bundles in " + outputFolder);
}
createDirectoryIfNecessary(outputFolder);
if (StringUtils.isEmpty(bundleInputEncoding)) {
// use the default encoding
bundleInputEncoding = getEncoding();
if (getLog().isDebugEnabled()) {
getLog().debug("Use as input encoding the default one : " +
bundleInputEncoding);
}
}
if (StringUtils.isEmpty(bundleOutputEncoding)) {
// use the default encoding
bundleOutputEncoding = getEncoding();
if (getLog().isDebugEnabled()) {
getLog().debug("Use as output encoding the default one : " +
bundleOutputEncoding);
}
}
// add root bundle directory as resources of the project
addResourceDir(bundleOutputDir, "**/*.properties");
if (generateCsvFile) {
getLog().warn("");
getLog().warn("--------------------------------------------------------------------------------------------");
getLog().warn("Please use now the i18n:generate-csv-bundle mojo.");
getLog().warn("The csv bundle generation will be removed from this mojo soon.");
getLog().warn("--------------------------------------------------------------------------------------------");
getLog().warn("");
addResourceDir(bundleOutputDir, "**/*.csv");
}
if (StringUtils.isNotEmpty(bundleFormatConverter)) {
// get converter from universe
converter = bundleFormatConverters.get(bundleFormatConverter);
if (converter == null) {
// unknown converter
throw new MojoExecutionException(
"There is no bundleFormatConverter named \"" +
bundleFormatConverter + "\", known ones are " +
bundleFormatConverters.keySet());
}
}
}
@Override
protected void doAction() throws Exception {
long t00 = System.nanoTime();
String version = getProject().getVersion();
version = PluginHelper.removeSnapshotSuffix(version);
String inputEncoding = getBundleInputEncoding();
String outputEncoding = getBundleOutputEncoding();
if (!silent) {
getLog().info("config - resources dir : " + bundleOutputDir);
getLog().info("config - package name : " + bundleOutputPackage);
getLog().info("config - bundle name : " + bundleOutputName);
getLog().info("config - input encoding : " + inputEncoding);
getLog().info("config - output encoding : " + outputEncoding);
if (bundleFormatConverter != null) {
getLog().info("config - format converter : " + bundleFormatConverter);
}
getLog().info("config - locales : " + Arrays.toString(locales));
getLog().info("config - version : " + version);
}
Map bundleDico =
new LinkedHashMap(locales.length);
for (Locale locale : locales) {
long t0 = System.nanoTime();
File bundleOut = I18nUtil.getI18nFile(outputFolder,
bundleOutputName,
locale,
false
);
SortedProperties propertiesOut =
new SortedProperties(outputEncoding, false);
StringBuilder buffer = new StringBuilder();
URL[] urls = getCollectI18nResources(locale);
if (urls.length == 0) {
getLog().warn("no bundle for locale " + locale);
continue;
}
if (!silent) {
getLog().info("generate bundle for locale " + locale +
" from " + urls.length + " i18n resource(s)");
}
List bundlesUrls = new ArrayList();
Charset loadEncoding = Charset.forName(inputEncoding);
for (URL url : urls) {
long t000 = System.nanoTime();
I18nBundleEntry bundleEntry =
new I18nBundleEntry(url, locale, null);
bundleEntry.load(propertiesOut, loadEncoding);
String strPath = bundleEntry.getPath().toString();
int index = strPath.indexOf("i18n/");
String str = strPath.substring(index);
bundlesUrls.add(str);
buffer.append(',').append(str);
if (verbose) {
getLog().info(
"loaded " + bundleEntry.getPath() + " in " +
PluginHelper.convertTime(t000, System.nanoTime()));
}
}
if (!bundlesUrls.isEmpty()) {
bundleDico.put(locale, buffer.substring(1));
if (!silent) {
if (getLog().isDebugEnabled()) {
getLog().debug(bundlesUrls.size() +
" i18n resource(s) detected");
}
for (String u : bundlesUrls) {
getLog().info(u);
}
}
}
// Apply conversion if necessary, depends on input bundleFormatConverter
if (converter != null) {
applyConversion(propertiesOut);
}
propertiesOut.store(bundleOut);
if (!silent && verbose) {
getLog().info(
"bundle created in " +
PluginHelper.convertTime(t0, System.nanoTime()) +
" (detected sentences : " + propertiesOut.size() + ")");
}
if (checkBundle) {
checkBundle(locale, propertiesOut, showEmpty, bundleValidation);
}
}
failsIfAnyKeyMissingValue(failsIfWarning || failsIfAnyKeyMissingValue, bundleValidation);
failsIfAnyKeyMissingInBundle(failsIfAnyKeyMissingInBundle, bundleValidation);
if (generateDefaultLocale) {
generateDefaultBundle();
}
if (generateDefinitionFile) {
generateDefinitionFile(version, bundleDico);
}
if (generateCsvFile) {
generateCsvFile();
}
if (!silent && verbose) {
getLog().info("done in " +
PluginHelper.convertTime(t00, System.nanoTime()));
}
}
protected File getBundleFile(File root,
String artifactId,
Locale locale,
boolean create) throws IOException {
return I18nUtil.getI18nFile(root, artifactId, locale, create);
}
protected void generateDefinitionFile(String version,
Map bundleDico) throws IOException {
// ecriture du ficher des definitions i18n (permet de faire une
// recherche exacte sur un fichier puis d'en deduire les bundles a
// charger
String f = String.format(DefaultI18nInitializer.UNIQUE_BUNDLE_DEF,
bundleOutputName);
File defOut = new File(outputFolder, f);
if (!silent) {
getLog().info("prepare i18n definition file in " +
defOut.getAbsolutePath());
}
SortedProperties p = new SortedProperties(encoding, false);
p.setProperty(DefaultI18nInitializer.BUNDLE_DEF_LOCALES, bundles);
p.setProperty(DefaultI18nInitializer.BUNDLE_DEF_VERSION, version);
p.setProperty(DefaultI18nInitializer.BUNDLE_DEF_ENCODING, encoding);
for (Entry e : bundleDico.entrySet()) {
p.setProperty(DefaultI18nInitializer.BUNDLES_FOR_LOCALE +
e.getKey().toString(), e.getValue());
}
p.store(defOut);
}
@Override
protected URL[] getCollectI18nResources(Locale locale) throws IOException {
File file = getCollectOutputFile(locale, false);
if (!file.exists()) {
return I18nBundleUtil.EMPTY_URL_ARRAY;
}
URL[] urls = PluginHelper.getLinesAsURL(file);
return urls;
}
/**
* Apply conversion over {@code properties} with internal converter.
*
* @param properties Properties to walk through
* @since 2.4
*/
protected void applyConversion(Properties properties) {
for (Entry