Internationalization has become an invaluable method for capturing the attention of Java app users, making them feel right at home when using one. In this article, let’s look into how we can perform translations in our apps or websites and how to make sure our applications support multiple languages using Java internationalization.
We will be covering the following topics in this tutorial:
- Java i18n.
Locale
andResourceBundle
classes.- Switching between locales.
- Pluralization.
- Date-time localization.
- Using Cloud Translation API.
You may also be interested in learning how to localize Android applications.
Special thanks to Anton Malich for technical review and insights.
Internationalization in Java
So, you have an exciting idea for a Java app/website or maybe you use a seasoned Java application in your workplace. But what if your Java application plans to support multiple languages? It needs to operate swiftly, not just in the common English language, but also in a plethora of other languages.
That’s where Java internationalization comes in.
How Java supports Internationalization
Primarily we talk about three concepts when it comes to localization in Java. For localization purposes we will be needing:
- Locale class objects representing the specific geographical, political, and cultural regions we plan to support.
- Resources holding locale-specific data in the form of classes or properties files.
- ResourceBundle class objects fetching data for the relevant locales from the respective resources.
Note that these Java classes are already built into the java.util
package.
Rules to follow
So, when adding language resources, it is essential to follow these rules:
- All resource files must reside in the same package.
- All resource files must share a common base name. Please remember this term as we’ll be using it later on in this tutorial. Note that your base name can be whatever you like.
- The default resource should simply have the base name:
bundle.properties
orBundle.java
- Additional
properties
files must be named following this pattern:
base name
_ language suffix
bundle_fr.properties
orBundle_fr.java
- Let’s assume that at least one resource file with a language suffix already exists. However for a particular language, you might like to narrow down the target locale to specific countries as well. In this case, you can add more resource files with additional country suffixes. For example:
base name
_ language suffix
_ country suffix
bundle_en_US.properties
orBundle_en_US.java
- Likewise, following the same logic, you may narrow it down to resource files with an additional variant suffix as well. For instance:
base name
_ language suffix
_ country suffix
_ variant suffix
bundle_th_TH_TH.properties
orBundle_th_TH_TH.java
Simple translations in Java
Let’s build a basic Java localization example utilizing the previously mentioned features to get a proper idea of how internationalization works.
Create a new Java Application named java-i18n
.
The source code is available on GitHub.
Create a Resource Bundle
First off, let’s add some language resources for Java internationalization.
Resources in Property File Form
We can create simple Java properties
files to hold our resources. These will be handled by the concrete class PropertyResourceBundle for Java localization purposes.
Create a new res
package within the main package and create a bundle.properties
file inside it.
In the properties file, add a few words or sentences to be internationalized on your Java app in key-value form.
hello=Hello welcome=Welcome to my app ok=OK cancel=Cancel tryagain=Please try again
Here, 'bundle'
will act as the base name, and the bundle.properties
file will act as the default resource file that our Java application will resort to in the case that no match is found.
Note that this is the serialized form in which we can keep our resource files. It provides us with the useful advantage of not requiring any project recompilations on resource file updates. Nonetheless, at the same time, the serialized form has the disadvantage of resource value types being restricted to strings.
Resources in Java class form
Alternatively, we can achieve the same result by creating the resource using a class extending ListResourceBundle abstract class.
In the res
package, create a Bundle
class extending ListResourceBundle
class and override its getContents
method.
package res; import java.util.ListResourceBundle; public class Bundle extends ListResourceBundle { @Override protected Object[][] getContents() { return new Object[][] { {"hello", "Hello"}, {"ourfeatures", new String[] {"Collaborative Translation", "Localization Workflow Management", "Localization Process Automation"}} }; } }
Note that this resource is in a deserialized form and hence provides all the benefits of non-static objects such as less overheads and proper garbage collection to name but a few.
{"ourfeatures", new String[] {"Collaborative Translation", "Localization Workflow Management", "Localization Process Automation"}}
Evidently, the most notable advantage would be the ability to hold any type of object as a resource value unlike when using property files.
Add more resources
Let’s say we need to localize our app to cater to Italian-speaking users as well. Simply add another properties file containing the same keys as in bundle.properties
but with their Italian language counterparts as values.
In this case, let’s add a new bundle_it_IT.properties
file in the same package and add the corresponding language values.
hello=Ciao welcome=Benvenuti nella mia app ok=OK cancel=Annulla tryagain=Per favore riprova
Like last time, instead of using a property file we can create a Bundle_it_IT
class to hold the resources as well. Make a new Bundle_it_IT
class in the same package as follows:
package res; import java.util.ListResourceBundle; public class Bundle_it_IT extends ListResourceBundle { @Override protected Object[][] getContents() { return new Object[][] { {"hello", "Ciao"}, {"ourfeatures", new String[] {"Traduzione Collaborativa", "Gestione del flusso di lavoro di localizzazione", "Automazione del processo di localizzazione"}} }; } }
The multiple resources together form a resource bundle which can be used for our i18n purposes.
Add some locale class objects
Let’s create a Locale object within the main method of our java-i18n
project. This will simply represent an Italian speaking geographical region in Italy.
Locale locale_it_IT = new Locale("it", "IT");
Refer to this list for locales supported on Java 8.
If you are getting deprecation warnings, use the following approach instead:
Locale locale_it_IT = Locale.of("it", "IT");
Use ResourceBundle class
Now it’s time to fetch some language-specific data from our resource bundle for the program. Note that we must always provide the fully-qualified class name of the base name when providing the first parameter of the ResourceBundle.getBundle
static method.
ResourceBundle resourceBundle = ResourceBundle.getBundle("res.bundle", locale_it_IT); System.out.println(resourceBundle.getString("welcome"));
This should properly localize and print out the resource value corresponding to the it-IT
locale.
But, what if someone asked for data from a locale that is absent from our resource bundle?
Locale locale_ru_RU = new Locale("ru", "RU"); ResourceBundle resourceBundle = ResourceBundle.getBundle("res.bundle", locale_ru_RU); System.out.println(resourceBundle.getString("welcome"));
You’ll see that this prints out a value belonging to an en-US
locale. This is because, as we discussed earlier when initially creating property resources, our Java application will always resort to the default resource file if no match was found. In this case it would be the bundle.properties
file.
Switching between locales
How about we create a simple GUI app to get a visual idea of how locale switching looks in the User Interface of a Java I18n application? For this, let’s create a Java Swing GUI application that utilizes the language resources we created earlier. There will be an additional Switch
button that switches the language of the Java app each time the user presses it.
Prerequisites
- NetBeans IDE
- Maven
I will be using NetBeans IDE 11.3 for Java GUI development purposes.
Create a new Maven project named java-i18n-gui
. Create a gui
package in the project and make a new JFrame form named SwitchLang
inside it.
Add GUI Components
Let’s add some GUI components as follows:
- First, add two JLabel components named
jLabelHello
andjLabelWelcome
. - Next, add two JButton components named
jButtonOk
andjButtonCancel
. - Finally, add another JButton named
jButtonSwitchLang
with a text value ofSwitch
.
Add language resources
Now we need to create a resource bundle containing a few language resources and synchronize it with our SwitchLang JFrame form. Follow these steps to achieve this:
First off following Maven conventions https://maven.apache.org/guides/introduction/introduction-to-the-standard-directory-layout.html, click on the Files tab in Netbeans and create a new resources folder in the src/main/ directory of the project. This src/main/resources path will automatically be marked as a resource directory next time we build the project using Maven.
Create a new bundle.properties file within resources. This will act as the default resource file of our resource bundle.
Open the SwitchLang form from the Projects window and click on the Design tab in the Editor.
In the Navigator section, in the lower-left corner, click on the root node in the upper corner. This should be mentioned as Form SwitchLang in our case.
Next, in the Properties window on the right, look for an option called Properties Bundle. For its value browse src/main/resources and select the default resource file bundle.properties we created a little while ago.
In the same window, mark the Automatic Internationalization option as checked.
Open up the bundle.properties
file again and replace its content as follows:
SwitchLang.jLabelHello.text=Hello SwitchLang.jLabelWelcome.text=Welcome to my app SwitchLang.jButtonOk.text=OK SwitchLang.jButtonCancel.text=Cancel SwitchLang.jButtonSwitchLang.text=Switch
Note that we simply set resource keys representing names of the Swing elements we created earlier and put their corresponding resource values on an en-US
locale.
Finally, create a new bundle_it_IT.properties
file within resources and put these lines as its content:
SwitchLang.jLabelHello.text=Ciao SwitchLang.jLabelWelcome.text=Benvenuti nella mia app SwitchLang.jButtonOk.text=OK SwitchLang.jButtonCancel.text=Annulla SwitchLang.jButtonSwitchLang.text=Interruttore
Here we added a resource file on an it-IT locale.
Now, our resource bundle is nicely synced with the SwitchLang
JFrame form elements.
Code switch button functionality
Finally, we need to code the functionality of jButtonSwitchLang
.
Double click on the jButtonSwitchLang
button in the Editor
to get us into the jButtonSwitchLangActionPerformed
private method which will hold the action performed upon clicking the Switch
button. Let’s set the button click action to make the java-i18n-gui
app change its locale to Italian.
First off, we need to retrieve the it-IT
locale resource bundle. Code this in the jButtonSwitchLangActionPerformed
private method:
Locale locale_it_IT = new Locale("it", "IT"); ResourceBundle resourceBundleIT = ResourceBundle.getBundle("bundle", locale_it_IT);
Make sure to import the required classes from the java.util
package.
As the final step, let’s add these lines to change the text of our java-i18n-gui
Swing elements to the it-IT
locale.
jLabelHello.setText(resourceBundleIT.getString("SwitchLang.jLabelHello.text")); jLabelWelcome.setText(resourceBundleIT.getString("SwitchLang.jLabelWelcome.text")); jButtonOk.setText(resourceBundleIT.getString("SwitchLang.jButtonOk.text")); jButtonCancel.setText(resourceBundleIT.getString("SwitchLang.jButtonCancel.text")); jButtonSwitchLang.setText(resourceBundleIT.getString("SwitchLang.jButtonSwitchLang.text"));
That’s it! Run the app and press the Switch
button to check that switching between locales works properly.
A few other Java internationalization features
Let’s look at a few more Java features that could come in handy when dealing with localization in Java.
Pluralization
Handling the proper pluralization of text can become quite a necessity when dealing with Java i18n.
For instance, let’s assume we need to handle text representing some apples based on a provided quantity. So for the English language it would take this form:
- 0 apples
- 1 apple
- 2 apples
We can code a Java method to handle this, like so:
public static String getAppleCountMsg(int count) { if (count == 1) { return "1 apple"; } else if (count == 0 || count > 1) { return count + " apples"; } else { return "You don't count apples in minus numbers! Please input a whole number"; } }
However, as you can see, this is still a tedious amount of work just to handle some text about apples on a Java app.
ChoiceFormat Helps a Bit
Java provides a ChoiceFormat class that we can use to make the previous code look a bit less messy.
public static ChoiceFormat getAppleCountMsgChoiceFormat() { double[] minAppleCount = {0, 1, 2}; String[] appleCountFormat = {"apples", "apple", "apples"}; return new ChoiceFormat(minAppleCount, appleCountFormat); }
The ChoiceFormat class accepts two parameters.
The first parameter holds an array of primitive doubles marking the starting points of a set of intervals. For instance, {0, 1, 2}
in our example represents three intervals in ascending order:
- The first interval ranges from 0 to 1 including double value 0 and excluding double value 1.
- Similarly, the second interval ranges from 1 to 2 including 1 and excluding 2.
- And finally, the third interval ranges from 2 to upwards including 2.
The second parameter holds a String array with values to be used according to each of those intervals. Therefore in our case, it should consecutively hold apples
, apple
, apples
.
We can pass the ChoiceFormat
object returned from getAppleCountMsgChoiceFormat()
to a separate method like this to retrieve our apple count messages in much the same way.
public static String getAppleCountMsg(ChoiceFormat appleCountChoiceFormat, int count) { return count + " " + appleCountChoiceFormat.format(count); }
Note that since the pluralization forms for different languages could deviate from the pluralization form in English, you would eventually have to create separate ChoiceFormat
objects manually for each of the languages you plan to support in your internationalized Java application.
Date and time
We can use getDateTimeInstance
method provided in the DateFormat class to handle localization in Java. For example, we can use the following to get the current date and time for a given locale in the SHORT date and time formatting style:
public String getCurrentDateAndTime(Locale locale) { DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, locale); return dateFormat.format(new Date()); }
Use Lokalise Instead
As we saw with our Java translation and internationalization examples, it can become quite an annoying and time-consuming task to properly internationalize your Java app. Why not get the help of a proper translation management system to bear the burden of this?
Lokalise comes to the rescue with the ability to manage translation files, and perform Google translations. It also has features like collaborative translations, easy integration with various other services, Quality Assurance tools for translations, and easy management of your translations through a central dashboard, among countless others.
Getting started with Lokalise is easier than you might think:
- Sign up for a free trial (No credit card information required).
- Log in to your account.
- Create a new project under any name you like.
- Upload your translation files, and edit them as required.
That’s it! You have successfully completed the basic Lokalise setup process.
See the Getting Started section for a collection of articles that will help you kick-start the Lokalise journey. Plus, you can refer to Lokalise API Documentation for a complete list of REST commands you can call on in your Lokalise translation project.
Meet Cloud Translation API
As a bonus, let me show you how to work with Google Translate API in your Java app.
Google Cloud Platform provides an API, namely Cloud Translation API, which we can easily employ for the previously mentioned use case of performing basic text translations.
Be sure to have Maven installed locally on your computer before continuing.
These will be the steps needed to integrate with Google Cloud Platform:
- Create Google Cloud project.
- Enable Google Cloud Translation API.
- Set up credentials.
Let’s see how we can do this one step at a time.
Create Google Cloud project
This step can be skipped if you have an existing Google Cloud project we can utilize for our translation purposes. Nevertheless, in case you don’t have an existing Google Cloud project, you can follow the steps mentioned in the Google Cloud documentation to create one.
Enable Google Cloud Translation API
Now we can get started, let’s enable the Cloud Translation API on our project. Head over to the Translation API homepage, select your Google Cloud project from the dropdown, and click Enable
.
Set up credentials
We have to set up some credentials to allow communications between Cloud Translation API and the Translator
app to take place effectively. These authentication steps are described in the Authentication section of the Google Cloud documentation.
Simple app with Cloud Translation API
As an example, let’s create a basic Java application performing translations through the Cloud Translation API Java client library. We will be calling it the Translator
application from here on out for easier reference.
The source code is available on GitHub.
Create Maven project
Create a new Maven project by running:
mvn archetype:generate -DgroupId=com.lokalise -DartifactId=translate -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false
Add dependencies to project
Adding the following dependency will introduce the Java client for Google Cloud Translation API to our Translator
project.
<dependency> <groupId>com.google.cloud</groupId> <artifactId>google-cloud-translate</artifactId> <version>2.28.0</version> </dependency>
Note that the dependency version mentioned here might have changed over time. Refer to the Maven repository to get the latest version.
If you are using Gradle:
implementation(“com.google.cloud:google-cloud-translate:2.28.0”)
Translator class
public class Translator { public static void main(String[] args) { //Initiate google cloud translation service Translate translate = TranslateOptions.getDefaultInstance().getService(); //1 String textToTranslate = "Localization in Java is fun"; //Perform translation Translation translation = translate.translate(textToTranslate, Translate.TranslateOption.sourceLanguage("en"), //2 Translate.TranslateOption.targetLanguage("it")); String translatedText = translation.getTranslatedText(); System.out.println(translatedText); } }
- The
TranslateOptions.getDefaultInstance
method forces the Google Cloud Translation Java client library to retrieve credentials through theGOOGLE_APPLICATION_CREDENTIALS
environment variable we set when setting up credentials for Cloud Translation API locally. - Note that providing the source language here is optional since Cloud Translation API can automatically detect languages. Hence, the
TranslateOption.sourceLanguage("en")
parameter is optional; but just to be on the safe side, it is best to manually provide the source language to avoid misinterpretations.
Consequently, running this program should print out an Italian translation of the textToTranslate
on the console.
See here for a list of languages supported by Google Cloud Translation API.
Conclusions on Java i18n
In this tutorial, we got the hang of Java internationalization and localization for your app or website. We discovered how to approach translations using Google Cloud Translation API, set up Translation API on a Google Cloud Project, and how we can carry out simple text translations using the Java client of Google Cloud Translation API on a simple Maven project.
Additionally, we looked into adding multi-language support to Java applications. We explored Java’s built-in Locale & ResourceBundle classes for Java localization purposes, and how we can utilize them in a basic Java internationalization example.
So, that marks the end of this tutorial. We hope we helped you get kick-started on Java localization through this article. Until next time, hasta la vista fellow coder!
Further reading
- Spring Boot internationalization i18n: Step-by-step with examples
- Java LocalDate localization tutorial: step by step examples
- How to choose the best translation management system for your team and company
- Date and time localization
- I18n and l10n: List of developer tutorials