The Intl
library can help you design and develop globalized server and web client applications. It supports all major locales and sublanguage pairs. As all information on the screen represents a set of strings, we must translate the other types of objects into the string format with special formatters provided by the Intl
library. Translated versions of displayed strings are bundled in separate text files. The Intl
library contains a class of the same name that is used to work with messages. The Intl
library considers numbers, dates, and messages in different internalization areas. Each area is intentionally initialized separately to reduce the application size and avoid loading unnecessary resources. The internalization of numbers and dates is implemented via formatters. Messages are internalized through special functions and can be externalized into files in the Application Resource Bundle (ARB) format.
By default, the current locale is set to the English language of USA (en_US
). A new value assigned to defaultLocale
can affect all the methods of the Intl
library using the following code:
Intl.defaultLocale = "fr_FR";
There are several ways to use a different locale on a temporary basis than using the current one. They are as follows:
Intl
classwithLocale
method of the Intl
class:static withLocale(String locale, Function message_function)
The main purpose of the withLocale
method is to delay calling the message_function
function until the proper locale has been set. The message_function
function can be a simple message function, a wrapper around the message function, or a complex wrapper that manipulates multiple message functions. As the locale string is not known at the static analysis time, this method silently performs the following steps:
message_function
function and saves the result.message_function
function.The NumberFormat
class provides the ability to format a number in a locale-specific manner. To create NumberFormat
, we must specify the pattern in the ICU format, as shown in the following code:
var f = new NumberFormat("###.0#"); print(f.format(12.345)); // Result: 12.35
The second optional parameter of the NumberFormat
factory constructor is the locale. If the locale parameter is not specified, the constructor uses a default value in the current locale. The NumberFormat
class contains the following named constructors for the quick creation of frequently used patterns in a specific locale:
#,##0.###
:var d = new NumberFormat.decimalPattern("de_DE"); print(d.format(12.345)); // Result: 12,345
#,##0%
:var p = new NumberFormat.percentPattern("de_DE"); print(p.format(12.345)); // Result: 1.235%
#E0
and does not take into account the significant digits:var s = new NumberFormat.scientificPattern("de_DE"); print(s.format(12.345)); // ==> 1E1
var c = new NumberFormat.currencyPattern("de_DE", 'EUR'), print(c.format(12.345)); // ==> 12,35EUR
The DateFormat
class can format and parse the date in a locale-sensitive manner. We can choose the format-parse pattern from a set of standard date and time formats, or we can create a customized one under certain locales. The DateFormat
class formats the date in the default en_US
locale without any initialization. For other locales, the formatting data must be obtained and the global initializeDateFormatting
function must be called to return Future
that is complete once the locale data is available. Depending on the type of the application you develop, you can choose one of the following libraries that provide this function implementation and enables you to access to the formatting data:
date_symbol_data_local
: For a small application, the data to be formatted can be embedded in the code that is available locally so that you can choose the date_symbol_data_local
library. In the following code, we initialize the date formatting for all the locales at once. Both the parameters of the initializeDateFormatting
method are ignored because the data for all the locales is directly available, as shown in the following code:import 'package:intl/date_symbol_data_local.dart'; import 'package:intl/intl.dart'; void main() { initializeDateFormatting(null, null) .then((_) { Intl.defaultLocale = "de_DE"; DateFormat df = new DateFormat("EEE, MMM d, yyyy"); print(df.format(new DateTime.now())); }).catchError((err) { print(err); }); // Result: Sa., Sep. 20, 2014 }
date_symbol_data_http_request
: For the client side, you need an application that runs inside the web browser and possibly compiles into the JavaScript code. You need to read the data from the server using the XmlHttpRequest
mechanism so that you can choose the date_symbol_data_http_request
library. We need set up the lookup for the date symbols using URL as a second parameter of the initializeDateFormatting
method. We use the path package that provides common operations to manipulate paths in our example:import 'package:intl/date_symbol_data_http_request.dart'; import 'package:intl/intl.dart'; import 'package:path/path.dart' as path; void main() { String datesPath = path.join(path.current, path.fromUri("packages/intl/src/data/dates/")); initializeDateFormatting("pt_BR", datesPath) .then((_) { Intl.defaultLocale = "pt_BR"; DateFormat df = new DateFormat("EEE, MMM d, yyyy"); print(df.format(new DateTime.now())); }).catchError((err) { print(err); }); } // Result: sáb, set 20, 2014
In the preceding code, we requested the date formats for Portuguese – BRAZIL
locale and then set them as a default locale. After that, we used DateFormat
to format current date and time.
date_symbol_data_file
: For the server side, you need an application that executes inside the Dart VM so that you can choose the date_symbol_data_file
library that helps you to read the data from the files in the filesystem. We use the second parameter of the initializeDateFormatting
method to pass the path to those files. The path
parameter will end with a directory separator that is appropriate for the platform. We use the path package for the following example again:import 'package:intl/date_symbol_data_local.dart'; import 'package:intl/Intl.dart'; import 'package:path/path.dart' as path; void main() { String datesPath = path.join(path.current, path.fromUri("packages/intl/src/data/dates/")); initializeDateFormatting("fr", datesPath) .then((_) { DateFormat df = new DateFormat("EEE, MMM d, yyyy", "fr_FR"); print(df.format(new DateTime.now())); }).catchError((err) { print(err); }); // Result: sam., sept. 20, 2014 }
When the locale data is ready to use, we need to specify the ICU date/time patterns, which should be used either in full names or preferably their compact skeleton forms as shown in the following code:
new DateFormat.yMd(); // Skeleton form new DateFormat(DateFormat.YEAR_NUM_MONTH_DAY); // ICU full name // Result: 7/10/2005
We can create compound formats with a set of the add_*
methods as follows:
new DateFormat.yMd().add_Hm(); // Result: 7/10/2005 09:10 PM
The DateFormat
class accepts custom formats that follow the explicit pattern syntax. The constructor resolves a custom pattern and adapts it in different locales, as shown in the following code:
new DateFormat("EEE, MMM d, yyyy"); // Result: Fri, October 7, 2005
The locale is the second optional parameter of the DateFormat
constructor that helps to create the formatter in a specific locale. The constructor generates ArgumentError
if a specified locale does not exist in the set of supported locales.
The internalization of messages is based on a lookup via a named localized version of messages and returning the translated message, possibly interpolated with a list of specified arguments. So, to localize any message, such as Hello $name from Dart!
, we will create a lookup message function that returns the result of the Intl.message
method call, as shown in the following code:
String hello(name) => Intl.message( "Hello $name from Dart!", name:"hello", args: [name], examples: {"name":"World"}, desc: "Greet the user with specified name");
We will use the hello
message function as a wrapper due to the following reasons:
Intl.message
methodThe message string that is passed as the first parameter must be a simple expression where only function parameters and curly brackets are allowed. Other parameters must be literal and should not contain any interpolation expressions.
Now, instead of assigning the message to our code, we will call the hello
function to return the translated message for us, as shown in the following code:
querySelector("#dart_greating") ..text = hello('John'),
In other cases that are similar to our example, the hello
function can interpolate the results of the translated message with a list of arguments that is passed in. The examples
and desc
parameters are not used at runtime and are only made available to the translators. Now, we ready to start using the message functions without any localization and will finally have the correct translation in the current locale.
We can use parentheses to combine the singular and the plural forms into one string with the plural
method of the Intl
class, as shown in the following code:
String replace(int num, String str) => Intl.plural( num, zero: "No one occurrence replaced for $str", one: "$num occurrence replaced for $str", other: "$num occurrences replaced for $str", name: "replace", args: [num, str], desc:"How many occurrences replaced for string", examples: {'num':2, 'str':'hello'});
The plural
method translates and interpolates the contents of the zero
, one
, and other
parameters into a localized message. We missed the two
, few
, and many
parameters because a method can only combine the one
and other
methods but you can specify them if necessary. The plural
method when represented as a String
expression can be used as part of Intl.message
, which specifies only the plural attributes as shown in the following code:
String replace(int num, String str) => Intl.message( """${Intl.plural( num, zero: "No one occurrence replaced for $str", one: "$num occurrence replaced for $str", two: "$num occurrence replaced for $str", few: "$num occurrences replaced for $str", other: "$num occurrences replaced for $str")}""", name: "replace", args: [num, str], desc:"How many occurrences replaced for string", examples: {'num':2, 'str':'hello'}); );
The gender
method of the Intl
class provides out-of-the-box support for gender-based selection, as shown in the following code:
String usage(String name, String gender, String car) => Intl.gender( gender, male: "$name uses his $car", female: "$name uses her $car", other: "$name uses its car", name: "usage",", args: [name, gender, car], desc: "A person uses the car.");
The gender
parameter must equal to one of the literal values: male
, female
, or other
. This method can be used as a part of the Intl.message
method:
String usage(String name, String gender, String car) => Intl.message( """${Intl.gender( gender, male: "$name uses his $car", female: "$name uses her $car", other: "$name uses its car")}""", name: "usage", args: [name, gender, car], desc: "A person uses the car.");
Last but not least, a select
method from the Intl
class is used to format messages differently, depending on the available choice:
String currencySelector(currency, amount) => Intl.select(currency, { "USD": "$amount United States dollars", "CDN" : "$amount Canadian dollars", "other" : "$amount some currency or other." }, name: "currencySelector", args: [currency, amount], examples: {'currency': 'USD', 'amount':'20'}, desc: "Translate abbreviation into full name of currency");
The select
method looks up the value of the currency in a map of cases and returns the results that are found or returns an empty string. It can be a part of the Intl.message
method, as shown in the following code:
String currencySelector(currency, amount) => Intl.message( """${Intl.select(currency, { "USD": "$amount United States dollars", "CDN" : "$amount Canadian dollars", "other" : "$amount some currency or other." })}""", name: "currencySelector", args: [currency, amount], examples: {'currency': 'USD', 'amount':'20'}, desc: "Translate abbreviation into full name of currency");
The message
, plural
, gender
, and select
methods can be combined with each other to create complex message translations as shown in the following code:
String currencySelector(currency, amount) => Intl.select(currency, { "USD": """${Intl.plural(amount, one: '$amount United States dollar', other: '$amount United States dollars')}""", "CDN": """${Intl.plural(amount, one: '$amount Canadian dollar', other: '$amount Canadian dollars')}""", "other": "$amount some currency or other.", }, name: "currencySelector", args: [currency, amount], examples: {'currency': 'USD', 'amount':'20'}, desc: "Translate abbreviation into full name of currency");
In the preceding code, we translated the abbreviation into a full currency name depending on the amount of money and use them to create the available choice.
The Intl
library supports the development of web applications localized for both right-to-left (RTL) and left-to-right (LTR) locales.
We can combine languages with locales that have different directions in only one text by easily using the HTML markup wrappers. In the following code, we use the <span>
HTML tags to embed the company name in Hebrew and have the surrounding text in English:
<span dir="RTL">Copyright 2014 ףותישו דיתע</span>
In cases where the information is entered by the user or if it comes from the backend or third-party web resources, the BidiFormatter
class can insert it automatically at runtime as shown in the following code:
copyrightLbl() => Intl.message("Copyright 2014 ףותישו דיתע",
name: "copyrightLbl",
desc: "Copyright label");
…
BidiFormatter bidiFormatter = new BidiFormatter.UNKNOWN();
querySelector("#copyrightLbl").text =
bidiFormatter.wrapWithUnicode(copyrightLbl());
18.227.26.217