Software localization

Internationalization with Java Locale

Java Locale can help in identifying objects, and it consists of languages, scripts, countries, and more. Here's how to use it for internationalization.
Software localization blog category featured image | Phrase

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 Constructors
  • Locale.Builder Class
  • Locale.forLanguageTag Factory Method
  • Locale 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
  • FranceFR
  • 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 Suite 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.