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