Skip to content

Commit c83a5a6

Browse files
committed
feat(intl): implement Number.prototype.toLocaleString with Intl support
1 parent 79e6549 commit c83a5a6

File tree

2 files changed

+56
-11
lines changed

2 files changed

+56
-11
lines changed

core/engine/src/builtins/number/mod.rs

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -291,23 +291,51 @@ impl Number {
291291
///
292292
/// The `toLocaleString()` method returns a string with a language-sensitive representation of this number.
293293
///
294-
/// Note that while this technically conforms to the Ecma standard, it does no actual
295-
/// internationalization logic.
296-
///
297294
/// More information:
298295
/// - [ECMAScript reference][spec]
299296
/// - [MDN documentation][mdn]
300297
///
301-
/// [spec]: https://tc39.es/ecma262/#sec-number.prototype.tolocalestring
298+
/// [spec]: https://tc39.es/ecma402/#sup-number.prototype.tolocalestring
302299
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toLocaleString
303300
#[allow(clippy::wrong_self_convention)]
301+
#[allow(
302+
unused_variables,
303+
reason = "`args` and `context` are used if the `intl` feature is enabled"
304+
)]
304305
pub(crate) fn to_locale_string(
305306
this: &JsValue,
306-
_: &[JsValue],
307-
_: &mut Context,
307+
args: &[JsValue],
308+
context: &mut Context,
308309
) -> JsResult<JsValue> {
309-
let this_num = Self::this_number_value(this)?;
310-
Ok(JsValue::new(js_string!(this_num)))
310+
// Let x be ? thisNumberValue(this value).
311+
let x = Self::this_number_value(this)?;
312+
313+
#[cfg(feature = "intl")]
314+
{
315+
use fixed_decimal::{Decimal, FloatPrecision};
316+
317+
use crate::builtins::intl::NumberFormat;
318+
319+
if !x.is_finite() {
320+
return Ok(JsValue::new(js_string!(x)));
321+
}
322+
323+
let locales = args.get_or_undefined(0).clone();
324+
let options = args.get_or_undefined(1).clone();
325+
326+
// Let numberFormat be ? Construct(%Intl.NumberFormat%, « locales, options »).
327+
let number_format = NumberFormat::new(&locales, &options, context)?;
328+
let mut x = Decimal::try_from_f64(x, FloatPrecision::RoundTrip)
329+
.map_err(|err| JsNativeError::range().with_message(err.to_string()))?;
330+
331+
// Return FormatNumeric(numberFormat, ℝ(x)).
332+
Ok(js_string!(number_format.format(&mut x).to_string()).into())
333+
}
334+
335+
#[cfg(not(feature = "intl"))]
336+
{
337+
Ok(JsValue::new(js_string!(x)))
338+
}
311339
}
312340

313341
/// `flt_str_to_exp` - used in `to_precision`

core/engine/src/builtins/number/tests.rs

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -167,13 +167,30 @@ fn issue_2609() {
167167

168168
#[test]
169169
fn to_locale_string() {
170-
// TODO: We don't actually do any locale checking here
171-
// To honor the spec we should print numbers according to user locale.
172170
run_test_actions([
173171
TestAction::assert_eq("Number().toLocaleString()", js_str!("0")),
174172
TestAction::assert_eq("Number(5).toLocaleString()", js_str!("5")),
175-
TestAction::assert_eq("Number('345600').toLocaleString()", js_str!("345600")),
176173
TestAction::assert_eq("Number(-25).toLocaleString()", js_str!("-25")),
174+
TestAction::assert_eq("NaN.toLocaleString()", js_str!("NaN")),
175+
TestAction::assert_eq("Infinity.toLocaleString()", js_str!("Infinity")),
176+
TestAction::assert_eq("(-Infinity).toLocaleString()", js_str!("-Infinity")),
177+
]);
178+
}
179+
180+
#[test]
181+
#[cfg(feature = "intl")]
182+
fn to_locale_string_intl() {
183+
run_test_actions([
184+
TestAction::assert_eq("(345600).toLocaleString('en-US')", js_str!("345,600")),
185+
TestAction::assert_eq("(1234.5).toLocaleString('de-DE')", js_str!("1.234,5")),
186+
TestAction::assert_eq(
187+
"(1000).toLocaleString('en-US', { useGrouping: false })",
188+
js_str!("1000"),
189+
),
190+
TestAction::assert_eq(
191+
"(12.3).toLocaleString('en-US', { minimumFractionDigits: 2 })",
192+
js_str!("12.30"),
193+
),
177194
]);
178195
}
179196

0 commit comments

Comments
 (0)