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
{{ message }}
Repository navigation
Console: AddressField sub-labels and placeholders are hardcoded English literals — an address field is untranslatable on any non-English console #4028
Migrated from objectstack-ai/objectstack#5083 under the file-at-destination ruling (objectstack-ai/objectstack#7167, maintainer 2026-08-10). Originally filed 2026-08-04T03:00:07Z. The full prior thread — including triage rulings and hold/restart conditions — remains on the source issue and MUST be read before acting on this card.
Summary
Field.address() renders through the console's AddressField, whose five sub-labels are English string literals with no t() call and no i18n key. On a Chinese console every address field shows five English labels in the middle of an otherwise fully translated form. An app has no way to change them: the labels do not correspond to any authorable field, so there is nothing for a translation bundle to key on.
Evidence
Fresh database, @objectstack/console 17.0.0-rc.1, browser locale zh-CN. The 「地址与位置」 tab of the new-account dialog, measured with getBoundingClientRect() so screen-reader-only text is excluded — every one of these is visibly rendered:
Every other label on that dialog is Chinese — the tab strip, the field labels, the buttons — because those go through objects.<name>.fields.<field>.label in the translation bundle, which works correctly. These five do not.
Mechanism
AddressField in ui-components:
<LabelhtmlFor="street"className="text-xs">Street Address</Label><Inputid="street"placeholder="123 Main St"…/>…<LabelhtmlFor="city"className="text-xs">City</Label><Inputid="city"placeholder="San Francisco"…/>…<LabelhtmlFor="state"className="text-xs">State/Province</Label>
No useTranslation, no key lookup — the strings are inline JSX children.
The placeholders are worse than untranslated, they are US-specific: 123 Main St and San Francisco are shown to a Chinese user entering a Chinese address.
The read-only branch has the same problem in a different form — it joins the parts as street, city, state zipCode, country, which is US address order and produces a wrong-looking address for locales that order it differently.
Why an app cannot work around it
The sub-labels are not fields on the object. billing_address is a single address column; there is no billing_address.street for a bundle to translate.
There is no subLabels / partLabels property on Field.address().
Replacing the widget is not possible from metadata.
The only "workaround" available to us is to stop using Field.address() and author five separate text fields, which loses the structured value, the geocoding, and the map view.
Suggested fix
Route the five labels through the console's own i18n bundle (keys like fields.address.street, .city, .state, .postalCode, .country). The bundle already ships zh-CN, ja-JP, es-ES, de, fr, pt, ru, ar, ko — the translations for these five words are trivial.
Localize or drop the US-specific placeholders.
Make the read-only formatter locale-aware rather than hardcoding US part order.
Dispatched by the repo:objectui PM seat (session session_01RnQd8iMMUwXQEV1crFmQiQ), branch claude/issue-4028-addressfield-locale-remainder — re-scoped: the card is partially fixed since filing.
Premise re-check (origin/main, today). The five sub-labels ("Street Address", "City", …) no longer exist as literals in AddressField.tsx — they were keyed by the composite-group-label work (they now appear only in tests/CHANGELOGs). What remains of the card:
Placeholders are still hardcoded and US-specific: 123 Main St (L118), San Francisco (L133), CA (L146), 94102 (L161), United States (L174).
The read-only branch still joins parts in US order (street, city, state zip, country).
Adjudication. First action: verify and record in the PR body that the sub-label half is already done (with the keys that now serve it). Then fix the remainder. Placeholders: localize via ten-pack keys if a fields.address.*-shaped namespace slots naturally, else drop them — pick by measurement and justify; US example text shown to a zh/ja/ar user is worse than no placeholder. Read-only order: locale-aware part order in the widget's own read-only branch. Scope boundary from the source-thread triage (objectstack#5083, 2026-08-05): this card is the input-side widget (including its read-only branch); the display-side cell renderer family was converged elsewhere — touch AddressCellRenderer only if it provably shares the same formatter and the unification is measured, not speculative.
Suggested route. Tests: per-locale placeholder resolution (or absence) and read-only order for at least zh + one Latin locale. Changeset: fields patch (+ i18n patch if packs gain keys). No skip-changeset mechanism exists in objectui. Note: #4023 is dispatched concurrently and may also touch the ten packs in a different namespace — if the merge queue reports a textual conflict, rebase; do not hand-merge the other card's keys.
{
"issue": 4028,
"branch": "claude/issue-4028-addressfield-locale-remainder",
"pr_number": 4740,
"head_sha": "c22a9a87e9aec287f380eb9a729dca743648006f",
"status": "done",
"premise_still_valid": true,
"premise_note": "The ISSUE's premise holds in full. The DISPATCH's re-scope premise does not: the five sub-labels were still hardcoded English literals on origin/main at baa89a1ea (AddressField.tsx L111/127/140/155/168, inline JSX children, no t() call). The composite-group-label work (objectui#3961/#3990) touched the group CONTAINER's naming only. So all three halves of the card were implemented here, not just the two the dispatch scoped.",
"mechanism_assumptions_checked": [
"DISPATCH ASSUMPTION FALSIFIED — 'the five sub-labels no longer exist as literals': they did. Grepped at the exact tree whose placeholder line numbers the dispatch quotes correctly (L118/133/146/161/174). The 'Street Address' hits that read as keyed-already are composite-group-label-e2e.test.tsx asserting getByLabelText provider-free — the English default, not a pack lookup.",
"CONFIRMED — a fields.address.* namespace slots naturally: fields.tags.* / fields.textarea.* / fields.options.* / fields.objectRef.* already exist in the same block of all ten packs; no new mechanism, no new hook (useFieldTranslation / createSafeTranslation is the repo's one factory for this).",
"CONFIRMED — AddressCellRenderer provably shares the formatter: formatAddress has exactly two callers on this branch (AddressField readonly + AddressCellRenderer), which is objectui#4037's whole purpose. Passing the locale to only one re-forks that rule, so the cell renderer takes the same locale (2 lines, useDisplayLocale — the resolver every other locale-aware cell renderer in that file already uses).",
"MEASURED — placeholder disposition: DROP, not key. (a) every example-data placeholder the packs carry teaches a FORMAT its label cannot (name@example.com, +1 555 000 0000, 'Icon name (e.g. Users)'); the five address boxes already have labels naming exactly what they want. (b) the right example follows the address's country, and this widget writes no countryCode, so no channel in the value can pick one — a UI-language-keyed example is wrong for every cross-border address. (c) keying costs 50 invented fictional address fragments bound by check:i18n-drift, plus untranslated-identity-4376 allowlist entries for the Latin-script ones in zh/ja/ko/ru/ar.",
"CONFIRMED — en no-op: the five en pack values and FIELD_DEFAULTS are byte-identical to the replaced literals, so English and provider-less rendering are unchanged (pinned positively, not assumed).",
"CONCURRENCY (#4023) — the ten packs were touched in the fields.address.* namespace only, inserted after each pack's fields.tags block. No overlap with another namespace; a textual conflict would be resolvable by rebase.",
"FIXTURE TRIAGE — InlineFieldInput.composite.test.tsx used getByPlaceholderText('123 Main St'/'San Francisco') as a LOCATOR for the sub-inputs. Disposition: re-spell to getByLabelText (the suite is provider-free, so FIELD_DEFAULTS English resolves). It pinned the limb that was deleted only as a handle, not as the assertion."
],
"gates": {
"vitest packages/fields/": "PASS — 94 files, 1441 tests",
"vitest packages/i18n/": "PASS — 45 files, 804 tests",
"vitest plugin-detail InlineFieldInput composite+delegation": "PASS — 2 files, 51 tests",
"type-check (fields, i18n, plugin-detail)": "PASS — after pnpm --filter '<pkg>^...' build for the dependency closure (TS6305 without it)",
"lint (fields, i18n, plugin-detail)": "PASS — 0 errors (35/803/793 pre-existing warnings)",
"check-changeset-presence.mjs": "PASS — 1 changeset for 15 changed source files",
"check:i18n-keys": "PASS — 2932 en keys, every call-site key resolves",
"check:i18n-drift": "PASS — 0 en values changed (5 keys added; that is parity's business, not drift's)",
"check:i18n-dead-keys": "PASS (report-only) — the 5 new keys are not among the candidates",
"check:control-bytes": "PASS — 4248 files",
"check:spec-symbols / check:phantom-deps / changeset:check": "PASS",
"reverse_verification": "RED as designed — 16 failed / 9 passed with the three source files reverted to origin/main. The 9 green are the intended no-op pins (en labels, the es/de/en order controls, the non-address branches); a fully red reverse run would have meant the controls were not controlling anything.",
"CI": "in_progress — reported at draft-PR time per the dispatch contract; not waited on"
},
"deviations": [
"SCOPE — implemented the sub-label half the dispatch had excluded as already done. It was not done; the issue's acceptance criteria include it, and leaving it would have shipped a PR closing #4028 with its headline defect intact.",
"SCOPE — touched AddressCellRenderer (2 lines). Permitted by the dispatch's stated condition, and the measurement is in the PR body: formatAddress has exactly two callers and objectui#4037 exists to keep them identical.",
"TEST FIXTURE — one assertion in packages/plugin-detail (a placeholder-based locator) re-spelled to a label-based one. Unavoidable consequence of dropping the placeholders; no behavior of plugin-detail changed."
],
"followups_filed": [
"filed as #4738 (label: finding, unassigned): address part order follows the READER's locale because AddressField writes no countryCode, so nothing carries the address's own country; also records that de/fr/es write the postal code before the city, which the two-profile rule does not model. The test named 'orders a US address by the READER, not by the address — the stated limit' is the pin that must move if it is taken."
]
}
Activity
yinlianghui commentedon Aug 15, 2026
Dispatched by the
repo:objectuiPM seat (sessionsession_01RnQd8iMMUwXQEV1crFmQiQ), branchclaude/issue-4028-addressfield-locale-remainder— re-scoped: the card is partially fixed since filing.Premise re-check (origin/main, today). The five sub-labels ("Street Address", "City", …) no longer exist as literals in
AddressField.tsx— they were keyed by the composite-group-label work (they now appear only in tests/CHANGELOGs). What remains of the card:123 Main St(L118),San Francisco(L133),CA(L146),94102(L161),United States(L174).street, city, state zip, country).Adjudication. First action: verify and record in the PR body that the sub-label half is already done (with the keys that now serve it). Then fix the remainder. Placeholders: localize via ten-pack keys if a
fields.address.*-shaped namespace slots naturally, else drop them — pick by measurement and justify; US example text shown to a zh/ja/ar user is worse than no placeholder. Read-only order: locale-aware part order in the widget's own read-only branch. Scope boundary from the source-thread triage (objectstack#5083, 2026-08-05): this card is the input-side widget (including its read-only branch); the display-side cell renderer family was converged elsewhere — touchAddressCellRendereronly if it provably shares the same formatter and the unification is measured, not speculative.Suggested route. Tests: per-locale placeholder resolution (or absence) and read-only order for at least zh + one Latin locale. Changeset:
fieldspatch (+i18npatch if packs gain keys). No skip-changeset mechanism exists in objectui. Note: #4023 is dispatched concurrently and may also touch the ten packs in a different namespace — if the merge queue reports a textual conflict, rebase; do not hand-merge the other card's keys.Generated by Claude Code
yinlianghui commentedon Aug 15, 2026
{ "issue": 4028, "branch": "claude/issue-4028-addressfield-locale-remainder", "pr_number": 4740, "head_sha": "c22a9a87e9aec287f380eb9a729dca743648006f", "status": "done", "premise_still_valid": true, "premise_note": "The ISSUE's premise holds in full. The DISPATCH's re-scope premise does not: the five sub-labels were still hardcoded English literals on origin/main at baa89a1ea (AddressField.tsx L111/127/140/155/168, inline JSX children, no t() call). The composite-group-label work (objectui#3961/#3990) touched the group CONTAINER's naming only. So all three halves of the card were implemented here, not just the two the dispatch scoped.", "mechanism_assumptions_checked": [ "DISPATCH ASSUMPTION FALSIFIED — 'the five sub-labels no longer exist as literals': they did. Grepped at the exact tree whose placeholder line numbers the dispatch quotes correctly (L118/133/146/161/174). The 'Street Address' hits that read as keyed-already are composite-group-label-e2e.test.tsx asserting getByLabelText provider-free — the English default, not a pack lookup.", "CONFIRMED — a fields.address.* namespace slots naturally: fields.tags.* / fields.textarea.* / fields.options.* / fields.objectRef.* already exist in the same block of all ten packs; no new mechanism, no new hook (useFieldTranslation / createSafeTranslation is the repo's one factory for this).", "CONFIRMED — AddressCellRenderer provably shares the formatter: formatAddress has exactly two callers on this branch (AddressField readonly + AddressCellRenderer), which is objectui#4037's whole purpose. Passing the locale to only one re-forks that rule, so the cell renderer takes the same locale (2 lines, useDisplayLocale — the resolver every other locale-aware cell renderer in that file already uses).", "MEASURED — placeholder disposition: DROP, not key. (a) every example-data placeholder the packs carry teaches a FORMAT its label cannot (name@example.com, +1 555 000 0000, 'Icon name (e.g. Users)'); the five address boxes already have labels naming exactly what they want. (b) the right example follows the address's country, and this widget writes no countryCode, so no channel in the value can pick one — a UI-language-keyed example is wrong for every cross-border address. (c) keying costs 50 invented fictional address fragments bound by check:i18n-drift, plus untranslated-identity-4376 allowlist entries for the Latin-script ones in zh/ja/ko/ru/ar.", "CONFIRMED — en no-op: the five en pack values and FIELD_DEFAULTS are byte-identical to the replaced literals, so English and provider-less rendering are unchanged (pinned positively, not assumed).", "CONCURRENCY (#4023) — the ten packs were touched in the fields.address.* namespace only, inserted after each pack's fields.tags block. No overlap with another namespace; a textual conflict would be resolvable by rebase.", "FIXTURE TRIAGE — InlineFieldInput.composite.test.tsx used getByPlaceholderText('123 Main St'/'San Francisco') as a LOCATOR for the sub-inputs. Disposition: re-spell to getByLabelText (the suite is provider-free, so FIELD_DEFAULTS English resolves). It pinned the limb that was deleted only as a handle, not as the assertion." ], "gates": { "vitest packages/fields/": "PASS — 94 files, 1441 tests", "vitest packages/i18n/": "PASS — 45 files, 804 tests", "vitest plugin-detail InlineFieldInput composite+delegation": "PASS — 2 files, 51 tests", "type-check (fields, i18n, plugin-detail)": "PASS — after pnpm --filter '<pkg>^...' build for the dependency closure (TS6305 without it)", "lint (fields, i18n, plugin-detail)": "PASS — 0 errors (35/803/793 pre-existing warnings)", "check-changeset-presence.mjs": "PASS — 1 changeset for 15 changed source files", "check:i18n-keys": "PASS — 2932 en keys, every call-site key resolves", "check:i18n-drift": "PASS — 0 en values changed (5 keys added; that is parity's business, not drift's)", "check:i18n-dead-keys": "PASS (report-only) — the 5 new keys are not among the candidates", "check:control-bytes": "PASS — 4248 files", "check:spec-symbols / check:phantom-deps / changeset:check": "PASS", "reverse_verification": "RED as designed — 16 failed / 9 passed with the three source files reverted to origin/main. The 9 green are the intended no-op pins (en labels, the es/de/en order controls, the non-address branches); a fully red reverse run would have meant the controls were not controlling anything.", "CI": "in_progress — reported at draft-PR time per the dispatch contract; not waited on" }, "deviations": [ "SCOPE — implemented the sub-label half the dispatch had excluded as already done. It was not done; the issue's acceptance criteria include it, and leaving it would have shipped a PR closing #4028 with its headline defect intact.", "SCOPE — touched AddressCellRenderer (2 lines). Permitted by the dispatch's stated condition, and the measurement is in the PR body: formatAddress has exactly two callers and objectui#4037 exists to keep them identical.", "TEST FIXTURE — one assertion in packages/plugin-detail (a placeholder-based locator) re-spelled to a label-based one. Unavoidable consequence of dropping the placeholders; no behavior of plugin-detail changed." ], "followups_filed": [ "filed as #4738 (label: finding, unassigned): address part order follows the READER's locale because AddressField writes no countryCode, so nothing carries the address's own country; also records that de/fr/es write the postal code before the city, which the two-profile rule does not model. The test named 'orders a US address by the READER, not by the address — the stated limit' is the pin that must move if it is taken." ] }Generated by Claude Code