diff --git a/docs/README.adoc b/docs/README.adoc index ef6db1bd0e4..99d8f9b61ea 100644 --- a/docs/README.adoc +++ b/docs/README.adoc @@ -19,6 +19,13 @@ the index.tmpl. `po4a.cfg` is used to map source files to translated documentation when generating translated files using the files in `docs/po/`. +Every translated HTML page is emitted regardless of completeness; the +topbar language-switcher post-process greys out per-page entries whose +`.po` coverage falls below `POKEEP` percent (default 80). Override at +build time: `make POKEEP=30 docs` to surface in-progress translations, +`make POKEEP=0 docs` to keep every entry clickable. See +`docs/src/code/contributing-to-linuxcnc.adoc` for details. + == Notes on LinuxCNC documentation The main LinuxCNC Makefile can optionally build the documentation and diff --git a/docs/po4a.cfg b/docs/po4a.cfg index 9a97dd1abe2..c1b72869e85 100644 --- a/docs/po4a.cfg +++ b/docs/po4a.cfg @@ -26,6 +26,7 @@ [type: AsciiDoc_def] src/Master_Getting_Started.adoc $lang:build/adoc/$lang/Master_Getting_Started.adoc [type: AsciiDoc_def] src/Master_Integrator.adoc $lang:build/adoc/$lang/Master_Integrator.adoc [type: xhtml_def] src/index.tmpl $lang:build/adoc/$lang/index.tmpl +[type: Text_def] src/topbar-labels $lang:build/adoc/$lang/topbar-labels [type: AsciiDoc_def] src/help/rtfaults.adoc $lang:build/adoc/$lang/help/rtfaults.adoc [type: AsciiDoc_def] src/help/tklinuxcnc.adoc $lang:build/adoc/$lang/help/tklinuxcnc.adoc [type: AsciiDoc_def] src/code/adding-configs.adoc $lang:build/adoc/$lang/code/adding-configs.adoc @@ -79,7 +80,7 @@ [type: AsciiDoc_def] src/examples/spindle.adoc $lang:build/adoc/$lang/examples/spindle.adoc [type: AsciiDoc_def] src/gcode/coordinates.adoc $lang:build/adoc/$lang/gcode/coordinates.adoc [type: AsciiDoc_def] src/gcode/g-code.adoc $lang:build/adoc/$lang/gcode/g-code.adoc -[type: xhtml_def] src/gcode.html $lang:build/html/$lang/gcode.html +[type: xhtml_def] src/gcode.html.in $lang:build/adoc/$lang/gcode-raw.html [type: AsciiDoc_def] src/gcode/machining-center.adoc $lang:build/adoc/$lang/gcode/machining-center.adoc [type: AsciiDoc_def] src/gcode/m-code.adoc $lang:build/adoc/$lang/gcode/m-code.adoc [type: AsciiDoc_def] src/gcode/o-code.adoc $lang:build/adoc/$lang/gcode/o-code.adoc diff --git a/docs/src/.gitignore b/docs/src/.gitignore new file mode 100644 index 00000000000..89cc0d93c3c --- /dev/null +++ b/docs/src/.gitignore @@ -0,0 +1 @@ +docinfo-header.html diff --git a/docs/src/Submakefile b/docs/src/Submakefile index 7611b346612..025166c2d66 100644 --- a/docs/src/Submakefile +++ b/docs/src/Submakefile @@ -59,6 +59,23 @@ ASCIIDOCTOR_DEFAULT_CSS := $(shell ruby -e 'require "asciidoctor"; print Asciido LANGUAGES := $(strip $(shell sed -e's/#.*//' < $(DOC_DIR)/po4a.cfg | grep '^\[po4a_langs\]' | cut -d" " -f2-)) LANGUAGES_MATCH := $(shell echo $(LANGUAGES) | tr " " "|") +# Native switcher labels read from docs/src/lang-labels (tagname); +# a tag with no entry falls back to its code with a warning. +LANG_LABEL_FILE := $(DOC_SRCDIR)/lang-labels +define LANG_LABEL_template +LANG_LABEL_$(1) := $(or $(shell sed -ne 's/^$(1)[[:space:]][[:space:]]*//p' $(LANG_LABEL_FILE)),$(1)$(warning lang-labels: no entry for '$(1)', using tag)) +endef +$(foreach L,en $(LANGUAGES),$(eval $(call LANG_LABEL_template,$(L)))) + +# Minimum per-master translation completeness (percent) below which the +# language-switcher post-process pass greys out a page's entry for that +# language. The translated HTML is still emitted (po4a runs with --keep 0) +# so deep links never 404; the post-process just demotes to . +# Default 80% matches po4a's own default and the MDN/Hugo/Sphinx +# convention. Translators previewing work-in-progress can lower it: +# `make POKEEP=30 docs`. +POKEEP ?= 80 + GENERATED_MANPAGES += ../docs/man/man1/linuxcnc.1 GENERATED_MANPAGES += $(patsubst ../docs/src/man/%.adoc, ../docs/man/%, $(wildcard ../docs/src/man/*/*.adoc)) @@ -442,10 +459,51 @@ endif # English gcode reference must land in the build tree even when only PDF docs # are enabled (debian/configure passes --enable-build-documentation=pdf, so # .copy-asciidoc-stamp -- which lives under htmldocs -- does not fire). +# Per-lang topbar fragments. Pre-substituted versions of docinfo-header.html +# with lcnc-cssrel and lcnc-lang-label resolved; consumed by the gcode.html +# and index.html rules below, which still need to substitute {lcnc-subpath}. +objects/topbar-en.html: $(DOC_SRCDIR)/docinfo-header.html + @mkdir -p $(@D) + @sed -e 's|{lcnc-cssrel}|../|g' -e 's|{lcnc-lang-label}|$(LANG_LABEL_en)|g' $< > $@ + +$(foreach L,$(LANGUAGES),objects/topbar-$(L).html): objects/topbar-%.html: $(DOC_SRCDIR)/docinfo-header.html + @mkdir -p $(@D) + @sed -e 's|{lcnc-cssrel}|../|g' -e 's|{lcnc-lang-label}|$(LANG_LABEL_$*)|g' $< > $@ + +# debian/linuxcnc-doc-en.docs installs docs/build/html/en/gcode.html, so the +# English gcode reference must land in the build tree even when only PDF docs +# are enabled (debian/configure passes --enable-build-documentation=pdf, so +# .copy-asciidoc-stamp -- which lives under htmldocs -- does not fire). +# Wraps the .in template with the English topbar fragment and rewrites the +# CSS path (the source sits at docs/build/html/en/, css at docs/build/html/). docs: $(DOC_OUT_HTML)/en/gcode.html -$(DOC_OUT_HTML)/en/gcode.html: $(DOC_SRCDIR)/gcode.html +$(DOC_OUT_HTML)/en/gcode.html: $(DOC_SRCDIR)/gcode.html.in objects/topbar-en.html $(DOC_SRCDIR)/Submakefile @mkdir -p $(@D) - cp -f $< $@ + $(Q){ \ + topbar=$$(mktemp) ; \ + sed 's|{lcnc-subpath}|gcode.html|g' objects/topbar-en.html > $$topbar ; \ + awk -v t="$$topbar" 'BEGIN{ while ((getline line < t) > 0) buf = buf line "\n" } //{ print; printf "%s", buf; next } { print }' $< \ + | sed 's|href="lcnc-overrides.css"|href="../lcnc-overrides.css"|g' > $@ ; \ + rm -f $$topbar ; \ + } + +# Per-language gcode.html: po4a translates docs/src/gcode.html.in into +# $(DOC_OUT_ADOC)//gcode-raw.html. Wrap each raw output with the +# lang-specific topbar to produce the final $(DOC_OUT_HTML)//gcode.html. +$(foreach L,$(LANGUAGES),$(DOC_OUT_HTML)/$(L)/gcode.html): $(DOC_OUT_HTML)/%/gcode.html: objects/topbar-%.html $(DOC_SRCDIR)/Submakefile | translateddocs + @mkdir -p $(dir $@) + $(Q){ \ + topbar=$$(mktemp) ; \ + sed 's|{lcnc-subpath}|gcode.html|g' objects/topbar-$*.html > $$topbar ; \ + awk -v t="$$topbar" 'BEGIN{ while ((getline line < t) > 0) buf = buf line "\n" } //{ print; printf "%s", buf; next } { print }' $(DOC_OUT_ADOC)/$*/gcode-raw.html \ + | sed 's|href="lcnc-overrides.css"|href="../lcnc-overrides.css"|g' > $@ ; \ + rm -f $$topbar ; \ + } + +# Hook per-lang gcode.html into the docs target when translations are on. +ifeq ($(BUILD_DOCS_TRANSLATED),yes) +docs: $(foreach L,$(LANGUAGES),$(DOC_OUT_HTML)/$(L)/gcode.html) +endif pdfdocs: svgs_made_from_dots $(PDF_TARGETS) $(DOC_OUT_HTML)/pdf/index.html @@ -475,9 +533,24 @@ endif # gen_complist aliases: a phony prereq is always "newer", so naming them # re-touched .htmldoc-stamp every run, dragging checkref and the css copy # with it. The stamps fire only when their real inputs change. -.htmldoc-stamp: .copy-asciidoc-stamp $(DOC_DIR)/.gen_complist-stamp $(HTML_TARGETS) .images-stamp .include-stamp $(DOC_OUT_HTML)/asciidoctor.css $(DOC_OUT_HTML)/rouge-github.css +.htmldoc-stamp: .copy-asciidoc-stamp $(DOC_DIR)/.gen_complist-stamp $(HTML_TARGETS) .images-stamp .include-stamp $(DOC_OUT_HTML)/asciidoctor.css $(DOC_OUT_HTML)/rouge-github.css .lang-switcher-stamp touch $@ +# Walk every generated HTML page and grey out language-switcher entries +# whose translated counterpart is below POKEEP coverage. Runs at the end +# of the build (depends on every HTML target so it sees the final tree). +# The script is idempotent so re-running over a processed tree is a +# content-stable no-op. Gated on BUILD_DOCS_TRANSLATED: with English-only +# builds there is nothing to grey out. +ifeq ($(BUILD_DOCS_TRANSLATED),yes) +.lang-switcher-stamp: $(DOC_SRCDIR)/lang_switcher_postprocess.py $(HTML_TARGETS) $(TRANSLATED_MAN_HTML_TARGETS) $(wildcard $(DOC_DIR)/po/*.po) + $(Q)python3 $(DOC_SRCDIR)/lang_switcher_postprocess.py $(DOC_OUT_HTML) $(DOC_DIR)/po $(POKEEP) $(LANGUAGES) + @touch $@ +else +.lang-switcher-stamp: + @touch $@ +endif + # Shared assets at the top of the html tree. Index templates and the # asciidoctor docinfo reference them via ../ relative paths so we ship # one copy regardless of how many languages render. gcode.html is NOT @@ -485,14 +558,40 @@ endif # to its sibling gcode.html. SHARED_HTML_ASSETS = \ $(DOC_SRCDIR)/lcnc-overrides.css \ + $(DOC_SRCDIR)/lcnc-docs.svg \ $(DOC_SRCDIR)/index.css \ $(DOC_SRCDIR)/linuxcnc-logo-chips.png +# docinfo-header.html: generated from .in template. The @LANGUAGE_SWITCHER@ +# placeholder expands to one
  • per language (English plus everything +# in $(LANGUAGES)), labelled via $(LANG_LABEL_). asciidoctor then +# substitutes the {lcnc-cssrel} / {lcnc-lang-label} / {lcnc-subpath} +# attributes per page. po4a.cfg drives the language list, lang-labels +# the display names. +$(DOC_SRCDIR)/docinfo-header.html: $(DOC_SRCDIR)/docinfo-header.html.in $(DOC_DIR)/po4a.cfg $(LANG_LABEL_FILE) $(DOC_SRCDIR)/Submakefile + @block=$$(mktemp); \ + printf '
    \n' > $$block ; \ + printf ' \n' >> $$block ; \ + printf ' \n' >> $$block ; \ + printf ' \n' >> $$block ; \ + printf '
    \n' >> $$block ; \ + awk -v block="$$block" ' \ + /@LANGUAGE_SWITCHER@/ { while ((getline line < block) > 0) print line; next } \ + { print } \ + ' $< > $@ ; \ + rm -f $$block + # Stamp-gated asset copy; copy_asciidoc_files stays as a phony alias. # The en/gcode.html copy lives in its own rule above so it fires for PDF-only -# builds as well; .copy-asciidoc-stamp depends on it for HTML builds. +# builds as well; .copy-asciidoc-stamp needs it built for HTML builds but only +# copies the shared assets, so gcode.html is an order-only prerequisite: the +# post-processor later rewrites gcode.html in place, and a normal prerequisite +# would then leave this stamp stale and re-fire the copy every make. copy_asciidoc_files: .copy-asciidoc-stamp -.copy-asciidoc-stamp: $(SHARED_HTML_ASSETS) $(DOC_OUT_HTML)/en/gcode.html +.copy-asciidoc-stamp: $(SHARED_HTML_ASSETS) | $(DOC_OUT_HTML)/en/gcode.html @mkdir -p $(DOC_OUT_HTML) cp -f $(SHARED_HTML_ASSETS) $(DOC_OUT_HTML) @touch $@ @@ -521,7 +620,11 @@ $(DOC_OUT_HTML)/rouge-github.css: $(DOC_SRCDIR)/render-rouge-css.rb # with HTML-existence-dependent content (different miss_in_man set), # bumping mtime past .pot and re-triggering po4a on the next build. # Broken-link validation against generated HTML is checkref's job. -$(DOC_DIR)/.gen_complist-stamp: $(DOC_SRCDIR)/hal/components_gen.adoc $(MAN_HTML_TARGETS) +# MAN_HTML_TARGETS is order-only: this stamp only needs the manpages built, +# it does not read their content, and the post-processor rewrites them in +# place later -- a normal prerequisite would then re-fire this stamp (and the +# .htmldoc-stamp / checkref that depend on it) on every subsequent make. +$(DOC_DIR)/.gen_complist-stamp: $(DOC_SRCDIR)/hal/components_gen.adoc | $(MAN_HTML_TARGETS) mkdir -p $(DOC_OUT_HTML)/en/hal @touch $@ @@ -607,7 +710,7 @@ $(foreach L,$(LANGUAGES), \ # from docs/man//manN. cssrel is ../../../ for both: each output # sits at docs/build/html//man/manN/X.html (4 levels under html/). define MAN_HTML_RULE -$(DOC_OUT_HTML)/$(1)/man/%.html: $(2)/% $(DOC_SRCDIR)/docinfo.html +$(DOC_OUT_HTML)/$(1)/man/%.html: $(2)/% $(DOC_SRCDIR)/docinfo.html $(DOC_SRCDIR)/docinfo-header.html @$$(ECHO) Formatting $$(notdir $$<) as HTML @mkdir -p $$(dir $$@) $$(Q)if grep -q '^\.so' $$<; then \ @@ -636,6 +739,8 @@ $(DOC_OUT_HTML)/$(1)/man/%.html: $(2)/% $(DOC_SRCDIR)/docinfo.html -a mansource=LinuxCNC \ -a manmanual='LinuxCNC Documentation' \ -a "lcnc-cssrel=../../../" \ + -a "lcnc-lang-label=$(LANG_LABEL_$(1))" \ + -a "lcnc-subpath=$$(patsubst $(DOC_OUT_HTML)/$(1)/%,%,$$@)" \ -a docinfo=shared \ -a docinfodir=$$(realpath $$(DOC_SRCDIR)) \ -a source-highlighter=rouge \ @@ -696,19 +801,34 @@ objects/index.incl: $(GENERATED_MANPAGES) objects/var-MAN_HTML_TARGETS $(DOC_SRC mkdir -p $(DOC_OUT_HTML)/en/man/man/images/ find $(DOC_DIR)/man -maxdepth 3 -name "*.png" ! -name "grohtml*" -exec mv {} "$(DOC_OUT_HTML)/en/man/man/images/" \; -$(DOC_OUT_HTML)/%/index.html: $(DOC_OUT_ADOC)/%/index.tmpl ../VERSION $(DOC_SRCDIR)/index.foot +$(DOC_OUT_HTML)/%/index.html: $(DOC_OUT_ADOC)/%/index.tmpl objects/index.incl ../VERSION $(DOC_SRCDIR)/index.foot $(DOC_SRCDIR)/docinfo-header.html $(DOC_SRCDIR)/Submakefile @mkdir -p $(dir $@) - cat $(filter-out ../VERSION, $^) | \ - sed "s/@VERSION@/`cat ../VERSION`/" | \ - if [ "yes" != "$(BUILD_DOCS_TRANSLATED)" ]; then sed '/@TRANSLATIONS@/,/@ENDTRANSLATIONS@/d' ; else grep -Ev '@(END)?TRANSLATIONS@'; fi > $@ + $(Q){ \ + topbar=$$(mktemp) ; \ + sed -e 's|{lcnc-cssrel}|../|g' -e 's|{lcnc-lang-label}|$(LANG_LABEL_$*)|g' -e 's|{lcnc-subpath}|index.html|g' $(DOC_SRCDIR)/docinfo-header.html > $$topbar ; \ + (cat $(DOC_OUT_ADOC)/$*/index.tmpl objects/index.incl $(DOC_SRCDIR)/index.foot) \ + | sed "s/@VERSION@/`cat ../VERSION`/" \ + | awk -v t="$$topbar" 'BEGIN{ while ((getline line < t) > 0) buf = buf line "\n" } //{ print; printf "%s", buf; next } { print }' \ + | sed 's|href="lcnc-overrides.css"|href="../lcnc-overrides.css"|g' \ + | if [ "yes" != "$(BUILD_DOCS_TRANSLATED)" ]; then sed '/@TRANSLATIONS@/,/@ENDTRANSLATIONS@/d' ; else grep -Ev '@(END)?TRANSLATIONS@'; fi > $@ ; \ + rm -f $$topbar ; \ + } # Rich English landing page lives at /en/index.html (sibling to the # translated //index.html files), generated from index.tmpl # plus the manpage index include. -$(DOC_OUT_HTML)/en/index.html: $(DOC_SRCDIR)/index.tmpl objects/index.incl $(DOC_SRCDIR)/index.foot ../VERSION $(DOC_SRCDIR)/Submakefile +$(DOC_OUT_HTML)/en/index.html: $(DOC_SRCDIR)/index.tmpl objects/index.incl $(DOC_SRCDIR)/index.foot ../VERSION $(DOC_SRCDIR)/docinfo-header.html $(DOC_SRCDIR)/Submakefile @mkdir -p $(dir $@) - (cat $(DOC_SRCDIR)/index.tmpl objects/index.incl $(DOC_SRCDIR)/index.foot) | sed "s/@VERSION@/`cat ../VERSION`/" | \ - if [ "yes" != "$(BUILD_DOCS_TRANSLATED)" ]; then sed '/@TRANSLATIONS@/,/@ENDTRANSLATIONS@/d' ; else grep -Ev '@(END)?TRANSLATIONS@'; fi > $@ + $(Q){ \ + topbar=$$(mktemp) ; \ + sed 's|{lcnc-subpath}|index.html|g' objects/topbar-en.html > $$topbar ; \ + (cat $(DOC_SRCDIR)/index.tmpl objects/index.incl $(DOC_SRCDIR)/index.foot) \ + | sed "s/@VERSION@/`cat ../VERSION`/" \ + | awk -v t="$$topbar" 'BEGIN{ while ((getline line < t) > 0) buf = buf line "\n" } //{ print; printf "%s", buf; next } { print }' \ + | sed 's|href="lcnc-overrides.css"|href="../lcnc-overrides.css"|g' \ + | if [ "yes" != "$(BUILD_DOCS_TRANSLATED)" ]; then sed '/@TRANSLATIONS@/,/@ENDTRANSLATIONS@/d' ; else grep -Ev '@(END)?TRANSLATIONS@'; fi > $@ ; \ + rm -f $$topbar ; \ + } # Tree-root redirect: docs/build/html/index.html bounces to en/. The # page is a checked-in static file under docs/src/; just copy it. @@ -944,13 +1064,16 @@ $(foreach L,$(LANGUAGES),$(eval $(call HTML_COPY_RULE,$(L)))) # $(DOC_OUT_HTML)///X.html. The source images are in # $(DOC_SRCDIR)// regardless of language (translations are # image-symlinked to English originals via the sed below). -.html-images-stamp: $(DOC_TARGETS_HTML) +# Depend on .lang-switcher-stamp so this runs after the post-processor has +# rewritten the HTML in place; otherwise that later rewrite leaves this stamp +# older than its inputs and the image copy re-fires on every subsequent make. +.html-images-stamp: $(DOC_TARGETS_HTML) .lang-switcher-stamp set -e; for HTML_FILE in $^; do \ HTML_REL=$$(echo $$HTML_FILE | sed 's%^$(DOC_OUT_HTML)/%%'); \ LANG=$$(echo $$HTML_REL | cut -d/ -f1); \ REST=$$(echo $$HTML_REL | cut -d/ -f2-); \ HTML_DIR=$$(dirname $$REST | cut -d/ -f1); \ - for IMAGE_FILE in $$(grep -oE 'src="[^"]+"' $$HTML_FILE | sed 's/src="//;s/"$$//' | grep -vE '^https?:|^data:|^/'); do \ + for IMAGE_FILE in $$(grep -oE 'src="[^"]+"' $$HTML_FILE | sed 's/src="//;s/"$$//' | grep -vE '^https?:|^data:|^/|lcnc-docs\.svg'); do \ IMAGE_DIR=$$(dirname $$IMAGE_FILE); \ IMAGE_PATH=$(DOC_SRCDIR)/$$HTML_DIR/$$IMAGE_FILE; \ mkdir -p $(DOC_OUT_HTML)/$$LANG/$$HTML_DIR/$$IMAGE_DIR; \ @@ -1024,7 +1147,7 @@ $(DOC_OUT_ADOC)/%.html: LCNC_CSSREL=$(shell python3 -c "print('../' * '$*'.count # $4 xref-exclude pattern (English filters all lang subdirs; translated # trees are rooted inside their own lang dir so the exclude is empty). define ASCIIDOCTOR_HTML_RULE -$$(patsubst %.adoc,$2/%.html,$$(DOC_SRCS_$(call toUC,$1)_SMALL)): $2/%.html: $2/%.adoc $$(DOC_SRCDIR)/docinfo.html +$$(patsubst %.adoc,$2/%.html,$$(DOC_SRCS_$(call toUC,$1)_SMALL)): $2/%.html: $2/%.adoc $$(DOC_SRCDIR)/docinfo.html $$(DOC_SRCDIR)/docinfo-header.html $$(ECHO) "Building '$1' adoc to html: " $$< $$(Q)asciidoctor -r $$(realpath $$(DOC_SRCDIR))/extensions/xref_resolver.rb \ -r $$(realpath $$(DOC_SRCDIR))/extensions/rouge_hal.rb \ @@ -1035,6 +1158,8 @@ $$(patsubst %.adoc,$2/%.html,$$(DOC_SRCS_$(call toUC,$1)_SMALL)): $2/%.html: $2/ -a "xref-exclude=$4" \ -a "relindir=$$(shell dirname $$*)" \ -a "lcnc-cssrel=$$(LCNC_CSSREL)" \ + -a "lcnc-lang-label=$(LANG_LABEL_$1)" \ + -a "lcnc-subpath=$$(patsubst $1/%,%,$$*).html" \ -a docinfo=shared \ -a docinfodir=$$(realpath $$(DOC_SRCDIR)) \ -a source-highlighter=rouge \ @@ -1042,7 +1167,7 @@ $$(patsubst %.adoc,$2/%.html,$$(DOC_SRCS_$(call toUC,$1)_SMALL)): $2/%.html: $2/ -a webfonts! \ -a linkcss -a copycss! \ -a "stylesdir=$$(LCNC_CSSREL)" \ - -d book -a toc -a numbered \ + -d book -a toc=left -a numbered \ -o $$@ $$< || (X=$$$$?; rm -f $$@; exit $$$$X) endef diff --git a/docs/src/code/building-linuxcnc.adoc b/docs/src/code/building-linuxcnc.adoc index c5dbd87c433..831f6276430 100644 --- a/docs/src/code/building-linuxcnc.adoc +++ b/docs/src/code/building-linuxcnc.adoc @@ -194,6 +194,14 @@ The most commonly used arguments are: Disable building the translated documentation for all available languages. The building of the translated documentation takes a huge amount of time, so it is recommend to skip that if not really needed. +Translation completeness threshold:: + `make POKEEP=N docs`. Per-master threshold (percent) below which a + language-switcher entry is greyed out on the corresponding page. + Translated HTML is always emitted; this knob only affects whether + the entry stays clickable. Default 80. Translators can lower the + threshold (`POKEEP=30`, `POKEEP=0`) to make their work-in-progress + show up as live links. See the contributing guide for details. + [[make-arguments]] ==== `make` arguments diff --git a/docs/src/code/contributing-to-linuxcnc.adoc b/docs/src/code/contributing-to-linuxcnc.adoc index 85e32473be4..edaaa75b7e3 100644 --- a/docs/src/code/contributing-to-linuxcnc.adoc +++ b/docs/src/code/contributing-to-linuxcnc.adoc @@ -260,6 +260,31 @@ https://hosted.weblate.org/projects/linuxcnc/ Documentation on how to use Weblate is here: https://docs.weblate.org/en/latest/user/basic.html +=== Previewing a translation locally + +Every translated HTML page is emitted regardless of how much of its +source has actually been translated. The topbar language switcher +greys out entries whose underlying `.po` coverage for that specific +master falls below *80%* by default. Greyed entries are still in the +DOM (so deep links keep working) but rendered as plain text rather +than clickable links, signalling to readers that the page is mostly +English. + +If you are actively translating and want your work-in-progress to +appear as a live link on its companion pages, lower the threshold at +build time: + +[source,sh] +---- +cd src +make POKEEP=30 docs # any page >= 30% translated stays clickable +make POKEEP=0 docs # everything clickable, even untouched pages +---- + +`POKEEP` is consumed by the post-process pass that walks every HTML +file once at the end of the build. The default value, used by the +published build, is 80. + == Other ways to contribute There are many ways to contribute to LinuxCNC, that are not addressed diff --git a/docs/src/docinfo-header.html.in b/docs/src/docinfo-header.html.in new file mode 100644 index 00000000000..690b4421960 --- /dev/null +++ b/docs/src/docinfo-header.html.in @@ -0,0 +1,17 @@ +
    + + LinuxCNC Documentation + + +@LANGUAGE_SWITCHER@ +
    diff --git a/docs/src/gcode.html b/docs/src/gcode.html.in similarity index 94% rename from docs/src/gcode.html rename to docs/src/gcode.html.in index 2ae21a5d18b..6e4c641129f 100644 --- a/docs/src/gcode.html +++ b/docs/src/gcode.html.in @@ -197,28 +197,6 @@ } } -function fixup_urls() { -var links=document.evaluate('//a[@href]', document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); - for(var i=0; i diff --git a/docs/src/index.tmpl b/docs/src/index.tmpl index c8fa4a0e0f7..522275ac71d 100644 --- a/docs/src/index.tmpl +++ b/docs/src/index.tmpl @@ -15,33 +15,10 @@ LinuxCNC Logo -@TRANSLATIONS@ -

    Translations: -Arabic -Deutsch -Español -Français -Norsk bokmål -Russian -Svensk - -Ukranian -中文 -

    -@ENDTRANSLATIONS@ -

    LinuxCNC version @VERSION@ Documentation

    - -

    Getting Started & Configuration

    diff --git a/docs/src/lang-labels b/docs/src/lang-labels new file mode 100644 index 00000000000..c3e3e264f0d --- /dev/null +++ b/docs/src/lang-labels @@ -0,0 +1,11 @@ +# Native display labels for the docs language switcher. +# Format: . A language listed in +# po4a.cfg [po4a_langs] but absent here falls back to its bare tag. +en English +de Deutsch +es Español +fr Français +nb Norsk +ru Русский +uk Українська +zh_CN 中文 diff --git a/docs/src/lang_switcher_postprocess.py b/docs/src/lang_switcher_postprocess.py new file mode 100644 index 00000000000..071e381875d --- /dev/null +++ b/docs/src/lang_switcher_postprocess.py @@ -0,0 +1,538 @@ +#!/usr/bin/env python3 +"""Post-process every generated HTML page for translation status. + +Two passes, both reversible so a second run is a no-op (safe to bake into +the build via a stamp target): + + 1. Language switcher: grey a
  • only when its target file is + genuinely absent (so the switcher never offers a 404). Sparse but + present translations stay clickable -- completeness is conveyed by + the banner, not by hiding the link. + 2. Per-page banner: on a translated page that is not fully translated, + inject a no-JS notice just below the topbar stating "this page is + N% translated", tinted red(0%)..yellow..green(100%). N comes from + the language's .po: msgids whose location comment points at the + page's master source, counted translated / total. + +Pages without a discoverable master (English, generated index pages) get +no banner. Dead links in the manpage index lists are still greyed by +file existence (separate concern, see rewrite_details_block).""" + +import os +import re +import sys +from collections import defaultdict + +LIST_RE = re.compile( + r'(
      )(.*?)(
    )', + re.DOTALL, +) +# Generic link entry inside
  • ' +) +# Match every
  • in the list, capturing its class attr (if any), the +# href, and the visible label. The class attr is rewritten in-place each +# run so re-running with a different threshold is reversible. +ENTRY_RE = re.compile( + r'(\s*)' + r'([^<]+)
  • ' +) +# Topbar header element (HTML5
    , distinct from asciidoctor's +#
  • ' + return ENTRY_RE.sub(repl, block_body) + + +def rewrite_details_block(html_dir, block_body): + """Toggle the lcnc-link-unavail class on every
  • entry in a + details-list block based on whether the link target exists on disk. + Used so translated index pages do not surface dead manpage links.""" + def repl(m): + before, after, href, label = m.group(1), m.group(2), m.group(3), m.group(4) + target = os.path.normpath(os.path.join(html_dir, href)) + if os.path.exists(target): + cls = '' + else: + cls = ' class="lcnc-link-unavail"' + return f'{before}{cls}{after}{label}
  • ' + return DETAILS_ENTRY_RE.sub(repl, block_body) + + +# Whole-document navigation tree mirroring the Master_*.adoc structure; each +# page gets the tree with its branch expanded and its entry active. +SRC_DIR = os.path.dirname(os.path.abspath(__file__)) +SITENAV_ROOTS = [] # built once in main() +_TITLE_CACHE = {} + + +def _html_escape(s): + return (s.replace('&', '&').replace('<', '<') + .replace('>', '>').replace('"', '"')) + + +def _clean_title(s): + # Drop the " V{lversion}" / attribute tail and any leftover {attr}. + s = re.sub(r'\s*V?\{[^}]*\}.*$', '', s) + return s.strip() + + +def _humanize(path): + return os.path.basename(path).replace('-', ' ').replace('_', ' ').title() + + +def _page_title(path): + """First '= Heading' of the page's English source adoc, else humanized.""" + if path in _TITLE_CACHE: + return _TITLE_CACHE[path] + title = None + src = os.path.join(SRC_DIR, path + '.adoc') + try: + with open(src, 'r', encoding='utf-8') as f: + for line in f: + if line.startswith('= '): + title = _clean_title(line[2:]) + break + except OSError: + pass + title = title or _humanize(path) + _TITLE_CACHE[path] = title + return title + + +# Books that seed the tree. promote=True splices the book's parts in at top +# level instead of nesting them under a redundant book node. +SITENAV_MASTERS = (('Master_Documentation.adoc', True), + ('Master_Integrator.adoc', False), + ('Master_Developer.adoc', False)) +_INC_RE = re.compile(r'^include::([^\[]+)\.adoc\[') +_LVL_RE = re.compile(r'^:leveloffset:\s*([+-]?\d+)') + + +def _parse_master(master): + """One book node {title, path:None, children:[parts/groups/pages]}.""" + try: + f = open(os.path.join(SRC_DIR, master), 'r', encoding='utf-8') + except OSError: + return None + book = {'title': None, 'path': None, 'children': []} + stack = [(-1, book)] # book holds everything below level 0 + level = 0 + with f: + for line in f: + line = line.rstrip('\n') + m = _LVL_RE.match(line) + if m: + v = m.group(1) + level = level + int(v) if v[0] in '+-' else int(v) + continue + if line.startswith('= '): + title = _clean_title(line[2:]) + if book['title'] is None: + book['title'] = title + continue + node = {'title': title, 'path': None, 'children': []} + while stack[-1][0] >= level: + stack.pop() + stack[-1][1]['children'].append(node) + stack.append((level, node)) + continue + mi = _INC_RE.match(line) + if mi: + path = mi.group(1).strip() + stack[-1][1]['children'].append( + {'title': _page_title(path), 'path': path, 'children': []}) + return book + + +def build_sitenav(masters=SITENAV_MASTERS): + """Nested nav model. Node dicts: group/book = {title, path:None, + children:[...]}, page = {title, path, children:[]}.""" + roots = [] + for master, promote in masters: + book = _parse_master(master) + if not book or not book['children']: + continue + if promote: + roots.extend(book['children']) + else: + roots.append(book) + return roots + + +def _has_active(node, active): + if node['path'] == active: + return True + return any(_has_active(c, active) for c in node['children']) + + +def _active_leaf(title, href, page_toc): + """The active page entry. With its own sections it becomes the + disclosure itself: the name is the summary (toggles the section list + rather than linking back to the page you are on), collapsed so the + sections do not push sibling pages out of view.""" + if not page_toc: + return (f'{_html_escape(title)}') + return (f'
    {_html_escape(title)}{page_toc}
    ') + + +def _render_node(node, active, prefix, page_toc): + if node['path'] is not None and not node['children']: + href = prefix + node['path'] + '.html' + if node['path'] == active: + return _active_leaf(node['title'], href, page_toc) + return f'{_html_escape(node["title"])}' + inner = ''.join(_render_node(c, active, prefix, page_toc) + for c in node['children']) + # Open only the branch leading to the active page; everything else folds. + op = ' open' if _has_active(node, active) else '' + return (f'{_html_escape(node["title"])}' + f'{inner}') + + +def render_sitenav(active, prefix, page_toc='', orphan_title=None): + body = '' + # A page absent from the master tree (orphan_title set) still gets its own + # entry + section list at the top, so it is never left without a TOC. + if orphan_title is not None: + href = prefix + active + '.html' + body += _active_leaf(orphan_title, href, page_toc) + body += ''.join(_render_node(n, active, prefix, page_toc) + for n in SITENAV_ROOTS) + return f'' + + +def _extract_sectlevel1(html): + """The page's own asciidoctor TOC list (balanced