Skip to content

Learn: GTK and NativeScript targets do not share a catalog #177

Description

@JumpLink

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions