Localize your docs
clean-jsdoc-theme can ship your documentation in multiple languages. Each locale is rendered to its own static output — the default language at the root, the others under /<locale> — and a language switcher in the header navigates between them. Three kinds of content get translated:
| Content | Source | How |
|---|---|---|
| UI chrome (search, settings, nav labels) | a key→string catalog | translated in the locale JSON |
| API descriptions (class/member/param/return prose) | your doclets | translated in the locale JSON |
| Prose (home page, docs pages) | per-locale files | README.<locale>.md + docs.<locale>/ |
The workflow runs through the clean-jsdoc CLI (the aadesh package), backed by the pure i18n core (bhasha).
Prerequisites: JSDoc ≥ 4 (or TypeDoc, for extract) and Node ≥ 20. Install the CLI alongside the theme:
pnpm add -D clean-jsdoc-theme @clean-jsdoc-theme/aadesh
The localization commands live under the clean-jsdoc i18n group; build stays top-level (it renders your site with or without locales).
Localized builds are JSDoc-only today. The TypeDoc bridge can extract catalogs but does not yet render the per-locale sites — full multi-language output is on the JSDoc path.
1. Declare your locales
Locales live in your existing jsdoc.json opts (TypeDoc: the cleanJsdocTheme block) — there's no separate config file:
{
"opts": {
"destination": "dist",
"readme": "./README.md",
"docs": "docs",
"locales": [
{ "code": "en", "name": "English" },
{ "code": "ja", "name": "日本語" },
{ "code": "hi", "name": "हिन्दी" }
],
"defaultLocale": "en"
}
}A list of { code, name } (or bare "en" strings) plus the default. The name is the switcher label, and codes are BCP-47-ish (en, pt-BR). defaultLocale is optional — it defaults to the first locale in the list. A single-locale (or no-locales) build is unaffected — it renders exactly as before.
2. Extract the catalogs
clean-jsdoc i18n extractThis runs your pipeline, collects every translatable string (chrome + API), and writes one committable catalog per locale under clean-jsdoc-theme-artifacts/locales/:
clean-jsdoc-theme-artifacts/locales/
en.json # the skeleton — values ARE the source text
en.meta.json # auto-managed bookkeeping (don't edit)
ja.json # values blank until you translate them
ja.meta.json
...Re-run i18n extract any time your docs change — it merges: new keys are added, changed source marks a key stale, removed keys are soft-deleted (kept until --prune). A no-change run produces a zero git diff.
3. Translate
Edit each locale's <code>.json by hand, or generate a prompt for an LLM:
clean-jsdoc i18n prompti18n prompt writes a ready-to-use prompt file per locale under clean-jsdoc-theme-artifacts/locales/prompts/ — <code>.md for a small catalog, or <code>.part-01.md, <code>.part-02.md, … chunked for context limits. Each file contains only the untranslated and stale entries, with instructions to preserve markdown, @link, code fences, and {var} interpolation tokens. The CLI prints where the files landed:
ja: 60 entries → 2 prompt files:
clean-jsdoc-theme-artifacts/locales/prompts/ja.part-01.md
clean-jsdoc-theme-artifacts/locales/prompts/ja.part-02.mdOpen each file and paste its contents into your LLM — or upload the .md directly — then copy the returned translations back into the matching <code>.json catalog. (The prompts directory is regenerated on every run and git-ignored, so it never clutters your commits.)
You don't have to translate everything — anything left blank falls back to the default language, so a partially-translated site is fine (and the coverage shows up in the report).
4. Validate (optional)
clean-jsdoc i18n validate # warns on gaps, errors on malformations
clean-jsdoc i18n validate --strict # gaps become failures too (for CI)5. Build
clean-jsdoc buildOne site per locale: the default locale at destination, each other locale under destination/<locale>. The language switcher and hreflang alternates are wired automatically from the set of locales each page actually exists in.
6. Preview & deploy
Serve the output over HTTP (not file://) — the full-text Pagefind index is fetched at runtime and needs a real server:
pnpm dlx serve distDeploy the whole dist/ directory to any static host. The default locale sits at the root and each other locale under its /<locale>/ prefix, so the switcher and hreflang links resolve the same way in production as they do locally.
Localizing prose
Catalogs cover the chrome and API reference. Free-form prose is localized by file — no extraction:
- Home page — add a
README.<locale>.mdnext to your configured README (README.ja.md,README.hi.md, …). aadesh renders it as that locale's home; a missing variant falls back to the default README. - Docs pages — add a sibling
docs.<locale>/directory next to youropts.docsfolder and translate the files you want. It overlays the default docs per file: a translated page wins, a missing one falls back to the default. So a locale only needs the pages it has actually translated.
README.md docs/ # default language
README.ja.md docs.ja/ # Japanese overlay (translate what you want)
README.hi.md docs.hi/ # Hindi overlayKeep a doc's
groupfrontmatter value the same across locales (translate thetitle, not thegroup) — otherwise the same section can split into two sidebar groups.
Per-language fonts
A translated heading rendered in a Latin display font can look wrong for CJK or Devanagari. Override the font per locale with a <locale>: prefix in opts.fonts — anything without a prefix is the default, and a locale that omits a slot falls back to it:
{
"opts": {
"fonts": {
"heading": "Source Serif 4",
"body": "Roboto",
"ja:heading": "Noto Sans JP",
"ja:body": "Noto Sans JP",
"hi:heading": "Noto Sans Devanagari",
"hi:body": "Noto Sans Devanagari"
}
}
}Each locale's build then requests only its own families from Google Fonts.
Interactive mode
Prefer a guided run? Invoke the CLI with no arguments:
clean-jsdocIt opens a welcome banner and a command picker — the i18n group (drilling into extract / prompt / validate) and build — that prompts for each command's options and offers to save the equivalent (namespaced) command to your package.json scripts.
A complete example
See examples/with-i18n-example in the repo — a three-locale (en / ja / hi) project with translated API descriptions, chrome, a per-locale home page, a localized Guide docs section (with a deliberate fallback to show partial translation), and per-language fonts.
Next
- aadesh Overview — the CLI in depth.
- bhasha Overview — the i18n core.
- Configuration — every theme option.