Creating this Blog: Hugo, making the Hyde theme multilingual, and adding Latex support

Tags: Blog setup , Hugo , Katex

(Español: La creación de este blog: Hugo, agregar al tema Hyde soporte de varios idiomas y soporte de Latex)

This blog does not live in a commercial platform for bloggers. It is what is technically called a “static site”, meaning it is just a collection of html files that are linked between each other and some other formatting files (css, for example). As such, it can be hosted anywhere and is not tied to any particular platform. Moreover, it was created with a fantastic open source tool caled Hugo. In this blog entry I explain how I set it up.

What got me started was Hui Gong’s excellent blog entry on building a personal blog using GitHub Pages and Hugo. I knew about Github Pages, but not about Hugo. I had Hugo running and my own skeleton version of this blog using the very nice and minimal Hyde theme by following the blog.

The Hyde theme is extremely minimal (nice), but I wanted three important things for my blog that the Hyde theme did not have:

  • Browsing by tags: Allow readers to find posts about related topics instead of only seeing a post list by date.
  • Multilingual: Posts and navigation in English and Spanish.
  • $\LaTeX$ support: I wanted to be able to write posts that had math symbols in them, and for me $\LaTeX$ is still the only sensible way to write math.

Getting these set up allowed me to understand the whole Hugo setup, which I must say I really like! Hugo now takes care of almost everything besides the content, which is great.

Browsing by tags or categories

This could not have been simpler! Hugo comes by default with two ways to categorize content: tags and categories. The Hyde theme comes set up to show them in the sidebar. One just needs to define menu entries in the config.toml file, which controls global settings of the site and themes.

I just had to add the following lines to that file, and I was all set!

[[menu.main]]
    name = "Tags"
    identifier = "tags"
    weight = 300
    url = "/tags/"

[[menu.main]]
    name = "Categories"
    identifier = "categories"
    weight = 300
    url = "/categories/"

Now, all I have to make sure is that the header for each post has tags and categories entries, like the example below:

---
title: "Creating this Blog"
date: 2021-12-06T22:49:34-05:00
draft: false
tags : ["Blog setup"]
categories: ["Hugo", "Coding"]
---

The Categories and Tags pages linked from the menu get automagically built from the posts themselves. Great!

Setting up Multilingual Support

This required lots of trial and error, but I finally got it to run.

Initial setup

I added the following code to the config.toml file, which controls global settings of the site and themes, to allow switching between English and Spanish:

defaultContentLanguage = 'en'

[languages]
  [languages.en]
    baseURL= 'https://enriqueacosta.github.io/blog/en/'
    title = 'Enrique Acosta Jaramillo'
    languageName = 'English'
    weight = 1
    description = 'Personal Blog'

  [languages.es]
    baseURL= 'https://enriqueacosta.github.io/blog/es/'
    title = 'Enrique Acosta Jaramillo'
    languageName = 'Español'
    weight = 2
    description = 'Blog personal'

This makes sure that Hugo exports the static HTML site in a very symmetric way: if there is a post (say this one, posts/creating-this-blog.md) with its corresponding Spanish version (posts/creating-this-blog.es.md), then the posts will have parallel URLs:

  • https://enriqueacosta.github.io/blog/en/creating-this-blog/
  • https://enriqueacosta.github.io/blog/es/creating-this-blog/

(The asymmetry of the filenames with the English one not needing the .en. comes from using the command defaultContentLanguage = 'en' in the above code.)

The setup is great, but it broke the way Hugo deals with images! This required some extra tweaking which I describe below.

Adding a link to each post in the other language.

I wanted each post to link to its corresponding version in the other language. This turned out to be much simpler than I imagined!

The file themes/hyde/layouts/_default/single.html is the layout template for a single post. I copied it to layouts/_default/single.html, which gives me a version I can edit and overrides the theme’s file.

In the single.html file, the {{ .Content }} is what tells Hugo to input the content of the post. I added the following code before it:

{{ if .IsTranslated }}
   {{ range .Translations }}
      <h4>(<a href="{{ .Permalink }}">{{ .Language.LanguageName }}: {{ .Title }}</a>)</h4>
   {{ end }}
{{ end }}
{{ .Content }}

What is does is pretty cool! The if statement checks to see if there is a translation (it knows this just from checking if there are other files with the same filename with or without an .es.). If so, it sets up a loop (this is the range) over the available translations and for each one it adds the following link inside the h4 heading <h4>(....)</h4> :

<a href="{{ .Permalink }}">{{ .Language.LanguageName }}: {{ .Title }}</a>

Here:

  • {{ .Permalink }} is the path of the post in the other language.
  • {{ .Language.LanguageName }} will get replaced by the language of the translation (note the config.toml lines above have a languageName = property — this is the name that gets used).
  • {{ .Title }} is the title of the post of the translation it is linking to.

That is all! It takes care of adding those links to all posts in the output:

Screenshot of post translation link

This all helps to understand how Hugo works: it is a collection of “html templates/snippets” that instruct Hugo how to build the HTML site to host the content that one writes. Very nice!

Making dates change language automatically.

All post dates were being displayed in English, even if the post was in Spanish. Fixing this also turned out to be pretty simple. The original line in the index.html file instructing Hugo to add the date date of the post was:

<time datetime={{ .Date.Format "2006-01-02T15:04:05Z0700" }} class="post-date">{{ .Date.Format "Mon, Jan 2, 2006" }}</time>

Here, the {{ .Date.Format "Mon, Jan 2, 2006" }} is what was putting in the date of the post. After some internet search I figured out I had to change into one that was “language aware” given by {{ .Date | time.Format ":date_long" }}.

According to the language {{ .Date | time.Format ":date_long" }} will display either (note that the month is correctly not capitalized in Spanish):

  • December 6, 2021
  • 6 de diciembre de 2021

So, the post date input in the index.html file now looks like this:

<time datetime={{ .Date.Format "2006-01-02T15:04:05Z0700" }} class="post-date">{{ .Date | time.Format ":date_long" }}</time>

And that was it! Now post dates change according to language.

Fixing image support with the above multilingual setup

(Note: this hack is needed for Hugo version v0.89.4+extended, it may not be needed for later versions.)

The issue with images and the multilingual setup in the config.toml I described above came from the fact that I wanted the base url to be https://enriqueacosta.github.io/blog/, but Hugo does not like base urls having directory structure (/blog/ in this case). It was stripping it from the hrefs for the images in the output, so the browser would point to the wrong place in the output html files when trying to load the image. This is a known issue that has not been fixed (see here and here).

Note: The hack/fix that follows is not needed if the base URL changes to something like https://enriqueacosta.github.io.

The fix involved learning about the <base> tag in html. It turns out the <base> tag can be used in the <head> in plain html to specify the “previous” path of all relative paths in the page. So, if an image in HTML has an href="/here/there/image.png", then the <base> tag instructs the broswer to look for the image in <base href>/here/there/image.png. Thus, getting the following code inside the <head> of the output html pages would do the trick:

<base href="{{ .Site.BaseURL }}">

This is accomplished by adding the line to the hook_head_end.html file in

/layouts/partials/hook_head_end.html

This file overrides the file with the same filename in the theme

/themes/hyde/layouts/partials/hook_head_end.html

(which is where I copied it from). This “partial” file gets loaded at the end of the layouts/partials/head.html file.

After this, I could reference the image file fig-twistedCubic2.png located at static/fig-twistedCubic2.png in markdown by using :

![Twisted Cubic](fig-twistedCubic2.png)

Note: this is different from the “regular” syntax/strategy for inserting images in Hugo, since without the <base> hack above, figure referencing should use a /:

<!--- 
Regular Hugo syntax for imputing the 
image at "static/fig-twistedCubic2.png" 
-->
![Twisted Cubic](/fig-twistedCubic2.png)

Note: this also (unfortunately) breaks the Table of Contents built-in functionality in Hugo, because it relies on using relative links of the form href="#heading-title" which due to the <base> command en up pointing to

broken href: {{base url}}/#heading-title

instead of where they should:

correct href: {{base url}}/{{ Lang }}/posts/{{post title}}#heading-title

Note: I am still looking for a better solution to this issue, because using a <base> tag seems pretty radical and may break other things besides the Table of Contents functionality.

Adding a language selector to the sidebar

Having “other language” links in each post was nice, but I wanted the blog to really feel multilingual by having a language selector in the side bar. That way if one pressed “English” or “Spanish” in the sidebar, the content would switch language.

This also turned out to be pretty simple. I must say I am very impressed at all the things Hugo is doing seamlessly in the background.

The template for the sidebar is in layouts/partials/sidebar.html and to my copy (which overrides the theme one) I added the lines:

{{ if .IsTranslated }}
  <nav class="LangNav">
  {{ range .AllTranslations }}
    <a href="{{ .Permalink }}">{{ .Language.LanguageName }}</a>
  {{ end }}
  </nav>
{{ end }}

And that is all! Since Hugo knows the language of the content just from the filename, changing the language in the navigation has Hugo look for the content with the right language filename.

Making the menus be multilingual too

The last little piece to make the site feel truly multilingual was getting the sidebar menu to change language. I replaced the lines that created the Categories and Tags menu entries in the config.toml file y described above by the following Language-specific ones:


[[languages.en.menu.main]]
    name = "Tags"
    identifier = "tags"
    weight = 300
    url = "/tags/"

[[languages.en.menu.main]]
    name = "Categories"
    identifier = "categories"
    weight = 300
    url = "/categories/"

[[languages.es.menu.main]]
    name = "Etiquetas"
    identifier = "tags"
    weight = 300
    url = "/tags/"

[[languages.es.menu.main]]
    name = "Categorias"
    identifier = "categories"
    weight = 300
    url = "/categories/"

Note the url entry in English and Spanish is the same. This is the very important because it has Hugo stops Hugo from showing Spanish tags in the list of tags when Site is in English, and vice versa.

Adding $\LaTeX$ support

Adding $\LaTeX$ support also ended up being way simpler than I expected. I discovered there were some annoyances with using Mathjax and Hugo, like having to escape _ characters, which are used very often in Latex, by writting \_. Apparently in some setups it also involved using \\ instead of \ all over the place (even worse since all Latex commands start with \). I discovered that KaTeX integration had less of these pitfalls, so even though MathJax is more widely used than Katex, I decided to go with KaTeX integration.

Following this blog post, adding Katex support one involved adding the following code inside the <head> tag in any page I wanted it, so I decided to add it to my hook_head_end.html file. This is the code:

<!-- Katex Support-->
<!-- For a way to do this that only loads KaTeX if it is "invoked" in a post, see:
https://dzhg.dev/posts/2020/08/how-to-add-latex-support-in-hugo/ -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/katex.min.css" integrity="sha384-zB1R0rpPzHqg7Kpt0Aljp8JPLqbXI3bhnPWROx27a9N0Ll6ZP/+DiW/UqRcLbRjq" crossorigin="anonymous">

<!-- The loading of KaTeX is deferred to speed up page rendering -->
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/katex.min.js" integrity="sha384-y23I5Q6l+B6vatafAwxRu/0oK/79VlbSz7Q9aiSZUvyWYIYsd+qj+o24G5ZU2zJz" crossorigin="anonymous"></script>

<!-- To automatically render math in text elements, include the auto-render extension: -->
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/contrib/auto-render.min.js" integrity="sha384-kWPLUVMOks5AQFrykwIup5lo0m3iMkkHrD0uJ4H5cjeGihAutqP0yW0J6dpFiVkI"
crossorigin="anonymous"
onload='renderMathInElement(document.body);'></script>

<script>
    document.addEventListener("DOMContentLoaded", function() {
        renderMathInElement(document.body, {
            delimiters: [
                {left: "$$", right: "$$", display: true},
                {left: "\\[", right: "\\]", display: true},
                {left: "$", right: "$", display: false},
                {left: "\\(", right: "\\)", display: false}
            ]
        });
    });
</script>

With this, I can now write math in any place using $...$. For example, $\int_{a_1}^{b_1} f_1(t)\thinspace dt$ renders like this: $\int_{a_1}^{b_1} f_1(t)\thinspace dt$.

I also can now have “displayed math” (centered in a new line) with the usual $\LaTeX$ delimiters $$...$$. For example, $$\int_{a_1}^{b_1} f_1(t)\thinspace dt$$ renders like this: $$\int_{a_1}^{b_1} f_1(t)\thinspace dt$$

More on Creating This Blog

See other posts under the Blog setup tag.

Subscribe

Want to get an email when a new post is added? If so, subscribe here.