Skip to content

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;
}

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.

A component that owns a Resource and reacts to locale changes. Implement this interface to make any object locale-aware.

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();

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.


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:

PriorityFormatFile
1Java classMyFrameBundle.class
2JSONMyFrameBundle.json
3XMLMyFrameBundle.xml
4PropertiesMyFrameBundle.properties

Both classpath and JPMS module-path deployments use the same loading strategy.


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)

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
}
}

// Add a listener to any Localizable
myFrame.addLocaleEventListener(event -> {
// called on the correct UI thread by the i18n-swing / i18n-fx layer
statusBar.setText("Locale: " + event.getLocale().getDisplayName());
});
// Fire to all listeners
myFrame.setBundleLocale(Locale.JAPAN);

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.


public class MyFrame extends LocalizableImpl {
static {
MyModuleRegistrar.ensureRegistered();
}
@Override
public Locale[] getAvailableLocales() {
return new Locale[]{ Locale.ROOT, Locale.FRANCE };
}
}
// Create and use
MyFrame frame = new MyFrame();
frame.setBundleLocale(Locale.FRANCE);
ResourceBundle rb = frame.getResourceBundle();