From ac1ddec4788de33cf55619163fab16cfde53910c Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 8 Jul 2026 10:52:15 +0000 Subject: [PATCH 1/5] docs: rewrite translations contributing guide for Crowdin Replace manual PO/FTL translation walkthroughs with a Crowdin-first two-page structure: an overview for translators (architecture, CI sync, build-time wiring for UI, mdBook, and website) and a developer guide for adding English strings, regenerating messages.pot, and i18n:skip directives. Simplify devenv.nix translation messaging and remove Poedit from the development environment. Co-authored-by: Kevin --- devenv.nix | 16 +- docs/src/SUMMARY.md | 3 +- .../contributing/translations/developers.md | 107 ++++++++++++ docs/src/contributing/translations/docs.md | 155 ------------------ docs/src/contributing/translations/index.md | 115 ++++++++++--- .../contributing/translations/source-code.md | 108 ------------ 6 files changed, 202 insertions(+), 302 deletions(-) create mode 100644 docs/src/contributing/translations/developers.md delete mode 100644 docs/src/contributing/translations/docs.md delete mode 100644 docs/src/contributing/translations/source-code.md diff --git a/devenv.nix b/devenv.nix index 7c5ccb6f..c27a9a77 100644 --- a/devenv.nix +++ b/devenv.nix @@ -227,8 +227,6 @@ let doCheck = true; }; - poedit-fixed = pkgs.poedit.override { boost = pkgs.boost186; }; - # Grafana datasource provisioning grafanaDatasources = pkgs.writeText "datasources.yaml" '' apiVersion: 1 @@ -356,8 +354,6 @@ in pkgs.gcc # This seems to be borken on macos - # https://github.com/NixOS/nixpkgs/blob/ed142ab1b3a092c4d149245d0c4126a5d7ea00b0/pkgs/by-name/po/poedit/package.nix#L88 - poedit-fixed ] # macOS-specific packages ++ pkgs.lib.optionals isDarwin [ @@ -804,9 +800,8 @@ in echo "Extracting translatable strings from documentation..." MDBOOK_OUTPUT='{"xgettext": {}}' mdbook build -d $DEVENV_ROOT/docs/po $DEVENV_ROOT/docs echo "" - echo "Translation files generated in docs/po/" - echo "Use Poedit or any gettext editor to translate" - echo "Then run 'devenv tasks run docs:build' to build translated books" + echo "Updated docs/po/messages.pot" + echo "Translate on Crowdin: https://crowdin.com/project/cadmus" ''; }; @@ -820,12 +815,7 @@ in echo " cadmus-docs-serve - Serve website locally (http://localhost:3000)" echo " cargo test - Run tests (after setup)" echo " cargo xtask run-emulator - Run the emulator (after setup)" - echo " cadmus-translate - Extract translatable strings from documentation" - echo "" - echo "Translation workflow:" - echo " 1. Run 'cadmus-translate' to extract strings into docs/po/" - echo " 2. Edit .po files with Poedit or any gettext editor" - echo " 3. Run 'devenv tasks run docs:build' to build translated books" + echo " cadmus-translate - Regenerate docs/po/messages.pot (translations on Crowdin)" echo "" echo "xtask commands (cargo xtask --help for options):" echo " cargo xtask fmt - Check formatting" diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index 7891c283..a17c38bb 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -38,8 +38,7 @@ - [Profiling](contributing/telemetry/profiling.md) - [Documentation](contributing/documentation.md) - [Translations](contributing/translations/index.md) - - [UI Strings](contributing/translations/source-code.md) - - [Guide](contributing/translations/docs.md) + - [For developers](contributing/translations/developers.md) - [SQLite & SQLx](contributing/sqlite-sqlx.md) - [Library Database](contributing/library-database.md) - [Runtime Migrations](contributing/runtime-migrations.md) diff --git a/docs/src/contributing/translations/developers.md b/docs/src/contributing/translations/developers.md new file mode 100644 index 00000000..2101ce80 --- /dev/null +++ b/docs/src/contributing/translations/developers.md @@ -0,0 +1,107 @@ + + +# Translations — for developers + +This page is for contributors who change English source strings or +documentation. Translators should use +[Crowdin](https://crowdin.com/project/cadmus) instead. + +## Adding UI strings + +1. Add the message to + [`crates/core/i18n/en-GB/cadmus_core.ftl`](https://github.com/ogkevin/cadmus/blob/master/crates/core/i18n/en-GB/cadmus_core.ftl) + (kebab-case IDs, sorted within sections — see + [`crates/core/AGENTS.md`](https://github.com/ogkevin/cadmus/blob/master/crates/core/AGENTS.md)). +2. Use `fl!("message-id")` or `fl!("id", var = value)` in Rust. +3. Other languages are filled in on Crowdin — do not hand-edit locale FTL files + unless you have a specific reason. +4. Run `cargo check -p cadmus-core` to validate message IDs at compile time. + +```rust +let label = crate::fl!("my-new-message"); +let label = crate::fl!("books-loaded", count = book_count); +``` + +## Updating documentation translation sources + +After changing **user-facing** English docs (`docs/src/**/*.md` outside +`contributing/`): + +```bash +cadmus-translate # devenv +# or: MDBOOK_OUTPUT='{"xgettext": {}}' mdbook build -d docs/po docs +``` + +Commit the updated `docs/po/messages.pot` alongside your doc edits. Crowdin +picks up the new template; translators update locale `.po` files there. + +When changing website chrome strings, update +[`website/messages/en.json`](https://github.com/ogkevin/cadmus/blob/master/website/messages/en.json) +only — Crowdin handles `website/messages/.json`. + +## You do not need to maintain translated files + +After changing English source strings, do **not** hand-edit `docs/po/*.po`, +`crates/core/i18n/*/*.ftl`, or `website/messages/*.json` for other locales. + +Outdated or fuzzy entries in those files are reconciled on Crowdin and synced +back via the `crowdin` CI workflow (`skip_untranslated_strings: true` on +export). Missing translations fall back to English at runtime or build time +until translators catch up. + +## Excluding content from extraction + +Use `` before a single block (paragraph, code block, table): + + + +```markdown + + +This paragraph will not appear in the POT file. +``` + +Use `` / `` to exclude multiple +consecutive blocks: + + + +```markdown + + +This paragraph is excluded. + +And so is this one. + + +``` + +When skip directives appear inside a list, indent them to match the list +continuation level: + + + +```markdown +1. Step one. + + + + | Path | Value | + | -------------- | ------ | + | `/mnt/onboard` | stable | + + + +2. Step two. +``` + +## Previewing translated docs locally + +```bash +cargo xtask docs +cadmus-docs-serve +``` + +Use this only to verify builds — translation work happens on Crowdin. + + diff --git a/docs/src/contributing/translations/docs.md b/docs/src/contributing/translations/docs.md deleted file mode 100644 index a276800c..00000000 --- a/docs/src/contributing/translations/docs.md +++ /dev/null @@ -1,155 +0,0 @@ - - -# Translating Cadmus Documentation - -This guide explains how to translate the Cadmus documentation into other -languages. Translations live in `docs/po/` as standard GNU gettext PO files. - -## Prerequisites - -If you are using the `devenv` environment, all required tools are already -available: - -- `mdbook-xgettext` / `mdbook-gettext` — string extraction and preprocessing -- `msginit` / `msgmerge` / `msgfmt` — gettext utilities (from `gettext`) -- `poedit` — graphical PO editor - -Install them outside devenv from the vendored fork with -`cargo install --path thirdparty/mdbook-i18n-helpers/i18n-helpers --locked` and -your system's `gettext` package. - -## Adding a new language - -### 1. Extract the POT template - -Run the `cadmus-translate` script (devenv) or the equivalent command: - -```bash -cadmus-translate -``` - -This writes `docs/po/messages.pot` — the source template every translation -derives from. Commit this file whenever the English source changes so -translators have an up-to-date starting point. - -### 2. Create a PO file for your locale - -```bash -# Replace 'fr' with the BCP 47 language tag you are adding. -msginit --input=docs/po/messages.pot \ - --output-file=docs/po/fr.po \ - --locale=fr -``` - -Open `docs/po/fr.po` and set the `Language-Name` header so the language picker -displays a readable label: - -```po -"Language-Name: Français\n" -``` - -The xtask reads this header when generating `locales.json`; without it the -locale code (e.g. `fr`) is shown instead. - -### 3. Translate the strings - -Open the PO file in Poedit or any text editor and fill in each `msgstr`: - -```po -msgid "Welcome to Cadmus!" -msgstr "Bienvenue dans Cadmus !" -``` - -Preserve Markdown formatting — bold, code spans, links — exactly as in the -`msgid`. Untranslated or _fuzzy_ entries fall back to the English source. - -### 4. Build and preview - -```bash -# Build everything including translated books -cargo xtask docs --base-url http://localhost - -# Serve -cd docs-portal -zola serve -``` - -Navigate to `http://localhost:1111/en/guide/` and use the language picker in the sidebar -to switch to your locale. - -## Keeping translations up to date - -When English source files change, regenerate the template and merge new strings -into existing PO files: - -```bash -cadmus-translate # regenerate docs/po/messages.pot -msgmerge --update docs/po/fr.po docs/po/messages.pot -``` - -## Excluding content from extraction - -Two forms are available depending on how much content you need to skip. - -### Single block - -Use `` before a single block (paragraph, code block, table): - - - -```markdown - - -This paragraph will not appear in the POT file. -``` - -### Range - -Use `` / `` to exclude multiple -consecutive blocks, or an entire file section: - - - -```markdown - - -This paragraph is excluded. - -And so is this one. - - -``` - -When the skip directives appear inside an ordered or unordered list, indent -them to match the list continuation level so markdownlint does not treat them -as list-breaking elements: - - - -```markdown -1. Step one. - - - - | Path | Value | - | -------------- | ------ | - | `/mnt/onboard` | stable | - - - -2. Step two. -``` - -## How the build works - -1. `cargo xtask docs` calls `mdbook build -d book/` for each `.po` file - found in `docs/po/`, passing `MDBOOK_BOOK__LANGUAGE=`. -2. The `[preprocessor.gettext]` in `docs/book.toml` substitutes translated - strings at build time. -3. `locales.json` is written to `website/public/_shared/locales.json` with the - available locales; `lang-picker.js` fetches it at runtime to populate the - language dropdown. -4. Symlinks under `website/public//guide/` expose each locale build so - it is served at `//guide/`. - - diff --git a/docs/src/contributing/translations/index.md b/docs/src/contributing/translations/index.md index 024b5502..4c3be6fd 100644 --- a/docs/src/contributing/translations/index.md +++ b/docs/src/contributing/translations/index.md @@ -2,36 +2,103 @@ # Translations -Cadmus has three separate translation systems, each covering a different part of -the project. +## Help translate on Crowdin -| What | System | Files | -| ---------------------------- | ---------------- | --------------------------- | -| [Documentation](docs.md) | GNU gettext / PO | `docs/po/*.po` | -| Website landing page | i18next JSON | `website/messages/*.json` | -| [UI strings](source-code.md) | Fluent (FTL) | `crates/core/i18n/**/*.ftl` | +The easiest way to contribute translations is on +[Crowdin](https://crowdin.com/project/cadmus). UI strings, the user guide, and +website chrome are all translated there — no local setup or PO/FTL/JSON editing +required. -Locale codes for the website UI are derived from `docs/po/*.po` (same set as -the user guide). Add `website/messages/{locale}.json` when translating UI -strings; an empty `{}` file enables the locale route with English fallback -until strings are translated. +> [!NOTE] +> Direct pull requests to `docs/po/*.po`, `crates/core/i18n/*/*.ftl`, or +> `website/messages/*.json` are discouraged. Crowdin keeps translators +> coordinated and avoids merge conflicts. -Pick the guide that matches what you want to translate. +## Three translation surfaces -## Website URL layout +| What | System | Source files | Crowdin source | +| ------------------- | ---------------- | ----------------------------------------- | ------------------------------ | +| Cadmus UI | [Fluent] FTL | `crates/core/i18n//cadmus_core.ftl` | `crates/core/i18n/en-GB/*.ftl` | +| User guide (mdBook) | GNU gettext PO | `docs/po/.po` | `docs/po/messages.pot` | +| Website | [next-intl] JSON | `website/messages/.json` | `website/messages/en.json` | -All website content uses **locale-first URLs**. The locale is always the first -path segment: +The website strings cover the portal chrome (landing page, nav, doc-grid cards, +footer). The mdBook user guide is a separate, larger translation surface +embedded into the site. -| Content | Example URL | -| ------------- | -------------------------- | -| Website home | `/en/`, `/fr/` | -| User guide | `/en/guide/`, `/fr/guide/` | -| API reference | `/en/api/cadmus_core/` | -| Storybook | `/en/storybook/` | +## How Crowdin sync works -Bare `/` redirects to `/en/` (or a saved or browser-matched locale). API docs -and Storybook serve the same English content under every locale prefix so -language switching only swaps the first URL segment. +```mermaid +flowchart LR + contributor[Contributor merges English changes] + crowdin[Crowdin project] + ci[Crowdin GitHub Action] + l10nPR["l10n PR (l10n/main)"] + main[main branch] + + contributor --> crowdin + crowdin --> ci + ci --> l10nPR + l10nPR --> main +``` + +- [`crowdin.yml`](https://github.com/ogkevin/cadmus/blob/master/crowdin.yml) + maps all three English sources to per-locale paths (`%two_letters_code%`). +- [`.github/workflows/crowdin.yml`](https://github.com/ogkevin/cadmus/blob/master/.github/workflows/crowdin.yml) + runs on pushes to `main` and on pull requests that touch + `crates/core/i18n/**`, `docs/po/**`, or `website/messages/**`. +- The action uploads sources and existing translations, downloads updates, and + opens or updates an `l10n/` pull request labelled `l10n` and + `crowdin`. +- **Open Crowdin sync PRs:** + [github.com/ogkevin/cadmus/pulls?q=is%3Aopen+label%3Acrowdin](https://github.com/ogkevin/cadmus/pulls?q=is%3Aopen+label%3Acrowdin) + +## How translations are wired at build time + +### UI (Fluent) + +- English fallback: `en-GB` + ([`crates/core/i18n/en-GB/cadmus_core.ftl`](https://github.com/ogkevin/cadmus/blob/master/crates/core/i18n/en-GB/cadmus_core.ftl)). +- [`crates/core/src/i18n.rs`](https://github.com/ogkevin/cadmus/blob/master/crates/core/src/i18n.rs) + embeds FTL files at compile time via `rust-embed` / `i18n_embed`; `fl!()` + resolves message IDs at compile time. +- [`crates/core/build.rs`](https://github.com/ogkevin/cadmus/blob/master/crates/core/build.rs) + scans `i18n/` subdirectories and emits `AVAILABLE_LOCALES` for the Settings + language picker. +- Active language comes from `settings.locale` at startup. + +### User guide (gettext / mdBook) + +- [`docs/book.toml`](https://github.com/ogkevin/cadmus/blob/master/docs/book.toml) + `[preprocessor.gettext]` substitutes translated strings during mdBook build. +- `cargo xtask docs` builds one mdBook output per `docs/po/*.po` locale. +- [`docs/lang-picker.js`](https://github.com/ogkevin/cadmus/blob/master/docs/lang-picker.js) + reads `website/public/_shared/locales.json` for the mdBook sidebar language + dropdown. + +### Website (Next.js / next-intl) + +- English source: + [`website/messages/en.json`](https://github.com/ogkevin/cadmus/blob/master/website/messages/en.json); + locale files loaded by + [`website/i18n/request.ts`](https://github.com/ogkevin/cadmus/blob/master/website/i18n/request.ts). +- [`website/scripts/generate-locales.mjs`](https://github.com/ogkevin/cadmus/blob/master/website/scripts/generate-locales.mjs) + scans `docs/po/*.po` (plus `en`) and writes + [`website/i18n/locales.generated.ts`](https://github.com/ogkevin/cadmus/blob/master/website/i18n/locales.generated.ts) + — the website locale list follows the mdBook translation set. +- `cargo xtask docs` orchestrates the full portal build: mdBook per locale → + `generate-locales.mjs` / `generate-version.mjs` → symlinks under + `website/public//guide/` → `next build` → static export at + `website/out//`. +- The language switcher on the landing page + ([`website/components/language-switcher/`](https://github.com/ogkevin/cadmus/tree/master/website/components/language-switcher)) + routes between locale-prefixed site paths; each locale's `/guide/` subtree is + the translated mdBook output. + +For adding new English source strings or updating the POT template, see +[For developers](developers.md). + +[Fluent]: https://projectfluent.org +[next-intl]: https://next-intl.dev diff --git a/docs/src/contributing/translations/source-code.md b/docs/src/contributing/translations/source-code.md deleted file mode 100644 index 35b50cc2..00000000 --- a/docs/src/contributing/translations/source-code.md +++ /dev/null @@ -1,108 +0,0 @@ - - -# Translating Source Strings - -The Cadmus UI uses [Fluent] for all user-visible strings. Translations are -embedded directly into the binary at compile time — no external files are -needed on the device. - -## How it works - -- FTL files live under `crates/core/i18n//cadmus_core.ftl`. -- The fallback language is **en-GB**; any string missing from a translation - falls back to the English text automatically. -- `crates/core/src/i18n.rs` loads the correct language at startup based on - `settings.locale`. -- The `fl!("message-id")` macro resolves message IDs at **compile time**. - -## Adding a new language - -### 1. Create the FTL file - -Create a new file at the path matching the BCP 47 tag for your language: - -```text -crates/core/i18n//cadmus_core.ftl -``` - -For example, for French: - -```text -crates/core/i18n/fr/cadmus_core.ftl -``` - -### 2. Copy and translate the English strings - -Use the English fallback file as your starting point: - -```text -crates/core/i18n/en-GB/cadmus_core.ftl -``` - -Translate each message value. The message ID (left of `=`) must stay -unchanged — only the value (right of `=`) changes: - -```fluent -# en-GB -startup-loading = Cadmus starting up… - -# fr -startup-loading = Chargement de Cadmus… -``` - -### 3. Set the locale in Settings - -To activate the new language locally during development, add a `locale` key to -your `Settings.toml`: - -```toml -locale = "fr" -``` - -### 4. Build and verify - -```bash -cargo check -p cadmus-core -``` - -The `fl!()` macro validates all message IDs at compile time. A successful build -confirms the FTL file is well-formed and all IDs referenced in code are present. - -## Adding a string to the source code - -When you add a new UI string: - -1. Add the message to `en-GB`, and any other languages you know how to translate - it into. -2. Use the `fl!()` macro in the Rust source: - - ```rust - let label = crate::fl!("my-new-message"); - ``` - -3. For messages with variables, use named arguments: - - ```fluent - # In the FTL file - books-loaded = Loaded { $count } books - ``` - - ```rust - let label = crate::fl!("books-loaded", count = book_count); - ``` - -## FTL file format - -Fluent uses a straightforward syntax. A few rules to keep in mind: - -- Message IDs use `kebab-case`. -- Values can span multiple lines by indenting continuation lines. -- Use Unicode characters directly — no escaping needed. -- Comments start with `#`. - -For the full syntax reference see the [Fluent syntax guide]. - -[Fluent]: https://projectfluent.org -[Fluent syntax guide]: https://projectfluent.org/fluent/guide/ - - From adaba95c9d252e1fa65c5496d1302157b97604a1 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 8 Jul 2026 10:58:10 +0000 Subject: [PATCH 2/5] docs: simplify website wording in translations guide Drop 'chrome' and 'portal' in favour of 'website', and clarify that i18n:skip directives apply to mdBook documentation extraction only. Co-authored-by: Kevin --- docs/src/contributing/translations/developers.md | 7 +++++-- docs/src/contributing/translations/index.md | 10 +++++----- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/docs/src/contributing/translations/developers.md b/docs/src/contributing/translations/developers.md index 2101ce80..8cb99be9 100644 --- a/docs/src/contributing/translations/developers.md +++ b/docs/src/contributing/translations/developers.md @@ -35,7 +35,7 @@ cadmus-translate # devenv Commit the updated `docs/po/messages.pot` alongside your doc edits. Crowdin picks up the new template; translators update locale `.po` files there. -When changing website chrome strings, update +When changing website strings, update [`website/messages/en.json`](https://github.com/ogkevin/cadmus/blob/master/website/messages/en.json) only — Crowdin handles `website/messages/.json`. @@ -49,7 +49,10 @@ back via the `crowdin` CI workflow (`skip_untranslated_strings: true` on export). Missing translations fall back to English at runtime or build time until translators catch up. -## Excluding content from extraction +## Excluding documentation from extraction + +These directives apply to the mdBook user guide only. They tell +`cadmus-translate` which Markdown blocks to omit from `docs/po/messages.pot`. Use `` before a single block (paragraph, code block, table): diff --git a/docs/src/contributing/translations/index.md b/docs/src/contributing/translations/index.md index 4c3be6fd..9b513e67 100644 --- a/docs/src/contributing/translations/index.md +++ b/docs/src/contributing/translations/index.md @@ -6,7 +6,7 @@ The easiest way to contribute translations is on [Crowdin](https://crowdin.com/project/cadmus). UI strings, the user guide, and -website chrome are all translated there — no local setup or PO/FTL/JSON editing +the website are all translated there — no local setup or PO/FTL/JSON editing required. > [!NOTE] @@ -22,9 +22,9 @@ required. | User guide (mdBook) | GNU gettext PO | `docs/po/.po` | `docs/po/messages.pot` | | Website | [next-intl] JSON | `website/messages/.json` | `website/messages/en.json` | -The website strings cover the portal chrome (landing page, nav, doc-grid cards, -footer). The mdBook user guide is a separate, larger translation surface -embedded into the site. +The website strings cover the landing page, nav, doc-grid cards, and footer. +The mdBook user guide is a separate, larger translation surface embedded into +the site. ## How Crowdin sync works @@ -86,7 +86,7 @@ flowchart LR scans `docs/po/*.po` (plus `en`) and writes [`website/i18n/locales.generated.ts`](https://github.com/ogkevin/cadmus/blob/master/website/i18n/locales.generated.ts) — the website locale list follows the mdBook translation set. -- `cargo xtask docs` orchestrates the full portal build: mdBook per locale → +- `cargo xtask docs` orchestrates the full website build: mdBook per locale → `generate-locales.mjs` / `generate-version.mjs` → symlinks under `website/public//guide/` → `next build` → static export at `website/out//`. From 3d9d38b8888882f34c2e92dad8f34aa9abb1f0c6 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 8 Jul 2026 11:03:59 +0000 Subject: [PATCH 3/5] docs: trim Crowdin intro and link developers page to index Co-authored-by: Kevin --- docs/src/contributing/translations/developers.md | 4 ++-- docs/src/contributing/translations/index.md | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/src/contributing/translations/developers.md b/docs/src/contributing/translations/developers.md index 8cb99be9..64658b36 100644 --- a/docs/src/contributing/translations/developers.md +++ b/docs/src/contributing/translations/developers.md @@ -3,8 +3,8 @@ # Translations — for developers This page is for contributors who change English source strings or -documentation. Translators should use -[Crowdin](https://crowdin.com/project/cadmus) instead. +documentation. Translators should use [Crowdin](https://crowdin.com/project/cadmus) +or see the main [Translations](index.md) page. ## Adding UI strings diff --git a/docs/src/contributing/translations/index.md b/docs/src/contributing/translations/index.md index 9b513e67..5f7acfcf 100644 --- a/docs/src/contributing/translations/index.md +++ b/docs/src/contributing/translations/index.md @@ -6,8 +6,7 @@ The easiest way to contribute translations is on [Crowdin](https://crowdin.com/project/cadmus). UI strings, the user guide, and -the website are all translated there — no local setup or PO/FTL/JSON editing -required. +the website are all translated there. > [!NOTE] > Direct pull requests to `docs/po/*.po`, `crates/core/i18n/*/*.ftl`, or From 544864d0a1d25ad97cd97915a7e2d06cc02f56dc Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 8 Jul 2026 11:18:43 +0000 Subject: [PATCH 4/5] fix(docs): correct mermaid image paths for nested mdBook chapters The EPUB preprocessor always used ../mermaid-images/, which resolves incorrectly for chapters nested below src/ (e.g. contributing/translations/). Compute the relative path from each chapter file instead. Fixes cadmus-docs build failure on translations/index.md mermaid diagram. Co-authored-by: Kevin --- docs/mdbook-mermaid-preprocessor.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/docs/mdbook-mermaid-preprocessor.py b/docs/mdbook-mermaid-preprocessor.py index a9be411b..fead68b0 100755 --- a/docs/mdbook-mermaid-preprocessor.py +++ b/docs/mdbook-mermaid-preprocessor.py @@ -106,7 +106,19 @@ def render_mermaid_to_png(mermaid_code: str, output_path: Path) -> bool: return False -def process_chapter_content(content: str, chapter_name: str, png_dir: Path) -> str: +def relative_mermaid_path(chapter_path: str | None, img_filename: str) -> str: + """Return a POSIX path from the chapter file to a PNG in `src/mermaid-images/`.""" + mermaid_dir = Path("mermaid-images") + if chapter_path: + chapter_dir = Path(chapter_path).parent + rel_dir = os.path.relpath(mermaid_dir, chapter_dir) + return f"{rel_dir}/{img_filename}".replace("\\", "/") + return f"../mermaid-images/{img_filename}" + + +def process_chapter_content( + content: str, chapter_name: str, png_dir: Path, chapter_path: str | None +) -> str: """ Process chapter content, converting mermaid code blocks to PNG image references. @@ -128,7 +140,7 @@ def replace_mermaid_block(match): mermaid_code = match.group(1).strip() img_filename = f"{chapter_name}-diagram-{diagram_count}.png" img_path = png_dir / img_filename - rel_path = f"../mermaid-images/{img_filename}" + rel_path = relative_mermaid_path(chapter_path, img_filename) if render_mermaid_to_png(mermaid_code, img_path): return f"![Mermaid Diagram]({rel_path})" @@ -163,11 +175,12 @@ def process_section(section): chapter = section["Chapter"] chapter_name = chapter.get("name", "unnamed") chapter_name = re.sub(r"[^\w\-]", "-", chapter_name.lower()) + chapter_path = chapter.get("path") content = chapter.get("content", "") if "```mermaid" in content: chapter["content"] = process_chapter_content( - content, chapter_name, png_dir + content, chapter_name, png_dir, chapter_path ) sub_items = chapter.get("sub_items", []) From 49d46ef9d86c3a5df3cca3b69f390ac9c1d84e2b Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 8 Jul 2026 12:35:16 +0000 Subject: [PATCH 5/5] docs: document chapter_path in mermaid preprocessor docstring Co-authored-by: Kevin --- docs/mdbook-mermaid-preprocessor.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/mdbook-mermaid-preprocessor.py b/docs/mdbook-mermaid-preprocessor.py index fead68b0..e2781027 100755 --- a/docs/mdbook-mermaid-preprocessor.py +++ b/docs/mdbook-mermaid-preprocessor.py @@ -126,6 +126,8 @@ def process_chapter_content( content: The markdown content of the chapter chapter_name: Name of the chapter (for PNG filenames) png_dir: Directory where PNG files should be saved + chapter_path: Source path of the chapter relative to `src/`, used to + compute the image link path for nested chapters Returns: Modified content with mermaid blocks replaced by image references