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. :)
What you can learn from this post
- How to turn a Jekyll theme multilingual
Where I started
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
So I was stuck, and turned back to the author of the theme
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.
Here’s how…
_config.yml
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
_data/navigation.yml
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.
_includes/header.html
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="{{ url }}">{{ link.title }}</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.
_layouts/archive.html
Then the magic starts happening in the _layouts/archive.html
This <h1 class="page-heading">Blog Archive</h1>
becomes this <h1 class="page-heading">{{ page.title }}</h1>
. To avoid hard coding titles.
This {% for post in site.posts %}
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">{{ post.date | date: "%b %-d, %Y" }}{% if post.categories != empty %} • {% include category_links.html categories=post.categories %}{% endif %}</span>
{% endraw %}
Becomes this:
{% raw %}
<span class="post-meta">{{ post.date | date: "%b %-d, %Y" }}{% if post.tags != empty %} • {% for tag in post.tags %}{{ tag }}{% 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.
_layouts/page.html and _layouts/post.html
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">{{ page.title | escape }}</h1>
<p class="post-meta"><time datetime="{{ page.date | date_to_xmlschema }}" itemprop="datePublished">{{ page.date | date: "%b %-d, %Y" }}</time> • {% assign pages=site.pages | where: "ref", page.ref | sort: 'lang' %}{% for page in pages %}<a href="{{ page.url }}" class="{{ page.lang }}">{{ page.lang }}</a> {% endfor %}</p>
</header>
{% endraw %}
And some meta data added to the post’s post-header:
{% raw %}
<p class="post-meta"><time datetime="{{ page.date | date_to_xmlschema }}" itemprop="datePublished">{{ page.date | date: "%b %-d, %Y" }}</time>{% if page.tags != empty %} • {% for tag in page.tags %}{{ tag }} • {% endfor %}{% endif %} {% assign posts=site.posts | where: "ref", page.ref | sort: 'lang' %}{% for post in posts %}<a href="{{ post.url }}" class="{{ post.lang }}">{{ post.lang }}</a> {% endfor %} • {{ content | number_of_words }} words</p>
{% endraw %}
index.html
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 %}{{ site.lang | default: "en" }}{% endcapture %}
{% assign posts=site.posts | where:"lang", page.lang %}
<ul class="post-list">
{% for post in posts %}
{% capture lang %}{% if post.lang != site_lang %}{{ post.lang }}{% else %}{{ site_lang }}{% endif %}{% endcapture %}
<li {% if lang != empty %} lang="{{ lang }}"{% endif %}>
<header class="post-header">
<h1 class="post-title">
<a class="post-link" href="{{ post.url | relative_url }}">{{ post.title | escape }}{% if post.external-url %} →{% endif %}</a>
</h1>
<p class="post-meta">{{ post.date | date: "%b %-d, %Y" }}{% if post.tags != empty %} • {% for tag in post.tags %} {{ tag }} • {% endfor %}{% endif %}</p>
</header>
<div class="post-content">
{{ post.excerpt }}
</div>
{% if post.content contains site.excerpt_separator %}
<p class="post-continue">
<a href="{{ post.url | relative_url }}">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.
‘about’ becomes ‘sobre’, ‘archives’ become ‘arquivos’
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.
File tree
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)?
Front matter for new posts
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!