fix(rest): localize the canonical PLURAL /meta spelling, not just the singular (#6349) - #7372
Conversation
… singular (#6349) `translateMetaItem` / `translateMetaItems` decide whether a metadata type is localizable by asking `isTranslatableMetaType`, which reads `TRANSLATABLE_METADATA_TYPES` — a set DERIVED (#3786) from `METADATA_DOCUMENT_TRANSLATORS`' keys, and those keys are singular-only (view / action / object / app / dashboard / page), matching `translateMetadataDocument`'s documented "Canonical metadata type string". The three `/meta` read handlers handed those helpers the RAW `:type` path segment. Prime Directive #3 makes PLURAL the canonical REST spelling, so a caller following the documentation missed the set, the predicate answered false, and the whole localization was skipped — same route, same document, same Accept-Language, two different answers. Normalized in the two helpers (the issue's option 2) rather than at the five call sites, for the reason #6241 proved: a normalization the callers own is one a later caller forgets. `metaTypeSingular` leaves an unmapped type untouched, so the translatable SET is unchanged — only the spellings that reach it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0158ZQo7LiHSxGWpYKuPq1wu
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
📓 Docs Drift CheckThis PR changes 1 package(s): 9 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
⛔ 3 release-owned page(s) also reference the affected code. These are read-only:
|
Fixes #6349
translateMetaItem/translateMetaItemsdecide "does this type translate" by askingisTranslatableMetaType, which readsTRANSLATABLE_METADATA_TYPES— a set derived(#3786) from
METADATA_DOCUMENT_TRANSLATORS' keys, and those keys are singular-only:view/action/object/app/dashboard/page. That matchestranslateMetadataDocument's documented parameter, "Canonical metadata type string".The
/metaread handlers were handing those helpers the raw:typepath segment.Prime Directive #3 makes PLURAL the canonical REST spelling (
/api/v1/meta/apps), so acaller following the documentation missed the set, the predicate answered
false, andthe entire localization was skipped. Same route, same document, same
Accept-Language,two different answers.
This is #3984's family — a per-type judgement that only ever saw the singular — landing
on the i18n predicate instead of on an authorization gate, so the cost is an English
fallback, not a leak.
Premise re-verified on
origin/main, and my own censusThe issue's line numbers had drifted (this file took several merges); I relocated every
passthrough by content. The card said "three handlers"; the issue listed four call sites.
Measured on my merge base: five passthrough sites across three handlers.
GET /meta/:typeGET /meta/:type/:nameGET /meta/:type/:section/:nameThe single-item handler's three are the cached-normal exit, the non-cached exit, and
the cached branch's ADR-0106 D6 "visibility undetermined" exit — which has its own
res.jsonand its own re-wrapped envelope. That fifth site is the one the issue did nothave; it arrived after the card was filed. All five reach
translateMetaItem(
translateMetaEnvelopedelegates to it).The PM's dispatch note corrected its own claim comment, and the correction is right:
TRANSLATABLE_METADATA_TYPESis not defined inrest-server.ts. It is derived inpackages/spec/src/system/i18n-resolver.ts;rest-server.tsimports and consumes it viaa local memoised
isTranslatableMetaType.packages/specis untouched here — option 3was ruled out of this card.
The fix — option 2, and the existing normaliser reused
Normalized inside the two helpers rather than at the five call sites, per the issue's
preference and the ruling. The reason is #6241's, written into the code: a normalization
the callers own is one a later caller forgets — eight days after #3984 a new branch in
this same file was still comparing the raw param. The helper owns the question "does this
type translate", so it owns the spelling that question is asked in, and a sixth call site
inherits the fix for free.
No new pluralisation table. The repo already has the normaliser and I reused it:
RestServer.metaTypeSingular(rest-server.ts), a one-liner overPLURAL_TO_SINGULARfrom@objectstack/spec/shared, introduced by the #3984 family fixand already used by this file's per-type gates and by the compound handler's
compoundType. A second table would have been exactly the drift pair this family keepsdeleting.
Both helpers now also pass the normalized type on to
translateMetadataDocument,which is what its "canonical" contract asks for.
metaTypeSingularleaves an unmapped type untouched, so the translatable set isunchanged — only which spellings reach it.
Tests — 10 cases,
packages/rest/src/meta-plural-i18n.test.tsEvery case asserts the pair: the plural body is translated, AND it
toEquals thesingular body. Asserting only "the plural is translated" would stay green if a later
change translated it differently from the singular, which is precisely the
list-translated / detail-not inconsistency the issue asked not to trade for.
One case per handler exit, against a fixture
zh-CNbundle, plus three controls:widget/widgets) isuntranslated in both spellings — and its document is deliberately named
home, thesame name the bundle translates under
pages, so a fix that keyed on name rather thantype would fail right here;
books→book: a plural that does normalize but whose singular is not atranslator key must still come back untranslated. This separates "folds the spelling"
from "translates whatever it can fold" — a fix that folded the spelling into the
translator lookup while dropping the predicate passes every other case and fails this
one;
normalizing the type did not move the early
extractLocaleexit.One correction I had to make to my own doubles rather than to the code: the real producer
folds the plural to the singular canonical key in the envelope it answers with (#4432), so
a
getMetaItemdouble echoing the raw spelling back failed threetoEquals on thedouble's own artefact. The double now canonicalizes, as the producer does.
Reverse verification — direction predicted first
Prediction: removing the fix turns red exactly the six plural-spelling cases (§1
plural, §2's four exits, §3), and leaves green §1's singular case plus the three §4
controls — because the controls pin behaviour the fix does not change. So 6 failed |
4 passed.
Fix removed with
git checkout origin/main -- packages/rest/src/rest-server.ts(nevergit stash— shared stack), restored withgit applyof the saved patch.Measured: 6 failed | 4 passed, exactly the predicted set, every red being raw English
where the singular spelling got the translation:
All 10 green again after restore.
Verification
Merged
origin/mainbefore the first edit and again before push; #7324 (#6877,query-multiplicity, 24 gate sites in this same file) and #7293 (#7035) are both
ancestors of this branch, and the merge was clean.
TEST_DEBT['@objectstack/rest']was the live hazard — 155 with zero margin. My firstdraft of the test file spent 4 of a margin that does not exist: four
TS2550from.at(-1), this package'slibtarget predating ES2022. Replaced with an indexedlastBodyhelper and re-measured the way the gate does it (a siblingextendsconfigwith the test globs dropped from
exclude, after the full built closure): 155 errors —exactly the ceiling, and zero of them in the new file.
.jsimport extension on the newtest's import (the TS2835 trap).
Wire-visible
Plural
/metaspellings now return localizedlabel/description/navigationwherethey previously returned raw English. No singular-spelling response changes, and no type
becomes translatable that was not already. Changeset added
(
.changeset/meta-plural-spelling-i18n.md, patch).Scope
packages/restonly, as ruled.packages/specuntouched — option 3 (teachingTRANSLATABLE_METADATA_TYPES/isTranslatableMetaTypeto accept plurals) is anacceptance-surface change and belongs to
domain:specwith its own card. No out-of-scopefindings to file.
Generated by Claude Code