Internationalization has become an invaluable method for engaging users and providing a seamless experience in today's globalized world. By tailoring your Java app to support multiple languages and cultural nuances, you make it accessible to users across different regions, ensuring they feel right at home. In this article, we’ll explore how Java internationalization can help you translate your apps or websites effectively and build software that supports diverse audiences with ease.
Special thanks to Anton Malich for insights and reviewing the source code.
Internationalization in Java
So, you have an exciting idea for a Java app or website, or maybe you’re managing a well-established Java application at work. But what happens when your application needs to support multiple languages? It must operate seamlessly, not only in English but also in a variety of other languages to cater to a global audience, possibly with the help of a JavaScript translation library.
That’s where Java internationalization comes in.
Support for Java internationalization
When it comes to software localization and supporting different languages in Java, three main concepts are essential to understand. These elements enable you to adapt your applications for different regions and languages effectively:
Locale class: Objects representing the specific geographical, political, and cultural regions you plan to support.
Resources: Locale-specific data stored in the form of classes or properties files.
ResourceBundle class: Objects used to fetch data for the relevant locales from the appropriate resources.
These classes are built into the java.util package, simplifying the implementation of Java internationalization in your web application or software.
Rules to follow
When adding language resources, ensure you follow these rules:
Resource file location: All resource files must reside in the same package.
Naming convention: All resource files must share a common base name. This base name is crucial and will be referenced throughout your code. You can choose any name you prefer.
For example:
The default resource should use only the base name:
bundle.properties or Bundle.java
Additional properties files must follow this pattern:
base name _ language suffix
Example: bundle_fr.properties or Bundle_fr.java
Narrowing down locales
If you need to target a specific country within a language, you can add more resource files using a country suffix. For instance:
base name _ language suffix _ country suffix
Example: bundle_en_US.properties or Bundle_en_US.java
For even more granularity, you can include a variant suffix:
base name _ language suffix _ country suffix _ variant suffix
Example: bundle_th_TH_TH.properties or Bundle_th_TH_TH.java
By following these conventions, you ensure your Java application adheres to internationalization best practices, making it easy to scale for new locales.
Simple translations in Java
To understand how internationalization works in Java, let’s build a basic localization example using the features we discussed earlier.
Create a new Java application
Start by creating a new Java application named java-i18n. This will serve as the foundation for demonstrating how to implement simple translations in Java.
Create a resource bundle
The first step in Java internationalization is to add language resources. These resources will serve as the foundation for localizing your application into multiple languages.
Resources in property file form
The simplest way to store localization data is by using Java properties files. These files are handled by the PropertyResourceBundle class and are perfect for Java localization.
Create the resource package: Add a new res package within your main package. Inside it, create a bundle.properties file.
Define key-value pairs: In the properties file, include the words or phrases to be internationalized in a key-value format:
hello=Hellowelcome=Welcome to my appok=OKcancel=Canceltryagain=Please try again
Here, bundle is the base name for the resource file.
The bundle.properties file acts as the default resource file, used when no locale-specific file matches.
Advantages:
Updating resource files doesn’t require recompilation, which streamlines maintenance.
Limitations:
Resource values are restricted to strings.
Resources in Java class form
Alternatively, you can create resources by extending the ListResourceBundle abstract class. This approach allows you to use more complex objects as resource values.
In the res package, create a Bundle class extending the ListResourceBundle class. Override the 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" }} }; }}
Benefits:
Supports any type of object as a resource value.
Reduces overhead and improves garbage collection compared to serialized property files.
Add more resources
To localize your app for Italian-speaking users, add another properties file with the same keys but with Italian translations as values.
Create a new properties file. Add a bundle_it_IT.properties file in the same package with the following content:
hello=Ciaowelcome=Benvenuti nella mia appok=OKcancel=Annullatryagain=Per favore riprova
Alternatively, create a resource class. You can also use a Bundle_it_IT class:
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"}} }; }}
These resources collectively form a resource bundle, which is essential for Java translation and i18n.
Add some locale class objects
To represent specific geographical, political, or cultural regions, you need to create Locale objects. For instance, in the main method of your java-i18n project, you can define a Locale for Italian speakers in Italy:
Locale locale_it_IT = new Locale("it", "IT");
If you encounter deprecation warnings, use the updated approach:
Now that we have created our resource bundle, let’s fetch language-specific data from it using the ResourceBundle class. To do this, call the ResourceBundle.getBundle static method, providing the fully-qualified class name of the base name as the first parameter.
For example, to retrieve data for the Italian (it-IT) locale:
This will correctly localize and print the specific value corresponding to the it-IT country code defined in your resource bundle.
Handling missing locales
But what happens if you request data for a locale not present in your resource bundle? For example:
Locale locale_ru_RU = new Locale("ru", "RU");ResourceBundle resourceBundle = ResourceBundle.getBundle("res.bundle", locale_ru_RU);System.out.println(resourceBundle.getString("welcome"));
In this case, the program will fall back to the default resource file (bundle.properties) and print the value from the en-US locale, as it is the default resource. This behavior is consistent with Java’s internationalization mechanism, ensuring your application always provides a response, even when the exact locale is unavailable.
Switching between locales
Let’s create a simple GUI application to visualize how locale switching works in a Java internationalization (i18n) application. Using Java Swing, we’ll design a GUI that utilizes the language resources we created earlier. The application will include a "Switch" button to toggle the app’s language each time it’s pressed.
Prerequisites
Before proceeding, ensure you have the following tools:
NetBeans IDE: I will be using NetBeans IDE 11.3 for Java GUI software development.
Maven: For managing project dependencies.
Set up the project
Create a new Maven project: Name the project java-i18n-gui.
Set up the GUI package: Add a gui package to the project.
Create a Swing form: Inside the gui package, create a new JFrame form named SwitchLang.
Add GUI components
To build the user interface for our locale-switching application, we’ll add the following components to the SwitchLang form:
JLabel components:
jLabelHello: Displays a localized greeting.
jLabelWelcome: Displays a localized welcome message.
JButton components:
jButtonOk: Represents an "OK" button with localized text.
jButtonCancel: Represents a "Cancel" button with localized text
jButtonSwitchLang: A button labeled “Switch” that toggles the app’s language when pressed.
Add language resources
To synchronize the SwitchLang JFrame form with localized resources, follow these steps:
Create a resources folder inside the src/main/ directory of your project.
Maven will automatically mark src/main/resources as a resource directory during the next build.
Create the default resource file:
Inside the resources folder, create a bundle.properties file.
This file will act as the default resource file for your resource bundle.
Link the resource file to the form:
Open the SwitchLang form from the Projects window and switch to the Design tab.
In the Navigator section (lower-left corner), click the root node labeled “Form SwitchLang.”
In the Properties window (on the right), locate the Properties Bundle field.
Browse to src/main/resources and select the bundle.properties file.
Check the Automatic Internationalization option in the same window.
Define default resource values: Open the bundle.properties file and replace its content with the following:
SwitchLang.jLabelHello.text=HelloSwitchLang.jLabelWelcome.text=Welcome to my appSwitchLang.jButtonOk.text=OKSwitchLang.jButtonCancel.text=CancelSwitchLang.jButtonSwitchLang.text=Switch
These keys correspond to the Swing elements created earlier and define their default (en-US) text.
Add an Italian locale file:
Create a new file named bundle_it_IT.properties in the resources folder.
Add the following content:
SwitchLang.jLabelHello.text=CiaoSwitchLang.jLabelWelcome.text=Benvenuti nella mia appSwitchLang.jButtonOk.text=OKSwitchLang.jButtonCancel.text=AnnullaSwitchLang.jButtonSwitchLang.text=Interruttore
This file defines text for the it-IT locale.
Now, the resource bundle is synchronized with the SwitchLang JFrame form elements, and the application is ready for locale-specific text display.
Code the switch button functionality
Now, let’s implement the functionality for the jButtonSwitchLang button to allow the user to switch the application’s locale to Italian.
Create the action method:
Double-click the jButtonSwitchLang button in the Editor.
This will create a jButtonSwitchLangActionPerformed method where you can define the behavior triggered by the button.
Retrieve the Italian locale resource bundle: Inside the jButtonSwitchLangActionPerformed method, add the following code to retrieve the resource bundle for the it-IT locale:
Locale locale_it_IT = new Locale("it", "IT");ResourceBundle resourceBundleIT = ResourceBundle.getBundle("bundle", locale_it_IT);
Ensure the necessary classes (Locale and ResourceBundle) from the java.util package are imported.
Update Swing element text: Use the resource bundle to update the text of your Swing components:
Click the Switch button to verify that the locale changes and the UI updates to display the Italian text.
A few other Java internationalization features
In addition to resource bundles and locales, Java offers additional features to help with localization tasks. Let’s explore a couple of these features.
Pluralization
Proper pluralization is often necessary when dealing with internationalized text. For example, consider representing the number of apples in English:
0 apples
1 apple
2 apples
A simple method to handle this in Java might look like this:
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"; }}
While functional, this approach can quickly become tedious and error-prone, especially when dealing with multiple languages.
Using ChoiceFormat for pluralization
Java’s ChoiceFormat class simplifies pluralization by reducing the clutter and making the code more maintainable. Here’s how it can be used:
It’s important to note that pluralization rules vary between languages. For example:
English has two plural forms: singular and plural.
Other languages, like Arabic or Russian, may have more complex pluralization rules.
You will need to manually create separate ChoiceFormat objects for each language supported in your internationalized application to account for these differences.
Using ICU MessageFormat for advanced pluralization
While the ChoiceFormat class in Java helps manage basic pluralization, it can become cumbersome when dealing with complex plural rules in languages with multiple plural forms (e.g., Arabic, Russian). For such cases, the ICU MessageFormat library provides a robust solution, supporting language-specific pluralization rules defined by the Unicode CLDR (Common Locale Data Repository).
Setting up ICU4J
To use ICU MessageFormat in your Java application, include the ICU4J library in your project. For Maven, add the following dependency:
The ICU MessageFormat class allows you to define plural rules dynamically based on the locale. Here's an example of using it to handle pluralization for a message about apples:
import com.ibm.icu.text.MessageFormat;import java.util.Locale;public class ICUPuralizationExample { public static void main(String[] args) { // Define the pluralization pattern String pattern = "{0, plural, one {# apple} other {# apples}}"; // Create a MessageFormat instance for a specific locale MessageFormat messageFormat = new MessageFormat(pattern, Locale.ENGLISH); // Format pluralized messages System.out.println(messageFormat.format(new Object[]{1})); // Output: "1 apple" System.out.println(messageFormat.format(new Object[]{5})); // Output: "5 apples" }}
Adding language-specific pluralization
ICU MessageFormat handles locale-specific rules automatically. For example, Russian uses distinct forms for 1, 2-4, and 5 or more. The same MessageFormat can be adapted to handle Russian pluralization:
public class ICULocaleExample { public static void main(String[] args) { // Define the pluralization pattern String pattern = "{0, plural, one {# яблоко} few {# яблока} other {# яблок}}"; // Create a MessageFormat instance for the Russian locale MessageFormat messageFormat = new MessageFormat(pattern, new Locale("ru", "RU")); // Format pluralized messages System.out.println(messageFormat.format(new Object[]{1})); // Output: "1 яблоко" System.out.println(messageFormat.format(new Object[]{3})); // Output: "3 яблока" System.out.println(messageFormat.format(new Object[]{5})); // Output: "5 яблок" }}
In this example:
The one rule applies to singular numbers like 1.
The few rule applies to numbers ending in 2, 3, or 4 (excluding 12-14).
The other rule applies to numbers like 0, 5, or anything not covered by the first two rules.
Why ICU is better for pluralization
Locale-awareness: Automatically adapts to the plural rules of the provided locale.
Ease of use: Allows complex pluralization rules to be defined in a single, readable pattern.
Flexibility: Supports not just pluralization but also gender and select rules for advanced localization.
Date and time
Handling localized date and time formats is a critical part of Java internationalization, especially in applications that cater to users from multiple regions. Java provides the DateFormat class to simplify this task.
Using getDateTimeInstance
The getDateTimeInstance method in the DateFormat class allows you to retrieve locale-specific date and time formats. For example, here’s how you can get the current date and time in the SHORT format for a given locale:
Locale locale_us = new Locale("en", "US");Locale locale_fr = new Locale("fr", "FR");System.out.println(getCurrentDateAndTime(locale_us)); // Output: 12/23/24, 4:30 PMSystem.out.println(getCurrentDateAndTime(locale_fr)); // Output: 23/12/24, 16:30
This ensures that the date and time are formatted appropriately for different regions.
Using DateTimeFormatter
Introduced in Java 8, the DateTimeFormatter class in the java.time package offers greater flexibility and modern formatting options. It works seamlessly with Locale to create locale-specific date and time formats.
Locale locale_de = new Locale("de", "DE");Locale locale_jp = new Locale("ja", "JP");System.out.println(getLocalizedDateAndTime(locale_de)); // Output: 23.12.24, 16:30System.out.println(getLocalizedDateAndTime(locale_jp)); // Output: 2024/12/23 16:30
Formatting numbers and currency
In addition to dates and times, Java internationalization also includes number and currency formatting. The NumberFormat class can be used to format values based on a given locale.
Locale locale_uk = new Locale("en", "GB");Locale locale_in = new Locale("hi", "IN");System.out.println(getFormattedCurrency(locale_uk, 1234.56)); // Output: £1,234.56System.out.println(getFormattedCurrency(locale_in, 1234.56)); // Output: ₹1,234.56
By leveraging these features, Java makes it easy to handle date, time, and number formatting in internationalized applications. These tools are invaluable when localizing web applications, handling HTTP requests, or developing software for a global audience.
Errors and fallbacks in Java internationalization
Errors can arise in internationalized applications when translations are missing, locales are unsupported, or external services (like Google Cloud Translation API) fail to respond. To ensure your Java application remains robust, it’s essential to implement fallback mechanisms.
Handling missing resources
When using ResourceBundle, a missing resource or locale may cause an exception or lead to undefined behavior. To handle this gracefully, Java automatically falls back to the default resource bundle (e.g., bundle.properties). However, you can add custom fallback logic to ensure meaningful behavior.
Example: Adding a fallback message for missing translations:
import java.util.Locale;import java.util.MissingResourceException;import java.util.ResourceBundle;public class TranslationFallbackExample { public static void main(String[] args) { Locale locale_ru = new Locale("ru", "RU"); // A locale without translations String key = "welcome"; ResourceBundle resourceBundle; try { resourceBundle = ResourceBundle.getBundle("bundle", locale_ru); System.out.println(resourceBundle.getString(key)); } catch (MissingResourceException e) { // Fallback to default message System.out.println("Translation not found. Default message: Welcome!"); } }}
This ensures the application gracefully handles scenarios where a specific locale’s resource bundle is missing or incomplete.
Dealing with API errors
When working with external APIs like the Google Cloud Translation API, network issues or service outages can disrupt functionality. Implement retry logic or fallbacks to a default translation mechanism to maintain a seamless user experience.
Example: Fallback logic with API failure:
import com.google.cloud.translate.Translate;import com.google.cloud.translate.TranslateOptions;import com.google.cloud.translate.Translation;public class ApiFallbackExample { public static void main(String[] args) { String textToTranslate = "Hello, world!"; String translatedText; try { Translate translate = TranslateOptions.getDefaultInstance().getService(); Translation translation = translate.translate( textToTranslate, Translate.TranslateOption.targetLanguage("es") ); translatedText = translation.getTranslatedText(); } catch (Exception e) { // Log the error and use a default translation System.err.println("Translation API failed: " + e.getMessage()); translatedText = "Hola, mundo!"; // Fallback to a predefined translation } System.out.println(translatedText); }}
Best practices for error handling and fallbacks
Use meaningful defaults: Always provide a sensible default message in case translations are unavailable. This ensures users can still understand the UI.
Log errors: Keep track of missing translations or failed API calls using a logging framework (e.g., SLF4J or Log4j). This helps identify gaps in localization.
Cache translations: Cache translated content to minimize reliance on external APIs during network issues or outages.
Validate resource bundles: Regularly test your ResourceBundle files to ensure all required keys exist for every locale.
Notify users gracefully: If a fallback is used, consider notifying users with a subtle message or log for development purposes.
Use Lokalise for Java internationalization
As demonstrated in our Java translation and internationalization examples, properly internationalizing your Java app can be a time-consuming and complex task. Managing resource bundles, handling pluralization, and formatting localized data often require significant effort. Why not simplify this process with a professional translation management system like Lokalise?
Why Lokalise?
Lokalise offers a comprehensive platform for managing translation files and streamlining the localization workflow. It provides several features to make internationalization more efficient:
Google translations: Generate initial translations with ease.
Collaborative translations: Enable teams to work together in real time.
Quality Assurance tools: Ensure your translations meet high standards.
Integration support: Seamlessly integrate with various services and platforms.
Centralized management: Handle all translations through an intuitive dashboard.
With Lokalise, you can save time and reduce the complexity of managing Java internationalization in your web applications or software projects.
Getting started with Lokalise
Follow these simple steps to start using Lokalise:
Sign up for a free trial: Create a free account on Lokalise (no credit card required).
Log in: Access your Lokalise dashboard.
Create a project: Start a new project and name it however you like.
Upload your translation files: Import your existing translation files (e.g., bundle.properties or other formats). Edit them as needed directly in Lokalise.
Manage and deploy: Use Lokalise’s tools to refine your translations and export updated files to your Java application.
More resources
Getting Started Guide: Explore step-by-step tutorials to help you kick off your Lokalise journey.
Lokalise API Documentation: Access a comprehensive list of REST commands to integrate and automate translation management in your projects.
By leveraging Lokalise, you can simplify your workflow, ensure high-quality translations, and focus on building an exceptional Java application for a global audience.
Meet Cloud Translation API
As a bonus, let’s explore how to integrate Google Translate functionality into your Java application using the Google Cloud Translation API. This API, provided by Google Cloud Platform, allows you to perform basic text translations with ease.
Before you begin, ensure that Maven is installed on your computer as it will be required for dependency management.
Create Google Cloud project
If you already have an existing Google Cloud project, you can skip this step and use it for your translation purposes. Otherwise, refer to the Google Cloud documentation for step-by-step instructions on creating a new project.
Enable Google Cloud Translation API
To get started, enable the Cloud Translation API for your project:
Select your Google Cloud project from the dropdown menu.
Click Enable to activate the API.
Set up credentials
To establish secure communication between the Cloud Translation API and your application, you need to set up credentials. Detailed instructions are available in the Authentication section of the Google Cloud documentation. Follow the steps to generate a service account key and download the JSON credentials file.
Simple app with Cloud Translation API
Let’s create a basic Java application, called the Translator application, to demonstrate how to perform translations using the Cloud Translation API Java client library.
Create a Maven project
Start by generating a new Maven project using the following command:
Let’s implement the Translator class to perform text translation using the Google Cloud Translation API. This simple Java program demonstrates how to set up the translation service, translate text, and retrieve the translated result.
import com.google.cloud.translate.Translate;import com.google.cloud.translate.TranslateOptions;import com.google.cloud.translate.Translation;public class Translator { public static void main(String[] args) { // Initialize Google Cloud Translation service Translate translate = TranslateOptions.getDefaultInstance().getService(); // 1 // Text to translate String textToTranslate = "Localization in Java is fun"; // Perform translation Translation translation = translate.translate( textToTranslate, Translate.TranslateOption.sourceLanguage("en"), // 2 Translate.TranslateOption.targetLanguage("it") ); // Retrieve and print the translated text String translatedText = translation.getTranslatedText(); System.out.println(translatedText); }}
How it works
Initializing the translation service: The TranslateOptions.getDefaultInstance().getService() method initializes the Google Cloud Translation API client. It uses credentials set via the GOOGLE_APPLICATION_CREDENTIALS environment variable. Ensure that this variable points to the JSON key file downloaded during the Set up credentials step.
Specifying translation options:
The TranslateOption.sourceLanguage("en") parameter specifies the source language (English in this case).
The TranslateOption.sourceLanguage("it") parameter specifies the target language (Italian).
The source language parameter is optional since the API can detect languages automatically. However, it is good practice to specify the source language explicitly to prevent misinterpretations.
Performing translation: The translate.translate() method processes the input text and returns a Translation object containing the translated text.
Retrieving the result: The getTranslatedText() method retrieves the translated string, which is then printed to the console.
Output
Running the program will print the Italian translation of the input text (textToTranslate) on the console. For example:
La localizzazione in Java è divertente
Supported languages
The Google Cloud Translation API supports a wide range of languages. Refer to the list of supported languages to find the language codes you can use in the TranslateOption.targetLanguage() parameter.
Conclusions on Java i18n
In this tutorial, we explored the fundamentals of Java internationalization (i18n) and localization for your app or website. We walked through the process of setting up Google Cloud Translation API, integrating it into a simple Maven project, and performing basic text translations using its Java client library.
We also delved into adding multi-language support to Java applications by leveraging Java's built-in Locale and ResourceBundle classes. These tools, combined with our practical examples, showcased how to handle translations and build internationalized applications effectively.
From implementing basic pluralization to exploring advanced options like ICU MessageFormat, you now have a strong foundation to create applications that cater to a global audience.
That’s a wrap for this guide! We hope this article helps you kick-start your Java localization journey. Until next time—happy coding, and ciao, fellow developer!
Ilya is a lead of content/documentation/onboarding at Lokalise, an IT tutor and author, web developer, and ex-Microsoft/Cisco specialist. His primary programming languages are Ruby, JavaScript, Python, and Elixir. He enjoys coding, teaching people and learning new things. In his free time he writes educational posts, participates in OpenSource projects, goes in for sports and plays music.
Ilya is a lead of content/documentation/onboarding at Lokalise, an IT tutor and author, web developer, and ex-Microsoft/Cisco specialist. His primary programming languages are Ruby, JavaScript, Python, and Elixir. He enjoys coding, teaching people and learning new things. In his free time he writes educational posts, participates in OpenSource projects, goes in for sports and plays music.
Building an AI-powered translation flow using Lokalise API and webhooks
Managing translations in a growing product can quickly become repetitive and error-prone, especially when dealing with frequent content updates or multiple languages. Lokalise helps automate this process, and with the right setup you can build a full AI-powered translation pipeline that runs with minimal manual input. In this guide, you’ll learn how to: Upload translation files to Lokalise automaticallyCreate AI-based translation tasksUse webhooks to downloa
Build a smooth translation pipeline with Lokalise and Vercel
Internationalization can sometimes feel like a massive headache. Juggling multiple JSON files, keeping translations in sync, and redeploying every time you tweak a string… What if you could offload most of that grunt work to a modern toolchain and let your CI/CD do the heavy lifting? In this guide, we’ll wire up a Next.js 15 project hosted on Vercel. It will load translation files on demand f
Hands‑on guide to GitHub Actions for Lokalise translation sync: A deep dive
In this tutorial, we’ll set up GitHub Actions to manage translation files using Lokalise: no manual uploads or downloads, no reinventing a bicycle. Instead of relying on the Lokalise GitHub app, we’ll use open-source GitHub Actions. These let you push and pull translation files directly via the API in an automated way. You’ll learn how to: Push translation files from your repo to LokalisePull translated content back and open pull requests automaticallyWork w