Ideally, the javafx.controls module would make use of the ResourceBundleProvider SPI. This would let your own module provide an implementation that would find the custom resource bundles. But since JavaFX does not make use of said SPI (as of version 21.0.1), and since split packages are not allowed, another solution is needed.
One option is to make use of --patch-module. For instance, if you wanted to add translations for Russian (which is not one of the provided languages in JavaFX 21), then you would create the properties file in the correct package:
com/sun/javafx/scene/control/skin/resources/controls_ru.properties
And place that in some directory; let's say that directory is named bundles for this example. Then at run-time, you would use the following option:
--patch-module javafx.controls=bundles
Note: This assumes bundles is relative to the working directory. If that's not the case, then use the proper relative path or an absolute path.
Example
Here's a full example using Java 21.0.1, JavaFX 21.0.1, and Gradle 8.5.
Source Code
module-info
module app {
requires javafx.controls;
exports sample to
javafx.graphics;
}
sample.Main
package sample;
import java.util.Locale;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonType;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class Main extends Application {
@Override
public void start(Stage primaryStage) throws Exception {
Locale.setDefault(Locale.of("ru"));
var dialogBtn = new Button("Show dialog");
dialogBtn.setOnAction(
e -> {
var alert = new Alert(AlertType.CONFIRMATION);
alert.getButtonTypes().setAll(ButtonType.YES, ButtonType.NO);
alert.initOwner(primaryStage);
alert.setContentText("This text is intentionally English.");
alert.show();
});
var root = new StackPane(dialogBtn);
primaryStage.setScene(new Scene(root, 500, 300));
primaryStage.setTitle("Demo");
primaryStage.show();
}
}
Bundle Properties File
controls_ru.properties
Dialog.yes.button = Да
Dialog.no.button = Нет
Dialog.confirm.title = Подтверждение
Dialog.confirm.header = Подтверждение
Note: There are many more properties you'd have to set to properly add a language, but these are all that were needed for this example.
These translations were taken from Google Translate. I have no idea how accurate they are.
| English |
Russian |
| Yes |
Да |
| No |
Нет |
| Confirmation |
Подтверждение |
Gradle Files
settings.gradle.kts
rootProject.name = "app"
build.gradle.kts
plugins {
id("org.openjfx.javafxplugin") version "0.1.0"
application
}
group = "sample"
version = "0.1.0"
application {
mainModule = "app"
mainClass = "sample.Main"
}
javafx {
modules("javafx.controls")
version = "21.0.1"
}
repositories {
mavenCentral()
}
tasks {
named<JavaExec>("run") {
jvmArgs("--patch-module=javafx.controls=${file("bundles")}")
}
}
Project Structure
<PROJECT_DIRECTORY>
│ build.gradle.kts
│ settings.gradle.kts
│
├───bundles
│ └───com
│ └───sun
│ └───javafx
│ └───scene
│ └───control
│ └───skin
│ └───resources
│ controls_ru.properties
│
└───src
└───main
└───java
│ module-info.java
│
└───sample
Main.java
Note the bundles directory is not under src/main/resources. You don't want to package this in your own module, as doing so will cause a split-package error. Unfortunately, this means you have to find a way to deploy bundles alongside your application. I assume doing this would be relatively easy using a tool like jpackage, but I haven't tried it.
Output
From executing the Gradle run task.

--patch-module(see my answer below) might be to simply modify thejavafx.controlsJAR file and insert your properties file. This would be done as part of your project's build process. Though I would prefer using--patch-module, as that seems more "official" to me.