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 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.0). 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.
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 when verify would report no errors or warnings. |
help |
— | — | Print plugin usage information (generated by the maven-plugin-plugin). |
push and pull run per module (they act on the bundles of the current project), whereas verify
and cleanup are aggregators that look at the whole reactor at once and are therefore run on the root
project.
Common Configuration¶
Like all Tentackle mojos, the goals inherit the common parameters verbosity, skip, charset and
jdkToolchain (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 (maybe 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. Underscores are accepted and normalized to hyphens (en_US→en-US). The default bundle (no suffix) is always processed.
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, warnings, errors and updates — and fails the build if any errors were counted.
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.
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 resource roots). If a property file does not exist yet it is created. A key is written
only when it is missing from the file or differs from the stored value; changed files are rewritten
with a comment recording the source URL and the user/host that pulled them. Keys that exist in the
file but not in the database are left in place — removing obsolete keys is the job of cleanup.
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 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.
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 would yield any
errors or warnings — it then tells you to run tentackle-i18n:verify and resolve the pending
refactorings (e.g., via BundleMonkey) first. Only when the database and the sources are otherwise in
sync does it delete the obsolete keys and bundles, all within a single transaction.
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 - Plugin reference (generated)