Adding languages to a Hugo site
Andrew Owen
Posted on March 16, 2023
This article originally appeared on my personal dev blog: Byte High, No Limit.
I'm a long-term advocate for localization, but this site has been monolingual for over a year now. It's past time I started following my own advice. So last weekend I finally got around to localizing the site for French.
As with most localization tasks, I'm retrofitting it to an existing project. Fortunately, I chose Hugo as my static site generator, and it has built-in internationalization support using go-i18n. On the downside, I chose a theme that doesn't fully support localization, which meant there was some work involved. But not as much as I'd expected. So let me take you through the steps involved.
First, I updated my config.toml
file to tell Hugo that my site is now multilingual:
defaultContentLanguageInSubdir = false
DefaultContentLanguage = "en-us"
[languages]
[languages.en-us]
title = "Andrew Owen | Writer | Designer"
languageName = "English"
weight = 1
[languages.fr]
title = "Andrew Owen | Écrivain | Concepteur"
languageName = "Français"
weight = 2
Setting defaultContentLanguageInSubdir
leaves the language out of the URL for the default language, in my case US English. Any settings you leave out from the language definitions will fall back to the default.
My 404.md
page is already stored in a folder called en
. So adding a French version was just a case of creating a new folder called fr
, copying the file across and changing the text.
The next step was to set up some a set of translatable phrases for the generated content. This is as simple as creating an i18n
folder in the root of the Hugo repository and adding a YAML or TOML file for each language. In my case en-us.yaml
and fr.yaml
. These phrases are included using the shortcode {{T "phrase" | formatting}}
. If you want to include HTML such as <br>
tags, then use safeHTML
for the formatting
value. Each value should have an ID and a definition in each language file. For example:
- id: January
translation: "janvier"
In several places in the site, titles were derived from data contained in YAML files. In each of these cases, I replaced the data-derived version with a shortcode to the translated version instead. I also had to modify some of the links to go to the correct place when viewing the site in French. In most cases, that just meant using the .Site.Language.Lang
value.
Because I'm only supporting two languages, I wanted to have a simple toggle. The nav bar was starting to get rather full, so I removed the Home
link, because the logo already takes you there. I had to change the RSS link to make it work in French, so I also replaced it with the icon. I figure anyone who still cares about RSS should recognize it:
<li class="nav-item">
{{ if eq .Site.Language.Lang "en-us" }}
<a class="nav-link" href="/blog/index.xml"><i class='fa fa-rss'></i></a>
{{ else }}
<a class="nav-link" href="/fr/blog/index.xml"><i class='fa fa-rss'></i></a>
{{ end }}
</li>
The last thing to do was to translate the individual articles. Hugo gives you two ways of doing this. You can set up a folder for each language, and if you have three or more languages, you should do this. But by default, Hugo will treat anything ending in .md
as the default language, and you can add other languages by adding the language code to the extension, for example: .fr.md
. If you're using folders, then you add the folder details to the language definitions in your TOML file. For example:
[languages]
[languages.en]
contentDir = "content/english"
While I was editing the site in VScode, I also took the opportunity to reorganize the images. They were getting a bit tricky to navigate with TinaCMS, so I moved the blog images into folders by year. I also did some cleanup on the top nav bar. The last thing to do was change the article date string from {{ .PublishDate.Format “2 January 2006” }}
to {{.Date.Day}} {{i18n .Date.Month}} {{.Date.Year}}
after setting up a set of translatable date strings in the YAML files in the i18n
folder.
Of course, just when I thought I'd caught everything, I noticed that tag links were broken. I fixed this by making the URL relative so that it would pick up the current language. I'm pleased to say that I didn't have to do anything at all to localize search.
With the technical piece done, all that's left to do is the translation. There are ways to automate the process, but there's still no substitute for a human reviewer. I'm using a combination of DeepL, LanguageTool and my own modest language ability. To begin with, I'm only translating the core content. But eventually I intend to have a fully translated site. If you are a native French user, and you spot any egregious mistakes, please drop me an email.
Posted on March 16, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.