It's been a little while since I last blogged about my favorite web platform feature, Intl. I think it was maybe two or so years ago when I was prepping for my first conference talk on the topic and using that as an opportunity to dig much deeper into the spec then I had before and wow, I was unprepared for how flexible, and powerful, this functionality is in the browser.

I blogged about localized relative timings back in March of 2024 (ah, I remember March 2024, I had a job then), and discussed how to dynamically handle different quantities of time differences.

More recently, I blogged about dynamic time durations and how best to select the right duration for the formatter object.

In both cases, the interesting aspect wasn't so much Intl, but rather, how best to use Intl when rendering your results. It was a pretty fascinating set of posts I think (ok, I'm biased perhaps) and I'm glad I investigated those parts of the spec.

Today I'm looking at another part of Intl - number formatting with units. When working with NumberFormat, the style option of the constructor reflects what kind of formatting you want to do. The options are:

  • decimal (default)
  • currency (money money money)
  • percent
  • unit

That last one may not be obvious and is the focus of my post today. Unit formatting is used for formatting a number of a certain type of thing, so for example, 5 ounces of water, or 9 pounds of sugar. Intl lets you handle formatting those measurements in a locale specific format. Let's look at some examples.

What's the Unit, man?

First off, what unit values are supported? There's an API for that! The [Intl.supportedValuesOf()] method can return valid values of units like so:

units = Intl.supportedValuesOf('unit');

Those values, as of today, in my browser, are:

  • acre
  • bit
  • byte
  • celsius
  • centimeter
  • day
  • degree
  • fahrenheit
  • fluid-ounce
  • foot
  • gallon
  • gigabit
  • gigabyte
  • gram
  • hectare
  • hour
  • inch
  • kilobit
  • kilobyte
  • kilogram
  • kilometer
  • liter
  • megabit
  • megabyte
  • meter
  • microsecond
  • mile
  • mile-scandinavian
  • milliliter
  • millimeter
  • millisecond
  • minute
  • month
  • nanosecond
  • ounce
  • percent
  • petabyte
  • pound
  • second
  • stone
  • terabit
  • terabyte
  • week
  • yard
  • year

I built a simple demo for this that let's you enter an arbitrary numeric value, select a unit, and it renders the results in seven different locales:

See the Pen Intl Unit Test1 by Raymond Camden (@cfjedimaster) on CodePen.

Formatting Bytes

Ok, so the whole reason I actually went down this route of research this week was for a simple task - given a file size in bytes, I wanted to format in kilobytes, megabytes, and so forth. I was curious if Intl maybe had this baked in, and it doesn't... not exactly. As with the blog posts I mentioned from earlier, it's up to you to decide what unit of measure to use, i.e., what makes sense, and then you can use Intl to render it properly.

Now, here's where I have to make a confession. I had Googled for this, and Google actually spat out a JavaScript function to do this for me. And... from what I could see... it worked well and was kinda clever. Here's the function it created:

function formatBytes(bytes, locale = 'en-US') {
  const units = ['byte', 'kilobyte', 'megabyte', 'gigabyte', 'terabyte'];
  const i = Math.floor(Math.log(bytes) / Math.log(1024));
  const value = bytes / Math.pow(1024, i);

  const formatter = new Intl.NumberFormat(locale, {
    style: 'unit',
    unit: units[i],
    unitDisplay: 'narrow', // or 'short', 'long'
    maximumFractionDigits: 2, // Adjust as needed
  });

  return formatter.format(value);
}

The clever part comes in from figuring out what 'level' of size to use, from byte to terabyte, but doing a bit of match. Honestly, that never would have occurred to me and is a prime reason I fail those high end coding challenges in interviews. I'm ok with that. Probably the only thing I'd change in that is to swap out the default of en-US to navigator.language.

That being, I whipped up another CodePen that takes in a set of inputs and renders them for multiple locales. I'll share the CodePen below, but make special note of this:

let inputs = [1_024, 2_500_000, 5_000_000_000, 123, 5_000_000_000_000];

Don't forget that JavaScript lets you add underscores in numbers to make them easier to read. They're completely ignored by the parser. Alright, here's the demo:

See the Pen Untitled by Raymond Camden (@cfjedimaster) on CodePen.

Let me know if you've got any questions, or anything else in Intl you would like me to dig into!

"Round Measuring Spoons" by Theen ... is licensed under CC BY-NC-SA 2.0 .