Create a multilingual blog with Hugo
Writing in your native language allow to stay in your comfort zone. But on the Web, it can limit our audience. That’s what I’m telling myself since a couple of time, so I took a look to the multilingual options in order to translate some posts in English. Typically, this article is the first one.
As this blog is made with the static website generator Hugo, I propose to explain how I proceed to activate its multilingual support. There is no mysteries, this post is mainly based on the official documentation.
Our goals will be the following ones :
- Adapt the main menu according to the language
- Adapte template texts to it
- Allow the visitor to choose the article language if available
Activate the multilingual mode
Hugo is multilingual by design, you just need to indicate it in the config.yaml
file (because I’m using the YAML version of the configuration, I’m not a big fan of Toml).
First, I’ve set the following instructions :
DefaultContentLanguage: fr
DefaultContentLanguageInSubdir: true
The first parameters says that the default language will be French with fr
. The second one tells Hugo to look inside sub folders named after the language in order to find the content (ex : content/en
or content/fr
).
You have different ways to enable the multilingual support. One by naming your files with the language code (ex : about.en.md
), or by placing them inside a sub-folder named after it (ex : content/en/about.md
and content/fr/about.md
).
In this article, we’ll use the sub-folder way.
Having a main menu adapted with the chosen language
This part depends on your template and how its main menu is displayed. However, the settings seems to be generic.
In the config.yaml
file, we set an entry for each main menu section attached to the language. Before that, this blog main menu was set like this :
params:
mainSections: ["posts"]
description: "Tout est dans le titre !"
menu:
main:
- identifier: "about"
name: "π€ A Propos"
url: "/about/"
weight: 10
- identifier: "contact"
name: "π¬ Contact"
url: "/contact/"
weight: 20
# ...
The menu
entry is replaced by languages
. In this element, you’ll add a sub section for each language you want to have on your blog. For example in my case, en
and fr
.
For each language, you’ll add a menu
, then a main
section inside it, and finally, an identifier
value.
In the languages
section, you can specify some elements related to the blog settings. You may notice that the params.description
setting was replaced by languages.fr.description
and languages.en.description
.
Here is the result :
languages:
fr:
contentDir: "content/fr"
languageName: "Français"
weight: 1
description: "Tout est dans le titre !"
menu:
main:
- identifier: "about"
name: "π€ A Propos"
url: "/fr/a-propos/"
weight: 10
- identifier: "contact"
name: "π¬ Contact"
url: "/fr/contact/"
slug: "contact"
weight: 20
# (....)
en:
contentDir: "content/en"
languageName: "English"
weight: 2
description: "It's all in the title !"
menu:
main:
- identifier: "about"
name: "π€ About"
url: "/en/about/"
weight: 10
- identifier: "contact"
name: "π¬ Contact"
url: "/en/contact/"
slug: "contact"
weight: 20
# (...)
Adapt your template
The Casper3 template I utilize use a subfolder content/posts
to display the articles. This is the params.mainSections: ["posts]"
setting.
I’ve modified nothing in this case. However, I’ve added some elements like the language selection in the main menu, some translations on static template texts, and a like for English/French version of a post when available.
Display the supported languages in your blog
At the expected main menu place in your template, add this :
{{ range $.Site.Home.AllTranslations }}
<li><a href="{{ .Permalink }}">{{ .Language.LanguageName }}</a></li>
{{ end }}
You should have something like that :
Translate the static elements
If your template has static text displayed in the code, you can translate it. In my case, the “Egalement disponible dans la langue suivante” and “Table des matiΓ¨res” were hardcoded. We need to adapt them to the displayed language.
You can specify these values in a i18n/
folder created at your Hugo site root. Inside it, you create a file named after the various languages you’ll use : en.yaml
and fr.yaml
in my case. The file must be named after the language codes specified by the RFC 5646 (so en.yaml
or fr-FR.yaml
).
In these files, we put the technical identifier for the sentence and the translated value.
# File : fr.yaml
table_of_content: # Text identifier
other: Table des matières # Displayed value
translations:
other: 'Egalement disponible dans la langue suivante :'
# File : en.yaml
table_of_content:
other: Table of Content
translations:
other: 'Also available in the following language :'
In your template, where the text to translate is located, replace it with that :
<section>
<h2>Table des matières</h2>
{{.TableOfContents}}
</section>
<!--- becomes : -->
<section>
<h2>{{ i18n "table_of_content" }}</h2>
{{.TableOfContents}}
</section>
Be careful, the text identifier must be encapsulated by double quotes only.
Display which languages are available for an article
I’ve put this before the table of content above an article. To display the available languages, just add this part :
{{ if .IsTranslated }}
<section>
<!-- shows the translated title -->
<h4>{{ i18n "translations" }}</h4>
<ul>
{{ range .Translations }}
<li>
<a href="{{ .Permalink }}">{{ .Lang | upper }} : {{ .Title }}{{ if .IsPage }}{{ end }}</a>
</li>
{{ end }}
</ul>
</section>
{{ end }}
Result :
Reorganize your content
Sort according to the language
This part consists in reorganize the content/
folder content where your articles are. Before, I was organized like this :
content
|ββ about.md
βββ contact.md
βββ posts
βΒ Β βββ blog_hugo_multilingue.md
βββ soutiens.md
βββ support.md
βββ uses.md
The files present at the root of content/
are for the main menu sections. The posts/
sub-folder contains my articles.
To separate translated articles, we create a en/
and fr/
inside /content
.
Then, I moved all markdown files and the posts/
sub folder inside fr/
because all of my content is in French.
After this, I’ve created the en/
sub folder in /posts
and I’ve copied the about.md
, contanct.md
, etc, into it en/
in order to translate them.
Now, this is what I have :
content
βββ en
βΒ Β βββ about.md
βΒ Β βββ coffee.md
βΒ Β βββ contact.md
βΒ Β βββ posts
βΒ Β βΒ Β βββ blog_hugo_multilingue.md
βΒ Β βββ supporting.md
βΒ Β βββ uses.md
βββ fr
βββ about.md
βββ contact.md
βββ posts
βΒ Β βββ blog_hugo_multilingue.md
(...)
βββ soutiens.md
βββ support.md
βββ uses.md
Translate the URLs
Hugo needs to have the same filename for an article in en/
and fr/
folders in order to match them between their respective languages. However, having posts links translated into the associated language is also more appropriate, and better for indexing.
Example :
fr/posts/creer-un-blog-multilingue-avec-hugo/
en/posts/create-a-multilingual-blog-with-hugo/
But the files are named :
./content/fr/posts/blog_hugo_multilingue.md
./content/en/posts/blog_hugo_multilingue.md
To use this, you just need to use the slug
parameters in the article headings.
French version :
# file : ./content/fr/posts/blog_hugo_multilingue.md
---
title: "CrΓ©er un blog multilingue avec Hugo"
slug: "creer-un-blog-multilingue-avec-hugo"
---
English version :
# file : ./content/en/posts/blog_hugo_multilingue.md
---
title: "Create a multilingual blog with Hugo"
slug: "create-a-multilingual-blog-with-hugo"
---
While rendering, Hugo will create the links /fr/creer-un-blog-multilingue-avec-hugo
et /en/create-a-multilingual-blog-with-hugo
for each language.
Warning, if you use the url
parameter in your articles headings, you’ll need to specify the language code before : fr/a-propos
where slug
expects only the article identifier.
Result
Let’s see how it is going !
French is the default language, so I’m redirected automatically :
If I click on “English”, I’m redirected to English version.
Currently, this article is the only one to be in the en
folder, so Huge sees only this one.
And voilΓ !