You are currently viewing Internationalization in JavaFX: Building Multilingual Apps

Internationalization in JavaFX: Building Multilingual Apps

In today’s interconnected world, creating software that caters to a global audience is essential. Whether you’re developing a business application, a mobile app, or a desktop program, making your software accessible to users from different language backgrounds is a significant factor in its success. JavaFX, a popular framework for building Java-based desktop applications, provides powerful tools for internationalization, allowing developers to create multilingual applications that can reach a broader audience. In this article, we will explore the concept of internationalization in JavaFX and demonstrate how to build multilingual apps effectively.

What is Internationalization and Localization?

Internationalization (i18n) is the process of designing your application so that it can be easily adapted to different languages and regions without code changes. This typically involves separating user-interface text and other locale-specific data from the application’s code. Localization (l10n) is the process of translating and adapting an internationalized application to a specific locale or language. This includes translating text, formatting dates and numbers, and adjusting other cultural aspects.

Creating Resource Bundles

To build a multilingual JavaFX application, the first step is to externalize your application’s text resources into resource bundles. Resource bundles are key-value pairs that store localized text for your application. Create a folder named resources in your project directory. Inside the resources folder, create a separate properties file for each supported language, naming them with the language code and the .properties extension (e.g., language_en.properties for English, language_fr.properties for French, language_de.properties for German, language_es.properties for Spanish).

Here’s an example language_en.properties file for English:

# English translations
label=Hello, World!
title=Internationalization in JavaFX: Building Multilingual Apps

Here’s language_fr.properties for French:

# French translations
label=Bonjour le monde!
title=Internationalisation dans JavaFX : création d'applications multilingues

Here’s language_de.properties for German:

# German translations
label=Hallo Welt!
title=Internationalisierung in JavaFX: Erstellen mehrsprachiger Apps

And here’s language_es.properties for Spanish:

# Spanish translations
label=¡Hola Mundo!
title=Internacionalización en JavaFX: creación de aplicaciones multilingües

Loading Resource Bundles

In your JavaFX application, you can load the appropriate resource bundle based on the user’s locale. Here’s an example:

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.*;
import javafx.stage.Stage;

import java.util.Locale;
import java.util.ResourceBundle;

public class Main extends Application {

    // The parent layout manager
    private final BorderPane parent = new BorderPane();

    // The resource bundle
    private ResourceBundle bundle;

    @Override
    public void init() throws Exception {
        super.init();

        // Determine the user's locale, based on user preferences
        Locale locale = Locale.getDefault();

        // Load the appropriate resource bundle
        bundle =  ResourceBundle.getBundle("resources.language", locale);

        // Build the user interface
        this.buildUI();

    }

    private void buildUI() {

        // Create UI components
        Label label = new Label(this.bundle.getString("label"));

        // Add the Label to the center of the BorderPane
        this.parent.setCenter(label);

    }

    @Override
    public void start(Stage stage) throws Exception {

        // Setup and display the stage
        this.setupStage(stage);
    }

    private void setupStage(Stage stage) {

        // Create a scene with the BorderPane as the root
        Scene scene = new Scene(this.parent, 640, 480);

        // Set the stage title
        stage.setTitle(this.bundle.getString("title"));

        // Set the scene for the stage
        stage.setScene(scene);

        // Center the stage on the screen
        stage.centerOnScreen();

        // Display the stage
        stage.show();

    }

}

In this example, we determine the user’s locale (you can use Locale.getDefault() or ask the user to select a language), load the appropriate resource bundle, and display the localized content on the UI.

Language Selection

To provide a user-friendly way to select a language and update the UI components accordingly, you can use a ComboBox to let the user choose their preferred language. When the user selects a language from the ComboBox, the application will update its locale and refresh the UI components with the selected language.

Here’s how you can achieve this in JavaFX:

import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.layout.*;
import javafx.stage.Stage;

import java.util.Locale;
import java.util.ResourceBundle;

public class Main extends Application {

    // The parent layout manager
    private final BorderPane parent = new BorderPane();

    // Determine the user's locale, based on user preferences
    private Locale locale;

    // The resource bundle
    private ResourceBundle bundle;

    @Override
    public void init() throws Exception {
        super.init();

        // Determine the user's locale, based on user preferences
        this.locale = Locale.getDefault();

        // Load the appropriate resource bundle
        bundle =  ResourceBundle.getBundle("resources.language", this.locale);

        // Build the user interface
        this.buildUI();

    }

    private void buildUI() {

        // Create UI components
        Label label = new Label(this.bundle.getString("label"));

        ObservableList<String> languages = FXCollections.observableArrayList(
                "English", "French", "German", "Spanish"
        );

        // Create a ComboBox for language selection
        ComboBox<String> languageComboBox = new ComboBox<>(languages);

        languageComboBox.setValue(this.locale.getDisplayLanguage());

        // Handle ComboBox selection changes
        languageComboBox.getSelectionModel().selectedItemProperty().addListener((observable, oldLanguage, newLanguage) -> {

            this.locale = switch (newLanguage) {
                case "German" -> Locale.forLanguageTag("de");
                case "French" -> Locale.forLanguageTag("fr");
                case "Spanish" -> Locale.forLanguageTag("es");
                default -> Locale.forLanguageTag("en");
            };

            this.bundle = ResourceBundle.getBundle("resources.language", this.locale);

            // Update UI components with localized strings
            label.setText(bundle.getString("label"));

            ((Stage)this.parent.getScene().getWindow()).setTitle(bundle.getString("title"));

        });

        // Create a VBox to hold UI components
        VBox vbox = new VBox(10, label, languageComboBox);
        vbox.setAlignment(Pos.CENTER);

        // Add the VBox to the center of the BorderPane
        this.parent.setCenter(vbox);

    }

    @Override
    public void start(Stage stage) throws Exception {

        // Setup and display the stage
        this.setupStage(stage);
    }

    private void setupStage(Stage stage) {

        // Create a scene with the BorderPane as the root
        Scene scene = new Scene(this.parent, 640, 480);

        // Set the stage title
        stage.setTitle(this.bundle.getString("title"));

        // Set the scene for the stage
        stage.setScene(scene);

        // Center the stage on the screen
        stage.centerOnScreen();

        // Display the stage
        stage.show();

    }

}

We’ve added a ChoiceBox to the UI, allowing the user to select between “English”, “Español”, “French”, and “German” When the user selects a language, the application sets the appropriate locale and updates the UI components accordingly.

Internationalization in JavaFX: Building Multilingual Apps

ResourceBundle Control API for Fallback Mechanisms

Sometimes, your application might not have translations for all supported languages. In such cases, you can use the ResourceBundle Control API to define fallback mechanisms. For example, you can specify a default language to use when a translation is missing.

Here’s how you can modify the previous example to include fallback:

import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.layout.*;
import javafx.stage.Stage;

import java.util.Locale;
import java.util.ResourceBundle;

public class Main extends Application {

    // The parent layout manager
    private final BorderPane parent = new BorderPane();

    // Determine the user's locale, based on user preferences
    private Locale locale;

    // The resource bundle
    private ResourceBundle bundle;

    @Override
    public void init() throws Exception {
        super.init();

        // Determine the user's locale, based on user preferences
        this.locale = Locale.getDefault();

        // Create a custom ResourceBundle.Control with fallback to English
        ResourceBundle.Control control = new ResourceBundle.Control() {

            @Override
            public Locale getFallbackLocale(String baseName, Locale locale) {
                // Fallback to English if translation is missing
                Main.this.locale = Locale.ENGLISH;
                return Main.this.locale;
            }

        };

        // Load the appropriate resource bundle with the custom control
        bundle =  ResourceBundle.getBundle("resources.language", this.locale, control);

        // Build the user interface
        this.buildUI();

    }

    private void buildUI() {

        // Create UI components
        Label label = new Label(this.bundle.getString("label"));

        ObservableList<String> languages = FXCollections.observableArrayList(
                "English", "French", "German", "Spanish", "Bemba"
        );

        // Create a ComboBox for language selection
        ComboBox<String> languageComboBox = new ComboBox<>(languages);

        languageComboBox.setValue(this.locale.getDisplayLanguage());

        // Handle ComboBox selection changes
        languageComboBox.getSelectionModel().selectedItemProperty().addListener((observable, oldLanguage, newLanguage) -> {

            this.locale = switch (newLanguage) {
                case "German" -> Locale.forLanguageTag("de");
                case "French" -> Locale.forLanguageTag("fr");
                case "Spanish" -> Locale.forLanguageTag("es");
                default -> Locale.forLanguageTag("en");
            };

            this.bundle = ResourceBundle.getBundle("resources.language", this.locale);

            // Update UI components with localized strings
            label.setText(bundle.getString("label"));

            ((Stage)this.parent.getScene().getWindow()).setTitle(bundle.getString("title"));

        });

        // Create a VBox to hold UI components
        VBox vbox = new VBox(10, label, languageComboBox);
        vbox.setAlignment(Pos.CENTER);

        // Add the VBox to the center of the BorderPane
        this.parent.setCenter(vbox);

    }

    @Override
    public void start(Stage stage) throws Exception {

        // Setup and display the stage
        this.setupStage(stage);
    }

    private void setupStage(Stage stage) {

        // Create a scene with the BorderPane as the root
        Scene scene = new Scene(this.parent, 640, 480);

        // Set the stage title
        stage.setTitle(this.bundle.getString("title"));

        // Set the scene for the stage
        stage.setScene(scene);

        // Center the stage on the screen
        stage.centerOnScreen();

        // Display the stage
        stage.show();

    }

}

In this updated code, we create a custom ResourceBundle.Control that specifies English as the fallback language. If a translation is missing for the user’s locale, the application will use the English version as a fallback.

Handling Date and Number Formatting

In addition to managing text resources, you may also need to format dates, numbers, and currencies according to the user’s locale. JavaFX provides the java.text package for this purpose.

import java.text.NumberFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;

public class LocaleUtils {

    // Date format pattern
    private static final String DATE_FORMAT = "dd/MM/yyyy";

    // Create a date formatter for the specified locale
    public static SimpleDateFormat getDateFormatter(Locale locale) {
        return new SimpleDateFormat(DATE_FORMAT, locale);
    }

    // Format a date to a localized string
    public static String formatDate(Date date, Locale locale) {
        SimpleDateFormat formatter = getDateFormatter(locale);
        return formatter.format(date);
    }

    // Parse a localized date string to a Date object
    public static Date parseDate(String dateString, Locale locale) throws ParseException {
        SimpleDateFormat formatter = getDateFormatter(locale);
        return formatter.parse(dateString);
    }

    // Create a number formatter for the specified locale
    public static NumberFormat getNumberFormatter(Locale locale) {
        return NumberFormat.getNumberInstance(locale);
    }

    // Convert a number to a localized string
    public static String formatNumber(Number number, Locale locale) {
        NumberFormat formatter = getNumberFormatter(locale);
        return formatter.format(number);
    }

    // Convert a localized string to a number
    public static Number parseNumber(String numberString, Locale locale) throws ParseException {
        NumberFormat formatter = getNumberFormatter(locale);
        return formatter.parse(numberString);
    }

}

In the above code, we have defined utility methods for formatting and parsing dates and numbers according to the specified locale. You can use these methods throughout your JavaFX application to ensure proper localization.

Conclusion

Internationalization is a crucial aspect of software development, especially when targeting a global audience. JavaFX offers robust tools and techniques to build multilingual applications effectively. By following best practices for internationalization and localization, you can create software that speaks to users in their preferred language and provides a seamless experience, regardless of their location or language background. This not only enhances user satisfaction but also opens up new opportunities for your software in international markets.

I hope you found this article informative and useful. If you would like to receive more content, please consider subscribing to our newsletter.

Leave a Reply