What you need to internationalize your Jekyll website

Hello, there! If you are looking for information regarding the internationalization of Jekyll websites, you have come to the right place. Here, we are going to talk about how to add i18n and develop a nice way to switch between available languages.

We are going to start from the point where you already have a Jekyll website running but got stuck on how to translate your content straightforwardly. However, I know many of you may not know much about Jekyll and are here for curiosity purposes, so let’s walk you through some general information first so everyone can follow us along nicely.

Jekyll

What is it?

If you haven’t heard of Jekyll before, let me introduce you to the concept given by Jekyll’s documentation:

Jekyll is a static site generator. It takes text written in your favorite markup language and uses layouts to create a static website. You can tweak the site’s look and feel, URLs, the data displayed on the page, and more.

Jekyll was released in 2008 and is written in ruby. It became highly popular mainly for being adopted by GitHub Pages.

With Jekyll, you can create different pages for your website and even posts to your blog. That’s right, building a blog is one of the reasons to choose Jekyll: it has features specifically for it. However, many company websites use Jekyll as well.

Content

Before we can dive into the world of an internationalized Jekyll, let’s talk about what it can offer us.

You can build different pages in your root directory or by using a specific directory structure and use permalinks; create posts that, in the correct directory and the correct name structure will be recognized as posts… but let’s focus on what really matters moving forward for us: Jekyll’s front matter, data files, includes and the usage of Liquid.

Front matter

The front matter is an area at the top of your HTML or markdown file where you can write variables and content for your page. It is a YAML block and it must be the first part of your file. To be valid, it must be between two sets of triple-dashed lines, as you can see below:

---
layout: post
title: Kinds of food
---

All variables set there will be available throughout your page and can be accessed like this:

---
food: Pizza
---

<h2>{{ page.food }}</h2>

Data files

Okay, now that we have an idea of what the front matter does, let’s talk about data files. The _data directory might have files supported by Jekyll, such as YAML, JSON, CSV, TSV and these will be available as variables throughout your application.

For example, you might have a list of food in a yaml file in the _data directory and access this list as a variable in any of your pages by referencing it correctly (add pic).

In your _data directory:

#_data/foods

- type: Pizza
- type: Pretzel
- type: Lasagna

In your HTML:

<ul>
{% for food in site.data.foods %}
  <li>
      {{ food.type }}
  </li>
{% endfor %}
</ul>

Isn’t it similar to what the front matter does? Yes, indeed. But this way you might access the same variables in your whole application without repeating yourself.

Includes

Jekyll makes it available for you to separate content inside an _includes directory. This means that basically, you can have something similar to components and, therefore, separate your pages’ smaller and shared parts, such as header and footer, as well as other content that you might find that becomes more readable when breaking down in different files.

All this content will be accessed using the include tag:

{% include header.html %}

There are more applications to this tag, and you can check it out here.

Liquid

Liquid is a templating language used by Jekyll. You already saw it above to access variables and other content. The double curly braces {{ }} will output content, while using curly braces along with percentage signs will perform logic statements {% %} . We will see more examples using liquid below, just hold on one second.

Configuration

Jekyll can be very flexible and it gives you many options of how to customize your website. That’s where the _config.yml or _config.toml file comes in: placed in the root directory, this file will include all the configuration options you might add.

There is much more we can say about Jekyll’s content and site structure, but we will try to focus on making an internationalized website here. In case you’re curious to know more about Jekyll and what it can do, feel free to read the documentation.

Polyglot gem

Internationalizing Jekyll

As Jekyll can be used to build blogs, you might think that making it multilingual is an easy job. I’ve read a few blog posts where the blogger wanted to make their posts available in two languages since they have two nationalities, or for a number of other reasons.

Well, you can’t. Jekyll doesn’t support internationalization. You might think, “But it’s written in ruby, what about the i18n gem?”. It doesn’t support it.

“So that’s it?”. Don’t be sad, let me help you out! There are a few ruby gems available, some deprecated — unfortunately. However, the polyglot gem comes to the rescue:

Polyglot is an open source internationalization plugin for Jekyll blogs. Polyglot is easy to setup and use with any project, and it scales to the languages you want to support.

I don’t know if you’ve noticed there, but it says “for Jekyll blogs”. Yeah, that might be a problem if a blog isn’t what you’re working on, right? Wrong! Don’t forget I’m here to help you out. So let’s setup Polyglot (assuming you already have your Jekyll application running):

gem add jekyll-polyglot

After that, we must add to our _config file the following info:

#I18n
languages: ["en", "pt-BR"]
default_lang: "pt-BR"
exclude_from_localization: ["javascript", "images", "css", "public"]
parallel_localization: true

These configurations are setting the languages your website will provide, the default language (fallback), whether I want to exclude files/directories from localization (based on their paths), and whether the language will be processed in parallel or serial.

As I said before, this file should have the general information about your site so you can customize it accordingly. However, it will not provide the data you add here in multiple languages. What to do then? Well, this is where we start talking about our _data directory.

Internationalization

As a tool for blogs, Jekyll has a specific feature for posts, where you can add them to a _posts directory, and Jekyll will do the rest (as long you respect the naming of the file as it expects, as you can see here).

With that in mind, the polyglot gem will let you have files of the same posts in different languages and they will be considered as one. All you have to do is add the correct language to the post’s front matter such as this:

---
lang: en
title: Lots of food
---

This is a nice feature and all, but if you consider a website, I don’t believe it would be nice to double all your content just so you might have them available in two languages. And can you believe if your website is available in three, four or five languages? This wouldn’t be very DRY, would it? So let’s talk about internationalization.

If you’ve worked with Rails before, you probably worked with the i18n gem and have worked with internationalization. Basically, we will have files where you will add whatever information each page needs in directories separated according to the languages your website provides. Well, Jekyll will let you localize your information all right. However, only in one language. That’s where our friend, the polyglot gem, comes in. With it, you’re able to do it:

_data
├── en
│   └── index-page.yml
├── it
│   └── index-page.yml

You might add a file for each of your pages, for your _includes, general website information… Organize it as you wish. After that, you can access the information added to your _data directory *via site.*data.filename.translation (site accesses variables throughout your application, data is the mentioned directory and, after that, you have to provide the file name and the variable you have added).

Okay, but this is a feature naturally provided by Jekyll to localize variables… what changes with polyglot? What changes is that you will add active_lang to this path: site.data.[site.active_lang].filename.translation . That’s it! You have now an internationalized website, congratulations!

This information will be used in your html tags (or another type of file supported by Jekyll and transformed into html after). Like this:

<h3>{{site.data[site.active_lang].index.title}}</h3>

However, it isn’t limited to that. What if you have some variables in the front matter part of your file that you call afterwards using liquid? Could I possibly translate it in the front matter itself? Well, no. Jekyll will not let you do this even with polyglot. You will have to add a file in your _data directory for these variables too. And then access it just as I explained above:

#_data/en/pizzas

- title: Pizzas
  items:
    - name: Peperonni
      toppings: Cheese, peperoni
    - name: Chicken
      toppings: Chicken, cheese, cream cheese
{% for pizza in site.data[site.active_lang].pizzas %}
  <div>
    <h2>{{ food.title }}</h2>
    <ul>
      {% for item in food.items %}
        <li>
          <span>
            {{ item.name }}: { item.toppings }}  
          </span>
        </li>
      {% endfor %}
    </ul>
  </div>
  {% endfor %}

But how does it work? You might ask. Polyglot will provide a new URL for the newly added idiom. So if your website is www.ilovepizza.com , now you will also have www.ilovepizza.com/it (since you will definitely want to discuss pizza with the Italians).

Well, turns out that having two different URLs will bring us some more work. I mean, how does the user know this new URL exists? And what about the SEO? Shouldn’t these be considered just one page, since they are the same?

I would never leave you hanging on these questions, don’t worry about it. I have just what you need (and maybe some extra info you might find neat).

SEO preferences

Since now we have two different URLs, we have to figure out how to handle the SEO. The Search Engine Optimization will optimize the website’s configuration related to web search, relevance, and so on.

However, having two different URLs might become a problem here, since it might be considered two different pages. Well, we are dealing with the same page, just different languages. Do not worry, the polyglot gem has the answer to our problems.

Let’s set the meta tag in our HTML <head>:
<meta http-equiv="Content-Language" content="{{site.active_lang}}">

This will state the language and country where this content is most relevant.

Then we must set the hreflang alternate tags:

<link rel="alternate"
      hreflang="{{site.default_lang}}"
      href="http://yoursite.com{{page.permalink}}" />
{% for lang in site.languages %}
{% if lang == site.default_lang %}
  {% continue %}
{% endif %}
<link rel="alternate"
    hreflang="{{lang}}"
    href="http://yoursite.com/{{lang}}{{page.permalink}}" />
{% endfor %}

Here we’re able to let Google know we have different variations of our website for different languages and web searches will point to the most relevant variation according to the user’s language or region.

Wait, it gets better: all of that can be achieved with just one tag:

{% I18n_Headers %}

So now we have one last thing to worry about. Let’s move on.

Switch between languages

Now that your website has two language options and, consequently, two URLs, let’s improve the user’s experience by adding a button so they can switch between the options (let’s work with two options here, but feel free to explore the multiple of multilingual!).

As I’ve mentioned before, Jekyll uses Liquid templating language to process information. That’s how we access the variable, as I have shown in the images above. I’m telling you this because now you will have to develop some logic to provide the correct language option on your language button.

{% for lang in site.languages %}
    {% assign lang_name = site.data[lang].l10n.lang_name %}
    {% if lang == site.active_lang %}
{{ lang_name }}
    {% else %}
      {% if lang == site.default_lang %}
                <a href=" {{ page.url }}">{{ lang_name }}</a>
      {% else %}
                <a href="/{{ lang }}{{ page.url }}">{{ lang_name }}</a>
      {% endif %}
    {% endif %}
{% endfor %}

This will show the website’s current chosen language and a button for the other languages available. Feel free to tweak it and make it look like you.

Language button to switch between languages
Button to switch between languages.

And that’s it! Your website is now internationalized and fully functional!

Cookies

Okay, so after all of that, let’s give you some extra work: what if we could save the user’s preference to improve the experience? So, if the user has previously visited your website and chosen a specific language, they can find it set once they come back.

And here comes the cookies! We are not going to specifically talk about cookies here and, if you don’t know much about them, you can check the basics out here.

Since the idea is to internationalize your Jekyll’s website, we are considering here your website is already working and that you probably have already set the pertinent cookies. So, the plan is to continue from that point on and add your new cookie key related to the language preference.

I don’t know if you remember from the beginning of this post, but Jekyll is a static website generator. This means we can’t manage cookies with Jekyll only (as you will probably know, if you have already created yours). To do that, we must use JavaScript.

Seeing you are already managing cookies when they are allowed by the user, let’s think about what we must do related to the user’s language preference:

  • Wait until the button is clicked;

    Let’s add an id to the button and add an event listener to it.

  • Check the URL provided by the button;

    We will use the <a></a> tag for the button, so we can get the href and pass it along.

  • Set the cookie with a key and the value as the URL (check if the the cookie already exists before setting and if it matches the current value);

    When we set the cookie, we need to follow the correct syntax with at least the following attributes (others might be passed, including expiration date):

    <cookie-name>=<cookie-value>

    In Jekyll’s case, the cookie name will be provided in our _config file:

    # Website Language Preference Cookie
    lang_pref_cookie_key: lang-pref

and when we set it, we must pass it as a string '{{ site.lang_pref_cookie_key }}' .

  • Check if the the cookie already exists;

    Once the cookie is set, we must consider future website visits and check whether this cookie already exists before moving on.

  • Redirect to the cookie’s URL in case it doesn’t match the current URL.

    We check the URL provided by the cookie and the current website’s URL to check if they match. If they don’t, we must replace it with our cookie’s URL:

    window.location.replace(urlCookie)

  • Update cookie every time the user clicks the button;

    The user’s preference isn’t written in stone, so we must also consider the possibility of multiple button clicks and resetting the cookie. To do so, we have to check if there is already a cookie and if the cookie’s value is different from the current URL. If so, we reset the cookie with the new value.

  • Set cookie path and domain

    As you follow the steps above, you will see the cookie will be set, but doubled… Why is that?

    Now there is one more thing we need to consider: we have two URLs (the default and the /lang) and we must set the cookie as if there’s only one URL. To do that, we need to add some attributes to our cookie syntax when setting it.

    First, let’s talk about the domain:

    Domain=<domain-value>

    Here we have your website’s domain name and it must match exactly with the domain in your cookie, so the cookie will be sent to the user agent. This way we make the cookie available to your domain and its subdomains.

    The other attribute we must cover is the path:

    Path=<path-value>

    The path needs to be set along with the domain and it says what path must exist so the cookie can be requested. We are going to set it as path=/ , since it must be global.

    This way we set the cookie correctly, improve the user’s experience and guarantee the preferred idiom every time it’s necessary.

Conclusion

That was quite a bit of information, wasn’t it? So let’s recap: here we learned how to move forward with a Jekyll website and add multiple language options, even though Jekyll itself doesn’t provide us this option.

The polyglot gem comes as an alternative to this problem to make our lives easier. We went a little further to the point of not only adding a way to switch among languages with a button, but also setting a cookie to improve user experience by setting the URL according to the previously chosen preferences.

I hope this article helps you to internationalize your website and, if you don’t have a Jekyll website yet, try and read the references for more information and get an application running. Then come back here and enjoy this reading once more!

References

We want to work with you. Check out our "What We Do" section!