A date library plus three hand-rolled helpers is the standard stack for money and time. The browser already carries all of it behind Intl, localized for Indonesia, with no download cost at all.

new Intl.NumberFormat("id-ID", { style: "currency", currency: "IDR" })
  .format(250000); // "Rp 250.000"

new Intl.DateTimeFormat("id-ID", { dateStyle: "long" })
  .format(new Date()); // "26 Oktober 2026"

new Intl.RelativeTimeFormat("id-ID")
  .format(-3, "day"); // "3 hari yang lalu"

The three rules that keep it cheap

  1. Build formatters once and reuse: construction parses locale data and is the expensive part.
  2. Let the locale own the order: never slice a formatted string; ask Intl for the exact parts shape you need.
  3. Store numbers as numbers and format at the edge; 'Rp 250,000' with the wrong separator is a data bug, not a formatter bug.

One measured caveat: Node and browsers agree on display shapes for the tested locales, but test each new locale once wherever it renders; test results get recorded or they get forgotten.

This site prints every article date through Intl on four locales: the Indonesian edition reads 'Diterbitkan 24 Oktober 2026' from the same single call the French and Spanish editions use. Zero i18n libraries in package.json.