Skip to content

I18N Maven Plugin

The Tentackle I18N Maven Plugin

The tentackle-i18n-maven-plugin synchronizes an application's resource bundles between its *.properties files and a database backend. Tentackle can serve localized texts from the database instead of (or in addition to) the property files baked into the jars, which lets translators edit texts at runtime and add languages not provided by the property files — typically through the BundleMonkey tool — without rebuilding and redeploying the application. This plugin is the build-time counterpart that moves translations in both directions and keeps the two representations consistent.

The texts live in the database as StoredBundle PDOs (table bundle), each holding the translations of one bundle for one locale, with the per-key entries kept in StoredBundleKey components. A StoredBundle with a null locale carries the default (fall-back) translations. The locale of the other ones is the suffix ResourceBundle appends to the bundle name, such as de or de_DE (see Tentackle I18N).

The goals share the prefix tentackle-i18n, so they can be invoked directly from the command line, e.g. mvn tentackle-i18n:push.

Prerequisites

The plugin requires the same JDK and Maven versions as the rest of the framework (JDK >= 25, Maven >= 3.9.10, Maven 4 is supported as well). Every goal connects to a live database, so a reachable backend and the matching JDBC driver on the plugin's classpath are required (add the driver to the plugin's <dependencies>).

How Bundles Are Discovered

None of the goals scan the source tree for *.properties files directly. Instead, they ask BundleSupport for every class that is marked as a resource bundle — that is, every class annotated with @Bundle or with another bundle-providing annotation such as @FxControllerService. These annotations are picked up during the analyze step and recorded under META-INF, so the plugin sees exactly the bundles the running application would see, in both modular and classpath mode.

For each discovered bundle the plugin derives the resource name from the bundle name and loads the corresponding property file from the project's compile classpath — first the default file (name.properties), then one file per extra locale configured via locales (name_de.properties, name_en_US.properties, …). Because the bundles are resolved through the compile classpath, the goals require dependency resolution and operate on compiled modules. The compile classpath includes the dependencies, so the bundles of the dependencies (including Tentackle's own) are processed as well.

A bundle without a property file for one of the configured locales is reported as a warning. At runtime, such a bundle simply falls back to a less specific locale, so a missing file is not wrong per se — but the plugin expects every bundle to be translated into every locale listed in locales. Since a warning blocks cleanup, list only the locales the application ships property files for. Locales that exist only in the database (added via BundleMonkey) need not be listed.

Overview of Goals

Goal Aggregator Direction Purpose
push no files → DB Insert the property-file translations into the database. Existing translations are kept unless override is set.
pull no DB → files Write translations found in the database back into the project's property files.
verify yes compare Report differing, missing and obsolete translations; fail the build on a missing default translation.
cleanup yes DB Delete obsolete bundles and keys from the database — but only if verify reports no other errors or warnings.
help — — Print plugin usage information (generated by the maven-plugin-plugin).

push and pull run per module, whereas verify and cleanup are aggregators that look at the whole reactor at once and are therefore run on the root project. Besides the modules collected by the reactor, the aggregators process the project they are executed on, unless its packaging is pom. This makes them work for single-module projects as well.

Common Configuration

Like all Tentackle mojos, the goals inherit the common parameters verbosity, skip, charset, jdkToolchain and minLogLevel (see the tentackle-maven-plugin docs). On top of that, every i18n goal needs the database connection and may take a locale list:

  • url (required): the JDBC URL of the backend holding the stored bundles.
  • user (required): the database user.
  • password: the database password (may be omitted for password-less or externally authenticated connections).
  • locales: a comma/space/semicolon-separated list of the additional locale suffixes to process besides the default one, e.g. de, en_US, fr. The language-tag notation is accepted as well and normalized to the ResourceBundle suffix (en-US → en_US). The default bundle (no suffix) is always processed.

None of these parameters (nor override below) has a user property: they are configured in the POM, not with -D… on the command line. To keep the credentials out of the POM, reference properties defined in settings.xml or a profile, e.g. <password>${i18n.password}</password>.

A minimal configuration looks like this:

<plugin>
  <groupId>org.tentackle</groupId>
  <artifactId>tentackle-i18n-maven-plugin</artifactId>
  <version>${project.version}</version>
  <configuration>
    <url>jdbc:postgresql://localhost/muz</url>
    <user>muz</user>
    <password>secret</password>
    <locales>de, en</locales>
  </configuration>
  <dependencies>
    <dependency>
      <groupId>org.postgresql</groupId>
      <artifactId>postgresql</artifactId>
      <version>${postgresql.version}</version>
    </dependency>
  </dependencies>
</plugin>

Every goal prints a summary line at the end — number of bundles processed (counted per locale), warnings, errors and updates — and fails the build if any errors were counted.

The goals are thread-safe, but bind the JVM-wide modification tracker to their database session, so concurrent executions in a parallel build (mvn -T …) are serialized; see Parallel builds.

The push Goal

Loads the translations from the property files and writes them to the database. For each bundle it looks up the matching StoredBundle (by name and locale), creating it if it does not yet exist, and adds every key/value pair. A missing property file is reported as a warning.

Unlike pull, push is not restricted to the bundles of the current project: it pushes every bundle found on the module's compile classpath. Running it in each module of a reactor pushes the bundles of the shared dependencies more than once, which is harmless, since a translation that is already stored is not changed.

mvn tentackle-i18n:push
  • override (default false): by default, existing database translations are left untouched and only keys that are missing in the database are added — so manual edits made through BundleMonkey are preserved. Set override=true to overwrite the stored values with the ones from the property files wherever they differ.

Use push to seed a fresh database from the texts shipped in the sources, or to add newly introduced keys without disturbing existing translations.

The pull Goal

The reverse of push: it reads the translations from the database and updates the project's property files so that the sources reflect the texts edited at runtime.

mvn tentackle-i18n:pull

Only bundles whose package belongs to the current project are written (the target file is located via the project's non-filtered resource roots). If a property file does not exist yet, it is created, which is reported as a warning. A key is written only when it is missing from the file or differs from the stored value. Changed files are rewritten by java.util.Properties: the order of the keys is kept and new keys are appended, but comments and blank lines of the original file are lost. A header comment records the backend URL, the user/host that pulled them and the date. Keys that exist in the file but not in the database are left in place — removing obsolete keys is the job of cleanup. A bundle that has no StoredBundle at all — one that has never been pushed, or a locale nobody has translated yet — is reported as a warning and skipped, leaving no empty property file behind.

The verify Goal

An aggregator that compares the property files of the whole reactor against the database and reports the discrepancies without changing anything. It distinguishes:

  • differing translations — key present on both sides but with different text (info),
  • obsolete bundles — a StoredBundle whose bundle no longer exists in the application (warning),
  • obsolete keys — keys in the database that are no longer present in the property file (warning),
  • missing stored bundles — a bundle that has no StoredBundle at all (warning),
  • missing property files — a bundle without a property file for a configured locale (warning),
  • missing translations for a non-default locale (warning), and
  • missing default translations — a key absent from the null-locale bundle (error).
mvn tentackle-i18n:verify

A missing default translation is the only condition that fails the build, because at runtime it would cause a MissingResourceException. The other findings are reported so they can be resolved — typically by push/pull or by BundleMonkey — before running cleanup. The differing translations are counted as updates in the summary line, since a pull would update them.

Note: the application's own resource bundles (keys referenced in code) are checked separately by the tentackle-check-maven-plugin during the build; verify focuses on the consistency between the property files and the database.

The cleanup Goal

An aggregator that removes obsolete bundles and obsolete keys from the database. As a safety measure it performs the same analysis as verify first and does nothing if that analysis yields any errors or warnings besides the obsolete bundles and keys themselves — that is, missing stored bundles, missing property files or missing translations. It then tells you to run tentackle-i18n:verify and resolve the pending refactorings (e.g., via BundleMonkey) first. Differing translations don't block the cleanup. Only when the database and the sources are otherwise in sync does it delete the obsolete keys and bundles, all within a single transaction, and log what was removed.

mvn tentackle-i18n:cleanup

A Typical Workflow

  1. Developers add or change keys in the *.properties files and run tentackle-i18n:push to make the new keys available in the database (existing translations stay untouched).
  2. Translators edit the texts at runtime through BundleMonkey.
  3. tentackle-i18n:pull brings the edited texts back into the property files so they are committed to source control.
  4. tentackle-i18n:verify is run (e.g., in CI) to surface differing, missing or obsolete entries.
  5. Once verify is clean, tentackle-i18n:cleanup prunes the keys and bundles that have been removed from the application.

Further Reading