setting up a multilingual website with Zola
> go back to: my blog- cette page existe aussi en français : mettre en place un site plurilingue avec Zola
available translations
the official zola docs were incomplete or unclear, so here is my own take on it
- why this article exists
- what Zola gives you out of the box
- what is possible with some tweaks
- aside, what else is possible with Zola
- conclusion
table of contents
why this article exists
the main goal of this article is to explore how Zola lets you create a multilingual website and what else you can do with some templating magic
its contents are heavily inspired by my own experience of working with Zola and trying to set my site up so i can have both an english and a french version of it
what Zola gives you out of the box
let's first take a look at what the Zola engine lets us do
automatic term translation
by default, the config.toml
file lets you specify translations of some variables. let's see what it looks like with the following example:
# Default language is English
= "en"
# English versions of some terms
[]
= "foo"
= "bar"
# French versions of some terms
[]
= "toto"
= "titi"
in the above example, the default language for the website will be en
(English). we then listed the default translations for two variable under the [translations]
section
we also defined translations for these variables in fr
(French) in the [languages.fr.translations]
part
these translations are then exposed to all the templates (ending in .html
), and can be retrieved using the trans
function, like this:
and the result will be: (what we set for the default translation)
what is not obvious when looking at the docs is that this function takes an optional second argument named lang
. this argument should be a valid language code defined in your config for the key
you passed to the function. what you's expect is that the default behaviour is to pass the current page's lang
to the function. however, what Zola does is passing the default lang
you specified in the config.toml
to be clear, what this means is that
is always equivalent to
in my example
rest assured, the simple workaround for this is to always use
this variant is able to retrieve the current page's lang
variable and pass it to the function so that the translation works as intended. this leads to more verbose code, but it works
language conditional text
as said previously, every page and section exposes a lang
variable that is set to the current language code of it. this variable can then be used to define conditional html
parts in a template
1
2 FOO bar
3
4 BAR foo
5
for example, the above code would display <p>FOO <i>bar</i></p>
on english pages and <p>BAR <i>foo</i></p>
on french ones
for this page, the result would be:
FOO bar
moreover, if we ever modified the code like this:
5
6 Language " " not supported
7
, it would display that the language of a page has no option for this particular part of the template. if the {% else %}
part is not set, this would simply not display anything
this different way of dealing with multiple languages is particularly useful if there are some places where it'd make it more readable to have all the versions in the same place instead of in the config.toml
, which should preferably be used for text that's used in multiple places or is short
furthermore, leaving a fallback case when the language is not supported allows us to work first on some part of the translation, leaving the rest for later while still having a functioning website
navigating between translated pages
by default, every page exposes a translations
variable, described here in the docs
this variable is accessible within templates, using the section.translations
or page.translations
variables. for example, one could create a list of variants for a page like this:
version:
note: we check that the translations list has more than one element, because it will always contain the current version of the page
what is possible with some tweaks
now, let's see what other things we can achieve using the knowledge we have
extending translated pages navigation to make it automatic
one of the main issues with the method described in the previous section is that one would need to add the translation list to every template, adapting it so that it works with both sections and pages. one way to do it is using a small html
file that is then included in the page, such as this one:
<!-- translations_list.html -->
cette page existe aussi en français :
this page is also available in english:
integrating this to an existing base of templates
one issue we might encounter is that most templates one uses will be extended from a base.html
template, and the current version of the engine doesn't accept the inclusion of another template when the current one already extends one. simply put, the code below would not function:
to solve this issue, i simply included the translations_list.html
file inside of my base.html
file in a specific block, and then added this block to every template i might want it in, adding some context around, like the example below:
<!-- base.html -->
<!-- page.html -->
this setup is what lets me have the "available translations" list at the top of every page and section on this website
aside, what else is possible with Zola
now that we've taken a look at what is possible with translations, i'll present some other interesting things i have achieved with Zola and its templating engine
automated backlinks
by default, every page and section exposes a list of ancestors
. this list contains the name of the files defining the sections prior to it. for example, this article would have:
// English version
page.ancestors =
// French version
page.ancestors =
this set of ancestors is very useful, because it allows us to craft breadcrumbs or links back to the previous section. to be able to do this, we need to use the provided function called get_section
, that gives back a bunch of metadata about the section for which we specified the path (relative to the content/
folder). what interests us the most in our case is:
- section.title: the title of the section we retrieved
- section.permalink: the link to it
these two pieces of information allow us to craft links to the section we got from the get_section
function, like this:
automated breadcrumbs
previously, we used the last
mapping to get only the last element of the page's ancestors. this is what i use for this website to create the little "go back to: [thing]" link on top of my pages. however, one could also use it to create breadcrumbs for their website, like this:
>
>
for this page, the result would be:
> welcome to my swamp > my blog > setting up a multilingual website with Zola
note on this section
in this section's code listings, i have assumed the ancestors
variable to be accessible. this is not the default Zola behaviour though, so what is necessary for it to work across all pages and sections is just to add the simple code below before any use of the variable. this exposes the correct variable corresponding to the current type of html
template being processed
furthermore, the part about backlinks would be correct for every page except the landing page
this is due to the way Tera always gives back an element from {{ ancestors | last }}
, even when the ancestors
array is empty. this mapping gives back an empty string, which would result in the following error when rendering the home page:
Error: Reason: Function call 'get_section' failed
Error: Reason: Section `` not found.
to avoid this error, we can just wrap the whole call to ancestors
in an if
statement, giving the following code:
>
note: in the code sample above, we reference the translated variable back
. to see how these are setup, check the automatic term translation section of this article
conclusion
Zola is a great SSG, and it allows a lot of stuff out of the box. however, it was lacking some features that i really wanted to have for my own website. these were not that trivial to implement, and the official documentation didn't help that much to understand how everything works
pro-tip if you ever get lost in what one object you're manipulating contains, just write the code sample below in any template file the current page uses and see what fields you have at your disposal
->
this simple thing helped a lot when trying to implement the features described in this article, so i can attest personnally of all the good it does
thanks for reading this article, i appreciate any feedback you could have about it 💜
-- eckler