Introducing Zola
2022-06-13 (Updated on 2022-06-20)
This website is generated with Zola. This blog post introduces my findings in using Zola.
What is Zola?
Zola is a static site generator like Hugo. It is written in Rust. Please refer to the official documentation for more details. I am neither the author nor a contributor of Zola by the way.
Why did I choose Zola for my website?
I was attracted to Zola because its implementation language Rust was the language I was and am learning (as of June 13, 2022). As long as I just use Zola the implementation language does not matter though.
Using Bulma with Zola
Bulma is my favorite CSS framework and I wanted to apply it to this website. I needed some tweaks before Bulma worked.
Importing Bulma
Just throwing Bulma's sass
folder and bulma.sass
file into Zola's sass
folder did not work, because Bulma exposes all the Sass files in the subfolders.
I found a discussion about the same issue, and I decided to follow the solution suggested by the author of the issue.
Thus, I prefixed all the Bulma's sass files with an underscore (_
) and it worked.
Styling markdown with Bulma
To apply Bulma's styles to markdown contents rendered by Zola, I wrapped them in an element with the .content
class.
I found a forum post about the same issue.
Multilingual support
As I want to provide articles both in English and Japanese (日本語) from this website, multilingual support is crucial for me. Zola supports multilingual sites though, that feature is not well-documented.
Section _index.{code}.md file
A section is accompanied by a _index.md
file.
If you want to write a section in a language other than the default one, you have to also create a _index.{code}.md
file replacing {code}
with the code of the language you want to use.
In my case, I prepared the following two files in each section,
_index.md
→ English (default)_index.ja.md
→ Japanese
Obtaining a section in a language-aware manner
You can use the get_section
function to obtain a section object at a specific path.
As get_section
does not take any language option, it is a little tricky to request a section object in the current language given by the lang
variable.
Simply concatenating lang
like "_index." ~ lang ~ ".md"
did not work, because it produced a wrong path for the default language like _index.en.md
despite _index.md
was expected.
So I made a macro lang_ext
that is substituted with an appropriate extension for the current language.
It turns into ".{code}"
but an empty string for the default language.
{% macro lang_ext() %}{% if lang != config.default_language %}.{{ lang }}{% else %}{% endif %}{% endmacro lang_ext %}
I am using this macro like the following,
{% set lang_ext = macros::lang_ext() %}
{% set root = get_section(path="_index" ~ lang_ext ~ ".md") %}
You cannot do like {% set root = get_section(path="_index" ~ macros::lang_ext() ~ ".md") %}
by the way.
Obtaining the root URL of the current language
The root URL for a specific language is given as {base_url}/{code}
except the one for the default language is given as {base_url}
.
Like Obtaining a section in a specific language, the root URL for a specific language is also a little tricky.
Again I made a macro lang_seg
that is substituted with an appropriate path segment for the current language.
It turns into "/{code}"
but an empty string for the default language.
{% macro lang_seg() %}{% if lang != config.default_language %}/{{ lang }}{% else %}{% endif %}{% endmacro lang_seg %}
I am using this macro like the following,
Switching the language of the current page
I wanted every page on my website to have a link to switch languages between English and Japanese, and I did not like to manually embed a link on every page.
So I decided to embed the link in base.html
which is extended by every HTML template on this website.
The get_url
function looked suitable for this feature because it takes a lang
option in addition to a path
argument.
If I can obtain the path of the current page in the form of @/{section}/{page}.md
, I can easily exchange it with the URL of the translation in a desired language through get_url
.
After some trial and error, I realized that I can use page.components
and section.components
to form a path
argument for get_url
.
I carefully observed the behavior of page.components
and section.components
, and found,
page.components
andsection.components
are an array of path segments in the current path separated by a slash (/
).page.components
andsection.components
start with a language code unless the current language is the default one. The default language code is omitted.- The last item of
page.components
is the name of the current page without an extension (.md
). - The last item of
section.components
is not an index,_index.md
, but the name of the section. section.components
of the root page of the default language is empty.
Then I came up with the following complicated template,
{% if page %}
{% if lang == config.default_language %}
{% set relative_path = page.components | join(sep="/") %}
{% else %}
{% set relative_path = page.components | slice(start=1) | join(sep="/") %}
{% endif %}
{% set internal_path = "@/" ~ relative_path ~ ".md" %}
{% elif section %}
{% if section.components | length > 0 %}
{% if lang == config.default_language %}
{% set relative_path = section.components | join(sep="/") %}
{% else %}
{% set relative_path = section.components | slice(start=1) | join(sep="/") %}
{% endif %}
{% set internal_path = "@/" ~ relative_path ~ "/_index.md" %}
{% else %}
{% set internal_path = "/" %}
{% endif %}
{% endif %}
After the above template is evaluted, get_url(path=internal_path, lang={code})
will give you the URL of the page in a language given by {code}
.
A major drawback is that I have to prepare both English and Japanese translations for every single page, or Zola ends up with an error.
Difficulties in anchor IDs
Zola turns every section title in markdown into a slugified anchor ID by default. There is no problem as long as a section title contains only ASCII characters, but once non-ASCII characters, e.g., Japanese characters, are involved, Zola emits an unpredictable, at least for me, anchor ID. For instance, a section title "言語を意識してセクションを取得する" turns into an anchor ID "yan-yu-woyi-shi-sitesekusiyonwoqu-de-suru".
This conversion can be avoided by changing the slugify
option for anchors to "safe"
in the config.toml
file.
But this configuration introduces unfamiliar* behavior over non-letter characters; e.g., whitespace is replaced with an underscore (_
) not a dash (-
), capital letters stay, and symbols also stay.
(*I am familiar with GitHub rules.)
My workaround so far is to make the slugify
option for anchors "safe"
and accept the new rules.
I should suggest a pull request someday, maybe.
Serving contents from S3 via CloudFront
I am serving this website from an Amazon S3 bucket through an Amazon CloudFront distribution though, there were some challenges to doing so. But this is not actually Zola's issue, I am going to leave it for another post.