Skip to content
Codeloom
JavaScript

The Intl API: Format Numbers, Dates, and Strings Like a Pro

Master the JavaScript Intl API for locale-aware formatting of numbers, currencies, dates, relative time, and list conjunctions.

·6 min read · By Codeloom
Intermediate 10 min read

What you'll learn

  • How to format numbers, currencies, and percentages with Intl.NumberFormat
  • How to format dates and times with Intl.DateTimeFormat
  • How to use RelativeTimeFormat, ListFormat, and PluralRules

Prerequisites

  • Basic JavaScript
  • Familiarity with Date objects

The Intl namespace provides locale-aware formatting for numbers, dates, strings, and more — all built into every modern JavaScript runtime. No external libraries required.

Intl.NumberFormat

Format numbers according to locale conventions, including currencies, percentages, units, and compact notation.

// Basic number formatting
const num = 1234567.89;

console.log(new Intl.NumberFormat("en-US").format(num));
// "1,234,567.89"

console.log(new Intl.NumberFormat("de-DE").format(num));
// "1.234.567,89"

console.log(new Intl.NumberFormat("en-IN").format(num));
// "12,34,567.89"

Currency formatting

function formatCurrency(amount, currency, locale = "en-US") {
  return new Intl.NumberFormat(locale, {
    style: "currency",
    currency,
  }).format(amount);
}

console.log(formatCurrency(1499.99, "USD"));         // "$1,499.99"
console.log(formatCurrency(1499.99, "EUR", "de-DE")); // "1.499,99 €"
console.log(formatCurrency(1499.99, "JPY", "ja-JP")); // "¥1,500"
console.log(formatCurrency(1499.99, "GBP", "en-GB")); // "£1,499.99"

Compact notation

const compact = new Intl.NumberFormat("en-US", {
  notation: "compact",
  compactDisplay: "short",
});

console.log(compact.format(1200));       // "1.2K"
console.log(compact.format(1500000));    // "1.5M"
console.log(compact.format(2300000000)); // "2.3B"

Percentages and units

// Percentages
const pct = new Intl.NumberFormat("en-US", {
  style: "percent",
  minimumFractionDigits: 1,
});
console.log(pct.format(0.7523)); // "75.2%"

// Units
const speed = new Intl.NumberFormat("en-US", {
  style: "unit",
  unit: "kilometer-per-hour",
  unitDisplay: "short",
});
console.log(speed.format(120)); // "120 km/h"

const temp = new Intl.NumberFormat("en-US", {
  style: "unit",
  unit: "celsius",
  unitDisplay: "long",
});
console.log(temp.format(22)); // "22 degrees Celsius"

Intl.DateTimeFormat

Format dates and times according to locale conventions without reaching for date libraries.

const date = new Date("2026-07-01T14:30:00Z");

// Basic formatting
console.log(new Intl.DateTimeFormat("en-US").format(date));
// "7/1/2026"

console.log(new Intl.DateTimeFormat("en-GB").format(date));
// "01/07/2026"

console.log(new Intl.DateTimeFormat("ja-JP").format(date));
// "2026/7/1"

Detailed formatting options

const date = new Date("2026-07-01T14:30:00Z");

const detailed = new Intl.DateTimeFormat("en-US", {
  weekday: "long",
  year: "numeric",
  month: "long",
  day: "numeric",
  hour: "2-digit",
  minute: "2-digit",
  timeZone: "America/New_York",
  timeZoneName: "short",
});

console.log(detailed.format(date));
// "Wednesday, July 1, 2026 at 10:30 AM EDT"

Format to parts

formatToParts() gives you an array of labeled tokens for custom rendering.

const formatter = new Intl.DateTimeFormat("en-US", {
  year: "numeric",
  month: "long",
  day: "numeric",
});

const parts = formatter.formatToParts(new Date("2026-07-01"));
console.log(parts);
// [
//   { type: "month", value: "July" },
//   { type: "literal", value: " " },
//   { type: "day", value: "1" },
//   { type: "literal", value: ", " },
//   { type: "year", value: "2026" }
// ]

// Build custom format
const month = parts.find((p) => p.type === "month").value;
const day = parts.find((p) => p.type === "day").value;
console.log(`${month} ${day}`); // "July 1"

Time zone conversion

function formatInTimeZone(date, timeZone, locale = "en-US") {
  return new Intl.DateTimeFormat(locale, {
    dateStyle: "medium",
    timeStyle: "long",
    timeZone,
  }).format(date);
}

const now = new Date();
console.log(formatInTimeZone(now, "America/New_York"));
// "Jul 1, 2026, 10:30:00 AM EDT"

console.log(formatInTimeZone(now, "Asia/Tokyo"));
// "Jul 1, 2026, 11:30:00 PM JST"

Intl.RelativeTimeFormat

Express durations in human-readable relative terms like “3 days ago” or “in 2 hours.”

const rtf = new Intl.RelativeTimeFormat("en", { numeric: "auto" });

console.log(rtf.format(-1, "day"));     // "yesterday"
console.log(rtf.format(0, "day"));      // "today"
console.log(rtf.format(1, "day"));      // "tomorrow"
console.log(rtf.format(-3, "hour"));    // "3 hours ago"
console.log(rtf.format(2, "month"));    // "in 2 months"

// Other locales
const rtfFr = new Intl.RelativeTimeFormat("fr", { numeric: "auto" });
console.log(rtfFr.format(-1, "day"));   // "hier"
console.log(rtfFr.format(2, "week"));   // "dans 2 semaines"

Building a smart relative time function

function timeAgo(date) {
  const rtf = new Intl.RelativeTimeFormat("en", { numeric: "auto" });
  const now = Date.now();
  const diffMs = date.getTime() - now;
  const diffSec = Math.round(diffMs / 1000);
  const diffMin = Math.round(diffSec / 60);
  const diffHour = Math.round(diffMin / 60);
  const diffDay = Math.round(diffHour / 24);

  if (Math.abs(diffSec) < 60) return rtf.format(diffSec, "second");
  if (Math.abs(diffMin) < 60) return rtf.format(diffMin, "minute");
  if (Math.abs(diffHour) < 24) return rtf.format(diffHour, "hour");
  if (Math.abs(diffDay) < 30) return rtf.format(diffDay, "day");
  return rtf.format(Math.round(diffDay / 30), "month");
}

const fiveMinAgo = new Date(Date.now() - 5 * 60 * 1000);
console.log(timeAgo(fiveMinAgo)); // "5 minutes ago"

Intl.ListFormat

Join lists of items with proper locale-aware conjunctions.

const items = ["Alice", "Bob", "Charlie"];

// Conjunction (and)
const and = new Intl.ListFormat("en", { type: "conjunction" });
console.log(and.format(items)); // "Alice, Bob, and Charlie"

// Disjunction (or)
const or = new Intl.ListFormat("en", { type: "disjunction" });
console.log(or.format(items)); // "Alice, Bob, or Charlie"

// Other locales
const frList = new Intl.ListFormat("fr", { type: "conjunction" });
console.log(frList.format(items)); // "Alice, Bob et Charlie"

const deList = new Intl.ListFormat("de", { type: "conjunction" });
console.log(deList.format(items)); // "Alice, Bob und Charlie"

Intl.PluralRules

Determine the correct plural category for a number in a given locale.

const pr = new Intl.PluralRules("en");

console.log(pr.select(0));  // "other"
console.log(pr.select(1));  // "one"
console.log(pr.select(2));  // "other"

function pluralize(count, singular, plural) {
  const rule = new Intl.PluralRules("en").select(count);
  return `${count} ${rule === "one" ? singular : plural}`;
}

console.log(pluralize(1, "item", "items")); // "1 item"
console.log(pluralize(5, "item", "items")); // "5 items"
console.log(pluralize(0, "item", "items")); // "0 items"

For languages with more plural forms (like Arabic, which has six), PluralRules returns categories like "zero", "one", "two", "few", "many", and "other".

Intl.Collator

Locale-sensitive string comparison for sorting.

const words = ["resume", "Resume", "cafe", "cafe"];
const collator = new Intl.Collator("en", { sensitivity: "base" });

// Case-insensitive, accent-insensitive sort
console.log(words.sort(collator.compare));

Intl.Segmenter

Break text into meaningful segments: words, sentences, or grapheme clusters.

const segmenter = new Intl.Segmenter("en", { granularity: "word" });
const text = "Hello, world! How are you?";
const words = [...segmenter.segment(text)]
  .filter((s) => s.isWordLike)
  .map((s) => s.segment);

console.log(words);
// ["Hello", "world", "How", "are", "you"]

// Grapheme clusters handle emoji correctly
const emojiSegmenter = new Intl.Segmenter("en", { granularity: "grapheme" });
const emoji = "family emoji test";
const graphemes = [...emojiSegmenter.segment(emoji)].map((s) => s.segment);
console.log(graphemes.length);

Combining formatters for a dashboard

function formatDashboardMetrics(locale, metrics) {
  const number = new Intl.NumberFormat(locale, { notation: "compact" });
  const currency = new Intl.NumberFormat(locale, {
    style: "currency",
    currency: metrics.currency,
    notation: "compact",
  });
  const percent = new Intl.NumberFormat(locale, {
    style: "percent",
    minimumFractionDigits: 1,
  });

  return {
    users: number.format(metrics.users),
    revenue: currency.format(metrics.revenue),
    growth: percent.format(metrics.growth),
  };
}

console.log(
  formatDashboardMetrics("en-US", {
    users: 1_450_000,
    revenue: 12_500_000,
    growth: 0.234,
    currency: "USD",
  })
);
// { users: "1.5M", revenue: "$13M", growth: "23.4%" }

Performance tip: reuse formatters

Creating Intl objects has a setup cost. Reuse them when formatting multiple values.

// Bad: creates a new formatter on every call
function formatPrice(amount) {
  return new Intl.NumberFormat("en-US", { style: "currency", currency: "USD" }).format(amount);
}

// Good: create once, reuse
const priceFormatter = new Intl.NumberFormat("en-US", {
  style: "currency",
  currency: "USD",
});

function formatPriceFast(amount) {
  return priceFormatter.format(amount);
}

Summary

The Intl API covers nearly every formatting need for internationalized applications:

  • NumberFormat for currencies, percentages, units, and compact notation.
  • DateTimeFormat for locale-aware dates, times, and time zone conversions.
  • RelativeTimeFormat for human-readable relative durations.
  • ListFormat for joining lists with proper conjunctions.
  • PluralRules for grammatically correct pluralization.
  • Collator for locale-aware string sorting.
  • Segmenter for breaking text into words, sentences, or grapheme clusters.

All of these are built into the runtime with zero bundle cost. Before reaching for a formatting library, check if Intl already does what you need.