Blog Internationalization with Hugo

📢 This article was translated by gemini-3-flash-preview

Introduction

As I consume more information, I often find the need to write articles in other languages (especially since modern projects are almost always multilingual). Ever since I added i18n support to my graduation project, I’ve wanted it for everything I design or build. Even my recent practice project includes multilingual support (though that one is a bit dated; I’m working on a more modern version now).

Looking back at my blog—the project I’ve maintained the longest—it still lacked internationalization. While I mostly write in Simplified Chinese, my previous blog was a messy mix of Chinese and English in the UI. It worked for me, but it was likely confusing for others.

I tried setting up multilingual support in Jekyll but never quite got it right, so I switched to Hugo, which supports it natively. Every theme has its quirks, and this theme’s approach to categories and tags differs significantly from my previous one, so I’ll need time to adapt. This post is a bit rough because the configuration seems heavily theme-dependent, and the official documentation is already quite comprehensive.

Environment

I’m using Hugo via Docker, so I didn’t have to mess with local environment issues. You can see my setup here .

Creating the Blog

Create a new blog:

1
hugo new site blogName

Enter the directory:

1
cd blogName

Initialize the repository:

1
git init

Install a theme. I’m using hugo-theme-stack , but the process is similar for others:

1
git submodule add https://github.com/CaiJimmy/hugo-theme-stack/ themes/hugo-theme-stack

Configure hugo.toml to use the theme:

1
theme = 'hugo-theme-stack'

Preview:

1
hugo server

Preview including drafts:

1
hugo server -D

Some changes (like configuration or page structure) might require clearing the cache:

1
hugo --cleanDestinationDir

Build the site (output defaults to the public directory):

1
hugo

Multilingual Support

In your site configuration file (I use YAML), set the default language:

1
2
defaultContentLanguage: 'zh-cn' # Default language code
defaultContentLanguageInSubdir: false # Whether to include the language code in the default language's path

Then define the specific language settings:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
languages:
  zh-cn:
    contentDir: 'content/zh-cn' # Content directory for this language
    disabled: false
    languageCode: 'zh-cn'
    languageDirection: 'ltr' # Left-to-right (ltr) or right-to-left (rtl)
    languageName: '简体中文'
    title: "yexca'Blog"
    weight: 1
  zh-tw:
    contentDir: 'content/zh-tw'
    disabled: false
    languageCode: 'zh-tw'
    languageDirection: 'ltr'
    languageName: '繁體中文'
    title: "yexca'Blog"
    weight: 2

Font Internationalization: https://blog.yexca.net/archives/240

Writing Articles

Unlike Jekyll, Hugo’s article structure is slightly more complex. Adding i18n makes it even more so, though this allows for high customization. This may vary by theme. In my case, the language configuration points to specific directories under content.

You can specify which directories contain your main content in the stack theme configuration:

1
2
3
params:
    mainSections:
        - posts

This means my Simplified Chinese posts should be in content/zh-cn/posts. Other directories are theme-specific; check your theme’s example site for details.

To create a new Simplified Chinese post:

1
TZ="Asia/Tokyo" hugo new content/zh-cn/posts/test.md

If you don’t specify a timezone, it defaults to UTC. Coming from Jekyll, I find manually writing the timestamp a bit tedious.

Default post templates are located in blogName/archetypes and can be converted to YAML format.

References

Hugo - Quick start

https://stack.jimmycai.com/guide/

Hugo - Multilingual mode