Skip to main content

Internationalization APIs

This document describes the current state of Android implementation of the ECMAScript Internationalization API Specification (ECMA-402, or Intl). ECMA-402 is still evolving and the latest iteration is 7th edition which was published in June 2020. Each new edition is built on top of the last one and adds new capabilities typically as,

  • New Intl service constructors (e.g. Intl.Collator, Intl.NumberFormat etc.) or extending existing ones by accepting more parameters
  • New functions or properties in Intl objects (e.g. Intl.Collator.prototype.compare)
  • New locale aware functions in standard Javascript object prototypes (e.g. String.prototype.localeCompare)

One popular implementation strategy followed by other engines, is to bundle an internationalization framework (typically ICU) along with the application package. This guarantees deterministic behaviours at the cost of applications package bloat. We decided to consume the Android platform provided facilities for space efficiency, but at the cost of some variance in behaviours across Android platforms.

ECMA-402 Compliance

Supported

  • Intl.Collator

    • Intl.Collator.supportedLocalesOf
    • Intl.Collator.prototype.compare
    • Intl.Collator.prototype.resolvedOptions
  • Intl.NumberFormat

    • Intl.NumberFormat.supportedLocalesOf
    • Intl.NumberFormat.prototype.format
    • Intl.NumberFormat.prototype.formatToParts
    • Intl.NumberFormat.prototype.resolvedOptions
  • Intl.DateTimeFormat

    • Intl.DateTimeFormat.supportedLocalesOf
    • Intl.DateTimeFormat.prototype.format
    • Intl.DateTimeFormat.prototype.formatToParts
    • Intl.DateTimeFormat.prototype.resolvedOptions
  • Intl.getCanonicalLocales

  • String.prototype

    • localeCompare
    • toLocaleLowerCase
    • toLocaleUpperCase
  • Array.prototype

    • toLocaleString
  • Number.prototype

    • toLocaleString
  • Date.prototype

    • toLocaleString
    • toLocaleDateString
    • toLocaleTimeString

Not yet supported

Excluded

  • Intl.DateTimeFormat: formatMatcher parameter is not respected. The parameter enables the implementation to pick the best display format when it supports only a subset of all possible formats. ICU library in Android platform and hence our implementation allows all subsets and formats which makes this formatMatcher property unnecessary.

Limitations across Android SDKs

Android 11

  • The keys of the object returned by resolvedOptions function in all Intl services are not deterministically ordered as prescribed by spec.
  • DateFormat: ECMAScript beginning of time (-8,640,000,000,000,000), is formatted as November 271817, instead of expected April 271822.
  • Intl.NumberFormat implementation has some rough edges in supporting the following properties,
    • style: 'unit'
    • notation: 'compact'
    • signDisplay
    • currencyFormat

Android 10 and older (SDK < 30)

  • Intl.NumberFormat: Scientific notation formatting has issues on some cases. e.g. -Infinity may get formatted as '-∞E0' instead of expected '-∞'. Another manifestation of the issues is that the formatToParts may return 4 parts instead of 2.
  • Intl.NumberFormat: Compact notation formatToParts doesn't identify unit, hence we report unit as 'literal'. For e.g. the second part of "100ac" gets reported as "literal" instead of "compact"

Android 9 and older (SDK < 29)

  • There are some failures likely due to older Unicode and CLDR version, which are hard to generalize. Some examples are,
    • Intl.NumberFormat: 'Percent' is not accepted as a unit.
    • Intl.NumberFormat: unit symbols difference, kph vs km/h
    • Some issue in significant digit precision, which is not yet looked into the details.

Android 8.0 – 8.1 and older (SDK < 28)

  • Intl.getCanonicalLocales: Unicode/CLDR version differences results in some variances. e.g. und-u-tz-utc vs. und-u-tz-gmt.
  • Intl.NumberFormat: CompactFormatter doesn't respect the precision inputs.

Android 7.0 - 7.1 and older (SDK < 26)

  • Intl.getCanonicalLocales: Unicode/CLDR version differences results in some variances. e.g. und-u-ms-imperial vs. und-u-ms-uksystem.

Android 7.0 - 7.1 and older (SDK < 24)

  • Intl.Collator: Doesn't canonically decompose the input strings. Canonically equivalent string with non-identical code points may not match.
  • Intl.getCanonicalLocales: Unicode/CLDR version differences results in some variances. e.g. und-u-ca-ethiopic-amete-alem vs. und-u-ca-ethioaa, und-u-ks-primary vs. und-u-ks-level1.
  • Intl.NumberFormat: Unit style does not work.
  • Intl.NumberFormat: There are issues in the precision configuration due to lack of APIs.
  • Intl.DateFormat: There are issues with the calendar configuration which needs to be dug into.

SDK < 21 and older

On platforms before 21, Locale.forLanguageTag() is not available, hence we can't construct java.util.Locale object from locale tag. Hence, we fallback to English for any locale input.

Internationalization framework in Android Platform

Our implementation is essentially projecting the Android platform provided internationalization facilities through the ECMA-402 specified services. It implies that the results of running the same code may vary between devices running different versions of Android.

Android platform internationalization libraries have been based on ICU4j project. Version of ICU4j and the backing CLDR data varies across Android platform versions. Also, the ICU APIs were never exposed directly, but only through wrappers and aliases. This results in significant variance in internationalization API surface and data across platform versions.

The following table summarizes ICU, CLDR and Unicode versions available on the Android platforms.

Platform 24+ where ICU4j APIs are available.

Android Platform VersionICUUnicodeCLDR
Android 11 (API level 30)ICU4J 66.1 (ref)Unicode 13 betaCLDR 36.1
Android 10 (API level 29)ICU4j 63.2 (ref)CLDR 34Unicode 11.0
Android 9 (API level 28)ICU4j 60.2 (ref)CLDR 32.0.1Unicode 10.0
Android 8.0 - 8.1 (API levels 26 - 27)ICU4j 58.2( ref)CLDR 30.0.3Unicode 9.0
Android 7.0 - 7.1 (API levels 24 - 25)ICU4j 56 (ref)CLDR 28Unicode 8.0

Pre-24 platforms

Android Platform VersionICUUnicodeCLDR
Android 6.0 (API level 23)ICU4j 55.1 (ref)CLDR 27.0.1Unicode 7.0
Android 5.0 (API levels 21–22)ICU4j 53 (ref)CLDR 25Unicode 6.3
Android 4.4 (API levels 19–20)ICU4j 51 (ref)CLDR 23Unicode 6.2
Android 4.3 (API level 18)ICU4j 50 (ref)CLDR 22.1Unicode 6.2
Android 4.1 (API levels 16–17)ICU4j 4.8 (ref)CLDR 2.0Unicode 6.0

In summary,

  1. Platforms >= 24 have much better internationalization support than earlier, as many ICU classes are available as is.

  2. Platforms 21-24 still has reasonable internationalization support, by allowing creation of Locale objects and enabling selected ICU services through java.text namespace.

  3. Platforms < 21 doesn't allow creation of Locale objects from tags, severely limiting general purpose international code.

  4. Platform 30 has introduced classes under android.icu.number namespace which will majorly improve our Intl.NumberFormat implementation

Impact on Application Size

The following numbers are measured using a test application which takes dependency on the Hermes library to evaluate a JavaScript snippet. Essentially, enabling Intl APIs adds 57-62K per ABI.

Product APK SizeNOINTLINTLDIFFPERC
ARM641,672,2351,729,57957,3443.43%
ARM1,471,5391,528,88357,3443.90%
X86_641,844,2551,901,59957,3443.11%
X861,950,7392,012,17961,4403.15%

The overhead is contributed by both compiled native C++ and Java bits

The uncompressed size of the Hermes shared library got bigger as follows,

libhermes.so SizeNOINTLINTLDIFFPERC
ARM642,473,7602,551,59277,8323.15%
ARM1,696,6721,754,01657,3443.38%
X86_642,633,5282,711,36877,8402.96%
X862,859,9162,945,93686,0203.01%

And the Java bits got bigger as well,

Java SizeNOINTLINTLDIFFPERC
classes.jar
(in hermes.aar)
559120975120,41621541.32%
classes.dex
(replapp.apk)
16070823480874,10046.11%

Please note that the application dex file contains non-hermes class files too.

And finally, this is the increase in the final npm package,

NPM PackageNOINTLINTLDIFFPERC
hermes2144479732192912204,843,2472.26%