Format with Intl

Format dates, numbers and currency in the reader's locale with Intl.

From web-design-guidelines (Web Interface Guidelines).

One invoice, seven readers

Switch the locale; only the right invoice follows.

HardcodedBad
Invoice
INV-0042
Issued
Due
Subtotal
Tax
Total
"$" + n.toFixed(2)`${mm}/${dd}/${yyyy}`

IntlGood
Invoice
INV-0042
Issued
Due
Subtotal
Tax
Total
Intl.NumberFormat(locale, { style: "currency", currency })
const money = new Intl.NumberFormat(locale, { style: "currency", currency });
const pct = new Intl.NumberFormat(locale, { style: "percent" });
const date = new Intl.DateTimeFormat(locale, { dateStyle: "medium" });

Ask the browser, not the IP

Pick a reader and where they connect from.

GeoIPBad

Interface
Updated
Balance

navigator.languagesGood

Interface
Updated
Balance
const SUPPORTED = ["en", "de", "fr", "ja", "pt"];
const ui = navigator.languages
  .map((tag) => tag.split("-")[0])
  .find((lang) => SUPPORTED.includes(lang)) ?? "en";
const when = new Intl.DateTimeFormat(navigator.languages[0], {
  dateStyle: "full",
  timeStyle: "short",
});

Keep names untranslated

Press Translate, then type a product name like Plaid.

No translate="no"Bad
translate="no"Good
<p>Connect <span translate="no">Stripe</span>, post alerts to <span translate="no">Slack</span> …</p>
<p>Then call <code translate="no">useState</code> and run <code translate="no">npm run build</code>.</p>

Why it matters, pros and cons

Why it matters

03/04/2026 is March 4 in the US and April 3 elsewhere.

Pros

  • Correct separators, symbols and date order, free.
  • The currency decides decimals: JPY gets none.
  • navigator.languages survives VPNs and travel.

Cons

  • Output varies by browser; never parse formatted strings.
  • Server and client can differ; pass the locale explicitly.
  • CSV, APIs and logs should stay ISO 8601.