You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
finding(i18n): template-literal t() key families are only PREFIX-checked, so a member missing from all ten packs is invisible to every gate — ~20 families exposed #4964
Recorded while fixing objectstack-ai/objectstack#8815 (PR #4962). That card is the proof this class is live; this issue is about the class, not that card.
The hole
When a call site builds its key from a template literal, no gate in the repo can tell which members it needs:
{t(`filterBuilder.operators.${op.value}`)}
scripts/check-i18n-call-site-keys.mjs classifies these as missing-prefix and asks only whether the prefix resolves. Its own header is explicit that this is deliberate — it is "about a dynamic key that is true without knowing the value." filterBuilder.operators resolved sixteen members deep, so it passed.
packages/i18n/src/__tests__/all-locales-key-parity.test.ts asks whether the ten packs agree with each other. Its header already records the consequence in as many words: "Ten packs identically missing it is full parity, so this file is green on it by construction."
check:i18n-drift only fires when an existing envalue changes. A key that was never added never changes.
So a member missing from all ten packs at once is invisible to the whole gate farm, and the user is shown the raw key — i18next resolves a missing key to the key itself.
Note this is not covered by the createSafeTranslation defaults table: that table serves only the no-provider path, and the Console mounts a provider, so the pack's answer is the one that renders.
Measured instance (already fixed, do not re-fix here)
filterBuilder.operators.* was missing startsWith, endsWith, isNull, isNotNull, exists, notExists from all ten packs. Users saw filterBuilder.operators.isNull in the list-filter operator dropdown. Fixed in PR #4962, which also adds a parity test pinning the packs against FILTER_BUILDER_OPERATORS in both directions — but that guard is specific to that one family. Nothing generalises it.
Exposed families
Every other dynamic key family has the same structural exposure. Enumerated with:
⚠️I did not measure these. I confirmed the mechanism and enumerated the call sites; whether any given family actually has missing members is exactly what nobody currently knows, which is the point. Triage should decide whether this is worth a sweep. engine.* is partly accounted for by the closed #4662 (module-local two-locale table) and may need different treatment.
Shape of a general fix, if wanted
The generalisable move is what PR #4962 did for one family: assert the pack contents against the vocabulary export the call site iterates, since a dynamic key is nearly always built from a known finite set in code. Options worth pricing at triage:
Teach check-i18n-call-site-keys.mjs to resolve a template key whose interpolated expression is a statically-known union or a readonly array of literals, and check each member. Wider reach, real parser work.
A ratchet listing the known dynamic families and requiring each to name its vocabulary source, so a new one cannot land unguarded.
No pm:queue — filing as an observation for triage to grade, not a claim that a sweep is warranted.
Recorded while fixing objectstack-ai/objectstack#8815 (PR #4962). That card is the proof this class is live; this issue is about the class, not that card.
The hole
When a call site builds its key from a template literal, no gate in the repo can tell which members it needs:
scripts/check-i18n-call-site-keys.mjsclassifies these asmissing-prefixand asks only whether the prefix resolves. Its own header is explicit that this is deliberate — it is "about a dynamic key that is true without knowing the value."filterBuilder.operatorsresolved sixteen members deep, so it passed.packages/i18n/src/__tests__/all-locales-key-parity.test.tsasks whether the ten packs agree with each other. Its header already records the consequence in as many words: "Ten packs identically missing it is full parity, so this file is green on it by construction."check:i18n-driftonly fires when an existingenvalue changes. A key that was never added never changes.So a member missing from all ten packs at once is invisible to the whole gate farm, and the user is shown the raw key — i18next resolves a missing key to the key itself.
Note this is not covered by the
createSafeTranslationdefaults table: that table serves only the no-provider path, and the Console mounts a provider, so the pack's answer is the one that renders.Measured instance (already fixed, do not re-fix here)
filterBuilder.operators.*was missingstartsWith,endsWith,isNull,isNotNull,exists,notExistsfrom all ten packs. Users sawfilterBuilder.operators.isNullin the list-filter operator dropdown. Fixed in PR #4962, which also adds a parity test pinning the packs againstFILTER_BUILDER_OPERATORSin both directions — but that guard is specific to that one family. Nothing generalises it.Exposed families
Every other dynamic key family has the same structural exposure. Enumerated with:
Roughly twenty, including:
engine.fieldType.*,engine.fieldCategory.*,engine.inspector.appNav.type.*,engine.studio.access.explain.layer.*,home.recentApps.itemType.*,managedByBadge.*,organization.invitations.status.*,grid.import.type.*,grid.import.jobStatus.*,grid.import.confidence.*,marketplace.category.*,marketplace.disclosure.runtime.*,approvalsInbox.*,report.aggregate.*,perm.tabs.vis.*,perm.cel.type.*,gantt.viewMode.*.engine.*is partly accounted for by the closed #4662 (module-local two-locale table) and may need different treatment.Shape of a general fix, if wanted
The generalisable move is what PR #4962 did for one family: assert the pack contents against the vocabulary export the call site iterates, since a dynamic key is nearly always built from a known finite set in code. Options worth pricing at triage:
check-i18n-call-site-keys.mjsto resolve a template key whose interpolated expression is a statically-known union or areadonlyarray of literals, and check each member. Wider reach, real parser work.No
pm:queue— filing as an observation for triage to grade, not a claim that a sweep is warranted.