docs: move English sources into docs/en/ locale folder (#5501)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
James Rich
2026-05-19 12:06:44 -07:00
committed by GitHub
parent 92cfbaee9b
commit 11bc37c968
42 changed files with 155 additions and 110 deletions

View File

@@ -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<<EOF" >> "$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"

View File

@@ -100,9 +100,10 @@ abstract class GenerateDocsBundleTask : DefaultTask() {
val indexEntries = mutableListOf<String>()
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("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
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 """
|<!DOCTYPE html>

View File

@@ -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

45
docs/README.md Normal file
View File

@@ -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

View File

@@ -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"

View File

@@ -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 %}
<div class="locale-page-banner" {% if locale_info.dir == "rtl" %}dir="rtl"{% endif %}>
<p class="locale-notice">

View File

View File

View File

View File

View File

View File

@@ -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"
}
}

View File

@@ -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"),

View File

@@ -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).

View File

@@ -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;

View File

@@ -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}`);

View File

@@ -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}'`);