Right, let’s talk about the silent, soul-crushing workhorses of any multilingual site: dates and numbers. You can have the most beautiful translation in the world, but if you present a French user with “5/12/2023” and they have to wonder if it’s May 12th or December 5th, you’ve lost them. It’s a small detail that screams “amateur hour.” We’re not amateurs. We’re going to get this right.

The secret weapon here is the Intl object (short for Internationalization). This is the browser’s and Node.js’s built-in, no-dependency-required powerhouse for doing this stuff correctly. It’s not some clunky library from 2010; it’s a modern API that leans on the Unicode CLDR (Common Locale Data Repository), which is basically the holy text for how cultures format things. We use it.

The Intl Object is Your New Best Friend

Forget hand-rolling formatters or, heaven forbid, writing a giant switch statement for every locale. Intl does the heavy lifting. You create a “formatter” object for a specific locale and set of options, and then you just feed it data. It handles the rest, from the right-to-left formatting of numbers in Arabic to the specific characters used for decimals and thousands separators in German.

Here’s the basic mantra: you don’t format the data; you let the user’s locale format it for you.

Formatting Numbers: It’s Not Just Commas and Periods

Think number formatting is just swapping a comma for a decimal point? Oh, my sweet summer child. Let’s look at a simple number: 1234567.89.

// Create formatters for different locales
const usFormatter = new Intl.NumberFormat('en-US');
const deFormatter = new Intl.NumberFormat('de-DE');
const frFormatter = new Intl.NumberFormat('fr-FR');
const inFormatter = new Intl.NumberFormat('en-IN');

console.log(usFormatter.format(1234567.89)); // "1,234,567.89"
console.log(deFormatter.format(1234567.89)); // "1.234.567,89" (uses periods for thousands, comma for decimal)
console.log(frFormatter.format(1234567.89)); // "1 234 567,89" (uses NON-BREAKING SPACES for thousands! Brilliant.)
console.log(inFormatter.format(1234567.89)); // "12,34,567.89" (Indian Lakh/Crore system)

See? The French use a thin space (or a non-breaking space), not a comma. The Indian numbering system groups digits differently. You didn’t know any of that, and you don’t have to! Intl.NumberFormat does.

You can also format these numbers as currencies, which is where it gets really powerful and saves you from looking like a complete fool.

const jpyFormatter = new Intl.NumberFormat('ja-JP', {
  style: 'currency',
  currency: 'JPY'
});
const usdFormatter = new Intl.NumberFormat('en-US', {
  style: 'currency',
  currency: 'USD'
});
const eurFormatter = new Intl.NumberFormat('de-DE', {
  style: 'currency',
  currency: 'EUR'
});

console.log(jpyFormatter.format(123456)); // "¥123,456" (No decimal places for Yen? Correct!)
console.log(usdFormatter.format(123456.789)); // "$123,456.79" (Rounds to cents)
console.log(eurFormatter.format(123456.789)); // "123.456,79 €" (Currency symbol at the end, with German formatting)

Formatting Dates: The World’s Most Unnecessary Complexity

Date formatting is a minefield. The Date object in JavaScript is famously… not great. But Intl.DateTimeFormat is the balm that soothes that particular burn.

The key is to never, ever output a raw ISO string (e.g., 2023-05-12) to a user. That’s for data exchange, not for human consumption. Let’s format the same date for different locales.

const now = new Date(); // Let's say it's May 12th, 2023

const usDateFormatter = new Intl.DateTimeFormat('en-US');
const gbDateFormatter = new Intl.DateTimeFormat('en-GB');
const saDateFormatter = new Intl.DateTimeFormat('ar-SA');
const jpDateFormatter = new Intl.DateTimeFormat('ja-JP');

console.log(usDateFormatter.format(now)); // "5/12/2023" (Ambiguous? Yes. Standard? Unfortunately.)
console.log(gbDateFormatter.format(now)); // "12/05/2023" (Day first, the superior format. I will die on this hill.)
console.log(saDateFormatter.format(now)); // "٢١/١١/١٤٤٤ هـ" (Whoa! The Hijri calendar, right-to-left, Eastern Arabic numerals. All automatic.)
console.log(jpDateFormatter.format(now)); // "2023/5/12" (Sensible year-month-day order)

But you have control. You can specify exactly how much detail you want.

const longFormatter = new Intl.DateTimeFormat('en-US', {
  dateStyle: 'full',
  timeStyle: 'long'
});
console.log(longFormatter.format(now)); // "Friday, May 12, 2023 at 2:45:30 PM EDT"

const shortFormatter = new Intl.DateTimeFormat('de-DE', {
  dateStyle: 'short',
  timeStyle: 'short'
});
console.log(shortFormatter.format(now)); // "12.05.23, 14:45"

The Critical Pitfall: Locale Detection

Here’s the most important part: how do you know which locale to use? You have two sources, and you must prioritize them correctly.

  1. The User’s Explicit Choice (Highest Priority): If your app has a language switcher and the user has selected “Français,” you must use fr-FR for formatting, regardless of their browser’s settings. Their choice is law.
  2. The Browser’s Language Setting (Fallback): If the user hasn’t made a choice, you can sniff the browser’s accepted language via navigator.languages (client-side) or the Accept-Language header (server-side). This is a good guess, but it’s just a guess.

Never assume the browser’s locale is correct for formatting if the user has told you otherwise. The most robust approach is to store the user’s locale preference (e.g., fr-FR) in their user account or a cookie and use that to initialize your Intl formatters on every request.

The Edge Case You Will Hit: Mixing Locales

What if you’re showing a list of users from around the world on a single page? A German admin looking at a Japanese user’s account creation date. Do you format it for the admin (German format) or for the data’s origin (Japanese format)?

The best practice is to format for the viewer. The admin is German, so show them all dates in the German format. Consistency for the person using the interface is more important than representing the data’s “origin locale.” A small note like “User located in Japan” is often a kinder, more understandable solution than suddenly switching date formats mid-page and causing confusion.

Use Intl. Respect the user’s choice. Format everything. It’s the difference between a site that feels local and one that feels merely translated.