Java Internationalization

Java Internationalization: Overview

Internationalizing a Java application typically means making the application able to handle multipule languages,  number formats, data formats etc.

This text gives you an overview of these aspects.

Basically, your application should be able to handle application input, output and operations. In other words, your application should be able to adapt itself to input, output and operations specific to different locations and user preferences.

                                                                                  

 

                                                            Your application should be able to handle international input and output.

Input and output is a coarse grained categorization of what your application need to handle. Here are the issues your application need to cover, in a bit more detail:

Handling International Input

Input is everything that your application receives from its users, either directly through the user interface, send to it via service calls(e.g SOAP or REST calls), 

imported from files etc. Input includes:

  • Form input including
    • Text (language + character encoding)
    • Number formats
    • Date and time formats and calculations
  • Files received
  • Service calls received

Handling International Output

Output is everything your application is showing or sending to its users, either during the installation process, or during general use. Output includes:

  • Text (language + character encoding)
  • Number and currency formatting
  • Date and time formatting and calculations
  • Character comparisons
  • String comparisons

How many of these aspects your application needs to be able to handle depends on your application.

Keep in mind, that if users can have the application display text etc. to them in their own language, they may also expect that support, emails sent to them, forums, help texts etc. to be in their own language.

Adapting Operations

In addition to adapting to input and output, your application may need to adapt certain internal operations to the users location and preferences. For instance, a web shop may need to add different amounts of VAT to a sale, depending on what country the buyer lives in. An investment calculator may have to take different laws and tax rates into consideration etc.

User Information Needed

In order to properly localize your application to a user you may need one or more of the following:

  • Preferred language
  • Country of residence
  • Current location
  • Preferred time zone

The user should be able to tell what language he or she prefers the application to use. Don't just assume that a user living in a given country want the application localized to that language. For instance, I am from Denmark but I prefer that applications are in english, so I can talk to people all over the world about its features, without having to translate feature names etc. Let the user decide.

The country of residence of a user may impact what features are available to the user, and how these features work. For instance, YouTube doesn't allow anyone to become a YouTube partner. Only users living in certain countries are allowed into that program.

The current location may be interesting to know for some applications, especially for all location based applications. The application may take action based on the users location.

Some applications, like a shared calendar, may need to know the users time zone in order to work properly. An appointment at 10 AM - what time is that in the users country? What time is that in the time zone that e.g. an application server is running in? Is the user temporarily out of the country and need to show all appointments in the time zone of the temporary location?

Application Internationalization Layer

In order to handle internationalization of input and output, your application will have a kind of internationalization layer. Here is a diagram illustrating that:

                                                                                       

 

 

                                                                                     An application internationalization layer.

The internationalization layer takes care of converting between the users language and formatting, and the language and formatting used internally in the application. For instance, String's are kept internally in the memory of the JVM as UTF-16. The internationalization layer should convert from whatever character encoding the input comes in (ascii, UTF-8 etc.) and to UTF-16.

The internationalization layer may not look like an abstraction layer sitting between the inner and outer components in your application. Rather, it will most likely be a set of API calls to an internationalization component capable of making the conversions and translations. The layer in your application receiving the input data will call this internationalization API, and pass the converted values on to the core operations.

In order to properly convert the input data the internationalization layer must collect the necessary information about the user. That is, the language, character encoding, number formatting, date formatting, time zone conversions etc. of the input data.

When the application needs to output data, the process is similar. The application decides on what to output, and invoke the internationalization layer to have the output converted to formats suitable for the user. That again includes both language, character encoding, number formatting, date formatting, time zone conversions etc.

In reality, your application may look more like this:

                                                              

                                                                        Java internationalization components in an application.

What is going on is something like this:

  1. The input (event or request) reaches an input handler (event handler / request handler) in the application.
  2. The input handler determines the user settings necessary for localization.
  3. The input handler calls the I18n component to obtain localized texts, numbers, dates etc.
  4. The input handler calls the application operations. If user preferences are needed to perform the operations, they are passed to the application operations.

Your application may do internationalization a bit different from this. No worries. There are no right and wrong designs here.

Why an Internationalization Component?

The reason you have an internationalization layer or component is to separate the internationalization code from the rest of the application. You may choose to use Java's built-in internationalization features, or you may use another API like ICU4J. By encapsulating your internationalization code in an I18n component, you can change the API used inside it, without affecting the rest of your application.

Java's Internationalization Classes

Java has a set of built-in classes that can help you handle internationalization of your application. These classes are:

 

 

ClassDescription
Locale The Locale class represents a language and a country or region. A Locale may also represent a certain type of formatting - e.g. date or number formatting.
ResourceBundle The ResourceBundle class can contain localized texts or components (objects). You obtain a ResourceBundle for a specific Locale, and thus obtain texts or objects localized to that Locale.
NumberFormat The NumberFormat class is used to format numbers according to a certain Locale.
DecimalFormat The DecimalFormat class is used to format numbers according to customized formatting patterns. These patterns are also Locale sensitive.
DateFormat The DateFormat class is used to format dates according to a specific Locale.
SimpleDateFormat The SimpleDateFormat class is used to parse and format dates according to custom formatting patterns. These patterns are also Locale sensitive.

Each of these classes are covered in more detail in later texts in this trail.

Locale

The Java local class, java.util.Local, is used to represent a "geographical, political or cultural" region to localize a given text, number, date or operation to.

A Local Object may thus contain a country, region, language, and also a variant of a language, for instance,  a dialect spoken in a certain region of a country,

or spoken in a different country than the country from which the language originates.

The Local Instance is handed to components that need to localize their actions, whether it is converting input, output,or just need it for internal operations.

The Local class cannot do any internationalization or localization by itself.

Locale Standards

The Locale class complies with the BCP 47 (IETF BCP 47, "Tags for Identifying Languages") standard.

The Locale class also has support for the LDML (UTS#35 "Unicode Locale Data Markup Language") standard, which is a BCP 47-compatible extension for locale data exchange. See the respective standards for more informati

Locale Contents

Locale instance contains the following subparts:

  • Language
  • Script
  • Country (region)
  • Variant
  • Extensions

Language

The language must be an ISO 639 2 or 3 character language code, or a registered language subtag of up to 8 characters. In case a language has both a 2 and 3 character language code, use the 2 character code. A full list of language codes can be found in the IANA Language Subtag Registry.

Language codes are case insensitive, but the Locale class always use lowercase versions of the language codes.

Script

The script code represents the written form of the language. Some languages can be written using different scripts (e.g. different alphabets).

The script code is a 4 character code following the ISO 15924 standard. A full list of script codes can be found in the IANA Language Subtag Registry.

Script codes are case insensitive, but the Locale class always uses a version with the first letter in uppercase, and the rest in lowercase.

Country (Region)

The country code is a 2 character code following the ISO 3166 standard, or a UN M.49 numeric area code. A full list of country and region codes can be found in the IANA Language Subtag Registry.

The country code is case insensitive, but the Locale class uses an uppercase version of the country code.

Variant

The variant part describes a variant (dialect) of a language follwing the BCP 47 standard. See the JavaDoc for the Locale class for more detail about variant.

Extensions

The extension part signals extensions to the Locale in addition to the language and region. For instance, what calendar to use when displaying dates (Gregorian, Arab, Japanese etc.). See the JavaDoc for the Locale class for more detail about extension

Creating a Locale

  • Locale constants
  • Locale constructors
  • Locale.Builder class (from Java 7)
  • Locale.forLanguageTag factory method (from Java 7)

Each of these methods are covered in the sections below.

Locale Constants

The java.util.Locale class contains a set of constants that represent the most commonly used languages in the world. These are:

Locale.CANADA
Locale.CANADA_FRENCH
Locale.CHINA
Locale.CHINESE
Locale.ENGLISH
Locale.FRANCE
Locale.FRENCH
Locale.GERMAN
Locale.GERMANY
Locale.ITALIAN
Locale.ITALY
Locale.JAPAN
Locale.JAPANESE
Locale.KOREA
Locale.KOREAN
Locale.PRC
Locale.ROOT
Locale.SIMPLIFIED_CHINESE
Locale.TAIWAN
Locale.TRADITIONAL_CHINESE
Locale.UK
Locale.US

You use one of these constants simply by referring to it, like this:

Locale locale = Locale.JAPANESE;

Locale Constructors

You can also create a java.util.Locale instance by using one of its constructors. The constructors are:

Locale(String language)

Locale(String language, String country)

Locale(String language, String country, String variant)

The language parameter should be a 2 or 3 letter ISO language code from the ISO 639 standard. You can also use a language subtag of up to 8 characters.

The country should be a 2 character ISO country cod from the ISO 3166 standard. Alternatively a UN M.49 character area code can be used.

The variant should be any valid BCP 47 variant of a language.

Here are a few examples:

Locale locale = new Locale("en");       // English language

Locale locale = new Locale("en", "UK"); // English language, United Kingdom
Locale locale = new Locale("en", "CA"); // English language, Canada

Locale Builder

From Java 7 you can use the Locale.Builder class to build a Locale instance. Here is an example:

Locale cLocale = new Locale.Builder().setLanguage("en")
                                     .setRegion("US").build();

Locale.forLanguageTag()

The factory method Locale.forLanguageTag() can also be used to create a Locale instance. Here is an example:

Locale aLocale = Locale.forLanguageTag("en-US");

Using the Locale Instance

Once you have a Locale instance you can use it as input to other components that use a Locale to localize their functions. Here are a few examples:

Locale locale = new Locale("da", "DK");

ResourceBundle resourceBundle =
        ResourceBundle.getBundle("bundleName", locale);

This example creates a Locale representing the danish language in Denmark, and uses this Locale to obtain a ResourceBundle containing texts in danish.

The ResourceBundle class is covered in more detail in the text about the ResourceBundle class

Locale Criticism

The idea of a Locale is to represent a certain language and a set of formatting of numbers and dates related to that language and possibly a country or region. However, the language, number and date formatting a user wants, may not be related to what country or region they live in.

For instance, If I am Danish but live in Spain and use a spanish netbank, that netbank may not be able to show Danish language. So, I may choose the second best language, which for me is English. Even if the netbank had Danish language, I may still, however, prefer English formatting of numbers, because being a programmer, that is the number formatting I am used to. Additionally, being a Spanish bank account, the saldo should be shown in euro. Not in GBP or DKK (Danish Kroner).

If I travel a lot I may prefer to display date and time of an application in the local time, or home time, depending on what I am trying to do. For instance, if I need to catch a train locally, I may want local time. But if I am phoning home to my family, I may want to be able to see what time it is where they are.

Finally, I may want to show how a given application works to a person who doesn't speak the language that my application is localized to (e.g. English or Danish), so I may want to temporarily change language to e.g. Spanish or German.

All in all, it doesn't make sense to try to tie too many localization settings to the language, country or region of a user. Let the user choose what language, time zone, what number formatting and date formatting to use etc. Make it changeable too, and independently of each other. Do not tie it to the home country of the user.

Java ResourceBundle

The Java ResourceBundle class, java.util.ResouceBundle, is used to store texts and components that are locale sensitvie.

For instance, the text labels used inside your application might need to change depending on the language of the user currently using your application.

The text labels are thus said to be user locale sensitive. A user's locale is representde by the Java Locale class, by the way.

This text takes a closer look at the ResourceBundle class and its subclasses.

The ResourceBundle Class Hierarchy

The ResourceBundle class has two subclasses called PropertyResourceBundle and ListResourceBundle.

Here is a diagram illustrating the class Hierarchy:

 

 ResourceBundle has the subclasses PropertiesResourceBundle and ListResourceBundl

The PropertyResouceBundle class stores localized texts in standard Java Property files.

The format of these files is explained in my Java Propeties tutorial.(Link: https://www.cnblogs.com/qq3511107946/articles/16629623.html)

You do not directly interact with these two subclasses. All interaction goes through the ResourceBundle.

Creating a ResourceBundle

You create a ResourceBundle instance like this:

Locale locale = new Locale("en", "US");

ResourceBundle labels = ResourceBundle.getBundle("i18n.MyBundle", locale);

System.out.println(labels.getString("label1"));

First you need a Local instance. Then you pass that Local instance to the ResouceBundle.getBundle() method along with the name fo the Resource Bundle to load.

Finally you can access the localized values in the ResouceBundle via its different getString() and getObject() etc. 

You are never actually creating a ResourceBundle instance, but an instance of one of its two subclasses.

Both are created using the above factory method. First the ResourceBundle class will look for a ListResourceBundle, and then for a PropertyResourceBundle. It does so by matching the name of the requested resource bundle (first parameter in the getBundle() method) against the class names of a ListResourceBundle first, and if none found, against a property file resource bundle.

Both ListResourceBundle and PropertyResourceBundle are covered in more detail in the following sections.

Property Files as ResourceBundle

You can use standard property files for storing localized texts. You can load these properties via the ResouceBundle class. Here is an example:

Locale locale = new Locale("en", "US");

ResourceBundle labels = ResourceBundle.getBundle("i18n.MyBundle", locale);

System.out.println(labels.getString("label1"));

For this example to work you should put a standard Java property file named MyBundle.properties in a Java package named i18n.

Make sure this property file is available on your class path when you run the above code, meaning the property file should be located among the classes of

your application, and in  the i18n package . 

The name of a resource bundle like a class name. Thus, i18n.MyBundle means a property file named MyBunlde.properties in the package(directory) i18n.

Here is an example of what the content of the property file could look like:

label1 = Label 1 is done!
label2 = Label 2 is through!

AS is the standard with Java property files, it is a list of key and value pairs. The key is on the left side of the =, the value is on the right side of the =.

The value is what you should localized, not the key.

Different Languages in Different Property Files

In order to provide strings in different languages, create a property file for each language, and suffix them with underscore(_) and then language code.

For example:

MyBundle.properties
MyBundle_da.properties
MyBundle_de.properties
MyBundle_fr.properties

All of these files should be located in the same package(directory)

The file without language suffix(e.g. MyBundle.properties) is the default property file. In case no property file is available for the language(local) passed to the 

ResourceBundle.getBundle() method, and the system has no default Locale set(e.g. German computer will have a German Locale as default), this file is read

and returned as a ResourceBundle.

The other property files with the language code suffixes contain the same keys but with values in different languages. Thus, the danish property file could look like:

label1 = Label 1 er klar!
label2 = Label 2 er igennem! 

Java NumberFormat

The java.text.NumberFormat class is used to format numbers according to a specific Locale. Different countries have different standards for how they format numbers.

In Denmark fractions of a number are separated from the integer parts using a comma. In England they use a dot.

Creating a NumberFormat

Creating a NumberFormat for a specific Locale is done like this:

Locale locale = new Locale("da", "DK");

NumberFormat numberFormat = NumberFormat.getInstance(locale);

Formatting Numbers

Formatting a number using a NumberFormatter is done using the format() method. Here is an example:

String number = numberFormat.format(100.99);

System.out.println(number);

Using a Danish Locale, the output printed from this code would be:

100,99

Notice that numbers like 100.00 might be formatted without the decimals, as 100.

Formatting Currencies

To format a number as currency you need a currency NumberFormat instance. You create a currency NumberFormat using the getCurrencyInstance() like this:

NumberFormat currencyFormat = NumberFormat.getCurrencyInstance(locale);

Formatting a number as a currency is still done using the format() method. Here is an example:

String currency = currencyFormat.format(100.999);

System.out.println(currency)

The output printed from this code would be:

kr 101,00

Notice how the localized Danish currency code is written in front of the number. Other countries might have the currency code after the number instead. Notice also that the amount is rounded.

It is also possible to obtain the Currency object representing the currency used to format the number. You do so using the getCurrency() method. Here is an example:

Currency currency = currencyFormat.getCurrency();

From the Currency instance you can get the name, symbol etc. of the currency, using its getDisplayName() and getCurrencyCode() etc. methods.

You can also set the Currency object to use for formatting a number as a currency, using the setCurrency() method on the NumberFormat class. Here is an example:

NumberFormat numberFormat =
    NumberFormat.getCurrencyInstance(new Locale("da", "DK"));
numberFormat.setCurrency(Currency.getInstance("EUR"));

String currencyString = numberFormat.format(120.99);
System.out.println(currencyString);

This code create a currency formatter using a Danish locale, then exchanges the currency to use for formatting with EURO. The output printed from this code would be:

€ 120,99

Formatting Percentages

To format a number as percentage you need a percentage NumberFormat instance. You create that using the getPercentageInstance() like this:

NumberFormat percentageFormat = NumberFormat.getPercentInstance(locale);

Formatting a number as a percentage is also done using the format() method. Here is an example:

String percentage = percentageFormat.format(99.999);

System.out.println(percentage);

The output printed from this code would be:

10.000%

Notice the % character after the number. Notice also that the number is rounded.

Minimum and Maximum Number of Digits

You can set both the minimum and maximum number of digits to use both for the integer part and the fractional part of the number. You do so using these methods:

numberFormat.setMinimumIntegerDigits(int digits)
numberFormat.setMaximumIntegerDigits(int digits)

numberFormat.setMinimumFractionDigits(int digits)
numberFormat.setMaximumFractionDigits(int digits)

You can also obtain these values again, using these methods:

int digits = numberFormat.getMinimumIntegerDigits();
int digits = numberFormat.getMaximumIntegerDigits();

int digits = numberFormat.getMinimumFractionDigits();
int digits = numberFormat.getMaximumFractionDigits();

Rounding Mode

As you may have noticed earlier, the formatted numbers are rounded. You can set the rounding mode using the setRoundingMode() method of the NumberFormat class. Here is an example:

NumberFormat numberFormat =
    NumberFormat.getInstance(new Locale("da", "DK"));

numberFormat.setRoundingMode(RoundingMode.HALF_DOWN);
numberFormat.setMinimumFractionDigits(0);
numberFormat.setMaximumFractionDigits(0);

String number = numberFormat.format(99.50);
System.out.println(number);

java.math.RoundingMode is used to set round model.

The output from this code would be 99. The fraction part would be rounded down because of the rounding mode, and to 0 fraction digits.

Java DecimalFormat

The java.text.DecimalFormat  class is used to format numbers using a formatting pattern you specify yourself. This text explains how to use the DecimalFormat class to format different types of numbers.

Creating a DecimalFormat

Creating a DecimalFormat instance is done like this:

String pattern = "###,###.###";
DecimalFormat decimalFormat = new DecimalFormat(pattern);

The pattern parameter passed to the DecimalFormat constructor is the number pattern that numbers should be formatted according to.

和国际化相关的还有:

Java DateFormat

Java SimpleDateFormat

文章转载自:https://jenkov.com/tutorials/java-internationalization/dateformat.html

 

String currency = currencyFormat.format(100.999);

System.out.println(currency)
posted @ 2022-08-27 21:05  fjxszx_hss  阅读(39)  评论(0)    收藏  举报