fix(fields,i18n): translate AddressField sub-labels, drop US placeholders, localize read-only part order - #4740
Conversation
…ders, localize part order
The five sub-labels ("Street Address", "City", "State / Province", "ZIP /
Postal Code", "Country") were English string literals with no i18n key, so a
non-English console rendered five English words inside an otherwise fully
translated address form and an app had nothing to key a bundle on — the parts
are not fields on the object. They now resolve through `fields.address.*`,
added to all ten locale packs; the `en` values and `FIELD_DEFAULTS` are
byte-identical to the literals, so English and provider-less rendering are
unchanged.
The five input placeholders (`123 Main St`, `San Francisco`, `CA`, `94102`,
`United States`) are removed rather than keyed: the correct example follows the
address's country, not the reader's language, and no channel in the stored
value can supply one today.
The readonly line's part order now follows `useDisplayLocale()` — zh/ja/ko read
largest-first, every other locale keeps the unchanged small-to-large order. The
display cell renderer passes the same locale through the same shared
`formatAddress`, so the two surfaces objectui#4037 unified cannot drift apart.
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
|
PM review — ACCEPT (reviewer of record, session My re-scope was wrong, and the dev was right to override it. The dispatch claimed the five sub-labels were already keyed. They were not — they were still inline JSX literals on Independent verification on the branch:
Body: Generated by Claude Code |
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
Fixes #4028
Premise re-check first — the dispatch's "already fixed" half was not fixed
The dispatch re-scoped this card on the finding that the five sub-labels "no longer exist as literals in
AddressField.tsx— they were keyed by the composite-group-label work". They still existed. Measured onorigin/mainatbaa89a1ea(the tip this branch forks from, and the same tree whose placeholder line numbers the dispatch quotes correctly at L118/133/146/161/174):(The space after each angle bracket is a GitHub-sanitizer workaround, not part of the source.) Inline JSX children, no
t()call, no key — exactly what the issue reports. What the composite-group-label work (objectui#3961 / #3990) did touch is the group container's naming (toHostGroupProps, the hostid/aria-labelledbypair); it never went near the five sub-labels. The literals incomposite-group-label-e2e.test.tsxthat read like "keyed already" are that suite assertinggetByLabelText('Street Address')provider-free — the English default, not a pack lookup.So this PR implements the whole card, not the remainder: the sub-labels are keyed here for the first time, and that is a deviation from the dispatch's scope, taken because the premise excluding them was false and the issue's acceptance criteria include them.
1. Sub-labels are keyed (
fields.address.*)New keys in all ten packs and in
FIELD_DEFAULTS:fields.address.streetfields.address.cityfields.address.statefields.address.postalCodefields.address.countryenvalues are byte-identical to the literals they replace, andFIELD_DEFAULTScarries the same five, so English rendering and provider-less rendering are unchanged (pinned positively inAddressField.no-provider.test.tsx, not merely assumed). Afields.address.*namespace slots into the existingfields.tags.*/fields.textarea.*/fields.options.*shape with no new mechanism.2. Placeholders: DROPPED, not keyed
123 Main St/San Francisco/CA/94102/United Statesare gone. The measurement behind choosing removal over ten-pack keys:name@example.com(local@domain),+1 555 000 0000(dial prefix),Icon name (e.g. Users)(which identifier). Each address box already has a visible label naming exactly what it wants, so its example added nothing but a country — and a US postal code's shape actively misinforms anyone whose own is a different length.countryCode— see the follow-up below), and a UI-language-keyed example is wrong for every cross-border address, which is the CRM case this was reported from. A Japanese example address shown to a zh user entering a US customer's address is the same defect with a different flag on it.check:i18n-drift; each Latin-script one inside zh/ja/ko/ru/ar would additionally need anuntranslated-identity-4376allowlist entry — an explicit written admission that the "translation" is not one.Consistent with the dispatch's own instruction that US example text shown to a zh/ja/ar user is worse than no placeholder. Absence is asserted per locale, because a re-added
placeholder=attribute is invisible to every i18n gate (they only ever look at keys).3. Read-only part order is locale-aware
formatAddress(addr, locale)now has two orders, the second being exactly the first reversed:enand every Latin/Cyrillic/Arabic locale shipped) —Street, City, State ZIP, Country. Unchanged.zh,ja,ko) —Country, ZIP State, City, Street. The reported case:中国, 310000 浙江, 杭州, 中策路 1 号.Only the ORDER varies. The separator stays
', 'in both — not what the issue reports, and these lines carry mixed-script data, where fullwidth punctuation would read stranger than the comma already there. The state/postal pair keeps its single comma group in both directions, and missing parts are still dropped rather than spaced over.AddressCellRenderer: touched, and here is the measurement that says it had to be
The dispatch permits touching it only if it provably shares the formatter and unification is measured. It does, and it is:
formatAddresshas exactly two callers on this branch —AddressField's readonly branch andAddressCellRenderer(grep -rn formatAddress packages/ apps/). Their sharing one definition is not incidental; it is the entire point of objectui#4037, which exists because that rule previously had two spellings.zhconsole would show中国, 310000 浙江, 杭州, 中策路 1 号in a record's readonly form and the US-ordered line in the grid cell beside it — one stored address, two spellings, on two surfaces of one app.The change there is two lines (
useDisplayLocale()plus passing it), using the same resolver every other locale-aware cell renderer in that file already uses. Nothing else about the renderer moves: string /{}/ unrecognized-shape branches are pinned unchanged under azhprovider.Tests
New:
AddressField.i18n.test.tsx(labels under en/zh/ja/ar with an English-literal negative; placeholder absence per locale; readonly order zh vs theesLatin control vsen),AddressField.no-provider.test.tsx(theFIELD_DEFAULTShalf — English, no raw key, unchanged readonly line),AddressCellRenderer.locale-order.test.tsx(the display side, resolved throughgetCellRendererso a registry regression fails it too).Changed:
InlineFieldInput.composite.test.tsxlocated two sub-inputs by placeholder text; re-spelled togetByLabelText. The suite is provider-free, so the labels resolve throughFIELD_DEFAULTS— a more durable handle than the example text, and the one that names each empty box for a real user.Reverse verification (fix committed first, then
git checkout origin/main --the three source files, re-run, restore): 16 failed / 9 passed. The nine that stay green are the intended no-op pins —enlabels, thees/de/enorder controls, and the non-address branches — which is the direction to expect here: this change is designed to leave English and Latin locales byte-identical, so a reverse run that went fully red would have meant the controls were not controlling anything.Gates run locally (at
c22a9a87e, the final commit)vitest packages/fields/vitest packages/i18n/vitestplugin-detail InlineFieldInput composite + delegationtype-checkfields / i18n / plugin-detaillintfields / i18n / plugin-detailcheck-changeset-presence.mjscheck:i18n-keyscheck:i18n-driftcheck:i18n-dead-keyscheck:control-bytescheck:spec-symbols,check:phantom-deps,changeset:check@object-ui/fieldspatch +@object-ui/i18npatch changeset added. (objectui has noskip-changesetmechanism; none was applied.)Follow-up filed
finding, unassigned) — the stated limit of this fix: part order follows the READER's locale becauseAddressFieldwrites nocountryCode, so nothing carries the address's own country. Also records thatde/fr/eswrite the postal code before the city, which the current two-profile rule does not model. The test namedorders a US address by the READER, not by the address — the stated limitexists to make that decision visible rather than silent, and is the pin that must move if Address part order follows the reader's locale because nothing carries the address's own country #4738 is taken.Generated by Claude Code