Beyond translations in Stimulus: formatting dates, time and currency
Rails Designer

Rails Designer @railsdesigner

About: I help teams around the world make their Rails apps a bit nicer to maintain and look at. 🎨 Also kickstart SaaS' in a month. 👷 And built a Rails UI Components library, used by 1000+ developers. 🚀

Location:
Amsterdam, The Netherlands
Joined:
Sep 29, 2023

Beyond translations in Stimulus: formatting dates, time and currency

Publish Date: Apr 3
4 0

This articles was previously published on Rails Designer


In a previous article I showed various techniques to add translations in your Stimulus controller. From super basic using the Stimulus Values API, to a cool custom method that could be used beyond a Stimulus controller (for example in plain JavaScript classes).

In this article, which is also taken (but adapted for the web) from the book JavaScript for Rails Developers, I want to explore the other part of internationalization (i18n), formatting dates, time and currency based on a given locale.

In ECMAScript 2011 (ES5.1), finalized in 2011, the Intl object was defined. It became widely supported in major browsers the following years. It is a built-in i18n API that provides language-sensitive string comparison, number formatting, date and time formatting and so on.

Instead of manually formatting to, let's say the Dutch currency formatting (€ 42,42), like so € ${currency} ${number.toFixed(2).replace(".", ",")} you can do this:

// for Dutch in The Netherlands
const formatter = new Intl.NumberFormat("nl-NL", {
  style: "currency",
  currency: "EUR"
})

formatter.format(42.42) // => "€ 42,42"

// for French in France
const formatter = new Intl.NumberFormat("fr-FR", {
  style: "currency",
  currency: "EUR"
})

formatter.format(42.42) // => "42,42 €"
Enter fullscreen mode Exit fullscreen mode

Or just numbers:

const formatter = new Intl.NumberFormat("nl-NL")

formatter.format(1000) // => "1.000"

const formatter = new Intl.NumberFormat("en-US")

formatter.format(1000) // => "1,000"
Enter fullscreen mode Exit fullscreen mode

You can also use Intl for dates:

const date = new Date()
const formatter = new Intl.DateTimeFormat("ar-EG") // Arabic in Egypt

formatter.format(date) // => "٢١‏/٣‏/٢٠٢٥"
Enter fullscreen mode Exit fullscreen mode

Or pass it a time zone:

const date = new Date()
const formatter = new Intl.DateTimeFormat("nl-NL", {timezone: "Europe/Amsterdam"})

formatter.format(date) // => "3-4-2025"
Enter fullscreen mode Exit fullscreen mode

For optimization reasons, it is important to set the formatter once (e.g. in the controller's initialization).

Above API is hardly elegant. Most of you are (also) Ruby developers (right?), so let's see how this can be improved for developer-happiness. This kind of API is what I am after:

i18n.number(1234.567) // "1.234,57"
i18n.currency(42.42)  // "€ 42,42"
i18n.date(new Date()) // "21 maart 2025"
i18n.time(new Date()) // "14:30"
Enter fullscreen mode Exit fullscreen mode

All that is needed is one JavaScript class that abstracts away some of this logic, just import it wherever you need the i18n's methods (import { i18n } from "./helpers/i18n":

// app/javascript/helpers/i18n.js
class I18n {
  constructor(locale = "nl-NL", timezone = "Europe/Amsterdam") {
    this.#numberFormatter = new Intl.NumberFormat(locale)

    this.#currencyFormatter = new Intl.NumberFormat(locale, {
      style: "currency",
      currency: "EUR"
    })

    this.#dateFormatter = new Intl.DateTimeFormat(locale, {
      dateStyle: "long",
      timeZone: timezone
    })

    this.#timeFormatter = new Intl.DateTimeFormat(locale, {
      timeStyle: "short",
      timeZone: timezone
    })
  }

  number(value) {
    return this.#numberFormatter.format(value)
  }

  currency(value) {
    return this.#currencyFormatter.format(value)
  }

  date(value) {
    return this.#dateFormatter.format(value)
  }

  time(value) {
    return this.#timeFormatter.format(value)
  }

  // private

  #numberFormatter
  #currencyFormatter
  #dateFormatter
  #timeFormatter
}

export const i18n = new I18n()
Enter fullscreen mode Exit fullscreen mode

This is your typical JavaScript class (if this looks weird to you, check out JavaScript for Rails Developers 😊), it takes two arguments: locale and timezone. Default is respectively set to nl-NL and Europe/Amsterdam (🇳🇱 hallo! hoe is het?). Then various (private) formatters are defined based off of these attributes' values. Feel free to extend the methods with those you need (or check the added resources in the book 💡).

Other available locales (there are many!) are, for example: en-US, en-GB, en-CA, es-ES, es-MX fr-FR and de-DE.

Because the class is exported as i18n, you can use the methods in your JavaScript like i18n.number(42) // => 42.

And that's how you level up your i18n needs in your JavaScript!

Comments 0 total

    Add comment