The Intl library

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.

Changing a locale

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:

  • Specify the locale directly when you call the methods of the Intl class
  • Provide the locale when you create an instance of the formatter class
  • Use the following special withLocale 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:

  1. It swaps the specified locale string with the current one.
  2. Then, it executes the message_function function and saves the result.
  3. It swaps the locales back.
  4. Finally, it returns the result of the message_function function.

    Note

    International Components for Unicode (ICU) is an open source project created for the Unicode support via the implementation of the Unicode standard. The Intl library uses the ICU patterns for internalization and localization.

Formatting numbers

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:

  • The decimal format uses the decimal pattern, that is, #,##0.###:
    var d = new NumberFormat.decimalPattern("de_DE");
    print(d.format(12.345));
    // Result: 12,345
  • The percent format uses the percent pattern, that is, #,##0%:
    var p = new NumberFormat.percentPattern("de_DE");
    print(p.format(12.345));
    // Result: 1.235%
  • The scientific format prints only the terms equivalent to #E0 and does not take into account the significant digits:
    var s = new NumberFormat.scientificPattern("de_DE");
    print(s.format(12.345));
    // ==> 1E1
  • The currency format always uses the name of the currency passed as the second parameter:
    var c = new NumberFormat.currencyPattern("de_DE", 'EUR'),
    print(c.format(12.345));
    // ==> 12,35EUR

Formatting dates

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.

Internalizing messages

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:

  • The function scope can encapsulate an implementation
  • The function parameters can be passed as parameters in the Intl.message method

The 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.

Note

The name and args arguments of the message function are required and the name argument must match the name of the caller function.

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.

Adding parentheses

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'});
);

Adding gender

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.");

Adding select

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");

Creating complex message translations

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.

Bidirectional formatting

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());
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.227.26.217