Multi-language sites
DocsForge has built-in static internationalization (i18n). Add translated files next to your default-language files and DocsForge builds locale-agnostic pages: every URL serves the same address, while the service worker returns the requested locale sibling (index.<locale>.html) from the cache.
Enabling i18n
Exactly one language must be marked as default. Two equivalent ways to configure it:
Zero-config — the i18n plugin loads automatically; declare languages under extra.i18n_languages (what this documentation site uses):
extra:
i18n_languages:
- locale: en
name: English
default: true
- locale: zh
name: 中文
Explicit plugin — declare the plugin with languages: when you need to tune plugin options:
plugins:
- material/i18n:
languages:
- locale: en
name: English
default: true
- locale: zh
name: 中文
File naming
Translated pages use the locale suffix. They are emitted as siblings of the default page and share the same public URL.
docs/
├── index.md # English (default) → site/index.html
├── index.zh.md # Chinese → site/index.zh.html
├── second.md # English → site/second/index.html
└── second.zh.md # Chinese → site/second/index.zh.html
Both index.html and index.zh.html are served from the same locale-agnostic URL (/ and /second/). The service worker stores the user's preferred locale in IndexedDB and returns the matching sibling from the cache.
A translation keeps the identity of the page it translates: index.zh.md is an index page just like index.md, so navigation tab icons, navigation.indexes section merging, and automatic ordering behave identically in every language.
Translated files can live anywhere in docs/, including subdirectories:
docs/
└── guide/
├── intro.md
└── intro.zh.md
Fallback pages
The fallback_to_default option is a legacy setting and is ignored in the current locale-agnostic architecture. DocsForge only builds translations that actually exist; there are no fallback copies.
Navigation translations
Translate section and page titles in the language switcher and sidebar with nav_translations:
plugins:
- material/i18n:
languages:
- locale: en
name: English
default: true
- locale: zh
name: 中文
nav_translations:
Home: 首页
"Getting started": 入门
Installation: 安装
Language switcher
A language switcher appears automatically in the header when the i18n plugin is configured. Selecting a language stores the preference in IndexedDB and reloads the same URL; the service worker then serves the matching locale sibling. The page also emits <link rel="alternate" hreflang="..."> tags for SEO.
Site name and description per language
Override site_name and site_description for a locale:
plugins:
- material/i18n:
languages:
- locale: en
name: English
default: true
site_name: My Project
- locale: zh
name: 中文
site_name: 我的项目
site_description: 中文文档
Search and sitemaps
Each locale gets its own search index, but URLs remain locale-agnostic and only one sitemap is emitted at the site root.
site/
├── search/
│ ├── search_index.json # default locale
│ └── search_index.zh.json # Chinese
└── sitemap.xml
The frontend loads search/search_index.<locale>.json for the currently displayed locale, so search results stay in the same language.
Submit the single root sitemap to search engines. The sitemap points to the locale-agnostic canonical URLs.
How it works
- Build emits
index.htmlandindex.<locale>.htmlas siblings for every translated page. - The cache manifest lists both files.
- The service worker caches all siblings during activation.
- On each page request the service worker reads the preferred locale from IndexedDB and returns the matching sibling if cached, otherwise the default
index.html. - The language switcher writes the new preference to IndexedDB and reloads the same URL.
Limitations
- Only the
suffixfile layout is supported (index.zh.md). - The language switcher requires JavaScript and service-worker support; without it, the default locale is displayed.