Software localization
Internationalization with Java Locale
We emphasize quite often the importance and value of respecting the user's language and geographical region when developing software applications. Allowing the user to communicate with the software in their own language could be a serious boost to the software's sales. When it comes to Java, it is the concept of Java Locale that spells out the internationalization process.
Java Locale consists of 3 main elements: language, country, and variants. Language and country are quite self-explanatory, but variant code is slightly different. Sometimes, software vendors, be it operating systems or browsers, can use the code for additional functionalities. In Java Locale, you can provide these additional details using variant codes. An example of a locale with three components would be de_DE_WIN, a locale for Windows for German speakers in Germany.
What is a Locale object?
The concept behind Java Locale is implemented by the java.util.Locale
class. To define the locale for the application(language, country, and variant), you would use the Locale
object, which is only an identifier. Real localization is done by locale-sensitive classes. Objects that you create from locale-sensitive classes customize themselves as to how to format and present data to the user. These classes use the Locale
object to understand which locale is being used in the application; a good example would be the NumberFormat
class. Thus, NumberFormat
may return a number as 302 400 for France; 302 .400 for Germany; 302, 400 for the United States.
Some of the Local-sensitive classes defined in the Java Standard API are:
NumberFormat
DateFormat
DecimalFormat
Creating Java Locale objects
Let’s dive into the code now. There are four ways to create Locale
objects.
Locale
ConstructorsLocale.Builder
ClassLocale.forLanguageTag
Factory MethodLocale
Constants
Using Locale constructors
There are three constructors available for the creation of Locale
objects.
Locale(String language)
, you can use only the language to create the locale object:Locale locale = new Locale("en");
Locale(String language, String country)
, you can use both language and country to create the locale object:Locale locale = new Locale("en", "US");
Locale(String language, String country, String variant)
, you can use all three components—language, country, and variant—to create the locale object:Locale locale = new Locale("en", "US", "SiliconValley");
Here's a complete example of using Locale
constructors:
import java.util.Locale; import java.text.NumberFormat; import java.text.DateFormat; import java.util.Date; class Main { public static void main(String[] args) { Locale deLocale = new Locale("de","De"); Locale usLocale = new Locale("en","US"); long number = 123456789L; NumberFormat denf = NumberFormat.getInstance(deLocale); NumberFormat usnf = NumberFormat.getInstance(usLocale); System.out.println( denf.format(number) ); System.out.println( usnf.format(number) ); Date now = new Date(); DateFormat usdf = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, usLocale); DateFormat dedf = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, deLocale); System.out.println( usdf.format(now) ); System.out.println( dedf.format(now) ); } }
In the output, you can see how German and US English display large numbers and dates:
- 123.456.789
- 123,456,789
- March 28, 2019 12:30:07 PM UTC
- 28. März 2019 12:30:07 UTC
Using the Locale.Builder class
You can also use the Locale.Builder
class to createLocale
objects. This class has only one constructor and doesn’t take any arguments. You have to use a chain of setter methods to specify the language, country, and variant.
Locale locale = new Locale.Builder().setLanguage("de").setRegion("CA").build();
Using the Locale.forLanguageTag factory method
IETF BCP 47 is a standard that defines Language
tags to identify locales, and the Java SE 7 package conforms to it. You can use the IETF BCP 47 standard language tags to create Locale
objects with the Locale.forLanguageTag
Factory Method. For example:
Locale aLocale = Locale.forLanguageTag("en-US"); Locale bLocale = Locale.forLanguageTag("de-Germany");
We will go through language tags later on; the code below shows you how to use the Locale.forLanguageTag
Factory Method to create locale objects.
import java.util.Locale; import java.text.NumberFormat; class LocaleConstantExample { public static void main(String[] args) { Locale usLocale = Locale.forLanguageTag("en-US"); Locale deLocale = Locale.forLanguageTag("de-Germany"); long number = 123456789L; NumberFormat denf = NumberFormat.getInstance(deLocale); NumberFormat usnf = NumberFormat.getInstance(usLocale); System.out.println( denf.format(number) ); System.out.println( usnf.format(number) ); } }
The output would be:
- 123.456.789
- 123,456,789
Locale constants
This is probably the easiest way to create the Locale
object. To make things more convenient, Java has pre-defined constants for some languages and countries. For instance: Japan is the constant for Japan, the country, while Japanese is the constant for the Japanese language. You simply need to use a constant to create a locale of that kind. The following code is an example of how to use Locale
constants.
import java.util.Locale; import java.text.NumberFormat; class LocaleConstantExample { public static void main(String[] args) { Locale deLocale = Locale.GERMANY; Locale usLocale = Locale.US; long number = 123456789L; NumberFormat denf = NumberFormat.getInstance(deLocale); NumberFormat usnf = NumberFormat.getInstance(usLocale); System.out.println( denf.format(number) ); System.out.println( usnf.format(number) ); } }
This code basically does the same thing as the previous example, the only difference is how it creates the Locale
object. The output is:
- 123.456.789
- 123,456,789
Java Locale codes
You might have noticed that we are using codes when creating objects with Locale
constructors and the Locale.Builder
method. There is a list of available Language codes and Country codes for Java. A full list of available codes will make this article unnecessarily long. Here is an overview of the more common ones.
Language codes:
- English—en
- German—de
- French—fr
- Russian—ru
- Japanese—ja
- Chinese—zh
- Arabic—ar
The same language could be spoken in several countries. How the language is used could be different from country to country. For instance, English used in the USA is different from the English used in the UK. That’s why it’s important to specify the country in your Locale
object. The country is specified with unique country codes.
Country codes:
- United States—US
- Germany—DE
- France—FR
- United Kingdom—UK
- Canada—CA
Java Locale language tags
Language tags are strings with a special format used to give information on a particular locale. It can be as simple as “en” for English or as complex as "zh-cmn-Hans-CN" for Chinese, Mandarin, Simplified script, used in China.
Language tags are used by Locale.forLanguageTag(LanguageTag)
to create Locale
objects.
import java.util.Locale; public class LanguageTagExample { public static void main(String[] args) { Locale locale = Locale.forLanguageTag("en-US"); } }
This code does nothing but creating a Locale
object using the “en-US” language tag.
Language ranges
Language range is a set of Language tags that share certain attributes. For example, “es-*” can be used to recognize Spanish in any region.
import java.util.Locale; class Main { public static void main(String[] args) { Locale locale = Locale.forLanguageTag("en-GB"); Locale.LanguageRange r1= new Locale.LanguageRange("es-*",1); Locale.LanguageRange r2= new Locale.LanguageRange("de-DE",0.5); Locale.LanguageRange r3= new Locale.LanguageRange("en-GB*",0); } }
TheLocale.LanguageRange
constructor used in the above example takes two arguments. The first argument is the language range, and the second argument is weight. Usually, this weight is used to express the user’s preference. In this example, we've created three ranges from the highest user preference to the lowest user preference.
Creating priority lists
You can create a priority list using a set of language ranges.
import java.util.Locale; class Main { public static void main(String[] args) { Locale locale = Locale.forLanguageTag("en-GB"); String rangeString= "en-US;q=1.0,en-GB;q=0.5,de-DE;q=0.0"; List<Locale.LanguageRange> rangeList= Locale.LanguageRange.parse(rangeString); } }
rangeString is a valid string with set of locales and their weights. Then we parse it using the parse()
method in Locale.LanguageRange
class to create a Language priority list.
Using priority lists to filter tags
In the previous example, we created a Language priority list. In the Language tag filtering process, we match a set of language tags against a Priority list.
import java.util.Locale; import java.util.Collection; import java.util.List; import java.util.ArrayList; public class LanguageTagFilteringExample { public static void main(String[] args) { String ranges = "en-US;q=1.0,en-GB;q=0.5,de-DE;q=0.0"; List<Locale.LanguageRange> languageRanges = Locale.LanguageRange.parse(ranges); Collection<Locale> localesList = new ArrayList<>(); localesList.add(Locale.forLanguageTag("en-GB")); localesList.add(Locale.forLanguageTag("en-US")); localesList.add(Locale.forLanguageTag("ja-*")); localesList.add(Locale.forLanguageTag("fe-FE")); List<Locale> filteredSet = Locale.filter(languageRanges,localesList); for(Locale locale : filteredSet){ System.out.println(locale.toString()); } } }
The output of this program is:
- en_US
- en_GB
The scope of Java Locale
One of the most important aspects of using locale in Java is its scope. You don’t have to use the same locale in one single program. Actually, you can give a different locale to each locale-sensitive object used in your application.
An interesting case would be the development of distributed applications. Suppose your application receives requests from different countries. How do you decide on the locale of the application? What you can do here is using separate threads to serve requests and assign a locale for each thread.
Retrieving Locale information
The Locale
object offers many methods for obtaining information about the locale being used. Let’s get to know some of them.
The getLanguage() and getISO3Language() methods
The getLanguage()
and getISO3Language()
methods can be used to retrieve the language of the locale. Here, getLanguage()
returns an ISO2 letter while getISO3Language()
an ISO3 letter.
import java.util.Locale; class Main { public static void main(String[] args) { Locale locale = Locale.GERMAN; System.out.println("Locale:" + locale); System.out.println("ISO 2: " + locale.getLanguage()); System.out.println("ISO 3: " + locale.getISO3Language()); } }
The output is as follows:
- Locale: de
- ISO 2: de
- ISO 3: deu
First, we created a locale object using Locale
constants. Then, we called the getLanguage()
method on that object. It returns an ISO2 code for the German language which is de. Next, we called locale.getISO3Language()
. It returned an ISO3 code for the German language, which is deu.
The getCountry() and getISO3Country() methods
The getCountry()
and getISO3Country()
methods can be used to retrieve the language of the locale. Here, getCountry()
returns an ISO2 letter while getISO3Country()
returns an ISO3 letter.
import java.util.Locale; class Main { public static void main(String[] args) { Locale locale = Locale.GERMANY; System.out.println("Locale: " + locale); System.out.println("ISO 2 letter: " + locale.getCountry()); System.out.println("ISO 3 letter: " + locale.getISO3Country()); } }
The output of this code will be quite similar to the previous example:
- Locale: de_DE
- ISO 2 letter: DE
- ISO 3 letter: DEU
If you check the output in the above examples, those short-form tags are not suitable for users. The Java Locale object provides methods which return that above details in more readable fashion.
The getDisplayLanguage method
The getDisplayLanguage
method is used to get the language in a more common fashion. There are 2 overriding getDisplayLanguage
methods. If you use an empty-argument method, it will return the value in the default locale. If you pass the target locale as an argument, this method will return a value from the target locale.
import java.util.Locale; class Main { public static void main(String[] args) { Locale locale = Locale.GERMANY; String defaultLanguage = locale.getDisplayLanguage(); String targetLanguage = locale.getDisplayLanguage(locale); System.out.println(defaultLanguage); System.out.println(targetLanguage); } }
The output is as follows:
- German
- Deutsch
In the first case, you haven’t passed any argument to the getDisplayLanguage
method. Therefore, the locale value (German) is displayed in the default locale, which is en_US. Next, I attributed the target locale to the getDisplayLanguage
method. Therefore Deutsch is displayed.
The getDisplayCountry method
The getDisplayCountry
method will return the locale called in a readable form. The getDisplayCountry
Method works similarly to the getDisplayLanguage
Method.
import java.util.Locale; class Main { public static void main(String[] args) { Locale locale = Locale.GERMANY; String defaultCountry = locale.getDisplayCountry(); String targetCountry = locale.getDisplayCountry(locale); System.out.println(defaultCountry); System.out.println(targetCountry); } }
The output will be quite similar to the previous example, except that this time the country will be displayed.
The getDisplayName method
The getDisplayName
method can be used to get the full locale name on which it is called. It works similar to the previous two methods:
import java.util.Locale; class Main { public static void main(String[] args) { Locale locale = Locale.GERMANY; String defaultCountry = locale.getDisplayName(); String targetCountry = locale.getDisplayName(locale); System.out.println(defaultCountry); System.out.println(targetCountry); } }
The output would look like this:
- German (Germany)
- Deutsch (Deutschland)
Conclusion
If you're ready for the next step, we suggest having a look at The Java i18n Guide You’ve Been Waiting For. Should you feel comfortable with Java internationalization, you may want to do a deep dive on internationalizing JSP web applications or internationalizing a Spring Boot app.
Once your app is ready for localization, a localization solution, such as Phrase, can help your team manage your localization projects easily and in the most efficient way possible.
The Phrase Localization Platform features a flexible API and CLI, and a beautiful web platform for your translators. With GitHub, GitLab, and Bitbucket sync, Phrase does the heavy lifting in your localization pipeline, so you can stay focused on the code you love.
Check out all Phrase features for developers and see for yourself how it can help you take your apps global.