Summary
The GNOME and Android targets are generated from the same MDX source but do not share their translatable strings, so most of the Android tutorial has no catalog entry at all and is shown in English regardless of the user's language.
The two targets are localised through the same catalog:
- app-gnome:
xgettext extracts <property name="label" translatable="yes"> from packages/learn/dist/*.ui into the POT, and Gtk.Label picks up the translation via gettext.
- app-android:
po2json converts the same .po files into packages/app-android/app/i18n/*.json, and MdxView.localizeHtmlViews() (packages/app-android/app/mdx/mdx-view.ts) looks up the whole HTML string as the key: localize(htmlView.html).
So an Android string is translatable only if it is byte-identical to a GTK label that reached the POT. Most are not.
Measurement
Built from main (gjsify workspace @learn6502/learn build), comparing the translatable label texts of dist/tutorial.ui with the html="…" attributes of dist/tutorial.ns.xml, both XML-unescaped:
|
unique strings |
GTK (translatable="yes" labels) |
144 |
NativeScript (html= attributes) |
139 |
| identical in both |
56 |
quick-help is worse in proportion: 80 vs 24 unique, 12 shared.
Cause
Of the 83 NativeScript strings with no GTK counterpart:
- 81 contain a
<w:SourceView …> element. Inline code becomes <tt>x</tt> on the GTK target but a whole SourceView element — carrying a generated id, editable, lineNumbers, selectable, lineNumberStart — on the NativeScript one. Rewriting each <w:SourceView … code="x" …></w:SourceView> back to <tt>x</tt> makes 78 of them match a GTK label exactly, which is the measure of how much of the gap is purely this encoding difference.
- 2 differ structurally for other reasons: the document title (
Tutorial <small>by <a …>Nick Morgan</a>…</small>, where GTK uses <sub>), and lists — GTK's GtkTextList splits a list into one label per item, while NativeScript renders the entire <ol>/<ul> as a single html string.
The generated id attribute is also per-render (nsSourceView216, …), so even if the catalogs were extracted from the NativeScript output instead, the msgids would churn on every renderer run.
Reproduce
gjsify workspace @learn6502/learn build
node -e '
const { readFileSync } = require("node:fs");
const un = (s) => s.replace(/&(amp|lt|gt|quot|apos);/g, (m, e) =>
({ amp: "&", lt: "<", gt: ">", quot: `"`, apos: "\x27" })[e]);
const src = (f) => readFileSync(`packages/learn/dist/${f}`, "utf8");
const gtk = new Set([...src("tutorial.ui")
.matchAll(/<property name="label" translatable="yes"[^>]*>([\s\S]*?)<\/property>/g)].map((m) => un(m[1])));
const ns = new Set([...src("tutorial.ns.xml").matchAll(/\bhtml="([^"]*)"/g)].map((m) => un(m[1])));
console.log("gtk", gtk.size, "ns", ns.size, "common", [...gtk].filter((s) => ns.has(s)).length);
'
# gtk 144 ns 139 common 56
Not a bug fix
How the two targets should share a catalog is a design decision, not something to patch: the options (extract from a target-neutral intermediate; render inline code identically on both; key Android lookups on something other than the full HTML string; teach MdxView to reassemble a translated GTK label) differ in what they cost the Android renderer, the existing 16 catalogs and Weblate. Filing the measurement rather than inventing an answer.
Related: the emitter now has a structural check (gjsify workspace @learn6502/learn check) that compares code literals across all three targets, but deliberately says nothing about which strings are translatable in each — that is this issue.
Summary
The GNOME and Android targets are generated from the same MDX source but do not share their translatable strings, so most of the Android tutorial has no catalog entry at all and is shown in English regardless of the user's language.
The two targets are localised through the same catalog:
xgettextextracts<property name="label" translatable="yes">frompackages/learn/dist/*.uiinto the POT, andGtk.Labelpicks up the translation via gettext.po2jsonconverts the same.pofiles intopackages/app-android/app/i18n/*.json, andMdxView.localizeHtmlViews()(packages/app-android/app/mdx/mdx-view.ts) looks up the whole HTML string as the key:localize(htmlView.html).So an Android string is translatable only if it is byte-identical to a GTK label that reached the POT. Most are not.
Measurement
Built from
main(gjsify workspace @learn6502/learn build), comparing the translatable label texts ofdist/tutorial.uiwith thehtml="…"attributes ofdist/tutorial.ns.xml, both XML-unescaped:translatable="yes"labels)html=attributes)quick-helpis worse in proportion: 80 vs 24 unique, 12 shared.Cause
Of the 83 NativeScript strings with no GTK counterpart:
<w:SourceView …>element. Inline code becomes<tt>x</tt>on the GTK target but a wholeSourceViewelement — carrying a generatedid,editable,lineNumbers,selectable,lineNumberStart— on the NativeScript one. Rewriting each<w:SourceView … code="x" …></w:SourceView>back to<tt>x</tt>makes 78 of them match a GTK label exactly, which is the measure of how much of the gap is purely this encoding difference.Tutorial <small>by <a …>Nick Morgan</a>…</small>, where GTK uses<sub>), and lists — GTK'sGtkTextListsplits a list into one label per item, while NativeScript renders the entire<ol>/<ul>as a singlehtmlstring.The generated
idattribute is also per-render (nsSourceView216, …), so even if the catalogs were extracted from the NativeScript output instead, the msgids would churn on every renderer run.Reproduce
Not a bug fix
How the two targets should share a catalog is a design decision, not something to patch: the options (extract from a target-neutral intermediate; render inline code identically on both; key Android lookups on something other than the full HTML string; teach
MdxViewto reassemble a translated GTK label) differ in what they cost the Android renderer, the existing 16 catalogs and Weblate. Filing the measurement rather than inventing an answer.Related: the emitter now has a structural check (
gjsify workspace @learn6502/learn check) that compares code literals across all three targets, but deliberately says nothing about which strings are translatable in each — that is this issue.