Thursday, 8 January 2015

Understanding Localization in Java

Background

Localization is a very important concept. Generally when you write a application or create a website you do it in a particular language Eg. English. But customers of your application/website may be from different country understanding different languages. In fact English itself has may variation. For Eg. you have US English, British English, Indian English etc To make you application/website dynamic to the customers locale understanding and implementing Localization is very important.


In this post I will show basics of localization , classes used in Java.

Locale class

Locale class is used in Java as basis of deciding which locale your program uses. To get all available locales that is supported in the JVM you are running you can run the following code - 

    public static void main(String args[])
    {
        for(Locale locale : Locale.getAvailableLocales())
        {
            System.out.println(locale);
        }
    }


I am not providing output for above as the list is big. Lets see a shorter version of this. Lets see all locales of languages english. Before that's lets understand locale.  you can imagine locale as language specs that people of certain region understand. You can identify locale as  (language + country +variant [if any]). Consider variants as same language different variation. In terms of databases this is the primary key. We will see this in more details in some time. But for now lets come to Locale class. So I was saying following code will show all locales of english language (but different country or variants if any).

    public static void main(String args[])
    {
        for(Locale locale : Locale.getAvailableLocales())
        {
            if(locale.getLanguage().equals("en"))
            {
                System.out.println(locale + " : " + locale.getDisplayName());
            }
        }
    }

and the output is :

en_MT : English (Malta)
en_GB : English (United Kingdom)
en_CA : English (Canada)
en_US : English (United States)
en_ZA : English (South Africa)
en : English
en_SG : English (Singapore)
en_IE : English (Ireland)
en_IN : English (India)
en_AU : English (Australia)
en_NZ : English (New Zealand)
en_PH : English (Philippines)


You can get the default locale using Locale.getDefault() method where as you can set default locale using Locale.setDefault(yourLocaleObject); 

You can create locale class in following ways -

  1. Locale locale = new Locale("en", "", ""); 
  2. Locale locale = Locale.forLanguageTag("en");
  3. Locale locale = new Locale.Builder().setLanguageTag("en").build(); 
  4. Locale locale = Locale.ENGLISH;

Programatically speaking locale can be of following format

language + "_" + country + "_" + (variant + "_#" | "#") + script + "-" + extensions

Resource Bundles

Resource Bundles are entities used to map keys to values in different locales. We can simply load or get a resource bundle and then call get on some key to get the locale specific value and show it to the user. We will see this in detail code in some time.

ResourceBundle is an abstract class. It has two derived classes - 

  1. PropertyResourceBundle
  2. ListResourceBundle

In  PropertyResourceBundle we essentially provide property files - one for each locale containing locale specific values. When this resource bundle is loaded and get is called keys from the property file corresponding to locale supplied is fetched and returned.

In ListResourceBundle property file is replaced by Classes. classes that extend ListResourceBundle class and override getContents() method, which returns an Object [][].

 
Code to understand PropertyResourceBundle 

Create project structure as follows -



Add following content in propertied file.

Contents of ResourceBundle_en.properties
HW = "Hello World in English!"

Contents of ResourceBundle_fr.properties
HW = "Hello World in French!"

Contents of ResourceBundle_it.properties
HW = "Hello World in Italian!"

Contents of ResourceBundle.properties
HW = "Hello World!"

and execute the following code - 

import java.util.Locale;
import java.util.ResourceBundle;


public class LocalizationTest {
    
    public static void main1(String args[])
    {

        
        System.out.println("Setting Default locale : " + Locale.getDefault());
        System.out.println("Using Resource Bundle with locale : " + Locale.getDefault());
        ResourceBundle rb = ResourceBundle.getBundle("Resourcebundle", Locale.getDefault());
        System.out.println("HW : " + rb.getString("HW"));
        System.out.println("-----------------------------");
        
        Locale.setDefault(Locale.ITALIAN);
        System.out.println(" Setting Default locale : " + Locale.getDefault());
        System.out.println("Using Resource Bundle with locale : " + Locale.getDefault());
        rb = ResourceBundle.getBundle("Resourcebundle", Locale.getDefault());
        System.out.println("HW : " + rb.getString("HW"));
        System.out.println("-----------------------------");
        
        Locale.setDefault(Locale.FRENCH);
        System.out.println("Setting Default locale : " + Locale.getDefault());
        System.out.println("Using Resource Bundle with locale : " + Locale.getDefault());
        rb = ResourceBundle.getBundle("Resourcebundle", Locale.getDefault());
        System.out.println("HW : " + rb.getString("HW"));
        System.out.println("-----------------------------");
        
        /**
         * If resource bundle property file for given locale (GERMAN in this case) is 
         * not found then resource bundle property file of default locale is considered
         *  i.e FRENCH in this case
         */
        System.out.println("Using Default locale : " + Locale.getDefault());
        System.out.println("Using Resource Bundle with locale : " +  Locale.GERMAN);
        rb = ResourceBundle.getBundle("Resourcebundle", Locale.GERMAN);
        System.out.println("HW : " + rb.getString("HW")); 
        System.out.println("-----------------------------");
        
        /**
         * If resource bundle property file for given locale (GERMAN) is 
         * not found and neither for the default locale (CHINESE)
         * then property file with same name as resource bundle
         * base name(no language or country extensions) 
         * is picked up (ResourceBundle.properties)
         */
        Locale.setDefault(Locale.CHINESE);
        System.out.println("Setting Default locale : " + Locale.getDefault());
        System.out.println("Using Resource Bundle with locale : " +  Locale.GERMAN);
        rb = ResourceBundle.getBundle("Resourcebundle", Locale.GERMAN);
        System.out.println("HW : " + rb.getString("HW"));
    }

}

 and the output is :

Setting Default locale : en_US
Using Resource Bundle with locale : en_US
HW : "Hello World in English!"
-----------------------------
 Setting Default locale : it
Using Resource Bundle with locale : it
HW : "Hello World in Italian!"
-----------------------------
Setting Default locale : fr
Using Resource Bundle with locale : fr
HW : "Hello World in French!"
-----------------------------
Using Default locale : fr
Using Resource Bundle with locale : de
HW : "Hello World in French!"
-----------------------------
Setting Default locale : zh
Using Resource Bundle with locale : de
HW : "Hello World!"

Note : If properties file are not in the path or key is not found then MissingResourceException is thrown.

Similarly for Code to understand ListResourceBundle

// Italian version
public class ResourceBundle_it_IT extends ListResourceBundle {
    public Object[][] getContents() {
        return contents;
    }

    static final Object[][] contents = { 
        { "HW", "Hello World in Italian!" },
        { "GB", "Good bye in Italian" } 
    };
}

Resource Bundle is loaded in same way as Property resource bundle.

Note 1: However in case of ListResourceBundle you call resourceBundle.getObject("ABC") and the call returns an Object unlike the case of PropertyResourceBundle which returns String.

Note2 : Make sure Resource Bundle classes that extend ListResourceBundle  are public as java uses reflection to load these classes.

There are two main advantages of using a Java class instead of a property file for a resource bundle:
  • You can use a value type that is not a String.
  • You can create the values of the properties at runtime. 

You can print all values in your property resource using following Java 8 snippet -


Locale us = new Locale("en", "US");
ResourceBundle rb = ResourceBundle.getBundle("ResourceBundle", locale);
System.out.println(rb.getString("hello"));
Set<String> keys = rb.keySet();
keys.stream().map(k -> k + " " + rb.getString(k)).forEach(System.out::println);


or you can even use a Property (by converting ResourceBundle to property) advantage of which being you can give default value -


Properties props = new Properties();
rb.keySet().stream().forEach(k -> props.put(k, rb.getString(k)));
System.out.println(props.getProperty("propertyNotPresent")); //null
System.out.println(props.getProperty("propertyNotPresent", "Hello World!")); //123 



Resolution of Resource Bundles

A fully qualified resource bundle should be of following format - 

bundlename + "_" + language + "_" + country + "_" + (variant + "_#" | "#") + script + "-" + extensions

When you load a bundle with a locale a sequence is followed to resolve the bundle used. That is as follows - 

  1. The resolution starts by searching for an exact match for the resource bundle with the full name.
  2. If there is a tie between properties file and java file, java resource is given preference.
  3. The last part (separated by _) is dropped and the search is repeated with the resulting shorter name. This process is repeated till the last locale modifier is left.
  4. The search is restarted using the full name of the bundle for the default locale.
  5. Search for the resource bundle with just the name of the bundle.
  6. If all the above fails , then MissingBundleException is thrown.

Following diagram shows steps Java goes through when asked for resource bundle Zoo with the locale new Locale("fr", "FR") when the default locale is US English


Lets take a quick question then -

QQ > How many files Java would need to lookup for resource bundle with the followind code
    Locale.setDefault(new Locale("at"));
    ResourceBundle rb = ResourceBundle.getBundle("BundleResource", new Locale("en"));

The answer is six.

1. BundleResource_at.java
2. BundleResource_at.properties
3. BundleResource_en.java
4. BundleResource_en.properties
5. BundleResource.java
6. BundleResource.properties

NOTE* : Java isn’t required to get all of the keys from the same resource bundle. It can get them from any parent of the matching resource bundle. A parent resource bundle in the hierarchy just removes components of the name until it gets to the top.

For eg. for matching bundle

BundleResource_fr_FR.java

Java can key value from
  1. BundleResource_fr_FR.java
  2. BundleResource_fr.java
  3. BundleResource.java
and for BundleResource_fr.properties

  1. BundleResource_fr.properties
  2. BundleResource.properties


Note : The getBundle() method takes a ResourceBundle.Control object as an additional parameter.
You can extend ResourceBundle.Control class, override getCandidateLocales() method and pass it's instance  to the getBundle() method. By this you can change the default resource bundle search process. You can even read from other file formats (Eg. XML files).

Note :  All of the above classes are for String localization. This does not consider numbers or currency representation in different languages. You need to use Format abstract class for that. It's implementation include -
  1. NumberFormat
    1. getInstance()
    2. getIntegerInstance()
    3. getCurrencyInstance()
    4. getPercentageInstance()
  2. DateFormat (SimpleDateFormat)
    1. getDateInstance()
    2. getTimeInstance()
    3. getDateTimeInstance()
After you get an instance of above concrete classes you can call format() method to get locale specific string representation of data. If you want data back you can use corresponding parse() method.


NOTE :The format classes are not thread-safe. Do not store them in instance variables
or static variables. 

Note : Localization(l10n) and Internationalization(i18n) are two different concepts.
Internationalization is the process of designing and building an application to facilitate localization. Localization, in turn, is the cultural and linguistic adaptation of an internationalized application to two or more culturally-distinct markets.

For more on this see SO question in related Links section.

Related Links

No comments:

Post a comment

t> UA-39527780-1 back to top