Translations: validate catalog structure, repair Tamil - #176
Merged
Conversation
Seven catalogs had dropped an inline span or rewritten a code literal that
the tutorial tells the reader to look up in the editor. Restored from the
source string, leaving the surrounding translation as it was:
- de: <b>type</b> lost its emphasis
- fr: <tt>AND</tt> became "ET logique" — the opcode, not the conjunction
- he: <tt>B</tt> lost its markup in the reset-flags line
- ja: spinWheels dropped, <tt>X</tt>/<tt>$01</tt> merged into <tt>X=$01</tt>,
and the four <i> spans of "Then why 6502?" replaced by quotes
- uk: the second spinWheels became a pronoun
- vi: <tt>JSR loop</tt> and <tt>BNE decrement</tt> were translated, so the
instructions no longer assemble
- zh_Hans: hex ranges gained en dashes, <tt>$05ff</tt>/<tt>$00</tt>/<tt>$0f</tt>
lost their spans, and one sentence about the 16 colours went missing
Claude-Session: https://claude.ai/code/session_01URLYj1wgGxVNRbBpNT8xAV
The Tamil catalog arrived from Weblate at 456/456 translated, which is what msgfmt measures. It also carried six msgstr that Pango cannot parse — the only catalog with any — and ten translated code identifiers. Markup: a URL that swallowed half a sentence, an <a> with no closing >, an unclosed <b> that truncated the BVS/BVC list, "< tt>" with spaces so it rendered as literal text, and two paragraphs that lost their closing tag. These feed a GtkLabel with use-markup, where a parse error blanks the label. Code: checkSnakeCollision, illegalMove, generateApplePosition, loop and JSR end were translated, and "0001 AND 0001" turned the AND opcode into the Tamil word for "and". Restored verbatim. Terminology: "assembly" appeared as four different wrong words — பேரவை and சட்டசபை (a legislative assembly), சபை, கூட்டி (an adder) — alongside the correct அசெம்பிளி, including in "Open Assembly File" and the application name itself. Also fixed "to" as an e-mail recipient, "lead" as the metal, "current" as electric current, "logic" as a lawsuit, and "van der" as a delivery van in a surname. Four entries were left half-English by the machine pass and are now translated. The submission's house style is kept throughout: no grantha letters, s/j/z written with ச, as in the rest of the file. Tamil is listed as "Needs validation" — @TamilNeram, or any Tamil speaker, please check the wording. Claude-Session: https://claude.ai/code/session_01URLYj1wgGxVNRbBpNT8xAV
msgfmt only asks whether a msgstr is non-empty, so a catalog reports 100% translated while shipping markup Pango refuses to parse or a code identifier that was translated. Both classes reached main and neither is visible in the Weblate percentage. check.js reads every catalog and enforces four rules: the markup parses, no tag the source opens is dropped, <tt> content survives byte-for-byte, and format placeholders survive. Tag comparison is by name, so a translator can still point an <a> at a target-language article as the README allows; only in-document anchors are pinned. One <tt> literal is exempt, with its reason written down: "label" in the addressing-mode list is a placeholder word. Calibrated against all sixteen catalogs: it finds the seventeen real defects fixed in the previous two commits and nothing else, and reverting any one of them makes it red again. The catalog directory is the working directory, not the script's own. Under `--app gjs` the script runs as a bundle in dist/, where a script-relative path finds no .po files at all: every rule then passes over an empty set and the run reports success having read nothing. Checking LINGUAS makes that state loud instead, since it is the one failure this check must never report as success. Claude-Session: https://claude.ai/code/session_01URLYj1wgGxVNRbBpNT8xAV
JumpLink
force-pushed
the
fix/translation-structural-validation
branch
from
September 3, 2026 21:04
393a77e to
b415bb7
Compare
The check passed content GtkLabel renders as an empty string, which is the
one thing it exists to prevent. Measured against Gtk.Label with use-markup
on GTK 4 / Pango 1.57, it accepted every one of these:
A & B unescaped ampersand — GMarkup entity error
A B HTML-only entity name — unknown to GMarkup
<blink>y</blink> tag Pango does not know, added alongside the
source tags so the multiset comparison stayed happy
<b onclick="x">x</b> attribute the tag does not support
<tt class="c">LDA</tt> same, and the <tt> literal stops being compared
because the rule matched a bare <tt> only
<a>here</a> href dropped from a link
Zeile %s %s placeholder added rather than dropped
and rejected <a href='x'>, which GtkLabel accepts.
Rule 1 is now a model of the vocabulary GtkLabel takes — tag allowlist, per-tag
attributes, required href, GMarkup entities — and rule 4 compares placeholders
in both directions. One scan feeds rules 1 and 2 so a token the balance check
sees and the tag comparison misses is no longer possible.
The PO reader gained msgctxt and plural forms. It skipped msgstr[0] silently,
so the day a plural is extracted, all four rules would have passed it without
looking: the same failure as reading no catalogs at all, one shape smaller.
The catalog guard compares names against LINGUAS instead of counting, so a
stray .po beside a missing one no longer keeps the total right while leaving
a shipped language unchecked. English source strings are checked once each,
since a broken fragment in tutorial.mdx reaches every catalog through the .pot.
A gate that has stopped biting reports success over content nobody has looked
at, so 21 pairs the rules must classify correctly now run before any catalog is
read. Verified by mutation: weakening the tag allowlist, the entity pattern,
the attribute table, the <tt> comparison or the added-placeholder check each
turns the run red on the self-test alone.
No behaviour change on the current catalogs: 0 findings on this branch, the
same 32 strings on main.
Claude-Session: https://claude.ai/code/session_01URLYj1wgGxVNRbBpNT8xAV
Three entries of the same class were still on the branch. The structural
check cannot see them — the markup and the <tt> literals are intact, only
the prose is untranslated English with a word swapped in — so they were
found by measuring how much of each msgid survives verbatim in its msgstr:
Games now automatically இடைநிறுத்தம் when switching between screens
<b><tt>$10</tt>-<tt>$15</tt></b>: Snake position தகவல்கள் (head, body
segments, tail)
இதோ ஒரு சான்று. குறிப்பு that immediate operands அரே still prefixed
with a <tt>#</tt>.
The last one also carries அரே, a transliteration of the English "are".
Every other catalog translates all three.
Each replacement is built from words the catalog already uses — பாம்பின்
உடல் பகுதிகள் and தலை from the checkSnakeCollision paragraph, உடனடி and
முன்னொட்ட- from the addressing-mode entries, இடைநிறுத்த- from the Pause
button — and keeps the house style: no grantha letters, and the markup
and <tt> literals untouched. Wording still needs a native speaker, like
the rest of ta.
Claude-Session: https://claude.ai/code/session_01URLYj1wgGxVNRbBpNT8xAV
The README said the script catches "both" — unparseable markup and a translated code identifier. It now names the four rules, so a translator reading it knows what will be rejected and why, and the entity rule gets its own line under external links: a link retargeted to a URL joined with a bare `&` blanks the paragraph, which is the least obvious of the traps. AGENTS.md still pointed only at `build` for validation. `build` rewrites every catalog as a side effect; `check` is the one that reads them. Claude-Session: https://claude.ai/code/session_01URLYj1wgGxVNRbBpNT8xAV
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up to #155, which added Tamil.
Why
That PR arrived at 456/456 translated and merged green, because 456/456 is what
msgfmtmeasures: amsgstrcounts as translated when it is non-empty. Two things it never looks at had come along with it.Six Tamil strings are not well-formed markup. Tutorial paragraphs render through
GtkLabel use-markup, so Pango treats those as parse errors and the reader loses the paragraph. Tamil was the only catalog with any. And ten entries carried translated code literals — thirteen<tt>spans, nine distinct:<tt>checkSnakeCollision</tt>became<tt>செக் பாம்பு மோதல்</tt>,<tt>0001 AND 0001</tt>turned the AND opcode into the Tamil word for "and", and<tt>JSR end</tt>became<tt>JSR முடிவு</tt>, which no longer assembles. The tutorial tells the reader to find these in the editor.Checking the other fifteen catalogs found the same class already on
main, in seven of them. So this PR fixes the class, not the one instance, and adds the check that would have caught all of it.What changed
Seventeen entries across seven catalogs —
de1,fr1,he1,ja6,uk1,vi2,zh_Hans5.vihad translatedJSR loopandBNE decrement;zh_Hanshad swapped hyphens for en dashes inside hex ranges and dropped a sentence about the 16 colours;jahad lost four<i>spans andspinWheels. Surrounding translations left alone.Tamil. Markup repaired, code literals restored, four half-English entries translated. The word "assembly" appeared as three different wrong words: பேரவை (8x) and சட்டசபை (3x), both a legislative assembly, and கூட்டி (an adder, 1x), next to the correct அசெம்பிளி (8x). It reached the application name and the file menu:
Learn 6502 AssemblyAssemblyAssembly FilesOpen Assembly FileSave Assembly FileStep-by-step guide to 6502 assemblyAbout Learn 6502 AssemblySame pattern in the prose: "to" as an e-mail recipient (பெறுநர்), "lead" as the metal (ஈயம்), "current" as electric current, "logic" as a lawsuit, and "van der" as a delivery van inside a surname. 32 entries changed of 456.
The submission's house style is preserved. The catalog contains no grantha letters at all, writing s/j/z with ச (ச்டாக், ச்னேக், ச்பானிச்), and the new text follows that.
packages/translations/check.js, wired intogjsify workspace @learn6502/translations checkand run in CI before the type checks. Four rules: markup parses, no tag the source opens is dropped,<tt>content survives byte-for-byte, format placeholders survive.Tags are compared by name, so a translator can still point an
<a>at a target-language article as the README allows. Only in-document anchors are pinned, since a rewritten#jumpingis a dead link rather than a localisation. One<tt>literal is exempt with its reason written down:labelin the addressing-mode list is a placeholder word, not an identifier.Calibrated against all sixteen catalogs: on
mainit reports 32 strings across eight catalogs — the 17 above plus 15 in Tamil — and nothing else. Reverting any one of those 32 fixes makes it red again, on both the Node and the GJS path. The remaining Tamil edits are terminology, which no structural rule can see.Worth flagging, because the first version of this check was green for the wrong reason. It located the catalogs relative to its own file, and under
--app gjsit runs as a bundle indist/, where there are no.pofiles. Every rule passed over an empty set and the run reported success having read nothing, while the same code under Node correctly went red. The catalog directory is now the working directory, and the check compares the number of catalogs it read againstLINGUASso that reading nothing is loud rather than green.LINGUAS sorted (
tahad been appended afterzh_Hans), and the README gained the Tamil row plus the<tt>-is-code rule and the check command.Review pass
The check was then attacked deliberately, and three findings came back.
It passed content
GtkLabelrenders as an empty string. Measured against GTK 4 / Pango 1.57, the first version accepted a bare&, , a tag Pango does not know (added alongside the source tags, so the multiset comparison stayed happy), an attribute a tag does not support,<tt class="c">— which also stops the<tt>literal from being compared at all — and an<a>with thehrefdropped. It also accepted a placeholder added by the translation, and rejected<a href='x'>, whichGtkLabelaccepts. Rule 1 is now a model of the vocabulary a label takes, and rule 4 compares placeholders in both directions.The PO reader skipped
msgstr[0]silently. No catalog has a plural yet, so nothing was wrong today; the day one is extracted, all four rules would have passed it without looking.msgctxtand plural forms are read now. The catalog guard compares names againstLINGUASrather than counting them, so a stray.pobeside a missing one cannot keep the total right while leaving a language unchecked.Nothing proved the rules still bite. 21 pairs the rules must classify correctly run before any catalog is read, and weakening the tag allowlist, the entity pattern, the attribute table, the
<tt>comparison or the added-placeholder check each turns the run red on the self-test alone.Three Tamil entries of the repaired class were still on the branch, found by measuring how much of each
msgidsurvives verbatim in itsmsgstr— the structural check cannot see them, because the markup and the<tt>literals are intact and only the prose is untranslated:The last one also carries அரே, a transliteration of the English "are". Every other catalog translates all three. Each replacement is built from words the catalog already uses and keeps the house style, but the wording needs the same validation as the rest of ta.
What this does not do
The check says nothing about language quality. Tamil is listed as Needs validation, like the other AI-assisted catalogs.
@TamilNeram, thank you for the Tamil translation. The machine pass that produced parts of it damaged some markup and mistranslated "assembly", and I have repaired what I could verify mechanically while keeping your spelling conventions. Would you be willing to check the wording? The table above is the place to start. Two open questions where I picked a form and would rather you decided:
Verification
gjsify workspace @learn6502/translations checkpasses on all sixteen catalogsmsgfmt -cpasses on all sixteen, 456/456 each, no fuzzygjsify format --checkandgjsify lintclean for the added fileLANGUAGE=taThe second tile now reads
6502 அசெம்பிளிக்கான படிப்படியான வழிகாட்டி, where it read6502 சட்டசபைக்கு …before.I did not drive the app to the tutorial page, so the repaired tutorial markup is verified by the parser rather than by eye.
Noticed while testing, not part of this PR
With
LANGUAGE=taset and the main window in Tamil, the About dialog shows the German application name and English row labels. I have not looked into why, and it is unrelated to the catalog changes here. Flagging it in case it is worth its own issue.🤖 Generated with Claude Code
https://claude.ai/code/session_01URLYj1wgGxVNRbBpNT8xAV