From 4e685b5f35b5a811b0c2cc488e5015d03ff82aac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Juli=C3=A1n=20Espina?= Date: Thu, 22 Jan 2026 17:06:32 -0600 Subject: [PATCH 1/6] restructure Intl implementation to use ICU4X's locale preferences --- core/engine/src/builtins/intl/collator/mod.rs | 105 +------------- .../src/builtins/intl/collator/options.rs | 75 +++++++++- .../src/builtins/intl/date_time_format/mod.rs | 4 +- .../src/builtins/intl/list_format/mod.rs | 6 +- core/engine/src/builtins/intl/locale/tests.rs | 4 +- core/engine/src/builtins/intl/locale/utils.rs | 34 ++++- core/engine/src/builtins/intl/mod.rs | 21 ++- .../src/builtins/intl/number_format/mod.rs | 128 ++++++++---------- .../builtins/intl/number_format/options.rs | 59 +++++++- core/engine/src/builtins/intl/options.rs | 30 +++- .../src/builtins/intl/plural_rules/mod.rs | 5 +- .../engine/src/builtins/intl/segmenter/mod.rs | 3 +- 12 files changed, 278 insertions(+), 196 deletions(-) diff --git a/core/engine/src/builtins/intl/collator/mod.rs b/core/engine/src/builtins/intl/collator/mod.rs index 4cf8696cc83..e121f98f5f4 100644 --- a/core/engine/src/builtins/intl/collator/mod.rs +++ b/core/engine/src/builtins/intl/collator/mod.rs @@ -6,11 +6,7 @@ use icu_collator::{ provider::CollationMetadataV1, }; -use icu_locale::{ - Locale, extensions::unicode, extensions_unicode_key as key, preferences::PreferenceKey, - subtags::subtag, -}; -use icu_provider::DataMarkerAttributes; +use icu_locale::{Locale, extensions::unicode}; use crate::{ Context, JsArgs, JsData, JsNativeError, JsResult, JsString, JsValue, @@ -18,10 +14,9 @@ use crate::{ BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject, OrdinaryObject, options::get_option, }, - context::{ - icu::IntlProvider, - intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, - }, + context:: + intrinsics::{Intrinsics, StandardConstructor, StandardConstructors} + , js_string, native_function::NativeFunction, object::{ @@ -36,7 +31,7 @@ use crate::{ use super::{ Service, - locale::{canonicalize_locale_list, filter_locales, resolve_locale, validate_extension}, + locale::{canonicalize_locale_list, filter_locales, resolve_locale}, options::{IntlOptions, coerce_options_to_object}, }; @@ -72,95 +67,7 @@ impl Collator { impl Service for Collator { type LangMarker = CollationMetadataV1; - type LocaleOptions = CollatorPreferences; - - fn resolve(locale: &mut Locale, options: &mut Self::LocaleOptions, provider: &IntlProvider) { - let mut locale_preferences = CollatorPreferences::from(&*locale); - locale_preferences.collation_type = locale_preferences.collation_type.take().filter(|co| { - let attr = DataMarkerAttributes::from_str_or_panic(co.as_str()); - co != &CollationType::Search - && validate_extension::(locale.id.clone(), attr, provider) - }); - locale.extensions.unicode.clear(); - - options.locale_preferences = (&*locale).into(); - - options.collation_type = options - .collation_type - .take() - .filter(|co| { - let attr = DataMarkerAttributes::from_str_or_panic(co.as_str()); - co != &CollationType::Search - && validate_extension::(locale.id.clone(), attr, provider) - }) - .inspect(|co| { - if Some(co) == locale_preferences.collation_type.as_ref() - && let Some(co) = co.unicode_extension_value() - { - locale.extensions.unicode.keywords.set(key!("co"), co); - } - }) - .or_else(|| { - if let Some(co) = locale_preferences - .collation_type - .as_ref() - .and_then(CollationType::unicode_extension_value) - { - locale.extensions.unicode.keywords.set(key!("co"), co); - } - locale_preferences.collation_type - }); - - options.numeric_ordering = options - .numeric_ordering - .take() - .inspect(|kn| { - if Some(kn) == locale_preferences.numeric_ordering.as_ref() - && let Some(mut kn) = kn.unicode_extension_value() - { - if kn.as_single_subtag() == Some(&subtag!("true")) { - kn = unicode::Value::new_empty(); - } - locale.extensions.unicode.keywords.set(key!("kn"), kn); - } - }) - .or_else(|| { - if let Some(mut kn) = locale_preferences - .numeric_ordering - .as_ref() - .and_then(CollationNumericOrdering::unicode_extension_value) - { - if kn.as_single_subtag() == Some(&subtag!("true")) { - kn = unicode::Value::new_empty(); - } - locale.extensions.unicode.keywords.set(key!("kn"), kn); - } - - locale_preferences.numeric_ordering - }); - - options.case_first = options - .case_first - .take() - .inspect(|kf| { - if Some(kf) == locale_preferences.case_first.as_ref() - && let Some(kn) = kf.unicode_extension_value() - { - locale.extensions.unicode.keywords.set(key!("kf"), kn); - } - }) - .or_else(|| { - if let Some(kf) = locale_preferences - .case_first - .as_ref() - .and_then(CollationCaseFirst::unicode_extension_value) - { - locale.extensions.unicode.keywords.set(key!("kf"), kf); - } - - locale_preferences.case_first - }); - } + type Preferences = CollatorPreferences; } impl IntrinsicObject for Collator { diff --git a/core/engine/src/builtins/intl/collator/options.rs b/core/engine/src/builtins/intl/collator/options.rs index c1c552ce8f8..e8911fdd354 100644 --- a/core/engine/src/builtins/intl/collator/options.rs +++ b/core/engine/src/builtins/intl/collator/options.rs @@ -1,13 +1,22 @@ use std::str::FromStr; use icu_collator::{ + CollatorPreferences, options::{CaseLevel, Strength}, - preferences::CollationCaseFirst, + preferences::{CollationCaseFirst, CollationType}, +}; +use icu_locale::{LanguageIdentifier, preferences::PreferenceKey}; +use icu_provider::{ + DataMarker, DataMarkerAttributes, DryDataProvider, + prelude::icu_locale_core::{extensions::unicode, preferences::LocalePreferences}, }; use crate::{ Context, JsNativeError, JsResult, JsValue, - builtins::options::{OptionType, ParsableOptionType}, + builtins::{ + intl::{ServicePreferences, locale::validate_extension}, + options::{OptionType, ParsableOptionType}, + }, }; #[derive(Debug, Clone, Copy)] @@ -97,3 +106,65 @@ impl OptionType for CollationCaseFirst { } } } + +impl ServicePreferences for CollatorPreferences { + fn validate_extensions( + &mut self, + id: &LanguageIdentifier, + provider: &impl DryDataProvider, + ) { + self.collation_type = self.collation_type.take().filter(|co| { + let attr = DataMarkerAttributes::from_str_or_panic(co.as_str()); + co != &CollationType::Search && validate_extension::(id, attr, provider) + }); + } + + fn set_locale(&mut self, locale: LocalePreferences) { + self.locale_preferences = locale + } + + fn as_unicode(&self) -> unicode::Unicode { + let mut exts = unicode::Unicode::new(); + + if let Some(co) = self.collation_type + && let Some(value) = co.unicode_extension_value() + { + exts.keywords.set(unicode::key!("co"), value); + } + + if let Some(kn) = self.numeric_ordering + && let Some(value) = kn.unicode_extension_value() + { + exts.keywords.set(unicode::key!("kn"), value); + } + + if let Some(kf) = self.case_first + && let Some(value) = kf.unicode_extension_value() + { + exts.keywords.set(unicode::key!("kf"), value); + } + + exts + } + + fn extend(&mut self, other: &Self) { + self.extend(*other); + } + + fn intersection(&self, other: &Self) -> Self { + let mut inter = self.clone(); + if inter.locale_preferences != other.locale_preferences { + inter.locale_preferences = LocalePreferences::default() + } + if inter.collation_type != other.collation_type { + inter.collation_type.take(); + } + if inter.case_first != other.case_first { + inter.case_first.take(); + } + if inter.numeric_ordering != other.numeric_ordering { + inter.numeric_ordering.take(); + } + inter + } +} diff --git a/core/engine/src/builtins/intl/date_time_format/mod.rs b/core/engine/src/builtins/intl/date_time_format/mod.rs index 6fdaebe3a3e..81a2cdbbba5 100644 --- a/core/engine/src/builtins/intl/date_time_format/mod.rs +++ b/core/engine/src/builtins/intl/date_time_format/mod.rs @@ -96,9 +96,9 @@ pub(crate) struct DateTimeFormat { impl Service for DateTimeFormat { type LangMarker = DecimalSymbolsV1; - type LocaleOptions = DateTimeFormatterPreferences; + type Preferences = DateTimeFormatterPreferences; - fn resolve(locale: &mut Locale, options: &mut Self::LocaleOptions, provider: &IntlProvider) { + fn resolve(locale: &mut Locale, options: &mut Self::Preferences, provider: &IntlProvider) { let locale_preferences = DateTimeFormatterPreferences::from(&*locale); // TODO: Determine if any locale_preferences processing is needed here. diff --git a/core/engine/src/builtins/intl/list_format/mod.rs b/core/engine/src/builtins/intl/list_format/mod.rs index 5ac0c952b20..0faef7169ea 100644 --- a/core/engine/src/builtins/intl/list_format/mod.rs +++ b/core/engine/src/builtins/intl/list_format/mod.rs @@ -11,9 +11,7 @@ use icu_locale::Locale; use crate::{ Context, JsArgs, JsData, JsNativeError, JsResult, JsString, JsValue, builtins::{ - Array, BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject, OrdinaryObject, - iterable::IteratorHint, - options::{get_option, get_options_object}, + Array, BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject, OrdinaryObject, intl::options::EmptyPreferences, iterable::IteratorHint, options::{get_option, get_options_object} }, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, js_string, @@ -48,7 +46,7 @@ impl Service for ListFormat { const ATTRIBUTES: &'static icu_provider::DataMarkerAttributes = ListFormatterPatterns::WIDE; - type LocaleOptions = (); + type Preferences = EmptyPreferences; } impl IntrinsicObject for ListFormat { diff --git a/core/engine/src/builtins/intl/locale/tests.rs b/core/engine/src/builtins/intl/locale/tests.rs index c9c280f581a..6cdd121ec80 100644 --- a/core/engine/src/builtins/intl/locale/tests.rs +++ b/core/engine/src/builtins/intl/locale/tests.rs @@ -29,9 +29,9 @@ struct TestService; impl Service for TestService { type LangMarker = PluralsCardinalV1; - type LocaleOptions = TestOptions; + type Preferences = TestOptions; - fn resolve(locale: &mut Locale, options: &mut Self::LocaleOptions, provider: &IntlProvider) { + fn resolve(locale: &mut Locale, options: &mut Self::Preferences, provider: &IntlProvider) { let loc_hc = locale .extensions .unicode diff --git a/core/engine/src/builtins/intl/locale/utils.rs b/core/engine/src/builtins/intl/locale/utils.rs index a4a3e5f8272..fa5c0fbb2cf 100644 --- a/core/engine/src/builtins/intl/locale/utils.rs +++ b/core/engine/src/builtins/intl/locale/utils.rs @@ -3,7 +3,7 @@ use crate::{ builtins::{ Array, intl::{ - Service, + Service, ServicePreferences, options::{IntlOptions, LocaleMatcher, coerce_options_to_object}, }, options::get_option, @@ -311,7 +311,7 @@ where /// [spec]: https://tc39.es/ecma402/#sec-resolvelocale pub(in crate::builtins::intl) fn resolve_locale( requested_locales: impl IntoIterator, - options: &mut IntlOptions, + options: &mut IntlOptions, provider: &IntlProvider, ) -> JsResult where @@ -390,11 +390,32 @@ where // a. Let foundLocale be InsertUnicodeExtensionAndCanonicalize(foundLocale, supportedExtension). // 11. Set result.[[locale]] to foundLocale. - // 12. Return result. - S::resolve(&mut found_locale, &mut options.service_options, provider); + // This is basically an adaptation of the process above, which + // ensures: + // - All options provided by the locale are valid. + // - All options provided by args are valid. + // - Options provided by args are extended (but not overriden) by + // options provided in the locale. + // - Only the locale options that extended the args options are + // added to the final locale. provider .locale_canonicalizer()? .canonicalize(&mut found_locale); + let mut locale_prefs: S::Preferences = S::Preferences::from(&found_locale); + + options + .service_options + .validate_extensions(&found_locale.id, provider); + locale_prefs.validate_extensions(&found_locale.id, provider); + + // This also sets the locale to the found locale. + options.service_options.extend(&locale_prefs); + found_locale.extensions.unicode = options + .service_options + .intersection(&locale_prefs) + .as_unicode(); + + // 12. Return result. Ok(found_locale) } @@ -465,7 +486,7 @@ where /// /// Calling this function with a singleton `DataMarker` will always return `None`. pub(in crate::builtins::intl) fn validate_extension( - language: LanguageIdentifier, + language: &LanguageIdentifier, attributes: &DataMarkerAttributes, provider: &impl DryDataProvider, ) -> bool { @@ -491,13 +512,14 @@ mod tests { impl Service for TestService { type LangMarker = PluralsCardinalV1; - type LocaleOptions = (); + type Preferences = EmptyPreferences; } use crate::{ builtins::intl::{ Service, locale::utils::{lookup_matching_locale_by_best_fit, lookup_matching_locale_by_prefix}, + options::EmptyPreferences, }, context::icu::IntlProvider, }; diff --git a/core/engine/src/builtins/intl/mod.rs b/core/engine/src/builtins/intl/mod.rs index 8d9d4269bce..09dcc25c13e 100644 --- a/core/engine/src/builtins/intl/mod.rs +++ b/core/engine/src/builtins/intl/mod.rs @@ -26,7 +26,8 @@ use crate::{ }; use boa_gc::{Finalize, Trace}; -use icu_provider::{DataMarker, DataMarkerAttributes}; +use icu_locale::{LanguageIdentifier, extensions::unicode, preferences::LocalePreferences}; +use icu_provider::{DataMarker, DataMarkerAttributes, DryDataProvider}; use static_assertions::const_assert; pub(crate) mod collator; @@ -178,6 +179,18 @@ impl Intl { } } +trait ServicePreferences: for<'a> From<&'a icu_locale::Locale> { + fn validate_extensions( + &mut self, + id: &LanguageIdentifier, + provider: &impl DryDataProvider, + ); + fn set_locale(&mut self, locale: LocalePreferences); + fn as_unicode(&self) -> unicode::Unicode; + fn extend(&mut self, other: &Self); + fn intersection(&self, other: &Self) -> Self; +} + /// A service component that is part of the `Intl` API. /// /// This needs to be implemented for every `Intl` service in order to use the functions @@ -190,9 +203,9 @@ trait Service { /// The attributes used to resolve the locale. const ATTRIBUTES: &'static DataMarkerAttributes = DataMarkerAttributes::empty(); - /// The set of options used in the [`Service::resolve`] method to resolve the provided + /// The set of preferences used in the [`Service::resolve`] method to resolve the provided /// locale. - type LocaleOptions; + type Preferences: ServicePreferences; /// Resolves the final value of `locale` from a set of `options`. /// @@ -207,7 +220,7 @@ trait Service { /// skipped. fn resolve( _locale: &mut icu_locale::Locale, - _options: &mut Self::LocaleOptions, + _options: &mut Self::Preferences, _provider: &IntlProvider, ) { } diff --git a/core/engine/src/builtins/intl/number_format/mod.rs b/core/engine/src/builtins/intl/number_format/mod.rs index e442923c5c3..92ddec2f3b1 100644 --- a/core/engine/src/builtins/intl/number_format/mod.rs +++ b/core/engine/src/builtins/intl/number_format/mod.rs @@ -1,28 +1,26 @@ +use std::cell::Cell; + use boa_gc::{Finalize, Trace}; use fixed_decimal::{Decimal, FloatPrecision, SignDisplay}; use icu_decimal::{ - DecimalFormatter, FormattedDecimal, + DecimalFormatter, DecimalFormatterPreferences, FormattedDecimal, options::{DecimalFormatterOptions, GroupingStrategy}, preferences::NumberingSystem, - provider::DecimalSymbolsV1, + provider::{DecimalDigitsV1, DecimalSymbolsV1}, }; mod options; -use icu_locale::{ - Locale, - extensions::unicode::{Value, key}, -}; -use icu_provider::DataMarkerAttributes; +use icu_locale::{Locale, extensions::unicode::Value}; +use icu_provider::{DataMarker, DataMarkerAttributes, DynamicDataProvider, buf::BufferMarker}; use num_bigint::BigInt; use num_traits::Num; pub(crate) use options::*; use super::{ Service, - locale::{canonicalize_locale_list, filter_locales, resolve_locale, validate_extension}, + locale::{canonicalize_locale_list, filter_locales, resolve_locale}, options::{IntlOptions, coerce_options_to_object}, }; -use crate::value::JsVariant; use crate::{ Context, JsArgs, JsData, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue, NativeFunction, @@ -41,6 +39,7 @@ use crate::{ string::StaticJsStrings, value::PreferredType, }; +use crate::{js_error, value::JsVariant}; #[cfg(test)] mod tests; @@ -51,7 +50,7 @@ mod tests; pub(crate) struct NumberFormat { locale: Locale, formatter: DecimalFormatter, - numbering_system: Option, + numbering_system: NumberingSystem, unit_options: UnitFormatOptions, digit_options: DigitFormatOptions, notation: Notation, @@ -87,49 +86,7 @@ pub(super) struct NumberFormatLocaleOptions { impl Service for NumberFormat { type LangMarker = DecimalSymbolsV1; - type LocaleOptions = NumberFormatLocaleOptions; - - fn resolve( - locale: &mut Locale, - options: &mut Self::LocaleOptions, - provider: &crate::context::icu::IntlProvider, - ) { - let numbering_system = options - .numbering_system - .take() - .filter(|nu| { - NumberingSystem::try_from(nu.clone()).is_ok_and(|nu| { - let attr = DataMarkerAttributes::from_str_or_panic(nu.as_str()); - validate_extension::(locale.id.clone(), attr, provider) - }) - }) - .or_else(|| { - locale - .extensions - .unicode - .keywords - .get(&key!("nu")) - .cloned() - .filter(|nu| { - NumberingSystem::try_from(nu.clone()).is_ok_and(|nu| { - let attr = DataMarkerAttributes::from_str_or_panic(nu.as_str()); - validate_extension::( - locale.id.clone(), - attr, - provider, - ) - }) - }) - }); - - locale.extensions.unicode.clear(); - - if let Some(nu) = numbering_system.clone() { - locale.extensions.unicode.keywords.set(key!("nu"), nu); - } - - options.numbering_system = numbering_system; - } + type Preferences = DecimalFormatterPreferences; } impl IntrinsicObject for NumberFormat { @@ -301,8 +258,10 @@ impl NumberFormat { let mut intl_options = IntlOptions { matcher, - service_options: NumberFormatLocaleOptions { - numbering_system: numbering_system.map(Value::from), + service_options: { + let mut prefs = DecimalFormatterPreferences::default(); + prefs.numbering_system = numbering_system; + prefs }, }; @@ -440,16 +399,49 @@ impl NumberFormat { let mut options = DecimalFormatterOptions::default(); options.grouping_strategy = Some(use_grouping); - let formatter = DecimalFormatter::try_new_with_buffer_provider( - context.intl_provider().erased_provider(), - (&locale).into(), - options, - ) - .map_err(|err| JsNativeError::typ().with_message(err.to_string()))?; + let (formatter, numbering_system) = { + struct RequestInspector<'a> { + inner: &'a dyn DynamicDataProvider, + nu: Cell>>, + } + impl DynamicDataProvider for RequestInspector<'_> { + fn load_data( + &self, + marker: icu_provider::DataMarkerInfo, + req: icu_provider::DataRequest<'_>, + ) -> Result, icu_provider::DataError> + { + if marker.id == DecimalDigitsV1::INFO.id { + self.nu.set(Some(req.id.marker_attributes.to_owned())); + } + self.inner.load_data(marker, req) + } + } + + let inspector = RequestInspector { + inner: context.intl_provider().erased_provider(), + nu: Cell::new(None), + }; + let formatter = DecimalFormatter::try_new_with_buffer_provider( + context.intl_provider().erased_provider(), + (&locale).into(), + options, + ) + .map_err(|err| JsNativeError::typ().with_message(err.to_string()))?; + + let nu = (|| { + let nu = inspector.nu.into_inner()?; + let nu = Value::try_from_str(&nu).ok()?; + NumberingSystem::try_from(nu).ok() + })() + .ok_or_else(|| js_error!(TypeError: "invalid numbering system from Intl provider"))?; + + (formatter, nu) + }; Ok(NumberFormat { locale, - numbering_system: intl_options.service_options.numbering_system, + numbering_system, formatter, unit_options, digit_options, @@ -573,13 +565,11 @@ impl NumberFormat { js_string!(nf.locale.to_string()), Attribute::all(), ); - if let Some(nu) = &nf.numbering_system { - options.property( - js_string!("numberingSystem"), - js_string!(nu.to_string()), - Attribute::all(), - ); - } + options.property( + js_string!("numberingSystem"), + js_string!(nf.numbering_system.as_str()), + Attribute::all(), + ); options.property( js_string!("style"), diff --git a/core/engine/src/builtins/intl/number_format/options.rs b/core/engine/src/builtins/intl/number_format/options.rs index 89cf0b8b223..51c9ff697c4 100644 --- a/core/engine/src/builtins/intl/number_format/options.rs +++ b/core/engine/src/builtins/intl/number_format/options.rs @@ -6,14 +6,24 @@ use fixed_decimal::{ }; use boa_macros::js_str; -use icu_decimal::preferences::NumberingSystem; -use icu_locale::extensions::unicode::Value; +use icu_decimal::{DecimalFormatterPreferences, preferences::NumberingSystem}; +use icu_locale::{extensions::unicode::Value, preferences::PreferenceKey}; +use icu_provider::{ + DataMarkerAttributes, + prelude::icu_locale_core::{ + LanguageIdentifier, extensions::unicode, preferences::LocalePreferences, + }, +}; use tinystr::TinyAsciiStr; use crate::{ Context, JsNativeError, JsObject, JsResult, JsStr, JsString, JsValue, builtins::{ - intl::options::{default_number_option, get_number_option}, + intl::{ + ServicePreferences, + locale::validate_extension, + options::{default_number_option, get_number_option}, + }, options::{OptionType, ParsableOptionType, get_option}, }, js_string, @@ -1253,3 +1263,46 @@ impl RoundingType { } } } + +impl ServicePreferences for DecimalFormatterPreferences { + fn validate_extensions( + &mut self, + id: &LanguageIdentifier, + provider: &impl icu_provider::DryDataProvider, + ) { + self.numbering_system = self.numbering_system.take().filter(|nu| { + let attr = DataMarkerAttributes::from_str_or_panic(nu.as_str()); + validate_extension::(id, attr, provider) + }); + } + + fn set_locale(&mut self, locale: LocalePreferences) { + self.locale_preferences = locale + } + + fn as_unicode(&self) -> unicode::Unicode { + let mut exts = unicode::Unicode::new(); + + if let Some(nu) = self.numbering_system + && let Some(value) = nu.unicode_extension_value() + { + exts.keywords.set(unicode::key!("nu"), value); + } + exts + } + + fn extend(&mut self, other: &Self) { + self.extend(*other); + } + + fn intersection(&self, other: &Self) -> Self { + let mut inter = self.clone(); + if inter.locale_preferences != other.locale_preferences { + inter.locale_preferences = LocalePreferences::default() + } + if inter.numbering_system != other.numbering_system { + inter.numbering_system.take(); + } + inter + } +} diff --git a/core/engine/src/builtins/intl/options.rs b/core/engine/src/builtins/intl/options.rs index 17fbde775d4..e11ec9022fd 100644 --- a/core/engine/src/builtins/intl/options.rs +++ b/core/engine/src/builtins/intl/options.rs @@ -1,10 +1,12 @@ use std::{fmt, str::FromStr}; +use icu_locale::{LanguageIdentifier, extensions::unicode, preferences::LocalePreferences}; +use icu_provider::{DataMarker, DryDataProvider}; use num_traits::FromPrimitive; use crate::{ Context, JsNativeError, JsResult, JsString, JsValue, - builtins::{OrdinaryObject, options::ParsableOptionType}, + builtins::{OrdinaryObject, intl::ServicePreferences, options::ParsableOptionType}, object::JsObject, }; @@ -48,6 +50,32 @@ impl FromStr for LocaleMatcher { impl ParsableOptionType for LocaleMatcher {} +#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)] +pub(super) struct EmptyPreferences; + +impl From<&icu_locale::Locale> for EmptyPreferences { + fn from(_: &icu_locale::Locale) -> Self { + Self + } +} + +impl ServicePreferences for EmptyPreferences { + fn validate_extensions( + &mut self, + _: &LanguageIdentifier, + _: &impl DryDataProvider, + ) { + } + fn set_locale(&mut self, _: LocalePreferences) {} + fn as_unicode(&self) -> unicode::Unicode { + unicode::Unicode::new() + } + fn extend(&mut self, _: &Self) {} + fn intersection(&self, _: &Self) -> Self { + Self + } +} + /// Abstract operation `GetNumberOption ( options, property, minimum, maximum, fallback )` /// /// Extracts the value of the property named `property` from the provided `options` diff --git a/core/engine/src/builtins/intl/plural_rules/mod.rs b/core/engine/src/builtins/intl/plural_rules/mod.rs index 68400788b93..bd3ec42c3c2 100644 --- a/core/engine/src/builtins/intl/plural_rules/mod.rs +++ b/core/engine/src/builtins/intl/plural_rules/mod.rs @@ -11,8 +11,7 @@ use icu_plurals::{ use crate::{ Context, JsArgs, JsData, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue, builtins::{ - Array, BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject, - options::get_option, + Array, BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject, intl::options::EmptyPreferences, options::get_option }, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, js_string, @@ -43,7 +42,7 @@ pub(crate) struct PluralRules { impl Service for PluralRules { type LangMarker = PluralsCardinalV1; - type LocaleOptions = (); + type Preferences = EmptyPreferences; } impl IntrinsicObject for PluralRules { diff --git a/core/engine/src/builtins/intl/segmenter/mod.rs b/core/engine/src/builtins/intl/segmenter/mod.rs index 2846506ec48..459a385f6ed 100644 --- a/core/engine/src/builtins/intl/segmenter/mod.rs +++ b/core/engine/src/builtins/intl/segmenter/mod.rs @@ -4,6 +4,7 @@ use crate::{ Context, JsArgs, JsData, JsNativeError, JsResult, JsString, JsSymbol, JsValue, builtins::{ BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject, + intl::options::EmptyPreferences, options::{get_option, get_options_object}, }, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, @@ -98,7 +99,7 @@ impl Service for Segmenter { // and replace when segmenters are locale-aware. type LangMarker = CollationDiacriticsV1; - type LocaleOptions = (); + type Preferences = EmptyPreferences; } impl IntrinsicObject for Segmenter { From 49cc51f66f34cc6d3e90262cd33b3e658baadcb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juli=C3=A1n=20Espina=20Del=20=C3=81ngel?= Date: Thu, 22 Jan 2026 23:14:01 -0600 Subject: [PATCH 2/6] finish first draft --- core/engine/src/builtins/intl/collator/mod.rs | 4 +- .../src/builtins/intl/collator/options.rs | 17 +-- .../src/builtins/intl/date_time_format/mod.rs | 106 +----------------- .../builtins/intl/date_time_format/options.rs | 86 +++++++++++++- .../src/builtins/intl/list_format/mod.rs | 5 +- core/engine/src/builtins/intl/locale/tests.rs | 86 ++++++++------ core/engine/src/builtins/intl/mod.rs | 29 +---- .../src/builtins/intl/number_format/mod.rs | 5 - .../builtins/intl/number_format/options.rs | 17 +-- core/engine/src/builtins/intl/options.rs | 12 +- .../src/builtins/intl/plural_rules/mod.rs | 3 +- 11 files changed, 161 insertions(+), 209 deletions(-) diff --git a/core/engine/src/builtins/intl/collator/mod.rs b/core/engine/src/builtins/intl/collator/mod.rs index e121f98f5f4..18939b54008 100644 --- a/core/engine/src/builtins/intl/collator/mod.rs +++ b/core/engine/src/builtins/intl/collator/mod.rs @@ -14,9 +14,7 @@ use crate::{ BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject, OrdinaryObject, options::get_option, }, - context:: - intrinsics::{Intrinsics, StandardConstructor, StandardConstructors} - , + context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, js_string, native_function::NativeFunction, object::{ diff --git a/core/engine/src/builtins/intl/collator/options.rs b/core/engine/src/builtins/intl/collator/options.rs index e8911fdd354..0cfbfd02f85 100644 --- a/core/engine/src/builtins/intl/collator/options.rs +++ b/core/engine/src/builtins/intl/collator/options.rs @@ -4,10 +4,11 @@ use icu_collator::{ CollatorPreferences, options::{CaseLevel, Strength}, preferences::{CollationCaseFirst, CollationType}, + provider::CollationMetadataV1, }; use icu_locale::{LanguageIdentifier, preferences::PreferenceKey}; use icu_provider::{ - DataMarker, DataMarkerAttributes, DryDataProvider, + DataMarkerAttributes, prelude::icu_locale_core::{extensions::unicode, preferences::LocalePreferences}, }; @@ -17,6 +18,7 @@ use crate::{ intl::{ServicePreferences, locale::validate_extension}, options::{OptionType, ParsableOptionType}, }, + context::icu::IntlProvider, }; #[derive(Debug, Clone, Copy)] @@ -108,21 +110,14 @@ impl OptionType for CollationCaseFirst { } impl ServicePreferences for CollatorPreferences { - fn validate_extensions( - &mut self, - id: &LanguageIdentifier, - provider: &impl DryDataProvider, - ) { + fn validate_extensions(&mut self, id: &LanguageIdentifier, provider: &IntlProvider) { self.collation_type = self.collation_type.take().filter(|co| { let attr = DataMarkerAttributes::from_str_or_panic(co.as_str()); - co != &CollationType::Search && validate_extension::(id, attr, provider) + co != &CollationType::Search + && validate_extension::(id, attr, provider) }); } - fn set_locale(&mut self, locale: LocalePreferences) { - self.locale_preferences = locale - } - fn as_unicode(&self) -> unicode::Unicode { let mut exts = unicode::Unicode::new(); diff --git a/core/engine/src/builtins/intl/date_time_format/mod.rs b/core/engine/src/builtins/intl/date_time_format/mod.rs index 81a2cdbbba5..3d1b37680ba 100644 --- a/core/engine/src/builtins/intl/date_time_format/mod.rs +++ b/core/engine/src/builtins/intl/date_time_format/mod.rs @@ -18,15 +18,12 @@ use crate::{ intl::{ Service, date_time_format::options::{DateStyle, FormatMatcher, FormatOptions, TimeStyle}, - locale::{canonicalize_locale_list, resolve_locale, validate_extension}, + locale::{canonicalize_locale_list, resolve_locale}, options::{IntlOptions, coerce_options_to_object}, }, options::get_option, }, - context::{ - icu::IntlProvider, - intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, - }, + context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, error::JsNativeError, js_error, js_string, object::{ @@ -52,10 +49,7 @@ use icu_datetime::{ }; use icu_decimal::preferences::NumberingSystem; use icu_decimal::provider::DecimalSymbolsV1; -use icu_locale::{ - Locale, extensions::unicode::Value, extensions_unicode_key as key, preferences::PreferenceKey, -}; -use icu_provider::DataMarkerAttributes; +use icu_locale::{Locale, extensions::unicode::Value}; use icu_time::{ TimeZoneInfo, ZonedDateTime, zone::{IanaParser, models::Base}, @@ -97,100 +91,6 @@ impl Service for DateTimeFormat { type LangMarker = DecimalSymbolsV1; type Preferences = DateTimeFormatterPreferences; - - fn resolve(locale: &mut Locale, options: &mut Self::Preferences, provider: &IntlProvider) { - let locale_preferences = DateTimeFormatterPreferences::from(&*locale); - // TODO: Determine if any locale_preferences processing is needed here. - - options.locale_preferences = (&*locale).into(); - - // The below handles the [[RelevantExtensionKeys]] of DateTimeFormatters - // internal slots. - // - // See https://tc39.es/ecma402/#sec-intl.datetimeformat-internal-slots - - // Handle LDML unicode key "ca", Calendar algorithm - options.calendar_algorithm = options - .calendar_algorithm - .take() - .filter(|ca| { - let attr = DataMarkerAttributes::from_str_or_panic(ca.as_str()); - validate_extension::(locale.id.clone(), attr, provider) - }) - .inspect(|ca| { - if Some(ca) == locale_preferences.calendar_algorithm.as_ref() - && let Some(ca) = ca.unicode_extension_value() - { - locale.extensions.unicode.keywords.set(key!("ca"), ca); - } - }) - .or_else(|| { - if let Some(ca) = locale_preferences - .calendar_algorithm - .as_ref() - .and_then(CalendarAlgorithm::unicode_extension_value) - { - locale.extensions.unicode.keywords.set(key!("ca"), ca); - } - locale_preferences.calendar_algorithm - }); - - // Handle LDML unicode key "nu", Numbering system - options.numbering_system = options - .numbering_system - .take() - .filter(|nu| { - let attr = DataMarkerAttributes::from_str_or_panic(nu.as_str()); - validate_extension::(locale.id.clone(), attr, provider) - }) - .inspect(|nu| { - if Some(nu) == locale_preferences.numbering_system.as_ref() - && let Some(nu) = nu.unicode_extension_value() - { - locale.extensions.unicode.keywords.set(key!("nu"), nu); - } - }) - .or_else(|| { - if let Some(nu) = locale_preferences - .numbering_system - .as_ref() - .and_then(NumberingSystem::unicode_extension_value) - { - locale.extensions.unicode.keywords.set(key!("nu"), nu); - } - locale_preferences.numbering_system - }); - - // NOTE (nekevss): issue: this will not support `H24` as ICU4X does - // not currently support it. - // - // track: https://github.com/unicode-org/icu4x/issues/6597 - // Handle LDML unicode key "hc", Hour cycle - options.hour_cycle = options - .hour_cycle - .take() - .filter(|hc| { - let attr = DataMarkerAttributes::from_str_or_panic(hc.as_str()); - validate_extension::(locale.id.clone(), attr, provider) - }) - .inspect(|hc| { - if Some(hc) == locale_preferences.hour_cycle.as_ref() - && let Some(hc) = hc.unicode_extension_value() - { - locale.extensions.unicode.keywords.set(key!("hc"), hc); - } - }) - .or_else(|| { - if let Some(hc) = locale_preferences - .hour_cycle - .as_ref() - .and_then(IcuHourCycle::unicode_extension_value) - { - locale.extensions.unicode.keywords.set(key!("hc"), hc); - } - locale_preferences.hour_cycle - }); - } } impl IntrinsicObject for DateTimeFormat { diff --git a/core/engine/src/builtins/intl/date_time_format/options.rs b/core/engine/src/builtins/intl/date_time_format/options.rs index 9394394c209..1a3704fbf8b 100644 --- a/core/engine/src/builtins/intl/date_time_format/options.rs +++ b/core/engine/src/builtins/intl/date_time_format/options.rs @@ -3,18 +3,30 @@ use crate::{ Context, JsError, JsNativeError, JsObject, JsResult, JsValue, builtins::{ - intl::{date_time_format::FormatType, options::get_number_option}, + intl::{ + ServicePreferences, date_time_format::FormatType, locale::validate_extension, + options::get_number_option, + }, options::{OptionType, get_option}, }, + context::icu::IntlProvider, js_error, js_string, }; use icu_datetime::{ + DateTimeFormatterPreferences, fieldsets::builder::{DateFields, ZoneStyle}, options::{Length, SubsecondDigits as IcuSubsecondDigits, TimePrecision}, preferences::{CalendarAlgorithm, HourCycle as IcuHourCycle}, }; -use icu_locale::extensions::unicode::Value; +use icu_decimal::provider::DecimalSymbolsV1; +use icu_locale::{extensions::unicode::Value, preferences::PreferenceKey}; +use icu_provider::{ + DataMarkerAttributes, + prelude::icu_locale_core::{ + LanguageIdentifier, extensions::unicode, preferences::LocalePreferences, + }, +}; pub(crate) enum HourCycle { H11, @@ -567,3 +579,73 @@ impl TimeZoneName { } } } + +// The below handles the [[RelevantExtensionKeys]] of DateTimeFormatters +// internal slots. +// +// See https://tc39.es/ecma402/#sec-intl.datetimeformat-internal-slots +impl ServicePreferences for DateTimeFormatterPreferences { + fn validate_extensions(&mut self, id: &LanguageIdentifier, provider: &IntlProvider) { + // Handle LDML unicode key "nu", Numbering system + self.numbering_system = self.numbering_system.take().filter(|nu| { + let attr = DataMarkerAttributes::from_str_or_panic(nu.as_str()); + validate_extension::(id, attr, provider) + }); + + // Handle LDML unicode key "ca", Calendar algorithm + // TODO: determine the correct way to verify the calendar algorithm data. + + // NOTE (nekevss): issue: this will not support `H24` as ICU4X does + // not currently support it. + // + // track: https://github.com/unicode-org/icu4x/issues/6597 + // Handle LDML unicode key "hc", Hour cycle + // No need to validate hour_cycle since it only affects formatting + // behaviour. + } + + fn as_unicode(&self) -> unicode::Unicode { + let mut exts = unicode::Unicode::new(); + + if let Some(nu) = self.numbering_system + && let Some(value) = nu.unicode_extension_value() + { + exts.keywords.set(unicode::key!("nu"), value); + } + + if let Some(ca) = self.calendar_algorithm + && let Some(value) = ca.unicode_extension_value() + { + exts.keywords.set(unicode::key!("ca"), value); + } + + if let Some(hc) = self.hour_cycle + && let Some(value) = hc.unicode_extension_value() + { + exts.keywords.set(unicode::key!("hc"), value); + } + + exts + } + + fn extend(&mut self, other: &Self) { + self.extend(*other); + } + + fn intersection(&self, other: &Self) -> Self { + let mut inter = self.clone(); + if inter.locale_preferences != other.locale_preferences { + inter.locale_preferences = LocalePreferences::default() + } + if inter.numbering_system != other.numbering_system { + inter.numbering_system.take(); + } + if inter.calendar_algorithm != other.calendar_algorithm { + inter.calendar_algorithm.take(); + } + if inter.hour_cycle != other.hour_cycle { + inter.hour_cycle.take(); + } + inter + } +} diff --git a/core/engine/src/builtins/intl/list_format/mod.rs b/core/engine/src/builtins/intl/list_format/mod.rs index 0faef7169ea..cd32657e56c 100644 --- a/core/engine/src/builtins/intl/list_format/mod.rs +++ b/core/engine/src/builtins/intl/list_format/mod.rs @@ -11,7 +11,10 @@ use icu_locale::Locale; use crate::{ Context, JsArgs, JsData, JsNativeError, JsResult, JsString, JsValue, builtins::{ - Array, BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject, OrdinaryObject, intl::options::EmptyPreferences, iterable::IteratorHint, options::{get_option, get_options_object} + Array, BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject, OrdinaryObject, + intl::options::EmptyPreferences, + iterable::IteratorHint, + options::{get_option, get_options_object}, }, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, js_string, diff --git a/core/engine/src/builtins/intl/locale/tests.rs b/core/engine/src/builtins/intl/locale/tests.rs index 6cdd121ec80..2ca5ff59f5c 100644 --- a/core/engine/src/builtins/intl/locale/tests.rs +++ b/core/engine/src/builtins/intl/locale/tests.rs @@ -1,60 +1,72 @@ -use icu_decimal::provider::DecimalSymbolsV1; use icu_locale::{ - Locale, extensions::unicode::Value, extensions_unicode_key as key, - extensions_unicode_value as value, locale, + Locale, extensions_unicode_key as key, extensions_unicode_value as value, locale, preferences::extensions::unicode::keywords::NumberingSystem, }; use icu_plurals::provider::PluralsCardinalV1; use icu_provider::{ - DataIdentifierBorrowed, DataLocale, DataProvider, DataRequest, DataRequestMetadata, - DryDataProvider, + DataIdentifierBorrowed, DataLocale, DataRequest, DataRequestMetadata, DryDataProvider, + prelude::icu_locale_core::{LanguageIdentifier, extensions::unicode}, }; use crate::{ builtins::intl::{ - Service, + Service, ServicePreferences, locale::{default_locale, resolve_locale}, options::{IntlOptions, LocaleMatcher}, }, context::icu::IntlProvider, }; -#[derive(Debug)] -struct TestOptions { +#[derive(Debug, Clone)] +struct TestPreferences { nu: Option, } +impl From<&Locale> for TestPreferences { + fn from(value: &Locale) -> Self { + Self { + nu: value + .extensions + .unicode + .keywords + .get(&unicode::key!("nu")) + .and_then(|nu| NumberingSystem::try_from(nu.clone()).ok()), + } + } +} + +impl ServicePreferences for TestPreferences { + fn validate_extensions(&mut self, _id: &LanguageIdentifier, _provider: &IntlProvider) {} + + fn as_unicode(&self) -> unicode::Unicode { + let mut exts = unicode::Unicode::new(); + if let Some(nu) = self.nu { + exts.keywords.set(unicode::key!("nu"), nu.into()); + } + exts + } + + fn extend(&mut self, other: &Self) { + if self.nu.is_none() { + self.nu = other.nu + } + } + + fn intersection(&self, other: &Self) -> Self { + let mut inter = self.clone(); + if inter.nu != other.nu { + inter.nu.take(); + } + inter + } +} + struct TestService; impl Service for TestService { type LangMarker = PluralsCardinalV1; - type Preferences = TestOptions; - - fn resolve(locale: &mut Locale, options: &mut Self::Preferences, provider: &IntlProvider) { - let loc_hc = locale - .extensions - .unicode - .keywords - .get(&key!("nu")) - .and_then(|v| NumberingSystem::try_from(v.clone()).ok()); - let nu = options.nu.or(loc_hc).unwrap_or_else(|| { - let locale = &DataLocale::from(&*locale); - let req = DataRequest { - id: DataIdentifierBorrowed::for_locale(locale), - metadata: DataRequestMetadata::default(), - }; - let data = DataProvider::::load(provider, req).unwrap(); - let preferred = data.payload.get().numsys(); - NumberingSystem::try_from(Value::try_from_str(preferred).unwrap()).unwrap() - }); - locale - .extensions - .unicode - .keywords - .set(key!("nu"), nu.into()); - options.nu = Some(nu); - } + type Preferences = TestPreferences; } #[test] @@ -85,7 +97,7 @@ fn locale_resolution() { // test lookup let mut options = IntlOptions { matcher: LocaleMatcher::Lookup, - service_options: TestOptions { + service_options: TestPreferences { nu: Some(NumberingSystem::try_from(value!("latn")).unwrap()), }, }; @@ -95,7 +107,7 @@ fn locale_resolution() { // test best fit let mut options = IntlOptions { matcher: LocaleMatcher::BestFit, - service_options: TestOptions { + service_options: TestPreferences { nu: Some(NumberingSystem::try_from(value!("latn")).unwrap()), }, }; @@ -106,7 +118,7 @@ fn locale_resolution() { // requested: [es-ES] let mut options = IntlOptions { matcher: LocaleMatcher::Lookup, - service_options: TestOptions { nu: None }, + service_options: TestPreferences { nu: None }, }; let locale = diff --git a/core/engine/src/builtins/intl/mod.rs b/core/engine/src/builtins/intl/mod.rs index 09dcc25c13e..39e2d6b5681 100644 --- a/core/engine/src/builtins/intl/mod.rs +++ b/core/engine/src/builtins/intl/mod.rs @@ -26,8 +26,8 @@ use crate::{ }; use boa_gc::{Finalize, Trace}; -use icu_locale::{LanguageIdentifier, extensions::unicode, preferences::LocalePreferences}; -use icu_provider::{DataMarker, DataMarkerAttributes, DryDataProvider}; +use icu_locale::{LanguageIdentifier, extensions::unicode}; +use icu_provider::{DataMarker, DataMarkerAttributes}; use static_assertions::const_assert; pub(crate) mod collator; @@ -180,12 +180,7 @@ impl Intl { } trait ServicePreferences: for<'a> From<&'a icu_locale::Locale> { - fn validate_extensions( - &mut self, - id: &LanguageIdentifier, - provider: &impl DryDataProvider, - ); - fn set_locale(&mut self, locale: LocalePreferences); + fn validate_extensions(&mut self, id: &LanguageIdentifier, provider: &IntlProvider); fn as_unicode(&self) -> unicode::Unicode; fn extend(&mut self, other: &Self); fn intersection(&self, other: &Self) -> Self; @@ -206,22 +201,4 @@ trait Service { /// The set of preferences used in the [`Service::resolve`] method to resolve the provided /// locale. type Preferences: ServicePreferences; - - /// Resolves the final value of `locale` from a set of `options`. - /// - /// The provided `options` will also be modified with the final values, in case there were - /// changes in the resolution algorithm. - /// - /// # Note - /// - /// - A correct implementation must ensure `locale` and `options` are both written with the - /// new final values. - /// - If the implementor service doesn't contain any `[[RelevantExtensionKeys]]`, this can be - /// skipped. - fn resolve( - _locale: &mut icu_locale::Locale, - _options: &mut Self::Preferences, - _provider: &IntlProvider, - ) { - } } diff --git a/core/engine/src/builtins/intl/number_format/mod.rs b/core/engine/src/builtins/intl/number_format/mod.rs index 92ddec2f3b1..4c6f4a83c79 100644 --- a/core/engine/src/builtins/intl/number_format/mod.rs +++ b/core/engine/src/builtins/intl/number_format/mod.rs @@ -78,11 +78,6 @@ impl NumberFormat { } } -#[derive(Debug, Clone)] -pub(super) struct NumberFormatLocaleOptions { - numbering_system: Option, -} - impl Service for NumberFormat { type LangMarker = DecimalSymbolsV1; diff --git a/core/engine/src/builtins/intl/number_format/options.rs b/core/engine/src/builtins/intl/number_format/options.rs index 51c9ff697c4..f364ae95d85 100644 --- a/core/engine/src/builtins/intl/number_format/options.rs +++ b/core/engine/src/builtins/intl/number_format/options.rs @@ -6,7 +6,9 @@ use fixed_decimal::{ }; use boa_macros::js_str; -use icu_decimal::{DecimalFormatterPreferences, preferences::NumberingSystem}; +use icu_decimal::{ + DecimalFormatterPreferences, preferences::NumberingSystem, provider::DecimalSymbolsV1, +}; use icu_locale::{extensions::unicode::Value, preferences::PreferenceKey}; use icu_provider::{ DataMarkerAttributes, @@ -26,6 +28,7 @@ use crate::{ }, options::{OptionType, ParsableOptionType, get_option}, }, + context::icu::IntlProvider, js_string, }; @@ -1265,21 +1268,13 @@ impl RoundingType { } impl ServicePreferences for DecimalFormatterPreferences { - fn validate_extensions( - &mut self, - id: &LanguageIdentifier, - provider: &impl icu_provider::DryDataProvider, - ) { + fn validate_extensions(&mut self, id: &LanguageIdentifier, provider: &IntlProvider) { self.numbering_system = self.numbering_system.take().filter(|nu| { let attr = DataMarkerAttributes::from_str_or_panic(nu.as_str()); - validate_extension::(id, attr, provider) + validate_extension::(id, attr, provider) }); } - fn set_locale(&mut self, locale: LocalePreferences) { - self.locale_preferences = locale - } - fn as_unicode(&self) -> unicode::Unicode { let mut exts = unicode::Unicode::new(); diff --git a/core/engine/src/builtins/intl/options.rs b/core/engine/src/builtins/intl/options.rs index e11ec9022fd..fc4e10cc841 100644 --- a/core/engine/src/builtins/intl/options.rs +++ b/core/engine/src/builtins/intl/options.rs @@ -1,12 +1,12 @@ use std::{fmt, str::FromStr}; -use icu_locale::{LanguageIdentifier, extensions::unicode, preferences::LocalePreferences}; -use icu_provider::{DataMarker, DryDataProvider}; +use icu_locale::{LanguageIdentifier, extensions::unicode}; use num_traits::FromPrimitive; use crate::{ Context, JsNativeError, JsResult, JsString, JsValue, builtins::{OrdinaryObject, intl::ServicePreferences, options::ParsableOptionType}, + context::icu::IntlProvider, object::JsObject, }; @@ -60,13 +60,7 @@ impl From<&icu_locale::Locale> for EmptyPreferences { } impl ServicePreferences for EmptyPreferences { - fn validate_extensions( - &mut self, - _: &LanguageIdentifier, - _: &impl DryDataProvider, - ) { - } - fn set_locale(&mut self, _: LocalePreferences) {} + fn validate_extensions(&mut self, _: &LanguageIdentifier, _: &IntlProvider) {} fn as_unicode(&self) -> unicode::Unicode { unicode::Unicode::new() } diff --git a/core/engine/src/builtins/intl/plural_rules/mod.rs b/core/engine/src/builtins/intl/plural_rules/mod.rs index bd3ec42c3c2..01e3b5fd0fc 100644 --- a/core/engine/src/builtins/intl/plural_rules/mod.rs +++ b/core/engine/src/builtins/intl/plural_rules/mod.rs @@ -11,7 +11,8 @@ use icu_plurals::{ use crate::{ Context, JsArgs, JsData, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue, builtins::{ - Array, BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject, intl::options::EmptyPreferences, options::get_option + Array, BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject, + intl::options::EmptyPreferences, options::get_option, }, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, js_string, From d5614070f58c76c379580fde57a9b87be8784090 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juli=C3=A1n=20Espina=20Del=20=C3=81ngel?= Date: Thu, 22 Jan 2026 23:16:01 -0600 Subject: [PATCH 3/6] cargo clippy --- core/engine/src/builtins/intl/collator/options.rs | 4 ++-- core/engine/src/builtins/intl/date_time_format/options.rs | 4 ++-- core/engine/src/builtins/intl/locale/tests.rs | 2 +- core/engine/src/builtins/intl/number_format/options.rs | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/core/engine/src/builtins/intl/collator/options.rs b/core/engine/src/builtins/intl/collator/options.rs index 0cfbfd02f85..820771b83c7 100644 --- a/core/engine/src/builtins/intl/collator/options.rs +++ b/core/engine/src/builtins/intl/collator/options.rs @@ -147,9 +147,9 @@ impl ServicePreferences for CollatorPreferences { } fn intersection(&self, other: &Self) -> Self { - let mut inter = self.clone(); + let mut inter = *self; if inter.locale_preferences != other.locale_preferences { - inter.locale_preferences = LocalePreferences::default() + inter.locale_preferences = LocalePreferences::default(); } if inter.collation_type != other.collation_type { inter.collation_type.take(); diff --git a/core/engine/src/builtins/intl/date_time_format/options.rs b/core/engine/src/builtins/intl/date_time_format/options.rs index 1a3704fbf8b..73b5e40ee3a 100644 --- a/core/engine/src/builtins/intl/date_time_format/options.rs +++ b/core/engine/src/builtins/intl/date_time_format/options.rs @@ -633,9 +633,9 @@ impl ServicePreferences for DateTimeFormatterPreferences { } fn intersection(&self, other: &Self) -> Self { - let mut inter = self.clone(); + let mut inter = *self; if inter.locale_preferences != other.locale_preferences { - inter.locale_preferences = LocalePreferences::default() + inter.locale_preferences = LocalePreferences::default(); } if inter.numbering_system != other.numbering_system { inter.numbering_system.take(); diff --git a/core/engine/src/builtins/intl/locale/tests.rs b/core/engine/src/builtins/intl/locale/tests.rs index 2ca5ff59f5c..d91bae66132 100644 --- a/core/engine/src/builtins/intl/locale/tests.rs +++ b/core/engine/src/builtins/intl/locale/tests.rs @@ -48,7 +48,7 @@ impl ServicePreferences for TestPreferences { fn extend(&mut self, other: &Self) { if self.nu.is_none() { - self.nu = other.nu + self.nu = other.nu; } } diff --git a/core/engine/src/builtins/intl/number_format/options.rs b/core/engine/src/builtins/intl/number_format/options.rs index f364ae95d85..1e7a3f1e517 100644 --- a/core/engine/src/builtins/intl/number_format/options.rs +++ b/core/engine/src/builtins/intl/number_format/options.rs @@ -1291,9 +1291,9 @@ impl ServicePreferences for DecimalFormatterPreferences { } fn intersection(&self, other: &Self) -> Self { - let mut inter = self.clone(); + let mut inter = *self; if inter.locale_preferences != other.locale_preferences { - inter.locale_preferences = LocalePreferences::default() + inter.locale_preferences = LocalePreferences::default(); } if inter.numbering_system != other.numbering_system { inter.numbering_system.take(); From 26b3c394c5a255966dfa6c52fba41479a37b6026 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Juli=C3=A1n=20Espina?= Date: Fri, 23 Jan 2026 13:27:07 -0600 Subject: [PATCH 4/6] Fix regressions --- core/engine/src/builtins/intl/collator/mod.rs | 12 ++++----- .../src/builtins/intl/date_time_format/mod.rs | 17 ++++++------ core/engine/src/builtins/intl/locale/tests.rs | 27 +++++++++++++++---- core/engine/src/builtins/intl/locale/utils.rs | 14 +++++----- core/engine/src/builtins/intl/mod.rs | 2 +- .../src/builtins/intl/number_format/mod.rs | 10 ++++--- core/engine/src/builtins/intl/options.rs | 2 +- 7 files changed, 52 insertions(+), 32 deletions(-) diff --git a/core/engine/src/builtins/intl/collator/mod.rs b/core/engine/src/builtins/intl/collator/mod.rs index 18939b54008..f782ae817b5 100644 --- a/core/engine/src/builtins/intl/collator/mod.rs +++ b/core/engine/src/builtins/intl/collator/mod.rs @@ -190,7 +190,7 @@ impl BuiltInConstructor for Collator { let mut intl_options = IntlOptions { matcher, - service_options: { + preferences: { let mut prefs = CollatorPreferences::default(); prefs.collation_type = collation; prefs.numeric_ordering = numeric.map(|kn| { @@ -217,16 +217,16 @@ impl BuiltInConstructor for Collator { // 21. Let collation be r.[[co]]. // 22. If collation is null, let collation be "default". // 23. Set collator.[[Collation]] to collation. - let collation = intl_options.service_options.collation_type; + let collation = intl_options.preferences.collation_type; // 24. If relevantExtensionKeys contains "kn", then // a. Set collator.[[Numeric]] to SameValue(r.[[kn]], "true"). let numeric = - intl_options.service_options.numeric_ordering == Some(CollationNumericOrdering::True); + intl_options.preferences.numeric_ordering == Some(CollationNumericOrdering::True); // 25. If relevantExtensionKeys contains "kf", then // a. Set collator.[[CaseFirst]] to r.[[kf]]. - let case_first = intl_options.service_options.case_first; + let case_first = intl_options.preferences.case_first; // 26. Let sensitivity be ? GetOption(options, "sensitivity", string, « "base", "accent", "case", "variant" », undefined). // 28. Set collator.[[Sensitivity]] to sensitivity. @@ -259,12 +259,12 @@ impl BuiltInConstructor for Collator { options.max_variable = max_variable; if usage == Usage::Search { - intl_options.service_options.collation_type = Some(CollationType::Search); + intl_options.preferences.collation_type = Some(CollationType::Search); } let collator = icu_collator::Collator::try_new_with_buffer_provider( context.intl_provider().erased_provider(), - intl_options.service_options, + intl_options.preferences, options, ) .map_err(|e| JsNativeError::typ().with_message(e.to_string()))?; diff --git a/core/engine/src/builtins/intl/date_time_format/mod.rs b/core/engine/src/builtins/intl/date_time_format/mod.rs index 3d1b37680ba..733c4bd9011 100644 --- a/core/engine/src/builtins/intl/date_time_format/mod.rs +++ b/core/engine/src/builtins/intl/date_time_format/mod.rs @@ -270,7 +270,7 @@ impl DateTimeFormat { ) .map_err(|e| { JsNativeError::range() - .with_message(format!("Failed to load formatter: {e}")) + .with_message(format!("failed to load formatter: {e}")) })?; let dt = fields.to_formattable_datetime(); @@ -408,7 +408,7 @@ fn create_date_time_format( // NOTE: We unroll the below const loop in step 6 using the // ResolutionOptionDescriptors from the internal slots // https://tc39.es/ecma402/#sec-intl.datetimeformat-internal-slots - let mut service_options = DateTimeFormatterPreferences::default(); + let mut preferences = DateTimeFormatterPreferences::default(); // 6. For each Resolution Option Descriptor desc of constructor.[[ResolutionOptionDescriptors]], do // a. If desc has a [[Type]] field, let type be desc.[[Type]]. Otherwise, let type be string. @@ -421,14 +421,14 @@ fn create_date_time_format( // f. Set opt.[[]] to value. // Handle { [[Key]]: "ca", [[Property]]: "calendar" } - service_options.calendar_algorithm = + preferences.calendar_algorithm = get_option::(&options, js_string!("calendar"), context)? .map(|ca| CalendarAlgorithm::try_from(&ca)) .transpose() .map_err(|_icu4x_error| js_error!(RangeError: "unknown calendar algorithm"))?; // { [[Key]]: "nu", [[Property]]: "numberingSystem" } - service_options.numbering_system = + preferences.numbering_system = get_option::(&options, js_string!("numberingSystem"), context)? .map(NumberingSystem::try_from) .transpose() @@ -438,7 +438,7 @@ fn create_date_time_format( let hour_12 = get_option::(&options, js_string!("hour12"), context)?; // { [[Key]]: "hc", [[Property]]: "hourCycle", [[Values]]: « "h11", "h12", "h23", "h24" » } - service_options.hour_cycle = + preferences.hour_cycle = get_option::(&options, js_string!("hourCycle"), context)? .map(|hc| { // Handle steps 3.a-c here @@ -455,7 +455,7 @@ fn create_date_time_format( let mut intl_options = IntlOptions { matcher, - service_options, + preferences, }; // ResolveOptions 8. Let resolution be ResolveLocale(constructor.[[AvailableLocales]], requestedLocales, @@ -546,8 +546,7 @@ fn create_date_time_format( // d. Set formatOptions.[[]] to value. // e. If value is not undefined, then // i. Set hasExplicitFormatComponents to true. - let mut format_options = - FormatOptions::try_init(&options, service_options.hour_cycle, context)?; + let mut format_options = FormatOptions::try_init(&options, preferences.hour_cycle, context)?; // TODO: how should formatMatcher be used? // 25. Let formatMatcher be ? GetOption(options, "formatMatcher", string, « "basic", "best fit" », "best fit"). @@ -631,7 +630,7 @@ fn create_date_time_format( prototype, DateTimeFormat { locale: resolved_locale, - _calendar_algorithm: intl_options.service_options.calendar_algorithm, + _calendar_algorithm: intl_options.preferences.calendar_algorithm, time_zone, fieldset, bound_format: None, diff --git a/core/engine/src/builtins/intl/locale/tests.rs b/core/engine/src/builtins/intl/locale/tests.rs index d91bae66132..5be15d9d400 100644 --- a/core/engine/src/builtins/intl/locale/tests.rs +++ b/core/engine/src/builtins/intl/locale/tests.rs @@ -1,10 +1,12 @@ +use icu_decimal::provider::DecimalSymbolsV1; use icu_locale::{ Locale, extensions_unicode_key as key, extensions_unicode_value as value, locale, preferences::extensions::unicode::keywords::NumberingSystem, }; use icu_plurals::provider::PluralsCardinalV1; use icu_provider::{ - DataIdentifierBorrowed, DataLocale, DataRequest, DataRequestMetadata, DryDataProvider, + DataIdentifierBorrowed, DataLocale, DataProvider, DataRequest, DataRequestMetadata, + DryDataProvider, prelude::icu_locale_core::{LanguageIdentifier, extensions::unicode}, }; @@ -36,7 +38,22 @@ impl From<&Locale> for TestPreferences { } impl ServicePreferences for TestPreferences { - fn validate_extensions(&mut self, _id: &LanguageIdentifier, _provider: &IntlProvider) {} + fn validate_extensions(&mut self, id: &LanguageIdentifier, provider: &IntlProvider) { + if self.nu.is_some() { + return; + } + + let locale = &DataLocale::from(id); + let req = DataRequest { + id: DataIdentifierBorrowed::for_locale(locale), + metadata: DataRequestMetadata::default(), + }; + let data = DataProvider::::load(provider, req).unwrap(); + let preferred = data.payload.get().numsys(); + self.nu = Some( + NumberingSystem::try_from(unicode::Value::try_from_str(preferred).unwrap()).unwrap(), + ); + } fn as_unicode(&self) -> unicode::Unicode { let mut exts = unicode::Unicode::new(); @@ -97,7 +114,7 @@ fn locale_resolution() { // test lookup let mut options = IntlOptions { matcher: LocaleMatcher::Lookup, - service_options: TestPreferences { + preferences: TestPreferences { nu: Some(NumberingSystem::try_from(value!("latn")).unwrap()), }, }; @@ -107,7 +124,7 @@ fn locale_resolution() { // test best fit let mut options = IntlOptions { matcher: LocaleMatcher::BestFit, - service_options: TestPreferences { + preferences: TestPreferences { nu: Some(NumberingSystem::try_from(value!("latn")).unwrap()), }, }; @@ -118,7 +135,7 @@ fn locale_resolution() { // requested: [es-ES] let mut options = IntlOptions { matcher: LocaleMatcher::Lookup, - service_options: TestPreferences { nu: None }, + preferences: TestPreferences { nu: None }, }; let locale = diff --git a/core/engine/src/builtins/intl/locale/utils.rs b/core/engine/src/builtins/intl/locale/utils.rs index fa5c0fbb2cf..f98f55825f8 100644 --- a/core/engine/src/builtins/intl/locale/utils.rs +++ b/core/engine/src/builtins/intl/locale/utils.rs @@ -401,19 +401,19 @@ where provider .locale_canonicalizer()? .canonicalize(&mut found_locale); - let mut locale_prefs: S::Preferences = S::Preferences::from(&found_locale); + let mut locale_prefs = S::Preferences::from(&found_locale); options - .service_options + .preferences .validate_extensions(&found_locale.id, provider); locale_prefs.validate_extensions(&found_locale.id, provider); + let mut prefs = locale_prefs.clone(); + // This also sets the locale to the found locale. - options.service_options.extend(&locale_prefs); - found_locale.extensions.unicode = options - .service_options - .intersection(&locale_prefs) - .as_unicode(); + prefs.extend(&options.preferences); + found_locale.extensions.unicode = prefs.intersection(&locale_prefs).as_unicode(); + options.preferences = prefs; // 12. Return result. Ok(found_locale) diff --git a/core/engine/src/builtins/intl/mod.rs b/core/engine/src/builtins/intl/mod.rs index 39e2d6b5681..6403f41b2cf 100644 --- a/core/engine/src/builtins/intl/mod.rs +++ b/core/engine/src/builtins/intl/mod.rs @@ -179,7 +179,7 @@ impl Intl { } } -trait ServicePreferences: for<'a> From<&'a icu_locale::Locale> { +trait ServicePreferences: for<'a> From<&'a icu_locale::Locale> + Clone { fn validate_extensions(&mut self, id: &LanguageIdentifier, provider: &IntlProvider); fn as_unicode(&self) -> unicode::Unicode; fn extend(&mut self, other: &Self); diff --git a/core/engine/src/builtins/intl/number_format/mod.rs b/core/engine/src/builtins/intl/number_format/mod.rs index 4c6f4a83c79..0449790d7be 100644 --- a/core/engine/src/builtins/intl/number_format/mod.rs +++ b/core/engine/src/builtins/intl/number_format/mod.rs @@ -253,7 +253,7 @@ impl NumberFormat { let mut intl_options = IntlOptions { matcher, - service_options: { + preferences: { let mut prefs = DecimalFormatterPreferences::default(); prefs.numbering_system = numbering_system; prefs @@ -418,7 +418,7 @@ impl NumberFormat { nu: Cell::new(None), }; let formatter = DecimalFormatter::try_new_with_buffer_provider( - context.intl_provider().erased_provider(), + &inspector, (&locale).into(), options, ) @@ -429,7 +429,11 @@ impl NumberFormat { let nu = Value::try_from_str(&nu).ok()?; NumberingSystem::try_from(nu).ok() })() - .ok_or_else(|| js_error!(TypeError: "invalid numbering system from Intl provider"))?; + .ok_or_else(|| { + js_error!( + TypeError: "could not obtain resolved numbering system from Intl provider" + ) + })?; (formatter, nu) }; diff --git a/core/engine/src/builtins/intl/options.rs b/core/engine/src/builtins/intl/options.rs index fc4e10cc841..9bfbcd1c3f6 100644 --- a/core/engine/src/builtins/intl/options.rs +++ b/core/engine/src/builtins/intl/options.rs @@ -17,7 +17,7 @@ use crate::{ #[derive(Debug, Default)] pub(super) struct IntlOptions { pub(super) matcher: LocaleMatcher, - pub(super) service_options: O, + pub(super) preferences: O, } #[derive(Clone, Copy, Debug, PartialEq, Eq, Default)] From 23bdf34fad0bf0c8ed239e3f99873515fb5ea952 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Juli=C3=A1n=20Espina?= Date: Fri, 23 Jan 2026 14:18:32 -0600 Subject: [PATCH 5/6] Slightly change trait definition --- .../src/builtins/intl/collator/options.rs | 8 ++++--- .../builtins/intl/date_time_format/options.rs | 8 ++++--- core/engine/src/builtins/intl/locale/tests.rs | 14 ++++++----- core/engine/src/builtins/intl/locale/utils.rs | 11 ++++----- core/engine/src/builtins/intl/mod.rs | 24 +++++++++++++------ .../builtins/intl/number_format/options.rs | 8 ++++--- core/engine/src/builtins/intl/options.rs | 6 +++-- 7 files changed, 48 insertions(+), 31 deletions(-) diff --git a/core/engine/src/builtins/intl/collator/options.rs b/core/engine/src/builtins/intl/collator/options.rs index 820771b83c7..b725e61ec87 100644 --- a/core/engine/src/builtins/intl/collator/options.rs +++ b/core/engine/src/builtins/intl/collator/options.rs @@ -110,7 +110,7 @@ impl OptionType for CollationCaseFirst { } impl ServicePreferences for CollatorPreferences { - fn validate_extensions(&mut self, id: &LanguageIdentifier, provider: &IntlProvider) { + fn validate(&mut self, id: &LanguageIdentifier, provider: &IntlProvider) { self.collation_type = self.collation_type.take().filter(|co| { let attr = DataMarkerAttributes::from_str_or_panic(co.as_str()); co != &CollationType::Search @@ -142,8 +142,10 @@ impl ServicePreferences for CollatorPreferences { exts } - fn extend(&mut self, other: &Self) { - self.extend(*other); + fn extended(&self, other: &Self) -> Self { + let mut result = *self; + result.extend(*other); + result } fn intersection(&self, other: &Self) -> Self { diff --git a/core/engine/src/builtins/intl/date_time_format/options.rs b/core/engine/src/builtins/intl/date_time_format/options.rs index 73b5e40ee3a..802917b0f04 100644 --- a/core/engine/src/builtins/intl/date_time_format/options.rs +++ b/core/engine/src/builtins/intl/date_time_format/options.rs @@ -585,7 +585,7 @@ impl TimeZoneName { // // See https://tc39.es/ecma402/#sec-intl.datetimeformat-internal-slots impl ServicePreferences for DateTimeFormatterPreferences { - fn validate_extensions(&mut self, id: &LanguageIdentifier, provider: &IntlProvider) { + fn validate(&mut self, id: &LanguageIdentifier, provider: &IntlProvider) { // Handle LDML unicode key "nu", Numbering system self.numbering_system = self.numbering_system.take().filter(|nu| { let attr = DataMarkerAttributes::from_str_or_panic(nu.as_str()); @@ -628,8 +628,10 @@ impl ServicePreferences for DateTimeFormatterPreferences { exts } - fn extend(&mut self, other: &Self) { - self.extend(*other); + fn extended(&self, other: &Self) -> Self { + let mut result = *self; + result.extend(*other); + result } fn intersection(&self, other: &Self) -> Self { diff --git a/core/engine/src/builtins/intl/locale/tests.rs b/core/engine/src/builtins/intl/locale/tests.rs index 5be15d9d400..22606661165 100644 --- a/core/engine/src/builtins/intl/locale/tests.rs +++ b/core/engine/src/builtins/intl/locale/tests.rs @@ -19,7 +19,7 @@ use crate::{ context::icu::IntlProvider, }; -#[derive(Debug, Clone)] +#[derive(Debug, Copy, Clone)] struct TestPreferences { nu: Option, } @@ -38,7 +38,7 @@ impl From<&Locale> for TestPreferences { } impl ServicePreferences for TestPreferences { - fn validate_extensions(&mut self, id: &LanguageIdentifier, provider: &IntlProvider) { + fn validate(&mut self, id: &LanguageIdentifier, provider: &IntlProvider) { if self.nu.is_some() { return; } @@ -63,14 +63,16 @@ impl ServicePreferences for TestPreferences { exts } - fn extend(&mut self, other: &Self) { - if self.nu.is_none() { - self.nu = other.nu; + fn extended(&self, other: &Self) -> Self { + let mut result = *self; + if result.nu.is_none() { + result.nu = other.nu; } + result } fn intersection(&self, other: &Self) -> Self { - let mut inter = self.clone(); + let mut inter = *self; if inter.nu != other.nu { inter.nu.take(); } diff --git a/core/engine/src/builtins/intl/locale/utils.rs b/core/engine/src/builtins/intl/locale/utils.rs index f98f55825f8..7c4a4f705d6 100644 --- a/core/engine/src/builtins/intl/locale/utils.rs +++ b/core/engine/src/builtins/intl/locale/utils.rs @@ -403,15 +403,12 @@ where .canonicalize(&mut found_locale); let mut locale_prefs = S::Preferences::from(&found_locale); - options - .preferences - .validate_extensions(&found_locale.id, provider); - locale_prefs.validate_extensions(&found_locale.id, provider); + options.preferences.validate(&found_locale.id, provider); + locale_prefs.validate(&found_locale.id, provider); - let mut prefs = locale_prefs.clone(); + // This should not touch the found locale. + let prefs = locale_prefs.extended(&options.preferences); - // This also sets the locale to the found locale. - prefs.extend(&options.preferences); found_locale.extensions.unicode = prefs.intersection(&locale_prefs).as_unicode(); options.preferences = prefs; diff --git a/core/engine/src/builtins/intl/mod.rs b/core/engine/src/builtins/intl/mod.rs index 6403f41b2cf..0d9c4bfc58a 100644 --- a/core/engine/src/builtins/intl/mod.rs +++ b/core/engine/src/builtins/intl/mod.rs @@ -179,26 +179,36 @@ impl Intl { } } +/// A set of preferences that can be provided to a [`Service`] through +/// a locale. trait ServicePreferences: for<'a> From<&'a icu_locale::Locale> + Clone { - fn validate_extensions(&mut self, id: &LanguageIdentifier, provider: &IntlProvider); + /// Validates that every preference value is available. + /// + /// This usually entails having to query the `IntlProvider` to check + /// if it has the required data to support the requested values. + fn validate(&mut self, id: &LanguageIdentifier, provider: &IntlProvider); + + /// Converts this set of preferences into a Unicode locale extension. fn as_unicode(&self) -> unicode::Unicode; - fn extend(&mut self, other: &Self); + + /// Extends all values set in `self` with the values set in `other`. + fn extended(&self, other: &Self) -> Self; + + /// Gets the set of preference values that are the same in `self` and `other`. fn intersection(&self, other: &Self) -> Self; } /// A service component that is part of the `Intl` API. /// /// This needs to be implemented for every `Intl` service in order to use the functions -/// defined in `locale::utils`, such as locale resolution and selection. +/// defined in `locale::utils`, such as [`resolve_locale`][locale::resolve_locale]. trait Service { - /// The data marker used by [`resolve_locale`][locale::resolve_locale] to decide - /// which locales are supported by this service. + /// The data marker used to decide which locales are supported by this service. type LangMarker: DataMarker; /// The attributes used to resolve the locale. const ATTRIBUTES: &'static DataMarkerAttributes = DataMarkerAttributes::empty(); - /// The set of preferences used in the [`Service::resolve`] method to resolve the provided - /// locale. + /// The set of preferences used to resolve the provided locale. type Preferences: ServicePreferences; } diff --git a/core/engine/src/builtins/intl/number_format/options.rs b/core/engine/src/builtins/intl/number_format/options.rs index 1e7a3f1e517..0b58124e6bd 100644 --- a/core/engine/src/builtins/intl/number_format/options.rs +++ b/core/engine/src/builtins/intl/number_format/options.rs @@ -1268,7 +1268,7 @@ impl RoundingType { } impl ServicePreferences for DecimalFormatterPreferences { - fn validate_extensions(&mut self, id: &LanguageIdentifier, provider: &IntlProvider) { + fn validate(&mut self, id: &LanguageIdentifier, provider: &IntlProvider) { self.numbering_system = self.numbering_system.take().filter(|nu| { let attr = DataMarkerAttributes::from_str_or_panic(nu.as_str()); validate_extension::(id, attr, provider) @@ -1286,8 +1286,10 @@ impl ServicePreferences for DecimalFormatterPreferences { exts } - fn extend(&mut self, other: &Self) { - self.extend(*other); + fn extended(&self, other: &Self) -> Self { + let mut result = *self; + result.extend(*other); + result } fn intersection(&self, other: &Self) -> Self { diff --git a/core/engine/src/builtins/intl/options.rs b/core/engine/src/builtins/intl/options.rs index 9bfbcd1c3f6..52543429017 100644 --- a/core/engine/src/builtins/intl/options.rs +++ b/core/engine/src/builtins/intl/options.rs @@ -60,11 +60,13 @@ impl From<&icu_locale::Locale> for EmptyPreferences { } impl ServicePreferences for EmptyPreferences { - fn validate_extensions(&mut self, _: &LanguageIdentifier, _: &IntlProvider) {} + fn validate(&mut self, _: &LanguageIdentifier, _: &IntlProvider) {} fn as_unicode(&self) -> unicode::Unicode { unicode::Unicode::new() } - fn extend(&mut self, _: &Self) {} + fn extended(&self, _: &Self) -> Self { + Self + } fn intersection(&self, _: &Self) -> Self { Self } From 796ca0b2db66de6a77d3a6f80cdc3f9eb8854e61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Juli=C3=A1n=20Espina?= Date: Fri, 23 Jan 2026 14:39:04 -0600 Subject: [PATCH 6/6] fix typo --- core/engine/src/builtins/intl/locale/utils.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/engine/src/builtins/intl/locale/utils.rs b/core/engine/src/builtins/intl/locale/utils.rs index 7c4a4f705d6..bfd3f0e53aa 100644 --- a/core/engine/src/builtins/intl/locale/utils.rs +++ b/core/engine/src/builtins/intl/locale/utils.rs @@ -394,7 +394,7 @@ where // ensures: // - All options provided by the locale are valid. // - All options provided by args are valid. - // - Options provided by args are extended (but not overriden) by + // - Options provided by args are extended (but not overridden) by // options provided in the locale. // - Only the locale options that extended the args options are // added to the final locale.