ci(i18n): let a protected term be satisfied by a declared equivalent form - #1966
Open
bloxster wants to merge 2 commits into
Open
ci(i18n): let a protected term be satisfied by a declared equivalent form#1966bloxster wants to merge 2 commits into
bloxster wants to merge 2 commits into
Conversation
Three gaps found while auditing this list against the categorized list in
the frontend repo.
1. Key and address compounds are only half protected. `Unified Address` and
`Viewing Key` are enforced, the other expanded forms are not, so a
translation can localize them and nothing complains:
Full Viewing Key 5 English pages, 0 translations missing it
Incoming Viewing Key 2 English pages, 0 translations missing it
2. The check is case-sensitive, so a term protected in one casing is
unprotected in the other. The list carries `Free2Z`, `Zcash.Me`,
`MetaMask Snap` and `Zingo`, while the variants that actually appear in
the wiki went unenforced:
Zcash.me 1 English page, 0 translations missing it
Zingo! 20 English pages, 0 translations missing it
ZGo 27 English pages, 0 translations missing it
Zgo 9 English pages, 0 translations missing it
Same trap as `Zodl` vs `ZODL`, which is still open.
3. `ZK-SNARKs` is enforced but its singular is not:
ZK-SNARK 1 English page, 0 translations missing it
All seven are free: every existing translation already keeps them verbatim,
so this locks in current behaviour and prevents a future regression.
Verified — the whole-tree audit reports 72 violations both before and after,
and none names a newly added term. (Those 72 are pre-existing drift from
ZecHub#1948 and ZecHub#1957, cleared by the pending backfill/re-sync PR.)
WHAT IS NOT HERE, AND WHY IT SHOULD NOT BE ADDED LATER EITHER
An earlier draft of this change listed `Unified Addresses` as merely
expensive — 94 violations, 113 after the pending re-sync. Measuring what
those violations actually are changed the conclusion: protecting an English
PLURAL is wrong in principle, not just costly.
Of the 113, 70% already carry the protected singular `Unified Address`.
They did not lose the concept; they declined to pluralize an English
loanword. English "for inspecting Unified Addresses" becomes Italian "per
ispezionare gli Unified Address", which is correct Italian — Italian does
not add -s to English loanwords. Japanese writes `Unified Addressを調べる`
because Japanese does not inflect plurals at all, and 23 of the 113 sit in
ja/ko/zh, none of which do.
Enforcing the plural would push a grammatical error into Italian, French,
Spanish and others, and a meaningless string into three more, to satisfy a
check the singular already satisfies conceptually.
The principle: protect singulars, not plurals. Languages differ in whether
and how they pluralize borrowed nouns, so a protected plural imposes English
grammar on all 18 locales.
This is not hypothetical. `ZK-SNARKs` is enforced today, and on all four
English pages that use it, ja/ko/zh/it carry the English plural verbatim —
they had no choice. A follow-up will teach the checker that a plural is
satisfied by its protected singular and retire the plural entries; this
change adds the singular so that follow-up has something to fall back to.
Also still held back, each priced against both main and the pending re-sync
tree, because adding them creates violations no queued sync repairs (the
terms list is not part of staleness detection, so the debt is invisible to
the dashboard and unreachable by the sync agent): TEX Address 39,
Dev Fund 25/29, Diversified Address 14, Spending Key 7, Metamask Snap 2,
zk-SNARK 2, Free2z 1.
Skipped entirely: `Unified Viewing Key` and `Outgoing Viewing Key` are in
the categorized list but appear on no English page.
…form
Some protected terms are the same word in two shapes — a singular and its
plural. Whether an English loanword takes an -s is a decision each language
makes for itself, not one English makes for it: Italian writes "gli Unified
Address" because Italian does not pluralize borrowed nouns, and Japanese,
Korean and Chinese do not inflect plurals at all. Demanding one exact shape
forces English grammar into 18 languages that do not share it, to assert a
term the other shape already carries.
Not hypothetical: `ZK-SNARKs` is protected and its singular was not, so on
all four English pages using it, ja/ko/zh/it carry the English plural
verbatim. They had no choice — the gate required it.
A translation may now satisfy a term with any form DECLARED equivalent to it,
via a new optional `equivalentForms` key:
"equivalentForms": [
["ZK-SNARK", "ZK-SNARKs"]
]
The pairing is declared, never inferred from spelling. An earlier draft
derived it by stripping a trailing "s"/"es", which two reviewers independently
rejected: that makes `Argos` interchangeable with `Argo` and `zec.rocks` with
`zec.rock` — different things that merely look related — breaks on acronyms
(`CSS` -> `CS`), and silently misses irregular plurals like `Technologies` /
`Technology`. It also got its own motivating case wrong, deriving "Unified
Addresse" from "Unified Addresses". A maintainer states which forms are one
word; the checker never guesses.
Membership is symmetric because a group asserts "these are one term", so any
member satisfies it. That is now a declared property rather than an accident
of the derivation.
The source side still tests the exact term — the English page really does use
that form. Only the translation side accepts an equivalent.
Every malformed declaration is fatal: a member absent from preserveVerbatim,
a term in two groups, a duplicate, a group of one, a non-array. A group that
was quietly ignored would weaken this gate in the way least likely to be
noticed.
`satisfiedIn` is applied to classify()'s baseline test as well, and that is
load-bearing in the opposite direction from the obvious one: it TIGHTENS the
gate. If a base translation carries only the singular and a change strips it,
the previous exact-match baseline made violatedAtBase true, bucketing the
result "pre-existing" and exiting 0 — silently passing a change that removed
the last protected form. It is now correctly "introduced", exit 1.
Logic lives in translation/lib/term-forms.mjs (pure, no fs/process/clock) so
the existing hash-lib-tests job picks up its 11 unit tests with no workflow
change: the declared pair in both directions, the -es case the derivation got
wrong, look-alikes that must NOT pair, and each validation failure.
termRegExp now memoizes; it was recompiling per (page, locale, term).
Note this changes NOTHING on the current corpus — 72 violations before and
after — because the harm already landed: the locales were forced to carry the
plural, so no violations remain to clear. The value is preventive, and it is
why the companion terms PR adds `ZK-SNARK` rather than removing `ZK-SNARKs`.
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.
Why
Some protected terms are the same word in two shapes — a singular and its plural. Whether an English loanword takes an
-sis a decision each language makes for itself, not one English makes for it. Italian writes "gli Unified Address", because Italian does not pluralize borrowed nouns; Japanese, Korean and Chinese do not inflect plurals at all.Demanding one exact shape therefore forces English grammar into 18 languages that do not share it, to assert a term the other shape already carries.
This is not hypothetical.
ZK-SNARKsis protected and its singular was not, so on all four English pages using it,ja/ko/zh/itcarry the English plural verbatim. They had no choice — the gate required it.What this does
A translation may now satisfy a protected term with any form declared equivalent to it, via a new optional key in
translation/protected-terms.json:A group means "these are one term", so membership is symmetric — any member asserts it. The source side still tests the exact term, because the English page really does use that form; only the translation side accepts an equivalent.
Declared, never derived
An earlier draft inferred the pairing by stripping a trailing
s/es. Two reviewers independently rejected that, and they were right:Argosinterchangeable withArgo, andzec.rockswithzec.rock— different things that merely look related;CSS→CS);Technologies/Technology;Unified AddressefromUnified Addresses, so the relaxation would never have fired for the term that prompted it.A maintainer now states which forms are one word; the checker never guesses.
Every malformed declaration is fatal: a member absent from
preserveVerbatim, a term in two groups, a duplicate within a group, a group of fewer than two, a non-array, a blank or whitespace-padded entry. A group that was quietly ignored would weaken this gate in the way least likely to be noticed — and a blank entry is the one shape that could do real damage, since a term of" "matches nearly every page.It also closes a false negative
satisfiedInis applied toclassify()'s baseline test, and that tightens the gate rather than merely preserving it. If a base translation carries only the singular and a change strips it, the previous exact-match baseline madeviolatedAtBasetrue — bucketing the resultpre-existingand exiting 0, silently passing a change that removed the last protected form. It is now correctlyintroduced, exit 1.Base content is judged by the base declaration, read from git at the merge base, not by this change's config. Otherwise a PR that removed an equivalence group could never fail the gate: the base would look as though it had already been violating. A base config that cannot be read or parsed falls back to the head declaration rather than failing a PR over history it did not write.
Verification
The logic lives in
translation/lib/term-forms.mjs— pure, no filesystem, process or clock — which puts its tests where the existinghash-lib-testsjob already globs (translation/lib/*.test.mjs), so no workflow change is needed. They cover the declared pair in both directions, the-escase the old derivation got wrong, look-alikes that must not pair, and each validation failure.termRegExpnow memoizes; it was recompiling per (page, locale, term).Note on scope
This changes nothing on the current corpus — 72 violations before and after — because the harm already landed: the locales were forced to carry the plural, so no violations remain to clear. The value is preventive, and it is why the companion PR adds
ZK-SNARKrather than removingZK-SNARKs.Merge order
Stacked on #1965, which adds the
ZK-SNARKsingular that the declared group refers to. Merge that first; the diff here collapses to the checker change once it lands.Known limitation
A PR that edits
equivalentFormswhile also changing translations is judged partly by its own declaration, so removing a group cannot itself fail the gate in every case. Reviewers should treat a change toequivalentFormsas needing a whole-tree audit (node scripts/check-protected-terms.mjswith no--base) rather than trusting the scoped result alone.