Loading...

Loading...

Multilingual & Wiki System

This page explains a signature feature of this blog — the multilingual multi-Wiki system: one config manages multiple wiki document sets (docs/api/tutorial), each wiki can have multiple language versions (zh-cn/en), missing translations fall back to the default language automatically, and correct canonical/hreflang tags are output for search engines. This page is also the creation & maintenance manual for wiki pages.

The preference section of userConfig/_config.tmp.yml is the authoritative config source.

1. Feature Overview

FeatureDescription
Multi-wikiauto-scans source/{wiki}/ directories (currently: docs / api / tutorial)
Multilingual9-language capability table (de/en/eo/es/ja/ru/zh-CN/zh-HK/zh-TW); lang_meta enabled controls activation (currently: zh-cn / en)
Path prefixlanguage versions are distinguished by path prefix: /docs/setup/ (default) vs /en/docs/setup/ (English)
Fallback3-level fallback chain: requested → fallback_lang → default_lang → any available; the notice bar shows the actual fallback source language
Independent sidebarone source/_data/{wiki}-sidebar.yml per wiki
SEOcorrect html lang + canonical per page; hreflang output when a translated version exists

2. Config Details (preference language section)

Config (theme config, preference section; migrated from preference.wiki on 2026-09-08 to site-wide, removed duplicate langs):

preference:
  default_lang: zh-cn         # default language (used for prefix-less paths; all components: wiki/post/search)
  fallback_lang: en           # fallback language: used first when the requested language version is missing (must be enabled in lang_meta)
  lang_meta:                  # language capability table + enable switch: key=language, enabled=true only takes effect
    zh-cn:
      name: 简体中文
      flag: 🇨🇳
      enabled: true
    en:
      name: English
      flag: 🇬🇧
      enabled: true
    de:
      name: Deutsch
      flag: 🇩🇪
      enabled: false
    # ... all 9 languages (de/en/eo/es/ja/ru/zh-CN/zh-HK/zh-TW), enabled controls activation
  wiki:
    enable: true            # master switch of the multilingual wiki system (wiki-specific switch stays here)
  # Chinese trad/simp conversion (formerly footer.translate, consolidated here on 2026-08-16)
  translate:
    enable: true

Notes:

  • The wiki system auto-scans source/{wiki}/ directories (front-matter layout: wiki) and source/_data/{wiki}-sidebar.yml — no config registration needed
  • Language capability table = keys of lang_meta (all 9 languages supported); active languages = keys with enabled: true — inactive languages generate no fallback pages, appear in no language dropdown, and are not fallback targets
  • fallback_lang: middle fallback layer — used first when the requested language is missing; defaults to the first enabled non-default language
  • name/flag in lang_meta drive the language dropdown display; they must be added when introducing a new language
  • default_lang is not written into the URL prefix (/docs/ is Chinese); other languages are (/en/docs/)
  • The old preference.wiki.default_lang/langs location is deprecated (migrated 2026-09-08; lang-config.js still reads it as fallback during the transition)

3. Language Switching (preference panel)

Purpose: readers switch language/trad-simp from the preference panel in the top-right corner.

  • Language switching: the panel’s language dropdown lists the enabled languages (lang_meta with enabled: true, with flags); switching jumps to the current page’s version in that language
  • Trad/simp conversion: preference.translate.enable controls the Chinese conversion button; the default direction is decided by zhDefaultEncoding (1 traditional / 2 simplified)
  • Remembered preference: the choice is stored in localStorage (key wiki_lang)

Client language strategy (2026-09-10, Plan A — site-wide follow):

  • First-visit prompt: no saved preference and browser language matches an enabled (non-default) language → a dialog asks whether to switch; accept = remember + jump, decline = remember the default language (never asked again)
  • Load / refresh follow: with a saved preference, any page loads directly in that language version (inline redirect in head, before render — no flash); crawlers are skipped (SEO); paths without a multilingual version are skipped (no 404)
  • Panel display: the language dropdown shows the saved preference (localStorage.wiki_lang), not the current URL language

404 / aggregate / encrypted page language (2026-09-27):

  • 404 page: switched to client-side language detection (single file) — copy language follows the visitor’s browser language / saved preference instead of statically generating one page per language (the old /en/404.html etc. are no longer needed)
  • archives aggregate page: unified pagination across all languages — no longer diverges per language; every language version shares the same pagination structure
  • Encrypted page language: encryption prompt / button copy follows the page language (hbe-bundle fallback copy goes through MATERY_I18N, E7 2026-09-27)
  • In-content links & runtime copy: url_for_lang points to the matching language version; search / dialog runtime copy is i18n-ised too (P0/P1)

4. Wiki Page Creation Flow

Purpose: create a new wiki (or add pages to an existing one). Using the tutorial wiki as an example (this document is its product), 4 steps total:

① Create the page directory (source/{wiki}/):

mkdir -p source/tutorial/config   # pages go in subdirectories by chapter

② Write the page file (front-matter must contain layout: wiki + wiki: <name>):

---
title: Global Configuration
layout: wiki
wiki: tutorial
---

## Global Configuration

Body content...

③ Configure the sidebar (source/_data/{wiki}-sidebar.yml, chapter → page mapping):

Getting Started:
  overview: index.html
  install: install.html
Configuration Reference:
  global: global.html
  multilingual: multilingual.html

④ Done: the wiki system auto-scans the source/tutorial/ directory (front-matter layout: wiki + wiki: tutorial) — no config registration needed. Visiting /tutorial/ enters the wiki; the left sidebar renders the chapter tree from tutorial-sidebar.yml automatically (collapsible), with in-wiki navigation at the top of the page.

5. Sidebar Data Format

File: source/_data/{wiki}-sidebar.yml (e.g. docs-sidebar.yml, tutorial-sidebar.yml).

Format: section name: page-key: page.html — the key is the page file’s slug, the value is the HTML path relative to that wiki’s root:

# tutorial-sidebar.yml example
Feature Guide:
  layout: layout.html
  tag-plugins: tag-plugins.html
Configuration Reference:
  global: global.html
  nav-home: nav-home.html
  post: post.html
  widgets: widgets.html
  code-highlight: code-highlight.html
  multilingual: multilingual.html

Notes:

  • Section order = render order; page order within a section = file order
  • The page filename (slug) MUST match the key in {wiki}-sidebar.yml, otherwise the sidebar link 404s
  • Adding a page = ① create the md file ② add a line to the corresponding section in sidebar.yml — both steps required

6. Multilingual & Translation

Directory convention: translations live under source/{lang}/{wiki}/, same name and path as the source files:

source/
├── docs/                  # default language (zh-cn): docs wiki Chinese pages
│   ├── index.md
│   ├── setup.md
│   └── …
├── en/
│   ├── docs/              # docs wiki English translations
│   │   ├── index.md
│   │   ├── setup.md
│   │   └── …
│   ├── api/
│   └── tutorial/
└── _data/
    ├── docs-sidebar.yml
    └── tutorial-sidebar.yml

Translation essentials:

  • English pages also write layout: wiki + wiki: docs in front-matter; add lang: en so the theme knows the page language (the helpers use page.lang for the sidebar link prefix and canonical)
  • The language codes in lang_meta match the directory names (en ↔ /en/)
  • The sidebar data is maintained once for the default language only; translations reuse it

Post translation (source/{lang}/_posts/): same “same filename” convention as wiki —

TypeTranslation sourcefront-matter
Postsource/{lang}/_posts/{same name as Chinese}.mdkeep the same abbrlink + lang: en
  • Two mapping modes: with the abbrlink plugin → matched by identical abbrlink; without it → matched by identical filename (with a filesystem fallback check)
  • Hexo natively only recognizes source/_posts/ (en/_posts/ is ignored); post translations are generated by the theme’s i18n-post-generator.js at /{lang}/posts/{abbrlink}/
  • A real translation → a real content page; otherwise → a fallback page (see §7)

7. Fallback Mechanism (3-level chain, unified 2026-09-08)

Purpose: when a page has no translation in a language, visiting that language path still returns content instead of a blank page.

Fallback chain (unified across all modules: wiki/post/archive-pages/search, resolved by lang-config.js):

requested language exists?      → use it
→ not found → fallback_lang exists? → fall back to fallback_lang
→ not found → default_lang exists? → fall back to default_lang
→ not found → any available language (first one)

Flow (generation-level fallback, generated by scripts/generators/{post,wiki}-fallback.js):

visit /de/docs/setup/ (de enabled but untranslated)
  → en translation exists → fallback page content in en (fallback_lang takes effect) + notice "de not found, showing en"
  → no en translation → fallback page content in zh-cn + notice "de not found, showing zh-cn"

Effect:

  • Content language is chosen by the fallback chain (_fallbackFrom marks the actual language); the notice bar shows “xx language not found, showing yy language version”
  • canonical points to the default language, preventing search engines from indexing duplicate content
  • Standalone pages (about/musics/movies/bb/friends/galleries etc.) do generate multilingual versions (/{lang}/{page}/) — UI copy goes through i18n keys (languages/*.yml), content stays in the author’s language; generated by standalone-page-generator.js via an exclusion rule (layout ∉ {wiki, tags, categories, 404}). Navigation links (url_for_lang) add a language prefix to content paths, pointing to the matching language version
  • Archive pages (tag/category/archive) generate enabled-language versions (generated only, no notice bar)

8. SEO (canonical / hreflang)

Output rules (auto-generated per wiki page):

Scenariohtml langcanonicalhreflang
Default-language page (has translation)zh-CN/docs/setup/itself + /en/docs/setup/
Translated pageen/en/docs/setup/itself + /docs/setup/
Fallback page (no translation)zh-CN/docs/setup/ (points to default)none

Notes: path prefix (not query string) + hreflang + canonical is the standard Google multilingual SEO practice; the sitemap is output uniformly by hexo-generator-sitemap, wiki pages included automatically.

9. Common Operations

Add a Wiki

  1. Create the source/{new-wiki}/ directory and pages (front-matter: layout: wiki + wiki: {new-wiki})
  2. Write the source/_data/{new-wiki}-sidebar.yml sidebar
  3. Done — the wiki system auto-scans, no config registration needed

Add a Language

  1. Add the language code + name/flag/enabled: true to preference.lang_meta (enable directly within the 9-language capability table)
  2. Optional: set preference.fallback_lang to that language (as the middle fallback layer)
  3. Create source/{lang}/{wiki}/ for translation files

Add a Post (existing wiki)

  1. Write source/tutorial/xxx.md (front-matter as above)
  2. Add a line to the corresponding section of _data/tutorial-sidebar.yml
  3. Optional: translate to source/en/tutorial/xxx.md

Add a Post Translation

  1. Create a file with the same name under source/en/_posts/ (e.g. source/en/_posts/my-post.md)
  2. In front-matter, keep the same abbrlink and add lang: en
  3. Translate the body to English
  4. After build, the page is automatically generated at /en/posts/{abbrlink}/

Key: the abbrlink must match the Chinese original, otherwise the two language versions cannot be linked. The abbrlink is generated by the hexo-abbrlink plugin on first build and written into front-matter (with writeback: false it is no longer written back, the field is fixed) — never manually edit the abbrlink of an existing post.

Common Troubleshooting

  • Sidebar link 404: sidebar.yml key and md filename don’t match
  • Sidebar not showing: front-matter missing layout: wiki, or the wiki name doesn’t match the source/_data/{wiki}-sidebar.yml filename
  • Config changes not taking effect: rm -f db.json && hexo generate (see the development notes)

10. Wiki-Wide Encryption Configuration

Purpose: encrypt the entire wiki’s content column and right-side TOC; the left navigation stays visible (navigation is equivalent to a title, and its exposure is acceptable).

Declaration location: top-level keys in source/_data/{wiki}-sidebar.yml — side by side with navigation section keys like getting_started. The encrypt_ prefix was unified on 2026-09-07 to distinguish them:

KeyRequiredDescription
encrypt_passwordYesEncryption password; the presence of this key enables wiki-wide encryption
encrypt_messageNoEncryption prompt message (overrides theme encrypt.message)
encrypt_wpmNoWrong-password message (overrides theme encrypt.wrong_pass_message)
encrypt_placeholderNoPassword input box placeholder text
encrypt_button_textNoDecrypt button label

The password in the sidebar data uses a {{PASSWORD}} placeholder. The real value must not be committed to the repository.

Behavior (based on themes/matery/layout/wiki.ejs and themes/matery/source/js/wiki.js):

  • Encryption scope: content column only (encryptHtml(page.content, …)) and right-side TOC (#wiki-toc-aside gets the wiki-toc-encrypted class); the left wiki-sidebar navigation always renders and stays visible
  • Encryption parameters: stableSalt: true + storageKey: 'wiki.' + page.wiki — all pages in the same wiki share a single localStorage cache key
  • TOC rebuild after decryption: a successful frontend decryption triggers the hexo-blog-decrypt event → wiki.js removes the wiki-toc-encrypted class, changes the content column from l10 to l8, then runs tocbot.destroy() followed by tocbot.init() — in the encrypted state, body headings are unreadable, so the TOC must be rebuilt after decryption

Appendix: Multilingual & Wiki Quick Reference

Config/fileDescription
preference.enable (inherited from preference.wiki.enable)system master switch
preference.default_langdefault language (prefix-less paths)
preference.fallback_langmiddle fallback language (used first when the requested language is missing)
preference.lang_metalanguage capability table + enabled switch (9 languages) + name/flag
preference.translateChinese trad/simp conversion switch
source/_data/{wiki}-sidebar.ymlsidebar section → page mapping
source/{lang}/{wiki}/wiki pages per language
Fallback3-level chain: requested → fallback_lang → default_lang → any available; the notice bar shows the actual fallback source language
Wiki-wide encryptiontop-level encrypt_password in {wiki}-sidebar.yml (other encrypt_* keys optional)
SEOhtml lang / canonical / hreflang / sitemap output automatically
Comments
Data Loading...