Multilingual Ghost CMS Blog: 1. Making Posts and Pages Multilingual
Ghost can create a Collection of specific posts. Let's create tags for each language we want to support and make a Collection for each one to build a multilingual blog.
- Create internal tags for the supported languages
- Set up Collections in routes.yaml
- Modify the hbs files
I want to build a multilingual blog with English as the primary language and Korean as the secondary language.
Create internal tags
In the Ghost admin screen, select Tags on the left and click New tag to add an internal tag. An internal tag must start with #.
I set the internal tag for English posts to #en and the internal tag for Korean posts to #ko. If you enter #en in Name, the Slug is automatically set to hash-en, but I deleted the hash- part to make it more concise.


When you select Internal tags, you should see a screen like the one below.

Publish test posts
Let's publish some posts with internal tags for later testing.
I published two posts for each internal tag.
- en news 1: News, #en
- en news 2: News, #en
- ko news 1: News, #ko
- ko news 2: News, #ko

I set the first tag of every post to 'News' and the second tag to the language tag (#en or #ko).
For example, if you want to apply the three tags news, latest, and #en, you should set the order as [news, #en, latest] or [latest, #en, news]. The key point is that the second tag must be the language tag.
Now if you look at the blog's main page, you can see a total of 5 published posts: the 1 default post published during installation plus the 4 additional posts I published.

Set up Collections in routes.yaml
- In the admin dashboard, click the settings icon, then in Labs select Download current routes to download the routes.yaml file.
The initial state of routes.yaml is as follows.
routes:
collections:
/:
permalink: /{slug}/
template: index
taxonomies:
tag: /tag/{slug}/
author: /author/{slug}/
Original routes.yaml
Modify it as shown below and upload it.
routes:
collections:
/:
filter: "tag:en"
permalink: /{slug}/
template: index-en
/ko/:
filter: "tag:ko"
permalink: /ko/{slug}/
template: index-ko
taxonomies:
tag: /tag/{slug}/
author: /author/{slug}/
routes.yaml (English as the main language)
This sets things up so that when you access https://sanghunkang.com/, only posts with the en tag are shown, and when you access https://sanghunkang.com/ko/, only posts with the ko tag are shown. One important point is that the primary language must be placed at the top of collections.
Each collection is exclusive. In other words, there cannot be overlap. If a single post has both #en and #ko tags, it will only appear when accessed through the path of the collection placed higher up, and it will not appear when accessed through the other path.
If you want to make Korean the primary language, you can configure it like this.
routes:
collections:
/:
filter: "tag:ko"
permalink: /{slug}/
template: index-ko
/en/:
filter: "tag:en"
permalink: /en/{slug}/
template: index-en
taxonomies:
tag: /tag/{slug}/
author: /author/{slug}/routes.yaml (Korean as the main language)
In this post, I will follow the configuration where English is the primary language.
Modify the hbs files
This explanation is based on version 5.7 of the Casper theme.
For the code changes, refer to the commit below.
- Add two files
- index-en.hbs
- index-ko.hbs
- Modify three files
- default.hbs
- post.hbs
- page.hbs
index-en.hbs & index-ko.hbs
{{!< index}}
{{#contentFor "lang"}}en{{/contentFor}}index-en.hbs
{{!< index}}
{{#contentFor "lang"}}ko{{/contentFor}}index-ko.hbs
Add the two files above to the root path.
default.hbs
<html lang="{{@site.locale}}"{{#match @custom.color_scheme "Dark"}} class="dark-mode"{{else match @custom.color_scheme "Auto"}} class="auto-color"{{/match}}>Modify this part as follows.
<html lang="{{#if (block "lang")}}{{{block "lang"}}}{{else}}en{{/if}}"{{#match @custom.color_scheme "Dark"}} class="dark-mode"{{else match @custom.color_scheme "Auto"}} class="auto-color"{{/match}}>With this, pages that use the index-en.hbs template will be set to html lang="en", and pages that use the index-ko.hbs template will be set to html lang="ko".
And if there is no special lang information, the default value en is set.
post.hbs
Add these four lines under {{#post}}.
{{#post}}
{{#has tag="#ko"}}
{{#contentFor "lang"}}ko{{/contentFor}}
{{else}}
{{#contentFor "lang"}}en{{/contentFor}}
{{/has}}page.hbs
Add these four lines under {{#post}}.
{{#post}}
{{#has tag="#ko"}}
{{#contentFor "lang"}}ko{{/contentFor}}
{{else}}
{{#contentFor "lang"}}en{{/contentFor}}
{{/has}}One important point is that the distinction should be based on the secondary language tag. In this post, I set Korean as the secondary language, so if the #ko tag is present it is set to ko, and if not it is set to the default value en.
Result
If you access the / and /ko/ paths configured in routes.yaml, you can confirm that posts are correctly separated and displayed by language tag. This result was achieved with a single Ghost CMS, without using multiple Ghost blogs.


If you check in the developer tools, you can see that the language declaration is also properly set: <html lang="en"> for /, English posts, and English pages, and <html lang="ko"> for /ko/, Korean posts, and Korean pages.
Things to watch out for
- From now on, every post must include either the #en or #ko tag. If it has neither of these two tags, that post cannot belong to any collection.
- A post URL slug must not match the slug of a language tag. Since the slug of #en is en and the slug of #ko is ko, a post URL slug must not be en or ko


This must not happen
Next post
