i18n-core
i18n-core extends the standard ResourceBundle API with polymorphic inheritance, typed JSON/XML bundles, and locale-change events. It has no UI dependency and works with any Java application.
Artifact: dev.javai18n:i18n-core · Requires: Java 17+ · View Javadoc →
<dependency> <groupId>dev.javai18n</groupId> <artifactId>i18n-core</artifactId> <version>1.4.0</version></dependency>module my.module { requires dev.javai18n.core;}Core abstractions
Section titled “Core abstractions”Localizable
Section titled “Localizable”The locale-event source. Holds the current Locale, fires a LocaleEvent to all registered LocaleEventListeners when setBundleLocale() is called, and provides getResourceBundle() for direct bundle access.
The interface uses getBundleLocale / setBundleLocale (not getLocale / setLocale) to avoid collision with existing methods on java.awt.Component, javafx.scene.Node, and other common base classes.
Resourceful
Section titled “Resourceful”A component that owns a Resource and reacts to locale changes. Implement this interface to make any object locale-aware.
Resource
Section titled “Resource”Pairs a Localizable source with a string key. Calling resource.getObject() returns the typed AttributeCollection for the current locale.
Resource okResource = new Resource(myFrame, "okButton");AbstractButtonPropertyBundle bundle = (AbstractButtonPropertyBundle) okResource.getObject();AttributeCollection
Section titled “AttributeCollection”A typed property bag deserialized from a JSON or XML resource file. Implement it to define the localized properties of a component. No code generation, no annotation processors — just a class with a setAttribute(String, Object) method.
Resource bundle loading
Section titled “Resource bundle loading”Bundles are located by appending Bundle to the fully-qualified class name. For com.example.MyFrame, the framework searches for com/example/MyFrameBundle in four formats, in order:
| Priority | Format | File |
|---|---|---|
| 1 | Java class | MyFrameBundle.class |
| 2 | JSON | MyFrameBundle.json |
| 3 | XML | MyFrameBundle.xml |
| 4 | Properties | MyFrameBundle.properties |
Both classpath and JPMS module-path deployments use the same loading strategy.
Polymorphic inheritance
Section titled “Polymorphic inheritance”Bundles follow the class hierarchy. When LocalizationDelegate resolves a key, it walks from the concrete class up through every Localizable superclass, building a NestedResourceBundle chain. A subclass bundle needs only the entries it overrides — everything else falls through to the parent.
MyFrame → MyFrameBundle.json (overrides title) └─ LocalizableJFrame → LocalizableJFrameBundle.json (base window properties)JSON bundle format
Section titled “JSON bundle format”Bundle entries that carry multiple properties are typed AttributeCollection objects:
{ "okButton": { "type": "dev.javai18n.swing.AbstractButtonPropertyBundle", "Text": "OK", "Mnemonic": 79, "ToolTipText": "Confirm and close" }, "cancelButton": { "type": "dev.javai18n.swing.AbstractButtonPropertyBundle", "Text": "Cancel", "Mnemonic": 67 }}The French variant only needs to list what changes:
{ "okButton": { "type": "dev.javai18n.swing.AbstractButtonPropertyBundle", "Text": "D'accord", "Mnemonic": 68 }}Locale-change events
Section titled “Locale-change events”// Add a listener to any LocalizablemyFrame.addLocaleEventListener(event -> { // called on the correct UI thread by the i18n-swing / i18n-fx layer statusBar.setText("Locale: " + event.getLocale().getDisplayName());});
// Fire to all listenersmyFrame.setBundleLocale(Locale.JAPAN);JPMS module declaration
Section titled “JPMS module declaration”module my.module { requires dev.javai18n.core;
// For each Localizable class in your module: uses com.example.MyFrameProvider; provides com.example.MyFrameProvider with com.example.spi.ModuleProviderImpl;}Each Localizable class on the module path requires a corresponding SPI interface that extends ResourceBundleProvider. A single ModuleProviderImpl can implement all such interfaces in the module.
Quick start
Section titled “Quick start”public class MyFrame extends LocalizableImpl {
static { MyModuleRegistrar.ensureRegistered(); }
@Override public Locale[] getAvailableLocales() { return new Locale[]{ Locale.ROOT, Locale.FRANCE }; }}
// Create and useMyFrame frame = new MyFrame();frame.setBundleLocale(Locale.FRANCE);ResourceBundle rb = frame.getResourceBundle();Register a GetResourceBundleCallback singleton in your module, then declare uses / provides in module-info.java. See the JPMS section above and the test module in the repository for a complete example.