Contributing¶
Why this manual exists¶
The previous manual (ethos-manual)
split into two disconnected halves per language. The English tree was only
ever a screenshot-generation rig — shell scripts driving the real Ethos
simulator through a Lua macro API to capture UI screenshots — with no
Markdown (or any other plain-text) source for the manual's actual prose; the
English text only ever existed as a stack of PDF/ODT exports. The French
tree, by contrast, was a fully written GitBook export with real content, but
built and maintained independently, with its own separate set of
hand-pasted screenshots. Other languages had neither. There was no single
source of truth to translate from, and no way to tell when a translated
page had drifted out of date with the (nonexistent) English source.
This repo starts over with one format for every page, in every language:
plain Markdown, built with MkDocs Material
(the same stack used for wingflight-docs),
deployed to GitHub Pages on every push to main.
Workflow¶
There's no CMS or web editor in front of the content — writers and translators work directly in git, the same as any other change to this repo:
- Branch off
main(in this repo directly — see the fork note below). - Edit the relevant
.mdfile(s) underdocs/en/. - Preview locally with
mkdocs serve(see the root README), or just open the pull request and use the automatic PR preview below. - Open a pull request.
Screenshots referenced from a page live next to it in docs/en/assets/ and
are just Markdown image links — no special syntax. See
Screenshot Pipeline for how they're generated.
PR previews¶
Every pull request against main gets its own live preview, built and
deployed automatically by .github/workflows/pr-preview.yml: at
manual.rt-rc.com/pr-preview/<PR number>/, linked in a bot comment on the
PR and updated on every push. It's removed automatically when the PR closes.
The main site itself (manual.rt-rc.com) is unaffected — previews live
alongside it in a pr-preview/ folder on the gh-pages branch that survives
every production deploy.
This only runs for branches pushed directly to this repo, not forks — a PR
from a fork won't get a live preview (GitHub withholds write access to
GITHUB_TOKEN for fork-triggered pull_request workflows, deliberately, so
a fork can't use CI to push arbitrary content to gh-pages). Fork
contributors can still preview locally with mkdocs serve.
Versioning¶
Multiple firmware versions' manuals (1.6.x alongside the current Ethos26
26.1) live in the same repo as separate branches, each deployed to its own
manual.rt-rc.com/<version>/ path with a version-select dropdown — see
Versioning for the full scheme and how to cut a new one.
Translation plan¶
Translators (human or AI) work directly in git, same as any other change — no CMS, no separate translation app. A first French pilot (a handful of pages) proved the mechanics out end to end; here's how it actually works.
Adding/updating a translation¶
- Branch, create/edit
docs/<locale>/<same path as the English page>, translating the prose. Keep code-literal text (key names likeENT,RTN, UI element names shown on screen) as-is. - Stamp the page with which English commit it was translated from:
---
translated_from: <commit sha of docs/en/... at translation time>
---
Find that sha with git log -1 --format=%H -- docs/en/<path>.
3. If the English page has a heading that other pages link to by
anchor (check by searching for #that-heading-slug across
docs/en/), don't let the translated heading's own auto-generated
slug change the target — pin the same, locale-stable ID explicitly
with attr_list (already enabled):
## Choisir une source {: #choosing-a-source }
Skipping this doesn't break the build, but it does silently break the anchor scroll-to for any other, still-untranslated page linking into that heading via fallback. 4. Open a PR — preview it like any other change, including the language switcher.
Screenshots¶
Nothing to duplicate up front. mkdocs-static-i18n
falls back to the English file for any asset a locale doesn't have its
own copy of — a translated page's ../assets/foo.png just works,
unmodified, showing the English screenshot, until a real localized one
is dropped in at the same filename under docs/<locale>/assets/, which
silently overrides the fallback from then on.
de and fr already have real localized screenshots — not captured
here, but bulk-imported from the old ethos-manual
repo, which turned out to have near-complete per-language screenshot sets
FrSky's own team had already captured (german/assets/ and, for French,
french_LT/assets/ — the more complete of its two French asset sets, not
the smaller french/assets/ its README describes as "half way"). Filenames
match our own docs/en/assets/ 1:1, so importing was a straight copy: 586
of our 589 currently-referenced screenshots landed for both languages in
one pass, no simulator involved. The handful that didn't match (2-3 files,
mostly newer pages the old repo's macros never covered) still fall back to
English as normal.
For any locale beyond de/fr, or for closing that last few percent,
capturing new screenshots means the screenshot pipeline
— porting/running the real macro rig against the simulator — since that
work wasn't already done upstream.
Staleness tracking¶
Translation Status is generated automatically
before every build (hooks/i18n_status.py, wired up via mkdocs.yml's
hooks: — runs locally, in PR previews, and in production alike, always
current, never committed to git) and compares every locale's
translated_from marker against each English page's actual last-changed
commit: current, stale (English moved on), or missing. That
page is the worklist — no GitHub Issues, no digging through Actions logs.
Automated translation (optional)¶
scripts/translate.py is a standalone local script (not part of the site
build or CI) that drives the same missing/stale worklist through the
Claude API to produce a first-draft translation for each page, stamped
with the correct translated_from: frontmatter automatically:
pip install anthropic pyyaml # if not already installed
export ANTHROPIC_API_KEY=... # or use `ant auth login`
python scripts/translate.py --dry-run # see what's queued, no API calls
python scripts/translate.py --only fr # translate everything missing/stale for French
python scripts/translate.py --pages model-setup/mixes.md # just one page
It reads every locale from mkdocs.yml's i18n plugin config by default
(--only restricts to specific ones), skips anything already current
unless --force is passed, and never commits or pushes — it only writes
files under docs/<locale>/, same as if you'd hand-edited them. Review the
diff, do the anchor-pinning check for any
newly-translated heading, then open a PR as usual.
The system prompt pre-seeds Claude with the manual's domain (FrSky Ethos
radio firmware, RC hobbyist audience) and a list of terms that must never
be translated (physical key names, protocol names, brand names), the same
technique used by the sister
rotorflight-lua-ethos-suite
repo's own bin/i18n/auto-translate.py. Each supported locale has its own
glossary baked into GLOSSARIES in the script for terminology
consistency from the first translated page.
Bootstrapping an entirely new locale — from picking the right locale code through translation, screenshots, anchor fixes, and nav labels — is a repeatable process end to end: see Adding a New Language for the full playbook.
Nav labels (nav_translations)¶
Tab and sidebar labels in nav: (e.g. "Model Setup") don't pick up a
locale's translated page title automatically unless the nav entry has no
explicit label at all (e.g. - how-to/index.md — MkDocs then uses that
page's own H1). Everywhere nav: gives an explicit Label: path.md
string, or names a section (Model Setup: as a dict key with children),
that label stays in English until the locale's nav_translations map in
mkdocs.yml covers it — added for a locale once its page coverage is
substantial enough that translating the chrome ahead of most of the
content wouldn't read oddly. fr's map was filled in once French reached
full page coverage; each leaf label was copied verbatim from that page's
own translated H1, so the sidebar text matches the page heading exactly.