Every post in Shirone can ship in more than one content language. Language versions live side by side in a single folder, share one slug, and link to each other automatically — while the top bar and page copy follow the current URL’s language and aggregate lists (archive, categories, tags) keep rendering the default language only. This very page (and its Chinese twin) is the working demo.
1. File layout
Put each language variant in the same post folder, one file per
language, named index.<locale>.md:
src/content/posts/bilingual-posts/├── index.en.md ← default language (English)└── index.zh-CN.md ← Simplified ChineseThe language is derived from the file-name suffix — no extra frontmatter field is required:
index.en.md→ collection idbilingual-postsindex.zh-CN.md→ collection idzh-CN/bilingual-posts
The default-language file may keep the plain index.md name as well; a
suffix-less file simply maps to the default language. Keep published,
tags, category and other structural fields in sync between the two
files, and write title / description in the language of each file.
2. URL mapping
Each language version gets its own route. The non-default language prefix
replaces the posts/ segment instead of stacking on top of it:
| Default language (en) | zh-CN | |
|---|---|---|
| Collection id | bilingual-posts | zh-CN/bilingual-posts |
| Route | /posts/bilingual-posts/ | /zh/bilingual-posts/ |
Custom permalink | /my-slug/ | /zh/my-slug/ |
3. Cross links between versions
When a post has at least one other language version, an “Also available
in / 本文还提供” block is rendered above the body, right after the cover
and metadata. It lists every other language as a chip link annotated with
hreflang — the current language never lists itself, and a monolingual post
renders no block at all, so pages without translations stay byte-identical
to a site without i18n.
4. The top-bar language menu
The translate button between search and the palette opens the same language set as a dropdown:
- On an article page it lists every language version of this post; picking one navigates to it.
- The home page has a counterpart in every configured language
(
/↔/zh/, a full-page switch). Pages without a version — archive, feeds, and the rest — show the missing language as a disabled entry.
Menu labels are endonyms (“English”, “简体中文”) and never translate.
5. What follows the page language, what stays default
UI copy follows the current page language: under /zh/* the top bar,
sidebar, pagination, and search panel all speak Chinese — this page is the
demo. Cross-language in-site jumps perform a full page load (Swup’s
ignoreVisit guard), so the persistent shell never lingers in the old
language.
Aggregate content is deliberately scoped to the default language:
- Archive, categories, tags, RSS/Atom feeds,
llms.txt, and stats pages always list the default-language posts; the Chinese home page (/zh/) lists Chinese posts only. - Per-page metadata follows the article language:
<html lang>, JSON-LDinLanguage,og:urlandContent-Languageall reflect the language you are reading.
To try it, use the translate button in the top bar, or open the link block above — the Chinese version of this article is one click away.
Share Article
Generate a share poster or copy the link to share this article.
Continue reading
Last updated on , 6 days ago
Some content may be outdated