🌐 本文还有其它语言版本:中文日本語

Hexo Upgrade Journey: Six Years Later & Multilingual Support

1. Background & Motivation

My blog (misaka10013.cn) has been running on Hexo 4.2.0 + NexT 6.0.0 since it was first launched — six years without touching the framework. Every time I recently asked an AI to help add a feature or tweak a page, it complained that my versions were too old: outdated dependencies, incompatible plugins, even the config syntax has changed. I should have upgraded long ago, but I kept putting it off.

Lately I’ve had a few new ideas:

1. Multilingual support. I wanted Chinese, English and Japanese versions of the site — first, to practice foreign languages by reading my own posts in different languages; second, for SEO. My posts are hard to find no matter where the search happens, and localized versions are friendlier to search engines.

2. A new theme. I had my eye on the AnZhiYu theme — rich colors, rounded cards, gradient animations, the exact opposite of NexT’s minimalist “indifferent” style. Since I had to change things anyway, why not jump straight to the latest versions and solve everything at once?

2. Framework Upgrade (Hexo 4→8 + NexT 6→8)

Overall Plan

After an initial discussion with the AI and a review of the blog’s current state, we settled on an execution plan: create a next8 branch off master, do the upgrade, theme migration, feature porting and multilingual development all on the branch, and only merge back to master for production once everything passes testing. The live site keeps running the old version throughout, completely unaffected.

Division of Labor

  • Me (Misaka): defining the plan, reviewing each step, making the final calls
  • AI: doing the actual work, assessing impact, reporting pitfalls

Upgrade Checklist

Item Old version New version
Hexo 4.2.0 8.1.2
NexT 6.0.0 (git clone) 8.29.0 (npm package)
Node.js 16.x 22.22.2
Deployment CNB cloud build Same (node:18→node:22)

3. The Multilingual Approach

Aligning Requirements

Working with an AI means aligning on the right level of detail. I had the AI propose concrete implementation methods for my rough requirements, then used a Socratic approach — it kept asking me questions until the requirements were fully pinned down:

  1. Trilingual UI (CN/EN/JA) — ✅ required
  2. Language switcher always visible in the sidebar across the site — ✅ required
  3. Each language home page shows only that language’s posts — ✅ required
  4. Post pages: has translation → jump; no translation → button disabled + in-post notice — ✅ compromise
  5. Translation is the user’s job; AI only builds the framework — ✅ clear
  6. Empty Japanese site initially is acceptable — ✅ accepted

Final Approach: Single Build + Thin Script

The final approach is a single build plus one scripts/i18n-blog.js that handles all the logic.

1. Translated Post Routing: the post_permalink Filter (priority 9)

  • Problem: by default every post shares the same URL structure, so language versions can’t be distinguished.
  • Approach: intercept and rewrite the permalink when Hexo generates it.
    • Default-language posts (e.g. zh-CN) keep their original path, e.g. /p/xxx.html.
    • Translated posts (e.g. en) are forced to /en/p/xxx.html.

Result: each language version of a post gets an independent, clean URL, which helps SEO and makes language switching straightforward.

2. Language-Specific Listing Pages: Overriding the index / archive / category / tag Generators

  • Problem: Hexo’s default generators mix all posts together for the index and archives, so they can’t be separated by language.
  • Approach:
    • Rewrite the generator logic.
    • For each language, generate that language’s home, archives, categories and tags pages.
    • Filter to only that language’s posts when generating.
    • If a language has no posts, generate a placeholder page instead of returning 404.

Result:

  • Each language site has its own home page and content listings, completely independent.
  • Visiting /en/ shows a placeholder instead of an empty-page error — a much better experience.

3.1 Language Switcher (Select Dropdown) & In-Post Language Banner

  • The script analyzes whether the current page has versions in other languages.

  • Has translation: the switch option is highlighted/clickable and points to the correct translated URL.

  • No translation: the option is grayed out (disabled) with a title tooltip like “This post has no English version yet”.

  • When other language versions exist, a notice banner is inserted at the top of the post body.

  • E.g.: “This post also has an English version: English Version”

  • On the English page it shows: “The Chinese original of this post: 中文原文”

Result: language switching is driven entirely by whether the content actually exists, so users never land on a missing page — a much smarter interaction.

4. Core Implementation: the i18n_map Generator

  • Problem: the frontend JS needs a dictionary for instant language jumps.
  • Approach: additionally generate a static JS file at build time: /js/i18n-map.js.

It contains a mapping table, e.g.:

1
2
3
4
5
6
window.I18N_MAP = {
"my-post": {
"zh-CN": "/p/my-post.html",
"en": "/en/p/my-post.html"
}
};

Result: this data links each post’s unique ID (abbrlink) to the routes of all its language versions. The frontend reads the table directly for instant switching — no server round-trip needed, faster response.

5. Overall Workflow

With this script orchestrating, Hexo’s build order is:

1
2
3
4
5
6
7
graph TD
A[Build starts] --> B[Step 1: Fix translated permalinks]
B --> C[Step 2: Generate per-language listing pages]
C --> D[Step 3: Inject switcher logic & translation banners]
B --> E[Step 4: Generate frontend language map i18n-map.js]
D --> F[Static site generated]
E --> F
Stage Technique Output Frontend result
Routing post_permalink filter Per-language post URLs Clean URL structure
Listing Override 4 generators Per-language home/archives/categories/tags Content isolated by language, no 404s
Page decoration after_render:html Modified HTML Smart switcher + translation banner
Data layer i18n_map generator /js/i18n-map.js Frontend routing dictionary

Translation Workflow (my process going forward)

1
2
3
AI drafts the translation → I refine it → save to source/_posts/en/
→ front-matter: lang: en + the same abbrlink as the original
→ verify in local preview → git push to production

Switcher Interaction Logic

Scenario Behavior
Post has a CN/EN/JA version Clickable, jumps to that language version
Post has no version in that language Button grayed out/disabled, hover tooltip “This post has no version in that language”
Post has other language versions Banner at top: “🌐 This post also has other language versions: English”
Language site home page Shows only that language’s posts

4. Theme Test Drive (AnZhiYu)

Zero-Contamination Coexistence

Without touching the existing NexT config, I ran two local servers in parallel, so I could gradually migrate the old theme’s custom settings based on what each page needs:

1
2
Port 4100 → NexT theme (hexo server -p 4100)
Port 4200 → AnZhiYu theme (hexo server --config "_config.yml,_config.anzhiyu_test.yml" -p 4200)

_config.anzhiyu_test.yml switches themes with a single line of config, so I can flip back to the minimalist style any time:

1
theme: anzhiyu

At startup Hexo merges the two config files; AnZhiYu overrides the theme field while everything else (posts, the i18n script, etc.) is shared from the main config.

AnZhiYu Config System

Important: all AnZhiYu settings live in _config.anzhiyu.yml at the blog root — never touch files inside themes/anzhiyu/.

Feature Migration Checklist

A migration list for the old blog’s custom features — some carried over to the new theme, some dropped.

Feature Status on AnZhiYu Notes
Live2D mascot ✅ Kept Inject autoload.js; music player moved to top-left to make room
Language switcher ✅ Ported Injected into the right-side floating bar; same disabled/clickable logic
Copy button ❌ Dropped The new theme has its own
Crash fake-out ❌ Dropped The new theme offers other forms
Reading progress ❌ Dropped The new theme has its own
Comments/friend links/menu Migrating manually Moving item by item from the NexT config

5. Current Config & Operations Guide

Multilingual Config

Edit these fields in _config.yml:

1
2
3
4
5
6
7
# Language order: the first one is the default (Chinese)
language: [zh-CN, en, ja]

# Multilingual plugin config
i18n_blog:
default_language: zh-CN # default language
langs: [en, ja] # other enabled languages

Writing a New Post

Chinese post (default language):

Location: source/_posts/
Example: source/_posts/我的新文章.md

front-matter example:

1
2
3
4
5
6
7
8
9
---
title: 我的新文章
tags:
- 标签1
- 标签2
categories:
- 技术笔记
date: 2026-08-28 17:20:00
---

No lang field needed; abbrlink is auto-generated at build time.

English translation (requires the Chinese original):

Location: source/_posts/en/
Example: source/_posts/en/my-new-article.md

front-matter example:

1
2
3
4
5
6
7
8
9
10
11
---
title: My New Article
tags:
- tag1
- tag2
categories:
- Tech Notes
date: 2026-08-28 17:20:00
lang: en
abbrlink: 12345678 # the same abbrlink as the Chinese version
---

Key: lang: en is required, and abbrlink must match the Chinese original.

Japanese post (empty site for now; add when translated):

Location: source/_posts/ja/
front-matter needs lang: ja + the same abbrlink.

Local Preview

NexT theme (port 4100):

Run in the blog root:

1
2
cd D:\1\coding\hexo-blog-master
hexo server -p 4100

Visit http://localhost:4100

AnZhiYu theme (port 4200):

Run in the blog root:

1
2
cd D:\1\coding\hexo-blog-master
hexo server --config "_config.yml,_config.anzhiyu_test.yml" -p 4200

Visit http://localhost:4200

Note: after changing _config.anzhiyu.yml or files under source/css/ etc., restart the server to apply. Press Ctrl+C to stop, then re-run the command above.

Publishing

CNB cloud-native build: pushing to master auto-deploys. No local hexo g needed.

1
2
3
4
cd D:\1\coding\hexo-blog-master
git add -A
git commit -m "update"
git push origin master

Note: git push inside the sandbox fails with credential errors — run the commands above in a local terminal (cmd/PowerShell).

Previewing During Config Migration

The live site is still on NexT. The AnZhiYu test-drive config lives in the following files, not yet committed to master:

File Description
_config.anzhiyu.yml AnZhiYu override config (1342 lines)
_config.anzhiyu_test.yml Test-drive switch (single line: theme: anzhiyu)
source/css/anzhiyu-custom.css AnZhiYu-specific CSS
source/js/i18n-switcher.js Language switcher button JS
themes/anzhiyu/ The AnZhiYu theme itself

Once I finish migrating the menu, friend links, comments and other configs, I’ll decide whether to make AnZhiYu the production theme.

6. TODO & Outlook

  • SEO work: hreflang alternates, multilingual sitemap entries, robots.txt — searching “misaka10013” currently surfaces GitHub first; the blog’s visibility needs work
  • Translating key posts: pick important posts, AI drafts, I refine, gradually enriching the English site
  • AnZhiYu goes live: decide whether to switch once config migration is done
  • OSS key rotation: the plaintext keys in the deploy section are in git history — rotation recommended