From 11bc37c96878cd1d9a822c1dad658f43c9be6604 Mon Sep 17 00:00:00 2001 From: James Rich <2199651+jamesarich@users.noreply.github.com> Date: Tue, 19 May 2026 12:06:44 -0700 Subject: [PATCH] docs: move English sources into docs/en/ locale folder (#5501) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/docs-governance.yml | 34 ++++---- .../org/meshtastic/buildlogic/DocsTasks.kt | 12 +-- crowdin.yml | 5 +- docs/README.md | 45 +++++++++++ docs/_config.yml | 81 ++++++++++--------- docs/_layouts/locale_page.html | 3 +- docs/{ => en}/developer.md | 0 .../developer/adding-a-feature-module.md | 0 docs/{ => en}/developer/architecture.md | 0 docs/{ => en}/developer/codebase.md | 0 docs/{ => en}/developer/contributing.md | 0 docs/{ => en}/developer/measurement.md | 0 .../developer/navigation-and-deep-links.md | 0 docs/{ => en}/developer/persistence.md | 0 docs/{ => en}/developer/testing.md | 0 docs/{ => en}/developer/transport.md | 0 docs/{ => en}/index.md | 0 docs/{ => en}/translations.md | 0 docs/{ => en}/user.md | 0 docs/{ => en}/user/connections.md | 0 docs/{ => en}/user/desktop.md | 0 docs/{ => en}/user/discovery.md | 0 docs/{ => en}/user/firmware.md | 0 docs/{ => en}/user/map-and-waypoints.md | 0 docs/{ => en}/user/messages-and-channels.md | 0 docs/{ => en}/user/mqtt.md | 0 docs/{ => en}/user/node-metrics.md | 0 docs/{ => en}/user/nodes.md | 0 docs/{ => en}/user/onboarding.md | 0 docs/{ => en}/user/settings-module-admin.md | 0 docs/{ => en}/user/settings-radio-user.md | 0 docs/{ => en}/user/signal-meter.md | 0 docs/{ => en}/user/tak.md | 0 docs/{ => en}/user/telemetry-and-sensors.md | 0 docs/{ => en}/user/translate.md | 0 docs/{ => en}/user/units-and-locale.md | 0 feature/docs/build.gradle.kts | 19 ++--- .../feature/docs/data/DocBundleLoader.kt | 52 ++++++------ scripts/check-doc-coverage.js | 2 +- scripts/check-doc-freshness.js | 2 +- scripts/sync-android-docs.js | 4 +- scripts/validate-doc-links.js | 6 +- 42 files changed, 155 insertions(+), 110 deletions(-) create mode 100644 docs/README.md rename docs/{ => en}/developer.md (100%) rename docs/{ => en}/developer/adding-a-feature-module.md (100%) rename docs/{ => en}/developer/architecture.md (100%) rename docs/{ => en}/developer/codebase.md (100%) rename docs/{ => en}/developer/contributing.md (100%) rename docs/{ => en}/developer/measurement.md (100%) rename docs/{ => en}/developer/navigation-and-deep-links.md (100%) rename docs/{ => en}/developer/persistence.md (100%) rename docs/{ => en}/developer/testing.md (100%) rename docs/{ => en}/developer/transport.md (100%) rename docs/{ => en}/index.md (100%) rename docs/{ => en}/translations.md (100%) rename docs/{ => en}/user.md (100%) rename docs/{ => en}/user/connections.md (100%) rename docs/{ => en}/user/desktop.md (100%) rename docs/{ => en}/user/discovery.md (100%) rename docs/{ => en}/user/firmware.md (100%) rename docs/{ => en}/user/map-and-waypoints.md (100%) rename docs/{ => en}/user/messages-and-channels.md (100%) rename docs/{ => en}/user/mqtt.md (100%) rename docs/{ => en}/user/node-metrics.md (100%) rename docs/{ => en}/user/nodes.md (100%) rename docs/{ => en}/user/onboarding.md (100%) rename docs/{ => en}/user/settings-module-admin.md (100%) rename docs/{ => en}/user/settings-radio-user.md (100%) rename docs/{ => en}/user/signal-meter.md (100%) rename docs/{ => en}/user/tak.md (100%) rename docs/{ => en}/user/telemetry-and-sensors.md (100%) rename docs/{ => en}/user/translate.md (100%) rename docs/{ => en}/user/units-and-locale.md (100%) diff --git a/.github/workflows/docs-governance.yml b/.github/workflows/docs-governance.yml index 4b526c996..a10ed89b8 100644 --- a/.github/workflows/docs-governance.yml +++ b/.github/workflows/docs-governance.yml @@ -39,7 +39,7 @@ jobs: '^feature/.*/src/commonMain/.*/(ui|component|screen)/|^feature/.*/src/androidMain/.*/ui/|^core/ui/src/commonMain/' \ | grep -v 'Test\|Preview\|__Snapshots__' || true) - docs_changed=$(echo "$changed" | grep -E '^docs/(user|developer)/' || true) + docs_changed=$(echo "$changed" | grep -E '^docs/en/(user|developer)/' || true) echo "views_changed<> "$GITHUB_OUTPUT" echo "$views_changed" >> "$GITHUB_OUTPUT" @@ -64,9 +64,9 @@ jobs: const body = [ '## 📄 Docs staleness check — advisory', '', - 'This PR modifies user-facing UI source files but does not update any page under `docs/user/` or `docs/developer/`.', + 'This PR modifies user-facing UI source files but does not update any page under `docs/en/user/` or `docs/en/developer/`.', '', - '> ⚠️ Doc changes propagate to **3 consumers**: in-app docs browser, Jekyll site (GitHub Pages), and meshtastic.org (Docusaurus sync). Updating a page in `docs/` automatically flows to all three.', + '> ⚠️ Doc changes propagate to **3 consumers**: in-app docs browser, Jekyll site (GitHub Pages), and meshtastic.org (Docusaurus sync). Updating a page in `docs/en/` automatically flows to all three.', '', '**Changed source files:**', '```', @@ -76,19 +76,19 @@ jobs: '**What to check:**', '| Changed area | Likely doc page |', '|---|---|', - '| `feature/messaging/` | `docs/user/messages-and-channels.md` |', - '| `feature/node/` | `docs/user/nodes.md` or `docs/user/node-metrics.md` |', - '| `feature/map/` | `docs/user/map-and-waypoints.md` |', - '| `feature/connections/` | `docs/user/connections.md` |', - '| `feature/settings/` | `docs/user/settings-radio-user.md` or `docs/user/settings-module-admin.md` |', - '| `feature/firmware/` | `docs/user/firmware.md` |', - '| `feature/intro/` | `docs/user/onboarding.md` |', - '| `feature/discovery/` | `docs/user/discovery.md` |', + '| `feature/messaging/` | `docs/en/user/messages-and-channels.md` |', + '| `feature/node/` | `docs/en/user/nodes.md` or `docs/en/user/node-metrics.md` |', + '| `feature/map/` | `docs/en/user/map-and-waypoints.md` |', + '| `feature/connections/` | `docs/en/user/connections.md` |', + '| `feature/settings/` | `docs/en/user/settings-radio-user.md` or `docs/en/user/settings-module-admin.md` |', + '| `feature/firmware/` | `docs/en/user/firmware.md` |', + '| `feature/intro/` | `docs/en/user/onboarding.md` |', + '| `feature/discovery/` | `docs/en/user/discovery.md` |', '| `feature/docs/` | Internal docs infrastructure |', - '| `core/ui/` | `docs/developer/codebase.md` or component-specific user pages |', + '| `core/ui/` | `docs/en/developer/codebase.md` or component-specific user pages |', '', '**New page checklist** (if adding a new doc page):', - '1. Create the `.md` file in `docs/user/` or `docs/developer/` with `last_updated` frontmatter', + '1. Create the `.md` file in `docs/en/user/` or `docs/en/developer/` with `last_updated` frontmatter', '2. Register in `DocBundleLoader.kt` with string resources (in-app browser)', '3. Jekyll and Docusaurus sync pick up new pages automatically — no config change needed', '', @@ -141,14 +141,14 @@ jobs: owner: context.repo.owner, repo: context.repo.repo, comment_id: existing.id, - body: '## ✅ Docs staleness check passed\n\nThis PR includes updates to `docs/` alongside the source changes. Thank you!', + body: '## ✅ Docs staleness check passed\n\nThis PR includes updates to `docs/en/` alongside the source changes. Thank you!', }); } - name: Advisory status if: steps.changed.outputs.stale == 'true' run: | - echo "::warning::UI source files changed without corresponding docs/ updates." + echo "::warning::UI source files changed without corresponding docs/en/ updates." echo "Add the 'skip-docs-check' label if this PR does not require a doc update." echo "NOTE: This check is advisory while docs coverage matures across platforms." echo "To upgrade to blocking, change this step to 'exit 1'." @@ -170,7 +170,7 @@ jobs: node-version: "24" - name: Validate internal links - run: node scripts/validate-doc-links.js docs + run: node scripts/validate-doc-links.js docs/en - name: Check doc coverage run: node scripts/check-doc-coverage.js . @@ -179,7 +179,7 @@ jobs: run: | loader="feature/docs/src/commonMain/kotlin/org/meshtastic/feature/docs/data/DocBundleLoader.kt" missing=0 - for f in docs/user/*.md docs/developer/*.md; do + for f in docs/en/user/*.md docs/en/developer/*.md; do slug=$(basename "$f" .md) if ! grep -q "\"$slug\"" "$loader"; then echo "ERROR: $slug not registered in DocBundleLoader.kt" diff --git a/build-logic/convention/src/main/kotlin/org/meshtastic/buildlogic/DocsTasks.kt b/build-logic/convention/src/main/kotlin/org/meshtastic/buildlogic/DocsTasks.kt index 15cbdca9e..4e9745934 100644 --- a/build-logic/convention/src/main/kotlin/org/meshtastic/buildlogic/DocsTasks.kt +++ b/build-logic/convention/src/main/kotlin/org/meshtastic/buildlogic/DocsTasks.kt @@ -100,9 +100,10 @@ abstract class GenerateDocsBundleTask : DefaultTask() { val indexEntries = mutableListOf() var pageCount = 0 - // Process English user and developer directories + // Process English user and developer directories (under docs/en/) + val enDir = File(src, "en") listOf("user", "developer").forEach { section -> - val sectionDir = File(src, section) + val sectionDir = File(enDir, section) if (!sectionDir.exists()) return@forEach sectionDir.listFiles { f -> f.extension == "md" }?.sortedBy { it.name }?.forEach { mdFile -> @@ -143,8 +144,9 @@ abstract class GenerateDocsBundleTask : DefaultTask() { // Process Crowdin locale directories: docs/{qualifier}/user/*.md // Crowdin %android_code% produces: fr, pt-rBR, zh-rCN, zh-rTW + // Skip "en" since English sources are handled above. val localePattern = Regex("^[a-z]{2,3}(-r[A-Z]{2})?$") - src.listFiles { f -> f.isDirectory && localePattern.matches(f.name) } + src.listFiles { f -> f.isDirectory && localePattern.matches(f.name) && f.name != "en" } ?.sortedBy { it.name } ?.forEach { localeDir -> val locale = localeDir.name @@ -198,7 +200,7 @@ abstract class GenerateDocsBundleTask : DefaultTask() { File(cssDir, "docs.css").writeText(generateCss()) // Write locales manifest (for consumers that need to know available translations) - val localesManifest = src.listFiles { f -> f.isDirectory && localePattern.matches(f.name) } + val localesManifest = src.listFiles { f -> f.isDirectory && localePattern.matches(f.name) && f.name != "en" } ?.map { it.name }?.sorted() ?: emptyList() val manifestFile = File(out, "locales.json") manifestFile.writeText(localesManifest.joinToString(", ", "[", "]") { "\"$it\"" }) @@ -258,7 +260,7 @@ abstract class GenerateDocsBundleTask : DefaultTask() { .replace(Regex("^---[\\s\\S]*?---\\s*", RegexOption.MULTILINE), "") .replace("&", "&").replace("<", "<").replace(">", ">") val dir = if (locale == "ar") "rtl" else "ltr" - // Locale pages are one level deeper: docs/{locale}/user/foo.html vs docs/user/foo.html + // Locale pages are one level deeper: docs/{locale}/user/foo.html vs docs/en/user/foo.html val cssPath = if (locale != "en") "../../styles/docs.css" else "../styles/docs.css" return """ | diff --git a/crowdin.yml b/crowdin.yml index 64a130ca7..6fa9688fe 100644 --- a/crowdin.yml +++ b/crowdin.yml @@ -11,10 +11,11 @@ files: - source: /fastlane/metadata/android/en-US/changelogs/default.txt translation: /fastlane/metadata/android/%locale%/changelogs/%original_file_name% # In-app docs — user guide only (developer guide is English-only) + # English sources live under docs/en/; translations land at docs/{android_code}/ # Uses %android_code% to output Android/CMP qualifier format directly (pt-rBR, zh-rCN, fr) - - source: /docs/user/*.md + - source: /docs/en/user/*.md translation: /docs/%android_code%/user/%original_file_name% type: md - - source: /docs/index.md + - source: /docs/en/index.md translation: /docs/%android_code%/%original_file_name% type: md diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 000000000..35461fd8d --- /dev/null +++ b/docs/README.md @@ -0,0 +1,45 @@ +# Documentation Structure + +This directory contains the source documentation for the Meshtastic Android/Desktop/iOS app. +It serves three consumers: + +1. **In-app docs browser** — bundled via Compose Resources at build time +2. **Jekyll site** — GitHub Pages (this directory is the Jekyll source root) +3. **meshtastic.org** — Docusaurus sync (upstream consumption) + +## Locale Layout + +``` +docs/ +├── _config.yml, _data/, _layouts/, _sass/ ← Jekyll site infrastructure +├── en/ ← English source (edit here) +│ ├── user/ ← User Guide pages +│ ├── developer/ ← Developer Guide pages +│ ├── index.md ← Site home page +│ ├── user.md ← User Guide nav parent +│ ├── developer.md ← Developer Guide nav parent +│ └── translations.md ← Translations landing page +├── fr-rFR/ ← French (Crowdin-generated) +│ └── user/ ← Translated user guide +├── de-rDE/ ← German (Crowdin-generated) +│ └── user/ +└── ... ← Other locales +``` + +## Editing Guidelines + +- **English source**: Edit files under `docs/en/`. These are the authoritative source. +- **Translations**: Do **not** edit files in locale folders directly. They are auto-generated + by [Crowdin](https://crowdin.com/project/meshtastic-android) and will be overwritten on sync. + Contribute translations via Crowdin instead. +- **Adding a page**: Create the `.md` file in `docs/en/user/` or `docs/en/developer/`, then + register it in `feature/docs/.../DocBundleLoader.kt` for in-app bundling. + +## How Translations Work + +1. English source files (`docs/en/user/*.md`) are uploaded to Crowdin as translation sources +2. Volunteers translate via the Crowdin web UI +3. Crowdin PRs land translated files at `docs/{android_code}/user/*.md` (e.g., `fr-rFR`, `pt-rBR`) +4. At build time, the Gradle `syncTranslatedDocsToComposeResources` task bundles them into + locale-qualified Compose Resources for the in-app reader +5. The in-app `DocBundleLoader` tries the user's locale first, then falls back to English diff --git a/docs/_config.yml b/docs/_config.yml index bd7c6dc00..d29e8509e 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -28,12 +28,12 @@ color_scheme: meshtastic # Default front-matter for pages in subdirectories defaults: - scope: - path: "user/" + path: "en/user/" values: parent: User Guide layout: default - scope: - path: "developer/" + path: "en/developer/" values: parent: Developer Guide layout: default @@ -41,229 +41,229 @@ defaults: # They use a dedicated locale layout with a back-link to the English version. # Auto-generated from Android app locales (values-* resource dirs). - scope: - path: "ar/" + path: "ar" values: layout: locale_page locale: ar nav_exclude: true - scope: - path: "be/" + path: "be" values: layout: locale_page locale: be nav_exclude: true - scope: - path: "bg/" + path: "bg" values: layout: locale_page locale: bg nav_exclude: true - scope: - path: "ca/" + path: "ca" values: layout: locale_page locale: ca nav_exclude: true - scope: - path: "cs/" + path: "cs" values: layout: locale_page locale: cs nav_exclude: true - scope: - path: "de/" + path: "de" values: layout: locale_page locale: de nav_exclude: true - scope: - path: "el/" + path: "el" values: layout: locale_page locale: el nav_exclude: true - scope: - path: "es/" + path: "es" values: layout: locale_page locale: es nav_exclude: true - scope: - path: "et/" + path: "et" values: layout: locale_page locale: et nav_exclude: true - scope: - path: "fi/" + path: "fi" values: layout: locale_page locale: fi nav_exclude: true - scope: - path: "fr/" + path: "fr" values: layout: locale_page locale: fr nav_exclude: true - scope: - path: "ga/" + path: "ga" values: layout: locale_page locale: ga nav_exclude: true - scope: - path: "gl/" + path: "gl" values: layout: locale_page locale: gl nav_exclude: true - scope: - path: "he/" + path: "he" values: layout: locale_page locale: he nav_exclude: true - scope: - path: "hr/" + path: "hr" values: layout: locale_page locale: hr nav_exclude: true - scope: - path: "ht/" + path: "ht" values: layout: locale_page locale: ht nav_exclude: true - scope: - path: "hu/" + path: "hu" values: layout: locale_page locale: hu nav_exclude: true - scope: - path: "is/" + path: "is" values: layout: locale_page locale: is nav_exclude: true - scope: - path: "it/" + path: "it" values: layout: locale_page locale: it nav_exclude: true - scope: - path: "ja/" + path: "ja" values: layout: locale_page locale: ja nav_exclude: true - scope: - path: "ko/" + path: "ko" values: layout: locale_page locale: ko nav_exclude: true - scope: - path: "lt/" + path: "lt" values: layout: locale_page locale: lt nav_exclude: true - scope: - path: "nl/" + path: "nl" values: layout: locale_page locale: nl nav_exclude: true - scope: - path: "no/" + path: "no" values: layout: locale_page locale: no nav_exclude: true - scope: - path: "pl/" + path: "pl" values: layout: locale_page locale: pl nav_exclude: true - scope: - path: "pt/" + path: "pt" values: layout: locale_page locale: pt nav_exclude: true - scope: - path: "pt-rBR/" + path: "pt-rBR" values: layout: locale_page locale: pt-rBR nav_exclude: true - scope: - path: "ro/" + path: "ro" values: layout: locale_page locale: ro nav_exclude: true - scope: - path: "ru/" + path: "ru" values: layout: locale_page locale: ru nav_exclude: true - scope: - path: "sk/" + path: "sk" values: layout: locale_page locale: sk nav_exclude: true - scope: - path: "sl/" + path: "sl" values: layout: locale_page locale: sl nav_exclude: true - scope: - path: "sq/" + path: "sq" values: layout: locale_page locale: sq nav_exclude: true - scope: - path: "sr/" + path: "sr" values: layout: locale_page locale: sr nav_exclude: true - scope: - path: "sv/" + path: "sv" values: layout: locale_page locale: sv nav_exclude: true - scope: - path: "tr/" + path: "tr" values: layout: locale_page locale: tr nav_exclude: true - scope: - path: "uk/" + path: "uk" values: layout: locale_page locale: uk nav_exclude: true - scope: - path: "zh-rCN/" + path: "zh-rCN" values: layout: locale_page locale: zh-rCN nav_exclude: true - scope: - path: "zh-rTW/" + path: "zh-rTW" values: layout: locale_page locale: zh-rTW @@ -285,5 +285,6 @@ exclude: - Gemfile - Gemfile.lock - assets/screenshots/.gitkeep + - README.md - "*.sh" diff --git a/docs/_layouts/locale_page.html b/docs/_layouts/locale_page.html index 167e80b09..b8e958245 100644 --- a/docs/_layouts/locale_page.html +++ b/docs/_layouts/locale_page.html @@ -7,7 +7,8 @@ layout: default {% assign page_path = page.path %} {% assign path_parts = page_path | split: "/" %} {% assign remaining_parts = path_parts | slice: 1, path_parts.size %} -{% assign en_path = remaining_parts | join: "/" | replace: ".md", "" %} +{% assign en_relative = remaining_parts | join: "/" | replace: ".md", "" %} +{% assign en_path = "en/" | append: en_relative %}

diff --git a/docs/developer.md b/docs/en/developer.md similarity index 100% rename from docs/developer.md rename to docs/en/developer.md diff --git a/docs/developer/adding-a-feature-module.md b/docs/en/developer/adding-a-feature-module.md similarity index 100% rename from docs/developer/adding-a-feature-module.md rename to docs/en/developer/adding-a-feature-module.md diff --git a/docs/developer/architecture.md b/docs/en/developer/architecture.md similarity index 100% rename from docs/developer/architecture.md rename to docs/en/developer/architecture.md diff --git a/docs/developer/codebase.md b/docs/en/developer/codebase.md similarity index 100% rename from docs/developer/codebase.md rename to docs/en/developer/codebase.md diff --git a/docs/developer/contributing.md b/docs/en/developer/contributing.md similarity index 100% rename from docs/developer/contributing.md rename to docs/en/developer/contributing.md diff --git a/docs/developer/measurement.md b/docs/en/developer/measurement.md similarity index 100% rename from docs/developer/measurement.md rename to docs/en/developer/measurement.md diff --git a/docs/developer/navigation-and-deep-links.md b/docs/en/developer/navigation-and-deep-links.md similarity index 100% rename from docs/developer/navigation-and-deep-links.md rename to docs/en/developer/navigation-and-deep-links.md diff --git a/docs/developer/persistence.md b/docs/en/developer/persistence.md similarity index 100% rename from docs/developer/persistence.md rename to docs/en/developer/persistence.md diff --git a/docs/developer/testing.md b/docs/en/developer/testing.md similarity index 100% rename from docs/developer/testing.md rename to docs/en/developer/testing.md diff --git a/docs/developer/transport.md b/docs/en/developer/transport.md similarity index 100% rename from docs/developer/transport.md rename to docs/en/developer/transport.md diff --git a/docs/index.md b/docs/en/index.md similarity index 100% rename from docs/index.md rename to docs/en/index.md diff --git a/docs/translations.md b/docs/en/translations.md similarity index 100% rename from docs/translations.md rename to docs/en/translations.md diff --git a/docs/user.md b/docs/en/user.md similarity index 100% rename from docs/user.md rename to docs/en/user.md diff --git a/docs/user/connections.md b/docs/en/user/connections.md similarity index 100% rename from docs/user/connections.md rename to docs/en/user/connections.md diff --git a/docs/user/desktop.md b/docs/en/user/desktop.md similarity index 100% rename from docs/user/desktop.md rename to docs/en/user/desktop.md diff --git a/docs/user/discovery.md b/docs/en/user/discovery.md similarity index 100% rename from docs/user/discovery.md rename to docs/en/user/discovery.md diff --git a/docs/user/firmware.md b/docs/en/user/firmware.md similarity index 100% rename from docs/user/firmware.md rename to docs/en/user/firmware.md diff --git a/docs/user/map-and-waypoints.md b/docs/en/user/map-and-waypoints.md similarity index 100% rename from docs/user/map-and-waypoints.md rename to docs/en/user/map-and-waypoints.md diff --git a/docs/user/messages-and-channels.md b/docs/en/user/messages-and-channels.md similarity index 100% rename from docs/user/messages-and-channels.md rename to docs/en/user/messages-and-channels.md diff --git a/docs/user/mqtt.md b/docs/en/user/mqtt.md similarity index 100% rename from docs/user/mqtt.md rename to docs/en/user/mqtt.md diff --git a/docs/user/node-metrics.md b/docs/en/user/node-metrics.md similarity index 100% rename from docs/user/node-metrics.md rename to docs/en/user/node-metrics.md diff --git a/docs/user/nodes.md b/docs/en/user/nodes.md similarity index 100% rename from docs/user/nodes.md rename to docs/en/user/nodes.md diff --git a/docs/user/onboarding.md b/docs/en/user/onboarding.md similarity index 100% rename from docs/user/onboarding.md rename to docs/en/user/onboarding.md diff --git a/docs/user/settings-module-admin.md b/docs/en/user/settings-module-admin.md similarity index 100% rename from docs/user/settings-module-admin.md rename to docs/en/user/settings-module-admin.md diff --git a/docs/user/settings-radio-user.md b/docs/en/user/settings-radio-user.md similarity index 100% rename from docs/user/settings-radio-user.md rename to docs/en/user/settings-radio-user.md diff --git a/docs/user/signal-meter.md b/docs/en/user/signal-meter.md similarity index 100% rename from docs/user/signal-meter.md rename to docs/en/user/signal-meter.md diff --git a/docs/user/tak.md b/docs/en/user/tak.md similarity index 100% rename from docs/user/tak.md rename to docs/en/user/tak.md diff --git a/docs/user/telemetry-and-sensors.md b/docs/en/user/telemetry-and-sensors.md similarity index 100% rename from docs/user/telemetry-and-sensors.md rename to docs/en/user/telemetry-and-sensors.md diff --git a/docs/user/translate.md b/docs/en/user/translate.md similarity index 100% rename from docs/user/translate.md rename to docs/en/user/translate.md diff --git a/docs/user/units-and-locale.md b/docs/en/user/units-and-locale.md similarity index 100% rename from docs/user/units-and-locale.md rename to docs/en/user/units-and-locale.md diff --git a/feature/docs/build.gradle.kts b/feature/docs/build.gradle.kts index 1f1a72b72..a783f88ca 100644 --- a/feature/docs/build.gradle.kts +++ b/feature/docs/build.gradle.kts @@ -59,24 +59,16 @@ kotlin { */ val syncDocsToComposeResources by tasks.registering(Sync::class) { - description = "Syncs docs/ markdown source into composeResources for in-app bundling" + description = "Syncs docs/en/ markdown source into composeResources for in-app bundling" group = "docs" - val docsSourceDir = rootProject.layout.projectDirectory.dir("docs") + val docsEnDir = rootProject.layout.projectDirectory.dir("docs/en") val screenshotsDir = rootProject.layout.projectDirectory.dir("docs/screenshots") val composeResourcesTarget = layout.projectDirectory.dir("src/commonMain/composeResources/files/docs") - from(docsSourceDir) { + from(docsEnDir) { include("user/**/*.md") include("developer/**/*.md") - // Exclude Jekyll/site-only files that are not needed in-app - exclude("_config.yml") - exclude("_data/**") - exclude("_includes/**") - exclude("_layouts/**") - exclude("index.md") - exclude("assets/**") - exclude("Gemfile*") } // FR-038: Bundle screenshots into assets/screenshots/ to match markdown image paths. @@ -117,8 +109,7 @@ val syncTranslatedDocsToComposeResources by from(docsDir) { // Crowdin outputs dirs in Android qualifier format (fr, pt-rBR, zh-rCN) include("*/user/**/*.md") - exclude("user/**") - exclude("developer/**") + exclude("en/**") exclude("_*/**") exclude("assets/**") exclude("screenshots/**") @@ -133,6 +124,8 @@ val syncTranslatedDocsToComposeResources by if (segments.size >= 3) { val qualifier = segments[0] val rest = segments.drop(1).joinToString("/") + // Output: files/{locale}/docs/user/page.md + // English source lives at: docs/en/$rest path = "$qualifier/docs/$rest" } } diff --git a/feature/docs/src/commonMain/kotlin/org/meshtastic/feature/docs/data/DocBundleLoader.kt b/feature/docs/src/commonMain/kotlin/org/meshtastic/feature/docs/data/DocBundleLoader.kt index 20af93b8c..9ea02dbf4 100644 --- a/feature/docs/src/commonMain/kotlin/org/meshtastic/feature/docs/data/DocBundleLoader.kt +++ b/feature/docs/src/commonMain/kotlin/org/meshtastic/feature/docs/data/DocBundleLoader.kt @@ -241,7 +241,7 @@ class DefaultDocBundleLoader : DocBundleLoader { "onboarding", CoreRes.string.doc_title_onboarding, CoreRes.string.doc_keywords_onboarding, - "docs/user/onboarding.html", + "en/user/onboarding.html", 1, listOf("first-launch", "setup", "intro"), 3200, @@ -251,7 +251,7 @@ class DefaultDocBundleLoader : DocBundleLoader { "connections", CoreRes.string.doc_title_connections, CoreRes.string.doc_keywords_connections, - "docs/user/connections.html", + "en/user/connections.html", 2, listOf("bluetooth", "usb", "tcp", "pairing"), 4100, @@ -261,7 +261,7 @@ class DefaultDocBundleLoader : DocBundleLoader { "messages-and-channels", CoreRes.string.doc_title_messages, CoreRes.string.doc_keywords_messages, - "docs/user/messages-and-channels.html", + "en/user/messages-and-channels.html", 3, listOf("channels", "direct-messages", "messaging", "conversations"), 4500, @@ -271,7 +271,7 @@ class DefaultDocBundleLoader : DocBundleLoader { "nodes", CoreRes.string.doc_title_nodes, CoreRes.string.doc_keywords_nodes, - "docs/user/nodes.html", + "en/user/nodes.html", 4, listOf("node-list", "mesh-nodes", "peers"), 3800, @@ -281,7 +281,7 @@ class DefaultDocBundleLoader : DocBundleLoader { "node-metrics", CoreRes.string.doc_title_node_metrics, CoreRes.string.doc_keywords_node_metrics, - "docs/user/node-metrics.html", + "en/user/node-metrics.html", 5, listOf("metrics", "telemetry", "device-metrics", "signal"), 5200, @@ -291,7 +291,7 @@ class DefaultDocBundleLoader : DocBundleLoader { "map-and-waypoints", CoreRes.string.doc_title_map, CoreRes.string.doc_keywords_map, - "docs/user/map-and-waypoints.html", + "en/user/map-and-waypoints.html", 6, listOf("map", "waypoints", "gps", "location"), 3600, @@ -301,7 +301,7 @@ class DefaultDocBundleLoader : DocBundleLoader { "settings-radio-user", CoreRes.string.doc_title_settings_radio, CoreRes.string.doc_keywords_settings_radio, - "docs/user/settings-radio-user.html", + "en/user/settings-radio-user.html", 7, listOf("settings", "radio-config", "user-config", "lora"), 6800, @@ -311,7 +311,7 @@ class DefaultDocBundleLoader : DocBundleLoader { "settings-module-admin", CoreRes.string.doc_title_settings_module, CoreRes.string.doc_keywords_settings_module, - "docs/user/settings-module-admin.html", + "en/user/settings-module-admin.html", 8, listOf("modules", "module-config", "administration"), 5500, @@ -321,7 +321,7 @@ class DefaultDocBundleLoader : DocBundleLoader { "telemetry-and-sensors", CoreRes.string.doc_title_telemetry, CoreRes.string.doc_keywords_telemetry, - "docs/user/telemetry-and-sensors.html", + "en/user/telemetry-and-sensors.html", 9, listOf("sensors", "environment", "weather", "power-metrics"), 4800, @@ -331,7 +331,7 @@ class DefaultDocBundleLoader : DocBundleLoader { "tak", CoreRes.string.doc_title_tak, CoreRes.string.doc_keywords_tak, - "docs/user/tak.html", + "en/user/tak.html", 10, listOf("tak", "atak", "team-awareness-kit"), 2400, @@ -341,7 +341,7 @@ class DefaultDocBundleLoader : DocBundleLoader { "mqtt", CoreRes.string.doc_title_mqtt, CoreRes.string.doc_keywords_mqtt, - "docs/user/mqtt.html", + "en/user/mqtt.html", 11, listOf("mqtt", "internet-bridge", "broker"), 4200, @@ -351,7 +351,7 @@ class DefaultDocBundleLoader : DocBundleLoader { "discovery", CoreRes.string.doc_title_discovery, CoreRes.string.doc_keywords_discovery, - "docs/user/discovery.html", + "en/user/discovery.html", 12, listOf("mesh-discovery", "local-discovery", "network-scan"), 2800, @@ -361,7 +361,7 @@ class DefaultDocBundleLoader : DocBundleLoader { "firmware", CoreRes.string.doc_title_firmware, CoreRes.string.doc_keywords_firmware, - "docs/user/firmware.html", + "en/user/firmware.html", 13, listOf("firmware", "update", "ota", "flash"), 3400, @@ -371,7 +371,7 @@ class DefaultDocBundleLoader : DocBundleLoader { "desktop", CoreRes.string.doc_title_desktop, CoreRes.string.doc_keywords_desktop, - "docs/user/desktop.html", + "en/user/desktop.html", 14, listOf("desktop", "linux", "macos", "windows", "jvm"), 3900, @@ -381,7 +381,7 @@ class DefaultDocBundleLoader : DocBundleLoader { "signal-meter", CoreRes.string.doc_title_signal_meter, CoreRes.string.doc_keywords_signal_meter, - "docs/user/signal-meter.html", + "en/user/signal-meter.html", 15, listOf("signal-quality", "signal-strength", "rssi", "snr"), 3500, @@ -391,7 +391,7 @@ class DefaultDocBundleLoader : DocBundleLoader { "units-and-locale", CoreRes.string.doc_title_units, CoreRes.string.doc_keywords_units, - "docs/user/units-and-locale.html", + "en/user/units-and-locale.html", 16, listOf("measurement", "units", "locale", "metric", "imperial"), 3800, @@ -401,7 +401,7 @@ class DefaultDocBundleLoader : DocBundleLoader { "translate", CoreRes.string.doc_title_translate, CoreRes.string.doc_keywords_translate, - "docs/user/translate.html", + "en/user/translate.html", 17, listOf("crowdin", "localization", "language", "i18n", "contribute"), 3700, @@ -433,7 +433,7 @@ class DefaultDocBundleLoader : DocBundleLoader { "architecture", "Architecture", "developer", - "docs/developer/architecture.html", + "en/developer/architecture.html", 1, listOf("architecture", "kmp", "module", "layer", "core", "feature", "compose"), listOf("layers", "module-architecture", "kmp"), @@ -444,7 +444,7 @@ class DefaultDocBundleLoader : DocBundleLoader { "codebase", "Codebase", "developer", - "docs/developer/codebase.html", + "en/developer/codebase.html", 2, listOf("codebase", "repository", "layout", "gradle", "build", "namespace", "convention"), listOf("repository-layout", "project-structure", "source-code"), @@ -455,7 +455,7 @@ class DefaultDocBundleLoader : DocBundleLoader { "adding-a-feature-module", "Adding a Feature Module", "developer", - "docs/developer/adding-a-feature-module.html", + "en/developer/adding-a-feature-module.html", 3, listOf("module", "feature", "new", "create", "plugin", "di", "koin"), listOf("new-module", "feature-module", "module-guide"), @@ -466,7 +466,7 @@ class DefaultDocBundleLoader : DocBundleLoader { "navigation-and-deep-links", "Navigation & Deep Links", "developer", - "docs/developer/navigation-and-deep-links.html", + "en/developer/navigation-and-deep-links.html", 4, listOf("navigation", "deeplink", "route", "navkey", "backstack", "typed"), listOf("deeplinks", "navigation-3", "routes"), @@ -477,7 +477,7 @@ class DefaultDocBundleLoader : DocBundleLoader { "transport", "Transport", "developer", - "docs/developer/transport.html", + "en/developer/transport.html", 5, listOf("transport", "ble", "serial", "tcp", "radio", "connection"), listOf("ble", "serial", "tcp", "radio-transport"), @@ -488,7 +488,7 @@ class DefaultDocBundleLoader : DocBundleLoader { "persistence", "Persistence", "developer", - "docs/developer/persistence.html", + "en/developer/persistence.html", 6, listOf("room", "database", "datastore", "prefs", "storage", "migration"), listOf("room", "database", "datastore", "prefs"), @@ -499,7 +499,7 @@ class DefaultDocBundleLoader : DocBundleLoader { "testing", "Testing", "developer", - "docs/developer/testing.html", + "en/developer/testing.html", 7, listOf("test", "unit", "screenshot", "compose", "roborazzi", "ci"), listOf("tests", "unit-tests", "screenshot-tests"), @@ -510,7 +510,7 @@ class DefaultDocBundleLoader : DocBundleLoader { "contributing", "Contributing", "developer", - "docs/developer/contributing.html", + "en/developer/contributing.html", 8, listOf("contributing", "pull-request", "branch", "commit", "style", "pr"), listOf("contributing", "pull-request", "branch-naming"), @@ -521,7 +521,7 @@ class DefaultDocBundleLoader : DocBundleLoader { "measurement", "Measurement & Formatting", "developer", - "docs/developer/measurement.html", + "en/developer/measurement.html", 9, listOf("formatter", "metric", "number", "locale", "temperature", "conversion", "api"), listOf("metric-formatter", "number-formatter", "measurement"), diff --git a/scripts/check-doc-coverage.js b/scripts/check-doc-coverage.js index 7bebbaad7..3d73aac5e 100644 --- a/scripts/check-doc-coverage.js +++ b/scripts/check-doc-coverage.js @@ -12,7 +12,7 @@ const path = require("path"); const { forEachDocPage } = require("./lib/frontmatter"); const REPO_ROOT = path.resolve(process.argv[2] || "."); -const DOCS_DIR = path.join(REPO_ROOT, "docs"); +const DOCS_DIR = path.join(REPO_ROOT, "docs", "en"); // Map of feature module directory names to expected doc page slugs. // Modules not listed here are considered internal (no user-facing docs required). diff --git a/scripts/check-doc-freshness.js b/scripts/check-doc-freshness.js index a73c6261a..58099327b 100644 --- a/scripts/check-doc-freshness.js +++ b/scripts/check-doc-freshness.js @@ -13,7 +13,7 @@ const { parseFrontmatter, forEachDocPage } = require("./lib/frontmatter"); const args = process.argv.slice(2); const positional = args.filter(a => !a.startsWith("--")); -const DOCS_DIR = path.resolve(positional[0] || "docs"); +const DOCS_DIR = path.resolve(positional[0] || path.join("docs", "en")); const maxAgeArg = args.find(a => a.startsWith("--max-age-days=")); const MAX_AGE_DAYS = maxAgeArg ? parseInt(maxAgeArg.split("=")[1], 10) : 180; diff --git a/scripts/sync-android-docs.js b/scripts/sync-android-docs.js index b555f87db..8281a5db6 100755 --- a/scripts/sync-android-docs.js +++ b/scripts/sync-android-docs.js @@ -37,8 +37,8 @@ const IMAGE_EXTENSIONS = new Set([".png", ".jpg", ".jpeg", ".gif", ".svg", ".web const ANDROID_REPO_ROOT = positionalArgs.length > 0 ? path.resolve(positionalArgs[0]) : path.resolve(__dirname, ".."); -const SRC_DOCS_DIR = path.join(ANDROID_REPO_ROOT, "docs"); -const SRC_SCREENSHOTS_DIR = path.join(SRC_DOCS_DIR, "assets", "screenshots"); +const SRC_DOCS_DIR = path.join(ANDROID_REPO_ROOT, "docs", "en"); +const SRC_SCREENSHOTS_DIR = path.join(ANDROID_REPO_ROOT, "docs", "assets", "screenshots"); if (!fs.existsSync(SRC_DOCS_DIR)) { console.error(`Error: docs directory not found at ${SRC_DOCS_DIR}`); diff --git a/scripts/validate-doc-links.js b/scripts/validate-doc-links.js index dcd82c39f..1473e2301 100644 --- a/scripts/validate-doc-links.js +++ b/scripts/validate-doc-links.js @@ -11,7 +11,9 @@ const fs = require("fs"); const path = require("path"); const { discoverSlugs, forEachDocPage } = require("./lib/frontmatter"); -const DOCS_DIR = path.resolve(process.argv[2] || "docs"); +const DOCS_DIR = path.resolve(process.argv[2] || path.join("docs", "en")); +// Assets (screenshots) live at docs/ root, not inside the en/ locale folder +const DOCS_ROOT = path.resolve(DOCS_DIR, ".."); const IMAGE_EXTS = new Set([".png", ".jpg", ".jpeg", ".gif", ".svg", ".webp"]); // Collect known page slugs from both sections @@ -64,7 +66,7 @@ forEachDocPage(DOCS_DIR, (filePath, slug, section) => { if (/^https?:/.test(imgPath)) continue; const resolved = imgPath.startsWith("/") - ? path.join(DOCS_DIR, imgPath) + ? path.join(DOCS_ROOT, imgPath) : path.resolve(path.dirname(filePath), imgPath); if (!fs.existsSync(resolved)) { console.log(` ERROR: ${section}/${slug}.md:${lineNum} — missing image '${imgPath}'`);