DEV Community

Rails Designer
Rails Designer

Posted on • Originally published at railsdesigner.com

4 1 1 1 1

Translations in Stimulus Controllers

This article was originally published on Rails Designer


This is a sneak peek from the upcoming book, JavaScript for Rails Developers. For this blog post, I’ve simplified some of the more detailed sections to better fit the blog.


Translations, or more broadly Internationalization (i18n; because there are 18 letters between “i” and “n”), is the process of preparing apps to support multiple languages and regional settings (i.e. “locale”) to accommodate a global user base. So it's not just about translating words, but also about the formatting of dates, numbers and phone numbers.

It is one of those features product managers move back it to the backlog, because they know adding support for it is fairly easy (especially with Rails), but supporting it while the product is in development is a pain in the ass multiplied by the number of supported locales.

But here we are! And luckily Rails has great support for i18n out-of-the-box. But how does that extend to JavaScript and specifically, Stimulus? I want to explore multiple ways.

Let's assume you have a typical controller that has three static strings that are used to indicate a saved-state.

  • saved;
  • saving;
  • failed.

You can imagine them being used like this:

export default class extends Controller {
  static targets = ["status"]

  update() {
    this.statusTarget.textContent = "Saving"

    const response = fetch(
      // logic to make a PATCH request
    )

    response.ok ?
      this.statusTarget.textContent = "Saved" :
      this.statusTarget.textContent = "Failed"
  }
}
Enter fullscreen mode Exit fullscreen mode

First make sure you add the keys to your locale config, e.g. config/locales/en.yml:

en:
  hello: "Hello world"

  status:
    saved: Saved
    saving: Saving
    failed: Failed
Enter fullscreen mode Exit fullscreen mode

Use the HTML you already have

Stimulus' tagline is “a modest JavaScript framework for the HTML you already have”. And with that you can render the, translated, content in the HTML like you normally do with Rails and then show/hide, inject and transform HTML-elements using Stimulus. So instead of one status target, you create a savedStatus, savingStatus and failedStatus. Easy and straight-forward. No extra steps needed!

Using Stimulus' separate values API

If you need to do more DOM wrangling in your JavaScript, you can choose to use the values API.

export default class extends Controller {
  static values = {
    // …
    savedStatus: { type: String, default: "Saved" },
    savingStatus: { type: String, default: "Saving" },
    failedStatus: { type: String, default: "Failed" }
  }

  // …
}
Enter fullscreen mode Exit fullscreen mode

And then instead of using the static strings, you can use the value like any other: this.savedStatusValue. Perfect solution if you only have a few keys!

Using Stimulus' object values API

But when you have many keys (even the above three status keys I find too much already), you could pass them as an object instead.

Then you can pass them to the controller like so:

<div
  data-controller="editor"
  data-editor-i18n-value="<%= t('status').to_json %>"
>
</div>
Enter fullscreen mode Exit fullscreen mode

And instead of three separate values, you have one i18n object:

export default class extends Controller {
  static values = {
    // …
    i18n: Object
  }

  // …
}
Enter fullscreen mode Exit fullscreen mode

Then use it in the controller as this.i18nValue.saved. Pretty sweet! This solution doesn't support default values, but if you need to pass multiple keys—not just status—you can do so with some Ruby logic.

Custom JavaScript solution

These solutions all work pretty fine, but are limited to Stimulus controllers. What if you need some translated strings in a imported class or method? Pass those strings along as well? What about using syntax like this:

export default class extends Controller {
  update() {
    response.ok ?
      this.statusTarget.textContent = t("status.saved") :
      this.statusTarget.textContent = t("status.failed")
  }
}
Enter fullscreen mode Exit fullscreen mode

I recognize that! That is just like the t (short for I18n.t) in Rails! It is not supported, like this, out-of-the-box in JavaScript. Let's create the t method in app/javascript/helpers/i18n.js (and import it where you need it):

// app/javascript/helpers/i18n.js
export function t(key) {
  if (!window.i18n) return key

  try {
    return key.split(".").reduce(
      (translation, part) => translation[part], window.i18n
    )
  } catch {
    return key
  }
}
Enter fullscreen mode Exit fullscreen mode

So what about window.i18n? Where is that coming from? It is something you need to add. It will hold all the defined i18n-keys. You can add it to your application layout or only the view you need it at.

<script>
  window.i18n = <%= raw I18n.backend.send(:translations)[I18n.locale].to_json %>
</script>
Enter fullscreen mode Exit fullscreen mode

And there you have it. Multiple ways of using translated content in your JavaScript and Stimulus controllers.


For more in-depth explanations, specifically on the JavaScript used in the the t() method, check out the upcoming book: JavaScript for Rails Developers.

Heroku

Deploy with ease. Manage efficiently. Scale faster.

Leave the infrastructure headaches to us, while you focus on pushing boundaries, realizing your vision, and making a lasting impression on your users.

Get Started

Top comments (0)

5 Playwright CLI Flags That Will Transform Your Testing Workflow

  • 0:56 --last-failed
  • 2:34 --only-changed
  • 4:27 --repeat-each
  • 5:15 --forbid-only
  • 5:51 --ui --headed --workers 1

Learn how these powerful command-line options can save you time, strengthen your test suite, and streamline your Playwright testing experience. Click on any timestamp above to jump directly to that section in the tutorial!

👋 Kindness is contagious

If you found this post useful, please drop a ❤️ or leave a kind comment!

Okay