All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.nuiton.i18n.plugin.bundle.BundleMojo Maven / Gradle / Ivy

Go to download

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 entry : properties.entrySet()) { String convertedValue = converter.convert((String) entry.getValue()); properties.setProperty((String) entry.getKey(), convertedValue); } } /** * Generates a csv file with all translations. * * First column is key, second is first locale translation, ... * * @throws IOException if any IO problem while the copy. * @since 2.5 */ protected void generateCsvFile() throws IOException { if (!bundleCsvFile.exists()) { createNewFile(bundleCsvFile); } Set allKeys = new HashSet(); Map bundlesByLocale = new LinkedHashMap(); for (Locale locale : locales) { File bundleFile = getBundleFile(outputFolder, bundleOutputName, locale, false ); SortedProperties properties = new SortedProperties(bundleOutputEncoding); properties.load(bundleFile); for (Object o : properties.keySet()) { allKeys.add((String) o); } bundlesByLocale.put(locale, properties); } List keys = new ArrayList(allKeys); Collections.sort(keys); BufferedWriter writer = new BufferedWriter(new FileWriter(bundleCsvFile)); try { // add header line StringBuilder builder = new StringBuilder("Key"); for (Locale locale : locales) { builder.append(bundleCsvSeparator).append(locale.getCountry()); } writer.write(builder.toString()); // for each key get all translations for all for (String key : keys) { writer.newLine(); builder = new StringBuilder(key); for (Locale locale : locales) { SortedProperties properties = bundlesByLocale.get(locale); Object value = properties.get(key); builder.append(bundleCsvSeparator).append("\""); builder.append(value == null ? "" : value); builder.append("\""); } writer.write(builder.toString()); } writer.close(); } finally { IOUtils.closeQuietly(writer); } if (!isSilent()) { getLog().info("Generate csv bundle file at " + bundleCsvFile); } } /** * Generates the default bundle, says the bundle with no locale specialized. * * This bundle is a copy of the bundle of the first locale (which in fact * is considered as the main locale). * * @throws IOException if any IO problem while the copy. * @since 2.1 */ protected void generateDefaultBundle() throws IOException { File bundleFirstLocale = getBundleFile(outputFolder, bundleOutputName, locales[0], false ); File bundleWithoutLocale = getBundleFile(outputFolder, bundleOutputName, null, false ); if (!isSilent()) { getLog().info("Generate default bundle at " + bundleWithoutLocale); } FileUtils.copyFile(bundleFirstLocale, bundleWithoutLocale); } protected File getBundleOutputFolder() { File result = bundleOutputDir; if (StringUtils.isNotEmpty(bundleOutputPackage)) { String[] paths = bundleOutputPackage.split("\\."); for (String path : paths) { result = new File(result, path); } } return result; } public String getBundleOutputEncoding() { return bundleOutputEncoding; } public String getBundleInputEncoding() { return bundleInputEncoding; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy