← 输入框|github.com|刷新
Skip to content

Ship the Android and iOS V8 runtimes with ICU so Intl is available #11563

Open

Description

@aleclarson

Is your feature request related to a problem? Please describe.

The NativeScript 9 Android and iOS runtimes ship V8 without ICU, so Intl does not exist:

typeof Intl // 'undefined'
new Intl.NumberFormat() // ReferenceError: Intl is not defined

Verified at runtime with @nativescript/android 9.1.1 on a physical device (Android 16) and @nativescript/ios 9.1.0 on an iOS 27 simulator. The same code works in Chromium, Safari/JSC, Node, and Hermes.

The cause is a build flag, not a runtime bug. Both build scripts in NativeScript/v8-buildscripts set it:

The script comment says this "reproduces the shape of the 10.3 build these libraries replace". The flag was carried over from the old build rather than chosen fresh. The 14.9 rebuild seems like a good moment to revisit it.

The flag removes more than the Intl constructors. These engine features also fail or quietly misbehave on Android and iOS:

Feature Behavior without ICU
Intl.* (NumberFormat, DateTimeFormat, PluralRules, Collator, Segmenter, …) ReferenceError
Number#toLocaleString, Date#toLocale*String Options and locales ignored (#8249, #8472)
String#localeCompare Locale and options ignored (#7810, #10617)
String#normalize No-op
toLocaleUpperCase / toLocaleLowerCase Locale rules ignored
RegExp \p{…} property escapes SyntaxError at parse time

Real-world impact:

  • Libraries crash at import. Any module that builds a formatter at module scope throws on load. One example is the common const fmt = new Intl.NumberFormat() pattern.
  • i18n libraries break. @lingui/core calls Intl.PluralRules, so every pluralized message throws.
  • Bundles need patching. Libraries that use \p{L} regexes (linkifyjs, @lexical/link, and others) fail to parse. Apps have to patch them or transpile the regexes.

Describe the solution you'd like

Build the Android and iOS V8 libraries with v8_enable_i18n_support=true, and initialize ICU in both runtimes before V8 starts.

Two packaging options would work:

  1. On by default. This matches browsers and Node. React Native's Hermes exposes Intl on Android and iOS too, but delegates to the platform's ICU instead of shipping its own data — a precedent for exposing Intl by default, not for bundling ICU data (and that delegation path isn't available to V8 anyway; see alternatives).
  2. An ICU runtime flavor. Ship it next to the existing AAR flavors and the iOS framework, selectable from app config. This also fits the "optional" direction in WinterTC Minimum Common Web Platform API Alignment #10297 (comment). The optional piece would be the engine build, not a JS polyfill.

Size data to help weigh the cost:

ICU data file (Chromium's ICU checkout, chromium/deps/icu) Size
common/icudtl.dat (full CLDR) 10.3 MiB (4.4 MiB gzip)
android/icudtl.dat (Chromium's Android filter) 8.9 MiB
ios/icudtl.dat (Chromium's iOS filter) 6.2 MiB
flutter/icudtl.dat (Flutter's filter — text layout only, not an Intl implementation) 0.8 MiB
  • Ship the data once. The scripts set icu_use_data_file=false, which embeds the data in every ABI's .so. With icu_use_data_file=true, Android can ship one architecture-independent icudtl.dat in assets. That stores it once per APK instead of once per ABI.
  • Filter the data. ICU's data filters can trim locales and features further, as the Chromium and Flutter variants show.
  • Precedent: react-native-v8 reports about 7 MiB per ABI for ICU, and the jsc-android intl variant about 6 MiB.
  • Not measured yet: the ICU code-size delta under these exact GN args.

Describe alternatives you've considered

FormatJS polyfills. These are the current workaround, as suggested in #10588. They work: for the cases we tested, forced FormatJS gave byte-identical formatter output on Android, iOS, Chromium, and macOS JSC. They aren't a substitute for engine ICU:

  • They can't fix engine features. \p{…} regexes, normalize, locale case mapping, and localeCompare stay broken.
  • Size, measured with esbuild, minified:
    • About 957 KiB (250 KiB gzip) for eight constructors with English only.
    • About +337 KiB per extra locale.
    • About +789 KiB for time-zone data.
    • Intl.Collator with all locale data is 5.8 MiB.
  • Startup: importing a full bundle (about 10 MiB) took 314 ms in a debug build on a physical Android device.
  • Setup burden: every app must wire up load order, locale data, and defaults itself. Without that, the polyfills fall back to en and UTC on device.

Platform formatter bridges (android.icu, NSNumberFormatter/NSDateFormatter, like the legacy nativescript-intl). These give only a partial API whose output varies by OS version. iOS has no public plural-rules API. Hermes's maintainers describe the platform APIs as "simply inadequate for a complete and correct implementation" (facebook/hermes#1211). Hermes itself took this path for space efficiency and still hasn't reached parity — it lacks PluralRules, RelativeTimeFormat, ListFormat, Locale, and Segmenter, which a third-party ICU4X library (hermes-intl) now fills.

Anything else?

Please accept these terms

Activity

  1. farfromrefug commented on Oct 22, 2026

    @farfromrefug
    Collaborator

    @aleclarson i think this is wrong React Native's default engine, Hermes do not package Intl by default. I think they use a shim to talk to native. And i think they are right not to ship it.
    here is an hermes version shipping it https://github.com/magrinj/hermes-intl. and here is a repo explaining what they actually package https://github.com/i18next/i18next-icu
    I think you are also wrong to compare to flutter. I think flutter does NOT package Intl and so those ICU numbers are wrong.
    Flutter has a plugin for this https://pub.dev/packages/intl . i am not sure yet how they get ICU data. Looked at the code quickly but could not find it.

    We should be careful with those AI reports!

  2. aleclarson commented on Oct 25, 2026

    @aleclarson
    ContributorAuthor

    Thanks for the correction, @farfromrefug — you're right about Hermes, and I've updated the post.

    Verified in the source: Intl is on by default in Hermes, but on Android it's JNI shims (lib/Platform/Intl/java/) calling android.icu.*, and on iOS it calls into system frameworks. Their own doc says it plainly: "We decided to consume the Android and iOS platform provided facilities for space efficiency, but at the cost of some variance in behaviours."

    That said, the correction doesn't change the recommendation here, for three reasons:

    1. Delegation isn't an option for V8. Meta could take that path because they implemented ECMA-402 themselves — the "shim" is hand-written Java/ObjC inside the engine. V8's Intl is compiled statically against ICU; there's no build flag to route it to android.icu or Foundation. Even Chromium bundles icudtl.dat on Android rather than calling the platform ICU. For V8 the choice is bundled data or no Intl at all — which is why the proposal is about how to package the data (one filtered icudtl.dat per app, opt-in flavor), not whether to ship it.
    2. Hermes's delegation left Intl permanently incomplete. Stock Hermes still lacks PluralRules, RelativeTimeFormat, ListFormat, Locale, and Segmenter. That's exactly why the hermes-intl repo you linked exists — it isn't a Hermes build with ICU, it's a Rust/ICU4X library patching in the missing APIs at ~1–1.7 MB per ABI. Users ended up shipping ICU anyway.
    3. Half the broken surface can't be delegated or polyfilled anyway. \p{…} regexes, String#normalize, localeCompare, toLocale* live inside V8 — only v8_enable_i18n_support fixes them, and silently-wrong localeCompare is a worse outcome than a missing constructor.

    On Flutter: you're right that it doesn't expose Intl — the intl pub package is pure Dart with CLDR-generated data. But flutter/icudtl.dat is real (864,880 B in chromium/deps/icu, matching the 0.8 MiB figure); it's the engine's text-layout filter, cited only as precedent that ICU data filters work. I've annotated the table so it isn't read as an Intl claim.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions