gobuffalo

Since we started working on our business-card website using Buffalo, we might have extended our business to serve clients from all over the world. That's great, but how can we do that if our website is in English only? We definitely need to allow users to use different languages and provide appropriate content for that.

How does it work?

Building a multilingual website requires you to transform the way you display all the texts. Instead of hard-coding them in your HTML templates, you need to select some values depending on a language that should be used at given point. The flow is more or less common: create a file where all translatable texts in a file, with each message identified by some ID. Then, when the content is being rendered, that key alongside a language ID results in a concrete translation.

In order to localize (make it multi-lingual) our business-card, we need to search for locales directory and duplicate all.en-us.yaml file to have another one (all.pl-pl.yaml for me since I'm Polish native):

# For more information on using i18n see: https://github.com/nicksnyder/go-i18n
- id: welcome_greeting
  translation: "Welcome to Buffalo (EN)"

At this point, we have a few places where we need to add localizations: header, footer, and each subpage. Once we add those, each locales file should have a common structure like this:

# all.en-us.yaml
- id: page-subtitle
  translation: My very personal business card.
- id: footer-prefix
  translation: Powered by

- id: menu-home
  translation: Home
- id: menu-resume
  translation: Resume
- id: menu-contact
  translation: Contact

- id: home-welcome
  translation: "Welcome to my business card! Here you can learn a lot about me: who am I, what do I do, what are my experiences. You can also find a way to contact me if you believe it's worth it. It is!"

- id: resume-1
  translation: "[EN] Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla ac ultricies leo, 
  ...
- id: resume-2
  translation: "[EN] Nam porta mattis suscipit. Aliquam id ullamcorper nisi. Proin quis mollis ipsum. 
  ...

- id: contact-message
  translation: "You can contact using one of the following:"

Obviously, our all.pl-pl.yaml file contains the same keys with messages translated into Polish. That was the easy part, now we need to understand how to use those messages and how does the i18n mechanism work in Buffalo.

It all comes down to a middleware initialized in actions/app.go (this is where all the important stuff is done). First, it searches for a list of supported languages based on the filenames in the locales directory. It is done using packr package, which walks through all the files and extracts both the language codes (from filenames) and the translations (defined inside).

The second part of the process involves recognizing what language is currently selected to be displayed. This is done using cookies (lang cookie) or HTTP headers (Accept-language). If none of those provide the information, it falls back on a default value (en-US by default, but this can be changed in actions/app.go where the middleware is being initialized).

Finally, in order to use those messages in our templates, the middleware adds a special function to buffalo.Context (therefore it's available in HTML later on) simply called t which takes a single argument: the key of a message from locales file and translates it into a previously selected language. Using a function in an HTML template is easy, and it looks like this:

<!-- templates/home.html -->
<div class="home">
    <p>
        <%= t("home-welcome") %>            
    </p>
</div>

Now we can replace all messages in each template with its localized version.

i18n in action

We can test our solution using curl command by defining Accept-language header:

$ curl -X GET http://localhost:3000/ -H 'accept-language: en-us'
<html>
  ...
  <body>
    ...
          <h2 class="subtitle">
            My very personal business card.
          </h2>
    ...
    <p>
        Welcome to my business card! Here you can learn a lot about me: who am I, what do I do, what are my experiences. You can also find a way to contact me if you believe it&#39;s worth it. It is!            
    </p>
    ...
    <footer>
        Powered by <a href="http://gobuffalo.io/">gobuffalo.io</a>
    </footer>
    ...
  </body>
</html>

$ curl -X GET   http://localhost:3000/   -H 'accept-language: pl-pl'
<html>
  ...
  <body>
    ...
          <h2 class="subtitle">
            Moja bardzo osobista wizytówka.
    ...
    <p>
        Witaj w mojej wizytówce! Tutaj dowiesz się wiele o mnie: kim jestem, co robię, jakie mam doświadczenie. Możesz też się ze mną skontaktować, jeśli uznasz że warto. A warto!            
    </p>
    ...
    <footer>
        Napędzane przez <a href="http://gobuffalo.io/">gobuffalo.io</a>
    </footer>
    ...
  </body>
</html>

That certainly works, but we might want to provide our users with some way to change the language on the website itself. In order to do that we'll add a simple Java Script snippet to assets/application.js that adds a lang cookie with an appropriate value:

// assets/application.js
window.switchLanguage = function (lang) {
    document.cookie="lang=" + lang
    window.location.reload()
}

This certainly is not the best piece of JS that you've ever seen, but it gets the job done (and since we are not using JS anywhere else in the project, it doesn't make sense to add React or Angular, so don't yell at me). The second part is adding a switch on the page, so we create a piece of HTML:

<!-- templates/application.html -->
...
<header>
  <div class="language-switch">
    <div class="language-switch__option language-switch__option--en" onclick="switchLanguage('en-us')">EN</div>
    <div class="language-switch__option language-switch__option--pl" onclick="switchLanguage('pl-pl')">PL</div>
  </div>
...

This time we bind our JS function to two clickable elements on the page, and we can see how our page translates itself.

The full source code of this example is available on Github.