boa_engine/builtins/intl/
mod.rs1use crate::{
17 Context, JsArgs, JsData, JsResult, JsString, JsValue,
18 builtins::{Array, BuiltInBuilder, BuiltInObject, IntrinsicObject},
19 context::{icu::IntlProvider, intrinsics::Intrinsics},
20 js_string,
21 object::JsObject,
22 property::Attribute,
23 realm::Realm,
24 string::StaticJsStrings,
25 symbol::JsSymbol,
26};
27
28use boa_gc::{Finalize, Trace};
29use icu_locale::{LanguageIdentifier, extensions::unicode};
30use icu_provider::{DataMarker, DataMarkerAttributes};
31use static_assertions::const_assert;
32
33pub(crate) use self::{
34 collator::Collator, date_time_format::DateTimeFormat, list_format::ListFormat, locale::Locale,
35 number_format::NumberFormat, plural_rules::PluralRules, segmenter::Segmenter,
36};
37
38macro_rules! impl_service_preferences {
43 ($($field:ident),*) => {
44 fn extended(&self, other: &Self) -> Self {
45 let mut result = *self;
46 result.extend(*other);
47 result
48 }
49
50 fn as_unicode(&self) -> unicode::Unicode {
51 let mut exts = unicode::Unicode::new();
52
53 $(
54 if let Some(key) = &self.$field
55 && let Some((key, value)) = $crate::builtins::intl::get_kv_from_pref(key)
56 {
57 exts.keywords.set(key, value);
58 }
59 )*
60
61 exts
62 }
63
64 fn intersection(&self, other: &Self) -> Self {
65 let mut inter = *self;
66 if inter.locale_preferences != other.locale_preferences {
67 inter.locale_preferences = LocalePreferences::default();
68 }
69
70 $(
71 if inter.$field != other.$field {
72 inter.$field.take();
73 }
74 )*
75
76 inter
77 }
78 };
79}
80
81pub(crate) mod collator;
82pub(crate) mod date_time_format;
83pub(crate) mod list_format;
84pub(crate) mod locale;
85pub(crate) mod number_format;
86pub(crate) mod plural_rules;
87pub(crate) mod segmenter;
88
89mod options;
90
91const_assert! {!<Collator as Service>::LangMarker::INFO.is_singleton}
95const_assert! {!<ListFormat as Service>::LangMarker::INFO.is_singleton}
96const_assert! {!<NumberFormat as Service>::LangMarker::INFO.is_singleton}
97const_assert! {!<PluralRules as Service>::LangMarker::INFO.is_singleton}
98const_assert! {!<Segmenter as Service>::LangMarker::INFO.is_singleton}
99const_assert! {!<DateTimeFormat as Service>::LangMarker::INFO.is_singleton}
100
101#[derive(Debug, Clone, Trace, Finalize, JsData)]
103#[boa_gc(unsafe_empty_trace)]
104pub struct Intl {
105 fallback_symbol: JsSymbol,
106}
107
108impl Intl {
109 #[must_use]
111 pub fn fallback_symbol(&self) -> JsSymbol {
112 self.fallback_symbol.clone()
113 }
114
115 pub(crate) fn new() -> Option<Self> {
116 let fallback_symbol = JsSymbol::new(Some(js_string!("IntlLegacyConstructedSymbol")))?;
117 Some(Self { fallback_symbol })
118 }
119}
120
121impl IntrinsicObject for Intl {
122 fn init(realm: &Realm) {
123 BuiltInBuilder::with_intrinsic::<Self>(realm)
124 .static_property(
125 JsSymbol::to_string_tag(),
126 Self::NAME,
127 Attribute::CONFIGURABLE,
128 )
129 .static_property(
130 Collator::NAME,
131 realm.intrinsics().constructors().collator().constructor(),
132 Collator::ATTRIBUTE,
133 )
134 .static_property(
135 ListFormat::NAME,
136 realm
137 .intrinsics()
138 .constructors()
139 .list_format()
140 .constructor(),
141 ListFormat::ATTRIBUTE,
142 )
143 .static_property(
144 Locale::NAME,
145 realm.intrinsics().constructors().locale().constructor(),
146 Locale::ATTRIBUTE,
147 )
148 .static_property(
149 Segmenter::NAME,
150 realm.intrinsics().constructors().segmenter().constructor(),
151 Segmenter::ATTRIBUTE,
152 )
153 .static_property(
154 PluralRules::NAME,
155 realm
156 .intrinsics()
157 .constructors()
158 .plural_rules()
159 .constructor(),
160 PluralRules::ATTRIBUTE,
161 )
162 .static_property(
163 DateTimeFormat::NAME,
164 realm
165 .intrinsics()
166 .constructors()
167 .date_time_format()
168 .constructor(),
169 DateTimeFormat::ATTRIBUTE,
170 )
171 .static_property(
172 NumberFormat::NAME,
173 realm
174 .intrinsics()
175 .constructors()
176 .number_format()
177 .constructor(),
178 NumberFormat::ATTRIBUTE,
179 )
180 .static_method(
181 Self::get_canonical_locales,
182 js_string!("getCanonicalLocales"),
183 1,
184 )
185 .build();
186 }
187
188 fn get(intrinsics: &Intrinsics) -> JsObject {
189 intrinsics.objects().intl().upcast()
190 }
191}
192
193impl BuiltInObject for Intl {
194 const NAME: JsString = StaticJsStrings::INTL;
195}
196
197impl Intl {
198 pub(crate) fn get_canonical_locales(
209 _: &JsValue,
210 args: &[JsValue],
211 context: &mut Context,
212 ) -> JsResult<JsValue> {
213 let locales = args.get_or_undefined(0);
214
215 let ll = locale::canonicalize_locale_list(locales, context)?;
217
218 Ok(JsValue::new(Array::create_array_from_list(
220 ll.into_iter().map(|loc| js_string!(loc.to_string()).into()),
221 context,
222 )))
223 }
224}
225
226fn get_kv_from_pref<T: icu_locale::preferences::PreferenceKey>(
227 pref: &T,
228) -> Option<(unicode::Key, unicode::Value)> {
229 T::unicode_extension_key().zip(pref.unicode_extension_value())
230}
231
232trait ServicePreferences: for<'a> From<&'a icu_locale::Locale> + Clone {
235 fn validate(&mut self, id: &LanguageIdentifier, provider: &IntlProvider);
240
241 fn as_unicode(&self) -> unicode::Unicode;
243
244 fn extended(&self, other: &Self) -> Self;
246
247 fn intersection(&self, other: &Self) -> Self;
249}
250
251trait Service {
256 type LangMarker: DataMarker;
258
259 const ATTRIBUTES: &'static DataMarkerAttributes = DataMarkerAttributes::empty();
261
262 type Preferences: ServicePreferences;
264}