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 theResourceBundlesuffix (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.
override(defaultfalse): by default, existing database translations are left untouched and only keys that are missing in the database are added — so manual edits made throughBundleMonkeyare preserved. Setoverride=trueto 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.
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
StoredBundlewhose 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
StoredBundleat 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).
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-pluginduring the build;verifyfocuses 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.
A Typical Workflow¶
- Developers add or change keys in the
*.propertiesfiles and runtentackle-i18n:pushto make the new keys available in the database (existing translations stay untouched). - Translators edit the texts at runtime through
BundleMonkey. tentackle-i18n:pullbrings the edited texts back into the property files so they are committed to source control.tentackle-i18n:verifyis run (e.g., in CI) to surface differing, missing or obsolete entries.- Once
verifyis clean,tentackle-i18n:cleanupprunes the keys and bundles that have been removed from the application.
Further Reading¶
- Tentackle Maven Plugin —
the
analyzegoal that records the@Bundleannotations this plugin relies on - BundleMonkey —
the GUI tool translators use to edit the stored bundles between
pushandpull - Plugin reference (generated)