diff options
| author | Ralph Amissah <ralph.amissah@gmail.com> | 2026-09-21 10:29:15 -0400 |
|---|---|---|
| committer | Ralph Amissah <ralph.amissah@gmail.com> | 2026-09-22 14:14:40 -0400 |
| commit | b65fb777715cd02e52286ea11422c6c5e828291c (patch) | |
| tree | e23abedb96c766c729d4d985c50d496271688448 /src | |
| parent | pod: carry tools/po4a, the translation working set (diff) | |
pod: generate the po4a configuration, --po4a-cfg
A document's po4a configuration is derived rather than kept. Spine
already knows both halves without looking: the manifest says which
languages, and the markup says which files, through the << lines the
parser follows. Derived, it cannot disagree with the document, where a
hand-kept file eventually does: a language added to the manifest and
forgotten in the Makefile is a translation nobody updates.
It prints, rather than writing. Spine does not write into a source tree,
and the file belongs beside the catalogues in the pod it describes:
spine --po4a-cfg <pod> > <pod>/tools/po4a/po4a.cfg
Three things the Makefile it replaces knew, which had to be found by
running po4a rather than by reading:
--keep 0 is the difference between regenerating a document and
destroying it. po4a will not write a translated file below a
completeness threshold, and that threshold defaults to 80%. A partly
translated document is the normal state of one being worked on, and sisu
covers it: an untranslated string falls back to the source text.
Measured at the default, 181 of live-manual's 189 translated files would
be discarded, and nine languages would collapse to eight surviving
files, reading as the document reverting to its source language rather
than as a failure.
index.html.in is not markup and no << line names it, so a configuration
built from the manifest and the insert list alone drops it, and with it
index.html.in.pot and its nine .po files at the next regeneration. It is
found by asking whether the source language has one.
neverwrap is not set, and the measurement is recorded beside the code:
setting it turns 889 of 1126 strings fuzzy in four languages, and po4a
does not use a fuzzy translation, so Catalan's coverage would fall from
about 46% to about 12%. Tidy formatting is not worth that.
The run-complete banner is suppressed for this action. The configuration
goes to stdout and the banner would otherwise be part of the file, which
is what the round trips already avoid by leaving early; this one prints
from inside output processing and cannot.
(assisted by Claude-Code)
Diffstat (limited to 'src')
| -rw-r--r-- | src/sisudoc/outputs/io_out/hub.d | 15 | ||||
| -rw-r--r-- | src/sisudoc/outputs/io_out/po4a_cfg.d | 173 | ||||
| -rw-r--r-- | src/sisudoc/spine.d | 33 |
3 files changed, 214 insertions, 7 deletions
diff --git a/src/sisudoc/outputs/io_out/hub.d b/src/sisudoc/outputs/io_out/hub.d index 0973097..254eaa5 100644 --- a/src/sisudoc/outputs/io_out/hub.d +++ b/src/sisudoc/outputs/io_out/hub.d @@ -62,6 +62,21 @@ template outputHub() { @system void outputHub(D)(D doc) { mixin Msg; auto msg = Msg!()(doc.matters); + /+ ↓ the po4a configuration, printed rather than written. + Spine does not write into a source tree, and this belongs beside the + catalogues in the pod it describes, so it goes to stdout for the + caller to put where they want it: + spine --po4a-cfg <pod> > <pod>/tools/po4a/po4a.cfg + Once per document, not once per language: it is generated from the + source language's files and names every other language itself. + +/ + if (doc.matters.opt.action.po4a_cfg + && doc.matters.src.language == doc.matters.pod.manifest_list_of_languages[0] + ) { + import sisudoc.outputs.io_out.po4a_cfg; + mixin spinePo4aConfig; + write(po4aConfig(doc.matters)); + } enum outTask { source_or_pod, sqlite, sqlite_multi, latex, odt, epub, html_scroll, html_seg, html_stuff, text, skel } void Scheduled(D)(int sched, D doc) { auto msg = Msg!()(doc.matters); diff --git a/src/sisudoc/outputs/io_out/po4a_cfg.d b/src/sisudoc/outputs/io_out/po4a_cfg.d new file mode 100644 index 0000000..ddcc1ef --- /dev/null +++ b/src/sisudoc/outputs/io_out/po4a_cfg.d @@ -0,0 +1,173 @@ +/+ +- Name: SisuDoc Spine, Doc Reform [a part of] + - Description: documents, structuring, processing, publishing, search + - static content generator + + - Author: Ralph Amissah + [ralph.amissah@gmail.com] + + - Copyright: (C) 2015 (continuously updated, current 2026) Ralph Amissah, All Rights Reserved. + + - License: AGPL 3 or later: + + Spine (SiSU), a framework for document structuring, publishing and + search + + Copyright (C) Ralph Amissah + + This program is free software: you can redistribute it and/or modify it + under the terms of the GNU AFERO General Public License as published by the + Free Software Foundation, either version 3 of the License, or (at your + option) any later version. + + This program is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + more details. + + You should have received a copy of the GNU General Public License along with + this program. If not, see [https://www.gnu.org/licenses/]. + + If you have Internet connection, the latest version of the AGPL should be + available at these locations: + [https://www.fsf.org/licensing/licenses/agpl.html] + [https://www.gnu.org/licenses/agpl.html] + + - Spine (by Doc Reform, related to SiSU) uses standard: + - docReform markup syntax + - standard SiSU markup syntax with modified headers and minor modifications + - docReform object numbering + - standard SiSU object citation numbering & system + + - Homepages: + [https://www.sisudoc.org] + [https://www.doc-reform.org] + + - Git + [https://git.sisudoc.org/] + ++/ +/++ + module po4a_cfg;<BR> + - the po4a configuration for a document, generated from its manifest and + the files its markup names<BR> + - written for tools/po4a/po4a.cfg, so its paths are relative to there ++/ +module sisudoc.outputs.io_out.po4a_cfg; +@system: +/+ ↓ po4a's configuration, derived rather than kept + + The sample this replaces kept a Makefile that built po4a.cfg by globbing + a language directory and listing what it found. Spine already knows both + halves without looking: the manifest says which languages a document + has, and the markup says which files it is made of, through the << lines + the parser follows. Deriving the configuration from those two means it + cannot disagree with the document, which a hand-kept file eventually + does: a language added to the manifest and forgotten in the Makefile + produces a translation nobody updates. + . + It is derived, so it is not kept in git. The sample repository's + whitelist admits pot/ and po/ and not this. + . + neverwrap is deliberately NOT set, and the reason is worth keeping. + . + Without it po4a rewraps its output at about 75 columns, so a document + whose markup keeps one paragraph on one long line comes back reflowed. + That is untidy: it changes no object, since sisu delimits on blank + lines, but it churns every translated file in a diff and leaves the + source language and its translations formatted unlike each other. The + obvious thing is to turn wrapping off. + . + Measured on live-manual, same catalogues and same English, the option + the only difference: + . + fuzzy before with neverwrap without + ca 1 889 5 + de 8 103 10 + it 149 804 152 + . + of 1126 strings. po4a will not use a fuzzy translation, so each one + renders as the source language: Catalan's coverage would fall from about + 46% to about 12%, and Spanish, French and Japanese with it. Setting the + option re-extracts the strings in a form the existing catalogues do not + match, and four fifths of nine languages' work stops being used. + . + Tidy formatting is not worth that. The wrapping stays. ++/ +template spinePo4aConfig() { + string po4aConfig(M)(M doc_matters) { + import std.algorithm : sort; + import std.array : array, join; + import std.conv : to; + import std.file : exists; + import std.path : baseName; + string _src_lang = doc_matters.src.language; + /+ ↓ the languages po4a is to produce, which is every language the + manifest lists except the one being translated from + +/ + string[] _targets; + foreach (_lang; doc_matters.pod.manifest_list_of_languages) { + if (_lang != _src_lang) { + _targets ~= _lang; + } + } + /+ ↓ the files the document is made of: its master, and every insert the + markup names. In manifest order rather than directory order, so the + configuration is the same however a filesystem chooses to list a + directory. + +/ + string[] _files; + _files ~= doc_matters.src.filename; + string[] _inserts; + foreach (_insert; doc_matters.srcs.file_insert_list) { + _inserts ~= _insert.baseName; + } + _inserts = _inserts.sort.array; + _files ~= _inserts; + /+ ↓ paths are relative to tools/po4a/, where this file belongs +/ + enum string _to_text = "../../media/text"; + string _o; + _o ~= "# po4a configuration for " ~ doc_matters.src.filename_base ~ "\n"; + _o ~= "# generated by spine from pod.manifest and the markup; not kept in git\n"; + _o ~= "# run from the directory this file is in\n"; + _o ~= "\n"; + /+ ↓ --keep 0 is not optional here, it is the difference between + regenerating a document and destroying it. + . + po4a will not write a translated file that falls below a + completeness threshold, and that threshold defaults to 80%. A + partially translated document is the normal state of one being + worked on, and sisu's own behaviour covers it: an untranslated + string falls back to the source text, so a file at 3% is mostly the + source language and entirely readable. (Measured on live-manual at + the default: 181 of its 189 translated files would be discarded, and + nine languages would collapse to eight surviving files. It would not + look like a failure either, because what remains reads as the + document reverting to its source language.) + . + The Makefile this replaces passed --keep 0 for exactly this reason. + +/ + _o ~= "[options] --keep 0 --package-name " ~ doc_matters.src.filename_base ~ "\n"; + _o ~= "\n"; + _o ~= "[po4a_langs] " ~ _targets.join(" ") ~ "\n"; + _o ~= "[po4a_paths] pot/$master.pot $lang:po/$lang/$master.po\n"; + _o ~= "\n"; + foreach (_f; _files) { + _o ~= "[type: text] " ~ _to_text ~ "/" ~ _src_lang ~ "/" ~ _f + ~ " $lang:" ~ _to_text ~ "/$lang/" ~ _f ~ "\n"; + } + /+ ↓ index.html.in is not markup and no << line names it, so it is found + by asking whether the source language has one. live-manual does, the + Makefile it replaces carried it as xhtml, and the sample + repository's whitelist admits index.html.in.pot and .po by name. + Left out, regenerating would quietly drop those catalogue entries. + +/ + string _index_in = doc_matters.pod.manifest_path + ~ "/media/text/" ~ _src_lang ~ "/index.html.in"; + if (_index_in.exists) { + _o ~= "[type: xhtml] " ~ _to_text ~ "/" ~ _src_lang ~ "/index.html.in" + ~ " $lang:" ~ _to_text ~ "/$lang/index.html.in\n"; + } + return _o; + } +} diff --git a/src/sisudoc/spine.d b/src/sisudoc/spine.d index 9013c3e..9424e02 100644 --- a/src/sisudoc/spine.d +++ b/src/sisudoc/spine.d @@ -97,14 +97,24 @@ string program_name = "spine"; static auto rgx = RgxI(); static auto rgx_y = RgxYaml(); static auto rgx_files = RgxFiles(); + /+ ↓ an action whose output is a file's contents, printed to stdout, turns + this off: the banner would otherwise be part of what it printed. The + round trips do the same by leaving before scope(success) runs, which + they can because they leave early; --po4a-cfg prints from inside + output processing and so says so here instead. Set where the options + are read, below: the banner is declared before they exist. + +/ + bool _run_banner = true; scope(success) { - writefln( - "~ run complete, ok ~ (%s-%s.%s.%s, %s D:%s, %s %s)", - program_name, - _ver.major, _ver.minor, _ver.patch, - __VENDOR__, __VERSION__, - bits, os, - ); + if (_run_banner) { + writefln( + "~ run complete, ok ~ (%s-%s.%s.%s, %s D:%s, %s %s)", + program_name, + _ver.major, _ver.minor, _ver.patch, + __VENDOR__, __VERSION__, + bits, os, + ); + } } scope(failure) { debug(checkdoc) { @@ -166,6 +176,7 @@ string program_name = "spine"; "pdf" : false, "pdf-color-links" : false, "pdf-init" : false, + "po4a-cfg" : false, "pod" : false, "pod2" : false, "serial" : false, @@ -288,6 +299,7 @@ string program_name = "spine"; "pdf", "latex output for pdfs", &opts["pdf"], "pdf-color-links", "mono or color links for pdfs", &opts["pdf-color-links"], "pdf-init", "initialise latex shared files (see latex-header-sty)", &opts["pdf-init"], + "po4a-cfg", "print the po4a configuration for this document", &opts["po4a-cfg"], "pod", "spine (doc reform) pod source content bundled", &opts["pod"], "pod2", "pod with document abstraction (.ssp) bundled", &opts["pod2"], "quiet|q", "output to terminal", &opts["vox_is1"], @@ -359,6 +371,10 @@ string program_name = "spine"; "debug-stages", "debug stages", &opts["debug-stages"], // "sqlite-db-filename", "=[filename].sql.db", &settings["sqlite-db-filename"], ); + /+ ↓ --po4a-cfg prints a file's contents; see the banner above +/ + if (opts["po4a-cfg"]) { + _run_banner = false; + } if (helpInfo.helpWanted) { defaultGetoptPrinter("Some information about the program.", helpInfo.options); } @@ -611,6 +627,9 @@ string program_name = "spine"; @trusted bool ocn_off() { return ((opts["ocn-off"]) || (opts["no-ocn"])) ? true : false; } + @trusted bool po4a_cfg() { + return opts["po4a-cfg"]; + } @trusted bool pod() { return (opts["pod"] || opts["pod2"]) ? true : false; } |
