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 :

langue

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.

langue

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 :

langue

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 :

langue

If I click on “English”, I’m redirected to English version.

langue

Currently, this article is the only one to be in the en folder, so Huge sees only this one.

And voilΓ  !