fix(fields,i18n): key LocationField's residue refusal sentence, answering arity from precedent - #8150
Conversation
…ring arity explicitly objectui#6715 added a THIRD refusal arm to LocationField — a half that is only partly a number (`12abc, 34`) — after objectui#6755's ruling had already keyed the other two. All three arms share one `<p>` and one `refusalError` state, so that single line spoke the reader's language on two arms and English on the third. The arity question the other two sentences never had to answer is answered here rather than defaulted. `verb` was English grammar (`is not a number` / `are not numbers`); a pack whose plural rules differ cannot inflect around an English verb form passed through a hole. So the verb lives inside two SIBLING keys picked at the call site — this repo's own plural convention (`lookup.recordCount`/`recordCountOne` in the same defaults map), not i18next's `_one`/`_other` suffixes, which zh/ja/ko would legitimately omit and `all-locales-key-parity` would read as a missing key. The English conjunction and the coordinate nouns go the same way: `latitude` and `longitude` are keyed once each and interpolated into both arities, leaving only the characters the person typed in holes. English is byte-identical in both arities, so objectui#6715's own strictNumeric pins and plugin-form's ObjectForm.locationResidue suite are untouched. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YBWFb5YgMU5dw8p2VKj16S
pin `builtInLocales` is an `as const` object, so `(builtInLocales as any)[lang]` threw away the one thing that makes a renamed or dropped key fail loudly: with the access typed, `type-check` rejects it at compile time instead of leaving a `toContain` to run against `undefined`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YBWFb5YgMU5dw8p2VKj16S
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
|
Contract review — PASS — PR #8150 (objectui#6888), head Reviewed-by: Diff read by ref against ⭐ The arity answer — verified independently, ⛔ not accepted from the reportThis was the dispatch's binding clause: answer it explicitly from a survey, or stop and report. The report claims a precedent. I re-measured it on ⇒ the ⭐ And the methodological catch is the best thing in this round. The precedent lives in The card's and triage's "11 widgets" is also correctly dismantled rather than repeated or silently replaced: 21 matching files − 7 ① Derived judgmentsClause-②: NO — and I record the one case I actually had to think about rather than asserting it. The narrow reasons are sound: English values byte-identical (
Byte-identity, checked by reading rather than trusting the gate. Old: The ⛔ ② semver
③ Boundary flags — the ratchetOne ratchet moved, and I verified the arithmetic rather than accepting "three German values, three pairs". ⭐ The ablation's self-critique is the sharpest line in the report. Restoring the literal gave 13 failed / 13 passed, and the dev identified which 13 survived: the Lint measured, ⛔ not narrowed: whole repo, Triage's overturn condition considered and correctly not exercised — nothing in #6755's PR, tests or source freezes the tree, and Out-of-scope findings filed, ⛔ not folded in: #8148 ( VerdictPASS. ⛔ Not landing yet — CI unconverged: 32 enumerated against
Generated by Claude Code |
|
Landing — PR #8150 (objectui#6888), head Pre-landing three: ① Contract review PASS (comment 5562082036) at ② CI converged, enumerated 32 against ⭐ The single failure is
③ Ready flipped, auto-merge SQUASH enabled ( ⭐ Landing control established BEFORE the queue picks it up, across all three faces this card touches, so the post-merge confirmation is a reading:
⇒ every zero is a reading on a live file. ⭐ The
Generated by Claude Code |
Fixes #6888
LocationField's THIRD refusal sentence — the residue arm objectui#6715 added — now reaches the locale packs, like the three objectui#6755 keyed.Triage ruled this in-lane execution rather than a new adjudication (comment 5547164710): #6755's 「范围锁定这 3 句」 was a PR-sprawl constraint written 2026-08-29 14:53, and #6715 added this arm afterwards, so it is new terrain rather than excluded terrain. Triage's overturn condition — reading that scope lock as deliberately freezing the tree — was considered and not exercised: nothing in #6755's PR, its tests, or the source comments freezes anything.
LocationField.tsxitself carried the opposite instruction in a comment, naming this card and the open question by number.The defect this removes
All three refusal arms render through the same
pelement and the samerefusalErrorstate (LocationField.tsx, onerefusalError && ...render site). Verified, not assumed. So after #6755 landed, that one line spoke the reader's language when the format or range arm fired and English when the residue arm did — objectui#4028's reported shape compressed into a single sentence position.⭐ The arity question, answered with a survey rather than defaulted
The card, triage and the dispatch all flagged one thing as a real authoring choice: should
named/verbbe interpolation holes, or should the key split per arity? I was told to measure what this channel already does, follow a precedent if one exists and say which widget, and stop and report if there is none.There is a precedent, it is explicit, and it is documented in source with its reason. This repo's plural convention is two sibling keys,
XandXOne, branched at the call site — never i18next's_one/_othersuffixes.lookup.recordCount/lookup.recordCountOneRecordPickerDialog.tsx:1209— the same package and the sameFIELD_DEFAULTSmap this card writes intolist.recordCount/list.recordCountOneplugin-list/src/ListView.tsx:4228detail.reactionCount/detail.reactionCountOneplugin-detail/src/ReactionPicker.tsx:55detail.relatedRecords/detail.relatedRecordOneReactionPicker.tsx:55states the reason in the source:all-locales-key-parity.test.tsadds a second, independent reason (objectui#3863): an i18next plural family also needs a base key, becauseruhas four CLDR categories andarsix, no pack in this repo defines_few/_many/_two/_zero, and those categories silently fall back to English. The sibling pair has neither problem.So: precedent followed,
RecordPickerDialog's. Keys arefields.location.refusedResidue(both halves) andfields.location.refusedResidueOne(one half), picked at the call site.The same reasoning is carried one step further for the parts the card called out as also translatable:
is not a number/are not numbers, chosen in TypeScript. It now lives inside each pack's own two values.aruses its dual —ليسا رقمينfor two halves againstليس رقمًاfor one — which is exactly the form an English verb through a hole could never have produced, and it is pinned by name in the test.' and 'was English grammar joining the halves; each pack now writes its own inside the two-half value.fields.location.latitude/fields.location.longitude— once each, interpolated into both arities, so no locale holds two spellings of the same word. Every pack already spelled them inside its ownrefusedFormat(纬度/经度,широта/долгота,خط العرض/خط الطول), so this is not new translation work.The only holes carrying untranslated data are
{{text}}/{{otherText}}— the characters the person actually typed.Widget population, re-measured with a control
The card and the routing comment both say 11 widgets; the dispatch measured 21 files and told me not to carry the 11 forward. Both numbers are answering different questions, and neither is the count of widgets:
packages/fields/src/widgets/matchuseFieldTranslation— of which 7 are.test.files and 1 isuseFieldTranslation.tsitself, the definition..tsxconsumers remain.fieldWidgetMapregistry, i.e. actually renderable field types:AddressField,FilterConditionField,LocationField,LookupField,ObjectField,ObjectRefField,RecipientPickerField,SelectField,TagsField.CapabilityMultiSelectField,OptionsEmptyState,PeoplePicker,RecordPickerDialog.Controls for that read, in the same pass: grepping the identical corpus for
createSafeTranslationreturns 7 files rather than 21 (so the corpus is not matching everything), andLocationFieldis present in the 21 (so it is not matching nothing). Registry denominator: the map exposes 45 type keys over 42 distinct widget files.⭐ The precedent the arity answer rests on comes from
RecordPickerDialog— one of the four non-registered helpers — so the answer would have been missed by a survey that counted only the 9 registered widgets.Clause-② re-measured, and it agrees with the PM's assessment
check:i18n-driftreports0 en value(s) changed (4 key(s) added, 0 removed)— the gate's own words, not my claim. Both arities are additionally asserted byte-for-byte in the new test, and objectui#6715'sLocationField.strictNumeric.test.tsxandplugin-form'sObjectForm.locationResidue.test.tsxpass untouched.LocationValueSchema, or the refusal decision.parseDraftis unchanged apart fromResidueHalf.labelnarrowing fromstringto theCOORDINATE_LABELSunion — a tightening internal to the module, so a future half cannot silently select the wrong noun key.refusedResidueMessageandcoordinateNameare both module-private.One ratchet moved, deliberately
packages/i18n/src/__tests__/de-quote-pairing-3876.test.tscounts matched„…“spans in thedepack: 59 → 62,rdqstill 0. My three added German values contribute exactly three matched pairs (one in the one-half sentence, two in the two-half one); the two noun keys carry no quotes at all. Updated in the file's own idiom, with the same per-card note every prior entry carries.rdqstaying at 0 is the load-bearing half and it did.Tests
packages/fields/src/__tests__/LocationField.residueI18n-6888.test.tsx(26 tests). Shape inherited fromwidget-diagnostics-i18n-6755.test.tsx: positive and negative assertions together (the English default is the fallback, so "says the Chinese thing" alone cannot tell keyed from hard-coded), plus that file'sAddressFieldzh control so a dead provider cannot read as a pass. Asserts both arities inzh/ja/ar, both arities byte-identical inenand provider-less, the absence of all five English fragments (is not a number,are not numbers,' and ',latitude,longitude), that every pack's two arity values differ in its own language, theardual by name, and that onepstill serves all arms.packages/fieldsfull suite 136 files / 2220 tests;packages/i18n+plugin-form's residue suite +app-shell'sdefaults-maps-mirror-en-pack64 files / 1065 tests;type-checkfor both@object-ui/fieldsand@object-ui/i18n.check:i18n-keys,check:i18n-drift,check:i18n-dead-keys,check:control-bytes,check:unreferenced-sources— all exit 0 on the final head.check:i18n-keysconfirmsevery call site passes exactly the arguments that value has holes for, which is the interpolation contract for the four new keys.en, provider-less and pack-value assertions — which is the point: the English pins are green in both states and could never have caught this, so only the translated assertions can. Mutation proven on disk before reading the result (injected marker count 1, keyed-call count 0, blob hash moved off theHEADblob); restore proven by state rather than exit code (git diff HEADempty, blob hash back to046c63c3, marker count 0).vitestresolves@object-ui/i18nthrough the root config's alias topackages/i18n/src, so the source mutation is what ran.eslint . --no-inline-config --format jsoncompleted over the whole repo at0aa5812ac— 4384 files, the universe read from eslint's own config resolution rather than estimated. My 14 linted files: 0 errors, 3 warnings (twono-explicit-anyfield fixtures matching the sibling pin file's existing pattern, and one pre-existingreact-hooks/set-state-in-effectthat only surfaces because--no-inline-configsuppresses its existingeslint-disable-next-line, in a region my diff does not touch). The repo's 94 errors are all in files this branch does not modify, and the config enables no type-aware linting, so this diff cannot move any untouched file's verdict.Out-of-scope findings, filed unassigned rather than fixed here
badInputMessageis a FIFTH hard-coded English refusal sentence — one literal serving four widgets, wider than the arm objectui#6888 just keyed #8148 —badInputMessageinnumberBadInput.tsxis a fifth hard-coded English refusal sentence, and the widest: one shared literal reachingNumberField,CurrencyField,PercentFieldandGeolocationField.GeolocationFieldsits besideLocationField, so after this PR two adjacent coordinate widgets refuse bad input in two different languages. It has no arity question, so it is the two-line copy this card was not.LocationField's'latitude, longitude'placeholder is a hard-coded English fallback — now sitting directly above a translated refusal sentence #8149 —LocationField's own'latitude, longitude'placeholder fallback is still an English literal, now directly above a refusal that says 纬度 / 经度 in the reader's language. Filed rather than fixed because finding(fields): widget-local diagnostics are hard-coded English while the package already carries a locale channel #6755's ruled principle is about a widget's refusal sentence, and extending it to a placeholder is a fresh judgement.Neither is touched by this PR. objectui#4752 (
@object-ui/components) is untouched as required.Generated by Claude Code