One of the few requirements that existed for this blog was to be multilingual. This is something that one can easily do with WordPress these days, but then I got turned on to GitHub Pages and one thing led to another and …. many hours later I had a blog that became multilingual, through mercilessly hacking the beautiful Whiteglass theme. Here is a brief explanation of how I made it happen.
Disclaimer: The code is not pretty. Consider it an MVP. :)
How to turn a Jekyll theme multilingual
Google of course! My thinking was “There are multiple plugins for this in WordPress, there must be one for Jekyll”. And… I was wrong. For Jekyll, only one language at a time is allowed to exist in the world. But I did come across some very useful blog posts:
This one by Sylvain Durand was the most useful and insightful
The one from developmentseed was not as detailed but still insightful in using categories for languages
Finally these two: by drallgood and by Anthony Gaudino are good alternatives, but I already had a theme, I was limited in terms of plugins because of GitHub Pages and did not want to keep using translations inside the posts themselves
Yous, the person who coded the Whiteglass theme, was super helpful and although he/she said that making the theme multilingual was not in the roadmap, he/she offered some tips on how to do it.
In _config.yml I changed three things (relevant to the multilingual business)
{% raw %}
lang: en
languages: ["en", "pt"]
permalink: /:categories/:year/:month/:day/:title/
{% endraw %}
Line 1 establishes the general default language of the blog
Line 2 establishes an array of possible languages
Line 3 establishes that the URLs should include categories which will be used later
The _data/navigation.yml file was changed to add menus for the different languages as well
{% raw %}
languages:
- language: "en"
links:
- title: "About"
url: /about/
- title: "Archives"
url: /archives/
- title: "GitHub"
url: https://github.com/minac
- title: "pt"
url: /pt/
- language: "pt"
links:
- title: "Sobre"
url: /sobre/
- title: "Arquivos"
url: /arquivos/
- title: "GitHub"
url: https://github.com/minac
- title: "en"
url: /
{% endraw %}
There is no explaining to do, two languages, two subtrees with the correct URLs for either.
Then I needed to incorporate that into the _includes/header.html
{% raw %}
{% for item in site.data.navigation.languages %}
{% if item.language == page.lang %}
{% for link in item.links %}
{% if link.url contains "http" %}
{% assign url = link.url %}
{% else %}
{% assign url = link.url | relative_url %}
{% endif %}
<a class="page-link" href=""></a>
{% endfor %}
{% endif %}
{% endfor %}
{% endraw %}
Only the first 2 lines are important here, a loop and an if clause to show the navigation in the right language.
Then the magic starts happening in the _layouts/archive.html
{% raw %} This <h1 class="page-heading">Blog Archive</h1>
becomes this <h1 class="page-heading"></h1>
. To avoid hard coding titles. {% endraw %}
This {% raw %}{% for post in site.posts %}{% endraw %} becomes this:
{% raw %}
{% assign posts=site.posts | where: "lang", page.lang %}
{% for post in posts %}
...
{% endfor %}
{% endraw %}
So that the archives page is language aware.
And finally this:
{% raw %}
<span class="post-meta">{% if post.categories != empty %} • {% include category_links.html categories=post.categories %}{% endif %}</span>
{% endraw %}
Becomes this:
{% raw %}
<span class="post-meta">{% if post.tags != empty %} • {% for tag in post.tags %}{% endfor %}{% endif %}</span>
{% endraw %}
Because we are using categories for the languages, I went with tags for the categorization of the posts. That way each post will have one or more tags but the URL will only reflect the categories themselves.
Once the hard work is done, the _layouts/page.html and _layouts/post.html become easy.
Here’s some meta data added to the page’s post-header:
{% raw %}
<header class="post-header">
<h1 class="post-title"></h1>
<p class="post-meta"><time datetime="" itemprop="datePublished"></time> • {% assign pages=site.pages | where: "ref", page.ref | sort: 'lang' %}{% for page in pages %}<a href="" class=""></a> {% endfor %}</p>
</header>
{% endraw %}
And some meta data added to the post’s post-header:
{% raw %}
<p class="post-meta"><time datetime="" itemprop="datePublished"></time>{% if page.tags != empty %} • {% for tag in page.tags %} • {% endfor %}{% endif %} {% assign posts=site.posts | where: "ref", page.ref | sort: 'lang' %}{% for post in posts %}<a href="" class=""></a> {% endfor %} • words</p>
{% endraw %}
I decided out of lazyness to use the index.html
instead of home
which the theme author suggests. Here are the contents:
{% raw %}
<div class="home">
{% capture site_lang %}{% endcapture %}
{% assign posts=site.posts | where:"lang", page.lang %}
<ul class="post-list">
{% for post in posts %}
{% capture lang %}{% if post.lang != site_lang %}{% else %}{% endif %}{% endcapture %}
<li{% if lang != empty %} lang=""{% endif %}>
<header class="post-header">
<h1 class="post-title">
<a class="post-link" href="">{% if post.external-url %} →{% endif %}</a>
</h1>
<p class="post-meta">{% if post.tags != empty %} • {% for tag in post.tags %} • {% endfor %}{% endif %}</p>
</header>
<div class="post-content">
</div>
{% if post.content contains site.excerpt_separator %}
<p class="post-continue">
<a href="">Read on →</a>
</p>
{% endif %}
</li>
{% endfor %}
</ul>
{% include pagination.html %}
</div>
{% endraw %}
Line 2, 3 and 6 are the only ones that matter for this purpose.
After all this was done I needed to create Portuguese pages for the English equivalents. So about.md
got a sister sobre.md
and archives.md
got arquivos.md
. The contents are not relevant.
So in the end this is the tree of files of the project:
{% raw %}
./_config.yml
./_data
./_data/navigation.yml
./_includes
./_includes/fonts.html
./_includes/footer.html
./_includes/footer_content.html
./_includes/google_analytics.html
./_includes/head.html
./_includes/head_custom.html
./_includes/header.html
./_includes/pagination.html
./_layouts
./_layouts/archive.html
./_layouts/category_archives.html
./_layouts/default.html
./_layouts/feed.xml
./_layouts/page.html
./_layouts/post.html
./_sass
./_sass/whiteglass
./sass/whiteglass/base.scss
./sass/whiteglass/layout.scss
./sass/whiteglass/syntax-highlighting.scss
./_sass/whiteglass.scss
./about.md
./sobre.md
./archives.md
./arquivos.md
./assets
./assets/main.scss
./feed.xml
./Gemfile
./index.html
./en
./en/_posts
./en/_posts/2017-03-04-new-blog-new-life.md
./pt
./pt/_posts
./pt/_posts/2017-03-04-new-blog-new-life.md
./pt.html
{% endraw %}
Most of the files were already part of the theme. The ones that I would like to draw your attention to are on lines 26/27, 28/29 and the English directory 35-37 and the Portuguese directory 38-40. pt.html
is the unimaginatively named Portuguese index.html.
So now that all of that is done, how does one create a new post in Portuguese and English (or either one)?
Well, see ./en/_posts/2017-03-04-new-blog-new-life.md
and ./pt/_posts/2017-03-04-new-blog-new-life.md
? Here are the respective Front matters:
{% raw %}
---
layout: post
title: "New blog, new life"
tags: [blog]
author: "Miguel David"
date: 2017-03-04 16:12:07 +0000
lang: en
ref: new-blog-new-life
---
{% endraw %}
And for the Portuguese one:
{% raw %}
---
layout: post
title: "Novo blog, nova vida"
tags: [blog]
author: "Miguel David"
date: 2017-03-04 16:12:07 +0000
lang: pt
ref: new-blog-new-life
---
{% endraw %}
What distinguises them (besides the contents after the front matter) is their localized title
and lang
. Everything else is the same, including the ref
erence which is used to change between both languages when looking at the specific post!
I hope this helps!