Kocur4d
Kocur4d

Reputation: 6931

Currency formatting using Intl.NumberFormat without currency symbol

I am trying to use Intl as a default currency formatter and it is almost perfect.

Using the example from a Intl.NumberFormat() constructor:

const number = 123456.789;

console.log(new Intl.NumberFormat('de-DE', { style: 'currency',
currency: 'EUR' }).format(number)); // expected output: "123.456,79 €"
 
// the Japanese yen doesn't use a minor unit 
console.log(new Intl.NumberFormat('ja-JP', { style: 'currency', currency: 'JPY'
}).format(number)); // expected output: "¥123,457"

This is almost perfect but I would actually like to drop the symbol from the output. So I would expect to see:

// expected output: "123.456,79"
// expected output: "123,457"

I find it bizarre that I spend over an hour looking for a solution and only found some sort of replace/trim usage.

Why there is not an option to format the number with all the Intl power but only dropping the currency symbol?!?

I hope I missed it, tbh.

Upvotes: 20

Views: 29640

Answers (2)

VLAZ
VLAZ

Reputation: 28982

One simple way to do achieve what you want is to use String#replace() to remove the currency from the string. To make this easier, you can set currencyDisplay to "code" which will use the ISO currency code - the same one passed in to currency:

const number = 123456.789;

console.log(new Intl.NumberFormat('de-DE', { 
    style: 'currency',
    currency: 'EUR', 
    currencyDisplay: "code" 
  })
  .format(number)
  .replace("EUR", "")
  .trim()
); // 123.456,79
 
// the Japanese yen doesn't use a minor unit 
console.log(new Intl.NumberFormat('ja-JP', { 
    style: 'currency', 
   currency: 'JPY', 
    currencyDisplay: "code" 
  })
  .format(number)
  .replace("JPY", "")
  .trim()
); // 123,457

This can be extracted into a function:

const number = 123456.789;

console.log(format('de-DE', 'EUR', number)); // 123.456,79
console.log(format('ja-JP', 'JPY', number)); // 123,457

function format (locale, currency, number) {
  return new Intl.NumberFormat(locale, { 
    style: 'currency', 
    currency, 
    currencyDisplay: "code" 
  })
  .format(number)
  .replace(currency, "")
  .trim();
}


An alternative that allows you more control is to use Intl.NumberFormat#formatToParts() which formats the number but gives you tokens that you can programmatically consume and manipulate. For example, using the method with locale = "de-DE" and currency = "EUR" you get the following output:

[
  {
    "type": "integer",
    "value": "123"
  },
  {
    "type": "group",
    "value": "."
  },
  {
    "type": "integer",
    "value": "456"
  },
  {
    "type": "decimal",
    "value": ","
  },
  {
    "type": "fraction",
    "value": "79"
  },
  {
    "type": "literal",
    "value": " "
  },
  {
    "type": "currency",
    "value": "EUR"
  }
]

Which means that you can easily filter out "type": "currency" and then combine the rest into a string. For example:

const number = 123456.789;

console.log(format('de-DE', 'EUR', number)); // 123.456,79
console.log(format('ja-JP', 'JPY', number)); // 123,457

function format (locale, currency, number) {
  return new Intl.NumberFormat(locale, { 
    style: 'currency',
    currency, 
    currencyDisplay: "code",
  })
  .formatToParts(number)
  .filter(x => x.type !== "currency")
  .filter(x => x.type !== "literal" || x.value.trim().length !== 0)
  .map(x => x.value)
  .join("")
}

NOTE: the exclusion here: .filter(x => x.type !== "literal" || x.value.trim().length !== 0) handles whitespace characters within the number. That might come up when using the option currencySign: 'accounting' in the formatter. In some locales this will use parentheses for negative numbers which would leave a space inside if just the currency is removed:

const number = -123456.789;

const parts = new Intl.NumberFormat('ja-JP', { 
    style: 'currency',
    currency: 'JPY', 
    currencySign: "accounting",
    currencyDisplay: "code",
  })
  .formatToParts(number);
  
console.log(parts); 

/* output:
[
  { type: "literal" , value: "("    },
  { type: "currency", value: "JPY"  },
  { type: "literal" , value: " "    },
  { type: "integer" , value: "123"  },
  { type: "group"   , value: ","    },
  { type: "integer" , value: "457"  },
  { type: "literal" , value: ")"    }
]
*/
.as-console-wrapper { max-height: 100% !important; }

Thus negative numbers are handled correctly:

const number = -123456.789;

console.log(format('de-DE', 'EUR', number)); // 123.456,79
console.log(format('ja-JP', 'JPY', number)); // 123,457

function format (locale, currency, number) {
  return new Intl.NumberFormat(locale, { 
    style: 'currency',
    currency, 
    currencyDisplay: "code",
    currencySign: "accounting",
  })
  .formatToParts(number)
  .filter(x => x.type !== "currency")
  .filter(x => x.type !== "literal" || x.value.trim().length !== 0)
  .map(x => x.value)
  .join("")
}

Thanks to Chris Peckham for pointing out potential pitfalls when using the accounting currency sign option.

Upvotes: 27

mikemaccana
mikemaccana

Reputation: 123178

If you only want the separators in between individual digits, just format as a number instead of using currency:

const numberFormatter = new Intl.NumberFormat("en-US", {
  // Do not show fractions - front end should only handle whole numbers
  maximumFractionDigits: 0,
});

And then numberFormatter.format(asNumber); with whatever number you like.

For your example of 123456, "de-DE" is 123.456, "ja-JP" is 123,456

Upvotes: 4

Related Questions