3

I would like to ask for a help with internationalization of buttons in JavaFX built in dialogs.

Translations for buttons are available in https://github.com/openjdk/jfx/tree/master/modules/javafx.controls/src/main/resources/com/sun/javafx/scene/control/skin/resources There are some other countries and also fallback to English variant.

From code POV translatiosn are loaded by this class: https://github.com/openjdk/jfx/blob/master/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/skin/resources/ControlResources.java

public final class ControlResources {

    // Translatable properties
    private static final String BASE_NAME = "com/sun/javafx/scene/control/skin/resources/controls";

    public static String getString(String key) {
        return ResourceBundle.getBundle(BASE_NAME).getString(key);
    }
}

To add custom translation I would create new controls_xx.properties file in resources of my application under following package: com/sun/javafx/scene/control/skin/resources/controls.

The issue is that "java-modules" do not allow to have one package in multiple modules.

What are the possibilities to load custom translations for JavaFX dialogs?

Note: disabling "java-modules" is not an option.

Thank you

EDIT: I'm aware of this thread: Javafx Internationalization with custom language

4
  • Perhaps something to ask on the openjfx-dev mailing list. Commented Dec 4, 2023 at 12:03
  • This kind of functionality is documented well for the standard JDK and Swing distributions. I think that allows customization of locales via service provider interfaces such as java.util.spi and java.text.spi. If so, the service provider would allow you to add new locales and still work with modules. But I don't know where it is equivalently documented for JavaFX (or if documentation or spi classes exists). Commented Dec 4, 2023 at 23:34
  • I checked the JavaFX 21 documentation and it doesn't define any services for modules at all. Perhaps a service to allow addition of new language localization resources for built-in JavaFX platform dialogs (or other text), could be added in the future via a feature request (again perhaps something to discuss on the openjfx-dev mailing list). Commented Dec 4, 2023 at 23:43
  • 1
    An alternative to --patch-module (see my answer below) might be to simply modify the javafx.controls JAR 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. Commented Dec 5, 2023 at 4:53

2 Answers 2

3

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.

screenshot of running application

Sign up to request clarification or add additional context in comments.

Comments

0

I am not 100% sure I understand, but here is a workaround if I understand you correctly.

This is tedious: Find the button using lookupButton.

Example

import java.util.Locale;
import java.util.Optional;
import java.util.ResourceBundle;
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;


/**
 * JavaFX App
 */
public class App extends Application {

    @Override
    public void start(Stage stage) {
        Locale defaultLocale = Locale.getDefault();
        System.out.println("default Locale: " + defaultLocale.toLanguageTag());
        ResourceBundle resourceBundle = ResourceBundle.getBundle("sed.work.internationalizationexample.resourcebundle.main", defaultLocale);
        
        Button btnAlert = new Button("Press for alert!");
        
        btnAlert.setOnAction((t) -> {
            Alert alert = new Alert(AlertType.CONFIRMATION);
            alert.setTitle(resourceBundle.getString("alertTitle"));
            alert.setHeaderText(resourceBundle.getString("alertHeaderText"));
            alert.setContentText(resourceBundle.getString("alertContentText"));

            Button okButton = (Button)alert.getDialogPane().lookupButton(ButtonType.OK);
            okButton.setText(resourceBundle.getString("alertOkBtnText"));
            Button cancelButton = (Button)alert.getDialogPane().lookupButton(ButtonType.CANCEL);
            cancelButton.setText(resourceBundle.getString("alertCancelBtnText"));
            
            Optional<ButtonType> result = alert.showAndWait();
            if (result.get() == ButtonType.OK){
                // ... user chose OK
            } else {
                // ... user chose CANCEL or closed the dialog
            }
        });

        
        //var label = new Label("Hello, JavaFX " + javafxVersion + ", running on Java " + javaVersion + ".");
        var scene = new Scene(new StackPane(btnAlert), 640, 480);
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        launch();
    }

}

Output

enter image description here

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.