diff --git a/Cargo.lock b/Cargo.lock index b83a06a2336..f33a7e929f8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -410,6 +410,7 @@ dependencies = [ "icu_collator", "icu_datetime", "icu_decimal", + "icu_experimental", "icu_list", "icu_locale", "icu_normalizer", diff --git a/Cargo.toml b/Cargo.toml index 93b69074373..ac23f0cab6e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -167,6 +167,7 @@ icu_provider_blob = { version = "~2.1.1", default-features = false } icu_properties = { version = "~2.1.2", default-features = true } icu_normalizer = { version = "~2.1.1", default-features = false } icu_decimal = { version = "~2.1.1", default-features = false } +icu_experimental = { version = "0.4.0", default-features = false } writeable = "~0.6.2" tinystr = "~0.8.2" yoke = "~0.8.1" diff --git a/core/engine/Cargo.toml b/core/engine/Cargo.toml index 8a070e5ecea..f17cdce0687 100644 --- a/core/engine/Cargo.toml +++ b/core/engine/Cargo.toml @@ -49,6 +49,7 @@ intl = [ "dep:icu_list", "dep:icu_segmenter", "dep:icu_decimal", + "dep:icu_experimental", "dep:writeable", "dep:sys-locale", "dep:yoke", @@ -178,6 +179,9 @@ icu_segmenter = { workspace = true, default-features = false, features = [ icu_decimal = { workspace = true, default-features = false, features = [ "serde", ], optional = true } +icu_experimental = { workspace = true, default-features = false, features = [ + "serde", +], optional = true } writeable = { workspace = true, optional = true } yoke = { workspace = true, optional = true } zerofrom = { workspace = true, optional = true } diff --git a/core/engine/src/builtins/intl/display_names/mod.rs b/core/engine/src/builtins/intl/display_names/mod.rs new file mode 100644 index 00000000000..10252d708d1 --- /dev/null +++ b/core/engine/src/builtins/intl/display_names/mod.rs @@ -0,0 +1,254 @@ +//! This module implements the global `Intl.DisplayNames` object. +//! +//! # TODO +//! - Implement the constructor following `InitializeDisplayNames` (§12.1.1) +//! - Implement `of()` (§12.3.3) +//! - Implement `resolvedOptions()` (§12.3.4) +//! +//! [spec]: https://tc39.es/ecma402/#intl-displaynames-objects + +use crate::{ + Context, JsArgs, JsData, JsNativeError, JsResult, JsString, JsValue, + builtins::{ + BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject, + intl::options::EmptyPreferences, + }, + context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, + js_string, + object::{JsObject, internal_methods::get_prototype_from_constructor}, + property::Attribute, + realm::Realm, + string::StaticJsStrings, + symbol::JsSymbol, +}; + +use boa_gc::{Finalize, Trace}; +use icu_experimental::displaynames::provider::LocaleDisplayNamesV1; +// use icu_experimental::displaynames::{Fallback, LanguageDisplay, RegionDisplayNames, Style}; +//use icu_locale::Locale; + +mod options; +//uncomment when the constructor is implemented. +//pub(crate) use options::DisplayNamesType; + +use super::{ + Service, + locale::{canonicalize_locale_list, filter_locales}, +}; + +#[derive(Trace, Finalize, JsData)] +#[boa_gc(unsafe_empty_trace)] +pub(crate) struct DisplayNames { + //locale: Locale, + // style: Style, + // typ: DisplayNamesType, + // fallback: Fallback, + // language_display: Option, + // native: RegionDisplayNames, +} + +// NOTE: +// `Intl.DisplayNames` supports multiple display name categories (language, +// region, script, currency, calendar, datetimefield)[https://docs.rs/icu/latest/icu/experimental/displaynames/provider/index.html#structs], each backed by different ICU providers. +// However, the `Service` trait is required for `supportedLocalesOf`, +// which only depends on locale availability. Therefore, we use +// `LocaleDisplayNamesV1` as a general marker here, while actual +// formatting dispatches on `type` at runtime inside `of()`. + +impl Service for DisplayNames { + //`LocaleDisplayNamesV1` + // is used here as a temporary stand-in for `supportedLocalesOf` locale + // availability only, and may be replaced by a more specific marker in the future as ICU4X's DisplayNames API design finalizes + type LangMarker = LocaleDisplayNamesV1; + type Preferences = EmptyPreferences; +} + +impl IntrinsicObject for DisplayNames { + fn init(realm: &Realm) { + BuiltInBuilder::from_standard_constructor::(realm) + .static_method( + Self::supported_locales_of, + js_string!("supportedLocalesOf"), + 1, + ) + .property( + JsSymbol::to_string_tag(), + js_string!("Intl.DisplayNames"), + Attribute::CONFIGURABLE, + ) + .method(Self::of, js_string!("of"), 1) + .method(Self::resolved_options, js_string!("resolvedOptions"), 0) + .build(); + } + fn get(intrinsics: &Intrinsics) -> JsObject { + Self::STANDARD_CONSTRUCTOR(intrinsics.constructors()).constructor() + } +} + +impl BuiltInObject for DisplayNames { + const NAME: JsString = StaticJsStrings::DISPLAY_NAMES; +} + +impl BuiltInConstructor for DisplayNames { + const CONSTRUCTOR_ARGUMENTS: usize = 2; + const PROTOTYPE_STORAGE_SLOTS: usize = 3; + const CONSTRUCTOR_STORAGE_SLOTS: usize = 1; + + const STANDARD_CONSTRUCTOR: fn(&StandardConstructors) -> &StandardConstructor = + StandardConstructors::display_names; + + ///The `DisplayNames` constructor is the %Intl.DisplayNames% intrinsic object and a standard built-in property of the Intl object. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma402/#sec-intl-displaynames-constructor + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DisplayNames + // TODO: implement §12.1.1 + fn constructor( + new_target: &JsValue, + args: &[JsValue], + context: &mut Context, + ) -> JsResult { + // 1. If NewTarget is undefined, throw a TypeError exception. + if new_target.is_undefined() { + return Err(JsNativeError::typ() + .with_message("Intl.DisplayNames must be called with new") + .into()); + } + + let _locales = args.get_or_undefined(0); + let _options = args.get_or_undefined(1); + + // 2. Let displayNames be ? OrdinaryCreateFromConstructor(NewTarget, "%Intl.DisplayNames.prototype%", « [[InitializedDisplayNames]], [[Locale]], [[Style]], [[Type]], [[Fallback]], [[LanguageDisplay]], [[Fields]] »). + let prototype = get_prototype_from_constructor( + new_target, + StandardConstructors::display_names, + context, + )?; + let display_names_format = JsObject::from_proto_and_data_with_shared_shape( + context.root_shape(), + prototype, + Self{ + //locale, + // style, + // typ, + // fallback, + // language_display, + // native, + }, + ); + + // 3. Let optionsResolution be ? ResolveOptions(%Intl.DisplayNames%, %Intl.DisplayNames%.[[LocaleData]], locales, options, « require-options »). + // 4. Set options to optionsResolution.[[Options]]. + // 5. Let r be optionsResolution.[[ResolvedLocale]]. + // 6. Let style be ? GetOption(options, "style", string, « "narrow", "short", "long" », "long"). + // 7. Set displayNames.[[Style]] to style. + // 8. Let type be ? GetOption(options, "type", string, « "language", "region", "script", "currency", "calendar", "dateTimeField" », undefined). + // 9. If type is undefined, throw a TypeError exception. + // 10. Set displayNames.[[Type]] to type. + // 11. Let fallback be ? GetOption(options, "fallback", string, « "code", "none" », "code"). + // 12. Set displayNames.[[Fallback]] to fallback. + // 13. Set displayNames.[[Locale]] to r.[[Locale]]. + // 14. Let resolvedLocaleData be r.[[LocaleData]]. + // 15. Let types be resolvedLocaleData.[[types]]. + // 16. Assert: types is a Record (see 12.2.3). + // 17. Let languageDisplay be ? GetOption(options, "languageDisplay", string, « "dialect", "standard" », "dialect"). + // 18. Let typeFields be types.[[]]. + // 19. Assert: typeFields is a Record (see 12.2.3). + // 20. If type is "language", then + // a. Set displayNames.[[LanguageDisplay]] to languageDisplay. + // b. Set typeFields to typeFields.[[]]. + // c. Assert: typeFields is a Record (see 12.2.3). + // 21. Let styleFields be typeFields.[[